import { useState, useCallback } from 'react'; import { useConfig, useSources, useSinks, useSanitizers, useTerminators, useProfiles, } from '../api/queries/config'; import { useAddSource, useDeleteSource, useAddSink, useDeleteSink, useAddSanitizer, useDeleteSanitizer, useAddTerminator, useDeleteTerminator, useAddProfile, useDeleteProfile, useActivateProfile, useToggleTriageSync, } from '../api/mutations/config'; import { LoadingState } from '../components/ui/LoadingState'; import { ErrorState } from '../components/ui/ErrorState'; import type { LabelEntryView, TerminatorView, ProfileView } from '../api/types'; const LANG_OPTIONS = [ 'javascript', 'typescript', 'python', 'go', 'java', 'c', 'cpp', 'php', 'ruby', 'rust', ]; const CAP_OPTIONS = [ 'all', 'env_var', 'html_escape', 'shell_escape', 'url_encode', 'json_parse', 'file_io', 'sql_query', 'deserialize', 'ssrf', 'code_exec', 'crypto', ]; // ── Collapsible Config Section ─────────────────────────────────────────────── function ConfigSection({ title, id, children, }: { title: string; id: string; children: React.ReactNode; }) { const [collapsed, setCollapsed] = useState(false); return (
setCollapsed(!collapsed)} > {' '} {title}
{children}
); } // ── Label Table (Source/Sink/Sanitizer) ────────────────────────────────────── function LabelSection({ title, id, kind, entries, onAdd, onDelete, }: { title: string; id: string; kind: string; entries: LabelEntryView[]; onAdd: (body: { lang: string; matchers: string[]; cap: string }) => void; onDelete: (entry: LabelEntryView) => void; }) { const [lang, setLang] = useState(''); const [matcher, setMatcher] = useState(''); const [cap, setCap] = useState('all'); const builtins = entries.filter((e) => e.is_builtin); const custom = entries.filter((e) => !e.is_builtin); const handleAdd = useCallback(() => { if (!lang || !matcher) return; onAdd({ lang, matchers: [matcher], cap }); setMatcher(''); }, [lang, matcher, cap, onAdd]); return (
setMatcher(e.target.value)} />
{entries.length === 0 ? (

No {kind} rules

) : ( {builtins.map((e, i) => ( ))} {custom.map((e, i) => ( ))}
Language Matchers Cap
{e.lang} {e.matchers.join(', ')} {e.cap} built-in
{e.lang} {e.matchers.join(', ')} {e.cap}
)}
); } // ── Config Page ────────────────────────────────────────────────────────────── export function ConfigPage() { const { data: config, isLoading: configLoading, error: configError, } = useConfig(); const { data: sources } = useSources(); const { data: sinks } = useSinks(); const { data: sanitizers } = useSanitizers(); const { data: terminators } = useTerminators(); const { data: profiles } = useProfiles(); const addSource = useAddSource(); const deleteSource = useDeleteSource(); const addSink = useAddSink(); const deleteSink = useDeleteSink(); const addSanitizer = useAddSanitizer(); const deleteSanitizer = useDeleteSanitizer(); const addTerminator = useAddTerminator(); const deleteTerminator = useDeleteTerminator(); const addProfile = useAddProfile(); const deleteProfile = useDeleteProfile(); const activateProfile = useActivateProfile(); const toggleTriageSync = useToggleTriageSync(); const [termLang, setTermLang] = useState(''); const [termName, setTermName] = useState(''); const [profileName, setProfileName] = useState(''); const handleAddTerminator = useCallback(() => { if (!termLang || !termName) return; addTerminator.mutate({ lang: termLang, name: termName }); setTermName(''); }, [termLang, termName, addTerminator]); const handleSaveProfile = useCallback(() => { if (!profileName) return; addProfile.mutate({ name: profileName, settings: {} }); setProfileName(''); }, [profileName, addProfile]); if (configLoading) return ; if (configError) return ; // Extract config fields (config is typed as unknown since it's the raw NyxConfig) const cfg = config as Record> | undefined; const scanner = cfg?.scanner as Record | undefined; const output = cfg?.output as Record | undefined; const server = cfg?.server as Record | undefined; return ( <>

Config

{/* General Section */}
Analysis Mode: {String(scanner?.mode || 'full')}
Min Severity:{' '} {String(scanner?.min_severity || 'Low')}
Max File Size:{' '} {scanner?.max_file_size_mb ? String(scanner.max_file_size_mb) + ' MB' : 'unlimited'}
Excluded Dirs:{' '} {((scanner?.excluded_directories as string[]) || []).join(', ')}
Excluded Exts:{' '} {((scanner?.excluded_extensions as string[]) || []).join(', ')}
Attack Surface Ranking:{' '} {output?.attack_surface_ranking ? 'Enabled' : 'Disabled'}
toggleTriageSync.mutate({ enabled: e.target.checked }) } />
{/* Sources */} addSource.mutate(body)} onDelete={(e) => deleteSource.mutate({ lang: e.lang, matchers: e.matchers, cap: e.cap, }) } /> {/* Sinks */} addSink.mutate(body)} onDelete={(e) => deleteSink.mutate({ lang: e.lang, matchers: e.matchers, cap: e.cap }) } /> {/* Sanitizers */} addSanitizer.mutate(body)} onDelete={(e) => deleteSanitizer.mutate({ lang: e.lang, matchers: e.matchers, cap: e.cap, }) } /> {/* Terminators */}
setTermName(e.target.value)} />
{!terminators || terminators.length === 0 ? (

No terminators configured

) : ( {(terminators as TerminatorView[]).map((t, i) => ( ))}
Language Name
{t.lang} {t.name}
)}
{/* Profiles */}
{!profiles || profiles.length === 0 ? (

No profiles configured

) : ( {(profiles as ProfileView[]).map((p) => ( ))}
Name Type Settings
{p.name} {p.is_builtin ? ( built-in ) : ( custom )} {JSON.stringify(p.settings)} {!p.is_builtin && ( )}
)}
setProfileName(e.target.value)} />
); }