293 lines
8.7 KiB
TypeScript
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>
|
|
);
|
|
} |