mirror of
https://github.com/mblanke/ThreatHunt.git
synced 2026-03-01 14:00:20 -05:00
post CLAUDE updates
had CLAUDE AI made suggestions and edits to code. added all route and moves some requirements around.
This commit is contained in:
69
README.md
69
README.md
@@ -1,4 +1,65 @@
|
||||
# Velo Threat Hunter UI v2
|
||||
- React + Flask + Tailwind
|
||||
- Sidebar layout with icons
|
||||
- Ready to extend
|
||||
# Velo Threat Hunter
|
||||
|
||||
A modern web application for threat hunting and security analysis, built with React frontend and Flask backend.
|
||||
|
||||
## Features
|
||||
|
||||
- **Security Tools Detection**: Identify running security tools (AV, EDR, VPN)
|
||||
- **CSV Processing**: Upload and analyze security logs
|
||||
- **Baseline Analysis**: System baseline comparison
|
||||
- **Network Analysis**: Network traffic and connection analysis
|
||||
- **VirusTotal Integration**: File and URL reputation checking
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
ThreatHunt/
|
||||
├── frontend/ # React application
|
||||
├── backend/ # Flask API server
|
||||
├── uploaded/ # File upload storage
|
||||
└── output/ # Analysis results
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Backend Setup
|
||||
|
||||
```bash
|
||||
cd backend
|
||||
chmod +x setup_backend.sh
|
||||
./setup_backend.sh
|
||||
source venv/bin/activate
|
||||
python app.py
|
||||
```
|
||||
|
||||
### Frontend Setup
|
||||
|
||||
```bash
|
||||
cd frontend
|
||||
npm install
|
||||
npm run dev
|
||||
```
|
||||
|
||||
## API Endpoints
|
||||
|
||||
- `GET /` - Serve React app
|
||||
- `GET /api/health` - Health check
|
||||
- `POST /api/upload` - File upload
|
||||
- `GET /api/analysis/<id>` - Get analysis results
|
||||
|
||||
## Security Considerations
|
||||
|
||||
- File upload validation
|
||||
- Input sanitization
|
||||
- Rate limiting
|
||||
- CORS configuration
|
||||
|
||||
## Contributing
|
||||
|
||||
1. Fork the repository
|
||||
2. Create feature branch
|
||||
3. Submit pull request
|
||||
|
||||
## License
|
||||
|
||||
MIT License
|
||||
|
||||
10
backend/.env.example
Normal file
10
backend/.env.example
Normal file
@@ -0,0 +1,10 @@
|
||||
FLASK_ENV=development
|
||||
FLASK_DEBUG=True
|
||||
SECRET_KEY=your-secret-key-here
|
||||
MAX_CONTENT_LENGTH=104857600
|
||||
UPLOAD_FOLDER=uploaded
|
||||
OUTPUT_FOLDER=output
|
||||
VIRUSTOTAL_API_KEY=your-virustotal-api-key
|
||||
REDIS_URL=redis://localhost:6379/0
|
||||
CELERY_BROKER_URL=redis://localhost:6379/0
|
||||
CELERY_RESULT_BACKEND=redis://localhost:6379/0
|
||||
165
backend/app.py
165
backend/app.py
@@ -1,18 +1,169 @@
|
||||
from flask import Flask, send_from_directory
|
||||
import os
|
||||
app = Flask(__name__, static_folder="../frontend/dist")
|
||||
import logging
|
||||
from flask import Flask, request, jsonify, send_from_directory
|
||||
from flask_cors import CORS
|
||||
from werkzeug.utils import secure_filename
|
||||
from werkzeug.exceptions import RequestEntityTooLarge
|
||||
import pandas as pd
|
||||
import magic
|
||||
from datetime import datetime
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
app = Flask(__name__, static_folder="../frontend/dist")
|
||||
CORS(app)
|
||||
|
||||
# Configuration
|
||||
app.config['MAX_CONTENT_LENGTH'] = 100 * 1024 * 1024 # 100MB max file size
|
||||
app.config['UPLOAD_FOLDER'] = 'uploaded'
|
||||
app.config['OUTPUT_FOLDER'] = 'output'
|
||||
app.config['ALLOWED_EXTENSIONS'] = {'csv', 'json', 'txt', 'log'}
|
||||
|
||||
# Ensure upload directories exist
|
||||
os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
|
||||
os.makedirs(app.config['OUTPUT_FOLDER'], exist_ok=True)
|
||||
|
||||
def allowed_file(filename):
|
||||
return '.' in filename and \
|
||||
filename.rsplit('.', 1)[1].lower() in app.config['ALLOWED_EXTENSIONS']
|
||||
|
||||
def validate_file_content(filepath):
|
||||
"""Validate file content using python-magic"""
|
||||
try:
|
||||
mime = magic.Magic(mime=True)
|
||||
file_mime = mime.from_file(filepath)
|
||||
allowed_mimes = ['text/plain', 'text/csv', 'application/json']
|
||||
return file_mime in allowed_mimes
|
||||
except Exception as e:
|
||||
logger.error(f"File validation error: {e}")
|
||||
return False
|
||||
|
||||
@app.errorhandler(RequestEntityTooLarge)
|
||||
def handle_file_too_large(e):
|
||||
return jsonify({'error': 'File too large. Maximum size is 100MB.'}), 413
|
||||
|
||||
@app.errorhandler(Exception)
|
||||
def handle_exception(e):
|
||||
logger.error(f"Unhandled exception: {e}")
|
||||
return jsonify({'error': 'Internal server error'}), 500
|
||||
|
||||
# API Routes
|
||||
@app.route('/api/health')
|
||||
def health_check():
|
||||
return jsonify({
|
||||
'status': 'healthy',
|
||||
'timestamp': datetime.utcnow().isoformat(),
|
||||
'version': '1.0.0'
|
||||
})
|
||||
|
||||
@app.route('/api/upload', methods=['POST'])
|
||||
def upload_file():
|
||||
try:
|
||||
if 'file' not in request.files:
|
||||
return jsonify({'error': 'No file provided'}), 400
|
||||
|
||||
file = request.files['file']
|
||||
if file.filename == '':
|
||||
return jsonify({'error': 'No file selected'}), 400
|
||||
|
||||
if not allowed_file(file.filename):
|
||||
return jsonify({'error': 'File type not allowed'}), 400
|
||||
|
||||
filename = secure_filename(file.filename)
|
||||
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
|
||||
filename = f"{timestamp}_{filename}"
|
||||
filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
|
||||
|
||||
file.save(filepath)
|
||||
|
||||
# Validate file content
|
||||
if not validate_file_content(filepath):
|
||||
os.remove(filepath)
|
||||
return jsonify({'error': 'Invalid file content'}), 400
|
||||
|
||||
logger.info(f"File uploaded successfully: {filename}")
|
||||
return jsonify({
|
||||
'message': 'File uploaded successfully',
|
||||
'filename': filename,
|
||||
'size': os.path.getsize(filepath)
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Upload error: {e}")
|
||||
return jsonify({'error': 'Upload failed'}), 500
|
||||
|
||||
@app.route('/api/analyze/<filename>')
|
||||
def analyze_file(filename):
|
||||
try:
|
||||
filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
|
||||
if not os.path.exists(filepath):
|
||||
return jsonify({'error': 'File not found'}), 404
|
||||
|
||||
# Basic file analysis
|
||||
file_stats = os.stat(filepath)
|
||||
analysis = {
|
||||
'filename': filename,
|
||||
'size': file_stats.st_size,
|
||||
'created': datetime.fromtimestamp(file_stats.st_ctime).isoformat(),
|
||||
'modified': datetime.fromtimestamp(file_stats.st_mtime).isoformat()
|
||||
}
|
||||
|
||||
# CSV specific analysis
|
||||
if filename.lower().endswith('.csv'):
|
||||
try:
|
||||
df = pd.read_csv(filepath)
|
||||
analysis.update({
|
||||
'rows': len(df),
|
||||
'columns': len(df.columns),
|
||||
'column_names': df.columns.tolist(),
|
||||
'preview': df.head().to_dict('records')
|
||||
})
|
||||
except Exception as e:
|
||||
analysis['csv_error'] = str(e)
|
||||
|
||||
return jsonify(analysis)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Analysis error: {e}")
|
||||
return jsonify({'error': 'Analysis failed'}), 500
|
||||
|
||||
@app.route('/api/files')
|
||||
def list_files():
|
||||
try:
|
||||
files = []
|
||||
upload_dir = app.config['UPLOAD_FOLDER']
|
||||
|
||||
for filename in os.listdir(upload_dir):
|
||||
filepath = os.path.join(upload_dir, filename)
|
||||
if os.path.isfile(filepath):
|
||||
stat = os.stat(filepath)
|
||||
files.append({
|
||||
'name': filename,
|
||||
'size': stat.st_size,
|
||||
'modified': datetime.fromtimestamp(stat.st_mtime).isoformat()
|
||||
})
|
||||
|
||||
return jsonify({'files': files})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"List files error: {e}")
|
||||
return jsonify({'error': 'Failed to list files'}), 500
|
||||
|
||||
# Static file serving
|
||||
@app.route("/assets/<path:path>")
|
||||
def send_assets(path):
|
||||
return send_from_directory(os.path.join(app.static_folder, "assets"), path)
|
||||
|
||||
@app.route("/uploaded/<path:path>")
|
||||
def send_uploads(path):
|
||||
return send_from_directory(os.path.join(app.static_folder, "assets"), path)
|
||||
|
||||
@app.route("/")
|
||||
def index():
|
||||
return send_from_directory(app.static_folder, "index.html")
|
||||
|
||||
# Catch-all route for React Router
|
||||
@app.route("/<path:path>")
|
||||
def catch_all(path):
|
||||
return send_from_directory(app.static_folder, "index.html")
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run(host="0.0.0.0", port=5000)
|
||||
app.run(host="0.0.0.0", port=5000, debug=True)
|
||||
|
||||
@@ -1,2 +1,11 @@
|
||||
flask
|
||||
python-dotenv
|
||||
flask==3.0.0
|
||||
flask-cors==4.0.0
|
||||
python-dotenv==1.0.0
|
||||
pandas==2.1.3
|
||||
requests==2.31.0
|
||||
werkzeug==3.0.1
|
||||
python-magic==0.4.27
|
||||
validators==0.22.0
|
||||
celery==5.3.4
|
||||
redis==5.0.1
|
||||
gunicorn==21.2.0
|
||||
|
||||
@@ -1,34 +1,34 @@
|
||||
{
|
||||
"name": "velo-threat-hunter-ui",
|
||||
"version": "0.1.0",
|
||||
"name": "velo-threat-hunter",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
"lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
|
||||
"preview": "vite preview",
|
||||
"test": "vitest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@emotion/styled": "^11.14.0",
|
||||
"@mui/icons-material": "^6.4.8",
|
||||
"@mui/material": "^6.4.8",
|
||||
"@tailwindcss/vite": "^4.1.7",
|
||||
"axios": "^1.10.0",
|
||||
"csv-parser": "^3.2.0",
|
||||
"express": "^5.1.0",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-router-dom": "^6.26.1",
|
||||
"lucide-react": "^0.515.0",
|
||||
"multer": "^2.0.1",
|
||||
"react": "^18.0.0",
|
||||
"react-dom": "^18.0.0",
|
||||
"react-dropzone": "^14.3.8",
|
||||
"react-router": "^7.6.0"
|
||||
"axios": "^1.7.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react-router": "^5.1.20",
|
||||
"@vitejs/plugin-react": "^4.4.1",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"postcss": "^8.5.5",
|
||||
"tailwindcss": "^4.1.10",
|
||||
"vite": "^6.3.5"
|
||||
"@types/react": "^18.3.3",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@vitejs/plugin-react": "^4.3.1",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-plugin-react": "^7.34.3",
|
||||
"eslint-plugin-react-hooks": "^4.6.2",
|
||||
"eslint-plugin-react-refresh": "^0.4.7",
|
||||
"vite": "^5.3.4",
|
||||
"vitest": "^1.6.0",
|
||||
"tailwindcss": "^3.4.4",
|
||||
"autoprefixer": "^10.4.19",
|
||||
"postcss": "^8.4.39"
|
||||
}
|
||||
}
|
||||
|
||||
12407
frontend/src/# Code Citations.md
Normal file
12407
frontend/src/# Code Citations.md
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,14 +1,19 @@
|
||||
import React, { Suspense, useMemo, lazy } from "react";
|
||||
import { createTheme, ThemeProvider } from "@mui/material/styles";
|
||||
import { CssBaseline } from "@mui/material";
|
||||
import { BrowserRouter, Routes, Route } from "react-router";
|
||||
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
|
||||
|
||||
import Sidebar from "./components/Sidebar";
|
||||
const HomePage = lazy(() => import("./components/HomePage"));
|
||||
const Baseline = lazy(() => import("./components/Baseline"));
|
||||
const Networking = lazy(() => import("./components/Networking"));
|
||||
const Applications = lazy(() => import("./components/Applications"));
|
||||
const SecurityTools = lazy(() => import("./components/securitytools"));
|
||||
const CSVProcessing = lazy(() => import("./components/CSVProcessing"));
|
||||
const SettingsConfig = lazy(() => import("./components/SettingsConfig"));
|
||||
const VirusTotal = lazy(() => import("./components/VirusTotal"));
|
||||
|
||||
function App() {
|
||||
|
||||
const theme = useMemo(
|
||||
() =>
|
||||
createTheme({
|
||||
@@ -22,20 +27,27 @@ function App() {
|
||||
return (
|
||||
<ThemeProvider theme={theme}>
|
||||
<CssBaseline />
|
||||
<div className="flex h-screen text-white">
|
||||
<div className="flex h-screen bg-zinc-900 text-white">
|
||||
<Router>
|
||||
<Sidebar />
|
||||
<div className="flex-1 p-6 overflow-auto">
|
||||
<BrowserRouter>
|
||||
<main className="flex-1 p-6 overflow-auto">
|
||||
<Suspense fallback={<div>Loading...</div>}>
|
||||
<Routes>
|
||||
<Route path="/" element={<HomePage />} />
|
||||
<Route path="/baseline" element={<Baseline />} />
|
||||
<Route path="/securitytools" element={<SecurityTools />} />
|
||||
<Route path="/networking" element={<Networking />} />
|
||||
<Route path="/applications" element={<Applications />} />
|
||||
<Route path="/csv-processing" element={<CSVProcessing />} />
|
||||
<Route path="/settings" element={<SettingsConfig />} />
|
||||
<Route path="/virus-total" element={<VirusTotal />} />
|
||||
<Route path="/security-tools" element={<SecurityTools />} />
|
||||
</Routes>
|
||||
</Suspense>
|
||||
</BrowserRouter>
|
||||
</div>
|
||||
</main>
|
||||
</Router>
|
||||
</div>
|
||||
</ThemeProvider>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
|
||||
@@ -1,7 +1,95 @@
|
||||
import React from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Activity, Upload, FileText, Shield } from 'lucide-react';
|
||||
|
||||
const HomePage = () => {
|
||||
return <div>Home Page Placeholder</div>;
|
||||
const [stats, setStats] = useState({
|
||||
filesUploaded: 0,
|
||||
analysesCompleted: 0,
|
||||
threatsDetected: 0
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
// Fetch dashboard stats
|
||||
fetch('/api/stats')
|
||||
.then(res => res.json())
|
||||
.then(data => setStats(data))
|
||||
.catch(err => console.error('Failed to fetch stats:', err));
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold mb-2">Velo Threat Hunter</h1>
|
||||
<p className="text-zinc-400">
|
||||
Advanced threat hunting and security analysis platform
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
<div className="card">
|
||||
<div className="flex items-center">
|
||||
<Upload className="w-8 h-8 text-cyan-400 mr-4" />
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold">Files Uploaded</h3>
|
||||
<p className="text-2xl font-bold text-cyan-400">{stats.filesUploaded}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="card">
|
||||
<div className="flex items-center">
|
||||
<Activity className="w-8 h-8 text-green-400 mr-4" />
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold">Analyses Completed</h3>
|
||||
<p className="text-2xl font-bold text-green-400">{stats.analysesCompleted}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="card">
|
||||
<div className="flex items-center">
|
||||
<Shield className="w-8 h-8 text-red-400 mr-4" />
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold">Threats Detected</h3>
|
||||
<p className="text-2xl font-bold text-red-400">{stats.threatsDetected}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="card">
|
||||
<h2 className="text-xl font-bold mb-4">Quick Actions</h2>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<button className="btn-primary flex items-center justify-center">
|
||||
<Upload className="w-5 h-5 mr-2" />
|
||||
Upload File for Analysis
|
||||
</button>
|
||||
<button className="btn-secondary flex items-center justify-center">
|
||||
<FileText className="w-5 h-5 mr-2" />
|
||||
View Recent Reports
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="card">
|
||||
<h2 className="text-xl font-bold mb-4">System Health</h2>
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between">
|
||||
<span>Backend Status</span>
|
||||
<span className="text-green-400">✓ Healthy</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span>Database Connection</span>
|
||||
<span className="text-green-400">✓ Connected</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span>Storage Available</span>
|
||||
<span className="text-yellow-400">⚠ 75% Used</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default HomePage;
|
||||
|
||||
@@ -1,32 +1,50 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Link, useLocation } from 'react-router-dom';
|
||||
import {
|
||||
ShieldCheck, Server, Bug, Lock, Globe, Settings,
|
||||
ChevronDown, ChevronRight, Folder
|
||||
ShieldCheck,
|
||||
Server,
|
||||
Bug,
|
||||
Folder,
|
||||
Globe,
|
||||
Settings,
|
||||
ChevronDown,
|
||||
ChevronRight,
|
||||
} from 'lucide-react';
|
||||
import AddIcon from '@mui/icons-material/Add';
|
||||
import BugReportIcon from '@mui/icons-material/BugReport';
|
||||
import EngineeringIcon from '@mui/icons-material/Engineering';
|
||||
|
||||
|
||||
const SidebarItem = ({ icon: Icon, label, children }) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const SidebarItem = ({ icon: Icon, label, to, children }) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const location = useLocation();
|
||||
const hasChildren = !!children;
|
||||
const isActive = location.pathname === to;
|
||||
|
||||
const handleClick = () => {
|
||||
if (hasChildren) {
|
||||
setIsOpen(!isOpen);
|
||||
}
|
||||
};
|
||||
|
||||
const itemContent = (
|
||||
<div
|
||||
className={`sidebar-item ${isActive ? 'bg-zinc-800' : ''}`}
|
||||
onClick={handleClick}
|
||||
>
|
||||
<Icon className="sidebar-icon" />
|
||||
<span className="flex-grow">{label}</span>
|
||||
{hasChildren && (isOpen ? <ChevronDown className="w-4 h-4" /> : <ChevronRight className="w-4 h-4" />)}
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="text-sm w-full">
|
||||
<div
|
||||
className="flex items-center justify-between px-4 py-2 cursor-pointer rounded hover:bg-zinc-800 text-white transition-all"
|
||||
onClick={() => hasChildren && setOpen(!open)}
|
||||
>
|
||||
<div className="flex items-center space-x-3">
|
||||
<Icon className="w-5 h-5 text-cyan-400" />
|
||||
<span>{label}</span>
|
||||
{hasChildren &&
|
||||
(open ? <AddIcon className="w-4 h-4" /> : <ChevronRight className="w-4 h-4" />)}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{hasChildren && open && (
|
||||
<div className="text-sm">
|
||||
{to && !hasChildren ? (
|
||||
<Link to={to}>{itemContent}</Link>
|
||||
) : (
|
||||
itemContent
|
||||
)}
|
||||
{hasChildren && isOpen && (
|
||||
<div className="ml-8 mt-1 space-y-1 text-zinc-400">
|
||||
{children}
|
||||
</div>
|
||||
@@ -36,20 +54,24 @@ const SidebarItem = ({ icon: Icon, label, children }) => {
|
||||
};
|
||||
|
||||
const Sidebar = () => (
|
||||
<div className="h-screen w-64 shadow-lg p-4 flex flex-col space-y-2">
|
||||
<div className="w-56 h-full bg-zinc-950 p-4 flex flex-col space-y-2 rounded-r-xl shadow-md">
|
||||
<h2 className="text-xl font-bold text-white mb-4">Threat Hunt Dashboard</h2>
|
||||
<SidebarItem icon={ShieldCheck} label="HomePage" />
|
||||
<SidebarItem icon={Server} label="Baseline" />
|
||||
<SidebarItem icon={Bug} label="Networking" />
|
||||
<SidebarItem icon={Folder} label="Applications" />
|
||||
<SidebarItem icon={Globe} label="CSV Processing" />
|
||||
<SidebarItem icon={Settings} label="Security Tools">
|
||||
<div>Anti Virus</div>
|
||||
<div>Endpoint Detection & Response</div>
|
||||
<div>Virtual Private Networks</div>
|
||||
</SidebarItem>
|
||||
<SidebarItem icon={BugReportIcon} label="Virus Totals" />
|
||||
<SidebarItem icon={EngineeringIcon} label="Settings & Config" />
|
||||
<SidebarItem icon={ShieldCheck} label="HomePage" to="/" />
|
||||
<SidebarItem icon={Server} label="Baseline" to="/baseline" />
|
||||
<SidebarItem icon={Bug} label="Networking" to="/networking" />
|
||||
<SidebarItem icon={Folder} label="Applications" to="/applications" />
|
||||
<SidebarItem icon={Globe} label="CSV Processing" to="/csv-processing" />
|
||||
<SidebarItem
|
||||
icon={Settings}
|
||||
label="Tools / Configs"
|
||||
children={
|
||||
<>
|
||||
<Link to="/security-tools" className="block hover:text-white">Security Tools</Link>
|
||||
<Link to="/settings" className="block hover:text-white">Configuration</Link>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<SidebarItem icon={Globe} label="Virus Total" to="/virus-total" />
|
||||
</div>
|
||||
);
|
||||
|
||||
|
||||
@@ -1 +1,33 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer base {
|
||||
body {
|
||||
@apply bg-zinc-900 text-white font-sans;
|
||||
}
|
||||
}
|
||||
|
||||
@layer components {
|
||||
.sidebar-item {
|
||||
@apply flex items-center cursor-pointer px-4 py-2 rounded-xl hover:bg-zinc-800 transition-all;
|
||||
}
|
||||
|
||||
.sidebar-icon {
|
||||
@apply w-5 h-5 mr-3 text-cyan-400;
|
||||
}
|
||||
|
||||
.card {
|
||||
@apply bg-zinc-800 rounded-lg p-6 shadow-lg;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
@apply bg-cyan-600 hover:bg-cyan-700 text-white px-4 py-2 rounded-lg transition-colors;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
@apply bg-zinc-700 hover:bg-zinc-600 text-white px-4 py-2 rounded-lg transition-colors;
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import App from './App';
|
||||
import { StrictMode } from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import App from './App.jsx';
|
||||
import './index.css';
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root')).render(
|
||||
<React.StrictMode>
|
||||
createRoot(document.getElementById('root')).render(
|
||||
<StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>
|
||||
</StrictMode>,
|
||||
);
|
||||
|
||||
@@ -7,4 +7,19 @@ export default defineConfig({
|
||||
react(),
|
||||
tailwindcss(),
|
||||
],
|
||||
server: {
|
||||
port: 3000,
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'http://localhost:5000',
|
||||
changeOrigin: true,
|
||||
secure: false
|
||||
}
|
||||
}
|
||||
},
|
||||
build: {
|
||||
outDir: 'dist',
|
||||
assetsDir: 'assets',
|
||||
sourcemap: true
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user