/**
* Flynn Live Ops Dashboard
*
* Shows core counters, model performance, event stream, active requests,
* and channel status. Fast metrics refresh every 3s, slow health every 10s.
*/
let _fastTimer = null;
let _slowTimer = null;
function formatUptime(seconds) {
const d = Math.floor(seconds / 86400);
const h = Math.floor((seconds % 86400) / 3600);
const m = Math.floor((seconds % 3600) / 60);
const s = seconds % 60;
const parts = [];
if (d > 0) {parts.push(`${d}d`);}
if (h > 0) {parts.push(`${h}h`);}
if (m > 0) {parts.push(`${m}m`);}
parts.push(`${s}s`);
return parts.join(' ');
}
function timeAgo(timestamp) {
const secs = Math.floor((Date.now() - timestamp) / 1000);
if (secs < 60) {return `${secs}s ago`;}
if (secs < 3600) {return `${Math.floor(secs / 60)}m ago`;}
return `${Math.floor(secs / 3600)}h ago`;
}
function formatTime(timestamp) {
const d = new Date(timestamp);
return d.toLocaleTimeString('en-GB', { hour12: false });
}
function escapeHtml(str) {
const div = document.createElement('div');
div.textContent = str;
return div.innerHTML;
}
// ── Initial full render ─────────────────────────────────────────
function renderSkeleton(el) {
el.innerHTML = `
Live Ops Dashboard
Core Counters
Model Performance
Event Stream
Active Requests
Channels
`;
}
// ── Section updaters (targeted DOM updates) ─────────────────────
function updateCounters(metrics, health) {
const el = document.getElementById('ops-counters');
if (!el) {return;}
const sessions = health?.sessions ?? 0;
const errCount = metrics?.errors ?? 0;
const cards = [
{ label: 'Messages Processed', value: String(metrics?.messagesProcessed ?? 0), cls: '' },
{ label: 'Active Sessions', value: String(sessions), cls: '' },
{ label: 'Queue Depth', value: String(metrics?.queueDepth ?? 0), cls: '' },
{ label: 'Uptime', value: formatUptime(metrics?.uptime ?? 0), cls: '' },
{ label: 'Active Requests', value: String(metrics?.activeRequests ?? 0), cls: '' },
{ label: 'Errors', value: String(errCount), cls: errCount > 0 ? 'error' : '' },
];
el.innerHTML = cards.map(c =>
``,
).join('');
}
function updateModelTable(metrics) {
const el = document.getElementById('ops-model-table');
if (!el) {return;}
const mc = metrics?.modelCalls;
const calls = mc?.recentCalls ?? [];
if (calls.length === 0) {
el.innerHTML = 'No model calls recorded yet
';
return;
}
const totalCalls = mc.total ?? 0;
const avgLatency = mc.avgLatency ?? 0;
const errorRate = mc.errorRate ?? 0;
const summaryHtml = `
Total Calls: ${totalCalls}
Avg Latency: ${avgLatency}ms
Error Rate: ${(errorRate * 100).toFixed(2)}%
`;
// Show newest first
const rows = [...calls].reverse().map(c => {
const status = c.error ? '✗' : '✓';
return `
| ${timeAgo(c.timestamp)} |
${escapeHtml(c.provider)} |
${c.latency}ms |
${c.tokensPerSec.toFixed(1)} |
${c.inputTokens}/${c.outputTokens} |
${status} |
`;
}).join('');
el.innerHTML = `
${summaryHtml}
| Time |
Provider |
Latency |
Tokens/sec |
In/Out |
Status |
${rows}
`;
}
function updateEvents(eventsData) {
const el = document.getElementById('ops-events');
if (!el) {return;}
const events = eventsData?.events ?? [];
if (events.length === 0) {
el.innerHTML = 'No events recorded yet
';
return;
}
// Events come newest-first from the API; show newest at bottom for log feel
const reversed = [...events].reverse();
el.innerHTML = reversed.map(e => {
const time = formatTime(e.timestamp);
const level = (e.level || 'info').toUpperCase();
const cls = `event-level-${e.level || 'info'}`;
return `[${time}] [${level}] ${escapeHtml(e.source)}: ${escapeHtml(e.message)}
`;
}).join('');
// Auto-scroll to bottom
el.scrollTop = el.scrollHeight;
}
function updateActiveRequests(requestsData) {
const el = document.getElementById('ops-requests');
if (!el) {return;}
const requests = requestsData?.requests ?? [];
if (requests.length === 0) {
el.innerHTML = 'No active requests
';
return;
}
const rows = requests.map(r => {
const duration = r.durationMs < 1000
? `${r.durationMs}ms`
: `${(r.durationMs / 1000).toFixed(1)}s`;
const started = formatTime(r.startedAt);
return `
| ${escapeHtml(r.sessionId)} |
${escapeHtml(r.channel)} |
${duration} |
${started} |
`;
}).join('');
el.innerHTML = `
| Session |
Channel |
Duration |
Started |
${rows}
`;
}
function updateChannels(channelsData) {
const el = document.getElementById('ops-channels');
if (!el) {return;}
const channels = channelsData?.channels ?? [];
if (channels.length === 0) {
el.innerHTML = 'No channels registered
';
return;
}
el.innerHTML = channels.map(ch =>
`
${escapeHtml(ch.name)}
`,
).join('');
}
// ── Data fetching ───────────────────────────────────────────────
async function fetchFast(client) {
try {
const [metrics, eventsData, requestsData] = await Promise.all([
client.call('system.metrics'),
client.call('system.events', { limit: 50 }),
client.call('system.activeRequests'),
]);
return { metrics, eventsData, requestsData };
} catch {
return null;
}
}
async function fetchSlow(client) {
try {
const [health, channels] = await Promise.all([
client.call('system.health'),
client.call('system.channels'),
]);
return { health, channels };
} catch {
return null;
}
}
// ── Main load function ──────────────────────────────────────────
let _lastHealth = null;
let _lastMetrics = null;
async function loadDashboard(el, client) {
renderSkeleton(el);
// Fetch everything initially
const [fast, slow] = await Promise.all([
fetchFast(client),
fetchSlow(client),
]);
_lastHealth = slow?.health ?? null;
_lastMetrics = fast?.metrics ?? null;
if (fast) {
updateCounters(fast.metrics, _lastHealth);
updateModelTable(fast.metrics);
updateEvents(fast.eventsData);
updateActiveRequests(fast.requestsData);
}
if (slow) {
updateChannels(slow.channels);
}
// Fast refresh: 3 seconds for metrics, events, requests
_fastTimer = setInterval(async () => {
const data = await fetchFast(client);
if (data) {
_lastMetrics = data.metrics;
updateCounters(data.metrics, _lastHealth);
updateModelTable(data.metrics);
updateEvents(data.eventsData);
updateActiveRequests(data.requestsData);
}
}, 3000);
// Slow refresh: 10 seconds for health, channels
_slowTimer = setInterval(async () => {
const data = await fetchSlow(client);
if (data) {
_lastHealth = data.health;
updateCounters(_lastMetrics, _lastHealth);
updateChannels(data.channels);
}
}, 10000);
}
export const DashboardPage = {
async render(el, client) {
await loadDashboard(el, client);
},
teardown() {
if (_fastTimer) {
clearInterval(_fastTimer);
_fastTimer = null;
}
if (_slowTimer) {
clearInterval(_slowTimer);
_slowTimer = null;
}
_lastHealth = null;
_lastMetrics = null;
},
};