/** * 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 = ' 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 = `
System is healthy. No recent incidents.
'; return; } fails.forEach(f => { const item = document.createElement('div'); item.style.padding = '10px 0'; item.style.borderBottom = '1px solid var(--border)'; item.innerHTML = `DOWN ${new Date(f.timestamp).toLocaleString()}`; 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 = '