@solongate/proxy 0.64.0 → 0.66.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 +188 -104
- package/dist/tui/index.js +187 -103
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -7020,19 +7020,27 @@ var init_hooks = __esm({
|
|
|
7020
7020
|
|
|
7021
7021
|
// src/tui/panels/Live.tsx
|
|
7022
7022
|
import { Box as Box2, Text as Text2 } from "ink";
|
|
7023
|
-
import { useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
|
|
7023
|
+
import { useCallback as useCallback2, 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 ColumnChart({ series, height, width: width2, color
|
|
7026
|
-
const
|
|
7027
|
-
const data = [...new Array(
|
|
7028
|
-
const
|
|
7025
|
+
function ColumnChart({ series, hot, height, width: width2, color }) {
|
|
7026
|
+
const pad = Math.max(0, width2 - series.length);
|
|
7027
|
+
const data = [...new Array(pad).fill(0), ...series.slice(-width2)];
|
|
7028
|
+
const hotPad = hot ? [...new Array(pad).fill(false), ...hot.slice(-width2)] : void 0;
|
|
7029
|
+
const max = Math.max(1, ...data);
|
|
7029
7030
|
const lines = [];
|
|
7030
7031
|
for (let r = height; r >= 1; r--) {
|
|
7031
7032
|
const segs = [];
|
|
7032
7033
|
for (let i = 0; i < data.length; i++) {
|
|
7033
|
-
const
|
|
7034
|
-
const
|
|
7035
|
-
|
|
7034
|
+
const v = data[i];
|
|
7035
|
+
const frac = v / max;
|
|
7036
|
+
let ch;
|
|
7037
|
+
let c2;
|
|
7038
|
+
if (frac >= r / height) ch = "\u2588";
|
|
7039
|
+
else if (frac >= (r - 0.5) / height) ch = "\u2584";
|
|
7040
|
+
else if (r === 1) ch = "\u2581";
|
|
7041
|
+
else ch = " ";
|
|
7042
|
+
if (r === 1 && v === 0) c2 = "#233457";
|
|
7043
|
+
else c2 = hotPad?.[i] ? theme.bad : color;
|
|
7036
7044
|
const last = segs[segs.length - 1];
|
|
7037
7045
|
if (last && last.color === c2) last.text += ch;
|
|
7038
7046
|
else segs.push({ text: ch, color: c2 });
|
|
@@ -7046,26 +7054,82 @@ function ColumnChart({ series, height, width: width2, color, limit = 0 }) {
|
|
|
7046
7054
|
function HBar({ label, value, max, width: width2, color }) {
|
|
7047
7055
|
const filled = max > 0 ? Math.round(value / max * width2) : 0;
|
|
7048
7056
|
return /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
7049
|
-
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: label.padEnd(
|
|
7057
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: label.padEnd(10) }),
|
|
7050
7058
|
/* @__PURE__ */ jsx2(Text2, { color, children: "\u2588".repeat(Math.min(width2, filled)) }),
|
|
7051
|
-
/* @__PURE__ */ jsx2(Text2, { color:
|
|
7059
|
+
/* @__PURE__ */ jsx2(Text2, { color: "#233457", children: "\u2591".repeat(Math.max(0, width2 - filled)) }),
|
|
7060
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " " + value })
|
|
7052
7061
|
] });
|
|
7053
7062
|
}
|
|
7054
|
-
function
|
|
7055
|
-
const
|
|
7056
|
-
|
|
7063
|
+
function PaneTitle({ label, extra, width: width2 }) {
|
|
7064
|
+
const tail = extra ? ` ${extra} ` : " ";
|
|
7065
|
+
const used = 2 + 1 + label.length + tail.length;
|
|
7066
|
+
return /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
7067
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "\u2500 " }),
|
|
7068
|
+
/* @__PURE__ */ jsxs2(Text2, { color: theme.accentBright, bold: true, children: [
|
|
7069
|
+
"\u258E",
|
|
7070
|
+
label
|
|
7071
|
+
] }),
|
|
7072
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: tail + "\u2500".repeat(Math.max(0, width2 - used)) })
|
|
7073
|
+
] });
|
|
7057
7074
|
}
|
|
7058
7075
|
function LivePanel({ active: active2 }) {
|
|
7059
|
-
const
|
|
7060
|
-
const
|
|
7061
|
-
const
|
|
7076
|
+
const [s, setS] = useState2(null);
|
|
7077
|
+
const [tsData, setTsData] = useState2(null);
|
|
7078
|
+
const [lat, setLat] = useState2([]);
|
|
7079
|
+
const [buffer, setBuffer] = useState2([]);
|
|
7080
|
+
const [log6, setLog] = useState2([]);
|
|
7081
|
+
const seenRef = useRef2(/* @__PURE__ */ new Set());
|
|
7082
|
+
const pushLog = useCallback2((msg, level = "ok") => {
|
|
7083
|
+
setLog((prev) => [...prev.slice(-59), { ts: Date.now(), msg, level }]);
|
|
7084
|
+
}, []);
|
|
7085
|
+
const doPoll = useCallback2(async () => {
|
|
7086
|
+
const t0 = Date.now();
|
|
7087
|
+
try {
|
|
7088
|
+
const [sd, fd, td] = await Promise.all([api.stats.get(), api.audit.list({ limit: 50 }), api.stats.timeseries({ period: "24h" })]);
|
|
7089
|
+
const ms = Date.now() - t0;
|
|
7090
|
+
setS(sd);
|
|
7091
|
+
setTsData(td);
|
|
7092
|
+
setLat((l) => [...l.slice(-47), ms]);
|
|
7093
|
+
const fresh = fd.entries.filter((e) => !seenRef.current.has(e.id));
|
|
7094
|
+
for (const e of fresh) seenRef.current.add(e.id);
|
|
7095
|
+
const firstLoad = seenRef.current.size === fresh.length && fresh.length > 1;
|
|
7096
|
+
if (fresh.length) {
|
|
7097
|
+
setBuffer((prev) => [...prev, ...fresh.sort((a, b) => Date.parse(a.created_at) - Date.parse(b.created_at))].slice(-400));
|
|
7098
|
+
}
|
|
7099
|
+
const denies = fresh.filter((e) => e.decision !== "ALLOW").length;
|
|
7100
|
+
const dlp = fresh.filter((e) => e.dlp_matches?.length).length;
|
|
7101
|
+
if (firstLoad) pushLog(`link up \xB7 api ${ms}ms \xB7 loaded ${fresh.length} calls`);
|
|
7102
|
+
else
|
|
7103
|
+
pushLog(
|
|
7104
|
+
`api ${ms}ms \xB7 +${fresh.length} calls${denies ? ` \xB7 ${denies} DENIED` : ""}${dlp ? ` \xB7 ${dlp} DLP` : ""}`,
|
|
7105
|
+
denies || dlp ? "bad" : fresh.length ? "warn" : "ok"
|
|
7106
|
+
);
|
|
7107
|
+
} catch (e) {
|
|
7108
|
+
pushLog(`api error \xB7 ${truncate2(e instanceof Error ? e.message : String(e), 40)}`, "bad");
|
|
7109
|
+
}
|
|
7110
|
+
}, [pushLog]);
|
|
7111
|
+
useEffect2(() => {
|
|
7112
|
+
if (!active2) return;
|
|
7113
|
+
void doPoll();
|
|
7114
|
+
const t = setInterval(() => void doPoll(), 2e3);
|
|
7115
|
+
return () => clearInterval(t);
|
|
7116
|
+
}, [active2, doPoll]);
|
|
7117
|
+
const sessions = useLoader(() => api.agents.live({ limit: 30, includeDeactivated: true }));
|
|
7062
7118
|
const insights = useLoader(() => api.stats.securityInsights(7));
|
|
7063
|
-
usePoll(
|
|
7064
|
-
stats.reload();
|
|
7065
|
-
ts.reload();
|
|
7066
|
-
feed.reload();
|
|
7067
|
-
}, 2e3, active2);
|
|
7119
|
+
usePoll(sessions.reload, 5e3, active2);
|
|
7068
7120
|
usePoll(insights.reload, 1e4, active2);
|
|
7121
|
+
useEffect2(() => {
|
|
7122
|
+
const c2 = sessions.data?.counts;
|
|
7123
|
+
if (c2) pushLog(`sessions ${c2.active}\u25B2 ${c2.idle}\u25CC ${c2.deactivated}\u25CB`);
|
|
7124
|
+
}, [sessions.data, pushLog]);
|
|
7125
|
+
useEffect2(() => {
|
|
7126
|
+
const ib = insights.data;
|
|
7127
|
+
if (ib?.layers) {
|
|
7128
|
+
const rl2 = ib.layers.rateLimit;
|
|
7129
|
+
const dl2 = ib.layers.dlp;
|
|
7130
|
+
pushLog(`layers \xB7 ratelimit ${rl2?.mode ?? "?"} ${rl2?.perMinute || "\u2014"}/m \xB7 dlp ${dl2?.mode ?? "?"} ${(dl2?.patterns ?? []).length} armed`);
|
|
7131
|
+
}
|
|
7132
|
+
}, [insights.data, pushLog]);
|
|
7069
7133
|
const [tick, setTick] = useState2(0);
|
|
7070
7134
|
useEffect2(() => {
|
|
7071
7135
|
if (!active2) return;
|
|
@@ -7073,107 +7137,128 @@ function LivePanel({ active: active2 }) {
|
|
|
7073
7137
|
return () => clearInterval(t);
|
|
7074
7138
|
}, [active2]);
|
|
7075
7139
|
const startRef = useRef2(Date.now());
|
|
7076
|
-
const [buffer, setBuffer] = useState2([]);
|
|
7077
|
-
useEffect2(() => {
|
|
7078
|
-
const incoming = feed.data?.entries;
|
|
7079
|
-
if (!incoming?.length) return;
|
|
7080
|
-
setBuffer((prev) => {
|
|
7081
|
-
const seen = new Set(prev.map((e) => e.id));
|
|
7082
|
-
const fresh = incoming.filter((e) => !seen.has(e.id)).sort((a, b) => Date.parse(a.created_at) - Date.parse(b.created_at));
|
|
7083
|
-
return fresh.length ? [...prev, ...fresh].slice(-400) : prev;
|
|
7084
|
-
});
|
|
7085
|
-
}, [feed.data]);
|
|
7086
7140
|
const cols = process.stdout.columns ?? 100;
|
|
7087
7141
|
const rows = process.stdout.rows ?? 30;
|
|
7088
|
-
const s = stats.data;
|
|
7089
7142
|
const ins = insights.data ?? {};
|
|
7090
|
-
const minuteRaw = (ins.activity?.minute ?? []).map((b) => b.count);
|
|
7091
|
-
const hourRaw = (ins.activity?.hour ?? []).map((b) => b.count);
|
|
7092
|
-
const minuteLive = minuteRaw.some((c2) => c2 > 0);
|
|
7093
|
-
const minuteSeries = minuteLive ? minuteRaw : hourRaw;
|
|
7094
|
-
const activityLabel = minuteLive ? "calls/min \xB7 last 60m" : "calls/hour \xB7 last 24h";
|
|
7095
|
-
const limit = minuteLive ? ins.layers?.rateLimit?.perMinute ?? 0 : 0;
|
|
7096
|
-
const denySeries = (ts.data?.timeseries ?? []).map((p) => p.denied);
|
|
7097
7143
|
const spin = SPIN[tick % SPIN.length];
|
|
7098
|
-
const
|
|
7144
|
+
const points = tsData?.timeseries ?? [];
|
|
7145
|
+
const traffic = points.map((p) => p.total);
|
|
7146
|
+
const trafficHot = points.map((p) => p.denied > 0);
|
|
7147
|
+
const rl = ins.layers?.rateLimit;
|
|
7148
|
+
const dl = ins.layers?.dlp;
|
|
7149
|
+
const minuteNow = (ins.activity?.minute ?? []).slice(-1)[0]?.count ?? 0;
|
|
7150
|
+
const dlpBars = (ins.dlpByPattern ?? []).slice(0, 2);
|
|
7151
|
+
const maxDlpBar = dlpBars[0]?.count ?? 1;
|
|
7099
7152
|
const toolCounts = /* @__PURE__ */ new Map();
|
|
7100
|
-
const
|
|
7101
|
-
|
|
7102
|
-
toolCounts.set(e.tool_name, (toolCounts.get(e.tool_name) ?? 0) + 1);
|
|
7103
|
-
const perm = (e.permission || "?").toUpperCase();
|
|
7104
|
-
permCounts.set(perm, (permCounts.get(perm) ?? 0) + 1);
|
|
7105
|
-
}
|
|
7106
|
-
const topTools = [...toolCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 5);
|
|
7107
|
-
const maxTool = topTools[0]?.[1] ?? 1;
|
|
7108
|
-
const perms = ["READ", "WRITE", "EXECUTE", "NETWORK"].map((p) => [p, permCounts.get(p) ?? 0]);
|
|
7109
|
-
const maxPerm = Math.max(1, ...perms.map(([, c2]) => c2));
|
|
7110
|
-
const dlpTop = (ins.dlpByPattern ?? []).slice(0, 2);
|
|
7153
|
+
for (const e of buffer) toolCounts.set(e.tool_name, (toolCounts.get(e.tool_name) ?? 0) + 1);
|
|
7154
|
+
const topTools = [...toolCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 3);
|
|
7111
7155
|
const denials = buffer.filter((e) => e.decision !== "ALLOW");
|
|
7112
7156
|
const lastDeny = denials[denials.length - 1];
|
|
7113
|
-
const
|
|
7114
|
-
const
|
|
7115
|
-
const
|
|
7116
|
-
const
|
|
7157
|
+
const latNow = lat[lat.length - 1] ?? 0;
|
|
7158
|
+
const latAvg = lat.length ? Math.round(lat.reduce((a, b) => a + b, 0) / lat.length) : 0;
|
|
7159
|
+
const sess = (sessions.data?.agents ?? []).slice().sort((a, b) => Date.parse(b.last_seen_at) - Date.parse(a.last_seen_at));
|
|
7160
|
+
const sessCounts = sessions.data?.counts;
|
|
7161
|
+
const chartH = rows >= 36 ? 6 : 4;
|
|
7162
|
+
const colH = rows >= 32 ? 6 : 5;
|
|
7163
|
+
const streamRows = Math.max(4, rows - 6 - chartH - colH);
|
|
7117
7164
|
const tail = buffer.slice(-streamRows);
|
|
7118
|
-
const
|
|
7119
|
-
|
|
7120
|
-
|
|
7121
|
-
|
|
7122
|
-
|
|
7165
|
+
const innerW = cols - 2;
|
|
7166
|
+
const leftW = Math.floor(innerW * 0.55);
|
|
7167
|
+
const rightW = innerW - leftW - 2;
|
|
7168
|
+
const colW = Math.max(20, Math.floor((innerW - 4) / 3));
|
|
7169
|
+
const sessionDot = (st) => st === "active" ? { ch: "\u25CF", color: theme.ok } : st === "idle" ? { ch: "\u25D0", color: theme.warn } : { ch: "\u25CB", color: theme.dim };
|
|
7123
7170
|
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingX: 1, children: [
|
|
7124
7171
|
/* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
7125
|
-
/* @__PURE__ */ jsx2(Text2, { backgroundColor: "#1432A0", color: "#AAC8FA", bold: true, children: " SOLONGATE
|
|
7126
|
-
/* @__PURE__ */
|
|
7127
|
-
|
|
7128
|
-
spin,
|
|
7129
|
-
" up ",
|
|
7130
|
-
fmtUp(Date.now() - startRef.current),
|
|
7131
|
-
" \xB7 ",
|
|
7132
|
-
hhmmss(Date.now()),
|
|
7133
|
-
" \xB7 poll 2s"
|
|
7134
|
-
] }),
|
|
7135
|
-
lastDeny ? /* @__PURE__ */ jsxs2(Text2, { color: theme.bad, children: [
|
|
7136
|
-
" \u26A0 ",
|
|
7137
|
-
hhmmss(lastDeny.created_at),
|
|
7138
|
-
" ",
|
|
7139
|
-
lastDeny.tool_name,
|
|
7140
|
-
" denied"
|
|
7141
|
-
] }) : null,
|
|
7142
|
-
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " esc menu \xB7 q quit" })
|
|
7172
|
+
/* @__PURE__ */ jsx2(Text2, { backgroundColor: "#1432A0", color: "#AAC8FA", bold: true, children: " \u26E8 SOLONGATE LIVE " }),
|
|
7173
|
+
/* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: ` ${spin} up ${fmtUp(Date.now() - startRef.current)} \xB7 ${hhmmss(Date.now())} \xB7 api ${latNow}ms ` }),
|
|
7174
|
+
lastDeny ? /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#3d1220", color: "#ff6b6b", bold: true, children: ` \u26A0 ${hhmmss(lastDeny.created_at)} ${lastDeny.tool_name} DENIED ` }) : /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#4f6db8", children: " \u2713 clean " })
|
|
7143
7175
|
] }),
|
|
7144
7176
|
/* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
7145
|
-
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "calls " }),
|
|
7177
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "\u2502 calls " }),
|
|
7146
7178
|
/* @__PURE__ */ jsx2(Text2, { bold: true, children: s ? s.total_calls : "\xB7\xB7\xB7\xB7" }),
|
|
7147
|
-
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "
|
|
7179
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 allow " }),
|
|
7148
7180
|
/* @__PURE__ */ jsx2(Text2, { color: theme.ok, children: s ? s.allowed : "\xB7\xB7\xB7" }),
|
|
7149
|
-
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "
|
|
7181
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 deny " }),
|
|
7150
7182
|
/* @__PURE__ */ jsx2(Text2, { color: theme.bad, bold: true, children: s ? s.denied : "\xB7\xB7" }),
|
|
7151
|
-
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "
|
|
7152
|
-
/* @__PURE__ */ jsx2(Text2, { color:
|
|
7153
|
-
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "
|
|
7154
|
-
/* @__PURE__ */ jsx2(Text2, { color: theme.
|
|
7155
|
-
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "
|
|
7156
|
-
/* @__PURE__ */ jsx2(Text2, { color: theme.accent, children:
|
|
7183
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 ratelimit " }),
|
|
7184
|
+
/* @__PURE__ */ jsx2(Text2, { color: rl?.mode === "block" ? theme.ok : rl?.mode === "detect" ? theme.warn : theme.dim, children: rl?.mode ?? "?" }),
|
|
7185
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 dlp " }),
|
|
7186
|
+
/* @__PURE__ */ jsx2(Text2, { color: dl?.mode === "block" ? theme.ok : dl?.mode === "detect" ? theme.warn : theme.dim, children: dl?.mode ?? "?" }),
|
|
7187
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 sessions " }),
|
|
7188
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: sessCounts ? `${sessCounts.active}\u25B2 ${sessCounts.idle}\u25CC ${sessCounts.deactivated}\u25CB` : "\xB7" }),
|
|
7189
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 policies " }),
|
|
7190
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: s ? s.active_policies : "\xB7" }),
|
|
7191
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502" })
|
|
7192
|
+
] }),
|
|
7193
|
+
/* @__PURE__ */ jsxs2(Box2, { children: [
|
|
7194
|
+
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: leftW, marginRight: 2, children: [
|
|
7195
|
+
/* @__PURE__ */ jsx2(PaneTitle, { label: "TRAFFIC", extra: `24h \xB7 peak ${Math.max(0, ...traffic)} \xB7 red = had denials`, width: leftW }),
|
|
7196
|
+
/* @__PURE__ */ jsx2(ColumnChart, { series: traffic, hot: trafficHot, height: chartH, width: leftW, color: theme.accent })
|
|
7197
|
+
] }),
|
|
7198
|
+
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: rightW, children: [
|
|
7199
|
+
/* @__PURE__ */ jsx2(PaneTitle, { label: "API LATENCY", extra: `live \xB7 now ${latNow}ms \xB7 avg ${latAvg}ms`, width: rightW }),
|
|
7200
|
+
/* @__PURE__ */ jsx2(ColumnChart, { series: lat, hot: lat.map((v) => v > 800), height: chartH, width: rightW, color: "#82AAF0" })
|
|
7201
|
+
] })
|
|
7157
7202
|
] }),
|
|
7158
|
-
/* @__PURE__ */ jsx2(SectionTitle, { width: cols - 2, children: `ACTIVITY ${activityLabel} \xB7 peak ${Math.max(0, ...minuteSeries)}${limit ? ` \xB7 limit ${limit} (red = over)` : ""}` }),
|
|
7159
|
-
minuteSeries.length ? /* @__PURE__ */ jsx2(ColumnChart, { series: minuteSeries, height: bigH, width: cols - 2, color: theme.accent, limit }) : /* @__PURE__ */ jsx2(Box2, { height: bigH, children: /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: insights.loading ? "loading activity\u2026" : "no activity data" }) }),
|
|
7160
7203
|
/* @__PURE__ */ jsxs2(Box2, { children: [
|
|
7161
7204
|
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: colW, marginRight: 2, children: [
|
|
7162
|
-
/* @__PURE__ */ jsx2(
|
|
7163
|
-
/* @__PURE__ */
|
|
7205
|
+
/* @__PURE__ */ jsx2(PaneTitle, { label: "LAYERS", width: colW }),
|
|
7206
|
+
/* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
7207
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "RATELIMIT ".padEnd(10) }),
|
|
7208
|
+
/* @__PURE__ */ jsx2(Text2, { color: rl?.mode === "block" ? theme.ok : rl?.mode === "detect" ? theme.warn : theme.dim, children: (rl?.mode ?? "?").padEnd(7) }),
|
|
7209
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: rl?.perMinute ? `${minuteNow}/${rl.perMinute} per min` : "no limit set" })
|
|
7210
|
+
] }),
|
|
7211
|
+
rl?.perMinute ? /* @__PURE__ */ jsx2(HBar, { label: "load", value: minuteNow, max: rl.perMinute, width: Math.max(6, colW - 16), color: minuteNow > rl.perMinute ? theme.bad : theme.accent }) : null,
|
|
7212
|
+
/* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
7213
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "DLP".padEnd(10) }),
|
|
7214
|
+
/* @__PURE__ */ jsx2(Text2, { color: dl?.mode === "block" ? theme.ok : dl?.mode === "detect" ? theme.warn : theme.dim, children: (dl?.mode ?? "?").padEnd(7) }),
|
|
7215
|
+
/* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
|
|
7216
|
+
(dl?.patterns ?? []).length,
|
|
7217
|
+
" patterns armed"
|
|
7218
|
+
] })
|
|
7219
|
+
] }),
|
|
7220
|
+
dlpBars.length ? dlpBars.map((d) => /* @__PURE__ */ jsx2(HBar, { label: truncate2(d.pattern, 10), value: d.count, max: maxDlpBar, width: Math.max(6, colW - 16), color: theme.bad }, d.pattern)) : /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "hits 7d none \u2014 clean" })
|
|
7164
7221
|
] }),
|
|
7165
7222
|
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: colW, marginRight: 2, children: [
|
|
7166
|
-
/* @__PURE__ */ jsx2(
|
|
7167
|
-
|
|
7223
|
+
/* @__PURE__ */ jsx2(PaneTitle, { label: "SESSIONS", extra: sessCounts ? `${sessCounts.active} live` : "", width: colW }),
|
|
7224
|
+
sess.length === 0 ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "scanning\u2026" }) : null,
|
|
7225
|
+
sess.slice(0, colH - 1).map((a) => {
|
|
7226
|
+
const dot = sessionDot(a.status);
|
|
7227
|
+
return /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
7228
|
+
/* @__PURE__ */ jsxs2(Text2, { color: dot.color, children: [
|
|
7229
|
+
dot.ch,
|
|
7230
|
+
" "
|
|
7231
|
+
] }),
|
|
7232
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: truncate2(a.agent_name ?? a.session_id, 12).padEnd(13) }),
|
|
7233
|
+
/* @__PURE__ */ jsxs2(Text2, { children: [
|
|
7234
|
+
String(a.total_calls).padStart(4),
|
|
7235
|
+
"c "
|
|
7236
|
+
] }),
|
|
7237
|
+
/* @__PURE__ */ jsxs2(Text2, { color: a.denied_calls ? theme.bad : theme.dim, children: [
|
|
7238
|
+
String(a.denied_calls).padStart(3),
|
|
7239
|
+
"d "
|
|
7240
|
+
] }),
|
|
7241
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: ago(a.last_seen_at) })
|
|
7242
|
+
] }, a.session_id);
|
|
7243
|
+
})
|
|
7168
7244
|
] }),
|
|
7169
7245
|
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: colW, children: [
|
|
7170
|
-
/* @__PURE__ */ jsx2(
|
|
7171
|
-
|
|
7172
|
-
|
|
7246
|
+
/* @__PURE__ */ jsx2(PaneTitle, { label: "EVENT LOG", extra: "streaming", width: colW }),
|
|
7247
|
+
log6.slice(-(colH - 1)).map((l, i) => /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
7248
|
+
/* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
|
|
7249
|
+
hhmmss(l.ts),
|
|
7250
|
+
" "
|
|
7251
|
+
] }),
|
|
7252
|
+
/* @__PURE__ */ jsx2(Text2, { color: l.level === "bad" ? theme.bad : l.level === "warn" ? theme.warn : "#4f8f6b", children: "\u25B8 " }),
|
|
7253
|
+
/* @__PURE__ */ jsx2(Text2, { color: l.level === "bad" ? theme.bad : theme.dim, children: l.msg })
|
|
7254
|
+
] }, l.ts + ":" + i))
|
|
7173
7255
|
] })
|
|
7174
7256
|
] }),
|
|
7175
|
-
/* @__PURE__ */ jsx2(
|
|
7176
|
-
tail.length === 0 ? /* @__PURE__ */
|
|
7257
|
+
/* @__PURE__ */ jsx2(PaneTitle, { label: "TOOL STREAM", extra: `tail -f \xB7 ${buffer.length} buffered`, width: innerW }),
|
|
7258
|
+
tail.length === 0 ? /* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
|
|
7259
|
+
spin,
|
|
7260
|
+
" awaiting traffic\u2026"
|
|
7261
|
+
] }) : null,
|
|
7177
7262
|
tail.map((e) => /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
7178
7263
|
/* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
|
|
7179
7264
|
"[",
|
|
@@ -7187,15 +7272,13 @@ function LivePanel({ active: active2 }) {
|
|
|
7187
7272
|
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: (e.arguments_summary ? JSON.stringify(e.arguments_summary) : e.reason ?? "").replace(/\s+/g, " ") })
|
|
7188
7273
|
] }, e.id)),
|
|
7189
7274
|
/* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
7190
|
-
/* @__PURE__ */ jsx2(Text2, { color:
|
|
7191
|
-
/* @__PURE__ */ jsx2(Text2, { color:
|
|
7192
|
-
/* @__PURE__ */ jsx2(Text2, { color:
|
|
7193
|
-
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "$ " }),
|
|
7194
|
-
/* @__PURE__ */ jsx2(Text2, { color: theme.accentBright, children: cursor })
|
|
7275
|
+
/* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", bold: true, children: " \u25B2 LIVE " }),
|
|
7276
|
+
/* @__PURE__ */ jsx2(Text2, { backgroundColor: "#0b1530", color: "#4f6db8", children: ` top ${topTools.map(([t, c2]) => `${t}\xD7${c2}`).join(" ") || "\u2014"} \xB7 buf ${buffer.length} ` }),
|
|
7277
|
+
/* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: " esc menu \xB7 q quit " })
|
|
7195
7278
|
] })
|
|
7196
7279
|
] });
|
|
7197
7280
|
}
|
|
7198
|
-
var SPIN, hhmmss, fmtUp;
|
|
7281
|
+
var SPIN, BG, hhmmss, fmtUp;
|
|
7199
7282
|
var init_Live = __esm({
|
|
7200
7283
|
"src/tui/panels/Live.tsx"() {
|
|
7201
7284
|
"use strict";
|
|
@@ -7203,6 +7286,7 @@ var init_Live = __esm({
|
|
|
7203
7286
|
init_hooks();
|
|
7204
7287
|
init_theme();
|
|
7205
7288
|
SPIN = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
7289
|
+
BG = "#12234f";
|
|
7206
7290
|
hhmmss = (ts) => {
|
|
7207
7291
|
const d = new Date(ts);
|
|
7208
7292
|
return Number.isNaN(d.getTime()) ? "--:--:--" : d.toTimeString().slice(0, 8);
|
package/dist/tui/index.js
CHANGED
|
@@ -126,7 +126,7 @@ function KeyHints({ hints }) {
|
|
|
126
126
|
|
|
127
127
|
// src/tui/panels/Live.tsx
|
|
128
128
|
import { Box as Box2, Text as Text2 } from "ink";
|
|
129
|
-
import { useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
|
|
129
|
+
import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
|
|
130
130
|
|
|
131
131
|
// src/api-client/client.ts
|
|
132
132
|
import { readFileSync, existsSync } from "fs";
|
|
@@ -432,6 +432,7 @@ function usePoll(reload, intervalMs, enabled = true) {
|
|
|
432
432
|
// src/tui/panels/Live.tsx
|
|
433
433
|
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
434
434
|
var SPIN = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
435
|
+
var BG = "#12234f";
|
|
435
436
|
var hhmmss = (ts) => {
|
|
436
437
|
const d = new Date(ts);
|
|
437
438
|
return Number.isNaN(d.getTime()) ? "--:--:--" : d.toTimeString().slice(0, 8);
|
|
@@ -441,17 +442,25 @@ var fmtUp = (ms) => {
|
|
|
441
442
|
const p = (n) => String(n).padStart(2, "0");
|
|
442
443
|
return `${p(Math.floor(s / 3600))}:${p(Math.floor(s % 3600 / 60))}:${p(s % 60)}`;
|
|
443
444
|
};
|
|
444
|
-
function ColumnChart({ series, height, width, color
|
|
445
|
-
const
|
|
446
|
-
const data = [...new Array(
|
|
447
|
-
const
|
|
445
|
+
function ColumnChart({ series, hot, height, width, color }) {
|
|
446
|
+
const pad = Math.max(0, width - series.length);
|
|
447
|
+
const data = [...new Array(pad).fill(0), ...series.slice(-width)];
|
|
448
|
+
const hotPad = hot ? [...new Array(pad).fill(false), ...hot.slice(-width)] : void 0;
|
|
449
|
+
const max = Math.max(1, ...data);
|
|
448
450
|
const lines = [];
|
|
449
451
|
for (let r = height; r >= 1; r--) {
|
|
450
452
|
const segs = [];
|
|
451
453
|
for (let i = 0; i < data.length; i++) {
|
|
452
|
-
const
|
|
453
|
-
const
|
|
454
|
-
|
|
454
|
+
const v = data[i];
|
|
455
|
+
const frac = v / max;
|
|
456
|
+
let ch;
|
|
457
|
+
let c2;
|
|
458
|
+
if (frac >= r / height) ch = "\u2588";
|
|
459
|
+
else if (frac >= (r - 0.5) / height) ch = "\u2584";
|
|
460
|
+
else if (r === 1) ch = "\u2581";
|
|
461
|
+
else ch = " ";
|
|
462
|
+
if (r === 1 && v === 0) c2 = "#233457";
|
|
463
|
+
else c2 = hotPad?.[i] ? theme.bad : color;
|
|
455
464
|
const last = segs[segs.length - 1];
|
|
456
465
|
if (last && last.color === c2) last.text += ch;
|
|
457
466
|
else segs.push({ text: ch, color: c2 });
|
|
@@ -465,26 +474,82 @@ function ColumnChart({ series, height, width, color, limit = 0 }) {
|
|
|
465
474
|
function HBar({ label, value, max, width, color }) {
|
|
466
475
|
const filled = max > 0 ? Math.round(value / max * width) : 0;
|
|
467
476
|
return /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
468
|
-
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: label.padEnd(
|
|
477
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: label.padEnd(10) }),
|
|
469
478
|
/* @__PURE__ */ jsx2(Text2, { color, children: "\u2588".repeat(Math.min(width, filled)) }),
|
|
470
|
-
/* @__PURE__ */ jsx2(Text2, { color:
|
|
479
|
+
/* @__PURE__ */ jsx2(Text2, { color: "#233457", children: "\u2591".repeat(Math.max(0, width - filled)) }),
|
|
480
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " " + value })
|
|
471
481
|
] });
|
|
472
482
|
}
|
|
473
|
-
function
|
|
474
|
-
const
|
|
475
|
-
|
|
483
|
+
function PaneTitle({ label, extra, width }) {
|
|
484
|
+
const tail = extra ? ` ${extra} ` : " ";
|
|
485
|
+
const used = 2 + 1 + label.length + tail.length;
|
|
486
|
+
return /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
487
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "\u2500 " }),
|
|
488
|
+
/* @__PURE__ */ jsxs2(Text2, { color: theme.accentBright, bold: true, children: [
|
|
489
|
+
"\u258E",
|
|
490
|
+
label
|
|
491
|
+
] }),
|
|
492
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: tail + "\u2500".repeat(Math.max(0, width - used)) })
|
|
493
|
+
] });
|
|
476
494
|
}
|
|
477
495
|
function LivePanel({ active: active2 }) {
|
|
478
|
-
const
|
|
479
|
-
const
|
|
480
|
-
const
|
|
496
|
+
const [s, setS] = useState2(null);
|
|
497
|
+
const [tsData, setTsData] = useState2(null);
|
|
498
|
+
const [lat, setLat] = useState2([]);
|
|
499
|
+
const [buffer, setBuffer] = useState2([]);
|
|
500
|
+
const [log, setLog] = useState2([]);
|
|
501
|
+
const seenRef = useRef2(/* @__PURE__ */ new Set());
|
|
502
|
+
const pushLog = useCallback2((msg, level = "ok") => {
|
|
503
|
+
setLog((prev) => [...prev.slice(-59), { ts: Date.now(), msg, level }]);
|
|
504
|
+
}, []);
|
|
505
|
+
const doPoll = useCallback2(async () => {
|
|
506
|
+
const t0 = Date.now();
|
|
507
|
+
try {
|
|
508
|
+
const [sd, fd, td] = await Promise.all([api.stats.get(), api.audit.list({ limit: 50 }), api.stats.timeseries({ period: "24h" })]);
|
|
509
|
+
const ms = Date.now() - t0;
|
|
510
|
+
setS(sd);
|
|
511
|
+
setTsData(td);
|
|
512
|
+
setLat((l) => [...l.slice(-47), ms]);
|
|
513
|
+
const fresh = fd.entries.filter((e) => !seenRef.current.has(e.id));
|
|
514
|
+
for (const e of fresh) seenRef.current.add(e.id);
|
|
515
|
+
const firstLoad = seenRef.current.size === fresh.length && fresh.length > 1;
|
|
516
|
+
if (fresh.length) {
|
|
517
|
+
setBuffer((prev) => [...prev, ...fresh.sort((a, b) => Date.parse(a.created_at) - Date.parse(b.created_at))].slice(-400));
|
|
518
|
+
}
|
|
519
|
+
const denies = fresh.filter((e) => e.decision !== "ALLOW").length;
|
|
520
|
+
const dlp = fresh.filter((e) => e.dlp_matches?.length).length;
|
|
521
|
+
if (firstLoad) pushLog(`link up \xB7 api ${ms}ms \xB7 loaded ${fresh.length} calls`);
|
|
522
|
+
else
|
|
523
|
+
pushLog(
|
|
524
|
+
`api ${ms}ms \xB7 +${fresh.length} calls${denies ? ` \xB7 ${denies} DENIED` : ""}${dlp ? ` \xB7 ${dlp} DLP` : ""}`,
|
|
525
|
+
denies || dlp ? "bad" : fresh.length ? "warn" : "ok"
|
|
526
|
+
);
|
|
527
|
+
} catch (e) {
|
|
528
|
+
pushLog(`api error \xB7 ${truncate(e instanceof Error ? e.message : String(e), 40)}`, "bad");
|
|
529
|
+
}
|
|
530
|
+
}, [pushLog]);
|
|
531
|
+
useEffect2(() => {
|
|
532
|
+
if (!active2) return;
|
|
533
|
+
void doPoll();
|
|
534
|
+
const t = setInterval(() => void doPoll(), 2e3);
|
|
535
|
+
return () => clearInterval(t);
|
|
536
|
+
}, [active2, doPoll]);
|
|
537
|
+
const sessions = useLoader(() => api.agents.live({ limit: 30, includeDeactivated: true }));
|
|
481
538
|
const insights = useLoader(() => api.stats.securityInsights(7));
|
|
482
|
-
usePoll(
|
|
483
|
-
stats.reload();
|
|
484
|
-
ts.reload();
|
|
485
|
-
feed.reload();
|
|
486
|
-
}, 2e3, active2);
|
|
539
|
+
usePoll(sessions.reload, 5e3, active2);
|
|
487
540
|
usePoll(insights.reload, 1e4, active2);
|
|
541
|
+
useEffect2(() => {
|
|
542
|
+
const c2 = sessions.data?.counts;
|
|
543
|
+
if (c2) pushLog(`sessions ${c2.active}\u25B2 ${c2.idle}\u25CC ${c2.deactivated}\u25CB`);
|
|
544
|
+
}, [sessions.data, pushLog]);
|
|
545
|
+
useEffect2(() => {
|
|
546
|
+
const ib = insights.data;
|
|
547
|
+
if (ib?.layers) {
|
|
548
|
+
const rl2 = ib.layers.rateLimit;
|
|
549
|
+
const dl2 = ib.layers.dlp;
|
|
550
|
+
pushLog(`layers \xB7 ratelimit ${rl2?.mode ?? "?"} ${rl2?.perMinute || "\u2014"}/m \xB7 dlp ${dl2?.mode ?? "?"} ${(dl2?.patterns ?? []).length} armed`);
|
|
551
|
+
}
|
|
552
|
+
}, [insights.data, pushLog]);
|
|
488
553
|
const [tick, setTick] = useState2(0);
|
|
489
554
|
useEffect2(() => {
|
|
490
555
|
if (!active2) return;
|
|
@@ -492,107 +557,128 @@ function LivePanel({ active: active2 }) {
|
|
|
492
557
|
return () => clearInterval(t);
|
|
493
558
|
}, [active2]);
|
|
494
559
|
const startRef = useRef2(Date.now());
|
|
495
|
-
const [buffer, setBuffer] = useState2([]);
|
|
496
|
-
useEffect2(() => {
|
|
497
|
-
const incoming = feed.data?.entries;
|
|
498
|
-
if (!incoming?.length) return;
|
|
499
|
-
setBuffer((prev) => {
|
|
500
|
-
const seen = new Set(prev.map((e) => e.id));
|
|
501
|
-
const fresh = incoming.filter((e) => !seen.has(e.id)).sort((a, b) => Date.parse(a.created_at) - Date.parse(b.created_at));
|
|
502
|
-
return fresh.length ? [...prev, ...fresh].slice(-400) : prev;
|
|
503
|
-
});
|
|
504
|
-
}, [feed.data]);
|
|
505
560
|
const cols = process.stdout.columns ?? 100;
|
|
506
561
|
const rows = process.stdout.rows ?? 30;
|
|
507
|
-
const s = stats.data;
|
|
508
562
|
const ins = insights.data ?? {};
|
|
509
|
-
const minuteRaw = (ins.activity?.minute ?? []).map((b) => b.count);
|
|
510
|
-
const hourRaw = (ins.activity?.hour ?? []).map((b) => b.count);
|
|
511
|
-
const minuteLive = minuteRaw.some((c2) => c2 > 0);
|
|
512
|
-
const minuteSeries = minuteLive ? minuteRaw : hourRaw;
|
|
513
|
-
const activityLabel = minuteLive ? "calls/min \xB7 last 60m" : "calls/hour \xB7 last 24h";
|
|
514
|
-
const limit = minuteLive ? ins.layers?.rateLimit?.perMinute ?? 0 : 0;
|
|
515
|
-
const denySeries = (ts.data?.timeseries ?? []).map((p) => p.denied);
|
|
516
563
|
const spin = SPIN[tick % SPIN.length];
|
|
517
|
-
const
|
|
564
|
+
const points = tsData?.timeseries ?? [];
|
|
565
|
+
const traffic = points.map((p) => p.total);
|
|
566
|
+
const trafficHot = points.map((p) => p.denied > 0);
|
|
567
|
+
const rl = ins.layers?.rateLimit;
|
|
568
|
+
const dl = ins.layers?.dlp;
|
|
569
|
+
const minuteNow = (ins.activity?.minute ?? []).slice(-1)[0]?.count ?? 0;
|
|
570
|
+
const dlpBars = (ins.dlpByPattern ?? []).slice(0, 2);
|
|
571
|
+
const maxDlpBar = dlpBars[0]?.count ?? 1;
|
|
518
572
|
const toolCounts = /* @__PURE__ */ new Map();
|
|
519
|
-
const
|
|
520
|
-
|
|
521
|
-
toolCounts.set(e.tool_name, (toolCounts.get(e.tool_name) ?? 0) + 1);
|
|
522
|
-
const perm = (e.permission || "?").toUpperCase();
|
|
523
|
-
permCounts.set(perm, (permCounts.get(perm) ?? 0) + 1);
|
|
524
|
-
}
|
|
525
|
-
const topTools = [...toolCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 5);
|
|
526
|
-
const maxTool = topTools[0]?.[1] ?? 1;
|
|
527
|
-
const perms = ["READ", "WRITE", "EXECUTE", "NETWORK"].map((p) => [p, permCounts.get(p) ?? 0]);
|
|
528
|
-
const maxPerm = Math.max(1, ...perms.map(([, c2]) => c2));
|
|
529
|
-
const dlpTop = (ins.dlpByPattern ?? []).slice(0, 2);
|
|
573
|
+
for (const e of buffer) toolCounts.set(e.tool_name, (toolCounts.get(e.tool_name) ?? 0) + 1);
|
|
574
|
+
const topTools = [...toolCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 3);
|
|
530
575
|
const denials = buffer.filter((e) => e.decision !== "ALLOW");
|
|
531
576
|
const lastDeny = denials[denials.length - 1];
|
|
532
|
-
const
|
|
533
|
-
const
|
|
534
|
-
const
|
|
535
|
-
const
|
|
577
|
+
const latNow = lat[lat.length - 1] ?? 0;
|
|
578
|
+
const latAvg = lat.length ? Math.round(lat.reduce((a, b) => a + b, 0) / lat.length) : 0;
|
|
579
|
+
const sess = (sessions.data?.agents ?? []).slice().sort((a, b) => Date.parse(b.last_seen_at) - Date.parse(a.last_seen_at));
|
|
580
|
+
const sessCounts = sessions.data?.counts;
|
|
581
|
+
const chartH = rows >= 36 ? 6 : 4;
|
|
582
|
+
const colH = rows >= 32 ? 6 : 5;
|
|
583
|
+
const streamRows = Math.max(4, rows - 6 - chartH - colH);
|
|
536
584
|
const tail = buffer.slice(-streamRows);
|
|
537
|
-
const
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
585
|
+
const innerW = cols - 2;
|
|
586
|
+
const leftW = Math.floor(innerW * 0.55);
|
|
587
|
+
const rightW = innerW - leftW - 2;
|
|
588
|
+
const colW = Math.max(20, Math.floor((innerW - 4) / 3));
|
|
589
|
+
const sessionDot = (st) => st === "active" ? { ch: "\u25CF", color: theme.ok } : st === "idle" ? { ch: "\u25D0", color: theme.warn } : { ch: "\u25CB", color: theme.dim };
|
|
542
590
|
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingX: 1, children: [
|
|
543
591
|
/* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
544
|
-
/* @__PURE__ */ jsx2(Text2, { backgroundColor: "#1432A0", color: "#AAC8FA", bold: true, children: " SOLONGATE
|
|
545
|
-
/* @__PURE__ */
|
|
546
|
-
|
|
547
|
-
spin,
|
|
548
|
-
" up ",
|
|
549
|
-
fmtUp(Date.now() - startRef.current),
|
|
550
|
-
" \xB7 ",
|
|
551
|
-
hhmmss(Date.now()),
|
|
552
|
-
" \xB7 poll 2s"
|
|
553
|
-
] }),
|
|
554
|
-
lastDeny ? /* @__PURE__ */ jsxs2(Text2, { color: theme.bad, children: [
|
|
555
|
-
" \u26A0 ",
|
|
556
|
-
hhmmss(lastDeny.created_at),
|
|
557
|
-
" ",
|
|
558
|
-
lastDeny.tool_name,
|
|
559
|
-
" denied"
|
|
560
|
-
] }) : null,
|
|
561
|
-
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " esc menu \xB7 q quit" })
|
|
592
|
+
/* @__PURE__ */ jsx2(Text2, { backgroundColor: "#1432A0", color: "#AAC8FA", bold: true, children: " \u26E8 SOLONGATE LIVE " }),
|
|
593
|
+
/* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: ` ${spin} up ${fmtUp(Date.now() - startRef.current)} \xB7 ${hhmmss(Date.now())} \xB7 api ${latNow}ms ` }),
|
|
594
|
+
lastDeny ? /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#3d1220", color: "#ff6b6b", bold: true, children: ` \u26A0 ${hhmmss(lastDeny.created_at)} ${lastDeny.tool_name} DENIED ` }) : /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#4f6db8", children: " \u2713 clean " })
|
|
562
595
|
] }),
|
|
563
596
|
/* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
564
|
-
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "calls " }),
|
|
597
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "\u2502 calls " }),
|
|
565
598
|
/* @__PURE__ */ jsx2(Text2, { bold: true, children: s ? s.total_calls : "\xB7\xB7\xB7\xB7" }),
|
|
566
|
-
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "
|
|
599
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 allow " }),
|
|
567
600
|
/* @__PURE__ */ jsx2(Text2, { color: theme.ok, children: s ? s.allowed : "\xB7\xB7\xB7" }),
|
|
568
|
-
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "
|
|
601
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 deny " }),
|
|
569
602
|
/* @__PURE__ */ jsx2(Text2, { color: theme.bad, bold: true, children: s ? s.denied : "\xB7\xB7" }),
|
|
570
|
-
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "
|
|
571
|
-
/* @__PURE__ */ jsx2(Text2, { color:
|
|
572
|
-
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "
|
|
573
|
-
/* @__PURE__ */ jsx2(Text2, { color: theme.
|
|
574
|
-
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "
|
|
575
|
-
/* @__PURE__ */ jsx2(Text2, { color: theme.accent, children:
|
|
603
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 ratelimit " }),
|
|
604
|
+
/* @__PURE__ */ jsx2(Text2, { color: rl?.mode === "block" ? theme.ok : rl?.mode === "detect" ? theme.warn : theme.dim, children: rl?.mode ?? "?" }),
|
|
605
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 dlp " }),
|
|
606
|
+
/* @__PURE__ */ jsx2(Text2, { color: dl?.mode === "block" ? theme.ok : dl?.mode === "detect" ? theme.warn : theme.dim, children: dl?.mode ?? "?" }),
|
|
607
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 sessions " }),
|
|
608
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: sessCounts ? `${sessCounts.active}\u25B2 ${sessCounts.idle}\u25CC ${sessCounts.deactivated}\u25CB` : "\xB7" }),
|
|
609
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 policies " }),
|
|
610
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: s ? s.active_policies : "\xB7" }),
|
|
611
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502" })
|
|
612
|
+
] }),
|
|
613
|
+
/* @__PURE__ */ jsxs2(Box2, { children: [
|
|
614
|
+
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: leftW, marginRight: 2, children: [
|
|
615
|
+
/* @__PURE__ */ jsx2(PaneTitle, { label: "TRAFFIC", extra: `24h \xB7 peak ${Math.max(0, ...traffic)} \xB7 red = had denials`, width: leftW }),
|
|
616
|
+
/* @__PURE__ */ jsx2(ColumnChart, { series: traffic, hot: trafficHot, height: chartH, width: leftW, color: theme.accent })
|
|
617
|
+
] }),
|
|
618
|
+
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: rightW, children: [
|
|
619
|
+
/* @__PURE__ */ jsx2(PaneTitle, { label: "API LATENCY", extra: `live \xB7 now ${latNow}ms \xB7 avg ${latAvg}ms`, width: rightW }),
|
|
620
|
+
/* @__PURE__ */ jsx2(ColumnChart, { series: lat, hot: lat.map((v) => v > 800), height: chartH, width: rightW, color: "#82AAF0" })
|
|
621
|
+
] })
|
|
576
622
|
] }),
|
|
577
|
-
/* @__PURE__ */ jsx2(SectionTitle, { width: cols - 2, children: `ACTIVITY ${activityLabel} \xB7 peak ${Math.max(0, ...minuteSeries)}${limit ? ` \xB7 limit ${limit} (red = over)` : ""}` }),
|
|
578
|
-
minuteSeries.length ? /* @__PURE__ */ jsx2(ColumnChart, { series: minuteSeries, height: bigH, width: cols - 2, color: theme.accent, limit }) : /* @__PURE__ */ jsx2(Box2, { height: bigH, children: /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: insights.loading ? "loading activity\u2026" : "no activity data" }) }),
|
|
579
623
|
/* @__PURE__ */ jsxs2(Box2, { children: [
|
|
580
624
|
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: colW, marginRight: 2, children: [
|
|
581
|
-
/* @__PURE__ */ jsx2(
|
|
582
|
-
/* @__PURE__ */
|
|
625
|
+
/* @__PURE__ */ jsx2(PaneTitle, { label: "LAYERS", width: colW }),
|
|
626
|
+
/* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
627
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "RATELIMIT ".padEnd(10) }),
|
|
628
|
+
/* @__PURE__ */ jsx2(Text2, { color: rl?.mode === "block" ? theme.ok : rl?.mode === "detect" ? theme.warn : theme.dim, children: (rl?.mode ?? "?").padEnd(7) }),
|
|
629
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: rl?.perMinute ? `${minuteNow}/${rl.perMinute} per min` : "no limit set" })
|
|
630
|
+
] }),
|
|
631
|
+
rl?.perMinute ? /* @__PURE__ */ jsx2(HBar, { label: "load", value: minuteNow, max: rl.perMinute, width: Math.max(6, colW - 16), color: minuteNow > rl.perMinute ? theme.bad : theme.accent }) : null,
|
|
632
|
+
/* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
633
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "DLP".padEnd(10) }),
|
|
634
|
+
/* @__PURE__ */ jsx2(Text2, { color: dl?.mode === "block" ? theme.ok : dl?.mode === "detect" ? theme.warn : theme.dim, children: (dl?.mode ?? "?").padEnd(7) }),
|
|
635
|
+
/* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
|
|
636
|
+
(dl?.patterns ?? []).length,
|
|
637
|
+
" patterns armed"
|
|
638
|
+
] })
|
|
639
|
+
] }),
|
|
640
|
+
dlpBars.length ? dlpBars.map((d) => /* @__PURE__ */ jsx2(HBar, { label: truncate(d.pattern, 10), value: d.count, max: maxDlpBar, width: Math.max(6, colW - 16), color: theme.bad }, d.pattern)) : /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "hits 7d none \u2014 clean" })
|
|
583
641
|
] }),
|
|
584
642
|
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: colW, marginRight: 2, children: [
|
|
585
|
-
/* @__PURE__ */ jsx2(
|
|
586
|
-
|
|
643
|
+
/* @__PURE__ */ jsx2(PaneTitle, { label: "SESSIONS", extra: sessCounts ? `${sessCounts.active} live` : "", width: colW }),
|
|
644
|
+
sess.length === 0 ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "scanning\u2026" }) : null,
|
|
645
|
+
sess.slice(0, colH - 1).map((a) => {
|
|
646
|
+
const dot = sessionDot(a.status);
|
|
647
|
+
return /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
648
|
+
/* @__PURE__ */ jsxs2(Text2, { color: dot.color, children: [
|
|
649
|
+
dot.ch,
|
|
650
|
+
" "
|
|
651
|
+
] }),
|
|
652
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: truncate(a.agent_name ?? a.session_id, 12).padEnd(13) }),
|
|
653
|
+
/* @__PURE__ */ jsxs2(Text2, { children: [
|
|
654
|
+
String(a.total_calls).padStart(4),
|
|
655
|
+
"c "
|
|
656
|
+
] }),
|
|
657
|
+
/* @__PURE__ */ jsxs2(Text2, { color: a.denied_calls ? theme.bad : theme.dim, children: [
|
|
658
|
+
String(a.denied_calls).padStart(3),
|
|
659
|
+
"d "
|
|
660
|
+
] }),
|
|
661
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: ago(a.last_seen_at) })
|
|
662
|
+
] }, a.session_id);
|
|
663
|
+
})
|
|
587
664
|
] }),
|
|
588
665
|
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: colW, children: [
|
|
589
|
-
/* @__PURE__ */ jsx2(
|
|
590
|
-
|
|
591
|
-
|
|
666
|
+
/* @__PURE__ */ jsx2(PaneTitle, { label: "EVENT LOG", extra: "streaming", width: colW }),
|
|
667
|
+
log.slice(-(colH - 1)).map((l, i) => /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
668
|
+
/* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
|
|
669
|
+
hhmmss(l.ts),
|
|
670
|
+
" "
|
|
671
|
+
] }),
|
|
672
|
+
/* @__PURE__ */ jsx2(Text2, { color: l.level === "bad" ? theme.bad : l.level === "warn" ? theme.warn : "#4f8f6b", children: "\u25B8 " }),
|
|
673
|
+
/* @__PURE__ */ jsx2(Text2, { color: l.level === "bad" ? theme.bad : theme.dim, children: l.msg })
|
|
674
|
+
] }, l.ts + ":" + i))
|
|
592
675
|
] })
|
|
593
676
|
] }),
|
|
594
|
-
/* @__PURE__ */ jsx2(
|
|
595
|
-
tail.length === 0 ? /* @__PURE__ */
|
|
677
|
+
/* @__PURE__ */ jsx2(PaneTitle, { label: "TOOL STREAM", extra: `tail -f \xB7 ${buffer.length} buffered`, width: innerW }),
|
|
678
|
+
tail.length === 0 ? /* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
|
|
679
|
+
spin,
|
|
680
|
+
" awaiting traffic\u2026"
|
|
681
|
+
] }) : null,
|
|
596
682
|
tail.map((e) => /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
597
683
|
/* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
|
|
598
684
|
"[",
|
|
@@ -606,11 +692,9 @@ function LivePanel({ active: active2 }) {
|
|
|
606
692
|
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: (e.arguments_summary ? JSON.stringify(e.arguments_summary) : e.reason ?? "").replace(/\s+/g, " ") })
|
|
607
693
|
] }, e.id)),
|
|
608
694
|
/* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
609
|
-
/* @__PURE__ */ jsx2(Text2, { color:
|
|
610
|
-
/* @__PURE__ */ jsx2(Text2, { color:
|
|
611
|
-
/* @__PURE__ */ jsx2(Text2, { color:
|
|
612
|
-
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "$ " }),
|
|
613
|
-
/* @__PURE__ */ jsx2(Text2, { color: theme.accentBright, children: cursor })
|
|
695
|
+
/* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", bold: true, children: " \u25B2 LIVE " }),
|
|
696
|
+
/* @__PURE__ */ jsx2(Text2, { backgroundColor: "#0b1530", color: "#4f6db8", children: ` top ${topTools.map(([t, c2]) => `${t}\xD7${c2}`).join(" ") || "\u2014"} \xB7 buf ${buffer.length} ` }),
|
|
697
|
+
/* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: " esc menu \xB7 q quit " })
|
|
614
698
|
] })
|
|
615
699
|
] });
|
|
616
700
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solongate/proxy",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.66.0",
|
|
4
4
|
"description": "AI tool security proxy: protect any AI tool server with customizable policies, path/command constraints, rate limiting, and audit logging. No code changes required.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|