import { useState, useEffect, useCallback, useMemo } from 'react' import { Save, Shield, Trash2, RefreshCw, AlertTriangle, Brain, Router, Eye, ChevronDown, Loader2, Bell, Send, Phone, MessageCircle, Hash, X, Settings, Database, Zap, CheckCircle2, } from 'lucide-react' import Card from '../components/common/Card' import Button from '../components/common/Button' import Input from '../components/common/Input' /* ------------------------------------------------------------------ */ /* Types */ /* ------------------------------------------------------------------ */ interface Settings { llm_provider: string llm_model: string has_anthropic_key: boolean has_openai_key: boolean has_openrouter_key: boolean has_gemini_key: boolean has_together_key: boolean has_fireworks_key: boolean ollama_base_url: string lmstudio_base_url: string max_concurrent_scans: number aggressive_mode: boolean default_scan_type: string recon_enabled_by_default: boolean enable_model_routing: boolean enable_knowledge_augmentation: boolean enable_browser_validation: boolean max_output_tokens: number | null // Notifications enable_notifications: boolean has_discord_webhook: boolean has_telegram_bot: boolean has_twilio_credentials: boolean notification_severity_filter: string } interface DbStats { scans: number vulnerabilities: number endpoints: number reports: number } interface ModelInfo { model_id: string display_name: string size?: string context_length?: number is_local: boolean } /* ------------------------------------------------------------------ */ /* Toast notification system */ /* ------------------------------------------------------------------ */ 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}
))}
) } /* ------------------------------------------------------------------ */ /* Constants */ /* ------------------------------------------------------------------ */ const PROVIDERS = [ { id: 'claude', label: 'Claude', color: 'orange' }, { id: 'openai', label: 'OpenAI', color: 'emerald' }, { id: 'gemini', label: 'Gemini', color: 'blue' }, { id: 'openrouter', label: 'OpenRouter', color: 'violet' }, { id: 'together', label: 'Together AI', color: 'teal' }, { id: 'fireworks', label: 'Fireworks AI', color: 'rose' }, { id: 'ollama', label: 'Ollama', color: 'gray' }, { id: 'lmstudio', label: 'LM Studio', color: 'slate' }, ] as const const FEATURE_TOGGLES = [ { key: 'modelRouting' as const, icon: Router, iconColor: 'text-blue-400', title: 'Model Routing', description: 'Route tasks to specialized LLM profiles by type (reasoning, analysis, generation)', }, { key: 'knowledgeAugmentation' as const, icon: Brain, iconColor: 'text-purple-400', title: 'Knowledge Augmentation', description: 'Enrich AI context with bug bounty pattern datasets (19 vuln types)', }, { key: 'browserValidation' as const, icon: Eye, iconColor: 'text-green-400', title: 'Browser Validation', description: 'Playwright-based browser validation with screenshot capture', }, ] as const /* ------------------------------------------------------------------ */ /* Helpers */ /* ------------------------------------------------------------------ */ function formatNumber(n: number): string { if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M` if (n >= 1_000) return `${(n / 1_000).toFixed(1)}K` return String(n) } /* ------------------------------------------------------------------ */ /* Page Component */ /* ------------------------------------------------------------------ */ export default function SettingsPage() { const [settings, setSettings] = useState(null) const [dbStats, setDbStats] = useState(null) const [apiKey, setApiKey] = useState('') const [openaiKey, setOpenaiKey] = useState('') const [openrouterKey, setOpenrouterKey] = useState('') const [geminiKey, setGeminiKey] = useState('') const [togetherKey, setTogetherKey] = useState('') const [fireworksKey, setFireworksKey] = useState('') const [ollamaUrl, setOllamaUrl] = useState('') const [lmstudioUrl, setLmstudioUrl] = useState('') const [llmProvider, setLlmProvider] = useState('claude') const [llmModel, setLlmModel] = useState('') const [maxConcurrentScans, setMaxConcurrentScans] = useState('3') const [maxOutputTokens, setMaxOutputTokens] = useState('') const [aggressiveMode, setAggressiveMode] = useState(false) const [enableModelRouting, setEnableModelRouting] = useState(false) const [enableKnowledgeAugmentation, setEnableKnowledgeAugmentation] = useState(false) const [enableBrowserValidation, setEnableBrowserValidation] = useState(false) // Notifications const [enableNotifications, setEnableNotifications] = useState(false) const [discordWebhookUrl, setDiscordWebhookUrl] = useState('') const [telegramBotToken, setTelegramBotToken] = useState('') const [telegramChatId, setTelegramChatId] = useState('') const [twilioAccountSid, setTwilioAccountSid] = useState('') const [twilioAuthToken, setTwilioAuthToken] = useState('') const [twilioFromNumber, setTwilioFromNumber] = useState('') const [twilioToNumber, setTwilioToNumber] = useState('') const [notificationSeverityFilter, setNotificationSeverityFilter] = useState('critical,high') const [testingChannel, setTestingChannel] = useState(null) const [isSaving, setIsSaving] = useState(false) const [isClearing, setIsClearing] = useState(false) const [showClearConfirm, setShowClearConfirm] = useState(false) const [availableModels, setAvailableModels] = useState([]) const [loadingModels, setLoadingModels] = useState(false) const [refreshSpinning, setRefreshSpinning] = useState(false) const [statsRefreshing, setStatsRefreshing] = useState(false) const [toasts, setToasts] = useState([]) /* ---------- Toast helpers ---------- */ const addToast = useCallback((message: string, severity: Toast['severity']) => { const id = ++_toastId setToasts(prev => [...prev, { id, message, severity }]) setTimeout(() => setToasts(prev => prev.filter(t => t.id !== id)), 5000) }, []) const dismissToast = useCallback((id: number) => { setToasts(prev => prev.filter(t => t.id !== id)) }, []) /* ---------- Derived data ---------- */ const totalDbRecords = useMemo(() => { if (!dbStats) return 0 return dbStats.scans + dbStats.vulnerabilities + dbStats.endpoints + dbStats.reports }, [dbStats]) const activeProviderLabel = useMemo(() => { return PROVIDERS.find(p => p.id === llmProvider)?.label || llmProvider }, [llmProvider]) const hasApiKeyForProvider = useMemo((): boolean => { if (!settings) return false const keyMap: Record = { claude: settings.has_anthropic_key, openai: settings.has_openai_key, gemini: settings.has_gemini_key, openrouter: settings.has_openrouter_key, together: settings.has_together_key, fireworks: settings.has_fireworks_key, ollama: true, lmstudio: true, } return keyMap[llmProvider] ?? false }, [settings, llmProvider]) const enabledFeaturesCount = useMemo(() => { return [enableModelRouting, enableKnowledgeAugmentation, enableBrowserValidation].filter(Boolean).length }, [enableModelRouting, enableKnowledgeAugmentation, enableBrowserValidation]) /* ---------- Data fetching ---------- */ const fetchSettings = useCallback(async () => { try { const response = await fetch('/api/v1/settings') if (response.ok) { const data: Settings = await response.json() setSettings(data) setLlmProvider(data.llm_provider) setLlmModel(data.llm_model || '') setMaxConcurrentScans(String(data.max_concurrent_scans)) setAggressiveMode(data.aggressive_mode) setEnableModelRouting(data.enable_model_routing ?? false) setEnableKnowledgeAugmentation(data.enable_knowledge_augmentation ?? false) setEnableBrowserValidation(data.enable_browser_validation ?? false) setMaxOutputTokens(data.max_output_tokens ? String(data.max_output_tokens) : '') setOllamaUrl(data.ollama_base_url || '') setLmstudioUrl(data.lmstudio_base_url || '') setEnableNotifications(data.enable_notifications ?? false) setNotificationSeverityFilter(data.notification_severity_filter || 'critical,high') } } catch (error) { console.error('Failed to fetch settings:', error) addToast('Failed to load settings', 'error') } }, [addToast]) const fetchDbStats = useCallback(async () => { try { const response = await fetch('/api/v1/settings/stats') if (response.ok) { const data: DbStats = await response.json() setDbStats(data) } } catch (error) { console.error('Failed to fetch db stats:', error) } }, []) const fetchModels = useCallback(async (provider: string) => { setLoadingModels(true) try { const response = await fetch(`/api/v1/settings/models/${provider}`) if (response.ok) { const data = await response.json() setAvailableModels((data.models as ModelInfo[]) || []) } else { setAvailableModels([]) } } catch { setAvailableModels([]) } finally { setLoadingModels(false) } }, []) useEffect(() => { fetchSettings() fetchDbStats() }, [fetchSettings, fetchDbStats]) useEffect(() => { fetchModels(llmProvider) }, [llmProvider, fetchModels]) /* ---------- Handlers ---------- */ const handleSave = useCallback(async () => { setIsSaving(true) try { const body: Record = { llm_provider: llmProvider, llm_model: llmModel || undefined, max_concurrent_scans: parseInt(maxConcurrentScans), aggressive_mode: aggressiveMode, enable_model_routing: enableModelRouting, enable_knowledge_augmentation: enableKnowledgeAugmentation, enable_browser_validation: enableBrowserValidation, max_output_tokens: maxOutputTokens ? parseInt(maxOutputTokens) : null, enable_notifications: enableNotifications, notification_severity_filter: notificationSeverityFilter, } // Notification credentials (only send if changed) if (discordWebhookUrl) body.discord_webhook_url = discordWebhookUrl if (telegramBotToken) body.telegram_bot_token = telegramBotToken if (telegramChatId) body.telegram_chat_id = telegramChatId if (twilioAccountSid) body.twilio_account_sid = twilioAccountSid if (twilioAuthToken) body.twilio_auth_token = twilioAuthToken if (twilioFromNumber) body.twilio_from_number = twilioFromNumber if (twilioToNumber) body.twilio_to_number = twilioToNumber // Only send keys that were changed if (apiKey) body.anthropic_api_key = apiKey if (openaiKey) body.openai_api_key = openaiKey if (openrouterKey) body.openrouter_api_key = openrouterKey if (geminiKey) body.gemini_api_key = geminiKey if (togetherKey) body.together_api_key = togetherKey if (fireworksKey) body.fireworks_api_key = fireworksKey if (ollamaUrl) body.ollama_base_url = ollamaUrl if (lmstudioUrl) body.lmstudio_base_url = lmstudioUrl const response = await fetch('/api/v1/settings', { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body), }) if (response.ok) { const data: Settings = await response.json() setSettings(data) setApiKey('') setOpenaiKey('') setOpenrouterKey('') setGeminiKey('') setTogetherKey('') setFireworksKey('') setDiscordWebhookUrl('') setTelegramBotToken('') setTelegramChatId('') setTwilioAccountSid('') setTwilioAuthToken('') setTwilioFromNumber('') setTwilioToNumber('') addToast('Settings saved successfully!', 'success') } else { addToast('Failed to save settings', 'error') } } catch { addToast('Failed to save settings', 'error') } finally { setIsSaving(false) } }, [ llmProvider, llmModel, maxConcurrentScans, aggressiveMode, enableModelRouting, enableKnowledgeAugmentation, enableBrowserValidation, maxOutputTokens, enableNotifications, notificationSeverityFilter, discordWebhookUrl, telegramBotToken, telegramChatId, twilioAccountSid, twilioAuthToken, twilioFromNumber, twilioToNumber, apiKey, openaiKey, openrouterKey, geminiKey, togetherKey, fireworksKey, ollamaUrl, lmstudioUrl, addToast, ]) const handleClearDatabase = useCallback(async () => { setIsClearing(true) try { const response = await fetch('/api/v1/settings/clear-database', { method: 'POST', }) if (response.ok) { addToast('Database cleared successfully!', 'success') setShowClearConfirm(false) fetchDbStats() } else { const data = await response.json() addToast(data.detail || 'Failed to clear database', 'error') } } catch { addToast('Failed to clear database', 'error') } finally { setIsClearing(false) } }, [addToast, fetchDbStats]) const handleTestNotification = useCallback(async (channel: string) => { setTestingChannel(channel) try { const response = await fetch(`/api/v1/settings/notifications/test/${channel}`, { method: 'POST' }) const data = await response.json() if (data.success) { addToast(data.message || `Test sent to ${channel}`, 'success') } else { addToast(data.error || `Test failed for ${channel}`, 'error') } } catch { addToast(`Failed to send test to ${channel}`, 'error') } finally { setTestingChannel(null) } }, [addToast]) const handleRefreshModels = useCallback(() => { setRefreshSpinning(true) fetchModels(llmProvider) setTimeout(() => setRefreshSpinning(false), 800) }, [fetchModels, llmProvider]) const handleRefreshStats = useCallback(() => { setStatsRefreshing(true) fetchDbStats() setTimeout(() => setStatsRefreshing(false), 800) }, [fetchDbStats]) const handleProviderSelect = useCallback((providerId: string) => { setLlmProvider(providerId) }, []) const handleModelChange = useCallback((e: React.ChangeEvent) => { setLlmModel(e.target.value) }, []) const featureToggleSetters = useMemo(() => ({ modelRouting: () => setEnableModelRouting(prev => !prev), knowledgeAugmentation: () => setEnableKnowledgeAugmentation(prev => !prev), browserValidation: () => setEnableBrowserValidation(prev => !prev), }), []) const featureToggleValues = useMemo(() => ({ modelRouting: enableModelRouting, knowledgeAugmentation: enableKnowledgeAugmentation, browserValidation: enableBrowserValidation, }), [enableModelRouting, enableKnowledgeAugmentation, enableBrowserValidation]) /* ---------- Sub-components ---------- */ const ToggleSwitch = useCallback(({ enabled, onToggle }: { enabled: boolean; onToggle: () => void }) => ( ), []) /* ---------- Render ---------- */ return (
{/* Inline keyframes */} {/* Toast Notifications */} {/* Page Header */}

Settings

{activeProviderLabel} provider {hasApiKeyForProvider && ( Key configured )} {enabledFeaturesCount > 0 && ( {enabledFeaturesCount} feature{enabledFeaturesCount !== 1 ? 's' : ''} active )}

{/* LLM Configuration */}
{/* Provider selection */}
{PROVIDERS.map((p) => ( ))}
{/* Model Picker */}
{llmModel && (

Selected: {llmModel}

)} {['ollama', 'lmstudio'].includes(llmProvider) && availableModels.length === 0 && !loadingModels && (

No models found. Make sure {llmProvider === 'ollama' ? 'Ollama' : 'LM Studio'} is running.

)}
{/* API Key inputs */} {llmProvider === 'claude' && (
setApiKey(e.target.value)} helperText={settings?.has_anthropic_key ? 'API key is configured. Enter a new key to update.' : 'Required for Claude-powered analysis'} />
)} {llmProvider === 'openai' && (
setOpenaiKey(e.target.value)} helperText={settings?.has_openai_key ? 'API key is configured. Enter a new key to update.' : 'Required for OpenAI-powered analysis'} />
)} {llmProvider === 'gemini' && (
setGeminiKey(e.target.value)} helperText={settings?.has_gemini_key ? 'API key is configured.' : 'Required for Gemini models'} />
)} {llmProvider === 'openrouter' && (
setOpenrouterKey(e.target.value)} helperText={settings?.has_openrouter_key ? 'API key is configured.' : 'Required for OpenRouter model access'} />
)} {llmProvider === 'together' && (
setTogetherKey(e.target.value)} helperText={settings?.has_together_key ? 'API key is configured.' : 'Required for Together AI models (Llama, Qwen, Mixtral, etc.)'} />
)} {llmProvider === 'fireworks' && (
setFireworksKey(e.target.value)} helperText={settings?.has_fireworks_key ? 'API key is configured.' : 'Required for Fireworks AI models'} />
)} {llmProvider === 'ollama' && (
setOllamaUrl(e.target.value)} helperText="URL of your local Ollama instance. Supports Llama, Qwen, DeepSeek, Mistral, etc." />
)} {llmProvider === 'lmstudio' && (
setLmstudioUrl(e.target.value)} helperText="URL of your local LM Studio server." />
)} setMaxOutputTokens(e.target.value)} helperText="Override max output tokens (up to 64000 for Claude). Leave empty for profile defaults." />
{/* Advanced Features */}
{FEATURE_TOGGLES.map((feature, idx) => { const Icon = feature.icon return (

{feature.title}

{feature.description}

) })}
{/* Notifications */}
{/* Master toggle */}

Enable Notifications

Send alerts when scans start, find vulnerabilities, or complete

setEnableNotifications(!enableNotifications)} />
{enableNotifications && (
{/* Severity filter */}
setNotificationSeverityFilter(e.target.value)} placeholder="critical,high" className="w-full bg-dark-900 border border-dark-700 rounded-lg px-4 py-2.5 text-white text-sm focus:border-primary-500/50 focus:ring-1 focus:ring-primary-500/20 transition-colors" />

Only notify for vulnerabilities matching these severities

{/* Discord */}
Discord {settings?.has_discord_webhook && ( Configured )}
setDiscordWebhookUrl(e.target.value)} helperText={settings?.has_discord_webhook ? 'Webhook configured. Enter new URL to update.' : 'Create a webhook in your Discord channel settings'} />
{/* Telegram */}
Telegram {settings?.has_telegram_bot && ( Configured )}
setTelegramBotToken(e.target.value)} helperText="Get from @BotFather on Telegram" /> setTelegramChatId(e.target.value)} helperText="Channel or group chat ID (use @userinfobot to find)" />
{/* WhatsApp (Twilio) */}
WhatsApp (Twilio) {settings?.has_twilio_credentials && ( Configured )}
setTwilioAccountSid(e.target.value)} /> setTwilioAuthToken(e.target.value)} /> setTwilioFromNumber(e.target.value)} /> setTwilioToNumber(e.target.value)} />

Requires Twilio account with WhatsApp sandbox enabled

)}
{/* Scan Settings */}
setMaxConcurrentScans(e.target.value)} helperText="Maximum number of scans that can run simultaneously" />

Enable Aggressive Mode

Use more payloads and bypass techniques (may be slower)

setAggressiveMode(!aggressiveMode)} />
{/* Database Management */}
{/* Stats */} {dbStats && (
{([ { label: 'Scans', value: dbStats.scans, color: 'text-blue-400', bg: 'bg-blue-500/10' }, { label: 'Vulnerabilities', value: dbStats.vulnerabilities, color: 'text-red-400', bg: 'bg-red-500/10' }, { label: 'Endpoints', value: dbStats.endpoints, color: 'text-green-400', bg: 'bg-green-500/10' }, { label: 'Reports', value: dbStats.reports, color: 'text-purple-400', bg: 'bg-purple-500/10' }, ] as const).map((stat, idx) => (

{formatNumber(stat.value)}

{stat.label}

))}
)} {totalDbRecords > 0 && (

{totalDbRecords.toLocaleString()} total records stored

)} {/* Clear Database */} {!showClearConfirm ? (

Clear All Data

Remove all scans, vulnerabilities, and reports

) : (

Are you sure?

This will permanently delete all scans, vulnerabilities, endpoints, and reports. This action cannot be undone.

)} {/* Refresh Stats */}
{/* About */}

NeuroSploit v3.0

AI-Powered Penetration Testing Platform

{[ 'Dynamic vulnerability testing driven by AI prompts', '100+ vulnerability types across 10 categories', 'Multi-provider LLM support (8 providers)', 'Custom knowledge learning from security research', 'Adaptive learning from TP/FP feedback', 'MCP server integration for extended tooling', 'Playwright browser validation with screenshots', 'OHVR-structured PoC reporting', ].map((feature, idx) => (
{feature}
))}
{/* Sticky Save Button (bottom) */}
) }