claw-dashboard 2.0.0 → 2.1.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/.github/workflows/ci.yml +10 -10
- package/.github/workflows/release.yml +2 -2
- package/.github/workflows/security.yml +7 -7
- package/index.js +53 -4
- package/package.json +3 -4
- package/src/plugin-scaffold.js +64 -71
- package/src/widgets/builtin-widgets.js +0 -1
package/.github/workflows/ci.yml
CHANGED
|
@@ -25,10 +25,10 @@ jobs:
|
|
|
25
25
|
|
|
26
26
|
steps:
|
|
27
27
|
- name: Checkout code
|
|
28
|
-
uses: actions/checkout@
|
|
28
|
+
uses: actions/checkout@v6
|
|
29
29
|
|
|
30
30
|
- name: Setup Node.js ${{ matrix.node-version }}
|
|
31
|
-
uses: actions/setup-node@
|
|
31
|
+
uses: actions/setup-node@v6
|
|
32
32
|
with:
|
|
33
33
|
node-version: ${{ matrix.node-version }}
|
|
34
34
|
cache: 'npm'
|
|
@@ -48,7 +48,7 @@ jobs:
|
|
|
48
48
|
|
|
49
49
|
- name: Upload coverage to Codecov
|
|
50
50
|
if: matrix.node-version == 20 && github.event_name != 'pull_request'
|
|
51
|
-
uses: codecov/codecov-action@
|
|
51
|
+
uses: codecov/codecov-action@v5
|
|
52
52
|
with:
|
|
53
53
|
files: ./coverage/lcov.info
|
|
54
54
|
fail_ci_if_error: false
|
|
@@ -63,10 +63,10 @@ jobs:
|
|
|
63
63
|
|
|
64
64
|
steps:
|
|
65
65
|
- name: Checkout code
|
|
66
|
-
uses: actions/checkout@
|
|
66
|
+
uses: actions/checkout@v6
|
|
67
67
|
|
|
68
68
|
- name: Setup Node.js
|
|
69
|
-
uses: actions/setup-node@
|
|
69
|
+
uses: actions/setup-node@v6
|
|
70
70
|
with:
|
|
71
71
|
node-version: 20
|
|
72
72
|
cache: 'npm'
|
|
@@ -78,7 +78,7 @@ jobs:
|
|
|
78
78
|
run: npm run build
|
|
79
79
|
|
|
80
80
|
- name: Upload build artifacts
|
|
81
|
-
uses: actions/upload-artifact@
|
|
81
|
+
uses: actions/upload-artifact@v7
|
|
82
82
|
with:
|
|
83
83
|
name: dist
|
|
84
84
|
path: dist/
|
|
@@ -92,7 +92,7 @@ jobs:
|
|
|
92
92
|
|
|
93
93
|
steps:
|
|
94
94
|
- name: Checkout code
|
|
95
|
-
uses: actions/checkout@
|
|
95
|
+
uses: actions/checkout@v6
|
|
96
96
|
|
|
97
97
|
- name: Set up Docker Buildx
|
|
98
98
|
uses: docker/setup-buildx-action@v3
|
|
@@ -117,10 +117,10 @@ jobs:
|
|
|
117
117
|
|
|
118
118
|
steps:
|
|
119
119
|
- name: Checkout code
|
|
120
|
-
uses: actions/checkout@
|
|
120
|
+
uses: actions/checkout@v6
|
|
121
121
|
|
|
122
122
|
- name: Setup Node.js
|
|
123
|
-
uses: actions/setup-node@
|
|
123
|
+
uses: actions/setup-node@v6
|
|
124
124
|
with:
|
|
125
125
|
node-version: 20
|
|
126
126
|
cache: 'npm'
|
|
@@ -141,7 +141,7 @@ jobs:
|
|
|
141
141
|
output: 'trivy-results.sarif'
|
|
142
142
|
|
|
143
143
|
- name: Upload Trivy scan results
|
|
144
|
-
uses: github/codeql-action/upload-sarif@
|
|
144
|
+
uses: github/codeql-action/upload-sarif@v4
|
|
145
145
|
if: always()
|
|
146
146
|
with:
|
|
147
147
|
sarif_file: 'trivy-results.sarif'
|
|
@@ -17,10 +17,10 @@ jobs:
|
|
|
17
17
|
|
|
18
18
|
steps:
|
|
19
19
|
- name: Checkout code
|
|
20
|
-
uses: actions/checkout@
|
|
20
|
+
uses: actions/checkout@v6
|
|
21
21
|
|
|
22
22
|
- name: Setup Node.js
|
|
23
|
-
uses: actions/setup-node@
|
|
23
|
+
uses: actions/setup-node@v6
|
|
24
24
|
with:
|
|
25
25
|
node-version: 20
|
|
26
26
|
cache: 'npm'
|
|
@@ -45,7 +45,7 @@ jobs:
|
|
|
45
45
|
|
|
46
46
|
steps:
|
|
47
47
|
- name: Checkout code
|
|
48
|
-
uses: actions/checkout@
|
|
48
|
+
uses: actions/checkout@v6
|
|
49
49
|
|
|
50
50
|
- name: Dependency Review
|
|
51
51
|
uses: actions/dependency-review-action@v4
|
|
@@ -68,17 +68,17 @@ jobs:
|
|
|
68
68
|
|
|
69
69
|
steps:
|
|
70
70
|
- name: Checkout code
|
|
71
|
-
uses: actions/checkout@
|
|
71
|
+
uses: actions/checkout@v6
|
|
72
72
|
|
|
73
73
|
- name: Initialize CodeQL
|
|
74
|
-
uses: github/codeql-action/init@
|
|
74
|
+
uses: github/codeql-action/init@v4
|
|
75
75
|
with:
|
|
76
76
|
languages: ${{ matrix.language }}
|
|
77
77
|
|
|
78
78
|
- name: Autobuild
|
|
79
|
-
uses: github/codeql-action/autobuild@
|
|
79
|
+
uses: github/codeql-action/autobuild@v4
|
|
80
80
|
|
|
81
81
|
- name: Perform CodeQL Analysis
|
|
82
|
-
uses: github/codeql-action/analyze@
|
|
82
|
+
uses: github/codeql-action/analyze@v4
|
|
83
83
|
with:
|
|
84
84
|
category: "/language:${{matrix.language}}"
|
package/index.js
CHANGED
|
@@ -397,6 +397,22 @@ async function getLatestVersion() {
|
|
|
397
397
|
} catch { return null; }
|
|
398
398
|
}
|
|
399
399
|
|
|
400
|
+
async function getLatestDashboardVersion() {
|
|
401
|
+
try {
|
|
402
|
+
return await new Promise((resolve) => {
|
|
403
|
+
https.get('https://api.github.com/repos/spleck/claw-dashboard/releases/latest', {
|
|
404
|
+
headers: { 'User-Agent': 'claw-dashboard' }
|
|
405
|
+
}, (res) => {
|
|
406
|
+
let data = '';
|
|
407
|
+
res.on('data', chunk => data += chunk);
|
|
408
|
+
res.on('end', () => {
|
|
409
|
+
try { resolve(JSON.parse(data).tag_name?.replace(/^v/, '')); } catch { resolve(null); }
|
|
410
|
+
});
|
|
411
|
+
}).on('error', () => resolve(null)).setTimeout(3000);
|
|
412
|
+
});
|
|
413
|
+
} catch { return null; }
|
|
414
|
+
}
|
|
415
|
+
|
|
400
416
|
function formatDuration(seconds) {
|
|
401
417
|
if (!seconds || seconds < 0) return '--';
|
|
402
418
|
const days = Math.floor(seconds / 86400);
|
|
@@ -836,7 +852,7 @@ class Dashboard {
|
|
|
836
852
|
// Favorites state
|
|
837
853
|
this.showFavoritesOnly = this.settings.showFavoritesOnly || false;
|
|
838
854
|
this.history = { cpu: new Array(HISTORY_LENGTH).fill(0), memory: new Array(HISTORY_LENGTH).fill(0), netRx: new Array(NETWORK_HISTORY_LENGTH).fill(0), netTx: new Array(NETWORK_HISTORY_LENGTH).fill(0) };
|
|
839
|
-
this.data = { cpu: [], memory: {}, openclaw: null, gpu: null, network: null, sessions: [], agents: [], version: null, latest: null, sessionTPS: {}, sessionLastTPS: {} };
|
|
855
|
+
this.data = { cpu: [], memory: {}, openclaw: null, gpu: null, network: null, sessions: [], agents: [], version: null, latest: null, dashboardVersion: DASHBOARD_VERSION, dashboardLatest: null, sessionTPS: {}, sessionLastTPS: {} };
|
|
840
856
|
// Data freshness tracking - stores timestamps when each data type was last successfully fetched
|
|
841
857
|
this.dataTimestamps = { cpu: null, memory: null, gpu: null, network: null, disk: null, system: null, sessions: null };
|
|
842
858
|
this.prev = null;
|
|
@@ -1265,6 +1281,13 @@ class Dashboard {
|
|
|
1265
1281
|
}).catch(() => {
|
|
1266
1282
|
this.data.latest = null;
|
|
1267
1283
|
});
|
|
1284
|
+
|
|
1285
|
+
// Also check for dashboard updates
|
|
1286
|
+
getLatestDashboardVersion().then(latest => {
|
|
1287
|
+
this.data.dashboardLatest = latest;
|
|
1288
|
+
}).catch(() => {
|
|
1289
|
+
this.data.dashboardLatest = null;
|
|
1290
|
+
});
|
|
1268
1291
|
}
|
|
1269
1292
|
|
|
1270
1293
|
createWidgets() {
|
|
@@ -5081,11 +5104,23 @@ class Dashboard {
|
|
|
5081
5104
|
}
|
|
5082
5105
|
}
|
|
5083
5106
|
|
|
5107
|
+
// Fetch gateway uptime FIRST - if process isn't running, gateway is down
|
|
5108
|
+
// This must be checked before fetchSessions() since it can succeed via local fallback
|
|
5109
|
+
this.data.gatewayUptime = await getGatewayUptime();
|
|
5110
|
+
const isGatewayProcessRunning = this.data.gatewayUptime !== null;
|
|
5111
|
+
|
|
5084
5112
|
// Fetch sessions via API (same as clawps) - has displayName and channel
|
|
5085
5113
|
try {
|
|
5086
5114
|
const sessions = await timeOperation('Sessions', () => this.fetchSessions(), 5000);
|
|
5087
5115
|
this.data.sessions = sessions || [];
|
|
5088
|
-
|
|
5116
|
+
// Gateway is ONLY reachable if: process is running AND at least one endpoint responds
|
|
5117
|
+
const stats = this.data.gatewayStats;
|
|
5118
|
+
const allUnreachable = stats && stats.totalEndpoints > 0 && stats.reachableEndpoints === 0;
|
|
5119
|
+
if (!isGatewayProcessRunning || allUnreachable) {
|
|
5120
|
+
this.data.openclaw = { gateway: { reachable: false } };
|
|
5121
|
+
} else {
|
|
5122
|
+
this.data.openclaw = { gateway: { reachable: true } };
|
|
5123
|
+
}
|
|
5089
5124
|
this.dataTimestamps.sessions = now;
|
|
5090
5125
|
|
|
5091
5126
|
// Clean up stale sessionTPS entries for sessions that no longer exist
|
|
@@ -5437,7 +5472,18 @@ class Dashboard {
|
|
|
5437
5472
|
openclawText = `openclaw ${current}`;
|
|
5438
5473
|
}
|
|
5439
5474
|
}
|
|
5440
|
-
|
|
5475
|
+
|
|
5476
|
+
// Render dashboard version with update check
|
|
5477
|
+
let dashboardText = `dashboard ${this.data.dashboardVersion}`;
|
|
5478
|
+
if (this.data.dashboardLatest && this.data.dashboardVersion) {
|
|
5479
|
+
if (this.data.dashboardVersion === this.data.dashboardLatest) {
|
|
5480
|
+
dashboardText = `dashboard ${this.data.dashboardVersion} ✓`;
|
|
5481
|
+
} else {
|
|
5482
|
+
dashboardText = `dashboard ${this.data.dashboardVersion} → ${this.data.dashboardLatest}`;
|
|
5483
|
+
}
|
|
5484
|
+
}
|
|
5485
|
+
|
|
5486
|
+
this.diffRenderer.setContent('title', this.w.title, `Claw ${dashboardText}, ${openclawText}`);
|
|
5441
5487
|
|
|
5442
5488
|
// Update clock - show current local time, PAUSED indicator on the right (with differential updates)
|
|
5443
5489
|
const now = new Date();
|
|
@@ -5588,9 +5634,12 @@ class Dashboard {
|
|
|
5588
5634
|
const gatewayHealth = gatewayManager.getEndpointHealth();
|
|
5589
5635
|
const unreachableCount = gatewayHealth.filter(ep => ep.enabled && !ep.reachable).length;
|
|
5590
5636
|
const enabledCount = gatewayHealth.filter(ep => ep.enabled).length;
|
|
5637
|
+
const isGatewayProcessRunning = this.data.gatewayUptime !== null;
|
|
5591
5638
|
let gatewayIndicator = '';
|
|
5592
5639
|
if (enabledCount > 0) {
|
|
5593
|
-
if (
|
|
5640
|
+
if (!isGatewayProcessRunning) {
|
|
5641
|
+
gatewayIndicator = '{red-fg}✗ gateway offline{/red-fg} [G] retry ';
|
|
5642
|
+
} else if (unreachableCount === 0) {
|
|
5594
5643
|
gatewayIndicator = '{green-fg}● gateway{/green-fg} ';
|
|
5595
5644
|
} else if (unreachableCount === enabledCount) {
|
|
5596
5645
|
gatewayIndicator = '{red-fg}✗ gateway offline{/red-fg} [G] retry ';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claw-dashboard",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "A beautiful console dashboard for monitoring OpenClaw instances — inspired by htop/btop/mactop",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"exports": {
|
|
@@ -60,8 +60,7 @@
|
|
|
60
60
|
"node": ">=18"
|
|
61
61
|
},
|
|
62
62
|
"dependencies": {
|
|
63
|
-
"blessed": "^0.1.81",
|
|
64
|
-
"blessed-contrib": "^1.0.11",
|
|
63
|
+
"@pm2/blessed": "^0.1.81",
|
|
65
64
|
"chalk": "^5.3.0",
|
|
66
65
|
"sql.js": "^1.14.0",
|
|
67
66
|
"systeminformation": "^5.21.22"
|
|
@@ -69,7 +68,7 @@
|
|
|
69
68
|
"devDependencies": {
|
|
70
69
|
"@eslint/js": "^10.0.1",
|
|
71
70
|
"c8": "^11.0.0",
|
|
72
|
-
"esbuild": "^0.
|
|
71
|
+
"esbuild": "^0.27.3",
|
|
73
72
|
"eslint": "^10.0.2",
|
|
74
73
|
"husky": "^9.1.7",
|
|
75
74
|
"jest": "^30.2.0",
|
package/src/plugin-scaffold.js
CHANGED
|
@@ -585,7 +585,7 @@ export { ${className} };
|
|
|
585
585
|
|
|
586
586
|
chart: {
|
|
587
587
|
name: 'Chart Widget',
|
|
588
|
-
description: 'Widget with real-time line chart visualization using
|
|
588
|
+
description: 'Widget with real-time line chart visualization using ASCII art',
|
|
589
589
|
manifest: (id, name, author, options = {}) => ({
|
|
590
590
|
id,
|
|
591
591
|
name: name || id.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' '),
|
|
@@ -607,7 +607,7 @@ export { ${className} };
|
|
|
607
607
|
|
|
608
608
|
widgetCode: (id, className) => `/**
|
|
609
609
|
* ${className} Widget Plugin
|
|
610
|
-
* Chart widget with real-time data visualization using
|
|
610
|
+
* Chart widget with real-time data visualization using ASCII art
|
|
611
611
|
*/
|
|
612
612
|
|
|
613
613
|
import { BaseWidget } from 'claw-dashboard/widgets';
|
|
@@ -644,14 +644,13 @@ export default class ${className} extends BaseWidget {
|
|
|
644
644
|
}
|
|
645
645
|
|
|
646
646
|
/**
|
|
647
|
-
* Create the widget UI with
|
|
647
|
+
* Create the widget UI with ASCII line chart
|
|
648
648
|
* @param {Object} screen - Blessed screen object
|
|
649
649
|
* @param {Object} theme - Theme colors
|
|
650
650
|
*/
|
|
651
651
|
async create(screen, theme = {}) {
|
|
652
652
|
const C = theme.colors || {};
|
|
653
653
|
const blessed = await import('blessed');
|
|
654
|
-
const contrib = await import('blessed-contrib');
|
|
655
654
|
|
|
656
655
|
this.screen = screen;
|
|
657
656
|
this.theme = theme;
|
|
@@ -668,27 +667,15 @@ export default class ${className} extends BaseWidget {
|
|
|
668
667
|
},
|
|
669
668
|
});
|
|
670
669
|
|
|
671
|
-
// Create
|
|
672
|
-
this.
|
|
670
|
+
// Create ASCII chart area using text element
|
|
671
|
+
this.chartArea = blessed.default.text({
|
|
673
672
|
parent: this.box,
|
|
674
673
|
top: 1,
|
|
675
674
|
left: 1,
|
|
676
675
|
width: '95%',
|
|
677
|
-
height:
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
text: C.white || 'white',
|
|
681
|
-
baseline: C.gray || 'gray',
|
|
682
|
-
},
|
|
683
|
-
xLabelPadding: 3,
|
|
684
|
-
xPadding: 5,
|
|
685
|
-
numYLabels: 5,
|
|
686
|
-
showNthLabel: Math.ceil(this.maxDataPoints / 6),
|
|
687
|
-
showLegend: this.showLegend,
|
|
688
|
-
legend: { width: 12 },
|
|
689
|
-
minY: 0,
|
|
690
|
-
maxY: 100,
|
|
691
|
-
wholeNumbersOnly: true,
|
|
676
|
+
height: 13,
|
|
677
|
+
tags: true,
|
|
678
|
+
style: { fg: C.green || 'green' },
|
|
692
679
|
});
|
|
693
680
|
|
|
694
681
|
// Add info text
|
|
@@ -709,6 +696,29 @@ export default class ${className} extends BaseWidget {
|
|
|
709
696
|
return this;
|
|
710
697
|
}
|
|
711
698
|
|
|
699
|
+
/**
|
|
700
|
+
* Generate ASCII chart from data
|
|
701
|
+
*/
|
|
702
|
+
_renderAsciiChart(values, width = 60, height = 10) {
|
|
703
|
+
if (!values || values.length === 0) return 'No data';
|
|
704
|
+
|
|
705
|
+
const min = Math.min(...values);
|
|
706
|
+
const max = Math.max(...values);
|
|
707
|
+
const range = max - min || 1;
|
|
708
|
+
|
|
709
|
+
let chart = '';
|
|
710
|
+
for (let row = height - 1; row >= 0; row--) {
|
|
711
|
+
const threshold = min + (range * row / height);
|
|
712
|
+
let line = '';
|
|
713
|
+
for (let i = 0; i < Math.min(values.length, width); i++) {
|
|
714
|
+
const val = values[i];
|
|
715
|
+
line += val >= threshold ? '█' : '░';
|
|
716
|
+
}
|
|
717
|
+
chart += line + '\n';
|
|
718
|
+
}
|
|
719
|
+
return chart;
|
|
720
|
+
}
|
|
721
|
+
|
|
712
722
|
/**
|
|
713
723
|
* Generate sample data - customize this for your data source
|
|
714
724
|
*/
|
|
@@ -748,16 +758,9 @@ export default class ${className} extends BaseWidget {
|
|
|
748
758
|
render(data) {
|
|
749
759
|
if (!this.chart || !data) return;
|
|
750
760
|
|
|
751
|
-
//
|
|
752
|
-
const
|
|
753
|
-
|
|
754
|
-
x: data.labels,
|
|
755
|
-
y: data.values,
|
|
756
|
-
style: { line: 'green' },
|
|
757
|
-
};
|
|
758
|
-
|
|
759
|
-
// Update the chart
|
|
760
|
-
this.chart.setData([chartData]);
|
|
761
|
+
// Generate ASCII chart
|
|
762
|
+
const asciiChart = this._renderAsciiChart(data.values);
|
|
763
|
+
this.chartArea.setContent(asciiChart);
|
|
761
764
|
|
|
762
765
|
// Update info text
|
|
763
766
|
const avg = data.values.length > 0
|
|
@@ -1102,7 +1105,6 @@ export default class ${className} extends BaseWidget {
|
|
|
1102
1105
|
async create(screen, theme = {}) {
|
|
1103
1106
|
const C = theme.colors || {};
|
|
1104
1107
|
const blessed = await import('blessed');
|
|
1105
|
-
const contrib = await import('blessed-contrib');
|
|
1106
1108
|
|
|
1107
1109
|
this.screen = screen;
|
|
1108
1110
|
this.theme = theme;
|
|
@@ -1119,35 +1121,16 @@ export default class ${className} extends BaseWidget {
|
|
|
1119
1121
|
},
|
|
1120
1122
|
});
|
|
1121
1123
|
|
|
1122
|
-
// Create gauge
|
|
1123
|
-
|
|
1124
|
-
this.
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
value: { fg: C.green || 'green' },
|
|
1133
|
-
},
|
|
1134
|
-
label: this.name,
|
|
1135
|
-
});
|
|
1136
|
-
} else {
|
|
1137
|
-
// Linear gauge
|
|
1138
|
-
this.gauge = contrib.default.gauge({
|
|
1139
|
-
parent: this.box,
|
|
1140
|
-
top: 2,
|
|
1141
|
-
left: 1,
|
|
1142
|
-
width: '96%',
|
|
1143
|
-
height: 4,
|
|
1144
|
-
style: {
|
|
1145
|
-
label: { fg: C.white || 'white' },
|
|
1146
|
-
value: { fg: C.green || 'green' },
|
|
1147
|
-
},
|
|
1148
|
-
label: this.name,
|
|
1149
|
-
});
|
|
1150
|
-
}
|
|
1124
|
+
// Create ASCII gauge area
|
|
1125
|
+
this.gaugeArea = blessed.default.text({
|
|
1126
|
+
parent: this.box,
|
|
1127
|
+
top: 1,
|
|
1128
|
+
left: 'center',
|
|
1129
|
+
width: '90%',
|
|
1130
|
+
height: 6,
|
|
1131
|
+
tags: true,
|
|
1132
|
+
style: { fg: C.green || 'green' },
|
|
1133
|
+
});
|
|
1151
1134
|
|
|
1152
1135
|
// Value display
|
|
1153
1136
|
this.valueText = blessed.default.text({
|
|
@@ -1167,6 +1150,15 @@ export default class ${className} extends BaseWidget {
|
|
|
1167
1150
|
return this;
|
|
1168
1151
|
}
|
|
1169
1152
|
|
|
1153
|
+
/**
|
|
1154
|
+
* Generate ASCII gauge
|
|
1155
|
+
*/
|
|
1156
|
+
_renderAsciiGauge(percent, width = 20) {
|
|
1157
|
+
const filled = Math.round((percent / 100) * width);
|
|
1158
|
+
const bar = '█'.repeat(filled) + '░'.repeat(width - filled);
|
|
1159
|
+
return bar;
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1170
1162
|
/**
|
|
1171
1163
|
* Generate sample data - customize this for your data source
|
|
1172
1164
|
*/
|
|
@@ -1186,14 +1178,11 @@ export default class ${className} extends BaseWidget {
|
|
|
1186
1178
|
* Render the gauge with data
|
|
1187
1179
|
*/
|
|
1188
1180
|
render(result) {
|
|
1189
|
-
if (!this.
|
|
1190
|
-
|
|
1191
|
-
// Set gauge percentage (0-100)
|
|
1192
|
-
this.gauge.setData(result.percentage);
|
|
1193
|
-
|
|
1194
|
-
// Update value text
|
|
1195
|
-
this.valueText.setContent(result.value + this.unit);
|
|
1181
|
+
if (!this.gaugeArea || !result) return;
|
|
1196
1182
|
|
|
1183
|
+
// Generate ASCII gauge
|
|
1184
|
+
const asciiGauge = this._renderAsciiGauge(result.percentage);
|
|
1185
|
+
|
|
1197
1186
|
// Color based on value
|
|
1198
1187
|
let color = 'green';
|
|
1199
1188
|
if (result.percentage > 80) {
|
|
@@ -1202,6 +1191,11 @@ export default class ${className} extends BaseWidget {
|
|
|
1202
1191
|
color = 'yellow';
|
|
1203
1192
|
}
|
|
1204
1193
|
|
|
1194
|
+
// Update gauge display
|
|
1195
|
+
this.gaugeArea.setContent('{' + color + '-fg}' + asciiGauge + '{/' + color + '-fg}');
|
|
1196
|
+
|
|
1197
|
+
// Update value text
|
|
1198
|
+
this.valueText.setContent(result.value + this.unit);
|
|
1205
1199
|
this.valueText.style.fg = this.theme?.colors?.[color] || color;
|
|
1206
1200
|
}
|
|
1207
1201
|
|
|
@@ -1242,9 +1236,8 @@ export default class ${className} extends BaseWidget {
|
|
|
1242
1236
|
async destroy() {
|
|
1243
1237
|
this.stopAutoRefresh();
|
|
1244
1238
|
|
|
1245
|
-
if (this.
|
|
1246
|
-
this.
|
|
1247
|
-
this.gauge = null;
|
|
1239
|
+
if (this.gaugeArea) {
|
|
1240
|
+
this.gaugeArea = null;
|
|
1248
1241
|
}
|
|
1249
1242
|
|
|
1250
1243
|
if (this.box) {
|