fix(domain-proxy): improve endpoint and logic to retrieve domain proxy capabilities
This commit is contained in:
1 parent
e71eaaa6ef
commit
e488207bf8
6 files changed
+122
-37
No files matched your search
@@ -21,11 +21,7 @@ import {
|
||||
DomainConfigMode,
|
||||
PortOption,
|
||||
} from "./domain-types";
|
||||
import {
|
||||
detectAvailableProxies,
|
||||
isIpv4WithPort,
|
||||
parseInspectPortOptions,
|
||||
} from "./domain-utils";
|
||||
import { isIpv4WithPort, parseInspectPortOptions } from "./domain-utils";
|
||||
|
||||
interface AddDomainModalProps {
|
||||
serverList: Server[];
|
||||
@@ -84,6 +80,7 @@ export default function AddDomainModal({
|
||||
const [proxyCapabilityError, setProxyCapabilityError] = useState("");
|
||||
const [certbotInstalled, setCertbotInstalled] = useState(false);
|
||||
const [serverContainers, setServerContainers] = useState<Container[]>([]);
|
||||
const [serverContainersLoading, setServerContainersLoading] = useState(false);
|
||||
const [portOptions, setPortOptions] = useState<PortOption[]>([]);
|
||||
const [containerInspectLoading, setContainerInspectLoading] = useState(false);
|
||||
const [availableProxies, setAvailableProxies] = useState<
|
||||
@@ -175,6 +172,7 @@ export default function AddDomainModal({
|
||||
setProxyCapabilityError("");
|
||||
setAvailableProxies(null);
|
||||
setServerContainers([]);
|
||||
setServerContainersLoading(false);
|
||||
setPortOptions([]);
|
||||
setCertbotInstalled(false);
|
||||
setForm((current) => {
|
||||
@@ -203,32 +201,27 @@ export default function AddDomainModal({
|
||||
}
|
||||
|
||||
setCapabilityLoading(true);
|
||||
setServerContainersLoading(true);
|
||||
setProxyCapabilityError("");
|
||||
|
||||
const containersRequest = containersApi.list({ serverId: form.serverId });
|
||||
void Promise.all([
|
||||
serversApi.getConfig(form.serverId),
|
||||
containersApi.list({ serverId: form.serverId }),
|
||||
serversApi.getDomainProxyCapability(form.serverId),
|
||||
containersRequest,
|
||||
])
|
||||
.then(([configRes, containersRes]) => {
|
||||
.then(([capabilityRes, containersRes]) => {
|
||||
if (cancelled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const proxies = detectAvailableProxies(
|
||||
configRes.data,
|
||||
containersRes.data,
|
||||
);
|
||||
const hasCertbot = configRes.data.webServer.components.some(
|
||||
(component) => component.key === "certbot" && component.installed,
|
||||
);
|
||||
const proxies: DomainProxy[] = ["NONE"];
|
||||
if (containersRes.data.some((container) => `${container.name} ${container.image}`.toLowerCase().includes("traefik"))) proxies.push("TRAEFIK");
|
||||
if (capabilityRes.data.nginx) proxies.push("NGINX");
|
||||
if (capabilityRes.data.caddy) proxies.push("CADDY");
|
||||
const hasCertbot = capabilityRes.data.certbot;
|
||||
|
||||
setCertbotInstalled(hasCertbot);
|
||||
setAvailableProxies(proxies);
|
||||
setServerContainers(
|
||||
containersRes.data.filter(
|
||||
(container) => container.status === "RUNNING",
|
||||
),
|
||||
);
|
||||
setForm((current) => ({
|
||||
...current,
|
||||
sslEnabled: hasCertbot ? current.sslEnabled : false,
|
||||
@@ -244,8 +237,6 @@ export default function AddDomainModal({
|
||||
|
||||
setAvailableProxies(["NONE"]);
|
||||
setCertbotInstalled(false);
|
||||
setServerContainers([]);
|
||||
setPortOptions([]);
|
||||
setForm((current) => ({ ...current, proxy: "NONE" }));
|
||||
setProxyCapabilityError(
|
||||
err instanceof Error
|
||||
@@ -259,6 +250,17 @@ export default function AddDomainModal({
|
||||
}
|
||||
});
|
||||
|
||||
void containersRequest
|
||||
.then((containersRes) => {
|
||||
if (!cancelled) setServerContainers(containersRes.data.filter((container) => container.status === "RUNNING"));
|
||||
})
|
||||
.catch(() => {
|
||||
if (!cancelled) setServerContainers([]);
|
||||
})
|
||||
.finally(() => {
|
||||
if (!cancelled) setServerContainersLoading(false);
|
||||
});
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
@@ -629,6 +631,7 @@ export default function AddDomainModal({
|
||||
capabilityLoading={capabilityLoading}
|
||||
proxyCapabilityError={proxyCapabilityError}
|
||||
serverContainers={serverContainers}
|
||||
serverContainersLoading={serverContainersLoading}
|
||||
portOptions={portOptions}
|
||||
containerInspectLoading={containerInspectLoading}
|
||||
selectableProxies={selectableProxies}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { Container, DomainProxy } from "@/lib/api";
|
||||
import SearchableSelect, { type SearchableSelectOption } from "@/components/SearchableSelect";
|
||||
import { Loader2 } from "lucide-react";
|
||||
import { AddDomainFormState, PortOption } from "./domain-types";
|
||||
|
||||
interface AddDomainTargetingSectionProps {
|
||||
@@ -6,6 +8,7 @@ interface AddDomainTargetingSectionProps {
|
||||
capabilityLoading: boolean;
|
||||
proxyCapabilityError: string;
|
||||
serverContainers: Container[];
|
||||
serverContainersLoading: boolean;
|
||||
portOptions: PortOption[];
|
||||
containerInspectLoading: boolean;
|
||||
selectableProxies: DomainProxy[];
|
||||
@@ -23,6 +26,7 @@ export default function AddDomainTargetingSection({
|
||||
capabilityLoading,
|
||||
proxyCapabilityError,
|
||||
serverContainers,
|
||||
serverContainersLoading,
|
||||
portOptions,
|
||||
containerInspectLoading,
|
||||
selectableProxies,
|
||||
@@ -32,16 +36,22 @@ export default function AddDomainTargetingSection({
|
||||
linkedPortDomains,
|
||||
onChange,
|
||||
}: AddDomainTargetingSectionProps) {
|
||||
const containerOptions: SearchableSelectOption[] = serverContainers.map((container) => {
|
||||
const linkedDomains = linkedContainerDomains.get(container.id) ?? [];
|
||||
const disabled = linkedDomains.length > 0 && !allowSharedTargetReuse;
|
||||
return { value: container.id, label: `${container.name} (${container.image})`, keywords: `${container.name} ${container.image} ${container.dockerId ?? ""}`, description: disabled ? linkedDomains.length > 1 ? "Used by 1+ domains" : `Used by ${linkedDomains.join(", ")}` : undefined, disabled };
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
style={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: "1fr 1fr",
|
||||
gridTemplateColumns: "minmax(0, 1fr)",
|
||||
gap: 12,
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<div style={{ minWidth: 0 }}>
|
||||
<label
|
||||
style={{
|
||||
fontSize: 12,
|
||||
@@ -52,18 +62,22 @@ export default function AddDomainTargetingSection({
|
||||
>
|
||||
Target Container *
|
||||
</label>
|
||||
<select
|
||||
className="input"
|
||||
<SearchableSelect
|
||||
value={form.targetContainerId}
|
||||
onChange={(event) =>
|
||||
options={containerOptions}
|
||||
onChange={(targetContainerId) =>
|
||||
onChange({
|
||||
targetContainerId: event.target.value,
|
||||
targetContainerId,
|
||||
targetPort: "",
|
||||
})
|
||||
}
|
||||
disabled={!form.serverId || capabilityLoading || !targetingEnabled}
|
||||
placeholder="Select container..."
|
||||
searchPlaceholder="Search containers..."
|
||||
emptyText="No matching containers found"
|
||||
disabled={!form.serverId || serverContainersLoading || !targetingEnabled}
|
||||
style={{ width: "100%" }}
|
||||
>
|
||||
/>
|
||||
{/*
|
||||
<option value="">— Select container —</option>
|
||||
{serverContainers.map((container) => {
|
||||
const linkedDomains =
|
||||
@@ -83,9 +97,9 @@ export default function AddDomainTargetingSection({
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
</select>
|
||||
*/}
|
||||
</div>
|
||||
<div>
|
||||
<div style={{ minWidth: 0 }}>
|
||||
<label
|
||||
style={{
|
||||
fontSize: 12,
|
||||
@@ -136,7 +150,14 @@ export default function AddDomainTargetingSection({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{form.serverId && serverContainers.length === 0 && (
|
||||
{form.serverId && serverContainersLoading && (
|
||||
<div role="status" aria-live="polite" style={{ display: "flex", alignItems: "center", gap: 7, marginTop: 8, padding: "8px 10px", border: "1px solid rgba(59,130,246,0.28)", borderRadius: 7, background: "rgba(59,130,246,0.08)", color: "var(--text-muted)", fontSize: 12 }}>
|
||||
<Loader2 size={14} className="animate-spin" aria-hidden="true" />
|
||||
Loading running containers from the selected server…
|
||||
</div>
|
||||
)}
|
||||
|
||||
{form.serverId && !serverContainersLoading && serverContainers.length === 0 && (
|
||||
<p style={{ fontSize: 11, color: "var(--text-muted)" }}>
|
||||
No running containers were found on the selected server.
|
||||
</p>
|
||||
@@ -197,10 +218,11 @@ export default function AddDomainTargetingSection({
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<p style={{ fontSize: 11, color: "var(--text-muted)", marginTop: 6 }}>
|
||||
<p style={{ display: "flex", alignItems: "center", gap: 6, fontSize: 11, color: "var(--text-muted)", marginTop: 6 }}>
|
||||
{form.serverId && capabilityLoading && <Loader2 size={13} className="animate-spin" aria-hidden="true" />}
|
||||
{form.serverId
|
||||
? capabilityLoading
|
||||
? "Inspecting selected server for available reverse proxies..."
|
||||
? "Checking reverse proxy availability on the selected server…"
|
||||
: "Choose container and port first, then pick one of the proxies detected on the selected server."
|
||||
: "Select Server Target first to enable proxy-aware validation."}
|
||||
</p>
|
||||
|
||||
Reference in new issue
Block a user