@solongate/proxy 0.74.0 → 0.75.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 +64 -46
- package/dist/tui/index.js +64 -46
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -7030,6 +7030,7 @@ 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";
|
|
@@ -7135,7 +7136,6 @@ function StreamLine({ e, loc }) {
|
|
|
7135
7136
|
}
|
|
7136
7137
|
function LivePanel({ active: active2 }) {
|
|
7137
7138
|
const [s, setS] = useState2(null);
|
|
7138
|
-
const [tsData, setTsData] = useState2(null);
|
|
7139
7139
|
const [lat, setLat] = useState2([]);
|
|
7140
7140
|
const [cloudBuf, setCloudBuf] = useState2([]);
|
|
7141
7141
|
const [localBuf, setLocalBuf] = useState2([]);
|
|
@@ -7143,6 +7143,8 @@ function LivePanel({ active: active2 }) {
|
|
|
7143
7143
|
const [ring, setRing] = useState2(null);
|
|
7144
7144
|
const [log6, setLog] = useState2([]);
|
|
7145
7145
|
const [filter, setFilter] = useState2("all");
|
|
7146
|
+
const [search, setSearch] = useState2("");
|
|
7147
|
+
const [editingSearch, setEditingSearch] = useState2(false);
|
|
7146
7148
|
const [scroll, setScroll] = useState2(0);
|
|
7147
7149
|
const [mode, setMode] = useState2("stream");
|
|
7148
7150
|
const [pickIdx, setPickIdx] = useState2(0);
|
|
@@ -7262,38 +7264,26 @@ function LivePanel({ active: active2 }) {
|
|
|
7262
7264
|
onApiError(e);
|
|
7263
7265
|
}
|
|
7264
7266
|
}, [onApiError]);
|
|
7265
|
-
const pollSlow = useCallback2(async () => {
|
|
7266
|
-
if (paused()) return;
|
|
7267
|
-
try {
|
|
7268
|
-
const td = await api.stats.timeseries({ period: "24h" });
|
|
7269
|
-
if (!frozenRef.current) setTsData(td);
|
|
7270
|
-
} catch (e) {
|
|
7271
|
-
onApiError(e);
|
|
7272
|
-
}
|
|
7273
|
-
}, [onApiError]);
|
|
7274
7267
|
useEffect2(() => {
|
|
7275
7268
|
if (!active2) return;
|
|
7276
7269
|
void pollFeed();
|
|
7277
7270
|
void pollMedium();
|
|
7278
|
-
void
|
|
7279
|
-
const
|
|
7280
|
-
const t2 = setInterval(() => void pollMedium(), 1e4);
|
|
7281
|
-
const t3 = setInterval(() => void pollSlow(), 3e4);
|
|
7271
|
+
const t1 = setInterval(() => void pollFeed(), 3e3);
|
|
7272
|
+
const t2 = setInterval(() => void pollMedium(), 8e3);
|
|
7282
7273
|
return () => {
|
|
7283
7274
|
clearInterval(t1);
|
|
7284
7275
|
clearInterval(t2);
|
|
7285
|
-
clearInterval(t3);
|
|
7286
7276
|
};
|
|
7287
|
-
}, [active2, pollFeed, pollMedium
|
|
7277
|
+
}, [active2, pollFeed, pollMedium]);
|
|
7288
7278
|
const sessions = useLoader(() => api.agents.live({ limit: 30, includeDeactivated: true }));
|
|
7289
7279
|
const insights = useLoader(() => api.stats.securityInsights(7));
|
|
7290
7280
|
const guard = useLoader(() => api.settings.getGuardStatus());
|
|
7291
7281
|
usePoll(() => {
|
|
7292
7282
|
if (!paused()) sessions.reload();
|
|
7293
|
-
},
|
|
7283
|
+
}, 8e3, active2);
|
|
7294
7284
|
usePoll(() => {
|
|
7295
7285
|
if (!paused()) insights.reload();
|
|
7296
|
-
},
|
|
7286
|
+
}, 2e4, active2);
|
|
7297
7287
|
usePoll(() => {
|
|
7298
7288
|
if (!paused()) guard.reload();
|
|
7299
7289
|
}, 6e4, active2);
|
|
@@ -7329,21 +7319,17 @@ function LivePanel({ active: active2 }) {
|
|
|
7329
7319
|
}
|
|
7330
7320
|
const cloudDeduped = cloudBuf.filter((e) => !localKeys.has(`${e.tool}|${e.decision}|${Math.round(e.at / 6e4)}`));
|
|
7331
7321
|
const mergedAll = [...cloudDeduped, ...localBuf].sort((a, b) => a.at - b.at);
|
|
7332
|
-
const points = tsData?.timeseries ?? [];
|
|
7333
7322
|
const nowSlot = Math.floor(nowMs / 1e4);
|
|
7334
|
-
const
|
|
7335
|
-
const
|
|
7323
|
+
const traffic = new Array(60).fill(0);
|
|
7324
|
+
const trafficHot = new Array(60).fill(false);
|
|
7336
7325
|
for (const e of mergedAll) {
|
|
7337
7326
|
const idx = 59 - (nowSlot - Math.floor(e.at / 1e4));
|
|
7338
7327
|
if (idx >= 0 && idx < 60) {
|
|
7339
|
-
|
|
7340
|
-
if (e.decision !== "ALLOW")
|
|
7328
|
+
traffic[idx]++;
|
|
7329
|
+
if (e.decision !== "ALLOW") trafficHot[idx] = true;
|
|
7341
7330
|
}
|
|
7342
7331
|
}
|
|
7343
|
-
const
|
|
7344
|
-
const traffic = localTraffic ? slotCounts : points.map((p) => p.total);
|
|
7345
|
-
const trafficHot = localTraffic ? slotHot : points.map((p) => p.denied > 0);
|
|
7346
|
-
const trafficLabel = localTraffic ? "calls/10s \xB7 last 10m" : "24h \xB7 cloud";
|
|
7332
|
+
const trafficLabel = "calls/10s \xB7 last 10m \xB7 live";
|
|
7347
7333
|
const rl = ins.layers?.rateLimit;
|
|
7348
7334
|
const dl = ins.layers?.dlp;
|
|
7349
7335
|
const gh = ins.layers?.ghost;
|
|
@@ -7388,7 +7374,9 @@ function LivePanel({ active: active2 }) {
|
|
|
7388
7374
|
const localSessIds = new Set(localSess.keys());
|
|
7389
7375
|
if (ring?.session) localSessIds.add(ring.session);
|
|
7390
7376
|
const isLoc = (e) => e.source === "local" || !!e.session && localSessIds.has(e.session);
|
|
7391
|
-
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);
|
|
7392
7380
|
const visibleDesc = filtered.slice(-STREAM_KEEP).reverse();
|
|
7393
7381
|
const chartH = rows >= 36 ? 6 : 4;
|
|
7394
7382
|
const colH = rows >= 32 ? 7 : 5;
|
|
@@ -7408,12 +7396,21 @@ function LivePanel({ active: active2 }) {
|
|
|
7408
7396
|
const key = `${e.at}:${e.tool}:${e.decision}`;
|
|
7409
7397
|
if (seen.has(key)) continue;
|
|
7410
7398
|
seen.add(key);
|
|
7411
|
-
all.push(e);
|
|
7399
|
+
if (matches(e)) all.push(e);
|
|
7412
7400
|
}
|
|
7413
7401
|
return all.sort((a, b) => b.at - a.at);
|
|
7414
7402
|
})() : [];
|
|
7415
7403
|
useInput(
|
|
7416
7404
|
(input, key) => {
|
|
7405
|
+
if (input === " ") {
|
|
7406
|
+
setFrozen((f) => !f);
|
|
7407
|
+
return;
|
|
7408
|
+
}
|
|
7409
|
+
if (frozen) return;
|
|
7410
|
+
if (input === "/" && mode !== "pick") {
|
|
7411
|
+
setEditingSearch(true);
|
|
7412
|
+
return;
|
|
7413
|
+
}
|
|
7417
7414
|
if (mode === "detail") {
|
|
7418
7415
|
const maxD = Math.max(0, detailEntries.length - (rows - 10));
|
|
7419
7416
|
if (key.leftArrow) {
|
|
@@ -7437,11 +7434,6 @@ function LivePanel({ active: active2 }) {
|
|
|
7437
7434
|
} else if (input === "s" || key.leftArrow) setMode("stream");
|
|
7438
7435
|
return;
|
|
7439
7436
|
}
|
|
7440
|
-
if (input === " ") {
|
|
7441
|
-
setFrozen((f) => !f);
|
|
7442
|
-
return;
|
|
7443
|
-
}
|
|
7444
|
-
if (frozen) return;
|
|
7445
7437
|
if (input === "f") {
|
|
7446
7438
|
setFilter((f) => FILTERS[(FILTERS.indexOf(f) + 1) % FILTERS.length]);
|
|
7447
7439
|
setScroll(0);
|
|
@@ -7453,8 +7445,32 @@ function LivePanel({ active: active2 }) {
|
|
|
7453
7445
|
else if (key.pageDown) setScroll((n) => Math.min(maxScroll, n + streamRows));
|
|
7454
7446
|
else if (key.pageUp) setScroll((n) => Math.max(0, n - streamRows));
|
|
7455
7447
|
},
|
|
7456
|
-
{ isActive: active2 }
|
|
7448
|
+
{ isActive: active2 && !editingSearch }
|
|
7457
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
|
+
if (v.endsWith("/")) {
|
|
7458
|
+
setEditingSearch(false);
|
|
7459
|
+
return;
|
|
7460
|
+
}
|
|
7461
|
+
setSearch(v);
|
|
7462
|
+
setScroll(0);
|
|
7463
|
+
setDetailScroll(0);
|
|
7464
|
+
},
|
|
7465
|
+
onSubmit: () => setEditingSearch(false)
|
|
7466
|
+
}
|
|
7467
|
+
),
|
|
7468
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " (enter/\u200B/ done \xB7 empty = clear)" })
|
|
7469
|
+
] }) : search ? /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
7470
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.warn, children: "search: " }),
|
|
7471
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.accentBright, bold: true, children: search }),
|
|
7472
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: ` ${filtered.length} match \xB7 / edit` })
|
|
7473
|
+
] }) : null;
|
|
7458
7474
|
const sessionDot = (st) => st === "active" ? { ch: "\u25CF", color: theme.ok } : st === "idle" ? { ch: "\u25CC", color: theme.warn } : { ch: "\u25CB", color: theme.dim };
|
|
7459
7475
|
void sessionDot;
|
|
7460
7476
|
if (s === null && localOn === null) {
|
|
@@ -7491,6 +7507,7 @@ function LivePanel({ active: active2 }) {
|
|
|
7491
7507
|
const tl = d.slice(dScroll, dScroll + tlRows);
|
|
7492
7508
|
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingX: 1, children: [
|
|
7493
7509
|
titleBar,
|
|
7510
|
+
searchRow,
|
|
7494
7511
|
/* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
7495
7512
|
/* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#AAC8FA", bold: true, children: ` SESSION ${detail.id.slice(0, 8)} ` }),
|
|
7496
7513
|
/* @__PURE__ */ jsx2(Text2, { color: detail.source === "local" ? theme.ok : "#4f6db8", children: " " + detail.source.toUpperCase() }),
|
|
@@ -7522,7 +7539,7 @@ function LivePanel({ active: active2 }) {
|
|
|
7522
7539
|
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: perms.map(([p, c2]) => `${p}\xD7${c2}`).join(" ") || "\u2014" }),
|
|
7523
7540
|
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502" })
|
|
7524
7541
|
] }),
|
|
7525
|
-
/* @__PURE__ */ jsx2(PaneTitle, { label: "TIMELINE", extra: `${d.length} calls \xB7 newest first${dScroll ? ` \xB7 \u25BC${dScroll} older` : ""} \xB7 \u2191\u2193 scroll \xB7 \u2190 back`, width: innerW }),
|
|
7542
|
+
/* @__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 }),
|
|
7526
7543
|
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", height: tlRows, overflow: "hidden", children: [
|
|
7527
7544
|
tl.length === 0 ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: detailCloud.length === 0 ? spin + " loading history\u2026" : "no entries" }) : null,
|
|
7528
7545
|
tl.map((e) => /* @__PURE__ */ jsx2(StreamLine, { e, loc: isLoc(e) }, e.id))
|
|
@@ -7557,6 +7574,7 @@ function LivePanel({ active: active2 }) {
|
|
|
7557
7574
|
/* @__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" }),
|
|
7558
7575
|
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502" })
|
|
7559
7576
|
] }),
|
|
7577
|
+
searchRow,
|
|
7560
7578
|
/* @__PURE__ */ jsxs2(Box2, { height: 1 + chartH, children: [
|
|
7561
7579
|
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: leftW, marginRight: 2, children: [
|
|
7562
7580
|
/* @__PURE__ */ jsx2(PaneTitle, { label: "TRAFFIC", extra: `${trafficLabel} \xB7 peak ${Math.max(0, ...traffic)} \xB7 red = denials`, width: leftW }),
|
|
@@ -7631,11 +7649,11 @@ function LivePanel({ active: active2 }) {
|
|
|
7631
7649
|
PaneTitle,
|
|
7632
7650
|
{
|
|
7633
7651
|
label: "TOOL STREAM",
|
|
7634
|
-
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`,
|
|
7652
|
+
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`,
|
|
7635
7653
|
width: innerW
|
|
7636
7654
|
}
|
|
7637
7655
|
),
|
|
7638
|
-
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", height: streamRows, overflow: "hidden", children: [
|
|
7656
|
+
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", height: searchRow ? streamRows - 1 : streamRows, overflow: "hidden", children: [
|
|
7639
7657
|
localOn === false ? /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
7640
7658
|
/* @__PURE__ */ jsx2(Text2, { color: theme.warn, children: "local logs off" }),
|
|
7641
7659
|
/* @__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`" })
|
|
@@ -7649,7 +7667,7 @@ function LivePanel({ active: active2 }) {
|
|
|
7649
7667
|
/* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
7650
7668
|
/* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", bold: true, children: " LIVE " }),
|
|
7651
7669
|
/* @__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"} ` }),
|
|
7652
|
-
/* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: " space copy-mode \xB7 f filter \xB7 s sessions \xB7 esc menu \xB7 q quit " })
|
|
7670
|
+
/* @__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 " })
|
|
7653
7671
|
] })
|
|
7654
7672
|
] });
|
|
7655
7673
|
}
|
|
@@ -7681,7 +7699,7 @@ var init_Live = __esm({
|
|
|
7681
7699
|
|
|
7682
7700
|
// src/tui/panels/Policies.tsx
|
|
7683
7701
|
import { Box as Box3, Text as Text3, useInput as useInput2 } from "ink";
|
|
7684
|
-
import
|
|
7702
|
+
import TextInput2 from "ink-text-input";
|
|
7685
7703
|
import { useEffect as useEffect3, useState as useState3 } from "react";
|
|
7686
7704
|
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
7687
7705
|
function ruleSummary(r) {
|
|
@@ -7870,7 +7888,7 @@ function PoliciesPanel({ focused }) {
|
|
|
7870
7888
|
return /* @__PURE__ */ jsxs3(Box3, { children: [
|
|
7871
7889
|
/* @__PURE__ */ jsx3(Text3, { color: active2 ? theme.accentBright : void 0, children: (active2 ? "\u25B8 " : " ") + f.label.padEnd(13) }),
|
|
7872
7890
|
active2 && editing && f.kind === "text" ? /* @__PURE__ */ jsx3(
|
|
7873
|
-
|
|
7891
|
+
TextInput2,
|
|
7874
7892
|
{
|
|
7875
7893
|
value: editVal,
|
|
7876
7894
|
onChange: setEditVal,
|
|
@@ -8066,7 +8084,7 @@ var init_RateLimit = __esm({
|
|
|
8066
8084
|
|
|
8067
8085
|
// src/tui/panels/Dlp.tsx
|
|
8068
8086
|
import { Box as Box5, Text as Text5, useInput as useInput4 } from "ink";
|
|
8069
|
-
import
|
|
8087
|
+
import TextInput3 from "ink-text-input";
|
|
8070
8088
|
import { useEffect as useEffect5, useState as useState5 } from "react";
|
|
8071
8089
|
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
8072
8090
|
function DlpPanel({ focused }) {
|
|
@@ -8168,7 +8186,7 @@ function DlpPanel({ focused }) {
|
|
|
8168
8186
|
adding ? /* @__PURE__ */ jsxs5(Box5, { marginTop: 1, children: [
|
|
8169
8187
|
/* @__PURE__ */ jsx5(Text5, { color: theme.warn, children: adding === "name" ? "New pattern name: " : `Regex for "${newName}": ` }),
|
|
8170
8188
|
adding === "name" ? /* @__PURE__ */ jsx5(
|
|
8171
|
-
|
|
8189
|
+
TextInput3,
|
|
8172
8190
|
{
|
|
8173
8191
|
value: newName,
|
|
8174
8192
|
onChange: setNewName,
|
|
@@ -8179,7 +8197,7 @@ function DlpPanel({ focused }) {
|
|
|
8179
8197
|
}
|
|
8180
8198
|
}
|
|
8181
8199
|
) : /* @__PURE__ */ jsx5(
|
|
8182
|
-
|
|
8200
|
+
TextInput3,
|
|
8183
8201
|
{
|
|
8184
8202
|
value: newRe,
|
|
8185
8203
|
onChange: setNewRe,
|
|
@@ -8292,7 +8310,7 @@ var init_Stats = __esm({
|
|
|
8292
8310
|
|
|
8293
8311
|
// src/tui/panels/Audit.tsx
|
|
8294
8312
|
import { Box as Box7, Text as Text7, useInput as useInput5 } from "ink";
|
|
8295
|
-
import
|
|
8313
|
+
import TextInput4 from "ink-text-input";
|
|
8296
8314
|
import { useState as useState6 } from "react";
|
|
8297
8315
|
import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
8298
8316
|
function AuditPanel({ active: active2, focused }) {
|
|
@@ -8409,7 +8427,7 @@ function AuditPanel({ active: active2, focused }) {
|
|
|
8409
8427
|
": "
|
|
8410
8428
|
] }),
|
|
8411
8429
|
/* @__PURE__ */ jsx7(
|
|
8412
|
-
|
|
8430
|
+
TextInput4,
|
|
8413
8431
|
{
|
|
8414
8432
|
value: editing === "tool" ? tool : editing === "agent" ? agent : search,
|
|
8415
8433
|
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";
|
|
@@ -560,7 +561,6 @@ function StreamLine({ e, loc }) {
|
|
|
560
561
|
var FILTERS = ["all", "local", "cloud"];
|
|
561
562
|
function LivePanel({ active: active2 }) {
|
|
562
563
|
const [s, setS] = useState2(null);
|
|
563
|
-
const [tsData, setTsData] = useState2(null);
|
|
564
564
|
const [lat, setLat] = useState2([]);
|
|
565
565
|
const [cloudBuf, setCloudBuf] = useState2([]);
|
|
566
566
|
const [localBuf, setLocalBuf] = useState2([]);
|
|
@@ -568,6 +568,8 @@ function LivePanel({ active: active2 }) {
|
|
|
568
568
|
const [ring, setRing] = useState2(null);
|
|
569
569
|
const [log, setLog] = useState2([]);
|
|
570
570
|
const [filter, setFilter] = useState2("all");
|
|
571
|
+
const [search, setSearch] = useState2("");
|
|
572
|
+
const [editingSearch, setEditingSearch] = useState2(false);
|
|
571
573
|
const [scroll, setScroll] = useState2(0);
|
|
572
574
|
const [mode, setMode] = useState2("stream");
|
|
573
575
|
const [pickIdx, setPickIdx] = useState2(0);
|
|
@@ -687,38 +689,26 @@ function LivePanel({ active: active2 }) {
|
|
|
687
689
|
onApiError(e);
|
|
688
690
|
}
|
|
689
691
|
}, [onApiError]);
|
|
690
|
-
const pollSlow = useCallback2(async () => {
|
|
691
|
-
if (paused()) return;
|
|
692
|
-
try {
|
|
693
|
-
const td = await api.stats.timeseries({ period: "24h" });
|
|
694
|
-
if (!frozenRef.current) setTsData(td);
|
|
695
|
-
} catch (e) {
|
|
696
|
-
onApiError(e);
|
|
697
|
-
}
|
|
698
|
-
}, [onApiError]);
|
|
699
692
|
useEffect2(() => {
|
|
700
693
|
if (!active2) return;
|
|
701
694
|
void pollFeed();
|
|
702
695
|
void pollMedium();
|
|
703
|
-
void
|
|
704
|
-
const
|
|
705
|
-
const t2 = setInterval(() => void pollMedium(), 1e4);
|
|
706
|
-
const t3 = setInterval(() => void pollSlow(), 3e4);
|
|
696
|
+
const t1 = setInterval(() => void pollFeed(), 3e3);
|
|
697
|
+
const t2 = setInterval(() => void pollMedium(), 8e3);
|
|
707
698
|
return () => {
|
|
708
699
|
clearInterval(t1);
|
|
709
700
|
clearInterval(t2);
|
|
710
|
-
clearInterval(t3);
|
|
711
701
|
};
|
|
712
|
-
}, [active2, pollFeed, pollMedium
|
|
702
|
+
}, [active2, pollFeed, pollMedium]);
|
|
713
703
|
const sessions = useLoader(() => api.agents.live({ limit: 30, includeDeactivated: true }));
|
|
714
704
|
const insights = useLoader(() => api.stats.securityInsights(7));
|
|
715
705
|
const guard = useLoader(() => api.settings.getGuardStatus());
|
|
716
706
|
usePoll(() => {
|
|
717
707
|
if (!paused()) sessions.reload();
|
|
718
|
-
},
|
|
708
|
+
}, 8e3, active2);
|
|
719
709
|
usePoll(() => {
|
|
720
710
|
if (!paused()) insights.reload();
|
|
721
|
-
},
|
|
711
|
+
}, 2e4, active2);
|
|
722
712
|
usePoll(() => {
|
|
723
713
|
if (!paused()) guard.reload();
|
|
724
714
|
}, 6e4, active2);
|
|
@@ -754,21 +744,17 @@ function LivePanel({ active: active2 }) {
|
|
|
754
744
|
}
|
|
755
745
|
const cloudDeduped = cloudBuf.filter((e) => !localKeys.has(`${e.tool}|${e.decision}|${Math.round(e.at / 6e4)}`));
|
|
756
746
|
const mergedAll = [...cloudDeduped, ...localBuf].sort((a, b) => a.at - b.at);
|
|
757
|
-
const points = tsData?.timeseries ?? [];
|
|
758
747
|
const nowSlot = Math.floor(nowMs / 1e4);
|
|
759
|
-
const
|
|
760
|
-
const
|
|
748
|
+
const traffic = new Array(60).fill(0);
|
|
749
|
+
const trafficHot = new Array(60).fill(false);
|
|
761
750
|
for (const e of mergedAll) {
|
|
762
751
|
const idx = 59 - (nowSlot - Math.floor(e.at / 1e4));
|
|
763
752
|
if (idx >= 0 && idx < 60) {
|
|
764
|
-
|
|
765
|
-
if (e.decision !== "ALLOW")
|
|
753
|
+
traffic[idx]++;
|
|
754
|
+
if (e.decision !== "ALLOW") trafficHot[idx] = true;
|
|
766
755
|
}
|
|
767
756
|
}
|
|
768
|
-
const
|
|
769
|
-
const traffic = localTraffic ? slotCounts : points.map((p) => p.total);
|
|
770
|
-
const trafficHot = localTraffic ? slotHot : points.map((p) => p.denied > 0);
|
|
771
|
-
const trafficLabel = localTraffic ? "calls/10s \xB7 last 10m" : "24h \xB7 cloud";
|
|
757
|
+
const trafficLabel = "calls/10s \xB7 last 10m \xB7 live";
|
|
772
758
|
const rl = ins.layers?.rateLimit;
|
|
773
759
|
const dl = ins.layers?.dlp;
|
|
774
760
|
const gh = ins.layers?.ghost;
|
|
@@ -813,7 +799,9 @@ function LivePanel({ active: active2 }) {
|
|
|
813
799
|
const localSessIds = new Set(localSess.keys());
|
|
814
800
|
if (ring?.session) localSessIds.add(ring.session);
|
|
815
801
|
const isLoc = (e) => e.source === "local" || !!e.session && localSessIds.has(e.session);
|
|
816
|
-
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);
|
|
817
805
|
const visibleDesc = filtered.slice(-STREAM_KEEP).reverse();
|
|
818
806
|
const chartH = rows >= 36 ? 6 : 4;
|
|
819
807
|
const colH = rows >= 32 ? 7 : 5;
|
|
@@ -833,12 +821,21 @@ function LivePanel({ active: active2 }) {
|
|
|
833
821
|
const key = `${e.at}:${e.tool}:${e.decision}`;
|
|
834
822
|
if (seen.has(key)) continue;
|
|
835
823
|
seen.add(key);
|
|
836
|
-
all.push(e);
|
|
824
|
+
if (matches(e)) all.push(e);
|
|
837
825
|
}
|
|
838
826
|
return all.sort((a, b) => b.at - a.at);
|
|
839
827
|
})() : [];
|
|
840
828
|
useInput(
|
|
841
829
|
(input, key) => {
|
|
830
|
+
if (input === " ") {
|
|
831
|
+
setFrozen((f) => !f);
|
|
832
|
+
return;
|
|
833
|
+
}
|
|
834
|
+
if (frozen) return;
|
|
835
|
+
if (input === "/" && mode !== "pick") {
|
|
836
|
+
setEditingSearch(true);
|
|
837
|
+
return;
|
|
838
|
+
}
|
|
842
839
|
if (mode === "detail") {
|
|
843
840
|
const maxD = Math.max(0, detailEntries.length - (rows - 10));
|
|
844
841
|
if (key.leftArrow) {
|
|
@@ -862,11 +859,6 @@ function LivePanel({ active: active2 }) {
|
|
|
862
859
|
} else if (input === "s" || key.leftArrow) setMode("stream");
|
|
863
860
|
return;
|
|
864
861
|
}
|
|
865
|
-
if (input === " ") {
|
|
866
|
-
setFrozen((f) => !f);
|
|
867
|
-
return;
|
|
868
|
-
}
|
|
869
|
-
if (frozen) return;
|
|
870
862
|
if (input === "f") {
|
|
871
863
|
setFilter((f) => FILTERS[(FILTERS.indexOf(f) + 1) % FILTERS.length]);
|
|
872
864
|
setScroll(0);
|
|
@@ -878,8 +870,32 @@ function LivePanel({ active: active2 }) {
|
|
|
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
|
+
if (v.endsWith("/")) {
|
|
883
|
+
setEditingSearch(false);
|
|
884
|
+
return;
|
|
885
|
+
}
|
|
886
|
+
setSearch(v);
|
|
887
|
+
setScroll(0);
|
|
888
|
+
setDetailScroll(0);
|
|
889
|
+
},
|
|
890
|
+
onSubmit: () => setEditingSearch(false)
|
|
891
|
+
}
|
|
892
|
+
),
|
|
893
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " (enter/\u200B/ done \xB7 empty = clear)" })
|
|
894
|
+
] }) : search ? /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
895
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.warn, children: "search: " }),
|
|
896
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.accentBright, bold: true, children: search }),
|
|
897
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: ` ${filtered.length} match \xB7 / edit` })
|
|
898
|
+
] }) : null;
|
|
883
899
|
const sessionDot = (st) => st === "active" ? { ch: "\u25CF", color: theme.ok } : st === "idle" ? { ch: "\u25CC", color: theme.warn } : { ch: "\u25CB", color: theme.dim };
|
|
884
900
|
void sessionDot;
|
|
885
901
|
if (s === null && localOn === null) {
|
|
@@ -916,6 +932,7 @@ function LivePanel({ active: active2 }) {
|
|
|
916
932
|
const tl = d.slice(dScroll, dScroll + tlRows);
|
|
917
933
|
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingX: 1, children: [
|
|
918
934
|
titleBar,
|
|
935
|
+
searchRow,
|
|
919
936
|
/* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
920
937
|
/* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#AAC8FA", bold: true, children: ` SESSION ${detail.id.slice(0, 8)} ` }),
|
|
921
938
|
/* @__PURE__ */ jsx2(Text2, { color: detail.source === "local" ? theme.ok : "#4f6db8", children: " " + detail.source.toUpperCase() }),
|
|
@@ -947,7 +964,7 @@ function LivePanel({ active: active2 }) {
|
|
|
947
964
|
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: perms.map(([p, c2]) => `${p}\xD7${c2}`).join(" ") || "\u2014" }),
|
|
948
965
|
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502" })
|
|
949
966
|
] }),
|
|
950
|
-
/* @__PURE__ */ jsx2(PaneTitle, { label: "TIMELINE", extra: `${d.length} calls \xB7 newest first${dScroll ? ` \xB7 \u25BC${dScroll} older` : ""} \xB7 \u2191\u2193 scroll \xB7 \u2190 back`, width: innerW }),
|
|
967
|
+
/* @__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 }),
|
|
951
968
|
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", height: tlRows, overflow: "hidden", children: [
|
|
952
969
|
tl.length === 0 ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: detailCloud.length === 0 ? spin + " loading history\u2026" : "no entries" }) : null,
|
|
953
970
|
tl.map((e) => /* @__PURE__ */ jsx2(StreamLine, { e, loc: isLoc(e) }, e.id))
|
|
@@ -982,6 +999,7 @@ function LivePanel({ active: active2 }) {
|
|
|
982
999
|
/* @__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" }),
|
|
983
1000
|
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502" })
|
|
984
1001
|
] }),
|
|
1002
|
+
searchRow,
|
|
985
1003
|
/* @__PURE__ */ jsxs2(Box2, { height: 1 + chartH, children: [
|
|
986
1004
|
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: leftW, marginRight: 2, children: [
|
|
987
1005
|
/* @__PURE__ */ jsx2(PaneTitle, { label: "TRAFFIC", extra: `${trafficLabel} \xB7 peak ${Math.max(0, ...traffic)} \xB7 red = denials`, width: leftW }),
|
|
@@ -1056,11 +1074,11 @@ function LivePanel({ active: active2 }) {
|
|
|
1056
1074
|
PaneTitle,
|
|
1057
1075
|
{
|
|
1058
1076
|
label: "TOOL STREAM",
|
|
1059
|
-
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`,
|
|
1077
|
+
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`,
|
|
1060
1078
|
width: innerW
|
|
1061
1079
|
}
|
|
1062
1080
|
),
|
|
1063
|
-
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", height: streamRows, overflow: "hidden", children: [
|
|
1081
|
+
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", height: searchRow ? streamRows - 1 : streamRows, overflow: "hidden", children: [
|
|
1064
1082
|
localOn === false ? /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
1065
1083
|
/* @__PURE__ */ jsx2(Text2, { color: theme.warn, children: "local logs off" }),
|
|
1066
1084
|
/* @__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`" })
|
|
@@ -1074,14 +1092,14 @@ function LivePanel({ active: active2 }) {
|
|
|
1074
1092
|
/* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
1075
1093
|
/* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", bold: true, children: " LIVE " }),
|
|
1076
1094
|
/* @__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"} ` }),
|
|
1077
|
-
/* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: " space copy-mode \xB7 f filter \xB7 s sessions \xB7 esc menu \xB7 q quit " })
|
|
1095
|
+
/* @__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 " })
|
|
1078
1096
|
] })
|
|
1079
1097
|
] });
|
|
1080
1098
|
}
|
|
1081
1099
|
|
|
1082
1100
|
// src/tui/panels/Policies.tsx
|
|
1083
1101
|
import { Box as Box3, Text as Text3, useInput as useInput2 } from "ink";
|
|
1084
|
-
import
|
|
1102
|
+
import TextInput2 from "ink-text-input";
|
|
1085
1103
|
import { useEffect as useEffect3, useState as useState3 } from "react";
|
|
1086
1104
|
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
1087
1105
|
var constr = (r, g, side) => (r[g]?.[side] ?? []).join(", ");
|
|
@@ -1291,7 +1309,7 @@ function PoliciesPanel({ focused }) {
|
|
|
1291
1309
|
return /* @__PURE__ */ jsxs3(Box3, { children: [
|
|
1292
1310
|
/* @__PURE__ */ jsx3(Text3, { color: active2 ? theme.accentBright : void 0, children: (active2 ? "\u25B8 " : " ") + f.label.padEnd(13) }),
|
|
1293
1311
|
active2 && editing && f.kind === "text" ? /* @__PURE__ */ jsx3(
|
|
1294
|
-
|
|
1312
|
+
TextInput2,
|
|
1295
1313
|
{
|
|
1296
1314
|
value: editVal,
|
|
1297
1315
|
onChange: setEditVal,
|
|
@@ -1446,7 +1464,7 @@ function FieldRow({ label, active: active2, children }) {
|
|
|
1446
1464
|
|
|
1447
1465
|
// src/tui/panels/Dlp.tsx
|
|
1448
1466
|
import { Box as Box5, Text as Text5, useInput as useInput4 } from "ink";
|
|
1449
|
-
import
|
|
1467
|
+
import TextInput3 from "ink-text-input";
|
|
1450
1468
|
import { useEffect as useEffect5, useState as useState5 } from "react";
|
|
1451
1469
|
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1452
1470
|
var MODES2 = ["off", "detect", "block"];
|
|
@@ -1549,7 +1567,7 @@ function DlpPanel({ focused }) {
|
|
|
1549
1567
|
adding ? /* @__PURE__ */ jsxs5(Box5, { marginTop: 1, children: [
|
|
1550
1568
|
/* @__PURE__ */ jsx5(Text5, { color: theme.warn, children: adding === "name" ? "New pattern name: " : `Regex for "${newName}": ` }),
|
|
1551
1569
|
adding === "name" ? /* @__PURE__ */ jsx5(
|
|
1552
|
-
|
|
1570
|
+
TextInput3,
|
|
1553
1571
|
{
|
|
1554
1572
|
value: newName,
|
|
1555
1573
|
onChange: setNewName,
|
|
@@ -1560,7 +1578,7 @@ function DlpPanel({ focused }) {
|
|
|
1560
1578
|
}
|
|
1561
1579
|
}
|
|
1562
1580
|
) : /* @__PURE__ */ jsx5(
|
|
1563
|
-
|
|
1581
|
+
TextInput3,
|
|
1564
1582
|
{
|
|
1565
1583
|
value: newRe,
|
|
1566
1584
|
onChange: setNewRe,
|
|
@@ -1653,7 +1671,7 @@ function StatsPanel({ active: active2 }) {
|
|
|
1653
1671
|
|
|
1654
1672
|
// src/tui/panels/Audit.tsx
|
|
1655
1673
|
import { Box as Box7, Text as Text7, useInput as useInput5 } from "ink";
|
|
1656
|
-
import
|
|
1674
|
+
import TextInput4 from "ink-text-input";
|
|
1657
1675
|
import { useState as useState6 } from "react";
|
|
1658
1676
|
import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
1659
1677
|
var DECISIONS = [void 0, "DENY", "ALLOW"];
|
|
@@ -1772,7 +1790,7 @@ function AuditPanel({ active: active2, focused }) {
|
|
|
1772
1790
|
": "
|
|
1773
1791
|
] }),
|
|
1774
1792
|
/* @__PURE__ */ jsx7(
|
|
1775
|
-
|
|
1793
|
+
TextInput4,
|
|
1776
1794
|
{
|
|
1777
1795
|
value: editing === "tool" ? tool : editing === "agent" ? agent : search,
|
|
1778
1796
|
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.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": {
|