import { useState, useRef, useEffect, useCallback, useMemo } from 'react' import { useNavigate } from 'react-router-dom' import { Upload, Link as LinkIcon, FileText, Play, AlertTriangle, Bot, Search, Target, Brain, BookOpen, ChevronDown, Key, Settings, X } from 'lucide-react' import Card from '../components/common/Card' import Button from '../components/common/Button' import Input from '../components/common/Input' import Textarea from '../components/common/Textarea' import { agentApi, targetsApi } from '../services/api' import type { AgentTask, AgentMode, AgentRequest } from '../types' type TargetInputMode = 'single' | 'multiple' | 'file' type AuthTypeOption = 'none' | 'cookie' | 'bearer' | 'basic' | 'header' interface OperationModeInfo { id: AgentMode name: string icon: React.ReactNode description: string warning?: string color: string } interface Toast { id: number message: string severity: 'info' | 'success' | 'warning' | 'error' } let _toastId = 0 function ToastContainer({ toasts, onDismiss }: { toasts: Toast[]; onDismiss: (id: number) => void }) { if (toasts.length === 0) return null const border: Record = { info: 'border-blue-500', success: 'border-green-500', warning: 'border-yellow-500', error: 'border-red-500' } return (
{toasts.map(t => (
{t.message}
))}
) } const OPERATION_MODES: OperationModeInfo[] = [ { id: 'full_auto', name: 'Full Auto', icon: , description: 'Complete workflow: Recon -> Analyze -> Test -> Report', color: 'primary' }, { id: 'recon_only', name: 'Recon Only', icon: , description: 'Reconnaissance and enumeration only, no vulnerability testing', color: 'blue' }, { id: 'prompt_only', name: 'AI Prompt Mode', icon: , description: 'AI decides everything based on your prompt - full autonomy', warning: 'HIGH TOKEN USAGE - The AI will use more API calls to decide what to do', color: 'purple' }, { id: 'analyze_only', name: 'Analyze Only', icon: , description: 'Analyze provided data without active testing', color: 'green' } ] const TASK_CATEGORIES = [ { id: 'all', name: 'All Tasks' }, { id: 'full_auto', name: 'Full Auto' }, { id: 'recon', name: 'Reconnaissance' }, { id: 'vulnerability', name: 'Vulnerability' }, { id: 'custom', name: 'Custom' }, { id: 'reporting', name: 'Reporting' } ] const AUTH_TYPE_OPTIONS: { id: AuthTypeOption; label: string }[] = [ { id: 'none', label: 'None' }, { id: 'cookie', label: 'Cookie' }, { id: 'bearer', label: 'Bearer Token' }, { id: 'basic', label: 'Basic Auth' }, { id: 'header', label: 'Custom Header' } ] export default function NewScanPage() { const navigate = useNavigate() const fileInputRef = useRef(null) // Target state const [targetMode, setTargetMode] = useState('single') const [singleUrl, setSingleUrl] = useState('') const [multipleUrls, setMultipleUrls] = useState('') const [uploadedUrls, setUploadedUrls] = useState([]) const [urlError, setUrlError] = useState('') // Operation mode const [operationMode, setOperationMode] = useState('full_auto') // Task library const [tasks, setTasks] = useState([]) const [selectedTask, setSelectedTask] = useState(null) const [taskCategory, setTaskCategory] = useState('all') const [showTaskLibrary, setShowTaskLibrary] = useState(false) const [loadingTasks, setLoadingTasks] = useState(false) // Custom prompt const [useCustomPrompt, setUseCustomPrompt] = useState(false) const [customPrompt, setCustomPrompt] = useState('') // Auth options const [showAuthOptions, setShowAuthOptions] = useState(false) const [authType, setAuthType] = useState('none') const [authValue, setAuthValue] = useState('') // Advanced options const [maxDepth, setMaxDepth] = useState(5) // UI state const [isLoading, setIsLoading] = useState(false) // Toast state const [toasts, setToasts] = useState([]) const addToast = useCallback((message: string, severity: Toast['severity'] = 'info') => { const id = ++_toastId setToasts(prev => [...prev, { id, message, severity }]) setTimeout(() => { setToasts(prev => prev.filter(t => t.id !== id)) }, 4000) }, []) const dismissToast = useCallback((id: number) => { setToasts(prev => prev.filter(t => t.id !== id)) }, []) // Load tasks on mount useEffect(() => { loadTasks() }, []) const loadTasks = async (category?: string) => { setLoadingTasks(true) try { const taskList = await agentApi.tasks.list(category === 'all' ? undefined : category) setTasks(taskList) } catch (error) { console.error('Failed to load tasks:', error) } finally { setLoadingTasks(false) } } const handleCategoryChange = useCallback((category: string) => { setTaskCategory(category) loadTasks(category) }, []) const handleFileUpload = useCallback(async (e: React.ChangeEvent) => { const file = e.target.files?.[0] if (!file) return try { const result = await targetsApi.upload(file) const validUrls = result.filter((r: { valid: boolean; normalized_url: string }) => r.valid).map((r: { valid: boolean; normalized_url: string }) => r.normalized_url) setUploadedUrls(validUrls) setUrlError('') } catch (error) { setUrlError('Failed to parse file') } }, []) const getTargetUrl = useCallback((): string => { switch (targetMode) { case 'single': return singleUrl.trim() case 'multiple': return multipleUrls.split(/[,\n]/)[0]?.trim() || '' case 'file': return uploadedUrls[0] || '' default: return '' } }, [targetMode, singleUrl, multipleUrls, uploadedUrls]) const handleStartAgent = useCallback(async () => { const target = getTargetUrl() if (!target) { setUrlError('Please enter a target URL') addToast('Please enter a target URL before deploying', 'warning') return } setIsLoading(true) try { // Validate URL const validation = await targetsApi.validateBulk([target]) if (!validation[0]?.valid) { setUrlError('Invalid URL format') addToast('Invalid URL format - please check and try again', 'error') setIsLoading(false) return } // Build request const request: AgentRequest = { target: validation[0].normalized_url, mode: operationMode, max_depth: maxDepth } // Add task or custom prompt if (selectedTask && !useCustomPrompt) { request.task_id = selectedTask.id } else if (useCustomPrompt && customPrompt.trim()) { request.prompt = customPrompt } // Add auth if specified if (authType !== 'none' && authValue.trim()) { request.auth_type = authType as AgentRequest['auth_type'] request.auth_value = authValue } // Start agent const response = await agentApi.run(request) addToast('Agent deployed successfully - redirecting...', 'success') // Navigate to agent status page setTimeout(() => { navigate(`/agent/${response.agent_id}`) }, 300) } catch (error) { console.error('Failed to start agent:', error) setUrlError('Failed to start agent. Please try again.') addToast('Failed to start agent - please try again', 'error') } finally { setIsLoading(false) } }, [getTargetUrl, operationMode, maxDepth, selectedTask, useCustomPrompt, customPrompt, authType, authValue, addToast, navigate]) const handleSelectTask = useCallback((task: AgentTask) => { setSelectedTask(task) addToast(`Task selected: ${task.name}`, 'info') }, [addToast]) const handleClearTask = useCallback(() => { setSelectedTask(null) }, []) const handleSetAuthType = useCallback((id: AuthTypeOption) => { setAuthType(id) }, []) const handleToggleTaskLibrary = useCallback(() => { setShowTaskLibrary(prev => !prev) }, []) const handleToggleAuthOptions = useCallback(() => { setShowAuthOptions(prev => !prev) }, []) const handleToggleCustomPrompt = useCallback((e: React.ChangeEvent) => { setUseCustomPrompt(e.target.checked) }, []) const handleSingleUrlChange = useCallback((e: React.ChangeEvent) => { setSingleUrl(e.target.value) setUrlError('') }, []) const handleMultipleUrlsChange = useCallback((e: React.ChangeEvent) => { setMultipleUrls(e.target.value) setUrlError('') }, []) const handleCustomPromptChange = useCallback((e: React.ChangeEvent) => { setCustomPrompt(e.target.value) }, []) const handleMaxDepthChange = useCallback((e: React.ChangeEvent) => { setMaxDepth(parseInt(e.target.value)) }, []) const handleAuthValueChange = useCallback((e: React.ChangeEvent) => { setAuthValue(e.target.value) }, []) const handleNavigateHome = useCallback(() => { navigate('/') }, [navigate]) // Memoized filtered task list const filteredTasks = useMemo(() => { if (showTaskLibrary) return tasks return tasks.slice(0, 4) }, [tasks, showTaskLibrary]) const currentModeInfo = OPERATION_MODES.find(m => m.id === operationMode)! return (
{/* Header */}

AI Security Agent

Autonomous penetration testing powered by AI

{/* Operation Mode Selector */}
{OPERATION_MODES.map((mode, idx) => (
setOperationMode(mode.id)} className={`p-4 rounded-xl border-2 cursor-pointer transition-all ${ operationMode === mode.id ? `border-${mode.color}-500 bg-${mode.color}-500/10` : 'border-dark-700 hover:border-dark-500 bg-dark-900/50' }`} style={{ animation: `fadeSlideIn 0.3s ease-out ${idx * 0.05}s both` }} >
{mode.icon} {mode.name}

{mode.description}

{mode.warning && operationMode === mode.id && (
{mode.warning}
)}
))}
{/* Target Input */}
{/* Mode Selector */}
{/* Input Fields */} {targetMode === 'single' && ( )} {targetMode === 'multiple' && (