Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 19 additions & 15 deletions frontend/src/components/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ import { ResultsSkeleton } from './ResultsSkeleton';
import { ToolPanels } from './tools/ToolPanels';
import { ScanComparison } from './views/ScanComparison';
import { WatchlistView } from './views/WatchlistView';
import { MODULE_MAP } from './Sidebar';
import { startScan, getWsUrl, getScan, getUsage } from '@/lib/api';
import { detectScanType, normalizeScanTarget } from '@/lib/scan-target';
import { MODULE_MAP } from './Sidebar';
import type { ScanType, ScanStatus, ToolMode, ScanResults as ScanResultsType, ScanMeta, LiveModuleStatus, UsageData } from '@/lib/types';

type View = 'idle' | 'tool' | 'scanning' | 'results' | 'compare' | 'watchlist';
Expand Down Expand Up @@ -47,7 +47,9 @@ export function App() {
const [totalModules, setTotalModules] = useState(0);
const [compareIds, setCompareIds] = useState<[string, string] | null>(null);
const [usage, setUsage] = useState<UsageData | null>(null);
const [isStarting, setIsStarting] = useState(false);
const wsRef = useRef<WebSocket | null>(null);
const urlScanStarted = useRef(false);
const usageLimitedRef = useRef(true);

const handleHome = useCallback(() => {
Expand Down Expand Up @@ -228,29 +230,23 @@ export function App() {
setProgressLog([]);
setModuleStatuses({});
setTotalModules(modules.length);
setScanStatus('running');
setView('scanning');
setScanMeta(null);
setIsStarting(true);
try {
const { scan_id } = await startScan(target, type, modules);
setIsStarting(false);
setScanStatus('running');
setView('scanning');
setScanId(scan_id);
connectWs(scan_id);
handleUsageRefresh();
} catch (e: unknown) {
setIsStarting(false);
setScanStatus('failed');
setProgressLog([`Failed to start scan: ${e instanceof Error ? e.message : 'Unknown error'}`]);
}
}, [connectWs, handleUsageRefresh]);

useEffect(() => {
handleUsageRefresh();
}, [handleUsageRefresh]);

useEffect(() => {
return () => { wsRef.current?.close(); };
}, []);

const urlScanStarted = useRef(false);
useEffect(() => {
if (urlScanStarted.current) return;
try {
Expand All @@ -265,14 +261,22 @@ export function App() {
} catch {}
}, [handleScan]);

useEffect(() => {
handleUsageRefresh();
}, [handleUsageRefresh]);

useEffect(() => {
return () => { wsRef.current?.close(); };
}, []);

return (
<div className="flex flex-col min-h-screen">
<Topbar status={scanStatus} usage={usage} onHome={handleHome} onWatchlist={handleWatchlist} onMenuToggle={() => setSidebarOpen(v => !v)} />
<div className="flex flex-1 relative">
{sidebarOpen && <div onClick={() => setSidebarOpen(false)} className="fixed inset-0 bg-black/50 z-40 md:hidden" />}
<Sidebar onScan={handleScan} onLoadScan={handleLoadScan} onCompare={handleCompare} isRunning={scanStatus === 'running'} isOpen={sidebarOpen} onClose={() => setSidebarOpen(false)} />
<Sidebar onScan={handleScan} onLoadScan={handleLoadScan} onCompare={handleCompare} isRunning={scanStatus === 'running'} isStarting={isStarting} isOpen={sidebarOpen} onClose={() => setSidebarOpen(false)} />
<main className="flex-1 min-w-0 relative z-10">
{view === 'idle' && <IdleView onTool={handleTool} onScan={handleScan} />}
{view === 'idle' && <IdleView onTool={handleTool} onScan={handleScan} isStarting={isStarting} />}
{view === 'tool' && toolMode && (
<ToolPanels mode={toolMode} onBack={() => setView('idle')} />
)}
Expand All @@ -293,4 +297,4 @@ export function App() {
</div>
</div>
);
}
}
11 changes: 6 additions & 5 deletions frontend/src/components/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ interface Props {
onLoadScan?: (scanId: string) => void;
onCompare?: (a: string, b: string) => void;
isRunning: boolean;
isStarting?: boolean;
isOpen: boolean;
onClose: () => void;
}
Expand All @@ -55,7 +56,7 @@ function useRecentScans() {
return { recents, add, clear };
}

export function Sidebar({ onScan, onLoadScan, onCompare, isRunning, isOpen, onClose }: Props) {
export function Sidebar({ onScan, onLoadScan, onCompare, isRunning, isStarting = false, isOpen, onClose }: Props) {
const { t } = useTranslations();
const [target, setTarget] = useState('');
const [scanType, setScanType] = useState<ScanType>('domain');
Expand Down Expand Up @@ -211,11 +212,11 @@ export function Sidebar({ onScan, onLoadScan, onCompare, isRunning, isOpen, onCl

<button
type="submit"
disabled={!target.trim() || isRunning || modules.length === 0}
disabled={!target.trim() || isRunning || isStarting || modules.length === 0}
className="btn-primary mt-1"
>
{isRunning ? <Loader2 size={13} className="spin" /> : <Play size={13} />}
{isRunning ? t('sidebar.scanning') : t('sidebar.runScan')}
{(isRunning || isStarting) ? <Loader2 size={13} className="spin" /> : <Play size={13} />}
{isRunning ? t('sidebar.scanning') : isStarting ? t('sidebar.scanning') : t('sidebar.runScan')}
</button>
</form>

Expand Down Expand Up @@ -367,4 +368,4 @@ export function Sidebar({ onScan, onLoadScan, onCompare, isRunning, isOpen, onCl
</div>
</aside>
);
}
}
11 changes: 6 additions & 5 deletions frontend/src/components/views/IdleView.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client';
import { useEffect, useState } from 'react';
import { FileText, Mail, Bitcoin, QrCode, ChevronRight, Globe, User, Phone, Shield, Database, Zap, Eye, Activity, Network, Hash, Binary, KeyRound, Search } from 'lucide-react';
import { FileText, Mail, Bitcoin, QrCode, ChevronRight, Globe, User, Phone, Shield, Database, Zap, Eye, Activity, Network, Hash, Binary, KeyRound, Search, Loader2 } from 'lucide-react';
import { useTranslations } from '@/lib/i18n';
import { getPrismDemoMode } from '@/lib/prism-config';
import { detectScanType, normalizeScanTarget } from '@/lib/scan-target';
Expand Down Expand Up @@ -29,9 +29,10 @@ type CapsKey = typeof CAPS_KEYS[number];
interface Props {
onTool: (mode: ToolMode) => void;
onScan: (target: string, type: ScanType, modules: string[]) => void;
isStarting?: boolean;
}

export function IdleView({ onTool, onScan }: Props) {
export function IdleView({ onTool, onScan, isStarting = false }: Props) {
const { t } = useTranslations();
const demoMode = getPrismDemoMode();
const [targetIdx, setTargetIdx] = useState(0);
Expand Down Expand Up @@ -110,11 +111,11 @@ export function IdleView({ onTool, onScan }: Props) {
</div>
<button
type="submit"
disabled={!query.trim()}
disabled={!query.trim() || isStarting}
className="btn-primary h-[42px] px-4 shrink-0 disabled:opacity-50 disabled:cursor-not-allowed"
aria-label={t('sidebar.runScan')}
>
<Search size={15} />
{isStarting ? <Loader2 size={15} className="spin" /> : <Search size={15} />}
</button>
</form>

Expand Down Expand Up @@ -194,4 +195,4 @@ export function IdleView({ onTool, onScan }: Props) {
)}
</div>
);
}
}
Loading