fix(web): validate restored dashboard tile shapes
This commit is contained in:
parent
88c448258d
commit
bac809952a
2 changed files with 122 additions and 7 deletions
|
|
@ -213,7 +213,21 @@ describe("dashboard refresh lifecycle", () => {
|
|||
tiles: [
|
||||
{},
|
||||
{
|
||||
item: { id: "infra-ram", detail: "live" },
|
||||
item: {
|
||||
id: "incomplete",
|
||||
detail: "live",
|
||||
},
|
||||
savedAt: 1_000,
|
||||
tile: { kind: "telemetry", id: "incomplete" },
|
||||
},
|
||||
{
|
||||
item: {
|
||||
id: "infra-ram",
|
||||
label: "Infra RAM",
|
||||
value: { kind: "percent", value: 42 },
|
||||
detail: "live",
|
||||
severity: "ok",
|
||||
},
|
||||
savedAt: 1_000,
|
||||
tile: { kind: "telemetry", id: "infra-ram" },
|
||||
},
|
||||
|
|
@ -242,6 +256,8 @@ describe("dashboard refresh lifecycle", () => {
|
|||
tile: { kind: "telemetry", id: "infra-ram" },
|
||||
item: {
|
||||
id: "infra-ram",
|
||||
label: "Infra RAM",
|
||||
value: { kind: "percent", value: 42 },
|
||||
detail: "live - stale 15s",
|
||||
severity: "stale",
|
||||
},
|
||||
|
|
|
|||
|
|
@ -346,7 +346,7 @@ function isDashboardTileSnapshotRecord(
|
|||
typeof value.savedAt === "number" &&
|
||||
Number.isFinite(value.savedAt) &&
|
||||
isDashboardTileReference(value.tile) &&
|
||||
isDashboardTileSnapshotItem(value.item)
|
||||
isDashboardTileSnapshotItem(value.tile, value.item)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -369,19 +369,118 @@ function isDashboardTileReference(
|
|||
}
|
||||
|
||||
function isDashboardTileSnapshotItem(
|
||||
tile: DashboardTileReference,
|
||||
value: unknown,
|
||||
): value is DashboardTileSnapshotItem {
|
||||
if (!isSnapshotRecord(value) || value.id !== tile.id) return false;
|
||||
|
||||
switch (tile.kind) {
|
||||
case "telemetry":
|
||||
return (
|
||||
isSnapshotRecord(value) &&
|
||||
typeof value.id === "string" &&
|
||||
(value.detail === undefined || typeof value.detail === "string")
|
||||
typeof value.label === "string" &&
|
||||
isMetricValue(value.value) &&
|
||||
isSeverity(value.severity) &&
|
||||
isOptionalString(value.detail) &&
|
||||
isOptionalString(value.description) &&
|
||||
isOptionalString(value.icon) &&
|
||||
isOptionalNumberArray(value.sparkline)
|
||||
);
|
||||
case "service":
|
||||
return (
|
||||
typeof value.label === "string" &&
|
||||
typeof value.description === "string" &&
|
||||
isSeverity(value.severity) &&
|
||||
isOptionalString(value.detail) &&
|
||||
isOptionalString(value.icon) &&
|
||||
isOptionalLink(value.link)
|
||||
);
|
||||
case "module":
|
||||
return (
|
||||
(value.kind === "summary" ||
|
||||
value.kind === "weather" ||
|
||||
value.kind === "custom") &&
|
||||
isOptionalString(value.title) &&
|
||||
isOptionalString(value.label) &&
|
||||
isOptionalString(value.value) &&
|
||||
isOptionalString(value.detail) &&
|
||||
isOptionalString(value.icon) &&
|
||||
(value.severity === undefined || isSeverity(value.severity))
|
||||
);
|
||||
case "status":
|
||||
return (
|
||||
typeof value.label === "string" &&
|
||||
typeof value.value === "string" &&
|
||||
isOptionalLink(value.link) &&
|
||||
(value.severity === undefined || isSeverity(value.severity))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function isSnapshotRecord(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === "object" && value !== null;
|
||||
}
|
||||
|
||||
function isMetricValue(value: unknown): boolean {
|
||||
if (!isSnapshotRecord(value) || typeof value.kind !== "string") return false;
|
||||
|
||||
if (value.kind === "text") {
|
||||
return (
|
||||
typeof value.value === "string" &&
|
||||
isOptionalString(value.unit)
|
||||
);
|
||||
}
|
||||
|
||||
const numericKinds = ["bytes", "latency", "number", "percent", "temperature"];
|
||||
if (!numericKinds.includes(value.kind)) return false;
|
||||
if (typeof value.value !== "number" || !Number.isFinite(value.value)) return false;
|
||||
if (value.kind === "percent" && (value.value < 0 || value.value > 100)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const precision = value.precision;
|
||||
return (
|
||||
isOptionalString(value.unit) &&
|
||||
(precision === undefined ||
|
||||
(typeof precision === "number" &&
|
||||
Number.isInteger(precision) &&
|
||||
precision >= 0 &&
|
||||
precision <= 4))
|
||||
);
|
||||
}
|
||||
|
||||
function isSeverity(value: unknown): boolean {
|
||||
return (
|
||||
value === "neutral" ||
|
||||
value === "ok" ||
|
||||
value === "warning" ||
|
||||
value === "danger" ||
|
||||
value === "stale" ||
|
||||
value === "unavailable"
|
||||
);
|
||||
}
|
||||
|
||||
function isOptionalString(value: unknown): boolean {
|
||||
return value === undefined || typeof value === "string";
|
||||
}
|
||||
|
||||
function isOptionalNumberArray(value: unknown): boolean {
|
||||
return (
|
||||
value === undefined ||
|
||||
(Array.isArray(value) &&
|
||||
value.every((item) => typeof item === "number" && Number.isFinite(item)))
|
||||
);
|
||||
}
|
||||
|
||||
function isOptionalLink(value: unknown): boolean {
|
||||
return (
|
||||
value === undefined ||
|
||||
(isSnapshotRecord(value) &&
|
||||
typeof value.href === "string" &&
|
||||
isOptionalString(value.label) &&
|
||||
(value.external === undefined || typeof value.external === "boolean"))
|
||||
);
|
||||
}
|
||||
|
||||
function staleDashboardTileDetail(
|
||||
detail: string | undefined,
|
||||
ageMs: number,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue