@solongate/proxy 0.81.22 → 0.81.24
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 +602 -452
- package/dist/tui/index.js +550 -396
- package/dist/tui/local-log.d.ts +22 -0
- package/package.json +1 -1
package/dist/tui/index.js
CHANGED
|
@@ -8,7 +8,7 @@ var __export = (target, all) => {
|
|
|
8
8
|
import { render } from "ink";
|
|
9
9
|
|
|
10
10
|
// src/tui/App.tsx
|
|
11
|
-
import { Box as
|
|
11
|
+
import { Box as Box9, Text as Text9, useApp, useInput as useInput8 } from "ink";
|
|
12
12
|
import { useState as useState9 } from "react";
|
|
13
13
|
|
|
14
14
|
// src/cli-utils.ts
|
|
@@ -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 : {};
|
|
@@ -614,8 +649,7 @@ var CONFIG = loadConfig();
|
|
|
614
649
|
var SPIN = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
615
650
|
var BG = "#12234f";
|
|
616
651
|
var DIM_FLOOR = "#233457";
|
|
617
|
-
var
|
|
618
|
-
var RING = join3(process.cwd(), ".solongate", ".eval-ring.jsonl");
|
|
652
|
+
var RING = join4(process.cwd(), ".solongate", ".eval-ring.jsonl");
|
|
619
653
|
var hhmmss = (ts) => {
|
|
620
654
|
const d = new Date(ts);
|
|
621
655
|
return Number.isNaN(d.getTime()) ? "--:--:--" : d.toTimeString().slice(0, 8);
|
|
@@ -625,21 +659,6 @@ var fmtUp = (ms) => {
|
|
|
625
659
|
const p = (n) => String(n).padStart(2, "0");
|
|
626
660
|
return `${p(Math.floor(s / 3600))}:${p(Math.floor(s % 3600 / 60))}:${p(s % 60)}`;
|
|
627
661
|
};
|
|
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
662
|
function extractTarget(detail) {
|
|
644
663
|
try {
|
|
645
664
|
const j = JSON.parse(detail);
|
|
@@ -1229,9 +1248,9 @@ function LivePanel({ active: active2 }) {
|
|
|
1229
1248
|
else if (input === "x") toggleSignal("dlp");
|
|
1230
1249
|
else if (input === "r") toggleSignal("ratelimit");
|
|
1231
1250
|
else if (input === "e") {
|
|
1232
|
-
const file =
|
|
1251
|
+
const file = join4(homedir4(), ".solongate", "live-export.jsonl");
|
|
1233
1252
|
try {
|
|
1234
|
-
mkdirSync(
|
|
1253
|
+
mkdirSync(join4(homedir4(), ".solongate"), { recursive: true });
|
|
1235
1254
|
writeFileSync(file, visibleDesc.map((x) => JSON.stringify(x)).join("\n") + "\n");
|
|
1236
1255
|
setActionMsg({ text: `\u2713 exported ${visibleDesc.length} lines \u2192 ${file}`, level: "ok", until: Date.now() + 6e3 });
|
|
1237
1256
|
} catch (err) {
|
|
@@ -1586,11 +1605,13 @@ function ruleSummary(r) {
|
|
|
1586
1605
|
function PoliciesPanel({ focused }) {
|
|
1587
1606
|
const list5 = useLoader(() => api.policies.list());
|
|
1588
1607
|
const policies = list5.data?.policies ?? [];
|
|
1608
|
+
const activeQ = useLoader(() => api.policies.active());
|
|
1609
|
+
const activeId = activeQ.data?.policy?.id ?? null;
|
|
1610
|
+
const activeBy = activeQ.data?.matched_by;
|
|
1589
1611
|
const [view, setView] = useState3("list");
|
|
1590
1612
|
const [pi, setPi] = useState3(0);
|
|
1591
1613
|
const [ri, setRi] = useState3(0);
|
|
1592
1614
|
const [fi, setFi] = useState3(0);
|
|
1593
|
-
const [vi, setVi] = useState3(0);
|
|
1594
1615
|
const [rules, setRules] = useState3([]);
|
|
1595
1616
|
const [mode, setMode] = useState3("denylist");
|
|
1596
1617
|
const [dirty, setDirty] = useState3(false);
|
|
@@ -1599,15 +1620,6 @@ function PoliciesPanel({ focused }) {
|
|
|
1599
1620
|
const [status, setStatus] = useState3(null);
|
|
1600
1621
|
const selected = policies[Math.min(pi, Math.max(0, policies.length - 1))];
|
|
1601
1622
|
const detail = useLoader(() => selected ? api.policies.get(selected.id) : Promise.resolve(null), [selected?.id]);
|
|
1602
|
-
const versionsQ = useLoader(
|
|
1603
|
-
() => view === "versions" && selected ? api.policies.versions(selected.id, { limit: 50 }) : Promise.resolve(null),
|
|
1604
|
-
[view, selected?.id]
|
|
1605
|
-
);
|
|
1606
|
-
const diffVer = versionsQ.data?.versions[vi]?.version;
|
|
1607
|
-
const diffQ = useLoader(
|
|
1608
|
-
() => view === "versions" && selected && diffVer !== void 0 ? api.policies.get(selected.id, diffVer) : Promise.resolve(null),
|
|
1609
|
-
[view, selected?.id, diffVer]
|
|
1610
|
-
);
|
|
1611
1623
|
useEffect3(() => {
|
|
1612
1624
|
if (detail.data) {
|
|
1613
1625
|
setRules(detail.data.rules);
|
|
@@ -1658,6 +1670,20 @@ function PoliciesPanel({ focused }) {
|
|
|
1658
1670
|
else if (key.return || key.rightArrow) {
|
|
1659
1671
|
setStatus(null);
|
|
1660
1672
|
setView("rules");
|
|
1673
|
+
} else if (input === "a") {
|
|
1674
|
+
if (selected) {
|
|
1675
|
+
setStatus("Activating\u2026");
|
|
1676
|
+
void api.policies.setActive(selected.id).then(() => {
|
|
1677
|
+
setStatus(`\u2713 "${selected.name}" is now the ACTIVE policy (pinned) \xB7 reaches agents in ~30s`);
|
|
1678
|
+
activeQ.reload();
|
|
1679
|
+
}).catch((e) => setStatus("\u2717 " + (e instanceof Error ? e.message : String(e))));
|
|
1680
|
+
}
|
|
1681
|
+
} else if (input === "x") {
|
|
1682
|
+
setStatus("Deactivating\u2026");
|
|
1683
|
+
void api.policies.setActive(null).then(() => {
|
|
1684
|
+
setStatus("\u2713 active policy: NONE \u2014 no rules enforced, w/b grants are refused");
|
|
1685
|
+
activeQ.reload();
|
|
1686
|
+
}).catch((e) => setStatus("\u2717 " + (e instanceof Error ? e.message : String(e))));
|
|
1661
1687
|
}
|
|
1662
1688
|
return;
|
|
1663
1689
|
}
|
|
@@ -1691,32 +1717,10 @@ function PoliciesPanel({ focused }) {
|
|
|
1691
1717
|
} else if (input === "D") {
|
|
1692
1718
|
setStatus("Dry-running\u2026");
|
|
1693
1719
|
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))));
|
|
1694
|
-
} else if (input === "v") {
|
|
1695
|
-
setVi(0);
|
|
1696
|
-
setView("versions");
|
|
1697
1720
|
} else if (input === "s") void save();
|
|
1698
1721
|
else if (input === "x") discard();
|
|
1699
1722
|
return;
|
|
1700
1723
|
}
|
|
1701
|
-
if (view === "versions") {
|
|
1702
|
-
const vers = versionsQ.data?.versions ?? [];
|
|
1703
|
-
if (key.leftArrow) return void setView("rules");
|
|
1704
|
-
if (key.upArrow) setVi((n) => Math.max(0, n - 1));
|
|
1705
|
-
else if (key.downArrow) setVi((n) => Math.min(vers.length - 1, n + 1));
|
|
1706
|
-
else if (key.return) {
|
|
1707
|
-
const v = vers[vi];
|
|
1708
|
-
if (v && selected) {
|
|
1709
|
-
setStatus("Rolling back\u2026");
|
|
1710
|
-
void api.policies.rollback(selected.id, v.version).then((r) => {
|
|
1711
|
-
setStatus(`\u2713 rolled back to v${v.version} \u2192 new v${r.version}`);
|
|
1712
|
-
detail.reload();
|
|
1713
|
-
list5.reload();
|
|
1714
|
-
setView("rules");
|
|
1715
|
-
}).catch((e) => setStatus("\u2717 " + (e instanceof Error ? e.message : String(e))));
|
|
1716
|
-
}
|
|
1717
|
-
}
|
|
1718
|
-
return;
|
|
1719
|
-
}
|
|
1720
1724
|
const rule2 = rules[ri];
|
|
1721
1725
|
if (!rule2) return setView("rules");
|
|
1722
1726
|
const field = FIELDS[fi];
|
|
@@ -1738,18 +1742,30 @@ function PoliciesPanel({ focused }) {
|
|
|
1738
1742
|
{ isActive: focused && !editing }
|
|
1739
1743
|
);
|
|
1740
1744
|
if (view === "list") {
|
|
1745
|
+
const activeName = activeId ? policies.find((p) => p.id === activeId)?.name ?? activeId : null;
|
|
1741
1746
|
return /* @__PURE__ */ jsx3(DataView, { loading: list5.loading && !list5.data, error: list5.error, empty: !!list5.data && policies.length === 0, emptyText: "No policies.", children: /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
|
|
1742
|
-
/* @__PURE__ */
|
|
1747
|
+
/* @__PURE__ */ jsxs3(Box3, { children: [
|
|
1748
|
+
/* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: "active: " }),
|
|
1749
|
+
activeQ.loading && !activeQ.data ? /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: "\u2026" }) : activeName ? /* @__PURE__ */ jsxs3(Fragment3, { children: [
|
|
1750
|
+
/* @__PURE__ */ jsxs3(Text3, { color: theme.ok, bold: true, children: [
|
|
1751
|
+
"\u25CF ",
|
|
1752
|
+
truncate(activeName, 28)
|
|
1753
|
+
] }),
|
|
1754
|
+
/* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: activeBy ? ` (${activeBy})` : "" })
|
|
1755
|
+
] }) : /* @__PURE__ */ jsx3(Text3, { color: theme.bad, bold: true, children: "\u25CB NONE \u2014 nothing enforced, grants refused" })
|
|
1756
|
+
] }),
|
|
1757
|
+
/* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: focused ? "\u2191\u2193 select \xB7 enter open \xB7 a activate \xB7 x deactivate (none)" : "press \u2192 to browse" }),
|
|
1743
1758
|
/* @__PURE__ */ jsx3(Box3, { marginTop: 1, flexDirection: "column", children: policies.map((p, i) => /* @__PURE__ */ jsxs3(Text3, { color: i === pi ? theme.accentBright : void 0, bold: i === pi, children: [
|
|
1744
1759
|
(i === pi ? "\u25B8 " : " ") + truncate(p.name, 26).padEnd(27),
|
|
1745
1760
|
/* @__PURE__ */ jsxs3(Text3, { color: theme.dim, children: [
|
|
1746
1761
|
p.mode.padEnd(10),
|
|
1747
1762
|
" ",
|
|
1748
1763
|
p.rules.length,
|
|
1749
|
-
" rules
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
] }, p.id)) })
|
|
1764
|
+
" rules"
|
|
1765
|
+
] }),
|
|
1766
|
+
p.id === activeId ? /* @__PURE__ */ jsx3(Text3, { color: theme.ok, bold: true, children: " \u25CF ACTIVE" }) : null
|
|
1767
|
+
] }, p.id)) }),
|
|
1768
|
+
status ? /* @__PURE__ */ jsx3(Text3, { color: status.startsWith("\u2717") ? theme.bad : theme.ok, children: status }) : null
|
|
1753
1769
|
] }) });
|
|
1754
1770
|
}
|
|
1755
1771
|
const dirtyTag = dirty ? /* @__PURE__ */ jsx3(Text3, { color: theme.warn, children: " \u25CF unsaved (s save \xB7 x discard)" }) : null;
|
|
@@ -1761,7 +1777,7 @@ function PoliciesPanel({ focused }) {
|
|
|
1761
1777
|
/* @__PURE__ */ jsx3(Text3, { color: mode === "whitelist" ? theme.warn : void 0, children: mode }),
|
|
1762
1778
|
dirtyTag
|
|
1763
1779
|
] }),
|
|
1764
|
-
/* @__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
|
|
1780
|
+
/* @__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" }),
|
|
1765
1781
|
/* @__PURE__ */ jsx3(Box3, { marginTop: 1, children: /* @__PURE__ */ jsx3(
|
|
1766
1782
|
Table,
|
|
1767
1783
|
{
|
|
@@ -1787,50 +1803,6 @@ function PoliciesPanel({ focused }) {
|
|
|
1787
1803
|
status ? /* @__PURE__ */ jsx3(Text3, { color: status.startsWith("\u2717") ? theme.bad : theme.ok, children: status }) : null
|
|
1788
1804
|
] }) });
|
|
1789
1805
|
}
|
|
1790
|
-
if (view === "versions") {
|
|
1791
|
-
const vers = versionsQ.data?.versions ?? [];
|
|
1792
|
-
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: [
|
|
1793
|
-
/* @__PURE__ */ jsx3(Text3, { bold: true, color: theme.accentBright, children: truncate(selected?.name ?? "", 28) }),
|
|
1794
|
-
/* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: "\u2191\u2193 select \xB7 enter roll back to that version \xB7 \u2190 back" }),
|
|
1795
|
-
/* @__PURE__ */ jsx3(Box3, { marginTop: 1, children: /* @__PURE__ */ jsx3(
|
|
1796
|
-
Table,
|
|
1797
|
-
{
|
|
1798
|
-
columns: [
|
|
1799
|
-
{ header: "", width: 2 },
|
|
1800
|
-
{ header: "VER", width: 5 },
|
|
1801
|
-
{ header: "RULES", width: 5 },
|
|
1802
|
-
{ header: "REASON", width: 34 },
|
|
1803
|
-
{ header: "WHEN", width: 16 }
|
|
1804
|
-
],
|
|
1805
|
-
rows: vers.map((v, i) => [
|
|
1806
|
-
{ value: i === vi ? "\u25B8" : "", color: theme.accentBright },
|
|
1807
|
-
{ value: `v${v.version}`, bold: i === vi },
|
|
1808
|
-
{ value: String(v.rules_count), dim: true },
|
|
1809
|
-
{ value: truncate(v.reason || "\u2014", 34) },
|
|
1810
|
-
{ value: truncate(v.created_at, 16), dim: true }
|
|
1811
|
-
])
|
|
1812
|
-
}
|
|
1813
|
-
) }),
|
|
1814
|
-
/* @__PURE__ */ jsxs3(Box3, { marginTop: 1, flexDirection: "column", children: [
|
|
1815
|
-
/* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: `Diff v${diffVer ?? "?"} vs current draft` }),
|
|
1816
|
-
(() => {
|
|
1817
|
-
const old = diffQ.data?.rules ?? [];
|
|
1818
|
-
const cur = rules;
|
|
1819
|
-
const oldIds = new Set(old.map((r) => r.id));
|
|
1820
|
-
const curIds = new Set(cur.map((r) => r.id));
|
|
1821
|
-
const added = cur.filter((r) => !oldIds.has(r.id));
|
|
1822
|
-
const removed = old.filter((r) => !curIds.has(r.id));
|
|
1823
|
-
if (diffQ.loading && !diffQ.data) return /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: " loading\u2026" });
|
|
1824
|
-
if (!added.length && !removed.length) return /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: " identical rule set" });
|
|
1825
|
-
return /* @__PURE__ */ jsxs3(Fragment3, { children: [
|
|
1826
|
-
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)),
|
|
1827
|
-
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))
|
|
1828
|
-
] });
|
|
1829
|
-
})()
|
|
1830
|
-
] }),
|
|
1831
|
-
status ? /* @__PURE__ */ jsx3(Text3, { color: status.startsWith("\u2717") ? theme.bad : theme.ok, children: status }) : null
|
|
1832
|
-
] }) });
|
|
1833
|
-
}
|
|
1834
1806
|
const rule = rules[ri];
|
|
1835
1807
|
return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
|
|
1836
1808
|
/* @__PURE__ */ jsxs3(Box3, { children: [
|
|
@@ -2192,171 +2164,229 @@ function RegexTest({ re }) {
|
|
|
2192
2164
|
] });
|
|
2193
2165
|
}
|
|
2194
2166
|
|
|
2195
|
-
// src/tui/panels/Stats.tsx
|
|
2196
|
-
import { Box as Box6, Text as Text6 } from "ink";
|
|
2197
|
-
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
2198
|
-
function StatsPanel({ active: active2 }) {
|
|
2199
|
-
const stats = useLoader(() => api.stats.get());
|
|
2200
|
-
const ts = useLoader(() => api.stats.timeseries({ period: "24h" }));
|
|
2201
|
-
const drift2 = useLoader(() => api.stats.drift(7));
|
|
2202
|
-
usePoll(() => {
|
|
2203
|
-
stats.reload();
|
|
2204
|
-
ts.reload();
|
|
2205
|
-
}, 5e3, active2);
|
|
2206
|
-
usePoll(drift2.reload, 3e4, active2);
|
|
2207
|
-
const s = stats.data;
|
|
2208
|
-
const points = ts.data?.timeseries ?? [];
|
|
2209
|
-
const driftRules = (drift2.data?.rules ?? []).slice(0, 5);
|
|
2210
|
-
return /* @__PURE__ */ jsx6(DataView, { loading: stats.loading && !s, error: stats.error, children: s ? /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
|
|
2211
|
-
/* @__PURE__ */ jsxs6(Box6, { children: [
|
|
2212
|
-
/* @__PURE__ */ jsxs6(Text6, { bold: true, children: [
|
|
2213
|
-
s.total_calls,
|
|
2214
|
-
" "
|
|
2215
|
-
] }),
|
|
2216
|
-
/* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: "calls " }),
|
|
2217
|
-
/* @__PURE__ */ jsxs6(Text6, { color: theme.ok, children: [
|
|
2218
|
-
s.allowed,
|
|
2219
|
-
" allowed"
|
|
2220
|
-
] }),
|
|
2221
|
-
/* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " " }),
|
|
2222
|
-
/* @__PURE__ */ jsxs6(Text6, { color: theme.bad, children: [
|
|
2223
|
-
s.denied,
|
|
2224
|
-
" denied"
|
|
2225
|
-
] })
|
|
2226
|
-
] }),
|
|
2227
|
-
/* @__PURE__ */ jsxs6(Text6, { color: theme.dim, children: [
|
|
2228
|
-
s.active_policies,
|
|
2229
|
-
" policies \xB7 ",
|
|
2230
|
-
s.registered_tools,
|
|
2231
|
-
" tools"
|
|
2232
|
-
] }),
|
|
2233
|
-
/* @__PURE__ */ jsxs6(Box6, { marginTop: 1, flexDirection: "column", children: [
|
|
2234
|
-
/* @__PURE__ */ jsxs6(Text6, { color: theme.dim, children: [
|
|
2235
|
-
"Last 24h ",
|
|
2236
|
-
ts.data ? `(per ${ts.data.granularity})` : ""
|
|
2237
|
-
] }),
|
|
2238
|
-
/* @__PURE__ */ jsxs6(Box6, { children: [
|
|
2239
|
-
/* @__PURE__ */ jsx6(Text6, { children: "total " }),
|
|
2240
|
-
/* @__PURE__ */ jsx6(Text6, { color: theme.accent, children: sparkline(points.map((p) => p.total), 64) })
|
|
2241
|
-
] }),
|
|
2242
|
-
/* @__PURE__ */ jsxs6(Box6, { children: [
|
|
2243
|
-
/* @__PURE__ */ jsx6(Text6, { children: "allowed " }),
|
|
2244
|
-
/* @__PURE__ */ jsx6(Text6, { color: theme.ok, children: sparkline(points.map((p) => p.allowed), 64) })
|
|
2245
|
-
] }),
|
|
2246
|
-
/* @__PURE__ */ jsxs6(Box6, { children: [
|
|
2247
|
-
/* @__PURE__ */ jsx6(Text6, { children: "denied " }),
|
|
2248
|
-
/* @__PURE__ */ jsx6(Text6, { color: theme.bad, children: sparkline(points.map((p) => p.denied), 64) })
|
|
2249
|
-
] })
|
|
2250
|
-
] }),
|
|
2251
|
-
/* @__PURE__ */ jsxs6(Box6, { marginTop: 1, flexDirection: "column", children: [
|
|
2252
|
-
/* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: "Recent" }),
|
|
2253
|
-
/* @__PURE__ */ jsx6(
|
|
2254
|
-
Table,
|
|
2255
|
-
{
|
|
2256
|
-
columns: [
|
|
2257
|
-
{ header: "DECISION", width: 9 },
|
|
2258
|
-
{ header: "TOOL", width: 20 },
|
|
2259
|
-
{ header: "MS", width: 5 },
|
|
2260
|
-
{ header: "WHEN", width: 6 }
|
|
2261
|
-
],
|
|
2262
|
-
rows: (s.recent_activity ?? []).slice(0, 6).map((a) => [
|
|
2263
|
-
{ value: a.decision, color: decisionColor(a.decision) },
|
|
2264
|
-
{ value: truncate(a.tool_name, 20), color: theme.accent },
|
|
2265
|
-
{ value: String(a.evaluation_time_ms ?? "\u2014"), dim: true },
|
|
2266
|
-
{ value: ago(a.created_at), dim: true }
|
|
2267
|
-
])
|
|
2268
|
-
}
|
|
2269
|
-
)
|
|
2270
|
-
] }),
|
|
2271
|
-
/* @__PURE__ */ jsxs6(Box6, { marginTop: 1, flexDirection: "column", children: [
|
|
2272
|
-
/* @__PURE__ */ jsxs6(Text6, { color: theme.dim, children: [
|
|
2273
|
-
"Denial drift ",
|
|
2274
|
-
drift2.data ? `(7d: ${drift2.data.total_current} now vs ${drift2.data.total_previous} prev)` : ""
|
|
2275
|
-
] }),
|
|
2276
|
-
driftRules.length ? /* @__PURE__ */ jsx6(
|
|
2277
|
-
Table,
|
|
2278
|
-
{
|
|
2279
|
-
columns: [
|
|
2280
|
-
{ header: "NOW", width: 5 },
|
|
2281
|
-
{ header: "PREV", width: 5 },
|
|
2282
|
-
{ header: "\u0394", width: 6 },
|
|
2283
|
-
{ header: "RULE", width: 22 },
|
|
2284
|
-
{ header: "REASON", width: 26 }
|
|
2285
|
-
],
|
|
2286
|
-
rows: driftRules.map((r) => [
|
|
2287
|
-
{ value: String(r.current), bold: true },
|
|
2288
|
-
{ value: String(r.previous), dim: true },
|
|
2289
|
-
{ value: r.is_new ? "NEW" : r.spike ? `+${r.delta}` : String(r.delta), color: r.is_new ? theme.ok : r.spike ? theme.bad : void 0 },
|
|
2290
|
-
{ value: truncate(r.rule_id ?? "\u2014", 22), color: theme.accent },
|
|
2291
|
-
{ value: truncate(r.reason ?? r.last_tool ?? "\u2014", 26), dim: true }
|
|
2292
|
-
])
|
|
2293
|
-
}
|
|
2294
|
-
) : /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " no denials in window" })
|
|
2295
|
-
] })
|
|
2296
|
-
] }) : null });
|
|
2297
|
-
}
|
|
2298
|
-
|
|
2299
2167
|
// src/tui/panels/Audit.tsx
|
|
2300
|
-
import { Box as
|
|
2168
|
+
import { Box as Box6, Text as Text6, useInput as useInput5 } from "ink";
|
|
2301
2169
|
import TextInput4 from "ink-text-input";
|
|
2302
2170
|
import { useState as useState6 } from "react";
|
|
2303
|
-
import { Fragment as Fragment4, jsx as
|
|
2171
|
+
import { Fragment as Fragment4, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
2304
2172
|
var DECISIONS = [void 0, "DENY", "ALLOW"];
|
|
2305
2173
|
var SIGNALS = [void 0, "dlp", "ratelimit"];
|
|
2306
|
-
var
|
|
2174
|
+
var SESS_STATUS = [void 0, "active", "idle", "ended"];
|
|
2175
|
+
var PAGE = 500;
|
|
2176
|
+
var LOCAL_MAX_BYTES = 16 * 1024 * 1024;
|
|
2177
|
+
var cloudRow = (e) => ({
|
|
2178
|
+
id: e.id,
|
|
2179
|
+
at: Date.parse(e.created_at),
|
|
2180
|
+
tool: e.tool_name,
|
|
2181
|
+
decision: e.decision,
|
|
2182
|
+
permission: e.permission,
|
|
2183
|
+
trust: e.trust_level,
|
|
2184
|
+
agent: e.agent_name,
|
|
2185
|
+
session: e.session_id,
|
|
2186
|
+
reason: e.reason,
|
|
2187
|
+
rule: e.matched_rule_id,
|
|
2188
|
+
evalMs: e.evaluation_time_ms,
|
|
2189
|
+
dlp: e.dlp_matches ?? [],
|
|
2190
|
+
burst: !!e.rate_limit_burst,
|
|
2191
|
+
args: e.arguments_summary ? JSON.stringify(e.arguments_summary) : null
|
|
2192
|
+
});
|
|
2193
|
+
function loadLocalRows() {
|
|
2194
|
+
return parseLocalLines(tailLines(LOCAL_LOG, LOCAL_MAX_BYTES)).map((j, i) => ({
|
|
2195
|
+
id: "l:" + j.at + ":" + i,
|
|
2196
|
+
at: j.at,
|
|
2197
|
+
tool: j.tool ?? "?",
|
|
2198
|
+
decision: j.decision ?? "ALLOW",
|
|
2199
|
+
permission: j.permission ?? "\u2014",
|
|
2200
|
+
trust: j.trust_level ?? "\u2014",
|
|
2201
|
+
agent: j.agent_name ?? null,
|
|
2202
|
+
session: j.session_id ?? null,
|
|
2203
|
+
reason: j.reason ?? null,
|
|
2204
|
+
rule: j.matched_rule_id ?? null,
|
|
2205
|
+
evalMs: j.evaluation_time_ms ?? null,
|
|
2206
|
+
dlp: j.dlp ? Array.isArray(j.dlp) ? j.dlp.map(String) : ["dlp"] : [],
|
|
2207
|
+
burst: !!j.rate_limit_burst,
|
|
2208
|
+
args: j.arguments ? JSON.stringify(j.arguments) : null
|
|
2209
|
+
})).sort((a, b) => b.at - a.at);
|
|
2210
|
+
}
|
|
2211
|
+
var sessStatus2 = (lastAt) => Date.now() - lastAt < 6e4 ? "active" : Date.now() - lastAt < 3e5 ? "idle" : "ended";
|
|
2212
|
+
var STATUS_DOT = {
|
|
2213
|
+
active: { ch: "\u25CF", color: theme.ok },
|
|
2214
|
+
idle: { ch: "\u25D0", color: theme.warn },
|
|
2215
|
+
ended: { ch: "\u25CB", color: theme.dim }
|
|
2216
|
+
};
|
|
2307
2217
|
function AuditPanel({ active: active2, focused }) {
|
|
2308
2218
|
const { cols, rows } = usePanelSize();
|
|
2219
|
+
const [source, setSource] = useState6("cloud");
|
|
2220
|
+
const [view, setView] = useState6("logs");
|
|
2309
2221
|
const [di, setDi] = useState6(0);
|
|
2310
2222
|
const [gi, setGi] = useState6(0);
|
|
2311
2223
|
const [tool, setTool] = useState6("");
|
|
2312
2224
|
const [agent, setAgent] = useState6("");
|
|
2313
2225
|
const [search, setSearch] = useState6("");
|
|
2226
|
+
const [sessFilter, setSessFilter] = useState6("");
|
|
2227
|
+
const [page, setPage] = useState6(0);
|
|
2314
2228
|
const [sel, setSel] = useState6(0);
|
|
2315
|
-
const [view, setView] = useState6("list");
|
|
2316
2229
|
const [detailScroll, setDetailScroll] = useState6(0);
|
|
2317
2230
|
const [editing, setEditing] = useState6(null);
|
|
2231
|
+
const [si, setSi] = useState6(0);
|
|
2232
|
+
const [sessSearch, setSessSearch] = useState6("");
|
|
2233
|
+
const [sessSel, setSessSel] = useState6(0);
|
|
2234
|
+
const toTop = () => setSel(0);
|
|
2235
|
+
const statsQ = useLoader(() => source === "cloud" ? api.stats.get() : Promise.resolve(null), [source]);
|
|
2236
|
+
const tsQ = useLoader(() => source === "cloud" ? api.stats.timeseries({ period: "24h" }) : Promise.resolve(null), [source]);
|
|
2237
|
+
usePoll(() => {
|
|
2238
|
+
statsQ.reload();
|
|
2239
|
+
tsQ.reload();
|
|
2240
|
+
}, 15e3, active2 && source === "cloud");
|
|
2318
2241
|
const query = {
|
|
2319
2242
|
filter: DECISIONS[di],
|
|
2320
2243
|
signal: SIGNALS[gi],
|
|
2321
2244
|
tool: tool || void 0,
|
|
2322
2245
|
agent_name: agent || void 0,
|
|
2323
2246
|
search: search || void 0,
|
|
2324
|
-
|
|
2247
|
+
session_id: sessFilter || void 0,
|
|
2248
|
+
limit: PAGE,
|
|
2249
|
+
offset: page * PAGE
|
|
2325
2250
|
};
|
|
2326
|
-
const
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2251
|
+
const cloudQ = useLoader(
|
|
2252
|
+
() => source === "cloud" ? api.audit.list(query) : Promise.resolve(null),
|
|
2253
|
+
[source, di, gi, tool, agent, search, sessFilter, page]
|
|
2254
|
+
);
|
|
2255
|
+
usePoll(cloudQ.reload, 6e3, active2 && source === "cloud" && view === "logs" && !editing && page === 0);
|
|
2256
|
+
const localQ = useLoader(() => source === "local" ? Promise.resolve(loadLocalRows()) : Promise.resolve(null), [source]);
|
|
2257
|
+
usePoll(localQ.reload, 6e3, active2 && source === "local" && view === "logs" && !editing && page === 0);
|
|
2258
|
+
let pageRows = [];
|
|
2259
|
+
let total = 0;
|
|
2260
|
+
if (source === "cloud") {
|
|
2261
|
+
pageRows = (cloudQ.data?.entries ?? []).map(cloudRow).sort((a, b) => b.at - a.at);
|
|
2262
|
+
total = cloudQ.data?.total ?? 0;
|
|
2263
|
+
} else {
|
|
2264
|
+
const all = localQ.data ?? [];
|
|
2265
|
+
const q = search.trim().toLowerCase();
|
|
2266
|
+
const filtered = all.filter((r) => {
|
|
2267
|
+
if (DECISIONS[di] && r.decision !== DECISIONS[di]) return false;
|
|
2268
|
+
if (SIGNALS[gi] === "dlp" && r.dlp.length === 0) return false;
|
|
2269
|
+
if (SIGNALS[gi] === "ratelimit" && !r.burst) return false;
|
|
2270
|
+
if (tool && !r.tool.toLowerCase().includes(tool.toLowerCase())) return false;
|
|
2271
|
+
if (agent && (r.agent ?? "").toLowerCase() !== agent.toLowerCase()) return false;
|
|
2272
|
+
if (sessFilter && r.session !== sessFilter) return false;
|
|
2273
|
+
if (q && !`${r.tool} ${r.agent ?? ""} ${r.reason ?? ""} ${r.args ?? ""}`.toLowerCase().includes(q)) return false;
|
|
2274
|
+
return true;
|
|
2275
|
+
});
|
|
2276
|
+
total = filtered.length;
|
|
2277
|
+
pageRows = filtered.slice(page * PAGE, page * PAGE + PAGE);
|
|
2278
|
+
}
|
|
2279
|
+
const pages = Math.max(1, Math.ceil(total / PAGE));
|
|
2280
|
+
const current = pageRows[Math.min(sel, Math.max(0, pageRows.length - 1))];
|
|
2281
|
+
const agentsQ = useLoader(
|
|
2282
|
+
() => source === "cloud" ? api.agents.live({ limit: 100, includeDeactivated: true }) : Promise.resolve(null),
|
|
2283
|
+
[source]
|
|
2284
|
+
);
|
|
2285
|
+
usePoll(agentsQ.reload, 8e3, active2 && source === "cloud" && view === "sessions");
|
|
2286
|
+
let sessions = [];
|
|
2287
|
+
if (source === "cloud") {
|
|
2288
|
+
sessions = (agentsQ.data?.agents ?? []).map((a) => ({
|
|
2289
|
+
id: a.session_id,
|
|
2290
|
+
agent: a.agent_name ?? a.session_id,
|
|
2291
|
+
status: a.status === "deactivated" ? "ended" : a.status,
|
|
2292
|
+
calls: a.total_calls,
|
|
2293
|
+
denies: a.denied_calls,
|
|
2294
|
+
dlp: a.dlp_events,
|
|
2295
|
+
trust: a.trust_score,
|
|
2296
|
+
lastAt: Date.parse(a.last_seen_at)
|
|
2297
|
+
}));
|
|
2298
|
+
} else {
|
|
2299
|
+
const bySess = /* @__PURE__ */ new Map();
|
|
2300
|
+
for (const r of localQ.data ?? []) {
|
|
2301
|
+
if (!r.session) continue;
|
|
2302
|
+
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 };
|
|
2303
|
+
cur.calls++;
|
|
2304
|
+
if (r.decision !== "ALLOW") cur.denies++;
|
|
2305
|
+
if (r.dlp.length) cur.dlp++;
|
|
2306
|
+
cur.lastAt = Math.max(cur.lastAt, r.at);
|
|
2307
|
+
if (r.agent) cur.agent = r.agent;
|
|
2308
|
+
bySess.set(r.session, cur);
|
|
2309
|
+
}
|
|
2310
|
+
sessions = [...bySess.values()];
|
|
2311
|
+
}
|
|
2312
|
+
const sq = sessSearch.trim().toLowerCase();
|
|
2313
|
+
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);
|
|
2314
|
+
const currentSess = sessionsFiltered[Math.min(sessSel, Math.max(0, sessionsFiltered.length - 1))];
|
|
2330
2315
|
const sessionQ = useLoader(
|
|
2331
|
-
() => view === "detail" && current?.
|
|
2332
|
-
[view, current?.
|
|
2316
|
+
() => view === "detail" && current?.session && source === "cloud" ? api.audit.list({ session_id: current.session, limit: 40 }) : Promise.resolve(null),
|
|
2317
|
+
[view, current?.session, source]
|
|
2333
2318
|
);
|
|
2334
2319
|
useInput5(
|
|
2335
2320
|
(input, key) => {
|
|
2336
2321
|
if (view === "detail") {
|
|
2337
|
-
if (key.leftArrow || key.escape) setView("
|
|
2322
|
+
if (key.leftArrow || key.escape) setView("logs");
|
|
2338
2323
|
else if (key.upArrow) setDetailScroll((n) => Math.max(0, n - 1));
|
|
2339
2324
|
else if (key.downArrow) setDetailScroll((n) => n + 1);
|
|
2340
2325
|
else if (key.pageUp) setDetailScroll((n) => Math.max(0, n - 10));
|
|
2341
2326
|
else if (key.pageDown) setDetailScroll((n) => n + 10);
|
|
2342
2327
|
return;
|
|
2343
2328
|
}
|
|
2344
|
-
|
|
2329
|
+
if (input === "s") {
|
|
2330
|
+
setSource((s) => s === "cloud" ? "local" : "cloud");
|
|
2331
|
+
setPage(0);
|
|
2332
|
+
toTop();
|
|
2333
|
+
setSessSel(0);
|
|
2334
|
+
return;
|
|
2335
|
+
}
|
|
2336
|
+
if (input === "v") {
|
|
2337
|
+
setView((v) => v === "logs" ? "sessions" : "logs");
|
|
2338
|
+
return;
|
|
2339
|
+
}
|
|
2340
|
+
if (view === "sessions") {
|
|
2341
|
+
if (key.upArrow) setSessSel((n) => Math.max(0, n - 1));
|
|
2342
|
+
else if (key.downArrow) setSessSel((n) => Math.min(sessionsFiltered.length - 1, n + 1));
|
|
2343
|
+
else if (key.pageUp) setSessSel((n) => Math.max(0, n - 10));
|
|
2344
|
+
else if (key.pageDown) setSessSel((n) => Math.min(sessionsFiltered.length - 1, n + 10));
|
|
2345
|
+
else if (key.return || key.rightArrow) {
|
|
2346
|
+
if (currentSess) {
|
|
2347
|
+
setSessFilter(currentSess.id);
|
|
2348
|
+
setPage(0);
|
|
2349
|
+
toTop();
|
|
2350
|
+
setView("logs");
|
|
2351
|
+
}
|
|
2352
|
+
} else if (input === "f") {
|
|
2353
|
+
setSi((n) => (n + 1) % SESS_STATUS.length);
|
|
2354
|
+
setSessSel(0);
|
|
2355
|
+
} else if (input === "/") setEditing("sess-search");
|
|
2356
|
+
else if (input === "c") {
|
|
2357
|
+
setSi(0);
|
|
2358
|
+
setSessSearch("");
|
|
2359
|
+
setSessSel(0);
|
|
2360
|
+
}
|
|
2361
|
+
return;
|
|
2362
|
+
}
|
|
2345
2363
|
if (key.upArrow) setSel((n) => Math.max(0, n - 1));
|
|
2346
|
-
else if (key.downArrow) setSel((n) => Math.min(
|
|
2364
|
+
else if (key.downArrow) setSel((n) => Math.min(pageRows.length - 1, n + 1));
|
|
2347
2365
|
else if (key.pageUp) setSel((n) => Math.max(0, n - 10));
|
|
2348
|
-
else if (key.pageDown) setSel((n) => Math.min(
|
|
2366
|
+
else if (key.pageDown) setSel((n) => Math.min(pageRows.length - 1, n + 10));
|
|
2349
2367
|
else if (key.return || key.rightArrow) {
|
|
2350
2368
|
if (current) {
|
|
2351
2369
|
setDetailScroll(0);
|
|
2352
2370
|
setView("detail");
|
|
2353
2371
|
}
|
|
2372
|
+
} else if (input === "]") {
|
|
2373
|
+
if (page < pages - 1) {
|
|
2374
|
+
setPage(page + 1);
|
|
2375
|
+
toTop();
|
|
2376
|
+
}
|
|
2377
|
+
} else if (input === "[") {
|
|
2378
|
+
if (page > 0) {
|
|
2379
|
+
setPage(page - 1);
|
|
2380
|
+
toTop();
|
|
2381
|
+
}
|
|
2354
2382
|
} else if (input === "f") {
|
|
2355
2383
|
setDi((n) => (n + 1) % DECISIONS.length);
|
|
2356
|
-
|
|
2384
|
+
setPage(0);
|
|
2385
|
+
toTop();
|
|
2357
2386
|
} else if (input === "g") {
|
|
2358
2387
|
setGi((n) => (n + 1) % SIGNALS.length);
|
|
2359
|
-
|
|
2388
|
+
setPage(0);
|
|
2389
|
+
toTop();
|
|
2360
2390
|
} else if (input === "t") setEditing("tool");
|
|
2361
2391
|
else if (input === "n") setEditing("agent");
|
|
2362
2392
|
else if (input === "/") setEditing("search");
|
|
@@ -2366,46 +2396,89 @@ function AuditPanel({ active: active2, focused }) {
|
|
|
2366
2396
|
setTool("");
|
|
2367
2397
|
setAgent("");
|
|
2368
2398
|
setSearch("");
|
|
2369
|
-
|
|
2399
|
+
setSessFilter("");
|
|
2400
|
+
setPage(0);
|
|
2401
|
+
toTop();
|
|
2370
2402
|
}
|
|
2371
2403
|
},
|
|
2372
2404
|
{ isActive: focused && !editing }
|
|
2373
2405
|
);
|
|
2406
|
+
const points = tsQ.data?.timeseries ?? [];
|
|
2407
|
+
const localAll = localQ.data ?? [];
|
|
2408
|
+
const strip = source === "cloud" ? /* @__PURE__ */ jsxs6(Text6, { wrap: "truncate", children: [
|
|
2409
|
+
/* @__PURE__ */ jsx6(Text6, { bold: true, children: statsQ.data ? statsQ.data.total_calls : "\xB7\xB7\xB7\xB7" }),
|
|
2410
|
+
/* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " calls \xB7 " }),
|
|
2411
|
+
/* @__PURE__ */ jsxs6(Text6, { color: theme.ok, children: [
|
|
2412
|
+
statsQ.data?.allowed ?? "\xB7\xB7\xB7",
|
|
2413
|
+
" allow"
|
|
2414
|
+
] }),
|
|
2415
|
+
/* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " \xB7 " }),
|
|
2416
|
+
/* @__PURE__ */ jsxs6(Text6, { color: theme.bad, children: [
|
|
2417
|
+
statsQ.data?.denied ?? "\xB7\xB7",
|
|
2418
|
+
" deny"
|
|
2419
|
+
] }),
|
|
2420
|
+
/* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: ` \xB7 ${statsQ.data?.active_policies ?? "\xB7"} policies \xB7 ${statsQ.data?.registered_tools ?? "\xB7"} tools \xB7 24h ` }),
|
|
2421
|
+
/* @__PURE__ */ jsx6(Text6, { color: theme.accent, children: sparkline(points.map((p) => p.total), 24) }),
|
|
2422
|
+
/* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " deny " }),
|
|
2423
|
+
/* @__PURE__ */ jsx6(Text6, { color: theme.bad, children: sparkline(points.map((p) => p.denied), 24) })
|
|
2424
|
+
] }) : /* @__PURE__ */ jsxs6(Text6, { wrap: "truncate", children: [
|
|
2425
|
+
/* @__PURE__ */ jsx6(Text6, { bold: true, children: localAll.length }),
|
|
2426
|
+
/* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " calls in local file \xB7 " }),
|
|
2427
|
+
/* @__PURE__ */ jsxs6(Text6, { color: theme.ok, children: [
|
|
2428
|
+
localAll.filter((r) => r.decision === "ALLOW").length,
|
|
2429
|
+
" allow"
|
|
2430
|
+
] }),
|
|
2431
|
+
/* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " \xB7 " }),
|
|
2432
|
+
/* @__PURE__ */ jsxs6(Text6, { color: theme.bad, children: [
|
|
2433
|
+
localAll.filter((r) => r.decision !== "ALLOW").length,
|
|
2434
|
+
" deny"
|
|
2435
|
+
] }),
|
|
2436
|
+
/* @__PURE__ */ jsxs6(Text6, { color: theme.dim, children: [
|
|
2437
|
+
" \xB7 ",
|
|
2438
|
+
LOCAL_LOG
|
|
2439
|
+
] })
|
|
2440
|
+
] });
|
|
2441
|
+
const srcChip = /* @__PURE__ */ jsxs6(Text6, { children: [
|
|
2442
|
+
/* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: "src:" }),
|
|
2443
|
+
/* @__PURE__ */ jsx6(Text6, { color: source === "local" ? theme.ok : "#4f6db8", bold: true, children: source }),
|
|
2444
|
+
/* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " (s) " })
|
|
2445
|
+
] });
|
|
2374
2446
|
if (view === "detail" && current) {
|
|
2375
|
-
const sessionCalls = sessionQ.data?.entries ?? [];
|
|
2447
|
+
const sessionCalls = source === "cloud" ? (sessionQ.data?.entries ?? []).map(cloudRow) : localAll.filter((r) => r.session && r.session === current.session).slice(0, 40);
|
|
2376
2448
|
const bodyW = Math.max(20, cols - 2);
|
|
2377
2449
|
const reasonLines = wrapLines("reason: " + (current.reason ?? "\u2014"), bodyW);
|
|
2378
|
-
const argLines = current.
|
|
2379
|
-
const sessionRows = current.
|
|
2450
|
+
const argLines = current.args ? wrapLines(prettyJson(current.args), bodyW) : ["(no arguments recorded)"];
|
|
2451
|
+
const sessionRows = current.session ? Math.min(4, sessionCalls.length) + 2 : 1;
|
|
2380
2452
|
const reasonShown = reasonLines.slice(0, 4);
|
|
2381
2453
|
const argBudget = Math.max(3, rows - 2 - 5 - reasonShown.length - 1 - sessionRows - 1);
|
|
2382
2454
|
const maxScroll = Math.max(0, argLines.length - argBudget);
|
|
2383
2455
|
const off = Math.min(detailScroll, maxScroll);
|
|
2384
2456
|
const argWin = argLines.slice(off, off + argBudget);
|
|
2385
|
-
return /* @__PURE__ */
|
|
2386
|
-
/* @__PURE__ */
|
|
2387
|
-
/* @__PURE__ */
|
|
2388
|
-
/* @__PURE__ */
|
|
2389
|
-
/* @__PURE__ */
|
|
2457
|
+
return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
|
|
2458
|
+
/* @__PURE__ */ jsxs6(Box6, { children: [
|
|
2459
|
+
/* @__PURE__ */ jsx6(Text6, { color: decisionColor(current.decision), bold: true, children: current.decision }),
|
|
2460
|
+
/* @__PURE__ */ jsx6(Text6, { color: theme.accent, children: " " + current.tool }),
|
|
2461
|
+
/* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " " + current.permission + " \xB7 " + current.trust + " " }),
|
|
2462
|
+
srcChip
|
|
2390
2463
|
] }),
|
|
2391
|
-
reasonShown.map((l, i) => /* @__PURE__ */
|
|
2392
|
-
/* @__PURE__ */
|
|
2393
|
-
/* @__PURE__ */
|
|
2394
|
-
] }) : /* @__PURE__ */
|
|
2395
|
-
/* @__PURE__ */
|
|
2396
|
-
/* @__PURE__ */
|
|
2397
|
-
/* @__PURE__ */
|
|
2398
|
-
/* @__PURE__ */
|
|
2399
|
-
/* @__PURE__ */
|
|
2400
|
-
/* @__PURE__ */
|
|
2401
|
-
/* @__PURE__ */
|
|
2402
|
-
current.
|
|
2403
|
-
/* @__PURE__ */
|
|
2464
|
+
reasonShown.map((l, i) => /* @__PURE__ */ jsx6(Text6, { wrap: "truncate", children: i === 0 ? /* @__PURE__ */ jsxs6(Fragment4, { children: [
|
|
2465
|
+
/* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: "reason " }),
|
|
2466
|
+
/* @__PURE__ */ jsx6(Text6, { children: l.slice("reason: ".length) })
|
|
2467
|
+
] }) : /* @__PURE__ */ jsx6(Text6, { children: " " + l }) }, "r" + i)),
|
|
2468
|
+
/* @__PURE__ */ jsx6(Detail, { label: "rule", value: current.rule ?? "\u2014" }),
|
|
2469
|
+
/* @__PURE__ */ jsx6(Detail, { label: "agent", value: current.agent ?? "\u2014" }),
|
|
2470
|
+
/* @__PURE__ */ jsx6(Detail, { label: "session", value: current.session ?? "\u2014" }),
|
|
2471
|
+
/* @__PURE__ */ jsx6(Detail, { label: "dlp", value: current.dlp.length ? current.dlp.join(", ") : "none", color: current.dlp.length ? theme.bad : void 0 }),
|
|
2472
|
+
/* @__PURE__ */ jsx6(Detail, { label: "when", value: new Date(current.at).toLocaleString() + (current.evalMs != null ? ` \xB7 eval ${current.evalMs}ms` : "") }),
|
|
2473
|
+
/* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: `arguments \u2014 full \xB7 ${argLines.length} lines${maxScroll ? ` \xB7 \u25BC${maxScroll - off} more \xB7 \u2191\u2193 scroll` : ""}` }),
|
|
2474
|
+
/* @__PURE__ */ jsx6(Box6, { flexDirection: "column", height: argBudget, overflow: "hidden", children: argWin.map((l, i) => /* @__PURE__ */ jsx6(Text6, { wrap: "truncate", children: l || " " }, off + i)) }),
|
|
2475
|
+
current.session ? /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
|
|
2476
|
+
/* @__PURE__ */ jsxs6(Text6, { color: theme.dim, children: [
|
|
2404
2477
|
"Session \xB7 ",
|
|
2405
2478
|
sessionCalls.length,
|
|
2406
2479
|
" calls"
|
|
2407
2480
|
] }),
|
|
2408
|
-
/* @__PURE__ */
|
|
2481
|
+
/* @__PURE__ */ jsx6(
|
|
2409
2482
|
Table,
|
|
2410
2483
|
{
|
|
2411
2484
|
columns: [
|
|
@@ -2416,59 +2489,136 @@ function AuditPanel({ active: active2, focused }) {
|
|
|
2416
2489
|
],
|
|
2417
2490
|
rows: sessionCalls.slice(0, Math.max(1, sessionRows - 2)).map((e) => [
|
|
2418
2491
|
{ value: e.decision, color: decisionColor(e.decision) },
|
|
2419
|
-
{ value: truncate(e.
|
|
2492
|
+
{ value: truncate(e.tool, 20), color: theme.accent },
|
|
2420
2493
|
{ value: truncate(e.reason ?? "\u2014", Math.max(16, cols - 48)) },
|
|
2421
|
-
{ value: ago(e.
|
|
2494
|
+
{ value: ago(e.at), dim: true }
|
|
2422
2495
|
])
|
|
2423
2496
|
}
|
|
2424
2497
|
)
|
|
2425
|
-
] }) : /* @__PURE__ */
|
|
2426
|
-
/* @__PURE__ */
|
|
2498
|
+
] }) : /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: "(no session id)" }),
|
|
2499
|
+
/* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: "\u2191\u2193 scroll arguments \xB7 \u2190/esc back to logs" })
|
|
2427
2500
|
] });
|
|
2428
2501
|
}
|
|
2429
|
-
const chip = (label, val, on) => /* @__PURE__ */
|
|
2430
|
-
/* @__PURE__ */
|
|
2502
|
+
const chip = (label, val, on) => /* @__PURE__ */ jsxs6(Text6, { children: [
|
|
2503
|
+
/* @__PURE__ */ jsxs6(Text6, { color: theme.dim, children: [
|
|
2431
2504
|
label,
|
|
2432
2505
|
":"
|
|
2433
2506
|
] }),
|
|
2434
|
-
/* @__PURE__ */
|
|
2435
|
-
/* @__PURE__ */
|
|
2507
|
+
/* @__PURE__ */ jsx6(Text6, { color: on ? theme.accentBright : theme.dim, children: val }),
|
|
2508
|
+
/* @__PURE__ */ jsx6(Text6, { children: " " })
|
|
2436
2509
|
] });
|
|
2437
|
-
|
|
2510
|
+
if (view === "sessions") {
|
|
2511
|
+
const headerRows2 = 5 + (editing ? 1 : 0);
|
|
2512
|
+
const listRows2 = Math.max(4, rows - headerRows2);
|
|
2513
|
+
const selC = Math.min(sessSel, Math.max(0, sessionsFiltered.length - 1));
|
|
2514
|
+
const start2 = Math.min(Math.max(0, selC - Math.floor((listRows2 - 1) / 2)), Math.max(0, sessionsFiltered.length - listRows2));
|
|
2515
|
+
const win = sessionsFiltered.slice(start2, start2 + listRows2);
|
|
2516
|
+
const loading2 = source === "cloud" ? agentsQ.loading && !agentsQ.data : localQ.loading && !localQ.data;
|
|
2517
|
+
return /* @__PURE__ */ jsx6(DataView, { loading: loading2, error: source === "cloud" ? agentsQ.error : localQ.error, children: /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
|
|
2518
|
+
strip,
|
|
2519
|
+
/* @__PURE__ */ jsxs6(Box6, { children: [
|
|
2520
|
+
srcChip,
|
|
2521
|
+
/* @__PURE__ */ jsx6(Text6, { color: theme.accentBright, bold: true, children: "SESSIONS" }),
|
|
2522
|
+
/* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " \xB7 logs (v) " }),
|
|
2523
|
+
chip("status", SESS_STATUS[si] ?? "all", si !== 0),
|
|
2524
|
+
chip("search", sessSearch || "\xB7", !!sessSearch)
|
|
2525
|
+
] }),
|
|
2526
|
+
/* @__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" }),
|
|
2527
|
+
editing === "sess-search" ? /* @__PURE__ */ jsxs6(Box6, { children: [
|
|
2528
|
+
/* @__PURE__ */ jsx6(Text6, { color: theme.warn, children: "search: " }),
|
|
2529
|
+
/* @__PURE__ */ jsx6(
|
|
2530
|
+
TextInput4,
|
|
2531
|
+
{
|
|
2532
|
+
value: sessSearch,
|
|
2533
|
+
onChange: (v) => {
|
|
2534
|
+
setSessSearch(v);
|
|
2535
|
+
setSessSel(0);
|
|
2536
|
+
},
|
|
2537
|
+
onSubmit: () => setEditing(null)
|
|
2538
|
+
}
|
|
2539
|
+
)
|
|
2540
|
+
] }) : null,
|
|
2541
|
+
/* @__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}` : ""}` }),
|
|
2542
|
+
/* @__PURE__ */ jsx6(
|
|
2543
|
+
Table,
|
|
2544
|
+
{
|
|
2545
|
+
columns: [
|
|
2546
|
+
{ header: "", width: 2 },
|
|
2547
|
+
{ header: "STATUS", width: 8 },
|
|
2548
|
+
{ header: "AGENT", width: 18 },
|
|
2549
|
+
{ header: "SESSION", width: 10 },
|
|
2550
|
+
{ header: "CALLS", width: 6 },
|
|
2551
|
+
{ header: "DENY", width: 5 },
|
|
2552
|
+
{ header: "DLP", width: 4 },
|
|
2553
|
+
{ header: "TRUST", width: 5 },
|
|
2554
|
+
{ header: "LAST", width: 6 }
|
|
2555
|
+
],
|
|
2556
|
+
rows: win.map((r, i) => {
|
|
2557
|
+
const st = STATUS_DOT[r.status];
|
|
2558
|
+
const isSel = start2 + i === selC && focused;
|
|
2559
|
+
return [
|
|
2560
|
+
{ value: isSel ? "\u25B8" : "", color: theme.accentBright },
|
|
2561
|
+
{ value: `${st.ch} ${r.status}`, color: st.color },
|
|
2562
|
+
{ value: truncate(r.agent, 18), color: theme.accent, bold: isSel },
|
|
2563
|
+
{ value: r.id.slice(0, 8), dim: true },
|
|
2564
|
+
{ value: String(r.calls) },
|
|
2565
|
+
{ value: String(r.denies), color: r.denies ? theme.bad : void 0, dim: !r.denies },
|
|
2566
|
+
{ value: String(r.dlp), color: r.dlp ? theme.bad : void 0, dim: !r.dlp },
|
|
2567
|
+
{ value: r.trust != null ? String(r.trust) : "\u2014", dim: true },
|
|
2568
|
+
{ value: ago(r.lastAt), dim: true }
|
|
2569
|
+
];
|
|
2570
|
+
})
|
|
2571
|
+
}
|
|
2572
|
+
),
|
|
2573
|
+
sessionsFiltered.length === 0 ? /* @__PURE__ */ jsxs6(Text6, { color: theme.dim, children: [
|
|
2574
|
+
"(no sessions",
|
|
2575
|
+
source === "local" ? " in the local file" : "",
|
|
2576
|
+
")"
|
|
2577
|
+
] }) : null
|
|
2578
|
+
] }) });
|
|
2579
|
+
}
|
|
2580
|
+
const headerRows = 6 + (editing && editing !== "sess-search" ? 1 : 0);
|
|
2438
2581
|
const listRows = Math.max(4, rows - headerRows);
|
|
2439
|
-
const selClamped = Math.min(sel, Math.max(0,
|
|
2440
|
-
const maxStart = Math.max(0,
|
|
2582
|
+
const selClamped = Math.min(sel, Math.max(0, pageRows.length - 1));
|
|
2583
|
+
const maxStart = Math.max(0, pageRows.length - listRows);
|
|
2441
2584
|
const start = Math.min(Math.max(0, selClamped - Math.floor((listRows - 1) / 2)), maxStart);
|
|
2442
|
-
const windowed =
|
|
2585
|
+
const windowed = pageRows.slice(start, start + listRows);
|
|
2443
2586
|
const reasonW = Math.min(60, Math.max(12, cols - 67));
|
|
2444
|
-
|
|
2445
|
-
|
|
2587
|
+
const loading = source === "cloud" ? cloudQ.loading && !cloudQ.data : localQ.loading && !localQ.data;
|
|
2588
|
+
return /* @__PURE__ */ jsx6(DataView, { loading, error: source === "cloud" ? cloudQ.error : localQ.error, children: /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
|
|
2589
|
+
strip,
|
|
2590
|
+
/* @__PURE__ */ jsxs6(Box6, { children: [
|
|
2591
|
+
srcChip,
|
|
2592
|
+
/* @__PURE__ */ jsx6(Text6, { color: theme.accentBright, bold: true, children: "LOGS" }),
|
|
2593
|
+
/* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " \xB7 sessions (v) " }),
|
|
2446
2594
|
chip("dec", DECISIONS[di] ?? "all", di !== 0),
|
|
2447
2595
|
chip("sig", SIGNALS[gi] ?? "all", gi !== 0),
|
|
2448
2596
|
chip("tool", tool || "\xB7", !!tool),
|
|
2449
2597
|
chip("agent", agent || "\xB7", !!agent),
|
|
2450
|
-
chip("search", search || "\xB7", !!search)
|
|
2598
|
+
chip("search", search || "\xB7", !!search),
|
|
2599
|
+
sessFilter ? chip("sess", sessFilter.slice(0, 8), true) : null
|
|
2451
2600
|
] }),
|
|
2452
|
-
/* @__PURE__ */
|
|
2453
|
-
editing ? /* @__PURE__ */
|
|
2454
|
-
/* @__PURE__ */
|
|
2601
|
+
/* @__PURE__ */ jsx6(Text6, { color: theme.dim, wrap: "truncate", children: focused ? "\u2191\u2193 select \xB7 enter full entry \xB7 [ ] 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" }),
|
|
2602
|
+
editing && editing !== "sess-search" ? /* @__PURE__ */ jsxs6(Box6, { children: [
|
|
2603
|
+
/* @__PURE__ */ jsxs6(Text6, { color: theme.warn, children: [
|
|
2455
2604
|
editing,
|
|
2456
2605
|
": "
|
|
2457
2606
|
] }),
|
|
2458
|
-
/* @__PURE__ */
|
|
2607
|
+
/* @__PURE__ */ jsx6(
|
|
2459
2608
|
TextInput4,
|
|
2460
2609
|
{
|
|
2461
2610
|
value: editing === "tool" ? tool : editing === "agent" ? agent : search,
|
|
2462
2611
|
onChange: (v) => {
|
|
2463
2612
|
(editing === "tool" ? setTool : editing === "agent" ? setAgent : setSearch)(v);
|
|
2464
|
-
|
|
2613
|
+
setPage(0);
|
|
2614
|
+
toTop();
|
|
2465
2615
|
},
|
|
2466
2616
|
onSubmit: () => setEditing(null)
|
|
2467
2617
|
}
|
|
2468
2618
|
)
|
|
2469
2619
|
] }) : null,
|
|
2470
|
-
/* @__PURE__ */
|
|
2471
|
-
/* @__PURE__ */
|
|
2620
|
+
/* @__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` : ""}` }),
|
|
2621
|
+
/* @__PURE__ */ jsx6(
|
|
2472
2622
|
Table,
|
|
2473
2623
|
{
|
|
2474
2624
|
columns: [
|
|
@@ -2482,40 +2632,45 @@ function AuditPanel({ active: active2, focused }) {
|
|
|
2482
2632
|
],
|
|
2483
2633
|
rows: windowed.map((e, i) => rowFor(e, start + i === selClamped && focused, reasonW))
|
|
2484
2634
|
}
|
|
2485
|
-
)
|
|
2635
|
+
),
|
|
2636
|
+
pageRows.length === 0 ? /* @__PURE__ */ jsxs6(Text6, { color: theme.dim, children: [
|
|
2637
|
+
"(no entries",
|
|
2638
|
+
source === "local" ? " in the local file" : "",
|
|
2639
|
+
" \u2014 c clears filters)"
|
|
2640
|
+
] }) : null
|
|
2486
2641
|
] }) });
|
|
2487
2642
|
}
|
|
2488
2643
|
function rowFor(e, active2, reasonW) {
|
|
2489
2644
|
return [
|
|
2490
2645
|
{ value: active2 ? "\u25B8" : "", color: theme.accentBright },
|
|
2491
2646
|
{ value: e.decision, color: decisionColor(e.decision) },
|
|
2492
|
-
{ value: truncate(e.
|
|
2493
|
-
{ value: truncate(e.
|
|
2494
|
-
{ value: truncate(e.reason ?? "\u2014", reasonW) },
|
|
2495
|
-
{ value: e.
|
|
2496
|
-
{ value: ago(e.
|
|
2647
|
+
{ value: truncate(e.tool, 18), color: theme.accent },
|
|
2648
|
+
{ value: truncate(e.agent ?? "\u2014", 14), dim: true },
|
|
2649
|
+
{ value: truncate(e.reason ?? e.args ?? "\u2014", reasonW) },
|
|
2650
|
+
{ value: e.dlp.length ? String(e.dlp.length) : "0", color: e.dlp.length ? theme.bad : void 0, dim: !e.dlp.length },
|
|
2651
|
+
{ value: ago(e.at), dim: true }
|
|
2497
2652
|
];
|
|
2498
2653
|
}
|
|
2499
2654
|
function Detail({ label, value, color }) {
|
|
2500
|
-
return /* @__PURE__ */
|
|
2501
|
-
/* @__PURE__ */
|
|
2502
|
-
/* @__PURE__ */
|
|
2655
|
+
return /* @__PURE__ */ jsxs6(Box6, { children: [
|
|
2656
|
+
/* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: label.padEnd(9) }),
|
|
2657
|
+
/* @__PURE__ */ jsx6(Text6, { color, wrap: "truncate", children: value })
|
|
2503
2658
|
] });
|
|
2504
2659
|
}
|
|
2505
2660
|
|
|
2506
2661
|
// src/tui/panels/Agents.tsx
|
|
2507
|
-
import { Box as
|
|
2662
|
+
import { Box as Box7, Text as Text7, useInput as useInput6 } from "ink";
|
|
2508
2663
|
import { useState as useState7 } from "react";
|
|
2509
|
-
import { Fragment as Fragment5, jsx as
|
|
2664
|
+
import { Fragment as Fragment5, jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
2510
2665
|
var statusColor = (s) => s === "active" ? theme.ok : s === "idle" ? theme.warn : theme.dim;
|
|
2511
2666
|
var sevColor = (s) => s === "high" ? theme.bad : s === "medium" ? theme.warn : theme.dim;
|
|
2512
2667
|
function Bar({ label, value, max = 100, width = 16, color = theme.accent }) {
|
|
2513
2668
|
const filled = max > 0 ? Math.round(Math.min(value, max) / max * width) : 0;
|
|
2514
|
-
return /* @__PURE__ */
|
|
2515
|
-
/* @__PURE__ */
|
|
2516
|
-
/* @__PURE__ */
|
|
2517
|
-
/* @__PURE__ */
|
|
2518
|
-
/* @__PURE__ */
|
|
2669
|
+
return /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
|
|
2670
|
+
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: label.padEnd(9) }),
|
|
2671
|
+
/* @__PURE__ */ jsx7(Text7, { color, children: "\u2588".repeat(filled) }),
|
|
2672
|
+
/* @__PURE__ */ jsx7(Text7, { color: "#233457", children: "\u2591".repeat(Math.max(0, width - filled)) }),
|
|
2673
|
+
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " " + value })
|
|
2519
2674
|
] });
|
|
2520
2675
|
}
|
|
2521
2676
|
function AgentsPanel({ focused }) {
|
|
@@ -2540,60 +2695,60 @@ function AgentsPanel({ focused }) {
|
|
|
2540
2695
|
const tools = tmap?.tools ?? [];
|
|
2541
2696
|
const resources = tmap?.resources ?? [];
|
|
2542
2697
|
const listW = 30;
|
|
2543
|
-
return /* @__PURE__ */
|
|
2544
|
-
/* @__PURE__ */
|
|
2545
|
-
/* @__PURE__ */
|
|
2546
|
-
agents.slice(0, 16).map((a, i) => /* @__PURE__ */
|
|
2547
|
-
/* @__PURE__ */
|
|
2698
|
+
return /* @__PURE__ */ jsx7(DataView, { loading: live2.loading && !live2.data, error: live2.error, empty: !!live2.data && agents.length === 0, emptyText: "No identified agents yet.", children: /* @__PURE__ */ jsxs7(Box7, { children: [
|
|
2699
|
+
/* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", width: listW, marginRight: 2, overflow: "hidden", children: [
|
|
2700
|
+
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: focused ? "\u2191\u2193 select agent" : "press \u2192 to browse" }),
|
|
2701
|
+
agents.slice(0, 16).map((a, i) => /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", backgroundColor: focused && i === sel ? "#1c2f63" : void 0, bold: i === sel, children: [
|
|
2702
|
+
/* @__PURE__ */ jsxs7(Text7, { color: statusColor(a.status), children: [
|
|
2548
2703
|
a.status === "active" ? "\u25CF" : a.status === "idle" ? "\u25D0" : "\u25CB",
|
|
2549
2704
|
" "
|
|
2550
2705
|
] }),
|
|
2551
|
-
/* @__PURE__ */
|
|
2552
|
-
/* @__PURE__ */
|
|
2706
|
+
/* @__PURE__ */ jsx7(Text7, { color: theme.accent, children: truncate(a.agent_name ?? a.agent_id ?? "?", 16).padEnd(17) }),
|
|
2707
|
+
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: `t${a.trust_score}` })
|
|
2553
2708
|
] }, a.session_id))
|
|
2554
2709
|
] }),
|
|
2555
|
-
/* @__PURE__ */
|
|
2556
|
-
/* @__PURE__ */
|
|
2557
|
-
/* @__PURE__ */
|
|
2558
|
-
/* @__PURE__ */
|
|
2710
|
+
/* @__PURE__ */ jsx7(Box7, { flexDirection: "column", flexGrow: 1, overflow: "hidden", children: !selected ? /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "select an agent" }) : detail.loading && !d ? /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "loading profile\u2026" }) : !d ? /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "no profile (agent has no calls yet)" }) : /* @__PURE__ */ jsxs7(Fragment5, { children: [
|
|
2711
|
+
/* @__PURE__ */ jsxs7(Box7, { children: [
|
|
2712
|
+
/* @__PURE__ */ jsx7(Text7, { bold: true, color: theme.accentBright, children: truncate(selected.agent_name ?? selected.agent_id ?? "?", 22) }),
|
|
2713
|
+
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: ` trust ${base?.trustScore ?? "\u2014"}/100 \xB7 ${base?.character ?? ""}` })
|
|
2559
2714
|
] }),
|
|
2560
|
-
base?.characterBlurb ? /* @__PURE__ */
|
|
2561
|
-
radar ? /* @__PURE__ */
|
|
2562
|
-
/* @__PURE__ */
|
|
2563
|
-
/* @__PURE__ */
|
|
2564
|
-
/* @__PURE__ */
|
|
2565
|
-
/* @__PURE__ */
|
|
2566
|
-
/* @__PURE__ */
|
|
2567
|
-
/* @__PURE__ */
|
|
2715
|
+
base?.characterBlurb ? /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: truncate(String(base.characterBlurb), 70) }) : null,
|
|
2716
|
+
radar ? /* @__PURE__ */ jsxs7(Box7, { marginTop: 1, flexDirection: "column", children: [
|
|
2717
|
+
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "Behaviour radar (0\u2013100)" }),
|
|
2718
|
+
/* @__PURE__ */ jsx7(Bar, { label: "read", value: Math.round((radar.read ?? 0) * 100) }),
|
|
2719
|
+
/* @__PURE__ */ jsx7(Bar, { label: "write", value: Math.round((radar.write ?? 0) * 100), color: theme.warn }),
|
|
2720
|
+
/* @__PURE__ */ jsx7(Bar, { label: "execute", value: Math.round((radar.execute ?? 0) * 100), color: theme.warn }),
|
|
2721
|
+
/* @__PURE__ */ jsx7(Bar, { label: "network", value: Math.round((radar.network ?? 0) * 100) }),
|
|
2722
|
+
/* @__PURE__ */ jsx7(Bar, { label: "complian.", value: Math.round((radar.compliance ?? 0) * 100), color: theme.ok })
|
|
2568
2723
|
] }) : null,
|
|
2569
|
-
/* @__PURE__ */
|
|
2570
|
-
/* @__PURE__ */
|
|
2724
|
+
/* @__PURE__ */ jsxs7(Box7, { marginTop: 1, flexDirection: "column", children: [
|
|
2725
|
+
/* @__PURE__ */ jsxs7(Text7, { color: theme.dim, children: [
|
|
2571
2726
|
"Anomalies ",
|
|
2572
2727
|
anomalies2.length ? `(${anomalies2.length})` : ""
|
|
2573
2728
|
] }),
|
|
2574
|
-
anomalies2.length === 0 ? /* @__PURE__ */
|
|
2575
|
-
/* @__PURE__ */
|
|
2576
|
-
/* @__PURE__ */
|
|
2577
|
-
/* @__PURE__ */
|
|
2729
|
+
anomalies2.length === 0 ? /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " none" }) : anomalies2.slice(0, 4).map((an, i) => /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
|
|
2730
|
+
/* @__PURE__ */ jsx7(Text7, { color: sevColor(String(an.severity)), children: ` ${String(an.severity).toUpperCase().slice(0, 4).padEnd(5)}` }),
|
|
2731
|
+
/* @__PURE__ */ jsx7(Text7, { color: theme.accent, children: String(an.kind).padEnd(12) }),
|
|
2732
|
+
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: truncate(String(an.description ?? ""), 44) })
|
|
2578
2733
|
] }, i))
|
|
2579
2734
|
] }),
|
|
2580
|
-
/* @__PURE__ */
|
|
2581
|
-
/* @__PURE__ */
|
|
2582
|
-
tools.slice(0, 4).map((t, i) => /* @__PURE__ */
|
|
2583
|
-
/* @__PURE__ */
|
|
2584
|
-
/* @__PURE__ */
|
|
2735
|
+
/* @__PURE__ */ jsxs7(Box7, { marginTop: 1, flexDirection: "column", children: [
|
|
2736
|
+
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "Trust map \xB7 top tools" }),
|
|
2737
|
+
tools.slice(0, 4).map((t, i) => /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
|
|
2738
|
+
/* @__PURE__ */ jsx7(Text7, { color: t.anomalous ? theme.bad : theme.accent, children: ` ${truncate(String(t.name), 16).padEnd(17)}` }),
|
|
2739
|
+
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: `${t.count}\xD7 ${t.permission ?? ""}` })
|
|
2585
2740
|
] }, i)),
|
|
2586
|
-
resources.slice(0, 3).map((r, i) => /* @__PURE__ */
|
|
2741
|
+
resources.slice(0, 3).map((r, i) => /* @__PURE__ */ jsx7(Text7, { 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))
|
|
2587
2742
|
] })
|
|
2588
2743
|
] }) })
|
|
2589
2744
|
] }) });
|
|
2590
2745
|
}
|
|
2591
2746
|
|
|
2592
2747
|
// src/tui/panels/Settings.tsx
|
|
2593
|
-
import { Box as
|
|
2748
|
+
import { Box as Box8, Text as Text8, useInput as useInput7 } from "ink";
|
|
2594
2749
|
import TextInput5 from "ink-text-input";
|
|
2595
2750
|
import { useState as useState8 } from "react";
|
|
2596
|
-
import { jsx as
|
|
2751
|
+
import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
2597
2752
|
var EVENTS = ["denials", "allowed", "all"];
|
|
2598
2753
|
function alertBodyFor(channel) {
|
|
2599
2754
|
const c2 = channel.trim();
|
|
@@ -2719,7 +2874,7 @@ function SettingsPanel({ active: active2, focused }) {
|
|
|
2719
2874
|
const loading = localQ.loading && !localQ.data || whQ.loading && !whQ.data || alertQ.loading && !alertQ.data;
|
|
2720
2875
|
const error = localQ.error ?? whQ.error ?? alertQ.error;
|
|
2721
2876
|
const mark = (r) => keyOf(r) === keyOf(cur) && focused ? "\u25B8 " : " ";
|
|
2722
|
-
const onOff = (on) => on ? /* @__PURE__ */
|
|
2877
|
+
const onOff = (on) => on ? /* @__PURE__ */ jsx8(Text8, { color: theme.ok, children: "on " }) : /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "off" });
|
|
2723
2878
|
const chanOf = (rule) => [...(rule.slackUrls ?? []).map((u) => "slack " + u), ...rule.emails ?? [], ...(rule.telegram ?? []).map((t) => "tg " + t)].join(", ") || "\u2014";
|
|
2724
2879
|
const listBudget = Math.max(4, rows - 8);
|
|
2725
2880
|
const start = Math.min(Math.max(0, selClamped - Math.floor(listBudget / 2)), Math.max(0, rowsAll.length - listBudget));
|
|
@@ -2727,83 +2882,83 @@ function SettingsPanel({ active: active2, focused }) {
|
|
|
2727
2882
|
const i = rowsAll.findIndex((x) => keyOf(x) === keyOf(r));
|
|
2728
2883
|
return i >= start && i < start + listBudget;
|
|
2729
2884
|
};
|
|
2730
|
-
return /* @__PURE__ */
|
|
2731
|
-
/* @__PURE__ */
|
|
2732
|
-
editing ? /* @__PURE__ */
|
|
2733
|
-
/* @__PURE__ */
|
|
2734
|
-
/* @__PURE__ */
|
|
2735
|
-
] }) : msg ? /* @__PURE__ */
|
|
2736
|
-
inWindow(rowsAll[0]) || inWindow(rowsAll[1]) ? /* @__PURE__ */
|
|
2737
|
-
/* @__PURE__ */
|
|
2885
|
+
return /* @__PURE__ */ jsx8(DataView, { loading, error, children: /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
|
|
2886
|
+
/* @__PURE__ */ jsx8(Text8, { 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" }),
|
|
2887
|
+
editing ? /* @__PURE__ */ jsxs8(Box8, { children: [
|
|
2888
|
+
/* @__PURE__ */ jsx8(Text8, { color: theme.warn, children: editing === "path" ? "local log path: " : editing === "wh-url" ? "webhook url: " : "channel (slack url / email / telegram id): " }),
|
|
2889
|
+
/* @__PURE__ */ jsx8(TextInput5, { value: input, onChange: setInput, onSubmit: submitInput })
|
|
2890
|
+
] }) : msg ? /* @__PURE__ */ jsx8(Text8, { color: msg.level === "bad" ? theme.bad : theme.ok, children: truncate(msg.text, cols) }) : /* @__PURE__ */ jsx8(Text8, { children: " " }),
|
|
2891
|
+
inWindow(rowsAll[0]) || inWindow(rowsAll[1]) ? /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexDirection: "column", children: [
|
|
2892
|
+
/* @__PURE__ */ jsxs8(Text8, { bold: true, color: theme.accentBright, children: [
|
|
2738
2893
|
"LOCAL LOGS ",
|
|
2739
|
-
/* @__PURE__ */
|
|
2894
|
+
/* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "\u2014 hooks mirror every decision to a file on the agent machine" })
|
|
2740
2895
|
] }),
|
|
2741
|
-
/* @__PURE__ */
|
|
2742
|
-
/* @__PURE__ */
|
|
2743
|
-
/* @__PURE__ */
|
|
2896
|
+
/* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
|
|
2897
|
+
/* @__PURE__ */ jsx8(Text8, { color: keyOf(cur) === "ll-enabled" && focused ? theme.accentBright : theme.dim, children: mark(rowsAll[0]) }),
|
|
2898
|
+
/* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "enabled ".padEnd(10) }),
|
|
2744
2899
|
onOff(!!local?.enabled),
|
|
2745
|
-
/* @__PURE__ */
|
|
2900
|
+
/* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: " enter toggles" })
|
|
2746
2901
|
] }),
|
|
2747
|
-
/* @__PURE__ */
|
|
2748
|
-
/* @__PURE__ */
|
|
2749
|
-
/* @__PURE__ */
|
|
2750
|
-
local?.path ? /* @__PURE__ */
|
|
2902
|
+
/* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
|
|
2903
|
+
/* @__PURE__ */ jsx8(Text8, { color: keyOf(cur) === "ll-path" && focused ? theme.accentBright : theme.dim, children: mark(rowsAll[1]) }),
|
|
2904
|
+
/* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "path ".padEnd(10) }),
|
|
2905
|
+
local?.path ? /* @__PURE__ */ jsx8(Text8, { color: theme.accent, children: truncate(local.path, cols - 14) }) : /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "not set \u2014 enter to edit" })
|
|
2751
2906
|
] })
|
|
2752
2907
|
] }) : null,
|
|
2753
|
-
/* @__PURE__ */
|
|
2754
|
-
/* @__PURE__ */
|
|
2908
|
+
/* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexDirection: "column", children: [
|
|
2909
|
+
/* @__PURE__ */ jsxs8(Text8, { bold: true, color: theme.accentBright, children: [
|
|
2755
2910
|
"WEBHOOKS ",
|
|
2756
|
-
/* @__PURE__ */
|
|
2911
|
+
/* @__PURE__ */ jsxs8(Text8, { color: theme.dim, children: [
|
|
2757
2912
|
"\u2014 POST every matching event to your endpoint (",
|
|
2758
2913
|
webhooks.length,
|
|
2759
2914
|
")"
|
|
2760
2915
|
] })
|
|
2761
2916
|
] }),
|
|
2762
|
-
webhooks.filter((wh) => inWindow({ kind: "wh", wh })).map((wh) => /* @__PURE__ */
|
|
2763
|
-
/* @__PURE__ */
|
|
2917
|
+
webhooks.filter((wh) => inWindow({ kind: "wh", wh })).map((wh) => /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
|
|
2918
|
+
/* @__PURE__ */ jsx8(Text8, { color: keyOf(cur) === "wh:" + wh.id && focused ? theme.accentBright : theme.dim, children: mark({ kind: "wh", wh }) }),
|
|
2764
2919
|
onOff(wh.enabled),
|
|
2765
|
-
/* @__PURE__ */
|
|
2766
|
-
/* @__PURE__ */
|
|
2920
|
+
/* @__PURE__ */ jsx8(Text8, { color: theme.warn, children: (" " + wh.events).padEnd(9) }),
|
|
2921
|
+
/* @__PURE__ */ jsx8(Text8, { color: theme.accent, children: truncate(wh.url, cols - 16) })
|
|
2767
2922
|
] }, wh.id)),
|
|
2768
|
-
inWindow(rowsAll.find((r) => r.kind === "wh-add")) ? /* @__PURE__ */
|
|
2769
|
-
/* @__PURE__ */
|
|
2770
|
-
/* @__PURE__ */
|
|
2923
|
+
inWindow(rowsAll.find((r) => r.kind === "wh-add")) ? /* @__PURE__ */ jsxs8(Text8, { children: [
|
|
2924
|
+
/* @__PURE__ */ jsx8(Text8, { color: cur.kind === "wh-add" && focused ? theme.accentBright : theme.dim, children: mark({ kind: "wh-add" }) }),
|
|
2925
|
+
/* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "+ add webhook (enter)" })
|
|
2771
2926
|
] }) : null
|
|
2772
2927
|
] }),
|
|
2773
|
-
/* @__PURE__ */
|
|
2774
|
-
/* @__PURE__ */
|
|
2928
|
+
/* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexDirection: "column", children: [
|
|
2929
|
+
/* @__PURE__ */ jsxs8(Text8, { bold: true, color: theme.accentBright, children: [
|
|
2775
2930
|
"ALERTS ",
|
|
2776
|
-
/* @__PURE__ */
|
|
2931
|
+
/* @__PURE__ */ jsxs8(Text8, { color: theme.dim, children: [
|
|
2777
2932
|
"\u2014 notify on a signal spike (",
|
|
2778
2933
|
alerts.length,
|
|
2779
2934
|
")"
|
|
2780
2935
|
] })
|
|
2781
2936
|
] }),
|
|
2782
|
-
alerts.filter((rule) => inWindow({ kind: "alert", rule })).map((rule) => /* @__PURE__ */
|
|
2783
|
-
/* @__PURE__ */
|
|
2937
|
+
alerts.filter((rule) => inWindow({ kind: "alert", rule })).map((rule) => /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
|
|
2938
|
+
/* @__PURE__ */ jsx8(Text8, { color: keyOf(cur) === "alert:" + rule.id && focused ? theme.accentBright : theme.dim, children: mark({ kind: "alert", rule }) }),
|
|
2784
2939
|
onOff(rule.enabled),
|
|
2785
|
-
/* @__PURE__ */
|
|
2786
|
-
/* @__PURE__ */
|
|
2787
|
-
/* @__PURE__ */
|
|
2940
|
+
/* @__PURE__ */ jsx8(Text8, { color: theme.warn, children: (" " + rule.signal).padEnd(11) }),
|
|
2941
|
+
/* @__PURE__ */ jsx8(Text8, { children: `\u2265${rule.threshold}/${rule.windowSeconds}s ` }),
|
|
2942
|
+
/* @__PURE__ */ jsx8(Text8, { color: theme.accent, children: truncate(chanOf(rule), cols - 26) })
|
|
2788
2943
|
] }, rule.id)),
|
|
2789
|
-
inWindow(rowsAll[rowsAll.length - 1]) ? /* @__PURE__ */
|
|
2790
|
-
/* @__PURE__ */
|
|
2791
|
-
/* @__PURE__ */
|
|
2944
|
+
inWindow(rowsAll[rowsAll.length - 1]) ? /* @__PURE__ */ jsxs8(Text8, { children: [
|
|
2945
|
+
/* @__PURE__ */ jsx8(Text8, { color: cur.kind === "alert-add" && focused ? theme.accentBright : theme.dim, children: mark({ kind: "alert-add" }) }),
|
|
2946
|
+
/* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "+ add alert (enter \u2014 one channel: slack url, email or telegram chat id)" })
|
|
2792
2947
|
] }) : null
|
|
2793
2948
|
] })
|
|
2794
2949
|
] }) });
|
|
2795
2950
|
}
|
|
2796
2951
|
|
|
2797
2952
|
// src/tui/App.tsx
|
|
2798
|
-
import { jsx as
|
|
2953
|
+
import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
2799
2954
|
function LiveHint() {
|
|
2800
|
-
return /* @__PURE__ */
|
|
2801
|
-
/* @__PURE__ */
|
|
2802
|
-
/* @__PURE__ */
|
|
2803
|
-
/* @__PURE__ */
|
|
2804
|
-
/* @__PURE__ */
|
|
2955
|
+
return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
|
|
2956
|
+
/* @__PURE__ */ jsx9(Text9, { color: theme.accentBright, bold: true, children: "\u25B6 REALTIME CONSOLE" }),
|
|
2957
|
+
/* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "Fullscreen live monitor \u2014 streaming tool calls, active charts," }),
|
|
2958
|
+
/* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "counters, DLP hits and denial alerts. Updates every 2s." }),
|
|
2959
|
+
/* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsxs9(Text9, { children: [
|
|
2805
2960
|
"press ",
|
|
2806
|
-
/* @__PURE__ */
|
|
2961
|
+
/* @__PURE__ */ jsx9(Text9, { color: theme.accent, children: "enter" }),
|
|
2807
2962
|
" to go live"
|
|
2808
2963
|
] }) })
|
|
2809
2964
|
] });
|
|
@@ -2814,7 +2969,6 @@ var SECTIONS = [
|
|
|
2814
2969
|
{ label: "Policies", Panel: PoliciesPanel },
|
|
2815
2970
|
{ label: "Rate Limit", Panel: RateLimitPanel },
|
|
2816
2971
|
{ label: "DLP", Panel: DlpPanel },
|
|
2817
|
-
{ label: "Stats", Panel: StatsPanel },
|
|
2818
2972
|
{ label: "Audit", Panel: AuditPanel },
|
|
2819
2973
|
{ label: "Settings", Panel: SettingsPanel }
|
|
2820
2974
|
];
|
|
@@ -2822,14 +2976,14 @@ var BANNER_HEX = ["#1432A0", "#2850BE", "#3C6ED7", "#5A8CE6", "#82AAF0", "#AAC8F
|
|
|
2822
2976
|
function Banner({ cols }) {
|
|
2823
2977
|
const wide = cols >= 82;
|
|
2824
2978
|
if (!wide) {
|
|
2825
|
-
return /* @__PURE__ */
|
|
2826
|
-
/* @__PURE__ */
|
|
2827
|
-
/* @__PURE__ */
|
|
2979
|
+
return /* @__PURE__ */ jsxs9(Box9, { children: [
|
|
2980
|
+
/* @__PURE__ */ jsx9(Text9, { bold: true, color: theme.accentBright, children: "SolonGate" }),
|
|
2981
|
+
/* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: " \u2014 security control center" })
|
|
2828
2982
|
] });
|
|
2829
2983
|
}
|
|
2830
|
-
return /* @__PURE__ */
|
|
2831
|
-
BANNER_FULL.map((line, i) => /* @__PURE__ */
|
|
2832
|
-
/* @__PURE__ */
|
|
2984
|
+
return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
|
|
2985
|
+
BANNER_FULL.map((line, i) => /* @__PURE__ */ jsx9(Text9, { bold: true, color: BANNER_HEX[i], children: line }, i)),
|
|
2986
|
+
/* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: " security control center \xB7 manage policies, rate limits, DLP & more" })
|
|
2833
2987
|
] });
|
|
2834
2988
|
}
|
|
2835
2989
|
function App() {
|
|
@@ -2858,44 +3012,44 @@ function App() {
|
|
|
2858
3012
|
});
|
|
2859
3013
|
const { cols, rows } = useTermSize();
|
|
2860
3014
|
const current = SECTIONS[section];
|
|
2861
|
-
if (help) return /* @__PURE__ */
|
|
3015
|
+
if (help) return /* @__PURE__ */ jsx9(HelpOverlay, { cols, rows });
|
|
2862
3016
|
if (current.label === "Live" && focus === "panel") {
|
|
2863
|
-
return /* @__PURE__ */
|
|
3017
|
+
return /* @__PURE__ */ jsx9(Box9, { flexDirection: "column", width: cols, height: rows, children: /* @__PURE__ */ jsx9(LivePanel, { active: true, focused: true }) });
|
|
2864
3018
|
}
|
|
2865
3019
|
const Panel = current.Panel;
|
|
2866
|
-
return /* @__PURE__ */
|
|
2867
|
-
/* @__PURE__ */
|
|
2868
|
-
/* @__PURE__ */
|
|
2869
|
-
/* @__PURE__ */
|
|
2870
|
-
/* @__PURE__ */
|
|
3020
|
+
return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", width: cols, height: rows, paddingX: 1, paddingTop: 1, children: [
|
|
3021
|
+
/* @__PURE__ */ jsx9(Banner, { cols }),
|
|
3022
|
+
/* @__PURE__ */ jsxs9(Box9, { marginTop: 1, flexGrow: 1, children: [
|
|
3023
|
+
/* @__PURE__ */ jsx9(Box9, { flexDirection: "column", width: 16, borderStyle: "round", borderColor: focus === "nav" ? theme.accent : "gray", paddingX: 1, children: SECTIONS.map((s, i) => /* @__PURE__ */ jsx9(Text9, { color: i === section ? theme.accentBright : void 0, bold: i === section, children: (i === section ? "\u25B8 " : " ") + s.label }, s.label)) }),
|
|
3024
|
+
/* @__PURE__ */ jsx9(Box9, { flexGrow: 1, borderStyle: "round", borderColor: focus === "panel" ? theme.accent : "gray", paddingX: 1, paddingY: 0, children: /* @__PURE__ */ jsx9(Panel, { active: focus === "panel" || current.label !== "Live", focused: focus === "panel" }) })
|
|
2871
3025
|
] }),
|
|
2872
|
-
/* @__PURE__ */
|
|
3026
|
+
/* @__PURE__ */ jsx9(Box9, { children: focus === "nav" ? /* @__PURE__ */ jsx9(KeyHints, { hints: [["\u2191\u2193", "section"], ["\u2192/enter", "open"], ["?", "help"], ["q", "quit"]] }) : /* @__PURE__ */ jsx9(KeyHints, { hints: [["\u2190/esc", "back"], ["\u2191\u2193", "in-panel"], ["space/s", "act"]] }) })
|
|
2873
3027
|
] });
|
|
2874
3028
|
}
|
|
2875
3029
|
var HELP = [
|
|
2876
3030
|
["Global", [["\u2191\u2193", "move between sections"], ["\u2192 / enter", "open a section"], ["\u2190 / esc", "back to the menu"], ["?", "this help"], ["q", "quit"]]],
|
|
2877
3031
|
["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)"]]],
|
|
2878
|
-
["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"], ["
|
|
3032
|
+
["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"]]],
|
|
2879
3033
|
["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"]]],
|
|
2880
|
-
["Audit", [["\u2191\u2193", "select (list scrolls)"], ["enter", "full entry
|
|
3034
|
+
["Audit", [["v", "logs \u2194 sessions"], ["s", "source: cloud \u2194 local"], ["[ / ]", "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"]]],
|
|
2881
3035
|
["Settings", [["\u2191\u2193", "move"], ["enter / space", "toggle \xB7 edit \xB7 add"], ["e", "webhook events"], ["d d", "delete"], ["r", "refresh"]]]
|
|
2882
3036
|
];
|
|
2883
3037
|
function HelpOverlay({ cols, rows }) {
|
|
2884
|
-
return /* @__PURE__ */
|
|
2885
|
-
/* @__PURE__ */
|
|
2886
|
-
/* @__PURE__ */
|
|
2887
|
-
/* @__PURE__ */
|
|
2888
|
-
keys.map(([k, desc]) => /* @__PURE__ */
|
|
2889
|
-
/* @__PURE__ */
|
|
2890
|
-
/* @__PURE__ */
|
|
3038
|
+
return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", width: cols, height: rows, paddingX: 2, paddingTop: 1, children: [
|
|
3039
|
+
/* @__PURE__ */ jsx9(Text9, { bold: true, color: theme.accentBright, children: "SolonGate \u2014 keyboard shortcuts" }),
|
|
3040
|
+
/* @__PURE__ */ jsx9(Box9, { marginTop: 1, flexDirection: "column", children: HELP.map(([group, keys]) => /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", marginBottom: 1, children: [
|
|
3041
|
+
/* @__PURE__ */ jsx9(Text9, { bold: true, color: theme.accent, children: group }),
|
|
3042
|
+
keys.map(([k, desc]) => /* @__PURE__ */ jsxs9(Text9, { children: [
|
|
3043
|
+
/* @__PURE__ */ jsx9(Text9, { color: theme.accentBright, children: (" " + k).padEnd(16) }),
|
|
3044
|
+
/* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: desc })
|
|
2891
3045
|
] }, k))
|
|
2892
3046
|
] }, group)) }),
|
|
2893
|
-
/* @__PURE__ */
|
|
3047
|
+
/* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "press any key to close" })
|
|
2894
3048
|
] });
|
|
2895
3049
|
}
|
|
2896
3050
|
|
|
2897
3051
|
// src/tui/index.tsx
|
|
2898
|
-
import { jsx as
|
|
3052
|
+
import { jsx as jsx10 } from "react/jsx-runtime";
|
|
2899
3053
|
async function launchTui() {
|
|
2900
3054
|
if (!process.stdout.isTTY || !process.stdin.isTTY) {
|
|
2901
3055
|
process.stderr.write(
|
|
@@ -2909,7 +3063,7 @@ async function launchTui() {
|
|
|
2909
3063
|
}
|
|
2910
3064
|
process.stdout.write("\x1B[?1049h\x1B[H");
|
|
2911
3065
|
try {
|
|
2912
|
-
const { waitUntilExit } = render(/* @__PURE__ */
|
|
3066
|
+
const { waitUntilExit } = render(/* @__PURE__ */ jsx10(App, {}));
|
|
2913
3067
|
await waitUntilExit();
|
|
2914
3068
|
} finally {
|
|
2915
3069
|
process.stdout.write("\x1B[?1049l");
|