519 lines
47 KiB
Diff
519 lines
47 KiB
Diff
diff --git a/src/app/(dashboard)/agents/page.tsx b/src/app/(dashboard)/agents/page.tsx
|
||
index 29b337e..7fc94d9 100644
|
||
--- a/src/app/(dashboard)/agents/page.tsx
|
||
+++ b/src/app/(dashboard)/agents/page.tsx
|
||
@@ -1,4 +1,4 @@
|
||
-"use client";
|
||
+"use client";
|
||
|
||
import { useState, useEffect } from "react";
|
||
import { useRouter } from "next/navigation";
|
||
@@ -66,7 +66,7 @@ export default function AgentsPage() {
|
||
router.refresh();
|
||
window.location.reload();
|
||
} catch (err: any) {
|
||
- alert('Gagal masuk mode View As: ' + err.message);
|
||
+ alert('Failed to enter View As mode: ' + err.message);
|
||
} finally {
|
||
setViewAsLoading(null);
|
||
}
|
||
@@ -210,7 +210,7 @@ export default function AgentsPage() {
|
||
>
|
||
{isViewAsLoading ? <Loader2 className="w-4 h-4 animate-spin" /> : <Eye className="w-4 h-4" />}
|
||
</button>
|
||
- <button onClick={(e) => { e.stopPropagation(); openDelete(row); }} className="p-1.5 text-red-400 hover:text-red-300 hover:bg-red-950/30 rounded transition-colors" title="Hapus">
|
||
+ <button onClick={(e) => { e.stopPropagation(); openDelete(row); }} className="p-1.5 text-red-400 hover:text-red-300 hover:bg-red-950/30 rounded transition-colors" title="Delete">
|
||
<Trash2 className="w-4 h-4" />
|
||
</button>
|
||
</>
|
||
@@ -224,11 +224,7 @@ export default function AgentsPage() {
|
||
return (
|
||
<div className="space-y-6">
|
||
<div className="flex items-center justify-between">
|
||
- <h2 className="text-2xl font-bold tracking-tight text-white flex items-center gap-3">
|
||
- <Server className="w-6 h-6 text-primary" />
|
||
- Agents Inventory
|
||
- {isLoading && <Loader2 className="w-5 h-5 animate-spin text-muted-foreground" />}
|
||
- </h2>
|
||
+
|
||
<button
|
||
onClick={openCreate}
|
||
className="flex items-center gap-2 bg-primary hover:bg-primary/90 text-primary-foreground px-4 py-2 rounded-md text-sm font-medium transition-colors"
|
||
@@ -253,7 +249,7 @@ export default function AgentsPage() {
|
||
API Key yang Anda gunakan saat ini (<code className="bg-red-950/40 text-red-300 px-1.5 py-0.5 rounded font-mono text-xs">sk_db_live_...</code>) adalah <strong>Site-scoped API Key</strong>. BackOne membatasi kunci ini agar hanya dapat membaca data statistik jaringan (Informatics), sehingga tidak memiliki izin untuk mengelola inventaris Agents global.
|
||
</p>
|
||
<div className="bg-slate-900/60 rounded-lg p-4 border border-border/40 text-xs space-y-2 leading-relaxed">
|
||
- <p className="font-semibold text-white">💡 Cara Mengatasinya:</p>
|
||
+ <p className="font-semibold text-white">💡 Cara Mengatasinya:</p>
|
||
<ol className="list-decimal pl-4 space-y-1 text-slate-400">
|
||
<li>Buka dan login ke portal <a href="https://portal.netify.ai" target="_blank" rel="noopener noreferrer" className="text-primary hover:underline font-medium">BackOne Portal</a>.</li>
|
||
<li>Di menu sebelah kiri, navigasikan ke <strong>Settings</strong> > <strong>API Keys</strong>.</li>
|
||
@@ -354,3 +350,4 @@ export default function AgentsPage() {
|
||
</div>
|
||
);
|
||
}
|
||
+
|
||
diff --git a/src/app/(dashboard)/apps/page.tsx b/src/app/(dashboard)/apps/page.tsx
|
||
index d8efe96..5fda0f0 100644
|
||
--- a/src/app/(dashboard)/apps/page.tsx
|
||
+++ b/src/app/(dashboard)/apps/page.tsx
|
||
@@ -1,4 +1,4 @@
|
||
-"use client";
|
||
+"use client";
|
||
|
||
import { useState, useEffect } from "react";
|
||
import { useTopApps, AppStat } from "@/lib/api";
|
||
@@ -11,7 +11,9 @@ import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip as RechartsTooltip,
|
||
|
||
export default function AppsPage() {
|
||
const [mounted, setMounted] = useState(false);
|
||
- const { data, isLoading } = useTopApps(50); // fetch 50 apps
|
||
+ const [page, setPage] = useState(0);
|
||
+ const pageSize = 20;
|
||
+ const { data, isLoading } = useTopApps(pageSize, page * pageSize);
|
||
const [selectedApp, setSelectedApp] = useState<AppStat | null>(null);
|
||
|
||
useEffect(() => {
|
||
@@ -23,6 +25,7 @@ export default function AppsPage() {
|
||
const top5 = data.slice(0, 5);
|
||
|
||
const columns: Column<AppStat>[] = [
|
||
+ { header: "Last Seen", accessor: (row) => row.timestamp ? new Date(row.timestamp).toLocaleString() : '-' },
|
||
{ header: "#", accessor: (row, i) => i + 1 },
|
||
{
|
||
header: "Application",
|
||
@@ -57,17 +60,7 @@ export default function AppsPage() {
|
||
|
||
return (
|
||
<div className="space-y-6">
|
||
- <div className="flex items-center justify-between">
|
||
- <div>
|
||
- <h2 className="text-2xl font-bold tracking-tight text-white flex items-center gap-3">
|
||
- Applications
|
||
- {isLoading && <Loader2 className="w-5 h-5 animate-spin text-muted-foreground" />}
|
||
- </h2>
|
||
- <p className="text-muted-foreground mt-1 text-sm">
|
||
- Click any application to see which agents and devices are using it.
|
||
- </p>
|
||
- </div>
|
||
- </div>
|
||
+
|
||
|
||
<Card className="bg-card/50 backdrop-blur-sm border-border/50">
|
||
<CardHeader>
|
||
@@ -134,3 +127,6 @@ export default function AppsPage() {
|
||
</div>
|
||
);
|
||
}
|
||
+
|
||
+
|
||
+
|
||
diff --git a/src/app/(dashboard)/devices/page.tsx b/src/app/(dashboard)/devices/page.tsx
|
||
index a6d2602..ab450fe 100644
|
||
--- a/src/app/(dashboard)/devices/page.tsx
|
||
+++ b/src/app/(dashboard)/devices/page.tsx
|
||
@@ -1,4 +1,4 @@
|
||
-"use client";
|
||
+"use client";
|
||
|
||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/Card";
|
||
import { DataTable, Column } from "@/components/ui/DataTable";
|
||
@@ -10,7 +10,9 @@ import { Loader2, ChevronRight } from "lucide-react";
|
||
|
||
export default function DevicesPage() {
|
||
const [mounted, setMounted] = useState(false);
|
||
- const devices = useDevices(1000);
|
||
+ const [page, setPage] = useState(0);
|
||
+ const pageSize = 50;
|
||
+ const devices = useDevices(pageSize, page * pageSize);
|
||
const [selectedDevice, setSelectedDevice] = useState<DeviceStat | null>(null);
|
||
|
||
useEffect(() => {
|
||
@@ -20,6 +22,7 @@ export default function DevicesPage() {
|
||
if (!mounted) return null;
|
||
|
||
const columns: Column<DeviceStat>[] = [
|
||
+ { header: "Last Seen", accessor: (row) => row.timestamp ? new Date(row.timestamp).toLocaleString() : '-' },
|
||
{ header: "#", accessor: (row, i) => i + 1 },
|
||
{
|
||
header: "IP Address",
|
||
@@ -51,7 +54,7 @@ export default function DevicesPage() {
|
||
{(isRouted || info) && (
|
||
<span className={`text-[10px] font-sans font-medium ${isRouted ? "text-amber-500/90 italic" : "text-slate-400/80"}`}>
|
||
{isRouted ? "Via Routed Gateway" : ""}
|
||
- {isRouted && info ? ` • ${info}` : (!isRouted ? info : "")}
|
||
+ {isRouted && info ? ` • ${info}` : (!isRouted ? info : "")}
|
||
</span>
|
||
)}
|
||
</div>
|
||
@@ -77,18 +80,7 @@ export default function DevicesPage() {
|
||
|
||
return (
|
||
<div className="space-y-8 animate-fade-in pb-10">
|
||
- <div className="flex items-center justify-between">
|
||
- <div>
|
||
- <h2 className="text-3xl font-bold tracking-tight text-white flex items-center gap-3">
|
||
- Devices
|
||
- {devices.isLoading && <Loader2 className="w-5 h-5 animate-spin text-primary" />}
|
||
- </h2>
|
||
- <p className="text-muted-foreground mt-1">
|
||
- Monitor and manage connected network devices.{" "}
|
||
- <span className="text-slate-500 text-xs">Click an IP to see app usage & flows.</span>
|
||
- </p>
|
||
- </div>
|
||
- </div>
|
||
+
|
||
|
||
<Card className="bg-card/80 backdrop-blur-xl border-border/50 animate-slide-up" style={{ animationDelay: "100ms" }}>
|
||
<CardHeader className="border-b border-border/50 pb-4 mb-4">
|
||
@@ -126,3 +118,4 @@ export default function DevicesPage() {
|
||
</div>
|
||
);
|
||
}
|
||
+
|
||
diff --git a/src/app/(dashboard)/dns/page.tsx b/src/app/(dashboard)/dns/page.tsx
|
||
index 05a7a48..0b8d8a3 100644
|
||
--- a/src/app/(dashboard)/dns/page.tsx
|
||
+++ b/src/app/(dashboard)/dns/page.tsx
|
||
@@ -32,12 +32,7 @@ export default function DNSPage() {
|
||
|
||
return (
|
||
<div className="space-y-6">
|
||
- <div className="flex items-center justify-between">
|
||
- <h2 className="text-2xl font-bold tracking-tight text-white flex items-center gap-3">
|
||
- DNS Intelligence
|
||
- {isLoading && <Loader2 className="w-5 h-5 animate-spin text-muted-foreground" />}
|
||
- </h2>
|
||
- </div>
|
||
+
|
||
|
||
<Card className="bg-card/50 backdrop-blur-sm border-border/50">
|
||
<CardHeader>
|
||
diff --git a/src/app/(dashboard)/events/page.tsx b/src/app/(dashboard)/events/page.tsx
|
||
index c521e04..416dae0 100644
|
||
--- a/src/app/(dashboard)/events/page.tsx
|
||
+++ b/src/app/(dashboard)/events/page.tsx
|
||
@@ -105,12 +105,7 @@ function EventsContent() {
|
||
|
||
return (
|
||
<div className="space-y-6">
|
||
- <div className="flex items-center justify-between">
|
||
- <h2 className="text-2xl font-bold tracking-tight text-white flex items-center gap-3">
|
||
- Network Events
|
||
- {isLoading && <Loader2 className="w-5 h-5 animate-spin text-muted-foreground" />}
|
||
- </h2>
|
||
- </div>
|
||
+
|
||
|
||
<Card className="bg-card/50 backdrop-blur-sm border-border/50">
|
||
<CardHeader>
|
||
diff --git a/src/app/(dashboard)/flows/page.tsx b/src/app/(dashboard)/flows/page.tsx
|
||
index 115d8ec..3e4064e 100644
|
||
--- a/src/app/(dashboard)/flows/page.tsx
|
||
+++ b/src/app/(dashboard)/flows/page.tsx
|
||
@@ -1,4 +1,4 @@
|
||
-"use client";
|
||
+"use client";
|
||
|
||
import { useState, useEffect } from "react";
|
||
import { useFlows } from "@/lib/api";
|
||
@@ -110,12 +110,7 @@ export default function FlowsPage() {
|
||
|
||
return (
|
||
<div className="space-y-6">
|
||
- <div className="flex items-center justify-between">
|
||
- <h2 className="text-2xl font-bold tracking-tight text-white flex items-center gap-3">
|
||
- Active Flows
|
||
- {isLoading && <Loader2 className="w-5 h-5 animate-spin text-muted-foreground" />}
|
||
- </h2>
|
||
- </div>
|
||
+
|
||
|
||
<Card className="bg-card/50 backdrop-blur-sm border-border/50">
|
||
<CardHeader>
|
||
@@ -142,3 +137,4 @@ export default function FlowsPage() {
|
||
</div>
|
||
);
|
||
}
|
||
+
|
||
diff --git a/src/app/(dashboard)/geography/page.tsx b/src/app/(dashboard)/geography/page.tsx
|
||
index 6ddd2e0..9913e33 100644
|
||
--- a/src/app/(dashboard)/geography/page.tsx
|
||
+++ b/src/app/(dashboard)/geography/page.tsx
|
||
@@ -65,15 +65,7 @@ export default function GeographyPage() {
|
||
|
||
return (
|
||
<div className="space-y-8 animate-fade-in pb-10">
|
||
- <div className="flex items-center justify-between">
|
||
- <div>
|
||
- <h2 className="text-3xl font-bold tracking-tight text-white flex items-center gap-3">
|
||
- Geographic Traffic
|
||
- {isLoading && <Loader2 className="w-5 h-5 animate-spin text-primary" />}
|
||
- </h2>
|
||
- <p className="text-muted-foreground mt-1">Visualize data flow and activity across the globe.</p>
|
||
- </div>
|
||
- </div>
|
||
+
|
||
|
||
{showMap && (
|
||
<Card className="bg-card/80 backdrop-blur-xl border-border/50 animate-slide-up" style={{ animationDelay: '100ms' }}>
|
||
diff --git a/src/app/(dashboard)/intelligence/page.tsx b/src/app/(dashboard)/intelligence/page.tsx
|
||
index 3418107..581845a 100644
|
||
--- a/src/app/(dashboard)/intelligence/page.tsx
|
||
+++ b/src/app/(dashboard)/intelligence/page.tsx
|
||
@@ -228,12 +228,7 @@ export default function IntelligencePage() {
|
||
|
||
return (
|
||
<div className="space-y-6">
|
||
- <div className="flex items-center justify-between">
|
||
- <h2 className="text-2xl font-bold tracking-tight text-white flex items-center gap-3">
|
||
- Deep Packet Inspection Intelligence
|
||
- {loadingStats && <Loader2 className="w-5 h-5 animate-spin text-muted-foreground" />}
|
||
- </h2>
|
||
- </div>
|
||
+
|
||
|
||
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
|
||
{intelItems.map((item, idx) => {
|
||
diff --git a/src/app/(dashboard)/layout.tsx b/src/app/(dashboard)/layout.tsx
|
||
index b6888fc..83239e6 100644
|
||
--- a/src/app/(dashboard)/layout.tsx
|
||
+++ b/src/app/(dashboard)/layout.tsx
|
||
@@ -1,9 +1,14 @@
|
||
import { DashboardLayout } from "@/components/layout/DashboardLayout";
|
||
+import { TimeFilterProvider } from "@/contexts/TimeFilterContext";
|
||
|
||
export default function AppLayout({
|
||
children,
|
||
}: Readonly<{
|
||
children: React.ReactNode;
|
||
}>) {
|
||
- return <DashboardLayout>{children}</DashboardLayout>;
|
||
+ return (
|
||
+ <TimeFilterProvider>
|
||
+ <DashboardLayout>{children}</DashboardLayout>
|
||
+ </TimeFilterProvider>
|
||
+ );
|
||
}
|
||
diff --git a/src/app/(dashboard)/network-intelligence/page.tsx b/src/app/(dashboard)/network-intelligence/page.tsx
|
||
index 93e8e40..15c33b3 100644
|
||
--- a/src/app/(dashboard)/network-intelligence/page.tsx
|
||
+++ b/src/app/(dashboard)/network-intelligence/page.tsx
|
||
@@ -36,19 +36,7 @@ export default function NetworkIntelligencePage() {
|
||
|
||
return (
|
||
<div className="space-y-8 animate-fade-in pb-10">
|
||
- <div className="flex items-center justify-between">
|
||
- <div>
|
||
- <h2 className="text-3xl font-bold tracking-tight text-white flex items-center gap-3">
|
||
- Network Intelligence
|
||
- {isLoading && <Loader2 className="w-5 h-5 animate-spin text-primary" />}
|
||
- </h2>
|
||
- <p className="text-muted-foreground mt-1">Deep packet inspection analytics and geography.</p>
|
||
- </div>
|
||
- <div className="flex items-center gap-2 text-sm font-medium text-blue-400 bg-blue-500/10 border border-blue-500/20 px-4 py-2 rounded-lg shadow-[0_0_15px_rgba(59,130,246,0.1)]">
|
||
- <Activity className="h-4 w-4" />
|
||
- <span>DPI Engine Active</span>
|
||
- </div>
|
||
- </div>
|
||
+
|
||
|
||
<div className="grid gap-6 md:grid-cols-2">
|
||
{/* Left Column: App Categories List */}
|
||
diff --git a/src/app/(dashboard)/page.tsx b/src/app/(dashboard)/page.tsx
|
||
index 98b2969..b16ca54 100644
|
||
--- a/src/app/(dashboard)/page.tsx
|
||
+++ b/src/app/(dashboard)/page.tsx
|
||
@@ -1,4 +1,4 @@
|
||
-"use client";
|
||
+"use client";
|
||
|
||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/Card";
|
||
import { Activity, Download, Upload, ShieldAlert, Zap, Server, Loader2 } from "lucide-react";
|
||
@@ -49,15 +49,7 @@ export default function SummaryPage() {
|
||
|
||
return (
|
||
<div className="space-y-8 animate-fade-in pb-10">
|
||
- <div className="flex items-center justify-between">
|
||
- <div>
|
||
- <h2 className="text-3xl font-bold tracking-tight text-white flex items-center gap-3">
|
||
- Summary Overview
|
||
- {isLoading && <Loader2 className="w-5 h-5 animate-spin text-primary" />}
|
||
- </h2>
|
||
- <p className="text-muted-foreground mt-1">Real-time network traffic and intelligence summary.</p>
|
||
- </div>
|
||
- </div>
|
||
+
|
||
|
||
{/* KPI Cards */}
|
||
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-6">
|
||
@@ -292,3 +284,4 @@ export default function SummaryPage() {
|
||
</div>
|
||
);
|
||
}
|
||
+
|
||
diff --git a/src/app/(dashboard)/security-audit/page.tsx b/src/app/(dashboard)/security-audit/page.tsx
|
||
index 9ee84e9..3a59875 100644
|
||
--- a/src/app/(dashboard)/security-audit/page.tsx
|
||
+++ b/src/app/(dashboard)/security-audit/page.tsx
|
||
@@ -1,4 +1,4 @@
|
||
-"use client";
|
||
+"use client";
|
||
|
||
import { useState, useEffect } from "react";
|
||
import {
|
||
@@ -26,9 +26,9 @@ const COLORS = {
|
||
};
|
||
|
||
const RISK_CONFIG = {
|
||
- Rawan: { color: "text-red-400", bg: "bg-red-500/20 ring-red-500/40", icon: <ShieldX className="w-3.5 h-3.5" />, dot: "bg-red-500" },
|
||
- Sedang: { color: "text-orange-400", bg: "bg-orange-500/20 ring-orange-500/40", icon: <ShieldAlert className="w-3.5 h-3.5" />, dot: "bg-orange-500" },
|
||
- Aman: { color: "text-green-400", bg: "bg-green-500/20 ring-green-500/40", icon: <ShieldCheck className="w-3.5 h-3.5" />, dot: "bg-green-500" },
|
||
+ Vulnerable: { color: "text-red-400", bg: "bg-red-500/20 ring-red-500/40", icon: <ShieldX className="w-3.5 h-3.5" />, dot: "bg-red-500" },
|
||
+ Moderate: { color: "text-orange-400", bg: "bg-orange-500/20 ring-orange-500/40", icon: <ShieldAlert className="w-3.5 h-3.5" />, dot: "bg-orange-500" },
|
||
+ Safe: { color: "text-green-400", bg: "bg-green-500/20 ring-green-500/40", icon: <ShieldCheck className="w-3.5 h-3.5" />, dot: "bg-green-500" },
|
||
} as const;
|
||
|
||
export default function SecurityAuditPage() {
|
||
@@ -54,9 +54,9 @@ export default function SecurityAuditPage() {
|
||
const isLoading = versions.isLoading || ciphers.isLoading || security.isLoading;
|
||
|
||
// Risk summary counts
|
||
- const rawan = secDevices.filter((d) => d.risk_level === "Rawan").length;
|
||
- const sedang = secDevices.filter((d) => d.risk_level === "Sedang").length;
|
||
- const aman = secDevices.filter((d) => d.risk_level === "Aman").length;
|
||
+ const vulnerableCount = secDevices.filter((d) => d.risk_level === "Vulnerable").length;
|
||
+ const moderateCount = secDevices.filter((d) => d.risk_level === "Moderate").length;
|
||
+ const safeCount = secDevices.filter((d) => d.risk_level === "Safe").length;
|
||
|
||
const versionColumns: Column<typeof versions.data[0]>[] = [
|
||
{ header: "#", accessor: (row, i) => i + 1 },
|
||
@@ -134,7 +134,7 @@ export default function SecurityAuditPage() {
|
||
{
|
||
header: "Risk Level",
|
||
accessor: (row) => {
|
||
- const cfg = RISK_CONFIG[row.risk_level] ?? RISK_CONFIG.Aman;
|
||
+ const cfg = RISK_CONFIG[row.risk_level] ?? RISK_CONFIG.Safe;
|
||
return (
|
||
<span className={`inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-semibold ring-1 ${cfg.bg} ${cfg.color}`}>
|
||
{cfg.icon}
|
||
@@ -152,15 +152,7 @@ export default function SecurityAuditPage() {
|
||
|
||
return (
|
||
<div className="space-y-6">
|
||
- <div className="flex items-center justify-between">
|
||
- <div>
|
||
- <h2 className="text-2xl font-bold tracking-tight text-white flex items-center gap-3">
|
||
- Security Audit
|
||
- {(isLoading || secDevicesLoading) && <Loader2 className="w-5 h-5 animate-spin text-muted-foreground" />}
|
||
- </h2>
|
||
- <p className="text-muted-foreground mt-1 text-sm">TLS encryption analysis and device-level risk assessment.</p>
|
||
- </div>
|
||
- </div>
|
||
+
|
||
|
||
{/* Security Overview Cards */}
|
||
<div className="grid gap-4 md:grid-cols-3">
|
||
@@ -186,32 +178,32 @@ export default function SecurityAuditPage() {
|
||
<div className="grid gap-4 md:grid-cols-3">
|
||
<Card className="bg-red-950/20 border-red-500/20 backdrop-blur-sm">
|
||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||
- <CardTitle className="text-sm font-medium text-red-300">Rawan (Vulnerable)</CardTitle>
|
||
+ <CardTitle className="text-sm font-medium text-red-300">Vulnerable</CardTitle>
|
||
<ShieldX className="h-4 w-4 text-red-400" />
|
||
</CardHeader>
|
||
<CardContent>
|
||
- <div className="text-3xl font-bold text-red-400">{rawan}</div>
|
||
- <p className="text-xs text-red-300/60 mt-1">device{rawan !== 1 ? "s" : ""} at risk</p>
|
||
+ <div className="text-3xl font-bold text-red-400">{vulnerableCount}</div>
|
||
+ <p className="text-xs text-red-300/60 mt-1">device{vulnerableCount !== 1 ? "s" : ""} at risk</p>
|
||
</CardContent>
|
||
</Card>
|
||
<Card className="bg-orange-950/20 border-orange-500/20 backdrop-blur-sm">
|
||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||
- <CardTitle className="text-sm font-medium text-orange-300">Sedang (Moderate)</CardTitle>
|
||
+ <CardTitle className="text-sm font-medium text-orange-300">Moderate</CardTitle>
|
||
<ShieldAlert className="h-4 w-4 text-orange-400" />
|
||
</CardHeader>
|
||
<CardContent>
|
||
- <div className="text-3xl font-bold text-orange-400">{sedang}</div>
|
||
- <p className="text-xs text-orange-300/60 mt-1">device{sedang !== 1 ? "s" : ""} moderate risk</p>
|
||
+ <div className="text-3xl font-bold text-orange-400">{moderateCount}</div>
|
||
+ <p className="text-xs text-orange-300/60 mt-1">device{moderateCount !== 1 ? "s" : ""} moderate risk</p>
|
||
</CardContent>
|
||
</Card>
|
||
<Card className="bg-green-950/20 border-green-500/20 backdrop-blur-sm">
|
||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||
- <CardTitle className="text-sm font-medium text-green-300">Aman (Secure)</CardTitle>
|
||
+ <CardTitle className="text-sm font-medium text-green-300">Safe (Secure)</CardTitle>
|
||
<ShieldCheck className="h-4 w-4 text-green-400" />
|
||
</CardHeader>
|
||
<CardContent>
|
||
- <div className="text-3xl font-bold text-green-400">{aman}</div>
|
||
- <p className="text-xs text-green-300/60 mt-1">device{aman !== 1 ? "s" : ""} secure</p>
|
||
+ <div className="text-3xl font-bold text-green-400">{safeCount}</div>
|
||
+ <p className="text-xs text-green-300/60 mt-1">device{safeCount !== 1 ? "s" : ""} secure</p>
|
||
</CardContent>
|
||
</Card>
|
||
</div>
|
||
@@ -377,3 +369,4 @@ export default function SecurityAuditPage() {
|
||
</div>
|
||
);
|
||
}
|
||
+
|
||
diff --git a/src/app/(dashboard)/threats/page.tsx b/src/app/(dashboard)/threats/page.tsx
|
||
index 522a61a..4410624 100644
|
||
--- a/src/app/(dashboard)/threats/page.tsx
|
||
+++ b/src/app/(dashboard)/threats/page.tsx
|
||
@@ -1,4 +1,4 @@
|
||
-"use client";
|
||
+"use client";
|
||
|
||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/Card";
|
||
import { DataTable, Column } from "@/components/ui/DataTable";
|
||
@@ -13,7 +13,9 @@ import { Suspense } from "react";
|
||
|
||
function ThreatsContent() {
|
||
const [mounted, setMounted] = useState(false);
|
||
- const threats = useThreats(100);
|
||
+ const [page, setPage] = useState(0);
|
||
+ const pageSize = 50;
|
||
+ const threats = useThreats(pageSize, page * pageSize);
|
||
const searchParams = useSearchParams();
|
||
const highlightId = searchParams.get("highlight");
|
||
|
||
@@ -33,6 +35,7 @@ function ThreatsContent() {
|
||
};
|
||
|
||
const columns: Column<ThreatStat>[] = [
|
||
+ { header: "Timestamp", accessor: (row) => row.timestamp ? new Date(row.timestamp).toLocaleString() : '-' },
|
||
{ header: "#", accessor: (row, i) => i + 1 },
|
||
{ header: "Type", accessor: (row) => row.threat_type || 'Unknown' },
|
||
{ header: "Severity", accessor: (row) => getSeverityBadge(row.severity) },
|
||
@@ -54,19 +57,7 @@ function ThreatsContent() {
|
||
|
||
return (
|
||
<div className="space-y-8 animate-fade-in pb-10">
|
||
- <div className="flex items-center justify-between">
|
||
- <div>
|
||
- <h2 className="text-3xl font-bold tracking-tight text-white flex items-center gap-3">
|
||
- Threat Intelligence
|
||
- {threats.isLoading && <Loader2 className="w-5 h-5 animate-spin text-primary" />}
|
||
- </h2>
|
||
- <p className="text-muted-foreground mt-1">Monitor and respond to network security threats.</p>
|
||
- </div>
|
||
- <div className="flex items-center gap-2 text-sm font-medium text-emerald-400 bg-emerald-500/10 border border-emerald-500/20 px-4 py-2 rounded-lg shadow-[0_0_15px_rgba(16,185,129,0.1)]">
|
||
- <ShieldCheck className="h-4 w-4" />
|
||
- <span>Active Monitoring</span>
|
||
- </div>
|
||
- </div>
|
||
+
|
||
|
||
<Card className="bg-card/80 backdrop-blur-xl border-border/50 animate-slide-up" style={{ animationDelay: '100ms' }}>
|
||
<CardHeader className="border-b border-border/50 pb-4 mb-4">
|
||
@@ -103,3 +94,4 @@ export default function ThreatsPage() {
|
||
</Suspense>
|
||
);
|
||
}
|
||
+
|