This commit is contained in:
2026-09-13 06:53:33 +01:00
parent 75670e4d5c
commit e7e63bc491
45 changed files with 2117 additions and 22 deletions
@@ -0,0 +1,20 @@
import AdminCard from "@/src/components/general/admin-card";
import ActivityRow from "../(partials)/activity-row";
import { ACTIVITY } from "../(data)/dashboard-mock-data";
export default function ActivitySection() {
return (
<AdminCard tier="secondary" className="w-full">
<div className="px-5 pt-4 pb-1">
<h2 className="text-[13.5px] font-medium text-foreground-light/60 dark:text-foreground-dark/60">
Recent activity
</h2>
</div>
<ul className="divide-y divide-slate-200/60 dark:divide-white/5">
{ACTIVITY.map((event) => (
<ActivityRow key={event.id} event={event} />
))}
</ul>
</AdminCard>
);
}
@@ -0,0 +1,23 @@
import StatCard from "../(partials)/stat-card";
import Sparkline from "../(partials)/sparkline";
import { HERO_KPI } from "../(data)/dashboard-mock-data";
export default function KpiHeroSection() {
return (
<StatCard
tier="default"
label={HERO_KPI.label}
value={HERO_KPI.value}
delta={HERO_KPI.delta}
deltaTone={HERO_KPI.deltaTone}
subtext={HERO_KPI.subtext}
valueClassName="text-4xl"
trailing={
<Sparkline
data={HERO_KPI.sparkline}
className="text-secondary dark:text-secondary"
/>
}
/>
);
}
@@ -0,0 +1,18 @@
import StatCard from "../(partials)/stat-card";
import { SECONDARY_KPIS } from "../(data)/dashboard-mock-data";
export default function KpiSecondarySection() {
return (
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3 w-full">
{SECONDARY_KPIS.map((kpi) => (
<StatCard
key={kpi.id}
label={kpi.label}
value={kpi.value}
delta={kpi.delta}
deltaTone={kpi.deltaTone}
/>
))}
</div>
);
}
@@ -0,0 +1,52 @@
import { ArrowUpRight } from "lucide-react";
import Link from "@/src/components/twui/layout/Link";
import AdminCard from "@/src/components/general/admin-card";
import PeerRow from "../(partials)/peer-row";
import AsidePanel from "../(partials)/aside-panel";
import { PEERS } from "../(data)/dashboard-mock-data";
const thClass =
"px-4 py-2 text-left text-[11px] font-semibold uppercase tracking-[0.08em] " +
"text-foreground-light/40 dark:text-foreground-dark/40 whitespace-nowrap";
const thRightClass = `${thClass} text-right`;
export default function PeersTableSection() {
return (
<div className="grid grid-cols-1 lg:grid-cols-[1fr_260px] gap-4 w-full items-start">
<AdminCard className="overflow-hidden">
<div className="flex items-center justify-between px-5 h-11">
<h2 className="text-[13.5px] font-medium text-foreground-light/60 dark:text-foreground-dark/60">
Recent peers
</h2>
<Link
href="/admin/clients"
className="inline-flex items-center gap-0.5 no-underline! border-b-0! text-[12.5px] text-foreground-light/50 dark:text-foreground-dark/50 hover:text-foreground-light dark:hover:text-foreground-dark transition-colors"
>
View all
<ArrowUpRight size={12} className="-mt-0.5" />
</Link>
</div>
<div className="overflow-x-auto">
<table className="w-full min-w-[680px]">
<thead>
<tr className="border-y border-slate-200 dark:border-white/10 bg-foreground-light/[0.02] dark:bg-foreground-dark/[0.03]">
<th className={thClass}>Peer</th>
<th className={thClass}>Tunnel IP</th>
<th className={thClass}>Endpoint</th>
<th className={thRightClass}>Traffic</th>
<th className={thRightClass}>Last handshake</th>
</tr>
</thead>
<tbody>
{PEERS.map((peer) => (
<PeerRow key={peer.id} peer={peer} />
))}
</tbody>
</table>
</div>
</AdminCard>
<AsidePanel />
</div>
);
}
@@ -0,0 +1,78 @@
import { useMemo } from "react";
import AdminCard from "@/src/components/general/admin-card";
import TrafficChart from "../(partials)/traffic-chart";
import { TRAFFIC_SERIES } from "../(data)/dashboard-mock-data";
function formatGb(value: number) {
return `${value.toFixed(1)} GB`;
}
export default function TrafficChartSection() {
const footer = useMemo(() => {
const maxDown = Math.max(...TRAFFIC_SERIES.map((p) => p.down));
const maxUp = Math.max(...TRAFFIC_SERIES.map((p) => p.up));
const avgDown =
TRAFFIC_SERIES.reduce((sum, p) => sum + p.down, 0) /
TRAFFIC_SERIES.length;
const total =
TRAFFIC_SERIES.reduce((sum, p) => sum + p.down + p.up, 0);
return {
maxDown: formatGb(maxDown),
maxUp: formatGb(maxUp),
avgDown: formatGb(avgDown),
total: formatGb(total),
};
}, []);
const stats = [
{ label: "Peak download", value: footer.maxDown },
{ label: "Peak upload", value: footer.maxUp },
{ label: "Avg download", value: footer.avgDown },
{ label: "Total · 24h", value: footer.total },
];
return (
<AdminCard className="w-full">
<div className="flex items-center justify-between px-5 pt-4 flex-wrap gap-2">
<div>
<h2 className="text-[13.5px] font-medium text-foreground-light/60 dark:text-foreground-dark/60">
Network traffic
</h2>
<p className="text-[12px] text-foreground-light/40 dark:text-foreground-dark/40">
Last 24 hours
</p>
</div>
<div className="flex items-center gap-4">
<span className="inline-flex items-center gap-1.5 text-[12px] text-foreground-light/60 dark:text-foreground-dark/60">
<span className="w-[7px] h-[7px] rounded-full bg-secondary" />
Download
</span>
<span className="inline-flex items-center gap-1.5 text-[12px] text-foreground-light/60 dark:text-foreground-dark/60">
<span className="w-[7px] h-[7px] rounded-full bg-slate-400" />
Upload
</span>
</div>
</div>
<div className="px-5 pt-3 pb-1">
<TrafficChart data={TRAFFIC_SERIES} />
</div>
<div className="mx-5 mt-3 mb-4 pt-3 border-t border-slate-200 dark:border-white/10 flex items-center gap-5">
{stats.map((stat, i) => (
<div key={stat.label} className="flex items-center gap-5">
{i > 0 ? (
<span className="w-px h-4 bg-slate-200 dark:bg-white/10" />
) : null}
<div className="flex items-baseline gap-1.5">
<span className="text-[11.5px] text-foreground-light/45 dark:text-foreground-dark/45">
{stat.label}
</span>
<span className="tabular text-[13px] font-medium text-foreground-light/80 dark:text-foreground-dark/80">
{stat.value}
</span>
</div>
</div>
))}
</div>
</AdminCard>
);
}