@i4ctime/q-ring 0.2.9 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-HOMNGA55.js +651 -0
- package/dist/chunk-HOMNGA55.js.map +1 -0
- package/dist/chunk-WQPJ2FTM.js +673 -0
- package/dist/chunk-WQPJ2FTM.js.map +1 -0
- package/dist/dashboard-7GII7BS2.js +482 -0
- package/dist/dashboard-7GII7BS2.js.map +1 -0
- package/dist/dashboard-JTALLJM6.js +482 -0
- package/dist/dashboard-JTALLJM6.js.map +1 -0
- package/dist/index.js +48 -648
- package/dist/index.js.map +1 -1
- package/dist/mcp.js +35 -626
- package/dist/mcp.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/dashboard.ts","../src/core/dashboard-html.ts"],"sourcesContent":["/**\n * Quantum Status Dashboard: local HTTP server with SSE live updates.\n *\n * Collects a full snapshot of all quantum state every few seconds and\n * pushes it to connected browsers. Never exposes secret values.\n */\n\nimport { createServer, type IncomingMessage, type ServerResponse, type Server } from \"node:http\";\nimport { listSecrets } from \"./keyring.js\";\nimport { checkDecay, type DecayStatus, type QuantumEnvelope } from \"./envelope.js\";\nimport { listEntanglements, type EntanglementPair } from \"./entanglement.js\";\nimport { tunnelList } from \"./tunnel.js\";\nimport { queryAudit, detectAnomalies, type AuditEvent, type AccessAnomaly } from \"./observer.js\";\nimport { collapseEnvironment, type CollapseResult } from \"./collapse.js\";\nimport { getDashboardHtml } from \"./dashboard-html.js\";\n\nexport interface SecretSnapshot {\n key: string;\n scope: string;\n type: \"collapsed\" | \"superposition\";\n environments?: string[];\n defaultEnv?: string;\n decay: DecayStatus;\n accessCount: number;\n lastAccessedAt?: string;\n createdAt: string;\n updatedAt: string;\n description?: string;\n tags?: string[];\n entangled?: { service: string; key: string }[];\n}\n\nexport interface TunnelSnapshot {\n id: string;\n createdAt: number;\n expiresAt?: number;\n accessCount: number;\n maxReads?: number;\n}\n\nexport interface DashboardSnapshot {\n timestamp: string;\n secrets: SecretSnapshot[];\n health: { healthy: number; stale: number; expired: number; noDecay: number; total: number };\n entanglements: EntanglementPair[];\n tunnels: TunnelSnapshot[];\n audit: AuditEvent[];\n anomalies: AccessAnomaly[];\n environment: CollapseResult | null;\n}\n\nfunction toSecretSnapshot(entry: {\n key: string;\n scope: string;\n envelope?: QuantumEnvelope;\n decay?: DecayStatus;\n}): SecretSnapshot {\n const envelope = entry.envelope;\n const decay = envelope ? checkDecay(envelope) : {\n isExpired: false,\n isStale: false,\n lifetimePercent: 0,\n secondsRemaining: null,\n timeRemaining: null,\n };\n\n return {\n key: entry.key,\n scope: entry.scope,\n type: envelope?.states ? \"superposition\" : \"collapsed\",\n environments: envelope?.states ? Object.keys(envelope.states) : undefined,\n defaultEnv: envelope?.defaultEnv,\n decay,\n accessCount: envelope?.meta.accessCount ?? 0,\n lastAccessedAt: envelope?.meta.lastAccessedAt,\n createdAt: envelope?.meta.createdAt ?? \"\",\n updatedAt: envelope?.meta.updatedAt ?? \"\",\n description: envelope?.meta.description,\n tags: envelope?.meta.tags,\n entangled: envelope?.meta.entangled,\n };\n}\n\nexport function collectSnapshot(): DashboardSnapshot {\n const entries = listSecrets({ source: \"api\" });\n\n const secrets = entries.map(toSecretSnapshot);\n\n let healthy = 0;\n let stale = 0;\n let expired = 0;\n let noDecay = 0;\n\n for (const s of secrets) {\n if (!s.decay.timeRemaining) {\n noDecay++;\n } else if (s.decay.isExpired) {\n expired++;\n } else if (s.decay.isStale) {\n stale++;\n } else {\n healthy++;\n }\n }\n\n return {\n timestamp: new Date().toISOString(),\n secrets,\n health: { healthy, stale, expired, noDecay, total: secrets.length },\n entanglements: listEntanglements(),\n tunnels: tunnelList(),\n audit: queryAudit({ limit: 50 }),\n anomalies: detectAnomalies(),\n environment: collapseEnvironment(),\n };\n}\n\nexport interface DashboardServerOptions {\n port?: number;\n}\n\nexport function startDashboardServer(\n options: DashboardServerOptions = {},\n): { port: number; close: () => void; server: Server } {\n const port = options.port ?? 9876;\n const clients = new Set<ServerResponse>();\n let intervalHandle: ReturnType<typeof setInterval> | null = null;\n\n const html = getDashboardHtml();\n\n function broadcast() {\n const snapshot = collectSnapshot();\n const data = `data: ${JSON.stringify(snapshot)}\\n\\n`;\n for (const res of clients) {\n try {\n res.write(data);\n } catch {\n clients.delete(res);\n }\n }\n }\n\n const server = createServer((req: IncomingMessage, res: ServerResponse) => {\n const url = req.url ?? \"/\";\n\n if (url === \"/events\") {\n res.writeHead(200, {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"keep-alive\",\n \"Access-Control-Allow-Origin\": \"*\",\n });\n\n // Send initial snapshot immediately\n const snapshot = collectSnapshot();\n res.write(`data: ${JSON.stringify(snapshot)}\\n\\n`);\n\n clients.add(res);\n req.on(\"close\", () => clients.delete(res));\n return;\n }\n\n if (url === \"/api/status\") {\n const snapshot = collectSnapshot();\n res.writeHead(200, {\n \"Content-Type\": \"application/json\",\n \"Access-Control-Allow-Origin\": \"*\",\n });\n res.end(JSON.stringify(snapshot, null, 2));\n return;\n }\n\n // Serve the dashboard HTML\n res.writeHead(200, { \"Content-Type\": \"text/html; charset=utf-8\" });\n res.end(html);\n });\n\n server.listen(port, \"127.0.0.1\", () => {\n intervalHandle = setInterval(broadcast, 5000);\n if (intervalHandle && typeof intervalHandle === \"object\" && \"unref\" in intervalHandle) {\n intervalHandle.unref();\n }\n });\n\n return {\n port,\n close: () => {\n if (intervalHandle) clearInterval(intervalHandle);\n for (const res of clients) {\n try { res.end(); } catch { /* noop */ }\n }\n clients.clear();\n server.close();\n },\n server,\n };\n}\n","/**\n * Self-contained HTML dashboard for q-ring quantum status.\n * Uses the Quantum Fluidity design system: Obsidian Void background,\n * glassmorphism cards, Electric Cyan / Hyper-Violet accents.\n *\n * Zero dependencies — inline CSS + vanilla JS with EventSource for SSE.\n */\n\nexport function getDashboardHtml(): string {\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\"/>\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"/>\n<title>q-ring \\u2014 quantum status</title>\n<style>\n*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}\n:root{\n --void:#050505;--mist:#1A1A2E;--ghost:#F8F9FA;--slate:#7A8490;\n --cyan:#00D1FF;--violet:#B266FF;--emerald:#00E676;--amber:#FFB800;--pink:#FF0055;\n --glass-bg:rgba(255,255,255,0.03);--glass-border:rgba(255,255,255,0.06);\n --shadow-q:0 0 20px rgba(0,209,255,0.12),0 0 40px rgba(178,102,255,0.08);\n --shadow-glass:0 4px 24px -1px rgba(0,0,0,0.3),0 0 1px rgba(255,255,255,0.04);\n --glow-sm:0 0 8px rgba(0,209,255,0.2);\n --radius:12px;\n}\nhtml{background:var(--void);color:var(--ghost);font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;font-size:14px;line-height:1.5}\nbody{min-height:100vh;overflow-x:hidden;position:relative}\n\n/* Mesh blobs */\n.blob{position:fixed;border-radius:50%;filter:blur(100px);opacity:.18;pointer-events:none;z-index:0}\n.blob-1{width:600px;height:600px;top:-120px;left:-100px;background:radial-gradient(circle,var(--cyan),transparent 70%);animation:drift1 22s ease-in-out infinite}\n.blob-2{width:500px;height:500px;bottom:-80px;right:-60px;background:radial-gradient(circle,var(--violet),transparent 70%);animation:drift2 26s ease-in-out infinite}\n.blob-3{width:350px;height:350px;top:40%;left:50%;background:radial-gradient(circle,var(--emerald),transparent 70%);animation:drift3 30s ease-in-out infinite;opacity:.08}\n@keyframes drift1{0%,100%{transform:translate(0,0) scale(1)}33%{transform:translate(60px,-40px) scale(1.08)}66%{transform:translate(-30px,50px) scale(.95)}}\n@keyframes drift2{0%,100%{transform:translate(0,0) scale(1)}33%{transform:translate(-50px,30px) scale(1.05)}66%{transform:translate(40px,-60px) scale(.92)}}\n@keyframes drift3{0%,100%{transform:translate(-50%,-50%) scale(1)}50%{transform:translate(-40%,-60%) scale(1.1)}}\n\n.container{position:relative;z-index:1;max-width:1280px;margin:0 auto;padding:24px 20px 48px}\n\n/* Header */\n.header{display:flex;align-items:center;justify-content:space-between;margin-bottom:28px}\n.header h1{font-size:1.6rem;font-weight:700;letter-spacing:-.02em}\n.header h1 span{background:linear-gradient(135deg,var(--cyan),var(--violet));-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent}\n.status-dot{width:8px;height:8px;border-radius:50%;background:var(--emerald);box-shadow:0 0 8px var(--emerald);display:inline-block;margin-right:8px;animation:pulse 2s ease-in-out infinite}\n.status-dot.disconnected{background:var(--pink);box-shadow:0 0 8px var(--pink)}\n@keyframes pulse{0%,100%{opacity:1}50%{opacity:.4}}\n.conn-label{font-size:.75rem;color:var(--slate);display:flex;align-items:center;gap:6px}\n\n/* Grid */\n.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(360px,1fr));gap:16px}\n.grid-wide{grid-column:1/-1}\n\n/* Cards */\n.card{background:var(--glass-bg);backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px);border:1px solid var(--glass-border);border-radius:var(--radius);padding:18px 20px;box-shadow:var(--shadow-glass);transition:border-color .25s,box-shadow .25s}\n.card:hover{border-color:rgba(255,255,255,0.1);box-shadow:var(--shadow-glass),var(--glow-sm)}\n.card-title{font-size:.7rem;text-transform:uppercase;letter-spacing:.08em;color:var(--slate);margin-bottom:12px;display:flex;align-items:center;gap:6px}\n.card-title .icon{font-size:.85rem}\n\n/* Health donut */\n.health-row{display:flex;align-items:center;gap:24px}\n.donut-wrap{position:relative;width:100px;height:100px;flex-shrink:0}\n.donut-wrap svg{transform:rotate(-90deg)}\n.donut-wrap .donut-label{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;flex-direction:column;font-size:1.5rem;font-weight:700;line-height:1}\n.donut-wrap .donut-label small{font-size:.6rem;color:var(--slate);font-weight:400;margin-top:2px}\n.health-legend{display:flex;flex-direction:column;gap:6px}\n.legend-item{display:flex;align-items:center;gap:8px;font-size:.8rem}\n.legend-dot{width:8px;height:8px;border-radius:50%;flex-shrink:0}\n\n/* Decay bars */\n.decay-list{display:flex;flex-direction:column;gap:8px;max-height:280px;overflow-y:auto}\n.decay-item{display:flex;align-items:center;gap:10px}\n.decay-key{font-family:'SF Mono',SFMono-Regular,Consolas,'Liberation Mono',Menlo,monospace;font-size:.78rem;min-width:120px;max-width:180px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.decay-bar{flex:1;height:6px;border-radius:3px;background:rgba(255,255,255,0.06);overflow:hidden;position:relative}\n.decay-fill{height:100%;border-radius:3px;transition:width .6s ease}\n.decay-time{font-size:.7rem;color:var(--slate);min-width:56px;text-align:right}\n\n/* Superposition pills */\n.super-list{display:flex;flex-direction:column;gap:8px;max-height:280px;overflow-y:auto}\n.super-item{display:flex;align-items:center;gap:8px;flex-wrap:wrap}\n.super-key{font-family:'SF Mono',SFMono-Regular,Consolas,monospace;font-size:.78rem;min-width:100px}\n.env-pill{font-size:.65rem;padding:2px 8px;border-radius:99px;font-weight:600;letter-spacing:.03em}\n.env-prod{background:rgba(255,0,85,0.2);color:var(--pink);border:1px solid rgba(255,0,85,0.3)}\n.env-staging{background:rgba(255,184,0,0.15);color:var(--amber);border:1px solid rgba(255,184,0,0.25)}\n.env-dev{background:rgba(0,230,118,0.15);color:var(--emerald);border:1px solid rgba(0,230,118,0.25)}\n.env-default{background:rgba(178,102,255,0.15);color:var(--violet);border:1px solid rgba(178,102,255,0.25)}\n\n/* Entanglement */\n.entangle-list{display:flex;flex-direction:column;gap:6px;max-height:240px;overflow-y:auto}\n.entangle-pair{display:flex;align-items:center;gap:8px;font-family:'SF Mono',SFMono-Regular,Consolas,monospace;font-size:.78rem}\n.entangle-arrow{color:var(--cyan)}\n\n/* Tunnels */\n.tunnel-list{display:flex;flex-direction:column;gap:8px;max-height:240px;overflow-y:auto}\n.tunnel-card{background:rgba(178,102,255,0.06);border:1px solid rgba(178,102,255,0.12);border-radius:8px;padding:10px 12px;font-size:.78rem;font-family:'SF Mono',SFMono-Regular,Consolas,monospace}\n.tunnel-meta{display:flex;gap:12px;margin-top:4px;font-size:.7rem;color:var(--slate)}\n\n/* Audit feed */\n.audit-feed{display:flex;flex-direction:column;gap:4px;max-height:300px;overflow-y:auto;font-size:.75rem;font-family:'SF Mono',SFMono-Regular,Consolas,monospace}\n.audit-row{display:flex;gap:8px;padding:3px 0;border-bottom:1px solid rgba(255,255,255,0.03)}\n.audit-ts{color:var(--slate);min-width:70px;flex-shrink:0}\n.audit-action{min-width:64px;font-weight:600}\n.audit-action.read{color:var(--cyan)}.audit-action.write{color:var(--emerald)}.audit-action.delete{color:var(--pink)}\n.audit-action.entangle{color:var(--violet)}.audit-action.tunnel{color:var(--violet)}.audit-action.teleport{color:var(--amber)}\n.audit-action.generate{color:var(--amber)}.audit-action.list{color:var(--slate)}.audit-action.export{color:var(--slate)}\n.audit-action.collapse{color:var(--cyan)}\n.audit-key{color:var(--ghost)}\n.audit-detail{color:var(--slate);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n\n/* Anomalies */\n.anomaly-card{background:rgba(255,0,85,0.06);border:1px solid rgba(255,0,85,0.15);border-radius:8px;padding:10px 14px;animation:anomaly-pulse 3s ease-in-out infinite}\n@keyframes anomaly-pulse{0%,100%{border-color:rgba(255,0,85,0.15)}50%{border-color:rgba(255,0,85,0.35)}}\n.anomaly-type{font-size:.7rem;text-transform:uppercase;letter-spacing:.06em;color:var(--pink);font-weight:700;margin-bottom:2px}\n.anomaly-desc{font-size:.8rem;color:var(--ghost)}\n\n/* Environment badge */\n.env-status{display:flex;align-items:center;gap:12px}\n.env-big{font-size:1.1rem;font-weight:700;padding:4px 14px;border-radius:8px}\n.env-source{font-size:.75rem;color:var(--slate)}\n\n/* Empty states */\n.empty{color:var(--slate);font-size:.8rem;font-style:italic;padding:8px 0}\n\n/* Scrollbar */\n::-webkit-scrollbar{width:4px;height:4px}\n::-webkit-scrollbar-track{background:transparent}\n::-webkit-scrollbar-thumb{background:rgba(255,255,255,0.08);border-radius:2px}\n::-webkit-scrollbar-thumb:hover{background:rgba(255,255,255,0.15)}\n</style>\n</head>\n<body>\n<div class=\"blob blob-1\"></div>\n<div class=\"blob blob-2\"></div>\n<div class=\"blob blob-3\"></div>\n<div class=\"container\">\n <div class=\"header\">\n <h1>\\u26a1 <span>q-ring</span> quantum status</h1>\n <div class=\"conn-label\"><span class=\"status-dot\" id=\"connDot\"></span><span id=\"connText\">connecting\\u2026</span></div>\n </div>\n <div class=\"grid\" id=\"dashboard\">\n <div class=\"card\"><div class=\"empty\">Connecting to q-ring\\u2026</div></div>\n </div>\n</div>\n\n<script>\n(function(){\n const $ = (id) => document.getElementById(id);\n const dash = $('dashboard');\n const dot = $('connDot');\n const connText = $('connText');\n let connected = false;\n\n function esc(s){ const d=document.createElement('div');d.textContent=s;return d.innerHTML; }\n\n function envClass(e){\n if(e==='prod'||e==='production') return 'env-prod';\n if(e==='staging'||e==='stage') return 'env-staging';\n if(e==='dev'||e==='development') return 'env-dev';\n return 'env-default';\n }\n\n function decayColor(pct,expired){\n if(expired) return 'var(--pink)';\n if(pct>=90) return 'var(--pink)';\n if(pct>=75) return 'var(--amber)';\n return 'var(--cyan)';\n }\n\n function fmtTime(ts){\n const d=new Date(ts);\n const h=d.getHours().toString().padStart(2,'0');\n const m=d.getMinutes().toString().padStart(2,'0');\n const s=d.getSeconds().toString().padStart(2,'0');\n return h+':'+m+':'+s;\n }\n\n function renderHealth(h){\n const total=h.total||1;\n const r=42, circ=2*Math.PI*r;\n const slices=[\n {v:h.healthy,c:'var(--cyan)'},\n {v:h.stale,c:'var(--amber)'},\n {v:h.expired,c:'var(--pink)'},\n {v:h.noDecay,c:'var(--slate)'}\n ];\n let offset=0;\n let rings='';\n for(const sl of slices){\n const pct=sl.v/total;\n const len=pct*circ;\n rings+=\\`<circle cx=\"50\" cy=\"50\" r=\"\\${r}\" fill=\"none\" stroke=\"\\${sl.c}\" stroke-width=\"12\" stroke-dasharray=\"\\${len} \\${circ-len}\" stroke-dashoffset=\"-\\${offset}\" stroke-linecap=\"round\" opacity=\"0.85\"/>\\`;\n offset+=len;\n }\n return \\`<div class=\"health-row\">\n <div class=\"donut-wrap\">\n <svg viewBox=\"0 0 100 100\" width=\"100\" height=\"100\">\\${rings}</svg>\n <div class=\"donut-label\">\\${h.total}<small>secrets</small></div>\n </div>\n <div class=\"health-legend\">\n <div class=\"legend-item\"><span class=\"legend-dot\" style=\"background:var(--cyan)\"></span>Healthy \\${h.healthy}</div>\n <div class=\"legend-item\"><span class=\"legend-dot\" style=\"background:var(--amber)\"></span>Stale \\${h.stale}</div>\n <div class=\"legend-item\"><span class=\"legend-dot\" style=\"background:var(--pink)\"></span>Expired \\${h.expired}</div>\n <div class=\"legend-item\"><span class=\"legend-dot\" style=\"background:var(--slate)\"></span>No decay \\${h.noDecay}</div>\n </div>\n </div>\\`;\n }\n\n function renderDecay(secrets){\n const withDecay=secrets.filter(s=>s.decay&&s.decay.timeRemaining);\n if(!withDecay.length) return '<div class=\"empty\">No secrets with decay configured</div>';\n return '<div class=\"decay-list\">'+withDecay.map(s=>{\n const pct=Math.min(s.decay.lifetimePercent,100);\n const col=decayColor(pct,s.decay.isExpired);\n const label=s.decay.isExpired?'expired':s.decay.timeRemaining||'';\n return \\`<div class=\"decay-item\">\n <span class=\"decay-key\" title=\"\\${esc(s.key)}\">\\${esc(s.key)}</span>\n <div class=\"decay-bar\"><div class=\"decay-fill\" style=\"width:\\${pct}%;background:\\${col}\"></div></div>\n <span class=\"decay-time\" style=\"color:\\${col}\">\\${esc(label)}</span>\n </div>\\`;\n }).join('')+'</div>';\n }\n\n function renderSuperposition(secrets){\n const sup=secrets.filter(s=>s.type==='superposition'&&s.environments);\n if(!sup.length) return '<div class=\"empty\">No secrets in superposition</div>';\n return '<div class=\"super-list\">'+sup.map(s=>{\n const pills=(s.environments||[]).map(e=>{\n const isDefault=e===s.defaultEnv;\n return \\`<span class=\"env-pill \\${envClass(e)}\">\\${esc(e)}\\${isDefault?' \\u2713':''}</span>\\`;\n }).join('');\n return \\`<div class=\"super-item\"><span class=\"super-key\">\\${esc(s.key)}</span>\\${pills}</div>\\`;\n }).join('')+'</div>';\n }\n\n function renderEntanglements(pairs){\n if(!pairs.length) return '<div class=\"empty\">No entangled secrets</div>';\n const seen=new Set();\n const unique=pairs.filter(p=>{\n const id=[p.source.service,p.source.key,p.target.service,p.target.key].sort().join('|');\n if(seen.has(id)) return false;\n seen.add(id);return true;\n });\n return '<div class=\"entangle-list\">'+unique.map(p=>\n \\`<div class=\"entangle-pair\"><span>\\${esc(p.source.key)}</span><span class=\"entangle-arrow\">\\u2194</span><span>\\${esc(p.target.key)}</span></div>\\`\n ).join('')+'</div>';\n }\n\n function renderTunnels(tunnels){\n if(!tunnels.length) return '<div class=\"empty\">No active tunnels</div>';\n return '<div class=\"tunnel-list\">'+tunnels.map(t=>{\n const rem=t.expiresAt?Math.max(0,Math.floor((t.expiresAt-Date.now())/1000)):null;\n return \\`<div class=\"tunnel-card\">\\${esc(t.id)}<div class=\"tunnel-meta\">\n <span>reads: \\${t.accessCount}\\${t.maxReads?'/'+t.maxReads:''}</span>\n \\${rem!==null?'<span>expires: '+rem+'s</span>':'<span>no expiry</span>'}\n </div></div>\\`;\n }).join('')+'</div>';\n }\n\n function renderAudit(events){\n if(!events.length) return '<div class=\"empty\">No audit events</div>';\n return '<div class=\"audit-feed\">'+events.slice(0,40).map(e=>\n \\`<div class=\"audit-row\">\n <span class=\"audit-ts\">\\${fmtTime(e.timestamp)}</span>\n <span class=\"audit-action \\${e.action}\">\\${e.action}</span>\n <span class=\"audit-key\">\\${e.key?esc(e.key):''}</span>\n <span class=\"audit-detail\">\\${e.detail?esc(e.detail):''}</span>\n </div>\\`\n ).join('')+'</div>';\n }\n\n function renderAnomalies(anomalies){\n if(!anomalies.length) return '<div class=\"empty\">No anomalies detected \\u2014 all clear</div>';\n return anomalies.map(a=>\n \\`<div class=\"anomaly-card\"><div class=\"anomaly-type\">\\${esc(a.type)}</div><div class=\"anomaly-desc\">\\${esc(a.description)}</div></div>\\`\n ).join('');\n }\n\n function renderEnvironment(env){\n if(!env) return '<div class=\"empty\">No environment detected</div>';\n return \\`<div class=\"env-status\">\n <span class=\"env-big env-pill \\${envClass(env.env)}\">\\${esc(env.env)}</span>\n <span class=\"env-source\">detected via \\${esc(env.source)}</span>\n </div>\\`;\n }\n\n function render(snap){\n dash.innerHTML=\\`\n <div class=\"card\">\n <div class=\"card-title\"><span class=\"icon\">\\u{1f6e1}\\ufe0f</span> Health Summary</div>\n \\${renderHealth(snap.health)}\n </div>\n <div class=\"card\">\n <div class=\"card-title\"><span class=\"icon\">\\u26a1</span> Environment</div>\n \\${renderEnvironment(snap.environment)}\n </div>\n <div class=\"card\">\n <div class=\"card-title\"><span class=\"icon\">\\u23f0</span> Decay Timers</div>\n \\${renderDecay(snap.secrets)}\n </div>\n <div class=\"card\">\n <div class=\"card-title\"><span class=\"icon\">\\u{1f30a}</span> Superposition States</div>\n \\${renderSuperposition(snap.secrets)}\n </div>\n <div class=\"card\">\n <div class=\"card-title\"><span class=\"icon\">\\u{1f517}</span> Entanglement</div>\n \\${renderEntanglements(snap.entanglements)}\n </div>\n <div class=\"card\">\n <div class=\"card-title\"><span class=\"icon\">\\u{1f47b}</span> Quantum Tunnels</div>\n \\${renderTunnels(snap.tunnels)}\n </div>\n <div class=\"card grid-wide\">\n <div class=\"card-title\"><span class=\"icon\">\\u26a0\\ufe0f</span> Anomaly Alerts</div>\n \\${renderAnomalies(snap.anomalies)}\n </div>\n <div class=\"card grid-wide\">\n <div class=\"card-title\"><span class=\"icon\">\\u{1f441}\\ufe0f</span> Audit Log</div>\n \\${renderAudit(snap.audit)}\n </div>\n \\`;\n }\n\n function connect(){\n const es=new EventSource('/events');\n es.onopen=()=>{\n connected=true;\n dot.classList.remove('disconnected');\n connText.textContent='live';\n };\n es.onmessage=(e)=>{\n try{ render(JSON.parse(e.data)); }catch(err){ console.error('render error',err); }\n };\n es.onerror=()=>{\n connected=false;\n dot.classList.add('disconnected');\n connText.textContent='reconnecting\\u2026';\n };\n }\n\n connect();\n})();\n</script>\n</body>\n</html>`;\n}\n"],"mappings":";;;;;;;;;;;;AAOA,SAAS,oBAA4E;;;ACC9E,SAAS,mBAA2B;AACzC,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;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","names":[]}
|
|
@@ -0,0 +1,482 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
checkDecay,
|
|
4
|
+
collapseEnvironment,
|
|
5
|
+
detectAnomalies,
|
|
6
|
+
listEntanglements,
|
|
7
|
+
listSecrets,
|
|
8
|
+
queryAudit,
|
|
9
|
+
tunnelList
|
|
10
|
+
} from "./chunk-WQPJ2FTM.js";
|
|
11
|
+
|
|
12
|
+
// src/core/dashboard.ts
|
|
13
|
+
import { createServer } from "http";
|
|
14
|
+
|
|
15
|
+
// src/core/dashboard-html.ts
|
|
16
|
+
function getDashboardHtml() {
|
|
17
|
+
return `<!DOCTYPE html>
|
|
18
|
+
<html lang="en">
|
|
19
|
+
<head>
|
|
20
|
+
<meta charset="utf-8"/>
|
|
21
|
+
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
|
22
|
+
<title>q-ring \u2014 quantum status</title>
|
|
23
|
+
<style>
|
|
24
|
+
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
|
|
25
|
+
:root{
|
|
26
|
+
--void:#050505;--mist:#1A1A2E;--ghost:#F8F9FA;--slate:#7A8490;
|
|
27
|
+
--cyan:#00D1FF;--violet:#B266FF;--emerald:#00E676;--amber:#FFB800;--pink:#FF0055;
|
|
28
|
+
--glass-bg:rgba(255,255,255,0.03);--glass-border:rgba(255,255,255,0.06);
|
|
29
|
+
--shadow-q:0 0 20px rgba(0,209,255,0.12),0 0 40px rgba(178,102,255,0.08);
|
|
30
|
+
--shadow-glass:0 4px 24px -1px rgba(0,0,0,0.3),0 0 1px rgba(255,255,255,0.04);
|
|
31
|
+
--glow-sm:0 0 8px rgba(0,209,255,0.2);
|
|
32
|
+
--radius:12px;
|
|
33
|
+
}
|
|
34
|
+
html{background:var(--void);color:var(--ghost);font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;font-size:14px;line-height:1.5}
|
|
35
|
+
body{min-height:100vh;overflow-x:hidden;position:relative}
|
|
36
|
+
|
|
37
|
+
/* Mesh blobs */
|
|
38
|
+
.blob{position:fixed;border-radius:50%;filter:blur(100px);opacity:.18;pointer-events:none;z-index:0}
|
|
39
|
+
.blob-1{width:600px;height:600px;top:-120px;left:-100px;background:radial-gradient(circle,var(--cyan),transparent 70%);animation:drift1 22s ease-in-out infinite}
|
|
40
|
+
.blob-2{width:500px;height:500px;bottom:-80px;right:-60px;background:radial-gradient(circle,var(--violet),transparent 70%);animation:drift2 26s ease-in-out infinite}
|
|
41
|
+
.blob-3{width:350px;height:350px;top:40%;left:50%;background:radial-gradient(circle,var(--emerald),transparent 70%);animation:drift3 30s ease-in-out infinite;opacity:.08}
|
|
42
|
+
@keyframes drift1{0%,100%{transform:translate(0,0) scale(1)}33%{transform:translate(60px,-40px) scale(1.08)}66%{transform:translate(-30px,50px) scale(.95)}}
|
|
43
|
+
@keyframes drift2{0%,100%{transform:translate(0,0) scale(1)}33%{transform:translate(-50px,30px) scale(1.05)}66%{transform:translate(40px,-60px) scale(.92)}}
|
|
44
|
+
@keyframes drift3{0%,100%{transform:translate(-50%,-50%) scale(1)}50%{transform:translate(-40%,-60%) scale(1.1)}}
|
|
45
|
+
|
|
46
|
+
.container{position:relative;z-index:1;max-width:1280px;margin:0 auto;padding:24px 20px 48px}
|
|
47
|
+
|
|
48
|
+
/* Header */
|
|
49
|
+
.header{display:flex;align-items:center;justify-content:space-between;margin-bottom:28px}
|
|
50
|
+
.header h1{font-size:1.6rem;font-weight:700;letter-spacing:-.02em}
|
|
51
|
+
.header h1 span{background:linear-gradient(135deg,var(--cyan),var(--violet));-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent}
|
|
52
|
+
.status-dot{width:8px;height:8px;border-radius:50%;background:var(--emerald);box-shadow:0 0 8px var(--emerald);display:inline-block;margin-right:8px;animation:pulse 2s ease-in-out infinite}
|
|
53
|
+
.status-dot.disconnected{background:var(--pink);box-shadow:0 0 8px var(--pink)}
|
|
54
|
+
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.4}}
|
|
55
|
+
.conn-label{font-size:.75rem;color:var(--slate);display:flex;align-items:center;gap:6px}
|
|
56
|
+
|
|
57
|
+
/* Grid */
|
|
58
|
+
.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(360px,1fr));gap:16px}
|
|
59
|
+
.grid-wide{grid-column:1/-1}
|
|
60
|
+
|
|
61
|
+
/* Cards */
|
|
62
|
+
.card{background:var(--glass-bg);backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px);border:1px solid var(--glass-border);border-radius:var(--radius);padding:18px 20px;box-shadow:var(--shadow-glass);transition:border-color .25s,box-shadow .25s}
|
|
63
|
+
.card:hover{border-color:rgba(255,255,255,0.1);box-shadow:var(--shadow-glass),var(--glow-sm)}
|
|
64
|
+
.card-title{font-size:.7rem;text-transform:uppercase;letter-spacing:.08em;color:var(--slate);margin-bottom:12px;display:flex;align-items:center;gap:6px}
|
|
65
|
+
.card-title .icon{font-size:.85rem}
|
|
66
|
+
|
|
67
|
+
/* Health donut */
|
|
68
|
+
.health-row{display:flex;align-items:center;gap:24px}
|
|
69
|
+
.donut-wrap{position:relative;width:100px;height:100px;flex-shrink:0}
|
|
70
|
+
.donut-wrap svg{transform:rotate(-90deg)}
|
|
71
|
+
.donut-wrap .donut-label{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;flex-direction:column;font-size:1.5rem;font-weight:700;line-height:1}
|
|
72
|
+
.donut-wrap .donut-label small{font-size:.6rem;color:var(--slate);font-weight:400;margin-top:2px}
|
|
73
|
+
.health-legend{display:flex;flex-direction:column;gap:6px}
|
|
74
|
+
.legend-item{display:flex;align-items:center;gap:8px;font-size:.8rem}
|
|
75
|
+
.legend-dot{width:8px;height:8px;border-radius:50%;flex-shrink:0}
|
|
76
|
+
|
|
77
|
+
/* Decay bars */
|
|
78
|
+
.decay-list{display:flex;flex-direction:column;gap:8px;max-height:280px;overflow-y:auto}
|
|
79
|
+
.decay-item{display:flex;align-items:center;gap:10px}
|
|
80
|
+
.decay-key{font-family:'SF Mono',SFMono-Regular,Consolas,'Liberation Mono',Menlo,monospace;font-size:.78rem;min-width:120px;max-width:180px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
81
|
+
.decay-bar{flex:1;height:6px;border-radius:3px;background:rgba(255,255,255,0.06);overflow:hidden;position:relative}
|
|
82
|
+
.decay-fill{height:100%;border-radius:3px;transition:width .6s ease}
|
|
83
|
+
.decay-time{font-size:.7rem;color:var(--slate);min-width:56px;text-align:right}
|
|
84
|
+
|
|
85
|
+
/* Superposition pills */
|
|
86
|
+
.super-list{display:flex;flex-direction:column;gap:8px;max-height:280px;overflow-y:auto}
|
|
87
|
+
.super-item{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
|
|
88
|
+
.super-key{font-family:'SF Mono',SFMono-Regular,Consolas,monospace;font-size:.78rem;min-width:100px}
|
|
89
|
+
.env-pill{font-size:.65rem;padding:2px 8px;border-radius:99px;font-weight:600;letter-spacing:.03em}
|
|
90
|
+
.env-prod{background:rgba(255,0,85,0.2);color:var(--pink);border:1px solid rgba(255,0,85,0.3)}
|
|
91
|
+
.env-staging{background:rgba(255,184,0,0.15);color:var(--amber);border:1px solid rgba(255,184,0,0.25)}
|
|
92
|
+
.env-dev{background:rgba(0,230,118,0.15);color:var(--emerald);border:1px solid rgba(0,230,118,0.25)}
|
|
93
|
+
.env-default{background:rgba(178,102,255,0.15);color:var(--violet);border:1px solid rgba(178,102,255,0.25)}
|
|
94
|
+
|
|
95
|
+
/* Entanglement */
|
|
96
|
+
.entangle-list{display:flex;flex-direction:column;gap:6px;max-height:240px;overflow-y:auto}
|
|
97
|
+
.entangle-pair{display:flex;align-items:center;gap:8px;font-family:'SF Mono',SFMono-Regular,Consolas,monospace;font-size:.78rem}
|
|
98
|
+
.entangle-arrow{color:var(--cyan)}
|
|
99
|
+
|
|
100
|
+
/* Tunnels */
|
|
101
|
+
.tunnel-list{display:flex;flex-direction:column;gap:8px;max-height:240px;overflow-y:auto}
|
|
102
|
+
.tunnel-card{background:rgba(178,102,255,0.06);border:1px solid rgba(178,102,255,0.12);border-radius:8px;padding:10px 12px;font-size:.78rem;font-family:'SF Mono',SFMono-Regular,Consolas,monospace}
|
|
103
|
+
.tunnel-meta{display:flex;gap:12px;margin-top:4px;font-size:.7rem;color:var(--slate)}
|
|
104
|
+
|
|
105
|
+
/* Audit feed */
|
|
106
|
+
.audit-feed{display:flex;flex-direction:column;gap:4px;max-height:300px;overflow-y:auto;font-size:.75rem;font-family:'SF Mono',SFMono-Regular,Consolas,monospace}
|
|
107
|
+
.audit-row{display:flex;gap:8px;padding:3px 0;border-bottom:1px solid rgba(255,255,255,0.03)}
|
|
108
|
+
.audit-ts{color:var(--slate);min-width:70px;flex-shrink:0}
|
|
109
|
+
.audit-action{min-width:64px;font-weight:600}
|
|
110
|
+
.audit-action.read{color:var(--cyan)}.audit-action.write{color:var(--emerald)}.audit-action.delete{color:var(--pink)}
|
|
111
|
+
.audit-action.entangle{color:var(--violet)}.audit-action.tunnel{color:var(--violet)}.audit-action.teleport{color:var(--amber)}
|
|
112
|
+
.audit-action.generate{color:var(--amber)}.audit-action.list{color:var(--slate)}.audit-action.export{color:var(--slate)}
|
|
113
|
+
.audit-action.collapse{color:var(--cyan)}
|
|
114
|
+
.audit-key{color:var(--ghost)}
|
|
115
|
+
.audit-detail{color:var(--slate);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
116
|
+
|
|
117
|
+
/* Anomalies */
|
|
118
|
+
.anomaly-card{background:rgba(255,0,85,0.06);border:1px solid rgba(255,0,85,0.15);border-radius:8px;padding:10px 14px;animation:anomaly-pulse 3s ease-in-out infinite}
|
|
119
|
+
@keyframes anomaly-pulse{0%,100%{border-color:rgba(255,0,85,0.15)}50%{border-color:rgba(255,0,85,0.35)}}
|
|
120
|
+
.anomaly-type{font-size:.7rem;text-transform:uppercase;letter-spacing:.06em;color:var(--pink);font-weight:700;margin-bottom:2px}
|
|
121
|
+
.anomaly-desc{font-size:.8rem;color:var(--ghost)}
|
|
122
|
+
|
|
123
|
+
/* Environment badge */
|
|
124
|
+
.env-status{display:flex;align-items:center;gap:12px}
|
|
125
|
+
.env-big{font-size:1.1rem;font-weight:700;padding:4px 14px;border-radius:8px}
|
|
126
|
+
.env-source{font-size:.75rem;color:var(--slate)}
|
|
127
|
+
|
|
128
|
+
/* Empty states */
|
|
129
|
+
.empty{color:var(--slate);font-size:.8rem;font-style:italic;padding:8px 0}
|
|
130
|
+
|
|
131
|
+
/* Scrollbar */
|
|
132
|
+
::-webkit-scrollbar{width:4px;height:4px}
|
|
133
|
+
::-webkit-scrollbar-track{background:transparent}
|
|
134
|
+
::-webkit-scrollbar-thumb{background:rgba(255,255,255,0.08);border-radius:2px}
|
|
135
|
+
::-webkit-scrollbar-thumb:hover{background:rgba(255,255,255,0.15)}
|
|
136
|
+
</style>
|
|
137
|
+
</head>
|
|
138
|
+
<body>
|
|
139
|
+
<div class="blob blob-1"></div>
|
|
140
|
+
<div class="blob blob-2"></div>
|
|
141
|
+
<div class="blob blob-3"></div>
|
|
142
|
+
<div class="container">
|
|
143
|
+
<div class="header">
|
|
144
|
+
<h1>\u26A1 <span>q-ring</span> quantum status</h1>
|
|
145
|
+
<div class="conn-label"><span class="status-dot" id="connDot"></span><span id="connText">connecting\u2026</span></div>
|
|
146
|
+
</div>
|
|
147
|
+
<div class="grid" id="dashboard">
|
|
148
|
+
<div class="card"><div class="empty">Connecting to q-ring\u2026</div></div>
|
|
149
|
+
</div>
|
|
150
|
+
</div>
|
|
151
|
+
|
|
152
|
+
<script>
|
|
153
|
+
(function(){
|
|
154
|
+
const $ = (id) => document.getElementById(id);
|
|
155
|
+
const dash = $('dashboard');
|
|
156
|
+
const dot = $('connDot');
|
|
157
|
+
const connText = $('connText');
|
|
158
|
+
let connected = false;
|
|
159
|
+
|
|
160
|
+
function esc(s){ const d=document.createElement('div');d.textContent=s;return d.innerHTML; }
|
|
161
|
+
|
|
162
|
+
function envClass(e){
|
|
163
|
+
if(e==='prod'||e==='production') return 'env-prod';
|
|
164
|
+
if(e==='staging'||e==='stage') return 'env-staging';
|
|
165
|
+
if(e==='dev'||e==='development') return 'env-dev';
|
|
166
|
+
return 'env-default';
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function decayColor(pct,expired){
|
|
170
|
+
if(expired) return 'var(--pink)';
|
|
171
|
+
if(pct>=90) return 'var(--pink)';
|
|
172
|
+
if(pct>=75) return 'var(--amber)';
|
|
173
|
+
return 'var(--cyan)';
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function fmtTime(ts){
|
|
177
|
+
const d=new Date(ts);
|
|
178
|
+
const h=d.getHours().toString().padStart(2,'0');
|
|
179
|
+
const m=d.getMinutes().toString().padStart(2,'0');
|
|
180
|
+
const s=d.getSeconds().toString().padStart(2,'0');
|
|
181
|
+
return h+':'+m+':'+s;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function renderHealth(h){
|
|
185
|
+
const total=h.total||1;
|
|
186
|
+
const r=42, circ=2*Math.PI*r;
|
|
187
|
+
const slices=[
|
|
188
|
+
{v:h.healthy,c:'var(--cyan)'},
|
|
189
|
+
{v:h.stale,c:'var(--amber)'},
|
|
190
|
+
{v:h.expired,c:'var(--pink)'},
|
|
191
|
+
{v:h.noDecay,c:'var(--slate)'}
|
|
192
|
+
];
|
|
193
|
+
let offset=0;
|
|
194
|
+
let rings='';
|
|
195
|
+
for(const sl of slices){
|
|
196
|
+
const pct=sl.v/total;
|
|
197
|
+
const len=pct*circ;
|
|
198
|
+
rings+=\`<circle cx="50" cy="50" r="\${r}" fill="none" stroke="\${sl.c}" stroke-width="12" stroke-dasharray="\${len} \${circ-len}" stroke-dashoffset="-\${offset}" stroke-linecap="round" opacity="0.85"/>\`;
|
|
199
|
+
offset+=len;
|
|
200
|
+
}
|
|
201
|
+
return \`<div class="health-row">
|
|
202
|
+
<div class="donut-wrap">
|
|
203
|
+
<svg viewBox="0 0 100 100" width="100" height="100">\${rings}</svg>
|
|
204
|
+
<div class="donut-label">\${h.total}<small>secrets</small></div>
|
|
205
|
+
</div>
|
|
206
|
+
<div class="health-legend">
|
|
207
|
+
<div class="legend-item"><span class="legend-dot" style="background:var(--cyan)"></span>Healthy \${h.healthy}</div>
|
|
208
|
+
<div class="legend-item"><span class="legend-dot" style="background:var(--amber)"></span>Stale \${h.stale}</div>
|
|
209
|
+
<div class="legend-item"><span class="legend-dot" style="background:var(--pink)"></span>Expired \${h.expired}</div>
|
|
210
|
+
<div class="legend-item"><span class="legend-dot" style="background:var(--slate)"></span>No decay \${h.noDecay}</div>
|
|
211
|
+
</div>
|
|
212
|
+
</div>\`;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function renderDecay(secrets){
|
|
216
|
+
const withDecay=secrets.filter(s=>s.decay&&s.decay.timeRemaining);
|
|
217
|
+
if(!withDecay.length) return '<div class="empty">No secrets with decay configured</div>';
|
|
218
|
+
return '<div class="decay-list">'+withDecay.map(s=>{
|
|
219
|
+
const pct=Math.min(s.decay.lifetimePercent,100);
|
|
220
|
+
const col=decayColor(pct,s.decay.isExpired);
|
|
221
|
+
const label=s.decay.isExpired?'expired':s.decay.timeRemaining||'';
|
|
222
|
+
return \`<div class="decay-item">
|
|
223
|
+
<span class="decay-key" title="\${esc(s.key)}">\${esc(s.key)}</span>
|
|
224
|
+
<div class="decay-bar"><div class="decay-fill" style="width:\${pct}%;background:\${col}"></div></div>
|
|
225
|
+
<span class="decay-time" style="color:\${col}">\${esc(label)}</span>
|
|
226
|
+
</div>\`;
|
|
227
|
+
}).join('')+'</div>';
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function renderSuperposition(secrets){
|
|
231
|
+
const sup=secrets.filter(s=>s.type==='superposition'&&s.environments);
|
|
232
|
+
if(!sup.length) return '<div class="empty">No secrets in superposition</div>';
|
|
233
|
+
return '<div class="super-list">'+sup.map(s=>{
|
|
234
|
+
const pills=(s.environments||[]).map(e=>{
|
|
235
|
+
const isDefault=e===s.defaultEnv;
|
|
236
|
+
return \`<span class="env-pill \${envClass(e)}">\${esc(e)}\${isDefault?' \u2713':''}</span>\`;
|
|
237
|
+
}).join('');
|
|
238
|
+
return \`<div class="super-item"><span class="super-key">\${esc(s.key)}</span>\${pills}</div>\`;
|
|
239
|
+
}).join('')+'</div>';
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function renderEntanglements(pairs){
|
|
243
|
+
if(!pairs.length) return '<div class="empty">No entangled secrets</div>';
|
|
244
|
+
const seen=new Set();
|
|
245
|
+
const unique=pairs.filter(p=>{
|
|
246
|
+
const id=[p.source.service,p.source.key,p.target.service,p.target.key].sort().join('|');
|
|
247
|
+
if(seen.has(id)) return false;
|
|
248
|
+
seen.add(id);return true;
|
|
249
|
+
});
|
|
250
|
+
return '<div class="entangle-list">'+unique.map(p=>
|
|
251
|
+
\`<div class="entangle-pair"><span>\${esc(p.source.key)}</span><span class="entangle-arrow">\u2194</span><span>\${esc(p.target.key)}</span></div>\`
|
|
252
|
+
).join('')+'</div>';
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function renderTunnels(tunnels){
|
|
256
|
+
if(!tunnels.length) return '<div class="empty">No active tunnels</div>';
|
|
257
|
+
return '<div class="tunnel-list">'+tunnels.map(t=>{
|
|
258
|
+
const rem=t.expiresAt?Math.max(0,Math.floor((t.expiresAt-Date.now())/1000)):null;
|
|
259
|
+
return \`<div class="tunnel-card">\${esc(t.id)}<div class="tunnel-meta">
|
|
260
|
+
<span>reads: \${t.accessCount}\${t.maxReads?'/'+t.maxReads:''}</span>
|
|
261
|
+
\${rem!==null?'<span>expires: '+rem+'s</span>':'<span>no expiry</span>'}
|
|
262
|
+
</div></div>\`;
|
|
263
|
+
}).join('')+'</div>';
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function renderAudit(events){
|
|
267
|
+
if(!events.length) return '<div class="empty">No audit events</div>';
|
|
268
|
+
return '<div class="audit-feed">'+events.slice(0,40).map(e=>
|
|
269
|
+
\`<div class="audit-row">
|
|
270
|
+
<span class="audit-ts">\${fmtTime(e.timestamp)}</span>
|
|
271
|
+
<span class="audit-action \${e.action}">\${e.action}</span>
|
|
272
|
+
<span class="audit-key">\${e.key?esc(e.key):''}</span>
|
|
273
|
+
<span class="audit-detail">\${e.detail?esc(e.detail):''}</span>
|
|
274
|
+
</div>\`
|
|
275
|
+
).join('')+'</div>';
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function renderAnomalies(anomalies){
|
|
279
|
+
if(!anomalies.length) return '<div class="empty">No anomalies detected \u2014 all clear</div>';
|
|
280
|
+
return anomalies.map(a=>
|
|
281
|
+
\`<div class="anomaly-card"><div class="anomaly-type">\${esc(a.type)}</div><div class="anomaly-desc">\${esc(a.description)}</div></div>\`
|
|
282
|
+
).join('');
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function renderEnvironment(env){
|
|
286
|
+
if(!env) return '<div class="empty">No environment detected</div>';
|
|
287
|
+
return \`<div class="env-status">
|
|
288
|
+
<span class="env-big env-pill \${envClass(env.env)}">\${esc(env.env)}</span>
|
|
289
|
+
<span class="env-source">detected via \${esc(env.source)}</span>
|
|
290
|
+
</div>\`;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function render(snap){
|
|
294
|
+
dash.innerHTML=\`
|
|
295
|
+
<div class="card">
|
|
296
|
+
<div class="card-title"><span class="icon">\u{1F6E1}\uFE0F</span> Health Summary</div>
|
|
297
|
+
\${renderHealth(snap.health)}
|
|
298
|
+
</div>
|
|
299
|
+
<div class="card">
|
|
300
|
+
<div class="card-title"><span class="icon">\u26A1</span> Environment</div>
|
|
301
|
+
\${renderEnvironment(snap.environment)}
|
|
302
|
+
</div>
|
|
303
|
+
<div class="card">
|
|
304
|
+
<div class="card-title"><span class="icon">\u23F0</span> Decay Timers</div>
|
|
305
|
+
\${renderDecay(snap.secrets)}
|
|
306
|
+
</div>
|
|
307
|
+
<div class="card">
|
|
308
|
+
<div class="card-title"><span class="icon">\u{1F30A}</span> Superposition States</div>
|
|
309
|
+
\${renderSuperposition(snap.secrets)}
|
|
310
|
+
</div>
|
|
311
|
+
<div class="card">
|
|
312
|
+
<div class="card-title"><span class="icon">\u{1F517}</span> Entanglement</div>
|
|
313
|
+
\${renderEntanglements(snap.entanglements)}
|
|
314
|
+
</div>
|
|
315
|
+
<div class="card">
|
|
316
|
+
<div class="card-title"><span class="icon">\u{1F47B}</span> Quantum Tunnels</div>
|
|
317
|
+
\${renderTunnels(snap.tunnels)}
|
|
318
|
+
</div>
|
|
319
|
+
<div class="card grid-wide">
|
|
320
|
+
<div class="card-title"><span class="icon">\u26A0\uFE0F</span> Anomaly Alerts</div>
|
|
321
|
+
\${renderAnomalies(snap.anomalies)}
|
|
322
|
+
</div>
|
|
323
|
+
<div class="card grid-wide">
|
|
324
|
+
<div class="card-title"><span class="icon">\u{1F441}\uFE0F</span> Audit Log</div>
|
|
325
|
+
\${renderAudit(snap.audit)}
|
|
326
|
+
</div>
|
|
327
|
+
\`;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function connect(){
|
|
331
|
+
const es=new EventSource('/events');
|
|
332
|
+
es.onopen=()=>{
|
|
333
|
+
connected=true;
|
|
334
|
+
dot.classList.remove('disconnected');
|
|
335
|
+
connText.textContent='live';
|
|
336
|
+
};
|
|
337
|
+
es.onmessage=(e)=>{
|
|
338
|
+
try{ render(JSON.parse(e.data)); }catch(err){ console.error('render error',err); }
|
|
339
|
+
};
|
|
340
|
+
es.onerror=()=>{
|
|
341
|
+
connected=false;
|
|
342
|
+
dot.classList.add('disconnected');
|
|
343
|
+
connText.textContent='reconnecting\u2026';
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
connect();
|
|
348
|
+
})();
|
|
349
|
+
</script>
|
|
350
|
+
</body>
|
|
351
|
+
</html>`;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// src/core/dashboard.ts
|
|
355
|
+
function toSecretSnapshot(entry) {
|
|
356
|
+
const envelope = entry.envelope;
|
|
357
|
+
const decay = envelope ? checkDecay(envelope) : {
|
|
358
|
+
isExpired: false,
|
|
359
|
+
isStale: false,
|
|
360
|
+
lifetimePercent: 0,
|
|
361
|
+
secondsRemaining: null,
|
|
362
|
+
timeRemaining: null
|
|
363
|
+
};
|
|
364
|
+
return {
|
|
365
|
+
key: entry.key,
|
|
366
|
+
scope: entry.scope,
|
|
367
|
+
type: envelope?.states ? "superposition" : "collapsed",
|
|
368
|
+
environments: envelope?.states ? Object.keys(envelope.states) : void 0,
|
|
369
|
+
defaultEnv: envelope?.defaultEnv,
|
|
370
|
+
decay,
|
|
371
|
+
accessCount: envelope?.meta.accessCount ?? 0,
|
|
372
|
+
lastAccessedAt: envelope?.meta.lastAccessedAt,
|
|
373
|
+
createdAt: envelope?.meta.createdAt ?? "",
|
|
374
|
+
updatedAt: envelope?.meta.updatedAt ?? "",
|
|
375
|
+
description: envelope?.meta.description,
|
|
376
|
+
tags: envelope?.meta.tags,
|
|
377
|
+
entangled: envelope?.meta.entangled
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
function collectSnapshot() {
|
|
381
|
+
const entries = listSecrets({ source: "api" });
|
|
382
|
+
const secrets = entries.map(toSecretSnapshot);
|
|
383
|
+
let healthy = 0;
|
|
384
|
+
let stale = 0;
|
|
385
|
+
let expired = 0;
|
|
386
|
+
let noDecay = 0;
|
|
387
|
+
for (const s of secrets) {
|
|
388
|
+
if (!s.decay.timeRemaining) {
|
|
389
|
+
noDecay++;
|
|
390
|
+
} else if (s.decay.isExpired) {
|
|
391
|
+
expired++;
|
|
392
|
+
} else if (s.decay.isStale) {
|
|
393
|
+
stale++;
|
|
394
|
+
} else {
|
|
395
|
+
healthy++;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
return {
|
|
399
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
400
|
+
secrets,
|
|
401
|
+
health: { healthy, stale, expired, noDecay, total: secrets.length },
|
|
402
|
+
entanglements: listEntanglements(),
|
|
403
|
+
tunnels: tunnelList(),
|
|
404
|
+
audit: queryAudit({ limit: 50 }),
|
|
405
|
+
anomalies: detectAnomalies(),
|
|
406
|
+
environment: collapseEnvironment()
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
function startDashboardServer(options = {}) {
|
|
410
|
+
const port = options.port ?? 9876;
|
|
411
|
+
const clients = /* @__PURE__ */ new Set();
|
|
412
|
+
let intervalHandle = null;
|
|
413
|
+
const html = getDashboardHtml();
|
|
414
|
+
function broadcast() {
|
|
415
|
+
const snapshot = collectSnapshot();
|
|
416
|
+
const data = `data: ${JSON.stringify(snapshot)}
|
|
417
|
+
|
|
418
|
+
`;
|
|
419
|
+
for (const res of clients) {
|
|
420
|
+
try {
|
|
421
|
+
res.write(data);
|
|
422
|
+
} catch {
|
|
423
|
+
clients.delete(res);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
const server = createServer((req, res) => {
|
|
428
|
+
const url = req.url ?? "/";
|
|
429
|
+
if (url === "/events") {
|
|
430
|
+
res.writeHead(200, {
|
|
431
|
+
"Content-Type": "text/event-stream",
|
|
432
|
+
"Cache-Control": "no-cache",
|
|
433
|
+
Connection: "keep-alive",
|
|
434
|
+
"Access-Control-Allow-Origin": "*"
|
|
435
|
+
});
|
|
436
|
+
const snapshot = collectSnapshot();
|
|
437
|
+
res.write(`data: ${JSON.stringify(snapshot)}
|
|
438
|
+
|
|
439
|
+
`);
|
|
440
|
+
clients.add(res);
|
|
441
|
+
req.on("close", () => clients.delete(res));
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
if (url === "/api/status") {
|
|
445
|
+
const snapshot = collectSnapshot();
|
|
446
|
+
res.writeHead(200, {
|
|
447
|
+
"Content-Type": "application/json",
|
|
448
|
+
"Access-Control-Allow-Origin": "*"
|
|
449
|
+
});
|
|
450
|
+
res.end(JSON.stringify(snapshot, null, 2));
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
454
|
+
res.end(html);
|
|
455
|
+
});
|
|
456
|
+
server.listen(port, "127.0.0.1", () => {
|
|
457
|
+
intervalHandle = setInterval(broadcast, 5e3);
|
|
458
|
+
if (intervalHandle && typeof intervalHandle === "object" && "unref" in intervalHandle) {
|
|
459
|
+
intervalHandle.unref();
|
|
460
|
+
}
|
|
461
|
+
});
|
|
462
|
+
return {
|
|
463
|
+
port,
|
|
464
|
+
close: () => {
|
|
465
|
+
if (intervalHandle) clearInterval(intervalHandle);
|
|
466
|
+
for (const res of clients) {
|
|
467
|
+
try {
|
|
468
|
+
res.end();
|
|
469
|
+
} catch {
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
clients.clear();
|
|
473
|
+
server.close();
|
|
474
|
+
},
|
|
475
|
+
server
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
export {
|
|
479
|
+
collectSnapshot,
|
|
480
|
+
startDashboardServer
|
|
481
|
+
};
|
|
482
|
+
//# sourceMappingURL=dashboard-JTALLJM6.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/dashboard.ts","../src/core/dashboard-html.ts"],"sourcesContent":["/**\n * Quantum Status Dashboard: local HTTP server with SSE live updates.\n *\n * Collects a full snapshot of all quantum state every few seconds and\n * pushes it to connected browsers. Never exposes secret values.\n */\n\nimport { createServer, type IncomingMessage, type ServerResponse, type Server } from \"node:http\";\nimport { listSecrets } from \"./keyring.js\";\nimport { checkDecay, type DecayStatus, type QuantumEnvelope } from \"./envelope.js\";\nimport { listEntanglements, type EntanglementPair } from \"./entanglement.js\";\nimport { tunnelList } from \"./tunnel.js\";\nimport { queryAudit, detectAnomalies, type AuditEvent, type AccessAnomaly } from \"./observer.js\";\nimport { collapseEnvironment, type CollapseResult } from \"./collapse.js\";\nimport { getDashboardHtml } from \"./dashboard-html.js\";\n\nexport interface SecretSnapshot {\n key: string;\n scope: string;\n type: \"collapsed\" | \"superposition\";\n environments?: string[];\n defaultEnv?: string;\n decay: DecayStatus;\n accessCount: number;\n lastAccessedAt?: string;\n createdAt: string;\n updatedAt: string;\n description?: string;\n tags?: string[];\n entangled?: { service: string; key: string }[];\n}\n\nexport interface TunnelSnapshot {\n id: string;\n createdAt: number;\n expiresAt?: number;\n accessCount: number;\n maxReads?: number;\n}\n\nexport interface DashboardSnapshot {\n timestamp: string;\n secrets: SecretSnapshot[];\n health: { healthy: number; stale: number; expired: number; noDecay: number; total: number };\n entanglements: EntanglementPair[];\n tunnels: TunnelSnapshot[];\n audit: AuditEvent[];\n anomalies: AccessAnomaly[];\n environment: CollapseResult | null;\n}\n\nfunction toSecretSnapshot(entry: {\n key: string;\n scope: string;\n envelope?: QuantumEnvelope;\n decay?: DecayStatus;\n}): SecretSnapshot {\n const envelope = entry.envelope;\n const decay = envelope ? checkDecay(envelope) : {\n isExpired: false,\n isStale: false,\n lifetimePercent: 0,\n secondsRemaining: null,\n timeRemaining: null,\n };\n\n return {\n key: entry.key,\n scope: entry.scope,\n type: envelope?.states ? \"superposition\" : \"collapsed\",\n environments: envelope?.states ? Object.keys(envelope.states) : undefined,\n defaultEnv: envelope?.defaultEnv,\n decay,\n accessCount: envelope?.meta.accessCount ?? 0,\n lastAccessedAt: envelope?.meta.lastAccessedAt,\n createdAt: envelope?.meta.createdAt ?? \"\",\n updatedAt: envelope?.meta.updatedAt ?? \"\",\n description: envelope?.meta.description,\n tags: envelope?.meta.tags,\n entangled: envelope?.meta.entangled,\n };\n}\n\nexport function collectSnapshot(): DashboardSnapshot {\n const entries = listSecrets({ source: \"api\" });\n\n const secrets = entries.map(toSecretSnapshot);\n\n let healthy = 0;\n let stale = 0;\n let expired = 0;\n let noDecay = 0;\n\n for (const s of secrets) {\n if (!s.decay.timeRemaining) {\n noDecay++;\n } else if (s.decay.isExpired) {\n expired++;\n } else if (s.decay.isStale) {\n stale++;\n } else {\n healthy++;\n }\n }\n\n return {\n timestamp: new Date().toISOString(),\n secrets,\n health: { healthy, stale, expired, noDecay, total: secrets.length },\n entanglements: listEntanglements(),\n tunnels: tunnelList(),\n audit: queryAudit({ limit: 50 }),\n anomalies: detectAnomalies(),\n environment: collapseEnvironment(),\n };\n}\n\nexport interface DashboardServerOptions {\n port?: number;\n}\n\nexport function startDashboardServer(\n options: DashboardServerOptions = {},\n): { port: number; close: () => void; server: Server } {\n const port = options.port ?? 9876;\n const clients = new Set<ServerResponse>();\n let intervalHandle: ReturnType<typeof setInterval> | null = null;\n\n const html = getDashboardHtml();\n\n function broadcast() {\n const snapshot = collectSnapshot();\n const data = `data: ${JSON.stringify(snapshot)}\\n\\n`;\n for (const res of clients) {\n try {\n res.write(data);\n } catch {\n clients.delete(res);\n }\n }\n }\n\n const server = createServer((req: IncomingMessage, res: ServerResponse) => {\n const url = req.url ?? \"/\";\n\n if (url === \"/events\") {\n res.writeHead(200, {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"keep-alive\",\n \"Access-Control-Allow-Origin\": \"*\",\n });\n\n // Send initial snapshot immediately\n const snapshot = collectSnapshot();\n res.write(`data: ${JSON.stringify(snapshot)}\\n\\n`);\n\n clients.add(res);\n req.on(\"close\", () => clients.delete(res));\n return;\n }\n\n if (url === \"/api/status\") {\n const snapshot = collectSnapshot();\n res.writeHead(200, {\n \"Content-Type\": \"application/json\",\n \"Access-Control-Allow-Origin\": \"*\",\n });\n res.end(JSON.stringify(snapshot, null, 2));\n return;\n }\n\n // Serve the dashboard HTML\n res.writeHead(200, { \"Content-Type\": \"text/html; charset=utf-8\" });\n res.end(html);\n });\n\n server.listen(port, \"127.0.0.1\", () => {\n intervalHandle = setInterval(broadcast, 5000);\n if (intervalHandle && typeof intervalHandle === \"object\" && \"unref\" in intervalHandle) {\n intervalHandle.unref();\n }\n });\n\n return {\n port,\n close: () => {\n if (intervalHandle) clearInterval(intervalHandle);\n for (const res of clients) {\n try { res.end(); } catch { /* noop */ }\n }\n clients.clear();\n server.close();\n },\n server,\n };\n}\n","/**\n * Self-contained HTML dashboard for q-ring quantum status.\n * Uses the Quantum Fluidity design system: Obsidian Void background,\n * glassmorphism cards, Electric Cyan / Hyper-Violet accents.\n *\n * Zero dependencies — inline CSS + vanilla JS with EventSource for SSE.\n */\n\nexport function getDashboardHtml(): string {\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\"/>\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"/>\n<title>q-ring \\u2014 quantum status</title>\n<style>\n*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}\n:root{\n --void:#050505;--mist:#1A1A2E;--ghost:#F8F9FA;--slate:#7A8490;\n --cyan:#00D1FF;--violet:#B266FF;--emerald:#00E676;--amber:#FFB800;--pink:#FF0055;\n --glass-bg:rgba(255,255,255,0.03);--glass-border:rgba(255,255,255,0.06);\n --shadow-q:0 0 20px rgba(0,209,255,0.12),0 0 40px rgba(178,102,255,0.08);\n --shadow-glass:0 4px 24px -1px rgba(0,0,0,0.3),0 0 1px rgba(255,255,255,0.04);\n --glow-sm:0 0 8px rgba(0,209,255,0.2);\n --radius:12px;\n}\nhtml{background:var(--void);color:var(--ghost);font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;font-size:14px;line-height:1.5}\nbody{min-height:100vh;overflow-x:hidden;position:relative}\n\n/* Mesh blobs */\n.blob{position:fixed;border-radius:50%;filter:blur(100px);opacity:.18;pointer-events:none;z-index:0}\n.blob-1{width:600px;height:600px;top:-120px;left:-100px;background:radial-gradient(circle,var(--cyan),transparent 70%);animation:drift1 22s ease-in-out infinite}\n.blob-2{width:500px;height:500px;bottom:-80px;right:-60px;background:radial-gradient(circle,var(--violet),transparent 70%);animation:drift2 26s ease-in-out infinite}\n.blob-3{width:350px;height:350px;top:40%;left:50%;background:radial-gradient(circle,var(--emerald),transparent 70%);animation:drift3 30s ease-in-out infinite;opacity:.08}\n@keyframes drift1{0%,100%{transform:translate(0,0) scale(1)}33%{transform:translate(60px,-40px) scale(1.08)}66%{transform:translate(-30px,50px) scale(.95)}}\n@keyframes drift2{0%,100%{transform:translate(0,0) scale(1)}33%{transform:translate(-50px,30px) scale(1.05)}66%{transform:translate(40px,-60px) scale(.92)}}\n@keyframes drift3{0%,100%{transform:translate(-50%,-50%) scale(1)}50%{transform:translate(-40%,-60%) scale(1.1)}}\n\n.container{position:relative;z-index:1;max-width:1280px;margin:0 auto;padding:24px 20px 48px}\n\n/* Header */\n.header{display:flex;align-items:center;justify-content:space-between;margin-bottom:28px}\n.header h1{font-size:1.6rem;font-weight:700;letter-spacing:-.02em}\n.header h1 span{background:linear-gradient(135deg,var(--cyan),var(--violet));-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent}\n.status-dot{width:8px;height:8px;border-radius:50%;background:var(--emerald);box-shadow:0 0 8px var(--emerald);display:inline-block;margin-right:8px;animation:pulse 2s ease-in-out infinite}\n.status-dot.disconnected{background:var(--pink);box-shadow:0 0 8px var(--pink)}\n@keyframes pulse{0%,100%{opacity:1}50%{opacity:.4}}\n.conn-label{font-size:.75rem;color:var(--slate);display:flex;align-items:center;gap:6px}\n\n/* Grid */\n.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(360px,1fr));gap:16px}\n.grid-wide{grid-column:1/-1}\n\n/* Cards */\n.card{background:var(--glass-bg);backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px);border:1px solid var(--glass-border);border-radius:var(--radius);padding:18px 20px;box-shadow:var(--shadow-glass);transition:border-color .25s,box-shadow .25s}\n.card:hover{border-color:rgba(255,255,255,0.1);box-shadow:var(--shadow-glass),var(--glow-sm)}\n.card-title{font-size:.7rem;text-transform:uppercase;letter-spacing:.08em;color:var(--slate);margin-bottom:12px;display:flex;align-items:center;gap:6px}\n.card-title .icon{font-size:.85rem}\n\n/* Health donut */\n.health-row{display:flex;align-items:center;gap:24px}\n.donut-wrap{position:relative;width:100px;height:100px;flex-shrink:0}\n.donut-wrap svg{transform:rotate(-90deg)}\n.donut-wrap .donut-label{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;flex-direction:column;font-size:1.5rem;font-weight:700;line-height:1}\n.donut-wrap .donut-label small{font-size:.6rem;color:var(--slate);font-weight:400;margin-top:2px}\n.health-legend{display:flex;flex-direction:column;gap:6px}\n.legend-item{display:flex;align-items:center;gap:8px;font-size:.8rem}\n.legend-dot{width:8px;height:8px;border-radius:50%;flex-shrink:0}\n\n/* Decay bars */\n.decay-list{display:flex;flex-direction:column;gap:8px;max-height:280px;overflow-y:auto}\n.decay-item{display:flex;align-items:center;gap:10px}\n.decay-key{font-family:'SF Mono',SFMono-Regular,Consolas,'Liberation Mono',Menlo,monospace;font-size:.78rem;min-width:120px;max-width:180px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.decay-bar{flex:1;height:6px;border-radius:3px;background:rgba(255,255,255,0.06);overflow:hidden;position:relative}\n.decay-fill{height:100%;border-radius:3px;transition:width .6s ease}\n.decay-time{font-size:.7rem;color:var(--slate);min-width:56px;text-align:right}\n\n/* Superposition pills */\n.super-list{display:flex;flex-direction:column;gap:8px;max-height:280px;overflow-y:auto}\n.super-item{display:flex;align-items:center;gap:8px;flex-wrap:wrap}\n.super-key{font-family:'SF Mono',SFMono-Regular,Consolas,monospace;font-size:.78rem;min-width:100px}\n.env-pill{font-size:.65rem;padding:2px 8px;border-radius:99px;font-weight:600;letter-spacing:.03em}\n.env-prod{background:rgba(255,0,85,0.2);color:var(--pink);border:1px solid rgba(255,0,85,0.3)}\n.env-staging{background:rgba(255,184,0,0.15);color:var(--amber);border:1px solid rgba(255,184,0,0.25)}\n.env-dev{background:rgba(0,230,118,0.15);color:var(--emerald);border:1px solid rgba(0,230,118,0.25)}\n.env-default{background:rgba(178,102,255,0.15);color:var(--violet);border:1px solid rgba(178,102,255,0.25)}\n\n/* Entanglement */\n.entangle-list{display:flex;flex-direction:column;gap:6px;max-height:240px;overflow-y:auto}\n.entangle-pair{display:flex;align-items:center;gap:8px;font-family:'SF Mono',SFMono-Regular,Consolas,monospace;font-size:.78rem}\n.entangle-arrow{color:var(--cyan)}\n\n/* Tunnels */\n.tunnel-list{display:flex;flex-direction:column;gap:8px;max-height:240px;overflow-y:auto}\n.tunnel-card{background:rgba(178,102,255,0.06);border:1px solid rgba(178,102,255,0.12);border-radius:8px;padding:10px 12px;font-size:.78rem;font-family:'SF Mono',SFMono-Regular,Consolas,monospace}\n.tunnel-meta{display:flex;gap:12px;margin-top:4px;font-size:.7rem;color:var(--slate)}\n\n/* Audit feed */\n.audit-feed{display:flex;flex-direction:column;gap:4px;max-height:300px;overflow-y:auto;font-size:.75rem;font-family:'SF Mono',SFMono-Regular,Consolas,monospace}\n.audit-row{display:flex;gap:8px;padding:3px 0;border-bottom:1px solid rgba(255,255,255,0.03)}\n.audit-ts{color:var(--slate);min-width:70px;flex-shrink:0}\n.audit-action{min-width:64px;font-weight:600}\n.audit-action.read{color:var(--cyan)}.audit-action.write{color:var(--emerald)}.audit-action.delete{color:var(--pink)}\n.audit-action.entangle{color:var(--violet)}.audit-action.tunnel{color:var(--violet)}.audit-action.teleport{color:var(--amber)}\n.audit-action.generate{color:var(--amber)}.audit-action.list{color:var(--slate)}.audit-action.export{color:var(--slate)}\n.audit-action.collapse{color:var(--cyan)}\n.audit-key{color:var(--ghost)}\n.audit-detail{color:var(--slate);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n\n/* Anomalies */\n.anomaly-card{background:rgba(255,0,85,0.06);border:1px solid rgba(255,0,85,0.15);border-radius:8px;padding:10px 14px;animation:anomaly-pulse 3s ease-in-out infinite}\n@keyframes anomaly-pulse{0%,100%{border-color:rgba(255,0,85,0.15)}50%{border-color:rgba(255,0,85,0.35)}}\n.anomaly-type{font-size:.7rem;text-transform:uppercase;letter-spacing:.06em;color:var(--pink);font-weight:700;margin-bottom:2px}\n.anomaly-desc{font-size:.8rem;color:var(--ghost)}\n\n/* Environment badge */\n.env-status{display:flex;align-items:center;gap:12px}\n.env-big{font-size:1.1rem;font-weight:700;padding:4px 14px;border-radius:8px}\n.env-source{font-size:.75rem;color:var(--slate)}\n\n/* Empty states */\n.empty{color:var(--slate);font-size:.8rem;font-style:italic;padding:8px 0}\n\n/* Scrollbar */\n::-webkit-scrollbar{width:4px;height:4px}\n::-webkit-scrollbar-track{background:transparent}\n::-webkit-scrollbar-thumb{background:rgba(255,255,255,0.08);border-radius:2px}\n::-webkit-scrollbar-thumb:hover{background:rgba(255,255,255,0.15)}\n</style>\n</head>\n<body>\n<div class=\"blob blob-1\"></div>\n<div class=\"blob blob-2\"></div>\n<div class=\"blob blob-3\"></div>\n<div class=\"container\">\n <div class=\"header\">\n <h1>\\u26a1 <span>q-ring</span> quantum status</h1>\n <div class=\"conn-label\"><span class=\"status-dot\" id=\"connDot\"></span><span id=\"connText\">connecting\\u2026</span></div>\n </div>\n <div class=\"grid\" id=\"dashboard\">\n <div class=\"card\"><div class=\"empty\">Connecting to q-ring\\u2026</div></div>\n </div>\n</div>\n\n<script>\n(function(){\n const $ = (id) => document.getElementById(id);\n const dash = $('dashboard');\n const dot = $('connDot');\n const connText = $('connText');\n let connected = false;\n\n function esc(s){ const d=document.createElement('div');d.textContent=s;return d.innerHTML; }\n\n function envClass(e){\n if(e==='prod'||e==='production') return 'env-prod';\n if(e==='staging'||e==='stage') return 'env-staging';\n if(e==='dev'||e==='development') return 'env-dev';\n return 'env-default';\n }\n\n function decayColor(pct,expired){\n if(expired) return 'var(--pink)';\n if(pct>=90) return 'var(--pink)';\n if(pct>=75) return 'var(--amber)';\n return 'var(--cyan)';\n }\n\n function fmtTime(ts){\n const d=new Date(ts);\n const h=d.getHours().toString().padStart(2,'0');\n const m=d.getMinutes().toString().padStart(2,'0');\n const s=d.getSeconds().toString().padStart(2,'0');\n return h+':'+m+':'+s;\n }\n\n function renderHealth(h){\n const total=h.total||1;\n const r=42, circ=2*Math.PI*r;\n const slices=[\n {v:h.healthy,c:'var(--cyan)'},\n {v:h.stale,c:'var(--amber)'},\n {v:h.expired,c:'var(--pink)'},\n {v:h.noDecay,c:'var(--slate)'}\n ];\n let offset=0;\n let rings='';\n for(const sl of slices){\n const pct=sl.v/total;\n const len=pct*circ;\n rings+=\\`<circle cx=\"50\" cy=\"50\" r=\"\\${r}\" fill=\"none\" stroke=\"\\${sl.c}\" stroke-width=\"12\" stroke-dasharray=\"\\${len} \\${circ-len}\" stroke-dashoffset=\"-\\${offset}\" stroke-linecap=\"round\" opacity=\"0.85\"/>\\`;\n offset+=len;\n }\n return \\`<div class=\"health-row\">\n <div class=\"donut-wrap\">\n <svg viewBox=\"0 0 100 100\" width=\"100\" height=\"100\">\\${rings}</svg>\n <div class=\"donut-label\">\\${h.total}<small>secrets</small></div>\n </div>\n <div class=\"health-legend\">\n <div class=\"legend-item\"><span class=\"legend-dot\" style=\"background:var(--cyan)\"></span>Healthy \\${h.healthy}</div>\n <div class=\"legend-item\"><span class=\"legend-dot\" style=\"background:var(--amber)\"></span>Stale \\${h.stale}</div>\n <div class=\"legend-item\"><span class=\"legend-dot\" style=\"background:var(--pink)\"></span>Expired \\${h.expired}</div>\n <div class=\"legend-item\"><span class=\"legend-dot\" style=\"background:var(--slate)\"></span>No decay \\${h.noDecay}</div>\n </div>\n </div>\\`;\n }\n\n function renderDecay(secrets){\n const withDecay=secrets.filter(s=>s.decay&&s.decay.timeRemaining);\n if(!withDecay.length) return '<div class=\"empty\">No secrets with decay configured</div>';\n return '<div class=\"decay-list\">'+withDecay.map(s=>{\n const pct=Math.min(s.decay.lifetimePercent,100);\n const col=decayColor(pct,s.decay.isExpired);\n const label=s.decay.isExpired?'expired':s.decay.timeRemaining||'';\n return \\`<div class=\"decay-item\">\n <span class=\"decay-key\" title=\"\\${esc(s.key)}\">\\${esc(s.key)}</span>\n <div class=\"decay-bar\"><div class=\"decay-fill\" style=\"width:\\${pct}%;background:\\${col}\"></div></div>\n <span class=\"decay-time\" style=\"color:\\${col}\">\\${esc(label)}</span>\n </div>\\`;\n }).join('')+'</div>';\n }\n\n function renderSuperposition(secrets){\n const sup=secrets.filter(s=>s.type==='superposition'&&s.environments);\n if(!sup.length) return '<div class=\"empty\">No secrets in superposition</div>';\n return '<div class=\"super-list\">'+sup.map(s=>{\n const pills=(s.environments||[]).map(e=>{\n const isDefault=e===s.defaultEnv;\n return \\`<span class=\"env-pill \\${envClass(e)}\">\\${esc(e)}\\${isDefault?' \\u2713':''}</span>\\`;\n }).join('');\n return \\`<div class=\"super-item\"><span class=\"super-key\">\\${esc(s.key)}</span>\\${pills}</div>\\`;\n }).join('')+'</div>';\n }\n\n function renderEntanglements(pairs){\n if(!pairs.length) return '<div class=\"empty\">No entangled secrets</div>';\n const seen=new Set();\n const unique=pairs.filter(p=>{\n const id=[p.source.service,p.source.key,p.target.service,p.target.key].sort().join('|');\n if(seen.has(id)) return false;\n seen.add(id);return true;\n });\n return '<div class=\"entangle-list\">'+unique.map(p=>\n \\`<div class=\"entangle-pair\"><span>\\${esc(p.source.key)}</span><span class=\"entangle-arrow\">\\u2194</span><span>\\${esc(p.target.key)}</span></div>\\`\n ).join('')+'</div>';\n }\n\n function renderTunnels(tunnels){\n if(!tunnels.length) return '<div class=\"empty\">No active tunnels</div>';\n return '<div class=\"tunnel-list\">'+tunnels.map(t=>{\n const rem=t.expiresAt?Math.max(0,Math.floor((t.expiresAt-Date.now())/1000)):null;\n return \\`<div class=\"tunnel-card\">\\${esc(t.id)}<div class=\"tunnel-meta\">\n <span>reads: \\${t.accessCount}\\${t.maxReads?'/'+t.maxReads:''}</span>\n \\${rem!==null?'<span>expires: '+rem+'s</span>':'<span>no expiry</span>'}\n </div></div>\\`;\n }).join('')+'</div>';\n }\n\n function renderAudit(events){\n if(!events.length) return '<div class=\"empty\">No audit events</div>';\n return '<div class=\"audit-feed\">'+events.slice(0,40).map(e=>\n \\`<div class=\"audit-row\">\n <span class=\"audit-ts\">\\${fmtTime(e.timestamp)}</span>\n <span class=\"audit-action \\${e.action}\">\\${e.action}</span>\n <span class=\"audit-key\">\\${e.key?esc(e.key):''}</span>\n <span class=\"audit-detail\">\\${e.detail?esc(e.detail):''}</span>\n </div>\\`\n ).join('')+'</div>';\n }\n\n function renderAnomalies(anomalies){\n if(!anomalies.length) return '<div class=\"empty\">No anomalies detected \\u2014 all clear</div>';\n return anomalies.map(a=>\n \\`<div class=\"anomaly-card\"><div class=\"anomaly-type\">\\${esc(a.type)}</div><div class=\"anomaly-desc\">\\${esc(a.description)}</div></div>\\`\n ).join('');\n }\n\n function renderEnvironment(env){\n if(!env) return '<div class=\"empty\">No environment detected</div>';\n return \\`<div class=\"env-status\">\n <span class=\"env-big env-pill \\${envClass(env.env)}\">\\${esc(env.env)}</span>\n <span class=\"env-source\">detected via \\${esc(env.source)}</span>\n </div>\\`;\n }\n\n function render(snap){\n dash.innerHTML=\\`\n <div class=\"card\">\n <div class=\"card-title\"><span class=\"icon\">\\u{1f6e1}\\ufe0f</span> Health Summary</div>\n \\${renderHealth(snap.health)}\n </div>\n <div class=\"card\">\n <div class=\"card-title\"><span class=\"icon\">\\u26a1</span> Environment</div>\n \\${renderEnvironment(snap.environment)}\n </div>\n <div class=\"card\">\n <div class=\"card-title\"><span class=\"icon\">\\u23f0</span> Decay Timers</div>\n \\${renderDecay(snap.secrets)}\n </div>\n <div class=\"card\">\n <div class=\"card-title\"><span class=\"icon\">\\u{1f30a}</span> Superposition States</div>\n \\${renderSuperposition(snap.secrets)}\n </div>\n <div class=\"card\">\n <div class=\"card-title\"><span class=\"icon\">\\u{1f517}</span> Entanglement</div>\n \\${renderEntanglements(snap.entanglements)}\n </div>\n <div class=\"card\">\n <div class=\"card-title\"><span class=\"icon\">\\u{1f47b}</span> Quantum Tunnels</div>\n \\${renderTunnels(snap.tunnels)}\n </div>\n <div class=\"card grid-wide\">\n <div class=\"card-title\"><span class=\"icon\">\\u26a0\\ufe0f</span> Anomaly Alerts</div>\n \\${renderAnomalies(snap.anomalies)}\n </div>\n <div class=\"card grid-wide\">\n <div class=\"card-title\"><span class=\"icon\">\\u{1f441}\\ufe0f</span> Audit Log</div>\n \\${renderAudit(snap.audit)}\n </div>\n \\`;\n }\n\n function connect(){\n const es=new EventSource('/events');\n es.onopen=()=>{\n connected=true;\n dot.classList.remove('disconnected');\n connText.textContent='live';\n };\n es.onmessage=(e)=>{\n try{ render(JSON.parse(e.data)); }catch(err){ console.error('render error',err); }\n };\n es.onerror=()=>{\n connected=false;\n dot.classList.add('disconnected');\n connText.textContent='reconnecting\\u2026';\n };\n }\n\n connect();\n})();\n</script>\n</body>\n</html>`;\n}\n"],"mappings":";;;;;;;;;;;;AAOA,SAAS,oBAA4E;;;ACC9E,SAAS,mBAA2B;AACzC,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;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","names":[]}
|