@solongate/proxy 0.81.19 → 0.81.21
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/api-client/settings.d.ts +16 -0
- package/dist/commands/index.js +17 -1
- package/dist/index.js +539 -92
- package/dist/tui/hooks.d.ts +14 -0
- package/dist/tui/index.js +526 -91
- package/dist/tui/panels/Settings.d.ts +4 -0
- package/dist/tui/theme.d.ts +4 -0
- package/package.json +1 -1
package/dist/tui/index.js
CHANGED
|
@@ -8,8 +8,8 @@ var __export = (target, all) => {
|
|
|
8
8
|
import { render } from "ink";
|
|
9
9
|
|
|
10
10
|
// src/tui/App.tsx
|
|
11
|
-
import { Box as
|
|
12
|
-
import { useState as
|
|
11
|
+
import { Box as Box10, Text as Text10, useApp, useInput as useInput8 } from "ink";
|
|
12
|
+
import { useState as useState9 } from "react";
|
|
13
13
|
|
|
14
14
|
// src/cli-utils.ts
|
|
15
15
|
var c = {
|
|
@@ -96,6 +96,25 @@ function sparkline(values, width) {
|
|
|
96
96
|
if (max === 0) return BLOCKS[0].repeat(v.length);
|
|
97
97
|
return v.map((n) => BLOCKS[Math.min(BLOCKS.length - 1, Math.round(n / max * (BLOCKS.length - 1)))]).join("");
|
|
98
98
|
}
|
|
99
|
+
function prettyJson(s) {
|
|
100
|
+
try {
|
|
101
|
+
return JSON.stringify(JSON.parse(s), null, 2);
|
|
102
|
+
} catch {
|
|
103
|
+
return s;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function wrapLines(s, width) {
|
|
107
|
+
const w = Math.max(8, width);
|
|
108
|
+
const out = [];
|
|
109
|
+
for (const raw of (s ?? "").split("\n")) {
|
|
110
|
+
if (raw.length <= w) {
|
|
111
|
+
out.push(raw);
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
for (let i = 0; i < raw.length; i += w) out.push(raw.slice(i, i + w));
|
|
115
|
+
}
|
|
116
|
+
return out;
|
|
117
|
+
}
|
|
99
118
|
function truncate(s, n) {
|
|
100
119
|
if (!s) return "";
|
|
101
120
|
return s.length <= n ? s : s.slice(0, Math.max(0, n - 1)) + "\u2026";
|
|
@@ -353,12 +372,16 @@ __export(settings_exports, {
|
|
|
353
372
|
deleteWebhook: () => deleteWebhook,
|
|
354
373
|
getAlerts: () => getAlerts,
|
|
355
374
|
getGuardStatus: () => getGuardStatus,
|
|
375
|
+
getLocalLogs: () => getLocalLogs,
|
|
356
376
|
getRateLimitHistory: () => getRateLimitHistory,
|
|
357
377
|
getSecurityLayers: () => getSecurityLayers,
|
|
358
378
|
getSelfProtection: () => getSelfProtection,
|
|
359
379
|
getWebhooks: () => getWebhooks,
|
|
380
|
+
setAlertEnabled: () => setAlertEnabled,
|
|
381
|
+
setLocalLogs: () => setLocalLogs,
|
|
360
382
|
setSecurityLayers: () => setSecurityLayers,
|
|
361
|
-
setSelfProtection: () => setSelfProtection
|
|
383
|
+
setSelfProtection: () => setSelfProtection,
|
|
384
|
+
updateWebhook: () => updateWebhook
|
|
362
385
|
});
|
|
363
386
|
function getSecurityLayers() {
|
|
364
387
|
return request("GET", "/settings/security-layers");
|
|
@@ -381,6 +404,12 @@ function getSelfProtection() {
|
|
|
381
404
|
function setSelfProtection(enabled) {
|
|
382
405
|
return request("PUT", "/settings/self-protection", { body: { enabled } });
|
|
383
406
|
}
|
|
407
|
+
function getLocalLogs() {
|
|
408
|
+
return request("GET", "/settings/local-logs");
|
|
409
|
+
}
|
|
410
|
+
function setLocalLogs(cfg) {
|
|
411
|
+
return request("PUT", "/settings/local-logs", { body: cfg });
|
|
412
|
+
}
|
|
384
413
|
function getAlerts() {
|
|
385
414
|
return request("GET", "/settings/denial-alerts");
|
|
386
415
|
}
|
|
@@ -390,6 +419,9 @@ function createAlert(body) {
|
|
|
390
419
|
function deleteAlert(id) {
|
|
391
420
|
return request("DELETE", "/settings/denial-alerts", { query: { id } });
|
|
392
421
|
}
|
|
422
|
+
function setAlertEnabled(id, enabled) {
|
|
423
|
+
return request("PATCH", "/settings/denial-alerts", { query: { id }, body: { enabled } });
|
|
424
|
+
}
|
|
393
425
|
function getWebhooks() {
|
|
394
426
|
return request("GET", "/settings/denial-webhook");
|
|
395
427
|
}
|
|
@@ -399,6 +431,9 @@ function createWebhook(body) {
|
|
|
399
431
|
function deleteWebhook(id) {
|
|
400
432
|
return request("DELETE", "/settings/denial-webhook", { query: { id } });
|
|
401
433
|
}
|
|
434
|
+
function updateWebhook(id, patch) {
|
|
435
|
+
return request("PATCH", "/settings/denial-webhook", { body: { id, ...patch } });
|
|
436
|
+
}
|
|
402
437
|
|
|
403
438
|
// src/api-client/stats.ts
|
|
404
439
|
var stats_exports = {};
|
|
@@ -554,6 +589,24 @@ function usePoll(reload, intervalMs, enabled = true) {
|
|
|
554
589
|
return () => clearInterval(t);
|
|
555
590
|
}, [reload, intervalMs, enabled]);
|
|
556
591
|
}
|
|
592
|
+
function useTermSize() {
|
|
593
|
+
const read = () => ({ cols: process.stdout.columns ?? 100, rows: process.stdout.rows ?? 30 });
|
|
594
|
+
const [size, setSize] = useState(read);
|
|
595
|
+
useEffect(() => {
|
|
596
|
+
const onResize = () => setSize(read());
|
|
597
|
+
process.stdout.on("resize", onResize);
|
|
598
|
+
return () => {
|
|
599
|
+
process.stdout.off("resize", onResize);
|
|
600
|
+
};
|
|
601
|
+
}, []);
|
|
602
|
+
return size;
|
|
603
|
+
}
|
|
604
|
+
function usePanelSize() {
|
|
605
|
+
const { cols, rows } = useTermSize();
|
|
606
|
+
const wide = cols >= 82;
|
|
607
|
+
const shellRows = wide ? 12 : 7;
|
|
608
|
+
return { cols: Math.max(30, cols - 22), rows: Math.max(8, rows - shellRows) };
|
|
609
|
+
}
|
|
557
610
|
|
|
558
611
|
// src/tui/panels/Live.tsx
|
|
559
612
|
import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
@@ -692,6 +745,45 @@ var STATUS_STYLE = {
|
|
|
692
745
|
idle: { dot: "\u25D0", label: "IDLE", color: theme.warn },
|
|
693
746
|
ended: { dot: "\u25CB", label: "ENDED", color: theme.dim }
|
|
694
747
|
};
|
|
748
|
+
var LIVE_HELP = [
|
|
749
|
+
[
|
|
750
|
+
"Stream",
|
|
751
|
+
[
|
|
752
|
+
["\u2191\u2193 / PgUp PgDn", "select a row (window follows)"],
|
|
753
|
+
["enter", "open the FULL entry content"],
|
|
754
|
+
["w", "whitelist the selected DENY (adds ALLOW rule)"],
|
|
755
|
+
["b", "block the selected ALLOW (adds DENY rule)"],
|
|
756
|
+
["d / x / r", "filter: denies / dlp hits / rate-limit bursts"],
|
|
757
|
+
["f", "source filter: all \u2192 local \u2192 cloud"],
|
|
758
|
+
["/", "live search (tool, agent, command\u2026) \xB7 enter done"],
|
|
759
|
+
["s", "session picker"],
|
|
760
|
+
["e", "export visible rows \u2192 ~/.solongate/live-export.jsonl"],
|
|
761
|
+
["space", "copy mode: freeze screen for mouse selection"],
|
|
762
|
+
["esc", "back to menu"],
|
|
763
|
+
["q", "quit dataroom"]
|
|
764
|
+
]
|
|
765
|
+
],
|
|
766
|
+
[
|
|
767
|
+
"Entry (full content)",
|
|
768
|
+
[
|
|
769
|
+
["\u2191\u2193 / PgUp PgDn", "scroll the content"],
|
|
770
|
+
["space", "copy mode (freeze, then select with mouse)"],
|
|
771
|
+
["\u2190", "back to where you came from"]
|
|
772
|
+
]
|
|
773
|
+
],
|
|
774
|
+
[
|
|
775
|
+
"Sessions",
|
|
776
|
+
[
|
|
777
|
+
["\u2191\u2193 + enter", "pick & open a session (picker)"],
|
|
778
|
+
["s or \u2190", "cancel the picker"],
|
|
779
|
+
["\u2191\u2193", "select a timeline row (detail)"],
|
|
780
|
+
["enter", "full entry content of the selected row"],
|
|
781
|
+
["/", "search within the timeline"],
|
|
782
|
+
["\u2190", "back to stream"]
|
|
783
|
+
]
|
|
784
|
+
],
|
|
785
|
+
["Anywhere", [["?", "this help"], ["any key", "close this help"]]]
|
|
786
|
+
];
|
|
695
787
|
function LivePanel({ active: active2 }) {
|
|
696
788
|
const [s, setS] = useState2(null);
|
|
697
789
|
const [lat, setLat] = useState2([]);
|
|
@@ -714,6 +806,9 @@ function LivePanel({ active: active2 }) {
|
|
|
714
806
|
const [detail, setDetail] = useState2(null);
|
|
715
807
|
const [detailCloud, setDetailCloud] = useState2([]);
|
|
716
808
|
const [detailScroll, setDetailScroll] = useState2(0);
|
|
809
|
+
const [inspect, setInspect] = useState2(null);
|
|
810
|
+
const [inspectScroll, setInspectScroll] = useState2(0);
|
|
811
|
+
const inspectFromRef = useRef2("stream");
|
|
717
812
|
const seenRef = useRef2(/* @__PURE__ */ new Set());
|
|
718
813
|
const lastLocalTs = useRef2(0);
|
|
719
814
|
const pausedUntil = useRef2(0);
|
|
@@ -724,6 +819,7 @@ function LivePanel({ active: active2 }) {
|
|
|
724
819
|
const [frozen, setFrozen] = useState2(false);
|
|
725
820
|
const frozenRef = useRef2(false);
|
|
726
821
|
frozenRef.current = frozen;
|
|
822
|
+
const [showHelp, setShowHelp] = useState2(false);
|
|
727
823
|
const pushLog = useCallback2((msg, level = "ok") => {
|
|
728
824
|
setLog((prev) => [...prev.slice(-59), { ts: Date.now(), msg, level }]);
|
|
729
825
|
}, []);
|
|
@@ -919,8 +1015,7 @@ function LivePanel({ active: active2 }) {
|
|
|
919
1015
|
else if (e.burst) fireAlert("sec:" + e.id, "Rate-limit burst", `BURST ${e.tool}${who}`, "warn");
|
|
920
1016
|
}
|
|
921
1017
|
}, [tick, active2, fireAlert]);
|
|
922
|
-
const cols
|
|
923
|
-
const rows = process.stdout.rows ?? 30;
|
|
1018
|
+
const { cols, rows } = useTermSize();
|
|
924
1019
|
const ins = insights.data ?? {};
|
|
925
1020
|
const spin = SPIN[tick % SPIN.length];
|
|
926
1021
|
const nowMs = Date.now();
|
|
@@ -1062,12 +1157,30 @@ function LivePanel({ active: active2 }) {
|
|
|
1062
1157
|
return;
|
|
1063
1158
|
}
|
|
1064
1159
|
if (frozen) return;
|
|
1160
|
+
if (showHelp) {
|
|
1161
|
+
setShowHelp(false);
|
|
1162
|
+
return;
|
|
1163
|
+
}
|
|
1164
|
+
if (input === "?") {
|
|
1165
|
+
setShowHelp(true);
|
|
1166
|
+
return;
|
|
1167
|
+
}
|
|
1065
1168
|
if (input === "/" && mode !== "pick") {
|
|
1066
1169
|
setEditingSearch(true);
|
|
1067
1170
|
return;
|
|
1068
1171
|
}
|
|
1172
|
+
if (mode === "inspect") {
|
|
1173
|
+
if (key.leftArrow) {
|
|
1174
|
+
setMode(inspectFromRef.current);
|
|
1175
|
+
setInspect(null);
|
|
1176
|
+
} else if (key.upArrow) setInspectScroll((n) => Math.max(0, n - 1));
|
|
1177
|
+
else if (key.downArrow) setInspectScroll((n) => n + 1);
|
|
1178
|
+
else if (key.pageUp) setInspectScroll((n) => Math.max(0, n - 10));
|
|
1179
|
+
else if (key.pageDown) setInspectScroll((n) => n + 10);
|
|
1180
|
+
return;
|
|
1181
|
+
}
|
|
1069
1182
|
if (mode === "detail") {
|
|
1070
|
-
const maxD = Math.max(0, detailEntries.length -
|
|
1183
|
+
const maxD = Math.max(0, detailEntries.length - 1);
|
|
1071
1184
|
if (key.leftArrow) {
|
|
1072
1185
|
setMode("stream");
|
|
1073
1186
|
setDetail(null);
|
|
@@ -1075,6 +1188,15 @@ function LivePanel({ active: active2 }) {
|
|
|
1075
1188
|
else if (key.downArrow) setDetailScroll((n) => Math.min(maxD, n + 1));
|
|
1076
1189
|
else if (key.pageUp) setDetailScroll((n) => Math.max(0, n - 10));
|
|
1077
1190
|
else if (key.pageDown) setDetailScroll((n) => Math.min(maxD, n + 10));
|
|
1191
|
+
else if (key.return) {
|
|
1192
|
+
const e = detailEntries[Math.min(detailScroll, maxD)];
|
|
1193
|
+
if (e) {
|
|
1194
|
+
inspectFromRef.current = "detail";
|
|
1195
|
+
setInspect(e);
|
|
1196
|
+
setInspectScroll(0);
|
|
1197
|
+
setMode("inspect");
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1078
1200
|
return;
|
|
1079
1201
|
}
|
|
1080
1202
|
if (mode === "pick") {
|
|
@@ -1096,6 +1218,13 @@ function LivePanel({ active: active2 }) {
|
|
|
1096
1218
|
if (input === "f") {
|
|
1097
1219
|
setFilter((f) => FILTERS[(FILTERS.indexOf(f) + 1) % FILTERS.length]);
|
|
1098
1220
|
setSel(0);
|
|
1221
|
+
} else if (key.return) {
|
|
1222
|
+
if (selEntry) {
|
|
1223
|
+
inspectFromRef.current = "stream";
|
|
1224
|
+
setInspect(selEntry);
|
|
1225
|
+
setInspectScroll(0);
|
|
1226
|
+
setMode("inspect");
|
|
1227
|
+
}
|
|
1099
1228
|
} else if (input === "s") {
|
|
1100
1229
|
setPickIdx(0);
|
|
1101
1230
|
setMode("pick");
|
|
@@ -1158,6 +1287,66 @@ function LivePanel({ active: active2 }) {
|
|
|
1158
1287
|
/* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: ` ${spin} up ${fmtUp(nowMs - startRef.current)} \xB7 ${hhmmss(nowMs)} \xB7 api ${latNow}ms ` }),
|
|
1159
1288
|
frozen ? /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#123d1f", color: "#7bd88f", bold: true, children: " \u23F5 COPY MODE \u2014 screen frozen, select & copy freely \xB7 space resume " }) : backingOff ? /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#3d2a12", color: "#ffb454", bold: true, children: " RATE LIMITED \xB7 backing off " }) : lastDeny ? /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#3d1220", color: "#ff6b6b", bold: true, children: ` \u26A0 ${hhmmss(lastDeny.at)} ${lastDeny.tool} DENIED ` }) : /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#4f6db8", children: " \u2713 clean " })
|
|
1160
1289
|
] });
|
|
1290
|
+
if (showHelp) {
|
|
1291
|
+
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingX: 1, children: [
|
|
1292
|
+
titleBar,
|
|
1293
|
+
/* @__PURE__ */ jsx2(Text2, { bold: true, color: theme.accentBright, children: "LIVE \u2014 all keys" }),
|
|
1294
|
+
/* @__PURE__ */ jsx2(Box2, { flexDirection: "column", marginTop: 1, children: LIVE_HELP.map(([group, keys]) => /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", marginBottom: 1, children: [
|
|
1295
|
+
/* @__PURE__ */ jsx2(Text2, { bold: true, color: theme.accent, children: group }),
|
|
1296
|
+
keys.map(([k, desc]) => /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
1297
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.accentBright, children: (" " + k).padEnd(20) }),
|
|
1298
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: desc })
|
|
1299
|
+
] }, k))
|
|
1300
|
+
] }, group)) }),
|
|
1301
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "press any key to close" })
|
|
1302
|
+
] });
|
|
1303
|
+
}
|
|
1304
|
+
if (mode === "inspect" && inspect) {
|
|
1305
|
+
const e = inspect;
|
|
1306
|
+
const bodyW = Math.max(20, cols - 4);
|
|
1307
|
+
const lines = wrapLines(prettyJson(e.detail || "(no arguments / reason recorded)"), bodyW);
|
|
1308
|
+
const bodyRows = Math.max(4, rows - 7);
|
|
1309
|
+
const maxScroll2 = Math.max(0, lines.length - bodyRows);
|
|
1310
|
+
const off = Math.min(inspectScroll, maxScroll2);
|
|
1311
|
+
const win = lines.slice(off, off + bodyRows);
|
|
1312
|
+
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingX: 1, children: [
|
|
1313
|
+
titleBar,
|
|
1314
|
+
/* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
1315
|
+
/* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#AAC8FA", bold: true, children: " ENTRY " }),
|
|
1316
|
+
/* @__PURE__ */ jsx2(Text2, { color: decisionColor(e.decision), bold: true, children: " " + e.decision }),
|
|
1317
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.accent, bold: true, children: " " + e.tool }),
|
|
1318
|
+
/* @__PURE__ */ jsx2(Text2, { color: isLoc(e) ? theme.ok : "#4f6db8", children: " " + (isLoc(e) ? "LOC" : "CLD") }),
|
|
1319
|
+
e.dlp ? /* @__PURE__ */ jsx2(Text2, { color: theme.bad, children: " DLP!" }) : null,
|
|
1320
|
+
e.burst ? /* @__PURE__ */ jsx2(Text2, { color: theme.warn, children: " BURST" }) : null,
|
|
1321
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2190 back" })
|
|
1322
|
+
] }),
|
|
1323
|
+
/* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
1324
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "\u2502 when " }),
|
|
1325
|
+
/* @__PURE__ */ jsx2(Text2, { children: new Date(e.at).toLocaleString() }),
|
|
1326
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 perm " }),
|
|
1327
|
+
/* @__PURE__ */ jsx2(Text2, { children: e.permission || "\u2014" }),
|
|
1328
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 eval " }),
|
|
1329
|
+
/* @__PURE__ */ jsx2(Text2, { color: e.evalMs != null && e.evalMs > 500 ? theme.warn : void 0, children: e.evalMs != null ? `${e.evalMs}ms` : "\u2014" }),
|
|
1330
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 agent " }),
|
|
1331
|
+
/* @__PURE__ */ jsx2(Text2, { children: e.agent ?? "\u2014" }),
|
|
1332
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502" })
|
|
1333
|
+
] }),
|
|
1334
|
+
/* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
1335
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "\u2502 session " }),
|
|
1336
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: e.session ?? "\u2014" }),
|
|
1337
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 rule " }),
|
|
1338
|
+
/* @__PURE__ */ jsx2(Text2, { color: e.rule && e.decision !== "ALLOW" ? theme.bad : theme.dim, children: e.rule ?? "\u2014" }),
|
|
1339
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502" })
|
|
1340
|
+
] }),
|
|
1341
|
+
/* @__PURE__ */ jsx2(PaneTitle, { label: "FULL CONTENT", extra: `${lines.length} lines${maxScroll2 ? ` \xB7 \u25BC${maxScroll2 - off} more \xB7 \u2191\u2193 scroll` : ""} \xB7 space copy \xB7 \u2190 back`, width: innerW }),
|
|
1342
|
+
/* @__PURE__ */ jsx2(Box2, { flexDirection: "column", height: bodyRows, overflow: "hidden", children: win.map((l, i) => /* @__PURE__ */ jsx2(Text2, { wrap: "truncate", children: l || " " }, off + i)) }),
|
|
1343
|
+
/* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
1344
|
+
/* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", bold: true, children: " ENTRY " }),
|
|
1345
|
+
/* @__PURE__ */ jsx2(Text2, { backgroundColor: "#0b1530", color: "#4f6db8", children: ` ${e.id} ` }),
|
|
1346
|
+
/* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: " \u2191\u2193 scroll \xB7 space copy \xB7 ? all keys \xB7 \u2190 back \xB7 esc menu " })
|
|
1347
|
+
] })
|
|
1348
|
+
] });
|
|
1349
|
+
}
|
|
1161
1350
|
if (mode === "detail" && detail) {
|
|
1162
1351
|
const d = detailEntries;
|
|
1163
1352
|
const allow = d.filter((e) => e.decision === "ALLOW").length;
|
|
@@ -1177,9 +1366,10 @@ function LivePanel({ active: active2 }) {
|
|
|
1177
1366
|
const last = d[0]?.at;
|
|
1178
1367
|
const tlRows = Math.max(4, rows - 10);
|
|
1179
1368
|
const tlBody = Math.max(3, tlRows - 1);
|
|
1180
|
-
const
|
|
1181
|
-
const
|
|
1182
|
-
const
|
|
1369
|
+
const dSel = Math.min(detailScroll, Math.max(0, d.length - 1));
|
|
1370
|
+
const maxStart = Math.max(0, d.length - tlBody);
|
|
1371
|
+
const start = Math.min(Math.max(0, dSel - Math.floor((tlBody - 1) / 2)), maxStart);
|
|
1372
|
+
const tl = d.slice(start, start + tlBody);
|
|
1183
1373
|
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingX: 1, children: [
|
|
1184
1374
|
titleBar,
|
|
1185
1375
|
/* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
@@ -1217,16 +1407,16 @@ function LivePanel({ active: active2 }) {
|
|
|
1217
1407
|
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: perms.map(([p, c2]) => `${p}\xD7${c2}`).join(" ") || "\u2014" }),
|
|
1218
1408
|
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502" })
|
|
1219
1409
|
] }),
|
|
1220
|
-
/* @__PURE__ */ jsx2(PaneTitle, { label: "TIMELINE", extra: `${d.length}
|
|
1410
|
+
/* @__PURE__ */ jsx2(PaneTitle, { label: "TIMELINE", extra: `${dSel + 1}/${d.length} \xB7 newest first \xB7 \u2191\u2193 select \xB7 enter full entry \xB7 / search \xB7 \u2190 back`, width: innerW }),
|
|
1221
1411
|
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", height: tlRows, overflow: "hidden", children: [
|
|
1222
1412
|
searchRow,
|
|
1223
1413
|
tl.length === 0 ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: detailCloud.length === 0 ? spin + " loading history\u2026" : "no entries" }) : null,
|
|
1224
|
-
tl.map((e) => /* @__PURE__ */ jsx2(StreamLine, { e, loc: isLoc(e) }, e.id))
|
|
1414
|
+
tl.map((e, i) => /* @__PURE__ */ jsx2(StreamLine, { e, loc: isLoc(e), selected: start + i === dSel }, e.id))
|
|
1225
1415
|
] }),
|
|
1226
1416
|
/* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
1227
1417
|
/* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", bold: true, children: " SESSION " }),
|
|
1228
1418
|
/* @__PURE__ */ jsx2(Text2, { backgroundColor: "#0b1530", color: "#4f6db8", children: ` ${detail.id} ` }),
|
|
1229
|
-
/* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: " \u2191\u2193
|
|
1419
|
+
/* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: " \u2191\u2193 select \xB7 enter full entry \xB7 ? all keys \xB7 \u2190 back \xB7 esc menu " })
|
|
1230
1420
|
] })
|
|
1231
1421
|
] });
|
|
1232
1422
|
}
|
|
@@ -1339,7 +1529,7 @@ function LivePanel({ active: active2 }) {
|
|
|
1339
1529
|
PaneTitle,
|
|
1340
1530
|
{
|
|
1341
1531
|
label: "TOOL STREAM",
|
|
1342
|
-
extra: `${selClamped + 1}/${visibleDesc.length}${signal !== "none" ? ` \xB7 ${signal}` : ""}${q ? " \xB7 search" : ""}
|
|
1532
|
+
extra: `${selClamped + 1}/${visibleDesc.length}${signal !== "none" ? ` \xB7 ${signal}` : ""}${q ? " \xB7 search" : ""}${filter !== "all" ? ` \xB7 source:${filter}` : ""} \xB7 enter full entry \xB7 ? all keys`,
|
|
1343
1533
|
width: innerW
|
|
1344
1534
|
}
|
|
1345
1535
|
),
|
|
@@ -1347,7 +1537,7 @@ function LivePanel({ active: active2 }) {
|
|
|
1347
1537
|
searchRow,
|
|
1348
1538
|
localOn === false ? /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
1349
1539
|
/* @__PURE__ */ jsx2(Text2, { color: theme.warn, children: "local logs off" }),
|
|
1350
|
-
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2014 enable:
|
|
1540
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2014 enable: dataroom \u2192 Settings (local logs) or dashboard \u2192 Settings \xB7 hooks write ~/.solongate/local-logs" })
|
|
1351
1541
|
] }) : null,
|
|
1352
1542
|
windowed.length === 0 ? /* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
|
|
1353
1543
|
spin,
|
|
@@ -1358,7 +1548,7 @@ function LivePanel({ active: active2 }) {
|
|
|
1358
1548
|
/* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
1359
1549
|
/* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", bold: true, children: " LIVE " }),
|
|
1360
1550
|
/* @__PURE__ */ jsx2(Text2, { backgroundColor: "#0b1530", color: "#4f6db8", children: ` local ${localOn ? "on" : "off"} \xB7 ${localBuf.length} loc/${cloudBuf.length} cld \xB7 top ${topTools.map(([t, c2]) => `${t}\xD7${c2}`).join(" ") || "\u2014"} ` }),
|
|
1361
|
-
/* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children:
|
|
1551
|
+
/* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: ` \u2191\u2193 select \xB7 enter full entry \xB7 / search \xB7 space copy \xB7 ? all keys \xB7 esc menu \xB7 q quit ` })
|
|
1362
1552
|
] })
|
|
1363
1553
|
] });
|
|
1364
1554
|
}
|
|
@@ -2115,10 +2305,12 @@ function StatsPanel({ active: active2 }) {
|
|
|
2115
2305
|
import { Box as Box7, Text as Text7, useInput as useInput5 } from "ink";
|
|
2116
2306
|
import TextInput4 from "ink-text-input";
|
|
2117
2307
|
import { useState as useState6 } from "react";
|
|
2118
|
-
import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
2308
|
+
import { Fragment as Fragment4, jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
2119
2309
|
var DECISIONS = [void 0, "DENY", "ALLOW"];
|
|
2120
2310
|
var SIGNALS = [void 0, "dlp", "ratelimit"];
|
|
2311
|
+
var FETCH_LIMIT = 500;
|
|
2121
2312
|
function AuditPanel({ active: active2, focused }) {
|
|
2313
|
+
const { cols, rows } = usePanelSize();
|
|
2122
2314
|
const [di, setDi] = useState6(0);
|
|
2123
2315
|
const [gi, setGi] = useState6(0);
|
|
2124
2316
|
const [tool, setTool] = useState6("");
|
|
@@ -2126,6 +2318,7 @@ function AuditPanel({ active: active2, focused }) {
|
|
|
2126
2318
|
const [search, setSearch] = useState6("");
|
|
2127
2319
|
const [sel, setSel] = useState6(0);
|
|
2128
2320
|
const [view, setView] = useState6("list");
|
|
2321
|
+
const [detailScroll, setDetailScroll] = useState6(0);
|
|
2129
2322
|
const [editing, setEditing] = useState6(null);
|
|
2130
2323
|
const query = {
|
|
2131
2324
|
filter: DECISIONS[di],
|
|
@@ -2133,7 +2326,7 @@ function AuditPanel({ active: active2, focused }) {
|
|
|
2133
2326
|
tool: tool || void 0,
|
|
2134
2327
|
agent_name: agent || void 0,
|
|
2135
2328
|
search: search || void 0,
|
|
2136
|
-
limit:
|
|
2329
|
+
limit: FETCH_LIMIT
|
|
2137
2330
|
};
|
|
2138
2331
|
const auditQ = useLoader(() => api.audit.list(query), [di, gi, tool, agent, search]);
|
|
2139
2332
|
usePoll(auditQ.reload, 6e3, active2 && view === "list" && !editing);
|
|
@@ -2147,15 +2340,29 @@ function AuditPanel({ active: active2, focused }) {
|
|
|
2147
2340
|
(input, key) => {
|
|
2148
2341
|
if (view === "detail") {
|
|
2149
2342
|
if (key.leftArrow || key.escape) setView("list");
|
|
2343
|
+
else if (key.upArrow) setDetailScroll((n) => Math.max(0, n - 1));
|
|
2344
|
+
else if (key.downArrow) setDetailScroll((n) => n + 1);
|
|
2345
|
+
else if (key.pageUp) setDetailScroll((n) => Math.max(0, n - 10));
|
|
2346
|
+
else if (key.pageDown) setDetailScroll((n) => n + 10);
|
|
2150
2347
|
return;
|
|
2151
2348
|
}
|
|
2349
|
+
const clearSel = () => setSel(0);
|
|
2152
2350
|
if (key.upArrow) setSel((n) => Math.max(0, n - 1));
|
|
2153
2351
|
else if (key.downArrow) setSel((n) => Math.min(entries.length - 1, n + 1));
|
|
2352
|
+
else if (key.pageUp) setSel((n) => Math.max(0, n - 10));
|
|
2353
|
+
else if (key.pageDown) setSel((n) => Math.min(entries.length - 1, n + 10));
|
|
2154
2354
|
else if (key.return || key.rightArrow) {
|
|
2155
|
-
if (current)
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2355
|
+
if (current) {
|
|
2356
|
+
setDetailScroll(0);
|
|
2357
|
+
setView("detail");
|
|
2358
|
+
}
|
|
2359
|
+
} else if (input === "f") {
|
|
2360
|
+
setDi((n) => (n + 1) % DECISIONS.length);
|
|
2361
|
+
clearSel();
|
|
2362
|
+
} else if (input === "g") {
|
|
2363
|
+
setGi((n) => (n + 1) % SIGNALS.length);
|
|
2364
|
+
clearSel();
|
|
2365
|
+
} else if (input === "t") setEditing("tool");
|
|
2159
2366
|
else if (input === "n") setEditing("agent");
|
|
2160
2367
|
else if (input === "/") setEditing("search");
|
|
2161
2368
|
else if (input === "c") {
|
|
@@ -2164,49 +2371,64 @@ function AuditPanel({ active: active2, focused }) {
|
|
|
2164
2371
|
setTool("");
|
|
2165
2372
|
setAgent("");
|
|
2166
2373
|
setSearch("");
|
|
2374
|
+
clearSel();
|
|
2167
2375
|
}
|
|
2168
2376
|
},
|
|
2169
2377
|
{ isActive: focused && !editing }
|
|
2170
2378
|
);
|
|
2171
2379
|
if (view === "detail" && current) {
|
|
2172
2380
|
const sessionCalls = sessionQ.data?.entries ?? [];
|
|
2381
|
+
const bodyW = Math.max(20, cols - 2);
|
|
2382
|
+
const reasonLines = wrapLines("reason: " + (current.reason ?? "\u2014"), bodyW);
|
|
2383
|
+
const argLines = current.arguments_summary ? wrapLines(prettyJson(JSON.stringify(current.arguments_summary)), bodyW) : ["(no arguments recorded)"];
|
|
2384
|
+
const sessionRows = current.session_id ? Math.min(4, sessionCalls.length) + 2 : 1;
|
|
2385
|
+
const reasonShown = reasonLines.slice(0, 4);
|
|
2386
|
+
const argBudget = Math.max(3, rows - 2 - 5 - reasonShown.length - 1 - sessionRows - 1);
|
|
2387
|
+
const maxScroll = Math.max(0, argLines.length - argBudget);
|
|
2388
|
+
const off = Math.min(detailScroll, maxScroll);
|
|
2389
|
+
const argWin = argLines.slice(off, off + argBudget);
|
|
2173
2390
|
return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
|
|
2174
2391
|
/* @__PURE__ */ jsxs7(Box7, { children: [
|
|
2175
2392
|
/* @__PURE__ */ jsx7(Text7, { color: decisionColor(current.decision), bold: true, children: current.decision }),
|
|
2176
2393
|
/* @__PURE__ */ jsx7(Text7, { color: theme.accent, children: " " + current.tool_name }),
|
|
2177
2394
|
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " " + current.permission + " \xB7 " + current.trust_level })
|
|
2178
2395
|
] }),
|
|
2179
|
-
/* @__PURE__ */ jsx7(
|
|
2396
|
+
reasonShown.map((l, i) => /* @__PURE__ */ jsx7(Text7, { wrap: "truncate", children: i === 0 ? /* @__PURE__ */ jsxs7(Fragment4, { children: [
|
|
2397
|
+
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "reason " }),
|
|
2398
|
+
/* @__PURE__ */ jsx7(Text7, { children: l.slice("reason: ".length) })
|
|
2399
|
+
] }) : /* @__PURE__ */ jsx7(Text7, { children: " " + l }) }, "r" + i)),
|
|
2180
2400
|
/* @__PURE__ */ jsx7(Detail, { label: "rule", value: current.matched_rule_id ?? "\u2014" }),
|
|
2181
2401
|
/* @__PURE__ */ jsx7(Detail, { label: "agent", value: current.agent_name ?? "\u2014" }),
|
|
2182
2402
|
/* @__PURE__ */ jsx7(Detail, { label: "session", value: current.session_id ?? "\u2014" }),
|
|
2183
2403
|
/* @__PURE__ */ jsx7(Detail, { label: "dlp", value: current.dlp_matches?.length ? current.dlp_matches.join(", ") : "none", color: current.dlp_matches?.length ? theme.bad : void 0 }),
|
|
2184
|
-
/* @__PURE__ */ jsx7(Detail, { label: "when", value: new Date(current.created_at).toLocaleString() }),
|
|
2185
|
-
|
|
2186
|
-
/* @__PURE__ */
|
|
2404
|
+
/* @__PURE__ */ jsx7(Detail, { label: "when", value: new Date(current.created_at).toLocaleString() + (current.evaluation_time_ms != null ? ` \xB7 eval ${current.evaluation_time_ms}ms` : "") }),
|
|
2405
|
+
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: `arguments \u2014 full \xB7 ${argLines.length} lines${maxScroll ? ` \xB7 \u25BC${maxScroll - off} more \xB7 \u2191\u2193 scroll` : ""}` }),
|
|
2406
|
+
/* @__PURE__ */ jsx7(Box7, { flexDirection: "column", height: argBudget, overflow: "hidden", children: argWin.map((l, i) => /* @__PURE__ */ jsx7(Text7, { wrap: "truncate", children: l || " " }, off + i)) }),
|
|
2407
|
+
current.session_id ? /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
|
|
2187
2408
|
/* @__PURE__ */ jsxs7(Text7, { color: theme.dim, children: [
|
|
2188
|
-
"Session ",
|
|
2189
|
-
|
|
2409
|
+
"Session \xB7 ",
|
|
2410
|
+
sessionCalls.length,
|
|
2411
|
+
" calls"
|
|
2190
2412
|
] }),
|
|
2191
|
-
|
|
2413
|
+
/* @__PURE__ */ jsx7(
|
|
2192
2414
|
Table,
|
|
2193
2415
|
{
|
|
2194
2416
|
columns: [
|
|
2195
2417
|
{ header: "DECISION", width: 9 },
|
|
2196
2418
|
{ header: "TOOL", width: 20 },
|
|
2197
|
-
{ header: "REASON", width:
|
|
2419
|
+
{ header: "REASON", width: Math.max(16, cols - 48) },
|
|
2198
2420
|
{ header: "WHEN", width: 6 }
|
|
2199
2421
|
],
|
|
2200
|
-
rows: sessionCalls.slice(0,
|
|
2422
|
+
rows: sessionCalls.slice(0, Math.max(1, sessionRows - 2)).map((e) => [
|
|
2201
2423
|
{ value: e.decision, color: decisionColor(e.decision) },
|
|
2202
2424
|
{ value: truncate(e.tool_name, 20), color: theme.accent },
|
|
2203
|
-
{ value: truncate(e.reason ?? "\u2014",
|
|
2425
|
+
{ value: truncate(e.reason ?? "\u2014", Math.max(16, cols - 48)) },
|
|
2204
2426
|
{ value: ago(e.created_at), dim: true }
|
|
2205
2427
|
])
|
|
2206
2428
|
}
|
|
2207
|
-
)
|
|
2208
|
-
] }),
|
|
2209
|
-
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "\u2190 back to list" })
|
|
2429
|
+
)
|
|
2430
|
+
] }) : /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "(no session id)" }),
|
|
2431
|
+
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "\u2191\u2193 scroll arguments \xB7 \u2190/esc back to list" })
|
|
2210
2432
|
] });
|
|
2211
2433
|
}
|
|
2212
2434
|
const chip = (label, val, on) => /* @__PURE__ */ jsxs7(Text7, { children: [
|
|
@@ -2217,6 +2439,13 @@ function AuditPanel({ active: active2, focused }) {
|
|
|
2217
2439
|
/* @__PURE__ */ jsx7(Text7, { color: on ? theme.accentBright : theme.dim, children: val }),
|
|
2218
2440
|
/* @__PURE__ */ jsx7(Text7, { children: " " })
|
|
2219
2441
|
] });
|
|
2442
|
+
const headerRows = 4 + (editing ? 1 : 0);
|
|
2443
|
+
const listRows = Math.max(4, rows - headerRows);
|
|
2444
|
+
const selClamped = Math.min(sel, Math.max(0, entries.length - 1));
|
|
2445
|
+
const maxStart = Math.max(0, entries.length - listRows);
|
|
2446
|
+
const start = Math.min(Math.max(0, selClamped - Math.floor((listRows - 1) / 2)), maxStart);
|
|
2447
|
+
const windowed = entries.slice(start, start + listRows);
|
|
2448
|
+
const reasonW = Math.min(60, Math.max(12, cols - 67));
|
|
2220
2449
|
return /* @__PURE__ */ jsx7(DataView, { loading: auditQ.loading && !auditQ.data, error: auditQ.error, children: /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
|
|
2221
2450
|
/* @__PURE__ */ jsxs7(Box7, { children: [
|
|
2222
2451
|
chip("dec", DECISIONS[di] ?? "all", di !== 0),
|
|
@@ -2225,8 +2454,8 @@ function AuditPanel({ active: active2, focused }) {
|
|
|
2225
2454
|
chip("agent", agent || "\xB7", !!agent),
|
|
2226
2455
|
chip("search", search || "\xB7", !!search)
|
|
2227
2456
|
] }),
|
|
2228
|
-
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: focused ? "\u2191\u2193 \xB7 enter
|
|
2229
|
-
editing ? /* @__PURE__ */ jsxs7(Box7, {
|
|
2457
|
+
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, wrap: "truncate", children: focused ? "\u2191\u2193 select \xB7 enter full entry \xB7 f dec \xB7 g sig \xB7 t tool \xB7 n agent \xB7 / search \xB7 c clear" : "press \u2192 to browse" }),
|
|
2458
|
+
editing ? /* @__PURE__ */ jsxs7(Box7, { children: [
|
|
2230
2459
|
/* @__PURE__ */ jsxs7(Text7, { color: theme.warn, children: [
|
|
2231
2460
|
editing,
|
|
2232
2461
|
": "
|
|
@@ -2235,16 +2464,15 @@ function AuditPanel({ active: active2, focused }) {
|
|
|
2235
2464
|
TextInput4,
|
|
2236
2465
|
{
|
|
2237
2466
|
value: editing === "tool" ? tool : editing === "agent" ? agent : search,
|
|
2238
|
-
onChange:
|
|
2467
|
+
onChange: (v) => {
|
|
2468
|
+
(editing === "tool" ? setTool : editing === "agent" ? setAgent : setSearch)(v);
|
|
2469
|
+
setSel(0);
|
|
2470
|
+
},
|
|
2239
2471
|
onSubmit: () => setEditing(null)
|
|
2240
2472
|
}
|
|
2241
2473
|
)
|
|
2242
2474
|
] }) : null,
|
|
2243
|
-
/* @__PURE__ */ jsx7(
|
|
2244
|
-
auditQ.data?.total ?? 0,
|
|
2245
|
-
" matched \xB7 showing ",
|
|
2246
|
-
Math.min(entries.length, 12)
|
|
2247
|
-
] }) }),
|
|
2475
|
+
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, wrap: "truncate", children: `${auditQ.data?.total ?? 0} matched \xB7 ${entries.length} loaded \xB7 ${selClamped + 1}/${entries.length}${start ? ` \xB7 \u25B2${start} newer` : ""}${start + listRows < entries.length ? ` \xB7 \u25BC${entries.length - start - listRows} older` : ""}` }),
|
|
2248
2476
|
/* @__PURE__ */ jsx7(
|
|
2249
2477
|
Table,
|
|
2250
2478
|
{
|
|
@@ -2253,22 +2481,22 @@ function AuditPanel({ active: active2, focused }) {
|
|
|
2253
2481
|
{ header: "DECISION", width: 9 },
|
|
2254
2482
|
{ header: "TOOL", width: 18 },
|
|
2255
2483
|
{ header: "AGENT", width: 14 },
|
|
2256
|
-
{ header: "REASON", width:
|
|
2484
|
+
{ header: "REASON", width: reasonW },
|
|
2257
2485
|
{ header: "DLP", width: 4 },
|
|
2258
2486
|
{ header: "WHEN", width: 6 }
|
|
2259
2487
|
],
|
|
2260
|
-
rows:
|
|
2488
|
+
rows: windowed.map((e, i) => rowFor(e, start + i === selClamped && focused, reasonW))
|
|
2261
2489
|
}
|
|
2262
2490
|
)
|
|
2263
2491
|
] }) });
|
|
2264
2492
|
}
|
|
2265
|
-
function rowFor(e, active2) {
|
|
2493
|
+
function rowFor(e, active2, reasonW) {
|
|
2266
2494
|
return [
|
|
2267
2495
|
{ value: active2 ? "\u25B8" : "", color: theme.accentBright },
|
|
2268
2496
|
{ value: e.decision, color: decisionColor(e.decision) },
|
|
2269
2497
|
{ value: truncate(e.tool_name, 18), color: theme.accent },
|
|
2270
2498
|
{ value: truncate(e.agent_name ?? "\u2014", 14), dim: true },
|
|
2271
|
-
{ value: truncate(e.reason ?? "\u2014",
|
|
2499
|
+
{ value: truncate(e.reason ?? "\u2014", reasonW) },
|
|
2272
2500
|
{ value: e.dlp_matches?.length ? String(e.dlp_matches.length) : "0", color: e.dlp_matches?.length ? theme.bad : void 0, dim: !e.dlp_matches?.length },
|
|
2273
2501
|
{ value: ago(e.created_at), dim: true }
|
|
2274
2502
|
];
|
|
@@ -2276,14 +2504,14 @@ function rowFor(e, active2) {
|
|
|
2276
2504
|
function Detail({ label, value, color }) {
|
|
2277
2505
|
return /* @__PURE__ */ jsxs7(Box7, { children: [
|
|
2278
2506
|
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: label.padEnd(9) }),
|
|
2279
|
-
/* @__PURE__ */ jsx7(Text7, { color, children: value })
|
|
2507
|
+
/* @__PURE__ */ jsx7(Text7, { color, wrap: "truncate", children: value })
|
|
2280
2508
|
] });
|
|
2281
2509
|
}
|
|
2282
2510
|
|
|
2283
2511
|
// src/tui/panels/Agents.tsx
|
|
2284
2512
|
import { Box as Box8, Text as Text8, useInput as useInput6 } from "ink";
|
|
2285
2513
|
import { useState as useState7 } from "react";
|
|
2286
|
-
import { Fragment as
|
|
2514
|
+
import { Fragment as Fragment5, jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
2287
2515
|
var statusColor = (s) => s === "active" ? theme.ok : s === "idle" ? theme.warn : theme.dim;
|
|
2288
2516
|
var sevColor = (s) => s === "high" ? theme.bad : s === "medium" ? theme.warn : theme.dim;
|
|
2289
2517
|
function Bar({ label, value, max = 100, width = 16, color = theme.accent }) {
|
|
@@ -2329,7 +2557,7 @@ function AgentsPanel({ focused }) {
|
|
|
2329
2557
|
/* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: `t${a.trust_score}` })
|
|
2330
2558
|
] }, a.session_id))
|
|
2331
2559
|
] }),
|
|
2332
|
-
/* @__PURE__ */ jsx8(Box8, { flexDirection: "column", flexGrow: 1, overflow: "hidden", children: !selected ? /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "select an agent" }) : detail.loading && !d ? /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "loading profile\u2026" }) : !d ? /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "no profile (agent has no calls yet)" }) : /* @__PURE__ */ jsxs8(
|
|
2560
|
+
/* @__PURE__ */ jsx8(Box8, { flexDirection: "column", flexGrow: 1, overflow: "hidden", children: !selected ? /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "select an agent" }) : detail.loading && !d ? /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "loading profile\u2026" }) : !d ? /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "no profile (agent has no calls yet)" }) : /* @__PURE__ */ jsxs8(Fragment5, { children: [
|
|
2333
2561
|
/* @__PURE__ */ jsxs8(Box8, { children: [
|
|
2334
2562
|
/* @__PURE__ */ jsx8(Text8, { bold: true, color: theme.accentBright, children: truncate(selected.agent_name ?? selected.agent_id ?? "?", 22) }),
|
|
2335
2563
|
/* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: ` trust ${base?.trustScore ?? "\u2014"}/100 \xB7 ${base?.character ?? ""}` })
|
|
@@ -2366,16 +2594,221 @@ function AgentsPanel({ focused }) {
|
|
|
2366
2594
|
] }) });
|
|
2367
2595
|
}
|
|
2368
2596
|
|
|
2369
|
-
// src/tui/
|
|
2597
|
+
// src/tui/panels/Settings.tsx
|
|
2598
|
+
import { Box as Box9, Text as Text9, useInput as useInput7 } from "ink";
|
|
2599
|
+
import TextInput5 from "ink-text-input";
|
|
2600
|
+
import { useState as useState8 } from "react";
|
|
2370
2601
|
import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
2602
|
+
var EVENTS = ["denials", "allowed", "all"];
|
|
2603
|
+
function alertBodyFor(channel) {
|
|
2604
|
+
const c2 = channel.trim();
|
|
2605
|
+
const base = { name: "dataroom alert", threshold: 5, windowSeconds: 60, signal: "any" };
|
|
2606
|
+
if (/^https?:\/\//i.test(c2)) return { ...base, slackUrl: c2 };
|
|
2607
|
+
if (c2.includes("@")) return { ...base, emails: [c2] };
|
|
2608
|
+
return { ...base, telegram: [c2] };
|
|
2609
|
+
}
|
|
2610
|
+
function SettingsPanel({ active: active2, focused }) {
|
|
2611
|
+
void active2;
|
|
2612
|
+
const { cols, rows } = usePanelSize();
|
|
2613
|
+
const [sel, setSel] = useState8(0);
|
|
2614
|
+
const [editing, setEditing] = useState8(null);
|
|
2615
|
+
const [input, setInput] = useState8("");
|
|
2616
|
+
const [confirmDel, setConfirmDel] = useState8(null);
|
|
2617
|
+
const [msg, setMsg] = useState8(null);
|
|
2618
|
+
const [busy, setBusy] = useState8(false);
|
|
2619
|
+
const localQ = useLoader(() => api.settings.getLocalLogs());
|
|
2620
|
+
const whQ = useLoader(() => api.settings.getWebhooks());
|
|
2621
|
+
const alertQ = useLoader(() => api.settings.getAlerts());
|
|
2622
|
+
const local = localQ.data;
|
|
2623
|
+
const webhooks = whQ.data?.webhooks ?? [];
|
|
2624
|
+
const alerts = alertQ.data?.rules ?? [];
|
|
2625
|
+
const rowsAll = [
|
|
2626
|
+
{ kind: "ll-enabled" },
|
|
2627
|
+
{ kind: "ll-path" },
|
|
2628
|
+
...webhooks.map((wh) => ({ kind: "wh", wh })),
|
|
2629
|
+
{ kind: "wh-add" },
|
|
2630
|
+
...alerts.map((rule) => ({ kind: "alert", rule })),
|
|
2631
|
+
{ kind: "alert-add" }
|
|
2632
|
+
];
|
|
2633
|
+
const selClamped = Math.min(sel, rowsAll.length - 1);
|
|
2634
|
+
const cur = rowsAll[selClamped];
|
|
2635
|
+
const keyOf = (r) => r.kind === "wh" ? "wh:" + r.wh.id : r.kind === "alert" ? "alert:" + r.rule.id : r.kind;
|
|
2636
|
+
const reloadAll = () => {
|
|
2637
|
+
localQ.reload();
|
|
2638
|
+
whQ.reload();
|
|
2639
|
+
alertQ.reload();
|
|
2640
|
+
};
|
|
2641
|
+
const run = (label, fn, reload) => {
|
|
2642
|
+
if (busy) return;
|
|
2643
|
+
setBusy(true);
|
|
2644
|
+
setMsg({ text: label + "\u2026", level: "ok" });
|
|
2645
|
+
fn().then(() => {
|
|
2646
|
+
setMsg({ text: "\u2713 " + label, level: "ok" });
|
|
2647
|
+
reload();
|
|
2648
|
+
}).catch((e) => setMsg({ text: "\u2717 " + (e instanceof Error ? e.message : String(e)), level: "bad" })).finally(() => setBusy(false));
|
|
2649
|
+
};
|
|
2650
|
+
const activate = (r) => {
|
|
2651
|
+
if (r.kind === "ll-enabled") {
|
|
2652
|
+
if (!local) return;
|
|
2653
|
+
if (!local.enabled && !local.path.trim()) {
|
|
2654
|
+
setMsg({ text: "set a path first (\u2193 then enter)", level: "bad" });
|
|
2655
|
+
return;
|
|
2656
|
+
}
|
|
2657
|
+
run(local.enabled ? "local logs disabled" : "local logs enabled", () => api.settings.setLocalLogs({ enabled: !local.enabled, path: local.path }), localQ.reload);
|
|
2658
|
+
} else if (r.kind === "ll-path") {
|
|
2659
|
+
setInput(local?.path ?? "");
|
|
2660
|
+
setEditing("path");
|
|
2661
|
+
} else if (r.kind === "wh") {
|
|
2662
|
+
run(r.wh.enabled ? "webhook disabled" : "webhook enabled", () => api.settings.updateWebhook(r.wh.id, { enabled: !r.wh.enabled }), whQ.reload);
|
|
2663
|
+
} else if (r.kind === "wh-add") {
|
|
2664
|
+
setInput("");
|
|
2665
|
+
setEditing("wh-url");
|
|
2666
|
+
} else if (r.kind === "alert") {
|
|
2667
|
+
run(r.rule.enabled ? "alert disabled" : "alert enabled", () => api.settings.setAlertEnabled(r.rule.id, !r.rule.enabled), alertQ.reload);
|
|
2668
|
+
} else {
|
|
2669
|
+
setInput("");
|
|
2670
|
+
setEditing("alert-channel");
|
|
2671
|
+
}
|
|
2672
|
+
};
|
|
2673
|
+
const submitInput = () => {
|
|
2674
|
+
const v = input.trim();
|
|
2675
|
+
const which = editing;
|
|
2676
|
+
setEditing(null);
|
|
2677
|
+
if (which === "path") {
|
|
2678
|
+
const enabled = (local?.enabled ?? false) && v.length > 0;
|
|
2679
|
+
run(v ? `path saved${enabled ? "" : " (press enter on enabled to turn on)"}` : "path cleared (local logs off)", () => api.settings.setLocalLogs({ enabled, path: v }), localQ.reload);
|
|
2680
|
+
} else if (which === "wh-url") {
|
|
2681
|
+
if (!v) return;
|
|
2682
|
+
run("webhook added", () => api.settings.createWebhook({ url: v, events: "denials" }), whQ.reload);
|
|
2683
|
+
} else if (which === "alert-channel") {
|
|
2684
|
+
if (!v) return;
|
|
2685
|
+
run("alert rule added", () => api.settings.createAlert(alertBodyFor(v)), alertQ.reload);
|
|
2686
|
+
}
|
|
2687
|
+
};
|
|
2688
|
+
useInput7(
|
|
2689
|
+
(inp, key) => {
|
|
2690
|
+
setMsg(null);
|
|
2691
|
+
if (key.upArrow) {
|
|
2692
|
+
setSel((n) => Math.max(0, n - 1));
|
|
2693
|
+
setConfirmDel(null);
|
|
2694
|
+
} else if (key.downArrow) {
|
|
2695
|
+
setSel((n) => Math.min(rowsAll.length - 1, n + 1));
|
|
2696
|
+
setConfirmDel(null);
|
|
2697
|
+
} else if (key.return || inp === " ") activate(cur);
|
|
2698
|
+
else if (inp === "e" && cur.kind === "wh") {
|
|
2699
|
+
const next = EVENTS[(EVENTS.indexOf(cur.wh.events) + 1) % EVENTS.length];
|
|
2700
|
+
run(`webhook events \u2192 ${next}`, () => api.settings.updateWebhook(cur.wh.id, { events: next }), whQ.reload);
|
|
2701
|
+
} else if (inp === "e" && cur.kind === "ll-path") activate(cur);
|
|
2702
|
+
else if (inp === "a") {
|
|
2703
|
+
if (cur.kind === "wh" || cur.kind === "wh-add") {
|
|
2704
|
+
setInput("");
|
|
2705
|
+
setEditing("wh-url");
|
|
2706
|
+
} else if (cur.kind === "alert" || cur.kind === "alert-add") {
|
|
2707
|
+
setInput("");
|
|
2708
|
+
setEditing("alert-channel");
|
|
2709
|
+
}
|
|
2710
|
+
} else if (inp === "d" && (cur.kind === "wh" || cur.kind === "alert")) {
|
|
2711
|
+
const k = keyOf(cur);
|
|
2712
|
+
if (confirmDel !== k) {
|
|
2713
|
+
setConfirmDel(k);
|
|
2714
|
+
setMsg({ text: "press d again to delete", level: "bad" });
|
|
2715
|
+
return;
|
|
2716
|
+
}
|
|
2717
|
+
setConfirmDel(null);
|
|
2718
|
+
if (cur.kind === "wh") run("webhook deleted", () => api.settings.deleteWebhook(cur.wh.id), whQ.reload);
|
|
2719
|
+
else run("alert deleted", () => api.settings.deleteAlert(cur.rule.id), alertQ.reload);
|
|
2720
|
+
} else if (inp === "r") reloadAll();
|
|
2721
|
+
},
|
|
2722
|
+
{ isActive: focused && !editing }
|
|
2723
|
+
);
|
|
2724
|
+
const loading = localQ.loading && !localQ.data || whQ.loading && !whQ.data || alertQ.loading && !alertQ.data;
|
|
2725
|
+
const error = localQ.error ?? whQ.error ?? alertQ.error;
|
|
2726
|
+
const mark = (r) => keyOf(r) === keyOf(cur) && focused ? "\u25B8 " : " ";
|
|
2727
|
+
const onOff = (on) => on ? /* @__PURE__ */ jsx9(Text9, { color: theme.ok, children: "on " }) : /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "off" });
|
|
2728
|
+
const chanOf = (rule) => [...(rule.slackUrls ?? []).map((u) => "slack " + u), ...rule.emails ?? [], ...(rule.telegram ?? []).map((t) => "tg " + t)].join(", ") || "\u2014";
|
|
2729
|
+
const listBudget = Math.max(4, rows - 8);
|
|
2730
|
+
const start = Math.min(Math.max(0, selClamped - Math.floor(listBudget / 2)), Math.max(0, rowsAll.length - listBudget));
|
|
2731
|
+
const inWindow = (r) => {
|
|
2732
|
+
const i = rowsAll.findIndex((x) => keyOf(x) === keyOf(r));
|
|
2733
|
+
return i >= start && i < start + listBudget;
|
|
2734
|
+
};
|
|
2735
|
+
return /* @__PURE__ */ jsx9(DataView, { loading, error, children: /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
|
|
2736
|
+
/* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: focused ? "\u2191\u2193 move \xB7 enter/space toggle-edit-add \xB7 e events \xB7 d delete \xB7 r refresh" : "press \u2192 to configure" }),
|
|
2737
|
+
editing ? /* @__PURE__ */ jsxs9(Box9, { children: [
|
|
2738
|
+
/* @__PURE__ */ jsx9(Text9, { color: theme.warn, children: editing === "path" ? "local log path: " : editing === "wh-url" ? "webhook url: " : "channel (slack url / email / telegram id): " }),
|
|
2739
|
+
/* @__PURE__ */ jsx9(TextInput5, { value: input, onChange: setInput, onSubmit: submitInput })
|
|
2740
|
+
] }) : msg ? /* @__PURE__ */ jsx9(Text9, { color: msg.level === "bad" ? theme.bad : theme.ok, children: truncate(msg.text, cols) }) : /* @__PURE__ */ jsx9(Text9, { children: " " }),
|
|
2741
|
+
inWindow(rowsAll[0]) || inWindow(rowsAll[1]) ? /* @__PURE__ */ jsxs9(Box9, { marginTop: 1, flexDirection: "column", children: [
|
|
2742
|
+
/* @__PURE__ */ jsxs9(Text9, { bold: true, color: theme.accentBright, children: [
|
|
2743
|
+
"LOCAL LOGS ",
|
|
2744
|
+
/* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "\u2014 hooks mirror every decision to a file on the agent machine" })
|
|
2745
|
+
] }),
|
|
2746
|
+
/* @__PURE__ */ jsxs9(Text9, { wrap: "truncate", children: [
|
|
2747
|
+
/* @__PURE__ */ jsx9(Text9, { color: keyOf(cur) === "ll-enabled" && focused ? theme.accentBright : theme.dim, children: mark(rowsAll[0]) }),
|
|
2748
|
+
/* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "enabled ".padEnd(10) }),
|
|
2749
|
+
onOff(!!local?.enabled),
|
|
2750
|
+
/* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: " enter toggles" })
|
|
2751
|
+
] }),
|
|
2752
|
+
/* @__PURE__ */ jsxs9(Text9, { wrap: "truncate", children: [
|
|
2753
|
+
/* @__PURE__ */ jsx9(Text9, { color: keyOf(cur) === "ll-path" && focused ? theme.accentBright : theme.dim, children: mark(rowsAll[1]) }),
|
|
2754
|
+
/* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "path ".padEnd(10) }),
|
|
2755
|
+
local?.path ? /* @__PURE__ */ jsx9(Text9, { color: theme.accent, children: truncate(local.path, cols - 14) }) : /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "not set \u2014 enter to edit" })
|
|
2756
|
+
] })
|
|
2757
|
+
] }) : null,
|
|
2758
|
+
/* @__PURE__ */ jsxs9(Box9, { marginTop: 1, flexDirection: "column", children: [
|
|
2759
|
+
/* @__PURE__ */ jsxs9(Text9, { bold: true, color: theme.accentBright, children: [
|
|
2760
|
+
"WEBHOOKS ",
|
|
2761
|
+
/* @__PURE__ */ jsxs9(Text9, { color: theme.dim, children: [
|
|
2762
|
+
"\u2014 POST every matching event to your endpoint (",
|
|
2763
|
+
webhooks.length,
|
|
2764
|
+
")"
|
|
2765
|
+
] })
|
|
2766
|
+
] }),
|
|
2767
|
+
webhooks.filter((wh) => inWindow({ kind: "wh", wh })).map((wh) => /* @__PURE__ */ jsxs9(Text9, { wrap: "truncate", children: [
|
|
2768
|
+
/* @__PURE__ */ jsx9(Text9, { color: keyOf(cur) === "wh:" + wh.id && focused ? theme.accentBright : theme.dim, children: mark({ kind: "wh", wh }) }),
|
|
2769
|
+
onOff(wh.enabled),
|
|
2770
|
+
/* @__PURE__ */ jsx9(Text9, { color: theme.warn, children: (" " + wh.events).padEnd(9) }),
|
|
2771
|
+
/* @__PURE__ */ jsx9(Text9, { color: theme.accent, children: truncate(wh.url, cols - 16) })
|
|
2772
|
+
] }, wh.id)),
|
|
2773
|
+
inWindow(rowsAll.find((r) => r.kind === "wh-add")) ? /* @__PURE__ */ jsxs9(Text9, { children: [
|
|
2774
|
+
/* @__PURE__ */ jsx9(Text9, { color: cur.kind === "wh-add" && focused ? theme.accentBright : theme.dim, children: mark({ kind: "wh-add" }) }),
|
|
2775
|
+
/* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "+ add webhook (enter)" })
|
|
2776
|
+
] }) : null
|
|
2777
|
+
] }),
|
|
2778
|
+
/* @__PURE__ */ jsxs9(Box9, { marginTop: 1, flexDirection: "column", children: [
|
|
2779
|
+
/* @__PURE__ */ jsxs9(Text9, { bold: true, color: theme.accentBright, children: [
|
|
2780
|
+
"ALERTS ",
|
|
2781
|
+
/* @__PURE__ */ jsxs9(Text9, { color: theme.dim, children: [
|
|
2782
|
+
"\u2014 notify on a signal spike (",
|
|
2783
|
+
alerts.length,
|
|
2784
|
+
")"
|
|
2785
|
+
] })
|
|
2786
|
+
] }),
|
|
2787
|
+
alerts.filter((rule) => inWindow({ kind: "alert", rule })).map((rule) => /* @__PURE__ */ jsxs9(Text9, { wrap: "truncate", children: [
|
|
2788
|
+
/* @__PURE__ */ jsx9(Text9, { color: keyOf(cur) === "alert:" + rule.id && focused ? theme.accentBright : theme.dim, children: mark({ kind: "alert", rule }) }),
|
|
2789
|
+
onOff(rule.enabled),
|
|
2790
|
+
/* @__PURE__ */ jsx9(Text9, { color: theme.warn, children: (" " + rule.signal).padEnd(11) }),
|
|
2791
|
+
/* @__PURE__ */ jsx9(Text9, { children: `\u2265${rule.threshold}/${rule.windowSeconds}s ` }),
|
|
2792
|
+
/* @__PURE__ */ jsx9(Text9, { color: theme.accent, children: truncate(chanOf(rule), cols - 26) })
|
|
2793
|
+
] }, rule.id)),
|
|
2794
|
+
inWindow(rowsAll[rowsAll.length - 1]) ? /* @__PURE__ */ jsxs9(Text9, { children: [
|
|
2795
|
+
/* @__PURE__ */ jsx9(Text9, { color: cur.kind === "alert-add" && focused ? theme.accentBright : theme.dim, children: mark({ kind: "alert-add" }) }),
|
|
2796
|
+
/* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "+ add alert (enter \u2014 one channel: slack url, email or telegram chat id)" })
|
|
2797
|
+
] }) : null
|
|
2798
|
+
] })
|
|
2799
|
+
] }) });
|
|
2800
|
+
}
|
|
2801
|
+
|
|
2802
|
+
// src/tui/App.tsx
|
|
2803
|
+
import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
2371
2804
|
function LiveHint() {
|
|
2372
|
-
return /* @__PURE__ */
|
|
2373
|
-
/* @__PURE__ */
|
|
2374
|
-
/* @__PURE__ */
|
|
2375
|
-
/* @__PURE__ */
|
|
2376
|
-
/* @__PURE__ */
|
|
2805
|
+
return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", children: [
|
|
2806
|
+
/* @__PURE__ */ jsx10(Text10, { color: theme.accentBright, bold: true, children: "\u25B6 REALTIME CONSOLE" }),
|
|
2807
|
+
/* @__PURE__ */ jsx10(Text10, { color: theme.dim, children: "Fullscreen live monitor \u2014 streaming tool calls, active charts," }),
|
|
2808
|
+
/* @__PURE__ */ jsx10(Text10, { color: theme.dim, children: "counters, DLP hits and denial alerts. Updates every 2s." }),
|
|
2809
|
+
/* @__PURE__ */ jsx10(Box10, { marginTop: 1, children: /* @__PURE__ */ jsxs10(Text10, { children: [
|
|
2377
2810
|
"press ",
|
|
2378
|
-
/* @__PURE__ */
|
|
2811
|
+
/* @__PURE__ */ jsx10(Text10, { color: theme.accent, children: "enter" }),
|
|
2379
2812
|
" to go live"
|
|
2380
2813
|
] }) })
|
|
2381
2814
|
] });
|
|
@@ -2387,28 +2820,29 @@ var SECTIONS = [
|
|
|
2387
2820
|
{ label: "Rate Limit", Panel: RateLimitPanel },
|
|
2388
2821
|
{ label: "DLP", Panel: DlpPanel },
|
|
2389
2822
|
{ label: "Stats", Panel: StatsPanel },
|
|
2390
|
-
{ label: "Audit", Panel: AuditPanel }
|
|
2823
|
+
{ label: "Audit", Panel: AuditPanel },
|
|
2824
|
+
{ label: "Settings", Panel: SettingsPanel }
|
|
2391
2825
|
];
|
|
2392
2826
|
var BANNER_HEX = ["#1432A0", "#2850BE", "#3C6ED7", "#5A8CE6", "#82AAF0", "#AAC8FA"];
|
|
2393
|
-
function Banner() {
|
|
2394
|
-
const wide =
|
|
2827
|
+
function Banner({ cols }) {
|
|
2828
|
+
const wide = cols >= 82;
|
|
2395
2829
|
if (!wide) {
|
|
2396
|
-
return /* @__PURE__ */
|
|
2397
|
-
/* @__PURE__ */
|
|
2398
|
-
/* @__PURE__ */
|
|
2830
|
+
return /* @__PURE__ */ jsxs10(Box10, { children: [
|
|
2831
|
+
/* @__PURE__ */ jsx10(Text10, { bold: true, color: theme.accentBright, children: "SolonGate" }),
|
|
2832
|
+
/* @__PURE__ */ jsx10(Text10, { color: theme.dim, children: " \u2014 security control center" })
|
|
2399
2833
|
] });
|
|
2400
2834
|
}
|
|
2401
|
-
return /* @__PURE__ */
|
|
2402
|
-
BANNER_FULL.map((line, i) => /* @__PURE__ */
|
|
2403
|
-
/* @__PURE__ */
|
|
2835
|
+
return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", children: [
|
|
2836
|
+
BANNER_FULL.map((line, i) => /* @__PURE__ */ jsx10(Text10, { bold: true, color: BANNER_HEX[i], children: line }, i)),
|
|
2837
|
+
/* @__PURE__ */ jsx10(Text10, { color: theme.dim, children: " security control center \xB7 manage policies, rate limits, DLP & more" })
|
|
2404
2838
|
] });
|
|
2405
2839
|
}
|
|
2406
2840
|
function App() {
|
|
2407
2841
|
const { exit } = useApp();
|
|
2408
|
-
const [section, setSection] =
|
|
2409
|
-
const [focus, setFocus] =
|
|
2410
|
-
const [help, setHelp] =
|
|
2411
|
-
|
|
2842
|
+
const [section, setSection] = useState9(0);
|
|
2843
|
+
const [focus, setFocus] = useState9("nav");
|
|
2844
|
+
const [help, setHelp] = useState9(false);
|
|
2845
|
+
useInput8((input, key) => {
|
|
2412
2846
|
if (help) {
|
|
2413
2847
|
setHelp(false);
|
|
2414
2848
|
return;
|
|
@@ -2427,45 +2861,46 @@ function App() {
|
|
|
2427
2861
|
else if (input === "q" && SECTIONS[section].label === "Live") exit();
|
|
2428
2862
|
}
|
|
2429
2863
|
});
|
|
2430
|
-
const cols
|
|
2431
|
-
const rows = process.stdout.rows ?? 30;
|
|
2864
|
+
const { cols, rows } = useTermSize();
|
|
2432
2865
|
const current = SECTIONS[section];
|
|
2433
|
-
if (help) return /* @__PURE__ */
|
|
2866
|
+
if (help) return /* @__PURE__ */ jsx10(HelpOverlay, { cols, rows });
|
|
2434
2867
|
if (current.label === "Live" && focus === "panel") {
|
|
2435
|
-
return /* @__PURE__ */
|
|
2868
|
+
return /* @__PURE__ */ jsx10(Box10, { flexDirection: "column", width: cols, height: rows, children: /* @__PURE__ */ jsx10(LivePanel, { active: true, focused: true }) });
|
|
2436
2869
|
}
|
|
2437
2870
|
const Panel = current.Panel;
|
|
2438
|
-
return /* @__PURE__ */
|
|
2439
|
-
/* @__PURE__ */
|
|
2440
|
-
/* @__PURE__ */
|
|
2441
|
-
/* @__PURE__ */
|
|
2442
|
-
/* @__PURE__ */
|
|
2871
|
+
return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", width: cols, height: rows, paddingX: 1, paddingTop: 1, children: [
|
|
2872
|
+
/* @__PURE__ */ jsx10(Banner, { cols }),
|
|
2873
|
+
/* @__PURE__ */ jsxs10(Box10, { marginTop: 1, flexGrow: 1, children: [
|
|
2874
|
+
/* @__PURE__ */ jsx10(Box10, { flexDirection: "column", width: 16, borderStyle: "round", borderColor: focus === "nav" ? theme.accent : "gray", paddingX: 1, children: SECTIONS.map((s, i) => /* @__PURE__ */ jsx10(Text10, { color: i === section ? theme.accentBright : void 0, bold: i === section, children: (i === section ? "\u25B8 " : " ") + s.label }, s.label)) }),
|
|
2875
|
+
/* @__PURE__ */ jsx10(Box10, { flexGrow: 1, borderStyle: "round", borderColor: focus === "panel" ? theme.accent : "gray", paddingX: 1, paddingY: 0, children: /* @__PURE__ */ jsx10(Panel, { active: focus === "panel" || current.label !== "Live", focused: focus === "panel" }) })
|
|
2443
2876
|
] }),
|
|
2444
|
-
/* @__PURE__ */
|
|
2877
|
+
/* @__PURE__ */ jsx10(Box10, { children: focus === "nav" ? /* @__PURE__ */ jsx10(KeyHints, { hints: [["\u2191\u2193", "section"], ["\u2192/enter", "open"], ["?", "help"], ["q", "quit"]] }) : /* @__PURE__ */ jsx10(KeyHints, { hints: [["\u2190/esc", "back"], ["\u2191\u2193", "in-panel"], ["space/s", "act"]] }) })
|
|
2445
2878
|
] });
|
|
2446
2879
|
}
|
|
2447
2880
|
var HELP = [
|
|
2448
2881
|
["Global", [["\u2191\u2193", "move between sections"], ["\u2192 / enter", "open a section"], ["\u2190 / esc", "back to the menu"], ["?", "this help"], ["q", "quit"]]],
|
|
2449
|
-
["Live", [["\u2191\u2193", "select a stream row"], ["w", "whitelist the selected DENY"], ["b", "block the selected ALLOW"], ["d / x / r", "filter denies / dlp / rate-limit"], ["f", "local / cloud filter"], ["/", "search"], ["s", "sessions (\u2191\u2193 pick, enter open)"], ["space", "copy mode (freeze)"]]],
|
|
2882
|
+
["Live", [["\u2191\u2193", "select a stream row"], ["enter", "full entry content"], ["w", "whitelist the selected DENY"], ["b", "block the selected ALLOW"], ["d / x / r", "filter denies / dlp / rate-limit"], ["f", "local / cloud filter"], ["/", "search"], ["s", "sessions (\u2191\u2193 pick, enter open)"], ["space", "copy mode (freeze)"]]],
|
|
2450
2883
|
["Policies", [["\u2191\u2193", "browse / select"], ["enter", "open rules \u2192 open a rule"], ["space", "toggle a rule on/off"], ["e", "flip effect"], ["n", "new rule"], ["d", "delete rule"], ["m", "flip mode"], ["D", "dry-run the draft"], ["v", "versions / rollback"], ["s", "save"], ["x", "discard"]]],
|
|
2451
|
-
["Rate limit / DLP", [["\u2191\u2193", "field / pattern"], ["\u2190\u2192", "adjust (shift = \xB110)"], ["space", "toggle pattern"], ["m", "mode"], ["a / d", "add / remove custom"], ["g", "ghost mode"], ["P", "self-protection"], ["s", "save"]]]
|
|
2884
|
+
["Rate limit / DLP", [["\u2191\u2193", "field / pattern"], ["\u2190\u2192", "adjust (shift = \xB110)"], ["space", "toggle pattern"], ["m", "mode"], ["a / d", "add / remove custom"], ["g", "ghost mode"], ["P", "self-protection"], ["s", "save"]]],
|
|
2885
|
+
["Audit", [["\u2191\u2193", "select (list scrolls)"], ["enter", "full entry detail"], ["f / g", "decision / signal filter"], ["t / n / /", "tool / agent / search"], ["c", "clear filters"]]],
|
|
2886
|
+
["Settings", [["\u2191\u2193", "move"], ["enter / space", "toggle \xB7 edit \xB7 add"], ["e", "webhook events"], ["d d", "delete"], ["r", "refresh"]]]
|
|
2452
2887
|
];
|
|
2453
2888
|
function HelpOverlay({ cols, rows }) {
|
|
2454
|
-
return /* @__PURE__ */
|
|
2455
|
-
/* @__PURE__ */
|
|
2456
|
-
/* @__PURE__ */
|
|
2457
|
-
/* @__PURE__ */
|
|
2458
|
-
keys.map(([k, desc]) => /* @__PURE__ */
|
|
2459
|
-
/* @__PURE__ */
|
|
2460
|
-
/* @__PURE__ */
|
|
2889
|
+
return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", width: cols, height: rows, paddingX: 2, paddingTop: 1, children: [
|
|
2890
|
+
/* @__PURE__ */ jsx10(Text10, { bold: true, color: theme.accentBright, children: "SolonGate \u2014 keyboard shortcuts" }),
|
|
2891
|
+
/* @__PURE__ */ jsx10(Box10, { marginTop: 1, flexDirection: "column", children: HELP.map(([group, keys]) => /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", marginBottom: 1, children: [
|
|
2892
|
+
/* @__PURE__ */ jsx10(Text10, { bold: true, color: theme.accent, children: group }),
|
|
2893
|
+
keys.map(([k, desc]) => /* @__PURE__ */ jsxs10(Text10, { children: [
|
|
2894
|
+
/* @__PURE__ */ jsx10(Text10, { color: theme.accentBright, children: (" " + k).padEnd(16) }),
|
|
2895
|
+
/* @__PURE__ */ jsx10(Text10, { color: theme.dim, children: desc })
|
|
2461
2896
|
] }, k))
|
|
2462
2897
|
] }, group)) }),
|
|
2463
|
-
/* @__PURE__ */
|
|
2898
|
+
/* @__PURE__ */ jsx10(Text10, { color: theme.dim, children: "press any key to close" })
|
|
2464
2899
|
] });
|
|
2465
2900
|
}
|
|
2466
2901
|
|
|
2467
2902
|
// src/tui/index.tsx
|
|
2468
|
-
import { jsx as
|
|
2903
|
+
import { jsx as jsx11 } from "react/jsx-runtime";
|
|
2469
2904
|
async function launchTui() {
|
|
2470
2905
|
if (!process.stdout.isTTY || !process.stdin.isTTY) {
|
|
2471
2906
|
process.stderr.write(
|
|
@@ -2479,7 +2914,7 @@ async function launchTui() {
|
|
|
2479
2914
|
}
|
|
2480
2915
|
process.stdout.write("\x1B[?1049h\x1B[H");
|
|
2481
2916
|
try {
|
|
2482
|
-
const { waitUntilExit } = render(/* @__PURE__ */
|
|
2917
|
+
const { waitUntilExit } = render(/* @__PURE__ */ jsx11(App, {}));
|
|
2483
2918
|
await waitUntilExit();
|
|
2484
2919
|
} finally {
|
|
2485
2920
|
process.stdout.write("\x1B[?1049l");
|