adonisjs-server-stats 1.6.1 → 1.6.2
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/dist/src/collectors/app_collector.d.ts.map +1 -1
- package/dist/src/collectors/app_collector.js +2 -1
- package/dist/src/collectors/auto_detect.d.ts.map +1 -1
- package/dist/src/collectors/auto_detect.js +7 -3
- package/dist/src/collectors/db_pool_collector.d.ts.map +1 -1
- package/dist/src/collectors/db_pool_collector.js +2 -1
- package/dist/src/collectors/http_collector.d.ts +4 -3
- package/dist/src/collectors/http_collector.d.ts.map +1 -1
- package/dist/src/collectors/http_collector.js +3 -5
- package/dist/src/collectors/redis_collector.d.ts.map +1 -1
- package/dist/src/collectors/redis_collector.js +2 -1
- package/dist/src/controller/debug_controller.d.ts +5 -0
- package/dist/src/controller/debug_controller.d.ts.map +1 -1
- package/dist/src/controller/debug_controller.js +24 -1
- package/dist/src/dashboard/chart_aggregator.d.ts.map +1 -1
- package/dist/src/dashboard/chart_aggregator.js +3 -2
- package/dist/src/dashboard/dashboard_store.d.ts.map +1 -1
- package/dist/src/dashboard/dashboard_store.js +47 -11
- package/dist/src/dashboard/migrator.d.ts +5 -0
- package/dist/src/dashboard/migrator.d.ts.map +1 -1
- package/dist/src/dashboard/migrator.js +44 -9
- package/dist/src/data/data_access.d.ts +4 -2
- package/dist/src/data/data_access.d.ts.map +1 -1
- package/dist/src/data/data_access.js +8 -3
- package/dist/src/define_config.d.ts.map +1 -1
- package/dist/src/define_config.js +7 -6
- package/dist/src/edge/client-vue/dashboard.js +1 -1
- package/dist/src/edge/client-vue/debug-panel-deferred.js +1 -1
- package/dist/src/edge/client-vue/debug-panel.js +1 -1
- package/dist/src/middleware/request_tracking_middleware.d.ts.map +1 -1
- package/dist/src/middleware/request_tracking_middleware.js +7 -0
- package/dist/src/provider/server_stats_provider.d.ts +9 -0
- package/dist/src/provider/server_stats_provider.d.ts.map +1 -1
- package/dist/src/provider/server_stats_provider.js +196 -59
- package/dist/src/utils/app_import.d.ts +23 -0
- package/dist/src/utils/app_import.d.ts.map +1 -0
- package/dist/src/utils/app_import.js +44 -0
- package/dist/vue/{CacheSection-CkrIB4-j.js → CacheSection-C788Yfai.js} +1 -1
- package/dist/vue/{ConfigSection-gulpOiq1.js → ConfigSection-CRzYxqW2.js} +1 -1
- package/dist/vue/{CustomPaneTab-J57ED_bh.js → CustomPaneTab-BJxT5Dp7.js} +33 -33
- package/dist/vue/{EmailsSection-BlKvQDx8.js → EmailsSection-C8JFMtW7.js} +1 -1
- package/dist/vue/{EventsSection-BdzQvIVJ.js → EventsSection-C4wXUgxG.js} +1 -1
- package/dist/vue/{JobsSection-DOzuMrG3.js → JobsSection-CsKWTjgN.js} +1 -1
- package/dist/vue/{LogsSection-CNN4y92u.js → LogsSection-BFVjSZ24.js} +12 -12
- package/dist/vue/{LogsTab-CJerb22r.js → LogsTab-DpEQ7euu.js} +17 -17
- package/dist/vue/{OverviewSection-SITNR_dA.js → OverviewSection-CbMdAido.js} +1 -1
- package/dist/vue/{QueriesSection-BAebAHkD.js → QueriesSection-BPiv7u3r.js} +1 -1
- package/dist/vue/{RequestsSection-CIR0IX39.js → RequestsSection-LtImH4rD.js} +1 -1
- package/dist/vue/{RoutesSection-j1U2oa0g.js → RoutesSection-CrxOxmzx.js} +1 -1
- package/dist/vue/{TimelineSection-Dw980UPg.js → TimelineSection-DLxMW2J_.js} +1 -1
- package/dist/vue/{index-COgsk_nv.js → index-qCQpBftQ.js} +2 -2
- package/dist/vue/index.js +1 -1
- package/package.json +5 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"request_tracking_middleware.d.ts","sourceRoot":"","sources":["../../../src/middleware/request_tracking_middleware.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAA;AACjE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AACpD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AACtD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,2BAA2B,CAAA;AAcvD;;;;GAIG;AACH,wBAAgB,iBAAiB,IAAI,OAAO,CAE3C;AAYD,wBAAgB,aAAa,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,EAAE,WAAW,KAAK,OAAO,CAAC,GAAG,IAAI,QAEvE;AAOD,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,cAAc,GAAG,IAAI,QAEjE;AAQD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,QAEnD;AASD,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAErD;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAA;IACd,GAAG,EAAE,MAAM,CAAA;IACX,UAAU,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,CAAC,EAAE,WAAW,CAAA;CACpB;AAQD,wBAAgB,oBAAoB,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,mBAAmB,KAAK,IAAI,CAAC,GAAG,IAAI,QAEpF;AAED,MAAM,CAAC,OAAO,OAAO,yBAAyB;IACtC,MAAM,CAAC,GAAG,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM;
|
|
1
|
+
{"version":3,"file":"request_tracking_middleware.d.ts","sourceRoot":"","sources":["../../../src/middleware/request_tracking_middleware.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAA;AACjE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AACpD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AACtD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,2BAA2B,CAAA;AAcvD;;;;GAIG;AACH,wBAAgB,iBAAiB,IAAI,OAAO,CAE3C;AAYD,wBAAgB,aAAa,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,EAAE,WAAW,KAAK,OAAO,CAAC,GAAG,IAAI,QAEvE;AAOD,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,cAAc,GAAG,IAAI,QAEjE;AAQD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,QAEnD;AASD,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAErD;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAA;IACd,GAAG,EAAE,MAAM,CAAA;IACX,UAAU,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,CAAC,EAAE,WAAW,CAAA;CACpB;AAQD,wBAAgB,oBAAoB,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,mBAAmB,KAAK,IAAI,CAAC,GAAG,IAAI,QAEpF;AAED,MAAM,CAAC,OAAO,OAAO,yBAAyB;IACtC,MAAM,CAAC,GAAG,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM;CAoF5C"}
|
|
@@ -67,7 +67,14 @@ export default class RequestTrackingMiddleware {
|
|
|
67
67
|
await next();
|
|
68
68
|
return;
|
|
69
69
|
}
|
|
70
|
+
// Gracefully handle the startup window before collectors initialize.
|
|
71
|
+
// The provider defers initialization via setImmediate, so early
|
|
72
|
+
// requests may arrive before httpCollector() has been called.
|
|
70
73
|
const metrics = getRequestMetrics();
|
|
74
|
+
if (!metrics) {
|
|
75
|
+
await next();
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
71
78
|
const start = performance.now();
|
|
72
79
|
metrics.incrementActiveConnections();
|
|
73
80
|
// Share a lazy shouldShow evaluator with Edge for @serverStats() tag.
|
|
@@ -14,6 +14,7 @@ export default class ServerStatsProvider {
|
|
|
14
14
|
private statsController;
|
|
15
15
|
private debugController;
|
|
16
16
|
private apiController;
|
|
17
|
+
private dashboardDepsAvailable;
|
|
17
18
|
private pinoHookActive;
|
|
18
19
|
private edgePluginActive;
|
|
19
20
|
private prometheusActive;
|
|
@@ -23,6 +24,7 @@ export default class ServerStatsProvider {
|
|
|
23
24
|
private resolvedCollectors;
|
|
24
25
|
constructor(app: ApplicationService);
|
|
25
26
|
boot(): Promise<void>;
|
|
27
|
+
private initializeBoot;
|
|
26
28
|
/**
|
|
27
29
|
* Read start/kernel.ts and detect auth-related middleware in server.use()
|
|
28
30
|
* or router.use() blocks. Returns import paths of problematic middleware.
|
|
@@ -40,6 +42,13 @@ export default class ServerStatsProvider {
|
|
|
40
42
|
*/
|
|
41
43
|
private hookPinoLogger;
|
|
42
44
|
ready(): Promise<void>;
|
|
45
|
+
private initializeServerStats;
|
|
46
|
+
/**
|
|
47
|
+
* Set up the stats collection interval, transmit broadcasting,
|
|
48
|
+
* and Prometheus integration. Extracted from initializeServerStats
|
|
49
|
+
* so the ready log fires promptly.
|
|
50
|
+
*/
|
|
51
|
+
private setupStatsInterval;
|
|
43
52
|
private setupDevToolbar;
|
|
44
53
|
/**
|
|
45
54
|
* Initialize the full-page dashboard: SQLite store, controller,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server_stats_provider.d.ts","sourceRoot":"","sources":["../../../src/provider/server_stats_provider.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"server_stats_provider.d.ts","sourceRoot":"","sources":["../../../src/provider/server_stats_provider.ts"],"names":[],"mappings":"AAwBA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAA;AAQ9D,MAAM,CAAC,OAAO,OAAO,mBAAmB;IA2B1B,SAAS,CAAC,GAAG,EAAE,kBAAkB;IA1B7C,OAAO,CAAC,UAAU,CAA8C;IAChE,OAAO,CAAC,MAAM,CAA2B;IACzC,OAAO,CAAC,UAAU,CAA0B;IAC5C,OAAO,CAAC,cAAc,CAA8B;IACpD,OAAO,CAAC,mBAAmB,CAAmC;IAC9D,OAAO,CAAC,kBAAkB,CAAgC;IAC1D,OAAO,CAAC,uBAAuB,CAA8C;IAC7E,OAAO,CAAC,mBAAmB,CAA6C;IACxE,OAAO,CAAC,WAAW,CAAsB;IACzC,OAAO,CAAC,UAAU,CAA8C;IAChE,OAAO,CAAC,eAAe,CAAqC;IAC5D,OAAO,CAAC,eAAe,CAA+B;IACtD,OAAO,CAAC,aAAa,CAA6B;IAGlD,OAAO,CAAC,sBAAsB,CAAgB;IAG9C,OAAO,CAAC,cAAc,CAAiB;IACvC,OAAO,CAAC,gBAAgB,CAAiB;IACzC,OAAO,CAAC,gBAAgB,CAAiB;IACzC,OAAO,CAAC,iBAAiB,CAAiB;IAC1C,OAAO,CAAC,gBAAgB,CAAe;IACvC,OAAO,CAAC,cAAc,CAAyC;IAC/D,OAAO,CAAC,kBAAkB,CAAwB;gBAE5B,GAAG,EAAE,kBAAkB;IAEvC,IAAI;YAcI,cAAc;IA8I5B;;;;;OAKG;IACH,OAAO,CAAC,0BAA0B;IAmDlC;;;;;;;OAOG;YACW,cAAc;IAqCtB,KAAK;YAuBG,qBAAqB;IA6FnC;;;;OAIG;IACH,OAAO,CAAC,kBAAkB;YA2DZ,eAAe;IA8G7B;;;;;;OAMG;YACW,cAAc;IA0L5B,2DAA2D;IAC3D,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IA2ER,QAAQ;CAwCf"}
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs';
|
|
2
2
|
import { getLogStreamService } from '../collectors/log_collector.js';
|
|
3
|
-
import { DashboardStore } from '../dashboard/dashboard_store.js';
|
|
4
|
-
import { DataAccess } from '../data/data_access.js';
|
|
5
3
|
import { DebugStore } from '../debug/debug_store.js';
|
|
6
4
|
import { StatsEngine } from '../engine/stats_engine.js';
|
|
7
5
|
import { LogStreamService } from '../log_stream/log_stream_service.js';
|
|
@@ -23,6 +21,8 @@ export default class ServerStatsProvider {
|
|
|
23
21
|
statsController = null;
|
|
24
22
|
debugController = null;
|
|
25
23
|
apiController = null;
|
|
24
|
+
// Dashboard dependency check (set in boot, read in ready)
|
|
25
|
+
dashboardDepsAvailable = true;
|
|
26
26
|
// Diagnostics tracking
|
|
27
27
|
pinoHookActive = false;
|
|
28
28
|
edgePluginActive = false;
|
|
@@ -35,9 +35,24 @@ export default class ServerStatsProvider {
|
|
|
35
35
|
this.app = app;
|
|
36
36
|
}
|
|
37
37
|
async boot() {
|
|
38
|
+
try {
|
|
39
|
+
await this.initializeBoot();
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
log.warn(`boot failed: ${err?.message ?? err}\n` +
|
|
43
|
+
` ${dim('The server will continue without server-stats.')}`);
|
|
44
|
+
if (err?.stack) {
|
|
45
|
+
console.error(err.stack);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
async initializeBoot() {
|
|
38
50
|
const config = this.app.config.get('server_stats');
|
|
39
|
-
if (!config)
|
|
51
|
+
if (!config) {
|
|
52
|
+
log.warn('no config found — is config/server_stats.ts set up?');
|
|
40
53
|
return;
|
|
54
|
+
}
|
|
55
|
+
log.info('booting...');
|
|
41
56
|
// Wire up the per-request shouldShow callback
|
|
42
57
|
if (config.shouldShow) {
|
|
43
58
|
setShouldShow(config.shouldShow);
|
|
@@ -58,7 +73,36 @@ export default class ServerStatsProvider {
|
|
|
58
73
|
const debugEndpoint = toolbarConfig?.enabled
|
|
59
74
|
? (toolbarConfig.debugEndpoint ?? '/admin/api/debug')
|
|
60
75
|
: undefined;
|
|
61
|
-
|
|
76
|
+
// Check dashboard dependencies before registering dashboard routes.
|
|
77
|
+
// Must use appImport — bare import() resolves to this package's
|
|
78
|
+
// devDeps when symlinked, not the app's actual dependencies.
|
|
79
|
+
if (toolbarConfig?.enabled && toolbarConfig.dashboard) {
|
|
80
|
+
const { appImport } = await import('../utils/app_import.js');
|
|
81
|
+
const missing = [];
|
|
82
|
+
try {
|
|
83
|
+
await appImport('knex');
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
missing.push('knex');
|
|
87
|
+
}
|
|
88
|
+
try {
|
|
89
|
+
await appImport('better-sqlite3');
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
missing.push('better-sqlite3');
|
|
93
|
+
}
|
|
94
|
+
if (missing.length > 0) {
|
|
95
|
+
this.dashboardDepsAvailable = false;
|
|
96
|
+
log.block(`Dashboard requires ${missing.join(' and ')}. Install with:`, [
|
|
97
|
+
'',
|
|
98
|
+
bold(`npm install ${missing.join(' ')}`),
|
|
99
|
+
'',
|
|
100
|
+
dim('Dashboard routes have been skipped for now.'),
|
|
101
|
+
dim('Everything else (stats bar, debug panel) works without it.'),
|
|
102
|
+
]);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
const dashboardPath = toolbarConfig?.enabled && toolbarConfig.dashboard && this.dashboardDepsAvailable
|
|
62
106
|
? (toolbarConfig.dashboardPath ?? '/__stats')
|
|
63
107
|
: undefined;
|
|
64
108
|
// ── Register all routes via the unified registrar ──────────
|
|
@@ -85,7 +129,7 @@ export default class ServerStatsProvider {
|
|
|
85
129
|
}
|
|
86
130
|
// Log registered routes
|
|
87
131
|
if (registeredPaths.length > 0) {
|
|
88
|
-
log.list('routes registered:', registeredPaths);
|
|
132
|
+
log.list('routes auto-registered (no manual setup needed):', registeredPaths);
|
|
89
133
|
// Only warn about global auth middleware if:
|
|
90
134
|
// 1. shouldShow is NOT configured (user hasn't set up access control)
|
|
91
135
|
// 2. There IS auth middleware in server.use() or router.use()
|
|
@@ -118,7 +162,11 @@ export default class ServerStatsProvider {
|
|
|
118
162
|
if (!this.app.usingEdgeJS)
|
|
119
163
|
return;
|
|
120
164
|
try {
|
|
121
|
-
|
|
165
|
+
// Must use appImport for edge.js — when this package is symlinked,
|
|
166
|
+
// bare import('edge.js') resolves to the package's devDep copy,
|
|
167
|
+
// which is a different singleton than the app's Edge instance.
|
|
168
|
+
const { appImport } = await import('../utils/app_import.js');
|
|
169
|
+
const edge = await appImport('edge.js');
|
|
122
170
|
const { edgePluginServerStats } = await import('../edge/plugin.js');
|
|
123
171
|
edge.default.use(edgePluginServerStats(config));
|
|
124
172
|
this.edgePluginActive = true;
|
|
@@ -228,6 +276,21 @@ export default class ServerStatsProvider {
|
|
|
228
276
|
return;
|
|
229
277
|
if (this.app.inTest && config.skipInTest !== false)
|
|
230
278
|
return;
|
|
279
|
+
// Defer the entire initialization to setImmediate so ready() returns
|
|
280
|
+
// immediately. AdonisJS waits for all provider ready() hooks before
|
|
281
|
+
// processing HTTP requests — blocking here would hang the server.
|
|
282
|
+
// Routes use lazy controller getters that return 503 until init completes.
|
|
283
|
+
setImmediate(() => {
|
|
284
|
+
this.initializeServerStats(config).catch((err) => {
|
|
285
|
+
log.warn(`failed to initialize: ${err?.message ?? err}\n` +
|
|
286
|
+
` ${dim('The server will continue without server-stats.')}`);
|
|
287
|
+
if (err?.stack) {
|
|
288
|
+
console.error(err.stack);
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
async initializeServerStats(config) {
|
|
231
294
|
this.resolvedConfig = config;
|
|
232
295
|
let collectors;
|
|
233
296
|
if (!config.collectors || config.collectors === 'auto') {
|
|
@@ -251,21 +314,27 @@ export default class ServerStatsProvider {
|
|
|
251
314
|
// Dev toolbar setup
|
|
252
315
|
const toolbarConfig = config.devToolbar;
|
|
253
316
|
if (toolbarConfig?.enabled && !this.app.inProduction) {
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
317
|
+
try {
|
|
318
|
+
await this.setupDevToolbar({
|
|
319
|
+
enabled: true,
|
|
320
|
+
maxQueries: toolbarConfig.maxQueries ?? 500,
|
|
321
|
+
maxEvents: toolbarConfig.maxEvents ?? 200,
|
|
322
|
+
maxEmails: toolbarConfig.maxEmails ?? 100,
|
|
323
|
+
slowQueryThresholdMs: toolbarConfig.slowQueryThresholdMs ?? 100,
|
|
324
|
+
persistDebugData: toolbarConfig.persistDebugData ?? false,
|
|
325
|
+
tracing: toolbarConfig.tracing ?? false,
|
|
326
|
+
maxTraces: toolbarConfig.maxTraces ?? 200,
|
|
327
|
+
dashboard: toolbarConfig.dashboard ?? false,
|
|
328
|
+
dashboardPath: toolbarConfig.dashboardPath ?? '/__stats',
|
|
329
|
+
retentionDays: toolbarConfig.retentionDays ?? 7,
|
|
330
|
+
dbPath: toolbarConfig.dbPath ?? '.adonisjs/server-stats/dashboard.sqlite3',
|
|
331
|
+
debugEndpoint: toolbarConfig.debugEndpoint ?? '/admin/api/debug',
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
catch (err) {
|
|
335
|
+
log.warn(`dev toolbar setup failed: ${err?.message ?? err}\n` +
|
|
336
|
+
` ${dim('Stats bar will still work, but debug panel may be unavailable.')}`);
|
|
337
|
+
}
|
|
269
338
|
// Exclude the stats endpoint and user-specified prefixes from tracing
|
|
270
339
|
// so the debug panel's own polling doesn't flood the timeline
|
|
271
340
|
const debugEndpoint = toolbarConfig.debugEndpoint ?? '/admin/api/debug';
|
|
@@ -277,41 +346,58 @@ export default class ServerStatsProvider {
|
|
|
277
346
|
if (prefixes.length > 0) {
|
|
278
347
|
setExcludedPrefixes(prefixes);
|
|
279
348
|
}
|
|
280
|
-
// Create the unified ApiController now that
|
|
349
|
+
// Create the unified ApiController now that debug store is available.
|
|
350
|
+
// Dashboard store is passed as a getter so it picks up the reference
|
|
351
|
+
// once setupDashboard() completes asynchronously.
|
|
281
352
|
if (this.debugStore) {
|
|
282
353
|
const logPath = this.app.makePath('logs', 'adonisjs.log');
|
|
283
|
-
const
|
|
354
|
+
const { DataAccess: DataAccessClass } = await import('../data/data_access.js');
|
|
355
|
+
const dataAccess = new DataAccessClass(this.debugStore, () => this.dashboardStore, logPath);
|
|
284
356
|
const { ApiController: ApiControllerClass } = await import('../controller/api_controller.js');
|
|
285
357
|
this.apiController = new ApiControllerClass(dataAccess);
|
|
286
358
|
}
|
|
287
359
|
}
|
|
360
|
+
// ── Stats collection interval + transmit (lightweight, set up inline) ──
|
|
361
|
+
this.setupStatsInterval(config);
|
|
362
|
+
log.info('ready');
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* Set up the stats collection interval, transmit broadcasting,
|
|
366
|
+
* and Prometheus integration. Extracted from initializeServerStats
|
|
367
|
+
* so the ready log fires promptly.
|
|
368
|
+
*/
|
|
369
|
+
setupStatsInterval(config) {
|
|
288
370
|
let transmit = null;
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
371
|
+
let prometheusCollector = null;
|
|
372
|
+
// Resolve transmit + prometheus asynchronously but don't block ready()
|
|
373
|
+
const resolveIntegrations = async () => {
|
|
374
|
+
if (config.transport === 'transmit') {
|
|
375
|
+
try {
|
|
376
|
+
transmit = await this.app.container.make('transmit');
|
|
377
|
+
if (transmit) {
|
|
378
|
+
this.transmitAvailable = true;
|
|
379
|
+
if (config.channelName) {
|
|
380
|
+
this.transmitChannels.push(config.channelName);
|
|
381
|
+
}
|
|
296
382
|
}
|
|
297
383
|
}
|
|
384
|
+
catch {
|
|
385
|
+
log.info('transport is "transmit" but @adonisjs/transmit is not installed — falling back to polling');
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
try {
|
|
389
|
+
const mod = await import('../prometheus/prometheus_collector.js');
|
|
390
|
+
prometheusCollector = mod.ServerStatsCollector.instance;
|
|
298
391
|
}
|
|
299
392
|
catch {
|
|
300
|
-
|
|
393
|
+
// Prometheus not installed — skip (optional dependency)
|
|
301
394
|
}
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
}
|
|
308
|
-
catch {
|
|
309
|
-
// Prometheus not installed — skip (optional dependency)
|
|
310
|
-
}
|
|
311
|
-
if (prometheusCollector) {
|
|
312
|
-
this.prometheusActive = true;
|
|
313
|
-
log.info('Prometheus integration active');
|
|
314
|
-
}
|
|
395
|
+
if (prometheusCollector) {
|
|
396
|
+
this.prometheusActive = true;
|
|
397
|
+
log.info('Prometheus integration active');
|
|
398
|
+
}
|
|
399
|
+
};
|
|
400
|
+
resolveIntegrations().catch(() => { });
|
|
315
401
|
this.intervalId = setInterval(async () => {
|
|
316
402
|
try {
|
|
317
403
|
const stats = await this.engine.collect();
|
|
@@ -365,6 +451,7 @@ export default class ServerStatsProvider {
|
|
|
365
451
|
getEngine: () => this.engine,
|
|
366
452
|
getDashboardStore: () => this.dashboardStore,
|
|
367
453
|
getProviderDiagnostics: () => this.getDiagnostics(),
|
|
454
|
+
getApp: () => this.app,
|
|
368
455
|
});
|
|
369
456
|
// Wire trace collector into the request tracking middleware
|
|
370
457
|
if (this.debugStore.traces) {
|
|
@@ -415,9 +502,17 @@ export default class ServerStatsProvider {
|
|
|
415
502
|
}, 200);
|
|
416
503
|
});
|
|
417
504
|
}
|
|
418
|
-
// Full-page dashboard setup
|
|
419
|
-
|
|
420
|
-
|
|
505
|
+
// Full-page dashboard setup — deferred with setImmediate so it runs
|
|
506
|
+
// AFTER the current event-loop cycle completes. This guarantees
|
|
507
|
+
// ready() returns and AdonisJS can process HTTP requests while the
|
|
508
|
+
// SQLite store initializes in the background.
|
|
509
|
+
if (toolbarConfig.dashboard && this.dashboardDepsAvailable) {
|
|
510
|
+
setImmediate(() => {
|
|
511
|
+
this.setupDashboard(toolbarConfig, emitter).catch((err) => {
|
|
512
|
+
log.warn(`dashboard setup failed: ${err?.message ?? err}\n` +
|
|
513
|
+
` ${dim('Everything else continues to work.')}`);
|
|
514
|
+
});
|
|
515
|
+
});
|
|
421
516
|
}
|
|
422
517
|
}
|
|
423
518
|
/**
|
|
@@ -428,34 +523,76 @@ export default class ServerStatsProvider {
|
|
|
428
523
|
* This method creates the controller so those routes become functional.
|
|
429
524
|
*/
|
|
430
525
|
async setupDashboard(toolbarConfig, emitter) {
|
|
431
|
-
|
|
432
|
-
|
|
526
|
+
log.info('dashboard: initializing SQLite store...');
|
|
527
|
+
// Dynamically import DashboardStore so knex/better-sqlite3 are truly optional
|
|
528
|
+
const { DashboardStore: DashboardStoreClass } = await import('../dashboard/dashboard_store.js');
|
|
529
|
+
this.dashboardStore = new DashboardStoreClass(toolbarConfig);
|
|
433
530
|
const appRoot = this.app.makePath('');
|
|
434
531
|
try {
|
|
435
|
-
|
|
532
|
+
// Timeout safety net: if SQLite init hangs (e.g. wrong native binary
|
|
533
|
+
// loaded via symlink), abort after 15s instead of freezing forever.
|
|
534
|
+
const TIMEOUT_MS = 15_000;
|
|
535
|
+
const startPromise = this.dashboardStore.start(null, emitter, appRoot);
|
|
536
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
537
|
+
setTimeout(() => reject(new Error(`Dashboard SQLite initialization timed out after ${TIMEOUT_MS / 1000}s`)), TIMEOUT_MS);
|
|
538
|
+
});
|
|
539
|
+
await Promise.race([startPromise, timeoutPromise]);
|
|
540
|
+
log.info('dashboard: SQLite store ready');
|
|
436
541
|
}
|
|
437
542
|
catch (err) {
|
|
438
543
|
const msg = err?.message || '';
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
544
|
+
const code = err?.code || '';
|
|
545
|
+
const isMissingDep = msg.includes('better-sqlite3') ||
|
|
546
|
+
msg.includes('knex') ||
|
|
547
|
+
msg.includes('Cannot find module') ||
|
|
548
|
+
msg.includes('Cannot find package') ||
|
|
549
|
+
code === 'ERR_MODULE_NOT_FOUND' ||
|
|
550
|
+
code === 'MODULE_NOT_FOUND';
|
|
551
|
+
const isTimeout = msg.includes('timed out');
|
|
552
|
+
if (isMissingDep) {
|
|
553
|
+
log.block('Dashboard could not start — missing dependencies. Install with:', [
|
|
554
|
+
'',
|
|
555
|
+
bold('npm install knex better-sqlite3'),
|
|
556
|
+
'',
|
|
557
|
+
dim('Dashboard has been disabled for this session.'),
|
|
558
|
+
dim('Everything else (stats bar, debug panel) works without it.'),
|
|
559
|
+
]);
|
|
560
|
+
}
|
|
561
|
+
else if (isTimeout) {
|
|
562
|
+
log.block('Dashboard initialization timed out', [
|
|
563
|
+
dim('SQLite setup took too long — this usually means a wrong native'),
|
|
564
|
+
dim('binary was loaded (common with symlinked/file: dependencies).'),
|
|
565
|
+
'',
|
|
566
|
+
dim('Try running:'),
|
|
567
|
+
` ${bold('npm install knex better-sqlite3')}`,
|
|
568
|
+
dim('in your app directory to ensure the correct copies are used.'),
|
|
569
|
+
'',
|
|
570
|
+
dim('Dashboard has been disabled for this session.'),
|
|
571
|
+
dim('Everything else (stats bar, debug panel) works without it.'),
|
|
572
|
+
]);
|
|
445
573
|
}
|
|
446
|
-
|
|
574
|
+
else {
|
|
575
|
+
log.warn(`Dashboard could not start: ${msg}\n` +
|
|
576
|
+
` ${dim('Dashboard has been disabled for this session.')}`);
|
|
577
|
+
if (err?.stack) {
|
|
578
|
+
console.error(err.stack);
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
this.dashboardStore = null;
|
|
582
|
+
return;
|
|
447
583
|
}
|
|
448
|
-
|
|
449
|
-
;
|
|
584
|
+
log.info('dashboard: binding to container...');
|
|
450
585
|
this.app.container.singleton('dashboard.store', () => this.dashboardStore);
|
|
451
586
|
// Set dashboard path in middleware for self-exclusion
|
|
452
587
|
setDashboardPath(toolbarConfig.dashboardPath);
|
|
453
588
|
// Create the controller — this makes the routes registered in boot() functional
|
|
589
|
+
log.info('dashboard: creating controller...');
|
|
454
590
|
const DashboardControllerClass = (await import('../dashboard/dashboard_controller.js')).default;
|
|
455
591
|
this.dashboardController = new DashboardControllerClass(this.dashboardStore, this.app);
|
|
456
592
|
// ── Log piping ────────────────────────────────────────────────
|
|
457
593
|
// If the log collector is already hooked into Pino (zero-config mode),
|
|
458
594
|
// piggyback on it instead of creating a separate file poller.
|
|
595
|
+
log.info('dashboard: setting up log piping...');
|
|
459
596
|
const existingLogStream = getLogStreamService();
|
|
460
597
|
if (existingLogStream && !existingLogStream['logPath']) {
|
|
461
598
|
// Stream mode — add a listener for dashboard persistence
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dynamically import a module, resolving from the app root (`process.cwd()`)
|
|
3
|
+
* instead of from this package's location.
|
|
4
|
+
*
|
|
5
|
+
* This is critical when `adonisjs-server-stats` is symlinked into the app
|
|
6
|
+
* (e.g. via `file:../../adonisjs-server-stats` in package.json). Without
|
|
7
|
+
* this, Node.js dereferences the symlink and resolves bare specifiers from
|
|
8
|
+
* the package's *real* directory tree — which may contain devDependency
|
|
9
|
+
* stubs with different module identity than the app's actual packages.
|
|
10
|
+
*
|
|
11
|
+
* Falls back to a normal `import()` when `createRequire` resolution fails
|
|
12
|
+
* (e.g. when the package is installed normally, not symlinked).
|
|
13
|
+
*/
|
|
14
|
+
export declare function appImport<T = unknown>(specifier: string): Promise<T>;
|
|
15
|
+
/**
|
|
16
|
+
* Same as {@link appImport} but also returns the resolved file path.
|
|
17
|
+
* Useful for diagnostic logging.
|
|
18
|
+
*/
|
|
19
|
+
export declare function appImportWithPath<T = unknown>(specifier: string): Promise<{
|
|
20
|
+
module: T;
|
|
21
|
+
resolvedPath: string;
|
|
22
|
+
}>;
|
|
23
|
+
//# sourceMappingURL=app_import.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app_import.d.ts","sourceRoot":"","sources":["../../../src/utils/app_import.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;GAYG;AACH,wBAAsB,SAAS,CAAC,CAAC,GAAG,OAAO,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CAS1E;AAED;;;GAGG;AACH,wBAAsB,iBAAiB,CAAC,CAAC,GAAG,OAAO,EACjD,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC;IAAE,MAAM,EAAE,CAAC,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,CAAC,CAW9C"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { createRequire } from 'node:module';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { pathToFileURL } from 'node:url';
|
|
4
|
+
/**
|
|
5
|
+
* Dynamically import a module, resolving from the app root (`process.cwd()`)
|
|
6
|
+
* instead of from this package's location.
|
|
7
|
+
*
|
|
8
|
+
* This is critical when `adonisjs-server-stats` is symlinked into the app
|
|
9
|
+
* (e.g. via `file:../../adonisjs-server-stats` in package.json). Without
|
|
10
|
+
* this, Node.js dereferences the symlink and resolves bare specifiers from
|
|
11
|
+
* the package's *real* directory tree — which may contain devDependency
|
|
12
|
+
* stubs with different module identity than the app's actual packages.
|
|
13
|
+
*
|
|
14
|
+
* Falls back to a normal `import()` when `createRequire` resolution fails
|
|
15
|
+
* (e.g. when the package is installed normally, not symlinked).
|
|
16
|
+
*/
|
|
17
|
+
export async function appImport(specifier) {
|
|
18
|
+
try {
|
|
19
|
+
const appRequire = createRequire(join(process.cwd(), 'package.json'));
|
|
20
|
+
const resolved = appRequire.resolve(specifier);
|
|
21
|
+
return await import(pathToFileURL(resolved).href);
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
// Fallback: normal import (works when not symlinked)
|
|
25
|
+
return await import(specifier);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Same as {@link appImport} but also returns the resolved file path.
|
|
30
|
+
* Useful for diagnostic logging.
|
|
31
|
+
*/
|
|
32
|
+
export async function appImportWithPath(specifier) {
|
|
33
|
+
try {
|
|
34
|
+
const appRequire = createRequire(join(process.cwd(), 'package.json'));
|
|
35
|
+
const resolved = appRequire.resolve(specifier);
|
|
36
|
+
const module = await import(pathToFileURL(resolved).href);
|
|
37
|
+
return { module: module, resolvedPath: resolved };
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
// Fallback: normal import (works when not symlinked)
|
|
41
|
+
const module = await import(specifier);
|
|
42
|
+
return { module: module, resolvedPath: `(bare import: ${specifier})` };
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { defineComponent as E, inject as v, ref as h, computed as C, openBlock as a, createElementBlock as l, createElementVNode as e, toDisplayString as c, createCommentVNode as g, createVNode as F, unref as y, Fragment as N, renderList as A, withModifiers as B, createBlock as U } from "vue";
|
|
2
|
-
import { u as H } from "./index-
|
|
2
|
+
import { u as H } from "./index-qCQpBftQ.js";
|
|
3
3
|
import { u as M } from "./useResizableTable-BoivAevK.js";
|
|
4
4
|
import { DashboardApi as j, formatCacheSize as q, formatTtl as I } from "adonisjs-server-stats/core";
|
|
5
5
|
import { u as G } from "./useApiClient-BQQ9CF-q.js";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { defineComponent as re, inject as m, ref as w, computed as x, openBlock as i, createElementBlock as r, createElementVNode as n, normalizeClass as a, createCommentVNode as b, Fragment as _, toDisplayString as u, unref as c, renderList as A, withModifiers as I, normalizeStyle as R } from "vue";
|
|
2
|
-
import { u as ue } from "./index-
|
|
2
|
+
import { u as ue } from "./index-qCQpBftQ.js";
|
|
3
3
|
import { isRedactedValue as d, flattenConfig as z, TAB_ICONS as g, countLeaves as ce, collectTopLevelObjectKeys as pe, copyWithFeedback as de, formatFlatValue as ve } from "adonisjs-server-stats/core";
|
|
4
4
|
const fe = { style: { position: "relative", flex: 1 } }, he = ["value"], ge = ["title", "onClick"], ye = ["viewBox", "innerHTML"], be = ["viewBox", "innerHTML"], $e = ["onClick"], we = { key: 0 }, xe = ["title", "onClick"], ke = ["viewBox", "innerHTML"], Ce = ["viewBox", "innerHTML"], _e = ["onClick"], Se = { key: 0 }, Be = { style: { padding: "4px 16px", fontSize: "10px", color: "var(--ss-muted)" } }, Le = ["onClick"], He = ["title"], Te = ["title"], je = ["title", "onClick"], Me = ["viewBox", "innerHTML"], me = ["viewBox", "innerHTML"], Ae = ["onClick"], Ie = ["title", "onClick"], Ee = ["viewBox", "innerHTML"], Ve = ["viewBox", "innerHTML"], Oe = {
|
|
5
5
|
key: 1,
|