@solongate/proxy 0.81.19 → 0.81.20
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 +476 -91
- package/dist/tui/hooks.d.ts +14 -0
- package/dist/tui/index.js +464 -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";
|
|
@@ -714,6 +767,9 @@ function LivePanel({ active: active2 }) {
|
|
|
714
767
|
const [detail, setDetail] = useState2(null);
|
|
715
768
|
const [detailCloud, setDetailCloud] = useState2([]);
|
|
716
769
|
const [detailScroll, setDetailScroll] = useState2(0);
|
|
770
|
+
const [inspect, setInspect] = useState2(null);
|
|
771
|
+
const [inspectScroll, setInspectScroll] = useState2(0);
|
|
772
|
+
const inspectFromRef = useRef2("stream");
|
|
717
773
|
const seenRef = useRef2(/* @__PURE__ */ new Set());
|
|
718
774
|
const lastLocalTs = useRef2(0);
|
|
719
775
|
const pausedUntil = useRef2(0);
|
|
@@ -919,8 +975,7 @@ function LivePanel({ active: active2 }) {
|
|
|
919
975
|
else if (e.burst) fireAlert("sec:" + e.id, "Rate-limit burst", `BURST ${e.tool}${who}`, "warn");
|
|
920
976
|
}
|
|
921
977
|
}, [tick, active2, fireAlert]);
|
|
922
|
-
const cols
|
|
923
|
-
const rows = process.stdout.rows ?? 30;
|
|
978
|
+
const { cols, rows } = useTermSize();
|
|
924
979
|
const ins = insights.data ?? {};
|
|
925
980
|
const spin = SPIN[tick % SPIN.length];
|
|
926
981
|
const nowMs = Date.now();
|
|
@@ -1066,8 +1121,18 @@ function LivePanel({ active: active2 }) {
|
|
|
1066
1121
|
setEditingSearch(true);
|
|
1067
1122
|
return;
|
|
1068
1123
|
}
|
|
1124
|
+
if (mode === "inspect") {
|
|
1125
|
+
if (key.leftArrow) {
|
|
1126
|
+
setMode(inspectFromRef.current);
|
|
1127
|
+
setInspect(null);
|
|
1128
|
+
} else if (key.upArrow) setInspectScroll((n) => Math.max(0, n - 1));
|
|
1129
|
+
else if (key.downArrow) setInspectScroll((n) => n + 1);
|
|
1130
|
+
else if (key.pageUp) setInspectScroll((n) => Math.max(0, n - 10));
|
|
1131
|
+
else if (key.pageDown) setInspectScroll((n) => n + 10);
|
|
1132
|
+
return;
|
|
1133
|
+
}
|
|
1069
1134
|
if (mode === "detail") {
|
|
1070
|
-
const maxD = Math.max(0, detailEntries.length -
|
|
1135
|
+
const maxD = Math.max(0, detailEntries.length - 1);
|
|
1071
1136
|
if (key.leftArrow) {
|
|
1072
1137
|
setMode("stream");
|
|
1073
1138
|
setDetail(null);
|
|
@@ -1075,6 +1140,15 @@ function LivePanel({ active: active2 }) {
|
|
|
1075
1140
|
else if (key.downArrow) setDetailScroll((n) => Math.min(maxD, n + 1));
|
|
1076
1141
|
else if (key.pageUp) setDetailScroll((n) => Math.max(0, n - 10));
|
|
1077
1142
|
else if (key.pageDown) setDetailScroll((n) => Math.min(maxD, n + 10));
|
|
1143
|
+
else if (key.return) {
|
|
1144
|
+
const e = detailEntries[Math.min(detailScroll, maxD)];
|
|
1145
|
+
if (e) {
|
|
1146
|
+
inspectFromRef.current = "detail";
|
|
1147
|
+
setInspect(e);
|
|
1148
|
+
setInspectScroll(0);
|
|
1149
|
+
setMode("inspect");
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1078
1152
|
return;
|
|
1079
1153
|
}
|
|
1080
1154
|
if (mode === "pick") {
|
|
@@ -1096,6 +1170,13 @@ function LivePanel({ active: active2 }) {
|
|
|
1096
1170
|
if (input === "f") {
|
|
1097
1171
|
setFilter((f) => FILTERS[(FILTERS.indexOf(f) + 1) % FILTERS.length]);
|
|
1098
1172
|
setSel(0);
|
|
1173
|
+
} else if (key.return) {
|
|
1174
|
+
if (selEntry) {
|
|
1175
|
+
inspectFromRef.current = "stream";
|
|
1176
|
+
setInspect(selEntry);
|
|
1177
|
+
setInspectScroll(0);
|
|
1178
|
+
setMode("inspect");
|
|
1179
|
+
}
|
|
1099
1180
|
} else if (input === "s") {
|
|
1100
1181
|
setPickIdx(0);
|
|
1101
1182
|
setMode("pick");
|
|
@@ -1158,6 +1239,52 @@ function LivePanel({ active: active2 }) {
|
|
|
1158
1239
|
/* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: ` ${spin} up ${fmtUp(nowMs - startRef.current)} \xB7 ${hhmmss(nowMs)} \xB7 api ${latNow}ms ` }),
|
|
1159
1240
|
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
1241
|
] });
|
|
1242
|
+
if (mode === "inspect" && inspect) {
|
|
1243
|
+
const e = inspect;
|
|
1244
|
+
const bodyW = Math.max(20, cols - 4);
|
|
1245
|
+
const lines = wrapLines(prettyJson(e.detail || "(no arguments / reason recorded)"), bodyW);
|
|
1246
|
+
const bodyRows = Math.max(4, rows - 7);
|
|
1247
|
+
const maxScroll2 = Math.max(0, lines.length - bodyRows);
|
|
1248
|
+
const off = Math.min(inspectScroll, maxScroll2);
|
|
1249
|
+
const win = lines.slice(off, off + bodyRows);
|
|
1250
|
+
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingX: 1, children: [
|
|
1251
|
+
titleBar,
|
|
1252
|
+
/* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
1253
|
+
/* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#AAC8FA", bold: true, children: " ENTRY " }),
|
|
1254
|
+
/* @__PURE__ */ jsx2(Text2, { color: decisionColor(e.decision), bold: true, children: " " + e.decision }),
|
|
1255
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.accent, bold: true, children: " " + e.tool }),
|
|
1256
|
+
/* @__PURE__ */ jsx2(Text2, { color: isLoc(e) ? theme.ok : "#4f6db8", children: " " + (isLoc(e) ? "LOC" : "CLD") }),
|
|
1257
|
+
e.dlp ? /* @__PURE__ */ jsx2(Text2, { color: theme.bad, children: " DLP!" }) : null,
|
|
1258
|
+
e.burst ? /* @__PURE__ */ jsx2(Text2, { color: theme.warn, children: " BURST" }) : null,
|
|
1259
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2190 back" })
|
|
1260
|
+
] }),
|
|
1261
|
+
/* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
1262
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "\u2502 when " }),
|
|
1263
|
+
/* @__PURE__ */ jsx2(Text2, { children: new Date(e.at).toLocaleString() }),
|
|
1264
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 perm " }),
|
|
1265
|
+
/* @__PURE__ */ jsx2(Text2, { children: e.permission || "\u2014" }),
|
|
1266
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 eval " }),
|
|
1267
|
+
/* @__PURE__ */ jsx2(Text2, { color: e.evalMs != null && e.evalMs > 500 ? theme.warn : void 0, children: e.evalMs != null ? `${e.evalMs}ms` : "\u2014" }),
|
|
1268
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 agent " }),
|
|
1269
|
+
/* @__PURE__ */ jsx2(Text2, { children: e.agent ?? "\u2014" }),
|
|
1270
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502" })
|
|
1271
|
+
] }),
|
|
1272
|
+
/* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
1273
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "\u2502 session " }),
|
|
1274
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: e.session ?? "\u2014" }),
|
|
1275
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 rule " }),
|
|
1276
|
+
/* @__PURE__ */ jsx2(Text2, { color: e.rule && e.decision !== "ALLOW" ? theme.bad : theme.dim, children: e.rule ?? "\u2014" }),
|
|
1277
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502" })
|
|
1278
|
+
] }),
|
|
1279
|
+
/* @__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 }),
|
|
1280
|
+
/* @__PURE__ */ jsx2(Box2, { flexDirection: "column", height: bodyRows, overflow: "hidden", children: win.map((l, i) => /* @__PURE__ */ jsx2(Text2, { wrap: "truncate", children: l || " " }, off + i)) }),
|
|
1281
|
+
/* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
1282
|
+
/* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", bold: true, children: " ENTRY " }),
|
|
1283
|
+
/* @__PURE__ */ jsx2(Text2, { backgroundColor: "#0b1530", color: "#4f6db8", children: ` ${e.id} ` }),
|
|
1284
|
+
/* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: " \u2191\u2193 scroll \xB7 space copy mode \xB7 \u2190 back \xB7 esc menu " })
|
|
1285
|
+
] })
|
|
1286
|
+
] });
|
|
1287
|
+
}
|
|
1161
1288
|
if (mode === "detail" && detail) {
|
|
1162
1289
|
const d = detailEntries;
|
|
1163
1290
|
const allow = d.filter((e) => e.decision === "ALLOW").length;
|
|
@@ -1177,9 +1304,10 @@ function LivePanel({ active: active2 }) {
|
|
|
1177
1304
|
const last = d[0]?.at;
|
|
1178
1305
|
const tlRows = Math.max(4, rows - 10);
|
|
1179
1306
|
const tlBody = Math.max(3, tlRows - 1);
|
|
1180
|
-
const
|
|
1181
|
-
const
|
|
1182
|
-
const
|
|
1307
|
+
const dSel = Math.min(detailScroll, Math.max(0, d.length - 1));
|
|
1308
|
+
const maxStart = Math.max(0, d.length - tlBody);
|
|
1309
|
+
const start = Math.min(Math.max(0, dSel - Math.floor((tlBody - 1) / 2)), maxStart);
|
|
1310
|
+
const tl = d.slice(start, start + tlBody);
|
|
1183
1311
|
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingX: 1, children: [
|
|
1184
1312
|
titleBar,
|
|
1185
1313
|
/* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
@@ -1217,16 +1345,16 @@ function LivePanel({ active: active2 }) {
|
|
|
1217
1345
|
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: perms.map(([p, c2]) => `${p}\xD7${c2}`).join(" ") || "\u2014" }),
|
|
1218
1346
|
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502" })
|
|
1219
1347
|
] }),
|
|
1220
|
-
/* @__PURE__ */ jsx2(PaneTitle, { label: "TIMELINE", extra: `${d.length}
|
|
1348
|
+
/* @__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
1349
|
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", height: tlRows, overflow: "hidden", children: [
|
|
1222
1350
|
searchRow,
|
|
1223
1351
|
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))
|
|
1352
|
+
tl.map((e, i) => /* @__PURE__ */ jsx2(StreamLine, { e, loc: isLoc(e), selected: start + i === dSel }, e.id))
|
|
1225
1353
|
] }),
|
|
1226
1354
|
/* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
1227
1355
|
/* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", bold: true, children: " SESSION " }),
|
|
1228
1356
|
/* @__PURE__ */ jsx2(Text2, { backgroundColor: "#0b1530", color: "#4f6db8", children: ` ${detail.id} ` }),
|
|
1229
|
-
/* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: " \u2191\u2193
|
|
1357
|
+
/* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: " \u2191\u2193 select \xB7 enter full entry \xB7 \u2190 back \xB7 esc menu " })
|
|
1230
1358
|
] })
|
|
1231
1359
|
] });
|
|
1232
1360
|
}
|
|
@@ -1339,7 +1467,7 @@ function LivePanel({ active: active2 }) {
|
|
|
1339
1467
|
PaneTitle,
|
|
1340
1468
|
{
|
|
1341
1469
|
label: "TOOL STREAM",
|
|
1342
|
-
extra: `${selClamped + 1}/${visibleDesc.length}${signal !== "none" ? ` \xB7 ${signal}` : ""}${q ? " \xB7 search" : ""} \xB7 \u2191\u2193 select \xB7 w allow \xB7 b block \xB7 d/x/r ${filter} \xB7 s`,
|
|
1470
|
+
extra: `${selClamped + 1}/${visibleDesc.length}${signal !== "none" ? ` \xB7 ${signal}` : ""}${q ? " \xB7 search" : ""} \xB7 \u2191\u2193 select \xB7 enter full \xB7 w allow \xB7 b block \xB7 d/x/r ${filter} \xB7 s`,
|
|
1343
1471
|
width: innerW
|
|
1344
1472
|
}
|
|
1345
1473
|
),
|
|
@@ -1347,7 +1475,7 @@ function LivePanel({ active: active2 }) {
|
|
|
1347
1475
|
searchRow,
|
|
1348
1476
|
localOn === false ? /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
1349
1477
|
/* @__PURE__ */ jsx2(Text2, { color: theme.warn, children: "local logs off" }),
|
|
1350
|
-
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2014 enable:
|
|
1478
|
+
/* @__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
1479
|
] }) : null,
|
|
1352
1480
|
windowed.length === 0 ? /* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
|
|
1353
1481
|
spin,
|
|
@@ -1358,7 +1486,7 @@ function LivePanel({ active: active2 }) {
|
|
|
1358
1486
|
/* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
1359
1487
|
/* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", bold: true, children: " LIVE " }),
|
|
1360
1488
|
/* @__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: " \u2191\u2193 select \xB7 w allow \xB7 b block \xB7 d/x/r \xB7 / search \xB7 f \xB7 s \xB7 space copy \xB7 esc \xB7 q " })
|
|
1489
|
+
/* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: " \u2191\u2193 select \xB7 enter full \xB7 w allow \xB7 b block \xB7 d/x/r \xB7 / search \xB7 f \xB7 s \xB7 space copy \xB7 esc \xB7 q " })
|
|
1362
1490
|
] })
|
|
1363
1491
|
] });
|
|
1364
1492
|
}
|
|
@@ -2115,10 +2243,12 @@ function StatsPanel({ active: active2 }) {
|
|
|
2115
2243
|
import { Box as Box7, Text as Text7, useInput as useInput5 } from "ink";
|
|
2116
2244
|
import TextInput4 from "ink-text-input";
|
|
2117
2245
|
import { useState as useState6 } from "react";
|
|
2118
|
-
import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
2246
|
+
import { Fragment as Fragment4, jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
2119
2247
|
var DECISIONS = [void 0, "DENY", "ALLOW"];
|
|
2120
2248
|
var SIGNALS = [void 0, "dlp", "ratelimit"];
|
|
2249
|
+
var FETCH_LIMIT = 500;
|
|
2121
2250
|
function AuditPanel({ active: active2, focused }) {
|
|
2251
|
+
const { cols, rows } = usePanelSize();
|
|
2122
2252
|
const [di, setDi] = useState6(0);
|
|
2123
2253
|
const [gi, setGi] = useState6(0);
|
|
2124
2254
|
const [tool, setTool] = useState6("");
|
|
@@ -2126,6 +2256,7 @@ function AuditPanel({ active: active2, focused }) {
|
|
|
2126
2256
|
const [search, setSearch] = useState6("");
|
|
2127
2257
|
const [sel, setSel] = useState6(0);
|
|
2128
2258
|
const [view, setView] = useState6("list");
|
|
2259
|
+
const [detailScroll, setDetailScroll] = useState6(0);
|
|
2129
2260
|
const [editing, setEditing] = useState6(null);
|
|
2130
2261
|
const query = {
|
|
2131
2262
|
filter: DECISIONS[di],
|
|
@@ -2133,7 +2264,7 @@ function AuditPanel({ active: active2, focused }) {
|
|
|
2133
2264
|
tool: tool || void 0,
|
|
2134
2265
|
agent_name: agent || void 0,
|
|
2135
2266
|
search: search || void 0,
|
|
2136
|
-
limit:
|
|
2267
|
+
limit: FETCH_LIMIT
|
|
2137
2268
|
};
|
|
2138
2269
|
const auditQ = useLoader(() => api.audit.list(query), [di, gi, tool, agent, search]);
|
|
2139
2270
|
usePoll(auditQ.reload, 6e3, active2 && view === "list" && !editing);
|
|
@@ -2147,15 +2278,29 @@ function AuditPanel({ active: active2, focused }) {
|
|
|
2147
2278
|
(input, key) => {
|
|
2148
2279
|
if (view === "detail") {
|
|
2149
2280
|
if (key.leftArrow || key.escape) setView("list");
|
|
2281
|
+
else if (key.upArrow) setDetailScroll((n) => Math.max(0, n - 1));
|
|
2282
|
+
else if (key.downArrow) setDetailScroll((n) => n + 1);
|
|
2283
|
+
else if (key.pageUp) setDetailScroll((n) => Math.max(0, n - 10));
|
|
2284
|
+
else if (key.pageDown) setDetailScroll((n) => n + 10);
|
|
2150
2285
|
return;
|
|
2151
2286
|
}
|
|
2287
|
+
const clearSel = () => setSel(0);
|
|
2152
2288
|
if (key.upArrow) setSel((n) => Math.max(0, n - 1));
|
|
2153
2289
|
else if (key.downArrow) setSel((n) => Math.min(entries.length - 1, n + 1));
|
|
2290
|
+
else if (key.pageUp) setSel((n) => Math.max(0, n - 10));
|
|
2291
|
+
else if (key.pageDown) setSel((n) => Math.min(entries.length - 1, n + 10));
|
|
2154
2292
|
else if (key.return || key.rightArrow) {
|
|
2155
|
-
if (current)
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2293
|
+
if (current) {
|
|
2294
|
+
setDetailScroll(0);
|
|
2295
|
+
setView("detail");
|
|
2296
|
+
}
|
|
2297
|
+
} else if (input === "f") {
|
|
2298
|
+
setDi((n) => (n + 1) % DECISIONS.length);
|
|
2299
|
+
clearSel();
|
|
2300
|
+
} else if (input === "g") {
|
|
2301
|
+
setGi((n) => (n + 1) % SIGNALS.length);
|
|
2302
|
+
clearSel();
|
|
2303
|
+
} else if (input === "t") setEditing("tool");
|
|
2159
2304
|
else if (input === "n") setEditing("agent");
|
|
2160
2305
|
else if (input === "/") setEditing("search");
|
|
2161
2306
|
else if (input === "c") {
|
|
@@ -2164,49 +2309,64 @@ function AuditPanel({ active: active2, focused }) {
|
|
|
2164
2309
|
setTool("");
|
|
2165
2310
|
setAgent("");
|
|
2166
2311
|
setSearch("");
|
|
2312
|
+
clearSel();
|
|
2167
2313
|
}
|
|
2168
2314
|
},
|
|
2169
2315
|
{ isActive: focused && !editing }
|
|
2170
2316
|
);
|
|
2171
2317
|
if (view === "detail" && current) {
|
|
2172
2318
|
const sessionCalls = sessionQ.data?.entries ?? [];
|
|
2319
|
+
const bodyW = Math.max(20, cols - 2);
|
|
2320
|
+
const reasonLines = wrapLines("reason: " + (current.reason ?? "\u2014"), bodyW);
|
|
2321
|
+
const argLines = current.arguments_summary ? wrapLines(prettyJson(JSON.stringify(current.arguments_summary)), bodyW) : ["(no arguments recorded)"];
|
|
2322
|
+
const sessionRows = current.session_id ? Math.min(4, sessionCalls.length) + 2 : 1;
|
|
2323
|
+
const reasonShown = reasonLines.slice(0, 4);
|
|
2324
|
+
const argBudget = Math.max(3, rows - 2 - 5 - reasonShown.length - 1 - sessionRows - 1);
|
|
2325
|
+
const maxScroll = Math.max(0, argLines.length - argBudget);
|
|
2326
|
+
const off = Math.min(detailScroll, maxScroll);
|
|
2327
|
+
const argWin = argLines.slice(off, off + argBudget);
|
|
2173
2328
|
return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
|
|
2174
2329
|
/* @__PURE__ */ jsxs7(Box7, { children: [
|
|
2175
2330
|
/* @__PURE__ */ jsx7(Text7, { color: decisionColor(current.decision), bold: true, children: current.decision }),
|
|
2176
2331
|
/* @__PURE__ */ jsx7(Text7, { color: theme.accent, children: " " + current.tool_name }),
|
|
2177
2332
|
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " " + current.permission + " \xB7 " + current.trust_level })
|
|
2178
2333
|
] }),
|
|
2179
|
-
/* @__PURE__ */ jsx7(
|
|
2334
|
+
reasonShown.map((l, i) => /* @__PURE__ */ jsx7(Text7, { wrap: "truncate", children: i === 0 ? /* @__PURE__ */ jsxs7(Fragment4, { children: [
|
|
2335
|
+
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "reason " }),
|
|
2336
|
+
/* @__PURE__ */ jsx7(Text7, { children: l.slice("reason: ".length) })
|
|
2337
|
+
] }) : /* @__PURE__ */ jsx7(Text7, { children: " " + l }) }, "r" + i)),
|
|
2180
2338
|
/* @__PURE__ */ jsx7(Detail, { label: "rule", value: current.matched_rule_id ?? "\u2014" }),
|
|
2181
2339
|
/* @__PURE__ */ jsx7(Detail, { label: "agent", value: current.agent_name ?? "\u2014" }),
|
|
2182
2340
|
/* @__PURE__ */ jsx7(Detail, { label: "session", value: current.session_id ?? "\u2014" }),
|
|
2183
2341
|
/* @__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__ */
|
|
2342
|
+
/* @__PURE__ */ jsx7(Detail, { label: "when", value: new Date(current.created_at).toLocaleString() + (current.evaluation_time_ms != null ? ` \xB7 eval ${current.evaluation_time_ms}ms` : "") }),
|
|
2343
|
+
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: `arguments \u2014 full \xB7 ${argLines.length} lines${maxScroll ? ` \xB7 \u25BC${maxScroll - off} more \xB7 \u2191\u2193 scroll` : ""}` }),
|
|
2344
|
+
/* @__PURE__ */ jsx7(Box7, { flexDirection: "column", height: argBudget, overflow: "hidden", children: argWin.map((l, i) => /* @__PURE__ */ jsx7(Text7, { wrap: "truncate", children: l || " " }, off + i)) }),
|
|
2345
|
+
current.session_id ? /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
|
|
2187
2346
|
/* @__PURE__ */ jsxs7(Text7, { color: theme.dim, children: [
|
|
2188
|
-
"Session ",
|
|
2189
|
-
|
|
2347
|
+
"Session \xB7 ",
|
|
2348
|
+
sessionCalls.length,
|
|
2349
|
+
" calls"
|
|
2190
2350
|
] }),
|
|
2191
|
-
|
|
2351
|
+
/* @__PURE__ */ jsx7(
|
|
2192
2352
|
Table,
|
|
2193
2353
|
{
|
|
2194
2354
|
columns: [
|
|
2195
2355
|
{ header: "DECISION", width: 9 },
|
|
2196
2356
|
{ header: "TOOL", width: 20 },
|
|
2197
|
-
{ header: "REASON", width:
|
|
2357
|
+
{ header: "REASON", width: Math.max(16, cols - 48) },
|
|
2198
2358
|
{ header: "WHEN", width: 6 }
|
|
2199
2359
|
],
|
|
2200
|
-
rows: sessionCalls.slice(0,
|
|
2360
|
+
rows: sessionCalls.slice(0, Math.max(1, sessionRows - 2)).map((e) => [
|
|
2201
2361
|
{ value: e.decision, color: decisionColor(e.decision) },
|
|
2202
2362
|
{ value: truncate(e.tool_name, 20), color: theme.accent },
|
|
2203
|
-
{ value: truncate(e.reason ?? "\u2014",
|
|
2363
|
+
{ value: truncate(e.reason ?? "\u2014", Math.max(16, cols - 48)) },
|
|
2204
2364
|
{ value: ago(e.created_at), dim: true }
|
|
2205
2365
|
])
|
|
2206
2366
|
}
|
|
2207
|
-
)
|
|
2208
|
-
] }),
|
|
2209
|
-
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "\u2190 back to list" })
|
|
2367
|
+
)
|
|
2368
|
+
] }) : /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "(no session id)" }),
|
|
2369
|
+
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "\u2191\u2193 scroll arguments \xB7 \u2190/esc back to list" })
|
|
2210
2370
|
] });
|
|
2211
2371
|
}
|
|
2212
2372
|
const chip = (label, val, on) => /* @__PURE__ */ jsxs7(Text7, { children: [
|
|
@@ -2217,6 +2377,13 @@ function AuditPanel({ active: active2, focused }) {
|
|
|
2217
2377
|
/* @__PURE__ */ jsx7(Text7, { color: on ? theme.accentBright : theme.dim, children: val }),
|
|
2218
2378
|
/* @__PURE__ */ jsx7(Text7, { children: " " })
|
|
2219
2379
|
] });
|
|
2380
|
+
const headerRows = 4 + (editing ? 1 : 0);
|
|
2381
|
+
const listRows = Math.max(4, rows - headerRows);
|
|
2382
|
+
const selClamped = Math.min(sel, Math.max(0, entries.length - 1));
|
|
2383
|
+
const maxStart = Math.max(0, entries.length - listRows);
|
|
2384
|
+
const start = Math.min(Math.max(0, selClamped - Math.floor((listRows - 1) / 2)), maxStart);
|
|
2385
|
+
const windowed = entries.slice(start, start + listRows);
|
|
2386
|
+
const reasonW = Math.min(60, Math.max(12, cols - 67));
|
|
2220
2387
|
return /* @__PURE__ */ jsx7(DataView, { loading: auditQ.loading && !auditQ.data, error: auditQ.error, children: /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
|
|
2221
2388
|
/* @__PURE__ */ jsxs7(Box7, { children: [
|
|
2222
2389
|
chip("dec", DECISIONS[di] ?? "all", di !== 0),
|
|
@@ -2225,8 +2392,8 @@ function AuditPanel({ active: active2, focused }) {
|
|
|
2225
2392
|
chip("agent", agent || "\xB7", !!agent),
|
|
2226
2393
|
chip("search", search || "\xB7", !!search)
|
|
2227
2394
|
] }),
|
|
2228
|
-
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: focused ? "\u2191\u2193 \xB7 enter
|
|
2229
|
-
editing ? /* @__PURE__ */ jsxs7(Box7, {
|
|
2395
|
+
/* @__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" }),
|
|
2396
|
+
editing ? /* @__PURE__ */ jsxs7(Box7, { children: [
|
|
2230
2397
|
/* @__PURE__ */ jsxs7(Text7, { color: theme.warn, children: [
|
|
2231
2398
|
editing,
|
|
2232
2399
|
": "
|
|
@@ -2235,16 +2402,15 @@ function AuditPanel({ active: active2, focused }) {
|
|
|
2235
2402
|
TextInput4,
|
|
2236
2403
|
{
|
|
2237
2404
|
value: editing === "tool" ? tool : editing === "agent" ? agent : search,
|
|
2238
|
-
onChange:
|
|
2405
|
+
onChange: (v) => {
|
|
2406
|
+
(editing === "tool" ? setTool : editing === "agent" ? setAgent : setSearch)(v);
|
|
2407
|
+
setSel(0);
|
|
2408
|
+
},
|
|
2239
2409
|
onSubmit: () => setEditing(null)
|
|
2240
2410
|
}
|
|
2241
2411
|
)
|
|
2242
2412
|
] }) : null,
|
|
2243
|
-
/* @__PURE__ */ jsx7(
|
|
2244
|
-
auditQ.data?.total ?? 0,
|
|
2245
|
-
" matched \xB7 showing ",
|
|
2246
|
-
Math.min(entries.length, 12)
|
|
2247
|
-
] }) }),
|
|
2413
|
+
/* @__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
2414
|
/* @__PURE__ */ jsx7(
|
|
2249
2415
|
Table,
|
|
2250
2416
|
{
|
|
@@ -2253,22 +2419,22 @@ function AuditPanel({ active: active2, focused }) {
|
|
|
2253
2419
|
{ header: "DECISION", width: 9 },
|
|
2254
2420
|
{ header: "TOOL", width: 18 },
|
|
2255
2421
|
{ header: "AGENT", width: 14 },
|
|
2256
|
-
{ header: "REASON", width:
|
|
2422
|
+
{ header: "REASON", width: reasonW },
|
|
2257
2423
|
{ header: "DLP", width: 4 },
|
|
2258
2424
|
{ header: "WHEN", width: 6 }
|
|
2259
2425
|
],
|
|
2260
|
-
rows:
|
|
2426
|
+
rows: windowed.map((e, i) => rowFor(e, start + i === selClamped && focused, reasonW))
|
|
2261
2427
|
}
|
|
2262
2428
|
)
|
|
2263
2429
|
] }) });
|
|
2264
2430
|
}
|
|
2265
|
-
function rowFor(e, active2) {
|
|
2431
|
+
function rowFor(e, active2, reasonW) {
|
|
2266
2432
|
return [
|
|
2267
2433
|
{ value: active2 ? "\u25B8" : "", color: theme.accentBright },
|
|
2268
2434
|
{ value: e.decision, color: decisionColor(e.decision) },
|
|
2269
2435
|
{ value: truncate(e.tool_name, 18), color: theme.accent },
|
|
2270
2436
|
{ value: truncate(e.agent_name ?? "\u2014", 14), dim: true },
|
|
2271
|
-
{ value: truncate(e.reason ?? "\u2014",
|
|
2437
|
+
{ value: truncate(e.reason ?? "\u2014", reasonW) },
|
|
2272
2438
|
{ 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
2439
|
{ value: ago(e.created_at), dim: true }
|
|
2274
2440
|
];
|
|
@@ -2276,14 +2442,14 @@ function rowFor(e, active2) {
|
|
|
2276
2442
|
function Detail({ label, value, color }) {
|
|
2277
2443
|
return /* @__PURE__ */ jsxs7(Box7, { children: [
|
|
2278
2444
|
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: label.padEnd(9) }),
|
|
2279
|
-
/* @__PURE__ */ jsx7(Text7, { color, children: value })
|
|
2445
|
+
/* @__PURE__ */ jsx7(Text7, { color, wrap: "truncate", children: value })
|
|
2280
2446
|
] });
|
|
2281
2447
|
}
|
|
2282
2448
|
|
|
2283
2449
|
// src/tui/panels/Agents.tsx
|
|
2284
2450
|
import { Box as Box8, Text as Text8, useInput as useInput6 } from "ink";
|
|
2285
2451
|
import { useState as useState7 } from "react";
|
|
2286
|
-
import { Fragment as
|
|
2452
|
+
import { Fragment as Fragment5, jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
2287
2453
|
var statusColor = (s) => s === "active" ? theme.ok : s === "idle" ? theme.warn : theme.dim;
|
|
2288
2454
|
var sevColor = (s) => s === "high" ? theme.bad : s === "medium" ? theme.warn : theme.dim;
|
|
2289
2455
|
function Bar({ label, value, max = 100, width = 16, color = theme.accent }) {
|
|
@@ -2329,7 +2495,7 @@ function AgentsPanel({ focused }) {
|
|
|
2329
2495
|
/* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: `t${a.trust_score}` })
|
|
2330
2496
|
] }, a.session_id))
|
|
2331
2497
|
] }),
|
|
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(
|
|
2498
|
+
/* @__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
2499
|
/* @__PURE__ */ jsxs8(Box8, { children: [
|
|
2334
2500
|
/* @__PURE__ */ jsx8(Text8, { bold: true, color: theme.accentBright, children: truncate(selected.agent_name ?? selected.agent_id ?? "?", 22) }),
|
|
2335
2501
|
/* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: ` trust ${base?.trustScore ?? "\u2014"}/100 \xB7 ${base?.character ?? ""}` })
|
|
@@ -2366,16 +2532,221 @@ function AgentsPanel({ focused }) {
|
|
|
2366
2532
|
] }) });
|
|
2367
2533
|
}
|
|
2368
2534
|
|
|
2369
|
-
// src/tui/
|
|
2535
|
+
// src/tui/panels/Settings.tsx
|
|
2536
|
+
import { Box as Box9, Text as Text9, useInput as useInput7 } from "ink";
|
|
2537
|
+
import TextInput5 from "ink-text-input";
|
|
2538
|
+
import { useState as useState8 } from "react";
|
|
2370
2539
|
import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
2540
|
+
var EVENTS = ["denials", "allowed", "all"];
|
|
2541
|
+
function alertBodyFor(channel) {
|
|
2542
|
+
const c2 = channel.trim();
|
|
2543
|
+
const base = { name: "dataroom alert", threshold: 5, windowSeconds: 60, signal: "any" };
|
|
2544
|
+
if (/^https?:\/\//i.test(c2)) return { ...base, slackUrl: c2 };
|
|
2545
|
+
if (c2.includes("@")) return { ...base, emails: [c2] };
|
|
2546
|
+
return { ...base, telegram: [c2] };
|
|
2547
|
+
}
|
|
2548
|
+
function SettingsPanel({ active: active2, focused }) {
|
|
2549
|
+
void active2;
|
|
2550
|
+
const { cols, rows } = usePanelSize();
|
|
2551
|
+
const [sel, setSel] = useState8(0);
|
|
2552
|
+
const [editing, setEditing] = useState8(null);
|
|
2553
|
+
const [input, setInput] = useState8("");
|
|
2554
|
+
const [confirmDel, setConfirmDel] = useState8(null);
|
|
2555
|
+
const [msg, setMsg] = useState8(null);
|
|
2556
|
+
const [busy, setBusy] = useState8(false);
|
|
2557
|
+
const localQ = useLoader(() => api.settings.getLocalLogs());
|
|
2558
|
+
const whQ = useLoader(() => api.settings.getWebhooks());
|
|
2559
|
+
const alertQ = useLoader(() => api.settings.getAlerts());
|
|
2560
|
+
const local = localQ.data;
|
|
2561
|
+
const webhooks = whQ.data?.webhooks ?? [];
|
|
2562
|
+
const alerts = alertQ.data?.rules ?? [];
|
|
2563
|
+
const rowsAll = [
|
|
2564
|
+
{ kind: "ll-enabled" },
|
|
2565
|
+
{ kind: "ll-path" },
|
|
2566
|
+
...webhooks.map((wh) => ({ kind: "wh", wh })),
|
|
2567
|
+
{ kind: "wh-add" },
|
|
2568
|
+
...alerts.map((rule) => ({ kind: "alert", rule })),
|
|
2569
|
+
{ kind: "alert-add" }
|
|
2570
|
+
];
|
|
2571
|
+
const selClamped = Math.min(sel, rowsAll.length - 1);
|
|
2572
|
+
const cur = rowsAll[selClamped];
|
|
2573
|
+
const keyOf = (r) => r.kind === "wh" ? "wh:" + r.wh.id : r.kind === "alert" ? "alert:" + r.rule.id : r.kind;
|
|
2574
|
+
const reloadAll = () => {
|
|
2575
|
+
localQ.reload();
|
|
2576
|
+
whQ.reload();
|
|
2577
|
+
alertQ.reload();
|
|
2578
|
+
};
|
|
2579
|
+
const run = (label, fn, reload) => {
|
|
2580
|
+
if (busy) return;
|
|
2581
|
+
setBusy(true);
|
|
2582
|
+
setMsg({ text: label + "\u2026", level: "ok" });
|
|
2583
|
+
fn().then(() => {
|
|
2584
|
+
setMsg({ text: "\u2713 " + label, level: "ok" });
|
|
2585
|
+
reload();
|
|
2586
|
+
}).catch((e) => setMsg({ text: "\u2717 " + (e instanceof Error ? e.message : String(e)), level: "bad" })).finally(() => setBusy(false));
|
|
2587
|
+
};
|
|
2588
|
+
const activate = (r) => {
|
|
2589
|
+
if (r.kind === "ll-enabled") {
|
|
2590
|
+
if (!local) return;
|
|
2591
|
+
if (!local.enabled && !local.path.trim()) {
|
|
2592
|
+
setMsg({ text: "set a path first (\u2193 then enter)", level: "bad" });
|
|
2593
|
+
return;
|
|
2594
|
+
}
|
|
2595
|
+
run(local.enabled ? "local logs disabled" : "local logs enabled", () => api.settings.setLocalLogs({ enabled: !local.enabled, path: local.path }), localQ.reload);
|
|
2596
|
+
} else if (r.kind === "ll-path") {
|
|
2597
|
+
setInput(local?.path ?? "");
|
|
2598
|
+
setEditing("path");
|
|
2599
|
+
} else if (r.kind === "wh") {
|
|
2600
|
+
run(r.wh.enabled ? "webhook disabled" : "webhook enabled", () => api.settings.updateWebhook(r.wh.id, { enabled: !r.wh.enabled }), whQ.reload);
|
|
2601
|
+
} else if (r.kind === "wh-add") {
|
|
2602
|
+
setInput("");
|
|
2603
|
+
setEditing("wh-url");
|
|
2604
|
+
} else if (r.kind === "alert") {
|
|
2605
|
+
run(r.rule.enabled ? "alert disabled" : "alert enabled", () => api.settings.setAlertEnabled(r.rule.id, !r.rule.enabled), alertQ.reload);
|
|
2606
|
+
} else {
|
|
2607
|
+
setInput("");
|
|
2608
|
+
setEditing("alert-channel");
|
|
2609
|
+
}
|
|
2610
|
+
};
|
|
2611
|
+
const submitInput = () => {
|
|
2612
|
+
const v = input.trim();
|
|
2613
|
+
const which = editing;
|
|
2614
|
+
setEditing(null);
|
|
2615
|
+
if (which === "path") {
|
|
2616
|
+
const enabled = (local?.enabled ?? false) && v.length > 0;
|
|
2617
|
+
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);
|
|
2618
|
+
} else if (which === "wh-url") {
|
|
2619
|
+
if (!v) return;
|
|
2620
|
+
run("webhook added", () => api.settings.createWebhook({ url: v, events: "denials" }), whQ.reload);
|
|
2621
|
+
} else if (which === "alert-channel") {
|
|
2622
|
+
if (!v) return;
|
|
2623
|
+
run("alert rule added", () => api.settings.createAlert(alertBodyFor(v)), alertQ.reload);
|
|
2624
|
+
}
|
|
2625
|
+
};
|
|
2626
|
+
useInput7(
|
|
2627
|
+
(inp, key) => {
|
|
2628
|
+
setMsg(null);
|
|
2629
|
+
if (key.upArrow) {
|
|
2630
|
+
setSel((n) => Math.max(0, n - 1));
|
|
2631
|
+
setConfirmDel(null);
|
|
2632
|
+
} else if (key.downArrow) {
|
|
2633
|
+
setSel((n) => Math.min(rowsAll.length - 1, n + 1));
|
|
2634
|
+
setConfirmDel(null);
|
|
2635
|
+
} else if (key.return || inp === " ") activate(cur);
|
|
2636
|
+
else if (inp === "e" && cur.kind === "wh") {
|
|
2637
|
+
const next = EVENTS[(EVENTS.indexOf(cur.wh.events) + 1) % EVENTS.length];
|
|
2638
|
+
run(`webhook events \u2192 ${next}`, () => api.settings.updateWebhook(cur.wh.id, { events: next }), whQ.reload);
|
|
2639
|
+
} else if (inp === "e" && cur.kind === "ll-path") activate(cur);
|
|
2640
|
+
else if (inp === "a") {
|
|
2641
|
+
if (cur.kind === "wh" || cur.kind === "wh-add") {
|
|
2642
|
+
setInput("");
|
|
2643
|
+
setEditing("wh-url");
|
|
2644
|
+
} else if (cur.kind === "alert" || cur.kind === "alert-add") {
|
|
2645
|
+
setInput("");
|
|
2646
|
+
setEditing("alert-channel");
|
|
2647
|
+
}
|
|
2648
|
+
} else if (inp === "d" && (cur.kind === "wh" || cur.kind === "alert")) {
|
|
2649
|
+
const k = keyOf(cur);
|
|
2650
|
+
if (confirmDel !== k) {
|
|
2651
|
+
setConfirmDel(k);
|
|
2652
|
+
setMsg({ text: "press d again to delete", level: "bad" });
|
|
2653
|
+
return;
|
|
2654
|
+
}
|
|
2655
|
+
setConfirmDel(null);
|
|
2656
|
+
if (cur.kind === "wh") run("webhook deleted", () => api.settings.deleteWebhook(cur.wh.id), whQ.reload);
|
|
2657
|
+
else run("alert deleted", () => api.settings.deleteAlert(cur.rule.id), alertQ.reload);
|
|
2658
|
+
} else if (inp === "r") reloadAll();
|
|
2659
|
+
},
|
|
2660
|
+
{ isActive: focused && !editing }
|
|
2661
|
+
);
|
|
2662
|
+
const loading = localQ.loading && !localQ.data || whQ.loading && !whQ.data || alertQ.loading && !alertQ.data;
|
|
2663
|
+
const error = localQ.error ?? whQ.error ?? alertQ.error;
|
|
2664
|
+
const mark = (r) => keyOf(r) === keyOf(cur) && focused ? "\u25B8 " : " ";
|
|
2665
|
+
const onOff = (on) => on ? /* @__PURE__ */ jsx9(Text9, { color: theme.ok, children: "on " }) : /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "off" });
|
|
2666
|
+
const chanOf = (rule) => [...(rule.slackUrls ?? []).map((u) => "slack " + u), ...rule.emails ?? [], ...(rule.telegram ?? []).map((t) => "tg " + t)].join(", ") || "\u2014";
|
|
2667
|
+
const listBudget = Math.max(4, rows - 8);
|
|
2668
|
+
const start = Math.min(Math.max(0, selClamped - Math.floor(listBudget / 2)), Math.max(0, rowsAll.length - listBudget));
|
|
2669
|
+
const inWindow = (r) => {
|
|
2670
|
+
const i = rowsAll.findIndex((x) => keyOf(x) === keyOf(r));
|
|
2671
|
+
return i >= start && i < start + listBudget;
|
|
2672
|
+
};
|
|
2673
|
+
return /* @__PURE__ */ jsx9(DataView, { loading, error, children: /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
|
|
2674
|
+
/* @__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" }),
|
|
2675
|
+
editing ? /* @__PURE__ */ jsxs9(Box9, { children: [
|
|
2676
|
+
/* @__PURE__ */ jsx9(Text9, { color: theme.warn, children: editing === "path" ? "local log path: " : editing === "wh-url" ? "webhook url: " : "channel (slack url / email / telegram id): " }),
|
|
2677
|
+
/* @__PURE__ */ jsx9(TextInput5, { value: input, onChange: setInput, onSubmit: submitInput })
|
|
2678
|
+
] }) : msg ? /* @__PURE__ */ jsx9(Text9, { color: msg.level === "bad" ? theme.bad : theme.ok, children: truncate(msg.text, cols) }) : /* @__PURE__ */ jsx9(Text9, { children: " " }),
|
|
2679
|
+
inWindow(rowsAll[0]) || inWindow(rowsAll[1]) ? /* @__PURE__ */ jsxs9(Box9, { marginTop: 1, flexDirection: "column", children: [
|
|
2680
|
+
/* @__PURE__ */ jsxs9(Text9, { bold: true, color: theme.accentBright, children: [
|
|
2681
|
+
"LOCAL LOGS ",
|
|
2682
|
+
/* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "\u2014 hooks mirror every decision to a file on the agent machine" })
|
|
2683
|
+
] }),
|
|
2684
|
+
/* @__PURE__ */ jsxs9(Text9, { wrap: "truncate", children: [
|
|
2685
|
+
/* @__PURE__ */ jsx9(Text9, { color: keyOf(cur) === "ll-enabled" && focused ? theme.accentBright : theme.dim, children: mark(rowsAll[0]) }),
|
|
2686
|
+
/* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "enabled ".padEnd(10) }),
|
|
2687
|
+
onOff(!!local?.enabled),
|
|
2688
|
+
/* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: " enter toggles" })
|
|
2689
|
+
] }),
|
|
2690
|
+
/* @__PURE__ */ jsxs9(Text9, { wrap: "truncate", children: [
|
|
2691
|
+
/* @__PURE__ */ jsx9(Text9, { color: keyOf(cur) === "ll-path" && focused ? theme.accentBright : theme.dim, children: mark(rowsAll[1]) }),
|
|
2692
|
+
/* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "path ".padEnd(10) }),
|
|
2693
|
+
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" })
|
|
2694
|
+
] })
|
|
2695
|
+
] }) : null,
|
|
2696
|
+
/* @__PURE__ */ jsxs9(Box9, { marginTop: 1, flexDirection: "column", children: [
|
|
2697
|
+
/* @__PURE__ */ jsxs9(Text9, { bold: true, color: theme.accentBright, children: [
|
|
2698
|
+
"WEBHOOKS ",
|
|
2699
|
+
/* @__PURE__ */ jsxs9(Text9, { color: theme.dim, children: [
|
|
2700
|
+
"\u2014 POST every matching event to your endpoint (",
|
|
2701
|
+
webhooks.length,
|
|
2702
|
+
")"
|
|
2703
|
+
] })
|
|
2704
|
+
] }),
|
|
2705
|
+
webhooks.filter((wh) => inWindow({ kind: "wh", wh })).map((wh) => /* @__PURE__ */ jsxs9(Text9, { wrap: "truncate", children: [
|
|
2706
|
+
/* @__PURE__ */ jsx9(Text9, { color: keyOf(cur) === "wh:" + wh.id && focused ? theme.accentBright : theme.dim, children: mark({ kind: "wh", wh }) }),
|
|
2707
|
+
onOff(wh.enabled),
|
|
2708
|
+
/* @__PURE__ */ jsx9(Text9, { color: theme.warn, children: (" " + wh.events).padEnd(9) }),
|
|
2709
|
+
/* @__PURE__ */ jsx9(Text9, { color: theme.accent, children: truncate(wh.url, cols - 16) })
|
|
2710
|
+
] }, wh.id)),
|
|
2711
|
+
inWindow(rowsAll.find((r) => r.kind === "wh-add")) ? /* @__PURE__ */ jsxs9(Text9, { children: [
|
|
2712
|
+
/* @__PURE__ */ jsx9(Text9, { color: cur.kind === "wh-add" && focused ? theme.accentBright : theme.dim, children: mark({ kind: "wh-add" }) }),
|
|
2713
|
+
/* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "+ add webhook (enter)" })
|
|
2714
|
+
] }) : null
|
|
2715
|
+
] }),
|
|
2716
|
+
/* @__PURE__ */ jsxs9(Box9, { marginTop: 1, flexDirection: "column", children: [
|
|
2717
|
+
/* @__PURE__ */ jsxs9(Text9, { bold: true, color: theme.accentBright, children: [
|
|
2718
|
+
"ALERTS ",
|
|
2719
|
+
/* @__PURE__ */ jsxs9(Text9, { color: theme.dim, children: [
|
|
2720
|
+
"\u2014 notify on a signal spike (",
|
|
2721
|
+
alerts.length,
|
|
2722
|
+
")"
|
|
2723
|
+
] })
|
|
2724
|
+
] }),
|
|
2725
|
+
alerts.filter((rule) => inWindow({ kind: "alert", rule })).map((rule) => /* @__PURE__ */ jsxs9(Text9, { wrap: "truncate", children: [
|
|
2726
|
+
/* @__PURE__ */ jsx9(Text9, { color: keyOf(cur) === "alert:" + rule.id && focused ? theme.accentBright : theme.dim, children: mark({ kind: "alert", rule }) }),
|
|
2727
|
+
onOff(rule.enabled),
|
|
2728
|
+
/* @__PURE__ */ jsx9(Text9, { color: theme.warn, children: (" " + rule.signal).padEnd(11) }),
|
|
2729
|
+
/* @__PURE__ */ jsx9(Text9, { children: `\u2265${rule.threshold}/${rule.windowSeconds}s ` }),
|
|
2730
|
+
/* @__PURE__ */ jsx9(Text9, { color: theme.accent, children: truncate(chanOf(rule), cols - 26) })
|
|
2731
|
+
] }, rule.id)),
|
|
2732
|
+
inWindow(rowsAll[rowsAll.length - 1]) ? /* @__PURE__ */ jsxs9(Text9, { children: [
|
|
2733
|
+
/* @__PURE__ */ jsx9(Text9, { color: cur.kind === "alert-add" && focused ? theme.accentBright : theme.dim, children: mark({ kind: "alert-add" }) }),
|
|
2734
|
+
/* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "+ add alert (enter \u2014 one channel: slack url, email or telegram chat id)" })
|
|
2735
|
+
] }) : null
|
|
2736
|
+
] })
|
|
2737
|
+
] }) });
|
|
2738
|
+
}
|
|
2739
|
+
|
|
2740
|
+
// src/tui/App.tsx
|
|
2741
|
+
import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
2371
2742
|
function LiveHint() {
|
|
2372
|
-
return /* @__PURE__ */
|
|
2373
|
-
/* @__PURE__ */
|
|
2374
|
-
/* @__PURE__ */
|
|
2375
|
-
/* @__PURE__ */
|
|
2376
|
-
/* @__PURE__ */
|
|
2743
|
+
return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", children: [
|
|
2744
|
+
/* @__PURE__ */ jsx10(Text10, { color: theme.accentBright, bold: true, children: "\u25B6 REALTIME CONSOLE" }),
|
|
2745
|
+
/* @__PURE__ */ jsx10(Text10, { color: theme.dim, children: "Fullscreen live monitor \u2014 streaming tool calls, active charts," }),
|
|
2746
|
+
/* @__PURE__ */ jsx10(Text10, { color: theme.dim, children: "counters, DLP hits and denial alerts. Updates every 2s." }),
|
|
2747
|
+
/* @__PURE__ */ jsx10(Box10, { marginTop: 1, children: /* @__PURE__ */ jsxs10(Text10, { children: [
|
|
2377
2748
|
"press ",
|
|
2378
|
-
/* @__PURE__ */
|
|
2749
|
+
/* @__PURE__ */ jsx10(Text10, { color: theme.accent, children: "enter" }),
|
|
2379
2750
|
" to go live"
|
|
2380
2751
|
] }) })
|
|
2381
2752
|
] });
|
|
@@ -2387,28 +2758,29 @@ var SECTIONS = [
|
|
|
2387
2758
|
{ label: "Rate Limit", Panel: RateLimitPanel },
|
|
2388
2759
|
{ label: "DLP", Panel: DlpPanel },
|
|
2389
2760
|
{ label: "Stats", Panel: StatsPanel },
|
|
2390
|
-
{ label: "Audit", Panel: AuditPanel }
|
|
2761
|
+
{ label: "Audit", Panel: AuditPanel },
|
|
2762
|
+
{ label: "Settings", Panel: SettingsPanel }
|
|
2391
2763
|
];
|
|
2392
2764
|
var BANNER_HEX = ["#1432A0", "#2850BE", "#3C6ED7", "#5A8CE6", "#82AAF0", "#AAC8FA"];
|
|
2393
|
-
function Banner() {
|
|
2394
|
-
const wide =
|
|
2765
|
+
function Banner({ cols }) {
|
|
2766
|
+
const wide = cols >= 82;
|
|
2395
2767
|
if (!wide) {
|
|
2396
|
-
return /* @__PURE__ */
|
|
2397
|
-
/* @__PURE__ */
|
|
2398
|
-
/* @__PURE__ */
|
|
2768
|
+
return /* @__PURE__ */ jsxs10(Box10, { children: [
|
|
2769
|
+
/* @__PURE__ */ jsx10(Text10, { bold: true, color: theme.accentBright, children: "SolonGate" }),
|
|
2770
|
+
/* @__PURE__ */ jsx10(Text10, { color: theme.dim, children: " \u2014 security control center" })
|
|
2399
2771
|
] });
|
|
2400
2772
|
}
|
|
2401
|
-
return /* @__PURE__ */
|
|
2402
|
-
BANNER_FULL.map((line, i) => /* @__PURE__ */
|
|
2403
|
-
/* @__PURE__ */
|
|
2773
|
+
return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", children: [
|
|
2774
|
+
BANNER_FULL.map((line, i) => /* @__PURE__ */ jsx10(Text10, { bold: true, color: BANNER_HEX[i], children: line }, i)),
|
|
2775
|
+
/* @__PURE__ */ jsx10(Text10, { color: theme.dim, children: " security control center \xB7 manage policies, rate limits, DLP & more" })
|
|
2404
2776
|
] });
|
|
2405
2777
|
}
|
|
2406
2778
|
function App() {
|
|
2407
2779
|
const { exit } = useApp();
|
|
2408
|
-
const [section, setSection] =
|
|
2409
|
-
const [focus, setFocus] =
|
|
2410
|
-
const [help, setHelp] =
|
|
2411
|
-
|
|
2780
|
+
const [section, setSection] = useState9(0);
|
|
2781
|
+
const [focus, setFocus] = useState9("nav");
|
|
2782
|
+
const [help, setHelp] = useState9(false);
|
|
2783
|
+
useInput8((input, key) => {
|
|
2412
2784
|
if (help) {
|
|
2413
2785
|
setHelp(false);
|
|
2414
2786
|
return;
|
|
@@ -2427,45 +2799,46 @@ function App() {
|
|
|
2427
2799
|
else if (input === "q" && SECTIONS[section].label === "Live") exit();
|
|
2428
2800
|
}
|
|
2429
2801
|
});
|
|
2430
|
-
const cols
|
|
2431
|
-
const rows = process.stdout.rows ?? 30;
|
|
2802
|
+
const { cols, rows } = useTermSize();
|
|
2432
2803
|
const current = SECTIONS[section];
|
|
2433
|
-
if (help) return /* @__PURE__ */
|
|
2804
|
+
if (help) return /* @__PURE__ */ jsx10(HelpOverlay, { cols, rows });
|
|
2434
2805
|
if (current.label === "Live" && focus === "panel") {
|
|
2435
|
-
return /* @__PURE__ */
|
|
2806
|
+
return /* @__PURE__ */ jsx10(Box10, { flexDirection: "column", width: cols, height: rows, children: /* @__PURE__ */ jsx10(LivePanel, { active: true, focused: true }) });
|
|
2436
2807
|
}
|
|
2437
2808
|
const Panel = current.Panel;
|
|
2438
|
-
return /* @__PURE__ */
|
|
2439
|
-
/* @__PURE__ */
|
|
2440
|
-
/* @__PURE__ */
|
|
2441
|
-
/* @__PURE__ */
|
|
2442
|
-
/* @__PURE__ */
|
|
2809
|
+
return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", width: cols, height: rows, paddingX: 1, paddingTop: 1, children: [
|
|
2810
|
+
/* @__PURE__ */ jsx10(Banner, { cols }),
|
|
2811
|
+
/* @__PURE__ */ jsxs10(Box10, { marginTop: 1, flexGrow: 1, children: [
|
|
2812
|
+
/* @__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)) }),
|
|
2813
|
+
/* @__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
2814
|
] }),
|
|
2444
|
-
/* @__PURE__ */
|
|
2815
|
+
/* @__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
2816
|
] });
|
|
2446
2817
|
}
|
|
2447
2818
|
var HELP = [
|
|
2448
2819
|
["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)"]]],
|
|
2820
|
+
["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
2821
|
["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"]]]
|
|
2822
|
+
["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"]]],
|
|
2823
|
+
["Audit", [["\u2191\u2193", "select (list scrolls)"], ["enter", "full entry detail"], ["f / g", "decision / signal filter"], ["t / n / /", "tool / agent / search"], ["c", "clear filters"]]],
|
|
2824
|
+
["Settings", [["\u2191\u2193", "move"], ["enter / space", "toggle \xB7 edit \xB7 add"], ["e", "webhook events"], ["d d", "delete"], ["r", "refresh"]]]
|
|
2452
2825
|
];
|
|
2453
2826
|
function HelpOverlay({ cols, rows }) {
|
|
2454
|
-
return /* @__PURE__ */
|
|
2455
|
-
/* @__PURE__ */
|
|
2456
|
-
/* @__PURE__ */
|
|
2457
|
-
/* @__PURE__ */
|
|
2458
|
-
keys.map(([k, desc]) => /* @__PURE__ */
|
|
2459
|
-
/* @__PURE__ */
|
|
2460
|
-
/* @__PURE__ */
|
|
2827
|
+
return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", width: cols, height: rows, paddingX: 2, paddingTop: 1, children: [
|
|
2828
|
+
/* @__PURE__ */ jsx10(Text10, { bold: true, color: theme.accentBright, children: "SolonGate \u2014 keyboard shortcuts" }),
|
|
2829
|
+
/* @__PURE__ */ jsx10(Box10, { marginTop: 1, flexDirection: "column", children: HELP.map(([group, keys]) => /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", marginBottom: 1, children: [
|
|
2830
|
+
/* @__PURE__ */ jsx10(Text10, { bold: true, color: theme.accent, children: group }),
|
|
2831
|
+
keys.map(([k, desc]) => /* @__PURE__ */ jsxs10(Text10, { children: [
|
|
2832
|
+
/* @__PURE__ */ jsx10(Text10, { color: theme.accentBright, children: (" " + k).padEnd(16) }),
|
|
2833
|
+
/* @__PURE__ */ jsx10(Text10, { color: theme.dim, children: desc })
|
|
2461
2834
|
] }, k))
|
|
2462
2835
|
] }, group)) }),
|
|
2463
|
-
/* @__PURE__ */
|
|
2836
|
+
/* @__PURE__ */ jsx10(Text10, { color: theme.dim, children: "press any key to close" })
|
|
2464
2837
|
] });
|
|
2465
2838
|
}
|
|
2466
2839
|
|
|
2467
2840
|
// src/tui/index.tsx
|
|
2468
|
-
import { jsx as
|
|
2841
|
+
import { jsx as jsx11 } from "react/jsx-runtime";
|
|
2469
2842
|
async function launchTui() {
|
|
2470
2843
|
if (!process.stdout.isTTY || !process.stdin.isTTY) {
|
|
2471
2844
|
process.stderr.write(
|
|
@@ -2479,7 +2852,7 @@ async function launchTui() {
|
|
|
2479
2852
|
}
|
|
2480
2853
|
process.stdout.write("\x1B[?1049h\x1B[H");
|
|
2481
2854
|
try {
|
|
2482
|
-
const { waitUntilExit } = render(/* @__PURE__ */
|
|
2855
|
+
const { waitUntilExit } = render(/* @__PURE__ */ jsx11(App, {}));
|
|
2483
2856
|
await waitUntilExit();
|
|
2484
2857
|
} finally {
|
|
2485
2858
|
process.stdout.write("\x1B[?1049l");
|