Spaces:
Sleeping
Sleeping
Commit
·
bf30d02
1
Parent(s):
d58a19f
chore: remove frontend folder from backend deployment
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- frontend/.gitignore +0 -24
- frontend/README.md +0 -16
- frontend/components.json +0 -22
- frontend/eslint.config.js +0 -29
- frontend/index.html +0 -17
- frontend/jsconfig.json +0 -8
- frontend/package-lock.json +0 -0
- frontend/package.json +0 -65
- frontend/postcss.config.js +0 -6
- frontend/public/images.ico +0 -0
- frontend/public/vite.svg +0 -1
- frontend/src/App.jsx +0 -93
- frontend/src/api.js +0 -183
- frontend/src/assests/Dos-HTTPtest.jpg +0 -0
- frontend/src/assests/Dos_Attacks-hulk.jpeg +0 -0
- frontend/src/assests/HOIC.jpeg +0 -0
- frontend/src/assests/LOic.jpeg +0 -0
- frontend/src/assests/heroBlocks.svg +0 -30
- frontend/src/assests/sql.jpeg +0 -0
- frontend/src/assests/vpn.jpg +0 -0
- frontend/src/assests/web.jpeg +0 -0
- frontend/src/components/Alerts.jsx +0 -36
- frontend/src/components/ConstellationBackground.jsx +0 -85
- frontend/src/components/FeatureGuard.jsx +0 -63
- frontend/src/components/Header.jsx +0 -16
- frontend/src/components/IPInfoModal.jsx +0 -182
- frontend/src/components/LoadingOverlay.jsx +0 -12
- frontend/src/components/MainLayout.jsx +0 -13
- frontend/src/components/Navbar.jsx +0 -128
- frontend/src/components/NeonShield.jsx +0 -87
- frontend/src/components/ProtectedRoute.jsx +0 -10
- frontend/src/components/Sidebar.jsx +0 -324
- frontend/src/components/TopStatusBar.jsx +0 -13
- frontend/src/components/dashboard/Controls.jsx +0 -55
- frontend/src/components/dashboard/LiveDashboard.jsx +0 -254
- frontend/src/components/dashboard/LiveTable.jsx +0 -168
- frontend/src/components/dashboard/Sparkline.jsx +0 -28
- frontend/src/components/dashboard/StatsPanel.jsx +0 -104
- frontend/src/components/dashboard/ThreatFeed.jsx +0 -39
- frontend/src/components/dashboard/ThreatTimeline.jsx +0 -27
- frontend/src/components/dashboard/TopCountries.jsx +0 -26
- frontend/src/components/dashboard/TopIPs.jsx +0 -26
- frontend/src/components/ui/accordion.jsx +0 -43
- frontend/src/components/ui/badge.jsx +0 -34
- frontend/src/components/ui/button.jsx +0 -47
- frontend/src/components/ui/card.jsx +0 -50
- frontend/src/components/ui/input.jsx +0 -19
- frontend/src/components/ui/scroll-area.jsx +0 -38
- frontend/src/components/ui/separator.jsx +0 -23
- frontend/src/components/ui/switch.jsx +0 -24
frontend/.gitignore
DELETED
|
@@ -1,24 +0,0 @@
|
|
| 1 |
-
# Logs
|
| 2 |
-
logs
|
| 3 |
-
*.log
|
| 4 |
-
npm-debug.log*
|
| 5 |
-
yarn-debug.log*
|
| 6 |
-
yarn-error.log*
|
| 7 |
-
pnpm-debug.log*
|
| 8 |
-
lerna-debug.log*
|
| 9 |
-
|
| 10 |
-
node_modules
|
| 11 |
-
dist
|
| 12 |
-
dist-ssr
|
| 13 |
-
*.local
|
| 14 |
-
|
| 15 |
-
# Editor directories and files
|
| 16 |
-
.vscode/*
|
| 17 |
-
!.vscode/extensions.json
|
| 18 |
-
.idea
|
| 19 |
-
.DS_Store
|
| 20 |
-
*.suo
|
| 21 |
-
*.ntvs*
|
| 22 |
-
*.njsproj
|
| 23 |
-
*.sln
|
| 24 |
-
*.sw?
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
frontend/README.md
DELETED
|
@@ -1,16 +0,0 @@
|
|
| 1 |
-
# React + Vite
|
| 2 |
-
|
| 3 |
-
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
|
| 4 |
-
|
| 5 |
-
Currently, two official plugins are available:
|
| 6 |
-
|
| 7 |
-
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) (or [oxc](https://oxc.rs) when used in [rolldown-vite](https://vite.dev/guide/rolldown)) for Fast Refresh
|
| 8 |
-
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
|
| 9 |
-
|
| 10 |
-
## React Compiler
|
| 11 |
-
|
| 12 |
-
The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).
|
| 13 |
-
|
| 14 |
-
## Expanding the ESLint configuration
|
| 15 |
-
|
| 16 |
-
If you are developing a production application, we recommend using TypeScript with type-aware lint rules enabled. Check out the [TS template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) for information on how to integrate TypeScript and [`typescript-eslint`](https://typescript-eslint.io) in your project.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
frontend/components.json
DELETED
|
@@ -1,22 +0,0 @@
|
|
| 1 |
-
{
|
| 2 |
-
"$schema": "https://ui.shadcn.com/schema.json",
|
| 3 |
-
"style": "default",
|
| 4 |
-
"rsc": false,
|
| 5 |
-
"tsx": false,
|
| 6 |
-
"tailwind": {
|
| 7 |
-
"config": "tailwind.config.js",
|
| 8 |
-
"css": "src/index.css",
|
| 9 |
-
"baseColor": "slate",
|
| 10 |
-
"cssVariables": true,
|
| 11 |
-
"prefix": ""
|
| 12 |
-
},
|
| 13 |
-
"iconLibrary": "lucide",
|
| 14 |
-
"aliases": {
|
| 15 |
-
"components": "@/components",
|
| 16 |
-
"utils": "@/lib/utils",
|
| 17 |
-
"ui": "@/components/ui",
|
| 18 |
-
"lib": "@/lib",
|
| 19 |
-
"hooks": "@/hooks"
|
| 20 |
-
},
|
| 21 |
-
"registries": {}
|
| 22 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
frontend/eslint.config.js
DELETED
|
@@ -1,29 +0,0 @@
|
|
| 1 |
-
import js from '@eslint/js'
|
| 2 |
-
import globals from 'globals'
|
| 3 |
-
import reactHooks from 'eslint-plugin-react-hooks'
|
| 4 |
-
import reactRefresh from 'eslint-plugin-react-refresh'
|
| 5 |
-
import { defineConfig, globalIgnores } from 'eslint/config'
|
| 6 |
-
|
| 7 |
-
export default defineConfig([
|
| 8 |
-
globalIgnores(['dist']),
|
| 9 |
-
{
|
| 10 |
-
files: ['**/*.{js,jsx}'],
|
| 11 |
-
extends: [
|
| 12 |
-
js.configs.recommended,
|
| 13 |
-
reactHooks.configs['recommended-latest'],
|
| 14 |
-
reactRefresh.configs.vite,
|
| 15 |
-
],
|
| 16 |
-
languageOptions: {
|
| 17 |
-
ecmaVersion: 2020,
|
| 18 |
-
globals: globals.browser,
|
| 19 |
-
parserOptions: {
|
| 20 |
-
ecmaVersion: 'latest',
|
| 21 |
-
ecmaFeatures: { jsx: true },
|
| 22 |
-
sourceType: 'module',
|
| 23 |
-
},
|
| 24 |
-
},
|
| 25 |
-
rules: {
|
| 26 |
-
'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }],
|
| 27 |
-
},
|
| 28 |
-
},
|
| 29 |
-
])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
frontend/index.html
DELETED
|
@@ -1,17 +0,0 @@
|
|
| 1 |
-
<!doctype html>
|
| 2 |
-
<html lang="en">
|
| 3 |
-
<head>
|
| 4 |
-
<meta charset="UTF-8" />
|
| 5 |
-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
| 6 |
-
<link
|
| 7 |
-
rel="stylesheet"
|
| 8 |
-
href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
|
| 9 |
-
/>
|
| 10 |
-
<link rel="shortcut icon" href="/images.ico" />
|
| 11 |
-
<title>NIDS Cyber Security</title>
|
| 12 |
-
</head>
|
| 13 |
-
<body>
|
| 14 |
-
<div id="root"></div>
|
| 15 |
-
<script type="module" src="/src/main.jsx"></script>
|
| 16 |
-
</body>
|
| 17 |
-
</html>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
frontend/jsconfig.json
DELETED
|
@@ -1,8 +0,0 @@
|
|
| 1 |
-
{
|
| 2 |
-
"compilerOptions": {
|
| 3 |
-
"baseUrl": "./src",
|
| 4 |
-
"paths": {
|
| 5 |
-
"@/*": ["*"]
|
| 6 |
-
}
|
| 7 |
-
}
|
| 8 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
frontend/package-lock.json
DELETED
|
The diff for this file is too large to render.
See raw diff
|
|
|
frontend/package.json
DELETED
|
@@ -1,65 +0,0 @@
|
|
| 1 |
-
{
|
| 2 |
-
"name": "frontend",
|
| 3 |
-
"private": true,
|
| 4 |
-
"version": "0.0.0",
|
| 5 |
-
"type": "module",
|
| 6 |
-
"scripts": {
|
| 7 |
-
"dev": "vite",
|
| 8 |
-
"build": "vite build",
|
| 9 |
-
"lint": "eslint .",
|
| 10 |
-
"preview": "vite preview",
|
| 11 |
-
"tailwind-init": "tailwindcss init -p"
|
| 12 |
-
},
|
| 13 |
-
"dependencies": {
|
| 14 |
-
"@radix-ui/react-accordion": "^1.2.12",
|
| 15 |
-
"@radix-ui/react-scroll-area": "^1.2.10",
|
| 16 |
-
"@radix-ui/react-separator": "^1.1.8",
|
| 17 |
-
"@radix-ui/react-slot": "^1.2.4",
|
| 18 |
-
"@radix-ui/react-switch": "^1.2.6",
|
| 19 |
-
"@radix-ui/react-tabs": "^1.1.13",
|
| 20 |
-
"@react-three/drei": "^10.7.6",
|
| 21 |
-
"@react-three/fiber": "^9.4.0",
|
| 22 |
-
"axios": "^1.13.1",
|
| 23 |
-
"class-variance-authority": "^0.7.1",
|
| 24 |
-
"clsx": "^2.1.1",
|
| 25 |
-
"firebase": "^12.5.0",
|
| 26 |
-
"framer-motion": "^12.23.24",
|
| 27 |
-
"html2canvas": "^1.4.1",
|
| 28 |
-
"jspdf": "^3.0.3",
|
| 29 |
-
"leaflet": "^1.9.4",
|
| 30 |
-
"lodash": "^4.17.21",
|
| 31 |
-
"lucide-react": "^0.552.0",
|
| 32 |
-
"prop-types": "^15.8.1",
|
| 33 |
-
"react": "^19.1.1",
|
| 34 |
-
"react-circular-progressbar": "^2.2.0",
|
| 35 |
-
"react-dom": "^19.1.1",
|
| 36 |
-
"react-draggable": "^4.5.0",
|
| 37 |
-
"react-gauge-chart": "^0.5.1",
|
| 38 |
-
"react-hot-toast": "^2.6.0",
|
| 39 |
-
"react-is": "^19.2.0",
|
| 40 |
-
"react-leaflet": "^5.0.0",
|
| 41 |
-
"react-parallax-tilt": "^1.7.313",
|
| 42 |
-
"react-router-dom": "^7.9.5",
|
| 43 |
-
"react-simple-maps": "^3.0.0",
|
| 44 |
-
"recharts": "^3.3.0",
|
| 45 |
-
"socket.io-client": "^4.8.1",
|
| 46 |
-
"tailwind-merge": "^3.4.0",
|
| 47 |
-
"tailwindcss-animate": "^1.0.7",
|
| 48 |
-
"three": "^0.181.1",
|
| 49 |
-
"xlsx": "^0.18.5"
|
| 50 |
-
},
|
| 51 |
-
"devDependencies": {
|
| 52 |
-
"@eslint/js": "^9.36.0",
|
| 53 |
-
"@types/react": "^19.1.16",
|
| 54 |
-
"@types/react-dom": "^19.1.9",
|
| 55 |
-
"@vitejs/plugin-react": "^5.0.4",
|
| 56 |
-
"autoprefixer": "^10.4.21",
|
| 57 |
-
"eslint": "^9.36.0",
|
| 58 |
-
"eslint-plugin-react-hooks": "^5.2.0",
|
| 59 |
-
"eslint-plugin-react-refresh": "^0.4.22",
|
| 60 |
-
"globals": "^16.4.0",
|
| 61 |
-
"postcss": "^8.5.6",
|
| 62 |
-
"tailwindcss": "^3.4.18",
|
| 63 |
-
"vite": "^7.1.7"
|
| 64 |
-
}
|
| 65 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
frontend/postcss.config.js
DELETED
|
@@ -1,6 +0,0 @@
|
|
| 1 |
-
export default {
|
| 2 |
-
plugins: {
|
| 3 |
-
tailwindcss: {},
|
| 4 |
-
autoprefixer: {},
|
| 5 |
-
},
|
| 6 |
-
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
frontend/public/images.ico
DELETED
|
Binary file (4.29 kB)
|
|
|
frontend/public/vite.svg
DELETED
frontend/src/App.jsx
DELETED
|
@@ -1,93 +0,0 @@
|
|
| 1 |
-
import { BrowserRouter as Router, Routes, Route, useLocation, Navigate } 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 |
-
|
| 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";
|
| 14 |
-
import FlowPage from "./pages/FlowPage";
|
| 15 |
-
import SettingsPage from "./pages/SettingsPage";
|
| 16 |
-
import InfoPage from "./pages/InfoPage";
|
| 17 |
-
import ThreatIntel from "./pages/ThreatIntel";
|
| 18 |
-
import Reports from "./pages/ReportsPage";
|
| 19 |
-
import TrafficPage from "./pages/TrafficPage";
|
| 20 |
-
import IncidentsPage from "./pages/IncidentsPage";
|
| 21 |
-
import SystemPage from "./pages/SystemPage";
|
| 22 |
-
import ResponsePage from "./pages/ResponsePage";
|
| 23 |
-
import MlmodelPage from "./pages/MLModelsPage";
|
| 24 |
-
import ConstellationBackground from "./components/ConstellationBackground";
|
| 25 |
-
import SamplePred from "./pages/MLAttackSamplesPage";
|
| 26 |
-
import Aichat from "./pages/ChatAssistant";
|
| 27 |
-
import MainLayout from "./components/MainLayout";
|
| 28 |
-
|
| 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 |
-
<Sidebar collapsed={collapsed} setCollapsed={setCollapsed} />
|
| 44 |
-
)}
|
| 45 |
-
|
| 46 |
-
<main
|
| 47 |
-
className={`flex-1 overflow-y-auto overflow-x-hidden p-6 min-h-0 transition-all duration-300
|
| 48 |
-
${collapsed ? "ml-20" : "ml-64"}
|
| 49 |
-
`}
|
| 50 |
-
>
|
| 51 |
-
{/* Wrap the Routes in the FeatureGuard.
|
| 52 |
-
It will only trigger the popup if the current path is in localOnlyRoutes
|
| 53 |
-
AND the user is not on localhost.
|
| 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>} />
|
| 70 |
-
<Route path="/incidents" element={<ProtectedRoute><MainLayout><IncidentsPage /></MainLayout></ProtectedRoute>} />
|
| 71 |
-
<Route path="/threats" element={<ProtectedRoute><MainLayout><ThreatIntel /></MainLayout></ProtectedRoute>} />
|
| 72 |
-
<Route path="/reports" element={<ProtectedRoute><MainLayout><Reports /></MainLayout></ProtectedRoute>} />
|
| 73 |
-
<Route path="/response" element={<ProtectedRoute><MainLayout><ResponsePage /></MainLayout></ProtectedRoute>} />
|
| 74 |
-
<Route path="/mlmodels" element={<ProtectedRoute><MainLayout><MlmodelPage /></MainLayout></ProtectedRoute>} />
|
| 75 |
-
<Route path="/chat" element={<ProtectedRoute><Aichat /></ProtectedRoute>} />
|
| 76 |
-
</Routes>
|
| 77 |
-
</FeatureGuard>
|
| 78 |
-
</main>
|
| 79 |
-
</div>
|
| 80 |
-
);
|
| 81 |
-
}
|
| 82 |
-
|
| 83 |
-
export default function App() {
|
| 84 |
-
return (
|
| 85 |
-
<AuthProvider>
|
| 86 |
-
<LiveDataProvider>
|
| 87 |
-
<Router>
|
| 88 |
-
<AppLayout />
|
| 89 |
-
</Router>
|
| 90 |
-
</LiveDataProvider>
|
| 91 |
-
</AuthProvider>
|
| 92 |
-
);
|
| 93 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
frontend/src/api.js
DELETED
|
@@ -1,183 +0,0 @@
|
|
| 1 |
-
// ===============================================
|
| 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 |
-
try {
|
| 14 |
-
const res = await fetch(url, { ...options, signal: controller.signal });
|
| 15 |
-
|
| 16 |
-
if (!res.ok) {
|
| 17 |
-
const errText = await res.text();
|
| 18 |
-
throw new Error(`HTTP ${res.status}: ${errText}`);
|
| 19 |
-
}
|
| 20 |
-
return await res.json();
|
| 21 |
-
} catch (err) {
|
| 22 |
-
console.error(`❌ API Error [${url}]:`, err.message);
|
| 23 |
-
|
| 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 |
-
// 🚀 LIVE CAPTURE
|
| 37 |
-
// -------------------------------------------------------------
|
| 38 |
-
export async function startSniffer(iface = null) {
|
| 39 |
-
const q = iface ? `?iface=${iface}` : "";
|
| 40 |
-
return safeFetch(`${BASE_URL}/api/live/start${q}`);
|
| 41 |
-
}
|
| 42 |
-
|
| 43 |
-
export async function stopSniffer() {
|
| 44 |
-
return safeFetch(`${BASE_URL}/api/live/stop`);
|
| 45 |
-
}
|
| 46 |
-
|
| 47 |
-
export async function getStatus() {
|
| 48 |
-
return safeFetch(`${BASE_URL}/api/live/status`);
|
| 49 |
-
}
|
| 50 |
-
|
| 51 |
-
// MODEL-AWARE 🎯
|
| 52 |
-
export async function getRecent(model, limit = 300) {
|
| 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 |
-
// MODEL-AWARE 🎯
|
| 62 |
-
export async function getStats(model) {
|
| 63 |
-
return safeFetch(`${BASE_URL}/api/live/stats?model=${model}`);
|
| 64 |
-
}
|
| 65 |
-
|
| 66 |
-
// -------------------------------------------------------------
|
| 67 |
-
// 🧾 LOGS (MODEL-AWARE)
|
| 68 |
-
// -------------------------------------------------------------
|
| 69 |
-
export function download_logs(model) {
|
| 70 |
-
window.location.href = `${BASE_URL}/api/logs/download?model=${model}`;
|
| 71 |
-
}
|
| 72 |
-
|
| 73 |
-
export async function clearLogs(model, n = 50) {
|
| 74 |
-
return safeFetch(
|
| 75 |
-
`${BASE_URL}/api/logs/clear?model=${model}&n=${n}`,
|
| 76 |
-
{ method: "POST" }
|
| 77 |
-
);
|
| 78 |
-
}
|
| 79 |
-
|
| 80 |
-
export async function clearByPrediction(model, pred) {
|
| 81 |
-
return safeFetch(
|
| 82 |
-
`${BASE_URL}/api/logs/clear_pred?model=${model}&pred=${pred}`,
|
| 83 |
-
{ method: "POST" }
|
| 84 |
-
);
|
| 85 |
-
}
|
| 86 |
-
|
| 87 |
-
export async function deleteOne(model, index) {
|
| 88 |
-
return safeFetch(
|
| 89 |
-
`${BASE_URL}/api/logs/delete_one?model=${model}&index=${index}`,
|
| 90 |
-
{ method: "POST" }
|
| 91 |
-
);
|
| 92 |
-
}
|
| 93 |
-
|
| 94 |
-
// -------------------------------------------------------------
|
| 95 |
-
// 🌍 GEO + ALERTS
|
| 96 |
-
// -------------------------------------------------------------
|
| 97 |
-
export async function getGeoData() {
|
| 98 |
-
return safeFetch(`${BASE_URL}/api/geo/recent`, {}, 20000, 2);
|
| 99 |
-
}
|
| 100 |
-
|
| 101 |
-
export async function getAlerts() {
|
| 102 |
-
return safeFetch(`${BASE_URL}/api/alerts`);
|
| 103 |
-
}
|
| 104 |
-
|
| 105 |
-
// -------------------------------------------------------------
|
| 106 |
-
// 🧩 MODEL CONTROL
|
| 107 |
-
// -------------------------------------------------------------
|
| 108 |
-
export async function getActiveModel() {
|
| 109 |
-
return safeFetch(`${BASE_URL}/api/model/active`);
|
| 110 |
-
}
|
| 111 |
-
|
| 112 |
-
export async function switchModel(model) {
|
| 113 |
-
return safeFetch(`${BASE_URL}/api/model/select`, {
|
| 114 |
-
method: "POST",
|
| 115 |
-
headers: { "Content-Type": "application/json" },
|
| 116 |
-
body: JSON.stringify({ model }),
|
| 117 |
-
});
|
| 118 |
-
}
|
| 119 |
-
|
| 120 |
-
export async function getModelHealth() {
|
| 121 |
-
return safeFetch(`${BASE_URL}/api/model/health`);
|
| 122 |
-
}
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
// -------------------------------------------------------------
|
| 126 |
-
// 🤖 AI HELPERS
|
| 127 |
-
// -------------------------------------------------------------
|
| 128 |
-
export async function explainThreat(event) {
|
| 129 |
-
return safeFetch(`${BASE_URL}/api/ai/explain`, {
|
| 130 |
-
method: "POST",
|
| 131 |
-
headers: { "Content-Type": "application/json" },
|
| 132 |
-
body: JSON.stringify(event),
|
| 133 |
-
});
|
| 134 |
-
}
|
| 135 |
-
|
| 136 |
-
export async function getAISummary(model, n = 200) {
|
| 137 |
-
return safeFetch(
|
| 138 |
-
`${BASE_URL}/api/ai/summary?model=${encodeURIComponent(model)}&n=${n}`
|
| 139 |
-
);
|
| 140 |
-
}
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
export async function sendMessageToAI(message) {
|
| 144 |
-
try {
|
| 145 |
-
const res = await fetch(`${BASE_URL}/api/chat`, {
|
| 146 |
-
method: "POST",
|
| 147 |
-
headers: { "Content-Type": "application/json" },
|
| 148 |
-
body: JSON.stringify({ message })
|
| 149 |
-
});
|
| 150 |
-
|
| 151 |
-
if (!res.ok) throw new Error("Chat API failed");
|
| 152 |
-
return res.json();
|
| 153 |
-
} catch (err) {
|
| 154 |
-
console.error("Chat error:", err);
|
| 155 |
-
return { reply: "⚠ AI Assistant not responding." };
|
| 156 |
-
}
|
| 157 |
-
}
|
| 158 |
-
|
| 159 |
-
// ➤ Offline CSV/PCAP prediction
|
| 160 |
-
export const offlinePredictAPI = async (file, model) => {
|
| 161 |
-
const formData = new FormData();
|
| 162 |
-
formData.append("file", file);
|
| 163 |
-
formData.append("model", model);
|
| 164 |
-
|
| 165 |
-
// FIX: Using BASE_URL for live deployment
|
| 166 |
-
const res = await fetch(`${BASE_URL}/api/offline/predict`, {
|
| 167 |
-
method: "POST",
|
| 168 |
-
body: formData,
|
| 169 |
-
});
|
| 170 |
-
|
| 171 |
-
return res.json();
|
| 172 |
-
};
|
| 173 |
-
|
| 174 |
-
// ➤ Get PDF forensic report download link
|
| 175 |
-
export const downloadOfflineReport = () => {
|
| 176 |
-
// FIX: Using BASE_URL for live deployment
|
| 177 |
-
window.open(`${BASE_URL}/api/offline/report`, "_blank");
|
| 178 |
-
};
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
// -------------------------------------------------------------
|
| 182 |
-
export { BASE_URL };
|
| 183 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
frontend/src/assests/Dos-HTTPtest.jpg
DELETED
|
Binary file (65.9 kB)
|
|
|
frontend/src/assests/Dos_Attacks-hulk.jpeg
DELETED
|
Binary file (10.9 kB)
|
|
|
frontend/src/assests/HOIC.jpeg
DELETED
|
Binary file (11.1 kB)
|
|
|
frontend/src/assests/LOic.jpeg
DELETED
|
Binary file (6.24 kB)
|
|
|
frontend/src/assests/heroBlocks.svg
DELETED
frontend/src/assests/sql.jpeg
DELETED
|
Binary file (7.92 kB)
|
|
|
frontend/src/assests/vpn.jpg
DELETED
|
Binary file (15.7 kB)
|
|
|
frontend/src/assests/web.jpeg
DELETED
|
Binary file (10.2 kB)
|
|
|
frontend/src/components/Alerts.jsx
DELETED
|
@@ -1,36 +0,0 @@
|
|
| 1 |
-
import React, { useContext } from "react";
|
| 2 |
-
import { AlertTriangle, ShieldAlert, Info } from "lucide-react";
|
| 3 |
-
import { AlertContext } from "../context/AlertContext.jsx";
|
| 4 |
-
|
| 5 |
-
export default function Alerts() {
|
| 6 |
-
const { alerts, removeAlert } = useContext(AlertContext);
|
| 7 |
-
|
| 8 |
-
const COLORS = {
|
| 9 |
-
info: "border-blue-400/40 bg-blue-500/10 text-blue-300",
|
| 10 |
-
warn: "border-yellow-400/40 bg-yellow-500/10 text-yellow-300",
|
| 11 |
-
danger: "border-red-400/40 bg-red-500/10 text-red-300",
|
| 12 |
-
};
|
| 13 |
-
|
| 14 |
-
const ICONS = {
|
| 15 |
-
info: <Info size={16} />,
|
| 16 |
-
warn: <AlertTriangle size={16} />,
|
| 17 |
-
danger: <ShieldAlert size={16} />,
|
| 18 |
-
};
|
| 19 |
-
|
| 20 |
-
return (
|
| 21 |
-
<div className="fixed bottom-4 right-4 z-[9999] space-y-2 w-64 max-h-96 overflow-y-auto pointer-events-none">
|
| 22 |
-
{alerts.map(a => (
|
| 23 |
-
<div
|
| 24 |
-
key={a.id}
|
| 25 |
-
className={`pointer-events-auto cyber-card flex items-center gap-2 px-3 py-2
|
| 26 |
-
border rounded-xl shadow-neon animate-fade
|
| 27 |
-
${COLORS[a.type]}`}
|
| 28 |
-
onClick={() => removeAlert(a.id)}
|
| 29 |
-
>
|
| 30 |
-
{ICONS[a.type]}
|
| 31 |
-
<span className="text-xs">{a.msg}</span>
|
| 32 |
-
</div>
|
| 33 |
-
))}
|
| 34 |
-
</div>
|
| 35 |
-
);
|
| 36 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
frontend/src/components/ConstellationBackground.jsx
DELETED
|
@@ -1,85 +0,0 @@
|
|
| 1 |
-
import { useEffect, useRef } from "react";
|
| 2 |
-
|
| 3 |
-
export default function ConstellationBackground() {
|
| 4 |
-
const canvasRef = useRef(null);
|
| 5 |
-
|
| 6 |
-
useEffect(() => {
|
| 7 |
-
const canvas = canvasRef.current;
|
| 8 |
-
const ctx = canvas.getContext("2d");
|
| 9 |
-
|
| 10 |
-
let particles = [];
|
| 11 |
-
const count = 140; // number of stars
|
| 12 |
-
|
| 13 |
-
// resize canvas
|
| 14 |
-
function resize() {
|
| 15 |
-
canvas.width = window.innerWidth;
|
| 16 |
-
canvas.height = window.innerHeight;
|
| 17 |
-
}
|
| 18 |
-
resize();
|
| 19 |
-
window.addEventListener("resize", resize);
|
| 20 |
-
|
| 21 |
-
// create particles
|
| 22 |
-
for (let i = 0; i < count; i++) {
|
| 23 |
-
particles.push({
|
| 24 |
-
x: Math.random() * canvas.width,
|
| 25 |
-
y: Math.random() * canvas.height,
|
| 26 |
-
vx: (Math.random() - 0.5) * 0.3,
|
| 27 |
-
vy: (Math.random() - 0.5) * 0.3,
|
| 28 |
-
size: Math.random() * 2 + 1,
|
| 29 |
-
});
|
| 30 |
-
}
|
| 31 |
-
|
| 32 |
-
// animation loop
|
| 33 |
-
function animate() {
|
| 34 |
-
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
| 35 |
-
|
| 36 |
-
// draw particles
|
| 37 |
-
particles.forEach((p) => {
|
| 38 |
-
p.x += p.vx;
|
| 39 |
-
p.y += p.vy;
|
| 40 |
-
|
| 41 |
-
// soft bounce
|
| 42 |
-
if (p.x < 0 || p.x > canvas.width) p.vx *= -1;
|
| 43 |
-
if (p.y < 0 || p.y > canvas.height) p.vy *= -1;
|
| 44 |
-
|
| 45 |
-
ctx.beginPath();
|
| 46 |
-
ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
|
| 47 |
-
ctx.fillStyle = "rgba(0, 220, 255, 0.9)";
|
| 48 |
-
ctx.shadowBlur = 10;
|
| 49 |
-
ctx.shadowColor = "#66e0ff";
|
| 50 |
-
ctx.fill();
|
| 51 |
-
});
|
| 52 |
-
|
| 53 |
-
// draw connecting lines
|
| 54 |
-
for (let i = 0; i < count; i++) {
|
| 55 |
-
for (let j = i + 1; j < count; j++) {
|
| 56 |
-
const a = particles[i];
|
| 57 |
-
const b = particles[j];
|
| 58 |
-
const dist = Math.hypot(a.x - b.x, a.y - b.y);
|
| 59 |
-
|
| 60 |
-
if (dist < 140) {
|
| 61 |
-
ctx.strokeStyle = `rgba(120, 255, 255, ${1 - dist / 140})`;
|
| 62 |
-
ctx.lineWidth = 0.7;
|
| 63 |
-
ctx.beginPath();
|
| 64 |
-
ctx.moveTo(a.x, a.y);
|
| 65 |
-
ctx.lineTo(b.x, b.y);
|
| 66 |
-
ctx.stroke();
|
| 67 |
-
}
|
| 68 |
-
}
|
| 69 |
-
}
|
| 70 |
-
|
| 71 |
-
requestAnimationFrame(animate);
|
| 72 |
-
}
|
| 73 |
-
|
| 74 |
-
animate();
|
| 75 |
-
|
| 76 |
-
return () => window.removeEventListener("resize", resize);
|
| 77 |
-
}, []);
|
| 78 |
-
|
| 79 |
-
return (
|
| 80 |
-
<canvas
|
| 81 |
-
ref={canvasRef}
|
| 82 |
-
className="fixed inset-0 -z-10 pointer-events-none"
|
| 83 |
-
/>
|
| 84 |
-
);
|
| 85 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
frontend/src/components/FeatureGuard.jsx
DELETED
|
@@ -1,63 +0,0 @@
|
|
| 1 |
-
// FeatureGuard.jsx
|
| 2 |
-
import React, { useState, useEffect } from "react";
|
| 3 |
-
import { AlertTriangle, Github, Monitor, Terminal } from "lucide-react";
|
| 4 |
-
|
| 5 |
-
const FeatureGuard = ({ children, requireLocal = false }) => {
|
| 6 |
-
const [isLocal, setIsLocal] = useState(true);
|
| 7 |
-
const [showPopup, setShowPopup] = useState(false);
|
| 8 |
-
|
| 9 |
-
useEffect(() => {
|
| 10 |
-
// Detect environment
|
| 11 |
-
const local = window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1";
|
| 12 |
-
setIsLocal(local);
|
| 13 |
-
|
| 14 |
-
// Only show popup if it's required AND we are not local
|
| 15 |
-
if (requireLocal && !local) {
|
| 16 |
-
setShowPopup(true);
|
| 17 |
-
}
|
| 18 |
-
}, [requireLocal]);
|
| 19 |
-
|
| 20 |
-
// If we are local, or the feature doesn't require local access, show content normally
|
| 21 |
-
if (isLocal || !requireLocal) return <>{children}</>;
|
| 22 |
-
|
| 23 |
-
return (
|
| 24 |
-
<div className="relative min-h-screen">
|
| 25 |
-
{/* Background content is blurred for "Demo" look */}
|
| 26 |
-
<div className="blur-lg pointer-events-none opacity-50">
|
| 27 |
-
{children}
|
| 28 |
-
</div>
|
| 29 |
-
|
| 30 |
-
{showPopup && (
|
| 31 |
-
<div className="fixed inset-0 z-[9999] flex items-center justify-center bg-black/70 backdrop-blur-md p-4">
|
| 32 |
-
<div className="bg-[#0b1120] border-2 border-cyan-500/50 rounded-2xl p-8 max-w-lg w-full shadow-[0_0_50px_rgba(6,182,212,0.4)]">
|
| 33 |
-
<div className="flex items-center gap-3 text-amber-400 mb-4">
|
| 34 |
-
<AlertTriangle size={28} />
|
| 35 |
-
<h2 className="text-2xl font-bold">Local Agent Required</h2>
|
| 36 |
-
</div>
|
| 37 |
-
|
| 38 |
-
<p className="text-gray-300 mb-6">
|
| 39 |
-
This page requires <b>Live Network Sniffing</b>. To protect privacy, browsers cannot access your hardware. Please run the project locally.
|
| 40 |
-
</p>
|
| 41 |
-
|
| 42 |
-
<div className="bg-black/40 rounded-lg p-4 font-mono text-sm border border-cyan-900/50 mb-6">
|
| 43 |
-
<p className="text-emerald-400">$ git clone [Your-Repo-URL]</p>
|
| 44 |
-
<p className="text-blue-400">$ cd nids-project</p>
|
| 45 |
-
<p className="text-purple-400">$ python app.py --mode live</p>
|
| 46 |
-
</div>
|
| 47 |
-
|
| 48 |
-
<div className="flex gap-3">
|
| 49 |
-
<button onClick={() => window.open('https://github.com/your-repo', '_blank')} className="flex-1 bg-cyan-600 py-3 rounded-xl font-bold hover:bg-cyan-500 transition-all flex items-center justify-center gap-2">
|
| 50 |
-
<Github size={18}/> GitHub
|
| 51 |
-
</button>
|
| 52 |
-
<button onClick={() => window.history.back()} className="flex-1 bg-gray-800 py-3 rounded-xl font-bold hover:bg-gray-700 transition-all text-gray-400">
|
| 53 |
-
Go Back
|
| 54 |
-
</button>
|
| 55 |
-
</div>
|
| 56 |
-
</div>
|
| 57 |
-
</div>
|
| 58 |
-
)}
|
| 59 |
-
</div>
|
| 60 |
-
);
|
| 61 |
-
};
|
| 62 |
-
|
| 63 |
-
export default FeatureGuard;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
frontend/src/components/Header.jsx
DELETED
|
@@ -1,16 +0,0 @@
|
|
| 1 |
-
// src/components/Header.jsx
|
| 2 |
-
import React from "react";
|
| 3 |
-
import { useAuth } from "../context/AuthContext";
|
| 4 |
-
|
| 5 |
-
export default function Header() {
|
| 6 |
-
const { user, logout } = useAuth();
|
| 7 |
-
return (
|
| 8 |
-
<div className="flex items-center justify-between p-3">
|
| 9 |
-
<div className="text-cyan-300 font-bold">NIDS Cyber Defense</div>
|
| 10 |
-
<div className="flex items-center gap-3">
|
| 11 |
-
<div className="text-sm text-slate-300">{user?.email}</div>
|
| 12 |
-
<button onClick={logout} className="px-3 py-1 bg-rose-500/10 rounded">Logout</button>
|
| 13 |
-
</div>
|
| 14 |
-
</div>
|
| 15 |
-
);
|
| 16 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
frontend/src/components/IPInfoModal.jsx
DELETED
|
@@ -1,182 +0,0 @@
|
|
| 1 |
-
import React, { useEffect, useState } from "react";
|
| 2 |
-
|
| 3 |
-
export default function IPInfoModal({ ip, onClose }) {
|
| 4 |
-
const [info, setInfo] = useState(null);
|
| 5 |
-
const [loading, setLoading] = useState(true);
|
| 6 |
-
const [risk, setRisk] = useState({
|
| 7 |
-
level: "Unknown",
|
| 8 |
-
score: 0,
|
| 9 |
-
reason: "Unverified",
|
| 10 |
-
});
|
| 11 |
-
|
| 12 |
-
useEffect(() => {
|
| 13 |
-
if (!ip) return;
|
| 14 |
-
|
| 15 |
-
// Reset state for new IP
|
| 16 |
-
setInfo(null);
|
| 17 |
-
setRisk({ level: "Unknown", score: 0, reason: "Unverified" });
|
| 18 |
-
setLoading(true);
|
| 19 |
-
|
| 20 |
-
// ✅ Private / Local IP detection
|
| 21 |
-
const privateRanges = [
|
| 22 |
-
/^127\./,
|
| 23 |
-
/^10\./,
|
| 24 |
-
/^192\.168\./,
|
| 25 |
-
/^172\.(1[6-9]|2[0-9]|3[0-1])\./,
|
| 26 |
-
/^239\./, // multicast
|
| 27 |
-
];
|
| 28 |
-
|
| 29 |
-
if (privateRanges.some((r) => r.test(ip))) {
|
| 30 |
-
setInfo({
|
| 31 |
-
city: "Local Network",
|
| 32 |
-
region: "Private or Multicast Range",
|
| 33 |
-
country_name: "N/A",
|
| 34 |
-
org: "Local Device",
|
| 35 |
-
asn: "LAN",
|
| 36 |
-
version: "IPv4",
|
| 37 |
-
latitude: "-",
|
| 38 |
-
longitude: "-",
|
| 39 |
-
});
|
| 40 |
-
setRisk({
|
| 41 |
-
level: "Low",
|
| 42 |
-
score: 5,
|
| 43 |
-
reason: "Private / local or multicast IP",
|
| 44 |
-
});
|
| 45 |
-
setLoading(false);
|
| 46 |
-
return;
|
| 47 |
-
}
|
| 48 |
-
|
| 49 |
-
// ✅ Backend lookup (new route)
|
| 50 |
-
fetch(`http://127.0.0.1:5000/api/geo/resolve?ip=${ip}`)
|
| 51 |
-
.then(async (res) => {
|
| 52 |
-
if (!res.ok) {
|
| 53 |
-
const errData = await res.json().catch(() => ({}));
|
| 54 |
-
throw new Error(errData.error || "Lookup failed");
|
| 55 |
-
}
|
| 56 |
-
return res.json();
|
| 57 |
-
})
|
| 58 |
-
.then((data) => {
|
| 59 |
-
setInfo(data);
|
| 60 |
-
setRisk({
|
| 61 |
-
level:
|
| 62 |
-
data.country === "Local"
|
| 63 |
-
? "Low"
|
| 64 |
-
: data.country === "Unknown"
|
| 65 |
-
? "Medium"
|
| 66 |
-
: "Low",
|
| 67 |
-
score: data.country === "Unknown" ? 40 : 15,
|
| 68 |
-
reason: `Detected in ${data.country || "Unknown"}`,
|
| 69 |
-
});
|
| 70 |
-
})
|
| 71 |
-
.catch((err) => {
|
| 72 |
-
console.warn("⚠️ Geo lookup failed:", err.message);
|
| 73 |
-
setInfo({
|
| 74 |
-
city: "Unknown",
|
| 75 |
-
country_name: "Unknown",
|
| 76 |
-
org: "No data",
|
| 77 |
-
latitude: "-",
|
| 78 |
-
longitude: "-",
|
| 79 |
-
});
|
| 80 |
-
setRisk({
|
| 81 |
-
level: "Low",
|
| 82 |
-
score: 10,
|
| 83 |
-
reason: "No threat detected / lookup failed",
|
| 84 |
-
});
|
| 85 |
-
})
|
| 86 |
-
.finally(() => setLoading(false));
|
| 87 |
-
}, [ip]);
|
| 88 |
-
|
| 89 |
-
if (!ip) return null;
|
| 90 |
-
|
| 91 |
-
// ✅ Helper for dynamic color
|
| 92 |
-
const riskColor =
|
| 93 |
-
risk.level === "High"
|
| 94 |
-
? "text-red-400"
|
| 95 |
-
: risk.level === "Medium"
|
| 96 |
-
? "text-yellow-400"
|
| 97 |
-
: "text-green-400";
|
| 98 |
-
|
| 99 |
-
const riskBarColor =
|
| 100 |
-
risk.level === "High"
|
| 101 |
-
? "bg-red-500"
|
| 102 |
-
: risk.level === "Medium"
|
| 103 |
-
? "bg-yellow-400"
|
| 104 |
-
: "bg-green-400";
|
| 105 |
-
|
| 106 |
-
return (
|
| 107 |
-
<div className="fixed inset-0 bg-black/70 flex justify-center items-center z-50">
|
| 108 |
-
<div className="bg-cyber-panel/90 border border-cyan-400/30 rounded-xl p-5 w-96 text-slate-200 shadow-lg relative">
|
| 109 |
-
<h2 className="text-lg font-semibold text-cyan-400 mb-3">
|
| 110 |
-
🌐 IP Info — {ip}
|
| 111 |
-
</h2>
|
| 112 |
-
|
| 113 |
-
{loading ? (
|
| 114 |
-
<p className="text-slate-400 text-sm animate-pulse">
|
| 115 |
-
Fetching IP intelligence...
|
| 116 |
-
</p>
|
| 117 |
-
) : info ? (
|
| 118 |
-
<div className="space-y-2 text-sm">
|
| 119 |
-
<p>
|
| 120 |
-
<b>City:</b> {info.city || "Unknown"}
|
| 121 |
-
</p>
|
| 122 |
-
<p>
|
| 123 |
-
<b>Region:</b> {info.region || "Unknown"}
|
| 124 |
-
</p>
|
| 125 |
-
<p>
|
| 126 |
-
<b>Country:</b> {info.country_name || "Unknown"}
|
| 127 |
-
</p>
|
| 128 |
-
<p>
|
| 129 |
-
<b>ISP:</b> {info.org || "Unknown"}
|
| 130 |
-
</p>
|
| 131 |
-
<p>
|
| 132 |
-
<b>ASN:</b> {info.asn || "N/A"}
|
| 133 |
-
</p>
|
| 134 |
-
<p>
|
| 135 |
-
<b>IP Type:</b> {info.version}
|
| 136 |
-
</p>
|
| 137 |
-
<p>
|
| 138 |
-
<b>Coordinates:</b> {info.latitude}, {info.longitude}
|
| 139 |
-
</p>
|
| 140 |
-
</div>
|
| 141 |
-
) : (
|
| 142 |
-
<p className="text-red-400 text-sm">
|
| 143 |
-
Could not fetch IP intelligence data.
|
| 144 |
-
</p>
|
| 145 |
-
)}
|
| 146 |
-
|
| 147 |
-
{/* Risk / Threat assessment */}
|
| 148 |
-
{!loading && (
|
| 149 |
-
<div className="mt-4 border-t border-cyan-400/10 pt-3">
|
| 150 |
-
<h4 className="text-sm text-slate-300 mb-1">Threat Assessment</h4>
|
| 151 |
-
<div className="flex items-center justify-between">
|
| 152 |
-
<span className={`text-sm font-semibold ${riskColor}`}>
|
| 153 |
-
{risk.level} Risk ({risk.score}%)
|
| 154 |
-
</span>
|
| 155 |
-
<span className="text-xs text-slate-400 max-w-[180px] text-right">
|
| 156 |
-
{risk.reason}
|
| 157 |
-
</span>
|
| 158 |
-
</div>
|
| 159 |
-
|
| 160 |
-
{/* Risk Progress Bar */}
|
| 161 |
-
<div className="w-full bg-slate-700/40 rounded-full mt-2 h-2 overflow-hidden">
|
| 162 |
-
<div
|
| 163 |
-
className={`h-2 rounded-full ${riskBarColor}`}
|
| 164 |
-
style={{ width: `${risk.score}%` }}
|
| 165 |
-
/>
|
| 166 |
-
</div>
|
| 167 |
-
</div>
|
| 168 |
-
)}
|
| 169 |
-
|
| 170 |
-
{/* Close button */}
|
| 171 |
-
<button
|
| 172 |
-
onClick={onClose}
|
| 173 |
-
className="mt-5 px-3 py-2 bg-cyan-500/20 hover:bg-cyan-500/30 rounded-lg border border-cyan-400/30 w-full text-sm"
|
| 174 |
-
>
|
| 175 |
-
Close
|
| 176 |
-
</button>
|
| 177 |
-
</div>
|
| 178 |
-
</div>
|
| 179 |
-
);
|
| 180 |
-
}
|
| 181 |
-
|
| 182 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
frontend/src/components/LoadingOverlay.jsx
DELETED
|
@@ -1,12 +0,0 @@
|
|
| 1 |
-
// src/components/LoadingOverlay.jsx
|
| 2 |
-
export default function LoadingOverlay({ show, message = "Processing..." }) {
|
| 3 |
-
if (!show) return null;
|
| 4 |
-
return (
|
| 5 |
-
<div className="fixed inset-0 bg-black/70 backdrop-blur-sm flex items-center justify-center z-50">
|
| 6 |
-
<div className="text-center">
|
| 7 |
-
<div className="animate-spin rounded-full h-12 w-12 border-t-4 border-cyan-400 mb-3 mx-auto"></div>
|
| 8 |
-
<p className="text-cyan-300 font-semibold">{message}</p>
|
| 9 |
-
</div>
|
| 10 |
-
</div>
|
| 11 |
-
);
|
| 12 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
frontend/src/components/MainLayout.jsx
DELETED
|
@@ -1,13 +0,0 @@
|
|
| 1 |
-
import React from "react";
|
| 2 |
-
import Navbar from "./Navbar";
|
| 3 |
-
import ChatAssistant from "../pages/ChatAssistant";
|
| 4 |
-
|
| 5 |
-
export default function MainLayout({ children }) {
|
| 6 |
-
return (
|
| 7 |
-
<div className="relative">
|
| 8 |
-
<Navbar />
|
| 9 |
-
{children}
|
| 10 |
-
<ChatAssistant />
|
| 11 |
-
</div>
|
| 12 |
-
);
|
| 13 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
frontend/src/components/Navbar.jsx
DELETED
|
@@ -1,128 +0,0 @@
|
|
| 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 |
-
|
| 6 |
-
export default function Navbar({ onRefresh }) {
|
| 7 |
-
const { user, logout } = useAuth();
|
| 8 |
-
const navigate = useNavigate();
|
| 9 |
-
const location = useLocation();
|
| 10 |
-
|
| 11 |
-
const [searchQuery, setSearchQuery] = useState("");
|
| 12 |
-
const [profileOpen, setProfileOpen] = useState(false);
|
| 13 |
-
|
| 14 |
-
return (
|
| 15 |
-
<nav className="relative z-20 border-b border-accent/20 backdrop-blur-xl py-4 shadow-[0_4px_20px_rgba(0,0,0,0.45)]">
|
| 16 |
-
<div className="container mx-auto px-4 py-3 mb-2 flex items-center gap-6">
|
| 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={20} className="text-accent" />
|
| 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-4 text-sm text-slate-300 ml-4">
|
| 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"
|
| 53 |
-
}`}
|
| 54 |
-
>
|
| 55 |
-
{item.label}
|
| 56 |
-
</button>
|
| 57 |
-
))}
|
| 58 |
-
</div>
|
| 59 |
-
|
| 60 |
-
{/* Right side */}
|
| 61 |
-
<div className="ml-auto flex items-center gap-4">
|
| 62 |
-
|
| 63 |
-
{/* Search */}
|
| 64 |
-
<div className="hidden md: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">
|
| 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 dashboard..."
|
| 71 |
-
className="bg-transparent text-xs text-slate-200 placeholder:text-slate-500 focus:outline-none w-40"
|
| 72 |
-
/>
|
| 73 |
-
</div>
|
| 74 |
-
|
| 75 |
-
{/* Profile */}
|
| 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={16} className="text-accent" />
|
| 83 |
-
</div>
|
| 84 |
-
<div className="hidden sm:flex flex-col items-start max-w-[130px]">
|
| 85 |
-
<span className="text-[10px] text-slate-400 uppercase tracking-[0.16em]">
|
| 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-2 w-44 rounded-xl border border-accent/30 bg-[#020617]/95 shadow-lg py-1 text-sm z-40">
|
| 97 |
-
{onRefresh && (
|
| 98 |
-
<>
|
| 99 |
-
<button
|
| 100 |
-
onClick={() => {
|
| 101 |
-
onRefresh();
|
| 102 |
-
setProfileOpen(false);
|
| 103 |
-
}}
|
| 104 |
-
className="w-full flex items-center gap-2 px-3 py-2 hover:bg-accent/10 text-slate-200"
|
| 105 |
-
>
|
| 106 |
-
<RefreshCcw size={14} className="text-accent" />
|
| 107 |
-
<span>Reload data</span>
|
| 108 |
-
</button>
|
| 109 |
-
<div className="border-t border-slate-700/60 my-1" />
|
| 110 |
-
</>
|
| 111 |
-
)}
|
| 112 |
-
|
| 113 |
-
<button
|
| 114 |
-
onClick={() => logout()}
|
| 115 |
-
className="w-full flex items-center gap-2 px-3 py-2 hover:bg-red-500/15 text-red-300"
|
| 116 |
-
>
|
| 117 |
-
<LogOut size={14} />
|
| 118 |
-
<span>Logout</span>
|
| 119 |
-
</button>
|
| 120 |
-
</div>
|
| 121 |
-
)}
|
| 122 |
-
</div>
|
| 123 |
-
|
| 124 |
-
</div>
|
| 125 |
-
</div>
|
| 126 |
-
</nav>
|
| 127 |
-
);
|
| 128 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
frontend/src/components/NeonShield.jsx
DELETED
|
@@ -1,87 +0,0 @@
|
|
| 1 |
-
import { Canvas, useFrame } from "@react-three/fiber";
|
| 2 |
-
import * as THREE from "three";
|
| 3 |
-
import { useRef } from "react";
|
| 4 |
-
|
| 5 |
-
/* ------------- Rotating Packet Ring ------------- */
|
| 6 |
-
function PacketRing() {
|
| 7 |
-
const ring = useRef();
|
| 8 |
-
const pulse = useRef();
|
| 9 |
-
|
| 10 |
-
useFrame(({ clock }) => {
|
| 11 |
-
const t = clock.getElapsedTime();
|
| 12 |
-
|
| 13 |
-
ring.current.rotation.y = t * 0.6;
|
| 14 |
-
|
| 15 |
-
const r = 1.5;
|
| 16 |
-
pulse.current.position.x = r * Math.cos(t * 1.5);
|
| 17 |
-
pulse.current.position.y = r * Math.sin(t * 1.5);
|
| 18 |
-
});
|
| 19 |
-
|
| 20 |
-
return (
|
| 21 |
-
<group>
|
| 22 |
-
<mesh ref={ring} rotation={[0.5, 0.4, 0]}>
|
| 23 |
-
<torusGeometry args={[1.5, 0.07, 32, 100]} />
|
| 24 |
-
<meshStandardMaterial
|
| 25 |
-
emissive="#00e5ff"
|
| 26 |
-
emissiveIntensity={1.7}
|
| 27 |
-
color="#00e5ff"
|
| 28 |
-
/>
|
| 29 |
-
</mesh>
|
| 30 |
-
|
| 31 |
-
<mesh ref={pulse}>
|
| 32 |
-
<sphereGeometry args={[0.13, 32, 32]} />
|
| 33 |
-
<meshStandardMaterial emissive="#a855f7" emissiveIntensity={2} />
|
| 34 |
-
</mesh>
|
| 35 |
-
</group>
|
| 36 |
-
);
|
| 37 |
-
}
|
| 38 |
-
|
| 39 |
-
/* ------------- Floating Network Nodes ------------- */
|
| 40 |
-
function NetworkNodes() {
|
| 41 |
-
const group = useRef();
|
| 42 |
-
|
| 43 |
-
useFrame(({ clock }) => {
|
| 44 |
-
group.current.rotation.y = clock.getElapsedTime() * 0.3;
|
| 45 |
-
});
|
| 46 |
-
|
| 47 |
-
const nodes = Array.from({ length: 12 }).map(() => [
|
| 48 |
-
(Math.random() - 0.5) * 2,
|
| 49 |
-
(Math.random() - 0.5) * 1.5,
|
| 50 |
-
(Math.random() - 0.5) * 1.2,
|
| 51 |
-
]);
|
| 52 |
-
|
| 53 |
-
return (
|
| 54 |
-
<group ref={group}>
|
| 55 |
-
{nodes.map((pos, i) => (
|
| 56 |
-
<mesh position={pos} key={i}>
|
| 57 |
-
<sphereGeometry args={[0.1, 24, 24]} />
|
| 58 |
-
<meshStandardMaterial
|
| 59 |
-
emissive="#00e5ff"
|
| 60 |
-
emissiveIntensity={1.3}
|
| 61 |
-
color="#00e5ff"
|
| 62 |
-
/>
|
| 63 |
-
</mesh>
|
| 64 |
-
))}
|
| 65 |
-
</group>
|
| 66 |
-
);
|
| 67 |
-
}
|
| 68 |
-
|
| 69 |
-
/* ------------- Main Scene ------------- */
|
| 70 |
-
export default function NIDS3D() {
|
| 71 |
-
return (
|
| 72 |
-
<Canvas
|
| 73 |
-
style={{ width: "100%", height: "100%" }}
|
| 74 |
-
camera={{ position: [2.5, 2.5, 5], fov: 30 }}
|
| 75 |
-
>
|
| 76 |
-
<ambientLight intensity={0.5} />
|
| 77 |
-
<pointLight position={[3, 3, 3]} intensity={1} />
|
| 78 |
-
|
| 79 |
-
<group rotation={[0.5, 0.4, 0]}>
|
| 80 |
-
<PacketRing />
|
| 81 |
-
<NetworkNodes />
|
| 82 |
-
</group>
|
| 83 |
-
</Canvas>
|
| 84 |
-
);
|
| 85 |
-
}
|
| 86 |
-
|
| 87 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
frontend/src/components/ProtectedRoute.jsx
DELETED
|
@@ -1,10 +0,0 @@
|
|
| 1 |
-
// src/components/ProtectedRoute.jsx
|
| 2 |
-
import React from "react";
|
| 3 |
-
import { Navigate } from "react-router-dom";
|
| 4 |
-
import { useAuth } from "../context/AuthContext";
|
| 5 |
-
|
| 6 |
-
export default function ProtectedRoute({ children }) {
|
| 7 |
-
const { user, authLoading } = useAuth();
|
| 8 |
-
if (authLoading) return null; // or a spinner
|
| 9 |
-
return user ? children : <Navigate to="/login" replace />;
|
| 10 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
frontend/src/components/Sidebar.jsx
DELETED
|
@@ -1,324 +0,0 @@
|
|
| 1 |
-
// src/components/Sidebar.jsx
|
| 2 |
-
import React, { useEffect, useRef, useState } from "react";
|
| 3 |
-
import { NavLink } from "react-router-dom";
|
| 4 |
-
import {
|
| 5 |
-
Activity,
|
| 6 |
-
AlertTriangle,
|
| 7 |
-
FileText,
|
| 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("");
|
| 31 |
-
|
| 32 |
-
// clock tick
|
| 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 |
-
// mini-constellation canvas (node+line) - optimized for sidebar
|
| 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)); // scale nodes by size
|
| 62 |
-
|
| 63 |
-
// Resize handler
|
| 64 |
-
function resize() {
|
| 65 |
-
width = sidebar.clientWidth;
|
| 66 |
-
height = sidebar.clientHeight;
|
| 67 |
-
const dpr = Math.max(1, window.devicePixelRatio || 1);
|
| 68 |
-
canvas.width = Math.floor(width * dpr);
|
| 69 |
-
canvas.height = Math.floor(height * dpr);
|
| 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 |
-
const n = COUNT;
|
| 84 |
-
for (let i = 0; i < n; i++) {
|
| 85 |
-
particles.push({
|
| 86 |
-
x: rand(10, width - 10),
|
| 87 |
-
y: rand(10, height - 10),
|
| 88 |
-
vx: rand(-0.15, 0.15),
|
| 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 |
-
// move
|
| 123 |
-
p.x += p.vx;
|
| 124 |
-
p.y += p.vy;
|
| 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} /> },
|
| 170 |
-
{ to: "/threats", label: "Threat Intelligence", icon: <Info size={18} /> },
|
| 171 |
-
{ to: "/alerts", label: "Alerts", icon: <AlertTriangle size={18} /> },
|
| 172 |
-
{ to: "/incidents", label: "Offline Detection", icon: <BrickWallShield size={18} /> },
|
| 173 |
-
{ to: "/traffic", label: "Traffic Analysis", icon: <TrafficCone size={18} /> },
|
| 174 |
-
{ to: "/response", label: "Response System", icon: <GitBranchMinus size={18} /> },
|
| 175 |
-
{ to: "/mlmodels", label: "ML Model", icon: <MoonStar size={18} /> },
|
| 176 |
-
{ to: "/reports", label: "Reports", icon: <FileText size={18} /> },
|
| 177 |
-
{ to: "/system", label: "System Info", icon: <MonitorCog size={18} /> },
|
| 178 |
-
{ to: "/settings", label: "Settings", icon: <Settings size={18} /> },
|
| 179 |
-
];
|
| 180 |
-
|
| 181 |
-
return (
|
| 182 |
-
<aside
|
| 183 |
-
ref={sidebarRef}
|
| 184 |
-
aria-label="Main navigation"
|
| 185 |
-
className={`fixed top-0 left-0 h-screen
|
| 186 |
-
${collapsed ? "w-20" : "w-64"}
|
| 187 |
-
bg-[#030b17]/50 backdrop-blur-xl
|
| 188 |
-
flex flex-col z-50 transition-all duration-300
|
| 189 |
-
border-r-2 border-[var(--accent)]/14
|
| 190 |
-
shadow-[inset_0_0_18px_rgba(0,229,255,0.06)]
|
| 191 |
-
`}
|
| 192 |
-
>
|
| 193 |
-
{/* background canvas (mini constellation) */}
|
| 194 |
-
<canvas
|
| 195 |
-
ref={canvasRef}
|
| 196 |
-
className="pointer-events-none absolute inset-0 -z-10"
|
| 197 |
-
aria-hidden="true"
|
| 198 |
-
/>
|
| 199 |
-
|
| 200 |
-
{/* double neon border / decorative rings */}
|
| 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 |
-
{/* HEADER */}
|
| 210 |
-
<div className="p-4 border-b border-[var(--accent)]/12 relative overflow-hidden shrink-0">
|
| 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)] leading-tight">NIDS</div>
|
| 217 |
-
<div className="text-[11px] text-slate-400">Cyber Defense</div>
|
| 218 |
-
</div>
|
| 219 |
-
)}
|
| 220 |
-
</div>
|
| 221 |
-
|
| 222 |
-
<div className="flex items-center gap-2">
|
| 223 |
-
<span
|
| 224 |
-
className={`w-2.5 h-2.5 rounded-full ${
|
| 225 |
-
navigator.onLine ? "bg-emerald-400 animate-pulse" : "bg-rose-500 animate-ping"
|
| 226 |
-
}`}
|
| 227 |
-
/>
|
| 228 |
-
{!collapsed && (
|
| 229 |
-
<div className="text-xs text-slate-400">{navigator.onLine ? "LIVE" : "OFFLINE"}</div>
|
| 230 |
-
)}
|
| 231 |
-
</div>
|
| 232 |
-
</div>
|
| 233 |
-
|
| 234 |
-
{!collapsed && (
|
| 235 |
-
<div className="mt-3 text-xs text-slate-400">
|
| 236 |
-
<div className="flex justify-between items-center">
|
| 237 |
-
<span className="text-[var(--accent)]">Host:</span>
|
| 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 |
-
<div className="w-5 h-5 flex items-center justify-center text-[var(--accent)]">
|
| 269 |
-
{icon}
|
| 270 |
-
</div>
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
frontend/src/components/TopStatusBar.jsx
DELETED
|
@@ -1,13 +0,0 @@
|
|
| 1 |
-
// src/components/TopStatusBar.jsx
|
| 2 |
-
export default function TopStatusBar({ running, packetCount, iface, lastUpdate }) {
|
| 3 |
-
return (
|
| 4 |
-
<div className="flex items-center justify-between bg-cyan-900/20 px-4 py-2 rounded-lg border border-cyan-400/20 mb-4">
|
| 5 |
-
<span className="text-sm text-cyan-300">
|
| 6 |
-
{running ? "🟢 Capturing Packets" : "🔴 Capture Stopped"}
|
| 7 |
-
</span>
|
| 8 |
-
<span className="text-sm text-slate-400">
|
| 9 |
-
Packets: <b className="text-cyan-300">{packetCount}</b> | Interface: {iface || "auto"} | Updated: {lastUpdate || "—"}
|
| 10 |
-
</span>
|
| 11 |
-
</div>
|
| 12 |
-
);
|
| 13 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
frontend/src/components/dashboard/Controls.jsx
DELETED
|
@@ -1,55 +0,0 @@
|
|
| 1 |
-
import React, { useContext, useState, useEffect } from "react";
|
| 2 |
-
import { LiveDataContext } from "../../context/DataContext";
|
| 3 |
-
import { startSniffer, stopSniffer, getStatus } from "../../api";
|
| 4 |
-
|
| 5 |
-
export default function Controls() {
|
| 6 |
-
const { running, setRunning } = useContext(LiveDataContext);
|
| 7 |
-
const [loading, setLoading] = useState(false);
|
| 8 |
-
|
| 9 |
-
useEffect(() => {
|
| 10 |
-
(async () => {
|
| 11 |
-
const res = await getStatus();
|
| 12 |
-
setRunning(res?.running || false);
|
| 13 |
-
})();
|
| 14 |
-
}, []);
|
| 15 |
-
|
| 16 |
-
const handleStart = async () => {
|
| 17 |
-
setLoading(true);
|
| 18 |
-
// 🔥 RESET BUFFER on START
|
| 19 |
-
window.__EVENT_BUFFER_RESET = true;
|
| 20 |
-
|
| 21 |
-
await startSniffer();
|
| 22 |
-
setRunning(true);
|
| 23 |
-
setLoading(false);
|
| 24 |
-
};
|
| 25 |
-
|
| 26 |
-
const handleStop = async () => {
|
| 27 |
-
setLoading(true);
|
| 28 |
-
await stopSniffer();
|
| 29 |
-
setRunning(false);
|
| 30 |
-
setLoading(false);
|
| 31 |
-
};
|
| 32 |
-
|
| 33 |
-
return (
|
| 34 |
-
<div className="flex gap-4">
|
| 35 |
-
<button
|
| 36 |
-
onClick={handleStart}
|
| 37 |
-
disabled={running || loading}
|
| 38 |
-
className={`px-4 py-2 rounded-lg border transition-all ${
|
| 39 |
-
running ? "bg-green-600/20 border-green-500 text-green-400" : "border-green-400 text-green-300 hover:bg-green-500/20"
|
| 40 |
-
}`}
|
| 41 |
-
>
|
| 42 |
-
🚀 Start Capture
|
| 43 |
-
</button>
|
| 44 |
-
<button
|
| 45 |
-
onClick={handleStop}
|
| 46 |
-
disabled={!running || loading}
|
| 47 |
-
className={`px-4 py-2 rounded-lg border transition-all ${
|
| 48 |
-
!running ? "bg-red-500/20 border-red-500 text-red-400" : "border-red-400 text-red-300 hover:bg-red-500/30"
|
| 49 |
-
}`}
|
| 50 |
-
>
|
| 51 |
-
🛑 Stop Capture
|
| 52 |
-
</button>
|
| 53 |
-
</div>
|
| 54 |
-
);
|
| 55 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
frontend/src/components/dashboard/LiveDashboard.jsx
DELETED
|
@@ -1,254 +0,0 @@
|
|
| 1 |
-
import React, { useEffect, useState, useContext, useRef } from "react";
|
| 2 |
-
import Controls from "./Controls.jsx";
|
| 3 |
-
import LiveTable from "./LiveTable.jsx";
|
| 4 |
-
import ChatAssistant from "../../pages/ChatAssistant.jsx";
|
| 5 |
-
import StatsPanel from "./StatsPanel.jsx";
|
| 6 |
-
import { Download, Loader2, MessageSquare } from "lucide-react";
|
| 7 |
-
import { socket } from "../../socket.js";
|
| 8 |
-
import {
|
| 9 |
-
getRecent,
|
| 10 |
-
getStats,
|
| 11 |
-
download_logs,
|
| 12 |
-
switchModel,
|
| 13 |
-
getActiveModel,
|
| 14 |
-
getStatus
|
| 15 |
-
} from "../../api.js";
|
| 16 |
-
import { AlertContext } from "../../context/AlertContext.jsx";
|
| 17 |
-
import ThreatFeed from "./ThreatFeed";
|
| 18 |
-
import ThreatTimeline from "./ThreatTimeline";
|
| 19 |
-
import TopIPs from "./TopIPs";
|
| 20 |
-
import TopCountries from "./TopCountries";
|
| 21 |
-
import Sparkline from "./Sparkline";
|
| 22 |
-
|
| 23 |
-
export default function LiveDashboard() {
|
| 24 |
-
const [rows, setRows] = useState([]);
|
| 25 |
-
const [stats, setStats] = useState({});
|
| 26 |
-
const [connected, setConnected] = useState(false);
|
| 27 |
-
const [lastUpdate, setLastUpdate] = useState(null);
|
| 28 |
-
const [threatCount, setThreatCount] = useState(0);
|
| 29 |
-
const { pushAlert } = useContext(AlertContext);
|
| 30 |
-
|
| 31 |
-
const [model, setModel] = useState("bcc");
|
| 32 |
-
const [snifferRunning, setSnifferRunning] = useState(false);
|
| 33 |
-
const [switching, setSwitching] = useState(false);
|
| 34 |
-
const [modelReady, setModelReady] = useState(false);
|
| 35 |
-
|
| 36 |
-
const [tableKey, setTableKey] = useState(0);
|
| 37 |
-
const eventBufferRef = useRef([]);
|
| 38 |
-
|
| 39 |
-
// ============================================================
|
| 40 |
-
// INIT LOAD: Active model + sniffer status
|
| 41 |
-
// ============================================================
|
| 42 |
-
useEffect(() => {
|
| 43 |
-
(async () => {
|
| 44 |
-
const res = await getActiveModel();
|
| 45 |
-
if (res?.model) setModel(res.model);
|
| 46 |
-
|
| 47 |
-
const st = await getStatus();
|
| 48 |
-
setSnifferRunning(st?.running === true);
|
| 49 |
-
})();
|
| 50 |
-
}, []);
|
| 51 |
-
|
| 52 |
-
// ============================================================
|
| 53 |
-
// MODEL SWITCH (backend + UI reset)
|
| 54 |
-
// ============================================================
|
| 55 |
-
useEffect(() => {
|
| 56 |
-
const doSwitch = async () => {
|
| 57 |
-
setSwitching(true);
|
| 58 |
-
setModelReady(false);
|
| 59 |
-
|
| 60 |
-
setRows([]);
|
| 61 |
-
setStats({});
|
| 62 |
-
setThreatCount(0);
|
| 63 |
-
eventBufferRef.current = [];
|
| 64 |
-
setTableKey((k) => k + 1);
|
| 65 |
-
|
| 66 |
-
const res = await switchModel(model);
|
| 67 |
-
|
| 68 |
-
if (!res?.error) {
|
| 69 |
-
pushAlert(`${model.toUpperCase()} model activated`, "info");
|
| 70 |
-
setModelReady(true);
|
| 71 |
-
} else {
|
| 72 |
-
pushAlert("Model switch failed!", "danger");
|
| 73 |
-
}
|
| 74 |
-
|
| 75 |
-
setSwitching(false);
|
| 76 |
-
};
|
| 77 |
-
|
| 78 |
-
doSwitch();
|
| 79 |
-
}, [model]);
|
| 80 |
-
|
| 81 |
-
// ============================================================
|
| 82 |
-
// INITIAL FETCH (after model switch)
|
| 83 |
-
// ============================================================
|
| 84 |
-
useEffect(() => {
|
| 85 |
-
if (!modelReady) return;
|
| 86 |
-
(async () => {
|
| 87 |
-
const [r1, r2] = await Promise.all([
|
| 88 |
-
getRecent(model),
|
| 89 |
-
getStats(model)
|
| 90 |
-
]);
|
| 91 |
-
|
| 92 |
-
setRows(Array.isArray(r1?.events) ? r1.events : []);
|
| 93 |
-
setStats(r2 || {});
|
| 94 |
-
})();
|
| 95 |
-
}, [model, modelReady]);
|
| 96 |
-
|
| 97 |
-
// ============================================================
|
| 98 |
-
// SOCKET LIVE EVENTS
|
| 99 |
-
// ============================================================
|
| 100 |
-
useEffect(() => {
|
| 101 |
-
socket.on("connect", () => setConnected(true));
|
| 102 |
-
socket.on("disconnect", () => setConnected(false));
|
| 103 |
-
|
| 104 |
-
socket.on("new_event", (payload) => {
|
| 105 |
-
if (!modelReady || switching) return;
|
| 106 |
-
const events = payload?.items ?? [];
|
| 107 |
-
if (events.length) eventBufferRef.current.push(...events);
|
| 108 |
-
});
|
| 109 |
-
|
| 110 |
-
return () => {
|
| 111 |
-
socket.off("connect");
|
| 112 |
-
socket.off("disconnect");
|
| 113 |
-
socket.off("new_event");
|
| 114 |
-
};
|
| 115 |
-
}, [modelReady, switching]);
|
| 116 |
-
|
| 117 |
-
// ============================================================
|
| 118 |
-
// BUFFER TO UI processing
|
| 119 |
-
// ============================================================
|
| 120 |
-
useEffect(() => {
|
| 121 |
-
const interval = setInterval(() => {
|
| 122 |
-
if (!modelReady) return;
|
| 123 |
-
if (!eventBufferRef.current.length) return;
|
| 124 |
-
|
| 125 |
-
const batch = eventBufferRef.current.splice(0, 50);
|
| 126 |
-
|
| 127 |
-
setRows((prev) => [...batch, ...prev].slice(0, 180));
|
| 128 |
-
|
| 129 |
-
setStats((prev) => {
|
| 130 |
-
const next = { ...prev };
|
| 131 |
-
batch.forEach((evt) => {
|
| 132 |
-
const label = (evt.prediction || "UNKNOWN").toUpperCase();
|
| 133 |
-
next[label] = (next[label] || 0) + 1;
|
| 134 |
-
});
|
| 135 |
-
return next;
|
| 136 |
-
});
|
| 137 |
-
|
| 138 |
-
batch.forEach((evt) => {
|
| 139 |
-
const label = (evt.prediction || "").toUpperCase();
|
| 140 |
-
const isThreat = (model === "bcc" && ["TOR","I2P","ZERONET"].includes(label)) ||
|
| 141 |
-
(model === "cicids" && label !== "BENIGN");
|
| 142 |
-
if (isThreat) setThreatCount((c) => c + 1);
|
| 143 |
-
});
|
| 144 |
-
|
| 145 |
-
setLastUpdate(new Date().toLocaleTimeString());
|
| 146 |
-
}, 1200);
|
| 147 |
-
|
| 148 |
-
return () => clearInterval(interval);
|
| 149 |
-
}, [model, modelReady]);
|
| 150 |
-
|
| 151 |
-
// ============================================================
|
| 152 |
-
// AI Assistant Handler (local popup)
|
| 153 |
-
// ============================================================
|
| 154 |
-
const askAISummary = async () => {
|
| 155 |
-
const dangerous = rows.filter(evt => {
|
| 156 |
-
const label = (evt.prediction || "").toUpperCase();
|
| 157 |
-
return (model === "bcc" && ["TOR","I2P","ZERONET"].includes(label)) ||
|
| 158 |
-
(model === "cicids" && label !== "BENIGN");
|
| 159 |
-
});
|
| 160 |
-
|
| 161 |
-
alert(`AI Summary:
|
| 162 |
-
Danger Events: ${dangerous.length}
|
| 163 |
-
Recent Threat Types: ${dangerous.slice(0, 5).map(e => e.prediction).join(", ")}`);
|
| 164 |
-
};
|
| 165 |
-
|
| 166 |
-
// ============================================================
|
| 167 |
-
// RENDER UI
|
| 168 |
-
// ============================================================
|
| 169 |
-
return (
|
| 170 |
-
<div className="space-y-5">
|
| 171 |
-
<h2 className="py-6 text-5xl md:text-6xl font-extrabold text-transparent bg-gradient-to-r from-cyan-300 via-purple-400 to-pink-400 bg-clip-text">
|
| 172 |
-
Live Capture
|
| 173 |
-
</h2>
|
| 174 |
-
|
| 175 |
-
<div className="p-3 rounded-lg bg-cyan-500/10 border border-cyan-400/30 text-cyan-200">
|
| 176 |
-
Active Model: {model.toUpperCase()}
|
| 177 |
-
</div>
|
| 178 |
-
|
| 179 |
-
{/* Model Toggle */}
|
| 180 |
-
<div className="flex gap-3 mb-4">
|
| 181 |
-
<button
|
| 182 |
-
disabled={snifferRunning || switching}
|
| 183 |
-
className={`px-4 py-2 rounded-lg border ${
|
| 184 |
-
model === "bcc" ? "bg-cyan-500/20 border-cyan-400/50 text-cyan-300" :
|
| 185 |
-
"bg-black/20 border-cyan-400/20 text-slate-400"
|
| 186 |
-
}`}
|
| 187 |
-
onClick={() => setModel("bcc")}
|
| 188 |
-
>
|
| 189 |
-
{switching && model === "bcc" && <Loader2 className="animate-spin" size={16} />}
|
| 190 |
-
BCC Model
|
| 191 |
-
</button>
|
| 192 |
-
|
| 193 |
-
<button
|
| 194 |
-
disabled={snifferRunning || switching}
|
| 195 |
-
className={`px-4 py-2 rounded-lg border ${
|
| 196 |
-
model === "cicids" ? "bg-purple-500/20 border-purple-400/50 text-purple-300" :
|
| 197 |
-
"bg-black/20 border-purple-400/20 text-slate-400"
|
| 198 |
-
}`}
|
| 199 |
-
onClick={() => setModel("cicids")}
|
| 200 |
-
>
|
| 201 |
-
{switching && model === "cicids" && <Loader2 className="animate-spin" size={16} />}
|
| 202 |
-
CICIDS Model
|
| 203 |
-
</button>
|
| 204 |
-
|
| 205 |
-
{/* 🧠 AI Button */}
|
| 206 |
-
<button
|
| 207 |
-
onClick={askAISummary}
|
| 208 |
-
className="px-4 py-2 rounded-lg bg-yellow-500/10 border border-yellow-400/40 text-yellow-300 flex items-center gap-2 hover:bg-yellow-500/20"
|
| 209 |
-
>
|
| 210 |
-
<MessageSquare size={16} />
|
| 211 |
-
AI Assist
|
| 212 |
-
</button>
|
| 213 |
-
</div>
|
| 214 |
-
|
| 215 |
-
<Controls />
|
| 216 |
-
|
| 217 |
-
{/* Status + Logs */}
|
| 218 |
-
<div className="flex justify-between items-center">
|
| 219 |
-
<h3 className="text-xl text-cyan-400 font-semibold">
|
| 220 |
-
{model === "bcc" ? "Realtime BCC Monitor" : "Realtime CICIDS Monitor"}
|
| 221 |
-
</h3>
|
| 222 |
-
|
| 223 |
-
<button
|
| 224 |
-
onClick={() => download_logs(model)}
|
| 225 |
-
className="px-3 py-2 rounded-xl bg-emerald-500/20 border border-emerald-400/40 text-emerald-300 flex items-center gap-2"
|
| 226 |
-
>
|
| 227 |
-
<Download size={16} /> Logs
|
| 228 |
-
</button>
|
| 229 |
-
</div>
|
| 230 |
-
|
| 231 |
-
<StatsPanel stats={stats} />
|
| 232 |
-
|
| 233 |
-
<LiveTable key={tableKey} rows={rows} />
|
| 234 |
-
|
| 235 |
-
<ChatAssistant />
|
| 236 |
-
|
| 237 |
-
|
| 238 |
-
{/* VISUAL ANALYTICS BACK ADDED */}
|
| 239 |
-
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6 mt-6">
|
| 240 |
-
<ThreatFeed key={`${tableKey}-feed`} events={rows} />
|
| 241 |
-
<ThreatTimeline key={`${tableKey}-timeline`} events={rows} />
|
| 242 |
-
<Sparkline key={`${tableKey}-spark`} events={rows} />
|
| 243 |
-
<TopIPs key={`${tableKey}-ips`} events={rows} />
|
| 244 |
-
<TopCountries key={`${tableKey}-countries`} events={rows} />
|
| 245 |
-
</div>
|
| 246 |
-
|
| 247 |
-
<p className="text-xs text-slate-400 text-right">
|
| 248 |
-
Last packet: <span className="text-cyan-300">{lastUpdate || "Waiting…"}</span>
|
| 249 |
-
</p>
|
| 250 |
-
</div>
|
| 251 |
-
);
|
| 252 |
-
}
|
| 253 |
-
|
| 254 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
frontend/src/components/dashboard/LiveTable.jsx
DELETED
|
@@ -1,168 +0,0 @@
|
|
| 1 |
-
import React, { useState } from "react";
|
| 2 |
-
import Badge from "../../ui/Badge";
|
| 3 |
-
import { clearLogs, clearByPrediction, deleteOne } from "../../api";
|
| 4 |
-
import { Trash } from "lucide-react";
|
| 5 |
-
import IPInfoModal from "../IPInfoModal";
|
| 6 |
-
|
| 7 |
-
export default function LiveTable({ rows, refresh }) {
|
| 8 |
-
const [clearing, setClearing] = useState(false);
|
| 9 |
-
const [deleteCount, setDeleteCount] = useState(50);
|
| 10 |
-
const [selectedIP, setSelectedIP] = useState(null);
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
// ✅ Safely clear last N rows
|
| 15 |
-
const handleClear = async (n) => {
|
| 16 |
-
try {
|
| 17 |
-
setClearing(true);
|
| 18 |
-
// 🔥 RESET BUFFER on CLEAR
|
| 19 |
-
window.__EVENT_BUFFER_RESET = true;
|
| 20 |
-
await clearLogs(n);
|
| 21 |
-
if (typeof refresh === "function") await refresh();
|
| 22 |
-
} catch (err) {
|
| 23 |
-
console.error("Clear logs failed:", err);
|
| 24 |
-
} finally {
|
| 25 |
-
setTimeout(() => setClearing(false), 500);
|
| 26 |
-
}
|
| 27 |
-
};
|
| 28 |
-
|
| 29 |
-
// ✅ Clear logs by prediction type
|
| 30 |
-
const handleClearPrediction = async (pred) => {
|
| 31 |
-
if (!pred) return;
|
| 32 |
-
try {
|
| 33 |
-
await clearByPrediction(pred);
|
| 34 |
-
if (typeof refresh === "function") await refresh();
|
| 35 |
-
} catch (err) {
|
| 36 |
-
console.error("Clear by prediction failed:", err);
|
| 37 |
-
}
|
| 38 |
-
};
|
| 39 |
-
|
| 40 |
-
// ✅ Delete a single row safely
|
| 41 |
-
const deleteSingleRow = async (index) => {
|
| 42 |
-
try {
|
| 43 |
-
await deleteOne(index);
|
| 44 |
-
if (typeof refresh === "function") await refresh();
|
| 45 |
-
} catch (err) {
|
| 46 |
-
console.error("Delete single row failed:", err);
|
| 47 |
-
}
|
| 48 |
-
};
|
| 49 |
-
|
| 50 |
-
return (
|
| 51 |
-
<div className="cyber-card overflow-hidden transition-all duration-300">
|
| 52 |
-
{/* HEADER + ACTIONS */}
|
| 53 |
-
<div className="flex items-center justify-between mb-3">
|
| 54 |
-
<div>
|
| 55 |
-
<h3 className="font-semibold text-lg">Live Events</h3>
|
| 56 |
-
<span className="text-xs text-slate-400">
|
| 57 |
-
Showing last {rows.length} packets
|
| 58 |
-
</span>
|
| 59 |
-
</div>
|
| 60 |
-
|
| 61 |
-
{/* DELETE CONTROLS */}
|
| 62 |
-
<div className="flex items-center gap-2">
|
| 63 |
-
{/* DELETE BY PREDICTION */}
|
| 64 |
-
<select
|
| 65 |
-
onChange={(e) => handleClearPrediction(e.target.value)}
|
| 66 |
-
className="bg-cyber-panel/50 border border-cyan-400/20 px-2 py-1 rounded-lg text-xs"
|
| 67 |
-
>
|
| 68 |
-
<option value="">Clear by Class</option>
|
| 69 |
-
<option value="VPN">Clear VPN</option>
|
| 70 |
-
<option value="TOR">Clear TOR</option>
|
| 71 |
-
<option value="I2P">Clear I2P</option>
|
| 72 |
-
<option value="FREENET">Clear FREENET</option>
|
| 73 |
-
<option value="ZERONET">Clear ZERONET</option>
|
| 74 |
-
</select>
|
| 75 |
-
|
| 76 |
-
{/* CLEAR LAST N */}
|
| 77 |
-
<button
|
| 78 |
-
onClick={() => handleClear(deleteCount)}
|
| 79 |
-
className="px-3 py-2 bg-red-500/20 hover:bg-red-500/30 border border-red-500/40
|
| 80 |
-
rounded-lg text-xs flex items-center gap-1"
|
| 81 |
-
>
|
| 82 |
-
<Trash size={14} /> Clear
|
| 83 |
-
</button>
|
| 84 |
-
|
| 85 |
-
{/* CLEAR ALL */}
|
| 86 |
-
<button
|
| 87 |
-
onClick={() => handleClear(99999)}
|
| 88 |
-
className="px-3 py-2 bg-red-600/30 hover:bg-red-600/40 border border-red-500/50
|
| 89 |
-
rounded-lg text-xs"
|
| 90 |
-
>
|
| 91 |
-
Clear All
|
| 92 |
-
</button>
|
| 93 |
-
</div>
|
| 94 |
-
</div>
|
| 95 |
-
|
| 96 |
-
{/* TABLE */}
|
| 97 |
-
<div
|
| 98 |
-
className={`overflow-auto transition-opacity duration-500 ${
|
| 99 |
-
clearing ? "opacity-30" : "opacity-100"
|
| 100 |
-
}`}
|
| 101 |
-
style={{ maxHeight: "350px" }}
|
| 102 |
-
>
|
| 103 |
-
<table className="w-full text-sm">
|
| 104 |
-
<thead className="text-slate-300/80">
|
| 105 |
-
<tr className="border-b border-cyan-400/10">
|
| 106 |
-
<th className="text-left py-2 pr-3">Time</th>
|
| 107 |
-
<th className="text-left py-2 pr-3">Src → Dst</th>
|
| 108 |
-
<th className="text-left py-2 pr-3">Ports</th>
|
| 109 |
-
<th className="text-left py-2 pr-3">Proto</th>
|
| 110 |
-
<th className="text-left py-2 pr-3">Prediction</th>
|
| 111 |
-
</tr>
|
| 112 |
-
</thead>
|
| 113 |
-
|
| 114 |
-
<tbody>
|
| 115 |
-
{rows
|
| 116 |
-
.slice()
|
| 117 |
-
.reverse()
|
| 118 |
-
.map((r, idx) => (
|
| 119 |
-
<tr
|
| 120 |
-
key={idx}
|
| 121 |
-
className="border-b border-cyan-400/5 hover:bg-white/5 transition-all"
|
| 122 |
-
>
|
| 123 |
-
<td className="py-2 pr-3 font-mono text-xs">{r.time}</td>
|
| 124 |
-
<td className="py-2 pr-3 font-mono text-xs">
|
| 125 |
-
<span
|
| 126 |
-
className="text-cyan-400 cursor-pointer hover:text-cyan-300"
|
| 127 |
-
onClick={() => setSelectedIP(r.src_ip || r.src)}
|
| 128 |
-
>
|
| 129 |
-
{r.src_ip || r.src}
|
| 130 |
-
</span>{" "}
|
| 131 |
-
→
|
| 132 |
-
<span
|
| 133 |
-
className="text-cyan-400 cursor-pointer hover:text-cyan-300"
|
| 134 |
-
onClick={() => setSelectedIP(r.dst_ip || r.dst)}
|
| 135 |
-
>
|
| 136 |
-
{r.dst_ip || r.dst}
|
| 137 |
-
</span>
|
| 138 |
-
</td>
|
| 139 |
-
<td className="py-2 pr-3 font-mono text-xs">
|
| 140 |
-
{r.sport} → {r.dport}
|
| 141 |
-
</td>
|
| 142 |
-
<td className="py-2 pr-3 font-mono text-xs">{r.proto}</td>
|
| 143 |
-
<td className="py-2 pr-3 flex items-center gap-2">
|
| 144 |
-
<Badge value={r.prediction} />
|
| 145 |
-
<Trash
|
| 146 |
-
size={14}
|
| 147 |
-
className="text-red-400 cursor-pointer hover:text-red-600"
|
| 148 |
-
onClick={() =>
|
| 149 |
-
deleteSingleRow(rows.length - 1 - idx)
|
| 150 |
-
}
|
| 151 |
-
/>
|
| 152 |
-
</td>
|
| 153 |
-
</tr>
|
| 154 |
-
))}
|
| 155 |
-
</tbody>
|
| 156 |
-
</table>
|
| 157 |
-
</div>
|
| 158 |
-
|
| 159 |
-
{/* ✅ IP Lookup Modal */}
|
| 160 |
-
{selectedIP && (
|
| 161 |
-
<IPInfoModal
|
| 162 |
-
ip={selectedIP}
|
| 163 |
-
onClose={() => setSelectedIP(null)}
|
| 164 |
-
/>
|
| 165 |
-
)}
|
| 166 |
-
</div>
|
| 167 |
-
);
|
| 168 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
frontend/src/components/dashboard/Sparkline.jsx
DELETED
|
@@ -1,28 +0,0 @@
|
|
| 1 |
-
import { LineChart, Line, ResponsiveContainer } from "recharts";
|
| 2 |
-
|
| 3 |
-
export default function Sparkline({ events }) {
|
| 4 |
-
const data = events.slice(-40).map((e, i) => ({
|
| 5 |
-
index: i,
|
| 6 |
-
value: (e.prediction || "").toUpperCase() !== "UNKNOWN" ? 1 : 0,
|
| 7 |
-
}));
|
| 8 |
-
|
| 9 |
-
return (
|
| 10 |
-
<div className="cyber-card p-4">
|
| 11 |
-
<h3 className="text-accent mb-2 font-semibold">Threat Activity Trend</h3>
|
| 12 |
-
|
| 13 |
-
<div className="h-24">
|
| 14 |
-
<ResponsiveContainer>
|
| 15 |
-
<LineChart data={data}>
|
| 16 |
-
<Line
|
| 17 |
-
type="monotone"
|
| 18 |
-
dataKey="value"
|
| 19 |
-
stroke="#00e5ff"
|
| 20 |
-
strokeWidth={2}
|
| 21 |
-
dot={false}
|
| 22 |
-
/>
|
| 23 |
-
</LineChart>
|
| 24 |
-
</ResponsiveContainer>
|
| 25 |
-
</div>
|
| 26 |
-
</div>
|
| 27 |
-
);
|
| 28 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
frontend/src/components/dashboard/StatsPanel.jsx
DELETED
|
@@ -1,104 +0,0 @@
|
|
| 1 |
-
import React, { useMemo } from "react";
|
| 2 |
-
import {
|
| 3 |
-
PieChart,
|
| 4 |
-
Pie,
|
| 5 |
-
Cell,
|
| 6 |
-
Tooltip,
|
| 7 |
-
ResponsiveContainer,
|
| 8 |
-
BarChart,
|
| 9 |
-
Bar,
|
| 10 |
-
XAxis,
|
| 11 |
-
YAxis,
|
| 12 |
-
CartesianGrid,
|
| 13 |
-
} from "recharts";
|
| 14 |
-
|
| 15 |
-
// 🎨 Neon cyber color palette
|
| 16 |
-
const COLORS = ["#00e5ff", "#ff0059", "#a78bfa", "#fbbf24", "#10b981"];
|
| 17 |
-
|
| 18 |
-
export default function StatsPanel({ stats }) {
|
| 19 |
-
// ✅ Memoized data transformation for performance
|
| 20 |
-
const data = useMemo(() => {
|
| 21 |
-
if (!stats || typeof stats !== "object") return [];
|
| 22 |
-
return Object.entries(stats)
|
| 23 |
-
.map(([name, value]) => ({
|
| 24 |
-
name: String(name).toUpperCase(),
|
| 25 |
-
value: Number(value) || 0,
|
| 26 |
-
}))
|
| 27 |
-
.filter((d) => d.value > 0);
|
| 28 |
-
}, [stats]);
|
| 29 |
-
|
| 30 |
-
const total = data.reduce((sum, d) => sum + d.value, 0);
|
| 31 |
-
|
| 32 |
-
return (
|
| 33 |
-
<div className="grid md:grid-cols-2 gap-4">
|
| 34 |
-
{/* ==== PIE CHART ==== */}
|
| 35 |
-
<div className="cyber-card relative overflow-hidden">
|
| 36 |
-
<h3 className="font-semibold mb-2 text-cyan-400">
|
| 37 |
-
Class Distribution (Pie)
|
| 38 |
-
</h3>
|
| 39 |
-
|
| 40 |
-
<div className="h-64">
|
| 41 |
-
{data.length > 0 ? (
|
| 42 |
-
<ResponsiveContainer>
|
| 43 |
-
<PieChart>
|
| 44 |
-
<Pie
|
| 45 |
-
data={data}
|
| 46 |
-
dataKey="value"
|
| 47 |
-
nameKey="name"
|
| 48 |
-
outerRadius={90}
|
| 49 |
-
label={({ name, value }) => `${name}: ${value}`}
|
| 50 |
-
>
|
| 51 |
-
{data.map((entry, index) => (
|
| 52 |
-
<Cell key={index} fill={COLORS[index % COLORS.length]} />
|
| 53 |
-
))}
|
| 54 |
-
</Pie>
|
| 55 |
-
<Tooltip />
|
| 56 |
-
</PieChart>
|
| 57 |
-
</ResponsiveContainer>
|
| 58 |
-
) : (
|
| 59 |
-
<div className="flex items-center justify-center h-full text-slate-400 text-sm">
|
| 60 |
-
No Data Yet
|
| 61 |
-
</div>
|
| 62 |
-
)}
|
| 63 |
-
</div>
|
| 64 |
-
|
| 65 |
-
{/* Total Packets Indicator */}
|
| 66 |
-
<p className="text-xs text-slate-400 absolute bottom-2 right-3">
|
| 67 |
-
Total Packets:{" "}
|
| 68 |
-
<span className="text-cyan-300 font-semibold">{total}</span>
|
| 69 |
-
</p>
|
| 70 |
-
</div>
|
| 71 |
-
|
| 72 |
-
{/* ==== BAR CHART ==== */}
|
| 73 |
-
<div className="cyber-card relative overflow-hidden">
|
| 74 |
-
<h3 className="font-semibold mb-2 text-cyan-400">
|
| 75 |
-
Class Counts (Bar)
|
| 76 |
-
</h3>
|
| 77 |
-
|
| 78 |
-
<div className="h-64">
|
| 79 |
-
{data.length > 0 ? (
|
| 80 |
-
<ResponsiveContainer>
|
| 81 |
-
<BarChart data={data}>
|
| 82 |
-
<CartesianGrid strokeDasharray="3 3" strokeOpacity={0.1} />
|
| 83 |
-
<XAxis dataKey="name" stroke="#94a3b8" />
|
| 84 |
-
<YAxis stroke="#94a3b8" allowDecimals={false} />
|
| 85 |
-
<Tooltip />
|
| 86 |
-
<Bar
|
| 87 |
-
dataKey="value"
|
| 88 |
-
radius={[6, 6, 0, 0]}
|
| 89 |
-
fill="#00e5ff"
|
| 90 |
-
opacity={0.8}
|
| 91 |
-
/>
|
| 92 |
-
</BarChart>
|
| 93 |
-
</ResponsiveContainer>
|
| 94 |
-
) : (
|
| 95 |
-
<div className="flex items-center justify-center h-full text-slate-400 text-sm">
|
| 96 |
-
Awaiting Data...
|
| 97 |
-
</div>
|
| 98 |
-
)}
|
| 99 |
-
</div>
|
| 100 |
-
</div>
|
| 101 |
-
</div>
|
| 102 |
-
);
|
| 103 |
-
}
|
| 104 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
frontend/src/components/dashboard/ThreatFeed.jsx
DELETED
|
@@ -1,39 +0,0 @@
|
|
| 1 |
-
import { AlertTriangle } from "lucide-react";
|
| 2 |
-
|
| 3 |
-
export default function ThreatFeed({ events }) {
|
| 4 |
-
const highRisk = events
|
| 5 |
-
.filter((e) =>
|
| 6 |
-
["TOR", "I2P", "ZERONET"].includes((e.prediction || "").toUpperCase())
|
| 7 |
-
)
|
| 8 |
-
.slice(-20)
|
| 9 |
-
.reverse();
|
| 10 |
-
|
| 11 |
-
return (
|
| 12 |
-
<div className="cyber-card p-4 border border-red-500/30">
|
| 13 |
-
<h3 className="text-red-400 font-semibold mb-3 flex items-center gap-2">
|
| 14 |
-
<AlertTriangle size={18} /> High-Risk Events
|
| 15 |
-
</h3>
|
| 16 |
-
|
| 17 |
-
{/* Scrollable list */}
|
| 18 |
-
<div className="max-h-64 overflow-y-auto pr-2 custom-scroll space-y-2">
|
| 19 |
-
{highRisk.length === 0 ? (
|
| 20 |
-
<p className="text-slate-400 text-sm">No critical activity detected</p>
|
| 21 |
-
) : (
|
| 22 |
-
highRisk.map((e, i) => (
|
| 23 |
-
<div
|
| 24 |
-
key={i}
|
| 25 |
-
className="p-2 rounded-lg bg-red-500/10 border border-red-500/20
|
| 26 |
-
animate-fadeIn"
|
| 27 |
-
style={{ animationDelay: `${i * 50}ms` }}
|
| 28 |
-
>
|
| 29 |
-
<span className="text-red-300 font-semibold">{e.prediction}</span>
|
| 30 |
-
<span className="text-slate-300"> from </span>
|
| 31 |
-
<span className="text-cyan-300">{e.src_ip}</span>
|
| 32 |
-
</div>
|
| 33 |
-
))
|
| 34 |
-
)}
|
| 35 |
-
</div>
|
| 36 |
-
</div>
|
| 37 |
-
);
|
| 38 |
-
}
|
| 39 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
frontend/src/components/dashboard/ThreatTimeline.jsx
DELETED
|
@@ -1,27 +0,0 @@
|
|
| 1 |
-
export default function ThreatTimeline({ events }) {
|
| 2 |
-
const last20 = [...events].slice(-20).reverse();
|
| 3 |
-
|
| 4 |
-
return (
|
| 5 |
-
<div className="cyber-card p-4">
|
| 6 |
-
<h3 className="text-accent font-semibold mb-3">Event Timeline</h3>
|
| 7 |
-
|
| 8 |
-
{/* Scrollable container */}
|
| 9 |
-
<div className="space-y-3 max-h-64 overflow-y-auto pr-2 custom-scroll">
|
| 10 |
-
{last20.map((e, i) => (
|
| 11 |
-
<div key={i} className="flex gap-3 items-start">
|
| 12 |
-
<div className="w-2 h-2 rounded-full bg-accent mt-2 animate-pulse" />
|
| 13 |
-
<div>
|
| 14 |
-
<p className="text-sm text-slate-300">
|
| 15 |
-
<span className="text-cyan-300">{e.prediction}</span>{" "}
|
| 16 |
-
detected from {e.src_ip}
|
| 17 |
-
</p>
|
| 18 |
-
<p className="text-[11px] text-slate-500">
|
| 19 |
-
{new Date().toLocaleTimeString()}
|
| 20 |
-
</p>
|
| 21 |
-
</div>
|
| 22 |
-
</div>
|
| 23 |
-
))}
|
| 24 |
-
</div>
|
| 25 |
-
</div>
|
| 26 |
-
);
|
| 27 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
frontend/src/components/dashboard/TopCountries.jsx
DELETED
|
@@ -1,26 +0,0 @@
|
|
| 1 |
-
export default function TopCountries({ events }) {
|
| 2 |
-
const map = {};
|
| 3 |
-
|
| 4 |
-
events.forEach((e) => {
|
| 5 |
-
const c = e.src_country || "Unknown";
|
| 6 |
-
map[c] = (map[c] || 0) + 1;
|
| 7 |
-
});
|
| 8 |
-
|
| 9 |
-
const sorted = Object.entries(map)
|
| 10 |
-
.sort((a, b) => b[1] - a[1])
|
| 11 |
-
.slice(0, 6);
|
| 12 |
-
|
| 13 |
-
return (
|
| 14 |
-
<div className="cyber-card p-4">
|
| 15 |
-
<h3 className="text-accent mb-2 font-semibold">Top Source Countries</h3>
|
| 16 |
-
<ul className="text-sm space-y-1">
|
| 17 |
-
{sorted.map(([country, count], i) => (
|
| 18 |
-
<li key={i} className="flex justify-between">
|
| 19 |
-
<span>{country}</span>
|
| 20 |
-
<span className="text-cyan-300">{count}</span>
|
| 21 |
-
</li>
|
| 22 |
-
))}
|
| 23 |
-
</ul>
|
| 24 |
-
</div>
|
| 25 |
-
);
|
| 26 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
frontend/src/components/dashboard/TopIPs.jsx
DELETED
|
@@ -1,26 +0,0 @@
|
|
| 1 |
-
export default function TopIPs({ events }) {
|
| 2 |
-
const map = {};
|
| 3 |
-
|
| 4 |
-
events.forEach((e) => {
|
| 5 |
-
const ip = e.src_ip || "Unknown";
|
| 6 |
-
map[ip] = (map[ip] || 0) + 1;
|
| 7 |
-
});
|
| 8 |
-
|
| 9 |
-
const sorted = Object.entries(map)
|
| 10 |
-
.sort((a, b) => b[1] - a[1])
|
| 11 |
-
.slice(0, 6);
|
| 12 |
-
|
| 13 |
-
return (
|
| 14 |
-
<div className="cyber-card p-4">
|
| 15 |
-
<h3 className="text-accent mb-2 font-semibold">Top Source IPs</h3>
|
| 16 |
-
<ul className="text-sm space-y-1">
|
| 17 |
-
{sorted.map(([ip, count], i) => (
|
| 18 |
-
<li key={i} className="flex justify-between">
|
| 19 |
-
<span className="text-slate-300">{ip}</span>
|
| 20 |
-
<span className="text-cyan-300">{count}</span>
|
| 21 |
-
</li>
|
| 22 |
-
))}
|
| 23 |
-
</ul>
|
| 24 |
-
</div>
|
| 25 |
-
);
|
| 26 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
frontend/src/components/ui/accordion.jsx
DELETED
|
@@ -1,43 +0,0 @@
|
|
| 1 |
-
"use client"
|
| 2 |
-
|
| 3 |
-
import * as React from "react"
|
| 4 |
-
import * as AccordionPrimitive from "@radix-ui/react-accordion"
|
| 5 |
-
import { ChevronDown } from "lucide-react"
|
| 6 |
-
|
| 7 |
-
import { cn } from "@/lib/utils"
|
| 8 |
-
|
| 9 |
-
const Accordion = AccordionPrimitive.Root
|
| 10 |
-
|
| 11 |
-
const AccordionItem = React.forwardRef(({ className, ...props }, ref) => (
|
| 12 |
-
<AccordionPrimitive.Item ref={ref} className={cn("border-b", className)} {...props} />
|
| 13 |
-
))
|
| 14 |
-
AccordionItem.displayName = "AccordionItem"
|
| 15 |
-
|
| 16 |
-
const AccordionTrigger = React.forwardRef(({ className, children, ...props }, ref) => (
|
| 17 |
-
<AccordionPrimitive.Header className="flex">
|
| 18 |
-
<AccordionPrimitive.Trigger
|
| 19 |
-
ref={ref}
|
| 20 |
-
className={cn(
|
| 21 |
-
"flex flex-1 items-center justify-between py-4 font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180",
|
| 22 |
-
className
|
| 23 |
-
)}
|
| 24 |
-
{...props}>
|
| 25 |
-
{children}
|
| 26 |
-
<ChevronDown className="h-4 w-4 shrink-0 transition-transform duration-200" />
|
| 27 |
-
</AccordionPrimitive.Trigger>
|
| 28 |
-
</AccordionPrimitive.Header>
|
| 29 |
-
))
|
| 30 |
-
AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName
|
| 31 |
-
|
| 32 |
-
const AccordionContent = React.forwardRef(({ className, children, ...props }, ref) => (
|
| 33 |
-
<AccordionPrimitive.Content
|
| 34 |
-
ref={ref}
|
| 35 |
-
className="overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
|
| 36 |
-
{...props}>
|
| 37 |
-
<div className={cn("pb-4 pt-0", className)}>{children}</div>
|
| 38 |
-
</AccordionPrimitive.Content>
|
| 39 |
-
))
|
| 40 |
-
|
| 41 |
-
AccordionContent.displayName = AccordionPrimitive.Content.displayName
|
| 42 |
-
|
| 43 |
-
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
frontend/src/components/ui/badge.jsx
DELETED
|
@@ -1,34 +0,0 @@
|
|
| 1 |
-
import * as React from "react"
|
| 2 |
-
import { cva } from "class-variance-authority";
|
| 3 |
-
|
| 4 |
-
import { cn } from "@/lib/utils"
|
| 5 |
-
|
| 6 |
-
const badgeVariants = cva(
|
| 7 |
-
"inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
|
| 8 |
-
{
|
| 9 |
-
variants: {
|
| 10 |
-
variant: {
|
| 11 |
-
default:
|
| 12 |
-
"border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
|
| 13 |
-
secondary:
|
| 14 |
-
"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
| 15 |
-
destructive:
|
| 16 |
-
"border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
|
| 17 |
-
outline: "text-foreground",
|
| 18 |
-
},
|
| 19 |
-
},
|
| 20 |
-
defaultVariants: {
|
| 21 |
-
variant: "default",
|
| 22 |
-
},
|
| 23 |
-
}
|
| 24 |
-
)
|
| 25 |
-
|
| 26 |
-
function Badge({
|
| 27 |
-
className,
|
| 28 |
-
variant,
|
| 29 |
-
...props
|
| 30 |
-
}) {
|
| 31 |
-
return (<div className={cn(badgeVariants({ variant }), className)} {...props} />);
|
| 32 |
-
}
|
| 33 |
-
|
| 34 |
-
export { Badge, badgeVariants }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
frontend/src/components/ui/button.jsx
DELETED
|
@@ -1,47 +0,0 @@
|
|
| 1 |
-
import * as React from "react"
|
| 2 |
-
import { Slot } from "@radix-ui/react-slot"
|
| 3 |
-
import { cva } from "class-variance-authority";
|
| 4 |
-
|
| 5 |
-
import { cn } from "@/lib/utils"
|
| 6 |
-
|
| 7 |
-
const buttonVariants = cva(
|
| 8 |
-
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
| 9 |
-
{
|
| 10 |
-
variants: {
|
| 11 |
-
variant: {
|
| 12 |
-
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
| 13 |
-
destructive:
|
| 14 |
-
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
| 15 |
-
outline:
|
| 16 |
-
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
|
| 17 |
-
secondary:
|
| 18 |
-
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
| 19 |
-
ghost: "hover:bg-accent hover:text-accent-foreground",
|
| 20 |
-
link: "text-primary underline-offset-4 hover:underline",
|
| 21 |
-
},
|
| 22 |
-
size: {
|
| 23 |
-
default: "h-10 px-4 py-2",
|
| 24 |
-
sm: "h-9 rounded-md px-3",
|
| 25 |
-
lg: "h-11 rounded-md px-8",
|
| 26 |
-
icon: "h-10 w-10",
|
| 27 |
-
},
|
| 28 |
-
},
|
| 29 |
-
defaultVariants: {
|
| 30 |
-
variant: "default",
|
| 31 |
-
size: "default",
|
| 32 |
-
},
|
| 33 |
-
}
|
| 34 |
-
)
|
| 35 |
-
|
| 36 |
-
const Button = React.forwardRef(({ className, variant, size, asChild = false, ...props }, ref) => {
|
| 37 |
-
const Comp = asChild ? Slot : "button"
|
| 38 |
-
return (
|
| 39 |
-
<Comp
|
| 40 |
-
className={cn(buttonVariants({ variant, size, className }))}
|
| 41 |
-
ref={ref}
|
| 42 |
-
{...props} />
|
| 43 |
-
);
|
| 44 |
-
})
|
| 45 |
-
Button.displayName = "Button"
|
| 46 |
-
|
| 47 |
-
export { Button, buttonVariants }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
frontend/src/components/ui/card.jsx
DELETED
|
@@ -1,50 +0,0 @@
|
|
| 1 |
-
import * as React from "react"
|
| 2 |
-
|
| 3 |
-
import { cn } from "@/lib/utils"
|
| 4 |
-
|
| 5 |
-
const Card = React.forwardRef(({ className, ...props }, ref) => (
|
| 6 |
-
<div
|
| 7 |
-
ref={ref}
|
| 8 |
-
className={cn("rounded-lg border bg-card text-card-foreground shadow-sm", className)}
|
| 9 |
-
{...props} />
|
| 10 |
-
))
|
| 11 |
-
Card.displayName = "Card"
|
| 12 |
-
|
| 13 |
-
const CardHeader = React.forwardRef(({ className, ...props }, ref) => (
|
| 14 |
-
<div
|
| 15 |
-
ref={ref}
|
| 16 |
-
className={cn("flex flex-col space-y-1.5 p-6", className)}
|
| 17 |
-
{...props} />
|
| 18 |
-
))
|
| 19 |
-
CardHeader.displayName = "CardHeader"
|
| 20 |
-
|
| 21 |
-
const CardTitle = React.forwardRef(({ className, ...props }, ref) => (
|
| 22 |
-
<div
|
| 23 |
-
ref={ref}
|
| 24 |
-
className={cn("text-2xl font-semibold leading-none tracking-tight", className)}
|
| 25 |
-
{...props} />
|
| 26 |
-
))
|
| 27 |
-
CardTitle.displayName = "CardTitle"
|
| 28 |
-
|
| 29 |
-
const CardDescription = React.forwardRef(({ className, ...props }, ref) => (
|
| 30 |
-
<div
|
| 31 |
-
ref={ref}
|
| 32 |
-
className={cn("text-sm text-muted-foreground", className)}
|
| 33 |
-
{...props} />
|
| 34 |
-
))
|
| 35 |
-
CardDescription.displayName = "CardDescription"
|
| 36 |
-
|
| 37 |
-
const CardContent = React.forwardRef(({ className, ...props }, ref) => (
|
| 38 |
-
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
|
| 39 |
-
))
|
| 40 |
-
CardContent.displayName = "CardContent"
|
| 41 |
-
|
| 42 |
-
const CardFooter = React.forwardRef(({ className, ...props }, ref) => (
|
| 43 |
-
<div
|
| 44 |
-
ref={ref}
|
| 45 |
-
className={cn("flex items-center p-6 pt-0", className)}
|
| 46 |
-
{...props} />
|
| 47 |
-
))
|
| 48 |
-
CardFooter.displayName = "CardFooter"
|
| 49 |
-
|
| 50 |
-
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
frontend/src/components/ui/input.jsx
DELETED
|
@@ -1,19 +0,0 @@
|
|
| 1 |
-
import * as React from "react"
|
| 2 |
-
|
| 3 |
-
import { cn } from "@/lib/utils"
|
| 4 |
-
|
| 5 |
-
const Input = React.forwardRef(({ className, type, ...props }, ref) => {
|
| 6 |
-
return (
|
| 7 |
-
<input
|
| 8 |
-
type={type}
|
| 9 |
-
className={cn(
|
| 10 |
-
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-base ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
| 11 |
-
className
|
| 12 |
-
)}
|
| 13 |
-
ref={ref}
|
| 14 |
-
{...props} />
|
| 15 |
-
);
|
| 16 |
-
})
|
| 17 |
-
Input.displayName = "Input"
|
| 18 |
-
|
| 19 |
-
export { Input }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
frontend/src/components/ui/scroll-area.jsx
DELETED
|
@@ -1,38 +0,0 @@
|
|
| 1 |
-
import * as React from "react"
|
| 2 |
-
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
|
| 3 |
-
|
| 4 |
-
import { cn } from "@/lib/utils"
|
| 5 |
-
|
| 6 |
-
const ScrollArea = React.forwardRef(({ className, children, ...props }, ref) => (
|
| 7 |
-
<ScrollAreaPrimitive.Root
|
| 8 |
-
ref={ref}
|
| 9 |
-
className={cn("relative overflow-hidden", className)}
|
| 10 |
-
{...props}>
|
| 11 |
-
<ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]">
|
| 12 |
-
{children}
|
| 13 |
-
</ScrollAreaPrimitive.Viewport>
|
| 14 |
-
<ScrollBar />
|
| 15 |
-
<ScrollAreaPrimitive.Corner />
|
| 16 |
-
</ScrollAreaPrimitive.Root>
|
| 17 |
-
))
|
| 18 |
-
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName
|
| 19 |
-
|
| 20 |
-
const ScrollBar = React.forwardRef(({ className, orientation = "vertical", ...props }, ref) => (
|
| 21 |
-
<ScrollAreaPrimitive.ScrollAreaScrollbar
|
| 22 |
-
ref={ref}
|
| 23 |
-
orientation={orientation}
|
| 24 |
-
className={cn(
|
| 25 |
-
"flex touch-none select-none transition-colors",
|
| 26 |
-
orientation === "vertical" &&
|
| 27 |
-
"h-full w-2.5 border-l border-l-transparent p-[1px]",
|
| 28 |
-
orientation === "horizontal" &&
|
| 29 |
-
"h-2.5 flex-col border-t border-t-transparent p-[1px]",
|
| 30 |
-
className
|
| 31 |
-
)}
|
| 32 |
-
{...props}>
|
| 33 |
-
<ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border" />
|
| 34 |
-
</ScrollAreaPrimitive.ScrollAreaScrollbar>
|
| 35 |
-
))
|
| 36 |
-
ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName
|
| 37 |
-
|
| 38 |
-
export { ScrollArea, ScrollBar }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
frontend/src/components/ui/separator.jsx
DELETED
|
@@ -1,23 +0,0 @@
|
|
| 1 |
-
import * as React from "react"
|
| 2 |
-
import * as SeparatorPrimitive from "@radix-ui/react-separator"
|
| 3 |
-
|
| 4 |
-
import { cn } from "@/lib/utils"
|
| 5 |
-
|
| 6 |
-
const Separator = React.forwardRef((
|
| 7 |
-
{ className, orientation = "horizontal", decorative = true, ...props },
|
| 8 |
-
ref
|
| 9 |
-
) => (
|
| 10 |
-
<SeparatorPrimitive.Root
|
| 11 |
-
ref={ref}
|
| 12 |
-
decorative={decorative}
|
| 13 |
-
orientation={orientation}
|
| 14 |
-
className={cn(
|
| 15 |
-
"shrink-0 bg-border",
|
| 16 |
-
orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]",
|
| 17 |
-
className
|
| 18 |
-
)}
|
| 19 |
-
{...props} />
|
| 20 |
-
))
|
| 21 |
-
Separator.displayName = SeparatorPrimitive.Root.displayName
|
| 22 |
-
|
| 23 |
-
export { Separator }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
frontend/src/components/ui/switch.jsx
DELETED
|
@@ -1,24 +0,0 @@
|
|
| 1 |
-
"use client"
|
| 2 |
-
|
| 3 |
-
import * as React from "react"
|
| 4 |
-
import * as SwitchPrimitives from "@radix-ui/react-switch"
|
| 5 |
-
|
| 6 |
-
import { cn } from "@/lib/utils"
|
| 7 |
-
|
| 8 |
-
const Switch = React.forwardRef(({ className, ...props }, ref) => (
|
| 9 |
-
<SwitchPrimitives.Root
|
| 10 |
-
className={cn(
|
| 11 |
-
"peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input",
|
| 12 |
-
className
|
| 13 |
-
)}
|
| 14 |
-
{...props}
|
| 15 |
-
ref={ref}>
|
| 16 |
-
<SwitchPrimitives.Thumb
|
| 17 |
-
className={cn(
|
| 18 |
-
"pointer-events-none block h-5 w-5 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0"
|
| 19 |
-
)} />
|
| 20 |
-
</SwitchPrimitives.Root>
|
| 21 |
-
))
|
| 22 |
-
Switch.displayName = SwitchPrimitives.Root.displayName
|
| 23 |
-
|
| 24 |
-
export { Switch }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|