@dunx/dashboard 3.1.3 → 3.2.1
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 +7 -6
- package/dist/api/types.d.ts +33 -16
- package/dist/contracts.d.ts +37 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +14 -15
- package/dist/options.d.ts +9 -1
- package/dist/ui.js +3 -4
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -43,7 +43,7 @@ app.use(DashboardMiddleware); // first, ahead of any session guard
|
|
|
43
43
|
|
|
44
44
|
## The panels
|
|
45
45
|
|
|
46
|
-
|
|
46
|
+
Seven panels, each with a JSON sibling under `{path}/api/*`, so `curl` is a real
|
|
47
47
|
way to read this on a box with no browser. The queues page is bull-board's.
|
|
48
48
|
|
|
49
49
|
| Panel | Shows |
|
|
@@ -54,11 +54,12 @@ way to read this on a box with no browser. The queues page is bull-board's.
|
|
|
54
54
|
| Redis | Connection state and `INFO` |
|
|
55
55
|
| Config | Keys always, values only where `reveal` says so |
|
|
56
56
|
| Runtime | The process, its memory and its probes |
|
|
57
|
+
| Stats | Per-route request timings and per-operation query timings |
|
|
57
58
|
|
|
58
59
|
## Three things that are decisions
|
|
59
60
|
|
|
60
|
-
- **`authorize` has no default
|
|
61
|
-
|
|
61
|
+
- **`authorize` has no default.** Leaving it out serves the page to anyone who
|
|
62
|
+
can reach the port. Omitting it logs a warning naming the mount at boot.
|
|
62
63
|
- **A rejected request gets 404, not 403.** Register the middleware **ahead of
|
|
63
64
|
any session guard**: a guard running first answers 401 and tells a prober the
|
|
64
65
|
mount exists. `authorize` takes the raw `Request` so it can be self-sufficient.
|
|
@@ -67,9 +68,9 @@ way to read this on a box with no browser. The queues page is bull-board's.
|
|
|
67
68
|
|
|
68
69
|
## Notes
|
|
69
70
|
|
|
70
|
-
-
|
|
71
|
-
`
|
|
72
|
-
|
|
71
|
+
- `QueueSource` and `RedisProbe` restate structurally what `JobPublisher` and
|
|
72
|
+
`RedisConnection` already are, so `queues: publisher` is the whole wiring.
|
|
73
|
+
It depends on neither `@dunx/infra` nor `bullmq`.
|
|
73
74
|
- The board is built on the first request for the queues page, never at boot, so
|
|
74
75
|
an app that never opens it holds no broker socket.
|
|
75
76
|
- `commands: false` maps onto bull-board's own `readOnlyMode`.
|
package/dist/api/types.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { ModuleNode, ProviderNode } from '@dunx/core';
|
|
1
|
+
import type { MemoryReport, ModuleNode, ProviderNode, RuntimeReport as CoreRuntimeReport } from '@dunx/core';
|
|
2
2
|
import type { GatewayNode, RouteNode } from '@dunx/http/internal';
|
|
3
|
-
import type { ProbeState } from '../contracts.js';
|
|
3
|
+
import type { DbQueryStats, DbStatsReport, HistogramSnapshot, HttpStatsReport, ProbeState, RouteStats } from '../contracts.js';
|
|
4
4
|
/**
|
|
5
5
|
* Everything the page reads, declared once.
|
|
6
6
|
*
|
|
@@ -61,23 +61,19 @@ export interface ProbeReport {
|
|
|
61
61
|
/** How long the probe took, so a slow dependency is visible before it fails. */
|
|
62
62
|
readonly ms: number;
|
|
63
63
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
64
|
+
/**
|
|
65
|
+
* The process half moved down to `@dunx/core` once the health module wanted the
|
|
66
|
+
* same numbers. Re-exported so `internal/dashboard-ui`'s relative `import type`
|
|
67
|
+
* is unchanged, and so nothing here declares a second copy of it.
|
|
68
|
+
*/
|
|
69
|
+
export type { CoreRuntimeReport, MemoryReport };
|
|
70
70
|
/** The half that changes. Polled. */
|
|
71
|
-
export interface RuntimeReport {
|
|
72
|
-
readonly pid: number;
|
|
73
|
-
readonly uptimeMs: number;
|
|
74
|
-
readonly bun: string;
|
|
75
|
-
readonly platform: string;
|
|
76
|
-
readonly arch: string;
|
|
77
|
-
readonly memory: MemoryReport;
|
|
71
|
+
export interface RuntimeReport extends Omit<CoreRuntimeReport, 'now' | 'cpu' | 'resource'> {
|
|
78
72
|
readonly probes: readonly ProbeReport[];
|
|
79
|
-
/** Server clock, so the page can show ages
|
|
73
|
+
/** Server clock as epoch milliseconds, so the page can show ages. */
|
|
80
74
|
readonly now: number;
|
|
75
|
+
readonly cpu: CoreRuntimeReport['cpu'];
|
|
76
|
+
readonly resource: CoreRuntimeReport['resource'];
|
|
81
77
|
}
|
|
82
78
|
export interface RedisReport {
|
|
83
79
|
readonly configured: true;
|
|
@@ -108,3 +104,24 @@ export interface QueuesReport {
|
|
|
108
104
|
/** Why there is no board: no source, no queues opened, or bull-board absent. */
|
|
109
105
|
readonly unavailable?: string;
|
|
110
106
|
}
|
|
107
|
+
/** Absent because the app passed no source, which the page reads to hide a panel. */
|
|
108
|
+
export interface StatsAbsent {
|
|
109
|
+
readonly configured: false;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* A half that is present carries `configured: true`, the same discriminant
|
|
113
|
+
* `RedisReport` uses: the reports themselves are `@dunx/http`'s and a restatement
|
|
114
|
+
* of `@dunx/infra/db`'s, and neither should grow a field only this page reads.
|
|
115
|
+
*/
|
|
116
|
+
export type StatsHalf<T> = (T & {
|
|
117
|
+
readonly configured: true;
|
|
118
|
+
}) | StatsAbsent;
|
|
119
|
+
/**
|
|
120
|
+
* Both halves of `{path}/api/stats`. Independent: an app may time requests and
|
|
121
|
+
* not queries, or the other way round.
|
|
122
|
+
*/
|
|
123
|
+
export interface StatsReport {
|
|
124
|
+
readonly http: StatsHalf<HttpStatsReport>;
|
|
125
|
+
readonly db: StatsHalf<DbStatsReport>;
|
|
126
|
+
}
|
|
127
|
+
export type { DbQueryStats, DbStatsReport, HistogramSnapshot, HttpStatsReport, RouteStats, };
|
package/dist/contracts.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { HistogramSnapshot } from '@dunx/core';
|
|
2
|
+
import type { HttpStatsReport, ProbeResult } from '@dunx/http';
|
|
1
3
|
/**
|
|
2
4
|
* What the dashboard needs from the things it reports on, restated structurally
|
|
3
5
|
* so this package depends on `@dunx/infra` and `bullmq` not at all. Each is
|
|
@@ -8,6 +10,8 @@
|
|
|
8
10
|
* | `QueueSource` | `JobPublisher` from `@dunx/infra/queue` |
|
|
9
11
|
* | `RedisProbe` | `RedisConnection` from `@dunx/infra/redis` |
|
|
10
12
|
* | `ConfigValues` | `ConfigService` from `@dunx/core` |
|
|
13
|
+
* | `StatsSource` | `RequestMetrics` from `@dunx/http` |
|
|
14
|
+
* | `DbStatsSource`| `QueryMetrics` from `@dunx/infra/db` |
|
|
11
15
|
*/
|
|
12
16
|
/**
|
|
13
17
|
* The validated configuration. `ConfigService` satisfies it as written. Passed in
|
|
@@ -30,6 +34,39 @@ export interface QueueSource {
|
|
|
30
34
|
* `unknown` rather than a restatement. */
|
|
31
35
|
queue(name: string): unknown;
|
|
32
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* Per-route request counts and timings. `RequestMetrics` from `@dunx/http`
|
|
39
|
+
* satisfies it as written, and `HttpStatsReport` is imported rather than
|
|
40
|
+
* restated: this package already peer-depends on `@dunx/http`, so a second copy
|
|
41
|
+
* of `RouteStats` here would be a second thing to keep in step.
|
|
42
|
+
*/
|
|
43
|
+
export interface StatsSource {
|
|
44
|
+
snapshot(): HttpStatsReport;
|
|
45
|
+
}
|
|
46
|
+
export type { HistogramSnapshot } from '@dunx/core';
|
|
47
|
+
export type { HttpStatsReport, RouteStats } from '@dunx/http';
|
|
48
|
+
/**
|
|
49
|
+
* Query counts and timings. `QueryMetrics` from `@dunx/infra/db` satisfies it
|
|
50
|
+
* structurally, and `DbStatsReport` **is** restated here for the reason
|
|
51
|
+
* `QueueSource` is: this package depends on `@dunx/infra` not at all, and a peer
|
|
52
|
+
* on it for one report type would be the dependency the boundary exists to
|
|
53
|
+
* refuse. Narrowing a field silently un-satisfies `QueryMetrics`.
|
|
54
|
+
*/
|
|
55
|
+
export interface DbStatsSource {
|
|
56
|
+
snapshot(): DbStatsReport;
|
|
57
|
+
}
|
|
58
|
+
export interface DbQueryStats {
|
|
59
|
+
readonly operation: string;
|
|
60
|
+
readonly count: number;
|
|
61
|
+
readonly errors: number;
|
|
62
|
+
readonly duration: HistogramSnapshot;
|
|
63
|
+
readonly slowest?: string;
|
|
64
|
+
}
|
|
65
|
+
export interface DbStatsReport {
|
|
66
|
+
readonly operations: readonly DbQueryStats[];
|
|
67
|
+
readonly total: number;
|
|
68
|
+
readonly since: string;
|
|
69
|
+
}
|
|
33
70
|
/**
|
|
34
71
|
* Enough Redis to answer "is it up and what is it doing". `send` rather than a
|
|
35
72
|
* typed `info()`: a method per command is how a restatement becomes a client.
|
|
@@ -45,7 +82,6 @@ export interface RedisProbe {
|
|
|
45
82
|
* it. `RedisProbe` below did not move with them: `PingProbe` is a `ping` alone.
|
|
46
83
|
*/
|
|
47
84
|
export type { ProbeResult, ProbeState } from '@dunx/http';
|
|
48
|
-
import type { ProbeResult } from '@dunx/http';
|
|
49
85
|
/**
|
|
50
86
|
* Anything else worth a light on the page. Awaited with a timeout and never let
|
|
51
87
|
* to throw, so a probe that hangs costs one panel rather than the page.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { DashboardModule } from './module.js';
|
|
2
2
|
export { DashboardMiddleware } from './middleware.js';
|
|
3
3
|
export { DashboardOptions, normalizeMount, type Authorize, type DashboardOptionsInit, type Reveal, } from './options.js';
|
|
4
|
-
export type { ConfigValues, DashboardProbe, ProbeResult, ProbeState, QueueSource, RedisProbe, } from './contracts.js';
|
|
5
|
-
export type { ConfigEntry, GatewayNode, MemoryReport, Meta, ModuleNode, ProbeReport, ProviderNode, QueuesReport, RedisAbsent, RedisReport, RouteNode, RuntimeReport, Snapshot, } from './api/types.js';
|
|
4
|
+
export type { ConfigValues, DashboardProbe, DbStatsSource, ProbeResult, ProbeState, QueueSource, RedisProbe, StatsSource, } from './contracts.js';
|
|
5
|
+
export type { ConfigEntry, GatewayNode, DbQueryStats, DbStatsReport, HistogramSnapshot, HttpStatsReport, MemoryReport, Meta, ModuleNode, ProbeReport, RouteStats, StatsAbsent, StatsHalf, StatsReport, ProviderNode, QueuesReport, RedisAbsent, RedisReport, RouteNode, RuntimeReport, Snapshot, } from './api/types.js';
|
|
6
6
|
export { handleDashboard, type RouterDeps } from './router.js';
|
package/dist/index.js
CHANGED
|
@@ -145,6 +145,8 @@ class DashboardOptions {
|
|
|
145
145
|
queueNames;
|
|
146
146
|
redis;
|
|
147
147
|
probes;
|
|
148
|
+
stats;
|
|
149
|
+
dbStats;
|
|
148
150
|
config;
|
|
149
151
|
reveal;
|
|
150
152
|
openApiPath;
|
|
@@ -159,6 +161,8 @@ class DashboardOptions {
|
|
|
159
161
|
this.queueNames = init.queueNames ?? [];
|
|
160
162
|
this.redis = init.redis;
|
|
161
163
|
this.probes = init.probes ?? [];
|
|
164
|
+
this.stats = init.stats;
|
|
165
|
+
this.dbStats = init.dbStats;
|
|
162
166
|
this.config = init.config;
|
|
163
167
|
this.reveal = init.reveal ?? (() => false);
|
|
164
168
|
this.openApiPath = init.openApiPath;
|
|
@@ -251,6 +255,7 @@ var redisReport = async (redis, timeoutMs) => {
|
|
|
251
255
|
};
|
|
252
256
|
|
|
253
257
|
// src/api/runtime.ts
|
|
258
|
+
import { RuntimeStats } from "@dunx/core";
|
|
254
259
|
var withTimeout = (probe, ms) => bounded(async () => {
|
|
255
260
|
try {
|
|
256
261
|
return await probe.check();
|
|
@@ -282,27 +287,14 @@ var redisProbe = (redis) => ({
|
|
|
282
287
|
};
|
|
283
288
|
}
|
|
284
289
|
});
|
|
285
|
-
var memory = () => {
|
|
286
|
-
const usage = process.memoryUsage();
|
|
287
|
-
return {
|
|
288
|
-
rss: usage.rss,
|
|
289
|
-
heapUsed: usage.heapUsed,
|
|
290
|
-
heapTotal: usage.heapTotal,
|
|
291
|
-
external: usage.external
|
|
292
|
-
};
|
|
293
|
-
};
|
|
294
290
|
var runtimeReport = async (options, startedAt) => {
|
|
295
291
|
const probes = [
|
|
296
292
|
...options.redis ? [redisProbe(options.redis)] : [],
|
|
297
293
|
...options.probes
|
|
298
294
|
];
|
|
295
|
+
const { now: _iso, ...process_ } = new RuntimeStats(startedAt).snapshot();
|
|
299
296
|
return {
|
|
300
|
-
|
|
301
|
-
uptimeMs: Math.round(performance.now() - startedAt),
|
|
302
|
-
bun: Bun.version,
|
|
303
|
-
platform: process.platform,
|
|
304
|
-
arch: process.arch,
|
|
305
|
-
memory: memory(),
|
|
297
|
+
...process_,
|
|
306
298
|
probes: await Promise.all(probes.map((probe) => runProbe(probe, options.probeTimeoutMs))),
|
|
307
299
|
now: Date.now()
|
|
308
300
|
};
|
|
@@ -324,6 +316,13 @@ var handleApi = async (deps, method, segments) => {
|
|
|
324
316
|
return json(await runtimeReport(deps.options, deps.startedAt));
|
|
325
317
|
case "redis":
|
|
326
318
|
return json(deps.options.redis === undefined ? { configured: false } : await redisReport(deps.options.redis, deps.options.probeTimeoutMs));
|
|
319
|
+
case "stats": {
|
|
320
|
+
const report = {
|
|
321
|
+
http: deps.options.stats === undefined ? { configured: false } : { ...deps.options.stats.snapshot(), configured: true },
|
|
322
|
+
db: deps.options.dbStats === undefined ? { configured: false } : { ...deps.options.dbStats.snapshot(), configured: true }
|
|
323
|
+
};
|
|
324
|
+
return json(report);
|
|
325
|
+
}
|
|
327
326
|
case "queues": {
|
|
328
327
|
const { names, unavailable } = boardNames(deps.options);
|
|
329
328
|
return json({
|
package/dist/options.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { BunRequest } from 'bun';
|
|
2
|
-
import type { ConfigValues, DashboardProbe, QueueSource, RedisProbe } from './contracts.js';
|
|
2
|
+
import type { ConfigValues, DashboardProbe, DbStatsSource, QueueSource, RedisProbe, StatsSource } from './contracts.js';
|
|
3
3
|
/**
|
|
4
4
|
* Decides whether a request may see the dashboard at all. It receives the raw
|
|
5
5
|
* `Request`: the middleware must be registered ahead of any session guard, so
|
|
@@ -44,6 +44,12 @@ export interface DashboardOptionsInit {
|
|
|
44
44
|
readonly redis?: RedisProbe;
|
|
45
45
|
/** Anything else worth a light: a database, an upstream, a leader lease. */
|
|
46
46
|
readonly probes?: readonly DashboardProbe[];
|
|
47
|
+
/** `RequestMetrics` from `@dunx/http` goes here, and needs `metrics: true` on
|
|
48
|
+
* `HttpFactory.create` to have anything in it. Absent means no stats panel. */
|
|
49
|
+
readonly stats?: StatsSource;
|
|
50
|
+
/** `QueryMetrics` from `@dunx/infra/db` goes here, and needs
|
|
51
|
+
* `DbModule.forRoot(options, { metrics: true })`. */
|
|
52
|
+
readonly dbStats?: DbStatsSource;
|
|
47
53
|
/** `ConfigService` goes here; the panel is absent without it. */
|
|
48
54
|
readonly config?: ConfigValues;
|
|
49
55
|
/** See {@link Reveal}. The default reveals nothing, even with `config` set. */
|
|
@@ -83,6 +89,8 @@ export declare class DashboardOptions {
|
|
|
83
89
|
readonly queueNames: readonly string[];
|
|
84
90
|
readonly redis: RedisProbe | undefined;
|
|
85
91
|
readonly probes: readonly DashboardProbe[];
|
|
92
|
+
readonly stats: StatsSource | undefined;
|
|
93
|
+
readonly dbStats: DbStatsSource | undefined;
|
|
86
94
|
readonly config: ConfigValues | undefined;
|
|
87
95
|
readonly reveal: Reveal;
|
|
88
96
|
readonly openApiPath: string | undefined;
|