claw-dashboard 2.1.1 → 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 (89) hide show
  1. package/CHANGELOG.md +29 -1
  2. package/docs/API.md +1 -2
  3. package/index.js +31 -41
  4. package/package.json +22 -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/.c8rc.json +0 -38
  14. package/.dockerignore +0 -68
  15. package/.github/dependabot.yml +0 -45
  16. package/.github/pull_request_template.md +0 -39
  17. package/.github/workflows/ci.yml +0 -147
  18. package/.github/workflows/release.yml +0 -40
  19. package/.github/workflows/security.yml +0 -84
  20. package/.husky/pre-commit +0 -1
  21. package/.planning/codebase/ARCHITECTURE.md +0 -206
  22. package/.planning/codebase/INTEGRATIONS.md +0 -150
  23. package/.planning/codebase/STACK.md +0 -122
  24. package/.planning/codebase/STRUCTURE.md +0 -201
  25. package/CONTRIBUTING.md +0 -378
  26. package/Dockerfile +0 -54
  27. package/FEATURES.md +0 -307
  28. package/TODO.md +0 -28
  29. package/ai.openclaw.dashboard.plist +0 -35
  30. package/build-cjs.js +0 -127
  31. package/cjs-shim.js +0 -13
  32. package/dist/clawdash +0 -1729
  33. package/dist/clawdash.meta.json +0 -2236
  34. package/dist/widgets.cjs +0 -6511
  35. package/docker-compose.yml +0 -67
  36. package/esbuild.config.js +0 -158
  37. package/eslint.config.js +0 -56
  38. package/examples/plugins/README.md +0 -122
  39. package/examples/plugins/api-status/index.js +0 -294
  40. package/examples/plugins/api-status/plugin.json +0 -19
  41. package/examples/plugins/hello-world/index.js +0 -104
  42. package/examples/plugins/hello-world/plugin.json +0 -15
  43. package/examples/plugins/system-metrics-chart/index.js +0 -339
  44. package/examples/plugins/system-metrics-chart/plugin.json +0 -18
  45. package/examples/plugins/weather-widget/index.js +0 -136
  46. package/examples/plugins/weather-widget/plugin.json +0 -19
  47. package/index.cjs +0 -23005
  48. package/jest.config.js +0 -11
  49. package/scripts/release.js +0 -595
  50. package/src/database.js +0 -734
  51. package/tests/alerts.test.js +0 -437
  52. package/tests/auto-save.test.js +0 -529
  53. package/tests/cache.test.js +0 -317
  54. package/tests/cli.test.js +0 -351
  55. package/tests/command-palette.test.js +0 -320
  56. package/tests/config-processor.test.js +0 -452
  57. package/tests/config-validator.test.js +0 -710
  58. package/tests/config-watcher.test.js +0 -594
  59. package/tests/config.test.js +0 -755
  60. package/tests/database.test.js +0 -438
  61. package/tests/errors.test.js +0 -189
  62. package/tests/example-plugins.test.js +0 -624
  63. package/tests/integration.test.js +0 -1321
  64. package/tests/loading-states.test.js +0 -300
  65. package/tests/manifest-validation-on-load.test.js +0 -671
  66. package/tests/performance-monitor.test.js +0 -190
  67. package/tests/plugin-api-rate-limit.test.js +0 -302
  68. package/tests/plugin-errors.test.js +0 -311
  69. package/tests/plugin-lifecycle-e2e.test.js +0 -1036
  70. package/tests/plugin-reload.test.js +0 -764
  71. package/tests/rate-limiter.test.js +0 -539
  72. package/tests/removed-dependencies.test.js +0 -74
  73. package/tests/retry.test.js +0 -308
  74. package/tests/security.test.js +0 -411
  75. package/tests/settings-modal.test.js +0 -226
  76. package/tests/theme-selector.test.js +0 -57
  77. package/tests/utils.js +0 -242
  78. package/tests/utils.test.js +0 -317
  79. package/tests/validate-plugin-cli.test.js +0 -359
  80. package/tests/validation.test.js +0 -837
  81. package/tests/web-server.test.js +0 -646
  82. package/tests/widget-arrange-mode.test.js +0 -183
  83. package/tests/widget-config-hot-reload.test.js +0 -465
  84. package/tests/widget-dependency.test.js +0 -591
  85. package/tests/widget-error-boundary.test.js +0 -749
  86. package/tests/widget-error-isolation.test.js +0 -469
  87. package/tests/widget-integration.test.js +0 -1147
  88. package/tests/widget-refresh-intervals.test.js +0 -284
  89. 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
- });