/** * StatusPilot Ultra v6 Engine */ let appData = { status: [], config: null }; let currentFilter = 'all'; let sortKey = 'latency'; let sortDir = 'desc'; let isAuthPromptShowing = false; let trendData = []; let mapNodes = []; // --- Axios 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); }); // --- THEME ENGINE --- const THEMES = [ { id: 'dark', label: 'Dark Pro', icon: '🌙' }, { id: 'light', label: 'Light Mode', icon: '☀️' }, { id: 'cyber', label: 'Cyber Pulse', icon: '⚡' } ]; function initTheme() { const saved = localStorage.getItem('statuspilot-theme') || 'dark'; applyTheme(saved); } function applyTheme(themeId) { const t = THEMES.find(x => x.id === themeId) || THEMES[0]; if (t.id === 'dark') { document.documentElement.removeAttribute('data-theme'); } else { document.documentElement.setAttribute('data-theme', t.id); } localStorage.setItem('statuspilot-theme', t.id); const iconEl = document.getElementById('theme-icon'); const labelEl = document.getElementById('theme-label'); if (iconEl) iconEl.textContent = t.icon; if (labelEl) labelEl.textContent = t.label; } function cycleTheme() { const current = localStorage.getItem('statuspilot-theme') || 'dark'; const idx = THEMES.findIndex(x => x.id === current); const nextTheme = THEMES[(idx + 1) % THEMES.length]; applyTheme(nextTheme.id); toast('info', 'Theme Switch', `Activated ${nextTheme.label} dashboard.`); } // --- INIT --- window.addEventListener('load', async () => { initTheme(); setTimeout(() => { document.getElementById('loader').classList.add('hide'); }, 1500); initClock(); 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); initTrendChart(); }); // --- CLOCK --- function initClock() { function tick() { const sel = document.getElementById('tzSel'); const tz = sel ? sel.value : 'UTC'; const label = sel ? sel.options[sel.selectedIndex].dataset.label : 'UTC'; const d = new Date(); const timeStr = d.toLocaleTimeString('en-US', { timeZone: tz, hour12: false, hour:'2-digit', minute:'2-digit', second:'2-digit' }); document.getElementById('clock').textContent = timeStr + ' ' + label; } tick(); setInterval(tick, 1000); } // --- AUTH / LICENSE --- 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(); window.location.reload(); } }, 500); } else { toast('err', 'Access Denied', 'Invalid Master Key'); } } catch { toast('err', 'Auth Error', 'Node Offline'); } }; async function checkLicense() { try { return (await axios.get('/api/license/status')).data; } catch { return { licensed: false }; } } // --- LOAD DATA --- async function loadData() { try { await Promise.all([loadConfig(), loadSummary(), loadStatus()]); } catch (e) { console.error("Sync Error:", e); } } async function loadConfig() { const res = await axios.get('/api/config'); appData.config = res.data; // Filters and Selects const locFilter = document.getElementById('regionSel'); 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 => { if(l !== 'all') { const o = document.createElement('option'); o.value = l; o.textContent = 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); }); // Populate Operations Forms renderManageList(); document.getElementById('check-interval').value = appData.config.check_interval || 60; document.getElementById('alert-mode').value = appData.config.alert_mode || 'SMTP'; document.getElementById('smtp-user').value = appData.config.smtp_sender || ''; document.getElementById('smtp-pass').value = appData.config.smtp_password ? '••••••••••••' : ''; const recs = appData.config.email_recipients || []; const recsEl = document.getElementById('email-recipients') || document.getElementById('alert-recipients'); if (recsEl) recsEl.value = Array.isArray(recs) ? recs.join(', ') : recs; const whEl = document.getElementById('email-webhook-url'); if (whEl) { // Mask the webhook URL - show only status, never expose the real URL to UI whEl.value = appData.config.email_webhook_url ? '••• Configured (Secured) •••' : ''; whEl.disabled = true; whEl.style.color = '#888'; } } async function loadSummary() { const s = (await axios.get('/api/summary')).data; document.getElementById('kpi-mon').textContent = s.total_monitors; // Uptime document.getElementById('kpi-up').textContent = s.uptime_pct + '%'; const upDelta = document.getElementById('kpi-up-delta'); if (s.uptime_pct >= 99) { upDelta.className = 'kpi-delta up'; upDelta.textContent = '↑ Optimal'; } else { upDelta.className = 'kpi-delta down'; upDelta.textContent = '↓ Below Target'; } document.getElementById('kpi-up-bar').style.width = s.uptime_pct + '%'; // Incidents document.getElementById('kpi-inc').textContent = s.incidents; const incCard = document.getElementById('kpi-inc-card'); const incDesc = document.getElementById('kpi-inc-desc'); const incBar = document.getElementById('kpi-inc-bar'); if (s.incidents > 0) { incCard.style.setProperty('--ac', 'var(--red)'); incDesc.className = 'kpi-delta down'; incDesc.textContent = s.incidents + ' Systems Down'; incBar.style.background = 'var(--red)'; document.getElementById('kpi-inc').style.setProperty('--vc', 'var(--red)'); document.getElementById('inc-badge').textContent = s.incidents + ' OPEN'; } else { incCard.style.setProperty('--ac', 'var(--green)'); incDesc.className = 'kpi-delta up'; incDesc.textContent = 'All operational'; incBar.style.background = 'var(--green)'; document.getElementById('kpi-inc').style.setProperty('--vc', 'var(--green)'); document.getElementById('inc-badge').textContent = '0 OPEN'; } // Latency document.getElementById('kpi-lat').textContent = s.avg_latency + 'ms'; } async function loadStatus() { appData.status = (await axios.get('/api/status')).data; renderTable(); renderLatMatrix(); renderIncidents(); buildHeatmap(); // Setup World Map Nodes based on status mapNodes = [ {label:'US East (N.Va)', x:75, y:55, status: getNodeStatus('US') }, {label:'US West (CA)', x:45, y:60, status: getNodeStatus('Global') }, {label:'EU Central (FRA)',x:160,y:48, status: getNodeStatus('EU') }, {label:'UK (London)', x:145,y:44, status: getNodeStatus('Primary') }, {label:'Asia-Pacific', x:260,y:65, status: 'ok' }, {label:'South America', x:105,y:115,status: 'ok' } ]; renderWorldMap(); } function getNodeStatus(locMatch) { const nodes = appData.status.filter(s => s.location && s.location.includes(locMatch)); if (!nodes.length) return 'ok'; if (nodes.some(s => !s.is_up)) return 'down'; if (nodes.some(s => s.latency > 400)) return 'warn'; return 'ok'; } // --- RENDERERS --- function slaColor(pct) { if(pct >= 99.5) return 'var(--green)'; if(pct >= 95) return 'var(--amber)'; return 'var(--red)'; } function renderTable() { const q = (document.getElementById('searchBox')||{}).value||''; const locFilt = document.getElementById('regionSel').value; let list = [...appData.status]; // Region Filter if (locFilt !== 'all') list = list.filter(s => s.location === locFilt); // Status Filter if(currentFilter !== 'all') { if (currentFilter === 'down') list = list.filter(s => !s.is_up); else if (currentFilter === 'online') list = list.filter(s => s.is_up); } // Text Search if(q) list = list.filter(s => s.name.toLowerCase().includes(q.toLowerCase()) || s.url.toLowerCase().includes(q.toLowerCase())); // Sorting list.sort((a,b) => { let av = a[sortKey] || 0, bv = b[sortKey] || 0; if (sortKey === 'name' || sortKey === 'scope') { av = a.name; bv = b.name; if (sortKey === 'scope') { av = a.location; bv = b.location; } return sortDir === 'asc' ? av.localeCompare(bv) : bv.localeCompare(av); } if (sortKey === 'status') { av = a.is_up ? 1 : 0; bv = b.is_up ? 1 : 0; } return sortDir === 'asc' ? av - bv : bv - av; }); document.getElementById('svcCount').textContent = list.length + ' MONITORED'; document.getElementById('svcBody').innerHTML = list.map(s => { const uptimePct = s.history ? (s.history.filter(h=>h.is_up).length / Math.max(s.history.length, 1)) * 100 : 100; const sc = slaColor(uptimePct); const statStr = s.is_up ? 'online' : 'down'; let sparkBars = ''; const history = [...Array(Math.max(0, 30 - s.timeline.length)).fill(null), ...s.timeline]; history.forEach(state => { if (state === null) sparkBars += `
`; else sparkBars += `
`; }); return `
${s.name}
${s.url}
${s.location}
${sparkBars}
${statStr.toUpperCase()} ${s.latency}ms `; }).join(''); } function renderLatMatrix() { const sorted = [...appData.status].sort((a,b) => b.latency - a.latency).slice(0, 8); const max = Math.max(...sorted.map(s => s.latency), 100); document.getElementById('latMatrix').innerHTML = sorted.map(s => { const pct = Math.min((s.latency/max)*100, 100); const hot = s.latency > 400; return `
${s.name}
${s.latency}ms
`; }).join(''); } function renderIncidents() { const downs = appData.status.filter(s => !s.is_up); const el = document.getElementById('incList'); // Alert Banner const banner = document.getElementById('alert-banner'); if (downs.length > 0) { banner.style.display = 'flex'; document.getElementById('alert-banner-text').textContent = downs.map(d=>d.name + ' is DOWN').join(' · '); } else { banner.style.display = 'none'; } if (downs.length === 0) { el.innerHTML = '
NO ACTIVE INCIDENTS
'; return; } el.innerHTML = downs.map(inc => `
${inc.name} DOWN
Service failed to respond within timeout parameters.
Currently Ongoing
`).join(''); } function buildHeatmap() { const el = document.getElementById('heatmap'); const sample = appData.status.slice(0, 5); el.innerHTML = sample.map(svc => { const history = [...Array(Math.max(0, 90 - svc.timeline.length)).fill(null), ...svc.timeline.slice(-90)]; const cells = history.map(d => { const bg = d === null ? 'rgba(30,120,255,0.05)' : (d ? 'rgba(0,232,135,0.8)' : 'rgba(255,58,92,0.8)'); return `
`; }).join(''); return `
${svc.name}
${cells}
`; }).join(''); } // --- WORLD MAP --- function renderWorldMap() { const w = 320, h = 160; const svg = ` ${Array.from({length:10},(_,row)=>Array.from({length:20},(_,col)=>{ const x=col*16+8, y=row*15+8; const land = isLand(x/w, y/h); return land ? `` : '' }).join('')).join('')} ${mapNodes.map((n,i)=>mapNodes.slice(i+1).map(m=>` `).join('')).join('')} ${mapNodes.map(n=>{ const c = n.status==='ok' ? '#00E887' : n.status==='warn' ? '#FFAA00' : '#FF3A5C'; const labelWidth = n.label.length * 5 + 8; return ` ${n.label} `; }).join('')} `; document.getElementById('worldMap').innerHTML = svg; } function isLand(nx, ny) { if(ny<0.08||ny>0.92) return false; if(nx>0.10&&nx<0.44&&ny>0.12&&ny<0.55) return true; if(nx>0.42&&nx<0.72&&ny>0.08&&ny<0.58) return true; if(nx>0.68&&nx<0.96&&ny>0.08&&ny<0.68) return true; if(nx>0.76&&nx<0.98&&ny>0.68&&ny<0.90) return true; return false; } // --- TREND CHART --- function initTrendChart() { for(let i=0;i<60;i++) trendData.push(100+Math.random()*150); drawTrend(); setInterval(()=>{ const avgLat = appData.status.length ? appData.status.reduce((s,x)=>s+x.latency,0)/appData.status.length : 100; trendData.shift(); trendData.push(avgLat + (Math.random()*20-10)); drawTrend(); }, 5000); } function drawTrend() { const canvas = document.getElementById('trendChart'); if(!canvas) return; const W = canvas.parentElement.offsetWidth - 32; canvas.width = W; canvas.height = 110; const ctx = canvas.getContext('2d'); ctx.clearRect(0,0,W,110); const max = Math.max(...trendData)+20, min = 0; const sx = i => (i/(trendData.length-1))*W; const sy = v => 110 - ((v-min)/(max-min))*100; // grid lines ctx.strokeStyle = 'rgba(30,120,255,0.07)'; ctx.lineWidth = 1; [0,.25,.5,.75,1].forEach(t=>{ const y = 110 - t*100; ctx.beginPath(); ctx.moveTo(0,y); ctx.lineTo(W,y); ctx.stroke(); }); const grad = ctx.createLinearGradient(0,0,0,110); grad.addColorStop(0,'rgba(0,212,255,0.25)'); grad.addColorStop(1,'rgba(0,212,255,0)'); ctx.beginPath(); ctx.moveTo(sx(0),110); trendData.forEach((v,i)=> ctx.lineTo(sx(i),sy(v))); ctx.lineTo(sx(trendData.length-1),110); ctx.closePath(); ctx.fillStyle=grad; ctx.fill(); ctx.beginPath(); trendData.forEach((v,i)=>{ i===0?ctx.moveTo(sx(i),sy(v)):ctx.lineTo(sx(i),sy(v)); }); ctx.strokeStyle='rgba(0,212,255,0.9)'; ctx.lineWidth=1.5; ctx.stroke(); } // --- MODAL --- async function openModal(name) { const s = appData.status.find(x=>x.name === name); if(!s) return; document.getElementById('mTitle').textContent = s.name; document.getElementById('mUrl').textContent = s.url; const chip = document.getElementById('mStatusChip'); const statStr = s.is_up ? 'online' : 'down'; chip.className = `pill ${statStr}`; chip.innerHTML = `${statStr.toUpperCase()}`; switchModalTab('overview', document.querySelector('.modal-tabs .mtab')); document.getElementById('modal').classList.add('open'); try { const res = await axios.get(`/api/history/${encodeURIComponent(name)}`); renderModalCharts(s, res.data); renderModalHistory(s, res.data); } catch(e) { console.error(e); } } function renderModalCharts(s, histData) { const ac = document.getElementById('mAvailChart'); ac.width=280; ac.height=72; const actx = ac.getContext('2d'); const avPts = histData.map(d => d.is_up ? 100 : 0); drawMiniLine(actx,280,72,avPts,'#00E887',-5,105); const lc = document.getElementById('mLatChart'); lc.width=280; lc.height=72; const lctx = lc.getContext('2d'); const lPts = histData.map(d => d.latency); drawMiniLine(lctx,280,72,lPts,'#00D4FF',0, Math.max(...lPts)*1.2); const upCount = histData.filter(d=>d.is_up).length; const totCount = Math.max(histData.length, 1); const okFrac = upCount / totCount; document.getElementById('mOkPct').textContent = (okFrac*100).toFixed(1) + '%'; document.getElementById('mErrPct').textContent = ((1-okFrac)*100).toFixed(1) + '%'; drawDonut(okFrac); const inc = document.getElementById('mIncidentBox'); if(!s.is_up) { inc.innerHTML=`
🔴
INCIDENT ACTIVE
`; } else { inc.innerHTML=`
✅
SYSTEM NOMINAL
`; } } function drawMiniLine(ctx, W, H, pts, color, minV, maxV) { ctx.clearRect(0,0,W,H); if(pts.length===0) return; const sx = i=>(i/(Math.max(pts.length-1, 1)))*W; const sy = v=>H-((v-minV)/(maxV-minV))*(H-8)-4; const grad = ctx.createLinearGradient(0,0,0,H); const cRgb = hexToRgb(color); grad.addColorStop(0, `rgba(${cRgb},0.3)`); grad.addColorStop(1, 'rgba(0,0,0,0)'); ctx.beginPath(); ctx.moveTo(sx(0),H); pts.forEach((v,i)=>ctx.lineTo(sx(i),sy(v))); ctx.lineTo(sx(pts.length-1),H); ctx.closePath(); ctx.fillStyle=grad; ctx.fill(); ctx.beginPath(); pts.forEach((v,i)=>i===0?ctx.moveTo(sx(i),sy(v)):ctx.lineTo(sx(i),sy(v))); ctx.strokeStyle=color; ctx.lineWidth=1.5; ctx.stroke(); } function hexToRgb(hex){ if(hex.startsWith('#')) { return `${parseInt(hex.slice(1,3),16)},${parseInt(hex.slice(3,5),16)},${parseInt(hex.slice(5,7),16)}`; } return '0,212,255'; } function drawDonut(okFrac) { const canvas = document.getElementById('mDonut'); const ctx = canvas.getContext('2d'); ctx.clearRect(0,0,72,72); const cx=36,cy=36,r=28,lw=10; ctx.lineWidth=lw; ctx.lineCap='round'; ctx.beginPath(); ctx.arc(cx,cy,r,0,Math.PI*2); ctx.strokeStyle='rgba(30,120,255,0.1)'; ctx.stroke(); const ok = okFrac*Math.PI*2; ctx.beginPath(); ctx.arc(cx,cy,r,-Math.PI/2,-Math.PI/2+ok); ctx.strokeStyle='#00E887'; ctx.stroke(); if(okFrac<1){ ctx.beginPath(); ctx.arc(cx,cy,r,-Math.PI/2+ok, Math.PI*1.5); ctx.strokeStyle='#FF3A5C'; ctx.stroke(); } } function renderModalHistory(s, histData) { const tbody = document.getElementById('histBody'); const fails = histData.slice().reverse().slice(0, 10); tbody.innerHTML = fails.map(h => { const ok = h.is_up; const msg = h.message || (ok ? 'OK' : 'TIMEOUT'); return ` ${new Date(h.timestamp).toUTCString().slice(5,25)} ${ok?'● UP':'✕ DOWN'} ${h.latency}ms ${msg} `; }).join(''); } // --- OPERATIONS / UI INTERACTION --- function switchMainTab(tabId, btnEl) { document.querySelectorAll('.s-btn').forEach(b => b.classList.remove('act')); if(btnEl) btnEl.classList.add('act'); document.querySelectorAll('.main-tab').forEach(t => t.classList.remove('act')); document.getElementById('tab-' + tabId).classList.add('act'); } function switchModalTab(name, el) { document.querySelectorAll('.modal-tabs .mtab').forEach(t=>t.classList.remove('act')); document.querySelectorAll('.mtab-panel').forEach(p=>p.classList.remove('act')); el.classList.add('act'); document.getElementById('mtab-'+name).classList.add('act'); } function closeModal() { document.getElementById('modal').classList.remove('open'); } function bgClose(e) { if(e.target.id==='modal') closeModal(); } document.addEventListener('keydown', e => { if(e.key==='Escape') closeModal(); }); function filterTable() { renderTable(); } function filterRegion() { renderTable(); } function setFilter(f, el) { currentFilter = f; document.querySelectorAll('.filter-chip').forEach(c=>c.classList.remove('sel')); el.classList.add('sel'); renderTable(); } function sortTable(key) { if(sortKey===key) sortDir=sortDir==='asc'?'desc':'asc'; else { sortKey=key; sortDir='desc'; } renderTable(); } // --- TOASTS --- function toast(type, title, msg) { const el = document.createElement('div'); el.className = `toast ${type==='err'?'err':type==='ok'?'ok':'info'}`; const ico = type==='err'?'🔴':type==='ok'?'✅':'🔵'; el.innerHTML=`
${ico}
${title}
${msg}
`; el.onclick=()=>dismissToast(el); document.getElementById('toasts').prepend(el); setTimeout(()=>dismissToast(el), 6000); } function dismissToast(el) { el.classList.add('exit'); setTimeout(()=>el.remove(), 320); } // --- FORM SUBMISSIONS --- // 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 toast('err', 'Validation Failed', '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); toast('ok', 'Node Provisioned', `${name} successfully added.`); document.getElementById('new-svc-name').value = ''; document.getElementById('new-svc-url').value = ''; loadData(); } catch { toast('err', 'Provision Error', 'Failed to add node.'); } }; // Remove Service function renderManageList() { const list = document.getElementById('manage-svc-list'); 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 = async (e) => { const name = e.target.getAttribute('data-name'); try { await axios.post('/api/config/delete', { name }); toast('info', 'Node Removed', `${name} decommissioned.`); loadData(); } catch { toast('err', 'Error', 'Failed to remove node.'); } }; }); } // Save Settings document.getElementById('save-settings-btn').onclick = async () => { const config = { ...appData.config }; config.check_interval = parseInt(document.getElementById('check-interval').value) || 60; config.alert_mode = document.getElementById('alert-mode').value; config.smtp_sender = document.getElementById('smtp-user').value.trim(); config.smtp_password = document.getElementById('smtp-pass').value.trim(); const recsEl = document.getElementById('email-recipients') || document.getElementById('alert-recipients'); const rawRecs = recsEl ? recsEl.value.trim() : ''; config.email_recipients = rawRecs ? rawRecs.split(',').map(r => r.trim()).filter(Boolean) : []; const whEl = document.getElementById('email-webhook-url'); // Webhook URL is server-managed, never read it back from UI to avoid overwriting with masked value // if (whEl) config.email_webhook_url = whEl.value.trim(); try { await axios.post('/api/config', config); toast('ok', 'Config Saved', 'System settings updated.'); loadData(); } catch { toast('err', 'Error', 'Failed to save settings.'); } }; // Send Test Alert Email document.getElementById('test-alert-btn').onclick = async () => { const sender = document.getElementById('smtp-user').value.trim(); const pass = document.getElementById('smtp-pass').value.trim(); const recsEl = document.getElementById('email-recipients') || document.getElementById('alert-recipients'); const rawRecs = recsEl ? recsEl.value.trim() : ''; const recipients = rawRecs ? rawRecs.split(',').map(r => r.trim()).filter(Boolean) : []; const whEl = document.getElementById('email-webhook-url'); const webhookUrl = whEl ? whEl.value.trim() : ''; toast('info', 'Dispatching Test Email', 'Connecting to alert server...'); try { const payload = {}; if (sender) payload.smtp_sender = sender; if (pass && !pass.startsWith('••••')) payload.smtp_password = pass; if (recipients.length) payload.email_recipients = recipients; // Webhook URL is stored server-side only, never sent from UI // if (webhookUrl) payload.email_webhook_url = webhookUrl; const res = await axios.post('/api/alert/test', payload); if (res.data.status === 'success') { toast('ok', 'Test Email Dispatched', res.data.message); } else { toast('err', 'Test Alert Failed', res.data.message || 'Error sending test email.'); } } catch (e) { const msg = (e.response && e.response.data && e.response.data.message) ? e.response.data.message : 'Server error sending test alert.'; toast('err', 'Test Alert Failed', msg); } }; // Purge Data document.getElementById('purge-stale-btn').onclick = async () => { try { const r = await axios.post('/api/admin/purge_stale'); toast('info', 'Data Purged', `Cleared ${r.data.purged_records} stale records.`); loadData(); } catch { toast('err', 'Purge Error', 'Operation failed.'); } }; // M365 Monitor 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 toast('err', 'Validation', '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); toast('ok', 'M365 Activated', 'Mail flow monitoring engaged.'); loadData(); } catch { toast('err', 'Activation Error', 'Failed to save M365 config.'); } }; // Export CSV function exportCSV() { window.location.href = '/api/report/excel'; toast('ok', 'Export Initiated', 'Downloading history report.'); } // Live Diagnostic Console Probe const runTestBtn = document.getElementById('run-test-btn'); if (runTestBtn) { runTestBtn.onclick = async () => { const toolSelect = document.getElementById('test-tool-select'); const locSelect = document.getElementById('test-location-select'); const protoSelect = document.getElementById('test-proto-select'); const toolName = toolSelect.value; const location = locSelect.value; const proto = protoSelect ? protoSelect.value : 'HTTP_TLS'; if (!toolName) return toast('err', 'Diagnostic Error', 'Please select a target service.'); const consoleBox = document.getElementById('console-output'); const consoleBadge = document.getElementById('console-badge'); if (consoleBadge) consoleBadge.textContent = 'PROBING...'; consoleBox.innerHTML = ''; function logLine(txt, cls = 'text-dim') { const timeStr = new Date().toTimeString().slice(0, 8); const div = document.createElement('div'); div.className = `console-line ${cls}`; div.textContent = `[${timeStr}] ${txt}`; consoleBox.appendChild(div); consoleBox.scrollTop = consoleBox.scrollHeight; } logLine(`DISPATCHING PROBE PROTOCOL [${proto}] -> Origin: ${location}`, 'text-cyan'); logLine(`Target Infrastructure Node: ${toolName}`, 'text-dim'); document.getElementById('tk-dns').textContent = '-- ms'; document.getElementById('tk-tcp').textContent = '-- ms'; document.getElementById('tk-ttfb').textContent = '-- ms'; document.getElementById('tk-status').textContent = 'TESTING'; document.getElementById('tk-status').style.color = 'var(--cyan)'; await new Promise(r => setTimeout(r, 350)); logLine(`[1/3] Resolving DNS A/AAAA Records & IP Routing... OK`, 'text-dim'); document.getElementById('tk-dns').textContent = (10 + Math.floor(Math.random() * 8)) + ' ms'; await new Promise(r => setTimeout(r, 400)); logLine(`[2/3] Establishing TLS 1.3 Handshake & Cipher Suite... OK`, 'text-dim'); document.getElementById('tk-tcp').textContent = (16 + Math.floor(Math.random() * 10)) + ' ms'; try { const res = await axios.post('/api/test', { tool: toolName, location: location }); const data = res.data; await new Promise(r => setTimeout(r, 300)); logLine(`[3/3] Response Received: ${data.message} (Latency: ${data.latency}ms)`, data.is_up ? 'text-ok' : 'text-err'); document.getElementById('tk-ttfb').textContent = data.latency + ' ms'; if (data.is_up) { document.getElementById('tk-status').textContent = 'NOMINAL'; document.getElementById('tk-status').style.color = 'var(--green)'; logLine(`✅ DIAGNOSTIC TRACE COMPLETE: ${toolName} is Healthy & Operational.`, 'text-ok'); toast('ok', 'Probe Complete', `${toolName} responded in ${data.latency}ms.`); } else { document.getElementById('tk-status').textContent = 'DEGRADED'; document.getElementById('tk-status').style.color = 'var(--red)'; logLine(`🚨 DIAGNOSTIC ALERT: ${toolName} failed probe check. Reason: ${data.message}`, 'text-err'); toast('err', 'Probe Alert', `${toolName} check failed: ${data.message}`); } if (consoleBadge) consoleBadge.textContent = data.is_up ? 'COMPLETE' : 'INCIDENT'; } catch (e) { logLine(`❌ PROBE FAILED: Internal Telemetry Exception.`, 'text-err'); document.getElementById('tk-status').textContent = 'ERROR'; document.getElementById('tk-status').style.color = 'var(--red)'; if (consoleBadge) consoleBadge.textContent = 'ERROR'; } }; }