diff --git a/lightrag_webui/src/App.tsx b/lightrag_webui/src/App.tsx
index 4596f684..b3e1acca 100644
--- a/lightrag_webui/src/App.tsx
+++ b/lightrag_webui/src/App.tsx
@@ -1,13 +1,13 @@
-import { useState, useCallback } from 'react'
+import { useState, useCallback, useEffect, useRef } from 'react'
import ThemeProvider from '@/components/ThemeProvider'
import TabVisibilityProvider from '@/contexts/TabVisibilityProvider'
import MessageAlert from '@/components/MessageAlert'
import ApiKeyAlert from '@/components/ApiKeyAlert'
import StatusIndicator from '@/components/graph/StatusIndicator'
import { healthCheckInterval } from '@/lib/constants'
-import { useBackendState } from '@/stores/state'
+import { useBackendState, useAuthStore } from '@/stores/state'
import { useSettingsStore } from '@/stores/settings'
-import { useEffect } from 'react'
+import { getAuthStatus } from '@/api/lightrag'
import SiteHeader from '@/features/SiteHeader'
import { InvalidApiKeyError, RequireApiKeError } from '@/api/lightrag'
@@ -23,17 +23,63 @@ function App() {
const enableHealthCheck = useSettingsStore.use.enableHealthCheck()
const currentTab = useSettingsStore.use.currentTab()
const [apiKeyInvalid, setApiKeyInvalid] = useState(false)
+ const versionCheckRef = useRef(false); // Prevent duplicate calls in Vite dev mode
- // Health check
+ // Health check - can be disabled
useEffect(() => {
- // Check immediately
- useBackendState.getState().check()
+ // Only execute if health check is enabled
+ if (!enableHealthCheck) return;
+
+ // Health check function
+ const performHealthCheck = async () => {
+ await useBackendState.getState().check();
+ };
+
+ // Execute immediately
+ performHealthCheck();
+
+ // Set interval for periodic execution
+ const interval = setInterval(performHealthCheck, healthCheckInterval * 1000);
+ return () => clearInterval(interval);
+ }, [enableHealthCheck]);
- const interval = setInterval(async () => {
- await useBackendState.getState().check()
- }, healthCheckInterval * 1000)
- return () => clearInterval(interval)
- }, [enableHealthCheck])
+ // Version check - independent and executed only once
+ useEffect(() => {
+ const checkVersion = async () => {
+ // Prevent duplicate calls in Vite dev mode
+ if (versionCheckRef.current) return;
+ versionCheckRef.current = true;
+
+ // Check if version info was already obtained in login page
+ const versionCheckedFromLogin = sessionStorage.getItem('VERSION_CHECKED_FROM_LOGIN') === 'true';
+ if (versionCheckedFromLogin) return;
+
+ // Get version info
+ const token = localStorage.getItem('LIGHTRAG-API-TOKEN');
+ if (!token) return;
+
+ try {
+ const status = await getAuthStatus();
+ if (status.core_version || status.api_version) {
+ // Update version info while maintaining login state
+ useAuthStore.getState().login(
+ token,
+ useAuthStore.getState().isGuestMode,
+ status.core_version,
+ status.api_version
+ );
+
+ // Set flag to indicate version info has been checked
+ sessionStorage.setItem('VERSION_CHECKED_FROM_LOGIN', 'true');
+ }
+ } catch (error) {
+ console.error('Failed to get version info:', error);
+ }
+ };
+
+ // Execute version check
+ checkVersion();
+ }, []); // Empty dependency array ensures it only runs once on mount
const handleTabChange = useCallback(
(tab: string) => useSettingsStore.getState().setCurrentTab(tab as any),
diff --git a/lightrag_webui/src/AppRouter.tsx b/lightrag_webui/src/AppRouter.tsx
index f6d1d08b..1d957174 100644
--- a/lightrag_webui/src/AppRouter.tsx
+++ b/lightrag_webui/src/AppRouter.tsx
@@ -2,98 +2,11 @@ import { HashRouter as Router, Routes, Route, useNavigate } from 'react-router-d
import { useEffect, useState } from 'react'
import { useAuthStore } from '@/stores/state'
import { navigationService } from '@/services/navigation'
-import { getAuthStatus } from '@/api/lightrag'
-import { toast } from 'sonner'
import { Toaster } from 'sonner'
import App from './App'
import LoginPage from '@/features/LoginPage'
import ThemeProvider from '@/components/ThemeProvider'
-interface ProtectedRouteProps {
- children: React.ReactNode
-}
-
-const ProtectedRoute = ({ children }: ProtectedRouteProps) => {
- const { isAuthenticated } = useAuthStore()
- const [isChecking, setIsChecking] = useState(true)
- const navigate = useNavigate()
-
- // Set navigate function for navigation service
- useEffect(() => {
- navigationService.setNavigate(navigate)
- }, [navigate])
-
- useEffect(() => {
- let isMounted = true; // Flag to prevent state updates after unmount
-
- // This effect will run when the component mounts
- // and will check if authentication is required
- const checkAuthStatus = async () => {
- try {
- // Skip check if already authenticated
- if (isAuthenticated) {
- if (isMounted) setIsChecking(false);
- return;
- }
-
- const status = await getAuthStatus()
-
- // Only proceed if component is still mounted
- if (!isMounted) return;
-
- if (!status.auth_configured && status.access_token) {
- // If auth is not configured, use the guest token
- useAuthStore.getState().login(status.access_token, true, status.core_version, status.api_version)
- if (status.message) {
- toast.info(status.message)
- }
- }
- } catch (error) {
- console.error('Failed to check auth status:', error)
- } finally {
- // Only update state if component is still mounted
- if (isMounted) {
- setIsChecking(false)
- }
- }
- }
-
- // Execute immediately
- checkAuthStatus()
-
- // Cleanup function to prevent state updates after unmount
- return () => {
- isMounted = false;
- }
- }, [isAuthenticated])
-
- // Handle navigation when authentication status changes
- useEffect(() => {
- if (!isChecking && !isAuthenticated) {
- const currentPath = window.location.hash.slice(1); // Remove the '#' from hash
- const isLoginPage = currentPath === '/login';
-
- if (!isLoginPage) {
- // Use navigation service for redirection
- console.log('Not authenticated, redirecting to login');
- navigationService.navigateToLogin();
- }
- }
- }, [isChecking, isAuthenticated]);
-
- // Show nothing while checking auth status or when not authenticated on login page
- if (isChecking || (!isAuthenticated && window.location.hash.slice(1) === '/login')) {
- return null;
- }
-
- // Show children only when authenticated
- if (!isAuthenticated) {
- return null;
- }
-
- return <>{children}>;
-}
-
const AppContent = () => {
const [initializing, setInitializing] = useState(true)
const { isAuthenticated } = useAuthStore()
@@ -104,34 +17,20 @@ const AppContent = () => {
navigationService.setNavigate(navigate)
}, [navigate])
- // Check token validity and auth configuration on app initialization
+ // Token validity check
useEffect(() => {
- let isMounted = true; // Flag to prevent state updates after unmount
+ let isMounted = true;
const checkAuth = async () => {
try {
const token = localStorage.getItem('LIGHTRAG-API-TOKEN')
- // If we have a token, we're already authenticated
if (token && isAuthenticated) {
if (isMounted) setInitializing(false);
return;
}
- // If no token or not authenticated, check if auth is configured
- const status = await getAuthStatus()
-
- // Only proceed if component is still mounted
- if (!isMounted) return;
-
- if (!status.auth_configured && status.access_token) {
- // If auth is not configured, use the guest token
- useAuthStore.getState().login(status.access_token, true, status.core_version, status.api_version)
- if (status.message) {
- toast.info(status.message)
- }
- } else if (!token) {
- // Only logout if we don't have a token
+ if (!token) {
useAuthStore.getState().logout()
}
} catch (error) {
@@ -140,22 +39,30 @@ const AppContent = () => {
useAuthStore.getState().logout()
}
} finally {
- // Only update state if component is still mounted
if (isMounted) {
setInitializing(false)
}
}
}
- // Execute immediately
checkAuth()
- // Cleanup function to prevent state updates after unmount
return () => {
isMounted = false;
}
}, [isAuthenticated])
+ // Redirect effect for protected routes
+ useEffect(() => {
+ if (!initializing && !isAuthenticated) {
+ const currentPath = window.location.hash.slice(1);
+ if (currentPath !== '/login') {
+ console.log('Not authenticated, redirecting to login');
+ navigate('/login');
+ }
+ }
+ }, [initializing, isAuthenticated, navigate]);
+
// Show nothing while initializing
if (initializing) {
return null
@@ -166,11 +73,7 @@ const AppContent = () => {
} />
-
-
- }
+ element={isAuthenticated ? : null}
/>
)
diff --git a/lightrag_webui/src/features/LoginPage.tsx b/lightrag_webui/src/features/LoginPage.tsx
index f8ca35f1..5b06c428 100644
--- a/lightrag_webui/src/features/LoginPage.tsx
+++ b/lightrag_webui/src/features/LoginPage.tsx
@@ -1,4 +1,4 @@
-import { useState, useEffect } from 'react'
+import { useState, useEffect, useRef } from 'react'
import { useNavigate } from 'react-router-dom'
import { useAuthStore } from '@/stores/state'
import { loginToServer, getAuthStatus } from '@/api/lightrag'
@@ -18,6 +18,7 @@ const LoginPage = () => {
const [username, setUsername] = useState('')
const [password, setPassword] = useState('')
const [checkingAuth, setCheckingAuth] = useState(true)
+ const authCheckRef = useRef(false); // Prevent duplicate calls in Vite dev mode
useEffect(() => {
console.log('LoginPage mounted')
@@ -28,6 +29,13 @@ const LoginPage = () => {
let isMounted = true; // Flag to prevent state updates after unmount
const checkAuthConfig = async () => {
+ // Prevent duplicate calls in Vite dev mode
+ if (authCheckRef.current) {
+ if (isMounted) setCheckingAuth(false);
+ return;
+ }
+ authCheckRef.current = true;
+
try {
// If already authenticated, redirect to home
if (isAuthenticated) {
@@ -37,6 +45,17 @@ const LoginPage = () => {
// Check auth status
const status = await getAuthStatus()
+
+ // Set checkingAuth to false immediately after getAuthStatus
+ // This allows the login page to render while other processing continues
+ if (isMounted) {
+ setCheckingAuth(false);
+ }
+
+ // Set session flag for version check to avoid duplicate checks in App component
+ if (isMounted && (status.core_version || status.api_version)) {
+ sessionStorage.setItem('VERSION_CHECKED_FROM_LOGIN', 'true');
+ }
// Only proceed if component is still mounted
if (!isMounted) return;
@@ -48,16 +67,16 @@ const LoginPage = () => {
toast.info(status.message)
}
navigate('/')
- return // Exit early, no need to set checkingAuth to false
+ return
}
} catch (error) {
console.error('Failed to check auth configuration:', error)
- } finally {
- // Only update state if component is still mounted
+ // Also set checkingAuth to false in case of error
if (isMounted) {
- setCheckingAuth(false)
+ setCheckingAuth(false);
}
}
+ // Removed finally block as we're setting checkingAuth earlier
}
// Execute immediately
@@ -88,6 +107,11 @@ const LoginPage = () => {
// Check authentication mode
const isGuestMode = response.auth_mode === 'disabled'
login(response.access_token, isGuestMode, response.core_version, response.api_version)
+
+ // Set session flag for version check
+ if (response.core_version || response.api_version) {
+ sessionStorage.setItem('VERSION_CHECKED_FROM_LOGIN', 'true');
+ }
if (isGuestMode) {
// Show authentication disabled notification