claw-dashboard 1.8.4 → 2.0.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.
- package/.c8rc.json +38 -0
- package/.dockerignore +68 -0
- package/.github/dependabot.yml +45 -0
- package/.github/pull_request_template.md +39 -0
- package/.github/workflows/ci.yml +147 -0
- package/.github/workflows/release.yml +40 -0
- package/.github/workflows/security.yml +84 -0
- package/.husky/pre-commit +1 -0
- package/.planning/codebase/ARCHITECTURE.md +206 -0
- package/.planning/codebase/INTEGRATIONS.md +150 -0
- package/.planning/codebase/STACK.md +122 -0
- package/.planning/codebase/STRUCTURE.md +201 -0
- package/CHANGELOG.md +293 -0
- package/CONTRIBUTING.md +378 -0
- package/Dockerfile +54 -0
- package/FEATURES.md +195 -21
- package/README.md +83 -6
- package/TODO.md +28 -0
- package/build-cjs.js +127 -0
- package/cjs-shim.js +13 -0
- package/dist/clawdash +1729 -0
- package/dist/clawdash.meta.json +2236 -0
- package/dist/widgets.cjs +6511 -0
- package/docker-compose.yml +67 -0
- package/docs/API.md +1050 -0
- package/docs/PLUGINS.md +1504 -0
- package/esbuild.config.js +158 -0
- package/eslint.config.js +56 -0
- package/examples/plugins/README.md +122 -0
- package/examples/plugins/api-status/index.js +294 -0
- package/examples/plugins/api-status/plugin.json +19 -0
- package/examples/plugins/hello-world/index.js +104 -0
- package/examples/plugins/hello-world/plugin.json +15 -0
- package/examples/plugins/system-metrics-chart/index.js +339 -0
- package/examples/plugins/system-metrics-chart/plugin.json +18 -0
- package/examples/plugins/weather-widget/index.js +136 -0
- package/examples/plugins/weather-widget/plugin.json +19 -0
- package/index.cjs +23005 -0
- package/index.js +5331 -512
- package/jest.config.js +11 -0
- package/man/clawdash.1 +276 -0
- package/package.json +54 -5
- package/schemas/plugin-manifest.json +128 -0
- package/scripts/release.js +595 -0
- package/src/alerts.js +693 -0
- package/src/auto-save.js +584 -0
- package/src/cache.js +390 -0
- package/src/checksum.js +146 -0
- package/src/cli/args.js +110 -0
- package/src/cli/export-schedule.js +423 -0
- package/src/cli/export-snapshot.js +138 -0
- package/src/cli/help.js +69 -0
- package/src/cli/import-snapshot.js +230 -0
- package/src/cli/index.js +14 -0
- package/src/cli/list-templates.js +69 -0
- package/src/cli/validate-config.js +76 -0
- package/src/cli/validate-plugin.js +187 -0
- package/src/cli/version.js +15 -0
- package/src/config-validator.js +586 -0
- package/src/config-watcher.js +401 -0
- package/src/config.js +684 -0
- package/src/container-detector.js +499 -0
- package/src/database.js +734 -0
- package/src/differential-render.js +327 -0
- package/src/errors.js +169 -0
- package/src/export-scheduler.js +581 -0
- package/src/gateway-manager.js +685 -0
- package/src/hints.js +371 -0
- package/src/loading-states.js +509 -0
- package/src/logger.js +185 -0
- package/src/memory-pressure.js +467 -0
- package/src/performance-monitor.js +374 -0
- package/src/plugin-errors.js +586 -0
- package/src/plugin-manifest-validator.js +219 -0
- package/src/plugin-reload.js +481 -0
- package/src/plugin-scaffold.js +1941 -0
- package/src/plugin-validator.js +284 -0
- package/src/retry.js +218 -0
- package/src/security.js +860 -0
- package/src/snapshot.js +372 -0
- package/src/splash.js +115 -0
- package/src/theme-selector.js +411 -0
- package/src/themes.js +874 -0
- package/src/transitions.js +534 -0
- package/src/types.d.ts +174 -0
- package/src/validation.js +926 -0
- package/src/web-server.js +885 -0
- package/src/widgets/builtin-widgets.js +1057 -0
- package/src/widgets/config-processor.js +401 -0
- package/src/widgets/dependency-resolver.js +596 -0
- package/src/widgets/index.js +79 -0
- package/src/widgets/plugin-api.js +845 -0
- package/src/widgets/widget-error-boundary.js +773 -0
- package/src/widgets/widget-error-isolation.js +551 -0
- package/src/widgets/widget-loader.js +1437 -0
- package/src/workers/system-worker.js +130 -0
- package/src/workers/worker-pool.js +633 -0
- package/tests/alerts.test.js +437 -0
- package/tests/auto-save.test.js +529 -0
- package/tests/cache.test.js +317 -0
- package/tests/cli.test.js +351 -0
- package/tests/command-palette.test.js +320 -0
- package/tests/config-processor.test.js +452 -0
- package/tests/config-validator.test.js +710 -0
- package/tests/config-watcher.test.js +594 -0
- package/tests/config.test.js +755 -0
- package/tests/database.test.js +438 -0
- package/tests/errors.test.js +189 -0
- package/tests/example-plugins.test.js +624 -0
- package/tests/integration.test.js +1321 -0
- package/tests/loading-states.test.js +300 -0
- package/tests/manifest-validation-on-load.test.js +671 -0
- package/tests/performance-monitor.test.js +190 -0
- package/tests/plugin-api-rate-limit.test.js +302 -0
- package/tests/plugin-errors.test.js +311 -0
- package/tests/plugin-lifecycle-e2e.test.js +1036 -0
- package/tests/plugin-reload.test.js +764 -0
- package/tests/rate-limiter.test.js +539 -0
- package/tests/retry.test.js +308 -0
- package/tests/security.test.js +411 -0
- package/tests/settings-modal.test.js +226 -0
- package/tests/theme-selector.test.js +57 -0
- package/tests/utils.js +242 -0
- package/tests/utils.test.js +317 -0
- package/tests/validate-plugin-cli.test.js +359 -0
- package/tests/validation.test.js +837 -0
- package/tests/web-server.test.js +646 -0
- package/tests/widget-arrange-mode.test.js +183 -0
- package/tests/widget-config-hot-reload.test.js +465 -0
- package/tests/widget-dependency.test.js +591 -0
- package/tests/widget-error-boundary.test.js +749 -0
- package/tests/widget-error-isolation.test.js +469 -0
- package/tests/widget-integration.test.js +1147 -0
- package/tests/widget-refresh-intervals.test.js +284 -0
- package/tests/worker-pool.test.js +522 -0
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hello World Widget Plugin
|
|
3
|
+
* The simplest possible widget example for Claw Dashboard
|
|
4
|
+
*
|
|
5
|
+
* This example demonstrates:
|
|
6
|
+
* - Basic widget structure extending BaseWidget
|
|
7
|
+
* - Required lifecycle methods (init, create, getData, render, destroy)
|
|
8
|
+
* - Simple configuration handling
|
|
9
|
+
* - Basic blessed UI creation
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { BaseWidget } from '../../../src/widgets/plugin-api.js';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* HelloWidget - A minimal widget that displays a greeting
|
|
16
|
+
*/
|
|
17
|
+
export default class HelloWidget extends BaseWidget {
|
|
18
|
+
constructor(options = {}) {
|
|
19
|
+
super(options);
|
|
20
|
+
this.name = options.name || 'Hello World';
|
|
21
|
+
this.description = 'Simple greeting widget';
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Initialize the widget
|
|
26
|
+
* Called once when the widget is first loaded
|
|
27
|
+
*/
|
|
28
|
+
async init() {
|
|
29
|
+
this.log('info', 'Hello World widget initialized');
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Create the widget UI
|
|
35
|
+
* @param {Object} screen - Blessed screen object
|
|
36
|
+
* @param {Object} theme - Theme colors
|
|
37
|
+
*/
|
|
38
|
+
async create(screen, theme = {}) {
|
|
39
|
+
const C = theme.colors || {};
|
|
40
|
+
const blessed = await import('blessed');
|
|
41
|
+
|
|
42
|
+
// Create a simple box with a border
|
|
43
|
+
this.box = blessed.default.box({
|
|
44
|
+
parent: screen,
|
|
45
|
+
width: '50%',
|
|
46
|
+
height: 5,
|
|
47
|
+
border: { type: 'line' },
|
|
48
|
+
label: ' HELLO ',
|
|
49
|
+
style: { border: { fg: C.green || 'green' } },
|
|
50
|
+
align: 'center',
|
|
51
|
+
valign: 'middle',
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
this.loaded = true;
|
|
55
|
+
this.log('debug', 'Hello World widget UI created');
|
|
56
|
+
|
|
57
|
+
return this;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Get data for the widget
|
|
62
|
+
* Returns the message to display
|
|
63
|
+
*/
|
|
64
|
+
async getData() {
|
|
65
|
+
const message = this.config.message || 'Hello, World!';
|
|
66
|
+
const showTimestamp = this.config.showTimestamp !== false;
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
message,
|
|
70
|
+
timestamp: showTimestamp ? new Date().toISOString() : null,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Render the widget with data
|
|
76
|
+
* @param {Object} data - Data from getData()
|
|
77
|
+
*/
|
|
78
|
+
render(data) {
|
|
79
|
+
if (!this.box) return;
|
|
80
|
+
|
|
81
|
+
let content = data.message;
|
|
82
|
+
if (data.timestamp) {
|
|
83
|
+
content += `\n[${data.timestamp}]`;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
this.box.setContent(content);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Destroy the widget
|
|
91
|
+
* Clean up resources
|
|
92
|
+
*/
|
|
93
|
+
async destroy() {
|
|
94
|
+
if (this.box) {
|
|
95
|
+
this.box.destroy();
|
|
96
|
+
this.box = null;
|
|
97
|
+
}
|
|
98
|
+
this.loaded = false;
|
|
99
|
+
this.log('info', 'Hello World widget destroyed');
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Export named export for flexibility
|
|
104
|
+
export { HelloWidget };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "example-hello-world",
|
|
3
|
+
"name": "Hello World",
|
|
4
|
+
"description": "Simplest possible widget example - displays a greeting message",
|
|
5
|
+
"version": "1.0.0",
|
|
6
|
+
"author": "Claw Dashboard Team",
|
|
7
|
+
"category": "example",
|
|
8
|
+
"type": "widget",
|
|
9
|
+
"lazyLoad": true,
|
|
10
|
+
"priority": 100,
|
|
11
|
+
"config": {
|
|
12
|
+
"message": "Hello, World!",
|
|
13
|
+
"showTimestamp": false
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* System Metrics Chart Widget Plugin
|
|
3
|
+
* Demonstrates data visualization capabilities using blessed-contrib line charts
|
|
4
|
+
*
|
|
5
|
+
* This example shows:
|
|
6
|
+
* - Creating line charts with blessed-contrib
|
|
7
|
+
* - Managing time-series data with configurable history
|
|
8
|
+
* - Multiple metric support (CPU, memory, network)
|
|
9
|
+
* - Dynamic data updates with smooth rendering
|
|
10
|
+
* - Theme integration for consistent styling
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { BaseWidget } from '../../../src/widgets/plugin-api.js';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* SystemMetricsChartWidget - Displays real-time system metrics as line charts
|
|
17
|
+
*
|
|
18
|
+
* Uses blessed-contrib line charts to visualize:
|
|
19
|
+
* - CPU usage percentage over time
|
|
20
|
+
* - Memory usage percentage over time
|
|
21
|
+
* - Network I/O rates over time
|
|
22
|
+
*/
|
|
23
|
+
export default class SystemMetricsChartWidget extends BaseWidget {
|
|
24
|
+
constructor(options = {}) {
|
|
25
|
+
super(options);
|
|
26
|
+
this.name = options.name || 'System Metrics Chart';
|
|
27
|
+
this.description = 'Real-time system metrics visualization with line charts';
|
|
28
|
+
|
|
29
|
+
// Chart configuration
|
|
30
|
+
this.metricType = this.config.metricType || 'cpu';
|
|
31
|
+
this.maxDataPoints = this.config.maxDataPoints || 30;
|
|
32
|
+
this.refreshInterval = this.config.refreshInterval || 2000;
|
|
33
|
+
this.showLegend = this.config.showLegend !== false;
|
|
34
|
+
this.chartHeight = this.config.chartHeight || 15;
|
|
35
|
+
|
|
36
|
+
// Data storage for time series
|
|
37
|
+
this.dataHistory = {
|
|
38
|
+
cpu: { labels: [], values: [] },
|
|
39
|
+
memory: { labels: [], values: [] },
|
|
40
|
+
network: { labels: [], values: [] },
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// Widget state
|
|
44
|
+
this.chart = null;
|
|
45
|
+
this.refreshTimer = null;
|
|
46
|
+
this.initialized = false;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Initialize the widget
|
|
51
|
+
* Called once when the widget is first loaded
|
|
52
|
+
*/
|
|
53
|
+
async init() {
|
|
54
|
+
this.log('info', `System Metrics Chart initialized (metric: ${this.metricType})`);
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Create the widget UI with blessed-contrib line chart
|
|
60
|
+
* @param {Object} screen - Blessed screen object
|
|
61
|
+
* @param {Object} theme - Theme colors
|
|
62
|
+
*/
|
|
63
|
+
async create(screen, theme = {}) {
|
|
64
|
+
const C = theme.colors || {};
|
|
65
|
+
const blessed = await import('blessed');
|
|
66
|
+
const contrib = await import('blessed-contrib');
|
|
67
|
+
|
|
68
|
+
this.screen = screen;
|
|
69
|
+
this.theme = theme;
|
|
70
|
+
|
|
71
|
+
// Create main container box
|
|
72
|
+
this.box = blessed.default.box({
|
|
73
|
+
parent: screen,
|
|
74
|
+
width: '70%',
|
|
75
|
+
height: this.chartHeight + 2,
|
|
76
|
+
border: { type: 'line' },
|
|
77
|
+
label: ` SYSTEM METRICS (${this.metricType.toUpperCase()}) `,
|
|
78
|
+
style: {
|
|
79
|
+
border: { fg: C.cyan || 'cyan' },
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// Create the line chart using blessed-contrib
|
|
84
|
+
this.chart = contrib.default.line({
|
|
85
|
+
parent: this.box,
|
|
86
|
+
top: 1,
|
|
87
|
+
left: 1,
|
|
88
|
+
width: '95%',
|
|
89
|
+
height: this.chartHeight - 2,
|
|
90
|
+
style: {
|
|
91
|
+
line: C.green || 'green',
|
|
92
|
+
text: C.white || 'white',
|
|
93
|
+
baseline: C.gray || 'gray',
|
|
94
|
+
},
|
|
95
|
+
xLabelPadding: 3,
|
|
96
|
+
xPadding: 5,
|
|
97
|
+
numYLabels: 5,
|
|
98
|
+
showNthLabel: Math.ceil(this.maxDataPoints / 6),
|
|
99
|
+
showLegend: this.showLegend,
|
|
100
|
+
legend: {
|
|
101
|
+
width: 12,
|
|
102
|
+
},
|
|
103
|
+
minY: 0,
|
|
104
|
+
maxY: 100,
|
|
105
|
+
wholeNumbersOnly: true,
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// Add metric info text
|
|
109
|
+
this.infoText = blessed.default.text({
|
|
110
|
+
parent: this.box,
|
|
111
|
+
bottom: 0,
|
|
112
|
+
left: 1,
|
|
113
|
+
content: 'Initializing...',
|
|
114
|
+
style: {
|
|
115
|
+
fg: C.gray || 'gray',
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
this.loaded = true;
|
|
120
|
+
this.initialized = true;
|
|
121
|
+
this.log('debug', 'System Metrics Chart UI created');
|
|
122
|
+
|
|
123
|
+
// Start auto-refresh
|
|
124
|
+
this.startAutoRefresh();
|
|
125
|
+
|
|
126
|
+
return this;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Generate sample metrics data
|
|
131
|
+
* In a real implementation, this would fetch from system APIs
|
|
132
|
+
*/
|
|
133
|
+
async getData() {
|
|
134
|
+
const now = new Date();
|
|
135
|
+
const timeLabel = `${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}:${now.getSeconds().toString().padStart(2, '0')}`;
|
|
136
|
+
|
|
137
|
+
// Generate realistic-looking data based on metric type
|
|
138
|
+
let value;
|
|
139
|
+
let unit;
|
|
140
|
+
|
|
141
|
+
switch (this.metricType) {
|
|
142
|
+
case 'cpu': {
|
|
143
|
+
// Simulate CPU percentage with some randomness but realistic patterns
|
|
144
|
+
const baseLoad = 30;
|
|
145
|
+
const variance = Math.random() * 40;
|
|
146
|
+
const spike = Math.random() > 0.9 ? 20 : 0; // Occasional spike
|
|
147
|
+
value = Math.min(100, Math.max(0, Math.floor(baseLoad + variance + spike)));
|
|
148
|
+
unit = '%';
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
case 'memory': {
|
|
152
|
+
// Simulate memory percentage (trending data)
|
|
153
|
+
const baseMem = 50;
|
|
154
|
+
const memVariance = Math.random() * 20;
|
|
155
|
+
value = Math.min(100, Math.max(0, Math.floor(baseMem + memVariance)));
|
|
156
|
+
unit = '%';
|
|
157
|
+
break;
|
|
158
|
+
}
|
|
159
|
+
case 'network': {
|
|
160
|
+
// Simulate network rate (0-50 MB/s)
|
|
161
|
+
value = Math.floor(Math.random() * 50);
|
|
162
|
+
unit = ' MB/s';
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
default: {
|
|
166
|
+
value = Math.floor(Math.random() * 100);
|
|
167
|
+
unit = '';
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Store in history
|
|
172
|
+
const history = this.dataHistory[this.metricType];
|
|
173
|
+
history.labels.push(timeLabel);
|
|
174
|
+
history.values.push(value);
|
|
175
|
+
|
|
176
|
+
// Trim to max data points
|
|
177
|
+
if (history.labels.length > this.maxDataPoints) {
|
|
178
|
+
history.labels.shift();
|
|
179
|
+
history.values.shift();
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return {
|
|
183
|
+
metricType: this.metricType,
|
|
184
|
+
currentValue: value,
|
|
185
|
+
unit,
|
|
186
|
+
timestamp: now.toISOString(),
|
|
187
|
+
labels: [...history.labels],
|
|
188
|
+
values: [...history.values],
|
|
189
|
+
dataPoints: history.values.length,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Render the chart with data
|
|
195
|
+
* @param {Object} data - Metrics data from getData()
|
|
196
|
+
*/
|
|
197
|
+
render(data) {
|
|
198
|
+
if (!this.chart || !data) return;
|
|
199
|
+
|
|
200
|
+
// Prepare chart data format for blessed-contrib
|
|
201
|
+
const chartData = {
|
|
202
|
+
title: `${this.metricType.toUpperCase()}`,
|
|
203
|
+
x: data.labels,
|
|
204
|
+
y: data.values,
|
|
205
|
+
style: this.getMetricStyle(this.metricType),
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
// Update the chart
|
|
209
|
+
this.chart.setData([chartData]);
|
|
210
|
+
|
|
211
|
+
// Update info text
|
|
212
|
+
const avg = data.values.length > 0
|
|
213
|
+
? Math.floor(data.values.reduce((a, b) => a + b, 0) / data.values.length)
|
|
214
|
+
: 0;
|
|
215
|
+
const current = data.currentValue;
|
|
216
|
+
const max = data.values.length > 0 ? Math.max(...data.values) : 0;
|
|
217
|
+
|
|
218
|
+
this.infoText.setContent(
|
|
219
|
+
`Current: ${current}${data.unit} | Average: ${avg}${data.unit} | Peak: ${max}${data.unit} | Points: ${data.dataPoints}/${this.maxDataPoints}`
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Get style configuration for different metric types
|
|
225
|
+
* @param {string} metricType - Type of metric
|
|
226
|
+
* @returns {Object} Style configuration
|
|
227
|
+
*/
|
|
228
|
+
getMetricStyle(metricType) {
|
|
229
|
+
const C = this.theme?.colors || {};
|
|
230
|
+
|
|
231
|
+
const styles = {
|
|
232
|
+
cpu: {
|
|
233
|
+
line: C.green || 'green',
|
|
234
|
+
text: C.brightGreen || 'bright-green',
|
|
235
|
+
},
|
|
236
|
+
memory: {
|
|
237
|
+
line: C.yellow || 'yellow',
|
|
238
|
+
text: C.brightYellow || 'bright-yellow',
|
|
239
|
+
},
|
|
240
|
+
network: {
|
|
241
|
+
line: C.cyan || 'cyan',
|
|
242
|
+
text: C.brightCyan || 'bright-cyan',
|
|
243
|
+
},
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
return styles[metricType] || styles.cpu;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Switch to display a different metric type
|
|
251
|
+
* @param {string} metricType - Metric type ('cpu', 'memory', 'network')
|
|
252
|
+
*/
|
|
253
|
+
switchMetric(metricType) {
|
|
254
|
+
if (!['cpu', 'memory', 'network'].includes(metricType)) {
|
|
255
|
+
this.log('warn', `Invalid metric type: ${metricType}`);
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
this.metricType = metricType;
|
|
260
|
+
|
|
261
|
+
// Update label
|
|
262
|
+
if (this.box) {
|
|
263
|
+
this.box.setLabel(` SYSTEM METRICS (${metricType.toUpperCase()}) `);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Adjust chart scale for network (different range)
|
|
267
|
+
if (this.chart && metricType === 'network') {
|
|
268
|
+
this.chart.options.maxY = 50; // MB/s
|
|
269
|
+
} else if (this.chart) {
|
|
270
|
+
this.chart.options.maxY = 100; // percentage
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
this.log('info', `Switched to ${metricType} metric display`);
|
|
274
|
+
|
|
275
|
+
// Trigger immediate refresh
|
|
276
|
+
this.getData().then(data => this.render(data));
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Start auto-refresh timer
|
|
281
|
+
*/
|
|
282
|
+
startAutoRefresh() {
|
|
283
|
+
this.stopAutoRefresh();
|
|
284
|
+
|
|
285
|
+
if (this.refreshInterval > 0) {
|
|
286
|
+
this.refreshTimer = setInterval(async () => {
|
|
287
|
+
try {
|
|
288
|
+
const data = await this.getData();
|
|
289
|
+
this.render(data);
|
|
290
|
+
} catch (err) {
|
|
291
|
+
this.log('error', `Auto-refresh failed: ${err.message}`);
|
|
292
|
+
}
|
|
293
|
+
}, this.refreshInterval);
|
|
294
|
+
|
|
295
|
+
this.log('debug', `Auto-refresh started (${this.refreshInterval}ms)`);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Stop auto-refresh timer
|
|
301
|
+
*/
|
|
302
|
+
stopAutoRefresh() {
|
|
303
|
+
if (this.refreshTimer) {
|
|
304
|
+
clearInterval(this.refreshTimer);
|
|
305
|
+
this.refreshTimer = null;
|
|
306
|
+
this.log('debug', 'Auto-refresh stopped');
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Destroy the widget and clean up resources
|
|
312
|
+
*/
|
|
313
|
+
async destroy() {
|
|
314
|
+
this.stopAutoRefresh();
|
|
315
|
+
|
|
316
|
+
// Clear data history
|
|
317
|
+
this.dataHistory = {
|
|
318
|
+
cpu: { labels: [], values: [] },
|
|
319
|
+
memory: { labels: [], values: [] },
|
|
320
|
+
network: { labels: [], values: [] },
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
if (this.chart) {
|
|
324
|
+
this.chart = null;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (this.box) {
|
|
328
|
+
this.box.destroy();
|
|
329
|
+
this.box = null;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
this.loaded = false;
|
|
333
|
+
this.initialized = false;
|
|
334
|
+
this.log('info', 'System Metrics Chart widget destroyed');
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Export named export for flexibility
|
|
339
|
+
export { SystemMetricsChartWidget };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "example-system-metrics-chart",
|
|
3
|
+
"name": "System Metrics Chart",
|
|
4
|
+
"description": "Example widget demonstrating data visualization with blessed-contrib line charts for CPU, memory, and network metrics",
|
|
5
|
+
"version": "1.0.0",
|
|
6
|
+
"author": "Claw Dashboard Team",
|
|
7
|
+
"category": "example",
|
|
8
|
+
"type": "widget",
|
|
9
|
+
"lazyLoad": true,
|
|
10
|
+
"priority": 100,
|
|
11
|
+
"config": {
|
|
12
|
+
"metricType": "cpu",
|
|
13
|
+
"maxDataPoints": 30,
|
|
14
|
+
"refreshInterval": 2000,
|
|
15
|
+
"showLegend": true,
|
|
16
|
+
"chartHeight": 15
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Example Weather Widget Plugin
|
|
3
|
+
* Demonstrates how to create a custom widget for Claw Dashboard
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { BaseWidget } from '../../../src/widgets/plugin-api.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Weather Widget - Displays simulated weather data
|
|
10
|
+
* In a real implementation, this would fetch from a weather API
|
|
11
|
+
*/
|
|
12
|
+
export default class WeatherWidget extends BaseWidget {
|
|
13
|
+
constructor(options = {}) {
|
|
14
|
+
super(options);
|
|
15
|
+
this.name = options.name || 'Weather';
|
|
16
|
+
this.description = 'Current weather conditions';
|
|
17
|
+
this.weatherData = null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Initialize the widget
|
|
22
|
+
*/
|
|
23
|
+
async init() {
|
|
24
|
+
this.log('info', 'Weather widget initialized');
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Create the widget UI
|
|
30
|
+
* @param {Object} screen - Blessed screen object
|
|
31
|
+
* @param {Object} theme - Theme colors
|
|
32
|
+
*/
|
|
33
|
+
async create(screen, theme = {}) {
|
|
34
|
+
const C = theme.colors || {};
|
|
35
|
+
const blessed = await import('blessed');
|
|
36
|
+
|
|
37
|
+
this.box = blessed.default.box({
|
|
38
|
+
parent: screen,
|
|
39
|
+
height: 5,
|
|
40
|
+
border: { type: 'line' },
|
|
41
|
+
label: ' WEATHER ',
|
|
42
|
+
style: { border: { fg: C.brightCyan || 'bright-cyan' } },
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
this.locationText = blessed.default.text({
|
|
46
|
+
parent: this.box,
|
|
47
|
+
top: 0,
|
|
48
|
+
left: 'center',
|
|
49
|
+
content: this.config.location || 'Unknown',
|
|
50
|
+
style: { fg: C.brightCyan || 'bright-cyan', bold: true },
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
this.tempText = blessed.default.text({
|
|
54
|
+
parent: this.box,
|
|
55
|
+
top: 1,
|
|
56
|
+
left: 'center',
|
|
57
|
+
content: '--°C',
|
|
58
|
+
style: { fg: C.white || 'white' },
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
this.conditionText = blessed.default.text({
|
|
62
|
+
parent: this.box,
|
|
63
|
+
top: 2,
|
|
64
|
+
left: 'center',
|
|
65
|
+
content: 'Loading...',
|
|
66
|
+
style: { fg: C.gray || 'gray' },
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
this.loaded = true;
|
|
70
|
+
this.log('debug', 'Weather widget UI created');
|
|
71
|
+
|
|
72
|
+
return this;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Get weather data
|
|
77
|
+
* In a real implementation, this would call a weather API
|
|
78
|
+
*/
|
|
79
|
+
async getData() {
|
|
80
|
+
// Simulate API call with random weather data
|
|
81
|
+
const conditions = ['Sunny', 'Cloudy', 'Rainy', 'Snowy', 'Windy'];
|
|
82
|
+
const randomCondition = conditions[Math.floor(Math.random() * conditions.length)];
|
|
83
|
+
const randomTemp = Math.floor(Math.random() * 30) - 5; // -5 to 25°C
|
|
84
|
+
|
|
85
|
+
// Simulate network delay
|
|
86
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
location: this.config.location || 'Local',
|
|
90
|
+
temperature: randomTemp,
|
|
91
|
+
condition: randomCondition,
|
|
92
|
+
humidity: Math.floor(Math.random() * 40) + 40, // 40-80%
|
|
93
|
+
windSpeed: Math.floor(Math.random() * 20) + 5, // 5-25 km/h
|
|
94
|
+
timestamp: new Date().toISOString(),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Update the display
|
|
100
|
+
* @param {Object} data - Weather data
|
|
101
|
+
*/
|
|
102
|
+
update(data) {
|
|
103
|
+
if (!this.box || !data) return;
|
|
104
|
+
|
|
105
|
+
this.weatherData = data;
|
|
106
|
+
|
|
107
|
+
this.locationText.setContent(data.location);
|
|
108
|
+
this.tempText.setContent(`${data.temperature}°C`);
|
|
109
|
+
this.conditionText.setContent(
|
|
110
|
+
`${data.condition} • ${data.humidity}% humidity`
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Render the widget
|
|
116
|
+
* @param {Object} data - Weather data from getData()
|
|
117
|
+
*/
|
|
118
|
+
render(data) {
|
|
119
|
+
this.update(data);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Destroy the widget
|
|
124
|
+
*/
|
|
125
|
+
async destroy() {
|
|
126
|
+
if (this.box) {
|
|
127
|
+
this.box.destroy();
|
|
128
|
+
this.box = null;
|
|
129
|
+
}
|
|
130
|
+
this.loaded = false;
|
|
131
|
+
this.log('info', 'Weather widget destroyed');
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Also export as named export for flexibility
|
|
136
|
+
export { WeatherWidget };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "example-weather",
|
|
3
|
+
"name": "Weather",
|
|
4
|
+
"description": "Example weather widget showing current conditions (demonstrates env var interpolation)",
|
|
5
|
+
"version": "1.0.0",
|
|
6
|
+
"author": "Claw Dashboard Team",
|
|
7
|
+
"category": "example",
|
|
8
|
+
"type": "widget",
|
|
9
|
+
"lazyLoad": true,
|
|
10
|
+
"priority": 100,
|
|
11
|
+
"config": {
|
|
12
|
+
"__version": "1.0.0",
|
|
13
|
+
"apiKey": "${WEATHER_API_KEY:-demo-key}",
|
|
14
|
+
"location": "${WEATHER_LOCATION:-New York}",
|
|
15
|
+
"unit": "${WEATHER_UNIT:-celsius}",
|
|
16
|
+
"refreshInterval": 300000,
|
|
17
|
+
"baseUrl": "${WEATHER_API_URL:-https://api.weather.example.com}"
|
|
18
|
+
}
|
|
19
|
+
}
|