import { useEffect, useState } from 'react' import { Toaster } from 'sonner' import { Loader2 } from 'lucide-react' import { Sidebar } from '@/components/layout/Sidebar' import { MainContent } from '@/components/layout/MainContent' import { CrawlerConfigPanel } from '@/components/config/CrawlerConfigPanel' import { MonitorDashboard } from '@/components/monitor/MonitorDashboard' import { ReportView } from '@/components/monitor/ReportView' import { SettingsView } from '@/components/settings/SettingsView' import { Login } from '@/components/auth/Login' import { EnvironmentCheck, isEnvChecked } from '@/components/env/EnvironmentCheck' import { authApi, setUnauthorizedHandler } from '@/lib/api' export type AppView = 'crawler' | 'monitor' | 'report' | 'settings' function App() { // null = still probing. Rendering the app while unknown would briefly mount // the log WebSocket before we know whether the user is authenticated. const [authed, setAuthed] = useState(null) // Initialize by checking localStorage if env check has passed const [envChecked, setEnvChecked] = useState(() => isEnvChecked()) // Which top-level workspace is visible. Only one is mounted at a time: the // crawler view owns a live log WebSocket and a 2s status poll, which should // not keep running while the user is looking at the monitor dashboard. const [view, setView] = useState('crawler') useEffect(() => { let cancelled = false authApi .me() .then(() => !cancelled && setAuthed(true)) .catch(() => !cancelled && setAuthed(false)) return () => { cancelled = true } }, []) // A 401 from any request means the session expired or was revoked elsewhere // (e.g. the password was changed on another device), so drop back to login. useEffect(() => { setUnauthorizedHandler(() => setAuthed(false)) return () => setUnauthorizedHandler(null) }, []) const handleEnvCheckComplete = () => { setEnvChecked(true) } const handleLogout = async () => { try { await authApi.logout() } finally { // Even if the call fails, the local session is over; unmounting the tree // closes the log WebSocket via the existing connection-count cleanup. setAuthed(false) } } if (authed === null) { return (
) } if (!authed) { return setAuthed(true)} /> } return (
{/* Environment Check Modal - shown until the check passes or is skipped. A configuration concern, so it lives inside the authenticated tree. */} {!envChecked && } {/* Header Bar */} {/* Main Area */}
{view === 'crawler' && ( <> {/* Config Panel - Primary Action Area (Always Expanded) */}
{/* Console - Collapsible Terminal */} )} {view === 'monitor' && } {view === 'report' && } {view === 'settings' && }
{/* Toast notifications - Theme-aware style */}
) } export default App