Initial project structure cleanup
This commit is contained in:
@@ -0,0 +1,293 @@
|
||||
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-zbt-we826-16m.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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user