@continuous-excellence/ze-great-dashboard-aws 0.3.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +108 -94
- package/client/.vite/manifest.json +2 -2
- package/client/assets/index-BCH1wUwb.js +3 -0
- package/client/assets/index-BCH1wUwb.js.map +1 -0
- package/client/assets/{index-r7iEInfY.css → index-CwWbINCq.css} +1 -1
- package/client/index.html +2 -2
- package/dist/lambda.mjs +4 -1
- package/package.json +1 -1
- package/client/assets/index-DOvxvZo2.js +0 -3
- package/client/assets/index-DOvxvZo2.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,133 +1,147 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Ze Great Dashboard AWS deployment
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
[](LICENSE)
|
|
3
|
+
`@continuous-excellence/ze-great-dashboard-aws` packages a configured Ze Great Dashboard as an AWS
|
|
4
|
+
Lambda deployment. It includes the compatible immutable client, Lambda runtime, CLI, and
|
|
5
|
+
CloudFormation template.
|
|
7
6
|
|
|
8
|
-
|
|
7
|
+
It does not create a public endpoint, choose an authentication policy, create a gateway, provide a
|
|
8
|
+
secret value, or own consumer AWS infrastructure. You provide the protected gateway and complete the
|
|
9
|
+
administrator-owned bootstrap process first.
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
whatever a team actually walks over to check. It stores nothing, computes no trends, and holds no
|
|
12
|
-
opinion about your process. Every panel is a live read of a system that already knows the answer.
|
|
11
|
+
## Prerequisites
|
|
13
12
|
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
You need Node.js 22+, npm, the AWS CLI, `jq`, and one AWS Region for the artifact bucket, Lambda,
|
|
14
|
+
and application stack. You also need a consumer-owned artifact bucket and the bootstrap-created
|
|
15
|
+
restricted roles. Follow the [AWS bootstrap guide](https://github.com/robertfmurdock/ze-great-dashboard/blob/main/docs/aws-bootstrap.md)
|
|
16
|
+
before this deployment procedure.
|
|
16
17
|
|
|
17
|
-
|
|
18
|
-
panels work end to end. Azure DevOps remains to be built.
|
|
18
|
+
## Deploy a dashboard
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
Use an exact package version in a deployment project:
|
|
21
21
|
|
|
22
22
|
```sh
|
|
23
|
-
npm install
|
|
24
|
-
npm run dev
|
|
23
|
+
npm install --save-exact @continuous-excellence/ze-great-dashboard-aws@1.2.3
|
|
25
24
|
```
|
|
26
25
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
26
|
+
Create `board.yaml`. This small configuration has one GitHub Actions panel and one HTTP value panel;
|
|
27
|
+
see the [board configuration guide](https://github.com/robertfmurdock/ze-great-dashboard/blob/main/docs/board-configuration.md)
|
|
28
|
+
for the full schema.
|
|
29
|
+
|
|
30
|
+
```yaml
|
|
31
|
+
sources:
|
|
32
|
+
github:
|
|
33
|
+
type: github-actions
|
|
34
|
+
repo: your-org/your-repo
|
|
35
|
+
branch: main
|
|
36
|
+
token_env: GITHUB_TOKEN
|
|
37
|
+
|
|
38
|
+
boards:
|
|
39
|
+
operations:
|
|
40
|
+
refresh: 60s
|
|
41
|
+
panels:
|
|
42
|
+
- id: build
|
|
43
|
+
type: pipeline-status
|
|
44
|
+
source: github
|
|
45
|
+
pipeline: main.yml
|
|
46
|
+
position: { x: 0, y: 0, w: 8, h: 6 }
|
|
47
|
+
- id: version
|
|
48
|
+
type: http-value
|
|
49
|
+
url: https://status.example.com/version.json
|
|
50
|
+
json_path: $.version
|
|
51
|
+
position: { x: 8, y: 0, w: 4, h: 6 }
|
|
38
52
|
```
|
|
39
53
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
Before you commit anything:
|
|
54
|
+
Generate the application parameters from the non-secret `dashboard-bootstrap.json` created during
|
|
55
|
+
bootstrap:
|
|
43
56
|
|
|
44
57
|
```sh
|
|
45
|
-
npm
|
|
58
|
+
npm exec -- ze-great-dashboard-aws parameters \
|
|
59
|
+
--bootstrap-config dashboard-bootstrap.json \
|
|
60
|
+
--output aws-dashboard-parameters.json
|
|
46
61
|
```
|
|
47
62
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
`core.hooksPath`, no extra setup.
|
|
51
|
-
|
|
52
|
-
Set `PLAYWRIGHT_DOCKER=1` to run the browser portion through the matching official Playwright
|
|
53
|
-
container. The root test script starts the Compose service, connects the existing test to it, and
|
|
54
|
-
tears it down afterward:
|
|
63
|
+
After the parameter file exists, run the read-only doctor. It checks local tooling, your AWS
|
|
64
|
+
identity, parameters, the artifact bucket and Region, and the matching hosted client:
|
|
55
65
|
|
|
56
66
|
```sh
|
|
57
|
-
|
|
67
|
+
npm exec -- ze-great-dashboard-aws doctor \
|
|
68
|
+
--parameters aws-dashboard-parameters.json \
|
|
69
|
+
--region us-east-1
|
|
58
70
|
```
|
|
59
71
|
|
|
60
|
-
|
|
72
|
+
Package the release. This validates the board and writes `lambda.zip`, `release.json`, and
|
|
73
|
+
`template.yml` to the output directory:
|
|
61
74
|
|
|
62
75
|
```sh
|
|
63
|
-
|
|
64
|
-
|
|
76
|
+
npm exec -- ze-great-dashboard-aws package \
|
|
77
|
+
--board-config board.yaml \
|
|
78
|
+
--output aws-dashboard-release
|
|
65
79
|
```
|
|
66
80
|
|
|
67
|
-
|
|
68
|
-
build involved. This is how you reproduce production behavior, or confirm that a specific published
|
|
69
|
-
version renders.
|
|
70
|
-
|
|
71
|
-
If you want to point it at something served from your own machine instead of the CDN, use the host's
|
|
72
|
-
LAN address, not `host.docker.internal` — on Docker Desktop that name can resolve to IPv6, and Node's
|
|
73
|
-
`fetch` will fail against an IPv4-only local server while `wget` in the same container succeeds. The
|
|
74
|
-
symptom is a refused start naming a URL you can reach perfectly well from the host.
|
|
81
|
+
Upload the generated ZIP to the bucket and key specified by the parameter file and release metadata:
|
|
75
82
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
a versioned CDN path, containing **zero environment values**. The server fetches that version's
|
|
80
|
-
`index.html` at request time, injects a `window.env` block as the first element of `<head>`, and
|
|
81
|
-
serves it uncached.
|
|
83
|
+
```sh
|
|
84
|
+
export AWS_REGION=us-east-1
|
|
85
|
+
export STACK_NAME=my-ze-great-dashboard
|
|
82
86
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
87
|
+
ARTIFACT_BUCKET="$(jq -er \
|
|
88
|
+
'.[] | select(.ParameterKey == "LambdaArtifactBucket") | .ParameterValue' \
|
|
89
|
+
aws-dashboard-parameters.json)"
|
|
90
|
+
ARTIFACT_KEY="$(jq -er '.artifactKey' aws-dashboard-release/release.json)"
|
|
86
91
|
|
|
92
|
+
aws s3 cp aws-dashboard-release/lambda.zip \
|
|
93
|
+
"s3://${ARTIFACT_BUCKET}/${ARTIFACT_KEY}" \
|
|
94
|
+
--region "$AWS_REGION"
|
|
87
95
|
```
|
|
88
|
-
browser ──► server (Lambda) ──fetch index.html──► CDN /dashboard/1.0.7/
|
|
89
|
-
│ (immutable, cached hard)
|
|
90
|
-
└── injects window.env, cache-control: no-store
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
Structurally that means:
|
|
94
96
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
credentials, stores nothing.
|
|
98
|
-
- **`packages/shared`** — the board config schema and signal envelope. The one definition both sides
|
|
99
|
-
agree on, so changing it is a single coordinated change the type checker enforces.
|
|
97
|
+
Deploy the generated CloudFormation template using the restricted execution role captured from the
|
|
98
|
+
core bootstrap stack:
|
|
100
99
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
100
|
+
```sh
|
|
101
|
+
aws cloudformation deploy \
|
|
102
|
+
--stack-name "$STACK_NAME" \
|
|
103
|
+
--template-file aws-dashboard-release/template.yml \
|
|
104
|
+
--role-arn "$CLOUDFORMATION_EXECUTION_ROLE_ARN" \
|
|
105
|
+
--region "$AWS_REGION" \
|
|
106
|
+
--capabilities CAPABILITY_NAMED_IAM \
|
|
107
|
+
--parameter-overrides file://aws-dashboard-parameters.json \
|
|
108
|
+
--no-fail-on-empty-changeset
|
|
109
|
+
```
|
|
105
110
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
111
|
+
The stack outputs `ServerFunctionArn`. Integrate that ARN with your protected API Gateway, ALB, or
|
|
112
|
+
other consumer-owned gateway, then verify the protected `/health` endpoint. The template deliberately
|
|
113
|
+
does not grant public Lambda invoke permission.
|
|
109
114
|
|
|
110
|
-
|
|
111
|
-
and the value lives in the environment. `.env` is gitignored; `.env.example` names every variable and
|
|
112
|
-
holds no values.
|
|
115
|
+
## Operate safely
|
|
113
116
|
|
|
114
|
-
|
|
117
|
+
To upgrade, install a newer exact package version and repeat package, upload, and deploy. A change to
|
|
118
|
+
`board.yaml` follows the same path; it does not require a package change.
|
|
115
119
|
|
|
116
|
-
|
|
120
|
+
Tokens belong in runtime secret handling, never in board YAML, `aws-dashboard-parameters.json`, or
|
|
121
|
+
the generated ZIP. See [runtime secrets](https://github.com/robertfmurdock/ze-great-dashboard/blob/main/docs/aws-setup.md#runtime-secrets)
|
|
122
|
+
for the supported secret reference and integration boundary.
|
|
117
123
|
|
|
118
|
-
|
|
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.
|
|
124
|
+
## Commands and boundaries
|
|
122
125
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
`
|
|
126
|
-
|
|
127
|
-
|
|
126
|
+
| Command | Use |
|
|
127
|
+
| --- | --- |
|
|
128
|
+
| `doctor` | Read-only preflight for an existing parameter file. |
|
|
129
|
+
| `parameters` | Generate or update application CloudFormation parameters. |
|
|
130
|
+
| `package` | Validate board YAML and build a deployable Lambda release. |
|
|
131
|
+
| `bootstrap` | Produce bootstrap templates, parameters, preflight, and guidance; administrators run AWS commands explicitly. |
|
|
132
|
+
| `publish-assets` | Provider-only: publish immutable client assets. Normal consumers use the hosted client. |
|
|
133
|
+
| `deploy` | Provider-only: automation helper for a provider-managed asset and Lambda deployment. |
|
|
128
134
|
|
|
129
|
-
|
|
135
|
+
## Further reading and support boundary
|
|
130
136
|
|
|
131
|
-
|
|
137
|
+
- [Board configuration](https://github.com/robertfmurdock/ze-great-dashboard/blob/main/docs/board-configuration.md)
|
|
138
|
+
- [AWS bootstrap](https://github.com/robertfmurdock/ze-great-dashboard/blob/main/docs/aws-bootstrap.md)
|
|
139
|
+
- [Consumer deployment and CI setup](https://github.com/robertfmurdock/ze-great-dashboard/blob/main/docs/aws-setup.md)
|
|
140
|
+
- [GitHub Actions deployment workflow YAML example](https://github.com/robertfmurdock/ze-great-dashboard/blob/main/docs/aws-setup.md#github-actions)
|
|
141
|
+
- [Runtime secret integration](https://github.com/robertfmurdock/ze-great-dashboard/blob/main/docs/aws-setup.md#runtime-secrets)
|
|
142
|
+
- [Repository source](https://github.com/robertfmurdock/ze-great-dashboard)
|
|
143
|
+
- [Issue tracker](https://github.com/robertfmurdock/ze-great-dashboard/issues)
|
|
144
|
+
- [MIT license](https://github.com/robertfmurdock/ze-great-dashboard/blob/main/LICENSE)
|
|
132
145
|
|
|
133
|
-
|
|
146
|
+
The package owns the Lambda deployment artifact. Gateway design, authentication, secret-value
|
|
147
|
+
provisioning, and consumer AWS account administration remain your responsibility.
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import{z as e}from"zod";import{StrictMode as t,useEffect as n,useRef as r,useState as i}from"react";import{createRoot as a}from"react-dom/client";import{Fragment as o,jsx as s,jsxs as c}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 l=/^(\d+)(ms|s|m|h)$/,u={ms:1,s:1e3,m:6e4,h:36e5};function d(e){let t=l.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*u[r]}var f=e.string().refine(e=>d(e)!==null,{message:`must be a positive duration like "30s", "5m", or "1h"`}).brand(),p=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)}),m=e.looseObject({id:e.string().min(1),type:e.string().min(1),source:e.string().min(1).optional(),position:p.optional(),refresh:f.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()}),h=e.looseObject({type:e.string().min(1),token_env:e.string().min(1).optional()}),g=e.object({refresh:f.optional(),panels:e.array(m).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})`})})})}),_=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),h).default({}),boards:e.record(e.string().min(1),g),auth:_.optional()});function v(e){let t=e.panelOverrideMillis??e.boardDefaultMillis;return Math.max(t,e.adapterFloorMillis)}var y=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 b(t=globalThis.window?.env){let n=y.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 x=e.enum([`unreachable`,`unauthorized`,`not-found`,`no-runs`,`upstream-error`]),S=e.object({kind:x,message:e.string()}),C=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:S})]),w=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(),sourceUpdatedAt:e.iso.datetime().optional()}),T=e.object({type:e.literal(`http-value`),value:e.union([e.string(),e.number(),e.boolean()])});function E({log:e}){let[t,r]=i(!1),[a,o]=i(e.count());return n(()=>e.subscribe(()=>o(e.count())),[e]),c(`section`,{className:`diagnostics`,children:[c(`button`,{className:`diagnostics__toggle`,type:`button`,"aria-expanded":t,onClick:()=>r(!t),children:[`Diagnostics (`,a,`)`]}),t&&c(`div`,{className:`diagnostics__area`,children:[c(`p`,{children:[a,` retained browser-local events. They are never uploaded.`]}),c(`div`,{className:`diagnostics__actions`,children:[s(`button`,{type:`button`,onClick:()=>{let t=new Blob([JSON.stringify(e.export(),null,2)],{type:`application/json`}),n=URL.createObjectURL(t),r=document.createElement(`a`);r.href=n,r.download=`dashboard-diagnostics-${new Date().toISOString().replaceAll(`:`,`-`)}.json`,r.click(),URL.revokeObjectURL(n)},children:`Download`}),s(`button`,{type:`button`,onClick:()=>{window.confirm(`Clear this browser’s retained dashboard diagnostics?`)&&e.clear()},children:`Clear`})]})]})]})}var D=`ze-great-dashboard.diagnostics.v1`,O=6048e5,k=[`session-start`,`board-fetch-start`,`board-fetch-response`,`board-fetch-parse-failure`,`board-fetch-failure`,`panel-fetch-start`,`panel-fetch-response`,`panel-fetch-parse-failure`,`panel-fetch-failure`,`panel-rendered`],A=class{env;storage;now;events=[];listeners=new Set;sessionId=crypto.randomUUID?.()??`${Date.now()}-${Math.random()}`;constructor(e,t=P(),n=()=>new Date){this.env=e,this.storage=t,this.now=n,this.events=this.read(),this.record({kind:`session-start`})}record(e){this.events=M([...this.events,{...e,at:this.now().toISOString(),sessionId:this.sessionId,board:this.env.board}],this.now()),this.persist(),this.notify()}count(){return this.events.length}clear(){this.events=[];try{this.storage?.removeItem(D)}catch{}this.notify()}subscribe(e){return this.listeners.add(e),()=>{this.listeners.delete(e)}}export(){return{schemaVersion:1,exportedAt:this.now().toISOString(),client:{version:this.env.clientVersion,assetPath:this.env.assetPath,board:this.env.board,sessionId:this.sessionId},events:this.events}}read(){try{let e=this.storage?.getItem(D);if(!e)return[];let t=JSON.parse(e);return t.schemaVersion!==1||!Array.isArray(t.events)||!t.events.every(N)?(this.storage?.removeItem(D),[]):M(t.events,this.now())}catch{try{this.storage?.removeItem(D)}catch{}return[]}}persist(){try{this.storage?.setItem(D,JSON.stringify({schemaVersion:1,events:this.events}))}catch{}}notify(){for(let e of this.listeners)e()}};function j(e){let t=Object.fromEntries([[`cacheControl`,`cache-control`],[`etag`,`etag`],[`lastModified`,`last-modified`],[`date`,`date`],[`age`,`age`]].flatMap(([t,n])=>{let r=e.get(n);return r?[[t,r]]:[]}));return Object.keys(t).length?t:void 0}function M(e,t){let n=t.valueOf()-O;return e.filter(e=>Number.isFinite(new Date(e.at).valueOf())&&new Date(e.at).valueOf()>=n).slice(-2e3)}function N(e){if(!e||typeof e!=`object`)return!1;let t=e;return typeof t.at==`string`&&Number.isFinite(new Date(t.at).valueOf())&&typeof t.sessionId==`string`&&typeof t.board==`string`&&k.includes(t.kind)}function P(){try{return window.localStorage}catch{return}}function F({value:e,label:t=`As of`}){let n=new Date(e),r=n.toLocaleTimeString([],{hour:`2-digit`,minute:`2-digit`}),i=Date.now()-n.getTime(),a=i>3e5,o=I(i);return c(`p`,{className:`panel__hint panel__observed${a?` panel__observed--stale`:``}`,children:[s(`span`,{"aria-hidden":`true`,children:`◷`}),` `,t,` `,r,` · `,o]})}function I(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 L(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 R({panel:e,data:t}){if(!t)return c(`section`,{className:`panel`,style:L(e),"aria-busy":`true`,children:[s(`h2`,{className:`panel__label`,children:e.id}),s(`p`,{className:`panel__hint`,children:`Loading…`})]});if(t.state===`error`)return c(`section`,{className:`panel panel--error`,style:L(e),children:[s(`h2`,{className:`panel__label`,children:e.id}),s(`p`,{className:`panel__status`,children:`⚠ Unable to read`}),s(`p`,{className:`panel__hint`,children:t.error.message}),s(F,{value:t.observedAt})]});let n=T.safeParse(t.signal);if(!n.success)return c(`section`,{className:`panel panel--error`,style:L(e),children:[s(`h2`,{className:`panel__label`,children:e.id}),s(`p`,{className:`panel__status`,children:`⚠ Invalid value`}),s(F,{value:t.observedAt})]});let r=c(o,{children:[s(`p`,{className:`panel__status`,children:String(n.data.value)}),s(F,{value:t.observedAt})]});return c(`section`,{className:`panel`,style:L(e),children:[s(`h2`,{className:`panel__label`,children:e.id}),t.link?s(`a`,{className:`panel__link`,href:t.link,children:r}):r]})}function z({label:e,hint:t,wide:n=!1}){return c(`section`,{className:n?`panel panel--wide`:`panel`,children:[s(`h2`,{className:`panel__label`,children:e}),s(`p`,{className:`panel__hint`,children:t})]})}function B({panel:e,data:t}){if(!t)return c(`section`,{className:`panel`,style:L(e),"aria-busy":`true`,children:[s(`h2`,{className:`panel__label`,children:e.id}),s(`p`,{className:`panel__hint`,children:`Loading…`})]});if(t.state===`error`)return c(`section`,{className:`panel panel--error`,style:L(e),children:[s(`h2`,{className:`panel__label`,children:e.id}),c(`p`,{className:`panel__status`,children:[`⚠ `,t.error.kind===`no-runs`?`No workflow runs`:`Unable to read`]}),s(`p`,{className:`panel__hint`,children:t.error.message}),s(F,{value:t.observedAt})]});let n=w.safeParse(t.signal);if(!n.success)return c(`section`,{className:`panel panel--error`,style:L(e),children:[s(`h2`,{className:`panel__label`,children:e.id}),s(`p`,{className:`panel__status`,children:`⚠ Invalid signal`}),s(F,{value:t.observedAt})]});let r=H(n.data.status),i=c(o,{children:[c(`p`,{className:`panel__status panel__status--${n.data.status}`,children:[r.glyph,` `,r.label]}),c(`p`,{className:`panel__hint`,children:[n.data.name,` · `,n.data.rawStatus,n.data.branch&&c(`span`,{className:`panel__branch`,title:`Branch: ${n.data.branch}`,children:[s(`span`,{"aria-hidden":`true`,children:` · ⎇ `}),s(`span`,{className:`screen-reader-only`,children:`Branch: `}),n.data.branch]})]}),n.data.status!==`running`&&n.data.durationMs!==void 0&&c(`p`,{className:`panel__hint`,children:[`Took `,V(n.data.durationMs)]}),n.data.sourceUpdatedAt&&s(F,{value:n.data.sourceUpdatedAt,label:`Run updated`}),s(F,{value:t.observedAt})]});return c(`section`,{className:`panel`,style:L(e),children:[s(`h2`,{className:`panel__label`,children:e.id}),t.link?s(`a`,{className:`panel__link`,href:t.link,children:i}):i]})}function V(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 H(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 U(e){let t=C.safeParse(e);return t.success?t.data:void 0}function W({env:e}){let[t,a]=i(),[o,l]=i(),[u,f]=i({}),p=r(null),m=r({});p.current||=new A(e);let h=p.current;return n(()=>{let t=!1;a(void 0),l(void 0),f({}),m.current={};let n=`${e.proxyPath}/boards/${encodeURIComponent(e.board)}`;return h.record({kind:`board-fetch-start`,path:n}),fetch(n).then(async e=>{if(h.record({kind:`board-fetch-response`,path:n,status:e.status,cache:j(e.headers)}),!e.ok)throw Error(`Board configuration returned ${e.status}`);try{return await e.json()}catch(e){throw h.record({kind:`board-fetch-parse-failure`,path:n,message:G(e)}),new K(e)}}).then(r=>{if(!t){a(r),l(e.board);let t=Array.isArray(r.panels)?r.panels:[];h.record({kind:`board-fetch-response`,path:n,boardSummary:{panelCount:t.length,panelIds:t.flatMap(e=>typeof e.id==`string`?[e.id]:[])}})}}).catch(r=>{r instanceof K||h.record({kind:`board-fetch-failure`,path:n,message:G(r)}),t||(a({panels:[]}),l(e.board))}),()=>{t=!0}},[h,e.board,e.proxyPath]),n(()=>{if(!t||o!==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=v({boardDefaultMillis:d(t.refresh??`60s`)??6e4,panelOverrideMillis:i.refresh?d(i.refresh)??void 0:void 0,adapterFloorMillis:0}),s=()=>{if(n||a)return;a=!0;let t=`${e.proxyPath}/panel/${encodeURIComponent(e.board)}/${encodeURIComponent(i.id)}`;h.record({kind:`panel-fetch-start`,panelId:i.id,path:t}),fetch(t).then(async e=>{if(h.record({kind:`panel-fetch-response`,panelId:i.id,path:t,status:e.status,cache:j(e.headers)}),e.status!==304)try{return await e.json()}catch(e){throw h.record({kind:`panel-fetch-parse-failure`,panelId:i.id,path:t,message:G(e)}),new K(e)}}).then(e=>{let r=U(e);if(e!==void 0&&!r&&h.record({kind:`panel-fetch-parse-failure`,panelId:i.id,path:t,message:`Response was not a valid signal envelope.`}),!n&&r){h.record({kind:`panel-fetch-response`,panelId:i.id,path:t,envelope:r});let e=m.current[i.id];J(e,r)&&h.record({kind:`panel-rendered`,panelId:i.id,path:t,rendered:q(r)}),m.current={...m.current,[i.id]:r},f(m.current)}}).catch(e=>{e instanceof K||h.record({kind:`panel-fetch-failure`,panelId:i.id,path:t,message:G(e)})}).finally(()=>{a=!1})};s(),r.push(window.setInterval(s,o))}return()=>{n=!0;for(let e of r)window.clearInterval(e)}},[t,h,e.board,e.proxyPath,o]),c(`div`,{className:`board`,children:[c(`header`,{className:`board__header`,children:[s(`h1`,{className:`board__title`,children:e.board}),c(`dl`,{className:`board__meta`,children:[c(`div`,{children:[s(`dt`,{children:`client`}),s(`dd`,{children:e.clientVersion})]}),c(`div`,{children:[s(`dt`,{children:`assets`}),s(`dd`,{children:e.assetPath})]})]})]}),c(`main`,{className:`board__grid`,children:[!t&&s(z,{label:`board`,hint:`Loading configuration…`,wide:!0}),t?.panels.map(e=>e.type===`pipeline-status`?s(B,{panel:e,data:u[e.id]},e.id):e.type===`http-value`?s(R,{panel:e,data:u[e.id]},e.id):s(z,{label:e.type,hint:`Not wired yet`,wide:!0},e.id))]}),c(`footer`,{className:`board__footer`,children:[s(`span`,{children:`Signals are read live from their configured authorities.`}),s(E,{log:h})]})]})}function G(e){return e instanceof Error?e.message:String(e)}var K=class extends Error{constructor(e){super(G(e))}};function q(e){if(e.state===`error`)return{state:e.state,link:e.link};let t=w.safeParse(e.signal);return{state:e.state,status:t.success?t.data.status:void 0,link:e.link}}function J(e,t){if(!e)return!0;let n=q(e),r=q(t);return n.state!==r.state||n.status!==r.status||n.link!==r.link}function Y({message:e}){return c(`div`,{className:`config-error`,role:`alert`,children:[s(`h1`,{children:`⚠️ Dashboard misconfigured`}),s(`p`,{children:`The page loaded, but the server did not hand it usable configuration.`}),s(`pre`,{children:e})]})}var X=document.getElementById(`root`);if(!X)throw Error(`No #root element in the document — the template is not the one we expect.`);var Z=a(X);try{Z.render(s(t,{children:s(W,{env:b()})}))}catch(e){Z.render(s(Y,{message:e instanceof Error?e.message:String(e)}))}
|
|
3
|
+
//# sourceMappingURL=index-BCH1wUwb.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index-BCH1wUwb.js","names":[],"sources":["../../../shared/dist/index.js","../../src/Diagnostics.tsx","../../src/diagnostics.ts","../../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 /** When this workflow run was last updated by its source, distinct from our observation time. */\n sourceUpdatedAt: z4.iso.datetime().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","import { useEffect, useState } from 'react'\nimport type { DiagnosticLog } from './diagnostics.ts'\n\nexport function Diagnostics({ log }: { log: DiagnosticLog }) {\n const [open, setOpen] = useState(false)\n const [count, setCount] = useState(log.count())\n useEffect(() => log.subscribe(() => setCount(log.count())), [log])\n const download = () => {\n const blob = new Blob([JSON.stringify(log.export(), null, 2)], { type: 'application/json' })\n const url = URL.createObjectURL(blob)\n const link = document.createElement('a')\n link.href = url\n link.download = `dashboard-diagnostics-${new Date().toISOString().replaceAll(':', '-')}.json`\n link.click()\n URL.revokeObjectURL(url)\n }\n const clear = () => {\n if (!window.confirm('Clear this browser’s retained dashboard diagnostics?')) return\n log.clear()\n }\n\n return (\n <section className=\"diagnostics\">\n <button\n className=\"diagnostics__toggle\"\n type=\"button\"\n aria-expanded={open}\n onClick={() => setOpen(!open)}\n >\n Diagnostics ({count})\n </button>\n {open && (\n <div className=\"diagnostics__area\">\n <p>{count} retained browser-local events. They are never uploaded.</p>\n <div className=\"diagnostics__actions\">\n <button type=\"button\" onClick={download}>\n Download\n </button>\n <button type=\"button\" onClick={clear}>\n Clear\n </button>\n </div>\n </div>\n )}\n </section>\n )\n}\n","import type { ClientEnv, Envelope } from '@ze-great-dashboard/shared'\n\nconst storageKey = 'ze-great-dashboard.diagnostics.v1'\nexport const diagnosticsSchemaVersion = 1\nconst maximumEvents = 2_000\nconst maximumAgeMillis = 7 * 24 * 60 * 60 * 1_000\nconst diagnosticKinds = [\n 'session-start',\n 'board-fetch-start',\n 'board-fetch-response',\n 'board-fetch-parse-failure',\n 'board-fetch-failure',\n 'panel-fetch-start',\n 'panel-fetch-response',\n 'panel-fetch-parse-failure',\n 'panel-fetch-failure',\n 'panel-rendered',\n] as const\n\ntype CacheMetadata = Record<'cacheControl' | 'etag' | 'lastModified' | 'date' | 'age', string>\n\nexport type DiagnosticEvent = {\n at: string\n sessionId: string\n board: string\n kind:\n | 'session-start'\n | 'board-fetch-start'\n | 'board-fetch-response'\n | 'board-fetch-parse-failure'\n | 'board-fetch-failure'\n | 'panel-fetch-start'\n | 'panel-fetch-response'\n | 'panel-fetch-parse-failure'\n | 'panel-fetch-failure'\n | 'panel-rendered'\n panelId?: string\n path?: string\n status?: number\n cache?: CacheMetadata\n envelope?: Envelope\n rendered?: { state: Envelope['state']; status?: string; link: string | null }\n message?: string\n boardSummary?: { panelCount: number; panelIds: string[] }\n}\n\ntype StoredDiagnostics = { schemaVersion: number; events: DiagnosticEvent[] }\n\ntype StorageLike = Pick<Storage, 'getItem' | 'setItem' | 'removeItem'>\n\nexport class DiagnosticLog {\n private events: DiagnosticEvent[] = []\n private readonly listeners = new Set<() => void>()\n private readonly sessionId = crypto.randomUUID?.() ?? `${Date.now()}-${Math.random()}`\n\n constructor(\n private readonly env: ClientEnv,\n private readonly storage: StorageLike | undefined = storageOrUndefined(),\n private readonly now: () => Date = () => new Date(),\n ) {\n this.events = this.read()\n this.record({ kind: 'session-start' })\n }\n\n record(event: Omit<DiagnosticEvent, 'at' | 'sessionId' | 'board'>) {\n this.events = prune(\n [\n ...this.events,\n {\n ...event,\n at: this.now().toISOString(),\n sessionId: this.sessionId,\n board: this.env.board,\n },\n ],\n this.now(),\n )\n this.persist()\n this.notify()\n }\n\n count() {\n return this.events.length\n }\n\n clear() {\n this.events = []\n try {\n this.storage?.removeItem(storageKey)\n } catch {\n // Diagnostics must not interrupt the radiator when browser storage is unavailable.\n }\n this.notify()\n }\n\n subscribe(listener: () => void) {\n this.listeners.add(listener)\n return () => {\n this.listeners.delete(listener)\n }\n }\n\n export() {\n return {\n schemaVersion: diagnosticsSchemaVersion,\n exportedAt: this.now().toISOString(),\n client: {\n version: this.env.clientVersion,\n assetPath: this.env.assetPath,\n board: this.env.board,\n sessionId: this.sessionId,\n },\n events: this.events,\n }\n }\n\n private read(): DiagnosticEvent[] {\n try {\n const raw = this.storage?.getItem(storageKey)\n if (!raw) return []\n const parsed = JSON.parse(raw) as Partial<StoredDiagnostics>\n if (parsed.schemaVersion !== diagnosticsSchemaVersion || !Array.isArray(parsed.events)) {\n this.storage?.removeItem(storageKey)\n return []\n }\n if (!parsed.events.every(isDiagnosticEvent)) {\n this.storage?.removeItem(storageKey)\n return []\n }\n return prune(parsed.events, this.now())\n } catch {\n try {\n this.storage?.removeItem(storageKey)\n } catch {\n // Storage can fail on both reads and writes (for example, private browsing policies).\n }\n return []\n }\n }\n\n private persist() {\n try {\n this.storage?.setItem(\n storageKey,\n JSON.stringify({ schemaVersion: diagnosticsSchemaVersion, events: this.events }),\n )\n } catch {\n // Keep the in-memory record for this page, but never make diagnostics a rendering failure.\n }\n }\n\n private notify() {\n for (const listener of this.listeners) listener()\n }\n}\n\nexport function cacheMetadata(headers: Headers): CacheMetadata | undefined {\n const entries = [\n ['cacheControl', 'cache-control'],\n ['etag', 'etag'],\n ['lastModified', 'last-modified'],\n ['date', 'date'],\n ['age', 'age'],\n ] as const\n const metadata = Object.fromEntries(\n entries.flatMap(([key, header]) => {\n const value = headers.get(header)\n return value ? [[key, value]] : []\n }),\n ) as CacheMetadata\n return Object.keys(metadata).length ? metadata : undefined\n}\n\nfunction prune(events: DiagnosticEvent[], now: Date) {\n const cutoff = now.valueOf() - maximumAgeMillis\n return events\n .filter(\n (event) =>\n Number.isFinite(new Date(event.at).valueOf()) && new Date(event.at).valueOf() >= cutoff,\n )\n .slice(-maximumEvents)\n}\n\nfunction isDiagnosticEvent(value: unknown): value is DiagnosticEvent {\n if (!value || typeof value !== 'object') return false\n const event = value as Partial<DiagnosticEvent>\n return (\n typeof event.at === 'string' &&\n Number.isFinite(new Date(event.at).valueOf()) &&\n typeof event.sessionId === 'string' &&\n typeof event.board === 'string' &&\n diagnosticKinds.includes(event.kind as (typeof diagnosticKinds)[number])\n )\n}\n\nfunction storageOrUndefined(): StorageLike | undefined {\n try {\n return window.localStorage\n } catch {\n return undefined\n }\n}\n","export function ObservedAt({ value, label = 'As of' }: { value: string; label?: 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> {label} {formatted} · {ageText}\n </p>\n )\n}\n\nexport function 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 {signal.data.sourceUpdatedAt && (\n <ObservedAt value={signal.data.sourceUpdatedAt} label=\"Run updated\" />\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 pipelineStatusSchema,\n resolveRefreshMillis,\n} from '@ze-great-dashboard/shared'\nimport { useEffect, useRef, useState } from 'react'\nimport { Diagnostics } from './Diagnostics.tsx'\nimport { cacheMetadata, DiagnosticLog } from './diagnostics.ts'\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 const diagnosticsRef = useRef<DiagnosticLog | null>(null)\n const signalsRef = useRef<Record<string, Envelope | undefined>>({})\n if (!diagnosticsRef.current) diagnosticsRef.current = new DiagnosticLog(env)\n const diagnostics = diagnosticsRef.current\n\n useEffect(() => {\n let cancelled = false\n setBoard(undefined)\n setLoadedBoardName(undefined)\n setSignals({})\n signalsRef.current = {}\n const path = `${env.proxyPath}/boards/${encodeURIComponent(env.board)}`\n diagnostics.record({ kind: 'board-fetch-start', path })\n fetch(path)\n .then(async (response) => {\n diagnostics.record({\n kind: 'board-fetch-response',\n path,\n status: response.status,\n cache: cacheMetadata(response.headers),\n })\n if (!response.ok) throw new Error(`Board configuration returned ${response.status}`)\n try {\n return await response.json()\n } catch (error) {\n diagnostics.record({\n kind: 'board-fetch-parse-failure',\n path,\n message: errorMessage(error),\n })\n throw new DiagnosticParseFailure(error)\n }\n })\n .then((value: unknown) => {\n if (!cancelled) {\n setBoard(value as Board)\n setLoadedBoardName(env.board)\n const panels = Array.isArray((value as { panels?: unknown }).panels)\n ? (value as { panels: Array<{ id?: unknown }> }).panels\n : []\n diagnostics.record({\n kind: 'board-fetch-response',\n path,\n boardSummary: {\n panelCount: panels.length,\n panelIds: panels.flatMap((panel) => (typeof panel.id === 'string' ? [panel.id] : [])),\n },\n })\n }\n })\n .catch((error) => {\n if (!(error instanceof DiagnosticParseFailure)) {\n diagnostics.record({ kind: 'board-fetch-failure', path, message: errorMessage(error) })\n }\n if (!cancelled) {\n setBoard({ panels: [] })\n setLoadedBoardName(env.board)\n }\n })\n return () => {\n cancelled = true\n }\n }, [diagnostics, 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 const path = `${env.proxyPath}/panel/${encodeURIComponent(env.board)}/${encodeURIComponent(panel.id)}`\n diagnostics.record({ kind: 'panel-fetch-start', panelId: panel.id, path })\n fetch(path)\n .then(async (response) => {\n diagnostics.record({\n kind: 'panel-fetch-response',\n panelId: panel.id,\n path,\n status: response.status,\n cache: cacheMetadata(response.headers),\n })\n if (response.status === 304) return undefined\n try {\n return await response.json()\n } catch (error) {\n diagnostics.record({\n kind: 'panel-fetch-parse-failure',\n panelId: panel.id,\n path,\n message: errorMessage(error),\n })\n throw new DiagnosticParseFailure(error)\n }\n })\n .then((value: unknown) => {\n const envelope = parseEnvelope(value)\n if (value !== undefined && !envelope) {\n diagnostics.record({\n kind: 'panel-fetch-parse-failure',\n panelId: panel.id,\n path,\n message: 'Response was not a valid signal envelope.',\n })\n }\n if (!cancelled && envelope) {\n diagnostics.record({\n kind: 'panel-fetch-response',\n panelId: panel.id,\n path,\n envelope,\n })\n const previous = signalsRef.current[panel.id]\n if (renderedChanged(previous, envelope)) {\n diagnostics.record({\n kind: 'panel-rendered',\n panelId: panel.id,\n path,\n rendered: renderedState(envelope),\n })\n }\n signalsRef.current = { ...signalsRef.current, [panel.id]: envelope }\n setSignals(signalsRef.current)\n }\n })\n .catch((error) => {\n if (!(error instanceof DiagnosticParseFailure)) {\n diagnostics.record({\n kind: 'panel-fetch-failure',\n panelId: panel.id,\n path,\n message: errorMessage(error),\n })\n }\n })\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, diagnostics, 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 <span>Signals are read live from their configured authorities.</span>\n <Diagnostics log={diagnostics} />\n </footer>\n </div>\n )\n}\n\nfunction errorMessage(error: unknown) {\n return error instanceof Error ? error.message : String(error)\n}\n\nclass DiagnosticParseFailure extends Error {\n constructor(error: unknown) {\n super(errorMessage(error))\n }\n}\n\nfunction renderedState(envelope: Envelope) {\n if (envelope.state === 'error') return { state: envelope.state, link: envelope.link }\n const signal = pipelineStatusSchema.safeParse(envelope.signal)\n return {\n state: envelope.state,\n status: signal.success ? signal.data.status : undefined,\n link: envelope.link,\n }\n}\n\nfunction renderedChanged(previous: Envelope | undefined, next: Envelope) {\n if (!previous) return true\n const before = renderedState(previous)\n const after = renderedState(next)\n return (\n before.state !== after.state || before.status !== after.status || before.link !== after.link\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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"}
|
|
@@ -1 +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}}
|
|
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);flex-wrap:wrap;justify-content:space-between;align-items:center;gap:.5rem;font-size:.75em;display:flex}.diagnostics{position:relative}.diagnostics button{border:1px solid var(--edge);background:var(--surface-raised);color:var(--ink-secondary);font:inherit;border-radius:.25rem;padding:.3em .55em}.diagnostics__area{z-index:1;border:1px solid var(--edge);border-radius:var(--radius);background:var(--surface-raised);width:min(24rem,90vw);padding:.75rem;position:absolute;bottom:calc(100% + .5rem);right:0;box-shadow:0 .5rem 1.5rem #0008}.diagnostics__area p{margin:0 0 .5rem}.diagnostics__actions{gap:.5rem;display:flex}.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-BCH1wUwb.js"></script>
|
|
15
|
+
<link rel="stylesheet" crossorigin href="/__ASSET_PATH__/assets/index-CwWbINCq.css">
|
|
16
16
|
</head>
|
|
17
17
|
<body>
|
|
18
18
|
<div id="root"></div>
|
package/dist/lambda.mjs
CHANGED
|
@@ -24407,6 +24407,7 @@ async function fetchGithubActionsPipeline(args) {
|
|
|
24407
24407
|
rawStatus: run.conclusion ?? run.status,
|
|
24408
24408
|
name: run.name,
|
|
24409
24409
|
branch: parsedSource.branch,
|
|
24410
|
+
...run.updated_at ? { sourceUpdatedAt: run.updated_at } : {},
|
|
24410
24411
|
...run.status === "completed" ? completedRunDuration(run) : {}
|
|
24411
24412
|
};
|
|
24412
24413
|
const envelope = {
|
|
@@ -24893,7 +24894,9 @@ var pipelineStatusSchema = external_exports.object({
|
|
|
24893
24894
|
/** The branch represented by this status, when the upstream supplies or filters by one. */
|
|
24894
24895
|
branch: external_exports.string().min(1).optional(),
|
|
24895
24896
|
/** Elapsed execution time, supplied only after the upstream run has completed. */
|
|
24896
|
-
durationMs: external_exports.number().int().nonnegative().optional()
|
|
24897
|
+
durationMs: external_exports.number().int().nonnegative().optional(),
|
|
24898
|
+
/** When this workflow run was last updated by its source, distinct from our observation time. */
|
|
24899
|
+
sourceUpdatedAt: external_exports.iso.datetime().optional()
|
|
24897
24900
|
});
|
|
24898
24901
|
var httpValueSchema = external_exports.object({
|
|
24899
24902
|
type: external_exports.literal("http-value"),
|
package/package.json
CHANGED
|
@@ -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`,`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
|
|
@@ -1 +0,0 @@
|
|
|
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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"}
|