adonisjs-server-stats 1.12.3 → 1.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -6
- package/dist/configure.js +6 -1
- package/dist/core/collectors/collector.d.ts +83 -0
- package/dist/core/core/types.d.ts +186 -0
- package/dist/core/debug/types.d.ts +467 -0
- package/dist/core/index.js +184 -169
- package/dist/core/types.d.ts +855 -167
- package/dist/react/{CacheSection-CX2duJuc.js → CacheSection-DkLtukWz.js} +1 -1
- package/dist/react/{CacheTab-CgT4t0oZ.js → CacheTab-DWeXnEBI.js} +51 -46
- package/dist/react/ConfigContent-Bwvfl_G7.js +350 -0
- package/dist/react/{ConfigSection-Dt0FyeaW.js → ConfigSection-DaVbFPDg.js} +2 -2
- package/dist/react/{ConfigTab-KtiTtsrj.js → ConfigTab-D4640WJZ.js} +2 -2
- package/dist/react/{CustomPaneTab-15QoEplP.js → CustomPaneTab-DglmAVMC.js} +1 -1
- package/dist/react/{EmailsSection-YcJYR5eA.js → EmailsSection-vYx6ExTb.js} +1 -1
- package/dist/react/{EmailsTab-CUjubln_.js → EmailsTab-DKDrRmRH.js} +54 -49
- package/dist/react/{EventsSection-GGPoYSNv.js → EventsSection-CapH5xut.js} +1 -1
- package/dist/react/{EventsTab-Wdwr0_ny.js → EventsTab-BNVEySAm.js} +1 -1
- package/dist/react/{InternalsContent-BNOnSoi9.js → InternalsContent-C9lIA92C.js} +1 -1
- package/dist/react/{InternalsSection-BwrTfpjA.js → InternalsSection-lOv_UcSV.js} +1 -1
- package/dist/react/{InternalsTab--RD-L1dX.js → InternalsTab-BygoSorC.js} +1 -1
- package/dist/react/{JobsSection-BqqIh_DR.js → JobsSection-ZIQsJWid.js} +1 -1
- package/dist/react/{JobsTab-CBrU-ryL.js → JobsTab-DXQzXrDt.js} +1 -1
- package/dist/react/{LogEntryRow-BOrRkhRU.js → LogEntryRow-BWkHE51-.js} +1 -1
- package/dist/react/{LogsSection-Cm_lphM6.js → LogsSection-ConXdBkL.js} +2 -2
- package/dist/react/LogsTab-CJM47LPn.js +82 -0
- package/dist/react/{OverviewSection-XF7bakyM.js → OverviewSection-DOMu2qvl.js} +131 -131
- package/dist/react/{QueriesSection-DsQBKrNK.js → QueriesSection-DFsaOSJI.js} +1 -1
- package/dist/react/{QueriesTab-CxCC1GVq.js → QueriesTab-CY9CG_7L.js} +1 -1
- package/dist/react/RequestsSection-CC-eVHsl.js +326 -0
- package/dist/react/{RoutesSection-D0y5JQP4.js → RoutesSection-p1DMq41y.js} +1 -1
- package/dist/react/{RoutesTab-Y_alJVMV.js → RoutesTab-Cnqy8UcK.js} +1 -1
- package/dist/react/{SplitPaneWrapper-CZl1ouIT.js → SplitPaneWrapper-XgkA0QxE.js} +1 -1
- package/dist/react/{TimelineTab-HyqZpfbp.js → TimelineTab-1YOERxe5.js} +3 -3
- package/dist/react/index-DOSlCpZ9.js +1159 -0
- package/dist/react/index.js +1 -1
- package/dist/react/useApiClient-CtEG7rHs.js +11 -0
- package/dist/src/collectors/adonisjs_queue_collector.d.ts +21 -0
- package/dist/src/collectors/adonisjs_queue_collector.js +61 -0
- package/dist/src/collectors/auto_detect.js +8 -2
- package/dist/src/collectors/index.d.ts +2 -0
- package/dist/src/collectors/index.js +1 -0
- package/dist/src/collectors/queue_collector.js +22 -6
- package/dist/src/config/deprecation_migration.js +11 -0
- package/dist/src/dashboard/cache_handlers.js +22 -2
- package/dist/src/dashboard/flush_manager.d.ts +2 -0
- package/dist/src/dashboard/flush_manager.js +17 -0
- package/dist/src/dashboard/inspector_manager.d.ts +4 -4
- package/dist/src/dashboard/inspector_manager.js +20 -10
- package/dist/src/dashboard/integrations/adonisjs_queue_inspector.d.ts +42 -0
- package/dist/src/dashboard/integrations/adonisjs_queue_inspector.js +135 -0
- package/dist/src/dashboard/integrations/adonisjs_queue_store.d.ts +166 -0
- package/dist/src/dashboard/integrations/adonisjs_queue_store.js +629 -0
- package/dist/src/dashboard/integrations/config_inspector.d.ts +0 -1
- package/dist/src/dashboard/integrations/config_inspector.js +4 -2
- package/dist/src/dashboard/integrations/index.d.ts +4 -1
- package/dist/src/dashboard/integrations/index.js +2 -0
- package/dist/src/dashboard/integrations/queue_inspector.d.ts +5 -61
- package/dist/src/dashboard/integrations/queue_inspector.js +5 -1
- package/dist/src/dashboard/integrations/queue_inspector_contract.d.ts +73 -0
- package/dist/src/dashboard/integrations/queue_inspector_contract.js +15 -0
- package/dist/src/dashboard/paginate_helper.d.ts +5 -0
- package/dist/src/dashboard/paginate_helper.js +16 -4
- package/dist/src/dashboard/query_explain_handler.d.ts +6 -1
- package/dist/src/dashboard/query_explain_handler.js +12 -2
- package/dist/src/dashboard/write_queue.d.ts +13 -0
- package/dist/src/dashboard/write_queue.js +34 -2
- package/dist/src/debug/debug_store.js +8 -1
- package/dist/src/debug/event_collector.js +4 -0
- package/dist/src/debug/query_collector.js +2 -0
- package/dist/src/debug/ring_buffer.js +8 -1
- package/dist/src/debug/trace_collector.js +34 -10
- package/dist/src/debug/types.d.ts +2 -0
- package/dist/src/define_config.js +1 -0
- package/dist/src/edge/client/dashboard.js +2 -2
- package/dist/src/edge/client/debug-panel-deferred.js +1 -1
- package/dist/src/edge/client/stats-bar.js +1 -1
- package/dist/src/edge/client-vue/dashboard.js +5 -5
- package/dist/src/edge/client-vue/debug-panel-deferred.js +2 -2
- package/dist/src/engine/request_metrics.js +8 -10
- package/dist/src/log_stream/log_stream_service.d.ts +1 -0
- package/dist/src/log_stream/log_stream_service.js +7 -5
- package/dist/src/prometheus/prometheus_collector.d.ts +9 -0
- package/dist/src/prometheus/prometheus_collector.js +11 -0
- package/dist/src/provider/dashboard_init.js +15 -0
- package/dist/src/provider/dashboard_setup.js +9 -2
- package/dist/src/provider/email_bridge.d.ts +14 -3
- package/dist/src/provider/email_bridge.js +25 -3
- package/dist/src/provider/email_helpers.d.ts +9 -1
- package/dist/src/provider/email_helpers.js +32 -4
- package/dist/src/provider/pino_hook.d.ts +8 -0
- package/dist/src/provider/pino_hook.js +19 -1
- package/dist/src/provider/provider_helpers_extra.d.ts +2 -0
- package/dist/src/provider/provider_helpers_extra.js +17 -2
- package/dist/src/provider/server_stats_provider.js +19 -1
- package/dist/src/provider/shutdown_helpers.js +2 -0
- package/dist/src/provider/toolbar_setup.js +32 -17
- package/dist/src/routes/dashboard_routes.js +96 -62
- package/dist/src/routes/debug_routes.js +45 -35
- package/dist/src/routes/no_session_middleware.d.ts +6 -3
- package/dist/src/routes/no_session_middleware.js +7 -3
- package/dist/src/routes/register_routes.d.ts +8 -0
- package/dist/src/routes/register_routes.js +19 -0
- package/dist/src/types.d.ts +31 -0
- package/dist/src/utils/app_import.js +12 -4
- package/dist/vue/CacheSection-CxEBVVkF.js +156 -0
- package/dist/vue/ConfigSection-BiRAiaHj.js +415 -0
- package/dist/vue/EmailsSection-Dl44qyqY.js +208 -0
- package/dist/vue/{EmailsTab-CwIF1fik.js → EmailsTab-Diya54CP.js} +6 -5
- package/dist/vue/{EventsSection-BpgkWIwM.js → EventsSection-CWjeitjU.js} +1 -1
- package/dist/vue/{InternalsTab-521fxYYj.js → InternalsTab-Z3c82glB.js} +15 -15
- package/dist/vue/{JobsSection-DrghFEKL.js → JobsSection-DOBb4LjZ.js} +1 -1
- package/dist/vue/LogsSection-CXx-HOWJ.js +260 -0
- package/dist/vue/{OverviewSection-BAgZTPjY.js → OverviewSection-CyfNQ8uV.js} +318 -302
- package/dist/vue/{QueriesSection-CUpwhp7u.js → QueriesSection-CXBsFp-y.js} +1 -1
- package/dist/vue/{RequestsSection-D8P2xpF2.js → RequestsSection-YTIaZGZd.js} +151 -145
- package/dist/vue/{RoutesSection-0qB81hTT.js → RoutesSection-CDKMey49.js} +1 -1
- package/dist/vue/composables/useApiClient.d.ts +8 -1
- package/dist/vue/index-14x39RI_.js +1239 -0
- package/dist/vue/index.js +1 -1
- package/dist/vue/style.css +1 -1
- package/package.json +16 -3
- package/dist/react/ConfigContent-CnsEI4j3.js +0 -397
- package/dist/react/LogsTab-jKwv9G7Q.js +0 -79
- package/dist/react/RequestsSection-D8cMbZU0.js +0 -321
- package/dist/react/index-CsprmgzI.js +0 -1121
- package/dist/react/useApiClient-BVtNCmnL.js +0 -9
- package/dist/vue/CacheSection-DRqV3YX2.js +0 -149
- package/dist/vue/ConfigSection-C6pQCHAL.js +0 -576
- package/dist/vue/EmailsSection-BTNw3ZU2.js +0 -206
- package/dist/vue/LogsSection-BDxx9Bfi.js +0 -258
- package/dist/vue/index-30jLw-_w.js +0 -1232
- /package/dist/core/{api-client.d.ts → core/api-client.d.ts} +0 -0
- /package/dist/core/{config-utils.d.ts → core/config-utils.d.ts} +0 -0
- /package/dist/core/{constants.d.ts → core/constants.d.ts} +0 -0
- /package/dist/core/{dashboard-api.d.ts → core/dashboard-api.d.ts} +0 -0
- /package/dist/core/{dashboard-data-controller.d.ts → core/dashboard-data-controller.d.ts} +0 -0
- /package/dist/core/{dashboard-data-helpers.d.ts → core/dashboard-data-helpers.d.ts} +0 -0
- /package/dist/core/{debug-data-controller.d.ts → core/debug-data-controller.d.ts} +0 -0
- /package/dist/core/{define-config-helpers.d.ts → core/define-config-helpers.d.ts} +0 -0
- /package/dist/core/{explain-utils.d.ts → core/explain-utils.d.ts} +0 -0
- /package/dist/core/{feature-detect-helpers.d.ts → core/feature-detect-helpers.d.ts} +0 -0
- /package/dist/core/{feature-detect.d.ts → core/feature-detect.d.ts} +0 -0
- /package/dist/core/{field-resolvers.d.ts → core/field-resolvers.d.ts} +0 -0
- /package/dist/core/{formatters-helpers.d.ts → core/formatters-helpers.d.ts} +0 -0
- /package/dist/core/{formatters.d.ts → core/formatters.d.ts} +0 -0
- /package/dist/core/{history-buffer.d.ts → core/history-buffer.d.ts} +0 -0
- /package/dist/core/{icons.d.ts → core/icons.d.ts} +0 -0
- /package/dist/core/{index.d.ts → core/index.d.ts} +0 -0
- /package/dist/core/{internals-utils.d.ts → core/internals-utils.d.ts} +0 -0
- /package/dist/core/{job-utils.d.ts → core/job-utils.d.ts} +0 -0
- /package/dist/core/{log-utils-helpers.d.ts → core/log-utils-helpers.d.ts} +0 -0
- /package/dist/core/{log-utils.d.ts → core/log-utils.d.ts} +0 -0
- /package/dist/core/{metrics.d.ts → core/metrics.d.ts} +0 -0
- /package/dist/core/{pagination.d.ts → core/pagination.d.ts} +0 -0
- /package/dist/core/{queries-columns.d.ts → core/queries-columns.d.ts} +0 -0
- /package/dist/core/{queries-controller.d.ts → core/queries-controller.d.ts} +0 -0
- /package/dist/core/{query-utils.d.ts → core/query-utils.d.ts} +0 -0
- /package/dist/core/{resizable-columns.d.ts → core/resizable-columns.d.ts} +0 -0
- /package/dist/core/{routes.d.ts → core/routes.d.ts} +0 -0
- /package/dist/core/{server-stats-controller.d.ts → core/server-stats-controller.d.ts} +0 -0
- /package/dist/core/{sparkline.d.ts → core/sparkline.d.ts} +0 -0
- /package/dist/core/{split-pane.d.ts → core/split-pane.d.ts} +0 -0
- /package/dist/core/{theme.d.ts → core/theme.d.ts} +0 -0
- /package/dist/core/{trace-utils.d.ts → core/trace-utils.d.ts} +0 -0
- /package/dist/core/{transmit-adapter.d.ts → core/transmit-adapter.d.ts} +0 -0
- /package/dist/core/{transmit-helpers.d.ts → core/transmit-helpers.d.ts} +0 -0
- /package/dist/core/{types-dashboard.d.ts → core/types-dashboard.d.ts} +0 -0
- /package/dist/core/{types-diagnostics.d.ts → core/types-diagnostics.d.ts} +0 -0
|
@@ -1,9 +1,33 @@
|
|
|
1
1
|
import { log } from '../utils/logger.js';
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
import { clampPerPage } from '../dashboard/paginate_helper.js';
|
|
3
|
+
/**
|
|
4
|
+
* Lightweight CSRF guard for state-changing routes. Since `noSessionMiddleware`
|
|
5
|
+
* strips `Set-Cookie`, session-based CSRF tokens can't apply — instead we require
|
|
6
|
+
* the request to be provably same-origin (via the `Sec-Fetch-Site` fetch-metadata
|
|
7
|
+
* header) or to carry an `X-Requested-With` header, neither of which a cross-site
|
|
8
|
+
* form/navigation can forge. Returns true when the request is allowed.
|
|
9
|
+
*/
|
|
10
|
+
function isSameOriginRequest(ctx) {
|
|
11
|
+
const fetchSite = ctx.request.header('sec-fetch-site');
|
|
12
|
+
if (fetchSite)
|
|
13
|
+
return fetchSite === 'same-origin' || fetchSite === 'none';
|
|
14
|
+
// Older clients without fetch metadata: require an explicit XHR marker,
|
|
15
|
+
// which a cross-site simple form submission cannot set.
|
|
16
|
+
return !!ctx.request.header('x-requested-with');
|
|
17
|
+
}
|
|
18
|
+
/** Wrap a dashboard handler so it rejects cross-site state-changing requests. */
|
|
19
|
+
function guardCsrf(handler) {
|
|
20
|
+
return async (ctx) => {
|
|
21
|
+
if (!isSameOriginRequest(ctx)) {
|
|
22
|
+
return ctx.response.forbidden({ error: 'Cross-site request blocked' });
|
|
23
|
+
}
|
|
24
|
+
return handler(ctx);
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
function bindDash(getController, method, whenReady) {
|
|
4
28
|
return async (ctx) => {
|
|
5
|
-
if (
|
|
6
|
-
await
|
|
29
|
+
if (whenReady)
|
|
30
|
+
await whenReady();
|
|
7
31
|
const controller = getController();
|
|
8
32
|
if (!controller) {
|
|
9
33
|
log.warn(`503 on ${ctx.request.url()} — dashboardController is null (method: ${method}). ` +
|
|
@@ -15,10 +39,10 @@ function bindDash(getController, method) {
|
|
|
15
39
|
return controller[method].call(controller, ctx);
|
|
16
40
|
};
|
|
17
41
|
}
|
|
18
|
-
function bindApi(getApi, fn) {
|
|
42
|
+
function bindApi(getApi, fn, whenReady) {
|
|
19
43
|
return async (ctx) => {
|
|
20
|
-
if (
|
|
21
|
-
await
|
|
44
|
+
if (whenReady)
|
|
45
|
+
await whenReady();
|
|
22
46
|
const api = getApi();
|
|
23
47
|
if (!api) {
|
|
24
48
|
log.warn(`503 on ${ctx.request.url()} — apiController is null. ` +
|
|
@@ -30,13 +54,13 @@ function bindApi(getApi, fn) {
|
|
|
30
54
|
return fn(api, ctx);
|
|
31
55
|
};
|
|
32
56
|
}
|
|
33
|
-
function registerQueryRoutes(router, getApi) {
|
|
57
|
+
function registerQueryRoutes(router, getApi, whenReady) {
|
|
34
58
|
router
|
|
35
59
|
.get('/api/queries', bindApi(getApi, async (api, ctx) => {
|
|
36
60
|
const qs = ctx.request.qs();
|
|
37
61
|
return ctx.response.json(await api.getQueries({
|
|
38
62
|
page: Number(qs.page) || 1,
|
|
39
|
-
perPage: Number(qs.perPage) || 25,
|
|
63
|
+
perPage: clampPerPage(Number(qs.perPage) || 25),
|
|
40
64
|
search: qs.search || undefined,
|
|
41
65
|
filters: {
|
|
42
66
|
durationMin: qs.duration_min ? Number(qs.duration_min) : undefined,
|
|
@@ -45,50 +69,50 @@ function registerQueryRoutes(router, getApi) {
|
|
|
45
69
|
connection: qs.connection || undefined,
|
|
46
70
|
},
|
|
47
71
|
}));
|
|
48
|
-
}))
|
|
72
|
+
}, whenReady))
|
|
49
73
|
.as('server-stats.queries');
|
|
50
74
|
}
|
|
51
|
-
function registerEventRoutes(router, getApi) {
|
|
75
|
+
function registerEventRoutes(router, getApi, whenReady) {
|
|
52
76
|
router
|
|
53
77
|
.get('/api/events', bindApi(getApi, async (api, ctx) => {
|
|
54
78
|
const qs = ctx.request.qs();
|
|
55
79
|
return ctx.response.json(await api.getEvents({
|
|
56
80
|
page: Number(qs.page) || 1,
|
|
57
|
-
perPage: Number(qs.perPage) || 25,
|
|
81
|
+
perPage: clampPerPage(Number(qs.perPage) || 25),
|
|
58
82
|
search: qs.search || undefined,
|
|
59
83
|
filters: { eventName: qs.event_name || undefined },
|
|
60
84
|
}));
|
|
61
|
-
}))
|
|
85
|
+
}, whenReady))
|
|
62
86
|
.as('server-stats.events');
|
|
63
87
|
}
|
|
64
|
-
function registerRouteApiRoutes(router, getApi) {
|
|
88
|
+
function registerRouteApiRoutes(router, getApi, whenReady) {
|
|
65
89
|
router
|
|
66
|
-
.get('/api/routes', bindApi(getApi, (api, ctx) => ctx.response.json(api.getRoutes(ctx.request.qs().search || undefined))))
|
|
90
|
+
.get('/api/routes', bindApi(getApi, (api, ctx) => ctx.response.json(api.getRoutes(ctx.request.qs().search || undefined)), whenReady))
|
|
67
91
|
.as('server-stats.routes');
|
|
68
92
|
}
|
|
69
|
-
function registerLogRoutes(router, getApi) {
|
|
93
|
+
function registerLogRoutes(router, getApi, whenReady) {
|
|
70
94
|
router
|
|
71
95
|
.get('/api/logs', bindApi(getApi, async (api, ctx) => {
|
|
72
96
|
const qs = ctx.request.qs();
|
|
73
97
|
return ctx.response.json(await api.getLogs({
|
|
74
98
|
page: Number(qs.page) || 1,
|
|
75
|
-
perPage: Number(qs.perPage) || 50,
|
|
99
|
+
perPage: clampPerPage(Number(qs.perPage) || 50),
|
|
76
100
|
search: qs.message || qs.search || undefined,
|
|
77
101
|
filters: {
|
|
78
102
|
level: qs.level || undefined,
|
|
79
103
|
requestId: qs.request_id || qs.requestId || undefined,
|
|
80
104
|
},
|
|
81
105
|
}));
|
|
82
|
-
}))
|
|
106
|
+
}, whenReady))
|
|
83
107
|
.as('server-stats.logs');
|
|
84
108
|
}
|
|
85
|
-
function registerEmailRoutes(router, getApi) {
|
|
109
|
+
function registerEmailRoutes(router, getApi, whenReady) {
|
|
86
110
|
router
|
|
87
111
|
.get('/api/emails', bindApi(getApi, async (api, ctx) => {
|
|
88
112
|
const qs = ctx.request.qs();
|
|
89
113
|
return ctx.response.json(await api.getEmails({
|
|
90
114
|
page: Number(qs.page) || 1,
|
|
91
|
-
perPage: Number(qs.perPage) || 25,
|
|
115
|
+
perPage: clampPerPage(Number(qs.perPage) || 25),
|
|
92
116
|
search: qs.search || undefined,
|
|
93
117
|
filters: {
|
|
94
118
|
from: qs.from || undefined,
|
|
@@ -98,24 +122,29 @@ function registerEmailRoutes(router, getApi) {
|
|
|
98
122
|
mailer: qs.mailer || undefined,
|
|
99
123
|
},
|
|
100
124
|
}));
|
|
101
|
-
}))
|
|
125
|
+
}, whenReady))
|
|
102
126
|
.as('server-stats.emails');
|
|
103
127
|
router
|
|
104
128
|
.get('/api/emails/:id/preview', bindApi(getApi, async (api, ctx) => {
|
|
105
129
|
const html = await api.getEmailPreview(Number(ctx.params.id));
|
|
106
130
|
if (!html)
|
|
107
131
|
return ctx.response.notFound({ error: 'Email not found' });
|
|
108
|
-
return ctx.response
|
|
109
|
-
|
|
132
|
+
return ctx.response
|
|
133
|
+
.header('Content-Type', 'text/html; charset=utf-8')
|
|
134
|
+
.header('Content-Security-Policy', "default-src 'none'; style-src 'unsafe-inline'")
|
|
135
|
+
.header('X-Content-Type-Options', 'nosniff')
|
|
136
|
+
.header('X-Frame-Options', 'SAMEORIGIN')
|
|
137
|
+
.send(html);
|
|
138
|
+
}, whenReady))
|
|
110
139
|
.as('server-stats.emails.preview');
|
|
111
140
|
}
|
|
112
|
-
function registerTraceRoutes(router, getApi) {
|
|
141
|
+
function registerTraceRoutes(router, getApi, whenReady) {
|
|
113
142
|
router
|
|
114
143
|
.get('/api/traces', bindApi(getApi, async (api, ctx) => {
|
|
115
144
|
const qs = ctx.request.qs();
|
|
116
145
|
return ctx.response.json(await api.getTraces({
|
|
117
146
|
page: Number(qs.page) || 1,
|
|
118
|
-
perPage: Number(qs.perPage) || 25,
|
|
147
|
+
perPage: clampPerPage(Number(qs.perPage) || 25),
|
|
119
148
|
search: qs.search || undefined,
|
|
120
149
|
filters: {
|
|
121
150
|
method: qs.method ? qs.method.toUpperCase() : undefined,
|
|
@@ -124,7 +153,7 @@ function registerTraceRoutes(router, getApi) {
|
|
|
124
153
|
statusMax: qs.status_max ? Number(qs.status_max) : undefined,
|
|
125
154
|
},
|
|
126
155
|
}));
|
|
127
|
-
}))
|
|
156
|
+
}, whenReady))
|
|
128
157
|
.as('server-stats.traces');
|
|
129
158
|
router
|
|
130
159
|
.get('/api/traces/:id', bindApi(getApi, async (api, ctx) => {
|
|
@@ -132,83 +161,88 @@ function registerTraceRoutes(router, getApi) {
|
|
|
132
161
|
if (!trace)
|
|
133
162
|
return ctx.response.notFound({ error: 'Trace not found' });
|
|
134
163
|
return ctx.response.json(trace);
|
|
135
|
-
}))
|
|
164
|
+
}, whenReady))
|
|
136
165
|
.as('server-stats.traces.show');
|
|
137
166
|
}
|
|
138
|
-
function registerDashboardPageRoutes(router, getDashboardController) {
|
|
139
|
-
router.get('/', bindDash(getDashboardController, 'page')).as('server-stats.dashboard');
|
|
167
|
+
function registerDashboardPageRoutes(router, getDashboardController, whenReady) {
|
|
168
|
+
router.get('/', bindDash(getDashboardController, 'page', whenReady)).as('server-stats.dashboard');
|
|
140
169
|
router
|
|
141
|
-
.get('/api/overview', bindDash(getDashboardController, 'overview'))
|
|
170
|
+
.get('/api/overview', bindDash(getDashboardController, 'overview', whenReady))
|
|
142
171
|
.as('server-stats.overview');
|
|
143
172
|
router
|
|
144
|
-
.get('/api/overview/chart', bindDash(getDashboardController, 'overviewChart'))
|
|
173
|
+
.get('/api/overview/chart', bindDash(getDashboardController, 'overviewChart', whenReady))
|
|
145
174
|
.as('server-stats.overview.chart');
|
|
146
175
|
router
|
|
147
|
-
.get('/api/requests', bindDash(getDashboardController, 'requests'))
|
|
176
|
+
.get('/api/requests', bindDash(getDashboardController, 'requests', whenReady))
|
|
148
177
|
.as('server-stats.requests');
|
|
149
178
|
router
|
|
150
|
-
.get('/api/requests/:id', bindDash(getDashboardController, 'requestDetail'))
|
|
179
|
+
.get('/api/requests/:id', bindDash(getDashboardController, 'requestDetail', whenReady))
|
|
151
180
|
.as('server-stats.requests.show');
|
|
152
181
|
}
|
|
153
|
-
function registerQueryDashboardRoutes(router, getDashboardController) {
|
|
182
|
+
function registerQueryDashboardRoutes(router, getDashboardController, whenReady) {
|
|
154
183
|
router
|
|
155
|
-
.get('/api/queries/grouped', bindDash(getDashboardController, 'queriesGrouped'))
|
|
184
|
+
.get('/api/queries/grouped', bindDash(getDashboardController, 'queriesGrouped', whenReady))
|
|
156
185
|
.as('server-stats.queries.grouped');
|
|
157
186
|
router
|
|
158
|
-
.get('/api/queries/:id/explain', bindDash(getDashboardController, 'queryExplain'))
|
|
187
|
+
.get('/api/queries/:id/explain', bindDash(getDashboardController, 'queryExplain', whenReady))
|
|
159
188
|
.as('server-stats.queries.explain');
|
|
160
189
|
}
|
|
161
|
-
function registerCacheRoutes(router, getDashboardController) {
|
|
162
|
-
router
|
|
190
|
+
function registerCacheRoutes(router, getDashboardController, whenReady) {
|
|
191
|
+
router
|
|
192
|
+
.get('/api/cache', bindDash(getDashboardController, 'cacheStats', whenReady))
|
|
193
|
+
.as('server-stats.cache');
|
|
163
194
|
router
|
|
164
|
-
.get('/api/cache/:key', bindDash(getDashboardController, 'cacheKey'))
|
|
195
|
+
.get('/api/cache/:key', bindDash(getDashboardController, 'cacheKey', whenReady))
|
|
165
196
|
.as('server-stats.cache.show')
|
|
166
197
|
.where('key', /.*/);
|
|
167
198
|
router
|
|
168
|
-
.delete('/api/cache/:key', bindDash(getDashboardController, 'cacheKeyDelete'))
|
|
199
|
+
.delete('/api/cache/:key', guardCsrf(bindDash(getDashboardController, 'cacheKeyDelete', whenReady)))
|
|
169
200
|
.as('server-stats.cache.delete')
|
|
170
201
|
.where('key', /.*/);
|
|
171
202
|
}
|
|
172
|
-
function registerJobRoutes(router, getDashboardController) {
|
|
173
|
-
router.get('/api/jobs', bindDash(getDashboardController, 'jobs')).as('server-stats.jobs');
|
|
203
|
+
function registerJobRoutes(router, getDashboardController, whenReady) {
|
|
174
204
|
router
|
|
175
|
-
.get('/api/jobs
|
|
205
|
+
.get('/api/jobs', bindDash(getDashboardController, 'jobs', whenReady))
|
|
206
|
+
.as('server-stats.jobs');
|
|
207
|
+
router
|
|
208
|
+
.get('/api/jobs/:id', bindDash(getDashboardController, 'jobDetail', whenReady))
|
|
176
209
|
.as('server-stats.jobs.show');
|
|
177
210
|
router
|
|
178
|
-
.post('/api/jobs/:id/retry', bindDash(getDashboardController, 'jobRetry'))
|
|
211
|
+
.post('/api/jobs/:id/retry', guardCsrf(bindDash(getDashboardController, 'jobRetry', whenReady)))
|
|
179
212
|
.as('server-stats.jobs.retry');
|
|
180
213
|
}
|
|
181
|
-
function registerConfigAndFilterRoutes(router, getDashboardController) {
|
|
182
|
-
router
|
|
214
|
+
function registerConfigAndFilterRoutes(router, getDashboardController, whenReady) {
|
|
215
|
+
router
|
|
216
|
+
.get('/api/config', bindDash(getDashboardController, 'config', whenReady))
|
|
217
|
+
.as('server-stats.config');
|
|
183
218
|
router
|
|
184
|
-
.get('/api/filters', bindDash(getDashboardController, 'savedFilters'))
|
|
219
|
+
.get('/api/filters', bindDash(getDashboardController, 'savedFilters', whenReady))
|
|
185
220
|
.as('server-stats.filters');
|
|
186
221
|
router
|
|
187
|
-
.post('/api/filters', bindDash(getDashboardController, 'createSavedFilter'))
|
|
222
|
+
.post('/api/filters', guardCsrf(bindDash(getDashboardController, 'createSavedFilter', whenReady)))
|
|
188
223
|
.as('server-stats.filters.create');
|
|
189
224
|
router
|
|
190
|
-
.delete('/api/filters/:id', bindDash(getDashboardController, 'deleteSavedFilter'))
|
|
225
|
+
.delete('/api/filters/:id', guardCsrf(bindDash(getDashboardController, 'deleteSavedFilter', whenReady)))
|
|
191
226
|
.as('server-stats.filters.delete');
|
|
192
227
|
}
|
|
193
228
|
/** Register dashboard routes. */
|
|
194
229
|
export function registerDashboardRoutes(opts) {
|
|
195
230
|
const { router, dashboardPath, getDashboardController, getApiController, middleware } = opts;
|
|
196
|
-
|
|
197
|
-
_whenReady = opts.whenReady;
|
|
231
|
+
const whenReady = opts.whenReady;
|
|
198
232
|
const base = dashboardPath.replace(/\/+$/, '');
|
|
199
233
|
router
|
|
200
234
|
.group(() => {
|
|
201
|
-
registerDashboardPageRoutes(router, getDashboardController);
|
|
202
|
-
registerQueryRoutes(router, getApiController);
|
|
203
|
-
registerEventRoutes(router, getApiController);
|
|
204
|
-
registerRouteApiRoutes(router, getApiController);
|
|
205
|
-
registerLogRoutes(router, getApiController);
|
|
206
|
-
registerEmailRoutes(router, getApiController);
|
|
207
|
-
registerTraceRoutes(router, getApiController);
|
|
208
|
-
registerQueryDashboardRoutes(router, getDashboardController);
|
|
209
|
-
registerCacheRoutes(router, getDashboardController);
|
|
210
|
-
registerJobRoutes(router, getDashboardController);
|
|
211
|
-
registerConfigAndFilterRoutes(router, getDashboardController);
|
|
235
|
+
registerDashboardPageRoutes(router, getDashboardController, whenReady);
|
|
236
|
+
registerQueryRoutes(router, getApiController, whenReady);
|
|
237
|
+
registerEventRoutes(router, getApiController, whenReady);
|
|
238
|
+
registerRouteApiRoutes(router, getApiController, whenReady);
|
|
239
|
+
registerLogRoutes(router, getApiController, whenReady);
|
|
240
|
+
registerEmailRoutes(router, getApiController, whenReady);
|
|
241
|
+
registerTraceRoutes(router, getApiController, whenReady);
|
|
242
|
+
registerQueryDashboardRoutes(router, getDashboardController, whenReady);
|
|
243
|
+
registerCacheRoutes(router, getDashboardController, whenReady);
|
|
244
|
+
registerJobRoutes(router, getDashboardController, whenReady);
|
|
245
|
+
registerConfigAndFilterRoutes(router, getDashboardController, whenReady);
|
|
212
246
|
})
|
|
213
247
|
.prefix(base)
|
|
214
248
|
.use(middleware);
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import { getAppDbClient, buildExplainSql, extractPlan } from '../dashboard/query_explain_handler.js';
|
|
2
|
-
|
|
3
|
-
function bindDebug(getController, method) {
|
|
1
|
+
import { getAppDbClient, buildExplainSql, extractPlan, hasUnsafeSqlTokens, } from '../dashboard/query_explain_handler.js';
|
|
2
|
+
function bindDebug(getController, method, whenReady) {
|
|
4
3
|
return async (ctx) => {
|
|
5
|
-
if (
|
|
6
|
-
await
|
|
4
|
+
if (whenReady)
|
|
5
|
+
await whenReady();
|
|
7
6
|
const controller = getController();
|
|
8
7
|
if (!controller)
|
|
9
8
|
return ctx.response.serviceUnavailable({
|
|
@@ -12,10 +11,10 @@ function bindDebug(getController, method) {
|
|
|
12
11
|
return controller[method].call(controller, ctx);
|
|
13
12
|
};
|
|
14
13
|
}
|
|
15
|
-
function bindApi(getApi, fn) {
|
|
14
|
+
function bindApi(getApi, fn, whenReady) {
|
|
16
15
|
return async (ctx) => {
|
|
17
|
-
if (
|
|
18
|
-
await
|
|
16
|
+
if (whenReady)
|
|
17
|
+
await whenReady();
|
|
19
18
|
const api = getApi();
|
|
20
19
|
if (!api)
|
|
21
20
|
return ctx.response.serviceUnavailable({
|
|
@@ -24,62 +23,69 @@ function bindApi(getApi, fn) {
|
|
|
24
23
|
return fn(api, ctx);
|
|
25
24
|
};
|
|
26
25
|
}
|
|
27
|
-
function registerDebugConfigRoutes(router, getDebugController) {
|
|
28
|
-
router.get('/config', bindDebug(getDebugController, 'config')).as('server-stats.debug.config');
|
|
26
|
+
function registerDebugConfigRoutes(router, getDebugController, whenReady) {
|
|
29
27
|
router
|
|
30
|
-
.get('/
|
|
28
|
+
.get('/config', bindDebug(getDebugController, 'config', whenReady))
|
|
29
|
+
.as('server-stats.debug.config');
|
|
30
|
+
router
|
|
31
|
+
.get('/diagnostics', bindDebug(getDebugController, 'diagnostics', whenReady))
|
|
31
32
|
.as('server-stats.debug.diagnostics');
|
|
32
33
|
}
|
|
33
|
-
function registerDebugQueryAndEventRoutes(router, getApiController) {
|
|
34
|
+
function registerDebugQueryAndEventRoutes(router, getApiController, whenReady) {
|
|
34
35
|
router
|
|
35
36
|
.get('/queries', bindApi(getApiController, async (api, ctx) => {
|
|
36
37
|
const queries = await api.getQueries({ source: 'memory' });
|
|
37
38
|
return ctx.response.json({ queries: queries.data, summary: api.getQuerySummary() });
|
|
38
|
-
}))
|
|
39
|
+
}, whenReady))
|
|
39
40
|
.as('server-stats.debug.queries');
|
|
40
41
|
router
|
|
41
42
|
.get('/events', bindApi(getApiController, async (api, ctx) => {
|
|
42
43
|
const result = await api.getEvents({ source: 'memory' });
|
|
43
44
|
return ctx.response.json({ events: result.data, total: result.meta.total });
|
|
44
|
-
}))
|
|
45
|
+
}, whenReady))
|
|
45
46
|
.as('server-stats.debug.events');
|
|
46
47
|
router
|
|
47
48
|
.get('/routes', bindApi(getApiController, (api, ctx) => {
|
|
48
49
|
const result = api.getRoutes();
|
|
49
50
|
return ctx.response.json({ routes: result.data, total: result.meta.total });
|
|
50
|
-
}))
|
|
51
|
+
}, whenReady))
|
|
51
52
|
.as('server-stats.debug.routes');
|
|
52
53
|
}
|
|
53
|
-
function registerDebugLogRoutes(router, getApiController) {
|
|
54
|
+
function registerDebugLogRoutes(router, getApiController, whenReady) {
|
|
54
55
|
router
|
|
55
56
|
.get('/logs', bindApi(getApiController, async (api, ctx) => {
|
|
56
57
|
const result = await api.getLogs({ source: 'auto', perPage: 200 });
|
|
57
58
|
return ctx.response.json(result.data);
|
|
58
|
-
}))
|
|
59
|
+
}, whenReady))
|
|
59
60
|
.as('server-stats.debug.logs');
|
|
60
61
|
}
|
|
61
|
-
function registerDebugEmailRoutes(router, getApiController) {
|
|
62
|
+
function registerDebugEmailRoutes(router, getApiController, whenReady) {
|
|
62
63
|
router
|
|
63
64
|
.get('/emails', bindApi(getApiController, async (api, ctx) => {
|
|
64
65
|
const result = await api.getEmails({});
|
|
65
66
|
return ctx.response.json({ emails: result.data, total: result.meta.total });
|
|
66
|
-
}))
|
|
67
|
+
}, whenReady))
|
|
67
68
|
.as('server-stats.debug.emails');
|
|
68
69
|
router
|
|
69
70
|
.get('/emails/:id/preview', bindApi(getApiController, async (api, ctx) => {
|
|
70
71
|
const html = await api.getEmailPreview(Number(ctx.params.id));
|
|
71
72
|
if (!html)
|
|
72
73
|
return ctx.response.notFound({ error: 'Email not found' });
|
|
73
|
-
return ctx.response
|
|
74
|
-
|
|
74
|
+
return ctx.response
|
|
75
|
+
.header('Content-Type', 'text/html; charset=utf-8')
|
|
76
|
+
.header('Content-Security-Policy', "default-src 'none'; style-src 'unsafe-inline'")
|
|
77
|
+
.header('X-Content-Type-Options', 'nosniff')
|
|
78
|
+
.header('X-Frame-Options', 'SAMEORIGIN')
|
|
79
|
+
.send(html);
|
|
80
|
+
}, whenReady))
|
|
75
81
|
.as('server-stats.debug.emailPreview');
|
|
76
82
|
}
|
|
77
|
-
function registerDebugTraceRoutes(router, getApiController) {
|
|
83
|
+
function registerDebugTraceRoutes(router, getApiController, whenReady) {
|
|
78
84
|
router
|
|
79
85
|
.get('/traces', bindApi(getApiController, async (api, ctx) => {
|
|
80
86
|
const result = await api.getTraces({ source: 'memory' });
|
|
81
87
|
return ctx.response.json({ traces: result.data, total: result.meta.total });
|
|
82
|
-
}))
|
|
88
|
+
}, whenReady))
|
|
83
89
|
.as('server-stats.debug.traces');
|
|
84
90
|
router
|
|
85
91
|
.get('/traces/:id', bindApi(getApiController, async (api, ctx) => {
|
|
@@ -87,16 +93,16 @@ function registerDebugTraceRoutes(router, getApiController) {
|
|
|
87
93
|
if (!trace)
|
|
88
94
|
return ctx.response.notFound({ error: 'Trace not found' });
|
|
89
95
|
return ctx.response.json(trace);
|
|
90
|
-
}))
|
|
96
|
+
}, whenReady))
|
|
91
97
|
.as('server-stats.debug.traceDetail');
|
|
92
98
|
}
|
|
93
|
-
function registerDebugExplainRoute(router, getDebugStore, getApp) {
|
|
99
|
+
function registerDebugExplainRoute(router, whenReady, getDebugStore, getApp) {
|
|
94
100
|
if (!getDebugStore || !getApp)
|
|
95
101
|
return;
|
|
96
102
|
router
|
|
97
103
|
.get('/queries/:id/explain', async (ctx) => {
|
|
98
|
-
if (
|
|
99
|
-
await
|
|
104
|
+
if (whenReady)
|
|
105
|
+
await whenReady();
|
|
100
106
|
const store = getDebugStore();
|
|
101
107
|
const app = getApp();
|
|
102
108
|
if (!store || !app) {
|
|
@@ -108,6 +114,11 @@ function registerDebugExplainRoute(router, getDebugStore, getApp) {
|
|
|
108
114
|
if (!query.sql.trim().toUpperCase().startsWith('SELECT')) {
|
|
109
115
|
return ctx.response.badRequest({ error: 'EXPLAIN is only supported for SELECT queries' });
|
|
110
116
|
}
|
|
117
|
+
if (hasUnsafeSqlTokens(query.sql)) {
|
|
118
|
+
return ctx.response.badRequest({
|
|
119
|
+
error: 'EXPLAIN rejected: query contains disallowed tokens',
|
|
120
|
+
});
|
|
121
|
+
}
|
|
111
122
|
try {
|
|
112
123
|
const appDb = await getAppDbClient(app, query.connection || undefined);
|
|
113
124
|
if (!appDb) {
|
|
@@ -136,17 +147,16 @@ function registerDebugExplainRoute(router, getDebugStore, getApp) {
|
|
|
136
147
|
/** Register debug panel API routes. */
|
|
137
148
|
export function registerDebugRoutes(opts) {
|
|
138
149
|
const { router, debugEndpoint, getDebugController, getApiController, middleware } = opts;
|
|
139
|
-
|
|
140
|
-
_whenReady = opts.whenReady;
|
|
150
|
+
const whenReady = opts.whenReady;
|
|
141
151
|
const base = debugEndpoint.replace(/\/+$/, '');
|
|
142
152
|
router
|
|
143
153
|
.group(() => {
|
|
144
|
-
registerDebugConfigRoutes(router, getDebugController);
|
|
145
|
-
registerDebugQueryAndEventRoutes(router, getApiController);
|
|
146
|
-
registerDebugExplainRoute(router, opts.getDebugStore, opts.getApp);
|
|
147
|
-
registerDebugLogRoutes(router, getApiController);
|
|
148
|
-
registerDebugEmailRoutes(router, getApiController);
|
|
149
|
-
registerDebugTraceRoutes(router, getApiController);
|
|
154
|
+
registerDebugConfigRoutes(router, getDebugController, whenReady);
|
|
155
|
+
registerDebugQueryAndEventRoutes(router, getApiController, whenReady);
|
|
156
|
+
registerDebugExplainRoute(router, whenReady, opts.getDebugStore, opts.getApp);
|
|
157
|
+
registerDebugLogRoutes(router, getApiController, whenReady);
|
|
158
|
+
registerDebugEmailRoutes(router, getApiController, whenReady);
|
|
159
|
+
registerDebugTraceRoutes(router, getApiController, whenReady);
|
|
150
160
|
})
|
|
151
161
|
.prefix(base)
|
|
152
162
|
.use(middleware);
|
|
@@ -7,8 +7,11 @@ import type { HttpContext } from '@adonisjs/core/http';
|
|
|
7
7
|
* Because this package's routes are polled every few seconds, this causes
|
|
8
8
|
* rapid cookie accumulation that can break the browser.
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* The `Set-Cookie` header is stripped **before** `next()` runs so it is gone
|
|
11
|
+
* even when a downstream handler flushes headers early (e.g. a streaming/SSE
|
|
12
|
+
* route under these prefixes) — stripping only after `next()` would be a no-op
|
|
13
|
+
* once the response has already been flushed. It is stripped again after
|
|
14
|
+
* `next()` to also cover cookies set by later global middleware on buffered
|
|
15
|
+
* responses.
|
|
13
16
|
*/
|
|
14
17
|
export declare function noSessionMiddleware(ctx: HttpContext, next: () => Promise<void>): Promise<void>;
|
|
@@ -6,11 +6,15 @@
|
|
|
6
6
|
* Because this package's routes are polled every few seconds, this causes
|
|
7
7
|
* rapid cookie accumulation that can break the browser.
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
9
|
+
* The `Set-Cookie` header is stripped **before** `next()` runs so it is gone
|
|
10
|
+
* even when a downstream handler flushes headers early (e.g. a streaming/SSE
|
|
11
|
+
* route under these prefixes) — stripping only after `next()` would be a no-op
|
|
12
|
+
* once the response has already been flushed. It is stripped again after
|
|
13
|
+
* `next()` to also cover cookies set by later global middleware on buffered
|
|
14
|
+
* responses.
|
|
12
15
|
*/
|
|
13
16
|
export async function noSessionMiddleware(ctx, next) {
|
|
17
|
+
ctx.response.response.removeHeader('set-cookie');
|
|
14
18
|
await next();
|
|
15
19
|
ctx.response.response.removeHeader('set-cookie');
|
|
16
20
|
}
|
|
@@ -21,6 +21,14 @@ export interface RegisterRoutesOptions {
|
|
|
21
21
|
debugEndpoint?: string;
|
|
22
22
|
dashboardPath?: string;
|
|
23
23
|
shouldShow?: (ctx: HttpContext) => boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Escape hatch to register the sensitive dashboard/debug/stats routes WITHOUT
|
|
26
|
+
* any access guard when no `shouldShow` callback is provided. Off by default —
|
|
27
|
+
* without either `shouldShow` or this flag, sensitive routes are NOT registered
|
|
28
|
+
* (fail closed). Enabling this exposes the dashboard (which can surface secrets,
|
|
29
|
+
* email bodies, and SQL) to anyone who can reach it. Local dev only.
|
|
30
|
+
*/
|
|
31
|
+
unsafeAllowNoAuth?: boolean;
|
|
24
32
|
/** Optional promise that resolves when controllers are initialized. */
|
|
25
33
|
whenReady?: () => Promise<void>;
|
|
26
34
|
}
|
|
@@ -3,10 +3,29 @@ import { registerDashboardRoutes } from './dashboard_routes.js';
|
|
|
3
3
|
import { registerDebugRoutes } from './debug_routes.js';
|
|
4
4
|
import { noSessionMiddleware } from './no_session_middleware.js';
|
|
5
5
|
import { registerStatsRoute } from './stats_routes.js';
|
|
6
|
+
import { log } from '../utils/logger.js';
|
|
7
|
+
/** One-time warning latch for the unsafeAllowNoAuth escape hatch. */
|
|
8
|
+
let _warnedNoAuth = false;
|
|
6
9
|
/**
|
|
7
10
|
* Register all server-stats routes in a single call.
|
|
8
11
|
*/
|
|
9
12
|
export function registerAllRoutes(options) {
|
|
13
|
+
// Fail closed: without an access guard (`shouldShow`) the sensitive routes must
|
|
14
|
+
// not be registered unless the caller explicitly opts in via `unsafeAllowNoAuth`.
|
|
15
|
+
if (!options.shouldShow) {
|
|
16
|
+
if (!options.unsafeAllowNoAuth) {
|
|
17
|
+
log.warn('server-stats: no `authorize`/`shouldShow` guard configured — sensitive routes ' +
|
|
18
|
+
'(dashboard, debug API, stats) will NOT be registered. Provide an authorize callback, ' +
|
|
19
|
+
'or set `unsafeAllowNoAuth: true` to expose them without auth (local dev only).');
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
if (!_warnedNoAuth) {
|
|
23
|
+
_warnedNoAuth = true;
|
|
24
|
+
log.warn('server-stats: `unsafeAllowNoAuth` is enabled — the dashboard is exposed WITHOUT ' +
|
|
25
|
+
'authentication. It can surface secrets, email bodies, and SQL to anyone who can ' +
|
|
26
|
+
'reach it. Do not use this outside strictly local development.');
|
|
27
|
+
}
|
|
28
|
+
}
|
|
10
29
|
const middleware = [
|
|
11
30
|
noSessionMiddleware,
|
|
12
31
|
...(options.shouldShow ? [createAccessMiddleware(options.shouldShow)] : []),
|
package/dist/src/types.d.ts
CHANGED
|
@@ -751,6 +751,18 @@ export interface ServerStatsConfig {
|
|
|
751
751
|
* ```
|
|
752
752
|
*/
|
|
753
753
|
authorize?: (ctx: import('@adonisjs/core/http').HttpContext) => boolean;
|
|
754
|
+
/**
|
|
755
|
+
* Register the sensitive dashboard/debug/stats routes WITHOUT any access
|
|
756
|
+
* guard when no {@link authorize} callback is provided.
|
|
757
|
+
*
|
|
758
|
+
* The package fails closed: without either {@link authorize} or this flag,
|
|
759
|
+
* the sensitive routes are **not** registered. Enabling this exposes the
|
|
760
|
+
* dashboard — which can surface secrets, email bodies, and SQL — to anyone
|
|
761
|
+
* who can reach it. Local development only.
|
|
762
|
+
*
|
|
763
|
+
* @default false
|
|
764
|
+
*/
|
|
765
|
+
unsafeAllowNoAuth?: boolean;
|
|
754
766
|
/**
|
|
755
767
|
* Enable the debug panel (toolbar overlay).
|
|
756
768
|
*
|
|
@@ -838,6 +850,25 @@ export interface ResolvedServerStatsConfig {
|
|
|
838
850
|
devToolbar?: DevToolbarOptions;
|
|
839
851
|
/** Optional access-control callback. */
|
|
840
852
|
shouldShow?: (ctx: import('@adonisjs/core/http').HttpContext) => boolean;
|
|
853
|
+
/** Collection interval in milliseconds (new name for {@link intervalMs}). */
|
|
854
|
+
pollInterval?: number;
|
|
855
|
+
/** Whether real-time (SSE) broadcasting is enabled (new name for {@link transport}). */
|
|
856
|
+
realtime?: boolean;
|
|
857
|
+
/** HTTP endpoint path or `false` to disable (new name for {@link endpoint}). */
|
|
858
|
+
statsEndpoint?: string | false;
|
|
859
|
+
/** Access-control callback (new name for {@link shouldShow}). */
|
|
860
|
+
authorize?: (ctx: import('@adonisjs/core/http').HttpContext) => boolean;
|
|
861
|
+
/**
|
|
862
|
+
* Escape hatch to register sensitive routes without an access guard.
|
|
863
|
+
* Exposes the dashboard without auth — local development only.
|
|
864
|
+
*/
|
|
865
|
+
unsafeAllowNoAuth?: boolean;
|
|
866
|
+
/** Debug panel configuration (new name/shape replacing {@link devToolbar}). */
|
|
867
|
+
toolbar?: boolean | ToolbarConfig;
|
|
868
|
+
/** Full-page dashboard configuration. */
|
|
869
|
+
dashboard?: boolean | DashboardConfig;
|
|
870
|
+
/** Advanced fine-tuning options. */
|
|
871
|
+
advanced?: AdvancedConfig;
|
|
841
872
|
/** Whether verbose informational logging is enabled. Always present after `defineConfig()`. */
|
|
842
873
|
verbose: boolean;
|
|
843
874
|
}
|
|
@@ -36,12 +36,16 @@ export async function appImport(specifier) {
|
|
|
36
36
|
try {
|
|
37
37
|
return await import(pathToFileURL(resolved).href);
|
|
38
38
|
}
|
|
39
|
-
catch {
|
|
39
|
+
catch (err) {
|
|
40
|
+
errors.push(err);
|
|
41
|
+
}
|
|
40
42
|
// Strategy B: CJS require (works for native addons like better-sqlite3)
|
|
41
43
|
try {
|
|
42
44
|
return appRequire(specifier);
|
|
43
45
|
}
|
|
44
|
-
catch {
|
|
46
|
+
catch (err) {
|
|
47
|
+
errors.push(err);
|
|
48
|
+
}
|
|
45
49
|
}
|
|
46
50
|
catch (err) {
|
|
47
51
|
errors.push(err);
|
|
@@ -70,13 +74,17 @@ export async function appImportWithPath(specifier) {
|
|
|
70
74
|
const module = await import(pathToFileURL(resolved).href);
|
|
71
75
|
return { module: module, resolvedPath: resolved };
|
|
72
76
|
}
|
|
73
|
-
catch {
|
|
77
|
+
catch (err) {
|
|
78
|
+
errors.push(err);
|
|
79
|
+
}
|
|
74
80
|
// Strategy B: CJS require (works for native addons like better-sqlite3)
|
|
75
81
|
try {
|
|
76
82
|
const module = appRequire(specifier);
|
|
77
83
|
return { module, resolvedPath: resolved };
|
|
78
84
|
}
|
|
79
|
-
catch {
|
|
85
|
+
catch (err) {
|
|
86
|
+
errors.push(err);
|
|
87
|
+
}
|
|
80
88
|
}
|
|
81
89
|
catch (err) {
|
|
82
90
|
errors.push(err);
|