@solongate/proxy 0.60.0 → 0.61.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 +180 -62
- package/dist/tui/index.js +180 -62
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -7020,56 +7020,128 @@ var init_hooks = __esm({
|
|
|
7020
7020
|
|
|
7021
7021
|
// src/tui/panels/Policies.tsx
|
|
7022
7022
|
import { Box as Box2, Text as Text2, useInput } from "ink";
|
|
7023
|
-
import { useState as useState2 } from "react";
|
|
7023
|
+
import { useEffect as useEffect2, useState as useState2 } from "react";
|
|
7024
7024
|
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
7025
7025
|
function PoliciesPanel({ focused }) {
|
|
7026
|
-
const [sel, setSel] = useState2(0);
|
|
7027
7026
|
const list4 = useLoader(() => api.policies.list());
|
|
7028
7027
|
const policies = list4.data?.policies ?? [];
|
|
7029
|
-
const
|
|
7028
|
+
const [view, setView] = useState2("list");
|
|
7029
|
+
const [pi, setPi] = useState2(0);
|
|
7030
|
+
const [ri, setRi] = useState2(0);
|
|
7031
|
+
const [rules, setRules] = useState2(null);
|
|
7032
|
+
const [mode, setMode] = useState2("denylist");
|
|
7033
|
+
const [status, setStatus] = useState2(null);
|
|
7034
|
+
const selected = policies[Math.min(pi, Math.max(0, policies.length - 1))];
|
|
7030
7035
|
const detail = useLoader(() => selected ? api.policies.get(selected.id) : Promise.resolve(null), [selected?.id]);
|
|
7036
|
+
useEffect2(() => {
|
|
7037
|
+
if (detail.data) {
|
|
7038
|
+
setRules(detail.data.rules);
|
|
7039
|
+
setMode(detail.data.mode ?? "denylist");
|
|
7040
|
+
setRi(0);
|
|
7041
|
+
}
|
|
7042
|
+
}, [detail.data]);
|
|
7043
|
+
const persist = async (nextRules, nextMode) => {
|
|
7044
|
+
if (!detail.data || !selected) return;
|
|
7045
|
+
setStatus("Saving\u2026");
|
|
7046
|
+
try {
|
|
7047
|
+
const res = await api.policies.update(selected.id, {
|
|
7048
|
+
id: selected.id,
|
|
7049
|
+
name: detail.data.name,
|
|
7050
|
+
description: detail.data.description,
|
|
7051
|
+
mode: nextMode,
|
|
7052
|
+
agents: detail.data.agents,
|
|
7053
|
+
rules: nextRules
|
|
7054
|
+
});
|
|
7055
|
+
setRules(res.rules);
|
|
7056
|
+
setStatus("\u2713 Saved v" + res._version);
|
|
7057
|
+
list4.reload();
|
|
7058
|
+
} catch (e) {
|
|
7059
|
+
setStatus("\u2717 " + (e instanceof Error ? e.message : String(e)));
|
|
7060
|
+
}
|
|
7061
|
+
};
|
|
7031
7062
|
useInput(
|
|
7032
|
-
(
|
|
7033
|
-
if (
|
|
7034
|
-
|
|
7063
|
+
(input, key) => {
|
|
7064
|
+
if (view === "list") {
|
|
7065
|
+
if (key.upArrow) setPi((n) => Math.max(0, n - 1));
|
|
7066
|
+
else if (key.downArrow) setPi((n) => Math.min(policies.length - 1, n + 1));
|
|
7067
|
+
else if (key.return || key.rightArrow) {
|
|
7068
|
+
setStatus(null);
|
|
7069
|
+
setView("detail");
|
|
7070
|
+
}
|
|
7071
|
+
return;
|
|
7072
|
+
}
|
|
7073
|
+
const rs2 = rules ?? [];
|
|
7074
|
+
if (key.leftArrow) {
|
|
7075
|
+
setView("list");
|
|
7076
|
+
setStatus(null);
|
|
7077
|
+
} else if (key.upArrow) setRi((n) => Math.max(0, n - 1));
|
|
7078
|
+
else if (key.downArrow) setRi((n) => Math.min(rs2.length - 1, n + 1));
|
|
7079
|
+
else if (input === " " || input === "e") {
|
|
7080
|
+
if (!rs2[ri]) return;
|
|
7081
|
+
const next = rs2.map((r, i) => i === ri ? { ...r, enabled: !r.enabled } : r);
|
|
7082
|
+
setRules(next);
|
|
7083
|
+
void persist(next, mode);
|
|
7084
|
+
} else if (input === "d") {
|
|
7085
|
+
const target = rs2[ri];
|
|
7086
|
+
if (!target || !selected) return;
|
|
7087
|
+
setStatus("Deleting\u2026");
|
|
7088
|
+
void api.policies.revokeRule(selected.id, target.id).then(() => {
|
|
7089
|
+
setStatus("\u2713 Deleted");
|
|
7090
|
+
detail.reload();
|
|
7091
|
+
list4.reload();
|
|
7092
|
+
}).catch((e) => setStatus("\u2717 " + (e instanceof Error ? e.message : String(e))));
|
|
7093
|
+
} else if (input === "m") {
|
|
7094
|
+
const nm = mode === "denylist" ? "whitelist" : "denylist";
|
|
7095
|
+
setMode(nm);
|
|
7096
|
+
void persist(rs2, nm);
|
|
7097
|
+
}
|
|
7035
7098
|
},
|
|
7036
7099
|
{ isActive: focused }
|
|
7037
7100
|
);
|
|
7038
|
-
|
|
7039
|
-
/* @__PURE__ */ jsx2(
|
|
7040
|
-
|
|
7041
|
-
|
|
7042
|
-
|
|
7043
|
-
|
|
7044
|
-
|
|
7045
|
-
|
|
7046
|
-
|
|
7047
|
-
|
|
7048
|
-
|
|
7049
|
-
|
|
7050
|
-
|
|
7051
|
-
|
|
7052
|
-
|
|
7053
|
-
|
|
7054
|
-
|
|
7055
|
-
|
|
7056
|
-
|
|
7057
|
-
|
|
7058
|
-
|
|
7059
|
-
|
|
7060
|
-
|
|
7061
|
-
|
|
7062
|
-
|
|
7063
|
-
|
|
7064
|
-
|
|
7065
|
-
|
|
7066
|
-
|
|
7067
|
-
|
|
7068
|
-
|
|
7069
|
-
|
|
7070
|
-
|
|
7071
|
-
|
|
7072
|
-
|
|
7101
|
+
if (view === "list") {
|
|
7102
|
+
return /* @__PURE__ */ jsx2(DataView, { loading: list4.loading && !list4.data, error: list4.error, empty: !!list4.data && policies.length === 0, emptyText: "No policies.", children: /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
|
|
7103
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: focused ? "\u2191\u2193 select \xB7 enter open" : "press \u2192 to browse" }),
|
|
7104
|
+
/* @__PURE__ */ jsx2(Box2, { marginTop: 1, flexDirection: "column", children: policies.map((p, i) => /* @__PURE__ */ jsxs2(Text2, { color: i === pi ? theme.accentBright : void 0, bold: i === pi, children: [
|
|
7105
|
+
(i === pi ? "\u25B8 " : " ") + truncate2(p.name, 26).padEnd(27),
|
|
7106
|
+
/* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
|
|
7107
|
+
p.mode.padEnd(10),
|
|
7108
|
+
" ",
|
|
7109
|
+
p.rules.length,
|
|
7110
|
+
" rules \xB7 v",
|
|
7111
|
+
p.version
|
|
7112
|
+
] })
|
|
7113
|
+
] }, p.id)) })
|
|
7114
|
+
] }) });
|
|
7115
|
+
}
|
|
7116
|
+
const rs = rules ?? [];
|
|
7117
|
+
return /* @__PURE__ */ jsx2(DataView, { loading: detail.loading && !rules, error: detail.error, children: /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
|
|
7118
|
+
/* @__PURE__ */ jsxs2(Box2, { children: [
|
|
7119
|
+
/* @__PURE__ */ jsx2(Text2, { bold: true, color: theme.accentBright, children: truncate2(selected?.name ?? "", 30) }),
|
|
7120
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " mode: " }),
|
|
7121
|
+
/* @__PURE__ */ jsx2(Text2, { color: mode === "whitelist" ? theme.ok : void 0, children: mode })
|
|
7122
|
+
] }),
|
|
7123
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "\u2191\u2193 rule \xB7 space toggle \xB7 d delete \xB7 m mode \xB7 \u2190 back" }),
|
|
7124
|
+
/* @__PURE__ */ jsx2(Box2, { marginTop: 1, children: /* @__PURE__ */ jsx2(
|
|
7125
|
+
Table,
|
|
7126
|
+
{
|
|
7127
|
+
columns: [
|
|
7128
|
+
{ header: "", width: 2 },
|
|
7129
|
+
{ header: "EFFECT", width: 7 },
|
|
7130
|
+
{ header: "PRIO", width: 4 },
|
|
7131
|
+
{ header: "TOOL", width: 20 },
|
|
7132
|
+
{ header: "DESCRIPTION", width: 34 }
|
|
7133
|
+
],
|
|
7134
|
+
rows: rs.map((r, i) => [
|
|
7135
|
+
{ value: i === ri ? "\u25B8" : "", color: theme.accentBright },
|
|
7136
|
+
{ value: r.effect, color: r.effect === "ALLOW" ? theme.ok : theme.bad, dim: !r.enabled },
|
|
7137
|
+
{ value: String(r.priority), dim: true },
|
|
7138
|
+
{ value: (r.enabled ? "" : "\u2298 ") + truncate2(r.toolPattern, 18), color: theme.accent, dim: !r.enabled },
|
|
7139
|
+
{ value: truncate2(r.description || "\u2014", 34), dim: !r.enabled }
|
|
7140
|
+
])
|
|
7141
|
+
}
|
|
7142
|
+
) }),
|
|
7143
|
+
rs.length === 0 ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "(no rules)" }) : null,
|
|
7144
|
+
status ? /* @__PURE__ */ jsx2(Box2, { marginTop: 1, children: /* @__PURE__ */ jsx2(Text2, { color: status.startsWith("\u2717") ? theme.bad : theme.ok, children: status }) }) : null
|
|
7073
7145
|
] }) });
|
|
7074
7146
|
}
|
|
7075
7147
|
var init_Policies = __esm({
|
|
@@ -7084,26 +7156,26 @@ var init_Policies = __esm({
|
|
|
7084
7156
|
|
|
7085
7157
|
// src/tui/panels/RateLimit.tsx
|
|
7086
7158
|
import { Box as Box3, Text as Text3, useInput as useInput2 } from "ink";
|
|
7087
|
-
import { useEffect as
|
|
7159
|
+
import { useEffect as useEffect3, useState as useState3 } from "react";
|
|
7088
7160
|
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
7089
7161
|
function RateLimitPanel({ focused }) {
|
|
7090
7162
|
const layersQ = useLoader(() => api.settings.getSecurityLayers());
|
|
7091
7163
|
const historyQ = useLoader(() => api.settings.getRateLimitHistory());
|
|
7164
|
+
const insightsQ = useLoader(() => api.stats.securityInsights(7));
|
|
7092
7165
|
const [draft, setDraft] = useState3(null);
|
|
7093
7166
|
const [dirty, setDirty] = useState3(false);
|
|
7094
7167
|
const [fi, setFi] = useState3(0);
|
|
7095
7168
|
const [status, setStatus] = useState3(null);
|
|
7096
|
-
|
|
7169
|
+
useEffect3(() => {
|
|
7097
7170
|
if (layersQ.data && !draft) setDraft({ ...layersQ.data.layers.rateLimit });
|
|
7098
7171
|
}, [layersQ.data, draft]);
|
|
7099
|
-
const adjust = (dir) => {
|
|
7172
|
+
const adjust = (dir, step) => {
|
|
7100
7173
|
if (!draft) return;
|
|
7101
7174
|
const field = FIELDS[fi];
|
|
7102
7175
|
if (field === "mode") {
|
|
7103
7176
|
const idx = (MODES.indexOf(draft.mode) + dir + MODES.length) % MODES.length;
|
|
7104
7177
|
setDraft({ ...draft, mode: MODES[idx] });
|
|
7105
7178
|
} else {
|
|
7106
|
-
const step = 10;
|
|
7107
7179
|
setDraft({ ...draft, [field]: Math.max(0, draft[field] + dir * step) });
|
|
7108
7180
|
}
|
|
7109
7181
|
setDirty(true);
|
|
@@ -7125,35 +7197,81 @@ function RateLimitPanel({ focused }) {
|
|
|
7125
7197
|
};
|
|
7126
7198
|
useInput2(
|
|
7127
7199
|
(input, key) => {
|
|
7200
|
+
const step = key.shift ? 10 : 1;
|
|
7128
7201
|
if (key.upArrow) setFi((n) => (n - 1 + FIELDS.length) % FIELDS.length);
|
|
7129
7202
|
else if (key.downArrow) setFi((n) => (n + 1) % FIELDS.length);
|
|
7130
|
-
else if (key.leftArrow) adjust(-1);
|
|
7131
|
-
else if (key.rightArrow) adjust(1);
|
|
7203
|
+
else if (key.leftArrow) adjust(-1, step);
|
|
7204
|
+
else if (key.rightArrow) adjust(1, step);
|
|
7132
7205
|
else if (input === "s") void save();
|
|
7133
7206
|
},
|
|
7134
7207
|
{ isActive: focused }
|
|
7135
7208
|
);
|
|
7136
|
-
const history = historyQ.data?.history ?? [];
|
|
7209
|
+
const history = (historyQ.data?.history ?? []).slice().sort((a, b) => b.ts - a.ts);
|
|
7210
|
+
const bursts = (insightsQ.data?.["anomalies"] ?? []).slice(0, 6);
|
|
7137
7211
|
return /* @__PURE__ */ jsx3(DataView, { loading: layersQ.loading && !draft, error: layersQ.error, children: draft ? /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
|
|
7138
|
-
/* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: focused ? "\u2191\u2193 field
|
|
7212
|
+
/* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: focused ? "\u2191\u2193 field \xB7 \u2190\u2192 \xB11 \xB7 shift+\u2190\u2192 \xB110 \xB7 s save" : "press \u2192 to edit" }),
|
|
7139
7213
|
/* @__PURE__ */ jsxs3(Box3, { marginTop: 1, flexDirection: "column", children: [
|
|
7140
|
-
/* @__PURE__ */ jsx3(
|
|
7141
|
-
/* @__PURE__ */ jsx3(
|
|
7142
|
-
/* @__PURE__ */ jsx3(
|
|
7143
|
-
/* @__PURE__ */ jsx3(
|
|
7214
|
+
/* @__PURE__ */ jsx3(FieldRow, { label: "Mode", active: focused && fi === 0, children: /* @__PURE__ */ jsx3(Text3, { color: modeColor(draft.mode), children: draft.mode }) }),
|
|
7215
|
+
/* @__PURE__ */ jsx3(FieldRow, { label: "Per minute", active: focused && fi === 1, children: /* @__PURE__ */ jsx3(Text3, { bold: true, children: draft.perMinute }) }),
|
|
7216
|
+
/* @__PURE__ */ jsx3(FieldRow, { label: "Per hour", active: focused && fi === 2, children: /* @__PURE__ */ jsx3(Text3, { bold: true, children: draft.perHour === 0 ? "off" : draft.perHour }) }),
|
|
7217
|
+
/* @__PURE__ */ jsx3(FieldRow, { label: "Per day", active: focused && fi === 3, children: /* @__PURE__ */ jsx3(Text3, { bold: true, children: draft.perDay === 0 ? "off" : draft.perDay }) })
|
|
7144
7218
|
] }),
|
|
7145
|
-
|
|
7146
|
-
/* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: "history " }),
|
|
7147
|
-
/* @__PURE__ */ jsx3(Text3, { color: theme.accent, children: sparkline(history.map((h) => h.minute), 40) }),
|
|
7148
|
-
/* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: " " + history.length + " changes" })
|
|
7149
|
-
] }) : null,
|
|
7150
|
-
/* @__PURE__ */ jsxs3(Box3, { marginTop: 1, children: [
|
|
7219
|
+
dirty || status ? /* @__PURE__ */ jsxs3(Box3, { marginTop: 1, children: [
|
|
7151
7220
|
dirty ? /* @__PURE__ */ jsx3(Text3, { color: theme.warn, children: "\u25CF unsaved \u2014 press s to apply " }) : null,
|
|
7152
7221
|
status ? /* @__PURE__ */ jsx3(Text3, { color: status.startsWith("\u2717") ? theme.bad : theme.ok, children: status }) : null
|
|
7222
|
+
] }) : null,
|
|
7223
|
+
/* @__PURE__ */ jsxs3(Box3, { marginTop: 1, flexDirection: "column", children: [
|
|
7224
|
+
/* @__PURE__ */ jsxs3(Text3, { color: theme.dim, children: [
|
|
7225
|
+
"Change history ",
|
|
7226
|
+
history.length ? `(${history.length})` : ""
|
|
7227
|
+
] }),
|
|
7228
|
+
history.length ? /* @__PURE__ */ jsx3(
|
|
7229
|
+
Table,
|
|
7230
|
+
{
|
|
7231
|
+
columns: [
|
|
7232
|
+
{ header: "WHEN", width: 8 },
|
|
7233
|
+
{ header: "/MIN", width: 6 },
|
|
7234
|
+
{ header: "/HOUR", width: 7 },
|
|
7235
|
+
{ header: "/DAY", width: 6 }
|
|
7236
|
+
],
|
|
7237
|
+
rows: history.slice(0, 5).map((h, i) => [
|
|
7238
|
+
{ value: ago(h.ts) + " ago", dim: true, bold: i === 0 },
|
|
7239
|
+
{ value: String(h.minute), bold: i === 0 },
|
|
7240
|
+
{ value: h.hour === 0 ? "off" : String(h.hour), dim: h.hour === 0 },
|
|
7241
|
+
{ value: h.day === 0 ? "off" : String(h.day), dim: h.day === 0 }
|
|
7242
|
+
])
|
|
7243
|
+
}
|
|
7244
|
+
) : /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: " no changes" })
|
|
7245
|
+
] }),
|
|
7246
|
+
/* @__PURE__ */ jsxs3(Box3, { marginTop: 1, flexDirection: "column", children: [
|
|
7247
|
+
/* @__PURE__ */ jsxs3(Text3, { color: theme.dim, children: [
|
|
7248
|
+
"Recent bursts ",
|
|
7249
|
+
insightsQ.loading && !insightsQ.data ? "\u2026" : `(${bursts.length})`
|
|
7250
|
+
] }),
|
|
7251
|
+
bursts.length ? /* @__PURE__ */ jsx3(
|
|
7252
|
+
Table,
|
|
7253
|
+
{
|
|
7254
|
+
columns: [
|
|
7255
|
+
{ header: "RESULT", width: 9 },
|
|
7256
|
+
{ header: "AGENT", width: 16 },
|
|
7257
|
+
{ header: "CALLS/LIMIT", width: 12 },
|
|
7258
|
+
{ header: "WHEN", width: 8 }
|
|
7259
|
+
],
|
|
7260
|
+
rows: bursts.map((b) => [
|
|
7261
|
+
{ value: b.blocked ? "blocked" : "BYPASSED", color: b.blocked ? theme.accent : theme.bad, bold: !b.blocked },
|
|
7262
|
+
{ value: (b.agent || "\u2014").slice(0, 16), dim: true },
|
|
7263
|
+
{ value: `${b.count}/${b.limit}` },
|
|
7264
|
+
{ value: ago(b.minute) + " ago", dim: true }
|
|
7265
|
+
])
|
|
7266
|
+
}
|
|
7267
|
+
) : /* @__PURE__ */ jsxs3(Text3, { color: theme.dim, children: [
|
|
7268
|
+
" ",
|
|
7269
|
+
insightsQ.error ? insightsQ.error : "none in 7d"
|
|
7270
|
+
] })
|
|
7153
7271
|
] })
|
|
7154
7272
|
] }) : null });
|
|
7155
7273
|
}
|
|
7156
|
-
function
|
|
7274
|
+
function FieldRow({ label, active: active2, children }) {
|
|
7157
7275
|
return /* @__PURE__ */ jsxs3(Box3, { children: [
|
|
7158
7276
|
/* @__PURE__ */ jsx3(Text3, { color: active2 ? theme.accentBright : void 0, children: (active2 ? "\u25B8 " : " ") + label.padEnd(12) }),
|
|
7159
7277
|
children
|
|
@@ -7174,7 +7292,7 @@ var init_RateLimit = __esm({
|
|
|
7174
7292
|
|
|
7175
7293
|
// src/tui/panels/Dlp.tsx
|
|
7176
7294
|
import { Box as Box4, Text as Text4, useInput as useInput3 } from "ink";
|
|
7177
|
-
import { useEffect as
|
|
7295
|
+
import { useEffect as useEffect4, useState as useState4 } from "react";
|
|
7178
7296
|
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
7179
7297
|
function DlpPanel({ focused }) {
|
|
7180
7298
|
const q = useLoader(() => api.settings.getSecurityLayers());
|
|
@@ -7182,7 +7300,7 @@ function DlpPanel({ focused }) {
|
|
|
7182
7300
|
const [available, setAvailable] = useState4([]);
|
|
7183
7301
|
const [sel, setSel] = useState4(0);
|
|
7184
7302
|
const [status, setStatus] = useState4(null);
|
|
7185
|
-
|
|
7303
|
+
useEffect4(() => {
|
|
7186
7304
|
if (q.data && !dlp) {
|
|
7187
7305
|
setDlp({ ...q.data.layers.dlp, patterns: [...q.data.layers.dlp.patterns] });
|
|
7188
7306
|
setAvailable(q.data.availablePatterns);
|
package/dist/tui/index.js
CHANGED
|
@@ -126,7 +126,7 @@ function KeyHints({ hints }) {
|
|
|
126
126
|
|
|
127
127
|
// src/tui/panels/Policies.tsx
|
|
128
128
|
import { Box as Box2, Text as Text2, useInput } from "ink";
|
|
129
|
-
import { useState as useState2 } from "react";
|
|
129
|
+
import { useEffect as useEffect2, useState as useState2 } from "react";
|
|
130
130
|
|
|
131
131
|
// src/api-client/client.ts
|
|
132
132
|
import { readFileSync, existsSync } from "fs";
|
|
@@ -432,80 +432,152 @@ function usePoll(reload, intervalMs, enabled = true) {
|
|
|
432
432
|
// src/tui/panels/Policies.tsx
|
|
433
433
|
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
434
434
|
function PoliciesPanel({ focused }) {
|
|
435
|
-
const [sel, setSel] = useState2(0);
|
|
436
435
|
const list3 = useLoader(() => api.policies.list());
|
|
437
436
|
const policies = list3.data?.policies ?? [];
|
|
438
|
-
const
|
|
437
|
+
const [view, setView] = useState2("list");
|
|
438
|
+
const [pi, setPi] = useState2(0);
|
|
439
|
+
const [ri, setRi] = useState2(0);
|
|
440
|
+
const [rules, setRules] = useState2(null);
|
|
441
|
+
const [mode, setMode] = useState2("denylist");
|
|
442
|
+
const [status, setStatus] = useState2(null);
|
|
443
|
+
const selected = policies[Math.min(pi, Math.max(0, policies.length - 1))];
|
|
439
444
|
const detail = useLoader(() => selected ? api.policies.get(selected.id) : Promise.resolve(null), [selected?.id]);
|
|
445
|
+
useEffect2(() => {
|
|
446
|
+
if (detail.data) {
|
|
447
|
+
setRules(detail.data.rules);
|
|
448
|
+
setMode(detail.data.mode ?? "denylist");
|
|
449
|
+
setRi(0);
|
|
450
|
+
}
|
|
451
|
+
}, [detail.data]);
|
|
452
|
+
const persist = async (nextRules, nextMode) => {
|
|
453
|
+
if (!detail.data || !selected) return;
|
|
454
|
+
setStatus("Saving\u2026");
|
|
455
|
+
try {
|
|
456
|
+
const res = await api.policies.update(selected.id, {
|
|
457
|
+
id: selected.id,
|
|
458
|
+
name: detail.data.name,
|
|
459
|
+
description: detail.data.description,
|
|
460
|
+
mode: nextMode,
|
|
461
|
+
agents: detail.data.agents,
|
|
462
|
+
rules: nextRules
|
|
463
|
+
});
|
|
464
|
+
setRules(res.rules);
|
|
465
|
+
setStatus("\u2713 Saved v" + res._version);
|
|
466
|
+
list3.reload();
|
|
467
|
+
} catch (e) {
|
|
468
|
+
setStatus("\u2717 " + (e instanceof Error ? e.message : String(e)));
|
|
469
|
+
}
|
|
470
|
+
};
|
|
440
471
|
useInput(
|
|
441
|
-
(
|
|
442
|
-
if (
|
|
443
|
-
|
|
472
|
+
(input, key) => {
|
|
473
|
+
if (view === "list") {
|
|
474
|
+
if (key.upArrow) setPi((n) => Math.max(0, n - 1));
|
|
475
|
+
else if (key.downArrow) setPi((n) => Math.min(policies.length - 1, n + 1));
|
|
476
|
+
else if (key.return || key.rightArrow) {
|
|
477
|
+
setStatus(null);
|
|
478
|
+
setView("detail");
|
|
479
|
+
}
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
482
|
+
const rs2 = rules ?? [];
|
|
483
|
+
if (key.leftArrow) {
|
|
484
|
+
setView("list");
|
|
485
|
+
setStatus(null);
|
|
486
|
+
} else if (key.upArrow) setRi((n) => Math.max(0, n - 1));
|
|
487
|
+
else if (key.downArrow) setRi((n) => Math.min(rs2.length - 1, n + 1));
|
|
488
|
+
else if (input === " " || input === "e") {
|
|
489
|
+
if (!rs2[ri]) return;
|
|
490
|
+
const next = rs2.map((r, i) => i === ri ? { ...r, enabled: !r.enabled } : r);
|
|
491
|
+
setRules(next);
|
|
492
|
+
void persist(next, mode);
|
|
493
|
+
} else if (input === "d") {
|
|
494
|
+
const target = rs2[ri];
|
|
495
|
+
if (!target || !selected) return;
|
|
496
|
+
setStatus("Deleting\u2026");
|
|
497
|
+
void api.policies.revokeRule(selected.id, target.id).then(() => {
|
|
498
|
+
setStatus("\u2713 Deleted");
|
|
499
|
+
detail.reload();
|
|
500
|
+
list3.reload();
|
|
501
|
+
}).catch((e) => setStatus("\u2717 " + (e instanceof Error ? e.message : String(e))));
|
|
502
|
+
} else if (input === "m") {
|
|
503
|
+
const nm = mode === "denylist" ? "whitelist" : "denylist";
|
|
504
|
+
setMode(nm);
|
|
505
|
+
void persist(rs2, nm);
|
|
506
|
+
}
|
|
444
507
|
},
|
|
445
508
|
{ isActive: focused }
|
|
446
509
|
);
|
|
447
|
-
|
|
448
|
-
/* @__PURE__ */ jsx2(
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
510
|
+
if (view === "list") {
|
|
511
|
+
return /* @__PURE__ */ jsx2(DataView, { loading: list3.loading && !list3.data, error: list3.error, empty: !!list3.data && policies.length === 0, emptyText: "No policies.", children: /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
|
|
512
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: focused ? "\u2191\u2193 select \xB7 enter open" : "press \u2192 to browse" }),
|
|
513
|
+
/* @__PURE__ */ jsx2(Box2, { marginTop: 1, flexDirection: "column", children: policies.map((p, i) => /* @__PURE__ */ jsxs2(Text2, { color: i === pi ? theme.accentBright : void 0, bold: i === pi, children: [
|
|
514
|
+
(i === pi ? "\u25B8 " : " ") + truncate(p.name, 26).padEnd(27),
|
|
515
|
+
/* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
|
|
516
|
+
p.mode.padEnd(10),
|
|
517
|
+
" ",
|
|
518
|
+
p.rules.length,
|
|
519
|
+
" rules \xB7 v",
|
|
520
|
+
p.version
|
|
521
|
+
] })
|
|
522
|
+
] }, p.id)) })
|
|
523
|
+
] }) });
|
|
524
|
+
}
|
|
525
|
+
const rs = rules ?? [];
|
|
526
|
+
return /* @__PURE__ */ jsx2(DataView, { loading: detail.loading && !rules, error: detail.error, children: /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
|
|
527
|
+
/* @__PURE__ */ jsxs2(Box2, { children: [
|
|
528
|
+
/* @__PURE__ */ jsx2(Text2, { bold: true, color: theme.accentBright, children: truncate(selected?.name ?? "", 30) }),
|
|
529
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " mode: " }),
|
|
530
|
+
/* @__PURE__ */ jsx2(Text2, { color: mode === "whitelist" ? theme.ok : void 0, children: mode })
|
|
531
|
+
] }),
|
|
532
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "\u2191\u2193 rule \xB7 space toggle \xB7 d delete \xB7 m mode \xB7 \u2190 back" }),
|
|
533
|
+
/* @__PURE__ */ jsx2(Box2, { marginTop: 1, children: /* @__PURE__ */ jsx2(
|
|
534
|
+
Table,
|
|
535
|
+
{
|
|
536
|
+
columns: [
|
|
537
|
+
{ header: "", width: 2 },
|
|
538
|
+
{ header: "EFFECT", width: 7 },
|
|
539
|
+
{ header: "PRIO", width: 4 },
|
|
540
|
+
{ header: "TOOL", width: 20 },
|
|
541
|
+
{ header: "DESCRIPTION", width: 34 }
|
|
542
|
+
],
|
|
543
|
+
rows: rs.map((r, i) => [
|
|
544
|
+
{ value: i === ri ? "\u25B8" : "", color: theme.accentBright },
|
|
545
|
+
{ value: r.effect, color: r.effect === "ALLOW" ? theme.ok : theme.bad, dim: !r.enabled },
|
|
546
|
+
{ value: String(r.priority), dim: true },
|
|
547
|
+
{ value: (r.enabled ? "" : "\u2298 ") + truncate(r.toolPattern, 18), color: theme.accent, dim: !r.enabled },
|
|
548
|
+
{ value: truncate(r.description || "\u2014", 34), dim: !r.enabled }
|
|
549
|
+
])
|
|
550
|
+
}
|
|
551
|
+
) }),
|
|
552
|
+
rs.length === 0 ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "(no rules)" }) : null,
|
|
553
|
+
status ? /* @__PURE__ */ jsx2(Box2, { marginTop: 1, children: /* @__PURE__ */ jsx2(Text2, { color: status.startsWith("\u2717") ? theme.bad : theme.ok, children: status }) }) : null
|
|
482
554
|
] }) });
|
|
483
555
|
}
|
|
484
556
|
|
|
485
557
|
// src/tui/panels/RateLimit.tsx
|
|
486
558
|
import { Box as Box3, Text as Text3, useInput as useInput2 } from "ink";
|
|
487
|
-
import { useEffect as
|
|
559
|
+
import { useEffect as useEffect3, useState as useState3 } from "react";
|
|
488
560
|
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
489
561
|
var MODES = ["off", "detect", "block"];
|
|
490
562
|
var FIELDS = ["mode", "perMinute", "perHour", "perDay"];
|
|
491
563
|
function RateLimitPanel({ focused }) {
|
|
492
564
|
const layersQ = useLoader(() => api.settings.getSecurityLayers());
|
|
493
565
|
const historyQ = useLoader(() => api.settings.getRateLimitHistory());
|
|
566
|
+
const insightsQ = useLoader(() => api.stats.securityInsights(7));
|
|
494
567
|
const [draft, setDraft] = useState3(null);
|
|
495
568
|
const [dirty, setDirty] = useState3(false);
|
|
496
569
|
const [fi, setFi] = useState3(0);
|
|
497
570
|
const [status, setStatus] = useState3(null);
|
|
498
|
-
|
|
571
|
+
useEffect3(() => {
|
|
499
572
|
if (layersQ.data && !draft) setDraft({ ...layersQ.data.layers.rateLimit });
|
|
500
573
|
}, [layersQ.data, draft]);
|
|
501
|
-
const adjust = (dir) => {
|
|
574
|
+
const adjust = (dir, step) => {
|
|
502
575
|
if (!draft) return;
|
|
503
576
|
const field = FIELDS[fi];
|
|
504
577
|
if (field === "mode") {
|
|
505
578
|
const idx = (MODES.indexOf(draft.mode) + dir + MODES.length) % MODES.length;
|
|
506
579
|
setDraft({ ...draft, mode: MODES[idx] });
|
|
507
580
|
} else {
|
|
508
|
-
const step = 10;
|
|
509
581
|
setDraft({ ...draft, [field]: Math.max(0, draft[field] + dir * step) });
|
|
510
582
|
}
|
|
511
583
|
setDirty(true);
|
|
@@ -527,35 +599,81 @@ function RateLimitPanel({ focused }) {
|
|
|
527
599
|
};
|
|
528
600
|
useInput2(
|
|
529
601
|
(input, key) => {
|
|
602
|
+
const step = key.shift ? 10 : 1;
|
|
530
603
|
if (key.upArrow) setFi((n) => (n - 1 + FIELDS.length) % FIELDS.length);
|
|
531
604
|
else if (key.downArrow) setFi((n) => (n + 1) % FIELDS.length);
|
|
532
|
-
else if (key.leftArrow) adjust(-1);
|
|
533
|
-
else if (key.rightArrow) adjust(1);
|
|
605
|
+
else if (key.leftArrow) adjust(-1, step);
|
|
606
|
+
else if (key.rightArrow) adjust(1, step);
|
|
534
607
|
else if (input === "s") void save();
|
|
535
608
|
},
|
|
536
609
|
{ isActive: focused }
|
|
537
610
|
);
|
|
538
|
-
const history = historyQ.data?.history ?? [];
|
|
611
|
+
const history = (historyQ.data?.history ?? []).slice().sort((a, b) => b.ts - a.ts);
|
|
612
|
+
const bursts = (insightsQ.data?.["anomalies"] ?? []).slice(0, 6);
|
|
539
613
|
return /* @__PURE__ */ jsx3(DataView, { loading: layersQ.loading && !draft, error: layersQ.error, children: draft ? /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
|
|
540
|
-
/* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: focused ? "\u2191\u2193 field
|
|
614
|
+
/* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: focused ? "\u2191\u2193 field \xB7 \u2190\u2192 \xB11 \xB7 shift+\u2190\u2192 \xB110 \xB7 s save" : "press \u2192 to edit" }),
|
|
541
615
|
/* @__PURE__ */ jsxs3(Box3, { marginTop: 1, flexDirection: "column", children: [
|
|
542
|
-
/* @__PURE__ */ jsx3(
|
|
543
|
-
/* @__PURE__ */ jsx3(
|
|
544
|
-
/* @__PURE__ */ jsx3(
|
|
545
|
-
/* @__PURE__ */ jsx3(
|
|
616
|
+
/* @__PURE__ */ jsx3(FieldRow, { label: "Mode", active: focused && fi === 0, children: /* @__PURE__ */ jsx3(Text3, { color: modeColor(draft.mode), children: draft.mode }) }),
|
|
617
|
+
/* @__PURE__ */ jsx3(FieldRow, { label: "Per minute", active: focused && fi === 1, children: /* @__PURE__ */ jsx3(Text3, { bold: true, children: draft.perMinute }) }),
|
|
618
|
+
/* @__PURE__ */ jsx3(FieldRow, { label: "Per hour", active: focused && fi === 2, children: /* @__PURE__ */ jsx3(Text3, { bold: true, children: draft.perHour === 0 ? "off" : draft.perHour }) }),
|
|
619
|
+
/* @__PURE__ */ jsx3(FieldRow, { label: "Per day", active: focused && fi === 3, children: /* @__PURE__ */ jsx3(Text3, { bold: true, children: draft.perDay === 0 ? "off" : draft.perDay }) })
|
|
546
620
|
] }),
|
|
547
|
-
|
|
548
|
-
/* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: "history " }),
|
|
549
|
-
/* @__PURE__ */ jsx3(Text3, { color: theme.accent, children: sparkline(history.map((h) => h.minute), 40) }),
|
|
550
|
-
/* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: " " + history.length + " changes" })
|
|
551
|
-
] }) : null,
|
|
552
|
-
/* @__PURE__ */ jsxs3(Box3, { marginTop: 1, children: [
|
|
621
|
+
dirty || status ? /* @__PURE__ */ jsxs3(Box3, { marginTop: 1, children: [
|
|
553
622
|
dirty ? /* @__PURE__ */ jsx3(Text3, { color: theme.warn, children: "\u25CF unsaved \u2014 press s to apply " }) : null,
|
|
554
623
|
status ? /* @__PURE__ */ jsx3(Text3, { color: status.startsWith("\u2717") ? theme.bad : theme.ok, children: status }) : null
|
|
624
|
+
] }) : null,
|
|
625
|
+
/* @__PURE__ */ jsxs3(Box3, { marginTop: 1, flexDirection: "column", children: [
|
|
626
|
+
/* @__PURE__ */ jsxs3(Text3, { color: theme.dim, children: [
|
|
627
|
+
"Change history ",
|
|
628
|
+
history.length ? `(${history.length})` : ""
|
|
629
|
+
] }),
|
|
630
|
+
history.length ? /* @__PURE__ */ jsx3(
|
|
631
|
+
Table,
|
|
632
|
+
{
|
|
633
|
+
columns: [
|
|
634
|
+
{ header: "WHEN", width: 8 },
|
|
635
|
+
{ header: "/MIN", width: 6 },
|
|
636
|
+
{ header: "/HOUR", width: 7 },
|
|
637
|
+
{ header: "/DAY", width: 6 }
|
|
638
|
+
],
|
|
639
|
+
rows: history.slice(0, 5).map((h, i) => [
|
|
640
|
+
{ value: ago(h.ts) + " ago", dim: true, bold: i === 0 },
|
|
641
|
+
{ value: String(h.minute), bold: i === 0 },
|
|
642
|
+
{ value: h.hour === 0 ? "off" : String(h.hour), dim: h.hour === 0 },
|
|
643
|
+
{ value: h.day === 0 ? "off" : String(h.day), dim: h.day === 0 }
|
|
644
|
+
])
|
|
645
|
+
}
|
|
646
|
+
) : /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: " no changes" })
|
|
647
|
+
] }),
|
|
648
|
+
/* @__PURE__ */ jsxs3(Box3, { marginTop: 1, flexDirection: "column", children: [
|
|
649
|
+
/* @__PURE__ */ jsxs3(Text3, { color: theme.dim, children: [
|
|
650
|
+
"Recent bursts ",
|
|
651
|
+
insightsQ.loading && !insightsQ.data ? "\u2026" : `(${bursts.length})`
|
|
652
|
+
] }),
|
|
653
|
+
bursts.length ? /* @__PURE__ */ jsx3(
|
|
654
|
+
Table,
|
|
655
|
+
{
|
|
656
|
+
columns: [
|
|
657
|
+
{ header: "RESULT", width: 9 },
|
|
658
|
+
{ header: "AGENT", width: 16 },
|
|
659
|
+
{ header: "CALLS/LIMIT", width: 12 },
|
|
660
|
+
{ header: "WHEN", width: 8 }
|
|
661
|
+
],
|
|
662
|
+
rows: bursts.map((b) => [
|
|
663
|
+
{ value: b.blocked ? "blocked" : "BYPASSED", color: b.blocked ? theme.accent : theme.bad, bold: !b.blocked },
|
|
664
|
+
{ value: (b.agent || "\u2014").slice(0, 16), dim: true },
|
|
665
|
+
{ value: `${b.count}/${b.limit}` },
|
|
666
|
+
{ value: ago(b.minute) + " ago", dim: true }
|
|
667
|
+
])
|
|
668
|
+
}
|
|
669
|
+
) : /* @__PURE__ */ jsxs3(Text3, { color: theme.dim, children: [
|
|
670
|
+
" ",
|
|
671
|
+
insightsQ.error ? insightsQ.error : "none in 7d"
|
|
672
|
+
] })
|
|
555
673
|
] })
|
|
556
674
|
] }) : null });
|
|
557
675
|
}
|
|
558
|
-
function
|
|
676
|
+
function FieldRow({ label, active: active2, children }) {
|
|
559
677
|
return /* @__PURE__ */ jsxs3(Box3, { children: [
|
|
560
678
|
/* @__PURE__ */ jsx3(Text3, { color: active2 ? theme.accentBright : void 0, children: (active2 ? "\u25B8 " : " ") + label.padEnd(12) }),
|
|
561
679
|
children
|
|
@@ -564,7 +682,7 @@ function Row({ label, active: active2, children }) {
|
|
|
564
682
|
|
|
565
683
|
// src/tui/panels/Dlp.tsx
|
|
566
684
|
import { Box as Box4, Text as Text4, useInput as useInput3 } from "ink";
|
|
567
|
-
import { useEffect as
|
|
685
|
+
import { useEffect as useEffect4, useState as useState4 } from "react";
|
|
568
686
|
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
569
687
|
var MODES2 = ["off", "detect", "block"];
|
|
570
688
|
function DlpPanel({ focused }) {
|
|
@@ -573,7 +691,7 @@ function DlpPanel({ focused }) {
|
|
|
573
691
|
const [available, setAvailable] = useState4([]);
|
|
574
692
|
const [sel, setSel] = useState4(0);
|
|
575
693
|
const [status, setStatus] = useState4(null);
|
|
576
|
-
|
|
694
|
+
useEffect4(() => {
|
|
577
695
|
if (q.data && !dlp) {
|
|
578
696
|
setDlp({ ...q.data.layers.dlp, patterns: [...q.data.layers.dlp.patterns] });
|
|
579
697
|
setAvailable(q.data.availablePatterns);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solongate/proxy",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.61.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": {
|