@solongate/proxy 0.65.0 → 0.66.1
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 +183 -85
- package/dist/tui/index.js +183 -85
- 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,17 +7054,17 @@ 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
7063
|
function PaneTitle({ label, extra, width: width2 }) {
|
|
7055
|
-
const head = `\u2500 `;
|
|
7056
7064
|
const tail = extra ? ` ${extra} ` : " ";
|
|
7057
|
-
const used =
|
|
7065
|
+
const used = 2 + 1 + label.length + tail.length;
|
|
7058
7066
|
return /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
7059
|
-
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children:
|
|
7067
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "\u2500 " }),
|
|
7060
7068
|
/* @__PURE__ */ jsxs2(Text2, { color: theme.accentBright, bold: true, children: [
|
|
7061
7069
|
"\u258E",
|
|
7062
7070
|
label
|
|
@@ -7065,18 +7073,104 @@ function PaneTitle({ label, extra, width: width2 }) {
|
|
|
7065
7073
|
] });
|
|
7066
7074
|
}
|
|
7067
7075
|
function LivePanel({ active: active2 }) {
|
|
7068
|
-
const
|
|
7069
|
-
const
|
|
7070
|
-
const
|
|
7071
|
-
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 pausedUntil = useRef2(0);
|
|
7083
|
+
const pushLog = useCallback2((msg, level = "ok") => {
|
|
7084
|
+
setLog((prev) => [...prev.slice(-59), { ts: Date.now(), msg, level }]);
|
|
7085
|
+
}, []);
|
|
7086
|
+
const onApiError = useCallback2(
|
|
7087
|
+
(e) => {
|
|
7088
|
+
const is429 = e instanceof ApiError && e.status === 429;
|
|
7089
|
+
if (is429 && Date.now() >= pausedUntil.current) {
|
|
7090
|
+
pausedUntil.current = Date.now() + 3e4;
|
|
7091
|
+
pushLog("rate limited by api \xB7 backing off 30s", "warn");
|
|
7092
|
+
} else if (!is429) {
|
|
7093
|
+
pushLog(`api error \xB7 ${truncate2(e instanceof Error ? e.message : String(e), 40)}`, "bad");
|
|
7094
|
+
}
|
|
7095
|
+
},
|
|
7096
|
+
[pushLog]
|
|
7097
|
+
);
|
|
7098
|
+
const paused = () => Date.now() < pausedUntil.current;
|
|
7099
|
+
const pollFeed = useCallback2(async () => {
|
|
7100
|
+
if (paused()) return;
|
|
7101
|
+
const t0 = Date.now();
|
|
7102
|
+
try {
|
|
7103
|
+
const fd = await api.audit.list({ limit: 50 });
|
|
7104
|
+
const ms = Date.now() - t0;
|
|
7105
|
+
setLat((l) => [...l, ms].slice(-240));
|
|
7106
|
+
const fresh = fd.entries.filter((e) => !seenRef.current.has(e.id));
|
|
7107
|
+
const firstLoad = seenRef.current.size === 0 && fresh.length > 1;
|
|
7108
|
+
for (const e of fresh) seenRef.current.add(e.id);
|
|
7109
|
+
if (fresh.length) {
|
|
7110
|
+
setBuffer((prev) => [...prev, ...fresh.sort((a, b) => Date.parse(a.created_at) - Date.parse(b.created_at))].slice(-400));
|
|
7111
|
+
}
|
|
7112
|
+
const denies = fresh.filter((e) => e.decision !== "ALLOW").length;
|
|
7113
|
+
const dlp = fresh.filter((e) => e.dlp_matches?.length).length;
|
|
7114
|
+
if (firstLoad) pushLog(`link up \xB7 api ${ms}ms \xB7 loaded ${fresh.length} calls`);
|
|
7115
|
+
else
|
|
7116
|
+
pushLog(
|
|
7117
|
+
`api ${ms}ms \xB7 +${fresh.length} calls${denies ? ` \xB7 ${denies} DENIED` : ""}${dlp ? ` \xB7 ${dlp} DLP` : ""}`,
|
|
7118
|
+
denies || dlp ? "bad" : fresh.length ? "warn" : "ok"
|
|
7119
|
+
);
|
|
7120
|
+
} catch (e) {
|
|
7121
|
+
onApiError(e);
|
|
7122
|
+
}
|
|
7123
|
+
}, [pushLog, onApiError]);
|
|
7124
|
+
const pollMedium = useCallback2(async () => {
|
|
7125
|
+
if (paused()) return;
|
|
7126
|
+
try {
|
|
7127
|
+
setS(await api.stats.get());
|
|
7128
|
+
} catch (e) {
|
|
7129
|
+
onApiError(e);
|
|
7130
|
+
}
|
|
7131
|
+
}, [onApiError]);
|
|
7132
|
+
const pollSlow = useCallback2(async () => {
|
|
7133
|
+
if (paused()) return;
|
|
7134
|
+
try {
|
|
7135
|
+
setTsData(await api.stats.timeseries({ period: "24h" }));
|
|
7136
|
+
} catch (e) {
|
|
7137
|
+
onApiError(e);
|
|
7138
|
+
}
|
|
7139
|
+
}, [onApiError]);
|
|
7140
|
+
useEffect2(() => {
|
|
7141
|
+
if (!active2) return;
|
|
7142
|
+
void pollFeed();
|
|
7143
|
+
void pollMedium();
|
|
7144
|
+
void pollSlow();
|
|
7145
|
+
const t1 = setInterval(() => void pollFeed(), 4e3);
|
|
7146
|
+
const t2 = setInterval(() => void pollMedium(), 1e4);
|
|
7147
|
+
const t3 = setInterval(() => void pollSlow(), 3e4);
|
|
7148
|
+
return () => {
|
|
7149
|
+
clearInterval(t1);
|
|
7150
|
+
clearInterval(t2);
|
|
7151
|
+
clearInterval(t3);
|
|
7152
|
+
};
|
|
7153
|
+
}, [active2, pollFeed, pollMedium, pollSlow]);
|
|
7072
7154
|
const sessions = useLoader(() => api.agents.live({ limit: 30, includeDeactivated: true }));
|
|
7155
|
+
const insights = useLoader(() => api.stats.securityInsights(7));
|
|
7073
7156
|
usePoll(() => {
|
|
7074
|
-
|
|
7075
|
-
|
|
7076
|
-
|
|
7077
|
-
|
|
7078
|
-
|
|
7079
|
-
|
|
7157
|
+
if (!paused()) sessions.reload();
|
|
7158
|
+
}, 1e4, active2);
|
|
7159
|
+
usePoll(() => {
|
|
7160
|
+
if (!paused()) insights.reload();
|
|
7161
|
+
}, 3e4, active2);
|
|
7162
|
+
useEffect2(() => {
|
|
7163
|
+
const c2 = sessions.data?.counts;
|
|
7164
|
+
if (c2) pushLog(`sessions ${c2.active}\u25B2 ${c2.idle}\u25CC ${c2.deactivated}\u25CB`);
|
|
7165
|
+
}, [sessions.data, pushLog]);
|
|
7166
|
+
useEffect2(() => {
|
|
7167
|
+
const ib = insights.data;
|
|
7168
|
+
if (ib?.layers) {
|
|
7169
|
+
const rl2 = ib.layers.rateLimit;
|
|
7170
|
+
const dl2 = ib.layers.dlp;
|
|
7171
|
+
pushLog(`layers \xB7 ratelimit ${rl2?.mode ?? "?"} ${rl2?.perMinute || "\u2014"}/m \xB7 dlp ${dl2?.mode ?? "?"} ${(dl2?.patterns ?? []).length} armed`);
|
|
7172
|
+
}
|
|
7173
|
+
}, [insights.data, pushLog]);
|
|
7080
7174
|
const [tick, setTick] = useState2(0);
|
|
7081
7175
|
useEffect2(() => {
|
|
7082
7176
|
if (!active2) return;
|
|
@@ -7084,61 +7178,43 @@ function LivePanel({ active: active2 }) {
|
|
|
7084
7178
|
return () => clearInterval(t);
|
|
7085
7179
|
}, [active2]);
|
|
7086
7180
|
const startRef = useRef2(Date.now());
|
|
7087
|
-
const [buffer, setBuffer] = useState2([]);
|
|
7088
|
-
useEffect2(() => {
|
|
7089
|
-
const incoming = feed.data?.entries;
|
|
7090
|
-
if (!incoming?.length) return;
|
|
7091
|
-
setBuffer((prev) => {
|
|
7092
|
-
const seen = new Set(prev.map((e) => e.id));
|
|
7093
|
-
const fresh = incoming.filter((e) => !seen.has(e.id)).sort((a, b) => Date.parse(a.created_at) - Date.parse(b.created_at));
|
|
7094
|
-
return fresh.length ? [...prev, ...fresh].slice(-400) : prev;
|
|
7095
|
-
});
|
|
7096
|
-
}, [feed.data]);
|
|
7097
7181
|
const cols = process.stdout.columns ?? 100;
|
|
7098
7182
|
const rows = process.stdout.rows ?? 30;
|
|
7099
|
-
const s = stats.data;
|
|
7100
7183
|
const ins = insights.data ?? {};
|
|
7101
|
-
const minuteRaw = (ins.activity?.minute ?? []).map((b) => b.count);
|
|
7102
|
-
const hourRaw = (ins.activity?.hour ?? []).map((b) => b.count);
|
|
7103
|
-
const minuteLive = minuteRaw.some((c2) => c2 > 0);
|
|
7104
|
-
const rateSeries = minuteLive ? minuteRaw : hourRaw;
|
|
7105
|
-
const rateLabel = minuteLive ? "calls/min \xB7 60m" : "calls/hour \xB7 24h";
|
|
7106
|
-
const limit = minuteLive ? ins.layers?.rateLimit?.perMinute ?? 0 : 0;
|
|
7107
|
-
const rlMode = ins.layers?.rateLimit?.mode ?? "?";
|
|
7108
|
-
const dlpMode = ins.layers?.dlp?.mode ?? "?";
|
|
7109
|
-
const denySeries = (ts.data?.timeseries ?? []).map((p) => p.denied);
|
|
7110
7184
|
const spin = SPIN[tick % SPIN.length];
|
|
7185
|
+
const points = tsData?.timeseries ?? [];
|
|
7186
|
+
const traffic = points.map((p) => p.total);
|
|
7187
|
+
const trafficHot = points.map((p) => p.denied > 0);
|
|
7188
|
+
const rl = ins.layers?.rateLimit;
|
|
7189
|
+
const dl = ins.layers?.dlp;
|
|
7190
|
+
const minuteNow = (ins.activity?.minute ?? []).slice(-1)[0]?.count ?? 0;
|
|
7191
|
+
const dlpBars = (ins.dlpByPattern ?? []).slice(0, 2);
|
|
7192
|
+
const maxDlpBar = dlpBars[0]?.count ?? 1;
|
|
7111
7193
|
const toolCounts = /* @__PURE__ */ new Map();
|
|
7112
|
-
|
|
7113
|
-
for (const e of buffer) {
|
|
7114
|
-
toolCounts.set(e.tool_name, (toolCounts.get(e.tool_name) ?? 0) + 1);
|
|
7115
|
-
if (e.dlp_matches?.length) dlpBufHits++;
|
|
7116
|
-
}
|
|
7194
|
+
for (const e of buffer) toolCounts.set(e.tool_name, (toolCounts.get(e.tool_name) ?? 0) + 1);
|
|
7117
7195
|
const topTools = [...toolCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 3);
|
|
7118
|
-
const dlpBars = (ins.dlpByPattern ?? []).slice(0, 3);
|
|
7119
|
-
const maxDlp = dlpBars[0]?.count ?? 1;
|
|
7120
|
-
const dlpRecent = (ins.dlpHits ?? []).slice(0, 2);
|
|
7121
7196
|
const denials = buffer.filter((e) => e.decision !== "ALLOW");
|
|
7122
7197
|
const lastDeny = denials[denials.length - 1];
|
|
7123
|
-
const
|
|
7198
|
+
const latNow = lat[lat.length - 1] ?? 0;
|
|
7199
|
+
const latAvg = lat.length ? Math.round(lat.reduce((a, b) => a + b, 0) / lat.length) : 0;
|
|
7200
|
+
const latHotAt = Math.max(1500, latAvg * 1.8);
|
|
7201
|
+
const backingOff = Date.now() < pausedUntil.current;
|
|
7124
7202
|
const sess = (sessions.data?.agents ?? []).slice().sort((a, b) => Date.parse(b.last_seen_at) - Date.parse(a.last_seen_at));
|
|
7125
7203
|
const sessCounts = sessions.data?.counts;
|
|
7126
7204
|
const chartH = rows >= 36 ? 6 : 4;
|
|
7127
7205
|
const colH = rows >= 32 ? 6 : 5;
|
|
7128
7206
|
const streamRows = Math.max(4, rows - 6 - chartH - colH);
|
|
7129
7207
|
const tail = buffer.slice(-streamRows);
|
|
7130
|
-
const colW = Math.max(20, Math.floor((cols - 6) / 3));
|
|
7131
7208
|
const innerW = cols - 2;
|
|
7132
|
-
|
|
7133
|
-
|
|
7134
|
-
|
|
7135
|
-
] });
|
|
7209
|
+
const leftW = Math.floor(innerW * 0.55);
|
|
7210
|
+
const rightW = innerW - leftW - 2;
|
|
7211
|
+
const colW = Math.max(20, Math.floor((innerW - 4) / 3));
|
|
7136
7212
|
const sessionDot = (st) => st === "active" ? { ch: "\u25CF", color: theme.ok } : st === "idle" ? { ch: "\u25D0", color: theme.warn } : { ch: "\u25CB", color: theme.dim };
|
|
7137
7213
|
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingX: 1, children: [
|
|
7138
7214
|
/* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
7139
7215
|
/* @__PURE__ */ jsx2(Text2, { backgroundColor: "#1432A0", color: "#AAC8FA", bold: true, children: " \u26E8 SOLONGATE LIVE " }),
|
|
7140
|
-
/* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: ` ${spin} up ${fmtUp(Date.now() - startRef.current)} \xB7 ${hhmmss(Date.now())} ` }),
|
|
7141
|
-
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
|
|
7216
|
+
/* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: ` ${spin} up ${fmtUp(Date.now() - startRef.current)} \xB7 ${hhmmss(Date.now())} \xB7 api ${latNow}ms ` }),
|
|
7217
|
+
backingOff ? /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#3d2a12", color: "#ffb454", bold: true, children: " \u23F8 RATE LIMITED \xB7 backing off " }) : 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 " })
|
|
7142
7218
|
] }),
|
|
7143
7219
|
/* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
7144
7220
|
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "\u2502 calls " }),
|
|
@@ -7147,40 +7223,48 @@ function LivePanel({ active: active2 }) {
|
|
|
7147
7223
|
/* @__PURE__ */ jsx2(Text2, { color: theme.ok, children: s ? s.allowed : "\xB7\xB7\xB7" }),
|
|
7148
7224
|
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 deny " }),
|
|
7149
7225
|
/* @__PURE__ */ jsx2(Text2, { color: theme.bad, bold: true, children: s ? s.denied : "\xB7\xB7" }),
|
|
7150
|
-
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502
|
|
7151
|
-
/* @__PURE__ */ jsx2(Text2, { color:
|
|
7152
|
-
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502
|
|
7153
|
-
/* @__PURE__ */ jsx2(Text2, { color:
|
|
7226
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 ratelimit " }),
|
|
7227
|
+
/* @__PURE__ */ jsx2(Text2, { color: rl?.mode === "block" ? theme.ok : rl?.mode === "detect" ? theme.warn : theme.dim, children: rl?.mode ?? "?" }),
|
|
7228
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 dlp " }),
|
|
7229
|
+
/* @__PURE__ */ jsx2(Text2, { color: dl?.mode === "block" ? theme.ok : dl?.mode === "detect" ? theme.warn : theme.dim, children: dl?.mode ?? "?" }),
|
|
7154
7230
|
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 sessions " }),
|
|
7155
|
-
/* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: sessCounts ? `${sessCounts.active}\u25B2 ${sessCounts.idle}\u25CC` : "\xB7" }),
|
|
7231
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: sessCounts ? `${sessCounts.active}\u25B2 ${sessCounts.idle}\u25CC ${sessCounts.deactivated}\u25CB` : "\xB7" }),
|
|
7156
7232
|
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 policies " }),
|
|
7157
7233
|
/* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: s ? s.active_policies : "\xB7" }),
|
|
7158
7234
|
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502" })
|
|
7159
7235
|
] }),
|
|
7160
|
-
/* @__PURE__ */ jsx2(PaneTitle, { label: "RATE LIMIT", extra: `${rateLabel} \xB7 peak ${Math.max(0, ...rateSeries)} \xB7 limit ${limit || "off"} \xB7 mode ${rlMode}${limit ? " \xB7 red = over" : ""}`, width: innerW }),
|
|
7161
|
-
rateSeries.length ? /* @__PURE__ */ jsx2(ColumnChart, { series: rateSeries, height: chartH, width: innerW, color: theme.accent, limit }) : /* @__PURE__ */ jsx2(Box2, { height: chartH, children: /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: insights.loading ? `${spin} loading activity\u2026` : "no activity data" }) }),
|
|
7162
7236
|
/* @__PURE__ */ jsxs2(Box2, { children: [
|
|
7163
|
-
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width:
|
|
7164
|
-
/* @__PURE__ */ jsx2(PaneTitle, { label: "
|
|
7165
|
-
/* @__PURE__ */ jsx2(ColumnChart, { series:
|
|
7237
|
+
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: leftW, marginRight: 2, children: [
|
|
7238
|
+
/* @__PURE__ */ jsx2(PaneTitle, { label: "TRAFFIC", extra: `24h \xB7 peak ${Math.max(0, ...traffic)} \xB7 red = had denials`, width: leftW }),
|
|
7239
|
+
/* @__PURE__ */ jsx2(ColumnChart, { series: traffic, hot: trafficHot, height: chartH, width: leftW, color: theme.accent })
|
|
7166
7240
|
] }),
|
|
7241
|
+
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: rightW, children: [
|
|
7242
|
+
/* @__PURE__ */ jsx2(PaneTitle, { label: "API LATENCY", extra: `live \xB7 now ${latNow}ms \xB7 avg ${latAvg}ms \xB7 red >${Math.round(latHotAt)}ms`, width: rightW }),
|
|
7243
|
+
/* @__PURE__ */ jsx2(ColumnChart, { series: lat, hot: lat.map((v) => v > latHotAt), height: chartH, width: rightW, color: "#82AAF0" })
|
|
7244
|
+
] })
|
|
7245
|
+
] }),
|
|
7246
|
+
/* @__PURE__ */ jsxs2(Box2, { children: [
|
|
7167
7247
|
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: colW, marginRight: 2, children: [
|
|
7168
|
-
/* @__PURE__ */ jsx2(PaneTitle, { label: "
|
|
7169
|
-
|
|
7170
|
-
|
|
7171
|
-
/* @__PURE__ */ jsx2(Text2, { color:
|
|
7248
|
+
/* @__PURE__ */ jsx2(PaneTitle, { label: "LAYERS", width: colW }),
|
|
7249
|
+
/* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
7250
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "RATELIMIT ".padEnd(10) }),
|
|
7251
|
+
/* @__PURE__ */ jsx2(Text2, { color: rl?.mode === "block" ? theme.ok : rl?.mode === "detect" ? theme.warn : theme.dim, children: (rl?.mode ?? "?").padEnd(7) }),
|
|
7252
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: rl?.perMinute ? `${minuteNow}/${rl.perMinute} per min` : "no limit set" })
|
|
7253
|
+
] }),
|
|
7254
|
+
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,
|
|
7255
|
+
/* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
7256
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "DLP".padEnd(10) }),
|
|
7257
|
+
/* @__PURE__ */ jsx2(Text2, { color: dl?.mode === "block" ? theme.ok : dl?.mode === "detect" ? theme.warn : theme.dim, children: (dl?.mode ?? "?").padEnd(7) }),
|
|
7172
7258
|
/* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
|
|
7173
|
-
|
|
7174
|
-
|
|
7175
|
-
" ",
|
|
7176
|
-
ago(h.at),
|
|
7177
|
-
" ago"
|
|
7259
|
+
(dl?.patterns ?? []).length,
|
|
7260
|
+
" patterns armed"
|
|
7178
7261
|
] })
|
|
7179
|
-
] },
|
|
7262
|
+
] }),
|
|
7263
|
+
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" })
|
|
7180
7264
|
] }),
|
|
7181
|
-
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: colW, children: [
|
|
7182
|
-
/* @__PURE__ */ jsx2(PaneTitle, { label: "SESSIONS", extra: sessCounts ? `${sessCounts.active}
|
|
7183
|
-
sess.length === 0 ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "
|
|
7265
|
+
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: colW, marginRight: 2, children: [
|
|
7266
|
+
/* @__PURE__ */ jsx2(PaneTitle, { label: "SESSIONS", extra: sessCounts ? `${sessCounts.active} live` : "", width: colW }),
|
|
7267
|
+
sess.length === 0 ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "scanning\u2026" }) : null,
|
|
7184
7268
|
sess.slice(0, colH - 1).map((a) => {
|
|
7185
7269
|
const dot = sessionDot(a.status);
|
|
7186
7270
|
return /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
@@ -7200,10 +7284,24 @@ function LivePanel({ active: active2 }) {
|
|
|
7200
7284
|
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: ago(a.last_seen_at) })
|
|
7201
7285
|
] }, a.session_id);
|
|
7202
7286
|
})
|
|
7287
|
+
] }),
|
|
7288
|
+
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: colW, children: [
|
|
7289
|
+
/* @__PURE__ */ jsx2(PaneTitle, { label: "EVENT LOG", extra: "streaming", width: colW }),
|
|
7290
|
+
log6.slice(-(colH - 1)).map((l, i) => /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
7291
|
+
/* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
|
|
7292
|
+
hhmmss(l.ts),
|
|
7293
|
+
" "
|
|
7294
|
+
] }),
|
|
7295
|
+
/* @__PURE__ */ jsx2(Text2, { color: l.level === "bad" ? theme.bad : l.level === "warn" ? theme.warn : "#4f8f6b", children: "\u25B8 " }),
|
|
7296
|
+
/* @__PURE__ */ jsx2(Text2, { color: l.level === "bad" ? theme.bad : theme.dim, children: l.msg })
|
|
7297
|
+
] }, l.ts + ":" + i))
|
|
7203
7298
|
] })
|
|
7204
7299
|
] }),
|
|
7205
|
-
/* @__PURE__ */ jsx2(PaneTitle, { label: "TOOL STREAM", extra: `
|
|
7206
|
-
tail.length === 0 ? /* @__PURE__ */
|
|
7300
|
+
/* @__PURE__ */ jsx2(PaneTitle, { label: "TOOL STREAM", extra: `tail -f \xB7 ${buffer.length} buffered`, width: innerW }),
|
|
7301
|
+
tail.length === 0 ? /* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
|
|
7302
|
+
spin,
|
|
7303
|
+
" awaiting traffic\u2026"
|
|
7304
|
+
] }) : null,
|
|
7207
7305
|
tail.map((e) => /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
7208
7306
|
/* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
|
|
7209
7307
|
"[",
|
|
@@ -7218,7 +7316,7 @@ function LivePanel({ active: active2 }) {
|
|
|
7218
7316
|
] }, e.id)),
|
|
7219
7317
|
/* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
7220
7318
|
/* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", bold: true, children: " \u25B2 LIVE " }),
|
|
7221
|
-
/* @__PURE__ */ jsx2(Text2, { backgroundColor: "#0b1530", color: "#4f6db8", children: ` top ${topTools.map(([t, c2]) => `${t}\xD7${c2}`).join(" ") || "\u2014"} ` }),
|
|
7319
|
+
/* @__PURE__ */ jsx2(Text2, { backgroundColor: "#0b1530", color: "#4f6db8", children: ` top ${topTools.map(([t, c2]) => `${t}\xD7${c2}`).join(" ") || "\u2014"} \xB7 buf ${buffer.length} ` }),
|
|
7222
7320
|
/* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: " esc menu \xB7 q quit " })
|
|
7223
7321
|
] })
|
|
7224
7322
|
] });
|
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";
|
|
@@ -442,17 +442,25 @@ var fmtUp = (ms) => {
|
|
|
442
442
|
const p = (n) => String(n).padStart(2, "0");
|
|
443
443
|
return `${p(Math.floor(s / 3600))}:${p(Math.floor(s % 3600 / 60))}:${p(s % 60)}`;
|
|
444
444
|
};
|
|
445
|
-
function ColumnChart({ series, height, width, color
|
|
446
|
-
const
|
|
447
|
-
const data = [...new Array(
|
|
448
|
-
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);
|
|
449
450
|
const lines = [];
|
|
450
451
|
for (let r = height; r >= 1; r--) {
|
|
451
452
|
const segs = [];
|
|
452
453
|
for (let i = 0; i < data.length; i++) {
|
|
453
|
-
const
|
|
454
|
-
const
|
|
455
|
-
|
|
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;
|
|
456
464
|
const last = segs[segs.length - 1];
|
|
457
465
|
if (last && last.color === c2) last.text += ch;
|
|
458
466
|
else segs.push({ text: ch, color: c2 });
|
|
@@ -466,17 +474,17 @@ function ColumnChart({ series, height, width, color, limit = 0 }) {
|
|
|
466
474
|
function HBar({ label, value, max, width, color }) {
|
|
467
475
|
const filled = max > 0 ? Math.round(value / max * width) : 0;
|
|
468
476
|
return /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
469
|
-
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: label.padEnd(
|
|
477
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: label.padEnd(10) }),
|
|
470
478
|
/* @__PURE__ */ jsx2(Text2, { color, children: "\u2588".repeat(Math.min(width, filled)) }),
|
|
471
|
-
/* @__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 })
|
|
472
481
|
] });
|
|
473
482
|
}
|
|
474
483
|
function PaneTitle({ label, extra, width }) {
|
|
475
|
-
const head = `\u2500 `;
|
|
476
484
|
const tail = extra ? ` ${extra} ` : " ";
|
|
477
|
-
const used =
|
|
485
|
+
const used = 2 + 1 + label.length + tail.length;
|
|
478
486
|
return /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
479
|
-
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children:
|
|
487
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "\u2500 " }),
|
|
480
488
|
/* @__PURE__ */ jsxs2(Text2, { color: theme.accentBright, bold: true, children: [
|
|
481
489
|
"\u258E",
|
|
482
490
|
label
|
|
@@ -485,18 +493,104 @@ function PaneTitle({ label, extra, width }) {
|
|
|
485
493
|
] });
|
|
486
494
|
}
|
|
487
495
|
function LivePanel({ active: active2 }) {
|
|
488
|
-
const
|
|
489
|
-
const
|
|
490
|
-
const
|
|
491
|
-
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 pausedUntil = useRef2(0);
|
|
503
|
+
const pushLog = useCallback2((msg, level = "ok") => {
|
|
504
|
+
setLog((prev) => [...prev.slice(-59), { ts: Date.now(), msg, level }]);
|
|
505
|
+
}, []);
|
|
506
|
+
const onApiError = useCallback2(
|
|
507
|
+
(e) => {
|
|
508
|
+
const is429 = e instanceof ApiError && e.status === 429;
|
|
509
|
+
if (is429 && Date.now() >= pausedUntil.current) {
|
|
510
|
+
pausedUntil.current = Date.now() + 3e4;
|
|
511
|
+
pushLog("rate limited by api \xB7 backing off 30s", "warn");
|
|
512
|
+
} else if (!is429) {
|
|
513
|
+
pushLog(`api error \xB7 ${truncate(e instanceof Error ? e.message : String(e), 40)}`, "bad");
|
|
514
|
+
}
|
|
515
|
+
},
|
|
516
|
+
[pushLog]
|
|
517
|
+
);
|
|
518
|
+
const paused = () => Date.now() < pausedUntil.current;
|
|
519
|
+
const pollFeed = useCallback2(async () => {
|
|
520
|
+
if (paused()) return;
|
|
521
|
+
const t0 = Date.now();
|
|
522
|
+
try {
|
|
523
|
+
const fd = await api.audit.list({ limit: 50 });
|
|
524
|
+
const ms = Date.now() - t0;
|
|
525
|
+
setLat((l) => [...l, ms].slice(-240));
|
|
526
|
+
const fresh = fd.entries.filter((e) => !seenRef.current.has(e.id));
|
|
527
|
+
const firstLoad = seenRef.current.size === 0 && fresh.length > 1;
|
|
528
|
+
for (const e of fresh) seenRef.current.add(e.id);
|
|
529
|
+
if (fresh.length) {
|
|
530
|
+
setBuffer((prev) => [...prev, ...fresh.sort((a, b) => Date.parse(a.created_at) - Date.parse(b.created_at))].slice(-400));
|
|
531
|
+
}
|
|
532
|
+
const denies = fresh.filter((e) => e.decision !== "ALLOW").length;
|
|
533
|
+
const dlp = fresh.filter((e) => e.dlp_matches?.length).length;
|
|
534
|
+
if (firstLoad) pushLog(`link up \xB7 api ${ms}ms \xB7 loaded ${fresh.length} calls`);
|
|
535
|
+
else
|
|
536
|
+
pushLog(
|
|
537
|
+
`api ${ms}ms \xB7 +${fresh.length} calls${denies ? ` \xB7 ${denies} DENIED` : ""}${dlp ? ` \xB7 ${dlp} DLP` : ""}`,
|
|
538
|
+
denies || dlp ? "bad" : fresh.length ? "warn" : "ok"
|
|
539
|
+
);
|
|
540
|
+
} catch (e) {
|
|
541
|
+
onApiError(e);
|
|
542
|
+
}
|
|
543
|
+
}, [pushLog, onApiError]);
|
|
544
|
+
const pollMedium = useCallback2(async () => {
|
|
545
|
+
if (paused()) return;
|
|
546
|
+
try {
|
|
547
|
+
setS(await api.stats.get());
|
|
548
|
+
} catch (e) {
|
|
549
|
+
onApiError(e);
|
|
550
|
+
}
|
|
551
|
+
}, [onApiError]);
|
|
552
|
+
const pollSlow = useCallback2(async () => {
|
|
553
|
+
if (paused()) return;
|
|
554
|
+
try {
|
|
555
|
+
setTsData(await api.stats.timeseries({ period: "24h" }));
|
|
556
|
+
} catch (e) {
|
|
557
|
+
onApiError(e);
|
|
558
|
+
}
|
|
559
|
+
}, [onApiError]);
|
|
560
|
+
useEffect2(() => {
|
|
561
|
+
if (!active2) return;
|
|
562
|
+
void pollFeed();
|
|
563
|
+
void pollMedium();
|
|
564
|
+
void pollSlow();
|
|
565
|
+
const t1 = setInterval(() => void pollFeed(), 4e3);
|
|
566
|
+
const t2 = setInterval(() => void pollMedium(), 1e4);
|
|
567
|
+
const t3 = setInterval(() => void pollSlow(), 3e4);
|
|
568
|
+
return () => {
|
|
569
|
+
clearInterval(t1);
|
|
570
|
+
clearInterval(t2);
|
|
571
|
+
clearInterval(t3);
|
|
572
|
+
};
|
|
573
|
+
}, [active2, pollFeed, pollMedium, pollSlow]);
|
|
492
574
|
const sessions = useLoader(() => api.agents.live({ limit: 30, includeDeactivated: true }));
|
|
575
|
+
const insights = useLoader(() => api.stats.securityInsights(7));
|
|
493
576
|
usePoll(() => {
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
577
|
+
if (!paused()) sessions.reload();
|
|
578
|
+
}, 1e4, active2);
|
|
579
|
+
usePoll(() => {
|
|
580
|
+
if (!paused()) insights.reload();
|
|
581
|
+
}, 3e4, active2);
|
|
582
|
+
useEffect2(() => {
|
|
583
|
+
const c2 = sessions.data?.counts;
|
|
584
|
+
if (c2) pushLog(`sessions ${c2.active}\u25B2 ${c2.idle}\u25CC ${c2.deactivated}\u25CB`);
|
|
585
|
+
}, [sessions.data, pushLog]);
|
|
586
|
+
useEffect2(() => {
|
|
587
|
+
const ib = insights.data;
|
|
588
|
+
if (ib?.layers) {
|
|
589
|
+
const rl2 = ib.layers.rateLimit;
|
|
590
|
+
const dl2 = ib.layers.dlp;
|
|
591
|
+
pushLog(`layers \xB7 ratelimit ${rl2?.mode ?? "?"} ${rl2?.perMinute || "\u2014"}/m \xB7 dlp ${dl2?.mode ?? "?"} ${(dl2?.patterns ?? []).length} armed`);
|
|
592
|
+
}
|
|
593
|
+
}, [insights.data, pushLog]);
|
|
500
594
|
const [tick, setTick] = useState2(0);
|
|
501
595
|
useEffect2(() => {
|
|
502
596
|
if (!active2) return;
|
|
@@ -504,61 +598,43 @@ function LivePanel({ active: active2 }) {
|
|
|
504
598
|
return () => clearInterval(t);
|
|
505
599
|
}, [active2]);
|
|
506
600
|
const startRef = useRef2(Date.now());
|
|
507
|
-
const [buffer, setBuffer] = useState2([]);
|
|
508
|
-
useEffect2(() => {
|
|
509
|
-
const incoming = feed.data?.entries;
|
|
510
|
-
if (!incoming?.length) return;
|
|
511
|
-
setBuffer((prev) => {
|
|
512
|
-
const seen = new Set(prev.map((e) => e.id));
|
|
513
|
-
const fresh = incoming.filter((e) => !seen.has(e.id)).sort((a, b) => Date.parse(a.created_at) - Date.parse(b.created_at));
|
|
514
|
-
return fresh.length ? [...prev, ...fresh].slice(-400) : prev;
|
|
515
|
-
});
|
|
516
|
-
}, [feed.data]);
|
|
517
601
|
const cols = process.stdout.columns ?? 100;
|
|
518
602
|
const rows = process.stdout.rows ?? 30;
|
|
519
|
-
const s = stats.data;
|
|
520
603
|
const ins = insights.data ?? {};
|
|
521
|
-
const minuteRaw = (ins.activity?.minute ?? []).map((b) => b.count);
|
|
522
|
-
const hourRaw = (ins.activity?.hour ?? []).map((b) => b.count);
|
|
523
|
-
const minuteLive = minuteRaw.some((c2) => c2 > 0);
|
|
524
|
-
const rateSeries = minuteLive ? minuteRaw : hourRaw;
|
|
525
|
-
const rateLabel = minuteLive ? "calls/min \xB7 60m" : "calls/hour \xB7 24h";
|
|
526
|
-
const limit = minuteLive ? ins.layers?.rateLimit?.perMinute ?? 0 : 0;
|
|
527
|
-
const rlMode = ins.layers?.rateLimit?.mode ?? "?";
|
|
528
|
-
const dlpMode = ins.layers?.dlp?.mode ?? "?";
|
|
529
|
-
const denySeries = (ts.data?.timeseries ?? []).map((p) => p.denied);
|
|
530
604
|
const spin = SPIN[tick % SPIN.length];
|
|
605
|
+
const points = tsData?.timeseries ?? [];
|
|
606
|
+
const traffic = points.map((p) => p.total);
|
|
607
|
+
const trafficHot = points.map((p) => p.denied > 0);
|
|
608
|
+
const rl = ins.layers?.rateLimit;
|
|
609
|
+
const dl = ins.layers?.dlp;
|
|
610
|
+
const minuteNow = (ins.activity?.minute ?? []).slice(-1)[0]?.count ?? 0;
|
|
611
|
+
const dlpBars = (ins.dlpByPattern ?? []).slice(0, 2);
|
|
612
|
+
const maxDlpBar = dlpBars[0]?.count ?? 1;
|
|
531
613
|
const toolCounts = /* @__PURE__ */ new Map();
|
|
532
|
-
|
|
533
|
-
for (const e of buffer) {
|
|
534
|
-
toolCounts.set(e.tool_name, (toolCounts.get(e.tool_name) ?? 0) + 1);
|
|
535
|
-
if (e.dlp_matches?.length) dlpBufHits++;
|
|
536
|
-
}
|
|
614
|
+
for (const e of buffer) toolCounts.set(e.tool_name, (toolCounts.get(e.tool_name) ?? 0) + 1);
|
|
537
615
|
const topTools = [...toolCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 3);
|
|
538
|
-
const dlpBars = (ins.dlpByPattern ?? []).slice(0, 3);
|
|
539
|
-
const maxDlp = dlpBars[0]?.count ?? 1;
|
|
540
|
-
const dlpRecent = (ins.dlpHits ?? []).slice(0, 2);
|
|
541
616
|
const denials = buffer.filter((e) => e.decision !== "ALLOW");
|
|
542
617
|
const lastDeny = denials[denials.length - 1];
|
|
543
|
-
const
|
|
618
|
+
const latNow = lat[lat.length - 1] ?? 0;
|
|
619
|
+
const latAvg = lat.length ? Math.round(lat.reduce((a, b) => a + b, 0) / lat.length) : 0;
|
|
620
|
+
const latHotAt = Math.max(1500, latAvg * 1.8);
|
|
621
|
+
const backingOff = Date.now() < pausedUntil.current;
|
|
544
622
|
const sess = (sessions.data?.agents ?? []).slice().sort((a, b) => Date.parse(b.last_seen_at) - Date.parse(a.last_seen_at));
|
|
545
623
|
const sessCounts = sessions.data?.counts;
|
|
546
624
|
const chartH = rows >= 36 ? 6 : 4;
|
|
547
625
|
const colH = rows >= 32 ? 6 : 5;
|
|
548
626
|
const streamRows = Math.max(4, rows - 6 - chartH - colH);
|
|
549
627
|
const tail = buffer.slice(-streamRows);
|
|
550
|
-
const colW = Math.max(20, Math.floor((cols - 6) / 3));
|
|
551
628
|
const innerW = cols - 2;
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
] });
|
|
629
|
+
const leftW = Math.floor(innerW * 0.55);
|
|
630
|
+
const rightW = innerW - leftW - 2;
|
|
631
|
+
const colW = Math.max(20, Math.floor((innerW - 4) / 3));
|
|
556
632
|
const sessionDot = (st) => st === "active" ? { ch: "\u25CF", color: theme.ok } : st === "idle" ? { ch: "\u25D0", color: theme.warn } : { ch: "\u25CB", color: theme.dim };
|
|
557
633
|
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingX: 1, children: [
|
|
558
634
|
/* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
559
635
|
/* @__PURE__ */ jsx2(Text2, { backgroundColor: "#1432A0", color: "#AAC8FA", bold: true, children: " \u26E8 SOLONGATE LIVE " }),
|
|
560
|
-
/* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: ` ${spin} up ${fmtUp(Date.now() - startRef.current)} \xB7 ${hhmmss(Date.now())} ` }),
|
|
561
|
-
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
|
|
636
|
+
/* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: ` ${spin} up ${fmtUp(Date.now() - startRef.current)} \xB7 ${hhmmss(Date.now())} \xB7 api ${latNow}ms ` }),
|
|
637
|
+
backingOff ? /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#3d2a12", color: "#ffb454", bold: true, children: " \u23F8 RATE LIMITED \xB7 backing off " }) : 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
638
|
] }),
|
|
563
639
|
/* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
564
640
|
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "\u2502 calls " }),
|
|
@@ -567,40 +643,48 @@ function LivePanel({ active: active2 }) {
|
|
|
567
643
|
/* @__PURE__ */ jsx2(Text2, { color: theme.ok, children: s ? s.allowed : "\xB7\xB7\xB7" }),
|
|
568
644
|
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 deny " }),
|
|
569
645
|
/* @__PURE__ */ jsx2(Text2, { color: theme.bad, bold: true, children: s ? s.denied : "\xB7\xB7" }),
|
|
570
|
-
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502
|
|
571
|
-
/* @__PURE__ */ jsx2(Text2, { color:
|
|
572
|
-
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502
|
|
573
|
-
/* @__PURE__ */ jsx2(Text2, { color:
|
|
646
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 ratelimit " }),
|
|
647
|
+
/* @__PURE__ */ jsx2(Text2, { color: rl?.mode === "block" ? theme.ok : rl?.mode === "detect" ? theme.warn : theme.dim, children: rl?.mode ?? "?" }),
|
|
648
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 dlp " }),
|
|
649
|
+
/* @__PURE__ */ jsx2(Text2, { color: dl?.mode === "block" ? theme.ok : dl?.mode === "detect" ? theme.warn : theme.dim, children: dl?.mode ?? "?" }),
|
|
574
650
|
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 sessions " }),
|
|
575
|
-
/* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: sessCounts ? `${sessCounts.active}\u25B2 ${sessCounts.idle}\u25CC` : "\xB7" }),
|
|
651
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: sessCounts ? `${sessCounts.active}\u25B2 ${sessCounts.idle}\u25CC ${sessCounts.deactivated}\u25CB` : "\xB7" }),
|
|
576
652
|
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 policies " }),
|
|
577
653
|
/* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: s ? s.active_policies : "\xB7" }),
|
|
578
654
|
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502" })
|
|
579
655
|
] }),
|
|
580
|
-
/* @__PURE__ */ jsx2(PaneTitle, { label: "RATE LIMIT", extra: `${rateLabel} \xB7 peak ${Math.max(0, ...rateSeries)} \xB7 limit ${limit || "off"} \xB7 mode ${rlMode}${limit ? " \xB7 red = over" : ""}`, width: innerW }),
|
|
581
|
-
rateSeries.length ? /* @__PURE__ */ jsx2(ColumnChart, { series: rateSeries, height: chartH, width: innerW, color: theme.accent, limit }) : /* @__PURE__ */ jsx2(Box2, { height: chartH, children: /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: insights.loading ? `${spin} loading activity\u2026` : "no activity data" }) }),
|
|
582
656
|
/* @__PURE__ */ jsxs2(Box2, { children: [
|
|
583
|
-
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width:
|
|
584
|
-
/* @__PURE__ */ jsx2(PaneTitle, { label: "
|
|
585
|
-
/* @__PURE__ */ jsx2(ColumnChart, { series:
|
|
657
|
+
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: leftW, marginRight: 2, children: [
|
|
658
|
+
/* @__PURE__ */ jsx2(PaneTitle, { label: "TRAFFIC", extra: `24h \xB7 peak ${Math.max(0, ...traffic)} \xB7 red = had denials`, width: leftW }),
|
|
659
|
+
/* @__PURE__ */ jsx2(ColumnChart, { series: traffic, hot: trafficHot, height: chartH, width: leftW, color: theme.accent })
|
|
586
660
|
] }),
|
|
661
|
+
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: rightW, children: [
|
|
662
|
+
/* @__PURE__ */ jsx2(PaneTitle, { label: "API LATENCY", extra: `live \xB7 now ${latNow}ms \xB7 avg ${latAvg}ms \xB7 red >${Math.round(latHotAt)}ms`, width: rightW }),
|
|
663
|
+
/* @__PURE__ */ jsx2(ColumnChart, { series: lat, hot: lat.map((v) => v > latHotAt), height: chartH, width: rightW, color: "#82AAF0" })
|
|
664
|
+
] })
|
|
665
|
+
] }),
|
|
666
|
+
/* @__PURE__ */ jsxs2(Box2, { children: [
|
|
587
667
|
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: colW, marginRight: 2, children: [
|
|
588
|
-
/* @__PURE__ */ jsx2(PaneTitle, { label: "
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
/* @__PURE__ */ jsx2(Text2, { color:
|
|
668
|
+
/* @__PURE__ */ jsx2(PaneTitle, { label: "LAYERS", width: colW }),
|
|
669
|
+
/* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
670
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "RATELIMIT ".padEnd(10) }),
|
|
671
|
+
/* @__PURE__ */ jsx2(Text2, { color: rl?.mode === "block" ? theme.ok : rl?.mode === "detect" ? theme.warn : theme.dim, children: (rl?.mode ?? "?").padEnd(7) }),
|
|
672
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: rl?.perMinute ? `${minuteNow}/${rl.perMinute} per min` : "no limit set" })
|
|
673
|
+
] }),
|
|
674
|
+
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,
|
|
675
|
+
/* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
676
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "DLP".padEnd(10) }),
|
|
677
|
+
/* @__PURE__ */ jsx2(Text2, { color: dl?.mode === "block" ? theme.ok : dl?.mode === "detect" ? theme.warn : theme.dim, children: (dl?.mode ?? "?").padEnd(7) }),
|
|
592
678
|
/* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
" ",
|
|
596
|
-
ago(h.at),
|
|
597
|
-
" ago"
|
|
679
|
+
(dl?.patterns ?? []).length,
|
|
680
|
+
" patterns armed"
|
|
598
681
|
] })
|
|
599
|
-
] },
|
|
682
|
+
] }),
|
|
683
|
+
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" })
|
|
600
684
|
] }),
|
|
601
|
-
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: colW, children: [
|
|
602
|
-
/* @__PURE__ */ jsx2(PaneTitle, { label: "SESSIONS", extra: sessCounts ? `${sessCounts.active}
|
|
603
|
-
sess.length === 0 ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "
|
|
685
|
+
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: colW, marginRight: 2, children: [
|
|
686
|
+
/* @__PURE__ */ jsx2(PaneTitle, { label: "SESSIONS", extra: sessCounts ? `${sessCounts.active} live` : "", width: colW }),
|
|
687
|
+
sess.length === 0 ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "scanning\u2026" }) : null,
|
|
604
688
|
sess.slice(0, colH - 1).map((a) => {
|
|
605
689
|
const dot = sessionDot(a.status);
|
|
606
690
|
return /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
@@ -620,10 +704,24 @@ function LivePanel({ active: active2 }) {
|
|
|
620
704
|
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: ago(a.last_seen_at) })
|
|
621
705
|
] }, a.session_id);
|
|
622
706
|
})
|
|
707
|
+
] }),
|
|
708
|
+
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: colW, children: [
|
|
709
|
+
/* @__PURE__ */ jsx2(PaneTitle, { label: "EVENT LOG", extra: "streaming", width: colW }),
|
|
710
|
+
log.slice(-(colH - 1)).map((l, i) => /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
711
|
+
/* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
|
|
712
|
+
hhmmss(l.ts),
|
|
713
|
+
" "
|
|
714
|
+
] }),
|
|
715
|
+
/* @__PURE__ */ jsx2(Text2, { color: l.level === "bad" ? theme.bad : l.level === "warn" ? theme.warn : "#4f8f6b", children: "\u25B8 " }),
|
|
716
|
+
/* @__PURE__ */ jsx2(Text2, { color: l.level === "bad" ? theme.bad : theme.dim, children: l.msg })
|
|
717
|
+
] }, l.ts + ":" + i))
|
|
623
718
|
] })
|
|
624
719
|
] }),
|
|
625
|
-
/* @__PURE__ */ jsx2(PaneTitle, { label: "TOOL STREAM", extra: `
|
|
626
|
-
tail.length === 0 ? /* @__PURE__ */
|
|
720
|
+
/* @__PURE__ */ jsx2(PaneTitle, { label: "TOOL STREAM", extra: `tail -f \xB7 ${buffer.length} buffered`, width: innerW }),
|
|
721
|
+
tail.length === 0 ? /* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
|
|
722
|
+
spin,
|
|
723
|
+
" awaiting traffic\u2026"
|
|
724
|
+
] }) : null,
|
|
627
725
|
tail.map((e) => /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
628
726
|
/* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
|
|
629
727
|
"[",
|
|
@@ -638,7 +736,7 @@ function LivePanel({ active: active2 }) {
|
|
|
638
736
|
] }, e.id)),
|
|
639
737
|
/* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
640
738
|
/* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", bold: true, children: " \u25B2 LIVE " }),
|
|
641
|
-
/* @__PURE__ */ jsx2(Text2, { backgroundColor: "#0b1530", color: "#4f6db8", children: ` top ${topTools.map(([t, c2]) => `${t}\xD7${c2}`).join(" ") || "\u2014"} ` }),
|
|
739
|
+
/* @__PURE__ */ jsx2(Text2, { backgroundColor: "#0b1530", color: "#4f6db8", children: ` top ${topTools.map(([t, c2]) => `${t}\xD7${c2}`).join(" ") || "\u2014"} \xB7 buf ${buffer.length} ` }),
|
|
642
740
|
/* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: " esc menu \xB7 q quit " })
|
|
643
741
|
] })
|
|
644
742
|
] });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solongate/proxy",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.66.1",
|
|
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": {
|