adonisjs-server-stats 1.2.2 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +148 -9
- package/dist/src/dashboard/chart_aggregator.d.ts +21 -0
- package/dist/src/dashboard/chart_aggregator.d.ts.map +1 -0
- package/dist/src/dashboard/chart_aggregator.js +89 -0
- package/dist/src/dashboard/dashboard_controller.d.ts +147 -0
- package/dist/src/dashboard/dashboard_controller.d.ts.map +1 -0
- package/dist/src/dashboard/dashboard_controller.js +1008 -0
- package/dist/src/dashboard/dashboard_routes.d.ts +16 -0
- package/dist/src/dashboard/dashboard_routes.d.ts.map +1 -0
- package/dist/src/dashboard/dashboard_routes.js +88 -0
- package/dist/src/dashboard/dashboard_store.d.ts +158 -0
- package/dist/src/dashboard/dashboard_store.d.ts.map +1 -0
- package/dist/src/dashboard/dashboard_store.js +723 -0
- package/dist/src/dashboard/integrations/cache_inspector.d.ts +88 -0
- package/dist/src/dashboard/integrations/cache_inspector.d.ts.map +1 -0
- package/dist/src/dashboard/integrations/cache_inspector.js +215 -0
- package/dist/src/dashboard/integrations/config_inspector.d.ts +33 -0
- package/dist/src/dashboard/integrations/config_inspector.d.ts.map +1 -0
- package/dist/src/dashboard/integrations/config_inspector.js +155 -0
- package/dist/src/dashboard/integrations/index.d.ts +7 -0
- package/dist/src/dashboard/integrations/index.d.ts.map +1 -0
- package/dist/src/dashboard/integrations/index.js +3 -0
- package/dist/src/dashboard/integrations/queue_inspector.d.ts +106 -0
- package/dist/src/dashboard/integrations/queue_inspector.d.ts.map +1 -0
- package/dist/src/dashboard/integrations/queue_inspector.js +182 -0
- package/dist/src/dashboard/migrator.d.ts +18 -0
- package/dist/src/dashboard/migrator.d.ts.map +1 -0
- package/dist/src/dashboard/migrator.js +144 -0
- package/dist/src/dashboard/models/stats_email.d.ts +19 -0
- package/dist/src/dashboard/models/stats_email.d.ts.map +1 -0
- package/dist/src/dashboard/models/stats_email.js +66 -0
- package/dist/src/dashboard/models/stats_event.d.ts +14 -0
- package/dist/src/dashboard/models/stats_event.d.ts.map +1 -0
- package/dist/src/dashboard/models/stats_event.js +43 -0
- package/dist/src/dashboard/models/stats_log.d.ts +12 -0
- package/dist/src/dashboard/models/stats_log.d.ts.map +1 -0
- package/dist/src/dashboard/models/stats_log.js +42 -0
- package/dist/src/dashboard/models/stats_metric.d.ts +15 -0
- package/dist/src/dashboard/models/stats_metric.d.ts.map +1 -0
- package/dist/src/dashboard/models/stats_metric.js +50 -0
- package/dist/src/dashboard/models/stats_query.d.ts +20 -0
- package/dist/src/dashboard/models/stats_query.d.ts.map +1 -0
- package/dist/src/dashboard/models/stats_query.js +67 -0
- package/dist/src/dashboard/models/stats_request.d.ts +21 -0
- package/dist/src/dashboard/models/stats_request.d.ts.map +1 -0
- package/dist/src/dashboard/models/stats_request.js +61 -0
- package/dist/src/dashboard/models/stats_saved_filter.d.ts +11 -0
- package/dist/src/dashboard/models/stats_saved_filter.d.ts.map +1 -0
- package/dist/src/dashboard/models/stats_saved_filter.js +38 -0
- package/dist/src/dashboard/models/stats_trace.d.ts +19 -0
- package/dist/src/dashboard/models/stats_trace.d.ts.map +1 -0
- package/dist/src/dashboard/models/stats_trace.js +67 -0
- package/dist/src/debug/debug_store.d.ts +5 -0
- package/dist/src/debug/debug_store.d.ts.map +1 -1
- package/dist/src/debug/debug_store.js +10 -0
- package/dist/src/debug/email_collector.d.ts +2 -0
- package/dist/src/debug/email_collector.d.ts.map +1 -1
- package/dist/src/debug/email_collector.js +4 -0
- package/dist/src/debug/event_collector.d.ts +2 -0
- package/dist/src/debug/event_collector.d.ts.map +1 -1
- package/dist/src/debug/event_collector.js +11 -2
- package/dist/src/debug/query_collector.d.ts +2 -0
- package/dist/src/debug/query_collector.d.ts.map +1 -1
- package/dist/src/debug/query_collector.js +11 -0
- package/dist/src/debug/ring_buffer.d.ts +3 -0
- package/dist/src/debug/ring_buffer.d.ts.map +1 -1
- package/dist/src/debug/ring_buffer.js +6 -0
- package/dist/src/debug/trace_collector.d.ts +4 -2
- package/dist/src/debug/trace_collector.d.ts.map +1 -1
- package/dist/src/debug/trace_collector.js +7 -2
- package/dist/src/debug/types.d.ts +8 -0
- package/dist/src/debug/types.d.ts.map +1 -1
- package/dist/src/edge/client/dashboard.css +1504 -0
- package/dist/src/edge/client/dashboard.js +2378 -0
- package/dist/src/edge/client/debug-panel.css +528 -108
- package/dist/src/edge/client/debug-panel.js +663 -22
- package/dist/src/edge/client/stats-bar.css +112 -38
- package/dist/src/edge/client/stats-bar.js +37 -3
- package/dist/src/edge/plugin.d.ts.map +1 -1
- package/dist/src/edge/plugin.js +21 -0
- package/dist/src/edge/views/dashboard.edge +382 -0
- package/dist/src/edge/views/debug-panel.edge +60 -14
- package/dist/src/edge/views/stats-bar.edge +9 -0
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -0
- package/dist/src/middleware/request_tracking_middleware.d.ts +20 -0
- package/dist/src/middleware/request_tracking_middleware.d.ts.map +1 -1
- package/dist/src/middleware/request_tracking_middleware.js +66 -2
- package/dist/src/provider/server_stats_provider.d.ts +13 -0
- package/dist/src/provider/server_stats_provider.d.ts.map +1 -1
- package/dist/src/provider/server_stats_provider.js +175 -1
- package/dist/src/types.d.ts +42 -0
- package/dist/src/types.d.ts.map +1 -1
- package/package.json +14 -1
package/README.md
CHANGED
|
@@ -37,12 +37,14 @@ Zero frontend dependencies. Zero build step. Just `@serverStats()` and go.
|
|
|
37
37
|
- **Debug toolbar** -- SQL queries, events, emails, routes, logs with search and filtering
|
|
38
38
|
- **Request tracing** -- per-request waterfall timeline showing DB queries, events, and custom spans
|
|
39
39
|
- **Custom panes** -- add your own tabs (webhooks, emails, cache, anything) with a simple config
|
|
40
|
+
- **Full-page dashboard** -- dedicated page at `/__stats` with overview cards, charts, request history, query analysis, EXPLAIN plans, cache/queue/config inspection, and saved filters
|
|
40
41
|
- **Pluggable collectors** -- use built-in collectors or write your own
|
|
41
42
|
- **Visibility control** -- show only to admins, specific roles, or in dev mode
|
|
42
43
|
- **SSE broadcasting** -- real-time updates via AdonisJS Transmit
|
|
43
44
|
- **Prometheus export** -- expose all metrics as Prometheus gauges
|
|
44
45
|
- **Self-contained** -- inline HTML/CSS/JS Edge tag, no React, no external assets
|
|
45
46
|
- **Graceful degradation** -- missing optional dependencies are handled automatically
|
|
47
|
+
- **Theme support** -- dark and light themes across dashboard, debug panel, and stats bar with system preference detection and manual toggle
|
|
46
48
|
|
|
47
49
|
## Installation
|
|
48
50
|
|
|
@@ -226,6 +228,11 @@ export default class ServerStatsController {
|
|
|
226
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. |
|
|
227
229
|
| `tracing` | `boolean` | `false` | Enable per-request tracing with timeline visualization |
|
|
228
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. |
|
|
229
236
|
| `panes` | `DebugPane[]` | -- | Custom debug panel tabs |
|
|
230
237
|
|
|
231
238
|
---
|
|
@@ -466,6 +473,123 @@ const result = await trace('organization.fetchMembers', async () => {
|
|
|
466
473
|
|
|
467
474
|
If tracing is disabled or no request is active, `trace()` executes the function directly with no overhead.
|
|
468
475
|
|
|
476
|
+
### Full-Page Dashboard
|
|
477
|
+
|
|
478
|
+
The dashboard is a dedicated page that provides historical data, charts, query analysis, and integration inspectors -- all persisted to a local SQLite database. It's like having Laravel Telescope built into your dev toolbar.
|
|
479
|
+
|
|
480
|
+
#### Prerequisites
|
|
481
|
+
|
|
482
|
+
The dashboard requires `better-sqlite3` for local data storage:
|
|
483
|
+
|
|
484
|
+
```bash
|
|
485
|
+
npm install better-sqlite3
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
If `better-sqlite3` is not installed, the dashboard will log a helpful message and disable itself gracefully -- the rest of the stats bar and debug toolbar continues to work.
|
|
489
|
+
|
|
490
|
+
#### Enable the Dashboard
|
|
491
|
+
|
|
492
|
+
```ts
|
|
493
|
+
// config/server_stats.ts
|
|
494
|
+
export default defineConfig({
|
|
495
|
+
devToolbar: {
|
|
496
|
+
enabled: true,
|
|
497
|
+
dashboard: true,
|
|
498
|
+
},
|
|
499
|
+
})
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
Restart your dev server and visit **`/__stats`** (or your configured `dashboardPath`).
|
|
503
|
+
|
|
504
|
+
#### Configuration
|
|
505
|
+
|
|
506
|
+
```ts
|
|
507
|
+
devToolbar: {
|
|
508
|
+
enabled: true,
|
|
509
|
+
dashboard: true,
|
|
510
|
+
|
|
511
|
+
// URL path for the dashboard (default: '/__stats')
|
|
512
|
+
dashboardPath: '/__stats',
|
|
513
|
+
|
|
514
|
+
// Days to retain historical data (default: 7)
|
|
515
|
+
retentionDays: 7,
|
|
516
|
+
|
|
517
|
+
// SQLite database file path, relative to app root (default: '.adonisjs/server-stats/dashboard.sqlite3')
|
|
518
|
+
dbPath: '.adonisjs/server-stats/dashboard.sqlite3',
|
|
519
|
+
|
|
520
|
+
// URL prefixes to exclude from tracing and dashboard persistence (default: [])
|
|
521
|
+
excludeFromTracing: ['/admin/api/debug'],
|
|
522
|
+
|
|
523
|
+
// Enable tracing for per-request timeline in the dashboard (recommended)
|
|
524
|
+
tracing: true,
|
|
525
|
+
}
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
#### Dashboard Sections
|
|
529
|
+
|
|
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 |
|
|
534
|
+
| **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 |
|
|
543
|
+
|
|
544
|
+
#### Access Control
|
|
545
|
+
|
|
546
|
+
The dashboard reuses the `shouldShow` callback from the main config. If set, all dashboard routes are gated by it -- unauthorized requests receive a 403.
|
|
547
|
+
|
|
548
|
+
```ts
|
|
549
|
+
export default defineConfig({
|
|
550
|
+
shouldShow: (ctx) => ctx.auth?.user?.role === 'admin',
|
|
551
|
+
devToolbar: {
|
|
552
|
+
enabled: true,
|
|
553
|
+
dashboard: true,
|
|
554
|
+
},
|
|
555
|
+
})
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
#### Deep Links from Debug Panel
|
|
559
|
+
|
|
560
|
+
When the dashboard is enabled, the debug panel gains link icons on query, event, email, and trace rows. Clicking them opens the dashboard in a new tab, navigating directly to the relevant section and item.
|
|
561
|
+
|
|
562
|
+
#### Real-Time Updates
|
|
563
|
+
|
|
564
|
+
The dashboard supports real-time updates via two mechanisms:
|
|
565
|
+
- **Transmit (SSE)**: If `@adonisjs/transmit` is installed, the dashboard subscribes to `server-stats/dashboard` for live overview updates
|
|
566
|
+
- **Polling fallback**: If Transmit is not available, the dashboard polls the API at a configurable interval
|
|
567
|
+
|
|
568
|
+
#### Data Storage
|
|
569
|
+
|
|
570
|
+
The dashboard uses a dedicated SQLite database (separate from your app's database) with 8 tables prefixed with `server_stats_`. The database is:
|
|
571
|
+
- **Auto-migrated** on startup (no manual migration step)
|
|
572
|
+
- **Self-contained** -- uses its own Knex connection, never touches your app's migration history
|
|
573
|
+
- **Self-cleaning** -- old data is automatically purged based on `retentionDays`
|
|
574
|
+
- **WAL mode** -- concurrent reads don't block writes
|
|
575
|
+
|
|
576
|
+
The SQLite file is created at the configured `dbPath` (default: `.adonisjs/server-stats/dashboard.sqlite3`). Add it to your `.gitignore`:
|
|
577
|
+
|
|
578
|
+
```
|
|
579
|
+
.adonisjs/server-stats/
|
|
580
|
+
```
|
|
581
|
+
|
|
582
|
+
#### Theme Support
|
|
583
|
+
|
|
584
|
+
All three UIs (dashboard, debug panel, and stats bar) support dark and light themes:
|
|
585
|
+
|
|
586
|
+
- **System preference** -- automatically follows `prefers-color-scheme` (dark is default)
|
|
587
|
+
- **Manual toggle** -- sun/moon button in the dashboard sidebar and debug panel tab bar
|
|
588
|
+
- **Synced** -- theme choice is shared via `localStorage` across all three UIs, including cross-tab sync
|
|
589
|
+
- **Scoped** -- CSS variables are scoped to their containers, so they won't leak into your app's styles
|
|
590
|
+
|
|
591
|
+
---
|
|
592
|
+
|
|
469
593
|
### Custom Debug Panes
|
|
470
594
|
|
|
471
595
|
Add custom tabs to the debug panel:
|
|
@@ -629,6 +753,20 @@ import type {
|
|
|
629
753
|
TraceRecord,
|
|
630
754
|
} from 'adonisjs-server-stats'
|
|
631
755
|
|
|
756
|
+
// Dashboard types
|
|
757
|
+
import type {
|
|
758
|
+
RequestFilters,
|
|
759
|
+
QueryFilters,
|
|
760
|
+
EventFilters,
|
|
761
|
+
EmailFilters,
|
|
762
|
+
LogFilters,
|
|
763
|
+
TraceFilters,
|
|
764
|
+
PaginatedResult,
|
|
765
|
+
} from 'adonisjs-server-stats'
|
|
766
|
+
|
|
767
|
+
// Dashboard store (for advanced use)
|
|
768
|
+
import { DashboardStore } from 'adonisjs-server-stats'
|
|
769
|
+
|
|
632
770
|
// Trace helper
|
|
633
771
|
import { trace } from 'adonisjs-server-stats'
|
|
634
772
|
|
|
@@ -649,15 +787,16 @@ import type {
|
|
|
649
787
|
|
|
650
788
|
All integrations use lazy `import()` -- missing peer deps won't crash the app. The corresponding collector simply returns defaults.
|
|
651
789
|
|
|
652
|
-
| Dependency | Required By
|
|
653
|
-
|
|
654
|
-
| `@adonisjs/core` | Everything (required)
|
|
655
|
-
| `@adonisjs/lucid` | `dbPoolCollector`, `appCollector
|
|
656
|
-
| `@adonisjs/redis` | `redisCollector
|
|
657
|
-
| `@adonisjs/transmit` | Provider (SSE broadcast)
|
|
658
|
-
| `@julr/adonisjs-prometheus` | `serverStatsCollector`
|
|
659
|
-
| `bullmq` | `queueCollector`
|
|
660
|
-
| `
|
|
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 |
|
|
661
800
|
|
|
662
801
|
## License
|
|
663
802
|
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Knex } from 'knex';
|
|
2
|
+
/**
|
|
3
|
+
* Periodically aggregates recent request data into time-bucketed
|
|
4
|
+
* metrics stored in `server_stats_metrics`.
|
|
5
|
+
*
|
|
6
|
+
* Runs every 60 seconds. Each tick:
|
|
7
|
+
* 1. Counts requests in the last minute
|
|
8
|
+
* 2. Calculates avg/p95 response time
|
|
9
|
+
* 3. Counts errors (4xx + 5xx)
|
|
10
|
+
* 4. Counts queries and average query duration
|
|
11
|
+
* 5. Stores a row with a bucket timestamp rounded to the minute
|
|
12
|
+
*/
|
|
13
|
+
export declare class ChartAggregator {
|
|
14
|
+
private db;
|
|
15
|
+
private timer;
|
|
16
|
+
constructor(db: Knex);
|
|
17
|
+
start(): void;
|
|
18
|
+
stop(): void;
|
|
19
|
+
private aggregate;
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=chart_aggregator.d.ts.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Periodically aggregates recent request data into time-bucketed
|
|
3
|
+
* metrics stored in `server_stats_metrics`.
|
|
4
|
+
*
|
|
5
|
+
* Runs every 60 seconds. Each tick:
|
|
6
|
+
* 1. Counts requests in the last minute
|
|
7
|
+
* 2. Calculates avg/p95 response time
|
|
8
|
+
* 3. Counts errors (4xx + 5xx)
|
|
9
|
+
* 4. Counts queries and average query duration
|
|
10
|
+
* 5. Stores a row with a bucket timestamp rounded to the minute
|
|
11
|
+
*/
|
|
12
|
+
export class ChartAggregator {
|
|
13
|
+
db;
|
|
14
|
+
timer = null;
|
|
15
|
+
constructor(db) {
|
|
16
|
+
this.db = db;
|
|
17
|
+
}
|
|
18
|
+
start() {
|
|
19
|
+
// Run immediately on startup, then every 60s
|
|
20
|
+
this.aggregate().catch(() => { });
|
|
21
|
+
this.timer = setInterval(() => {
|
|
22
|
+
this.aggregate().catch(() => { });
|
|
23
|
+
}, 60_000);
|
|
24
|
+
}
|
|
25
|
+
stop() {
|
|
26
|
+
if (this.timer) {
|
|
27
|
+
clearInterval(this.timer);
|
|
28
|
+
this.timer = null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
async aggregate() {
|
|
32
|
+
const bucket = getBucketTimestamp();
|
|
33
|
+
// 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();
|
|
37
|
+
if (existing)
|
|
38
|
+
return;
|
|
39
|
+
// Get requests from the last 60 seconds
|
|
40
|
+
const cutoff = new Date(Date.now() - 60_000).toISOString().replace('T', ' ').slice(0, 19);
|
|
41
|
+
const requests = await this.db('server_stats_requests')
|
|
42
|
+
.where('created_at', '>=', cutoff)
|
|
43
|
+
.select('duration', 'status_code');
|
|
44
|
+
const requestCount = requests.length;
|
|
45
|
+
if (requestCount === 0) {
|
|
46
|
+
// Still insert a zero-row so the chart shows continuous data
|
|
47
|
+
await this.db('server_stats_metrics').insert({
|
|
48
|
+
bucket,
|
|
49
|
+
request_count: 0,
|
|
50
|
+
avg_duration: 0,
|
|
51
|
+
p95_duration: 0,
|
|
52
|
+
error_count: 0,
|
|
53
|
+
query_count: 0,
|
|
54
|
+
avg_query_duration: 0,
|
|
55
|
+
});
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
// Calculate avg and p95 duration
|
|
59
|
+
const durations = requests.map((r) => r.duration).sort((a, b) => a - b);
|
|
60
|
+
const avgDuration = durations.reduce((sum, d) => sum + d, 0) / requestCount;
|
|
61
|
+
const p95Index = Math.floor(requestCount * 0.95);
|
|
62
|
+
const p95Duration = durations[Math.min(p95Index, requestCount - 1)];
|
|
63
|
+
// Count errors (status >= 400)
|
|
64
|
+
const errorCount = requests.filter((r) => r.status_code >= 400).length;
|
|
65
|
+
// Get query stats for the same window
|
|
66
|
+
const queryStats = await this.db('server_stats_queries')
|
|
67
|
+
.where('created_at', '>=', cutoff)
|
|
68
|
+
.select(this.db.raw('COUNT(*) as query_count'), this.db.raw('AVG(duration) as avg_query_duration'))
|
|
69
|
+
.first();
|
|
70
|
+
await this.db('server_stats_metrics').insert({
|
|
71
|
+
bucket,
|
|
72
|
+
request_count: requestCount,
|
|
73
|
+
avg_duration: Math.round(avgDuration * 100) / 100,
|
|
74
|
+
p95_duration: Math.round(p95Duration * 100) / 100,
|
|
75
|
+
error_count: errorCount,
|
|
76
|
+
query_count: queryStats?.query_count ?? 0,
|
|
77
|
+
avg_query_duration: Math.round((queryStats?.avg_query_duration ?? 0) * 100) / 100,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Returns an ISO timestamp string rounded down to the current minute.
|
|
83
|
+
* Used as the bucket key for metrics aggregation.
|
|
84
|
+
*/
|
|
85
|
+
function getBucketTimestamp() {
|
|
86
|
+
const now = new Date();
|
|
87
|
+
now.setSeconds(0, 0);
|
|
88
|
+
return now.toISOString().replace('T', ' ').slice(0, 19);
|
|
89
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import type { HttpContext } from '@adonisjs/core/http';
|
|
2
|
+
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
|
+
/**
|
|
6
|
+
* Controller for the full-page dashboard.
|
|
7
|
+
*
|
|
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.
|
|
11
|
+
*/
|
|
12
|
+
export default class DashboardController {
|
|
13
|
+
private dashboardStore;
|
|
14
|
+
private debugStore;
|
|
15
|
+
private app;
|
|
16
|
+
private cacheInspector;
|
|
17
|
+
private queueInspector;
|
|
18
|
+
private configInspector;
|
|
19
|
+
private cacheAvailable;
|
|
20
|
+
private queueAvailable;
|
|
21
|
+
private cachedCss;
|
|
22
|
+
private cachedJs;
|
|
23
|
+
private cachedTransmitClient;
|
|
24
|
+
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
|
+
page(ctx: HttpContext): Promise<any>;
|
|
32
|
+
/**
|
|
33
|
+
* GET {dashboardPath}/api/overview — Overview metrics cards.
|
|
34
|
+
*/
|
|
35
|
+
overview({ request, response }: HttpContext): Promise<void>;
|
|
36
|
+
/**
|
|
37
|
+
* GET {dashboardPath}/api/overview/chart — Chart data with time range.
|
|
38
|
+
*/
|
|
39
|
+
overviewChart({ request, response }: HttpContext): Promise<void>;
|
|
40
|
+
/**
|
|
41
|
+
* GET {dashboardPath}/api/requests — Paginated request history.
|
|
42
|
+
*/
|
|
43
|
+
requests({ request, response }: HttpContext): Promise<void>;
|
|
44
|
+
/**
|
|
45
|
+
* GET {dashboardPath}/api/requests/:id — Single request with trace.
|
|
46
|
+
*/
|
|
47
|
+
requestDetail({ params, response }: HttpContext): Promise<void>;
|
|
48
|
+
/**
|
|
49
|
+
* GET {dashboardPath}/api/queries — Paginated query history.
|
|
50
|
+
*/
|
|
51
|
+
queries({ request, response }: HttpContext): Promise<void>;
|
|
52
|
+
/**
|
|
53
|
+
* GET {dashboardPath}/api/queries/grouped — Grouped by normalized SQL.
|
|
54
|
+
*/
|
|
55
|
+
queriesGrouped({ request, response }: HttpContext): Promise<void>;
|
|
56
|
+
/**
|
|
57
|
+
* GET {dashboardPath}/api/queries/:id/explain — Run EXPLAIN on a query.
|
|
58
|
+
*/
|
|
59
|
+
queryExplain({ params, response }: HttpContext): Promise<void>;
|
|
60
|
+
/**
|
|
61
|
+
* GET {dashboardPath}/api/events — Paginated event history.
|
|
62
|
+
*/
|
|
63
|
+
events({ request, response }: HttpContext): Promise<void>;
|
|
64
|
+
/**
|
|
65
|
+
* GET {dashboardPath}/api/routes — Route table (delegates to DebugStore).
|
|
66
|
+
*/
|
|
67
|
+
routes({ response }: HttpContext): Promise<void>;
|
|
68
|
+
/**
|
|
69
|
+
* GET {dashboardPath}/api/logs — Paginated logs with structured search.
|
|
70
|
+
*/
|
|
71
|
+
logs({ request, response }: HttpContext): Promise<void>;
|
|
72
|
+
/**
|
|
73
|
+
* GET {dashboardPath}/api/emails — Paginated email history.
|
|
74
|
+
*/
|
|
75
|
+
emails({ request, response }: HttpContext): Promise<void>;
|
|
76
|
+
/**
|
|
77
|
+
* GET {dashboardPath}/api/emails/:id/preview — Email HTML preview.
|
|
78
|
+
*/
|
|
79
|
+
emailPreview({ params, response }: HttpContext): Promise<void>;
|
|
80
|
+
/**
|
|
81
|
+
* GET {dashboardPath}/api/traces — Paginated trace list (lightweight).
|
|
82
|
+
*/
|
|
83
|
+
traces({ request, response }: HttpContext): Promise<void>;
|
|
84
|
+
/**
|
|
85
|
+
* GET {dashboardPath}/api/traces/:id — Single trace with full spans.
|
|
86
|
+
*/
|
|
87
|
+
traceDetail({ params, response }: HttpContext): Promise<void>;
|
|
88
|
+
/**
|
|
89
|
+
* GET {dashboardPath}/api/cache — Cache stats and key list.
|
|
90
|
+
*/
|
|
91
|
+
cacheStats({ request, response }: HttpContext): Promise<void>;
|
|
92
|
+
/**
|
|
93
|
+
* GET {dashboardPath}/api/cache/:key — Single cache key detail.
|
|
94
|
+
*/
|
|
95
|
+
cacheKey({ params, response }: HttpContext): Promise<void>;
|
|
96
|
+
/**
|
|
97
|
+
* GET {dashboardPath}/api/jobs — Job list with status filter.
|
|
98
|
+
*/
|
|
99
|
+
jobs({ request, response }: HttpContext): Promise<void>;
|
|
100
|
+
/**
|
|
101
|
+
* GET {dashboardPath}/api/jobs/:id — Single job detail.
|
|
102
|
+
*/
|
|
103
|
+
jobDetail({ params, response }: HttpContext): Promise<void>;
|
|
104
|
+
/**
|
|
105
|
+
* POST {dashboardPath}/api/jobs/:id/retry — Retry a failed job.
|
|
106
|
+
*/
|
|
107
|
+
jobRetry({ params, response }: HttpContext): Promise<void>;
|
|
108
|
+
/**
|
|
109
|
+
* GET {dashboardPath}/api/config — Sanitized app config and env vars.
|
|
110
|
+
*/
|
|
111
|
+
config({ response }: HttpContext): Promise<void>;
|
|
112
|
+
/**
|
|
113
|
+
* GET {dashboardPath}/api/filters — List saved filter presets.
|
|
114
|
+
*/
|
|
115
|
+
savedFilters({ response }: HttpContext): Promise<void>;
|
|
116
|
+
/**
|
|
117
|
+
* POST {dashboardPath}/api/filters — Create a saved filter preset.
|
|
118
|
+
*/
|
|
119
|
+
createSavedFilter({ request, response }: HttpContext): Promise<void>;
|
|
120
|
+
/**
|
|
121
|
+
* DELETE {dashboardPath}/api/filters/:id — Delete a saved filter preset.
|
|
122
|
+
*/
|
|
123
|
+
deleteSavedFilter({ params, response }: HttpContext): Promise<void>;
|
|
124
|
+
/**
|
|
125
|
+
* Check if the current request is authorized via shouldShow.
|
|
126
|
+
*/
|
|
127
|
+
private checkAccess;
|
|
128
|
+
/**
|
|
129
|
+
* Get the configured dashboard path.
|
|
130
|
+
*/
|
|
131
|
+
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;
|
|
146
|
+
}
|
|
147
|
+
//# sourceMappingURL=dashboard_controller.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dashboard_controller.d.ts","sourceRoot":"","sources":["../../../src/dashboard/dashboard_controller.ts"],"names":[],"mappings":"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"}
|