@solongate/proxy 0.81.18 → 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 +598 -326
- package/dist/tui/hooks.d.ts +14 -0
- package/dist/tui/index.js +489 -227
- package/dist/tui/notify.d.ts +1 -3
- 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";
|
|
@@ -150,9 +169,9 @@ function KeyHints({ hints }) {
|
|
|
150
169
|
// src/tui/panels/Live.tsx
|
|
151
170
|
import { Box as Box2, Text as Text2, useInput } from "ink";
|
|
152
171
|
import TextInput from "ink-text-input";
|
|
153
|
-
import { closeSync, mkdirSync, openSync, readSync, statSync, writeFileSync
|
|
172
|
+
import { closeSync, mkdirSync, openSync, readSync, statSync, writeFileSync } from "fs";
|
|
154
173
|
import { homedir as homedir3 } from "os";
|
|
155
|
-
import { join as
|
|
174
|
+
import { join as join3 } from "path";
|
|
156
175
|
import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
|
|
157
176
|
|
|
158
177
|
// src/api-client/client.ts
|
|
@@ -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 = {};
|
|
@@ -489,138 +524,28 @@ function list4() {
|
|
|
489
524
|
var api = { policies: policies_exports, settings: settings_exports, stats: stats_exports, audit: audit_exports, agents: agents_exports, keys: keys_exports, mcp: mcp_exports };
|
|
490
525
|
|
|
491
526
|
// src/tui/notify.ts
|
|
492
|
-
import { spawn
|
|
493
|
-
|
|
494
|
-
import { join as join3 } from "path";
|
|
495
|
-
import { tmpdir } from "os";
|
|
496
|
-
var focusToken = null;
|
|
497
|
-
function captureFocusTarget() {
|
|
527
|
+
import { spawn } from "child_process";
|
|
528
|
+
function desktopNotify(title, msg) {
|
|
498
529
|
try {
|
|
499
530
|
if (process.platform === "linux") {
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
} catch {
|
|
505
|
-
focusToken = null;
|
|
506
|
-
}
|
|
507
|
-
}
|
|
531
|
+
const p = spawn("notify-send", ["-a", "SolonGate", title, msg], { stdio: "ignore", detached: true });
|
|
532
|
+
p.on("error", () => {
|
|
533
|
+
});
|
|
534
|
+
p.unref();
|
|
508
535
|
} else if (process.platform === "win32") {
|
|
509
|
-
const
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
536
|
+
const q = (s) => s.replace(/'/g, "''");
|
|
537
|
+
const ps = `Add-Type -AssemblyName System.Windows.Forms;Add-Type -AssemblyName System.Drawing;$n=New-Object System.Windows.Forms.NotifyIcon;$n.Icon=[System.Drawing.SystemIcons]::Information;$n.Visible=$true;$n.ShowBalloonTip(6000,'${q(title)}','${q(msg)}',[System.Windows.Forms.ToolTipIcon]::Warning);Start-Sleep -Milliseconds 6500;$n.Dispose()`;
|
|
538
|
+
const p = spawn("powershell", ["-NoProfile", "-NonInteractive", "-Command", ps], { stdio: "ignore", detached: true, windowsHide: true });
|
|
539
|
+
p.on("error", () => {
|
|
540
|
+
});
|
|
541
|
+
p.unref();
|
|
542
|
+
} else if (process.platform === "darwin") {
|
|
543
|
+
const esc = (s) => s.replace(/"/g, '\\"');
|
|
544
|
+
const p = spawn("osascript", ["-e", `display notification "${esc(msg)}" with title "${esc(title)}"`], { stdio: "ignore", detached: true });
|
|
545
|
+
p.on("error", () => {
|
|
546
|
+
});
|
|
547
|
+
p.unref();
|
|
515
548
|
}
|
|
516
|
-
} catch {
|
|
517
|
-
focusToken = null;
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
function desktopNotify(title, msg) {
|
|
521
|
-
try {
|
|
522
|
-
if (process.platform === "linux") linuxNotify(title, msg);
|
|
523
|
-
else if (process.platform === "win32") winNotify(title, msg);
|
|
524
|
-
else if (process.platform === "darwin") macNotify(title, msg);
|
|
525
|
-
} catch {
|
|
526
|
-
}
|
|
527
|
-
}
|
|
528
|
-
function linuxNotify(title, msg) {
|
|
529
|
-
let p;
|
|
530
|
-
try {
|
|
531
|
-
p = spawn("notify-send", ["--wait", "--action=default=Open", "-a", "SolonGate", title, msg], { stdio: ["ignore", "pipe", "ignore"] });
|
|
532
|
-
} catch {
|
|
533
|
-
return;
|
|
534
|
-
}
|
|
535
|
-
p.on("error", () => {
|
|
536
|
-
});
|
|
537
|
-
p.stdout?.on("data", (d) => {
|
|
538
|
-
if (String(d).trim() === "default") raiseLinuxWindow();
|
|
539
|
-
});
|
|
540
|
-
p.unref();
|
|
541
|
-
}
|
|
542
|
-
function raiseLinuxWindow() {
|
|
543
|
-
try {
|
|
544
|
-
process.stdout.write("\x1B[5t");
|
|
545
|
-
} catch {
|
|
546
|
-
}
|
|
547
|
-
if (!focusToken) return;
|
|
548
|
-
try {
|
|
549
|
-
const x = spawn("xdotool", ["windowactivate", focusToken], { stdio: "ignore" });
|
|
550
|
-
x.on("error", () => {
|
|
551
|
-
try {
|
|
552
|
-
const hex = "0x" + Number(focusToken).toString(16);
|
|
553
|
-
const w = spawn("wmctrl", ["-ia", hex], { stdio: "ignore" });
|
|
554
|
-
w.on("error", () => {
|
|
555
|
-
});
|
|
556
|
-
w.unref();
|
|
557
|
-
} catch {
|
|
558
|
-
}
|
|
559
|
-
});
|
|
560
|
-
x.unref();
|
|
561
|
-
} catch {
|
|
562
|
-
}
|
|
563
|
-
}
|
|
564
|
-
var winScriptPath = null;
|
|
565
|
-
function ensureWinScript() {
|
|
566
|
-
if (winScriptPath && existsSync2(winScriptPath)) return winScriptPath;
|
|
567
|
-
const p = join3(tmpdir(), "solongate-toast.ps1");
|
|
568
|
-
const script = [
|
|
569
|
-
"param([string]$Title,[string]$Msg,[string]$Hwnd)",
|
|
570
|
-
"Add-Type -AssemblyName System.Windows.Forms",
|
|
571
|
-
"Add-Type -AssemblyName System.Drawing",
|
|
572
|
-
`Add-Type -Name Win -Namespace Sg -MemberDefinition '[DllImport("user32.dll")]public static extern bool SetForegroundWindow(System.IntPtr h);[DllImport("user32.dll")]public static extern bool ShowWindow(System.IntPtr h,int c);'`,
|
|
573
|
-
"$ni=New-Object System.Windows.Forms.NotifyIcon",
|
|
574
|
-
"$ni.Icon=[System.Drawing.SystemIcons]::Information",
|
|
575
|
-
"$ni.Visible=$true",
|
|
576
|
-
"$script:clicked=$false",
|
|
577
|
-
"$ni.add_BalloonTipClicked({$script:clicked=$true})",
|
|
578
|
-
"$ni.ShowBalloonTip(8000,$Title,$Msg,[System.Windows.Forms.ToolTipIcon]::Warning)",
|
|
579
|
-
"$sw=[System.Diagnostics.Stopwatch]::StartNew()",
|
|
580
|
-
"while($sw.Elapsed.TotalSeconds -lt 9 -and -not $script:clicked){[System.Windows.Forms.Application]::DoEvents();Start-Sleep -Milliseconds 100}",
|
|
581
|
-
"if($script:clicked -and $Hwnd){try{$h=[System.IntPtr][int64]$Hwnd;[Sg.Win]::ShowWindow($h,9)|Out-Null;[Sg.Win]::SetForegroundWindow($h)|Out-Null}catch{}}",
|
|
582
|
-
"$ni.Visible=$false;$ni.Dispose()"
|
|
583
|
-
].join("\n");
|
|
584
|
-
try {
|
|
585
|
-
writeFileSync(p, script);
|
|
586
|
-
winScriptPath = p;
|
|
587
|
-
return p;
|
|
588
|
-
} catch {
|
|
589
|
-
return null;
|
|
590
|
-
}
|
|
591
|
-
}
|
|
592
|
-
function winNotify(title, msg) {
|
|
593
|
-
const script = ensureWinScript();
|
|
594
|
-
if (!script) return;
|
|
595
|
-
try {
|
|
596
|
-
const p = spawn("powershell", ["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-File", script, "-Title", title, "-Msg", msg, "-Hwnd", focusToken || ""], { stdio: "ignore", detached: true, windowsHide: true });
|
|
597
|
-
p.on("error", () => {
|
|
598
|
-
});
|
|
599
|
-
p.unref();
|
|
600
|
-
} catch {
|
|
601
|
-
}
|
|
602
|
-
}
|
|
603
|
-
function macBundleId() {
|
|
604
|
-
const prog = (process.env.TERM_PROGRAM || "").toLowerCase();
|
|
605
|
-
if (prog.includes("iterm")) return "com.googlecode.iterm2";
|
|
606
|
-
if (prog.includes("apple_terminal")) return "com.apple.Terminal";
|
|
607
|
-
if (prog.includes("vscode")) return "com.microsoft.VSCode";
|
|
608
|
-
return "com.apple.Terminal";
|
|
609
|
-
}
|
|
610
|
-
function macNotify(title, msg) {
|
|
611
|
-
try {
|
|
612
|
-
const p = spawn("terminal-notifier", ["-title", title, "-message", msg, "-activate", macBundleId(), "-sender", macBundleId()], { stdio: "ignore", detached: true });
|
|
613
|
-
p.on("error", () => {
|
|
614
|
-
try {
|
|
615
|
-
const esc = (s) => s.replace(/"/g, '\\"');
|
|
616
|
-
const a = spawn("osascript", ["-e", `display notification "${esc(msg)}" with title "${esc(title)}"`], { stdio: "ignore", detached: true });
|
|
617
|
-
a.on("error", () => {
|
|
618
|
-
});
|
|
619
|
-
a.unref();
|
|
620
|
-
} catch {
|
|
621
|
-
}
|
|
622
|
-
});
|
|
623
|
-
p.unref();
|
|
624
549
|
} catch {
|
|
625
550
|
}
|
|
626
551
|
}
|
|
@@ -664,6 +589,24 @@ function usePoll(reload, intervalMs, enabled = true) {
|
|
|
664
589
|
return () => clearInterval(t);
|
|
665
590
|
}, [reload, intervalMs, enabled]);
|
|
666
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
|
+
}
|
|
667
610
|
|
|
668
611
|
// src/tui/panels/Live.tsx
|
|
669
612
|
import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
@@ -671,8 +614,8 @@ var CONFIG = loadConfig();
|
|
|
671
614
|
var SPIN = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
672
615
|
var BG = "#12234f";
|
|
673
616
|
var DIM_FLOOR = "#233457";
|
|
674
|
-
var LOCAL_LOG =
|
|
675
|
-
var RING =
|
|
617
|
+
var LOCAL_LOG = join3(homedir3(), ".solongate", "local-logs", "solongate-audit.jsonl");
|
|
618
|
+
var RING = join3(process.cwd(), ".solongate", ".eval-ring.jsonl");
|
|
676
619
|
var hhmmss = (ts) => {
|
|
677
620
|
const d = new Date(ts);
|
|
678
621
|
return Number.isNaN(d.getTime()) ? "--:--:--" : d.toTimeString().slice(0, 8);
|
|
@@ -824,6 +767,9 @@ function LivePanel({ active: active2 }) {
|
|
|
824
767
|
const [detail, setDetail] = useState2(null);
|
|
825
768
|
const [detailCloud, setDetailCloud] = useState2([]);
|
|
826
769
|
const [detailScroll, setDetailScroll] = useState2(0);
|
|
770
|
+
const [inspect, setInspect] = useState2(null);
|
|
771
|
+
const [inspectScroll, setInspectScroll] = useState2(0);
|
|
772
|
+
const inspectFromRef = useRef2("stream");
|
|
827
773
|
const seenRef = useRef2(/* @__PURE__ */ new Set());
|
|
828
774
|
const lastLocalTs = useRef2(0);
|
|
829
775
|
const pausedUntil = useRef2(0);
|
|
@@ -1029,8 +975,7 @@ function LivePanel({ active: active2 }) {
|
|
|
1029
975
|
else if (e.burst) fireAlert("sec:" + e.id, "Rate-limit burst", `BURST ${e.tool}${who}`, "warn");
|
|
1030
976
|
}
|
|
1031
977
|
}, [tick, active2, fireAlert]);
|
|
1032
|
-
const cols
|
|
1033
|
-
const rows = process.stdout.rows ?? 30;
|
|
978
|
+
const { cols, rows } = useTermSize();
|
|
1034
979
|
const ins = insights.data ?? {};
|
|
1035
980
|
const spin = SPIN[tick % SPIN.length];
|
|
1036
981
|
const nowMs = Date.now();
|
|
@@ -1176,8 +1121,18 @@ function LivePanel({ active: active2 }) {
|
|
|
1176
1121
|
setEditingSearch(true);
|
|
1177
1122
|
return;
|
|
1178
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
|
+
}
|
|
1179
1134
|
if (mode === "detail") {
|
|
1180
|
-
const maxD = Math.max(0, detailEntries.length -
|
|
1135
|
+
const maxD = Math.max(0, detailEntries.length - 1);
|
|
1181
1136
|
if (key.leftArrow) {
|
|
1182
1137
|
setMode("stream");
|
|
1183
1138
|
setDetail(null);
|
|
@@ -1185,6 +1140,15 @@ function LivePanel({ active: active2 }) {
|
|
|
1185
1140
|
else if (key.downArrow) setDetailScroll((n) => Math.min(maxD, n + 1));
|
|
1186
1141
|
else if (key.pageUp) setDetailScroll((n) => Math.max(0, n - 10));
|
|
1187
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
|
+
}
|
|
1188
1152
|
return;
|
|
1189
1153
|
}
|
|
1190
1154
|
if (mode === "pick") {
|
|
@@ -1206,6 +1170,13 @@ function LivePanel({ active: active2 }) {
|
|
|
1206
1170
|
if (input === "f") {
|
|
1207
1171
|
setFilter((f) => FILTERS[(FILTERS.indexOf(f) + 1) % FILTERS.length]);
|
|
1208
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
|
+
}
|
|
1209
1180
|
} else if (input === "s") {
|
|
1210
1181
|
setPickIdx(0);
|
|
1211
1182
|
setMode("pick");
|
|
@@ -1215,10 +1186,10 @@ function LivePanel({ active: active2 }) {
|
|
|
1215
1186
|
else if (input === "x") toggleSignal("dlp");
|
|
1216
1187
|
else if (input === "r") toggleSignal("ratelimit");
|
|
1217
1188
|
else if (input === "e") {
|
|
1218
|
-
const file =
|
|
1189
|
+
const file = join3(homedir3(), ".solongate", "live-export.jsonl");
|
|
1219
1190
|
try {
|
|
1220
|
-
mkdirSync(
|
|
1221
|
-
|
|
1191
|
+
mkdirSync(join3(homedir3(), ".solongate"), { recursive: true });
|
|
1192
|
+
writeFileSync(file, visibleDesc.map((x) => JSON.stringify(x)).join("\n") + "\n");
|
|
1222
1193
|
setActionMsg({ text: `\u2713 exported ${visibleDesc.length} lines \u2192 ${file}`, level: "ok", until: Date.now() + 6e3 });
|
|
1223
1194
|
} catch (err) {
|
|
1224
1195
|
setActionMsg({ text: "\u2717 export failed: " + (err instanceof Error ? err.message : String(err)), level: "bad", until: Date.now() + 6e3 });
|
|
@@ -1268,6 +1239,52 @@ function LivePanel({ active: active2 }) {
|
|
|
1268
1239
|
/* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: ` ${spin} up ${fmtUp(nowMs - startRef.current)} \xB7 ${hhmmss(nowMs)} \xB7 api ${latNow}ms ` }),
|
|
1269
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 " })
|
|
1270
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
|
+
}
|
|
1271
1288
|
if (mode === "detail" && detail) {
|
|
1272
1289
|
const d = detailEntries;
|
|
1273
1290
|
const allow = d.filter((e) => e.decision === "ALLOW").length;
|
|
@@ -1287,9 +1304,10 @@ function LivePanel({ active: active2 }) {
|
|
|
1287
1304
|
const last = d[0]?.at;
|
|
1288
1305
|
const tlRows = Math.max(4, rows - 10);
|
|
1289
1306
|
const tlBody = Math.max(3, tlRows - 1);
|
|
1290
|
-
const
|
|
1291
|
-
const
|
|
1292
|
-
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);
|
|
1293
1311
|
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingX: 1, children: [
|
|
1294
1312
|
titleBar,
|
|
1295
1313
|
/* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
@@ -1327,16 +1345,16 @@ function LivePanel({ active: active2 }) {
|
|
|
1327
1345
|
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: perms.map(([p, c2]) => `${p}\xD7${c2}`).join(" ") || "\u2014" }),
|
|
1328
1346
|
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502" })
|
|
1329
1347
|
] }),
|
|
1330
|
-
/* @__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 }),
|
|
1331
1349
|
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", height: tlRows, overflow: "hidden", children: [
|
|
1332
1350
|
searchRow,
|
|
1333
1351
|
tl.length === 0 ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: detailCloud.length === 0 ? spin + " loading history\u2026" : "no entries" }) : null,
|
|
1334
|
-
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))
|
|
1335
1353
|
] }),
|
|
1336
1354
|
/* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
1337
1355
|
/* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", bold: true, children: " SESSION " }),
|
|
1338
1356
|
/* @__PURE__ */ jsx2(Text2, { backgroundColor: "#0b1530", color: "#4f6db8", children: ` ${detail.id} ` }),
|
|
1339
|
-
/* @__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 " })
|
|
1340
1358
|
] })
|
|
1341
1359
|
] });
|
|
1342
1360
|
}
|
|
@@ -1449,7 +1467,7 @@ function LivePanel({ active: active2 }) {
|
|
|
1449
1467
|
PaneTitle,
|
|
1450
1468
|
{
|
|
1451
1469
|
label: "TOOL STREAM",
|
|
1452
|
-
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`,
|
|
1453
1471
|
width: innerW
|
|
1454
1472
|
}
|
|
1455
1473
|
),
|
|
@@ -1457,7 +1475,7 @@ function LivePanel({ active: active2 }) {
|
|
|
1457
1475
|
searchRow,
|
|
1458
1476
|
localOn === false ? /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
1459
1477
|
/* @__PURE__ */ jsx2(Text2, { color: theme.warn, children: "local logs off" }),
|
|
1460
|
-
/* @__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" })
|
|
1461
1479
|
] }) : null,
|
|
1462
1480
|
windowed.length === 0 ? /* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
|
|
1463
1481
|
spin,
|
|
@@ -1468,7 +1486,7 @@ function LivePanel({ active: active2 }) {
|
|
|
1468
1486
|
/* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
1469
1487
|
/* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", bold: true, children: " LIVE " }),
|
|
1470
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"} ` }),
|
|
1471
|
-
/* @__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 " })
|
|
1472
1490
|
] })
|
|
1473
1491
|
] });
|
|
1474
1492
|
}
|
|
@@ -2225,10 +2243,12 @@ function StatsPanel({ active: active2 }) {
|
|
|
2225
2243
|
import { Box as Box7, Text as Text7, useInput as useInput5 } from "ink";
|
|
2226
2244
|
import TextInput4 from "ink-text-input";
|
|
2227
2245
|
import { useState as useState6 } from "react";
|
|
2228
|
-
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";
|
|
2229
2247
|
var DECISIONS = [void 0, "DENY", "ALLOW"];
|
|
2230
2248
|
var SIGNALS = [void 0, "dlp", "ratelimit"];
|
|
2249
|
+
var FETCH_LIMIT = 500;
|
|
2231
2250
|
function AuditPanel({ active: active2, focused }) {
|
|
2251
|
+
const { cols, rows } = usePanelSize();
|
|
2232
2252
|
const [di, setDi] = useState6(0);
|
|
2233
2253
|
const [gi, setGi] = useState6(0);
|
|
2234
2254
|
const [tool, setTool] = useState6("");
|
|
@@ -2236,6 +2256,7 @@ function AuditPanel({ active: active2, focused }) {
|
|
|
2236
2256
|
const [search, setSearch] = useState6("");
|
|
2237
2257
|
const [sel, setSel] = useState6(0);
|
|
2238
2258
|
const [view, setView] = useState6("list");
|
|
2259
|
+
const [detailScroll, setDetailScroll] = useState6(0);
|
|
2239
2260
|
const [editing, setEditing] = useState6(null);
|
|
2240
2261
|
const query = {
|
|
2241
2262
|
filter: DECISIONS[di],
|
|
@@ -2243,7 +2264,7 @@ function AuditPanel({ active: active2, focused }) {
|
|
|
2243
2264
|
tool: tool || void 0,
|
|
2244
2265
|
agent_name: agent || void 0,
|
|
2245
2266
|
search: search || void 0,
|
|
2246
|
-
limit:
|
|
2267
|
+
limit: FETCH_LIMIT
|
|
2247
2268
|
};
|
|
2248
2269
|
const auditQ = useLoader(() => api.audit.list(query), [di, gi, tool, agent, search]);
|
|
2249
2270
|
usePoll(auditQ.reload, 6e3, active2 && view === "list" && !editing);
|
|
@@ -2257,15 +2278,29 @@ function AuditPanel({ active: active2, focused }) {
|
|
|
2257
2278
|
(input, key) => {
|
|
2258
2279
|
if (view === "detail") {
|
|
2259
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);
|
|
2260
2285
|
return;
|
|
2261
2286
|
}
|
|
2287
|
+
const clearSel = () => setSel(0);
|
|
2262
2288
|
if (key.upArrow) setSel((n) => Math.max(0, n - 1));
|
|
2263
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));
|
|
2264
2292
|
else if (key.return || key.rightArrow) {
|
|
2265
|
-
if (current)
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
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");
|
|
2269
2304
|
else if (input === "n") setEditing("agent");
|
|
2270
2305
|
else if (input === "/") setEditing("search");
|
|
2271
2306
|
else if (input === "c") {
|
|
@@ -2274,49 +2309,64 @@ function AuditPanel({ active: active2, focused }) {
|
|
|
2274
2309
|
setTool("");
|
|
2275
2310
|
setAgent("");
|
|
2276
2311
|
setSearch("");
|
|
2312
|
+
clearSel();
|
|
2277
2313
|
}
|
|
2278
2314
|
},
|
|
2279
2315
|
{ isActive: focused && !editing }
|
|
2280
2316
|
);
|
|
2281
2317
|
if (view === "detail" && current) {
|
|
2282
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);
|
|
2283
2328
|
return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
|
|
2284
2329
|
/* @__PURE__ */ jsxs7(Box7, { children: [
|
|
2285
2330
|
/* @__PURE__ */ jsx7(Text7, { color: decisionColor(current.decision), bold: true, children: current.decision }),
|
|
2286
2331
|
/* @__PURE__ */ jsx7(Text7, { color: theme.accent, children: " " + current.tool_name }),
|
|
2287
2332
|
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " " + current.permission + " \xB7 " + current.trust_level })
|
|
2288
2333
|
] }),
|
|
2289
|
-
/* @__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)),
|
|
2290
2338
|
/* @__PURE__ */ jsx7(Detail, { label: "rule", value: current.matched_rule_id ?? "\u2014" }),
|
|
2291
2339
|
/* @__PURE__ */ jsx7(Detail, { label: "agent", value: current.agent_name ?? "\u2014" }),
|
|
2292
2340
|
/* @__PURE__ */ jsx7(Detail, { label: "session", value: current.session_id ?? "\u2014" }),
|
|
2293
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 }),
|
|
2294
|
-
/* @__PURE__ */ jsx7(Detail, { label: "when", value: new Date(current.created_at).toLocaleString() }),
|
|
2295
|
-
|
|
2296
|
-
/* @__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: [
|
|
2297
2346
|
/* @__PURE__ */ jsxs7(Text7, { color: theme.dim, children: [
|
|
2298
|
-
"Session ",
|
|
2299
|
-
|
|
2347
|
+
"Session \xB7 ",
|
|
2348
|
+
sessionCalls.length,
|
|
2349
|
+
" calls"
|
|
2300
2350
|
] }),
|
|
2301
|
-
|
|
2351
|
+
/* @__PURE__ */ jsx7(
|
|
2302
2352
|
Table,
|
|
2303
2353
|
{
|
|
2304
2354
|
columns: [
|
|
2305
2355
|
{ header: "DECISION", width: 9 },
|
|
2306
2356
|
{ header: "TOOL", width: 20 },
|
|
2307
|
-
{ header: "REASON", width:
|
|
2357
|
+
{ header: "REASON", width: Math.max(16, cols - 48) },
|
|
2308
2358
|
{ header: "WHEN", width: 6 }
|
|
2309
2359
|
],
|
|
2310
|
-
rows: sessionCalls.slice(0,
|
|
2360
|
+
rows: sessionCalls.slice(0, Math.max(1, sessionRows - 2)).map((e) => [
|
|
2311
2361
|
{ value: e.decision, color: decisionColor(e.decision) },
|
|
2312
2362
|
{ value: truncate(e.tool_name, 20), color: theme.accent },
|
|
2313
|
-
{ value: truncate(e.reason ?? "\u2014",
|
|
2363
|
+
{ value: truncate(e.reason ?? "\u2014", Math.max(16, cols - 48)) },
|
|
2314
2364
|
{ value: ago(e.created_at), dim: true }
|
|
2315
2365
|
])
|
|
2316
2366
|
}
|
|
2317
|
-
)
|
|
2318
|
-
] }),
|
|
2319
|
-
/* @__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" })
|
|
2320
2370
|
] });
|
|
2321
2371
|
}
|
|
2322
2372
|
const chip = (label, val, on) => /* @__PURE__ */ jsxs7(Text7, { children: [
|
|
@@ -2327,6 +2377,13 @@ function AuditPanel({ active: active2, focused }) {
|
|
|
2327
2377
|
/* @__PURE__ */ jsx7(Text7, { color: on ? theme.accentBright : theme.dim, children: val }),
|
|
2328
2378
|
/* @__PURE__ */ jsx7(Text7, { children: " " })
|
|
2329
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));
|
|
2330
2387
|
return /* @__PURE__ */ jsx7(DataView, { loading: auditQ.loading && !auditQ.data, error: auditQ.error, children: /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
|
|
2331
2388
|
/* @__PURE__ */ jsxs7(Box7, { children: [
|
|
2332
2389
|
chip("dec", DECISIONS[di] ?? "all", di !== 0),
|
|
@@ -2335,8 +2392,8 @@ function AuditPanel({ active: active2, focused }) {
|
|
|
2335
2392
|
chip("agent", agent || "\xB7", !!agent),
|
|
2336
2393
|
chip("search", search || "\xB7", !!search)
|
|
2337
2394
|
] }),
|
|
2338
|
-
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: focused ? "\u2191\u2193 \xB7 enter
|
|
2339
|
-
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: [
|
|
2340
2397
|
/* @__PURE__ */ jsxs7(Text7, { color: theme.warn, children: [
|
|
2341
2398
|
editing,
|
|
2342
2399
|
": "
|
|
@@ -2345,16 +2402,15 @@ function AuditPanel({ active: active2, focused }) {
|
|
|
2345
2402
|
TextInput4,
|
|
2346
2403
|
{
|
|
2347
2404
|
value: editing === "tool" ? tool : editing === "agent" ? agent : search,
|
|
2348
|
-
onChange:
|
|
2405
|
+
onChange: (v) => {
|
|
2406
|
+
(editing === "tool" ? setTool : editing === "agent" ? setAgent : setSearch)(v);
|
|
2407
|
+
setSel(0);
|
|
2408
|
+
},
|
|
2349
2409
|
onSubmit: () => setEditing(null)
|
|
2350
2410
|
}
|
|
2351
2411
|
)
|
|
2352
2412
|
] }) : null,
|
|
2353
|
-
/* @__PURE__ */ jsx7(
|
|
2354
|
-
auditQ.data?.total ?? 0,
|
|
2355
|
-
" matched \xB7 showing ",
|
|
2356
|
-
Math.min(entries.length, 12)
|
|
2357
|
-
] }) }),
|
|
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` : ""}` }),
|
|
2358
2414
|
/* @__PURE__ */ jsx7(
|
|
2359
2415
|
Table,
|
|
2360
2416
|
{
|
|
@@ -2363,22 +2419,22 @@ function AuditPanel({ active: active2, focused }) {
|
|
|
2363
2419
|
{ header: "DECISION", width: 9 },
|
|
2364
2420
|
{ header: "TOOL", width: 18 },
|
|
2365
2421
|
{ header: "AGENT", width: 14 },
|
|
2366
|
-
{ header: "REASON", width:
|
|
2422
|
+
{ header: "REASON", width: reasonW },
|
|
2367
2423
|
{ header: "DLP", width: 4 },
|
|
2368
2424
|
{ header: "WHEN", width: 6 }
|
|
2369
2425
|
],
|
|
2370
|
-
rows:
|
|
2426
|
+
rows: windowed.map((e, i) => rowFor(e, start + i === selClamped && focused, reasonW))
|
|
2371
2427
|
}
|
|
2372
2428
|
)
|
|
2373
2429
|
] }) });
|
|
2374
2430
|
}
|
|
2375
|
-
function rowFor(e, active2) {
|
|
2431
|
+
function rowFor(e, active2, reasonW) {
|
|
2376
2432
|
return [
|
|
2377
2433
|
{ value: active2 ? "\u25B8" : "", color: theme.accentBright },
|
|
2378
2434
|
{ value: e.decision, color: decisionColor(e.decision) },
|
|
2379
2435
|
{ value: truncate(e.tool_name, 18), color: theme.accent },
|
|
2380
2436
|
{ value: truncate(e.agent_name ?? "\u2014", 14), dim: true },
|
|
2381
|
-
{ value: truncate(e.reason ?? "\u2014",
|
|
2437
|
+
{ value: truncate(e.reason ?? "\u2014", reasonW) },
|
|
2382
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 },
|
|
2383
2439
|
{ value: ago(e.created_at), dim: true }
|
|
2384
2440
|
];
|
|
@@ -2386,14 +2442,14 @@ function rowFor(e, active2) {
|
|
|
2386
2442
|
function Detail({ label, value, color }) {
|
|
2387
2443
|
return /* @__PURE__ */ jsxs7(Box7, { children: [
|
|
2388
2444
|
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: label.padEnd(9) }),
|
|
2389
|
-
/* @__PURE__ */ jsx7(Text7, { color, children: value })
|
|
2445
|
+
/* @__PURE__ */ jsx7(Text7, { color, wrap: "truncate", children: value })
|
|
2390
2446
|
] });
|
|
2391
2447
|
}
|
|
2392
2448
|
|
|
2393
2449
|
// src/tui/panels/Agents.tsx
|
|
2394
2450
|
import { Box as Box8, Text as Text8, useInput as useInput6 } from "ink";
|
|
2395
2451
|
import { useState as useState7 } from "react";
|
|
2396
|
-
import { Fragment as
|
|
2452
|
+
import { Fragment as Fragment5, jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
2397
2453
|
var statusColor = (s) => s === "active" ? theme.ok : s === "idle" ? theme.warn : theme.dim;
|
|
2398
2454
|
var sevColor = (s) => s === "high" ? theme.bad : s === "medium" ? theme.warn : theme.dim;
|
|
2399
2455
|
function Bar({ label, value, max = 100, width = 16, color = theme.accent }) {
|
|
@@ -2439,7 +2495,7 @@ function AgentsPanel({ focused }) {
|
|
|
2439
2495
|
/* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: `t${a.trust_score}` })
|
|
2440
2496
|
] }, a.session_id))
|
|
2441
2497
|
] }),
|
|
2442
|
-
/* @__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: [
|
|
2443
2499
|
/* @__PURE__ */ jsxs8(Box8, { children: [
|
|
2444
2500
|
/* @__PURE__ */ jsx8(Text8, { bold: true, color: theme.accentBright, children: truncate(selected.agent_name ?? selected.agent_id ?? "?", 22) }),
|
|
2445
2501
|
/* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: ` trust ${base?.trustScore ?? "\u2014"}/100 \xB7 ${base?.character ?? ""}` })
|
|
@@ -2476,16 +2532,221 @@ function AgentsPanel({ focused }) {
|
|
|
2476
2532
|
] }) });
|
|
2477
2533
|
}
|
|
2478
2534
|
|
|
2479
|
-
// 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";
|
|
2480
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";
|
|
2481
2742
|
function LiveHint() {
|
|
2482
|
-
return /* @__PURE__ */
|
|
2483
|
-
/* @__PURE__ */
|
|
2484
|
-
/* @__PURE__ */
|
|
2485
|
-
/* @__PURE__ */
|
|
2486
|
-
/* @__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: [
|
|
2487
2748
|
"press ",
|
|
2488
|
-
/* @__PURE__ */
|
|
2749
|
+
/* @__PURE__ */ jsx10(Text10, { color: theme.accent, children: "enter" }),
|
|
2489
2750
|
" to go live"
|
|
2490
2751
|
] }) })
|
|
2491
2752
|
] });
|
|
@@ -2497,28 +2758,29 @@ var SECTIONS = [
|
|
|
2497
2758
|
{ label: "Rate Limit", Panel: RateLimitPanel },
|
|
2498
2759
|
{ label: "DLP", Panel: DlpPanel },
|
|
2499
2760
|
{ label: "Stats", Panel: StatsPanel },
|
|
2500
|
-
{ label: "Audit", Panel: AuditPanel }
|
|
2761
|
+
{ label: "Audit", Panel: AuditPanel },
|
|
2762
|
+
{ label: "Settings", Panel: SettingsPanel }
|
|
2501
2763
|
];
|
|
2502
2764
|
var BANNER_HEX = ["#1432A0", "#2850BE", "#3C6ED7", "#5A8CE6", "#82AAF0", "#AAC8FA"];
|
|
2503
|
-
function Banner() {
|
|
2504
|
-
const wide =
|
|
2765
|
+
function Banner({ cols }) {
|
|
2766
|
+
const wide = cols >= 82;
|
|
2505
2767
|
if (!wide) {
|
|
2506
|
-
return /* @__PURE__ */
|
|
2507
|
-
/* @__PURE__ */
|
|
2508
|
-
/* @__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" })
|
|
2509
2771
|
] });
|
|
2510
2772
|
}
|
|
2511
|
-
return /* @__PURE__ */
|
|
2512
|
-
BANNER_FULL.map((line, i) => /* @__PURE__ */
|
|
2513
|
-
/* @__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" })
|
|
2514
2776
|
] });
|
|
2515
2777
|
}
|
|
2516
2778
|
function App() {
|
|
2517
2779
|
const { exit } = useApp();
|
|
2518
|
-
const [section, setSection] =
|
|
2519
|
-
const [focus, setFocus] =
|
|
2520
|
-
const [help, setHelp] =
|
|
2521
|
-
|
|
2780
|
+
const [section, setSection] = useState9(0);
|
|
2781
|
+
const [focus, setFocus] = useState9("nav");
|
|
2782
|
+
const [help, setHelp] = useState9(false);
|
|
2783
|
+
useInput8((input, key) => {
|
|
2522
2784
|
if (help) {
|
|
2523
2785
|
setHelp(false);
|
|
2524
2786
|
return;
|
|
@@ -2537,45 +2799,46 @@ function App() {
|
|
|
2537
2799
|
else if (input === "q" && SECTIONS[section].label === "Live") exit();
|
|
2538
2800
|
}
|
|
2539
2801
|
});
|
|
2540
|
-
const cols
|
|
2541
|
-
const rows = process.stdout.rows ?? 30;
|
|
2802
|
+
const { cols, rows } = useTermSize();
|
|
2542
2803
|
const current = SECTIONS[section];
|
|
2543
|
-
if (help) return /* @__PURE__ */
|
|
2804
|
+
if (help) return /* @__PURE__ */ jsx10(HelpOverlay, { cols, rows });
|
|
2544
2805
|
if (current.label === "Live" && focus === "panel") {
|
|
2545
|
-
return /* @__PURE__ */
|
|
2806
|
+
return /* @__PURE__ */ jsx10(Box10, { flexDirection: "column", width: cols, height: rows, children: /* @__PURE__ */ jsx10(LivePanel, { active: true, focused: true }) });
|
|
2546
2807
|
}
|
|
2547
2808
|
const Panel = current.Panel;
|
|
2548
|
-
return /* @__PURE__ */
|
|
2549
|
-
/* @__PURE__ */
|
|
2550
|
-
/* @__PURE__ */
|
|
2551
|
-
/* @__PURE__ */
|
|
2552
|
-
/* @__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" }) })
|
|
2553
2814
|
] }),
|
|
2554
|
-
/* @__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"]] }) })
|
|
2555
2816
|
] });
|
|
2556
2817
|
}
|
|
2557
2818
|
var HELP = [
|
|
2558
2819
|
["Global", [["\u2191\u2193", "move between sections"], ["\u2192 / enter", "open a section"], ["\u2190 / esc", "back to the menu"], ["?", "this help"], ["q", "quit"]]],
|
|
2559
|
-
["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)"]]],
|
|
2560
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"]]],
|
|
2561
|
-
["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"]]]
|
|
2562
2825
|
];
|
|
2563
2826
|
function HelpOverlay({ cols, rows }) {
|
|
2564
|
-
return /* @__PURE__ */
|
|
2565
|
-
/* @__PURE__ */
|
|
2566
|
-
/* @__PURE__ */
|
|
2567
|
-
/* @__PURE__ */
|
|
2568
|
-
keys.map(([k, desc]) => /* @__PURE__ */
|
|
2569
|
-
/* @__PURE__ */
|
|
2570
|
-
/* @__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 })
|
|
2571
2834
|
] }, k))
|
|
2572
2835
|
] }, group)) }),
|
|
2573
|
-
/* @__PURE__ */
|
|
2836
|
+
/* @__PURE__ */ jsx10(Text10, { color: theme.dim, children: "press any key to close" })
|
|
2574
2837
|
] });
|
|
2575
2838
|
}
|
|
2576
2839
|
|
|
2577
2840
|
// src/tui/index.tsx
|
|
2578
|
-
import { jsx as
|
|
2841
|
+
import { jsx as jsx11 } from "react/jsx-runtime";
|
|
2579
2842
|
async function launchTui() {
|
|
2580
2843
|
if (!process.stdout.isTTY || !process.stdin.isTTY) {
|
|
2581
2844
|
process.stderr.write(
|
|
@@ -2587,10 +2850,9 @@ async function launchTui() {
|
|
|
2587
2850
|
process.stderr.write("Not logged in. Run `solongate login` first.\n");
|
|
2588
2851
|
return;
|
|
2589
2852
|
}
|
|
2590
|
-
captureFocusTarget();
|
|
2591
2853
|
process.stdout.write("\x1B[?1049h\x1B[H");
|
|
2592
2854
|
try {
|
|
2593
|
-
const { waitUntilExit } = render(/* @__PURE__ */
|
|
2855
|
+
const { waitUntilExit } = render(/* @__PURE__ */ jsx11(App, {}));
|
|
2594
2856
|
await waitUntilExit();
|
|
2595
2857
|
} finally {
|
|
2596
2858
|
process.stdout.write("\x1B[?1049l");
|