131 lines
4.9 KiB
TypeScript
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>
|
|
);
|
|
}
|