services -> domains
This commit is contained in:
@@ -7,7 +7,7 @@ import { Alert, AlertDescription } from './ui/alert';
|
||||
import { Badge } from './ui/badge';
|
||||
import {
|
||||
LayoutDashboard, Cloud, CheckCircle, XCircle, AlertCircle, Loader2,
|
||||
RefreshCw, BookOpen, Globe, Router, Server, RotateCw, Key, Plus, X,
|
||||
RefreshCw, BookOpen, Globe, Router, Server, RotateCw, Key,
|
||||
} from 'lucide-react';
|
||||
import { useCloudflare } from '../hooks/useCloudflare';
|
||||
import { useDdns } from '../hooks/useDdns';
|
||||
@@ -22,7 +22,7 @@ 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 { config: globalConfig, updateConfig } = useConfig();
|
||||
const { config: globalConfig } = useConfig();
|
||||
const { config: vpnConfig } = useVpn();
|
||||
|
||||
const queryClient = useQueryClient();
|
||||
@@ -36,8 +36,6 @@ export function DashboardComponent() {
|
||||
|
||||
const [editingToken, setEditingToken] = useState(false);
|
||||
const [tokenValue, setTokenValue] = useState('');
|
||||
const [addingDdnsRecord, setAddingDdnsRecord] = useState(false);
|
||||
const [newDdnsRecord, setNewDdnsRecord] = useState('');
|
||||
|
||||
const updateSecretsMutation = useMutation({
|
||||
mutationFn: (values: Record<string, unknown>) => secretsApi.update(values),
|
||||
@@ -260,76 +258,33 @@ export function DashboardComponent() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Record list with remove buttons */}
|
||||
{globalConfig?.cloud?.ddns?.records?.length ? (
|
||||
{/* Record list (read-only — derived from public domains) */}
|
||||
{ddnsStatus.records?.length ? (
|
||||
<div className="space-y-1">
|
||||
{globalConfig.cloud.ddns.records.map((r: string) => {
|
||||
const rs: DdnsRecordStatus | undefined = ddnsStatus.records?.find((s: DdnsRecordStatus) => s.name === r);
|
||||
return (
|
||||
<div key={r} className="flex items-center gap-2 text-xs group">
|
||||
{rs?.ok ? (
|
||||
<CheckCircle className="h-3.5 w-3.5 text-green-500 shrink-0" />
|
||||
) : rs && !rs.ok ? (
|
||||
<XCircle className="h-3.5 w-3.5 text-red-500 shrink-0" />
|
||||
) : (
|
||||
<div className="h-3.5 w-3.5 shrink-0" />
|
||||
)}
|
||||
<span className="font-mono bg-muted px-2 py-0.5 rounded">{r}</span>
|
||||
{rs?.ok && rs.ip && (
|
||||
<span className="text-muted-foreground font-mono">{rs.ip}</span>
|
||||
)}
|
||||
{rs && !rs.ok && rs.error && (
|
||||
<span className="text-red-500 truncate" title={rs.error}>{rs.error}</span>
|
||||
)}
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-5 w-5 p-0 opacity-0 group-hover:opacity-100 ml-auto"
|
||||
onClick={async () => {
|
||||
if (!globalConfig) return;
|
||||
const records = (globalConfig.cloud?.ddns?.records || []).filter((rec: string) => rec !== r);
|
||||
await updateConfig({
|
||||
...globalConfig,
|
||||
cloud: { ...globalConfig.cloud, ddns: { ...globalConfig.cloud?.ddns, records } },
|
||||
});
|
||||
}}
|
||||
>
|
||||
<X className="h-3 w-3" />
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{ddnsStatus.records.map((rs: DdnsRecordStatus) => (
|
||||
<div key={rs.name} className="flex items-center gap-2 text-xs">
|
||||
{rs.ok ? (
|
||||
<CheckCircle className="h-3.5 w-3.5 text-green-500 shrink-0" />
|
||||
) : (
|
||||
<XCircle className="h-3.5 w-3.5 text-red-500 shrink-0" />
|
||||
)}
|
||||
<span className="font-mono bg-muted px-2 py-0.5 rounded">{rs.name}</span>
|
||||
{rs.ok && rs.ip && (
|
||||
<span className="text-muted-foreground font-mono">{rs.ip}</span>
|
||||
)}
|
||||
{!rs.ok && rs.error && (
|
||||
<span className="text-red-500 truncate" title={rs.error}>{rs.error}</span>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
<p className="text-xs text-muted-foreground mt-1">
|
||||
Records are derived from public domains. Toggle Public on the Domains page to manage.
|
||||
</p>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{/* Add DDNS record */}
|
||||
{!addingDdnsRecord ? (
|
||||
<Button variant="ghost" size="sm" onClick={() => setAddingDdnsRecord(true)} className="gap-1 text-muted-foreground">
|
||||
<Plus className="h-3 w-3" />
|
||||
Add Record
|
||||
</Button>
|
||||
) : (
|
||||
<div className="flex gap-2 items-center">
|
||||
<Input
|
||||
value={newDdnsRecord}
|
||||
onChange={(e) => setNewDdnsRecord(e.target.value)}
|
||||
placeholder="dev.payne.io"
|
||||
className="h-8 text-xs font-mono flex-1"
|
||||
/>
|
||||
<Button size="sm" className="h-8" disabled={!newDdnsRecord} onClick={async () => {
|
||||
if (!globalConfig || !newDdnsRecord) return;
|
||||
const records = [...(globalConfig.cloud?.ddns?.records || []), newDdnsRecord];
|
||||
await updateConfig({
|
||||
...globalConfig,
|
||||
cloud: { ...globalConfig.cloud, ddns: { ...globalConfig.cloud?.ddns, records } },
|
||||
});
|
||||
setNewDdnsRecord('');
|
||||
setAddingDdnsRecord(false);
|
||||
}}>Add</Button>
|
||||
<Button size="sm" variant="ghost" className="h-8" onClick={() => { setAddingDdnsRecord(false); setNewDdnsRecord(''); }}>
|
||||
<X className="h-3 w-3" />
|
||||
</Button>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
No public domains registered. Set a domain to Public on the Domains page to enable DDNS.
|
||||
</p>
|
||||
)}
|
||||
|
||||
<Button
|
||||
@@ -345,7 +300,7 @@ export function DashboardComponent() {
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-sm text-muted-foreground ml-7">
|
||||
DDNS is not enabled. Configure it in the DDNS settings to keep DNS records in sync with your public IP.
|
||||
DDNS is not enabled. Enable it in the global config to keep public domain DNS records in sync with your public IP.
|
||||
</p>
|
||||
)}
|
||||
</Card>
|
||||
@@ -378,10 +333,11 @@ export function DashboardComponent() {
|
||||
<div className="space-y-3 ml-7">
|
||||
{/* Service badges */}
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<ServiceBadge name="dnsmasq" />
|
||||
<ServiceBadge name="haproxy" />
|
||||
<ServiceBadge name="wireguard" />
|
||||
<ServiceBadge name="crowdsec" />
|
||||
<ServiceBadge name="dnsmasq" active={centralStatus?.daemons?.dnsmasq?.active} />
|
||||
<ServiceBadge name="haproxy" active={centralStatus?.daemons?.haproxy?.active} />
|
||||
<ServiceBadge name="nftables" active={centralStatus?.daemons?.nftables?.active} />
|
||||
<ServiceBadge name="wireguard" active={centralStatus?.daemons?.wireguard?.active} />
|
||||
<ServiceBadge name="crowdsec" active={centralStatus?.daemons?.crowdsec?.active} />
|
||||
</div>
|
||||
|
||||
{/* Version and uptime */}
|
||||
@@ -452,13 +408,12 @@ export function DashboardComponent() {
|
||||
);
|
||||
}
|
||||
|
||||
/** Inline badge showing a service name with a colored dot. Status is informational only. */
|
||||
function ServiceBadge({ name }: { name: string }) {
|
||||
// These are presentational — the daemon is running if we can reach the API,
|
||||
// and individual service health comes from their own endpoints.
|
||||
/** Inline badge showing a service name with a colored dot reflecting actual daemon status. */
|
||||
function ServiceBadge({ name, active }: { name: string; active?: boolean }) {
|
||||
const dotColor = active === undefined ? 'bg-gray-400' : active ? 'bg-green-500' : 'bg-red-500';
|
||||
return (
|
||||
<span className="inline-flex items-center gap-1.5 rounded-md bg-muted px-2 py-1 text-xs font-medium">
|
||||
<span className="h-2 w-2 rounded-full bg-green-500" />
|
||||
<span className={`h-2 w-2 rounded-full ${dotColor}`} />
|
||||
{name}
|
||||
</span>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user