@continuous-excellence/ze-great-dashboard-aws 0.1.24 → 0.1.26
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/README.md +4 -3
- package/client/.vite/manifest.json +2 -2
- package/client/assets/index-DOvxvZo2.js +3 -0
- package/client/assets/index-DOvxvZo2.js.map +1 -0
- package/client/assets/index-r7iEInfY.css +1 -0
- package/client/index.html +2 -2
- package/dist/index.d.ts +1 -1
- package/dist/lambda.mjs +38 -5
- package/package.json +1 -1
- package/client/assets/index-3-dS42M8.css +0 -1
- package/client/assets/index-BddJ8Qxp.js +0 -3
- package/client/assets/index-BddJ8Qxp.js.map +0 -1
package/README.md
CHANGED
|
@@ -115,9 +115,10 @@ Server environment variables are documented in `.env.example`. The one that matt
|
|
|
115
115
|
|
|
116
116
|
## Deploying
|
|
117
117
|
|
|
118
|
-
Push to `main`. CI checks, versions with
|
|
119
|
-
|
|
120
|
-
reference
|
|
118
|
+
Push to `main`. CI builds and checks a release candidate, versions it with
|
|
119
|
+
[Tagger](https://github.com/robertfmurdock/tagger), publishes its versioned client assets, and deploys
|
|
120
|
+
the exact npm tarball to the repository-owned consumer reference. Only then does it publish that
|
|
121
|
+
same tarball and tag the release.
|
|
121
122
|
|
|
122
123
|
Infrastructure is a CloudFormation stack under `infra/`. On `main`, CI deploys the stack before it
|
|
123
124
|
publishes assets and updates the Lambda. The public client origin is
|
|
@@ -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`,`no-runs`,`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),branch:e.string().min(1).optional(),durationMs:e.number().int().nonnegative().optional()}),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}),s(`p`,{className:`panel__status`,children:[`⚠ `,t.error.kind===`no-runs`?`No workflow runs`:`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=M(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,n.data.branch&&s(`span`,{className:`panel__branch`,title:`Branch: ${n.data.branch}`,children:[o(`span`,{"aria-hidden":`true`,children:` · ⎇ `}),o(`span`,{className:`screen-reader-only`,children:`Branch: `}),n.data.branch]})]}),n.data.status!==`running`&&n.data.durationMs!==void 0&&s(`p`,{className:`panel__hint`,children:[`Took `,j(n.data.durationMs)]}),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){let t=Math.floor(e/1e3),n=Math.floor(t/3600),r=Math.floor(t%3600/60),i=t%60;return n>0?`${n}h ${r}m`:r>0?`${r}m ${i}s`:`${i}s`}function M(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 N(e){let t=S.safeParse(e);return t.success?t.data:void 0}function P({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=N(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 F({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 I=document.getElementById(`root`);if(!I)throw Error(`No #root element in the document — the template is not the one we expect.`);var L=i(I);try{L.render(o(t,{children:o(P,{env:y()})}))}catch(e){L.render(o(F,{message:e instanceof Error?e.message:String(e)}))}
|
|
3
|
+
//# sourceMappingURL=index-DOvxvZo2.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index-DOvxvZo2.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 \"no-runs\",\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 /** The branch represented by this status, when the upstream supplies or filters by one. */\n branch: z4.string().min(1).optional(),\n /** Elapsed execution time, supplied only after the upstream run has completed. */\n durationMs: z4.number().int().nonnegative().optional()\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\">\n ⚠ {data.error.kind === 'no-runs' ? 'No workflow runs' : 'Unable to read'}\n </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 {signal.data.branch && (\n <span className=\"panel__branch\" title={`Branch: ${signal.data.branch}`}>\n <span aria-hidden=\"true\"> · ⎇ </span>\n <span className=\"screen-reader-only\">Branch: </span>\n {signal.data.branch}\n </span>\n )}\n </p>\n {signal.data.status !== 'running' && signal.data.durationMs !== undefined && (\n <p className=\"panel__hint\">Took {formatDuration(signal.data.durationMs)}</p>\n )}\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 formatDuration(durationMs: number) {\n const totalSeconds = Math.floor(durationMs / 1_000)\n const hours = Math.floor(totalSeconds / 3_600)\n const minutes = Math.floor((totalSeconds % 3_600) / 60)\n const seconds = totalSeconds % 60\n\n if (hours > 0) return `${hours}h ${minutes}m`\n if (minutes > 0) return `${minutes}m ${seconds}s`\n return `${seconds}s`\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":"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"}
|
|
@@ -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}.screen-reader-only{clip:rect(0, 0, 0, 0);white-space:nowrap;border:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}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__branch{white-space:nowrap;font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.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}}
|
package/client/index.html
CHANGED
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
<title>Trust Dashboard</title>
|
|
12
12
|
|
|
13
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-
|
|
15
|
-
<link rel="stylesheet" crossorigin href="/__ASSET_PATH__/assets/index-
|
|
14
|
+
<script type="module" crossorigin src="/__ASSET_PATH__/assets/index-DOvxvZo2.js"></script>
|
|
15
|
+
<link rel="stylesheet" crossorigin href="/__ASSET_PATH__/assets/index-r7iEInfY.css">
|
|
16
16
|
</head>
|
|
17
17
|
<body>
|
|
18
18
|
<div id="root"></div>
|
package/dist/index.d.ts
CHANGED
|
@@ -33,7 +33,7 @@ export type PublishClientAssetsOptions = {
|
|
|
33
33
|
assetsBaseUrl: string;
|
|
34
34
|
version: string;
|
|
35
35
|
};
|
|
36
|
-
/** Publishes the
|
|
36
|
+
/** Publishes the versioned client half of a provider-managed release. */
|
|
37
37
|
export declare function publishClientAssets(options: PublishClientAssetsOptions): Promise<string>;
|
|
38
38
|
/** Performs the AWS-specific half of a release using explicit deployment outputs. */
|
|
39
39
|
export declare function deployLambda(options: DeployLambdaOptions): Promise<void>;
|
package/dist/lambda.mjs
CHANGED
|
@@ -24312,6 +24312,8 @@ config(en_default());
|
|
|
24312
24312
|
var githubSourceSchema = external_exports.object({
|
|
24313
24313
|
type: external_exports.literal("github-actions"),
|
|
24314
24314
|
repo: external_exports.string().regex(/^[^/\s]+\/[^/\s]+$/, "must be an owner/repository pair"),
|
|
24315
|
+
/** The branch whose workflow health the dashboard represents. */
|
|
24316
|
+
branch: external_exports.string().min(1).optional(),
|
|
24315
24317
|
token_env: external_exports.string().min(1).optional()
|
|
24316
24318
|
});
|
|
24317
24319
|
var pipelinePanelSchema = external_exports.object({
|
|
@@ -24325,15 +24327,18 @@ var runSchema = external_exports.object({
|
|
|
24325
24327
|
status: external_exports.string(),
|
|
24326
24328
|
conclusion: external_exports.string().nullable(),
|
|
24327
24329
|
name: external_exports.string().min(1),
|
|
24328
|
-
html_url: external_exports.url()
|
|
24330
|
+
html_url: external_exports.url(),
|
|
24331
|
+
run_started_at: external_exports.iso.datetime().nullable().optional(),
|
|
24332
|
+
updated_at: external_exports.iso.datetime().optional()
|
|
24329
24333
|
});
|
|
24330
|
-
var runsSchema = external_exports.object({ workflow_runs: external_exports.array(runSchema)
|
|
24334
|
+
var runsSchema = external_exports.object({ workflow_runs: external_exports.array(runSchema) });
|
|
24331
24335
|
function permittedGithubActionsCalls(panel, source) {
|
|
24332
24336
|
const parsedPanel = pipelinePanelSchema.parse(panel);
|
|
24333
24337
|
const parsedSource = githubSourceSchema.parse(source);
|
|
24334
24338
|
const url2 = new URL(
|
|
24335
24339
|
`https://api.github.com/repos/${parsedSource.repo}/actions/workflows/${encodeURIComponent(parsedPanel.pipeline)}/runs`
|
|
24336
24340
|
);
|
|
24341
|
+
if (parsedSource.branch) url2.searchParams.set("branch", parsedSource.branch);
|
|
24337
24342
|
url2.searchParams.set("per_page", "1");
|
|
24338
24343
|
const headers = new Headers({ accept: "application/vnd.github+json" });
|
|
24339
24344
|
const token = parsedSource.token_env ? process.env[parsedSource.token_env] : void 0;
|
|
@@ -24341,6 +24346,7 @@ function permittedGithubActionsCalls(panel, source) {
|
|
|
24341
24346
|
return [{ url: url2.toString(), headers }];
|
|
24342
24347
|
}
|
|
24343
24348
|
async function fetchGithubActionsPipeline(args) {
|
|
24349
|
+
const parsedSource = githubSourceSchema.parse(args.source);
|
|
24344
24350
|
const call = permittedGithubActionsCalls(args.panel, args.source)[0];
|
|
24345
24351
|
if (!call) throw new Error("GitHub Actions adapter declared no permitted call");
|
|
24346
24352
|
for (const name of ["if-none-match", "if-modified-since"]) {
|
|
@@ -24379,12 +24385,29 @@ async function fetchGithubActionsPipeline(args) {
|
|
|
24379
24385
|
}
|
|
24380
24386
|
try {
|
|
24381
24387
|
const run = runsSchema.parse(await upstream.json()).workflow_runs[0];
|
|
24382
|
-
if (!run)
|
|
24388
|
+
if (!run) {
|
|
24389
|
+
return {
|
|
24390
|
+
response: new Response(
|
|
24391
|
+
JSON.stringify(
|
|
24392
|
+
errorEnvelope(
|
|
24393
|
+
args.panel.id,
|
|
24394
|
+
"no-runs",
|
|
24395
|
+
parsedSource.branch ? `No workflow runs found for branch "${parsedSource.branch}". Check the source's branch setting.` : "No workflow runs found.",
|
|
24396
|
+
sourceLink(args.panel, args.source),
|
|
24397
|
+
observedAt(upstream.headers.get("date"))
|
|
24398
|
+
)
|
|
24399
|
+
),
|
|
24400
|
+
{ status: 200 }
|
|
24401
|
+
)
|
|
24402
|
+
};
|
|
24403
|
+
}
|
|
24383
24404
|
const signal = {
|
|
24384
24405
|
type: "pipeline-status",
|
|
24385
24406
|
status: normalizeStatus(run.status, run.conclusion),
|
|
24386
24407
|
rawStatus: run.conclusion ?? run.status,
|
|
24387
|
-
name: run.name
|
|
24408
|
+
name: run.name,
|
|
24409
|
+
branch: parsedSource.branch,
|
|
24410
|
+
...run.status === "completed" ? completedRunDuration(run) : {}
|
|
24388
24411
|
};
|
|
24389
24412
|
const envelope = {
|
|
24390
24413
|
panelId: args.panel.id,
|
|
@@ -24411,6 +24434,11 @@ async function fetchGithubActionsPipeline(args) {
|
|
|
24411
24434
|
};
|
|
24412
24435
|
}
|
|
24413
24436
|
}
|
|
24437
|
+
function completedRunDuration(run) {
|
|
24438
|
+
if (!run.run_started_at || !run.updated_at) return {};
|
|
24439
|
+
const durationMs = new Date(run.updated_at).valueOf() - new Date(run.run_started_at).valueOf();
|
|
24440
|
+
return Number.isFinite(durationMs) && durationMs >= 0 ? { durationMs } : {};
|
|
24441
|
+
}
|
|
24414
24442
|
function normalizeStatus(status, conclusion) {
|
|
24415
24443
|
if (status !== "completed") return "running";
|
|
24416
24444
|
if (conclusion === "success") return "passed";
|
|
@@ -24826,6 +24854,7 @@ var errorKindSchema = external_exports.enum([
|
|
|
24826
24854
|
"unreachable",
|
|
24827
24855
|
"unauthorized",
|
|
24828
24856
|
"not-found",
|
|
24857
|
+
"no-runs",
|
|
24829
24858
|
"upstream-error"
|
|
24830
24859
|
]);
|
|
24831
24860
|
var envelopeErrorSchema = external_exports.object({
|
|
@@ -24860,7 +24889,11 @@ var pipelineStatusSchema = external_exports.object({
|
|
|
24860
24889
|
status: external_exports.enum(["passed", "failed", "running", "cancelled", "unknown"]),
|
|
24861
24890
|
/** The source's unmodified status/result vocabulary, for an honest display. */
|
|
24862
24891
|
rawStatus: external_exports.string(),
|
|
24863
|
-
name: external_exports.string().min(1)
|
|
24892
|
+
name: external_exports.string().min(1),
|
|
24893
|
+
/** The branch represented by this status, when the upstream supplies or filters by one. */
|
|
24894
|
+
branch: external_exports.string().min(1).optional(),
|
|
24895
|
+
/** Elapsed execution time, supplied only after the upstream run has completed. */
|
|
24896
|
+
durationMs: external_exports.number().int().nonnegative().optional()
|
|
24864
24897
|
});
|
|
24865
24898
|
var httpValueSchema = external_exports.object({
|
|
24866
24899
|
type: external_exports.literal("http-value"),
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
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}}
|
|
@@ -1,3 +0,0 @@
|
|
|
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
|
|
@@ -1 +0,0 @@
|
|
|
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":"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"}
|