adonisjs-server-stats 1.16.0 → 1.17.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.
Files changed (91) hide show
  1. package/README.md +14 -2
  2. package/dist/core/core/dashboard-api.d.ts +6 -4
  3. package/dist/core/index.js +9 -6
  4. package/dist/core/types.d.ts +24 -1
  5. package/dist/react/{CacheSection-DkLtukWz.js → CacheSection-C-X2Tnca.js} +48 -44
  6. package/dist/react/{CacheTab-DWeXnEBI.js → CacheTab-D55mfmeE.js} +1 -1
  7. package/dist/react/{ConfigSection-DaVbFPDg.js → ConfigSection-C-HGnZfd.js} +1 -1
  8. package/dist/react/{ConfigTab-D4640WJZ.js → ConfigTab-CNnsOXBh.js} +1 -1
  9. package/dist/react/{CustomPaneTab-DglmAVMC.js → CustomPaneTab-D-267UVd.js} +1 -1
  10. package/dist/react/{EmailsSection-vYx6ExTb.js → EmailsSection-Bqw4DI4r.js} +65 -57
  11. package/dist/react/{EmailsTab-DKDrRmRH.js → EmailsTab-CkHOhj2F.js} +1 -1
  12. package/dist/react/{EventsSection-CapH5xut.js → EventsSection-CMeyjzVC.js} +1 -1
  13. package/dist/react/{EventsTab-BNVEySAm.js → EventsTab-DgS2v1HX.js} +1 -1
  14. package/dist/react/{JobsSection-ZIQsJWid.js → JobsSection-oWH6pbrG.js} +1 -1
  15. package/dist/react/{JobsTab-DXQzXrDt.js → JobsTab-zdxX_as-.js} +1 -1
  16. package/dist/react/{LogEntryRow-BWkHE51-.js → LogEntryRow-Cn8Ee_X0.js} +1 -1
  17. package/dist/react/{LogsSection-ConXdBkL.js → LogsSection-XmiI2MMs.js} +2 -2
  18. package/dist/react/{LogsTab-CJM47LPn.js → LogsTab-ixusY4v7.js} +2 -2
  19. package/dist/react/{OverviewSection-DOMu2qvl.js → OverviewSection-CvhObTLP.js} +119 -111
  20. package/dist/react/{QueriesSection-DFsaOSJI.js → QueriesSection-BvledwBI.js} +1 -1
  21. package/dist/react/{QueriesTab-CY9CG_7L.js → QueriesTab-smQQJvJa.js} +1 -1
  22. package/dist/react/{RequestsSection-CC-eVHsl.js → RequestsSection-DKuYpViv.js} +2 -2
  23. package/dist/react/{RoutesSection-p1DMq41y.js → RoutesSection-5RNVurQ6.js} +1 -1
  24. package/dist/react/{RoutesTab-Cnqy8UcK.js → RoutesTab-CpXNO-Rx.js} +1 -1
  25. package/dist/react/{SplitPaneWrapper-XgkA0QxE.js → SplitPaneWrapper-DQvJnsuk.js} +1 -1
  26. package/dist/react/{TimelineTab-1YOERxe5.js → TimelineTab-LHQHHKkO.js} +2 -2
  27. package/dist/react/components/Dashboard/overview-data-context.d.ts +15 -0
  28. package/dist/react/components/Dashboard/sections/OverviewSection.d.ts +9 -0
  29. package/dist/react/{index-DOSlCpZ9.js → index-LlFvooUN.js} +148 -141
  30. package/dist/react/index.js +1 -1
  31. package/dist/src/collectors/app_collector.d.ts +11 -0
  32. package/dist/src/collectors/app_collector.js +11 -0
  33. package/dist/src/collectors/redis_collector.js +13 -13
  34. package/dist/src/dashboard/cache_handlers.d.ts +17 -3
  35. package/dist/src/dashboard/cache_handlers.js +14 -19
  36. package/dist/src/dashboard/coalesce_cache.d.ts +2 -8
  37. package/dist/src/dashboard/coalesce_cache.js +25 -0
  38. package/dist/src/dashboard/dashboard_controller.d.ts +1 -0
  39. package/dist/src/dashboard/dashboard_controller.js +8 -3
  40. package/dist/src/dashboard/dashboard_store.d.ts +1 -3
  41. package/dist/src/dashboard/dashboard_store.js +14 -9
  42. package/dist/src/dashboard/dashboard_types.d.ts +2 -0
  43. package/dist/src/dashboard/filter_handlers.js +26 -4
  44. package/dist/src/dashboard/flush_manager.d.ts +2 -6
  45. package/dist/src/dashboard/flush_manager.js +9 -11
  46. package/dist/src/dashboard/format_helpers.d.ts +0 -4
  47. package/dist/src/dashboard/format_helpers.js +0 -19
  48. package/dist/src/dashboard/integrations/config_inspector.js +15 -52
  49. package/dist/src/dashboard/migrator_tables.js +4 -0
  50. package/dist/src/dashboard/paginate_helper.js +21 -11
  51. package/dist/src/dashboard/sensitive_patterns.d.ts +76 -0
  52. package/dist/src/dashboard/sensitive_patterns.js +259 -0
  53. package/dist/src/dashboard/write_queue.d.ts +27 -16
  54. package/dist/src/dashboard/write_queue.js +50 -47
  55. package/dist/src/debug/event_collector.d.ts +8 -0
  56. package/dist/src/debug/event_collector.js +12 -0
  57. package/dist/src/debug/query_collector.js +6 -1
  58. package/dist/src/debug/trace_collector.js +26 -13
  59. package/dist/src/edge/client/dashboard.js +2 -2
  60. package/dist/src/edge/client-vue/dashboard.js +5 -5
  61. package/dist/src/edge/views/dashboard.edge +1 -1
  62. package/dist/src/edge/views/debug-panel.edge +1 -1
  63. package/dist/src/edge/views/stats-bar.edge +1 -1
  64. package/dist/src/middleware/request_tracking_middleware.js +7 -2
  65. package/dist/src/provider/dashboard_init.d.ts +9 -1
  66. package/dist/src/provider/dashboard_init.js +19 -0
  67. package/dist/src/provider/email_helpers.js +20 -5
  68. package/dist/src/provider/server_stats_provider.d.ts +9 -0
  69. package/dist/src/provider/server_stats_provider.js +26 -7
  70. package/dist/src/provider/shutdown_helpers.js +3 -1
  71. package/dist/src/provider/toolbar_setup.js +21 -15
  72. package/dist/src/types.d.ts +24 -1
  73. package/dist/vue/CacheSection-Bm0goqX6.js +161 -0
  74. package/dist/vue/{ConfigSection-BiRAiaHj.js → ConfigSection-BrYslIMN.js} +1 -1
  75. package/dist/vue/{EmailsSection-Dl44qyqY.js → EmailsSection-B8N5vaWA.js} +89 -85
  76. package/dist/vue/{EventsSection-CWjeitjU.js → EventsSection-D2Y_xt0m.js} +1 -1
  77. package/dist/vue/{JobsSection-DOBb4LjZ.js → JobsSection-cEKqELLC.js} +1 -1
  78. package/dist/vue/{LogsSection-CXx-HOWJ.js → LogsSection-C3H2c0S3.js} +1 -1
  79. package/dist/vue/{OverviewSection-CyfNQ8uV.js → OverviewSection-I2swuHDa.js} +1 -1
  80. package/dist/vue/{QueriesSection-CXBsFp-y.js → QueriesSection-CF1IE3SN.js} +1 -1
  81. package/dist/vue/{RequestsSection-YTIaZGZd.js → RequestsSection-CuwgXzmU.js} +1 -1
  82. package/dist/vue/{RoutesSection-CDKMey49.js → RoutesSection-BhJCF7Rn.js} +1 -1
  83. package/dist/vue/composables/useDashboardData.d.ts +1 -1
  84. package/dist/vue/{index-14x39RI_.js → index-DUsImuIN.js} +95 -95
  85. package/dist/vue/index.js +1 -1
  86. package/package.json +12 -3
  87. package/dist/src/dashboard/explain_query.d.ts +0 -8
  88. package/dist/src/dashboard/explain_query.js +0 -22
  89. package/dist/src/provider/boot_initializer.d.ts +0 -28
  90. package/dist/src/provider/boot_initializer.js +0 -35
  91. package/dist/vue/CacheSection-CxEBVVkF.js +0 -156
package/README.md CHANGED
@@ -13,6 +13,8 @@ Drop a single Edge tag into your layout and get a live stats bar showing CPU, me
13
13
 
14
14
  Zero frontend dependencies. Zero build step. Just `@serverStats()` and go.
15
15
 
16
+ **Zero runtime dependencies.** Everything this package needs at runtime is either built into Node or an optional peer dependency you already chose to install (`@adonisjs/lucid`, `better-sqlite3`, ...). Nothing extra lands in your production `node_modules` supply chain.
17
+
16
18
  **New (alpha):** Native [React & Vue components](#react--vue-inertiajs--alpha) for Inertia.js apps — same features, framework-native.
17
19
 
18
20
  ![adonisjs-server-stats demo](https://raw.githubusercontent.com/simulieren/adonisjs-server-stats/main/screenshots/demo.gif)
@@ -265,6 +267,8 @@ The following field names still work but will show deprecation warnings at boot.
265
267
  | `dbPath` | `string` | `'.adonisjs/server-stats/dashboard.sqlite3'` | Path to the SQLite database file (relative to app root) |
266
268
  | `debugEndpoint` | `string` | `'/admin/api/debug'` | Base path for the debug toolbar API endpoints |
267
269
  | `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. |
270
+ | `cacheKeyPrefix` | `string` | -- | Allow-list prefix for the Cache tab (e.g. `'myapp:cache:'`). Scopes which Redis keys can be listed, read, and deleted. Unset: unrestricted in dev, single-key access refused in production. Env var `SERVER_STATS_CACHE_KEY_PREFIX` works as a fallback. |
271
+ | `emailBridgeChannel` | `string` | `'adonisjs-server-stats:emails'` | Redis pub/sub channel for the cross-process email bridge. Namespace it on a shared Redis so other subscribers cannot read your outbound mail. |
268
272
  | `panes` | `DebugPane[]` | -- | Custom debug panel tabs |
269
273
 
270
274
  ---
@@ -288,6 +292,14 @@ Each collector is a factory function that returns a `MetricCollector`. All colle
288
292
  | `logCollector(opts?)` | Errors/warnings/entries (5m window), entries/minute | optional | -- |
289
293
  | `appCollector()` | Online users, pending webhooks, pending emails | none | `@adonisjs/lucid` |
290
294
 
295
+ > [!WARNING]
296
+ > `appCollector()` polls **your primary database** -- three `COUNT(*)` queries every collection interval (3s by default), forever. Index the columns it filters on or every tick is a full table scan:
297
+ >
298
+ > ```sql
299
+ > CREATE INDEX webhook_events_status_index ON webhook_events (status);
300
+ > CREATE INDEX scheduled_emails_status_index ON scheduled_emails (status);
301
+ > ```
302
+
291
303
  ### Collector Options
292
304
 
293
305
  ```ts
@@ -464,9 +476,9 @@ An `authorize` callback may be sync or async -- the route guard awaits it, so `a
464
476
 
465
477
  Worth knowing before you expose it, even behind an admin guard:
466
478
 
467
- - **One guard, no tiers.** Anyone who passes `authorize` can also `DELETE /api/cache/:key` and `POST /api/jobs/:id/retry`. Scope cache access with `SERVER_STATS_CACHE_KEY_PREFIX` -- unset means unrestricted.
479
+ - **One guard, no tiers.** Anyone who passes `authorize` can also `DELETE /api/cache/:key` and `POST /api/jobs/:id/retry`. Scope cache access with `advanced.cacheKeyPrefix` (or the `SERVER_STATS_CACHE_KEY_PREFIX` env var) -- unset means unrestricted in dev, and in production single-key cache reads/deletes are refused until a prefix is configured.
468
480
  - **No audit log and no rate limiting** on those mutating endpoints.
469
- - **SQL bindings are truncated at 256 characters but not redacted by key name.** A short secret passed as a bound parameter is stored as-is. Config and env values *are* redacted; query bindings are not.
481
+ - **Redaction is pattern-based, not exhaustive.** SQL bindings, config/env values, stored URLs, and persisted log records are all redacted against a shared rule set (credential-shaped names incl. camelCase, hashes, JWTs, provider key prefixes, long hex/base64 blobs), and anything that survives is truncated at 256 chars. A secret under a name the patterns don't recognise, with an ordinary-looking value, still gets stored.
470
482
 
471
483
  ---
472
484
 
@@ -45,9 +45,10 @@ export declare class DashboardApi {
45
45
  /**
46
46
  * Fetch the value of a specific cache key.
47
47
  *
48
- * @param key - Cache key (will be URI-encoded).
48
+ * @param key - Cache key (will be URI-encoded).
49
+ * @param init - Optional `RequestInit` overrides (e.g. `{ signal }`).
49
50
  */
50
- fetchCacheKey(key: string): Promise<Record<string, unknown>>;
51
+ fetchCacheKey(key: string, init?: RequestInit): Promise<Record<string, unknown>>;
51
52
  /**
52
53
  * Delete a cache entry by key.
53
54
  *
@@ -59,7 +60,8 @@ export declare class DashboardApi {
59
60
  /**
60
61
  * Fetch the HTML preview for an email record.
61
62
  *
62
- * @param id - Email record ID.
63
+ * @param id - Email record ID.
64
+ * @param init - Optional `RequestInit` overrides (e.g. `{ signal }`).
63
65
  */
64
- fetchEmailPreview(id: number): Promise<string>;
66
+ fetchEmailPreview(id: number, init?: RequestInit): Promise<string>;
65
67
  }
@@ -1110,11 +1110,13 @@ class At {
1110
1110
  /**
1111
1111
  * Fetch the value of a specific cache key.
1112
1112
  *
1113
- * @param key - Cache key (will be URI-encoded).
1113
+ * @param key - Cache key (will be URI-encoded).
1114
+ * @param init - Optional `RequestInit` overrides (e.g. `{ signal }`).
1114
1115
  */
1115
- async fetchCacheKey(e) {
1116
+ async fetchCacheKey(e, r) {
1116
1117
  return this.client.fetch(
1117
- `${this.basePath}/cache/${encodeURIComponent(e)}`
1118
+ `${this.basePath}/cache/${encodeURIComponent(e)}`,
1119
+ r
1118
1120
  );
1119
1121
  }
1120
1122
  /**
@@ -1133,10 +1135,11 @@ class At {
1133
1135
  /**
1134
1136
  * Fetch the HTML preview for an email record.
1135
1137
  *
1136
- * @param id - Email record ID.
1138
+ * @param id - Email record ID.
1139
+ * @param init - Optional `RequestInit` overrides (e.g. `{ signal }`).
1137
1140
  */
1138
- async fetchEmailPreview(e) {
1139
- return this.client.fetch(`${this.basePath}/emails/${e}/preview`);
1141
+ async fetchEmailPreview(e, r) {
1142
+ return this.client.fetch(`${this.basePath}/emails/${e}/preview`, r);
1140
1143
  }
1141
1144
  }
1142
1145
  function $t(t) {
@@ -461,7 +461,7 @@ export type AccessGuard = (ctx: import('@adonisjs/core/http').HttpContext) => bo
461
461
  export interface CaptureConfig {
462
462
  /** SQL text, bindings, and timings for every query. */
463
463
  queries?: boolean;
464
- /** Application events (in-memory only never persisted to SQLite). */
464
+ /** Application events, linked to the request that follows them and persisted with it. */
465
465
  events?: boolean;
466
466
  /** Sent mail, including subject and body. */
467
467
  emails?: boolean;
@@ -622,6 +622,29 @@ export interface AdvancedConfig {
622
622
  * @default 200
623
623
  */
624
624
  maxTraces?: number;
625
+ /**
626
+ * Allow-list prefix for the dashboard's cache inspector.
627
+ *
628
+ * Scopes which Redis keys the Cache tab may list, read, and delete
629
+ * (e.g. `'myapp:cache:'`). Unset keeps full access in development for
630
+ * back-compat, but in production single-key reads and deletes are
631
+ * refused until a prefix is configured — a production dashboard must
632
+ * not be able to read session keys or delete rate-limit counters.
633
+ *
634
+ * The `SERVER_STATS_CACHE_KEY_PREFIX` env var is used as a fallback.
635
+ */
636
+ cacheKeyPrefix?: string;
637
+ /**
638
+ * Redis pub/sub channel for the cross-process email bridge.
639
+ *
640
+ * Queue workers publish captured mail on this channel and the web
641
+ * process persists it. On a Redis instance shared between apps or
642
+ * tenants, namespace this (e.g. `myapp:server-stats:emails`) so
643
+ * other subscribers cannot read your outbound mail.
644
+ *
645
+ * @default 'adonisjs-server-stats:emails'
646
+ */
647
+ emailBridgeChannel?: string;
625
648
  }
626
649
  /**
627
650
  * Top-level configuration for `adonisjs-server-stats`.
@@ -1,46 +1,50 @@
1
- import { jsxs as o, jsx as t } from "react/jsx-runtime";
2
- import { useState as c, useCallback as k } from "react";
3
- import { formatCacheSize as x, formatTtl as D } from "adonisjs-server-stats/core";
4
- import { a as N, J as V } from "./index-DOSlCpZ9.js";
5
- import { C as L } from "./CacheStatsBar-CRodCOeP.js";
6
- import { D as F } from "./DataTable-YyShr5B-.js";
7
- import { F as T } from "./FilterBar-CQ7bD669.js";
8
- function _({ options: v = {} }) {
9
- const [d, b] = c(""), [l, i] = c(null), [w, n] = c(null), [C, h] = c(!1), [y, r] = c(null), { data: u, isLoading: g, mutate: m, getApi: p } = N("cache", {
10
- ...v,
11
- search: d
12
- }), a = u, K = k(
1
+ import { jsxs as u, jsx as t } from "react/jsx-runtime";
2
+ import { useState as d, useRef as N, useCallback as v, useEffect as V } from "react";
3
+ import { formatCacheSize as L, formatTtl as R } from "adonisjs-server-stats/core";
4
+ import { a as E, J as F } from "./index-LlFvooUN.js";
5
+ import { C as T } from "./CacheStatsBar-CRodCOeP.js";
6
+ import { F as A } from "./FilterBar-CQ7bD669.js";
7
+ import { D as z } from "./DataTable-YyShr5B-.js";
8
+ function M({ options: g = {} }) {
9
+ const [y, w] = d(""), [r, h] = d(null), [C, n] = d(null), [K, m] = d(!1), [p, c] = d(null), s = N(null), { data: f, isLoading: S, mutate: k, getApi: b } = E("cache", {
10
+ ...g,
11
+ search: y
12
+ }), a = f, x = v(
13
13
  async (e) => {
14
14
  if (confirm(`Delete cache key "${e}"?`))
15
15
  try {
16
- await m(`cache/${encodeURIComponent(e)}`, "delete"), l === e && (i(null), n(null), r(null));
16
+ await k(`cache/${encodeURIComponent(e)}`, "delete"), r === e && (h(null), n(null), c(null));
17
17
  } catch {
18
18
  }
19
19
  },
20
- [m, l]
21
- ), S = k(
20
+ [k, r]
21
+ ), D = v(
22
22
  async (e) => {
23
- if (l === e) {
24
- i(null), n(null), r(null);
23
+ if (s.current?.abort(), r === e) {
24
+ s.current = null, h(null), n(null), c(null);
25
25
  return;
26
26
  }
27
- i(e), n(null), r(null), h(!0);
27
+ h(e), n(null), c(null), m(!0);
28
+ const l = new AbortController();
29
+ s.current = l;
28
30
  try {
29
- const s = await p().fetchCacheKey(e);
31
+ const i = await b().fetchCacheKey(e, { signal: l.signal });
32
+ if (s.current !== l) return;
30
33
  n(
31
- s.value !== void 0 ? s.value : s.data !== void 0 ? s.data : s
32
- ), r(null);
33
- } catch {
34
- n(null), r("Failed to fetch key value");
34
+ i.value !== void 0 ? i.value : i.data !== void 0 ? i.data : i
35
+ ), c(null);
36
+ } catch (o) {
37
+ if (o instanceof Error && o.name === "AbortError" || s.current !== l) return;
38
+ n(null), c("Failed to fetch key value");
35
39
  } finally {
36
- h(!1);
40
+ s.current === l && m(!1);
37
41
  }
38
42
  },
39
- [l, p]
43
+ [r, b]
40
44
  );
41
- return /* @__PURE__ */ o("div", { children: [
45
+ return V(() => () => s.current?.abort(), []), /* @__PURE__ */ u("div", { children: [
42
46
  a?.available && a?.stats && /* @__PURE__ */ t(
43
- L,
47
+ T,
44
48
  {
45
49
  hitRate: a.stats.hitRate ?? 0,
46
50
  hits: a.stats.hits ?? 0,
@@ -49,16 +53,16 @@ function _({ options: v = {} }) {
49
53
  }
50
54
  ),
51
55
  /* @__PURE__ */ t(
52
- T,
56
+ A,
53
57
  {
54
- search: d,
55
- onSearchChange: b,
58
+ search: y,
59
+ onSearchChange: w,
56
60
  placeholder: "Filter cache keys...",
57
61
  summary: `${(a?.keys || a?.data || []).length} keys`
58
62
  }
59
63
  ),
60
- g && !u ? /* @__PURE__ */ t("div", { className: "ss-dash-empty", children: "Loading cache..." }) : !a || !a.available ? /* @__PURE__ */ t("div", { className: "ss-dash-empty", children: "Cache inspector not available" }) : /* @__PURE__ */ t("div", { className: "ss-dash-table-wrap", children: /* @__PURE__ */ t(
61
- F,
64
+ S && !f ? /* @__PURE__ */ t("div", { className: "ss-dash-empty", children: "Loading cache..." }) : !a || !a.available ? /* @__PURE__ */ t("div", { className: "ss-dash-empty", children: "Cache inspector not available" }) : /* @__PURE__ */ t("div", { className: "ss-dash-table-wrap", children: /* @__PURE__ */ t(
65
+ z,
62
66
  {
63
67
  columns: [
64
68
  {
@@ -89,25 +93,25 @@ function _({ options: v = {} }) {
89
93
  key: "size",
90
94
  label: "Size",
91
95
  width: "60px",
92
- render: (e) => e != null && e > 0 ? x(e) : "-"
96
+ render: (e) => e != null && e > 0 ? L(e) : "-"
93
97
  },
94
98
  {
95
99
  key: "ttl",
96
100
  label: "TTL",
97
101
  width: "70px",
98
- render: (e) => e > 0 ? D(e) : "-"
102
+ render: (e) => e > 0 ? R(e) : "-"
99
103
  },
100
104
  {
101
105
  key: "_actions",
102
106
  label: "",
103
107
  width: "60px",
104
- render: (e, f) => /* @__PURE__ */ t(
108
+ render: (e, l) => /* @__PURE__ */ t(
105
109
  "button",
106
110
  {
107
111
  type: "button",
108
112
  className: "ss-dash-retry-btn",
109
- onClick: (s) => {
110
- s.stopPropagation(), K(f.key);
113
+ onClick: (o) => {
114
+ o.stopPropagation(), x(l.key);
111
115
  },
112
116
  children: "Delete"
113
117
  }
@@ -116,20 +120,20 @@ function _({ options: v = {} }) {
116
120
  ],
117
121
  data: a.keys || a.data || [],
118
122
  keyField: "key",
119
- onRowClick: (e) => S(e.key),
123
+ onRowClick: (e) => D(e.key),
120
124
  emptyMessage: "No cache keys found"
121
125
  }
122
126
  ) }),
123
- l && /* @__PURE__ */ o("div", { className: "ss-dash-cache-detail", children: [
124
- /* @__PURE__ */ o("h4", { children: [
127
+ r && /* @__PURE__ */ u("div", { className: "ss-dash-cache-detail", children: [
128
+ /* @__PURE__ */ u("h4", { children: [
125
129
  "Key: ",
126
- l
130
+ r
127
131
  ] }),
128
- C ? /* @__PURE__ */ t("div", { className: "ss-dash-empty", children: "Loading value..." }) : y ? /* @__PURE__ */ t("div", { className: "ss-dash-empty", style: { color: "var(--ss-red-fg)" }, children: y }) : /* @__PURE__ */ t(V, { data: w })
132
+ K ? /* @__PURE__ */ t("div", { className: "ss-dash-empty", children: "Loading value..." }) : p ? /* @__PURE__ */ t("div", { className: "ss-dash-empty", style: { color: "var(--ss-red-fg)" }, children: p }) : /* @__PURE__ */ t(F, { data: C })
129
133
  ] })
130
134
  ] });
131
135
  }
132
136
  export {
133
- _ as CacheSection,
134
- _ as default
137
+ M as CacheSection,
138
+ M as default
135
139
  };
@@ -2,7 +2,7 @@ import { jsx as e, jsxs as t } from "react/jsx-runtime";
2
2
  import { useState as p, useRef as z, useMemo as A, useCallback as K, useEffect as L } from "react";
3
3
  import { formatTtl as $, formatCacheSize as j } from "adonisjs-server-stats/core";
4
4
  import { u as F } from "./useDashboardApiBase-Bi36pJ2L.js";
5
- import { u as D, J as E } from "./index-DOSlCpZ9.js";
5
+ import { u as D, J as E } from "./index-LlFvooUN.js";
6
6
  import { u as V } from "./useResizableTable-CNJmACdt.js";
7
7
  import { C as J } from "./CacheStatsBar-CRodCOeP.js";
8
8
  import { F as M } from "./FilterBar-CQ7bD669.js";
@@ -1,5 +1,5 @@
1
1
  import { jsx as t } from "react/jsx-runtime";
2
- import { a as i } from "./index-DOSlCpZ9.js";
2
+ import { a as i } from "./index-LlFvooUN.js";
3
3
  import { C as n } from "./ConfigContent-Bwvfl_G7.js";
4
4
  function m({ options: o = {} }) {
5
5
  const { data: a, isLoading: s } = i("config", o);
@@ -1,6 +1,6 @@
1
1
  import { jsxs as i, jsx as n } from "react/jsx-runtime";
2
2
  import { u as f } from "./useDashboardApiBase-Bi36pJ2L.js";
3
- import { u as m } from "./index-DOSlCpZ9.js";
3
+ import { u as m } from "./index-LlFvooUN.js";
4
4
  import { C as d } from "./ConfigContent-Bwvfl_G7.js";
5
5
  function b({ options: r, dashboardPath: o }) {
6
6
  const { resolvedOptions: a } = f(o, r), { data: e, isLoading: t, error: s } = m("config", a);
@@ -1,7 +1,7 @@
1
1
  import { jsxs as l, jsx as r } from "react/jsx-runtime";
2
2
  import { useState as w, useMemo as f } from "react";
3
3
  import { compactPreview as k, formatDuration as x, durationClassName as M } from "adonisjs-server-stats/core";
4
- import { u as L, B as A, M as B } from "./index-DOSlCpZ9.js";
4
+ import { u as L, B as A, M as B } from "./index-LlFvooUN.js";
5
5
  import { T as b } from "./TimeAgoCell-o3KigGfM.js";
6
6
  import { u as D } from "./useResizableTable-CNJmACdt.js";
7
7
  import { F as P } from "./FilterBar-CQ7bD669.js";
@@ -1,65 +1,73 @@
1
- import { jsx as s, jsxs as y, Fragment as P } from "react/jsx-runtime";
2
- import { useState as l, useEffect as T, useCallback as j } from "react";
3
- import { resolveCcAddr as N, resolveToAddr as f, resolveFromAddr as b, resolveAttachmentCount as F, resolveTimestamp as _ } from "adonisjs-server-stats/core";
4
- import { T as $ } from "./TimeAgoCell-o3KigGfM.js";
5
- import { a as D } from "./index-DOSlCpZ9.js";
6
- import { D as E } from "./DataTable-YyShr5B-.js";
7
- import { F as O } from "./FilterBar-CQ7bD669.js";
8
- import { E as B } from "./EmailPreviewOverlay-BmXOAvqG.js";
9
- import { P as H } from "./Pagination-BkmzUDY8.js";
10
- function Q({ options: o = {} }) {
11
- const [x, d] = l(1), [i, g] = l(""), [m, n] = l(null), [p, c] = l(null), { data: h, meta: r, isLoading: k } = D("emails", { ...o, page: x, search: i }), u = h || [];
12
- T(() => d(1), [i]);
13
- const A = j(
1
+ import { jsx as s, jsxs as b, Fragment as E } from "react/jsx-runtime";
2
+ import { useState as o, useRef as N, useEffect as y, useCallback as F } from "react";
3
+ import { resolveCcAddr as _, resolveToAddr as g, resolveFromAddr as x, resolveAttachmentCount as $, resolveTimestamp as D } from "adonisjs-server-stats/core";
4
+ import { a as O } from "./index-LlFvooUN.js";
5
+ import { E as R } from "./EmailPreviewOverlay-BmXOAvqG.js";
6
+ import { F as B } from "./FilterBar-CQ7bD669.js";
7
+ import { T as H } from "./TimeAgoCell-o3KigGfM.js";
8
+ import { D as I } from "./DataTable-YyShr5B-.js";
9
+ import { P as L } from "./Pagination-BkmzUDY8.js";
10
+ function X({ options: n = {} }) {
11
+ const [k, m] = o(1), [i, A] = o(""), [p, c] = o(null), [h, d] = o(null), a = N(null), { data: u, meta: l, isLoading: C } = O("emails", { ...n, page: k, search: i }), w = u || [];
12
+ y(() => m(1), [i]);
13
+ const S = F(
14
14
  async (e) => {
15
- if (e.html) {
16
- n(e.id), c(e.html);
15
+ if (a.current?.abort(), e.html) {
16
+ a.current = null, c(e.id), d(e.html);
17
17
  return;
18
18
  }
19
+ const t = new AbortController();
20
+ a.current = t;
19
21
  try {
20
- const { baseUrl: t = "", dashboardEndpoint: a = "/__stats/api", authToken: w } = o, S = `${t}${a}/emails/${e.id}/preview`, v = { Accept: "text/html" };
21
- w && (v.Authorization = `Bearer ${w}`);
22
- const C = await (await fetch(S, { headers: v, credentials: "same-origin" })).text();
23
- n(e.id), c(C);
24
- } catch {
22
+ const { baseUrl: r = "", dashboardEndpoint: P = "/__stats/api", authToken: v } = n, T = `${r}${P}/emails/${e.id}/preview`, f = { Accept: "text/html" };
23
+ v && (f.Authorization = `Bearer ${v}`);
24
+ const j = await (await fetch(T, {
25
+ headers: f,
26
+ credentials: "same-origin",
27
+ signal: t.signal
28
+ })).text();
29
+ if (a.current !== t) return;
30
+ c(e.id), d(j);
31
+ } catch (r) {
32
+ if (r instanceof Error && r.name === "AbortError") return;
25
33
  }
26
34
  },
27
- [o]
35
+ [n]
28
36
  );
29
- if (m && p) {
30
- const e = u.find((a) => a.id === m), t = e ? {
37
+ if (y(() => () => a.current?.abort(), []), p && h) {
38
+ const e = w.find((r) => r.id === p), t = e ? {
31
39
  subject: e.subject,
32
- from: b(e),
33
- to: f(e),
34
- cc: N(e) || null,
40
+ from: x(e),
41
+ to: g(e),
42
+ cc: _(e) || null,
35
43
  status: e.status,
36
44
  mailer: e.mailer
37
45
  } : null;
38
46
  return /* @__PURE__ */ s(
39
- B,
47
+ R,
40
48
  {
41
49
  email: t,
42
- previewHtml: p,
50
+ previewHtml: h,
43
51
  onClose: () => {
44
- n(null), c(null);
52
+ c(null), d(null);
45
53
  },
46
54
  className: "ss-dash-email-preview"
47
55
  }
48
56
  );
49
57
  }
50
- return /* @__PURE__ */ y("div", { children: [
58
+ return /* @__PURE__ */ b("div", { children: [
51
59
  /* @__PURE__ */ s(
52
- O,
60
+ B,
53
61
  {
54
62
  search: i,
55
- onSearchChange: g,
63
+ onSearchChange: A,
56
64
  placeholder: "Filter emails...",
57
- summary: `${r?.total ?? 0} emails`
65
+ summary: `${l?.total ?? 0} emails`
58
66
  }
59
67
  ),
60
- k && !h ? /* @__PURE__ */ s("div", { className: "ss-dash-empty", children: "Loading emails..." }) : /* @__PURE__ */ y(P, { children: [
68
+ C && !u ? /* @__PURE__ */ s("div", { className: "ss-dash-empty", children: "Loading emails..." }) : /* @__PURE__ */ b(E, { children: [
61
69
  /* @__PURE__ */ s("div", { className: "ss-dash-table-wrap", children: /* @__PURE__ */ s(
62
- E,
70
+ I,
63
71
  {
64
72
  columns: [
65
73
  {
@@ -73,11 +81,11 @@ function Q({ options: o = {} }) {
73
81
  label: "From",
74
82
  width: "150px",
75
83
  render: (e, t) => {
76
- const a = b(t);
84
+ const r = x(t);
77
85
  return /* @__PURE__ */ s(
78
86
  "span",
79
87
  {
80
- title: a,
88
+ title: r,
81
89
  style: {
82
90
  color: "var(--ss-text-secondary)",
83
91
  overflow: "hidden",
@@ -85,7 +93,7 @@ function Q({ options: o = {} }) {
85
93
  whiteSpace: "nowrap",
86
94
  display: "block"
87
95
  },
88
- children: a
96
+ children: r
89
97
  }
90
98
  );
91
99
  }
@@ -95,11 +103,11 @@ function Q({ options: o = {} }) {
95
103
  label: "To",
96
104
  width: "150px",
97
105
  render: (e, t) => {
98
- const a = f(t);
106
+ const r = g(t);
99
107
  return /* @__PURE__ */ s(
100
108
  "span",
101
109
  {
102
- title: a,
110
+ title: r,
103
111
  style: {
104
112
  color: "var(--ss-text-secondary)",
105
113
  overflow: "hidden",
@@ -107,7 +115,7 @@ function Q({ options: o = {} }) {
107
115
  whiteSpace: "nowrap",
108
116
  display: "block"
109
117
  },
110
- children: a
118
+ children: r
111
119
  }
112
120
  );
113
121
  }
@@ -147,12 +155,12 @@ function Q({ options: o = {} }) {
147
155
  label: "ATT",
148
156
  width: "40px",
149
157
  render: (e, t) => {
150
- const a = F(t);
151
- return a > 0 ? /* @__PURE__ */ s(
158
+ const r = $(t);
159
+ return r > 0 ? /* @__PURE__ */ s(
152
160
  "span",
153
161
  {
154
162
  style: { color: "var(--ss-dim)", textAlign: "center", display: "block" },
155
- children: a
163
+ children: r
156
164
  }
157
165
  ) : /* @__PURE__ */ s(
158
166
  "span",
@@ -190,11 +198,11 @@ function Q({ options: o = {} }) {
190
198
  label: "Time",
191
199
  width: "80px",
192
200
  render: (e, t) => {
193
- const a = _(t);
201
+ const r = D(t);
194
202
  return /* @__PURE__ */ s(
195
- $,
203
+ H,
196
204
  {
197
- ts: a,
205
+ ts: r,
198
206
  className: "ss-dash-event-time",
199
207
  style: { whiteSpace: "nowrap" }
200
208
  }
@@ -202,25 +210,25 @@ function Q({ options: o = {} }) {
202
210
  }
203
211
  }
204
212
  ],
205
- data: u,
206
- onRowClick: A,
213
+ data: w,
214
+ onRowClick: S,
207
215
  rowClassName: "ss-dash-email-row",
208
216
  emptyMessage: "No emails captured yet"
209
217
  }
210
218
  ) }),
211
- r && /* @__PURE__ */ s(
212
- H,
219
+ l && /* @__PURE__ */ s(
220
+ L,
213
221
  {
214
- page: r.page,
215
- lastPage: r.lastPage,
216
- total: r.total,
217
- onPageChange: d
222
+ page: l.page,
223
+ lastPage: l.lastPage,
224
+ total: l.total,
225
+ onPageChange: m
218
226
  }
219
227
  )
220
228
  ] })
221
229
  ] });
222
230
  }
223
231
  export {
224
- Q as EmailsSection,
225
- Q as default
232
+ X as EmailsSection,
233
+ X as default
226
234
  };
@@ -2,7 +2,7 @@ import { jsx as e, jsxs as i } from "react/jsx-runtime";
2
2
  import { useState as c, useRef as A, useMemo as v, useCallback as y, useEffect as S } from "react";
3
3
  import { resolveTimestamp as j } from "adonisjs-server-stats/core";
4
4
  import { T as q } from "./TimeAgoCell-o3KigGfM.js";
5
- import { u as $ } from "./index-DOSlCpZ9.js";
5
+ import { u as $ } from "./index-LlFvooUN.js";
6
6
  import { u as F } from "./useResizableTable-CNJmACdt.js";
7
7
  import { F as R } from "./FilterBar-CQ7bD669.js";
8
8
  import { E as M } from "./EmailPreviewOverlay-BmXOAvqG.js";
@@ -2,7 +2,7 @@ import { jsxs as i, jsx as e, Fragment as g } from "react/jsx-runtime";
2
2
  import { useState as m, useEffect as f } from "react";
3
3
  import { resolveEventName as y, resolveTimestamp as u } from "adonisjs-server-stats/core";
4
4
  import { T as w } from "./TimeAgoCell-o3KigGfM.js";
5
- import { a as b, J as N } from "./index-DOSlCpZ9.js";
5
+ import { a as b, J as N } from "./index-LlFvooUN.js";
6
6
  import { D as x } from "./DataTable-YyShr5B-.js";
7
7
  import { F as P } from "./FilterBar-CQ7bD669.js";
8
8
  import { P as k } from "./Pagination-BkmzUDY8.js";
@@ -2,7 +2,7 @@ import { jsx as e, jsxs as t } from "react/jsx-runtime";
2
2
  import { useState as p, useMemo as u } from "react";
3
3
  import { resolveTimestamp as g } from "adonisjs-server-stats/core";
4
4
  import { T as b } from "./TimeAgoCell-o3KigGfM.js";
5
- import { u as f, J as v } from "./index-DOSlCpZ9.js";
5
+ import { u as f, J as v } from "./index-LlFvooUN.js";
6
6
  import { u as w } from "./useResizableTable-CNJmACdt.js";
7
7
  import { F as y } from "./FilterBar-CQ7bD669.js";
8
8
  function E({ options: c }) {
@@ -1,7 +1,7 @@
1
1
  import { jsxs as y, jsx as t, Fragment as T } from "react/jsx-runtime";
2
2
  import { useState as l, useCallback as C } from "react";
3
3
  import { extractJobs as D, extractJobStats as F, JOB_STATUS_FILTERS as j, getJobStatusBadgeColor as B, formatDuration as A, resolveJobTimestamp as L } from "adonisjs-server-stats/core";
4
- import { a as R, B as _, J as $ } from "./index-DOSlCpZ9.js";
4
+ import { a as R, B as _, J as $ } from "./index-LlFvooUN.js";
5
5
  import { J as M } from "./JobStatsBar-C7RslAFE.js";
6
6
  import { T as O } from "./TimeAgoCell-o3KigGfM.js";
7
7
  import { D as U } from "./DataTable-YyShr5B-.js";
@@ -3,7 +3,7 @@ import { useState as m, useMemo as C, useCallback as A } from "react";
3
3
  import { extractJobs as B, extractJobStats as $, JOB_STATUS_FILTERS as D, getJobStatusCssClass as F, formatDuration as L, resolveJobTimestamp as R } from "adonisjs-server-stats/core";
4
4
  import { T as P } from "./TimeAgoCell-o3KigGfM.js";
5
5
  import { u as k } from "./useDashboardApiBase-Bi36pJ2L.js";
6
- import { u as E, J as O } from "./index-DOSlCpZ9.js";
6
+ import { u as E, J as O } from "./index-LlFvooUN.js";
7
7
  import { u as q } from "./useResizableTable-CNJmACdt.js";
8
8
  import { F as z } from "./FilterBar-CQ7bD669.js";
9
9
  import { J as M } from "./JobStatsBar-C7RslAFE.js";
@@ -2,7 +2,7 @@ import { jsxs as i, jsx as s } from "react/jsx-runtime";
2
2
  import g from "react";
3
3
  import { T as v } from "./TimeAgoCell-o3KigGfM.js";
4
4
  import { resolveLogLevel as u, resolveLogMessage as f, resolveLogTimestamp as h, resolveLogRequestId as L, getStructuredData as x, getLogLevelCssClass as N } from "adonisjs-server-stats/core";
5
- import { J as y } from "./index-DOSlCpZ9.js";
5
+ import { J as y } from "./index-LlFvooUN.js";
6
6
  function $({ log: o, index: c, expanded: l, onToggleExpand: m, onReqIdClick: e }) {
7
7
  const n = u(o), d = f(o), p = h(o), a = L(o), t = x(o);
8
8
  return /* @__PURE__ */ i(g.Fragment, { children: [
@@ -1,9 +1,9 @@
1
1
  import { jsxs as l, jsx as t } from "react/jsx-runtime";
2
2
  import { useState as a, useCallback as c } from "react";
3
3
  import { LOG_LEVELS as B } from "adonisjs-server-stats/core";
4
- import { a as G } from "./index-DOSlCpZ9.js";
4
+ import { a as G } from "./index-LlFvooUN.js";
5
5
  import { F as T } from "./FilterBar-CQ7bD669.js";
6
- import { L as z } from "./LogEntryRow-BWkHE51-.js";
6
+ import { L as z } from "./LogEntryRow-Cn8Ee_X0.js";
7
7
  import { P as H } from "./Pagination-BkmzUDY8.js";
8
8
  function Z({ options: L = {} }) {
9
9
  const [E, i] = a(1), [b, x] = a(""), [r, q] = a("all"), [n, h] = a(""), [u, p] = a(""), [m, y] = a([]), [v, R] = a("level"), [g, $] = a("equals"), [f, C] = a(""), [F, P] = a(null), o = {};