herdr-remote 0.2.38 → 0.3.31
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/chunks/353DMT35.js +3 -0
- package/dist/chunks/42KARAL4.js +31 -0
- package/dist/chunks/7AUQHB3O.js +10 -0
- package/dist/chunks/7SMZ43LI.js +9 -0
- package/dist/chunks/AM3OEMFK.js +16 -0
- package/dist/chunks/ANNJESOL.js +10 -0
- package/dist/chunks/AWB4A65P.js +63 -0
- package/dist/chunks/C2UUZY6J.js +2 -0
- package/dist/chunks/DGUZCGQN.js +3 -0
- package/dist/chunks/H24ZP5D5.js +16 -0
- package/dist/chunks/I6NRCYLI.js +3 -0
- package/dist/chunks/JJGEV4EZ.js +3 -0
- package/dist/chunks/KRSURB5L.js +5 -0
- package/dist/chunks/KRVF37OW.js +3 -0
- package/dist/chunks/KUDCC7ZO.js +6 -0
- package/dist/chunks/M3NRPJK2.js +77 -0
- package/dist/chunks/M4AFZHKH.js +3 -0
- package/dist/chunks/M52LJEES.js +6 -0
- package/dist/chunks/O2LNVFUT.js +3 -0
- package/dist/chunks/OJPBI7KB.js +3 -0
- package/dist/chunks/OMN6C7J6.js +3 -0
- package/dist/chunks/PBMBFWLQ.js +3 -0
- package/dist/chunks/PBRQ3HEG.js +3 -0
- package/dist/chunks/PFFVGFPL.js +45 -0
- package/dist/chunks/QRGNEKME.js +5 -0
- package/dist/chunks/TNLCNVA2.js +3 -0
- package/dist/chunks/U27JJKJ6.js +3 -0
- package/dist/chunks/U6QAYG3E.js +3 -0
- package/dist/chunks/UEOQE77N.js +4 -0
- package/dist/chunks/WSACSE45.js +4 -0
- package/dist/chunks/WY7WHEJ4.js +3 -0
- package/dist/chunks/XJKWLK43.js +4 -0
- package/dist/chunks/XN6D5S55.js +3 -0
- package/dist/chunks/YD3MIP7B.js +5 -0
- package/dist/chunks/YXDQ62BB.js +4 -0
- package/dist/cli.js +21 -294
- package/dist/connector/admin-broker.js +3 -302
- package/dist/connector/main.js +20 -18
- package/dist/harfbuzz-subset.wasm +0 -0
- package/dist/postinstall.js +17 -0
- package/herdr-plugin.toml +1 -1
- package/node_modules/herdr-remote-relay/bin/herdr-remote-relay.js +33 -92
- package/node_modules/herdr-remote-relay/package.json +3 -46
- package/package.json +8 -27
- package/dist/agent-status.js +0 -130
- package/dist/binary-plist.js +0 -157
- package/dist/config.js +0 -278
- package/dist/connector/admin-broker-setup.js +0 -40
- package/dist/connector/admin-shell.js +0 -67
- package/dist/connector/admin-tab.js +0 -90
- package/dist/connector/adminBrokerEndpoint.js +0 -68
- package/dist/connector/agent-watch.js +0 -123
- package/dist/connector/elevatedPty.js +0 -141
- package/dist/connector/font-server.js +0 -165
- package/dist/connector/herdr-prefix-keys.js +0 -28
- package/dist/connector/host-connector.js +0 -433
- package/dist/connector/host-hello.js +0 -32
- package/dist/connector/lock.js +0 -73
- package/dist/connector/sessions.js +0 -381
- package/dist/connector/update-report.js +0 -70
- package/dist/connector/wire.js +0 -42
- package/dist/exit-codes.js +0 -13
- package/dist/font-subset.js +0 -110
- package/dist/herdr-api.js +0 -254
- package/dist/herdr-command.js +0 -245
- package/dist/herdr-plugin.js +0 -120
- package/dist/herdr-server.js +0 -190
- package/dist/i18n/en.js +0 -257
- package/dist/i18n/index.js +0 -48
- package/dist/i18n/zh.js +0 -256
- package/dist/keepalive/detached.js +0 -97
- package/dist/keepalive/environment.js +0 -44
- package/dist/keepalive/index.js +0 -129
- package/dist/keepalive/launchd.js +0 -117
- package/dist/keepalive/systemd.js +0 -112
- package/dist/keepalive/types.js +0 -1
- package/dist/keepalive/windows-service-setup.js +0 -138
- package/dist/keepalive/windows-service.js +0 -272
- package/dist/keepalive/windows-task.js +0 -191
- package/dist/lib/process.js +0 -132
- package/dist/lifecycle.js +0 -79
- package/dist/net-interfaces.js +0 -82
- package/dist/node-pty-loader.js +0 -80
- package/dist/pasted-files.js +0 -115
- package/dist/paths.js +0 -74
- package/dist/pty-session.js +0 -133
- package/dist/relay-client.js +0 -106
- package/dist/relay-urls.js +0 -99
- package/dist/runtime.js +0 -107
- package/dist/service.js +0 -267
- package/dist/settings-model.js +0 -298
- package/dist/socket-discovery.js +0 -70
- package/dist/supervisor.js +0 -323
- package/dist/terminal-font/deps.js +0 -83
- package/dist/terminal-font/files.js +0 -357
- package/dist/terminal-font/formats.js +0 -355
- package/dist/terminal-font/index.js +0 -187
- package/dist/terminal-font/readers.js +0 -373
- package/dist/terminal-font/terminals.js +0 -147
- package/dist/terminal-palette.js +0 -399
- package/dist/tui/App.js +0 -199
- package/dist/tui/api.js +0 -91
- package/dist/tui/components/TextField.js +0 -50
- package/dist/tui/components/common.js +0 -80
- package/dist/tui/index.js +0 -36
- package/dist/tui/mouse/index.js +0 -164
- package/dist/tui/mouse/protocol.js +0 -96
- package/dist/tui/mouse/source.js +0 -79
- package/dist/tui/screens/About.js +0 -172
- package/dist/tui/screens/Herdr.js +0 -75
- package/dist/tui/screens/Keepalive.js +0 -141
- package/dist/tui/screens/Overview.js +0 -36
- package/dist/tui/screens/Pair.js +0 -74
- package/dist/tui/screens/Relay.js +0 -243
- package/dist/tui/screens/Services.js +0 -63
- package/dist/tui/screens/Wizard.js +0 -241
- package/dist/tui/terminal.js +0 -39
- package/dist/tui/theme.js +0 -33
- package/dist/update-command.js +0 -118
- package/dist/updater/in-place.js +0 -83
- package/dist/updater/npm.js +0 -62
- package/dist/updater.js +0 -401
- package/node_modules/herdr-remote-relay/README.md +0 -76
- package/node_modules/herdr-remote-relay/README.zh-CN.md +0 -76
- package/node_modules/herdr-remote-relay/config.example.json +0 -18
- package/node_modules/herdr-remote-relay/deploy/cloudflared/config.yml.example +0 -9
- package/node_modules/herdr-remote-relay/deploy/docker/compose.yaml +0 -23
- package/node_modules/herdr-remote-relay/deploy/nginx/herdr-remote-relay.conf.example +0 -49
- package/node_modules/herdr-remote-relay/deploy/systemd/herdr-remote-relay.service +0 -41
- package/node_modules/herdr-remote-relay/deploy/systemd/relay.env.example +0 -25
- package/node_modules/herdr-remote-relay/dist/auth-store.d.ts +0 -114
- package/node_modules/herdr-remote-relay/dist/auth-store.js +0 -289
- package/node_modules/herdr-remote-relay/dist/metrics.d.ts +0 -76
- package/node_modules/herdr-remote-relay/dist/metrics.js +0 -249
- package/node_modules/herdr-remote-relay/dist/protocol/frames.d.ts +0 -27
- package/node_modules/herdr-remote-relay/dist/protocol/frames.js +0 -110
- package/node_modules/herdr-remote-relay/dist/protocol/herdr-keys.d.ts +0 -8
- package/node_modules/herdr-remote-relay/dist/protocol/herdr-keys.js +0 -34
- package/node_modules/herdr-remote-relay/dist/protocol/http.d.ts +0 -165
- package/node_modules/herdr-remote-relay/dist/protocol/http.js +0 -3
- package/node_modules/herdr-remote-relay/dist/protocol/index.d.ts +0 -7
- package/node_modules/herdr-remote-relay/dist/protocol/index.js +0 -9
- package/node_modules/herdr-remote-relay/dist/protocol/messages.d.ts +0 -362
- package/node_modules/herdr-remote-relay/dist/protocol/messages.js +0 -20
- package/node_modules/herdr-remote-relay/dist/protocol/paste.d.ts +0 -14
- package/node_modules/herdr-remote-relay/dist/protocol/paste.js +0 -50
- package/node_modules/herdr-remote-relay/dist/protocol/release.d.ts +0 -8
- package/node_modules/herdr-remote-relay/dist/protocol/release.js +0 -17
- package/node_modules/herdr-remote-relay/dist/protocol/terminal.d.ts +0 -79
- package/node_modules/herdr-remote-relay/dist/protocol/terminal.js +0 -150
- package/node_modules/herdr-remote-relay/dist/relay-config.d.ts +0 -74
- package/node_modules/herdr-remote-relay/dist/relay-config.js +0 -316
- package/node_modules/herdr-remote-relay/dist/relay-server.d.ts +0 -67
- package/node_modules/herdr-remote-relay/dist/relay-server.js +0 -196
- package/node_modules/herdr-remote-relay/dist/scroll-input.d.ts +0 -17
- package/node_modules/herdr-remote-relay/dist/scroll-input.js +0 -124
- package/node_modules/herdr-remote-relay/dist/server/client-channel.d.ts +0 -3
- package/node_modules/herdr-remote-relay/dist/server/client-channel.js +0 -396
- package/node_modules/herdr-remote-relay/dist/server/font-proxy.d.ts +0 -19
- package/node_modules/herdr-remote-relay/dist/server/font-proxy.js +0 -155
- package/node_modules/herdr-remote-relay/dist/server/host-broadcasts.d.ts +0 -27
- package/node_modules/herdr-remote-relay/dist/server/host-broadcasts.js +0 -83
- package/node_modules/herdr-remote-relay/dist/server/host-channel.d.ts +0 -3
- package/node_modules/herdr-remote-relay/dist/server/host-channel.js +0 -271
- package/node_modules/herdr-remote-relay/dist/server/http-api.d.ts +0 -4
- package/node_modules/herdr-remote-relay/dist/server/http-api.js +0 -219
- package/node_modules/herdr-remote-relay/dist/server/maintenance.d.ts +0 -5
- package/node_modules/herdr-remote-relay/dist/server/maintenance.js +0 -64
- package/node_modules/herdr-remote-relay/dist/server/requests.d.ts +0 -34
- package/node_modules/herdr-remote-relay/dist/server/requests.js +0 -142
- package/node_modules/herdr-remote-relay/dist/server/sessions.d.ts +0 -36
- package/node_modules/herdr-remote-relay/dist/server/sessions.js +0 -230
- package/node_modules/herdr-remote-relay/dist/server/sockets.d.ts +0 -12
- package/node_modules/herdr-remote-relay/dist/server/sockets.js +0 -64
- package/node_modules/herdr-remote-relay/dist/server/status.d.ts +0 -11
- package/node_modules/herdr-remote-relay/dist/server/status.js +0 -99
- package/node_modules/herdr-remote-relay/dist/server/transport.d.ts +0 -16
- package/node_modules/herdr-remote-relay/dist/server/transport.js +0 -110
- package/node_modules/herdr-remote-relay/dist/server/types.d.ts +0 -133
- package/node_modules/herdr-remote-relay/dist/server/types.js +0 -3
- package/node_modules/herdr-remote-relay/dist/server/version.d.ts +0 -2
- package/node_modules/herdr-remote-relay/dist/server/version.js +0 -5
- package/node_modules/herdr-remote-relay/dist/state.d.ts +0 -6
- package/node_modules/herdr-remote-relay/dist/state.js +0 -47
- package/node_modules/ws/LICENSE +0 -20
- package/node_modules/ws/README.md +0 -548
- package/node_modules/ws/browser.js +0 -8
- package/node_modules/ws/index.js +0 -22
- package/node_modules/ws/lib/buffer-util.js +0 -131
- package/node_modules/ws/lib/constants.js +0 -19
- package/node_modules/ws/lib/event-target.js +0 -292
- package/node_modules/ws/lib/extension.js +0 -203
- package/node_modules/ws/lib/limiter.js +0 -55
- package/node_modules/ws/lib/permessage-deflate.js +0 -530
- package/node_modules/ws/lib/receiver.js +0 -743
- package/node_modules/ws/lib/sender.js +0 -607
- package/node_modules/ws/lib/stream.js +0 -161
- package/node_modules/ws/lib/subprotocol.js +0 -62
- package/node_modules/ws/lib/validation.js +0 -152
- package/node_modules/ws/lib/websocket-server.js +0 -562
- package/node_modules/ws/lib/websocket.js +0 -1407
- package/node_modules/ws/package.json +0 -74
- package/node_modules/ws/wrapper.mjs +0 -21
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useEffect, useState } from 'react';
|
|
3
|
-
import { Box, Text, useInput } from 'ink';
|
|
4
|
-
import { theme } from '../theme.js';
|
|
5
|
-
import { FieldRow, Message, Panel, Row, Selectable, StatusDot } from '../components/common.js';
|
|
6
|
-
import { TextField } from '../components/TextField.js';
|
|
7
|
-
import { MIN_HERDR_VERSION, getField, getFieldPlaceholder, herdrVersion, saveDraft, setField, } from '../api.js';
|
|
8
|
-
export function HerdrScreen({ ctx }) {
|
|
9
|
-
const { t, draft, editingId } = ctx;
|
|
10
|
-
const [selected, setSelected] = useState('socketPath');
|
|
11
|
-
const [installed, setInstalled] = useState(null);
|
|
12
|
-
useEffect(() => {
|
|
13
|
-
setInstalled(herdrVersion());
|
|
14
|
-
}, []);
|
|
15
|
-
const entries = [
|
|
16
|
-
{ id: 'socketPath', kind: 'field', label: t('herdr.socketPath') },
|
|
17
|
-
{ id: 'herdrArgs', kind: 'field', label: t('herdr.args') },
|
|
18
|
-
{ id: 'herdrAutoStart', kind: 'toggle', label: t('field.herdrAutoStart') },
|
|
19
|
-
{ id: 'save', kind: 'action', label: t('common.save') },
|
|
20
|
-
];
|
|
21
|
-
const applyField = (id, value) => {
|
|
22
|
-
const result = setField(draft, id, value);
|
|
23
|
-
const { errorKey } = result;
|
|
24
|
-
if (errorKey) {
|
|
25
|
-
ctx.notify((t) => t(errorKey), 'error');
|
|
26
|
-
return false;
|
|
27
|
-
}
|
|
28
|
-
ctx.updateDraft(result.draft);
|
|
29
|
-
return true;
|
|
30
|
-
};
|
|
31
|
-
const save = () => {
|
|
32
|
-
try {
|
|
33
|
-
const result = saveDraft(draft);
|
|
34
|
-
ctx.reloadConfig();
|
|
35
|
-
ctx.notify((t) => t('common.saved', { path: result.path }), 'success');
|
|
36
|
-
}
|
|
37
|
-
catch (error) {
|
|
38
|
-
ctx.notify((t) => t('error.saveFailed', { message: error.message }), 'error');
|
|
39
|
-
}
|
|
40
|
-
};
|
|
41
|
-
const activate = (id) => {
|
|
42
|
-
if (id === 'socketPath' || id === 'herdrArgs') {
|
|
43
|
-
ctx.setEditing(id);
|
|
44
|
-
return;
|
|
45
|
-
}
|
|
46
|
-
if (id === 'herdrAutoStart') {
|
|
47
|
-
applyField(id, draft.herdr.autoStart ? 'off' : 'on');
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
if (id === 'save')
|
|
51
|
-
save();
|
|
52
|
-
};
|
|
53
|
-
useInput((input, key) => {
|
|
54
|
-
const index = entries.findIndex((entry) => entry.id === selected);
|
|
55
|
-
const safeIndex = index >= 0 ? index : 0;
|
|
56
|
-
if (key.upArrow)
|
|
57
|
-
setSelected(entries[(safeIndex - 1 + entries.length) % entries.length].id);
|
|
58
|
-
else if (key.downArrow)
|
|
59
|
-
setSelected(entries[(safeIndex + 1) % entries.length].id);
|
|
60
|
-
else if (key.return)
|
|
61
|
-
activate(selected);
|
|
62
|
-
else if (input === 's')
|
|
63
|
-
save();
|
|
64
|
-
}, { isActive: editingId === null });
|
|
65
|
-
return (_jsxs(Panel, { title: t('herdr.title'), children: [entries.map((entry) => entry.kind === 'toggle' ? (_jsx(FieldRow, { label: entry.label, selected: selected === entry.id, onSelect: () => {
|
|
66
|
-
setSelected(entry.id);
|
|
67
|
-
activate(entry.id);
|
|
68
|
-
}, onHover: () => setSelected(entry.id), children: _jsx(Text, { color: draft.herdr.autoStart ? theme.ok : theme.muted, children: draft.herdr.autoStart ? `[×] ${t('common.on')}` : `[ ] ${t('common.off')}` }) }, entry.id)) : entry.kind === 'field' ? (_jsx(FieldRow, { label: entry.label, selected: selected === entry.id, onSelect: () => activate(entry.id), onHover: () => setSelected(entry.id), children: _jsx(TextField, { value: getField(draft, entry.id), placeholder: ctx.t(getFieldPlaceholder(draft, entry.id)), active: editingId === entry.id, onSubmit: (value) => {
|
|
69
|
-
if (applyField(entry.id, value))
|
|
70
|
-
ctx.setEditing(null);
|
|
71
|
-
}, onCancel: () => ctx.setEditing(null) }) }, entry.id)) : (_jsx(Box, { marginTop: 1, children: _jsx(Selectable, { selected: selected === entry.id, onSelect: () => {
|
|
72
|
-
setSelected(entry.id);
|
|
73
|
-
activate(entry.id);
|
|
74
|
-
}, onHover: () => setSelected(entry.id), children: entry.label }) }, entry.id))), selected === 'herdrAutoStart' ? (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: theme.muted, children: t('herdr.autoStartHint') }) })) : null, ctx.dirty ? (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: theme.warn, children: t('hint.unsavedChanges') }) })) : null, _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [installed?.version ? (_jsx(Row, { label: t('herdr.version'), children: _jsxs(Box, { children: [_jsx(StatusDot, { level: installed.supported ? 'ok' : 'warn' }), _jsx(Text, { children: ` ${installed.version}` })] }) })) : null, installed?.version && !installed.supported ? (_jsx(Text, { color: theme.warn, children: t('herdr.versionOutdated', { version: installed.version, minimum: MIN_HERDR_VERSION }) })) : null, installed && !installed.ok ? (_jsx(Text, { color: theme.warn, children: t('herdr.cliMissing') })) : null] }), _jsx(Message, { text: ctx.message?.text ?? null, level: ctx.message?.level ?? 'info' })] }));
|
|
75
|
-
}
|
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { useEffect, useState } from 'react';
|
|
3
|
-
import { Box, Text, useInput } from 'ink';
|
|
4
|
-
import { theme } from '../theme.js';
|
|
5
|
-
import { Message, Panel, Row, Selectable, StatusDot } from '../components/common.js';
|
|
6
|
-
import { ChoiceList } from './Relay.js';
|
|
7
|
-
import { KEEPALIVE_MANAGERS, adminBrokerStatus, configureAdminBroker, keepalive, saveDraft, setField, } from '../api.js';
|
|
8
|
-
export function Keepalive({ ctx }) {
|
|
9
|
-
const { t, status, draft, editingId } = ctx;
|
|
10
|
-
const [selected, setSelected] = useState('install');
|
|
11
|
-
const [brokerReady, setBrokerReady] = useState(null);
|
|
12
|
-
useEffect(() => {
|
|
13
|
-
if (process.platform !== 'win32')
|
|
14
|
-
return undefined;
|
|
15
|
-
let cancelled = false;
|
|
16
|
-
adminBrokerStatus()
|
|
17
|
-
.then((available) => {
|
|
18
|
-
if (!cancelled)
|
|
19
|
-
setBrokerReady(available);
|
|
20
|
-
})
|
|
21
|
-
.catch(() => {
|
|
22
|
-
if (!cancelled)
|
|
23
|
-
setBrokerReady(false);
|
|
24
|
-
});
|
|
25
|
-
return () => {
|
|
26
|
-
cancelled = true;
|
|
27
|
-
};
|
|
28
|
-
}, []);
|
|
29
|
-
const current = status?.keepalive;
|
|
30
|
-
const entries = [
|
|
31
|
-
{ id: 'install', label: t('keepalive.install') },
|
|
32
|
-
{ id: 'restart', label: t('keepalive.restart') },
|
|
33
|
-
{ id: 'uninstall', label: t('keepalive.uninstall') },
|
|
34
|
-
{ id: 'manager', label: `${t('keepalive.manager')}: ${draft.keepalive.manager}` },
|
|
35
|
-
...(process.platform === 'linux' && current?.manager === 'systemd' && !current?.linger
|
|
36
|
-
? [{ id: 'linger', label: t('keepalive.enableLinger') }]
|
|
37
|
-
: []),
|
|
38
|
-
...(process.platform === 'win32'
|
|
39
|
-
? [
|
|
40
|
-
{ id: 'adminBrokerInstall', label: t('adminBroker.install') },
|
|
41
|
-
{ id: 'adminBrokerUninstall', label: t('adminBroker.uninstall') },
|
|
42
|
-
]
|
|
43
|
-
: []),
|
|
44
|
-
];
|
|
45
|
-
const activate = (id) => {
|
|
46
|
-
if (id === 'manager') {
|
|
47
|
-
ctx.setEditing('keepaliveManager');
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
if (id === 'adminBrokerInstall' || id === 'adminBrokerUninstall') {
|
|
51
|
-
const action = id === 'adminBrokerInstall' ? 'install' : 'uninstall';
|
|
52
|
-
ctx.run(async () => {
|
|
53
|
-
let ready = false;
|
|
54
|
-
try {
|
|
55
|
-
await configureAdminBroker(action, t);
|
|
56
|
-
ready = action === 'install' && (await adminBrokerStatus({ waitForStart: true }));
|
|
57
|
-
}
|
|
58
|
-
catch (error) {
|
|
59
|
-
if (error.code === 'ELEVATION_CANCELLED') {
|
|
60
|
-
ctx.notify((t) => t('adminBroker.elevationCancelled'), 'info');
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
|
-
throw error;
|
|
64
|
-
}
|
|
65
|
-
setBrokerReady(ready);
|
|
66
|
-
ctx.notify((t) => t(`adminBroker.${action}Done`), 'success');
|
|
67
|
-
});
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
ctx.run(async () => {
|
|
71
|
-
if (id === 'install') {
|
|
72
|
-
if (keepalive.detectManager(ctx.config.keepalive.manager) === 'windows-service')
|
|
73
|
-
ctx.notify((t) => t('keepalive.serviceWindowHint'), 'info');
|
|
74
|
-
try {
|
|
75
|
-
const result = await keepalive.install(ctx.config);
|
|
76
|
-
ctx.notify((t) => t('keepalive.installed', { manager: result.manager }), 'success');
|
|
77
|
-
}
|
|
78
|
-
catch (error) {
|
|
79
|
-
if (error.code === 'ELEVATION_CANCELLED') {
|
|
80
|
-
ctx.notify((t) => t('keepalive.elevationCancelled'), 'info');
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
throw error;
|
|
84
|
-
}
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
|
-
if (id === 'uninstall') {
|
|
88
|
-
await keepalive.uninstall(ctx.config);
|
|
89
|
-
ctx.notify((t) => t('keepalive.uninstalled'), 'success');
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
|
-
if (id === 'restart') {
|
|
93
|
-
keepalive.restart(ctx.config);
|
|
94
|
-
ctx.notify((t) => t('keepalive.restarted'), 'success');
|
|
95
|
-
return;
|
|
96
|
-
}
|
|
97
|
-
if (id === 'linger') {
|
|
98
|
-
const result = keepalive.enableLinger();
|
|
99
|
-
ctx.notify((t) => t('keepalive.lingerDone', { username: result.username }), 'success');
|
|
100
|
-
}
|
|
101
|
-
});
|
|
102
|
-
};
|
|
103
|
-
useInput((_input, key) => {
|
|
104
|
-
const index = entries.findIndex((entry) => entry.id === selected);
|
|
105
|
-
if (key.upArrow)
|
|
106
|
-
setSelected(entries[(index - 1 + entries.length) % entries.length].id);
|
|
107
|
-
else if (key.downArrow)
|
|
108
|
-
setSelected(entries[(index + 1) % entries.length].id);
|
|
109
|
-
else if (key.return)
|
|
110
|
-
activate(selected);
|
|
111
|
-
}, { isActive: editingId === null });
|
|
112
|
-
if (editingId === 'keepaliveManager') {
|
|
113
|
-
return (_jsx(Panel, { title: t('field.keepalive'), children: _jsx(ChoiceList, { options: KEEPALIVE_MANAGERS.map((manager) => ({ id: manager, label: manager })), current: draft.keepalive.manager, onPick: (manager) => {
|
|
114
|
-
const result = setField(draft, 'keepaliveManager', manager);
|
|
115
|
-
const { errorKey } = result;
|
|
116
|
-
if (errorKey) {
|
|
117
|
-
ctx.notify((t) => t(errorKey), 'error');
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
|
-
ctx.updateDraft(result.draft);
|
|
121
|
-
try {
|
|
122
|
-
saveDraft(result.draft);
|
|
123
|
-
ctx.reloadConfig();
|
|
124
|
-
}
|
|
125
|
-
catch (error) {
|
|
126
|
-
ctx.notify((t) => t('error.saveFailed', { message: error.message }), 'error');
|
|
127
|
-
}
|
|
128
|
-
ctx.setEditing(null);
|
|
129
|
-
}, onCancel: () => ctx.setEditing(null) }) }));
|
|
130
|
-
}
|
|
131
|
-
return (_jsxs(Panel, { title: t('keepalive.title'), children: [_jsx(Row, { label: t('keepalive.manager'), children: _jsx(Text, { children: current?.manager ?? t('common.unknown') }) }), _jsx(Row, { label: t('keepalive.state'), children: _jsxs(Box, { children: [_jsx(StatusDot, { level: current?.active ? 'ok' : current?.installed ? 'warn' : 'idle' }), _jsxs(Text, { children: [' ', current?.active
|
|
132
|
-
? t('common.running')
|
|
133
|
-
: current?.installed
|
|
134
|
-
? t('common.installed')
|
|
135
|
-
: t('common.notInstalled')] })] }) }), process.platform === 'win32' ? (_jsxs(_Fragment, { children: [_jsx(Row, { label: t('adminBroker.title'), children: _jsx(Text, { color: brokerReady ? theme.ok : theme.warn, children: brokerReady === null
|
|
136
|
-
? t('adminBroker.checking')
|
|
137
|
-
: t(brokerReady ? 'adminBroker.ready' : 'adminBroker.notReady') }) }), _jsx(Text, { color: theme.muted, children: t('adminBroker.tuiHint') })] })) : null, current?.unitPath ? (_jsx(Row, { label: t('keepalive.unitPath'), children: _jsx(Text, { color: theme.muted, children: current.unitPath }) })) : null, _jsx(Box, { marginTop: 1, flexDirection: "column", children: entries.map((entry) => (_jsx(Selectable, { selected: selected === entry.id, onSelect: () => {
|
|
138
|
-
setSelected(entry.id);
|
|
139
|
-
activate(entry.id);
|
|
140
|
-
}, onHover: () => setSelected(entry.id), children: entry.label }, entry.id))) }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [current?.manager === 'systemd' ? (_jsx(Text, { color: current.linger ? theme.muted : theme.warn, children: current.linger ? t('keepalive.lingerEnabled') : t('keepalive.lingerDisabled') })) : null, current?.manager === 'supervisor' ? (_jsx(Text, { color: theme.warn, children: t('keepalive.fallbackNote') })) : null, current?.manager === 'windows-task' ? (_jsx(Text, { color: theme.muted, children: t('keepalive.windowsTaskNote') })) : null, current?.manager === 'windows-service' ? (_jsx(Text, { color: theme.warn, children: t('keepalive.serviceNote') })) : null, _jsx(Text, { color: theme.muted, children: t('keepalive.logsHint', { command: status?.logsHint ?? '' }) })] }), _jsx(Message, { text: ctx.message?.text ?? null, level: ctx.message?.level ?? 'info' })] }));
|
|
141
|
-
}
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { Box, Text } from 'ink';
|
|
3
|
-
import { theme } from '../theme.js';
|
|
4
|
-
import { Message, Panel, Row, StatusDot } from '../components/common.js';
|
|
5
|
-
import { formatUptime } from '../api.js';
|
|
6
|
-
function level(alive) {
|
|
7
|
-
if (alive === null || alive === undefined)
|
|
8
|
-
return 'idle';
|
|
9
|
-
return alive ? 'ok' : 'bad';
|
|
10
|
-
}
|
|
11
|
-
export function Overview({ ctx }) {
|
|
12
|
-
const { t, status, config } = ctx;
|
|
13
|
-
if (!status) {
|
|
14
|
-
return (_jsx(Panel, { title: t('overview.title'), children: _jsx(Text, { color: theme.muted, children: t('common.loading') }) }));
|
|
15
|
-
}
|
|
16
|
-
const health = status.relay.health || {};
|
|
17
|
-
const relayReachable = health.ok !== false;
|
|
18
|
-
const relayLevel = status.relay.local
|
|
19
|
-
? level(status.relay.alive)
|
|
20
|
-
: relayReachable
|
|
21
|
-
? 'ok'
|
|
22
|
-
: 'bad';
|
|
23
|
-
const keepaliveLevel = status.keepalive.active
|
|
24
|
-
? 'ok'
|
|
25
|
-
: status.keepalive.installed
|
|
26
|
-
? 'warn'
|
|
27
|
-
: 'idle';
|
|
28
|
-
const nothingRunning = !status.host.alive && (status.relay.local ? !status.relay.alive : !relayReachable);
|
|
29
|
-
return (_jsxs(Panel, { title: t('overview.title'), children: [_jsx(Row, { label: t('overview.mode'), children: _jsx(Text, { children: t(`mode.${status.mode}`) }) }), _jsx(Row, { label: t('overview.relay'), children: _jsxs(Box, { children: [_jsx(StatusDot, { level: relayLevel }), _jsxs(Text, { children: [' ', status.relay.local
|
|
30
|
-
? t('overview.relayLocal', { bind: status.relay.bind ?? '', port: status.relay.port })
|
|
31
|
-
: t('overview.relayRemote', { url: status.relay.remoteUrl ?? '' })] }), status.relay.local && status.relay.pid ? (_jsx(Text, { color: theme.muted, children: ` ${t('common.pid', { pid: status.relay.pid })}` })) : null] }) }), _jsx(Row, { label: t('overview.host'), children: _jsxs(Box, { children: [_jsx(StatusDot, { level: level(status.host.alive) }), _jsx(Text, { children: ` ${status.host.alive ? t('common.running') : t('common.stopped')}` }), status.host.pid ? (_jsx(Text, { color: theme.muted, children: ` ${t('common.pid', { pid: status.host.pid })}` })) : null] }) }), _jsx(Row, { label: t('overview.socket'), children: _jsxs(Box, { children: [_jsx(StatusDot, { level: status.host.socketExists ? 'ok' : 'warn' }), _jsx(Text, { color: theme.muted, children: ` ${status.host.socketPath}` }), status.host.socketExists ? null : (_jsx(Text, { color: theme.warn, children: ` ${t('overview.socketMissing')}` }))] }) }), _jsx(Row, { label: t('overview.webUrl'), children: _jsx(Text, { color: theme.accent, children: status.publicUrl }) }), _jsx(Row, { label: t('overview.keepalive'), children: _jsxs(Box, { children: [_jsx(StatusDot, { level: keepaliveLevel }), _jsxs(Text, { children: [` ${status.keepalive.manager} — `, status.keepalive.active
|
|
32
|
-
? t('common.running')
|
|
33
|
-
: status.keepalive.installed
|
|
34
|
-
? t('common.installed')
|
|
35
|
-
: t('common.notInstalled')] })] }) }), _jsx(Box, { marginTop: 1, flexDirection: "column", children: relayReachable ? (_jsxs(_Fragment, { children: [typeof health.hosts === 'number' ? (_jsx(Row, { label: t('overview.hosts'), children: _jsx(Text, { children: health.hosts }) })) : null, typeof health.clients === 'number' ? (_jsx(Row, { label: t('overview.devices'), children: _jsx(Text, { children: health.clients }) })) : null, _jsx(Row, { label: t('overview.uptime'), children: _jsx(Text, { color: theme.muted, children: formatUptime(health.uptimeSeconds, t) }) })] })) : (_jsx(Text, { color: theme.warn, children: t('overview.unreachable', { message: health.message ?? '' }) })) }), nothingRunning ? (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: theme.muted, children: t('overview.notStarted') }) })) : null, config.relay.mode === 'local' ? (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: theme.muted, children: t('mode.local.description') }) })) : null, _jsx(Message, { text: ctx.message?.text ?? null, level: ctx.message?.level ?? 'info' })] }));
|
|
36
|
-
}
|
package/dist/tui/screens/Pair.js
DELETED
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useEffect, useState } from 'react';
|
|
3
|
-
import { Box, Text, useInput, useWindowSize } from 'ink';
|
|
4
|
-
import QRCode from 'qrcode';
|
|
5
|
-
import { theme } from '../theme.js';
|
|
6
|
-
import { Menu, Message, Panel } from '../components/common.js';
|
|
7
|
-
import { extractPairingCode, pair } from '../api.js';
|
|
8
|
-
// A QR block needs room for the symbol plus its quiet zone; below this the
|
|
9
|
-
// output wraps into noise, so the URL alone is the honest thing to show.
|
|
10
|
-
const MIN_QR_COLUMNS = 44;
|
|
11
|
-
export function PairScreen({ ctx }) {
|
|
12
|
-
const { t, run } = ctx;
|
|
13
|
-
const { columns } = useWindowSize();
|
|
14
|
-
const [pairing, setPairing] = useState(null);
|
|
15
|
-
const [qr, setQr] = useState(null);
|
|
16
|
-
const [remainingMs, setRemainingMs] = useState(0);
|
|
17
|
-
const [selected, setSelected] = useState('generate');
|
|
18
|
-
useEffect(() => {
|
|
19
|
-
if (!pairing)
|
|
20
|
-
return undefined;
|
|
21
|
-
const tick = () => setRemainingMs(Math.max(0, pairing.expiresAt - Date.now()));
|
|
22
|
-
tick();
|
|
23
|
-
const timer = setInterval(tick, 1000);
|
|
24
|
-
return () => clearInterval(timer);
|
|
25
|
-
}, [pairing]);
|
|
26
|
-
useEffect(() => {
|
|
27
|
-
if (!pairing || columns < MIN_QR_COLUMNS) {
|
|
28
|
-
setQr(null);
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
let cancelled = false;
|
|
32
|
-
QRCode.toString(pairing.pairUrl, { type: 'terminal', small: true, errorCorrectionLevel: 'L' })
|
|
33
|
-
.then((value) => {
|
|
34
|
-
if (!cancelled)
|
|
35
|
-
setQr(value.replace(/\n$/, ''));
|
|
36
|
-
})
|
|
37
|
-
.catch(() => {
|
|
38
|
-
if (!cancelled)
|
|
39
|
-
setQr(null);
|
|
40
|
-
});
|
|
41
|
-
return () => {
|
|
42
|
-
cancelled = true;
|
|
43
|
-
};
|
|
44
|
-
}, [pairing, columns]);
|
|
45
|
-
const generate = () => run(async () => {
|
|
46
|
-
ctx.notify((t) => t('pair.working'));
|
|
47
|
-
try {
|
|
48
|
-
const result = await pair();
|
|
49
|
-
const code = extractPairingCode(result);
|
|
50
|
-
setPairing({ ...result, code });
|
|
51
|
-
ctx.notify('', 'info');
|
|
52
|
-
}
|
|
53
|
-
catch (error) {
|
|
54
|
-
const message = error.message;
|
|
55
|
-
setPairing(null);
|
|
56
|
-
throw new Error(/host_offline|no Herdr host|did not register/i.test(message)
|
|
57
|
-
? t('pair.hostOffline')
|
|
58
|
-
: t('pair.failed', { message }));
|
|
59
|
-
}
|
|
60
|
-
});
|
|
61
|
-
useInput((_input, key) => {
|
|
62
|
-
if (key.return)
|
|
63
|
-
generate();
|
|
64
|
-
}, { isActive: ctx.editingId === null });
|
|
65
|
-
const expired = pairing !== null && remainingMs <= 0;
|
|
66
|
-
const minutes = Math.floor(remainingMs / 60000);
|
|
67
|
-
const seconds = Math.floor((remainingMs % 60000) / 1000);
|
|
68
|
-
return (_jsxs(Panel, { title: t('pair.title'), children: [_jsx(Menu, { items: [{ id: 'generate', label: pairing ? t('pair.regenerate') : t('pair.generate') }], selectedId: selected, onChange: setSelected, onSelect: generate }), pairing ? (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsxs(Box, { children: [_jsx(Text, { color: theme.muted, children: `${t('pair.code')} ` }), _jsx(Text, { bold: true, color: expired ? theme.muted : theme.accent, children: pairing.code.split('').join(' ') })] }), _jsxs(Box, { children: [_jsx(Text, { color: theme.muted, children: `${t('pair.url')} ` }), _jsx(Text, { children: pairing.pairUrl })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: expired ? theme.bad : theme.muted, children: expired
|
|
69
|
-
? t('pair.expired')
|
|
70
|
-
: t('pair.expires', {
|
|
71
|
-
minutes: `${minutes}:${String(seconds).padStart(2, '0')}`,
|
|
72
|
-
time: new Date(pairing.expiresAt).toLocaleTimeString(),
|
|
73
|
-
}) }) }), !expired && qr ? (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { children: qr }), _jsx(Text, { color: theme.muted, children: t('pair.qrHint') })] })) : null, !expired && !qr && columns < MIN_QR_COLUMNS ? (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: theme.muted, children: t('pair.qrUnavailable') }) })) : null, _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: theme.muted, children: t('pair.instructions') }) })] })) : null, _jsx(Message, { text: ctx.message?.text ?? null, level: ctx.message?.level ?? 'info' })] }));
|
|
74
|
-
}
|
|
@@ -1,243 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useMemo, useState } from 'react';
|
|
3
|
-
import { Box, Text, useInput } from 'ink';
|
|
4
|
-
import { theme } from '../theme.js';
|
|
5
|
-
import { FieldRow, Menu, Message, Panel, Row, Selectable } from '../components/common.js';
|
|
6
|
-
import { TextField } from '../components/TextField.js';
|
|
7
|
-
import { bindAddress, fieldsForMode, getField, getFieldPlaceholder, isOfficialRelay, listReachableAddresses, probeRelay, regenerateHostIdentity, relayStartCommand, saveDraft, selectedMode, setField, setRelayPassword, } from '../api.js';
|
|
8
|
-
export function RelayScreen({ ctx }) {
|
|
9
|
-
const { t, draft, editingId } = ctx;
|
|
10
|
-
const [selected, setSelected] = useState('mode');
|
|
11
|
-
const [revealTokens, setRevealTokens] = useState(false);
|
|
12
|
-
const [showEnv, setShowEnv] = useState(false);
|
|
13
|
-
const [password, setPassword] = useState(ctx.runtime.relayPassword || '');
|
|
14
|
-
// The official relay is ours: its address is fixed and it takes no password.
|
|
15
|
-
// Both are therefore facts to *show*, not fields to fill in — an editable box
|
|
16
|
-
// holding a value that must not change is an invitation to break the setup.
|
|
17
|
-
const official = isOfficialRelay(draft);
|
|
18
|
-
const fields = useMemo(() => fieldsForMode(draft.relay.mode).filter((field) => ['mode', 'port', 'lanHost', 'remoteUrl', 'publicUrl'].includes(field.id)), [draft.relay.mode]);
|
|
19
|
-
const entries = [
|
|
20
|
-
...fields.map((field) => ({
|
|
21
|
-
id: field.id,
|
|
22
|
-
// The official relay's address is ours and its browser URL follows from
|
|
23
|
-
// it, so neither is offered for editing: an editable box holding a value
|
|
24
|
-
// that must not change is an invitation to break the setup.
|
|
25
|
-
kind: (official && (field.id === 'remoteUrl' || field.id === 'publicUrl')
|
|
26
|
-
? 'readonly'
|
|
27
|
-
: 'field'),
|
|
28
|
-
fieldKind: field.kind,
|
|
29
|
-
label: t(field.labelKey),
|
|
30
|
-
})),
|
|
31
|
-
// The password only matters when talking to a relay somebody else started;
|
|
32
|
-
// a local relay is configured with our own token automatically, and the
|
|
33
|
-
// official one authenticates workstations by their own host token.
|
|
34
|
-
...(draft.relay.mode === 'remote' && !official
|
|
35
|
-
? [{ id: 'password', kind: 'password', label: t('relay.password') }]
|
|
36
|
-
: []),
|
|
37
|
-
// Saving needs a row of its own. Every field here only edits a draft, and
|
|
38
|
-
// the services restart from what is on disk — with `s` as the sole way to
|
|
39
|
-
// commit, a change could look applied, survive a restart, and never take
|
|
40
|
-
// effect. It is also the only way to save with the mouse.
|
|
41
|
-
{ id: 'save', kind: 'action', label: t('common.save') },
|
|
42
|
-
{ id: 'test', kind: 'action', label: t('relay.test') },
|
|
43
|
-
...(draft.relay.mode === 'remote' && !official
|
|
44
|
-
? [
|
|
45
|
-
{
|
|
46
|
-
id: 'reveal',
|
|
47
|
-
kind: 'action',
|
|
48
|
-
label: revealTokens ? t('relay.hidePassword') : t('relay.showPassword'),
|
|
49
|
-
},
|
|
50
|
-
{ id: 'env', kind: 'action', label: t('relay.envSnippet') },
|
|
51
|
-
]
|
|
52
|
-
: []),
|
|
53
|
-
{ id: 'regenerate', kind: 'action', label: t('relay.regenerate') },
|
|
54
|
-
];
|
|
55
|
-
const addresses = useMemo(() => listReachableAddresses({ includeLoopback: false }), []);
|
|
56
|
-
/**
|
|
57
|
-
* Apply several fields as one edit.
|
|
58
|
-
*
|
|
59
|
-
* Each `setField` has to build on the previous result rather than on `draft`,
|
|
60
|
-
* which is the value captured when this render began. Calling a single-field
|
|
61
|
-
* helper twice in a row silently dropped the first change: the second call
|
|
62
|
-
* started from the stale draft and committed it back over the first.
|
|
63
|
-
*/
|
|
64
|
-
const applyFields = (updates) => {
|
|
65
|
-
let next = draft;
|
|
66
|
-
for (const [id, value] of updates) {
|
|
67
|
-
const result = setField(next, id, value);
|
|
68
|
-
const { errorKey } = result;
|
|
69
|
-
if (errorKey) {
|
|
70
|
-
ctx.notify((t) => t(errorKey), 'error');
|
|
71
|
-
return false;
|
|
72
|
-
}
|
|
73
|
-
next = result.draft;
|
|
74
|
-
}
|
|
75
|
-
ctx.updateDraft(next);
|
|
76
|
-
ctx.notify('', 'info');
|
|
77
|
-
return true;
|
|
78
|
-
};
|
|
79
|
-
const applyField = (id, value) => applyFields([[id, value]]);
|
|
80
|
-
const save = () => {
|
|
81
|
-
try {
|
|
82
|
-
const result = saveDraft(draft);
|
|
83
|
-
ctx.reloadConfig();
|
|
84
|
-
ctx.notify((t) => t('common.saved', { path: result.path }), 'success');
|
|
85
|
-
}
|
|
86
|
-
catch (error) {
|
|
87
|
-
const problems = error.problems;
|
|
88
|
-
ctx.notify((t) => problems
|
|
89
|
-
? problems.map((key) => t(key)).join(' ')
|
|
90
|
-
: t('error.saveFailed', { message: error.message }), 'error');
|
|
91
|
-
}
|
|
92
|
-
};
|
|
93
|
-
const activate = (id) => {
|
|
94
|
-
const entry = entries.find((candidate) => candidate.id === id);
|
|
95
|
-
if (!entry)
|
|
96
|
-
return;
|
|
97
|
-
if (entry.kind === 'readonly') {
|
|
98
|
-
ctx.notify((t) => t('relay.officialFixed'), 'info');
|
|
99
|
-
return;
|
|
100
|
-
}
|
|
101
|
-
if (entry.kind === 'field' || entry.kind === 'password') {
|
|
102
|
-
ctx.setEditing(id);
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
105
|
-
if (id === 'save') {
|
|
106
|
-
save();
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
109
|
-
if (id === 'reveal') {
|
|
110
|
-
setRevealTokens((current) => !current);
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
if (id === 'env') {
|
|
114
|
-
setShowEnv((current) => !current);
|
|
115
|
-
return;
|
|
116
|
-
}
|
|
117
|
-
if (id === 'regenerate') {
|
|
118
|
-
ctx.run(() => {
|
|
119
|
-
regenerateHostIdentity();
|
|
120
|
-
ctx.reloadConfig();
|
|
121
|
-
ctx.notify((t) => t('relay.regenerated'), 'success');
|
|
122
|
-
});
|
|
123
|
-
return;
|
|
124
|
-
}
|
|
125
|
-
if (id === 'test') {
|
|
126
|
-
ctx.run(async () => {
|
|
127
|
-
const result = await probeRelay(ctx.config);
|
|
128
|
-
ctx.notify((t) => result.ok
|
|
129
|
-
? t('relay.testOk', { version: result.version ?? '?', hosts: result.hosts ?? 0 })
|
|
130
|
-
: t('relay.testFailed', { message: result.message ?? '' }), result.ok ? 'success' : 'error');
|
|
131
|
-
});
|
|
132
|
-
}
|
|
133
|
-
};
|
|
134
|
-
useInput((input, key) => {
|
|
135
|
-
const index = entries.findIndex((entry) => entry.id === selected);
|
|
136
|
-
if (key.upArrow)
|
|
137
|
-
setSelected(entries[(index - 1 + entries.length) % entries.length].id);
|
|
138
|
-
else if (key.downArrow)
|
|
139
|
-
setSelected(entries[(index + 1) % entries.length].id);
|
|
140
|
-
else if (key.return)
|
|
141
|
-
activate(selected);
|
|
142
|
-
else if (input === 's')
|
|
143
|
-
save();
|
|
144
|
-
}, { isActive: editingId === null });
|
|
145
|
-
// Choice editors take over the panel so the option list is unambiguous.
|
|
146
|
-
if (editingId === 'mode') {
|
|
147
|
-
return (_jsx(Panel, { title: t('field.mode'), children: _jsx(ChoiceList, { options: [
|
|
148
|
-
...['local', 'lan'].map((mode) => ({
|
|
149
|
-
id: mode,
|
|
150
|
-
label: t(`mode.${mode}`),
|
|
151
|
-
description: t(`mode.${mode}.description`),
|
|
152
|
-
})),
|
|
153
|
-
{
|
|
154
|
-
id: 'official',
|
|
155
|
-
label: t('mode.official'),
|
|
156
|
-
description: t('mode.official.description'),
|
|
157
|
-
},
|
|
158
|
-
{ id: 'remote', label: t('mode.remote'), description: t('mode.remote.description') },
|
|
159
|
-
],
|
|
160
|
-
// Reflects the official relay as its own selection rather than as an
|
|
161
|
-
// indistinguishable "self-hosted" row that happens to hold our URL.
|
|
162
|
-
current: selectedMode(draft), onPick: (choice) => {
|
|
163
|
-
// `official` is a mode the settings model understands: it sets the
|
|
164
|
-
// address along with the mode, and choosing the self-hosted relay
|
|
165
|
-
// clears it again. The screen does not have to keep the two fields
|
|
166
|
-
// in step by hand, which is what let the picker land on "official"
|
|
167
|
-
// while every row below still described a self-hosted relay.
|
|
168
|
-
if (!applyField('mode', choice))
|
|
169
|
-
return;
|
|
170
|
-
if (choice === 'official') {
|
|
171
|
-
// A password typed for somebody else's relay is not a credential
|
|
172
|
-
// the official one wants, and leaving it behind would send it
|
|
173
|
-
// there on the next connection.
|
|
174
|
-
setPassword('');
|
|
175
|
-
setRelayPassword('');
|
|
176
|
-
}
|
|
177
|
-
ctx.setEditing(null);
|
|
178
|
-
}, onCancel: () => ctx.setEditing(null) }) }));
|
|
179
|
-
}
|
|
180
|
-
if (editingId === 'lanHost') {
|
|
181
|
-
const options = addresses.map((address) => ({
|
|
182
|
-
id: address.address,
|
|
183
|
-
label: `${address.address} (${address.name}, ${t(addressKindKey(address.kind))})`,
|
|
184
|
-
}));
|
|
185
|
-
return (_jsx(Panel, { title: t('relay.selectAddress'), children: options.length === 0 ? (_jsx(Text, { color: theme.warn, children: t('relay.noAddresses') })) : (_jsx(ChoiceList, { options: options, current: draft.relay.lanHost || options[0].id, onPick: (address) => {
|
|
186
|
-
applyField('lanHost', address);
|
|
187
|
-
ctx.setEditing(null);
|
|
188
|
-
}, onCancel: () => ctx.setEditing(null) })) }));
|
|
189
|
-
}
|
|
190
|
-
return (_jsxs(Panel, { title: t('relay.title'), children: [draft.relay.mode !== 'remote' ? (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Row, { label: t('relay.listenAddress'), children: _jsx(Text, { color: theme.muted, children: `${bindAddress(draft)}:${draft.relay.port}` }) }), _jsx(Text, { color: theme.muted, children: t('relay.listenAddressHint') })] })) : null, entries.map((entry) => entry.kind === 'action' ? (_jsx(Selectable, { selected: selected === entry.id, onSelect: () => {
|
|
191
|
-
setSelected(entry.id);
|
|
192
|
-
activate(entry.id);
|
|
193
|
-
}, onHover: () => setSelected(entry.id), children: entry.label }, entry.id)) : (_jsx(FieldRow, { label: entry.label, selected: selected === entry.id, onSelect: () => activate(entry.id), onHover: () => setSelected(entry.id), children: entry.kind === 'password' ? (_jsx(TextField, { value: password, placeholder: t('relay.passwordEmpty'), active: editingId === 'password', mask: !revealTokens, onSubmit: (value) => {
|
|
194
|
-
setPassword(value);
|
|
195
|
-
setRelayPassword(value);
|
|
196
|
-
ctx.setEditing(null);
|
|
197
|
-
ctx.notify((t) => t('relay.passwordSaved'), 'success');
|
|
198
|
-
}, onCancel: () => ctx.setEditing(null) })) : entry.kind === 'readonly' ? (
|
|
199
|
-
// Fixed by us, so it is printed rather than offered for editing.
|
|
200
|
-
_jsx(Text, { color: theme.muted, children: `${getField(draft, entry.id) || placeholderText(ctx, entry.id)} (${t('relay.locked')})` })) : entry.fieldKind === 'choice' ? (
|
|
201
|
-
// Choice fields are picked from a list, never typed, so they
|
|
202
|
-
// show the translated label rather than the stored id — and the
|
|
203
|
-
// official relay is named as itself, not as "self-hosted".
|
|
204
|
-
_jsx(Text, { children: t(`mode.${selectedMode(draft)}`) })) : (_jsx(TextField, { value: getField(draft, entry.id), placeholder: placeholderText(ctx, entry.id), active: editingId === entry.id, onSubmit: (value) => {
|
|
205
|
-
if (applyField(entry.id, value))
|
|
206
|
-
ctx.setEditing(null);
|
|
207
|
-
}, onCancel: () => ctx.setEditing(null) })) }, entry.id))), ctx.dirty ? (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: theme.warn, children: t('hint.unsavedChanges') }) })) : null, draft.relay.mode === 'remote' ? (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: theme.muted, children: official ? t('relay.officialHint') : t('relay.passwordHint') }) })) : null, _jsx(Box, { marginTop: 1, children: _jsx(Row, { label: t('relay.identity'), children: _jsx(Text, { color: theme.muted, children: ctx.runtime.hostId }) }) }), showEnv ? (_jsxs(Box, { flexDirection: "column", marginTop: 1, borderStyle: "round", borderColor: theme.border, paddingX: 1, children: [_jsx(Text, { color: theme.muted, children: t('relay.envHint') }), _jsx(Text, { children: relayStartCommand(ctx.config, password) })] })) : null, official ? null : (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: theme.muted, children: t('relay.docsHint') }) })), _jsx(Message, { text: ctx.message?.text ?? null, level: ctx.message?.level ?? 'info' })] }));
|
|
208
|
-
}
|
|
209
|
-
function placeholderText(ctx, id) {
|
|
210
|
-
const raw = getFieldPlaceholder(ctx.draft, id);
|
|
211
|
-
// Placeholders that name a translation key are resolved here so the model
|
|
212
|
-
// stays free of language concerns.
|
|
213
|
-
return raw.startsWith('placeholder.') ? ctx.t(raw) : raw;
|
|
214
|
-
}
|
|
215
|
-
function addressKindKey(kind) {
|
|
216
|
-
return `relay.address${kind.charAt(0).toUpperCase()}${kind.slice(1)}`;
|
|
217
|
-
}
|
|
218
|
-
/**
|
|
219
|
-
* A modal list of options driven by both arrow keys and the mouse.
|
|
220
|
-
*
|
|
221
|
-
* It owns the cursor so the highlighted row and the keyboard agree; the parent
|
|
222
|
-
* only learns which option was chosen.
|
|
223
|
-
*/
|
|
224
|
-
export function ChoiceList({ options, current, onPick, onCancel, }) {
|
|
225
|
-
const [cursor, setCursor] = useState(() => {
|
|
226
|
-
const index = options.findIndex((option) => option.id === current);
|
|
227
|
-
return index >= 0 ? index : 0;
|
|
228
|
-
});
|
|
229
|
-
const active = options[Math.min(cursor, options.length - 1)];
|
|
230
|
-
useInput((_input, key) => {
|
|
231
|
-
if (key.escape) {
|
|
232
|
-
onCancel();
|
|
233
|
-
return;
|
|
234
|
-
}
|
|
235
|
-
if (key.upArrow)
|
|
236
|
-
setCursor((value) => (value - 1 + options.length) % options.length);
|
|
237
|
-
else if (key.downArrow)
|
|
238
|
-
setCursor((value) => (value + 1) % options.length);
|
|
239
|
-
else if (key.return && active)
|
|
240
|
-
onPick(active.id);
|
|
241
|
-
});
|
|
242
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Menu, { items: options.map((option) => ({ id: option.id, label: option.label })), selectedId: active?.id ?? '', onChange: (id) => setCursor(options.findIndex((option) => option.id === id)), onSelect: onPick }), active?.description ? (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: theme.muted, children: active.description }) })) : null] }));
|
|
243
|
-
}
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useState } from 'react';
|
|
3
|
-
import { Box, Text, useInput } from 'ink';
|
|
4
|
-
import { theme } from '../theme.js';
|
|
5
|
-
import { Menu, Message, Panel } from '../components/common.js';
|
|
6
|
-
import { readLogTail, restartAll, startAll, stopAll } from '../api.js';
|
|
7
|
-
const LOG_LINES = 8;
|
|
8
|
-
export function Services({ ctx }) {
|
|
9
|
-
const { t, run, status } = ctx;
|
|
10
|
-
const [selected, setSelected] = useState('start');
|
|
11
|
-
const items = [
|
|
12
|
-
{ id: 'start', label: t('services.start') },
|
|
13
|
-
{ id: 'stop', label: t('services.stop') },
|
|
14
|
-
{ id: 'restart', label: t('services.restart') },
|
|
15
|
-
];
|
|
16
|
-
const activate = (id) => {
|
|
17
|
-
// Starting or restarting reads the configuration from disk, so doing it on
|
|
18
|
-
// top of an unsaved draft silently applies the old settings — which reads
|
|
19
|
-
// as "I changed the relay and it never took effect". Stopping is unaffected.
|
|
20
|
-
if (ctx.dirty && id !== 'stop') {
|
|
21
|
-
ctx.notify((t) => t('services.unsavedBlocked'), 'error');
|
|
22
|
-
return;
|
|
23
|
-
}
|
|
24
|
-
return run(() => {
|
|
25
|
-
const config = ctx.config;
|
|
26
|
-
if (id === 'start') {
|
|
27
|
-
const result = startAll(config);
|
|
28
|
-
ctx.notify((t) => result.managed
|
|
29
|
-
? t('services.managedNotice', { manager: result.manager ?? '' })
|
|
30
|
-
: t('services.started'), 'success');
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
33
|
-
if (id === 'stop') {
|
|
34
|
-
const result = stopAll(config);
|
|
35
|
-
ctx.notify((t) => result.managed
|
|
36
|
-
? t('services.managedNotice', { manager: result.manager ?? '' })
|
|
37
|
-
: t('services.stopped'), 'success');
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
const result = restartAll(config);
|
|
41
|
-
ctx.notify((t) => result.managed
|
|
42
|
-
? t('services.managedNotice', { manager: result.manager ?? '' })
|
|
43
|
-
: t('services.restarted'), 'success');
|
|
44
|
-
});
|
|
45
|
-
};
|
|
46
|
-
useInput((_input, key) => {
|
|
47
|
-
const index = items.findIndex((item) => item.id === selected);
|
|
48
|
-
if (key.upArrow)
|
|
49
|
-
setSelected(items[(index - 1 + items.length) % items.length].id);
|
|
50
|
-
else if (key.downArrow)
|
|
51
|
-
setSelected(items[(index + 1) % items.length].id);
|
|
52
|
-
else if (key.return)
|
|
53
|
-
activate(selected);
|
|
54
|
-
}, { isActive: ctx.editingId === null });
|
|
55
|
-
const relayLog = readLogTail('relay', LOG_LINES);
|
|
56
|
-
const hostLog = readLogTail('host', LOG_LINES);
|
|
57
|
-
return (_jsxs(Panel, { title: t('services.title'), children: [_jsx(Menu, { items: items, selectedId: selected, onChange: setSelected, onSelect: activate }), ctx.dirty ? (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: theme.warn, children: t('services.unsavedBlocked') }) })) : null, status?.keepalive.installed ? (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: theme.muted, children: t('services.managedNotice', { manager: status.keepalive.manager }) }) })) : null, _jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { color: theme.muted, children: t('services.logs') }), _jsx(LogBlock, { title: t('services.logRelay'), lines: relayLog, emptyText: t('services.logEmpty') }), _jsx(LogBlock, { title: t('services.logHost'), lines: hostLog, emptyText: t('services.logEmpty') })] }), _jsx(Message, { text: ctx.message?.text ?? null, level: ctx.message?.level ?? 'info' })] }));
|
|
58
|
-
}
|
|
59
|
-
function LogBlock({ title, lines, emptyText, }) {
|
|
60
|
-
return (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { bold: true, color: theme.muted, children: title }), lines.length === 0 ? (_jsx(Text, { color: theme.muted, children: emptyText })) : (lines.map((line, index) => (
|
|
61
|
-
// biome-ignore lint/suspicious/noArrayIndexKey: log lines are positional and may repeat
|
|
62
|
-
_jsx(Text, { color: theme.muted, wrap: "truncate-end", children: line }, index))))] }));
|
|
63
|
-
}
|