@zerotal/monitor 1.0.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/CHANGELOG.md +17 -0
- package/LICENSE +21 -0
- package/README.md +174 -0
- package/package.json +74 -0
- package/src/MonitorStore.ts +1471 -0
- package/src/alerting.ts +173 -0
- package/src/config.ts +171 -0
- package/src/facades/Monitor.ts +75 -0
- package/src/index.ts +62 -0
- package/src/instance.ts +16 -0
- package/src/middleware/MonitorAuthMiddleware.ts +20 -0
- package/src/middleware/MonitorPayloadMiddleware.ts +96 -0
- package/src/panel.ts +159 -0
- package/src/prometheus.ts +128 -0
- package/src/provider/MonitorProvider.ts +318 -0
- package/src/recorder/MonitorEventBridge.ts +157 -0
- package/src/recorder/ctxBuffers.ts +91 -0
- package/src/sources/live.ts +249 -0
- package/src/sources/realtime.ts +24 -0
- package/src/sources/system.ts +151 -0
- package/src/store/MonitorDb.ts +415 -0
- package/src/store/RingBuffer.ts +89 -0
- package/src/store/types.ts +580 -0
- package/src/support/time.ts +25 -0
- package/src/ui/MonitorLayout.tsx +42 -0
- package/src/ui/MonitorPage.tsx +3722 -0
- package/src/ui/charts.tsx +55 -0
- package/src/ui/icons.ts +32 -0
- package/src/ui/tones.ts +78 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/** @jsxImportSource @zerotal/flow */
|
|
2
|
+
import type { HtmlNode } from "@zerotal/flow";
|
|
3
|
+
|
|
4
|
+
/** Build a polyline points string normalised to a w×h box. */
|
|
5
|
+
export function sparkPoints(s: number[], w: number, h: number): string {
|
|
6
|
+
if (s.length === 0) return "";
|
|
7
|
+
const max = Math.max(...s);
|
|
8
|
+
const min = Math.min(...s);
|
|
9
|
+
const r = max - min || 1;
|
|
10
|
+
return s
|
|
11
|
+
.map(
|
|
12
|
+
(v, i) => `${((i / (s.length - 1)) * w).toFixed(1)},${(h - ((v - min) / r) * h).toFixed(1)}`,
|
|
13
|
+
)
|
|
14
|
+
.join(" ");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Build line + filled-area paths for an area chart normalised to a w×h box. */
|
|
18
|
+
export function areaPaths(s: number[], w: number, h: number): { line: string; area: string } {
|
|
19
|
+
if (s.length === 0) return { line: "", area: "" };
|
|
20
|
+
const max = Math.max(...s) * 1.15 || 1;
|
|
21
|
+
const pts = s.map((v, i) => [(i / (s.length - 1)) * w, h - (v / max) * h] as const);
|
|
22
|
+
const line = pts.map((p, i) => `${i ? "L" : "M"}${p[0].toFixed(1)} ${p[1].toFixed(1)}`).join(" ");
|
|
23
|
+
return { line, area: `${line} L${w} ${h} L0 ${h} Z` };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface SparklineProps {
|
|
27
|
+
series: number[];
|
|
28
|
+
w?: number;
|
|
29
|
+
h?: number;
|
|
30
|
+
stroke?: string;
|
|
31
|
+
class?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** A compact sparkline SVG. */
|
|
35
|
+
export function Sparkline(props: SparklineProps): HtmlNode {
|
|
36
|
+
const w = props.w ?? 120;
|
|
37
|
+
const h = props.h ?? 30;
|
|
38
|
+
return (
|
|
39
|
+
<svg
|
|
40
|
+
class={props.class ?? "w-full h-8"}
|
|
41
|
+
viewBox={`0 0 ${w} ${h + 2}`}
|
|
42
|
+
preserveAspectRatio="none"
|
|
43
|
+
>
|
|
44
|
+
<polyline
|
|
45
|
+
points={sparkPoints(props.series, w, h)}
|
|
46
|
+
fill="none"
|
|
47
|
+
stroke={props.stroke ?? "hsl(var(--primary))"}
|
|
48
|
+
stroke-width="2"
|
|
49
|
+
stroke-linecap="round"
|
|
50
|
+
stroke-linejoin="round"
|
|
51
|
+
vector-effect="non-scaling-stroke"
|
|
52
|
+
/>
|
|
53
|
+
</svg>
|
|
54
|
+
);
|
|
55
|
+
}
|
package/src/ui/icons.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/** Inline SVG icon markup for the sidebar nav (rendered via dangerouslySetInnerHTML). */
|
|
2
|
+
export const icons: Record<string, string> = {
|
|
3
|
+
overview:
|
|
4
|
+
'<svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><rect x="2.5" y="2.5" width="6" height="6" rx="1"/><rect x="11.5" y="2.5" width="6" height="6" rx="1"/><rect x="2.5" y="11.5" width="6" height="6" rx="1"/><rect x="11.5" y="11.5" width="6" height="6" rx="1"/></svg>',
|
|
5
|
+
requests:
|
|
6
|
+
'<svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h14M3 10h14M3 14h14"/><circle cx="6" cy="6" r="0.4" fill="currentColor"/></svg>',
|
|
7
|
+
exceptions:
|
|
8
|
+
'<svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M10 2.5L18 16H2z"/><line x1="10" y1="8" x2="10" y2="11.5"/><circle cx="10" cy="13.6" r="0.4" fill="currentColor"/></svg>',
|
|
9
|
+
queues:
|
|
10
|
+
'<svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><line x1="3" y1="5" x2="17" y2="5"/><line x1="3" y1="10" x2="17" y2="10"/><line x1="3" y1="15" x2="11" y2="15"/></svg>',
|
|
11
|
+
mail: '<svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><rect x="2.5" y="4" width="15" height="12" rx="1.5"/><path d="M3 5l7 5.5L17 5"/></svg>',
|
|
12
|
+
database:
|
|
13
|
+
'<svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><ellipse cx="10" cy="5" rx="6.5" ry="2"/><path d="M3.5 5v10c0 1.1 2.9 2 6.5 2s6.5-.9 6.5-2V5"/><path d="M3.5 10c0 1.1 2.9 2 6.5 2s6.5-.9 6.5-2"/></svg>',
|
|
14
|
+
cache:
|
|
15
|
+
'<svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M11.5 2L5 11h4.5l-1.5 7 7.5-10H11z"/></svg>',
|
|
16
|
+
system:
|
|
17
|
+
'<svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M10 2.5l6 3v4c0 4-2.7 7.5-6 8.5C6.7 17 4 13.5 4 9.5v-4z"/><path d="M7.5 10l1.8 1.8L13 8"/></svg>',
|
|
18
|
+
logo: '<svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M10 2.5l6 3v8l-6 3-6-3v-8z"/><path d="M4 5.5l6 3 6-3M10 8.5v8"/></svg>',
|
|
19
|
+
security:
|
|
20
|
+
'<svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><rect x="4.5" y="9" width="11" height="8" rx="1.5"/><path d="M7 9V6.5a3 3 0 0 1 6 0V9"/></svg>',
|
|
21
|
+
realtime:
|
|
22
|
+
'<svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M5 13a6 6 0 0 1 0-6M15 7a6 6 0 0 1 0 6M3 15a9 9 0 0 1 0-10M17 5a9 9 0 0 1 0 10"/><circle cx="10" cy="10" r="1.4" fill="currentColor"/></svg>',
|
|
23
|
+
logs: '<svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="14" height="14" rx="2"/><path d="M6.5 7.5h7M6.5 10h7M6.5 12.5h4"/></svg>',
|
|
24
|
+
alerts:
|
|
25
|
+
'<svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M10 3a4.5 4.5 0 0 0-4.5 4.5c0 4-1.5 5.5-1.5 5.5h12s-1.5-1.5-1.5-5.5A4.5 4.5 0 0 0 10 3z"/><path d="M8.5 16a1.5 1.5 0 0 0 3 0"/></svg>',
|
|
26
|
+
route:
|
|
27
|
+
'<svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><circle cx="5" cy="15" r="2"/><circle cx="15" cy="5" r="2"/><path d="M7 15h5a3 3 0 0 0 0-6H8a3 3 0 0 1 0-6h5"/></svg>',
|
|
28
|
+
notifications:
|
|
29
|
+
'<svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M17 3L9 11"/><path d="M17 3l-5 14-3-6-6-3z"/></svg>',
|
|
30
|
+
commands:
|
|
31
|
+
'<svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><rect x="2.5" y="3.5" width="15" height="13" rx="2"/><path d="M6 8l2.5 2L6 12M10.5 12.5h3.5"/></svg>',
|
|
32
|
+
};
|
package/src/ui/tones.ts
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Value → colour mappings shared across the panel.
|
|
3
|
+
*
|
|
4
|
+
* Everything here resolves to flow-ui design tokens rather than literal palette
|
|
5
|
+
* classes, so the panel re-themes (and follows dark mode) wherever it is used.
|
|
6
|
+
* Semantic states use `success` / `warning` / `destructive`; the span colours are
|
|
7
|
+
* categorical and use the theme's chart palette instead — "middleware" isn't good
|
|
8
|
+
* or bad, it just has to stay distinguishable from "query".
|
|
9
|
+
*/
|
|
10
|
+
import type { Tone } from "../store/types.ts";
|
|
11
|
+
|
|
12
|
+
export function toneText(tone: Tone | undefined): string {
|
|
13
|
+
switch (tone) {
|
|
14
|
+
case "ok":
|
|
15
|
+
return "text-success";
|
|
16
|
+
case "warn":
|
|
17
|
+
return "text-warning";
|
|
18
|
+
case "bad":
|
|
19
|
+
return "text-destructive";
|
|
20
|
+
default:
|
|
21
|
+
return "text-foreground";
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function methodTone(m: string): string {
|
|
26
|
+
return (
|
|
27
|
+
(
|
|
28
|
+
{
|
|
29
|
+
GET: "bg-primary/10 text-primary",
|
|
30
|
+
POST: "bg-success/10 text-success",
|
|
31
|
+
PUT: "bg-warning/10 text-warning",
|
|
32
|
+
PATCH: "bg-warning/10 text-warning",
|
|
33
|
+
DELETE: "bg-destructive/10 text-destructive",
|
|
34
|
+
} as Record<string, string>
|
|
35
|
+
)[m] ?? "bg-muted text-muted-foreground"
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function statusTone(s: number): string {
|
|
40
|
+
if (s < 300) return "text-success";
|
|
41
|
+
if (s < 400) return "text-primary";
|
|
42
|
+
if (s < 500) return "text-warning";
|
|
43
|
+
return "text-destructive";
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function pctTone(ms: number): string {
|
|
47
|
+
return ms > 500 ? "text-destructive" : ms > 200 ? "text-warning" : "text-foreground";
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Colour for a trace span, by kind.
|
|
52
|
+
*
|
|
53
|
+
* Returned as a raw CSS colour rather than a class because these are applied to
|
|
54
|
+
* SVG `fill`/`stroke` and inline widths, which Tailwind utilities can't reach.
|
|
55
|
+
*/
|
|
56
|
+
export function spanColor(kind: string): string {
|
|
57
|
+
return (
|
|
58
|
+
(
|
|
59
|
+
{
|
|
60
|
+
boot: "hsl(var(--muted-foreground))",
|
|
61
|
+
middleware: "hsl(var(--chart-5))",
|
|
62
|
+
controller: "hsl(var(--chart-2))",
|
|
63
|
+
query: "hsl(var(--chart-3))",
|
|
64
|
+
cache: "hsl(var(--chart-4))",
|
|
65
|
+
http: "hsl(var(--chart-6))",
|
|
66
|
+
view: "hsl(var(--chart-7))",
|
|
67
|
+
} as Record<string, string>
|
|
68
|
+
)[kind] ?? "hsl(var(--muted-foreground))"
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function gaugeBar(value: number): string {
|
|
73
|
+
return value > 85 ? "bg-destructive" : value > 65 ? "bg-warning" : "bg-primary";
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function gaugeText(value: number): string {
|
|
77
|
+
return value > 85 ? "text-destructive" : value > 65 ? "text-warning" : "text-foreground";
|
|
78
|
+
}
|