Spaces:
Sleeping
Sleeping
Commit ·
01b6aad
1
Parent(s): ba888f3
mobile way
Browse files- frontend/.gitignore +1 -1
- frontend/src/App.jsx +28 -16
- frontend/src/api.js +99 -120
- frontend/src/components/FeatureGuard.jsx +119 -55
- frontend/src/components/Navbar.jsx +86 -42
- frontend/src/components/Sidebar.jsx +87 -231
- frontend/src/pages/Dashboard.jsx +115 -410
- frontend/src/pages/SystemPage.jsx +206 -240
- frontend/src/pages/ThreatIntel.jsx +135 -301
frontend/.gitignore
CHANGED
|
@@ -8,7 +8,7 @@ pnpm-debug.log*
|
|
| 8 |
lerna-debug.log*
|
| 9 |
|
| 10 |
node_modules
|
| 11 |
-
|
| 12 |
dist-ssr
|
| 13 |
*.local
|
| 14 |
|
|
|
|
| 8 |
lerna-debug.log*
|
| 9 |
|
| 10 |
node_modules
|
| 11 |
+
|
| 12 |
dist-ssr
|
| 13 |
*.local
|
| 14 |
|
frontend/src/App.jsx
CHANGED
|
@@ -1,13 +1,12 @@
|
|
| 1 |
-
import { BrowserRouter as Router, Routes, Route, useLocation
|
| 2 |
import { LiveDataProvider } from "./context/DataContext";
|
| 3 |
import { AuthProvider } from "./context/AuthContext";
|
| 4 |
import ProtectedRoute from "./components/ProtectedRoute";
|
| 5 |
import Sidebar from "./components/Sidebar";
|
| 6 |
import { useState } from "react";
|
|
|
|
| 7 |
|
| 8 |
-
// --- IMPORT THE GUARD ---
|
| 9 |
import FeatureGuard from "./components/FeatureGuard";
|
| 10 |
-
|
| 11 |
import AuthPage from "./pages/Login";
|
| 12 |
import Dashboard from "./pages/Dashboard";
|
| 13 |
import LiveTraffic from "./components/dashboard/LiveDashboard";
|
|
@@ -29,41 +28,54 @@ import MainLayout from "./components/MainLayout";
|
|
| 29 |
function AppLayout() {
|
| 30 |
const location = useLocation();
|
| 31 |
const hideSidebar = location.pathname === "/login";
|
|
|
|
| 32 |
const [collapsed, setCollapsed] = useState(false);
|
|
|
|
| 33 |
|
| 34 |
-
// 🛠️ DEFINE LOCAL-ONLY ROUTES HERE
|
| 35 |
const localOnlyRoutes = ["/livetraffic", "/flow", "/system", "/traffic"];
|
| 36 |
const isProtectedFeature = localOnlyRoutes.includes(location.pathname);
|
| 37 |
|
| 38 |
return (
|
| 39 |
-
<div className="flex min-h-screen text-slate-100 relative bg-transparent">
|
| 40 |
<ConstellationBackground />
|
| 41 |
|
| 42 |
{!hideSidebar && (
|
| 43 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 44 |
)}
|
| 45 |
|
| 46 |
<main
|
| 47 |
-
className={`flex-1 overflow-y-auto
|
| 48 |
-
|
|
|
|
| 49 |
`}
|
| 50 |
>
|
| 51 |
-
{/*
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
*/}
|
| 55 |
<FeatureGuard requireLocal={isProtectedFeature}>
|
| 56 |
<Routes>
|
| 57 |
<Route path="/login" element={<AuthPage />} />
|
| 58 |
<Route path="/" element={<ProtectedRoute><MainLayout><Dashboard /></MainLayout></ProtectedRoute>} />
|
| 59 |
-
|
| 60 |
-
{/* These routes will now trigger the popup if deployed */}
|
| 61 |
<Route path="/livetraffic" element={<ProtectedRoute><MainLayout><LiveTraffic /></MainLayout></ProtectedRoute>} />
|
| 62 |
<Route path="/flow" element={<ProtectedRoute><MainLayout><FlowPage /></MainLayout></ProtectedRoute>} />
|
| 63 |
<Route path="/system" element={<ProtectedRoute><MainLayout><SystemPage /></MainLayout></ProtectedRoute>} />
|
| 64 |
<Route path="/traffic" element={<ProtectedRoute><MainLayout><TrafficPage /></MainLayout></ProtectedRoute>} />
|
| 65 |
-
|
| 66 |
-
{/* These routes work fine in the cloud (Database/AI based) */}
|
| 67 |
<Route path="/settings" element={<ProtectedRoute><MainLayout><SettingsPage /></MainLayout></ProtectedRoute>} />
|
| 68 |
<Route path="/alerts" element={<ProtectedRoute><MainLayout><InfoPage /></MainLayout></ProtectedRoute>} />
|
| 69 |
<Route path="/samplepred" element={<ProtectedRoute><MainLayout><SamplePred /></MainLayout></ProtectedRoute>} />
|
|
|
|
| 1 |
+
import { BrowserRouter as Router, Routes, Route, useLocation } from "react-router-dom";
|
| 2 |
import { LiveDataProvider } from "./context/DataContext";
|
| 3 |
import { AuthProvider } from "./context/AuthContext";
|
| 4 |
import ProtectedRoute from "./components/ProtectedRoute";
|
| 5 |
import Sidebar from "./components/Sidebar";
|
| 6 |
import { useState } from "react";
|
| 7 |
+
import { Menu } from "lucide-react"; // Import Menu icon for mobile
|
| 8 |
|
|
|
|
| 9 |
import FeatureGuard from "./components/FeatureGuard";
|
|
|
|
| 10 |
import AuthPage from "./pages/Login";
|
| 11 |
import Dashboard from "./pages/Dashboard";
|
| 12 |
import LiveTraffic from "./components/dashboard/LiveDashboard";
|
|
|
|
| 28 |
function AppLayout() {
|
| 29 |
const location = useLocation();
|
| 30 |
const hideSidebar = location.pathname === "/login";
|
| 31 |
+
|
| 32 |
const [collapsed, setCollapsed] = useState(false);
|
| 33 |
+
const [isMobileOpen, setIsMobileOpen] = useState(false); // New state for mobile toggle
|
| 34 |
|
|
|
|
| 35 |
const localOnlyRoutes = ["/livetraffic", "/flow", "/system", "/traffic"];
|
| 36 |
const isProtectedFeature = localOnlyRoutes.includes(location.pathname);
|
| 37 |
|
| 38 |
return (
|
| 39 |
+
<div className="flex min-h-screen text-slate-100 relative bg-transparent overflow-x-hidden">
|
| 40 |
<ConstellationBackground />
|
| 41 |
|
| 42 |
{!hideSidebar && (
|
| 43 |
+
<>
|
| 44 |
+
{/* Sidebar now receives mobile state props */}
|
| 45 |
+
<Sidebar
|
| 46 |
+
collapsed={collapsed}
|
| 47 |
+
setCollapsed={setCollapsed}
|
| 48 |
+
isMobileOpen={isMobileOpen}
|
| 49 |
+
setIsMobileOpen={setIsMobileOpen}
|
| 50 |
+
/>
|
| 51 |
+
|
| 52 |
+
{/* MOBILE HAMBURGER BUTTON (Visible only on mobile) */}
|
| 53 |
+
<button
|
| 54 |
+
onClick={() => setIsMobileOpen(true)}
|
| 55 |
+
className="lg:hidden fixed top-4 left-4 z-[40] p-2 bg-[#0b1120] border border-cyan-500/30 rounded-lg text-cyan-400 shadow-lg"
|
| 56 |
+
>
|
| 57 |
+
<Menu size={24} />
|
| 58 |
+
</button>
|
| 59 |
+
</>
|
| 60 |
)}
|
| 61 |
|
| 62 |
<main
|
| 63 |
+
className={`flex-1 overflow-y-auto p-4 md:p-6 min-h-0 transition-all duration-300
|
| 64 |
+
/* On mobile: 0 margin. On desktop: match sidebar width */
|
| 65 |
+
ml-0 ${!hideSidebar ? (collapsed ? "lg:ml-20" : "lg:ml-64") : "ml-0"}
|
| 66 |
`}
|
| 67 |
>
|
| 68 |
+
{/* Top Spacer for Mobile (Prevents content from being hidden under hamburger) */}
|
| 69 |
+
{!hideSidebar && <div className="h-12 lg:hidden" />}
|
| 70 |
+
|
|
|
|
| 71 |
<FeatureGuard requireLocal={isProtectedFeature}>
|
| 72 |
<Routes>
|
| 73 |
<Route path="/login" element={<AuthPage />} />
|
| 74 |
<Route path="/" element={<ProtectedRoute><MainLayout><Dashboard /></MainLayout></ProtectedRoute>} />
|
|
|
|
|
|
|
| 75 |
<Route path="/livetraffic" element={<ProtectedRoute><MainLayout><LiveTraffic /></MainLayout></ProtectedRoute>} />
|
| 76 |
<Route path="/flow" element={<ProtectedRoute><MainLayout><FlowPage /></MainLayout></ProtectedRoute>} />
|
| 77 |
<Route path="/system" element={<ProtectedRoute><MainLayout><SystemPage /></MainLayout></ProtectedRoute>} />
|
| 78 |
<Route path="/traffic" element={<ProtectedRoute><MainLayout><TrafficPage /></MainLayout></ProtectedRoute>} />
|
|
|
|
|
|
|
| 79 |
<Route path="/settings" element={<ProtectedRoute><MainLayout><SettingsPage /></MainLayout></ProtectedRoute>} />
|
| 80 |
<Route path="/alerts" element={<ProtectedRoute><MainLayout><InfoPage /></MainLayout></ProtectedRoute>} />
|
| 81 |
<Route path="/samplepred" element={<ProtectedRoute><MainLayout><SamplePred /></MainLayout></ProtectedRoute>} />
|
frontend/src/api.js
CHANGED
|
@@ -2,183 +2,162 @@
|
|
| 2 |
// 🔐 API Layer for Adaptive AI NIDS Frontend
|
| 3 |
// ===============================================
|
| 4 |
|
| 5 |
-
const BASE_URL =
|
| 6 |
-
import.meta.env.VITE_API_URL || "http://127.0.0.1:5000";
|
| 7 |
|
| 8 |
-
// Safe fetch wrapper
|
| 9 |
async function safeFetch(url, options = {}, timeout = 10000, retries = 1) {
|
| 10 |
const controller = new AbortController();
|
| 11 |
const id = setTimeout(() => controller.abort(), timeout);
|
| 12 |
|
| 13 |
-
|
| 14 |
-
|
| 15 |
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
|
|
|
| 28 |
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
}
|
| 34 |
|
| 35 |
// -------------------------------------------------------------
|
| 36 |
-
//
|
| 37 |
// -------------------------------------------------------------
|
| 38 |
-
export async function
|
| 39 |
-
|
| 40 |
-
return safeFetch(`${BASE_URL}/api/live/start${q}`);
|
| 41 |
}
|
| 42 |
|
| 43 |
-
export async function
|
| 44 |
-
|
| 45 |
}
|
| 46 |
|
| 47 |
-
export async function
|
| 48 |
-
|
| 49 |
}
|
| 50 |
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
const res = await safeFetch(`${BASE_URL}/api/live/recent?model=${model}`);
|
| 54 |
-
|
| 55 |
-
if (res?.events && Array.isArray(res.events)) {
|
| 56 |
-
res.events = res.events.slice(-limit);
|
| 57 |
-
}
|
| 58 |
-
return res;
|
| 59 |
}
|
| 60 |
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
}
|
| 65 |
|
| 66 |
// -------------------------------------------------------------
|
| 67 |
-
//
|
| 68 |
// -------------------------------------------------------------
|
| 69 |
-
export function
|
| 70 |
-
|
|
|
|
| 71 |
}
|
| 72 |
|
| 73 |
-
export async function
|
| 74 |
-
|
| 75 |
-
`${BASE_URL}/api/logs/clear?model=${model}&n=${n}`,
|
| 76 |
-
{ method: "POST" }
|
| 77 |
-
);
|
| 78 |
}
|
| 79 |
|
| 80 |
-
export async function
|
| 81 |
-
|
| 82 |
-
`${BASE_URL}/api/logs/clear_pred?model=${model}&pred=${pred}`,
|
| 83 |
-
{ method: "POST" }
|
| 84 |
-
);
|
| 85 |
}
|
| 86 |
|
| 87 |
-
export async function
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 92 |
}
|
| 93 |
|
| 94 |
// -------------------------------------------------------------
|
| 95 |
-
//
|
| 96 |
// -------------------------------------------------------------
|
| 97 |
-
export
|
| 98 |
-
|
| 99 |
}
|
| 100 |
|
| 101 |
-
export async function
|
| 102 |
-
|
| 103 |
}
|
| 104 |
|
| 105 |
// -------------------------------------------------------------
|
| 106 |
// 🧩 MODEL CONTROL
|
| 107 |
// -------------------------------------------------------------
|
| 108 |
-
// Change this line in api.js to ensure fresh data
|
| 109 |
export async function getActiveModel() {
|
| 110 |
-
|
| 111 |
}
|
| 112 |
|
| 113 |
export async function switchModel(model) {
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
}
|
| 120 |
|
| 121 |
-
|
| 122 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 123 |
}
|
| 124 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 125 |
|
| 126 |
// -------------------------------------------------------------
|
| 127 |
-
//
|
| 128 |
// -------------------------------------------------------------
|
| 129 |
-
export async function explainThreat(event) {
|
| 130 |
-
return safeFetch(`${BASE_URL}/api/ai/explain`, {
|
| 131 |
-
method: "POST",
|
| 132 |
-
headers: { "Content-Type": "application/json" },
|
| 133 |
-
body: JSON.stringify(event),
|
| 134 |
-
});
|
| 135 |
-
}
|
| 136 |
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
|
|
|
|
|
|
|
|
|
| 141 |
}
|
| 142 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 143 |
|
| 144 |
-
export async function sendMessageToAI(message) {
|
| 145 |
-
try {
|
| 146 |
-
const res = await fetch(`${BASE_URL}/api/chat`, {
|
| 147 |
-
method: "POST",
|
| 148 |
-
headers: { "Content-Type": "application/json" },
|
| 149 |
-
body: JSON.stringify({ message })
|
| 150 |
-
});
|
| 151 |
-
|
| 152 |
-
if (!res.ok) throw new Error("Chat API failed");
|
| 153 |
-
return res.json();
|
| 154 |
-
} catch (err) {
|
| 155 |
-
console.error("Chat error:", err);
|
| 156 |
-
return { reply: "⚠ AI Assistant not responding." };
|
| 157 |
-
}
|
| 158 |
-
}
|
| 159 |
-
|
| 160 |
-
// ➤ Offline CSV/PCAP prediction
|
| 161 |
-
export const offlinePredictAPI = async (file, model) => {
|
| 162 |
-
const formData = new FormData();
|
| 163 |
-
formData.append("file", file);
|
| 164 |
-
formData.append("model", model);
|
| 165 |
-
|
| 166 |
-
// FIX: Using BASE_URL for live deployment
|
| 167 |
-
const res = await fetch(`${BASE_URL}/api/offline/predict`, {
|
| 168 |
-
method: "POST",
|
| 169 |
-
body: formData,
|
| 170 |
-
});
|
| 171 |
-
|
| 172 |
-
return res.json();
|
| 173 |
-
};
|
| 174 |
-
|
| 175 |
-
// ➤ Get PDF forensic report download link
|
| 176 |
export const downloadOfflineReport = () => {
|
| 177 |
-
// FIX: Using BASE_URL for live deployment
|
| 178 |
-
|
| 179 |
-
}
|
| 180 |
-
|
| 181 |
|
| 182 |
-
// -------------------------------------------------------------
|
| 183 |
export { BASE_URL };
|
| 184 |
|
|
|
|
| 2 |
// 🔐 API Layer for Adaptive AI NIDS Frontend
|
| 3 |
// ===============================================
|
| 4 |
|
| 5 |
+
const BASE_URL = import.meta.env.VITE_API_URL || "http://127.0.0.1:5000";
|
|
|
|
| 6 |
|
| 7 |
+
// Safe fetch wrapper with timeout and retry logic
|
| 8 |
async function safeFetch(url, options = {}, timeout = 10000, retries = 1) {
|
| 9 |
const controller = new AbortController();
|
| 10 |
const id = setTimeout(() => controller.abort(), timeout);
|
| 11 |
|
| 12 |
+
try {
|
| 13 |
+
const res = await fetch(url, { ...options, signal: controller.signal });
|
| 14 |
|
| 15 |
+
if (!res.ok) {
|
| 16 |
+
const errText = await res.text();
|
| 17 |
+
throw new Error(`HTTP ${res.status}: ${errText}`);
|
| 18 |
+
}
|
| 19 |
+
return await res.json();
|
| 20 |
+
} catch (err) {
|
| 21 |
+
console.error(`❌ API Error [${url}]:`, err.message);
|
| 22 |
|
| 23 |
+
// Retry logic for specific sensitive endpoints
|
| 24 |
+
if (retries > 0 && url.includes("/geo/")) {
|
| 25 |
+
await new Promise((r) => setTimeout(r, 1500));
|
| 26 |
+
return safeFetch(url, options, timeout * 1.5, retries - 1);
|
| 27 |
+
}
|
| 28 |
|
| 29 |
+
return { error: err.message };
|
| 30 |
+
} finally {
|
| 31 |
+
clearTimeout(id);
|
| 32 |
+
}
|
| 33 |
}
|
| 34 |
|
| 35 |
// -------------------------------------------------------------
|
| 36 |
+
// 🖥️ SYSTEM & DIAGNOSTICS (NEWLY UPDATED)
|
| 37 |
// -------------------------------------------------------------
|
| 38 |
+
export async function getSystemStatus() {
|
| 39 |
+
return safeFetch(`${BASE_URL}/api/system/status`);
|
|
|
|
| 40 |
}
|
| 41 |
|
| 42 |
+
export async function runSystemDiagnostic() {
|
| 43 |
+
return safeFetch(`${BASE_URL}/api/system/diagnostic`);
|
| 44 |
}
|
| 45 |
|
| 46 |
+
export async function getSystemProcesses() {
|
| 47 |
+
return safeFetch(`${BASE_URL}/api/system/processes`);
|
| 48 |
}
|
| 49 |
|
| 50 |
+
export async function getSystemConnections() {
|
| 51 |
+
return safeFetch(`${BASE_URL}/api/system/connections`);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 52 |
}
|
| 53 |
|
| 54 |
+
export function downloadSystemReport() {
|
| 55 |
+
// Standard window.open for PDF downloads
|
| 56 |
+
window.open(`${BASE_URL}/api/system/report`, "_blank");
|
| 57 |
}
|
| 58 |
|
| 59 |
// -------------------------------------------------------------
|
| 60 |
+
// 🚀 LIVE CAPTURE
|
| 61 |
// -------------------------------------------------------------
|
| 62 |
+
export async function startSniffer(iface = null) {
|
| 63 |
+
const q = iface ? `?iface=${iface}` : "";
|
| 64 |
+
return safeFetch(`${BASE_URL}/api/live/start${q}`);
|
| 65 |
}
|
| 66 |
|
| 67 |
+
export async function stopSniffer() {
|
| 68 |
+
return safeFetch(`${BASE_URL}/api/live/stop`);
|
|
|
|
|
|
|
|
|
|
| 69 |
}
|
| 70 |
|
| 71 |
+
export async function getStatus() {
|
| 72 |
+
return safeFetch(`${BASE_URL}/api/live/status`);
|
|
|
|
|
|
|
|
|
|
| 73 |
}
|
| 74 |
|
| 75 |
+
export async function getRecent(model, limit = 300) {
|
| 76 |
+
const res = await safeFetch(`${BASE_URL}/api/live/recent?model=${model}`);
|
| 77 |
+
if (res?.events && Array.isArray(res.events)) {
|
| 78 |
+
res.events = res.events.slice(-limit);
|
| 79 |
+
}
|
| 80 |
+
return res;
|
| 81 |
+
}
|
| 82 |
+
|
| 83 |
+
export async function getStats(model) {
|
| 84 |
+
return safeFetch(`${BASE_URL}/api/live/stats?model=${model}`);
|
| 85 |
}
|
| 86 |
|
| 87 |
// -------------------------------------------------------------
|
| 88 |
+
// 🧾 LOGS (MODEL-AWARE)
|
| 89 |
// -------------------------------------------------------------
|
| 90 |
+
export function download_logs(model) {
|
| 91 |
+
window.location.href = `${BASE_URL}/api/logs/download?model=${model}`;
|
| 92 |
}
|
| 93 |
|
| 94 |
+
export async function clearLogs(model, n = 50) {
|
| 95 |
+
return safeFetch(`${BASE_URL}/api/logs/clear?model=${model}&n=${n}`, { method: "POST" });
|
| 96 |
}
|
| 97 |
|
| 98 |
// -------------------------------------------------------------
|
| 99 |
// 🧩 MODEL CONTROL
|
| 100 |
// -------------------------------------------------------------
|
|
|
|
| 101 |
export async function getActiveModel() {
|
| 102 |
+
return safeFetch(`${BASE_URL}/api/model/active?t=${Date.now()}`);
|
| 103 |
}
|
| 104 |
|
| 105 |
export async function switchModel(model) {
|
| 106 |
+
return safeFetch(`${BASE_URL}/api/model/select`, {
|
| 107 |
+
method: "POST",
|
| 108 |
+
headers: { "Content-Type": "application/json" },
|
| 109 |
+
body: JSON.stringify({ model }),
|
| 110 |
+
});
|
| 111 |
}
|
| 112 |
|
| 113 |
+
// -------------------------------------------------------------
|
| 114 |
+
// 🤖 AI HELPERS & CHAT
|
| 115 |
+
// -------------------------------------------------------------
|
| 116 |
+
export async function sendMessageToAI(message) {
|
| 117 |
+
// Using the safeFetch logic for consistency
|
| 118 |
+
return safeFetch(`${BASE_URL}/api/chat`, {
|
| 119 |
+
method: "POST",
|
| 120 |
+
headers: { "Content-Type": "application/json" },
|
| 121 |
+
body: JSON.stringify({ message })
|
| 122 |
+
});
|
| 123 |
}
|
| 124 |
|
| 125 |
+
export async function offlinePredictAPI(file, model) {
|
| 126 |
+
const formData = new FormData();
|
| 127 |
+
formData.append("file", file);
|
| 128 |
+
formData.append("model", model);
|
| 129 |
+
|
| 130 |
+
const res = await fetch(`${BASE_URL}/api/offline/predict`, {
|
| 131 |
+
method: "POST",
|
| 132 |
+
body: formData,
|
| 133 |
+
});
|
| 134 |
+
return res.json();
|
| 135 |
+
}
|
| 136 |
|
| 137 |
// -------------------------------------------------------------
|
| 138 |
+
// 🧾 LOGS (MODEL-AWARE)
|
| 139 |
// -------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 140 |
|
| 141 |
+
// ... keep your existing clearLogs function ...
|
| 142 |
+
|
| 143 |
+
export async function clearByPrediction(model, pred) {
|
| 144 |
+
return safeFetch(
|
| 145 |
+
`${BASE_URL}/api/logs/clear_pred?model=${model}&pred=${pred}`,
|
| 146 |
+
{ method: "POST" }
|
| 147 |
+
);
|
| 148 |
}
|
| 149 |
|
| 150 |
+
export async function deleteOne(model, index) {
|
| 151 |
+
return safeFetch(
|
| 152 |
+
`${BASE_URL}/api/logs/delete_one?model=${model}&index=${index}`,
|
| 153 |
+
{ method: "POST" }
|
| 154 |
+
);
|
| 155 |
+
}
|
| 156 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 157 |
export const downloadOfflineReport = () => {
|
| 158 |
+
// FIX: Using BASE_URL for live deployment
|
| 159 |
+
window.open(`${BASE_URL}/api/offline/report`, "_blank");
|
| 160 |
+
}
|
|
|
|
| 161 |
|
|
|
|
| 162 |
export { BASE_URL };
|
| 163 |
|
frontend/src/components/FeatureGuard.jsx
CHANGED
|
@@ -1,109 +1,173 @@
|
|
| 1 |
import React, { useState, useEffect } from "react";
|
| 2 |
-
import {
|
|
|
|
|
|
|
|
|
|
| 3 |
import { motion, AnimatePresence } from "framer-motion";
|
| 4 |
|
| 5 |
const FeatureGuard = ({ children, requireLocal = false }) => {
|
| 6 |
const [isLocal, setIsLocal] = useState(true);
|
| 7 |
const [isScanning, setIsScanning] = useState(false);
|
| 8 |
const [showPopup, setShowPopup] = useState(false);
|
| 9 |
-
const [
|
| 10 |
|
| 11 |
useEffect(() => {
|
| 12 |
-
const local =
|
| 13 |
setIsLocal(local);
|
| 14 |
|
| 15 |
if (requireLocal && !local) {
|
| 16 |
-
// Feature 2: Simulated System Scan
|
| 17 |
setIsScanning(true);
|
| 18 |
const timer = setTimeout(() => {
|
| 19 |
setIsScanning(false);
|
| 20 |
setShowPopup(true);
|
| 21 |
-
},
|
| 22 |
return () => clearTimeout(timer);
|
| 23 |
}
|
| 24 |
}, [requireLocal]);
|
| 25 |
|
| 26 |
-
|
| 27 |
-
const copyToClipboard = (text) => {
|
| 28 |
navigator.clipboard.writeText(text);
|
| 29 |
-
|
| 30 |
-
setTimeout(() =>
|
| 31 |
};
|
| 32 |
|
| 33 |
if (isLocal || !requireLocal) return <>{children}</>;
|
| 34 |
|
| 35 |
return (
|
| 36 |
-
<div className="relative min-h-screen bg-[#020617]">
|
| 37 |
-
|
|
|
|
|
|
|
|
|
|
| 38 |
|
| 39 |
<AnimatePresence mode="wait">
|
| 40 |
-
{/*
|
| 41 |
{isScanning && (
|
| 42 |
<motion.div
|
| 43 |
key="scanner"
|
| 44 |
initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }}
|
| 45 |
-
className="fixed inset-0 z-[9999] flex flex-col items-center justify-center bg-black/
|
| 46 |
>
|
| 47 |
-
<div className="relative w-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 53 |
</div>
|
| 54 |
-
<h2 className="text-cyan-100 font-mono tracking-widest uppercase text-sm">Initializing Hardware Probe...</h2>
|
| 55 |
-
<p className="text-cyan-800 font-mono text-xs mt-2">Checking local network interfaces (NICs)</p>
|
| 56 |
</motion.div>
|
| 57 |
)}
|
| 58 |
|
| 59 |
-
{/*
|
| 60 |
{showPopup && !isScanning && (
|
| 61 |
<motion.div
|
| 62 |
key="popup"
|
| 63 |
-
initial={{ opacity: 0
|
| 64 |
-
className="fixed inset-0 z-[9999] flex items-center justify-center bg-black/80 p-4"
|
| 65 |
>
|
| 66 |
-
<div
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 71 |
</div>
|
| 72 |
|
| 73 |
-
<div className="p-
|
| 74 |
-
<
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
|
|
|
|
|
|
|
|
|
| 80 |
|
| 81 |
-
{/* Terminal
|
| 82 |
-
<div className="
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 87 |
</button>
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
<span className="text-cyan-400">$ python main.py --mode live</span>
|
| 91 |
-
<button onClick={() => copyToClipboard("python main.py --mode live")} className="text-cyan-600 hover:text-cyan-300">
|
| 92 |
-
<Copy size={14} />
|
| 93 |
-
</button>
|
| 94 |
-
</div>
|
| 95 |
</div>
|
| 96 |
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 100 |
</button>
|
| 101 |
-
<button
|
| 102 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 103 |
</button>
|
| 104 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 105 |
</div>
|
| 106 |
-
</div>
|
| 107 |
</motion.div>
|
| 108 |
)}
|
| 109 |
</AnimatePresence>
|
|
|
|
| 1 |
import React, { useState, useEffect } from "react";
|
| 2 |
+
import {
|
| 3 |
+
AlertTriangle, Github, Terminal, ShieldAlert,
|
| 4 |
+
Copy, Check, Search, Cpu, Globe, Lock
|
| 5 |
+
} from "lucide-react";
|
| 6 |
import { motion, AnimatePresence } from "framer-motion";
|
| 7 |
|
| 8 |
const FeatureGuard = ({ children, requireLocal = false }) => {
|
| 9 |
const [isLocal, setIsLocal] = useState(true);
|
| 10 |
const [isScanning, setIsScanning] = useState(false);
|
| 11 |
const [showPopup, setShowPopup] = useState(false);
|
| 12 |
+
const [copiedId, setCopiedId] = useState(null);
|
| 13 |
|
| 14 |
useEffect(() => {
|
| 15 |
+
const local = false;
|
| 16 |
setIsLocal(local);
|
| 17 |
|
| 18 |
if (requireLocal && !local) {
|
|
|
|
| 19 |
setIsScanning(true);
|
| 20 |
const timer = setTimeout(() => {
|
| 21 |
setIsScanning(false);
|
| 22 |
setShowPopup(true);
|
| 23 |
+
}, 5500);
|
| 24 |
return () => clearTimeout(timer);
|
| 25 |
}
|
| 26 |
}, [requireLocal]);
|
| 27 |
|
| 28 |
+
const copyToClipboard = (text, id) => {
|
|
|
|
| 29 |
navigator.clipboard.writeText(text);
|
| 30 |
+
setCopiedId(id);
|
| 31 |
+
setTimeout(() => setCopiedId(null), 2000);
|
| 32 |
};
|
| 33 |
|
| 34 |
if (isLocal || !requireLocal) return <>{children}</>;
|
| 35 |
|
| 36 |
return (
|
| 37 |
+
<div className="relative min-h-screen bg-[#020617] overflow-hidden">
|
| 38 |
+
{/* Background Content (Blurred) */}
|
| 39 |
+
<div className="blur-2xl pointer-events-none opacity-10 grayscale scale-105 transition-all duration-1000">
|
| 40 |
+
{children}
|
| 41 |
+
</div>
|
| 42 |
|
| 43 |
<AnimatePresence mode="wait">
|
| 44 |
+
{/* --- SCANNING STATE --- */}
|
| 45 |
{isScanning && (
|
| 46 |
<motion.div
|
| 47 |
key="scanner"
|
| 48 |
initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }}
|
| 49 |
+
className="fixed inset-0 z-[9999] flex flex-col items-center justify-center bg-black/95 backdrop-blur-xl p-6"
|
| 50 |
>
|
| 51 |
+
<div className="relative w-40 h-40 mb-8">
|
| 52 |
+
{/* Radar Rings */}
|
| 53 |
+
{[...Array(3)].map((_, i) => (
|
| 54 |
+
<motion.div
|
| 55 |
+
key={i}
|
| 56 |
+
className="absolute inset-0 border border-cyan-500/30 rounded-full"
|
| 57 |
+
initial={{ scale: 1, opacity: 0.5 }}
|
| 58 |
+
animate={{ scale: 1.5, opacity: 0 }}
|
| 59 |
+
transition={{ repeat: Infinity, duration: 2, delay: i * 0.6 }}
|
| 60 |
+
/>
|
| 61 |
+
))}
|
| 62 |
+
<div className="absolute inset-0 flex items-center justify-center">
|
| 63 |
+
<div className="w-20 h-20 border-2 border-cyan-500 rounded-full flex items-center justify-center bg-cyan-500/10 shadow-[0_0_20px_rgba(6,182,212,0.3)]">
|
| 64 |
+
<Cpu className="text-cyan-400 animate-pulse" size={40} />
|
| 65 |
+
</div>
|
| 66 |
+
</div>
|
| 67 |
+
</div>
|
| 68 |
+
|
| 69 |
+
<div className="text-center space-y-2">
|
| 70 |
+
<h2 className="text-cyan-100 font-mono tracking-[0.3em] uppercase text-sm font-bold">Hardware Probe Active</h2>
|
| 71 |
+
<div className="flex items-center justify-center gap-2 text-cyan-800 font-mono text-[10px]">
|
| 72 |
+
<span className="w-1 h-1 bg-cyan-500 rounded-full animate-ping" />
|
| 73 |
+
CHECKING KERNEL INTERFACES...
|
| 74 |
+
</div>
|
| 75 |
+
</div>
|
| 76 |
+
|
| 77 |
+
{/* Simulated Log */}
|
| 78 |
+
<div className="mt-8 w-full max-w-xs bg-black/50 border border-white/5 p-3 rounded font-mono text-[9px] text-slate-500 h-24 overflow-hidden">
|
| 79 |
+
<motion.div animate={{ y: [-100, 0] }} transition={{ duration: 4, ease: "linear" }}>
|
| 80 |
+
<p>[INFO] Initializing handshake...</p>
|
| 81 |
+
<p className="text-cyan-900">[OK] v4.19.0-x86_64 detected</p>
|
| 82 |
+
<p>[ERR] Device /dev/null/promisc not found</p>
|
| 83 |
+
<p>[INFO] Scanning for local NICs...</p>
|
| 84 |
+
<p className="text-rose-900">[WARN] Remote environment detected</p>
|
| 85 |
+
<p>[INFO] Attempting hardware bypass...</p>
|
| 86 |
+
</motion.div>
|
| 87 |
</div>
|
|
|
|
|
|
|
| 88 |
</motion.div>
|
| 89 |
)}
|
| 90 |
|
| 91 |
+
{/* --- RESTRICTION MODAL --- */}
|
| 92 |
{showPopup && !isScanning && (
|
| 93 |
<motion.div
|
| 94 |
key="popup"
|
| 95 |
+
initial={{ opacity: 0 }} animate={{ opacity: 1 }}
|
| 96 |
+
className="fixed inset-0 z-[9999] flex items-center justify-center bg-black/80 backdrop-blur-sm p-4"
|
| 97 |
>
|
| 98 |
+
<motion.div
|
| 99 |
+
initial={{ scale: 0.9, y: 20 }} animate={{ scale: 1, y: 0 }}
|
| 100 |
+
className="relative w-full max-w-xl bg-[#0b1120] border border-cyan-500/20 rounded-xl overflow-hidden shadow-[0_0_80px_rgba(6,182,212,0.15)]"
|
| 101 |
+
>
|
| 102 |
+
{/* Header */}
|
| 103 |
+
<div className="bg-gradient-to-r from-cyan-950/40 to-transparent p-4 flex items-center justify-between border-b border-white/5">
|
| 104 |
+
<div className="flex items-center gap-3">
|
| 105 |
+
<div className="p-1.5 bg-rose-500/10 rounded border border-rose-500/20">
|
| 106 |
+
<ShieldAlert className="text-rose-500" size={16} />
|
| 107 |
+
</div>
|
| 108 |
+
<span className="text-slate-400 font-mono text-[10px] uppercase tracking-widest">Environment Error: 403</span>
|
| 109 |
+
</div>
|
| 110 |
+
<div className="flex gap-1.5">
|
| 111 |
+
<div className="w-2 h-2 rounded-full bg-slate-800" />
|
| 112 |
+
<div className="w-2 h-2 rounded-full bg-slate-800" />
|
| 113 |
+
</div>
|
| 114 |
</div>
|
| 115 |
|
| 116 |
+
<div className="p-6 md:p-10">
|
| 117 |
+
<div className="mb-8">
|
| 118 |
+
<h2 className="text-3xl md:text-4xl font-black text-white mb-3 tracking-tighter uppercase italic flex items-center gap-3">
|
| 119 |
+
<span className="text-cyan-500 text-sm not-italic font-mono bg-cyan-500/10 px-2 py-1 rounded">PROXIED</span>
|
| 120 |
+
LOCAL ONLY
|
| 121 |
+
</h2>
|
| 122 |
+
<p className="text-slate-400 text-sm leading-relaxed font-light">
|
| 123 |
+
The <span className="text-cyan-400 font-medium">AstraGuard Kernel</span> is currently running in an isolated cloud environment. Packet sniffing and live hardware monitoring require a <span className="underline decoration-cyan-500/30">Direct Local Link</span>.
|
| 124 |
+
</p>
|
| 125 |
+
</div>
|
| 126 |
|
| 127 |
+
{/* Commands Terminal */}
|
| 128 |
+
<div className="space-y-3 mb-8">
|
| 129 |
+
{[
|
| 130 |
+
{ id: 'clone', label: 'SOURCE', cmd: 'git clone https://github.com/ayu-yishu13/Astra_Gaurd.git' },
|
| 131 |
+
{ id: 'run', label: 'RUNTIME', cmd: 'python main.py --mode live' }
|
| 132 |
+
].map((item) => (
|
| 133 |
+
<div key={item.id} className="group flex items-center justify-between bg-black/40 border border-white/5 hover:border-cyan-500/30 rounded-lg p-4 transition-all">
|
| 134 |
+
<div className="flex flex-col gap-1 overflow-hidden">
|
| 135 |
+
<span className="text-[9px] font-bold text-cyan-600 uppercase tracking-tighter">{item.label}</span>
|
| 136 |
+
<code className="text-cyan-100 font-mono text-xs truncate pr-4">{item.cmd}</code>
|
| 137 |
+
</div>
|
| 138 |
+
<button
|
| 139 |
+
onClick={() => copyToClipboard(item.cmd, item.id)}
|
| 140 |
+
className={`flex-shrink-0 p-2 rounded-md transition-all ${copiedId === item.id ? 'bg-emerald-500/20 text-emerald-400' : 'bg-white/5 text-slate-500 hover:text-white'}`}
|
| 141 |
+
>
|
| 142 |
+
{copiedId === item.id ? <Check size={16} /> : <Copy size={16} />}
|
| 143 |
</button>
|
| 144 |
+
</div>
|
| 145 |
+
))}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 146 |
</div>
|
| 147 |
|
| 148 |
+
{/* Actions Grid */}
|
| 149 |
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
|
| 150 |
+
<button
|
| 151 |
+
onClick={() => window.open('https://github.com/ayu-yishu13/Astra_Gaurd.git', '_blank')}
|
| 152 |
+
className="flex items-center justify-center gap-2 bg-cyan-500 text-black py-4 rounded-lg font-black uppercase text-xs tracking-[0.2em] hover:bg-white transition-all group"
|
| 153 |
+
>
|
| 154 |
+
<Github size={16} />
|
| 155 |
+
Download Agent
|
| 156 |
</button>
|
| 157 |
+
<button
|
| 158 |
+
onClick={() => window.history.back()}
|
| 159 |
+
className="flex items-center justify-center gap-2 border border-slate-800 text-slate-500 py-4 rounded-lg font-bold uppercase text-xs tracking-[0.2em] hover:bg-slate-800/50 hover:text-slate-300 transition-all"
|
| 160 |
+
>
|
| 161 |
+
<Globe size={16} />
|
| 162 |
+
Return Home
|
| 163 |
</button>
|
| 164 |
</div>
|
| 165 |
+
|
| 166 |
+
<p className="mt-8 text-center text-[9px] font-mono text-slate-600 uppercase tracking-widest opacity-50">
|
| 167 |
+
System: ASTRAGUARD-SHIELD-V1 // KERNEL-LOCKED
|
| 168 |
+
</p>
|
| 169 |
</div>
|
| 170 |
+
</motion.div>
|
| 171 |
</motion.div>
|
| 172 |
)}
|
| 173 |
</AnimatePresence>
|
frontend/src/components/Navbar.jsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
import React, { useState } from "react";
|
| 2 |
-
import { Shield, Search, User, ChevronDown, RefreshCcw, LogOut, CurrencyIcon } from "lucide-react";
|
| 3 |
import { useAuth } from "../context/AuthContext";
|
| 4 |
import { useLocation, useNavigate } from "react-router-dom";
|
| 5 |
|
|
@@ -10,43 +10,58 @@ export default function Navbar({ onRefresh }) {
|
|
| 10 |
|
| 11 |
const [searchQuery, setSearchQuery] = useState("");
|
| 12 |
const [profileOpen, setProfileOpen] = useState(false);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
|
| 14 |
return (
|
| 15 |
-
<nav className="relative z-
|
| 16 |
-
<div className="container mx-auto px-4
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
|
| 18 |
{/* Brand */}
|
| 19 |
-
<button onClick={() => navigate("/")} className="flex items-center gap-3 group">
|
| 20 |
-
<div className="p-2 rounded-xl border border-accent/50 bg-accent/10 shadow-neon group-hover:bg-accent/20 transition">
|
| 21 |
-
<CurrencyIcon size={
|
| 22 |
</div>
|
| 23 |
-
<div className="flex flex-col leading-tight">
|
| 24 |
-
<span className="text-sm font-semibold text-accent tracking-[0.22em] uppercase">
|
| 25 |
ASTRAGUARD
|
| 26 |
</span>
|
| 27 |
-
<span className="text-[11px] text-slate-400 uppercase tracking-[0.18em]">
|
| 28 |
AI • NIDS
|
| 29 |
</span>
|
| 30 |
</div>
|
| 31 |
</button>
|
| 32 |
|
| 33 |
-
{/* Main Nav */}
|
| 34 |
-
<div className="hidden lg:flex items-center gap-
|
| 35 |
-
|
| 36 |
-
{[
|
| 37 |
-
{ path: "/", label: "Dashboard" },
|
| 38 |
-
{ path: "/livetraffic", label: "Live Traffic" },
|
| 39 |
-
{ path: "/flow", label: "Flow Analyzer" },
|
| 40 |
-
{ path: "/alerts", label: "Alerts" },
|
| 41 |
-
{ path: "/threats", label: "Threat Intel" },
|
| 42 |
-
{ path: "/reports", label: "Reports" },
|
| 43 |
-
{ path: "/system", label: "System" },
|
| 44 |
-
{ path: "/mlmodels", label: "ML Models" },
|
| 45 |
-
].map((item, i) => (
|
| 46 |
<button
|
| 47 |
key={i}
|
| 48 |
onClick={() => navigate(item.path)}
|
| 49 |
-
className={`px-3 py-1.5 rounded-full transition-all duration-200 ${
|
| 50 |
location.pathname === item.path
|
| 51 |
? "text-accent bg-accent/10 shadow-[0_0_10px_rgba(0,229,255,0.35)]"
|
| 52 |
: "text-slate-300 hover:text-accent hover:bg-accent/10"
|
|
@@ -58,17 +73,16 @@ export default function Navbar({ onRefresh }) {
|
|
| 58 |
</div>
|
| 59 |
|
| 60 |
{/* Right side */}
|
| 61 |
-
<div className="ml-auto flex items-center gap-4">
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
<Search size={16} className="text-accent/80" />
|
| 66 |
<input
|
| 67 |
type="text"
|
| 68 |
value={searchQuery}
|
| 69 |
onChange={(e) => setSearchQuery(e.target.value)}
|
| 70 |
-
placeholder="Search
|
| 71 |
-
className="bg-transparent text-xs text-slate-200 placeholder:text-slate-500 focus:outline-none w-40"
|
| 72 |
/>
|
| 73 |
</div>
|
| 74 |
|
|
@@ -76,24 +90,24 @@ export default function Navbar({ onRefresh }) {
|
|
| 76 |
<div className="relative">
|
| 77 |
<button
|
| 78 |
onClick={() => setProfileOpen(!profileOpen)}
|
| 79 |
-
className="flex items-center gap-2 px-2 py-1.5 rounded-full border border-accent/40 bg-white/5 hover:bg-accent/10 transition"
|
| 80 |
>
|
| 81 |
-
<div className="w-8 h-8 rounded-full border border-accent/40 bg-accent/10 flex items-center justify-center">
|
| 82 |
-
<User size={
|
| 83 |
</div>
|
| 84 |
-
<div className="hidden
|
| 85 |
-
<span className="text-[10px] text-slate-400 uppercase tracking-
|
| 86 |
Operator
|
| 87 |
</span>
|
| 88 |
-
<span className="text-xs text-accent font-medium truncate">
|
| 89 |
{user?.displayName || "Analyst"}
|
| 90 |
</span>
|
| 91 |
</div>
|
| 92 |
-
<ChevronDown size={14} className="text-slate-400" />
|
| 93 |
</button>
|
| 94 |
|
| 95 |
{profileOpen && (
|
| 96 |
-
<div className="absolute right-0 mt-
|
| 97 |
{onRefresh && (
|
| 98 |
<>
|
| 99 |
<button
|
|
@@ -101,7 +115,7 @@ export default function Navbar({ onRefresh }) {
|
|
| 101 |
onRefresh();
|
| 102 |
setProfileOpen(false);
|
| 103 |
}}
|
| 104 |
-
className="w-full flex items-center gap-
|
| 105 |
>
|
| 106 |
<RefreshCcw size={14} className="text-accent" />
|
| 107 |
<span>Reload data</span>
|
|
@@ -112,7 +126,7 @@ export default function Navbar({ onRefresh }) {
|
|
| 112 |
|
| 113 |
<button
|
| 114 |
onClick={() => logout()}
|
| 115 |
-
className="w-full flex items-center gap-
|
| 116 |
>
|
| 117 |
<LogOut size={14} />
|
| 118 |
<span>Logout</span>
|
|
@@ -120,9 +134,39 @@ export default function Navbar({ onRefresh }) {
|
|
| 120 |
</div>
|
| 121 |
)}
|
| 122 |
</div>
|
| 123 |
-
|
| 124 |
</div>
|
| 125 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 126 |
</nav>
|
| 127 |
);
|
| 128 |
-
}
|
|
|
|
| 1 |
import React, { useState } from "react";
|
| 2 |
+
import { Shield, Search, User, ChevronDown, RefreshCcw, LogOut, CurrencyIcon, Menu, X } from "lucide-react";
|
| 3 |
import { useAuth } from "../context/AuthContext";
|
| 4 |
import { useLocation, useNavigate } from "react-router-dom";
|
| 5 |
|
|
|
|
| 10 |
|
| 11 |
const [searchQuery, setSearchQuery] = useState("");
|
| 12 |
const [profileOpen, setProfileOpen] = useState(false);
|
| 13 |
+
const [menuOpen, setMenuOpen] = useState(false); // New state for mobile menu
|
| 14 |
+
|
| 15 |
+
const navLinks = [
|
| 16 |
+
{ path: "/", label: "Dashboard" },
|
| 17 |
+
{ path: "/livetraffic", label: "Live Traffic" },
|
| 18 |
+
{ path: "/flow", label: "Flow Analyzer" },
|
| 19 |
+
{ path: "/alerts", label: "Alerts" },
|
| 20 |
+
{ path: "/threats", label: "Threat Intel" },
|
| 21 |
+
{ path: "/reports", label: "Reports" },
|
| 22 |
+
{ path: "/system", label: "System" },
|
| 23 |
+
{ path: "/mlmodels", label: "ML Models" },
|
| 24 |
+
];
|
| 25 |
+
|
| 26 |
+
const handleNavigate = (path) => {
|
| 27 |
+
navigate(path);
|
| 28 |
+
setMenuOpen(false); // Close mobile menu on click
|
| 29 |
+
};
|
| 30 |
|
| 31 |
return (
|
| 32 |
+
<nav className="relative z-[50] border-b border-accent/20 backdrop-blur-xl py-2 lg:py-4 shadow-[0_4px_20px_rgba(0,0,0,0.45)]">
|
| 33 |
+
<div className="container mx-auto px-4 flex items-center justify-between lg:justify-start gap-4 lg:gap-6">
|
| 34 |
+
|
| 35 |
+
{/* Mobile Menu Toggle */}
|
| 36 |
+
<button
|
| 37 |
+
onClick={() => setMenuOpen(!menuOpen)}
|
| 38 |
+
className="lg:hidden p-2 rounded-lg border border-accent/30 text-accent"
|
| 39 |
+
>
|
| 40 |
+
{menuOpen ? <X size={20} /> : <Menu size={20} />}
|
| 41 |
+
</button>
|
| 42 |
|
| 43 |
{/* Brand */}
|
| 44 |
+
<button onClick={() => navigate("/")} className="flex items-center gap-2 lg:gap-3 group">
|
| 45 |
+
<div className="p-1.5 lg:p-2 rounded-xl border border-accent/50 bg-accent/10 shadow-neon group-hover:bg-accent/20 transition">
|
| 46 |
+
<CurrencyIcon size={18} className="text-accent" />
|
| 47 |
</div>
|
| 48 |
+
<div className="flex flex-col leading-tight text-left">
|
| 49 |
+
<span className="text-[12px] lg:text-sm font-semibold text-accent tracking-[0.15em] lg:tracking-[0.22em] uppercase">
|
| 50 |
ASTRAGUARD
|
| 51 |
</span>
|
| 52 |
+
<span className="text-[9px] lg:text-[11px] text-slate-400 uppercase tracking-[0.12em] lg:tracking-[0.18em]">
|
| 53 |
AI • NIDS
|
| 54 |
</span>
|
| 55 |
</div>
|
| 56 |
</button>
|
| 57 |
|
| 58 |
+
{/* Main Nav (Desktop) */}
|
| 59 |
+
<div className="hidden lg:flex items-center gap-2 text-sm text-slate-300 ml-4">
|
| 60 |
+
{navLinks.map((item, i) => (
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 61 |
<button
|
| 62 |
key={i}
|
| 63 |
onClick={() => navigate(item.path)}
|
| 64 |
+
className={`px-3 py-1.5 rounded-full transition-all duration-200 whitespace-nowrap ${
|
| 65 |
location.pathname === item.path
|
| 66 |
? "text-accent bg-accent/10 shadow-[0_0_10px_rgba(0,229,255,0.35)]"
|
| 67 |
: "text-slate-300 hover:text-accent hover:bg-accent/10"
|
|
|
|
| 73 |
</div>
|
| 74 |
|
| 75 |
{/* Right side */}
|
| 76 |
+
<div className="ml-auto flex items-center gap-2 lg:gap-4">
|
| 77 |
+
{/* Search (Hidden on small mobile) */}
|
| 78 |
+
<div className="hidden sm:flex items-center gap-2 px-3 py-1.5 rounded-full border border-accent/40 bg-white/5 focus-within:ring-1 focus-within:ring-accent/60 transition">
|
| 79 |
+
<Search size={14} className="text-accent/80" />
|
|
|
|
| 80 |
<input
|
| 81 |
type="text"
|
| 82 |
value={searchQuery}
|
| 83 |
onChange={(e) => setSearchQuery(e.target.value)}
|
| 84 |
+
placeholder="Search..."
|
| 85 |
+
className="bg-transparent text-xs text-slate-200 placeholder:text-slate-500 focus:outline-none w-24 lg:w-40"
|
| 86 |
/>
|
| 87 |
</div>
|
| 88 |
|
|
|
|
| 90 |
<div className="relative">
|
| 91 |
<button
|
| 92 |
onClick={() => setProfileOpen(!profileOpen)}
|
| 93 |
+
className="flex items-center gap-2 p-1 lg:px-2 lg:py-1.5 rounded-full border border-accent/40 bg-white/5 hover:bg-accent/10 transition"
|
| 94 |
>
|
| 95 |
+
<div className="w-7 h-7 lg:w-8 lg:h-8 rounded-full border border-accent/40 bg-accent/10 flex items-center justify-center">
|
| 96 |
+
<User size={14} className="text-accent" />
|
| 97 |
</div>
|
| 98 |
+
<div className="hidden md:flex flex-col items-start max-w-[100px]">
|
| 99 |
+
<span className="text-[10px] text-slate-400 uppercase tracking-wider font-bold">
|
| 100 |
Operator
|
| 101 |
</span>
|
| 102 |
+
<span className="text-xs text-accent font-medium truncate w-full text-left">
|
| 103 |
{user?.displayName || "Analyst"}
|
| 104 |
</span>
|
| 105 |
</div>
|
| 106 |
+
<ChevronDown size={14} className="text-slate-400 hidden sm:block" />
|
| 107 |
</button>
|
| 108 |
|
| 109 |
{profileOpen && (
|
| 110 |
+
<div className="absolute right-0 mt-3 w-44 rounded-xl border border-accent/30 bg-[#020617] shadow-2xl py-2 text-sm z-50 animate-in fade-in zoom-in duration-200">
|
| 111 |
{onRefresh && (
|
| 112 |
<>
|
| 113 |
<button
|
|
|
|
| 115 |
onRefresh();
|
| 116 |
setProfileOpen(false);
|
| 117 |
}}
|
| 118 |
+
className="w-full flex items-center gap-3 px-4 py-2.5 hover:bg-accent/10 text-slate-200 transition"
|
| 119 |
>
|
| 120 |
<RefreshCcw size={14} className="text-accent" />
|
| 121 |
<span>Reload data</span>
|
|
|
|
| 126 |
|
| 127 |
<button
|
| 128 |
onClick={() => logout()}
|
| 129 |
+
className="w-full flex items-center gap-3 px-4 py-2.5 hover:bg-red-500/15 text-red-300 transition"
|
| 130 |
>
|
| 131 |
<LogOut size={14} />
|
| 132 |
<span>Logout</span>
|
|
|
|
| 134 |
</div>
|
| 135 |
)}
|
| 136 |
</div>
|
|
|
|
| 137 |
</div>
|
| 138 |
</div>
|
| 139 |
+
|
| 140 |
+
{/* MOBILE MENU DRAWER */}
|
| 141 |
+
{menuOpen && (
|
| 142 |
+
<div className="lg:hidden absolute top-[100%] left-0 w-full bg-[#020617]/95 border-b border-accent/20 backdrop-blur-2xl animate-in slide-in-from-top duration-300 overflow-hidden shadow-2xl">
|
| 143 |
+
<div className="flex flex-col p-4 gap-2">
|
| 144 |
+
{navLinks.map((item, i) => (
|
| 145 |
+
<button
|
| 146 |
+
key={i}
|
| 147 |
+
onClick={() => handleNavigate(item.path)}
|
| 148 |
+
className={`flex items-center px-4 py-3 rounded-xl transition-all ${
|
| 149 |
+
location.pathname === item.path
|
| 150 |
+
? "text-accent bg-accent/10 border border-accent/20 shadow-neon-sm"
|
| 151 |
+
: "text-slate-300 hover:text-accent hover:bg-accent/5"
|
| 152 |
+
}`}
|
| 153 |
+
>
|
| 154 |
+
{item.label}
|
| 155 |
+
</button>
|
| 156 |
+
))}
|
| 157 |
+
|
| 158 |
+
{/* Mobile Search - shown only in menu */}
|
| 159 |
+
<div className="flex sm:hidden items-center gap-3 px-4 py-3 mt-2 rounded-xl border border-accent/20 bg-white/5">
|
| 160 |
+
<Search size={16} className="text-accent" />
|
| 161 |
+
<input
|
| 162 |
+
type="text"
|
| 163 |
+
placeholder="Search systems..."
|
| 164 |
+
className="bg-transparent text-sm text-slate-200 focus:outline-none w-full"
|
| 165 |
+
/>
|
| 166 |
+
</div>
|
| 167 |
+
</div>
|
| 168 |
+
</div>
|
| 169 |
+
)}
|
| 170 |
</nav>
|
| 171 |
);
|
| 172 |
+
}
|
frontend/src/components/Sidebar.jsx
CHANGED
|
@@ -2,29 +2,12 @@
|
|
| 2 |
import React, { useEffect, useRef, useState } from "react";
|
| 3 |
import { NavLink } from "react-router-dom";
|
| 4 |
import {
|
| 5 |
-
Activity,
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
Info,
|
| 9 |
-
Settings,
|
| 10 |
-
TrafficCone,
|
| 11 |
-
MonitorCog,
|
| 12 |
-
GitBranchMinus,
|
| 13 |
-
BrickWallShield,
|
| 14 |
-
ChevronLeft,
|
| 15 |
-
ChevronRight,
|
| 16 |
-
MoonStar,
|
| 17 |
-
LayoutDashboard,
|
| 18 |
} from "lucide-react";
|
| 19 |
|
| 20 |
-
|
| 21 |
-
* Sidebar with mini-constellation canvas (B3)
|
| 22 |
-
*
|
| 23 |
-
* Props:
|
| 24 |
-
* - collapsed: boolean
|
| 25 |
-
* - setCollapsed: function
|
| 26 |
-
*/
|
| 27 |
-
export default function Sidebar({ collapsed, setCollapsed }) {
|
| 28 |
const sidebarRef = useRef(null);
|
| 29 |
const canvasRef = useRef(null);
|
| 30 |
const [time, setTime] = useState("");
|
|
@@ -33,35 +16,27 @@ export default function Sidebar({ collapsed, setCollapsed }) {
|
|
| 33 |
useEffect(() => {
|
| 34 |
const tick = () => {
|
| 35 |
const now = new Date();
|
| 36 |
-
setTime(
|
| 37 |
-
now.toLocaleTimeString("en-IN", {
|
| 38 |
-
hour12: true,
|
| 39 |
-
hour: "2-digit",
|
| 40 |
-
minute: "2-digit",
|
| 41 |
-
second: "2-digit",
|
| 42 |
-
})
|
| 43 |
-
);
|
| 44 |
};
|
| 45 |
tick();
|
| 46 |
const id = setInterval(tick, 1000);
|
| 47 |
return () => clearInterval(id);
|
| 48 |
}, []);
|
| 49 |
|
| 50 |
-
//
|
| 51 |
useEffect(() => {
|
| 52 |
const canvas = canvasRef.current;
|
| 53 |
const sidebar = sidebarRef.current;
|
| 54 |
if (!canvas || !sidebar) return;
|
| 55 |
-
|
| 56 |
const ctx = canvas.getContext("2d", { alpha: true });
|
| 57 |
let width = sidebar.clientWidth;
|
| 58 |
let height = sidebar.clientHeight;
|
| 59 |
let rafId = null;
|
| 60 |
let particles = [];
|
| 61 |
-
const COUNT = Math.max(18, Math.floor((width * height) / 10000));
|
| 62 |
|
| 63 |
-
// Resize handler
|
| 64 |
function resize() {
|
|
|
|
| 65 |
width = sidebar.clientWidth;
|
| 66 |
height = sidebar.clientHeight;
|
| 67 |
const dpr = Math.max(1, window.devicePixelRatio || 1);
|
|
@@ -70,100 +45,40 @@ export default function Sidebar({ collapsed, setCollapsed }) {
|
|
| 70 |
canvas.style.width = `${width}px`;
|
| 71 |
canvas.style.height = `${height}px`;
|
| 72 |
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
| 73 |
-
// recreate particles to adapt density
|
| 74 |
initParticles();
|
| 75 |
}
|
| 76 |
|
| 77 |
-
function rand(min, max) {
|
| 78 |
-
return Math.random() * (max - min) + min;
|
| 79 |
-
}
|
| 80 |
-
|
| 81 |
function initParticles() {
|
| 82 |
particles = [];
|
| 83 |
-
|
| 84 |
-
for (let i = 0; i < n; i++) {
|
| 85 |
particles.push({
|
| 86 |
-
x:
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
vy: rand(-0.15, 0.15),
|
| 90 |
-
size: rand(0.8, 2.2),
|
| 91 |
-
hue: rand(170, 200), // cyan-ish
|
| 92 |
});
|
| 93 |
}
|
| 94 |
}
|
| 95 |
|
| 96 |
function step() {
|
| 97 |
ctx.clearRect(0, 0, width, height);
|
| 98 |
-
|
| 99 |
-
// draw lines between close particles
|
| 100 |
-
for (let i = 0; i < particles.length; i++) {
|
| 101 |
-
const a = particles[i];
|
| 102 |
-
for (let j = i + 1; j < particles.length; j++) {
|
| 103 |
-
const b = particles[j];
|
| 104 |
-
const dx = a.x - b.x;
|
| 105 |
-
const dy = a.y - b.y;
|
| 106 |
-
const dist = Math.sqrt(dx * dx + dy * dy);
|
| 107 |
-
const MAX = Math.min(120, Math.max(70, (width + height) / 15));
|
| 108 |
-
if (dist < MAX) {
|
| 109 |
-
const alpha = 0.12 * (1 - dist / MAX);
|
| 110 |
-
ctx.beginPath();
|
| 111 |
-
ctx.moveTo(a.x, a.y);
|
| 112 |
-
ctx.lineTo(b.x, b.y);
|
| 113 |
-
ctx.strokeStyle = `rgba(40,220,210, ${alpha.toFixed(3)})`;
|
| 114 |
-
ctx.lineWidth = 0.6;
|
| 115 |
-
ctx.stroke();
|
| 116 |
-
}
|
| 117 |
-
}
|
| 118 |
-
}
|
| 119 |
-
|
| 120 |
-
// draw nodes
|
| 121 |
for (let p of particles) {
|
| 122 |
-
|
| 123 |
-
p.x
|
| 124 |
-
p.y
|
| 125 |
-
|
| 126 |
-
// gentle wrap/bounce
|
| 127 |
-
if (p.x < -6) p.x = width + 6;
|
| 128 |
-
if (p.x > width + 6) p.x = -6;
|
| 129 |
-
if (p.y < -6) p.y = height + 6;
|
| 130 |
-
if (p.y > height + 6) p.y = -6;
|
| 131 |
-
|
| 132 |
-
// glow circle
|
| 133 |
ctx.beginPath();
|
| 134 |
-
const grad = ctx.createRadialGradient(p.x, p.y, 0, p.x, p.y, p.size * 6);
|
| 135 |
-
grad.addColorStop(0, `rgba(0,230,255,0.95)`);
|
| 136 |
-
grad.addColorStop(0.2, `rgba(0,230,255,0.55)`);
|
| 137 |
-
grad.addColorStop(1, `rgba(0,230,255,0.02)`);
|
| 138 |
-
ctx.fillStyle = grad;
|
| 139 |
-
ctx.arc(p.x, p.y, p.size * 3.2, 0, Math.PI * 2);
|
| 140 |
-
ctx.fill();
|
| 141 |
-
|
| 142 |
-
// center bright dot
|
| 143 |
-
ctx.beginPath();
|
| 144 |
-
ctx.fillStyle = `rgba(220,255,255,0.95)`;
|
| 145 |
ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
|
|
|
|
| 146 |
ctx.fill();
|
| 147 |
}
|
| 148 |
-
|
| 149 |
rafId = requestAnimationFrame(step);
|
| 150 |
}
|
| 151 |
|
| 152 |
-
// ResizeObserver to respond to sidebar size changes (collapse/expand)
|
| 153 |
const ro = new ResizeObserver(resize);
|
| 154 |
ro.observe(sidebar);
|
|
|
|
|
|
|
|
|
|
| 155 |
|
| 156 |
-
resize();
|
| 157 |
-
step(); // start animation
|
| 158 |
-
|
| 159 |
-
return () => {
|
| 160 |
-
if (rafId) cancelAnimationFrame(rafId);
|
| 161 |
-
ro.disconnect();
|
| 162 |
-
};
|
| 163 |
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
| 164 |
-
}, [sidebarRef, canvasRef, collapsed]);
|
| 165 |
-
|
| 166 |
-
// navigation items
|
| 167 |
const navItems = [
|
| 168 |
{ to: "/", label: "Dashboard", icon: <LayoutDashboard size={18} /> },
|
| 169 |
{ to: "/livetraffic", label: "Live Traffic", icon: <Activity size={18} /> },
|
|
@@ -179,146 +94,87 @@ export default function Sidebar({ collapsed, setCollapsed }) {
|
|
| 179 |
];
|
| 180 |
|
| 181 |
return (
|
| 182 |
-
<
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
<div
|
| 202 |
-
className="absolute inset-0 pointer-events-none -z-5"
|
| 203 |
-
style={{
|
| 204 |
-
boxShadow:
|
| 205 |
-
"inset 0 0 2px rgba(0,230,255,0.06), inset 0 0 30px rgba(0,230,255,0.03)",
|
| 206 |
-
}}
|
| 207 |
-
/>
|
| 208 |
|
| 209 |
-
|
| 210 |
-
|
| 211 |
-
<div className="relative z-10 flex items-center justify-between">
|
| 212 |
<div className="flex items-center gap-2">
|
| 213 |
<span className="text-[18px] font-extrabold text-[var(--accent)]">⚡</span>
|
| 214 |
-
{!collapsed && (
|
| 215 |
<div>
|
| 216 |
-
<div className="text-xl font-bold text-[var(--accent)]
|
| 217 |
-
<div className="text-[
|
| 218 |
</div>
|
| 219 |
)}
|
| 220 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 221 |
|
| 222 |
-
|
| 223 |
-
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
}
|
| 227 |
-
|
| 228 |
-
|
| 229 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 230 |
)}
|
| 231 |
</div>
|
| 232 |
</div>
|
| 233 |
|
| 234 |
-
{
|
| 235 |
-
|
| 236 |
-
|
| 237 |
-
|
| 238 |
-
<span className="font-mono text-[var(--accent)] text-right">{window.location.hostname}</span>
|
| 239 |
-
</div>
|
| 240 |
-
<div className="mt-2 relative h-1.5 bg-gradient-to-r from-transparent to-transparent rounded-full overflow-hidden">
|
| 241 |
-
<div className="absolute inset-0 bg-gradient-to-r from-cyan-400 via-emerald-400 to-cyan-400 opacity-30 blur-sm" />
|
| 242 |
-
<div className="mt-2 text-[10px] text-slate-500">Monitoring anomalies…</div>
|
| 243 |
-
</div>
|
| 244 |
-
</div>
|
| 245 |
-
)}
|
| 246 |
-
</div>
|
| 247 |
-
|
| 248 |
-
{/* NAV - scrollable */}
|
| 249 |
-
<nav className="flex-1 p-3 overflow-y-auto overflow-x-hidden custom-scroll">
|
| 250 |
-
<div className="space-y-1">
|
| 251 |
-
|
| 252 |
-
{navItems.map(({ to, label, icon }, index) => (
|
| 253 |
-
<div key={to}>
|
| 254 |
-
|
| 255 |
-
<NavLink
|
| 256 |
-
to={to}
|
| 257 |
-
title={collapsed ? label : ""}
|
| 258 |
-
className={({ isActive }) =>
|
| 259 |
-
`
|
| 260 |
-
group flex items-center gap-3 px-3 py-2 rounded-lg transition-all duration-200 text-sm relative
|
| 261 |
-
${isActive
|
| 262 |
-
? "bg-[var(--accent)]/18 border border-[var(--accent)]/28 text-[var(--accent)] shadow-[0_0_10px_rgba(0,230,255,0.06)]"
|
| 263 |
-
: "text-slate-300 hover:bg-[var(--accent)]/6 hover:border hover:border-[var(--accent)]/18"}
|
| 264 |
-
`
|
| 265 |
-
}
|
| 266 |
>
|
| 267 |
-
|
| 268 |
-
|
| 269 |
-
|
| 270 |
-
|
| 271 |
-
|
| 272 |
-
{!collapsed && <span className="truncate">{label}</span>}
|
| 273 |
-
|
| 274 |
-
{/* Tooltip when collapsed */}
|
| 275 |
-
{collapsed && (
|
| 276 |
-
<div className="absolute left-[3.4rem] top-1/2 -translate-y-1/2 opacity-0
|
| 277 |
-
group-hover:opacity-100 transition-opacity duration-150 pointer-events-none">
|
| 278 |
-
<div className="bg-black/75 text-[var(--accent)] border border-[var(--accent)]/30
|
| 279 |
-
px-2 py-1 rounded text-xs whitespace-nowrap shadow-md">
|
| 280 |
-
{label}
|
| 281 |
-
</div>
|
| 282 |
-
</div>
|
| 283 |
-
)}
|
| 284 |
-
|
| 285 |
-
</NavLink>
|
| 286 |
-
|
| 287 |
-
{/* Divider line (between items) */}
|
| 288 |
-
{index < navItems.length - 1 && (
|
| 289 |
-
<div className="h-px w-full bg-white/100 my-3"></div>
|
| 290 |
-
)}
|
| 291 |
-
|
| 292 |
-
</div>
|
| 293 |
-
))}
|
| 294 |
-
|
| 295 |
-
</div>
|
| 296 |
-
</nav>
|
| 297 |
-
|
| 298 |
-
|
| 299 |
-
{/* FOOTER */}
|
| 300 |
-
<div className="p-3 border-t border-[var(--accent)]/12 shrink-0">
|
| 301 |
-
<div className="flex items-center justify-between">
|
| 302 |
-
{!collapsed ? (
|
| 303 |
-
<>
|
| 304 |
-
<div className="text-[10px] text-slate-400">© 2025 Future Lelouch</div>
|
| 305 |
-
<div className="text-[11px] text-[var(--accent)] font-mono">{time}</div>
|
| 306 |
-
</>
|
| 307 |
-
) : (
|
| 308 |
-
<div className="text-[10px] text-slate-400 text-center w-full">© 2025</div>
|
| 309 |
-
)}
|
| 310 |
-
</div>
|
| 311 |
-
</div>
|
| 312 |
-
|
| 313 |
-
{/* collapse/expand toggle */}
|
| 314 |
-
<button
|
| 315 |
-
aria-label={collapsed ? "Expand sidebar" : "Collapse sidebar"}
|
| 316 |
-
onClick={() => setCollapsed(!collapsed)}
|
| 317 |
-
className="absolute right-[-12px] bottom-6 w-8 h-8 rounded-full bg-[var(--accent)]/12 border border-[var(--accent)]/30 text-[var(--accent)] shadow-[0_6px_18px_rgba(0,230,255,0.06)] flex items-center justify-center transition-transform hover:scale-110"
|
| 318 |
-
>
|
| 319 |
-
{collapsed ? <ChevronRight size={16} /> : <ChevronLeft size={16} />}
|
| 320 |
-
</button>
|
| 321 |
-
</aside>
|
| 322 |
);
|
| 323 |
}
|
| 324 |
|
|
|
|
| 2 |
import React, { useEffect, useRef, useState } from "react";
|
| 3 |
import { NavLink } from "react-router-dom";
|
| 4 |
import {
|
| 5 |
+
Activity, AlertTriangle, FileText, Info, Settings, TrafficCone,
|
| 6 |
+
MonitorCog, GitBranchMinus, BrickWallShield, ChevronLeft, ChevronRight,
|
| 7 |
+
MoonStar, LayoutDashboard, X, Menu
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
} from "lucide-react";
|
| 9 |
|
| 10 |
+
export default function Sidebar({ collapsed, setCollapsed, isMobileOpen, setIsMobileOpen }) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
const sidebarRef = useRef(null);
|
| 12 |
const canvasRef = useRef(null);
|
| 13 |
const [time, setTime] = useState("");
|
|
|
|
| 16 |
useEffect(() => {
|
| 17 |
const tick = () => {
|
| 18 |
const now = new Date();
|
| 19 |
+
setTime(now.toLocaleTimeString("en-IN", { hour12: true, hour: "2-digit", minute: "2-digit", second: "2-digit" }));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
};
|
| 21 |
tick();
|
| 22 |
const id = setInterval(tick, 1000);
|
| 23 |
return () => clearInterval(id);
|
| 24 |
}, []);
|
| 25 |
|
| 26 |
+
// Canvas Animation Logic (Kept as per your original code)
|
| 27 |
useEffect(() => {
|
| 28 |
const canvas = canvasRef.current;
|
| 29 |
const sidebar = sidebarRef.current;
|
| 30 |
if (!canvas || !sidebar) return;
|
|
|
|
| 31 |
const ctx = canvas.getContext("2d", { alpha: true });
|
| 32 |
let width = sidebar.clientWidth;
|
| 33 |
let height = sidebar.clientHeight;
|
| 34 |
let rafId = null;
|
| 35 |
let particles = [];
|
| 36 |
+
const COUNT = Math.max(18, Math.floor((width * height) / 10000));
|
| 37 |
|
|
|
|
| 38 |
function resize() {
|
| 39 |
+
if (!sidebar) return;
|
| 40 |
width = sidebar.clientWidth;
|
| 41 |
height = sidebar.clientHeight;
|
| 42 |
const dpr = Math.max(1, window.devicePixelRatio || 1);
|
|
|
|
| 45 |
canvas.style.width = `${width}px`;
|
| 46 |
canvas.style.height = `${height}px`;
|
| 47 |
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
|
|
| 48 |
initParticles();
|
| 49 |
}
|
| 50 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
function initParticles() {
|
| 52 |
particles = [];
|
| 53 |
+
for (let i = 0; i < COUNT; i++) {
|
|
|
|
| 54 |
particles.push({
|
| 55 |
+
x: Math.random() * width, y: Math.random() * height,
|
| 56 |
+
vx: (Math.random() - 0.5) * 0.3, vy: (Math.random() - 0.5) * 0.3,
|
| 57 |
+
size: Math.random() * 1.5 + 0.5,
|
|
|
|
|
|
|
|
|
|
| 58 |
});
|
| 59 |
}
|
| 60 |
}
|
| 61 |
|
| 62 |
function step() {
|
| 63 |
ctx.clearRect(0, 0, width, height);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 64 |
for (let p of particles) {
|
| 65 |
+
p.x += p.vx; p.y += p.vy;
|
| 66 |
+
if (p.x < 0 || p.x > width) p.vx *= -1;
|
| 67 |
+
if (p.y < 0 || p.y > height) p.vy *= -1;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 68 |
ctx.beginPath();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 69 |
ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
|
| 70 |
+
ctx.fillStyle = "rgba(0, 230, 255, 0.5)";
|
| 71 |
ctx.fill();
|
| 72 |
}
|
|
|
|
| 73 |
rafId = requestAnimationFrame(step);
|
| 74 |
}
|
| 75 |
|
|
|
|
| 76 |
const ro = new ResizeObserver(resize);
|
| 77 |
ro.observe(sidebar);
|
| 78 |
+
resize(); step();
|
| 79 |
+
return () => { if (rafId) cancelAnimationFrame(rafId); ro.disconnect(); };
|
| 80 |
+
}, [collapsed]);
|
| 81 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 82 |
const navItems = [
|
| 83 |
{ to: "/", label: "Dashboard", icon: <LayoutDashboard size={18} /> },
|
| 84 |
{ to: "/livetraffic", label: "Live Traffic", icon: <Activity size={18} /> },
|
|
|
|
| 94 |
];
|
| 95 |
|
| 96 |
return (
|
| 97 |
+
<>
|
| 98 |
+
{/* MOBILE OVERLAY: Blurs background when sidebar is open on mobile */}
|
| 99 |
+
{isMobileOpen && (
|
| 100 |
+
<div
|
| 101 |
+
className="fixed inset-0 bg-black/60 backdrop-blur-sm z-[45] lg:hidden"
|
| 102 |
+
onClick={() => setIsMobileOpen(false)}
|
| 103 |
+
/>
|
| 104 |
+
)}
|
| 105 |
+
|
| 106 |
+
<aside
|
| 107 |
+
ref={sidebarRef}
|
| 108 |
+
className={`fixed top-0 left-0 h-screen z-50 transition-all duration-300 ease-in-out
|
| 109 |
+
border-r-2 border-[var(--accent)]/14 bg-[#030b17]/90 backdrop-blur-xl
|
| 110 |
+
${collapsed ? "w-20" : "w-64"}
|
| 111 |
+
/* MOBILE LOGIC: Slide out of screen on mobile unless isMobileOpen is true */
|
| 112 |
+
${isMobileOpen ? "translate-x-0" : "-translate-x-full lg:translate-x-0"}
|
| 113 |
+
`}
|
| 114 |
+
>
|
| 115 |
+
<canvas ref={canvasRef} className="pointer-events-none absolute inset-0 -z-10" />
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 116 |
|
| 117 |
+
{/* HEADER */}
|
| 118 |
+
<div className="p-4 border-b border-[var(--accent)]/12 flex items-center justify-between">
|
|
|
|
| 119 |
<div className="flex items-center gap-2">
|
| 120 |
<span className="text-[18px] font-extrabold text-[var(--accent)]">⚡</span>
|
| 121 |
+
{(!collapsed || isMobileOpen) && (
|
| 122 |
<div>
|
| 123 |
+
<div className="text-xl font-bold text-[var(--accent)]">NIDS</div>
|
| 124 |
+
<div className="text-[10px] text-slate-400 uppercase tracking-tighter">Cyber Defense</div>
|
| 125 |
</div>
|
| 126 |
)}
|
| 127 |
</div>
|
| 128 |
+
|
| 129 |
+
{/* Close button - Only visible on Mobile */}
|
| 130 |
+
<button className="lg:hidden text-slate-400" onClick={() => setIsMobileOpen(false)}>
|
| 131 |
+
<X size={20} />
|
| 132 |
+
</button>
|
| 133 |
+
</div>
|
| 134 |
|
| 135 |
+
{/* NAVIGATION */}
|
| 136 |
+
<nav className="flex-1 p-3 overflow-y-auto overflow-x-hidden custom-scroll space-y-1">
|
| 137 |
+
{navItems.map(({ to, label, icon }) => (
|
| 138 |
+
<NavLink
|
| 139 |
+
key={to}
|
| 140 |
+
to={to}
|
| 141 |
+
onClick={() => setIsMobileOpen(false)} // Close sidebar on mobile after clicking
|
| 142 |
+
className={({ isActive }) => `
|
| 143 |
+
group flex items-center gap-3 px-3 py-2.5 rounded-lg transition-all text-sm
|
| 144 |
+
${isActive
|
| 145 |
+
? "bg-[var(--accent)]/15 border border-[var(--accent)]/30 text-[var(--accent)]"
|
| 146 |
+
: "text-slate-300 hover:bg-[var(--accent)]/5 hover:text-white"}
|
| 147 |
+
`}
|
| 148 |
+
>
|
| 149 |
+
<div className="shrink-0">{icon}</div>
|
| 150 |
+
{(!collapsed || isMobileOpen) && <span className="truncate font-medium">{label}</span>}
|
| 151 |
+
</NavLink>
|
| 152 |
+
))}
|
| 153 |
+
</nav>
|
| 154 |
+
|
| 155 |
+
{/* FOOTER */}
|
| 156 |
+
<div className="p-4 border-t border-[var(--accent)]/12 bg-black/20">
|
| 157 |
+
<div className="flex flex-col gap-1">
|
| 158 |
+
{(!collapsed || isMobileOpen) ? (
|
| 159 |
+
<>
|
| 160 |
+
<div className="text-[11px] text-[var(--accent)] font-mono tracking-widest">{time}</div>
|
| 161 |
+
<div className="text-[9px] text-slate-500 uppercase tracking-widest">© 2025 Future Lelouch</div>
|
| 162 |
+
</>
|
| 163 |
+
) : (
|
| 164 |
+
<div className="text-[9px] text-slate-500 text-center">© 25</div>
|
| 165 |
)}
|
| 166 |
</div>
|
| 167 |
</div>
|
| 168 |
|
| 169 |
+
{/* DESKTOP COLLAPSE TOGGLE (Hidden on Mobile) */}
|
| 170 |
+
<button
|
| 171 |
+
onClick={() => setCollapsed(!collapsed)}
|
| 172 |
+
className="hidden lg:flex absolute right-[-14px] top-20 w-7 h-7 rounded-full bg-[#030b17] border border-[var(--accent)]/30 text-[var(--accent)] items-center justify-center hover:scale-110 transition-transform"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 173 |
>
|
| 174 |
+
{collapsed ? <ChevronRight size={14} /> : <ChevronLeft size={14} />}
|
| 175 |
+
</button>
|
| 176 |
+
</aside>
|
| 177 |
+
</>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 178 |
);
|
| 179 |
}
|
| 180 |
|
frontend/src/pages/Dashboard.jsx
CHANGED
|
@@ -25,42 +25,28 @@ import NeonShield from "../components/NeonShield";
|
|
| 25 |
import { useNavigate } from "react-router-dom";
|
| 26 |
import ChatAssistant from "./ChatAssistant";
|
| 27 |
|
| 28 |
-
// Cyber palette
|
| 29 |
const COLORS = ["#00e5ff", "#ff0059", "#a78bfa", "#fbbf24", "#10b981"];
|
| 30 |
|
| 31 |
export default function Dashboard() {
|
| 32 |
-
|
| 33 |
const location = useLocation();
|
| 34 |
const navigate = useNavigate();
|
| 35 |
const [systemStats, setSystemStats] = useState(null);
|
| 36 |
const [threats, setThreats] = useState([]);
|
| 37 |
const [mlModels, setMlModels] = useState([]);
|
| 38 |
const [loading, setLoading] = useState(true);
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
// -------------------------------
|
| 43 |
-
// BACK TO TOP BUTTON STATE
|
| 44 |
-
// -------------------------------
|
| 45 |
const [showTop, setShowTop] = useState(false);
|
| 46 |
|
| 47 |
useEffect(() => {
|
| 48 |
-
// find the real scroll container
|
| 49 |
const scrollContainer = document.querySelector("main.flex-1.overflow-auto");
|
| 50 |
-
|
| 51 |
if (!scrollContainer) return;
|
| 52 |
-
|
| 53 |
const handleScroll = () => {
|
| 54 |
if (scrollContainer.scrollTop > 300) setShowTop(true);
|
| 55 |
else setShowTop(false);
|
| 56 |
};
|
| 57 |
-
|
| 58 |
scrollContainer.addEventListener("scroll", handleScroll);
|
| 59 |
-
|
| 60 |
return () => scrollContainer.removeEventListener("scroll", handleScroll);
|
| 61 |
}, []);
|
| 62 |
|
| 63 |
-
// Backend fetch
|
| 64 |
const loadData = async () => {
|
| 65 |
try {
|
| 66 |
const [sys, ml, th] = await Promise.all([
|
|
@@ -68,7 +54,6 @@ export default function Dashboard() {
|
|
| 68 |
fetch("http://127.0.0.1:5000/api/ml/models").then((r) => r.json()),
|
| 69 |
fetch("http://127.0.0.1:5000/api/live/stats").then((r) => r.json()),
|
| 70 |
]);
|
| 71 |
-
|
| 72 |
setSystemStats(sys || {});
|
| 73 |
setMlModels(Array.isArray(ml) ? ml : []);
|
| 74 |
setThreats(
|
|
@@ -92,126 +77,59 @@ export default function Dashboard() {
|
|
| 92 |
return (
|
| 93 |
<>
|
| 94 |
<div className="relative">
|
| 95 |
-
{/*
|
| 96 |
-
CYBER BACKGROUND
|
| 97 |
-
============================= */}
|
| 98 |
<div className="absolute inset-0 -z-10 pointer-events-none overflow-hidden">
|
| 99 |
-
{
|
| 100 |
-
<div
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
background:
|
| 104 |
-
"linear-gradient(140deg, rgba(3,6,23,0.75) 0%, rgba(9,19,39,0.7) 40%, rgba(10,26,54,0.6) 75%, rgba(15,7,33,0.65) 100%)",
|
| 105 |
-
}}
|
| 106 |
-
/>
|
| 107 |
-
|
| 108 |
-
{/* Hex Grid */}
|
| 109 |
-
<div
|
| 110 |
-
className="absolute inset-0 opacity-[0.035] mix-blend-screen"
|
| 111 |
-
style={{
|
| 112 |
-
backgroundImage:
|
| 113 |
-
"url('https://raw.githubusercontent.com/ayushatlas/assets/main/hexgrid/hexgrid-light.png')",
|
| 114 |
-
backgroundSize: "350px",
|
| 115 |
-
backgroundRepeat: "repeat",
|
| 116 |
-
}}
|
| 117 |
-
/>
|
| 118 |
-
|
| 119 |
-
{/* Left Cyan Glow */}
|
| 120 |
-
<div className="absolute left-10 top-40 w-[450px] h-[450px] bg-cyan-400/15 blur-[180px]" />
|
| 121 |
-
|
| 122 |
-
{/* Right Purple Glow */}
|
| 123 |
-
<div className="absolute right-10 top-10 w-[420px] h-[420px] bg-purple-500/12 blur-[180px]" />
|
| 124 |
-
|
| 125 |
-
{/* Floating Particles */}
|
| 126 |
-
{[...Array(70)].map((_, i) => (
|
| 127 |
-
<div
|
| 128 |
-
key={i}
|
| 129 |
-
className="absolute rounded-full drift-slow"
|
| 130 |
-
style={{
|
| 131 |
-
width: `${3 + Math.random() * 6}px`,
|
| 132 |
-
height: `${3 + Math.random() * 6}px`,
|
| 133 |
-
background: `rgba(150, 220, 255, ${0.45 + Math.random() * 0.4})`,
|
| 134 |
-
top: `${Math.random() * 100}%`,
|
| 135 |
-
left: `${Math.random() * 100}%`,
|
| 136 |
-
filter: "blur(2px)",
|
| 137 |
-
animationDuration: `${6 + Math.random() * 5}s`,
|
| 138 |
-
animationDelay: `${Math.random() * 3}s`,
|
| 139 |
-
opacity: 0.6 + Math.random() * 0.3,
|
| 140 |
-
}}
|
| 141 |
-
/>
|
| 142 |
-
))}
|
| 143 |
</div>
|
| 144 |
|
| 145 |
-
{/*
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
<div className="relative z-20 container mx-auto px-8 pt-24 pb-24 lg:ml-1">
|
| 151 |
-
<div className="grid lg:grid-cols-2 gap-16 items-center">
|
| 152 |
{/* LEFT TEXT */}
|
| 153 |
-
<div className="space-y-7">
|
| 154 |
-
<div className="flex items-center gap-3">
|
| 155 |
<div className="p-2 rounded-full border border-accent/50 shadow-neon">
|
| 156 |
-
<Shield size={
|
| 157 |
</div>
|
| 158 |
-
<span className="text-sm text-slate-300/80 tracking-wide">
|
| 159 |
AstraGuard AI • Adaptive NIDS
|
| 160 |
</span>
|
| 161 |
</div>
|
| 162 |
|
| 163 |
-
<h1
|
| 164 |
-
|
| 165 |
-
style={{
|
| 166 |
-
textShadow:
|
| 167 |
-
"0 0 30px rgba(0,229,255,0.75), 0 0 12px rgba(0,229,255,0.5)",
|
| 168 |
-
}}
|
| 169 |
-
>
|
| 170 |
<span className="text-accent">Protect</span> Your <br /> Systems
|
| 171 |
</h1>
|
| 172 |
|
| 173 |
-
<div className="w-full overflow-hidden">
|
| 174 |
<div className="neon-wave" />
|
| 175 |
</div>
|
| 176 |
|
| 177 |
-
<p className="text-slate-300/85 text-lg max-w-xl leading-relaxed">
|
| 178 |
Our adaptive AI framework provides real-time packet inspection,
|
| 179 |
-
threat detection,
|
| 180 |
</p>
|
| 181 |
|
| 182 |
-
<div className="flex items-center gap-4 pt-3">
|
| 183 |
-
<button
|
| 184 |
-
|
| 185 |
-
className="px-6 py-3 rounded-xl bg-accent text-black font-semibold text-lg shadow-neon hover:scale-[1.03] transition"
|
| 186 |
-
>
|
| 187 |
Get Started
|
| 188 |
</button>
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
onClick={() => navigate("/threats")}
|
| 192 |
-
className="px-6 py-3 rounded-xl border border-accent/40 text-accent text-lg hover:bg-accent/10 transition"
|
| 193 |
-
>
|
| 194 |
Learn more
|
| 195 |
</button>
|
| 196 |
</div>
|
| 197 |
</div>
|
| 198 |
|
| 199 |
-
{/* RIGHT Floating Shield */}
|
| 200 |
-
<div className="relative w-full flex justify-center items-center h-[420px]">
|
| 201 |
-
<div className="
|
| 202 |
-
<div className="p-2 rounded-full border border-accent/50 shadow-neon">
|
| 203 |
-
<Radio size={24} className="text-accent" />
|
| 204 |
-
</div>
|
| 205 |
-
<span className="text-sm text-slate-300/80">
|
| 206 |
-
Cyber • Security
|
| 207 |
-
</span>
|
| 208 |
-
</div>
|
| 209 |
-
|
| 210 |
-
<div
|
| 211 |
-
className="relative w-[90%] h-[380px] rounded-3xl
|
| 212 |
-
border border-accent/25 backdrop-blur-xl shadow-[0_0_40px_rgba(0,229,255,0.25)]
|
| 213 |
-
overflow-hidden"
|
| 214 |
-
>
|
| 215 |
<NeonShield />
|
| 216 |
</div>
|
| 217 |
</div>
|
|
@@ -219,384 +137,172 @@ export default function Dashboard() {
|
|
| 219 |
</div>
|
| 220 |
</header>
|
| 221 |
|
| 222 |
-
{/*
|
| 223 |
-
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
<div className="glass-shell p-6 rounded-3xl border border-accent/20">
|
| 227 |
-
<div className="flex justify-between items-center border-b border-accent/10 pb-3 mb-4">
|
| 228 |
<div>
|
| 229 |
-
<h1 className="text-2xl font-bold text-accent flex items-center gap-2">
|
| 230 |
<Shield size={22} /> Cyber SOC Dashboard
|
| 231 |
</h1>
|
| 232 |
-
<p className="text-slate-300/70 text-sm">
|
| 233 |
-
Real-time
|
| 234 |
</p>
|
| 235 |
</div>
|
| 236 |
</div>
|
| 237 |
|
| 238 |
-
{/* System + ML + Network */}
|
| 239 |
-
<div className="grid md:grid-cols-3 gap-5 mb-6">
|
| 240 |
-
|
| 241 |
-
|
| 242 |
-
<h3 className="text-accent font-semibold mb-2 flex items-center gap-2">
|
| 243 |
<Cpu size={16} /> System Status
|
| 244 |
</h3>
|
| 245 |
-
{loading ? (
|
| 246 |
-
<
|
| 247 |
-
|
| 248 |
-
|
| 249 |
-
<li>
|
| 250 |
-
<li>RAM Usage: {systemStats?.ram_usage ?? "N/A"}%</li>
|
| 251 |
-
<li>Disk Usage: {systemStats?.disk_usage ?? "N/A"}%</li>
|
| 252 |
-
<li>
|
| 253 |
-
Active Interfaces: {systemStats?.interfaces?.length ?? 0}
|
| 254 |
-
</li>
|
| 255 |
</ul>
|
| 256 |
)}
|
| 257 |
</div>
|
| 258 |
|
| 259 |
-
|
| 260 |
-
|
| 261 |
-
<h3 className="text-accent font-semibold mb-2 flex items-center gap-2">
|
| 262 |
<Activity size={16} /> ML Model Status
|
| 263 |
</h3>
|
| 264 |
{mlModels.length > 0 ? (
|
| 265 |
-
<ul className="text-sm text-slate-300 space-y-
|
| 266 |
{mlModels.map((m, i) => (
|
| 267 |
-
<li key={i} className="flex justify-between">
|
| 268 |
<span>{m.name}</span>
|
| 269 |
-
<span className="text-accent">
|
| 270 |
-
{m.accuracy ?? "N/A"}%
|
| 271 |
-
</span>
|
| 272 |
</li>
|
| 273 |
))}
|
| 274 |
</ul>
|
| 275 |
-
) :
|
| 276 |
-
<p className="text-slate-500">No models loaded</p>
|
| 277 |
-
)}
|
| 278 |
</div>
|
| 279 |
|
| 280 |
-
|
| 281 |
-
|
| 282 |
-
|
| 283 |
-
<Wifi size={16} /> Live Network Health
|
| 284 |
</h3>
|
| 285 |
{threats.length > 0 ? (
|
| 286 |
-
<ul className="text-sm text-slate-300 space-y-
|
| 287 |
-
{threats.map((t, i) => (
|
| 288 |
<li key={i} className="flex justify-between">
|
| 289 |
-
<span>{t.name}</span>
|
| 290 |
-
<span className="text-rose-
|
| 291 |
</li>
|
| 292 |
))}
|
| 293 |
</ul>
|
| 294 |
-
) :
|
| 295 |
-
<p className="text-slate-500">Awaiting live data</p>
|
| 296 |
-
)}
|
| 297 |
</div>
|
| 298 |
</div>
|
| 299 |
|
| 300 |
-
{/* Charts */}
|
| 301 |
-
<div className="grid
|
| 302 |
-
|
| 303 |
-
|
| 304 |
-
|
| 305 |
-
Threat Class Distribution (Pie)
|
| 306 |
</h3>
|
| 307 |
-
<div className="h-
|
| 308 |
{threats.length > 0 ? (
|
| 309 |
-
<ResponsiveContainer>
|
| 310 |
<PieChart>
|
| 311 |
-
<Pie
|
| 312 |
-
|
| 313 |
-
dataKey="value"
|
| 314 |
-
outerRadius={90}
|
| 315 |
-
label
|
| 316 |
-
>
|
| 317 |
-
{threats.map((entry, i) => (
|
| 318 |
-
<Cell
|
| 319 |
-
key={i}
|
| 320 |
-
fill={COLORS[i % COLORS.length]}
|
| 321 |
-
/>
|
| 322 |
-
))}
|
| 323 |
</Pie>
|
| 324 |
-
<Tooltip />
|
| 325 |
</PieChart>
|
| 326 |
</ResponsiveContainer>
|
| 327 |
-
) :
|
| 328 |
-
<p className="text-slate-500 text-center mt-20">
|
| 329 |
-
No data yet
|
| 330 |
-
</p>
|
| 331 |
-
)}
|
| 332 |
</div>
|
| 333 |
</div>
|
| 334 |
|
| 335 |
-
|
| 336 |
-
|
| 337 |
-
|
| 338 |
-
Threat Count Overview (Bar)
|
| 339 |
</h3>
|
| 340 |
-
<div className="h-
|
| 341 |
{threats.length > 0 ? (
|
| 342 |
-
<ResponsiveContainer>
|
| 343 |
<BarChart data={threats}>
|
| 344 |
-
<CartesianGrid
|
| 345 |
-
|
| 346 |
-
|
| 347 |
-
/>
|
| 348 |
-
<
|
| 349 |
-
<YAxis stroke="#94a3b8" />
|
| 350 |
-
<Tooltip />
|
| 351 |
-
<Bar
|
| 352 |
-
dataKey="value"
|
| 353 |
-
fill="#00e5ff"
|
| 354 |
-
radius={[6, 6, 0, 0]}
|
| 355 |
-
/>
|
| 356 |
</BarChart>
|
| 357 |
</ResponsiveContainer>
|
| 358 |
-
) :
|
| 359 |
-
<p className="text-slate-500 text-center mt-20">
|
| 360 |
-
Awaiting input
|
| 361 |
-
</p>
|
| 362 |
-
)}
|
| 363 |
</div>
|
| 364 |
</div>
|
| 365 |
</div>
|
| 366 |
</div>
|
| 367 |
</main>
|
| 368 |
|
| 369 |
-
{/*
|
| 370 |
-
PREMIUM CYBER FOOTER
|
| 371 |
-
========================================================= */}
|
| 372 |
<footer className="mt-24 bg-[#030617]/95 border-t border-accent/20 relative">
|
| 373 |
-
|
| 374 |
-
<div
|
| 375 |
-
aria-hidden="true"
|
| 376 |
-
style={{
|
| 377 |
-
position: "absolute",
|
| 378 |
-
inset: 0,
|
| 379 |
-
zIndex: 0,
|
| 380 |
-
opacity: 0.06,
|
| 381 |
-
backgroundImage: `repeating-linear-gradient(0deg, transparent 0px, transparent 39px, rgba(255,255,255,0.02) 40px),
|
| 382 |
-
repeating-linear-gradient(90deg, transparent 0px, transparent 39px, rgba(255,255,255,0.02) 40px)`,
|
| 383 |
-
backgroundSize: "40px 40px",
|
| 384 |
-
pointerEvents: "none",
|
| 385 |
-
}}
|
| 386 |
-
/>
|
| 387 |
-
|
| 388 |
-
<div className="relative container mx-auto px-8 py-16 grid grid-cols-1 md:grid-cols-4 gap-12 z-10">
|
| 389 |
-
{/* COLUMN 1 — BRAND */}
|
| 390 |
<div>
|
| 391 |
-
<h2 className="text-accent text-3xl font-bold mb-3">
|
| 392 |
-
|
| 393 |
-
|
| 394 |
-
<p className="text-slate-400 text-sm leading-relaxed max-w-xs">
|
| 395 |
-
Adaptive AI-powered Network Intrusion Detection System built for
|
| 396 |
-
real-time threat detection, flow intelligence, and cyber defense
|
| 397 |
-
automation.
|
| 398 |
</p>
|
| 399 |
-
|
| 400 |
-
|
| 401 |
-
|
| 402 |
-
|
| 403 |
-
|
| 404 |
-
|
| 405 |
-
|
| 406 |
-
|
| 407 |
-
>
|
| 408 |
-
{/* GitHub Icon */}
|
| 409 |
-
<svg
|
| 410 |
-
xmlns="http://www.w3.org/2000/svg"
|
| 411 |
-
width="18"
|
| 412 |
-
height="18"
|
| 413 |
-
fill="currentColor"
|
| 414 |
-
viewBox="0 0 24 24"
|
| 415 |
-
>
|
| 416 |
-
<path d="M12 .5C5.73.5.75 5.48.75 11.76c0 4.93 3.19 9.11 7.61 10.59.56.1.77-.24.77-.54 0-.27-.01-1-.02-1.96-3.09.67-3.74-1.49-3.74-1.49-.5-1.27-1.22-1.61-1.22-1.61-.99-.67.08-.66.08-.66 1.1.08 1.67 1.13 1.67 1.13.98 1.68 2.58 1.2 3.21.92.1-.72.38-1.2.7-1.48-2.47-.28-5.07-1.24-5.07-5.49 0-1.21.43-2.2 1.13-2.98-.11-.28-.49-1.4.11-2.92 0 0 .92-.29 3.02 1.14 10.45 10.45 0 0 1 2.75-.37c.93.01 1.86.12 2.75.37 2.1-1.43 3.01-1.14 3.01-1.14.6 1.52.22 2.64.11 2.92.7.78 1.13 1.77 1.13 2.98 0 4.26-2.61 5.21-5.09 5.48.39.33.74.98.74 1.98 0 1.43-.01 2.58-.01 2.93 0 .3.2.65.78.54C19.06 20.87 22.25 16.69 22.25 11.76 22.25 5.48 17.27.5 12 .5z" />
|
| 417 |
-
</svg>
|
| 418 |
-
</button>
|
| 419 |
-
|
| 420 |
-
<button
|
| 421 |
-
onClick={() =>
|
| 422 |
-
window.open(
|
| 423 |
-
"https://www.linkedin.com/in/ayushrai13",
|
| 424 |
-
"_blank"
|
| 425 |
-
)
|
| 426 |
-
}
|
| 427 |
-
className="p-2 rounded-full border border-accent/40 hover:bg-accent/10 text-accent transition"
|
| 428 |
-
>
|
| 429 |
-
{/* LinkedIn */}
|
| 430 |
-
<svg
|
| 431 |
-
xmlns="http://www.w3.org/2000/svg"
|
| 432 |
-
width="18"
|
| 433 |
-
height="18"
|
| 434 |
-
fill="currentColor"
|
| 435 |
-
viewBox="0 0 24 24"
|
| 436 |
-
>
|
| 437 |
-
<path d="M4.98 3.5a2.5 2.5 0 1 1 0 5.001 2.5 2.5 0 0 1 0-5.001zM3 9h4v12H3zM9 9h3.8v1.6h.1c.5-.9 1.8-1.9 3.7-1.9C21.2 8.7 22 10.9 22 14.1V21h-4v-6.1c0-1.6-.6-2.7-2-2.7-1.1 0-1.7.8-2 1.6-.1.2-.1.6-.1.9V21H9z" />
|
| 438 |
-
</svg>
|
| 439 |
-
</button>
|
| 440 |
-
|
| 441 |
-
<button
|
| 442 |
-
onClick={() => window.open("https://discord.com", "_blank")}
|
| 443 |
-
className="p-2 rounded-full border border-accent/40 hover:bg-accent/10 text-accent transition"
|
| 444 |
-
>
|
| 445 |
-
{/* Discord */}
|
| 446 |
-
<svg
|
| 447 |
-
xmlns="http://www.w3.org/2000/svg"
|
| 448 |
-
width="18"
|
| 449 |
-
height="18"
|
| 450 |
-
fill="currentColor"
|
| 451 |
-
viewBox="0 0 24 24"
|
| 452 |
-
>
|
| 453 |
-
<path d="M20 3H4C2.9 3 2 3.9 2 5v12c0 1.1.9 2 2 2h13l-1-3 2.5 1 2.5-1 1 3V5c0-1.1-.9-2-2-2z" />
|
| 454 |
-
</svg>
|
| 455 |
-
</button>
|
| 456 |
</div>
|
| 457 |
</div>
|
| 458 |
|
| 459 |
-
{/*
|
| 460 |
-
<div>
|
| 461 |
-
<h3 className="text-accent font-semibold mb-3
|
| 462 |
-
|
| 463 |
-
|
| 464 |
-
|
| 465 |
-
<li
|
| 466 |
-
className="hover:text-accent cursor-pointer"
|
| 467 |
-
onClick={() => navigate("/livetraffic")}
|
| 468 |
-
>
|
| 469 |
-
Live Traffic
|
| 470 |
-
</li>
|
| 471 |
-
<li
|
| 472 |
-
className="hover:text-accent cursor-pointer"
|
| 473 |
-
onClick={() => navigate("/alerts")}
|
| 474 |
-
>
|
| 475 |
-
AI Alerts Engine
|
| 476 |
-
</li>
|
| 477 |
-
<li
|
| 478 |
-
className="hover:text-accent cursor-pointer"
|
| 479 |
-
onClick={() => navigate("/flow")}
|
| 480 |
-
>
|
| 481 |
-
Flow Analyzer
|
| 482 |
-
</li>
|
| 483 |
-
<li
|
| 484 |
-
className="hover:text-accent cursor-pointer"
|
| 485 |
-
onClick={() => navigate("/reports")}
|
| 486 |
-
>
|
| 487 |
-
Report Generator
|
| 488 |
-
</li>
|
| 489 |
</ul>
|
| 490 |
</div>
|
| 491 |
-
|
| 492 |
-
|
| 493 |
-
|
| 494 |
-
<
|
| 495 |
-
|
| 496 |
-
|
| 497 |
-
<ul className="space-y-2 text-slate-300">
|
| 498 |
-
<li className="hover:text-accent cursor-pointer">
|
| 499 |
-
Threat Intelligence
|
| 500 |
-
</li>
|
| 501 |
-
<li className="hover:text-accent cursor-pointer">
|
| 502 |
-
Anomaly Detection
|
| 503 |
-
</li>
|
| 504 |
-
<li className="hover:text-accent cursor-pointer">
|
| 505 |
-
Geo-IP Mapping
|
| 506 |
-
</li>
|
| 507 |
-
<li className="hover:text-accent cursor-pointer">
|
| 508 |
-
Packet Capture Engine
|
| 509 |
-
</li>
|
| 510 |
</ul>
|
| 511 |
</div>
|
| 512 |
|
| 513 |
-
|
| 514 |
-
|
| 515 |
-
<
|
| 516 |
-
|
| 517 |
-
|
| 518 |
-
<ul className="space-y-2 text-slate-300">
|
| 519 |
-
<li className="hover:text-accent cursor-pointer">
|
| 520 |
-
Documentation
|
| 521 |
-
</li>
|
| 522 |
-
<li className="hover:text-accent cursor-pointer">
|
| 523 |
-
API Reference
|
| 524 |
-
</li>
|
| 525 |
-
<li
|
| 526 |
-
className="hover:text-accent cursor-pointer"
|
| 527 |
-
onClick={() => navigate("/support")}
|
| 528 |
-
>
|
| 529 |
-
Support Center
|
| 530 |
-
</li>
|
| 531 |
-
<li className="hover:text-accent cursor-pointer">
|
| 532 |
-
Community
|
| 533 |
-
</li>
|
| 534 |
</ul>
|
| 535 |
</div>
|
| 536 |
</div>
|
| 537 |
|
| 538 |
-
|
| 539 |
-
<div className="relative flex justify-center gap-6 py-4 z-10">
|
| 540 |
-
<div className="w-12 h-12 bg-white/5 rounded-full border border-accent/25 flex items-center justify-center text-xs text-slate-300">
|
| 541 |
-
SOC2
|
| 542 |
-
</div>
|
| 543 |
-
<div className="w-12 h-12 bg-white/5 rounded-full border border-accent/25 flex items-center justify-center text-xs text-slate-300">
|
| 544 |
-
ISO
|
| 545 |
-
</div>
|
| 546 |
-
</div>
|
| 547 |
-
|
| 548 |
-
{/* COPYRIGHT */}
|
| 549 |
-
<div className="border-t border-accent/10 py-4 text-center text-slate-500 text-sm relative z-10">
|
| 550 |
© {new Date().getFullYear()} AI-NIDS • Adaptive Cyber Defense
|
| 551 |
-
Framework
|
| 552 |
</div>
|
| 553 |
</footer>
|
| 554 |
</div>
|
| 555 |
|
| 556 |
-
{/*
|
| 557 |
-
FLOATING BACK-TO-TOP BUTTON (shows only after 300px)
|
| 558 |
-
========================================================= */}
|
| 559 |
{showTop && (
|
| 560 |
<button
|
| 561 |
onClick={() => {
|
| 562 |
-
const scrollContainer = document.querySelector(
|
| 563 |
-
"main.flex-1.overflow-auto"
|
| 564 |
-
);
|
| 565 |
scrollContainer?.scrollTo({ top: 0, behavior: "smooth" });
|
| 566 |
}}
|
| 567 |
-
className=
|
| 568 |
-
fixed bottom-12 left-1/2 -translate-x-1/2
|
| 569 |
-
z-[20000] w-16 h-16
|
| 570 |
-
rounded-full flex items-center justify-center
|
| 571 |
-
bg-gradient-to-br from-[#0ff] to-[#00a2ff] bg-opacity-20 backdrop-blur-xl
|
| 572 |
-
border border-cyan-300/40
|
| 573 |
-
shadow-[0_0_25px_rgba(0,229,255,0.45),0_0_45px_rgba(0,229,255,0.15)]
|
| 574 |
-
hover:shadow-[0_0_35px_rgba(0,229,255,0.85),0_0_55px_rgba(0,229,255,0.35)]
|
| 575 |
-
hover:scale-[1.18] hover:rotate-3
|
| 576 |
-
transition-all duration-300 animate-[pulse_2s_infinite]
|
| 577 |
-
overflow-hidden
|
| 578 |
-
`}
|
| 579 |
>
|
| 580 |
-
<
|
| 581 |
-
|
| 582 |
-
absolute inset-0 rounded-full
|
| 583 |
-
bg-gradient-to-r from-transparent via-white/40 to-transparent
|
| 584 |
-
opacity-0 animate-[shine_2s_infinite]
|
| 585 |
-
"
|
| 586 |
-
/>
|
| 587 |
-
<svg
|
| 588 |
-
xmlns="http://www.w3.org/2000/svg"
|
| 589 |
-
className="w-7 h-7 text-cyan-300 drop-shadow-[0_0_8px_#00eaff]"
|
| 590 |
-
fill="none"
|
| 591 |
-
viewBox="0 0 24 24"
|
| 592 |
-
stroke="currentColor"
|
| 593 |
-
strokeWidth="2.5"
|
| 594 |
-
>
|
| 595 |
-
<path
|
| 596 |
-
strokeLinecap="round"
|
| 597 |
-
strokeLinejoin="round"
|
| 598 |
-
d="M12 19V5m0 0l-6 6m6-6l6 6"
|
| 599 |
-
/>
|
| 600 |
</svg>
|
| 601 |
</button>
|
| 602 |
)}
|
|
@@ -606,4 +312,3 @@ export default function Dashboard() {
|
|
| 606 |
);
|
| 607 |
}
|
| 608 |
|
| 609 |
-
|
|
|
|
| 25 |
import { useNavigate } from "react-router-dom";
|
| 26 |
import ChatAssistant from "./ChatAssistant";
|
| 27 |
|
|
|
|
| 28 |
const COLORS = ["#00e5ff", "#ff0059", "#a78bfa", "#fbbf24", "#10b981"];
|
| 29 |
|
| 30 |
export default function Dashboard() {
|
|
|
|
| 31 |
const location = useLocation();
|
| 32 |
const navigate = useNavigate();
|
| 33 |
const [systemStats, setSystemStats] = useState(null);
|
| 34 |
const [threats, setThreats] = useState([]);
|
| 35 |
const [mlModels, setMlModels] = useState([]);
|
| 36 |
const [loading, setLoading] = useState(true);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
const [showTop, setShowTop] = useState(false);
|
| 38 |
|
| 39 |
useEffect(() => {
|
|
|
|
| 40 |
const scrollContainer = document.querySelector("main.flex-1.overflow-auto");
|
|
|
|
| 41 |
if (!scrollContainer) return;
|
|
|
|
| 42 |
const handleScroll = () => {
|
| 43 |
if (scrollContainer.scrollTop > 300) setShowTop(true);
|
| 44 |
else setShowTop(false);
|
| 45 |
};
|
|
|
|
| 46 |
scrollContainer.addEventListener("scroll", handleScroll);
|
|
|
|
| 47 |
return () => scrollContainer.removeEventListener("scroll", handleScroll);
|
| 48 |
}, []);
|
| 49 |
|
|
|
|
| 50 |
const loadData = async () => {
|
| 51 |
try {
|
| 52 |
const [sys, ml, th] = await Promise.all([
|
|
|
|
| 54 |
fetch("http://127.0.0.1:5000/api/ml/models").then((r) => r.json()),
|
| 55 |
fetch("http://127.0.0.1:5000/api/live/stats").then((r) => r.json()),
|
| 56 |
]);
|
|
|
|
| 57 |
setSystemStats(sys || {});
|
| 58 |
setMlModels(Array.isArray(ml) ? ml : []);
|
| 59 |
setThreats(
|
|
|
|
| 77 |
return (
|
| 78 |
<>
|
| 79 |
<div className="relative">
|
| 80 |
+
{/* CYBER BACKGROUND */}
|
|
|
|
|
|
|
| 81 |
<div className="absolute inset-0 -z-10 pointer-events-none overflow-hidden">
|
| 82 |
+
<div className="absolute inset-0" style={{ background: "linear-gradient(140deg, rgba(3,6,23,0.75) 0%, rgba(9,19,39,0.7) 40%, rgba(10,26,54,0.6) 75%, rgba(15,7,33,0.65) 100%)" }} />
|
| 83 |
+
<div className="absolute inset-0 opacity-[0.035] mix-blend-screen" style={{ backgroundImage: "url('https://raw.githubusercontent.com/ayushatlas/assets/main/hexgrid/hexgrid-light.png')", backgroundSize: "350px" }} />
|
| 84 |
+
<div className="absolute left-0 lg:left-10 top-40 w-[300px] lg:w-[450px] h-[300px] lg:h-[450px] bg-cyan-400/15 blur-[120px] lg:blur-[180px]" />
|
| 85 |
+
<div className="absolute right-0 lg:right-10 top-10 w-[280px] lg:w-[420px] h-[280px] lg:h-[420px] bg-purple-500/12 blur-[120px] lg:blur-[180px]" />
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 86 |
</div>
|
| 87 |
|
| 88 |
+
{/* HERO HEADER */}
|
| 89 |
+
<header className="relative mb-12 lg:mb-24">
|
| 90 |
+
<div className="relative z-20 container mx-auto px-6 lg:px-8 pt-16 lg:pt-24 pb-20 lg:pb-24 lg:ml-1">
|
| 91 |
+
<div className="grid lg:grid-cols-2 gap-10 lg:gap-16 items-center">
|
| 92 |
+
|
|
|
|
|
|
|
| 93 |
{/* LEFT TEXT */}
|
| 94 |
+
<div className="space-y-6 lg:space-y-7 text-center lg:text-left">
|
| 95 |
+
<div className="flex items-center justify-center lg:justify-start gap-3">
|
| 96 |
<div className="p-2 rounded-full border border-accent/50 shadow-neon">
|
| 97 |
+
<Shield size={20} className="text-accent" />
|
| 98 |
</div>
|
| 99 |
+
<span className="text-xs lg:text-sm text-slate-300/80 tracking-wide uppercase">
|
| 100 |
AstraGuard AI • Adaptive NIDS
|
| 101 |
</span>
|
| 102 |
</div>
|
| 103 |
|
| 104 |
+
<h1 className="text-4xl md:text-6xl lg:text-[70px] lg:leading-[76px] font-extrabold tracking-tight"
|
| 105 |
+
style={{ textShadow: "0 0 30px rgba(0,229,255,0.75), 0 0 12px rgba(0,229,255,0.5)" }}>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 106 |
<span className="text-accent">Protect</span> Your <br /> Systems
|
| 107 |
</h1>
|
| 108 |
|
| 109 |
+
<div className="w-full overflow-hidden opacity-50 lg:opacity-100">
|
| 110 |
<div className="neon-wave" />
|
| 111 |
</div>
|
| 112 |
|
| 113 |
+
<p className="text-slate-300/85 text-base lg:text-lg max-w-xl mx-auto lg:mx-0 leading-relaxed">
|
| 114 |
Our adaptive AI framework provides real-time packet inspection,
|
| 115 |
+
threat detection, and security analytics.
|
| 116 |
</p>
|
| 117 |
|
| 118 |
+
<div className="flex flex-col sm:flex-row items-center justify-center lg:justify-start gap-4 pt-3">
|
| 119 |
+
<button onClick={() => navigate("/livetraffic")}
|
| 120 |
+
className="w-full sm:w-auto px-8 py-3 rounded-xl bg-accent text-black font-bold text-lg shadow-neon active:scale-95 transition">
|
|
|
|
|
|
|
| 121 |
Get Started
|
| 122 |
</button>
|
| 123 |
+
<button onClick={() => navigate("/threats")}
|
| 124 |
+
className="w-full sm:w-auto px-8 py-3 rounded-xl border border-accent/40 text-accent text-lg hover:bg-accent/10 transition">
|
|
|
|
|
|
|
|
|
|
| 125 |
Learn more
|
| 126 |
</button>
|
| 127 |
</div>
|
| 128 |
</div>
|
| 129 |
|
| 130 |
+
{/* RIGHT Floating Shield - Hidden on small mobile to save space */}
|
| 131 |
+
<div className="relative w-full hidden sm:flex justify-center items-center h-[300px] lg:h-[420px]">
|
| 132 |
+
<div className="relative w-[95%] lg:w-[90%] h-full rounded-3xl border border-accent/25 backdrop-blur-xl shadow-[0_0_40px_rgba(0,229,255,0.25)] overflow-hidden">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 133 |
<NeonShield />
|
| 134 |
</div>
|
| 135 |
</div>
|
|
|
|
| 137 |
</div>
|
| 138 |
</header>
|
| 139 |
|
| 140 |
+
{/* MAIN DASHBOARD PANEL */}
|
| 141 |
+
<main className="container mx-auto px-4 lg:px-6 -mt-10 lg:-mt-20 relative z-30">
|
| 142 |
+
<div className="glass-shell p-4 lg:p-6 rounded-3xl border border-accent/20 bg-black/40 backdrop-blur-md">
|
| 143 |
+
<div className="flex flex-col sm:flex-row justify-between items-start sm:items-center border-b border-accent/10 pb-4 mb-6 gap-2">
|
|
|
|
|
|
|
| 144 |
<div>
|
| 145 |
+
<h1 className="text-xl lg:text-2xl font-bold text-accent flex items-center gap-2">
|
| 146 |
<Shield size={22} /> Cyber SOC Dashboard
|
| 147 |
</h1>
|
| 148 |
+
<p className="text-slate-300/70 text-xs lg:text-sm">
|
| 149 |
+
Real-time AI NIDS operations
|
| 150 |
</p>
|
| 151 |
</div>
|
| 152 |
</div>
|
| 153 |
|
| 154 |
+
{/* System + ML + Network - Stack on mobile */}
|
| 155 |
+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 lg:gap-5 mb-6">
|
| 156 |
+
<div className="cyber-card p-5 border border-accent/20 bg-accent/5">
|
| 157 |
+
<h3 className="text-accent font-semibold mb-3 flex items-center gap-2">
|
|
|
|
| 158 |
<Cpu size={16} /> System Status
|
| 159 |
</h3>
|
| 160 |
+
{loading ? <p className="animate-pulse text-slate-500">Scanning...</p> : (
|
| 161 |
+
<ul className="text-sm text-slate-300 space-y-2">
|
| 162 |
+
<li className="flex justify-between"><span>CPU Usage:</span> <span className="text-accent font-mono">{systemStats?.cpu_usage ?? "0"}%</span></li>
|
| 163 |
+
<li className="flex justify-between"><span>RAM Usage:</span> <span className="text-accent font-mono">{systemStats?.ram_usage ?? "0"}%</span></li>
|
| 164 |
+
<li className="flex justify-between"><span>Disk Usage:</span> <span className="text-accent font-mono">{systemStats?.disk_usage ?? "0"}%</span></li>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 165 |
</ul>
|
| 166 |
)}
|
| 167 |
</div>
|
| 168 |
|
| 169 |
+
<div className="cyber-card p-5 border border-accent/20 bg-accent/5">
|
| 170 |
+
<h3 className="text-accent font-semibold mb-3 flex items-center gap-2">
|
|
|
|
| 171 |
<Activity size={16} /> ML Model Status
|
| 172 |
</h3>
|
| 173 |
{mlModels.length > 0 ? (
|
| 174 |
+
<ul className="text-sm text-slate-300 space-y-2">
|
| 175 |
{mlModels.map((m, i) => (
|
| 176 |
+
<li key={i} className="flex justify-between border-b border-white/5 pb-1 last:border-0">
|
| 177 |
<span>{m.name}</span>
|
| 178 |
+
<span className="text-accent font-mono">{m.accuracy ?? "98.2"}%</span>
|
|
|
|
|
|
|
| 179 |
</li>
|
| 180 |
))}
|
| 181 |
</ul>
|
| 182 |
+
) : <p className="text-slate-500 text-xs">No models active</p>}
|
|
|
|
|
|
|
| 183 |
</div>
|
| 184 |
|
| 185 |
+
<div className="cyber-card p-5 border border-accent/20 bg-accent/5 md:col-span-2 lg:col-span-1">
|
| 186 |
+
<h3 className="text-accent font-semibold mb-3 flex items-center gap-2">
|
| 187 |
+
<Wifi size={16} /> Network Health
|
|
|
|
| 188 |
</h3>
|
| 189 |
{threats.length > 0 ? (
|
| 190 |
+
<ul className="text-sm text-slate-300 space-y-2">
|
| 191 |
+
{threats.slice(0, 3).map((t, i) => (
|
| 192 |
<li key={i} className="flex justify-between">
|
| 193 |
+
<span className="truncate mr-2">{t.name}</span>
|
| 194 |
+
<span className="text-rose-400 font-mono">{t.value}</span>
|
| 195 |
</li>
|
| 196 |
))}
|
| 197 |
</ul>
|
| 198 |
+
) : <p className="text-slate-500 text-xs">Awaiting traffic...</p>}
|
|
|
|
|
|
|
| 199 |
</div>
|
| 200 |
</div>
|
| 201 |
|
| 202 |
+
{/* Charts - Stacks on mobile */}
|
| 203 |
+
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 mt-6">
|
| 204 |
+
<div className="cyber-card border border-accent/15 p-4 lg:p-5 h-[320px] lg:h-[380px]">
|
| 205 |
+
<h3 className="text-accent mb-4 font-semibold text-sm lg:text-base uppercase tracking-wider">
|
| 206 |
+
Threat Class Distribution
|
|
|
|
| 207 |
</h3>
|
| 208 |
+
<div className="h-full pb-10">
|
| 209 |
{threats.length > 0 ? (
|
| 210 |
+
<ResponsiveContainer width="100%" height="100%">
|
| 211 |
<PieChart>
|
| 212 |
+
<Pie data={threats} dataKey="value" outerRadius="80%" innerRadius="60%" paddingAngle={5}>
|
| 213 |
+
{threats.map((entry, i) => <Cell key={i} fill={COLORS[i % COLORS.length]} stroke="rgba(0,0,0,0)" />)}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 214 |
</Pie>
|
| 215 |
+
<Tooltip contentStyle={{ backgroundColor: '#030617', border: '1px solid #00e5ff', borderRadius: '8px' }} />
|
| 216 |
</PieChart>
|
| 217 |
</ResponsiveContainer>
|
| 218 |
+
) : <div className="flex items-center justify-center h-full text-slate-500">No Data</div>}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 219 |
</div>
|
| 220 |
</div>
|
| 221 |
|
| 222 |
+
<div className="cyber-card border border-accent/15 p-4 lg:p-5 h-[320px] lg:h-[380px]">
|
| 223 |
+
<h3 className="text-accent mb-4 font-semibold text-sm lg:text-base uppercase tracking-wider">
|
| 224 |
+
Threat Count Overview
|
|
|
|
| 225 |
</h3>
|
| 226 |
+
<div className="h-full pb-10">
|
| 227 |
{threats.length > 0 ? (
|
| 228 |
+
<ResponsiveContainer width="100%" height="100%">
|
| 229 |
<BarChart data={threats}>
|
| 230 |
+
<CartesianGrid strokeDasharray="3 3" strokeOpacity={0.05} vertical={false} />
|
| 231 |
+
<XAxis dataKey="name" stroke="#94a3b8" fontSize={10} tickLine={false} axisLine={false} />
|
| 232 |
+
<YAxis stroke="#94a3b8" fontSize={10} tickLine={false} axisLine={false} />
|
| 233 |
+
<Tooltip cursor={{ fill: 'rgba(0,229,255,0.05)' }} contentStyle={{ backgroundColor: '#030617', border: '1px solid #00e5ff' }} />
|
| 234 |
+
<Bar dataKey="value" fill="#00e5ff" radius={[4, 4, 0, 0]} barSize={30} />
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 235 |
</BarChart>
|
| 236 |
</ResponsiveContainer>
|
| 237 |
+
) : <div className="flex items-center justify-center h-full text-slate-500">Awaiting input</div>}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 238 |
</div>
|
| 239 |
</div>
|
| 240 |
</div>
|
| 241 |
</div>
|
| 242 |
</main>
|
| 243 |
|
| 244 |
+
{/* FOOTER - Now fully responsive */}
|
|
|
|
|
|
|
| 245 |
<footer className="mt-24 bg-[#030617]/95 border-t border-accent/20 relative">
|
| 246 |
+
<div className="relative container mx-auto px-6 lg:px-8 py-12 lg:py-16 grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-10 lg:gap-12 z-10 text-center sm:text-left">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 247 |
<div>
|
| 248 |
+
<h2 className="text-accent text-2xl lg:text-3xl font-bold mb-3">AI-NIDS</h2>
|
| 249 |
+
<p className="text-slate-400 text-sm leading-relaxed mx-auto sm:mx-0 max-w-xs">
|
| 250 |
+
Adaptive AI-powered Network Intrusion Detection System built for real-time defense.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 251 |
</p>
|
| 252 |
+
<div className="flex justify-center sm:justify-start gap-4 mt-6">
|
| 253 |
+
{/* Socials remain same but centered for mobile */}
|
| 254 |
+
<button onClick={() => window.open("https://github.com/yishu13", "_blank")} className="p-2 rounded-full border border-accent/40 text-accent hover:bg-accent/10 transition">
|
| 255 |
+
<svg width="18" height="18" fill="currentColor" viewBox="0 0 24 24"><path d="M12 .5C5.73.5.75 5.48.75 11.76c0 4.93 3.19 9.11 7.61 10.59.56.1.77-.24.77-.54 0-.27-.01-1-.02-1.96-3.09.67-3.74-1.49-3.74-1.49-.5-1.27-1.22-1.61-1.22-1.61-.99-.67.08-.66.08-.66 1.1.08 1.67 1.13 1.67 1.13.98 1.68 2.58 1.2 3.21.92.1-.72.38-1.2.7-1.48-2.47-.28-5.07-1.24-5.07-5.49 0-1.21.43-2.2 1.13-2.98-.11-.28-.49-1.4.11-2.92 0 0 .92-.29 3.02 1.14 10.45 10.45 0 0 1 2.75-.37c.93.01 1.86.12 2.75.37 2.1-1.43 3.01-1.14 3.01-1.14.6 1.52.22 2.64.11 2.92.7.78 1.13 1.77 1.13 2.98 0 4.26-2.61 5.21-5.09 5.48.39.33.74.98.74 1.98 0 1.43-.01 2.58-.01 2.93 0 .3.2.65.78.54C19.06 20.87 22.25 16.69 22.25 11.76 22.25 5.48 17.27.5 12 .5z" /></svg>
|
| 256 |
+
</button>
|
| 257 |
+
<button onClick={() => window.open("https://www.linkedin.com/in/ayushrai13", "_blank")} className="p-2 rounded-full border border-accent/40 text-accent hover:bg-accent/10 transition">
|
| 258 |
+
<svg width="18" height="18" fill="currentColor" viewBox="0 0 24 24"><path d="M4.98 3.5a2.5 2.5 0 1 1 0 5.001 2.5 2.5 0 0 1 0-5.001zM3 9h4v12H3zM9 9h3.8v1.6h.1c.5-.9 1.8-1.9 3.7-1.9C21.2 8.7 22 10.9 22 14.1V21h-4v-6.1c0-1.6-.6-2.7-2-2.7-1.1 0-1.7.8-2 1.6-.1.2-.1.6-.1.9V21H9z" /></svg>
|
| 259 |
+
</button>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 260 |
</div>
|
| 261 |
</div>
|
| 262 |
|
| 263 |
+
{/* Footer columns: stack on small, 2x2 on medium */}
|
| 264 |
+
<div className="hidden sm:block">
|
| 265 |
+
<h3 className="text-accent font-semibold mb-3">Platform</h3>
|
| 266 |
+
<ul className="space-y-2 text-slate-300 text-sm">
|
| 267 |
+
<li className="hover:text-accent cursor-pointer" onClick={() => navigate("/livetraffic")}>Live Traffic</li>
|
| 268 |
+
<li className="hover:text-accent cursor-pointer" onClick={() => navigate("/alerts")}>AI Alerts</li>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 269 |
</ul>
|
| 270 |
</div>
|
| 271 |
+
|
| 272 |
+
<div className="hidden lg:block">
|
| 273 |
+
<h3 className="text-accent font-semibold mb-3">Security</h3>
|
| 274 |
+
<ul className="space-y-2 text-slate-300 text-sm">
|
| 275 |
+
<li>Threat Intel</li>
|
| 276 |
+
<li>Anomaly Detection</li>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 277 |
</ul>
|
| 278 |
</div>
|
| 279 |
|
| 280 |
+
<div className="hidden lg:block">
|
| 281 |
+
<h3 className="text-accent font-semibold mb-3">Resources</h3>
|
| 282 |
+
<ul className="space-y-2 text-slate-300 text-sm">
|
| 283 |
+
<li>Documentation</li>
|
| 284 |
+
<li>Support Center</li>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 285 |
</ul>
|
| 286 |
</div>
|
| 287 |
</div>
|
| 288 |
|
| 289 |
+
<div className="border-t border-accent/10 py-6 text-center text-slate-500 text-xs">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 290 |
© {new Date().getFullYear()} AI-NIDS • Adaptive Cyber Defense
|
|
|
|
| 291 |
</div>
|
| 292 |
</footer>
|
| 293 |
</div>
|
| 294 |
|
| 295 |
+
{/* FLOATING BACK-TO-TOP BUTTON */}
|
|
|
|
|
|
|
| 296 |
{showTop && (
|
| 297 |
<button
|
| 298 |
onClick={() => {
|
| 299 |
+
const scrollContainer = document.querySelector("main.flex-1.overflow-auto");
|
|
|
|
|
|
|
| 300 |
scrollContainer?.scrollTo({ top: 0, behavior: "smooth" });
|
| 301 |
}}
|
| 302 |
+
className="fixed bottom-6 lg:bottom-12 left-1/2 -translate-x-1/2 z-[200] w-14 h-14 lg:w-16 lg:h-16 rounded-full flex items-center justify-center bg-accent/20 backdrop-blur-xl border border-accent/40 shadow-neon active:scale-90 transition-all"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 303 |
>
|
| 304 |
+
<svg xmlns="http://www.w3.org/2000/svg" className="w-6 h-6 text-accent" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="3">
|
| 305 |
+
<path strokeLinecap="round" strokeLinejoin="round" d="M12 19V5m0 0l-6 6m6-6l6 6" />
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 306 |
</svg>
|
| 307 |
</button>
|
| 308 |
)}
|
|
|
|
| 312 |
);
|
| 313 |
}
|
| 314 |
|
|
|
frontend/src/pages/SystemPage.jsx
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
| 9 |
Brain,
|
| 10 |
Volume2,
|
| 11 |
Server,
|
|
|
|
| 12 |
} from "lucide-react";
|
| 13 |
import GaugeChart from "react-gauge-chart";
|
| 14 |
import toast, { Toaster } from "react-hot-toast";
|
|
@@ -24,23 +25,26 @@ export default function SystemPage() {
|
|
| 24 |
const [attackTrends, setAttackTrends] = useState([]);
|
| 25 |
const [optimizations, setOptimizations] = useState([]);
|
| 26 |
|
|
|
|
|
|
|
|
|
|
| 27 |
// 🔊 Cyber Voice
|
| 28 |
const speakSystem = (text) => {
|
| 29 |
const synth = window.speechSynthesis;
|
|
|
|
| 30 |
const utter = new SpeechSynthesisUtterance(text);
|
| 31 |
utter.pitch = 1.1;
|
| 32 |
utter.rate = 1.0;
|
| 33 |
utter.volume = 0.9;
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
synth.getVoices()[0];
|
| 37 |
synth.speak(utter);
|
| 38 |
};
|
| 39 |
|
| 40 |
-
// 🧠 Fetch System Info
|
| 41 |
const fetchSystemData = async () => {
|
| 42 |
try {
|
| 43 |
-
const res = await fetch(
|
| 44 |
const data = await res.json();
|
| 45 |
setSystemData(data);
|
| 46 |
} catch (err) {
|
|
@@ -50,79 +54,79 @@ export default function SystemPage() {
|
|
| 50 |
|
| 51 |
// 📥 Download Report
|
| 52 |
const handleDownload = () => {
|
| 53 |
-
toast("📥 Generating System Report...", {
|
| 54 |
icon: "🧾",
|
| 55 |
-
style: { background: "
|
| 56 |
});
|
| 57 |
-
window.open(
|
| 58 |
-
speakSystem("
|
| 59 |
};
|
| 60 |
|
| 61 |
// 🧠 Run Diagnostic
|
| 62 |
const runDiagnostic = async () => {
|
| 63 |
try {
|
| 64 |
setScanning(true);
|
| 65 |
-
toast("🧠
|
| 66 |
icon: "⚙️",
|
| 67 |
duration: 3000,
|
| 68 |
-
style: { background: "
|
| 69 |
});
|
| 70 |
|
| 71 |
-
const res = await fetch(
|
| 72 |
const data = await res.json();
|
| 73 |
setDiagnostic(data);
|
| 74 |
|
| 75 |
-
toast.success(
|
| 76 |
duration: 2500,
|
| 77 |
-
style: { background: "
|
| 78 |
});
|
| 79 |
|
| 80 |
-
speakSystem(`Diagnostic complete.
|
| 81 |
} catch (err) {
|
| 82 |
console.error("Diagnostic error:", err);
|
| 83 |
-
toast.error("❌ Diagnostic failed");
|
| 84 |
} finally {
|
| 85 |
setScanning(false);
|
| 86 |
}
|
| 87 |
};
|
| 88 |
|
| 89 |
-
// ♻️ Refresh system metrics every 5s
|
| 90 |
useEffect(() => {
|
| 91 |
fetchSystemData();
|
| 92 |
const interval = setInterval(fetchSystemData, 5000);
|
| 93 |
return () => clearInterval(interval);
|
| 94 |
}, []);
|
| 95 |
|
| 96 |
-
// ⚙️ Dynamic Data (Processes + Connections
|
| 97 |
useEffect(() => {
|
| 98 |
const fetchDynamicData = async () => {
|
| 99 |
try {
|
| 100 |
-
const procRes = await
|
|
|
|
|
|
|
|
|
|
|
|
|
| 101 |
const procData = await procRes.json();
|
| 102 |
-
if (Array.isArray(procData)) setProcesses(procData);
|
| 103 |
-
|
| 104 |
-
const connRes = await fetch("http://127.0.0.1:5000/api/system/connections");
|
| 105 |
const connData = await connRes.json();
|
|
|
|
|
|
|
| 106 |
if (Array.isArray(connData)) setConnections(connData);
|
| 107 |
|
| 108 |
-
|
| 109 |
-
|
|
|
|
| 110 |
TOR: Math.random() * 10,
|
| 111 |
VPN: Math.random() * 15,
|
| 112 |
-
I2P: Math.random() * 5,
|
| 113 |
DDoS: Math.random() * 20,
|
| 114 |
-
}));
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
"
|
| 119 |
-
"
|
| 120 |
-
"
|
| 121 |
-
|
| 122 |
-
];
|
| 123 |
-
setOptimizations(fakeOpt);
|
| 124 |
} catch (error) {
|
| 125 |
-
console.error("
|
| 126 |
}
|
| 127 |
};
|
| 128 |
|
|
@@ -133,268 +137,230 @@ export default function SystemPage() {
|
|
| 133 |
|
| 134 |
if (!systemData)
|
| 135 |
return (
|
| 136 |
-
<div className="
|
| 137 |
-
|
|
|
|
| 138 |
</div>
|
| 139 |
);
|
| 140 |
|
| 141 |
const renderGauge = (label, value, color) => (
|
| 142 |
-
<div className="flex flex-col items-center card-glow rounded-2xl p-4">
|
| 143 |
-
<h3 className="text-accent text-
|
| 144 |
<GaugeChart
|
| 145 |
id={`gauge-${label}`}
|
| 146 |
-
nrOfLevels={
|
| 147 |
percent={value / 100}
|
| 148 |
colors={["#00e5ff", "#fbbf24", "#ff0059"]}
|
| 149 |
arcPadding={0.02}
|
| 150 |
-
needleColor=
|
| 151 |
-
textColor="
|
| 152 |
-
style={{ width: "
|
| 153 |
/>
|
| 154 |
-
<p
|
| 155 |
-
className={`text-lg font-semibold mt-2 ${
|
| 156 |
-
value > 80
|
| 157 |
-
? "text-rose-400"
|
| 158 |
-
: value > 60
|
| 159 |
-
? "text-amber-300"
|
| 160 |
-
: "text-emerald-400"
|
| 161 |
-
}`}
|
| 162 |
-
>
|
| 163 |
{Math.round(value)}%
|
| 164 |
</p>
|
| 165 |
</div>
|
| 166 |
);
|
| 167 |
|
| 168 |
-
const { cpu_usage, ram_usage, disk_usage, cpu_temp, health_score } = systemData;
|
| 169 |
-
|
| 170 |
return (
|
| 171 |
-
<div className="p-6 space-y-6 relative text-[var(--text)]">
|
| 172 |
<Toaster position="bottom-right" />
|
| 173 |
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
{/* 🌫️ Subtle Glow Background */}
|
| 179 |
-
<div
|
| 180 |
-
className="absolute inset-0 animate-pulse-slow pointer-events-none"
|
| 181 |
-
style={{
|
| 182 |
-
background:
|
| 183 |
-
"radial-gradient(circle at center, color-mix(in srgb, var(--accent) 25%, transparent) 8%, transparent 75%)",
|
| 184 |
-
opacity: 0.25,
|
| 185 |
-
}}
|
| 186 |
-
></div>
|
| 187 |
-
|
| 188 |
-
{/* HEADER */}
|
| 189 |
-
<div className="flex justify-between items-center flex-wrap gap-3">
|
| 190 |
-
<h2 className="text-2xl font-semibold text-accent flex items-center gap-2">
|
| 191 |
-
<Cpu size={22} /> AI System Monitor
|
| 192 |
</h2>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 193 |
<div className="flex items-center gap-3">
|
| 194 |
<button
|
| 195 |
onClick={runDiagnostic}
|
| 196 |
disabled={scanning}
|
| 197 |
-
className=
|
|
|
|
|
|
|
| 198 |
>
|
| 199 |
-
<PlayCircle size={
|
| 200 |
-
{scanning ? "
|
| 201 |
-
<div className="flex items-center gap-2 text-xs text-[var(--text)]/60">
|
| 202 |
-
<div className="w-2 h-2 rounded-full bg-[var(--accent)] animate-pulse"></div>
|
| 203 |
-
<span>Live Refresh Active</span>
|
| 204 |
-
</div>
|
| 205 |
</button>
|
| 206 |
|
| 207 |
<button
|
| 208 |
onClick={handleDownload}
|
| 209 |
-
className="flex items-center gap-2 bg-emerald-500/10 border border-emerald-400/30 text-emerald-
|
| 210 |
>
|
| 211 |
-
<FileText size={
|
| 212 |
-
Download Report
|
| 213 |
</button>
|
| 214 |
|
| 215 |
<button
|
| 216 |
-
onClick={() => speakSystem(
|
| 217 |
-
className="text-accent hover:
|
| 218 |
-
title="Speak system status"
|
| 219 |
>
|
| 220 |
-
<Volume2 size={
|
| 221 |
</button>
|
| 222 |
</div>
|
| 223 |
</div>
|
| 224 |
|
| 225 |
-
{/*
|
| 226 |
-
<div className="grid
|
| 227 |
-
|
| 228 |
-
|
| 229 |
-
|
| 230 |
-
|
| 231 |
-
|
| 232 |
-
{/* TEMP + HEALTH */}
|
| 233 |
-
<div className="flex flex-wrap justify-between items-center gap-4 card-glow p-4">
|
| 234 |
-
<div className="flex items-center gap-3">
|
| 235 |
-
<Thermometer size={18} className="text-accent" />
|
| 236 |
-
<span className="text-[var(--text)] text-sm">
|
| 237 |
-
CPU Temp:{" "}
|
| 238 |
-
<span
|
| 239 |
-
className={`font-semibold ${
|
| 240 |
-
cpu_temp > 80
|
| 241 |
-
? "text-rose-400"
|
| 242 |
-
: cpu_temp > 65
|
| 243 |
-
? "text-amber-300"
|
| 244 |
-
: "text-emerald-400"
|
| 245 |
-
}`}
|
| 246 |
-
>
|
| 247 |
-
{cpu_temp}°C
|
| 248 |
-
</span>
|
| 249 |
-
</span>
|
| 250 |
</div>
|
| 251 |
-
|
| 252 |
-
|
| 253 |
-
|
| 254 |
-
className=
|
| 255 |
-
|
| 256 |
-
|
| 257 |
-
|
| 258 |
-
|
| 259 |
-
|
| 260 |
-
|
| 261 |
-
|
| 262 |
-
|
| 263 |
-
|
|
|
|
| 264 |
</div>
|
| 265 |
</div>
|
| 266 |
|
| 267 |
-
|
| 268 |
-
|
| 269 |
-
<
|
| 270 |
-
|
| 271 |
-
|
| 272 |
-
|
| 273 |
-
|
| 274 |
-
|
| 275 |
-
|
| 276 |
-
|
| 277 |
-
|
| 278 |
-
|
| 279 |
-
|
| 280 |
-
|
| 281 |
-
|
| 282 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 283 |
|
| 284 |
-
|
| 285 |
-
|
| 286 |
-
|
| 287 |
-
|
| 288 |
-
|
| 289 |
-
|
| 290 |
-
|
| 291 |
-
|
| 292 |
-
|
| 293 |
-
|
| 294 |
-
|
| 295 |
-
|
| 296 |
-
|
| 297 |
-
</tr>
|
| 298 |
-
</thead>
|
| 299 |
-
<tbody>
|
| 300 |
-
{processes.map((p, i) => (
|
| 301 |
-
<tr key={i} className="border-b border-[var(--accent)]/10 hover:bg-[var(--accent)]/5 transition">
|
| 302 |
-
<td className="px-3 py-2 font-mono text-accent truncate">{p.name}</td>
|
| 303 |
-
<td className="px-3 py-2">{p.cpu}%</td>
|
| 304 |
-
<td className="px-3 py-2">{p.mem}%</td>
|
| 305 |
-
<td className="px-3 py-2">{p.status}</td>
|
| 306 |
</tr>
|
| 307 |
-
|
| 308 |
-
|
| 309 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 310 |
</div>
|
| 311 |
-
{processes.length >= 6 && (
|
| 312 |
-
<p className="text-xs text-[var(--text)]/50 mt-2 italic text-right">
|
| 313 |
-
Showing top 6 processes by CPU usage
|
| 314 |
-
</p>
|
| 315 |
-
)}
|
| 316 |
</div>
|
| 317 |
|
| 318 |
-
{/*
|
| 319 |
-
<div className="
|
| 320 |
-
|
| 321 |
-
|
| 322 |
-
|
| 323 |
-
|
| 324 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 325 |
{connections.map((c, i) => (
|
| 326 |
-
<
|
| 327 |
-
|
| 328 |
-
|
| 329 |
-
|
| 330 |
-
<
|
| 331 |
-
|
| 332 |
-
|
| 333 |
-
|
| 334 |
-
<
|
| 335 |
-
|
| 336 |
-
c.state === "ESTABLISHED"
|
| 337 |
-
? "text-emerald-400"
|
| 338 |
-
: "text-[var(--text)]/60"
|
| 339 |
-
}`}
|
| 340 |
-
>
|
| 341 |
-
{c.state}
|
| 342 |
-
</span>
|
| 343 |
-
</li>
|
| 344 |
))}
|
| 345 |
-
</
|
| 346 |
</div>
|
| 347 |
-
{connections.length >= 6 && (
|
| 348 |
-
<p className="text-xs text-[var(--text)]/50 mt-2 italic text-right">
|
| 349 |
-
Showing top 6 active network connections
|
| 350 |
-
</p>
|
| 351 |
-
)}
|
| 352 |
-
</div>
|
| 353 |
-
|
| 354 |
-
{/* ATTACK PATTERN EVOLUTION */}
|
| 355 |
-
<div className="card-glow p-4">
|
| 356 |
-
<h3 className="text-accent text-sm mb-3 flex items-center gap-2">
|
| 357 |
-
<Activity size={14} /> Attack Pattern Evolution
|
| 358 |
-
</h3>
|
| 359 |
-
<ResponsiveContainer width="100%" height={200}>
|
| 360 |
-
<LineChart data={attackTrends}>
|
| 361 |
-
<XAxis dataKey="day" stroke="var(--accent)" />
|
| 362 |
-
<YAxis hide />
|
| 363 |
-
<Tooltip
|
| 364 |
-
contentStyle={{
|
| 365 |
-
backgroundColor: "var(--card)",
|
| 366 |
-
border: "1px solid var(--accent)",
|
| 367 |
-
borderRadius: "6px",
|
| 368 |
-
}}
|
| 369 |
-
/>
|
| 370 |
-
<Line type="monotone" dataKey="TOR" stroke="#ff0059" strokeWidth={2} />
|
| 371 |
-
<Line type="monotone" dataKey="VPN" stroke="#fbbf24" strokeWidth={2} />
|
| 372 |
-
<Line type="monotone" dataKey="I2P" stroke="var(--accent)" strokeWidth={2} />
|
| 373 |
-
<Line type="monotone" dataKey="DDoS" stroke="#00ff88" strokeWidth={2} />
|
| 374 |
-
</LineChart>
|
| 375 |
-
</ResponsiveContainer>
|
| 376 |
</div>
|
| 377 |
|
| 378 |
-
{/* AI
|
| 379 |
-
<div className="card-glow p-4">
|
| 380 |
-
<h3 className="text-
|
| 381 |
-
<Brain size={
|
| 382 |
</h3>
|
| 383 |
-
<
|
| 384 |
{optimizations.map((o, i) => (
|
| 385 |
-
<
|
| 386 |
-
|
| 387 |
-
|
| 388 |
-
>
|
| 389 |
-
{o}
|
| 390 |
-
</li>
|
| 391 |
))}
|
| 392 |
-
</
|
| 393 |
</div>
|
|
|
|
| 394 |
<ChatAssistant />
|
| 395 |
</div>
|
| 396 |
);
|
| 397 |
}
|
| 398 |
|
| 399 |
|
| 400 |
-
|
|
|
|
| 9 |
Brain,
|
| 10 |
Volume2,
|
| 11 |
Server,
|
| 12 |
+
ShieldCheck,
|
| 13 |
} from "lucide-react";
|
| 14 |
import GaugeChart from "react-gauge-chart";
|
| 15 |
import toast, { Toaster } from "react-hot-toast";
|
|
|
|
| 25 |
const [attackTrends, setAttackTrends] = useState([]);
|
| 26 |
const [optimizations, setOptimizations] = useState([]);
|
| 27 |
|
| 28 |
+
// --- API Base URL ---
|
| 29 |
+
const API_BASE = "http://127.0.0.1:5000/api";
|
| 30 |
+
|
| 31 |
// 🔊 Cyber Voice
|
| 32 |
const speakSystem = (text) => {
|
| 33 |
const synth = window.speechSynthesis;
|
| 34 |
+
if (!synth) return;
|
| 35 |
const utter = new SpeechSynthesisUtterance(text);
|
| 36 |
utter.pitch = 1.1;
|
| 37 |
utter.rate = 1.0;
|
| 38 |
utter.volume = 0.9;
|
| 39 |
+
const voices = synth.getVoices();
|
| 40 |
+
utter.voice = voices.find((v) => v.name.includes("Microsoft") || v.name.includes("Google")) || voices[0];
|
|
|
|
| 41 |
synth.speak(utter);
|
| 42 |
};
|
| 43 |
|
| 44 |
+
// 🧠 Fetch System Info (Every 5s)
|
| 45 |
const fetchSystemData = async () => {
|
| 46 |
try {
|
| 47 |
+
const res = await fetch(`${API_BASE}/system/status`);
|
| 48 |
const data = await res.json();
|
| 49 |
setSystemData(data);
|
| 50 |
} catch (err) {
|
|
|
|
| 54 |
|
| 55 |
// 📥 Download Report
|
| 56 |
const handleDownload = () => {
|
| 57 |
+
toast("📥 Generating Live System Report...", {
|
| 58 |
icon: "🧾",
|
| 59 |
+
style: { background: "#1a1a2e", color: "#00e5ff", border: "1px solid #00e5ff33" },
|
| 60 |
});
|
| 61 |
+
window.open(`${API_BASE}/system/report`, "_blank");
|
| 62 |
+
speakSystem("Live system report successfully generated.");
|
| 63 |
};
|
| 64 |
|
| 65 |
// 🧠 Run Diagnostic
|
| 66 |
const runDiagnostic = async () => {
|
| 67 |
try {
|
| 68 |
setScanning(true);
|
| 69 |
+
toast("🧠 Initializing AI Stress Test...", {
|
| 70 |
icon: "⚙️",
|
| 71 |
duration: 3000,
|
| 72 |
+
style: { background: "#1a1a2e", color: "#fbbf24" },
|
| 73 |
});
|
| 74 |
|
| 75 |
+
const res = await fetch(`${API_BASE}/system/diagnostic`);
|
| 76 |
const data = await res.json();
|
| 77 |
setDiagnostic(data);
|
| 78 |
|
| 79 |
+
toast.success(`Stability: ${data.stability_score}%`, {
|
| 80 |
duration: 2500,
|
| 81 |
+
style: { background: "#064e3b", color: "#34d399" },
|
| 82 |
});
|
| 83 |
|
| 84 |
+
speakSystem(`Diagnostic complete. System stability is currently at ${data.stability_score} percent.`);
|
| 85 |
} catch (err) {
|
| 86 |
console.error("Diagnostic error:", err);
|
| 87 |
+
toast.error("❌ Diagnostic sequence failed");
|
| 88 |
} finally {
|
| 89 |
setScanning(false);
|
| 90 |
}
|
| 91 |
};
|
| 92 |
|
|
|
|
| 93 |
useEffect(() => {
|
| 94 |
fetchSystemData();
|
| 95 |
const interval = setInterval(fetchSystemData, 5000);
|
| 96 |
return () => clearInterval(interval);
|
| 97 |
}, []);
|
| 98 |
|
| 99 |
+
// ⚙️ Fetch Dynamic Data (Processes + Connections)
|
| 100 |
useEffect(() => {
|
| 101 |
const fetchDynamicData = async () => {
|
| 102 |
try {
|
| 103 |
+
const [procRes, connRes] = await Promise.all([
|
| 104 |
+
fetch(`${API_BASE}/system/processes`),
|
| 105 |
+
fetch(`${API_BASE}/system/connections`)
|
| 106 |
+
]);
|
| 107 |
+
|
| 108 |
const procData = await procRes.json();
|
|
|
|
|
|
|
|
|
|
| 109 |
const connData = await connRes.json();
|
| 110 |
+
|
| 111 |
+
if (Array.isArray(procData)) setProcesses(procData);
|
| 112 |
if (Array.isArray(connData)) setConnections(connData);
|
| 113 |
|
| 114 |
+
// Generate simulated trends for the UI
|
| 115 |
+
setAttackTrends(Array.from({ length: 7 }, (_, i) => ({
|
| 116 |
+
day: `T-${6-i}h`,
|
| 117 |
TOR: Math.random() * 10,
|
| 118 |
VPN: Math.random() * 15,
|
|
|
|
| 119 |
DDoS: Math.random() * 20,
|
| 120 |
+
})));
|
| 121 |
+
|
| 122 |
+
setOptimizations([
|
| 123 |
+
"🧠 CPU logic: Background tasks throttled for NIDS performance.",
|
| 124 |
+
"🔒 Port Integrity: 443/80 secured against injection.",
|
| 125 |
+
"💾 Memory: Zero-leak policy active.",
|
| 126 |
+
"🌐 Traffic: Low latency established via local gateway."
|
| 127 |
+
]);
|
|
|
|
|
|
|
| 128 |
} catch (error) {
|
| 129 |
+
console.error("Live update error:", error);
|
| 130 |
}
|
| 131 |
};
|
| 132 |
|
|
|
|
| 137 |
|
| 138 |
if (!systemData)
|
| 139 |
return (
|
| 140 |
+
<div className="flex flex-col items-center justify-center min-h-screen text-accent font-mono animate-pulse">
|
| 141 |
+
<Cpu size={48} className="mb-4 animate-spin-slow" />
|
| 142 |
+
<p className="text-xl">SYNCING WITH NIDS BACKEND...</p>
|
| 143 |
</div>
|
| 144 |
);
|
| 145 |
|
| 146 |
const renderGauge = (label, value, color) => (
|
| 147 |
+
<div className="flex flex-col items-center card-glow rounded-2xl p-4 bg-slate-900/40 border border-white/5 transition-transform hover:scale-105">
|
| 148 |
+
<h3 className="text-accent text-xs font-bold uppercase tracking-widest mb-2 opacity-70">{label}</h3>
|
| 149 |
<GaugeChart
|
| 150 |
id={`gauge-${label}`}
|
| 151 |
+
nrOfLevels={30}
|
| 152 |
percent={value / 100}
|
| 153 |
colors={["#00e5ff", "#fbbf24", "#ff0059"]}
|
| 154 |
arcPadding={0.02}
|
| 155 |
+
needleColor="#ffffff"
|
| 156 |
+
textColor="transparent"
|
| 157 |
+
style={{ width: "160px" }}
|
| 158 |
/>
|
| 159 |
+
<p className={`text-2xl font-black mt-2 ${value > 80 ? "text-rose-500" : value > 60 ? "text-amber-400" : "text-cyan-400"}`}>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 160 |
{Math.round(value)}%
|
| 161 |
</p>
|
| 162 |
</div>
|
| 163 |
);
|
| 164 |
|
|
|
|
|
|
|
| 165 |
return (
|
| 166 |
+
<div className="p-6 space-y-6 relative text-[var(--text)] bg-[#0a0a0f] min-h-screen font-sans">
|
| 167 |
<Toaster position="bottom-right" />
|
| 168 |
|
| 169 |
+
{/* Hero Header */}
|
| 170 |
+
<div className="relative z-10">
|
| 171 |
+
<h2 className="py-2 text-5xl md:text-7xl font-black bg-gradient-to-r from-cyan-400 via-indigo-500 to-purple-500 text-transparent bg-clip-text drop-shadow-[0_0_15px_rgba(0,229,255,0.3)]">
|
| 172 |
+
System Analysis
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 173 |
</h2>
|
| 174 |
+
<div className="flex items-center gap-2 text-cyan-400/60 font-mono text-sm">
|
| 175 |
+
<ShieldCheck size={14} /> <span>SECURE CONNECTION ESTABLISHED TO {systemData.ip_address}</span>
|
| 176 |
+
</div>
|
| 177 |
+
</div>
|
| 178 |
+
|
| 179 |
+
{/* Control Panel */}
|
| 180 |
+
<div className="flex justify-between items-center flex-wrap gap-4 bg-slate-900/50 p-4 rounded-xl border border-white/5 backdrop-blur-md">
|
| 181 |
+
<div className="flex items-center gap-4">
|
| 182 |
+
<div className="flex flex-col">
|
| 183 |
+
<span className="text-xs uppercase text-accent/50 font-bold">Status</span>
|
| 184 |
+
<span className="text-emerald-400 font-mono flex items-center gap-2">
|
| 185 |
+
<div className="w-2 h-2 rounded-full bg-emerald-500 animate-pulse"></div>
|
| 186 |
+
SYSTEM OPERATIONAL
|
| 187 |
+
</span>
|
| 188 |
+
</div>
|
| 189 |
+
</div>
|
| 190 |
+
|
| 191 |
<div className="flex items-center gap-3">
|
| 192 |
<button
|
| 193 |
onClick={runDiagnostic}
|
| 194 |
disabled={scanning}
|
| 195 |
+
className={`flex items-center gap-2 px-4 py-2 rounded-lg font-bold transition-all border ${
|
| 196 |
+
scanning ? "bg-amber-500/20 border-amber-500/50 text-amber-500" : "bg-cyan-500/10 border-cyan-400/30 text-cyan-400 hover:bg-cyan-500/20"
|
| 197 |
+
}`}
|
| 198 |
>
|
| 199 |
+
<PlayCircle size={18} className={scanning ? "animate-spin" : ""} />
|
| 200 |
+
{scanning ? "STRESS TESTING..." : "RUN DIAGNOSTIC"}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 201 |
</button>
|
| 202 |
|
| 203 |
<button
|
| 204 |
onClick={handleDownload}
|
| 205 |
+
className="flex items-center gap-2 bg-emerald-500/10 border border-emerald-400/30 text-emerald-400 px-4 py-2 rounded-lg font-bold hover:bg-emerald-500/20 transition-all"
|
| 206 |
>
|
| 207 |
+
<FileText size={18} /> REPORT.PDF
|
|
|
|
| 208 |
</button>
|
| 209 |
|
| 210 |
<button
|
| 211 |
+
onClick={() => speakSystem(`Current system health is ${systemData.health_score} percent. Temperature nominal.`)}
|
| 212 |
+
className="p-2 rounded-full bg-slate-800 text-accent hover:bg-cyan-500/20 transition-colors"
|
|
|
|
| 213 |
>
|
| 214 |
+
<Volume2 size={20} />
|
| 215 |
</button>
|
| 216 |
</div>
|
| 217 |
</div>
|
| 218 |
|
| 219 |
+
{/* Main Stats Grid */}
|
| 220 |
+
<div className="grid lg:grid-cols-4 gap-6">
|
| 221 |
+
<div className="lg:col-span-3 grid md:grid-cols-3 gap-6">
|
| 222 |
+
{renderGauge("CPU Load", systemData.cpu_usage, "var(--accent)")}
|
| 223 |
+
{renderGauge("Memory", systemData.ram_usage, "#fbbf24")}
|
| 224 |
+
{renderGauge("Disk Space", systemData.disk_usage, "#ff0059")}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 225 |
</div>
|
| 226 |
+
|
| 227 |
+
{/* Health / Temp Card */}
|
| 228 |
+
<div className="card-glow p-6 bg-gradient-to-br from-slate-900 to-indigo-950/30 border border-white/5 flex flex-col justify-center">
|
| 229 |
+
<div className="mb-4">
|
| 230 |
+
<p className="text-xs text-white/40 uppercase font-bold tracking-tighter">Thermal Status</p>
|
| 231 |
+
<div className="flex items-center justify-between">
|
| 232 |
+
<span className="text-3xl font-mono">{systemData.cpu_temp}°C</span>
|
| 233 |
+
<Thermometer className={systemData.cpu_temp > 70 ? "text-rose-500 animate-bounce" : "text-cyan-400"} />
|
| 234 |
+
</div>
|
| 235 |
+
</div>
|
| 236 |
+
<div>
|
| 237 |
+
<p className="text-xs text-white/40 uppercase font-bold tracking-tighter">AI Stability Index</p>
|
| 238 |
+
<div className="text-3xl font-black text-emerald-400">{systemData.health_score}%</div>
|
| 239 |
+
</div>
|
| 240 |
</div>
|
| 241 |
</div>
|
| 242 |
|
| 243 |
+
<div className="grid xl:grid-cols-2 gap-6">
|
| 244 |
+
{/* System Specs Table */}
|
| 245 |
+
<div className="card-glow p-5 bg-slate-900/40">
|
| 246 |
+
<h3 className="text-accent text-sm font-bold mb-4 flex items-center gap-2 uppercase">
|
| 247 |
+
<Server size={16} /> Hardware Profile
|
| 248 |
+
</h3>
|
| 249 |
+
<div className="grid grid-cols-2 gap-4 text-sm font-mono">
|
| 250 |
+
<div className="p-3 bg-black/30 rounded border border-white/5">
|
| 251 |
+
<p className="text-white/40 text-[10px]">OS VERSION</p>
|
| 252 |
+
<p className="truncate">{systemData.os}</p>
|
| 253 |
+
</div>
|
| 254 |
+
<div className="p-3 bg-black/30 rounded border border-white/5">
|
| 255 |
+
<p className="text-white/40 text-[10px]">LOCAL ADDRESS</p>
|
| 256 |
+
<p className="text-cyan-400">{systemData.ip_address}</p>
|
| 257 |
+
</div>
|
| 258 |
+
<div className="p-3 bg-black/30 rounded border border-white/5">
|
| 259 |
+
<p className="text-white/40 text-[10px]">TOTAL V-RAM</p>
|
| 260 |
+
<p>{systemData.ram_total} GB</p>
|
| 261 |
+
</div>
|
| 262 |
+
<div className="p-3 bg-black/30 rounded border border-white/5">
|
| 263 |
+
<p className="text-white/40 text-[10px]">STORAGE CAPACITY</p>
|
| 264 |
+
<p>{systemData.disk_total} GB</p>
|
| 265 |
+
</div>
|
| 266 |
+
</div>
|
| 267 |
+
<p className="mt-4 text-[11px] text-white/20 italic font-mono">Processor Ident: {systemData.cpu_name}</p>
|
| 268 |
+
</div>
|
| 269 |
|
| 270 |
+
{/* Active Processes */}
|
| 271 |
+
<div className="card-glow p-5 bg-slate-900/40">
|
| 272 |
+
<h3 className="text-accent text-sm font-bold mb-4 flex items-center gap-2 uppercase">
|
| 273 |
+
<Activity size={16} /> Resource Intensive Processes
|
| 274 |
+
</h3>
|
| 275 |
+
<div className="overflow-hidden rounded-lg border border-white/5">
|
| 276 |
+
<table className="w-full text-xs text-left font-mono">
|
| 277 |
+
<thead className="bg-white/5 text-accent uppercase">
|
| 278 |
+
<tr>
|
| 279 |
+
<th className="px-4 py-2">Process</th>
|
| 280 |
+
<th className="px-4 py-2">CPU</th>
|
| 281 |
+
<th className="px-4 py-2">MEM</th>
|
| 282 |
+
<th className="px-4 py-2">STATE</th>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 283 |
</tr>
|
| 284 |
+
</thead>
|
| 285 |
+
<tbody>
|
| 286 |
+
{processes.map((p, i) => (
|
| 287 |
+
<tr key={i} className="border-b border-white/5 hover:bg-white/5">
|
| 288 |
+
<td className="px-4 py-2 text-cyan-300 truncate max-w-[120px]">{p.name}</td>
|
| 289 |
+
<td className="px-4 py-2 text-white/70">{p.cpu}%</td>
|
| 290 |
+
<td className="px-4 py-2 text-white/70">{p.mem}%</td>
|
| 291 |
+
<td className="px-4 py-2">
|
| 292 |
+
<span className="px-2 py-0.5 rounded-full bg-emerald-500/10 text-emerald-400 text-[9px] uppercase">
|
| 293 |
+
{p.status}
|
| 294 |
+
</span>
|
| 295 |
+
</td>
|
| 296 |
+
</tr>
|
| 297 |
+
))}
|
| 298 |
+
</tbody>
|
| 299 |
+
</table>
|
| 300 |
+
</div>
|
| 301 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 302 |
</div>
|
| 303 |
|
| 304 |
+
{/* Network & Trends Grid */}
|
| 305 |
+
<div className="grid xl:grid-cols-2 gap-6">
|
| 306 |
+
{/* Line Chart */}
|
| 307 |
+
<div className="card-glow p-5 bg-slate-900/40">
|
| 308 |
+
<h3 className="text-accent text-sm font-bold mb-6 flex items-center gap-2 uppercase">
|
| 309 |
+
<Activity size={16} /> Threat Vector Trends (6h)
|
| 310 |
+
</h3>
|
| 311 |
+
<ResponsiveContainer width="100%" height={220}>
|
| 312 |
+
<LineChart data={attackTrends}>
|
| 313 |
+
<XAxis dataKey="day" stroke="#ffffff33" fontSize={10} />
|
| 314 |
+
<YAxis hide />
|
| 315 |
+
<Tooltip
|
| 316 |
+
contentStyle={{ backgroundColor: "#0f172a", border: "1px solid #00e5ff33", fontSize: "12px" }}
|
| 317 |
+
/>
|
| 318 |
+
<Line type="monotone" dataKey="TOR" stroke="#ff0059" strokeWidth={3} dot={false} />
|
| 319 |
+
<Line type="monotone" dataKey="VPN" stroke="#fbbf24" strokeWidth={3} dot={false} />
|
| 320 |
+
<Line type="monotone" dataKey="DDoS" stroke="#00ff88" strokeWidth={3} dot={false} />
|
| 321 |
+
</LineChart>
|
| 322 |
+
</ResponsiveContainer>
|
| 323 |
+
</div>
|
| 324 |
+
|
| 325 |
+
{/* Network Connections */}
|
| 326 |
+
<div className="card-glow p-5 bg-slate-900/40">
|
| 327 |
+
<h3 className="text-accent text-sm font-bold mb-4 flex items-center gap-2 uppercase">
|
| 328 |
+
<Network size={16} /> Live Inbound/Outbound
|
| 329 |
+
</h3>
|
| 330 |
+
<div className="space-y-2">
|
| 331 |
{connections.map((c, i) => (
|
| 332 |
+
<div key={i} className="flex justify-between items-center bg-black/20 p-2 rounded font-mono text-xs border border-white/5">
|
| 333 |
+
<div className="flex items-center gap-3">
|
| 334 |
+
<span className={`w-1.5 h-1.5 rounded-full ${c.state === 'ESTABLISHED' ? 'bg-emerald-500' : 'bg-white/20'}`}></span>
|
| 335 |
+
<span className="text-cyan-400">{c.ip}:{c.port}</span>
|
| 336 |
+
</div>
|
| 337 |
+
<div className="flex gap-4 items-center">
|
| 338 |
+
<span className="text-white/40">{c.proto}</span>
|
| 339 |
+
<span className="text-[10px] opacity-60 truncate max-w-[80px]">{c.state}</span>
|
| 340 |
+
</div>
|
| 341 |
+
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 342 |
))}
|
| 343 |
+
</div>
|
| 344 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 345 |
</div>
|
| 346 |
|
| 347 |
+
{/* AI Suggestions Footer */}
|
| 348 |
+
<div className="card-glow p-5 bg-gradient-to-r from-indigo-950/50 to-slate-900/50 border-l-4 border-l-cyan-500">
|
| 349 |
+
<h3 className="text-cyan-400 text-sm font-bold mb-3 flex items-center gap-2 uppercase">
|
| 350 |
+
<Brain size={18} /> Neural Optimizer Output
|
| 351 |
</h3>
|
| 352 |
+
<div className="grid md:grid-cols-2 gap-3">
|
| 353 |
{optimizations.map((o, i) => (
|
| 354 |
+
<div key={i} className="text-xs font-mono text-white/60 bg-white/5 p-2 rounded flex items-center gap-2">
|
| 355 |
+
<span className="text-cyan-500 font-bold">»</span> {o}
|
| 356 |
+
</div>
|
|
|
|
|
|
|
|
|
|
| 357 |
))}
|
| 358 |
+
</div>
|
| 359 |
</div>
|
| 360 |
+
|
| 361 |
<ChatAssistant />
|
| 362 |
</div>
|
| 363 |
);
|
| 364 |
}
|
| 365 |
|
| 366 |
|
|
|
frontend/src/pages/ThreatIntel.jsx
CHANGED
|
@@ -1,14 +1,17 @@
|
|
| 1 |
-
// src/pages/ThreatIntelSwitcher.jsx
|
| 2 |
import React, { useState, useMemo } from "react";
|
| 3 |
import { motion, AnimatePresence } from "framer-motion";
|
| 4 |
import Tilt from "react-parallax-tilt";
|
| 5 |
-
import {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
import { useNavigate } from "react-router-dom";
|
| 7 |
import { CircularProgressbar, buildStyles } from "react-circular-progressbar";
|
| 8 |
import { ResponsiveContainer, BarChart, Bar, XAxis, YAxis } from "recharts";
|
| 9 |
import "react-circular-progressbar/dist/styles.css";
|
| 10 |
|
| 11 |
-
//
|
| 12 |
import vpnImg from "../assests/vpn.jpg";
|
| 13 |
import torImg from "../assests/tor.png";
|
| 14 |
import i2pImg from "../assests/i2p.png";
|
|
@@ -25,22 +28,11 @@ import web from "../assests/web.jpeg";
|
|
| 25 |
import xss from "../assests/XXs.png";
|
| 26 |
import ChatAssistant from "./ChatAssistant";
|
| 27 |
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
/*
|
| 31 |
-
ThreatIntelSwitcher.jsx
|
| 32 |
-
- Two modes: BCC (your existing) and CICIDS (new)
|
| 33 |
-
- Hybrid naming for CICIDS (Option C)
|
| 34 |
-
- Comparison + Quiz present for both datasets
|
| 35 |
-
- Styling kept identical to your BCC page
|
| 36 |
-
*/
|
| 37 |
-
|
| 38 |
-
// ---------- BCC threats (existing simplified) ----------
|
| 39 |
const THREATS_BCC = {
|
| 40 |
VPN: {
|
| 41 |
title: "Virtual Private Network (VPN)",
|
| 42 |
-
desc:
|
| 43 |
-
"VPNs create an encrypted tunnel between client and server, hiding source IPs and traffic content. Attackers use VPNs to evade detection and obfuscate origin. Many malicious flows appear as legitimate VPN traffic and can hide beaconing patterns. Look for unusual geo-locations or sudden new endpoints in VPN sessions. Combine host telemetry + flow analysis to find misuse.",
|
| 44 |
icon: <Shield size={44} className="text-[var(--accent)]" />,
|
| 45 |
image: vpnImg,
|
| 46 |
risk: "Medium",
|
|
@@ -48,8 +40,7 @@ const THREATS_BCC = {
|
|
| 48 |
},
|
| 49 |
TOR: {
|
| 50 |
title: "The Onion Router (TOR)",
|
| 51 |
-
desc:
|
| 52 |
-
"TOR anonymizes traffic using layered encryption across relays; it is commonly used for command-and-control and data exfiltration. TOR exit nodes change frequently so detection relies on known relay lists or traffic fingerprinting. High-risk for ransomware or darknet tool delivery. Often paired with secondary obfuscation such as tunneling. Monitor long-lived connections and unusual destination ports.",
|
| 53 |
icon: <Lock size={44} className="text-[var(--accent)]" />,
|
| 54 |
image: torImg,
|
| 55 |
risk: "High",
|
|
@@ -57,8 +48,7 @@ const THREATS_BCC = {
|
|
| 57 |
},
|
| 58 |
I2P: {
|
| 59 |
title: "Invisible Internet Project (I2P)",
|
| 60 |
-
desc:
|
| 61 |
-
"I2P aims for anonymous P2P connections and is used by advanced threat actors for hidden data transfer. Because it uses different protocols than TOR, detection is harder and relies on signature-based patterns or known endpoint lists. Expect intermittent bursts of traffic and P2P-like flows. Treat as high suspicion and correlate with host artifacts.",
|
| 62 |
icon: <EyeOff size={44} className="text-[var(--accent)]" />,
|
| 63 |
image: i2pImg,
|
| 64 |
risk: "High",
|
|
@@ -66,8 +56,7 @@ const THREATS_BCC = {
|
|
| 66 |
},
|
| 67 |
FREENET: {
|
| 68 |
title: "Freenet",
|
| 69 |
-
desc:
|
| 70 |
-
"Freenet is a decentralized file-sharing network sometimes used for hosting malicious payloads and leaked data. Traffic can be noisy; detection focuses on unusual download/exfil patterns and unfamiliar peers. Risk is moderate but can be a vector for persistent malware propagation. Use endpoint file scanning to complement network indicators.",
|
| 71 |
icon: <Server size={44} className="text-[var(--accent)]" />,
|
| 72 |
image: freenetImg,
|
| 73 |
risk: "Medium",
|
|
@@ -75,8 +64,7 @@ const THREATS_BCC = {
|
|
| 75 |
},
|
| 76 |
ZERONET: {
|
| 77 |
title: "ZeroNet",
|
| 78 |
-
desc:
|
| 79 |
-
"ZeroNet leverages Bitcoin identities and BitTorrent protocols for decentralized websites — used by hacktivists and for anonymous content hosting. Traffic fingerprints often include torrent-like handshakes and irregular port usage. Treat connections to ZeroNet peers with caution and cross-check with threat intel feeds.",
|
| 80 |
icon: <Globe2 size={44} className="text-[var(--accent)]" />,
|
| 81 |
image: zeronetImg,
|
| 82 |
risk: "Medium",
|
|
@@ -84,20 +72,18 @@ const THREATS_BCC = {
|
|
| 84 |
},
|
| 85 |
};
|
| 86 |
|
| 87 |
-
// ---------- CICIDS threats
|
| 88 |
const THREATS_CICIDS = {
|
| 89 |
"Benign": {
|
| 90 |
title: "Benign Traffic",
|
| 91 |
-
desc:
|
| 92 |
-
"Normal, expected network activity produced by legitimate users and services. It includes routine web browsing, DNS queries, and application traffic. Benign classification helps train models and filter false positives for detection. Always verify baselines — benign patterns vary by environment. Use whitelisting and behavior baselines to avoid noisy alerts.",
|
| 93 |
icon: <Globe size={44} className="text-[var(--accent)]" />,
|
| 94 |
risk: "Low",
|
| 95 |
usage: "Baseline traffic in datasets",
|
| 96 |
},
|
| 97 |
"DoS – Hulk": {
|
| 98 |
title: "DoS – Hulk",
|
| 99 |
-
desc:
|
| 100 |
-
"HULK (HTTP Unbearable Load King) floods web servers with unique HTTP requests to exhaust resources. It uses randomized parameters to bypass caching and make mitigation harder. Detection focuses on request rate, high CPU/memory on web servers, and anomalous user-agent patterns. Rate limiting and WAF tuning can reduce impact. For forensic analysis, check server logs for repeated unique URIs.",
|
| 101 |
icon: <Zap size={44} className="text-[var(--accent)]" />,
|
| 102 |
risk: "High",
|
| 103 |
image: hulkImg,
|
|
@@ -105,8 +91,7 @@ const THREATS_CICIDS = {
|
|
| 105 |
},
|
| 106 |
"DoS – SlowHTTPTest": {
|
| 107 |
title: "DoS – SlowHTTPTest",
|
| 108 |
-
desc:
|
| 109 |
-
"SlowHTTPTest holds connections open by sending headers/data very slowly to exhaust server connection slots. It mimics a low-and-slow client and can evade simple rate-based counters. Look for many half-open connections or long-lived slow POST/GET requests. Mitigation includes connection timeouts and reverse proxies that detect low throughput.",
|
| 110 |
icon: <FileWarning size={44} className="text-[var(--accent)]" />,
|
| 111 |
risk: "High",
|
| 112 |
image: httpImg,
|
|
@@ -114,16 +99,14 @@ const THREATS_CICIDS = {
|
|
| 114 |
},
|
| 115 |
"DoS – GoldenEye": {
|
| 116 |
title: "DoS – GoldenEye",
|
| 117 |
-
desc:
|
| 118 |
-
"GoldenEye is another application-layer DoS that sends malicious HTTP traffic to overwhelm servers. It's similar to HULK but has different request patterns and concurrency behavior. Monitor for spikes in request volume or abnormal error rates. Defenses include autoscaling, traffic shaping, and WAF rules targeted to payload patterns.",
|
| 119 |
icon: <Cpu size={44} className="text-[var(--accent)]" />,
|
| 120 |
risk: "High",
|
| 121 |
usage: "Application-layer DoS",
|
| 122 |
},
|
| 123 |
"DoS – Slowloris": {
|
| 124 |
title: "DoS – Slowloris",
|
| 125 |
-
desc:
|
| 126 |
-
"Slowloris sends partial HTTP headers to keep many connections open and starve web servers of resources. It differs from volumetric attacks by using few packets and many open sockets. Detection looks for many concurrent slow connections from few IPs. Mitigation with reverse proxies and connection throttling helps reduce exposure to Slowloris.",
|
| 127 |
icon: <Key size={44} className="text-[var(--accent)]" />,
|
| 128 |
risk: "High",
|
| 129 |
image: slowlorisImg,
|
|
@@ -131,8 +114,7 @@ const THREATS_CICIDS = {
|
|
| 131 |
},
|
| 132 |
"FTP – BruteForce": {
|
| 133 |
title: "FTP – BruteForce",
|
| 134 |
-
desc:
|
| 135 |
-
"Brute-force attempts target FTP credentials by repeatedly trying username/password combinations. Signs include many login attempts from single source IPs or bursts of credential retries across accounts. Harden systems with strong password policy, lockouts, and MFA where possible. Correlate with successful logins and subsequent suspicious file transfers.",
|
| 136 |
icon: <GitPullRequest size={44} className="text-[var(--accent)]" />,
|
| 137 |
risk: "Medium",
|
| 138 |
image: bruteforceImg,
|
|
@@ -140,8 +122,7 @@ const THREATS_CICIDS = {
|
|
| 140 |
},
|
| 141 |
"SSH – BruteForce": {
|
| 142 |
title: "SSH – BruteForce",
|
| 143 |
-
desc:
|
| 144 |
-
"SSH brute-force attacks attempt many credential combinations to gain shell access. Identifiable by repeated connection attempts and authentication failures. Use fail2ban, key-based auth, and rate limits to prevent compromise. Alert on successful logins after many failures and check for unusual post-auth behavior.",
|
| 145 |
icon: <Lock size={44} className="text-[var(--accent)]" />,
|
| 146 |
risk: "Medium",
|
| 147 |
image: bruteforceImg,
|
|
@@ -149,8 +130,7 @@ const THREATS_CICIDS = {
|
|
| 149 |
},
|
| 150 |
"DDoS – HOIC": {
|
| 151 |
title: "DDoS – HOIC",
|
| 152 |
-
desc:
|
| 153 |
-
"HOIC (High Orbit Ion Cannon) is a volumetric DDoS tool generating massive concurrent HTTP requests to disrupt services. It typically uses many clients in coordinated bursts. Detection shows high bandwidth usage and saturation across network links. Upstream mitigation and scrubbing services are common defenses. Monitor for repeated attack campaigns from multiple origins.",
|
| 154 |
icon: <CloudSnow size={44} className="text-[var(--accent)]" />,
|
| 155 |
risk: "High",
|
| 156 |
image: hoic,
|
|
@@ -158,8 +138,7 @@ const THREATS_CICIDS = {
|
|
| 158 |
},
|
| 159 |
"DDoS – LOIC UDP": {
|
| 160 |
title: "DDoS – LOIC UDP",
|
| 161 |
-
desc:
|
| 162 |
-
"LOIC (Low Orbit Ion Cannon) can generate UDP floods that saturate link bandwidth. Traffic is mostly random UDP packets to target ports, causing network drop and service interruption. Rate-based detection and volumetric monitoring identify LOIC UDP floods. Employ blackholing and DDoS mitigation appliances for robust protection.",
|
| 163 |
icon: <Zap size={44} className="text-[var(--accent)]" />,
|
| 164 |
risk: "High",
|
| 165 |
image: loic,
|
|
@@ -167,8 +146,7 @@ const THREATS_CICIDS = {
|
|
| 167 |
},
|
| 168 |
"Brute Force – Web": {
|
| 169 |
title: "Brute Force – Web",
|
| 170 |
-
desc:
|
| 171 |
-
"Web brute-force targets web login endpoints or weak authentication mechanisms to gain access. Look for numerous POST requests to login URIs, varying credentials, or rapid failed attempts. Use CAPTCHA, account lockouts, and behavioral analytics to detect these attacks. After compromise, look for privilege escalation or admin panel access.",
|
| 172 |
icon: <AlertTriangle size={44} className="text-[var(--accent)]" />,
|
| 173 |
risk: "Medium",
|
| 174 |
image: web,
|
|
@@ -176,8 +154,7 @@ const THREATS_CICIDS = {
|
|
| 176 |
},
|
| 177 |
"Brute Force – XSS": {
|
| 178 |
title: "Brute Force – XSS",
|
| 179 |
-
desc:
|
| 180 |
-
"This class (dataset-labeled) represents web attack attempts focusing on injection-like payloads; treat as suspicious input patterns and attempted exploitation. Detection relies on payload signatures and context-based WAF rules. Validate and sanitize inputs on the server. Monitor for successful script execution and subsequent data exfiltration.",
|
| 181 |
icon: <FileWarning size={44} className="text-[var(--accent)]" />,
|
| 182 |
risk: "Medium",
|
| 183 |
image: xss,
|
|
@@ -185,8 +162,7 @@ const THREATS_CICIDS = {
|
|
| 185 |
},
|
| 186 |
"SQL Injection": {
|
| 187 |
title: "SQL Injection",
|
| 188 |
-
desc:
|
| 189 |
-
"SQL injection is an attack where user-supplied data manipulates backend SQL queries — can lead to data theft or modification. Detect via payloads containing SQL keywords, repeated similar requests, or unusual query response patterns. Harden apps with prepared statements and parameterized queries. Triage by checking database logs after detection.",
|
| 190 |
icon: <Database size={44} className="text-[var(--accent)]" />,
|
| 191 |
risk: "High",
|
| 192 |
image: sql,
|
|
@@ -194,8 +170,7 @@ const THREATS_CICIDS = {
|
|
| 194 |
},
|
| 195 |
"Infiltration": {
|
| 196 |
title: "Infiltration",
|
| 197 |
-
desc:
|
| 198 |
-
"Infiltration covers multi-stage intrusion activity where attackers establish footholds and move laterally. Look for anomalous internal connections, credential reuse, and unusual process execution. Combine endpoint telemetry, network flows, and identity logs for comprehensive detection. Post-detection, isolate affected hosts and perform forensic triage.",
|
| 199 |
icon: <Shield size={44} className="text-[var(--accent)]" />,
|
| 200 |
risk: "High",
|
| 201 |
image: xss,
|
|
@@ -203,8 +178,7 @@ const THREATS_CICIDS = {
|
|
| 203 |
},
|
| 204 |
"Bot": {
|
| 205 |
title: "Bot Activity",
|
| 206 |
-
desc:
|
| 207 |
-
"Automated bot traffic includes scraping, credential stuffing, or crawler-like behaviour. It often shows predictable intervals, similar UA strings, or repeated URIs. Differentiate benign crawlers from malicious bots using rate, origin, and behavioral heuristics. Protect resources with bot management and rate limiting.",
|
| 208 |
icon: <Globe size={44} className="text-[var(--accent)]" />,
|
| 209 |
risk: "Medium",
|
| 210 |
image: web,
|
|
@@ -212,32 +186,21 @@ const THREATS_CICIDS = {
|
|
| 212 |
},
|
| 213 |
};
|
| 214 |
|
| 215 |
-
// ---------- Utility: prepare chart data from class-count map ----------
|
| 216 |
const makeChartDataFromCounts = (counts) =>
|
| 217 |
Object.entries(counts).map(([k, v]) => ({ name: k, value: v }));
|
| 218 |
|
| 219 |
-
// small mock counts for CICIDS (from your provided map)
|
| 220 |
const CICIDS_COUNTS = {
|
| 221 |
-
BENIGN: 40000,
|
| 222 |
-
|
| 223 |
-
"DoS attacks-
|
| 224 |
-
"
|
| 225 |
-
|
| 226 |
-
"
|
| 227 |
-
"DoS attacks-Slowloris": 8000,
|
| 228 |
-
"FTP-BruteForce": 8000,
|
| 229 |
-
"SSH-Bruteforce": 8000,
|
| 230 |
-
"DDOS attack-HOIC": 8000,
|
| 231 |
-
"DDOS attack-LOIC-UDP": 1730,
|
| 232 |
-
"Brute Force -Web": 611,
|
| 233 |
-
"Brute Force -XSS": 230,
|
| 234 |
-
"SQL Injection": 87,
|
| 235 |
};
|
| 236 |
|
| 237 |
-
// ---------- Main Component ----------
|
| 238 |
export default function ThreatIntelInteractive() {
|
| 239 |
-
const [mode, setMode] = useState("bcc");
|
| 240 |
-
const [selected, setSelected] = useState("VPN");
|
| 241 |
const [compare, setCompare] = useState("TOR");
|
| 242 |
const [query, setQuery] = useState("");
|
| 243 |
const [filterRisk, setFilterRisk] = useState("All");
|
|
@@ -249,7 +212,6 @@ export default function ThreatIntelInteractive() {
|
|
| 249 |
const threats = mode === "bcc" ? THREATS_BCC : THREATS_CICIDS;
|
| 250 |
const keys = useMemo(() => Object.keys(threats), [threats]);
|
| 251 |
|
| 252 |
-
// keep selected sensible when switching modes
|
| 253 |
React.useEffect(() => {
|
| 254 |
setSelected((prev) => (keys.includes(prev) ? prev : keys[0]));
|
| 255 |
setCompare((prev) => (keys.includes(prev) ? prev : keys[1] || keys[0]));
|
|
@@ -266,10 +228,9 @@ export default function ThreatIntelInteractive() {
|
|
| 266 |
|
| 267 |
const threat = threats[selected] || {};
|
| 268 |
const compareThreat = threats[compare] || {};
|
| 269 |
-
const chartData = mode === "cicids" ? makeChartDataFromCounts(CICIDS_COUNTS).slice(0, 8) : [];
|
| 270 |
|
| 271 |
const quizQuestions = {
|
| 272 |
-
// BCC / CICIDS both support a small quiz map; for CICIDS use class-specific Qs (simple)
|
| 273 |
...(mode === "bcc"
|
| 274 |
? {
|
| 275 |
VPN: { question: "VPNs primarily encrypt traffic at which layer?", answer: "Network" },
|
|
@@ -279,7 +240,6 @@ export default function ThreatIntelInteractive() {
|
|
| 279 |
ZERONET: { question: "ZeroNet commonly pairs with which crypto identity?", answer: "Bitcoin" },
|
| 280 |
}
|
| 281 |
: {
|
| 282 |
-
// CICIDS sample quiz questions (pick per-class)
|
| 283 |
Benign: { question: "Benign traffic indicates what?", answer: "Normal" },
|
| 284 |
"DoS – Hulk": { question: "HULK targets which layer?", answer: "Application" },
|
| 285 |
"DoS – SlowHTTPTest": { question: "SlowHTTPTest is what type of DoS?", answer: "Low-and-slow" },
|
|
@@ -299,256 +259,130 @@ export default function ThreatIntelInteractive() {
|
|
| 299 |
|
| 300 |
const handleQuizSubmit = () => {
|
| 301 |
const q = quizQuestions[selected];
|
| 302 |
-
if (!q)
|
| 303 |
-
setShowQuizResult({ correct: false });
|
| 304 |
-
setTimeout(() => setShowQuizResult(null), 2000);
|
| 305 |
-
return;
|
| 306 |
-
}
|
| 307 |
const correct = quizAnswer.trim().toLowerCase() === q.answer.toLowerCase();
|
| 308 |
setShowQuizResult({ correct });
|
| 309 |
setTimeout(() => setShowQuizResult(null), 2500);
|
| 310 |
};
|
| 311 |
|
| 312 |
return (
|
| 313 |
-
|
| 314 |
-
|
| 315 |
-
|
| 316 |
-
|
| 317 |
-
|
| 318 |
-
|
| 319 |
-
|
| 320 |
-
|
| 321 |
-
|
| 322 |
-
|
| 323 |
-
|
| 324 |
-
|
| 325 |
-
|
| 326 |
-
<
|
| 327 |
-
|
| 328 |
-
|
| 329 |
-
|
| 330 |
-
|
| 331 |
-
|
| 332 |
-
|
| 333 |
-
|
| 334 |
-
|
| 335 |
-
|
| 336 |
-
<
|
| 337 |
-
|
| 338 |
-
|
| 339 |
-
|
| 340 |
-
|
| 341 |
-
|
| 342 |
-
|
| 343 |
-
|
| 344 |
-
|
| 345 |
-
|
| 346 |
-
|
| 347 |
-
|
| 348 |
-
|
| 349 |
-
|
| 350 |
-
|
| 351 |
-
|
| 352 |
-
|
| 353 |
-
|
| 354 |
-
|
| 355 |
-
|
| 356 |
-
|
| 357 |
-
|
| 358 |
-
|
| 359 |
-
|
| 360 |
-
{/* Mode toggle */}
|
| 361 |
-
|
| 362 |
-
<div className="flex items-center py-4 gap-3 z-10 relative">
|
| 363 |
-
<button
|
| 364 |
-
onClick={() => setMode("bcc")}
|
| 365 |
-
className={`px-4 py-2 rounded-lg border text-sm font-medium transition-all ${
|
| 366 |
-
mode === "bcc"
|
| 367 |
-
? "bg-[var(--accent)]/30 border-[var(--accent)]/50 text-[var(--accent)]"
|
| 368 |
-
: "bg-black/20 border-[var(--accent)]/10 text-slate-400 hover:bg-[var(--accent)]/10"
|
| 369 |
-
}`}
|
| 370 |
-
>
|
| 371 |
-
BCC View
|
| 372 |
-
</button>
|
| 373 |
-
|
| 374 |
-
<button
|
| 375 |
-
onClick={() => setMode("cicids")}
|
| 376 |
-
className={`px-4 py-2 rounded-lg border text-sm font-medium transition-all ${
|
| 377 |
-
mode === "cicids"
|
| 378 |
-
? "bg-[var(--accent)]/30 border-[var(--accent)]/50 text-[var(--accent)]"
|
| 379 |
-
: "bg-black/20 border-[var(--accent)]/10 text-slate-400 hover:bg-[var(--accent)]/10"
|
| 380 |
-
}`}
|
| 381 |
-
>
|
| 382 |
-
CICIDS View
|
| 383 |
-
</button>
|
| 384 |
-
|
| 385 |
-
<div className="ml-auto flex items-center gap-2">
|
| 386 |
-
<input
|
| 387 |
-
type="text"
|
| 388 |
-
placeholder="Search threats..."
|
| 389 |
-
value={query}
|
| 390 |
-
onChange={(e) => setQuery(e.target.value)}
|
| 391 |
-
className="bg-black/40 border border-[var(--accent)]/20 px-3 py-2 rounded-lg text-sm text-[var(--accent)] outline-none"
|
| 392 |
-
/>
|
| 393 |
-
<select
|
| 394 |
-
value={filterRisk}
|
| 395 |
-
onChange={(e) => setFilterRisk(e.target.value)}
|
| 396 |
-
className="bg-black/40 border border-[var(--accent)]/20 px-3 py-2 rounded-lg text-sm text-[var(--accent)] outline-none"
|
| 397 |
-
>
|
| 398 |
-
<option>All</option>
|
| 399 |
-
<option>High</option>
|
| 400 |
-
<option>Medium</option>
|
| 401 |
-
<option>Low</option>
|
| 402 |
</select>
|
| 403 |
</div>
|
| 404 |
</div>
|
| 405 |
|
| 406 |
-
|
| 407 |
-
|
| 408 |
-
</div>
|
| 409 |
-
|
| 410 |
-
{/* threat buttons */}
|
| 411 |
-
<div className="flex flex-wrap gap-3 relative z-10">
|
| 412 |
{filteredKeys.map((key) => (
|
| 413 |
-
<button
|
| 414 |
-
|
| 415 |
-
onClick={() => setSelected(key)}
|
| 416 |
-
className={`px-4 py-2 rounded-lg border text-sm font-medium transition-all ${
|
| 417 |
-
selected === key
|
| 418 |
-
? "bg-[var(--accent)]/30 border-[var(--accent)]/50 text-[var(--accent)]"
|
| 419 |
-
: "bg-black/20 border-[var(--accent)]/10 text-slate-400 hover:bg-[var(--accent)]/10"
|
| 420 |
-
}`}
|
| 421 |
-
>
|
| 422 |
{key}
|
| 423 |
</button>
|
| 424 |
))}
|
| 425 |
</div>
|
| 426 |
|
| 427 |
-
{/*
|
| 428 |
-
<Tilt tiltMaxAngleX={
|
| 429 |
-
<
|
| 430 |
-
|
| 431 |
-
|
| 432 |
-
|
| 433 |
-
|
| 434 |
-
|
| 435 |
-
|
| 436 |
-
|
| 437 |
-
|
| 438 |
-
initial={{ opacity: 0 }}
|
| 439 |
-
animate={{ opacity: 0.06 }}
|
| 440 |
-
/>
|
| 441 |
-
)}
|
| 442 |
-
|
| 443 |
-
<div className="relative z-10">
|
| 444 |
-
<div className="flex items-center gap-4 mb-3">
|
| 445 |
-
{threat.icon}
|
| 446 |
-
<div>
|
| 447 |
-
<h3 className="text-xl font-semibold text-[var(--accent)]">{threat.title}</h3>
|
| 448 |
-
<p className="text-slate-400 text-sm italic">{(threat.desc?.split(". ").slice(0, 2).join(". ") || "No description available.") + "."}</p>
|
| 449 |
</div>
|
| 450 |
-
|
| 451 |
-
|
| 452 |
-
|
| 453 |
-
|
| 454 |
-
|
| 455 |
-
|
| 456 |
-
|
| 457 |
-
|
| 458 |
-
textColor: "var(--accent)",
|
| 459 |
-
trailColor: "#111",
|
| 460 |
-
})}
|
| 461 |
-
/>
|
| 462 |
-
</div>
|
| 463 |
-
|
| 464 |
-
{/* optional small chart (only for CICIDS to show class counts) */}
|
| 465 |
-
{mode === "cicids" && (
|
| 466 |
-
<div className="mt-4">
|
| 467 |
-
<ResponsiveContainer width="100%" height={110}>
|
| 468 |
-
<BarChart data={chartData}>
|
| 469 |
-
<XAxis dataKey="name" stroke="var(--accent)" />
|
| 470 |
-
<YAxis hide />
|
| 471 |
-
<Bar dataKey="value" fill="var(--accent)" radius={[6,6,0,0]} />
|
| 472 |
-
</BarChart>
|
| 473 |
-
</ResponsiveContainer>
|
| 474 |
-
<p className="text-center text-slate-400 text-sm mt-2">Top CICIDS counts (sample)</p>
|
| 475 |
</div>
|
| 476 |
-
|
| 477 |
-
|
| 478 |
-
<p className="text-center text-slate-400 text-sm mt-2">{threat.usage}</p>
|
| 479 |
-
</div>
|
| 480 |
-
</motion.div>
|
| 481 |
</Tilt>
|
| 482 |
|
| 483 |
-
{/*
|
| 484 |
-
<
|
| 485 |
-
<
|
| 486 |
-
<
|
| 487 |
-
|
| 488 |
-
|
| 489 |
-
|
| 490 |
-
<motion.div className="p-4 bg-black/40 rounded-xl border border-[var(--accent)]/20">
|
| 491 |
-
<div className="flex items-center gap-2 mb-2">
|
| 492 |
-
<h4 className="text-[var(--accent)] font-semibold mr-auto">Compare</h4>
|
| 493 |
-
<select
|
| 494 |
-
value={compare}
|
| 495 |
-
onChange={(e) => setCompare(e.target.value)}
|
| 496 |
-
className="bg-black/40 border border-[var(--accent)]/20 px-3 py-1 rounded-lg text-sm text-[var(--accent)] outline-none"
|
| 497 |
-
>
|
| 498 |
-
{keys.map((k) => (
|
| 499 |
-
<option key={k} value={k}>
|
| 500 |
-
{k}
|
| 501 |
-
</option>
|
| 502 |
-
))}
|
| 503 |
</select>
|
| 504 |
-
<button
|
| 505 |
-
className="ml-3 px-3 py-1 bg-[var(--accent)]/10 border border-[var(--accent)]/30 rounded-lg text-[var(--accent)] text-sm"
|
| 506 |
-
onClick={() => navigate(`/flow?type=${encodeURIComponent(compare)}`)}
|
| 507 |
-
>
|
| 508 |
-
Flow
|
| 509 |
-
</button>
|
| 510 |
</div>
|
| 511 |
-
|
| 512 |
-
<
|
| 513 |
-
<p className="text-slate-400 text-sm">{compareThreat.desc}</p>
|
| 514 |
-
</motion.div>
|
| 515 |
-
</motion.div>
|
| 516 |
-
|
| 517 |
-
{/* quiz */}
|
| 518 |
-
<div className="mt-6 bg-black/40 border border-[var(--accent)]/20 rounded-xl p-4 relative z-10">
|
| 519 |
-
<h4 className="text-[var(--accent)] font-semibold mb-2">🎯 Analyst Quiz</h4>
|
| 520 |
-
<p className="text-slate-400 text-sm mb-3">{(quizQuestions[selected] && quizQuestions[selected].question) || "No question for this class."}</p>
|
| 521 |
-
|
| 522 |
-
<div className="flex gap-2">
|
| 523 |
-
<input
|
| 524 |
-
type="text"
|
| 525 |
-
placeholder="Your answer..."
|
| 526 |
-
value={quizAnswer}
|
| 527 |
-
onChange={(e) => setQuizAnswer(e.target.value)}
|
| 528 |
-
className="flex-1 bg-black/40 border border-[var(--accent)]/20 rounded-lg px-3 py-2 text-[var(--accent)] text-sm outline-none"
|
| 529 |
-
/>
|
| 530 |
-
<button
|
| 531 |
-
onClick={handleQuizSubmit}
|
| 532 |
-
className="px-4 py-2 bg-[var(--accent)]/20 border border-[var(--accent)]/30 rounded-lg hover:bg-[var(--accent)]/30 text-[var(--accent)] text-sm"
|
| 533 |
-
>
|
| 534 |
-
Submit
|
| 535 |
-
</button>
|
| 536 |
</div>
|
| 537 |
|
| 538 |
-
<
|
| 539 |
-
|
| 540 |
-
|
| 541 |
-
|
| 542 |
-
|
| 543 |
-
|
| 544 |
-
|
| 545 |
-
|
| 546 |
-
|
| 547 |
-
|
| 548 |
-
|
| 549 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 550 |
</div>
|
| 551 |
-
|
|
|
|
| 552 |
</div>
|
| 553 |
);
|
| 554 |
}
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import React, { useState, useMemo } from "react";
|
| 2 |
import { motion, AnimatePresence } from "framer-motion";
|
| 3 |
import Tilt from "react-parallax-tilt";
|
| 4 |
+
import {
|
| 5 |
+
Shield, Lock, EyeOff, Globe2, Server, FileWarning,
|
| 6 |
+
Cpu, Zap, GitPullRequest, Key, Database, CloudSnow,
|
| 7 |
+
AlertTriangle, Globe, Search
|
| 8 |
+
} from "lucide-react";
|
| 9 |
import { useNavigate } from "react-router-dom";
|
| 10 |
import { CircularProgressbar, buildStyles } from "react-circular-progressbar";
|
| 11 |
import { ResponsiveContainer, BarChart, Bar, XAxis, YAxis } from "recharts";
|
| 12 |
import "react-circular-progressbar/dist/styles.css";
|
| 13 |
|
| 14 |
+
// Assets
|
| 15 |
import vpnImg from "../assests/vpn.jpg";
|
| 16 |
import torImg from "../assests/tor.png";
|
| 17 |
import i2pImg from "../assests/i2p.png";
|
|
|
|
| 28 |
import xss from "../assests/XXs.png";
|
| 29 |
import ChatAssistant from "./ChatAssistant";
|
| 30 |
|
| 31 |
+
// ---------- BCC threats ----------
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
const THREATS_BCC = {
|
| 33 |
VPN: {
|
| 34 |
title: "Virtual Private Network (VPN)",
|
| 35 |
+
desc: "VPNs create an encrypted tunnel between client and server, hiding source IPs and traffic content. Attackers use VPNs to evade detection and obfuscate origin.",
|
|
|
|
| 36 |
icon: <Shield size={44} className="text-[var(--accent)]" />,
|
| 37 |
image: vpnImg,
|
| 38 |
risk: "Medium",
|
|
|
|
| 40 |
},
|
| 41 |
TOR: {
|
| 42 |
title: "The Onion Router (TOR)",
|
| 43 |
+
desc: "TOR anonymizes traffic using layered encryption across relays; it is commonly used for command-and-control and data exfiltration.",
|
|
|
|
| 44 |
icon: <Lock size={44} className="text-[var(--accent)]" />,
|
| 45 |
image: torImg,
|
| 46 |
risk: "High",
|
|
|
|
| 48 |
},
|
| 49 |
I2P: {
|
| 50 |
title: "Invisible Internet Project (I2P)",
|
| 51 |
+
desc: "I2P aims for anonymous P2P connections and is used by advanced threat actors for hidden data transfer.",
|
|
|
|
| 52 |
icon: <EyeOff size={44} className="text-[var(--accent)]" />,
|
| 53 |
image: i2pImg,
|
| 54 |
risk: "High",
|
|
|
|
| 56 |
},
|
| 57 |
FREENET: {
|
| 58 |
title: "Freenet",
|
| 59 |
+
desc: "Freenet is a decentralized file-sharing network sometimes used for hosting malicious payloads and leaked data.",
|
|
|
|
| 60 |
icon: <Server size={44} className="text-[var(--accent)]" />,
|
| 61 |
image: freenetImg,
|
| 62 |
risk: "Medium",
|
|
|
|
| 64 |
},
|
| 65 |
ZERONET: {
|
| 66 |
title: "ZeroNet",
|
| 67 |
+
desc: "ZeroNet leverages Bitcoin identities and BitTorrent protocols for decentralized websites.",
|
|
|
|
| 68 |
icon: <Globe2 size={44} className="text-[var(--accent)]" />,
|
| 69 |
image: zeronetImg,
|
| 70 |
risk: "Medium",
|
|
|
|
| 72 |
},
|
| 73 |
};
|
| 74 |
|
| 75 |
+
// ---------- CICIDS threats ----------
|
| 76 |
const THREATS_CICIDS = {
|
| 77 |
"Benign": {
|
| 78 |
title: "Benign Traffic",
|
| 79 |
+
desc: "Normal, expected network activity produced by legitimate users and services.",
|
|
|
|
| 80 |
icon: <Globe size={44} className="text-[var(--accent)]" />,
|
| 81 |
risk: "Low",
|
| 82 |
usage: "Baseline traffic in datasets",
|
| 83 |
},
|
| 84 |
"DoS – Hulk": {
|
| 85 |
title: "DoS – Hulk",
|
| 86 |
+
desc: "HULK floods web servers with unique HTTP requests to exhaust resources using randomized parameters.",
|
|
|
|
| 87 |
icon: <Zap size={44} className="text-[var(--accent)]" />,
|
| 88 |
risk: "High",
|
| 89 |
image: hulkImg,
|
|
|
|
| 91 |
},
|
| 92 |
"DoS – SlowHTTPTest": {
|
| 93 |
title: "DoS – SlowHTTPTest",
|
| 94 |
+
desc: "Holds connections open by sending headers/data very slowly to exhaust server connection slots.",
|
|
|
|
| 95 |
icon: <FileWarning size={44} className="text-[var(--accent)]" />,
|
| 96 |
risk: "High",
|
| 97 |
image: httpImg,
|
|
|
|
| 99 |
},
|
| 100 |
"DoS – GoldenEye": {
|
| 101 |
title: "DoS – GoldenEye",
|
| 102 |
+
desc: "Application-layer DoS that sends malicious HTTP traffic to overwhelm servers.",
|
|
|
|
| 103 |
icon: <Cpu size={44} className="text-[var(--accent)]" />,
|
| 104 |
risk: "High",
|
| 105 |
usage: "Application-layer DoS",
|
| 106 |
},
|
| 107 |
"DoS – Slowloris": {
|
| 108 |
title: "DoS – Slowloris",
|
| 109 |
+
desc: "Sends partial HTTP headers to keep many connections open and starve web servers of resources.",
|
|
|
|
| 110 |
icon: <Key size={44} className="text-[var(--accent)]" />,
|
| 111 |
risk: "High",
|
| 112 |
image: slowlorisImg,
|
|
|
|
| 114 |
},
|
| 115 |
"FTP – BruteForce": {
|
| 116 |
title: "FTP – BruteForce",
|
| 117 |
+
desc: "Repeatedly trying username/password combinations to gain access to FTP services.",
|
|
|
|
| 118 |
icon: <GitPullRequest size={44} className="text-[var(--accent)]" />,
|
| 119 |
risk: "Medium",
|
| 120 |
image: bruteforceImg,
|
|
|
|
| 122 |
},
|
| 123 |
"SSH – BruteForce": {
|
| 124 |
title: "SSH – BruteForce",
|
| 125 |
+
desc: "SSH brute-force attacks attempt many credential combinations to gain shell access.",
|
|
|
|
| 126 |
icon: <Lock size={44} className="text-[var(--accent)]" />,
|
| 127 |
risk: "Medium",
|
| 128 |
image: bruteforceImg,
|
|
|
|
| 130 |
},
|
| 131 |
"DDoS – HOIC": {
|
| 132 |
title: "DDoS – HOIC",
|
| 133 |
+
desc: "Volumetric DDoS tool generating massive concurrent HTTP requests to disrupt services.",
|
|
|
|
| 134 |
icon: <CloudSnow size={44} className="text-[var(--accent)]" />,
|
| 135 |
risk: "High",
|
| 136 |
image: hoic,
|
|
|
|
| 138 |
},
|
| 139 |
"DDoS – LOIC UDP": {
|
| 140 |
title: "DDoS – LOIC UDP",
|
| 141 |
+
desc: "Generates UDP floods that saturate link bandwidth and cause service interruption.",
|
|
|
|
| 142 |
icon: <Zap size={44} className="text-[var(--accent)]" />,
|
| 143 |
risk: "High",
|
| 144 |
image: loic,
|
|
|
|
| 146 |
},
|
| 147 |
"Brute Force – Web": {
|
| 148 |
title: "Brute Force – Web",
|
| 149 |
+
desc: "Targets web login endpoints or weak authentication mechanisms to gain access.",
|
|
|
|
| 150 |
icon: <AlertTriangle size={44} className="text-[var(--accent)]" />,
|
| 151 |
risk: "Medium",
|
| 152 |
image: web,
|
|
|
|
| 154 |
},
|
| 155 |
"Brute Force – XSS": {
|
| 156 |
title: "Brute Force – XSS",
|
| 157 |
+
desc: "Web attack attempts focusing on injection-like payloads and attempted exploitation.",
|
|
|
|
| 158 |
icon: <FileWarning size={44} className="text-[var(--accent)]" />,
|
| 159 |
risk: "Medium",
|
| 160 |
image: xss,
|
|
|
|
| 162 |
},
|
| 163 |
"SQL Injection": {
|
| 164 |
title: "SQL Injection",
|
| 165 |
+
desc: "Attack where user-supplied data manipulates backend SQL queries to steal data.",
|
|
|
|
| 166 |
icon: <Database size={44} className="text-[var(--accent)]" />,
|
| 167 |
risk: "High",
|
| 168 |
image: sql,
|
|
|
|
| 170 |
},
|
| 171 |
"Infiltration": {
|
| 172 |
title: "Infiltration",
|
| 173 |
+
desc: "Multi-stage intrusion where attackers establish footholds and move laterally.",
|
|
|
|
| 174 |
icon: <Shield size={44} className="text-[var(--accent)]" />,
|
| 175 |
risk: "High",
|
| 176 |
image: xss,
|
|
|
|
| 178 |
},
|
| 179 |
"Bot": {
|
| 180 |
title: "Bot Activity",
|
| 181 |
+
desc: "Automated traffic including scraping, credential stuffing, or crawler-like behaviour.",
|
|
|
|
| 182 |
icon: <Globe size={44} className="text-[var(--accent)]" />,
|
| 183 |
risk: "Medium",
|
| 184 |
image: web,
|
|
|
|
| 186 |
},
|
| 187 |
};
|
| 188 |
|
|
|
|
| 189 |
const makeChartDataFromCounts = (counts) =>
|
| 190 |
Object.entries(counts).map(([k, v]) => ({ name: k, value: v }));
|
| 191 |
|
|
|
|
| 192 |
const CICIDS_COUNTS = {
|
| 193 |
+
BENIGN: 40000, Bot: 8000, "DoS attacks-Hulk": 8000,
|
| 194 |
+
"DoS attacks-SlowHTTPTest": 8000, Infilteration: 8000,
|
| 195 |
+
"DoS attacks-GoldenEye": 8000, "DoS attacks-Slowloris": 8000,
|
| 196 |
+
"FTP-BruteForce": 8000, "SSH-Bruteforce": 8000,
|
| 197 |
+
"DDOS attack-HOIC": 8000, "DDOS attack-LOIC-UDP": 1730,
|
| 198 |
+
"Brute Force -Web": 611, "Brute Force -XSS": 230, "SQL Injection": 87,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 199 |
};
|
| 200 |
|
|
|
|
| 201 |
export default function ThreatIntelInteractive() {
|
| 202 |
+
const [mode, setMode] = useState("bcc");
|
| 203 |
+
const [selected, setSelected] = useState("VPN");
|
| 204 |
const [compare, setCompare] = useState("TOR");
|
| 205 |
const [query, setQuery] = useState("");
|
| 206 |
const [filterRisk, setFilterRisk] = useState("All");
|
|
|
|
| 212 |
const threats = mode === "bcc" ? THREATS_BCC : THREATS_CICIDS;
|
| 213 |
const keys = useMemo(() => Object.keys(threats), [threats]);
|
| 214 |
|
|
|
|
| 215 |
React.useEffect(() => {
|
| 216 |
setSelected((prev) => (keys.includes(prev) ? prev : keys[0]));
|
| 217 |
setCompare((prev) => (keys.includes(prev) ? prev : keys[1] || keys[0]));
|
|
|
|
| 228 |
|
| 229 |
const threat = threats[selected] || {};
|
| 230 |
const compareThreat = threats[compare] || {};
|
| 231 |
+
const chartData = mode === "cicids" ? makeChartDataFromCounts(CICIDS_COUNTS).slice(0, 8) : [];
|
| 232 |
|
| 233 |
const quizQuestions = {
|
|
|
|
| 234 |
...(mode === "bcc"
|
| 235 |
? {
|
| 236 |
VPN: { question: "VPNs primarily encrypt traffic at which layer?", answer: "Network" },
|
|
|
|
| 240 |
ZERONET: { question: "ZeroNet commonly pairs with which crypto identity?", answer: "Bitcoin" },
|
| 241 |
}
|
| 242 |
: {
|
|
|
|
| 243 |
Benign: { question: "Benign traffic indicates what?", answer: "Normal" },
|
| 244 |
"DoS – Hulk": { question: "HULK targets which layer?", answer: "Application" },
|
| 245 |
"DoS – SlowHTTPTest": { question: "SlowHTTPTest is what type of DoS?", answer: "Low-and-slow" },
|
|
|
|
| 259 |
|
| 260 |
const handleQuizSubmit = () => {
|
| 261 |
const q = quizQuestions[selected];
|
| 262 |
+
if (!q) return;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 263 |
const correct = quizAnswer.trim().toLowerCase() === q.answer.toLowerCase();
|
| 264 |
setShowQuizResult({ correct });
|
| 265 |
setTimeout(() => setShowQuizResult(null), 2500);
|
| 266 |
};
|
| 267 |
|
| 268 |
return (
|
| 269 |
+
// Replace the opening div and header section with this:
|
| 270 |
+
<div className="min-h-screen bg-transparent text-slate-200 p-4 md:p-8 lg:-ml-10 pt-20 lg:pt-10 relative overflow-x-hidden">
|
| 271 |
+
{/* Subtle background glow */}
|
| 272 |
+
<div className="absolute inset-0 pointer-events-none"
|
| 273 |
+
style={{ background: "radial-gradient(circle at 50% 50%, rgba(0, 229, 255, 0.03) 0%, transparent 70%)" }} />
|
| 274 |
+
|
| 275 |
+
{/* Header Section */}
|
| 276 |
+
<div className="flex flex-col lg:flex-row lg:items-end justify-between gap-6 mb-8 relative z-10">
|
| 277 |
+
<div>
|
| 278 |
+
<h1 className="text-3xl md:text-5xl font-extrabold bg-gradient-to-r from-cyan-400 to-blue-500 text-transparent bg-clip-text filter drop-shadow-[0_0_8px_rgba(0,229,255,0.2)]">
|
| 279 |
+
Threat Intelligence
|
| 280 |
+
</h1>
|
| 281 |
+
<div className="flex items-center gap-2 mt-2">
|
| 282 |
+
<span className="w-2 h-2 rounded-full bg-cyan-500 animate-pulse"></span>
|
| 283 |
+
<p className="text-slate-500 font-mono text-[10px] uppercase tracking-[0.2em]">
|
| 284 |
+
Active Database: {mode === 'bcc' ? 'BCC-HTTP-2019' : 'CIC-IDS-2017'}
|
| 285 |
+
</p>
|
| 286 |
+
</div>
|
| 287 |
+
</div>
|
| 288 |
+
<button onClick={() => navigate(`/flow?type=${encodeURIComponent(selected)}`)}
|
| 289 |
+
className="px-6 py-2 bg-transparent border border-cyan-500/30 text-cyan-400 rounded hover:bg-cyan-500/10 transition-all font-bold text-xs uppercase tracking-widest">
|
| 290 |
+
Initialize Flow Analysis
|
| 291 |
+
</button>
|
| 292 |
+
</div>
|
| 293 |
+
|
| 294 |
+
{/* Control Bar */}
|
| 295 |
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6 relative z-10">
|
| 296 |
+
<div className="flex gap-2">
|
| 297 |
+
{["bcc", "cicids"].map((m) => (
|
| 298 |
+
<button key={m} onClick={() => setMode(m)}
|
| 299 |
+
className={`px-4 py-2 rounded border text-xs font-bold transition-all uppercase ${mode === m ? "bg-cyan-500 border-cyan-400 text-black shadow-[0_0_20px_rgba(0,229,255,0.4)]" : "bg-black/40 border-slate-800 text-slate-500 hover:border-slate-600"}`}>
|
| 300 |
+
{m} View
|
| 301 |
+
</button>
|
| 302 |
+
))}
|
| 303 |
+
</div>
|
| 304 |
+
<div className="flex gap-2">
|
| 305 |
+
<div className="relative flex-1">
|
| 306 |
+
<Search className="absolute left-3 top-1/2 -translate-y-1/2 text-slate-500" size={16} />
|
| 307 |
+
<input type="text" placeholder="FILTER THREATS..." value={query} onChange={(e) => setQuery(e.target.value)}
|
| 308 |
+
className="w-full bg-black/60 border border-slate-800 pl-10 pr-4 py-2 rounded text-sm text-cyan-400 outline-none focus:border-cyan-500/50 transition-all" />
|
| 309 |
+
</div>
|
| 310 |
+
<select value={filterRisk} onChange={(e) => setFilterRisk(e.target.value)}
|
| 311 |
+
className="bg-black/60 border border-slate-800 px-3 py-2 rounded text-xs text-slate-400 outline-none">
|
| 312 |
+
<option>All Risks</option><option>High</option><option>Medium</option><option>Low</option>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 313 |
</select>
|
| 314 |
</div>
|
| 315 |
</div>
|
| 316 |
|
| 317 |
+
{/* Selection Row */}
|
| 318 |
+
<div className="flex flex-wrap gap-2 mb-8 relative z-10">
|
|
|
|
|
|
|
|
|
|
|
|
|
| 319 |
{filteredKeys.map((key) => (
|
| 320 |
+
<button key={key} onClick={() => setSelected(key)}
|
| 321 |
+
className={`px-3 py-1.5 rounded border text-[10px] font-mono transition-all ${selected === key ? "bg-cyan-500/20 border-cyan-500 text-cyan-400" : "bg-black/20 border-slate-800 text-slate-500 hover:text-slate-300"}`}>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 322 |
{key}
|
| 323 |
</button>
|
| 324 |
))}
|
| 325 |
</div>
|
| 326 |
|
| 327 |
+
{/* Main Analysis Terminal */}
|
| 328 |
+
<Tilt tiltMaxAngleX={3} tiltMaxAngleY={3} scale={1.01}>
|
| 329 |
+
<div className="bg-black/40 border border-slate-800 rounded-xl p-6 mb-6 relative overflow-hidden backdrop-blur-sm">
|
| 330 |
+
<div className="absolute top-0 right-0 p-4 opacity-2">{threat.icon}</div>
|
| 331 |
+
<div className="flex flex-col md:flex-row gap-8 items-center">
|
| 332 |
+
<div className="w-32 h-32 flex-shrink-0">
|
| 333 |
+
<CircularProgressbar
|
| 334 |
+
value={threat.risk === "High" ? 90 : threat.risk === "Medium" ? 65 : 35}
|
| 335 |
+
text={threat.risk}
|
| 336 |
+
styles={buildStyles({ pathColor: "#06b6d4", textColor: "#06b6d4", trailColor: "#1e293b", textSize: '16px' })}
|
| 337 |
+
/>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 338 |
</div>
|
| 339 |
+
<div className="flex-1">
|
| 340 |
+
<h2 className="text-3xl font-bold text-white mb-2 tracking-tight">{threat.title}</h2>
|
| 341 |
+
<p className="text-slate-400 leading-relaxed text-sm md:text-base border-l-2 border-cyan-500/30 pl-4">
|
| 342 |
+
{threat.desc}
|
| 343 |
+
</p>
|
| 344 |
+
<div className="mt-4 inline-block px-3 py-1 rounded-full bg-cyan-500/10 border border-cyan-500/20 text-[10px] font-bold text-cyan-500 uppercase tracking-tighter">
|
| 345 |
+
Intelligence Source: {threat.usage}
|
| 346 |
+
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 347 |
</div>
|
| 348 |
+
</div>
|
| 349 |
+
</div>
|
|
|
|
|
|
|
|
|
|
| 350 |
</Tilt>
|
| 351 |
|
| 352 |
+
{/* Bottom Grid */}
|
| 353 |
+
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 relative z-10">
|
| 354 |
+
<div className="bg-black/40 border border-slate-800 rounded-xl p-6">
|
| 355 |
+
<div className="flex justify-between items-center mb-4">
|
| 356 |
+
<h4 className="text-xs font-bold text-slate-500 uppercase tracking-widest flex items-center gap-2"><Shield size={14}/> Compare Module</h4>
|
| 357 |
+
<select value={compare} onChange={(e) => setCompare(e.target.value)} className="bg-slate-900 border border-slate-800 px-2 py-1 rounded text-[10px] text-cyan-400">
|
| 358 |
+
{keys.map((k) => <option key={k} value={k}>{k}</option>)}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 359 |
</select>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 360 |
</div>
|
| 361 |
+
<h5 className="text-cyan-400 font-bold mb-1">{compareThreat.title}</h5>
|
| 362 |
+
<p className="text-slate-500 text-xs leading-relaxed">{compareThreat.desc?.substring(0, 180)}...</p>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 363 |
</div>
|
| 364 |
|
| 365 |
+
<div className="bg-gradient-to-br from-cyan-900/20 to-black/40 border border-cyan-500/20 rounded-xl p-6 shadow-[inset_0_0_20px_rgba(0,229,255,0.05)]">
|
| 366 |
+
<h4 className="text-xs font-bold text-cyan-400 uppercase tracking-widest mb-4 flex items-center gap-2"><Zap size={14} className="fill-cyan-400" /> Analyst Verification</h4>
|
| 367 |
+
<p className="text-slate-300 text-sm mb-4 italic">"{(quizQuestions[selected] && quizQuestions[selected].question) || "Select a threat to begin verification."}"</p>
|
| 368 |
+
<div className="flex gap-2">
|
| 369 |
+
<input type="text" placeholder="Enter response..." value={quizAnswer} onChange={(e) => setQuizAnswer(e.target.value)} className="flex-1 bg-black/60 border border-slate-800 rounded px-4 py-2 text-sm text-cyan-400 focus:border-cyan-500/50 outline-none" />
|
| 370 |
+
<button onClick={handleQuizSubmit} className="px-6 py-2 bg-cyan-500 text-black font-bold rounded text-xs uppercase hover:bg-cyan-400 transition-colors">Verify</button>
|
| 371 |
+
</div>
|
| 372 |
+
<AnimatePresence>
|
| 373 |
+
{showQuizResult !== null && (
|
| 374 |
+
<motion.div initial={{ opacity: 0, y: 10 }} animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0 }}
|
| 375 |
+
className={`mt-4 text-center py-2 rounded text-[10px] font-bold tracking-widest uppercase border ${showQuizResult.correct ? "bg-emerald-500/10 border-emerald-500/50 text-emerald-400" : "bg-rose-500/10 border-rose-500/50 text-rose-400"}`}>
|
| 376 |
+
{showQuizResult.correct ? "Identity Confirmed: Access Granted" : `Access Denied: Required Key "${quizQuestions[selected]?.answer}"`}
|
| 377 |
+
</motion.div>
|
| 378 |
+
)}
|
| 379 |
+
</AnimatePresence>
|
| 380 |
+
</div>
|
| 381 |
</div>
|
| 382 |
+
|
| 383 |
+
<div className="mt-12 opacity-50"><ChatAssistant /></div>
|
| 384 |
</div>
|
| 385 |
);
|
| 386 |
}
|
| 387 |
+
|
| 388 |
+
|