# ruff: noqa: E501 from html import escape from typing import Annotated, Any from fastapi import APIRouter, Depends from fastapi.responses import HTMLResponse from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from core_hub.db import session_scope from core_hub.models import ( ApiConsumer, Hub, HubCapabilityManifest, InteractionEvent, MigrationRun, Widget, ) from core_hub.security import require_api_token router = APIRouter(tags=["operator-console"]) TokenDependency = Annotated[str, Depends(require_api_token)] SessionDependency = Annotated[AsyncSession, Depends(session_scope)] CONSOLE_CSS = """ :root { --ink: #0a0a0a; --ink-2: #1f1f1f; --ink-3: #5c5c5c; --ink-4: #8a8a8a; --ink-5: #b5b5b3; --line: #e5e5e2; --line-strong: #c9c9c5; --line-soft: #f0f0ec; --paper: #ffffff; --paper-2: #fafaf7; --paper-3: #f4f4ef; --hi: #ffe14a; --ff-sans: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; --ff-mono: ui-monospace, "SF Mono", Menlo, Consolas, monospace; --sp-2: 8px; --sp-3: 12px; --sp-4: 16px; --sp-5: 24px; --sp-6: 32px; --r-2: 4px; } * { box-sizing: border-box; } html { font-family: var(--ff-sans); color: var(--ink); background: var(--paper); } body { margin: 0; } a { color: inherit; text-decoration: underline; text-decoration-color: var(--line-strong); text-underline-offset: 3px; } code { font-family: var(--ff-mono); font-size: 0.92em; background: var(--paper-3); padding: 1px 5px; border-radius: 2px; } .wn-topnav { height: 56px; border-bottom: 1px solid var(--line); display: flex; align-items: center; justify-content: space-between; padding: 0 var(--sp-5); background: var(--paper); position: sticky; top: 0; z-index: 2; } .wn-topnav__brand { display: flex; align-items: baseline; gap: var(--sp-2); font-weight: 600; } .wn-topnav__brand-slug { font: 500 12px/1 var(--ff-mono); color: var(--ink-3); letter-spacing: 0.04em; } .wn-topnav__right { font: 500 11px/1 var(--ff-mono); color: var(--ink-3); text-transform: uppercase; letter-spacing: 0.08em; } .console-shell { min-height: calc(100vh - 56px); display: grid; grid-template-columns: 248px minmax(0, 1fr); } .wn-sidebar { border-right: 1px solid var(--line); background: var(--paper-2); padding: var(--sp-5); position: sticky; top: 56px; height: calc(100vh - 56px); } .wn-sidebar__group { display: grid; gap: var(--sp-2); margin-bottom: var(--sp-5); } .wn-sidebar__group-label, .wn-eyebrow { font: 500 11px/1.2 var(--ff-mono); letter-spacing: 0.08em; text-transform: uppercase; color: var(--ink-3); } .wn-sidebar__item { display: flex; justify-content: space-between; gap: var(--sp-3); padding: 8px 0; font: 500 13px/1.35 var(--ff-sans); text-decoration: none; border-bottom: 1px solid transparent; } .wn-sidebar__item:hover { border-bottom-color: var(--line-strong); } .wn-sidebar__count { font: 500 11px/1 var(--ff-mono); color: var(--ink-4); } .console-main { padding: var(--sp-6); min-width: 0; } .wn-page-header { display: grid; gap: var(--sp-3); margin-bottom: var(--sp-6); } .wn-page-header__row { display: flex; justify-content: space-between; gap: var(--sp-5); align-items: end; } .wn-page-header__title { font: 600 36px/1.05 var(--ff-sans); margin: 0; letter-spacing: 0; } .wn-page-header__lede { margin: 0; max-width: 760px; color: var(--ink-3); line-height: 1.55; } .wn-btn { display: inline-flex; align-items: center; border: 1px solid var(--ink); border-radius: var(--r-2); padding: 9px 14px; font: 500 13px/1.2 var(--ff-sans); text-decoration: none; background: var(--ink); color: var(--paper); } .kpi-grid { display: grid; grid-template-columns: repeat(6, minmax(120px, 1fr)); gap: var(--sp-3); margin-bottom: var(--sp-6); } .wn-card { background: var(--paper); border: 1px solid var(--line); border-radius: var(--r-2); padding: var(--sp-4); } .stat-card { min-height: 96px; display: grid; align-content: space-between; gap: var(--sp-3); } .stat-card strong { font: 600 30px/1 var(--ff-sans); } .stat-card span { font: 500 11px/1.2 var(--ff-mono); letter-spacing: 0.08em; text-transform: uppercase; color: var(--ink-3); } .console-section { margin-bottom: var(--sp-6); } .section-head { display: flex; justify-content: space-between; gap: var(--sp-5); align-items: baseline; margin-bottom: var(--sp-3); } .section-head h2 { font: 600 20px/1.2 var(--ff-sans); margin: 0; letter-spacing: 0; } .section-head p { margin: 0; color: var(--ink-3); font-size: 13px; } .table-wrap { border: 1px solid var(--line); border-radius: var(--r-2); overflow-x: auto; background: var(--paper); } table { width: 100%; border-collapse: collapse; font-size: 13px; min-width: 680px; } th, td { text-align: left; padding: 11px 14px; border-bottom: 1px solid var(--line); vertical-align: top; } th { font: 500 11px/1.2 var(--ff-mono); letter-spacing: 0.06em; text-transform: uppercase; color: var(--ink-3); background: var(--paper-2); } tr:last-child td { border-bottom: 0; } .wn-tag { display: inline-block; border: 1px solid var(--line); border-radius: 999px; padding: 5px 9px; font: 500 10px/1 var(--ff-mono); letter-spacing: 0.08em; text-transform: uppercase; white-space: nowrap; color: var(--ink-3); } .wn-tag--active, .wn-tag--done, .wn-tag--imported { background: var(--ink); border-color: var(--ink); color: var(--paper); } .wn-tag--wait, .wn-tag--blocked, .wn-tag--degraded { background: var(--paper-3); color: var(--ink-2); } .wn-tag--warn, .wn-tag--draft, .wn-tag--dry-run { background: var(--hi); border-color: transparent; color: #1a1500; } .wn-empty-state { border: 1px dashed var(--line-strong); border-radius: var(--r-2); padding: var(--sp-5); color: var(--ink-3); font-size: 14px; } .gate-list { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--sp-3); } .gate { border: 1px solid var(--line); border-radius: var(--r-2); padding: var(--sp-4); display: grid; gap: var(--sp-2); } .gate b { font-size: 14px; } .gate span { color: var(--ink-3); font-size: 13px; line-height: 1.45; } @media (max-width: 980px) { .console-shell { grid-template-columns: 1fr; } .wn-sidebar { position: static; height: auto; border-right: 0; border-bottom: 1px solid var(--line); } .kpi-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } .gate-list { grid-template-columns: 1fr; } } @media (max-width: 640px) { .wn-topnav { padding: 0 var(--sp-4); } .console-main { padding: var(--sp-4); } .wn-page-header__row { display: grid; } .wn-page-header__title { font-size: 28px; } .kpi-grid { grid-template-columns: 1fr; } } """ def text(value: Any) -> str: if value is None or value == "": return "-" return escape(str(value)) def short_id(value: str | None) -> str: if not value: return "-" return escape(value[:8]) def tag(value: str | None) -> str: status = str(value or "unknown") css_status = escape(status.lower().replace("_", "-").replace(" ", "-")) return f'{escape(status)}' def table_or_empty(headers: list[str], rows: list[list[str]], empty: str) -> str: if not rows: return f'
{escape(empty)}
' head = "".join(f"{escape(header)}" for header in headers) body = "".join("" + "".join(f"{cell}" for cell in row) + "" for row in rows) return f'
{head}{body}
' async def fetch_rows( session: AsyncSession, model: Any, order_by: Any | None = None, limit: int = 12 ) -> list[Any]: statement = select(model) if order_by is not None: statement = statement.order_by(order_by) statement = statement.limit(limit) result = await session.execute(statement) return list(result.scalars()) @router.get("/console", response_class=HTMLResponse) async def operator_console(_: TokenDependency, session: SessionDependency) -> HTMLResponse: hubs = await fetch_rows(session, Hub, Hub.slug) manifests = await fetch_rows( session, HubCapabilityManifest, HubCapabilityManifest.created_at.desc() ) widgets = await fetch_rows(session, Widget, Widget.name) events = await fetch_rows( session, InteractionEvent, InteractionEvent.created_at.desc(), limit=8 ) consumers = await fetch_rows(session, ApiConsumer, ApiConsumer.name) migrations = await fetch_rows(session, MigrationRun, MigrationRun.created_at.desc(), limit=8) active_manifests = sum(1 for item in manifests if item.status == "active") stats = [ ("Hubs", len(hubs)), ("Active manifests", active_manifests), ("Widgets", len(widgets)), ("Events", len(events)), ("Consumers", len(consumers)), ("Migration runs", len(migrations)), ] registry_rows = [ [ f"{text(hub.slug)}", text(hub.name), text(hub.domain), text(hub.hub_kind), tag(hub.status), ] for hub in hubs ] migration_rows = [ [ f"{short_id(run.id)}", text(run.source), text(run.schema_version), f"{short_id(run.bundle_sha256)}", tag("dry-run" if run.dry_run else run.status), ] for run in migrations ] consumer_rows = [ [ f"{text(consumer.slug)}", text(consumer.name), text(consumer.key_prefix), text(consumer.rate_limit_per_minute), tag(consumer.status), ] for consumer in consumers ] event_rows = [ [ f"{short_id(event.id)}", text(event.event_type), f"{short_id(event.widget_id)}", text(event.view_context), ] for event in events ] stat_cards = "".join( f'
{escape(label)}{value}
' for label, value in stats ) html = f""" Core Hub operator console
Core Hub/ operator
protected console
INFOTECH / CORE-HUB

Operator console

OpenAPI

Registry, migration, access, and event state from the Core Hub replacement runtime.

{stat_cards}

Readiness gates

Current blocker map

{tag("blocked")} ops-hub smokeLocal smoke passed. Closure waits on deployed runtime and approved credential routing.
{tag("blocked")} staging importImporter is ready. Closure waits on approved Inter-Hub export bundle and staging DB.
{tag("blocked")} activity-core smokeWaits on deployed Core Hub runtime or explicit transition fallback decision.
{tag("active")} console prototypeProtected read-only console surface is active.

Hub registry

Hubs visible to the protected runtime

{table_or_empty(["Slug", "Name", "Domain", "Kind", "Status"], registry_rows, "No hubs registered yet.")}

Migration runs

Import evidence and bundle identity

{table_or_empty(["Run", "Source", "Schema", "Bundle", "Status"], migration_rows, "No migration runs recorded yet.")}

Access

Non-secret API consumer metadata

{table_or_empty(["Slug", "Name", "Key prefix", "Rate/min", "Status"], consumer_rows, "No API consumers registered yet.")}

Recent events

Latest interaction evidence

{table_or_empty(["Event", "Type", "Widget", "Context"], event_rows, "No interaction events recorded yet.")}
""" return HTMLResponse(html)