@solongate/proxy 0.81.23 → 0.81.25
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/index.js +561 -533
- package/dist/tui/hooks.d.ts +3 -0
- package/dist/tui/index.js +505 -462
- package/dist/tui/local-log.d.ts +22 -0
- package/package.json +1 -1
- package/dist/tui/panels/Agents.d.ts +0 -4
- package/dist/tui/panels/Stats.d.ts +0 -3
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 Box8, Text as Text8, useApp, useInput as useInput7 } from "ink";
|
|
12
|
+
import { useState as useState8 } from "react";
|
|
13
13
|
|
|
14
14
|
// src/cli-utils.ts
|
|
15
15
|
var c = {
|
|
@@ -142,7 +142,7 @@ function DataView({
|
|
|
142
142
|
"\u2717 ",
|
|
143
143
|
error
|
|
144
144
|
] });
|
|
145
|
-
if (loading) return /* @__PURE__ */ jsx(Text, { color: theme.
|
|
145
|
+
if (loading) return /* @__PURE__ */ jsx(Text, { color: theme.warn, children: "\u27F3 loading\u2026" });
|
|
146
146
|
if (empty) return /* @__PURE__ */ jsx(Text, { color: theme.dim, children: emptyText ?? "No data." });
|
|
147
147
|
return /* @__PURE__ */ jsx(Fragment, { children });
|
|
148
148
|
}
|
|
@@ -169,15 +169,50 @@ function KeyHints({ hints }) {
|
|
|
169
169
|
// src/tui/panels/Live.tsx
|
|
170
170
|
import { Box as Box2, Text as Text2, useInput } from "ink";
|
|
171
171
|
import TextInput from "ink-text-input";
|
|
172
|
-
import {
|
|
173
|
-
import { homedir as
|
|
174
|
-
import { join as
|
|
172
|
+
import { mkdirSync, writeFileSync } from "fs";
|
|
173
|
+
import { homedir as homedir4 } from "os";
|
|
174
|
+
import { join as join4 } from "path";
|
|
175
|
+
|
|
176
|
+
// src/tui/local-log.ts
|
|
177
|
+
import { closeSync, openSync, readSync, statSync } from "fs";
|
|
178
|
+
import { homedir as homedir2 } from "os";
|
|
179
|
+
import { join as join2 } from "path";
|
|
180
|
+
var LOCAL_LOG = join2(homedir2(), ".solongate", "local-logs", "solongate-audit.jsonl");
|
|
181
|
+
function tailLines(file, maxBytes = 131072) {
|
|
182
|
+
try {
|
|
183
|
+
const size = statSync(file).size;
|
|
184
|
+
const start = Math.max(0, size - maxBytes);
|
|
185
|
+
const fd = openSync(file, "r");
|
|
186
|
+
const buf = Buffer.alloc(size - start);
|
|
187
|
+
readSync(fd, buf, 0, buf.length, start);
|
|
188
|
+
closeSync(fd);
|
|
189
|
+
const lines = buf.toString("utf-8").split("\n").filter(Boolean);
|
|
190
|
+
if (start > 0) lines.shift();
|
|
191
|
+
return lines;
|
|
192
|
+
} catch {
|
|
193
|
+
return [];
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
function parseLocalLines(lines) {
|
|
197
|
+
const out = [];
|
|
198
|
+
for (const line of lines) {
|
|
199
|
+
try {
|
|
200
|
+
const j = JSON.parse(line);
|
|
201
|
+
const at = Date.parse(j.ts ?? "");
|
|
202
|
+
if (Number.isFinite(at)) out.push({ ...j, at });
|
|
203
|
+
} catch {
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return out;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// src/tui/panels/Live.tsx
|
|
175
210
|
import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
|
|
176
211
|
|
|
177
212
|
// src/api-client/client.ts
|
|
178
213
|
import { readFileSync as readFileSync2, existsSync } from "fs";
|
|
179
|
-
import { resolve, join as
|
|
180
|
-
import { homedir as
|
|
214
|
+
import { resolve, join as join3 } from "path";
|
|
215
|
+
import { homedir as homedir3 } from "os";
|
|
181
216
|
var DEFAULT_API_URL = "https://api.solongate.com";
|
|
182
217
|
var ApiError = class extends Error {
|
|
183
218
|
status;
|
|
@@ -197,7 +232,7 @@ var NotAuthenticatedError = class extends Error {
|
|
|
197
232
|
};
|
|
198
233
|
function loginCredentialFile() {
|
|
199
234
|
try {
|
|
200
|
-
const p =
|
|
235
|
+
const p = join3(homedir3(), ".solongate", "cloud-guard.json");
|
|
201
236
|
if (!existsSync(p)) return {};
|
|
202
237
|
const c2 = JSON.parse(readFileSync2(p, "utf-8"));
|
|
203
238
|
return c2 && typeof c2 === "object" ? c2 : {};
|
|
@@ -558,6 +593,7 @@ function useLoader(fn, deps = []) {
|
|
|
558
593
|
const [loading, setLoading] = useState(true);
|
|
559
594
|
const [nonce, setNonce] = useState(0);
|
|
560
595
|
const alive = useRef(true);
|
|
596
|
+
const quiet = useRef(false);
|
|
561
597
|
const fnRef = useRef(fn);
|
|
562
598
|
fnRef.current = fn;
|
|
563
599
|
useEffect(() => {
|
|
@@ -567,7 +603,8 @@ function useLoader(fn, deps = []) {
|
|
|
567
603
|
};
|
|
568
604
|
}, []);
|
|
569
605
|
useEffect(() => {
|
|
570
|
-
setLoading(true);
|
|
606
|
+
if (!quiet.current) setLoading(true);
|
|
607
|
+
quiet.current = false;
|
|
571
608
|
fnRef.current().then((d) => {
|
|
572
609
|
if (!alive.current) return;
|
|
573
610
|
setData(d);
|
|
@@ -580,7 +617,11 @@ function useLoader(fn, deps = []) {
|
|
|
580
617
|
});
|
|
581
618
|
}, [nonce, ...deps]);
|
|
582
619
|
const reload = useCallback(() => setNonce((n) => n + 1), []);
|
|
583
|
-
|
|
620
|
+
const reloadQuiet = useCallback(() => {
|
|
621
|
+
quiet.current = true;
|
|
622
|
+
setNonce((n) => n + 1);
|
|
623
|
+
}, []);
|
|
624
|
+
return { data, error, loading, reload, reloadQuiet };
|
|
584
625
|
}
|
|
585
626
|
function usePoll(reload, intervalMs, enabled = true) {
|
|
586
627
|
useEffect(() => {
|
|
@@ -614,8 +655,7 @@ var CONFIG = loadConfig();
|
|
|
614
655
|
var SPIN = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
615
656
|
var BG = "#12234f";
|
|
616
657
|
var DIM_FLOOR = "#233457";
|
|
617
|
-
var
|
|
618
|
-
var RING = join3(process.cwd(), ".solongate", ".eval-ring.jsonl");
|
|
658
|
+
var RING = join4(process.cwd(), ".solongate", ".eval-ring.jsonl");
|
|
619
659
|
var hhmmss = (ts) => {
|
|
620
660
|
const d = new Date(ts);
|
|
621
661
|
return Number.isNaN(d.getTime()) ? "--:--:--" : d.toTimeString().slice(0, 8);
|
|
@@ -625,21 +665,6 @@ var fmtUp = (ms) => {
|
|
|
625
665
|
const p = (n) => String(n).padStart(2, "0");
|
|
626
666
|
return `${p(Math.floor(s / 3600))}:${p(Math.floor(s % 3600 / 60))}:${p(s % 60)}`;
|
|
627
667
|
};
|
|
628
|
-
function tailLines(file, maxBytes = 131072) {
|
|
629
|
-
try {
|
|
630
|
-
const size = statSync(file).size;
|
|
631
|
-
const start = Math.max(0, size - maxBytes);
|
|
632
|
-
const fd = openSync(file, "r");
|
|
633
|
-
const buf = Buffer.alloc(size - start);
|
|
634
|
-
readSync(fd, buf, 0, buf.length, start);
|
|
635
|
-
closeSync(fd);
|
|
636
|
-
const lines = buf.toString("utf-8").split("\n").filter(Boolean);
|
|
637
|
-
if (start > 0) lines.shift();
|
|
638
|
-
return lines;
|
|
639
|
-
} catch {
|
|
640
|
-
return [];
|
|
641
|
-
}
|
|
642
|
-
}
|
|
643
668
|
function extractTarget(detail) {
|
|
644
669
|
try {
|
|
645
670
|
const j = JSON.parse(detail);
|
|
@@ -949,13 +974,13 @@ function LivePanel({ active: active2 }) {
|
|
|
949
974
|
const insights = useLoader(() => api.stats.securityInsights(7));
|
|
950
975
|
const guard = useLoader(() => api.settings.getGuardStatus());
|
|
951
976
|
usePoll(() => {
|
|
952
|
-
if (!paused()) sessions.
|
|
977
|
+
if (!paused()) sessions.reloadQuiet();
|
|
953
978
|
}, 8e3, active2);
|
|
954
979
|
usePoll(() => {
|
|
955
|
-
if (!paused()) insights.
|
|
980
|
+
if (!paused()) insights.reloadQuiet();
|
|
956
981
|
}, 2e4, active2);
|
|
957
982
|
usePoll(() => {
|
|
958
|
-
if (!paused()) guard.
|
|
983
|
+
if (!paused()) guard.reloadQuiet();
|
|
959
984
|
}, 6e4, active2);
|
|
960
985
|
useEffect2(() => {
|
|
961
986
|
if (!detail) return;
|
|
@@ -1229,9 +1254,9 @@ function LivePanel({ active: active2 }) {
|
|
|
1229
1254
|
else if (input === "x") toggleSignal("dlp");
|
|
1230
1255
|
else if (input === "r") toggleSignal("ratelimit");
|
|
1231
1256
|
else if (input === "e") {
|
|
1232
|
-
const file =
|
|
1257
|
+
const file = join4(homedir4(), ".solongate", "live-export.jsonl");
|
|
1233
1258
|
try {
|
|
1234
|
-
mkdirSync(
|
|
1259
|
+
mkdirSync(join4(homedir4(), ".solongate"), { recursive: true });
|
|
1235
1260
|
writeFileSync(file, visibleDesc.map((x) => JSON.stringify(x)).join("\n") + "\n");
|
|
1236
1261
|
setActionMsg({ text: `\u2713 exported ${visibleDesc.length} lines \u2192 ${file}`, level: "ok", until: Date.now() + 6e3 });
|
|
1237
1262
|
} catch (err) {
|
|
@@ -1593,7 +1618,6 @@ function PoliciesPanel({ focused }) {
|
|
|
1593
1618
|
const [pi, setPi] = useState3(0);
|
|
1594
1619
|
const [ri, setRi] = useState3(0);
|
|
1595
1620
|
const [fi, setFi] = useState3(0);
|
|
1596
|
-
const [vi, setVi] = useState3(0);
|
|
1597
1621
|
const [rules, setRules] = useState3([]);
|
|
1598
1622
|
const [mode, setMode] = useState3("denylist");
|
|
1599
1623
|
const [dirty, setDirty] = useState3(false);
|
|
@@ -1602,15 +1626,6 @@ function PoliciesPanel({ focused }) {
|
|
|
1602
1626
|
const [status, setStatus] = useState3(null);
|
|
1603
1627
|
const selected = policies[Math.min(pi, Math.max(0, policies.length - 1))];
|
|
1604
1628
|
const detail = useLoader(() => selected ? api.policies.get(selected.id) : Promise.resolve(null), [selected?.id]);
|
|
1605
|
-
const versionsQ = useLoader(
|
|
1606
|
-
() => view === "versions" && selected ? api.policies.versions(selected.id, { limit: 50 }) : Promise.resolve(null),
|
|
1607
|
-
[view, selected?.id]
|
|
1608
|
-
);
|
|
1609
|
-
const diffVer = versionsQ.data?.versions[vi]?.version;
|
|
1610
|
-
const diffQ = useLoader(
|
|
1611
|
-
() => view === "versions" && selected && diffVer !== void 0 ? api.policies.get(selected.id, diffVer) : Promise.resolve(null),
|
|
1612
|
-
[view, selected?.id, diffVer]
|
|
1613
|
-
);
|
|
1614
1629
|
useEffect3(() => {
|
|
1615
1630
|
if (detail.data) {
|
|
1616
1631
|
setRules(detail.data.rules);
|
|
@@ -1708,32 +1723,10 @@ function PoliciesPanel({ focused }) {
|
|
|
1708
1723
|
} else if (input === "D") {
|
|
1709
1724
|
setStatus("Dry-running\u2026");
|
|
1710
1725
|
void api.policies.dryRun({ rules, mode }).then((res) => setStatus(`dry-run ${res.evaluated} calls \xB7 allow ${res.would_allow} / deny ${res.would_deny} \xB7 newly blocked ${res.newly_blocked} \xB7 newly allowed ${res.newly_allowed}`)).catch((e) => setStatus("\u2717 " + (e instanceof Error ? e.message : String(e))));
|
|
1711
|
-
} else if (input === "v") {
|
|
1712
|
-
setVi(0);
|
|
1713
|
-
setView("versions");
|
|
1714
1726
|
} else if (input === "s") void save();
|
|
1715
1727
|
else if (input === "x") discard();
|
|
1716
1728
|
return;
|
|
1717
1729
|
}
|
|
1718
|
-
if (view === "versions") {
|
|
1719
|
-
const vers = versionsQ.data?.versions ?? [];
|
|
1720
|
-
if (key.leftArrow) return void setView("rules");
|
|
1721
|
-
if (key.upArrow) setVi((n) => Math.max(0, n - 1));
|
|
1722
|
-
else if (key.downArrow) setVi((n) => Math.min(vers.length - 1, n + 1));
|
|
1723
|
-
else if (key.return) {
|
|
1724
|
-
const v = vers[vi];
|
|
1725
|
-
if (v && selected) {
|
|
1726
|
-
setStatus("Rolling back\u2026");
|
|
1727
|
-
void api.policies.rollback(selected.id, v.version).then((r) => {
|
|
1728
|
-
setStatus(`\u2713 rolled back to v${v.version} \u2192 new v${r.version}`);
|
|
1729
|
-
detail.reload();
|
|
1730
|
-
list5.reload();
|
|
1731
|
-
setView("rules");
|
|
1732
|
-
}).catch((e) => setStatus("\u2717 " + (e instanceof Error ? e.message : String(e))));
|
|
1733
|
-
}
|
|
1734
|
-
}
|
|
1735
|
-
return;
|
|
1736
|
-
}
|
|
1737
1730
|
const rule2 = rules[ri];
|
|
1738
1731
|
if (!rule2) return setView("rules");
|
|
1739
1732
|
const field = FIELDS[fi];
|
|
@@ -1774,8 +1767,7 @@ function PoliciesPanel({ focused }) {
|
|
|
1774
1767
|
p.mode.padEnd(10),
|
|
1775
1768
|
" ",
|
|
1776
1769
|
p.rules.length,
|
|
1777
|
-
" rules
|
|
1778
|
-
p.version
|
|
1770
|
+
" rules"
|
|
1779
1771
|
] }),
|
|
1780
1772
|
p.id === activeId ? /* @__PURE__ */ jsx3(Text3, { color: theme.ok, bold: true, children: " \u25CF ACTIVE" }) : null
|
|
1781
1773
|
] }, p.id)) }),
|
|
@@ -1791,7 +1783,7 @@ function PoliciesPanel({ focused }) {
|
|
|
1791
1783
|
/* @__PURE__ */ jsx3(Text3, { color: mode === "whitelist" ? theme.warn : void 0, children: mode }),
|
|
1792
1784
|
dirtyTag
|
|
1793
1785
|
] }),
|
|
1794
|
-
/* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: "\u2191\u2193 \xB7 enter edit \xB7 space on/off \xB7 e effect \xB7 n new \xB7 d del \xB7 m mode \xB7 D dry-run \xB7
|
|
1786
|
+
/* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: "\u2191\u2193 \xB7 enter edit \xB7 space on/off \xB7 e effect \xB7 n new \xB7 d del \xB7 m mode \xB7 D dry-run \xB7 s save \xB7 \u2190 back" }),
|
|
1795
1787
|
/* @__PURE__ */ jsx3(Box3, { marginTop: 1, children: /* @__PURE__ */ jsx3(
|
|
1796
1788
|
Table,
|
|
1797
1789
|
{
|
|
@@ -1817,50 +1809,6 @@ function PoliciesPanel({ focused }) {
|
|
|
1817
1809
|
status ? /* @__PURE__ */ jsx3(Text3, { color: status.startsWith("\u2717") ? theme.bad : theme.ok, children: status }) : null
|
|
1818
1810
|
] }) });
|
|
1819
1811
|
}
|
|
1820
|
-
if (view === "versions") {
|
|
1821
|
-
const vers = versionsQ.data?.versions ?? [];
|
|
1822
|
-
return /* @__PURE__ */ jsx3(DataView, { loading: versionsQ.loading && !versionsQ.data, error: versionsQ.error, empty: !!versionsQ.data && vers.length === 0, emptyText: "No versions.", children: /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
|
|
1823
|
-
/* @__PURE__ */ jsx3(Text3, { bold: true, color: theme.accentBright, children: truncate(selected?.name ?? "", 28) }),
|
|
1824
|
-
/* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: "\u2191\u2193 select \xB7 enter roll back to that version \xB7 \u2190 back" }),
|
|
1825
|
-
/* @__PURE__ */ jsx3(Box3, { marginTop: 1, children: /* @__PURE__ */ jsx3(
|
|
1826
|
-
Table,
|
|
1827
|
-
{
|
|
1828
|
-
columns: [
|
|
1829
|
-
{ header: "", width: 2 },
|
|
1830
|
-
{ header: "VER", width: 5 },
|
|
1831
|
-
{ header: "RULES", width: 5 },
|
|
1832
|
-
{ header: "REASON", width: 34 },
|
|
1833
|
-
{ header: "WHEN", width: 16 }
|
|
1834
|
-
],
|
|
1835
|
-
rows: vers.map((v, i) => [
|
|
1836
|
-
{ value: i === vi ? "\u25B8" : "", color: theme.accentBright },
|
|
1837
|
-
{ value: `v${v.version}`, bold: i === vi },
|
|
1838
|
-
{ value: String(v.rules_count), dim: true },
|
|
1839
|
-
{ value: truncate(v.reason || "\u2014", 34) },
|
|
1840
|
-
{ value: truncate(v.created_at, 16), dim: true }
|
|
1841
|
-
])
|
|
1842
|
-
}
|
|
1843
|
-
) }),
|
|
1844
|
-
/* @__PURE__ */ jsxs3(Box3, { marginTop: 1, flexDirection: "column", children: [
|
|
1845
|
-
/* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: `Diff v${diffVer ?? "?"} vs current draft` }),
|
|
1846
|
-
(() => {
|
|
1847
|
-
const old = diffQ.data?.rules ?? [];
|
|
1848
|
-
const cur = rules;
|
|
1849
|
-
const oldIds = new Set(old.map((r) => r.id));
|
|
1850
|
-
const curIds = new Set(cur.map((r) => r.id));
|
|
1851
|
-
const added = cur.filter((r) => !oldIds.has(r.id));
|
|
1852
|
-
const removed = old.filter((r) => !curIds.has(r.id));
|
|
1853
|
-
if (diffQ.loading && !diffQ.data) return /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: " loading\u2026" });
|
|
1854
|
-
if (!added.length && !removed.length) return /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: " identical rule set" });
|
|
1855
|
-
return /* @__PURE__ */ jsxs3(Fragment3, { children: [
|
|
1856
|
-
added.slice(0, 4).map((r) => /* @__PURE__ */ jsx3(Text3, { wrap: "truncate", color: theme.ok, children: ` + ${r.effect} ${truncate(r.toolPattern, 18)} ${truncate(r.description || r.id, 30)}` }, "a" + r.id)),
|
|
1857
|
-
removed.slice(0, 4).map((r) => /* @__PURE__ */ jsx3(Text3, { wrap: "truncate", color: theme.bad, children: ` - ${r.effect} ${truncate(r.toolPattern, 18)} ${truncate(r.description || r.id, 30)}` }, "r" + r.id))
|
|
1858
|
-
] });
|
|
1859
|
-
})()
|
|
1860
|
-
] }),
|
|
1861
|
-
status ? /* @__PURE__ */ jsx3(Text3, { color: status.startsWith("\u2717") ? theme.bad : theme.ok, children: status }) : null
|
|
1862
|
-
] }) });
|
|
1863
|
-
}
|
|
1864
1812
|
const rule = rules[ri];
|
|
1865
1813
|
return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
|
|
1866
1814
|
/* @__PURE__ */ jsxs3(Box3, { children: [
|
|
@@ -2222,171 +2170,229 @@ function RegexTest({ re }) {
|
|
|
2222
2170
|
] });
|
|
2223
2171
|
}
|
|
2224
2172
|
|
|
2225
|
-
// src/tui/panels/Stats.tsx
|
|
2226
|
-
import { Box as Box6, Text as Text6 } from "ink";
|
|
2227
|
-
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
2228
|
-
function StatsPanel({ active: active2 }) {
|
|
2229
|
-
const stats = useLoader(() => api.stats.get());
|
|
2230
|
-
const ts = useLoader(() => api.stats.timeseries({ period: "24h" }));
|
|
2231
|
-
const drift2 = useLoader(() => api.stats.drift(7));
|
|
2232
|
-
usePoll(() => {
|
|
2233
|
-
stats.reload();
|
|
2234
|
-
ts.reload();
|
|
2235
|
-
}, 5e3, active2);
|
|
2236
|
-
usePoll(drift2.reload, 3e4, active2);
|
|
2237
|
-
const s = stats.data;
|
|
2238
|
-
const points = ts.data?.timeseries ?? [];
|
|
2239
|
-
const driftRules = (drift2.data?.rules ?? []).slice(0, 5);
|
|
2240
|
-
return /* @__PURE__ */ jsx6(DataView, { loading: stats.loading && !s, error: stats.error, children: s ? /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
|
|
2241
|
-
/* @__PURE__ */ jsxs6(Box6, { children: [
|
|
2242
|
-
/* @__PURE__ */ jsxs6(Text6, { bold: true, children: [
|
|
2243
|
-
s.total_calls,
|
|
2244
|
-
" "
|
|
2245
|
-
] }),
|
|
2246
|
-
/* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: "calls " }),
|
|
2247
|
-
/* @__PURE__ */ jsxs6(Text6, { color: theme.ok, children: [
|
|
2248
|
-
s.allowed,
|
|
2249
|
-
" allowed"
|
|
2250
|
-
] }),
|
|
2251
|
-
/* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " " }),
|
|
2252
|
-
/* @__PURE__ */ jsxs6(Text6, { color: theme.bad, children: [
|
|
2253
|
-
s.denied,
|
|
2254
|
-
" denied"
|
|
2255
|
-
] })
|
|
2256
|
-
] }),
|
|
2257
|
-
/* @__PURE__ */ jsxs6(Text6, { color: theme.dim, children: [
|
|
2258
|
-
s.active_policies,
|
|
2259
|
-
" policies \xB7 ",
|
|
2260
|
-
s.registered_tools,
|
|
2261
|
-
" tools"
|
|
2262
|
-
] }),
|
|
2263
|
-
/* @__PURE__ */ jsxs6(Box6, { marginTop: 1, flexDirection: "column", children: [
|
|
2264
|
-
/* @__PURE__ */ jsxs6(Text6, { color: theme.dim, children: [
|
|
2265
|
-
"Last 24h ",
|
|
2266
|
-
ts.data ? `(per ${ts.data.granularity})` : ""
|
|
2267
|
-
] }),
|
|
2268
|
-
/* @__PURE__ */ jsxs6(Box6, { children: [
|
|
2269
|
-
/* @__PURE__ */ jsx6(Text6, { children: "total " }),
|
|
2270
|
-
/* @__PURE__ */ jsx6(Text6, { color: theme.accent, children: sparkline(points.map((p) => p.total), 64) })
|
|
2271
|
-
] }),
|
|
2272
|
-
/* @__PURE__ */ jsxs6(Box6, { children: [
|
|
2273
|
-
/* @__PURE__ */ jsx6(Text6, { children: "allowed " }),
|
|
2274
|
-
/* @__PURE__ */ jsx6(Text6, { color: theme.ok, children: sparkline(points.map((p) => p.allowed), 64) })
|
|
2275
|
-
] }),
|
|
2276
|
-
/* @__PURE__ */ jsxs6(Box6, { children: [
|
|
2277
|
-
/* @__PURE__ */ jsx6(Text6, { children: "denied " }),
|
|
2278
|
-
/* @__PURE__ */ jsx6(Text6, { color: theme.bad, children: sparkline(points.map((p) => p.denied), 64) })
|
|
2279
|
-
] })
|
|
2280
|
-
] }),
|
|
2281
|
-
/* @__PURE__ */ jsxs6(Box6, { marginTop: 1, flexDirection: "column", children: [
|
|
2282
|
-
/* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: "Recent" }),
|
|
2283
|
-
/* @__PURE__ */ jsx6(
|
|
2284
|
-
Table,
|
|
2285
|
-
{
|
|
2286
|
-
columns: [
|
|
2287
|
-
{ header: "DECISION", width: 9 },
|
|
2288
|
-
{ header: "TOOL", width: 20 },
|
|
2289
|
-
{ header: "MS", width: 5 },
|
|
2290
|
-
{ header: "WHEN", width: 6 }
|
|
2291
|
-
],
|
|
2292
|
-
rows: (s.recent_activity ?? []).slice(0, 6).map((a) => [
|
|
2293
|
-
{ value: a.decision, color: decisionColor(a.decision) },
|
|
2294
|
-
{ value: truncate(a.tool_name, 20), color: theme.accent },
|
|
2295
|
-
{ value: String(a.evaluation_time_ms ?? "\u2014"), dim: true },
|
|
2296
|
-
{ value: ago(a.created_at), dim: true }
|
|
2297
|
-
])
|
|
2298
|
-
}
|
|
2299
|
-
)
|
|
2300
|
-
] }),
|
|
2301
|
-
/* @__PURE__ */ jsxs6(Box6, { marginTop: 1, flexDirection: "column", children: [
|
|
2302
|
-
/* @__PURE__ */ jsxs6(Text6, { color: theme.dim, children: [
|
|
2303
|
-
"Denial drift ",
|
|
2304
|
-
drift2.data ? `(7d: ${drift2.data.total_current} now vs ${drift2.data.total_previous} prev)` : ""
|
|
2305
|
-
] }),
|
|
2306
|
-
driftRules.length ? /* @__PURE__ */ jsx6(
|
|
2307
|
-
Table,
|
|
2308
|
-
{
|
|
2309
|
-
columns: [
|
|
2310
|
-
{ header: "NOW", width: 5 },
|
|
2311
|
-
{ header: "PREV", width: 5 },
|
|
2312
|
-
{ header: "\u0394", width: 6 },
|
|
2313
|
-
{ header: "RULE", width: 22 },
|
|
2314
|
-
{ header: "REASON", width: 26 }
|
|
2315
|
-
],
|
|
2316
|
-
rows: driftRules.map((r) => [
|
|
2317
|
-
{ value: String(r.current), bold: true },
|
|
2318
|
-
{ value: String(r.previous), dim: true },
|
|
2319
|
-
{ value: r.is_new ? "NEW" : r.spike ? `+${r.delta}` : String(r.delta), color: r.is_new ? theme.ok : r.spike ? theme.bad : void 0 },
|
|
2320
|
-
{ value: truncate(r.rule_id ?? "\u2014", 22), color: theme.accent },
|
|
2321
|
-
{ value: truncate(r.reason ?? r.last_tool ?? "\u2014", 26), dim: true }
|
|
2322
|
-
])
|
|
2323
|
-
}
|
|
2324
|
-
) : /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " no denials in window" })
|
|
2325
|
-
] })
|
|
2326
|
-
] }) : null });
|
|
2327
|
-
}
|
|
2328
|
-
|
|
2329
2173
|
// src/tui/panels/Audit.tsx
|
|
2330
|
-
import { Box as
|
|
2174
|
+
import { Box as Box6, Text as Text6, useInput as useInput5 } from "ink";
|
|
2331
2175
|
import TextInput4 from "ink-text-input";
|
|
2332
2176
|
import { useState as useState6 } from "react";
|
|
2333
|
-
import { Fragment as Fragment4, jsx as
|
|
2177
|
+
import { Fragment as Fragment4, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
2334
2178
|
var DECISIONS = [void 0, "DENY", "ALLOW"];
|
|
2335
2179
|
var SIGNALS = [void 0, "dlp", "ratelimit"];
|
|
2336
|
-
var
|
|
2180
|
+
var SESS_STATUS = [void 0, "active", "idle", "ended"];
|
|
2181
|
+
var PAGE = 500;
|
|
2182
|
+
var LOCAL_MAX_BYTES = 16 * 1024 * 1024;
|
|
2183
|
+
var cloudRow = (e) => ({
|
|
2184
|
+
id: e.id,
|
|
2185
|
+
at: Date.parse(e.created_at),
|
|
2186
|
+
tool: e.tool_name,
|
|
2187
|
+
decision: e.decision,
|
|
2188
|
+
permission: e.permission,
|
|
2189
|
+
trust: e.trust_level,
|
|
2190
|
+
agent: e.agent_name,
|
|
2191
|
+
session: e.session_id,
|
|
2192
|
+
reason: e.reason,
|
|
2193
|
+
rule: e.matched_rule_id,
|
|
2194
|
+
evalMs: e.evaluation_time_ms,
|
|
2195
|
+
dlp: e.dlp_matches ?? [],
|
|
2196
|
+
burst: !!e.rate_limit_burst,
|
|
2197
|
+
args: e.arguments_summary ? JSON.stringify(e.arguments_summary) : null
|
|
2198
|
+
});
|
|
2199
|
+
function loadLocalRows() {
|
|
2200
|
+
return parseLocalLines(tailLines(LOCAL_LOG, LOCAL_MAX_BYTES)).map((j, i) => ({
|
|
2201
|
+
id: "l:" + j.at + ":" + i,
|
|
2202
|
+
at: j.at,
|
|
2203
|
+
tool: j.tool ?? "?",
|
|
2204
|
+
decision: j.decision ?? "ALLOW",
|
|
2205
|
+
permission: j.permission ?? "\u2014",
|
|
2206
|
+
trust: j.trust_level ?? "\u2014",
|
|
2207
|
+
agent: j.agent_name ?? null,
|
|
2208
|
+
session: j.session_id ?? null,
|
|
2209
|
+
reason: j.reason ?? null,
|
|
2210
|
+
rule: j.matched_rule_id ?? null,
|
|
2211
|
+
evalMs: j.evaluation_time_ms ?? null,
|
|
2212
|
+
dlp: j.dlp ? Array.isArray(j.dlp) ? j.dlp.map(String) : ["dlp"] : [],
|
|
2213
|
+
burst: !!j.rate_limit_burst,
|
|
2214
|
+
args: j.arguments ? JSON.stringify(j.arguments) : null
|
|
2215
|
+
})).sort((a, b) => b.at - a.at);
|
|
2216
|
+
}
|
|
2217
|
+
var sessStatus2 = (lastAt) => Date.now() - lastAt < 6e4 ? "active" : Date.now() - lastAt < 3e5 ? "idle" : "ended";
|
|
2218
|
+
var STATUS_DOT = {
|
|
2219
|
+
active: { ch: "\u25CF", color: theme.ok },
|
|
2220
|
+
idle: { ch: "\u25D0", color: theme.warn },
|
|
2221
|
+
ended: { ch: "\u25CB", color: theme.dim }
|
|
2222
|
+
};
|
|
2337
2223
|
function AuditPanel({ active: active2, focused }) {
|
|
2338
2224
|
const { cols, rows } = usePanelSize();
|
|
2225
|
+
const [source, setSource] = useState6("cloud");
|
|
2226
|
+
const [view, setView] = useState6("logs");
|
|
2339
2227
|
const [di, setDi] = useState6(0);
|
|
2340
2228
|
const [gi, setGi] = useState6(0);
|
|
2341
2229
|
const [tool, setTool] = useState6("");
|
|
2342
2230
|
const [agent, setAgent] = useState6("");
|
|
2343
2231
|
const [search, setSearch] = useState6("");
|
|
2232
|
+
const [sessFilter, setSessFilter] = useState6("");
|
|
2233
|
+
const [page, setPage] = useState6(0);
|
|
2344
2234
|
const [sel, setSel] = useState6(0);
|
|
2345
|
-
const [view, setView] = useState6("list");
|
|
2346
2235
|
const [detailScroll, setDetailScroll] = useState6(0);
|
|
2347
2236
|
const [editing, setEditing] = useState6(null);
|
|
2237
|
+
const [si, setSi] = useState6(0);
|
|
2238
|
+
const [sessSearch, setSessSearch] = useState6("");
|
|
2239
|
+
const [sessSel, setSessSel] = useState6(0);
|
|
2240
|
+
const toTop = () => setSel(0);
|
|
2241
|
+
const statsQ = useLoader(() => source === "cloud" ? api.stats.get() : Promise.resolve(null), [source]);
|
|
2242
|
+
const tsQ = useLoader(() => source === "cloud" ? api.stats.timeseries({ period: "24h" }) : Promise.resolve(null), [source]);
|
|
2243
|
+
usePoll(() => {
|
|
2244
|
+
statsQ.reloadQuiet();
|
|
2245
|
+
tsQ.reloadQuiet();
|
|
2246
|
+
}, 15e3, active2 && source === "cloud");
|
|
2348
2247
|
const query = {
|
|
2349
2248
|
filter: DECISIONS[di],
|
|
2350
2249
|
signal: SIGNALS[gi],
|
|
2351
2250
|
tool: tool || void 0,
|
|
2352
2251
|
agent_name: agent || void 0,
|
|
2353
2252
|
search: search || void 0,
|
|
2354
|
-
|
|
2253
|
+
session_id: sessFilter || void 0,
|
|
2254
|
+
limit: PAGE,
|
|
2255
|
+
offset: page * PAGE
|
|
2355
2256
|
};
|
|
2356
|
-
const
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2257
|
+
const cloudQ = useLoader(
|
|
2258
|
+
() => source === "cloud" ? api.audit.list(query) : Promise.resolve(null),
|
|
2259
|
+
[source, di, gi, tool, agent, search, sessFilter, page]
|
|
2260
|
+
);
|
|
2261
|
+
usePoll(cloudQ.reloadQuiet, 6e3, active2 && source === "cloud" && view === "logs" && !editing && page === 0);
|
|
2262
|
+
const localQ = useLoader(() => source === "local" ? Promise.resolve(loadLocalRows()) : Promise.resolve(null), [source]);
|
|
2263
|
+
usePoll(localQ.reloadQuiet, 6e3, active2 && source === "local" && view === "logs" && !editing && page === 0);
|
|
2264
|
+
let pageRows = [];
|
|
2265
|
+
let total = 0;
|
|
2266
|
+
if (source === "cloud") {
|
|
2267
|
+
pageRows = (cloudQ.data?.entries ?? []).map(cloudRow).sort((a, b) => b.at - a.at);
|
|
2268
|
+
total = cloudQ.data?.total ?? 0;
|
|
2269
|
+
} else {
|
|
2270
|
+
const all = localQ.data ?? [];
|
|
2271
|
+
const q = search.trim().toLowerCase();
|
|
2272
|
+
const filtered = all.filter((r) => {
|
|
2273
|
+
if (DECISIONS[di] && r.decision !== DECISIONS[di]) return false;
|
|
2274
|
+
if (SIGNALS[gi] === "dlp" && r.dlp.length === 0) return false;
|
|
2275
|
+
if (SIGNALS[gi] === "ratelimit" && !r.burst) return false;
|
|
2276
|
+
if (tool && !r.tool.toLowerCase().includes(tool.toLowerCase())) return false;
|
|
2277
|
+
if (agent && (r.agent ?? "").toLowerCase() !== agent.toLowerCase()) return false;
|
|
2278
|
+
if (sessFilter && r.session !== sessFilter) return false;
|
|
2279
|
+
if (q && !`${r.tool} ${r.agent ?? ""} ${r.reason ?? ""} ${r.args ?? ""}`.toLowerCase().includes(q)) return false;
|
|
2280
|
+
return true;
|
|
2281
|
+
});
|
|
2282
|
+
total = filtered.length;
|
|
2283
|
+
pageRows = filtered.slice(page * PAGE, page * PAGE + PAGE);
|
|
2284
|
+
}
|
|
2285
|
+
const pages = Math.max(1, Math.ceil(total / PAGE));
|
|
2286
|
+
const current = pageRows[Math.min(sel, Math.max(0, pageRows.length - 1))];
|
|
2287
|
+
const agentsQ = useLoader(
|
|
2288
|
+
() => source === "cloud" ? api.agents.live({ limit: 100, includeDeactivated: true }) : Promise.resolve(null),
|
|
2289
|
+
[source]
|
|
2290
|
+
);
|
|
2291
|
+
usePoll(agentsQ.reloadQuiet, 8e3, active2 && source === "cloud" && view === "sessions");
|
|
2292
|
+
let sessions = [];
|
|
2293
|
+
if (source === "cloud") {
|
|
2294
|
+
sessions = (agentsQ.data?.agents ?? []).map((a) => ({
|
|
2295
|
+
id: a.session_id,
|
|
2296
|
+
agent: a.agent_name ?? a.session_id,
|
|
2297
|
+
status: a.status === "deactivated" ? "ended" : a.status,
|
|
2298
|
+
calls: a.total_calls,
|
|
2299
|
+
denies: a.denied_calls,
|
|
2300
|
+
dlp: a.dlp_events,
|
|
2301
|
+
trust: a.trust_score,
|
|
2302
|
+
lastAt: Date.parse(a.last_seen_at)
|
|
2303
|
+
}));
|
|
2304
|
+
} else {
|
|
2305
|
+
const bySess = /* @__PURE__ */ new Map();
|
|
2306
|
+
for (const r of localQ.data ?? []) {
|
|
2307
|
+
if (!r.session) continue;
|
|
2308
|
+
const cur = bySess.get(r.session) ?? { id: r.session, agent: r.agent ?? "local agent", status: "ended", calls: 0, denies: 0, dlp: 0, trust: null, lastAt: 0 };
|
|
2309
|
+
cur.calls++;
|
|
2310
|
+
if (r.decision !== "ALLOW") cur.denies++;
|
|
2311
|
+
if (r.dlp.length) cur.dlp++;
|
|
2312
|
+
cur.lastAt = Math.max(cur.lastAt, r.at);
|
|
2313
|
+
if (r.agent) cur.agent = r.agent;
|
|
2314
|
+
bySess.set(r.session, cur);
|
|
2315
|
+
}
|
|
2316
|
+
sessions = [...bySess.values()];
|
|
2317
|
+
}
|
|
2318
|
+
const sq = sessSearch.trim().toLowerCase();
|
|
2319
|
+
const sessionsFiltered = sessions.map((s) => ({ ...s, status: source === "cloud" ? s.status : sessStatus2(s.lastAt) })).filter((s) => SESS_STATUS[si] ? s.status === SESS_STATUS[si] : true).filter((s) => !sq || `${s.agent} ${s.id}`.toLowerCase().includes(sq)).sort((a, b) => b.lastAt - a.lastAt);
|
|
2320
|
+
const currentSess = sessionsFiltered[Math.min(sessSel, Math.max(0, sessionsFiltered.length - 1))];
|
|
2360
2321
|
const sessionQ = useLoader(
|
|
2361
|
-
() => view === "detail" && current?.
|
|
2362
|
-
[view, current?.
|
|
2322
|
+
() => view === "detail" && current?.session && source === "cloud" ? api.audit.list({ session_id: current.session, limit: 40 }) : Promise.resolve(null),
|
|
2323
|
+
[view, current?.session, source]
|
|
2363
2324
|
);
|
|
2364
2325
|
useInput5(
|
|
2365
2326
|
(input, key) => {
|
|
2366
2327
|
if (view === "detail") {
|
|
2367
|
-
if (key.leftArrow || key.escape) setView("
|
|
2328
|
+
if (key.leftArrow || key.escape) setView("logs");
|
|
2368
2329
|
else if (key.upArrow) setDetailScroll((n) => Math.max(0, n - 1));
|
|
2369
2330
|
else if (key.downArrow) setDetailScroll((n) => n + 1);
|
|
2370
2331
|
else if (key.pageUp) setDetailScroll((n) => Math.max(0, n - 10));
|
|
2371
2332
|
else if (key.pageDown) setDetailScroll((n) => n + 10);
|
|
2372
2333
|
return;
|
|
2373
2334
|
}
|
|
2374
|
-
|
|
2335
|
+
if (input === "s") {
|
|
2336
|
+
setSource((s) => s === "cloud" ? "local" : "cloud");
|
|
2337
|
+
setPage(0);
|
|
2338
|
+
toTop();
|
|
2339
|
+
setSessSel(0);
|
|
2340
|
+
return;
|
|
2341
|
+
}
|
|
2342
|
+
if (input === "v") {
|
|
2343
|
+
setView((v) => v === "logs" ? "sessions" : "logs");
|
|
2344
|
+
return;
|
|
2345
|
+
}
|
|
2346
|
+
if (view === "sessions") {
|
|
2347
|
+
if (key.upArrow) setSessSel((n) => Math.max(0, n - 1));
|
|
2348
|
+
else if (key.downArrow) setSessSel((n) => Math.min(sessionsFiltered.length - 1, n + 1));
|
|
2349
|
+
else if (key.pageUp) setSessSel((n) => Math.max(0, n - 10));
|
|
2350
|
+
else if (key.pageDown) setSessSel((n) => Math.min(sessionsFiltered.length - 1, n + 10));
|
|
2351
|
+
else if (key.return) {
|
|
2352
|
+
if (currentSess) {
|
|
2353
|
+
setSessFilter(currentSess.id);
|
|
2354
|
+
setPage(0);
|
|
2355
|
+
toTop();
|
|
2356
|
+
setView("logs");
|
|
2357
|
+
}
|
|
2358
|
+
} else if (input === "f") {
|
|
2359
|
+
setSi((n) => (n + 1) % SESS_STATUS.length);
|
|
2360
|
+
setSessSel(0);
|
|
2361
|
+
} else if (input === "/") setEditing("sess-search");
|
|
2362
|
+
else if (input === "c") {
|
|
2363
|
+
setSi(0);
|
|
2364
|
+
setSessSearch("");
|
|
2365
|
+
setSessSel(0);
|
|
2366
|
+
}
|
|
2367
|
+
return;
|
|
2368
|
+
}
|
|
2375
2369
|
if (key.upArrow) setSel((n) => Math.max(0, n - 1));
|
|
2376
|
-
else if (key.downArrow) setSel((n) => Math.min(
|
|
2370
|
+
else if (key.downArrow) setSel((n) => Math.min(pageRows.length - 1, n + 1));
|
|
2377
2371
|
else if (key.pageUp) setSel((n) => Math.max(0, n - 10));
|
|
2378
|
-
else if (key.pageDown) setSel((n) => Math.min(
|
|
2379
|
-
else if (key.return
|
|
2372
|
+
else if (key.pageDown) setSel((n) => Math.min(pageRows.length - 1, n + 10));
|
|
2373
|
+
else if (key.return) {
|
|
2380
2374
|
if (current) {
|
|
2381
2375
|
setDetailScroll(0);
|
|
2382
2376
|
setView("detail");
|
|
2383
2377
|
}
|
|
2378
|
+
} else if (key.rightArrow) {
|
|
2379
|
+
if (page < pages - 1) {
|
|
2380
|
+
setPage(page + 1);
|
|
2381
|
+
toTop();
|
|
2382
|
+
}
|
|
2383
|
+
} else if (key.leftArrow) {
|
|
2384
|
+
if (page > 0) {
|
|
2385
|
+
setPage(page - 1);
|
|
2386
|
+
toTop();
|
|
2387
|
+
}
|
|
2384
2388
|
} else if (input === "f") {
|
|
2385
2389
|
setDi((n) => (n + 1) % DECISIONS.length);
|
|
2386
|
-
|
|
2390
|
+
setPage(0);
|
|
2391
|
+
toTop();
|
|
2387
2392
|
} else if (input === "g") {
|
|
2388
2393
|
setGi((n) => (n + 1) % SIGNALS.length);
|
|
2389
|
-
|
|
2394
|
+
setPage(0);
|
|
2395
|
+
toTop();
|
|
2390
2396
|
} else if (input === "t") setEditing("tool");
|
|
2391
2397
|
else if (input === "n") setEditing("agent");
|
|
2392
2398
|
else if (input === "/") setEditing("search");
|
|
@@ -2396,46 +2402,89 @@ function AuditPanel({ active: active2, focused }) {
|
|
|
2396
2402
|
setTool("");
|
|
2397
2403
|
setAgent("");
|
|
2398
2404
|
setSearch("");
|
|
2399
|
-
|
|
2405
|
+
setSessFilter("");
|
|
2406
|
+
setPage(0);
|
|
2407
|
+
toTop();
|
|
2400
2408
|
}
|
|
2401
2409
|
},
|
|
2402
2410
|
{ isActive: focused && !editing }
|
|
2403
2411
|
);
|
|
2412
|
+
const points = tsQ.data?.timeseries ?? [];
|
|
2413
|
+
const localAll = localQ.data ?? [];
|
|
2414
|
+
const strip = source === "cloud" ? /* @__PURE__ */ jsxs6(Text6, { wrap: "truncate", children: [
|
|
2415
|
+
/* @__PURE__ */ jsx6(Text6, { bold: true, children: statsQ.data ? statsQ.data.total_calls : "\xB7\xB7\xB7\xB7" }),
|
|
2416
|
+
/* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " calls \xB7 " }),
|
|
2417
|
+
/* @__PURE__ */ jsxs6(Text6, { color: theme.ok, children: [
|
|
2418
|
+
statsQ.data?.allowed ?? "\xB7\xB7\xB7",
|
|
2419
|
+
" allow"
|
|
2420
|
+
] }),
|
|
2421
|
+
/* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " \xB7 " }),
|
|
2422
|
+
/* @__PURE__ */ jsxs6(Text6, { color: theme.bad, children: [
|
|
2423
|
+
statsQ.data?.denied ?? "\xB7\xB7",
|
|
2424
|
+
" deny"
|
|
2425
|
+
] }),
|
|
2426
|
+
/* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: ` \xB7 ${statsQ.data?.active_policies ?? "\xB7"} policies \xB7 ${statsQ.data?.registered_tools ?? "\xB7"} tools \xB7 24h ` }),
|
|
2427
|
+
/* @__PURE__ */ jsx6(Text6, { color: theme.accent, children: sparkline(points.map((p) => p.total), 24) }),
|
|
2428
|
+
/* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " deny " }),
|
|
2429
|
+
/* @__PURE__ */ jsx6(Text6, { color: theme.bad, children: sparkline(points.map((p) => p.denied), 24) })
|
|
2430
|
+
] }) : /* @__PURE__ */ jsxs6(Text6, { wrap: "truncate", children: [
|
|
2431
|
+
/* @__PURE__ */ jsx6(Text6, { bold: true, children: localAll.length }),
|
|
2432
|
+
/* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " calls in local file \xB7 " }),
|
|
2433
|
+
/* @__PURE__ */ jsxs6(Text6, { color: theme.ok, children: [
|
|
2434
|
+
localAll.filter((r) => r.decision === "ALLOW").length,
|
|
2435
|
+
" allow"
|
|
2436
|
+
] }),
|
|
2437
|
+
/* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " \xB7 " }),
|
|
2438
|
+
/* @__PURE__ */ jsxs6(Text6, { color: theme.bad, children: [
|
|
2439
|
+
localAll.filter((r) => r.decision !== "ALLOW").length,
|
|
2440
|
+
" deny"
|
|
2441
|
+
] }),
|
|
2442
|
+
/* @__PURE__ */ jsxs6(Text6, { color: theme.dim, children: [
|
|
2443
|
+
" \xB7 ",
|
|
2444
|
+
LOCAL_LOG
|
|
2445
|
+
] })
|
|
2446
|
+
] });
|
|
2447
|
+
const srcChip = /* @__PURE__ */ jsxs6(Text6, { children: [
|
|
2448
|
+
/* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: "src:" }),
|
|
2449
|
+
/* @__PURE__ */ jsx6(Text6, { color: source === "local" ? theme.ok : "#4f6db8", bold: true, children: source }),
|
|
2450
|
+
/* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " (s) " })
|
|
2451
|
+
] });
|
|
2404
2452
|
if (view === "detail" && current) {
|
|
2405
|
-
const sessionCalls = sessionQ.data?.entries ?? [];
|
|
2453
|
+
const sessionCalls = source === "cloud" ? (sessionQ.data?.entries ?? []).map(cloudRow) : localAll.filter((r) => r.session && r.session === current.session).slice(0, 40);
|
|
2406
2454
|
const bodyW = Math.max(20, cols - 2);
|
|
2407
2455
|
const reasonLines = wrapLines("reason: " + (current.reason ?? "\u2014"), bodyW);
|
|
2408
|
-
const argLines = current.
|
|
2409
|
-
const sessionRows = current.
|
|
2456
|
+
const argLines = current.args ? wrapLines(prettyJson(current.args), bodyW) : ["(no arguments recorded)"];
|
|
2457
|
+
const sessionRows = current.session ? Math.min(4, sessionCalls.length) + 2 : 1;
|
|
2410
2458
|
const reasonShown = reasonLines.slice(0, 4);
|
|
2411
2459
|
const argBudget = Math.max(3, rows - 2 - 5 - reasonShown.length - 1 - sessionRows - 1);
|
|
2412
2460
|
const maxScroll = Math.max(0, argLines.length - argBudget);
|
|
2413
2461
|
const off = Math.min(detailScroll, maxScroll);
|
|
2414
2462
|
const argWin = argLines.slice(off, off + argBudget);
|
|
2415
|
-
return /* @__PURE__ */
|
|
2416
|
-
/* @__PURE__ */
|
|
2417
|
-
/* @__PURE__ */
|
|
2418
|
-
/* @__PURE__ */
|
|
2419
|
-
/* @__PURE__ */
|
|
2463
|
+
return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
|
|
2464
|
+
/* @__PURE__ */ jsxs6(Box6, { children: [
|
|
2465
|
+
/* @__PURE__ */ jsx6(Text6, { color: decisionColor(current.decision), bold: true, children: current.decision }),
|
|
2466
|
+
/* @__PURE__ */ jsx6(Text6, { color: theme.accent, children: " " + current.tool }),
|
|
2467
|
+
/* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " " + current.permission + " \xB7 " + current.trust + " " }),
|
|
2468
|
+
srcChip
|
|
2420
2469
|
] }),
|
|
2421
|
-
reasonShown.map((l, i) => /* @__PURE__ */
|
|
2422
|
-
/* @__PURE__ */
|
|
2423
|
-
/* @__PURE__ */
|
|
2424
|
-
] }) : /* @__PURE__ */
|
|
2425
|
-
/* @__PURE__ */
|
|
2426
|
-
/* @__PURE__ */
|
|
2427
|
-
/* @__PURE__ */
|
|
2428
|
-
/* @__PURE__ */
|
|
2429
|
-
/* @__PURE__ */
|
|
2430
|
-
/* @__PURE__ */
|
|
2431
|
-
/* @__PURE__ */
|
|
2432
|
-
current.
|
|
2433
|
-
/* @__PURE__ */
|
|
2470
|
+
reasonShown.map((l, i) => /* @__PURE__ */ jsx6(Text6, { wrap: "truncate", children: i === 0 ? /* @__PURE__ */ jsxs6(Fragment4, { children: [
|
|
2471
|
+
/* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: "reason " }),
|
|
2472
|
+
/* @__PURE__ */ jsx6(Text6, { children: l.slice("reason: ".length) })
|
|
2473
|
+
] }) : /* @__PURE__ */ jsx6(Text6, { children: " " + l }) }, "r" + i)),
|
|
2474
|
+
/* @__PURE__ */ jsx6(Detail, { label: "rule", value: current.rule ?? "\u2014" }),
|
|
2475
|
+
/* @__PURE__ */ jsx6(Detail, { label: "agent", value: current.agent ?? "\u2014" }),
|
|
2476
|
+
/* @__PURE__ */ jsx6(Detail, { label: "session", value: current.session ?? "\u2014" }),
|
|
2477
|
+
/* @__PURE__ */ jsx6(Detail, { label: "dlp", value: current.dlp.length ? current.dlp.join(", ") : "none", color: current.dlp.length ? theme.bad : void 0 }),
|
|
2478
|
+
/* @__PURE__ */ jsx6(Detail, { label: "when", value: new Date(current.at).toLocaleString() + (current.evalMs != null ? ` \xB7 eval ${current.evalMs}ms` : "") }),
|
|
2479
|
+
/* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: `arguments \u2014 full \xB7 ${argLines.length} lines${maxScroll ? ` \xB7 \u25BC${maxScroll - off} more \xB7 \u2191\u2193 scroll` : ""}` }),
|
|
2480
|
+
/* @__PURE__ */ jsx6(Box6, { flexDirection: "column", height: argBudget, overflow: "hidden", children: argWin.map((l, i) => /* @__PURE__ */ jsx6(Text6, { wrap: "truncate", children: l || " " }, off + i)) }),
|
|
2481
|
+
current.session ? /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
|
|
2482
|
+
/* @__PURE__ */ jsxs6(Text6, { color: theme.dim, children: [
|
|
2434
2483
|
"Session \xB7 ",
|
|
2435
2484
|
sessionCalls.length,
|
|
2436
2485
|
" calls"
|
|
2437
2486
|
] }),
|
|
2438
|
-
/* @__PURE__ */
|
|
2487
|
+
/* @__PURE__ */ jsx6(
|
|
2439
2488
|
Table,
|
|
2440
2489
|
{
|
|
2441
2490
|
columns: [
|
|
@@ -2446,59 +2495,136 @@ function AuditPanel({ active: active2, focused }) {
|
|
|
2446
2495
|
],
|
|
2447
2496
|
rows: sessionCalls.slice(0, Math.max(1, sessionRows - 2)).map((e) => [
|
|
2448
2497
|
{ value: e.decision, color: decisionColor(e.decision) },
|
|
2449
|
-
{ value: truncate(e.
|
|
2498
|
+
{ value: truncate(e.tool, 20), color: theme.accent },
|
|
2450
2499
|
{ value: truncate(e.reason ?? "\u2014", Math.max(16, cols - 48)) },
|
|
2451
|
-
{ value: ago(e.
|
|
2500
|
+
{ value: ago(e.at), dim: true }
|
|
2452
2501
|
])
|
|
2453
2502
|
}
|
|
2454
2503
|
)
|
|
2455
|
-
] }) : /* @__PURE__ */
|
|
2456
|
-
/* @__PURE__ */
|
|
2504
|
+
] }) : /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: "(no session id)" }),
|
|
2505
|
+
/* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: "\u2191\u2193 scroll arguments \xB7 \u2190/esc back to logs" })
|
|
2457
2506
|
] });
|
|
2458
2507
|
}
|
|
2459
|
-
const chip = (label, val, on) => /* @__PURE__ */
|
|
2460
|
-
/* @__PURE__ */
|
|
2508
|
+
const chip = (label, val, on) => /* @__PURE__ */ jsxs6(Text6, { children: [
|
|
2509
|
+
/* @__PURE__ */ jsxs6(Text6, { color: theme.dim, children: [
|
|
2461
2510
|
label,
|
|
2462
2511
|
":"
|
|
2463
2512
|
] }),
|
|
2464
|
-
/* @__PURE__ */
|
|
2465
|
-
/* @__PURE__ */
|
|
2513
|
+
/* @__PURE__ */ jsx6(Text6, { color: on ? theme.accentBright : theme.dim, children: val }),
|
|
2514
|
+
/* @__PURE__ */ jsx6(Text6, { children: " " })
|
|
2466
2515
|
] });
|
|
2467
|
-
|
|
2516
|
+
if (view === "sessions") {
|
|
2517
|
+
const headerRows2 = 5 + (editing ? 1 : 0);
|
|
2518
|
+
const listRows2 = Math.max(4, rows - headerRows2);
|
|
2519
|
+
const selC = Math.min(sessSel, Math.max(0, sessionsFiltered.length - 1));
|
|
2520
|
+
const start2 = Math.min(Math.max(0, selC - Math.floor((listRows2 - 1) / 2)), Math.max(0, sessionsFiltered.length - listRows2));
|
|
2521
|
+
const win = sessionsFiltered.slice(start2, start2 + listRows2);
|
|
2522
|
+
const loading2 = source === "cloud" ? agentsQ.loading : localQ.loading;
|
|
2523
|
+
return /* @__PURE__ */ jsx6(DataView, { loading: loading2, error: source === "cloud" ? agentsQ.error : localQ.error, children: /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
|
|
2524
|
+
strip,
|
|
2525
|
+
/* @__PURE__ */ jsxs6(Box6, { children: [
|
|
2526
|
+
srcChip,
|
|
2527
|
+
/* @__PURE__ */ jsx6(Text6, { color: theme.accentBright, bold: true, children: "SESSIONS" }),
|
|
2528
|
+
/* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " \xB7 logs (v) " }),
|
|
2529
|
+
chip("status", SESS_STATUS[si] ?? "all", si !== 0),
|
|
2530
|
+
chip("search", sessSearch || "\xB7", !!sessSearch)
|
|
2531
|
+
] }),
|
|
2532
|
+
/* @__PURE__ */ jsx6(Text6, { color: theme.dim, wrap: "truncate", children: focused ? "\u2191\u2193 select \xB7 enter \u2192 session logs \xB7 f status \xB7 / search \xB7 s source \xB7 v logs \xB7 c clear" : "press \u2192 to browse" }),
|
|
2533
|
+
editing === "sess-search" ? /* @__PURE__ */ jsxs6(Box6, { children: [
|
|
2534
|
+
/* @__PURE__ */ jsx6(Text6, { color: theme.warn, children: "search: " }),
|
|
2535
|
+
/* @__PURE__ */ jsx6(
|
|
2536
|
+
TextInput4,
|
|
2537
|
+
{
|
|
2538
|
+
value: sessSearch,
|
|
2539
|
+
onChange: (v) => {
|
|
2540
|
+
setSessSearch(v);
|
|
2541
|
+
setSessSel(0);
|
|
2542
|
+
},
|
|
2543
|
+
onSubmit: () => setEditing(null)
|
|
2544
|
+
}
|
|
2545
|
+
)
|
|
2546
|
+
] }) : null,
|
|
2547
|
+
/* @__PURE__ */ jsx6(Text6, { color: theme.dim, wrap: "truncate", children: `${sessionsFiltered.length} sessions \xB7 ${selC + 1}/${sessionsFiltered.length}${start2 ? ` \xB7 \u25B2${start2}` : ""}${start2 + listRows2 < sessionsFiltered.length ? ` \xB7 \u25BC${sessionsFiltered.length - start2 - listRows2}` : ""}` }),
|
|
2548
|
+
/* @__PURE__ */ jsx6(
|
|
2549
|
+
Table,
|
|
2550
|
+
{
|
|
2551
|
+
columns: [
|
|
2552
|
+
{ header: "", width: 2 },
|
|
2553
|
+
{ header: "STATUS", width: 8 },
|
|
2554
|
+
{ header: "AGENT", width: 18 },
|
|
2555
|
+
{ header: "SESSION", width: 10 },
|
|
2556
|
+
{ header: "CALLS", width: 6 },
|
|
2557
|
+
{ header: "DENY", width: 5 },
|
|
2558
|
+
{ header: "DLP", width: 4 },
|
|
2559
|
+
{ header: "TRUST", width: 5 },
|
|
2560
|
+
{ header: "LAST", width: 6 }
|
|
2561
|
+
],
|
|
2562
|
+
rows: win.map((r, i) => {
|
|
2563
|
+
const st = STATUS_DOT[r.status];
|
|
2564
|
+
const isSel = start2 + i === selC && focused;
|
|
2565
|
+
return [
|
|
2566
|
+
{ value: isSel ? "\u25B8" : "", color: theme.accentBright },
|
|
2567
|
+
{ value: `${st.ch} ${r.status}`, color: st.color },
|
|
2568
|
+
{ value: truncate(r.agent, 18), color: theme.accent, bold: isSel },
|
|
2569
|
+
{ value: r.id.slice(0, 8), dim: true },
|
|
2570
|
+
{ value: String(r.calls) },
|
|
2571
|
+
{ value: String(r.denies), color: r.denies ? theme.bad : void 0, dim: !r.denies },
|
|
2572
|
+
{ value: String(r.dlp), color: r.dlp ? theme.bad : void 0, dim: !r.dlp },
|
|
2573
|
+
{ value: r.trust != null ? String(r.trust) : "\u2014", dim: true },
|
|
2574
|
+
{ value: ago(r.lastAt), dim: true }
|
|
2575
|
+
];
|
|
2576
|
+
})
|
|
2577
|
+
}
|
|
2578
|
+
),
|
|
2579
|
+
sessionsFiltered.length === 0 ? /* @__PURE__ */ jsxs6(Text6, { color: theme.dim, children: [
|
|
2580
|
+
"(no sessions",
|
|
2581
|
+
source === "local" ? " in the local file" : "",
|
|
2582
|
+
")"
|
|
2583
|
+
] }) : null
|
|
2584
|
+
] }) });
|
|
2585
|
+
}
|
|
2586
|
+
const headerRows = 6 + (editing && editing !== "sess-search" ? 1 : 0);
|
|
2468
2587
|
const listRows = Math.max(4, rows - headerRows);
|
|
2469
|
-
const selClamped = Math.min(sel, Math.max(0,
|
|
2470
|
-
const maxStart = Math.max(0,
|
|
2588
|
+
const selClamped = Math.min(sel, Math.max(0, pageRows.length - 1));
|
|
2589
|
+
const maxStart = Math.max(0, pageRows.length - listRows);
|
|
2471
2590
|
const start = Math.min(Math.max(0, selClamped - Math.floor((listRows - 1) / 2)), maxStart);
|
|
2472
|
-
const windowed =
|
|
2591
|
+
const windowed = pageRows.slice(start, start + listRows);
|
|
2473
2592
|
const reasonW = Math.min(60, Math.max(12, cols - 67));
|
|
2474
|
-
|
|
2475
|
-
|
|
2593
|
+
const loading = (source === "cloud" ? cloudQ.loading : localQ.loading) && !editing;
|
|
2594
|
+
return /* @__PURE__ */ jsx6(DataView, { loading, error: source === "cloud" ? cloudQ.error : localQ.error, children: /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
|
|
2595
|
+
strip,
|
|
2596
|
+
/* @__PURE__ */ jsxs6(Box6, { children: [
|
|
2597
|
+
srcChip,
|
|
2598
|
+
/* @__PURE__ */ jsx6(Text6, { color: theme.accentBright, bold: true, children: "LOGS" }),
|
|
2599
|
+
/* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " \xB7 sessions (v) " }),
|
|
2476
2600
|
chip("dec", DECISIONS[di] ?? "all", di !== 0),
|
|
2477
2601
|
chip("sig", SIGNALS[gi] ?? "all", gi !== 0),
|
|
2478
2602
|
chip("tool", tool || "\xB7", !!tool),
|
|
2479
2603
|
chip("agent", agent || "\xB7", !!agent),
|
|
2480
|
-
chip("search", search || "\xB7", !!search)
|
|
2604
|
+
chip("search", search || "\xB7", !!search),
|
|
2605
|
+
sessFilter ? chip("sess", sessFilter.slice(0, 8), true) : null
|
|
2481
2606
|
] }),
|
|
2482
|
-
/* @__PURE__ */
|
|
2483
|
-
editing ? /* @__PURE__ */
|
|
2484
|
-
/* @__PURE__ */
|
|
2607
|
+
/* @__PURE__ */ jsx6(Text6, { color: theme.dim, wrap: "truncate", children: focused ? "\u2191\u2193 select \xB7 enter full entry \xB7 \u2190\u2192 page \xB7 f dec \xB7 g sig \xB7 t tool \xB7 n agent \xB7 / search \xB7 s source \xB7 v sessions \xB7 c clear" : "press \u2192 to browse" }),
|
|
2608
|
+
editing && editing !== "sess-search" ? /* @__PURE__ */ jsxs6(Box6, { children: [
|
|
2609
|
+
/* @__PURE__ */ jsxs6(Text6, { color: theme.warn, children: [
|
|
2485
2610
|
editing,
|
|
2486
2611
|
": "
|
|
2487
2612
|
] }),
|
|
2488
|
-
/* @__PURE__ */
|
|
2613
|
+
/* @__PURE__ */ jsx6(
|
|
2489
2614
|
TextInput4,
|
|
2490
2615
|
{
|
|
2491
2616
|
value: editing === "tool" ? tool : editing === "agent" ? agent : search,
|
|
2492
2617
|
onChange: (v) => {
|
|
2493
2618
|
(editing === "tool" ? setTool : editing === "agent" ? setAgent : setSearch)(v);
|
|
2494
|
-
|
|
2619
|
+
setPage(0);
|
|
2620
|
+
toTop();
|
|
2495
2621
|
},
|
|
2496
2622
|
onSubmit: () => setEditing(null)
|
|
2497
2623
|
}
|
|
2498
2624
|
)
|
|
2499
2625
|
] }) : null,
|
|
2500
|
-
/* @__PURE__ */
|
|
2501
|
-
/* @__PURE__ */
|
|
2626
|
+
/* @__PURE__ */ jsx6(Text6, { color: theme.dim, wrap: "truncate", children: `${total} matched \xB7 page ${Math.min(page + 1, pages)}/${pages} \xB7 ${PAGE}/page \xB7 ${pageRows.length ? selClamped + 1 : 0}/${pageRows.length}${start ? ` \xB7 \u25B2${start} newer` : ""}${start + listRows < pageRows.length ? ` \xB7 \u25BC${pageRows.length - start - listRows} older` : ""}` }),
|
|
2627
|
+
/* @__PURE__ */ jsx6(
|
|
2502
2628
|
Table,
|
|
2503
2629
|
{
|
|
2504
2630
|
columns: [
|
|
@@ -2512,118 +2638,37 @@ function AuditPanel({ active: active2, focused }) {
|
|
|
2512
2638
|
],
|
|
2513
2639
|
rows: windowed.map((e, i) => rowFor(e, start + i === selClamped && focused, reasonW))
|
|
2514
2640
|
}
|
|
2515
|
-
)
|
|
2641
|
+
),
|
|
2642
|
+
pageRows.length === 0 ? /* @__PURE__ */ jsxs6(Text6, { color: theme.dim, children: [
|
|
2643
|
+
"(no entries",
|
|
2644
|
+
source === "local" ? " in the local file" : "",
|
|
2645
|
+
" \u2014 c clears filters)"
|
|
2646
|
+
] }) : null
|
|
2516
2647
|
] }) });
|
|
2517
2648
|
}
|
|
2518
2649
|
function rowFor(e, active2, reasonW) {
|
|
2519
2650
|
return [
|
|
2520
2651
|
{ value: active2 ? "\u25B8" : "", color: theme.accentBright },
|
|
2521
2652
|
{ value: e.decision, color: decisionColor(e.decision) },
|
|
2522
|
-
{ value: truncate(e.
|
|
2523
|
-
{ value: truncate(e.
|
|
2524
|
-
{ value: truncate(e.reason ?? "\u2014", reasonW) },
|
|
2525
|
-
{ value: e.
|
|
2526
|
-
{ value: ago(e.
|
|
2653
|
+
{ value: truncate(e.tool, 18), color: theme.accent },
|
|
2654
|
+
{ value: truncate(e.agent ?? "\u2014", 14), dim: true },
|
|
2655
|
+
{ value: truncate(e.reason ?? e.args ?? "\u2014", reasonW) },
|
|
2656
|
+
{ value: e.dlp.length ? String(e.dlp.length) : "0", color: e.dlp.length ? theme.bad : void 0, dim: !e.dlp.length },
|
|
2657
|
+
{ value: ago(e.at), dim: true }
|
|
2527
2658
|
];
|
|
2528
2659
|
}
|
|
2529
2660
|
function Detail({ label, value, color }) {
|
|
2530
|
-
return /* @__PURE__ */
|
|
2531
|
-
/* @__PURE__ */
|
|
2532
|
-
/* @__PURE__ */
|
|
2661
|
+
return /* @__PURE__ */ jsxs6(Box6, { children: [
|
|
2662
|
+
/* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: label.padEnd(9) }),
|
|
2663
|
+
/* @__PURE__ */ jsx6(Text6, { color, wrap: "truncate", children: value })
|
|
2533
2664
|
] });
|
|
2534
2665
|
}
|
|
2535
2666
|
|
|
2536
|
-
// src/tui/panels/Agents.tsx
|
|
2537
|
-
import { Box as Box8, Text as Text8, useInput as useInput6 } from "ink";
|
|
2538
|
-
import { useState as useState7 } from "react";
|
|
2539
|
-
import { Fragment as Fragment5, jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
2540
|
-
var statusColor = (s) => s === "active" ? theme.ok : s === "idle" ? theme.warn : theme.dim;
|
|
2541
|
-
var sevColor = (s) => s === "high" ? theme.bad : s === "medium" ? theme.warn : theme.dim;
|
|
2542
|
-
function Bar({ label, value, max = 100, width = 16, color = theme.accent }) {
|
|
2543
|
-
const filled = max > 0 ? Math.round(Math.min(value, max) / max * width) : 0;
|
|
2544
|
-
return /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
|
|
2545
|
-
/* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: label.padEnd(9) }),
|
|
2546
|
-
/* @__PURE__ */ jsx8(Text8, { color, children: "\u2588".repeat(filled) }),
|
|
2547
|
-
/* @__PURE__ */ jsx8(Text8, { color: "#233457", children: "\u2591".repeat(Math.max(0, width - filled)) }),
|
|
2548
|
-
/* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: " " + value })
|
|
2549
|
-
] });
|
|
2550
|
-
}
|
|
2551
|
-
function AgentsPanel({ focused }) {
|
|
2552
|
-
const live2 = useLoader(() => api.agents.live({ limit: 30, includeDeactivated: true }));
|
|
2553
|
-
usePoll(live2.reload, 6e3, focused ? false : true);
|
|
2554
|
-
const [sel, setSel] = useState7(0);
|
|
2555
|
-
const agents = (live2.data?.agents ?? []).filter((a) => a.agent_id);
|
|
2556
|
-
const selected = agents[Math.min(sel, Math.max(0, agents.length - 1))];
|
|
2557
|
-
const detail = useLoader(() => selected?.agent_id ? api.agents.get(selected.agent_id) : Promise.resolve(null), [selected?.agent_id]);
|
|
2558
|
-
useInput6(
|
|
2559
|
-
(_input, key) => {
|
|
2560
|
-
if (key.upArrow) setSel((n) => Math.max(0, n - 1));
|
|
2561
|
-
else if (key.downArrow) setSel((n) => Math.min(agents.length - 1, n + 1));
|
|
2562
|
-
},
|
|
2563
|
-
{ isActive: focused }
|
|
2564
|
-
);
|
|
2565
|
-
const d = detail.data;
|
|
2566
|
-
const base = d?.baseline;
|
|
2567
|
-
const radar = base?.radar;
|
|
2568
|
-
const anomalies2 = d?.anomalies ?? [];
|
|
2569
|
-
const tmap = d?.trust_map;
|
|
2570
|
-
const tools = tmap?.tools ?? [];
|
|
2571
|
-
const resources = tmap?.resources ?? [];
|
|
2572
|
-
const listW = 30;
|
|
2573
|
-
return /* @__PURE__ */ jsx8(DataView, { loading: live2.loading && !live2.data, error: live2.error, empty: !!live2.data && agents.length === 0, emptyText: "No identified agents yet.", children: /* @__PURE__ */ jsxs8(Box8, { children: [
|
|
2574
|
-
/* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", width: listW, marginRight: 2, overflow: "hidden", children: [
|
|
2575
|
-
/* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: focused ? "\u2191\u2193 select agent" : "press \u2192 to browse" }),
|
|
2576
|
-
agents.slice(0, 16).map((a, i) => /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", backgroundColor: focused && i === sel ? "#1c2f63" : void 0, bold: i === sel, children: [
|
|
2577
|
-
/* @__PURE__ */ jsxs8(Text8, { color: statusColor(a.status), children: [
|
|
2578
|
-
a.status === "active" ? "\u25CF" : a.status === "idle" ? "\u25D0" : "\u25CB",
|
|
2579
|
-
" "
|
|
2580
|
-
] }),
|
|
2581
|
-
/* @__PURE__ */ jsx8(Text8, { color: theme.accent, children: truncate(a.agent_name ?? a.agent_id ?? "?", 16).padEnd(17) }),
|
|
2582
|
-
/* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: `t${a.trust_score}` })
|
|
2583
|
-
] }, a.session_id))
|
|
2584
|
-
] }),
|
|
2585
|
-
/* @__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: [
|
|
2586
|
-
/* @__PURE__ */ jsxs8(Box8, { children: [
|
|
2587
|
-
/* @__PURE__ */ jsx8(Text8, { bold: true, color: theme.accentBright, children: truncate(selected.agent_name ?? selected.agent_id ?? "?", 22) }),
|
|
2588
|
-
/* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: ` trust ${base?.trustScore ?? "\u2014"}/100 \xB7 ${base?.character ?? ""}` })
|
|
2589
|
-
] }),
|
|
2590
|
-
base?.characterBlurb ? /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: truncate(String(base.characterBlurb), 70) }) : null,
|
|
2591
|
-
radar ? /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexDirection: "column", children: [
|
|
2592
|
-
/* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "Behaviour radar (0\u2013100)" }),
|
|
2593
|
-
/* @__PURE__ */ jsx8(Bar, { label: "read", value: Math.round((radar.read ?? 0) * 100) }),
|
|
2594
|
-
/* @__PURE__ */ jsx8(Bar, { label: "write", value: Math.round((radar.write ?? 0) * 100), color: theme.warn }),
|
|
2595
|
-
/* @__PURE__ */ jsx8(Bar, { label: "execute", value: Math.round((radar.execute ?? 0) * 100), color: theme.warn }),
|
|
2596
|
-
/* @__PURE__ */ jsx8(Bar, { label: "network", value: Math.round((radar.network ?? 0) * 100) }),
|
|
2597
|
-
/* @__PURE__ */ jsx8(Bar, { label: "complian.", value: Math.round((radar.compliance ?? 0) * 100), color: theme.ok })
|
|
2598
|
-
] }) : null,
|
|
2599
|
-
/* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexDirection: "column", children: [
|
|
2600
|
-
/* @__PURE__ */ jsxs8(Text8, { color: theme.dim, children: [
|
|
2601
|
-
"Anomalies ",
|
|
2602
|
-
anomalies2.length ? `(${anomalies2.length})` : ""
|
|
2603
|
-
] }),
|
|
2604
|
-
anomalies2.length === 0 ? /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: " none" }) : anomalies2.slice(0, 4).map((an, i) => /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
|
|
2605
|
-
/* @__PURE__ */ jsx8(Text8, { color: sevColor(String(an.severity)), children: ` ${String(an.severity).toUpperCase().slice(0, 4).padEnd(5)}` }),
|
|
2606
|
-
/* @__PURE__ */ jsx8(Text8, { color: theme.accent, children: String(an.kind).padEnd(12) }),
|
|
2607
|
-
/* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: truncate(String(an.description ?? ""), 44) })
|
|
2608
|
-
] }, i))
|
|
2609
|
-
] }),
|
|
2610
|
-
/* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexDirection: "column", children: [
|
|
2611
|
-
/* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "Trust map \xB7 top tools" }),
|
|
2612
|
-
tools.slice(0, 4).map((t, i) => /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
|
|
2613
|
-
/* @__PURE__ */ jsx8(Text8, { color: t.anomalous ? theme.bad : theme.accent, children: ` ${truncate(String(t.name), 16).padEnd(17)}` }),
|
|
2614
|
-
/* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: `${t.count}\xD7 ${t.permission ?? ""}` })
|
|
2615
|
-
] }, i)),
|
|
2616
|
-
resources.slice(0, 3).map((r, i) => /* @__PURE__ */ jsx8(Text8, { wrap: "truncate", color: r.anomalous ? theme.bad : theme.dim, children: ` ${r.type === "domain" ? "\u{1F310}" : "\u{1F4C1}"} ${truncate(String(r.value), 40)} ${r.count}\xD7` }, "r" + i))
|
|
2617
|
-
] })
|
|
2618
|
-
] }) })
|
|
2619
|
-
] }) });
|
|
2620
|
-
}
|
|
2621
|
-
|
|
2622
2667
|
// src/tui/panels/Settings.tsx
|
|
2623
|
-
import { Box as
|
|
2668
|
+
import { Box as Box7, Text as Text7, useInput as useInput6 } from "ink";
|
|
2624
2669
|
import TextInput5 from "ink-text-input";
|
|
2625
|
-
import { useState as
|
|
2626
|
-
import { jsx as
|
|
2670
|
+
import { useState as useState7 } from "react";
|
|
2671
|
+
import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
2627
2672
|
var EVENTS = ["denials", "allowed", "all"];
|
|
2628
2673
|
function alertBodyFor(channel) {
|
|
2629
2674
|
const c2 = channel.trim();
|
|
@@ -2635,12 +2680,12 @@ function alertBodyFor(channel) {
|
|
|
2635
2680
|
function SettingsPanel({ active: active2, focused }) {
|
|
2636
2681
|
void active2;
|
|
2637
2682
|
const { cols, rows } = usePanelSize();
|
|
2638
|
-
const [sel, setSel] =
|
|
2639
|
-
const [editing, setEditing] =
|
|
2640
|
-
const [input, setInput] =
|
|
2641
|
-
const [confirmDel, setConfirmDel] =
|
|
2642
|
-
const [msg, setMsg] =
|
|
2643
|
-
const [busy, setBusy] =
|
|
2683
|
+
const [sel, setSel] = useState7(0);
|
|
2684
|
+
const [editing, setEditing] = useState7(null);
|
|
2685
|
+
const [input, setInput] = useState7("");
|
|
2686
|
+
const [confirmDel, setConfirmDel] = useState7(null);
|
|
2687
|
+
const [msg, setMsg] = useState7(null);
|
|
2688
|
+
const [busy, setBusy] = useState7(false);
|
|
2644
2689
|
const localQ = useLoader(() => api.settings.getLocalLogs());
|
|
2645
2690
|
const whQ = useLoader(() => api.settings.getWebhooks());
|
|
2646
2691
|
const alertQ = useLoader(() => api.settings.getAlerts());
|
|
@@ -2710,7 +2755,7 @@ function SettingsPanel({ active: active2, focused }) {
|
|
|
2710
2755
|
run("alert rule added", () => api.settings.createAlert(alertBodyFor(v)), alertQ.reload);
|
|
2711
2756
|
}
|
|
2712
2757
|
};
|
|
2713
|
-
|
|
2758
|
+
useInput6(
|
|
2714
2759
|
(inp, key) => {
|
|
2715
2760
|
setMsg(null);
|
|
2716
2761
|
if (key.upArrow) {
|
|
@@ -2749,7 +2794,7 @@ function SettingsPanel({ active: active2, focused }) {
|
|
|
2749
2794
|
const loading = localQ.loading && !localQ.data || whQ.loading && !whQ.data || alertQ.loading && !alertQ.data;
|
|
2750
2795
|
const error = localQ.error ?? whQ.error ?? alertQ.error;
|
|
2751
2796
|
const mark = (r) => keyOf(r) === keyOf(cur) && focused ? "\u25B8 " : " ";
|
|
2752
|
-
const onOff = (on) => on ? /* @__PURE__ */
|
|
2797
|
+
const onOff = (on) => on ? /* @__PURE__ */ jsx7(Text7, { color: theme.ok, children: "on " }) : /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "off" });
|
|
2753
2798
|
const chanOf = (rule) => [...(rule.slackUrls ?? []).map((u) => "slack " + u), ...rule.emails ?? [], ...(rule.telegram ?? []).map((t) => "tg " + t)].join(", ") || "\u2014";
|
|
2754
2799
|
const listBudget = Math.max(4, rows - 8);
|
|
2755
2800
|
const start = Math.min(Math.max(0, selClamped - Math.floor(listBudget / 2)), Math.max(0, rowsAll.length - listBudget));
|
|
@@ -2757,94 +2802,92 @@ function SettingsPanel({ active: active2, focused }) {
|
|
|
2757
2802
|
const i = rowsAll.findIndex((x) => keyOf(x) === keyOf(r));
|
|
2758
2803
|
return i >= start && i < start + listBudget;
|
|
2759
2804
|
};
|
|
2760
|
-
return /* @__PURE__ */
|
|
2761
|
-
/* @__PURE__ */
|
|
2762
|
-
editing ? /* @__PURE__ */
|
|
2763
|
-
/* @__PURE__ */
|
|
2764
|
-
/* @__PURE__ */
|
|
2765
|
-
] }) : msg ? /* @__PURE__ */
|
|
2766
|
-
inWindow(rowsAll[0]) || inWindow(rowsAll[1]) ? /* @__PURE__ */
|
|
2767
|
-
/* @__PURE__ */
|
|
2805
|
+
return /* @__PURE__ */ jsx7(DataView, { loading, error, children: /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
|
|
2806
|
+
/* @__PURE__ */ jsx7(Text7, { 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" }),
|
|
2807
|
+
editing ? /* @__PURE__ */ jsxs7(Box7, { children: [
|
|
2808
|
+
/* @__PURE__ */ jsx7(Text7, { color: theme.warn, children: editing === "path" ? "local log path: " : editing === "wh-url" ? "webhook url: " : "channel (slack url / email / telegram id): " }),
|
|
2809
|
+
/* @__PURE__ */ jsx7(TextInput5, { value: input, onChange: setInput, onSubmit: submitInput })
|
|
2810
|
+
] }) : msg ? /* @__PURE__ */ jsx7(Text7, { color: msg.level === "bad" ? theme.bad : theme.ok, children: truncate(msg.text, cols) }) : /* @__PURE__ */ jsx7(Text7, { children: " " }),
|
|
2811
|
+
inWindow(rowsAll[0]) || inWindow(rowsAll[1]) ? /* @__PURE__ */ jsxs7(Box7, { marginTop: 1, flexDirection: "column", children: [
|
|
2812
|
+
/* @__PURE__ */ jsxs7(Text7, { bold: true, color: theme.accentBright, children: [
|
|
2768
2813
|
"LOCAL LOGS ",
|
|
2769
|
-
/* @__PURE__ */
|
|
2814
|
+
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "\u2014 hooks mirror every decision to a file on the agent machine" })
|
|
2770
2815
|
] }),
|
|
2771
|
-
/* @__PURE__ */
|
|
2772
|
-
/* @__PURE__ */
|
|
2773
|
-
/* @__PURE__ */
|
|
2816
|
+
/* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
|
|
2817
|
+
/* @__PURE__ */ jsx7(Text7, { color: keyOf(cur) === "ll-enabled" && focused ? theme.accentBright : theme.dim, children: mark(rowsAll[0]) }),
|
|
2818
|
+
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "enabled ".padEnd(10) }),
|
|
2774
2819
|
onOff(!!local?.enabled),
|
|
2775
|
-
/* @__PURE__ */
|
|
2820
|
+
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " enter toggles" })
|
|
2776
2821
|
] }),
|
|
2777
|
-
/* @__PURE__ */
|
|
2778
|
-
/* @__PURE__ */
|
|
2779
|
-
/* @__PURE__ */
|
|
2780
|
-
local?.path ? /* @__PURE__ */
|
|
2822
|
+
/* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
|
|
2823
|
+
/* @__PURE__ */ jsx7(Text7, { color: keyOf(cur) === "ll-path" && focused ? theme.accentBright : theme.dim, children: mark(rowsAll[1]) }),
|
|
2824
|
+
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "path ".padEnd(10) }),
|
|
2825
|
+
local?.path ? /* @__PURE__ */ jsx7(Text7, { color: theme.accent, children: truncate(local.path, cols - 14) }) : /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "not set \u2014 enter to edit" })
|
|
2781
2826
|
] })
|
|
2782
2827
|
] }) : null,
|
|
2783
|
-
/* @__PURE__ */
|
|
2784
|
-
/* @__PURE__ */
|
|
2828
|
+
/* @__PURE__ */ jsxs7(Box7, { marginTop: 1, flexDirection: "column", children: [
|
|
2829
|
+
/* @__PURE__ */ jsxs7(Text7, { bold: true, color: theme.accentBright, children: [
|
|
2785
2830
|
"WEBHOOKS ",
|
|
2786
|
-
/* @__PURE__ */
|
|
2831
|
+
/* @__PURE__ */ jsxs7(Text7, { color: theme.dim, children: [
|
|
2787
2832
|
"\u2014 POST every matching event to your endpoint (",
|
|
2788
2833
|
webhooks.length,
|
|
2789
2834
|
")"
|
|
2790
2835
|
] })
|
|
2791
2836
|
] }),
|
|
2792
|
-
webhooks.filter((wh) => inWindow({ kind: "wh", wh })).map((wh) => /* @__PURE__ */
|
|
2793
|
-
/* @__PURE__ */
|
|
2837
|
+
webhooks.filter((wh) => inWindow({ kind: "wh", wh })).map((wh) => /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
|
|
2838
|
+
/* @__PURE__ */ jsx7(Text7, { color: keyOf(cur) === "wh:" + wh.id && focused ? theme.accentBright : theme.dim, children: mark({ kind: "wh", wh }) }),
|
|
2794
2839
|
onOff(wh.enabled),
|
|
2795
|
-
/* @__PURE__ */
|
|
2796
|
-
/* @__PURE__ */
|
|
2840
|
+
/* @__PURE__ */ jsx7(Text7, { color: theme.warn, children: (" " + wh.events).padEnd(9) }),
|
|
2841
|
+
/* @__PURE__ */ jsx7(Text7, { color: theme.accent, children: truncate(wh.url, cols - 16) })
|
|
2797
2842
|
] }, wh.id)),
|
|
2798
|
-
inWindow(rowsAll.find((r) => r.kind === "wh-add")) ? /* @__PURE__ */
|
|
2799
|
-
/* @__PURE__ */
|
|
2800
|
-
/* @__PURE__ */
|
|
2843
|
+
inWindow(rowsAll.find((r) => r.kind === "wh-add")) ? /* @__PURE__ */ jsxs7(Text7, { children: [
|
|
2844
|
+
/* @__PURE__ */ jsx7(Text7, { color: cur.kind === "wh-add" && focused ? theme.accentBright : theme.dim, children: mark({ kind: "wh-add" }) }),
|
|
2845
|
+
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "+ add webhook (enter)" })
|
|
2801
2846
|
] }) : null
|
|
2802
2847
|
] }),
|
|
2803
|
-
/* @__PURE__ */
|
|
2804
|
-
/* @__PURE__ */
|
|
2848
|
+
/* @__PURE__ */ jsxs7(Box7, { marginTop: 1, flexDirection: "column", children: [
|
|
2849
|
+
/* @__PURE__ */ jsxs7(Text7, { bold: true, color: theme.accentBright, children: [
|
|
2805
2850
|
"ALERTS ",
|
|
2806
|
-
/* @__PURE__ */
|
|
2851
|
+
/* @__PURE__ */ jsxs7(Text7, { color: theme.dim, children: [
|
|
2807
2852
|
"\u2014 notify on a signal spike (",
|
|
2808
2853
|
alerts.length,
|
|
2809
2854
|
")"
|
|
2810
2855
|
] })
|
|
2811
2856
|
] }),
|
|
2812
|
-
alerts.filter((rule) => inWindow({ kind: "alert", rule })).map((rule) => /* @__PURE__ */
|
|
2813
|
-
/* @__PURE__ */
|
|
2857
|
+
alerts.filter((rule) => inWindow({ kind: "alert", rule })).map((rule) => /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
|
|
2858
|
+
/* @__PURE__ */ jsx7(Text7, { color: keyOf(cur) === "alert:" + rule.id && focused ? theme.accentBright : theme.dim, children: mark({ kind: "alert", rule }) }),
|
|
2814
2859
|
onOff(rule.enabled),
|
|
2815
|
-
/* @__PURE__ */
|
|
2816
|
-
/* @__PURE__ */
|
|
2817
|
-
/* @__PURE__ */
|
|
2860
|
+
/* @__PURE__ */ jsx7(Text7, { color: theme.warn, children: (" " + rule.signal).padEnd(11) }),
|
|
2861
|
+
/* @__PURE__ */ jsx7(Text7, { children: `\u2265${rule.threshold}/${rule.windowSeconds}s ` }),
|
|
2862
|
+
/* @__PURE__ */ jsx7(Text7, { color: theme.accent, children: truncate(chanOf(rule), cols - 26) })
|
|
2818
2863
|
] }, rule.id)),
|
|
2819
|
-
inWindow(rowsAll[rowsAll.length - 1]) ? /* @__PURE__ */
|
|
2820
|
-
/* @__PURE__ */
|
|
2821
|
-
/* @__PURE__ */
|
|
2864
|
+
inWindow(rowsAll[rowsAll.length - 1]) ? /* @__PURE__ */ jsxs7(Text7, { children: [
|
|
2865
|
+
/* @__PURE__ */ jsx7(Text7, { color: cur.kind === "alert-add" && focused ? theme.accentBright : theme.dim, children: mark({ kind: "alert-add" }) }),
|
|
2866
|
+
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "+ add alert (enter \u2014 one channel: slack url, email or telegram chat id)" })
|
|
2822
2867
|
] }) : null
|
|
2823
2868
|
] })
|
|
2824
2869
|
] }) });
|
|
2825
2870
|
}
|
|
2826
2871
|
|
|
2827
2872
|
// src/tui/App.tsx
|
|
2828
|
-
import { jsx as
|
|
2873
|
+
import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
2829
2874
|
function LiveHint() {
|
|
2830
|
-
return /* @__PURE__ */
|
|
2831
|
-
/* @__PURE__ */
|
|
2832
|
-
/* @__PURE__ */
|
|
2833
|
-
/* @__PURE__ */
|
|
2834
|
-
/* @__PURE__ */
|
|
2875
|
+
return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
|
|
2876
|
+
/* @__PURE__ */ jsx8(Text8, { color: theme.accentBright, bold: true, children: "\u25B6 REALTIME CONSOLE" }),
|
|
2877
|
+
/* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "Fullscreen live monitor \u2014 streaming tool calls, active charts," }),
|
|
2878
|
+
/* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "counters, DLP hits and denial alerts. Updates every 2s." }),
|
|
2879
|
+
/* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: /* @__PURE__ */ jsxs8(Text8, { children: [
|
|
2835
2880
|
"press ",
|
|
2836
|
-
/* @__PURE__ */
|
|
2881
|
+
/* @__PURE__ */ jsx8(Text8, { color: theme.accent, children: "enter" }),
|
|
2837
2882
|
" to go live"
|
|
2838
2883
|
] }) })
|
|
2839
2884
|
] });
|
|
2840
2885
|
}
|
|
2841
2886
|
var SECTIONS = [
|
|
2842
2887
|
{ label: "Live", Panel: LiveHint },
|
|
2843
|
-
{ label: "Agents", Panel: AgentsPanel },
|
|
2844
2888
|
{ label: "Policies", Panel: PoliciesPanel },
|
|
2845
2889
|
{ label: "Rate Limit", Panel: RateLimitPanel },
|
|
2846
2890
|
{ label: "DLP", Panel: DlpPanel },
|
|
2847
|
-
{ label: "Stats", Panel: StatsPanel },
|
|
2848
2891
|
{ label: "Audit", Panel: AuditPanel },
|
|
2849
2892
|
{ label: "Settings", Panel: SettingsPanel }
|
|
2850
2893
|
];
|
|
@@ -2852,22 +2895,22 @@ var BANNER_HEX = ["#1432A0", "#2850BE", "#3C6ED7", "#5A8CE6", "#82AAF0", "#AAC8F
|
|
|
2852
2895
|
function Banner({ cols }) {
|
|
2853
2896
|
const wide = cols >= 82;
|
|
2854
2897
|
if (!wide) {
|
|
2855
|
-
return /* @__PURE__ */
|
|
2856
|
-
/* @__PURE__ */
|
|
2857
|
-
/* @__PURE__ */
|
|
2898
|
+
return /* @__PURE__ */ jsxs8(Box8, { children: [
|
|
2899
|
+
/* @__PURE__ */ jsx8(Text8, { bold: true, color: theme.accentBright, children: "SolonGate" }),
|
|
2900
|
+
/* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: " \u2014 security control center" })
|
|
2858
2901
|
] });
|
|
2859
2902
|
}
|
|
2860
|
-
return /* @__PURE__ */
|
|
2861
|
-
BANNER_FULL.map((line, i) => /* @__PURE__ */
|
|
2862
|
-
/* @__PURE__ */
|
|
2903
|
+
return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
|
|
2904
|
+
BANNER_FULL.map((line, i) => /* @__PURE__ */ jsx8(Text8, { bold: true, color: BANNER_HEX[i], children: line }, i)),
|
|
2905
|
+
/* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: " security control center \xB7 manage policies, rate limits, DLP & more" })
|
|
2863
2906
|
] });
|
|
2864
2907
|
}
|
|
2865
2908
|
function App() {
|
|
2866
2909
|
const { exit } = useApp();
|
|
2867
|
-
const [section, setSection] =
|
|
2868
|
-
const [focus, setFocus] =
|
|
2869
|
-
const [help, setHelp] =
|
|
2870
|
-
|
|
2910
|
+
const [section, setSection] = useState8(0);
|
|
2911
|
+
const [focus, setFocus] = useState8("nav");
|
|
2912
|
+
const [help, setHelp] = useState8(false);
|
|
2913
|
+
useInput7((input, key) => {
|
|
2871
2914
|
if (help) {
|
|
2872
2915
|
setHelp(false);
|
|
2873
2916
|
return;
|
|
@@ -2888,44 +2931,44 @@ function App() {
|
|
|
2888
2931
|
});
|
|
2889
2932
|
const { cols, rows } = useTermSize();
|
|
2890
2933
|
const current = SECTIONS[section];
|
|
2891
|
-
if (help) return /* @__PURE__ */
|
|
2934
|
+
if (help) return /* @__PURE__ */ jsx8(HelpOverlay, { cols, rows });
|
|
2892
2935
|
if (current.label === "Live" && focus === "panel") {
|
|
2893
|
-
return /* @__PURE__ */
|
|
2936
|
+
return /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", width: cols, height: rows, children: /* @__PURE__ */ jsx8(LivePanel, { active: true, focused: true }) });
|
|
2894
2937
|
}
|
|
2895
2938
|
const Panel = current.Panel;
|
|
2896
|
-
return /* @__PURE__ */
|
|
2897
|
-
/* @__PURE__ */
|
|
2898
|
-
/* @__PURE__ */
|
|
2899
|
-
/* @__PURE__ */
|
|
2900
|
-
/* @__PURE__ */
|
|
2939
|
+
return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", width: cols, height: rows, paddingX: 1, paddingTop: 1, children: [
|
|
2940
|
+
/* @__PURE__ */ jsx8(Banner, { cols }),
|
|
2941
|
+
/* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexGrow: 1, children: [
|
|
2942
|
+
/* @__PURE__ */ jsx8(Box8, { flexDirection: "column", width: 16, borderStyle: "round", borderColor: focus === "nav" ? theme.accent : "gray", paddingX: 1, children: SECTIONS.map((s, i) => /* @__PURE__ */ jsx8(Text8, { color: i === section ? theme.accentBright : void 0, bold: i === section, children: (i === section ? "\u25B8 " : " ") + s.label }, s.label)) }),
|
|
2943
|
+
/* @__PURE__ */ jsx8(Box8, { flexGrow: 1, borderStyle: "round", borderColor: focus === "panel" ? theme.accent : "gray", paddingX: 1, paddingY: 0, children: /* @__PURE__ */ jsx8(Panel, { active: focus === "panel" || current.label !== "Live", focused: focus === "panel" }) })
|
|
2901
2944
|
] }),
|
|
2902
|
-
/* @__PURE__ */
|
|
2945
|
+
/* @__PURE__ */ jsx8(Box8, { children: focus === "nav" ? /* @__PURE__ */ jsx8(KeyHints, { hints: [["\u2191\u2193", "section"], ["\u2192/enter", "open"], ["?", "help"], ["q", "quit"]] }) : /* @__PURE__ */ jsx8(KeyHints, { hints: [["\u2190/esc", "back"], ["\u2191\u2193", "in-panel"], ["space/s", "act"]] }) })
|
|
2903
2946
|
] });
|
|
2904
2947
|
}
|
|
2905
2948
|
var HELP = [
|
|
2906
2949
|
["Global", [["\u2191\u2193", "move between sections"], ["\u2192 / enter", "open a section"], ["\u2190 / esc", "back to the menu"], ["?", "this help"], ["q", "quit"]]],
|
|
2907
2950
|
["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)"]]],
|
|
2908
|
-
["Policies", [["\u2191\u2193", "browse / select"], ["a", "activate (pin) selected policy"], ["x", "deactivate \u2014 no active policy"], ["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"], ["
|
|
2951
|
+
["Policies", [["\u2191\u2193", "browse / select"], ["a", "activate (pin) selected policy"], ["x", "deactivate \u2014 no active policy"], ["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"], ["s", "save"], ["x", "discard"]]],
|
|
2909
2952
|
["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"]]],
|
|
2910
|
-
["Audit", [["\u2191\u2193", "select (list scrolls)"], ["enter", "full entry
|
|
2953
|
+
["Audit", [["v", "logs \u2194 sessions"], ["s", "source: cloud \u2194 local"], ["\u2190 \u2192", "prev / next page (500 each)"], ["\u2191\u2193", "select (list scrolls)"], ["enter", "full entry / session logs"], ["f / g", "decision / signal filter"], ["t / n / /", "tool / agent / search"], ["c", "clear filters"]]],
|
|
2911
2954
|
["Settings", [["\u2191\u2193", "move"], ["enter / space", "toggle \xB7 edit \xB7 add"], ["e", "webhook events"], ["d d", "delete"], ["r", "refresh"]]]
|
|
2912
2955
|
];
|
|
2913
2956
|
function HelpOverlay({ cols, rows }) {
|
|
2914
|
-
return /* @__PURE__ */
|
|
2915
|
-
/* @__PURE__ */
|
|
2916
|
-
/* @__PURE__ */
|
|
2917
|
-
/* @__PURE__ */
|
|
2918
|
-
keys.map(([k, desc]) => /* @__PURE__ */
|
|
2919
|
-
/* @__PURE__ */
|
|
2920
|
-
/* @__PURE__ */
|
|
2957
|
+
return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", width: cols, height: rows, paddingX: 2, paddingTop: 1, children: [
|
|
2958
|
+
/* @__PURE__ */ jsx8(Text8, { bold: true, color: theme.accentBright, children: "SolonGate \u2014 keyboard shortcuts" }),
|
|
2959
|
+
/* @__PURE__ */ jsx8(Box8, { marginTop: 1, flexDirection: "column", children: HELP.map(([group, keys]) => /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", marginBottom: 1, children: [
|
|
2960
|
+
/* @__PURE__ */ jsx8(Text8, { bold: true, color: theme.accent, children: group }),
|
|
2961
|
+
keys.map(([k, desc]) => /* @__PURE__ */ jsxs8(Text8, { children: [
|
|
2962
|
+
/* @__PURE__ */ jsx8(Text8, { color: theme.accentBright, children: (" " + k).padEnd(16) }),
|
|
2963
|
+
/* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: desc })
|
|
2921
2964
|
] }, k))
|
|
2922
2965
|
] }, group)) }),
|
|
2923
|
-
/* @__PURE__ */
|
|
2966
|
+
/* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "press any key to close" })
|
|
2924
2967
|
] });
|
|
2925
2968
|
}
|
|
2926
2969
|
|
|
2927
2970
|
// src/tui/index.tsx
|
|
2928
|
-
import { jsx as
|
|
2971
|
+
import { jsx as jsx9 } from "react/jsx-runtime";
|
|
2929
2972
|
async function launchTui() {
|
|
2930
2973
|
if (!process.stdout.isTTY || !process.stdin.isTTY) {
|
|
2931
2974
|
process.stderr.write(
|
|
@@ -2939,7 +2982,7 @@ async function launchTui() {
|
|
|
2939
2982
|
}
|
|
2940
2983
|
process.stdout.write("\x1B[?1049h\x1B[H");
|
|
2941
2984
|
try {
|
|
2942
|
-
const { waitUntilExit } = render(/* @__PURE__ */
|
|
2985
|
+
const { waitUntilExit } = render(/* @__PURE__ */ jsx9(App, {}));
|
|
2943
2986
|
await waitUntilExit();
|
|
2944
2987
|
} finally {
|
|
2945
2988
|
process.stdout.write("\x1B[?1049l");
|