/** * StatusPilot Ultra v6 — Fusion Engine * Best of all 3 dashboards combined */ let appData = { status: [], config: null }; let charts = {}; let currentLocationFilter = 'All'; let searchQuery = ''; let isAuthPromptShowing = false; // --- Axios 401 Interceptor --- axios.interceptors.response.use(r => r, error => { if (error.response && error.response.status === 401 && !isAuthPromptShowing) { isAuthPromptShowing = true; document.getElementById('master-auth-overlay').style.display = 'flex'; } return Promise.reject(error); }); // --- Init --- async function init() { updateClock(); setInterval(updateClock, 1000); const auth = await checkAuth(); if (auth.required && !auth.authenticated) { document.getElementById('master-auth-overlay').style.display = 'flex'; return; } unlock(); const lic = await checkLicense(); if (!lic.licensed) { document.getElementById('license-overlay').style.display = 'flex'; return; } await loadData(); setInterval(loadData, 30000); } async function loadData() { try { await Promise.all([loadConfig(), loadSummary(), loadStatus()]); } catch (e) { console.error("Sync:", e); } } async function checkAuth() { try { return (await axios.get('/api/auth/status')).data; } catch { return { required: true, authenticated: false }; } } function unlock() { isAuthPromptShowing = false; document.getElementById('master-auth-overlay').style.display = 'none'; const app = document.getElementById('main-app'); app.style.filter = 'none'; app.style.opacity = '1'; app.style.pointerEvents = 'auto'; } document.getElementById('master-login-btn').onclick = async () => { const key = document.getElementById('master-key-input').value.trim(); if (!key) return; try { const res = await axios.post('/api/auth/login', { key }); if (res.data.success) { setTimeout(async () => { const s = await checkAuth(); if (s.authenticated) { unlock(); await init(); } }, 500); } else { alert("Invalid Master Key"); } } catch { alert("Auth Node Offline"); } }; async function checkLicense() { try { return (await axios.get('/api/license/status')).data; } catch { return { licensed: false }; } } // --- Config --- async function loadConfig() { const res = await axios.get('/api/config'); appData.config = res.data; const locFilter = document.getElementById('location-filter'); const toolSelect = document.getElementById('test-tool-select'); const locs = new Set(['All', 'Global']); appData.config.services_to_monitor.forEach(s => { if(s.location) locs.add(s.location); }); const prev = locFilter.value; locFilter.innerHTML = ''; locs.forEach(l => { const o = document.createElement('option'); o.value = l; o.textContent = l === 'All' ? 'All Regions' : l; locFilter.appendChild(o); }); if (prev) locFilter.value = prev; toolSelect.innerHTML = ''; appData.config.services_to_monitor.forEach(s => { const o = document.createElement('option'); o.value = s.name; o.textContent = s.name; toolSelect.appendChild(o); }); renderManageList(); 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 renderManageList() { const list = document.getElementById('manage-svc-list'); if (!list) return; list.innerHTML = ''; appData.config.services_to_monitor.forEach(s => { const item = document.createElement('div'); item.className = 'manage-item'; item.innerHTML = `
${s.name}
${s.url}
`; list.appendChild(item); }); list.querySelectorAll('.btn-remove').forEach(btn => { btn.onclick = (e) => { const t = e.target.closest('.btn-remove'); if (t) deleteService(t.getAttribute('data-name')); }; }); } async function deleteService(name) { try { const res = await axios.post('/api/config/delete', { name }); if (res.data.status === 'success') { await loadConfig(); await loadStatus(); } else alert("Error: " + res.data.message); } catch (e) { alert("Delete failed."); } } // --- Summary --- async function loadSummary() { const s = (await axios.get('/api/summary')).data; document.getElementById('stat-monitors').textContent = s.total_monitors; document.getElementById('stat-uptime').textContent = s.uptime_pct + '%'; document.getElementById('stat-incidents').textContent = s.incidents; document.getElementById('stat-latency').textContent = s.avg_latency + 'ms'; } // --- Status --- async function loadStatus() { appData.status = (await axios.get('/api/status')).data; renderStatusGrid(); renderLatencyMatrix(); } function renderLatencyMatrix() { const container = document.getElementById('latency-matrix-bars'); if (!container) return; container.innerHTML = ''; const sorted = [...appData.status].sort((a, b) => b.latency - a.latency).slice(0, 8); const max = Math.max(...sorted.map(s => s.latency), 100); sorted.forEach(s => { const pct = Math.min((s.latency / max) * 100, 100); const row = document.createElement('div'); row.className = 'latency-row'; row.innerHTML = ` ${s.name}
${s.latency}ms `; container.appendChild(row); }); } function renderStatusGrid() { const grid = document.getElementById('status-grid'); grid.innerHTML = ''; const filtered = appData.status.filter(s => { const locOk = currentLocationFilter === 'All' || s.location === currentLocationFilter; const searchOk = !searchQuery || s.name.toLowerCase().includes(searchQuery.toLowerCase()); return locOk && searchOk; }); filtered.forEach(s => { const row = document.createElement('div'); row.className = 'svc-row'; row.onclick = () => openDetails(s.name); let barsHTML = '
'; const history = [...Array(Math.max(0, 30 - s.timeline.length)).fill(null), ...s.timeline]; history.forEach(state => { const cls = state === null ? 'none' : (state ? 'up' : 'down'); barsHTML += `
`; }); barsHTML += '
'; const scopeClass = s.location === 'Security Node' ? 'security' : (s.location === 'Primary Web' ? 'web' : 'global'); row.innerHTML = `
${s.name}
${s.url}
${s.location}
${barsHTML}
${s.is_up ? 'ONLINE' : 'DOWN'}
${s.latency}ms
`; grid.appendChild(row); }); } // --- Modal --- async function openDetails(name) { document.getElementById('modal-title').textContent = name; document.getElementById('modal-subtitle').textContent = `Historical performance data for ${name}`; document.getElementById('matrix-modal').classList.add('open'); try { const res = await axios.get(`/api/history/${encodeURIComponent(name)}`); renderModalCharts(res.data); } catch (e) { console.error(e); } } function renderModalCharts(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 base = { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: false } }, scales: { x: { display: false }, y: { grid: { color: 'rgba(255,255,255,0.04)' }, ticks: { color: '#64748b', font: { size: 9 } } } } }; charts.detUp = new Chart(document.getElementById('uptimeChart'), { type: 'line', data: { labels, datasets: [{ data: uptimes, borderColor: '#10b981', backgroundColor: 'rgba(16,185,129,0.08)', fill: true, stepped: true, pointRadius: 0, borderWidth: 1.5 }] }, options: { ...base, scales: { ...base.scales, y: { ...base.scales.y, max: 105, min: -5 } } } }); charts.detLat = new Chart(document.getElementById('latencyChart'), { type: 'line', data: { labels, datasets: [{ data: latencies, borderColor: '#0ea5e9', backgroundColor: 'rgba(14,165,233,0.08)', fill: true, tension: 0.4, pointRadius: 0, borderWidth: 1.5 }] }, options: base }); const upCount = data.filter(d => d.is_up).length; const downCount = data.filter(d => !d.is_up).length; charts.detErr = new Chart(document.getElementById('errorChart'), { type: 'doughnut', data: { labels: ['Operational', 'Issues'], datasets: [{ data: [upCount, downCount], backgroundColor: ['#10b981', '#1e293b'], borderWidth: 0 }] }, options: { cutout: '75%', plugins: { legend: { position: 'bottom', labels: { color: '#64748b', padding: 12, font: { size: 10 } } } } } }); renderIncidents(data); } function renderIncidents(data) { const log = document.getElementById('incident-log'); if (!log) return; log.innerHTML = ''; const fails = data.filter(d => !d.is_up).reverse().slice(0, 10); if (fails.length === 0) { log.innerHTML = '

✓ SYSTEM IS NOMINAL

'; return; } fails.forEach(f => { const el = document.createElement('div'); el.style.cssText = 'display:flex; justify-content:space-between; align-items:center; padding:6px 10px; margin-bottom:4px; background:rgba(244,63,94,0.06); border-left:3px solid var(--danger); border-radius:4px;'; const msg = f.message || "Connection Error"; el.innerHTML = `
Outage
${msg}
${new Date(f.timestamp).toLocaleTimeString()}`; log.appendChild(el); }); } function updateClock() { document.getElementById('live-clock').textContent = new Date().toLocaleTimeString(); } // --- Event Handlers --- // Modal close document.getElementById('modal-close-btn').onclick = () => document.getElementById('matrix-modal').classList.remove('open'); document.getElementById('matrix-modal').onclick = (e) => { if (e.target === document.getElementById('matrix-modal')) document.getElementById('matrix-modal').classList.remove('open'); }; // Navigation document.querySelectorAll('.sidebar-nav a').forEach(item => { item.onclick = (e) => { e.preventDefault(); document.querySelectorAll('.sidebar-nav a').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'; }; }); // Location filter document.getElementById('location-filter').onchange = (e) => { currentLocationFilter = e.target.value; renderStatusGrid(); }; // Search document.getElementById('search-services').oninput = (e) => { searchQuery = e.target.value; renderStatusGrid(); }; // M365 document.getElementById('activate-m365-btn').onclick = async () => { const sender = document.getElementById('m365-sender').value.trim(); const pass = document.getElementById('m365-pass').value.trim(); const recipient = document.getElementById('m365-recipient').value.trim(); if (!sender || !pass || !recipient) return alert("All M365 fields required."); const config = { ...appData.config }; config.services_to_monitor.push({ name: "M365 Mail Flow", url: "https://outlook.office365.com", location: "Global", type: "m365_mail", m365_sender: sender, m365_password: pass, m365_recipient: recipient }); try { await axios.post('/api/config', config); alert("M365 Monitor Activated!"); loadConfig(); } catch { alert("Failed to activate."); } }; // Add service 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("Name and URL required."); const config = { ...appData.config }; config.services_to_monitor.push({ name, url, location: loc, type }); try { await axios.post('/api/config', config); alert("Node Provisioned!"); document.getElementById('new-svc-name').value = ''; document.getElementById('new-svc-url').value = ''; loadConfig(); loadStatus(); } catch { alert("Error."); } }; // Save settings 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("Config Saved!"); loadConfig(); } catch { alert("Save failed."); } }; // Purge const purgeBtn = document.getElementById('purge-stale-btn'); if (purgeBtn) purgeBtn.onclick = async () => { try { const r = await axios.post('/api/admin/purge_stale'); alert(`Purged ${r.data.purged_records} records.`); loadData(); } catch { alert("Purge failed."); } }; // Export const exportBtn = document.getElementById('download-excel-btn'); if (exportBtn) exportBtn.onclick = () => window.location.href = '/api/report/excel'; init();