Updates
This commit is contained in:
@@ -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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user