diff --git a/web/src/pages/AdminLinks.tsx b/web/src/pages/AdminLinks.tsx index 9172929..57df684 100644 --- a/web/src/pages/AdminLinks.tsx +++ b/web/src/pages/AdminLinks.tsx @@ -1,9 +1,10 @@ import { useEffect, useMemo, useState, type FormEvent } from "react"; import { Link } from "react-router-dom"; -import { api, type AdminLink, type CurrentUser, type ServerRecord } from "../api/client"; +import { api, type AdminLink, type CurrentUser, type PortEntry, type ServerRecord } from "../api/client"; import { useSortable } from "../hooks/useSortable"; import SortableTh from "../components/SortableTh"; import { downloadCsv } from "../utils/csv"; +import { guessAdminUrl, portOptionLabel } from "../utils/adminLinkUrl"; interface LinkForm { serverId: number | ""; @@ -26,6 +27,11 @@ export default function AdminLinks({ user }: { user: CurrentUser }) { const [form, setForm] = useState(emptyForm); const [saving, setSaving] = useState(false); + const [portOptions, setPortOptions] = useState(null); + const [portAddress, setPortAddress] = useState(null); + const [loadingPorts, setLoadingPorts] = useState(false); + const [portPick, setPortPick] = useState(""); + function load() { api.servers.allLinks() .then((res) => setLinks(res.links)) @@ -40,6 +46,37 @@ export default function AdminLinks({ user }: { user: CurrentUser }) { .catch(() => {}); }, []); + // Once a server is picked in the add/edit form, offer to fill the URL from one of its own known + // ports (agent-reported or already noted on its Ports card) instead of typing it out by hand. + useEffect(() => { + setPortPick(""); + if (form.serverId === "") { + setPortOptions(null); + setPortAddress(null); + return; + } + const serverId = form.serverId; + setLoadingPorts(true); + Promise.all([api.servers.detail(serverId), api.servers.ports.list(serverId)]) + .then(([detail, portList]) => { + setPortAddress(detail.server.hostname || detail.ipAddresses[0] || null); + setPortOptions(portList.ports); + }) + .catch(() => { + setPortOptions(null); + setPortAddress(null); + }) + .finally(() => setLoadingPorts(false)); + }, [form.serverId]); + + function pickPort(key: string) { + setPortPick(key); + const entry = portOptions?.find((p) => `${p.protocol}:${p.port}` === key); + if (!entry) return; + const url = guessAdminUrl(portAddress, entry); + setForm((f) => ({ ...f, url: url ?? f.url, label: f.label || entry.label || entry.agent?.process || "" })); + } + const filtered = useMemo(() => { if (!links) return []; const q = search.trim().toLowerCase(); @@ -166,6 +203,37 @@ export default function AdminLinks({ user }: { user: CurrentUser }) { onChange={(e) => setForm({ ...form, url: e.target.value })} /> + {form.serverId !== "" && ( +
+ + + {portOptions && portOptions.length > 0 && !portAddress && ( +
+ This server has no known hostname or IP yet, so picking a port won't fill in an address — + see its Ports card under Infrastructure → Servers. +
+ )} +
+ )}
+ {!!adminLinkPorts?.length && ( +
+ + +
+ )}