@solongate/proxy 0.73.0 → 0.75.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 +184 -187
- package/dist/tui/index.js +183 -186
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -7030,11 +7030,12 @@ var init_hooks = __esm({
|
|
|
7030
7030
|
|
|
7031
7031
|
// src/tui/panels/Live.tsx
|
|
7032
7032
|
import { Box as Box2, Text as Text2, useInput } from "ink";
|
|
7033
|
+
import TextInput from "ink-text-input";
|
|
7033
7034
|
import { closeSync, openSync, readSync, statSync } from "fs";
|
|
7034
7035
|
import { homedir as homedir3 } from "os";
|
|
7035
7036
|
import { join as join5 } from "path";
|
|
7036
7037
|
import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
|
|
7037
|
-
import {
|
|
7038
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
7038
7039
|
function tailLines(file, maxBytes = 131072) {
|
|
7039
7040
|
try {
|
|
7040
7041
|
const size = statSync(file).size;
|
|
@@ -7059,7 +7060,6 @@ function cloudItem(e) {
|
|
|
7059
7060
|
permission: (e.permission ?? "").slice(0, 4),
|
|
7060
7061
|
detail: (e.arguments_summary ? JSON.stringify(e.arguments_summary) : e.reason ?? "").replace(/\s+/g, " "),
|
|
7061
7062
|
dlp: !!e.dlp_matches?.length,
|
|
7062
|
-
burst: !!e.rate_limit_burst,
|
|
7063
7063
|
source: "cloud",
|
|
7064
7064
|
session: e.session_id ?? void 0,
|
|
7065
7065
|
agent: e.agent_name ?? void 0,
|
|
@@ -7115,12 +7115,6 @@ function PaneTitle({ label, extra, width: width2 }) {
|
|
|
7115
7115
|
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: tail + "\u2500".repeat(Math.max(0, width2 - used)) })
|
|
7116
7116
|
] });
|
|
7117
7117
|
}
|
|
7118
|
-
function Stat({ label, value, color }) {
|
|
7119
|
-
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", marginRight: 3, children: [
|
|
7120
|
-
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: label }),
|
|
7121
|
-
/* @__PURE__ */ jsx2(Text2, { bold: true, color, children: String(value) })
|
|
7122
|
-
] });
|
|
7123
|
-
}
|
|
7124
7118
|
function StreamLine({ e, loc }) {
|
|
7125
7119
|
return /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
7126
7120
|
/* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
|
|
@@ -7136,14 +7130,12 @@ function StreamLine({ e, loc }) {
|
|
|
7136
7130
|
/* @__PURE__ */ jsx2(Text2, { color: e.evalMs != null && e.evalMs > 500 ? theme.warn : theme.dim, children: (e.evalMs != null ? `${e.evalMs}ms` : "\u2014").padEnd(7) }),
|
|
7137
7131
|
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: truncate2(e.agent ?? "-", 11).padEnd(12) }),
|
|
7138
7132
|
e.dlp ? /* @__PURE__ */ jsx2(Text2, { color: theme.bad, children: "DLP! " }) : null,
|
|
7139
|
-
e.burst ? /* @__PURE__ */ jsx2(Text2, { color: theme.warn, children: "BURST " }) : null,
|
|
7140
7133
|
e.rule && e.decision !== "ALLOW" ? /* @__PURE__ */ jsx2(Text2, { color: theme.bad, children: truncate2(e.rule, 14) + " " }) : null,
|
|
7141
7134
|
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: e.detail })
|
|
7142
7135
|
] });
|
|
7143
7136
|
}
|
|
7144
7137
|
function LivePanel({ active: active2 }) {
|
|
7145
7138
|
const [s, setS] = useState2(null);
|
|
7146
|
-
const [tsData, setTsData] = useState2(null);
|
|
7147
7139
|
const [lat, setLat] = useState2([]);
|
|
7148
7140
|
const [cloudBuf, setCloudBuf] = useState2([]);
|
|
7149
7141
|
const [localBuf, setLocalBuf] = useState2([]);
|
|
@@ -7151,12 +7143,14 @@ function LivePanel({ active: active2 }) {
|
|
|
7151
7143
|
const [ring, setRing] = useState2(null);
|
|
7152
7144
|
const [log6, setLog] = useState2([]);
|
|
7153
7145
|
const [filter, setFilter] = useState2("all");
|
|
7146
|
+
const [search, setSearch] = useState2("");
|
|
7147
|
+
const [editingSearch, setEditingSearch] = useState2(false);
|
|
7154
7148
|
const [scroll, setScroll] = useState2(0);
|
|
7155
7149
|
const [mode, setMode] = useState2("stream");
|
|
7156
|
-
const [sessFilter, setSessFilter] = useState2("all");
|
|
7157
7150
|
const [pickIdx, setPickIdx] = useState2(0);
|
|
7151
|
+
const [detail, setDetail] = useState2(null);
|
|
7158
7152
|
const [detailCloud, setDetailCloud] = useState2([]);
|
|
7159
|
-
const [
|
|
7153
|
+
const [detailScroll, setDetailScroll] = useState2(0);
|
|
7160
7154
|
const seenRef = useRef2(/* @__PURE__ */ new Set());
|
|
7161
7155
|
const lastLocalTs = useRef2(0);
|
|
7162
7156
|
const pausedUntil = useRef2(0);
|
|
@@ -7201,7 +7195,6 @@ function LivePanel({ active: active2 }) {
|
|
|
7201
7195
|
permission: (j.permission ?? "").slice(0, 4),
|
|
7202
7196
|
detail: (j.arguments ? JSON.stringify(j.arguments) : j.reason ?? "").replace(/\s+/g, " "),
|
|
7203
7197
|
dlp: !!j.dlp,
|
|
7204
|
-
burst: !!j.rate_limit_burst,
|
|
7205
7198
|
source: "local",
|
|
7206
7199
|
session: j.session_id,
|
|
7207
7200
|
agent: j.agent_name,
|
|
@@ -7271,41 +7264,42 @@ function LivePanel({ active: active2 }) {
|
|
|
7271
7264
|
onApiError(e);
|
|
7272
7265
|
}
|
|
7273
7266
|
}, [onApiError]);
|
|
7274
|
-
const pollSlow = useCallback2(async () => {
|
|
7275
|
-
if (paused()) return;
|
|
7276
|
-
try {
|
|
7277
|
-
const td = await api.stats.timeseries({ period: "24h" });
|
|
7278
|
-
if (!frozenRef.current) setTsData(td);
|
|
7279
|
-
} catch (e) {
|
|
7280
|
-
onApiError(e);
|
|
7281
|
-
}
|
|
7282
|
-
}, [onApiError]);
|
|
7283
7267
|
useEffect2(() => {
|
|
7284
7268
|
if (!active2) return;
|
|
7285
7269
|
void pollFeed();
|
|
7286
7270
|
void pollMedium();
|
|
7287
|
-
void
|
|
7288
|
-
const
|
|
7289
|
-
const t2 = setInterval(() => void pollMedium(), 1e4);
|
|
7290
|
-
const t3 = setInterval(() => void pollSlow(), 3e4);
|
|
7271
|
+
const t1 = setInterval(() => void pollFeed(), 3e3);
|
|
7272
|
+
const t2 = setInterval(() => void pollMedium(), 8e3);
|
|
7291
7273
|
return () => {
|
|
7292
7274
|
clearInterval(t1);
|
|
7293
7275
|
clearInterval(t2);
|
|
7294
|
-
clearInterval(t3);
|
|
7295
7276
|
};
|
|
7296
|
-
}, [active2, pollFeed, pollMedium
|
|
7277
|
+
}, [active2, pollFeed, pollMedium]);
|
|
7297
7278
|
const sessions = useLoader(() => api.agents.live({ limit: 30, includeDeactivated: true }));
|
|
7298
7279
|
const insights = useLoader(() => api.stats.securityInsights(7));
|
|
7299
7280
|
const guard = useLoader(() => api.settings.getGuardStatus());
|
|
7300
7281
|
usePoll(() => {
|
|
7301
7282
|
if (!paused()) sessions.reload();
|
|
7302
|
-
},
|
|
7283
|
+
}, 8e3, active2);
|
|
7303
7284
|
usePoll(() => {
|
|
7304
7285
|
if (!paused()) insights.reload();
|
|
7305
|
-
},
|
|
7286
|
+
}, 2e4, active2);
|
|
7306
7287
|
usePoll(() => {
|
|
7307
7288
|
if (!paused()) guard.reload();
|
|
7308
7289
|
}, 6e4, active2);
|
|
7290
|
+
useEffect2(() => {
|
|
7291
|
+
if (!detail) return;
|
|
7292
|
+
setDetailCloud([]);
|
|
7293
|
+
setDetailScroll(0);
|
|
7294
|
+
let alive = true;
|
|
7295
|
+
api.audit.list({ session_id: detail.id, limit: 100 }).then((r) => {
|
|
7296
|
+
if (alive) setDetailCloud(r.entries.map(cloudItem));
|
|
7297
|
+
}).catch(() => {
|
|
7298
|
+
});
|
|
7299
|
+
return () => {
|
|
7300
|
+
alive = false;
|
|
7301
|
+
};
|
|
7302
|
+
}, [detail]);
|
|
7309
7303
|
const [tick, setTick] = useState2(0);
|
|
7310
7304
|
useEffect2(() => {
|
|
7311
7305
|
if (!active2 || frozen) return;
|
|
@@ -7325,68 +7319,65 @@ function LivePanel({ active: active2 }) {
|
|
|
7325
7319
|
}
|
|
7326
7320
|
const cloudDeduped = cloudBuf.filter((e) => !localKeys.has(`${e.tool}|${e.decision}|${Math.round(e.at / 6e4)}`));
|
|
7327
7321
|
const mergedAll = [...cloudDeduped, ...localBuf].sort((a, b) => a.at - b.at);
|
|
7322
|
+
const nowSlot = Math.floor(nowMs / 1e4);
|
|
7323
|
+
const traffic = new Array(60).fill(0);
|
|
7324
|
+
const trafficHot = new Array(60).fill(false);
|
|
7325
|
+
for (const e of mergedAll) {
|
|
7326
|
+
const idx = 59 - (nowSlot - Math.floor(e.at / 1e4));
|
|
7327
|
+
if (idx >= 0 && idx < 60) {
|
|
7328
|
+
traffic[idx]++;
|
|
7329
|
+
if (e.decision !== "ALLOW") trafficHot[idx] = true;
|
|
7330
|
+
}
|
|
7331
|
+
}
|
|
7332
|
+
const trafficLabel = "calls/10s \xB7 last 10m \xB7 live";
|
|
7333
|
+
const rl = ins.layers?.rateLimit;
|
|
7334
|
+
const dl = ins.layers?.dlp;
|
|
7335
|
+
const gh = ins.layers?.ghost;
|
|
7336
|
+
const minuteNow = mergedAll.filter((e) => nowMs - e.at < 6e4).length;
|
|
7337
|
+
const dlpBars = (ins.dlpByPattern ?? []).slice(0, 2);
|
|
7338
|
+
const maxDlpBar = dlpBars[0]?.count ?? 1;
|
|
7339
|
+
const denialsAll = mergedAll.filter((e) => e.decision !== "ALLOW");
|
|
7340
|
+
const lastDeny = denialsAll[denialsAll.length - 1];
|
|
7341
|
+
const toolCounts = /* @__PURE__ */ new Map();
|
|
7342
|
+
for (const e of mergedAll) toolCounts.set(e.tool, (toolCounts.get(e.tool) ?? 0) + 1);
|
|
7343
|
+
const topTools = [...toolCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 3);
|
|
7344
|
+
const latNow = lat[lat.length - 1] ?? 0;
|
|
7345
|
+
const sortedLat = [...lat].sort((a, b) => a - b);
|
|
7346
|
+
const latMed = sortedLat.length ? sortedLat[Math.floor(sortedLat.length / 2)] : 0;
|
|
7347
|
+
const latHotAt = Math.max(2e3, latMed * 2.5);
|
|
7348
|
+
const backingOff = Date.now() < pausedUntil.current;
|
|
7349
|
+
const sess = (sessions.data?.agents ?? []).slice().sort((a, b) => Date.parse(b.last_seen_at) - Date.parse(a.last_seen_at));
|
|
7350
|
+
const sessCounts = sessions.data?.counts;
|
|
7328
7351
|
const localSess = /* @__PURE__ */ new Map();
|
|
7329
7352
|
for (const e of localBuf) {
|
|
7330
7353
|
if (!e.session) continue;
|
|
7331
|
-
const cur = localSess.get(e.session) ?? { agent: e.agent ?? "local agent", calls: 0, denies: 0,
|
|
7354
|
+
const cur = localSess.get(e.session) ?? { agent: e.agent ?? "local agent", calls: 0, denies: 0, lastAt: 0 };
|
|
7332
7355
|
cur.calls++;
|
|
7333
7356
|
if (e.decision !== "ALLOW") cur.denies++;
|
|
7334
|
-
if (e.dlp) cur.dlp++;
|
|
7335
7357
|
cur.lastAt = Math.max(cur.lastAt, e.at);
|
|
7336
7358
|
if (e.agent) cur.agent = e.agent;
|
|
7337
7359
|
localSess.set(e.session, cur);
|
|
7338
7360
|
}
|
|
7339
|
-
const
|
|
7340
|
-
...[...localSess.entries()].sort((a, b) => b[1].lastAt - a[1].lastAt).map(([id, v]) => ({ source: "local", id, agent: v.agent, calls: v.calls, denies: v.denies,
|
|
7341
|
-
...
|
|
7361
|
+
const combinedSess = [
|
|
7362
|
+
...[...localSess.entries()].sort((a, b) => b[1].lastAt - a[1].lastAt).map(([id, v]) => ({ source: "local", id, agent: v.agent, calls: v.calls, denies: v.denies, lastAt: v.lastAt, isMe: ring?.session === id })),
|
|
7363
|
+
...sess.filter((a) => !localSess.has(a.session_id)).map((a) => ({
|
|
7342
7364
|
source: "cloud",
|
|
7343
7365
|
id: a.session_id,
|
|
7344
7366
|
agent: a.agent_name ?? a.session_id,
|
|
7345
7367
|
calls: a.total_calls,
|
|
7346
7368
|
denies: a.denied_calls,
|
|
7347
|
-
dlp: a.dlp_events,
|
|
7348
7369
|
lastAt: Date.parse(a.last_seen_at),
|
|
7349
7370
|
trust: a.trust_score,
|
|
7350
7371
|
isMe: false
|
|
7351
7372
|
}))
|
|
7352
7373
|
];
|
|
7353
|
-
const combinedSess = sessFilter === "all" ? combinedSessAll : combinedSessAll.filter((r) => sessStatus(r.lastAt, nowMs) === sessFilter);
|
|
7354
7374
|
const localSessIds = new Set(localSess.keys());
|
|
7355
7375
|
if (ring?.session) localSessIds.add(ring.session);
|
|
7356
7376
|
const isLoc = (e) => e.source === "local" || !!e.session && localSessIds.has(e.session);
|
|
7357
|
-
const
|
|
7377
|
+
const q = search.trim().toLowerCase();
|
|
7378
|
+
const matches = (e) => !q || `${e.tool} ${e.agent ?? ""} ${e.decision} ${e.permission} ${e.rule ?? ""} ${e.detail}`.toLowerCase().includes(q);
|
|
7379
|
+
const filtered = (filter === "all" ? mergedAll : mergedAll.filter((e) => filter === "local" ? isLoc(e) : !isLoc(e))).filter(matches);
|
|
7358
7380
|
const visibleDesc = filtered.slice(-STREAM_KEEP).reverse();
|
|
7359
|
-
const points = tsData?.timeseries ?? [];
|
|
7360
|
-
const nowSlot = Math.floor(nowMs / 1e4);
|
|
7361
|
-
const slotCounts = new Array(60).fill(0);
|
|
7362
|
-
const slotHot = new Array(60).fill(false);
|
|
7363
|
-
for (const e of mergedAll) {
|
|
7364
|
-
const idx = 59 - (nowSlot - Math.floor(e.at / 1e4));
|
|
7365
|
-
if (idx >= 0 && idx < 60) {
|
|
7366
|
-
slotCounts[idx]++;
|
|
7367
|
-
if (e.decision !== "ALLOW") slotHot[idx] = true;
|
|
7368
|
-
}
|
|
7369
|
-
}
|
|
7370
|
-
const localTraffic = localOn === true && slotCounts.some((c2) => c2 > 0);
|
|
7371
|
-
const traffic = localTraffic ? slotCounts : points.map((p) => p.total);
|
|
7372
|
-
const trafficHot = localTraffic ? slotHot : points.map((p) => p.denied > 0);
|
|
7373
|
-
const trafficLabel = localTraffic ? "calls/10s \xB7 last 10m" : "24h \xB7 cloud";
|
|
7374
|
-
const rl = ins.layers?.rateLimit;
|
|
7375
|
-
const dl = ins.layers?.dlp;
|
|
7376
|
-
const gh = ins.layers?.ghost;
|
|
7377
|
-
const minuteNow = mergedAll.filter((e) => nowMs - e.at < 6e4).length;
|
|
7378
|
-
const dlpBars = (ins.dlpByPattern ?? []).slice(0, 2);
|
|
7379
|
-
const maxDlpBar = dlpBars[0]?.count ?? 1;
|
|
7380
|
-
const denialsAll = mergedAll.filter((e) => e.decision !== "ALLOW");
|
|
7381
|
-
const lastDeny = denialsAll[denialsAll.length - 1];
|
|
7382
|
-
const toolCounts = /* @__PURE__ */ new Map();
|
|
7383
|
-
for (const e of mergedAll) toolCounts.set(e.tool, (toolCounts.get(e.tool) ?? 0) + 1);
|
|
7384
|
-
const topTools = [...toolCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 3);
|
|
7385
|
-
const latNow = lat[lat.length - 1] ?? 0;
|
|
7386
|
-
const sortedLat = [...lat].sort((a, b) => a - b);
|
|
7387
|
-
const latMed = sortedLat.length ? sortedLat[Math.floor(sortedLat.length / 2)] : 0;
|
|
7388
|
-
const latHotAt = Math.max(2e3, latMed * 2.5);
|
|
7389
|
-
const backingOff = Date.now() < pausedUntil.current;
|
|
7390
7381
|
const chartH = rows >= 36 ? 6 : 4;
|
|
7391
7382
|
const colH = rows >= 32 ? 7 : 5;
|
|
7392
7383
|
const streamRows = Math.max(4, rows - 5 - (1 + chartH) - colH);
|
|
@@ -7394,29 +7385,18 @@ function LivePanel({ active: active2 }) {
|
|
|
7394
7385
|
const leftW = Math.floor(innerW * 0.55);
|
|
7395
7386
|
const rightW = innerW - leftW - 2;
|
|
7396
7387
|
const colW = Math.max(20, Math.floor((innerW - 4) / 3));
|
|
7388
|
+
const pickable = combinedSess.slice(0, colH - 1);
|
|
7397
7389
|
const maxScroll = Math.max(0, visibleDesc.length - streamRows);
|
|
7398
7390
|
const clampedScroll = Math.min(scroll, maxScroll);
|
|
7399
7391
|
const windowed = visibleDesc.slice(clampedScroll, clampedScroll + streamRows);
|
|
7400
|
-
const
|
|
7401
|
-
const selected = combinedSess[selIdx];
|
|
7402
|
-
useEffect2(() => {
|
|
7403
|
-
if (mode !== "sessions" || !selected?.id) return;
|
|
7404
|
-
setTlScroll(0);
|
|
7405
|
-
const t = setTimeout(() => {
|
|
7406
|
-
api.audit.list({ session_id: selected.id, limit: 100 }).then((r) => {
|
|
7407
|
-
if (!frozenRef.current) setDetailCloud(r.entries.map(cloudItem));
|
|
7408
|
-
}).catch(() => setDetailCloud([]));
|
|
7409
|
-
}, 400);
|
|
7410
|
-
return () => clearTimeout(t);
|
|
7411
|
-
}, [mode, selected?.id]);
|
|
7412
|
-
const detailEntries = selected ? (() => {
|
|
7392
|
+
const detailEntries = detail ? (() => {
|
|
7413
7393
|
const seen = /* @__PURE__ */ new Set();
|
|
7414
7394
|
const all = [];
|
|
7415
|
-
for (const e of [...detailCloud, ...mergedAll.filter((x) => x.session ===
|
|
7395
|
+
for (const e of [...detailCloud, ...mergedAll.filter((x) => x.session === detail.id)]) {
|
|
7416
7396
|
const key = `${e.at}:${e.tool}:${e.decision}`;
|
|
7417
7397
|
if (seen.has(key)) continue;
|
|
7418
7398
|
seen.add(key);
|
|
7419
|
-
all.push(e);
|
|
7399
|
+
if (matches(e)) all.push(e);
|
|
7420
7400
|
}
|
|
7421
7401
|
return all.sort((a, b) => b.at - a.at);
|
|
7422
7402
|
})() : [];
|
|
@@ -7427,17 +7407,31 @@ function LivePanel({ active: active2 }) {
|
|
|
7427
7407
|
return;
|
|
7428
7408
|
}
|
|
7429
7409
|
if (frozen) return;
|
|
7430
|
-
if (
|
|
7410
|
+
if (input === "/" && mode !== "pick") {
|
|
7411
|
+
setEditingSearch(true);
|
|
7412
|
+
return;
|
|
7413
|
+
}
|
|
7414
|
+
if (mode === "detail") {
|
|
7415
|
+
const maxD = Math.max(0, detailEntries.length - (rows - 10));
|
|
7431
7416
|
if (key.leftArrow) {
|
|
7432
7417
|
setMode("stream");
|
|
7433
|
-
|
|
7434
|
-
} else if (key.upArrow)
|
|
7435
|
-
else if (key.downArrow)
|
|
7436
|
-
else if (
|
|
7437
|
-
|
|
7438
|
-
|
|
7439
|
-
|
|
7440
|
-
|
|
7418
|
+
setDetail(null);
|
|
7419
|
+
} else if (key.upArrow) setDetailScroll((n) => Math.max(0, n - 1));
|
|
7420
|
+
else if (key.downArrow) setDetailScroll((n) => Math.min(maxD, n + 1));
|
|
7421
|
+
else if (key.pageUp) setDetailScroll((n) => Math.max(0, n - 10));
|
|
7422
|
+
else if (key.pageDown) setDetailScroll((n) => Math.min(maxD, n + 10));
|
|
7423
|
+
return;
|
|
7424
|
+
}
|
|
7425
|
+
if (mode === "pick") {
|
|
7426
|
+
if (key.upArrow) setPickIdx((n) => Math.max(0, n - 1));
|
|
7427
|
+
else if (key.downArrow) setPickIdx((n) => Math.min(pickable.length - 1, n + 1));
|
|
7428
|
+
else if (key.return) {
|
|
7429
|
+
const row = pickable[pickIdx];
|
|
7430
|
+
if (row) {
|
|
7431
|
+
setDetail(row);
|
|
7432
|
+
setMode("detail");
|
|
7433
|
+
}
|
|
7434
|
+
} else if (input === "s" || key.leftArrow) setMode("stream");
|
|
7441
7435
|
return;
|
|
7442
7436
|
}
|
|
7443
7437
|
if (input === "f") {
|
|
@@ -7445,14 +7439,36 @@ function LivePanel({ active: active2 }) {
|
|
|
7445
7439
|
setScroll(0);
|
|
7446
7440
|
} else if (input === "s") {
|
|
7447
7441
|
setPickIdx(0);
|
|
7448
|
-
setMode("
|
|
7442
|
+
setMode("pick");
|
|
7449
7443
|
} else if (key.downArrow) setScroll((n) => Math.min(maxScroll, n + 1));
|
|
7450
7444
|
else if (key.upArrow) setScroll((n) => Math.max(0, n - 1));
|
|
7451
7445
|
else if (key.pageDown) setScroll((n) => Math.min(maxScroll, n + streamRows));
|
|
7452
7446
|
else if (key.pageUp) setScroll((n) => Math.max(0, n - streamRows));
|
|
7453
7447
|
},
|
|
7454
|
-
{ isActive: active2 }
|
|
7448
|
+
{ isActive: active2 && !editingSearch }
|
|
7455
7449
|
);
|
|
7450
|
+
const searchRow = editingSearch ? /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
7451
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.warn, children: "search: " }),
|
|
7452
|
+
/* @__PURE__ */ jsx2(
|
|
7453
|
+
TextInput,
|
|
7454
|
+
{
|
|
7455
|
+
value: search,
|
|
7456
|
+
onChange: (v) => {
|
|
7457
|
+
setSearch(v);
|
|
7458
|
+
setScroll(0);
|
|
7459
|
+
setDetailScroll(0);
|
|
7460
|
+
},
|
|
7461
|
+
onSubmit: () => setEditingSearch(false)
|
|
7462
|
+
}
|
|
7463
|
+
),
|
|
7464
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " (enter done \xB7 empty = clear)" })
|
|
7465
|
+
] }) : search ? /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
7466
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.warn, children: "search: " }),
|
|
7467
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.accentBright, bold: true, children: search }),
|
|
7468
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: ` ${filtered.length} match \xB7 / edit` })
|
|
7469
|
+
] }) : null;
|
|
7470
|
+
const sessionDot = (st) => st === "active" ? { ch: "\u25CF", color: theme.ok } : st === "idle" ? { ch: "\u25CC", color: theme.warn } : { ch: "\u25CB", color: theme.dim };
|
|
7471
|
+
void sessionDot;
|
|
7456
7472
|
if (s === null && localOn === null) {
|
|
7457
7473
|
return /* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
|
|
7458
7474
|
spin,
|
|
@@ -7464,89 +7480,70 @@ function LivePanel({ active: active2 }) {
|
|
|
7464
7480
|
/* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: ` ${spin} up ${fmtUp(nowMs - startRef.current)} \xB7 ${hhmmss(nowMs)} \xB7 api ${latNow}ms ` }),
|
|
7465
7481
|
frozen ? /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#123d1f", color: "#7bd88f", bold: true, children: " \u23F5 COPY MODE \u2014 screen frozen, select & copy freely \xB7 space resume " }) : backingOff ? /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#3d2a12", color: "#ffb454", bold: true, children: " RATE LIMITED \xB7 backing off " }) : lastDeny ? /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#3d1220", color: "#ff6b6b", bold: true, children: ` \u26A0 ${hhmmss(lastDeny.at)} ${lastDeny.tool} DENIED ` }) : /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#4f6db8", children: " \u2713 clean " })
|
|
7466
7482
|
] });
|
|
7467
|
-
if (mode === "
|
|
7468
|
-
const listW = Math.min(44, Math.floor(innerW * 0.38));
|
|
7469
|
-
const detailW = innerW - listW - 2;
|
|
7470
|
-
const bodyH = rows - 4;
|
|
7471
|
-
const listRows = bodyH - 1;
|
|
7483
|
+
if (mode === "detail" && detail) {
|
|
7472
7484
|
const d = detailEntries;
|
|
7473
7485
|
const allow = d.filter((e) => e.decision === "ALLOW").length;
|
|
7474
7486
|
const deny = d.length - allow;
|
|
7475
7487
|
const dlpN = d.filter((e) => e.dlp).length;
|
|
7476
|
-
const burstN = d.filter((e) => e.burst).length;
|
|
7477
7488
|
const evals = d.filter((e) => e.evalMs != null).map((e) => e.evalMs);
|
|
7478
7489
|
const evalAvg = evals.length ? Math.round(evals.reduce((a, b) => a + b, 0) / evals.length) : null;
|
|
7479
7490
|
const tCounts = /* @__PURE__ */ new Map();
|
|
7480
|
-
|
|
7481
|
-
|
|
7491
|
+
const pCounts = /* @__PURE__ */ new Map();
|
|
7492
|
+
for (const e of d) {
|
|
7493
|
+
tCounts.set(e.tool, (tCounts.get(e.tool) ?? 0) + 1);
|
|
7494
|
+
if (e.permission) pCounts.set(e.permission, (pCounts.get(e.permission) ?? 0) + 1);
|
|
7495
|
+
}
|
|
7496
|
+
const top = [...tCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 6);
|
|
7497
|
+
const perms = [...pCounts.entries()].sort((a, b) => b[1] - a[1]);
|
|
7482
7498
|
const first = d[d.length - 1]?.at;
|
|
7483
7499
|
const last = d[0]?.at;
|
|
7484
|
-
const tlRows = Math.max(
|
|
7485
|
-
const
|
|
7486
|
-
const
|
|
7487
|
-
const tl = d.slice(
|
|
7500
|
+
const tlRows = Math.max(4, rows - 10);
|
|
7501
|
+
const maxD = Math.max(0, d.length - tlRows);
|
|
7502
|
+
const dScroll = Math.min(detailScroll, maxD);
|
|
7503
|
+
const tl = d.slice(dScroll, dScroll + tlRows);
|
|
7488
7504
|
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingX: 1, children: [
|
|
7489
7505
|
titleBar,
|
|
7490
|
-
/* @__PURE__ */
|
|
7491
|
-
|
|
7492
|
-
/* @__PURE__ */
|
|
7493
|
-
|
|
7494
|
-
|
|
7495
|
-
|
|
7496
|
-
] }) : null,
|
|
7497
|
-
combinedSess.slice(0, listRows).map((r, i) => {
|
|
7498
|
-
const st = sessStatus(r.lastAt, nowMs);
|
|
7499
|
-
const dot = st === "active" ? { ch: "\u25CF", color: theme.ok } : st === "idle" ? { ch: "\u25CC", color: theme.warn } : { ch: "\u25CB", color: theme.dim };
|
|
7500
|
-
const sel = i === selIdx;
|
|
7501
|
-
return /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", backgroundColor: sel ? "#1c2f63" : void 0, bold: sel, children: [
|
|
7502
|
-
/* @__PURE__ */ jsxs2(Text2, { color: sel ? theme.accentBright : dot.color, children: [
|
|
7503
|
-
sel ? "\u25B8" : dot.ch,
|
|
7504
|
-
" "
|
|
7505
|
-
] }),
|
|
7506
|
-
/* @__PURE__ */ jsx2(Text2, { color: r.source === "local" ? theme.ok : "#4f6db8", children: r.source === "local" ? "LOC " : "CLD " }),
|
|
7507
|
-
/* @__PURE__ */ jsx2(Text2, { color: theme.accentBright, children: truncate2(r.isMe ? "this machine" : r.agent, 13).padEnd(14) }),
|
|
7508
|
-
/* @__PURE__ */ jsxs2(Text2, { children: [
|
|
7509
|
-
String(r.calls).padStart(4),
|
|
7510
|
-
"c "
|
|
7511
|
-
] }),
|
|
7512
|
-
/* @__PURE__ */ jsxs2(Text2, { color: r.denies ? theme.bad : theme.dim, children: [
|
|
7513
|
-
String(r.denies).padStart(3),
|
|
7514
|
-
"d "
|
|
7515
|
-
] }),
|
|
7516
|
-
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: ago(r.lastAt) })
|
|
7517
|
-
] }, r.id);
|
|
7518
|
-
})
|
|
7519
|
-
] }),
|
|
7520
|
-
/* @__PURE__ */ jsx2(Box2, { flexDirection: "column", width: detailW, overflow: "hidden", children: !selected ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "select a session to see its run timeline" }) : /* @__PURE__ */ jsxs2(Fragment2, { children: [
|
|
7521
|
-
/* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
7522
|
-
/* @__PURE__ */ jsx2(Text2, { color: sessStatus(selected.lastAt, nowMs) === "active" ? theme.ok : theme.dim, children: sessStatus(selected.lastAt, nowMs) === "active" ? "\u25CF " : "\u25CB " }),
|
|
7523
|
-
/* @__PURE__ */ jsx2(Text2, { bold: true, children: truncate2(selected.isMe ? selected.agent + " (this machine)" : selected.agent, 30) }),
|
|
7524
|
-
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " " + selected.id })
|
|
7525
|
-
] }),
|
|
7526
|
-
/* @__PURE__ */ jsxs2(Box2, { marginTop: 1, children: [
|
|
7527
|
-
/* @__PURE__ */ jsx2(Stat, { label: "Total", value: d.length }),
|
|
7528
|
-
/* @__PURE__ */ jsx2(Stat, { label: "Allowed", value: allow, color: theme.ok }),
|
|
7529
|
-
/* @__PURE__ */ jsx2(Stat, { label: "Denied", value: deny, color: deny ? theme.bad : void 0 }),
|
|
7530
|
-
/* @__PURE__ */ jsx2(Stat, { label: "DLP", value: dlpN, color: dlpN ? theme.bad : void 0 }),
|
|
7531
|
-
/* @__PURE__ */ jsx2(Stat, { label: "Rate limit", value: burstN, color: burstN ? theme.warn : void 0 }),
|
|
7532
|
-
/* @__PURE__ */ jsx2(Stat, { label: "Avg eval", value: evalAvg != null ? `${evalAvg}ms` : "\u2014" }),
|
|
7533
|
-
selected.trust != null ? /* @__PURE__ */ jsx2(Stat, { label: "Trust", value: `${selected.trust}/100`, color: theme.accent }) : null
|
|
7534
|
-
] }),
|
|
7535
|
-
/* @__PURE__ */ jsx2(Text2, { wrap: "truncate", color: theme.dim, children: `first ${first ? `${hhmmss(first)} (${ago(first)} ago)` : "\u2014"} \xB7 last ${last ? `${hhmmss(last)} (${ago(last)} ago)` : "\u2014"} \xB7 tools ${top.map(([t, c2]) => `${t}\xD7${c2}`).join(" ") || "\u2014"}` }),
|
|
7536
|
-
/* @__PURE__ */ jsx2(PaneTitle, { label: "TIMELINE", extra: `${d.length} events \xB7 newest first${tScroll ? ` \xB7 \u25BC${tScroll}` : ""}`, width: detailW }),
|
|
7537
|
-
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", overflow: "hidden", flexGrow: 1, children: [
|
|
7538
|
-
tl.length === 0 ? /* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
|
|
7539
|
-
spin,
|
|
7540
|
-
" loading history\u2026"
|
|
7541
|
-
] }) : null,
|
|
7542
|
-
tl.map((e) => /* @__PURE__ */ jsx2(StreamLine, { e, loc: isLoc(e) }, e.id))
|
|
7543
|
-
] })
|
|
7544
|
-
] }) })
|
|
7506
|
+
/* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
7507
|
+
/* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#AAC8FA", bold: true, children: ` SESSION ${detail.id.slice(0, 8)} ` }),
|
|
7508
|
+
/* @__PURE__ */ jsx2(Text2, { color: detail.source === "local" ? theme.ok : "#4f6db8", children: " " + detail.source.toUpperCase() }),
|
|
7509
|
+
/* @__PURE__ */ jsx2(Text2, { bold: true, children: " " + truncate2(detail.isMe ? detail.agent + " (this machine)" : detail.agent, 34) }),
|
|
7510
|
+
detail.trust != null ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: ` trust ${detail.trust}/100` }) : null,
|
|
7511
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2190 back" })
|
|
7545
7512
|
] }),
|
|
7546
7513
|
/* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
7547
|
-
/* @__PURE__ */ jsx2(Text2, {
|
|
7548
|
-
/* @__PURE__ */ jsx2(Text2, {
|
|
7549
|
-
/* @__PURE__ */ jsx2(Text2, {
|
|
7514
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "\u2502 calls " }),
|
|
7515
|
+
/* @__PURE__ */ jsx2(Text2, { bold: true, children: d.length }),
|
|
7516
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 allow " }),
|
|
7517
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.ok, children: allow }),
|
|
7518
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 deny " }),
|
|
7519
|
+
/* @__PURE__ */ jsx2(Text2, { color: deny ? theme.bad : theme.dim, bold: deny > 0, children: deny }),
|
|
7520
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 dlp " }),
|
|
7521
|
+
/* @__PURE__ */ jsx2(Text2, { color: dlpN ? theme.bad : theme.dim, children: dlpN }),
|
|
7522
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 avg eval " }),
|
|
7523
|
+
/* @__PURE__ */ jsx2(Text2, { children: evalAvg != null ? `${evalAvg}ms` : "\u2014" }),
|
|
7524
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 first " }),
|
|
7525
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: first ? `${hhmmss(first)} (${ago(first)} ago)` : "\u2014" }),
|
|
7526
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 last " }),
|
|
7527
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: last ? `${hhmmss(last)} (${ago(last)} ago)` : "\u2014" }),
|
|
7528
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502" })
|
|
7529
|
+
] }),
|
|
7530
|
+
/* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
7531
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "\u2502 tools " }),
|
|
7532
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: top.map(([t, c2]) => `${t}\xD7${c2}`).join(" ") || "\u2014" }),
|
|
7533
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 perms " }),
|
|
7534
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: perms.map(([p, c2]) => `${p}\xD7${c2}`).join(" ") || "\u2014" }),
|
|
7535
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502" })
|
|
7536
|
+
] }),
|
|
7537
|
+
/* @__PURE__ */ jsx2(PaneTitle, { label: "TIMELINE", extra: `${d.length} calls \xB7 newest first${dScroll ? ` \xB7 \u25BC${dScroll} older` : ""} \xB7 \u2191\u2193 scroll \xB7 / search \xB7 \u2190 back`, width: innerW }),
|
|
7538
|
+
searchRow,
|
|
7539
|
+
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", height: tlRows, overflow: "hidden", children: [
|
|
7540
|
+
tl.length === 0 ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: detailCloud.length === 0 ? spin + " loading history\u2026" : "no entries" }) : null,
|
|
7541
|
+
tl.map((e) => /* @__PURE__ */ jsx2(StreamLine, { e, loc: isLoc(e) }, e.id))
|
|
7542
|
+
] }),
|
|
7543
|
+
/* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
7544
|
+
/* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", bold: true, children: " SESSION " }),
|
|
7545
|
+
/* @__PURE__ */ jsx2(Text2, { backgroundColor: "#0b1530", color: "#4f6db8", children: ` ${detail.id} ` }),
|
|
7546
|
+
/* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: " \u2191\u2193 scroll \xB7 \u2190 back \xB7 esc menu " })
|
|
7550
7547
|
] })
|
|
7551
7548
|
] });
|
|
7552
7549
|
}
|
|
@@ -7568,7 +7565,7 @@ function LivePanel({ active: active2 }) {
|
|
|
7568
7565
|
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 ghost " }),
|
|
7569
7566
|
/* @__PURE__ */ jsx2(Text2, { color: gh?.mode === "on" ? theme.ok : theme.dim, children: gh?.mode ?? "?" }),
|
|
7570
7567
|
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 sess " }),
|
|
7571
|
-
/* @__PURE__ */ jsx2(Text2, { color: theme.accent, children:
|
|
7568
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: `${combinedSess.length} (${sessCounts?.active ?? 0} live)` }),
|
|
7572
7569
|
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 hooks " }),
|
|
7573
7570
|
/* @__PURE__ */ jsx2(Text2, { color: guard.data?.up_to_date ? theme.ok : theme.warn, children: guard.data ? `v${guard.data.installed ?? "?"}${guard.data.up_to_date ? "" : "\u2192v" + guard.data.latest} ${guard.data.device_count}dev` : "\xB7" }),
|
|
7574
7571
|
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502" })
|
|
@@ -7607,13 +7604,14 @@ function LivePanel({ active: active2 }) {
|
|
|
7607
7604
|
] })
|
|
7608
7605
|
] }),
|
|
7609
7606
|
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: colW, marginRight: 2, overflow: "hidden", children: [
|
|
7610
|
-
/* @__PURE__ */ jsx2(PaneTitle, { label: "SESSIONS", extra: "
|
|
7611
|
-
|
|
7612
|
-
|
|
7613
|
-
const
|
|
7614
|
-
|
|
7615
|
-
|
|
7616
|
-
|
|
7607
|
+
/* @__PURE__ */ jsx2(PaneTitle, { label: "SESSIONS", extra: mode === "pick" ? "\u2191\u2193 pick \xB7 enter open" : `${combinedSess.length} \xB7 s = inspect`, width: colW }),
|
|
7608
|
+
pickable.length === 0 ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "scanning\u2026" }) : null,
|
|
7609
|
+
pickable.map((r, i) => {
|
|
7610
|
+
const freshDot = nowMs - r.lastAt < 9e4;
|
|
7611
|
+
const sel = mode === "pick" && i === pickIdx;
|
|
7612
|
+
return /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", backgroundColor: sel ? "#1c2f63" : void 0, bold: sel || r.isMe, children: [
|
|
7613
|
+
/* @__PURE__ */ jsxs2(Text2, { color: sel ? theme.accentBright : freshDot ? theme.ok : theme.dim, children: [
|
|
7614
|
+
sel ? "\u25B8" : freshDot ? "\u25CF" : "\u25CB",
|
|
7617
7615
|
" "
|
|
7618
7616
|
] }),
|
|
7619
7617
|
/* @__PURE__ */ jsx2(Text2, { color: r.source === "local" ? theme.ok : "#4f6db8", children: r.source === "local" ? "LOC " : "CLD " }),
|
|
@@ -7626,7 +7624,7 @@ function LivePanel({ active: active2 }) {
|
|
|
7626
7624
|
String(r.denies).padStart(3),
|
|
7627
7625
|
"d "
|
|
7628
7626
|
] }),
|
|
7629
|
-
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: ago(r.lastAt) })
|
|
7627
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: (r.trust != null ? `t${r.trust} ` : "") + ago(r.lastAt) })
|
|
7630
7628
|
] }, r.id);
|
|
7631
7629
|
})
|
|
7632
7630
|
] }),
|
|
@@ -7646,11 +7644,12 @@ function LivePanel({ active: active2 }) {
|
|
|
7646
7644
|
PaneTitle,
|
|
7647
7645
|
{
|
|
7648
7646
|
label: "TOOL STREAM",
|
|
7649
|
-
extra: `newest first \xB7 last ${STREAM_KEEP} \xB7 filter ${filter}${clampedScroll > 0 ? ` \xB7 \u25BC${clampedScroll} older` : ""} \xB7 f filter \xB7 \u2191\u2193 scroll \xB7 s sessions`,
|
|
7647
|
+
extra: `newest first \xB7 last ${STREAM_KEEP} \xB7 filter ${filter}${clampedScroll > 0 ? ` \xB7 \u25BC${clampedScroll} older` : ""} \xB7 f filter \xB7 / search \xB7 \u2191\u2193 scroll \xB7 s sessions`,
|
|
7650
7648
|
width: innerW
|
|
7651
7649
|
}
|
|
7652
7650
|
),
|
|
7653
7651
|
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", height: streamRows, overflow: "hidden", children: [
|
|
7652
|
+
searchRow,
|
|
7654
7653
|
localOn === false ? /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
7655
7654
|
/* @__PURE__ */ jsx2(Text2, { color: theme.warn, children: "local logs off" }),
|
|
7656
7655
|
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2014 enable: dashboard \u2192 Settings \u2192 Local logs \xB7 hooks write ~/.solongate/local-logs \xB7 view: `solongate logs-server`" })
|
|
@@ -7663,12 +7662,12 @@ function LivePanel({ active: active2 }) {
|
|
|
7663
7662
|
] }),
|
|
7664
7663
|
/* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
7665
7664
|
/* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", bold: true, children: " LIVE " }),
|
|
7666
|
-
/* @__PURE__ */ jsx2(Text2, { backgroundColor: "#0b1530", color: "#4f6db8", children: ` local ${localOn ? "on" : "off"} \xB7 ${localBuf.length} loc/${
|
|
7667
|
-
/* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: " space copy-mode \xB7 f filter \xB7 s sessions \xB7 esc menu \xB7 q quit " })
|
|
7665
|
+
/* @__PURE__ */ jsx2(Text2, { backgroundColor: "#0b1530", color: "#4f6db8", children: ` local ${localOn ? "on" : "off"} \xB7 ${localBuf.length} loc/${cloudBuf.length} cld \xB7 top ${topTools.map(([t, c2]) => `${t}\xD7${c2}`).join(" ") || "\u2014"} ` }),
|
|
7666
|
+
/* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: " space copy-mode \xB7 / search \xB7 f filter \xB7 s sessions \xB7 esc menu \xB7 q quit " })
|
|
7668
7667
|
] })
|
|
7669
7668
|
] });
|
|
7670
7669
|
}
|
|
7671
|
-
var SPIN, BG, DIM_FLOOR, LOCAL_LOG, RING, STREAM_KEEP, hhmmss, fmtUp, FILTERS
|
|
7670
|
+
var SPIN, BG, DIM_FLOOR, LOCAL_LOG, RING, STREAM_KEEP, hhmmss, fmtUp, FILTERS;
|
|
7672
7671
|
var init_Live = __esm({
|
|
7673
7672
|
"src/tui/panels/Live.tsx"() {
|
|
7674
7673
|
"use strict";
|
|
@@ -7691,14 +7690,12 @@ var init_Live = __esm({
|
|
|
7691
7690
|
return `${p(Math.floor(s / 3600))}:${p(Math.floor(s % 3600 / 60))}:${p(s % 60)}`;
|
|
7692
7691
|
};
|
|
7693
7692
|
FILTERS = ["all", "local", "cloud"];
|
|
7694
|
-
SESS_FILTERS = ["all", "active", "idle", "ended"];
|
|
7695
|
-
sessStatus = (lastAt, now) => now - lastAt < 9e4 ? "active" : now - lastAt < 3e5 ? "idle" : "ended";
|
|
7696
7693
|
}
|
|
7697
7694
|
});
|
|
7698
7695
|
|
|
7699
7696
|
// src/tui/panels/Policies.tsx
|
|
7700
7697
|
import { Box as Box3, Text as Text3, useInput as useInput2 } from "ink";
|
|
7701
|
-
import
|
|
7698
|
+
import TextInput2 from "ink-text-input";
|
|
7702
7699
|
import { useEffect as useEffect3, useState as useState3 } from "react";
|
|
7703
7700
|
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
7704
7701
|
function ruleSummary(r) {
|
|
@@ -7887,7 +7884,7 @@ function PoliciesPanel({ focused }) {
|
|
|
7887
7884
|
return /* @__PURE__ */ jsxs3(Box3, { children: [
|
|
7888
7885
|
/* @__PURE__ */ jsx3(Text3, { color: active2 ? theme.accentBright : void 0, children: (active2 ? "\u25B8 " : " ") + f.label.padEnd(13) }),
|
|
7889
7886
|
active2 && editing && f.kind === "text" ? /* @__PURE__ */ jsx3(
|
|
7890
|
-
|
|
7887
|
+
TextInput2,
|
|
7891
7888
|
{
|
|
7892
7889
|
value: editVal,
|
|
7893
7890
|
onChange: setEditVal,
|
|
@@ -8083,7 +8080,7 @@ var init_RateLimit = __esm({
|
|
|
8083
8080
|
|
|
8084
8081
|
// src/tui/panels/Dlp.tsx
|
|
8085
8082
|
import { Box as Box5, Text as Text5, useInput as useInput4 } from "ink";
|
|
8086
|
-
import
|
|
8083
|
+
import TextInput3 from "ink-text-input";
|
|
8087
8084
|
import { useEffect as useEffect5, useState as useState5 } from "react";
|
|
8088
8085
|
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
8089
8086
|
function DlpPanel({ focused }) {
|
|
@@ -8185,7 +8182,7 @@ function DlpPanel({ focused }) {
|
|
|
8185
8182
|
adding ? /* @__PURE__ */ jsxs5(Box5, { marginTop: 1, children: [
|
|
8186
8183
|
/* @__PURE__ */ jsx5(Text5, { color: theme.warn, children: adding === "name" ? "New pattern name: " : `Regex for "${newName}": ` }),
|
|
8187
8184
|
adding === "name" ? /* @__PURE__ */ jsx5(
|
|
8188
|
-
|
|
8185
|
+
TextInput3,
|
|
8189
8186
|
{
|
|
8190
8187
|
value: newName,
|
|
8191
8188
|
onChange: setNewName,
|
|
@@ -8196,7 +8193,7 @@ function DlpPanel({ focused }) {
|
|
|
8196
8193
|
}
|
|
8197
8194
|
}
|
|
8198
8195
|
) : /* @__PURE__ */ jsx5(
|
|
8199
|
-
|
|
8196
|
+
TextInput3,
|
|
8200
8197
|
{
|
|
8201
8198
|
value: newRe,
|
|
8202
8199
|
onChange: setNewRe,
|
|
@@ -8309,7 +8306,7 @@ var init_Stats = __esm({
|
|
|
8309
8306
|
|
|
8310
8307
|
// src/tui/panels/Audit.tsx
|
|
8311
8308
|
import { Box as Box7, Text as Text7, useInput as useInput5 } from "ink";
|
|
8312
|
-
import
|
|
8309
|
+
import TextInput4 from "ink-text-input";
|
|
8313
8310
|
import { useState as useState6 } from "react";
|
|
8314
8311
|
import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
8315
8312
|
function AuditPanel({ active: active2, focused }) {
|
|
@@ -8426,7 +8423,7 @@ function AuditPanel({ active: active2, focused }) {
|
|
|
8426
8423
|
": "
|
|
8427
8424
|
] }),
|
|
8428
8425
|
/* @__PURE__ */ jsx7(
|
|
8429
|
-
|
|
8426
|
+
TextInput4,
|
|
8430
8427
|
{
|
|
8431
8428
|
value: editing === "tool" ? tool : editing === "agent" ? agent : search,
|
|
8432
8429
|
onChange: editing === "tool" ? setTool : editing === "agent" ? setAgent : setSearch,
|
package/dist/tui/index.js
CHANGED
|
@@ -126,6 +126,7 @@ function KeyHints({ hints }) {
|
|
|
126
126
|
|
|
127
127
|
// src/tui/panels/Live.tsx
|
|
128
128
|
import { Box as Box2, Text as Text2, useInput } from "ink";
|
|
129
|
+
import TextInput from "ink-text-input";
|
|
129
130
|
import { closeSync, openSync, readSync, statSync } from "fs";
|
|
130
131
|
import { homedir as homedir2 } from "os";
|
|
131
132
|
import { join as join2 } from "path";
|
|
@@ -443,7 +444,7 @@ function usePoll(reload, intervalMs, enabled = true) {
|
|
|
443
444
|
}
|
|
444
445
|
|
|
445
446
|
// src/tui/panels/Live.tsx
|
|
446
|
-
import {
|
|
447
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
447
448
|
var SPIN = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
448
449
|
var BG = "#12234f";
|
|
449
450
|
var DIM_FLOOR = "#233457";
|
|
@@ -483,7 +484,6 @@ function cloudItem(e) {
|
|
|
483
484
|
permission: (e.permission ?? "").slice(0, 4),
|
|
484
485
|
detail: (e.arguments_summary ? JSON.stringify(e.arguments_summary) : e.reason ?? "").replace(/\s+/g, " "),
|
|
485
486
|
dlp: !!e.dlp_matches?.length,
|
|
486
|
-
burst: !!e.rate_limit_burst,
|
|
487
487
|
source: "cloud",
|
|
488
488
|
session: e.session_id ?? void 0,
|
|
489
489
|
agent: e.agent_name ?? void 0,
|
|
@@ -539,12 +539,6 @@ function PaneTitle({ label, extra, width }) {
|
|
|
539
539
|
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: tail + "\u2500".repeat(Math.max(0, width - used)) })
|
|
540
540
|
] });
|
|
541
541
|
}
|
|
542
|
-
function Stat({ label, value, color }) {
|
|
543
|
-
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", marginRight: 3, children: [
|
|
544
|
-
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: label }),
|
|
545
|
-
/* @__PURE__ */ jsx2(Text2, { bold: true, color, children: String(value) })
|
|
546
|
-
] });
|
|
547
|
-
}
|
|
548
542
|
function StreamLine({ e, loc }) {
|
|
549
543
|
return /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
550
544
|
/* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
|
|
@@ -560,17 +554,13 @@ function StreamLine({ e, loc }) {
|
|
|
560
554
|
/* @__PURE__ */ jsx2(Text2, { color: e.evalMs != null && e.evalMs > 500 ? theme.warn : theme.dim, children: (e.evalMs != null ? `${e.evalMs}ms` : "\u2014").padEnd(7) }),
|
|
561
555
|
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: truncate(e.agent ?? "-", 11).padEnd(12) }),
|
|
562
556
|
e.dlp ? /* @__PURE__ */ jsx2(Text2, { color: theme.bad, children: "DLP! " }) : null,
|
|
563
|
-
e.burst ? /* @__PURE__ */ jsx2(Text2, { color: theme.warn, children: "BURST " }) : null,
|
|
564
557
|
e.rule && e.decision !== "ALLOW" ? /* @__PURE__ */ jsx2(Text2, { color: theme.bad, children: truncate(e.rule, 14) + " " }) : null,
|
|
565
558
|
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: e.detail })
|
|
566
559
|
] });
|
|
567
560
|
}
|
|
568
561
|
var FILTERS = ["all", "local", "cloud"];
|
|
569
|
-
var SESS_FILTERS = ["all", "active", "idle", "ended"];
|
|
570
|
-
var sessStatus = (lastAt, now) => now - lastAt < 9e4 ? "active" : now - lastAt < 3e5 ? "idle" : "ended";
|
|
571
562
|
function LivePanel({ active: active2 }) {
|
|
572
563
|
const [s, setS] = useState2(null);
|
|
573
|
-
const [tsData, setTsData] = useState2(null);
|
|
574
564
|
const [lat, setLat] = useState2([]);
|
|
575
565
|
const [cloudBuf, setCloudBuf] = useState2([]);
|
|
576
566
|
const [localBuf, setLocalBuf] = useState2([]);
|
|
@@ -578,12 +568,14 @@ function LivePanel({ active: active2 }) {
|
|
|
578
568
|
const [ring, setRing] = useState2(null);
|
|
579
569
|
const [log, setLog] = useState2([]);
|
|
580
570
|
const [filter, setFilter] = useState2("all");
|
|
571
|
+
const [search, setSearch] = useState2("");
|
|
572
|
+
const [editingSearch, setEditingSearch] = useState2(false);
|
|
581
573
|
const [scroll, setScroll] = useState2(0);
|
|
582
574
|
const [mode, setMode] = useState2("stream");
|
|
583
|
-
const [sessFilter, setSessFilter] = useState2("all");
|
|
584
575
|
const [pickIdx, setPickIdx] = useState2(0);
|
|
576
|
+
const [detail, setDetail] = useState2(null);
|
|
585
577
|
const [detailCloud, setDetailCloud] = useState2([]);
|
|
586
|
-
const [
|
|
578
|
+
const [detailScroll, setDetailScroll] = useState2(0);
|
|
587
579
|
const seenRef = useRef2(/* @__PURE__ */ new Set());
|
|
588
580
|
const lastLocalTs = useRef2(0);
|
|
589
581
|
const pausedUntil = useRef2(0);
|
|
@@ -628,7 +620,6 @@ function LivePanel({ active: active2 }) {
|
|
|
628
620
|
permission: (j.permission ?? "").slice(0, 4),
|
|
629
621
|
detail: (j.arguments ? JSON.stringify(j.arguments) : j.reason ?? "").replace(/\s+/g, " "),
|
|
630
622
|
dlp: !!j.dlp,
|
|
631
|
-
burst: !!j.rate_limit_burst,
|
|
632
623
|
source: "local",
|
|
633
624
|
session: j.session_id,
|
|
634
625
|
agent: j.agent_name,
|
|
@@ -698,41 +689,42 @@ function LivePanel({ active: active2 }) {
|
|
|
698
689
|
onApiError(e);
|
|
699
690
|
}
|
|
700
691
|
}, [onApiError]);
|
|
701
|
-
const pollSlow = useCallback2(async () => {
|
|
702
|
-
if (paused()) return;
|
|
703
|
-
try {
|
|
704
|
-
const td = await api.stats.timeseries({ period: "24h" });
|
|
705
|
-
if (!frozenRef.current) setTsData(td);
|
|
706
|
-
} catch (e) {
|
|
707
|
-
onApiError(e);
|
|
708
|
-
}
|
|
709
|
-
}, [onApiError]);
|
|
710
692
|
useEffect2(() => {
|
|
711
693
|
if (!active2) return;
|
|
712
694
|
void pollFeed();
|
|
713
695
|
void pollMedium();
|
|
714
|
-
void
|
|
715
|
-
const
|
|
716
|
-
const t2 = setInterval(() => void pollMedium(), 1e4);
|
|
717
|
-
const t3 = setInterval(() => void pollSlow(), 3e4);
|
|
696
|
+
const t1 = setInterval(() => void pollFeed(), 3e3);
|
|
697
|
+
const t2 = setInterval(() => void pollMedium(), 8e3);
|
|
718
698
|
return () => {
|
|
719
699
|
clearInterval(t1);
|
|
720
700
|
clearInterval(t2);
|
|
721
|
-
clearInterval(t3);
|
|
722
701
|
};
|
|
723
|
-
}, [active2, pollFeed, pollMedium
|
|
702
|
+
}, [active2, pollFeed, pollMedium]);
|
|
724
703
|
const sessions = useLoader(() => api.agents.live({ limit: 30, includeDeactivated: true }));
|
|
725
704
|
const insights = useLoader(() => api.stats.securityInsights(7));
|
|
726
705
|
const guard = useLoader(() => api.settings.getGuardStatus());
|
|
727
706
|
usePoll(() => {
|
|
728
707
|
if (!paused()) sessions.reload();
|
|
729
|
-
},
|
|
708
|
+
}, 8e3, active2);
|
|
730
709
|
usePoll(() => {
|
|
731
710
|
if (!paused()) insights.reload();
|
|
732
|
-
},
|
|
711
|
+
}, 2e4, active2);
|
|
733
712
|
usePoll(() => {
|
|
734
713
|
if (!paused()) guard.reload();
|
|
735
714
|
}, 6e4, active2);
|
|
715
|
+
useEffect2(() => {
|
|
716
|
+
if (!detail) return;
|
|
717
|
+
setDetailCloud([]);
|
|
718
|
+
setDetailScroll(0);
|
|
719
|
+
let alive = true;
|
|
720
|
+
api.audit.list({ session_id: detail.id, limit: 100 }).then((r) => {
|
|
721
|
+
if (alive) setDetailCloud(r.entries.map(cloudItem));
|
|
722
|
+
}).catch(() => {
|
|
723
|
+
});
|
|
724
|
+
return () => {
|
|
725
|
+
alive = false;
|
|
726
|
+
};
|
|
727
|
+
}, [detail]);
|
|
736
728
|
const [tick, setTick] = useState2(0);
|
|
737
729
|
useEffect2(() => {
|
|
738
730
|
if (!active2 || frozen) return;
|
|
@@ -752,68 +744,65 @@ function LivePanel({ active: active2 }) {
|
|
|
752
744
|
}
|
|
753
745
|
const cloudDeduped = cloudBuf.filter((e) => !localKeys.has(`${e.tool}|${e.decision}|${Math.round(e.at / 6e4)}`));
|
|
754
746
|
const mergedAll = [...cloudDeduped, ...localBuf].sort((a, b) => a.at - b.at);
|
|
747
|
+
const nowSlot = Math.floor(nowMs / 1e4);
|
|
748
|
+
const traffic = new Array(60).fill(0);
|
|
749
|
+
const trafficHot = new Array(60).fill(false);
|
|
750
|
+
for (const e of mergedAll) {
|
|
751
|
+
const idx = 59 - (nowSlot - Math.floor(e.at / 1e4));
|
|
752
|
+
if (idx >= 0 && idx < 60) {
|
|
753
|
+
traffic[idx]++;
|
|
754
|
+
if (e.decision !== "ALLOW") trafficHot[idx] = true;
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
const trafficLabel = "calls/10s \xB7 last 10m \xB7 live";
|
|
758
|
+
const rl = ins.layers?.rateLimit;
|
|
759
|
+
const dl = ins.layers?.dlp;
|
|
760
|
+
const gh = ins.layers?.ghost;
|
|
761
|
+
const minuteNow = mergedAll.filter((e) => nowMs - e.at < 6e4).length;
|
|
762
|
+
const dlpBars = (ins.dlpByPattern ?? []).slice(0, 2);
|
|
763
|
+
const maxDlpBar = dlpBars[0]?.count ?? 1;
|
|
764
|
+
const denialsAll = mergedAll.filter((e) => e.decision !== "ALLOW");
|
|
765
|
+
const lastDeny = denialsAll[denialsAll.length - 1];
|
|
766
|
+
const toolCounts = /* @__PURE__ */ new Map();
|
|
767
|
+
for (const e of mergedAll) toolCounts.set(e.tool, (toolCounts.get(e.tool) ?? 0) + 1);
|
|
768
|
+
const topTools = [...toolCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 3);
|
|
769
|
+
const latNow = lat[lat.length - 1] ?? 0;
|
|
770
|
+
const sortedLat = [...lat].sort((a, b) => a - b);
|
|
771
|
+
const latMed = sortedLat.length ? sortedLat[Math.floor(sortedLat.length / 2)] : 0;
|
|
772
|
+
const latHotAt = Math.max(2e3, latMed * 2.5);
|
|
773
|
+
const backingOff = Date.now() < pausedUntil.current;
|
|
774
|
+
const sess = (sessions.data?.agents ?? []).slice().sort((a, b) => Date.parse(b.last_seen_at) - Date.parse(a.last_seen_at));
|
|
775
|
+
const sessCounts = sessions.data?.counts;
|
|
755
776
|
const localSess = /* @__PURE__ */ new Map();
|
|
756
777
|
for (const e of localBuf) {
|
|
757
778
|
if (!e.session) continue;
|
|
758
|
-
const cur = localSess.get(e.session) ?? { agent: e.agent ?? "local agent", calls: 0, denies: 0,
|
|
779
|
+
const cur = localSess.get(e.session) ?? { agent: e.agent ?? "local agent", calls: 0, denies: 0, lastAt: 0 };
|
|
759
780
|
cur.calls++;
|
|
760
781
|
if (e.decision !== "ALLOW") cur.denies++;
|
|
761
|
-
if (e.dlp) cur.dlp++;
|
|
762
782
|
cur.lastAt = Math.max(cur.lastAt, e.at);
|
|
763
783
|
if (e.agent) cur.agent = e.agent;
|
|
764
784
|
localSess.set(e.session, cur);
|
|
765
785
|
}
|
|
766
|
-
const
|
|
767
|
-
...[...localSess.entries()].sort((a, b) => b[1].lastAt - a[1].lastAt).map(([id, v]) => ({ source: "local", id, agent: v.agent, calls: v.calls, denies: v.denies,
|
|
768
|
-
...
|
|
786
|
+
const combinedSess = [
|
|
787
|
+
...[...localSess.entries()].sort((a, b) => b[1].lastAt - a[1].lastAt).map(([id, v]) => ({ source: "local", id, agent: v.agent, calls: v.calls, denies: v.denies, lastAt: v.lastAt, isMe: ring?.session === id })),
|
|
788
|
+
...sess.filter((a) => !localSess.has(a.session_id)).map((a) => ({
|
|
769
789
|
source: "cloud",
|
|
770
790
|
id: a.session_id,
|
|
771
791
|
agent: a.agent_name ?? a.session_id,
|
|
772
792
|
calls: a.total_calls,
|
|
773
793
|
denies: a.denied_calls,
|
|
774
|
-
dlp: a.dlp_events,
|
|
775
794
|
lastAt: Date.parse(a.last_seen_at),
|
|
776
795
|
trust: a.trust_score,
|
|
777
796
|
isMe: false
|
|
778
797
|
}))
|
|
779
798
|
];
|
|
780
|
-
const combinedSess = sessFilter === "all" ? combinedSessAll : combinedSessAll.filter((r) => sessStatus(r.lastAt, nowMs) === sessFilter);
|
|
781
799
|
const localSessIds = new Set(localSess.keys());
|
|
782
800
|
if (ring?.session) localSessIds.add(ring.session);
|
|
783
801
|
const isLoc = (e) => e.source === "local" || !!e.session && localSessIds.has(e.session);
|
|
784
|
-
const
|
|
802
|
+
const q = search.trim().toLowerCase();
|
|
803
|
+
const matches = (e) => !q || `${e.tool} ${e.agent ?? ""} ${e.decision} ${e.permission} ${e.rule ?? ""} ${e.detail}`.toLowerCase().includes(q);
|
|
804
|
+
const filtered = (filter === "all" ? mergedAll : mergedAll.filter((e) => filter === "local" ? isLoc(e) : !isLoc(e))).filter(matches);
|
|
785
805
|
const visibleDesc = filtered.slice(-STREAM_KEEP).reverse();
|
|
786
|
-
const points = tsData?.timeseries ?? [];
|
|
787
|
-
const nowSlot = Math.floor(nowMs / 1e4);
|
|
788
|
-
const slotCounts = new Array(60).fill(0);
|
|
789
|
-
const slotHot = new Array(60).fill(false);
|
|
790
|
-
for (const e of mergedAll) {
|
|
791
|
-
const idx = 59 - (nowSlot - Math.floor(e.at / 1e4));
|
|
792
|
-
if (idx >= 0 && idx < 60) {
|
|
793
|
-
slotCounts[idx]++;
|
|
794
|
-
if (e.decision !== "ALLOW") slotHot[idx] = true;
|
|
795
|
-
}
|
|
796
|
-
}
|
|
797
|
-
const localTraffic = localOn === true && slotCounts.some((c2) => c2 > 0);
|
|
798
|
-
const traffic = localTraffic ? slotCounts : points.map((p) => p.total);
|
|
799
|
-
const trafficHot = localTraffic ? slotHot : points.map((p) => p.denied > 0);
|
|
800
|
-
const trafficLabel = localTraffic ? "calls/10s \xB7 last 10m" : "24h \xB7 cloud";
|
|
801
|
-
const rl = ins.layers?.rateLimit;
|
|
802
|
-
const dl = ins.layers?.dlp;
|
|
803
|
-
const gh = ins.layers?.ghost;
|
|
804
|
-
const minuteNow = mergedAll.filter((e) => nowMs - e.at < 6e4).length;
|
|
805
|
-
const dlpBars = (ins.dlpByPattern ?? []).slice(0, 2);
|
|
806
|
-
const maxDlpBar = dlpBars[0]?.count ?? 1;
|
|
807
|
-
const denialsAll = mergedAll.filter((e) => e.decision !== "ALLOW");
|
|
808
|
-
const lastDeny = denialsAll[denialsAll.length - 1];
|
|
809
|
-
const toolCounts = /* @__PURE__ */ new Map();
|
|
810
|
-
for (const e of mergedAll) toolCounts.set(e.tool, (toolCounts.get(e.tool) ?? 0) + 1);
|
|
811
|
-
const topTools = [...toolCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 3);
|
|
812
|
-
const latNow = lat[lat.length - 1] ?? 0;
|
|
813
|
-
const sortedLat = [...lat].sort((a, b) => a - b);
|
|
814
|
-
const latMed = sortedLat.length ? sortedLat[Math.floor(sortedLat.length / 2)] : 0;
|
|
815
|
-
const latHotAt = Math.max(2e3, latMed * 2.5);
|
|
816
|
-
const backingOff = Date.now() < pausedUntil.current;
|
|
817
806
|
const chartH = rows >= 36 ? 6 : 4;
|
|
818
807
|
const colH = rows >= 32 ? 7 : 5;
|
|
819
808
|
const streamRows = Math.max(4, rows - 5 - (1 + chartH) - colH);
|
|
@@ -821,29 +810,18 @@ function LivePanel({ active: active2 }) {
|
|
|
821
810
|
const leftW = Math.floor(innerW * 0.55);
|
|
822
811
|
const rightW = innerW - leftW - 2;
|
|
823
812
|
const colW = Math.max(20, Math.floor((innerW - 4) / 3));
|
|
813
|
+
const pickable = combinedSess.slice(0, colH - 1);
|
|
824
814
|
const maxScroll = Math.max(0, visibleDesc.length - streamRows);
|
|
825
815
|
const clampedScroll = Math.min(scroll, maxScroll);
|
|
826
816
|
const windowed = visibleDesc.slice(clampedScroll, clampedScroll + streamRows);
|
|
827
|
-
const
|
|
828
|
-
const selected = combinedSess[selIdx];
|
|
829
|
-
useEffect2(() => {
|
|
830
|
-
if (mode !== "sessions" || !selected?.id) return;
|
|
831
|
-
setTlScroll(0);
|
|
832
|
-
const t = setTimeout(() => {
|
|
833
|
-
api.audit.list({ session_id: selected.id, limit: 100 }).then((r) => {
|
|
834
|
-
if (!frozenRef.current) setDetailCloud(r.entries.map(cloudItem));
|
|
835
|
-
}).catch(() => setDetailCloud([]));
|
|
836
|
-
}, 400);
|
|
837
|
-
return () => clearTimeout(t);
|
|
838
|
-
}, [mode, selected?.id]);
|
|
839
|
-
const detailEntries = selected ? (() => {
|
|
817
|
+
const detailEntries = detail ? (() => {
|
|
840
818
|
const seen = /* @__PURE__ */ new Set();
|
|
841
819
|
const all = [];
|
|
842
|
-
for (const e of [...detailCloud, ...mergedAll.filter((x) => x.session ===
|
|
820
|
+
for (const e of [...detailCloud, ...mergedAll.filter((x) => x.session === detail.id)]) {
|
|
843
821
|
const key = `${e.at}:${e.tool}:${e.decision}`;
|
|
844
822
|
if (seen.has(key)) continue;
|
|
845
823
|
seen.add(key);
|
|
846
|
-
all.push(e);
|
|
824
|
+
if (matches(e)) all.push(e);
|
|
847
825
|
}
|
|
848
826
|
return all.sort((a, b) => b.at - a.at);
|
|
849
827
|
})() : [];
|
|
@@ -854,17 +832,31 @@ function LivePanel({ active: active2 }) {
|
|
|
854
832
|
return;
|
|
855
833
|
}
|
|
856
834
|
if (frozen) return;
|
|
857
|
-
if (
|
|
835
|
+
if (input === "/" && mode !== "pick") {
|
|
836
|
+
setEditingSearch(true);
|
|
837
|
+
return;
|
|
838
|
+
}
|
|
839
|
+
if (mode === "detail") {
|
|
840
|
+
const maxD = Math.max(0, detailEntries.length - (rows - 10));
|
|
858
841
|
if (key.leftArrow) {
|
|
859
842
|
setMode("stream");
|
|
860
|
-
|
|
861
|
-
} else if (key.upArrow)
|
|
862
|
-
else if (key.downArrow)
|
|
863
|
-
else if (
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
843
|
+
setDetail(null);
|
|
844
|
+
} else if (key.upArrow) setDetailScroll((n) => Math.max(0, n - 1));
|
|
845
|
+
else if (key.downArrow) setDetailScroll((n) => Math.min(maxD, n + 1));
|
|
846
|
+
else if (key.pageUp) setDetailScroll((n) => Math.max(0, n - 10));
|
|
847
|
+
else if (key.pageDown) setDetailScroll((n) => Math.min(maxD, n + 10));
|
|
848
|
+
return;
|
|
849
|
+
}
|
|
850
|
+
if (mode === "pick") {
|
|
851
|
+
if (key.upArrow) setPickIdx((n) => Math.max(0, n - 1));
|
|
852
|
+
else if (key.downArrow) setPickIdx((n) => Math.min(pickable.length - 1, n + 1));
|
|
853
|
+
else if (key.return) {
|
|
854
|
+
const row = pickable[pickIdx];
|
|
855
|
+
if (row) {
|
|
856
|
+
setDetail(row);
|
|
857
|
+
setMode("detail");
|
|
858
|
+
}
|
|
859
|
+
} else if (input === "s" || key.leftArrow) setMode("stream");
|
|
868
860
|
return;
|
|
869
861
|
}
|
|
870
862
|
if (input === "f") {
|
|
@@ -872,14 +864,36 @@ function LivePanel({ active: active2 }) {
|
|
|
872
864
|
setScroll(0);
|
|
873
865
|
} else if (input === "s") {
|
|
874
866
|
setPickIdx(0);
|
|
875
|
-
setMode("
|
|
867
|
+
setMode("pick");
|
|
876
868
|
} else if (key.downArrow) setScroll((n) => Math.min(maxScroll, n + 1));
|
|
877
869
|
else if (key.upArrow) setScroll((n) => Math.max(0, n - 1));
|
|
878
870
|
else if (key.pageDown) setScroll((n) => Math.min(maxScroll, n + streamRows));
|
|
879
871
|
else if (key.pageUp) setScroll((n) => Math.max(0, n - streamRows));
|
|
880
872
|
},
|
|
881
|
-
{ isActive: active2 }
|
|
873
|
+
{ isActive: active2 && !editingSearch }
|
|
882
874
|
);
|
|
875
|
+
const searchRow = editingSearch ? /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
876
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.warn, children: "search: " }),
|
|
877
|
+
/* @__PURE__ */ jsx2(
|
|
878
|
+
TextInput,
|
|
879
|
+
{
|
|
880
|
+
value: search,
|
|
881
|
+
onChange: (v) => {
|
|
882
|
+
setSearch(v);
|
|
883
|
+
setScroll(0);
|
|
884
|
+
setDetailScroll(0);
|
|
885
|
+
},
|
|
886
|
+
onSubmit: () => setEditingSearch(false)
|
|
887
|
+
}
|
|
888
|
+
),
|
|
889
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " (enter done \xB7 empty = clear)" })
|
|
890
|
+
] }) : search ? /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
891
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.warn, children: "search: " }),
|
|
892
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.accentBright, bold: true, children: search }),
|
|
893
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: ` ${filtered.length} match \xB7 / edit` })
|
|
894
|
+
] }) : null;
|
|
895
|
+
const sessionDot = (st) => st === "active" ? { ch: "\u25CF", color: theme.ok } : st === "idle" ? { ch: "\u25CC", color: theme.warn } : { ch: "\u25CB", color: theme.dim };
|
|
896
|
+
void sessionDot;
|
|
883
897
|
if (s === null && localOn === null) {
|
|
884
898
|
return /* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
|
|
885
899
|
spin,
|
|
@@ -891,89 +905,70 @@ function LivePanel({ active: active2 }) {
|
|
|
891
905
|
/* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: ` ${spin} up ${fmtUp(nowMs - startRef.current)} \xB7 ${hhmmss(nowMs)} \xB7 api ${latNow}ms ` }),
|
|
892
906
|
frozen ? /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#123d1f", color: "#7bd88f", bold: true, children: " \u23F5 COPY MODE \u2014 screen frozen, select & copy freely \xB7 space resume " }) : backingOff ? /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#3d2a12", color: "#ffb454", bold: true, children: " RATE LIMITED \xB7 backing off " }) : lastDeny ? /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#3d1220", color: "#ff6b6b", bold: true, children: ` \u26A0 ${hhmmss(lastDeny.at)} ${lastDeny.tool} DENIED ` }) : /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#4f6db8", children: " \u2713 clean " })
|
|
893
907
|
] });
|
|
894
|
-
if (mode === "
|
|
895
|
-
const listW = Math.min(44, Math.floor(innerW * 0.38));
|
|
896
|
-
const detailW = innerW - listW - 2;
|
|
897
|
-
const bodyH = rows - 4;
|
|
898
|
-
const listRows = bodyH - 1;
|
|
908
|
+
if (mode === "detail" && detail) {
|
|
899
909
|
const d = detailEntries;
|
|
900
910
|
const allow = d.filter((e) => e.decision === "ALLOW").length;
|
|
901
911
|
const deny = d.length - allow;
|
|
902
912
|
const dlpN = d.filter((e) => e.dlp).length;
|
|
903
|
-
const burstN = d.filter((e) => e.burst).length;
|
|
904
913
|
const evals = d.filter((e) => e.evalMs != null).map((e) => e.evalMs);
|
|
905
914
|
const evalAvg = evals.length ? Math.round(evals.reduce((a, b) => a + b, 0) / evals.length) : null;
|
|
906
915
|
const tCounts = /* @__PURE__ */ new Map();
|
|
907
|
-
|
|
908
|
-
|
|
916
|
+
const pCounts = /* @__PURE__ */ new Map();
|
|
917
|
+
for (const e of d) {
|
|
918
|
+
tCounts.set(e.tool, (tCounts.get(e.tool) ?? 0) + 1);
|
|
919
|
+
if (e.permission) pCounts.set(e.permission, (pCounts.get(e.permission) ?? 0) + 1);
|
|
920
|
+
}
|
|
921
|
+
const top = [...tCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 6);
|
|
922
|
+
const perms = [...pCounts.entries()].sort((a, b) => b[1] - a[1]);
|
|
909
923
|
const first = d[d.length - 1]?.at;
|
|
910
924
|
const last = d[0]?.at;
|
|
911
|
-
const tlRows = Math.max(
|
|
912
|
-
const
|
|
913
|
-
const
|
|
914
|
-
const tl = d.slice(
|
|
925
|
+
const tlRows = Math.max(4, rows - 10);
|
|
926
|
+
const maxD = Math.max(0, d.length - tlRows);
|
|
927
|
+
const dScroll = Math.min(detailScroll, maxD);
|
|
928
|
+
const tl = d.slice(dScroll, dScroll + tlRows);
|
|
915
929
|
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingX: 1, children: [
|
|
916
930
|
titleBar,
|
|
917
|
-
/* @__PURE__ */
|
|
918
|
-
|
|
919
|
-
/* @__PURE__ */
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
] }) : null,
|
|
924
|
-
combinedSess.slice(0, listRows).map((r, i) => {
|
|
925
|
-
const st = sessStatus(r.lastAt, nowMs);
|
|
926
|
-
const dot = st === "active" ? { ch: "\u25CF", color: theme.ok } : st === "idle" ? { ch: "\u25CC", color: theme.warn } : { ch: "\u25CB", color: theme.dim };
|
|
927
|
-
const sel = i === selIdx;
|
|
928
|
-
return /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", backgroundColor: sel ? "#1c2f63" : void 0, bold: sel, children: [
|
|
929
|
-
/* @__PURE__ */ jsxs2(Text2, { color: sel ? theme.accentBright : dot.color, children: [
|
|
930
|
-
sel ? "\u25B8" : dot.ch,
|
|
931
|
-
" "
|
|
932
|
-
] }),
|
|
933
|
-
/* @__PURE__ */ jsx2(Text2, { color: r.source === "local" ? theme.ok : "#4f6db8", children: r.source === "local" ? "LOC " : "CLD " }),
|
|
934
|
-
/* @__PURE__ */ jsx2(Text2, { color: theme.accentBright, children: truncate(r.isMe ? "this machine" : r.agent, 13).padEnd(14) }),
|
|
935
|
-
/* @__PURE__ */ jsxs2(Text2, { children: [
|
|
936
|
-
String(r.calls).padStart(4),
|
|
937
|
-
"c "
|
|
938
|
-
] }),
|
|
939
|
-
/* @__PURE__ */ jsxs2(Text2, { color: r.denies ? theme.bad : theme.dim, children: [
|
|
940
|
-
String(r.denies).padStart(3),
|
|
941
|
-
"d "
|
|
942
|
-
] }),
|
|
943
|
-
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: ago(r.lastAt) })
|
|
944
|
-
] }, r.id);
|
|
945
|
-
})
|
|
946
|
-
] }),
|
|
947
|
-
/* @__PURE__ */ jsx2(Box2, { flexDirection: "column", width: detailW, overflow: "hidden", children: !selected ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "select a session to see its run timeline" }) : /* @__PURE__ */ jsxs2(Fragment2, { children: [
|
|
948
|
-
/* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
949
|
-
/* @__PURE__ */ jsx2(Text2, { color: sessStatus(selected.lastAt, nowMs) === "active" ? theme.ok : theme.dim, children: sessStatus(selected.lastAt, nowMs) === "active" ? "\u25CF " : "\u25CB " }),
|
|
950
|
-
/* @__PURE__ */ jsx2(Text2, { bold: true, children: truncate(selected.isMe ? selected.agent + " (this machine)" : selected.agent, 30) }),
|
|
951
|
-
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " " + selected.id })
|
|
952
|
-
] }),
|
|
953
|
-
/* @__PURE__ */ jsxs2(Box2, { marginTop: 1, children: [
|
|
954
|
-
/* @__PURE__ */ jsx2(Stat, { label: "Total", value: d.length }),
|
|
955
|
-
/* @__PURE__ */ jsx2(Stat, { label: "Allowed", value: allow, color: theme.ok }),
|
|
956
|
-
/* @__PURE__ */ jsx2(Stat, { label: "Denied", value: deny, color: deny ? theme.bad : void 0 }),
|
|
957
|
-
/* @__PURE__ */ jsx2(Stat, { label: "DLP", value: dlpN, color: dlpN ? theme.bad : void 0 }),
|
|
958
|
-
/* @__PURE__ */ jsx2(Stat, { label: "Rate limit", value: burstN, color: burstN ? theme.warn : void 0 }),
|
|
959
|
-
/* @__PURE__ */ jsx2(Stat, { label: "Avg eval", value: evalAvg != null ? `${evalAvg}ms` : "\u2014" }),
|
|
960
|
-
selected.trust != null ? /* @__PURE__ */ jsx2(Stat, { label: "Trust", value: `${selected.trust}/100`, color: theme.accent }) : null
|
|
961
|
-
] }),
|
|
962
|
-
/* @__PURE__ */ jsx2(Text2, { wrap: "truncate", color: theme.dim, children: `first ${first ? `${hhmmss(first)} (${ago(first)} ago)` : "\u2014"} \xB7 last ${last ? `${hhmmss(last)} (${ago(last)} ago)` : "\u2014"} \xB7 tools ${top.map(([t, c2]) => `${t}\xD7${c2}`).join(" ") || "\u2014"}` }),
|
|
963
|
-
/* @__PURE__ */ jsx2(PaneTitle, { label: "TIMELINE", extra: `${d.length} events \xB7 newest first${tScroll ? ` \xB7 \u25BC${tScroll}` : ""}`, width: detailW }),
|
|
964
|
-
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", overflow: "hidden", flexGrow: 1, children: [
|
|
965
|
-
tl.length === 0 ? /* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
|
|
966
|
-
spin,
|
|
967
|
-
" loading history\u2026"
|
|
968
|
-
] }) : null,
|
|
969
|
-
tl.map((e) => /* @__PURE__ */ jsx2(StreamLine, { e, loc: isLoc(e) }, e.id))
|
|
970
|
-
] })
|
|
971
|
-
] }) })
|
|
931
|
+
/* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
932
|
+
/* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#AAC8FA", bold: true, children: ` SESSION ${detail.id.slice(0, 8)} ` }),
|
|
933
|
+
/* @__PURE__ */ jsx2(Text2, { color: detail.source === "local" ? theme.ok : "#4f6db8", children: " " + detail.source.toUpperCase() }),
|
|
934
|
+
/* @__PURE__ */ jsx2(Text2, { bold: true, children: " " + truncate(detail.isMe ? detail.agent + " (this machine)" : detail.agent, 34) }),
|
|
935
|
+
detail.trust != null ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: ` trust ${detail.trust}/100` }) : null,
|
|
936
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2190 back" })
|
|
972
937
|
] }),
|
|
973
938
|
/* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
974
|
-
/* @__PURE__ */ jsx2(Text2, {
|
|
975
|
-
/* @__PURE__ */ jsx2(Text2, {
|
|
976
|
-
/* @__PURE__ */ jsx2(Text2, {
|
|
939
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "\u2502 calls " }),
|
|
940
|
+
/* @__PURE__ */ jsx2(Text2, { bold: true, children: d.length }),
|
|
941
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 allow " }),
|
|
942
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.ok, children: allow }),
|
|
943
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 deny " }),
|
|
944
|
+
/* @__PURE__ */ jsx2(Text2, { color: deny ? theme.bad : theme.dim, bold: deny > 0, children: deny }),
|
|
945
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 dlp " }),
|
|
946
|
+
/* @__PURE__ */ jsx2(Text2, { color: dlpN ? theme.bad : theme.dim, children: dlpN }),
|
|
947
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 avg eval " }),
|
|
948
|
+
/* @__PURE__ */ jsx2(Text2, { children: evalAvg != null ? `${evalAvg}ms` : "\u2014" }),
|
|
949
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 first " }),
|
|
950
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: first ? `${hhmmss(first)} (${ago(first)} ago)` : "\u2014" }),
|
|
951
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 last " }),
|
|
952
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: last ? `${hhmmss(last)} (${ago(last)} ago)` : "\u2014" }),
|
|
953
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502" })
|
|
954
|
+
] }),
|
|
955
|
+
/* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
956
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "\u2502 tools " }),
|
|
957
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: top.map(([t, c2]) => `${t}\xD7${c2}`).join(" ") || "\u2014" }),
|
|
958
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 perms " }),
|
|
959
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: perms.map(([p, c2]) => `${p}\xD7${c2}`).join(" ") || "\u2014" }),
|
|
960
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502" })
|
|
961
|
+
] }),
|
|
962
|
+
/* @__PURE__ */ jsx2(PaneTitle, { label: "TIMELINE", extra: `${d.length} calls \xB7 newest first${dScroll ? ` \xB7 \u25BC${dScroll} older` : ""} \xB7 \u2191\u2193 scroll \xB7 / search \xB7 \u2190 back`, width: innerW }),
|
|
963
|
+
searchRow,
|
|
964
|
+
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", height: tlRows, overflow: "hidden", children: [
|
|
965
|
+
tl.length === 0 ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: detailCloud.length === 0 ? spin + " loading history\u2026" : "no entries" }) : null,
|
|
966
|
+
tl.map((e) => /* @__PURE__ */ jsx2(StreamLine, { e, loc: isLoc(e) }, e.id))
|
|
967
|
+
] }),
|
|
968
|
+
/* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
969
|
+
/* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", bold: true, children: " SESSION " }),
|
|
970
|
+
/* @__PURE__ */ jsx2(Text2, { backgroundColor: "#0b1530", color: "#4f6db8", children: ` ${detail.id} ` }),
|
|
971
|
+
/* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: " \u2191\u2193 scroll \xB7 \u2190 back \xB7 esc menu " })
|
|
977
972
|
] })
|
|
978
973
|
] });
|
|
979
974
|
}
|
|
@@ -995,7 +990,7 @@ function LivePanel({ active: active2 }) {
|
|
|
995
990
|
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 ghost " }),
|
|
996
991
|
/* @__PURE__ */ jsx2(Text2, { color: gh?.mode === "on" ? theme.ok : theme.dim, children: gh?.mode ?? "?" }),
|
|
997
992
|
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 sess " }),
|
|
998
|
-
/* @__PURE__ */ jsx2(Text2, { color: theme.accent, children:
|
|
993
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: `${combinedSess.length} (${sessCounts?.active ?? 0} live)` }),
|
|
999
994
|
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 hooks " }),
|
|
1000
995
|
/* @__PURE__ */ jsx2(Text2, { color: guard.data?.up_to_date ? theme.ok : theme.warn, children: guard.data ? `v${guard.data.installed ?? "?"}${guard.data.up_to_date ? "" : "\u2192v" + guard.data.latest} ${guard.data.device_count}dev` : "\xB7" }),
|
|
1001
996
|
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502" })
|
|
@@ -1034,13 +1029,14 @@ function LivePanel({ active: active2 }) {
|
|
|
1034
1029
|
] })
|
|
1035
1030
|
] }),
|
|
1036
1031
|
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: colW, marginRight: 2, overflow: "hidden", children: [
|
|
1037
|
-
/* @__PURE__ */ jsx2(PaneTitle, { label: "SESSIONS", extra: "
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
const
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1032
|
+
/* @__PURE__ */ jsx2(PaneTitle, { label: "SESSIONS", extra: mode === "pick" ? "\u2191\u2193 pick \xB7 enter open" : `${combinedSess.length} \xB7 s = inspect`, width: colW }),
|
|
1033
|
+
pickable.length === 0 ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "scanning\u2026" }) : null,
|
|
1034
|
+
pickable.map((r, i) => {
|
|
1035
|
+
const freshDot = nowMs - r.lastAt < 9e4;
|
|
1036
|
+
const sel = mode === "pick" && i === pickIdx;
|
|
1037
|
+
return /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", backgroundColor: sel ? "#1c2f63" : void 0, bold: sel || r.isMe, children: [
|
|
1038
|
+
/* @__PURE__ */ jsxs2(Text2, { color: sel ? theme.accentBright : freshDot ? theme.ok : theme.dim, children: [
|
|
1039
|
+
sel ? "\u25B8" : freshDot ? "\u25CF" : "\u25CB",
|
|
1044
1040
|
" "
|
|
1045
1041
|
] }),
|
|
1046
1042
|
/* @__PURE__ */ jsx2(Text2, { color: r.source === "local" ? theme.ok : "#4f6db8", children: r.source === "local" ? "LOC " : "CLD " }),
|
|
@@ -1053,7 +1049,7 @@ function LivePanel({ active: active2 }) {
|
|
|
1053
1049
|
String(r.denies).padStart(3),
|
|
1054
1050
|
"d "
|
|
1055
1051
|
] }),
|
|
1056
|
-
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: ago(r.lastAt) })
|
|
1052
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: (r.trust != null ? `t${r.trust} ` : "") + ago(r.lastAt) })
|
|
1057
1053
|
] }, r.id);
|
|
1058
1054
|
})
|
|
1059
1055
|
] }),
|
|
@@ -1073,11 +1069,12 @@ function LivePanel({ active: active2 }) {
|
|
|
1073
1069
|
PaneTitle,
|
|
1074
1070
|
{
|
|
1075
1071
|
label: "TOOL STREAM",
|
|
1076
|
-
extra: `newest first \xB7 last ${STREAM_KEEP} \xB7 filter ${filter}${clampedScroll > 0 ? ` \xB7 \u25BC${clampedScroll} older` : ""} \xB7 f filter \xB7 \u2191\u2193 scroll \xB7 s sessions`,
|
|
1072
|
+
extra: `newest first \xB7 last ${STREAM_KEEP} \xB7 filter ${filter}${clampedScroll > 0 ? ` \xB7 \u25BC${clampedScroll} older` : ""} \xB7 f filter \xB7 / search \xB7 \u2191\u2193 scroll \xB7 s sessions`,
|
|
1077
1073
|
width: innerW
|
|
1078
1074
|
}
|
|
1079
1075
|
),
|
|
1080
1076
|
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", height: streamRows, overflow: "hidden", children: [
|
|
1077
|
+
searchRow,
|
|
1081
1078
|
localOn === false ? /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
1082
1079
|
/* @__PURE__ */ jsx2(Text2, { color: theme.warn, children: "local logs off" }),
|
|
1083
1080
|
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2014 enable: dashboard \u2192 Settings \u2192 Local logs \xB7 hooks write ~/.solongate/local-logs \xB7 view: `solongate logs-server`" })
|
|
@@ -1090,15 +1087,15 @@ function LivePanel({ active: active2 }) {
|
|
|
1090
1087
|
] }),
|
|
1091
1088
|
/* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
1092
1089
|
/* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", bold: true, children: " LIVE " }),
|
|
1093
|
-
/* @__PURE__ */ jsx2(Text2, { backgroundColor: "#0b1530", color: "#4f6db8", children: ` local ${localOn ? "on" : "off"} \xB7 ${localBuf.length} loc/${
|
|
1094
|
-
/* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: " space copy-mode \xB7 f filter \xB7 s sessions \xB7 esc menu \xB7 q quit " })
|
|
1090
|
+
/* @__PURE__ */ jsx2(Text2, { backgroundColor: "#0b1530", color: "#4f6db8", children: ` local ${localOn ? "on" : "off"} \xB7 ${localBuf.length} loc/${cloudBuf.length} cld \xB7 top ${topTools.map(([t, c2]) => `${t}\xD7${c2}`).join(" ") || "\u2014"} ` }),
|
|
1091
|
+
/* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: " space copy-mode \xB7 / search \xB7 f filter \xB7 s sessions \xB7 esc menu \xB7 q quit " })
|
|
1095
1092
|
] })
|
|
1096
1093
|
] });
|
|
1097
1094
|
}
|
|
1098
1095
|
|
|
1099
1096
|
// src/tui/panels/Policies.tsx
|
|
1100
1097
|
import { Box as Box3, Text as Text3, useInput as useInput2 } from "ink";
|
|
1101
|
-
import
|
|
1098
|
+
import TextInput2 from "ink-text-input";
|
|
1102
1099
|
import { useEffect as useEffect3, useState as useState3 } from "react";
|
|
1103
1100
|
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
1104
1101
|
var constr = (r, g, side) => (r[g]?.[side] ?? []).join(", ");
|
|
@@ -1308,7 +1305,7 @@ function PoliciesPanel({ focused }) {
|
|
|
1308
1305
|
return /* @__PURE__ */ jsxs3(Box3, { children: [
|
|
1309
1306
|
/* @__PURE__ */ jsx3(Text3, { color: active2 ? theme.accentBright : void 0, children: (active2 ? "\u25B8 " : " ") + f.label.padEnd(13) }),
|
|
1310
1307
|
active2 && editing && f.kind === "text" ? /* @__PURE__ */ jsx3(
|
|
1311
|
-
|
|
1308
|
+
TextInput2,
|
|
1312
1309
|
{
|
|
1313
1310
|
value: editVal,
|
|
1314
1311
|
onChange: setEditVal,
|
|
@@ -1463,7 +1460,7 @@ function FieldRow({ label, active: active2, children }) {
|
|
|
1463
1460
|
|
|
1464
1461
|
// src/tui/panels/Dlp.tsx
|
|
1465
1462
|
import { Box as Box5, Text as Text5, useInput as useInput4 } from "ink";
|
|
1466
|
-
import
|
|
1463
|
+
import TextInput3 from "ink-text-input";
|
|
1467
1464
|
import { useEffect as useEffect5, useState as useState5 } from "react";
|
|
1468
1465
|
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1469
1466
|
var MODES2 = ["off", "detect", "block"];
|
|
@@ -1566,7 +1563,7 @@ function DlpPanel({ focused }) {
|
|
|
1566
1563
|
adding ? /* @__PURE__ */ jsxs5(Box5, { marginTop: 1, children: [
|
|
1567
1564
|
/* @__PURE__ */ jsx5(Text5, { color: theme.warn, children: adding === "name" ? "New pattern name: " : `Regex for "${newName}": ` }),
|
|
1568
1565
|
adding === "name" ? /* @__PURE__ */ jsx5(
|
|
1569
|
-
|
|
1566
|
+
TextInput3,
|
|
1570
1567
|
{
|
|
1571
1568
|
value: newName,
|
|
1572
1569
|
onChange: setNewName,
|
|
@@ -1577,7 +1574,7 @@ function DlpPanel({ focused }) {
|
|
|
1577
1574
|
}
|
|
1578
1575
|
}
|
|
1579
1576
|
) : /* @__PURE__ */ jsx5(
|
|
1580
|
-
|
|
1577
|
+
TextInput3,
|
|
1581
1578
|
{
|
|
1582
1579
|
value: newRe,
|
|
1583
1580
|
onChange: setNewRe,
|
|
@@ -1670,7 +1667,7 @@ function StatsPanel({ active: active2 }) {
|
|
|
1670
1667
|
|
|
1671
1668
|
// src/tui/panels/Audit.tsx
|
|
1672
1669
|
import { Box as Box7, Text as Text7, useInput as useInput5 } from "ink";
|
|
1673
|
-
import
|
|
1670
|
+
import TextInput4 from "ink-text-input";
|
|
1674
1671
|
import { useState as useState6 } from "react";
|
|
1675
1672
|
import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
1676
1673
|
var DECISIONS = [void 0, "DENY", "ALLOW"];
|
|
@@ -1789,7 +1786,7 @@ function AuditPanel({ active: active2, focused }) {
|
|
|
1789
1786
|
": "
|
|
1790
1787
|
] }),
|
|
1791
1788
|
/* @__PURE__ */ jsx7(
|
|
1792
|
-
|
|
1789
|
+
TextInput4,
|
|
1793
1790
|
{
|
|
1794
1791
|
value: editing === "tool" ? tool : editing === "agent" ? agent : search,
|
|
1795
1792
|
onChange: editing === "tool" ? setTool : editing === "agent" ? setAgent : setSearch,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solongate/proxy",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.75.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": {
|