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,534 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* View Transitions Module
|
|
3
|
+
* Provides smooth animations for modal views (fade, slide, scale)
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import blessed from 'blessed';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Easing functions for animations
|
|
10
|
+
*/
|
|
11
|
+
const EASING = {
|
|
12
|
+
linear: t => t,
|
|
13
|
+
easeIn: t => t * t,
|
|
14
|
+
easeOut: t => 1 - (1 - t) * (1 - t),
|
|
15
|
+
easeInOut: t => t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2,
|
|
16
|
+
spring: t => {
|
|
17
|
+
const c4 = (2 * Math.PI) / 3;
|
|
18
|
+
return t === 0 ? 0 : t === 1 ? 1 : Math.pow(2, -10 * t) * Math.sin((t * 10 - 0.75) * c4) + 1;
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Default transition options
|
|
24
|
+
*/
|
|
25
|
+
const DEFAULT_OPTIONS = {
|
|
26
|
+
duration: 200, // Animation duration in ms
|
|
27
|
+
easing: 'easeOut', // Easing function name
|
|
28
|
+
fade: true, // Fade opacity
|
|
29
|
+
slide: false, // Slide from direction
|
|
30
|
+
scale: false, // Scale effect
|
|
31
|
+
slideDirection: 'up', // 'up', 'down', 'left', 'right'
|
|
32
|
+
fadeBackground: true // Fade background opacity
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Active animations map (to handle cleanup)
|
|
37
|
+
* @type {Map<string, {stop: Function}>}
|
|
38
|
+
*/
|
|
39
|
+
const activeAnimations = new Map();
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Create a background overlay for modal dialogs
|
|
43
|
+
* @param {blessed.Screen} screen
|
|
44
|
+
* @param {Object} options
|
|
45
|
+
* @returns {blessed.Box}
|
|
46
|
+
*/
|
|
47
|
+
export function createBackground(screen, options = {}) {
|
|
48
|
+
const bg = blessed.box({
|
|
49
|
+
parent: screen,
|
|
50
|
+
top: 0,
|
|
51
|
+
left: 0,
|
|
52
|
+
width: '100%',
|
|
53
|
+
height: '100%',
|
|
54
|
+
style: {
|
|
55
|
+
bg: 'black'
|
|
56
|
+
},
|
|
57
|
+
transparent: true
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// Set initial opacity based on options
|
|
61
|
+
const opacity = options.fadeBackground !== false ? 0 : 0.4;
|
|
62
|
+
bg.style.transparent = true;
|
|
63
|
+
bg._targetOpacity = options.backgroundOpacity || 0.4;
|
|
64
|
+
bg._currentOpacity = opacity;
|
|
65
|
+
|
|
66
|
+
return bg;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Animate a value over time
|
|
71
|
+
* @param {Object} config
|
|
72
|
+
* @param {number} config.from - Start value
|
|
73
|
+
* @param {number} config.to - End value
|
|
74
|
+
* @param {number} config.duration - Duration in ms
|
|
75
|
+
* @param {string} config.easing - Easing function name
|
|
76
|
+
* @param {Function} config.onUpdate - Called with current value
|
|
77
|
+
* @param {Function} config.onComplete - Called when animation completes
|
|
78
|
+
* @returns {{stop: Function}} Animation controller
|
|
79
|
+
*/
|
|
80
|
+
export function animate({
|
|
81
|
+
from,
|
|
82
|
+
to,
|
|
83
|
+
duration = 200,
|
|
84
|
+
easing = 'easeOut',
|
|
85
|
+
onUpdate,
|
|
86
|
+
onComplete
|
|
87
|
+
}) {
|
|
88
|
+
const easeFn = EASING[easing] || EASING.easeOut;
|
|
89
|
+
const startTime = Date.now();
|
|
90
|
+
let animationId = null;
|
|
91
|
+
let stopped = false;
|
|
92
|
+
|
|
93
|
+
const step = () => {
|
|
94
|
+
if (stopped) return;
|
|
95
|
+
|
|
96
|
+
const elapsed = Date.now() - startTime;
|
|
97
|
+
const progress = Math.min(elapsed / duration, 1);
|
|
98
|
+
const easedProgress = easeFn(progress);
|
|
99
|
+
const currentValue = from + (to - from) * easedProgress;
|
|
100
|
+
|
|
101
|
+
onUpdate(currentValue);
|
|
102
|
+
|
|
103
|
+
if (progress < 1) {
|
|
104
|
+
// Use setImmediate for next frame to avoid blocking
|
|
105
|
+
animationId = setImmediate(step);
|
|
106
|
+
} else {
|
|
107
|
+
onComplete?.();
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
// Start animation
|
|
112
|
+
animationId = setImmediate(step);
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
stop: () => {
|
|
116
|
+
stopped = true;
|
|
117
|
+
if (animationId) {
|
|
118
|
+
clearImmediate(animationId);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Transition a widget in (show with animation)
|
|
126
|
+
* @param {blessed.Screen} screen
|
|
127
|
+
* @param {blessed.Box} widget
|
|
128
|
+
* @param {Object} options
|
|
129
|
+
* @returns {Promise<void>}
|
|
130
|
+
*/
|
|
131
|
+
export function transitionIn(screen, widget, options = {}) {
|
|
132
|
+
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
133
|
+
const animationId = `in_${widget.uid || Math.random().toString(36).substr(2, 9)}`;
|
|
134
|
+
|
|
135
|
+
// Stop any existing animation on this widget
|
|
136
|
+
if (activeAnimations.has(animationId)) {
|
|
137
|
+
activeAnimations.get(animationId).stop();
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return new Promise((resolve) => {
|
|
141
|
+
const animations = [];
|
|
142
|
+
const originalTop = widget.top;
|
|
143
|
+
const originalLeft = widget.left;
|
|
144
|
+
const originalWidth = widget.width;
|
|
145
|
+
const originalHeight = widget.height;
|
|
146
|
+
|
|
147
|
+
// Store original position for restore
|
|
148
|
+
widget._originalPosition = {
|
|
149
|
+
top: originalTop,
|
|
150
|
+
left: originalLeft,
|
|
151
|
+
width: originalWidth,
|
|
152
|
+
height: originalHeight
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
// Fade animation
|
|
156
|
+
if (opts.fade) {
|
|
157
|
+
widget.style.transparent = true;
|
|
158
|
+
widget._opacity = 0;
|
|
159
|
+
|
|
160
|
+
const fadeAnim = animate({
|
|
161
|
+
from: 0,
|
|
162
|
+
to: 1,
|
|
163
|
+
duration: opts.duration,
|
|
164
|
+
easing: opts.easing,
|
|
165
|
+
onUpdate: (value) => {
|
|
166
|
+
widget._opacity = value;
|
|
167
|
+
// blessed doesn't support true opacity, so we simulate it via style
|
|
168
|
+
widget.style.alpha = value;
|
|
169
|
+
widget.style.transparent = value < 0.1;
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
animations.push(fadeAnim);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Slide animation
|
|
176
|
+
if (opts.slide) {
|
|
177
|
+
let fromTop = originalTop;
|
|
178
|
+
let fromLeft = originalLeft;
|
|
179
|
+
const slideDistance = 20;
|
|
180
|
+
|
|
181
|
+
switch (opts.slideDirection) {
|
|
182
|
+
case 'up':
|
|
183
|
+
fromTop = originalTop + slideDistance;
|
|
184
|
+
break;
|
|
185
|
+
case 'down':
|
|
186
|
+
fromTop = originalTop - slideDistance;
|
|
187
|
+
break;
|
|
188
|
+
case 'left':
|
|
189
|
+
fromLeft = originalLeft + slideDistance;
|
|
190
|
+
break;
|
|
191
|
+
case 'right':
|
|
192
|
+
fromLeft = originalLeft - slideDistance;
|
|
193
|
+
break;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
widget.top = fromTop;
|
|
197
|
+
widget.left = fromLeft;
|
|
198
|
+
|
|
199
|
+
const slideAnim = animate({
|
|
200
|
+
from: 0,
|
|
201
|
+
to: 1,
|
|
202
|
+
duration: opts.duration,
|
|
203
|
+
easing: opts.easing,
|
|
204
|
+
onUpdate: (value) => {
|
|
205
|
+
widget.top = fromTop + (originalTop - fromTop) * value;
|
|
206
|
+
widget.left = fromLeft + (originalLeft - fromLeft) * value;
|
|
207
|
+
screen.render();
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
animations.push(slideAnim);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Scale animation (simulated via size changes)
|
|
214
|
+
if (opts.scale) {
|
|
215
|
+
const parseDim = (dim) => {
|
|
216
|
+
if (typeof dim === 'string' && dim.includes('%')) {
|
|
217
|
+
return { value: parseInt(dim), unit: '%' };
|
|
218
|
+
}
|
|
219
|
+
return { value: parseInt(dim) || 10, unit: typeof dim === 'string' && dim.includes('%') ? '%' : '' };
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
const origW = parseDim(originalWidth);
|
|
223
|
+
const origH = parseDim(originalHeight);
|
|
224
|
+
|
|
225
|
+
// Start from smaller size
|
|
226
|
+
const startScale = 0.9;
|
|
227
|
+
const currentW = Math.round(origW.value * startScale);
|
|
228
|
+
const currentH = Math.round(origH.value * startScale);
|
|
229
|
+
|
|
230
|
+
widget.width = currentW + origW.unit;
|
|
231
|
+
widget.height = currentH + origH.unit;
|
|
232
|
+
|
|
233
|
+
const scaleAnim = animate({
|
|
234
|
+
from: startScale,
|
|
235
|
+
to: 1,
|
|
236
|
+
duration: opts.duration,
|
|
237
|
+
easing: opts.easing,
|
|
238
|
+
onUpdate: (value) => {
|
|
239
|
+
const newW = Math.round(origW.value * value);
|
|
240
|
+
const newH = Math.round(origH.value * value);
|
|
241
|
+
widget.width = newW + origW.unit;
|
|
242
|
+
widget.height = newH + origH.unit;
|
|
243
|
+
screen.render();
|
|
244
|
+
},
|
|
245
|
+
onComplete: () => {
|
|
246
|
+
widget.width = originalWidth;
|
|
247
|
+
widget.height = originalHeight;
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
animations.push(scaleAnim);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Background fade
|
|
254
|
+
let bgAnim = null;
|
|
255
|
+
if (opts.fadeBackground && opts.background) {
|
|
256
|
+
bgAnim = animate({
|
|
257
|
+
from: 0,
|
|
258
|
+
to: opts.background._targetOpacity || 0.4,
|
|
259
|
+
duration: opts.duration,
|
|
260
|
+
easing: 'linear',
|
|
261
|
+
onUpdate: (value) => {
|
|
262
|
+
// Simulate opacity via character density or just use a flag
|
|
263
|
+
opts.background._currentOpacity = value;
|
|
264
|
+
opts.background.style.alpha = value;
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Cleanup and resolve when done
|
|
270
|
+
setTimeout(() => {
|
|
271
|
+
animations.forEach(a => a.stop());
|
|
272
|
+
if (bgAnim) bgAnim.stop();
|
|
273
|
+
activeAnimations.delete(animationId);
|
|
274
|
+
|
|
275
|
+
// Ensure final state
|
|
276
|
+
widget.top = originalTop;
|
|
277
|
+
widget.left = originalLeft;
|
|
278
|
+
widget.width = originalWidth;
|
|
279
|
+
widget.height = originalHeight;
|
|
280
|
+
widget.style.transparent = false;
|
|
281
|
+
widget.style.alpha = 1;
|
|
282
|
+
|
|
283
|
+
screen.render();
|
|
284
|
+
resolve();
|
|
285
|
+
}, opts.duration);
|
|
286
|
+
|
|
287
|
+
activeAnimations.set(animationId, {
|
|
288
|
+
stop: () => {
|
|
289
|
+
animations.forEach(a => a.stop());
|
|
290
|
+
if (bgAnim) bgAnim.stop();
|
|
291
|
+
activeAnimations.delete(animationId);
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
// Render to show initial state
|
|
296
|
+
screen.render();
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Transition a widget out (hide with animation)
|
|
302
|
+
* @param {blessed.Screen} screen
|
|
303
|
+
* @param {blessed.Box} widget
|
|
304
|
+
* @param {Object} options
|
|
305
|
+
* @returns {Promise<void>}
|
|
306
|
+
*/
|
|
307
|
+
export function transitionOut(screen, widget, options = {}) {
|
|
308
|
+
if (!widget || widget.destroyed) {
|
|
309
|
+
return Promise.resolve();
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
313
|
+
const animationId = `out_${widget.uid || Math.random().toString(36).substr(2, 9)}`;
|
|
314
|
+
|
|
315
|
+
// Stop any existing animation on this widget
|
|
316
|
+
if (activeAnimations.has(animationId)) {
|
|
317
|
+
activeAnimations.get(animationId).stop();
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
return new Promise((resolve) => {
|
|
321
|
+
const animations = [];
|
|
322
|
+
const originalPosition = widget._originalPosition || {
|
|
323
|
+
top: widget.top,
|
|
324
|
+
left: widget.left,
|
|
325
|
+
width: widget.width,
|
|
326
|
+
height: widget.height
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
// Fade animation
|
|
330
|
+
if (opts.fade) {
|
|
331
|
+
const fadeAnim = animate({
|
|
332
|
+
from: 1,
|
|
333
|
+
to: 0,
|
|
334
|
+
duration: opts.duration,
|
|
335
|
+
easing: opts.easing,
|
|
336
|
+
onUpdate: (value) => {
|
|
337
|
+
widget.style.alpha = value;
|
|
338
|
+
widget.style.transparent = value < 0.1;
|
|
339
|
+
screen.render();
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
animations.push(fadeAnim);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Slide animation
|
|
346
|
+
if (opts.slide) {
|
|
347
|
+
let toTop = originalPosition.top;
|
|
348
|
+
let toLeft = originalPosition.left;
|
|
349
|
+
const slideDistance = 20;
|
|
350
|
+
|
|
351
|
+
switch (opts.slideDirection) {
|
|
352
|
+
case 'up':
|
|
353
|
+
toTop = originalPosition.top - slideDistance;
|
|
354
|
+
break;
|
|
355
|
+
case 'down':
|
|
356
|
+
toTop = originalPosition.top + slideDistance;
|
|
357
|
+
break;
|
|
358
|
+
case 'left':
|
|
359
|
+
toLeft = originalPosition.left - slideDistance;
|
|
360
|
+
break;
|
|
361
|
+
case 'right':
|
|
362
|
+
toLeft = originalPosition.left + slideDistance;
|
|
363
|
+
break;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const slideAnim = animate({
|
|
367
|
+
from: 0,
|
|
368
|
+
to: 1,
|
|
369
|
+
duration: opts.duration,
|
|
370
|
+
easing: opts.easing,
|
|
371
|
+
onUpdate: (value) => {
|
|
372
|
+
widget.top = originalPosition.top + (toTop - originalPosition.top) * value;
|
|
373
|
+
widget.left = originalPosition.left + (toLeft - originalPosition.left) * value;
|
|
374
|
+
screen.render();
|
|
375
|
+
}
|
|
376
|
+
});
|
|
377
|
+
animations.push(slideAnim);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// Scale animation
|
|
381
|
+
if (opts.scale) {
|
|
382
|
+
const parseDim = (dim) => {
|
|
383
|
+
if (typeof dim === 'string' && dim.includes('%')) {
|
|
384
|
+
return { value: parseInt(dim), unit: '%' };
|
|
385
|
+
}
|
|
386
|
+
return { value: parseInt(dim) || 10, unit: typeof dim === 'string' && dim.includes('%') ? '%' : '' };
|
|
387
|
+
};
|
|
388
|
+
|
|
389
|
+
const origW = parseDim(originalPosition.width);
|
|
390
|
+
const origH = parseDim(originalPosition.height);
|
|
391
|
+
|
|
392
|
+
const endScale = 0.9;
|
|
393
|
+
|
|
394
|
+
const scaleAnim = animate({
|
|
395
|
+
from: 1,
|
|
396
|
+
to: endScale,
|
|
397
|
+
duration: opts.duration,
|
|
398
|
+
easing: opts.easing,
|
|
399
|
+
onUpdate: (value) => {
|
|
400
|
+
const newW = Math.round(origW.value * value);
|
|
401
|
+
const newH = Math.round(origH.value * value);
|
|
402
|
+
widget.width = newW + origW.unit;
|
|
403
|
+
widget.height = newH + origH.unit;
|
|
404
|
+
screen.render();
|
|
405
|
+
}
|
|
406
|
+
});
|
|
407
|
+
animations.push(scaleAnim);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// Background fade
|
|
411
|
+
let bgAnim = null;
|
|
412
|
+
if (opts.fadeBackground && opts.background) {
|
|
413
|
+
const startOpacity = opts.background._currentOpacity || 0.4;
|
|
414
|
+
bgAnim = animate({
|
|
415
|
+
from: startOpacity,
|
|
416
|
+
to: 0,
|
|
417
|
+
duration: opts.duration,
|
|
418
|
+
easing: 'linear',
|
|
419
|
+
onUpdate: (value) => {
|
|
420
|
+
opts.background._currentOpacity = value;
|
|
421
|
+
opts.background.style.alpha = value;
|
|
422
|
+
screen.render();
|
|
423
|
+
}
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// Cleanup when done
|
|
428
|
+
setTimeout(() => {
|
|
429
|
+
animations.forEach(a => a.stop());
|
|
430
|
+
if (bgAnim) bgAnim.stop();
|
|
431
|
+
activeAnimations.delete(animationId);
|
|
432
|
+
resolve();
|
|
433
|
+
}, opts.duration);
|
|
434
|
+
|
|
435
|
+
activeAnimations.set(animationId, {
|
|
436
|
+
stop: () => {
|
|
437
|
+
animations.forEach(a => a.stop());
|
|
438
|
+
if (bgAnim) bgAnim.stop();
|
|
439
|
+
activeAnimations.delete(animationId);
|
|
440
|
+
}
|
|
441
|
+
});
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* Quick fade in/out utility for simple cases
|
|
447
|
+
* @param {blessed.Screen} screen
|
|
448
|
+
* @param {blessed.Box} widget
|
|
449
|
+
* @param {boolean} show - true to fade in, false to fade out
|
|
450
|
+
* @param {number} duration
|
|
451
|
+
* @returns {Promise<void>}
|
|
452
|
+
*/
|
|
453
|
+
export function quickFade(screen, widget, show, duration = 150) {
|
|
454
|
+
if (!widget || widget.destroyed) return Promise.resolve();
|
|
455
|
+
|
|
456
|
+
return new Promise((resolve) => {
|
|
457
|
+
const from = show ? 0 : 1;
|
|
458
|
+
const to = show ? 1 : 0;
|
|
459
|
+
|
|
460
|
+
animate({
|
|
461
|
+
from,
|
|
462
|
+
to,
|
|
463
|
+
duration,
|
|
464
|
+
easing: 'easeOut',
|
|
465
|
+
onUpdate: (value) => {
|
|
466
|
+
widget.style.alpha = value;
|
|
467
|
+
widget.style.transparent = value < 0.1;
|
|
468
|
+
screen.render();
|
|
469
|
+
},
|
|
470
|
+
onComplete: () => {
|
|
471
|
+
if (!show) {
|
|
472
|
+
widget.hide();
|
|
473
|
+
} else {
|
|
474
|
+
widget.show();
|
|
475
|
+
widget.style.alpha = 1;
|
|
476
|
+
widget.style.transparent = false;
|
|
477
|
+
}
|
|
478
|
+
screen.render();
|
|
479
|
+
resolve();
|
|
480
|
+
}
|
|
481
|
+
});
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* Staggered animation for lists of items
|
|
487
|
+
* @param {blessed.Screen} screen
|
|
488
|
+
* @param {Array<blessed.Box>} items
|
|
489
|
+
* @param {boolean} show
|
|
490
|
+
* @param {Object} options
|
|
491
|
+
* @returns {Promise<void>}
|
|
492
|
+
*/
|
|
493
|
+
export function staggeredFade(screen, items, show, options = {}) {
|
|
494
|
+
const delay = options.staggerDelay || 30;
|
|
495
|
+
const duration = options.duration || 100;
|
|
496
|
+
|
|
497
|
+
const promises = items.map((item, index) => {
|
|
498
|
+
return new Promise((resolve) => {
|
|
499
|
+
setTimeout(() => {
|
|
500
|
+
quickFade(screen, item, show, duration).then(resolve);
|
|
501
|
+
}, index * delay);
|
|
502
|
+
});
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
return Promise.all(promises);
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
/**
|
|
509
|
+
* Check if any animation is currently running
|
|
510
|
+
* @returns {boolean}
|
|
511
|
+
*/
|
|
512
|
+
export function isAnimating() {
|
|
513
|
+
return activeAnimations.size > 0;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/**
|
|
517
|
+
* Stop all active animations
|
|
518
|
+
*/
|
|
519
|
+
export function stopAll() {
|
|
520
|
+
activeAnimations.forEach(anim => anim.stop());
|
|
521
|
+
activeAnimations.clear();
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
export default {
|
|
525
|
+
animate,
|
|
526
|
+
transitionIn,
|
|
527
|
+
transitionOut,
|
|
528
|
+
quickFade,
|
|
529
|
+
staggeredFade,
|
|
530
|
+
createBackground,
|
|
531
|
+
isAnimating,
|
|
532
|
+
stopAll,
|
|
533
|
+
EASING
|
|
534
|
+
};
|
package/src/types.d.ts
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claw Dashboard Type Definitions
|
|
3
|
+
* ProvidesScript-like Type type hints for JavaScript code via JSDoc
|
|
4
|
+
* IDEs like VS Code will use these for autocomplete and type checking
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @typedef {Object} Session
|
|
9
|
+
* @property {string} key - Unique session identifier
|
|
10
|
+
* @property {string} name - Session name
|
|
11
|
+
* @property {string} status - Session status (active, idle, etc.)
|
|
12
|
+
* @property {number} tokens - Current token count
|
|
13
|
+
* @property {number} totalTokens - Total tokens used
|
|
14
|
+
* @property {number} startTime - Session start timestamp
|
|
15
|
+
* @property {number} lastActive - Last activity timestamp
|
|
16
|
+
* @property {string} [agentName] - Associated agent name
|
|
17
|
+
* @property {boolean} [favorite] - Whether session is favorited
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @typedef {Object} CPUData
|
|
22
|
+
* @property {number[]} current - Current CPU usage per core
|
|
23
|
+
* @property {number} avg - Average CPU usage
|
|
24
|
+
* @property {string} model - CPU model name
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @typedef {Object} MemoryData
|
|
29
|
+
* @property {number} total - Total memory in bytes
|
|
30
|
+
* @property {number} used - Used memory in bytes
|
|
31
|
+
* @property {number} free - Free memory in bytes
|
|
32
|
+
* @property {number} percent - Usage percentage
|
|
33
|
+
* @property {string} usedGB - Formatted used memory
|
|
34
|
+
* @property {string} totalGB - Formatted total memory
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @typedef {Object} GPUData
|
|
39
|
+
* @property {string} short - Short GPU info string
|
|
40
|
+
* @property {string} long - Long GPU info string
|
|
41
|
+
* @property {number} [percent] - GPU usage percentage
|
|
42
|
+
* @property {number} [memory] - GPU memory usage
|
|
43
|
+
*/
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* @typedef {Object} NetworkData
|
|
47
|
+
* @property {string} iface - Network interface name
|
|
48
|
+
* @property {number} rx - Receive bytes
|
|
49
|
+
* @property {number} tx - Transmit bytes
|
|
50
|
+
* @property {number} rx_sec - Receive bytes per second
|
|
51
|
+
* @property {number} tx_sec - Transmit bytes per second
|
|
52
|
+
* @property {string} rx_rate - Formatted receive rate
|
|
53
|
+
* @property {string} tx_rate - Formatted transmit rate
|
|
54
|
+
*/
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* @typedef {Object} DiskData
|
|
58
|
+
* @property {string} mount - Mount point
|
|
59
|
+
* @property {string} fs - Filesystem type
|
|
60
|
+
* @property {number} size - Total size in bytes
|
|
61
|
+
* @property {number} used - Used space in bytes
|
|
62
|
+
* @property {number} available - Available space in bytes
|
|
63
|
+
* @property {number} percent - Usage percentage
|
|
64
|
+
* @property {string} usedGB - Formatted used space
|
|
65
|
+
* @property {string} totalGB - Formatted total space
|
|
66
|
+
*/
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* @typedef {Object} SystemData
|
|
70
|
+
* @property {string} os - OS name
|
|
71
|
+
* @property {string} version - OS version
|
|
72
|
+
* @property {string} arch - Architecture
|
|
73
|
+
* @property {string} hostname - Hostname
|
|
74
|
+
*/
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* @typedef {Object} DashboardData
|
|
78
|
+
* @property {CPUData} cpu - CPU data
|
|
79
|
+
* @property {MemoryData} memory - Memory data
|
|
80
|
+
* @property {GPUData} [gpu] - GPU data
|
|
81
|
+
* @property {NetworkData} [network] - Network data
|
|
82
|
+
* @property {DiskData} [disk] - Disk data
|
|
83
|
+
* @property {SystemData} [system] - System data
|
|
84
|
+
* @property {Session[]} sessions - Active sessions
|
|
85
|
+
* @property {Object} sessionTPS - Tokens per second tracking
|
|
86
|
+
* @property {Object} sessionLastTPS - Last TPS values
|
|
87
|
+
* @property {string|null} version - OpenClaw version
|
|
88
|
+
* @property {string|null} latest - Latest available version
|
|
89
|
+
* @property {string|null} openclaw - OpenClaw status
|
|
90
|
+
* @property {number|null} gatewayUptime - Gateway uptime in seconds
|
|
91
|
+
*/
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* @typedef {Object} Settings
|
|
95
|
+
* @property {number} refreshInterval - Refresh interval in ms
|
|
96
|
+
* @property {string} logLevelFilter - Log level filter
|
|
97
|
+
* @property {string} sessionSortMode - Session sort mode
|
|
98
|
+
* @property {boolean} showWidget1 - Show CPU widget
|
|
99
|
+
* @property {boolean} showWidget2 - Show Memory widget
|
|
100
|
+
* @property {boolean} showWidget3 - Show GPU widget
|
|
101
|
+
* @property {boolean} showWidget4 - Show Network widget
|
|
102
|
+
* @property {boolean} showWidget5 - Show Disk widget
|
|
103
|
+
* @property {boolean} showWidget6 - Show System widget
|
|
104
|
+
* @property {boolean} showWidget7 - Show Uptime widget
|
|
105
|
+
* @property {boolean} showWidget8 - Show Data Health widget
|
|
106
|
+
* @property {string} theme - UI theme
|
|
107
|
+
* @property {string} exportFormat - Export format
|
|
108
|
+
* @property {string} exportDirectory - Export directory path
|
|
109
|
+
* @property {string} sessionSearchQuery - Session search query
|
|
110
|
+
* @property {Object.<string, boolean>} favorites - Favorited session IDs
|
|
111
|
+
* @property {boolean} showFavoritesOnly - Show only favorites
|
|
112
|
+
* @property {boolean} firstRun - First run flag
|
|
113
|
+
*/
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* @typedef {'warning'|'critical'|'cleared'} AlertLevel
|
|
117
|
+
*/
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* @typedef {Object} Alert
|
|
121
|
+
* @property {string} id - Unique alert ID
|
|
122
|
+
* @property {AlertLevel} level - Alert level
|
|
123
|
+
* @property {string} metric - Metric type (cpu, memory, disk)
|
|
124
|
+
* @property {number} value - Current value
|
|
125
|
+
* @property {number} threshold - Threshold that was crossed
|
|
126
|
+
* @property {number} timestamp - Alert timestamp
|
|
127
|
+
* @property {boolean} dismissed - Whether alert is dismissed
|
|
128
|
+
*/
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* @typedef {Object} RetryOptions
|
|
132
|
+
* @property {number} maxRetries - Maximum retry attempts
|
|
133
|
+
* @property {number} initialDelay - Initial delay in ms
|
|
134
|
+
* @property {number} maxDelay - Maximum delay in ms
|
|
135
|
+
* @property {number} backoffMultiplier - Backoff multiplier
|
|
136
|
+
* @property {number[]} retryableStatuses - HTTP statuses to retry
|
|
137
|
+
* @property {string[]} retryableErrors - Error codes to retry
|
|
138
|
+
*/
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* @typedef {'default'|'dark'|'high-contrast'|'ocean'|'auto'} ThemeName
|
|
142
|
+
*/
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* @typedef {'time'|'tokens'|'idle'|'name'} SortMode
|
|
146
|
+
*/
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* @typedef {'all'|'error'|'warn'|'info'|'debug'} LogLevel
|
|
150
|
+
*/
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* @typedef {'json'|'csv'} ExportFormat
|
|
154
|
+
*/
|
|
155
|
+
|
|
156
|
+
// Export type constants for use in code
|
|
157
|
+
/** @type {ThemeName} */
|
|
158
|
+
export const VALID_THEMES = ['default', 'dark', 'high-contrast', 'ocean', 'auto'];
|
|
159
|
+
|
|
160
|
+
/** @type {SortMode} */
|
|
161
|
+
export const VALID_SORT_MODES = ['time', 'tokens', 'idle', 'name'];
|
|
162
|
+
|
|
163
|
+
/** @type {LogLevel} */
|
|
164
|
+
export const VALID_LOG_LEVELS = ['all', 'error', 'warn', 'info', 'debug'];
|
|
165
|
+
|
|
166
|
+
/** @type {ExportFormat} */
|
|
167
|
+
export const VALID_EXPORT_FORMATS = ['json', 'csv'];
|
|
168
|
+
|
|
169
|
+
export default {
|
|
170
|
+
VALID_THEMES,
|
|
171
|
+
VALID_SORT_MODES,
|
|
172
|
+
VALID_LOG_LEVELS,
|
|
173
|
+
VALID_EXPORT_FORMATS
|
|
174
|
+
};
|