teleo-codex/ops/diagnostics/shared_ui.py
m3taversal 05d74d5e32 sync: import all VPS pipeline + diagnostics code as baseline
Imports 67 files from VPS (/opt/teleo-eval/) into repo as the single source
of truth. Previously only 8 of 67 files existed in repo — the rest were
deployed directly to VPS via SCP, causing massive drift.

Includes:
- pipeline/lib/: 33 Python modules (daemon core, extraction, evaluation, merge, cascade, cross-domain, costs, attribution, etc.)
- pipeline/: main daemon (teleo-pipeline.py), reweave.py, batch-extract-50.sh
- diagnostics/: 19 files (4-page dashboard, alerting, daily digest, review queue, tier1 metrics)
- agent-state/: bootstrap, lib-state, cascade inbox processor, schema
- systemd/: service unit files for reference
- deploy.sh: rsync-based deploy with --dry-run, syntax checks, dirty-tree gate
- research-session.sh: updated with Step 8.5 digest + cascade inbox processing

No new code written — all files are exact copies from VPS as of 2026-04-06.
From this point forward: edit in repo, commit, then deploy.sh.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 00:00:00 +01:00

149 lines
7.2 KiB
Python

"""Shared UI components for the 4-page Argus dashboard.
Provides: nav bar, CSS, page skeleton, Chart.js imports, shared JS helpers.
All pages import render_page() and pass their body HTML + page-specific scripts.
"""
# Page definitions — used by nav bar
PAGES = [
{"path": "/prs", "label": "PRs", "icon": "&#9998;"},
{"path": "/ops", "label": "Operations", "icon": "&#9881;"},
{"path": "/health", "label": "Knowledge Health", "icon": "&#9829;"},
{"path": "/agents", "label": "Agents", "icon": "&#9733;"},
{"path": "/epistemic", "label": "Epistemic", "icon": "&#9878;"},
]
def _nav_html(active_path: str) -> str:
"""Render the shared navigation bar."""
links = []
for p in PAGES:
cls = "nav-active" if p["path"] == active_path else ""
links.append(
f'<a href="{p["path"]}" class="nav-link {cls}">'
f'{p["icon"]} {p["label"]}</a>'
)
return f"""<nav class="top-nav">
<div class="nav-brand">Argus</div>
<div class="nav-links">{"".join(links)}</div>
<div class="nav-aux">
<a href="/audit" class="nav-link">Audit</a>
<a href="/api/metrics" class="nav-link">API</a>
</div>
</nav>"""
SHARED_CSS = """
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: -apple-system, system-ui, 'Segoe UI', sans-serif; background: #0d1117; color: #c9d1d9; }
.top-nav { display: flex; align-items: center; gap: 16px; padding: 12px 24px;
background: #161b22; border-bottom: 1px solid #30363d; position: sticky; top: 0; z-index: 100; }
.nav-brand { color: #58a6ff; font-weight: 700; font-size: 18px; }
.nav-links { display: flex; gap: 4px; flex: 1; }
.nav-aux { display: flex; gap: 4px; }
.nav-link { color: #8b949e; text-decoration: none; padding: 6px 12px; border-radius: 6px;
font-size: 13px; transition: all 0.15s; white-space: nowrap; }
.nav-link:hover { color: #c9d1d9; background: #21262d; }
.nav-active { color: #58a6ff !important; background: #0d1117; font-weight: 600; }
.page-content { padding: 24px; max-width: 1400px; margin: 0 auto; }
.page-header { margin-bottom: 20px; }
.page-header h1 { color: #58a6ff; font-size: 22px; }
.page-header .subtitle { color: #8b949e; font-size: 13px; margin-top: 4px; }
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 12px; margin: 16px 0; }
.card { background: #161b22; border: 1px solid #30363d; border-radius: 8px; padding: 16px; }
.card .label { color: #8b949e; font-size: 11px; text-transform: uppercase; letter-spacing: 0.5px; }
.card .value { font-size: 28px; font-weight: 700; margin-top: 2px; }
.card .detail { color: #8b949e; font-size: 11px; margin-top: 2px; }
.green { color: #3fb950; }
.yellow { color: #d29922; }
.red { color: #f85149; }
.blue { color: #58a6ff; }
.purple { color: #bc8cff; }
.chart-container { background: #161b22; border: 1px solid #30363d; border-radius: 8px; padding: 16px; margin: 16px 0; }
.chart-container h2 { color: #c9d1d9; font-size: 14px; margin-bottom: 12px; }
canvas { max-height: 260px; }
.row { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
@media (max-width: 800px) { .row { grid-template-columns: 1fr; } }
table { width: 100%; border-collapse: collapse; font-size: 13px; }
th { color: #8b949e; font-size: 11px; text-transform: uppercase; text-align: left; padding: 6px 10px; border-bottom: 1px solid #30363d; }
td { padding: 6px 10px; border-bottom: 1px solid #21262d; }
code { background: #21262d; padding: 2px 6px; border-radius: 3px; font-size: 12px; }
.section { margin-top: 28px; }
.section-title { color: #58a6ff; font-size: 15px; font-weight: 600; margin-bottom: 12px; padding-bottom: 6px; border-bottom: 1px solid #21262d; }
.funnel { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.funnel-step { text-align: center; flex: 1; min-width: 100px; }
.funnel-step .num { font-size: 24px; font-weight: 700; }
.funnel-step .lbl { font-size: 11px; color: #8b949e; text-transform: uppercase; }
.funnel-arrow { color: #30363d; font-size: 20px; }
.footer { margin-top: 40px; padding: 16px 24px; border-top: 1px solid #21262d; color: #484f58; font-size: 11px; text-align: center; }
.footer a { color: #484f58; text-decoration: none; }
.footer a:hover { color: #8b949e; }
.alert-banner { padding: 8px 16px; font-size: 12px; border-radius: 6px; margin-bottom: 12px; }
.alert-critical { background: #f8514922; border: 1px solid #f85149; color: #f85149; }
.alert-warning { background: #d2992222; border: 1px solid #d29922; color: #d29922; }
.alert-info { background: #58a6ff22; border: 1px solid #58a6ff; color: #58a6ff; }
.badge { display: inline-block; padding: 2px 8px; border-radius: 4px; font-size: 11px; font-weight: 600; }
.badge-green { background: #23863633; color: #3fb950; }
.badge-yellow { background: #d2992233; color: #d29922; }
.badge-red { background: #f8514933; color: #f85149; }
.badge-blue { background: #1f6feb33; color: #58a6ff; }
"""
CHART_JS_IMPORTS = """<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.6"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-date-fns@3.0.0"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-annotation@3.1.0"></script>"""
SHARED_JS = """
const AGENT_COLORS = {
'rio': '#58a6ff', 'clay': '#3fb950', 'astra': '#bc8cff',
'leo': '#d29922', 'vida': '#f0883e', 'theseus': '#f85149',
'epimetheus': '#79c0ff', 'ganymede': '#8b949e', 'oberon': '#ec4899',
};
function agentColor(name) {
return AGENT_COLORS[name?.toLowerCase()] ||
'#' + ((name||'').split('').reduce((a,c) => (a*31+c.charCodeAt(0))&0xFFFFFF, 0x556677)).toString(16).padStart(6,'0');
}
Chart.defaults.color = '#8b949e';
Chart.defaults.borderColor = '#21262d';
Chart.defaults.font.family = '-apple-system, system-ui, sans-serif';
Chart.defaults.font.size = 11;
function esc(s) { const d = document.createElement('div'); d.textContent = s; return d.innerHTML; }
function fmtPct(v) { return v != null ? (v * 100).toFixed(1) + '%' : '--'; }
function fmtNum(v) { return v != null ? v.toLocaleString() : '--'; }
function fmtDollars(v) { return v != null ? '$' + v.toFixed(2) : '--'; }
"""
def render_page(title: str, subtitle: str, active_path: str, body_html: str,
scripts: str = "", extra_css: str = "", timestamp: str = "") -> str:
"""Render a complete page with nav, content, and footer."""
ts_display = f" &middot; {timestamp}" if timestamp else ""
return f"""<!DOCTYPE html>
<html lang="en"><head>
<meta charset="utf-8">
<title>Argus - {title}</title>
<meta http-equiv="refresh" content="60">
<meta name="viewport" content="width=device-width, initial-scale=1">
{CHART_JS_IMPORTS}
<style>{SHARED_CSS}{extra_css}</style>
</head><body>
{_nav_html(active_path)}
<div class="page-content">
<div class="page-header">
<h1>{title}</h1>
<div class="subtitle">{subtitle}{ts_display} &middot; auto-refresh 60s</div>
</div>
{body_html}
</div>
<div class="footer">
Argus &middot; Teleo Pipeline Diagnostics &middot;
<a href="/api/metrics">Metrics API</a> &middot;
<a href="/api/vital-signs">Vital Signs API</a> &middot;
<a href="/api/contributors">Contributors API</a>
</div>
<script>{SHARED_JS}</script>
{scripts}
</body></html>"""