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 upstream health table on the Dashboard reflects 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)} />
); }