Removes more Wild Cloud cruft.

This commit is contained in:
2026-07-11 23:21:23 +00:00
parent ac66ba653d
commit 5d8fe6a754
21 changed files with 196 additions and 1391 deletions

View File

@@ -1,42 +0,0 @@
#root {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
transition: filter 300ms;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.react:hover {
filter: drop-shadow(0 0 2em #61dafbaa);
}
@keyframes logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
@media (prefers-reduced-motion: no-preference) {
a:nth-of-type(2) .logo {
animation: logo-spin infinite 20s linear;
}
}
.card {
padding: 2em;
}
.read-the-docs {
color: #888;
}

View File

@@ -154,7 +154,7 @@ function MobileControls({
{hasCert ? (
<>
<CheckCircle className="h-3.5 w-3.5 text-green-500" />
<span>TLS: {cert!.coveredBy ? `*.${cert!.domain}` : cert!.domain}</span>
<span>TLS: {(cert!.cert.wildcard || cert!.coveredBy) ? `*.${cert!.domain}` : cert!.domain}</span>
{cert!.cert.daysLeft != null && <span className="text-muted-foreground">({cert!.cert.daysLeft}d)</span>}
</>
) : (

View File

@@ -7,7 +7,7 @@ import {
import '@xyflow/react/dist/style.css';
import {
Globe, Wifi, Lock, Shield, ShieldAlert, Router,
AlertCircle, Loader2, Plus, RotateCw, Pencil,
AlertCircle, Loader2, Plus, RotateCw,
} from 'lucide-react';
import { cn } from '@/lib/utils';
import { Button } from '@/components/ui/button';
@@ -57,6 +57,68 @@ export interface DomainTopologyProps {
const handleStyle = { opacity: 0, width: 4, height: 4 };
function BackendNode({ data, leftHandle, rightHandle, bottomHandle }: {
data: Record<string, unknown>;
leftHandle: React.ReactNode;
rightHandle: React.ReactNode;
bottomHandle: React.ReactNode;
}) {
const onChangeBackend = data.onChangeBackend as ((addr: string) => void) | undefined;
const hasRoutes = data.hasRoutes as boolean;
const editable = !!onChangeBackend && !hasRoutes;
const [editing, setEditing] = useState(false);
const inputRef = useRef<HTMLInputElement>(null);
const handleSave = () => {
const val = inputRef.current?.value.trim();
if (val && onChangeBackend) onChangeBackend(val);
setEditing(false);
};
return (
<>
{leftHandle}
<TopologyNode
label={editing ? '' : (data.label as string)}
sublabel={editing ? undefined : (data.sublabel as string | undefined)}
status="ok"
className="font-mono"
interactive={editable && !editing}
onClick={editable && !editing ? () => setEditing(true) : undefined}
tooltip={editable && !editing ? 'Click to edit backend address' : undefined}
>
{editing && (
<div className="nopan nodrag" style={{ pointerEvents: 'all' }} onPointerDown={e => e.stopPropagation()}>
<Input
ref={inputRef}
defaultValue={data.label as string}
className="h-5 text-[10px] font-mono px-1.5"
autoFocus
onKeyDown={e => {
if (e.key === 'Enter') handleSave();
if (e.key === 'Escape') setEditing(false);
}}
/>
<div className="flex gap-1 mt-1">
<Button variant="ghost" size="sm" className="h-4 text-[9px] px-1 flex-1 !cursor-pointer"
style={{ pointerEvents: 'all' }} onPointerDown={e => e.stopPropagation()} onClick={handleSave}>
Save
</Button>
<Button variant="ghost" size="sm" className="h-4 text-[9px] px-1 flex-1 !cursor-pointer"
style={{ pointerEvents: 'all' }} onPointerDown={e => e.stopPropagation()} onClick={() => setEditing(false)}>
Cancel
</Button>
</div>
</div>
)}
</TopologyNode>
{rightHandle}
{bottomHandle}
</>
);
}
function FlowNode({ data }: NodeProps) {
const v = data.variant as string;
@@ -172,6 +234,7 @@ function FlowNode({ data }: NodeProps) {
if (v === 'tls') {
const status = data.status as NodeStatus;
const certDomain = data.certDomain as string | undefined;
const isWildcard = data.isWildcardCert as boolean;
const daysLeft = data.daysLeft as number;
const hasCert = data.hasCert as boolean;
const provisionDomain = data.provisionDomain as string;
@@ -192,7 +255,7 @@ function FlowNode({ data }: NodeProps) {
tooltip={
hasCert ? (
<div className="space-y-1">
<div>Certificate: {certDomain}</div>
<div>Certificate: {certDomain} ({isWildcard ? 'wildcard' : 'single domain'})</div>
<div>Expires: {expiry ?? 'unknown'} ({daysLeft}d)</div>
{issuer && <div>Issuer: {issuer}</div>}
</div>
@@ -230,63 +293,7 @@ function FlowNode({ data }: NodeProps) {
}
if (v === 'backend') {
const onChangeBackend = data.onChangeBackend as ((addr: string) => void) | undefined;
const hasRoutes = data.hasRoutes as boolean;
const editable = !!onChangeBackend && !hasRoutes;
const [editing, setEditing] = useState(false);
const inputRef = useRef<HTMLInputElement>(null);
const handleSave = () => {
const val = inputRef.current?.value.trim();
if (val && onChangeBackend) onChangeBackend(val);
setEditing(false);
};
return (
<>
{leftHandle}
<TopologyNode
label={data.label as string}
sublabel={data.sublabel as string | undefined}
status="ok"
className="font-mono"
interactive={editable && !editing}
onClick={editable ? () => setEditing(true) : undefined}
tooltip={editable ? 'Click to edit backend address' : undefined}
>
{editable && !editing && (
<Pencil className="absolute top-1.5 right-1.5 h-2.5 w-2.5 text-muted-foreground opacity-0 group-hover:opacity-100 transition-opacity" />
)}
{editing && (
<div className="mt-1.5 nopan nodrag" style={{ pointerEvents: 'all' }} onPointerDown={e => e.stopPropagation()}>
<Input
ref={inputRef}
defaultValue={data.label as string}
className="h-5 text-[10px] font-mono px-1.5"
autoFocus
onKeyDown={e => {
if (e.key === 'Enter') handleSave();
if (e.key === 'Escape') setEditing(false);
}}
/>
<div className="flex gap-1 mt-1">
<Button variant="ghost" size="sm" className="h-4 text-[9px] px-1 flex-1 !cursor-pointer"
style={{ pointerEvents: 'all' }} onPointerDown={e => e.stopPropagation()} onClick={handleSave}>
Save
</Button>
<Button variant="ghost" size="sm" className="h-4 text-[9px] px-1 flex-1 !cursor-pointer"
style={{ pointerEvents: 'all' }} onPointerDown={e => e.stopPropagation()} onClick={() => setEditing(false)}>
Cancel
</Button>
</div>
</div>
)}
</TopologyNode>
{rightHandle}
{bottomHandle}
</>
);
return <BackendNode data={data} leftHandle={leftHandle} rightHandle={rightHandle} bottomHandle={bottomHandle} />;
}
if (v === 'lan') {
@@ -474,7 +481,8 @@ export function DomainTopology(props: DomainTopologyProps) {
// TLS
const hasCert = !!cert?.cert.exists;
const tlsStatus: NodeStatus = hasCert ? 'ok' : 'error';
const certDomain = hasCert ? (cert!.coveredBy ? `*.${cert!.domain}` : cert!.domain) : undefined;
const isWildcardCert = !!(cert?.cert.wildcard || cert?.coveredBy);
const certDomain = hasCert ? (isWildcardCert ? `*.${cert!.domain}` : cert!.domain) : undefined;
const daysLeft = cert?.cert.daysLeft ?? 0;
const provisionDomain = domain.subdomains ? `*.${domain.domain}` : domain.domain;
@@ -556,7 +564,7 @@ export function DomainTopology(props: DomainTopologyProps) {
id: 'tls', type: 'topology',
position: { x: tlsX, y: pipelineY },
data: {
variant: 'tls', status: tlsStatus, certDomain, daysLeft, hasCert, provisionDomain,
variant: 'tls', status: tlsStatus, certDomain, isWildcardCert, daysLeft, hasCert, provisionDomain,
expiry: cert?.cert.expiry ? new Date(cert.cert.expiry).toLocaleDateString() : undefined,
issuer: cert?.cert.issuerCN,
isProvisioningCert, isRenewingCert, onProvisionCert, onRenewCert,

View File

@@ -57,18 +57,20 @@ export const TopologyNode = forwardRef<HTMLDivElement, TopologyNodeProps>(
className,
)}
>
<div className="flex items-center gap-2">
{icon && <span className="shrink-0 text-muted-foreground">{icon}</span>}
<div className="min-w-0 flex-1">
<div className="flex items-center gap-1.5">
<span className={cn('h-1.5 w-1.5 rounded-full shrink-0', statusDotColors[status])} />
<span className="font-medium truncate">{label}</span>
{label && (
<div className="flex items-center gap-2">
{icon && <span className="shrink-0 text-muted-foreground">{icon}</span>}
<div className="min-w-0 flex-1">
<div className="flex items-center gap-1.5">
<span className={cn('h-1.5 w-1.5 rounded-full shrink-0', statusDotColors[status])} />
<span className="font-medium truncate">{label}</span>
</div>
{sublabel && (
<div className="text-[10px] text-muted-foreground mt-0.5 truncate">{sublabel}</div>
)}
</div>
{sublabel && (
<div className="text-[10px] text-muted-foreground mt-0.5 truncate">{sublabel}</div>
)}
</div>
</div>
)}
{children}
</div>
);

View File

@@ -3,6 +3,7 @@ import { apiClient } from './client';
export interface CertInfo {
exists: boolean;
domain?: string;
wildcard?: boolean;
expiry?: string;
certPath?: string;
keyPath?: string;

View File

@@ -1,9 +1,5 @@
import { apiClient } from './client';
export interface DnsmasqStatus {
running: boolean;
status?: string;
}
import type { DnsmasqStatus } from '../../types';
export const dnsmasqApi = {
async getStatus(): Promise<DnsmasqStatus> {

View File

@@ -9,5 +9,5 @@ export { certApi } from './cert';
export type { CertStatusResponse, CertInfo } from './cert';
export { cloudflareApi } from './cloudflare';
export type { CloudflareVerifyResponse, CloudflareZone } from './cloudflare';
export { operatorApi, centralDomainApi, dnsSettingsApi, ddnsConfigApi, dhcpConfigApi, nftablesConfigApi, haproxyRoutesApi } from './settings';
export { dnsSettingsApi, ddnsConfigApi, dhcpConfigApi, nftablesConfigApi, haproxyRoutesApi } from './settings';
export type { OperatorSettings, CentralDomainSettings, DNSSettings, DDNSConfig, DHCPConfig, NftablesConfig, HAProxyCustomRoute, HAProxyRoutes } from './settings';

View File

@@ -10,10 +10,6 @@ export interface Message {
type: 'info' | 'success' | 'error';
}
export interface LoadingState {
[key: string]: boolean;
}
export interface Messages {
[key: string]: Message;
}

View File

@@ -1,3 +0,0 @@
export const formatTimestamp = (timestamp: string): string => {
return new Date(timestamp).toLocaleString();
};