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/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
+ }