pyre-cli 1.4.0 → 3.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/README.md +78 -11
- package/dist/index.js +1325 -33
- package/package.json +2 -2
- package/src/formatters/index.ts +1 -1
- package/src/formatters/output.ts +47 -19
- package/src/formatters/render.ts +207 -16
- package/src/formatters/types.ts +4 -0
- package/src/history.ts +85 -47
- package/src/index.ts +200 -20
- package/src/live/export.ts +5 -2
- package/src/live/input.ts +296 -164
- package/src/live/render.ts +32 -28
- package/src/live/state.ts +41 -21
- package/src/monitors/collectors.ts +220 -17
- package/src/monitors/index.ts +2 -2
- package/src/monitors/types.ts +42 -1
- package/src/p2p/client.ts +271 -0
- package/src/p2p/index.ts +3 -0
- package/src/p2p/protocol.ts +112 -0
- package/src/p2p/server.ts +390 -0
- package/src/p2p/types.ts +97 -0
package/src/live/input.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
import readline from 'node:readline';
|
|
10
10
|
import chalk from 'chalk';
|
|
11
11
|
import { collectAll, StatsData } from '../monitors/index.js';
|
|
12
|
-
import { THEMES, type ThemeName, type VisibleItems } from '../formatters/index.js';
|
|
12
|
+
import { THEMES, type ThemeName, type VisibleItems, getTabHitboxes, TAB_BAR_ROW } from '../formatters/index.js';
|
|
13
13
|
import { state, setStatus } from './state.js';
|
|
14
14
|
import { exportSnapshot, startLogging, stopLogging, toggleLogging, writeLogRow } from './export.js';
|
|
15
15
|
import { render, footerLine, checkAlerts, processRowBudget } from './render.js';
|
|
@@ -118,29 +118,33 @@ function killProcess(pidStr: string, signal: string = 'SIGTERM') {
|
|
|
118
118
|
state.intervalHandle = setInterval(tick, state.interval * 1000);
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
121
|
+
async function tick() {
|
|
122
|
+
if (state.paused) return;
|
|
123
|
+
try {
|
|
124
|
+
const data = await collectAll({ detailed: state.detailed, processLimit: processRowBudget() });
|
|
125
|
+
state.lastData = data;
|
|
126
|
+
|
|
127
|
+
const temp = data.cpu.temperature ?? data.thermal.temperatures?.cpu_die ?? null;
|
|
128
|
+
state.history.push({
|
|
129
|
+
cpuUsage: data.cpu.usage,
|
|
130
|
+
memUsage: data.memory.usagePercent,
|
|
131
|
+
temp,
|
|
132
|
+
rxBytes: data.network.rxBytes,
|
|
133
|
+
txBytes: data.network.txBytes,
|
|
134
|
+
gpuUtil: data.gpu?.utilization,
|
|
135
|
+
powerWatts: data.power?.combinedWatts ?? data.power?.cpuWatts ?? 0,
|
|
136
|
+
rxPackets: data.network.rxPackets,
|
|
137
|
+
txPackets: data.network.txPackets,
|
|
138
|
+
connections: data.network.connections ?? 0,
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
writeLogRow(data);
|
|
142
|
+
checkAlerts(data);
|
|
143
|
+
render();
|
|
144
|
+
} catch {
|
|
145
|
+
// skip bad tick
|
|
146
|
+
}
|
|
147
|
+
}
|
|
144
148
|
|
|
145
149
|
/**
|
|
146
150
|
* Start the interactive live dashboard.
|
|
@@ -149,163 +153,291 @@ function killProcess(pidStr: string, signal: string = 'SIGTERM') {
|
|
|
149
153
|
* rendering interval. Idempotent — calling while already
|
|
150
154
|
* running is a no-op.
|
|
151
155
|
*/
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
156
|
+
export async function startLive(opts: LiveOptions) {
|
|
157
|
+
if (state.running) return;
|
|
158
|
+
state.running = true;
|
|
159
|
+
state.paused = false;
|
|
160
|
+
state.detailed = !!opts.detailed;
|
|
161
|
+
state.interval = opts.interval;
|
|
162
|
+
if (opts.exportDir) state.exportDir = opts.exportDir;
|
|
163
|
+
state.termWidth = process.stdout.columns || 80;
|
|
164
|
+
state.termHeight = process.stdout.rows || 24;
|
|
165
|
+
state.history.reset();
|
|
166
|
+
state.history.setMaxLen(Math.max(20, Math.min(200, state.termWidth - 30)));
|
|
167
|
+
|
|
168
|
+
process.stdout.write('\x1b[?1049h');
|
|
169
|
+
process.stdout.write('\x1b[?25l');
|
|
170
|
+
process.stdout.write('\x1b[2J\x1b[H');
|
|
171
|
+
process.title = 'pyre';
|
|
172
|
+
|
|
173
|
+
if (state.mouseEnabled) {
|
|
174
|
+
process.stdout.write('\x1b[?1000h');
|
|
175
|
+
}
|
|
163
176
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
process.stdout.write('\x1b[2J\x1b[H');
|
|
167
|
-
process.title = 'pyre';
|
|
177
|
+
await tick();
|
|
178
|
+
restartTicker();
|
|
168
179
|
|
|
169
|
-
|
|
170
|
-
restartTicker();
|
|
180
|
+
if (opts.autoLog) startLogging();
|
|
171
181
|
|
|
172
|
-
|
|
182
|
+
readline.emitKeypressEvents(process.stdin);
|
|
183
|
+
if (process.stdin.isTTY) process.stdin.setRawMode(true);
|
|
184
|
+
process.stdin.resume();
|
|
173
185
|
|
|
174
|
-
|
|
175
|
-
if (process.stdin.isTTY) process.stdin.setRawMode(true);
|
|
176
|
-
process.stdin.resume();
|
|
186
|
+
process.stdout.on('resize', onResize);
|
|
177
187
|
|
|
178
|
-
|
|
188
|
+
state.keypressHandler = (str: string, key: readline.Key) => {
|
|
189
|
+
if (!key) return;
|
|
179
190
|
|
|
180
|
-
|
|
181
|
-
|
|
191
|
+
if (key.ctrl && key.name === 'c') {
|
|
192
|
+
stopLive();
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
182
195
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
196
|
+
if (state.inputMode) {
|
|
197
|
+
handleInputModeKey(str, key);
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
187
200
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
201
|
+
if (key.mouse) {
|
|
202
|
+
handleMouseClick(key.mouse);
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
192
205
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
setStatus(`Interval set to ${state.interval}s`);
|
|
198
|
-
return;
|
|
199
|
-
case '-':
|
|
200
|
-
state.interval = Math.max(1, state.interval - 1);
|
|
201
|
-
restartTicker();
|
|
202
|
-
setStatus(`Interval set to ${state.interval}s`);
|
|
203
|
-
return;
|
|
204
|
-
case '/':
|
|
205
|
-
state.inputMode = 'filter';
|
|
206
|
-
state.inputBuffer = state.processFilter;
|
|
207
|
-
render();
|
|
208
|
-
return;
|
|
209
|
-
}
|
|
206
|
+
if (key.sequence && key.sequence.startsWith('\x1b[M')) {
|
|
207
|
+
handleNormalMouse(key.sequence);
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
210
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
211
|
+
if (key.sequence && key.sequence.startsWith('\x1b[<')) {
|
|
212
|
+
handleSGRMouse(key.sequence);
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
switch (key.sequence) {
|
|
217
|
+
case '+':
|
|
218
|
+
state.interval += 1;
|
|
219
|
+
restartTicker();
|
|
220
|
+
setStatus(`Interval set to ${state.interval}s`);
|
|
221
|
+
return;
|
|
222
|
+
case '-':
|
|
223
|
+
state.interval = Math.max(1, state.interval - 1);
|
|
224
|
+
restartTicker();
|
|
225
|
+
setStatus(`Interval set to ${state.interval}s`);
|
|
226
|
+
return;
|
|
227
|
+
case '/':
|
|
228
|
+
state.inputMode = 'filter';
|
|
229
|
+
state.inputBuffer = state.processFilter;
|
|
230
|
+
render();
|
|
231
|
+
return;
|
|
232
|
+
case '0': case '1': case '2': case '3': case '4':
|
|
233
|
+
case '5': case '6': case '7': case '8': case '9': {
|
|
234
|
+
const tabMap: Record<string, string> = {
|
|
235
|
+
'0': 'disk', '1': 'cpu', '2': 'mem', '3': 'gpu', '4': 'power',
|
|
236
|
+
'5': 'battery', '6': 'thermal', '7': 'network', '8': 'packets', '9': 'tasks'
|
|
237
|
+
};
|
|
238
|
+
const panelId = tabMap[key.sequence];
|
|
239
|
+
if (panelId) {
|
|
240
|
+
state.activePanel = state.activePanel === panelId ? 'grid' : panelId;
|
|
241
|
+
setStatus(state.activePanel === 'grid' ? 'Grid view' : `${panelId} panel`);
|
|
242
|
+
render();
|
|
243
|
+
}
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
case 'P': {
|
|
247
|
+
state.activePanel = state.activePanel === 'process' ? 'grid' : 'process';
|
|
248
|
+
setStatus(state.activePanel === 'grid' ? 'Grid view' : 'Process panel');
|
|
227
249
|
render();
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
switch (key.name) {
|
|
255
|
+
case 'q':
|
|
256
|
+
stopLive();
|
|
228
257
|
break;
|
|
229
|
-
case '
|
|
230
|
-
|
|
231
|
-
|
|
258
|
+
case 'left': {
|
|
259
|
+
const currentIdx = state.PANEL_TABS.findIndex(t => t.id === state.activePanel);
|
|
260
|
+
if (currentIdx > 0) {
|
|
261
|
+
state.activePanel = state.PANEL_TABS[currentIdx - 1].id;
|
|
262
|
+
} else if (currentIdx === -1) {
|
|
263
|
+
state.activePanel = state.PANEL_TABS[state.PANEL_TABS.length - 1].id;
|
|
264
|
+
}
|
|
265
|
+
setStatus(`Panel: ${state.activePanel}`);
|
|
232
266
|
render();
|
|
233
267
|
break;
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
268
|
+
}
|
|
269
|
+
case 'right': {
|
|
270
|
+
const currentIdx = state.PANEL_TABS.findIndex(t => t.id === state.activePanel);
|
|
271
|
+
if (currentIdx < state.PANEL_TABS.length - 1) {
|
|
272
|
+
state.activePanel = state.PANEL_TABS[currentIdx + 1].id;
|
|
273
|
+
} else if (currentIdx === -1) {
|
|
274
|
+
state.activePanel = state.PANEL_TABS[0].id;
|
|
275
|
+
}
|
|
276
|
+
setStatus(`Panel: ${state.activePanel}`);
|
|
277
|
+
render();
|
|
278
|
+
break;
|
|
279
|
+
}
|
|
280
|
+
case 'c':
|
|
281
|
+
state.inputMode = 'customizer';
|
|
282
|
+
state.customizerIndex = 0;
|
|
283
|
+
render();
|
|
284
|
+
break;
|
|
285
|
+
case 'p':
|
|
286
|
+
state.paused = !state.paused;
|
|
287
|
+
setStatus(state.paused ? 'Paused' : 'Resumed');
|
|
288
|
+
render();
|
|
289
|
+
break;
|
|
290
|
+
case 'g':
|
|
291
|
+
state.showGraphs = !state.showGraphs;
|
|
292
|
+
render();
|
|
293
|
+
break;
|
|
294
|
+
case 'b':
|
|
295
|
+
state.graphMode = state.graphMode === 'spark' ? 'bar' : 'spark';
|
|
296
|
+
setStatus(`Graph mode: ${state.graphMode}`);
|
|
297
|
+
render();
|
|
298
|
+
break;
|
|
299
|
+
case 'd':
|
|
300
|
+
|
|
301
|
+
state.detailed = !state.detailed;
|
|
302
|
+
setStatus(`Detailed sensor mode: ${state.detailed ? 'on' : 'off'}`);
|
|
303
|
+
break;
|
|
304
|
+
case 's':
|
|
305
|
+
const sortCycle: SortMode[] = ['cpu', 'mem', 'pid', 'user', 'command', 'state', 'threads', 'runtime'];
|
|
306
|
+
const curIdx = sortCycle.indexOf(state.sortMode);
|
|
307
|
+
state.sortMode = sortCycle[(curIdx + 1) % sortCycle.length];
|
|
308
|
+
setStatus(`Sorting by ${state.sortMode}`);
|
|
309
|
+
render();
|
|
310
|
+
break;
|
|
311
|
+
case 'k':
|
|
312
|
+
state.inputMode = 'kill';
|
|
313
|
+
state.inputBuffer = '';
|
|
314
|
+
render();
|
|
315
|
+
break;
|
|
316
|
+
case 'S':
|
|
317
|
+
state.inputMode = 'signal';
|
|
318
|
+
state.inputBuffer = 'SIGTERM';
|
|
319
|
+
render();
|
|
320
|
+
break;
|
|
321
|
+
case 't':
|
|
322
|
+
state.treeView = !state.treeView;
|
|
323
|
+
setStatus(state.treeView ? 'Tree view enabled' : 'Flat view enabled');
|
|
324
|
+
render();
|
|
325
|
+
break;
|
|
326
|
+
case 'e':
|
|
327
|
+
exportSnapshot();
|
|
328
|
+
render();
|
|
329
|
+
break;
|
|
330
|
+
case 'l':
|
|
331
|
+
toggleLogging();
|
|
332
|
+
render();
|
|
333
|
+
break;
|
|
334
|
+
case 'f':
|
|
335
|
+
state.exportFormat = state.exportFormat === 'json' ? 'csv' : state.exportFormat === 'csv' ? 'tsv' : 'json';
|
|
336
|
+
setStatus(`Export format: ${state.exportFormat}`);
|
|
337
|
+
render();
|
|
338
|
+
break;
|
|
339
|
+
case 'tab':
|
|
340
|
+
cycleTab(1);
|
|
341
|
+
break;
|
|
342
|
+
case 'left':
|
|
343
|
+
cycleTab(-1);
|
|
344
|
+
break;
|
|
345
|
+
case 'right':
|
|
346
|
+
cycleTab(1);
|
|
347
|
+
break;
|
|
348
|
+
default:
|
|
349
|
+
if (str && str.length === 1) {
|
|
350
|
+
const tabId = tabKeyToId(str);
|
|
351
|
+
if (tabId) {
|
|
352
|
+
state.activePanel = tabId;
|
|
353
|
+
setStatus(`Panel: ${tabId.toUpperCase()}`);
|
|
354
|
+
render();
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
};
|
|
276
359
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
360
|
+
process.stdin.on('keypress', state.keypressHandler);
|
|
361
|
+
process.once('SIGINT', () => stopLive());
|
|
362
|
+
}
|
|
280
363
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
364
|
+
function tabKeyToId(str: string): string | null {
|
|
365
|
+
const map: Record<string, string> = { '1': 'cpu', '2': 'mem', '3': 'gpu', '4': 'power', '5': 'battery', '6': 'thermal', '7': 'network', '8': 'packets', '9': 'tasks', '0': 'disk', 'p': 'process' };
|
|
366
|
+
return map[str] ?? null;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function cycleTab(direction: number) {
|
|
370
|
+
const tabs = state.PANEL_TABS.map(t => t.id);
|
|
371
|
+
const current = state.activePanel === 'grid' ? -1 : tabs.indexOf(state.activePanel);
|
|
372
|
+
const next = (current + direction + tabs.length) % tabs.length;
|
|
373
|
+
state.activePanel = tabs[next];
|
|
374
|
+
setStatus(`Panel: ${state.activePanel.toUpperCase()}`);
|
|
375
|
+
render();
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
function handleNormalMouse(seq: string) {
|
|
379
|
+
if (seq.length < 6) return;
|
|
380
|
+
const cb = seq.charCodeAt(3) - 32;
|
|
381
|
+
const cx = seq.charCodeAt(4) - 32;
|
|
382
|
+
const cy = seq.charCodeAt(5) - 32;
|
|
383
|
+
if (cb === 0 || cb === 1) {
|
|
384
|
+
handleMouseClick(cy, cx);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
function handleSGRMouse(seq: string) {
|
|
389
|
+
const match = seq.match(/^\x1b\[<(\d+);(\d+);(\d+)([Mm])/);
|
|
390
|
+
if (!match) return;
|
|
391
|
+
const button = parseInt(match[1]);
|
|
392
|
+
const cx = parseInt(match[2]);
|
|
393
|
+
const cy = parseInt(match[3]);
|
|
394
|
+
if (button === 0 || button === 1) {
|
|
395
|
+
handleMouseClick(cy, cx);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
299
398
|
|
|
300
|
-
|
|
301
|
-
|
|
399
|
+
function handleMouseClick(y: number, x: number) {
|
|
400
|
+
if (y !== TAB_BAR_ROW) return;
|
|
401
|
+
const hitboxes = getTabHitboxes();
|
|
402
|
+
const hit = hitboxes.find(h => x >= h.start && x < h.end);
|
|
403
|
+
if (hit) {
|
|
404
|
+
state.activePanel = state.activePanel === hit.id ? 'grid' : (hit.id as typeof state.activePanel);
|
|
405
|
+
setStatus(state.activePanel === 'grid' ? 'Grid view' : `Panel: ${hit.id.toUpperCase()}`);
|
|
406
|
+
render();
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
export function stopLive() {
|
|
411
|
+
if (state.intervalHandle) {
|
|
412
|
+
clearInterval(state.intervalHandle);
|
|
413
|
+
state.intervalHandle = null;
|
|
414
|
+
}
|
|
415
|
+
if (state.statusTimer) {
|
|
416
|
+
clearTimeout(state.statusTimer);
|
|
417
|
+
state.statusTimer = null;
|
|
418
|
+
}
|
|
419
|
+
if (state.logStream) {
|
|
420
|
+
state.logStream.end();
|
|
421
|
+
state.logStream = null;
|
|
422
|
+
}
|
|
423
|
+
if (state.keypressHandler) {
|
|
424
|
+
process.stdin.removeListener('keypress', state.keypressHandler);
|
|
425
|
+
state.keypressHandler = null;
|
|
426
|
+
}
|
|
427
|
+
process.stdout.removeListener('resize', onResize);
|
|
302
428
|
|
|
303
|
-
|
|
304
|
-
|
|
429
|
+
state.running = false;
|
|
430
|
+
state.logging = false;
|
|
305
431
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
432
|
+
if (process.stdin.isTTY) process.stdin.setRawMode(false);
|
|
433
|
+
process.stdin.pause();
|
|
434
|
+
|
|
435
|
+
if (state.mouseEnabled) {
|
|
436
|
+
process.stdout.write('\x1b[?1000l');
|
|
437
|
+
}
|
|
438
|
+
process.stdout.write('\x1b[?25h');
|
|
439
|
+
process.stdout.write('\x1b[?1049l');
|
|
440
|
+
process.stdout.write('\x1b[2J\x1b[H');
|
|
309
441
|
|
|
310
|
-
|
|
311
|
-
|
|
442
|
+
process.exit(0);
|
|
443
|
+
}
|
package/src/live/render.ts
CHANGED
|
@@ -19,7 +19,8 @@ import type { StatsData } from '../monitors/index.js';
|
|
|
19
19
|
*/
|
|
20
20
|
function processRowBudget(): number {
|
|
21
21
|
const columns = gridColumns(state.termWidth);
|
|
22
|
-
const
|
|
22
|
+
const visibleCards = Object.values(state.visiblePanels).filter(v => v !== false).length;
|
|
23
|
+
const cardRows = Math.ceil(visibleCards / columns);
|
|
23
24
|
const reserved = 2 + cardRows * 8 + 11 + (state.showGraphs ? 9 : 0) + 4;
|
|
24
25
|
return Math.max(3, Math.min(40, state.termHeight - reserved));
|
|
25
26
|
}
|
|
@@ -65,6 +66,7 @@ function render() {
|
|
|
65
66
|
theme: state.currentTheme,
|
|
66
67
|
visible: state.visiblePanels,
|
|
67
68
|
treeView: state.treeView,
|
|
69
|
+
activePanel: state.activePanel,
|
|
68
70
|
})
|
|
69
71
|
);
|
|
70
72
|
|
|
@@ -76,21 +78,21 @@ function render() {
|
|
|
76
78
|
lines.push('');
|
|
77
79
|
lines.push(footerLine());
|
|
78
80
|
|
|
79
|
-
if (state.inputMode === 'customizer') {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
81
|
+
if (state.inputMode === 'customizer') {
|
|
82
|
+
lines.push(renderCustomizerOverlay());
|
|
83
|
+
} else if (state.inputMode === 'filter') {
|
|
84
|
+
lines.push(chalk.cyan(` Filter processes: ${state.inputBuffer}_`));
|
|
85
|
+
} else if (state.inputMode === 'kill') {
|
|
86
|
+
lines.push(chalk.cyan(` Kill PID: ${state.inputBuffer}_ (enter to confirm, esc to cancel)`));
|
|
87
|
+
} else if (state.inputMode === 'signal') {
|
|
88
|
+
const sigIdx = state.SIGNAL_OPTIONS.indexOf(state.inputBuffer as typeof state.SIGNAL_OPTIONS[number]);
|
|
89
|
+
const sigList = state.SIGNAL_OPTIONS.map((s, i) => i === sigIdx ? chalk.yellow(s) : chalk.dim(s)).join(' ');
|
|
90
|
+
lines.push(chalk.cyan(` Signal: ${state.inputBuffer}_`));
|
|
91
|
+
lines.push(chalk.dim(` ${sigList}`));
|
|
92
|
+
lines.push(chalk.dim(' ↑/↓ to select, Enter to confirm, Esc to cancel'));
|
|
93
|
+
} else if (state.statusMessage) {
|
|
94
|
+
lines.push(` ${state.statusMessage}`);
|
|
95
|
+
}
|
|
94
96
|
|
|
95
97
|
process.stdout.write('\x1b[2J\x1b[H');
|
|
96
98
|
process.stdout.write(lines.join('\n'));
|
|
@@ -104,27 +106,29 @@ function footerLine(): string {
|
|
|
104
106
|
['g', state.showGraphs ? 'hide graphs' : 'show graphs'],
|
|
105
107
|
['b', `graph:${state.graphMode}`],
|
|
106
108
|
['d', state.detailed ? 'basic' : 'detailed'],
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
109
|
+
['←/→', 'tab'],
|
|
110
|
+
['1-9', 'panels'],
|
|
111
|
+
['s', `sort:${state.sortMode}`],
|
|
112
|
+
['/', 'filter'],
|
|
113
|
+
['k', 'kill'],
|
|
114
|
+
['S', 'signal'],
|
|
115
|
+
['t', state.treeView ? 'flat' : 'tree'],
|
|
116
|
+
['e', 'export'],
|
|
117
|
+
['l', state.logging ? 'stop log' : 'log'],
|
|
118
|
+
['f', state.exportFormat],
|
|
119
|
+
['+/-', `${state.interval}s`],
|
|
120
|
+
];
|
|
118
121
|
const keyStr = keys.map(([k, label]) => `${chalk.hex('#50fa7b').bold(k)} ${chalk.dim(label)}`).join(chalk.dim(' | '));
|
|
119
122
|
|
|
120
123
|
|
|
121
124
|
const badges: string[] = [];
|
|
122
125
|
if (state.paused) badges.push(chalk.yellow.bold('⏸ PAUSED'));
|
|
123
126
|
if (state.logging) badges.push(chalk.red.bold('● REC'));
|
|
127
|
+
if (state.activePanel !== 'grid') badges.push(chalk.cyan.bold(`◉ ${state.activePanel.toUpperCase()}`));
|
|
124
128
|
const badgeStr = badges.join(' ');
|
|
125
129
|
|
|
126
130
|
return badgeStr ? `${keyStr} ${badgeStr}` : keyStr;
|
|
127
|
-
|
|
131
|
+
}
|
|
128
132
|
|
|
129
133
|
function checkAlerts(data: StatsData) {
|
|
130
134
|
const temp = data.cpu.temperature ?? data.thermal.temperatures?.cpu_die ?? null;
|