diff --git a/docker-compose.lite.yml b/docker-compose.lite.yml new file mode 100644 index 0000000..875cbfe --- /dev/null +++ b/docker-compose.lite.yml @@ -0,0 +1,45 @@ +# NeuroSploit v3 - LITE Docker Compose +# Fast builds without external security tools +# Usage: docker compose -f docker-compose.lite.yml up --build + +services: + backend: + build: + context: . + dockerfile: docker/Dockerfile.backend.lite + container_name: neurosploit-backend + env_file: + - .env + environment: + - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY:-} + - OPENAI_API_KEY=${OPENAI_API_KEY:-} + - DATABASE_URL=sqlite+aiosqlite:///./data/neurosploit.db + volumes: + - neurosploit-data:/app/data + ports: + - "8000:8000" + restart: unless-stopped + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8000/api/health"] + interval: 30s + timeout: 10s + retries: 3 + + frontend: + build: + context: . + dockerfile: docker/Dockerfile.frontend + container_name: neurosploit-frontend + ports: + - "3000:80" + depends_on: + backend: + condition: service_healthy + restart: unless-stopped + +volumes: + neurosploit-data: + +networks: + default: + name: neurosploit-network diff --git a/docker-compose.yml b/docker-compose.yml index c53af4d..486e37b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,9 +1,44 @@ services: - dvwa: - image: vulnerables/web-dvwa - container_name: dvwa - ports: - - "8080:80" + backend: + build: + context: . + # Use Dockerfile.backend.lite for faster builds (no security tools) + # Use Dockerfile.backend for full version with all tools + dockerfile: docker/Dockerfile.backend + container_name: neurosploit-backend + env_file: + - .env environment: - - MYSQL_PASS=password + # These override .env if set + - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY:-} + - OPENAI_API_KEY=${OPENAI_API_KEY:-} + - DATABASE_URL=sqlite+aiosqlite:///./data/neurosploit.db + volumes: + - neurosploit-data:/app/data + ports: + - "8000:8000" restart: unless-stopped + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8000/api/health"] + interval: 30s + timeout: 10s + retries: 3 + + frontend: + build: + context: . + dockerfile: docker/Dockerfile.frontend + container_name: neurosploit-frontend + ports: + - "3000:80" + depends_on: + backend: + condition: service_healthy + restart: unless-stopped + +volumes: + neurosploit-data: + +networks: + default: + name: neurosploit-network diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000..32b2743 --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + NeuroSploit v3 - AI-Powered Penetration Testing + + +
+ + + diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..3c42dec --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,34 @@ +{ + "name": "neurosploit-frontend", + "version": "3.0.0", + "description": "NeuroSploit v3 - AI-Powered Penetration Testing Platform", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview", + "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0" + }, + "dependencies": { + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-router-dom": "^6.21.0", + "zustand": "^4.4.0", + "axios": "^1.6.0", + "socket.io-client": "^4.6.0", + "recharts": "^2.10.0", + "lucide-react": "^0.303.0", + "clsx": "^2.1.0", + "tailwind-merge": "^2.2.0" + }, + "devDependencies": { + "@types/react": "^18.2.0", + "@types/react-dom": "^18.2.0", + "@vitejs/plugin-react": "^4.2.0", + "autoprefixer": "^10.4.16", + "postcss": "^8.4.32", + "tailwindcss": "^3.4.0", + "typescript": "^5.3.0", + "vite": "^5.0.0" + } +} diff --git a/frontend/postcss.config.js b/frontend/postcss.config.js new file mode 100644 index 0000000..2e7af2b --- /dev/null +++ b/frontend/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/frontend/public/favicon.svg b/frontend/public/favicon.svg new file mode 100644 index 0000000..2e8af02 --- /dev/null +++ b/frontend/public/favicon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx new file mode 100644 index 0000000..988f9a0 --- /dev/null +++ b/frontend/src/App.tsx @@ -0,0 +1,31 @@ +import { Routes, Route } from 'react-router-dom' +import Layout from './components/layout/Layout' +import HomePage from './pages/HomePage' +import NewScanPage from './pages/NewScanPage' +import ScanDetailsPage from './pages/ScanDetailsPage' +import AgentStatusPage from './pages/AgentStatusPage' +import TaskLibraryPage from './pages/TaskLibraryPage' +import RealtimeTaskPage from './pages/RealtimeTaskPage' +import ReportsPage from './pages/ReportsPage' +import ReportViewPage from './pages/ReportViewPage' +import SettingsPage from './pages/SettingsPage' + +function App() { + return ( + + + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + + ) +} + +export default App diff --git a/frontend/src/components/common/Badge.tsx b/frontend/src/components/common/Badge.tsx new file mode 100644 index 0000000..c3b318d --- /dev/null +++ b/frontend/src/components/common/Badge.tsx @@ -0,0 +1,37 @@ +import { clsx } from 'clsx' + +interface BadgeProps { + variant?: 'critical' | 'high' | 'medium' | 'low' | 'info' | 'success' | 'warning' | 'default' + children: React.ReactNode + className?: string +} + +const variants = { + critical: 'bg-red-500/20 text-red-400 border-red-500/30', + high: 'bg-orange-500/20 text-orange-400 border-orange-500/30', + medium: 'bg-yellow-500/20 text-yellow-400 border-yellow-500/30', + low: 'bg-blue-500/20 text-blue-400 border-blue-500/30', + info: 'bg-gray-500/20 text-gray-400 border-gray-500/30', + success: 'bg-green-500/20 text-green-400 border-green-500/30', + warning: 'bg-amber-500/20 text-amber-400 border-amber-500/30', + default: 'bg-dark-900/50 text-dark-300 border-dark-700', +} + +export default function Badge({ variant = 'default', children, className }: BadgeProps) { + return ( + + {children} + + ) +} + +export function SeverityBadge({ severity }: { severity: string }) { + const variant = severity.toLowerCase() as BadgeProps['variant'] + return {severity.toUpperCase()} +} diff --git a/frontend/src/components/common/Button.tsx b/frontend/src/components/common/Button.tsx new file mode 100644 index 0000000..48622b5 --- /dev/null +++ b/frontend/src/components/common/Button.tsx @@ -0,0 +1,70 @@ +import { ButtonHTMLAttributes, ReactNode } from 'react' +import { clsx } from 'clsx' + +interface ButtonProps extends ButtonHTMLAttributes { + variant?: 'primary' | 'secondary' | 'danger' | 'ghost' + size?: 'sm' | 'md' | 'lg' + isLoading?: boolean + children: ReactNode +} + +export default function Button({ + variant = 'primary', + size = 'md', + isLoading = false, + children, + className, + disabled, + ...props +}: ButtonProps) { + const baseStyles = 'inline-flex items-center justify-center font-medium rounded-lg transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-dark-700 disabled:opacity-50 disabled:cursor-not-allowed' + + const variants = { + primary: 'bg-primary-500 text-white hover:bg-primary-600 focus:ring-primary-500', + secondary: 'bg-dark-900 text-white hover:bg-dark-800 focus:ring-dark-500', + danger: 'bg-red-600 text-white hover:bg-red-700 focus:ring-red-500', + ghost: 'text-dark-300 hover:text-white hover:bg-dark-900/50 focus:ring-dark-500', + } + + const sizes = { + sm: 'px-3 py-1.5 text-sm', + md: 'px-4 py-2 text-sm', + lg: 'px-6 py-3 text-base', + } + + return ( + + ) +} diff --git a/frontend/src/components/common/Card.tsx b/frontend/src/components/common/Card.tsx new file mode 100644 index 0000000..129b17c --- /dev/null +++ b/frontend/src/components/common/Card.tsx @@ -0,0 +1,27 @@ +import { ReactNode } from 'react' +import { clsx } from 'clsx' + +interface CardProps { + children: ReactNode + className?: string + title?: ReactNode + subtitle?: string + action?: ReactNode +} + +export default function Card({ children, className, title, subtitle, action }: CardProps) { + return ( +
+ {(title || action) && ( +
+
+ {title &&

{title}

} + {subtitle &&

{subtitle}

} +
+ {action} +
+ )} +
{children}
+
+ ) +} diff --git a/frontend/src/components/common/Input.tsx b/frontend/src/components/common/Input.tsx new file mode 100644 index 0000000..b6e7b4a --- /dev/null +++ b/frontend/src/components/common/Input.tsx @@ -0,0 +1,41 @@ +import { InputHTMLAttributes, forwardRef } from 'react' +import { clsx } from 'clsx' + +interface InputProps extends InputHTMLAttributes { + label?: string + error?: string + helperText?: string +} + +const Input = forwardRef( + ({ label, error, helperText, className, ...props }, ref) => { + return ( +
+ {label && ( + + )} + + {error &&

{error}

} + {helperText && !error && ( +

{helperText}

+ )} +
+ ) + } +) + +Input.displayName = 'Input' + +export default Input diff --git a/frontend/src/components/common/Textarea.tsx b/frontend/src/components/common/Textarea.tsx new file mode 100644 index 0000000..ebab5b4 --- /dev/null +++ b/frontend/src/components/common/Textarea.tsx @@ -0,0 +1,41 @@ +import { TextareaHTMLAttributes, forwardRef } from 'react' +import { clsx } from 'clsx' + +interface TextareaProps extends TextareaHTMLAttributes { + label?: string + error?: string + helperText?: string +} + +const Textarea = forwardRef( + ({ label, error, helperText, className, ...props }, ref) => { + return ( +
+ {label && ( + + )} +