/* * SysDeck - AI Gateway (klanker) Panel (v0.3.0) * Author: Jeremy Anderson (https://dcos.net) * * UPSTREAM ATTRIBUTION: this panel is a client of klanker-gate — the * "Frosty Deno" LLM gateway by TykoDev * (https://github.com/TykoDev/klanker-gate, Apache-2.0, vendored * unmodified at ../klanker-gate in the master tarball). klanker-gate * is NOT SysDeck code — see klanker-gate/ATTRIBUTION.md and * THIRD_PARTY.md. Zero upstream code is contained here. * * v0.3.0 NEW MODULE — operator view of the vendored klanker-gate * service (master-build/klanker-gate): the Frosty Deno LLM gateway, * REST on 127.0.0.1:8080, through bridge/klanker.py: * * status / providers / models / vkeys / runtime read polls * logs (limit) → GET /api/logs?limit=N (request ring) * analytics → GET /api/analytics (24h rollups) * service (action) → systemctl klanker-gate.service * journal (n) → journalctl -u klanker-gate -n N * localstack → probes ollama/llama.cpp/koboldcpp/lmstudio/ * sglang/vllm on this host + wiring recipes (the * gateway is NOT SaaS-only — all-local stacks are * first-class: 5 keyless provider types upstream) * * Layout: gateway status card + stat grid (providers, keys, requests, * spend, cache hit rate, avg latency), providers table, virtual keys * table, recent requests table, model catalog, service control card * with a journal viewer. Auto-refreshes every 5s; the refresh loop * re-renders only the data containers — the service/journal cards are * rendered once, so their state survives every tick. Upstream money * is integer micro-USD everywhere; this panel divides by 1e6 and shows * $X.XXXX. If the gateway is down the panel shows the bridge's error * message verbatim (it carries the remediation hint) and keeps * retrying, flipping back online on its own. */ export async function mount(panel, { bridge, EventBus }) { panel.innerHTML = renderSkeleton(); const ctx = { panel, bridge, EventBus, laidOut: false, // full layout rendered (vs offline card) firstRenderDone: false, logsLimit: 25, // recent-requests ring size }; // The status probe decides online vs offline — its error message // carries the remediation hint, shown verbatim when offline. let status = null; let statusErr = null; try { status = await bridge.klanker.status(); } catch (err) { statusErr = err; } if (statusErr || !status || status.ok === false) { renderOffline(ctx, errMessage(statusErr, status)); EventBus.emit('klanker.offline', {}); } else { await mountLayout(ctx, status); } // ── auto-refresh (5s) ───────────────────────────────────────── // Re-fetches status + runtime + analytics + providers + vkeys + // logs and re-renders only the data containers. Also watches for // the gateway going away (→ offline card) or coming back (→ full // layout). if (panel._klankerInterval) clearInterval(panel._klankerInterval); panel._klankerInterval = setInterval(() => { poll(ctx); }, 5000); // Clean up the interval when the panel leaves the DOM // (house pattern from netsec.js / fester.js). // one observer per panel: a re-mount releases the previous one // instead of stacking body-wide observers on every refresh if (panel.klankerObserver) panel.klankerObserver.disconnect(); if (panel.klankerObserver) panel.klankerObserver.disconnect(); panel.klankerObserver = new MutationObserver(() => { if (!document.body.contains(panel)) { clearInterval(panel._klankerInterval); panel.klankerObserver.disconnect(); } }); panel.klankerObserver.observe(document.body, { childList: true, subtree: true }); } // ── refresh loop ──────────────────────────────────────────────────── async function poll(ctx) { let status = null; let statusErr = null; try { status = await ctx.bridge.klanker.status(); } catch (err) { statusErr = err; } const online = !statusErr && status && status.ok !== false; if (online && !ctx.laidOut) { // Gateway came back after an offline render — build the layout. await mountLayout(ctx, status); return; } if (!online && ctx.laidOut) { // Gateway dropped — swap to the offline card. renderOffline(ctx, errMessage(statusErr, status)); ctx.EventBus.emit('klanker.offline', {}); return; } if (!online) return; // still offline — the card is already shown await refreshData(ctx, status); } async function refreshNow(ctx) { // Immediate re-fetch after a user action (service control/manual). let status = null; let statusErr = null; try { status = await ctx.bridge.klanker.status(); } catch (err) { statusErr = err; } if (!statusErr && status && status.ok !== false && ctx.laidOut) { await refreshData(ctx, status); } } async function refreshData(ctx, status) { if (!ctx.laidOut) return; const { bridge, panel } = ctx; // Per-call try/catch: one failing endpoint must not sink the panel. const runtimeResp = await safe(bridge.klanker.runtime()); const analyticsResp = await safe(bridge.klanker.analytics()); const providersResp = await safe(bridge.klanker.providers()); const vkeysResp = await safe(bridge.klanker.vkeys()); const logsResp = await safe(bridge.klanker.logs(ctx.logsLimit)); const statusEl = panel.querySelector('#klanker-status'); const providersEl = panel.querySelector('#klanker-providers'); const vkeysEl = panel.querySelector('#klanker-vkeys'); const logsEl = panel.querySelector('#klanker-logs'); if (statusEl) statusEl.innerHTML = renderStatus(status, runtimeResp, analyticsResp, providersResp, vkeysResp, logsResp); if (providersEl) providersEl.innerHTML = renderProviders(providersResp, logsResp); if (vkeysEl) vkeysEl.innerHTML = renderVkeys(vkeysResp, analyticsResp); if (logsEl) logsEl.innerHTML = renderLogs(logsResp); wireStatus(ctx); if (!ctx.firstRenderDone) { ctx.firstRenderDone = true; const totals = analyticsTotals(analyticsResp); ctx.EventBus.emit('klanker.loaded', { requests: totals.requests }); } } // The model catalog is fetched once per layout (it changes only when // the operator edits provider configs — not on the 5s poll). let modelsRespCache = null; async function fetchModels(ctx) { modelsRespCache = await safe(ctx.bridge.klanker.models()); const modelsEl = ctx.panel.querySelector('#klanker-models'); if (modelsEl) modelsEl.innerHTML = renderModels(modelsRespCache); } // ── layout ────────────────────────────────────────────────────────── async function mountLayout(ctx, status) { const { panel } = ctx; ctx.laidOut = true; const baseUrl = (status && status.base_url) || 'http://127.0.0.1:8080'; panel.innerHTML = `

SysDeck AI Gateway

klanker-gate — Frosty Deno LLM gateway · local stack (ollama · llama.cpp · koboldcpp) + providers, vkeys, spend & runtime (${escapeHtml(baseUrl)} · REST)

${renderServiceCard()} ${renderJournalCard()}

Gateway configured via KLANKER_URL / KLANKER_ADMIN_TOKEN env (admin token is sent as a bearer header only — never displayed).

Upstream: klanker-gate (“Frosty Deno”) by TykoDev — github.com/TykoDev/klanker-gate · Apache-2.0 · vendored unmodified (SysDeck adds only the arch/ packaging).

`; wireServiceCard(ctx); wireJournalCard(ctx); await fetchModels(ctx); fetchLocalStack(ctx); await refreshUnitState(ctx); await refreshData(ctx, status); } function renderOffline(ctx, message) { ctx.laidOut = false; modelsRespCache = null; ctx.panel.innerHTML = `

SysDeck AI Gateway

klanker-gate — Frosty Deno LLM gateway (master-build/klanker-gate · REST)

AI Gateway Offline

offline

${escapeHtml(message)}

Retrying every 5 seconds — the panel reconnects automatically when the gateway is back.

Upstream: klanker-gate (“Frosty Deno”) by TykoDev — github.com/TykoDev/klanker-gate · Apache-2.0 · vendored unmodified (SysDeck adds only the arch/ packaging).

`; } // ── status card + stat grid ───────────────────────────────────────── function renderStatus(status, runtimeResp, analyticsResp, providersResp, vkeysResp, logsResp) { const runtime = (runtimeResp && runtimeResp.ok !== false && !runtimeResp.error) ? runtimeResp : null; const analytics = analyticsTotals(analyticsResp); const aOk = analyticsResp && analyticsResp.ok !== false && !analyticsResp.error; const providers = listProviders(providersResp); const vkeys = listVkeys(vkeysResp); const workers = (runtime && runtime.workers) || {}; const cache = (runtime && runtime.cache) || {}; const process = (runtime && runtime.process) || {}; // Avg latency over the recent request ring (honest: labeled "recent"). const logs = listLogs(logsResp); let latencySum = 0, latencyN = 0; for (const r of logs) { if (Number.isFinite(Number(r.durationMs))) { latencySum += Number(r.durationMs); latencyN++; } } const avgLatency = latencyN > 0 ? latencySum / latencyN : null; return `

Gateway

online
klanker-gate v${escapeHtml(status && status.version)}${process.denoVersion ? ' · Deno ' + escapeHtml(process.denoVersion) : ''} ${escapeHtml(status && status.base_url)} · ${escapeHtml(status && status.transport)}${status && status.auth ? ' · admin auth' : ' · open admin'}
workers ${workers.effective != null ? n(workers.effective) : '—'}/${workers.configured != null ? n(workers.configured) : '—'} effective/configured${workers.platform ? ' · ' + escapeHtml(workers.platform) : ''} up ${escapeHtml(fmtDuration(process.uptimeSeconds))}
cache ${escapeHtml(cache.mode || '—')}${cache.sharedTier ? ' · shared L2' : ''}${Number.isFinite(Number(cache.localEntries)) ? ' · ' + n(cache.localEntries) + ' L1 entries' : ''} ${(workers.reason ? escapeHtml(workers.reason) : '')}
${runtime ? '' : `

runtime view unavailable${runtimeResp && runtimeResp.error ? ': ' + escapeHtml(runtimeResp.error) : ''}

`}
${providers != null ? providers.length : '—'}
providers
${vkeys != null ? vkeys.length : '—'}
virtual keys
${aOk ? n(analytics.requests) : '—'}
requests 24h
${aOk ? fmtUsd(analytics.costMicroUsd) : '—'}
spend 24h
${aOk ? fmtHitRate(analytics.cacheHits, analytics.cacheMisses) : '—'}
cache hit rate
${avgLatency != null ? fmtMs(avgLatency) : '—'}
avg latency (recent)
`; } // ── providers table ───────────────────────────────────────────────── function renderProviders(providersResp, logsResp) { const providers = listProviders(providersResp); if (providers == null) { const msg = (providersResp && providersResp.error) ? providersResp.error : 'provider list unavailable'; return `

Providers

${escapeHtml(msg)}

`; } // Per-provider average latency over the recent request ring. const sums = new Map(); for (const r of listLogs(logsResp)) { if (!r.provider) continue; const dur = Number(r.durationMs); if (!Number.isFinite(dur)) continue; const e = sums.get(r.provider) || { total: 0, n: 0 }; e.total += dur; e.n += 1; sums.set(r.provider, e); } const rows = providers.map((p) => { const models = Array.isArray(p.models) ? p.models.length : 0; const enabled = p.enabled !== false; const hasCreds = !!(p.hasApiKey || p.hasCloudCredentials); const dot = enabled ? (hasCreds ? 'ok' : 'warn') : 'off'; const dotTitle = enabled ? (hasCreds ? 'enabled with credentials' : 'enabled but no credentials configured') : 'disabled'; const s = sums.get(p.id); const latency = s && s.n > 0 ? fmtMs(s.total / s.n) : '—'; return ` ${healthDot(dot, dotTitle)} ${escapeHtml(p.id)} ${escapeHtml(p.type || '—')} ${n(models)} ${enabled ? 'enabled' : 'disabled'} ${latency} `; }).join(''); return `

Providers

${rows || ''}
NameKindModelsStatusLatency (recent)
No providers configured — add one via the gateway admin UI.

Health dot: green = enabled with credentials · amber = enabled without credentials · gray = disabled. Latency is the mean request duration over the recent ring below.

`; } function healthDot(state, title) { const color = state === 'ok' ? 'var(--sysdeck-accent-success)' : state === 'warn' ? 'var(--sysdeck-accent-warn)' : 'var(--sysdeck-muted)'; return ``; } // ── virtual keys table ────────────────────────────────────────────── function renderVkeys(vkeysResp, analyticsResp) { const vkeys = listVkeys(vkeysResp); if (vkeys == null) { const msg = (vkeysResp && vkeysResp.error) ? vkeysResp.error : 'virtual key list unavailable'; return `

Virtual Keys

${escapeHtml(msg)}

`; } // 24h attribution join from the analytics rollup (by virtual key). const byKey = new Map(); if (analyticsResp && analyticsResp.ok !== false && Array.isArray(analyticsResp.byVirtualKey)) { for (const v of analyticsResp.byVirtualKey) { if (v && v.virtualKeyId != null) byKey.set(String(v.virtualKeyId), v); } } const rows = vkeys.map((k) => { const stats = byKey.get(String(k.id)); const scope = k.teamId ? `team ${escapeHtml(k.teamId)}` : (k.allowedProviders || k.allowedModels) ? `${n((k.allowedProviders || []).length)} providers · ${n((k.allowedModels || []).length)} models` : 'unrestricted'; const rl = k.rateLimit ? `${n(k.rateLimit.maxRequests)} / ${fmtWindowMs(k.rateLimit.windowMs)}` : '—'; return ` ${escapeHtml(k.name || k.id)}${k.tokenHint ? ` …${escapeHtml(k.tokenHint)}` : ''} ${scope} ${stats ? n(stats.requests) : '—'} ${stats ? n(stats.totalTokens) : '—'} ${stats ? fmtUsd(stats.costMicroUsd) : '—'} ${rl} ${k.enabled !== false ? 'enabled' : 'disabled'} `; }).join(''); return `

Virtual Keys

${rows || ''}
KeyTeam / scopeRequests 24hTokens 24hCost 24hRate limitState
No virtual keys — create one via the gateway admin UI.

Requests / tokens / cost columns come from the 24h analytics rollup; they show — when the gateway has no keyed traffic yet. Token values are never stored or shown — only the last-4 hint.

`; } // ── recent requests table ─────────────────────────────────────────── function renderLogs(logsResp) { const logs = listLogs(logsResp); if (logs == null) { const msg = (logsResp && logsResp.error) ? logsResp.error : 'request log unavailable'; return `

Recent Requests

${escapeHtml(msg)}

`; } const rows = logs.map((r) => { const tokens = (r.promptTokens != null || r.completionTokens != null) ? `${n(r.promptTokens)} / ${n(r.completionTokens)}` : '—'; const cost = r.costMicroUsd != null ? fmtUsd(r.costMicroUsd) : '—'; const cache = r.cacheHit === true ? 'hit' : r.cacheHit === false ? 'miss' : '—'; return ` ${escapeHtml(fmtIsoTime(r.ts))} ${escapeHtml(r.model || '—')} ${escapeHtml(r.provider || '—')} ${tokens} ${cost} ${Number.isFinite(Number(r.durationMs)) ? fmtMs(r.durationMs) : '—'} ${statusBadge(r.status)} ${cache} `; }).join(''); return `

Recent Requests

${rows || ''}
TimeModelProviderTokens in/outCostLatencyStatusCache
No requests recorded yet.

Last ${logs.length} requests from the gateway's in-memory ring (FROSTY_LOG_STORE=pg enables the durable audit trail). Costs are micro-USD upstream, shown here in USD.

`; } // ── model catalog ─────────────────────────────────────────────────── function renderModels(modelsResp) { if (!modelsResp) return ''; if (modelsResp.ok === false || modelsResp.error) { return `

Model Catalog

${escapeHtml(modelsResp.error || 'model catalog unavailable')}

`; } const data = Array.isArray(modelsResp.data) ? modelsResp.data : []; const rows = data.map((m) => ` ${escapeHtml(m.id)} ${escapeHtml(m.owned_by || '—')} `).join(''); return `

Model Catalog

${rows || ''}
ModelProvider
No models enabled — add provider accounts with models.

GET /v1/models — the aggregated enabled set, ids prefixed account/model.

`; } // ── local stack wiring card (probed; independent of the gateway) ──── function renderLocalStackLoading() { return `

Local stack wiring

probing local AI backends (0.4s timeout each)…

`; } function renderLocalStackError(message) { return `

Local stack wiring

${escapeHtml(message)}

`; } function renderLocalStack(data) { const backends = Array.isArray(data.backends) ? data.backends : []; const reachable = Number(data.reachable) || 0; const envLines = backends.filter((b) => b.env_wiring).map((b) => b.env_wiring).join('\n'); const rows = backends.map((b) => { const up = !!b.reachable; return ` ${healthDot(up ? 'ok' : 'off', up ? 'answered /v1/models' : 'no answer on this host')} ${escapeHtml(b.name)} ${escapeHtml(b.provider_type)} ${escapeHtml(b.base_url)} ${up ? 'up · ' + n(b.latency_ms) + 'ms' : 'offline'} ${escapeHtml(b.caps)} `; }).join(''); return `

Local stack wiring

${reachable}/${n(data.count)} reachable

klanker-gate is not SaaS-only: ollama, LM Studio and SGLang are native keyless provider types; llama.cpp (llama-server), KoboldCpp and vLLM plug in through the generic openai-compatible type — the whole inference stack can run local, keyless, zero marginal cost. Rows are live probes of each backend's /v1/models from this host.

${rows}
BackendProvider typeBase URL (probed)StatusCapabilities
env wiring (gateway .env)
${escapeHtml(envLines)}
admin API — llama.cpp + koboldcpp accounts
${escapeHtml(data.admin_register_example || '')}

Env registers one account per type; to run llama.cpp and koboldcpp side by side, register each via POST /api/providers, then refresh-models auto-discovers its catalog. Port note: llama-server defaults to :8080 — the gateway's own port — so run it elsewhere (8081 here) or move the gateway.

`; } async function fetchLocalStack(ctx) { const el = ctx.panel.querySelector('#klanker-localstack'); if (!el) return; el.innerHTML = renderLocalStackLoading(); let res = null; let err = null; try { res = await ctx.bridge.klanker.localstack(); } catch (e) { err = e; } if (err || !res || res.ok === false) { el.innerHTML = renderLocalStackError(errMessage(err, res)); return; } el.innerHTML = renderLocalStack(res); wireLocalStackCard(ctx); } function wireLocalStackCard(ctx) { const { panel } = ctx; panel.querySelector('#btn-klanker-reprobe')?.addEventListener('click', () => { fetchLocalStack(ctx); }); panel.querySelectorAll('.klanker-copy-btn').forEach((btn) => { btn.addEventListener('click', async () => { const code = btn.getAttribute('data-code') || ''; try { await navigator.clipboard.writeText(code); const label = btn.textContent; btn.textContent = 'copied'; setTimeout(() => { btn.textContent = label; }, 1600); } catch (e) { btn.textContent = 'select & copy'; } }); }); } // ── service control card ──────────────────────────────────────────── function renderServiceCard() { return `

Service — klanker-gate.service

systemd unit: … Actions run via systemctl through the cockpit superuser channel.
`; } function wireServiceCard(ctx) { const { panel } = ctx; panel.querySelector('#btn-klanker-start')?.addEventListener('click', () => { doService(ctx, 'start'); }); panel.querySelector('#btn-klanker-stop')?.addEventListener('click', () => { // Destructive action — confirm first (house pattern from // builder.js import confirm). const go = window.confirm( 'Stop the klanker-gate service?\n\n' + 'All in-flight inference requests will be dropped and the\n' + 'gateway will stop serving until started again.' ); if (go) doService(ctx, 'stop'); }); panel.querySelector('#btn-klanker-restart')?.addEventListener('click', () => { doService(ctx, 'restart'); }); } async function doService(ctx, action) { const result = ctx.panel.querySelector('#klanker-service-result'); const buttons = ['start', 'stop', 'restart'].map((a) => ctx.panel.querySelector(`#btn-klanker-${a}`)); buttons.forEach((b) => { if (b) b.disabled = true; }); if (result) result.textContent = `${action} requested…`; let res = null; let err = null; try { res = await ctx.bridge.klanker.service(action); } catch (e) { err = e; } const msg = err ? `${action} failed: ${errMessage(err, null)}` : (res && res.ok) ? `${action} ok` : `${action} failed: ${(res && (res.stderr || res.error)) || 'unknown error'}`; if (result) result.textContent = msg; buttons.forEach((b) => { if (b) b.disabled = false; }); // Refresh the unit state + data right away so the panel reflects // the new service reality immediately. await refreshUnitState(ctx); refreshNow(ctx); } async function refreshUnitState(ctx) { const el = ctx.panel.querySelector('#klanker-unit-state'); if (!el) return; let res = null; try { res = await ctx.bridge.klanker.service('status'); } catch { res = null; } if (res && res.ok) { el.textContent = `${res.active || 'unknown'} (${res.sub || 'unknown'}) · ${res.enabled || 'unknown'} at boot`; } else { el.textContent = (res && res.error) ? res.error : 'unknown'; } } // ── journal viewer ────────────────────────────────────────────────── function renderJournalCard() { return `

Journal — klanker-gate

(journal not loaded yet)
`; } function wireJournalCard(ctx) { const { panel } = ctx; panel.querySelector('#btn-klanker-journal')?.addEventListener('click', () => { loadJournal(ctx); }); // Load once on layout so the viewer has content immediately. loadJournal(ctx); } async function loadJournal(ctx) { const pre = ctx.panel.querySelector('#klanker-journal-pre'); if (!pre) return; const select = ctx.panel.querySelector('#klanker-journal-lines'); const lines = Number(select?.value || '40'); pre.textContent = `loading journal (${lines} lines)…`; let res = null; let err = null; try { res = await ctx.bridge.klanker.journal(lines); } catch (e) { err = e; } if (err) { pre.textContent = `journal failed: ${errMessage(err, null)}`; return; } if (res && res.ok) { pre.textContent = res.log || '(no journal entries)'; } else { pre.textContent = `journal failed: ${(res && (res.stderr || res.error)) || 'unknown error'}`; } } // ── utilities ─────────────────────────────────────────────────────── async function safe(p) { try { const v = await p; return v ?? null; } catch { return null; } } function errMessage(err, resp) { if (resp && resp.error) return String(resp.error); if (err && err.message) return String(err.message); if (err) return String(err); return 'klanker-gate service unreachable'; } function listProviders(resp) { if (!resp || resp.ok === false || resp.error) return null; return Array.isArray(resp.providers) ? resp.providers : []; } function listVkeys(resp) { if (!resp || resp.ok === false || resp.error) return null; return Array.isArray(resp.virtualKeys) ? resp.virtualKeys : []; } function listLogs(resp) { if (!resp || resp.ok === false || resp.error) return null; return Array.isArray(resp.logs) ? resp.logs : []; } function analyticsTotals(resp) { if (!resp || resp.ok === false || resp.error || !resp.totals) { return { requests: 0, costMicroUsd: 0, cacheHits: 0, cacheMisses: 0 }; } return resp.totals; } function wireStatus(ctx) { const { panel } = ctx; panel.querySelector('#btn-klanker-refresh')?.addEventListener('click', () => { refreshNow(ctx); }); } function n(v) { const num = Number(v); return Number.isFinite(num) ? num : 0; } function fmtNum(v, digits) { const num = Number(v); return Number.isFinite(num) ? num.toFixed(digits) : '—'; } /** micro-USD (the repo-wide cost unit) → "$X.XXXX". */ function fmtUsd(microUsd) { const num = Number(microUsd); if (!Number.isFinite(num)) return '—'; return '$' + (num / 1e6).toFixed(4); } function fmtHitRate(hits, misses) { const h = Number(hits), m = Number(misses); if (!Number.isFinite(h) || !Number.isFinite(m) || (h + m) === 0) return '—'; return ((h / (h + m)) * 100).toFixed(1) + '%'; } function fmtMs(v) { const num = Number(v); if (!Number.isFinite(num)) return '—'; if (num >= 1000) return (num / 1000).toFixed(2) + ' s'; return Math.round(num) + ' ms'; } function fmtWindowMs(ms) { const num = Number(ms); if (!Number.isFinite(num) || num <= 0) return '—'; if (num % 86_400_000 === 0) return (num / 86_400_000) + 'd'; if (num % 3_600_000 === 0) return (num / 3_600_000) + 'h'; if (num % 60_000 === 0) return (num / 60_000) + 'm'; if (num % 1000 === 0) return (num / 1000) + 's'; return num + ' ms'; } function fmtIsoTime(ts) { if (!ts) return '—'; const d = new Date(ts); return Number.isNaN(d.getTime()) ? String(ts) : d.toLocaleTimeString(); } function fmtDuration(s) { const num = Number(s); if (!Number.isFinite(num) || num < 0) return '—'; const h = Math.floor(num / 3600); const m = Math.floor((num % 3600) / 60); if (h > 0) return `${h}h ${m}m`; if (m > 0) return `${m}m ${Math.floor(num % 60)}s`; return `${Math.floor(num)}s`; } function statusBadge(status) { const num = Number(status); if (!Number.isFinite(num)) return '—'; const cls = num >= 200 && num < 300 ? 'suite-badge success' : num >= 400 ? 'suite-badge danger' : 'suite-badge warn'; return `${num}`; } function escapeHtml(s) { return String(s == null ? '' : s) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } function renderSkeleton() { return `
`; }