swarm-ui: the egress card on the swarm page lists and edits the swarm-wide denied domains, and says which hives have not applied them
This commit is contained in:
parent
0bc350a8a1
commit
0421f28d49
2 changed files with 210 additions and 103 deletions
|
|
@ -1,26 +1,47 @@
|
|||
// <EgressCard> — the names every agent in the swarm may reach through its
|
||||
// hive's egress proxy: the swarm-wide allowed domains, with an add box and a
|
||||
// remove beside each. A card that starts folded and reads nothing until it is
|
||||
// opened.
|
||||
// <EgressCard> — the names every agent in the swarm may or may not reach
|
||||
// through its hive's egress proxy: the swarm-wide allowed domains and the
|
||||
// swarm-wide denied ones, each with an add box and a remove beside each entry.
|
||||
// A card that starts folded and reads nothing until it is opened.
|
||||
//
|
||||
// The entries are the ones of `GET /api/egress/allowed-domains` that hold for
|
||||
// every agent (an agent's own entries stay on its card). They add to the names
|
||||
// each hive's own configuration allows. An entry the controller keeps from an
|
||||
// environment or account (`derived`) is not listed here. Adding and removing
|
||||
// are the agent card's own: `AddForm` without an agent, `useAllowedDomains`.
|
||||
// The entries are the ones of `GET /api/egress/allowed-domains` and
|
||||
// `GET /api/egress/denied-domains` that hold for every agent (an agent's own
|
||||
// entries stay on its card). Allowed names add to what each hive's own
|
||||
// configuration allows; a denied name is refused over everything, in every
|
||||
// mode. An allowed entry the controller keeps from an environment or account
|
||||
// (`derived`) is not listed here. Adding and removing are the agent card's own:
|
||||
// `AddForm` without an agent, `useAllowedDomains` and `useDeniedDomains`.
|
||||
import { useState } from "preact/hooks";
|
||||
import { ApiErrorPanel } from "@hive/shared/api-error-panel.js";
|
||||
import { AddForm } from "../../agent/detail/AllowedDomains.js";
|
||||
import { swarmWideEntries } from "../../agent/detail/allowed-domains.js";
|
||||
import { useAllowedDomains } from "../../agent/detail/useAllowedDomains.js";
|
||||
import {
|
||||
behindNote,
|
||||
swarmWideEntries,
|
||||
} from "../../agent/detail/allowed-domains.js";
|
||||
import {
|
||||
useAllowedDomains,
|
||||
useDeniedDomains,
|
||||
} from "../../agent/detail/useAllowedDomains.js";
|
||||
import { Button } from "../../ui/button/Button.js";
|
||||
import { LazyDisclosure } from "../../ui/disclosure/LazyDisclosure.js";
|
||||
import { Dialog } from "../../ui/dialog/Dialog.js";
|
||||
import "../../agent/detail/AgentLimits.css";
|
||||
import "./SwarmPage.css";
|
||||
|
||||
function EgressBody() {
|
||||
const { entries, error, busy, remove, refetch } = useAllowedDomains();
|
||||
type DomainList = ReturnType<typeof useAllowedDomains>;
|
||||
|
||||
// One list of the card: a heading, what it means, the rows and an add button.
|
||||
function DomainSection({
|
||||
kind,
|
||||
list,
|
||||
heading,
|
||||
help,
|
||||
}: {
|
||||
kind: "allow" | "deny";
|
||||
list: DomainList;
|
||||
heading: string;
|
||||
help: string;
|
||||
}) {
|
||||
const { entries, behind, error, busy, remove, refetch } = list;
|
||||
const [adding, setAdding] = useState(false);
|
||||
|
||||
// A list already loaded stays under a failed refetch, so a change that
|
||||
|
|
@ -28,64 +49,85 @@ function EgressBody() {
|
|||
const errorPanel = error ? (
|
||||
<>
|
||||
<ApiErrorPanel
|
||||
context="the allowed domains were not read or changed"
|
||||
context={`the ${kind === "deny" ? "denied" : "allowed"} domains were not read or changed`}
|
||||
problem={error}
|
||||
/>
|
||||
<Button onClick={() => refetch()}>retry</Button>
|
||||
</>
|
||||
) : null;
|
||||
if (entries === null) {
|
||||
return (
|
||||
errorPanel ?? (
|
||||
<span class="ui-agent-limits-muted" role="status">
|
||||
loading the allowed domains…
|
||||
</span>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const swarmWide = swarmWideEntries(entries);
|
||||
const swarmWide = entries === null ? [] : swarmWideEntries(entries);
|
||||
const note = kind === "deny" ? behindNote(behind) : null;
|
||||
return (
|
||||
<section class="ui-swarm-egress-section">
|
||||
<h3 class="ui-swarm-egress-heading">{heading}</h3>
|
||||
<p class="ui-swarm-egress-help">{help}</p>
|
||||
{errorPanel}
|
||||
{entries === null ? (
|
||||
errorPanel === null ? (
|
||||
<span class="ui-agent-limits-muted" role="status">
|
||||
loading the {kind === "deny" ? "denied" : "allowed"} domains…
|
||||
</span>
|
||||
) : null
|
||||
) : (
|
||||
<>
|
||||
{note ? <p class="ui-swarm-egress-behind">{note}</p> : null}
|
||||
{swarmWide.length === 0 ? (
|
||||
<p class="ui-agent-limits-muted">none added</p>
|
||||
) : (
|
||||
<ul class="ui-swarm-egress-list">
|
||||
{swarmWide.map((e) => (
|
||||
<li key={e.domain}>
|
||||
<code>{e.domain}</code>
|
||||
<Button disabled={busy} onClick={() => void remove(e)}>
|
||||
remove
|
||||
</Button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
<Button onClick={() => setAdding(true)}>add a domain</Button>
|
||||
<Dialog
|
||||
open={adding}
|
||||
onClose={() => setAdding(false)}
|
||||
label={`add a ${kind === "deny" ? "denied" : "allowed"} domain`}
|
||||
plain
|
||||
>
|
||||
{/* Remounted per open, so a cancelled add leaves nothing behind. */}
|
||||
{adding ? (
|
||||
<AddForm
|
||||
kind={kind}
|
||||
onClose={() => setAdding(false)}
|
||||
onAdded={() => {
|
||||
setAdding(false);
|
||||
refetch();
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
</Dialog>
|
||||
</>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function EgressBody() {
|
||||
const allowed = useAllowedDomains();
|
||||
const denied = useDeniedDomains();
|
||||
return (
|
||||
<>
|
||||
{errorPanel}
|
||||
<p class="ui-swarm-egress-help">
|
||||
Names every agent in the swarm may reach through its hive's proxy, on
|
||||
top of what each hive's own configuration allows. An agent that has
|
||||
unrestricted internet reaches any name anyway. The hive reads a change
|
||||
within about 30 seconds.
|
||||
</p>
|
||||
{swarmWide.length === 0 ? (
|
||||
<p class="ui-agent-limits-muted">none added</p>
|
||||
) : (
|
||||
<ul class="ui-swarm-egress-list">
|
||||
{swarmWide.map((e) => (
|
||||
<li key={e.domain}>
|
||||
<code>{e.domain}</code>
|
||||
<Button disabled={busy} onClick={() => void remove(e)}>
|
||||
remove
|
||||
</Button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
<Button onClick={() => setAdding(true)}>add a domain</Button>
|
||||
<Dialog
|
||||
open={adding}
|
||||
onClose={() => setAdding(false)}
|
||||
label="add an allowed domain"
|
||||
plain
|
||||
>
|
||||
{/* Remounted per open, so a cancelled add leaves nothing behind. */}
|
||||
{adding ? (
|
||||
<AddForm
|
||||
onClose={() => setAdding(false)}
|
||||
onAdded={() => {
|
||||
setAdding(false);
|
||||
refetch();
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
</Dialog>
|
||||
<DomainSection
|
||||
kind="allow"
|
||||
list={allowed}
|
||||
heading="allowed"
|
||||
help="Names every agent in the swarm may reach through its hive's proxy, on top of what each hive's own configuration allows. An agent that has unrestricted internet reaches any name anyway. The hive reads a change within about 30 seconds."
|
||||
/>
|
||||
<DomainSection
|
||||
kind="deny"
|
||||
list={denied}
|
||||
heading="denied"
|
||||
help="Names no agent in the swarm may reach, in every egress mode. A denied name is refused even when an entry above or a hive's own configuration allows it, and even for an agent with unrestricted internet."
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -95,7 +137,7 @@ export function EgressCard() {
|
|||
<LazyDisclosure
|
||||
title="egress"
|
||||
icon="🌐"
|
||||
hint="the domains every agent may reach through its hive's proxy"
|
||||
hint="the domains every agent may or may not reach through its hive's proxy"
|
||||
>
|
||||
<EgressBody />
|
||||
</LazyDisclosure>
|
||||
|
|
|
|||
|
|
@ -37,3 +37,19 @@
|
|||
padding: 0.25em 0;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
.ui-swarm-egress-section + .ui-swarm-egress-section {
|
||||
margin-top: 1.25em;
|
||||
padding-top: 1em;
|
||||
border-top: 1px dashed var(--border);
|
||||
}
|
||||
.ui-swarm-egress-heading {
|
||||
margin: 0 0 0.35em;
|
||||
font-size: 1em;
|
||||
}
|
||||
.ui-swarm-egress-behind {
|
||||
margin: 0 0 0.75em;
|
||||
padding: 0.4em 0.7em;
|
||||
border: 1px solid var(--yellow);
|
||||
border-left-width: 4px;
|
||||
border-radius: 0.4em;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue