import type { VaultSnapshot, FlowWindow, Alert } from "./monitor.js"; const fmt = (v: bigint, decimals: number, maxFrac = 4): string => { const neg = v < 0n; const abs = neg ? -v : v; const base = 10n ** BigInt(decimals); const whole = abs / base; const frac = abs % base; let fracStr = frac.toString().padStart(decimals, "0").slice(0, maxFrac).replace(/0+$/, ""); const wholeStr = whole.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); return (neg ? "-" : "") + wholeStr + (fracStr ? "." + fracStr : ""); }; const esc = (s: string): string => s.replace(/&/g, "&").replace(//g, ">"); /** * Render a self-contained HTML dashboard (KPI cards, health badges, alerts, flow summary, NAV * gauge) from a {@link VaultSnapshot}, an optional {@link FlowWindow}, and alerts. Chart.js is the * only external dependency (loaded from a CDN); everything else is inlined, so the file opens * directly in a browser. Re-run the monitor to refresh. */ export function renderDashboard( snap: VaultSnapshot, flows?: FlowWindow, alerts: Alert[] = [], ): string { const d = snap.decimals; const health = snap.accountant.isPaused || snap.teller.isPaused ? { label: "PAUSED", color: "#dc2626" } : alerts.some((a) => a.level === "critical") ? { label: "CRITICAL", color: "#dc2626" } : alerts.some((a) => a.level === "warn") ? { label: "WARNING", color: "#d97706" } : { label: "HEALTHY", color: "#16a34a" }; const kpis = [ { label: "NAV (rate)", value: fmt(snap.rate, d, 6) }, { label: "AUM", value: fmt(snap.aum, d, 2) }, { label: "Total supply", value: fmt(snap.totalSupply, d, 2) }, { label: "Fees owed", value: fmt(snap.accountant.feesOwedInBase, d, 2) }, ]; const badges = [ { label: "Accountant", on: !snap.accountant.isPaused }, { label: "Teller", on: !snap.teller.isPaused }, ...(snap.queue ? [{ label: "Queue", on: !snap.queue.isPaused }] : []), ]; const alertRows = alerts.length ? alerts .map( (a) => `
${a.level.toUpperCase()}${esc(a.code)}${esc(a.message)}
`, ) .join("") : `
OKNo alerts.
`; const flowCards = flows ? `
Deposits
${flows.deposits.count}
${fmt(flows.deposits.assets, d, 2)} in
Withdrawals
${flows.withdrawals.count}
${fmt(flows.withdrawals.shares, d, 2)} shares
Queue filled
${flows.queue.fulfilled}
${fmt(flows.queue.wantPaid, d, 2)} paid
Queue pending
${flows.queue.pending}
open requests

Flows over blocks ${flows.fromBlock}–${flows.toBlock}. Net share flow: ${fmt(flows.netShareFlow, d, 2)}.

` : ""; const data = JSON.stringify( { snapshot: snap, flows, alerts, }, (_k, v) => (typeof v === "bigint" ? v.toString() : v), 2, ); return ` ${esc(snap.name)} (${esc(snap.symbol)}) — Lucidly vault monitor

${esc(snap.name)} (${esc(snap.symbol)})

${snap.vault}
${health.label}
${badges.map((b) => `${b.label}: ${b.on ? "live" : "paused"}`).join("")}

Key metrics

${kpis.map((k) => `
${k.label}
${k.value}
`).join("")}

Alerts

${alertRows}

Flows

${flowCards || '

No flow window supplied.

'}

Configuration

NAV bounds+${snap.accountant.upperBoundBps} bps / -${snap.accountant.lowerBoundBps} bps, min delay ${snap.accountant.minimumUpdateDelaySec}s
Feesplatform ${snap.accountant.platformFeeBps} bps, performance ${snap.accountant.performanceFeeBps} bps
Share lock${snap.teller.shareLockPeriodSec}s
Last NAV update${snap.accountant.lastUpdateTimestamp ? new Date(snap.accountant.lastUpdateTimestamp * 1000).toISOString() : "—"}
Snapshot taken${new Date(snap.timestamp * 1000).toISOString()}

Raw snapshot


`; }