Added big fixes allowing dynamic obtaining of the token.
This commit is contained in:
parent
a20235a953
commit
08311a4a94
@ -66,7 +66,7 @@
|
|||||||
"eject": "react-scripts eject",
|
"eject": "react-scripts eject",
|
||||||
"minify-svgs": "node scripts/minify-svgs.js",
|
"minify-svgs": "node scripts/minify-svgs.js",
|
||||||
|
|
||||||
"dev:electron": "concurrently \"react-scripts start\" \"ELECTRON_START_URL=http://192.168.68.53:3000 electron src/electron/main.js\"",
|
"dev:electron": "concurrently \"react-scripts start\" \"ELECTRON_START_URL=http://192.168.68.53:3000 electron public/electron.js\"",
|
||||||
"build:electron": "npm run build && electron-builder"
|
"build:electron": "npm run build && electron-builder"
|
||||||
},
|
},
|
||||||
"eslintConfig": {
|
"eslintConfig": {
|
||||||
|
|||||||
@ -68,6 +68,10 @@
|
|||||||
line-height: 32.5px;
|
line-height: 32.5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.loading-modal .ant-modal-footer {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--unit-100vh: 100vh;
|
--unit-100vh: 100vh;
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import React, { useEffect, useState, useCallback } from 'react'
|
import React, { useEffect, useState, useCallback, useContext } from 'react'
|
||||||
import {
|
import {
|
||||||
Descriptions,
|
Descriptions,
|
||||||
Space,
|
Space,
|
||||||
@ -21,10 +21,12 @@ import ReloadIcon from '../../Icons/ReloadIcon'
|
|||||||
import useCollapseState from '../hooks/useCollapseState'
|
import useCollapseState from '../hooks/useCollapseState'
|
||||||
|
|
||||||
import config from '../../../config'
|
import config from '../../../config'
|
||||||
|
import { AuthContext } from '../context/AuthContext'
|
||||||
|
|
||||||
const { Title, Text } = Typography
|
const { Title, Text } = Typography
|
||||||
|
|
||||||
const ProductionOverview = () => {
|
const ProductionOverview = () => {
|
||||||
|
const { token } = useContext(AuthContext)
|
||||||
const [messageApi, contextHolder] = message.useMessage()
|
const [messageApi, contextHolder] = message.useMessage()
|
||||||
const [error, setError] = useState(null)
|
const [error, setError] = useState(null)
|
||||||
const [fetchPrinterStatsLoading, setFetchPrinterStatsLoading] = useState(true)
|
const [fetchPrinterStatsLoading, setFetchPrinterStatsLoading] = useState(true)
|
||||||
@ -68,9 +70,9 @@ const ProductionOverview = () => {
|
|||||||
setFetchPrinterStatsLoading(true)
|
setFetchPrinterStatsLoading(true)
|
||||||
const response = await axios.get(`${config.backendUrl}/printers/stats`, {
|
const response = await axios.get(`${config.backendUrl}/printers/stats`, {
|
||||||
headers: {
|
headers: {
|
||||||
Accept: 'application/json'
|
Accept: 'application/json',
|
||||||
},
|
Authorization: `Bearer ${token}`
|
||||||
withCredentials: true
|
}
|
||||||
})
|
})
|
||||||
const printStats = response.data
|
const printStats = response.data
|
||||||
setStats((prev) => ({ ...prev, printers: printStats }))
|
setStats((prev) => ({ ...prev, printers: printStats }))
|
||||||
@ -88,9 +90,9 @@ const ProductionOverview = () => {
|
|||||||
setFetchPrinterStatsLoading(true)
|
setFetchPrinterStatsLoading(true)
|
||||||
const response = await axios.get(`${config.backendUrl}/jobs/stats`, {
|
const response = await axios.get(`${config.backendUrl}/jobs/stats`, {
|
||||||
headers: {
|
headers: {
|
||||||
Accept: 'application/json'
|
Accept: 'application/json',
|
||||||
},
|
Authorization: `Bearer ${token}`
|
||||||
withCredentials: true
|
}
|
||||||
})
|
})
|
||||||
const jobstats = response.data
|
const jobstats = response.data
|
||||||
setStats((prev) => ({ ...prev, jobs: jobstats }))
|
setStats((prev) => ({ ...prev, jobs: jobstats }))
|
||||||
@ -107,9 +109,9 @@ const ProductionOverview = () => {
|
|||||||
try {
|
try {
|
||||||
const response = await axios.get(`${config.backendUrl}/stats/history`, {
|
const response = await axios.get(`${config.backendUrl}/stats/history`, {
|
||||||
headers: {
|
headers: {
|
||||||
Accept: 'application/json'
|
Accept: 'application/json',
|
||||||
},
|
Authorization: `Bearer ${token}`
|
||||||
withCredentials: true
|
}
|
||||||
})
|
})
|
||||||
setChartData(response.data)
|
setChartData(response.data)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -118,8 +120,10 @@ const ProductionOverview = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchAllStats()
|
if (token != null) {
|
||||||
}, [fetchAllStats])
|
fetchAllStats()
|
||||||
|
}
|
||||||
|
}, [fetchAllStats, token])
|
||||||
|
|
||||||
if (fetchPrinterStatsLoading || fetchPrinterStatsLoading) {
|
if (fetchPrinterStatsLoading || fetchPrinterStatsLoading) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -402,9 +402,6 @@ const ApiServerProvider = ({ children }) => {
|
|||||||
sorter = {},
|
sorter = {},
|
||||||
onDataChange
|
onDataChange
|
||||||
} = params
|
} = params
|
||||||
if (token == null) {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
logger.debug('Fetching table data from:', type, {
|
logger.debug('Fetching table data from:', type, {
|
||||||
page,
|
page,
|
||||||
limit,
|
limit,
|
||||||
@ -456,9 +453,6 @@ const ApiServerProvider = ({ children }) => {
|
|||||||
|
|
||||||
// Fetch table data with pagination, filtering, and sorting
|
// Fetch table data with pagination, filtering, and sorting
|
||||||
const fetchObjectsByProperty = async (type, params = {}) => {
|
const fetchObjectsByProperty = async (type, params = {}) => {
|
||||||
if (token == null) {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
const { filter = {}, properties = [] } = params
|
const { filter = {}, properties = [] } = params
|
||||||
|
|
||||||
logger.debug('Fetching property object data from:', type, {
|
logger.debug('Fetching property object data from:', type, {
|
||||||
@ -571,9 +565,6 @@ const ApiServerProvider = ({ children }) => {
|
|||||||
|
|
||||||
// Download GCode file content
|
// Download GCode file content
|
||||||
const fetchObjectContent = async (id, type, fileName) => {
|
const fetchObjectContent = async (id, type, fileName) => {
|
||||||
if (!token) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
const response = await axios.get(
|
const response = await axios.get(
|
||||||
`${config.backendUrl}/${type.toLowerCase()}s/${id}/content`,
|
`${config.backendUrl}/${type.toLowerCase()}s/${id}/content`,
|
||||||
|
|||||||
@ -7,7 +7,16 @@ import React, {
|
|||||||
useContext
|
useContext
|
||||||
} from 'react'
|
} from 'react'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import { message, Modal, notification, Progress, Button, Space } from 'antd'
|
import {
|
||||||
|
message,
|
||||||
|
Modal,
|
||||||
|
notification,
|
||||||
|
Progress,
|
||||||
|
Button,
|
||||||
|
Space,
|
||||||
|
Typography
|
||||||
|
} from 'antd'
|
||||||
|
import { LoadingOutlined } from '@ant-design/icons'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import ExclamationOctogonIcon from '../../Icons/ExclamationOctagonIcon'
|
import ExclamationOctogonIcon from '../../Icons/ExclamationOctagonIcon'
|
||||||
import InfoCircleIcon from '../../Icons/InfoCircleIcon'
|
import InfoCircleIcon from '../../Icons/InfoCircleIcon'
|
||||||
@ -21,12 +30,16 @@ logger.setLevel(config.logLevel)
|
|||||||
|
|
||||||
const AuthContext = createContext()
|
const AuthContext = createContext()
|
||||||
|
|
||||||
|
const Title = Typography
|
||||||
|
|
||||||
const AuthProvider = ({ children }) => {
|
const AuthProvider = ({ children }) => {
|
||||||
const [messageApi, contextHolder] = message.useMessage()
|
const [messageApi, contextHolder] = message.useMessage()
|
||||||
const [notificationApi, notificationContextHolder] =
|
const [notificationApi, notificationContextHolder] =
|
||||||
notification.useNotification()
|
notification.useNotification()
|
||||||
const [authenticated, setAuthenticated] = useState(false)
|
const [authenticated, setAuthenticated] = useState(false)
|
||||||
const [initialized, setInitialized] = useState(false)
|
const [initialized, setInitialized] = useState(false)
|
||||||
|
const [retreivedTokenFromSession, setRetreivedTokenFromSession] =
|
||||||
|
useState(false)
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
const [token, setToken] = useState(null)
|
const [token, setToken] = useState(null)
|
||||||
const [expiresAt, setExpiresAt] = useState(null)
|
const [expiresAt, setExpiresAt] = useState(null)
|
||||||
@ -46,7 +59,11 @@ const AuthProvider = ({ children }) => {
|
|||||||
setToken(storedToken)
|
setToken(storedToken)
|
||||||
setExpiresAt(storedExpiresAt)
|
setExpiresAt(storedExpiresAt)
|
||||||
setAuthenticated(true)
|
setAuthenticated(true)
|
||||||
|
} else {
|
||||||
|
setAuthenticated(false)
|
||||||
|
setShowUnauthorizedModal(true)
|
||||||
}
|
}
|
||||||
|
setRetreivedTokenFromSession(true)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const logout = useCallback((redirectUri = '/login') => {
|
const logout = useCallback((redirectUri = '/login') => {
|
||||||
@ -61,18 +78,19 @@ const AuthProvider = ({ children }) => {
|
|||||||
|
|
||||||
// Login using query parameters
|
// Login using query parameters
|
||||||
const loginWithSSO = useCallback(
|
const loginWithSSO = useCallback(
|
||||||
(redirectUri = window.location.pathname + window.location.search) => {
|
(redirectUri = location.pathname + location.search) => {
|
||||||
messageApi.info('Logging in with tombutcher.work')
|
messageApi.info('Logging in with tombutcher.work')
|
||||||
const loginUrl = `${config.backendUrl}/auth/${isElectron ? 'app/' : ''}login?redirect_uri=${encodeURIComponent(redirectUri)}`
|
const loginUrl = `${config.backendUrl}/auth/${isElectron ? 'app/' : ''}login?redirect_uri=${encodeURIComponent(redirectUri)}`
|
||||||
if (isElectron) {
|
if (isElectron) {
|
||||||
console.log('Opening external url...')
|
console.log('Opening external url...')
|
||||||
openExternalUrl(loginUrl)
|
openExternalUrl(loginUrl)
|
||||||
|
setLoading(true)
|
||||||
} else {
|
} else {
|
||||||
console.log('Redirecting...')
|
console.log('Redirecting...')
|
||||||
window.location.href = loginUrl
|
window.location.href = loginUrl
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[messageApi, openExternalUrl, isElectron]
|
[messageApi, openExternalUrl, isElectron, location.search]
|
||||||
)
|
)
|
||||||
|
|
||||||
const getLoginToken = useCallback(
|
const getLoginToken = useCallback(
|
||||||
@ -94,6 +112,11 @@ const AuthProvider = ({ children }) => {
|
|||||||
setUserProfile(response.data)
|
setUserProfile(response.data)
|
||||||
sessionStorage.setItem('authToken', response.data.access_token)
|
sessionStorage.setItem('authToken', response.data.access_token)
|
||||||
sessionStorage.setItem('authExpiresAt', response.data.expires_at)
|
sessionStorage.setItem('authExpiresAt', response.data.expires_at)
|
||||||
|
const searchParams = new URLSearchParams(location.search)
|
||||||
|
searchParams.delete('authCode')
|
||||||
|
const newSearch = searchParams.toString()
|
||||||
|
const newPath = location.pathname + (newSearch ? `?${newSearch}` : '')
|
||||||
|
navigate(newPath, { replace: true })
|
||||||
} else {
|
} else {
|
||||||
setAuthenticated(false)
|
setAuthenticated(false)
|
||||||
setAuthError('Failed to authenticate user.')
|
setAuthError('Failed to authenticate user.')
|
||||||
@ -261,23 +284,20 @@ const AuthProvider = ({ children }) => {
|
|||||||
}, [expiresAt, authenticated, notificationApi, refreshToken])
|
}, [expiresAt, authenticated, notificationApi, refreshToken])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (initialized == false) {
|
const authCode =
|
||||||
const authCode =
|
new URLSearchParams(location.search).get('authCode') || null
|
||||||
new URLSearchParams(location.search).get('authCode') || null
|
if (authCode != null) {
|
||||||
if (authCode != null) {
|
getLoginToken(authCode)
|
||||||
getLoginToken(authCode)
|
} else if (
|
||||||
const searchParams = new URLSearchParams(location.search)
|
token == null &&
|
||||||
if (searchParams.has('authCode')) {
|
retreivedTokenFromSession == true &&
|
||||||
searchParams.delete('authCode')
|
initialized == false &&
|
||||||
const newSearch = searchParams.toString()
|
authCode == null
|
||||||
const newPath = location.pathname + (newSearch ? `?${newSearch}` : '')
|
) {
|
||||||
navigate(newPath, { replace: true })
|
|
||||||
}
|
|
||||||
} else if (token == null) {
|
|
||||||
setShowUnauthorizedModal(true)
|
|
||||||
setAuthenticated(false)
|
|
||||||
}
|
|
||||||
setInitialized(true)
|
setInitialized(true)
|
||||||
|
console.log('Showing unauth')
|
||||||
|
setShowUnauthorizedModal(true)
|
||||||
|
setAuthenticated(false)
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
checkAuthStatus,
|
checkAuthStatus,
|
||||||
@ -286,7 +306,8 @@ const AuthProvider = ({ children }) => {
|
|||||||
initialized,
|
initialized,
|
||||||
location.pathname,
|
location.pathname,
|
||||||
navigate,
|
navigate,
|
||||||
token
|
token,
|
||||||
|
retreivedTokenFromSession
|
||||||
])
|
])
|
||||||
|
|
||||||
if (authError) {
|
if (authError) {
|
||||||
@ -370,18 +391,22 @@ const AuthProvider = ({ children }) => {
|
|||||||
tombutcher.work to continue.
|
tombutcher.work to continue.
|
||||||
</Modal>
|
</Modal>
|
||||||
<Modal
|
<Modal
|
||||||
title={
|
|
||||||
<Space size={'middle'}>
|
|
||||||
<ExclamationOctogonIcon />
|
|
||||||
Loading...
|
|
||||||
</Space>
|
|
||||||
}
|
|
||||||
open={loading}
|
open={loading}
|
||||||
style={{ maxWidth: 200, top: '50%', transform: 'translateY(-50%)' }}
|
className={'loading-modal'}
|
||||||
|
title={false}
|
||||||
|
height={20}
|
||||||
|
style={{ maxWidth: 220, top: '50%', transform: 'translateY(-50%)' }}
|
||||||
closable={false}
|
closable={false}
|
||||||
maskClosable={false}
|
maskClosable={false}
|
||||||
footer={false}
|
footer={false}
|
||||||
/>
|
>
|
||||||
|
<Space size={'middle'}>
|
||||||
|
<LoadingOutlined />
|
||||||
|
<Title level={5} style={{ margin: 0 }}>
|
||||||
|
Loading, please wait...
|
||||||
|
</Title>
|
||||||
|
</Space>
|
||||||
|
</Modal>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,45 +1,16 @@
|
|||||||
// PrivateRoute.js
|
// PrivateRoute.js
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import React, { useContext, useState, useEffect } from 'react'
|
import React, { useContext } from 'react'
|
||||||
import { AuthContext } from './Dashboard/context/AuthContext'
|
import { AuthContext } from './Dashboard/context/AuthContext'
|
||||||
import AuthLoading from './App/AppLoading'
|
|
||||||
import { useThemeContext } from './Dashboard/context/ThemeContext'
|
|
||||||
|
|
||||||
const PrivateRoute = ({ component: Component }) => {
|
const PrivateRoute = ({ component: Component }) => {
|
||||||
const { isDarkMode } = useThemeContext()
|
const { authenticated, showSessionExpiredModal } = useContext(AuthContext)
|
||||||
const { authenticated, loading, showSessionExpiredModal } =
|
|
||||||
useContext(AuthContext)
|
|
||||||
const [fadeIn, setFadeIn] = useState(false)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!loading) {
|
|
||||||
// Small delay to ensure smooth transition
|
|
||||||
const timer = setTimeout(() => setFadeIn(true), 50)
|
|
||||||
return () => clearTimeout(timer)
|
|
||||||
}
|
|
||||||
}, [loading])
|
|
||||||
|
|
||||||
// Show loading state while auth state is being determined
|
|
||||||
if (loading) {
|
|
||||||
return <AuthLoading />
|
|
||||||
}
|
|
||||||
|
|
||||||
// Redirect to login if not authenticated
|
// Redirect to login if not authenticated
|
||||||
return (
|
return (
|
||||||
<div style={{ background: isDarkMode ? '#000000' : '#ffffff' }}>
|
<>
|
||||||
<div
|
{authenticated || showSessionExpiredModal ? <Component /> : <Component />}
|
||||||
style={{
|
</>
|
||||||
opacity: fadeIn ? 1 : 0,
|
|
||||||
transition: 'opacity 0.3s ease-in-out'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{authenticated || showSessionExpiredModal ? (
|
|
||||||
<Component />
|
|
||||||
) : (
|
|
||||||
<Component />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -3,15 +3,9 @@ import PropTypes from 'prop-types'
|
|||||||
import React, { useContext } from 'react'
|
import React, { useContext } from 'react'
|
||||||
import { Navigate } from 'react-router-dom'
|
import { Navigate } from 'react-router-dom'
|
||||||
import { AuthContext } from './Dashboard/context/AuthContext'
|
import { AuthContext } from './Dashboard/context/AuthContext'
|
||||||
import AuthLoading from './App/AppLoading'
|
|
||||||
|
|
||||||
const PublicRoute = ({ component: Component }) => {
|
const PublicRoute = ({ component: Component }) => {
|
||||||
const { authenticated, loading } = useContext(AuthContext)
|
const { authenticated } = useContext(AuthContext)
|
||||||
|
|
||||||
// Show loading state while auth state is being determined
|
|
||||||
if (loading) {
|
|
||||||
return <AuthLoading />
|
|
||||||
}
|
|
||||||
|
|
||||||
// Redirect to login if not authenticated
|
// Redirect to login if not authenticated
|
||||||
return !authenticated ? (
|
return !authenticated ? (
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user