@mateusbmp/backup-manager 0.1.1 → 0.2.2
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 +15 -2
- package/dist/cli.js +330 -214
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -899,8 +899,11 @@ function useBackHandler(handler) {
|
|
|
899
899
|
}
|
|
900
900
|
function useHints(hints, deps = []) {
|
|
901
901
|
const { setHints } = useApp();
|
|
902
|
+
const ref = useRef(hints);
|
|
903
|
+
ref.current = hints;
|
|
902
904
|
useEffect(() => {
|
|
903
|
-
|
|
905
|
+
if (!hints) return;
|
|
906
|
+
setHints(hints.map((h, i) => h.run ? { ...h, run: () => ref.current?.[i]?.run?.() } : h));
|
|
904
907
|
}, [setHints, ...deps]);
|
|
905
908
|
}
|
|
906
909
|
function Provider({ value, children }) {
|
|
@@ -1012,6 +1015,9 @@ function Muted({ children }) {
|
|
|
1012
1015
|
const { theme: theme2 } = useApp();
|
|
1013
1016
|
return /* @__PURE__ */ jsx3(Text2, { color: theme2.muted, children });
|
|
1014
1017
|
}
|
|
1018
|
+
function FormTip() {
|
|
1019
|
+
return /* @__PURE__ */ jsx3(Muted, { children: "Ctrl+\u2193 (or Alt+\u2193) reaches the buttons below; Ctrl-S also saves." });
|
|
1020
|
+
}
|
|
1015
1021
|
function ErrorLine({ children }) {
|
|
1016
1022
|
const { theme: theme2 } = useApp();
|
|
1017
1023
|
return /* @__PURE__ */ jsxs2(Text2, { color: theme2.error, children: [
|
|
@@ -1150,12 +1156,31 @@ function Footer({
|
|
|
1150
1156
|
theme: theme2,
|
|
1151
1157
|
columns,
|
|
1152
1158
|
hints,
|
|
1153
|
-
global
|
|
1159
|
+
global,
|
|
1160
|
+
focused = false,
|
|
1161
|
+
selected = 0
|
|
1154
1162
|
}) {
|
|
1155
|
-
|
|
1156
|
-
/* @__PURE__ */
|
|
1157
|
-
|
|
1158
|
-
|
|
1163
|
+
if (!focused)
|
|
1164
|
+
return /* @__PURE__ */ jsxs3(Box3, { width: columns, children: [
|
|
1165
|
+
/* @__PURE__ */ jsx4(KeyHints, { hints: [...hints, ...global] }),
|
|
1166
|
+
/* @__PURE__ */ jsx4(Text3, { color: theme2.muted, children: " " })
|
|
1167
|
+
] });
|
|
1168
|
+
const buttons = [...hints, ...global].filter((h) => h.run);
|
|
1169
|
+
return /* @__PURE__ */ jsx4(Box3, { width: columns, children: /* @__PURE__ */ jsxs3(Text3, { wrap: "truncate-end", children: [
|
|
1170
|
+
buttons.map((b, i) => /* @__PURE__ */ jsxs3(Text3, { children: [
|
|
1171
|
+
" ",
|
|
1172
|
+
/* @__PURE__ */ jsx4(
|
|
1173
|
+
Text3,
|
|
1174
|
+
{
|
|
1175
|
+
color: i === selected ? theme2.focus : theme2.accent,
|
|
1176
|
+
inverse: i === selected,
|
|
1177
|
+
bold: i === selected,
|
|
1178
|
+
children: `[ ${b.label} ]`
|
|
1179
|
+
}
|
|
1180
|
+
)
|
|
1181
|
+
] }, `${b.key}-${b.label}`)),
|
|
1182
|
+
/* @__PURE__ */ jsx4(Text3, { color: theme2.muted, children: " \u2190\u2192 pick \u23CE press esc back" })
|
|
1183
|
+
] }) });
|
|
1159
1184
|
}
|
|
1160
1185
|
var SIDEBAR_WIDTH, WIDE;
|
|
1161
1186
|
var init_Frame = __esm({
|
|
@@ -1273,10 +1298,12 @@ var init_Overlay = __esm({
|
|
|
1273
1298
|
["PgUp PgDn", "page"],
|
|
1274
1299
|
["\u23CE", "open / select"],
|
|
1275
1300
|
["Esc", "back"],
|
|
1301
|
+
["Ctrl/Alt + \u2190 \u2192 \u2193", "zones: sidebar \xB7 content \xB7 buttons (\u2191 back)"],
|
|
1302
|
+
["\u2190\u2192 \u23CE (buttons)", "pick and press a button: save, add, run\u2026"],
|
|
1276
1303
|
["Tab", "switch sidebar / content (next field in forms)"],
|
|
1277
1304
|
["1\u20139", "jump to a menu entry"],
|
|
1278
1305
|
["n \xB7 e \xB7 s", "new backup \xB7 environments \xB7 service"],
|
|
1279
|
-
["Ctrl-S", "save a form"],
|
|
1306
|
+
["Ctrl-S", "save a form (or its save button)"],
|
|
1280
1307
|
["?", "this help"],
|
|
1281
1308
|
["q", "quit (jobs keep running)"]
|
|
1282
1309
|
];
|
|
@@ -1358,7 +1385,9 @@ function useListNav(count, options) {
|
|
|
1358
1385
|
},
|
|
1359
1386
|
{ isActive: options.isActive }
|
|
1360
1387
|
);
|
|
1361
|
-
|
|
1388
|
+
const at = Math.min(index, Math.max(0, count - 1));
|
|
1389
|
+
const select = () => count > 0 && options.onSelect?.(at);
|
|
1390
|
+
return { index: at, setIndex, select };
|
|
1362
1391
|
}
|
|
1363
1392
|
function splitList(text) {
|
|
1364
1393
|
const out = [];
|
|
@@ -1375,12 +1404,16 @@ function splitList(text) {
|
|
|
1375
1404
|
out.push(current);
|
|
1376
1405
|
return out.map((s) => s.trim()).filter(Boolean);
|
|
1377
1406
|
}
|
|
1378
|
-
var isUp, isDown, joinList;
|
|
1407
|
+
var isZoneKey, plain, isUp, isDown, joinList;
|
|
1379
1408
|
var init_keys = __esm({
|
|
1380
1409
|
"src/ui/keys.ts"() {
|
|
1381
1410
|
"use strict";
|
|
1382
|
-
|
|
1383
|
-
|
|
1411
|
+
isZoneKey = (key) => Boolean(
|
|
1412
|
+
(key.ctrl || key.meta) && (key.upArrow || key.downArrow || key.leftArrow || key.rightArrow)
|
|
1413
|
+
);
|
|
1414
|
+
plain = (key) => !key.ctrl && !key.meta;
|
|
1415
|
+
isUp = (input, key) => plain(key) && (key.upArrow || input === "k");
|
|
1416
|
+
isDown = (input, key) => plain(key) && (key.downArrow || input === "j");
|
|
1384
1417
|
joinList = (items) => items.join(", ");
|
|
1385
1418
|
}
|
|
1386
1419
|
});
|
|
@@ -1492,19 +1525,22 @@ function Backups({ envName }) {
|
|
|
1492
1525
|
if (b && env) navigate({ name: "backup-detail", env: env.name, id: b.id });
|
|
1493
1526
|
}
|
|
1494
1527
|
});
|
|
1528
|
+
const shiftEnv = (delta) => setEnvIndex((i) => (i + delta + envs.length) % Math.max(1, envs.length));
|
|
1529
|
+
const nextFilter = () => setFilter((f) => (f + 1) % FILTERS.length);
|
|
1495
1530
|
useHints(
|
|
1496
1531
|
[
|
|
1497
|
-
{ key: "\u23CE", label: "details" },
|
|
1498
|
-
{ key: "\u2190\u2192", label: "environment" },
|
|
1499
|
-
{ key: "f", label: `filter: ${status}
|
|
1532
|
+
{ key: "\u23CE", label: "details", run: () => nav.select() },
|
|
1533
|
+
{ key: "\u2190\u2192", label: "environment", run: () => shiftEnv(1) },
|
|
1534
|
+
{ key: "f", label: `filter: ${status}`, run: nextFilter }
|
|
1500
1535
|
],
|
|
1501
1536
|
[status]
|
|
1502
1537
|
);
|
|
1503
1538
|
useInput2(
|
|
1504
1539
|
(input, key) => {
|
|
1505
|
-
if (key
|
|
1506
|
-
|
|
1507
|
-
else if (
|
|
1540
|
+
if (isZoneKey(key)) return;
|
|
1541
|
+
if (key.leftArrow) shiftEnv(-1);
|
|
1542
|
+
else if (key.rightArrow) shiftEnv(1);
|
|
1543
|
+
else if (input === "f") nextFilter();
|
|
1508
1544
|
},
|
|
1509
1545
|
{ isActive: active }
|
|
1510
1546
|
);
|
|
@@ -1562,10 +1598,7 @@ function BackupDetail2({ envName, id }) {
|
|
|
1562
1598
|
0,
|
|
1563
1599
|
[config, envName, id]
|
|
1564
1600
|
);
|
|
1565
|
-
useHints([
|
|
1566
|
-
{ key: "\u2191\u2193", label: "scroll" },
|
|
1567
|
-
{ key: "esc", label: "back" }
|
|
1568
|
-
]);
|
|
1601
|
+
useHints([{ key: "\u2191\u2193", label: "scroll" }]);
|
|
1569
1602
|
const b = backup.data;
|
|
1570
1603
|
const lines = [];
|
|
1571
1604
|
if (b) {
|
|
@@ -1855,17 +1888,17 @@ function Dashboard() {
|
|
|
1855
1888
|
if (env) navigate({ name: "backups", env: env.name });
|
|
1856
1889
|
}
|
|
1857
1890
|
});
|
|
1891
|
+
const runNow = useRunNow(active, () => envs[nav.index]?.name);
|
|
1858
1892
|
useHints(
|
|
1859
1893
|
[
|
|
1860
1894
|
{ key: "\u2191\u2193", label: "move" },
|
|
1861
|
-
{ key: "\u23CE", label: "backups" },
|
|
1862
|
-
{ key: "r", label: "run now" },
|
|
1863
|
-
{ key: "n", label: "new backup" },
|
|
1864
|
-
{ key: "e", label: "environments" }
|
|
1895
|
+
{ key: "\u23CE", label: "backups", run: () => nav.select() },
|
|
1896
|
+
{ key: "r", label: "run now", run: runNow },
|
|
1897
|
+
{ key: "n", label: "new backup", run: () => navigate({ name: "new-backup" }) },
|
|
1898
|
+
{ key: "e", label: "environments", run: () => navigate({ name: "environments" }) }
|
|
1865
1899
|
],
|
|
1866
1900
|
[]
|
|
1867
1901
|
);
|
|
1868
|
-
useRunNow(active, () => envs[nav.index]?.name);
|
|
1869
1902
|
if (!config) return null;
|
|
1870
1903
|
if (config.environments.length === 0) {
|
|
1871
1904
|
return /* @__PURE__ */ jsxs7(Panel, { title: "Welcome", width, height, children: [
|
|
@@ -1920,29 +1953,27 @@ function Dashboard() {
|
|
|
1920
1953
|
}
|
|
1921
1954
|
function useRunNow(active, envName) {
|
|
1922
1955
|
const { services, config, navigate, toast, confirm } = useApp();
|
|
1923
|
-
|
|
1924
|
-
(
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
if (!
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
{ isActive: active }
|
|
1945
|
-
);
|
|
1956
|
+
const runNow = () => {
|
|
1957
|
+
const name2 = envName();
|
|
1958
|
+
if (!config || !name2) return;
|
|
1959
|
+
void (async () => {
|
|
1960
|
+
if (!await confirm({
|
|
1961
|
+
title: "Run backup now",
|
|
1962
|
+
message: `Back up every database and project of '${name2}' now?`,
|
|
1963
|
+
confirmLabel: "Run"
|
|
1964
|
+
}))
|
|
1965
|
+
return;
|
|
1966
|
+
try {
|
|
1967
|
+
const job = await services.startBackup(config, [{ environment: name2 }]);
|
|
1968
|
+
toast(`backup of ${name2} started`, "success");
|
|
1969
|
+
navigate({ name: "job-live", id: job.id });
|
|
1970
|
+
} catch (error) {
|
|
1971
|
+
toast(error.message, "error");
|
|
1972
|
+
}
|
|
1973
|
+
})();
|
|
1974
|
+
};
|
|
1975
|
+
useInput3((input) => input === "r" && runNow(), { isActive: active });
|
|
1976
|
+
return runNow;
|
|
1946
1977
|
}
|
|
1947
1978
|
var init_Dashboard = __esm({
|
|
1948
1979
|
"src/ui/screens/Dashboard.tsx"() {
|
|
@@ -1967,7 +1998,7 @@ function Doctor() {
|
|
|
1967
1998
|
const report = usePolling(() => services.doctor({ db: true }), 0);
|
|
1968
1999
|
const [offset, setOffset] = useState6(0);
|
|
1969
2000
|
useHints([
|
|
1970
|
-
{ key: "r", label: "re-run" },
|
|
2001
|
+
{ key: "r", label: "re-run", run: () => report.refresh() },
|
|
1971
2002
|
{ key: "\u2191\u2193", label: "scroll" }
|
|
1972
2003
|
]);
|
|
1973
2004
|
const color = { ok: theme2.success, warn: theme2.warning, fail: theme2.error };
|
|
@@ -2046,6 +2077,7 @@ function TextField({ value, onChange, focus, mask, placeholder, width }) {
|
|
|
2046
2077
|
(input, key) => {
|
|
2047
2078
|
const { value: v, cursor: cursor2 } = live.current;
|
|
2048
2079
|
const at2 = Math.min(cursor2, v.length);
|
|
2080
|
+
if (isZoneKey(key)) return;
|
|
2049
2081
|
if (key.leftArrow) commit(v, Math.max(0, at2 - 1));
|
|
2050
2082
|
else if (key.rightArrow) commit(v, Math.min(v.length, at2 + 1));
|
|
2051
2083
|
else if (key.home || key.ctrl && input === "a") commit(v, 0);
|
|
@@ -2088,6 +2120,7 @@ var init_TextField = __esm({
|
|
|
2088
2120
|
"src/ui/components/TextField.tsx"() {
|
|
2089
2121
|
"use strict";
|
|
2090
2122
|
init_context();
|
|
2123
|
+
init_keys();
|
|
2091
2124
|
}
|
|
2092
2125
|
});
|
|
2093
2126
|
|
|
@@ -2123,6 +2156,7 @@ function Form(props) {
|
|
|
2123
2156
|
};
|
|
2124
2157
|
useInput6(
|
|
2125
2158
|
(input, key) => {
|
|
2159
|
+
if (isZoneKey(key)) return;
|
|
2126
2160
|
if (key.ctrl && input === "s") return props.onSubmit(latest.current);
|
|
2127
2161
|
if (key.escape) return props.onCancel();
|
|
2128
2162
|
if (key.tab) return move(key.shift ? -1 : 1);
|
|
@@ -2234,6 +2268,7 @@ var init_Form = __esm({
|
|
|
2234
2268
|
"src/ui/components/Form.tsx"() {
|
|
2235
2269
|
"use strict";
|
|
2236
2270
|
init_context();
|
|
2271
|
+
init_keys();
|
|
2237
2272
|
init_basics();
|
|
2238
2273
|
init_TextField();
|
|
2239
2274
|
focusable = (f) => f.kind !== "heading";
|
|
@@ -2323,12 +2358,6 @@ function DatabaseEdit({
|
|
|
2323
2358
|
const [errors, setErrors] = useState9({});
|
|
2324
2359
|
const [general, setGeneral] = useState9([]);
|
|
2325
2360
|
const [test, setTest] = useState9(null);
|
|
2326
|
-
useHints([
|
|
2327
|
-
{ key: "tab", label: "next field" },
|
|
2328
|
-
{ key: "\u2190\u2192", label: "change type" },
|
|
2329
|
-
{ key: "ctrl-s", label: "apply" },
|
|
2330
|
-
{ key: "esc", label: "cancel" }
|
|
2331
|
-
]);
|
|
2332
2361
|
const validate = (values2) => {
|
|
2333
2362
|
const parsed = DatabaseSchema.safeParse(buildDatabase(values2));
|
|
2334
2363
|
if (!parsed.success) {
|
|
@@ -2368,6 +2397,12 @@ function DatabaseEdit({
|
|
|
2368
2397
|
setTest({ state: "running" });
|
|
2369
2398
|
void services.testConnection(env, db).then((r) => setTest({ state: "done", ...r }));
|
|
2370
2399
|
};
|
|
2400
|
+
useHints([
|
|
2401
|
+
{ key: "tab", label: "field" },
|
|
2402
|
+
{ key: "\u2190\u2192", label: "change" },
|
|
2403
|
+
{ key: "ctrl-s", label: "apply", run: () => submit(values) },
|
|
2404
|
+
{ key: "esc", label: "cancel", run: onCancel }
|
|
2405
|
+
]);
|
|
2371
2406
|
const laravel = values.type === "laravel";
|
|
2372
2407
|
const fields = [
|
|
2373
2408
|
{ kind: "text", key: "name", label: "Name", placeholder: "shop", error: errors.name },
|
|
@@ -2568,11 +2603,6 @@ function ProjectEdit({
|
|
|
2568
2603
|
const [errors, setErrors] = useState10({});
|
|
2569
2604
|
const [general, setGeneral] = useState10([]);
|
|
2570
2605
|
const hint = useArtisanHint(String(values.path));
|
|
2571
|
-
useHints([
|
|
2572
|
-
{ key: "tab", label: "next field" },
|
|
2573
|
-
{ key: "ctrl-s", label: "apply" },
|
|
2574
|
-
{ key: "esc", label: "cancel" }
|
|
2575
|
-
]);
|
|
2576
2606
|
const submit = (values2) => {
|
|
2577
2607
|
const candidate = {
|
|
2578
2608
|
name: String(values2.name).trim(),
|
|
@@ -2601,6 +2631,11 @@ function ProjectEdit({
|
|
|
2601
2631
|
);
|
|
2602
2632
|
onApply({ ...env, projects, databases });
|
|
2603
2633
|
};
|
|
2634
|
+
useHints([
|
|
2635
|
+
{ key: "tab", label: "field" },
|
|
2636
|
+
{ key: "ctrl-s", label: "apply", run: () => submit(values) },
|
|
2637
|
+
{ key: "esc", label: "cancel", run: onCancel }
|
|
2638
|
+
]);
|
|
2604
2639
|
const fields = [
|
|
2605
2640
|
{
|
|
2606
2641
|
kind: "text",
|
|
@@ -2709,7 +2744,7 @@ function EnvironmentEdit({
|
|
|
2709
2744
|
envName,
|
|
2710
2745
|
onboarding
|
|
2711
2746
|
}) {
|
|
2712
|
-
const { config, width, height, save, back, navigate, confirm, services, toast
|
|
2747
|
+
const { config, width, height, save, back, navigate, confirm, services, toast } = useApp();
|
|
2713
2748
|
const active = useScreenActive();
|
|
2714
2749
|
const original = config && envName ? findEnvironment(config, envName) : void 0;
|
|
2715
2750
|
const [draft, setDraft] = useState11(
|
|
@@ -2725,14 +2760,15 @@ function EnvironmentEdit({
|
|
|
2725
2760
|
const [mode, setMode] = useState11({ kind: "env" });
|
|
2726
2761
|
const [errors, setErrors] = useState11({});
|
|
2727
2762
|
const [general, setGeneral] = useState11([]);
|
|
2763
|
+
const cancel = () => onboarding ? void 0 : back();
|
|
2728
2764
|
useHints(
|
|
2729
2765
|
mode.kind === "env" ? [
|
|
2730
2766
|
{ key: "tab/\u2191\u2193", label: "field" },
|
|
2731
2767
|
{ key: "\u23CE", label: "open" },
|
|
2732
2768
|
{ key: "d", label: "remove item" },
|
|
2733
|
-
{ key: "ctrl-s", label: "save" },
|
|
2734
|
-
{ key: "esc", label: "cancel" }
|
|
2735
|
-
] :
|
|
2769
|
+
{ key: "ctrl-s", label: "save", run: () => void submit(values) },
|
|
2770
|
+
...onboarding ? [] : [{ key: "esc", label: "cancel", run: cancel }]
|
|
2771
|
+
] : null,
|
|
2736
2772
|
[mode.kind]
|
|
2737
2773
|
);
|
|
2738
2774
|
if (!config) return null;
|
|
@@ -2962,7 +2998,7 @@ function EnvironmentEdit({
|
|
|
2962
2998
|
values,
|
|
2963
2999
|
onChange: setValues,
|
|
2964
3000
|
onSubmit: (v) => void submit(v),
|
|
2965
|
-
onCancel:
|
|
3001
|
+
onCancel: cancel,
|
|
2966
3002
|
isActive: active,
|
|
2967
3003
|
width: width - 4,
|
|
2968
3004
|
height: height - 4 - general.length
|
|
@@ -2970,7 +3006,7 @@ function EnvironmentEdit({
|
|
|
2970
3006
|
),
|
|
2971
3007
|
/* @__PURE__ */ jsxs13(Box12, { flexDirection: "column", children: [
|
|
2972
3008
|
general.map((g) => /* @__PURE__ */ jsx14(ErrorLine, { children: g }, g)),
|
|
2973
|
-
onboarding && /* @__PURE__ */ jsx14(
|
|
3009
|
+
onboarding && /* @__PURE__ */ jsx14(FormTip, {})
|
|
2974
3010
|
] })
|
|
2975
3011
|
]
|
|
2976
3012
|
}
|
|
@@ -3004,34 +3040,41 @@ function Environments() {
|
|
|
3004
3040
|
state: useSessionState("environments:index", 0),
|
|
3005
3041
|
onSelect: (i) => navigate({ name: "environment-edit", env: envs[i]?.name })
|
|
3006
3042
|
});
|
|
3043
|
+
const runNow = useRunNow(active, () => envs[nav.index]?.name);
|
|
3044
|
+
const add = () => navigate({ name: "environment-edit" });
|
|
3045
|
+
const backups = () => {
|
|
3046
|
+
const env = envs[nav.index];
|
|
3047
|
+
if (env) navigate({ name: "backups", env: env.name });
|
|
3048
|
+
};
|
|
3049
|
+
const remove = () => {
|
|
3050
|
+
const env = envs[nav.index];
|
|
3051
|
+
if (!env || !config) return;
|
|
3052
|
+
void (async () => {
|
|
3053
|
+
const ok = await confirm({
|
|
3054
|
+
title: "Delete environment",
|
|
3055
|
+
message: `Remove '${env.name}' from the config? Its backups on disk are kept.`,
|
|
3056
|
+
confirmLabel: "Delete",
|
|
3057
|
+
danger: true
|
|
3058
|
+
});
|
|
3059
|
+
if (ok)
|
|
3060
|
+
await save(
|
|
3061
|
+
{ ...config, environments: envs.filter((e) => e.name !== env.name) },
|
|
3062
|
+
`deleted ${env.name}`
|
|
3063
|
+
);
|
|
3064
|
+
})();
|
|
3065
|
+
};
|
|
3007
3066
|
useHints([
|
|
3008
|
-
{ key: "\u23CE", label: "edit" },
|
|
3009
|
-
{ key: "a", label: "add" },
|
|
3010
|
-
{ key: "d", label: "delete" },
|
|
3011
|
-
{ key: "b", label: "backups" },
|
|
3012
|
-
{ key: "r", label: "run now" }
|
|
3067
|
+
{ key: "\u23CE", label: "edit", run: () => nav.select() },
|
|
3068
|
+
{ key: "a", label: "add", run: add },
|
|
3069
|
+
{ key: "d", label: "delete", run: remove },
|
|
3070
|
+
{ key: "b", label: "backups", run: backups },
|
|
3071
|
+
{ key: "r", label: "run now", run: runNow }
|
|
3013
3072
|
]);
|
|
3014
|
-
useRunNow(active, () => envs[nav.index]?.name);
|
|
3015
3073
|
useInput7(
|
|
3016
3074
|
(input) => {
|
|
3017
|
-
|
|
3018
|
-
if (input === "
|
|
3019
|
-
else if (input === "
|
|
3020
|
-
else if (input === "d" && env && config) {
|
|
3021
|
-
void (async () => {
|
|
3022
|
-
const ok = await confirm({
|
|
3023
|
-
title: "Delete environment",
|
|
3024
|
-
message: `Remove '${env.name}' from the config? Its backups on disk are kept.`,
|
|
3025
|
-
confirmLabel: "Delete",
|
|
3026
|
-
danger: true
|
|
3027
|
-
});
|
|
3028
|
-
if (ok)
|
|
3029
|
-
await save(
|
|
3030
|
-
{ ...config, environments: envs.filter((e) => e.name !== env.name) },
|
|
3031
|
-
`deleted ${env.name}`
|
|
3032
|
-
);
|
|
3033
|
-
})();
|
|
3034
|
-
}
|
|
3075
|
+
if (input === "a") add();
|
|
3076
|
+
else if (input === "b") backups();
|
|
3077
|
+
else if (input === "d") remove();
|
|
3035
3078
|
},
|
|
3036
3079
|
{ isActive: active }
|
|
3037
3080
|
);
|
|
@@ -3116,8 +3159,8 @@ function Jobs() {
|
|
|
3116
3159
|
}
|
|
3117
3160
|
});
|
|
3118
3161
|
useHints([
|
|
3119
|
-
{ key: "\u23CE", label: "follow" },
|
|
3120
|
-
{ key: "x", label: "stop" }
|
|
3162
|
+
{ key: "\u23CE", label: "follow", run: () => nav.select() },
|
|
3163
|
+
{ key: "x", label: "stop", run: () => kill(list[nav.index]) }
|
|
3121
3164
|
]);
|
|
3122
3165
|
useInput8((input) => input === "x" && kill(list[nav.index]), { isActive: active });
|
|
3123
3166
|
return /* @__PURE__ */ jsx16(
|
|
@@ -3213,8 +3256,8 @@ function JobLive({ id }) {
|
|
|
3213
3256
|
const max = Math.max(0, logLines.length - logHeight);
|
|
3214
3257
|
useHints([
|
|
3215
3258
|
{ key: "\u2191\u2193", label: "scroll log" },
|
|
3216
|
-
{ key: "G", label: "follow" },
|
|
3217
|
-
{ key: "x", label: "stop" }
|
|
3259
|
+
{ key: "G", label: "follow", run: () => setOffset(null) },
|
|
3260
|
+
{ key: "x", label: "stop", run: () => kill(j ?? void 0) }
|
|
3218
3261
|
]);
|
|
3219
3262
|
useInput8(
|
|
3220
3263
|
(input, key) => {
|
|
@@ -3302,7 +3345,7 @@ function buildTargets(envs, picked, items) {
|
|
|
3302
3345
|
return targets;
|
|
3303
3346
|
}
|
|
3304
3347
|
function NewBackup({ envName }) {
|
|
3305
|
-
const { config, services, width, height, navigate, toast, theme: theme2 } = useApp();
|
|
3348
|
+
const { config, services, width, height, navigate, back, toast, theme: theme2 } = useApp();
|
|
3306
3349
|
const active = useScreenActive();
|
|
3307
3350
|
const envs = (config?.environments ?? []).filter(
|
|
3308
3351
|
(e) => e.databases.length + e.projects.length > 0
|
|
@@ -3323,18 +3366,6 @@ function NewBackup({ envName }) {
|
|
|
3323
3366
|
const [starting, setStarting] = useState13(false);
|
|
3324
3367
|
const envNav = useListNav(envs.length, { isActive: active && step === "envs" });
|
|
3325
3368
|
const itemNav = useListNav(items.length, { isActive: active && step === "items" });
|
|
3326
|
-
useHints(
|
|
3327
|
-
step === "confirm" ? [
|
|
3328
|
-
{ key: "\u23CE", label: "start" },
|
|
3329
|
-
{ key: "esc", label: "back" }
|
|
3330
|
-
] : [
|
|
3331
|
-
{ key: "space", label: "toggle" },
|
|
3332
|
-
{ key: "a", label: "all/none" },
|
|
3333
|
-
{ key: "\u23CE", label: "next" },
|
|
3334
|
-
{ key: "esc", label: "back" }
|
|
3335
|
-
],
|
|
3336
|
-
[step]
|
|
3337
|
-
);
|
|
3338
3369
|
const start = async () => {
|
|
3339
3370
|
if (!config || starting) return;
|
|
3340
3371
|
setStarting(true);
|
|
@@ -3347,39 +3378,61 @@ function NewBackup({ envName }) {
|
|
|
3347
3378
|
setStarting(false);
|
|
3348
3379
|
}
|
|
3349
3380
|
};
|
|
3350
|
-
|
|
3381
|
+
const stepBack = () => {
|
|
3351
3382
|
if (step === "envs") return false;
|
|
3352
3383
|
setStep(step === "confirm" ? "items" : "envs");
|
|
3353
3384
|
return true;
|
|
3354
|
-
}
|
|
3385
|
+
};
|
|
3386
|
+
useBackHandler(stepBack);
|
|
3387
|
+
const toggle = () => {
|
|
3388
|
+
if (step === "envs") {
|
|
3389
|
+
const env = envs[envNav.index];
|
|
3390
|
+
if (!env) return;
|
|
3391
|
+
const next2 = new Set(selectedEnvs);
|
|
3392
|
+
next2.has(env.name) ? next2.delete(env.name) : next2.add(env.name);
|
|
3393
|
+
setSelectedEnvs(next2);
|
|
3394
|
+
} else if (step === "items") {
|
|
3395
|
+
const item = items[itemNav.index];
|
|
3396
|
+
if (!item) return;
|
|
3397
|
+
const next2 = new Set(excluded);
|
|
3398
|
+
next2.has(keyOf(item)) ? next2.delete(keyOf(item)) : next2.add(keyOf(item));
|
|
3399
|
+
setExcluded(next2);
|
|
3400
|
+
}
|
|
3401
|
+
};
|
|
3402
|
+
const all = () => {
|
|
3403
|
+
if (step === "envs")
|
|
3404
|
+
setSelectedEnvs(
|
|
3405
|
+
selectedEnvs.size === envs.length ? /* @__PURE__ */ new Set() : new Set(envs.map((e) => e.name))
|
|
3406
|
+
);
|
|
3407
|
+
else if (step === "items")
|
|
3408
|
+
setExcluded(picked.size === items.length ? new Set(items.map(keyOf)) : /* @__PURE__ */ new Set());
|
|
3409
|
+
};
|
|
3410
|
+
const next = () => {
|
|
3411
|
+
if (step === "envs") {
|
|
3412
|
+
const env = envs[envNav.index];
|
|
3413
|
+
if (selectedEnvs.size === 0 && env) setSelectedEnvs(/* @__PURE__ */ new Set([env.name]));
|
|
3414
|
+
setStep("items");
|
|
3415
|
+
} else if (step === "items") {
|
|
3416
|
+
if (picked.size > 0) setStep("confirm");
|
|
3417
|
+
} else void start();
|
|
3418
|
+
};
|
|
3419
|
+
useHints(
|
|
3420
|
+
step === "confirm" ? [
|
|
3421
|
+
{ key: "\u23CE", label: "start", run: next },
|
|
3422
|
+
{ key: "esc", label: "back", run: stepBack }
|
|
3423
|
+
] : [
|
|
3424
|
+
{ key: "space", label: "toggle", run: toggle },
|
|
3425
|
+
{ key: "a", label: "all/none", run: all },
|
|
3426
|
+
{ key: "\u23CE", label: "next", run: next },
|
|
3427
|
+
{ key: "esc", label: "back", run: () => stepBack() || back() }
|
|
3428
|
+
],
|
|
3429
|
+
[step]
|
|
3430
|
+
);
|
|
3355
3431
|
useInput9(
|
|
3356
3432
|
(input, key) => {
|
|
3357
|
-
if (
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
const next = new Set(selectedEnvs);
|
|
3361
|
-
next.has(env.name) ? next.delete(env.name) : next.add(env.name);
|
|
3362
|
-
setSelectedEnvs(next);
|
|
3363
|
-
} else if (input === "a") {
|
|
3364
|
-
setSelectedEnvs(
|
|
3365
|
-
selectedEnvs.size === envs.length ? /* @__PURE__ */ new Set() : new Set(envs.map((e) => e.name))
|
|
3366
|
-
);
|
|
3367
|
-
} else if (key.return) {
|
|
3368
|
-
if (selectedEnvs.size === 0 && env) setSelectedEnvs(/* @__PURE__ */ new Set([env.name]));
|
|
3369
|
-
setStep("items");
|
|
3370
|
-
}
|
|
3371
|
-
} else if (step === "items") {
|
|
3372
|
-
const item = items[itemNav.index];
|
|
3373
|
-
if (input === " " && item) {
|
|
3374
|
-
const next = new Set(excluded);
|
|
3375
|
-
next.has(keyOf(item)) ? next.delete(keyOf(item)) : next.add(keyOf(item));
|
|
3376
|
-
setExcluded(next);
|
|
3377
|
-
} else if (input === "a") {
|
|
3378
|
-
setExcluded(picked.size === items.length ? new Set(items.map(keyOf)) : /* @__PURE__ */ new Set());
|
|
3379
|
-
} else if (key.return && picked.size > 0) setStep("confirm");
|
|
3380
|
-
} else if (step === "confirm") {
|
|
3381
|
-
if (key.return) void start();
|
|
3382
|
-
}
|
|
3433
|
+
if (input === " ") toggle();
|
|
3434
|
+
else if (input === "a") all();
|
|
3435
|
+
else if (key.return) next();
|
|
3383
3436
|
},
|
|
3384
3437
|
{ isActive: active }
|
|
3385
3438
|
);
|
|
@@ -3460,10 +3513,6 @@ function Onboarding() {
|
|
|
3460
3513
|
const [values, setValues] = useState14({ backupDir: "", keepLast: "7", keepDays: "30" });
|
|
3461
3514
|
const [errors, setErrors] = useState14({});
|
|
3462
3515
|
const [general, setGeneral] = useState14([]);
|
|
3463
|
-
useHints([
|
|
3464
|
-
{ key: "tab", label: "field" },
|
|
3465
|
-
{ key: "ctrl-s", label: "continue" }
|
|
3466
|
-
]);
|
|
3467
3516
|
const submit = async (values2) => {
|
|
3468
3517
|
const base = emptyConfig();
|
|
3469
3518
|
const parsed = ConfigSchema.safeParse({
|
|
@@ -3506,15 +3555,12 @@ function Onboarding() {
|
|
|
3506
3555
|
label: "Keep days",
|
|
3507
3556
|
error: errors.keepDays,
|
|
3508
3557
|
hint: "also keep anything younger than this many days"
|
|
3509
|
-
},
|
|
3510
|
-
{
|
|
3511
|
-
kind: "action",
|
|
3512
|
-
key: "continue",
|
|
3513
|
-
label: "Continue \u25B8",
|
|
3514
|
-
detail: "save and add the first environment",
|
|
3515
|
-
onPress: () => void submit(values)
|
|
3516
3558
|
}
|
|
3517
3559
|
];
|
|
3560
|
+
useHints([
|
|
3561
|
+
{ key: "tab", label: "field" },
|
|
3562
|
+
{ key: "ctrl-s", label: "continue", run: () => void submit(values) }
|
|
3563
|
+
]);
|
|
3518
3564
|
return /* @__PURE__ */ jsxs17(Panel, { title: "Welcome", width, height, focused: active, right: "step 1 of 2", children: [
|
|
3519
3565
|
/* @__PURE__ */ jsxs17(Box15, { flexDirection: "column", marginBottom: 1, children: [
|
|
3520
3566
|
/* @__PURE__ */ jsx18(GradientText, { children: "Let's set up backup-manager" }),
|
|
@@ -3540,7 +3586,8 @@ function Onboarding() {
|
|
|
3540
3586
|
width: width - 4
|
|
3541
3587
|
}
|
|
3542
3588
|
),
|
|
3543
|
-
general.map((g) => /* @__PURE__ */ jsx18(ErrorLine, { children: g }, g))
|
|
3589
|
+
general.map((g) => /* @__PURE__ */ jsx18(ErrorLine, { children: g }, g)),
|
|
3590
|
+
/* @__PURE__ */ jsx18(Box15, { marginTop: 1, children: /* @__PURE__ */ jsx18(FormTip, {}) })
|
|
3544
3591
|
] });
|
|
3545
3592
|
}
|
|
3546
3593
|
var init_Onboarding = __esm({
|
|
@@ -3570,11 +3617,6 @@ function Prune() {
|
|
|
3570
3617
|
);
|
|
3571
3618
|
const [offset, setOffset] = useState15(0);
|
|
3572
3619
|
const [busy, setBusy] = useState15(false);
|
|
3573
|
-
useHints([
|
|
3574
|
-
{ key: "p", label: "apply" },
|
|
3575
|
-
{ key: "r", label: "refresh" },
|
|
3576
|
-
{ key: "\u2191\u2193", label: "scroll" }
|
|
3577
|
-
]);
|
|
3578
3620
|
const list = plans.data ?? [];
|
|
3579
3621
|
const toDelete = list.reduce((s, p) => s + p.delete.length + p.staleTemp.length, 0);
|
|
3580
3622
|
const freed = list.reduce((s, p) => s + p.freedBytes, 0);
|
|
@@ -3600,31 +3642,38 @@ function Prune() {
|
|
|
3600
3642
|
lines.push(/* @__PURE__ */ jsx19(Text18, { color: theme2.muted, children: " nothing to delete" }));
|
|
3601
3643
|
}
|
|
3602
3644
|
const viewHeight = height - 5;
|
|
3645
|
+
const apply = () => {
|
|
3646
|
+
if (toDelete === 0 || busy) return;
|
|
3647
|
+
void (async () => {
|
|
3648
|
+
const ok = await confirm({
|
|
3649
|
+
title: "Apply retention",
|
|
3650
|
+
message: `Delete ${toDelete} item(s) and free ${formatBytes(freed)}? This cannot be undone.`,
|
|
3651
|
+
confirmLabel: "Delete",
|
|
3652
|
+
danger: true
|
|
3653
|
+
});
|
|
3654
|
+
if (!ok) return;
|
|
3655
|
+
setBusy(true);
|
|
3656
|
+
const r = await services.applyPrune(list);
|
|
3657
|
+
setBusy(false);
|
|
3658
|
+
toast(
|
|
3659
|
+
r.errors.length ? `deleted ${r.deleted}; ${r.errors[0]}` : `deleted ${r.deleted} item(s)`,
|
|
3660
|
+
r.errors.length ? "warning" : "success"
|
|
3661
|
+
);
|
|
3662
|
+
plans.refresh();
|
|
3663
|
+
})();
|
|
3664
|
+
};
|
|
3665
|
+
useHints([
|
|
3666
|
+
{ key: "p", label: "apply", run: apply },
|
|
3667
|
+
{ key: "r", label: "refresh", run: () => plans.refresh() },
|
|
3668
|
+
{ key: "\u2191\u2193", label: "scroll" }
|
|
3669
|
+
]);
|
|
3603
3670
|
useInput10(
|
|
3604
3671
|
(input, key) => {
|
|
3605
3672
|
if (isUp(input, key)) setOffset((o) => Math.max(0, o - 1));
|
|
3606
3673
|
else if (isDown(input, key))
|
|
3607
3674
|
setOffset((o) => Math.min(Math.max(0, lines.length - viewHeight), o + 1));
|
|
3608
3675
|
else if (input === "r") plans.refresh();
|
|
3609
|
-
else if (input === "p"
|
|
3610
|
-
void (async () => {
|
|
3611
|
-
const ok = await confirm({
|
|
3612
|
-
title: "Apply retention",
|
|
3613
|
-
message: `Delete ${toDelete} item(s) and free ${formatBytes(freed)}? This cannot be undone.`,
|
|
3614
|
-
confirmLabel: "Delete",
|
|
3615
|
-
danger: true
|
|
3616
|
-
});
|
|
3617
|
-
if (!ok) return;
|
|
3618
|
-
setBusy(true);
|
|
3619
|
-
const r = await services.applyPrune(list);
|
|
3620
|
-
setBusy(false);
|
|
3621
|
-
toast(
|
|
3622
|
-
r.errors.length ? `deleted ${r.deleted}; ${r.errors[0]}` : `deleted ${r.deleted} item(s)`,
|
|
3623
|
-
r.errors.length ? "warning" : "success"
|
|
3624
|
-
);
|
|
3625
|
-
plans.refresh();
|
|
3626
|
-
})();
|
|
3627
|
-
}
|
|
3676
|
+
else if (input === "p") apply();
|
|
3628
3677
|
},
|
|
3629
3678
|
{ isActive: active }
|
|
3630
3679
|
);
|
|
@@ -3732,20 +3781,16 @@ function Service() {
|
|
|
3732
3781
|
isActive: active,
|
|
3733
3782
|
onSelect: (i) => void run(ACTIONS[i].action)
|
|
3734
3783
|
});
|
|
3784
|
+
const refresh = () => {
|
|
3785
|
+
status.refresh();
|
|
3786
|
+
logs.refresh();
|
|
3787
|
+
};
|
|
3735
3788
|
useHints([
|
|
3736
3789
|
{ key: "\u2191\u2193", label: "action" },
|
|
3737
|
-
{ key: "\u23CE", label: "run" },
|
|
3738
|
-
{ key: "r", label: "refresh" }
|
|
3790
|
+
{ key: "\u23CE", label: "run", run: () => nav.select() },
|
|
3791
|
+
{ key: "r", label: "refresh", run: refresh }
|
|
3739
3792
|
]);
|
|
3740
|
-
useInput11(
|
|
3741
|
-
(input) => {
|
|
3742
|
-
if (input === "r") {
|
|
3743
|
-
status.refresh();
|
|
3744
|
-
logs.refresh();
|
|
3745
|
-
}
|
|
3746
|
-
},
|
|
3747
|
-
{ isActive: active }
|
|
3748
|
-
);
|
|
3793
|
+
useInput11((input) => input === "r" && refresh(), { isActive: active });
|
|
3749
3794
|
const half = Math.floor(width / 2);
|
|
3750
3795
|
const tz = config ? timezone(config) : void 0;
|
|
3751
3796
|
const api = config?.api;
|
|
@@ -3874,8 +3919,8 @@ function Settings() {
|
|
|
3874
3919
|
useHints([
|
|
3875
3920
|
{ key: "tab", label: "field" },
|
|
3876
3921
|
{ key: "space", label: "toggle" },
|
|
3877
|
-
{ key: "ctrl-s", label: "save" },
|
|
3878
|
-
{ key: "esc", label: "
|
|
3922
|
+
{ key: "ctrl-s", label: "save", run: () => void submit(values) },
|
|
3923
|
+
{ key: "esc", label: "cancel", run: () => void leave() }
|
|
3879
3924
|
]);
|
|
3880
3925
|
if (!config) return null;
|
|
3881
3926
|
const submit = async (current) => {
|
|
@@ -3893,6 +3938,16 @@ function Settings() {
|
|
|
3893
3938
|
setGeneral([]);
|
|
3894
3939
|
await save(parsed.data, "settings saved");
|
|
3895
3940
|
};
|
|
3941
|
+
const leave = async () => {
|
|
3942
|
+
const dirty = JSON.stringify(values) !== JSON.stringify(settingsValues(config));
|
|
3943
|
+
if (!dirty || await confirm({
|
|
3944
|
+
title: "Discard changes",
|
|
3945
|
+
message: "Leave Settings without saving?",
|
|
3946
|
+
confirmLabel: "Discard",
|
|
3947
|
+
danger: true
|
|
3948
|
+
}))
|
|
3949
|
+
back();
|
|
3950
|
+
};
|
|
3896
3951
|
const fields = [
|
|
3897
3952
|
{ kind: "heading", key: "h1", label: "Storage" },
|
|
3898
3953
|
{
|
|
@@ -3986,16 +4041,7 @@ function Settings() {
|
|
|
3986
4041
|
values,
|
|
3987
4042
|
onChange: setValues,
|
|
3988
4043
|
onSubmit: (v) => void submit(v),
|
|
3989
|
-
onCancel: () => void (
|
|
3990
|
-
const dirty = JSON.stringify(values) !== JSON.stringify(settingsValues(config));
|
|
3991
|
-
if (!dirty || await confirm({
|
|
3992
|
-
title: "Discard changes",
|
|
3993
|
-
message: "Leave Settings without saving?",
|
|
3994
|
-
confirmLabel: "Discard",
|
|
3995
|
-
danger: true
|
|
3996
|
-
}))
|
|
3997
|
-
back();
|
|
3998
|
-
})(),
|
|
4044
|
+
onCancel: () => void leave(),
|
|
3999
4045
|
isActive: active,
|
|
4000
4046
|
width: width - 4,
|
|
4001
4047
|
height: height - 3 - general.length
|
|
@@ -4107,6 +4153,12 @@ function App(props) {
|
|
|
4107
4153
|
const [stack, setStack] = useState18([props.initialRoute ?? { name: "dashboard" }]);
|
|
4108
4154
|
const route = stack[stack.length - 1];
|
|
4109
4155
|
const [focus, setFocus] = useState18("content");
|
|
4156
|
+
const [actionIndex, setActionIndexState] = useState18(0);
|
|
4157
|
+
const actionRef = useRef7(0);
|
|
4158
|
+
const setActionIndex = (i) => {
|
|
4159
|
+
actionRef.current = i;
|
|
4160
|
+
setActionIndexState(i);
|
|
4161
|
+
};
|
|
4110
4162
|
const [sidebarIndex, setSidebarIndex] = useState18(0);
|
|
4111
4163
|
const [captures, setCaptures] = useState18([]);
|
|
4112
4164
|
const [hints, setHints] = useState18([]);
|
|
@@ -4206,6 +4258,23 @@ function App(props) {
|
|
|
4206
4258
|
const captured = captures.length > 0;
|
|
4207
4259
|
const wide = size.columns >= WIDE;
|
|
4208
4260
|
const current = menuIndex(route);
|
|
4261
|
+
const goBack = () => {
|
|
4262
|
+
if (backHandler.current?.()) return;
|
|
4263
|
+
if (stack.length > 1) back();
|
|
4264
|
+
else if (wide && config) {
|
|
4265
|
+
setSidebarIndex(current);
|
|
4266
|
+
setFocus("sidebar");
|
|
4267
|
+
}
|
|
4268
|
+
};
|
|
4269
|
+
const globals = [
|
|
4270
|
+
{ key: "ctrl+\u2190\u2193\u2192", label: "zones" },
|
|
4271
|
+
// Screens that declare their own Esc button (forms: cancel) don't get a second one.
|
|
4272
|
+
...stack.length > 1 && !hints.some((h) => h.key === "esc") ? [{ key: "esc", label: "back", run: goBack }] : [],
|
|
4273
|
+
{ key: "?", label: "help", run: () => setModal({ kind: "help" }) },
|
|
4274
|
+
{ key: "q", label: "quit", run: () => ink.exit() }
|
|
4275
|
+
];
|
|
4276
|
+
const buttons = [...hints, ...globals].filter((h) => h.run);
|
|
4277
|
+
const selectedAction = Math.min(actionIndex, Math.max(0, buttons.length - 1));
|
|
4209
4278
|
const closeModal = (result) => {
|
|
4210
4279
|
if (modal?.kind === "confirm") modal.resolve(result ?? false);
|
|
4211
4280
|
setModal(null);
|
|
@@ -4216,6 +4285,7 @@ function App(props) {
|
|
|
4216
4285
|
if (modal.kind === "help") return closeModal();
|
|
4217
4286
|
if (input === "y") return closeModal(true);
|
|
4218
4287
|
if (input === "n" || key.escape) return closeModal(false);
|
|
4288
|
+
if (isZoneKey(key)) return;
|
|
4219
4289
|
if (key.leftArrow || key.rightArrow || key.tab)
|
|
4220
4290
|
return setModal({ ...modal, choice: !modal.choice });
|
|
4221
4291
|
if (key.return) return closeModal(modal.choice);
|
|
@@ -4238,18 +4308,45 @@ function App(props) {
|
|
|
4238
4308
|
return setFocus((f) => f === "content" && wide ? "sidebar" : "content");
|
|
4239
4309
|
}
|
|
4240
4310
|
if (key.escape) {
|
|
4241
|
-
if (focus === "content"
|
|
4242
|
-
if (stack.length > 1) back();
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
|
|
4311
|
+
if (focus === "content") goBack();
|
|
4312
|
+
else if (stack.length > 1) back();
|
|
4313
|
+
}
|
|
4314
|
+
},
|
|
4315
|
+
{ isActive: !captured && modal === null && focus !== "actions" }
|
|
4316
|
+
);
|
|
4317
|
+
useInput12(
|
|
4318
|
+
(_input, key) => {
|
|
4319
|
+
if (!isZoneKey(key)) return;
|
|
4320
|
+
if (key.leftArrow) {
|
|
4321
|
+
if (!wide || !config) return;
|
|
4322
|
+
setSidebarIndex(current);
|
|
4323
|
+
setFocus("sidebar");
|
|
4324
|
+
} else if (key.downArrow) {
|
|
4325
|
+
setActionIndex(0);
|
|
4326
|
+
setFocus("actions");
|
|
4327
|
+
} else setFocus("content");
|
|
4328
|
+
},
|
|
4329
|
+
{ isActive: modal === null }
|
|
4330
|
+
);
|
|
4331
|
+
useInput12(
|
|
4332
|
+
(input, key) => {
|
|
4333
|
+
if (isZoneKey(key)) return;
|
|
4334
|
+
const n = buttons.length;
|
|
4335
|
+
const at = Math.min(actionRef.current, n - 1);
|
|
4336
|
+
if (key.leftArrow || input === "h" || key.tab && key.shift)
|
|
4337
|
+
setActionIndex((at - 1 + n) % n);
|
|
4338
|
+
else if (key.rightArrow || input === "l" || key.tab) setActionIndex((at + 1) % n);
|
|
4339
|
+
else if (key.escape || key.upArrow) setFocus("content");
|
|
4340
|
+
else if (key.return || input === " ") {
|
|
4341
|
+
setFocus("content");
|
|
4342
|
+
buttons[at]?.run?.();
|
|
4247
4343
|
}
|
|
4248
4344
|
},
|
|
4249
|
-
{ isActive:
|
|
4345
|
+
{ isActive: focus === "actions" && modal === null && buttons.length > 0 }
|
|
4250
4346
|
);
|
|
4251
4347
|
useInput12(
|
|
4252
4348
|
(input, key) => {
|
|
4349
|
+
if (isZoneKey(key)) return;
|
|
4253
4350
|
if (isUp(input, key)) setSidebarIndex((i) => (i - 1 + MENU.length) % MENU.length);
|
|
4254
4351
|
else if (isDown(input, key)) setSidebarIndex((i) => (i + 1) % MENU.length);
|
|
4255
4352
|
else if (key.return || key.rightArrow) {
|
|
@@ -4322,7 +4419,9 @@ function App(props) {
|
|
|
4322
4419
|
theme: theme2,
|
|
4323
4420
|
columns: size.columns,
|
|
4324
4421
|
hints,
|
|
4325
|
-
global: captured ?
|
|
4422
|
+
global: captured ? globals.slice(0, 1) : globals,
|
|
4423
|
+
focused: focus === "actions",
|
|
4424
|
+
selected: selectedAction
|
|
4326
4425
|
}
|
|
4327
4426
|
),
|
|
4328
4427
|
/* @__PURE__ */ jsx23(Toasts, { theme: theme2, toasts, now }),
|
|
@@ -4350,7 +4449,10 @@ function QuitOnQ() {
|
|
|
4350
4449
|
}
|
|
4351
4450
|
function ConfigErrorView({ message }) {
|
|
4352
4451
|
const { theme: theme2, reload, setHints } = useApp();
|
|
4353
|
-
useEffect7(
|
|
4452
|
+
useEffect7(
|
|
4453
|
+
() => setHints([{ key: "r", label: "reload", run: () => void reload() }]),
|
|
4454
|
+
[setHints, reload]
|
|
4455
|
+
);
|
|
4354
4456
|
useInput12((input) => {
|
|
4355
4457
|
if (input === "r") void reload();
|
|
4356
4458
|
});
|
|
@@ -4360,7 +4462,6 @@ function ConfigErrorView({ message }) {
|
|
|
4360
4462
|
/* @__PURE__ */ jsx23(Text21, { color: theme2.muted, children: "Fix the file (or restore config.json.bak) and press r to reload." })
|
|
4361
4463
|
] });
|
|
4362
4464
|
}
|
|
4363
|
-
var GLOBAL_HINTS;
|
|
4364
4465
|
var init_App = __esm({
|
|
4365
4466
|
"src/ui/App.tsx"() {
|
|
4366
4467
|
"use strict";
|
|
@@ -4374,11 +4475,6 @@ var init_App = __esm({
|
|
|
4374
4475
|
init_router();
|
|
4375
4476
|
init_screens();
|
|
4376
4477
|
init_theme();
|
|
4377
|
-
GLOBAL_HINTS = [
|
|
4378
|
-
{ key: "tab", label: "pane" },
|
|
4379
|
-
{ key: "?", label: "help" },
|
|
4380
|
-
{ key: "q", label: "quit" }
|
|
4381
|
-
];
|
|
4382
4478
|
}
|
|
4383
4479
|
});
|
|
4384
4480
|
|
|
@@ -6362,11 +6458,24 @@ import path18 from "path";
|
|
|
6362
6458
|
|
|
6363
6459
|
// src/core/service/systemctl.ts
|
|
6364
6460
|
import { execFile } from "child_process";
|
|
6461
|
+
import { existsSync } from "fs";
|
|
6462
|
+
function userBusEnv(env = process.env, uid = process.getuid?.() ?? null, exists = existsSync) {
|
|
6463
|
+
const out = { ...env };
|
|
6464
|
+
if (!out.XDG_RUNTIME_DIR && uid !== null) {
|
|
6465
|
+
const dir = `/run/user/${uid}`;
|
|
6466
|
+
if (exists(dir)) out.XDG_RUNTIME_DIR = dir;
|
|
6467
|
+
}
|
|
6468
|
+
if (!out.DBUS_SESSION_BUS_ADDRESS && out.XDG_RUNTIME_DIR) {
|
|
6469
|
+
const bus = `${out.XDG_RUNTIME_DIR}/bus`;
|
|
6470
|
+
if (exists(bus)) out.DBUS_SESSION_BUS_ADDRESS = `unix:path=${bus}`;
|
|
6471
|
+
}
|
|
6472
|
+
return out;
|
|
6473
|
+
}
|
|
6365
6474
|
var execRunner = (command, args) => new Promise((resolve) => {
|
|
6366
6475
|
execFile(
|
|
6367
6476
|
command,
|
|
6368
6477
|
args,
|
|
6369
|
-
{ timeout: 3e4, maxBuffer: 4 * 1024 * 1024 },
|
|
6478
|
+
{ timeout: 3e4, maxBuffer: 4 * 1024 * 1024, env: userBusEnv() },
|
|
6370
6479
|
(error, stdout, stderr) => {
|
|
6371
6480
|
const err = error;
|
|
6372
6481
|
if (err && err.code === "ENOENT") {
|
|
@@ -6452,6 +6561,10 @@ function renderUnit({ command, configPath }) {
|
|
|
6452
6561
|
}
|
|
6453
6562
|
|
|
6454
6563
|
// src/core/service/manager.ts
|
|
6564
|
+
function busHint(message, user) {
|
|
6565
|
+
if (!/Failed to connect to bus|XDG_RUNTIME_DIR not defined/i.test(message)) return message;
|
|
6566
|
+
return `${message} \u2014 no systemd user session for ${user}: log in as ${user} directly (e.g. \`ssh ${user}@host\`, not \`su\`/\`sudo -u\`), or have an administrator run \`loginctl enable-linger ${user}\` once, then retry`;
|
|
6567
|
+
}
|
|
6455
6568
|
var ServiceError = class extends Error {
|
|
6456
6569
|
name = "ServiceError";
|
|
6457
6570
|
};
|
|
@@ -6503,7 +6616,10 @@ var ServiceManager = class {
|
|
|
6503
6616
|
if (show.code !== 0) {
|
|
6504
6617
|
return {
|
|
6505
6618
|
...base,
|
|
6506
|
-
error: (
|
|
6619
|
+
error: busHint(
|
|
6620
|
+
(show.stderr || show.stdout).trim() || `systemctl exited with ${show.code}`,
|
|
6621
|
+
this.user
|
|
6622
|
+
)
|
|
6507
6623
|
};
|
|
6508
6624
|
}
|
|
6509
6625
|
const p = parseProperties(show.stdout);
|
|
@@ -6709,8 +6825,8 @@ import { Box, renderToString, Text } from "ink";
|
|
|
6709
6825
|
import Gradient from "ink-gradient";
|
|
6710
6826
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6711
6827
|
function printOptions(stream = process.stdout) {
|
|
6712
|
-
const
|
|
6713
|
-
return { plain, columns: stream.columns || 100 };
|
|
6828
|
+
const plain2 = !stream.isTTY || "NO_COLOR" in process.env && process.env.NO_COLOR !== "0";
|
|
6829
|
+
return { plain: plain2, columns: stream.columns || 100 };
|
|
6714
6830
|
}
|
|
6715
6831
|
function render(element, options) {
|
|
6716
6832
|
return renderToString(element, { columns: options.columns ?? 100 });
|