termweb-dashboard 0.2.1
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/bin/cli.js +108 -0
- package/dist/app.bundle.js +213 -0
- package/dist/index.html +486 -0
- package/lib/metrics.js +639 -0
- package/lib/server.js +138 -0
- package/package.json +45 -0
package/lib/server.js
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebSocket server for real-time system metrics
|
|
3
|
+
*/
|
|
4
|
+
const http = require('http');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const WebSocket = require('ws');
|
|
8
|
+
const { collectMetrics, collectLightMetrics, getMetricsCached, startBackgroundPolling, killProcess, getConnections, getFolderSizes } = require('./metrics');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Start the metrics server
|
|
12
|
+
* @param {number} port - Port to listen on
|
|
13
|
+
* @returns {Promise<{server: http.Server, wss: WebSocket.Server, port: number}>}
|
|
14
|
+
*/
|
|
15
|
+
function startServer(port = 0) {
|
|
16
|
+
return new Promise((resolve, reject) => {
|
|
17
|
+
const distPath = path.join(__dirname, '..', 'dist');
|
|
18
|
+
|
|
19
|
+
const mimeTypes = {
|
|
20
|
+
'.html': 'text/html',
|
|
21
|
+
'.js': 'application/javascript',
|
|
22
|
+
'.css': 'text/css',
|
|
23
|
+
'.json': 'application/json',
|
|
24
|
+
'.png': 'image/png',
|
|
25
|
+
'.jpg': 'image/jpeg',
|
|
26
|
+
'.svg': 'image/svg+xml'
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// Create HTTP server for static files
|
|
30
|
+
const server = http.createServer((req, res) => {
|
|
31
|
+
let filePath = req.url === '/' ? '/index.html' : req.url;
|
|
32
|
+
// Remove query string
|
|
33
|
+
filePath = filePath.split('?')[0];
|
|
34
|
+
const fullPath = path.join(distPath, filePath);
|
|
35
|
+
const ext = path.extname(fullPath);
|
|
36
|
+
const contentType = mimeTypes[ext] || 'application/octet-stream';
|
|
37
|
+
|
|
38
|
+
fs.readFile(fullPath, (err, data) => {
|
|
39
|
+
if (err) {
|
|
40
|
+
res.writeHead(404);
|
|
41
|
+
res.end('Not found');
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
res.writeHead(200, { 'Content-Type': contentType });
|
|
45
|
+
res.end(data);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// Create WebSocket server
|
|
50
|
+
const wss = new WebSocket.Server({ server });
|
|
51
|
+
|
|
52
|
+
// Start background polling for metrics (every 2 seconds)
|
|
53
|
+
// This ensures metrics are always cached and ready
|
|
54
|
+
startBackgroundPolling(2000);
|
|
55
|
+
|
|
56
|
+
wss.on('connection', async (ws) => {
|
|
57
|
+
// Send initial full metrics (from cache, instant)
|
|
58
|
+
try {
|
|
59
|
+
const metrics = await getMetricsCached();
|
|
60
|
+
ws.send(JSON.stringify({ type: 'full', data: metrics }));
|
|
61
|
+
} catch (err) {
|
|
62
|
+
console.error('Error collecting initial metrics:', err);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Send light metrics every second
|
|
66
|
+
const interval = setInterval(async () => {
|
|
67
|
+
if (ws.readyState !== WebSocket.OPEN) {
|
|
68
|
+
clearInterval(interval);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
const metrics = await collectLightMetrics();
|
|
74
|
+
ws.send(JSON.stringify({ type: 'update', data: metrics }));
|
|
75
|
+
} catch (err) {
|
|
76
|
+
console.error('Error collecting metrics:', err);
|
|
77
|
+
}
|
|
78
|
+
}, 1000);
|
|
79
|
+
|
|
80
|
+
// Send full metrics every 5 seconds (from cache, instant)
|
|
81
|
+
const fullInterval = setInterval(async () => {
|
|
82
|
+
if (ws.readyState !== WebSocket.OPEN) {
|
|
83
|
+
clearInterval(fullInterval);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
const metrics = await getMetricsCached();
|
|
89
|
+
ws.send(JSON.stringify({ type: 'full', data: metrics }));
|
|
90
|
+
} catch (err) {
|
|
91
|
+
console.error('Error collecting full metrics:', err);
|
|
92
|
+
}
|
|
93
|
+
}, 5000);
|
|
94
|
+
|
|
95
|
+
ws.on('close', () => {
|
|
96
|
+
clearInterval(interval);
|
|
97
|
+
clearInterval(fullInterval);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
ws.on('message', async (message) => {
|
|
101
|
+
try {
|
|
102
|
+
const cmd = JSON.parse(message.toString());
|
|
103
|
+
if (cmd.type === 'refresh') {
|
|
104
|
+
const metrics = await getMetricsCached();
|
|
105
|
+
ws.send(JSON.stringify({ type: 'full', data: metrics }));
|
|
106
|
+
} else if (cmd.type === 'kill' && cmd.pid) {
|
|
107
|
+
const success = killProcess(cmd.pid);
|
|
108
|
+
ws.send(JSON.stringify({ type: 'kill', success, pid: cmd.pid }));
|
|
109
|
+
} else if (cmd.type === 'connections') {
|
|
110
|
+
const connections = await getConnections();
|
|
111
|
+
ws.send(JSON.stringify({ type: 'connections', data: connections }));
|
|
112
|
+
} else if (cmd.type === 'folderSizes' && cmd.path) {
|
|
113
|
+
// Progressive scanning - returns estimates immediately, pushes updates
|
|
114
|
+
const onUpdate = (path, data) => {
|
|
115
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
116
|
+
ws.send(JSON.stringify({ type: 'folderSizes', path, data }));
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
const sizes = await getFolderSizes(cmd.path, onUpdate);
|
|
120
|
+
ws.send(JSON.stringify({ type: 'folderSizes', path: cmd.path, data: sizes }));
|
|
121
|
+
}
|
|
122
|
+
} catch (err) {
|
|
123
|
+
// Ignore invalid messages
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
server.listen(port, '127.0.0.1', () => {
|
|
129
|
+
const actualPort = server.address().port;
|
|
130
|
+
console.log(`Dashboard server running on http://127.0.0.1:${actualPort}`);
|
|
131
|
+
resolve({ server, wss, port: actualPort });
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
server.on('error', reject);
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
module.exports = { startServer };
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "termweb-dashboard",
|
|
3
|
+
"version": "0.2.1",
|
|
4
|
+
"description": "Terminal-based system monitoring dashboard powered by termweb",
|
|
5
|
+
"bin": {
|
|
6
|
+
"termweb-dashboard": "bin/cli.js"
|
|
7
|
+
},
|
|
8
|
+
"scripts": {
|
|
9
|
+
"build": "esbuild src/app.js --bundle --minify --outfile=dist/app.bundle.js",
|
|
10
|
+
"start": "node bin/cli.js"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"bin/",
|
|
14
|
+
"dist/",
|
|
15
|
+
"lib/"
|
|
16
|
+
],
|
|
17
|
+
"keywords": [
|
|
18
|
+
"terminal",
|
|
19
|
+
"dashboard",
|
|
20
|
+
"system",
|
|
21
|
+
"monitor",
|
|
22
|
+
"cpu",
|
|
23
|
+
"memory",
|
|
24
|
+
"cli"
|
|
25
|
+
],
|
|
26
|
+
"author": "teamch",
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "git+https://github.com/teamchong/termweb.git",
|
|
31
|
+
"directory": "packages/dashboard"
|
|
32
|
+
},
|
|
33
|
+
"publishConfig": {
|
|
34
|
+
"access": "public"
|
|
35
|
+
},
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"chart.js": "^4.4.1",
|
|
38
|
+
"systeminformation": "^5.21.0",
|
|
39
|
+
"termweb": "workspace:*",
|
|
40
|
+
"ws": "^8.14.0"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"esbuild": "^0.19.0"
|
|
44
|
+
}
|
|
45
|
+
}
|