@zintrust/trace 0.4.75
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 +288 -0
- package/dist/build-manifest.json +365 -0
- package/dist/cli-register.d.ts +9 -0
- package/dist/cli-register.js +32 -0
- package/dist/config.d.ts +9 -0
- package/dist/config.js +38 -0
- package/dist/context.d.ts +18 -0
- package/dist/context.js +86 -0
- package/dist/dashboard/handlers.d.ts +15 -0
- package/dist/dashboard/handlers.js +179 -0
- package/dist/dashboard/routes.d.ts +19 -0
- package/dist/dashboard/routes.js +50 -0
- package/dist/dashboard/ui.d.ts +2 -0
- package/dist/dashboard/ui.js +870 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.js +50 -0
- package/dist/migrations/20260331000001_create_zin_debugger_entries_table.d.ts +10 -0
- package/dist/migrations/20260331000001_create_zin_debugger_entries_table.js +28 -0
- package/dist/migrations/20260331000002_create_zin_debugger_entries_tags_table.d.ts +10 -0
- package/dist/migrations/20260331000002_create_zin_debugger_entries_tags_table.js +21 -0
- package/dist/migrations/20260331000003_create_zin_debugger_monitoring_table.d.ts +10 -0
- package/dist/migrations/20260331000003_create_zin_debugger_monitoring_table.js +17 -0
- package/dist/migrations/index.d.ts +6 -0
- package/dist/migrations/index.js +4 -0
- package/dist/plugin.d.ts +1 -0
- package/dist/plugin.js +3 -0
- package/dist/register.d.ts +1 -0
- package/dist/register.js +140 -0
- package/dist/storage/DebuggerStorage.d.ts +13 -0
- package/dist/storage/DebuggerStorage.js +195 -0
- package/dist/storage/TraceStorage.d.ts +13 -0
- package/dist/storage/TraceStorage.js +195 -0
- package/dist/storage/index.d.ts +2 -0
- package/dist/storage/index.js +1 -0
- package/dist/types.d.ts +270 -0
- package/dist/types.js +25 -0
- package/dist/ui.d.ts +8 -0
- package/dist/ui.js +7 -0
- package/dist/utils/authTag.d.ts +5 -0
- package/dist/utils/authTag.js +18 -0
- package/dist/utils/familyHash.d.ts +1 -0
- package/dist/utils/familyHash.js +8 -0
- package/dist/utils/redact.d.ts +6 -0
- package/dist/utils/redact.js +49 -0
- package/dist/utils/requestFilter.d.ts +4 -0
- package/dist/utils/requestFilter.js +26 -0
- package/dist/utils/stackFrame.d.ts +6 -0
- package/dist/utils/stackFrame.js +38 -0
- package/dist/watchers/AuthWatcher.d.ts +6 -0
- package/dist/watchers/AuthWatcher.js +49 -0
- package/dist/watchers/BatchWatcher.d.ts +6 -0
- package/dist/watchers/BatchWatcher.js +46 -0
- package/dist/watchers/CacheWatcher.d.ts +6 -0
- package/dist/watchers/CacheWatcher.js +51 -0
- package/dist/watchers/CommandWatcher.d.ts +6 -0
- package/dist/watchers/CommandWatcher.js +49 -0
- package/dist/watchers/DumpWatcher.d.ts +7 -0
- package/dist/watchers/DumpWatcher.js +41 -0
- package/dist/watchers/EventWatcher.d.ts +6 -0
- package/dist/watchers/EventWatcher.js +42 -0
- package/dist/watchers/ExceptionWatcher.d.ts +4 -0
- package/dist/watchers/ExceptionWatcher.js +103 -0
- package/dist/watchers/GateWatcher.d.ts +6 -0
- package/dist/watchers/GateWatcher.js +45 -0
- package/dist/watchers/HttpClientWatcher.d.ts +6 -0
- package/dist/watchers/HttpClientWatcher.js +50 -0
- package/dist/watchers/HttpWatcher.d.ts +2 -0
- package/dist/watchers/HttpWatcher.js +71 -0
- package/dist/watchers/JobWatcher.d.ts +10 -0
- package/dist/watchers/JobWatcher.js +108 -0
- package/dist/watchers/LogWatcher.d.ts +2 -0
- package/dist/watchers/LogWatcher.js +50 -0
- package/dist/watchers/MailWatcher.d.ts +6 -0
- package/dist/watchers/MailWatcher.js +45 -0
- package/dist/watchers/MiddlewareWatcher.d.ts +6 -0
- package/dist/watchers/MiddlewareWatcher.js +41 -0
- package/dist/watchers/ModelWatcher.d.ts +6 -0
- package/dist/watchers/ModelWatcher.js +42 -0
- package/dist/watchers/NotificationWatcher.d.ts +6 -0
- package/dist/watchers/NotificationWatcher.js +42 -0
- package/dist/watchers/QueryWatcher.d.ts +2 -0
- package/dist/watchers/QueryWatcher.js +72 -0
- package/dist/watchers/RedisWatcher.d.ts +7 -0
- package/dist/watchers/RedisWatcher.js +38 -0
- package/dist/watchers/ScheduleWatcher.d.ts +6 -0
- package/dist/watchers/ScheduleWatcher.js +46 -0
- package/dist/watchers/ViewWatcher.d.ts +6 -0
- package/dist/watchers/ViewWatcher.js +36 -0
- package/package.json +59 -0
- package/src/cli-register.ts +63 -0
- package/src/config.ts +46 -0
- package/src/context.ts +101 -0
- package/src/dashboard/handlers.ts +197 -0
- package/src/dashboard/routes.ts +101 -0
- package/src/dashboard/ui.ts +879 -0
- package/src/dashboard/zintrust-debuger.svg +30 -0
- package/src/index.ts +88 -0
- package/src/plugin.ts +9 -0
- package/src/register.ts +219 -0
- package/src/storage/TraceStorage.ts +306 -0
- package/src/storage/index.ts +2 -0
- package/src/types.ts +317 -0
- package/src/ui.ts +9 -0
- package/src/utils/authTag.ts +20 -0
- package/src/utils/familyHash.ts +8 -0
- package/src/utils/redact.ts +64 -0
- package/src/utils/requestFilter.ts +33 -0
- package/src/utils/stackFrame.ts +44 -0
- package/src/watchers/AuthWatcher.ts +50 -0
- package/src/watchers/BatchWatcher.ts +52 -0
- package/src/watchers/CacheWatcher.ts +58 -0
- package/src/watchers/CommandWatcher.ts +55 -0
- package/src/watchers/DumpWatcher.ts +42 -0
- package/src/watchers/EventWatcher.ts +43 -0
- package/src/watchers/ExceptionWatcher.ts +114 -0
- package/src/watchers/GateWatcher.ts +50 -0
- package/src/watchers/HttpClientWatcher.ts +56 -0
- package/src/watchers/HttpWatcher.ts +94 -0
- package/src/watchers/JobWatcher.ts +121 -0
- package/src/watchers/LogWatcher.ts +61 -0
- package/src/watchers/MailWatcher.ts +47 -0
- package/src/watchers/MiddlewareWatcher.ts +42 -0
- package/src/watchers/ModelWatcher.ts +48 -0
- package/src/watchers/NotificationWatcher.ts +43 -0
- package/src/watchers/QueryWatcher.ts +85 -0
- package/src/watchers/RedisWatcher.ts +39 -0
- package/src/watchers/ScheduleWatcher.ts +54 -0
- package/src/watchers/ViewWatcher.ts +37 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
type Registry = {
|
|
2
|
+
register: (id: string, provider: CliCommandProvider) => void;
|
|
3
|
+
};
|
|
4
|
+
|
|
5
|
+
type CliCommandProvider = {
|
|
6
|
+
getCommand: () => unknown;
|
|
7
|
+
name?: string;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
type TraceCommandsModule = {
|
|
11
|
+
TraceCommands: {
|
|
12
|
+
createTracePruneProvider: () => CliCommandProvider;
|
|
13
|
+
createTraceClearProvider: () => CliCommandProvider;
|
|
14
|
+
createTraceStatusProvider: () => CliCommandProvider;
|
|
15
|
+
createTraceMigrateProvider: () => CliCommandProvider;
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const commandModule = (await import('@zintrust/core/cli')) as unknown as TraceCommandsModule;
|
|
20
|
+
|
|
21
|
+
const getTraceProviders = (): Array<[string, CliCommandProvider]> => {
|
|
22
|
+
const { TraceCommands } = commandModule;
|
|
23
|
+
|
|
24
|
+
return [
|
|
25
|
+
['trace:prune', TraceCommands.createTracePruneProvider()],
|
|
26
|
+
['trace:clear', TraceCommands.createTraceClearProvider()],
|
|
27
|
+
['trace:status', TraceCommands.createTraceStatusProvider()],
|
|
28
|
+
['migrate:trace', TraceCommands.createTraceMigrateProvider()],
|
|
29
|
+
];
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export function registerTraceCliCommands(registry: Registry): void {
|
|
33
|
+
for (const [id, provider] of getTraceProviders()) {
|
|
34
|
+
registry.register(id, provider);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
type GlobalWithRegistry = {
|
|
39
|
+
__zintrust_cli_command_registry__?: Map<string, CliCommandProvider>;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const globalWithRegistry = globalThis as unknown as GlobalWithRegistry;
|
|
43
|
+
const globalRegistry =
|
|
44
|
+
globalWithRegistry.__zintrust_cli_command_registry__ ??
|
|
45
|
+
(globalWithRegistry.__zintrust_cli_command_registry__ = new Map<string, CliCommandProvider>());
|
|
46
|
+
|
|
47
|
+
registerTraceCliCommands({
|
|
48
|
+
register: (id, provider) => {
|
|
49
|
+
globalRegistry.set(id, provider);
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
const coreCli = (await import('@zintrust/core/cli')) as unknown as {
|
|
55
|
+
OptionalCliCommandRegistry?: Registry;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
if (coreCli.OptionalCliCommandRegistry !== undefined) {
|
|
59
|
+
registerTraceCliCommands(coreCli.OptionalCliCommandRegistry);
|
|
60
|
+
}
|
|
61
|
+
} catch {
|
|
62
|
+
// no-op
|
|
63
|
+
}
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TraceConfig — defaults and merge helper for @zintrust/trace
|
|
3
|
+
*/
|
|
4
|
+
import type { ITraceConfig, TraceConfigOverrides } from './types';
|
|
5
|
+
|
|
6
|
+
const DEFAULTS: ITraceConfig = Object.freeze({
|
|
7
|
+
enabled: false,
|
|
8
|
+
connection: undefined,
|
|
9
|
+
pruneAfterHours: 24,
|
|
10
|
+
ignoreRoutes: ['/trace', '/health', '/ping'],
|
|
11
|
+
slowQueryThreshold: 100,
|
|
12
|
+
logMinLevel: 'info',
|
|
13
|
+
watchers: {},
|
|
14
|
+
redaction: {
|
|
15
|
+
headers: ['authorization', 'cookie', 'x-api-key', 'x-auth-token'],
|
|
16
|
+
body: ['password', 'token', 'secret', 'apiKey', 'api_key', 'jwt', 'bearer'],
|
|
17
|
+
query: [],
|
|
18
|
+
},
|
|
19
|
+
} satisfies ITraceConfig);
|
|
20
|
+
|
|
21
|
+
const isWatcherEnabled = (config: ITraceConfig, key: keyof ITraceConfig['watchers']): boolean => {
|
|
22
|
+
const override = config.watchers[key];
|
|
23
|
+
return override !== false; // undefined = enabled by default; explicit false = disabled
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const TraceConfig = Object.freeze({
|
|
27
|
+
defaults(): ITraceConfig {
|
|
28
|
+
return DEFAULTS;
|
|
29
|
+
},
|
|
30
|
+
|
|
31
|
+
merge(overrides?: TraceConfigOverrides): ITraceConfig {
|
|
32
|
+
if (overrides === undefined || overrides === null) return DEFAULTS;
|
|
33
|
+
return Object.freeze({
|
|
34
|
+
...DEFAULTS,
|
|
35
|
+
...overrides,
|
|
36
|
+
watchers: { ...DEFAULTS.watchers, ...(overrides.watchers ?? {}) },
|
|
37
|
+
redaction: {
|
|
38
|
+
...DEFAULTS.redaction,
|
|
39
|
+
...(overrides.redaction ?? {}),
|
|
40
|
+
},
|
|
41
|
+
ignoreRoutes: overrides.ignoreRoutes ?? DEFAULTS.ignoreRoutes,
|
|
42
|
+
});
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
isWatcherEnabled,
|
|
46
|
+
});
|
package/src/context.ts
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TraceContext — sealed namespace for batch_id, userId, hostname, and memory.
|
|
3
|
+
* Piggybacks on RequestContext (already available in core) — no new ALS store.
|
|
4
|
+
*/
|
|
5
|
+
type RequestContextProvider = {
|
|
6
|
+
current?: () => unknown;
|
|
7
|
+
peek?: () => unknown;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
// Lazy reference to ZinTrust RequestContext — typed as unknown to stay runtime-agnostic.
|
|
11
|
+
let _reqCtx: RequestContextProvider | undefined;
|
|
12
|
+
|
|
13
|
+
const getRequestContext = (): RequestContextProvider | undefined => {
|
|
14
|
+
return _reqCtx;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const setRequestContextImpl = (impl: RequestContextProvider): void => {
|
|
18
|
+
_reqCtx = impl;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const isPromiseLike = (value: unknown): value is PromiseLike<unknown> => {
|
|
22
|
+
return typeof value === 'object' && value !== null && 'then' in value;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const getCurrentContext = (): Record<string, unknown> | undefined => {
|
|
26
|
+
const provider = getRequestContext();
|
|
27
|
+
if (!provider) return undefined;
|
|
28
|
+
|
|
29
|
+
let currentValue: unknown;
|
|
30
|
+
|
|
31
|
+
if (typeof provider.peek === 'function') {
|
|
32
|
+
currentValue = provider.peek();
|
|
33
|
+
} else if (typeof provider.current === 'function') {
|
|
34
|
+
currentValue = provider.current();
|
|
35
|
+
} else {
|
|
36
|
+
currentValue = undefined;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (isPromiseLike(currentValue)) return undefined;
|
|
40
|
+
if (typeof currentValue !== 'object' || currentValue === null) return undefined;
|
|
41
|
+
|
|
42
|
+
return currentValue as Record<string, unknown>;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const getContextString = (key: 'traceId' | 'userId' | 'path'): string | undefined => {
|
|
46
|
+
const value = getCurrentContext()?.[key];
|
|
47
|
+
if (typeof value === 'string' && value.trim() !== '') return value;
|
|
48
|
+
if (typeof value === 'number') return String(value);
|
|
49
|
+
return undefined;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const getBatchId = (): string => {
|
|
53
|
+
return getContextString('traceId') ?? crypto.randomUUID();
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const getUserId = (): string | undefined => {
|
|
57
|
+
return getContextString('userId');
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const getRequestPath = (): string | undefined => {
|
|
61
|
+
return getContextString('path');
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const getHostname = (): string => {
|
|
65
|
+
// Workers do not expose `os` or `process` — return 'worker' as fallback.
|
|
66
|
+
if (typeof process !== 'undefined' && typeof process.env === 'object') {
|
|
67
|
+
try {
|
|
68
|
+
// Dynamic import avoids the need for a node-singletons wrapper at the type level.
|
|
69
|
+
// Hostname is non-critical; we fall back gracefully.
|
|
70
|
+
const hostname = (process.env as Record<string, string | undefined>)['HOSTNAME'];
|
|
71
|
+
if (typeof hostname === 'string' && hostname.length > 0) return hostname;
|
|
72
|
+
} catch {
|
|
73
|
+
// fall through
|
|
74
|
+
}
|
|
75
|
+
return 'node';
|
|
76
|
+
}
|
|
77
|
+
return 'worker';
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const getMemory = (): number | null => {
|
|
81
|
+
if (typeof process !== 'undefined' && typeof process.memoryUsage === 'function') {
|
|
82
|
+
try {
|
|
83
|
+
return Math.round(process.memoryUsage().heapUsed / 1024 / 1024);
|
|
84
|
+
} catch {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return null;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const now = (): number => Date.now();
|
|
92
|
+
|
|
93
|
+
export const TraceContext = Object.freeze({
|
|
94
|
+
getBatchId,
|
|
95
|
+
getUserId,
|
|
96
|
+
getRequestPath,
|
|
97
|
+
getHostname,
|
|
98
|
+
getMemory,
|
|
99
|
+
now,
|
|
100
|
+
setRequestContextImpl,
|
|
101
|
+
});
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TraceDashboard handlers — pure handler functions wired to ITraceStorage.
|
|
3
|
+
* No auth in this layer — caller mounts middleware as needed.
|
|
4
|
+
*/
|
|
5
|
+
import type { IRequest, IResponse } from '@zintrust/core';
|
|
6
|
+
import type { EntryTypeValue, ITraceStorage } from '../types';
|
|
7
|
+
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
// Storage holder (set once from routes.ts)
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
|
|
12
|
+
let _storage: ITraceStorage | null = null;
|
|
13
|
+
|
|
14
|
+
export const setHandlerStorage = (s: ITraceStorage): void => {
|
|
15
|
+
_storage = s;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
// Helpers
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
|
|
22
|
+
const requireStorage = (res: IResponse): boolean => {
|
|
23
|
+
if (_storage) {
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
res.setStatus(503).json({ error: 'Trace not initialised' });
|
|
28
|
+
return false;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const getStorage = (res: IResponse): ITraceStorage | null => {
|
|
32
|
+
if (requireStorage(res)) {
|
|
33
|
+
return _storage;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return null;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const qp = (req: IRequest, key: string): string | undefined => {
|
|
40
|
+
const v = req.getQueryParam(key);
|
|
41
|
+
return Array.isArray(v) ? v[0] : v;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const qpInt = (req: IRequest, key: string, fallback: number): number => {
|
|
45
|
+
const raw = qp(req, key);
|
|
46
|
+
const n = typeof raw === 'string' ? Number.parseInt(raw, 10) : Number.NaN;
|
|
47
|
+
return Number.isNaN(n) ? fallback : n;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const getNumericQueryParam = (req: IRequest, key: string): number | undefined => {
|
|
51
|
+
const raw = qp(req, key);
|
|
52
|
+
if (typeof raw === 'string') {
|
|
53
|
+
const parsed = Number(raw);
|
|
54
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return undefined;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
// Entry handlers
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
|
|
64
|
+
export async function listEntries(req: IRequest, res: IResponse): Promise<void> {
|
|
65
|
+
const storage = getStorage(res);
|
|
66
|
+
if (storage !== null) {
|
|
67
|
+
const opts = {
|
|
68
|
+
type: qp(req, 'type') as EntryTypeValue | undefined,
|
|
69
|
+
tag: qp(req, 'tag'),
|
|
70
|
+
batchId: qp(req, 'batchId'),
|
|
71
|
+
from: getNumericQueryParam(req, 'from'),
|
|
72
|
+
to: getNumericQueryParam(req, 'to'),
|
|
73
|
+
page: qpInt(req, 'page', 1),
|
|
74
|
+
perPage: Math.min(qpInt(req, 'perPage', 50), 200),
|
|
75
|
+
};
|
|
76
|
+
try {
|
|
77
|
+
const result = await storage.queryEntries(opts);
|
|
78
|
+
res.json({ ok: true, ...result, page: opts.page, perPage: opts.perPage });
|
|
79
|
+
} catch (err) {
|
|
80
|
+
res.setStatus(500).json({ error: (err as Error).message });
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export async function getEntry(req: IRequest, res: IResponse): Promise<void> {
|
|
86
|
+
const storage = getStorage(res);
|
|
87
|
+
if (storage === null) return;
|
|
88
|
+
const uuid = req.getParam('uuid');
|
|
89
|
+
if (uuid) {
|
|
90
|
+
try {
|
|
91
|
+
const entry = await storage.getEntry(uuid);
|
|
92
|
+
if (entry) {
|
|
93
|
+
res.json({ ok: true, entry });
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
res.setStatus(404).json({ error: 'Not found' });
|
|
98
|
+
return;
|
|
99
|
+
} catch (err) {
|
|
100
|
+
res.setStatus(500).json({ error: (err as Error).message });
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
res.setStatus(400).json({ error: 'uuid required' });
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export async function getBatch(req: IRequest, res: IResponse): Promise<void> {
|
|
109
|
+
const storage = getStorage(res);
|
|
110
|
+
if (storage === null) return;
|
|
111
|
+
const batchId = req.getParam('batchId');
|
|
112
|
+
if (batchId) {
|
|
113
|
+
try {
|
|
114
|
+
const entries = await storage.getBatch(batchId);
|
|
115
|
+
res.json({ ok: true, entries });
|
|
116
|
+
return;
|
|
117
|
+
} catch (err) {
|
|
118
|
+
res.setStatus(500).json({ error: (err as Error).message });
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
res.setStatus(400).json({ error: 'batchId required' });
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export async function getStats(_req: IRequest, res: IResponse): Promise<void> {
|
|
127
|
+
const storage = getStorage(res);
|
|
128
|
+
if (storage === null) return;
|
|
129
|
+
try {
|
|
130
|
+
const stats = await storage.stats();
|
|
131
|
+
res.json({ ok: true, stats });
|
|
132
|
+
} catch (err) {
|
|
133
|
+
res.setStatus(500).json({ error: (err as Error).message });
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export async function clearEntries(_req: IRequest, res: IResponse): Promise<void> {
|
|
138
|
+
const storage = getStorage(res);
|
|
139
|
+
if (storage === null) return;
|
|
140
|
+
try {
|
|
141
|
+
await storage.clear();
|
|
142
|
+
res.json({ ok: true });
|
|
143
|
+
} catch (err) {
|
|
144
|
+
res.setStatus(500).json({ error: (err as Error).message });
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// ---------------------------------------------------------------------------
|
|
149
|
+
// Monitoring handlers
|
|
150
|
+
// ---------------------------------------------------------------------------
|
|
151
|
+
|
|
152
|
+
export async function getMonitoring(_req: IRequest, res: IResponse): Promise<void> {
|
|
153
|
+
const storage = getStorage(res);
|
|
154
|
+
if (storage === null) return;
|
|
155
|
+
try {
|
|
156
|
+
const tags = await storage.getMonitoring();
|
|
157
|
+
res.json({ ok: true, tags });
|
|
158
|
+
} catch (err) {
|
|
159
|
+
res.setStatus(500).json({ error: (err as Error).message });
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export async function addMonitoring(req: IRequest, res: IResponse): Promise<void> {
|
|
164
|
+
const storage = getStorage(res);
|
|
165
|
+
if (storage === null) return;
|
|
166
|
+
const tag = req.getParam('tag');
|
|
167
|
+
if (tag) {
|
|
168
|
+
try {
|
|
169
|
+
await storage.addMonitoring(tag);
|
|
170
|
+
res.json({ ok: true });
|
|
171
|
+
return;
|
|
172
|
+
} catch (err) {
|
|
173
|
+
res.setStatus(500).json({ error: (err as Error).message });
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
res.setStatus(400).json({ error: 'tag required' });
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export async function removeMonitoring(req: IRequest, res: IResponse): Promise<void> {
|
|
182
|
+
const storage = getStorage(res);
|
|
183
|
+
if (storage === null) return;
|
|
184
|
+
const tag = req.getParam('tag');
|
|
185
|
+
if (tag) {
|
|
186
|
+
try {
|
|
187
|
+
await storage.removeMonitoring(tag);
|
|
188
|
+
res.json({ ok: true });
|
|
189
|
+
return;
|
|
190
|
+
} catch (err) {
|
|
191
|
+
res.setStatus(500).json({ error: (err as Error).message });
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
res.setStatus(400).json({ error: 'tag required' });
|
|
197
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dashboard route registrar for @zintrust/trace.
|
|
3
|
+
* Mounts the SPA + all REST API endpoints under the configured basePath.
|
|
4
|
+
* Auth is NOT applied here — callers add middleware via routeOptions.
|
|
5
|
+
*/
|
|
6
|
+
import { appConfig, Router, useDatabase, type IRouter, type RouteOptions } from '@zintrust/core';
|
|
7
|
+
import { TraceConfig } from '../config';
|
|
8
|
+
import { TraceStorage } from '../storage';
|
|
9
|
+
import type { ITraceStorage } from '../types';
|
|
10
|
+
import {
|
|
11
|
+
addMonitoring,
|
|
12
|
+
clearEntries,
|
|
13
|
+
getBatch,
|
|
14
|
+
getEntry,
|
|
15
|
+
getMonitoring,
|
|
16
|
+
getStats,
|
|
17
|
+
listEntries,
|
|
18
|
+
removeMonitoring,
|
|
19
|
+
setHandlerStorage,
|
|
20
|
+
} from './handlers';
|
|
21
|
+
import { buildDashboardHtml } from './ui';
|
|
22
|
+
|
|
23
|
+
export type TraceDashboardOptions = {
|
|
24
|
+
/** Base path for the dashboard, e.g. '/trace'. Defaults to '/trace'. */
|
|
25
|
+
basePath?: string;
|
|
26
|
+
/** Optional ZinTrust middleware names to apply to all routes. */
|
|
27
|
+
middleware?: ReadonlyArray<string>;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export type TraceDashboardRegistrationOptions = TraceDashboardOptions & {
|
|
31
|
+
/** Optional trace storage connection override. Defaults to TraceConfig / runtime default. */
|
|
32
|
+
connectionName?: string;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const resolveDashboardConnectionName = (connectionName?: string): string | undefined => {
|
|
36
|
+
const explicitConnection = connectionName?.trim();
|
|
37
|
+
if (explicitConnection !== undefined && explicitConnection !== '') {
|
|
38
|
+
return explicitConnection;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const configuredConnection = TraceConfig.merge().connection?.trim();
|
|
42
|
+
return configuredConnection === undefined || configuredConnection === ''
|
|
43
|
+
? undefined
|
|
44
|
+
: configuredConnection;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export const registerTraceRoutes = (
|
|
48
|
+
router: IRouter,
|
|
49
|
+
storage: ITraceStorage,
|
|
50
|
+
options: TraceDashboardOptions = {}
|
|
51
|
+
): void => {
|
|
52
|
+
setHandlerStorage(storage);
|
|
53
|
+
|
|
54
|
+
const base = options.basePath ?? '/trace';
|
|
55
|
+
const routeOptions: RouteOptions | undefined =
|
|
56
|
+
(options.middleware?.length ?? 0) > 0
|
|
57
|
+
? ({ middleware: options.middleware } as RouteOptions)
|
|
58
|
+
: undefined;
|
|
59
|
+
|
|
60
|
+
// SPA shell
|
|
61
|
+
Router.get(
|
|
62
|
+
router,
|
|
63
|
+
base,
|
|
64
|
+
(_req, res) => {
|
|
65
|
+
res.html(buildDashboardHtml(base, appConfig.name));
|
|
66
|
+
},
|
|
67
|
+
routeOptions
|
|
68
|
+
);
|
|
69
|
+
// Serve the SPA for any /<basePath>/* sub-path (client-side routing)
|
|
70
|
+
Router.get(
|
|
71
|
+
router,
|
|
72
|
+
`${base}/*`,
|
|
73
|
+
(_req, res) => {
|
|
74
|
+
res.html(buildDashboardHtml(base, appConfig.name));
|
|
75
|
+
},
|
|
76
|
+
routeOptions
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
// REST API
|
|
80
|
+
Router.group(router, `${base}/api`, (r: IRouter) => {
|
|
81
|
+
Router.get(r, '/entries', listEntries, routeOptions);
|
|
82
|
+
Router.get(r, '/entries/:uuid', getEntry, routeOptions);
|
|
83
|
+
Router.del(r, '/entries', clearEntries, routeOptions);
|
|
84
|
+
Router.get(r, '/batch/:batchId', getBatch, routeOptions);
|
|
85
|
+
Router.get(r, '/stats', getStats, routeOptions);
|
|
86
|
+
Router.get(r, '/monitoring', getMonitoring, routeOptions);
|
|
87
|
+
Router.post(r, '/monitoring/:tag', addMonitoring, routeOptions);
|
|
88
|
+
Router.del(r, '/monitoring/:tag', removeMonitoring, routeOptions);
|
|
89
|
+
});
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
export const registerTraceDashboard = (
|
|
93
|
+
router: IRouter,
|
|
94
|
+
options: TraceDashboardRegistrationOptions = {}
|
|
95
|
+
): void => {
|
|
96
|
+
const storage = TraceStorage.resolveStorage(
|
|
97
|
+
useDatabase(undefined, resolveDashboardConnectionName(options.connectionName))
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
registerTraceRoutes(router, storage, options);
|
|
101
|
+
};
|