216 lines
4.7 KiB
TypeScript
216 lines
4.7 KiB
TypeScript
import { useEffect, useMemo, useState } from 'react';
|
|
import {
|
|
Clock,
|
|
Database,
|
|
Server,
|
|
ShieldCheck,
|
|
} from 'lucide-react';
|
|
|
|
import { TopBar } from '@/components/layout/TopBar';
|
|
import { MetricCard } from '@/components/dashboard/MetricCard';
|
|
import { IpPoolChart } from '@/components/dashboard/IpPoolChart';
|
|
import { NetworkTrafficChart } from '@/components/dashboard/NetworkTrafficChart';
|
|
|
|
import { ProvisioningWizard } from '@/components/provisioning/ProvisioningWizard';
|
|
import { BackendSettings } from '@/components/settings/BackendSettings';
|
|
import { ActivityLogs } from '@/components/activity/ActivityLogs';
|
|
import { Card } from '@/components/ui/Card';
|
|
|
|
import { vpnApi } from '@/services/vpnApi';
|
|
import { vpsApi } from '@/services/vpsApi';
|
|
|
|
import type {
|
|
UsedIpsResponse,
|
|
VpsHealth,
|
|
} from '@/types/api';
|
|
|
|
export function DashboardRoute() {
|
|
const [health, setHealth] =
|
|
useState<VpsHealth | null>(null);
|
|
|
|
const [usedIps, setUsedIps] =
|
|
useState<UsedIpsResponse | null>(null);
|
|
|
|
const [error, setError] = useState('');
|
|
|
|
useEffect(() => {
|
|
async function loadDashboard() {
|
|
try {
|
|
const [
|
|
healthResponse,
|
|
usedIpsResponse,
|
|
] = await Promise.all([
|
|
vpsApi.health(),
|
|
vpnApi.usedIps(),
|
|
]);
|
|
|
|
setHealth(healthResponse);
|
|
setUsedIps(usedIpsResponse);
|
|
setError('');
|
|
} catch (err) {
|
|
setHealth(null);
|
|
setUsedIps(null);
|
|
|
|
setError(String(err));
|
|
}
|
|
}
|
|
|
|
loadDashboard();
|
|
|
|
const intervalId = window.setInterval(
|
|
loadDashboard,
|
|
15000,
|
|
);
|
|
|
|
return () => {
|
|
window.clearInterval(intervalId);
|
|
};
|
|
}, []);
|
|
|
|
const ipPoolTotal =
|
|
health?.ipPoolTotal ?? 65534;
|
|
|
|
const usedCount =
|
|
health?.ipPoolUsed ??
|
|
usedIps?.count ??
|
|
0;
|
|
|
|
const ipPoolPercent = useMemo(() => {
|
|
return (
|
|
(usedCount / ipPoolTotal) *
|
|
100
|
|
).toFixed(2);
|
|
}, [usedCount, ipPoolTotal]);
|
|
|
|
const backendHealthy =
|
|
health?.backend === true;
|
|
|
|
const vpnHealthy =
|
|
health?.wireGuardRunning === true;
|
|
|
|
const dashboardReady =
|
|
!error &&
|
|
Boolean(health) &&
|
|
backendHealthy &&
|
|
vpnHealthy;
|
|
|
|
return (
|
|
<div className="flex h-full flex-col overflow-hidden">
|
|
<TopBar healthy={dashboardReady} />
|
|
|
|
{error && (
|
|
<div className="mb-3 rounded-xl border border-red-500/20 bg-red-500/10 p-3 text-sm text-red-300">
|
|
Dashboard backend error:{' '}
|
|
{error}
|
|
</div>
|
|
)}
|
|
|
|
<div className="grid grid-cols-4 gap-4">
|
|
<MetricCard
|
|
title="VPN Status"
|
|
value={
|
|
vpnHealthy
|
|
? 'Connected'
|
|
: 'Offline'
|
|
}
|
|
subtitle={
|
|
health?.wireGuardInterface
|
|
? `${health.wireGuardInterface} interface`
|
|
: 'WireGuard wg0 status'
|
|
}
|
|
icon={<ShieldCheck />}
|
|
/>
|
|
|
|
<MetricCard
|
|
title="IP Pool Usage"
|
|
value={`${ipPoolPercent}%`}
|
|
subtitle={`${usedCount} / ${ipPoolTotal} IPs used`}
|
|
icon={<Database />}
|
|
/>
|
|
|
|
<MetricCard
|
|
title="VPS Uptime"
|
|
value={
|
|
health?.systemUptime ??
|
|
'Unknown'
|
|
}
|
|
subtitle="Reported by VPS health"
|
|
icon={<Clock />}
|
|
/>
|
|
|
|
<MetricCard
|
|
title="Backend Health"
|
|
value={
|
|
backendHealthy
|
|
? 'Healthy'
|
|
: 'Offline'
|
|
}
|
|
subtitle="API connectivity"
|
|
icon={<Server />}
|
|
/>
|
|
</div>
|
|
|
|
<div className="mt-4 grid min-h-0 flex-1 grid-cols-12 gap-4 overflow-hidden pb-4">
|
|
<div className="col-span-9 min-h-0">
|
|
<NetworkTrafficChart />
|
|
</div>
|
|
|
|
<div className="col-span-3 min-h-0">
|
|
<IpPoolChart
|
|
used={usedCount}
|
|
total={ipPoolTotal}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function PlaceholderRoute({
|
|
name,
|
|
}: {
|
|
name: string;
|
|
}) {
|
|
return (
|
|
<Card>
|
|
<h2 className="text-2xl font-bold">
|
|
{name}
|
|
</h2>
|
|
|
|
<p className="mt-2 text-slate-400">
|
|
Screen scaffold ready for production
|
|
implementation.
|
|
</p>
|
|
</Card>
|
|
);
|
|
}
|
|
|
|
export function RouteView({
|
|
active,
|
|
}: {
|
|
active: string;
|
|
}) {
|
|
if (active === 'Dashboard') {
|
|
return <DashboardRoute />;
|
|
}
|
|
|
|
if (active === 'Provisioning') {
|
|
return <ProvisioningWizard />;
|
|
}
|
|
|
|
if (active === 'UDP2RAW Config') {
|
|
return (
|
|
<PlaceholderRoute name="UDP2RAW Config" />
|
|
);
|
|
}
|
|
|
|
if (active === 'Activity Logs') {
|
|
return <ActivityLogs />;
|
|
}
|
|
|
|
if (active === 'Workstation') {
|
|
return <BackendSettings />;
|
|
}
|
|
|
|
return <PlaceholderRoute name={active} />;
|
|
} |