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
package/src/database.js DELETED
@@ -1,734 +0,0 @@
1
- /**
2
- * SQLite database module for historical data persistence
3
- * Uses sql.js (WebAssembly-based SQLite) for cross-platform compatibility
4
- */
5
-
6
- import initSqlJs from 'sql.js';
7
- import fs from 'fs';
8
- import os from 'os';
9
- import path from 'path';
10
- import { fileURLToPath } from 'url';
11
- import logger from './logger.js';
12
- import config from './config.js';
13
-
14
- const __filename = fileURLToPath(import.meta.url);
15
- const __dirname = path.dirname(__filename);
16
-
17
- // Database file path
18
- const DB_PATH = config.DATABASE.PATH;
19
-
20
- // Database instance
21
- let db = null;
22
- let SQL = null;
23
- let saveInterval = null;
24
- let cleanupInterval = null;
25
-
26
- /**
27
- * Initialize the database and create tables if needed
28
- */
29
- export async function initDatabase() {
30
- try {
31
- // Initialize SQL.js
32
- SQL = await initSqlJs();
33
-
34
- // Load existing database or create new one
35
- let data = null;
36
- try {
37
- if (fs.existsSync(DB_PATH)) {
38
- data = fs.readFileSync(DB_PATH);
39
- logger.info('Loaded existing database from ' + DB_PATH);
40
- }
41
- } catch (err) {
42
- logger.warn('Could not load existing database: ' + err.message);
43
- }
44
-
45
- db = new SQL.Database(data);
46
-
47
- // Create tables
48
- createTables();
49
-
50
- // Set up periodic save (unref allows Jest to exit cleanly)
51
- saveInterval = setInterval(saveDatabase, config.DATABASE.SAVE_INTERVAL_MS).unref();
52
- cleanupInterval = setInterval(cleanupOldData, config.DATABASE.CLEANUP_INTERVAL_MS).unref();
53
-
54
- logger.info('Database initialized successfully');
55
- return true;
56
- } catch (err) {
57
- logger.error('Failed to initialize database: ' + err.message);
58
- return false;
59
- }
60
- }
61
-
62
- /**
63
- * Create all required tables
64
- */
65
- function createTables() {
66
- // Session snapshots table
67
- db.run(`
68
- CREATE TABLE IF NOT EXISTS session_snapshots (
69
- id INTEGER PRIMARY KEY AUTOINCREMENT,
70
- session_id TEXT NOT NULL,
71
- agent TEXT,
72
- channel TEXT,
73
- model TEXT,
74
- tokens INTEGER DEFAULT 0,
75
- status TEXT,
76
- created_at INTEGER NOT NULL,
77
- updated_at INTEGER NOT NULL
78
- )
79
- `);
80
-
81
- // Create index for faster queries
82
- db.run(`
83
- CREATE INDEX IF NOT EXISTS idx_session_snapshots_created
84
- ON session_snapshots(created_at)
85
- `);
86
-
87
- db.run(`
88
- CREATE INDEX IF NOT EXISTS idx_session_snapshots_session_id
89
- ON session_snapshots(session_id)
90
- `);
91
-
92
- // CPU metrics history table
93
- db.run(`
94
- CREATE TABLE IF NOT EXISTS cpu_metrics (
95
- id INTEGER PRIMARY KEY AUTOINCREMENT,
96
- timestamp INTEGER NOT NULL,
97
- cpu_count INTEGER,
98
- load_avg_1 REAL,
99
- load_avg_5 REAL,
100
- load_avg_15 REAL,
101
- cpu_usage_user REAL,
102
- cpu_usage_system REAL,
103
- cpu_usage_idle REAL,
104
- cpu_usage_irq REAL,
105
- cpu_usage_soft_irq REAL,
106
- cpu_usage_stolen REAL
107
- )
108
- `);
109
-
110
- db.run(`
111
- CREATE INDEX IF NOT EXISTS idx_cpu_metrics_timestamp
112
- ON cpu_metrics(timestamp)
113
- `);
114
-
115
- // Memory metrics history table
116
- db.run(`
117
- CREATE TABLE IF NOT EXISTS memory_metrics (
118
- id INTEGER PRIMARY KEY AUTOINCREMENT,
119
- timestamp INTEGER NOT NULL,
120
- total_bytes INTEGER,
121
- used_bytes INTEGER,
122
- free_bytes INTEGER,
123
- available_bytes INTEGER,
124
- used_percent REAL,
125
- swap_total_bytes INTEGER,
126
- swap_used_bytes INTEGER,
127
- swap_free_bytes INTEGER,
128
- swap_used_percent REAL
129
- )
130
- `);
131
-
132
- db.run(`
133
- CREATE INDEX IF NOT EXISTS idx_memory_metrics_timestamp
134
- ON memory_metrics(timestamp)
135
- `);
136
-
137
- // Network metrics history table
138
- db.run(`
139
- CREATE TABLE IF NOT EXISTS network_metrics (
140
- id INTEGER PRIMARY KEY AUTOINCREMENT,
141
- timestamp INTEGER NOT NULL,
142
- interface_name TEXT,
143
- rx_bytes INTEGER,
144
- tx_bytes INTEGER,
145
- rx_sec REAL,
146
- tx_sec REAL,
147
- ms REAL
148
- )
149
- `);
150
-
151
- db.run(`
152
- CREATE INDEX IF NOT EXISTS idx_network_metrics_timestamp
153
- ON network_metrics(timestamp)
154
- `);
155
-
156
- logger.debug('Database tables created');
157
- }
158
-
159
- /**
160
- * Save database to disk
161
- */
162
- function saveDatabase() {
163
- if (!db) return;
164
-
165
- try {
166
- const data = db.export();
167
- const buffer = Buffer.from(data);
168
-
169
- // Ensure directory exists
170
- const dir = path.dirname(DB_PATH);
171
- if (!fs.existsSync(dir)) {
172
- fs.mkdirSync(dir, { recursive: true });
173
- }
174
-
175
- fs.writeFileSync(DB_PATH, buffer);
176
- logger.debug('Database saved to disk');
177
- } catch (err) {
178
- logger.error('Failed to save database: ' + err.message);
179
- }
180
- }
181
-
182
- /**
183
- * Store a session snapshot
184
- * @param {Object} session - Session object from openclaw
185
- */
186
- export function storeSessionSnapshot(session) {
187
- if (!db || !session) return;
188
-
189
- try {
190
- const now = Date.now();
191
-
192
- // Extract relevant fields from session
193
- const sessionId = session.sessionId || session.key || null;
194
- const agent = session.agent || extractAgent(session.key) || 'unknown';
195
- const channel = session.deliveryContext?.channel ||
196
- session.origin?.channel ||
197
- session.origin?.surface || 'unknown';
198
- const model = session.model || session.llmModel || 'unknown';
199
- const tokens = session.totalTokens || session.tokens || 0;
200
- const status = session.status || (session.systemRunning ? 'running' : 'idle');
201
-
202
- // Check if session already exists, update or insert
203
- const existing = db.exec(
204
- 'SELECT id FROM session_snapshots WHERE session_id = ? ORDER BY created_at DESC LIMIT 1',
205
- [sessionId]
206
- );
207
-
208
- if (existing.length > 0 && existing[0].values.length > 0) {
209
- // Update existing
210
- db.run(
211
- `UPDATE session_snapshots
212
- SET agent = ?, channel = ?, model = ?, tokens = ?, status = ?, updated_at = ?
213
- WHERE session_id = ?`,
214
- [agent, channel, model, tokens, status, now, sessionId]
215
- );
216
- } else {
217
- // Insert new
218
- db.run(
219
- `INSERT INTO session_snapshots (session_id, agent, channel, model, tokens, status, created_at, updated_at)
220
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
221
- [sessionId, agent, channel, model, tokens, status, now, now]
222
- );
223
- }
224
-
225
- logger.debug('Stored session snapshot: ' + sessionId);
226
- } catch (err) {
227
- logger.error('Failed to store session snapshot: ' + err.message);
228
- }
229
- }
230
-
231
- /**
232
- * Store CPU metrics
233
- * @param {Object} cpuData - CPU metrics data
234
- */
235
- export function storeCpuMetrics(cpuData) {
236
- if (!db || !cpuData) return;
237
-
238
- try {
239
- const now = Date.now();
240
-
241
- // Handle both array of CPUs and single CPU object
242
- const cpus = cpuData.cpus || [cpuData] || [];
243
-
244
- for (const cpu of cpus) {
245
- db.run(
246
- `INSERT INTO cpu_metrics (
247
- timestamp, cpu_count, load_avg_1, load_avg_5, load_avg_15,
248
- cpu_usage_user, cpu_usage_system, cpu_usage_idle,
249
- cpu_usage_irq, cpu_usage_soft_irq, cpu_usage_stolen
250
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
251
- [
252
- now,
253
- cpu.cpuCount || cpu.cpu_count || cpus.length || 1,
254
- cpu.loadAvg1 || cpu.load_avg_1 || cpu.loadavg?.[0] || 0,
255
- cpu.loadAvg5 || cpu.load_avg_5 || cpu.loadavg?.[1] || 0,
256
- cpu.loadAvg15 || cpu.load_avg_15 || cpu.loadavg?.[2] || 0,
257
- cpu.cpuUsageUser || cpu.cpu_usage_user || cpu.cpuUsage?.[0] || 0,
258
- cpu.cpuUsageSystem || cpu.cpu_usage_system || cpu.cpuUsage?.[1] || 0,
259
- cpu.cpuUsageIdle || cpu.cpu_usage_idle || cpu.cpuUsage?.[2] || 100,
260
- cpu.cpuUsageIrq || cpu.cpu_usage_irq || cpu.cpuUsage?.[3] || 0,
261
- cpu.cpuUsageSoftIrq || cpu.cpu_usage_soft_irq || cpu.cpuUsage?.[4] || 0,
262
- cpu.cpuUsageStolen || cpu.cpu_usage_stolen || cpu.cpuUsage?.[5] || 0
263
- ]
264
- );
265
- }
266
-
267
- logger.debug('Stored CPU metrics');
268
- } catch (err) {
269
- logger.error('Failed to store CPU metrics: ' + err.message);
270
- }
271
- }
272
-
273
- /**
274
- * Store memory metrics
275
- * @param {Object} memoryData - Memory metrics data
276
- */
277
- export function storeMemoryMetrics(memoryData) {
278
- if (!db || !memoryData) return;
279
-
280
- try {
281
- const now = Date.now();
282
-
283
- db.run(
284
- `INSERT INTO memory_metrics (
285
- timestamp, total_bytes, used_bytes, free_bytes, available_bytes,
286
- used_percent, swap_total_bytes, swap_used_bytes, swap_free_bytes, swap_used_percent
287
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
288
- [
289
- now,
290
- memoryData.totalBytes || memoryData.total_bytes || 0,
291
- memoryData.usedBytes || memoryData.used_bytes || 0,
292
- memoryData.freeBytes || memoryData.free_bytes || 0,
293
- memoryData.availableBytes || memoryData.available_bytes || 0,
294
- memoryData.usedPercent || memoryData.used_percent || 0,
295
- memoryData.swapTotalBytes || memoryData.swap_total_bytes || 0,
296
- memoryData.swapUsedBytes || memoryData.swap_used_bytes || 0,
297
- memoryData.swapFreeBytes || memoryData.swap_free_bytes || 0,
298
- memoryData.swapUsedPercent || memoryData.swap_used_percent || 0
299
- ]
300
- );
301
-
302
- logger.debug('Stored memory metrics');
303
- } catch (err) {
304
- logger.error('Failed to store memory metrics: ' + err.message);
305
- }
306
- }
307
-
308
- /**
309
- * Store network metrics
310
- * @param {Object} networkData - Network metrics data
311
- */
312
- export function storeNetworkMetrics(networkData) {
313
- if (!db || !networkData) return;
314
-
315
- try {
316
- const now = Date.now();
317
-
318
- // Handle both array and single interface
319
- const interfaces = Array.isArray(networkData) ? networkData : [networkData];
320
-
321
- for (const iface of interfaces) {
322
- db.run(
323
- `INSERT INTO network_metrics (
324
- timestamp, interface_name, rx_bytes, tx_bytes, rx_sec, tx_sec, ms
325
- ) VALUES (?, ?, ?, ?, ?, ?, ?)`,
326
- [
327
- now,
328
- iface.interfaceName || iface.interface_name || 'unknown',
329
- iface.rxBytes || iface.rx_bytes || 0,
330
- iface.txBytes || iface.tx_bytes || 0,
331
- iface.rxSec || iface.rx_sec || 0,
332
- iface.txSec || iface.tx_sec || 0,
333
- iface.ms || 0
334
- ]
335
- );
336
- }
337
-
338
- logger.debug('Stored network metrics');
339
- } catch (err) {
340
- logger.error('Failed to store network metrics: ' + err.message);
341
- }
342
- }
343
-
344
- /**
345
- * Get sessions from the last 24 hours
346
- * @returns {Array} Session snapshots
347
- */
348
- export function getSessionsLast24Hours() {
349
- return getSessionsByHours(24);
350
- }
351
-
352
- /**
353
- * Get sessions from the last 7 days
354
- * @returns {Array} Session snapshots
355
- */
356
- export function getSessionsLast7Days() {
357
- return getSessionsByDays(7);
358
- }
359
-
360
- /**
361
- * Get sessions by number of hours
362
- * @param {number} hours - Hours to look back
363
- * @returns {Array} Session snapshots
364
- */
365
- export function getSessionsByHours(hours = 24) {
366
- if (!db) return [];
367
-
368
- try {
369
- const cutoff = Date.now() - (hours * 60 * 60 * 1000);
370
-
371
- const result = db.exec(
372
- `SELECT session_id, agent, channel, model, tokens, status, created_at, updated_at
373
- FROM session_snapshots
374
- WHERE created_at >= ?
375
- ORDER BY created_at DESC`,
376
- [cutoff]
377
- );
378
-
379
- if (result.length === 0) return [];
380
-
381
- return result[0].values.map(row => ({
382
- sessionId: row[0],
383
- agent: row[1],
384
- channel: row[2],
385
- model: row[3],
386
- tokens: row[4],
387
- status: row[5],
388
- createdAt: row[6],
389
- updatedAt: row[7]
390
- }));
391
- } catch (err) {
392
- logger.error('Failed to get sessions by hours: ' + err.message);
393
- return [];
394
- }
395
- }
396
-
397
- /**
398
- * Get sessions by number of days
399
- * @param {number} days - Days to look back
400
- * @returns {Array} Session snapshots
401
- */
402
- export function getSessionsByDays(days = 7) {
403
- if (!db) return [];
404
-
405
- try {
406
- const cutoff = Date.now() - (days * 24 * 60 * 60 * 1000);
407
-
408
- const result = db.exec(
409
- `SELECT session_id, agent, channel, model, tokens, status, created_at, updated_at
410
- FROM session_snapshots
411
- WHERE created_at >= ?
412
- ORDER BY created_at DESC`,
413
- [cutoff]
414
- );
415
-
416
- if (result.length === 0) return [];
417
-
418
- return result[0].values.map(row => ({
419
- sessionId: row[0],
420
- agent: row[1],
421
- channel: row[2],
422
- model: row[3],
423
- tokens: row[4],
424
- status: row[5],
425
- createdAt: row[6],
426
- updatedAt: row[7]
427
- }));
428
- } catch (err) {
429
- logger.error('Failed to get sessions by days: ' + err.message);
430
- return [];
431
- }
432
- }
433
-
434
- /**
435
- * Get CPU metrics history for the last N hours
436
- * @param {number} hours - Hours to look back
437
- * @returns {Array} CPU metrics
438
- */
439
- export function getCpuMetricsHistory(hours = 24) {
440
- if (!db) return [];
441
-
442
- try {
443
- const cutoff = Date.now() - (hours * 60 * 60 * 1000);
444
-
445
- const result = db.exec(
446
- `SELECT timestamp, cpu_count, load_avg_1, load_avg_5, load_avg_15,
447
- cpu_usage_user, cpu_usage_system, cpu_usage_idle
448
- FROM cpu_metrics
449
- WHERE timestamp >= ?
450
- ORDER BY timestamp ASC`,
451
- [cutoff]
452
- );
453
-
454
- if (result.length === 0) return [];
455
-
456
- return result[0].values.map(row => ({
457
- timestamp: row[0],
458
- cpuCount: row[1],
459
- loadAvg1: row[2],
460
- loadAvg5: row[3],
461
- loadAvg15: row[4],
462
- cpuUsageUser: row[5],
463
- cpuUsageSystem: row[6],
464
- cpuUsageIdle: row[7]
465
- }));
466
- } catch (err) {
467
- logger.error('Failed to get CPU metrics history: ' + err.message);
468
- return [];
469
- }
470
- }
471
-
472
- /**
473
- * Get memory metrics history for the last N hours
474
- * @param {number} hours - Hours to look back
475
- * @returns {Array} Memory metrics
476
- */
477
- export function getMemoryMetricsHistory(hours = 24) {
478
- if (!db) return [];
479
-
480
- try {
481
- const cutoff = Date.now() - (hours * 60 * 60 * 1000);
482
-
483
- const result = db.exec(
484
- `SELECT timestamp, total_bytes, used_bytes, free_bytes, available_bytes, used_percent
485
- FROM memory_metrics
486
- WHERE timestamp >= ?
487
- ORDER BY timestamp ASC`,
488
- [cutoff]
489
- );
490
-
491
- if (result.length === 0) return [];
492
-
493
- return result[0].values.map(row => ({
494
- timestamp: row[0],
495
- totalBytes: row[1],
496
- usedBytes: row[2],
497
- freeBytes: row[3],
498
- availableBytes: row[4],
499
- usedPercent: row[5]
500
- }));
501
- } catch (err) {
502
- logger.error('Failed to get memory metrics history: ' + err.message);
503
- return [];
504
- }
505
- }
506
-
507
- /**
508
- * Get network metrics history for the last N hours
509
- * @param {number} hours - Hours to look back
510
- * @returns {Array} Network metrics
511
- */
512
- export function getNetworkMetricsHistory(hours = 24) {
513
- if (!db) return [];
514
-
515
- try {
516
- const cutoff = Date.now() - (hours * 60 * 60 * 1000);
517
-
518
- const result = db.exec(
519
- `SELECT timestamp, interface_name, rx_bytes, tx_bytes, rx_sec, tx_sec
520
- FROM network_metrics
521
- WHERE timestamp >= ?
522
- ORDER BY timestamp ASC`,
523
- [cutoff]
524
- );
525
-
526
- if (result.length === 0) return [];
527
-
528
- return result[0].values.map(row => ({
529
- timestamp: row[0],
530
- interfaceName: row[1],
531
- rxBytes: row[2],
532
- txBytes: row[3],
533
- rxSec: row[4],
534
- txSec: row[5]
535
- }));
536
- } catch (err) {
537
- logger.error('Failed to get network metrics history: ' + err.message);
538
- return [];
539
- }
540
- }
541
-
542
- /**
543
- * Get aggregated metrics summary
544
- * @param {number} hours - Hours to look back
545
- * @returns {Object} Aggregated metrics
546
- */
547
- export function getMetricsSummary(hours = 24) {
548
- if (!db) return null;
549
-
550
- try {
551
- const cutoff = Date.now() - (hours * 60 * 60 * 1000);
552
-
553
- // Get CPU summary
554
- const cpuResult = db.exec(
555
- `SELECT
556
- AVG(cpu_usage_user) as avg_user,
557
- MAX(cpu_usage_user) as max_user,
558
- AVG(load_avg_1) as avg_load
559
- FROM cpu_metrics
560
- WHERE timestamp >= ?`,
561
- [cutoff]
562
- );
563
-
564
- // Get Memory summary
565
- const memResult = db.exec(
566
- `SELECT
567
- AVG(used_percent) as avg_used,
568
- MAX(used_percent) as max_used,
569
- AVG(available_bytes) as avg_available
570
- FROM memory_metrics
571
- WHERE timestamp >= ?`,
572
- [cutoff]
573
- );
574
-
575
- // Get session count
576
- const sessionResult = db.exec(
577
- `SELECT COUNT(DISTINCT session_id) as session_count
578
- FROM session_snapshots
579
- WHERE created_at >= ?`,
580
- [cutoff]
581
- );
582
-
583
- // Get total tokens
584
- const tokensResult = db.exec(
585
- `SELECT SUM(tokens) as total_tokens
586
- FROM session_snapshots
587
- WHERE created_at >= ?`,
588
- [cutoff]
589
- );
590
-
591
- return {
592
- cpu: cpuResult.length > 0 && cpuResult[0].values.length > 0 ? {
593
- avgUser: cpuResult[0].values[0][0] || 0,
594
- maxUser: cpuResult[0].values[0][1] || 0,
595
- avgLoad: cpuResult[0].values[0][2] || 0
596
- } : null,
597
- memory: memResult.length > 0 && memResult[0].values.length > 0 ? {
598
- avgUsed: memResult[0].values[0][0] || 0,
599
- maxUsed: memResult[0].values[0][1] || 0,
600
- avgAvailable: memResult[0].values[0][2] || 0
601
- } : null,
602
- sessions: sessionResult.length > 0 && sessionResult[0].values.length > 0 ? {
603
- count: sessionResult[0].values[0][0] || 0
604
- } : { count: 0 },
605
- tokens: tokensResult.length > 0 && tokensResult[0].values.length > 0 ? {
606
- total: tokensResult[0].values[0][0] || 0
607
- } : { total: 0 }
608
- };
609
- } catch (err) {
610
- logger.error('Failed to get metrics summary: ' + err.message);
611
- return null;
612
- }
613
- }
614
-
615
- /**
616
- * Clean up old data beyond retention period
617
- * @param {number} days - Number of days to retain
618
- */
619
- export function cleanupOldData(days = 30) {
620
- if (!db) return;
621
-
622
- try {
623
- const cutoff = Date.now() - (days * 24 * 60 * 60 * 1000);
624
-
625
- db.run('DELETE FROM session_snapshots WHERE created_at < ?', [cutoff]);
626
- db.run('DELETE FROM cpu_metrics WHERE timestamp < ?', [cutoff]);
627
- db.run('DELETE FROM memory_metrics WHERE timestamp < ?', [cutoff]);
628
- db.run('DELETE FROM network_metrics WHERE timestamp < ?', [cutoff]);
629
-
630
- logger.info('Cleaned up data older than ' + days + ' days');
631
- } catch (err) {
632
- logger.error('Failed to cleanup old data: ' + err.message);
633
- }
634
-
635
- // Save after cleanup, with its own try-catch
636
- try {
637
- saveDatabase();
638
- } catch (err) {
639
- logger.error('Failed to save database after cleanup: ' + err.message);
640
- }
641
- }
642
-
643
- /**
644
- * Store all metrics at once (called from refresh cycle)
645
- * @param {Object} data - Dashboard data object
646
- */
647
- export function storeMetricsSnapshot(data) {
648
- if (!data) return;
649
-
650
- // Store CPU metrics
651
- if (data.cpu) {
652
- // Handle both array format and object format
653
- const cpuData = Array.isArray(data.cpu) ? { cpus: data.cpu } : data.cpu;
654
- storeCpuMetrics(cpuData);
655
- }
656
-
657
- // Store memory metrics
658
- if (data.memory) {
659
- storeMemoryMetrics(data.memory);
660
- }
661
-
662
- // Store network metrics
663
- if (data.network) {
664
- storeNetworkMetrics(data.network);
665
- }
666
-
667
- // Store session snapshots
668
- if (data.sessions && data.sessions.length > 0) {
669
- for (const session of data.sessions) {
670
- storeSessionSnapshot(session);
671
- }
672
- }
673
- }
674
-
675
- /**
676
- * Extract agent name from session key
677
- * @param {string} key - Session key (e.g., "agent:main:main")
678
- * @returns {string} Agent name
679
- */
680
- function extractAgent(key) {
681
- if (!key) return 'unknown';
682
- const parts = key.split(':');
683
- return parts[1] || parts[0] || 'unknown';
684
- }
685
-
686
- /**
687
- * Close and save the database
688
- */
689
- export function closeDatabase() {
690
- // Clear intervals first
691
- if (saveInterval) {
692
- clearInterval(saveInterval);
693
- saveInterval = null;
694
- }
695
- if (cleanupInterval) {
696
- clearInterval(cleanupInterval);
697
- cleanupInterval = null;
698
- }
699
-
700
- if (db) {
701
- try {
702
- saveDatabase();
703
- } catch (err) {
704
- logger.error('Failed to save database during close: ' + err.message);
705
- }
706
- try {
707
- db.close();
708
- } catch (err) {
709
- logger.error('Failed to close database: ' + err.message);
710
- }
711
- db = null;
712
- logger.info('Database closed');
713
- }
714
- }
715
-
716
- // Export default object
717
- export default {
718
- initDatabase,
719
- closeDatabase,
720
- storeSessionSnapshot,
721
- storeCpuMetrics,
722
- storeMemoryMetrics,
723
- storeNetworkMetrics,
724
- storeMetricsSnapshot,
725
- getSessionsLast24Hours,
726
- getSessionsLast7Days,
727
- getSessionsByHours,
728
- getSessionsByDays,
729
- getCpuMetricsHistory,
730
- getMemoryMetricsHistory,
731
- getNetworkMetricsHistory,
732
- getMetricsSummary,
733
- cleanupOldData
734
- };