@silvery/examples 0.17.3 → 0.17.5

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 (111) hide show
  1. package/dist/UPNG-ShUlaTDh.mjs +5074 -0
  2. package/dist/__vite-browser-external-2447137e-Bopa5BFR.mjs +4 -0
  3. package/dist/_banner-A70_y2Vi.mjs +43 -0
  4. package/dist/ansi-0VXlUmNn.mjs +16397 -0
  5. package/dist/apng-B0gRaDVT.mjs +3 -0
  6. package/dist/apng-BTRDTfDW.mjs +68 -0
  7. package/dist/apps/aichat/index.mjs +1298 -0
  8. package/dist/apps/app-todo.mjs +138 -0
  9. package/dist/apps/async-data.mjs +203 -0
  10. package/dist/apps/cli-wizard.mjs +338 -0
  11. package/dist/apps/clipboard.mjs +197 -0
  12. package/dist/apps/components.mjs +863 -0
  13. package/dist/apps/data-explorer.mjs +482 -0
  14. package/dist/apps/dev-tools.mjs +396 -0
  15. package/dist/apps/explorer.mjs +697 -0
  16. package/dist/apps/gallery.mjs +765 -0
  17. package/dist/apps/inline-bench.mjs +115 -0
  18. package/dist/apps/kanban.mjs +279 -0
  19. package/dist/apps/layout-ref.mjs +186 -0
  20. package/dist/apps/outline.mjs +202 -0
  21. package/dist/apps/paste-demo.mjs +188 -0
  22. package/dist/apps/scroll.mjs +85 -0
  23. package/dist/apps/search-filter.mjs +286 -0
  24. package/dist/apps/selection.mjs +354 -0
  25. package/dist/apps/spatial-focus-demo.mjs +387 -0
  26. package/dist/apps/task-list.mjs +257 -0
  27. package/dist/apps/terminal-caps-demo.mjs +314 -0
  28. package/dist/apps/terminal.mjs +871 -0
  29. package/dist/apps/text-selection-demo.mjs +253 -0
  30. package/dist/apps/textarea.mjs +177 -0
  31. package/dist/apps/theme.mjs +660 -0
  32. package/dist/apps/transform.mjs +214 -0
  33. package/dist/apps/virtual-10k.mjs +421 -0
  34. package/dist/assets/resvgjs.darwin-arm64-BtufyGW1.node +0 -0
  35. package/dist/backends-Dj-11kZF.mjs +1179 -0
  36. package/dist/backends-U3QwStfO.mjs +3 -0
  37. package/dist/{cli.mjs → bin/cli.mjs} +15 -19
  38. package/dist/chunk-BSw8zbkd.mjs +37 -0
  39. package/dist/components/counter.mjs +47 -0
  40. package/dist/components/hello.mjs +30 -0
  41. package/dist/components/progress-bar.mjs +58 -0
  42. package/dist/components/select-list.mjs +84 -0
  43. package/dist/components/spinner.mjs +56 -0
  44. package/dist/components/text-input.mjs +61 -0
  45. package/dist/components/virtual-list.mjs +50 -0
  46. package/dist/flexily-zero-adapter-ByVzLTFP.mjs +3374 -0
  47. package/dist/gif-B6NGH5gs.mjs +3 -0
  48. package/dist/gif-CfkOF-iG.mjs +71 -0
  49. package/dist/gifenc-BI4ihP_T.mjs +728 -0
  50. package/dist/key-mapping-5oYQdAQE.mjs +3 -0
  51. package/dist/key-mapping-D4LR1go6.mjs +130 -0
  52. package/dist/layout/dashboard.mjs +1203 -0
  53. package/dist/layout/live-resize.mjs +302 -0
  54. package/dist/layout/overflow.mjs +69 -0
  55. package/dist/layout/text-layout.mjs +334 -0
  56. package/dist/node-nsrAOjH4.mjs +1083 -0
  57. package/dist/plugins-CT0DdV_E.mjs +3056 -0
  58. package/dist/resvg-js-Cnk2o49d.mjs +201 -0
  59. package/dist/src-9ZhfQyzD.mjs +814 -0
  60. package/dist/src-CUUOuRH6.mjs +5322 -0
  61. package/dist/src-jO3Zuzjj.mjs +23538 -0
  62. package/dist/usingCtx-CsEf0xO3.mjs +57 -0
  63. package/dist/yoga-adapter-BSQHuMV9.mjs +237 -0
  64. package/package.json +21 -14
  65. package/_banner.tsx +0 -60
  66. package/apps/aichat/components.tsx +0 -469
  67. package/apps/aichat/index.tsx +0 -220
  68. package/apps/aichat/script.ts +0 -460
  69. package/apps/aichat/state.ts +0 -325
  70. package/apps/aichat/types.ts +0 -19
  71. package/apps/app-todo.tsx +0 -201
  72. package/apps/async-data.tsx +0 -196
  73. package/apps/cli-wizard.tsx +0 -332
  74. package/apps/clipboard.tsx +0 -183
  75. package/apps/components.tsx +0 -658
  76. package/apps/data-explorer.tsx +0 -490
  77. package/apps/dev-tools.tsx +0 -395
  78. package/apps/explorer.tsx +0 -731
  79. package/apps/gallery.tsx +0 -653
  80. package/apps/inline-bench.tsx +0 -138
  81. package/apps/kanban.tsx +0 -265
  82. package/apps/layout-ref.tsx +0 -173
  83. package/apps/outline.tsx +0 -160
  84. package/apps/panes/index.tsx +0 -203
  85. package/apps/paste-demo.tsx +0 -185
  86. package/apps/scroll.tsx +0 -80
  87. package/apps/search-filter.tsx +0 -240
  88. package/apps/selection.tsx +0 -346
  89. package/apps/spatial-focus-demo.tsx +0 -372
  90. package/apps/task-list.tsx +0 -271
  91. package/apps/terminal-caps-demo.tsx +0 -317
  92. package/apps/terminal.tsx +0 -784
  93. package/apps/text-selection-demo.tsx +0 -193
  94. package/apps/textarea.tsx +0 -155
  95. package/apps/theme.tsx +0 -515
  96. package/apps/transform.tsx +0 -229
  97. package/apps/virtual-10k.tsx +0 -405
  98. package/apps/vterm-demo/index.tsx +0 -216
  99. package/components/counter.tsx +0 -49
  100. package/components/hello.tsx +0 -38
  101. package/components/progress-bar.tsx +0 -52
  102. package/components/select-list.tsx +0 -54
  103. package/components/spinner.tsx +0 -44
  104. package/components/text-input.tsx +0 -61
  105. package/components/virtual-list.tsx +0 -56
  106. package/dist/cli.d.mts +0 -1
  107. package/dist/cli.mjs.map +0 -1
  108. package/layout/dashboard.tsx +0 -953
  109. package/layout/live-resize.tsx +0 -282
  110. package/layout/overflow.tsx +0 -51
  111. package/layout/text-layout.tsx +0 -283
@@ -1,953 +0,0 @@
1
- /**
2
- * Dashboard Example
3
- *
4
- * A btop-style responsive dashboard demonstrating:
5
- * - Multi-pane flexbox layout with round borders
6
- * - Live-updating metrics with sparklines and progress bars
7
- * - Responsive 2-column / tabbed layout via useBoxRect()
8
- * - Semantic theme colors with severity-based color coding
9
- * - Process table with sorting
10
- */
11
-
12
- import React, { useState } from "react"
13
- import {
14
- render,
15
- Box,
16
- Text,
17
- Muted,
18
- Tabs,
19
- TabList,
20
- Tab,
21
- TabPanel,
22
- useBoxRect,
23
- useInput,
24
- useApp,
25
- useInterval,
26
- createTerm,
27
- type Key,
28
- } from "silvery"
29
- import { ExampleBanner, type ExampleMeta } from "../_banner.js"
30
-
31
- export const meta: ExampleMeta = {
32
- name: "Dashboard",
33
- description: "Responsive multi-pane dashboard with live metrics and charts",
34
- demo: true,
35
- features: ["Box flexGrow", "useBoxRect()", "responsive", "live data", "sparklines"],
36
- }
37
-
38
- // ============================================================================
39
- // Sparkline
40
- // ============================================================================
41
-
42
- const SPARK_CHARS = "▁▂▃▄▅▆▇█"
43
-
44
- function sparkline(values: number[]): string {
45
- return values.map((v) => SPARK_CHARS[Math.max(0, Math.min(7, v))] ?? SPARK_CHARS[0]).join("")
46
- }
47
-
48
- /** Fixed-width inline progress bar string: ████████░░░░ */
49
- function miniBar(pct: number, width: number): string {
50
- const filled = Math.round((pct / 100) * width)
51
- return "█".repeat(filled) + "░".repeat(width - filled)
52
- }
53
-
54
- // ============================================================================
55
- // Data Helpers
56
- // ============================================================================
57
-
58
- function jitter(base: number, range: number): number {
59
- return Math.max(0, Math.min(100, base + (Math.random() - 0.5) * range))
60
- }
61
-
62
- function pushHistory(history: number[], value: number, max = 20): number[] {
63
- const next = [...history]
64
- if (next.length >= max) next.shift()
65
- next.push(value)
66
- return next
67
- }
68
-
69
- function severityColor(pct: number): string {
70
- if (pct >= 80) return "$error"
71
- if (pct >= 60) return "$warning"
72
- return "$success"
73
- }
74
-
75
- function heatColor(temp: number): string {
76
- if (temp >= 75) return "$error"
77
- if (temp >= 60) return "$warning"
78
- return "$success"
79
- }
80
-
81
- // ============================================================================
82
- // State
83
- // ============================================================================
84
-
85
- interface CoreRow {
86
- label: string
87
- pct: number
88
- freq: string
89
- temp: number
90
- mode: string
91
- }
92
-
93
- interface MemoryMetrics {
94
- ramUsed: number
95
- ramTotal: number
96
- cached: number
97
- free: number
98
- slab: number
99
- apps: number
100
- wired: number
101
- buffers: number
102
- dirty: number
103
- shared: number
104
- reclaim: number
105
- swapUsed: number
106
- swapTotal: number
107
- history: number[]
108
- }
109
-
110
- interface NetworkMetrics {
111
- dlRate: number
112
- dlPeak: number
113
- ulRate: number
114
- ulPeak: number
115
- connEst: number
116
- listen: number
117
- syn: number
118
- drops: number
119
- rxPps: string
120
- txPps: string
121
- retrans: string
122
- rtt: string
123
- dlHistory: number[]
124
- ulHistory: number[]
125
- }
126
-
127
- interface ProcessInfo {
128
- pid: number
129
- name: string
130
- cpu: number
131
- memp: number
132
- mem: string
133
- status: "Running" | "Sleep" | "I/O wait"
134
- time: string
135
- io: string
136
- thr: number
137
- }
138
-
139
- interface DashboardState {
140
- cores: CoreRow[]
141
- totalCpu: number
142
- userCpu: number
143
- sysCpu: number
144
- waitCpu: number
145
- load: [number, number, number]
146
- avgTemp: number
147
- tasks: number
148
- avgFreq: string
149
- ctxPerSec: string
150
- uptime: string
151
- pkgPct: number
152
- power: number
153
- fan: number
154
- boostOn: boolean
155
- cpuHistory: number[]
156
- memory: MemoryMetrics
157
- network: NetworkMetrics
158
- processes: ProcessInfo[]
159
- }
160
-
161
- function createInitialState(): DashboardState {
162
- const cores: CoreRow[] = [
163
- { label: "cpu00", pct: 12, freq: "3.62", temp: 39, mode: "idle" },
164
- { label: "cpu01", pct: 28, freq: "3.79", temp: 42, mode: "balanced" },
165
- { label: "cpu02", pct: 44, freq: "4.02", temp: 47, mode: "steady" },
166
- { label: "cpu03", pct: 57, freq: "4.18", temp: 53, mode: "steady" },
167
- { label: "cpu04", pct: 63, freq: "4.31", temp: 61, mode: "warm" },
168
- { label: "cpu05", pct: 71, freq: "4.47", temp: 68, mode: "boost" },
169
- { label: "cpu06", pct: 79, freq: "4.62", temp: 72, mode: "boost" },
170
- { label: "cpu07", pct: 83, freq: "4.84", temp: 75, mode: "boost" },
171
- { label: "cpu08", pct: 88, freq: "5.02", temp: 77, mode: "turbo" },
172
- { label: "cpu09", pct: 94, freq: "5.21", temp: 81, mode: "turbo" },
173
- ]
174
-
175
- const cpuHistory = [
176
- 1, 2, 2, 3, 2, 4, 5, 4, 6, 5, 4, 6, 7, 6, 5, 6, 7, 6, 5, 4, 5, 6, 5, 7, 6, 5, 6, 7, 7, 6, 5, 4, 5, 6, 5, 4,
177
- ]
178
-
179
- const memHistory = [4, 4, 5, 5, 4, 5, 6, 5, 5, 6, 6, 5, 6, 6, 7, 6, 6, 5, 6, 6, 5, 5, 6, 5]
180
-
181
- const memory: MemoryMetrics = {
182
- ramUsed: 23.7,
183
- ramTotal: 32.0,
184
- cached: 5.9,
185
- free: 2.4,
186
- slab: 1.1,
187
- apps: 17.4,
188
- wired: 1.8,
189
- buffers: 0.612,
190
- dirty: 0.212,
191
- shared: 1.3,
192
- reclaim: 0.8,
193
- swapUsed: 2.1,
194
- swapTotal: 8.0,
195
- history: memHistory,
196
- }
197
-
198
- const dlHistory = [1, 2, 3, 5, 4, 6, 5, 7, 6, 4, 3, 5, 6, 7, 5, 4, 6, 7, 6, 5, 4, 6, 5, 4]
199
- const ulHistory = [0, 1, 1, 2, 2, 3, 2, 4, 3, 2, 1, 2, 3, 4, 3, 2, 1, 2, 3, 2, 2, 3, 2, 1]
200
-
201
- const network: NetworkMetrics = {
202
- dlRate: 428,
203
- dlPeak: 612,
204
- ulRate: 86,
205
- ulPeak: 143,
206
- connEst: 184,
207
- listen: 23,
208
- syn: 2,
209
- drops: 0,
210
- rxPps: "61.2kpps",
211
- txPps: "19.4kpps",
212
- retrans: "0.08%",
213
- rtt: "18ms",
214
- dlHistory,
215
- ulHistory,
216
- }
217
-
218
- const processes: ProcessInfo[] = [
219
- {
220
- pid: 31842,
221
- name: "bun dev --hot src/server.ts",
222
- cpu: 94.2,
223
- memp: 3.8,
224
- mem: "1.22G",
225
- status: "Running",
226
- time: "01:42:17",
227
- io: "24M/s",
228
- thr: 18,
229
- },
230
- {
231
- pid: 27114,
232
- name: "node /usr/bin/vite --host",
233
- cpu: 71.4,
234
- memp: 2.2,
235
- mem: "716M",
236
- status: "Running",
237
- time: "00:18:09",
238
- io: "12M/s",
239
- thr: 26,
240
- },
241
- {
242
- pid: 918,
243
- name: "postgres: checkpointer",
244
- cpu: 12.8,
245
- memp: 1.4,
246
- mem: "448M",
247
- status: "Sleep",
248
- time: "19:22:41",
249
- io: "3.1M/s",
250
- thr: 27,
251
- },
252
- {
253
- pid: 1023,
254
- name: "Code Helper (Renderer)",
255
- cpu: 9.6,
256
- memp: 4.8,
257
- mem: "1.53G",
258
- status: "Sleep",
259
- time: "07:13:51",
260
- io: "1.2M/s",
261
- thr: 44,
262
- },
263
- {
264
- pid: 2241,
265
- name: "docker-desktop",
266
- cpu: 8.9,
267
- memp: 6.3,
268
- mem: "2.01G",
269
- status: "Running",
270
- time: "11:08:04",
271
- io: "9.4M/s",
272
- thr: 61,
273
- },
274
- {
275
- pid: 1542,
276
- name: "redis-server *:6379",
277
- cpu: 6.7,
278
- memp: 0.9,
279
- mem: "289M",
280
- status: "Sleep",
281
- time: "02:51:17",
282
- io: "642K/s",
283
- thr: 8,
284
- },
285
- {
286
- pid: 612,
287
- name: "tailscaled --tun=userspace-networking",
288
- cpu: 5.4,
289
- memp: 0.4,
290
- mem: "132M",
291
- status: "Sleep",
292
- time: "05:44:22",
293
- io: "218K/s",
294
- thr: 19,
295
- },
296
- {
297
- pid: 33210,
298
- name: "bun test --watch",
299
- cpu: 4.2,
300
- memp: 1.1,
301
- mem: "356M",
302
- status: "Running",
303
- time: "00:06:38",
304
- io: "4.6M/s",
305
- thr: 12,
306
- },
307
- {
308
- pid: 1804,
309
- name: "nginx: worker process",
310
- cpu: 3.7,
311
- memp: 0.2,
312
- mem: "72M",
313
- status: "Sleep",
314
- time: "03:17:09",
315
- io: "118K/s",
316
- thr: 5,
317
- },
318
- {
319
- pid: 2877,
320
- name: "Chrome Helper (GPU)",
321
- cpu: 3.2,
322
- memp: 2.7,
323
- mem: "864M",
324
- status: "Sleep",
325
- time: "06:29:33",
326
- io: "2.3M/s",
327
- thr: 23,
328
- },
329
- {
330
- pid: 451,
331
- name: "kernel_task",
332
- cpu: 2.8,
333
- memp: 0.1,
334
- mem: "42M",
335
- status: "Running",
336
- time: "22:54:48",
337
- io: "0",
338
- thr: 179,
339
- },
340
- {
341
- pid: 1942,
342
- name: "syncthing serve --no-browser",
343
- cpu: 2.1,
344
- memp: 0.8,
345
- mem: "258M",
346
- status: "Sleep",
347
- time: "14:05:14",
348
- io: "884K/s",
349
- thr: 16,
350
- },
351
- {
352
- pid: 7621,
353
- name: "python scripts/indexer.py --incremental",
354
- cpu: 1.9,
355
- memp: 1.9,
356
- mem: "604M",
357
- status: "I/O wait",
358
- time: "00:43:58",
359
- io: "14M/s",
360
- thr: 9,
361
- },
362
- {
363
- pid: 266,
364
- name: "systemd-journald",
365
- cpu: 1.2,
366
- memp: 0.1,
367
- mem: "38M",
368
- status: "Sleep",
369
- time: "09:12:44",
370
- io: "96K/s",
371
- thr: 3,
372
- },
373
- {
374
- pid: 74,
375
- name: "zsh - bun gen-mockup.ts",
376
- cpu: 0.2,
377
- memp: 0.0,
378
- mem: "6M",
379
- status: "Running",
380
- time: "00:00:03",
381
- io: "0",
382
- thr: 1,
383
- },
384
- ]
385
-
386
- return {
387
- cores,
388
- totalCpu: 67,
389
- userCpu: 38,
390
- sysCpu: 12,
391
- waitCpu: 14,
392
- load: [4.21, 3.88, 3.11],
393
- avgTemp: 71,
394
- tasks: 287,
395
- avgFreq: "4.31",
396
- ctxPerSec: "128k",
397
- uptime: "12d 06h",
398
- pkgPct: 67,
399
- power: 84,
400
- fan: 1460,
401
- boostOn: true,
402
- cpuHistory,
403
- memory,
404
- network,
405
- processes,
406
- }
407
- }
408
-
409
- function tickState(prev: DashboardState): DashboardState {
410
- const cores = prev.cores.map((core) => {
411
- const pct = Math.max(0, Math.min(100, Math.round(jitter(core.pct, 10))))
412
- return { ...core, pct }
413
- })
414
-
415
- const totalCpu = Math.round(jitter(prev.totalCpu, 8))
416
- const cpuHistory = pushHistory(prev.cpuHistory, Math.max(0, Math.min(7, Math.round(totalCpu / 14))), 36)
417
-
418
- const ramPct = Math.round((prev.memory.ramUsed / prev.memory.ramTotal) * 100)
419
- const memHistory = pushHistory(prev.memory.history, Math.max(0, Math.min(7, Math.round(ramPct / 14))), 24)
420
- const memory: MemoryMetrics = { ...prev.memory, history: memHistory }
421
-
422
- const dlVal = Math.max(0, Math.min(7, Math.round(jitter(5, 4))))
423
- const ulVal = Math.max(0, Math.min(7, Math.round(jitter(2, 3))))
424
- const dlHistory = pushHistory(prev.network.dlHistory, dlVal, 24)
425
- const ulHistory = pushHistory(prev.network.ulHistory, ulVal, 24)
426
- const network: NetworkMetrics = { ...prev.network, dlHistory, ulHistory }
427
-
428
- const processes = prev.processes.map((p) => ({
429
- ...p,
430
- cpu: Math.max(0, Number(jitter(p.cpu, 4).toFixed(1))),
431
- }))
432
-
433
- return { ...prev, cores, totalCpu, cpuHistory, memory, network, processes }
434
- }
435
-
436
- // ============================================================================
437
- // Shared Components
438
- // ============================================================================
439
-
440
- function Sep() {
441
- return <Muted>{"┄".repeat(50)}</Muted>
442
- }
443
-
444
- function LR({ children }: { children: React.ReactNode }) {
445
- return (
446
- <Box justifyContent="space-between" wrap="truncate">
447
- {children}
448
- </Box>
449
- )
450
- }
451
-
452
- /** Label-value pair: `Label value` with muted label */
453
- function LV({ label, value, color }: { label: string; value: string | number; color?: string }) {
454
- return (
455
- <Box gap={1}>
456
- <Muted>{label}</Muted>
457
- <Text color={color}>{`${value}`}</Text>
458
- </Box>
459
- )
460
- }
461
-
462
- // ============================================================================
463
- // CPU Panel
464
- // ============================================================================
465
-
466
- function CpuSummary({ state }: { state: DashboardState }) {
467
- return (
468
- <>
469
- <LR>
470
- <Box gap={1} wrap="truncate">
471
- <Muted>Total</Muted>
472
- <Text color={severityColor(state.totalCpu)}>{`${state.totalCpu}%`}</Text>
473
- <Text color={severityColor(state.totalCpu)}>{miniBar(state.totalCpu, 24)}</Text>
474
- </Box>
475
- <Box gap={2} wrap="truncate">
476
- <LV
477
- label="Load"
478
- value={`${state.load[0].toFixed(2)} ${state.load[1].toFixed(2)} ${state.load[2].toFixed(2)}`}
479
- />
480
- <LV label="Temp" value={`${state.avgTemp}\u00B0C`} color={heatColor(state.avgTemp)} />
481
- <LV label="Tasks" value={state.tasks} />
482
- </Box>
483
- </LR>
484
- <LR>
485
- <Box gap={2} wrap="truncate">
486
- <LV label="User" value={`${state.userCpu}%`} color={severityColor(state.userCpu)} />
487
- <LV label="Sys" value={`${state.sysCpu}%`} color={severityColor(state.sysCpu)} />
488
- <LV label="Wait" value={`${state.waitCpu}%`} color={severityColor(state.waitCpu)} />
489
- </Box>
490
- <Box gap={2} wrap="truncate">
491
- <LV label="Avg" value={`${state.avgFreq}GHz`} />
492
- <LV label="Ctx/s" value={state.ctxPerSec} />
493
- <LV label="Uptime" value={state.uptime} />
494
- </Box>
495
- </LR>
496
- </>
497
- )
498
- }
499
-
500
- function CpuCore({ core }: { core: CoreRow }) {
501
- return (
502
- <LR>
503
- <Box gap={1} wrap="truncate">
504
- <Muted>{core.label}</Muted>
505
- <Text color={severityColor(core.pct)}>{`${core.pct}%`.padStart(4)}</Text>
506
- <Text color={severityColor(core.pct)}>{miniBar(core.pct, 24)}</Text>
507
- <Muted>{`${core.freq}GHz`}</Muted>
508
- </Box>
509
- <Box gap={2} wrap="truncate">
510
- <Box gap={1}>
511
- <Muted>temp</Muted>
512
- <Text color={heatColor(core.temp)}>{`${core.temp}\u00B0C`}</Text>
513
- </Box>
514
- <Text color={severityColor(core.pct)}>{core.mode}</Text>
515
- </Box>
516
- </LR>
517
- )
518
- }
519
-
520
- function CpuFooter({ state }: { state: DashboardState }) {
521
- return (
522
- <>
523
- <LR>
524
- <Box gap={1} wrap="truncate">
525
- <Muted>Pkg</Muted>
526
- <Text color={severityColor(state.pkgPct)}>{`${state.pkgPct}%`}</Text>
527
- <Text color={severityColor(state.pkgPct)}>{miniBar(state.pkgPct, 24)}</Text>
528
- </Box>
529
- <Box gap={2} wrap="truncate">
530
- <LV label="Power" value={`${state.power}W`} />
531
- <LV label="Fan" value={`${state.fan}RPM`} />
532
- <LV label="Boost" value="on" color="$success" />
533
- </Box>
534
- </LR>
535
- <LR>
536
- <Box gap={1} wrap="truncate">
537
- <Muted>History</Muted>
538
- <Text color="$primary">{sparkline(state.cpuHistory)}</Text>
539
- </Box>
540
- <Muted>60s</Muted>
541
- </LR>
542
- </>
543
- )
544
- }
545
-
546
- function CpuPanel({ state }: { state: DashboardState }) {
547
- return (
548
- <Box flexDirection="column" flexGrow={1}>
549
- <CpuSummary state={state} />
550
- <Sep />
551
- {state.cores.map((core) => (
552
- <CpuCore key={core.label} core={core} />
553
- ))}
554
- <Sep />
555
- <CpuFooter state={state} />
556
- </Box>
557
- )
558
- }
559
-
560
- // ============================================================================
561
- // Memory Panel
562
- // ============================================================================
563
-
564
- function MemoryPanel({ memory }: { memory: MemoryMetrics }) {
565
- const ramPct = Math.round((memory.ramUsed / memory.ramTotal) * 100)
566
- const swapPct = Math.round((memory.swapUsed / memory.swapTotal) * 100)
567
- const avail = (memory.ramTotal - memory.ramUsed).toFixed(1)
568
-
569
- return (
570
- <Box flexDirection="column" flexGrow={1}>
571
- <LR>
572
- <Box wrap="truncate">
573
- <Muted>{"RAM "}</Muted>
574
- <Text>{`${memory.ramUsed.toFixed(1)} / ${memory.ramTotal.toFixed(1)} GiB `}</Text>
575
- <Text color={severityColor(ramPct)}>{`${ramPct}% `}</Text>
576
- <Text color={severityColor(ramPct)}>{miniBar(ramPct, 12)}</Text>
577
- </Box>
578
- <Box gap={1} wrap="truncate">
579
- <Muted>avail</Muted>
580
- <Text>{`${avail}G`}</Text>
581
- </Box>
582
- </LR>
583
- <LR>
584
- <Box gap={2} wrap="truncate">
585
- <LV label="Used" value={`${memory.ramUsed.toFixed(1)}G`} />
586
- <LV label="Cache" value={`${memory.cached.toFixed(1)}G`} />
587
- </Box>
588
- <Box gap={2} wrap="truncate">
589
- <LV label="Free" value={`${memory.free.toFixed(1)}G`} />
590
- <LV label="Slab" value={`${memory.slab.toFixed(1)}G`} />
591
- </Box>
592
- </LR>
593
- <LR>
594
- <Box wrap="truncate">
595
- <Muted>{"Swap "}</Muted>
596
- <Text>{`${memory.swapUsed.toFixed(1)} / ${memory.swapTotal.toFixed(1)} GiB `}</Text>
597
- <Text color={severityColor(swapPct)}>{`${swapPct}% `}</Text>
598
- <Text color={severityColor(swapPct)}>{miniBar(swapPct, 12)}</Text>
599
- </Box>
600
- <Box gap={1} wrap="truncate">
601
- <Muted>zram</Muted>
602
- <Text>off</Text>
603
- </Box>
604
- </LR>
605
- <Sep />
606
- <LR>
607
- <Box gap={2} wrap="truncate">
608
- <LV label="Apps" value={`${memory.apps.toFixed(1)}G`} />
609
- <LV label="Wired" value={`${memory.wired.toFixed(1)}G`} />
610
- </Box>
611
- <LV label="Buffers" value="612M" />
612
- </LR>
613
- <LR>
614
- <Box gap={2} wrap="truncate">
615
- <LV label="Dirty" value="212M" />
616
- <LV label="Shared" value={`${memory.shared.toFixed(1)}G`} />
617
- </Box>
618
- <LV label="Reclaim" value={`${memory.reclaim.toFixed(1)}G`} />
619
- </LR>
620
- <LR>
621
- <Box gap={1} wrap="truncate">
622
- <Muted>Trend</Muted>
623
- <Text color="$primary">{sparkline(memory.history)}</Text>
624
- </Box>
625
- <Muted>30m</Muted>
626
- </LR>
627
- </Box>
628
- )
629
- }
630
-
631
- // ============================================================================
632
- // Network Panel
633
- // ============================================================================
634
-
635
- function NetworkPanel({ network }: { network: NetworkMetrics }) {
636
- const dlPct = Math.round((network.dlRate / 630) * 100)
637
- const ulPct = Math.round((network.ulRate / 400) * 100)
638
-
639
- return (
640
- <Box flexDirection="column" flexGrow={1}>
641
- <LR>
642
- <Box wrap="truncate">
643
- <Muted>{"DL "}</Muted>
644
- <Text>{`${network.dlRate} Mb/s `}</Text>
645
- <Text color={severityColor(dlPct)}>{`${dlPct}% `}</Text>
646
- <Text color={severityColor(dlPct)}>{miniBar(dlPct, 12)}</Text>
647
- </Box>
648
- <Box gap={1} wrap="truncate">
649
- <Muted>peak</Muted>
650
- <Text>{`${network.dlPeak}`}</Text>
651
- </Box>
652
- </LR>
653
- <LR>
654
- <Box wrap="truncate">
655
- <Muted>{"UL "}</Muted>
656
- <Text color="$info">{`${network.ulRate} Mb/s `}</Text>
657
- <Text color="$info">{`${ulPct}% `}</Text>
658
- <Text color="$info">{miniBar(ulPct, 12)}</Text>
659
- </Box>
660
- <Box gap={1} wrap="truncate">
661
- <Muted>peak</Muted>
662
- <Text>{`${network.ulPeak}`}</Text>
663
- </Box>
664
- </LR>
665
- <Sep />
666
- <LR>
667
- <Box gap={2} wrap="truncate">
668
- <LV label="Conn" value={`${network.connEst} est`} />
669
- <LV label="Listen" value={network.listen} />
670
- </Box>
671
- <Box gap={2} wrap="truncate">
672
- <LV label="SYN" value={network.syn} />
673
- <LV label="Drops" value={network.drops} />
674
- </Box>
675
- </LR>
676
- <LR>
677
- <Box gap={2} wrap="truncate">
678
- <LV label="Rx" value={network.rxPps} />
679
- <LV label="Tx" value={network.txPps} />
680
- </Box>
681
- <Box gap={2} wrap="truncate">
682
- <LV label="Retrans" value={network.retrans} />
683
- <LV label="RTT" value={network.rtt} />
684
- </Box>
685
- </LR>
686
- <LR>
687
- <Box gap={1} wrap="truncate">
688
- <Muted>DL</Muted>
689
- <Text color="$primary">{sparkline(network.dlHistory)}</Text>
690
- </Box>
691
- <Muted>60s</Muted>
692
- </LR>
693
- <LR>
694
- <Box gap={1} wrap="truncate">
695
- <Muted>UL</Muted>
696
- <Text color="$info">{sparkline(network.ulHistory)}</Text>
697
- </Box>
698
- <Muted>60s</Muted>
699
- </LR>
700
- </Box>
701
- )
702
- }
703
-
704
- // ============================================================================
705
- // Process Table
706
- // ============================================================================
707
-
708
- const COL = { pid: 6, name: 62, cpu: 6, memp: 6, mem: 9, status: 10, time: 10, io: 11, thr: 5 }
709
-
710
- function statusColor(status: ProcessInfo["status"]): string | undefined {
711
- switch (status) {
712
- case "Running":
713
- return "$success"
714
- case "I/O wait":
715
- return "$warning"
716
- case "Sleep":
717
- default:
718
- return "$muted"
719
- }
720
- }
721
-
722
- function ProcessHeader() {
723
- return (
724
- <Box wrap="clip">
725
- <Muted>{`${"PID".padStart(COL.pid)} `}</Muted>
726
- <Muted>{`${"NAME".padEnd(COL.name)} `}</Muted>
727
- <Text bold color="$primary">{`${"CPU%\u2193".padStart(COL.cpu)} `}</Text>
728
- <Muted>{`${"MEM%".padStart(COL.memp)} `}</Muted>
729
- <Muted>{`${"MEM".padStart(COL.mem)} `}</Muted>
730
- <Muted>{`${"STATUS".padEnd(COL.status)} `}</Muted>
731
- <Muted>{`${"TIME".padStart(COL.time)} `}</Muted>
732
- <Muted>{`${"IO".padStart(COL.io)} `}</Muted>
733
- <Muted>{`${"THR".padStart(COL.thr)}`}</Muted>
734
- </Box>
735
- )
736
- }
737
-
738
- function ProcessRow({ proc, isTop }: { proc: ProcessInfo; isTop: boolean }) {
739
- const cpuColor = severityColor(proc.cpu)
740
- const ioColor = proc.io === "0" ? "$muted" : "$primary"
741
-
742
- return (
743
- <Box wrap="clip">
744
- <Text>{`${String(proc.pid).padStart(COL.pid)} `}</Text>
745
- <Text bold={isTop}>{`${proc.name.padEnd(COL.name).slice(0, COL.name)} `}</Text>
746
- <Text bold={isTop} color={cpuColor}>{`${proc.cpu.toFixed(1).padStart(5)}% `}</Text>
747
- <Text color={severityColor(proc.memp * 10)}>{`${proc.memp.toFixed(1).padStart(5)}% `}</Text>
748
- <Text>{`${proc.mem.padStart(COL.mem)} `}</Text>
749
- <Text color={statusColor(proc.status)}>{`${proc.status.padEnd(COL.status)} `}</Text>
750
- <Text>{`${proc.time.padStart(COL.time)} `}</Text>
751
- <Text color={ioColor}>{`${proc.io.padStart(COL.io)} `}</Text>
752
- <Text>{`${String(proc.thr).padStart(COL.thr)}`}</Text>
753
- </Box>
754
- )
755
- }
756
-
757
- function ProcessFooter({ processes, state }: { processes: ProcessInfo[]; state: DashboardState }) {
758
- const running = processes.filter((p) => p.status === "Running").length
759
- const iowait = processes.filter((p) => p.status === "I/O wait").length
760
- const sleeping = 184 - running - iowait
761
- const ramPct = Math.round((state.memory.ramUsed / state.memory.ramTotal) * 100)
762
-
763
- return (
764
- <LR>
765
- <Box gap={2} wrap="truncate">
766
- <Muted>184 processes</Muted>
767
- <Text color="$success">{`${running} running`}</Text>
768
- <Muted>{`${sleeping} sleeping`}</Muted>
769
- <Text color="$warning">{`${iowait} iowait`}</Text>
770
- </Box>
771
- <Box gap={2} wrap="truncate">
772
- <LV label="Threads" value="1,942" />
773
- <LV label="CPU" value={`${state.totalCpu}%`} color={severityColor(state.totalCpu)} />
774
- <LV label="MEM" value={`${ramPct}%`} color={severityColor(ramPct)} />
775
- <Text color="$primary">{`428\u2193`}</Text>
776
- <Text color="$info">{`86\u2191`}</Text>
777
- </Box>
778
- </LR>
779
- )
780
- }
781
-
782
- function ProcessPanel({ state }: { state: DashboardState }) {
783
- const sorted = [...state.processes].sort((a, b) => b.cpu - a.cpu)
784
-
785
- return (
786
- <Box flexDirection="column" flexGrow={1}>
787
- <ProcessHeader />
788
- <Sep />
789
- {sorted.map((proc, i) => (
790
- <ProcessRow key={proc.pid} proc={proc} isTop={i === 0} />
791
- ))}
792
- <Sep />
793
- <ProcessFooter processes={state.processes} state={state} />
794
- </Box>
795
- )
796
- }
797
-
798
- // ============================================================================
799
- // Layouts
800
- // ============================================================================
801
-
802
- /** Panel with titled first row inside standard border */
803
- function Panel({
804
- title,
805
- subtitle,
806
- children,
807
- flexGrow,
808
- flexBasis,
809
- }: {
810
- title: string
811
- subtitle?: string
812
- children: React.ReactNode
813
- flexGrow?: number
814
- flexBasis?: number
815
- }) {
816
- return (
817
- <Box
818
- borderStyle="round"
819
- borderColor="$primary"
820
- paddingX={1}
821
- flexDirection="column"
822
- flexGrow={flexGrow}
823
- flexBasis={flexBasis}
824
- >
825
- <LR>
826
- <Text bold color="$primary">
827
- {` ${title} `}
828
- </Text>
829
- {subtitle && <Muted>{` ${subtitle} `}</Muted>}
830
- </LR>
831
- {children}
832
- </Box>
833
- )
834
- }
835
-
836
- function WideLayout({ state }: { state: DashboardState }) {
837
- return (
838
- <Box flexDirection="column" flexGrow={1}>
839
- {/* Top row: CPU (left ~60%) | Memory + Network stacked (right ~40%) */}
840
- <Box flexDirection="row" gap={1}>
841
- <Panel title="CPU / Compute" subtitle="10 logical" flexGrow={3} flexBasis={0}>
842
- <CpuPanel state={state} />
843
- </Panel>
844
- <Box flexDirection="column" flexGrow={2} flexBasis={0}>
845
- <Panel title="Memory" subtitle={`${state.memory.ramTotal.toFixed(0)} GiB`}>
846
- <MemoryPanel memory={state.memory} />
847
- </Panel>
848
- <Panel title="Network" subtitle="en0 • wifi6">
849
- <NetworkPanel network={state.network} />
850
- </Panel>
851
- </Box>
852
- </Box>
853
- {/* Bottom: Process table (full width) */}
854
- <Panel title="Processes" subtitle="sorted by CPU%">
855
- <ProcessPanel state={state} />
856
- </Panel>
857
- </Box>
858
- )
859
- }
860
-
861
- function NarrowLayout({ state }: { state: DashboardState }) {
862
- return (
863
- <Box flexDirection="column" flexGrow={1}>
864
- <Tabs defaultValue="cpu">
865
- <Box justifyContent="space-between" paddingX={1}>
866
- <TabList>
867
- <Tab value="cpu">CPU</Tab>
868
- <Tab value="memory">Memory</Tab>
869
- <Tab value="network">Network</Tab>
870
- <Tab value="processes">Processes</Tab>
871
- </TabList>
872
- </Box>
873
-
874
- <TabPanel value="cpu">
875
- <Panel title="CPU / Compute" subtitle="10 logical">
876
- <CpuPanel state={state} />
877
- </Panel>
878
- </TabPanel>
879
- <TabPanel value="memory">
880
- <Panel title="Memory" subtitle={`${state.memory.ramTotal.toFixed(0)} GiB`}>
881
- <MemoryPanel memory={state.memory} />
882
- </Panel>
883
- </TabPanel>
884
- <TabPanel value="network">
885
- <Panel title="Network" subtitle="en0 • wifi6">
886
- <NetworkPanel network={state.network} />
887
- </Panel>
888
- </TabPanel>
889
- <TabPanel value="processes">
890
- <Panel title="Processes" subtitle="sorted by CPU%">
891
- <ProcessPanel state={state} />
892
- </Panel>
893
- </TabPanel>
894
- </Tabs>
895
- </Box>
896
- )
897
- }
898
-
899
- // ============================================================================
900
- // Dashboard
901
- // ============================================================================
902
-
903
- export function Dashboard({ static: isStatic }: { static?: boolean } = {}) {
904
- const { exit } = useApp()
905
- const { width } = useBoxRect()
906
- const [state, setState] = useState(createInitialState)
907
- // Process table needs ~135 cols; below that switch to tabbed layout
908
- const isNarrow = width > 0 && width < 130
909
-
910
- useInterval(() => setState((prev) => tickState(prev)), 500, !isStatic)
911
-
912
- useInput((input: string, key: Key) => {
913
- if (input === "q" || key.escape) exit()
914
- })
915
-
916
- if (isNarrow) {
917
- return <NarrowLayout state={state} />
918
- }
919
-
920
- return (
921
- <Box flexDirection="column" flexGrow={1}>
922
- <Box wrap="truncate">
923
- <Text bold color="$primary">
924
- Silvery TUI
925
- </Text>
926
- <Muted>{" system monitor showcase "}</Muted>
927
- <Text color="$primary">devbox-01</Text>
928
- <Muted>{"┄".repeat(19)}</Muted>
929
- <Muted>14:27 UTC [h]help [1]cpu [2]mem [3]net [p]proc [/]filter [q]quit</Muted>
930
- </Box>
931
- <WideLayout state={state} />
932
- </Box>
933
- )
934
- }
935
-
936
- // ============================================================================
937
- // Main
938
- // ============================================================================
939
-
940
- export async function main() {
941
- using term = createTerm()
942
- const { waitUntilExit } = await render(
943
- <ExampleBanner meta={meta} controls="h/l tabs Esc/q quit">
944
- <Dashboard />
945
- </ExampleBanner>,
946
- term,
947
- )
948
- await waitUntilExit()
949
- }
950
-
951
- if (import.meta.main) {
952
- await main()
953
- }