@singhak/nodeui-core 0.1.0 → 0.3.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 +59 -30
- package/dist/index.cjs +652 -50
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +197 -10
- package/dist/index.d.ts +197 -10
- package/dist/index.mjs +632 -49
- package/dist/index.mjs.map +1 -1
- package/package.json +63 -63
- package/static/assets/index-Cu0xtlkD.js +11 -0
- package/static/assets/index-DlUszrsn.css +1 -0
- package/static/index.html +14 -13
- package/static/assets/index-DbLC8b3x.css +0 -1
- package/static/assets/index-DowJtJYZ.js +0 -10
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { IncomingMessage, ServerResponse } from 'node:http';
|
|
1
2
|
import { Readable } from 'node:stream';
|
|
2
|
-
import { ServerResponse, IncomingMessage } from 'node:http';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Shared typed contract for the NodeUI developer console.
|
|
@@ -8,7 +8,12 @@ import { ServerResponse, IncomingMessage } from 'node:http';
|
|
|
8
8
|
* contract. Every provider returns a {@link ProviderResult}; every REST
|
|
9
9
|
* endpoint returns an {@link ApiEnvelope} which is the same shape.
|
|
10
10
|
*/
|
|
11
|
-
type
|
|
11
|
+
type BuiltInPanelId = 'health' | 'memory' | 'cpu' | 'event-loop' | 'heap-snapshot' | 'startup' | 'requests' | 'env' | 'routes' | 'logs' | 'metrics' | 'outgoing';
|
|
12
|
+
/**
|
|
13
|
+
* Panel identifier. Built-in panels are listed in {@link BuiltInPanelId};
|
|
14
|
+
* plugins may register any other string id (lowercase letters, digits, `-`).
|
|
15
|
+
*/
|
|
16
|
+
type PanelId = BuiltInPanelId | (string & {});
|
|
12
17
|
/** Resolved, validated console configuration. */
|
|
13
18
|
interface NodeUIConfig {
|
|
14
19
|
/** URL path prefix where the console and its API are served. Default `/nodeui`. */
|
|
@@ -35,6 +40,18 @@ interface NodeUIConfig {
|
|
|
35
40
|
confirmTtlMs: number;
|
|
36
41
|
/** Directory where heap snapshot files are written. */
|
|
37
42
|
heapSnapshotDir: string;
|
|
43
|
+
/** Extra `Host` header hostnames accepted besides loopback names. */
|
|
44
|
+
allowedHosts: string[];
|
|
45
|
+
/** Extra `Origin` values accepted for cross-origin calls. */
|
|
46
|
+
allowedOrigins: string[];
|
|
47
|
+
/** Extra remote IPs / IPv4 CIDRs accepted (e.g. Docker bridge `172.17.0.0/16`). */
|
|
48
|
+
allowedRemoteAddresses: string[];
|
|
49
|
+
/** Accept requests carrying `X-Forwarded-*` headers. */
|
|
50
|
+
trustProxy: boolean;
|
|
51
|
+
/** Whether a shared access token is required. */
|
|
52
|
+
authRequired: boolean;
|
|
53
|
+
/** Maximum concurrent live (SSE) streams. */
|
|
54
|
+
maxSseClients: number;
|
|
38
55
|
}
|
|
39
56
|
/** Context handed to every provider call. */
|
|
40
57
|
interface ProviderContext {
|
|
@@ -72,6 +89,8 @@ type ApiEnvelope<T> = ProviderResult<T>;
|
|
|
72
89
|
*/
|
|
73
90
|
interface NodeUIProvider<T = unknown> {
|
|
74
91
|
readonly id: PanelId;
|
|
92
|
+
/** Display label for plugin panels; defaults to the id. */
|
|
93
|
+
readonly title?: string;
|
|
75
94
|
start?(ctx: ProviderContext): void;
|
|
76
95
|
stop?(ctx: ProviderContext): void;
|
|
77
96
|
get(ctx: ProviderContext): ProviderResult<T> | Promise<ProviderResult<T>>;
|
|
@@ -99,6 +118,12 @@ interface CpuData {
|
|
|
99
118
|
totalPercent: number;
|
|
100
119
|
sampleAtMs: number;
|
|
101
120
|
}
|
|
121
|
+
interface HealthCheckResult {
|
|
122
|
+
name: string;
|
|
123
|
+
status: 'up' | 'down';
|
|
124
|
+
durationMs: number;
|
|
125
|
+
error?: string;
|
|
126
|
+
}
|
|
102
127
|
interface HealthData {
|
|
103
128
|
status: 'ok' | 'degraded' | 'critical' | 'unknown';
|
|
104
129
|
statusReason: string;
|
|
@@ -108,6 +133,8 @@ interface HealthData {
|
|
|
108
133
|
platform: string;
|
|
109
134
|
eventLoopLagMs: number | null;
|
|
110
135
|
memoryUsedPercent: number | null;
|
|
136
|
+
/** Results of user-registered dependency checks; empty when none. */
|
|
137
|
+
checks: HealthCheckResult[];
|
|
111
138
|
}
|
|
112
139
|
interface StartupMark {
|
|
113
140
|
name: string;
|
|
@@ -128,9 +155,37 @@ interface RequestEntry {
|
|
|
128
155
|
timestampMs: number;
|
|
129
156
|
ip: string;
|
|
130
157
|
}
|
|
158
|
+
interface RouteStat {
|
|
159
|
+
method: string;
|
|
160
|
+
path: string;
|
|
161
|
+
count: number;
|
|
162
|
+
avgMs: number;
|
|
163
|
+
p95Ms: number;
|
|
164
|
+
errors: number;
|
|
165
|
+
}
|
|
166
|
+
interface RequestsSummary {
|
|
167
|
+
/** Requests currently held in the ring buffer. */
|
|
168
|
+
count: number;
|
|
169
|
+
errors: number;
|
|
170
|
+
/** `errors / count` in 0..1 (5xx only). */
|
|
171
|
+
errorRate: number;
|
|
172
|
+
avgMs: number;
|
|
173
|
+
p50Ms: number;
|
|
174
|
+
p95Ms: number;
|
|
175
|
+
p99Ms: number;
|
|
176
|
+
byStatus: {
|
|
177
|
+
'2xx': number;
|
|
178
|
+
'3xx': number;
|
|
179
|
+
'4xx': number;
|
|
180
|
+
'5xx': number;
|
|
181
|
+
};
|
|
182
|
+
/** Busiest/slowest routes, slowest p95 first (at most 8). */
|
|
183
|
+
routes: RouteStat[];
|
|
184
|
+
}
|
|
131
185
|
interface RequestsData {
|
|
132
186
|
total: number;
|
|
133
187
|
entries: RequestEntry[];
|
|
188
|
+
summary: RequestsSummary;
|
|
134
189
|
}
|
|
135
190
|
interface EnvEntry {
|
|
136
191
|
key: string;
|
|
@@ -165,6 +220,21 @@ interface MetricsBucket {
|
|
|
165
220
|
interface MetricsData {
|
|
166
221
|
buckets: MetricsBucket[];
|
|
167
222
|
}
|
|
223
|
+
interface OutgoingRequestEntry {
|
|
224
|
+
id: number;
|
|
225
|
+
method: string;
|
|
226
|
+
/** Target as `host/path` (query string removed). */
|
|
227
|
+
url: string;
|
|
228
|
+
status: number | null;
|
|
229
|
+
durationMs: number;
|
|
230
|
+
timestampMs: number;
|
|
231
|
+
error?: string;
|
|
232
|
+
}
|
|
233
|
+
interface OutgoingData {
|
|
234
|
+
total: number;
|
|
235
|
+
failed: number;
|
|
236
|
+
entries: OutgoingRequestEntry[];
|
|
237
|
+
}
|
|
168
238
|
interface HeapSnapshotData {
|
|
169
239
|
fileName: string;
|
|
170
240
|
filePath: string;
|
|
@@ -194,6 +264,12 @@ interface ConfigData {
|
|
|
194
264
|
enabled: boolean;
|
|
195
265
|
pattern: string;
|
|
196
266
|
};
|
|
267
|
+
locked: boolean;
|
|
268
|
+
/** Plugin panel ids and titles, in registration order. */
|
|
269
|
+
plugins: Array<{
|
|
270
|
+
id: PanelId;
|
|
271
|
+
title: string;
|
|
272
|
+
}>;
|
|
197
273
|
}
|
|
198
274
|
|
|
199
275
|
/** Replacement value used when masking secret values. */
|
|
@@ -222,6 +298,12 @@ declare class RingBuffer<T> {
|
|
|
222
298
|
|
|
223
299
|
/** Keys whose values are redacted in any panel output. */
|
|
224
300
|
declare const SECRET_KEY_PATTERN: RegExp;
|
|
301
|
+
/**
|
|
302
|
+
* Redacts secrets embedded in free text: credentials in URLs
|
|
303
|
+
* (`postgres://user:pass@host`), bearer/basic tokens, JWTs and
|
|
304
|
+
* `password=...`-style pairs. Used for log lines and any string value.
|
|
305
|
+
*/
|
|
306
|
+
declare function maskSecretText(text: string): string;
|
|
225
307
|
interface ActivationDecision {
|
|
226
308
|
active: boolean;
|
|
227
309
|
reason: string;
|
|
@@ -234,12 +316,53 @@ interface ActivationDecision {
|
|
|
234
316
|
declare function resolveActivation(env: Record<string, string | undefined>): ActivationDecision;
|
|
235
317
|
/** True when the socket address is a loopback address. */
|
|
236
318
|
declare function isLoopbackAddress(address: string | undefined): boolean;
|
|
319
|
+
/**
|
|
320
|
+
* True when `address` matches one of `allowed`, each an exact IP or an IPv4
|
|
321
|
+
* CIDR (e.g. `172.17.0.0/16`, the default Docker bridge network).
|
|
322
|
+
*/
|
|
323
|
+
declare function matchesAddress(address: string | undefined, allowed: readonly string[]): boolean;
|
|
324
|
+
/** Extracts the lower-cased hostname from a `Host` header value (no port). */
|
|
325
|
+
declare function hostnameFromHostHeader(value: string | undefined): string | null;
|
|
326
|
+
/** True for the hostnames a browser uses to reach a loopback server. */
|
|
327
|
+
declare function isLoopbackHostname(hostname: string | null): boolean;
|
|
237
328
|
/**
|
|
238
329
|
* Deep-clone a value, replacing any value under a key matching the secret
|
|
239
|
-
* pattern with `[REDACTED]
|
|
330
|
+
* pattern with `[REDACTED]` and scrubbing secrets embedded in string values
|
|
331
|
+
* (URL credentials, bearer tokens, `password=...`). The input is not mutated.
|
|
240
332
|
*/
|
|
241
333
|
declare function maskSecrets<T>(value: T, pattern?: RegExp): T;
|
|
242
334
|
|
|
335
|
+
declare const AUTH_COOKIE = "nodeui_token";
|
|
336
|
+
interface GuardOptions {
|
|
337
|
+
/** Configured console host; a loopback value enables the strict checks. */
|
|
338
|
+
host: string;
|
|
339
|
+
/** Extra `Host` header hostnames accepted (e.g. `myapp.local`). */
|
|
340
|
+
allowedHosts: readonly string[];
|
|
341
|
+
/** Extra `Origin` values accepted for cross-origin calls. */
|
|
342
|
+
allowedOrigins: readonly string[];
|
|
343
|
+
/** Extra remote IPs / IPv4 CIDRs accepted (e.g. `172.17.0.0/16` for Docker). */
|
|
344
|
+
allowedRemoteAddresses: readonly string[];
|
|
345
|
+
/** Accept requests carrying `X-Forwarded-*` headers. Default false. */
|
|
346
|
+
trustProxy: boolean;
|
|
347
|
+
/** When set, every request must present this token. */
|
|
348
|
+
authToken?: string;
|
|
349
|
+
}
|
|
350
|
+
type GuardResult = {
|
|
351
|
+
ok: true;
|
|
352
|
+
setToken?: string;
|
|
353
|
+
} | {
|
|
354
|
+
ok: false;
|
|
355
|
+
status: number;
|
|
356
|
+
code: string;
|
|
357
|
+
message: string;
|
|
358
|
+
};
|
|
359
|
+
/**
|
|
360
|
+
* Builds the request guard for the console. It combines: loopback/remote
|
|
361
|
+
* address policy, `Host` validation (DNS-rebinding defence), `Origin`
|
|
362
|
+
* validation (cross-site request defence) and an optional shared token.
|
|
363
|
+
*/
|
|
364
|
+
declare function createGuard(options: GuardOptions): (req: IncomingMessage) => GuardResult;
|
|
365
|
+
|
|
243
366
|
/**
|
|
244
367
|
* Single-use, expiring confirmation nonces. Mutating actions (such as heap
|
|
245
368
|
* snapshots) require a nonce issued here, presented via the
|
|
@@ -247,8 +370,9 @@ declare function maskSecrets<T>(value: T, pattern?: RegExp): T;
|
|
|
247
370
|
*/
|
|
248
371
|
declare class ConfirmationStore {
|
|
249
372
|
private readonly ttlMs;
|
|
373
|
+
private readonly maxOutstanding;
|
|
250
374
|
private nonces;
|
|
251
|
-
constructor(ttlMs: number);
|
|
375
|
+
constructor(ttlMs: number, maxOutstanding?: number);
|
|
252
376
|
issue(): ConfirmIssued;
|
|
253
377
|
/** Consumes `nonce` and returns true only if it was valid and unexpired. */
|
|
254
378
|
consume(nonce: string): boolean;
|
|
@@ -381,12 +505,45 @@ declare class EventLoopLagProvider implements NodeUIProvider<EventLoopSample> {
|
|
|
381
505
|
};
|
|
382
506
|
}
|
|
383
507
|
|
|
384
|
-
/**
|
|
508
|
+
/**
|
|
509
|
+
* A dependency check (database ping, cache ping, ...). Resolve for healthy,
|
|
510
|
+
* reject/throw (or resolve `false`) for unhealthy.
|
|
511
|
+
*/
|
|
512
|
+
type HealthCheck = () => unknown | Promise<unknown>;
|
|
513
|
+
/** Derives process + event-loop health, plus optional dependency checks. */
|
|
385
514
|
declare class HealthProvider implements NodeUIProvider<HealthData> {
|
|
515
|
+
private readonly checks;
|
|
386
516
|
readonly id: "health";
|
|
387
|
-
|
|
517
|
+
private cache;
|
|
518
|
+
constructor(checks?: Record<string, HealthCheck>);
|
|
519
|
+
private runChecks;
|
|
520
|
+
get(ctx: ProviderContext): Promise<{
|
|
388
521
|
ok: true;
|
|
389
522
|
data: HealthData;
|
|
523
|
+
}>;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
type Recorder = (entry: Omit<OutgoingRequestEntry, 'id'>) => void;
|
|
527
|
+
/**
|
|
528
|
+
* Refcounted instrumentation of outgoing HTTP calls made via `http`,
|
|
529
|
+
* `https` (axios, got, node-fetch, ...) and global `fetch` (undici). The
|
|
530
|
+
* first consumer installs the hooks, the last release restores everything.
|
|
531
|
+
*/
|
|
532
|
+
declare function interceptOutgoing(record: Recorder): () => void;
|
|
533
|
+
/** Recent outgoing HTTP calls, recorded lazily while the panel is active. */
|
|
534
|
+
declare class OutgoingProvider implements NodeUIProvider<OutgoingData> {
|
|
535
|
+
readonly id: "outgoing";
|
|
536
|
+
private buffer;
|
|
537
|
+
private nextId;
|
|
538
|
+
private failed;
|
|
539
|
+
private release;
|
|
540
|
+
constructor(size: number);
|
|
541
|
+
start(): void;
|
|
542
|
+
stop(): void;
|
|
543
|
+
record(entry: Omit<OutgoingRequestEntry, 'id'>): void;
|
|
544
|
+
get(): {
|
|
545
|
+
ok: true;
|
|
546
|
+
data: OutgoingData;
|
|
390
547
|
};
|
|
391
548
|
}
|
|
392
549
|
|
|
@@ -405,6 +562,7 @@ declare class HeapSnapshotProvider implements NodeUIProvider<HeapSnapshotPanelDa
|
|
|
405
562
|
takeSnapshot(ctx: ProviderContext): Promise<ProviderResult<HeapSnapshotData>>;
|
|
406
563
|
}
|
|
407
564
|
|
|
565
|
+
declare function summarizeRequests(entries: readonly RequestEntry[]): RequestsSummary;
|
|
408
566
|
/** Fixed-size ring buffer of recent HTTP requests, recorded via middleware hook. */
|
|
409
567
|
declare class RequestsProvider implements NodeUIProvider<RequestsData> {
|
|
410
568
|
readonly id: "requests";
|
|
@@ -521,7 +679,7 @@ interface SseStream {
|
|
|
521
679
|
* Minimal Server-Sent Events writer over a raw HTTP response. Events are
|
|
522
680
|
* emitted as `data: <json>\n\n`; heartbeats as `:ping\n\n`.
|
|
523
681
|
*/
|
|
524
|
-
declare function startSse(res: ServerResponse): SseStream;
|
|
682
|
+
declare function startSse(res: ServerResponse, extraHeaders?: Record<string, string>): SseStream;
|
|
525
683
|
|
|
526
684
|
interface NodeUIOptions {
|
|
527
685
|
/** URL path prefix. Default `/nodeui` (or `NODEUI_PATH`). */
|
|
@@ -548,8 +706,32 @@ interface NodeUIOptions {
|
|
|
548
706
|
inactivityTimeoutMs?: number;
|
|
549
707
|
/** TTL for mutation confirmation nonces. Default 60000. */
|
|
550
708
|
confirmTtlMs?: number;
|
|
551
|
-
/** Directory for heap snapshot files. Defaults to the OS temp dir. */
|
|
709
|
+
/** Directory for heap snapshot files. Defaults to a private dir under the OS temp dir. */
|
|
552
710
|
heapSnapshotDir?: string;
|
|
711
|
+
/** Extra `Host` hostnames accepted besides loopback names (or `NODEUI_ALLOWED_HOSTS`, comma-separated). */
|
|
712
|
+
allowedHosts?: string[];
|
|
713
|
+
/** Extra `Origin` values accepted for cross-origin calls (or `NODEUI_ALLOWED_ORIGINS`). */
|
|
714
|
+
allowedOrigins?: string[];
|
|
715
|
+
/**
|
|
716
|
+
* Extra remote IPs / IPv4 CIDRs accepted (or `NODEUI_ALLOWED_REMOTE`). Use
|
|
717
|
+
* `['172.16.0.0/12']` to reach the console from the host when the app runs in Docker.
|
|
718
|
+
*/
|
|
719
|
+
allowedRemoteAddresses?: string[];
|
|
720
|
+
/** Accept requests carrying `X-Forwarded-*` headers (or `NODEUI_TRUST_PROXY=true`). Default false. */
|
|
721
|
+
trustProxy?: boolean;
|
|
722
|
+
/**
|
|
723
|
+
* Shared access token (or `NODEUI_TOKEN`). When set, open `/nodeui/?token=<token>` once;
|
|
724
|
+
* the browser then keeps an HttpOnly cookie. API clients may send `Authorization: Bearer`.
|
|
725
|
+
*/
|
|
726
|
+
authToken?: string;
|
|
727
|
+
/** Maximum concurrent live (SSE) streams. Default 10. */
|
|
728
|
+
maxSseClients?: number;
|
|
729
|
+
/** Custom panels; each provider needs a unique lowercase id, e.g. `queues`. */
|
|
730
|
+
plugins?: NodeUIProvider[];
|
|
731
|
+
/** Named dependency checks (database, cache, ...) surfaced in the health panel. */
|
|
732
|
+
healthChecks?: Record<string, HealthCheck>;
|
|
733
|
+
/** Max recorded outgoing HTTP calls. Default 200. */
|
|
734
|
+
outgoingLogSize?: number;
|
|
553
735
|
}
|
|
554
736
|
interface NodeUIServer {
|
|
555
737
|
readonly config: NodeUIConfig;
|
|
@@ -559,6 +741,11 @@ interface NodeUIServer {
|
|
|
559
741
|
middleware(): NodeUIMiddleware;
|
|
560
742
|
/** Direct request handling (no `next`); used for tests and adapters. */
|
|
561
743
|
handle(req: IncomingMessage, res: ServerResponse): Promise<void>;
|
|
744
|
+
/**
|
|
745
|
+
* Supplies the route list directly (frameworks other than Express, e.g.
|
|
746
|
+
* Fastify's `onRoute`). Takes precedence over Express router introspection.
|
|
747
|
+
*/
|
|
748
|
+
setRoutes(source: RouteEntry[] | (() => RouteEntry[])): void;
|
|
562
749
|
/** Records a bootstrap timing mark, e.g. `mark("listening")`. */
|
|
563
750
|
mark(name: string): void;
|
|
564
751
|
/** Whether a background-sampling provider is currently running. */
|
|
@@ -576,7 +763,7 @@ type NodeUIMiddleware = (req: IncomingMessage, res: ServerResponse, next: () =>
|
|
|
576
763
|
* Serializes an API envelope to JSON, applying secret masking so values
|
|
577
764
|
* under keys like `TOKEN`/`KEY`/`SECRET`/`PASSWORD` never reach the UI.
|
|
578
765
|
*/
|
|
579
|
-
declare function serializeEnvelope<T>(envelope: ApiEnvelope<T
|
|
766
|
+
declare function serializeEnvelope<T>(envelope: ApiEnvelope<T>, mask?: boolean): string;
|
|
580
767
|
declare function createNodeUI(options?: NodeUIOptions): NodeUIServer;
|
|
581
768
|
|
|
582
|
-
export { type ActivationDecision, type ApiEnvelope, type ConfigData, type ConfirmIssued, ConfirmationStore, type CpuClock, type CpuData, CpuProvider, type CpuUsageSnapshot, DEFAULT_LOG_SIZE, type EnvData, type EnvEntry, EnvProvider, type EventLoopClock, EventLoopLagProvider, type EventLoopSample, type HealthData, HealthProvider, type HeapSnapshotData, type HeapSnapshotPanelData, HeapSnapshotProvider, type LogEntry, type LogLevel, type LogsData, LogsProvider, type MemoryData, MemoryProvider, type MetricsBucket, type MetricsData, MetricsProvider, type NodeUIConfig, type NodeUIMiddleware, type NodeUIOptions, type NodeUIProvider, type NodeUIServer, type PanelId, type ProviderContext, type ProviderError, ProviderRegistry, type ProviderResult, type RequestEntry, type RequestsData, RequestsProvider, RingBuffer, type RouteEntry, type RoutesData, RoutesProvider, SECRET_KEY_PATTERN, SECRET_MASKED, Sampler, type SamplerOptions, type SseStream, type StartupData, type StartupMark, StartupTracker, type StaticAsset, createNodeUI, extractRoutes, interceptConsole, isLoopbackAddress, maskSecrets, resolveActivation, resolveStaticAsset, serializeEnvelope, startSse };
|
|
769
|
+
export { AUTH_COOKIE, type ActivationDecision, type ApiEnvelope, type BuiltInPanelId, type ConfigData, type ConfirmIssued, ConfirmationStore, type CpuClock, type CpuData, CpuProvider, type CpuUsageSnapshot, DEFAULT_LOG_SIZE, type EnvData, type EnvEntry, EnvProvider, type EventLoopClock, EventLoopLagProvider, type EventLoopSample, type GuardOptions, type GuardResult, type HealthCheck, type HealthCheckResult, type HealthData, HealthProvider, type HeapSnapshotData, type HeapSnapshotPanelData, HeapSnapshotProvider, type LogEntry, type LogLevel, type LogsData, LogsProvider, type MemoryData, MemoryProvider, type MetricsBucket, type MetricsData, MetricsProvider, type NodeUIConfig, type NodeUIMiddleware, type NodeUIOptions, type NodeUIProvider, type NodeUIServer, type OutgoingData, OutgoingProvider, type OutgoingRequestEntry, type PanelId, type ProviderContext, type ProviderError, ProviderRegistry, type ProviderResult, type RequestEntry, type RequestsData, RequestsProvider, type RequestsSummary, RingBuffer, type RouteEntry, type RouteStat, type RoutesData, RoutesProvider, SECRET_KEY_PATTERN, SECRET_MASKED, Sampler, type SamplerOptions, type SseStream, type StartupData, type StartupMark, StartupTracker, type StaticAsset, createGuard, createNodeUI, extractRoutes, hostnameFromHostHeader, interceptConsole, interceptOutgoing, isLoopbackAddress, isLoopbackHostname, maskSecretText, maskSecrets, matchesAddress, resolveActivation, resolveStaticAsset, serializeEnvelope, startSse, summarizeRequests };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { IncomingMessage, ServerResponse } from 'node:http';
|
|
1
2
|
import { Readable } from 'node:stream';
|
|
2
|
-
import { ServerResponse, IncomingMessage } from 'node:http';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Shared typed contract for the NodeUI developer console.
|
|
@@ -8,7 +8,12 @@ import { ServerResponse, IncomingMessage } from 'node:http';
|
|
|
8
8
|
* contract. Every provider returns a {@link ProviderResult}; every REST
|
|
9
9
|
* endpoint returns an {@link ApiEnvelope} which is the same shape.
|
|
10
10
|
*/
|
|
11
|
-
type
|
|
11
|
+
type BuiltInPanelId = 'health' | 'memory' | 'cpu' | 'event-loop' | 'heap-snapshot' | 'startup' | 'requests' | 'env' | 'routes' | 'logs' | 'metrics' | 'outgoing';
|
|
12
|
+
/**
|
|
13
|
+
* Panel identifier. Built-in panels are listed in {@link BuiltInPanelId};
|
|
14
|
+
* plugins may register any other string id (lowercase letters, digits, `-`).
|
|
15
|
+
*/
|
|
16
|
+
type PanelId = BuiltInPanelId | (string & {});
|
|
12
17
|
/** Resolved, validated console configuration. */
|
|
13
18
|
interface NodeUIConfig {
|
|
14
19
|
/** URL path prefix where the console and its API are served. Default `/nodeui`. */
|
|
@@ -35,6 +40,18 @@ interface NodeUIConfig {
|
|
|
35
40
|
confirmTtlMs: number;
|
|
36
41
|
/** Directory where heap snapshot files are written. */
|
|
37
42
|
heapSnapshotDir: string;
|
|
43
|
+
/** Extra `Host` header hostnames accepted besides loopback names. */
|
|
44
|
+
allowedHosts: string[];
|
|
45
|
+
/** Extra `Origin` values accepted for cross-origin calls. */
|
|
46
|
+
allowedOrigins: string[];
|
|
47
|
+
/** Extra remote IPs / IPv4 CIDRs accepted (e.g. Docker bridge `172.17.0.0/16`). */
|
|
48
|
+
allowedRemoteAddresses: string[];
|
|
49
|
+
/** Accept requests carrying `X-Forwarded-*` headers. */
|
|
50
|
+
trustProxy: boolean;
|
|
51
|
+
/** Whether a shared access token is required. */
|
|
52
|
+
authRequired: boolean;
|
|
53
|
+
/** Maximum concurrent live (SSE) streams. */
|
|
54
|
+
maxSseClients: number;
|
|
38
55
|
}
|
|
39
56
|
/** Context handed to every provider call. */
|
|
40
57
|
interface ProviderContext {
|
|
@@ -72,6 +89,8 @@ type ApiEnvelope<T> = ProviderResult<T>;
|
|
|
72
89
|
*/
|
|
73
90
|
interface NodeUIProvider<T = unknown> {
|
|
74
91
|
readonly id: PanelId;
|
|
92
|
+
/** Display label for plugin panels; defaults to the id. */
|
|
93
|
+
readonly title?: string;
|
|
75
94
|
start?(ctx: ProviderContext): void;
|
|
76
95
|
stop?(ctx: ProviderContext): void;
|
|
77
96
|
get(ctx: ProviderContext): ProviderResult<T> | Promise<ProviderResult<T>>;
|
|
@@ -99,6 +118,12 @@ interface CpuData {
|
|
|
99
118
|
totalPercent: number;
|
|
100
119
|
sampleAtMs: number;
|
|
101
120
|
}
|
|
121
|
+
interface HealthCheckResult {
|
|
122
|
+
name: string;
|
|
123
|
+
status: 'up' | 'down';
|
|
124
|
+
durationMs: number;
|
|
125
|
+
error?: string;
|
|
126
|
+
}
|
|
102
127
|
interface HealthData {
|
|
103
128
|
status: 'ok' | 'degraded' | 'critical' | 'unknown';
|
|
104
129
|
statusReason: string;
|
|
@@ -108,6 +133,8 @@ interface HealthData {
|
|
|
108
133
|
platform: string;
|
|
109
134
|
eventLoopLagMs: number | null;
|
|
110
135
|
memoryUsedPercent: number | null;
|
|
136
|
+
/** Results of user-registered dependency checks; empty when none. */
|
|
137
|
+
checks: HealthCheckResult[];
|
|
111
138
|
}
|
|
112
139
|
interface StartupMark {
|
|
113
140
|
name: string;
|
|
@@ -128,9 +155,37 @@ interface RequestEntry {
|
|
|
128
155
|
timestampMs: number;
|
|
129
156
|
ip: string;
|
|
130
157
|
}
|
|
158
|
+
interface RouteStat {
|
|
159
|
+
method: string;
|
|
160
|
+
path: string;
|
|
161
|
+
count: number;
|
|
162
|
+
avgMs: number;
|
|
163
|
+
p95Ms: number;
|
|
164
|
+
errors: number;
|
|
165
|
+
}
|
|
166
|
+
interface RequestsSummary {
|
|
167
|
+
/** Requests currently held in the ring buffer. */
|
|
168
|
+
count: number;
|
|
169
|
+
errors: number;
|
|
170
|
+
/** `errors / count` in 0..1 (5xx only). */
|
|
171
|
+
errorRate: number;
|
|
172
|
+
avgMs: number;
|
|
173
|
+
p50Ms: number;
|
|
174
|
+
p95Ms: number;
|
|
175
|
+
p99Ms: number;
|
|
176
|
+
byStatus: {
|
|
177
|
+
'2xx': number;
|
|
178
|
+
'3xx': number;
|
|
179
|
+
'4xx': number;
|
|
180
|
+
'5xx': number;
|
|
181
|
+
};
|
|
182
|
+
/** Busiest/slowest routes, slowest p95 first (at most 8). */
|
|
183
|
+
routes: RouteStat[];
|
|
184
|
+
}
|
|
131
185
|
interface RequestsData {
|
|
132
186
|
total: number;
|
|
133
187
|
entries: RequestEntry[];
|
|
188
|
+
summary: RequestsSummary;
|
|
134
189
|
}
|
|
135
190
|
interface EnvEntry {
|
|
136
191
|
key: string;
|
|
@@ -165,6 +220,21 @@ interface MetricsBucket {
|
|
|
165
220
|
interface MetricsData {
|
|
166
221
|
buckets: MetricsBucket[];
|
|
167
222
|
}
|
|
223
|
+
interface OutgoingRequestEntry {
|
|
224
|
+
id: number;
|
|
225
|
+
method: string;
|
|
226
|
+
/** Target as `host/path` (query string removed). */
|
|
227
|
+
url: string;
|
|
228
|
+
status: number | null;
|
|
229
|
+
durationMs: number;
|
|
230
|
+
timestampMs: number;
|
|
231
|
+
error?: string;
|
|
232
|
+
}
|
|
233
|
+
interface OutgoingData {
|
|
234
|
+
total: number;
|
|
235
|
+
failed: number;
|
|
236
|
+
entries: OutgoingRequestEntry[];
|
|
237
|
+
}
|
|
168
238
|
interface HeapSnapshotData {
|
|
169
239
|
fileName: string;
|
|
170
240
|
filePath: string;
|
|
@@ -194,6 +264,12 @@ interface ConfigData {
|
|
|
194
264
|
enabled: boolean;
|
|
195
265
|
pattern: string;
|
|
196
266
|
};
|
|
267
|
+
locked: boolean;
|
|
268
|
+
/** Plugin panel ids and titles, in registration order. */
|
|
269
|
+
plugins: Array<{
|
|
270
|
+
id: PanelId;
|
|
271
|
+
title: string;
|
|
272
|
+
}>;
|
|
197
273
|
}
|
|
198
274
|
|
|
199
275
|
/** Replacement value used when masking secret values. */
|
|
@@ -222,6 +298,12 @@ declare class RingBuffer<T> {
|
|
|
222
298
|
|
|
223
299
|
/** Keys whose values are redacted in any panel output. */
|
|
224
300
|
declare const SECRET_KEY_PATTERN: RegExp;
|
|
301
|
+
/**
|
|
302
|
+
* Redacts secrets embedded in free text: credentials in URLs
|
|
303
|
+
* (`postgres://user:pass@host`), bearer/basic tokens, JWTs and
|
|
304
|
+
* `password=...`-style pairs. Used for log lines and any string value.
|
|
305
|
+
*/
|
|
306
|
+
declare function maskSecretText(text: string): string;
|
|
225
307
|
interface ActivationDecision {
|
|
226
308
|
active: boolean;
|
|
227
309
|
reason: string;
|
|
@@ -234,12 +316,53 @@ interface ActivationDecision {
|
|
|
234
316
|
declare function resolveActivation(env: Record<string, string | undefined>): ActivationDecision;
|
|
235
317
|
/** True when the socket address is a loopback address. */
|
|
236
318
|
declare function isLoopbackAddress(address: string | undefined): boolean;
|
|
319
|
+
/**
|
|
320
|
+
* True when `address` matches one of `allowed`, each an exact IP or an IPv4
|
|
321
|
+
* CIDR (e.g. `172.17.0.0/16`, the default Docker bridge network).
|
|
322
|
+
*/
|
|
323
|
+
declare function matchesAddress(address: string | undefined, allowed: readonly string[]): boolean;
|
|
324
|
+
/** Extracts the lower-cased hostname from a `Host` header value (no port). */
|
|
325
|
+
declare function hostnameFromHostHeader(value: string | undefined): string | null;
|
|
326
|
+
/** True for the hostnames a browser uses to reach a loopback server. */
|
|
327
|
+
declare function isLoopbackHostname(hostname: string | null): boolean;
|
|
237
328
|
/**
|
|
238
329
|
* Deep-clone a value, replacing any value under a key matching the secret
|
|
239
|
-
* pattern with `[REDACTED]
|
|
330
|
+
* pattern with `[REDACTED]` and scrubbing secrets embedded in string values
|
|
331
|
+
* (URL credentials, bearer tokens, `password=...`). The input is not mutated.
|
|
240
332
|
*/
|
|
241
333
|
declare function maskSecrets<T>(value: T, pattern?: RegExp): T;
|
|
242
334
|
|
|
335
|
+
declare const AUTH_COOKIE = "nodeui_token";
|
|
336
|
+
interface GuardOptions {
|
|
337
|
+
/** Configured console host; a loopback value enables the strict checks. */
|
|
338
|
+
host: string;
|
|
339
|
+
/** Extra `Host` header hostnames accepted (e.g. `myapp.local`). */
|
|
340
|
+
allowedHosts: readonly string[];
|
|
341
|
+
/** Extra `Origin` values accepted for cross-origin calls. */
|
|
342
|
+
allowedOrigins: readonly string[];
|
|
343
|
+
/** Extra remote IPs / IPv4 CIDRs accepted (e.g. `172.17.0.0/16` for Docker). */
|
|
344
|
+
allowedRemoteAddresses: readonly string[];
|
|
345
|
+
/** Accept requests carrying `X-Forwarded-*` headers. Default false. */
|
|
346
|
+
trustProxy: boolean;
|
|
347
|
+
/** When set, every request must present this token. */
|
|
348
|
+
authToken?: string;
|
|
349
|
+
}
|
|
350
|
+
type GuardResult = {
|
|
351
|
+
ok: true;
|
|
352
|
+
setToken?: string;
|
|
353
|
+
} | {
|
|
354
|
+
ok: false;
|
|
355
|
+
status: number;
|
|
356
|
+
code: string;
|
|
357
|
+
message: string;
|
|
358
|
+
};
|
|
359
|
+
/**
|
|
360
|
+
* Builds the request guard for the console. It combines: loopback/remote
|
|
361
|
+
* address policy, `Host` validation (DNS-rebinding defence), `Origin`
|
|
362
|
+
* validation (cross-site request defence) and an optional shared token.
|
|
363
|
+
*/
|
|
364
|
+
declare function createGuard(options: GuardOptions): (req: IncomingMessage) => GuardResult;
|
|
365
|
+
|
|
243
366
|
/**
|
|
244
367
|
* Single-use, expiring confirmation nonces. Mutating actions (such as heap
|
|
245
368
|
* snapshots) require a nonce issued here, presented via the
|
|
@@ -247,8 +370,9 @@ declare function maskSecrets<T>(value: T, pattern?: RegExp): T;
|
|
|
247
370
|
*/
|
|
248
371
|
declare class ConfirmationStore {
|
|
249
372
|
private readonly ttlMs;
|
|
373
|
+
private readonly maxOutstanding;
|
|
250
374
|
private nonces;
|
|
251
|
-
constructor(ttlMs: number);
|
|
375
|
+
constructor(ttlMs: number, maxOutstanding?: number);
|
|
252
376
|
issue(): ConfirmIssued;
|
|
253
377
|
/** Consumes `nonce` and returns true only if it was valid and unexpired. */
|
|
254
378
|
consume(nonce: string): boolean;
|
|
@@ -381,12 +505,45 @@ declare class EventLoopLagProvider implements NodeUIProvider<EventLoopSample> {
|
|
|
381
505
|
};
|
|
382
506
|
}
|
|
383
507
|
|
|
384
|
-
/**
|
|
508
|
+
/**
|
|
509
|
+
* A dependency check (database ping, cache ping, ...). Resolve for healthy,
|
|
510
|
+
* reject/throw (or resolve `false`) for unhealthy.
|
|
511
|
+
*/
|
|
512
|
+
type HealthCheck = () => unknown | Promise<unknown>;
|
|
513
|
+
/** Derives process + event-loop health, plus optional dependency checks. */
|
|
385
514
|
declare class HealthProvider implements NodeUIProvider<HealthData> {
|
|
515
|
+
private readonly checks;
|
|
386
516
|
readonly id: "health";
|
|
387
|
-
|
|
517
|
+
private cache;
|
|
518
|
+
constructor(checks?: Record<string, HealthCheck>);
|
|
519
|
+
private runChecks;
|
|
520
|
+
get(ctx: ProviderContext): Promise<{
|
|
388
521
|
ok: true;
|
|
389
522
|
data: HealthData;
|
|
523
|
+
}>;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
type Recorder = (entry: Omit<OutgoingRequestEntry, 'id'>) => void;
|
|
527
|
+
/**
|
|
528
|
+
* Refcounted instrumentation of outgoing HTTP calls made via `http`,
|
|
529
|
+
* `https` (axios, got, node-fetch, ...) and global `fetch` (undici). The
|
|
530
|
+
* first consumer installs the hooks, the last release restores everything.
|
|
531
|
+
*/
|
|
532
|
+
declare function interceptOutgoing(record: Recorder): () => void;
|
|
533
|
+
/** Recent outgoing HTTP calls, recorded lazily while the panel is active. */
|
|
534
|
+
declare class OutgoingProvider implements NodeUIProvider<OutgoingData> {
|
|
535
|
+
readonly id: "outgoing";
|
|
536
|
+
private buffer;
|
|
537
|
+
private nextId;
|
|
538
|
+
private failed;
|
|
539
|
+
private release;
|
|
540
|
+
constructor(size: number);
|
|
541
|
+
start(): void;
|
|
542
|
+
stop(): void;
|
|
543
|
+
record(entry: Omit<OutgoingRequestEntry, 'id'>): void;
|
|
544
|
+
get(): {
|
|
545
|
+
ok: true;
|
|
546
|
+
data: OutgoingData;
|
|
390
547
|
};
|
|
391
548
|
}
|
|
392
549
|
|
|
@@ -405,6 +562,7 @@ declare class HeapSnapshotProvider implements NodeUIProvider<HeapSnapshotPanelDa
|
|
|
405
562
|
takeSnapshot(ctx: ProviderContext): Promise<ProviderResult<HeapSnapshotData>>;
|
|
406
563
|
}
|
|
407
564
|
|
|
565
|
+
declare function summarizeRequests(entries: readonly RequestEntry[]): RequestsSummary;
|
|
408
566
|
/** Fixed-size ring buffer of recent HTTP requests, recorded via middleware hook. */
|
|
409
567
|
declare class RequestsProvider implements NodeUIProvider<RequestsData> {
|
|
410
568
|
readonly id: "requests";
|
|
@@ -521,7 +679,7 @@ interface SseStream {
|
|
|
521
679
|
* Minimal Server-Sent Events writer over a raw HTTP response. Events are
|
|
522
680
|
* emitted as `data: <json>\n\n`; heartbeats as `:ping\n\n`.
|
|
523
681
|
*/
|
|
524
|
-
declare function startSse(res: ServerResponse): SseStream;
|
|
682
|
+
declare function startSse(res: ServerResponse, extraHeaders?: Record<string, string>): SseStream;
|
|
525
683
|
|
|
526
684
|
interface NodeUIOptions {
|
|
527
685
|
/** URL path prefix. Default `/nodeui` (or `NODEUI_PATH`). */
|
|
@@ -548,8 +706,32 @@ interface NodeUIOptions {
|
|
|
548
706
|
inactivityTimeoutMs?: number;
|
|
549
707
|
/** TTL for mutation confirmation nonces. Default 60000. */
|
|
550
708
|
confirmTtlMs?: number;
|
|
551
|
-
/** Directory for heap snapshot files. Defaults to the OS temp dir. */
|
|
709
|
+
/** Directory for heap snapshot files. Defaults to a private dir under the OS temp dir. */
|
|
552
710
|
heapSnapshotDir?: string;
|
|
711
|
+
/** Extra `Host` hostnames accepted besides loopback names (or `NODEUI_ALLOWED_HOSTS`, comma-separated). */
|
|
712
|
+
allowedHosts?: string[];
|
|
713
|
+
/** Extra `Origin` values accepted for cross-origin calls (or `NODEUI_ALLOWED_ORIGINS`). */
|
|
714
|
+
allowedOrigins?: string[];
|
|
715
|
+
/**
|
|
716
|
+
* Extra remote IPs / IPv4 CIDRs accepted (or `NODEUI_ALLOWED_REMOTE`). Use
|
|
717
|
+
* `['172.16.0.0/12']` to reach the console from the host when the app runs in Docker.
|
|
718
|
+
*/
|
|
719
|
+
allowedRemoteAddresses?: string[];
|
|
720
|
+
/** Accept requests carrying `X-Forwarded-*` headers (or `NODEUI_TRUST_PROXY=true`). Default false. */
|
|
721
|
+
trustProxy?: boolean;
|
|
722
|
+
/**
|
|
723
|
+
* Shared access token (or `NODEUI_TOKEN`). When set, open `/nodeui/?token=<token>` once;
|
|
724
|
+
* the browser then keeps an HttpOnly cookie. API clients may send `Authorization: Bearer`.
|
|
725
|
+
*/
|
|
726
|
+
authToken?: string;
|
|
727
|
+
/** Maximum concurrent live (SSE) streams. Default 10. */
|
|
728
|
+
maxSseClients?: number;
|
|
729
|
+
/** Custom panels; each provider needs a unique lowercase id, e.g. `queues`. */
|
|
730
|
+
plugins?: NodeUIProvider[];
|
|
731
|
+
/** Named dependency checks (database, cache, ...) surfaced in the health panel. */
|
|
732
|
+
healthChecks?: Record<string, HealthCheck>;
|
|
733
|
+
/** Max recorded outgoing HTTP calls. Default 200. */
|
|
734
|
+
outgoingLogSize?: number;
|
|
553
735
|
}
|
|
554
736
|
interface NodeUIServer {
|
|
555
737
|
readonly config: NodeUIConfig;
|
|
@@ -559,6 +741,11 @@ interface NodeUIServer {
|
|
|
559
741
|
middleware(): NodeUIMiddleware;
|
|
560
742
|
/** Direct request handling (no `next`); used for tests and adapters. */
|
|
561
743
|
handle(req: IncomingMessage, res: ServerResponse): Promise<void>;
|
|
744
|
+
/**
|
|
745
|
+
* Supplies the route list directly (frameworks other than Express, e.g.
|
|
746
|
+
* Fastify's `onRoute`). Takes precedence over Express router introspection.
|
|
747
|
+
*/
|
|
748
|
+
setRoutes(source: RouteEntry[] | (() => RouteEntry[])): void;
|
|
562
749
|
/** Records a bootstrap timing mark, e.g. `mark("listening")`. */
|
|
563
750
|
mark(name: string): void;
|
|
564
751
|
/** Whether a background-sampling provider is currently running. */
|
|
@@ -576,7 +763,7 @@ type NodeUIMiddleware = (req: IncomingMessage, res: ServerResponse, next: () =>
|
|
|
576
763
|
* Serializes an API envelope to JSON, applying secret masking so values
|
|
577
764
|
* under keys like `TOKEN`/`KEY`/`SECRET`/`PASSWORD` never reach the UI.
|
|
578
765
|
*/
|
|
579
|
-
declare function serializeEnvelope<T>(envelope: ApiEnvelope<T
|
|
766
|
+
declare function serializeEnvelope<T>(envelope: ApiEnvelope<T>, mask?: boolean): string;
|
|
580
767
|
declare function createNodeUI(options?: NodeUIOptions): NodeUIServer;
|
|
581
768
|
|
|
582
|
-
export { type ActivationDecision, type ApiEnvelope, type ConfigData, type ConfirmIssued, ConfirmationStore, type CpuClock, type CpuData, CpuProvider, type CpuUsageSnapshot, DEFAULT_LOG_SIZE, type EnvData, type EnvEntry, EnvProvider, type EventLoopClock, EventLoopLagProvider, type EventLoopSample, type HealthData, HealthProvider, type HeapSnapshotData, type HeapSnapshotPanelData, HeapSnapshotProvider, type LogEntry, type LogLevel, type LogsData, LogsProvider, type MemoryData, MemoryProvider, type MetricsBucket, type MetricsData, MetricsProvider, type NodeUIConfig, type NodeUIMiddleware, type NodeUIOptions, type NodeUIProvider, type NodeUIServer, type PanelId, type ProviderContext, type ProviderError, ProviderRegistry, type ProviderResult, type RequestEntry, type RequestsData, RequestsProvider, RingBuffer, type RouteEntry, type RoutesData, RoutesProvider, SECRET_KEY_PATTERN, SECRET_MASKED, Sampler, type SamplerOptions, type SseStream, type StartupData, type StartupMark, StartupTracker, type StaticAsset, createNodeUI, extractRoutes, interceptConsole, isLoopbackAddress, maskSecrets, resolveActivation, resolveStaticAsset, serializeEnvelope, startSse };
|
|
769
|
+
export { AUTH_COOKIE, type ActivationDecision, type ApiEnvelope, type BuiltInPanelId, type ConfigData, type ConfirmIssued, ConfirmationStore, type CpuClock, type CpuData, CpuProvider, type CpuUsageSnapshot, DEFAULT_LOG_SIZE, type EnvData, type EnvEntry, EnvProvider, type EventLoopClock, EventLoopLagProvider, type EventLoopSample, type GuardOptions, type GuardResult, type HealthCheck, type HealthCheckResult, type HealthData, HealthProvider, type HeapSnapshotData, type HeapSnapshotPanelData, HeapSnapshotProvider, type LogEntry, type LogLevel, type LogsData, LogsProvider, type MemoryData, MemoryProvider, type MetricsBucket, type MetricsData, MetricsProvider, type NodeUIConfig, type NodeUIMiddleware, type NodeUIOptions, type NodeUIProvider, type NodeUIServer, type OutgoingData, OutgoingProvider, type OutgoingRequestEntry, type PanelId, type ProviderContext, type ProviderError, ProviderRegistry, type ProviderResult, type RequestEntry, type RequestsData, RequestsProvider, type RequestsSummary, RingBuffer, type RouteEntry, type RouteStat, type RoutesData, RoutesProvider, SECRET_KEY_PATTERN, SECRET_MASKED, Sampler, type SamplerOptions, type SseStream, type StartupData, type StartupMark, StartupTracker, type StaticAsset, createGuard, createNodeUI, extractRoutes, hostnameFromHostHeader, interceptConsole, interceptOutgoing, isLoopbackAddress, isLoopbackHostname, maskSecretText, maskSecrets, matchesAddress, resolveActivation, resolveStaticAsset, serializeEnvelope, startSse, summarizeRequests };
|