adonisjs-server-stats 1.3.2 → 1.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/README.md +114 -116
  2. package/dist/configure.d.ts.map +1 -1
  3. package/dist/src/controller/debug_controller.d.ts +2 -2
  4. package/dist/src/controller/debug_controller.d.ts.map +1 -1
  5. package/dist/src/controller/server_stats_controller.d.ts +1 -1
  6. package/dist/src/controller/server_stats_controller.d.ts.map +1 -1
  7. package/dist/src/dashboard/chart_aggregator.d.ts.map +1 -1
  8. package/dist/src/dashboard/chart_aggregator.js +8 -8
  9. package/dist/src/dashboard/dashboard_controller.d.ts +12 -97
  10. package/dist/src/dashboard/dashboard_controller.d.ts.map +1 -1
  11. package/dist/src/dashboard/dashboard_controller.js +258 -489
  12. package/dist/src/dashboard/dashboard_routes.d.ts.map +1 -1
  13. package/dist/src/dashboard/dashboard_routes.js +7 -2
  14. package/dist/src/dashboard/dashboard_store.d.ts +39 -3
  15. package/dist/src/dashboard/dashboard_store.d.ts.map +1 -1
  16. package/dist/src/dashboard/dashboard_store.js +145 -78
  17. package/dist/src/dashboard/integrations/cache_inspector.d.ts.map +1 -1
  18. package/dist/src/dashboard/integrations/queue_inspector.d.ts.map +1 -1
  19. package/dist/src/dashboard/migrator.d.ts.map +1 -1
  20. package/dist/src/dashboard/migrator.js +3 -1
  21. package/dist/src/dashboard/models/stats_event.d.ts +1 -1
  22. package/dist/src/dashboard/models/stats_event.d.ts.map +1 -1
  23. package/dist/src/dashboard/models/stats_query.d.ts +1 -1
  24. package/dist/src/dashboard/models/stats_query.d.ts.map +1 -1
  25. package/dist/src/dashboard/models/stats_request.d.ts +2 -2
  26. package/dist/src/dashboard/models/stats_request.d.ts.map +1 -1
  27. package/dist/src/dashboard/models/stats_request.js +1 -1
  28. package/dist/src/dashboard/models/stats_trace.d.ts +1 -1
  29. package/dist/src/dashboard/models/stats_trace.d.ts.map +1 -1
  30. package/dist/src/debug/debug_store.d.ts +6 -6
  31. package/dist/src/debug/debug_store.d.ts.map +1 -1
  32. package/dist/src/debug/debug_store.js +10 -10
  33. package/dist/src/debug/email_collector.d.ts +0 -9
  34. package/dist/src/debug/email_collector.d.ts.map +1 -1
  35. package/dist/src/debug/email_collector.js +6 -28
  36. package/dist/src/debug/event_collector.d.ts +1 -1
  37. package/dist/src/debug/event_collector.d.ts.map +1 -1
  38. package/dist/src/debug/event_collector.js +17 -17
  39. package/dist/src/debug/query_collector.d.ts +1 -1
  40. package/dist/src/debug/query_collector.d.ts.map +1 -1
  41. package/dist/src/debug/query_collector.js +13 -14
  42. package/dist/src/debug/ring_buffer.d.ts.map +1 -1
  43. package/dist/src/debug/route_inspector.d.ts +1 -1
  44. package/dist/src/debug/route_inspector.d.ts.map +1 -1
  45. package/dist/src/debug/route_inspector.js +12 -12
  46. package/dist/src/debug/trace_collector.d.ts.map +1 -1
  47. package/dist/src/debug/trace_collector.js +6 -5
  48. package/dist/src/edge/client/dashboard.css +555 -165
  49. package/dist/src/edge/client/dashboard.js +2797 -1556
  50. package/dist/src/edge/client/debug-panel.css +476 -133
  51. package/dist/src/edge/client/debug-panel.js +1496 -1043
  52. package/dist/src/edge/client/stats-bar.css +64 -30
  53. package/dist/src/edge/client/stats-bar.js +598 -319
  54. package/dist/src/edge/plugin.d.ts +1 -1
  55. package/dist/src/edge/plugin.d.ts.map +1 -1
  56. package/dist/src/edge/plugin.js +41 -59
  57. package/dist/src/edge/views/stats-bar.edge +1 -1
  58. package/dist/src/index.d.ts +1 -1
  59. package/dist/src/index.d.ts.map +1 -1
  60. package/dist/src/middleware/request_tracking_middleware.d.ts +4 -4
  61. package/dist/src/middleware/request_tracking_middleware.d.ts.map +1 -1
  62. package/dist/src/middleware/request_tracking_middleware.js +7 -6
  63. package/dist/src/prometheus/prometheus_collector.d.ts +1 -1
  64. package/dist/src/prometheus/prometheus_collector.d.ts.map +1 -1
  65. package/dist/src/provider/server_stats_provider.d.ts +1 -1
  66. package/dist/src/provider/server_stats_provider.d.ts.map +1 -1
  67. package/dist/src/provider/server_stats_provider.js +31 -31
  68. package/dist/src/utils/json_helpers.d.ts +8 -0
  69. package/dist/src/utils/json_helpers.d.ts.map +1 -0
  70. package/dist/src/utils/json_helpers.js +21 -0
  71. package/dist/src/utils/mail_helpers.d.ts +13 -0
  72. package/dist/src/utils/mail_helpers.d.ts.map +1 -0
  73. package/dist/src/utils/mail_helpers.js +26 -0
  74. package/dist/src/utils/math_helpers.d.ts +8 -0
  75. package/dist/src/utils/math_helpers.d.ts.map +1 -0
  76. package/dist/src/utils/math_helpers.js +11 -0
  77. package/dist/src/utils/time_helpers.d.ts +12 -0
  78. package/dist/src/utils/time_helpers.d.ts.map +1 -0
  79. package/dist/src/utils/time_helpers.js +32 -0
  80. package/dist/src/utils/transmit_client.d.ts +9 -0
  81. package/dist/src/utils/transmit_client.d.ts.map +1 -0
  82. package/dist/src/utils/transmit_client.js +20 -0
  83. package/package.json +35 -29
package/README.md CHANGED
@@ -19,16 +19,16 @@ Zero frontend dependencies. Zero build step. Just `@serverStats()` and go.
19
19
 
20
20
  **Debug toolbar** -- expandable panels for deep inspection:
21
21
 
22
- | Queries | Events |
23
- |---------|--------|
22
+ | Queries | Events |
23
+ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
24
24
  | ![Queries panel showing SQL queries with duration and model info](https://raw.githubusercontent.com/simulieren/adonisjs-server-stats/main/screenshots/debug-queries.png) | ![Events panel showing application events with payload data](https://raw.githubusercontent.com/simulieren/adonisjs-server-stats/main/screenshots/debug-events.png) |
25
25
 
26
- | Routes | Logs |
27
- |--------|------|
26
+ | Routes | Logs |
27
+ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
28
28
  | ![Routes panel showing all registered routes with handlers](https://raw.githubusercontent.com/simulieren/adonisjs-server-stats/main/screenshots/debug-routes.png) | ![Logs panel with level filtering and request ID correlation](https://raw.githubusercontent.com/simulieren/adonisjs-server-stats/main/screenshots/debug-logs.png) |
29
29
 
30
- | Emails (custom pane) |
31
- |----------------------|
30
+ | Emails (custom pane) |
31
+ | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
32
32
  | ![Emails panel showing sent emails with delivery status](https://raw.githubusercontent.com/simulieren/adonisjs-server-stats/main/screenshots/debug-emails.png) |
33
33
 
34
34
  ## Features
@@ -74,9 +74,7 @@ providers: [
74
74
 
75
75
  ```ts
76
76
  // start/kernel.ts
77
- server.use([
78
- () => import('adonisjs-server-stats/middleware'),
79
- ])
77
+ server.use([() => import('adonisjs-server-stats/middleware')])
80
78
  ```
81
79
 
82
80
  ### 3. Create config
@@ -87,11 +85,7 @@ import { defineConfig } from 'adonisjs-server-stats'
87
85
  import { processCollector, systemCollector, httpCollector } from 'adonisjs-server-stats/collectors'
88
86
 
89
87
  export default defineConfig({
90
- collectors: [
91
- processCollector(),
92
- systemCollector(),
93
- httpCollector(),
94
- ],
88
+ collectors: [processCollector(), systemCollector(), httpCollector()],
95
89
  })
96
90
  ```
97
91
 
@@ -184,7 +178,7 @@ import type { StatsEngine } from 'adonisjs-server-stats'
184
178
 
185
179
  export default class ServerStatsController {
186
180
  async index({ response }: HttpContext) {
187
- const engine = await app.container.make('server_stats.engine') as StatsEngine
181
+ const engine = (await app.container.make('server_stats.engine')) as StatsEngine
188
182
  return response.json(engine.getLatestStats())
189
183
  }
190
184
  }
@@ -204,36 +198,36 @@ export default class ServerStatsController {
204
198
 
205
199
  ### `ServerStatsConfig`
206
200
 
207
- | Option | Type | Default | Description |
208
- |---------------|------------------------|-----------------------------|--------------------------------------------|
209
- | `intervalMs` | `number` | `3000` | Collection + broadcast interval (ms) |
210
- | `transport` | `'transmit' \| 'none'` | `'transmit'` | SSE transport. `'none'` = poll-only. |
211
- | `channelName` | `string` | `'admin/server-stats'` | Transmit channel name |
212
- | `endpoint` | `string \| false` | `'/admin/api/server-stats'` | HTTP endpoint. `false` to disable. |
213
- | `collectors` | `MetricCollector[]` | `[]` | Array of collector instances |
214
- | `skipInTest` | `boolean` | `true` | Skip collection during tests |
215
- | `onStats` | `(stats) => void` | -- | Callback after each collection tick |
216
- | `shouldShow` | `(ctx) => boolean` | -- | Per-request visibility guard |
217
- | `devToolbar` | `DevToolbarOptions` | -- | Dev toolbar configuration |
201
+ | Option | Type | Default | Description |
202
+ | ------------- | ---------------------- | --------------------------- | ------------------------------------ |
203
+ | `intervalMs` | `number` | `3000` | Collection + broadcast interval (ms) |
204
+ | `transport` | `'transmit' \| 'none'` | `'transmit'` | SSE transport. `'none'` = poll-only. |
205
+ | `channelName` | `string` | `'admin/server-stats'` | Transmit channel name |
206
+ | `endpoint` | `string \| false` | `'/admin/api/server-stats'` | HTTP endpoint. `false` to disable. |
207
+ | `collectors` | `MetricCollector[]` | `[]` | Array of collector instances |
208
+ | `skipInTest` | `boolean` | `true` | Skip collection during tests |
209
+ | `onStats` | `(stats) => void` | -- | Callback after each collection tick |
210
+ | `shouldShow` | `(ctx) => boolean` | -- | Per-request visibility guard |
211
+ | `devToolbar` | `DevToolbarOptions` | -- | Dev toolbar configuration |
218
212
 
219
213
  ### `DevToolbarOptions`
220
214
 
221
- | Option | Type | Default | Description |
222
- |------------------------|-----------------|---------|------------------------------------------------|
223
- | `enabled` | `boolean` | `false` | Enable the dev toolbar |
224
- | `maxQueries` | `number` | `500` | Max SQL queries to buffer |
225
- | `maxEvents` | `number` | `200` | Max events to buffer |
226
- | `maxEmails` | `number` | `100` | Max emails to buffer |
227
- | `slowQueryThresholdMs` | `number` | `100` | Slow query threshold (ms) |
228
- | `persistDebugData` | `boolean \| string` | `false` | Persist debug data to disk across restarts. `true` writes to `.adonisjs/server-stats/debug-data.json`, or pass a custom path. |
229
- | `tracing` | `boolean` | `false` | Enable per-request tracing with timeline visualization |
230
- | `maxTraces` | `number` | `200` | Max request traces to buffer |
231
- | `dashboard` | `boolean` | `false` | Enable the full-page dashboard (requires `better-sqlite3`) |
232
- | `dashboardPath` | `string` | `'/__stats'` | URL path for the dashboard page |
233
- | `retentionDays` | `number` | `7` | Days to keep historical data in SQLite |
234
- | `dbPath` | `string` | `'.adonisjs/server-stats/dashboard.sqlite3'` | Path to the SQLite database file (relative to app root) |
235
- | `excludeFromTracing` | `string[]` | `[]` | URL prefixes to exclude from tracing and dashboard persistence. Requests still count toward HTTP metrics but won't appear in the timeline or be stored. The stats endpoint is always excluded automatically. |
236
- | `panes` | `DebugPane[]` | -- | Custom debug panel tabs |
215
+ | Option | Type | Default | Description |
216
+ | ---------------------- | ------------------- | -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
217
+ | `enabled` | `boolean` | `false` | Enable the dev toolbar |
218
+ | `maxQueries` | `number` | `500` | Max SQL queries to buffer |
219
+ | `maxEvents` | `number` | `200` | Max events to buffer |
220
+ | `maxEmails` | `number` | `100` | Max emails to buffer |
221
+ | `slowQueryThresholdMs` | `number` | `100` | Slow query threshold (ms) |
222
+ | `persistDebugData` | `boolean \| string` | `false` | Persist debug data to disk across restarts. `true` writes to `.adonisjs/server-stats/debug-data.json`, or pass a custom path. |
223
+ | `tracing` | `boolean` | `false` | Enable per-request tracing with timeline visualization |
224
+ | `maxTraces` | `number` | `200` | Max request traces to buffer |
225
+ | `dashboard` | `boolean` | `false` | Enable the full-page dashboard (requires `better-sqlite3`) |
226
+ | `dashboardPath` | `string` | `'/__stats'` | URL path for the dashboard page |
227
+ | `retentionDays` | `number` | `7` | Days to keep historical data in SQLite |
228
+ | `dbPath` | `string` | `'.adonisjs/server-stats/dashboard.sqlite3'` | Path to the SQLite database file (relative to app root) |
229
+ | `excludeFromTracing` | `string[]` | `[]` | URL prefixes to exclude from tracing and dashboard persistence. Requests still count toward HTTP metrics but won't appear in the timeline or be stored. The stats endpoint is always excluded automatically. |
230
+ | `panes` | `DebugPane[]` | -- | Custom debug panel tabs |
237
231
 
238
232
  ---
239
233
 
@@ -243,27 +237,27 @@ Each collector is a factory function that returns a `MetricCollector`. All colle
243
237
 
244
238
  ### Built-in Collectors
245
239
 
246
- | Collector | Metrics | Options | Peer Deps |
247
- |--------------------------|-------------------------------------------------------------|------------|---------------------|
248
- | `processCollector()` | CPU %, event loop lag, heap/RSS memory, uptime, Node version | none | -- |
249
- | `systemCollector()` | OS load averages, system memory, system uptime | none | -- |
250
- | `httpCollector(opts?)` | Requests/sec, avg response time, error rate, active connections | optional | -- |
251
- | `dbPoolCollector(opts?)` | Pool used/free/pending/max connections | optional | `@adonisjs/lucid` |
252
- | `redisCollector()` | Status, memory, clients, keys, hit rate | none | `@adonisjs/redis` |
253
- | `queueCollector(opts)` | Active/waiting/delayed/failed jobs, worker count | **required** | `bullmq` |
254
- | `logCollector(opts)` | Errors/warnings/entries (5m window), entries/minute | **required** | -- |
255
- | `appCollector()` | Online users, pending webhooks, pending emails | none | `@adonisjs/lucid` |
240
+ | Collector | Metrics | Options | Peer Deps |
241
+ | ------------------------ | --------------------------------------------------------------- | ------------ | ----------------- |
242
+ | `processCollector()` | CPU %, event loop lag, heap/RSS memory, uptime, Node version | none | -- |
243
+ | `systemCollector()` | OS load averages, system memory, system uptime | none | -- |
244
+ | `httpCollector(opts?)` | Requests/sec, avg response time, error rate, active connections | optional | -- |
245
+ | `dbPoolCollector(opts?)` | Pool used/free/pending/max connections | optional | `@adonisjs/lucid` |
246
+ | `redisCollector()` | Status, memory, clients, keys, hit rate | none | `@adonisjs/redis` |
247
+ | `queueCollector(opts)` | Active/waiting/delayed/failed jobs, worker count | **required** | `bullmq` |
248
+ | `logCollector(opts)` | Errors/warnings/entries (5m window), entries/minute | **required** | -- |
249
+ | `appCollector()` | Online users, pending webhooks, pending emails | none | `@adonisjs/lucid` |
256
250
 
257
251
  ### Collector Options
258
252
 
259
253
  ```ts
260
254
  httpCollector({
261
- maxRecords: 10_000, // Circular buffer size (default: 10,000)
262
- windowMs: 60_000, // Rolling window for rate calc (default: 60s)
255
+ maxRecords: 10_000, // Circular buffer size (default: 10,000)
256
+ windowMs: 60_000, // Rolling window for rate calc (default: 60s)
263
257
  })
264
258
 
265
259
  dbPoolCollector({
266
- connectionName: 'postgres', // Lucid connection name (default: 'postgres')
260
+ connectionName: 'postgres', // Lucid connection name (default: 'postgres')
267
261
  })
268
262
 
269
263
  queueCollector({
@@ -305,7 +299,7 @@ function diskCollector(): MetricCollector {
305
299
  export default defineConfig({
306
300
  collectors: [
307
301
  processCollector(),
308
- diskCollector(), // mix with built-in collectors
302
+ diskCollector(), // mix with built-in collectors
309
303
  ],
310
304
  })
311
305
  ```
@@ -369,6 +363,7 @@ The `@serverStats()` Edge tag renders a self-contained stats bar with inline HTM
369
363
  ```
370
364
 
371
365
  Features:
366
+
372
367
  - Polls the stats API at the configured interval
373
368
  - Color-coded thresholds (green/amber/red)
374
369
  - SVG sparkline charts with gradient fills
@@ -392,8 +387,8 @@ export default defineConfig({
392
387
  maxEvents: 200,
393
388
  maxEmails: 100,
394
389
  slowQueryThresholdMs: 100,
395
- persistDebugData: true, // or a custom path: 'custom/debug.json'
396
- tracing: true, // enable per-request timeline
390
+ persistDebugData: true, // or a custom path: 'custom/debug.json'
391
+ tracing: true, // enable per-request timeline
397
392
  },
398
393
  })
399
394
  ```
@@ -423,6 +418,7 @@ The debug toolbar captures all emails sent via AdonisJS mail (`mail:sending`, `m
423
418
  ### Persistent Debug Data
424
419
 
425
420
  Enable `persistDebugData: true` to save queries, events, and emails to `.adonisjs/server-stats/debug-data.json`. You can also pass a custom path (relative to app root) like `persistDebugData: 'custom/debug.json'`. Data is:
421
+
426
422
  - **Loaded** on server startup (before collectors start)
427
423
  - **Flushed** every 30 seconds (handles crashes)
428
424
  - **Saved** on graceful shutdown
@@ -449,14 +445,14 @@ GET /organizations/create 286ms
449
445
 
450
446
  #### Span categories
451
447
 
452
- | Category | Color | Auto-captured |
453
- |----------|--------|---------------|
454
- | DB | Purple | `db:query` events |
448
+ | Category | Color | Auto-captured |
449
+ | -------- | ------ | ---------------------- |
450
+ | DB | Purple | `db:query` events |
455
451
  | Request | Blue | Full request lifecycle |
456
- | Mail | Green | -- |
457
- | Event | Amber | -- |
458
- | View | Cyan | -- |
459
- | Custom | Gray | Via `trace()` helper |
452
+ | Mail | Green | -- |
453
+ | Event | Amber | -- |
454
+ | View | Cyan | -- |
455
+ | Custom | Gray | Via `trace()` helper |
460
456
 
461
457
  #### Custom spans
462
458
 
@@ -527,19 +523,19 @@ devToolbar: {
527
523
 
528
524
  #### Dashboard Sections
529
525
 
530
- | Section | Description |
531
- |------------|-------------|
532
- | **Overview** | Performance cards (avg/p95 response time, req/min, error rate) with sparkline charts and configurable time ranges (1h/6h/24h/7d) |
533
- | **Requests** | Paginated request history with method, URL, status, duration. Click for detail view with associated queries and trace |
526
+ | Section | Description |
527
+ | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
528
+ | **Overview** | Performance cards (avg/p95 response time, req/min, error rate) with sparkline charts and configurable time ranges (1h/6h/24h/7d) |
529
+ | **Requests** | Paginated request history with method, URL, status, duration. Click for detail view with associated queries and trace |
534
530
  | **Queries** | All captured SQL queries with duration, model, connection. Grouped view shows query patterns by normalized SQL. EXPLAIN plan support for SELECT queries |
535
- | **Events** | Application events captured from the AdonisJS emitter |
536
- | **Routes** | Full route table with methods, patterns, handlers, and middleware stacks |
537
- | **Logs** | Log history with level filtering, text search, and structured JSON field search (e.g. filter by `userId = 5`) |
538
- | **Emails** | Email history with sender, recipient, subject, status. Click for HTML preview in iframe |
539
- | **Timeline** | Per-request waterfall timeline (requires `tracing: true`) |
540
- | **Cache** | Redis key browser with SCAN-based listing, type-aware detail view, and server stats (requires `@adonisjs/redis`) |
541
- | **Jobs** | Queue overview with job listing, detail, and retry for failed jobs (requires `@rlanz/bull-queue`) |
542
- | **Config** | Sanitized view of app configuration and environment variables. Secrets are auto-redacted |
531
+ | **Events** | Application events captured from the AdonisJS emitter |
532
+ | **Routes** | Full route table with methods, patterns, handlers, and middleware stacks |
533
+ | **Logs** | Log history with level filtering, text search, and structured JSON field search (e.g. filter by `userId = 5`) |
534
+ | **Emails** | Email history with sender, recipient, subject, status. Click for HTML preview in iframe |
535
+ | **Timeline** | Per-request waterfall timeline (requires `tracing: true`) |
536
+ | **Cache** | Redis key browser with SCAN-based listing, type-aware detail view, and server stats (requires `@adonisjs/redis`) |
537
+ | **Jobs** | Queue overview with job listing, detail, and retry for failed jobs (requires `@rlanz/bull-queue`) |
538
+ | **Config** | Sanitized view of app configuration and environment variables. Secrets are auto-redacted |
543
539
 
544
540
  #### Access Control
545
541
 
@@ -562,12 +558,14 @@ When the dashboard is enabled, the debug panel gains link icons on query, event,
562
558
  #### Real-Time Updates
563
559
 
564
560
  The dashboard supports real-time updates via two mechanisms:
561
+
565
562
  - **Transmit (SSE)**: If `@adonisjs/transmit` is installed, the dashboard subscribes to `server-stats/dashboard` for live overview updates
566
563
  - **Polling fallback**: If Transmit is not available, the dashboard polls the API at a configurable interval
567
564
 
568
565
  #### Data Storage
569
566
 
570
567
  The dashboard uses a dedicated SQLite database (separate from your app's database) with 8 tables prefixed with `server_stats_`. The database is:
568
+
571
569
  - **Auto-migrated** on startup (no manual migration step)
572
570
  - **Self-contained** -- uses its own Knex connection, never touches your app's migration history
573
571
  - **Self-cleaning** -- old data is automatically purged based on `retentionDays`
@@ -640,40 +638,40 @@ async webhooks({ response }: HttpContext) {
640
638
 
641
639
  #### `DebugPane` Options
642
640
 
643
- | Option | Type | Default | Description |
644
- |-------------|---------------------|---------|----------------------------------------------|
645
- | `id` | `string` | -- | Unique identifier (also default data key) |
646
- | `label` | `string` | -- | Tab display name |
647
- | `endpoint` | `string` | -- | API endpoint URL |
648
- | `columns` | `DebugPaneColumn[]` | -- | Column definitions |
649
- | `search` | `{ placeholder }` | -- | Enable search bar |
650
- | `dataKey` | `string` | `id` | JSON key for data array (dot notation OK) |
651
- | `fetchOnce` | `boolean` | `false` | Cache after first fetch |
652
- | `clearable` | `boolean` | `false` | Show Clear button |
641
+ | Option | Type | Default | Description |
642
+ | ----------- | ------------------- | ------- | ----------------------------------------- |
643
+ | `id` | `string` | -- | Unique identifier (also default data key) |
644
+ | `label` | `string` | -- | Tab display name |
645
+ | `endpoint` | `string` | -- | API endpoint URL |
646
+ | `columns` | `DebugPaneColumn[]` | -- | Column definitions |
647
+ | `search` | `{ placeholder }` | -- | Enable search bar |
648
+ | `dataKey` | `string` | `id` | JSON key for data array (dot notation OK) |
649
+ | `fetchOnce` | `boolean` | `false` | Cache after first fetch |
650
+ | `clearable` | `boolean` | `false` | Show Clear button |
653
651
 
654
652
  #### `DebugPaneColumn` Options
655
653
 
656
- | Option | Type | Default | Description |
657
- |-----------------|--------------------------|----------|------------------------------------------|
658
- | `key` | `string` | -- | JSON field name |
659
- | `label` | `string` | -- | Column header text |
660
- | `width` | `string` | auto | CSS width (e.g. `'60px'`) |
661
- | `format` | `DebugPaneFormatType` | `'text'` | Cell format (see table below) |
662
- | `searchable` | `boolean` | `false` | Include in search filtering |
663
- | `filterable` | `boolean` | `false` | Click to set as search filter |
664
- | `badgeColorMap` | `Record<string, string>` | -- | Value-to-color map for `badge` format |
654
+ | Option | Type | Default | Description |
655
+ | --------------- | ------------------------ | -------- | ------------------------------------- |
656
+ | `key` | `string` | -- | JSON field name |
657
+ | `label` | `string` | -- | Column header text |
658
+ | `width` | `string` | auto | CSS width (e.g. `'60px'`) |
659
+ | `format` | `DebugPaneFormatType` | `'text'` | Cell format (see table below) |
660
+ | `searchable` | `boolean` | `false` | Include in search filtering |
661
+ | `filterable` | `boolean` | `false` | Click to set as search filter |
662
+ | `badgeColorMap` | `Record<string, string>` | -- | Value-to-color map for `badge` format |
665
663
 
666
664
  #### Format Types
667
665
 
668
- | Format | Renders As | Expected Input |
669
- |------------|----------------------------------------|-------------------------|
670
- | `text` | Escaped plain text | any |
671
- | `time` | `HH:MM:SS.mmm` | Unix timestamp (ms) |
672
- | `timeAgo` | `3s ago`, `2m ago` | Unix timestamp (ms) |
673
- | `duration` | `X.XXms` with color coding | number (ms) |
674
- | `method` | HTTP method pill badge | `'GET'`, `'POST'`, etc. |
675
- | `json` | Compact preview, click to expand | object or array |
676
- | `badge` | Colored pill via `badgeColorMap` | string |
666
+ | Format | Renders As | Expected Input |
667
+ | ---------- | -------------------------------- | ----------------------- |
668
+ | `text` | Escaped plain text | any |
669
+ | `time` | `HH:MM:SS.mmm` | Unix timestamp (ms) |
670
+ | `timeAgo` | `3s ago`, `2m ago` | Unix timestamp (ms) |
671
+ | `duration` | `X.XXms` with color coding | number (ms) |
672
+ | `method` | HTTP method pill badge | `'GET'`, `'POST'`, etc. |
673
+ | `json` | Compact preview, click to expand | object or array |
674
+ | `badge` | Colored pill via `badgeColorMap` | string |
677
675
 
678
676
  Badge colors: `green`, `amber`, `red`, `blue`, `purple`, `muted`
679
677
 
@@ -704,6 +702,7 @@ Gauges are updated automatically on each collection tick.
704
702
  The log stream module watches a JSON log file and broadcasts new entries via Transmit (SSE).
705
703
 
706
704
  **Two purposes:**
705
+
707
706
  1. Provides error/warning counts to the stats bar via `logCollector()`
708
707
  2. Broadcasts individual log entries to a Transmit channel via `LogStreamProvider`
709
708
 
@@ -778,7 +777,6 @@ import type {
778
777
  QueueRedisConnection,
779
778
  LogCollectorOptions,
780
779
  } from 'adonisjs-server-stats/collectors'
781
-
782
780
  ```
783
781
 
784
782
  ---
@@ -787,16 +785,16 @@ import type {
787
785
 
788
786
  All integrations use lazy `import()` -- missing peer deps won't crash the app. The corresponding collector simply returns defaults.
789
787
 
790
- | Dependency | Required By |
791
- |-----------------------------|------------------------------------------------|
792
- | `@adonisjs/core` | Everything (required) |
793
- | `@adonisjs/lucid` | `dbPoolCollector`, `appCollector`, dashboard |
794
- | `@adonisjs/redis` | `redisCollector`, dashboard cache inspector |
795
- | `@adonisjs/transmit` | Provider (SSE broadcast), dashboard real-time |
796
- | `@julr/adonisjs-prometheus` | `serverStatsCollector` |
797
- | `bullmq` | `queueCollector` |
798
- | `better-sqlite3` | Dashboard (`dashboard: true`) |
799
- | `edge.js` | Edge tag |
788
+ | Dependency | Required By |
789
+ | --------------------------- | --------------------------------------------- |
790
+ | `@adonisjs/core` | Everything (required) |
791
+ | `@adonisjs/lucid` | `dbPoolCollector`, `appCollector`, dashboard |
792
+ | `@adonisjs/redis` | `redisCollector`, dashboard cache inspector |
793
+ | `@adonisjs/transmit` | Provider (SSE broadcast), dashboard real-time |
794
+ | `@julr/adonisjs-prometheus` | `serverStatsCollector` |
795
+ | `bullmq` | `queueCollector` |
796
+ | `better-sqlite3` | Dashboard (`dashboard: true`) |
797
+ | `edge.js` | Edge tag |
800
798
 
801
799
  ## License
802
800
 
@@ -1 +1 @@
1
- {"version":3,"file":"configure.d.ts","sourceRoot":"","sources":["../configure.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,SAAS,MAAM,mCAAmC,CAAA;AAI9D,wBAAsB,SAAS,CAAC,OAAO,EAAE,SAAS,iBAWjD"}
1
+ {"version":3,"file":"configure.d.ts","sourceRoot":"","sources":["../configure.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,SAAS,MAAM,mCAAmC,CAAA;AAE9D,wBAAsB,SAAS,CAAC,OAAO,EAAE,SAAS,iBAWjD"}
@@ -1,5 +1,5 @@
1
- import type { HttpContext } from "@adonisjs/core/http";
2
- import type { DebugStore } from "../debug/debug_store.js";
1
+ import type { DebugStore } from '../debug/debug_store.js';
2
+ import type { HttpContext } from '@adonisjs/core/http';
3
3
  export default class DebugController {
4
4
  private store;
5
5
  constructor(store: DebugStore);
@@ -1 +1 @@
1
- {"version":3,"file":"debug_controller.d.ts","sourceRoot":"","sources":["../../../src/controller/debug_controller.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAE1D,MAAM,CAAC,OAAO,OAAO,eAAe;IACtB,OAAO,CAAC,KAAK;gBAAL,KAAK,EAAE,UAAU;IAE/B,OAAO,CAAC,EAAE,QAAQ,EAAE,EAAE,WAAW;IAMjC,MAAM,CAAC,EAAE,QAAQ,EAAE,EAAE,WAAW;IAKhC,MAAM,CAAC,EAAE,QAAQ,EAAE,EAAE,WAAW;IAKhC,MAAM,CAAC,EAAE,QAAQ,EAAE,EAAE,WAAW;IAOhC,YAAY,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,WAAW;IAS9C,MAAM,CAAC,EAAE,QAAQ,EAAE,EAAE,WAAW;IAahC,WAAW,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,WAAW;CAWpD"}
1
+ {"version":3,"file":"debug_controller.d.ts","sourceRoot":"","sources":["../../../src/controller/debug_controller.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAA;AACzD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AAEtD,MAAM,CAAC,OAAO,OAAO,eAAe;IACtB,OAAO,CAAC,KAAK;gBAAL,KAAK,EAAE,UAAU;IAE/B,OAAO,CAAC,EAAE,QAAQ,EAAE,EAAE,WAAW;IAMjC,MAAM,CAAC,EAAE,QAAQ,EAAE,EAAE,WAAW;IAKhC,MAAM,CAAC,EAAE,QAAQ,EAAE,EAAE,WAAW;IAKhC,MAAM,CAAC,EAAE,QAAQ,EAAE,EAAE,WAAW;IAOhC,YAAY,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,WAAW;IAS9C,MAAM,CAAC,EAAE,QAAQ,EAAE,EAAE,WAAW;IAahC,WAAW,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,WAAW;CAWpD"}
@@ -1,5 +1,5 @@
1
- import type { HttpContext } from '@adonisjs/core/http';
2
1
  import type { StatsEngine } from '../engine/stats_engine.js';
2
+ import type { HttpContext } from '@adonisjs/core/http';
3
3
  export default class ServerStatsController {
4
4
  private engine;
5
5
  constructor(engine: StatsEngine);
@@ -1 +1 @@
1
- {"version":3,"file":"server_stats_controller.d.ts","sourceRoot":"","sources":["../../../src/controller/server_stats_controller.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AACtD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAA;AAE5D,MAAM,CAAC,OAAO,OAAO,qBAAqB;IAC5B,OAAO,CAAC,MAAM;gBAAN,MAAM,EAAE,WAAW;IAEjC,KAAK,CAAC,EAAE,QAAQ,EAAE,EAAE,WAAW;CAItC"}
1
+ {"version":3,"file":"server_stats_controller.d.ts","sourceRoot":"","sources":["../../../src/controller/server_stats_controller.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAA;AAC5D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AAEtD,MAAM,CAAC,OAAO,OAAO,qBAAqB;IAC5B,OAAO,CAAC,MAAM;gBAAN,MAAM,EAAE,WAAW;IAEjC,KAAK,CAAC,EAAE,QAAQ,EAAE,EAAE,WAAW;CAItC"}
@@ -1 +1 @@
1
- {"version":3,"file":"chart_aggregator.d.ts","sourceRoot":"","sources":["../../../src/dashboard/chart_aggregator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAEhC;;;;;;;;;;GAUG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,EAAE,CAAM;IAChB,OAAO,CAAC,KAAK,CAA8C;gBAE/C,EAAE,EAAE,IAAI;IAIpB,KAAK,IAAI,IAAI;IAQb,IAAI,IAAI,IAAI;YAOE,SAAS;CA4DxB"}
1
+ {"version":3,"file":"chart_aggregator.d.ts","sourceRoot":"","sources":["../../../src/dashboard/chart_aggregator.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAEhC;;;;;;;;;;GAUG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,EAAE,CAAM;IAChB,OAAO,CAAC,KAAK,CAA8C;gBAE/C,EAAE,EAAE,IAAI;IAIpB,KAAK,IAAI,IAAI;IAQb,IAAI,IAAI,IAAI;YAOE,SAAS;CA0DxB"}
@@ -1,3 +1,5 @@
1
+ import { round } from '../utils/math_helpers.js';
2
+ import { toSqliteTimestamp } from '../utils/time_helpers.js';
1
3
  /**
2
4
  * Periodically aggregates recent request data into time-bucketed
3
5
  * metrics stored in `server_stats_metrics`.
@@ -31,13 +33,11 @@ export class ChartAggregator {
31
33
  async aggregate() {
32
34
  const bucket = getBucketTimestamp();
33
35
  // Check if we already have a row for this bucket (idempotent)
34
- const existing = await this.db('server_stats_metrics')
35
- .where('bucket', bucket)
36
- .first();
36
+ const existing = await this.db('server_stats_metrics').where('bucket', bucket).first();
37
37
  if (existing)
38
38
  return;
39
39
  // Get requests from the last 60 seconds
40
- const cutoff = new Date(Date.now() - 60_000).toISOString().replace('T', ' ').slice(0, 19);
40
+ const cutoff = toSqliteTimestamp(new Date(Date.now() - 60_000));
41
41
  const requests = await this.db('server_stats_requests')
42
42
  .where('created_at', '>=', cutoff)
43
43
  .select('duration', 'status_code');
@@ -70,11 +70,11 @@ export class ChartAggregator {
70
70
  await this.db('server_stats_metrics').insert({
71
71
  bucket,
72
72
  request_count: requestCount,
73
- avg_duration: Math.round(avgDuration * 100) / 100,
74
- p95_duration: Math.round(p95Duration * 100) / 100,
73
+ avg_duration: round(avgDuration),
74
+ p95_duration: round(p95Duration),
75
75
  error_count: errorCount,
76
76
  query_count: queryStats?.query_count ?? 0,
77
- avg_query_duration: Math.round((queryStats?.avg_query_duration ?? 0) * 100) / 100,
77
+ avg_query_duration: round(queryStats?.avg_query_duration ?? 0),
78
78
  });
79
79
  }
80
80
  }
@@ -85,5 +85,5 @@ export class ChartAggregator {
85
85
  function getBucketTimestamp() {
86
86
  const now = new Date();
87
87
  now.setSeconds(0, 0);
88
- return now.toISOString().replace('T', ' ').slice(0, 19);
88
+ return toSqliteTimestamp(now);
89
89
  }
@@ -1,13 +1,12 @@
1
+ import type { DebugStore } from '../debug/debug_store.js';
2
+ import type { DashboardStore } from './dashboard_store.js';
1
3
  import type { HttpContext } from '@adonisjs/core/http';
2
4
  import type { ApplicationService } from '@adonisjs/core/types';
3
- import type { DashboardStore } from './dashboard_store.js';
4
- import type { DebugStore } from '../debug/debug_store.js';
5
5
  /**
6
6
  * Controller for the full-page dashboard.
7
7
  *
8
8
  * Serves the dashboard HTML page and all JSON API endpoints.
9
- * Constructor-injected with the SQLite-backed DashboardStore,
10
- * the in-memory DebugStore, and the AdonisJS application instance.
9
+ * Delegates all data access to the DashboardStore.
11
10
  */
12
11
  export default class DashboardController {
13
12
  private dashboardStore;
@@ -22,126 +21,42 @@ export default class DashboardController {
22
21
  private cachedJs;
23
22
  private cachedTransmitClient;
24
23
  constructor(dashboardStore: DashboardStore, debugStore: DebugStore, app: ApplicationService);
25
- /**
26
- * GET {dashboardPath} — Render the dashboard Edge template.
27
- *
28
- * Reads the dashboard CSS/JS assets and passes them as template state
29
- * along with configuration for tracing and custom panes.
30
- */
31
24
  page(ctx: HttpContext): Promise<any>;
32
- /**
33
- * GET {dashboardPath}/api/overview — Overview metrics cards.
34
- */
35
25
  overview({ request, response }: HttpContext): Promise<void>;
36
- /**
37
- * GET {dashboardPath}/api/overview/chart — Chart data with time range.
38
- */
39
26
  overviewChart({ request, response }: HttpContext): Promise<void>;
40
- /**
41
- * GET {dashboardPath}/api/requests — Paginated request history.
42
- */
43
27
  requests({ request, response }: HttpContext): Promise<void>;
44
- /**
45
- * GET {dashboardPath}/api/requests/:id — Single request with trace.
46
- */
47
28
  requestDetail({ params, response }: HttpContext): Promise<void>;
48
- /**
49
- * GET {dashboardPath}/api/queries — Paginated query history.
50
- */
51
29
  queries({ request, response }: HttpContext): Promise<void>;
52
- /**
53
- * GET {dashboardPath}/api/queries/grouped — Grouped by normalized SQL.
54
- */
55
30
  queriesGrouped({ request, response }: HttpContext): Promise<void>;
56
- /**
57
- * GET {dashboardPath}/api/queries/:id/explain — Run EXPLAIN on a query.
58
- */
59
31
  queryExplain({ params, response }: HttpContext): Promise<void>;
60
- /**
61
- * GET {dashboardPath}/api/events — Paginated event history.
62
- */
63
32
  events({ request, response }: HttpContext): Promise<void>;
64
- /**
65
- * GET {dashboardPath}/api/routes — Route table (delegates to DebugStore).
66
- */
67
33
  routes({ response }: HttpContext): Promise<void>;
68
- /**
69
- * GET {dashboardPath}/api/logs — Paginated logs with structured search.
70
- */
71
34
  logs({ request, response }: HttpContext): Promise<void>;
72
- /**
73
- * GET {dashboardPath}/api/emails — Paginated email history.
74
- */
75
35
  emails({ request, response }: HttpContext): Promise<void>;
76
- /**
77
- * GET {dashboardPath}/api/emails/:id/preview — Email HTML preview.
78
- */
79
36
  emailPreview({ params, response }: HttpContext): Promise<void>;
80
- /**
81
- * GET {dashboardPath}/api/traces — Paginated trace list (lightweight).
82
- */
83
37
  traces({ request, response }: HttpContext): Promise<void>;
84
- /**
85
- * GET {dashboardPath}/api/traces/:id — Single trace with full spans.
86
- */
87
38
  traceDetail({ params, response }: HttpContext): Promise<void>;
88
- /**
89
- * GET {dashboardPath}/api/cache — Cache stats and key list.
90
- */
91
39
  cacheStats({ request, response }: HttpContext): Promise<void>;
92
- /**
93
- * GET {dashboardPath}/api/cache/:key — Single cache key detail.
94
- */
95
40
  cacheKey({ params, response }: HttpContext): Promise<void>;
96
- /**
97
- * GET {dashboardPath}/api/jobs — Job list with status filter.
98
- */
99
41
  jobs({ request, response }: HttpContext): Promise<void>;
100
- /**
101
- * GET {dashboardPath}/api/jobs/:id — Single job detail.
102
- */
103
42
  jobDetail({ params, response }: HttpContext): Promise<void>;
104
- /**
105
- * POST {dashboardPath}/api/jobs/:id/retry — Retry a failed job.
106
- */
107
43
  jobRetry({ params, response }: HttpContext): Promise<void>;
108
- /**
109
- * GET {dashboardPath}/api/config — Sanitized app config and env vars.
110
- */
111
44
  config({ response }: HttpContext): Promise<void>;
112
- /**
113
- * GET {dashboardPath}/api/filters — List saved filter presets.
114
- */
115
45
  savedFilters({ response }: HttpContext): Promise<void>;
116
- /**
117
- * POST {dashboardPath}/api/filters — Create a saved filter preset.
118
- */
119
46
  createSavedFilter({ request, response }: HttpContext): Promise<void>;
120
- /**
121
- * DELETE {dashboardPath}/api/filters/:id — Delete a saved filter preset.
122
- */
123
47
  deleteSavedFilter({ params, response }: HttpContext): Promise<void>;
124
48
  /**
125
- * Check if the current request is authorized via shouldShow.
49
+ * Wraps a store call with null-guard + try/catch boilerplate.
50
+ * Returns emptyValue if the store is not ready or the fn throws.
126
51
  */
52
+ private withDb;
127
53
  private checkAccess;
128
- /**
129
- * Get the configured dashboard path.
130
- */
131
54
  private getDashboardPath;
132
- /**
133
- * Try to locate and read the @adonisjs/transmit-client build file.
134
- * Returns the file contents wrapped to expose `window.Transmit`, or
135
- * an empty string if the package is not installed.
136
- */
137
- private loadTransmitClient;
138
- /**
139
- * Lazily initialize and return the CacheInspector (if Redis is available).
140
- */
141
- private getCacheInspector;
142
- /**
143
- * Lazily initialize and return the QueueInspector (if Bull Queue is available).
144
- */
145
- private getQueueInspector;
55
+ /** Lazy-init inspector pattern for cache and queue. */
56
+ private getInspector;
57
+ /** Fetch cache overview stats for the overview page. */
58
+ private fetchCacheOverview;
59
+ /** Fetch queue overview stats for the overview page. */
60
+ private fetchQueueOverview;
146
61
  }
147
62
  //# sourceMappingURL=dashboard_controller.d.ts.map