silvery 0.3.0 → 0.4.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/README.md +41 -145
- package/dist/chalk.js +3 -0
- package/dist/chalk.js.map +11 -0
- package/dist/index.js +340 -0
- package/dist/index.js.map +282 -0
- package/dist/ink.js +129 -0
- package/dist/ink.js.map +140 -0
- package/dist/runtime.js +394 -0
- package/dist/runtime.js.map +286 -0
- package/dist/theme.js +343 -0
- package/dist/theme.js.map +286 -0
- package/dist/ui/animation.js +3 -0
- package/dist/ui/animation.js.map +15 -0
- package/dist/ui/ansi.js +3 -0
- package/dist/ui/ansi.js.map +10 -0
- package/dist/ui/cli.js +8 -0
- package/dist/ui/cli.js.map +14 -0
- package/dist/ui/display.js +4 -0
- package/dist/ui/display.js.map +10 -0
- package/dist/ui/image.js +4 -0
- package/dist/ui/image.js.map +15 -0
- package/dist/ui/input.js +3 -0
- package/dist/ui/input.js.map +11 -0
- package/dist/ui/progress.js +8 -0
- package/dist/ui/progress.js.map +20 -0
- package/dist/ui/react.js +3 -0
- package/dist/ui/react.js.map +15 -0
- package/dist/ui/utils.js +3 -0
- package/dist/ui/utils.js.map +10 -0
- package/dist/ui/wrappers.js +14 -0
- package/dist/ui/wrappers.js.map +19 -0
- package/dist/ui.js +17 -0
- package/dist/ui.js.map +20 -0
- package/package.json +67 -15
- package/src/index.ts +67 -1
- package/src/runtime.ts +4 -0
- package/src/theme.ts +4 -0
- package/src/ui/animation.ts +2 -0
- package/src/ui/ansi.ts +2 -0
- package/src/ui/cli.ts +2 -0
- package/src/ui/display.ts +2 -0
- package/src/ui/image.ts +2 -0
- package/src/ui/input.ts +2 -0
- package/src/ui/progress.ts +2 -0
- package/src/ui/react.ts +2 -0
- package/src/ui/utils.ts +2 -0
- package/src/ui/wrappers.ts +2 -0
- package/src/ui.ts +4 -0
- package/examples/CLAUDE.md +0 -75
- package/examples/_banner.tsx +0 -60
- package/examples/cli.ts +0 -228
- package/examples/index.md +0 -101
- package/examples/inline/inline-nontty.tsx +0 -98
- package/examples/inline/inline-progress.tsx +0 -79
- package/examples/inline/inline-simple.tsx +0 -63
- package/examples/inline/scrollback.tsx +0 -185
- package/examples/interactive/_input-debug.tsx +0 -110
- package/examples/interactive/_stdin-test.ts +0 -71
- package/examples/interactive/_textarea-bare.tsx +0 -45
- package/examples/interactive/aichat/components.tsx +0 -468
- package/examples/interactive/aichat/index.tsx +0 -207
- package/examples/interactive/aichat/script.ts +0 -460
- package/examples/interactive/aichat/state.ts +0 -326
- package/examples/interactive/aichat/types.ts +0 -19
- package/examples/interactive/app-todo.tsx +0 -198
- package/examples/interactive/async-data.tsx +0 -208
- package/examples/interactive/cli-wizard.tsx +0 -332
- package/examples/interactive/clipboard.tsx +0 -183
- package/examples/interactive/components.tsx +0 -463
- package/examples/interactive/data-explorer.tsx +0 -506
- package/examples/interactive/dev-tools.tsx +0 -379
- package/examples/interactive/explorer.tsx +0 -747
- package/examples/interactive/gallery.tsx +0 -652
- package/examples/interactive/inline-bench.tsx +0 -136
- package/examples/interactive/kanban.tsx +0 -267
- package/examples/interactive/layout-ref.tsx +0 -185
- package/examples/interactive/outline.tsx +0 -171
- package/examples/interactive/paste-demo.tsx +0 -198
- package/examples/interactive/scroll.tsx +0 -77
- package/examples/interactive/search-filter.tsx +0 -240
- package/examples/interactive/task-list.tsx +0 -279
- package/examples/interactive/terminal.tsx +0 -798
- package/examples/interactive/textarea.tsx +0 -103
- package/examples/interactive/theme.tsx +0 -336
- package/examples/interactive/transform.tsx +0 -256
- package/examples/interactive/virtual-10k.tsx +0 -413
- package/examples/kitty/canvas.tsx +0 -519
- package/examples/kitty/generate-samples.ts +0 -236
- package/examples/kitty/image-component.tsx +0 -273
- package/examples/kitty/images.tsx +0 -604
- package/examples/kitty/input.tsx +0 -371
- package/examples/kitty/keys.tsx +0 -378
- package/examples/kitty/paint.tsx +0 -1017
- package/examples/layout/dashboard.tsx +0 -551
- package/examples/layout/live-resize.tsx +0 -290
- package/examples/layout/overflow.tsx +0 -51
- package/examples/playground/README.md +0 -69
- package/examples/playground/build.ts +0 -61
- package/examples/playground/index.html +0 -420
- package/examples/playground/playground-app.tsx +0 -416
- package/examples/runtime/elm-counter.tsx +0 -206
- package/examples/runtime/hello-runtime.tsx +0 -73
- package/examples/runtime/pipe-composition.tsx +0 -184
- package/examples/runtime/run-counter.tsx +0 -78
- package/examples/runtime/runtime-counter.tsx +0 -197
- package/examples/screenshots/generate.tsx +0 -563
- package/examples/scrollback-perf.tsx +0 -230
- package/examples/viewer.tsx +0 -654
- package/examples/web/build.ts +0 -365
- package/examples/web/canvas-app.tsx +0 -80
- package/examples/web/canvas.html +0 -89
- package/examples/web/dom-app.tsx +0 -81
- package/examples/web/dom.html +0 -113
- package/examples/web/showcase-app.tsx +0 -107
- package/examples/web/showcase.html +0 -34
- package/examples/web/showcases/index.tsx +0 -56
- package/examples/web/viewer-app.tsx +0 -555
- package/examples/web/viewer.html +0 -30
- package/examples/web/xterm-app.tsx +0 -105
- package/examples/web/xterm.html +0 -118
|
@@ -1,551 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Dashboard Example
|
|
3
|
-
*
|
|
4
|
-
* A btop-style responsive dashboard demonstrating:
|
|
5
|
-
* - Tab navigation with compound Tabs component
|
|
6
|
-
* - Live-updating metrics with sparklines
|
|
7
|
-
* - Responsive 2-column / 1-column layout via useContentRect()
|
|
8
|
-
* - Semantic theme colors with severity-based color coding
|
|
9
|
-
* - Flex-based progress bars
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import React, { useState } from "react"
|
|
13
|
-
import {
|
|
14
|
-
render,
|
|
15
|
-
Box,
|
|
16
|
-
Text,
|
|
17
|
-
H2,
|
|
18
|
-
Strong,
|
|
19
|
-
Small,
|
|
20
|
-
Muted,
|
|
21
|
-
Kbd,
|
|
22
|
-
Tabs,
|
|
23
|
-
TabList,
|
|
24
|
-
Tab,
|
|
25
|
-
TabPanel,
|
|
26
|
-
ProgressBar,
|
|
27
|
-
useContentRect,
|
|
28
|
-
useInput,
|
|
29
|
-
useApp,
|
|
30
|
-
useInterval,
|
|
31
|
-
createTerm,
|
|
32
|
-
type Key,
|
|
33
|
-
} from "../../src/index.js"
|
|
34
|
-
import { ExampleBanner, type ExampleMeta } from "../_banner.js"
|
|
35
|
-
|
|
36
|
-
export const meta: ExampleMeta = {
|
|
37
|
-
name: "Dashboard",
|
|
38
|
-
description: "Responsive multi-pane dashboard with live metrics and charts",
|
|
39
|
-
demo: true,
|
|
40
|
-
features: ["Box flexGrow", "useContentRect()", "responsive", "live data", "sparklines"],
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// ============================================================================
|
|
44
|
-
// Sparkline
|
|
45
|
-
// ============================================================================
|
|
46
|
-
|
|
47
|
-
const SPARK_CHARS = "▁▂▃▄▅▆▇█"
|
|
48
|
-
|
|
49
|
-
function sparkline(values: number[], max: number): string {
|
|
50
|
-
return values.map((v) => SPARK_CHARS[Math.round((v / max) * 7)] ?? SPARK_CHARS[0]).join("")
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// ============================================================================
|
|
54
|
-
// Data Helpers
|
|
55
|
-
// ============================================================================
|
|
56
|
-
|
|
57
|
-
function jitter(base: number, range: number): number {
|
|
58
|
-
return Math.max(0, Math.min(100, base + (Math.random() - 0.5) * range))
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function initHistory(base: number, range: number, len: number): number[] {
|
|
62
|
-
return Array.from({ length: len }, () => jitter(base, range))
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function pushHistory(history: number[], value: number): number[] {
|
|
66
|
-
const next = [...history]
|
|
67
|
-
if (next.length >= 20) next.shift()
|
|
68
|
-
next.push(value)
|
|
69
|
-
return next
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
function severityColor(pct: number): string {
|
|
73
|
-
if (pct > 80) return "$error"
|
|
74
|
-
if (pct > 60) return "$warning"
|
|
75
|
-
return "$success"
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// ============================================================================
|
|
79
|
-
// State
|
|
80
|
-
// ============================================================================
|
|
81
|
-
|
|
82
|
-
interface CoreMetrics {
|
|
83
|
-
usage: number
|
|
84
|
-
history: number[]
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
interface MemoryMetrics {
|
|
88
|
-
used: number
|
|
89
|
-
cached: number
|
|
90
|
-
buffers: number
|
|
91
|
-
free: number
|
|
92
|
-
swap: number
|
|
93
|
-
swapTotal: number
|
|
94
|
-
history: number[]
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
interface NetworkMetrics {
|
|
98
|
-
downloadRate: number
|
|
99
|
-
uploadRate: number
|
|
100
|
-
downloadHistory: number[]
|
|
101
|
-
uploadHistory: number[]
|
|
102
|
-
connections: number
|
|
103
|
-
packetsIn: number
|
|
104
|
-
packetsOut: number
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
interface ProcessInfo {
|
|
108
|
-
pid: number
|
|
109
|
-
name: string
|
|
110
|
-
cpu: number
|
|
111
|
-
mem: number
|
|
112
|
-
status: string
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
function createInitialState() {
|
|
116
|
-
const cores: CoreMetrics[] = Array.from({ length: 8 }, (_, i) => ({
|
|
117
|
-
usage: 20 + Math.random() * 60,
|
|
118
|
-
history: initHistory(30 + i * 5, 20, 20),
|
|
119
|
-
}))
|
|
120
|
-
|
|
121
|
-
const memory: MemoryMetrics = {
|
|
122
|
-
used: 8.2,
|
|
123
|
-
cached: 3.1,
|
|
124
|
-
buffers: 1.4,
|
|
125
|
-
free: 3.3,
|
|
126
|
-
swap: 0.8,
|
|
127
|
-
swapTotal: 4.0,
|
|
128
|
-
history: initHistory(55, 10, 20),
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
const network: NetworkMetrics = {
|
|
132
|
-
downloadRate: 42.5,
|
|
133
|
-
uploadRate: 12.3,
|
|
134
|
-
downloadHistory: initHistory(40, 30, 20),
|
|
135
|
-
uploadHistory: initHistory(12, 10, 20),
|
|
136
|
-
connections: 147,
|
|
137
|
-
packetsIn: 1842,
|
|
138
|
-
packetsOut: 923,
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
const processes: ProcessInfo[] = [
|
|
142
|
-
{ pid: 1201, name: "node", cpu: 24.3, mem: 4.2, status: "running" },
|
|
143
|
-
{ pid: 892, name: "chrome", cpu: 18.7, mem: 12.1, status: "running" },
|
|
144
|
-
{ pid: 3456, name: "vscode", cpu: 12.1, mem: 8.4, status: "running" },
|
|
145
|
-
{ pid: 2103, name: "postgres", cpu: 8.9, mem: 3.7, status: "sleeping" },
|
|
146
|
-
{ pid: 4521, name: "docker", cpu: 6.2, mem: 5.1, status: "running" },
|
|
147
|
-
{ pid: 1893, name: "nginx", cpu: 3.4, mem: 1.2, status: "sleeping" },
|
|
148
|
-
{ pid: 7234, name: "redis", cpu: 2.1, mem: 0.8, status: "sleeping" },
|
|
149
|
-
{ pid: 5612, name: "bun", cpu: 1.8, mem: 2.3, status: "running" },
|
|
150
|
-
]
|
|
151
|
-
|
|
152
|
-
return { cores, memory, network, processes }
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
function tickState(prev: ReturnType<typeof createInitialState>) {
|
|
156
|
-
const cores = prev.cores.map((core) => {
|
|
157
|
-
const usage = jitter(core.usage, 15)
|
|
158
|
-
return { usage, history: pushHistory(core.history, usage) }
|
|
159
|
-
})
|
|
160
|
-
|
|
161
|
-
const totalMem = prev.memory.used + prev.memory.cached + prev.memory.buffers + prev.memory.free
|
|
162
|
-
const usedJitter = (jitter((prev.memory.used / totalMem) * 100, 3) / 100) * totalMem
|
|
163
|
-
const memory: MemoryMetrics = {
|
|
164
|
-
...prev.memory,
|
|
165
|
-
used: Math.max(4, usedJitter),
|
|
166
|
-
swap: (jitter((prev.memory.swap / prev.memory.swapTotal) * 100, 5) / 100) * prev.memory.swapTotal,
|
|
167
|
-
history: pushHistory(prev.memory.history, (usedJitter / totalMem) * 100),
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
const downloadRate = jitter(prev.network.downloadRate, 20)
|
|
171
|
-
const uploadRate = jitter(prev.network.uploadRate, 8)
|
|
172
|
-
const network: NetworkMetrics = {
|
|
173
|
-
downloadRate,
|
|
174
|
-
uploadRate,
|
|
175
|
-
downloadHistory: pushHistory(prev.network.downloadHistory, downloadRate),
|
|
176
|
-
uploadHistory: pushHistory(prev.network.uploadHistory, uploadRate),
|
|
177
|
-
connections: Math.max(50, Math.round(jitter(prev.network.connections, 20))),
|
|
178
|
-
packetsIn: Math.max(100, Math.round(jitter(prev.network.packetsIn, 200))),
|
|
179
|
-
packetsOut: Math.max(50, Math.round(jitter(prev.network.packetsOut, 100))),
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
const processes = prev.processes.map((p) => ({
|
|
183
|
-
...p,
|
|
184
|
-
cpu: Math.max(0.1, jitter(p.cpu, 4)),
|
|
185
|
-
mem: Math.max(0.1, jitter(p.mem, 1)),
|
|
186
|
-
}))
|
|
187
|
-
|
|
188
|
-
return { cores, memory, network, processes }
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
// ============================================================================
|
|
192
|
-
// Components
|
|
193
|
-
// ============================================================================
|
|
194
|
-
|
|
195
|
-
function SectionHeader({ children }: { children: React.ReactNode }): JSX.Element {
|
|
196
|
-
return <H2>{children}</H2>
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
function LabelValue({ label, value, color }: { label: string; value: string; color?: string }): JSX.Element {
|
|
200
|
-
return (
|
|
201
|
-
<Box gap={1}>
|
|
202
|
-
<Muted>{label}</Muted>
|
|
203
|
-
<Text color={color}>
|
|
204
|
-
<Strong>{value}</Strong>
|
|
205
|
-
</Text>
|
|
206
|
-
</Box>
|
|
207
|
-
)
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
// --- CPU Tab ---
|
|
211
|
-
|
|
212
|
-
function CpuCore({ index, core }: { index: number; core: CoreMetrics }): JSX.Element {
|
|
213
|
-
const pct = Math.round(core.usage)
|
|
214
|
-
const color = severityColor(pct)
|
|
215
|
-
return (
|
|
216
|
-
<Box flexDirection="column">
|
|
217
|
-
<Box justifyContent="space-between">
|
|
218
|
-
<Text>
|
|
219
|
-
Core {index}{" "}
|
|
220
|
-
<Text color={color}>
|
|
221
|
-
<Strong>{String(pct).padStart(3)}%</Strong>
|
|
222
|
-
</Text>
|
|
223
|
-
</Text>
|
|
224
|
-
<Small>{sparkline(core.history, 100)}</Small>
|
|
225
|
-
</Box>
|
|
226
|
-
<ProgressBar value={core.usage / 100} color={color} showPercentage={false} />
|
|
227
|
-
</Box>
|
|
228
|
-
)
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
function CpuPane({ cores }: { cores: CoreMetrics[] }): JSX.Element {
|
|
232
|
-
const avgCpu = cores.reduce((sum, c) => sum + c.usage, 0) / cores.length
|
|
233
|
-
const maxCpu = Math.max(...cores.map((c) => c.usage))
|
|
234
|
-
const load1 = ((avgCpu / 100) * 8 * 0.8 + Math.random() * 0.5).toFixed(2)
|
|
235
|
-
const load5 = ((avgCpu / 100) * 8 * 0.7 + Math.random() * 0.3).toFixed(2)
|
|
236
|
-
const load15 = ((avgCpu / 100) * 8 * 0.6 + Math.random() * 0.2).toFixed(2)
|
|
237
|
-
|
|
238
|
-
return (
|
|
239
|
-
<Box flexDirection="column" gap={1} flexGrow={1}>
|
|
240
|
-
<SectionHeader>CPU</SectionHeader>
|
|
241
|
-
<Box gap={2}>
|
|
242
|
-
<LabelValue label="Avg:" value={`${Math.round(avgCpu)}%`} color={severityColor(avgCpu)} />
|
|
243
|
-
<LabelValue label="Max:" value={`${Math.round(maxCpu)}%`} color={severityColor(maxCpu)} />
|
|
244
|
-
<LabelValue label="Load:" value={`${load1} ${load5} ${load15}`} />
|
|
245
|
-
</Box>
|
|
246
|
-
{cores.map((core, i) => (
|
|
247
|
-
<CpuCore key={i} index={i} core={core} />
|
|
248
|
-
))}
|
|
249
|
-
</Box>
|
|
250
|
-
)
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
// --- Memory Tab ---
|
|
254
|
-
|
|
255
|
-
function StackedBar({ segments }: { segments: { value: number; color: string; char?: string }[] }): JSX.Element {
|
|
256
|
-
return (
|
|
257
|
-
<Box>
|
|
258
|
-
{segments.map((seg, i) => (
|
|
259
|
-
<Box key={i} flexGrow={Math.max(1, Math.round(seg.value * 100))}>
|
|
260
|
-
<Text color={seg.color}>{(seg.char ?? "█").repeat(80)}</Text>
|
|
261
|
-
</Box>
|
|
262
|
-
))}
|
|
263
|
-
</Box>
|
|
264
|
-
)
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
function MemoryPane({ memory }: { memory: MemoryMetrics }): JSX.Element {
|
|
268
|
-
const total = memory.used + memory.cached + memory.buffers + memory.free
|
|
269
|
-
const usedPct = (memory.used / total) * 100
|
|
270
|
-
const swapPct = (memory.swap / memory.swapTotal) * 100
|
|
271
|
-
|
|
272
|
-
return (
|
|
273
|
-
<Box flexDirection="column" gap={1} flexGrow={1}>
|
|
274
|
-
<SectionHeader>Memory</SectionHeader>
|
|
275
|
-
<Box gap={2}>
|
|
276
|
-
<LabelValue label="Total:" value={`${total.toFixed(1)} GB`} />
|
|
277
|
-
<LabelValue label="Used:" value={`${memory.used.toFixed(1)} GB`} color={severityColor(usedPct)} />
|
|
278
|
-
</Box>
|
|
279
|
-
<Box flexDirection="column">
|
|
280
|
-
<Muted>Memory Breakdown</Muted>
|
|
281
|
-
<StackedBar
|
|
282
|
-
segments={[
|
|
283
|
-
{ value: memory.used / total, color: severityColor(usedPct) },
|
|
284
|
-
{ value: memory.cached / total, color: "$info" },
|
|
285
|
-
{ value: memory.buffers / total, color: "$primary" },
|
|
286
|
-
{ value: memory.free / total, color: "$muted", char: "░" },
|
|
287
|
-
]}
|
|
288
|
-
/>
|
|
289
|
-
<Box gap={2}>
|
|
290
|
-
<Text color={severityColor(usedPct)}>
|
|
291
|
-
{"█"} Used {memory.used.toFixed(1)}G
|
|
292
|
-
</Text>
|
|
293
|
-
<Text color="$info">
|
|
294
|
-
{"█"} Cache {memory.cached.toFixed(1)}G
|
|
295
|
-
</Text>
|
|
296
|
-
<Text color="$primary">
|
|
297
|
-
{"█"} Buf {memory.buffers.toFixed(1)}G
|
|
298
|
-
</Text>
|
|
299
|
-
<Muted>
|
|
300
|
-
{"░"} Free {memory.free.toFixed(1)}G
|
|
301
|
-
</Muted>
|
|
302
|
-
</Box>
|
|
303
|
-
</Box>
|
|
304
|
-
<Box flexDirection="column">
|
|
305
|
-
<Muted>
|
|
306
|
-
Swap: {memory.swap.toFixed(1)}G / {memory.swapTotal.toFixed(1)}G
|
|
307
|
-
</Muted>
|
|
308
|
-
<ProgressBar value={swapPct / 100} color={severityColor(swapPct)} showPercentage />
|
|
309
|
-
</Box>
|
|
310
|
-
<Box flexDirection="column">
|
|
311
|
-
<Muted>Top Consumers</Muted>
|
|
312
|
-
<Box gap={2}>
|
|
313
|
-
<Text>
|
|
314
|
-
chrome <Strong color="$warning">12.1G</Strong>
|
|
315
|
-
</Text>
|
|
316
|
-
<Text>
|
|
317
|
-
vscode <Strong color="$primary">8.4G</Strong>
|
|
318
|
-
</Text>
|
|
319
|
-
<Text>
|
|
320
|
-
docker <Strong color="$primary">5.1G</Strong>
|
|
321
|
-
</Text>
|
|
322
|
-
</Box>
|
|
323
|
-
</Box>
|
|
324
|
-
<Box flexDirection="column">
|
|
325
|
-
<Muted>History</Muted>
|
|
326
|
-
<Text color="$primary">{sparkline(memory.history, 100)}</Text>
|
|
327
|
-
</Box>
|
|
328
|
-
</Box>
|
|
329
|
-
)
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
// --- Network Tab ---
|
|
333
|
-
|
|
334
|
-
function NetworkPane({ network }: { network: NetworkMetrics }): JSX.Element {
|
|
335
|
-
return (
|
|
336
|
-
<Box flexDirection="column" gap={1} flexGrow={1}>
|
|
337
|
-
<SectionHeader>Network</SectionHeader>
|
|
338
|
-
<Box flexDirection="column">
|
|
339
|
-
<Box justifyContent="space-between">
|
|
340
|
-
<Text color="$success">
|
|
341
|
-
{"↓"} Download: <Strong>{network.downloadRate.toFixed(1)} MB/s</Strong>
|
|
342
|
-
</Text>
|
|
343
|
-
<Small>{sparkline(network.downloadHistory, 100)}</Small>
|
|
344
|
-
</Box>
|
|
345
|
-
<ProgressBar value={network.downloadRate / 100} color="$success" showPercentage={false} />
|
|
346
|
-
</Box>
|
|
347
|
-
<Box flexDirection="column">
|
|
348
|
-
<Box justifyContent="space-between">
|
|
349
|
-
<Text color="$info">
|
|
350
|
-
{"↑"} Upload: <Strong>{network.uploadRate.toFixed(1)} MB/s</Strong>
|
|
351
|
-
</Text>
|
|
352
|
-
<Small>{sparkline(network.uploadHistory, 40)}</Small>
|
|
353
|
-
</Box>
|
|
354
|
-
<ProgressBar value={network.uploadRate / 40} color="$info" showPercentage={false} />
|
|
355
|
-
</Box>
|
|
356
|
-
<Box borderStyle="round" borderColor="$border" paddingX={1} flexDirection="column">
|
|
357
|
-
<Muted>Connection Stats</Muted>
|
|
358
|
-
<Box gap={2}>
|
|
359
|
-
<LabelValue label="Connections:" value={String(network.connections)} />
|
|
360
|
-
<LabelValue label="Packets In:" value={String(network.packetsIn)} />
|
|
361
|
-
<LabelValue label="Packets Out:" value={String(network.packetsOut)} />
|
|
362
|
-
</Box>
|
|
363
|
-
<Box gap={2}>
|
|
364
|
-
<LabelValue label="Interface:" value="en0" />
|
|
365
|
-
<LabelValue label="MTU:" value="1500" />
|
|
366
|
-
<LabelValue label="Duplex:" value="full" />
|
|
367
|
-
</Box>
|
|
368
|
-
</Box>
|
|
369
|
-
</Box>
|
|
370
|
-
)
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
// --- Processes Tab ---
|
|
374
|
-
|
|
375
|
-
function ProcessRow({ proc, isTop }: { proc: ProcessInfo; isTop: boolean }): JSX.Element {
|
|
376
|
-
const cpuColor = severityColor(proc.cpu)
|
|
377
|
-
return (
|
|
378
|
-
<Box gap={1}>
|
|
379
|
-
<Text color="$muted">{String(proc.pid).padStart(5)}</Text>
|
|
380
|
-
<Text bold={isTop}>{proc.name.padEnd(12)}</Text>
|
|
381
|
-
<Text color={cpuColor}>{proc.cpu.toFixed(1).padStart(5)}%</Text>
|
|
382
|
-
<Text color="$primary">{proc.mem.toFixed(1).padStart(5)}%</Text>
|
|
383
|
-
<Text color={proc.status === "running" ? "$success" : "$muted"}>{proc.status}</Text>
|
|
384
|
-
</Box>
|
|
385
|
-
)
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
function ProcessPane({ processes }: { processes: ProcessInfo[] }): JSX.Element {
|
|
389
|
-
const sorted = [...processes].sort((a, b) => b.cpu - a.cpu)
|
|
390
|
-
|
|
391
|
-
return (
|
|
392
|
-
<Box flexDirection="column" gap={1} flexGrow={1}>
|
|
393
|
-
<SectionHeader>Processes</SectionHeader>
|
|
394
|
-
<Box gap={1}>
|
|
395
|
-
<Muted>{" PID".padStart(5)}</Muted>
|
|
396
|
-
<Muted>{"Name".padEnd(12)}</Muted>
|
|
397
|
-
<Muted>{" CPU".padStart(5)}</Muted>
|
|
398
|
-
<Muted>{" MEM".padStart(5)}</Muted>
|
|
399
|
-
<Muted>Status</Muted>
|
|
400
|
-
</Box>
|
|
401
|
-
{sorted.map((proc, i) => (
|
|
402
|
-
<ProcessRow key={proc.pid} proc={proc} isTop={i === 0} />
|
|
403
|
-
))}
|
|
404
|
-
<Box gap={2} paddingTop={1}>
|
|
405
|
-
<LabelValue label="Total:" value={`${processes.length} processes`} />
|
|
406
|
-
<LabelValue
|
|
407
|
-
label="Running:"
|
|
408
|
-
value={String(processes.filter((p) => p.status === "running").length)}
|
|
409
|
-
color="$success"
|
|
410
|
-
/>
|
|
411
|
-
<LabelValue
|
|
412
|
-
label="Sleeping:"
|
|
413
|
-
value={String(processes.filter((p) => p.status === "sleeping").length)}
|
|
414
|
-
color="$muted"
|
|
415
|
-
/>
|
|
416
|
-
</Box>
|
|
417
|
-
</Box>
|
|
418
|
-
)
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
// --- Responsive Layout ---
|
|
422
|
-
|
|
423
|
-
function WideLayout({
|
|
424
|
-
cores,
|
|
425
|
-
memory,
|
|
426
|
-
network,
|
|
427
|
-
processes,
|
|
428
|
-
}: {
|
|
429
|
-
cores: CoreMetrics[]
|
|
430
|
-
memory: MemoryMetrics
|
|
431
|
-
network: NetworkMetrics
|
|
432
|
-
processes: ProcessInfo[]
|
|
433
|
-
}): JSX.Element {
|
|
434
|
-
return (
|
|
435
|
-
<Box flexDirection="column" flexGrow={1} gap={1}>
|
|
436
|
-
<Box flexDirection="row" gap={1} flexGrow={1}>
|
|
437
|
-
<Box flexGrow={1} borderStyle="round" borderColor="$border" paddingX={1} paddingY={1} flexDirection="column">
|
|
438
|
-
<CpuPane cores={cores} />
|
|
439
|
-
</Box>
|
|
440
|
-
<Box flexGrow={1} borderStyle="round" borderColor="$border" paddingX={1} paddingY={1} flexDirection="column">
|
|
441
|
-
<MemoryPane memory={memory} />
|
|
442
|
-
</Box>
|
|
443
|
-
</Box>
|
|
444
|
-
<Box flexDirection="row" gap={1} flexGrow={1}>
|
|
445
|
-
<Box flexGrow={1} borderStyle="round" borderColor="$border" paddingX={1} paddingY={1} flexDirection="column">
|
|
446
|
-
<NetworkPane network={network} />
|
|
447
|
-
</Box>
|
|
448
|
-
<Box flexGrow={1} borderStyle="round" borderColor="$border" paddingX={1} paddingY={1} flexDirection="column">
|
|
449
|
-
<ProcessPane processes={processes} />
|
|
450
|
-
</Box>
|
|
451
|
-
</Box>
|
|
452
|
-
</Box>
|
|
453
|
-
)
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
// ============================================================================
|
|
457
|
-
// Dashboard
|
|
458
|
-
// ============================================================================
|
|
459
|
-
|
|
460
|
-
export function Dashboard(): JSX.Element {
|
|
461
|
-
const { exit } = useApp()
|
|
462
|
-
const { width } = useContentRect()
|
|
463
|
-
const [state, setState] = useState(createInitialState)
|
|
464
|
-
const [tick, setTick] = useState(0)
|
|
465
|
-
const isWide = width > 100
|
|
466
|
-
|
|
467
|
-
useInterval(() => {
|
|
468
|
-
setState((prev) => tickState(prev))
|
|
469
|
-
setTick((t) => t + 1)
|
|
470
|
-
}, 500)
|
|
471
|
-
|
|
472
|
-
useInput((input: string, key: Key) => {
|
|
473
|
-
if (input === "q" || key.escape) exit()
|
|
474
|
-
})
|
|
475
|
-
|
|
476
|
-
if (isWide) {
|
|
477
|
-
return (
|
|
478
|
-
<Box flexDirection="column" flexGrow={1}>
|
|
479
|
-
<Box justifyContent="space-between" paddingX={1}>
|
|
480
|
-
<Text>
|
|
481
|
-
<Strong>System Monitor</Strong> <Small>Tick #{tick}</Small>
|
|
482
|
-
</Text>
|
|
483
|
-
<Muted>
|
|
484
|
-
<Kbd>q</Kbd> quit
|
|
485
|
-
</Muted>
|
|
486
|
-
</Box>
|
|
487
|
-
<WideLayout cores={state.cores} memory={state.memory} network={state.network} processes={state.processes} />
|
|
488
|
-
</Box>
|
|
489
|
-
)
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
return (
|
|
493
|
-
<Box flexDirection="column" flexGrow={1}>
|
|
494
|
-
<Tabs defaultValue="cpu">
|
|
495
|
-
<Box justifyContent="space-between" paddingX={1}>
|
|
496
|
-
<TabList>
|
|
497
|
-
<Tab value="cpu">CPU</Tab>
|
|
498
|
-
<Tab value="memory">Memory</Tab>
|
|
499
|
-
<Tab value="network">Network</Tab>
|
|
500
|
-
<Tab value="processes">Processes</Tab>
|
|
501
|
-
</TabList>
|
|
502
|
-
<Small>Tick #{tick}</Small>
|
|
503
|
-
</Box>
|
|
504
|
-
|
|
505
|
-
<TabPanel value="cpu">
|
|
506
|
-
<Box borderStyle="round" borderColor="$border" paddingX={1} paddingY={1} flexGrow={1}>
|
|
507
|
-
<CpuPane cores={state.cores} />
|
|
508
|
-
</Box>
|
|
509
|
-
</TabPanel>
|
|
510
|
-
<TabPanel value="memory">
|
|
511
|
-
<Box borderStyle="round" borderColor="$border" paddingX={1} paddingY={1} flexGrow={1}>
|
|
512
|
-
<MemoryPane memory={state.memory} />
|
|
513
|
-
</Box>
|
|
514
|
-
</TabPanel>
|
|
515
|
-
<TabPanel value="network">
|
|
516
|
-
<Box borderStyle="round" borderColor="$border" paddingX={1} paddingY={1} flexGrow={1}>
|
|
517
|
-
<NetworkPane network={state.network} />
|
|
518
|
-
</Box>
|
|
519
|
-
</TabPanel>
|
|
520
|
-
<TabPanel value="processes">
|
|
521
|
-
<Box borderStyle="round" borderColor="$border" paddingX={1} paddingY={1} flexGrow={1}>
|
|
522
|
-
<ProcessPane processes={state.processes} />
|
|
523
|
-
</Box>
|
|
524
|
-
</TabPanel>
|
|
525
|
-
</Tabs>
|
|
526
|
-
<Muted>
|
|
527
|
-
{" "}
|
|
528
|
-
<Kbd>h/l</Kbd> tabs <Kbd>Esc/q</Kbd> quit
|
|
529
|
-
</Muted>
|
|
530
|
-
</Box>
|
|
531
|
-
)
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
// ============================================================================
|
|
535
|
-
// Main
|
|
536
|
-
// ============================================================================
|
|
537
|
-
|
|
538
|
-
async function main() {
|
|
539
|
-
using term = createTerm()
|
|
540
|
-
const { waitUntilExit } = await render(
|
|
541
|
-
<ExampleBanner meta={meta} controls="h/l tabs Esc/q quit">
|
|
542
|
-
<Dashboard />
|
|
543
|
-
</ExampleBanner>,
|
|
544
|
-
term,
|
|
545
|
-
)
|
|
546
|
-
await waitUntilExit()
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
if (import.meta.main) {
|
|
550
|
-
main().catch(console.error)
|
|
551
|
-
}
|