@continuous-excellence/ze-great-dashboard-aws 0.24.2 → 0.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +52 -2
- package/dist/index.js +52 -2
- package/dist/lambda.mjs +217 -82
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -83,6 +83,26 @@ var positionSchema = z2.union([
|
|
|
83
83
|
]);
|
|
84
84
|
var panelDensities = ["auto", "comfortable", "compact"];
|
|
85
85
|
var panelDensitySchema = z2.enum(panelDensities);
|
|
86
|
+
var httpValueJsonPathSchema = z2.string().regex(/^\$(?:\.[A-Za-z_][A-Za-z0-9_]*|\[\d+\])*$/, "must be a simple JSON path");
|
|
87
|
+
var httpValueFactSchema = z2.object({
|
|
88
|
+
/** Stable address below the panel; labels and ordering may change without repointing it. */
|
|
89
|
+
id: z2.string().min(1),
|
|
90
|
+
label: z2.string().min(1),
|
|
91
|
+
url: z2.url(),
|
|
92
|
+
json_path: httpValueJsonPathSchema.optional()
|
|
93
|
+
});
|
|
94
|
+
var httpValuePanelIdentitySchema = z2.object({
|
|
95
|
+
id: z2.string().min(1),
|
|
96
|
+
type: z2.literal("http-value")
|
|
97
|
+
});
|
|
98
|
+
var httpValueScalarPanelSchema = httpValuePanelIdentitySchema.extend({
|
|
99
|
+
url: z2.url(),
|
|
100
|
+
json_path: httpValueJsonPathSchema.optional(),
|
|
101
|
+
link: z2.url().optional()
|
|
102
|
+
});
|
|
103
|
+
var httpValueGroupedPanelSchema = httpValuePanelIdentitySchema.extend({
|
|
104
|
+
facts: z2.array(httpValueFactSchema).min(1).max(4)
|
|
105
|
+
});
|
|
86
106
|
var visibleRunningAnimations = [
|
|
87
107
|
"radial",
|
|
88
108
|
"runway",
|
|
@@ -125,7 +145,9 @@ var panelSchema = z2.looseObject({
|
|
|
125
145
|
/** Source-agnostic endpoint used by the http-value signal. */
|
|
126
146
|
url: z2.url().optional(),
|
|
127
147
|
/** Small, deliberate JSON path subset: $.version, $.deployment.version, or $.response.docs[0].latestVersion. */
|
|
128
|
-
json_path:
|
|
148
|
+
json_path: httpValueJsonPathSchema.optional(),
|
|
149
|
+
/** Up to four independently fetched source-agnostic readings in one visual panel. */
|
|
150
|
+
facts: httpValueGroupedPanelSchema.shape.facts.optional()
|
|
129
151
|
}).superRefine((panel, ctx) => {
|
|
130
152
|
if ("display" in panel) {
|
|
131
153
|
ctx.addIssue({
|
|
@@ -134,6 +156,32 @@ var panelSchema = z2.looseObject({
|
|
|
134
156
|
message: "display was removed; use density"
|
|
135
157
|
});
|
|
136
158
|
}
|
|
159
|
+
if (panel.type !== "http-value") return;
|
|
160
|
+
if (panel.facts) {
|
|
161
|
+
if (panel.url !== void 0)
|
|
162
|
+
ctx.addIssue({
|
|
163
|
+
code: "custom",
|
|
164
|
+
path: ["url"],
|
|
165
|
+
message: "use facts URLs for grouped panels"
|
|
166
|
+
});
|
|
167
|
+
if (panel.json_path !== void 0)
|
|
168
|
+
ctx.addIssue({
|
|
169
|
+
code: "custom",
|
|
170
|
+
path: ["json_path"],
|
|
171
|
+
message: "use facts json_path values for grouped panels"
|
|
172
|
+
});
|
|
173
|
+
const seen = /* @__PURE__ */ new Map();
|
|
174
|
+
panel.facts.forEach((fact, index) => {
|
|
175
|
+
const first = seen.get(fact.id);
|
|
176
|
+
if (first === void 0) seen.set(fact.id, index);
|
|
177
|
+
else
|
|
178
|
+
ctx.addIssue({
|
|
179
|
+
code: "custom",
|
|
180
|
+
path: ["facts", index, "id"],
|
|
181
|
+
message: `duplicate fact id "${fact.id}" (already used by fact at index ${first})`
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
}
|
|
137
185
|
});
|
|
138
186
|
var githubAppSchema = z2.object({
|
|
139
187
|
app_id_env: z2.string().min(1),
|
|
@@ -303,6 +351,8 @@ import { z as z3 } from "zod";
|
|
|
303
351
|
var clientEnvSchema = z3.object({
|
|
304
352
|
/** Where the immutable client assets live. The one variable that repoints the whole client. */
|
|
305
353
|
assetPath: z3.string().min(1),
|
|
354
|
+
/** Opaque SHA-256 fingerprint of the canonical asset path, for diagnostic correlation only. */
|
|
355
|
+
assetPathId: z3.string().regex(/^sha256:[a-f0-9]{64}$/),
|
|
306
356
|
/** Same-origin path the proxy is mounted at, so there is no CORS and no cookie problem. */
|
|
307
357
|
proxyPath: z3.string().min(1),
|
|
308
358
|
/** Which board this is, resolved server-side so the client parses no URLs. */
|
|
@@ -313,7 +363,7 @@ var clientEnvSchema = z3.object({
|
|
|
313
363
|
clientId: z3.string().min(1).optional()
|
|
314
364
|
}).optional()
|
|
315
365
|
});
|
|
316
|
-
var
|
|
366
|
+
var clientIdentityResponseSchema = clientEnvSchema.pick({ assetPath: true, assetPathId: true }).extend({ serverVersion: z3.string().min(1) });
|
|
317
367
|
|
|
318
368
|
// packages/shared/src/envelope.ts
|
|
319
369
|
import { z as z4 } from "zod";
|
package/dist/index.js
CHANGED
|
@@ -764,6 +764,26 @@ var positionSchema = z2.union([
|
|
|
764
764
|
]);
|
|
765
765
|
var panelDensities = ["auto", "comfortable", "compact"];
|
|
766
766
|
var panelDensitySchema = z2.enum(panelDensities);
|
|
767
|
+
var httpValueJsonPathSchema = z2.string().regex(/^\$(?:\.[A-Za-z_][A-Za-z0-9_]*|\[\d+\])*$/, "must be a simple JSON path");
|
|
768
|
+
var httpValueFactSchema = z2.object({
|
|
769
|
+
/** Stable address below the panel; labels and ordering may change without repointing it. */
|
|
770
|
+
id: z2.string().min(1),
|
|
771
|
+
label: z2.string().min(1),
|
|
772
|
+
url: z2.url(),
|
|
773
|
+
json_path: httpValueJsonPathSchema.optional()
|
|
774
|
+
});
|
|
775
|
+
var httpValuePanelIdentitySchema = z2.object({
|
|
776
|
+
id: z2.string().min(1),
|
|
777
|
+
type: z2.literal("http-value")
|
|
778
|
+
});
|
|
779
|
+
var httpValueScalarPanelSchema = httpValuePanelIdentitySchema.extend({
|
|
780
|
+
url: z2.url(),
|
|
781
|
+
json_path: httpValueJsonPathSchema.optional(),
|
|
782
|
+
link: z2.url().optional()
|
|
783
|
+
});
|
|
784
|
+
var httpValueGroupedPanelSchema = httpValuePanelIdentitySchema.extend({
|
|
785
|
+
facts: z2.array(httpValueFactSchema).min(1).max(4)
|
|
786
|
+
});
|
|
767
787
|
var visibleRunningAnimations = [
|
|
768
788
|
"radial",
|
|
769
789
|
"runway",
|
|
@@ -806,7 +826,9 @@ var panelSchema = z2.looseObject({
|
|
|
806
826
|
/** Source-agnostic endpoint used by the http-value signal. */
|
|
807
827
|
url: z2.url().optional(),
|
|
808
828
|
/** Small, deliberate JSON path subset: $.version, $.deployment.version, or $.response.docs[0].latestVersion. */
|
|
809
|
-
json_path:
|
|
829
|
+
json_path: httpValueJsonPathSchema.optional(),
|
|
830
|
+
/** Up to four independently fetched source-agnostic readings in one visual panel. */
|
|
831
|
+
facts: httpValueGroupedPanelSchema.shape.facts.optional()
|
|
810
832
|
}).superRefine((panel, ctx) => {
|
|
811
833
|
if ("display" in panel) {
|
|
812
834
|
ctx.addIssue({
|
|
@@ -815,6 +837,32 @@ var panelSchema = z2.looseObject({
|
|
|
815
837
|
message: "display was removed; use density"
|
|
816
838
|
});
|
|
817
839
|
}
|
|
840
|
+
if (panel.type !== "http-value") return;
|
|
841
|
+
if (panel.facts) {
|
|
842
|
+
if (panel.url !== void 0)
|
|
843
|
+
ctx.addIssue({
|
|
844
|
+
code: "custom",
|
|
845
|
+
path: ["url"],
|
|
846
|
+
message: "use facts URLs for grouped panels"
|
|
847
|
+
});
|
|
848
|
+
if (panel.json_path !== void 0)
|
|
849
|
+
ctx.addIssue({
|
|
850
|
+
code: "custom",
|
|
851
|
+
path: ["json_path"],
|
|
852
|
+
message: "use facts json_path values for grouped panels"
|
|
853
|
+
});
|
|
854
|
+
const seen = /* @__PURE__ */ new Map();
|
|
855
|
+
panel.facts.forEach((fact, index) => {
|
|
856
|
+
const first = seen.get(fact.id);
|
|
857
|
+
if (first === void 0) seen.set(fact.id, index);
|
|
858
|
+
else
|
|
859
|
+
ctx.addIssue({
|
|
860
|
+
code: "custom",
|
|
861
|
+
path: ["facts", index, "id"],
|
|
862
|
+
message: `duplicate fact id "${fact.id}" (already used by fact at index ${first})`
|
|
863
|
+
});
|
|
864
|
+
});
|
|
865
|
+
}
|
|
818
866
|
});
|
|
819
867
|
var githubAppSchema = z2.object({
|
|
820
868
|
app_id_env: z2.string().min(1),
|
|
@@ -984,6 +1032,8 @@ import { z as z3 } from "zod";
|
|
|
984
1032
|
var clientEnvSchema = z3.object({
|
|
985
1033
|
/** Where the immutable client assets live. The one variable that repoints the whole client. */
|
|
986
1034
|
assetPath: z3.string().min(1),
|
|
1035
|
+
/** Opaque SHA-256 fingerprint of the canonical asset path, for diagnostic correlation only. */
|
|
1036
|
+
assetPathId: z3.string().regex(/^sha256:[a-f0-9]{64}$/),
|
|
987
1037
|
/** Same-origin path the proxy is mounted at, so there is no CORS and no cookie problem. */
|
|
988
1038
|
proxyPath: z3.string().min(1),
|
|
989
1039
|
/** Which board this is, resolved server-side so the client parses no URLs. */
|
|
@@ -994,7 +1044,7 @@ var clientEnvSchema = z3.object({
|
|
|
994
1044
|
clientId: z3.string().min(1).optional()
|
|
995
1045
|
}).optional()
|
|
996
1046
|
});
|
|
997
|
-
var
|
|
1047
|
+
var clientIdentityResponseSchema = clientEnvSchema.pick({ assetPath: true, assetPathId: true }).extend({ serverVersion: z3.string().min(1) });
|
|
998
1048
|
|
|
999
1049
|
// packages/shared/src/envelope.ts
|
|
1000
1050
|
import { z as z4 } from "zod";
|
package/dist/lambda.mjs
CHANGED
|
@@ -25202,7 +25202,7 @@ var require_dist_cjs11 = __commonJS({
|
|
|
25202
25202
|
var { setCredentialFeature: setCredentialFeature2 } = (init_client3(), __toCommonJS(client_exports2));
|
|
25203
25203
|
var { CredentialsProviderError: CredentialsProviderError2, parseKnownFiles: parseKnownFiles2, getProfileName: getProfileName2 } = (init_config2(), __toCommonJS(config_exports));
|
|
25204
25204
|
var { HttpRequest: HttpRequest2 } = (init_protocols(), __toCommonJS(protocols_exports));
|
|
25205
|
-
var { createHash:
|
|
25205
|
+
var { createHash: createHash6, createPrivateKey, createPublicKey, sign: sign2 } = __require("node:crypto");
|
|
25206
25206
|
var { promises } = __require("node:fs");
|
|
25207
25207
|
var { homedir: homedir2 } = __require("node:os");
|
|
25208
25208
|
var { dirname, join: join5 } = __require("node:path");
|
|
@@ -25371,7 +25371,7 @@ var require_dist_cjs11 = __commonJS({
|
|
|
25371
25371
|
getTokenFilePath() {
|
|
25372
25372
|
const directory = process.env.AWS_LOGIN_CACHE_DIRECTORY ?? join5(homedir2(), ".aws", "login", "cache");
|
|
25373
25373
|
const loginSessionBytes = Buffer.from(this.loginSession, "utf8");
|
|
25374
|
-
const loginSessionSha256 =
|
|
25374
|
+
const loginSessionSha256 = createHash6("sha256").update(loginSessionBytes).digest("hex");
|
|
25375
25375
|
return join5(directory, `${loginSessionSha256}.json`);
|
|
25376
25376
|
}
|
|
25377
25377
|
derToRawSignature(derSignature) {
|
|
@@ -68320,6 +68320,26 @@ var positionSchema = external_exports.union([
|
|
|
68320
68320
|
]);
|
|
68321
68321
|
var panelDensities = ["auto", "comfortable", "compact"];
|
|
68322
68322
|
var panelDensitySchema = external_exports.enum(panelDensities);
|
|
68323
|
+
var httpValueJsonPathSchema = external_exports.string().regex(/^\$(?:\.[A-Za-z_][A-Za-z0-9_]*|\[\d+\])*$/, "must be a simple JSON path");
|
|
68324
|
+
var httpValueFactSchema = external_exports.object({
|
|
68325
|
+
/** Stable address below the panel; labels and ordering may change without repointing it. */
|
|
68326
|
+
id: external_exports.string().min(1),
|
|
68327
|
+
label: external_exports.string().min(1),
|
|
68328
|
+
url: external_exports.url(),
|
|
68329
|
+
json_path: httpValueJsonPathSchema.optional()
|
|
68330
|
+
});
|
|
68331
|
+
var httpValuePanelIdentitySchema = external_exports.object({
|
|
68332
|
+
id: external_exports.string().min(1),
|
|
68333
|
+
type: external_exports.literal("http-value")
|
|
68334
|
+
});
|
|
68335
|
+
var httpValueScalarPanelSchema = httpValuePanelIdentitySchema.extend({
|
|
68336
|
+
url: external_exports.url(),
|
|
68337
|
+
json_path: httpValueJsonPathSchema.optional(),
|
|
68338
|
+
link: external_exports.url().optional()
|
|
68339
|
+
});
|
|
68340
|
+
var httpValueGroupedPanelSchema = httpValuePanelIdentitySchema.extend({
|
|
68341
|
+
facts: external_exports.array(httpValueFactSchema).min(1).max(4)
|
|
68342
|
+
});
|
|
68323
68343
|
var visibleRunningAnimations = [
|
|
68324
68344
|
"radial",
|
|
68325
68345
|
"runway",
|
|
@@ -68362,7 +68382,9 @@ var panelSchema = external_exports.looseObject({
|
|
|
68362
68382
|
/** Source-agnostic endpoint used by the http-value signal. */
|
|
68363
68383
|
url: external_exports.url().optional(),
|
|
68364
68384
|
/** Small, deliberate JSON path subset: $.version, $.deployment.version, or $.response.docs[0].latestVersion. */
|
|
68365
|
-
json_path:
|
|
68385
|
+
json_path: httpValueJsonPathSchema.optional(),
|
|
68386
|
+
/** Up to four independently fetched source-agnostic readings in one visual panel. */
|
|
68387
|
+
facts: httpValueGroupedPanelSchema.shape.facts.optional()
|
|
68366
68388
|
}).superRefine((panel, ctx) => {
|
|
68367
68389
|
if ("display" in panel) {
|
|
68368
68390
|
ctx.addIssue({
|
|
@@ -68371,6 +68393,32 @@ var panelSchema = external_exports.looseObject({
|
|
|
68371
68393
|
message: "display was removed; use density"
|
|
68372
68394
|
});
|
|
68373
68395
|
}
|
|
68396
|
+
if (panel.type !== "http-value") return;
|
|
68397
|
+
if (panel.facts) {
|
|
68398
|
+
if (panel.url !== void 0)
|
|
68399
|
+
ctx.addIssue({
|
|
68400
|
+
code: "custom",
|
|
68401
|
+
path: ["url"],
|
|
68402
|
+
message: "use facts URLs for grouped panels"
|
|
68403
|
+
});
|
|
68404
|
+
if (panel.json_path !== void 0)
|
|
68405
|
+
ctx.addIssue({
|
|
68406
|
+
code: "custom",
|
|
68407
|
+
path: ["json_path"],
|
|
68408
|
+
message: "use facts json_path values for grouped panels"
|
|
68409
|
+
});
|
|
68410
|
+
const seen = /* @__PURE__ */ new Map();
|
|
68411
|
+
panel.facts.forEach((fact, index) => {
|
|
68412
|
+
const first = seen.get(fact.id);
|
|
68413
|
+
if (first === void 0) seen.set(fact.id, index);
|
|
68414
|
+
else
|
|
68415
|
+
ctx.addIssue({
|
|
68416
|
+
code: "custom",
|
|
68417
|
+
path: ["facts", index, "id"],
|
|
68418
|
+
message: `duplicate fact id "${fact.id}" (already used by fact at index ${first})`
|
|
68419
|
+
});
|
|
68420
|
+
});
|
|
68421
|
+
}
|
|
68374
68422
|
});
|
|
68375
68423
|
var githubAppSchema = external_exports.object({
|
|
68376
68424
|
app_id_env: external_exports.string().min(1),
|
|
@@ -68552,6 +68600,8 @@ function boardSchemaModeline(url2) {
|
|
|
68552
68600
|
var clientEnvSchema = external_exports.object({
|
|
68553
68601
|
/** Where the immutable client assets live. The one variable that repoints the whole client. */
|
|
68554
68602
|
assetPath: external_exports.string().min(1),
|
|
68603
|
+
/** Opaque SHA-256 fingerprint of the canonical asset path, for diagnostic correlation only. */
|
|
68604
|
+
assetPathId: external_exports.string().regex(/^sha256:[a-f0-9]{64}$/),
|
|
68555
68605
|
/** Same-origin path the proxy is mounted at, so there is no CORS and no cookie problem. */
|
|
68556
68606
|
proxyPath: external_exports.string().min(1),
|
|
68557
68607
|
/** Which board this is, resolved server-side so the client parses no URLs. */
|
|
@@ -68562,7 +68612,7 @@ var clientEnvSchema = external_exports.object({
|
|
|
68562
68612
|
clientId: external_exports.string().min(1).optional()
|
|
68563
68613
|
}).optional()
|
|
68564
68614
|
});
|
|
68565
|
-
var
|
|
68615
|
+
var clientIdentityResponseSchema = clientEnvSchema.pick({ assetPath: true, assetPathId: true }).extend({ serverVersion: external_exports.string().min(1) });
|
|
68566
68616
|
|
|
68567
68617
|
// packages/shared/src/envelope.ts
|
|
68568
68618
|
var errorKindSchema = external_exports.enum([
|
|
@@ -69803,23 +69853,17 @@ function failure(panelId, kind, error62, link, date6, failure2) {
|
|
|
69803
69853
|
}
|
|
69804
69854
|
|
|
69805
69855
|
// packages/server/src/adapters/http-value.ts
|
|
69806
|
-
var httpValuePanelSchema = external_exports.object({
|
|
69807
|
-
id: external_exports.string().min(1),
|
|
69808
|
-
type: external_exports.literal("http-value"),
|
|
69809
|
-
url: external_exports.url(),
|
|
69810
|
-
json_path: external_exports.string().regex(/^\$(?:\.[A-Za-z_][A-Za-z0-9_]*|\[\d+\])*$/).optional(),
|
|
69811
|
-
link: external_exports.url().optional()
|
|
69812
|
-
});
|
|
69813
69856
|
function permittedHttpValueCalls(panel) {
|
|
69814
|
-
const
|
|
69815
|
-
const
|
|
69816
|
-
|
|
69817
|
-
|
|
69818
|
-
|
|
69819
|
-
|
|
69857
|
+
const grouped = httpValueGroupedPanelSchema.safeParse(panel);
|
|
69858
|
+
const urls = grouped.success ? grouped.data.facts.map((fact) => fact.url) : [httpValueScalarPanelSchema.parse(panel).url];
|
|
69859
|
+
return urls.map(permittedCall);
|
|
69860
|
+
}
|
|
69861
|
+
function httpValueFact(panel, factId) {
|
|
69862
|
+
const grouped = httpValueGroupedPanelSchema.parse(panel);
|
|
69863
|
+
return grouped.facts.find((fact) => fact.id === factId);
|
|
69820
69864
|
}
|
|
69821
69865
|
async function fetchHttpValue(args) {
|
|
69822
|
-
const panel =
|
|
69866
|
+
const panel = httpValueScalarPanelSchema.parse(args.panel);
|
|
69823
69867
|
const [call] = permittedHttpValueCalls(args.panel);
|
|
69824
69868
|
if (!call) throw new Error("http-value adapter declared no permitted call");
|
|
69825
69869
|
forwardValidators(args.requestHeaders, call.headers);
|
|
@@ -69868,6 +69912,21 @@ async function fetchHttpValue(args) {
|
|
|
69868
69912
|
};
|
|
69869
69913
|
}
|
|
69870
69914
|
}
|
|
69915
|
+
async function fetchHttpValueFact(args) {
|
|
69916
|
+
const fact = httpValueFact(args.panel, args.factId);
|
|
69917
|
+
if (!fact) throw new Error(`http-value fact "${args.factId}" is not configured`);
|
|
69918
|
+
return fetchHttpValue({
|
|
69919
|
+
panel: { id: args.panel.id, type: "http-value", url: fact.url, json_path: fact.json_path },
|
|
69920
|
+
requestHeaders: args.requestHeaders,
|
|
69921
|
+
fetcher: args.fetcher
|
|
69922
|
+
});
|
|
69923
|
+
}
|
|
69924
|
+
function permittedCall(value) {
|
|
69925
|
+
const url2 = new URL(value);
|
|
69926
|
+
if (url2.protocol !== "http:" && url2.protocol !== "https:")
|
|
69927
|
+
throw new Error("http-value URLs must use http or https");
|
|
69928
|
+
return { url: url2.toString(), headers: new Headers({ accept: "application/json, text/plain" }) };
|
|
69929
|
+
}
|
|
69871
69930
|
function parseValue(text) {
|
|
69872
69931
|
const trimmed = text.trim();
|
|
69873
69932
|
try {
|
|
@@ -72032,6 +72091,68 @@ var Hono2 = class extends Hono {
|
|
|
72032
72091
|
// packages/server/src/app.ts
|
|
72033
72092
|
var import_yaml = __toESM(require_dist(), 1);
|
|
72034
72093
|
|
|
72094
|
+
// packages/server/src/config.ts
|
|
72095
|
+
import { createHash as createHash5 } from "node:crypto";
|
|
72096
|
+
import { fileURLToPath } from "node:url";
|
|
72097
|
+
var DEFAULT_BOARD_CONFIG_URL = fileURLToPath(
|
|
72098
|
+
new URL("../../../boards/example.yaml", import.meta.url)
|
|
72099
|
+
);
|
|
72100
|
+
var configSchema = external_exports.object({
|
|
72101
|
+
/**
|
|
72102
|
+
* Where the built client lives. Points at a versioned CDN path in a deployment
|
|
72103
|
+
* (`https://assets.../dashboard/1.0.7`) or at the Vite dev server locally. Trailing slashes are
|
|
72104
|
+
* trimmed so `${assetPath}/index.html` never doubles up.
|
|
72105
|
+
*/
|
|
72106
|
+
assetPath: external_exports.string({
|
|
72107
|
+
error: "ASSET_PATH is required \u2014 the server has no client to serve without it."
|
|
72108
|
+
}).min(1, "ASSET_PATH is required \u2014 the server has no client to serve without it.").transform((value) => value.replace(/\/+$/, "")),
|
|
72109
|
+
proxyPath: external_exports.string().min(1).default("/api"),
|
|
72110
|
+
/** A URL or a local file path. Local development uses a path and needs no credential. */
|
|
72111
|
+
boardConfigUrl: external_exports.string().min(1).default(DEFAULT_BOARD_CONFIG_URL),
|
|
72112
|
+
/** Optional for single-board files; startup selects the only board automatically. */
|
|
72113
|
+
board: external_exports.string().min(1).optional(),
|
|
72114
|
+
/** Optional ARN of the one Secrets Manager JSON credential map for this Lambda. */
|
|
72115
|
+
secretReference: external_exports.string().min(1).optional(),
|
|
72116
|
+
/** Immutable server image identifier, supplied by the image build and emitted only in startup logs. */
|
|
72117
|
+
serverRelease: external_exports.string().min(1).default("development"),
|
|
72118
|
+
port: external_exports.coerce.number().int().min(1).max(65535).default(3e3),
|
|
72119
|
+
host: external_exports.string().min(1).default("localhost"),
|
|
72120
|
+
/**
|
|
72121
|
+
* How long to keep retrying an unreachable template before giving up, in milliseconds.
|
|
72122
|
+
*
|
|
72123
|
+
* Zero — the default, and what every deployment uses — means a bad ASSET_PATH fails on the first
|
|
72124
|
+
* attempt, which is the behavior a misconfiguration deserves. `npm run dev` sets a few seconds
|
|
72125
|
+
* because it starts the server and the Vite dev server at the same moment, and without this the
|
|
72126
|
+
* local loop is a race the developer loses about half the time.
|
|
72127
|
+
*/
|
|
72128
|
+
templateWaitMillis: external_exports.coerce.number().int().min(0).default(0)
|
|
72129
|
+
});
|
|
72130
|
+
function loadConfig2(env2 = process.env) {
|
|
72131
|
+
const result = configSchema.safeParse({
|
|
72132
|
+
assetPath: env2.ASSET_PATH,
|
|
72133
|
+
proxyPath: env2.PROXY_PATH,
|
|
72134
|
+
boardConfigUrl: env2.BOARD_CONFIG_URL,
|
|
72135
|
+
board: env2.BOARD,
|
|
72136
|
+
secretReference: env2.SECRET_REFERENCE,
|
|
72137
|
+
serverRelease: env2.SERVER_RELEASE,
|
|
72138
|
+
port: env2.PORT,
|
|
72139
|
+
host: env2.HOST,
|
|
72140
|
+
templateWaitMillis: env2.TEMPLATE_WAIT_MS
|
|
72141
|
+
});
|
|
72142
|
+
if (!result.success) {
|
|
72143
|
+
throw new Error(`Invalid server configuration:
|
|
72144
|
+
${external_exports.prettifyError(result.error)}`);
|
|
72145
|
+
}
|
|
72146
|
+
return result.data;
|
|
72147
|
+
}
|
|
72148
|
+
function assetPathId(assetPath) {
|
|
72149
|
+
return `sha256:${createHash5("sha256").update(assetPath).digest("hex")}`;
|
|
72150
|
+
}
|
|
72151
|
+
var LOCAL_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "::1"]);
|
|
72152
|
+
function isLocalHost(host) {
|
|
72153
|
+
return LOCAL_HOSTS.has(host);
|
|
72154
|
+
}
|
|
72155
|
+
|
|
72035
72156
|
// packages/server/src/logger.ts
|
|
72036
72157
|
var consoleLogger = {
|
|
72037
72158
|
log(event) {
|
|
@@ -72049,6 +72170,29 @@ function destinationOrigin(value) {
|
|
|
72049
72170
|
return void 0;
|
|
72050
72171
|
}
|
|
72051
72172
|
}
|
|
72173
|
+
function serverDiagnosticContext(serverVersion, configuredAssetPathId) {
|
|
72174
|
+
return { serverVersion, configuredAssetPathId };
|
|
72175
|
+
}
|
|
72176
|
+
function clientDiagnosticClaims(headers, context) {
|
|
72177
|
+
const version2 = headers.get("x-dashboard-client-version");
|
|
72178
|
+
const origin = headers.get("x-dashboard-client-origin");
|
|
72179
|
+
const assetPathId2 = headers.get("x-dashboard-client-asset-id");
|
|
72180
|
+
const clientOrigin = safeOrigin(origin);
|
|
72181
|
+
return {
|
|
72182
|
+
...version2 && /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(version2) ? { clientVersion: version2 } : {},
|
|
72183
|
+
...clientOrigin ? { clientOrigin } : {},
|
|
72184
|
+
...assetPathId2 && /^sha256:[a-f0-9]{64}$/.test(assetPathId2) ? { clientAssetPathMatchesConfigured: assetPathId2 === context.configuredAssetPathId } : {}
|
|
72185
|
+
};
|
|
72186
|
+
}
|
|
72187
|
+
function safeOrigin(value) {
|
|
72188
|
+
if (!value) return void 0;
|
|
72189
|
+
try {
|
|
72190
|
+
const origin = new URL(value).origin;
|
|
72191
|
+
return origin === "null" ? void 0 : origin;
|
|
72192
|
+
} catch {
|
|
72193
|
+
return void 0;
|
|
72194
|
+
}
|
|
72195
|
+
}
|
|
72052
72196
|
|
|
72053
72197
|
// packages/server/src/template.ts
|
|
72054
72198
|
var ASSET_PATH_SENTINEL = "/__ASSET_PATH__";
|
|
@@ -72134,6 +72278,10 @@ function createApp(deps) {
|
|
|
72134
72278
|
const credentials = deps.credentials ?? environmentCredentials();
|
|
72135
72279
|
const githubClient = createGithubClient(credentials);
|
|
72136
72280
|
const logger2 = deps.logger ?? consoleLogger;
|
|
72281
|
+
const diagnosticContext = serverDiagnosticContext(
|
|
72282
|
+
config2.serverRelease,
|
|
72283
|
+
assetPathId(config2.assetPath)
|
|
72284
|
+
);
|
|
72137
72285
|
const app = new Hono2();
|
|
72138
72286
|
app.use("/api/*", async (c5, next) => {
|
|
72139
72287
|
const id = requestId();
|
|
@@ -72145,8 +72293,10 @@ function createApp(deps) {
|
|
|
72145
72293
|
const id = c5.get("dashboardRequestId");
|
|
72146
72294
|
logger2.log({
|
|
72147
72295
|
event: "server.unhandled_exception",
|
|
72296
|
+
serverVersion: diagnosticContext.serverVersion,
|
|
72148
72297
|
...id ? { requestId: id } : {},
|
|
72149
|
-
operation: "route-handler"
|
|
72298
|
+
operation: "route-handler",
|
|
72299
|
+
...clientDiagnosticClaims(c5.req.raw.headers, diagnosticContext)
|
|
72150
72300
|
});
|
|
72151
72301
|
return c5.json(
|
|
72152
72302
|
{ error: "The dashboard could not complete this request." },
|
|
@@ -72156,7 +72306,11 @@ function createApp(deps) {
|
|
|
72156
72306
|
});
|
|
72157
72307
|
app.get("/health", (c5) => c5.json({ status: "ok" }));
|
|
72158
72308
|
app.get(`${config2.proxyPath}/client`, (c5) => {
|
|
72159
|
-
const identity = {
|
|
72309
|
+
const identity = {
|
|
72310
|
+
assetPath: config2.assetPath,
|
|
72311
|
+
assetPathId: diagnosticContext.configuredAssetPathId,
|
|
72312
|
+
serverVersion: diagnosticContext.serverVersion
|
|
72313
|
+
};
|
|
72160
72314
|
return c5.json(identity, 200, { "cache-control": "no-store" });
|
|
72161
72315
|
});
|
|
72162
72316
|
app.get("/", (c5) => renderEntrypoint(c5.req.raw, selectedBoard));
|
|
@@ -72208,6 +72362,27 @@ ${(0, import_yaml.stringify)(outputConfig, { sortMapEntries: true })}`,
|
|
|
72208
72362
|
};
|
|
72209
72363
|
app.get("/api/boards/:board/rendered", (c5) => layoutDownload(c5, "rendered"));
|
|
72210
72364
|
app.get("/api/boards/:board/authored", (c5) => layoutDownload(c5, "authored"));
|
|
72365
|
+
app.get("/api/panel/:board/:panelId/facts/:factId", async (c5) => {
|
|
72366
|
+
const boardName = c5.req.param("board");
|
|
72367
|
+
const panelId = c5.req.param("panelId");
|
|
72368
|
+
const factId = c5.req.param("factId");
|
|
72369
|
+
const panel = deps.boardConfig?.boards[boardName]?.panels.find(
|
|
72370
|
+
(candidate) => candidate.id === panelId
|
|
72371
|
+
);
|
|
72372
|
+
const fact = panel?.type === "http-value" && panel.facts ? httpValueFact(panel, factId) : void 0;
|
|
72373
|
+
if (!panel || !fact || !permitsPanelOperation(allowlist, boardName, panelId, "read"))
|
|
72374
|
+
return rejected(c5, { boardId: boardName, panelId, operation: "read" });
|
|
72375
|
+
return observation(
|
|
72376
|
+
c5,
|
|
72377
|
+
fetchHttpValueFact({
|
|
72378
|
+
panel,
|
|
72379
|
+
factId,
|
|
72380
|
+
requestHeaders: c5.req.raw.headers,
|
|
72381
|
+
fetcher: deps.fetcher ?? globalThis.fetch
|
|
72382
|
+
}),
|
|
72383
|
+
{ boardId: boardName, panelId, operation: "read", destination: fact.url }
|
|
72384
|
+
);
|
|
72385
|
+
});
|
|
72211
72386
|
app.get("/api/panel/:board/:panelId", async (c5) => {
|
|
72212
72387
|
const boardName = c5.req.param("board");
|
|
72213
72388
|
const panelId = c5.req.param("panelId");
|
|
@@ -72270,6 +72445,7 @@ ${(0, import_yaml.stringify)(outputConfig, { sortMapEntries: true })}`,
|
|
|
72270
72445
|
if (panel.type === "pull-request-health")
|
|
72271
72446
|
return rejected(c5, { boardId: boardName, panelId, operation: "read" });
|
|
72272
72447
|
if (panel.type === "http-value") {
|
|
72448
|
+
if (panel.facts) return rejected(c5, { boardId: boardName, panelId, operation: "read" });
|
|
72273
72449
|
const result = fetchHttpValue({
|
|
72274
72450
|
panel,
|
|
72275
72451
|
requestHeaders: c5.req.raw.headers,
|
|
@@ -72386,10 +72562,12 @@ ${(0, import_yaml.stringify)(outputConfig, { sortMapEntries: true })}`,
|
|
|
72386
72562
|
function rejected(c5, context) {
|
|
72387
72563
|
logger2.log({
|
|
72388
72564
|
event: "api.operation_rejected",
|
|
72565
|
+
serverVersion: diagnosticContext.serverVersion,
|
|
72389
72566
|
requestId: c5.get("dashboardRequestId"),
|
|
72390
72567
|
boardId: context.boardId,
|
|
72391
72568
|
panelId: context.panelId,
|
|
72392
|
-
operation: context.operation
|
|
72569
|
+
operation: context.operation,
|
|
72570
|
+
...clientDiagnosticClaims(c5.req.raw.headers, diagnosticContext)
|
|
72393
72571
|
});
|
|
72394
72572
|
return c5.notFound();
|
|
72395
72573
|
}
|
|
@@ -72400,10 +72578,12 @@ ${(0, import_yaml.stringify)(outputConfig, { sortMapEntries: true })}`,
|
|
|
72400
72578
|
if (adapted.envelope?.state === "error") {
|
|
72401
72579
|
logger2.log({
|
|
72402
72580
|
event: "panel.observation_failed",
|
|
72581
|
+
serverVersion: diagnosticContext.serverVersion,
|
|
72403
72582
|
requestId: c5.get("dashboardRequestId"),
|
|
72404
72583
|
boardId: context.boardId,
|
|
72405
72584
|
panelId: context.panelId,
|
|
72406
72585
|
operation: context.operation,
|
|
72586
|
+
...clientDiagnosticClaims(c5.req.raw.headers, diagnosticContext),
|
|
72407
72587
|
errorKind: adapted.envelope.error.kind,
|
|
72408
72588
|
elapsedMs: Math.round(performance.now() - started),
|
|
72409
72589
|
...context.sourceName ? { sourceName: context.sourceName } : {},
|
|
@@ -72419,6 +72599,7 @@ ${(0, import_yaml.stringify)(outputConfig, { sortMapEntries: true })}`,
|
|
|
72419
72599
|
const template = await templates.get(config2.assetPath);
|
|
72420
72600
|
const env2 = {
|
|
72421
72601
|
assetPath: config2.assetPath,
|
|
72602
|
+
assetPathId: diagnosticContext.configuredAssetPathId,
|
|
72422
72603
|
proxyPath: config2.proxyPath,
|
|
72423
72604
|
board
|
|
72424
72605
|
};
|
|
@@ -72465,68 +72646,13 @@ async function fetchBoardConfig(url2, fetcher) {
|
|
|
72465
72646
|
return response.text();
|
|
72466
72647
|
}
|
|
72467
72648
|
|
|
72468
|
-
// packages/server/src/config.ts
|
|
72469
|
-
import { fileURLToPath } from "node:url";
|
|
72470
|
-
var DEFAULT_BOARD_CONFIG_URL = fileURLToPath(
|
|
72471
|
-
new URL("../../../boards/example.yaml", import.meta.url)
|
|
72472
|
-
);
|
|
72473
|
-
var configSchema = external_exports.object({
|
|
72474
|
-
/**
|
|
72475
|
-
* Where the built client lives. Points at a versioned CDN path in a deployment
|
|
72476
|
-
* (`https://assets.../dashboard/1.0.7`) or at the Vite dev server locally. Trailing slashes are
|
|
72477
|
-
* trimmed so `${assetPath}/index.html` never doubles up.
|
|
72478
|
-
*/
|
|
72479
|
-
assetPath: external_exports.string({
|
|
72480
|
-
error: "ASSET_PATH is required \u2014 the server has no client to serve without it."
|
|
72481
|
-
}).min(1, "ASSET_PATH is required \u2014 the server has no client to serve without it.").transform((value) => value.replace(/\/+$/, "")),
|
|
72482
|
-
proxyPath: external_exports.string().min(1).default("/api"),
|
|
72483
|
-
/** A URL or a local file path. Local development uses a path and needs no credential. */
|
|
72484
|
-
boardConfigUrl: external_exports.string().min(1).default(DEFAULT_BOARD_CONFIG_URL),
|
|
72485
|
-
/** Optional for single-board files; startup selects the only board automatically. */
|
|
72486
|
-
board: external_exports.string().min(1).optional(),
|
|
72487
|
-
/** Optional ARN of the one Secrets Manager JSON credential map for this Lambda. */
|
|
72488
|
-
secretReference: external_exports.string().min(1).optional(),
|
|
72489
|
-
/** Immutable server image identifier, supplied by the image build and emitted only in startup logs. */
|
|
72490
|
-
serverRelease: external_exports.string().min(1).default("development"),
|
|
72491
|
-
port: external_exports.coerce.number().int().min(1).max(65535).default(3e3),
|
|
72492
|
-
host: external_exports.string().min(1).default("localhost"),
|
|
72493
|
-
/**
|
|
72494
|
-
* How long to keep retrying an unreachable template before giving up, in milliseconds.
|
|
72495
|
-
*
|
|
72496
|
-
* Zero — the default, and what every deployment uses — means a bad ASSET_PATH fails on the first
|
|
72497
|
-
* attempt, which is the behavior a misconfiguration deserves. `npm run dev` sets a few seconds
|
|
72498
|
-
* because it starts the server and the Vite dev server at the same moment, and without this the
|
|
72499
|
-
* local loop is a race the developer loses about half the time.
|
|
72500
|
-
*/
|
|
72501
|
-
templateWaitMillis: external_exports.coerce.number().int().min(0).default(0)
|
|
72502
|
-
});
|
|
72503
|
-
function loadConfig2(env2 = process.env) {
|
|
72504
|
-
const result = configSchema.safeParse({
|
|
72505
|
-
assetPath: env2.ASSET_PATH,
|
|
72506
|
-
proxyPath: env2.PROXY_PATH,
|
|
72507
|
-
boardConfigUrl: env2.BOARD_CONFIG_URL,
|
|
72508
|
-
board: env2.BOARD,
|
|
72509
|
-
secretReference: env2.SECRET_REFERENCE,
|
|
72510
|
-
serverRelease: env2.SERVER_RELEASE,
|
|
72511
|
-
port: env2.PORT,
|
|
72512
|
-
host: env2.HOST,
|
|
72513
|
-
templateWaitMillis: env2.TEMPLATE_WAIT_MS
|
|
72514
|
-
});
|
|
72515
|
-
if (!result.success) {
|
|
72516
|
-
throw new Error(`Invalid server configuration:
|
|
72517
|
-
${external_exports.prettifyError(result.error)}`);
|
|
72518
|
-
}
|
|
72519
|
-
return result.data;
|
|
72520
|
-
}
|
|
72521
|
-
var LOCAL_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "::1"]);
|
|
72522
|
-
function isLocalHost(host) {
|
|
72523
|
-
return LOCAL_HOSTS.has(host);
|
|
72524
|
-
}
|
|
72525
|
-
|
|
72526
72649
|
// packages/server/src/startup.ts
|
|
72527
72650
|
async function startup(options = {}) {
|
|
72528
72651
|
const logger2 = options.logger ?? consoleLogger;
|
|
72529
|
-
logger2.log({
|
|
72652
|
+
logger2.log({
|
|
72653
|
+
event: "server.starting",
|
|
72654
|
+
serverVersion: process.env.SERVER_RELEASE ?? "development"
|
|
72655
|
+
});
|
|
72530
72656
|
try {
|
|
72531
72657
|
const config2 = loadConfig2();
|
|
72532
72658
|
const fetcher = options.fetcher ?? globalThis.fetch;
|
|
@@ -72559,7 +72685,11 @@ async function startup(options = {}) {
|
|
|
72559
72685
|
config: resolvedConfig
|
|
72560
72686
|
};
|
|
72561
72687
|
} catch (error62) {
|
|
72562
|
-
logger2.log({
|
|
72688
|
+
logger2.log({
|
|
72689
|
+
event: "server.startup_failed",
|
|
72690
|
+
serverVersion: process.env.SERVER_RELEASE ?? "development",
|
|
72691
|
+
category: startupFailureCategory(error62)
|
|
72692
|
+
});
|
|
72563
72693
|
throw error62;
|
|
72564
72694
|
}
|
|
72565
72695
|
}
|
|
@@ -72598,7 +72728,12 @@ async function waitForTemplate(config2, fetcher) {
|
|
|
72598
72728
|
var RETRY_INTERVAL_MILLIS = 250;
|
|
72599
72729
|
function warnAboutMissingAuth(config2, logger2) {
|
|
72600
72730
|
if (isLocalHost(config2.host)) return;
|
|
72601
|
-
logger2.log({
|
|
72731
|
+
logger2.log({
|
|
72732
|
+
event: "server.no_auth_warning",
|
|
72733
|
+
serverVersion: config2.serverRelease,
|
|
72734
|
+
host: config2.host,
|
|
72735
|
+
port: config2.port
|
|
72736
|
+
});
|
|
72602
72737
|
}
|
|
72603
72738
|
function startupFailureCategory(error62) {
|
|
72604
72739
|
const message = error62 instanceof Error ? error62.message : "";
|
package/package.json
CHANGED