import { html, useHashRoute, useState, usePoll, num, ago } from './core.js'; import { Toast, Dot } from './ui.js'; import { Overview } from './overview.js'; import { Controls } from './controls.js'; import { Articles, Events, Intelligence } from './explore.js'; import { Sql } from './sql.js'; // The d3 force graph is a big specialised visualisation that already works. It is // mounted in a frame rather than rewritten, because porting it would risk breaking // something valuable to gain nothing the operator can see. const Graph = () => html` `; const ROUTES = [ { id: 'overview', label: 'Overview', view: Overview }, { id: 'controls', label: 'Controls', view: Controls }, { id: 'articles', label: 'Articles', view: Articles }, { id: 'events', label: 'Events', view: Events }, { id: 'intelligence', label: 'Intelligence', view: Intelligence }, { id: 'graph', label: 'Graph', view: Graph }, { id: 'sql', label: 'SQL', view: Sql }, ]; function Nav({ route, health }) { return html` `; } function App() { const route = useHashRoute('overview'); const [toast, setToast] = useState(null); // A single cheap poll drives the sidebar so every view does not need its own. const { data } = usePoll('/admin/api/ops/overview', 10000); const controls = (data && data.controls) || {}; const deadTotal = ((data && data.deadLetters) || []) .reduce((sum, row) => sum + Number(row.n || 0), 0); const ingestMinutes = data && data.freshness ? data.freshness.ingestMinutes : null; const halted = Boolean(controls.killSwitch); const health = { deadTotal, halted, ingestMinutes, tone: halted ? 'bad' : (deadTotal > 0 ? 'warn' : 'ok'), label: halted ? 'trading halted' : (controls.mode ? `${controls.mode} mode` : 'connecting…'), }; const active = ROUTES.find((r) => r.id === route) || ROUTES[0]; const View = active.view; return html`