84 lines
2.5 KiB
TypeScript
84 lines
2.5 KiB
TypeScript
// The swarm's allowed-domain and denied-domain entries and the one change that
|
|
// is not a form: removing an entry. Shared by an agent card's allowed domains
|
|
// and the swarm page's egress card, which differ only in which entries they
|
|
// show.
|
|
//
|
|
// Reads `GET <url>` (every entry) and removes one with a `DELETE` carrying the
|
|
// entry, each answering the whole list. `refetch` reads the list again, for a
|
|
// caller that added an entry through its own form. The deny list also reports
|
|
// `behind`, the hives that have not taken its current revision yet.
|
|
import { useEffect, useState } from "preact/hooks";
|
|
import { readApiError, type ProblemDetails } from "@hive/shared/api-error.js";
|
|
import {
|
|
ALLOWED_DOMAINS_URL,
|
|
DENIED_DOMAINS_URL,
|
|
type AllowedDomain,
|
|
} from "./allowed-domains.js";
|
|
|
|
interface Listing {
|
|
entries: AllowedDomain[];
|
|
behind?: string[];
|
|
}
|
|
|
|
function useDomainList(url: string) {
|
|
const [entries, setEntries] = useState<AllowedDomain[] | null>(null);
|
|
const [behind, setBehind] = useState<string[]>([]);
|
|
const [error, setError] = useState<ProblemDetails | null>(null);
|
|
// Bumped after an add or a removal, so the list is fetched again.
|
|
const [reload, setReload] = useState(0);
|
|
const [busy, setBusy] = useState(false);
|
|
|
|
useEffect(() => {
|
|
let cancelled = false;
|
|
(async () => {
|
|
const r = await fetch(url);
|
|
if (!r.ok) {
|
|
if (!cancelled) setError(await readApiError(r));
|
|
return;
|
|
}
|
|
const body = (await r.json()) as Listing;
|
|
if (cancelled) return;
|
|
setEntries(body.entries);
|
|
setBehind(body.behind ?? []);
|
|
setError(null);
|
|
})().catch((e: unknown) => {
|
|
if (!cancelled) setError({ detail: String(e) });
|
|
});
|
|
return () => {
|
|
cancelled = true;
|
|
};
|
|
}, [url, reload]);
|
|
|
|
async function remove(entry: AllowedDomain) {
|
|
if (busy) return;
|
|
setBusy(true);
|
|
try {
|
|
const r = await fetch(url, {
|
|
method: "DELETE",
|
|
headers: { "content-type": "application/json" },
|
|
body: JSON.stringify(entry),
|
|
});
|
|
if (!r.ok) {
|
|
setError(await readApiError(r));
|
|
return;
|
|
}
|
|
setReload((n) => n + 1);
|
|
} catch (e: unknown) {
|
|
setError({ detail: String(e) });
|
|
} finally {
|
|
setBusy(false);
|
|
}
|
|
}
|
|
|
|
return {
|
|
entries,
|
|
behind,
|
|
error,
|
|
busy,
|
|
remove,
|
|
refetch: () => setReload((n) => n + 1),
|
|
};
|
|
}
|
|
|
|
export const useAllowedDomains = () => useDomainList(ALLOWED_DOMAINS_URL);
|
|
export const useDeniedDomains = () => useDomainList(DENIED_DOMAINS_URL);
|