+
+
setCreateOpen(true),
- }}
+ description="Define TCP/UDP stream proxies powered by caddy-l4. Port mappings are applied automatically."
+ action={{ label: "Create L4 Host", onClick: () => setCreateOpen(true) }}
/>
- handleSearchChange(e.target.value)}
- placeholder="Search L4 hosts..."
- />
+
+ handleSearchChange(e.target.value)}
+ placeholder="Search L4 hosts..."
+ />
+
{
- setCreateOpen(false);
- setTimeout(() => setDuplicateHost(null), 200);
- signalBannerRefresh();
- }}
+ onClose={() => { setCreateOpen(false); setTimeout(() => setDuplicateHost(null), 200); signalBannerRefresh(); }}
initialData={duplicateHost}
/>
@@ -284,10 +231,7 @@ export default function L4ProxyHostsClient({ hosts, pagination, initialSearch }:
{
- setEditHost(null);
- signalBannerRefresh();
- }}
+ onClose={() => { setEditHost(null); signalBannerRefresh(); }}
/>
)}
@@ -295,10 +239,7 @@ export default function L4ProxyHostsClient({ hosts, pagination, initialSearch }:
{
- setDeleteHost(null);
- signalBannerRefresh();
- }}
+ onClose={() => { setDeleteHost(null); signalBannerRefresh(); }}
/>
)}
diff --git a/app/(dashboard)/proxy-hosts/ProxyHostsClient.tsx b/app/(dashboard)/proxy-hosts/ProxyHostsClient.tsx
index 2e07d03c..72f52d82 100644
--- a/app/(dashboard)/proxy-hosts/ProxyHostsClient.tsx
+++ b/app/(dashboard)/proxy-hosts/ProxyHostsClient.tsx
@@ -2,7 +2,7 @@
import { useEffect, useRef, useState } from "react";
import { useRouter, usePathname, useSearchParams } from "next/navigation";
-import { Copy, Pencil, Trash2 } from "lucide-react";
+import { MoreHorizontal } from "lucide-react";
import type { AccessList } from "@/lib/models/access-lists";
import type { Certificate } from "@/lib/models/certificates";
import type { ProxyHost } from "@/lib/models/proxy-hosts";
@@ -14,9 +14,16 @@ import { SearchField } from "@/components/ui/SearchField";
import { DataTable } from "@/components/ui/DataTable";
import { CreateHostDialog, EditHostDialog, DeleteHostDialog } from "@/components/proxy-hosts/HostDialogs";
import { Button } from "@/components/ui/button";
+import { Badge } from "@/components/ui/badge";
import { Switch } from "@/components/ui/switch";
-import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
-import { Card } from "@/components/ui/card";
+import { Card, CardContent } from "@/components/ui/card";
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuSeparator,
+ DropdownMenuTrigger,
+} from "@/components/ui/dropdown-menu";
type Props = {
hosts: ProxyHost[];
@@ -68,144 +75,102 @@ export default function ProxyHostsClient({ hosts, certificates, accessLists, caC
id: "name",
label: "Name",
render: (host: ProxyHost) => (
-
{host.name}
- )
+
+
{host.name}
+
+ {host.domains[0]}{host.domains.length > 1 && ` +${host.domains.length - 1}`}
+
+
+ ),
},
{
- id: "domains",
- label: "Domains",
- render: (host: ProxyHost) => (
-
- {host.domains[0]}
- {host.domains.length > 1 && ` +${host.domains.length - 1} more`}
-
- )
- },
- {
- id: "upstreams",
+ id: "target",
label: "Target",
render: (host: ProxyHost) => (
- {host.upstreams[0]}
- {host.upstreams.length > 1 && ` +${host.upstreams.length - 1} more`}
+ {host.upstreams[0]}{host.upstreams.length > 1 && ` +${host.upstreams.length - 1} more`}
- )
+ ),
+ },
+ {
+ id: "status",
+ label: "Status",
+ width: 100,
+ render: (host: ProxyHost) => (
+
+ {host.enabled ? "Active" : "Paused"}
+
+ ),
},
{
id: "actions",
- label: "Actions",
+ label: "",
align: "right" as const,
- width: 150,
+ width: 80,
render: (host: ProxyHost) => (
-
+
handleToggleEnabled(host.id, checked)}
/>
-
-
- {
- setDuplicateHost(host);
- setCreateOpen(true);
- }}
- >
-
+
+
+
+
+ Open menu
-
- Duplicate
-
-
-
- setEditHost(host)}
- >
-
-
-
- Edit
-
-
-
-
+
+ setEditHost(host)}>Edit
+ { setDuplicateHost(host); setCreateOpen(true); }}>Duplicate
+
+ setDeleteHost(host)}
>
-
-
-
- Delete
-
+ Delete
+
+
+
- )
- }
+ ),
+ },
];
const mobileCard = (host: ProxyHost) => (
-
-
-
-
{host.name}
-
+
+
+
+
+
{host.name}
+
+ {host.domains[0]}{host.domains.length > 1 ? ` +${host.domains.length - 1}` : ""} → {host.upstreams[0]}
+
+
+ {host.enabled ? "Active" : "Paused"}
+
+
+
handleToggleEnabled(host.id, checked)}
/>
-
-
- { setDuplicateHost(host); setCreateOpen(true); }}
- >
-
+
+
+
+
-
- Duplicate
-
-
-
- setEditHost(host)}
- >
-
-
-
- Edit
-
-
-
- setDeleteHost(host)}
- >
-
-
-
- Delete
-
+
+
+ setEditHost(host)}>Edit
+ { setDuplicateHost(host); setCreateOpen(true); }}>Duplicate
+
+ setDeleteHost(host)}>Delete
+
+
-
- {host.domains[0]}{host.domains.length > 1 ? ` +${host.domains.length - 1}` : ""} → {host.upstreams[0]}{host.upstreams.length > 1 ? ` +${host.upstreams.length - 1}` : ""}
-
-
+
);
@@ -214,17 +179,16 @@ export default function ProxyHostsClient({ hosts, certificates, accessLists, caC
setCreateOpen(true)
- }}
+ action={{ label: "Create Host", onClick: () => setCreateOpen(true) }}
/>
- handleSearchChange(e.target.value)}
- placeholder="Search hosts..."
- />
+
+ handleSearchChange(e.target.value)}
+ placeholder="Search hosts..."
+ />
+
{
- setCreateOpen(false);
- // Clear duplicate host after dialog transition
- setTimeout(() => setDuplicateHost(null), 200);
- }}
+ onClose={() => { setCreateOpen(false); setTimeout(() => setDuplicateHost(null), 200); }}
initialData={duplicateHost}
certificates={certificates}
accessLists={accessLists}