import { useCallback, useEffect, useRef, useState } from "react"; import { AlertTriangle, Globe, Lock, LockOpen, Power, RefreshCw, ShieldCheck, Users } from "lucide-react"; import { useClient } from "hooks/useClient"; import { useSearchParams } from "react-router"; /** * The proxy hosts, which is the thing anybody opens this plugin to see. * * Selection lives in the URL (`?host=`), not in a channel — `docs/navigation-audit.md` is the * authority and this is the case it names: the detail panel reads the same parameter rather than being * handed the id through a bus. */ type ProxyHost = { id: number; domain_names: string[]; forward_scheme: string; forward_host: string; forward_port: number; enabled: number; ssl_forced: number; certificate_id: number; access_list_id: number; meta?: { nginx_online?: boolean; nginx_err?: string | null }; }; type Failure = { error: string; likelyAuth?: boolean; unreachable?: boolean; unconfigured?: boolean }; export const HostsPanel = () => { const client = useClient(); // useClient() rebuilds its verbs every render, so it must not sit in a dependency list. const clientRef = useRef(client); clientRef.current = client; const [hosts, setHosts] = useState(null); const [failure, setFailure] = useState(null); const [busy, setBusy] = useState(null); const [params, setParams] = useSearchParams(); const selected = params.get("host"); const load = useCallback(async () => { try { setHosts(await clientRef.current.get("/proxy-manager/hosts")); setFailure(null); } catch (error) { const body = (error as { body?: Failure })?.body; setFailure(body ?? { error: error instanceof Error ? error.message : "Could not load hosts." }); setHosts(null); } }, []); useEffect(() => { void load(); }, [load]); const toggle = async (host: ProxyHost) => { setBusy(host.id); try { await clientRef.current.post(`/proxy-manager/hosts/${host.id}/${host.enabled ? "disable" : "enable"}`, {}); await load(); } catch { setFailure({ error: `Could not ${host.enabled ? "disable" : "enable"} that host.` }); } finally { setBusy(null); } }; if (failure) return void load()} />; if (!hosts) return Loading hosts…; if (hosts.length === 0) return Nginx Proxy Manager has no proxy hosts.; return (
{hosts.length} host{hosts.length === 1 ? "" : "s"}
    {hosts.map((host) => { const offline = host.meta?.nginx_online === false; return (
  • ); })}
{selected ? (
void toggle(host)} />
) : null}
); }; const ToggleButton = ({ hosts, selected, busy, onToggle, }: { hosts: ProxyHost[]; selected: string; busy: number | null; onToggle: (host: ProxyHost) => void; }) => { const host = hosts.find((h) => String(h.id) === selected); if (!host) return null; return ( ); }; /** * NPM answers 404 when unauthenticated, so a naive client shows "not found" for a wrong password. The * server marks that case `likelyAuth`; this turns it into a sentence that points somewhere useful. */ const Failed = ({ failure, onRetry }: { failure: Failure; onRetry: () => void }) => (
{failure.unconfigured ? ( <>

No Nginx Proxy Manager connection is set.

The owner sets it in this plugin's settings.

) : failure.likelyAuth ? ( <>

Nginx Proxy Manager refused the credential.

Its API answers 404 when unauthenticated, so this is almost always a wrong identity or secret rather than a missing endpoint.

) : failure.unreachable ? ( <>

Nginx Proxy Manager is not answering.

{failure.error}

) : (

{failure.error}

)}
); const Centred = ({ children }: { children: React.ReactNode }) => (
{children}
);