';
html += '';
html += '
';
html += '- Status
- ' + esc(status) + '
';
if (t.latest) html += '- Response
- ' + t.latest.response_time_ms + 'ms
';
if (t.uptime_24h != null) {
const c24 = uptimeClass(t.uptime_24h);
html += '- Uptime 24h
- ' + fmtPct(t.uptime_24h) + '
';
}
if (t.uptime_7d != null) {
const c7 = uptimeClass(t.uptime_7d);
html += '- Uptime 7d
- ' + fmtPct(t.uptime_7d) + '
';
}
html += '
';
if (t.latency_24h) {
html += '
';
html += '- Avg
- ' + t.latency_24h.avg_ms.toFixed(0) + 'ms
';
html += '- P95
- ' + t.latency_24h.p95_ms + 'ms
';
html += '- Min/Max
- ' + t.latency_24h.min_ms + '/' + t.latency_24h.max_ms + 'ms
';
html += '
';
}
if (t.tls) {
html += '
';
html += '- TLS
- ' + (t.tls.valid ? 'valid' : 'invalid') + '
';
const dc2 = t.tls.days_remaining > 14 ? 'uptime-ok' : t.tls.days_remaining > 7 ? 'uptime-warn' : 'uptime-danger';
html += '- Expires
- ' + t.tls.days_remaining + 'd
';
html += '
';
}
if (t.whois) {
html += '
';
const wd = t.whois.days_remaining;
const wc = wd > 30 ? 'uptime-ok' : wd > 14 ? 'uptime-warn' : 'uptime-danger';
html += '- Domain
- ' + (wd != null ? wd + 'd' : 'N/A') + '
';
if (t.whois.registrar) html += '- Registrar
- ' + esc(t.whois.registrar) + '
';
html += '
';
}
if (t.dns_status && t.dns_status.length > 0) {
const m = t.dns_status.filter(function(d) { return d.matches; }).length;
html += '
- DNS
- ' + m + '/' + t.dns_status.length + ' match
';
}
if (t.route_status && t.route_status.length > 0) {
const ok = t.route_status.filter(function(r) { return r.ok; }).length;
html += '
- Routes
- ' + ok + '/' + t.route_status.length + ' OK
';
}
if (t.current_incident) {
html += '
Incident: ' + esc(t.current_incident.from_status) + ' \u2192 ' + esc(t.current_incident.to_status) + '
';
}
if (t.test_staleness && t.test_staleness.stale) {
html += '
Tests stale: ' + esc(t.test_staleness.reason) + '
';
}
if (t.test_duration_drift) {
html += '
' + esc(t.test_duration_drift) + '
';
}
html += '
';
return html;
}
function esc(s) {
if (!s) return '';
var d = document.createElement('div');
d.textContent = s;
return d.innerHTML;
}
function renderDetails(targets) {
let html = '';
// Recent incidents
let hasIncidents = false;
let incHtml = '';
for (const name in targets) {
const t = targets[name];
if (t.incidents && t.incidents.length > 0) {
hasIncidents = true;
for (let i = 0; i < t.incidents.length; i++) {
const inc = t.incidents[i];
const dur = inc.duration_secs ? Math.round(inc.duration_secs / 60) + 'm' : 'ongoing';
incHtml += '';
for (const name in data.instances) {
const inst = data.instances[name];
html += '
';
if (inst.instance) {
html += '
- Version
- ' + esc(inst.instance.version) + '
';
}
if (inst.targets) {
html += '
';
for (const tn in inst.targets) {
const tt = inst.targets[tn];
const dc = dotClass(tt.status);
html += '- ' + esc(tt.label || tn) + '
';
}
html += '
';
}
html += '
';
}
html += '
';
return html;
}
async function refresh() {
try {
const resp = await fetch('/api/status', { headers: headers() });
if (!resp.ok) return;
const data = await resp.json();
const targets = data.targets || {};
// Global dot
let worst = 'operational';
for (const name in targets) {
const s = targets[name].latest ? targets[name].latest.status : 'unknown';
if (s === 'error' || s === 'unreachable') worst = 'error';
else if (s === 'degraded' && worst !== 'error') worst = 'degraded';
else if (s === 'unknown' && worst === 'operational') worst = 'unknown';
}
document.getElementById('global-dot').className = 'summary-dot ' + dotClass(worst).replace('dot-', 'dot-');
// Sort target names
const names = Object.keys(targets).sort();
let gridHtml = '';
for (let i = 0; i < names.length; i++) {
gridHtml += renderCard(names[i], targets[names[i]]);
}
document.getElementById('target-grid').innerHTML = gridHtml;
// Details
document.getElementById('details-section').innerHTML = renderDetails(targets);
// Update timestamp
document.getElementById('last-updated').textContent = 'Updated ' + new Date().toLocaleTimeString();
// Mesh
if (HAS_MESH) {
try {
const mr = await fetch('/api/mesh', { headers: headers() });
if (mr.ok) {
const md = await mr.json();
document.getElementById('mesh-section').innerHTML = renderMesh(md);
}
} catch(e) {}
}
} catch(e) {
console.error('Dashboard refresh failed:', e);
}
}
function tick() {
countdown--;
if (countdown <= 0) { countdown = 30; refresh(); }
document.getElementById('refresh-timer').textContent = 'Refresh: ' + countdown + 's';
}
refresh();
timer = setInterval(tick, 1000);
"##;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn escape_js_backslash() {
assert_eq!(escape_js(r"a\b"), r"a\\b");
}
#[test]
fn escape_js_double_quote() {
assert_eq!(escape_js(r#"a"b"#), r#"a\"b"#);
}
#[test]
fn escape_js_both() {
assert_eq!(escape_js(r#"a\"b"#), r#"a\\\"b"#);
}
#[test]
fn escape_js_clean_string() {
assert_eq!(escape_js("hello"), "hello");
}
#[test]
fn escape_js_empty() {
assert_eq!(escape_js(""), "");
}
#[test]
fn escape_js_newline() {
assert_eq!(escape_js("a\nb"), "a\\nb");
}
#[test]
fn escape_js_carriage_return() {
assert_eq!(escape_js("a\rb"), "a\\rb");
}
#[test]
fn escape_js_script_close_tag() {
assert_eq!(escape_js(""), "\\x3c/script>");
}
#[test]
fn escape_js_null() {
assert_eq!(escape_js("a\0b"), "a\\0b");
}
}