diff --git a/src/components/l4-proxy-hosts/L4HostDialogs.tsx b/src/components/l4-proxy-hosts/L4HostDialogs.tsx
index 423d71ca..3cfa030a 100644
--- a/src/components/l4-proxy-hosts/L4HostDialogs.tsx
+++ b/src/components/l4-proxy-hosts/L4HostDialogs.tsx
@@ -1,5 +1,5 @@
-import { Accordion, AccordionDetails, AccordionSummary, Alert, Box, FormControlLabel, MenuItem, Stack, Switch, TextField, Typography } from "@mui/material";
-import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
+"use client";
+
import { useFormState } from "react-dom";
import { useEffect, useState } from "react";
import {
@@ -7,9 +7,49 @@ import {
deleteL4ProxyHostAction,
updateL4ProxyHostAction,
} from "@/app/(dashboard)/l4-proxy-hosts/actions";
-import { INITIAL_ACTION_STATE } from "@/src/lib/actions";
-import type { L4ProxyHost } from "@/src/lib/models/l4-proxy-hosts";
-import { AppDialog } from "@/src/components/ui/AppDialog";
+import { INITIAL_ACTION_STATE } from "@/lib/actions";
+import type { L4ProxyHost } from "@/lib/models/l4-proxy-hosts";
+import { AppDialog } from "@/components/ui/AppDialog";
+import { Alert, AlertDescription } from "@/components/ui/alert";
+import { Input } from "@/components/ui/input";
+import { Label } from "@/components/ui/label";
+import { Switch } from "@/components/ui/switch";
+import { Textarea } from "@/components/ui/textarea";
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@/components/ui/select";
+import {
+ Accordion,
+ AccordionContent,
+ AccordionItem,
+ AccordionTrigger,
+} from "@/components/ui/accordion";
+
+function FormField({
+ label,
+ htmlFor,
+ helperText,
+ children,
+}: {
+ label: string;
+ htmlFor: string;
+ helperText?: string;
+ children: React.ReactNode;
+}) {
+ return (
+
+
+ {children}
+ {helperText && (
+
{helperText}
+ )}
+
+ );
+}
function L4HostForm({
formId,
@@ -23,310 +63,694 @@ function L4HostForm({
initialData?: L4ProxyHost | null;
}) {
const [protocol, setProtocol] = useState(initialData?.protocol ?? "tcp");
- const [matcherType, setMatcherType] = useState(initialData?.matcher_type ?? "none");
+ const [matcherType, setMatcherType] = useState(
+ initialData?.matcher_type ?? "none"
+ );
+
+ const defaultLbAccordion = initialData?.load_balancer?.enabled
+ ? "load-balancer"
+ : undefined;
+ const defaultDnsAccordion = initialData?.dns_resolver?.enabled
+ ? "dns-resolver"
+ : undefined;
+ const defaultGeoblockAccordion = initialData?.geoblock?.enabled
+ ? "geoblock"
+ : undefined;
+ const defaultUpstreamDnsAccordion =
+ initialData?.upstream_dns_resolution?.enabled === true
+ ? "upstream-dns"
+ : undefined;
return (
-
+
);
}
@@ -339,7 +763,10 @@ export function CreateL4HostDialog({
onClose: () => void;
initialData?: L4ProxyHost | null;
}) {
- const [state, formAction] = useFormState(createL4ProxyHostAction, INITIAL_ACTION_STATE);
+ const [state, formAction] = useFormState(
+ createL4ProxyHostAction,
+ INITIAL_ACTION_STATE
+ );
useEffect(() => {
if (state.status === "success") {
@@ -355,14 +782,18 @@ export function CreateL4HostDialog({
maxWidth="sm"
submitLabel="Create"
onSubmit={() => {
- (document.getElementById("create-l4-host-form") as HTMLFormElement)?.requestSubmit();
+ (
+ document.getElementById("create-l4-host-form") as HTMLFormElement
+ )?.requestSubmit();
}}
>
);
@@ -377,7 +808,10 @@ export function EditL4HostDialog({
host: L4ProxyHost;
onClose: () => void;
}) {
- const [state, formAction] = useFormState(updateL4ProxyHostAction.bind(null, host.id), INITIAL_ACTION_STATE);
+ const [state, formAction] = useFormState(
+ updateL4ProxyHostAction.bind(null, host.id),
+ INITIAL_ACTION_STATE
+ );
useEffect(() => {
if (state.status === "success") {
@@ -393,7 +827,9 @@ export function EditL4HostDialog({
maxWidth="sm"
submitLabel="Save Changes"
onSubmit={() => {
- (document.getElementById("edit-l4-host-form") as HTMLFormElement)?.requestSubmit();
+ (
+ document.getElementById("edit-l4-host-form") as HTMLFormElement
+ )?.requestSubmit();
}}
>
void;
}) {
- const [state, formAction] = useFormState(deleteL4ProxyHostAction.bind(null, host.id), INITIAL_ACTION_STATE);
+ const [state, formAction] = useFormState(
+ deleteL4ProxyHostAction.bind(null, host.id),
+ INITIAL_ACTION_STATE
+ );
useEffect(() => {
if (state.status === "success") {
@@ -431,36 +870,45 @@ export function DeleteL4HostDialog({
maxWidth="sm"
submitLabel="Delete"
onSubmit={() => {
- (document.getElementById("delete-l4-host-form") as HTMLFormElement)?.requestSubmit();
+ (
+ document.getElementById("delete-l4-host-form") as HTMLFormElement
+ )?.requestSubmit();
}}
>
-
+
+
+
);
}
diff --git a/src/components/l4-proxy-hosts/L4PortsApplyBanner.tsx b/src/components/l4-proxy-hosts/L4PortsApplyBanner.tsx
index 4159b841..0b1626b7 100644
--- a/src/components/l4-proxy-hosts/L4PortsApplyBanner.tsx
+++ b/src/components/l4-proxy-hosts/L4PortsApplyBanner.tsx
@@ -1,10 +1,11 @@
"use client";
import { useCallback, useEffect, useState } from "react";
-import { Alert, Button, Chip, CircularProgress, Stack, Typography } from "@mui/material";
-import SyncIcon from "@mui/icons-material/Sync";
-import CheckCircleIcon from "@mui/icons-material/CheckCircle";
-import ErrorIcon from "@mui/icons-material/Error";
+import { RefreshCw, CheckCircle, XCircle } from "lucide-react";
+import { Alert, AlertDescription } from "@/components/ui/alert";
+import { Button } from "@/components/ui/button";
+import { Badge } from "@/components/ui/badge";
+import { cn } from "@/lib/utils";
type PortsDiff = {
currentPorts: string[];
@@ -54,11 +55,15 @@ export function L4PortsApplyBanner({ refreshSignal }: { refreshSignal?: number }
useEffect(() => {
if (!data) return;
- const shouldPoll = data.status.state === "pending" || data.status.state === "applying";
+ const shouldPoll =
+ data.status.state === "pending" || data.status.state === "applying";
if (shouldPoll && !polling) {
setPolling(true);
const interval = setInterval(fetchStatus, 2000);
- return () => { clearInterval(interval); setPolling(false); };
+ return () => {
+ clearInterval(interval);
+ setPolling(false);
+ };
}
if (!shouldPoll && polling) {
setPolling(false);
@@ -88,54 +93,80 @@ export function L4PortsApplyBanner({ refreshSignal }: { refreshSignal?: number }
return null;
}
- const stateIcon = {
- idle: null,
- pending: ,
- applying: ,
- applied: ,
- failed: ,
- }[status.state];
+ const isSpinning =
+ status.state === "pending" || status.state === "applying";
- const severity = status.state === "failed" ? "error"
- : status.state === "applied" ? "success"
- : diff.needsApply ? "warning"
- : "info";
+ const alertVariant: "default" | "destructive" =
+ status.state === "failed" ? "destructive" : "default";
+
+ const stateIcon =
+ status.state === "applied" ? (
+
+ ) : status.state === "failed" ? (
+
+ ) : isSpinning ? (
+
+ ) : null;
return (
: }
- >
- Apply Ports
-
- ) : undefined
- }
+ variant={alertVariant}
+ className={cn(
+ "flex items-start gap-3",
+ status.state === "applied" && "border-green-500/50 text-green-700 dark:text-green-400",
+ diff.needsApply && status.state !== "failed" && status.state !== "applied" && "border-yellow-500/50 text-yellow-800 dark:text-yellow-400"
+ )}
>
-
- {diff.needsApply ? (
-
- Docker port changes pending. The caddy container needs to be recreated to expose L4 ports.
- {diff.requiredPorts.length > 0 && (
- <> Required: {diff.requiredPorts.map(p => (
-
- ))}>
- )}
-
- ) : (
- {status.message}
- )}
- {status.state === "failed" && status.error && (
- {status.error}
- )}
-
+ {stateIcon && {stateIcon}
}
+
+
+ {diff.needsApply ? (
+
+ Docker port changes pending. The caddy container
+ needs to be recreated to expose L4 ports.
+ {diff.requiredPorts.length > 0 && (
+
+ Required:{" "}
+ {diff.requiredPorts.map((p) => (
+
+ {p}
+
+ ))}
+
+ )}
+
+ ) : (
+
{status.message}
+ )}
+ {status.state === "failed" && status.error && (
+
{status.error}
+ )}
+
+
+ {diff.needsApply && (
+
+ )}
);
}