diff --git a/src/App.jsx b/src/App.jsx index 591ff2f..2a84457 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,6 +1,7 @@ import React from 'react' import { - BrowserRouter as Router, + HashRouter, + BrowserRouter, Routes, Route, Navigate @@ -70,8 +71,19 @@ import Hosts from './components/Dashboard/Management/Hosts.jsx' import { ElectronProvider } from './components/Dashboard/context/ElectronContext.js' import AuthCallback from './components/App/AuthCallback.jsx' +const getRouter = () => { + if ( + typeof window !== 'undefined' && + window.location.href.includes('index.html') + ) { + return HashRouter + } + return BrowserRouter +} + const AppContent = () => { const { themeConfig } = useThemeContext() + const Router = getRouter() return ( diff --git a/src/components/Dashboard/common/EditObjectForm.jsx b/src/components/Dashboard/common/EditObjectForm.jsx index cbebdbb..1d162f1 100644 --- a/src/components/Dashboard/common/EditObjectForm.jsx +++ b/src/components/Dashboard/common/EditObjectForm.jsx @@ -44,7 +44,7 @@ const EditObjectForm = ({ id, type, style, children }) => { subscribeToObject, subscribeToLock } = useContext(ApiServerContext) - const { authenticated } = useContext(AuthContext) + const { token } = useContext(AuthContext) // Validate form on change useEffect(() => { form @@ -92,11 +92,11 @@ const EditObjectForm = ({ id, type, style, children }) => { }, []) useEffect(() => { - if (!initialized && id && authenticated == true) { + if (!initialized && id && token != null) { setInitialized(true) handleFetchObject() } - }, [id, initialized, handleFetchObject, authenticated]) + }, [id, initialized, handleFetchObject, token]) useEffect(() => { if (id && connected) { diff --git a/src/components/Dashboard/common/NotesPanel.jsx b/src/components/Dashboard/common/NotesPanel.jsx index 0542123..53ebfd4 100644 --- a/src/components/Dashboard/common/NotesPanel.jsx +++ b/src/components/Dashboard/common/NotesPanel.jsx @@ -264,6 +264,7 @@ const NotesPanel = ({ _id, onNewNote, type }) => { const [newNoteOpen, setNewNoteOpen] = useState(false) const [showMarkdown, setShowMarkdown] = useState(false) const [loading, setLoading] = useState(true) + const [initialized, setInitialized] = useState(false) const [messageApi, contextHolder] = message.useMessage() const [newNoteFormLoading, setNewNoteFormLoading] = useState(false) const [newNoteFormValues, setNewNoteFormValues] = useState({}) @@ -290,19 +291,17 @@ const NotesPanel = ({ _id, onNewNote, type }) => { .catch(() => setDoneEnabled(false)) }, [newNoteForm, newNoteFormUpdateValues]) - const { authenticated, userProfile } = useContext(AuthContext) + const { token, userProfile } = useContext(AuthContext) const { fetchNotes } = useContext(ApiServerContext) const fetchData = useCallback( async (id) => { try { const newData = await fetchNotes(id) - setLoading(false) return newData } catch (error) { - setNotes([]) setError(error) - setLoading(false) + return null } }, [fetchNotes] @@ -322,24 +321,27 @@ const NotesPanel = ({ _id, onNewNote, type }) => { const generateNotes = useCallback( async (id) => { const notesData = await fetchData(id) + setLoading(false) + + if (notesData == null) { + return null + } if (notesData.length <= 0) { return ( - } spinning={loading}> - - - - - - No notes added. - - - + + + + + + No notes added. + + ) } @@ -458,10 +460,11 @@ const NotesPanel = ({ _id, onNewNote, type }) => { }, [_id, generateNotes]) useEffect(() => { - if (authenticated) { + if (token != null && !initialized) { handleReloadData() + setInitialized(true) } - }, [authenticated, handleReloadData]) + }, [token, handleReloadData, initialized]) const handleModalOk = async () => { try { diff --git a/src/components/Dashboard/common/ObjectTable.jsx b/src/components/Dashboard/common/ObjectTable.jsx index ba566d8..0ef33a9 100644 --- a/src/components/Dashboard/common/ObjectTable.jsx +++ b/src/components/Dashboard/common/ObjectTable.jsx @@ -56,7 +56,7 @@ const ObjectTable = forwardRef( }, ref ) => { - const { authenticated } = useContext(AuthContext) + const { token } = useContext(AuthContext) const { fetchObjects, connected, subscribeToObject, subscribeToType } = useContext(ApiServerContext) const isMobile = useMediaQuery({ maxWidth: 768 }) @@ -163,7 +163,7 @@ const ObjectTable = forwardRef( setLoading(false) setLazyLoading(false) - return result.data + return result.data || [] } catch (error) { setPages((prev) => prev.map((page) => ({ @@ -302,18 +302,20 @@ const ObjectTable = forwardRef( // Subscribe to each item in all pages pages.forEach((page) => { - page.items.forEach((item) => { - if (!item.isSkeleton) { - const unsubscribe = subscribeToObject( - item._id, - type, - updateEventHandler - ) - if (unsubscribe) { - unsubscribes.push(unsubscribe) + if (page?.items && page?.items?.length > 0) { + page.items.forEach((item) => { + if (!item.isSkeleton) { + const unsubscribe = subscribeToObject( + item._id, + type, + updateEventHandler + ) + if (unsubscribe) { + unsubscribes.push(unsubscribe) + } } - } - }) + }) + } }) return () => { @@ -378,11 +380,11 @@ const ObjectTable = forwardRef( })) useEffect(() => { - if (authenticated && !pages.includes(initialPage) && !initialized) { + if (token != null && !pages.includes(initialPage) && !initialized) { loadInitialPage() setInitialized(true) } - }, [authenticated, loadInitialPage, initialPage, pages, initialized]) + }, [token, loadInitialPage, initialPage, pages, initialized]) const getFilterDropdown = ({ setSelectedKeys, @@ -497,7 +499,7 @@ const ObjectTable = forwardRef( fixed: fixed, key: prop.name, render: (text, record) => { - if (record.isSkeleton) { + if (record?.isSkeleton) { return ( ) diff --git a/src/components/Dashboard/context/ApiServerContext.js b/src/components/Dashboard/context/ApiServerContext.js index 240d074..9133911 100644 --- a/src/components/Dashboard/context/ApiServerContext.js +++ b/src/components/Dashboard/context/ApiServerContext.js @@ -23,7 +23,8 @@ logger.setLevel(config.logLevel) const ApiServerContext = createContext() const ApiServerProvider = ({ children }) => { - const { token, userProfile, authenticated } = useContext(AuthContext) + const { token, userProfile, authenticated, setUnauthenticated } = + useContext(AuthContext) const socketRef = useRef(null) const [connected, setConnected] = useState(false) const [connecting, setConnecting] = useState(false) @@ -347,6 +348,11 @@ const ApiServerProvider = ({ children }) => { ) const showError = (error, callback = null) => { + const code = error.response.data.code || 'UNKNOWN' + if (code == 'UNAUTHORIZED') { + setUnauthenticated() + return + } var content = `Error ${error.code} (${error.status}): ${error.message}` if (error.response?.data?.error) { content = `${error.response?.data?.error} (${error.status})` @@ -377,14 +383,13 @@ const ApiServerProvider = ({ children }) => { Authorization: `Bearer ${token}` } }) + setFetchLoading(false) return response.data } catch (err) { showError(err, () => { fetchObject(id, type) }) return {} - } finally { - setFetchLoading(false) } } @@ -397,7 +402,9 @@ const ApiServerProvider = ({ children }) => { sorter = {}, onDataChange } = params - + if (token == null) { + return [] + } logger.debug('Fetching table data from:', type, { page, limit, @@ -449,6 +456,9 @@ const ApiServerProvider = ({ children }) => { // Fetch table data with pagination, filtering, and sorting const fetchObjectsByProperty = async (type, params = {}) => { + if (token == null) { + return [] + } const { filter = {}, properties = [] } = params logger.debug('Fetching property object data from:', type, { @@ -476,8 +486,9 @@ const ApiServerProvider = ({ children }) => { return newData } catch (err) { showError(err, () => { - fetchObjectsByProperty(type, params) + fetchObjects(type, params) }) + return [] } } @@ -604,7 +615,7 @@ const ApiServerProvider = ({ children }) => { } }) - const notesData = response.data + const notesData = response.data || [] logger.debug('Fetched notes:', notesData.length) return notesData } catch (error) { diff --git a/src/components/Dashboard/context/AuthContext.js b/src/components/Dashboard/context/AuthContext.js index a52523a..d12fd19 100644 --- a/src/components/Dashboard/context/AuthContext.js +++ b/src/components/Dashboard/context/AuthContext.js @@ -15,7 +15,7 @@ import config from '../../../config' import AppError from '../../App/AppError' import loglevel from 'loglevel' import { ElectronContext } from './ElectronContext' -import { useLocation } from 'react-router-dom' +import { useLocation, useNavigate } from 'react-router-dom' const logger = loglevel.getLogger('ApiServerContext') logger.setLevel(config.logLevel) @@ -36,6 +36,7 @@ const AuthProvider = ({ children }) => { const [authError, setAuthError] = useState(null) const { openExternalUrl, isElectron } = useContext(ElectronContext) const location = useLocation() + const navigate = useNavigate() // Read token from session storage if present useEffect(() => { @@ -147,6 +148,11 @@ const AuthProvider = ({ children }) => { } }, [token]) + const setUnauthenticated = () => { + setAuthenticated(false) + setShowUnauthorizedModal(true) + } + const refreshToken = useCallback(async () => { try { const response = await axios.get(`${config.backendUrl}/auth/refresh`, { @@ -260,22 +266,28 @@ const AuthProvider = ({ children }) => { new URLSearchParams(location.search).get('authCode') || null if (authCode != null) { getLoginToken(authCode) - if (window && window.history && window.location) { - const url = new URL(window.location.href) - if (url.searchParams.has('authCode')) { - url.searchParams.delete('authCode') - window.history.replaceState( - {}, - document.title, - url.pathname + url.search - ) - } + const searchParams = new URLSearchParams(location.search) + if (searchParams.has('authCode')) { + searchParams.delete('authCode') + const newSearch = searchParams.toString() + const newPath = location.pathname + (newSearch ? `?${newSearch}` : '') + navigate(newPath, { replace: true }) } - setInitialized(true) - return + } else if (token == null) { + setShowUnauthorizedModal(true) + setAuthenticated(false) } + setInitialized(true) } - }, [checkAuthStatus, location.search, getLoginToken, initialized]) + }, [ + checkAuthStatus, + location.search, + getLoginToken, + initialized, + location.pathname, + navigate, + token + ]) if (authError) { return @@ -288,6 +300,7 @@ const AuthProvider = ({ children }) => {