diff --git a/web/src/components/domains/DomainCard.tsx b/web/src/components/domains/DomainCard.tsx index 0371259..ec68ef1 100644 --- a/web/src/components/domains/DomainCard.tsx +++ b/web/src/components/domains/DomainCard.tsx @@ -5,7 +5,7 @@ import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { Switch } from '@/components/ui/switch'; import { Label } from '@/components/ui/label'; -import { Trash2, CheckCircle, AlertCircle, Loader2, Plus, Undo2, Save, RotateCw } from 'lucide-react'; +import { Trash2, CheckCircle, AlertCircle, Loader2, Plus, Undo2, Save, RotateCw, Pencil } from 'lucide-react'; import { cn } from '@/lib/utils'; import { useIsMobile } from '@/hooks/use-mobile'; import { DomainTopology } from './DomainTopology'; @@ -107,7 +107,7 @@ function describeChanges(server: DomainDraft, draft: DomainDraft): string[] { return changes; } -/** Compact controls for mobile */ +/** Compact controls for mobile — vertical pipeline: Source → Routing → Destination */ function MobileControls({ draft, onUpdateDraft, domain, cert, ddnsRecord, onProvisionCert, onRenewCert, isProvisioningCert, isRenewingCert, @@ -138,14 +138,29 @@ function MobileControls({ }; return ( -
-
- - onUpdateDraft({ public: v })} /> +
+ {/* === Section 1: Source === */} +
+
Source
+
+ + onUpdateDraft({ public: v })} /> +
+ {draft.public && ddnsRecord && !ddnsRecord.ok && ( +
+ + DDNS sync failed{ddnsRecord.error ? `: ${ddnsRecord.error}` : ''} +
+ )} +
+ + onUpdateDraft({ subdomains: v })} /> +
-
- + {/* === Section 2: Routing === */} +
+
Routing
{([ { value: 'dns-only' as const, label: 'Direct' }, @@ -163,114 +178,112 @@ function MobileControls({ ))}
+ {isDnsOnly && ( +
+ + No firewall, proxy, or TLS protection — traffic goes directly to your server +
+ )} + {isL7 && ( + <> +
+
+ {hasCert ? ( + <> + + TLS: {(cert!.cert.wildcard || cert!.coveredBy) ? `*.${cert!.domain}` : cert!.domain} + {cert!.cert.daysLeft != null && ({cert!.cert.daysLeft}d)} + + ) : ( + <> + + No TLS certificate + + )} +
+ {!hasCert && ( + + )} + {hasCert && daysLeft < 30 && ( + + )} +
+ {!hasCert && ( +
+ + L7 routing is inactive until a TLS certificate is provisioned +
+ )} + + )}
- {isL7 && ( -
-
- {hasCert ? ( - <> - - TLS: {(cert!.cert.wildcard || cert!.coveredBy) ? `*.${cert!.domain}` : cert!.domain} - {cert!.cert.daysLeft != null && ({cert!.cert.daysLeft}d)} - - ) : ( - <> - - No TLS certificate - - )} -
- {!hasCert && ( - - )} - {hasCert && daysLeft < 30 && ( - - )} -
- )} - -
- Backend: + {/* === Section 3: Destination === */} +
+
Destination
{editingBackend ? ( - +
{ if (e.key === 'Enter') handleBackendSave(); if (e.key === 'Escape') setEditingBackend(false); }} /> - - - +
+ + +
+
) : ( - +
+ + {hasRoutes ? `${domain.routes!.length} backends` : draft.backendAddress} + + {!hasRoutes && ( + + )} +
+ )} + {isL7 && domain.routes && domain.routes.length > 0 && ( +
+
Route table
+ {domain.routes.map((route, i) => ( +
+ + {route.paths?.length ? route.paths.join(', ') : '/*'} + + {'->'} + {route.backend.address} + {route.ipAllow && route.ipAllow.length > 0 && ( + + {route.ipAllow.length} IP rules + + )} +
+ ))} +
)}
- - {/* Route table */} - {isL7 && domain.routes && domain.routes.length > 0 && ( -
-
Route table
- {domain.routes.map((route, i) => ( -
- - {route.paths?.length ? route.paths.join(', ') : '/*'} - - {'->'} - {route.backend.address} - {route.ipAllow && route.ipAllow.length > 0 && ( - - {route.ipAllow.length} IP rules - - )} -
- ))} -
- )} - - {/* Warnings */} - {isDnsOnly && ( -
- - No firewall, proxy, or TLS protection — traffic goes directly to your server -
- )} - {isL7 && !hasCert && ( -
- - L7 routing is inactive until a TLS certificate is provisioned -
- )} - {draft.public && ddnsRecord && !ddnsRecord.ok && ( -
- - DDNS sync failed{ddnsRecord.error ? `: ${ddnsRecord.error}` : ''} -
- )}
); } @@ -507,16 +520,18 @@ export function DomainCard({
)} - {/* === Footer: subdomains + source + deregister === */} + {/* === Footer: subdomains (desktop only) + source + deregister === */}
- + {!isMobile && ( + + )} Source: {domain.source} diff --git a/web/src/components/domains/DomainTopology.tsx b/web/src/components/domains/DomainTopology.tsx index 682b904..f60091a 100644 --- a/web/src/components/domains/DomainTopology.tsx +++ b/web/src/components/domains/DomainTopology.tsx @@ -1,5 +1,4 @@ import { useMemo, useEffect, useState, useCallback, useRef } from 'react'; -import { Link } from 'react-router'; import { ReactFlow, Handle, Position, applyNodeChanges, type Node, type Edge, type NodeProps, type NodeChange, type ReactFlowInstance, @@ -169,23 +168,17 @@ function FlowNode({ data }: NodeProps) { <> {leftHandle}
- e.stopPropagation()}> - } - className="group-hover:border-primary/40" - /> - - e.stopPropagation()}> - } - className="group-hover:border-primary/40" - /> - + } + /> + } + />
{rightHandle} @@ -317,15 +310,12 @@ function FlowNode({ data }: NodeProps) { return ( <> {leftHandle} - e.stopPropagation()}> - } - className="hover:border-primary/40" - /> - + } + /> {rightHandle} ); @@ -336,16 +326,13 @@ function FlowNode({ data }: NodeProps) { return ( <> {leftHandle} - e.stopPropagation()}> - } - className="hover:border-primary/40" - tooltip="Your LAN router port-forwards these ports to Wild Central. See Dashboard for setup." - /> - + } + tooltip="Your LAN router port-forwards these ports to Wild Central" + /> {rightHandle} );