@profullstack/hqtui-demo 0.1.7 → 0.1.9
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 +4 -0
- package/dist/main.js +11 -40
- package/dist/main.js.map +1 -1
- package/dist/screens/components.js +30 -4
- package/dist/screens/components.js.map +1 -1
- package/dist/screens/dashboard.js +21 -4
- package/dist/screens/dashboard.js.map +1 -1
- package/dist/screens/network.js +14 -3
- package/dist/screens/network.js.map +1 -1
- package/dist/screens/services.js +23 -3
- package/dist/screens/services.js.map +1 -1
- package/dist/screens/sessions.js +23 -3
- package/dist/screens/sessions.js.map +1 -1
- package/dist/screens/traffic.js +30 -3
- package/dist/screens/traffic.js.map +1 -1
- package/dist/state.js +50 -4
- package/dist/state.js.map +1 -1
- package/package.json +4 -3
- package/src/main.ts +11 -41
- package/src/screens/components.ts +30 -4
- package/src/screens/dashboard.ts +21 -4
- package/src/screens/network.ts +14 -3
- package/src/screens/services.ts +23 -3
- package/src/screens/sessions.ts +24 -4
- package/src/screens/traffic.ts +30 -3
- package/src/state.ts +69 -9
package/src/screens/services.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Container, Theme } from "@profullstack/hqtui";
|
|
2
|
-
import {
|
|
2
|
+
import { focusPane, pane, scrollPane, type DemoState } from "../state.ts";
|
|
3
3
|
import { bytes, num, percent } from "../format.ts";
|
|
4
4
|
|
|
5
5
|
function rate(value: number): string {
|
|
@@ -23,11 +23,15 @@ export function servicesScreen(ui: Container, state: DemoState, theme: Theme): v
|
|
|
23
23
|
p.label("systemd not available on this host.");
|
|
24
24
|
return;
|
|
25
25
|
}
|
|
26
|
+
const units = pane(state, "services.units", t.services.length);
|
|
26
27
|
p.table({
|
|
27
28
|
rows: t.services,
|
|
28
|
-
selected:
|
|
29
|
-
offset:
|
|
29
|
+
selected: units.selected,
|
|
30
|
+
offset: units.offset,
|
|
30
31
|
followSelection: true,
|
|
32
|
+
onScroll: (delta) => scrollPane(units, delta),
|
|
33
|
+
onFocus: () => focusPane(state, "services.units"),
|
|
34
|
+
onSelectRow: (row) => { units.selected = units.offset + row; },
|
|
31
35
|
zebra: true,
|
|
32
36
|
scrollbar: true,
|
|
33
37
|
columns: [
|
|
@@ -66,8 +70,16 @@ export function servicesScreen(ui: Container, state: DemoState, theme: Theme): v
|
|
|
66
70
|
p.label("(docker not installed or not reachable)", { size: 1 });
|
|
67
71
|
return;
|
|
68
72
|
}
|
|
73
|
+
const containers = pane(state, "services.containers", t.containers.length);
|
|
69
74
|
p.table({
|
|
70
75
|
rows: t.containers,
|
|
76
|
+
selected: containers.selected,
|
|
77
|
+
offset: containers.offset,
|
|
78
|
+
followSelection: true,
|
|
79
|
+
scrollbar: true,
|
|
80
|
+
onScroll: (delta) => scrollPane(containers, delta),
|
|
81
|
+
onFocus: () => focusPane(state, "services.containers"),
|
|
82
|
+
onSelectRow: (row) => { containers.selected = containers.offset + row; },
|
|
71
83
|
zebra: true,
|
|
72
84
|
columns: [
|
|
73
85
|
{ key: "name", title: "Name", min: 12, color: theme.primary },
|
|
@@ -106,8 +118,16 @@ export function servicesScreen(ui: Container, state: DemoState, theme: Theme): v
|
|
|
106
118
|
p.label("No filesystems reported.");
|
|
107
119
|
return;
|
|
108
120
|
}
|
|
121
|
+
const filesystems = pane(state, "services.filesystems", t.filesystems.length);
|
|
109
122
|
p.table({
|
|
110
123
|
rows: t.filesystems,
|
|
124
|
+
selected: filesystems.selected,
|
|
125
|
+
offset: filesystems.offset,
|
|
126
|
+
followSelection: true,
|
|
127
|
+
scrollbar: true,
|
|
128
|
+
onScroll: (delta) => scrollPane(filesystems, delta),
|
|
129
|
+
onFocus: () => focusPane(state, "services.filesystems"),
|
|
130
|
+
onSelectRow: (row) => { filesystems.selected = filesystems.offset + row; },
|
|
111
131
|
zebra: true,
|
|
112
132
|
columns: [
|
|
113
133
|
{ key: "mount", title: "Mount", min: 14, color: theme.primary },
|
package/src/screens/sessions.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Container, Theme } from "@profullstack/hqtui";
|
|
2
|
-
import {
|
|
2
|
+
import { focusPane, pane, scrollPane, type DemoState } from "../state.ts";
|
|
3
3
|
|
|
4
4
|
/** Who is on this machine, who has been, and who failed to get in. */
|
|
5
5
|
export function sessionsScreen(ui: Container, state: DemoState, theme: Theme): void {
|
|
@@ -12,8 +12,16 @@ export function sessionsScreen(ui: Container, state: DemoState, theme: Theme): v
|
|
|
12
12
|
p.label("(`who` reports nothing on this host)", { size: 1 });
|
|
13
13
|
return;
|
|
14
14
|
}
|
|
15
|
+
const active = pane(state, "sessions.active", t.sessions.length);
|
|
15
16
|
p.table({
|
|
16
17
|
rows: t.sessions,
|
|
18
|
+
selected: active.selected,
|
|
19
|
+
offset: active.offset,
|
|
20
|
+
followSelection: true,
|
|
21
|
+
scrollbar: true,
|
|
22
|
+
onScroll: (delta) => scrollPane(active, delta),
|
|
23
|
+
onFocus: () => focusPane(state, "sessions.active"),
|
|
24
|
+
onSelectRow: (row) => { active.selected = active.offset + row; },
|
|
17
25
|
columns: [
|
|
18
26
|
{ key: "user", title: "User", width: 12, color: theme.primary },
|
|
19
27
|
{ key: "tty", title: "TTY", width: 10 },
|
|
@@ -40,11 +48,15 @@ export function sessionsScreen(ui: Container, state: DemoState, theme: Theme): v
|
|
|
40
48
|
p.label("No login history available.");
|
|
41
49
|
return;
|
|
42
50
|
}
|
|
51
|
+
const logins = pane(state, "sessions.logins", t.logins.length);
|
|
43
52
|
p.table({
|
|
44
53
|
rows: t.logins,
|
|
45
|
-
selected:
|
|
46
|
-
|
|
47
|
-
|
|
54
|
+
selected: logins.selected,
|
|
55
|
+
offset: logins.offset,
|
|
56
|
+
followSelection: true,
|
|
57
|
+
onScroll: (delta) => scrollPane(logins, delta),
|
|
58
|
+
onFocus: () => focusPane(state, "sessions.logins"),
|
|
59
|
+
onSelectRow: (row) => { logins.selected = logins.offset + row; },
|
|
48
60
|
zebra: true,
|
|
49
61
|
scrollbar: true,
|
|
50
62
|
columns: [
|
|
@@ -69,8 +81,16 @@ export function sessionsScreen(ui: Container, state: DemoState, theme: Theme): v
|
|
|
69
81
|
p.label("(btmp is usually root-only)", { size: 1 });
|
|
70
82
|
return;
|
|
71
83
|
}
|
|
84
|
+
const failed = pane(state, "sessions.failed", t.failedLogins.length);
|
|
72
85
|
p.table({
|
|
73
86
|
rows: t.failedLogins,
|
|
87
|
+
selected: failed.selected,
|
|
88
|
+
offset: failed.offset,
|
|
89
|
+
followSelection: true,
|
|
90
|
+
scrollbar: true,
|
|
91
|
+
onScroll: (delta) => scrollPane(failed, delta),
|
|
92
|
+
onFocus: () => focusPane(state, "sessions.failed"),
|
|
93
|
+
onSelectRow: (row) => { failed.selected = failed.offset + row; },
|
|
74
94
|
columns: [
|
|
75
95
|
{ key: "user", title: "User", width: 12, color: theme.danger },
|
|
76
96
|
{ key: "from", title: "From", min: 12 },
|
package/src/screens/traffic.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Container, Theme } from "@profullstack/hqtui";
|
|
2
2
|
import { seriesColor } from "@profullstack/hqtui";
|
|
3
|
-
import {
|
|
3
|
+
import { focusPane, pane, scrollPane, type DemoState } from "../state.ts";
|
|
4
4
|
import { num, percent } from "../format.ts";
|
|
5
5
|
|
|
6
6
|
function rate(value: number): string {
|
|
@@ -116,8 +116,16 @@ export function trafficScreen(ui: Container, state: DemoState, theme: Theme): vo
|
|
|
116
116
|
{ labelWidth: 5, valueWidth: 7 },
|
|
117
117
|
);
|
|
118
118
|
p.divider({ label: "top paths" });
|
|
119
|
+
const paths = pane(state, "traffic.paths", http.topPaths.length);
|
|
119
120
|
p.table({
|
|
120
121
|
rows: http.topPaths,
|
|
122
|
+
selected: paths.selected,
|
|
123
|
+
offset: paths.offset,
|
|
124
|
+
followSelection: true,
|
|
125
|
+
scrollbar: true,
|
|
126
|
+
onScroll: (delta) => scrollPane(paths, delta),
|
|
127
|
+
onFocus: () => focusPane(state, "traffic.paths"),
|
|
128
|
+
onSelectRow: (row) => { paths.selected = paths.offset + row; },
|
|
121
129
|
header: false,
|
|
122
130
|
columns: [
|
|
123
131
|
{ key: "path", title: "Path", min: 20, color: theme.primary },
|
|
@@ -133,11 +141,15 @@ export function trafficScreen(ui: Container, state: DemoState, theme: Theme): vo
|
|
|
133
141
|
p.label("No sshd events in the journal.");
|
|
134
142
|
return;
|
|
135
143
|
}
|
|
144
|
+
const ssh = pane(state, "traffic.ssh", t.ssh.length);
|
|
136
145
|
p.table({
|
|
137
146
|
rows: [...t.ssh].reverse(),
|
|
138
|
-
selected:
|
|
139
|
-
offset:
|
|
147
|
+
selected: ssh.selected,
|
|
148
|
+
offset: ssh.offset,
|
|
140
149
|
followSelection: true,
|
|
150
|
+
onScroll: (delta) => scrollPane(ssh, delta),
|
|
151
|
+
onFocus: () => focusPane(state, "traffic.ssh"),
|
|
152
|
+
onSelectRow: (row) => { ssh.selected = ssh.offset + row; },
|
|
141
153
|
zebra: true,
|
|
142
154
|
scrollbar: true,
|
|
143
155
|
columns: [
|
|
@@ -163,8 +175,16 @@ export function trafficScreen(ui: Container, state: DemoState, theme: Theme): vo
|
|
|
163
175
|
p.label("No remote peers.");
|
|
164
176
|
return;
|
|
165
177
|
}
|
|
178
|
+
const remotes = pane(state, "traffic.remotes", t.remotes.length);
|
|
166
179
|
p.table({
|
|
167
180
|
rows: t.remotes,
|
|
181
|
+
selected: remotes.selected,
|
|
182
|
+
offset: remotes.offset,
|
|
183
|
+
followSelection: true,
|
|
184
|
+
scrollbar: true,
|
|
185
|
+
onScroll: (delta) => scrollPane(remotes, delta),
|
|
186
|
+
onFocus: () => focusPane(state, "traffic.remotes"),
|
|
187
|
+
onSelectRow: (row) => { remotes.selected = remotes.offset + row; },
|
|
168
188
|
zebra: true,
|
|
169
189
|
columns: [
|
|
170
190
|
{ key: "host", title: "Host", min: 16, color: theme.accent },
|
|
@@ -178,8 +198,15 @@ export function trafficScreen(ui: Container, state: DemoState, theme: Theme): vo
|
|
|
178
198
|
|
|
179
199
|
if (http && http.recent.length) {
|
|
180
200
|
ui.panel({ title: "Recent Requests", size: 10, borderColor: theme.primary }, (p) => {
|
|
201
|
+
const requests = pane(state, "traffic.requests", http.recent.length);
|
|
181
202
|
p.table({
|
|
182
203
|
rows: http.recent,
|
|
204
|
+
selected: requests.selected,
|
|
205
|
+
offset: requests.offset,
|
|
206
|
+
followSelection: true,
|
|
207
|
+
onScroll: (delta) => scrollPane(requests, delta),
|
|
208
|
+
onFocus: () => focusPane(state, "traffic.requests"),
|
|
209
|
+
onSelectRow: (row) => { requests.selected = requests.offset + row; },
|
|
183
210
|
zebra: true,
|
|
184
211
|
scrollbar: true,
|
|
185
212
|
columns: [
|
package/src/state.ts
CHANGED
|
@@ -18,10 +18,13 @@ export interface DemoState {
|
|
|
18
18
|
/** Why hardware sensors are missing on this host, when they are. */
|
|
19
19
|
sensorNote: string;
|
|
20
20
|
/**
|
|
21
|
-
* One cursor per
|
|
22
|
-
*
|
|
21
|
+
* One cursor per scrollable pane, not per screen. Sharing a cursor across a
|
|
22
|
+
* screen meant only one of its lists could ever be driven, so the second
|
|
23
|
+
* scrollbar on a screen sat there doing nothing.
|
|
23
24
|
*/
|
|
24
|
-
|
|
25
|
+
panes: Record<string, Pane>;
|
|
26
|
+
/** Which pane the arrow keys drive, per screen. Clicking a pane sets it. */
|
|
27
|
+
focused: Partial<Record<ScreenName, string>>;
|
|
25
28
|
sort: "cpu" | "mem" | "pid" | "name";
|
|
26
29
|
filter: string;
|
|
27
30
|
filtering: boolean;
|
|
@@ -48,9 +51,67 @@ export interface DemoState {
|
|
|
48
51
|
bytes: number;
|
|
49
52
|
}
|
|
50
53
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
+
export interface Pane {
|
|
55
|
+
selected: number;
|
|
56
|
+
offset: number;
|
|
57
|
+
/** Row count, refreshed by the screen each frame so keys can clamp. */
|
|
58
|
+
total: number;
|
|
59
|
+
/**
|
|
60
|
+
* A log has no selected row: the arrows move its window instead, and it
|
|
61
|
+
* counts backwards from the newest line rather than forwards from the first.
|
|
62
|
+
*/
|
|
63
|
+
kind: "list" | "log";
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* The cursor for one scrollable, created on first use. Screens call this while
|
|
68
|
+
* drawing, which is also what registers the pane as existing.
|
|
69
|
+
*/
|
|
70
|
+
export function pane(state: DemoState, id: string, total: number, kind: Pane["kind"] = "list"): Pane {
|
|
71
|
+
const existing = state.panes[id];
|
|
72
|
+
if (existing) {
|
|
73
|
+
existing.total = total;
|
|
74
|
+
return existing;
|
|
75
|
+
}
|
|
76
|
+
const created: Pane = { selected: 0, offset: 0, total, kind };
|
|
77
|
+
state.panes[id] = created;
|
|
78
|
+
// The first pane a screen draws is the one the arrows drive by default.
|
|
79
|
+
if (!state.focused[state.screen]) state.focused[state.screen] = id;
|
|
80
|
+
return created;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** The pane the arrow keys act on, for the screen that is showing. */
|
|
84
|
+
export function focusedPane(state: DemoState): Pane | undefined {
|
|
85
|
+
const id = state.focused[state.screen];
|
|
86
|
+
return id ? state.panes[id] : undefined;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function focusPane(state: DemoState, id: string): void {
|
|
90
|
+
state.focused[state.screen] = id;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Move a pane's window, dragging the selection so it stays inside. */
|
|
94
|
+
export function scrollPane(p: Pane, delta: number, rows = 3): void {
|
|
95
|
+
const max = Math.max(0, p.total - 1);
|
|
96
|
+
p.offset = Math.max(0, Math.min(p.offset + delta * rows, max));
|
|
97
|
+
if (p.kind !== "log") p.selected = Math.max(p.offset, Math.min(p.selected, max));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* What the arrow keys do to the focused pane. Lists move the selection and let
|
|
102
|
+
* the widget scroll to follow it; logs have nothing to select, so they move
|
|
103
|
+
* their own window.
|
|
104
|
+
*/
|
|
105
|
+
export function moveSelection(state: DemoState, delta: number): void {
|
|
106
|
+
const p = focusedPane(state);
|
|
107
|
+
if (!p || p.total === 0) return;
|
|
108
|
+
if (p.kind === "log") {
|
|
109
|
+
// Down means newer, which is a smaller distance from the end.
|
|
110
|
+
p.offset = Math.max(0, Math.min(p.total - 1, p.offset - delta));
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
p.selected = Math.max(0, Math.min(p.total - 1, p.selected + delta));
|
|
114
|
+
p.offset = Math.max(0, Math.min(p.offset, Math.max(0, p.total - 1)));
|
|
54
115
|
}
|
|
55
116
|
|
|
56
117
|
export function createState(
|
|
@@ -65,9 +126,8 @@ export function createState(
|
|
|
65
126
|
screen: "dashboard",
|
|
66
127
|
source,
|
|
67
128
|
unavailable,
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
) as DemoState["cursors"],
|
|
129
|
+
panes: {},
|
|
130
|
+
focused: {},
|
|
71
131
|
sort: "cpu",
|
|
72
132
|
filter: "",
|
|
73
133
|
filtering: false,
|