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,437 +0,0 @@
1
- /**
2
- * Unit tests for alert threshold logic in alerts.js
3
- */
4
-
5
- import alerts from '../src/alerts.js';
6
-
7
- describe('Alert Threshold Logic', () => {
8
- beforeEach(() => {
9
- // Reset thresholds and clear alerts before each test
10
- alerts.resetThresholds();
11
- alerts.clearAllAlerts();
12
- alerts.resetRateLimit();
13
- });
14
-
15
- describe('checkThreshold', () => {
16
- test('should return null when value is below warning threshold', () => {
17
- const result = alerts.checkThreshold('cpu', 50);
18
- expect(result).toBeNull();
19
- });
20
-
21
- test('should create warning alert when value reaches warning threshold', () => {
22
- const result = alerts.checkThreshold('cpu', 70);
23
-
24
- expect(result).not.toBeNull();
25
- expect(result.type).toBe('cpu');
26
- expect(result.level).toBe(alerts.AlertLevel.WARNING);
27
- expect(result.value).toBe(70);
28
- expect(result.threshold).toBe(70);
29
- expect(result.message).toContain('Warning');
30
- });
31
-
32
- test('should create critical alert when value reaches critical threshold', () => {
33
- const result = alerts.checkThreshold('cpu', 90);
34
-
35
- expect(result).not.toBeNull();
36
- expect(result.type).toBe('cpu');
37
- expect(result.level).toBe(alerts.AlertLevel.CRITICAL);
38
- expect(result.value).toBe(90);
39
- expect(result.threshold).toBe(90);
40
- expect(result.message).toContain('Critical');
41
- });
42
-
43
- test('should upgrade warning to critical when value crosses threshold', () => {
44
- // First create a warning
45
- alerts.checkThreshold('cpu', 70);
46
- const criticalAlert = alerts.checkThreshold('cpu', 90);
47
-
48
- expect(criticalAlert).not.toBeNull();
49
- expect(criticalAlert.level).toBe(alerts.AlertLevel.CRITICAL);
50
-
51
- // Should have critical alert in active alerts
52
- const activeAlerts = alerts.getActiveAlerts();
53
- const cpuAlert = activeAlerts.find(a => a.type === 'cpu');
54
- expect(cpuAlert.level).toBe(alerts.AlertLevel.CRITICAL);
55
- });
56
-
57
- test('should not create duplicate alerts for same level', () => {
58
- alerts.checkThreshold('cpu', 75);
59
- const activeBefore = alerts.getActiveAlerts().length;
60
-
61
- alerts.checkThreshold('cpu', 78);
62
- const activeAfter = alerts.getActiveAlerts().length;
63
-
64
- // Should not add duplicate - just update existing
65
- expect(activeBefore).toBe(activeAfter);
66
- });
67
-
68
- test('should create cleared alert when value drops below threshold', () => {
69
- // Create a warning alert first
70
- alerts.checkThreshold('cpu', 75);
71
-
72
- // Clear the alert by dropping below threshold
73
- const clearedAlert = alerts.checkThreshold('cpu', 50);
74
-
75
- expect(clearedAlert).not.toBeNull();
76
- expect(clearedAlert.level).toBe(alerts.AlertLevel.CLEARED);
77
- expect(clearedAlert.message).toContain('normalized');
78
- });
79
-
80
- test('should return null when updating existing alert with same level', () => {
81
- alerts.checkThreshold('cpu', 75);
82
- const result = alerts.checkThreshold('cpu', 80);
83
-
84
- // Should update but return null (no new alert)
85
- expect(result).toBeNull();
86
- });
87
-
88
- test('should handle memory metric type', () => {
89
- const result = alerts.checkThreshold('memory', 80);
90
-
91
- expect(result).not.toBeNull();
92
- expect(result.type).toBe('memory');
93
- expect(result.level).toBe(alerts.AlertLevel.WARNING);
94
- });
95
-
96
- test('should handle disk metric type', () => {
97
- const result = alerts.checkThreshold('disk', 85);
98
-
99
- expect(result).not.toBeNull();
100
- expect(result.type).toBe('disk');
101
- expect(result.level).toBe(alerts.AlertLevel.WARNING);
102
- });
103
-
104
- test('should return null for unknown metric type', () => {
105
- const result = alerts.checkThreshold('unknown', 50);
106
- expect(result).toBeNull();
107
- });
108
-
109
- test('should use custom thresholds when set', () => {
110
- alerts.setThresholds({
111
- cpu: { warning: 50, critical: 80 }
112
- });
113
-
114
- // Should trigger warning at 50
115
- const warningResult = alerts.checkThreshold('cpu', 50);
116
- expect(warningResult.level).toBe(alerts.AlertLevel.WARNING);
117
-
118
- // Should trigger critical at 80
119
- const criticalResult = alerts.checkThreshold('cpu', 80);
120
- expect(criticalResult.level).toBe(alerts.AlertLevel.CRITICAL);
121
- });
122
-
123
- test('should handle edge case at exact threshold values', () => {
124
- // Reset to ensure clean state
125
- alerts.clearAllAlerts();
126
-
127
- // Exact warning threshold
128
- const warningResult = alerts.checkThreshold('cpu', 70);
129
- expect(warningResult.level).toBe(alerts.AlertLevel.WARNING);
130
-
131
- // Exact critical threshold
132
- const criticalResult = alerts.checkThreshold('cpu', 90);
133
- expect(criticalResult.level).toBe(alerts.AlertLevel.CRITICAL);
134
-
135
- // Clear the alert - value below warning but existing critical alert exists
136
- const clearedResult = alerts.checkThreshold('cpu', 69);
137
- // Should create a cleared alert since there was an active critical alert
138
- expect(clearedResult).not.toBeNull();
139
- expect(clearedResult.level).toBe(alerts.AlertLevel.CLEARED);
140
- });
141
- });
142
-
143
- describe('checkAllMetrics', () => {
144
- test('should check all provided metrics', () => {
145
- const metrics = {
146
- cpu: 85,
147
- memory: 80,
148
- disk: 90
149
- };
150
-
151
- const results = alerts.checkAllMetrics(metrics);
152
-
153
- expect(results.length).toBe(3);
154
- expect(results.map(r => r.type)).toContain('cpu');
155
- expect(results.map(r => r.type)).toContain('memory');
156
- expect(results.map(r => r.type)).toContain('disk');
157
- });
158
-
159
- test('should only check metrics that are defined', () => {
160
- const metrics = {
161
- cpu: 85
162
- // memory and disk not provided
163
- };
164
-
165
- const results = alerts.checkAllMetrics(metrics);
166
-
167
- expect(results.length).toBe(1);
168
- expect(results[0].type).toBe('cpu');
169
- });
170
-
171
- test('should return empty array when no metrics exceed thresholds', () => {
172
- const metrics = {
173
- cpu: 50,
174
- memory: 50,
175
- disk: 50
176
- };
177
-
178
- const results = alerts.checkAllMetrics(metrics);
179
-
180
- expect(results.length).toBe(0);
181
- });
182
-
183
- test('should handle undefined metrics object', () => {
184
- const results = alerts.checkAllMetrics(undefined);
185
- expect(results).toEqual([]);
186
- });
187
-
188
- test('should handle empty metrics object', () => {
189
- const results = alerts.checkAllMetrics({});
190
- expect(results).toEqual([]);
191
- });
192
-
193
- test('should return only new alerts, not updates', () => {
194
- const metrics1 = { cpu: 75 };
195
- const results1 = alerts.checkAllMetrics(metrics1);
196
- expect(results1.length).toBe(1);
197
-
198
- // Update same metric
199
- const metrics2 = { cpu: 80 };
200
- const results2 = alerts.checkAllMetrics(metrics2);
201
- // This returns null for updates, so should be 0
202
- expect(results2.length).toBe(0);
203
- });
204
- });
205
-
206
- describe('Alert Level Constants', () => {
207
- test('should have correct alert level values', () => {
208
- expect(alerts.AlertLevel.INFO).toBe('info');
209
- expect(alerts.AlertLevel.WARNING).toBe('warning');
210
- expect(alerts.AlertLevel.CRITICAL).toBe('critical');
211
- expect(alerts.AlertLevel.CLEARED).toBe('cleared');
212
- });
213
- });
214
-
215
- describe('Threshold Management', () => {
216
- test('getThresholds should return current thresholds', () => {
217
- const thresholds = alerts.getThresholds();
218
-
219
- expect(thresholds.cpu.warning).toBe(70);
220
- expect(thresholds.cpu.critical).toBe(90);
221
- expect(thresholds.memory.warning).toBe(75);
222
- expect(thresholds.memory.critical).toBe(90);
223
- expect(thresholds.disk.warning).toBe(80);
224
- expect(thresholds.disk.critical).toBe(95);
225
- });
226
-
227
- test('resetThresholds should restore defaults', () => {
228
- alerts.setThresholds({ cpu: { warning: 10, critical: 20 } });
229
- alerts.resetThresholds();
230
-
231
- const thresholds = alerts.getThresholds();
232
- expect(thresholds.cpu.warning).toBe(70);
233
- expect(thresholds.cpu.critical).toBe(90);
234
- });
235
- });
236
-
237
- describe('Alert Management', () => {
238
- test('getActiveAlerts should return non-dismissed alerts', () => {
239
- alerts.checkThreshold('cpu', 85);
240
- alerts.checkThreshold('memory', 85);
241
-
242
- const active = alerts.getActiveAlerts();
243
- expect(active.length).toBe(2);
244
- });
245
-
246
- test('dismissAlert should mark alert as dismissed', () => {
247
- const alert = alerts.checkThreshold('cpu', 85);
248
- alerts.dismissAlert(alert.id);
249
-
250
- const active = alerts.getActiveAlerts();
251
- expect(active.find(a => a.id === alert.id)).toBeUndefined();
252
- });
253
-
254
- test('getAlertsByLevel should filter by level', () => {
255
- alerts.checkThreshold('cpu', 95); // critical (above 90%)
256
- alerts.checkThreshold('memory', 75); // warning
257
-
258
- const critical = alerts.getAlertsByLevel(alerts.AlertLevel.CRITICAL);
259
- expect(critical.length).toBe(1);
260
- expect(critical[0].type).toBe('cpu');
261
-
262
- const warning = alerts.getAlertsByLevel(alerts.AlertLevel.WARNING);
263
- expect(warning.length).toBe(1);
264
- expect(warning[0].type).toBe('memory');
265
- });
266
-
267
- test('clearAllAlerts should dismiss all active alerts', () => {
268
- alerts.checkThreshold('cpu', 85);
269
- alerts.checkThreshold('memory', 85);
270
-
271
- alerts.clearAllAlerts();
272
-
273
- const active = alerts.getActiveAlerts();
274
- expect(active.length).toBe(0);
275
- });
276
-
277
- test('getAlertHistory should return all alerts including dismissed', () => {
278
- const alert = alerts.checkThreshold('cpu', 85);
279
- alerts.dismissAlert(alert.id);
280
-
281
- const history = alerts.getAlertHistory();
282
- expect(history.length).toBeGreaterThan(0);
283
- });
284
- });
285
- });
286
-
287
- describe('Rate Limiting', () => {
288
- beforeEach(() => {
289
- alerts.resetThresholds();
290
- alerts.clearAllAlerts();
291
- alerts.resetRateLimit();
292
- });
293
-
294
- describe('shouldRateLimitAlert', () => {
295
- test('should allow alerts when rate limiting is disabled', () => {
296
- alerts.setRateLimit({ enabled: false });
297
-
298
- // Should never rate limit when disabled
299
- for (let i = 0; i < 10; i++) {
300
- expect(alerts.shouldRateLimitAlert('cpu')).toBe(false);
301
- }
302
- });
303
-
304
- test('should allow first few alerts within limit', () => {
305
- alerts.setRateLimit({ enabled: true, maxAlerts: 3, windowMs: 60000 });
306
-
307
- expect(alerts.shouldRateLimitAlert('cpu')).toBe(false);
308
- expect(alerts.shouldRateLimitAlert('cpu')).toBe(false);
309
- expect(alerts.shouldRateLimitAlert('cpu')).toBe(false);
310
- });
311
-
312
- test('should rate limit alerts exceeding maxAlerts', () => {
313
- alerts.setRateLimit({ enabled: true, maxAlerts: 2, windowMs: 60000 });
314
-
315
- // First two should pass (check + record)
316
- expect(alerts.shouldRateLimitAlert('cpu')).toBe(false);
317
- alerts.recordAlertTimestamp('cpu');
318
-
319
- expect(alerts.shouldRateLimitAlert('cpu')).toBe(false);
320
- alerts.recordAlertTimestamp('cpu');
321
-
322
- // Third should be rate limited
323
- expect(alerts.shouldRateLimitAlert('cpu')).toBe(true);
324
- });
325
-
326
- test('should track rate limits separately per alert type', () => {
327
- alerts.setRateLimit({ enabled: true, maxAlerts: 1, windowMs: 60000 });
328
-
329
- // cpu should be limited after one
330
- expect(alerts.shouldRateLimitAlert('cpu')).toBe(false);
331
- alerts.recordAlertTimestamp('cpu');
332
-
333
- expect(alerts.shouldRateLimitAlert('cpu')).toBe(true);
334
-
335
- // memory should still be allowed
336
- expect(alerts.shouldRateLimitAlert('memory')).toBe(false);
337
- });
338
-
339
- test('should respect custom windowMs', async () => {
340
- alerts.setRateLimit({ enabled: true, maxAlerts: 1, windowMs: 50 });
341
-
342
- expect(alerts.shouldRateLimitAlert('cpu')).toBe(false);
343
- alerts.recordAlertTimestamp('cpu');
344
-
345
- expect(alerts.shouldRateLimitAlert('cpu')).toBe(true);
346
-
347
- // Wait for window to expire
348
- await new Promise(resolve => setTimeout(resolve, 60));
349
-
350
- // Should be allowed again after window expires
351
- expect(alerts.shouldRateLimitAlert('cpu')).toBe(false);
352
- });
353
- });
354
-
355
- describe('checkThreshold with rate limiting', () => {
356
- test('should suppress warning alerts when rate limited', () => {
357
- alerts.setRateLimit({ enabled: true, maxAlerts: 1, windowMs: 60000 });
358
-
359
- // First alert goes through
360
- const alert1 = alerts.checkThreshold('cpu', 75);
361
- expect(alert1).not.toBeNull();
362
-
363
- // Second warning should be suppressed
364
- const alert2 = alerts.checkThreshold('cpu', 80);
365
- expect(alert2).toBeNull();
366
- });
367
-
368
- test('should always allow critical alerts even when rate limited', () => {
369
- alerts.setRateLimit({ enabled: true, maxAlerts: 1, windowMs: 60000 });
370
-
371
- // First alert (warning) goes through
372
- const alert1 = alerts.checkThreshold('cpu', 75);
373
- expect(alert1).not.toBeNull();
374
-
375
- // Critical should always go through
376
- const alert2 = alerts.checkThreshold('cpu', 95);
377
- expect(alert2).not.toBeNull();
378
- expect(alert2.level).toBe(alerts.AlertLevel.CRITICAL);
379
- });
380
-
381
- test('should always allow cleared alerts', () => {
382
- alerts.setRateLimit({ enabled: true, maxAlerts: 1, windowMs: 60000 });
383
-
384
- // Create an alert
385
- alerts.checkThreshold('cpu', 85);
386
-
387
- // Clear the alert - should always go through
388
- const cleared = alerts.checkThreshold('cpu', 50);
389
- expect(cleared).not.toBeNull();
390
- expect(cleared.level).toBe(alerts.AlertLevel.CLEARED);
391
- });
392
- });
393
-
394
- describe('getRateLimit and setRateLimit', () => {
395
- test('should return current rate limit config', () => {
396
- const config = alerts.getRateLimit();
397
-
398
- expect(config.enabled).toBe(true);
399
- expect(config.windowMs).toBe(60000);
400
- expect(config.maxAlerts).toBe(5);
401
- });
402
-
403
- test('should update rate limit config', () => {
404
- alerts.setRateLimit({ enabled: false, maxAlerts: 10 });
405
-
406
- const config = alerts.getRateLimit();
407
- expect(config.enabled).toBe(false);
408
- expect(config.maxAlerts).toBe(10);
409
- });
410
-
411
- test('should preserve unspecified values when setting config', () => {
412
- alerts.setRateLimit({ maxAlerts: 3 });
413
-
414
- const config = alerts.getRateLimit();
415
- expect(config.maxAlerts).toBe(3);
416
- expect(config.enabled).toBe(true); // preserved
417
- expect(config.windowMs).toBe(60000); // preserved
418
- });
419
- });
420
-
421
- describe('resetRateLimit', () => {
422
- test('should reset rate limit state', () => {
423
- alerts.setRateLimit({ enabled: true, maxAlerts: 1, windowMs: 60000 });
424
-
425
- // Trigger rate limit
426
- alerts.shouldRateLimitAlert('cpu');
427
- alerts.shouldRateLimitAlert('cpu');
428
-
429
- // Reset
430
- alerts.resetRateLimit();
431
-
432
- const config = alerts.getRateLimit();
433
- expect(config.enabled).toBe(true);
434
- expect(config.maxAlerts).toBe(5); // back to default
435
- });
436
- });
437
- });