Spaces:
Sleeping
Sleeping
Download static/script.js from ParleDowntime/Pro: direct link, hf CLI and curl.
- Browser
- Download file 20.1 kB
-
https://huggingface.co/spaces/ParleDowntime/Pro/resolve/main/static/script.js
- Command line
-
hf download hf://spaces/ParleDowntime/Pro/static/script.js
-
curl -L -o script.js https://huggingface.co/spaces/ParleDowntime/Pro/resolve/main/static/script.js
20.1 kB
| /** | |
| * StatusPilot Ultra Pro v5 | |
| * Logic & Infrastructure Interface | |
| */ | |
| let appData = { | |
| status: [], | |
| config: null, | |
| summary: null | |
| }; | |
| let charts = {}; | |
| let currentLocationFilter = 'All'; | |
| let isAuthPromptShowing = false; | |
| // Global Axios Interceptor for 401s | |
| axios.interceptors.response.use( | |
| response => response, | |
| error => { | |
| if (error.response && error.response.status === 401) { | |
| console.warn("Session expired or unauthorized. Showing prompt."); | |
| if (!isAuthPromptShowing) { | |
| showAuthOverlay(); | |
| } | |
| } | |
| return Promise.reject(error); | |
| } | |
| ); | |
| function showAuthOverlay() { | |
| // Only show if not already visible | |
| if (isAuthPromptShowing) return; | |
| isAuthPromptShowing = true; | |
| const overlay = document.getElementById('master-auth-overlay'); | |
| if (overlay) { | |
| overlay.style.display = 'flex'; | |
| overlay.style.zIndex = "999999"; // Ensure it's on top | |
| } | |
| const mainApp = document.getElementById('main-app'); | |
| if (mainApp) { | |
| mainApp.style.filter = 'blur(20px)'; | |
| mainApp.style.opacity = '0.3'; | |
| mainApp.style.pointerEvents = 'none'; | |
| } | |
| } | |
| // Configuration | |
| const REFRESH_INTERVAL = 30000; | |
| const ACCENT_COLOR = '#10b981'; | |
| const PRIMARY_COLOR = '#3b82f6'; | |
| // --- Initialization --- | |
| async function init() { | |
| updateClock(); | |
| setInterval(updateClock, 1000); | |
| const authStatus = await checkAuthStatus(); | |
| if (authStatus.required && !authStatus.authenticated) { | |
| document.getElementById('master-auth-overlay').style.display = 'flex'; | |
| return; | |
| } | |
| unlockDashboard(); | |
| const license = await checkLicense(); | |
| if (!license.licensed) { | |
| document.getElementById('license-overlay').style.display = 'flex'; | |
| return; | |
| } | |
| await loadData(); | |
| setInterval(loadData, REFRESH_INTERVAL); | |
| } | |
| async function loadData() { | |
| try { | |
| await Promise.all([ | |
| loadConfig(), | |
| loadSummary(), | |
| loadStatus() | |
| ]); | |
| } catch (err) { | |
| console.error("Critical Sync Error:", err); | |
| } | |
| } | |
| // --- Auth & Security --- | |
| async function checkAuthStatus() { | |
| try { | |
| const res = await axios.get('/api/auth/status'); | |
| return res.data; | |
| } catch (err) { return { required: true, authenticated: false }; } | |
| } | |
| async function unlockDashboard() { | |
| isAuthPromptShowing = false; | |
| document.getElementById('master-auth-overlay').style.display = 'none'; | |
| const mainApp = document.getElementById('main-app'); | |
| mainApp.style.filter = 'none'; | |
| mainApp.style.opacity = '1'; | |
| mainApp.style.pointerEvents = 'auto'; | |
| } | |
| document.getElementById('master-login-btn').onclick = async () => { | |
| const keyInput = document.getElementById('master-key-input'); | |
| const key = keyInput.value.trim(); | |
| const btn = document.getElementById('master-login-btn'); | |
| const err = document.getElementById('master-error'); | |
| if (!key) return; | |
| btn.innerHTML = '<i class="fas fa-circle-notch fa-spin"></i> SYNCING...'; | |
| btn.disabled = true; | |
| err.style.display = 'none'; | |
| try { | |
| const res = await axios.post('/api/auth/login', { key }); | |
| if (res.data.success) { | |
| console.log("Login successful, verifying session..."); | |
| // CRITICAL: Wait a moment for the cookie to settle | |
| setTimeout(async () => { | |
| const status = await checkAuthStatus(); | |
| if (status.authenticated) { | |
| btn.textContent = 'SYNC SUCCESSFUL'; | |
| unlockDashboard(); | |
| await init(); // Re-run init to load data | |
| } else { | |
| throw new Error("Session verification failed after login."); | |
| } | |
| }, 500); | |
| } else { | |
| err.textContent = "Invalid Master Access Key"; | |
| err.style.display = 'block'; | |
| btn.textContent = 'Unlock Data Center'; | |
| btn.disabled = false; | |
| } | |
| } catch (e) { | |
| console.error(e); | |
| err.textContent = "Security Node Offline or Cookie Blocked"; | |
| err.style.display = 'block'; | |
| btn.textContent = 'Unlock Data Center'; | |
| btn.disabled = false; | |
| } | |
| }; | |
| async function checkLicense() { | |
| try { | |
| const res = await axios.get('/api/license/status'); | |
| return res.data; | |
| } catch (err) { return { licensed: false }; } | |
| } | |
| document.getElementById('activate-btn').onclick = async () => { | |
| const email = document.getElementById('lic-email').value; | |
| const key = document.getElementById('lic-key').value; | |
| try { | |
| const res = await axios.post('/api/license/activate', { email, key: key.trim().toUpperCase() }); | |
| if (res.data.success) init(); | |
| else { | |
| const err = document.getElementById('lic-error'); | |
| err.textContent = res.data.message; | |
| err.style.display = 'block'; | |
| } | |
| } catch (e) { console.error(e); } | |
| }; | |
| // --- Data Fetching --- | |
| async function loadConfig() { | |
| const res = await axios.get('/api/config'); | |
| appData.config = res.data; | |
| // Fill selects | |
| const locFilter = document.getElementById('location-filter'); | |
| const toolSelect = document.getElementById('test-tool-select'); | |
| const locations = new Set(['All', 'Global']); | |
| appData.config.services_to_monitor.forEach(s => { | |
| if(s.location) locations.add(s.location); | |
| }); | |
| const prevLoc = locFilter.value; | |
| locFilter.innerHTML = ''; | |
| locations.forEach(loc => { | |
| const opt = document.createElement('option'); | |
| opt.value = loc; opt.textContent = loc === 'All' ? 'GLOBAL NETWORK' : loc.toUpperCase(); | |
| locFilter.appendChild(opt); | |
| }); | |
| if(prevLoc) locFilter.value = prevLoc; | |
| toolSelect.innerHTML = ''; | |
| appData.config.services_to_monitor.forEach(s => { | |
| const opt = document.createElement('option'); | |
| opt.value = s.name; opt.textContent = s.name; | |
| toolSelect.appendChild(opt); | |
| }); | |
| // Populate Management List | |
| renderManagementList(); | |
| // Settings fields | |
| document.getElementById('check-interval').value = appData.config.check_interval; | |
| document.getElementById('alert-mode').value = appData.config.alert_mode || 'SMTP'; | |
| document.getElementById('smtp-user').value = appData.config.smtp_sender || ''; | |
| } | |
| function renderManagementList() { | |
| const list = document.getElementById('manage-svc-list'); | |
| if (!list) return; | |
| list.innerHTML = ''; | |
| appData.config.services_to_monitor.forEach((s, index) => { | |
| const item = document.createElement('div'); | |
| item.className = 'manage-item'; | |
| item.innerHTML = ` | |
| <div style="display:flex; flex-direction:column;"> | |
| <b style="font-size:0.9rem;">${s.name}</b> | |
| <span style="font-size:0.7rem; color:var(--text-muted);">${s.url}</span> | |
| </div> | |
| <button class="btn-delete" onclick="deleteService(${index}, '${s.name}')"> | |
| <i class="fas fa-trash-alt"></i> REMOVE | |
| </button> | |
| `; | |
| list.appendChild(item); | |
| }); | |
| } | |
| async function deleteService(index, name) { | |
| if (!confirm(`🚨 DECOMMISSION NODE: Are you sure you want to remove ${name} from active monitoring?`)) return; | |
| try { | |
| // Switching to POST for decommissioning to avoid DELETE method blocks in cloud proxies | |
| const res = await axios.post('/api/config/delete', { name }); | |
| if (res.data.status === 'success') { | |
| console.log(`Node ${name} successfully decommissioned.`); | |
| await loadConfig(); | |
| await loadStatus(); | |
| } else { | |
| throw new Error(res.data.message || "Unknown server error"); | |
| } | |
| } catch (e) { | |
| console.error(e); | |
| const msg = e.response?.data?.message || e.message; | |
| alert(`OPERATIONAL ERROR: ${msg}\n\nPlease ensure you are logged in and the node still exists.`); | |
| } | |
| } | |
| async function loadSummary() { | |
| const res = await axios.get('/api/summary'); | |
| const s = res.data; | |
| animateValue('stat-monitors', s.total_monitors); | |
| animateValue('stat-uptime', s.uptime_pct, '%'); | |
| animateValue('stat-incidents', s.incidents); | |
| animateValue('stat-latency', s.avg_latency, 'ms'); | |
| } | |
| async function loadStatus() { | |
| const res = await axios.get('/api/status'); | |
| appData.status = res.data; | |
| renderStatusGrid(); | |
| renderGlobalMatrix(); | |
| } | |
| // --- UI Rendering --- | |
| function renderStatusGrid() { | |
| const grid = document.getElementById('status-grid'); | |
| grid.innerHTML = ''; | |
| const filtered = appData.status.filter(s => currentLocationFilter === 'All' || s.location === currentLocationFilter); | |
| if (filtered.length === 0) { | |
| grid.innerHTML = '<div style="text-align:center; padding:3rem; color:var(--text-muted);">No services matching filter.</div>'; | |
| return; | |
| } | |
| filtered.forEach(s => { | |
| const row = document.createElement('div'); | |
| row.className = 'status-row'; | |
| row.onclick = () => openDetails(s.name); | |
| let timelineHTML = '<div class="uptime-timeline">'; | |
| const history = [...Array(30 - s.timeline.length).fill(null), ...s.timeline]; | |
| history.forEach(state => { | |
| const cls = state === null ? 'none' : (state ? 'up' : 'down'); | |
| timelineHTML += `<div class="timeline-bar ${cls}"></div>`; | |
| }); | |
| timelineHTML += '</div>'; | |
| row.innerHTML = ` | |
| <div class="svc-info"> | |
| <h3>${s.name}</h3> | |
| <span>${s.url}</span> | |
| </div> | |
| <div><span class="location-badge">${s.location}</span></div> | |
| ${timelineHTML} | |
| <div class="status-text ${s.is_up ? 'up' : 'down'}">${s.is_up ? 'Operational' : 'Major Outage'}</div> | |
| <div class="latency">${s.latency}ms</div> | |
| `; | |
| grid.appendChild(row); | |
| }); | |
| } | |
| function renderGlobalMatrix() { | |
| const ctx = document.getElementById('globalLatencyMatrix'); | |
| if (!ctx) return; | |
| const labels = appData.status.map(s => s.name); | |
| const data = appData.status.map(s => s.latency); | |
| if (charts.globalMatrix) charts.globalMatrix.destroy(); | |
| charts.globalMatrix = new Chart(ctx, { | |
| type: 'bar', | |
| data: { | |
| labels: labels, | |
| datasets: [{ | |
| label: 'Latency (ms)', | |
| data: data, | |
| backgroundColor: data.map(v => v > 500 ? 'rgba(239, 68, 68, 0.4)' : 'rgba(59, 130, 246, 0.4)'), | |
| borderColor: data.map(v => v > 500 ? '#ef4444' : '#3b82f6'), | |
| borderWidth: 1, | |
| borderRadius: 8 | |
| }] | |
| }, | |
| options: { | |
| indexAxis: 'y', | |
| responsive: true, | |
| maintainAspectRatio: false, | |
| plugins: { legend: { display: false } }, | |
| scales: { | |
| x: { grid: { color: 'rgba(255,255,255,0.05)' }, ticks: { color: '#94a3b8' } }, | |
| y: { grid: { display: false }, ticks: { color: '#fff', font: { weight: '600' } } } | |
| } | |
| } | |
| }); | |
| } | |
| // --- Analytics Modal --- | |
| async function openDetails(name) { | |
| document.getElementById('modal-title').textContent = name; | |
| document.getElementById('matrix-modal').style.display = 'block'; | |
| try { | |
| const res = await axios.get(`/api/history/${encodeURIComponent(name)}`); | |
| const history = res.data; | |
| renderMatrixCharts(history); | |
| renderIncidentLog(history); | |
| } catch (e) { console.error(e); } | |
| } | |
| function renderMatrixCharts(data) { | |
| const labels = data.map(d => new Date(d.timestamp).toLocaleTimeString()); | |
| const latencies = data.map(d => d.latency); | |
| const uptimes = data.map(d => d.is_up ? 100 : 0); | |
| if (charts.detLat) charts.detLat.destroy(); | |
| if (charts.detUp) charts.detUp.destroy(); | |
| if (charts.detErr) charts.detErr.destroy(); | |
| const chartOps = { | |
| responsive: true, | |
| plugins: { legend: { display: false } }, | |
| scales: { | |
| x: { grid: { display: false }, ticks: { display: false } }, | |
| y: { grid: { color: 'rgba(255,255,255,0.05)' }, ticks: { color: '#94a3b8' } } | |
| } | |
| }; | |
| charts.detLat = new Chart(document.getElementById('latencyChart'), { | |
| type: 'line', | |
| data: { | |
| labels: labels, | |
| datasets: [{ | |
| data: latencies, | |
| borderColor: PRIMARY_COLOR, | |
| backgroundColor: 'rgba(59, 130, 246, 0.1)', | |
| fill: true, | |
| tension: 0.4, | |
| pointRadius: 0 | |
| }] | |
| }, | |
| options: chartOps | |
| }); | |
| charts.detUp = new Chart(document.getElementById('uptimeChart'), { | |
| type: 'line', | |
| data: { | |
| labels: labels, | |
| datasets: [{ | |
| data: uptimes, | |
| borderColor: ACCENT_COLOR, | |
| backgroundColor: 'rgba(16, 185, 129, 0.1)', | |
| fill: true, | |
| stepped: true, | |
| pointRadius: 0 | |
| }] | |
| }, | |
| options: { ...chartOps, scales: { ...chartOps.scales, y: { ...chartOps.scales.y, max: 100, min: 0 } } } | |
| }); | |
| charts.detErr = new Chart(document.getElementById('errorChart'), { | |
| type: 'doughnut', | |
| data: { | |
| labels: ['Success', 'Failure'], | |
| datasets: [{ | |
| data: [data.filter(d => d.is_up).length, data.filter(d => !d.is_up).length], | |
| backgroundColor: [ACCENT_COLOR, '#ef4444'], | |
| borderWidth: 0 | |
| }] | |
| }, | |
| options: { cutout: '70%', plugins: { legend: { position: 'bottom', labels: { color: '#94a3b8' } } } } | |
| }); | |
| } | |
| function renderIncidentLog(data) { | |
| const log = document.getElementById('incident-log'); | |
| log.innerHTML = ''; | |
| const fails = data.filter(d => !d.is_up).reverse(); | |
| if (fails.length === 0) { | |
| log.innerHTML = '<p style="color: var(--accent); font-weight: 600;">System is healthy. No recent incidents.</p>'; | |
| return; | |
| } | |
| fails.forEach(f => { | |
| const item = document.createElement('div'); | |
| item.style.padding = '10px 0'; | |
| item.style.borderBottom = '1px solid var(--border)'; | |
| item.innerHTML = `<span style="color:#ef4444; font-weight:700;">DOWN</span> <span style="margin-left:10px;">${new Date(f.timestamp).toLocaleString()}</span>`; | |
| log.appendChild(item); | |
| }); | |
| } | |
| // --- Helpers & Utilities --- | |
| function animateValue(id, value, suffix = '') { | |
| const el = document.getElementById(id); | |
| const start = parseInt(el.textContent) || 0; | |
| const duration = 1000; | |
| const startTime = performance.now(); | |
| function update(now) { | |
| const elapsed = now - startTime; | |
| const progress = Math.min(elapsed / duration, 1); | |
| const current = start + (value - start) * progress; | |
| if (suffix === '%') el.textContent = current.toFixed(1) + suffix; | |
| else if (suffix === 'ms') el.textContent = Math.round(current) + suffix; | |
| else el.textContent = Math.round(current); | |
| if (progress < 1) requestAnimationFrame(update); | |
| } | |
| requestAnimationFrame(update); | |
| } | |
| function updateClock() { | |
| const el = document.getElementById('live-clock'); | |
| const now = new Date(); | |
| el.textContent = now.toLocaleTimeString() + ' | ' + now.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' }); | |
| } | |
| function showToast(msg, type = 'success') { | |
| // Simple alert for now, can be upgraded to toast | |
| console.log(`[${type.toUpperCase()}] ${msg}`); | |
| } | |
| // --- Event Handlers --- | |
| document.querySelectorAll('.nav-item').forEach(item => { | |
| item.onclick = (e) => { | |
| e.preventDefault(); | |
| document.querySelectorAll('.nav-item').forEach(n => n.classList.remove('active')); | |
| item.classList.add('active'); | |
| const target = item.id.replace('nav-', 'section-'); | |
| document.querySelectorAll('.content-section').forEach(s => s.style.display = 'none'); | |
| document.getElementById(target).style.display = 'block'; | |
| }; | |
| }); | |
| document.getElementById('location-filter').onchange = (e) => { | |
| currentLocationFilter = e.target.value; | |
| renderStatusGrid(); | |
| }; | |
| document.querySelector('.close-modal').onclick = () => { | |
| document.getElementById('matrix-modal').style.display = 'none'; | |
| }; | |
| document.getElementById('purge-stale-btn').onclick = async () => { | |
| if (!confirm("🚨 WARNING: This will permanently delete history for services no longer in your config. Proceed?")) return; | |
| try { | |
| const res = await axios.post('/api/admin/purge_stale'); | |
| alert(`Cleanup complete! Purged ${res.data.purged_records} records.`); | |
| loadData(); | |
| } catch (e) { alert("Failed to purge stale data."); } | |
| }; | |
| document.getElementById('save-settings-btn').onclick = async () => { | |
| const config = { ...appData.config }; | |
| config.check_interval = parseInt(document.getElementById('check-interval').value); | |
| config.alert_mode = document.getElementById('alert-mode').value; | |
| config.smtp_sender = document.getElementById('smtp-user').value; | |
| config.smtp_password = document.getElementById('smtp-pass').value; | |
| try { | |
| await axios.post('/api/config', config); | |
| alert("Configuration updated successfully!"); | |
| loadData(); | |
| } catch (e) { alert("Failed to save configuration."); } | |
| }; | |
| document.getElementById('add-svc-btn').onclick = async () => { | |
| const name = document.getElementById('new-svc-name').value.trim(); | |
| const url = document.getElementById('new-svc-url').value.trim(); | |
| const loc = document.getElementById('new-svc-loc').value.trim() || 'Global'; | |
| const type = document.getElementById('new-svc-type').value; | |
| if (!name || !url) return alert("Service Name and Resource URL are required."); | |
| const config = { ...appData.config }; | |
| const newService = { | |
| name, | |
| url, | |
| location: loc, | |
| type: type | |
| }; | |
| // Add specific fields based on type | |
| if (type === 'http_code') newService.expected_code = 200; | |
| if (type === 'json_status') { | |
| newService.key_to_check = 'status'; | |
| newService.expected_value = 'up'; | |
| } | |
| config.services_to_monitor.push(newService); | |
| try { | |
| await axios.post('/api/config', config); | |
| alert(`Node Activated: ${name}`); | |
| document.getElementById('new-svc-name').value = ''; | |
| document.getElementById('new-svc-url').value = ''; | |
| loadConfig(); | |
| loadStatus(); | |
| } catch (e) { alert("Failed to provision new service node."); } | |
| }; | |
| document.getElementById('run-test-btn').onclick = async () => { | |
| const tool = document.getElementById('test-tool-select').value; | |
| const location = document.getElementById('test-location-select').value; | |
| const results = document.getElementById('test-results'); | |
| results.innerHTML = '<div style="text-align:center; padding:2rem;"><i class="fas fa-circle-notch fa-spin"></i> Pinging remote node...</div>'; | |
| try { | |
| const res = await axios.post('/api/test', { tool, location }); | |
| const d = res.data; | |
| results.innerHTML = ` | |
| <div class="glass-panel" style="border-color: ${d.is_up ? 'var(--accent)' : 'var(--danger)'}"> | |
| <h3 style="color: ${d.is_up ? 'var(--accent)' : 'var(--danger)'}">${d.is_up ? 'TRACEROUTE SUCCESSFUL' : 'NODE UNREACHABLE'}</h3> | |
| <div style="margin-top: 1rem; display: grid; grid-template-columns: 1fr 1fr; gap: 10px;"> | |
| <div><b>Latency:</b> ${d.latency}ms</div> | |
| <div><b>Location:</b> ${d.location}</div> | |
| <div style="grid-column: 1/-1;"><b>Response:</b> ${d.message}</div> | |
| </div> | |
| </div> | |
| `; | |
| } catch (e) { results.innerHTML = '<div class="glass-panel" style="color:var(--danger)">Diagnostic timeout. Node might be offline.</div>'; } | |
| }; | |
| document.getElementById('download-excel-btn').onclick = () => { | |
| window.location.href = '/api/report/excel'; | |
| }; | |
| // Start | |
| init(); | |