@continuous-excellence/ze-great-dashboard-aws 0.1.13

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.
@@ -0,0 +1,11 @@
1
+ {
2
+ "index.html": {
3
+ "file": "assets/index-BddJ8Qxp.js",
4
+ "name": "index",
5
+ "src": "index.html",
6
+ "isEntry": true,
7
+ "css": [
8
+ "assets/index-3-dS42M8.css"
9
+ ]
10
+ }
11
+ }
@@ -0,0 +1 @@
1
+ :root{--surface:#12140f;--surface-raised:#1a1a19;--ink-primary:#fff;--ink-secondary:#c3c2b7;--ink-muted:#898781;--edge:#2f312b;--status-good:#0ca30c;--status-warning:#fab219;--status-serious:#ec835a;--status-critical:#d03b3b;--gap:1rem;--radius:.5rem;--lightningcss-light: ;--lightningcss-dark:initial;color-scheme:dark}*{box-sizing:border-box}html,body{height:100%;margin:0}body{background:var(--surface);color:var(--ink-primary);font-family:ui-sans-serif,system-ui,-apple-system,Segoe UI,Helvetica,Arial,sans-serif;font-size:clamp(16px,1.1vw + .5rem,28px);line-height:1.4}.board{gap:var(--gap);min-height:100%;padding:var(--gap);flex-direction:column;display:flex}.board__header{justify-content:space-between;align-items:baseline;gap:var(--gap);border-bottom:1px solid var(--edge);padding-bottom:var(--gap);flex-wrap:wrap;display:flex}.board__title{letter-spacing:-.01em;margin:0;font-size:1.75em;font-weight:650}.board__meta{gap:calc(var(--gap) * 1.5);flex-wrap:wrap;margin:0;font-size:.7em;display:flex}.board__meta div{flex-direction:column;display:flex}.board__meta dt{color:var(--ink-muted);text-transform:uppercase;letter-spacing:.08em;font-size:.8em}.board__meta dd{color:var(--ink-secondary);overflow-wrap:anywhere;margin:0;font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.board__grid{gap:var(--gap);flex:1;grid-template-columns:repeat(12,1fr);align-content:start;display:grid}.panel{grid-column:var(--panel-column,span 6);grid-row:var(--panel-row,auto);min-height:6em;padding:var(--gap);border:1px solid var(--edge);border-radius:var(--radius);background:var(--surface-raised);flex-direction:column;justify-content:center;gap:.25em;display:flex;overflow:hidden}.panel--wide{grid-column:span 12}.panel__label{color:var(--ink-secondary);margin:0;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:1em;font-weight:600}.panel__hint{color:var(--ink-muted);margin:0;font-size:.75em}.panel__observed{align-items:center;gap:.35em;display:flex}.panel__observed--stale{color:var(--status-warning)}.panel__status{margin:0;font-size:1.35em;font-weight:650}.panel__status--passed{color:var(--status-good)}.panel__status--failed{color:var(--status-critical)}.panel__status--running{color:var(--status-warning)}.panel__status--cancelled,.panel__status--unknown{color:var(--ink-secondary)}.panel--error{border-color:var(--status-serious)}.panel__link{color:inherit;text-decoration:none}.panel__link:hover .panel__status{text-decoration:underline}.board__footer{color:var(--ink-muted);font-size:.75em}.config-error{max-width:60ch;color:var(--ink-primary);margin:0 auto;padding:2rem}.config-error pre{border-radius:var(--radius);border:1px solid var(--status-critical);background:var(--surface-raised);color:var(--ink-secondary);white-space:pre-wrap;overflow-wrap:anywhere;padding:1rem;font-size:.75em}@media (width<=60rem){.board__grid{grid-template-columns:1fr}.panel{grid-area:auto/1/auto/-1}}
@@ -0,0 +1,3 @@
1
+ import{z as e}from"zod";import{StrictMode as t,useEffect as n,useState as r}from"react";import{createRoot as i}from"react-dom/client";import{Fragment as a,jsx as o,jsxs as s}from"react/jsx-runtime";(function(){let e=document.createElement(`link`).relList;if(e&&e.supports&&e.supports(`modulepreload`))return;for(let e of document.querySelectorAll(`link[rel="modulepreload"]`))n(e);new MutationObserver(e=>{for(let t of e)if(t.type===`childList`)for(let e of t.addedNodes)e.tagName===`LINK`&&e.rel===`modulepreload`&&n(e)}).observe(document,{childList:!0,subtree:!0});function t(e){let t={};return e.integrity&&(t.integrity=e.integrity),e.referrerPolicy&&(t.referrerPolicy=e.referrerPolicy),t.credentials=e.crossOrigin===`use-credentials`?`include`:e.crossOrigin===`anonymous`?`omit`:`same-origin`,t}function n(e){if(e.ep)return;e.ep=!0;let n=t(e);fetch(e.href,n)}})();var c=/^(\d+)(ms|s|m|h)$/,l={ms:1,s:1e3,m:6e4,h:36e5};function u(e){let t=c.exec(e);if(!t)return null;let[,n,r]=t;if(n===void 0||r===void 0)return null;let i=Number(n);return i<=0?null:i*l[r]}var d=e.string().refine(e=>u(e)!==null,{message:`must be a positive duration like "30s", "5m", or "1h"`}).brand(),f=e.object({x:e.number().int().min(0),y:e.number().int().min(0),w:e.number().int().min(1).max(12),h:e.number().int().min(1)}),p=e.looseObject({id:e.string().min(1),type:e.string().min(1),source:e.string().min(1).optional(),position:f.optional(),refresh:d.optional(),link:e.url().optional(),url:e.url().optional(),json_path:e.string().regex(/^\$(?:\.[A-Za-z_][A-Za-z0-9_]*|\[\d+\])*$/,`must be a simple JSON path`).optional()}),m=e.looseObject({type:e.string().min(1),token_env:e.string().min(1).optional()}),h=e.object({refresh:d.optional(),panels:e.array(p).min(1).superRefine((e,t)=>{let n=new Map;e.forEach((e,r)=>{let i=n.get(e.id);if(i===void 0){n.set(e.id,r);return}t.addIssue({code:`custom`,path:[r,`id`],message:`duplicate panel id "${e.id}" (already used by panel at index ${i})`})})})}),g=e.object({issuer:e.url(),client_id:e.string().min(1).optional(),allow:e.object({groups:e.array(e.string().min(1)).optional(),subjects:e.array(e.string().min(1)).optional()}).optional()});e.object({sources:e.record(e.string().min(1),m).default({}),boards:e.record(e.string().min(1),h),auth:g.optional()});function _(e){let t=e.panelOverrideMillis??e.boardDefaultMillis;return Math.max(t,e.adapterFloorMillis)}var v=e.object({assetPath:e.string().min(1),proxyPath:e.string().min(1),board:e.string().min(1),clientVersion:e.string().min(1),auth:e.object({issuer:e.string().min(1),clientId:e.string().min(1).optional()}).optional()});function y(t=globalThis.window?.env){let n=v.safeParse(t);if(!n.success)throw Error(`window.env is missing or invalid \u2014 the server did not inject usable configuration.
2
+ ${e.prettifyError(n.error)}`);return n.data}var b=e.enum([`unreachable`,`unauthorized`,`not-found`,`upstream-error`]),x=e.object({kind:b,message:e.string()}),S=e.discriminatedUnion(`state`,[e.object({panelId:e.string().min(1),state:e.literal(`ok`),observedAt:e.iso.datetime(),link:e.url().nullable(),signal:e.unknown()}),e.object({panelId:e.string().min(1),state:e.literal(`error`),observedAt:e.iso.datetime(),link:e.url().nullable(),error:x})]),C=e.object({type:e.literal(`pipeline-status`),status:e.enum([`passed`,`failed`,`running`,`cancelled`,`unknown`]),rawStatus:e.string(),name:e.string().min(1)}),w=e.object({type:e.literal(`http-value`),value:e.union([e.string(),e.number(),e.boolean()])});function T({value:e}){let t=new Date(e),n=t.toLocaleTimeString([],{hour:`2-digit`,minute:`2-digit`}),r=Date.now()-t.getTime(),i=r>3e5,a=E(r);return s(`p`,{className:`panel__hint panel__observed${i?` panel__observed--stale`:``}`,children:[o(`span`,{"aria-hidden":`true`,children:`◷`}),` As of `,n,` · `,a]})}function E(e){if(e<6e4)return`just now`;let t=Math.floor(e/6e4);return t<60?`${t}m ago`:`${Math.floor(t/60)}h ${t%60}m ago`}function D(e){let t=e.position;if(t)return{"--panel-column":`${t.x+1} / span ${t.w}`,"--panel-row":`${t.y+1} / span ${t.h}`}}function O({panel:e,data:t}){if(!t)return s(`section`,{className:`panel`,style:D(e),"aria-busy":`true`,children:[o(`h2`,{className:`panel__label`,children:e.id}),o(`p`,{className:`panel__hint`,children:`Loading…`})]});if(t.state===`error`)return s(`section`,{className:`panel panel--error`,style:D(e),children:[o(`h2`,{className:`panel__label`,children:e.id}),o(`p`,{className:`panel__status`,children:`⚠ Unable to read`}),o(`p`,{className:`panel__hint`,children:t.error.message}),o(T,{value:t.observedAt})]});let n=w.safeParse(t.signal);if(!n.success)return s(`section`,{className:`panel panel--error`,style:D(e),children:[o(`h2`,{className:`panel__label`,children:e.id}),o(`p`,{className:`panel__status`,children:`⚠ Invalid value`}),o(T,{value:t.observedAt})]});let r=s(a,{children:[o(`p`,{className:`panel__status`,children:String(n.data.value)}),o(T,{value:t.observedAt})]});return s(`section`,{className:`panel`,style:D(e),children:[o(`h2`,{className:`panel__label`,children:e.id}),t.link?o(`a`,{className:`panel__link`,href:t.link,children:r}):r]})}function k({label:e,hint:t,wide:n=!1}){return s(`section`,{className:n?`panel panel--wide`:`panel`,children:[o(`h2`,{className:`panel__label`,children:e}),o(`p`,{className:`panel__hint`,children:t})]})}function A({panel:e,data:t}){if(!t)return s(`section`,{className:`panel`,style:D(e),"aria-busy":`true`,children:[o(`h2`,{className:`panel__label`,children:e.id}),o(`p`,{className:`panel__hint`,children:`Loading…`})]});if(t.state===`error`)return s(`section`,{className:`panel panel--error`,style:D(e),children:[o(`h2`,{className:`panel__label`,children:e.id}),o(`p`,{className:`panel__status`,children:`⚠ Unable to read`}),o(`p`,{className:`panel__hint`,children:t.error.message}),o(T,{value:t.observedAt})]});let n=C.safeParse(t.signal);if(!n.success)return s(`section`,{className:`panel panel--error`,style:D(e),children:[o(`h2`,{className:`panel__label`,children:e.id}),o(`p`,{className:`panel__status`,children:`⚠ Invalid signal`}),o(T,{value:t.observedAt})]});let r=j(n.data.status),i=s(a,{children:[s(`p`,{className:`panel__status panel__status--${n.data.status}`,children:[r.glyph,` `,r.label]}),s(`p`,{className:`panel__hint`,children:[n.data.name,` · `,n.data.rawStatus]}),o(T,{value:t.observedAt})]});return s(`section`,{className:`panel`,style:D(e),children:[o(`h2`,{className:`panel__label`,children:e.id}),t.link?o(`a`,{className:`panel__link`,href:t.link,children:i}):i]})}function j(e){switch(e){case`passed`:return{glyph:`✓`,label:`Passed`};case`failed`:return{glyph:`✕`,label:`Failed`};case`running`:return{glyph:`↻`,label:`Running`};case`cancelled`:return{glyph:`⊘`,label:`Cancelled`};case`unknown`:return{glyph:`?`,label:`Unknown`}}}function M(e){let t=S.safeParse(e);return t.success?t.data:void 0}function N({env:e}){let[t,i]=r(),[a,c]=r(),[l,d]=r({});return n(()=>{let t=!1;return i(void 0),c(void 0),d({}),fetch(`${e.proxyPath}/boards/${encodeURIComponent(e.board)}`).then(e=>e.ok?e.json():Promise.reject(Error(`Board configuration returned ${e.status}`))).then(n=>{t||(i(n),c(e.board))}).catch(()=>{t||(i({panels:[]}),c(e.board))}),()=>{t=!0}},[e.board,e.proxyPath]),n(()=>{if(!t||a!==e.board)return;let n=!1,r=[];for(let i of t.panels){if(i.type!==`pipeline-status`&&i.type!==`http-value`)continue;let a=!1,o=_({boardDefaultMillis:u(t.refresh??`60s`)??6e4,panelOverrideMillis:i.refresh?u(i.refresh)??void 0:void 0,adapterFloorMillis:0}),s=()=>{n||a||(a=!0,fetch(`${e.proxyPath}/panel/${encodeURIComponent(e.board)}/${encodeURIComponent(i.id)}`).then(e=>e.status===304?void 0:e.json()).then(e=>{let t=M(e);!n&&t&&d(e=>({...e,[i.id]:t}))}).catch(()=>void 0).finally(()=>{a=!1}))};s(),r.push(window.setInterval(s,o))}return()=>{n=!0;for(let e of r)window.clearInterval(e)}},[t,e.board,e.proxyPath,a]),s(`div`,{className:`board`,children:[s(`header`,{className:`board__header`,children:[o(`h1`,{className:`board__title`,children:e.board}),s(`dl`,{className:`board__meta`,children:[s(`div`,{children:[o(`dt`,{children:`client`}),o(`dd`,{children:e.clientVersion})]}),s(`div`,{children:[o(`dt`,{children:`assets`}),o(`dd`,{children:e.assetPath})]})]})]}),s(`main`,{className:`board__grid`,children:[!t&&o(k,{label:`board`,hint:`Loading configuration…`,wide:!0}),t?.panels.map(e=>e.type===`pipeline-status`?o(A,{panel:e,data:l[e.id]},e.id):e.type===`http-value`?o(O,{panel:e,data:l[e.id]},e.id):o(k,{label:e.type,hint:`Not wired yet`,wide:!0},e.id))]}),o(`footer`,{className:`board__footer`,children:`Signals are read live from their configured authorities.`})]})}function P({message:e}){return s(`div`,{className:`config-error`,role:`alert`,children:[o(`h1`,{children:`⚠️ Dashboard misconfigured`}),o(`p`,{children:`The page loaded, but the server did not hand it usable configuration.`}),o(`pre`,{children:e})]})}var F=document.getElementById(`root`);if(!F)throw Error(`No #root element in the document — the template is not the one we expect.`);var I=i(F);try{I.render(o(t,{children:o(N,{env:y()})}))}catch(e){I.render(o(P,{message:e instanceof Error?e.message:String(e)}))}
3
+ //# sourceMappingURL=index-BddJ8Qxp.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index-BddJ8Qxp.js","names":[],"sources":["../../../shared/dist/index.js","../../src/ObservedAt.tsx","../../src/panel-layout.ts","../../src/HttpValuePanel.tsx","../../src/PanelPlaceholder.tsx","../../src/PipelinePanel.tsx","../../src/App.tsx","../../src/ConfigError.tsx","../../src/main.tsx"],"sourcesContent":["// packages/shared/src/board-config.ts\nimport { z as z2 } from \"zod\";\n\n// packages/shared/src/duration.ts\nimport { z } from \"zod\";\nvar DURATION_PATTERN = /^(\\d+)(ms|s|m|h)$/;\nvar UNIT_MILLIS = {\n ms: 1,\n s: 1e3,\n m: 6e4,\n h: 36e5\n};\nfunction parseDuration(value) {\n const match = DURATION_PATTERN.exec(value);\n if (!match) return null;\n const [, rawAmount, rawUnit] = match;\n if (rawAmount === void 0 || rawUnit === void 0) return null;\n const amount = Number(rawAmount);\n if (amount <= 0) return null;\n return amount * UNIT_MILLIS[rawUnit];\n}\nvar durationSchema = z.string().refine((value) => parseDuration(value) !== null, {\n message: 'must be a positive duration like \"30s\", \"5m\", or \"1h\"'\n}).brand();\n\n// packages/shared/src/board-config.ts\nvar positionSchema = z2.object({\n x: z2.number().int().min(0),\n y: z2.number().int().min(0),\n w: z2.number().int().min(1).max(12),\n h: z2.number().int().min(1)\n});\nvar panelSchema = z2.looseObject({\n id: z2.string().min(1),\n type: z2.string().min(1),\n source: z2.string().min(1).optional(),\n /** Advisory in v1 — a panel without a position renders in config order rather than not at all. */\n position: positionSchema.optional(),\n refresh: durationSchema.optional(),\n /** A deliberate override only. Adapters derive links; hand-written ones drift. */\n link: z2.url().optional(),\n /** Source-agnostic endpoint used by the http-value signal. */\n url: z2.url().optional(),\n /** Small, deliberate JSON path subset: $.version, $.deployment.version, or $.response.docs[0].latestVersion. */\n json_path: z2.string().regex(/^\\$(?:\\.[A-Za-z_][A-Za-z0-9_]*|\\[\\d+\\])*$/, \"must be a simple JSON path\").optional()\n});\nvar sourceSchema = z2.looseObject({\n type: z2.string().min(1),\n /** Credentials are never in the config — only the name of the env var holding one. */\n token_env: z2.string().min(1).optional()\n});\nvar boardSchema = z2.object({\n refresh: durationSchema.optional(),\n panels: z2.array(panelSchema).min(1).superRefine((panels, ctx) => {\n const seen = /* @__PURE__ */ new Map();\n panels.forEach((panel, index) => {\n const firstIndex = seen.get(panel.id);\n if (firstIndex === void 0) {\n seen.set(panel.id, index);\n return;\n }\n ctx.addIssue({\n code: \"custom\",\n path: [index, \"id\"],\n message: `duplicate panel id \"${panel.id}\" (already used by panel at index ${firstIndex})`\n });\n });\n })\n});\nvar authSchema = z2.object({\n issuer: z2.url(),\n /** Absent in gateway mode — the gateway does the flow, the proxy still validates. */\n client_id: z2.string().min(1).optional(),\n allow: z2.object({\n groups: z2.array(z2.string().min(1)).optional(),\n subjects: z2.array(z2.string().min(1)).optional()\n }).optional()\n});\nvar boardConfigSchema = z2.object({\n sources: z2.record(z2.string().min(1), sourceSchema).default({}),\n boards: z2.record(z2.string().min(1), boardSchema),\n auth: authSchema.optional()\n});\nfunction resolveRefreshMillis(args) {\n const requested = args.panelOverrideMillis ?? args.boardDefaultMillis;\n return Math.max(requested, args.adapterFloorMillis);\n}\n\n// packages/shared/src/client-env.ts\nimport { z as z3 } from \"zod\";\nvar clientEnvSchema = z3.object({\n /** Where the immutable client assets live. The one variable that repoints the whole client. */\n assetPath: z3.string().min(1),\n /** Same-origin path the proxy is mounted at, so there is no CORS and no cookie problem. */\n proxyPath: z3.string().min(1),\n /** Which board this is, resolved server-side so the client parses no URLs. */\n board: z3.string().min(1),\n /** The client version being served, so two published versions are visibly distinguishable. */\n clientVersion: z3.string().min(1),\n /** Omitted entirely when unauthenticated — presence is what turns auth on. */\n auth: z3.object({\n issuer: z3.string().min(1),\n clientId: z3.string().min(1).optional()\n }).optional()\n});\nfunction readClientEnv(source = globalThis.window?.env) {\n const result = clientEnvSchema.safeParse(source);\n if (!result.success) {\n throw new Error(\n `window.env is missing or invalid \\u2014 the server did not inject usable configuration.\n${z3.prettifyError(result.error)}`\n );\n }\n return result.data;\n}\n\n// packages/shared/src/envelope.ts\nimport { z as z4 } from \"zod\";\nvar errorKindSchema = z4.enum([\n \"unreachable\",\n \"unauthorized\",\n \"not-found\",\n \"upstream-error\"\n]);\nvar envelopeErrorSchema = z4.object({\n kind: errorKindSchema,\n message: z4.string()\n});\nvar envelopeSchema = z4.discriminatedUnion(\"state\", [\n z4.object({\n panelId: z4.string().min(1),\n state: z4.literal(\"ok\"),\n /** From the upstream response `Date`. A cached value must never be shown without its age. */\n observedAt: z4.iso.datetime(),\n /** Adapter-derived. The authority for this panel; the dashboard itself asserts nothing. */\n link: z4.url().nullable(),\n signal: z4.unknown()\n }),\n z4.object({\n panelId: z4.string().min(1),\n state: z4.literal(\"error\"),\n /**\n * Populated even on error: knowing *when* a failure was observed is the difference between\n * \"broken just now\" and \"broken all morning\", and a panel that can't reach its source is\n * exactly when a viewer most wants to click through.\n */\n observedAt: z4.iso.datetime(),\n link: z4.url().nullable(),\n error: envelopeErrorSchema\n })\n]);\nvar pipelineStatusSchema = z4.object({\n type: z4.literal(\"pipeline-status\"),\n status: z4.enum([\"passed\", \"failed\", \"running\", \"cancelled\", \"unknown\"]),\n /** The source's unmodified status/result vocabulary, for an honest display. */\n rawStatus: z4.string(),\n name: z4.string().min(1)\n});\nvar httpValueSchema = z4.object({\n type: z4.literal(\"http-value\"),\n value: z4.union([z4.string(), z4.number(), z4.boolean()])\n});\nexport {\n authSchema,\n boardConfigSchema,\n boardSchema,\n clientEnvSchema,\n durationSchema,\n envelopeSchema,\n errorKindSchema,\n httpValueSchema,\n panelSchema,\n parseDuration,\n pipelineStatusSchema,\n positionSchema,\n readClientEnv,\n resolveRefreshMillis,\n sourceSchema\n};\n","export function ObservedAt({ value }: { value: string }) {\n const observed = new Date(value)\n const formatted = observed.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })\n const age = Date.now() - observed.getTime()\n const stale = age > 5 * 60 * 1000\n const ageText = formatAge(age)\n return (\n <p className={`panel__hint panel__observed${stale ? ' panel__observed--stale' : ''}`}>\n <span aria-hidden=\"true\">◷</span> As of {formatted} · {ageText}\n </p>\n )\n}\n\nfunction formatAge(milliseconds: number) {\n if (milliseconds < 60_000) return 'just now'\n const minutes = Math.floor(milliseconds / 60_000)\n if (minutes < 60) return `${minutes}m ago`\n const hours = Math.floor(minutes / 60)\n return `${hours}h ${minutes % 60}m ago`\n}\n","import type { Panel } from '@ze-great-dashboard/shared'\nimport type { CSSProperties } from 'react'\n\n/** Convert the advisory board position into CSS grid placement. */\nexport function panelLayout(panel: Panel): CSSProperties | undefined {\n const position = panel.position\n if (!position) return undefined\n\n return {\n '--panel-column': `${position.x + 1} / span ${position.w}`,\n '--panel-row': `${position.y + 1} / span ${position.h}`,\n } as CSSProperties\n}\n","import type { Panel } from '@ze-great-dashboard/shared'\nimport { type Envelope, httpValueSchema } from '@ze-great-dashboard/shared'\nimport { ObservedAt } from './ObservedAt.tsx'\nimport { panelLayout } from './panel-layout.ts'\n\nexport function HttpValuePanel({ panel, data }: { panel: Panel; data: Envelope | undefined }) {\n if (!data)\n return (\n <section className=\"panel\" style={panelLayout(panel)} aria-busy=\"true\">\n <h2 className=\"panel__label\">{panel.id}</h2>\n <p className=\"panel__hint\">Loading…</p>\n </section>\n )\n if (data.state === 'error')\n return (\n <section className=\"panel panel--error\" style={panelLayout(panel)}>\n <h2 className=\"panel__label\">{panel.id}</h2>\n <p className=\"panel__status\">⚠ Unable to read</p>\n <p className=\"panel__hint\">{data.error.message}</p>\n <ObservedAt value={data.observedAt} />\n </section>\n )\n const signal = httpValueSchema.safeParse(data.signal)\n if (!signal.success)\n return (\n <section className=\"panel panel--error\" style={panelLayout(panel)}>\n <h2 className=\"panel__label\">{panel.id}</h2>\n <p className=\"panel__status\">⚠ Invalid value</p>\n <ObservedAt value={data.observedAt} />\n </section>\n )\n const content = (\n <>\n <p className=\"panel__status\">{String(signal.data.value)}</p>\n <ObservedAt value={data.observedAt} />\n </>\n )\n return (\n <section className=\"panel\" style={panelLayout(panel)}>\n <h2 className=\"panel__label\">{panel.id}</h2>\n {data.link ? (\n <a className=\"panel__link\" href={data.link}>\n {content}\n </a>\n ) : (\n content\n )}\n </section>\n )\n}\n","/**\n * A panel-shaped hole, so the grid can be looked at and argued with before any signal exists.\n * Replaced by real panels in Stage 2 — nothing here is meant to survive.\n */\nexport function PanelPlaceholder({\n label,\n hint,\n wide = false,\n}: {\n label: string\n hint: string\n wide?: boolean\n}) {\n return (\n <section className={wide ? 'panel panel--wide' : 'panel'}>\n <h2 className=\"panel__label\">{label}</h2>\n <p className=\"panel__hint\">{hint}</p>\n </section>\n )\n}\n","import {\n type Envelope,\n envelopeSchema,\n type Panel,\n pipelineStatusSchema,\n} from '@ze-great-dashboard/shared'\nimport { ObservedAt } from './ObservedAt.tsx'\nimport { panelLayout } from './panel-layout.ts'\n\nexport function PipelinePanel({ panel, data }: { panel: Panel; data: Envelope | undefined }) {\n if (!data)\n return (\n <section className=\"panel\" style={panelLayout(panel)} aria-busy=\"true\">\n <h2 className=\"panel__label\">{panel.id}</h2>\n <p className=\"panel__hint\">Loading…</p>\n </section>\n )\n if (data.state === 'error') {\n return (\n <section className=\"panel panel--error\" style={panelLayout(panel)}>\n <h2 className=\"panel__label\">{panel.id}</h2>\n <p className=\"panel__status\">⚠ Unable to read</p>\n <p className=\"panel__hint\">{data.error.message}</p>\n <ObservedAt value={data.observedAt} />\n </section>\n )\n }\n\n const signal = pipelineStatusSchema.safeParse(data.signal)\n if (!signal.success)\n return (\n <section className=\"panel panel--error\" style={panelLayout(panel)}>\n <h2 className=\"panel__label\">{panel.id}</h2>\n <p className=\"panel__status\">⚠ Invalid signal</p>\n <ObservedAt value={data.observedAt} />\n </section>\n )\n const presentation = statusPresentation(signal.data.status)\n const content = (\n <>\n <p className={`panel__status panel__status--${signal.data.status}`}>\n {presentation.glyph} {presentation.label}\n </p>\n <p className=\"panel__hint\">\n {signal.data.name} · {signal.data.rawStatus}\n </p>\n <ObservedAt value={data.observedAt} />\n </>\n )\n return (\n <section className=\"panel\" style={panelLayout(panel)}>\n <h2 className=\"panel__label\">{panel.id}</h2>\n {data.link ? (\n <a className=\"panel__link\" href={data.link}>\n {content}\n </a>\n ) : (\n content\n )}\n </section>\n )\n}\n\nfunction statusPresentation(status: 'passed' | 'failed' | 'running' | 'cancelled' | 'unknown') {\n switch (status) {\n case 'passed':\n return { glyph: '✓', label: 'Passed' }\n case 'failed':\n return { glyph: '✕', label: 'Failed' }\n case 'running':\n return { glyph: '↻', label: 'Running' }\n case 'cancelled':\n return { glyph: '⊘', label: 'Cancelled' }\n case 'unknown':\n return { glyph: '?', label: 'Unknown' }\n }\n}\n\nexport function parseEnvelope(value: unknown): Envelope | undefined {\n const result = envelopeSchema.safeParse(value)\n return result.success ? result.data : undefined\n}\n","import {\n type Board,\n type ClientEnv,\n type Envelope,\n parseDuration,\n resolveRefreshMillis,\n} from '@ze-great-dashboard/shared'\nimport { useEffect, useState } from 'react'\nimport { HttpValuePanel } from './HttpValuePanel.tsx'\nimport { PanelPlaceholder } from './PanelPlaceholder.tsx'\nimport { PipelinePanel, parseEnvelope } from './PipelinePanel.tsx'\n\n/**\n * The board shell.\n *\n * Stage 1 has no data: there is no proxy endpoint to poll yet. What this proves is the plumbing —\n * that the client was handed its configuration by the server and knows which version of itself it\n * is. The version readout is what makes two published versions visibly different, which is the\n * whole point of the Stage 1 exit criterion.\n */\nexport function App({ env }: { env: ClientEnv }) {\n const [board, setBoard] = useState<Board>()\n const [loadedBoardName, setLoadedBoardName] = useState<string>()\n const [signals, setSignals] = useState<Record<string, Envelope | undefined>>({})\n\n useEffect(() => {\n let cancelled = false\n setBoard(undefined)\n setLoadedBoardName(undefined)\n setSignals({})\n fetch(`${env.proxyPath}/boards/${encodeURIComponent(env.board)}`)\n .then((response) =>\n response.ok\n ? response.json()\n : Promise.reject(new Error(`Board configuration returned ${response.status}`)),\n )\n .then((value: unknown) => {\n if (!cancelled) {\n setBoard(value as Board)\n setLoadedBoardName(env.board)\n }\n })\n .catch(() => {\n if (!cancelled) {\n setBoard({ panels: [] })\n setLoadedBoardName(env.board)\n }\n })\n return () => {\n cancelled = true\n }\n }, [env.board, env.proxyPath])\n\n useEffect(() => {\n if (!board || loadedBoardName !== env.board) return\n let cancelled = false\n const timers: number[] = []\n\n for (const panel of board.panels) {\n if (panel.type !== 'pipeline-status' && panel.type !== 'http-value') continue\n\n let inFlight = false\n const refreshMillis = resolveRefreshMillis({\n boardDefaultMillis: parseDuration(board.refresh ?? '60s') ?? 60_000,\n panelOverrideMillis: panel.refresh\n ? (parseDuration(panel.refresh) ?? undefined)\n : undefined,\n adapterFloorMillis: 0,\n })\n\n const refresh = () => {\n if (cancelled || inFlight) return\n inFlight = true\n fetch(\n `${env.proxyPath}/panel/${encodeURIComponent(env.board)}/${encodeURIComponent(panel.id)}`,\n )\n .then((response) => (response.status === 304 ? undefined : response.json()))\n .then((value: unknown) => {\n const envelope = parseEnvelope(value)\n if (!cancelled && envelope)\n setSignals((current) => ({ ...current, [panel.id]: envelope }))\n })\n .catch(() => undefined)\n .finally(() => {\n inFlight = false\n })\n }\n\n refresh()\n timers.push(window.setInterval(refresh, refreshMillis))\n }\n return () => {\n cancelled = true\n for (const timer of timers) window.clearInterval(timer)\n }\n }, [board, env.board, env.proxyPath, loadedBoardName])\n\n return (\n <div className=\"board\">\n <header className=\"board__header\">\n <h1 className=\"board__title\">{env.board}</h1>\n <dl className=\"board__meta\">\n <div>\n <dt>client</dt>\n <dd>{env.clientVersion}</dd>\n </div>\n <div>\n <dt>assets</dt>\n <dd>{env.assetPath}</dd>\n </div>\n </dl>\n </header>\n\n <main className=\"board__grid\">\n {!board && <PanelPlaceholder label=\"board\" hint=\"Loading configuration…\" wide />}\n {board?.panels.map((panel) =>\n panel.type === 'pipeline-status' ? (\n <PipelinePanel key={panel.id} panel={panel} data={signals[panel.id]} />\n ) : panel.type === 'http-value' ? (\n <HttpValuePanel key={panel.id} panel={panel} data={signals[panel.id]} />\n ) : (\n <PanelPlaceholder key={panel.id} label={panel.type} hint=\"Not wired yet\" wide />\n ),\n )}\n </main>\n\n <footer className=\"board__footer\">\n Signals are read live from their configured authorities.\n </footer>\n </div>\n )\n}\n","export function ConfigError({ message }: { message: string }) {\n return (\n <div className=\"config-error\" role=\"alert\">\n <h1>⚠️ Dashboard misconfigured</h1>\n <p>The page loaded, but the server did not hand it usable configuration.</p>\n <pre>{message}</pre>\n </div>\n )\n}\n","import { readClientEnv } from '@ze-great-dashboard/shared'\nimport { StrictMode } from 'react'\nimport { createRoot } from 'react-dom/client'\nimport { App } from './App.tsx'\nimport { ConfigError } from './ConfigError.tsx'\nimport './styles.css'\n\nconst container = document.getElementById('root')\nif (!container) {\n throw new Error('No #root element in the document — the template is not the one we expect.')\n}\n\nconst root = createRoot(container)\n\ntry {\n root.render(\n <StrictMode>\n <App env={readClientEnv()} />\n </StrictMode>,\n )\n} catch (error) {\n // A radiator that fails silently is worse than one that says what's wrong. If configuration\n // never arrived, say so on screen rather than rendering an empty board.\n root.render(<ConfigError message={error instanceof Error ? error.message : String(error)} />)\n}\n"],"mappings":"o2BAKA,IAAI,EAAmB,oBACnB,EAAc,CAChB,GAAI,EACJ,EAAG,IACH,EAAG,IACH,EAAG,IACL,EACA,SAAS,EAAc,EAAO,CAC5B,IAAM,EAAQ,EAAiB,KAAK,CAAK,EACzC,GAAI,CAAC,EAAO,OAAO,KACnB,GAAM,EAAG,EAAW,GAAW,EAC/B,GAAI,IAAc,IAAK,IAAK,IAAY,IAAK,GAAG,OAAO,KACvD,IAAM,EAAS,OAAO,CAAS,EAE/B,OADI,GAAU,EAAU,KACjB,EAAS,EAAY,EAC9B,CACA,IAAI,EAAiB,EAAE,OAAO,CAAC,CAAC,OAAQ,GAAU,EAAc,CAAK,IAAM,KAAM,CAC/E,QAAS,uDACX,CAAC,CAAC,CAAC,MAAM,EAGL,EAAiB,EAAG,OAAO,CAC7B,EAAG,EAAG,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,EAC1B,EAAG,EAAG,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,EAC1B,EAAG,EAAG,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,EAClC,EAAG,EAAG,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAC5B,CAAC,EACG,EAAc,EAAG,YAAY,CAC/B,GAAI,EAAG,OAAO,CAAC,CAAC,IAAI,CAAC,EACrB,KAAM,EAAG,OAAO,CAAC,CAAC,IAAI,CAAC,EACvB,OAAQ,EAAG,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,EAEpC,SAAU,EAAe,SAAS,EAClC,QAAS,EAAe,SAAS,EAEjC,KAAM,EAAG,IAAI,CAAC,CAAC,SAAS,EAExB,IAAK,EAAG,IAAI,CAAC,CAAC,SAAS,EAEvB,UAAW,EAAG,OAAO,CAAC,CAAC,MAAM,4CAA6C,4BAA4B,CAAC,CAAC,SAAS,CACnH,CAAC,EACG,EAAe,EAAG,YAAY,CAChC,KAAM,EAAG,OAAO,CAAC,CAAC,IAAI,CAAC,EAEvB,UAAW,EAAG,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CACzC,CAAC,EACG,EAAc,EAAG,OAAO,CAC1B,QAAS,EAAe,SAAS,EACjC,OAAQ,EAAG,MAAM,CAAW,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,aAAa,EAAQ,IAAQ,CAChE,IAAM,EAAuB,IAAI,IACjC,EAAO,SAAS,EAAO,IAAU,CAC/B,IAAM,EAAa,EAAK,IAAI,EAAM,EAAE,EACpC,GAAI,IAAe,IAAK,GAAG,CACzB,EAAK,IAAI,EAAM,GAAI,CAAK,EACxB,MACF,CACA,EAAI,SAAS,CACX,KAAM,SACN,KAAM,CAAC,EAAO,IAAI,EAClB,QAAS,uBAAuB,EAAM,GAAG,oCAAoC,EAAW,EAC1F,CAAC,CACH,CAAC,CACH,CAAC,CACH,CAAC,EACG,EAAa,EAAG,OAAO,CACzB,OAAQ,EAAG,IAAI,EAEf,UAAW,EAAG,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,EACvC,MAAO,EAAG,OAAO,CACf,OAAQ,EAAG,MAAM,EAAG,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,EAC9C,SAAU,EAAG,MAAM,EAAG,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAClD,CAAC,CAAC,CAAC,SAAS,CACd,CAAC,EACuB,EAAG,OAAO,CAChC,QAAS,EAAG,OAAO,EAAG,OAAO,CAAC,CAAC,IAAI,CAAC,EAAG,CAAY,CAAC,CAAC,QAAQ,CAAC,CAAC,EAC/D,OAAQ,EAAG,OAAO,EAAG,OAAO,CAAC,CAAC,IAAI,CAAC,EAAG,CAAW,EACjD,KAAM,EAAW,SAAS,CAC5B,CAAC,EACD,SAAS,EAAqB,EAAM,CAClC,IAAM,EAAY,EAAK,qBAAuB,EAAK,mBACnD,OAAO,KAAK,IAAI,EAAW,EAAK,kBAAkB,CACpD,CAIA,IAAI,EAAkB,EAAG,OAAO,CAE9B,UAAW,EAAG,OAAO,CAAC,CAAC,IAAI,CAAC,EAE5B,UAAW,EAAG,OAAO,CAAC,CAAC,IAAI,CAAC,EAE5B,MAAO,EAAG,OAAO,CAAC,CAAC,IAAI,CAAC,EAExB,cAAe,EAAG,OAAO,CAAC,CAAC,IAAI,CAAC,EAEhC,KAAM,EAAG,OAAO,CACd,OAAQ,EAAG,OAAO,CAAC,CAAC,IAAI,CAAC,EACzB,SAAU,EAAG,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CACxC,CAAC,CAAC,CAAC,SAAS,CACd,CAAC,EACD,SAAS,EAAc,EAAS,WAAW,QAAQ,IAAK,CACtD,IAAM,EAAS,EAAgB,UAAU,CAAM,EAC/C,GAAI,CAAC,EAAO,QACV,MAAU,MACR;EACJ,EAAG,cAAc,EAAO,KAAK,GAC3B,EAEF,OAAO,EAAO,IAChB,CAIA,IAAI,EAAkB,EAAG,KAAK,CAC5B,cACA,eACA,YACA,gBACF,CAAC,EACG,EAAsB,EAAG,OAAO,CAClC,KAAM,EACN,QAAS,EAAG,OAAO,CACrB,CAAC,EACG,EAAiB,EAAG,mBAAmB,QAAS,CAClD,EAAG,OAAO,CACR,QAAS,EAAG,OAAO,CAAC,CAAC,IAAI,CAAC,EAC1B,MAAO,EAAG,QAAQ,IAAI,EAEtB,WAAY,EAAG,IAAI,SAAS,EAE5B,KAAM,EAAG,IAAI,CAAC,CAAC,SAAS,EACxB,OAAQ,EAAG,QAAQ,CACrB,CAAC,EACD,EAAG,OAAO,CACR,QAAS,EAAG,OAAO,CAAC,CAAC,IAAI,CAAC,EAC1B,MAAO,EAAG,QAAQ,OAAO,EAMzB,WAAY,EAAG,IAAI,SAAS,EAC5B,KAAM,EAAG,IAAI,CAAC,CAAC,SAAS,EACxB,MAAO,CACT,CAAC,CACH,CAAC,EACG,EAAuB,EAAG,OAAO,CACnC,KAAM,EAAG,QAAQ,iBAAiB,EAClC,OAAQ,EAAG,KAAK,CAAC,SAAU,SAAU,UAAW,YAAa,SAAS,CAAC,EAEvE,UAAW,EAAG,OAAO,EACrB,KAAM,EAAG,OAAO,CAAC,CAAC,IAAI,CAAC,CACzB,CAAC,EACG,EAAkB,EAAG,OAAO,CAC9B,KAAM,EAAG,QAAQ,YAAY,EAC7B,MAAO,EAAG,MAAM,CAAC,EAAG,OAAO,EAAG,EAAG,OAAO,EAAG,EAAG,QAAQ,CAAC,CAAC,CAC1D,CAAC,ECjKD,SAAgB,EAAW,CAAE,SAA4B,CACvD,IAAM,EAAW,IAAI,KAAK,CAAK,EACzB,EAAY,EAAS,mBAAmB,CAAC,EAAG,CAAE,KAAM,UAAW,OAAQ,SAAU,CAAC,EAClF,EAAM,KAAK,IAAI,EAAI,EAAS,QAAQ,EACpC,EAAQ,EAAM,IACd,EAAU,EAAU,CAAG,EAC7B,OACE,EAAC,IAAD,CAAG,UAAW,8BAA8B,EAAQ,0BAA4B,KAAhF,SAAA,CACE,EAAC,OAAD,CAAM,cAAY,OAAO,SAAA,GAAO,CAAA,EAAC,UAAQ,EAAU,MAAI,CACtD,GAEP,CAEA,SAAS,EAAU,EAAsB,CACvC,GAAI,EAAe,IAAQ,MAAO,WAClC,IAAM,EAAU,KAAK,MAAM,EAAe,GAAM,EAGhD,OAFI,EAAU,GAAW,GAAG,EAAQ,OAE7B,GADO,KAAK,MAAM,EAAU,EACzB,EAAM,IAAI,EAAU,GAAG,MACnC,CCfA,SAAgB,EAAY,EAAyC,CACnE,IAAM,EAAW,EAAM,SAClB,KAEL,MAAO,CACL,iBAAkB,GAAG,EAAS,EAAI,EAAE,UAAU,EAAS,IACvD,cAAe,GAAG,EAAS,EAAI,EAAE,UAAU,EAAS,GACtD,CACF,CCPA,SAAgB,EAAe,CAAE,QAAO,QAAsD,CAC5F,GAAI,CAAC,EACH,OACE,EAAC,UAAD,CAAS,UAAU,QAAQ,MAAO,EAAY,CAAK,EAAG,YAAU,OAAhE,SAAA,CACE,EAAC,KAAD,CAAI,UAAU,eAAgB,SAAA,EAAM,EAAO,CAAA,EAC3C,EAAC,IAAD,CAAG,UAAU,cAAc,SAAA,UAAW,CAAA,CAC/B,IAEb,GAAI,EAAK,QAAU,QACjB,OACE,EAAC,UAAD,CAAS,UAAU,qBAAqB,MAAO,EAAY,CAAK,EAAhE,SAAA,CACE,EAAC,KAAD,CAAI,UAAU,eAAgB,SAAA,EAAM,EAAO,CAAA,EAC3C,EAAC,IAAD,CAAG,UAAU,gBAAgB,SAAA,kBAAmB,CAAA,EAChD,EAAC,IAAD,CAAG,UAAU,cAAe,SAAA,EAAK,MAAM,OAAW,CAAA,EAClD,EAAC,EAAD,CAAY,MAAO,EAAK,UAAa,CAAA,CAC9B,IAEb,IAAM,EAAS,EAAgB,UAAU,EAAK,MAAM,EACpD,GAAI,CAAC,EAAO,QACV,OACE,EAAC,UAAD,CAAS,UAAU,qBAAqB,MAAO,EAAY,CAAK,EAAhE,SAAA,CACE,EAAC,KAAD,CAAI,UAAU,eAAgB,SAAA,EAAM,EAAO,CAAA,EAC3C,EAAC,IAAD,CAAG,UAAU,gBAAgB,SAAA,iBAAkB,CAAA,EAC/C,EAAC,EAAD,CAAY,MAAO,EAAK,UAAa,CAAA,CAC9B,IAEb,IAAM,EACJ,EAAA,EAAA,CAAA,SAAA,CACE,EAAC,IAAD,CAAG,UAAU,gBAAiB,SAAA,OAAO,EAAO,KAAK,KAAK,CAAK,CAAA,EAC3D,EAAC,EAAD,CAAY,MAAO,EAAK,UAAa,CAAA,CACrC,CAAA,CAAA,EAEJ,OACE,EAAC,UAAD,CAAS,UAAU,QAAQ,MAAO,EAAY,CAAK,EAAnD,SAAA,CACE,EAAC,KAAD,CAAI,UAAU,eAAgB,SAAA,EAAM,EAAO,CAAA,EAC1C,EAAK,KACJ,EAAC,IAAD,CAAG,UAAU,cAAc,KAAM,EAAK,KACnC,SAAA,CACA,CAAA,EAEH,CAEK,GAEb,CC7CA,SAAgB,EAAiB,CAC/B,QACA,OACA,OAAO,IAKN,CACD,OACE,EAAC,UAAD,CAAS,UAAW,EAAO,oBAAsB,QAAjD,SAAA,CACE,EAAC,KAAD,CAAI,UAAU,eAAgB,SAAA,CAAU,CAAA,EACxC,EAAC,IAAD,CAAG,UAAU,cAAe,SAAA,CAAQ,CAAA,CAC7B,GAEb,CCVA,SAAgB,EAAc,CAAE,QAAO,QAAsD,CAC3F,GAAI,CAAC,EACH,OACE,EAAC,UAAD,CAAS,UAAU,QAAQ,MAAO,EAAY,CAAK,EAAG,YAAU,OAAhE,SAAA,CACE,EAAC,KAAD,CAAI,UAAU,eAAgB,SAAA,EAAM,EAAO,CAAA,EAC3C,EAAC,IAAD,CAAG,UAAU,cAAc,SAAA,UAAW,CAAA,CAC/B,IAEb,GAAI,EAAK,QAAU,QACjB,OACE,EAAC,UAAD,CAAS,UAAU,qBAAqB,MAAO,EAAY,CAAK,EAAhE,SAAA,CACE,EAAC,KAAD,CAAI,UAAU,eAAgB,SAAA,EAAM,EAAO,CAAA,EAC3C,EAAC,IAAD,CAAG,UAAU,gBAAgB,SAAA,kBAAmB,CAAA,EAChD,EAAC,IAAD,CAAG,UAAU,cAAe,SAAA,EAAK,MAAM,OAAW,CAAA,EAClD,EAAC,EAAD,CAAY,MAAO,EAAK,UAAa,CAAA,CAC9B,IAIb,IAAM,EAAS,EAAqB,UAAU,EAAK,MAAM,EACzD,GAAI,CAAC,EAAO,QACV,OACE,EAAC,UAAD,CAAS,UAAU,qBAAqB,MAAO,EAAY,CAAK,EAAhE,SAAA,CACE,EAAC,KAAD,CAAI,UAAU,eAAgB,SAAA,EAAM,EAAO,CAAA,EAC3C,EAAC,IAAD,CAAG,UAAU,gBAAgB,SAAA,kBAAmB,CAAA,EAChD,EAAC,EAAD,CAAY,MAAO,EAAK,UAAa,CAAA,CAC9B,IAEb,IAAM,EAAe,EAAmB,EAAO,KAAK,MAAM,EACpD,EACJ,EAAA,EAAA,CAAA,SAAA,CACE,EAAC,IAAD,CAAG,UAAW,gCAAgC,EAAO,KAAK,SAA1D,SAAA,CACG,EAAa,MAAM,IAAE,EAAa,KAClC,IACH,EAAC,IAAD,CAAG,UAAU,cAAb,SAAA,CACG,EAAO,KAAK,KAAK,MAAI,EAAO,KAAK,SACjC,IACH,EAAC,EAAD,CAAY,MAAO,EAAK,UAAa,CAAA,CACrC,CAAA,CAAA,EAEJ,OACE,EAAC,UAAD,CAAS,UAAU,QAAQ,MAAO,EAAY,CAAK,EAAnD,SAAA,CACE,EAAC,KAAD,CAAI,UAAU,eAAgB,SAAA,EAAM,EAAO,CAAA,EAC1C,EAAK,KACJ,EAAC,IAAD,CAAG,UAAU,cAAc,KAAM,EAAK,KACnC,SAAA,CACA,CAAA,EAEH,CAEK,GAEb,CAEA,SAAS,EAAmB,EAAmE,CAC7F,OAAQ,EAAR,CACE,IAAK,SACH,MAAO,CAAE,MAAO,IAAK,MAAO,QAAS,EACvC,IAAK,SACH,MAAO,CAAE,MAAO,IAAK,MAAO,QAAS,EACvC,IAAK,UACH,MAAO,CAAE,MAAO,IAAK,MAAO,SAAU,EACxC,IAAK,YACH,MAAO,CAAE,MAAO,IAAK,MAAO,WAAY,EAC1C,IAAK,UACH,MAAO,CAAE,MAAO,IAAK,MAAO,SAAU,CAC1C,CACF,CAEA,SAAgB,EAAc,EAAsC,CAClE,IAAM,EAAS,EAAe,UAAU,CAAK,EAC7C,OAAO,EAAO,QAAU,EAAO,KAAO,IAAA,EACxC,CC7DA,SAAgB,EAAI,CAAE,OAA2B,CAC/C,GAAM,CAAC,EAAO,GAAY,EAAgB,EACpC,CAAC,EAAiB,GAAsB,EAAiB,EACzD,CAAC,EAAS,GAAc,EAA+C,CAAC,CAAC,EA0E/E,OAxEA,MAAgB,CACd,IAAI,EAAY,GAsBhB,OArBA,EAAS,IAAA,EAAS,EAClB,EAAmB,IAAA,EAAS,EAC5B,EAAW,CAAC,CAAC,EACb,MAAM,GAAG,EAAI,UAAU,UAAU,mBAAmB,EAAI,KAAK,GAAG,CAAC,CAC9D,KAAM,GACL,EAAS,GACL,EAAS,KAAK,EACd,QAAQ,OAAW,MAAM,gCAAgC,EAAS,QAAQ,CAAC,CACjF,CAAC,CACA,KAAM,GAAmB,CACnB,IACH,EAAS,CAAc,EACvB,EAAmB,EAAI,KAAK,EAEhC,CAAC,CAAC,CACD,UAAY,CACN,IACH,EAAS,CAAE,OAAQ,CAAC,CAAE,CAAC,EACvB,EAAmB,EAAI,KAAK,EAEhC,CAAC,MACU,CACX,EAAY,EACd,CACF,EAAG,CAAC,EAAI,MAAO,EAAI,SAAS,CAAC,EAE7B,MAAgB,CACd,GAAI,CAAC,GAAS,IAAoB,EAAI,MAAO,OAC7C,IAAI,EAAY,GACV,EAAmB,CAAC,EAE1B,IAAK,IAAM,KAAS,EAAM,OAAQ,CAChC,GAAI,EAAM,OAAS,mBAAqB,EAAM,OAAS,aAAc,SAErE,IAAI,EAAW,GACT,EAAgB,EAAqB,CACzC,mBAAoB,EAAc,EAAM,SAAW,KAAK,GAAK,IAC7D,oBAAqB,EAAM,QACtB,EAAc,EAAM,OAAO,GAAK,IAAA,GACjC,IAAA,GACJ,mBAAoB,CACtB,CAAC,EAEK,MAAgB,CAChB,GAAa,IACjB,EAAW,GACX,MACE,GAAG,EAAI,UAAU,SAAS,mBAAmB,EAAI,KAAK,EAAE,GAAG,mBAAmB,EAAM,EAAE,GACxF,CAAC,CACE,KAAM,GAAc,EAAS,SAAW,IAAM,IAAA,GAAY,EAAS,KAAK,CAAE,CAAC,CAC3E,KAAM,GAAmB,CACxB,IAAM,EAAW,EAAc,CAAK,EAChC,CAAC,GAAa,GAChB,EAAY,IAAa,CAAE,GAAG,GAAU,EAAM,IAAK,CAAS,EAAE,CAClE,CAAC,CAAC,CACD,UAAY,IAAA,EAAS,CAAC,CACtB,YAAc,CACb,EAAW,EACb,CAAC,EACL,EAEA,EAAQ,EACR,EAAO,KAAK,OAAO,YAAY,EAAS,CAAa,CAAC,CACxD,CACA,UAAa,CACX,EAAY,GACZ,IAAK,IAAM,KAAS,EAAQ,OAAO,cAAc,CAAK,CACxD,CACF,EAAG,CAAC,EAAO,EAAI,MAAO,EAAI,UAAW,CAAe,CAAC,EAGnD,EAAC,MAAD,CAAK,UAAU,QAAf,SAAA,CACE,EAAC,SAAD,CAAQ,UAAU,gBAAlB,SAAA,CACE,EAAC,KAAD,CAAI,UAAU,eAAgB,SAAA,EAAI,KAAU,CAAA,EAC5C,EAAC,KAAD,CAAI,UAAU,cAAd,SAAA,CACE,EAAC,MAAD,CAAA,SAAA,CACE,EAAC,KAAD,CAAA,SAAI,QAAU,CAAA,EACd,EAAC,KAAD,CAAA,SAAK,EAAI,aAAkB,CAAA,CACxB,CAAA,CAAA,EACL,EAAC,MAAD,CAAA,SAAA,CACE,EAAC,KAAD,CAAA,SAAI,QAAU,CAAA,EACd,EAAC,KAAD,CAAA,SAAK,EAAI,SAAc,CAAA,CACpB,CAAA,CAAA,CACH,CACE,CAAA,CAAA,IAER,EAAC,OAAD,CAAM,UAAU,cAAhB,SAAA,CACG,CAAC,GAAS,EAAC,EAAD,CAAkB,MAAM,QAAQ,KAAK,yBAAyB,KAAA,EAAM,CAAA,EAC9E,GAAO,OAAO,IAAK,GAClB,EAAM,OAAS,kBACb,EAAC,EAAD,CAAqC,QAAO,KAAM,EAAQ,EAAM,GAAM,EAAlD,EAAM,EAA4C,EACpE,EAAM,OAAS,aACjB,EAAC,EAAD,CAAsC,QAAO,KAAM,EAAQ,EAAM,GAAM,EAAlD,EAAM,EAA4C,EAEvE,EAAC,EAAD,CAAiC,MAAO,EAAM,KAAM,KAAK,gBAAgB,KAAA,EAAM,EAAxD,EAAM,EAAkD,CAEnF,CACI,IAEN,EAAC,SAAD,CAAQ,UAAU,gBAAgB,SAAA,0DAE1B,CAAA,CACL,GAET,CCnIA,SAAgB,EAAY,CAAE,WAAgC,CAC5D,OACE,EAAC,MAAD,CAAK,UAAU,eAAe,KAAK,QAAnC,SAAA,CACE,EAAC,KAAD,CAAA,SAAI,4BAA8B,CAAA,EAClC,EAAC,IAAD,CAAA,SAAG,uEAAwE,CAAA,EAC3E,EAAC,MAAD,CAAA,SAAM,CAAa,CAAA,CAChB,GAET,CCDA,IAAM,EAAY,SAAS,eAAe,MAAM,EAChD,GAAI,CAAC,EACH,MAAU,MAAM,2EAA2E,EAG7F,IAAM,EAAO,EAAW,CAAS,EAEjC,GAAI,CACF,EAAK,OACH,EAAC,EAAD,CAAA,SACE,EAAC,EAAD,CAAK,IAAK,EAAc,CAAI,CAAA,CAClB,CAAA,CACd,CACF,OAAS,EAAO,CAGd,EAAK,OAAO,EAAC,EAAD,CAAa,QAAS,aAAiB,MAAQ,EAAM,QAAU,OAAO,CAAK,CAAI,CAAA,CAAC,CAC9F"}
@@ -0,0 +1,20 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <!--
5
+ No configuration lives in this file. The server injects a window.env block as the first
6
+ element of <head> at request time; this template is a build artifact and stays free of
7
+ environment values so one build can serve any environment.
8
+ -->
9
+ <meta charset="UTF-8" />
10
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
11
+ <title>Trust Dashboard</title>
12
+
13
+ <script type="importmap">{"imports":{"react":"https://esm.sh/react@19.2.8","react/":"https://esm.sh/react@19.2.8/","react-dom":"https://esm.sh/react-dom@19.2.8","react-dom/":"https://esm.sh/react-dom@19.2.8/","scheduler":"https://esm.sh/scheduler@0.27.0","scheduler/":"https://esm.sh/scheduler@0.27.0/","zod":"https://esm.sh/zod@4.4.3","zod/":"https://esm.sh/zod@4.4.3/"}}</script>
14
+ <script type="module" crossorigin src="/__ASSET_PATH__/assets/index-BddJ8Qxp.js"></script>
15
+ <link rel="stylesheet" crossorigin href="/__ASSET_PATH__/assets/index-3-dS42M8.css">
16
+ </head>
17
+ <body>
18
+ <div id="root"></div>
19
+ </body>
20
+ </html>
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,238 @@
1
+ #!/usr/bin/env node
2
+
3
+ // packages/aws/src/cli.ts
4
+ import { join as join3 } from "node:path";
5
+
6
+ // packages/aws/src/index.ts
7
+ import { execFile } from "node:child_process";
8
+ import { cp, mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
9
+ import { join as join2, resolve as resolve2 } from "node:path";
10
+ import { fileURLToPath } from "node:url";
11
+ import { promisify } from "node:util";
12
+
13
+ // packages/aws/src/release.ts
14
+ import { createHash } from "node:crypto";
15
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
16
+ import { join, resolve } from "node:path";
17
+ import { parse, stringify } from "yaml";
18
+ import { z as z2 } from "zod";
19
+
20
+ // packages/aws/src/internal-board.ts
21
+ import { z } from "zod";
22
+ var durationSchema = z.string().regex(/^\d+(?:ms|s|m|h)$/).refine((value) => Number.parseInt(value, 10) > 0).brand();
23
+ var positionSchema = z.object({
24
+ x: z.number().int().min(0),
25
+ y: z.number().int().min(0),
26
+ w: z.number().int().min(1).max(12),
27
+ h: z.number().int().min(1)
28
+ });
29
+ var panelSchema = z.looseObject({
30
+ id: z.string().min(1),
31
+ type: z.string().min(1),
32
+ source: z.string().min(1).optional(),
33
+ position: positionSchema.optional(),
34
+ refresh: durationSchema.optional(),
35
+ link: z.url().optional(),
36
+ url: z.url().optional(),
37
+ json_path: z.string().regex(/^\$(?:\.[A-Za-z_][A-Za-z0-9_]*|\[\d+\])*$/).optional()
38
+ });
39
+ var sourceSchema = z.looseObject({
40
+ type: z.string().min(1),
41
+ token_env: z.string().min(1).optional()
42
+ });
43
+ var boardSchema = z.object({
44
+ refresh: durationSchema.optional(),
45
+ panels: z.array(panelSchema).min(1).superRefine((panels, ctx) => {
46
+ const seen = /* @__PURE__ */ new Map();
47
+ panels.forEach((panel, index) => {
48
+ const firstIndex = seen.get(panel.id);
49
+ if (firstIndex === void 0) seen.set(panel.id, index);
50
+ else
51
+ ctx.addIssue({
52
+ code: "custom",
53
+ path: [index, "id"],
54
+ message: `duplicate panel id "${panel.id}" (already used by panel at index ${firstIndex})`
55
+ });
56
+ });
57
+ })
58
+ });
59
+ var boardConfigSchema = z.object({
60
+ sources: z.record(z.string().min(1), sourceSchema).default({}),
61
+ boards: z.record(z.string().min(1), boardSchema),
62
+ auth: z.object({
63
+ issuer: z.url(),
64
+ client_id: z.string().min(1).optional(),
65
+ allow: z.object({
66
+ groups: z.array(z.string().min(1)).optional(),
67
+ subjects: z.array(z.string().min(1)).optional()
68
+ }).optional()
69
+ }).optional()
70
+ });
71
+
72
+ // packages/aws/src/release.ts
73
+ var CORE_RUNTIME_VERSION = "1.0.0";
74
+ var CANONICAL_ASSET_DOMAIN = "https://assets.zegreatrob.com";
75
+ function clientAssetUrl(version, domain = CANONICAL_ASSET_DOMAIN) {
76
+ if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(version))
77
+ throw new Error(`Invalid dashboard version: ${version}`);
78
+ return `${domain.replace(/\/+$/, "")}/dashboard/${version}`;
79
+ }
80
+ async function validateBoardConfig(path) {
81
+ const source = await readFile(resolve(path), "utf8");
82
+ const result = boardConfigSchema.safeParse(parse(source));
83
+ if (!result.success)
84
+ throw new Error(`Invalid board configuration:
85
+ ${z2.prettifyError(result.error)}`);
86
+ const yaml = stringify(result.data, { sortMapEntries: true });
87
+ return { yaml, sha256: sha256(yaml) };
88
+ }
89
+ async function assembleRelease(input) {
90
+ const board = await validateBoardConfig(input.boardConfigPath);
91
+ const outputDir = resolve(input.outputDir);
92
+ await mkdir(outputDir, { recursive: true });
93
+ await writeFile(join(outputDir, "board.yaml"), board.yaml);
94
+ const metadata = {
95
+ dashboardVersion: input.version,
96
+ clientAssetUrl: clientAssetUrl(input.version, input.assetDomain),
97
+ serverRuntimeVersion: CORE_RUNTIME_VERSION,
98
+ supportedProviders: input.providers ?? ["aws-lambda"],
99
+ artifactChecksums: { "board.yaml": board.sha256 },
100
+ runtimeCompatibility: { node: ">=22" }
101
+ };
102
+ const metadataText = `${JSON.stringify(metadata, null, 2)}
103
+ `;
104
+ await writeFile(join(outputDir, "release.json"), metadataText);
105
+ const files = { "board.yaml": board.sha256, "release.json": sha256(metadataText) };
106
+ await writeFile(
107
+ join(outputDir, "SHA256SUMS"),
108
+ `${Object.entries(files).map(([name, digest]) => `${digest} ${name}`).join("\n")}
109
+ `
110
+ );
111
+ return { metadata, files };
112
+ }
113
+ function sha256(value) {
114
+ return createHash("sha256").update(value).digest("hex");
115
+ }
116
+
117
+ // packages/aws/src/index.ts
118
+ var run = promisify(execFile);
119
+ async function packageLambda(options) {
120
+ const outputDir = resolve2(options.outputDir);
121
+ await mkdir2(outputDir, { recursive: true });
122
+ const runtimeDir = join2(outputDir, "lambda");
123
+ await mkdir2(runtimeDir, { recursive: true });
124
+ const lambdaSource = fileURLToPath(new URL("../dist/lambda.mjs", import.meta.url));
125
+ await cp(lambdaSource, join2(runtimeDir, "index.mjs"));
126
+ const release = await assembleRelease({
127
+ boardConfigPath: options.boardConfigPath,
128
+ outputDir: runtimeDir,
129
+ version: options.version,
130
+ providers: ["aws-lambda"],
131
+ assetDomain: options.assetDomain
132
+ });
133
+ const runtimeMetadata = {
134
+ ...release.metadata,
135
+ artifactChecksums: {
136
+ ...release.metadata.artifactChecksums,
137
+ "index.mjs": sha256(await readFile2(join2(runtimeDir, "index.mjs")))
138
+ }
139
+ };
140
+ const clientSource = fileURLToPath(new URL("../client", import.meta.url));
141
+ await cp(clientSource, join2(outputDir, "assets"), { recursive: true });
142
+ await writeFile2(join2(runtimeDir, "release.json"), `${JSON.stringify(runtimeMetadata, null, 2)}
143
+ `);
144
+ const sums = Object.entries(runtimeMetadata.artifactChecksums).map(([name, digest]) => `${digest} ${name}`).join("\n");
145
+ await writeFile2(join2(runtimeDir, "SHA256SUMS"), `${sums}
146
+ `);
147
+ await run("zip", ["-X", "-q", "-r", join2(outputDir, "lambda.zip"), "."], { cwd: runtimeDir });
148
+ return runtimeMetadata;
149
+ }
150
+ async function deployLambda(options) {
151
+ const artifactDir = resolve2(options.artifactDir);
152
+ const assetsDir = resolve2(options.assetsDir);
153
+ await readFile2(join2(artifactDir, "lambda.zip"));
154
+ await readFile2(join2(assetsDir, "index.html"));
155
+ if (options.dryRun) return;
156
+ const assetPath = `${options.assetsBaseUrl.replace(/\/+$/, "")}/dashboard/${options.version}`;
157
+ await run("aws", [
158
+ "s3",
159
+ "sync",
160
+ assetsDir,
161
+ `s3://${options.assetsBucket}/dashboard/${options.version}/`,
162
+ "--exclude",
163
+ "index.html",
164
+ "--cache-control",
165
+ "public, max-age=31536000, immutable"
166
+ ]);
167
+ await run("aws", [
168
+ "s3",
169
+ "cp",
170
+ join2(assetsDir, "index.html"),
171
+ `s3://${options.assetsBucket}/dashboard/${options.version}/index.html`,
172
+ "--cache-control",
173
+ "public, max-age=60"
174
+ ]);
175
+ await run("aws", [
176
+ "lambda",
177
+ "update-function-code",
178
+ "--function-name",
179
+ options.functionName,
180
+ "--zip-file",
181
+ `fileb://${join2(artifactDir, "lambda.zip")}`,
182
+ "--no-cli-pager"
183
+ ]);
184
+ await run("aws", ["lambda", "wait", "function-updated", "--function-name", options.functionName]);
185
+ await run("aws", [
186
+ "lambda",
187
+ "update-function-configuration",
188
+ "--function-name",
189
+ options.functionName,
190
+ "--environment",
191
+ `Variables={ASSET_PATH=${assetPath},BOARD_CONFIG_URL=./board.yaml,HOST=0.0.0.0}`,
192
+ "--no-cli-pager"
193
+ ]);
194
+ await run("aws", ["lambda", "wait", "function-updated", "--function-name", options.functionName]);
195
+ }
196
+
197
+ // packages/aws/src/cli.ts
198
+ var args = process.argv.slice(2);
199
+ var option = (name, fallback) => {
200
+ const i = args.indexOf(name);
201
+ return i >= 0 ? args[i + 1] : fallback;
202
+ };
203
+ try {
204
+ if (args[0] === "deploy") {
205
+ const required = (name) => {
206
+ const value = option(name);
207
+ if (!value) throw new Error(`${name} is required`);
208
+ return value;
209
+ };
210
+ const artifactDir = required("--artifact-dir");
211
+ await deployLambda({
212
+ artifactDir,
213
+ assetsDir: option("--assets-dir", join3(artifactDir, "assets")) ?? join3(artifactDir, "assets"),
214
+ assetsBucket: required("--assets-bucket"),
215
+ assetsBaseUrl: required("--assets-base-url"),
216
+ functionName: required("--function-name"),
217
+ version: required("--version"),
218
+ dryRun: args.includes("--dry-run")
219
+ });
220
+ console.log(JSON.stringify({ deployed: true, version: required("--version") }));
221
+ } else if (args[0] !== "package")
222
+ throw new Error("Usage: ze-great-dashboard-aws package|deploy [options]");
223
+ else {
224
+ const boardConfig = option("--board-config");
225
+ const version = option("--version", process.env.DASHBOARD_VERSION);
226
+ if (!boardConfig || !version) throw new Error("--board-config and --version are required");
227
+ const metadata = await packageLambda({
228
+ boardConfigPath: boardConfig,
229
+ outputDir: option("--output", "aws-release") ?? "aws-release",
230
+ version,
231
+ assetDomain: option("--asset-domain")
232
+ });
233
+ console.log(JSON.stringify(metadata));
234
+ }
235
+ } catch (error) {
236
+ console.error(error instanceof Error ? error.message : error);
237
+ process.exitCode = 1;
238
+ }
@@ -0,0 +1,29 @@
1
+ export type ReleaseMetadata = {
2
+ dashboardVersion: string;
3
+ clientAssetUrl: string;
4
+ serverRuntimeVersion: string;
5
+ supportedProviders: string[];
6
+ artifactChecksums: Record<string, string>;
7
+ runtimeCompatibility: {
8
+ node: string;
9
+ };
10
+ };
11
+ export type LambdaPackageOptions = {
12
+ boardConfigPath: string;
13
+ outputDir: string;
14
+ version: string;
15
+ assetDomain?: string;
16
+ };
17
+ export declare function packageLambda(options: LambdaPackageOptions): Promise<ReleaseMetadata>;
18
+ export type DeployLambdaOptions = {
19
+ artifactDir: string;
20
+ assetsDir: string;
21
+ assetsBucket: string;
22
+ assetsBaseUrl: string;
23
+ functionName: string;
24
+ version: string;
25
+ dryRun?: boolean;
26
+ };
27
+ /** Performs the AWS-specific half of a release using explicit deployment outputs. */
28
+ export declare function deployLambda(options: DeployLambdaOptions): Promise<void>;
29
+ export declare function cloudFormationTemplate(): Promise<string>;
package/dist/index.js ADDED
@@ -0,0 +1,198 @@
1
+ // packages/aws/src/index.ts
2
+ import { execFile } from "node:child_process";
3
+ import { cp, mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
4
+ import { join as join2, resolve as resolve2 } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ import { promisify } from "node:util";
7
+
8
+ // packages/aws/src/release.ts
9
+ import { createHash } from "node:crypto";
10
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
11
+ import { join, resolve } from "node:path";
12
+ import { parse, stringify } from "yaml";
13
+ import { z as z2 } from "zod";
14
+
15
+ // packages/aws/src/internal-board.ts
16
+ import { z } from "zod";
17
+ var durationSchema = z.string().regex(/^\d+(?:ms|s|m|h)$/).refine((value) => Number.parseInt(value, 10) > 0).brand();
18
+ var positionSchema = z.object({
19
+ x: z.number().int().min(0),
20
+ y: z.number().int().min(0),
21
+ w: z.number().int().min(1).max(12),
22
+ h: z.number().int().min(1)
23
+ });
24
+ var panelSchema = z.looseObject({
25
+ id: z.string().min(1),
26
+ type: z.string().min(1),
27
+ source: z.string().min(1).optional(),
28
+ position: positionSchema.optional(),
29
+ refresh: durationSchema.optional(),
30
+ link: z.url().optional(),
31
+ url: z.url().optional(),
32
+ json_path: z.string().regex(/^\$(?:\.[A-Za-z_][A-Za-z0-9_]*|\[\d+\])*$/).optional()
33
+ });
34
+ var sourceSchema = z.looseObject({
35
+ type: z.string().min(1),
36
+ token_env: z.string().min(1).optional()
37
+ });
38
+ var boardSchema = z.object({
39
+ refresh: durationSchema.optional(),
40
+ panels: z.array(panelSchema).min(1).superRefine((panels, ctx) => {
41
+ const seen = /* @__PURE__ */ new Map();
42
+ panels.forEach((panel, index) => {
43
+ const firstIndex = seen.get(panel.id);
44
+ if (firstIndex === void 0) seen.set(panel.id, index);
45
+ else
46
+ ctx.addIssue({
47
+ code: "custom",
48
+ path: [index, "id"],
49
+ message: `duplicate panel id "${panel.id}" (already used by panel at index ${firstIndex})`
50
+ });
51
+ });
52
+ })
53
+ });
54
+ var boardConfigSchema = z.object({
55
+ sources: z.record(z.string().min(1), sourceSchema).default({}),
56
+ boards: z.record(z.string().min(1), boardSchema),
57
+ auth: z.object({
58
+ issuer: z.url(),
59
+ client_id: z.string().min(1).optional(),
60
+ allow: z.object({
61
+ groups: z.array(z.string().min(1)).optional(),
62
+ subjects: z.array(z.string().min(1)).optional()
63
+ }).optional()
64
+ }).optional()
65
+ });
66
+
67
+ // packages/aws/src/release.ts
68
+ var CORE_RUNTIME_VERSION = "1.0.0";
69
+ var CANONICAL_ASSET_DOMAIN = "https://assets.zegreatrob.com";
70
+ function clientAssetUrl(version, domain = CANONICAL_ASSET_DOMAIN) {
71
+ if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(version))
72
+ throw new Error(`Invalid dashboard version: ${version}`);
73
+ return `${domain.replace(/\/+$/, "")}/dashboard/${version}`;
74
+ }
75
+ async function validateBoardConfig(path) {
76
+ const source = await readFile(resolve(path), "utf8");
77
+ const result = boardConfigSchema.safeParse(parse(source));
78
+ if (!result.success)
79
+ throw new Error(`Invalid board configuration:
80
+ ${z2.prettifyError(result.error)}`);
81
+ const yaml = stringify(result.data, { sortMapEntries: true });
82
+ return { yaml, sha256: sha256(yaml) };
83
+ }
84
+ async function assembleRelease(input) {
85
+ const board = await validateBoardConfig(input.boardConfigPath);
86
+ const outputDir = resolve(input.outputDir);
87
+ await mkdir(outputDir, { recursive: true });
88
+ await writeFile(join(outputDir, "board.yaml"), board.yaml);
89
+ const metadata = {
90
+ dashboardVersion: input.version,
91
+ clientAssetUrl: clientAssetUrl(input.version, input.assetDomain),
92
+ serverRuntimeVersion: CORE_RUNTIME_VERSION,
93
+ supportedProviders: input.providers ?? ["aws-lambda"],
94
+ artifactChecksums: { "board.yaml": board.sha256 },
95
+ runtimeCompatibility: { node: ">=22" }
96
+ };
97
+ const metadataText = `${JSON.stringify(metadata, null, 2)}
98
+ `;
99
+ await writeFile(join(outputDir, "release.json"), metadataText);
100
+ const files = { "board.yaml": board.sha256, "release.json": sha256(metadataText) };
101
+ await writeFile(
102
+ join(outputDir, "SHA256SUMS"),
103
+ `${Object.entries(files).map(([name, digest]) => `${digest} ${name}`).join("\n")}
104
+ `
105
+ );
106
+ return { metadata, files };
107
+ }
108
+ function sha256(value) {
109
+ return createHash("sha256").update(value).digest("hex");
110
+ }
111
+
112
+ // packages/aws/src/index.ts
113
+ var run = promisify(execFile);
114
+ async function packageLambda(options) {
115
+ const outputDir = resolve2(options.outputDir);
116
+ await mkdir2(outputDir, { recursive: true });
117
+ const runtimeDir = join2(outputDir, "lambda");
118
+ await mkdir2(runtimeDir, { recursive: true });
119
+ const lambdaSource = fileURLToPath(new URL("../dist/lambda.mjs", import.meta.url));
120
+ await cp(lambdaSource, join2(runtimeDir, "index.mjs"));
121
+ const release = await assembleRelease({
122
+ boardConfigPath: options.boardConfigPath,
123
+ outputDir: runtimeDir,
124
+ version: options.version,
125
+ providers: ["aws-lambda"],
126
+ assetDomain: options.assetDomain
127
+ });
128
+ const runtimeMetadata = {
129
+ ...release.metadata,
130
+ artifactChecksums: {
131
+ ...release.metadata.artifactChecksums,
132
+ "index.mjs": sha256(await readFile2(join2(runtimeDir, "index.mjs")))
133
+ }
134
+ };
135
+ const clientSource = fileURLToPath(new URL("../client", import.meta.url));
136
+ await cp(clientSource, join2(outputDir, "assets"), { recursive: true });
137
+ await writeFile2(join2(runtimeDir, "release.json"), `${JSON.stringify(runtimeMetadata, null, 2)}
138
+ `);
139
+ const sums = Object.entries(runtimeMetadata.artifactChecksums).map(([name, digest]) => `${digest} ${name}`).join("\n");
140
+ await writeFile2(join2(runtimeDir, "SHA256SUMS"), `${sums}
141
+ `);
142
+ await run("zip", ["-X", "-q", "-r", join2(outputDir, "lambda.zip"), "."], { cwd: runtimeDir });
143
+ return runtimeMetadata;
144
+ }
145
+ async function deployLambda(options) {
146
+ const artifactDir = resolve2(options.artifactDir);
147
+ const assetsDir = resolve2(options.assetsDir);
148
+ await readFile2(join2(artifactDir, "lambda.zip"));
149
+ await readFile2(join2(assetsDir, "index.html"));
150
+ if (options.dryRun) return;
151
+ const assetPath = `${options.assetsBaseUrl.replace(/\/+$/, "")}/dashboard/${options.version}`;
152
+ await run("aws", [
153
+ "s3",
154
+ "sync",
155
+ assetsDir,
156
+ `s3://${options.assetsBucket}/dashboard/${options.version}/`,
157
+ "--exclude",
158
+ "index.html",
159
+ "--cache-control",
160
+ "public, max-age=31536000, immutable"
161
+ ]);
162
+ await run("aws", [
163
+ "s3",
164
+ "cp",
165
+ join2(assetsDir, "index.html"),
166
+ `s3://${options.assetsBucket}/dashboard/${options.version}/index.html`,
167
+ "--cache-control",
168
+ "public, max-age=60"
169
+ ]);
170
+ await run("aws", [
171
+ "lambda",
172
+ "update-function-code",
173
+ "--function-name",
174
+ options.functionName,
175
+ "--zip-file",
176
+ `fileb://${join2(artifactDir, "lambda.zip")}`,
177
+ "--no-cli-pager"
178
+ ]);
179
+ await run("aws", ["lambda", "wait", "function-updated", "--function-name", options.functionName]);
180
+ await run("aws", [
181
+ "lambda",
182
+ "update-function-configuration",
183
+ "--function-name",
184
+ options.functionName,
185
+ "--environment",
186
+ `Variables={ASSET_PATH=${assetPath},BOARD_CONFIG_URL=./board.yaml,HOST=0.0.0.0}`,
187
+ "--no-cli-pager"
188
+ ]);
189
+ await run("aws", ["lambda", "wait", "function-updated", "--function-name", options.functionName]);
190
+ }
191
+ async function cloudFormationTemplate() {
192
+ return readFile2(fileURLToPath(new URL("../template.yml", import.meta.url)), "utf8");
193
+ }
194
+ export {
195
+ cloudFormationTemplate,
196
+ deployLambda,
197
+ packageLambda
198
+ };
@@ -0,0 +1,33 @@
1
+ import { z } from 'zod';
2
+ export declare const boardConfigSchema: z.ZodObject<{
3
+ sources: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
4
+ type: z.ZodString;
5
+ token_env: z.ZodOptional<z.ZodString>;
6
+ }, z.core.$loose>>>;
7
+ boards: z.ZodRecord<z.ZodString, z.ZodObject<{
8
+ refresh: z.ZodOptional<z.core.$ZodBranded<z.ZodString, "Duration", "out">>;
9
+ panels: z.ZodArray<z.ZodObject<{
10
+ id: z.ZodString;
11
+ type: z.ZodString;
12
+ source: z.ZodOptional<z.ZodString>;
13
+ position: z.ZodOptional<z.ZodObject<{
14
+ x: z.ZodNumber;
15
+ y: z.ZodNumber;
16
+ w: z.ZodNumber;
17
+ h: z.ZodNumber;
18
+ }, z.core.$strip>>;
19
+ refresh: z.ZodOptional<z.core.$ZodBranded<z.ZodString, "Duration", "out">>;
20
+ link: z.ZodOptional<z.ZodURL>;
21
+ url: z.ZodOptional<z.ZodURL>;
22
+ json_path: z.ZodOptional<z.ZodString>;
23
+ }, z.core.$loose>>;
24
+ }, z.core.$strip>>;
25
+ auth: z.ZodOptional<z.ZodObject<{
26
+ issuer: z.ZodURL;
27
+ client_id: z.ZodOptional<z.ZodString>;
28
+ allow: z.ZodOptional<z.ZodObject<{
29
+ groups: z.ZodOptional<z.ZodArray<z.ZodString>>;
30
+ subjects: z.ZodOptional<z.ZodArray<z.ZodString>>;
31
+ }, z.core.$strip>>;
32
+ }, z.core.$strip>>;
33
+ }, z.core.$strip>;