@solongate/proxy 0.79.0 → 0.80.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/api-client/settings.d.ts +6 -0
- package/dist/commands/index.js +9 -1
- package/dist/index.js +167 -8
- package/dist/tui/index.js +166 -7
- package/package.json +1 -1
|
@@ -20,6 +20,12 @@ export interface GuardStatus {
|
|
|
20
20
|
outdated_count: number;
|
|
21
21
|
}
|
|
22
22
|
export declare function getGuardStatus(): Promise<GuardStatus>;
|
|
23
|
+
export declare function getSelfProtection(): Promise<{
|
|
24
|
+
enabled: boolean;
|
|
25
|
+
}>;
|
|
26
|
+
export declare function setSelfProtection(enabled: boolean): Promise<{
|
|
27
|
+
enabled: boolean;
|
|
28
|
+
}>;
|
|
23
29
|
export interface AlertRule {
|
|
24
30
|
id: string;
|
|
25
31
|
name: string;
|
package/dist/commands/index.js
CHANGED
|
@@ -204,8 +204,10 @@ __export(settings_exports, {
|
|
|
204
204
|
getGuardStatus: () => getGuardStatus,
|
|
205
205
|
getRateLimitHistory: () => getRateLimitHistory,
|
|
206
206
|
getSecurityLayers: () => getSecurityLayers,
|
|
207
|
+
getSelfProtection: () => getSelfProtection,
|
|
207
208
|
getWebhooks: () => getWebhooks,
|
|
208
|
-
setSecurityLayers: () => setSecurityLayers
|
|
209
|
+
setSecurityLayers: () => setSecurityLayers,
|
|
210
|
+
setSelfProtection: () => setSelfProtection
|
|
209
211
|
});
|
|
210
212
|
function getSecurityLayers() {
|
|
211
213
|
return request("GET", "/settings/security-layers");
|
|
@@ -222,6 +224,12 @@ function clearRateLimitHistory() {
|
|
|
222
224
|
function getGuardStatus() {
|
|
223
225
|
return request("GET", "/settings/guard-status");
|
|
224
226
|
}
|
|
227
|
+
function getSelfProtection() {
|
|
228
|
+
return request("GET", "/settings/self-protection");
|
|
229
|
+
}
|
|
230
|
+
function setSelfProtection(enabled) {
|
|
231
|
+
return request("PUT", "/settings/self-protection", { body: { enabled } });
|
|
232
|
+
}
|
|
225
233
|
function getAlerts() {
|
|
226
234
|
return request("GET", "/settings/denial-alerts");
|
|
227
235
|
}
|
package/dist/index.js
CHANGED
|
@@ -6875,8 +6875,10 @@ __export(settings_exports, {
|
|
|
6875
6875
|
getGuardStatus: () => getGuardStatus,
|
|
6876
6876
|
getRateLimitHistory: () => getRateLimitHistory,
|
|
6877
6877
|
getSecurityLayers: () => getSecurityLayers,
|
|
6878
|
+
getSelfProtection: () => getSelfProtection,
|
|
6878
6879
|
getWebhooks: () => getWebhooks,
|
|
6879
|
-
setSecurityLayers: () => setSecurityLayers
|
|
6880
|
+
setSecurityLayers: () => setSecurityLayers,
|
|
6881
|
+
setSelfProtection: () => setSelfProtection
|
|
6880
6882
|
});
|
|
6881
6883
|
function getSecurityLayers() {
|
|
6882
6884
|
return request("GET", "/settings/security-layers");
|
|
@@ -6893,6 +6895,12 @@ function clearRateLimitHistory() {
|
|
|
6893
6895
|
function getGuardStatus() {
|
|
6894
6896
|
return request("GET", "/settings/guard-status");
|
|
6895
6897
|
}
|
|
6898
|
+
function getSelfProtection() {
|
|
6899
|
+
return request("GET", "/settings/self-protection");
|
|
6900
|
+
}
|
|
6901
|
+
function setSelfProtection(enabled) {
|
|
6902
|
+
return request("PUT", "/settings/self-protection", { body: { enabled } });
|
|
6903
|
+
}
|
|
6896
6904
|
function getAlerts() {
|
|
6897
6905
|
return request("GET", "/settings/denial-alerts");
|
|
6898
6906
|
}
|
|
@@ -7940,6 +7948,7 @@ function PoliciesPanel({ focused }) {
|
|
|
7940
7948
|
const [pi, setPi] = useState3(0);
|
|
7941
7949
|
const [ri, setRi] = useState3(0);
|
|
7942
7950
|
const [fi, setFi] = useState3(0);
|
|
7951
|
+
const [vi, setVi] = useState3(0);
|
|
7943
7952
|
const [rules, setRules] = useState3([]);
|
|
7944
7953
|
const [mode, setMode] = useState3("denylist");
|
|
7945
7954
|
const [dirty, setDirty] = useState3(false);
|
|
@@ -7948,6 +7957,10 @@ function PoliciesPanel({ focused }) {
|
|
|
7948
7957
|
const [status, setStatus] = useState3(null);
|
|
7949
7958
|
const selected = policies[Math.min(pi, Math.max(0, policies.length - 1))];
|
|
7950
7959
|
const detail = useLoader(() => selected ? api.policies.get(selected.id) : Promise.resolve(null), [selected?.id]);
|
|
7960
|
+
const versionsQ = useLoader(
|
|
7961
|
+
() => view === "versions" && selected ? api.policies.versions(selected.id, { limit: 50 }) : Promise.resolve(null),
|
|
7962
|
+
[view, selected?.id]
|
|
7963
|
+
);
|
|
7951
7964
|
useEffect3(() => {
|
|
7952
7965
|
if (detail.data) {
|
|
7953
7966
|
setRules(detail.data.rules);
|
|
@@ -8022,10 +8035,41 @@ function PoliciesPanel({ focused }) {
|
|
|
8022
8035
|
}
|
|
8023
8036
|
} else if (input === "m") {
|
|
8024
8037
|
mutate(rules, mode === "denylist" ? "whitelist" : "denylist");
|
|
8038
|
+
} else if (input === "n") {
|
|
8039
|
+
const nr = { id: `rule-${Date.now()}`, description: "", effect: "DENY", priority: 100, toolPattern: "*", minimumTrustLevel: "UNTRUSTED", enabled: true };
|
|
8040
|
+
mutate([nr, ...rules]);
|
|
8041
|
+
setRi(0);
|
|
8042
|
+
setFi(0);
|
|
8043
|
+
setView("rule");
|
|
8044
|
+
} else if (input === "D") {
|
|
8045
|
+
setStatus("Dry-running\u2026");
|
|
8046
|
+
void api.policies.dryRun({ rules, mode }).then((res) => setStatus(`dry-run ${res.evaluated} calls \xB7 allow ${res.would_allow} / deny ${res.would_deny} \xB7 newly blocked ${res.newly_blocked} \xB7 newly allowed ${res.newly_allowed}`)).catch((e) => setStatus("\u2717 " + (e instanceof Error ? e.message : String(e))));
|
|
8047
|
+
} else if (input === "v") {
|
|
8048
|
+
setVi(0);
|
|
8049
|
+
setView("versions");
|
|
8025
8050
|
} else if (input === "s") void save();
|
|
8026
8051
|
else if (input === "x") discard();
|
|
8027
8052
|
return;
|
|
8028
8053
|
}
|
|
8054
|
+
if (view === "versions") {
|
|
8055
|
+
const vers = versionsQ.data?.versions ?? [];
|
|
8056
|
+
if (key.leftArrow) return void setView("rules");
|
|
8057
|
+
if (key.upArrow) setVi((n) => Math.max(0, n - 1));
|
|
8058
|
+
else if (key.downArrow) setVi((n) => Math.min(vers.length - 1, n + 1));
|
|
8059
|
+
else if (key.return) {
|
|
8060
|
+
const v = vers[vi];
|
|
8061
|
+
if (v && selected) {
|
|
8062
|
+
setStatus("Rolling back\u2026");
|
|
8063
|
+
void api.policies.rollback(selected.id, v.version).then((r) => {
|
|
8064
|
+
setStatus(`\u2713 rolled back to v${v.version} \u2192 new v${r.version}`);
|
|
8065
|
+
detail.reload();
|
|
8066
|
+
list6.reload();
|
|
8067
|
+
setView("rules");
|
|
8068
|
+
}).catch((e) => setStatus("\u2717 " + (e instanceof Error ? e.message : String(e))));
|
|
8069
|
+
}
|
|
8070
|
+
}
|
|
8071
|
+
return;
|
|
8072
|
+
}
|
|
8029
8073
|
const rule2 = rules[ri];
|
|
8030
8074
|
if (!rule2) return setView("rules");
|
|
8031
8075
|
const field = FIELDS[fi];
|
|
@@ -8070,7 +8114,7 @@ function PoliciesPanel({ focused }) {
|
|
|
8070
8114
|
/* @__PURE__ */ jsx3(Text3, { color: mode === "whitelist" ? theme.warn : void 0, children: mode }),
|
|
8071
8115
|
dirtyTag
|
|
8072
8116
|
] }),
|
|
8073
|
-
/* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: "\u2191\u2193
|
|
8117
|
+
/* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: "\u2191\u2193 \xB7 enter edit \xB7 space on/off \xB7 e effect \xB7 n new \xB7 d del \xB7 m mode \xB7 D dry-run \xB7 v versions \xB7 s save \xB7 \u2190 back" }),
|
|
8074
8118
|
/* @__PURE__ */ jsx3(Box3, { marginTop: 1, children: /* @__PURE__ */ jsx3(
|
|
8075
8119
|
Table,
|
|
8076
8120
|
{
|
|
@@ -8096,6 +8140,33 @@ function PoliciesPanel({ focused }) {
|
|
|
8096
8140
|
status ? /* @__PURE__ */ jsx3(Text3, { color: status.startsWith("\u2717") ? theme.bad : theme.ok, children: status }) : null
|
|
8097
8141
|
] }) });
|
|
8098
8142
|
}
|
|
8143
|
+
if (view === "versions") {
|
|
8144
|
+
const vers = versionsQ.data?.versions ?? [];
|
|
8145
|
+
return /* @__PURE__ */ jsx3(DataView, { loading: versionsQ.loading && !versionsQ.data, error: versionsQ.error, empty: !!versionsQ.data && vers.length === 0, emptyText: "No versions.", children: /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
|
|
8146
|
+
/* @__PURE__ */ jsx3(Text3, { bold: true, color: theme.accentBright, children: truncate2(selected?.name ?? "", 28) }),
|
|
8147
|
+
/* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: "\u2191\u2193 select \xB7 enter roll back to that version \xB7 \u2190 back" }),
|
|
8148
|
+
/* @__PURE__ */ jsx3(Box3, { marginTop: 1, children: /* @__PURE__ */ jsx3(
|
|
8149
|
+
Table,
|
|
8150
|
+
{
|
|
8151
|
+
columns: [
|
|
8152
|
+
{ header: "", width: 2 },
|
|
8153
|
+
{ header: "VER", width: 5 },
|
|
8154
|
+
{ header: "RULES", width: 5 },
|
|
8155
|
+
{ header: "REASON", width: 34 },
|
|
8156
|
+
{ header: "WHEN", width: 16 }
|
|
8157
|
+
],
|
|
8158
|
+
rows: vers.map((v, i) => [
|
|
8159
|
+
{ value: i === vi ? "\u25B8" : "", color: theme.accentBright },
|
|
8160
|
+
{ value: `v${v.version}`, bold: i === vi },
|
|
8161
|
+
{ value: String(v.rules_count), dim: true },
|
|
8162
|
+
{ value: truncate2(v.reason || "\u2014", 34) },
|
|
8163
|
+
{ value: truncate2(v.created_at, 16), dim: true }
|
|
8164
|
+
])
|
|
8165
|
+
}
|
|
8166
|
+
) }),
|
|
8167
|
+
status ? /* @__PURE__ */ jsx3(Text3, { color: status.startsWith("\u2717") ? theme.bad : theme.ok, children: status }) : null
|
|
8168
|
+
] }) });
|
|
8169
|
+
}
|
|
8099
8170
|
const rule = rules[ri];
|
|
8100
8171
|
return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
|
|
8101
8172
|
/* @__PURE__ */ jsxs3(Box3, { children: [
|
|
@@ -8319,12 +8390,19 @@ function DlpPanel({ focused }) {
|
|
|
8319
8390
|
const [adding, setAdding] = useState5(null);
|
|
8320
8391
|
const [newName, setNewName] = useState5("");
|
|
8321
8392
|
const [newRe, setNewRe] = useState5("");
|
|
8393
|
+
const [ghostOn, setGhostOn] = useState5(false);
|
|
8394
|
+
const spQ = useLoader(() => api.settings.getSelfProtection());
|
|
8395
|
+
const [selfProt, setSelfProt] = useState5(null);
|
|
8322
8396
|
useEffect5(() => {
|
|
8323
8397
|
if (q.data && !dlp) {
|
|
8324
8398
|
setDlp({ ...q.data.layers.dlp, patterns: [...q.data.layers.dlp.patterns], custom: [...q.data.layers.dlp.custom] });
|
|
8325
8399
|
setAvailable(q.data.availablePatterns);
|
|
8400
|
+
setGhostOn(q.data.layers.ghost.mode === "on");
|
|
8326
8401
|
}
|
|
8327
8402
|
}, [q.data, dlp]);
|
|
8403
|
+
useEffect5(() => {
|
|
8404
|
+
if (spQ.data && selfProt === null) setSelfProt(spQ.data.enabled);
|
|
8405
|
+
}, [spQ.data, selfProt]);
|
|
8328
8406
|
const mutate = (next) => {
|
|
8329
8407
|
setDlp(next);
|
|
8330
8408
|
setDirty(true);
|
|
@@ -8334,14 +8412,27 @@ function DlpPanel({ focused }) {
|
|
|
8334
8412
|
if (!dlp || !q.data) return;
|
|
8335
8413
|
setStatus("Saving\u2026");
|
|
8336
8414
|
try {
|
|
8337
|
-
const
|
|
8415
|
+
const ghost = { ...q.data.layers.ghost, mode: ghostOn ? "on" : "off" };
|
|
8416
|
+
const res = await api.settings.setSecurityLayers({ ...q.data.layers, dlp, ghost });
|
|
8338
8417
|
setDlp({ ...res.layers.dlp, patterns: [...res.layers.dlp.patterns], custom: [...res.layers.dlp.custom] });
|
|
8418
|
+
setGhostOn(res.layers.ghost.mode === "on");
|
|
8339
8419
|
setDirty(false);
|
|
8340
8420
|
setStatus("\u2713 Saved");
|
|
8341
8421
|
} catch (e) {
|
|
8342
8422
|
setStatus("\u2717 " + (e instanceof Error ? e.message : String(e)));
|
|
8343
8423
|
}
|
|
8344
8424
|
};
|
|
8425
|
+
const toggleSelfProt = async () => {
|
|
8426
|
+
const next = !(selfProt ?? false);
|
|
8427
|
+
setSelfProt(next);
|
|
8428
|
+
try {
|
|
8429
|
+
await api.settings.setSelfProtection(next);
|
|
8430
|
+
setStatus(`\u2713 self-protection ${next ? "on" : "off"}`);
|
|
8431
|
+
} catch (e) {
|
|
8432
|
+
setSelfProt(!next);
|
|
8433
|
+
setStatus("\u2717 " + (e instanceof Error ? e.message : String(e)));
|
|
8434
|
+
}
|
|
8435
|
+
};
|
|
8345
8436
|
const discard = () => {
|
|
8346
8437
|
if (q.data) setDlp({ ...q.data.layers.dlp, patterns: [...q.data.layers.dlp.patterns], custom: [...q.data.layers.dlp.custom] });
|
|
8347
8438
|
setDirty(false);
|
|
@@ -8371,7 +8462,12 @@ function DlpPanel({ focused }) {
|
|
|
8371
8462
|
const ci = sel - available.length;
|
|
8372
8463
|
mutate({ ...dlp, custom: dlp.custom.filter((_, i) => i !== ci) });
|
|
8373
8464
|
setSel((n) => Math.max(0, n - 1));
|
|
8374
|
-
} else if (input === "
|
|
8465
|
+
} else if (input === "g") {
|
|
8466
|
+
setGhostOn((v) => !v);
|
|
8467
|
+
setDirty(true);
|
|
8468
|
+
setStatus(null);
|
|
8469
|
+
} else if (input === "P") void toggleSelfProt();
|
|
8470
|
+
else if (input === "s") void save();
|
|
8375
8471
|
else if (input === "x") discard();
|
|
8376
8472
|
},
|
|
8377
8473
|
{ isActive: focused && !adding }
|
|
@@ -8383,7 +8479,13 @@ function DlpPanel({ focused }) {
|
|
|
8383
8479
|
/* @__PURE__ */ jsx5(Text5, { color: modeColor(dlp.mode), children: dlp.mode }),
|
|
8384
8480
|
dirty ? /* @__PURE__ */ jsx5(Text5, { color: theme.warn, children: " \u25CF unsaved (s save \xB7 x discard)" }) : null
|
|
8385
8481
|
] }),
|
|
8386
|
-
/* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: focused ? "\u2191\u2193
|
|
8482
|
+
/* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: focused ? "\u2191\u2193 \xB7 space toggle \xB7 m mode \xB7 a add \xB7 d remove \xB7 g ghost \xB7 P self-protect \xB7 s save" : "press \u2192 to edit" }),
|
|
8483
|
+
/* @__PURE__ */ jsxs5(Box5, { children: [
|
|
8484
|
+
/* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: "ghost: " }),
|
|
8485
|
+
/* @__PURE__ */ jsx5(Text5, { color: ghostOn ? theme.ok : theme.dim, children: ghostOn ? "on" : "off" }),
|
|
8486
|
+
/* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: " self-protection: " }),
|
|
8487
|
+
/* @__PURE__ */ jsx5(Text5, { color: selfProt ? theme.ok : theme.dim, children: selfProt === null ? "\u2026" : selfProt ? "on" : "off" })
|
|
8488
|
+
] }),
|
|
8387
8489
|
/* @__PURE__ */ jsx5(Box5, { marginTop: 1, flexDirection: "column", children: available.map((p, i) => {
|
|
8388
8490
|
const on = enabled.has(p);
|
|
8389
8491
|
const active2 = focused && sel === i;
|
|
@@ -8451,12 +8553,15 @@ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
|
8451
8553
|
function StatsPanel({ active: active2 }) {
|
|
8452
8554
|
const stats = useLoader(() => api.stats.get());
|
|
8453
8555
|
const ts = useLoader(() => api.stats.timeseries({ period: "24h" }));
|
|
8556
|
+
const drift2 = useLoader(() => api.stats.drift(7));
|
|
8454
8557
|
usePoll(() => {
|
|
8455
8558
|
stats.reload();
|
|
8456
8559
|
ts.reload();
|
|
8457
8560
|
}, 5e3, active2);
|
|
8561
|
+
usePoll(drift2.reload, 3e4, active2);
|
|
8458
8562
|
const s = stats.data;
|
|
8459
8563
|
const points = ts.data?.timeseries ?? [];
|
|
8564
|
+
const driftRules = (drift2.data?.rules ?? []).slice(0, 5);
|
|
8460
8565
|
return /* @__PURE__ */ jsx6(DataView, { loading: stats.loading && !s, error: stats.error, children: s ? /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
|
|
8461
8566
|
/* @__PURE__ */ jsxs6(Box6, { children: [
|
|
8462
8567
|
/* @__PURE__ */ jsxs6(Text6, { bold: true, children: [
|
|
@@ -8509,7 +8614,7 @@ function StatsPanel({ active: active2 }) {
|
|
|
8509
8614
|
{ header: "MS", width: 5 },
|
|
8510
8615
|
{ header: "WHEN", width: 6 }
|
|
8511
8616
|
],
|
|
8512
|
-
rows: (s.recent_activity ?? []).slice(0,
|
|
8617
|
+
rows: (s.recent_activity ?? []).slice(0, 6).map((a) => [
|
|
8513
8618
|
{ value: a.decision, color: decisionColor(a.decision) },
|
|
8514
8619
|
{ value: truncate2(a.tool_name, 20), color: theme.accent },
|
|
8515
8620
|
{ value: String(a.evaluation_time_ms ?? "\u2014"), dim: true },
|
|
@@ -8517,6 +8622,31 @@ function StatsPanel({ active: active2 }) {
|
|
|
8517
8622
|
])
|
|
8518
8623
|
}
|
|
8519
8624
|
)
|
|
8625
|
+
] }),
|
|
8626
|
+
/* @__PURE__ */ jsxs6(Box6, { marginTop: 1, flexDirection: "column", children: [
|
|
8627
|
+
/* @__PURE__ */ jsxs6(Text6, { color: theme.dim, children: [
|
|
8628
|
+
"Denial drift ",
|
|
8629
|
+
drift2.data ? `(7d: ${drift2.data.total_current} now vs ${drift2.data.total_previous} prev)` : ""
|
|
8630
|
+
] }),
|
|
8631
|
+
driftRules.length ? /* @__PURE__ */ jsx6(
|
|
8632
|
+
Table,
|
|
8633
|
+
{
|
|
8634
|
+
columns: [
|
|
8635
|
+
{ header: "NOW", width: 5 },
|
|
8636
|
+
{ header: "PREV", width: 5 },
|
|
8637
|
+
{ header: "\u0394", width: 6 },
|
|
8638
|
+
{ header: "RULE", width: 22 },
|
|
8639
|
+
{ header: "REASON", width: 26 }
|
|
8640
|
+
],
|
|
8641
|
+
rows: driftRules.map((r) => [
|
|
8642
|
+
{ value: String(r.current), bold: true },
|
|
8643
|
+
{ value: String(r.previous), dim: true },
|
|
8644
|
+
{ value: r.is_new ? "NEW" : r.spike ? `+${r.delta}` : String(r.delta), color: r.is_new ? theme.ok : r.spike ? theme.bad : void 0 },
|
|
8645
|
+
{ value: truncate2(r.rule_id ?? "\u2014", 22), color: theme.accent },
|
|
8646
|
+
{ value: truncate2(r.reason ?? r.last_tool ?? "\u2014", 26), dim: true }
|
|
8647
|
+
])
|
|
8648
|
+
}
|
|
8649
|
+
) : /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " no denials in window" })
|
|
8520
8650
|
] })
|
|
8521
8651
|
] }) : null });
|
|
8522
8652
|
}
|
|
@@ -8742,7 +8872,16 @@ function App() {
|
|
|
8742
8872
|
const { exit } = useApp();
|
|
8743
8873
|
const [section, setSection] = useState7(0);
|
|
8744
8874
|
const [focus, setFocus] = useState7("nav");
|
|
8875
|
+
const [help, setHelp] = useState7(false);
|
|
8745
8876
|
useInput6((input, key) => {
|
|
8877
|
+
if (help) {
|
|
8878
|
+
setHelp(false);
|
|
8879
|
+
return;
|
|
8880
|
+
}
|
|
8881
|
+
if (input === "?" && focus === "nav") {
|
|
8882
|
+
setHelp(true);
|
|
8883
|
+
return;
|
|
8884
|
+
}
|
|
8746
8885
|
if (focus === "nav") {
|
|
8747
8886
|
if (key.upArrow) setSection((n) => (n - 1 + SECTIONS.length) % SECTIONS.length);
|
|
8748
8887
|
else if (key.downArrow) setSection((n) => (n + 1) % SECTIONS.length);
|
|
@@ -8756,6 +8895,7 @@ function App() {
|
|
|
8756
8895
|
const cols = process.stdout.columns ?? 100;
|
|
8757
8896
|
const rows = process.stdout.rows ?? 30;
|
|
8758
8897
|
const current = SECTIONS[section];
|
|
8898
|
+
if (help) return /* @__PURE__ */ jsx8(HelpOverlay, { cols, rows });
|
|
8759
8899
|
if (current.label === "Live" && focus === "panel") {
|
|
8760
8900
|
return /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", width: cols, height: rows, children: /* @__PURE__ */ jsx8(LivePanel, { active: true, focused: true }) });
|
|
8761
8901
|
}
|
|
@@ -8766,10 +8906,23 @@ function App() {
|
|
|
8766
8906
|
/* @__PURE__ */ jsx8(Box8, { flexDirection: "column", width: 16, borderStyle: "round", borderColor: focus === "nav" ? theme.accent : "gray", paddingX: 1, children: SECTIONS.map((s, i) => /* @__PURE__ */ jsx8(Text8, { color: i === section ? theme.accentBright : void 0, bold: i === section, children: (i === section ? "\u25B8 " : " ") + s.label }, s.label)) }),
|
|
8767
8907
|
/* @__PURE__ */ jsx8(Box8, { flexGrow: 1, borderStyle: "round", borderColor: focus === "panel" ? theme.accent : "gray", paddingX: 1, paddingY: 0, children: /* @__PURE__ */ jsx8(Panel, { active: focus === "panel" || current.label !== "Live", focused: focus === "panel" }) })
|
|
8768
8908
|
] }),
|
|
8769
|
-
/* @__PURE__ */ jsx8(Box8, { children: focus === "nav" ? /* @__PURE__ */ jsx8(KeyHints, { hints: [["\u2191\u2193", "section"], ["\u2192/enter", "open"], ["q", "quit"]] }) : /* @__PURE__ */ jsx8(KeyHints, { hints: [["\u2190/esc", "back"], ["\u2191\u2193", "in-panel"], ["space/s", "act"]] }) })
|
|
8909
|
+
/* @__PURE__ */ jsx8(Box8, { children: focus === "nav" ? /* @__PURE__ */ jsx8(KeyHints, { hints: [["\u2191\u2193", "section"], ["\u2192/enter", "open"], ["?", "help"], ["q", "quit"]] }) : /* @__PURE__ */ jsx8(KeyHints, { hints: [["\u2190/esc", "back"], ["\u2191\u2193", "in-panel"], ["space/s", "act"]] }) })
|
|
8770
8910
|
] });
|
|
8771
8911
|
}
|
|
8772
|
-
|
|
8912
|
+
function HelpOverlay({ cols, rows }) {
|
|
8913
|
+
return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", width: cols, height: rows, paddingX: 2, paddingTop: 1, children: [
|
|
8914
|
+
/* @__PURE__ */ jsx8(Text8, { bold: true, color: theme.accentBright, children: "SolonGate \u2014 keyboard shortcuts" }),
|
|
8915
|
+
/* @__PURE__ */ jsx8(Box8, { marginTop: 1, flexDirection: "column", children: HELP.map(([group, keys]) => /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", marginBottom: 1, children: [
|
|
8916
|
+
/* @__PURE__ */ jsx8(Text8, { bold: true, color: theme.accent, children: group }),
|
|
8917
|
+
keys.map(([k, desc]) => /* @__PURE__ */ jsxs8(Text8, { children: [
|
|
8918
|
+
/* @__PURE__ */ jsx8(Text8, { color: theme.accentBright, children: (" " + k).padEnd(16) }),
|
|
8919
|
+
/* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: desc })
|
|
8920
|
+
] }, k))
|
|
8921
|
+
] }, group)) }),
|
|
8922
|
+
/* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "press any key to close" })
|
|
8923
|
+
] });
|
|
8924
|
+
}
|
|
8925
|
+
var SECTIONS, BANNER_HEX, HELP;
|
|
8773
8926
|
var init_App = __esm({
|
|
8774
8927
|
"src/tui/App.tsx"() {
|
|
8775
8928
|
"use strict";
|
|
@@ -8791,6 +8944,12 @@ var init_App = __esm({
|
|
|
8791
8944
|
{ label: "Audit", Panel: AuditPanel }
|
|
8792
8945
|
];
|
|
8793
8946
|
BANNER_HEX = ["#1432A0", "#2850BE", "#3C6ED7", "#5A8CE6", "#82AAF0", "#AAC8FA"];
|
|
8947
|
+
HELP = [
|
|
8948
|
+
["Global", [["\u2191\u2193", "move between sections"], ["\u2192 / enter", "open a section"], ["\u2190 / esc", "back to the menu"], ["?", "this help"], ["q", "quit"]]],
|
|
8949
|
+
["Live", [["\u2191\u2193", "select a stream row"], ["w", "whitelist the selected DENY"], ["b", "block the selected ALLOW"], ["d / x / r", "filter denies / dlp / rate-limit"], ["f", "local / cloud filter"], ["/", "search"], ["s", "sessions (\u2191\u2193 pick, enter open)"], ["space", "copy mode (freeze)"]]],
|
|
8950
|
+
["Policies", [["\u2191\u2193", "browse / select"], ["enter", "open rules \u2192 open a rule"], ["space", "toggle a rule on/off"], ["e", "flip effect"], ["n", "new rule"], ["d", "delete rule"], ["m", "flip mode"], ["D", "dry-run the draft"], ["v", "versions / rollback"], ["s", "save"], ["x", "discard"]]],
|
|
8951
|
+
["Rate limit / DLP", [["\u2191\u2193", "field / pattern"], ["\u2190\u2192", "adjust (shift = \xB110)"], ["space", "toggle pattern"], ["m", "mode"], ["a / d", "add / remove custom"], ["g", "ghost mode"], ["P", "self-protection"], ["s", "save"]]]
|
|
8952
|
+
];
|
|
8794
8953
|
}
|
|
8795
8954
|
});
|
|
8796
8955
|
|
package/dist/tui/index.js
CHANGED
|
@@ -333,8 +333,10 @@ __export(settings_exports, {
|
|
|
333
333
|
getGuardStatus: () => getGuardStatus,
|
|
334
334
|
getRateLimitHistory: () => getRateLimitHistory,
|
|
335
335
|
getSecurityLayers: () => getSecurityLayers,
|
|
336
|
+
getSelfProtection: () => getSelfProtection,
|
|
336
337
|
getWebhooks: () => getWebhooks,
|
|
337
|
-
setSecurityLayers: () => setSecurityLayers
|
|
338
|
+
setSecurityLayers: () => setSecurityLayers,
|
|
339
|
+
setSelfProtection: () => setSelfProtection
|
|
338
340
|
});
|
|
339
341
|
function getSecurityLayers() {
|
|
340
342
|
return request("GET", "/settings/security-layers");
|
|
@@ -351,6 +353,12 @@ function clearRateLimitHistory() {
|
|
|
351
353
|
function getGuardStatus() {
|
|
352
354
|
return request("GET", "/settings/guard-status");
|
|
353
355
|
}
|
|
356
|
+
function getSelfProtection() {
|
|
357
|
+
return request("GET", "/settings/self-protection");
|
|
358
|
+
}
|
|
359
|
+
function setSelfProtection(enabled) {
|
|
360
|
+
return request("PUT", "/settings/self-protection", { body: { enabled } });
|
|
361
|
+
}
|
|
354
362
|
function getAlerts() {
|
|
355
363
|
return request("GET", "/settings/denial-alerts");
|
|
356
364
|
}
|
|
@@ -1347,6 +1355,7 @@ function PoliciesPanel({ focused }) {
|
|
|
1347
1355
|
const [pi, setPi] = useState3(0);
|
|
1348
1356
|
const [ri, setRi] = useState3(0);
|
|
1349
1357
|
const [fi, setFi] = useState3(0);
|
|
1358
|
+
const [vi, setVi] = useState3(0);
|
|
1350
1359
|
const [rules, setRules] = useState3([]);
|
|
1351
1360
|
const [mode, setMode] = useState3("denylist");
|
|
1352
1361
|
const [dirty, setDirty] = useState3(false);
|
|
@@ -1355,6 +1364,10 @@ function PoliciesPanel({ focused }) {
|
|
|
1355
1364
|
const [status, setStatus] = useState3(null);
|
|
1356
1365
|
const selected = policies[Math.min(pi, Math.max(0, policies.length - 1))];
|
|
1357
1366
|
const detail = useLoader(() => selected ? api.policies.get(selected.id) : Promise.resolve(null), [selected?.id]);
|
|
1367
|
+
const versionsQ = useLoader(
|
|
1368
|
+
() => view === "versions" && selected ? api.policies.versions(selected.id, { limit: 50 }) : Promise.resolve(null),
|
|
1369
|
+
[view, selected?.id]
|
|
1370
|
+
);
|
|
1358
1371
|
useEffect3(() => {
|
|
1359
1372
|
if (detail.data) {
|
|
1360
1373
|
setRules(detail.data.rules);
|
|
@@ -1429,10 +1442,41 @@ function PoliciesPanel({ focused }) {
|
|
|
1429
1442
|
}
|
|
1430
1443
|
} else if (input === "m") {
|
|
1431
1444
|
mutate(rules, mode === "denylist" ? "whitelist" : "denylist");
|
|
1445
|
+
} else if (input === "n") {
|
|
1446
|
+
const nr = { id: `rule-${Date.now()}`, description: "", effect: "DENY", priority: 100, toolPattern: "*", minimumTrustLevel: "UNTRUSTED", enabled: true };
|
|
1447
|
+
mutate([nr, ...rules]);
|
|
1448
|
+
setRi(0);
|
|
1449
|
+
setFi(0);
|
|
1450
|
+
setView("rule");
|
|
1451
|
+
} else if (input === "D") {
|
|
1452
|
+
setStatus("Dry-running\u2026");
|
|
1453
|
+
void api.policies.dryRun({ rules, mode }).then((res) => setStatus(`dry-run ${res.evaluated} calls \xB7 allow ${res.would_allow} / deny ${res.would_deny} \xB7 newly blocked ${res.newly_blocked} \xB7 newly allowed ${res.newly_allowed}`)).catch((e) => setStatus("\u2717 " + (e instanceof Error ? e.message : String(e))));
|
|
1454
|
+
} else if (input === "v") {
|
|
1455
|
+
setVi(0);
|
|
1456
|
+
setView("versions");
|
|
1432
1457
|
} else if (input === "s") void save();
|
|
1433
1458
|
else if (input === "x") discard();
|
|
1434
1459
|
return;
|
|
1435
1460
|
}
|
|
1461
|
+
if (view === "versions") {
|
|
1462
|
+
const vers = versionsQ.data?.versions ?? [];
|
|
1463
|
+
if (key.leftArrow) return void setView("rules");
|
|
1464
|
+
if (key.upArrow) setVi((n) => Math.max(0, n - 1));
|
|
1465
|
+
else if (key.downArrow) setVi((n) => Math.min(vers.length - 1, n + 1));
|
|
1466
|
+
else if (key.return) {
|
|
1467
|
+
const v = vers[vi];
|
|
1468
|
+
if (v && selected) {
|
|
1469
|
+
setStatus("Rolling back\u2026");
|
|
1470
|
+
void api.policies.rollback(selected.id, v.version).then((r) => {
|
|
1471
|
+
setStatus(`\u2713 rolled back to v${v.version} \u2192 new v${r.version}`);
|
|
1472
|
+
detail.reload();
|
|
1473
|
+
list5.reload();
|
|
1474
|
+
setView("rules");
|
|
1475
|
+
}).catch((e) => setStatus("\u2717 " + (e instanceof Error ? e.message : String(e))));
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1478
|
+
return;
|
|
1479
|
+
}
|
|
1436
1480
|
const rule2 = rules[ri];
|
|
1437
1481
|
if (!rule2) return setView("rules");
|
|
1438
1482
|
const field = FIELDS[fi];
|
|
@@ -1477,7 +1521,7 @@ function PoliciesPanel({ focused }) {
|
|
|
1477
1521
|
/* @__PURE__ */ jsx3(Text3, { color: mode === "whitelist" ? theme.warn : void 0, children: mode }),
|
|
1478
1522
|
dirtyTag
|
|
1479
1523
|
] }),
|
|
1480
|
-
/* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: "\u2191\u2193
|
|
1524
|
+
/* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: "\u2191\u2193 \xB7 enter edit \xB7 space on/off \xB7 e effect \xB7 n new \xB7 d del \xB7 m mode \xB7 D dry-run \xB7 v versions \xB7 s save \xB7 \u2190 back" }),
|
|
1481
1525
|
/* @__PURE__ */ jsx3(Box3, { marginTop: 1, children: /* @__PURE__ */ jsx3(
|
|
1482
1526
|
Table,
|
|
1483
1527
|
{
|
|
@@ -1503,6 +1547,33 @@ function PoliciesPanel({ focused }) {
|
|
|
1503
1547
|
status ? /* @__PURE__ */ jsx3(Text3, { color: status.startsWith("\u2717") ? theme.bad : theme.ok, children: status }) : null
|
|
1504
1548
|
] }) });
|
|
1505
1549
|
}
|
|
1550
|
+
if (view === "versions") {
|
|
1551
|
+
const vers = versionsQ.data?.versions ?? [];
|
|
1552
|
+
return /* @__PURE__ */ jsx3(DataView, { loading: versionsQ.loading && !versionsQ.data, error: versionsQ.error, empty: !!versionsQ.data && vers.length === 0, emptyText: "No versions.", children: /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
|
|
1553
|
+
/* @__PURE__ */ jsx3(Text3, { bold: true, color: theme.accentBright, children: truncate(selected?.name ?? "", 28) }),
|
|
1554
|
+
/* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: "\u2191\u2193 select \xB7 enter roll back to that version \xB7 \u2190 back" }),
|
|
1555
|
+
/* @__PURE__ */ jsx3(Box3, { marginTop: 1, children: /* @__PURE__ */ jsx3(
|
|
1556
|
+
Table,
|
|
1557
|
+
{
|
|
1558
|
+
columns: [
|
|
1559
|
+
{ header: "", width: 2 },
|
|
1560
|
+
{ header: "VER", width: 5 },
|
|
1561
|
+
{ header: "RULES", width: 5 },
|
|
1562
|
+
{ header: "REASON", width: 34 },
|
|
1563
|
+
{ header: "WHEN", width: 16 }
|
|
1564
|
+
],
|
|
1565
|
+
rows: vers.map((v, i) => [
|
|
1566
|
+
{ value: i === vi ? "\u25B8" : "", color: theme.accentBright },
|
|
1567
|
+
{ value: `v${v.version}`, bold: i === vi },
|
|
1568
|
+
{ value: String(v.rules_count), dim: true },
|
|
1569
|
+
{ value: truncate(v.reason || "\u2014", 34) },
|
|
1570
|
+
{ value: truncate(v.created_at, 16), dim: true }
|
|
1571
|
+
])
|
|
1572
|
+
}
|
|
1573
|
+
) }),
|
|
1574
|
+
status ? /* @__PURE__ */ jsx3(Text3, { color: status.startsWith("\u2717") ? theme.bad : theme.ok, children: status }) : null
|
|
1575
|
+
] }) });
|
|
1576
|
+
}
|
|
1506
1577
|
const rule = rules[ri];
|
|
1507
1578
|
return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
|
|
1508
1579
|
/* @__PURE__ */ jsxs3(Box3, { children: [
|
|
@@ -1686,12 +1757,19 @@ function DlpPanel({ focused }) {
|
|
|
1686
1757
|
const [adding, setAdding] = useState5(null);
|
|
1687
1758
|
const [newName, setNewName] = useState5("");
|
|
1688
1759
|
const [newRe, setNewRe] = useState5("");
|
|
1760
|
+
const [ghostOn, setGhostOn] = useState5(false);
|
|
1761
|
+
const spQ = useLoader(() => api.settings.getSelfProtection());
|
|
1762
|
+
const [selfProt, setSelfProt] = useState5(null);
|
|
1689
1763
|
useEffect5(() => {
|
|
1690
1764
|
if (q.data && !dlp) {
|
|
1691
1765
|
setDlp({ ...q.data.layers.dlp, patterns: [...q.data.layers.dlp.patterns], custom: [...q.data.layers.dlp.custom] });
|
|
1692
1766
|
setAvailable(q.data.availablePatterns);
|
|
1767
|
+
setGhostOn(q.data.layers.ghost.mode === "on");
|
|
1693
1768
|
}
|
|
1694
1769
|
}, [q.data, dlp]);
|
|
1770
|
+
useEffect5(() => {
|
|
1771
|
+
if (spQ.data && selfProt === null) setSelfProt(spQ.data.enabled);
|
|
1772
|
+
}, [spQ.data, selfProt]);
|
|
1695
1773
|
const mutate = (next) => {
|
|
1696
1774
|
setDlp(next);
|
|
1697
1775
|
setDirty(true);
|
|
@@ -1701,14 +1779,27 @@ function DlpPanel({ focused }) {
|
|
|
1701
1779
|
if (!dlp || !q.data) return;
|
|
1702
1780
|
setStatus("Saving\u2026");
|
|
1703
1781
|
try {
|
|
1704
|
-
const
|
|
1782
|
+
const ghost = { ...q.data.layers.ghost, mode: ghostOn ? "on" : "off" };
|
|
1783
|
+
const res = await api.settings.setSecurityLayers({ ...q.data.layers, dlp, ghost });
|
|
1705
1784
|
setDlp({ ...res.layers.dlp, patterns: [...res.layers.dlp.patterns], custom: [...res.layers.dlp.custom] });
|
|
1785
|
+
setGhostOn(res.layers.ghost.mode === "on");
|
|
1706
1786
|
setDirty(false);
|
|
1707
1787
|
setStatus("\u2713 Saved");
|
|
1708
1788
|
} catch (e) {
|
|
1709
1789
|
setStatus("\u2717 " + (e instanceof Error ? e.message : String(e)));
|
|
1710
1790
|
}
|
|
1711
1791
|
};
|
|
1792
|
+
const toggleSelfProt = async () => {
|
|
1793
|
+
const next = !(selfProt ?? false);
|
|
1794
|
+
setSelfProt(next);
|
|
1795
|
+
try {
|
|
1796
|
+
await api.settings.setSelfProtection(next);
|
|
1797
|
+
setStatus(`\u2713 self-protection ${next ? "on" : "off"}`);
|
|
1798
|
+
} catch (e) {
|
|
1799
|
+
setSelfProt(!next);
|
|
1800
|
+
setStatus("\u2717 " + (e instanceof Error ? e.message : String(e)));
|
|
1801
|
+
}
|
|
1802
|
+
};
|
|
1712
1803
|
const discard = () => {
|
|
1713
1804
|
if (q.data) setDlp({ ...q.data.layers.dlp, patterns: [...q.data.layers.dlp.patterns], custom: [...q.data.layers.dlp.custom] });
|
|
1714
1805
|
setDirty(false);
|
|
@@ -1738,7 +1829,12 @@ function DlpPanel({ focused }) {
|
|
|
1738
1829
|
const ci = sel - available.length;
|
|
1739
1830
|
mutate({ ...dlp, custom: dlp.custom.filter((_, i) => i !== ci) });
|
|
1740
1831
|
setSel((n) => Math.max(0, n - 1));
|
|
1741
|
-
} else if (input === "
|
|
1832
|
+
} else if (input === "g") {
|
|
1833
|
+
setGhostOn((v) => !v);
|
|
1834
|
+
setDirty(true);
|
|
1835
|
+
setStatus(null);
|
|
1836
|
+
} else if (input === "P") void toggleSelfProt();
|
|
1837
|
+
else if (input === "s") void save();
|
|
1742
1838
|
else if (input === "x") discard();
|
|
1743
1839
|
},
|
|
1744
1840
|
{ isActive: focused && !adding }
|
|
@@ -1750,7 +1846,13 @@ function DlpPanel({ focused }) {
|
|
|
1750
1846
|
/* @__PURE__ */ jsx5(Text5, { color: modeColor(dlp.mode), children: dlp.mode }),
|
|
1751
1847
|
dirty ? /* @__PURE__ */ jsx5(Text5, { color: theme.warn, children: " \u25CF unsaved (s save \xB7 x discard)" }) : null
|
|
1752
1848
|
] }),
|
|
1753
|
-
/* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: focused ? "\u2191\u2193
|
|
1849
|
+
/* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: focused ? "\u2191\u2193 \xB7 space toggle \xB7 m mode \xB7 a add \xB7 d remove \xB7 g ghost \xB7 P self-protect \xB7 s save" : "press \u2192 to edit" }),
|
|
1850
|
+
/* @__PURE__ */ jsxs5(Box5, { children: [
|
|
1851
|
+
/* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: "ghost: " }),
|
|
1852
|
+
/* @__PURE__ */ jsx5(Text5, { color: ghostOn ? theme.ok : theme.dim, children: ghostOn ? "on" : "off" }),
|
|
1853
|
+
/* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: " self-protection: " }),
|
|
1854
|
+
/* @__PURE__ */ jsx5(Text5, { color: selfProt ? theme.ok : theme.dim, children: selfProt === null ? "\u2026" : selfProt ? "on" : "off" })
|
|
1855
|
+
] }),
|
|
1754
1856
|
/* @__PURE__ */ jsx5(Box5, { marginTop: 1, flexDirection: "column", children: available.map((p, i) => {
|
|
1755
1857
|
const on = enabled.has(p);
|
|
1756
1858
|
const active2 = focused && sel === i;
|
|
@@ -1807,12 +1909,15 @@ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
|
1807
1909
|
function StatsPanel({ active: active2 }) {
|
|
1808
1910
|
const stats = useLoader(() => api.stats.get());
|
|
1809
1911
|
const ts = useLoader(() => api.stats.timeseries({ period: "24h" }));
|
|
1912
|
+
const drift2 = useLoader(() => api.stats.drift(7));
|
|
1810
1913
|
usePoll(() => {
|
|
1811
1914
|
stats.reload();
|
|
1812
1915
|
ts.reload();
|
|
1813
1916
|
}, 5e3, active2);
|
|
1917
|
+
usePoll(drift2.reload, 3e4, active2);
|
|
1814
1918
|
const s = stats.data;
|
|
1815
1919
|
const points = ts.data?.timeseries ?? [];
|
|
1920
|
+
const driftRules = (drift2.data?.rules ?? []).slice(0, 5);
|
|
1816
1921
|
return /* @__PURE__ */ jsx6(DataView, { loading: stats.loading && !s, error: stats.error, children: s ? /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
|
|
1817
1922
|
/* @__PURE__ */ jsxs6(Box6, { children: [
|
|
1818
1923
|
/* @__PURE__ */ jsxs6(Text6, { bold: true, children: [
|
|
@@ -1865,7 +1970,7 @@ function StatsPanel({ active: active2 }) {
|
|
|
1865
1970
|
{ header: "MS", width: 5 },
|
|
1866
1971
|
{ header: "WHEN", width: 6 }
|
|
1867
1972
|
],
|
|
1868
|
-
rows: (s.recent_activity ?? []).slice(0,
|
|
1973
|
+
rows: (s.recent_activity ?? []).slice(0, 6).map((a) => [
|
|
1869
1974
|
{ value: a.decision, color: decisionColor(a.decision) },
|
|
1870
1975
|
{ value: truncate(a.tool_name, 20), color: theme.accent },
|
|
1871
1976
|
{ value: String(a.evaluation_time_ms ?? "\u2014"), dim: true },
|
|
@@ -1873,6 +1978,31 @@ function StatsPanel({ active: active2 }) {
|
|
|
1873
1978
|
])
|
|
1874
1979
|
}
|
|
1875
1980
|
)
|
|
1981
|
+
] }),
|
|
1982
|
+
/* @__PURE__ */ jsxs6(Box6, { marginTop: 1, flexDirection: "column", children: [
|
|
1983
|
+
/* @__PURE__ */ jsxs6(Text6, { color: theme.dim, children: [
|
|
1984
|
+
"Denial drift ",
|
|
1985
|
+
drift2.data ? `(7d: ${drift2.data.total_current} now vs ${drift2.data.total_previous} prev)` : ""
|
|
1986
|
+
] }),
|
|
1987
|
+
driftRules.length ? /* @__PURE__ */ jsx6(
|
|
1988
|
+
Table,
|
|
1989
|
+
{
|
|
1990
|
+
columns: [
|
|
1991
|
+
{ header: "NOW", width: 5 },
|
|
1992
|
+
{ header: "PREV", width: 5 },
|
|
1993
|
+
{ header: "\u0394", width: 6 },
|
|
1994
|
+
{ header: "RULE", width: 22 },
|
|
1995
|
+
{ header: "REASON", width: 26 }
|
|
1996
|
+
],
|
|
1997
|
+
rows: driftRules.map((r) => [
|
|
1998
|
+
{ value: String(r.current), bold: true },
|
|
1999
|
+
{ value: String(r.previous), dim: true },
|
|
2000
|
+
{ value: r.is_new ? "NEW" : r.spike ? `+${r.delta}` : String(r.delta), color: r.is_new ? theme.ok : r.spike ? theme.bad : void 0 },
|
|
2001
|
+
{ value: truncate(r.rule_id ?? "\u2014", 22), color: theme.accent },
|
|
2002
|
+
{ value: truncate(r.reason ?? r.last_tool ?? "\u2014", 26), dim: true }
|
|
2003
|
+
])
|
|
2004
|
+
}
|
|
2005
|
+
) : /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " no denials in window" })
|
|
1876
2006
|
] })
|
|
1877
2007
|
] }) : null });
|
|
1878
2008
|
}
|
|
@@ -2086,7 +2216,16 @@ function App() {
|
|
|
2086
2216
|
const { exit } = useApp();
|
|
2087
2217
|
const [section, setSection] = useState7(0);
|
|
2088
2218
|
const [focus, setFocus] = useState7("nav");
|
|
2219
|
+
const [help, setHelp] = useState7(false);
|
|
2089
2220
|
useInput6((input, key) => {
|
|
2221
|
+
if (help) {
|
|
2222
|
+
setHelp(false);
|
|
2223
|
+
return;
|
|
2224
|
+
}
|
|
2225
|
+
if (input === "?" && focus === "nav") {
|
|
2226
|
+
setHelp(true);
|
|
2227
|
+
return;
|
|
2228
|
+
}
|
|
2090
2229
|
if (focus === "nav") {
|
|
2091
2230
|
if (key.upArrow) setSection((n) => (n - 1 + SECTIONS.length) % SECTIONS.length);
|
|
2092
2231
|
else if (key.downArrow) setSection((n) => (n + 1) % SECTIONS.length);
|
|
@@ -2100,6 +2239,7 @@ function App() {
|
|
|
2100
2239
|
const cols = process.stdout.columns ?? 100;
|
|
2101
2240
|
const rows = process.stdout.rows ?? 30;
|
|
2102
2241
|
const current = SECTIONS[section];
|
|
2242
|
+
if (help) return /* @__PURE__ */ jsx8(HelpOverlay, { cols, rows });
|
|
2103
2243
|
if (current.label === "Live" && focus === "panel") {
|
|
2104
2244
|
return /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", width: cols, height: rows, children: /* @__PURE__ */ jsx8(LivePanel, { active: true, focused: true }) });
|
|
2105
2245
|
}
|
|
@@ -2110,7 +2250,26 @@ function App() {
|
|
|
2110
2250
|
/* @__PURE__ */ jsx8(Box8, { flexDirection: "column", width: 16, borderStyle: "round", borderColor: focus === "nav" ? theme.accent : "gray", paddingX: 1, children: SECTIONS.map((s, i) => /* @__PURE__ */ jsx8(Text8, { color: i === section ? theme.accentBright : void 0, bold: i === section, children: (i === section ? "\u25B8 " : " ") + s.label }, s.label)) }),
|
|
2111
2251
|
/* @__PURE__ */ jsx8(Box8, { flexGrow: 1, borderStyle: "round", borderColor: focus === "panel" ? theme.accent : "gray", paddingX: 1, paddingY: 0, children: /* @__PURE__ */ jsx8(Panel, { active: focus === "panel" || current.label !== "Live", focused: focus === "panel" }) })
|
|
2112
2252
|
] }),
|
|
2113
|
-
/* @__PURE__ */ jsx8(Box8, { children: focus === "nav" ? /* @__PURE__ */ jsx8(KeyHints, { hints: [["\u2191\u2193", "section"], ["\u2192/enter", "open"], ["q", "quit"]] }) : /* @__PURE__ */ jsx8(KeyHints, { hints: [["\u2190/esc", "back"], ["\u2191\u2193", "in-panel"], ["space/s", "act"]] }) })
|
|
2253
|
+
/* @__PURE__ */ jsx8(Box8, { children: focus === "nav" ? /* @__PURE__ */ jsx8(KeyHints, { hints: [["\u2191\u2193", "section"], ["\u2192/enter", "open"], ["?", "help"], ["q", "quit"]] }) : /* @__PURE__ */ jsx8(KeyHints, { hints: [["\u2190/esc", "back"], ["\u2191\u2193", "in-panel"], ["space/s", "act"]] }) })
|
|
2254
|
+
] });
|
|
2255
|
+
}
|
|
2256
|
+
var HELP = [
|
|
2257
|
+
["Global", [["\u2191\u2193", "move between sections"], ["\u2192 / enter", "open a section"], ["\u2190 / esc", "back to the menu"], ["?", "this help"], ["q", "quit"]]],
|
|
2258
|
+
["Live", [["\u2191\u2193", "select a stream row"], ["w", "whitelist the selected DENY"], ["b", "block the selected ALLOW"], ["d / x / r", "filter denies / dlp / rate-limit"], ["f", "local / cloud filter"], ["/", "search"], ["s", "sessions (\u2191\u2193 pick, enter open)"], ["space", "copy mode (freeze)"]]],
|
|
2259
|
+
["Policies", [["\u2191\u2193", "browse / select"], ["enter", "open rules \u2192 open a rule"], ["space", "toggle a rule on/off"], ["e", "flip effect"], ["n", "new rule"], ["d", "delete rule"], ["m", "flip mode"], ["D", "dry-run the draft"], ["v", "versions / rollback"], ["s", "save"], ["x", "discard"]]],
|
|
2260
|
+
["Rate limit / DLP", [["\u2191\u2193", "field / pattern"], ["\u2190\u2192", "adjust (shift = \xB110)"], ["space", "toggle pattern"], ["m", "mode"], ["a / d", "add / remove custom"], ["g", "ghost mode"], ["P", "self-protection"], ["s", "save"]]]
|
|
2261
|
+
];
|
|
2262
|
+
function HelpOverlay({ cols, rows }) {
|
|
2263
|
+
return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", width: cols, height: rows, paddingX: 2, paddingTop: 1, children: [
|
|
2264
|
+
/* @__PURE__ */ jsx8(Text8, { bold: true, color: theme.accentBright, children: "SolonGate \u2014 keyboard shortcuts" }),
|
|
2265
|
+
/* @__PURE__ */ jsx8(Box8, { marginTop: 1, flexDirection: "column", children: HELP.map(([group, keys]) => /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", marginBottom: 1, children: [
|
|
2266
|
+
/* @__PURE__ */ jsx8(Text8, { bold: true, color: theme.accent, children: group }),
|
|
2267
|
+
keys.map(([k, desc]) => /* @__PURE__ */ jsxs8(Text8, { children: [
|
|
2268
|
+
/* @__PURE__ */ jsx8(Text8, { color: theme.accentBright, children: (" " + k).padEnd(16) }),
|
|
2269
|
+
/* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: desc })
|
|
2270
|
+
] }, k))
|
|
2271
|
+
] }, group)) }),
|
|
2272
|
+
/* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "press any key to close" })
|
|
2114
2273
|
] });
|
|
2115
2274
|
}
|
|
2116
2275
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solongate/proxy",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.80.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": {
|