Files
Deep-Package-Inspection/src/app/(dashboard)/page.tsx
T

131 lines
4.9 KiB
TypeScript

"use client";
import { useState, useEffect, useMemo } from "react";
import { Loader2 } from "lucide-react";
import { useDashboardSummary, useTimeline, useTopApps, useTopProtocols, useCountries } from "@/lib/api-with-context";
import { GlobeMap } from "@/components/ui/GlobeMap";
import { getOriginLocation, getDestinationLocation } from "@/lib/geoUtils";
import { KPICards } from "@/components/dashboard/KPICards";
import { TopWidgets } from "@/components/dashboard/TopWidgets";
import { HelpTrigger } from "@/components/help/HelpTrigger";
export default function SummaryPage() {
const [mounted, setMounted] = useState(false);
const summary = useDashboardSummary();
const timeline = useTimeline(24);
const rawTopApps = useTopApps(7);
const rawTopProtocols = useTopProtocols();
const countries = useCountries(0); // Load all countries for complete globemap (Requirement 16)
const { topProtocols, topApps } = useMemo(() => {
const rawProtos = rawTopProtocols.data || [];
let appsData = rawTopApps.data || [];
// Process Apps
let finalApps = appsData
.filter(a => a.app_label !== 'Unknown')
.map(a => ({
...a,
app_label: (a.app_label || '').replace(/siab\./g, 'backone.'),
total_bytes: (a.download || 0) + (a.upload || 0)
}))
.sort((a, b) => b.total_bytes - a.total_bytes)
.slice(0, 7);
// Process Protocols
let finalProtos = rawProtos
.map(p => ({
...p,
protocol_label: (p.protocol_label || '').replace(/siab\./g, 'backone.'),
total: (p.download || 0) + (p.upload || 0)
}))
.sort((a, b) => b.total - a.total)
.slice(0, 7);
return {
topProtocols: { ...rawTopProtocols, data: finalProtos },
topApps: { ...rawTopApps, data: finalApps.length > 0 ? finalApps : appsData }
};
}, [rawTopProtocols.data, rawTopApps.data, rawTopProtocols.isLoading, rawTopApps.isLoading]);
const mapData = useMemo(() => {
if (!countries.data) return [];
// Jika summary menunjukkan tidak ada traffic sama sekali, jangan tampilkan globe
const totalTraffic = (summary.data?.bandwidth_down || 0) + (summary.data?.bandwidth_up || 0);
if (totalTraffic === 0) return [];
const origin = getOriginLocation();
return countries.data.reduce((acc: any[], row) => {
// Hanya tampilkan negara yang punya traffic aktual > 0
const traffic = (row.download || 0) + (row.upload || 0);
if (traffic === 0) return acc;
const dest = getDestinationLocation(row.country_code, row.country_name);
if (dest) {
acc.push({
startLat: origin.lat,
startLng: origin.lng,
endLat: dest.lat,
endLng: dest.lng,
fromLabel: origin.label,
toLabel: dest.label,
value: traffic,
download: row.download,
upload: row.upload,
countryCode: row.country_code
});
}
return acc;
}, []);
}, [countries.data, summary.data]);
useEffect(() => {
setMounted(true);
document.title = "Overview Dashboard | BackOne - Deep Package Inspection";
}, []);
if (!mounted) return null;
const isLoading = summary.isLoading || timeline.isLoading || topApps.isLoading || topProtocols.isLoading;
return (
<div className="space-y-8 animate-fade-in pb-10">
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between sm:gap-4">
<div className="min-w-0 flex-1">
<div className="flex items-center justify-between gap-3 sm:justify-start sm:gap-4 w-full">
<h2 className="text-xl sm:text-2xl md:text-3xl font-bold tracking-tight text-white flex items-center gap-2 sm:gap-3 truncate">
<span>Overview Dashboard</span>
{isLoading && <Loader2 className="w-4 h-4 sm:w-5 sm:h-5 animate-spin text-primary flex-shrink-0" />}
</h2>
{/* Mobile Actions: Inline next to Title */}
<div className="flex items-center gap-1.5 flex-shrink-0 sm:hidden">
<HelpTrigger pageId="overview" />
</div>
</div>
<p className="text-xs sm:text-sm text-muted-foreground mt-1 leading-normal sm:leading-relaxed">Real-time network traffic and intelligence summary.</p>
</div>
{/* Laptop/Tablet Actions: Floats on the right */}
<div className="hidden sm:flex items-center gap-2 sm:gap-3 flex-shrink-0">
<HelpTrigger pageId="overview" />
</div>
</div>
{/* KPI Cards */}
<KPICards summary={summary.data} />
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-7 animate-slide-up" style={{ animationDelay: '300ms' }}>
{/* Global Connections Map */}
<div className="col-span-1 lg:col-span-4 relative flex items-center justify-center w-full min-w-0">
<GlobeMap data={mapData} />
</div>
{/* Top Widgets (Apps & Protocols) */}
<TopWidgets topApps={topApps} topProtocols={topProtocols} />
</div>
</div>
);
}