@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.
- package/dist/UPNG-ShUlaTDh.mjs +5074 -0
- package/dist/__vite-browser-external-2447137e-Bopa5BFR.mjs +4 -0
- package/dist/_banner-A70_y2Vi.mjs +43 -0
- package/dist/ansi-0VXlUmNn.mjs +16397 -0
- package/dist/apng-B0gRaDVT.mjs +3 -0
- package/dist/apng-BTRDTfDW.mjs +68 -0
- package/dist/apps/aichat/index.mjs +1298 -0
- package/dist/apps/app-todo.mjs +138 -0
- package/dist/apps/async-data.mjs +203 -0
- package/dist/apps/cli-wizard.mjs +338 -0
- package/dist/apps/clipboard.mjs +197 -0
- package/dist/apps/components.mjs +863 -0
- package/dist/apps/data-explorer.mjs +482 -0
- package/dist/apps/dev-tools.mjs +396 -0
- package/dist/apps/explorer.mjs +697 -0
- package/dist/apps/gallery.mjs +765 -0
- package/dist/apps/inline-bench.mjs +115 -0
- package/dist/apps/kanban.mjs +279 -0
- package/dist/apps/layout-ref.mjs +186 -0
- package/dist/apps/outline.mjs +202 -0
- package/dist/apps/paste-demo.mjs +188 -0
- package/dist/apps/scroll.mjs +85 -0
- package/dist/apps/search-filter.mjs +286 -0
- package/dist/apps/selection.mjs +354 -0
- package/dist/apps/spatial-focus-demo.mjs +387 -0
- package/dist/apps/task-list.mjs +257 -0
- package/dist/apps/terminal-caps-demo.mjs +314 -0
- package/dist/apps/terminal.mjs +871 -0
- package/dist/apps/text-selection-demo.mjs +253 -0
- package/dist/apps/textarea.mjs +177 -0
- package/dist/apps/theme.mjs +660 -0
- package/dist/apps/transform.mjs +214 -0
- package/dist/apps/virtual-10k.mjs +421 -0
- package/dist/assets/resvgjs.darwin-arm64-BtufyGW1.node +0 -0
- package/dist/backends-Dj-11kZF.mjs +1179 -0
- package/dist/backends-U3QwStfO.mjs +3 -0
- package/dist/{cli.mjs → bin/cli.mjs} +15 -19
- package/dist/chunk-BSw8zbkd.mjs +37 -0
- package/dist/components/counter.mjs +47 -0
- package/dist/components/hello.mjs +30 -0
- package/dist/components/progress-bar.mjs +58 -0
- package/dist/components/select-list.mjs +84 -0
- package/dist/components/spinner.mjs +56 -0
- package/dist/components/text-input.mjs +61 -0
- package/dist/components/virtual-list.mjs +50 -0
- package/dist/flexily-zero-adapter-ByVzLTFP.mjs +3374 -0
- package/dist/gif-B6NGH5gs.mjs +3 -0
- package/dist/gif-CfkOF-iG.mjs +71 -0
- package/dist/gifenc-BI4ihP_T.mjs +728 -0
- package/dist/key-mapping-5oYQdAQE.mjs +3 -0
- package/dist/key-mapping-D4LR1go6.mjs +130 -0
- package/dist/layout/dashboard.mjs +1203 -0
- package/dist/layout/live-resize.mjs +302 -0
- package/dist/layout/overflow.mjs +69 -0
- package/dist/layout/text-layout.mjs +334 -0
- package/dist/node-nsrAOjH4.mjs +1083 -0
- package/dist/plugins-CT0DdV_E.mjs +3056 -0
- package/dist/resvg-js-Cnk2o49d.mjs +201 -0
- package/dist/src-9ZhfQyzD.mjs +814 -0
- package/dist/src-CUUOuRH6.mjs +5322 -0
- package/dist/src-jO3Zuzjj.mjs +23538 -0
- package/dist/usingCtx-CsEf0xO3.mjs +57 -0
- package/dist/yoga-adapter-BSQHuMV9.mjs +237 -0
- package/package.json +21 -14
- package/_banner.tsx +0 -60
- package/apps/aichat/components.tsx +0 -469
- package/apps/aichat/index.tsx +0 -220
- package/apps/aichat/script.ts +0 -460
- package/apps/aichat/state.ts +0 -325
- package/apps/aichat/types.ts +0 -19
- package/apps/app-todo.tsx +0 -201
- package/apps/async-data.tsx +0 -196
- package/apps/cli-wizard.tsx +0 -332
- package/apps/clipboard.tsx +0 -183
- package/apps/components.tsx +0 -658
- package/apps/data-explorer.tsx +0 -490
- package/apps/dev-tools.tsx +0 -395
- package/apps/explorer.tsx +0 -731
- package/apps/gallery.tsx +0 -653
- package/apps/inline-bench.tsx +0 -138
- package/apps/kanban.tsx +0 -265
- package/apps/layout-ref.tsx +0 -173
- package/apps/outline.tsx +0 -160
- package/apps/panes/index.tsx +0 -203
- package/apps/paste-demo.tsx +0 -185
- package/apps/scroll.tsx +0 -80
- package/apps/search-filter.tsx +0 -240
- package/apps/selection.tsx +0 -346
- package/apps/spatial-focus-demo.tsx +0 -372
- package/apps/task-list.tsx +0 -271
- package/apps/terminal-caps-demo.tsx +0 -317
- package/apps/terminal.tsx +0 -784
- package/apps/text-selection-demo.tsx +0 -193
- package/apps/textarea.tsx +0 -155
- package/apps/theme.tsx +0 -515
- package/apps/transform.tsx +0 -229
- package/apps/virtual-10k.tsx +0 -405
- package/apps/vterm-demo/index.tsx +0 -216
- package/components/counter.tsx +0 -49
- package/components/hello.tsx +0 -38
- package/components/progress-bar.tsx +0 -52
- package/components/select-list.tsx +0 -54
- package/components/spinner.tsx +0 -44
- package/components/text-input.tsx +0 -61
- package/components/virtual-list.tsx +0 -56
- package/dist/cli.d.mts +0 -1
- package/dist/cli.mjs.map +0 -1
- package/layout/dashboard.tsx +0 -953
- package/layout/live-resize.tsx +0 -282
- package/layout/overflow.tsx +0 -51
- package/layout/text-layout.tsx +0 -283
|
@@ -0,0 +1,697 @@
|
|
|
1
|
+
import { t as _usingCtx } from "../usingCtx-CsEf0xO3.mjs";
|
|
2
|
+
import { t as ExampleBanner } from "../_banner-A70_y2Vi.mjs";
|
|
3
|
+
import { useCallback, useDeferredValue, useEffect, useMemo, useRef, useState } from "react";
|
|
4
|
+
import { Box, Divider, Kbd, ListView, Muted, Tab, TabList, Tabs, Text, TextInput, createTerm, render, useApp, useBoxRect, useInput } from "silvery";
|
|
5
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
+
//#region apps/explorer.tsx
|
|
7
|
+
/**
|
|
8
|
+
* Explorer — Log Viewer & Process Explorer
|
|
9
|
+
*
|
|
10
|
+
* A tabbed data exploration demo combining:
|
|
11
|
+
* - Streaming log viewer with ~2000 lines, severity-level coloring, and level toggles
|
|
12
|
+
* - Sortable process table with ~50 processes, live CPU/MEM jitter, and responsive columns
|
|
13
|
+
* - Shared TextInput search bar with useDeferredValue for non-blocking filtering
|
|
14
|
+
* - ListView with interactive scrolling for both tabs
|
|
15
|
+
*
|
|
16
|
+
* Usage: bun vendor/silvery/examples/apps/explorer.tsx
|
|
17
|
+
*
|
|
18
|
+
* Controls:
|
|
19
|
+
* Tab/h/l - Switch tabs (Logs / Processes)
|
|
20
|
+
* j/k or Up/Dn - Navigate rows
|
|
21
|
+
* d/u - Half-page down/up
|
|
22
|
+
* g/G - Jump to first/last
|
|
23
|
+
* / - Focus search bar
|
|
24
|
+
* 1-4 - Toggle log levels (Logs tab)
|
|
25
|
+
* s - Cycle sort column (Processes tab)
|
|
26
|
+
* Esc - Exit search / quit
|
|
27
|
+
* q - Quit (when not searching)
|
|
28
|
+
*/
|
|
29
|
+
const meta = {
|
|
30
|
+
name: "Explorer",
|
|
31
|
+
description: "Log viewer and process explorer with ListView search",
|
|
32
|
+
demo: true,
|
|
33
|
+
features: [
|
|
34
|
+
"ListView",
|
|
35
|
+
"TextInput",
|
|
36
|
+
"useBoxRect()",
|
|
37
|
+
"useDeferredValue",
|
|
38
|
+
"2000+ rows"
|
|
39
|
+
]
|
|
40
|
+
};
|
|
41
|
+
function seededRandom(seed) {
|
|
42
|
+
let s = seed;
|
|
43
|
+
return () => {
|
|
44
|
+
s = s * 1664525 + 1013904223 & 2147483647;
|
|
45
|
+
return s / 2147483647;
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
const SERVICES = [
|
|
49
|
+
"api",
|
|
50
|
+
"auth",
|
|
51
|
+
"db",
|
|
52
|
+
"cache",
|
|
53
|
+
"worker",
|
|
54
|
+
"gateway",
|
|
55
|
+
"scheduler",
|
|
56
|
+
"metrics",
|
|
57
|
+
"queue",
|
|
58
|
+
"ws"
|
|
59
|
+
];
|
|
60
|
+
const LOG_TEMPLATES = {
|
|
61
|
+
DEBUG: [
|
|
62
|
+
"Cache miss for key user:session:{id}",
|
|
63
|
+
"Query plan: sequential scan on events ({n} rows)",
|
|
64
|
+
"WebSocket frame received: {n} bytes",
|
|
65
|
+
"GC pause: {n}ms (minor collection)",
|
|
66
|
+
"Connection pool stats: {n} active, {n} idle",
|
|
67
|
+
"Route matched: GET /api/v2/resources/{id}",
|
|
68
|
+
"DNS resolution took {n}ms for upstream.svc",
|
|
69
|
+
"Retry backoff: sleeping {n}ms before attempt"
|
|
70
|
+
],
|
|
71
|
+
INFO: [
|
|
72
|
+
"Request completed: 200 OK ({n}ms)",
|
|
73
|
+
"User {id} authenticated via OAuth",
|
|
74
|
+
"Background job processed: email_dispatch #{id}",
|
|
75
|
+
"Server listening on port {n}",
|
|
76
|
+
"Database migration applied: v{n}",
|
|
77
|
+
"Health check passed (latency: {n}ms)",
|
|
78
|
+
"Deployed version 2.{n}.0 to production",
|
|
79
|
+
"Cache warmed: {n} entries loaded in {n}ms"
|
|
80
|
+
],
|
|
81
|
+
WARN: [
|
|
82
|
+
"Slow query detected: {n}ms (threshold: 200ms)",
|
|
83
|
+
"Rate limit approaching: {n}/1000 requests",
|
|
84
|
+
"Memory usage: {n}% of allocated heap",
|
|
85
|
+
"Retry attempt {n}/3 for external API call",
|
|
86
|
+
"Certificate expires in {n} days",
|
|
87
|
+
"Connection pool near capacity: {n}/100",
|
|
88
|
+
"Request body exceeds {n}KB soft limit",
|
|
89
|
+
"Stale cache entry served for key products:{id}"
|
|
90
|
+
],
|
|
91
|
+
ERROR: [
|
|
92
|
+
"Unhandled exception in request handler: TypeError",
|
|
93
|
+
"Database connection refused: ECONNREFUSED",
|
|
94
|
+
"Authentication failed for user {id}: invalid token",
|
|
95
|
+
"Timeout after {n}ms waiting for upstream service",
|
|
96
|
+
"Disk usage critical: {n}% on /var/data",
|
|
97
|
+
"Failed to process message from queue: malformed payload",
|
|
98
|
+
"OOM kill triggered for worker process PID {id}",
|
|
99
|
+
"TLS handshake failed: certificate chain incomplete"
|
|
100
|
+
]
|
|
101
|
+
};
|
|
102
|
+
const LEVEL_COLORS = {
|
|
103
|
+
DEBUG: "$muted",
|
|
104
|
+
INFO: "$success",
|
|
105
|
+
WARN: "$warning",
|
|
106
|
+
ERROR: "$error"
|
|
107
|
+
};
|
|
108
|
+
const LEVEL_BADGES = {
|
|
109
|
+
DEBUG: "DBG",
|
|
110
|
+
INFO: "INF",
|
|
111
|
+
WARN: "WRN",
|
|
112
|
+
ERROR: "ERR"
|
|
113
|
+
};
|
|
114
|
+
function generateLogs(count) {
|
|
115
|
+
const rng = seededRandom(42);
|
|
116
|
+
const levels = [
|
|
117
|
+
"DEBUG",
|
|
118
|
+
"INFO",
|
|
119
|
+
"INFO",
|
|
120
|
+
"INFO",
|
|
121
|
+
"INFO",
|
|
122
|
+
"WARN",
|
|
123
|
+
"WARN",
|
|
124
|
+
"ERROR"
|
|
125
|
+
];
|
|
126
|
+
const entries = [];
|
|
127
|
+
const baseHour = 14;
|
|
128
|
+
const baseMinute = 30;
|
|
129
|
+
for (let i = 0; i < count; i++) {
|
|
130
|
+
const level = levels[Math.floor(rng() * levels.length)];
|
|
131
|
+
const templates = LOG_TEMPLATES[level];
|
|
132
|
+
const message = templates[Math.floor(rng() * templates.length)].replace(/\{id\}/g, () => String(Math.floor(rng() * 99999))).replace(/\{n\}/g, () => String(Math.floor(rng() * 999)));
|
|
133
|
+
const totalSeconds = i / count * 1800;
|
|
134
|
+
const h = baseHour + Math.floor((baseMinute * 60 + totalSeconds) / 3600);
|
|
135
|
+
const m = Math.floor((baseMinute * 60 + totalSeconds) % 3600 / 60);
|
|
136
|
+
const s = Math.floor(totalSeconds % 60);
|
|
137
|
+
const ms = Math.floor(rng() * 1e3);
|
|
138
|
+
entries.push({
|
|
139
|
+
id: i,
|
|
140
|
+
timestamp: `${String(h).padStart(2, "0")}:${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}.${String(ms).padStart(3, "0")}`,
|
|
141
|
+
service: SERVICES[Math.floor(rng() * SERVICES.length)],
|
|
142
|
+
level,
|
|
143
|
+
message
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
return entries;
|
|
147
|
+
}
|
|
148
|
+
const ALL_LOGS = generateLogs(2e3);
|
|
149
|
+
const PROCESS_NAMES = [
|
|
150
|
+
"node",
|
|
151
|
+
"bun",
|
|
152
|
+
"postgres",
|
|
153
|
+
"redis-server",
|
|
154
|
+
"nginx",
|
|
155
|
+
"docker",
|
|
156
|
+
"sshd",
|
|
157
|
+
"containerd",
|
|
158
|
+
"kubelet",
|
|
159
|
+
"etcd",
|
|
160
|
+
"coredns",
|
|
161
|
+
"prometheus",
|
|
162
|
+
"grafana",
|
|
163
|
+
"elasticsearch",
|
|
164
|
+
"rabbitmq",
|
|
165
|
+
"kafka",
|
|
166
|
+
"consul",
|
|
167
|
+
"vault",
|
|
168
|
+
"haproxy",
|
|
169
|
+
"traefik",
|
|
170
|
+
"envoy",
|
|
171
|
+
"mysql",
|
|
172
|
+
"mongo",
|
|
173
|
+
"clickhouse",
|
|
174
|
+
"influxdb",
|
|
175
|
+
"jenkins",
|
|
176
|
+
"cadvisor",
|
|
177
|
+
"telegraf",
|
|
178
|
+
"deno",
|
|
179
|
+
"esbuild",
|
|
180
|
+
"python3",
|
|
181
|
+
"ruby",
|
|
182
|
+
"java",
|
|
183
|
+
"go",
|
|
184
|
+
"rustc",
|
|
185
|
+
"webpack",
|
|
186
|
+
"vite",
|
|
187
|
+
"swc",
|
|
188
|
+
"chrome",
|
|
189
|
+
"code",
|
|
190
|
+
"tmux",
|
|
191
|
+
"zsh",
|
|
192
|
+
"cron",
|
|
193
|
+
"systemd",
|
|
194
|
+
"rsyslogd",
|
|
195
|
+
"logstash",
|
|
196
|
+
"kibana",
|
|
197
|
+
"alertmanager",
|
|
198
|
+
"buildkitd",
|
|
199
|
+
"registry"
|
|
200
|
+
];
|
|
201
|
+
const PROCESS_STATUSES = [
|
|
202
|
+
"running",
|
|
203
|
+
"sleeping",
|
|
204
|
+
"stopped",
|
|
205
|
+
"zombie"
|
|
206
|
+
];
|
|
207
|
+
function generateProcesses(count) {
|
|
208
|
+
const rng = seededRandom(123);
|
|
209
|
+
const procs = [];
|
|
210
|
+
for (let i = 0; i < count; i++) {
|
|
211
|
+
const nameBase = PROCESS_NAMES[Math.floor(rng() * PROCESS_NAMES.length)];
|
|
212
|
+
const hasInstance = rng() > .7;
|
|
213
|
+
const status = rng() < .65 ? "running" : PROCESS_STATUSES[Math.floor(rng() * PROCESS_STATUSES.length)];
|
|
214
|
+
procs.push({
|
|
215
|
+
pid: 1e3 + Math.floor(rng() * 6e4),
|
|
216
|
+
name: hasInstance ? `${nameBase}:${Math.floor(rng() * 16)}` : nameBase,
|
|
217
|
+
cpu: status === "running" ? Math.round(rng() * 1e3) / 10 : 0,
|
|
218
|
+
mem: Math.round(rng() * 800) / 10,
|
|
219
|
+
status
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
return procs;
|
|
223
|
+
}
|
|
224
|
+
const INITIAL_PROCESSES = generateProcesses(50);
|
|
225
|
+
const STATUS_COLORS = {
|
|
226
|
+
running: "$success",
|
|
227
|
+
sleeping: "$muted",
|
|
228
|
+
stopped: "$warning",
|
|
229
|
+
zombie: "$error"
|
|
230
|
+
};
|
|
231
|
+
const STATUS_ICONS = {
|
|
232
|
+
running: "▶",
|
|
233
|
+
sleeping: "◌",
|
|
234
|
+
stopped: "■",
|
|
235
|
+
zombie: "☠"
|
|
236
|
+
};
|
|
237
|
+
const SORT_COLUMNS = [
|
|
238
|
+
"cpu",
|
|
239
|
+
"mem",
|
|
240
|
+
"pid",
|
|
241
|
+
"name",
|
|
242
|
+
"status"
|
|
243
|
+
];
|
|
244
|
+
function LogRow({ entry, isSelected }) {
|
|
245
|
+
return /* @__PURE__ */ jsxs(Box, {
|
|
246
|
+
paddingX: 1,
|
|
247
|
+
backgroundColor: isSelected ? "$mutedbg" : void 0,
|
|
248
|
+
children: [
|
|
249
|
+
/* @__PURE__ */ jsxs(Muted, { children: [entry.timestamp, " "] }),
|
|
250
|
+
/* @__PURE__ */ jsx(Text, {
|
|
251
|
+
color: LEVEL_COLORS[entry.level],
|
|
252
|
+
bold: true,
|
|
253
|
+
children: LEVEL_BADGES[entry.level]
|
|
254
|
+
}),
|
|
255
|
+
/* @__PURE__ */ jsxs(Muted, { children: [
|
|
256
|
+
" [",
|
|
257
|
+
entry.service.padEnd(9),
|
|
258
|
+
"] "
|
|
259
|
+
] }),
|
|
260
|
+
/* @__PURE__ */ jsx(Text, { children: entry.message })
|
|
261
|
+
]
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
function LogListArea({ entries, cursor }) {
|
|
265
|
+
const { height } = useBoxRect();
|
|
266
|
+
return /* @__PURE__ */ jsx(ListView, {
|
|
267
|
+
items: entries,
|
|
268
|
+
height,
|
|
269
|
+
estimateHeight: 1,
|
|
270
|
+
scrollTo: cursor,
|
|
271
|
+
overscan: 5,
|
|
272
|
+
renderItem: (entry, index) => /* @__PURE__ */ jsx(LogRow, {
|
|
273
|
+
entry,
|
|
274
|
+
isSelected: index === cursor
|
|
275
|
+
}, entry.id)
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
function LevelToggles({ levels, onToggle }) {
|
|
279
|
+
return /* @__PURE__ */ jsx(Box, {
|
|
280
|
+
gap: 1,
|
|
281
|
+
children: [
|
|
282
|
+
"DEBUG",
|
|
283
|
+
"INFO",
|
|
284
|
+
"WARN",
|
|
285
|
+
"ERROR"
|
|
286
|
+
].map((level, i) => {
|
|
287
|
+
const active = levels[level];
|
|
288
|
+
return /* @__PURE__ */ jsxs(Box, {
|
|
289
|
+
gap: 0,
|
|
290
|
+
children: [/* @__PURE__ */ jsxs(Text, {
|
|
291
|
+
color: "$muted",
|
|
292
|
+
dim: true,
|
|
293
|
+
children: [i + 1, ":"]
|
|
294
|
+
}), /* @__PURE__ */ jsx(Text, {
|
|
295
|
+
color: active ? LEVEL_COLORS[level] : "$muted",
|
|
296
|
+
bold: active,
|
|
297
|
+
dim: !active,
|
|
298
|
+
strikethrough: !active,
|
|
299
|
+
children: LEVEL_BADGES[level]
|
|
300
|
+
})]
|
|
301
|
+
}, level);
|
|
302
|
+
})
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
function useColumns(totalWidth) {
|
|
306
|
+
return useMemo(() => {
|
|
307
|
+
const pidW = 7;
|
|
308
|
+
const cpuW = 8;
|
|
309
|
+
const memW = 8;
|
|
310
|
+
const statusW = 11;
|
|
311
|
+
const fixed = pidW + cpuW + memW + statusW + 4;
|
|
312
|
+
return {
|
|
313
|
+
pidW,
|
|
314
|
+
nameW: Math.max(12, totalWidth - fixed),
|
|
315
|
+
cpuW,
|
|
316
|
+
memW,
|
|
317
|
+
statusW
|
|
318
|
+
};
|
|
319
|
+
}, [totalWidth]);
|
|
320
|
+
}
|
|
321
|
+
function ProcessHeader({ width }) {
|
|
322
|
+
const cols = useColumns(width);
|
|
323
|
+
return /* @__PURE__ */ jsx(Box, {
|
|
324
|
+
paddingX: 1,
|
|
325
|
+
children: /* @__PURE__ */ jsxs(Text, {
|
|
326
|
+
bold: true,
|
|
327
|
+
color: "$muted",
|
|
328
|
+
children: [
|
|
329
|
+
"PID".padEnd(cols.pidW),
|
|
330
|
+
"NAME".padEnd(cols.nameW),
|
|
331
|
+
"CPU%".padStart(cols.cpuW),
|
|
332
|
+
"MEM%".padStart(cols.memW),
|
|
333
|
+
" ",
|
|
334
|
+
"STATUS".padEnd(cols.statusW)
|
|
335
|
+
]
|
|
336
|
+
})
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
function ProcessRow({ proc, isSelected, width }) {
|
|
340
|
+
const cols = useColumns(width);
|
|
341
|
+
const cpuColor = proc.cpu > 80 ? "$error" : proc.cpu > 40 ? "$warning" : "$success";
|
|
342
|
+
const displayName = proc.name.length > cols.nameW - 1 ? proc.name.slice(0, cols.nameW - 2) + "…" : proc.name;
|
|
343
|
+
return /* @__PURE__ */ jsxs(Box, {
|
|
344
|
+
paddingX: 1,
|
|
345
|
+
backgroundColor: isSelected ? "$mutedbg" : void 0,
|
|
346
|
+
children: [
|
|
347
|
+
/* @__PURE__ */ jsx(Text, {
|
|
348
|
+
color: "$muted",
|
|
349
|
+
children: String(proc.pid).padEnd(cols.pidW)
|
|
350
|
+
}),
|
|
351
|
+
/* @__PURE__ */ jsx(Text, {
|
|
352
|
+
bold: isSelected,
|
|
353
|
+
children: displayName.padEnd(cols.nameW)
|
|
354
|
+
}),
|
|
355
|
+
/* @__PURE__ */ jsxs(Text, {
|
|
356
|
+
color: cpuColor,
|
|
357
|
+
children: [proc.cpu.toFixed(1).padStart(cols.cpuW - 1), "%"]
|
|
358
|
+
}),
|
|
359
|
+
/* @__PURE__ */ jsxs(Text, {
|
|
360
|
+
color: proc.mem > 40 ? "$warning" : "$muted",
|
|
361
|
+
children: [proc.mem.toFixed(1).padStart(cols.memW - 1), "%"]
|
|
362
|
+
}),
|
|
363
|
+
/* @__PURE__ */ jsx(Text, { children: " " }),
|
|
364
|
+
/* @__PURE__ */ jsxs(Text, {
|
|
365
|
+
color: STATUS_COLORS[proc.status],
|
|
366
|
+
children: [
|
|
367
|
+
STATUS_ICONS[proc.status],
|
|
368
|
+
" ",
|
|
369
|
+
proc.status.padEnd(cols.statusW - 2)
|
|
370
|
+
]
|
|
371
|
+
})
|
|
372
|
+
]
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
function ProcessListArea({ processes, cursor, width }) {
|
|
376
|
+
const { height } = useBoxRect();
|
|
377
|
+
return /* @__PURE__ */ jsx(ListView, {
|
|
378
|
+
items: processes,
|
|
379
|
+
height,
|
|
380
|
+
estimateHeight: 1,
|
|
381
|
+
scrollTo: cursor,
|
|
382
|
+
overscan: 5,
|
|
383
|
+
renderItem: (proc, index) => /* @__PURE__ */ jsx(ProcessRow, {
|
|
384
|
+
proc,
|
|
385
|
+
isSelected: index === cursor,
|
|
386
|
+
width
|
|
387
|
+
}, proc.pid)
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
function Explorer() {
|
|
391
|
+
const { exit } = useApp();
|
|
392
|
+
const { width } = useBoxRect();
|
|
393
|
+
const [activeTab, setActiveTab] = useState("logs");
|
|
394
|
+
const [searchMode, setSearchMode] = useState(false);
|
|
395
|
+
const [query, setQuery] = useState("");
|
|
396
|
+
const deferredQuery = useDeferredValue(query);
|
|
397
|
+
const [logCursor, setLogCursor] = useState(0);
|
|
398
|
+
const [logLevels, setLogLevels] = useState({
|
|
399
|
+
DEBUG: true,
|
|
400
|
+
INFO: true,
|
|
401
|
+
WARN: true,
|
|
402
|
+
ERROR: true
|
|
403
|
+
});
|
|
404
|
+
const [procCursor, setProcCursor] = useState(0);
|
|
405
|
+
const [sortCol, setSortCol] = useState("cpu");
|
|
406
|
+
const [processes, setProcesses] = useState(INITIAL_PROCESSES);
|
|
407
|
+
const jitterRef = useRef(null);
|
|
408
|
+
useEffect(() => {
|
|
409
|
+
const rng = seededRandom(Date.now());
|
|
410
|
+
jitterRef.current = setInterval(() => {
|
|
411
|
+
setProcesses((prev) => prev.map((p) => {
|
|
412
|
+
if (p.status !== "running") return p;
|
|
413
|
+
const cpuDelta = (rng() - .5) * 6;
|
|
414
|
+
const memDelta = (rng() - .5) * 2;
|
|
415
|
+
return {
|
|
416
|
+
...p,
|
|
417
|
+
cpu: Math.max(0, Math.min(100, Math.round((p.cpu + cpuDelta) * 10) / 10)),
|
|
418
|
+
mem: Math.max(0, Math.min(100, Math.round((p.mem + memDelta) * 10) / 10))
|
|
419
|
+
};
|
|
420
|
+
}));
|
|
421
|
+
}, 2e3);
|
|
422
|
+
return () => {
|
|
423
|
+
if (jitterRef.current) clearInterval(jitterRef.current);
|
|
424
|
+
};
|
|
425
|
+
}, []);
|
|
426
|
+
const filteredLogs = useMemo(() => {
|
|
427
|
+
let logs = ALL_LOGS.filter((e) => logLevels[e.level]);
|
|
428
|
+
if (deferredQuery) {
|
|
429
|
+
const q = deferredQuery.toLowerCase();
|
|
430
|
+
logs = logs.filter((e) => e.message.toLowerCase().includes(q) || e.service.toLowerCase().includes(q) || e.level.toLowerCase().includes(q));
|
|
431
|
+
}
|
|
432
|
+
return logs;
|
|
433
|
+
}, [deferredQuery, logLevels]);
|
|
434
|
+
const filteredProcesses = useMemo(() => {
|
|
435
|
+
let procs = processes;
|
|
436
|
+
if (deferredQuery) {
|
|
437
|
+
const q = deferredQuery.toLowerCase();
|
|
438
|
+
procs = procs.filter((p) => p.name.toLowerCase().includes(q) || p.status.includes(q) || String(p.pid).includes(q));
|
|
439
|
+
}
|
|
440
|
+
return [...procs].sort((a, b) => {
|
|
441
|
+
switch (sortCol) {
|
|
442
|
+
case "cpu": return b.cpu - a.cpu;
|
|
443
|
+
case "mem": return b.mem - a.mem;
|
|
444
|
+
case "pid": return a.pid - b.pid;
|
|
445
|
+
case "name": return a.name.localeCompare(b.name);
|
|
446
|
+
case "status": return a.status.localeCompare(b.status);
|
|
447
|
+
}
|
|
448
|
+
});
|
|
449
|
+
}, [
|
|
450
|
+
processes,
|
|
451
|
+
deferredQuery,
|
|
452
|
+
sortCol
|
|
453
|
+
]);
|
|
454
|
+
const currentItems = activeTab === "logs" ? filteredLogs : filteredProcesses;
|
|
455
|
+
const setCursor = activeTab === "logs" ? setLogCursor : setProcCursor;
|
|
456
|
+
const halfPage = Math.max(1, Math.floor(20 / 2));
|
|
457
|
+
const effectiveLogCursor = Math.min(logCursor, Math.max(0, filteredLogs.length - 1));
|
|
458
|
+
const effectiveProcCursor = Math.min(procCursor, Math.max(0, filteredProcesses.length - 1));
|
|
459
|
+
const handleSearchSubmit = useCallback(() => {
|
|
460
|
+
setSearchMode(false);
|
|
461
|
+
}, []);
|
|
462
|
+
const toggleLevel = useCallback((level) => {
|
|
463
|
+
setLogLevels((prev) => ({
|
|
464
|
+
...prev,
|
|
465
|
+
[level]: !prev[level]
|
|
466
|
+
}));
|
|
467
|
+
setLogCursor(0);
|
|
468
|
+
}, []);
|
|
469
|
+
useInput(useCallback((input, key) => {
|
|
470
|
+
if (searchMode) {
|
|
471
|
+
if (key.escape) {
|
|
472
|
+
setSearchMode(false);
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
if (input === "q" || key.escape) {
|
|
478
|
+
exit();
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
if (input === "/") {
|
|
482
|
+
setSearchMode(true);
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
if (activeTab === "logs") {
|
|
486
|
+
const levelMap = {
|
|
487
|
+
"1": "DEBUG",
|
|
488
|
+
"2": "INFO",
|
|
489
|
+
"3": "WARN",
|
|
490
|
+
"4": "ERROR"
|
|
491
|
+
};
|
|
492
|
+
if (levelMap[input]) {
|
|
493
|
+
toggleLevel(levelMap[input]);
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
if (activeTab === "processes" && input === "s") {
|
|
498
|
+
setSortCol((prev) => {
|
|
499
|
+
return SORT_COLUMNS[(SORT_COLUMNS.indexOf(prev) + 1) % SORT_COLUMNS.length];
|
|
500
|
+
});
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
if (input === "j" || key.downArrow) setCursor((c) => Math.min(currentItems.length - 1, c + 1));
|
|
504
|
+
if (input === "k" || key.upArrow) setCursor((c) => Math.max(0, c - 1));
|
|
505
|
+
if (input === "d" || key.pageDown) setCursor((c) => Math.min(currentItems.length - 1, c + halfPage));
|
|
506
|
+
if (input === "u" || key.pageUp) setCursor((c) => Math.max(0, c - halfPage));
|
|
507
|
+
if (input === "g" || key.home) setCursor(0);
|
|
508
|
+
if (input === "G" || key.end) setCursor(currentItems.length - 1);
|
|
509
|
+
if (key.backspace && query) {
|
|
510
|
+
setQuery("");
|
|
511
|
+
setCursor(0);
|
|
512
|
+
}
|
|
513
|
+
}, [
|
|
514
|
+
searchMode,
|
|
515
|
+
exit,
|
|
516
|
+
activeTab,
|
|
517
|
+
currentItems.length,
|
|
518
|
+
halfPage,
|
|
519
|
+
query,
|
|
520
|
+
toggleLevel,
|
|
521
|
+
setCursor
|
|
522
|
+
]));
|
|
523
|
+
return /* @__PURE__ */ jsxs(Box, {
|
|
524
|
+
flexDirection: "column",
|
|
525
|
+
flexGrow: 1,
|
|
526
|
+
children: [
|
|
527
|
+
/* @__PURE__ */ jsx(Box, {
|
|
528
|
+
paddingX: 1,
|
|
529
|
+
children: searchMode ? /* @__PURE__ */ jsxs(Box, {
|
|
530
|
+
flexGrow: 1,
|
|
531
|
+
children: [/* @__PURE__ */ jsxs(Text, {
|
|
532
|
+
color: "$primary",
|
|
533
|
+
bold: true,
|
|
534
|
+
children: ["/", " "]
|
|
535
|
+
}), /* @__PURE__ */ jsx(TextInput, {
|
|
536
|
+
value: query,
|
|
537
|
+
onChange: (v) => {
|
|
538
|
+
setQuery(v);
|
|
539
|
+
setLogCursor(0);
|
|
540
|
+
setProcCursor(0);
|
|
541
|
+
},
|
|
542
|
+
onSubmit: handleSearchSubmit,
|
|
543
|
+
prompt: "",
|
|
544
|
+
isActive: searchMode
|
|
545
|
+
})]
|
|
546
|
+
}) : query ? /* @__PURE__ */ jsxs(Muted, { children: [
|
|
547
|
+
"filter: ",
|
|
548
|
+
/* @__PURE__ */ jsx(Text, {
|
|
549
|
+
bold: true,
|
|
550
|
+
children: query
|
|
551
|
+
}),
|
|
552
|
+
" (",
|
|
553
|
+
/* @__PURE__ */ jsx(Kbd, { children: "backspace" }),
|
|
554
|
+
" clear, ",
|
|
555
|
+
/* @__PURE__ */ jsx(Kbd, { children: "/" }),
|
|
556
|
+
" edit)"
|
|
557
|
+
] }) : /* @__PURE__ */ jsxs(Muted, { children: [/* @__PURE__ */ jsx(Kbd, { children: "/" }), " search"] })
|
|
558
|
+
}),
|
|
559
|
+
/* @__PURE__ */ jsx(Tabs, {
|
|
560
|
+
value: activeTab,
|
|
561
|
+
onChange: setActiveTab,
|
|
562
|
+
isActive: !searchMode,
|
|
563
|
+
children: /* @__PURE__ */ jsx(Box, {
|
|
564
|
+
paddingX: 1,
|
|
565
|
+
children: /* @__PURE__ */ jsxs(TabList, { children: [/* @__PURE__ */ jsxs(Tab, {
|
|
566
|
+
value: "logs",
|
|
567
|
+
children: [
|
|
568
|
+
"Logs (",
|
|
569
|
+
filteredLogs.length.toLocaleString(),
|
|
570
|
+
")"
|
|
571
|
+
]
|
|
572
|
+
}), /* @__PURE__ */ jsxs(Tab, {
|
|
573
|
+
value: "processes",
|
|
574
|
+
children: [
|
|
575
|
+
"Processes (",
|
|
576
|
+
filteredProcesses.length,
|
|
577
|
+
")"
|
|
578
|
+
]
|
|
579
|
+
})] })
|
|
580
|
+
})
|
|
581
|
+
}),
|
|
582
|
+
activeTab === "logs" && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs(Box, {
|
|
583
|
+
paddingX: 1,
|
|
584
|
+
justifyContent: "space-between",
|
|
585
|
+
children: [/* @__PURE__ */ jsx(LevelToggles, {
|
|
586
|
+
levels: logLevels,
|
|
587
|
+
onToggle: toggleLevel
|
|
588
|
+
}), /* @__PURE__ */ jsxs(Muted, { children: [
|
|
589
|
+
effectiveLogCursor + 1,
|
|
590
|
+
"/",
|
|
591
|
+
filteredLogs.length.toLocaleString()
|
|
592
|
+
] })]
|
|
593
|
+
}), /* @__PURE__ */ jsx(Box, {
|
|
594
|
+
flexGrow: 1,
|
|
595
|
+
flexDirection: "column",
|
|
596
|
+
children: filteredLogs.length > 0 ? /* @__PURE__ */ jsx(LogListArea, {
|
|
597
|
+
entries: filteredLogs,
|
|
598
|
+
cursor: effectiveLogCursor
|
|
599
|
+
}) : /* @__PURE__ */ jsx(Box, {
|
|
600
|
+
paddingX: 1,
|
|
601
|
+
justifyContent: "center",
|
|
602
|
+
children: /* @__PURE__ */ jsx(Muted, { children: "No logs match the current filter" })
|
|
603
|
+
})
|
|
604
|
+
})] }),
|
|
605
|
+
activeTab === "processes" && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
606
|
+
/* @__PURE__ */ jsxs(Box, {
|
|
607
|
+
paddingX: 1,
|
|
608
|
+
justifyContent: "space-between",
|
|
609
|
+
children: [/* @__PURE__ */ jsxs(Box, {
|
|
610
|
+
gap: 1,
|
|
611
|
+
children: [
|
|
612
|
+
/* @__PURE__ */ jsx(Muted, { children: "sort:" }),
|
|
613
|
+
/* @__PURE__ */ jsx(Text, {
|
|
614
|
+
bold: true,
|
|
615
|
+
color: "$primary",
|
|
616
|
+
children: sortCol.toUpperCase()
|
|
617
|
+
}),
|
|
618
|
+
/* @__PURE__ */ jsxs(Muted, { children: [
|
|
619
|
+
"(",
|
|
620
|
+
/* @__PURE__ */ jsx(Kbd, { children: "s" }),
|
|
621
|
+
" cycle)"
|
|
622
|
+
] })
|
|
623
|
+
]
|
|
624
|
+
}), /* @__PURE__ */ jsxs(Muted, { children: [
|
|
625
|
+
effectiveProcCursor + 1,
|
|
626
|
+
"/",
|
|
627
|
+
filteredProcesses.length
|
|
628
|
+
] })]
|
|
629
|
+
}),
|
|
630
|
+
/* @__PURE__ */ jsx(ProcessHeader, { width }),
|
|
631
|
+
/* @__PURE__ */ jsx(Box, {
|
|
632
|
+
paddingX: 1,
|
|
633
|
+
children: /* @__PURE__ */ jsx(Divider, {})
|
|
634
|
+
}),
|
|
635
|
+
/* @__PURE__ */ jsx(Box, {
|
|
636
|
+
flexGrow: 1,
|
|
637
|
+
flexDirection: "column",
|
|
638
|
+
children: filteredProcesses.length > 0 ? /* @__PURE__ */ jsx(ProcessListArea, {
|
|
639
|
+
processes: filteredProcesses,
|
|
640
|
+
cursor: effectiveProcCursor,
|
|
641
|
+
width
|
|
642
|
+
}) : /* @__PURE__ */ jsx(Box, {
|
|
643
|
+
paddingX: 1,
|
|
644
|
+
justifyContent: "center",
|
|
645
|
+
children: /* @__PURE__ */ jsx(Muted, { children: "No processes match the current filter" })
|
|
646
|
+
})
|
|
647
|
+
})
|
|
648
|
+
] }),
|
|
649
|
+
/* @__PURE__ */ jsx(Box, {
|
|
650
|
+
paddingX: 1,
|
|
651
|
+
justifyContent: "space-between",
|
|
652
|
+
children: /* @__PURE__ */ jsxs(Muted, { children: [
|
|
653
|
+
/* @__PURE__ */ jsx(Kbd, { children: "h/l" }),
|
|
654
|
+
" tab ",
|
|
655
|
+
/* @__PURE__ */ jsx(Kbd, { children: "j/k" }),
|
|
656
|
+
" navigate ",
|
|
657
|
+
/* @__PURE__ */ jsx(Kbd, { children: "d/u" }),
|
|
658
|
+
" page ",
|
|
659
|
+
/* @__PURE__ */ jsx(Kbd, { children: "/" }),
|
|
660
|
+
" search",
|
|
661
|
+
" ",
|
|
662
|
+
activeTab === "logs" && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
663
|
+
/* @__PURE__ */ jsx(Kbd, { children: "1-4" }),
|
|
664
|
+
" levels",
|
|
665
|
+
" "
|
|
666
|
+
] }),
|
|
667
|
+
activeTab === "processes" && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
668
|
+
/* @__PURE__ */ jsx(Kbd, { children: "s" }),
|
|
669
|
+
" sort",
|
|
670
|
+
" "
|
|
671
|
+
] }),
|
|
672
|
+
/* @__PURE__ */ jsx(Kbd, { children: "q" }),
|
|
673
|
+
" quit"
|
|
674
|
+
] })
|
|
675
|
+
})
|
|
676
|
+
]
|
|
677
|
+
});
|
|
678
|
+
}
|
|
679
|
+
async function main() {
|
|
680
|
+
try {
|
|
681
|
+
var _usingCtx$1 = _usingCtx();
|
|
682
|
+
const term = _usingCtx$1.u(createTerm());
|
|
683
|
+
const { waitUntilExit } = await render(/* @__PURE__ */ jsx(ExampleBanner, {
|
|
684
|
+
meta,
|
|
685
|
+
controls: "h/l tab j/k navigate d/u page / search 1-4 levels s sort q quit",
|
|
686
|
+
children: /* @__PURE__ */ jsx(Explorer, {})
|
|
687
|
+
}), term);
|
|
688
|
+
await waitUntilExit();
|
|
689
|
+
} catch (_) {
|
|
690
|
+
_usingCtx$1.e = _;
|
|
691
|
+
} finally {
|
|
692
|
+
_usingCtx$1.d();
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
if (import.meta.main) await main();
|
|
696
|
+
//#endregion
|
|
697
|
+
export { Explorer, main, meta };
|