Show routes, TLS certs, and port-forwarding on services page
- Add Routes model to services UI (paths, headers, IP whitelisting per route) - Show TLS cert info per service with inline provision/renew actions - Remove TLS Certificates section from dashboard (now on services page) - Make gateway router port list dynamic from config + VPN state - Add TODO for header validation in HAProxy config generation
This commit is contained in:
@@ -7,13 +7,13 @@ import { Alert, AlertDescription } from './ui/alert';
|
||||
import { Badge } from './ui/badge';
|
||||
import {
|
||||
LayoutDashboard, Cloud, CheckCircle, XCircle, AlertCircle, Loader2,
|
||||
RefreshCw, BookOpen, Globe, Shield, Router, Server, RotateCw, Key, Plus, X,
|
||||
RefreshCw, BookOpen, Globe, Router, Server, RotateCw, Key, Plus, X,
|
||||
} from 'lucide-react';
|
||||
import { useCloudflare } from '../hooks/useCloudflare';
|
||||
import { useDdns } from '../hooks/useDdns';
|
||||
import { useCentralStatus } from '../hooks/useCentralStatus';
|
||||
import { useCert } from '../hooks/useCert';
|
||||
import { useConfig } from '../hooks';
|
||||
import { useVpn } from '../hooks/useVpn';
|
||||
import { secretsApi } from '../services/api';
|
||||
import { usePageHelp } from '../hooks/usePageHelp';
|
||||
import type { DdnsRecordStatus } from '../services/api/networking';
|
||||
@@ -22,8 +22,8 @@ export function DashboardComponent() {
|
||||
const { data: centralStatus, isLoading: statusLoading, restart: restartDaemon, isRestarting } = useCentralStatus();
|
||||
const { verification, isLoading: cfLoading } = useCloudflare();
|
||||
const { status: ddnsStatus, isLoadingStatus: ddnsLoading, trigger: triggerDdns, isTriggering } = useDdns();
|
||||
const { status: certStatus } = useCert();
|
||||
const { config: globalConfig, updateConfig } = useConfig();
|
||||
const { config: vpnConfig } = useVpn();
|
||||
|
||||
const queryClient = useQueryClient();
|
||||
const { data: globalSecrets } = useQuery({
|
||||
@@ -52,10 +52,8 @@ export function DashboardComponent() {
|
||||
// Warning conditions
|
||||
const cfTokenMissing = !cfLoading && !verification?.tokenConfigured;
|
||||
const cfTokenInvalid = !cfLoading && verification?.tokenConfigured && !verification?.tokenValid;
|
||||
const certExpiring = certStatus?.certs?.some(c => c.cert.exists && (c.cert.daysLeft ?? 999) < 14);
|
||||
const certMissing = certStatus?.certs?.some(c => !c.cert.exists);
|
||||
const ddnsFailed = ddnsStatus?.enabled && ddnsStatus?.lastError;
|
||||
const hasWarnings = cfTokenMissing || cfTokenInvalid || certExpiring || certMissing || ddnsFailed;
|
||||
const hasWarnings = cfTokenMissing || cfTokenInvalid || ddnsFailed;
|
||||
|
||||
usePageHelp({
|
||||
title: 'What is the Dashboard?',
|
||||
@@ -103,18 +101,6 @@ export function DashboardComponent() {
|
||||
<AlertDescription>Cloudflare API token is invalid. Check your token in Cloudflare settings.</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
{certMissing && (
|
||||
<Alert variant="warning">
|
||||
<AlertCircle className="h-4 w-4" />
|
||||
<AlertDescription>One or more TLS certificates are missing. Services may not be reachable over HTTPS.</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
{certExpiring && !certMissing && (
|
||||
<Alert variant="warning">
|
||||
<AlertCircle className="h-4 w-4" />
|
||||
<AlertDescription>A TLS certificate expires within 14 days. Consider renewing soon.</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
{ddnsFailed && (
|
||||
<Alert variant="error">
|
||||
<AlertCircle className="h-4 w-4" />
|
||||
@@ -426,62 +412,39 @@ export function DashboardComponent() {
|
||||
)}
|
||||
</Card>
|
||||
|
||||
{/* 4. TLS Certificates */}
|
||||
{certStatus?.certs && certStatus.certs.length > 0 && (
|
||||
<Card className="p-4 border-l-4 border-l-blue-500">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<Shield className="h-5 w-5 text-blue-500" />
|
||||
<div className="font-medium">TLS Certificates</div>
|
||||
</div>
|
||||
{(() => {
|
||||
const allExist = certStatus.certs!.every(c => c.cert.exists);
|
||||
const someExist = certStatus.certs!.some(c => c.cert.exists);
|
||||
if (allExist) {
|
||||
return <Badge variant="success" className="gap-1"><CheckCircle className="h-3 w-3" />All valid</Badge>;
|
||||
} else if (someExist) {
|
||||
return <Badge variant="warning" className="gap-1"><AlertCircle className="h-3 w-3" />Partial</Badge>;
|
||||
} else {
|
||||
return <Badge variant="destructive" className="gap-1"><XCircle className="h-3 w-3" />Missing</Badge>;
|
||||
}
|
||||
})()}
|
||||
</div>
|
||||
<div className="space-y-2 ml-7">
|
||||
{certStatus.certs!.map((entry) => (
|
||||
<div key={entry.domain} className="flex items-center justify-between text-sm">
|
||||
<span className="font-mono">{entry.domain}</span>
|
||||
{entry.cert.exists ? (
|
||||
<span className="text-xs text-green-600 dark:text-green-400">
|
||||
Valid ({entry.cert.daysLeft}d)
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-xs text-red-500">Missing</span>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* 5. Gateway Router */}
|
||||
{/* 4. Gateway Router */}
|
||||
<Card className="p-4 border-l-4 border-l-amber-500 bg-gradient-to-r from-amber-50/50 to-orange-50/50 dark:from-amber-950/10 dark:to-orange-950/10">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<Router className="h-5 w-5 text-amber-500" />
|
||||
<div className="font-medium">Gateway Router</div>
|
||||
</div>
|
||||
<div className="space-y-2 ml-7 text-sm text-muted-foreground">
|
||||
<p>Ensure your router is configured for Wild Central to work correctly:</p>
|
||||
<p>Your LAN router should be configured to work with Wild Central:</p>
|
||||
<ul className="list-disc list-inside space-y-1 text-xs">
|
||||
<li>
|
||||
Forward DNS to Wild Central
|
||||
Set the router's DNS server to Wild Central
|
||||
{globalConfig?.cloud?.dnsmasq?.ip && (
|
||||
<span className="font-mono bg-muted px-1.5 py-0.5 rounded ml-1">
|
||||
{globalConfig.cloud.dnsmasq.ip}
|
||||
</span>
|
||||
)}
|
||||
</li>
|
||||
<li>Forward ports <span className="font-mono">443</span>, <span className="font-mono">80</span>, <span className="font-mono">51820</span> to Wild Central</li>
|
||||
<li>Set Wild Central as the primary DNS server for your LAN</li>
|
||||
<li>
|
||||
Port-forward to Wild Central:{' '}
|
||||
{[
|
||||
{ port: 443, proto: 'TCP', label: 'HTTPS' },
|
||||
{ port: 80, proto: 'TCP', label: 'HTTP' },
|
||||
...(vpnConfig?.enabled ? [{ port: vpnConfig.listenPort || 51820, proto: 'UDP', label: 'VPN' }] : []),
|
||||
...((globalConfig?.cloud?.nftables?.extraPorts ?? []) as { port: number; protocol?: string; label?: string }[])
|
||||
.map(ep => ({ port: ep.port, proto: (ep.protocol || 'tcp').toUpperCase(), label: ep.label || '' })),
|
||||
].map((p, i) => (
|
||||
<span key={i}>
|
||||
{i > 0 && ', '}
|
||||
<span className="font-mono">{p.port}</span>/{p.proto}
|
||||
{p.label && <span className="text-muted-foreground/60"> ({p.label})</span>}
|
||||
</span>
|
||||
))}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
Reference in New Issue
Block a user