@http-client-toolkit/dashboard 0.1.0 → 1.0.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/dist/client/assets/index-6z-6Qs7v.css +598 -1
- package/dist/client/assets/index-C17ynkdl.js +13634 -14
- package/dist/client/index.html +1 -1
- package/lib/index.cjs +420 -295
- package/lib/index.cjs.map +1 -1
- package/lib/index.d.cts +221 -168
- package/lib/index.d.ts +221 -168
- package/lib/index.js +398 -292
- package/lib/index.js.map +1 -1
- package/package.json +9 -9
package/lib/index.cjs
CHANGED
|
@@ -6,19 +6,20 @@ var path = require('path');
|
|
|
6
6
|
var url = require('url');
|
|
7
7
|
var http = require('http');
|
|
8
8
|
|
|
9
|
-
var _documentCurrentScript =
|
|
9
|
+
var _documentCurrentScript =
|
|
10
|
+
typeof document !== 'undefined' ? document.currentScript : null;
|
|
10
11
|
// src/adapters/cache/generic.ts
|
|
11
12
|
function createGenericCacheAdapter(store) {
|
|
12
13
|
return {
|
|
13
|
-
type:
|
|
14
|
+
type: 'generic',
|
|
14
15
|
capabilities: {
|
|
15
16
|
canList: false,
|
|
16
17
|
canDelete: true,
|
|
17
18
|
canClear: true,
|
|
18
|
-
canGetStats: false
|
|
19
|
+
canGetStats: false,
|
|
19
20
|
},
|
|
20
21
|
async getStats() {
|
|
21
|
-
return { message:
|
|
22
|
+
return { message: 'Stats not available for this store type' };
|
|
22
23
|
},
|
|
23
24
|
async listEntries() {
|
|
24
25
|
return { entries: [] };
|
|
@@ -31,7 +32,7 @@ function createGenericCacheAdapter(store) {
|
|
|
31
32
|
},
|
|
32
33
|
async clearAll() {
|
|
33
34
|
await store.clear();
|
|
34
|
-
}
|
|
35
|
+
},
|
|
35
36
|
};
|
|
36
37
|
}
|
|
37
38
|
|
|
@@ -39,12 +40,12 @@ function createGenericCacheAdapter(store) {
|
|
|
39
40
|
function createMemoryCacheAdapter(store) {
|
|
40
41
|
const memStore = store;
|
|
41
42
|
return {
|
|
42
|
-
type:
|
|
43
|
+
type: 'memory',
|
|
43
44
|
capabilities: {
|
|
44
45
|
canList: true,
|
|
45
46
|
canDelete: true,
|
|
46
47
|
canClear: true,
|
|
47
|
-
canGetStats: true
|
|
48
|
+
canGetStats: true,
|
|
48
49
|
},
|
|
49
50
|
async getStats() {
|
|
50
51
|
return memStore.getStats();
|
|
@@ -62,7 +63,7 @@ function createMemoryCacheAdapter(store) {
|
|
|
62
63
|
},
|
|
63
64
|
async clearAll() {
|
|
64
65
|
await memStore.clear();
|
|
65
|
-
}
|
|
66
|
+
},
|
|
66
67
|
};
|
|
67
68
|
}
|
|
68
69
|
|
|
@@ -70,12 +71,12 @@ function createMemoryCacheAdapter(store) {
|
|
|
70
71
|
function createSqliteCacheAdapter(store) {
|
|
71
72
|
const sqlStore = store;
|
|
72
73
|
return {
|
|
73
|
-
type:
|
|
74
|
+
type: 'sqlite',
|
|
74
75
|
capabilities: {
|
|
75
76
|
canList: true,
|
|
76
77
|
canDelete: true,
|
|
77
78
|
canClear: true,
|
|
78
|
-
canGetStats: true
|
|
79
|
+
canGetStats: true,
|
|
79
80
|
},
|
|
80
81
|
async getStats() {
|
|
81
82
|
return sqlStore.getStats();
|
|
@@ -86,7 +87,7 @@ function createSqliteCacheAdapter(store) {
|
|
|
86
87
|
const entries = results.map((r) => ({
|
|
87
88
|
hash: r.hash,
|
|
88
89
|
expiresAt: r.expiresAt,
|
|
89
|
-
createdAt: r.createdAt
|
|
90
|
+
createdAt: r.createdAt,
|
|
90
91
|
}));
|
|
91
92
|
return { entries };
|
|
92
93
|
},
|
|
@@ -98,27 +99,27 @@ function createSqliteCacheAdapter(store) {
|
|
|
98
99
|
},
|
|
99
100
|
async clearAll() {
|
|
100
101
|
await sqlStore.clear();
|
|
101
|
-
}
|
|
102
|
+
},
|
|
102
103
|
};
|
|
103
104
|
}
|
|
104
105
|
|
|
105
106
|
// src/adapters/dedup/generic.ts
|
|
106
107
|
function createGenericDedupeAdapter(_store) {
|
|
107
108
|
return {
|
|
108
|
-
type:
|
|
109
|
+
type: 'generic',
|
|
109
110
|
capabilities: {
|
|
110
111
|
canList: false,
|
|
111
|
-
canGetStats: false
|
|
112
|
+
canGetStats: false,
|
|
112
113
|
},
|
|
113
114
|
async getStats() {
|
|
114
|
-
return { message:
|
|
115
|
+
return { message: 'Stats not available for this store type' };
|
|
115
116
|
},
|
|
116
117
|
async listJobs() {
|
|
117
118
|
return { jobs: [] };
|
|
118
119
|
},
|
|
119
120
|
async getJob() {
|
|
120
121
|
return void 0;
|
|
121
|
-
}
|
|
122
|
+
},
|
|
122
123
|
};
|
|
123
124
|
}
|
|
124
125
|
|
|
@@ -126,10 +127,10 @@ function createGenericDedupeAdapter(_store) {
|
|
|
126
127
|
function createMemoryDedupeAdapter(store) {
|
|
127
128
|
const memStore = store;
|
|
128
129
|
return {
|
|
129
|
-
type:
|
|
130
|
+
type: 'memory',
|
|
130
131
|
capabilities: {
|
|
131
132
|
canList: true,
|
|
132
|
-
canGetStats: true
|
|
133
|
+
canGetStats: true,
|
|
133
134
|
},
|
|
134
135
|
async getStats() {
|
|
135
136
|
return memStore.getStats();
|
|
@@ -142,7 +143,7 @@ function createMemoryDedupeAdapter(store) {
|
|
|
142
143
|
async getJob(hash) {
|
|
143
144
|
const jobs = memStore.listJobs(0, 1e3);
|
|
144
145
|
return jobs.find((j) => j.hash === hash);
|
|
145
|
-
}
|
|
146
|
+
},
|
|
146
147
|
};
|
|
147
148
|
}
|
|
148
149
|
|
|
@@ -150,10 +151,10 @@ function createMemoryDedupeAdapter(store) {
|
|
|
150
151
|
function createSqliteDedupeAdapter(store) {
|
|
151
152
|
const sqlStore = store;
|
|
152
153
|
return {
|
|
153
|
-
type:
|
|
154
|
+
type: 'sqlite',
|
|
154
155
|
capabilities: {
|
|
155
156
|
canList: true,
|
|
156
|
-
canGetStats: true
|
|
157
|
+
canGetStats: true,
|
|
157
158
|
},
|
|
158
159
|
async getStats() {
|
|
159
160
|
return sqlStore.getStats();
|
|
@@ -166,22 +167,22 @@ function createSqliteDedupeAdapter(store) {
|
|
|
166
167
|
async getJob(hash) {
|
|
167
168
|
const results = await sqlStore.listJobs(0, 1e3);
|
|
168
169
|
return results.find((j) => j.hash === hash);
|
|
169
|
-
}
|
|
170
|
+
},
|
|
170
171
|
};
|
|
171
172
|
}
|
|
172
173
|
|
|
173
174
|
// src/adapters/rate-limit/generic.ts
|
|
174
175
|
function createGenericRateLimitAdapter(store) {
|
|
175
176
|
return {
|
|
176
|
-
type:
|
|
177
|
+
type: 'generic',
|
|
177
178
|
capabilities: {
|
|
178
179
|
canList: false,
|
|
179
180
|
canGetStats: false,
|
|
180
181
|
canUpdateConfig: false,
|
|
181
|
-
canReset: true
|
|
182
|
+
canReset: true,
|
|
182
183
|
},
|
|
183
184
|
async getStats() {
|
|
184
|
-
return { message:
|
|
185
|
+
return { message: 'Stats not available for this store type' };
|
|
185
186
|
},
|
|
186
187
|
async listResources() {
|
|
187
188
|
return [];
|
|
@@ -190,11 +191,11 @@ function createGenericRateLimitAdapter(store) {
|
|
|
190
191
|
return store.getStatus(name);
|
|
191
192
|
},
|
|
192
193
|
async updateResourceConfig() {
|
|
193
|
-
throw new Error(
|
|
194
|
+
throw new Error('Config updates not supported for this store type');
|
|
194
195
|
},
|
|
195
196
|
async resetResource(name) {
|
|
196
197
|
await store.reset(name);
|
|
197
|
-
}
|
|
198
|
+
},
|
|
198
199
|
};
|
|
199
200
|
}
|
|
200
201
|
|
|
@@ -202,12 +203,12 @@ function createGenericRateLimitAdapter(store) {
|
|
|
202
203
|
function createMemoryRateLimitAdapter(store) {
|
|
203
204
|
const memStore = store;
|
|
204
205
|
return {
|
|
205
|
-
type:
|
|
206
|
+
type: 'memory',
|
|
206
207
|
capabilities: {
|
|
207
208
|
canList: true,
|
|
208
209
|
canGetStats: true,
|
|
209
210
|
canUpdateConfig: true,
|
|
210
|
-
canReset: true
|
|
211
|
+
canReset: true,
|
|
211
212
|
},
|
|
212
213
|
async getStats() {
|
|
213
214
|
return memStore.getStats();
|
|
@@ -223,7 +224,7 @@ function createMemoryRateLimitAdapter(store) {
|
|
|
223
224
|
},
|
|
224
225
|
async resetResource(name) {
|
|
225
226
|
await memStore.reset(name);
|
|
226
|
-
}
|
|
227
|
+
},
|
|
227
228
|
};
|
|
228
229
|
}
|
|
229
230
|
|
|
@@ -231,12 +232,12 @@ function createMemoryRateLimitAdapter(store) {
|
|
|
231
232
|
function createSqliteRateLimitAdapter(store) {
|
|
232
233
|
const sqlStore = store;
|
|
233
234
|
return {
|
|
234
|
-
type:
|
|
235
|
+
type: 'sqlite',
|
|
235
236
|
capabilities: {
|
|
236
237
|
canList: true,
|
|
237
238
|
canGetStats: true,
|
|
238
239
|
canUpdateConfig: true,
|
|
239
|
-
canReset: true
|
|
240
|
+
canReset: true,
|
|
240
241
|
},
|
|
241
242
|
async getStats() {
|
|
242
243
|
return sqlStore.getStats();
|
|
@@ -252,40 +253,72 @@ function createSqliteRateLimitAdapter(store) {
|
|
|
252
253
|
},
|
|
253
254
|
async resetResource(name) {
|
|
254
255
|
await sqlStore.reset(name);
|
|
255
|
-
}
|
|
256
|
+
},
|
|
256
257
|
};
|
|
257
258
|
}
|
|
258
259
|
|
|
259
260
|
// src/adapters/detect.ts
|
|
260
261
|
function isMemoryStore(store) {
|
|
261
|
-
if (!store || typeof store !==
|
|
262
|
+
if (!store || typeof store !== 'object') return false;
|
|
262
263
|
const s = store;
|
|
263
|
-
return
|
|
264
|
+
return (
|
|
265
|
+
typeof s.destroy === 'function' &&
|
|
266
|
+
typeof s.getStats === 'function' &&
|
|
267
|
+
typeof s.cleanup === 'function' &&
|
|
268
|
+
typeof s.getLRUItems === 'function'
|
|
269
|
+
);
|
|
264
270
|
}
|
|
265
271
|
function isSqliteStore(store) {
|
|
266
|
-
if (!store || typeof store !==
|
|
272
|
+
if (!store || typeof store !== 'object') return false;
|
|
267
273
|
const s = store;
|
|
268
|
-
return
|
|
274
|
+
return (
|
|
275
|
+
typeof s.destroy === 'function' &&
|
|
276
|
+
typeof s.getStats === 'function' &&
|
|
277
|
+
typeof s.close === 'function'
|
|
278
|
+
);
|
|
269
279
|
}
|
|
270
280
|
function isMemoryDedupeStore(store) {
|
|
271
|
-
if (!store || typeof store !==
|
|
281
|
+
if (!store || typeof store !== 'object') return false;
|
|
272
282
|
const s = store;
|
|
273
|
-
return
|
|
283
|
+
return (
|
|
284
|
+
typeof s.destroy === 'function' &&
|
|
285
|
+
typeof s.getStats === 'function' &&
|
|
286
|
+
typeof s.listJobs === 'function' &&
|
|
287
|
+
typeof s.cleanup === 'function' &&
|
|
288
|
+
!('close' in s && typeof s.close === 'function')
|
|
289
|
+
);
|
|
274
290
|
}
|
|
275
291
|
function isSqliteDedupeStore(store) {
|
|
276
|
-
if (!store || typeof store !==
|
|
292
|
+
if (!store || typeof store !== 'object') return false;
|
|
277
293
|
const s = store;
|
|
278
|
-
return
|
|
294
|
+
return (
|
|
295
|
+
typeof s.destroy === 'function' &&
|
|
296
|
+
typeof s.getStats === 'function' &&
|
|
297
|
+
typeof s.close === 'function' &&
|
|
298
|
+
typeof s.listJobs === 'function'
|
|
299
|
+
);
|
|
279
300
|
}
|
|
280
301
|
function isMemoryRateLimitStore(store) {
|
|
281
|
-
if (!store || typeof store !==
|
|
302
|
+
if (!store || typeof store !== 'object') return false;
|
|
282
303
|
const s = store;
|
|
283
|
-
return
|
|
304
|
+
return (
|
|
305
|
+
typeof s.destroy === 'function' &&
|
|
306
|
+
typeof s.getStats === 'function' &&
|
|
307
|
+
typeof s.listResources === 'function' &&
|
|
308
|
+
typeof s.setResourceConfig === 'function' &&
|
|
309
|
+
!('close' in s && typeof s.close === 'function')
|
|
310
|
+
);
|
|
284
311
|
}
|
|
285
312
|
function isSqliteRateLimitStore(store) {
|
|
286
|
-
if (!store || typeof store !==
|
|
313
|
+
if (!store || typeof store !== 'object') return false;
|
|
287
314
|
const s = store;
|
|
288
|
-
return
|
|
315
|
+
return (
|
|
316
|
+
typeof s.destroy === 'function' &&
|
|
317
|
+
typeof s.getStats === 'function' &&
|
|
318
|
+
typeof s.close === 'function' &&
|
|
319
|
+
typeof s.listResources === 'function' &&
|
|
320
|
+
typeof s.setResourceConfig === 'function'
|
|
321
|
+
);
|
|
289
322
|
}
|
|
290
323
|
function detectCacheAdapter(store) {
|
|
291
324
|
if (isMemoryStore(store)) {
|
|
@@ -315,35 +348,63 @@ function detectRateLimitAdapter(store) {
|
|
|
315
348
|
return createGenericRateLimitAdapter(store);
|
|
316
349
|
}
|
|
317
350
|
var CLIENT_NAME_REGEX = /^[a-zA-Z0-9_-]+$/;
|
|
318
|
-
var ClientConfigSchema = zod.z
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
)
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
);
|
|
351
|
+
var ClientConfigSchema = zod.z
|
|
352
|
+
.object({
|
|
353
|
+
client: zod.z.custom(
|
|
354
|
+
(val) =>
|
|
355
|
+
val != null &&
|
|
356
|
+
typeof val === 'object' &&
|
|
357
|
+
'stores' in val &&
|
|
358
|
+
'get' in val,
|
|
359
|
+
'Must be an HttpClient instance',
|
|
360
|
+
),
|
|
361
|
+
name: zod.z
|
|
362
|
+
.string()
|
|
363
|
+
.min(1, 'Client name must not be empty')
|
|
364
|
+
.regex(CLIENT_NAME_REGEX, 'Client name must be URL-safe (a-z, 0-9, -, _)')
|
|
365
|
+
.optional(),
|
|
366
|
+
})
|
|
367
|
+
.refine(
|
|
368
|
+
(data) => {
|
|
369
|
+
const { stores } = data.client;
|
|
370
|
+
return stores.cache || stores.dedupe || stores.rateLimit;
|
|
371
|
+
},
|
|
372
|
+
{ message: 'HttpClient must have at least one store configured' },
|
|
373
|
+
);
|
|
374
|
+
function resolveClientName(c) {
|
|
375
|
+
return c.name ?? c.client.name;
|
|
376
|
+
}
|
|
377
|
+
var DashboardOptionsSchema = zod.z
|
|
378
|
+
.object({
|
|
379
|
+
clients: zod.z
|
|
380
|
+
.array(ClientConfigSchema)
|
|
381
|
+
.min(1, 'At least one client is required'),
|
|
382
|
+
basePath: zod.z.string().default('/'),
|
|
383
|
+
pollIntervalMs: zod.z.number().int().positive().default(5e3),
|
|
384
|
+
})
|
|
385
|
+
.refine(
|
|
386
|
+
(data) => {
|
|
387
|
+
const names = data.clients.map(resolveClientName);
|
|
388
|
+
return new Set(names).size === names.length;
|
|
389
|
+
},
|
|
390
|
+
{ message: 'Client names must be unique' },
|
|
391
|
+
);
|
|
341
392
|
var StandaloneDashboardOptionsSchema = DashboardOptionsSchema.and(
|
|
342
393
|
zod.z.object({
|
|
343
394
|
port: zod.z.number().int().nonnegative().default(4e3),
|
|
344
|
-
host: zod.z.string().default(
|
|
345
|
-
})
|
|
395
|
+
host: zod.z.string().default('localhost'),
|
|
396
|
+
}),
|
|
346
397
|
);
|
|
398
|
+
function normalizeClient(config) {
|
|
399
|
+
const name = config.name ?? config.client.name;
|
|
400
|
+
const { stores } = config.client;
|
|
401
|
+
return {
|
|
402
|
+
name,
|
|
403
|
+
cacheStore: stores.cache,
|
|
404
|
+
dedupeStore: stores.dedupe,
|
|
405
|
+
rateLimitStore: stores.rateLimit,
|
|
406
|
+
};
|
|
407
|
+
}
|
|
347
408
|
function validateDashboardOptions(options) {
|
|
348
409
|
return DashboardOptionsSchema.parse(options);
|
|
349
410
|
}
|
|
@@ -353,41 +414,41 @@ function validateStandaloneOptions(options) {
|
|
|
353
414
|
|
|
354
415
|
// src/server/request-helpers.ts
|
|
355
416
|
function parseUrl(req, basePath) {
|
|
356
|
-
const raw = req.url ??
|
|
357
|
-
const url = new URL(raw,
|
|
417
|
+
const raw = req.url ?? '/';
|
|
418
|
+
const url = new URL(raw, 'http://localhost');
|
|
358
419
|
let pathname = url.pathname;
|
|
359
|
-
if (basePath !==
|
|
360
|
-
pathname = pathname.slice(basePath.length) ||
|
|
420
|
+
if (basePath !== '/' && pathname.startsWith(basePath)) {
|
|
421
|
+
pathname = pathname.slice(basePath.length) || '/';
|
|
361
422
|
}
|
|
362
423
|
return { pathname, query: url.searchParams };
|
|
363
424
|
}
|
|
364
425
|
function extractParam(pathname, pattern) {
|
|
365
|
-
const patternParts = pattern.split(
|
|
366
|
-
const pathParts = pathname.split(
|
|
426
|
+
const patternParts = pattern.split('/');
|
|
427
|
+
const pathParts = pathname.split('/');
|
|
367
428
|
if (patternParts.length !== pathParts.length) return void 0;
|
|
368
429
|
for (let i = 0; i < patternParts.length; i++) {
|
|
369
430
|
const pp = patternParts[i];
|
|
370
|
-
if (pp.startsWith(
|
|
431
|
+
if (pp.startsWith(':')) continue;
|
|
371
432
|
if (pp !== pathParts[i]) return void 0;
|
|
372
433
|
}
|
|
373
|
-
const paramIndex = patternParts.findIndex((p) => p.startsWith(
|
|
434
|
+
const paramIndex = patternParts.findIndex((p) => p.startsWith(':'));
|
|
374
435
|
if (paramIndex === -1) return void 0;
|
|
375
436
|
return pathParts[paramIndex];
|
|
376
437
|
}
|
|
377
438
|
async function readJsonBody(req) {
|
|
378
439
|
return new Promise((resolve, reject) => {
|
|
379
|
-
let body =
|
|
380
|
-
req.on(
|
|
440
|
+
let body = '';
|
|
441
|
+
req.on('data', (chunk) => {
|
|
381
442
|
body += chunk.toString();
|
|
382
443
|
});
|
|
383
|
-
req.on(
|
|
444
|
+
req.on('end', () => {
|
|
384
445
|
try {
|
|
385
446
|
resolve(JSON.parse(body));
|
|
386
447
|
} catch {
|
|
387
|
-
reject(new Error(
|
|
448
|
+
reject(new Error('Invalid JSON body'));
|
|
388
449
|
}
|
|
389
450
|
});
|
|
390
|
-
req.on(
|
|
451
|
+
req.on('error', reject);
|
|
391
452
|
});
|
|
392
453
|
}
|
|
393
454
|
|
|
@@ -395,9 +456,9 @@ async function readJsonBody(req) {
|
|
|
395
456
|
function sendJson(res, data, status = 200) {
|
|
396
457
|
const body = JSON.stringify(data);
|
|
397
458
|
res.writeHead(status, {
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
459
|
+
'Content-Type': 'application/json',
|
|
460
|
+
'Content-Length': Buffer.byteLength(body),
|
|
461
|
+
'Cache-Control': 'no-store',
|
|
401
462
|
});
|
|
402
463
|
res.end(body);
|
|
403
464
|
}
|
|
@@ -405,10 +466,10 @@ function sendError(res, message, status = 500) {
|
|
|
405
466
|
sendJson(res, { error: message }, status);
|
|
406
467
|
}
|
|
407
468
|
function sendNotFound(res) {
|
|
408
|
-
sendError(res,
|
|
469
|
+
sendError(res, 'Not found', 404);
|
|
409
470
|
}
|
|
410
471
|
function sendMethodNotAllowed(res) {
|
|
411
|
-
sendError(res,
|
|
472
|
+
sendError(res, 'Method not allowed', 405);
|
|
412
473
|
}
|
|
413
474
|
|
|
414
475
|
// src/server/handlers/cache.ts
|
|
@@ -417,22 +478,22 @@ async function handleCacheStats(res, adapter) {
|
|
|
417
478
|
const stats = await adapter.getStats();
|
|
418
479
|
sendJson(res, { stats, capabilities: adapter.capabilities });
|
|
419
480
|
} catch (err) {
|
|
420
|
-
sendError(res, err instanceof Error ? err.message :
|
|
481
|
+
sendError(res, err instanceof Error ? err.message : 'Unknown error');
|
|
421
482
|
}
|
|
422
483
|
}
|
|
423
484
|
async function handleCacheEntries(_req, res, adapter, query) {
|
|
424
485
|
try {
|
|
425
|
-
const page = parseInt(query.get(
|
|
426
|
-
const limit = parseInt(query.get(
|
|
486
|
+
const page = parseInt(query.get('page') ?? '0', 10);
|
|
487
|
+
const limit = parseInt(query.get('limit') ?? '50', 10);
|
|
427
488
|
const result = await adapter.listEntries(page, limit);
|
|
428
489
|
sendJson(res, result);
|
|
429
490
|
} catch (err) {
|
|
430
|
-
sendError(res, err instanceof Error ? err.message :
|
|
491
|
+
sendError(res, err instanceof Error ? err.message : 'Unknown error');
|
|
431
492
|
}
|
|
432
493
|
}
|
|
433
494
|
async function handleCacheEntry(res, adapter, pathname) {
|
|
434
495
|
try {
|
|
435
|
-
const hash = extractParam(pathname,
|
|
496
|
+
const hash = extractParam(pathname, '/cache/entries/:hash');
|
|
436
497
|
if (!hash) {
|
|
437
498
|
sendNotFound(res);
|
|
438
499
|
return;
|
|
@@ -444,12 +505,12 @@ async function handleCacheEntry(res, adapter, pathname) {
|
|
|
444
505
|
}
|
|
445
506
|
sendJson(res, { hash, value: entry });
|
|
446
507
|
} catch (err) {
|
|
447
|
-
sendError(res, err instanceof Error ? err.message :
|
|
508
|
+
sendError(res, err instanceof Error ? err.message : 'Unknown error');
|
|
448
509
|
}
|
|
449
510
|
}
|
|
450
511
|
async function handleDeleteCacheEntry(res, adapter, pathname) {
|
|
451
512
|
try {
|
|
452
|
-
const hash = extractParam(pathname,
|
|
513
|
+
const hash = extractParam(pathname, '/cache/entries/:hash');
|
|
453
514
|
if (!hash) {
|
|
454
515
|
sendNotFound(res);
|
|
455
516
|
return;
|
|
@@ -457,7 +518,7 @@ async function handleDeleteCacheEntry(res, adapter, pathname) {
|
|
|
457
518
|
await adapter.deleteEntry(hash);
|
|
458
519
|
sendJson(res, { deleted: true });
|
|
459
520
|
} catch (err) {
|
|
460
|
-
sendError(res, err instanceof Error ? err.message :
|
|
521
|
+
sendError(res, err instanceof Error ? err.message : 'Unknown error');
|
|
461
522
|
}
|
|
462
523
|
}
|
|
463
524
|
async function handleClearCache(res, adapter) {
|
|
@@ -465,7 +526,7 @@ async function handleClearCache(res, adapter) {
|
|
|
465
526
|
await adapter.clearAll();
|
|
466
527
|
sendJson(res, { cleared: true });
|
|
467
528
|
} catch (err) {
|
|
468
|
-
sendError(res, err instanceof Error ? err.message :
|
|
529
|
+
sendError(res, err instanceof Error ? err.message : 'Unknown error');
|
|
469
530
|
}
|
|
470
531
|
}
|
|
471
532
|
|
|
@@ -475,22 +536,22 @@ async function handleDedupeStats(res, adapter) {
|
|
|
475
536
|
const stats = await adapter.getStats();
|
|
476
537
|
sendJson(res, { stats, capabilities: adapter.capabilities });
|
|
477
538
|
} catch (err) {
|
|
478
|
-
sendError(res, err instanceof Error ? err.message :
|
|
539
|
+
sendError(res, err instanceof Error ? err.message : 'Unknown error');
|
|
479
540
|
}
|
|
480
541
|
}
|
|
481
542
|
async function handleDedupeJobs(_req, res, adapter, query) {
|
|
482
543
|
try {
|
|
483
|
-
const page = parseInt(query.get(
|
|
484
|
-
const limit = parseInt(query.get(
|
|
544
|
+
const page = parseInt(query.get('page') ?? '0', 10);
|
|
545
|
+
const limit = parseInt(query.get('limit') ?? '50', 10);
|
|
485
546
|
const result = await adapter.listJobs(page, limit);
|
|
486
547
|
sendJson(res, result);
|
|
487
548
|
} catch (err) {
|
|
488
|
-
sendError(res, err instanceof Error ? err.message :
|
|
549
|
+
sendError(res, err instanceof Error ? err.message : 'Unknown error');
|
|
489
550
|
}
|
|
490
551
|
}
|
|
491
552
|
async function handleDedupeJob(res, adapter, pathname) {
|
|
492
553
|
try {
|
|
493
|
-
const hash = extractParam(pathname,
|
|
554
|
+
const hash = extractParam(pathname, '/dedup/jobs/:hash');
|
|
494
555
|
if (!hash) {
|
|
495
556
|
sendNotFound(res);
|
|
496
557
|
return;
|
|
@@ -502,19 +563,25 @@ async function handleDedupeJob(res, adapter, pathname) {
|
|
|
502
563
|
}
|
|
503
564
|
sendJson(res, job);
|
|
504
565
|
} catch (err) {
|
|
505
|
-
sendError(res, err instanceof Error ? err.message :
|
|
566
|
+
sendError(res, err instanceof Error ? err.message : 'Unknown error');
|
|
506
567
|
}
|
|
507
568
|
}
|
|
508
569
|
|
|
509
570
|
// src/server/handlers/health.ts
|
|
510
571
|
function clientStoreInfo(client) {
|
|
511
572
|
return {
|
|
512
|
-
cache: client.cache
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
capabilities: client.
|
|
517
|
-
|
|
573
|
+
cache: client.cache
|
|
574
|
+
? { type: client.cache.type, capabilities: client.cache.capabilities }
|
|
575
|
+
: null,
|
|
576
|
+
dedup: client.dedup
|
|
577
|
+
? { type: client.dedup.type, capabilities: client.dedup.capabilities }
|
|
578
|
+
: null,
|
|
579
|
+
rateLimit: client.rateLimit
|
|
580
|
+
? {
|
|
581
|
+
type: client.rateLimit.type,
|
|
582
|
+
capabilities: client.rateLimit.capabilities,
|
|
583
|
+
}
|
|
584
|
+
: null,
|
|
518
585
|
};
|
|
519
586
|
}
|
|
520
587
|
function handleHealth(res, ctx) {
|
|
@@ -523,9 +590,9 @@ function handleHealth(res, ctx) {
|
|
|
523
590
|
clients[name] = clientStoreInfo(client);
|
|
524
591
|
}
|
|
525
592
|
sendJson(res, {
|
|
526
|
-
status:
|
|
593
|
+
status: 'ok',
|
|
527
594
|
clients,
|
|
528
|
-
pollIntervalMs: ctx.pollIntervalMs
|
|
595
|
+
pollIntervalMs: ctx.pollIntervalMs,
|
|
529
596
|
});
|
|
530
597
|
}
|
|
531
598
|
|
|
@@ -535,7 +602,7 @@ async function handleRateLimitStats(res, adapter) {
|
|
|
535
602
|
const stats = await adapter.getStats();
|
|
536
603
|
sendJson(res, { stats, capabilities: adapter.capabilities });
|
|
537
604
|
} catch (err) {
|
|
538
|
-
sendError(res, err instanceof Error ? err.message :
|
|
605
|
+
sendError(res, err instanceof Error ? err.message : 'Unknown error');
|
|
539
606
|
}
|
|
540
607
|
}
|
|
541
608
|
async function handleRateLimitResources(res, adapter) {
|
|
@@ -543,12 +610,12 @@ async function handleRateLimitResources(res, adapter) {
|
|
|
543
610
|
const resources = await adapter.listResources();
|
|
544
611
|
sendJson(res, { resources });
|
|
545
612
|
} catch (err) {
|
|
546
|
-
sendError(res, err instanceof Error ? err.message :
|
|
613
|
+
sendError(res, err instanceof Error ? err.message : 'Unknown error');
|
|
547
614
|
}
|
|
548
615
|
}
|
|
549
616
|
async function handleRateLimitResource(res, adapter, pathname) {
|
|
550
617
|
try {
|
|
551
|
-
const name = extractParam(pathname,
|
|
618
|
+
const name = extractParam(pathname, '/rate-limit/resources/:name');
|
|
552
619
|
if (!name) {
|
|
553
620
|
sendNotFound(res);
|
|
554
621
|
return;
|
|
@@ -556,12 +623,12 @@ async function handleRateLimitResource(res, adapter, pathname) {
|
|
|
556
623
|
const status = await adapter.getResourceStatus(name);
|
|
557
624
|
sendJson(res, { resource: name, ...status });
|
|
558
625
|
} catch (err) {
|
|
559
|
-
sendError(res, err instanceof Error ? err.message :
|
|
626
|
+
sendError(res, err instanceof Error ? err.message : 'Unknown error');
|
|
560
627
|
}
|
|
561
628
|
}
|
|
562
629
|
async function handleUpdateRateLimitConfig(req, res, adapter, pathname) {
|
|
563
630
|
try {
|
|
564
|
-
const name = extractParam(pathname,
|
|
631
|
+
const name = extractParam(pathname, '/rate-limit/resources/:name/config');
|
|
565
632
|
if (!name) {
|
|
566
633
|
sendNotFound(res);
|
|
567
634
|
return;
|
|
@@ -570,12 +637,12 @@ async function handleUpdateRateLimitConfig(req, res, adapter, pathname) {
|
|
|
570
637
|
await adapter.updateResourceConfig(name, body);
|
|
571
638
|
sendJson(res, { updated: true });
|
|
572
639
|
} catch (err) {
|
|
573
|
-
sendError(res, err instanceof Error ? err.message :
|
|
640
|
+
sendError(res, err instanceof Error ? err.message : 'Unknown error');
|
|
574
641
|
}
|
|
575
642
|
}
|
|
576
643
|
async function handleResetRateLimitResource(res, adapter, pathname) {
|
|
577
644
|
try {
|
|
578
|
-
const name = extractParam(pathname,
|
|
645
|
+
const name = extractParam(pathname, '/rate-limit/resources/:name/reset');
|
|
579
646
|
if (!name) {
|
|
580
647
|
sendNotFound(res);
|
|
581
648
|
return;
|
|
@@ -583,7 +650,7 @@ async function handleResetRateLimitResource(res, adapter, pathname) {
|
|
|
583
650
|
await adapter.resetResource(name);
|
|
584
651
|
sendJson(res, { reset: true });
|
|
585
652
|
} catch (err) {
|
|
586
|
-
sendError(res, err instanceof Error ? err.message :
|
|
653
|
+
sendError(res, err instanceof Error ? err.message : 'Unknown error');
|
|
587
654
|
}
|
|
588
655
|
}
|
|
589
656
|
|
|
@@ -594,13 +661,19 @@ function handleClients(res, ctx) {
|
|
|
594
661
|
clients.push({
|
|
595
662
|
name,
|
|
596
663
|
stores: {
|
|
597
|
-
cache: client.cache
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
capabilities: client.
|
|
602
|
-
|
|
603
|
-
|
|
664
|
+
cache: client.cache
|
|
665
|
+
? { type: client.cache.type, capabilities: client.cache.capabilities }
|
|
666
|
+
: null,
|
|
667
|
+
dedup: client.dedup
|
|
668
|
+
? { type: client.dedup.type, capabilities: client.dedup.capabilities }
|
|
669
|
+
: null,
|
|
670
|
+
rateLimit: client.rateLimit
|
|
671
|
+
? {
|
|
672
|
+
type: client.rateLimit.type,
|
|
673
|
+
capabilities: client.rateLimit.capabilities,
|
|
674
|
+
}
|
|
675
|
+
: null,
|
|
676
|
+
},
|
|
604
677
|
});
|
|
605
678
|
}
|
|
606
679
|
sendJson(res, { clients });
|
|
@@ -610,19 +683,19 @@ function handleClients(res, ctx) {
|
|
|
610
683
|
var CLIENT_ROUTE_REGEX = /^\/api\/clients\/([a-zA-Z0-9_-]+)(\/.*)?$/;
|
|
611
684
|
function createApiRouter(ctx) {
|
|
612
685
|
return async (req, res, pathname, query) => {
|
|
613
|
-
const method = req.method?.toUpperCase() ??
|
|
614
|
-
if (pathname ===
|
|
686
|
+
const method = req.method?.toUpperCase() ?? 'GET';
|
|
687
|
+
if (pathname === '/api/health' && method === 'GET') {
|
|
615
688
|
handleHealth(res, ctx);
|
|
616
689
|
return true;
|
|
617
690
|
}
|
|
618
|
-
if (pathname ===
|
|
691
|
+
if (pathname === '/api/clients' && method === 'GET') {
|
|
619
692
|
handleClients(res, ctx);
|
|
620
693
|
return true;
|
|
621
694
|
}
|
|
622
695
|
const clientMatch = pathname.match(CLIENT_ROUTE_REGEX);
|
|
623
696
|
if (clientMatch) {
|
|
624
697
|
const clientName = clientMatch[1];
|
|
625
|
-
const subPath = clientMatch[2] ??
|
|
698
|
+
const subPath = clientMatch[2] ?? '';
|
|
626
699
|
const client = ctx.clients.get(clientName);
|
|
627
700
|
if (!client) {
|
|
628
701
|
sendError(res, `Unknown client: ${clientName}`, 404);
|
|
@@ -630,7 +703,7 @@ function createApiRouter(ctx) {
|
|
|
630
703
|
}
|
|
631
704
|
return routeClientApi(req, res, client, subPath, method, query);
|
|
632
705
|
}
|
|
633
|
-
if (pathname.startsWith(
|
|
706
|
+
if (pathname.startsWith('/api/')) {
|
|
634
707
|
sendNotFound(res);
|
|
635
708
|
return true;
|
|
636
709
|
}
|
|
@@ -638,29 +711,30 @@ function createApiRouter(ctx) {
|
|
|
638
711
|
};
|
|
639
712
|
}
|
|
640
713
|
async function routeClientApi(req, res, client, subPath, method, query) {
|
|
641
|
-
if (subPath.startsWith(
|
|
714
|
+
if (subPath.startsWith('/cache')) {
|
|
642
715
|
if (!client.cache) {
|
|
643
|
-
sendError(res,
|
|
716
|
+
sendError(res, 'Cache store not configured', 404);
|
|
644
717
|
return true;
|
|
645
718
|
}
|
|
646
|
-
if (subPath ===
|
|
719
|
+
if (subPath === '/cache/stats' && method === 'GET') {
|
|
647
720
|
await handleCacheStats(res, client.cache);
|
|
648
721
|
return true;
|
|
649
722
|
}
|
|
650
|
-
if (subPath ===
|
|
723
|
+
if (subPath === '/cache/entries' && method === 'GET') {
|
|
651
724
|
await handleCacheEntries(req, res, client.cache, query);
|
|
652
725
|
return true;
|
|
653
726
|
}
|
|
654
|
-
if (subPath ===
|
|
727
|
+
if (subPath === '/cache/entries' && method === 'DELETE') {
|
|
655
728
|
await handleClearCache(res, client.cache);
|
|
656
729
|
return true;
|
|
657
730
|
}
|
|
658
|
-
const isSingleEntry =
|
|
659
|
-
|
|
731
|
+
const isSingleEntry =
|
|
732
|
+
subPath.startsWith('/cache/entries/') && subPath.split('/').length === 4;
|
|
733
|
+
if (isSingleEntry && method === 'GET') {
|
|
660
734
|
await handleCacheEntry(res, client.cache, subPath);
|
|
661
735
|
return true;
|
|
662
736
|
}
|
|
663
|
-
if (isSingleEntry && method ===
|
|
737
|
+
if (isSingleEntry && method === 'DELETE') {
|
|
664
738
|
await handleDeleteCacheEntry(res, client.cache, subPath);
|
|
665
739
|
return true;
|
|
666
740
|
}
|
|
@@ -669,21 +743,22 @@ async function routeClientApi(req, res, client, subPath, method, query) {
|
|
|
669
743
|
return true;
|
|
670
744
|
}
|
|
671
745
|
}
|
|
672
|
-
if (subPath.startsWith(
|
|
746
|
+
if (subPath.startsWith('/dedup')) {
|
|
673
747
|
if (!client.dedup) {
|
|
674
|
-
sendError(res,
|
|
748
|
+
sendError(res, 'Dedup store not configured', 404);
|
|
675
749
|
return true;
|
|
676
750
|
}
|
|
677
|
-
if (subPath ===
|
|
751
|
+
if (subPath === '/dedup/stats' && method === 'GET') {
|
|
678
752
|
await handleDedupeStats(res, client.dedup);
|
|
679
753
|
return true;
|
|
680
754
|
}
|
|
681
|
-
if (subPath ===
|
|
755
|
+
if (subPath === '/dedup/jobs' && method === 'GET') {
|
|
682
756
|
await handleDedupeJobs(req, res, client.dedup, query);
|
|
683
757
|
return true;
|
|
684
758
|
}
|
|
685
|
-
const isSingleJob =
|
|
686
|
-
|
|
759
|
+
const isSingleJob =
|
|
760
|
+
subPath.startsWith('/dedup/jobs/') && subPath.split('/').length === 4;
|
|
761
|
+
if (isSingleJob && method === 'GET') {
|
|
687
762
|
await handleDedupeJob(res, client.dedup, subPath);
|
|
688
763
|
return true;
|
|
689
764
|
}
|
|
@@ -692,29 +767,31 @@ async function routeClientApi(req, res, client, subPath, method, query) {
|
|
|
692
767
|
return true;
|
|
693
768
|
}
|
|
694
769
|
}
|
|
695
|
-
if (subPath.startsWith(
|
|
770
|
+
if (subPath.startsWith('/rate-limit')) {
|
|
696
771
|
if (!client.rateLimit) {
|
|
697
|
-
sendError(res,
|
|
772
|
+
sendError(res, 'Rate limit store not configured', 404);
|
|
698
773
|
return true;
|
|
699
774
|
}
|
|
700
|
-
if (subPath ===
|
|
775
|
+
if (subPath === '/rate-limit/stats' && method === 'GET') {
|
|
701
776
|
await handleRateLimitStats(res, client.rateLimit);
|
|
702
777
|
return true;
|
|
703
778
|
}
|
|
704
|
-
if (subPath ===
|
|
779
|
+
if (subPath === '/rate-limit/resources' && method === 'GET') {
|
|
705
780
|
await handleRateLimitResources(res, client.rateLimit);
|
|
706
781
|
return true;
|
|
707
782
|
}
|
|
708
|
-
if (subPath.endsWith(
|
|
783
|
+
if (subPath.endsWith('/config') && method === 'PUT') {
|
|
709
784
|
await handleUpdateRateLimitConfig(req, res, client.rateLimit, subPath);
|
|
710
785
|
return true;
|
|
711
786
|
}
|
|
712
|
-
if (subPath.endsWith(
|
|
787
|
+
if (subPath.endsWith('/reset') && method === 'POST') {
|
|
713
788
|
await handleResetRateLimitResource(res, client.rateLimit, subPath);
|
|
714
789
|
return true;
|
|
715
790
|
}
|
|
716
|
-
const isSingleResource =
|
|
717
|
-
|
|
791
|
+
const isSingleResource =
|
|
792
|
+
subPath.startsWith('/rate-limit/resources/') &&
|
|
793
|
+
subPath.split('/').length === 4;
|
|
794
|
+
if (isSingleResource && method === 'GET') {
|
|
718
795
|
await handleRateLimitResource(res, client.rateLimit, subPath);
|
|
719
796
|
return true;
|
|
720
797
|
}
|
|
@@ -727,36 +804,45 @@ async function routeClientApi(req, res, client, subPath, method, query) {
|
|
|
727
804
|
return true;
|
|
728
805
|
}
|
|
729
806
|
var MIME_TYPES = {
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
807
|
+
'.html': 'text/html',
|
|
808
|
+
'.js': 'application/javascript',
|
|
809
|
+
'.css': 'text/css',
|
|
810
|
+
'.json': 'application/json',
|
|
811
|
+
'.png': 'image/png',
|
|
812
|
+
'.svg': 'image/svg+xml',
|
|
813
|
+
'.ico': 'image/x-icon',
|
|
814
|
+
'.woff': 'font/woff',
|
|
815
|
+
'.woff2': 'font/woff2',
|
|
739
816
|
};
|
|
740
817
|
var cachedIndexHtml;
|
|
741
818
|
var clientDir;
|
|
742
819
|
function getCurrentDir() {
|
|
743
820
|
try {
|
|
744
|
-
return path.dirname(
|
|
821
|
+
return path.dirname(
|
|
822
|
+
url.fileURLToPath(
|
|
823
|
+
typeof document === 'undefined'
|
|
824
|
+
? require('u' + 'rl').pathToFileURL(__filename).href
|
|
825
|
+
: (_documentCurrentScript &&
|
|
826
|
+
_documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' &&
|
|
827
|
+
_documentCurrentScript.src) ||
|
|
828
|
+
new URL('index.cjs', document.baseURI).href,
|
|
829
|
+
),
|
|
830
|
+
);
|
|
745
831
|
} catch {
|
|
746
|
-
return typeof __dirname !==
|
|
832
|
+
return typeof __dirname !== 'undefined' ? __dirname : process.cwd();
|
|
747
833
|
}
|
|
748
834
|
}
|
|
749
835
|
function getClientDir() {
|
|
750
836
|
if (clientDir) return clientDir;
|
|
751
837
|
const currentDir = getCurrentDir();
|
|
752
|
-
clientDir = path.join(currentDir,
|
|
838
|
+
clientDir = path.join(currentDir, '..', 'dist', 'client');
|
|
753
839
|
return clientDir;
|
|
754
840
|
}
|
|
755
841
|
function getIndexHtml() {
|
|
756
842
|
if (cachedIndexHtml) return cachedIndexHtml;
|
|
757
|
-
const indexPath = path.join(getClientDir(),
|
|
843
|
+
const indexPath = path.join(getClientDir(), 'index.html');
|
|
758
844
|
if (fs.existsSync(indexPath)) {
|
|
759
|
-
cachedIndexHtml = fs.readFileSync(indexPath,
|
|
845
|
+
cachedIndexHtml = fs.readFileSync(indexPath, 'utf-8');
|
|
760
846
|
} else {
|
|
761
847
|
cachedIndexHtml = `<!DOCTYPE html>
|
|
762
848
|
<html lang="en">
|
|
@@ -774,29 +860,30 @@ function getIndexHtml() {
|
|
|
774
860
|
}
|
|
775
861
|
function serveStatic(res, pathname) {
|
|
776
862
|
const dir = getClientDir();
|
|
777
|
-
if (pathname !==
|
|
863
|
+
if (pathname !== '/' && pathname !== '/index.html') {
|
|
778
864
|
const filePath = path.join(dir, pathname);
|
|
779
865
|
if (fs.existsSync(filePath)) {
|
|
780
866
|
try {
|
|
781
867
|
const content = fs.readFileSync(filePath);
|
|
782
868
|
const ext = path.extname(pathname);
|
|
783
|
-
const mimeType = MIME_TYPES[ext] ??
|
|
869
|
+
const mimeType = MIME_TYPES[ext] ?? 'application/octet-stream';
|
|
784
870
|
res.writeHead(200, {
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
871
|
+
'Content-Type': mimeType,
|
|
872
|
+
'Content-Length': content.length,
|
|
873
|
+
'Cache-Control': pathname.includes('/assets/')
|
|
874
|
+
? 'public, max-age=31536000, immutable'
|
|
875
|
+
: 'no-cache',
|
|
788
876
|
});
|
|
789
877
|
res.end(content);
|
|
790
878
|
return true;
|
|
791
|
-
} catch {
|
|
792
|
-
}
|
|
879
|
+
} catch {}
|
|
793
880
|
}
|
|
794
881
|
}
|
|
795
882
|
const html = getIndexHtml();
|
|
796
883
|
res.writeHead(200, {
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
884
|
+
'Content-Type': 'text/html',
|
|
885
|
+
'Content-Length': Buffer.byteLength(html),
|
|
886
|
+
'Cache-Control': 'no-cache',
|
|
800
887
|
});
|
|
801
888
|
res.end(html);
|
|
802
889
|
return true;
|
|
@@ -807,33 +894,42 @@ function createDashboard(options) {
|
|
|
807
894
|
const opts = validateDashboardOptions(options);
|
|
808
895
|
const clients = /* @__PURE__ */ new Map();
|
|
809
896
|
for (const clientConfig of opts.clients) {
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
897
|
+
const normalized = normalizeClient(clientConfig);
|
|
898
|
+
clients.set(normalized.name, {
|
|
899
|
+
name: normalized.name,
|
|
900
|
+
cache: normalized.cacheStore
|
|
901
|
+
? detectCacheAdapter(normalized.cacheStore)
|
|
902
|
+
: void 0,
|
|
903
|
+
dedup: normalized.dedupeStore
|
|
904
|
+
? detectDedupeAdapter(normalized.dedupeStore)
|
|
905
|
+
: void 0,
|
|
906
|
+
rateLimit: normalized.rateLimitStore
|
|
907
|
+
? detectRateLimitAdapter(normalized.rateLimitStore)
|
|
908
|
+
: void 0,
|
|
815
909
|
});
|
|
816
910
|
}
|
|
817
911
|
const ctx = {
|
|
818
912
|
clients,
|
|
819
|
-
pollIntervalMs: opts.pollIntervalMs
|
|
913
|
+
pollIntervalMs: opts.pollIntervalMs,
|
|
820
914
|
};
|
|
821
915
|
const apiRouter = createApiRouter(ctx);
|
|
822
916
|
return (req, res, _next) => {
|
|
823
917
|
const { pathname, query } = parseUrl(req, opts.basePath);
|
|
824
|
-
apiRouter(req, res, pathname, query)
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
918
|
+
apiRouter(req, res, pathname, query)
|
|
919
|
+
.then((handled) => {
|
|
920
|
+
if (!handled) {
|
|
921
|
+
serveStatic(res, pathname);
|
|
922
|
+
}
|
|
923
|
+
})
|
|
924
|
+
.catch(() => {
|
|
925
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
926
|
+
res.end(JSON.stringify({ error: 'Internal server error' }));
|
|
927
|
+
});
|
|
832
928
|
};
|
|
833
929
|
}
|
|
834
930
|
var JSON_HEADERS = {
|
|
835
|
-
|
|
836
|
-
|
|
931
|
+
'Content-Type': 'application/json',
|
|
932
|
+
'Cache-Control': 'no-store',
|
|
837
933
|
};
|
|
838
934
|
function jsonResponse(data, status = 200) {
|
|
839
935
|
return new Response(JSON.stringify(data), { status, headers: JSON_HEADERS });
|
|
@@ -842,36 +938,45 @@ function errorResponse(message, status = 500) {
|
|
|
842
938
|
return jsonResponse({ error: message }, status);
|
|
843
939
|
}
|
|
844
940
|
var MIME_TYPES2 = {
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
941
|
+
'.html': 'text/html',
|
|
942
|
+
'.js': 'application/javascript',
|
|
943
|
+
'.css': 'text/css',
|
|
944
|
+
'.json': 'application/json',
|
|
945
|
+
'.png': 'image/png',
|
|
946
|
+
'.svg': 'image/svg+xml',
|
|
947
|
+
'.ico': 'image/x-icon',
|
|
948
|
+
'.woff': 'font/woff',
|
|
949
|
+
'.woff2': 'font/woff2',
|
|
854
950
|
};
|
|
855
951
|
var cachedIndexHtml2;
|
|
856
952
|
var clientDir2;
|
|
857
953
|
function getCurrentDir2() {
|
|
858
954
|
try {
|
|
859
|
-
return path.dirname(
|
|
955
|
+
return path.dirname(
|
|
956
|
+
url.fileURLToPath(
|
|
957
|
+
typeof document === 'undefined'
|
|
958
|
+
? require('u' + 'rl').pathToFileURL(__filename).href
|
|
959
|
+
: (_documentCurrentScript &&
|
|
960
|
+
_documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' &&
|
|
961
|
+
_documentCurrentScript.src) ||
|
|
962
|
+
new URL('index.cjs', document.baseURI).href,
|
|
963
|
+
),
|
|
964
|
+
);
|
|
860
965
|
} catch {
|
|
861
|
-
return typeof __dirname !==
|
|
966
|
+
return typeof __dirname !== 'undefined' ? __dirname : process.cwd();
|
|
862
967
|
}
|
|
863
968
|
}
|
|
864
969
|
function getClientDir2() {
|
|
865
970
|
if (clientDir2) return clientDir2;
|
|
866
971
|
const currentDir = getCurrentDir2();
|
|
867
|
-
clientDir2 = path.join(currentDir,
|
|
972
|
+
clientDir2 = path.join(currentDir, '..', 'dist', 'client');
|
|
868
973
|
return clientDir2;
|
|
869
974
|
}
|
|
870
975
|
function getIndexHtml2() {
|
|
871
976
|
if (cachedIndexHtml2) return cachedIndexHtml2;
|
|
872
|
-
const indexPath = path.join(getClientDir2(),
|
|
977
|
+
const indexPath = path.join(getClientDir2(), 'index.html');
|
|
873
978
|
if (fs.existsSync(indexPath)) {
|
|
874
|
-
cachedIndexHtml2 = fs.readFileSync(indexPath,
|
|
979
|
+
cachedIndexHtml2 = fs.readFileSync(indexPath, 'utf-8');
|
|
875
980
|
} else {
|
|
876
981
|
cachedIndexHtml2 = `<!DOCTYPE html>
|
|
877
982
|
<html lang="en">
|
|
@@ -889,59 +994,66 @@ function getIndexHtml2() {
|
|
|
889
994
|
}
|
|
890
995
|
function serveStaticWeb(pathname) {
|
|
891
996
|
const dir = getClientDir2();
|
|
892
|
-
if (pathname !==
|
|
997
|
+
if (pathname !== '/' && pathname !== '/index.html') {
|
|
893
998
|
const filePath = path.join(dir, pathname);
|
|
894
999
|
if (fs.existsSync(filePath)) {
|
|
895
1000
|
try {
|
|
896
1001
|
const content = fs.readFileSync(filePath);
|
|
897
1002
|
const ext = path.extname(pathname);
|
|
898
|
-
const mimeType = MIME_TYPES2[ext] ??
|
|
1003
|
+
const mimeType = MIME_TYPES2[ext] ?? 'application/octet-stream';
|
|
899
1004
|
return new Response(content, {
|
|
900
1005
|
status: 200,
|
|
901
1006
|
headers: {
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
1007
|
+
'Content-Type': mimeType,
|
|
1008
|
+
'Content-Length': String(content.length),
|
|
1009
|
+
'Cache-Control': pathname.includes('/assets/')
|
|
1010
|
+
? 'public, max-age=31536000, immutable'
|
|
1011
|
+
: 'no-cache',
|
|
1012
|
+
},
|
|
906
1013
|
});
|
|
907
|
-
} catch {
|
|
908
|
-
}
|
|
1014
|
+
} catch {}
|
|
909
1015
|
}
|
|
910
1016
|
}
|
|
911
1017
|
const html = getIndexHtml2();
|
|
912
1018
|
return new Response(html, {
|
|
913
1019
|
status: 200,
|
|
914
1020
|
headers: {
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
}
|
|
1021
|
+
'Content-Type': 'text/html',
|
|
1022
|
+
'Cache-Control': 'no-cache',
|
|
1023
|
+
},
|
|
918
1024
|
});
|
|
919
1025
|
}
|
|
920
1026
|
var CLIENT_ROUTE_REGEX2 = /^\/api\/clients\/([a-zA-Z0-9_-]+)(\/.*)?$/;
|
|
921
1027
|
function clientStoreInfo2(client) {
|
|
922
1028
|
return {
|
|
923
|
-
cache: client.cache
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
capabilities: client.
|
|
928
|
-
|
|
1029
|
+
cache: client.cache
|
|
1030
|
+
? { type: client.cache.type, capabilities: client.cache.capabilities }
|
|
1031
|
+
: null,
|
|
1032
|
+
dedup: client.dedup
|
|
1033
|
+
? { type: client.dedup.type, capabilities: client.dedup.capabilities }
|
|
1034
|
+
: null,
|
|
1035
|
+
rateLimit: client.rateLimit
|
|
1036
|
+
? {
|
|
1037
|
+
type: client.rateLimit.type,
|
|
1038
|
+
capabilities: client.rateLimit.capabilities,
|
|
1039
|
+
}
|
|
1040
|
+
: null,
|
|
929
1041
|
};
|
|
930
1042
|
}
|
|
931
1043
|
async function routeApi(request, pathname, query, ctx) {
|
|
932
1044
|
const method = request.method.toUpperCase();
|
|
933
|
-
if (pathname ===
|
|
1045
|
+
if (pathname === '/api/health' && method === 'GET') {
|
|
934
1046
|
const clients = {};
|
|
935
1047
|
for (const [name, client] of ctx.clients) {
|
|
936
1048
|
clients[name] = clientStoreInfo2(client);
|
|
937
1049
|
}
|
|
938
1050
|
return jsonResponse({
|
|
939
|
-
status:
|
|
1051
|
+
status: 'ok',
|
|
940
1052
|
clients,
|
|
941
|
-
pollIntervalMs: ctx.pollIntervalMs
|
|
1053
|
+
pollIntervalMs: ctx.pollIntervalMs,
|
|
942
1054
|
});
|
|
943
1055
|
}
|
|
944
|
-
if (pathname ===
|
|
1056
|
+
if (pathname === '/api/clients' && method === 'GET') {
|
|
945
1057
|
const clientList = [];
|
|
946
1058
|
for (const [name, client] of ctx.clients) {
|
|
947
1059
|
clientList.push({ name, stores: clientStoreInfo2(client) });
|
|
@@ -951,168 +1063,180 @@ async function routeApi(request, pathname, query, ctx) {
|
|
|
951
1063
|
const clientMatch = pathname.match(CLIENT_ROUTE_REGEX2);
|
|
952
1064
|
if (clientMatch) {
|
|
953
1065
|
const clientName = clientMatch[1];
|
|
954
|
-
const subPath = clientMatch[2] ??
|
|
1066
|
+
const subPath = clientMatch[2] ?? '';
|
|
955
1067
|
const client = ctx.clients.get(clientName);
|
|
956
1068
|
if (!client) {
|
|
957
1069
|
return errorResponse(`Unknown client: ${clientName}`, 404);
|
|
958
1070
|
}
|
|
959
1071
|
return routeClientApi2(request, subPath, method, client, query);
|
|
960
1072
|
}
|
|
961
|
-
if (pathname.startsWith(
|
|
962
|
-
return errorResponse(
|
|
1073
|
+
if (pathname.startsWith('/api/')) {
|
|
1074
|
+
return errorResponse('Not found', 404);
|
|
963
1075
|
}
|
|
964
1076
|
return null;
|
|
965
1077
|
}
|
|
966
1078
|
async function routeClientApi2(request, subPath, method, client, query) {
|
|
967
|
-
if (subPath.startsWith(
|
|
968
|
-
if (!client.cache) return errorResponse(
|
|
1079
|
+
if (subPath.startsWith('/cache')) {
|
|
1080
|
+
if (!client.cache) return errorResponse('Cache store not configured', 404);
|
|
969
1081
|
return routeCache(subPath, method, client.cache, query);
|
|
970
1082
|
}
|
|
971
|
-
if (subPath.startsWith(
|
|
972
|
-
if (!client.dedup) return errorResponse(
|
|
1083
|
+
if (subPath.startsWith('/dedup')) {
|
|
1084
|
+
if (!client.dedup) return errorResponse('Dedup store not configured', 404);
|
|
973
1085
|
return routeDedup(subPath, method, client.dedup, query);
|
|
974
1086
|
}
|
|
975
|
-
if (subPath.startsWith(
|
|
1087
|
+
if (subPath.startsWith('/rate-limit')) {
|
|
976
1088
|
if (!client.rateLimit)
|
|
977
|
-
return errorResponse(
|
|
1089
|
+
return errorResponse('Rate limit store not configured', 404);
|
|
978
1090
|
return routeRateLimit(request, subPath, method, client.rateLimit);
|
|
979
1091
|
}
|
|
980
|
-
return errorResponse(
|
|
1092
|
+
return errorResponse('Not found', 404);
|
|
981
1093
|
}
|
|
982
1094
|
async function routeCache(pathname, method, adapter, query) {
|
|
983
1095
|
try {
|
|
984
|
-
if (pathname ===
|
|
1096
|
+
if (pathname === '/cache/stats' && method === 'GET') {
|
|
985
1097
|
const stats = await adapter.getStats();
|
|
986
1098
|
return jsonResponse({ stats, capabilities: adapter.capabilities });
|
|
987
1099
|
}
|
|
988
|
-
if (pathname ===
|
|
989
|
-
const page = parseInt(query.get(
|
|
990
|
-
const limit = parseInt(query.get(
|
|
1100
|
+
if (pathname === '/cache/entries' && method === 'GET') {
|
|
1101
|
+
const page = parseInt(query.get('page') ?? '0', 10);
|
|
1102
|
+
const limit = parseInt(query.get('limit') ?? '50', 10);
|
|
991
1103
|
return jsonResponse(await adapter.listEntries(page, limit));
|
|
992
1104
|
}
|
|
993
|
-
if (pathname ===
|
|
1105
|
+
if (pathname === '/cache/entries' && method === 'DELETE') {
|
|
994
1106
|
await adapter.clearAll();
|
|
995
1107
|
return jsonResponse({ cleared: true });
|
|
996
1108
|
}
|
|
997
|
-
const isSingleEntry =
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1109
|
+
const isSingleEntry =
|
|
1110
|
+
pathname.startsWith('/cache/entries/') &&
|
|
1111
|
+
pathname.split('/').length === 4;
|
|
1112
|
+
if (isSingleEntry && method === 'GET') {
|
|
1113
|
+
const hash = extractParam(pathname, '/cache/entries/:hash');
|
|
1114
|
+
if (!hash) return errorResponse('Not found', 404);
|
|
1001
1115
|
const entry = await adapter.getEntry(hash);
|
|
1002
|
-
if (entry === void 0) return errorResponse(
|
|
1116
|
+
if (entry === void 0) return errorResponse('Not found', 404);
|
|
1003
1117
|
return jsonResponse({ hash, value: entry });
|
|
1004
1118
|
}
|
|
1005
|
-
if (isSingleEntry && method ===
|
|
1006
|
-
const hash = extractParam(pathname,
|
|
1007
|
-
if (!hash) return errorResponse(
|
|
1119
|
+
if (isSingleEntry && method === 'DELETE') {
|
|
1120
|
+
const hash = extractParam(pathname, '/cache/entries/:hash');
|
|
1121
|
+
if (!hash) return errorResponse('Not found', 404);
|
|
1008
1122
|
await adapter.deleteEntry(hash);
|
|
1009
1123
|
return jsonResponse({ deleted: true });
|
|
1010
1124
|
}
|
|
1011
1125
|
if (isSingleEntry) {
|
|
1012
|
-
return errorResponse(
|
|
1126
|
+
return errorResponse('Method not allowed', 405);
|
|
1013
1127
|
}
|
|
1014
1128
|
} catch (err) {
|
|
1015
|
-
return errorResponse(err instanceof Error ? err.message :
|
|
1129
|
+
return errorResponse(err instanceof Error ? err.message : 'Unknown error');
|
|
1016
1130
|
}
|
|
1017
|
-
return errorResponse(
|
|
1131
|
+
return errorResponse('Not found', 404);
|
|
1018
1132
|
}
|
|
1019
1133
|
async function routeDedup(pathname, method, adapter, query) {
|
|
1020
1134
|
try {
|
|
1021
|
-
if (pathname ===
|
|
1135
|
+
if (pathname === '/dedup/stats' && method === 'GET') {
|
|
1022
1136
|
const stats = await adapter.getStats();
|
|
1023
1137
|
return jsonResponse({ stats, capabilities: adapter.capabilities });
|
|
1024
1138
|
}
|
|
1025
|
-
if (pathname ===
|
|
1026
|
-
const page = parseInt(query.get(
|
|
1027
|
-
const limit = parseInt(query.get(
|
|
1139
|
+
if (pathname === '/dedup/jobs' && method === 'GET') {
|
|
1140
|
+
const page = parseInt(query.get('page') ?? '0', 10);
|
|
1141
|
+
const limit = parseInt(query.get('limit') ?? '50', 10);
|
|
1028
1142
|
return jsonResponse(await adapter.listJobs(page, limit));
|
|
1029
1143
|
}
|
|
1030
|
-
const isSingleJob =
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1144
|
+
const isSingleJob =
|
|
1145
|
+
pathname.startsWith('/dedup/jobs/') && pathname.split('/').length === 4;
|
|
1146
|
+
if (isSingleJob && method === 'GET') {
|
|
1147
|
+
const hash = extractParam(pathname, '/dedup/jobs/:hash');
|
|
1148
|
+
if (!hash) return errorResponse('Not found', 404);
|
|
1034
1149
|
const job = await adapter.getJob(hash);
|
|
1035
|
-
if (!job) return errorResponse(
|
|
1150
|
+
if (!job) return errorResponse('Not found', 404);
|
|
1036
1151
|
return jsonResponse(job);
|
|
1037
1152
|
}
|
|
1038
1153
|
if (isSingleJob) {
|
|
1039
|
-
return errorResponse(
|
|
1154
|
+
return errorResponse('Method not allowed', 405);
|
|
1040
1155
|
}
|
|
1041
1156
|
} catch (err) {
|
|
1042
|
-
return errorResponse(err instanceof Error ? err.message :
|
|
1157
|
+
return errorResponse(err instanceof Error ? err.message : 'Unknown error');
|
|
1043
1158
|
}
|
|
1044
|
-
return errorResponse(
|
|
1159
|
+
return errorResponse('Not found', 404);
|
|
1045
1160
|
}
|
|
1046
1161
|
async function routeRateLimit(request, pathname, method, adapter) {
|
|
1047
1162
|
try {
|
|
1048
|
-
if (pathname ===
|
|
1163
|
+
if (pathname === '/rate-limit/stats' && method === 'GET') {
|
|
1049
1164
|
const stats = await adapter.getStats();
|
|
1050
1165
|
return jsonResponse({ stats, capabilities: adapter.capabilities });
|
|
1051
1166
|
}
|
|
1052
|
-
if (pathname ===
|
|
1167
|
+
if (pathname === '/rate-limit/resources' && method === 'GET') {
|
|
1053
1168
|
const resources = await adapter.listResources();
|
|
1054
1169
|
return jsonResponse({ resources });
|
|
1055
1170
|
}
|
|
1056
|
-
if (pathname.endsWith(
|
|
1057
|
-
const name = extractParam(pathname,
|
|
1058
|
-
if (!name) return errorResponse(
|
|
1171
|
+
if (pathname.endsWith('/config') && method === 'PUT') {
|
|
1172
|
+
const name = extractParam(pathname, '/rate-limit/resources/:name/config');
|
|
1173
|
+
if (!name) return errorResponse('Not found', 404);
|
|
1059
1174
|
const body = await request.json();
|
|
1060
1175
|
await adapter.updateResourceConfig(name, body);
|
|
1061
1176
|
return jsonResponse({ updated: true });
|
|
1062
1177
|
}
|
|
1063
|
-
if (pathname.endsWith(
|
|
1064
|
-
const name = extractParam(pathname,
|
|
1065
|
-
if (!name) return errorResponse(
|
|
1178
|
+
if (pathname.endsWith('/reset') && method === 'POST') {
|
|
1179
|
+
const name = extractParam(pathname, '/rate-limit/resources/:name/reset');
|
|
1180
|
+
if (!name) return errorResponse('Not found', 404);
|
|
1066
1181
|
await adapter.resetResource(name);
|
|
1067
1182
|
return jsonResponse({ reset: true });
|
|
1068
1183
|
}
|
|
1069
|
-
const isSingleResource =
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1184
|
+
const isSingleResource =
|
|
1185
|
+
pathname.startsWith('/rate-limit/resources/') &&
|
|
1186
|
+
pathname.split('/').length === 4;
|
|
1187
|
+
if (isSingleResource && method === 'GET') {
|
|
1188
|
+
const name = extractParam(pathname, '/rate-limit/resources/:name');
|
|
1189
|
+
if (!name) return errorResponse('Not found', 404);
|
|
1073
1190
|
const status = await adapter.getResourceStatus(name);
|
|
1074
1191
|
return jsonResponse({ resource: name, ...status });
|
|
1075
1192
|
}
|
|
1076
1193
|
if (isSingleResource) {
|
|
1077
|
-
return errorResponse(
|
|
1194
|
+
return errorResponse('Method not allowed', 405);
|
|
1078
1195
|
}
|
|
1079
1196
|
} catch (err) {
|
|
1080
|
-
return errorResponse(err instanceof Error ? err.message :
|
|
1197
|
+
return errorResponse(err instanceof Error ? err.message : 'Unknown error');
|
|
1081
1198
|
}
|
|
1082
|
-
return errorResponse(
|
|
1199
|
+
return errorResponse('Not found', 404);
|
|
1083
1200
|
}
|
|
1084
1201
|
function createDashboardHandler(options) {
|
|
1085
1202
|
const opts = validateDashboardOptions(options);
|
|
1086
1203
|
const clients = /* @__PURE__ */ new Map();
|
|
1087
1204
|
for (const clientConfig of opts.clients) {
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1205
|
+
const normalized = normalizeClient(clientConfig);
|
|
1206
|
+
clients.set(normalized.name, {
|
|
1207
|
+
name: normalized.name,
|
|
1208
|
+
cache: normalized.cacheStore
|
|
1209
|
+
? detectCacheAdapter(normalized.cacheStore)
|
|
1210
|
+
: void 0,
|
|
1211
|
+
dedup: normalized.dedupeStore
|
|
1212
|
+
? detectDedupeAdapter(normalized.dedupeStore)
|
|
1213
|
+
: void 0,
|
|
1214
|
+
rateLimit: normalized.rateLimitStore
|
|
1215
|
+
? detectRateLimitAdapter(normalized.rateLimitStore)
|
|
1216
|
+
: void 0,
|
|
1093
1217
|
});
|
|
1094
1218
|
}
|
|
1095
1219
|
const ctx = {
|
|
1096
1220
|
clients,
|
|
1097
|
-
pollIntervalMs: opts.pollIntervalMs
|
|
1221
|
+
pollIntervalMs: opts.pollIntervalMs,
|
|
1098
1222
|
};
|
|
1099
1223
|
return async (request) => {
|
|
1100
1224
|
try {
|
|
1101
1225
|
const url = new URL(request.url);
|
|
1102
1226
|
let pathname = url.pathname;
|
|
1103
|
-
if (opts.basePath !==
|
|
1104
|
-
pathname = pathname.slice(opts.basePath.length) ||
|
|
1227
|
+
if (opts.basePath !== '/' && pathname.startsWith(opts.basePath)) {
|
|
1228
|
+
pathname = pathname.slice(opts.basePath.length) || '/';
|
|
1105
1229
|
}
|
|
1106
1230
|
const apiResponse = await routeApi(
|
|
1107
1231
|
request,
|
|
1108
1232
|
pathname,
|
|
1109
1233
|
url.searchParams,
|
|
1110
|
-
ctx
|
|
1234
|
+
ctx,
|
|
1111
1235
|
);
|
|
1112
1236
|
if (apiResponse) return apiResponse;
|
|
1113
1237
|
return serveStaticWeb(pathname);
|
|
1114
1238
|
} catch {
|
|
1115
|
-
return errorResponse(
|
|
1239
|
+
return errorResponse('Internal server error', 500);
|
|
1116
1240
|
}
|
|
1117
1241
|
};
|
|
1118
1242
|
}
|
|
@@ -1123,10 +1247,11 @@ async function startDashboard(options) {
|
|
|
1123
1247
|
middleware(req, res);
|
|
1124
1248
|
});
|
|
1125
1249
|
return new Promise((resolve, reject) => {
|
|
1126
|
-
server.on(
|
|
1250
|
+
server.on('error', reject);
|
|
1127
1251
|
server.listen(opts.port, opts.host, () => {
|
|
1128
1252
|
const addr = server.address();
|
|
1129
|
-
const url =
|
|
1253
|
+
const url =
|
|
1254
|
+
typeof addr === 'string' ? addr : `http://${opts.host}:${opts.port}`;
|
|
1130
1255
|
console.log(`Dashboard running at ${url}`);
|
|
1131
1256
|
resolve({
|
|
1132
1257
|
server,
|
|
@@ -1137,7 +1262,7 @@ async function startDashboard(options) {
|
|
|
1137
1262
|
else res();
|
|
1138
1263
|
});
|
|
1139
1264
|
});
|
|
1140
|
-
}
|
|
1265
|
+
},
|
|
1141
1266
|
});
|
|
1142
1267
|
});
|
|
1143
1268
|
});
|
|
@@ -1147,4 +1272,4 @@ exports.createDashboard = createDashboard;
|
|
|
1147
1272
|
exports.createDashboardHandler = createDashboardHandler;
|
|
1148
1273
|
exports.startDashboard = startDashboard;
|
|
1149
1274
|
//# sourceMappingURL=index.cjs.map
|
|
1150
|
-
//# sourceMappingURL=index.cjs.map
|
|
1275
|
+
//# sourceMappingURL=index.cjs.map
|