"""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": "✎"}, {"path": "/ops", "label": "Operations", "icon": "⚙"}, {"path": "/health", "label": "Knowledge Health", "icon": "♥"}, {"path": "/agents", "label": "Agents", "icon": "★"}, {"path": "/epistemic", "label": "Epistemic", "icon": "⚖"}, ] 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'' f'{p["icon"]} {p["label"]}' ) return f"""""" 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 = """ """ 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" · {timestamp}" if timestamp else "" return f"""