claw-dashboard 2.1.0 → 2.2.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 (90) hide show
  1. package/CHANGELOG.md +29 -1
  2. package/docs/API.md +1 -2
  3. package/index.js +32 -42
  4. package/package.json +23 -12
  5. package/src/auto-save.js +11 -5
  6. package/src/cli/export-snapshot.js +3 -1
  7. package/src/cli/import-snapshot.js +3 -1
  8. package/src/config.js +9 -15
  9. package/src/errors.js +0 -9
  10. package/src/security.js +6 -2
  11. package/src/snapshot.js +73 -26
  12. package/src/web-server.js +7 -10
  13. package/src/widgets/builtin-widgets.js +0 -94
  14. package/src/widgets/index.js +0 -1
  15. package/.c8rc.json +0 -38
  16. package/.dockerignore +0 -68
  17. package/.github/dependabot.yml +0 -45
  18. package/.github/pull_request_template.md +0 -39
  19. package/.github/workflows/ci.yml +0 -147
  20. package/.github/workflows/release.yml +0 -40
  21. package/.github/workflows/security.yml +0 -84
  22. package/.husky/pre-commit +0 -1
  23. package/.planning/codebase/ARCHITECTURE.md +0 -206
  24. package/.planning/codebase/INTEGRATIONS.md +0 -150
  25. package/.planning/codebase/STACK.md +0 -122
  26. package/.planning/codebase/STRUCTURE.md +0 -201
  27. package/CONTRIBUTING.md +0 -378
  28. package/Dockerfile +0 -54
  29. package/FEATURES.md +0 -307
  30. package/TODO.md +0 -28
  31. package/ai.openclaw.dashboard.plist +0 -35
  32. package/build-cjs.js +0 -127
  33. package/cjs-shim.js +0 -13
  34. package/dist/clawdash +0 -1729
  35. package/dist/clawdash.meta.json +0 -2236
  36. package/dist/widgets.cjs +0 -6511
  37. package/docker-compose.yml +0 -67
  38. package/esbuild.config.js +0 -158
  39. package/eslint.config.js +0 -56
  40. package/examples/plugins/README.md +0 -122
  41. package/examples/plugins/api-status/index.js +0 -294
  42. package/examples/plugins/api-status/plugin.json +0 -19
  43. package/examples/plugins/hello-world/index.js +0 -104
  44. package/examples/plugins/hello-world/plugin.json +0 -15
  45. package/examples/plugins/system-metrics-chart/index.js +0 -339
  46. package/examples/plugins/system-metrics-chart/plugin.json +0 -18
  47. package/examples/plugins/weather-widget/index.js +0 -136
  48. package/examples/plugins/weather-widget/plugin.json +0 -19
  49. package/index.cjs +0 -23005
  50. package/jest.config.js +0 -11
  51. package/scripts/release.js +0 -595
  52. package/src/database.js +0 -734
  53. package/tests/alerts.test.js +0 -437
  54. package/tests/auto-save.test.js +0 -529
  55. package/tests/cache.test.js +0 -317
  56. package/tests/cli.test.js +0 -351
  57. package/tests/command-palette.test.js +0 -320
  58. package/tests/config-processor.test.js +0 -452
  59. package/tests/config-validator.test.js +0 -710
  60. package/tests/config-watcher.test.js +0 -594
  61. package/tests/config.test.js +0 -755
  62. package/tests/database.test.js +0 -438
  63. package/tests/errors.test.js +0 -189
  64. package/tests/example-plugins.test.js +0 -624
  65. package/tests/integration.test.js +0 -1321
  66. package/tests/loading-states.test.js +0 -300
  67. package/tests/manifest-validation-on-load.test.js +0 -671
  68. package/tests/performance-monitor.test.js +0 -190
  69. package/tests/plugin-api-rate-limit.test.js +0 -302
  70. package/tests/plugin-errors.test.js +0 -311
  71. package/tests/plugin-lifecycle-e2e.test.js +0 -1036
  72. package/tests/plugin-reload.test.js +0 -764
  73. package/tests/rate-limiter.test.js +0 -539
  74. package/tests/retry.test.js +0 -308
  75. package/tests/security.test.js +0 -411
  76. package/tests/settings-modal.test.js +0 -226
  77. package/tests/theme-selector.test.js +0 -57
  78. package/tests/utils.js +0 -242
  79. package/tests/utils.test.js +0 -317
  80. package/tests/validate-plugin-cli.test.js +0 -359
  81. package/tests/validation.test.js +0 -837
  82. package/tests/web-server.test.js +0 -646
  83. package/tests/widget-arrange-mode.test.js +0 -183
  84. package/tests/widget-config-hot-reload.test.js +0 -465
  85. package/tests/widget-dependency.test.js +0 -591
  86. package/tests/widget-error-boundary.test.js +0 -749
  87. package/tests/widget-error-isolation.test.js +0 -469
  88. package/tests/widget-integration.test.js +0 -1147
  89. package/tests/widget-refresh-intervals.test.js +0 -284
  90. package/tests/worker-pool.test.js +0 -522
@@ -1,522 +0,0 @@
1
- /**
2
- * Tests for WorkerPool - task execution, timeout handling, worker recovery, error propagation
3
- */
4
-
5
- import { jest } from '@jest/globals';
6
- import { WorkerPool } from '../src/workers/worker-pool.js';
7
-
8
- // Mock systeminformation for fallback tests
9
- const mockSiData = {
10
- currentLoad: { avgLoad: 1.5, currentLoad: 25 },
11
- mem: { total: 16000000000, used: 8000000000 },
12
- graphics: { controllers: [] },
13
- networkStats: [{ iface: 'eth0', rx_bytes: 1000 }],
14
- fsSize: [{ fs: '/', size: 1000000000 }],
15
- osInfo: { platform: 'darwin', hostname: 'test-host' },
16
- versions: { node: '18.0.0' },
17
- time: { uptime: 3600 },
18
- };
19
-
20
- jest.unstable_mockModule('systeminformation', () => ({
21
- default: {
22
- currentLoad: jest.fn().mockResolvedValue(mockSiData.currentLoad),
23
- mem: jest.fn().mockResolvedValue(mockSiData.mem),
24
- graphics: jest.fn().mockResolvedValue(mockSiData.graphics),
25
- networkStats: jest.fn().mockResolvedValue(mockSiData.networkStats),
26
- fsSize: jest.fn().mockResolvedValue(mockSiData.fsSize),
27
- osInfo: jest.fn().mockResolvedValue(mockSiData.osInfo),
28
- versions: jest.fn().mockResolvedValue(mockSiData.versions),
29
- time: jest.fn().mockResolvedValue(mockSiData.time),
30
- },
31
- }));
32
-
33
- // Mock logger
34
- jest.unstable_mockModule('../src/logger.js', () => ({
35
- default: {
36
- info: jest.fn(),
37
- warn: jest.fn(),
38
- error: jest.fn(),
39
- debug: jest.fn(),
40
- },
41
- }));
42
-
43
- describe('WorkerPool', () => {
44
- let pool;
45
-
46
- afterEach(async () => {
47
- if (pool) {
48
- await pool.shutdown();
49
- pool = null;
50
- }
51
- });
52
-
53
- describe('constructor', () => {
54
- test('should create WorkerPool with default options from config', async () => {
55
- pool = new WorkerPool();
56
-
57
- // Uses config.js defaults since we can't mock it effectively in ES modules
58
- expect(pool.taskTimeout).toBe(10000); // Default from worker-pool.js
59
- expect(pool.enableWorkers).toBe(true);
60
- expect(pool.workersSupported).toBe(true);
61
- });
62
-
63
- test('should create WorkerPool with custom options', async () => {
64
- pool = new WorkerPool({
65
- maxWorkers: 4,
66
- taskTimeout: 5000,
67
- enableWorkers: false,
68
- });
69
-
70
- expect(pool.maxWorkers).toBe(4);
71
- expect(pool.taskTimeout).toBe(5000);
72
- expect(pool.enableWorkers).toBe(false);
73
- expect(pool.workers.length).toBe(0);
74
- });
75
-
76
- test('should respect workersSupported flag when disabled', async () => {
77
- pool = new WorkerPool({ enableWorkers: false });
78
- expect(pool.workersSupported).toBe(true);
79
- expect(pool.workers.length).toBe(0);
80
- });
81
- });
82
-
83
- describe('task execution', () => {
84
- test('should execute task and return result via fallback', async () => {
85
- pool = new WorkerPool({ enableWorkers: false });
86
-
87
- const result = await pool.execute('currentLoad');
88
-
89
- expect(result).toEqual(mockSiData.currentLoad);
90
- });
91
-
92
- test('should execute multiple commands', async () => {
93
- pool = new WorkerPool({ enableWorkers: false });
94
-
95
- const result1 = await pool.execute('mem');
96
- const result2 = await pool.execute('fsSize');
97
-
98
- expect(result1).toEqual(mockSiData.mem);
99
- expect(result2).toEqual(mockSiData.fsSize);
100
- });
101
- });
102
-
103
- describe('fallback execution', () => {
104
- test('should use fallback when workers are disabled', async () => {
105
- pool = new WorkerPool({ enableWorkers: false });
106
-
107
- const result = await pool.execute('currentLoad');
108
-
109
- expect(result).toEqual(mockSiData.currentLoad);
110
- });
111
-
112
- test('should use fallback when workers are not supported', async () => {
113
- pool = new WorkerPool({ enableWorkers: false });
114
- pool.workersSupported = false;
115
-
116
- const result = await pool.execute('mem');
117
-
118
- expect(result).toEqual(mockSiData.mem);
119
- });
120
-
121
- test('should fallback execute currentLoad command', async () => {
122
- pool = new WorkerPool({ enableWorkers: false });
123
-
124
- const result = await pool.execute('currentLoad');
125
- expect(result).toEqual(mockSiData.currentLoad);
126
- });
127
-
128
- test('should fallback execute mem command', async () => {
129
- pool = new WorkerPool({ enableWorkers: false });
130
-
131
- const result = await pool.execute('mem');
132
- expect(result).toEqual(mockSiData.mem);
133
- });
134
-
135
- test('should fallback execute graphics command', async () => {
136
- pool = new WorkerPool({ enableWorkers: false });
137
-
138
- const result = await pool.execute('graphics');
139
- expect(result).toEqual(mockSiData.graphics);
140
- });
141
-
142
- test('should fallback execute networkStats command', async () => {
143
- pool = new WorkerPool({ enableWorkers: false });
144
-
145
- const result = await pool.execute('networkStats');
146
- expect(result).toEqual(mockSiData.networkStats);
147
- });
148
-
149
- test('should fallback execute fsSize command', async () => {
150
- pool = new WorkerPool({ enableWorkers: false });
151
-
152
- const result = await pool.execute('fsSize');
153
- expect(result).toEqual(mockSiData.fsSize);
154
- });
155
-
156
- test('should fallback execute systemData command', async () => {
157
- pool = new WorkerPool({ enableWorkers: false });
158
-
159
- const result = await pool.execute('systemData');
160
-
161
- expect(result).toEqual({
162
- os: mockSiData.osInfo,
163
- ver: mockSiData.versions,
164
- time: mockSiData.time,
165
- });
166
- });
167
-
168
- test('should throw for unknown command in fallback', async () => {
169
- pool = new WorkerPool({ enableWorkers: false });
170
-
171
- await expect(pool.execute('unknownCommand')).rejects.toThrow('Unknown command: unknownCommand');
172
- });
173
-
174
- test('should propagate fallback execution errors', async () => {
175
- const { default: systemInfo } = await import('systeminformation');
176
- pool = new WorkerPool({ enableWorkers: false });
177
-
178
- systemInfo.currentLoad.mockRejectedValueOnce(new Error('SI Error'));
179
-
180
- await expect(pool.execute('currentLoad')).rejects.toThrow('SI Error');
181
- });
182
- });
183
-
184
- describe('getStatus', () => {
185
- test('should return pool status with workers disabled', async () => {
186
- pool = new WorkerPool({ enableWorkers: false });
187
-
188
- const status = pool.getStatus();
189
-
190
- expect(status.enabled).toBe(false);
191
- expect(status.supported).toBe(true);
192
- expect(status.totalWorkers).toBe(0);
193
- expect(status.busyWorkers).toBe(0);
194
- expect(status.readyWorkers).toBe(0);
195
- expect(status.pendingTasks).toBe(0);
196
- expect(status.queuedTasks).toBe(0);
197
- });
198
-
199
- test('should count pending and queued tasks correctly', async () => {
200
- pool = new WorkerPool({ enableWorkers: false });
201
-
202
- // Manually add some pending and queued tasks
203
- pool.pendingTasks.set(1, {
204
- id: 1,
205
- command: 'test',
206
- resolve: () => {},
207
- reject: () => {},
208
- timeout: null,
209
- });
210
- pool.taskQueue.push({ id: 2, command: 'test2' });
211
-
212
- const status = pool.getStatus();
213
- expect(status.pendingTasks).toBe(1);
214
- expect(status.queuedTasks).toBe(1);
215
- });
216
- });
217
-
218
- describe('shutdown', () => {
219
- test('should set shutdown flag', async () => {
220
- pool = new WorkerPool({ enableWorkers: false });
221
-
222
- expect(pool.isShutdown).toBe(false);
223
- await pool.shutdown();
224
-
225
- expect(pool.isShutdown).toBe(true);
226
- });
227
-
228
- test('should reject pending tasks on shutdown', async () => {
229
- pool = new WorkerPool({ enableWorkers: false, taskTimeout: 10000 });
230
-
231
- // Add a pending task with proper reject function
232
- const rejectFn = jest.fn();
233
- pool.pendingTasks.set(1, {
234
- id: 1,
235
- command: 'test',
236
- resolve: () => {},
237
- reject: rejectFn,
238
- timeout: null,
239
- });
240
-
241
- await pool.shutdown();
242
-
243
- expect(rejectFn).toHaveBeenCalledWith(new Error('Worker pool shutting down'));
244
- expect(pool.pendingTasks.size).toBe(0);
245
- });
246
-
247
- test('should clear task queue on shutdown', async () => {
248
- pool = new WorkerPool({ enableWorkers: false });
249
-
250
- // Add tasks to queue
251
- pool.taskQueue.push({ id: 1, command: 'test' });
252
- pool.taskQueue.push({ id: 2, command: 'test2' });
253
-
254
- await pool.shutdown();
255
-
256
- expect(pool.taskQueue.length).toBe(0);
257
- });
258
-
259
- test('should be safe to call shutdown multiple times', async () => {
260
- pool = new WorkerPool({ enableWorkers: false });
261
-
262
- await pool.shutdown();
263
- await pool.shutdown(); // Should not throw
264
-
265
- expect(pool.isShutdown).toBe(true);
266
- });
267
- });
268
-
269
- describe('checkWorkerSupport', () => {
270
- test('should return true for Node.js 12+', () => {
271
- pool = new WorkerPool();
272
- expect(pool.checkWorkerSupport()).toBe(true);
273
- });
274
- });
275
-
276
- describe('task ID generation', () => {
277
- test('taskId starts at 0', () => {
278
- pool = new WorkerPool({ enableWorkers: false });
279
- expect(pool.taskId).toBe(0);
280
- });
281
- });
282
-
283
- describe('processQueue edge cases', () => {
284
- test('should handle empty queue', async () => {
285
- pool = new WorkerPool({ enableWorkers: false });
286
-
287
- // Should not throw
288
- pool.processQueue();
289
- expect(pool.taskQueue.length).toBe(0);
290
- });
291
-
292
- test('should not process queue when workers are disabled', async () => {
293
- pool = new WorkerPool({ enableWorkers: false });
294
-
295
- // Add task to queue
296
- pool.taskQueue.push({ id: 1, command: 'test', options: {} });
297
-
298
- // Try to process
299
- pool.processQueue();
300
-
301
- // Task should remain in queue since no workers
302
- expect(pool.taskQueue.length).toBe(1);
303
- });
304
- });
305
-
306
- describe('worker management', () => {
307
- test('should create and remove workers', async () => {
308
- pool = new WorkerPool({ enableWorkers: false });
309
-
310
- // Mock worker for testing
311
- const mockWorker = { id: 99, terminate: jest.fn().mockResolvedValue(0) };
312
- pool.workers.push(mockWorker);
313
-
314
- expect(pool.workers.length).toBe(1);
315
-
316
- pool.removeWorker(99);
317
-
318
- expect(pool.workers.length).toBe(0);
319
- });
320
-
321
- test('should handle removing non-existent worker', async () => {
322
- pool = new WorkerPool({ enableWorkers: false });
323
-
324
- // Should not throw
325
- pool.removeWorker(999);
326
- expect(pool.workers.length).toBe(0);
327
- });
328
-
329
- test('should restart worker', async () => {
330
- pool = new WorkerPool({ enableWorkers: false });
331
-
332
- // Mock worker for testing
333
- const mockWorker = { id: 99, terminate: jest.fn().mockResolvedValue(0) };
334
- pool.workers.push(mockWorker);
335
-
336
- // Restart worker
337
- pool.restartWorker(99);
338
-
339
- // Should have called terminate
340
- expect(mockWorker.terminate).toHaveBeenCalled();
341
- });
342
- });
343
-
344
- describe('error handling in fallbackExecute', () => {
345
- test('should propagate fallback execution errors', async () => {
346
- const { default: systemInfo } = await import('systeminformation');
347
-
348
- pool = new WorkerPool({ enableWorkers: false });
349
-
350
- systemInfo.currentLoad.mockRejectedValueOnce(new Error('Test error'));
351
-
352
- await expect(pool.execute('currentLoad')).rejects.toThrow('Test error');
353
- });
354
- });
355
-
356
- describe('handleWorkerMessage', () => {
357
- test('should handle ready signal', async () => {
358
- pool = new WorkerPool({ enableWorkers: false });
359
-
360
- const mockWorker = { id: 1, isReady: false, isBusy: false };
361
-
362
- pool.handleWorkerMessage(mockWorker, { type: 'ready' });
363
-
364
- expect(mockWorker.isReady).toBe(true);
365
- });
366
-
367
- test('should handle successful task completion', async () => {
368
- pool = new WorkerPool({ enableWorkers: false });
369
-
370
- const resolve = jest.fn();
371
- const reject = jest.fn();
372
-
373
- pool.pendingTasks.set(1, {
374
- id: 1,
375
- command: 'test',
376
- resolve,
377
- reject,
378
- timeout: null,
379
- });
380
-
381
- const mockWorker = { id: 1, isBusy: true };
382
-
383
- pool.handleWorkerMessage(mockWorker, {
384
- id: 1,
385
- success: true,
386
- data: { result: 'test-data' },
387
- });
388
-
389
- expect(resolve).toHaveBeenCalledWith({ result: 'test-data' });
390
- expect(mockWorker.isBusy).toBe(false);
391
- expect(pool.pendingTasks.has(1)).toBe(false);
392
- });
393
-
394
- test('should handle failed task completion', async () => {
395
- pool = new WorkerPool({ enableWorkers: false });
396
-
397
- const resolve = jest.fn();
398
- const reject = jest.fn();
399
-
400
- pool.pendingTasks.set(1, {
401
- id: 1,
402
- command: 'test',
403
- resolve,
404
- reject,
405
- timeout: null,
406
- });
407
-
408
- const mockWorker = { id: 1, isBusy: true };
409
-
410
- pool.handleWorkerMessage(mockWorker, {
411
- id: 1,
412
- success: false,
413
- error: 'Task failed',
414
- stack: 'Error: Task failed\n at test',
415
- });
416
-
417
- expect(reject).toHaveBeenCalled();
418
- const errorArg = reject.mock.calls[0][0];
419
- expect(errorArg.message).toBe('Task failed');
420
- expect(errorArg.stack).toBe('Error: Task failed\n at test');
421
- expect(mockWorker.isBusy).toBe(false);
422
- });
423
-
424
- test('should clear timeout on task completion', async () => {
425
- pool = new WorkerPool({ enableWorkers: false });
426
-
427
- const timeout = setTimeout(() => {}, 10000);
428
- const clearTimeoutSpy = jest.spyOn(global, 'clearTimeout');
429
-
430
- const resolve = jest.fn();
431
-
432
- pool.pendingTasks.set(1, {
433
- id: 1,
434
- command: 'test',
435
- resolve,
436
- reject: jest.fn(),
437
- timeout,
438
- });
439
-
440
- const mockWorker = { id: 1, isBusy: true };
441
-
442
- pool.handleWorkerMessage(mockWorker, {
443
- id: 1,
444
- success: true,
445
- data: { result: 'test' },
446
- });
447
-
448
- expect(clearTimeoutSpy).toHaveBeenCalledWith(timeout);
449
-
450
- clearTimeoutSpy.mockRestore();
451
- });
452
-
453
- test('should ignore messages for unknown task IDs', async () => {
454
- pool = new WorkerPool({ enableWorkers: false });
455
-
456
- const mockWorker = { id: 1, isBusy: true };
457
-
458
- // Should not throw
459
- pool.handleWorkerMessage(mockWorker, {
460
- id: 999,
461
- success: true,
462
- data: { result: 'test' },
463
- });
464
-
465
- // Worker should remain busy since task wasn't found
466
- expect(mockWorker.isBusy).toBe(true);
467
- });
468
-
469
- test('should handle failed task with default error message', async () => {
470
- pool = new WorkerPool({ enableWorkers: false });
471
-
472
- const reject = jest.fn();
473
-
474
- pool.pendingTasks.set(1, {
475
- id: 1,
476
- command: 'test',
477
- resolve: jest.fn(),
478
- reject,
479
- timeout: null,
480
- });
481
-
482
- const mockWorker = { id: 1, isBusy: true };
483
-
484
- pool.handleWorkerMessage(mockWorker, {
485
- id: 1,
486
- success: false,
487
- // No error message provided
488
- });
489
-
490
- expect(reject).toHaveBeenCalled();
491
- const errorArg = reject.mock.calls[0][0];
492
- expect(errorArg.message).toBe('Worker task failed');
493
- });
494
- });
495
-
496
- describe('execute method', () => {
497
- test('should not set up timeout in fallback mode', async () => {
498
- pool = new WorkerPool({
499
- enableWorkers: false,
500
- taskTimeout: 5000,
501
- });
502
-
503
- // Mock setTimeout to verify it's not called in fallback mode
504
- const setTimeoutSpy = jest.spyOn(global, 'setTimeout');
505
-
506
- await pool.execute('currentLoad');
507
-
508
- // In fallback mode, setTimeout should not be called
509
- expect(setTimeoutSpy).not.toHaveBeenCalled();
510
-
511
- setTimeoutSpy.mockRestore();
512
- });
513
-
514
- test('should use direct execution in fallback mode', async () => {
515
- pool = new WorkerPool({ enableWorkers: false });
516
-
517
- const result = await pool.execute('currentLoad');
518
-
519
- expect(result).toEqual(mockSiData.currentLoad);
520
- });
521
- });
522
- });