Watch
0
0
Fork
You've already forked hyperhive
0

offer the log mode in the swarm ui

This commit is contained in:
atlas 2026-10-30 02:18:39 +01:00 • committed by mara
commit 9140d063d7
3 changed files with 88 additions and 28 deletions

View file

@ -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}
<Badge
label="internet"
value={internetSummary(grant.unrestricted_internet)}
value={internetSummary(grant.unrestricted_internet, grant.internet_log)}
/>
{INTERNET_MODES.filter(
([mode]) =>
mode !==
internetMode(grant.unrestricted_internet, grant.internet_log),
).map(([mode, label, title]) => (
<Badge
key={mode}
variant="quiet"
value={
grant.unrestricted_internet === false
? "allow unrestricted"
: "restrict"
}
title={`set whether ${agent}'s traffic may leave the host unfiltered`}
onClick={() =>
void setInternet(grant, grant.unrestricted_internet === false)
}
value={label}
title={`${title} for ${agent}`}
onClick={() => void setInternet(grant, mode)}
/>
))}
<Badge
variant="quiet"
value="edit"

View file

@ -22,26 +22,50 @@ export function grantSummary(stored: string[] | undefined): string {
return `${stored.length} granted`;
}
/** The three modes of an agent's internet access. */
export type InternetMode = "unrestricted" | "log" | "restricted";
/**
* How the panel reads the stored `unrestricted_internet`. An absent value
* counts as on, and says "legacy" because the swarm never set it.
* How the panel reads the stored `unrestricted_internet` and `internet_log`.
* An absent flag counts as on, and says "legacy" because the swarm never set
* it. `log` is `internet_log` beside an explicit `false`.
*/
export function internetSummary(stored: boolean | undefined): string {
export function internetSummary(
stored: boolean | undefined,
log?: boolean,
): string {
if (stored === undefined) return "unrestricted (legacy)";
return stored ? "unrestricted" : "restricted";
if (stored) return "unrestricted";
return log ? "log (unlisted names allowed, logged)" : "restricted";
}
/** The mode the stored fields put the agent in. */
export function internetMode(
stored: boolean | undefined,
log?: boolean,
): InternetMode {
if (stored === undefined || stored) return "unrestricted";
return log ? "log" : "restricted";
}
/**
* The `PUT` body that sets the internet flag. A `PUT` without `permissions`
* The `PUT` body that sets the internet mode. A `PUT` without `permissions`
* resets a stored list to the default, so the stored list goes along; an
* agent with none stored sends the flag alone, which leaves it unset.
* agent with none stored sends the mode alone, which leaves the list unset.
* `log` names `internet_log` beside `unrestricted_internet: false`; the other
* two leave it out, which ends logging.
*/
export function internetBody(
stored: string[] | undefined,
unrestricted: boolean,
): { permissions?: string[]; unrestricted_internet: boolean } {
mode: InternetMode,
): {
permissions?: string[];
unrestricted_internet: boolean;
internet_log?: true;
} {
return {
...(stored === undefined ? {} : { permissions: stored }),
unrestricted_internet: unrestricted,
unrestricted_internet: mode === "unrestricted",
...(mode === "log" ? { internet_log: true as const } : {}),
};
}

View file

@ -6,6 +6,7 @@ import {
editorSelection,
grantSummary,
internetBody,
internetMode,
internetSummary,
} from "../src/agent/detail/permissions.ts";
@ -43,18 +44,38 @@ test("an absent internet flag reads as unrestricted, marked legacy", () => {
});
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);
});