diff --git a/web/src/components/domains/DomainCard.tsx b/web/src/components/domains/DomainCard.tsx index a8bec75..0371259 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 } from 'lucide-react'; +import { Trash2, CheckCircle, AlertCircle, Loader2, Plus, Undo2, Save, RotateCw } from 'lucide-react'; import { cn } from '@/lib/utils'; import { useIsMobile } from '@/hooks/use-mobile'; import { DomainTopology } from './DomainTopology'; @@ -109,16 +109,33 @@ function describeChanges(server: DomainDraft, draft: DomainDraft): string[] { /** Compact controls for mobile */ function MobileControls({ - draft, onUpdateDraft, cert, onProvisionCert, isProvisioningCert, + draft, onUpdateDraft, domain, cert, ddnsRecord, + onProvisionCert, onRenewCert, isProvisioningCert, isRenewingCert, }: { draft: DomainDraft; onUpdateDraft: (patch: Partial) => void; + domain: RegisteredDomain; cert: CertEntry | undefined; + ddnsRecord: DdnsRecordStatus | undefined; onProvisionCert: (d: string) => void; + onRenewCert: () => void; isProvisioningCert: boolean; + isRenewingCert: boolean; }) { const isL7 = draft.mode === 'l7'; + const isDnsOnly = draft.mode === 'dns-only'; const hasCert = !!cert?.cert.exists; + const daysLeft = cert?.cert.daysLeft ?? 0; + const hasRoutes = !!domain.routes?.length; + + const [editingBackend, setEditingBackend] = useState(false); + const backendInputRef = useRef(null); + + const handleBackendSave = () => { + const val = backendInputRef.current?.value.trim(); + if (val) onUpdateDraft({ backendAddress: val }); + setEditingBackend(false); + }; return (
@@ -173,13 +190,87 @@ function MobileControls({ Provision )} + {hasCert && daysLeft < 30 && ( + + )}
)}
Backend: - {draft.backendAddress} + {editingBackend ? ( + + { + if (e.key === 'Enter') handleBackendSave(); + if (e.key === 'Escape') setEditingBackend(false); + }} + /> + + + + ) : ( + + )}
+ + {/* 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}` : ''} +
+ )} ); } @@ -362,9 +453,13 @@ export function DomainCard({ onProvisionCert(draft.subdomains ? `*.${domain.domain}` : d || domain.domain)} + onRenewCert={onRenewCert} isProvisioningCert={isProvisioningCert} + isRenewingCert={isRenewingCert} /> ) : ( {leftHandle}
- e.stopPropagation()}> + e.stopPropagation()}> - e.stopPropagation()}> + e.stopPropagation()}> {leftHandle} - e.stopPropagation()}> + e.stopPropagation()}> {leftHandle} - e.stopPropagation()}> + e.stopPropagation()}>