266 lines
15 KiB
HTML
266 lines
15 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>AI Evolution Dashboard</title>
|
|
<style>
|
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; background: #0d1117; color: #c9d1d9; height: 100vh; overflow: hidden; }
|
|
.app { display: flex; height: 100vh; }
|
|
.sidebar { width: 260px; background: #161b22; border-right: 1px solid #30363d; padding: 20px; display: flex; flex-direction: column; gap: 16px; }
|
|
.sidebar h1 { font-size: 18px; color: #f0f6fc; border-bottom: 1px solid #30363d; padding-bottom: 12px; margin-bottom: 8px; }
|
|
.sidebar-nav { list-style: none; display: flex; flex-direction: column; gap: 4px; }
|
|
.sidebar-nav li { padding: 8px 12px; border-radius: 6px; cursor: pointer; font-size: 14px; color: #8b949e; }
|
|
.sidebar-nav li:hover, .sidebar-nav li.active { background: #1f6feb22; color: #58a6ff; }
|
|
.sidebar-nav li i { margin-right: 8px; }
|
|
.main { flex: 1; padding: 24px; overflow-y: auto; }
|
|
.main-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 24px; }
|
|
.main-header h2 { font-size: 20px; }
|
|
.status-badge { padding: 4px 12px; border-radius: 12px; font-size: 12px; font-weight: 600; text-transform: uppercase; }
|
|
.status-badge.ok { background: #1b3a2d; color: #3fb950; border: 1px solid #3fb95044; }
|
|
.status-badge.degraded { background: #3a2e1b; color: #d29922; border: 1px solid #d2992244; }
|
|
.status-badge.fail { background: #3a1b1b; color: #f85149; border: 1px solid #f8514944; }
|
|
.card { background: #161b22; border: 1px solid #30363d; border-radius: 8px; padding: 16px; margin-bottom: 16px; }
|
|
.card-title { font-size: 14px; color: #8b949e; margin-bottom: 8px; }
|
|
.card-value { font-size: 28px; font-weight: 700; color: #f0f6fc; }
|
|
.row { display: flex; gap: 16px; }
|
|
.col { flex: 1; }
|
|
.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 16px; }
|
|
pre { white-space: pre-wrap; word-break: break-word; }
|
|
.command-item { padding: 12px; margin-bottom: 8px; border-radius: 6px; background: #0d1117; border: 1px solid #21262d; }
|
|
.command-item .meta { display: flex; gap: 8px; align-items: center; font-size: 12px; color: #8b949e; }
|
|
.command-item .name { font-size: 14px; color: #58a6ff; font-weight: 600; }
|
|
.command-item .state { font-size: 11px; padding: 2px 8px; border-radius: 10px; text-transform: uppercase; }
|
|
.state-active { background: #1b3a2d; color: #3fb950; }
|
|
.state-running { background: #3a2e1b; color: #d29922; }
|
|
.state-completed { background: #1b3a2d; color: #3fb950; }
|
|
.state-failed { background: #3a1b1b; color: #f85149; }
|
|
.loading { text-align: center; padding: 40px; color: #8b949e; }
|
|
.error { padding: 20px; background: #3a1b1b; border: 1px solid #f8514944; border-radius: 8px; color: #f85149; margin-bottom: 16px; }
|
|
.version-item { padding: 8px 12px; background: #0d1117; border: 1px solid #21262d; border-radius: 6px; margin-bottom: 4px; display: flex; justify-content: space-between; }
|
|
.version-item .tag { color: #58a6ff; font-weight: 600; }
|
|
.version-item .name { color: #8b949e; font-size: 13px; }
|
|
.agent-item { background: #0d1117; border: 1px solid #21262d; border-radius: 6px; margin-bottom: 8px; padding: 12px; }
|
|
.agent-item .agent-title { font-size: 14px; font-weight: 600; color: #c9d1d9; margin-bottom: 4px; }
|
|
.agent-item .agent-meta { font-size: 12px; color: #8b949e; }
|
|
.agent-run-item { padding: 8px 12px; margin-bottom: 4px; border-radius: 4px; background: #0d1117; border: 1px solid #21262d; display: flex; justify-content: space-between; align-items: center; }
|
|
.agent-run-item .run-id { color: #58a6ff; font-size: 13px; font-weight: 600; }
|
|
.agent-run-item .source { color: #8b949e; font-size: 12px; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div id="app" class="app">
|
|
<div class="sidebar">
|
|
<h1>🕹️ AI Evolution Dashboard</h1>
|
|
<ul class="sidebar-nav">
|
|
<li class="active" onclick="showPanel('overview')"><i>📊</i> Overview</li>
|
|
<li onclick="showPanel('versions')"><i>🏷️</i> Versions</li>
|
|
<li onclick="showPanel('commands')"><i>⚡</i> Commands</li>
|
|
<li onclick="showPanel('agents')"><i>🤖</i> Agent Runs</li>
|
|
<li onclick="showPanel('arch-owner')"><i>🔐</i> Owner Gate</li>
|
|
<li onclick="showPanel('arch-approve')"><i>✓</i> Approve Changes</li>
|
|
</ul>
|
|
</div>
|
|
<div class="main">
|
|
<div id="panel-overview" class="panel">
|
|
<div class="main-header">
|
|
<h2>System Overview</h2>
|
|
<div id="header-status" class="status-badge">Loading...</div>
|
|
</div>
|
|
<div id="loading" class="loading">Loading...</div>
|
|
<div id="error" class="error" style="display:none;"></div>
|
|
<div id="content">
|
|
<div class="row">
|
|
<div class="col"><div class="card"><div class="card-title">Current Baseline</div><div id="base" class="card-value">-</div></div></div>
|
|
<div class="col"><div class="card"><div class="card-title">Git Branch</div><div id="branch" class="card-value">-</div></div></div>
|
|
<div class="col"><div class="card"><div class="card-title">Uptime</div><div id="uptime" class="card-value">-</div></div></div>
|
|
</div>
|
|
<div class="card" style="margin-top:16px">
|
|
<div class="card-title">Event Bus Status</div>
|
|
<pre id="bus-status">-</pre>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div id="panel-versions" class="panel" style="display:none;">
|
|
<div class="main-header"><h2>Version Timeline</h2></div>
|
|
<div id="versions-list">Loading...</div>
|
|
</div>
|
|
<div id="panel-commands" class="panel" style="display:none;">
|
|
<div class="main-header"><h2>Command Lookup</h2></div>
|
|
<div id="commands-list">Loading...</div>
|
|
</div>
|
|
<div id="panel-agents" class="panel" style="display:none;">
|
|
<div class="main-header"><h2>Recent Agent Executions</h2></div>
|
|
<div id="panel-arch-owner" class="panel" style="display:none;">
|
|
<div class="main-header"><h2>🔐 Architecture Owner Gate</h2></div>
|
|
<div id="arch-owner-content">Loading...</div>
|
|
</div>
|
|
<div id="panel-arch-approve" class="panel" style="display:none;">
|
|
<div class="main-header"><h2>✓ Approve Architecture Changes</h2></div>
|
|
<div id="arch-approve-content">Loading...</div>
|
|
</div>
|
|
<div id="agents-list">Loading...</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<script>
|
|
async function fetchData() {
|
|
const r = await fetch('/admin/ai-evolution/api/data');
|
|
const data = await r.json();
|
|
document.getElementById('loading').style.display = 'none';
|
|
document.getElementById('error').style.display = 'none';
|
|
document.getElementById('content').style.display = '';
|
|
|
|
const hs = document.getElementById('header-status');
|
|
hs.className = 'status-badge ' + (data.healthy ? 'ok' : 'fail');
|
|
hs.textContent = data.healthy ? '▪ operational' : '⚠ degraded';
|
|
|
|
document.getElementById('base').textContent = data.baseline || '-';
|
|
document.getElementById('branch').textContent = data.main_branch || 'main';
|
|
document.getElementById('uptime').textContent = data.uptime || '-';
|
|
document.getElementById('bus-status').textContent = data.bus ? JSON.stringify(data.bus, null, 2) : 'no event bus data';
|
|
}
|
|
|
|
async function loadVersions() {
|
|
const el = document.getElementById('versions-list');
|
|
el.innerHTML = '<div class="loading">Loading versions...</div>';
|
|
try {
|
|
const r = await fetch('/admin/ai-evolution/api/versions');
|
|
const data = await r.json();
|
|
if (data.error) { el.innerHTML = '<div class="error">' + data.error + '</div>'; return; }
|
|
let html = '<div class="grid">';
|
|
for (const v of data.versions || []) {
|
|
html += '<div class="card"><div class="card-title">' + v.tag + '</div><div class="card-value">' + (v.version || v.tag) + '</div></div>';
|
|
}
|
|
html += '</div>';
|
|
el.innerHTML = html;
|
|
} catch(e) {
|
|
el.innerHTML = '<div class="error">Failed to load versions: ' + e.message + '</div>';
|
|
}
|
|
}
|
|
|
|
async function loadCommands() {
|
|
const el = document.getElementById('commands-list');
|
|
el.innerHTML = '<div class="loading">Loading commands...</div>';
|
|
try {
|
|
const r = await fetch('/admin/ai-evolution/api/data');
|
|
const data = await r.json();
|
|
let cmds = data.commands || [];
|
|
let html = '<div style="display:flex;gap:24px;margin-bottom:16px;">';
|
|
html += '<div><div class="card"><div class="card-title">Total</div><div class="card-value">' + cmds.length + '</div></div></div>';
|
|
html += '<div><div class="card"><div class="card-title">By Category</div>';
|
|
const cats = {};
|
|
for (const c of cmds) { const k = c.category || 'uncategorized'; cats[k] = (cats[k] || 0) + 1; }
|
|
for (const [k, v] of Object.entries(cats)) html += '<div style="font-size:14px;color:#8b949e;margin:4px 0;">' + k + ': ' + v + '</div>';
|
|
html += '</div></div></div>';
|
|
for (const cmd of cmds.slice().reverse()) {
|
|
html += '<div class="command-item"><div class="meta">' + cmd.category + ' | ' + cmd.filename + '</div><div class="name">' + cmd.title + '</div><div style="font-size:13px;color:#c9d1d9;margin-top:4px;">' + cmd.summary + '</div></div>';
|
|
}
|
|
el.innerHTML = html;
|
|
} catch(e) {
|
|
el.innerHTML = '<div class="error">Failed to load commands: ' + e.message + '</div>';
|
|
}
|
|
}
|
|
|
|
async function loadAgents() {
|
|
const el = document.getElementById('agents-list');
|
|
el.innerHTML = '<div class="loading">Loading agent runs...</div>';
|
|
try {
|
|
const r = await fetch('/admin/ai-evolution/api/agents');
|
|
const data = await r.json();
|
|
if (data.error) { el.innerHTML = '<div class="error">' + data.error + '</div>'; return; }
|
|
let html = '';
|
|
for (const agent of data.agents || []) {
|
|
html += '<div class="agent-item"><div class="agent-title">' + agent.name + ' <span class="state state-' + (agent.status === 'running' ? 'running' : (agent.status === 'ok' ? 'completed' : 'failed')) + '">' + agent.status + '</span></div><div class="agent-meta">' + (agent.runs || 0) + ' runs | last: ' + (agent.last_run || '-') + '</div></div>';
|
|
}
|
|
el.innerHTML = html || '<div style="padding:24px;color:#8b949e;">No agent runs recorded yet.</div>';
|
|
} catch(e) {
|
|
el.innerHTML = '<div class="error">Failed to load agents: ' + e.message + '</div>';
|
|
}
|
|
}
|
|
|
|
function showPanel(name) {
|
|
|
|
async function loadOwnerGate() {
|
|
const el = document.getElementById('arch-owner-content');
|
|
el.innerHTML = '<div class="loading">Loading owner gate data...</div>';
|
|
try {
|
|
const r = await fetch('/admin/ai-evolution/api/owner-gate');
|
|
const data = await r.json();
|
|
let html = '<div class="row">';
|
|
html += '<div class="col"><div class="card"><div class="card-title">Pending Requests</div><div class="card-value">' + data.pending_count + '</div></div></div>';
|
|
html += '<div class="col"><div class="card"><div class="card-title">Approved</div><div class="card-value">' + data.approved_count + '</div></div></div>';
|
|
html += '<div class="col"><div class="card"><div class="card-title">Rejected</div><div class="card-value">' + data.rejected_count + '</div></div></div>';
|
|
html += '</div>';
|
|
html += '<div class="card"><div class="card-title">Pending Approval Requests</div>';
|
|
if (data.pending_requests && data.pending_requests.length) {
|
|
for (const req of data.pending_requests) {
|
|
html += '<div class="command-item">';
|
|
html += '<div class="name">' + (req.title || req.change_id) + '</div>';
|
|
html += '<div class="meta">ID: ' + req.change_id + ' | Risk: ' + req.risk + '</div>';
|
|
html += '<div style="font-size:13px;color:#c9d1d9;margin-top:4px;">' + (req.reason || '') + '</div>';
|
|
html += '</div>';
|
|
}
|
|
} else {
|
|
html += '<div style="color:#8b949e;padding:12px;">No pending requests.</div>';
|
|
}
|
|
html += '</div>';
|
|
el.innerHTML = html;
|
|
} catch(e) {
|
|
el.innerHTML = '<div class="error">Failed to load: ' + e.message + '</div>';
|
|
}
|
|
}
|
|
|
|
async function loadApprove() {
|
|
const el = document.getElementById('arch-approve-content');
|
|
el.innerHTML = '<div class="loading">Loading pending approvals...</div>';
|
|
try {
|
|
const r = await fetch('/admin/ai-evolution/api/owner-gate');
|
|
const data = await r.json();
|
|
let html = '';
|
|
if (data.pending_requests && data.pending_requests.length) {
|
|
html += '<div class="card"><div class="card-title">Pending changes requiring your approval</div>';
|
|
for (const req of data.pending_requests) {
|
|
html += '<div class="command-item">';
|
|
html += '<div class="name">' + (req.title || req.change_id) + '</div>';
|
|
html += '<div class="meta">Risk: <strong>' + req.risk + '</strong></div>';
|
|
html += '<div style="font-size:13px;color:#c9d1d9;margin:8px 0;">' + (req.reason || '') + '</div>';
|
|
html += '<div style="display:flex;gap:8px;">';
|
|
html += '<button onclick="ownerAction.classList.add('\'approved\');this.disabled=true" style="padding:6px 16px;background:#238636;color:#fff;border:none;border-radius:6px;cursor:pointer;font-size:13px;">✓ Approve</button>';
|
|
html += '</div></div>';
|
|
}
|
|
html += '</div>';
|
|
} else {
|
|
html += '<div class="card"><div style="padding:24px;text-align:center;color:#8b949e;">✅ All changes processed. No pending approval requests.</div></div>';
|
|
}
|
|
el.innerHTML = html;
|
|
} catch(e) {
|
|
el.innerHTML = '<div class="error">Failed to load: ' + e.message + '</div>';
|
|
}
|
|
}
|
|
|
|
document.querySelectorAll('.panel').forEach(p => p.style.display = 'none');
|
|
document.getElementById('panel-' + name).style.display = '';
|
|
document.querySelectorAll('.sidebar-nav li').forEach(li => li.classList.remove('active'));
|
|
document.querySelector('.sidebar-nav li:nth-child(' + ({overview:1,versions:2,commands:3,agents:4}[name] || 1) + ')').classList.add('active');
|
|
if (name === 'overview') {
|
|
document.getElementById('panel-overview').innerHTML = '<div class="main-header"><h2>System Overview</h2><div id="header-status" class="status-badge">Loading...</div></div><div id="loading" class="loading">Loading...</div><div id="error" class="error" style="display:none;"></div><div id="content"><div class="row"><div class="col"><div class="card"><div class="card-title">Current Baseline</div><div id="base" class="card-value">-</div></div></div><div class="col"><div class="card"><div class="card-title">Git Branch</div><div id="branch" class="card-value">-</div></div></div><div class="col"><div class="card"><div class="card-title">Uptime</div><div id="uptime" class="card-value">-</div></div></div></div><div class="card" style="margin-top:16px"><div class="card-title">Event Bus Status</div><pre id="bus-status">-</pre></div></div>';
|
|
fetchData();
|
|
}
|
|
const loaders = {
|
|
'arch-owner': loadOwnerGate,
|
|
'arch-approve': loadApprove,
|
|
versions: loadVersions,
|
|
commands: loadCommands,
|
|
agents: loadAgents,
|
|
};
|
|
const fn = loaders[name];
|
|
if (fn) fn();
|
|
}
|
|
|
|
fetchData();
|
|
</script>
|
|
</body>
|
|
</html>
|