Refactor routing logic in App component to conditionally use HashRouter or BrowserRouter based on the environment. Update AuthContext and related components to replace 'authenticated' with 'token' for improved authentication handling. Enhance NotesPanel and ObjectTable components to manage loading states and data fetching more effectively, ensuring better user experience and error handling.
This commit is contained in:
parent
66e137fac2
commit
a20235a953
14
src/App.jsx
14
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 (
|
||||
<ConfigProvider theme={themeConfig}>
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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,10 +321,14 @@ 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 (
|
||||
<Spin indicator={<LoadingOutlined />} spinning={loading}>
|
||||
<Card>
|
||||
<Flex
|
||||
justify='center'
|
||||
@ -339,7 +342,6 @@ const NotesPanel = ({ _id, onNewNote, type }) => {
|
||||
<Text type='secondary'>No notes added.</Text>
|
||||
</Flex>
|
||||
</Card>
|
||||
</Spin>
|
||||
)
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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,6 +302,7 @@ const ObjectTable = forwardRef(
|
||||
|
||||
// Subscribe to each item in all pages
|
||||
pages.forEach((page) => {
|
||||
if (page?.items && page?.items?.length > 0) {
|
||||
page.items.forEach((item) => {
|
||||
if (!item.isSkeleton) {
|
||||
const unsubscribe = subscribeToObject(
|
||||
@ -314,6 +315,7 @@ const ObjectTable = forwardRef(
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
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 (
|
||||
<Skeleton.Input active size='small' style={{ width: '100%' }} />
|
||||
)
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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 })
|
||||
}
|
||||
} else if (token == null) {
|
||||
setShowUnauthorizedModal(true)
|
||||
setAuthenticated(false)
|
||||
}
|
||||
setInitialized(true)
|
||||
return
|
||||
}
|
||||
}
|
||||
}, [checkAuthStatus, location.search, getLoginToken, initialized])
|
||||
}, [
|
||||
checkAuthStatus,
|
||||
location.search,
|
||||
getLoginToken,
|
||||
initialized,
|
||||
location.pathname,
|
||||
navigate,
|
||||
token
|
||||
])
|
||||
|
||||
if (authError) {
|
||||
return <AppError message={authError} showBack={false} />
|
||||
@ -288,6 +300,7 @@ const AuthProvider = ({ children }) => {
|
||||
<AuthContext.Provider
|
||||
value={{
|
||||
authenticated,
|
||||
setUnauthenticated,
|
||||
loginWithSSO,
|
||||
getLoginToken,
|
||||
token,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user