Files
lr-openwrt-tool/src/components/settings/BackendSettings.tsx
T
2026-05-11 15:59:28 +01:00

293 lines
8.7 KiB
TypeScript

import { useMemo, useState } from 'react';
import {
CheckCircle2,
KeyRound,
Link2,
MonitorCog,
Save,
Server,
ShieldCheck,
} from 'lucide-react';
import { Badge } from '@/components/ui/Badge';
import { Button } from '@/components/ui/Button';
import { Card } from '@/components/ui/Card';
import {
getSettings,
saveSettings,
} from '@/services/apiClient';
const DEFAULT_OVERLAY_ROUTE = '198.19.0.0/16';
const DEFAULT_ROUTER_IP = '198.51.100.1';
const DEFAULT_CONTROLLER_IP = '198.51.100.10';
const DEFAULT_PLC_IP = '198.51.100.50';
const DEFAULT_FIRMWARE =
'openwrt-23.05.5-zbt-we826-16m-litoral-golden-sysupgrade.bin';
export function BackendSettings() {
const [settings, setSettings] =
useState(getSettings());
const [saved, setSaved] = useState(false);
const backendHost = useMemo(() => {
try {
return new URL(settings.backendUrl).host;
} catch {
return 'Invalid backend URL';
}
}, [settings.backendUrl]);
function handleSave() {
saveSettings(settings);
setSaved(true);
window.setTimeout(() => {
setSaved(false);
}, 2500);
}
return (
<div className="flex h-full flex-col overflow-hidden">
<div className="mb-6 flex items-start justify-between">
<div>
<h1 className="text-3xl font-bold tracking-tight text-white">
Workstation
</h1>
<p className="mt-1 text-slate-400">
Local technician console configuration
for router provisioning.
</p>
</div>
<Badge tone={saved ? 'green' : 'blue'}>
{saved ? 'Saved' : 'Local Config'}
</Badge>
</div>
<div className="grid min-h-0 flex-1 grid-cols-12 gap-5 overflow-hidden">
<Card className="col-span-7 flex flex-col">
<div className="mb-6 flex items-center gap-4">
<div className="rounded-2xl bg-blue-500/10 p-3 text-blue-300">
<Server size={24} />
</div>
<div>
<h2 className="text-xl font-semibold text-white">
Backend Connectivity
</h2>
<p className="text-sm text-slate-400">
API endpoint used for VPN IP
assignment and peer registration.
</p>
</div>
</div>
<div className="space-y-5">
<div>
<label className="mb-2 block text-xs font-semibold uppercase tracking-wide text-slate-500">
Backend URL
</label>
<div className="relative">
<Link2
size={16}
className="absolute left-4 top-1/2 -translate-y-1/2 text-blue-300/70"
/>
<input
value={settings.backendUrl}
onChange={(event) =>
setSettings({
...settings,
backendUrl: event.target.value,
})
}
className="w-full rounded-2xl border border-white/10 bg-slate-950/80 py-4 pl-11 pr-4 font-mono text-sm text-white outline-none transition placeholder:text-slate-600 focus:border-blue-500/50 focus:bg-slate-950"
placeholder="http://localhost:8080"
/>
</div>
</div>
<div>
<label className="mb-2 block text-xs font-semibold uppercase tracking-wide text-slate-500">
API Key
</label>
<div className="relative">
<KeyRound
size={16}
className="absolute left-4 top-1/2 -translate-y-1/2 text-blue-300/70"
/>
<input
value={settings.apiKey}
onChange={(event) =>
setSettings({
...settings,
apiKey: event.target.value,
})
}
className="w-full rounded-2xl border border-white/10 bg-slate-950/80 py-4 pl-11 pr-4 font-mono text-sm text-white outline-none transition placeholder:text-slate-600 focus:border-blue-500/50 focus:bg-slate-950"
placeholder="dev-api-key"
/>
</div>
</div>
</div>
<div className="mt-6 rounded-2xl border border-white/10 bg-white/[0.025] p-5">
<div className="flex items-start justify-between gap-4">
<div>
<p className="text-sm font-semibold text-white">
Connected Target
</p>
<p className="mt-1 font-mono text-sm text-slate-400">
{backendHost}
</p>
</div>
<Badge tone="green">
Ready
</Badge>
</div>
</div>
<div className="mt-auto flex justify-end pt-6">
<button
type="button"
onClick={handleSave}
className="group inline-flex items-center gap-2 rounded-2xl border border-white/10 bg-white/[0.02] px-5 py-3 text-sm font-semibold text-white transition-all duration-200 hover:-translate-y-[1px] hover:border-blue-500/30 hover:bg-blue-500/10 active:translate-y-0"
>
<Save
size={16}
className="transition-transform duration-200 group-hover:rotate-6"
/>
<span className="transition-colors group-hover:text-blue-200">
Save Workstation Config
</span>
</button>
</div>
</Card>
<div className="col-span-5 flex min-h-0 flex-col gap-5">
<Card>
<div className="mb-5 flex items-center gap-4">
<div className="rounded-2xl bg-purple-500/10 p-3 text-purple-300">
<MonitorCog size={24} />
</div>
<div>
<h2 className="text-xl font-semibold text-white">
Provisioning Baseline
</h2>
<p className="text-sm text-slate-400">
Read-only production values.
</p>
</div>
</div>
<div className="grid gap-3">
<InfoRow
label="Overlay Route"
value={DEFAULT_OVERLAY_ROUTE}
/>
<InfoRow
label="Router LAN IP"
value={DEFAULT_ROUTER_IP}
/>
<InfoRow
label="Controller IP"
value={DEFAULT_CONTROLLER_IP}
/>
<InfoRow
label="PLC IP"
value={DEFAULT_PLC_IP}
/>
<InfoRow
label="Firmware Target"
value={DEFAULT_FIRMWARE}
/>
</div>
</Card>
<Card className="flex-1">
<div className="flex items-start gap-4">
<div className="rounded-2xl bg-green-500/10 p-3 text-green-300">
<ShieldCheck size={24} />
</div>
<div>
<h2 className="text-xl font-semibold text-white">
Production Profile
</h2>
<p className="mt-3 text-sm leading-6 text-slate-400">
OpenWrt 23.05 baseline,
ZBT-WE826 16M target,
fw4/nftables firewall,
LuCI over WireGuard, stable
LAN topology and automated VPS
peer registration.
</p>
<div className="mt-5 flex flex-wrap gap-2">
<Badge tone="blue">
OpenWrt 23.05
</Badge>
<Badge tone="purple">
WireGuard
</Badge>
<Badge tone="green">
fw4/nftables
</Badge>
</div>
</div>
</div>
</Card>
{saved && (
<div className="rounded-2xl border border-green-500/20 bg-green-500/10 p-4 text-sm text-green-300">
<div className="flex items-center gap-2">
<CheckCircle2 size={16} />
Workstation configuration saved.
</div>
</div>
)}
</div>
</div>
</div>
);
}
function InfoRow({
label,
value,
}: {
label: string;
value: string;
}) {
return (
<div className="rounded-2xl border border-white/10 bg-slate-950/70 p-4">
<p className="text-xs font-semibold uppercase tracking-wide text-slate-500">
{label}
</p>
<p className="mt-2 break-all font-mono text-sm text-slate-100">
{value}
</p>
</div>
);
}