tuimon 0.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.
Files changed (50) hide show
  1. package/CLAUDE.md +80 -0
  2. package/README.md +153 -0
  3. package/client/tuimon-client.js +44 -0
  4. package/dist/browser.d.ts +7 -0
  5. package/dist/browser.d.ts.map +1 -0
  6. package/dist/browser.js +57 -0
  7. package/dist/browser.js.map +1 -0
  8. package/dist/cli.d.ts +3 -0
  9. package/dist/cli.d.ts.map +1 -0
  10. package/dist/cli.js +87 -0
  11. package/dist/cli.js.map +1 -0
  12. package/dist/detect.d.ts +8 -0
  13. package/dist/detect.d.ts.map +1 -0
  14. package/dist/detect.js +141 -0
  15. package/dist/detect.js.map +1 -0
  16. package/dist/encoder.d.ts +4 -0
  17. package/dist/encoder.d.ts.map +1 -0
  18. package/dist/encoder.js +115 -0
  19. package/dist/encoder.js.map +1 -0
  20. package/dist/fkeybar.d.ts +5 -0
  21. package/dist/fkeybar.d.ts.map +1 -0
  22. package/dist/fkeybar.js +89 -0
  23. package/dist/fkeybar.js.map +1 -0
  24. package/dist/index.d.ts +7 -0
  25. package/dist/index.d.ts.map +1 -0
  26. package/dist/index.js +166 -0
  27. package/dist/index.js.map +1 -0
  28. package/dist/keyhandler.d.ts +5 -0
  29. package/dist/keyhandler.d.ts.map +1 -0
  30. package/dist/keyhandler.js +20 -0
  31. package/dist/keyhandler.js.map +1 -0
  32. package/dist/router.d.ts +10 -0
  33. package/dist/router.d.ts.map +1 -0
  34. package/dist/router.js +154 -0
  35. package/dist/router.js.map +1 -0
  36. package/dist/server.d.ts +5 -0
  37. package/dist/server.d.ts.map +1 -0
  38. package/dist/server.js +142 -0
  39. package/dist/server.js.map +1 -0
  40. package/dist/types.d.ts +108 -0
  41. package/dist/types.d.ts.map +1 -0
  42. package/dist/types.js +3 -0
  43. package/dist/types.js.map +1 -0
  44. package/package.json +44 -0
  45. package/templates/internal/confirm-quit.html +94 -0
  46. package/templates/starter/pages/cpu-detail.html +176 -0
  47. package/templates/starter/pages/memory-detail.html +166 -0
  48. package/templates/starter/pages/overview.html +186 -0
  49. package/templates/starter/tuimon.config.ts +87 -0
  50. package/tsconfig.json +23 -0
package/dist/server.js ADDED
@@ -0,0 +1,142 @@
1
+ import { createServer } from 'node:http';
2
+ import { readFileSync, existsSync } from 'node:fs';
3
+ import { join, extname, resolve, sep } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ // ─── Paths to bundled assets ────────────────────────────────────────────────
6
+ const __dirname = fileURLToPath(new URL('.', import.meta.url));
7
+ const clientJsPath = join(__dirname, '..', 'client', 'tuimon-client.js');
8
+ const confirmQuitPath = join(__dirname, '..', 'templates', 'internal', 'confirm-quit.html');
9
+ // ─── MIME types ─────────────────────────────────────────────────────────────
10
+ const MIME_TYPES = {
11
+ '.html': 'text/html; charset=utf-8',
12
+ '.css': 'text/css; charset=utf-8',
13
+ '.js': 'application/javascript; charset=utf-8',
14
+ '.json': 'application/json; charset=utf-8',
15
+ '.png': 'image/png',
16
+ '.jpg': 'image/jpeg',
17
+ '.jpeg': 'image/jpeg',
18
+ '.svg': 'image/svg+xml',
19
+ '.woff2': 'font/woff2',
20
+ };
21
+ function mimeFor(filePath) {
22
+ return MIME_TYPES[extname(filePath).toLowerCase()] ?? 'application/octet-stream';
23
+ }
24
+ // ─── Script injection ───────────────────────────────────────────────────────
25
+ const CLIENT_SCRIPT_TAG = '<script src="/tuimon/client.js"></script>';
26
+ function injectClientScript(html) {
27
+ if (html.includes('tuimon/client.js'))
28
+ return html;
29
+ return html.replace('</head>', `${CLIENT_SCRIPT_TAG}</head>`);
30
+ }
31
+ // ─── Serve helpers ──────────────────────────────────────────────────────────
32
+ function serveFile(res, filePath, transform) {
33
+ if (!existsSync(filePath)) {
34
+ res.writeHead(404, { 'Content-Type': 'text/plain', Connection: 'close' });
35
+ res.end('Not Found');
36
+ return;
37
+ }
38
+ const mime = mimeFor(filePath);
39
+ const raw = readFileSync(filePath);
40
+ if (transform && mime.startsWith('text/html')) {
41
+ const transformed = transform(raw.toString('utf-8'));
42
+ res.writeHead(200, { 'Content-Type': mime, Connection: 'close' });
43
+ res.end(transformed);
44
+ }
45
+ else {
46
+ res.writeHead(200, { 'Content-Type': mime, Connection: 'close' });
47
+ res.end(raw);
48
+ }
49
+ }
50
+ // ─── Request handler ────────────────────────────────────────────────────────
51
+ function createHandler(rootDir) {
52
+ return (req, res) => {
53
+ const url = req.url ?? '/';
54
+ let pathname;
55
+ try {
56
+ pathname = decodeURIComponent(url.split('?')[0] ?? '/');
57
+ }
58
+ catch {
59
+ res.writeHead(400, { 'Content-Type': 'text/plain' });
60
+ res.end('Bad Request');
61
+ return;
62
+ }
63
+ // Internal tuimon routes
64
+ if (pathname === '/tuimon/client.js') {
65
+ serveFile(res, clientJsPath);
66
+ return;
67
+ }
68
+ if (pathname === '/tuimon/confirm-quit.html') {
69
+ serveFile(res, confirmQuitPath);
70
+ return;
71
+ }
72
+ // Serve from rootDir
73
+ const safePath = pathname.startsWith('/') ? pathname.slice(1) : pathname;
74
+ const filePath = resolve(rootDir, safePath);
75
+ // Prevent path traversal — ensure resolved path is within rootDir
76
+ const normalizedRoot = rootDir.endsWith(sep) ? rootDir : rootDir + sep;
77
+ if (!filePath.startsWith(normalizedRoot) && filePath !== rootDir) {
78
+ res.writeHead(403, { 'Content-Type': 'text/plain' });
79
+ res.end('Forbidden');
80
+ return;
81
+ }
82
+ serveFile(res, filePath, injectClientScript);
83
+ };
84
+ }
85
+ // ─── Server startup ─────────────────────────────────────────────────────────
86
+ const START_PORT = 7337;
87
+ const MAX_ATTEMPTS = 100;
88
+ function tryListen(server, port) {
89
+ return new Promise((resolve, reject) => {
90
+ const onError = (err) => {
91
+ if (err.code === 'EADDRINUSE') {
92
+ if (port - START_PORT >= MAX_ATTEMPTS) {
93
+ reject(new Error(`Could not find open port after ${MAX_ATTEMPTS} attempts`));
94
+ return;
95
+ }
96
+ server.removeListener('error', onError);
97
+ tryListen(server, port + 1).then(resolve, reject);
98
+ }
99
+ else {
100
+ reject(err);
101
+ }
102
+ };
103
+ server.once('error', onError);
104
+ server.listen(port, 'localhost', () => {
105
+ server.removeListener('error', onError);
106
+ resolve(port);
107
+ });
108
+ });
109
+ }
110
+ export async function startServer({ rootDir }) {
111
+ const server = createServer(createHandler(rootDir));
112
+ // Track open sockets so close() can destroy them immediately
113
+ const sockets = new Set();
114
+ server.on('connection', (socket) => {
115
+ sockets.add(socket);
116
+ socket.once('close', () => sockets.delete(socket));
117
+ });
118
+ const port = await tryListen(server, START_PORT);
119
+ const url = `http://localhost:${port}`;
120
+ return {
121
+ url,
122
+ urlFor(htmlPath) {
123
+ return `${url}/${htmlPath}`;
124
+ },
125
+ close() {
126
+ return new Promise((resolve, reject) => {
127
+ // Destroy all open sockets so the server can close immediately
128
+ for (const socket of sockets) {
129
+ socket.destroy();
130
+ }
131
+ sockets.clear();
132
+ server.close((err) => {
133
+ if (err)
134
+ reject(err);
135
+ else
136
+ resolve();
137
+ });
138
+ });
139
+ },
140
+ };
141
+ }
142
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,108 @@
1
+ export type FKey = 'F1' | 'F2' | 'F3' | 'F4' | 'F5' | 'F6' | 'F7' | 'F8' | 'F9' | 'F10' | 'F11' | 'F12';
2
+ /** Single lowercase letter a-z used as a panel/page shortcut */
3
+ export type ShortcutKey = string;
4
+ export interface KeyBinding {
5
+ label: string;
6
+ action: () => void | Promise<void>;
7
+ }
8
+ export type FKeyMap = Partial<Record<FKey, KeyBinding>>;
9
+ export interface PageConfig {
10
+ /** Absolute or cwd-relative path to the HTML file for this page */
11
+ html: string;
12
+ /** If true, this is the starting page. Exactly one page must be default. */
13
+ default?: boolean;
14
+ /**
15
+ * Single lowercase letter shortcut to navigate to this page from the overview.
16
+ * Not valid on the default page.
17
+ * Cannot conflict with other page shortcuts.
18
+ * Cannot be 'y', 'n', or any reserved key.
19
+ */
20
+ shortcut?: ShortcutKey;
21
+ /** Human-readable label shown in panel borders and the F-key bar */
22
+ label?: string;
23
+ /**
24
+ * F-key bindings active when this page is displayed.
25
+ * ESC and Ctrl+C are always reserved — defining them here has no effect
26
+ * and TuiMon will log a warning at startup.
27
+ */
28
+ keys?: FKeyMap;
29
+ }
30
+ export type PageMap = Record<string, PageConfig>;
31
+ export interface TuiMonOptions {
32
+ /** Page definitions. At least one page must have default: true. */
33
+ pages: PageMap;
34
+ /**
35
+ * Data function called before each render.
36
+ * Result is passed into the page via TuiMon.onUpdate().
37
+ */
38
+ data?: () => Record<string, unknown> | Promise<Record<string, unknown>>;
39
+ /**
40
+ * Auto-render interval in ms. Requires data to be set.
41
+ * Default: no auto-render — developer calls dash.render() manually.
42
+ */
43
+ refresh?: number;
44
+ /** Delay in ms after pushData() before screenshotting. Default: 50 */
45
+ renderDelay?: number;
46
+ }
47
+ export interface TuiMonDashboard {
48
+ /**
49
+ * Render the current page with new data.
50
+ * Caches data for use when navigating between pages.
51
+ */
52
+ render: (data: Record<string, unknown>) => Promise<void>;
53
+ /** Gracefully shut down — restores terminal state */
54
+ stop: () => Promise<void>;
55
+ }
56
+ export type PageState = {
57
+ type: 'overview';
58
+ pageId: string;
59
+ } | {
60
+ type: 'detail';
61
+ pageId: string;
62
+ } | {
63
+ type: 'confirm-quit';
64
+ returnTo: PageState;
65
+ };
66
+ export interface GraphicsSupport {
67
+ kitty: boolean;
68
+ sixel: boolean;
69
+ iterm2: boolean;
70
+ protocol: 'kitty' | 'sixel' | 'iterm2' | null;
71
+ }
72
+ export interface TerminalDimensions {
73
+ cols: number;
74
+ rows: number;
75
+ pixelWidth: number;
76
+ pixelHeight: number;
77
+ }
78
+ export interface ServerHandle {
79
+ /** Base URL the server is listening on */
80
+ url: string;
81
+ /** Returns the full URL for a given page html path */
82
+ urlFor: (htmlPath: string) => string;
83
+ close: () => Promise<void>;
84
+ }
85
+ export interface BrowserHandle {
86
+ screenshot: () => Promise<Buffer>;
87
+ pushData: (data: Record<string, unknown>) => Promise<void>;
88
+ navigate: (url: string) => Promise<void>;
89
+ resize: (width: number, height: number) => Promise<void>;
90
+ close: () => Promise<void>;
91
+ }
92
+ export interface FKeyBarHandle {
93
+ /** Replace the current key set and re-render the bar */
94
+ setKeys: (keys: FKeyMap) => void;
95
+ /** Show a temporary message in the bar for duration ms */
96
+ notify: (message: string, duration?: number) => void;
97
+ stop: () => void;
98
+ }
99
+ export interface KeyHandlerHandle {
100
+ stop: () => void;
101
+ }
102
+ export interface RouterHandle {
103
+ /** Process a raw key string from stdin */
104
+ handleKey: (key: string) => Promise<void>;
105
+ /** Current page state */
106
+ getState: () => PageState;
107
+ }
108
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js ADDED
@@ -0,0 +1,3 @@
1
+ // ─── Key types ───────────────────────────────────────────────────────────────
2
+ export {};
3
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,gFAAgF"}
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "tuimon",
3
+ "version": "0.1.0",
4
+ "description": "Render beautiful HTML dashboards directly in your terminal.",
5
+ "type": "module",
6
+ "bin": { "tuimon": "./dist/cli.js" },
7
+ "main": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": "./dist/index.js",
12
+ "types": "./dist/index.d.ts"
13
+ },
14
+ "./client": "./client/tuimon-client.js"
15
+ },
16
+ "engines": { "node": ">=20.0.0" },
17
+ "scripts": {
18
+ "build": "tsc",
19
+ "dev": "tsc --watch",
20
+ "test": "vitest run",
21
+ "test:watch": "vitest",
22
+ "test:coverage": "vitest run --coverage",
23
+ "lint": "eslint src --ext .ts",
24
+ "format": "prettier --write src",
25
+ "typecheck": "tsc --noEmit",
26
+ "prepublishOnly": "npm run typecheck && npm run test && npm run build"
27
+ },
28
+ "dependencies": {
29
+ "commander": "^12.0.0",
30
+ "playwright": "^1.44.0",
31
+ "sharp": "^0.33.0",
32
+ "chokidar": "^3.6.0"
33
+ },
34
+ "devDependencies": {
35
+ "@types/node": "^20.0.0",
36
+ "@typescript-eslint/eslint-plugin": "^7.0.0",
37
+ "@typescript-eslint/parser": "^7.0.0",
38
+ "@vitest/coverage-v8": "^1.0.0",
39
+ "eslint": "^8.0.0",
40
+ "prettier": "^3.0.0",
41
+ "typescript": "^5.4.0",
42
+ "vitest": "^1.0.0"
43
+ }
44
+ }
@@ -0,0 +1,94 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Quit TuiMon?</title>
7
+ <style>
8
+ * { margin: 0; padding: 0; box-sizing: border-box; }
9
+ body {
10
+ width: 100vw;
11
+ height: 100vh;
12
+ display: flex;
13
+ align-items: center;
14
+ justify-content: center;
15
+ background: rgba(0, 0, 0, 0.85);
16
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
17
+ color: #c9d1d9;
18
+ }
19
+ .card {
20
+ background: #1c2128;
21
+ border: 1px solid #30363d;
22
+ border-radius: 8px;
23
+ padding: 48px 64px;
24
+ text-align: center;
25
+ max-width: 480px;
26
+ }
27
+ h1 {
28
+ color: #ffffff;
29
+ font-size: 28px;
30
+ font-weight: 600;
31
+ margin-bottom: 12px;
32
+ }
33
+ .subtitle {
34
+ color: #8b949e;
35
+ font-size: 16px;
36
+ margin-bottom: 32px;
37
+ }
38
+ .keys {
39
+ display: flex;
40
+ gap: 24px;
41
+ justify-content: center;
42
+ margin-bottom: 20px;
43
+ }
44
+ .key {
45
+ display: flex;
46
+ align-items: center;
47
+ gap: 8px;
48
+ font-size: 18px;
49
+ font-weight: 500;
50
+ }
51
+ .key-badge {
52
+ display: inline-flex;
53
+ align-items: center;
54
+ justify-content: center;
55
+ width: 36px;
56
+ height: 36px;
57
+ border-radius: 6px;
58
+ font-size: 18px;
59
+ font-weight: 700;
60
+ font-family: monospace;
61
+ }
62
+ .key-badge.yes {
63
+ background: #da3633;
64
+ color: #ffffff;
65
+ }
66
+ .key-badge.no {
67
+ background: #238636;
68
+ color: #ffffff;
69
+ }
70
+ .esc-note {
71
+ color: #484f58;
72
+ font-size: 13px;
73
+ margin-top: 4px;
74
+ }
75
+ </style>
76
+ </head>
77
+ <body>
78
+ <div class="card">
79
+ <h1>Quit TuiMon?</h1>
80
+ <p class="subtitle">Press Y to confirm or N to cancel</p>
81
+ <div class="keys">
82
+ <div class="key">
83
+ <span class="key-badge yes">Y</span>
84
+ <span class="key-label">Confirm</span>
85
+ </div>
86
+ <div class="key">
87
+ <span class="key-badge no">N</span>
88
+ <span class="key-label">Cancel</span>
89
+ </div>
90
+ </div>
91
+ <p class="esc-note">ESC also cancels</p>
92
+ </div>
93
+ </body>
94
+ </html>
@@ -0,0 +1,176 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>TuiMon — CPU Detail</title>
7
+ <script src="https://cdn.jsdelivr.net/npm/chart.js@4"></script>
8
+ <style>
9
+ * { margin: 0; padding: 0; box-sizing: border-box; }
10
+ body {
11
+ background: #0d1117;
12
+ color: #c9d1d9;
13
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, monospace;
14
+ padding: 12px;
15
+ overflow: hidden;
16
+ height: 100vh;
17
+ display: grid;
18
+ grid-template-columns: 1fr 280px;
19
+ grid-template-rows: auto 1fr;
20
+ gap: 12px;
21
+ }
22
+ .header {
23
+ grid-column: 1 / -1;
24
+ display: flex;
25
+ justify-content: space-between;
26
+ align-items: center;
27
+ padding-bottom: 8px;
28
+ border-bottom: 1px solid #21262d;
29
+ }
30
+ .header h1 { font-size: 16px; color: #58a6ff; font-weight: 600; }
31
+ .header .back { font-size: 12px; color: #484f58; }
32
+ .chart-panel {
33
+ background: #161b22;
34
+ border: 1px solid #21262d;
35
+ border-radius: 6px;
36
+ padding: 16px;
37
+ min-height: 0;
38
+ }
39
+ .chart-panel h3 {
40
+ font-size: 12px;
41
+ color: #8b949e;
42
+ text-transform: uppercase;
43
+ letter-spacing: 1px;
44
+ margin-bottom: 12px;
45
+ }
46
+ .chart-wrap { height: calc(100% - 32px); }
47
+ .sidebar {
48
+ display: flex;
49
+ flex-direction: column;
50
+ gap: 10px;
51
+ }
52
+ .info-card {
53
+ background: #161b22;
54
+ border: 1px solid #21262d;
55
+ border-radius: 6px;
56
+ padding: 14px;
57
+ }
58
+ .info-card h4 {
59
+ font-size: 11px;
60
+ color: #8b949e;
61
+ text-transform: uppercase;
62
+ letter-spacing: 1px;
63
+ margin-bottom: 10px;
64
+ }
65
+ .info-row {
66
+ display: flex;
67
+ justify-content: space-between;
68
+ font-size: 13px;
69
+ line-height: 1.8;
70
+ }
71
+ .info-row .label { color: #8b949e; }
72
+ .info-row .val { color: #58a6ff; font-weight: 600; }
73
+ .core-bar {
74
+ height: 8px;
75
+ background: #21262d;
76
+ border-radius: 4px;
77
+ margin-top: 4px;
78
+ overflow: hidden;
79
+ }
80
+ .core-bar-fill {
81
+ height: 100%;
82
+ background: #58a6ff;
83
+ border-radius: 4px;
84
+ transition: width 0.3s ease;
85
+ }
86
+ .core-entry { margin-bottom: 8px; }
87
+ .core-label { font-size: 11px; color: #8b949e; display: flex; justify-content: space-between; }
88
+ </style>
89
+ </head>
90
+ <body>
91
+ <div class="header">
92
+ <h1>CPU Detail</h1>
93
+ <span class="back">ESC to go back</span>
94
+ </div>
95
+
96
+ <div class="chart-panel">
97
+ <h3>CPU Usage — Last 5 Minutes</h3>
98
+ <div class="chart-wrap"><canvas id="cpuChart"></canvas></div>
99
+ </div>
100
+
101
+ <div class="sidebar">
102
+ <div class="info-card">
103
+ <h4>System Info</h4>
104
+ <div class="info-row"><span class="label">Cores</span><span class="val" id="cores">--</span></div>
105
+ <div class="info-row"><span class="label">Load 1m</span><span class="val" id="load1">--</span></div>
106
+ <div class="info-row"><span class="label">Load 5m</span><span class="val" id="load5">--</span></div>
107
+ <div class="info-row"><span class="label">Load 15m</span><span class="val" id="load15">--</span></div>
108
+ <div class="info-row"><span class="label">Current</span><span class="val" id="cpuPct">--</span></div>
109
+ </div>
110
+
111
+ <div class="info-card">
112
+ <h4>Per-Core Usage</h4>
113
+ <div id="coreList"></div>
114
+ </div>
115
+ </div>
116
+
117
+ <script>
118
+ const cpuHistory = []
119
+ const MAX_POINTS = 300
120
+
121
+ const chart = new Chart(document.getElementById('cpuChart'), {
122
+ type: 'line',
123
+ data: {
124
+ labels: [],
125
+ datasets: [{
126
+ data: cpuHistory,
127
+ borderColor: '#58a6ff',
128
+ backgroundColor: 'rgba(88,166,255,0.15)',
129
+ fill: true,
130
+ tension: 0.2,
131
+ pointRadius: 0,
132
+ borderWidth: 2,
133
+ }],
134
+ },
135
+ options: {
136
+ responsive: true,
137
+ maintainAspectRatio: false,
138
+ animation: false,
139
+ scales: {
140
+ y: { min: 0, max: 100, grid: { color: '#21262d' }, ticks: { color: '#484f58', callback: v => v + '%' } },
141
+ x: { display: false },
142
+ },
143
+ plugins: { legend: { display: false } },
144
+ },
145
+ })
146
+
147
+ TuiMon.onUpdate(function (data) {
148
+ TuiMon.set('#cpuPct', data.cpu + '%')
149
+ TuiMon.set('#cores', data.coreCount)
150
+
151
+ if (data.loadAvg) {
152
+ TuiMon.set('#load1', data.loadAvg[0] != null ? data.loadAvg[0].toFixed(2) : '--')
153
+ TuiMon.set('#load5', data.loadAvg[1] != null ? data.loadAvg[1].toFixed(2) : '--')
154
+ TuiMon.set('#load15', data.loadAvg[2] != null ? data.loadAvg[2].toFixed(2) : '--')
155
+ }
156
+
157
+ cpuHistory.push(data.cpu)
158
+ if (cpuHistory.length > MAX_POINTS) cpuHistory.shift()
159
+ chart.data.labels = cpuHistory.map((_, i) => i)
160
+ chart.update()
161
+
162
+ // Simulated per-core usage
163
+ var coreList = document.getElementById('coreList')
164
+ var html = ''
165
+ for (var i = 0; i < (data.coreCount || 4); i++) {
166
+ var usage = Math.max(5, Math.min(98, data.cpu + Math.round((Math.random() - 0.5) * 30)))
167
+ html += '<div class="core-entry">'
168
+ + '<div class="core-label"><span>Core ' + i + '</span><span>' + usage + '%</span></div>'
169
+ + '<div class="core-bar"><div class="core-bar-fill" style="width:' + usage + '%"></div></div>'
170
+ + '</div>'
171
+ }
172
+ coreList.innerHTML = html
173
+ })
174
+ </script>
175
+ </body>
176
+ </html>