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.
Files changed (95) hide show
  1. package/README.md +148 -9
  2. package/dist/src/dashboard/chart_aggregator.d.ts +21 -0
  3. package/dist/src/dashboard/chart_aggregator.d.ts.map +1 -0
  4. package/dist/src/dashboard/chart_aggregator.js +89 -0
  5. package/dist/src/dashboard/dashboard_controller.d.ts +147 -0
  6. package/dist/src/dashboard/dashboard_controller.d.ts.map +1 -0
  7. package/dist/src/dashboard/dashboard_controller.js +1008 -0
  8. package/dist/src/dashboard/dashboard_routes.d.ts +16 -0
  9. package/dist/src/dashboard/dashboard_routes.d.ts.map +1 -0
  10. package/dist/src/dashboard/dashboard_routes.js +88 -0
  11. package/dist/src/dashboard/dashboard_store.d.ts +158 -0
  12. package/dist/src/dashboard/dashboard_store.d.ts.map +1 -0
  13. package/dist/src/dashboard/dashboard_store.js +723 -0
  14. package/dist/src/dashboard/integrations/cache_inspector.d.ts +88 -0
  15. package/dist/src/dashboard/integrations/cache_inspector.d.ts.map +1 -0
  16. package/dist/src/dashboard/integrations/cache_inspector.js +215 -0
  17. package/dist/src/dashboard/integrations/config_inspector.d.ts +33 -0
  18. package/dist/src/dashboard/integrations/config_inspector.d.ts.map +1 -0
  19. package/dist/src/dashboard/integrations/config_inspector.js +155 -0
  20. package/dist/src/dashboard/integrations/index.d.ts +7 -0
  21. package/dist/src/dashboard/integrations/index.d.ts.map +1 -0
  22. package/dist/src/dashboard/integrations/index.js +3 -0
  23. package/dist/src/dashboard/integrations/queue_inspector.d.ts +106 -0
  24. package/dist/src/dashboard/integrations/queue_inspector.d.ts.map +1 -0
  25. package/dist/src/dashboard/integrations/queue_inspector.js +182 -0
  26. package/dist/src/dashboard/migrator.d.ts +18 -0
  27. package/dist/src/dashboard/migrator.d.ts.map +1 -0
  28. package/dist/src/dashboard/migrator.js +144 -0
  29. package/dist/src/dashboard/models/stats_email.d.ts +19 -0
  30. package/dist/src/dashboard/models/stats_email.d.ts.map +1 -0
  31. package/dist/src/dashboard/models/stats_email.js +66 -0
  32. package/dist/src/dashboard/models/stats_event.d.ts +14 -0
  33. package/dist/src/dashboard/models/stats_event.d.ts.map +1 -0
  34. package/dist/src/dashboard/models/stats_event.js +43 -0
  35. package/dist/src/dashboard/models/stats_log.d.ts +12 -0
  36. package/dist/src/dashboard/models/stats_log.d.ts.map +1 -0
  37. package/dist/src/dashboard/models/stats_log.js +42 -0
  38. package/dist/src/dashboard/models/stats_metric.d.ts +15 -0
  39. package/dist/src/dashboard/models/stats_metric.d.ts.map +1 -0
  40. package/dist/src/dashboard/models/stats_metric.js +50 -0
  41. package/dist/src/dashboard/models/stats_query.d.ts +20 -0
  42. package/dist/src/dashboard/models/stats_query.d.ts.map +1 -0
  43. package/dist/src/dashboard/models/stats_query.js +67 -0
  44. package/dist/src/dashboard/models/stats_request.d.ts +21 -0
  45. package/dist/src/dashboard/models/stats_request.d.ts.map +1 -0
  46. package/dist/src/dashboard/models/stats_request.js +61 -0
  47. package/dist/src/dashboard/models/stats_saved_filter.d.ts +11 -0
  48. package/dist/src/dashboard/models/stats_saved_filter.d.ts.map +1 -0
  49. package/dist/src/dashboard/models/stats_saved_filter.js +38 -0
  50. package/dist/src/dashboard/models/stats_trace.d.ts +19 -0
  51. package/dist/src/dashboard/models/stats_trace.d.ts.map +1 -0
  52. package/dist/src/dashboard/models/stats_trace.js +67 -0
  53. package/dist/src/debug/debug_store.d.ts +5 -0
  54. package/dist/src/debug/debug_store.d.ts.map +1 -1
  55. package/dist/src/debug/debug_store.js +10 -0
  56. package/dist/src/debug/email_collector.d.ts +2 -0
  57. package/dist/src/debug/email_collector.d.ts.map +1 -1
  58. package/dist/src/debug/email_collector.js +4 -0
  59. package/dist/src/debug/event_collector.d.ts +2 -0
  60. package/dist/src/debug/event_collector.d.ts.map +1 -1
  61. package/dist/src/debug/event_collector.js +11 -2
  62. package/dist/src/debug/query_collector.d.ts +2 -0
  63. package/dist/src/debug/query_collector.d.ts.map +1 -1
  64. package/dist/src/debug/query_collector.js +11 -0
  65. package/dist/src/debug/ring_buffer.d.ts +3 -0
  66. package/dist/src/debug/ring_buffer.d.ts.map +1 -1
  67. package/dist/src/debug/ring_buffer.js +6 -0
  68. package/dist/src/debug/trace_collector.d.ts +4 -2
  69. package/dist/src/debug/trace_collector.d.ts.map +1 -1
  70. package/dist/src/debug/trace_collector.js +7 -2
  71. package/dist/src/debug/types.d.ts +8 -0
  72. package/dist/src/debug/types.d.ts.map +1 -1
  73. package/dist/src/edge/client/dashboard.css +1504 -0
  74. package/dist/src/edge/client/dashboard.js +2378 -0
  75. package/dist/src/edge/client/debug-panel.css +528 -108
  76. package/dist/src/edge/client/debug-panel.js +663 -22
  77. package/dist/src/edge/client/stats-bar.css +112 -38
  78. package/dist/src/edge/client/stats-bar.js +37 -3
  79. package/dist/src/edge/plugin.d.ts.map +1 -1
  80. package/dist/src/edge/plugin.js +21 -0
  81. package/dist/src/edge/views/dashboard.edge +382 -0
  82. package/dist/src/edge/views/debug-panel.edge +60 -14
  83. package/dist/src/edge/views/stats-bar.edge +9 -0
  84. package/dist/src/index.d.ts +2 -0
  85. package/dist/src/index.d.ts.map +1 -1
  86. package/dist/src/index.js +1 -0
  87. package/dist/src/middleware/request_tracking_middleware.d.ts +20 -0
  88. package/dist/src/middleware/request_tracking_middleware.d.ts.map +1 -1
  89. package/dist/src/middleware/request_tracking_middleware.js +66 -2
  90. package/dist/src/provider/server_stats_provider.d.ts +13 -0
  91. package/dist/src/provider/server_stats_provider.d.ts.map +1 -1
  92. package/dist/src/provider/server_stats_provider.js +175 -1
  93. package/dist/src/types.d.ts +42 -0
  94. package/dist/src/types.d.ts.map +1 -1
  95. package/package.json +14 -1
@@ -0,0 +1,88 @@
1
+ import type { ApplicationService } from '@adonisjs/core/types';
2
+ export interface CacheStats {
3
+ /** Whether the Redis connection is alive. */
4
+ connected: boolean;
5
+ /** Total keyspace hits. */
6
+ hits: number;
7
+ /** Total keyspace misses. */
8
+ misses: number;
9
+ /** Hit rate as a percentage (0-100). */
10
+ hitRate: number;
11
+ /** Redis memory usage in bytes. */
12
+ memoryUsedBytes: number;
13
+ /** Human-readable memory usage (e.g. "12.34 MB"). */
14
+ memoryUsedHuman: string;
15
+ /** Number of connected clients. */
16
+ connectedClients: number;
17
+ /** Total number of keys across all databases. */
18
+ totalKeys: number;
19
+ }
20
+ export interface CacheKeyEntry {
21
+ /** The full key name. */
22
+ key: string;
23
+ /** Redis data type (string, list, set, hash, zset, stream). */
24
+ type: string;
25
+ /** TTL in seconds, or -1 if no expiry, or -2 if key does not exist. */
26
+ ttl: number;
27
+ }
28
+ export interface CacheKeyListResult {
29
+ /** Keys matching the scan pattern. */
30
+ keys: CacheKeyEntry[];
31
+ /** Cursor for the next SCAN iteration, or '0' when complete. */
32
+ cursor: string;
33
+ }
34
+ export interface CacheKeyDetail {
35
+ /** The full key name. */
36
+ key: string;
37
+ /** The stored value (stringified). */
38
+ value: string;
39
+ /** Redis data type. */
40
+ type: string;
41
+ /** TTL in seconds, or -1 if no expiry. */
42
+ ttl: number;
43
+ /** Approximate size in bytes (from MEMORY USAGE, if available). */
44
+ sizeBytes: number | null;
45
+ }
46
+ /**
47
+ * Inspects Redis cache keys, values, and statistics.
48
+ *
49
+ * Designed for the full-page dashboard's Cache section.
50
+ * All methods are safe to call even when Redis is unavailable --
51
+ * they catch errors and return sensible defaults.
52
+ */
53
+ export declare class CacheInspector {
54
+ private redis;
55
+ constructor(redis: any);
56
+ /**
57
+ * Detect whether `@adonisjs/cache` or `@adonisjs/redis` is available
58
+ * in the application container.
59
+ */
60
+ static isAvailable(app: ApplicationService): Promise<boolean>;
61
+ /**
62
+ * Get high-level cache / Redis statistics.
63
+ */
64
+ getStats(): Promise<CacheStats>;
65
+ /**
66
+ * List keys using Redis SCAN (cursor-based, non-blocking).
67
+ *
68
+ * @param pattern Glob pattern for key matching (default `'*'`).
69
+ * @param cursor SCAN cursor from a previous call (default `'0'`).
70
+ * @param count Hint for how many keys to return per call (default `100`).
71
+ */
72
+ listKeys(pattern?: string, cursor?: string, count?: number): Promise<CacheKeyListResult>;
73
+ /**
74
+ * Get full details for a single cache key.
75
+ */
76
+ getKey(key: string): Promise<CacheKeyDetail | null>;
77
+ /**
78
+ * Delete a cache key.
79
+ *
80
+ * @returns `true` if the key was deleted, `false` otherwise.
81
+ */
82
+ deleteKey(key: string): Promise<boolean>;
83
+ /**
84
+ * Read a key's value using the appropriate Redis command for its type.
85
+ */
86
+ private getValueByType;
87
+ }
88
+ //# sourceMappingURL=cache_inspector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache_inspector.d.ts","sourceRoot":"","sources":["../../../../src/dashboard/integrations/cache_inspector.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAA;AAM9D,MAAM,WAAW,UAAU;IACzB,6CAA6C;IAC7C,SAAS,EAAE,OAAO,CAAA;IAElB,2BAA2B;IAC3B,IAAI,EAAE,MAAM,CAAA;IAEZ,6BAA6B;IAC7B,MAAM,EAAE,MAAM,CAAA;IAEd,wCAAwC;IACxC,OAAO,EAAE,MAAM,CAAA;IAEf,mCAAmC;IACnC,eAAe,EAAE,MAAM,CAAA;IAEvB,qDAAqD;IACrD,eAAe,EAAE,MAAM,CAAA;IAEvB,mCAAmC;IACnC,gBAAgB,EAAE,MAAM,CAAA;IAExB,iDAAiD;IACjD,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,aAAa;IAC5B,yBAAyB;IACzB,GAAG,EAAE,MAAM,CAAA;IAEX,+DAA+D;IAC/D,IAAI,EAAE,MAAM,CAAA;IAEZ,uEAAuE;IACvE,GAAG,EAAE,MAAM,CAAA;CACZ;AAED,MAAM,WAAW,kBAAkB;IACjC,sCAAsC;IACtC,IAAI,EAAE,aAAa,EAAE,CAAA;IAErB,gEAAgE;IAChE,MAAM,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,cAAc;IAC7B,yBAAyB;IACzB,GAAG,EAAE,MAAM,CAAA;IAEX,sCAAsC;IACtC,KAAK,EAAE,MAAM,CAAA;IAEb,uBAAuB;IACvB,IAAI,EAAE,MAAM,CAAA;IAEZ,0CAA0C;IAC1C,GAAG,EAAE,MAAM,CAAA;IAEX,mEAAmE;IACnE,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;CACzB;AAMD;;;;;;GAMG;AACH,qBAAa,cAAc;IACb,OAAO,CAAC,KAAK;gBAAL,KAAK,EAAE,GAAG;IAE9B;;;OAGG;WACU,WAAW,CAAC,GAAG,EAAE,kBAAkB,GAAG,OAAO,CAAC,OAAO,CAAC;IASnE;;OAEG;IACG,QAAQ,IAAI,OAAO,CAAC,UAAU,CAAC;IAoDrC;;;;;;OAMG;IACG,QAAQ,CAAC,OAAO,SAAM,EAAE,MAAM,SAAM,EAAE,KAAK,SAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC;IA0BrF;;OAEG;IACG,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC;IAqBzD;;;;OAIG;IACG,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAa9C;;OAEG;YACW,cAAc;CAkC7B"}
@@ -0,0 +1,215 @@
1
+ // ---------------------------------------------------------------------------
2
+ // CacheInspector
3
+ // ---------------------------------------------------------------------------
4
+ /**
5
+ * Inspects Redis cache keys, values, and statistics.
6
+ *
7
+ * Designed for the full-page dashboard's Cache section.
8
+ * All methods are safe to call even when Redis is unavailable --
9
+ * they catch errors and return sensible defaults.
10
+ */
11
+ export class CacheInspector {
12
+ redis;
13
+ constructor(redis) {
14
+ this.redis = redis;
15
+ }
16
+ /**
17
+ * Detect whether `@adonisjs/cache` or `@adonisjs/redis` is available
18
+ * in the application container.
19
+ */
20
+ static async isAvailable(app) {
21
+ try {
22
+ await app.container.make('redis');
23
+ return true;
24
+ }
25
+ catch {
26
+ return false;
27
+ }
28
+ }
29
+ /**
30
+ * Get high-level cache / Redis statistics.
31
+ */
32
+ async getStats() {
33
+ const defaults = {
34
+ connected: false,
35
+ hits: 0,
36
+ misses: 0,
37
+ hitRate: 0,
38
+ memoryUsedBytes: 0,
39
+ memoryUsedHuman: '0 B',
40
+ connectedClients: 0,
41
+ totalKeys: 0,
42
+ };
43
+ try {
44
+ const pong = await this.redis.ping();
45
+ if (pong !== 'PONG')
46
+ return defaults;
47
+ const [memoryInfo, statsInfo, serverInfo, dbSize] = await Promise.all([
48
+ this.redis.info('memory'),
49
+ this.redis.info('stats'),
50
+ this.redis.info('clients'),
51
+ this.redis.dbsize(),
52
+ ]);
53
+ const memParsed = parseRedisInfo(memoryInfo);
54
+ const statsParsed = parseRedisInfo(statsInfo);
55
+ const clientsParsed = parseRedisInfo(serverInfo);
56
+ const memoryUsedBytes = safeInt(memParsed['used_memory']);
57
+ const memoryUsedHuman = memParsed['used_memory_human'] ?? formatBytes(memoryUsedBytes);
58
+ const connectedClients = safeInt(clientsParsed['connected_clients'] ?? memParsed['connected_clients'] ?? '0');
59
+ const hits = safeInt(statsParsed['keyspace_hits']);
60
+ const misses = safeInt(statsParsed['keyspace_misses']);
61
+ const total = hits + misses;
62
+ const hitRate = total > 0 ? (hits / total) * 100 : 0;
63
+ return {
64
+ connected: true,
65
+ hits,
66
+ misses,
67
+ hitRate,
68
+ memoryUsedBytes,
69
+ memoryUsedHuman,
70
+ connectedClients,
71
+ totalKeys: typeof dbSize === 'number' ? dbSize : 0,
72
+ };
73
+ }
74
+ catch {
75
+ return defaults;
76
+ }
77
+ }
78
+ /**
79
+ * List keys using Redis SCAN (cursor-based, non-blocking).
80
+ *
81
+ * @param pattern Glob pattern for key matching (default `'*'`).
82
+ * @param cursor SCAN cursor from a previous call (default `'0'`).
83
+ * @param count Hint for how many keys to return per call (default `100`).
84
+ */
85
+ async listKeys(pattern = '*', cursor = '0', count = 100) {
86
+ try {
87
+ const [nextCursor, rawKeys] = await this.redis.scan(cursor, 'MATCH', pattern, 'COUNT', count);
88
+ const keys = await Promise.all(rawKeys.map(async (key) => {
89
+ const [type, ttl] = await Promise.all([
90
+ this.redis.type(key),
91
+ this.redis.ttl(key),
92
+ ]);
93
+ return { key, type, ttl };
94
+ }));
95
+ return { keys, cursor: String(nextCursor) };
96
+ }
97
+ catch {
98
+ return { keys: [], cursor: '0' };
99
+ }
100
+ }
101
+ /**
102
+ * Get full details for a single cache key.
103
+ */
104
+ async getKey(key) {
105
+ try {
106
+ const type = (await this.redis.type(key));
107
+ if (type === 'none')
108
+ return null;
109
+ const ttl = (await this.redis.ttl(key));
110
+ const value = await this.getValueByType(key, type);
111
+ let sizeBytes = null;
112
+ try {
113
+ sizeBytes = (await this.redis.call('MEMORY', 'USAGE', key));
114
+ }
115
+ catch {
116
+ // MEMORY USAGE may not be available on older Redis versions
117
+ }
118
+ return { key, value, type, ttl, sizeBytes };
119
+ }
120
+ catch {
121
+ return null;
122
+ }
123
+ }
124
+ /**
125
+ * Delete a cache key.
126
+ *
127
+ * @returns `true` if the key was deleted, `false` otherwise.
128
+ */
129
+ async deleteKey(key) {
130
+ try {
131
+ const count = (await this.redis.del(key));
132
+ return count > 0;
133
+ }
134
+ catch {
135
+ return false;
136
+ }
137
+ }
138
+ // ---------------------------------------------------------------------------
139
+ // Private helpers
140
+ // ---------------------------------------------------------------------------
141
+ /**
142
+ * Read a key's value using the appropriate Redis command for its type.
143
+ */
144
+ async getValueByType(key, type) {
145
+ try {
146
+ switch (type) {
147
+ case 'string': {
148
+ const val = await this.redis.get(key);
149
+ return val ?? '';
150
+ }
151
+ case 'list': {
152
+ const items = await this.redis.lrange(key, 0, 99);
153
+ return JSON.stringify(items);
154
+ }
155
+ case 'set': {
156
+ const members = await this.redis.smembers(key);
157
+ return JSON.stringify(members);
158
+ }
159
+ case 'zset': {
160
+ const entries = await this.redis.zrange(key, 0, 99, 'WITHSCORES');
161
+ return JSON.stringify(entries);
162
+ }
163
+ case 'hash': {
164
+ const hash = await this.redis.hgetall(key);
165
+ return JSON.stringify(hash);
166
+ }
167
+ case 'stream': {
168
+ const messages = await this.redis.xrange(key, '-', '+', 'COUNT', 100);
169
+ return JSON.stringify(messages);
170
+ }
171
+ default:
172
+ return `(unsupported type: ${type})`;
173
+ }
174
+ }
175
+ catch {
176
+ return '(error reading value)';
177
+ }
178
+ }
179
+ }
180
+ // ---------------------------------------------------------------------------
181
+ // Utility functions
182
+ // ---------------------------------------------------------------------------
183
+ /**
184
+ * Parse a Redis INFO response string into a key-value record.
185
+ */
186
+ function parseRedisInfo(info) {
187
+ const result = {};
188
+ for (const line of info.split('\r\n')) {
189
+ const idx = line.indexOf(':');
190
+ if (idx > 0) {
191
+ result[line.slice(0, idx)] = line.slice(idx + 1);
192
+ }
193
+ }
194
+ return result;
195
+ }
196
+ /**
197
+ * Safely parse a string to an integer, returning 0 on failure.
198
+ */
199
+ function safeInt(value) {
200
+ if (!value)
201
+ return 0;
202
+ const n = Number.parseInt(value, 10);
203
+ return Number.isNaN(n) ? 0 : n;
204
+ }
205
+ /**
206
+ * Format bytes into a human-readable string.
207
+ */
208
+ function formatBytes(bytes) {
209
+ if (bytes === 0)
210
+ return '0 B';
211
+ const units = ['B', 'KB', 'MB', 'GB', 'TB'];
212
+ const i = Math.floor(Math.log(bytes) / Math.log(1024));
213
+ const value = bytes / Math.pow(1024, i);
214
+ return `${value.toFixed(2)} ${units[i]}`;
215
+ }
@@ -0,0 +1,33 @@
1
+ import type { ApplicationService } from '@adonisjs/core/types';
2
+ export interface RedactedValue {
3
+ __redacted: true;
4
+ display: string;
5
+ value: string;
6
+ }
7
+ export interface SanitizedConfig {
8
+ /** App configuration from `app.config.all()` with secrets redacted. */
9
+ config: Record<string, any>;
10
+ }
11
+ export interface SanitizedEnvVars {
12
+ /** Environment variables from `process.env` with secrets redacted. */
13
+ env: Record<string, string | RedactedValue>;
14
+ }
15
+ /**
16
+ * Reads and sanitizes application configuration and environment variables.
17
+ *
18
+ * Designed for the full-page dashboard's Config section.
19
+ * Automatically redacts values whose keys match sensitive patterns.
20
+ */
21
+ export declare class ConfigInspector {
22
+ private app;
23
+ constructor(app: ApplicationService);
24
+ /**
25
+ * Get the full application config with sensitive values redacted.
26
+ */
27
+ getConfig(): SanitizedConfig;
28
+ /**
29
+ * Get environment variables with sensitive values redacted.
30
+ */
31
+ getEnvVars(): SanitizedEnvVars;
32
+ }
33
+ //# sourceMappingURL=config_inspector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config_inspector.d.ts","sourceRoot":"","sources":["../../../../src/dashboard/integrations/config_inspector.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAA;AAM9D,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,IAAI,CAAA;IAChB,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,EAAE,MAAM,CAAA;CACd;AAED,MAAM,WAAW,eAAe;IAC9B,uEAAuE;IACvE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;CAC5B;AAED,MAAM,WAAW,gBAAgB;IAC/B,sEAAsE;IACtE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,aAAa,CAAC,CAAA;CAC5C;AA4DD;;;;;GAKG;AACH,qBAAa,eAAe;IACd,OAAO,CAAC,GAAG;gBAAH,GAAG,EAAE,kBAAkB;IAE3C;;OAEG;IACH,SAAS,IAAI,eAAe;IAS5B;;OAEG;IACH,UAAU,IAAI,gBAAgB;CAmB/B"}
@@ -0,0 +1,155 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Sensitive key patterns
3
+ // ---------------------------------------------------------------------------
4
+ /**
5
+ * Patterns matched against key names (case-insensitive) to detect secrets.
6
+ *
7
+ * Uses `(?:^|[_.-])` and `(?:$|[_.-])` as boundaries instead of `\b`
8
+ * because env vars use `_` as separators and `_` is a word character
9
+ * in regex, so `\b` won't match between `CLIENT` and `SECRET` in
10
+ * `GOOGLE_CLIENT_SECRET`.
11
+ */
12
+ const B = '(?:^|[_.\\-])'; // boundary before
13
+ const A = '(?:$|[_.\\-])'; // boundary after
14
+ const SENSITIVE_PATTERNS = [
15
+ new RegExp(`${B}password${A}`, 'i'),
16
+ new RegExp(`${B}secret${A}`, 'i'),
17
+ new RegExp(`${B}token${A}`, 'i'),
18
+ new RegExp(`${B}credential${A}`, 'i'),
19
+ new RegExp(`${B}private${A}`, 'i'),
20
+ new RegExp(`${B}auth${A}`, 'i'),
21
+ // API keys: `api_key`, `apiKey`, `API_KEY`
22
+ /api[_-]?key/i,
23
+ // `_KEY` at end or `_KEY_` in middle (AWS_ACCESS_KEY_ID, ENCRYPTION_KEY, etc.)
24
+ /[_-]key([_-]|$)/i,
25
+ // ACCESS_KEY pattern (AWS credentials)
26
+ /access[_-]?key/i,
27
+ // Exact match for just "key" (standalone)
28
+ /^key$/i,
29
+ // Connection strings and DSNs
30
+ new RegExp(`${B}dsn${A}`, 'i'),
31
+ /connection[_-]?string/i,
32
+ // Email addresses in env var names
33
+ new RegExp(`${B}email${A}`, 'i'),
34
+ new RegExp(`${B}smtp${A}`, 'i'),
35
+ // Database/service URLs (often contain embedded credentials)
36
+ /database[_-]?url/i,
37
+ /redis[_-]?url/i,
38
+ // Webhook secrets
39
+ /webhook[_-]?secret/i,
40
+ // Signing / encryption
41
+ new RegExp(`${B}signing${A}`, 'i'),
42
+ new RegExp(`${B}encryption${A}`, 'i'),
43
+ // App key / app secret
44
+ /app[_-]key/i,
45
+ ];
46
+ const REDACTED_DISPLAY = '••••••••';
47
+ function redact(value) {
48
+ return { __redacted: true, display: REDACTED_DISPLAY, value };
49
+ }
50
+ // ---------------------------------------------------------------------------
51
+ // ConfigInspector
52
+ // ---------------------------------------------------------------------------
53
+ /**
54
+ * Reads and sanitizes application configuration and environment variables.
55
+ *
56
+ * Designed for the full-page dashboard's Config section.
57
+ * Automatically redacts values whose keys match sensitive patterns.
58
+ */
59
+ export class ConfigInspector {
60
+ app;
61
+ constructor(app) {
62
+ this.app = app;
63
+ }
64
+ /**
65
+ * Get the full application config with sensitive values redacted.
66
+ */
67
+ getConfig() {
68
+ try {
69
+ const raw = this.app.config?.all?.() ?? {};
70
+ return { config: sanitizeObject(raw) };
71
+ }
72
+ catch {
73
+ return { config: {} };
74
+ }
75
+ }
76
+ /**
77
+ * Get environment variables with sensitive values redacted.
78
+ */
79
+ getEnvVars() {
80
+ try {
81
+ const env = {};
82
+ const sorted = Object.keys(process.env).sort();
83
+ for (const key of sorted) {
84
+ const value = process.env[key];
85
+ if (value === undefined)
86
+ continue;
87
+ if (isSensitiveKey(key) || isSensitiveValue(value)) {
88
+ env[key] = redact(value);
89
+ }
90
+ else {
91
+ env[key] = value;
92
+ }
93
+ }
94
+ return { env };
95
+ }
96
+ catch {
97
+ return { env: {} };
98
+ }
99
+ }
100
+ }
101
+ // ---------------------------------------------------------------------------
102
+ // Sanitization helpers
103
+ // ---------------------------------------------------------------------------
104
+ /**
105
+ * Check if a key name matches any sensitive pattern.
106
+ */
107
+ function isSensitiveKey(key) {
108
+ return SENSITIVE_PATTERNS.some((pattern) => pattern.test(key));
109
+ }
110
+ /**
111
+ * Check if a value looks sensitive based on its content.
112
+ * Catches email addresses and URLs with embedded credentials.
113
+ */
114
+ function isSensitiveValue(value) {
115
+ // Email addresses
116
+ if (/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value))
117
+ return true;
118
+ // URLs with userinfo (credentials embedded in URL)
119
+ if (/^[a-z][a-z0-9+.-]*:\/\/[^/]*:[^/]*@/i.test(value))
120
+ return true;
121
+ return false;
122
+ }
123
+ /**
124
+ * Recursively sanitize an object, redacting string values whose keys
125
+ * match sensitive patterns. Booleans and numbers are never redacted.
126
+ */
127
+ function sanitizeObject(obj, seen = new WeakSet()) {
128
+ if (obj === null || obj === undefined)
129
+ return obj;
130
+ // Primitive types pass through
131
+ if (typeof obj !== 'object')
132
+ return obj;
133
+ // Avoid circular references
134
+ if (seen.has(obj))
135
+ return '[Circular]';
136
+ seen.add(obj);
137
+ if (Array.isArray(obj)) {
138
+ return obj.map((item) => sanitizeObject(item, seen));
139
+ }
140
+ const result = {};
141
+ for (const key of Object.keys(obj)) {
142
+ const value = obj[key];
143
+ if (typeof value === 'string' && (isSensitiveKey(key) || isSensitiveValue(value))) {
144
+ result[key] = redact(value);
145
+ }
146
+ else if (typeof value === 'object' && value !== null) {
147
+ result[key] = sanitizeObject(value, seen);
148
+ }
149
+ else {
150
+ // Booleans, numbers, and non-sensitive strings pass through
151
+ result[key] = value;
152
+ }
153
+ }
154
+ return result;
155
+ }
@@ -0,0 +1,7 @@
1
+ export { CacheInspector } from './cache_inspector.js';
2
+ export type { CacheStats, CacheKeyEntry, CacheKeyListResult, CacheKeyDetail, } from './cache_inspector.js';
3
+ export { QueueInspector } from './queue_inspector.js';
4
+ export type { QueueOverview, QueueJobSummary, QueueJobDetail, QueueJobListResult, } from './queue_inspector.js';
5
+ export { ConfigInspector } from './config_inspector.js';
6
+ export type { SanitizedConfig, SanitizedEnvVars } from './config_inspector.js';
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/dashboard/integrations/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AACrD,YAAY,EACV,UAAU,EACV,aAAa,EACb,kBAAkB,EAClB,cAAc,GACf,MAAM,sBAAsB,CAAA;AAE7B,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AACrD,YAAY,EACV,aAAa,EACb,eAAe,EACf,cAAc,EACd,kBAAkB,GACnB,MAAM,sBAAsB,CAAA;AAE7B,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAA;AACvD,YAAY,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA"}
@@ -0,0 +1,3 @@
1
+ export { CacheInspector } from './cache_inspector.js';
2
+ export { QueueInspector } from './queue_inspector.js';
3
+ export { ConfigInspector } from './config_inspector.js';
@@ -0,0 +1,106 @@
1
+ import type { ApplicationService } from '@adonisjs/core/types';
2
+ export interface QueueOverview {
3
+ /** Total jobs currently being processed. */
4
+ active: number;
5
+ /** Jobs waiting to be picked up by a worker. */
6
+ waiting: number;
7
+ /** Jobs scheduled for future execution. */
8
+ delayed: number;
9
+ /** Jobs that completed successfully. */
10
+ completed: number;
11
+ /** Jobs that permanently failed. */
12
+ failed: number;
13
+ /** Jobs paused in the queue. */
14
+ paused: number;
15
+ }
16
+ export interface QueueJobSummary {
17
+ /** Bull job ID. */
18
+ id: string;
19
+ /** Job name / type. */
20
+ name: string;
21
+ /** Current job status. */
22
+ status: 'active' | 'waiting' | 'delayed' | 'completed' | 'failed' | 'paused';
23
+ /** Job payload (data). */
24
+ data: any;
25
+ /** Number of attempts so far. */
26
+ attempts: number;
27
+ /** Maximum allowed attempts. */
28
+ maxAttempts: number;
29
+ /** Job progress (0-100 or custom). */
30
+ progress: number | object;
31
+ /** Error message if the job failed, or null. */
32
+ failedReason: string | null;
33
+ /** When the job was added (Unix timestamp ms). */
34
+ createdAt: number;
35
+ /** When processing started (Unix timestamp ms), or null. */
36
+ processedAt: number | null;
37
+ /** When the job finished (Unix timestamp ms), or null. */
38
+ finishedAt: number | null;
39
+ /** Processing duration in ms, or null if not finished. */
40
+ duration: number | null;
41
+ }
42
+ export interface QueueJobDetail extends QueueJobSummary {
43
+ /** Full stack trace if the job failed. */
44
+ stackTrace: string[];
45
+ /** Return value from the job handler, if any. */
46
+ returnValue: any;
47
+ /** Job options (delay, priority, repeat, etc.). */
48
+ opts: Record<string, any>;
49
+ }
50
+ export interface QueueJobListResult {
51
+ /** Jobs for the requested page. */
52
+ jobs: QueueJobSummary[];
53
+ /** Total number of jobs matching the status filter. */
54
+ total: number;
55
+ }
56
+ type JobStatus = 'active' | 'waiting' | 'delayed' | 'completed' | 'failed' | 'paused';
57
+ /**
58
+ * Inspects Bull Queue jobs, counts, and allows retrying failed jobs.
59
+ *
60
+ * Designed for the full-page dashboard's Jobs section.
61
+ * Only functional when `@rlanz/bull-queue` is installed.
62
+ * All methods catch errors and return safe defaults.
63
+ */
64
+ export declare class QueueInspector {
65
+ private queueManager;
66
+ constructor(queueManager: any);
67
+ /**
68
+ * Detect whether `@rlanz/bull-queue` is available in the application container.
69
+ */
70
+ static isAvailable(app: ApplicationService): Promise<boolean>;
71
+ /**
72
+ * Get an overview of job counts by status across all queues.
73
+ */
74
+ getOverview(): Promise<QueueOverview>;
75
+ /**
76
+ * List jobs filtered by status with pagination.
77
+ *
78
+ * @param status Job status to filter by.
79
+ * @param page Page number (1-based).
80
+ * @param perPage Jobs per page.
81
+ */
82
+ listJobs(status?: JobStatus, page?: number, perPage?: number): Promise<QueueJobListResult>;
83
+ /**
84
+ * Get full detail for a single job by ID.
85
+ */
86
+ getJob(id: string): Promise<QueueJobDetail | null>;
87
+ /**
88
+ * Retry a failed job.
89
+ *
90
+ * @returns `true` if the job was successfully requeued.
91
+ */
92
+ retryJob(id: string): Promise<boolean>;
93
+ /**
94
+ * Get the underlying Bull Queue instance from the queue manager.
95
+ *
96
+ * `@rlanz/bull-queue` exposes the BullMQ Queue via `.queue` on the manager
97
+ * or via `.useQueue()`. We try both patterns for compatibility.
98
+ */
99
+ private getQueue;
100
+ /**
101
+ * Format a Bull job into our summary shape.
102
+ */
103
+ private formatJobSummary;
104
+ }
105
+ export {};
106
+ //# sourceMappingURL=queue_inspector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queue_inspector.d.ts","sourceRoot":"","sources":["../../../../src/dashboard/integrations/queue_inspector.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAA;AAM9D,MAAM,WAAW,aAAa;IAC5B,4CAA4C;IAC5C,MAAM,EAAE,MAAM,CAAA;IAEd,gDAAgD;IAChD,OAAO,EAAE,MAAM,CAAA;IAEf,2CAA2C;IAC3C,OAAO,EAAE,MAAM,CAAA;IAEf,wCAAwC;IACxC,SAAS,EAAE,MAAM,CAAA;IAEjB,oCAAoC;IACpC,MAAM,EAAE,MAAM,CAAA;IAEd,gCAAgC;IAChC,MAAM,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,eAAe;IAC9B,mBAAmB;IACnB,EAAE,EAAE,MAAM,CAAA;IAEV,uBAAuB;IACvB,IAAI,EAAE,MAAM,CAAA;IAEZ,0BAA0B;IAC1B,MAAM,EAAE,QAAQ,GAAG,SAAS,GAAG,SAAS,GAAG,WAAW,GAAG,QAAQ,GAAG,QAAQ,CAAA;IAE5E,0BAA0B;IAC1B,IAAI,EAAE,GAAG,CAAA;IAET,iCAAiC;IACjC,QAAQ,EAAE,MAAM,CAAA;IAEhB,gCAAgC;IAChC,WAAW,EAAE,MAAM,CAAA;IAEnB,sCAAsC;IACtC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAA;IAEzB,gDAAgD;IAChD,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;IAE3B,kDAAkD;IAClD,SAAS,EAAE,MAAM,CAAA;IAEjB,4DAA4D;IAC5D,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IAE1B,0DAA0D;IAC1D,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;IAEzB,0DAA0D;IAC1D,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;CACxB;AAED,MAAM,WAAW,cAAe,SAAQ,eAAe;IACrD,0CAA0C;IAC1C,UAAU,EAAE,MAAM,EAAE,CAAA;IAEpB,iDAAiD;IACjD,WAAW,EAAE,GAAG,CAAA;IAEhB,mDAAmD;IACnD,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;CAC1B;AAED,MAAM,WAAW,kBAAkB;IACjC,mCAAmC;IACnC,IAAI,EAAE,eAAe,EAAE,CAAA;IAEvB,uDAAuD;IACvD,KAAK,EAAE,MAAM,CAAA;CACd;AAMD,KAAK,SAAS,GAAG,QAAQ,GAAG,SAAS,GAAG,SAAS,GAAG,WAAW,GAAG,QAAQ,GAAG,QAAQ,CAAA;AAErF;;;;;;GAMG;AACH,qBAAa,cAAc;IACb,OAAO,CAAC,YAAY;gBAAZ,YAAY,EAAE,GAAG;IAErC;;OAEG;WACU,WAAW,CAAC,GAAG,EAAE,kBAAkB,GAAG,OAAO,CAAC,OAAO,CAAC;IASnE;;OAEG;IACG,WAAW,IAAI,OAAO,CAAC,aAAa,CAAC;IAoC3C;;;;;;OAMG;IACG,QAAQ,CACZ,MAAM,GAAE,SAAoB,EAC5B,IAAI,SAAI,EACR,OAAO,SAAK,GACX,OAAO,CAAC,kBAAkB,CAAC;IA0B9B;;OAEG;IACG,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC;IAqBxD;;;;OAIG;IACG,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAsB5C;;;;;OAKG;IACH,OAAO,CAAC,QAAQ;IAuBhB;;OAEG;IACH,OAAO,CAAC,gBAAgB;CAqBzB"}