adonisjs-server-stats 1.5.1 → 1.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -159,16 +159,13 @@ export default defineConfig({
159
159
  })
160
160
  ```
161
161
 
162
- ### 4. Add a route
162
+ ### 4. Add routes
163
163
 
164
- ```ts
165
- // start/routes.ts
166
- router
167
- .get('/admin/api/server-stats', '#controllers/admin/server_stats_controller.index')
168
- .use(middleware.superadmin()) // Replace with your own middleware
169
- ```
164
+ The package has three layers of functionality, each with its own routes:
170
165
 
171
- ### 5. Create the controller
166
+ #### Stats bar API route (required)
167
+
168
+ The stats bar polls this endpoint for live metrics. Create a controller and route:
172
169
 
173
170
  ```ts
174
171
  // app/controllers/admin/server_stats_controller.ts
@@ -184,7 +181,148 @@ export default class ServerStatsController {
184
181
  }
185
182
  ```
186
183
 
187
- ### 6. Render the stats bar
184
+ ```ts
185
+ // start/routes.ts
186
+ router
187
+ .get('/admin/api/server-stats', '#controllers/admin/server_stats_controller.index')
188
+ .use(middleware.superadmin())
189
+ ```
190
+
191
+ > The route path must match `endpoint` in your config (default: `/admin/api/server-stats`).
192
+
193
+ #### Debug toolbar routes (optional -- when `devToolbar.enabled: true`)
194
+
195
+ The debug toolbar panels fetch data from these API endpoints. Create a controller and routes:
196
+
197
+ ```ts
198
+ // app/controllers/admin/debug_controller.ts
199
+ import app from '@adonisjs/core/services/app'
200
+ import type { HttpContext } from '@adonisjs/core/http'
201
+ import type { DebugStore } from 'adonisjs-server-stats/debug'
202
+
203
+ export default class DebugController {
204
+ private async getStore(): Promise<DebugStore | null> {
205
+ try {
206
+ return (await app.container.make('debug.store')) as DebugStore
207
+ } catch {
208
+ return null
209
+ }
210
+ }
211
+
212
+ async queries({ response }: HttpContext) {
213
+ const store = await this.getStore()
214
+ if (!store) return response.notFound({ error: 'Debug toolbar not enabled' })
215
+ return response.json({ queries: store.queries.getLatest(500), summary: store.queries.getSummary() })
216
+ }
217
+
218
+ async events({ response }: HttpContext) {
219
+ const store = await this.getStore()
220
+ if (!store) return response.notFound({ error: 'Debug toolbar not enabled' })
221
+ return response.json({ events: store.events.getLatest(200), total: store.events.getTotalCount() })
222
+ }
223
+
224
+ async routes({ response }: HttpContext) {
225
+ const store = await this.getStore()
226
+ if (!store) return response.notFound({ error: 'Debug toolbar not enabled' })
227
+ return response.json({ routes: store.routes.getRoutes(), total: store.routes.getRouteCount() })
228
+ }
229
+
230
+ async logs({ response }: HttpContext) {
231
+ const store = await this.getStore()
232
+ if (!store) return response.notFound({ error: 'Debug toolbar not enabled' })
233
+ return response.json({ logs: store.logs.getLatest(500), total: store.logs.getTotalCount() })
234
+ }
235
+
236
+ async emails({ response }: HttpContext) {
237
+ const store = await this.getStore()
238
+ if (!store) return response.notFound({ error: 'Debug toolbar not enabled' })
239
+ const emails = store.emails.getLatest(100)
240
+ const stripped = emails.map(({ html, text, ...rest }) => rest)
241
+ return response.json({ emails: stripped, total: store.emails.getTotalCount() })
242
+ }
243
+
244
+ async emailPreview({ params, response }: HttpContext) {
245
+ const store = await this.getStore()
246
+ if (!store) return response.notFound({ error: 'Debug toolbar not enabled' })
247
+ const html = store.emails.getEmailHtml(Number(params.id))
248
+ if (!html) return response.notFound({ error: 'Email not found' })
249
+ return response.header('Content-Type', 'text/html; charset=utf-8').send(html)
250
+ }
251
+
252
+ async traces({ response }: HttpContext) {
253
+ const store = await this.getStore()
254
+ if (!store) return response.notFound({ error: 'Debug toolbar not enabled' })
255
+ if (!store.traces) return response.json({ traces: [], total: 0 })
256
+ const traces = store.traces.getLatest(100)
257
+ const list = traces.map(({ spans, warnings, ...rest }: any) => ({
258
+ ...rest,
259
+ warningCount: warnings.length,
260
+ }))
261
+ return response.json({ traces: list, total: store.traces.getTotalCount() })
262
+ }
263
+
264
+ async traceDetail({ params, response }: HttpContext) {
265
+ const store = await this.getStore()
266
+ if (!store) return response.notFound({ error: 'Debug toolbar not enabled' })
267
+ if (!store.traces) return response.notFound({ error: 'Tracing not enabled' })
268
+ const trace = store.traces.getTrace(Number(params.id))
269
+ if (!trace) return response.notFound({ error: 'Trace not found' })
270
+ return response.json(trace)
271
+ }
272
+ }
273
+ ```
274
+
275
+ ```ts
276
+ // start/routes.ts
277
+ router
278
+ .group(() => {
279
+ router.get('queries', '#controllers/admin/debug_controller.queries')
280
+ router.get('events', '#controllers/admin/debug_controller.events')
281
+ router.get('routes', '#controllers/admin/debug_controller.routes')
282
+ router.get('logs', '#controllers/admin/debug_controller.logs')
283
+ router.get('emails', '#controllers/admin/debug_controller.emails')
284
+ router.get('emails/:id/preview', '#controllers/admin/debug_controller.emailPreview')
285
+ router.get('traces', '#controllers/admin/debug_controller.traces')
286
+ router.get('traces/:id', '#controllers/admin/debug_controller.traceDetail')
287
+ })
288
+ .prefix('/admin/api/debug')
289
+ .use(middleware.superadmin())
290
+ ```
291
+
292
+ #### Dashboard routes (automatic -- when `devToolbar.dashboard: true`)
293
+
294
+ The full-page dashboard at `/__stats` **registers its own routes automatically** -- no manual route setup needed. The following routes are created under the configured `dashboardPath` (default: `/__stats`):
295
+
296
+ | Method | Path | Description |
297
+ |--------|------|-------------|
298
+ | GET | `/` | Dashboard page (HTML) |
299
+ | GET | `/api/overview` | Overview metrics |
300
+ | GET | `/api/overview/chart` | Time-series chart data |
301
+ | GET | `/api/requests` | Paginated request history |
302
+ | GET | `/api/requests/:id` | Request detail with queries/trace |
303
+ | GET | `/api/queries` | Paginated query list |
304
+ | GET | `/api/queries/grouped` | Queries grouped by normalized SQL |
305
+ | GET | `/api/queries/:id/explain` | EXPLAIN plan for a query |
306
+ | GET | `/api/events` | Paginated event list |
307
+ | GET | `/api/routes` | Route table |
308
+ | GET | `/api/logs` | Paginated log entries |
309
+ | GET | `/api/emails` | Paginated email list |
310
+ | GET | `/api/emails/:id/preview` | Email HTML preview |
311
+ | GET | `/api/traces` | Paginated trace list |
312
+ | GET | `/api/traces/:id` | Trace detail with spans |
313
+ | GET | `/api/cache` | Cache stats and key listing |
314
+ | GET | `/api/cache/:key` | Cache key detail |
315
+ | GET | `/api/jobs` | Job queue overview |
316
+ | GET | `/api/jobs/:id` | Job detail |
317
+ | POST | `/api/jobs/:id/retry` | Retry a failed job |
318
+ | GET | `/api/config` | App config (secrets redacted) |
319
+ | GET | `/api/filters` | Saved filters |
320
+ | POST | `/api/filters` | Create saved filter |
321
+ | DELETE | `/api/filters/:id` | Delete saved filter |
322
+
323
+ All dashboard routes are gated by the `shouldShow` callback if configured.
324
+
325
+ ### 5. Render the stats bar
188
326
 
189
327
  **Edge** (add before `</body>`):
190
328
 
@@ -226,7 +364,7 @@ export default class ServerStatsController {
226
364
  | `dashboardPath` | `string` | `'/__stats'` | URL path for the dashboard page |
227
365
  | `retentionDays` | `number` | `7` | Days to keep historical data in SQLite |
228
366
  | `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. |
367
+ | `excludeFromTracing` | `string[]` | `['/admin/api/debug', '/admin/api/server-stats']` | 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
368
  | `panes` | `DebugPane[]` | -- | Custom debug panel tabs |
231
369
 
232
370
  ---
@@ -393,28 +531,22 @@ export default defineConfig({
393
531
  })
394
532
  ```
395
533
 
396
- Register the debug API routes:
397
-
398
- ```ts
399
- // start/routes.ts
400
- router
401
- .group(() => {
402
- router.get('queries', '#controllers/admin/debug_controller.queries')
403
- router.get('events', '#controllers/admin/debug_controller.events')
404
- router.get('routes', '#controllers/admin/debug_controller.routes')
405
- router.get('emails', '#controllers/admin/debug_controller.emails')
406
- router.get('emails/:id/preview', '#controllers/admin/debug_controller.emailPreview')
407
- router.get('traces', '#controllers/admin/debug_controller.traces')
408
- router.get('traces/:id', '#controllers/admin/debug_controller.traceDetail')
409
- })
410
- .prefix('/admin/api/debug')
411
- .use(middleware.admin())
412
- ```
534
+ Register the debug API routes (see [step 4](#4-add-routes) for the full controller and route setup).
413
535
 
414
536
  ### Built-in Emails Tab
415
537
 
416
538
  The debug toolbar captures all emails sent via AdonisJS mail (`mail:sending`, `mail:sent`, `mail:queued`, `queued:mail:error` events). Click any email row to preview its HTML in an iframe.
417
539
 
540
+ > **Note:** Email previews are rendered in an iframe. If your app uses `@adonisjs/shield` with the default `X-Frame-Options: DENY` header, the preview will be blocked. Change it to `SAMEORIGIN` in your shield config:
541
+ >
542
+ > ```ts
543
+ > // config/shield.ts
544
+ > xFrame: {
545
+ > enabled: true,
546
+ > action: 'SAMEORIGIN',
547
+ > },
548
+ > ```
549
+
418
550
  ### Persistent Debug Data
419
551
 
420
552
  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:
@@ -1 +1 @@
1
- {"version":3,"file":"server_stats_provider.d.ts","sourceRoot":"","sources":["../../../src/provider/server_stats_provider.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAA;AAE9D,MAAM,CAAC,OAAO,OAAO,mBAAmB;IAY1B,SAAS,CAAC,GAAG,EAAE,kBAAkB;IAX7C,OAAO,CAAC,UAAU,CAA8C;IAChE,OAAO,CAAC,MAAM,CAA2B;IACzC,OAAO,CAAC,UAAU,CAA0B;IAC5C,OAAO,CAAC,cAAc,CAA8B;IACpD,OAAO,CAAC,mBAAmB,CAAmC;IAC9D,OAAO,CAAC,kBAAkB,CAAgC;IAC1D,OAAO,CAAC,uBAAuB,CAA8C;IAC7E,OAAO,CAAC,mBAAmB,CAA6C;IACxE,OAAO,CAAC,WAAW,CAAsB;IACzC,OAAO,CAAC,UAAU,CAA8C;gBAE1C,GAAG,EAAE,kBAAkB;IAEvC,IAAI;IAkCJ,KAAK;YA8EG,eAAe;IAmF7B;;;;;;OAMG;YACW,cAAc;IAoGtB,QAAQ;CAwCf"}
1
+ {"version":3,"file":"server_stats_provider.d.ts","sourceRoot":"","sources":["../../../src/provider/server_stats_provider.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAA;AAE9D,MAAM,CAAC,OAAO,OAAO,mBAAmB;IAY1B,SAAS,CAAC,GAAG,EAAE,kBAAkB;IAX7C,OAAO,CAAC,UAAU,CAA8C;IAChE,OAAO,CAAC,MAAM,CAA2B;IACzC,OAAO,CAAC,UAAU,CAA0B;IAC5C,OAAO,CAAC,cAAc,CAA8B;IACpD,OAAO,CAAC,mBAAmB,CAAmC;IAC9D,OAAO,CAAC,kBAAkB,CAAgC;IAC1D,OAAO,CAAC,uBAAuB,CAA8C;IAC7E,OAAO,CAAC,mBAAmB,CAA6C;IACxE,OAAO,CAAC,WAAW,CAAsB;IACzC,OAAO,CAAC,UAAU,CAA8C;gBAE1C,GAAG,EAAE,kBAAkB;IAEvC,IAAI;IAkCJ,KAAK;YA+EG,eAAe;IAmF7B;;;;;;OAMG;YACW,cAAc;IAoGtB,QAAQ;CAwCf"}
@@ -79,7 +79,8 @@ export default class ServerStatsProvider {
79
79
  });
80
80
  // Exclude the stats endpoint and user-specified prefixes from tracing
81
81
  // so the debug panel's own polling doesn't flood the timeline
82
- const prefixes = [...(toolbarConfig.excludeFromTracing ?? [])];
82
+ const defaultExcludes = ['/admin/api/debug', '/admin/api/server-stats'];
83
+ const prefixes = [...(toolbarConfig.excludeFromTracing ?? defaultExcludes)];
83
84
  if (typeof config.endpoint === 'string') {
84
85
  prefixes.push(config.endpoint);
85
86
  }
@@ -281,10 +281,10 @@ export interface DevToolbarOptions {
281
281
  *
282
282
  * @example
283
283
  * ```ts
284
- * excludeFromTracing: ['/admin/api/debug']
284
+ * excludeFromTracing: ['/admin/api/debug', '/admin/api/server-stats']
285
285
  * ```
286
286
  *
287
- * @default []
287
+ * @default ['/admin/api/debug', '/admin/api/server-stats']
288
288
  */
289
289
  excludeFromTracing?: string[];
290
290
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "adonisjs-server-stats",
3
- "version": "1.5.1",
3
+ "version": "1.5.2",
4
4
  "description": "Real-time server monitoring for AdonisJS v6 applications",
5
5
  "keywords": [
6
6
  "adonisjs",