import { useState } from "react"; import { useMutation, useQueryClient, useSuspenseQuery } from "@tanstack/react-query"; import * as stylex from "@stylexjs/stylex"; import InlineError from "@/lib/InlineError"; import { upstreamCreateMutation, upstreamDeleteMutation, upstreamUpdateMutation, upstreamsQuery } from "@/lib/queries"; import type { Upstream, UpstreamInput } from "@/lib/types"; import { raiseRestartBanner } from "../settings/restartBanner"; import UpstreamForm from "./UpstreamForm"; import ConfirmDialog from "@/ui/ConfirmDialog"; import { styles as shared } from "@/ui/styles"; import { colors } from "@/ui/tokens.stylex"; import { READ_ONLY_HINT, useReadOnlyConfig } from "@/features/settings/authority"; const styles = stylex.create({ heading: { fontSize: "1.5rem", lineHeight: "2rem", fontWeight: 600, }, intro: { marginTop: "0.5rem", maxWidth: "42rem", fontSize: "0.875rem", lineHeight: "1.25rem", color: colors.textMuted, }, empty: { marginTop: "1rem", color: colors.textMuted, }, table: { width: "100%", minWidth: "max-content", borderCollapse: "collapse", fontSize: "0.875rem", lineHeight: "1.25rem", }, url: { display: "block", maxWidth: "18rem", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", fontWeight: 500, }, actions: { display: "flex", gap: "0.75rem", }, dimWhenDisabled: { opacity: { default: 1, ":disabled": 0.5 }, }, }); export default function UpstreamsPage() { const queryClient = useQueryClient(); const { data: upstreams } = useSuspenseQuery(upstreamsQuery()); const [editing, setEditing] = useState(null); const [pendingDelete, setPendingDelete] = useState(null); const create = useMutation(upstreamCreateMutation(queryClient)); const save = useMutation(upstreamUpdateMutation(queryClient)); const toggle = useMutation(upstreamUpdateMutation(queryClient)); const remove = useMutation(upstreamDeleteMutation(queryClient)); const readOnly = useReadOnlyConfig(); async function submitForm(input: UpstreamInput) { if (editing === null) { await create.mutateAsync(input); } else { await save.mutateAsync({ id: editing.id, input }); setEditing(null); } raiseRestartBanner(); } function toggleEnabled(u: Upstream) { toggle.mutate( { id: u.id, input: { url: u.url, priority: u.priority, enabled: !u.enabled, tls_name: u.tls_name }, }, { onSuccess: () => raiseRestartBanner() }, ); } function confirmDelete() { if (pendingDelete === null) return; remove.mutate(pendingDelete.id, { onSuccess: () => raiseRestartBanner() }); setPendingDelete(null); } const formError = editing === null ? create.error : save.error; const tableError = remove.error ?? toggle.error; return (

Upstreams

The pool builds its clients at startup, so an edit here takes effect at the next restart. The Upstreams row on Overview counts the running pool, not this list.

{upstreams.length === 0 ? (

No upstreams yet. Add one below.

) : (
{upstreams.map((u) => ( ))}
URL Priority Enabled TLS name Actions
{u.url} {u.priority} toggleEnabled(u)} {...stylex.props(shared.focusRing)} /> {u.tls_name === "" ? "—" : u.tls_name}
)} setEditing(null)} /> setPendingDelete(null)} />
); }