Files
ccsite-manager/templates/watchdog.html

117 lines
5.0 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Watchdog — CCSite Fleet</title>
<style>
*{margin:0;padding:0;box-sizing:border-box}
body{background:#0a0a0f;color:#e0e0e0;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;padding:1.5rem}
h1{font-size:1.5rem;margin-bottom:0.3rem}
h1 span{color:#22c55e}
.sub{color:#666;font-size:0.8rem;margin-bottom:1.5rem}
.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(320px,1fr));gap:1rem}
.card{background:#111118;border-radius:10px;padding:1.2rem;border:1px solid #1a1a24}
.card.pass{border-color:#22c55e33}
.card.warn{border-color:#f59e0b33}
.card.fail{border-color:#ef444433}
.card .header{display:flex;justify-content:space-between;align-items:center;margin-bottom:0.5rem}
.card .name{font-weight:700;font-size:0.9rem}
.card .status{font-size:0.7rem;padding:0.2rem 0.6rem;border-radius:4px;font-weight:700;text-transform:uppercase}
.status.pass{background:#22c55e22;color:#22c55e}
.status.warn{background:#f59e0b22;color:#f59e0b}
.status.fail{background:#ef444422;color:#ef4444}
.card .detail{font-size:0.75rem;color:#666;font-family:monospace}
.summary{display:flex;gap:1.5rem;margin-bottom:1.5rem;flex-wrap:wrap}
.summary .stat{text-align:center}
.summary .stat .n{font-size:2rem;font-weight:900}
.summary .stat .l{font-size:0.65rem;color:#666;text-transform:uppercase;letter-spacing:1px}
.summary .stat .n.green{color:#22c55e}
.summary .stat .n.yellow{color:#f59e0b}
.summary .stat .n.red{color:#ef4444}
.issues{background:#ef444411;border:1px solid #ef444433;border-radius:10px;padding:1rem;margin-bottom:1.5rem}
.issues h3{font-size:0.85rem;color:#ef4444;margin-bottom:0.5rem}
.issues li{font-size:0.8rem;color:#fca5a5;margin-left:1.2rem;line-height:1.6}
.healing{background:#f59e0b11;border:1px solid #f59e0b33;border-radius:10px;padding:1rem;margin-bottom:1.5rem}
.healing h3{font-size:0.85rem;color:#f59e0b;margin-bottom:0.5rem}
.healing li{font-size:0.8rem;color:#fcd34d;margin-left:1.2rem;line-height:1.6}
.actions{margin-top:1rem;display:flex;gap:0.5rem}
button{background:#1a1a2e;border:1px solid #333;color:#ccc;padding:0.5rem 1rem;border-radius:6px;cursor:pointer;font-size:0.8rem}
button:hover{background:#222;border-color:#555}
.refresh{color:#666;font-size:0.7rem;margin-top:0.5rem}
</style>
</head>
<body>
<h1>🛡️ <span>CCSite Watchdog</span></h1>
<p class="sub">Self-healing health monitor — checks every 60s</p>
<div id="summary" class="summary"></div>
<div id="issues"></div>
<div id="healing"></div>
<div class="actions">
<button onclick="runScan()">🔍 Run Scan Now</button>
<button onclick="location.reload()">🔄 Refresh</button>
</div>
<div class="grid" id="checks"></div>
<p class="refresh" id="last"></p>
<script>
function icon(s) { return s==='pass'?'✅':s==='warn'?'⚠️':'❌' }
async function load() {
try {
const r = await fetch('/api/watchdog');
const d = await r.json();
// Summary
const s = d.summary || {};
const statusEmoji = s.status === 'healthy' ? '🟢' : s.status === 'degraded' ? '🟡' : '🔴';
document.getElementById('summary').innerHTML =
`<div class="stat"><div class="n green">${s.pass||0}</div><div class="l">Passing</div></div>
<div class="stat"><div class="n yellow">${s.warn||0}</div><div class="l">Warnings</div></div>
<div class="stat"><div class="n red">${s.fail||0}</div><div class="l">Failing</div></div>
<div class="stat"><div class="n">${statusEmoji}</div><div class="l">${(s.status||'unknown').toUpperCase()}</div></div>`;
// Issues
const issues = d.issues || [];
document.getElementById('issues').innerHTML = issues.length ?
`<div class="issues"><h3>🚨 ${issues.length} Issue(s)</h3><ul>${issues.map(i=>`<li>${i}</li>`).join('')}</ul></div>` : '';
// Healing
const healing = d.healing_actions || [];
document.getElementById('healing').innerHTML = healing.length ?
`<div class="healing"><h3>🔧 Self-Healing Actions</h3><ul>${healing.map(h=>`<li>${h.success?'✅':'❌'} ${h.action}: ${h.detail}</li>`).join('')}</ul></div>` : '';
// Checks
const checks = d.checks || [];
document.getElementById('checks').innerHTML = checks.map(c =>
`<div class="card ${c.status}">
<div class="header">
<span class="name">${c.check}</span>
<span class="status ${c.status}">${c.status}</span>
</div>
<div class="detail">${c.detail}</div>
</div>`
).join('');
document.getElementById('last').textContent = `Last check: ${d.last_check || 'never'}`;
} catch(e) {
document.getElementById('summary').innerHTML = '<div class="stat"><div class="n red">⚠️</div><div class="l">Watchdog unavailable</div></div>';
}
}
async function runScan() {
document.querySelector('button').textContent = 'Scanning...';
await fetch('/api/watchdog/run', {method:'POST'});
await load();
document.querySelector('button').textContent = '🔍 Run Scan Now';
}
load();
setInterval(load, 30000);
</script>
</body>
</html>