@solongate/proxy 0.61.0 → 0.63.0
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 +685 -296
- package/dist/tui/index.js +668 -280
- package/dist/tui/panels/Live.d.ts +4 -0
- package/package.json +1 -1
- package/dist/tui/panels/Agents.d.ts +0 -3
package/dist/index.js
CHANGED
|
@@ -7018,47 +7018,234 @@ var init_hooks = __esm({
|
|
|
7018
7018
|
}
|
|
7019
7019
|
});
|
|
7020
7020
|
|
|
7021
|
-
// src/tui/panels/
|
|
7022
|
-
import { Box as Box2, Text as Text2
|
|
7023
|
-
import { useEffect as useEffect2, useState as useState2 } from "react";
|
|
7021
|
+
// src/tui/panels/Live.tsx
|
|
7022
|
+
import { Box as Box2, Text as Text2 } from "ink";
|
|
7023
|
+
import { useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
|
|
7024
7024
|
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
7025
|
+
function LivePanel({ active: active2 }) {
|
|
7026
|
+
const stats = useLoader(() => api.stats.get());
|
|
7027
|
+
const ts = useLoader(() => api.stats.timeseries({ period: "24h" }));
|
|
7028
|
+
const feed = useLoader(() => api.audit.list({ limit: 40 }));
|
|
7029
|
+
usePoll(() => {
|
|
7030
|
+
stats.reload();
|
|
7031
|
+
ts.reload();
|
|
7032
|
+
feed.reload();
|
|
7033
|
+
}, 2e3, active2);
|
|
7034
|
+
const [tick, setTick] = useState2(0);
|
|
7035
|
+
useEffect2(() => {
|
|
7036
|
+
if (!active2) return;
|
|
7037
|
+
const t = setInterval(() => setTick((n) => n + 1), 200);
|
|
7038
|
+
return () => clearInterval(t);
|
|
7039
|
+
}, [active2]);
|
|
7040
|
+
const startRef = useRef2(Date.now());
|
|
7041
|
+
const [buffer, setBuffer] = useState2([]);
|
|
7042
|
+
useEffect2(() => {
|
|
7043
|
+
const incoming = feed.data?.entries;
|
|
7044
|
+
if (!incoming?.length) return;
|
|
7045
|
+
setBuffer((prev) => {
|
|
7046
|
+
const seen = new Set(prev.map((e) => e.id));
|
|
7047
|
+
const fresh = incoming.filter((e) => !seen.has(e.id)).sort((a, b) => Date.parse(a.created_at) - Date.parse(b.created_at));
|
|
7048
|
+
return fresh.length ? [...prev, ...fresh].slice(-300) : prev;
|
|
7049
|
+
});
|
|
7050
|
+
}, [feed.data]);
|
|
7051
|
+
const s = stats.data;
|
|
7052
|
+
const points = ts.data?.timeseries ?? [];
|
|
7053
|
+
const cols = process.stdout.columns ?? 100;
|
|
7054
|
+
const rows = process.stdout.rows ?? 32;
|
|
7055
|
+
const feedRows = Math.max(8, Math.min(20, rows - 18));
|
|
7056
|
+
const wide = cols >= 112;
|
|
7057
|
+
const tail = buffer.slice(-feedRows);
|
|
7058
|
+
const denies = buffer.filter((e) => e.decision !== "ALLOW");
|
|
7059
|
+
const dlpHits = buffer.filter((e) => e.dlp_matches?.length);
|
|
7060
|
+
const lastDeny = denies[denies.length - 1];
|
|
7061
|
+
const spin = SPIN[tick % SPIN.length];
|
|
7062
|
+
const cursor = tick % 4 < 2 ? "\u258C" : " ";
|
|
7063
|
+
if (stats.error) return /* @__PURE__ */ jsxs2(Text2, { color: theme.bad, children: [
|
|
7064
|
+
"\u2717 ",
|
|
7065
|
+
stats.error
|
|
7066
|
+
] });
|
|
7067
|
+
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
|
|
7068
|
+
/* @__PURE__ */ jsxs2(Box2, { children: [
|
|
7069
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.bad, children: "\u25CF " }),
|
|
7070
|
+
/* @__PURE__ */ jsx2(Text2, { bold: true, color: theme.accentBright, children: "LIVE" }),
|
|
7071
|
+
/* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
|
|
7072
|
+
" ",
|
|
7073
|
+
spin,
|
|
7074
|
+
" scanning"
|
|
7075
|
+
] }),
|
|
7076
|
+
/* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
|
|
7077
|
+
" up ",
|
|
7078
|
+
fmtUp(Date.now() - startRef.current)
|
|
7079
|
+
] }),
|
|
7080
|
+
/* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
|
|
7081
|
+
" ",
|
|
7082
|
+
hhmmss(Date.now())
|
|
7083
|
+
] }),
|
|
7084
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " poll 2s" })
|
|
7085
|
+
] }),
|
|
7086
|
+
/* @__PURE__ */ jsxs2(Box2, { children: [
|
|
7087
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "calls " }),
|
|
7088
|
+
/* @__PURE__ */ jsx2(Text2, { bold: true, children: s ? s.total_calls : "\xB7\xB7\xB7\xB7" }),
|
|
7089
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " allow " }),
|
|
7090
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.ok, children: s ? s.allowed : "\xB7\xB7\xB7" }),
|
|
7091
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " deny " }),
|
|
7092
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.bad, bold: true, children: s ? s.denied : "\xB7\xB7" }),
|
|
7093
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " dlp(buf) " }),
|
|
7094
|
+
/* @__PURE__ */ jsx2(Text2, { color: dlpHits.length ? theme.bad : theme.dim, children: dlpHits.length }),
|
|
7095
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " policies " }),
|
|
7096
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: s ? s.active_policies : "\xB7" })
|
|
7097
|
+
] }),
|
|
7098
|
+
/* @__PURE__ */ jsxs2(Box2, { children: [
|
|
7099
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "act " }),
|
|
7100
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: sparkline(points.map((p) => p.total), Math.min(64, cols - 30)) })
|
|
7101
|
+
] }),
|
|
7102
|
+
/* @__PURE__ */ jsxs2(Box2, { children: [
|
|
7103
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "dny " }),
|
|
7104
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.bad, children: sparkline(points.map((p) => p.denied), Math.min(64, cols - 30)) })
|
|
7105
|
+
] }),
|
|
7106
|
+
/* @__PURE__ */ jsxs2(Box2, { children: [
|
|
7107
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: ">> " }),
|
|
7108
|
+
lastDeny ? /* @__PURE__ */ jsxs2(Text2, { color: theme.bad, children: [
|
|
7109
|
+
"last deny ",
|
|
7110
|
+
hhmmss(lastDeny.created_at),
|
|
7111
|
+
" ",
|
|
7112
|
+
lastDeny.tool_name,
|
|
7113
|
+
" \xB7 ",
|
|
7114
|
+
truncate2(lastDeny.reason ?? "policy", Math.max(20, cols - 50))
|
|
7115
|
+
] }) : /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "no denials in buffer" })
|
|
7116
|
+
] }),
|
|
7117
|
+
/* @__PURE__ */ jsxs2(Box2, { marginTop: 1, children: [
|
|
7118
|
+
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", flexGrow: 3, borderStyle: "single", borderColor: "gray", paddingX: 1, children: [
|
|
7119
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, bold: true, children: "\u2500\u2500 tool stream \u2500\u2500" }),
|
|
7120
|
+
tail.length === 0 ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "awaiting traffic\u2026" }) : null,
|
|
7121
|
+
tail.map((e) => /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
7122
|
+
/* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
|
|
7123
|
+
"[",
|
|
7124
|
+
hhmmss(e.created_at),
|
|
7125
|
+
"] "
|
|
7126
|
+
] }),
|
|
7127
|
+
/* @__PURE__ */ jsx2(Text2, { color: decisionColor(e.decision), bold: e.decision !== "ALLOW", children: e.decision.padEnd(6) }),
|
|
7128
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: truncate2(e.tool_name, 14).padEnd(15) }),
|
|
7129
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: (e.permission ?? "").slice(0, 4).padEnd(5) }),
|
|
7130
|
+
e.dlp_matches?.length ? /* @__PURE__ */ jsx2(Text2, { color: theme.bad, children: "DLP! " }) : null,
|
|
7131
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: (e.arguments_summary ? JSON.stringify(e.arguments_summary) : e.reason ?? "").replace(/\s+/g, " ") })
|
|
7132
|
+
] }, e.id))
|
|
7133
|
+
] }),
|
|
7134
|
+
wide ? /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: 34, borderStyle: "single", borderColor: "gray", paddingX: 1, children: [
|
|
7135
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, bold: true, children: "\u2500\u2500 raw \u2500\u2500" }),
|
|
7136
|
+
tail.slice(-Math.floor(feedRows / 2)).flatMap((e) => {
|
|
7137
|
+
const raw = e.arguments_summary ? JSON.stringify(e.arguments_summary) : `hash:${e.id.slice(0, 12)}`;
|
|
7138
|
+
return [
|
|
7139
|
+
/* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", color: e.decision === "ALLOW" ? theme.dim : theme.bad, children: [
|
|
7140
|
+
e.id.slice(0, 8),
|
|
7141
|
+
" ",
|
|
7142
|
+
e.tool_name.slice(0, 12)
|
|
7143
|
+
] }, e.id + ":h"),
|
|
7144
|
+
/* @__PURE__ */ jsx2(Text2, { wrap: "truncate", color: theme.dim, children: " " + raw.replace(/\s+/g, " ") }, e.id + ":b")
|
|
7145
|
+
];
|
|
7146
|
+
})
|
|
7147
|
+
] }) : null
|
|
7148
|
+
] }),
|
|
7149
|
+
/* @__PURE__ */ jsxs2(Box2, { children: [
|
|
7150
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.ok, children: "root@solongate" }),
|
|
7151
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: ":" }),
|
|
7152
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.accentBright, children: "~/live" }),
|
|
7153
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "$ " }),
|
|
7154
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.accentBright, children: cursor })
|
|
7155
|
+
] })
|
|
7156
|
+
] });
|
|
7157
|
+
}
|
|
7158
|
+
var SPIN, hhmmss, fmtUp;
|
|
7159
|
+
var init_Live = __esm({
|
|
7160
|
+
"src/tui/panels/Live.tsx"() {
|
|
7161
|
+
"use strict";
|
|
7162
|
+
init_api_client();
|
|
7163
|
+
init_hooks();
|
|
7164
|
+
init_theme();
|
|
7165
|
+
SPIN = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
7166
|
+
hhmmss = (ts) => {
|
|
7167
|
+
const d = new Date(ts);
|
|
7168
|
+
return Number.isNaN(d.getTime()) ? "--:--:--" : d.toTimeString().slice(0, 8);
|
|
7169
|
+
};
|
|
7170
|
+
fmtUp = (ms) => {
|
|
7171
|
+
const s = Math.floor(ms / 1e3);
|
|
7172
|
+
const p = (n) => String(n).padStart(2, "0");
|
|
7173
|
+
return `${p(Math.floor(s / 3600))}:${p(Math.floor(s % 3600 / 60))}:${p(s % 60)}`;
|
|
7174
|
+
};
|
|
7175
|
+
}
|
|
7176
|
+
});
|
|
7177
|
+
|
|
7178
|
+
// src/tui/panels/Policies.tsx
|
|
7179
|
+
import { Box as Box3, Text as Text3, useInput } from "ink";
|
|
7180
|
+
import TextInput from "ink-text-input";
|
|
7181
|
+
import { useEffect as useEffect3, useState as useState3 } from "react";
|
|
7182
|
+
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
7183
|
+
function ruleSummary(r) {
|
|
7184
|
+
const bits = [];
|
|
7185
|
+
for (const [g, tag] of [["commandConstraints", "cmd"], ["pathConstraints", "path"], ["filenameConstraints", "file"], ["urlConstraints", "url"]]) {
|
|
7186
|
+
const c2 = r[g];
|
|
7187
|
+
const n = (c2?.allowed?.length ?? 0) + (c2?.denied?.length ?? 0);
|
|
7188
|
+
if (n) bits.push(`${tag}:${n}`);
|
|
7189
|
+
}
|
|
7190
|
+
return bits.join(" ");
|
|
7191
|
+
}
|
|
7025
7192
|
function PoliciesPanel({ focused }) {
|
|
7026
7193
|
const list4 = useLoader(() => api.policies.list());
|
|
7027
7194
|
const policies = list4.data?.policies ?? [];
|
|
7028
|
-
const [view, setView] =
|
|
7029
|
-
const [pi, setPi] =
|
|
7030
|
-
const [ri, setRi] =
|
|
7031
|
-
const [
|
|
7032
|
-
const [
|
|
7033
|
-
const [
|
|
7195
|
+
const [view, setView] = useState3("list");
|
|
7196
|
+
const [pi, setPi] = useState3(0);
|
|
7197
|
+
const [ri, setRi] = useState3(0);
|
|
7198
|
+
const [fi, setFi] = useState3(0);
|
|
7199
|
+
const [rules, setRules] = useState3([]);
|
|
7200
|
+
const [mode, setMode] = useState3("denylist");
|
|
7201
|
+
const [dirty, setDirty] = useState3(false);
|
|
7202
|
+
const [editing, setEditing] = useState3(false);
|
|
7203
|
+
const [editVal, setEditVal] = useState3("");
|
|
7204
|
+
const [status, setStatus] = useState3(null);
|
|
7034
7205
|
const selected = policies[Math.min(pi, Math.max(0, policies.length - 1))];
|
|
7035
7206
|
const detail = useLoader(() => selected ? api.policies.get(selected.id) : Promise.resolve(null), [selected?.id]);
|
|
7036
|
-
|
|
7207
|
+
useEffect3(() => {
|
|
7037
7208
|
if (detail.data) {
|
|
7038
7209
|
setRules(detail.data.rules);
|
|
7039
7210
|
setMode(detail.data.mode ?? "denylist");
|
|
7211
|
+
setDirty(false);
|
|
7040
7212
|
setRi(0);
|
|
7041
7213
|
}
|
|
7042
7214
|
}, [detail.data]);
|
|
7043
|
-
const
|
|
7044
|
-
|
|
7215
|
+
const mutate = (nextRules, nextMode = mode) => {
|
|
7216
|
+
setRules(nextRules);
|
|
7217
|
+
setMode(nextMode);
|
|
7218
|
+
setDirty(true);
|
|
7219
|
+
setStatus(null);
|
|
7220
|
+
};
|
|
7221
|
+
const save = async () => {
|
|
7222
|
+
if (!selected || !detail.data) return;
|
|
7045
7223
|
setStatus("Saving\u2026");
|
|
7046
7224
|
try {
|
|
7047
7225
|
const res = await api.policies.update(selected.id, {
|
|
7048
7226
|
id: selected.id,
|
|
7049
7227
|
name: detail.data.name,
|
|
7050
7228
|
description: detail.data.description,
|
|
7051
|
-
mode
|
|
7229
|
+
mode,
|
|
7052
7230
|
agents: detail.data.agents,
|
|
7053
|
-
rules
|
|
7231
|
+
rules
|
|
7054
7232
|
});
|
|
7055
7233
|
setRules(res.rules);
|
|
7234
|
+
setDirty(false);
|
|
7056
7235
|
setStatus("\u2713 Saved v" + res._version);
|
|
7057
7236
|
list4.reload();
|
|
7058
7237
|
} catch (e) {
|
|
7059
7238
|
setStatus("\u2717 " + (e instanceof Error ? e.message : String(e)));
|
|
7060
7239
|
}
|
|
7061
7240
|
};
|
|
7241
|
+
const discard = () => {
|
|
7242
|
+
if (detail.data) {
|
|
7243
|
+
setRules(detail.data.rules);
|
|
7244
|
+
setMode(detail.data.mode ?? "denylist");
|
|
7245
|
+
}
|
|
7246
|
+
setDirty(false);
|
|
7247
|
+
setStatus("discarded");
|
|
7248
|
+
};
|
|
7062
7249
|
useInput(
|
|
7063
7250
|
(input, key) => {
|
|
7064
7251
|
if (view === "list") {
|
|
@@ -7066,44 +7253,61 @@ function PoliciesPanel({ focused }) {
|
|
|
7066
7253
|
else if (key.downArrow) setPi((n) => Math.min(policies.length - 1, n + 1));
|
|
7067
7254
|
else if (key.return || key.rightArrow) {
|
|
7068
7255
|
setStatus(null);
|
|
7069
|
-
setView("
|
|
7256
|
+
setView("rules");
|
|
7070
7257
|
}
|
|
7071
7258
|
return;
|
|
7072
7259
|
}
|
|
7073
|
-
|
|
7074
|
-
|
|
7075
|
-
|
|
7076
|
-
|
|
7077
|
-
|
|
7078
|
-
|
|
7079
|
-
|
|
7080
|
-
|
|
7081
|
-
|
|
7082
|
-
|
|
7083
|
-
|
|
7084
|
-
|
|
7085
|
-
|
|
7086
|
-
if (
|
|
7087
|
-
|
|
7088
|
-
|
|
7089
|
-
|
|
7090
|
-
|
|
7091
|
-
|
|
7092
|
-
}
|
|
7093
|
-
|
|
7094
|
-
|
|
7095
|
-
|
|
7096
|
-
|
|
7260
|
+
if (view === "rules") {
|
|
7261
|
+
if (key.leftArrow) return setView("list"), void setStatus(null);
|
|
7262
|
+
if (key.upArrow) setRi((n) => Math.max(0, n - 1));
|
|
7263
|
+
else if (key.downArrow) setRi((n) => Math.min(rules.length - 1, n + 1));
|
|
7264
|
+
else if (key.return || key.rightArrow) {
|
|
7265
|
+
if (rules[ri]) {
|
|
7266
|
+
setFi(0);
|
|
7267
|
+
setView("rule");
|
|
7268
|
+
}
|
|
7269
|
+
} else if (input === " ") {
|
|
7270
|
+
if (rules[ri]) mutate(rules.map((r, i) => i === ri ? { ...r, enabled: !r.enabled } : r));
|
|
7271
|
+
} else if (input === "e") {
|
|
7272
|
+
if (rules[ri]) mutate(rules.map((r, i) => i === ri ? { ...r, effect: r.effect === "ALLOW" ? "DENY" : "ALLOW" } : r));
|
|
7273
|
+
} else if (input === "d") {
|
|
7274
|
+
if (rules[ri]) {
|
|
7275
|
+
const next = rules.filter((_, i) => i !== ri);
|
|
7276
|
+
setRi((n) => Math.max(0, Math.min(n, next.length - 1)));
|
|
7277
|
+
mutate(next);
|
|
7278
|
+
}
|
|
7279
|
+
} else if (input === "m") {
|
|
7280
|
+
mutate(rules, mode === "denylist" ? "whitelist" : "denylist");
|
|
7281
|
+
} else if (input === "s") void save();
|
|
7282
|
+
else if (input === "x") discard();
|
|
7283
|
+
return;
|
|
7097
7284
|
}
|
|
7285
|
+
const rule2 = rules[ri];
|
|
7286
|
+
if (!rule2) return setView("rules");
|
|
7287
|
+
const field = FIELDS[fi];
|
|
7288
|
+
if (key.leftArrow) setView("rules");
|
|
7289
|
+
else if (key.upArrow) setFi((n) => Math.max(0, n - 1));
|
|
7290
|
+
else if (key.downArrow) setFi((n) => Math.min(FIELDS.length - 1, n + 1));
|
|
7291
|
+
else if (field.kind === "effect" && (input === " " || key.rightArrow)) {
|
|
7292
|
+
mutate(rules.map((r, i) => i === ri ? { ...r, effect: r.effect === "ALLOW" ? "DENY" : "ALLOW" } : r));
|
|
7293
|
+
} else if (field.kind === "enabled" && (input === " " || key.rightArrow)) {
|
|
7294
|
+
mutate(rules.map((r, i) => i === ri ? { ...r, enabled: !r.enabled } : r));
|
|
7295
|
+
} else if (field.kind === "priority" && (key.rightArrow || key.leftArrow)) {
|
|
7296
|
+
const d = key.rightArrow ? 1 : -1;
|
|
7297
|
+
mutate(rules.map((r, i) => i === ri ? { ...r, priority: Math.max(0, r.priority + d) } : r));
|
|
7298
|
+
} else if (field.kind === "text" && key.return) {
|
|
7299
|
+
setEditVal(field.get(rule2));
|
|
7300
|
+
setEditing(true);
|
|
7301
|
+
} else if (input === "s") void save();
|
|
7098
7302
|
},
|
|
7099
|
-
{ isActive: focused }
|
|
7303
|
+
{ isActive: focused && !editing }
|
|
7100
7304
|
);
|
|
7101
7305
|
if (view === "list") {
|
|
7102
|
-
return /* @__PURE__ */
|
|
7103
|
-
/* @__PURE__ */
|
|
7104
|
-
/* @__PURE__ */
|
|
7306
|
+
return /* @__PURE__ */ jsx3(DataView, { loading: list4.loading && !list4.data, error: list4.error, empty: !!list4.data && policies.length === 0, emptyText: "No policies.", children: /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
|
|
7307
|
+
/* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: focused ? "\u2191\u2193 select \xB7 enter open" : "press \u2192 to browse" }),
|
|
7308
|
+
/* @__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: [
|
|
7105
7309
|
(i === pi ? "\u25B8 " : " ") + truncate2(p.name, 26).padEnd(27),
|
|
7106
|
-
/* @__PURE__ */
|
|
7310
|
+
/* @__PURE__ */ jsxs3(Text3, { color: theme.dim, children: [
|
|
7107
7311
|
p.mode.padEnd(10),
|
|
7108
7312
|
" ",
|
|
7109
7313
|
p.rules.length,
|
|
@@ -7113,37 +7317,78 @@ function PoliciesPanel({ focused }) {
|
|
|
7113
7317
|
] }, p.id)) })
|
|
7114
7318
|
] }) });
|
|
7115
7319
|
}
|
|
7116
|
-
const
|
|
7117
|
-
|
|
7118
|
-
/* @__PURE__ */
|
|
7119
|
-
/* @__PURE__ */
|
|
7120
|
-
|
|
7121
|
-
|
|
7320
|
+
const dirtyTag = dirty ? /* @__PURE__ */ jsx3(Text3, { color: theme.warn, children: " \u25CF unsaved (s save \xB7 x discard)" }) : null;
|
|
7321
|
+
if (view === "rules") {
|
|
7322
|
+
return /* @__PURE__ */ jsx3(DataView, { loading: detail.loading && !detail.data, error: detail.error, children: /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
|
|
7323
|
+
/* @__PURE__ */ jsxs3(Box3, { children: [
|
|
7324
|
+
/* @__PURE__ */ jsx3(Text3, { bold: true, color: theme.accentBright, children: truncate2(selected?.name ?? "", 28) }),
|
|
7325
|
+
/* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: " mode: " }),
|
|
7326
|
+
/* @__PURE__ */ jsx3(Text3, { color: mode === "whitelist" ? theme.warn : void 0, children: mode }),
|
|
7327
|
+
dirtyTag
|
|
7328
|
+
] }),
|
|
7329
|
+
/* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: "\u2191\u2193 rule \xB7 enter edit \xB7 space on/off \xB7 e effect \xB7 d delete \xB7 m mode \xB7 s save \xB7 \u2190 back" }),
|
|
7330
|
+
/* @__PURE__ */ jsx3(Box3, { marginTop: 1, children: /* @__PURE__ */ jsx3(
|
|
7331
|
+
Table,
|
|
7332
|
+
{
|
|
7333
|
+
columns: [
|
|
7334
|
+
{ header: "", width: 2 },
|
|
7335
|
+
{ header: "ON", width: 2 },
|
|
7336
|
+
{ header: "EFFECT", width: 7 },
|
|
7337
|
+
{ header: "PRIO", width: 4 },
|
|
7338
|
+
{ header: "TOOL", width: 20 },
|
|
7339
|
+
{ header: "CONSTRAINTS", width: 22 }
|
|
7340
|
+
],
|
|
7341
|
+
rows: rules.map((r, i) => [
|
|
7342
|
+
{ value: i === ri ? "\u25B8" : "", color: theme.accentBright },
|
|
7343
|
+
{ value: r.enabled ? "\u25CF" : "\u25CB", color: r.enabled ? theme.ok : theme.dim },
|
|
7344
|
+
{ value: r.effect, color: r.effect === "ALLOW" ? theme.ok : theme.bad, dim: !r.enabled },
|
|
7345
|
+
{ value: String(r.priority), dim: true },
|
|
7346
|
+
{ value: truncate2(r.toolPattern, 20), color: theme.accent, dim: !r.enabled },
|
|
7347
|
+
{ value: ruleSummary(r) || truncate2(r.description || "\u2014", 22), dim: true }
|
|
7348
|
+
])
|
|
7349
|
+
}
|
|
7350
|
+
) }),
|
|
7351
|
+
rules.length === 0 ? /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: "(no rules)" }) : null,
|
|
7352
|
+
status ? /* @__PURE__ */ jsx3(Text3, { color: status.startsWith("\u2717") ? theme.bad : theme.ok, children: status }) : null
|
|
7353
|
+
] }) });
|
|
7354
|
+
}
|
|
7355
|
+
const rule = rules[ri];
|
|
7356
|
+
return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
|
|
7357
|
+
/* @__PURE__ */ jsxs3(Box3, { children: [
|
|
7358
|
+
/* @__PURE__ */ jsx3(Text3, { bold: true, color: theme.accentBright, children: "Rule " }),
|
|
7359
|
+
/* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: truncate2(rule?.id ?? "", 26) }),
|
|
7360
|
+
dirtyTag
|
|
7122
7361
|
] }),
|
|
7123
|
-
/* @__PURE__ */
|
|
7124
|
-
/* @__PURE__ */
|
|
7125
|
-
|
|
7126
|
-
|
|
7127
|
-
|
|
7128
|
-
|
|
7129
|
-
|
|
7130
|
-
|
|
7131
|
-
{
|
|
7132
|
-
|
|
7133
|
-
|
|
7134
|
-
|
|
7135
|
-
|
|
7136
|
-
|
|
7137
|
-
|
|
7138
|
-
|
|
7139
|
-
|
|
7140
|
-
|
|
7141
|
-
|
|
7142
|
-
|
|
7143
|
-
|
|
7144
|
-
|
|
7145
|
-
|
|
7362
|
+
/* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: "\u2191\u2193 field \xB7 enter edit text \xB7 space/\u2192 toggle \xB7 s save \xB7 \u2190 back" }),
|
|
7363
|
+
/* @__PURE__ */ jsx3(Box3, { marginTop: 1, flexDirection: "column", children: FIELDS.map((f, i) => {
|
|
7364
|
+
const val = rule ? f.get(rule) : "";
|
|
7365
|
+
const active2 = i === fi;
|
|
7366
|
+
return /* @__PURE__ */ jsxs3(Box3, { children: [
|
|
7367
|
+
/* @__PURE__ */ jsx3(Text3, { color: active2 ? theme.accentBright : void 0, children: (active2 ? "\u25B8 " : " ") + f.label.padEnd(13) }),
|
|
7368
|
+
active2 && editing && f.kind === "text" ? /* @__PURE__ */ jsx3(
|
|
7369
|
+
TextInput,
|
|
7370
|
+
{
|
|
7371
|
+
value: editVal,
|
|
7372
|
+
onChange: setEditVal,
|
|
7373
|
+
onSubmit: (v) => {
|
|
7374
|
+
if (rule && f.set) mutate(rules.map((r, idx) => idx === ri ? f.set(r, v) : r));
|
|
7375
|
+
setEditing(false);
|
|
7376
|
+
}
|
|
7377
|
+
}
|
|
7378
|
+
) : /* @__PURE__ */ jsx3(
|
|
7379
|
+
Text3,
|
|
7380
|
+
{
|
|
7381
|
+
color: f.kind === "effect" ? val === "ALLOW" ? theme.ok : theme.bad : void 0,
|
|
7382
|
+
dimColor: !val,
|
|
7383
|
+
children: val || "\u2014"
|
|
7384
|
+
}
|
|
7385
|
+
)
|
|
7386
|
+
] }, f.label);
|
|
7387
|
+
}) }),
|
|
7388
|
+
status ? /* @__PURE__ */ jsx3(Text3, { color: status.startsWith("\u2717") ? theme.bad : theme.ok, children: status }) : null
|
|
7389
|
+
] });
|
|
7146
7390
|
}
|
|
7391
|
+
var constr, setConstr, FIELDS;
|
|
7147
7392
|
var init_Policies = __esm({
|
|
7148
7393
|
"src/tui/panels/Policies.tsx"() {
|
|
7149
7394
|
"use strict";
|
|
@@ -7151,27 +7396,57 @@ var init_Policies = __esm({
|
|
|
7151
7396
|
init_components();
|
|
7152
7397
|
init_hooks();
|
|
7153
7398
|
init_theme();
|
|
7399
|
+
constr = (r, g, side) => (r[g]?.[side] ?? []).join(", ");
|
|
7400
|
+
setConstr = (r, g, side, val) => {
|
|
7401
|
+
const arr = val.split(",").map((s) => s.trim()).filter(Boolean);
|
|
7402
|
+
const prev = r[g] ?? {};
|
|
7403
|
+
return { ...r, [g]: { ...prev, [side]: arr } };
|
|
7404
|
+
};
|
|
7405
|
+
FIELDS = [
|
|
7406
|
+
{ label: "Effect", kind: "effect", get: (r) => r.effect },
|
|
7407
|
+
{ label: "Enabled", kind: "enabled", get: (r) => r.enabled ? "yes" : "no" },
|
|
7408
|
+
{ label: "Priority", kind: "priority", get: (r) => String(r.priority) },
|
|
7409
|
+
{ label: "Tool pattern", kind: "text", get: (r) => r.toolPattern, set: (r, v) => ({ ...r, toolPattern: v || "*" }) },
|
|
7410
|
+
{ label: "Description", kind: "text", get: (r) => r.description ?? "", set: (r, v) => ({ ...r, description: v }) },
|
|
7411
|
+
{ label: "Cmd allow", kind: "text", get: (r) => constr(r, "commandConstraints", "allowed"), set: (r, v) => setConstr(r, "commandConstraints", "allowed", v) },
|
|
7412
|
+
{ label: "Cmd deny", kind: "text", get: (r) => constr(r, "commandConstraints", "denied"), set: (r, v) => setConstr(r, "commandConstraints", "denied", v) },
|
|
7413
|
+
{ label: "Path allow", kind: "text", get: (r) => constr(r, "pathConstraints", "allowed"), set: (r, v) => setConstr(r, "pathConstraints", "allowed", v) },
|
|
7414
|
+
{ label: "Path deny", kind: "text", get: (r) => constr(r, "pathConstraints", "denied"), set: (r, v) => setConstr(r, "pathConstraints", "denied", v) },
|
|
7415
|
+
{ label: "File allow", kind: "text", get: (r) => constr(r, "filenameConstraints", "allowed"), set: (r, v) => setConstr(r, "filenameConstraints", "allowed", v) },
|
|
7416
|
+
{ label: "File deny", kind: "text", get: (r) => constr(r, "filenameConstraints", "denied"), set: (r, v) => setConstr(r, "filenameConstraints", "denied", v) },
|
|
7417
|
+
{ label: "URL allow", kind: "text", get: (r) => constr(r, "urlConstraints", "allowed"), set: (r, v) => setConstr(r, "urlConstraints", "allowed", v) },
|
|
7418
|
+
{ label: "URL deny", kind: "text", get: (r) => constr(r, "urlConstraints", "denied"), set: (r, v) => setConstr(r, "urlConstraints", "denied", v) }
|
|
7419
|
+
];
|
|
7154
7420
|
}
|
|
7155
7421
|
});
|
|
7156
7422
|
|
|
7157
7423
|
// src/tui/panels/RateLimit.tsx
|
|
7158
|
-
import { Box as
|
|
7159
|
-
import { useEffect as
|
|
7160
|
-
import { jsx as
|
|
7424
|
+
import { Box as Box4, Text as Text4, useInput as useInput2 } from "ink";
|
|
7425
|
+
import { useEffect as useEffect4, useState as useState4 } from "react";
|
|
7426
|
+
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
7427
|
+
function ColoredSpark({ values, limit, width: width2 = 60 }) {
|
|
7428
|
+
const v = values.length > width2 ? values.slice(values.length - width2) : values;
|
|
7429
|
+
const max = Math.max(1, ...v);
|
|
7430
|
+
return /* @__PURE__ */ jsx4(Text4, { children: v.map((n, i) => {
|
|
7431
|
+
const ch = BLOCKS2[Math.min(BLOCKS2.length - 1, Math.round(n / max * (BLOCKS2.length - 1)))];
|
|
7432
|
+
const over = limit > 0 && n > limit;
|
|
7433
|
+
return /* @__PURE__ */ jsx4(Text4, { color: over ? theme.bad : theme.accent, children: ch }, i);
|
|
7434
|
+
}) });
|
|
7435
|
+
}
|
|
7161
7436
|
function RateLimitPanel({ focused }) {
|
|
7162
7437
|
const layersQ = useLoader(() => api.settings.getSecurityLayers());
|
|
7163
7438
|
const historyQ = useLoader(() => api.settings.getRateLimitHistory());
|
|
7164
7439
|
const insightsQ = useLoader(() => api.stats.securityInsights(7));
|
|
7165
|
-
const [draft, setDraft] =
|
|
7166
|
-
const [dirty, setDirty] =
|
|
7167
|
-
const [fi, setFi] =
|
|
7168
|
-
const [status, setStatus] =
|
|
7169
|
-
|
|
7440
|
+
const [draft, setDraft] = useState4(null);
|
|
7441
|
+
const [dirty, setDirty] = useState4(false);
|
|
7442
|
+
const [fi, setFi] = useState4(0);
|
|
7443
|
+
const [status, setStatus] = useState4(null);
|
|
7444
|
+
useEffect4(() => {
|
|
7170
7445
|
if (layersQ.data && !draft) setDraft({ ...layersQ.data.layers.rateLimit });
|
|
7171
7446
|
}, [layersQ.data, draft]);
|
|
7172
7447
|
const adjust = (dir, step) => {
|
|
7173
7448
|
if (!draft) return;
|
|
7174
|
-
const field =
|
|
7449
|
+
const field = FIELDS2[fi];
|
|
7175
7450
|
if (field === "mode") {
|
|
7176
7451
|
const idx = (MODES.indexOf(draft.mode) + dir + MODES.length) % MODES.length;
|
|
7177
7452
|
setDraft({ ...draft, mode: MODES[idx] });
|
|
@@ -7185,8 +7460,7 @@ function RateLimitPanel({ focused }) {
|
|
|
7185
7460
|
if (!draft || !layersQ.data) return;
|
|
7186
7461
|
setStatus("Saving\u2026");
|
|
7187
7462
|
try {
|
|
7188
|
-
const
|
|
7189
|
-
const res = await api.settings.setSecurityLayers(next);
|
|
7463
|
+
const res = await api.settings.setSecurityLayers({ ...layersQ.data.layers, rateLimit: draft });
|
|
7190
7464
|
setDraft({ ...res.layers.rateLimit });
|
|
7191
7465
|
setDirty(false);
|
|
7192
7466
|
setStatus("\u2713 Saved");
|
|
@@ -7198,8 +7472,8 @@ function RateLimitPanel({ focused }) {
|
|
|
7198
7472
|
useInput2(
|
|
7199
7473
|
(input, key) => {
|
|
7200
7474
|
const step = key.shift ? 10 : 1;
|
|
7201
|
-
if (key.upArrow) setFi((n) => (n - 1 +
|
|
7202
|
-
else if (key.downArrow) setFi((n) => (n + 1) %
|
|
7475
|
+
if (key.upArrow) setFi((n) => (n - 1 + FIELDS2.length) % FIELDS2.length);
|
|
7476
|
+
else if (key.downArrow) setFi((n) => (n + 1) % FIELDS2.length);
|
|
7203
7477
|
else if (key.leftArrow) adjust(-1, step);
|
|
7204
7478
|
else if (key.rightArrow) adjust(1, step);
|
|
7205
7479
|
else if (input === "s") void save();
|
|
@@ -7207,77 +7481,72 @@ function RateLimitPanel({ focused }) {
|
|
|
7207
7481
|
{ isActive: focused }
|
|
7208
7482
|
);
|
|
7209
7483
|
const history = (historyQ.data?.history ?? []).slice().sort((a, b) => b.ts - a.ts);
|
|
7210
|
-
const
|
|
7211
|
-
|
|
7212
|
-
|
|
7213
|
-
|
|
7214
|
-
|
|
7215
|
-
|
|
7216
|
-
|
|
7217
|
-
/* @__PURE__ */
|
|
7484
|
+
const activity = insightsQ.data?.["activity"] ?? {};
|
|
7485
|
+
const minuteSeries = (activity.minute ?? []).map((b) => b.count);
|
|
7486
|
+
const hourSeries = (activity.hour ?? []).map((b) => b.count);
|
|
7487
|
+
const bursts = minuteSeries.filter((c2) => draft && draft.perMinute > 0 && c2 > draft.perMinute).length;
|
|
7488
|
+
return /* @__PURE__ */ jsx4(DataView, { loading: layersQ.loading && !draft, error: layersQ.error, children: draft ? /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
|
|
7489
|
+
/* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: focused ? "\u2191\u2193 field \xB7 \u2190\u2192 \xB11 \xB7 shift+\u2190\u2192 \xB110 \xB7 s save" : "press \u2192 to edit" }),
|
|
7490
|
+
/* @__PURE__ */ jsxs4(Box4, { marginTop: 1, flexDirection: "column", children: [
|
|
7491
|
+
/* @__PURE__ */ jsx4(FieldRow, { label: "Mode", active: focused && fi === 0, children: /* @__PURE__ */ jsx4(Text4, { color: modeColor(draft.mode), children: draft.mode }) }),
|
|
7492
|
+
/* @__PURE__ */ jsx4(FieldRow, { label: "Per minute", active: focused && fi === 1, children: /* @__PURE__ */ jsx4(Text4, { bold: true, children: draft.perMinute }) }),
|
|
7493
|
+
/* @__PURE__ */ jsx4(FieldRow, { label: "Per hour", active: focused && fi === 2, children: /* @__PURE__ */ jsx4(Text4, { bold: true, children: draft.perHour === 0 ? "off" : draft.perHour }) }),
|
|
7494
|
+
/* @__PURE__ */ jsx4(FieldRow, { label: "Per day", active: focused && fi === 3, children: /* @__PURE__ */ jsx4(Text4, { bold: true, children: draft.perDay === 0 ? "off" : draft.perDay }) })
|
|
7218
7495
|
] }),
|
|
7219
|
-
dirty || status ? /* @__PURE__ */
|
|
7220
|
-
dirty ? /* @__PURE__ */
|
|
7221
|
-
status ? /* @__PURE__ */
|
|
7496
|
+
dirty || status ? /* @__PURE__ */ jsxs4(Box4, { marginTop: 1, children: [
|
|
7497
|
+
dirty ? /* @__PURE__ */ jsx4(Text4, { color: theme.warn, children: "\u25CF unsaved \u2014 press s to apply " }) : null,
|
|
7498
|
+
status ? /* @__PURE__ */ jsx4(Text4, { color: status.startsWith("\u2717") ? theme.bad : theme.ok, children: status }) : null
|
|
7222
7499
|
] }) : null,
|
|
7223
|
-
/* @__PURE__ */
|
|
7224
|
-
/* @__PURE__ */
|
|
7500
|
+
/* @__PURE__ */ jsxs4(Box4, { marginTop: 1, flexDirection: "column", children: [
|
|
7501
|
+
/* @__PURE__ */ jsxs4(Box4, { children: [
|
|
7502
|
+
/* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: "Activity " }),
|
|
7503
|
+
bursts > 0 ? /* @__PURE__ */ jsxs4(Text4, { color: theme.bad, children: [
|
|
7504
|
+
bursts,
|
|
7505
|
+
" burst min"
|
|
7506
|
+
] }) : /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: "no bursts" }),
|
|
7507
|
+
insightsQ.loading && !insightsQ.data ? /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: " \u2026" }) : null
|
|
7508
|
+
] }),
|
|
7509
|
+
/* @__PURE__ */ jsxs4(Box4, { children: [
|
|
7510
|
+
/* @__PURE__ */ jsx4(Text4, { children: "/min " }),
|
|
7511
|
+
minuteSeries.length ? /* @__PURE__ */ jsx4(ColoredSpark, { values: minuteSeries, limit: draft.perMinute }) : /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: "no data" })
|
|
7512
|
+
] }),
|
|
7513
|
+
/* @__PURE__ */ jsxs4(Box4, { children: [
|
|
7514
|
+
/* @__PURE__ */ jsx4(Text4, { children: "/hour " }),
|
|
7515
|
+
hourSeries.length ? /* @__PURE__ */ jsx4(ColoredSpark, { values: hourSeries, limit: draft.perHour, width: 24 }) : /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: "no data" })
|
|
7516
|
+
] })
|
|
7517
|
+
] }),
|
|
7518
|
+
/* @__PURE__ */ jsxs4(Box4, { marginTop: 1, flexDirection: "column", children: [
|
|
7519
|
+
/* @__PURE__ */ jsxs4(Text4, { color: theme.dim, children: [
|
|
7225
7520
|
"Change history ",
|
|
7226
7521
|
history.length ? `(${history.length})` : ""
|
|
7227
7522
|
] }),
|
|
7228
|
-
history.length ? /* @__PURE__ */
|
|
7523
|
+
history.length ? /* @__PURE__ */ jsx4(
|
|
7229
7524
|
Table,
|
|
7230
7525
|
{
|
|
7231
7526
|
columns: [
|
|
7232
|
-
{ header: "WHEN", width:
|
|
7527
|
+
{ header: "WHEN", width: 10 },
|
|
7233
7528
|
{ header: "/MIN", width: 6 },
|
|
7234
7529
|
{ header: "/HOUR", width: 7 },
|
|
7235
7530
|
{ header: "/DAY", width: 6 }
|
|
7236
7531
|
],
|
|
7237
|
-
rows: history.slice(0,
|
|
7532
|
+
rows: history.slice(0, 4).map((h, i) => [
|
|
7238
7533
|
{ value: ago(h.ts) + " ago", dim: true, bold: i === 0 },
|
|
7239
7534
|
{ value: String(h.minute), bold: i === 0 },
|
|
7240
7535
|
{ value: h.hour === 0 ? "off" : String(h.hour), dim: h.hour === 0 },
|
|
7241
7536
|
{ value: h.day === 0 ? "off" : String(h.day), dim: h.day === 0 }
|
|
7242
7537
|
])
|
|
7243
7538
|
}
|
|
7244
|
-
) : /* @__PURE__ */
|
|
7245
|
-
] }),
|
|
7246
|
-
/* @__PURE__ */ jsxs3(Box3, { marginTop: 1, flexDirection: "column", children: [
|
|
7247
|
-
/* @__PURE__ */ jsxs3(Text3, { color: theme.dim, children: [
|
|
7248
|
-
"Recent bursts ",
|
|
7249
|
-
insightsQ.loading && !insightsQ.data ? "\u2026" : `(${bursts.length})`
|
|
7250
|
-
] }),
|
|
7251
|
-
bursts.length ? /* @__PURE__ */ jsx3(
|
|
7252
|
-
Table,
|
|
7253
|
-
{
|
|
7254
|
-
columns: [
|
|
7255
|
-
{ header: "RESULT", width: 9 },
|
|
7256
|
-
{ header: "AGENT", width: 16 },
|
|
7257
|
-
{ header: "CALLS/LIMIT", width: 12 },
|
|
7258
|
-
{ header: "WHEN", width: 8 }
|
|
7259
|
-
],
|
|
7260
|
-
rows: bursts.map((b) => [
|
|
7261
|
-
{ value: b.blocked ? "blocked" : "BYPASSED", color: b.blocked ? theme.accent : theme.bad, bold: !b.blocked },
|
|
7262
|
-
{ value: (b.agent || "\u2014").slice(0, 16), dim: true },
|
|
7263
|
-
{ value: `${b.count}/${b.limit}` },
|
|
7264
|
-
{ value: ago(b.minute) + " ago", dim: true }
|
|
7265
|
-
])
|
|
7266
|
-
}
|
|
7267
|
-
) : /* @__PURE__ */ jsxs3(Text3, { color: theme.dim, children: [
|
|
7268
|
-
" ",
|
|
7269
|
-
insightsQ.error ? insightsQ.error : "none in 7d"
|
|
7270
|
-
] })
|
|
7539
|
+
) : /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: " no changes" })
|
|
7271
7540
|
] })
|
|
7272
7541
|
] }) : null });
|
|
7273
7542
|
}
|
|
7274
7543
|
function FieldRow({ label, active: active2, children }) {
|
|
7275
|
-
return /* @__PURE__ */
|
|
7276
|
-
/* @__PURE__ */
|
|
7544
|
+
return /* @__PURE__ */ jsxs4(Box4, { children: [
|
|
7545
|
+
/* @__PURE__ */ jsx4(Text4, { color: active2 ? theme.accentBright : void 0, children: (active2 ? "\u25B8 " : " ") + label.padEnd(12) }),
|
|
7277
7546
|
children
|
|
7278
7547
|
] });
|
|
7279
7548
|
}
|
|
7280
|
-
var MODES,
|
|
7549
|
+
var MODES, FIELDS2, BLOCKS2;
|
|
7281
7550
|
var init_RateLimit = __esm({
|
|
7282
7551
|
"src/tui/panels/RateLimit.tsx"() {
|
|
7283
7552
|
"use strict";
|
|
@@ -7286,81 +7555,138 @@ var init_RateLimit = __esm({
|
|
|
7286
7555
|
init_hooks();
|
|
7287
7556
|
init_theme();
|
|
7288
7557
|
MODES = ["off", "detect", "block"];
|
|
7289
|
-
|
|
7558
|
+
FIELDS2 = ["mode", "perMinute", "perHour", "perDay"];
|
|
7559
|
+
BLOCKS2 = ["\u2581", "\u2582", "\u2583", "\u2584", "\u2585", "\u2586", "\u2587", "\u2588"];
|
|
7290
7560
|
}
|
|
7291
7561
|
});
|
|
7292
7562
|
|
|
7293
7563
|
// src/tui/panels/Dlp.tsx
|
|
7294
|
-
import { Box as
|
|
7295
|
-
import
|
|
7296
|
-
import {
|
|
7564
|
+
import { Box as Box5, Text as Text5, useInput as useInput3 } from "ink";
|
|
7565
|
+
import TextInput2 from "ink-text-input";
|
|
7566
|
+
import { useEffect as useEffect5, useState as useState5 } from "react";
|
|
7567
|
+
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
7297
7568
|
function DlpPanel({ focused }) {
|
|
7298
7569
|
const q = useLoader(() => api.settings.getSecurityLayers());
|
|
7299
|
-
const [dlp, setDlp] =
|
|
7300
|
-
const [available, setAvailable] =
|
|
7301
|
-
const [sel, setSel] =
|
|
7302
|
-
const [
|
|
7303
|
-
|
|
7570
|
+
const [dlp, setDlp] = useState5(null);
|
|
7571
|
+
const [available, setAvailable] = useState5([]);
|
|
7572
|
+
const [sel, setSel] = useState5(0);
|
|
7573
|
+
const [dirty, setDirty] = useState5(false);
|
|
7574
|
+
const [status, setStatus] = useState5(null);
|
|
7575
|
+
const [adding, setAdding] = useState5(null);
|
|
7576
|
+
const [newName, setNewName] = useState5("");
|
|
7577
|
+
const [newRe, setNewRe] = useState5("");
|
|
7578
|
+
useEffect5(() => {
|
|
7304
7579
|
if (q.data && !dlp) {
|
|
7305
|
-
setDlp({ ...q.data.layers.dlp, patterns: [...q.data.layers.dlp.patterns] });
|
|
7580
|
+
setDlp({ ...q.data.layers.dlp, patterns: [...q.data.layers.dlp.patterns], custom: [...q.data.layers.dlp.custom] });
|
|
7306
7581
|
setAvailable(q.data.availablePatterns);
|
|
7307
7582
|
}
|
|
7308
7583
|
}, [q.data, dlp]);
|
|
7309
|
-
const
|
|
7310
|
-
|
|
7311
|
-
|
|
7584
|
+
const mutate = (next) => {
|
|
7585
|
+
setDlp(next);
|
|
7586
|
+
setDirty(true);
|
|
7587
|
+
setStatus(null);
|
|
7588
|
+
};
|
|
7589
|
+
const save = async () => {
|
|
7590
|
+
if (!dlp || !q.data) return;
|
|
7312
7591
|
setStatus("Saving\u2026");
|
|
7313
7592
|
try {
|
|
7314
|
-
const res = await api.settings.setSecurityLayers({ ...q.data.layers, dlp
|
|
7315
|
-
setDlp({ ...res.layers.dlp, patterns: [...res.layers.dlp.patterns] });
|
|
7593
|
+
const res = await api.settings.setSecurityLayers({ ...q.data.layers, dlp });
|
|
7594
|
+
setDlp({ ...res.layers.dlp, patterns: [...res.layers.dlp.patterns], custom: [...res.layers.dlp.custom] });
|
|
7595
|
+
setDirty(false);
|
|
7316
7596
|
setStatus("\u2713 Saved");
|
|
7317
7597
|
} catch (e) {
|
|
7318
7598
|
setStatus("\u2717 " + (e instanceof Error ? e.message : String(e)));
|
|
7319
7599
|
}
|
|
7320
7600
|
};
|
|
7601
|
+
const discard = () => {
|
|
7602
|
+
if (q.data) setDlp({ ...q.data.layers.dlp, patterns: [...q.data.layers.dlp.patterns], custom: [...q.data.layers.dlp.custom] });
|
|
7603
|
+
setDirty(false);
|
|
7604
|
+
setStatus("discarded");
|
|
7605
|
+
};
|
|
7606
|
+
const customCount = dlp?.custom.length ?? 0;
|
|
7607
|
+
const total = available.length + customCount;
|
|
7321
7608
|
useInput3(
|
|
7322
7609
|
(input, key) => {
|
|
7323
7610
|
if (!dlp) return;
|
|
7324
7611
|
if (key.upArrow) setSel((n) => Math.max(0, n - 1));
|
|
7325
|
-
else if (key.downArrow) setSel((n) => Math.min(
|
|
7326
|
-
else if (input === "
|
|
7612
|
+
else if (key.downArrow) setSel((n) => Math.min(total - 1, n + 1));
|
|
7613
|
+
else if (input === "m") {
|
|
7614
|
+
const idx = (MODES2.indexOf(dlp.mode) + 1) % MODES2.length;
|
|
7615
|
+
mutate({ ...dlp, mode: MODES2[idx] });
|
|
7616
|
+
} else if (input === "a") {
|
|
7617
|
+
setNewName("");
|
|
7618
|
+
setNewRe("");
|
|
7619
|
+
setAdding("name");
|
|
7620
|
+
} else if (input === " " && sel < available.length) {
|
|
7327
7621
|
const p = available[sel];
|
|
7328
|
-
if (!p) return;
|
|
7329
7622
|
const set = new Set(dlp.patterns);
|
|
7330
7623
|
if (set.has(p)) set.delete(p);
|
|
7331
7624
|
else set.add(p);
|
|
7332
|
-
|
|
7333
|
-
} else if (input === "
|
|
7334
|
-
const
|
|
7335
|
-
|
|
7336
|
-
|
|
7625
|
+
mutate({ ...dlp, patterns: [...set] });
|
|
7626
|
+
} else if (input === "d" && sel >= available.length) {
|
|
7627
|
+
const ci = sel - available.length;
|
|
7628
|
+
mutate({ ...dlp, custom: dlp.custom.filter((_, i) => i !== ci) });
|
|
7629
|
+
setSel((n) => Math.max(0, n - 1));
|
|
7630
|
+
} else if (input === "s") void save();
|
|
7631
|
+
else if (input === "x") discard();
|
|
7337
7632
|
},
|
|
7338
|
-
{ isActive: focused }
|
|
7633
|
+
{ isActive: focused && !adding }
|
|
7339
7634
|
);
|
|
7340
7635
|
const enabled = new Set(dlp?.patterns ?? []);
|
|
7341
|
-
return /* @__PURE__ */
|
|
7342
|
-
/* @__PURE__ */
|
|
7343
|
-
/* @__PURE__ */
|
|
7344
|
-
/* @__PURE__ */
|
|
7345
|
-
/* @__PURE__ */
|
|
7636
|
+
return /* @__PURE__ */ jsx5(DataView, { loading: q.loading && !dlp, error: q.error, children: dlp ? /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
|
|
7637
|
+
/* @__PURE__ */ jsxs5(Box5, { children: [
|
|
7638
|
+
/* @__PURE__ */ jsx5(Text5, { children: "mode: " }),
|
|
7639
|
+
/* @__PURE__ */ jsx5(Text5, { color: modeColor(dlp.mode), children: dlp.mode }),
|
|
7640
|
+
dirty ? /* @__PURE__ */ jsx5(Text5, { color: theme.warn, children: " \u25CF unsaved (s save \xB7 x discard)" }) : null
|
|
7346
7641
|
] }),
|
|
7347
|
-
/* @__PURE__ */
|
|
7642
|
+
/* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: focused ? "\u2191\u2193 move \xB7 space toggle \xB7 m mode \xB7 a add \xB7 d remove \xB7 s save" : "press \u2192 to edit" }),
|
|
7643
|
+
/* @__PURE__ */ jsx5(Box5, { marginTop: 1, flexDirection: "column", children: available.map((p, i) => {
|
|
7348
7644
|
const on = enabled.has(p);
|
|
7349
|
-
|
|
7350
|
-
|
|
7351
|
-
|
|
7645
|
+
const active2 = focused && sel === i;
|
|
7646
|
+
return /* @__PURE__ */ jsxs5(Text5, { color: active2 ? theme.accentBright : void 0, bold: active2, children: [
|
|
7647
|
+
(active2 ? "\u25B8 " : " ") + (on ? "\u25CF " : "\u25CB "),
|
|
7648
|
+
/* @__PURE__ */ jsx5(Text5, { color: on ? theme.ok : theme.dim, children: p })
|
|
7352
7649
|
] }, p);
|
|
7353
7650
|
}) }),
|
|
7354
|
-
|
|
7355
|
-
/* @__PURE__ */
|
|
7356
|
-
dlp.custom.
|
|
7357
|
-
|
|
7358
|
-
|
|
7359
|
-
|
|
7360
|
-
|
|
7361
|
-
|
|
7651
|
+
/* @__PURE__ */ jsxs5(Box5, { marginTop: 1, flexDirection: "column", children: [
|
|
7652
|
+
/* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: "Custom patterns" }),
|
|
7653
|
+
dlp.custom.length === 0 ? /* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: " (none \u2014 press a to add)" }) : null,
|
|
7654
|
+
dlp.custom.map((c2, i) => {
|
|
7655
|
+
const active2 = focused && sel === available.length + i;
|
|
7656
|
+
return /* @__PURE__ */ jsxs5(Text5, { color: active2 ? theme.accentBright : void 0, bold: active2, children: [
|
|
7657
|
+
active2 ? "\u25B8 " : " ",
|
|
7658
|
+
/* @__PURE__ */ jsx5(Text5, { color: theme.accent, children: c2.name }),
|
|
7659
|
+
" ",
|
|
7660
|
+
/* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: c2.re })
|
|
7661
|
+
] }, c2.name + i);
|
|
7662
|
+
})
|
|
7663
|
+
] }),
|
|
7664
|
+
adding ? /* @__PURE__ */ jsxs5(Box5, { marginTop: 1, children: [
|
|
7665
|
+
/* @__PURE__ */ jsx5(Text5, { color: theme.warn, children: adding === "name" ? "New pattern name: " : `Regex for "${newName}": ` }),
|
|
7666
|
+
adding === "name" ? /* @__PURE__ */ jsx5(
|
|
7667
|
+
TextInput2,
|
|
7668
|
+
{
|
|
7669
|
+
value: newName,
|
|
7670
|
+
onChange: setNewName,
|
|
7671
|
+
onSubmit: (v) => {
|
|
7672
|
+
if (!v.trim()) return setAdding(null);
|
|
7673
|
+
setNewName(v.trim());
|
|
7674
|
+
setAdding("re");
|
|
7675
|
+
}
|
|
7676
|
+
}
|
|
7677
|
+
) : /* @__PURE__ */ jsx5(
|
|
7678
|
+
TextInput2,
|
|
7679
|
+
{
|
|
7680
|
+
value: newRe,
|
|
7681
|
+
onChange: setNewRe,
|
|
7682
|
+
onSubmit: (v) => {
|
|
7683
|
+
if (v.trim() && dlp) mutate({ ...dlp, custom: [...dlp.custom, { name: newName, re: v.trim() }] });
|
|
7684
|
+
setAdding(null);
|
|
7685
|
+
}
|
|
7686
|
+
}
|
|
7687
|
+
)
|
|
7362
7688
|
] }) : null,
|
|
7363
|
-
status ? /* @__PURE__ */
|
|
7689
|
+
status ? /* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text5, { color: status.startsWith("\u2717") ? theme.bad : theme.ok, children: status }) }) : null
|
|
7364
7690
|
] }) : null });
|
|
7365
7691
|
}
|
|
7366
7692
|
var MODES2;
|
|
@@ -7376,8 +7702,8 @@ var init_Dlp = __esm({
|
|
|
7376
7702
|
});
|
|
7377
7703
|
|
|
7378
7704
|
// src/tui/panels/Stats.tsx
|
|
7379
|
-
import { Box as
|
|
7380
|
-
import { jsx as
|
|
7705
|
+
import { Box as Box6, Text as Text6 } from "ink";
|
|
7706
|
+
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
7381
7707
|
function StatsPanel({ active: active2 }) {
|
|
7382
7708
|
const stats = useLoader(() => api.stats.get());
|
|
7383
7709
|
const ts = useLoader(() => api.stats.timeseries({ period: "24h" }));
|
|
@@ -7387,50 +7713,50 @@ function StatsPanel({ active: active2 }) {
|
|
|
7387
7713
|
}, 5e3, active2);
|
|
7388
7714
|
const s = stats.data;
|
|
7389
7715
|
const points = ts.data?.timeseries ?? [];
|
|
7390
|
-
return /* @__PURE__ */
|
|
7391
|
-
/* @__PURE__ */
|
|
7392
|
-
/* @__PURE__ */
|
|
7716
|
+
return /* @__PURE__ */ jsx6(DataView, { loading: stats.loading && !s, error: stats.error, children: s ? /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
|
|
7717
|
+
/* @__PURE__ */ jsxs6(Box6, { children: [
|
|
7718
|
+
/* @__PURE__ */ jsxs6(Text6, { bold: true, children: [
|
|
7393
7719
|
s.total_calls,
|
|
7394
7720
|
" "
|
|
7395
7721
|
] }),
|
|
7396
|
-
/* @__PURE__ */
|
|
7397
|
-
/* @__PURE__ */
|
|
7722
|
+
/* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: "calls " }),
|
|
7723
|
+
/* @__PURE__ */ jsxs6(Text6, { color: theme.ok, children: [
|
|
7398
7724
|
s.allowed,
|
|
7399
7725
|
" allowed"
|
|
7400
7726
|
] }),
|
|
7401
|
-
/* @__PURE__ */
|
|
7402
|
-
/* @__PURE__ */
|
|
7727
|
+
/* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " " }),
|
|
7728
|
+
/* @__PURE__ */ jsxs6(Text6, { color: theme.bad, children: [
|
|
7403
7729
|
s.denied,
|
|
7404
7730
|
" denied"
|
|
7405
7731
|
] })
|
|
7406
7732
|
] }),
|
|
7407
|
-
/* @__PURE__ */
|
|
7733
|
+
/* @__PURE__ */ jsxs6(Text6, { color: theme.dim, children: [
|
|
7408
7734
|
s.active_policies,
|
|
7409
7735
|
" policies \xB7 ",
|
|
7410
7736
|
s.registered_tools,
|
|
7411
7737
|
" tools"
|
|
7412
7738
|
] }),
|
|
7413
|
-
/* @__PURE__ */
|
|
7414
|
-
/* @__PURE__ */
|
|
7739
|
+
/* @__PURE__ */ jsxs6(Box6, { marginTop: 1, flexDirection: "column", children: [
|
|
7740
|
+
/* @__PURE__ */ jsxs6(Text6, { color: theme.dim, children: [
|
|
7415
7741
|
"Last 24h ",
|
|
7416
7742
|
ts.data ? `(per ${ts.data.granularity})` : ""
|
|
7417
7743
|
] }),
|
|
7418
|
-
/* @__PURE__ */
|
|
7419
|
-
/* @__PURE__ */
|
|
7420
|
-
/* @__PURE__ */
|
|
7744
|
+
/* @__PURE__ */ jsxs6(Box6, { children: [
|
|
7745
|
+
/* @__PURE__ */ jsx6(Text6, { children: "total " }),
|
|
7746
|
+
/* @__PURE__ */ jsx6(Text6, { color: theme.accent, children: sparkline(points.map((p) => p.total), 64) })
|
|
7421
7747
|
] }),
|
|
7422
|
-
/* @__PURE__ */
|
|
7423
|
-
/* @__PURE__ */
|
|
7424
|
-
/* @__PURE__ */
|
|
7748
|
+
/* @__PURE__ */ jsxs6(Box6, { children: [
|
|
7749
|
+
/* @__PURE__ */ jsx6(Text6, { children: "allowed " }),
|
|
7750
|
+
/* @__PURE__ */ jsx6(Text6, { color: theme.ok, children: sparkline(points.map((p) => p.allowed), 64) })
|
|
7425
7751
|
] }),
|
|
7426
|
-
/* @__PURE__ */
|
|
7427
|
-
/* @__PURE__ */
|
|
7428
|
-
/* @__PURE__ */
|
|
7752
|
+
/* @__PURE__ */ jsxs6(Box6, { children: [
|
|
7753
|
+
/* @__PURE__ */ jsx6(Text6, { children: "denied " }),
|
|
7754
|
+
/* @__PURE__ */ jsx6(Text6, { color: theme.bad, children: sparkline(points.map((p) => p.denied), 64) })
|
|
7429
7755
|
] })
|
|
7430
7756
|
] }),
|
|
7431
|
-
/* @__PURE__ */
|
|
7432
|
-
/* @__PURE__ */
|
|
7433
|
-
/* @__PURE__ */
|
|
7757
|
+
/* @__PURE__ */ jsxs6(Box6, { marginTop: 1, flexDirection: "column", children: [
|
|
7758
|
+
/* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: "Recent" }),
|
|
7759
|
+
/* @__PURE__ */ jsx6(
|
|
7434
7760
|
Table,
|
|
7435
7761
|
{
|
|
7436
7762
|
columns: [
|
|
@@ -7461,52 +7787,172 @@ var init_Stats = __esm({
|
|
|
7461
7787
|
});
|
|
7462
7788
|
|
|
7463
7789
|
// src/tui/panels/Audit.tsx
|
|
7464
|
-
import { Box as
|
|
7465
|
-
import
|
|
7466
|
-
import {
|
|
7790
|
+
import { Box as Box7, Text as Text7, useInput as useInput4 } from "ink";
|
|
7791
|
+
import TextInput3 from "ink-text-input";
|
|
7792
|
+
import { useState as useState6 } from "react";
|
|
7793
|
+
import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
7467
7794
|
function AuditPanel({ active: active2, focused }) {
|
|
7468
|
-
const [
|
|
7469
|
-
const
|
|
7470
|
-
const
|
|
7471
|
-
|
|
7795
|
+
const [di, setDi] = useState6(0);
|
|
7796
|
+
const [gi, setGi] = useState6(0);
|
|
7797
|
+
const [tool, setTool] = useState6("");
|
|
7798
|
+
const [agent, setAgent] = useState6("");
|
|
7799
|
+
const [search, setSearch] = useState6("");
|
|
7800
|
+
const [sel, setSel] = useState6(0);
|
|
7801
|
+
const [view, setView] = useState6("list");
|
|
7802
|
+
const [editing, setEditing] = useState6(null);
|
|
7803
|
+
const query = {
|
|
7804
|
+
filter: DECISIONS[di],
|
|
7805
|
+
signal: SIGNALS[gi],
|
|
7806
|
+
tool: tool || void 0,
|
|
7807
|
+
agent_name: agent || void 0,
|
|
7808
|
+
search: search || void 0,
|
|
7809
|
+
limit: 100
|
|
7810
|
+
};
|
|
7811
|
+
const auditQ = useLoader(() => api.audit.list(query), [di, gi, tool, agent, search]);
|
|
7812
|
+
usePoll(auditQ.reload, 6e3, active2 && view === "list" && !editing);
|
|
7813
|
+
const entries = auditQ.data?.entries ?? [];
|
|
7814
|
+
const current = entries[Math.min(sel, Math.max(0, entries.length - 1))];
|
|
7815
|
+
const sessionQ = useLoader(
|
|
7816
|
+
() => view === "detail" && current?.session_id ? api.audit.list({ session_id: current.session_id, limit: 40 }) : Promise.resolve(null),
|
|
7817
|
+
[view, current?.session_id]
|
|
7818
|
+
);
|
|
7472
7819
|
useInput4(
|
|
7473
|
-
(input) => {
|
|
7474
|
-
if (
|
|
7820
|
+
(input, key) => {
|
|
7821
|
+
if (view === "detail") {
|
|
7822
|
+
if (key.leftArrow || key.escape) setView("list");
|
|
7823
|
+
return;
|
|
7824
|
+
}
|
|
7825
|
+
if (key.upArrow) setSel((n) => Math.max(0, n - 1));
|
|
7826
|
+
else if (key.downArrow) setSel((n) => Math.min(entries.length - 1, n + 1));
|
|
7827
|
+
else if (key.return || key.rightArrow) {
|
|
7828
|
+
if (current) setView("detail");
|
|
7829
|
+
} else if (input === "f") setDi((n) => (n + 1) % DECISIONS.length);
|
|
7830
|
+
else if (input === "g") setGi((n) => (n + 1) % SIGNALS.length);
|
|
7831
|
+
else if (input === "t") setEditing("tool");
|
|
7832
|
+
else if (input === "n") setEditing("agent");
|
|
7833
|
+
else if (input === "/") setEditing("search");
|
|
7834
|
+
else if (input === "c") {
|
|
7835
|
+
setDi(0);
|
|
7836
|
+
setGi(0);
|
|
7837
|
+
setTool("");
|
|
7838
|
+
setAgent("");
|
|
7839
|
+
setSearch("");
|
|
7840
|
+
}
|
|
7475
7841
|
},
|
|
7476
|
-
{ isActive: focused }
|
|
7842
|
+
{ isActive: focused && !editing }
|
|
7477
7843
|
);
|
|
7478
|
-
|
|
7479
|
-
|
|
7480
|
-
/* @__PURE__ */
|
|
7481
|
-
|
|
7482
|
-
|
|
7483
|
-
|
|
7484
|
-
|
|
7844
|
+
if (view === "detail" && current) {
|
|
7845
|
+
const sessionCalls = sessionQ.data?.entries ?? [];
|
|
7846
|
+
return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
|
|
7847
|
+
/* @__PURE__ */ jsxs7(Box7, { children: [
|
|
7848
|
+
/* @__PURE__ */ jsx7(Text7, { color: decisionColor(current.decision), bold: true, children: current.decision }),
|
|
7849
|
+
/* @__PURE__ */ jsx7(Text7, { color: theme.accent, children: " " + current.tool_name }),
|
|
7850
|
+
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " " + current.permission + " \xB7 " + current.trust_level })
|
|
7851
|
+
] }),
|
|
7852
|
+
/* @__PURE__ */ jsx7(Detail, { label: "reason", value: current.reason ?? "\u2014" }),
|
|
7853
|
+
/* @__PURE__ */ jsx7(Detail, { label: "rule", value: current.matched_rule_id ?? "\u2014" }),
|
|
7854
|
+
/* @__PURE__ */ jsx7(Detail, { label: "agent", value: current.agent_name ?? "\u2014" }),
|
|
7855
|
+
/* @__PURE__ */ jsx7(Detail, { label: "session", value: current.session_id ?? "\u2014" }),
|
|
7856
|
+
/* @__PURE__ */ jsx7(Detail, { label: "dlp", value: current.dlp_matches?.length ? current.dlp_matches.join(", ") : "none", color: current.dlp_matches?.length ? theme.bad : void 0 }),
|
|
7857
|
+
/* @__PURE__ */ jsx7(Detail, { label: "when", value: new Date(current.created_at).toLocaleString() }),
|
|
7858
|
+
current.arguments_summary ? /* @__PURE__ */ jsx7(Detail, { label: "args", value: truncate2(JSON.stringify(current.arguments_summary), 60) }) : null,
|
|
7859
|
+
/* @__PURE__ */ jsxs7(Box7, { marginTop: 1, flexDirection: "column", children: [
|
|
7860
|
+
/* @__PURE__ */ jsxs7(Text7, { color: theme.dim, children: [
|
|
7861
|
+
"Session ",
|
|
7862
|
+
current.session_id ? `\xB7 ${sessionCalls.length} calls` : "(no session id)"
|
|
7863
|
+
] }),
|
|
7864
|
+
current.session_id ? /* @__PURE__ */ jsx7(
|
|
7865
|
+
Table,
|
|
7866
|
+
{
|
|
7867
|
+
columns: [
|
|
7868
|
+
{ header: "DECISION", width: 9 },
|
|
7869
|
+
{ header: "TOOL", width: 20 },
|
|
7870
|
+
{ header: "REASON", width: 26 },
|
|
7871
|
+
{ header: "WHEN", width: 6 }
|
|
7872
|
+
],
|
|
7873
|
+
rows: sessionCalls.slice(0, 12).map((e) => [
|
|
7874
|
+
{ value: e.decision, color: decisionColor(e.decision) },
|
|
7875
|
+
{ value: truncate2(e.tool_name, 20), color: theme.accent },
|
|
7876
|
+
{ value: truncate2(e.reason ?? "\u2014", 26) },
|
|
7877
|
+
{ value: ago(e.created_at), dim: true }
|
|
7878
|
+
])
|
|
7879
|
+
}
|
|
7880
|
+
) : null
|
|
7881
|
+
] }),
|
|
7882
|
+
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "\u2190 back to list" })
|
|
7883
|
+
] });
|
|
7884
|
+
}
|
|
7885
|
+
const chip = (label, val, on) => /* @__PURE__ */ jsxs7(Text7, { children: [
|
|
7886
|
+
/* @__PURE__ */ jsxs7(Text7, { color: theme.dim, children: [
|
|
7887
|
+
label,
|
|
7888
|
+
":"
|
|
7889
|
+
] }),
|
|
7890
|
+
/* @__PURE__ */ jsx7(Text7, { color: on ? theme.accentBright : theme.dim, children: val }),
|
|
7891
|
+
/* @__PURE__ */ jsx7(Text7, { children: " " })
|
|
7892
|
+
] });
|
|
7893
|
+
return /* @__PURE__ */ jsx7(DataView, { loading: auditQ.loading && !auditQ.data, error: auditQ.error, children: /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
|
|
7894
|
+
/* @__PURE__ */ jsxs7(Box7, { children: [
|
|
7895
|
+
chip("dec", DECISIONS[di] ?? "all", di !== 0),
|
|
7896
|
+
chip("sig", SIGNALS[gi] ?? "all", gi !== 0),
|
|
7897
|
+
chip("tool", tool || "\xB7", !!tool),
|
|
7898
|
+
chip("agent", agent || "\xB7", !!agent),
|
|
7899
|
+
chip("search", search || "\xB7", !!search)
|
|
7485
7900
|
] }),
|
|
7486
|
-
/* @__PURE__ */
|
|
7901
|
+
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: focused ? "\u2191\u2193 \xB7 enter detail \xB7 f dec \xB7 g sig \xB7 t tool \xB7 n agent \xB7 / search \xB7 c clear" : "press \u2192 to browse" }),
|
|
7902
|
+
editing ? /* @__PURE__ */ jsxs7(Box7, { marginTop: 1, children: [
|
|
7903
|
+
/* @__PURE__ */ jsxs7(Text7, { color: theme.warn, children: [
|
|
7904
|
+
editing,
|
|
7905
|
+
": "
|
|
7906
|
+
] }),
|
|
7907
|
+
/* @__PURE__ */ jsx7(
|
|
7908
|
+
TextInput3,
|
|
7909
|
+
{
|
|
7910
|
+
value: editing === "tool" ? tool : editing === "agent" ? agent : search,
|
|
7911
|
+
onChange: editing === "tool" ? setTool : editing === "agent" ? setAgent : setSearch,
|
|
7912
|
+
onSubmit: () => setEditing(null)
|
|
7913
|
+
}
|
|
7914
|
+
)
|
|
7915
|
+
] }) : null,
|
|
7916
|
+
/* @__PURE__ */ jsx7(Box7, { marginTop: 1, children: /* @__PURE__ */ jsxs7(Text7, { color: theme.dim, children: [
|
|
7917
|
+
auditQ.data?.total ?? 0,
|
|
7918
|
+
" matched \xB7 showing ",
|
|
7919
|
+
Math.min(entries.length, 12)
|
|
7920
|
+
] }) }),
|
|
7921
|
+
/* @__PURE__ */ jsx7(
|
|
7487
7922
|
Table,
|
|
7488
7923
|
{
|
|
7489
7924
|
columns: [
|
|
7925
|
+
{ header: "", width: 2 },
|
|
7490
7926
|
{ header: "DECISION", width: 9 },
|
|
7491
7927
|
{ header: "TOOL", width: 18 },
|
|
7492
7928
|
{ header: "AGENT", width: 14 },
|
|
7493
|
-
{ header: "REASON", width:
|
|
7929
|
+
{ header: "REASON", width: 24 },
|
|
7494
7930
|
{ header: "DLP", width: 4 },
|
|
7495
7931
|
{ header: "WHEN", width: 6 }
|
|
7496
7932
|
],
|
|
7497
|
-
rows: entries.map((e) =>
|
|
7498
|
-
{ value: e.decision, color: decisionColor(e.decision) },
|
|
7499
|
-
{ value: truncate2(e.tool_name, 18), color: theme.accent },
|
|
7500
|
-
{ value: truncate2(e.agent_name ?? "\u2014", 14), dim: true },
|
|
7501
|
-
{ value: truncate2(e.reason ?? "\u2014", 26) },
|
|
7502
|
-
{ value: e.dlp_matches?.length ? String(e.dlp_matches.length) : "0", color: e.dlp_matches?.length ? theme.bad : void 0, dim: !e.dlp_matches?.length },
|
|
7503
|
-
{ value: ago(e.created_at), dim: true }
|
|
7504
|
-
])
|
|
7933
|
+
rows: entries.slice(0, 12).map((e, i) => rowFor(e, i === sel && focused))
|
|
7505
7934
|
}
|
|
7506
|
-
)
|
|
7935
|
+
)
|
|
7507
7936
|
] }) });
|
|
7508
7937
|
}
|
|
7509
|
-
|
|
7938
|
+
function rowFor(e, active2) {
|
|
7939
|
+
return [
|
|
7940
|
+
{ value: active2 ? "\u25B8" : "", color: theme.accentBright },
|
|
7941
|
+
{ value: e.decision, color: decisionColor(e.decision) },
|
|
7942
|
+
{ value: truncate2(e.tool_name, 18), color: theme.accent },
|
|
7943
|
+
{ value: truncate2(e.agent_name ?? "\u2014", 14), dim: true },
|
|
7944
|
+
{ value: truncate2(e.reason ?? "\u2014", 24) },
|
|
7945
|
+
{ value: e.dlp_matches?.length ? String(e.dlp_matches.length) : "0", color: e.dlp_matches?.length ? theme.bad : void 0, dim: !e.dlp_matches?.length },
|
|
7946
|
+
{ value: ago(e.created_at), dim: true }
|
|
7947
|
+
];
|
|
7948
|
+
}
|
|
7949
|
+
function Detail({ label, value, color }) {
|
|
7950
|
+
return /* @__PURE__ */ jsxs7(Box7, { children: [
|
|
7951
|
+
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: label.padEnd(9) }),
|
|
7952
|
+
/* @__PURE__ */ jsx7(Text7, { color, children: value })
|
|
7953
|
+
] });
|
|
7954
|
+
}
|
|
7955
|
+
var DECISIONS, SIGNALS;
|
|
7510
7956
|
var init_Audit = __esm({
|
|
7511
7957
|
"src/tui/panels/Audit.tsx"() {
|
|
7512
7958
|
"use strict";
|
|
@@ -7514,71 +7960,14 @@ var init_Audit = __esm({
|
|
|
7514
7960
|
init_components();
|
|
7515
7961
|
init_hooks();
|
|
7516
7962
|
init_theme();
|
|
7517
|
-
|
|
7518
|
-
|
|
7519
|
-
});
|
|
7520
|
-
|
|
7521
|
-
// src/tui/panels/Agents.tsx
|
|
7522
|
-
import { Box as Box7, Text as Text7 } from "ink";
|
|
7523
|
-
import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
7524
|
-
function AgentsPanel({ active: active2 }) {
|
|
7525
|
-
const agents = useLoader(() => api.agents.live({ limit: 14 }));
|
|
7526
|
-
usePoll(agents.reload, 4e3, active2);
|
|
7527
|
-
const data = agents.data;
|
|
7528
|
-
const rows = data?.agents ?? [];
|
|
7529
|
-
return /* @__PURE__ */ jsx7(DataView, { loading: agents.loading && !data, error: agents.error, empty: !!data && rows.length === 0, emptyText: "No agent sessions.", children: /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
|
|
7530
|
-
data ? /* @__PURE__ */ jsxs7(Text7, { color: theme.dim, children: [
|
|
7531
|
-
/* @__PURE__ */ jsxs7(Text7, { color: theme.ok, children: [
|
|
7532
|
-
data.counts.active,
|
|
7533
|
-
" active"
|
|
7534
|
-
] }),
|
|
7535
|
-
" \xB7 ",
|
|
7536
|
-
/* @__PURE__ */ jsxs7(Text7, { color: theme.warn, children: [
|
|
7537
|
-
data.counts.idle,
|
|
7538
|
-
" idle"
|
|
7539
|
-
] }),
|
|
7540
|
-
" \xB7 ",
|
|
7541
|
-
data.counts.deactivated,
|
|
7542
|
-
" off"
|
|
7543
|
-
] }) : null,
|
|
7544
|
-
/* @__PURE__ */ jsx7(Box7, { marginTop: 1, children: /* @__PURE__ */ jsx7(
|
|
7545
|
-
Table,
|
|
7546
|
-
{
|
|
7547
|
-
columns: [
|
|
7548
|
-
{ header: "STATUS", width: 7 },
|
|
7549
|
-
{ header: "AGENT", width: 20 },
|
|
7550
|
-
{ header: "CALLS", width: 6 },
|
|
7551
|
-
{ header: "DENY", width: 5 },
|
|
7552
|
-
{ header: "DLP", width: 4 },
|
|
7553
|
-
{ header: "TRUST", width: 6 },
|
|
7554
|
-
{ header: "CHARACTER", width: 20 }
|
|
7555
|
-
],
|
|
7556
|
-
rows: rows.map((a) => [
|
|
7557
|
-
{ value: a.status, color: modeColor(a.status) },
|
|
7558
|
-
{ value: truncate2(a.agent_name ?? a.agent_id ?? a.session_id, 20), color: theme.accent },
|
|
7559
|
-
{ value: String(a.total_calls) },
|
|
7560
|
-
{ value: String(a.denied_calls), color: a.denied_calls ? theme.bad : void 0, dim: !a.denied_calls },
|
|
7561
|
-
{ value: String(a.dlp_events), color: a.dlp_events ? theme.bad : void 0, dim: !a.dlp_events },
|
|
7562
|
-
{ value: `${a.trust_score}` },
|
|
7563
|
-
{ value: truncate2(a.character || "\u2014", 20), dim: true }
|
|
7564
|
-
])
|
|
7565
|
-
}
|
|
7566
|
-
) })
|
|
7567
|
-
] }) });
|
|
7568
|
-
}
|
|
7569
|
-
var init_Agents = __esm({
|
|
7570
|
-
"src/tui/panels/Agents.tsx"() {
|
|
7571
|
-
"use strict";
|
|
7572
|
-
init_api_client();
|
|
7573
|
-
init_components();
|
|
7574
|
-
init_hooks();
|
|
7575
|
-
init_theme();
|
|
7963
|
+
DECISIONS = [void 0, "DENY", "ALLOW"];
|
|
7964
|
+
SIGNALS = [void 0, "dlp", "ratelimit"];
|
|
7576
7965
|
}
|
|
7577
7966
|
});
|
|
7578
7967
|
|
|
7579
7968
|
// src/tui/App.tsx
|
|
7580
7969
|
import { Box as Box8, Text as Text8, useApp, useInput as useInput5 } from "ink";
|
|
7581
|
-
import { useState as
|
|
7970
|
+
import { useState as useState7 } from "react";
|
|
7582
7971
|
import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
7583
7972
|
function Banner() {
|
|
7584
7973
|
const wide = (process.stdout.columns ?? 80) >= 82;
|
|
@@ -7595,8 +7984,8 @@ function Banner() {
|
|
|
7595
7984
|
}
|
|
7596
7985
|
function App() {
|
|
7597
7986
|
const { exit } = useApp();
|
|
7598
|
-
const [section, setSection] =
|
|
7599
|
-
const [focus, setFocus] =
|
|
7987
|
+
const [section, setSection] = useState7(0);
|
|
7988
|
+
const [focus, setFocus] = useState7("nav");
|
|
7600
7989
|
useInput5((input, key) => {
|
|
7601
7990
|
if (focus === "nav") {
|
|
7602
7991
|
if (key.upArrow) setSection((n) => (n - 1 + SECTIONS.length) % SECTIONS.length);
|
|
@@ -7625,19 +8014,19 @@ var init_App = __esm({
|
|
|
7625
8014
|
init_cli_utils();
|
|
7626
8015
|
init_components();
|
|
7627
8016
|
init_theme();
|
|
8017
|
+
init_Live();
|
|
7628
8018
|
init_Policies();
|
|
7629
8019
|
init_RateLimit();
|
|
7630
8020
|
init_Dlp();
|
|
7631
8021
|
init_Stats();
|
|
7632
8022
|
init_Audit();
|
|
7633
|
-
init_Agents();
|
|
7634
8023
|
SECTIONS = [
|
|
8024
|
+
{ label: "Live", Panel: LivePanel },
|
|
7635
8025
|
{ label: "Policies", Panel: PoliciesPanel },
|
|
7636
8026
|
{ label: "Rate Limit", Panel: RateLimitPanel },
|
|
7637
8027
|
{ label: "DLP", Panel: DlpPanel },
|
|
7638
8028
|
{ label: "Stats", Panel: StatsPanel },
|
|
7639
|
-
{ label: "Audit", Panel: AuditPanel }
|
|
7640
|
-
{ label: "Agents", Panel: AgentsPanel }
|
|
8029
|
+
{ label: "Audit", Panel: AuditPanel }
|
|
7641
8030
|
];
|
|
7642
8031
|
BANNER_HEX = ["#1432A0", "#2850BE", "#3C6ED7", "#5A8CE6", "#82AAF0", "#AAC8FA"];
|
|
7643
8032
|
}
|
|
@@ -7702,10 +8091,10 @@ function truncate3(s, n) {
|
|
|
7702
8091
|
function sparkline2(values) {
|
|
7703
8092
|
if (values.length === 0) return "";
|
|
7704
8093
|
const max = Math.max(...values, 0);
|
|
7705
|
-
if (max === 0) return
|
|
7706
|
-
return values.map((v) =>
|
|
8094
|
+
if (max === 0) return BLOCKS3[0].repeat(values.length);
|
|
8095
|
+
return values.map((v) => BLOCKS3[Math.min(BLOCKS3.length - 1, Math.round(v / max * (BLOCKS3.length - 1)))]).join("");
|
|
7707
8096
|
}
|
|
7708
|
-
var out, err, dim, bold, green, red, yellow, cyan, ANSI, width,
|
|
8097
|
+
var out, err, dim, bold, green, red, yellow, cyan, ANSI, width, BLOCKS3;
|
|
7709
8098
|
var init_format = __esm({
|
|
7710
8099
|
"src/commands/format.ts"() {
|
|
7711
8100
|
"use strict";
|
|
@@ -7720,7 +8109,7 @@ var init_format = __esm({
|
|
|
7720
8109
|
cyan = (s) => `${c.cyan}${s}${c.reset}`;
|
|
7721
8110
|
ANSI = /\x1b\[[0-9;]*m/g;
|
|
7722
8111
|
width = (s) => s.replace(ANSI, "").length;
|
|
7723
|
-
|
|
8112
|
+
BLOCKS3 = ["\u2581", "\u2582", "\u2583", "\u2584", "\u2585", "\u2586", "\u2587", "\u2588"];
|
|
7724
8113
|
}
|
|
7725
8114
|
});
|
|
7726
8115
|
|