From 9140d063d7153fc02e70f9b144a44feaa2f10558 Mon Sep 17 00:00:00 2001 From: atlas Date: Fri, 30 Oct 2026 02:18:39 +0100 Subject: [PATCH] offer the log mode in the swarm ui --- .../src/agent/detail/AgentPermissions.tsx | 47 ++++++++++++------- .../swarm-ui/src/agent/detail/permissions.ts | 42 +++++++++++++---- .../swarm-ui/test/agent-permissions.test.mjs | 27 +++++++++-- 3 files changed, 88 insertions(+), 28 deletions(-) diff --git a/frontend/packages/swarm-ui/src/agent/detail/AgentPermissions.tsx b/frontend/packages/swarm-ui/src/agent/detail/AgentPermissions.tsx index 03de607a..379a88c7 100644 --- a/frontend/packages/swarm-ui/src/agent/detail/AgentPermissions.tsx +++ b/frontend/packages/swarm-ui/src/agent/detail/AgentPermissions.tsx @@ -27,7 +27,9 @@ import { editorSelection, grantSummary, internetBody, + internetMode, internetSummary, + type InternetMode, } from "./permissions.js"; import "./AgentLimits.css"; @@ -35,6 +37,7 @@ import "./AgentLimits.css"; interface Grant { permissions?: string[]; unrestricted_internet?: boolean; + internet_log?: boolean; } // Mirrors swarm-controller's `Catalogue` (`GET /api/permissions`). @@ -43,6 +46,17 @@ interface Catalogue { default: string[]; } +// The buttons for the modes an agent is not in: mode, label, tooltip. +const INTERNET_MODES: [InternetMode, string, string][] = [ + [ + "unrestricted", + "allow unrestricted", + "let its traffic leave the host unfiltered", + ], + ["log", "log", "allow what restricted would deny, and log it as would_deny"], + ["restricted", "restrict", "allow only the listed names"], +]; + function permissionsUrl(agent: string): string { return `/api/agents/${encodeURIComponent(agent)}/permissions`; } @@ -75,15 +89,15 @@ export function AgentPermissions({ agent }: { agent: string }) { }; }, [agent, reload]); - // Flips the internet flag, sending the stored list with it. - async function setInternet(g: Grant, unrestricted: boolean) { + // Sets the internet mode, sending the stored list with it. + async function setInternet(g: Grant, mode: InternetMode) { if (flagBusy) return; setFlagBusy(true); try { const r = await fetch(permissionsUrl(agent), { method: "PUT", headers: { "content-type": "application/json" }, - body: JSON.stringify(internetBody(g.permissions, unrestricted)), + body: JSON.stringify(internetBody(g.permissions, mode)), }); if (!r.ok) { setError(await readApiError(r)); @@ -130,20 +144,21 @@ export function AgentPermissions({ agent }: { agent: string }) { ) : null} - - void setInternet(grant, grant.unrestricted_internet === false) - } + value={internetSummary(grant.unrestricted_internet, grant.internet_log)} /> + {INTERNET_MODES.filter( + ([mode]) => + mode !== + internetMode(grant.unrestricted_internet, grant.internet_log), + ).map(([mode, label, title]) => ( + void setInternet(grant, mode)} + /> + ))} { }); test("the internet toggle sends the stored list with the flag", () => { - assert.deepEqual(internetBody(["meta", "inbox"], false), { + assert.deepEqual(internetBody(["meta", "inbox"], "restricted"), { permissions: ["meta", "inbox"], unrestricted_internet: false, }); - assert.deepEqual(internetBody([], true), { + assert.deepEqual(internetBody([], "unrestricted"), { permissions: [], unrestricted_internet: true, }); }); test("with no stored list the toggle leaves the list unset", () => { - assert.deepEqual(internetBody(undefined, false), { + assert.deepEqual(internetBody(undefined, "restricted"), { unrestricted_internet: false, }); }); + +test("log is internet_log beside an explicit false, and nothing else reads as it", () => { + assert.equal(internetMode(false, true), "log"); + assert.equal(internetMode(false, undefined), "restricted"); + assert.equal(internetMode(false, false), "restricted"); + assert.equal(internetMode(true, true), "unrestricted"); + assert.equal(internetMode(undefined, true), "unrestricted"); + assert.match(internetSummary(false, true), /^log /); + assert.equal(internetSummary(false), "restricted"); +}); + +test("the log mode keeps the old flag false and names internet_log; the others leave it out", () => { + assert.deepEqual(internetBody(["meta"], "log"), { + permissions: ["meta"], + unrestricted_internet: false, + internet_log: true, + }); + assert.equal("internet_log" in internetBody([], "restricted"), false); + assert.equal("internet_log" in internetBody([], "unrestricted"), false); +});