Initial project structure cleanup
This commit is contained in:
@@ -0,0 +1,216 @@
|
||||
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 { ProvisioningWorkflow } from '@/components/dashboard/ProvisioningWorkflow';
|
||||
import { IpPoolChart } from '@/components/dashboard/IpPoolChart';
|
||||
import { NetworkTrafficChart } from '@/components/dashboard/NetworkTrafficChart';
|
||||
|
||||
import { VpnPeersTable } from '@/components/vpn/VpnPeersTable';
|
||||
import { IpManagementPanel } from '@/components/vpn/IpManagementPanel';
|
||||
|
||||
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 {
|
||||
setError('');
|
||||
|
||||
const [
|
||||
healthResponse,
|
||||
usedIpsResponse,
|
||||
] = await Promise.all([
|
||||
vpsApi.health(),
|
||||
vpnApi.usedIps(),
|
||||
]);
|
||||
|
||||
setHealth(healthResponse);
|
||||
setUsedIps(usedIpsResponse);
|
||||
} catch (err) {
|
||||
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 vpnHealthy =
|
||||
health?.wireGuardRunning ?? false;
|
||||
|
||||
const backendHealthy =
|
||||
health?.backend ?? !error;
|
||||
|
||||
return (
|
||||
<div className="flex h-full flex-col overflow-hidden">
|
||||
<TopBar />
|
||||
|
||||
{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 flex-1 grid-cols-12 grid-rows-[minmax(0,1fr)_auto] gap-4 overflow-hidden">
|
||||
<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 className="col-span-12">
|
||||
<ProvisioningWorkflow />
|
||||
</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} />;
|
||||
}
|
||||
Reference in New Issue
Block a user