Add ApiContextDebug component for API server debugging and testing functionality; removed PrintServerContextDebug component.
This commit is contained in:
parent
fbdb451659
commit
f0cb4b3b83
842
src/components/Dashboard/Developer/ApiContextDebug.jsx
Normal file
842
src/components/Dashboard/Developer/ApiContextDebug.jsx
Normal file
@ -0,0 +1,842 @@
|
|||||||
|
import { useContext, useState, useEffect } from 'react'
|
||||||
|
import {
|
||||||
|
Descriptions,
|
||||||
|
Button,
|
||||||
|
Typography,
|
||||||
|
Flex,
|
||||||
|
Space,
|
||||||
|
Dropdown,
|
||||||
|
message,
|
||||||
|
Tag,
|
||||||
|
Input,
|
||||||
|
InputNumber,
|
||||||
|
Select
|
||||||
|
} from 'antd'
|
||||||
|
import ReloadIcon from '../../Icons/ReloadIcon.jsx'
|
||||||
|
import CloudIcon from '../../Icons/CloudIcon.jsx'
|
||||||
|
import { ApiServerContext } from '../context/ApiServerContext.jsx'
|
||||||
|
import BoolDisplay from '../common/BoolDisplay.jsx'
|
||||||
|
import InfoCollapse from '../common/InfoCollapse.jsx'
|
||||||
|
|
||||||
|
const { Text, Paragraph } = Typography
|
||||||
|
|
||||||
|
const ApiContextDebug = () => {
|
||||||
|
const {
|
||||||
|
apiServer,
|
||||||
|
error,
|
||||||
|
connecting,
|
||||||
|
connected,
|
||||||
|
fetchLoading,
|
||||||
|
lockObject,
|
||||||
|
unlockObject,
|
||||||
|
fetchObjectLock,
|
||||||
|
updateObject,
|
||||||
|
createObject,
|
||||||
|
deleteObject,
|
||||||
|
subscribeToObjectUpdates,
|
||||||
|
subscribeToObjectEvent,
|
||||||
|
subscribeToObjectTypeUpdates,
|
||||||
|
subscribeToObjectLock,
|
||||||
|
fetchObject,
|
||||||
|
fetchObjects,
|
||||||
|
fetchObjectsByProperty,
|
||||||
|
fetchSpotlightData,
|
||||||
|
fetchObjectContent,
|
||||||
|
fetchTemplatePreview,
|
||||||
|
fetchNotes,
|
||||||
|
fetchHostOTP,
|
||||||
|
sendObjectAction
|
||||||
|
} = useContext(ApiServerContext)
|
||||||
|
|
||||||
|
const [msgApi, contextHolder] = message.useMessage()
|
||||||
|
const [connectionStatus, setConnectionStatus] = useState('disconnected')
|
||||||
|
const [socketId, setSocketId] = useState(null)
|
||||||
|
|
||||||
|
// Test input states
|
||||||
|
const [testInputs, setTestInputs] = useState({
|
||||||
|
objectId: 'test-id',
|
||||||
|
objectType: 'user',
|
||||||
|
hostId: 'test-host-id',
|
||||||
|
parentId: 'test-parent-id',
|
||||||
|
fileName: 'test.gcode',
|
||||||
|
query: 'test query',
|
||||||
|
action: 'start',
|
||||||
|
eventType: 'status',
|
||||||
|
properties: ['name', 'email'],
|
||||||
|
filter: { active: true },
|
||||||
|
scale: 1.0,
|
||||||
|
templateContent: 'Test template content',
|
||||||
|
testObject: { name: 'Test Object' }
|
||||||
|
})
|
||||||
|
|
||||||
|
// Collapse states
|
||||||
|
const [collapseStates, setCollapseStates] = useState({
|
||||||
|
fetchTests: false,
|
||||||
|
crudTests: false,
|
||||||
|
subscriptionTests: false,
|
||||||
|
specialTests: false
|
||||||
|
})
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (apiServer) {
|
||||||
|
setConnectionStatus(apiServer.connected ? 'connected' : 'disconnected')
|
||||||
|
setSocketId(apiServer.id)
|
||||||
|
|
||||||
|
// Listen for connection status changes
|
||||||
|
const handleConnect = () => {
|
||||||
|
setConnectionStatus('connected')
|
||||||
|
setSocketId(apiServer.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDisconnect = () => {
|
||||||
|
setConnectionStatus('disconnected')
|
||||||
|
setSocketId(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
apiServer.on('connect', handleConnect)
|
||||||
|
apiServer.on('disconnect', handleDisconnect)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
apiServer.off('connect', handleConnect)
|
||||||
|
apiServer.off('disconnect', handleDisconnect)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setConnectionStatus('disconnected')
|
||||||
|
setSocketId(null)
|
||||||
|
}
|
||||||
|
}, [apiServer])
|
||||||
|
|
||||||
|
// Helper functions
|
||||||
|
const updateTestInput = (key, value) => {
|
||||||
|
setTestInputs((prev) => ({ ...prev, [key]: value }))
|
||||||
|
}
|
||||||
|
|
||||||
|
const toggleCollapse = (key) => {
|
||||||
|
setCollapseStates((prev) => ({ ...prev, [key]: !prev[key] }))
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleReconnect = () => {
|
||||||
|
if (apiServer) {
|
||||||
|
apiServer.connect()
|
||||||
|
msgApi.info('Attempting to reconnect...')
|
||||||
|
} else {
|
||||||
|
msgApi.warning('No API server instance available')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDisconnect = () => {
|
||||||
|
if (apiServer) {
|
||||||
|
apiServer.disconnect()
|
||||||
|
msgApi.info('Disconnected from API server')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const testFetchObject = async () => {
|
||||||
|
try {
|
||||||
|
msgApi.loading('Testing fetchObject...', 0)
|
||||||
|
const result = await fetchObject(
|
||||||
|
testInputs.objectId,
|
||||||
|
testInputs.objectType
|
||||||
|
)
|
||||||
|
msgApi.destroy()
|
||||||
|
msgApi.success('fetchObject test completed')
|
||||||
|
console.log('fetchObject result:', result)
|
||||||
|
} catch (err) {
|
||||||
|
msgApi.destroy()
|
||||||
|
msgApi.error('fetchObject test failed')
|
||||||
|
console.error('fetchObject error:', err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const testFetchObjects = async () => {
|
||||||
|
try {
|
||||||
|
msgApi.loading('Testing fetchObjects...', 0)
|
||||||
|
const result = await fetchObjects(testInputs.objectType, {
|
||||||
|
page: 1,
|
||||||
|
limit: 5
|
||||||
|
})
|
||||||
|
msgApi.destroy()
|
||||||
|
msgApi.success('fetchObjects test completed')
|
||||||
|
console.log('fetchObjects result:', result)
|
||||||
|
} catch (err) {
|
||||||
|
msgApi.destroy()
|
||||||
|
msgApi.error('fetchObjects test failed')
|
||||||
|
console.error('fetchObjects error:', err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const testLockObject = () => {
|
||||||
|
try {
|
||||||
|
lockObject(testInputs.objectId, testInputs.objectType)
|
||||||
|
msgApi.success('Lock command sent')
|
||||||
|
} catch (err) {
|
||||||
|
msgApi.error('Lock command failed')
|
||||||
|
console.error('Lock error:', err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const testUnlockObject = () => {
|
||||||
|
try {
|
||||||
|
unlockObject(testInputs.objectId, testInputs.objectType)
|
||||||
|
msgApi.success('Unlock command sent')
|
||||||
|
} catch (err) {
|
||||||
|
msgApi.error('Unlock command failed')
|
||||||
|
console.error('Unlock error:', err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const testFetchObjectLock = async () => {
|
||||||
|
try {
|
||||||
|
msgApi.loading('Testing fetchObjectLock...', 0)
|
||||||
|
const result = await fetchObjectLock(
|
||||||
|
testInputs.objectId,
|
||||||
|
testInputs.objectType
|
||||||
|
)
|
||||||
|
msgApi.destroy()
|
||||||
|
msgApi.success('fetchObjectLock test completed')
|
||||||
|
console.log('fetchObjectLock result:', result)
|
||||||
|
} catch (err) {
|
||||||
|
msgApi.destroy()
|
||||||
|
msgApi.error('fetchObjectLock test failed')
|
||||||
|
console.error('fetchObjectLock error:', err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const testUpdateObject = async () => {
|
||||||
|
try {
|
||||||
|
msgApi.loading('Testing updateObject...', 0)
|
||||||
|
const testData = {
|
||||||
|
name: 'Test Update',
|
||||||
|
updated: new Date().toISOString()
|
||||||
|
}
|
||||||
|
const result = await updateObject(
|
||||||
|
testInputs.objectId,
|
||||||
|
testInputs.objectType,
|
||||||
|
testData
|
||||||
|
)
|
||||||
|
msgApi.destroy()
|
||||||
|
msgApi.success('updateObject test completed')
|
||||||
|
console.log('updateObject result:', result)
|
||||||
|
} catch (err) {
|
||||||
|
msgApi.destroy()
|
||||||
|
msgApi.error('updateObject test failed')
|
||||||
|
console.error('updateObject error:', err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const testCreateObject = async () => {
|
||||||
|
try {
|
||||||
|
msgApi.loading('Testing createObject...', 0)
|
||||||
|
const testData = {
|
||||||
|
name: 'Test Create',
|
||||||
|
created: new Date().toISOString()
|
||||||
|
}
|
||||||
|
const result = await createObject(testInputs.objectType, testData)
|
||||||
|
msgApi.destroy()
|
||||||
|
msgApi.success('createObject test completed')
|
||||||
|
console.log('createObject result:', result)
|
||||||
|
} catch (err) {
|
||||||
|
msgApi.destroy()
|
||||||
|
msgApi.error('createObject test failed')
|
||||||
|
console.error('createObject error:', err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const testDeleteObject = async () => {
|
||||||
|
try {
|
||||||
|
msgApi.loading('Testing deleteObject...', 0)
|
||||||
|
const result = await deleteObject(
|
||||||
|
testInputs.objectId,
|
||||||
|
testInputs.objectType
|
||||||
|
)
|
||||||
|
msgApi.destroy()
|
||||||
|
msgApi.success('deleteObject test completed')
|
||||||
|
console.log('deleteObject result:', result)
|
||||||
|
} catch (err) {
|
||||||
|
msgApi.destroy()
|
||||||
|
msgApi.error('deleteObject test failed')
|
||||||
|
console.error('deleteObject error:', err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const testFetchObjectsByProperty = async () => {
|
||||||
|
try {
|
||||||
|
msgApi.loading('Testing fetchObjectsByProperty...', 0)
|
||||||
|
const params = {
|
||||||
|
properties: testInputs.properties,
|
||||||
|
filter: testInputs.filter,
|
||||||
|
masterFilter: {}
|
||||||
|
}
|
||||||
|
const result = await fetchObjectsByProperty(testInputs.objectType, params)
|
||||||
|
msgApi.destroy()
|
||||||
|
msgApi.success('fetchObjectsByProperty test completed')
|
||||||
|
console.log('fetchObjectsByProperty result:', result)
|
||||||
|
} catch (err) {
|
||||||
|
msgApi.destroy()
|
||||||
|
msgApi.error('fetchObjectsByProperty test failed')
|
||||||
|
console.error('fetchObjectsByProperty error:', err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const testFetchSpotlightData = async () => {
|
||||||
|
try {
|
||||||
|
msgApi.loading('Testing fetchSpotlightData...', 0)
|
||||||
|
const result = await fetchSpotlightData(testInputs.query)
|
||||||
|
msgApi.destroy()
|
||||||
|
msgApi.success('fetchSpotlightData test completed')
|
||||||
|
console.log('fetchSpotlightData result:', result)
|
||||||
|
} catch (err) {
|
||||||
|
msgApi.destroy()
|
||||||
|
msgApi.error('fetchSpotlightData test failed')
|
||||||
|
console.error('fetchSpotlightData error:', err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const testFetchObjectContent = async () => {
|
||||||
|
try {
|
||||||
|
msgApi.loading('Testing fetchObjectContent...', 0)
|
||||||
|
await fetchObjectContent(
|
||||||
|
testInputs.objectId,
|
||||||
|
'gcodefile',
|
||||||
|
testInputs.fileName
|
||||||
|
)
|
||||||
|
msgApi.destroy()
|
||||||
|
msgApi.success('fetchObjectContent test completed')
|
||||||
|
} catch (err) {
|
||||||
|
msgApi.destroy()
|
||||||
|
msgApi.error('fetchObjectContent test failed')
|
||||||
|
console.error('fetchObjectContent error:', err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const testFetchTemplatePreview = async () => {
|
||||||
|
try {
|
||||||
|
msgApi.loading('Testing fetchTemplatePreview...', 0)
|
||||||
|
|
||||||
|
fetchTemplatePreview(
|
||||||
|
testInputs.objectId,
|
||||||
|
testInputs.templateContent,
|
||||||
|
testInputs.testObject,
|
||||||
|
testInputs.scale,
|
||||||
|
(result) => {
|
||||||
|
msgApi.destroy()
|
||||||
|
if (result.success) {
|
||||||
|
msgApi.success('fetchTemplatePreview test completed')
|
||||||
|
} else {
|
||||||
|
msgApi.error('fetchTemplatePreview test failed')
|
||||||
|
}
|
||||||
|
console.log('fetchTemplatePreview result:', result)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} catch (err) {
|
||||||
|
msgApi.destroy()
|
||||||
|
msgApi.error('fetchTemplatePreview test failed')
|
||||||
|
console.error('fetchTemplatePreview error:', err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const testFetchNotes = async () => {
|
||||||
|
try {
|
||||||
|
msgApi.loading('Testing fetchNotes...', 0)
|
||||||
|
const result = await fetchNotes(testInputs.parentId)
|
||||||
|
msgApi.destroy()
|
||||||
|
msgApi.success('fetchNotes test completed')
|
||||||
|
console.log('fetchNotes result:', result)
|
||||||
|
} catch (err) {
|
||||||
|
msgApi.destroy()
|
||||||
|
msgApi.error('fetchNotes test failed')
|
||||||
|
console.error('fetchNotes error:', err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const testFetchHostOTP = async () => {
|
||||||
|
try {
|
||||||
|
msgApi.loading('Testing fetchHostOTP...', 0)
|
||||||
|
|
||||||
|
fetchHostOTP(testInputs.hostId, (result) => {
|
||||||
|
msgApi.destroy()
|
||||||
|
if (result.otp) {
|
||||||
|
msgApi.success('fetchHostOTP test completed: ' + result.otp)
|
||||||
|
} else {
|
||||||
|
msgApi.error('fetchHostOTP test failed')
|
||||||
|
}
|
||||||
|
console.log('fetchHostOTP result:', result)
|
||||||
|
})
|
||||||
|
} catch (err) {
|
||||||
|
msgApi.destroy()
|
||||||
|
msgApi.error('fetchHostOTP test failed')
|
||||||
|
console.error('fetchHostOTP error:', err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const testSendObjectAction = async () => {
|
||||||
|
try {
|
||||||
|
msgApi.loading('Testing sendObjectAction...', 0)
|
||||||
|
|
||||||
|
sendObjectAction(
|
||||||
|
testInputs.objectId,
|
||||||
|
'printer',
|
||||||
|
testInputs.action,
|
||||||
|
(result) => {
|
||||||
|
msgApi.destroy()
|
||||||
|
if (result.success) {
|
||||||
|
msgApi.success('sendObjectAction test completed')
|
||||||
|
} else {
|
||||||
|
msgApi.error('sendObjectAction test failed')
|
||||||
|
}
|
||||||
|
console.log('sendObjectAction result:', result)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} catch (err) {
|
||||||
|
msgApi.destroy()
|
||||||
|
msgApi.error('sendObjectAction test failed')
|
||||||
|
console.error('sendObjectAction error:', err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const testSubscribeToObjectUpdates = () => {
|
||||||
|
try {
|
||||||
|
const callback = (data) => {
|
||||||
|
console.log('Object update received:', data)
|
||||||
|
}
|
||||||
|
const unsubscribe = subscribeToObjectUpdates(
|
||||||
|
testInputs.objectId,
|
||||||
|
testInputs.objectType,
|
||||||
|
callback
|
||||||
|
)
|
||||||
|
msgApi.success('Subscribed to object updates')
|
||||||
|
console.log('Subscribed to object updates for test-id')
|
||||||
|
|
||||||
|
// Store unsubscribe function for cleanup
|
||||||
|
setTimeout(() => {
|
||||||
|
if (unsubscribe) {
|
||||||
|
unsubscribe()
|
||||||
|
console.log('Unsubscribed from object updates')
|
||||||
|
}
|
||||||
|
}, 10000) // Auto-unsubscribe after 10 seconds
|
||||||
|
} catch (err) {
|
||||||
|
msgApi.error('Subscribe to object updates failed')
|
||||||
|
console.error('Subscribe error:', err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const testSubscribeToObjectEvent = () => {
|
||||||
|
try {
|
||||||
|
const callback = (event) => {
|
||||||
|
console.log('Object event received:', event)
|
||||||
|
}
|
||||||
|
const unsubscribe = subscribeToObjectEvent(
|
||||||
|
testInputs.objectId,
|
||||||
|
'printer',
|
||||||
|
testInputs.eventType,
|
||||||
|
callback
|
||||||
|
)
|
||||||
|
msgApi.success('Subscribed to object events')
|
||||||
|
console.log('Subscribed to object events for test-id')
|
||||||
|
|
||||||
|
// Store unsubscribe function for cleanup
|
||||||
|
setTimeout(() => {
|
||||||
|
if (unsubscribe) {
|
||||||
|
unsubscribe()
|
||||||
|
console.log('Unsubscribed from object events')
|
||||||
|
}
|
||||||
|
}, 10000) // Auto-unsubscribe after 10 seconds
|
||||||
|
} catch (err) {
|
||||||
|
msgApi.error('Subscribe to object events failed')
|
||||||
|
console.error('Subscribe error:', err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const testSubscribeToObjectTypeUpdates = () => {
|
||||||
|
try {
|
||||||
|
const callback = (data) => {
|
||||||
|
console.log('Object type update received:', data)
|
||||||
|
}
|
||||||
|
const unsubscribe = subscribeToObjectTypeUpdates(
|
||||||
|
testInputs.objectType,
|
||||||
|
callback
|
||||||
|
)
|
||||||
|
msgApi.success('Subscribed to object type updates')
|
||||||
|
console.log('Subscribed to object type updates for user')
|
||||||
|
|
||||||
|
// Store unsubscribe function for cleanup
|
||||||
|
setTimeout(() => {
|
||||||
|
if (unsubscribe) {
|
||||||
|
unsubscribe()
|
||||||
|
console.log('Unsubscribed from object type updates')
|
||||||
|
}
|
||||||
|
}, 10000) // Auto-unsubscribe after 10 seconds
|
||||||
|
} catch (err) {
|
||||||
|
msgApi.error('Subscribe to object type updates failed')
|
||||||
|
console.error('Subscribe error:', err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const testSubscribeToObjectLock = () => {
|
||||||
|
try {
|
||||||
|
const callback = (lockData) => {
|
||||||
|
console.log('Object lock update received:', lockData)
|
||||||
|
}
|
||||||
|
const unsubscribe = subscribeToObjectLock(
|
||||||
|
testInputs.objectId,
|
||||||
|
testInputs.objectType,
|
||||||
|
callback
|
||||||
|
)
|
||||||
|
msgApi.success('Subscribed to object lock updates')
|
||||||
|
console.log('Subscribed to object lock updates for test-id')
|
||||||
|
|
||||||
|
// Store unsubscribe function for cleanup
|
||||||
|
setTimeout(() => {
|
||||||
|
if (unsubscribe) {
|
||||||
|
unsubscribe()
|
||||||
|
console.log('Unsubscribed from object lock updates')
|
||||||
|
}
|
||||||
|
}, 10000) // Auto-unsubscribe after 10 seconds
|
||||||
|
} catch (err) {
|
||||||
|
msgApi.error('Subscribe to object lock updates failed')
|
||||||
|
console.error('Subscribe error:', err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const actionItems = {
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
label: 'Reconnect',
|
||||||
|
key: 'reconnect',
|
||||||
|
disabled: connected
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Disconnect',
|
||||||
|
key: 'disconnect',
|
||||||
|
disabled: !connected
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'divider'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Reload',
|
||||||
|
key: 'reload',
|
||||||
|
icon: <ReloadIcon />
|
||||||
|
}
|
||||||
|
],
|
||||||
|
onClick: ({ key }) => {
|
||||||
|
switch (key) {
|
||||||
|
case 'reconnect':
|
||||||
|
handleReconnect()
|
||||||
|
break
|
||||||
|
case 'disconnect':
|
||||||
|
handleDisconnect()
|
||||||
|
break
|
||||||
|
case 'reload':
|
||||||
|
msgApi.info('Reloading API State...')
|
||||||
|
window.location.reload()
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getConnectionStatusColor = () => {
|
||||||
|
switch (connectionStatus) {
|
||||||
|
case 'connected':
|
||||||
|
return 'success'
|
||||||
|
case 'connecting':
|
||||||
|
return 'processing'
|
||||||
|
case 'disconnected':
|
||||||
|
return 'error'
|
||||||
|
default:
|
||||||
|
return 'default'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex vertical gap='large' style={{ height: '100%', minHeight: 0 }}>
|
||||||
|
{contextHolder}
|
||||||
|
|
||||||
|
{/* Header with Actions */}
|
||||||
|
<Flex justify={'space-between'} align={'center'}>
|
||||||
|
<Space>
|
||||||
|
<Dropdown menu={actionItems}>
|
||||||
|
<Button>Actions</Button>
|
||||||
|
</Dropdown>
|
||||||
|
</Space>
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
<Descriptions bordered>
|
||||||
|
<Descriptions.Item label='Status'>
|
||||||
|
<Space>
|
||||||
|
<Tag color={getConnectionStatusColor()} icon={<CloudIcon />}>
|
||||||
|
{connectionStatus.charAt(0).toUpperCase() +
|
||||||
|
connectionStatus.slice(1)}
|
||||||
|
</Tag>
|
||||||
|
</Space>
|
||||||
|
</Descriptions.Item>
|
||||||
|
<Descriptions.Item label='Connecting'>
|
||||||
|
<BoolDisplay value={connecting} />
|
||||||
|
</Descriptions.Item>
|
||||||
|
<Descriptions.Item label='Connected'>
|
||||||
|
<BoolDisplay value={connected} />
|
||||||
|
</Descriptions.Item>
|
||||||
|
<Descriptions.Item label='Socket ID'>
|
||||||
|
<Text code>{socketId || 'None'}</Text>
|
||||||
|
</Descriptions.Item>
|
||||||
|
<Descriptions.Item label='Fetch Loading'>
|
||||||
|
<BoolDisplay value={fetchLoading} />
|
||||||
|
</Descriptions.Item>
|
||||||
|
<Descriptions.Item label='Error'>
|
||||||
|
<Text type={error ? 'danger' : 'secondary'}>{error || 'None'}</Text>
|
||||||
|
</Descriptions.Item>
|
||||||
|
</Descriptions>
|
||||||
|
|
||||||
|
<InfoCollapse
|
||||||
|
title='Params'
|
||||||
|
icon={<span>🔎</span>}
|
||||||
|
active={collapseStates.params}
|
||||||
|
onToggle={() => toggleCollapse('params')}
|
||||||
|
collapseKey='params'
|
||||||
|
>
|
||||||
|
<Descriptions bordered column={2}>
|
||||||
|
<Descriptions.Item label='Object ID'>
|
||||||
|
<Input
|
||||||
|
value={testInputs.objectId}
|
||||||
|
onChange={(e) => updateTestInput('objectId', e.target.value)}
|
||||||
|
placeholder='Enter object ID'
|
||||||
|
size='small'
|
||||||
|
/>
|
||||||
|
</Descriptions.Item>
|
||||||
|
<Descriptions.Item label='Object Type'>
|
||||||
|
<Select
|
||||||
|
value={testInputs.objectType}
|
||||||
|
onChange={(value) => updateTestInput('objectType', value)}
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
options={[
|
||||||
|
{ value: 'user', label: 'User' },
|
||||||
|
{ value: 'printer', label: 'Printer' },
|
||||||
|
{ value: 'job', label: 'Job' },
|
||||||
|
{ value: 'filament', label: 'Filament' },
|
||||||
|
{ value: 'gcodefile', label: 'GCode File' }
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</Descriptions.Item>
|
||||||
|
<Descriptions.Item label='Host ID'>
|
||||||
|
<Input
|
||||||
|
value={testInputs.hostId}
|
||||||
|
onChange={(e) => updateTestInput('hostId', e.target.value)}
|
||||||
|
placeholder='Enter host ID'
|
||||||
|
/>
|
||||||
|
</Descriptions.Item>
|
||||||
|
<Descriptions.Item label='Parent ID'>
|
||||||
|
<Input
|
||||||
|
value={testInputs.parentId}
|
||||||
|
onChange={(e) => updateTestInput('parentId', e.target.value)}
|
||||||
|
placeholder='Enter parent ID'
|
||||||
|
/>
|
||||||
|
</Descriptions.Item>
|
||||||
|
<Descriptions.Item label='File Name'>
|
||||||
|
<Input
|
||||||
|
value={testInputs.fileName}
|
||||||
|
onChange={(e) => updateTestInput('fileName', e.target.value)}
|
||||||
|
placeholder='Enter file name'
|
||||||
|
/>
|
||||||
|
</Descriptions.Item>
|
||||||
|
<Descriptions.Item label='Query'>
|
||||||
|
<Input
|
||||||
|
value={testInputs.query}
|
||||||
|
onChange={(e) => updateTestInput('query', e.target.value)}
|
||||||
|
placeholder='Enter search query'
|
||||||
|
/>
|
||||||
|
</Descriptions.Item>
|
||||||
|
<Descriptions.Item label='Action'>
|
||||||
|
<Select
|
||||||
|
value={testInputs.action}
|
||||||
|
onChange={(value) => updateTestInput('action', value)}
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
options={[
|
||||||
|
{ value: 'start', label: 'Start' },
|
||||||
|
{ value: 'stop', label: 'Stop' },
|
||||||
|
{ value: 'pause', label: 'Pause' },
|
||||||
|
{ value: 'resume', label: 'Resume' }
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</Descriptions.Item>
|
||||||
|
<Descriptions.Item label='Event Type'>
|
||||||
|
<Select
|
||||||
|
value={testInputs.eventType}
|
||||||
|
onChange={(value) => updateTestInput('eventType', value)}
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
options={[
|
||||||
|
{ value: 'status', label: 'Status' },
|
||||||
|
{ value: 'progress', label: 'Progress' },
|
||||||
|
{ value: 'error', label: 'Error' },
|
||||||
|
{ value: 'complete', label: 'Complete' }
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</Descriptions.Item>
|
||||||
|
<Descriptions.Item label='Scale'>
|
||||||
|
<InputNumber
|
||||||
|
value={testInputs.scale}
|
||||||
|
onChange={(value) => updateTestInput('scale', value)}
|
||||||
|
min={0.1}
|
||||||
|
max={10}
|
||||||
|
step={0.1}
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
/>
|
||||||
|
</Descriptions.Item>
|
||||||
|
<Descriptions.Item label='Template Content' span={1}>
|
||||||
|
<Input.TextArea
|
||||||
|
value={testInputs.templateContent}
|
||||||
|
onChange={(e) =>
|
||||||
|
updateTestInput('templateContent', e.target.value)
|
||||||
|
}
|
||||||
|
placeholder='Enter template content'
|
||||||
|
rows={2}
|
||||||
|
/>
|
||||||
|
</Descriptions.Item>
|
||||||
|
</Descriptions>
|
||||||
|
</InfoCollapse>
|
||||||
|
|
||||||
|
{/* Test Sections */}
|
||||||
|
<div style={{ height: '100%', overflow: 'auto' }}>
|
||||||
|
<Flex vertical gap={'large'}>
|
||||||
|
{/* Fetch Tests */}
|
||||||
|
<InfoCollapse
|
||||||
|
title='Fetch Tests'
|
||||||
|
icon={<span>📥</span>}
|
||||||
|
active={collapseStates.fetchTests}
|
||||||
|
onToggle={() => toggleCollapse('fetchTests')}
|
||||||
|
collapseKey='fetchTests'
|
||||||
|
>
|
||||||
|
<Space direction='vertical' style={{ width: '100%' }}>
|
||||||
|
<Button onClick={testFetchObject} block>
|
||||||
|
Test fetchObject
|
||||||
|
</Button>
|
||||||
|
<Button onClick={testFetchObjects} block>
|
||||||
|
Test fetchObjects
|
||||||
|
</Button>
|
||||||
|
<Button onClick={testFetchObjectLock} block>
|
||||||
|
Test fetchObjectLock
|
||||||
|
</Button>
|
||||||
|
<Button onClick={testFetchObjectsByProperty} block>
|
||||||
|
Test fetchObjectsByProperty
|
||||||
|
</Button>
|
||||||
|
<Button onClick={testFetchSpotlightData} block>
|
||||||
|
Test fetchSpotlightData
|
||||||
|
</Button>
|
||||||
|
<Button onClick={testFetchObjectContent} block>
|
||||||
|
Test fetchObjectContent
|
||||||
|
</Button>
|
||||||
|
<Button onClick={testFetchNotes} block>
|
||||||
|
Test fetchNotes
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
</InfoCollapse>
|
||||||
|
|
||||||
|
{/* CRUD Tests */}
|
||||||
|
<InfoCollapse
|
||||||
|
title='CRUD Tests'
|
||||||
|
icon={<span>✏️</span>}
|
||||||
|
active={collapseStates.crudTests}
|
||||||
|
onToggle={() => toggleCollapse('crudTests')}
|
||||||
|
collapseKey='crudTests'
|
||||||
|
>
|
||||||
|
<Space direction='vertical' style={{ width: '100%' }}>
|
||||||
|
<Button onClick={testLockObject} block>
|
||||||
|
Test Lock Object
|
||||||
|
</Button>
|
||||||
|
<Button onClick={testUnlockObject} block>
|
||||||
|
Test Unlock Object
|
||||||
|
</Button>
|
||||||
|
<Button onClick={testUpdateObject} block>
|
||||||
|
Test updateObject
|
||||||
|
</Button>
|
||||||
|
<Button onClick={testCreateObject} block>
|
||||||
|
Test createObject
|
||||||
|
</Button>
|
||||||
|
<Button onClick={testDeleteObject} block>
|
||||||
|
Test deleteObject
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
</InfoCollapse>
|
||||||
|
|
||||||
|
{/* Special Tests */}
|
||||||
|
<InfoCollapse
|
||||||
|
title='Special Tests'
|
||||||
|
icon={<span>🔧</span>}
|
||||||
|
active={collapseStates.specialTests}
|
||||||
|
onToggle={() => toggleCollapse('specialTests')}
|
||||||
|
collapseKey='specialTests'
|
||||||
|
>
|
||||||
|
<Space direction='vertical' style={{ width: '100%' }}>
|
||||||
|
<Button onClick={testFetchTemplatePreview} block>
|
||||||
|
Test fetchTemplatePreview
|
||||||
|
</Button>
|
||||||
|
<Button onClick={testFetchHostOTP} block>
|
||||||
|
Test fetchHostOTP
|
||||||
|
</Button>
|
||||||
|
<Button onClick={testSendObjectAction} block>
|
||||||
|
Test sendObjectAction
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
</InfoCollapse>
|
||||||
|
|
||||||
|
{/* Subscription Tests */}
|
||||||
|
<InfoCollapse
|
||||||
|
title='Subscription Tests'
|
||||||
|
icon={<span>📡</span>}
|
||||||
|
active={collapseStates.subscriptionTests}
|
||||||
|
onToggle={() => toggleCollapse('subscriptionTests')}
|
||||||
|
collapseKey='subscriptionTests'
|
||||||
|
>
|
||||||
|
<Space direction='vertical' style={{ width: '100%' }}>
|
||||||
|
<Button onClick={testSubscribeToObjectUpdates} block>
|
||||||
|
Test Subscribe Object Updates
|
||||||
|
</Button>
|
||||||
|
<Button onClick={testSubscribeToObjectEvent} block>
|
||||||
|
Test Subscribe Object Events
|
||||||
|
</Button>
|
||||||
|
<Button onClick={testSubscribeToObjectTypeUpdates} block>
|
||||||
|
Test Subscribe Object Type Updates
|
||||||
|
</Button>
|
||||||
|
<Button onClick={testSubscribeToObjectLock} block>
|
||||||
|
Test Subscribe Object Lock
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
</InfoCollapse>
|
||||||
|
|
||||||
|
{/* API Server Instance Info */}
|
||||||
|
<InfoCollapse
|
||||||
|
title='API Server Instance'
|
||||||
|
icon={<span>🖥️</span>}
|
||||||
|
active={false}
|
||||||
|
onToggle={() => {}}
|
||||||
|
collapseKey='apiServer'
|
||||||
|
>
|
||||||
|
<pre style={{ margin: 0, fontSize: 12 }}>
|
||||||
|
{apiServer ? (
|
||||||
|
<Paragraph>
|
||||||
|
<pre>
|
||||||
|
{JSON.stringify(
|
||||||
|
{
|
||||||
|
connected: apiServer.connected,
|
||||||
|
id: apiServer.id,
|
||||||
|
transport: apiServer.io.engine.transport.name,
|
||||||
|
readyState: apiServer.io.engine.readyState
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
2
|
||||||
|
)}
|
||||||
|
</pre>
|
||||||
|
</Paragraph>
|
||||||
|
) : (
|
||||||
|
<Text type='secondary'>No API server instance</Text>
|
||||||
|
)}
|
||||||
|
</pre>
|
||||||
|
</InfoCollapse>
|
||||||
|
</Flex>
|
||||||
|
</div>
|
||||||
|
</Flex>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ApiContextDebug
|
||||||
@ -1,77 +0,0 @@
|
|||||||
import { useContext } from 'react'
|
|
||||||
import {
|
|
||||||
Descriptions,
|
|
||||||
Button,
|
|
||||||
Typography,
|
|
||||||
Flex,
|
|
||||||
Space,
|
|
||||||
Dropdown,
|
|
||||||
message
|
|
||||||
} from 'antd'
|
|
||||||
import ReloadIcon from '../../Icons/ReloadIcon.jsx'
|
|
||||||
import { PrintServerContext } from '../context/PrintServerContext.jsx'
|
|
||||||
import BoolDisplay from '../common/BoolDisplay.jsx'
|
|
||||||
|
|
||||||
const { Text, Paragraph } = Typography
|
|
||||||
|
|
||||||
const PrintServerContextDebug = () => {
|
|
||||||
const { printServer, error, connecting } = useContext(PrintServerContext)
|
|
||||||
const [msgApi, contextHolder] = message.useMessage()
|
|
||||||
|
|
||||||
const actionItems = {
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
label: 'Reload',
|
|
||||||
key: 'reload',
|
|
||||||
icon: <ReloadIcon />
|
|
||||||
}
|
|
||||||
],
|
|
||||||
onClick: ({ key }) => {
|
|
||||||
if (key === 'reload') {
|
|
||||||
msgApi.info('Reloading Page...')
|
|
||||||
window.location.reload()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper to display socket info safely
|
|
||||||
const getSocketInfo = () => {
|
|
||||||
if (!printServer) return 'n/a'
|
|
||||||
// Only show safe properties
|
|
||||||
const { id, connected, disconnected, nsp } = printServer
|
|
||||||
return JSON.stringify({ id, connected, disconnected, nsp }, null, 2)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Flex vertical gap='large' style={{ height: '100%', minHeight: 0 }}>
|
|
||||||
{contextHolder}
|
|
||||||
<Flex justify={'space-between'} align={'center'}>
|
|
||||||
<Space>
|
|
||||||
<Dropdown menu={actionItems}>
|
|
||||||
<Button>Actions</Button>
|
|
||||||
</Dropdown>
|
|
||||||
</Space>
|
|
||||||
</Flex>
|
|
||||||
<div style={{ height: '100%', overflow: 'auto' }}>
|
|
||||||
<Descriptions bordered column={1}>
|
|
||||||
<Descriptions.Item label='Connected'>
|
|
||||||
<BoolDisplay value={printServer?.connected || false} />
|
|
||||||
</Descriptions.Item>
|
|
||||||
<Descriptions.Item label='Connecting'>
|
|
||||||
<BoolDisplay value={connecting} />
|
|
||||||
</Descriptions.Item>
|
|
||||||
<Descriptions.Item label='Error'>
|
|
||||||
{error ? <Text type='danger'>{error}</Text> : <Text>n/a</Text>}
|
|
||||||
</Descriptions.Item>
|
|
||||||
<Descriptions.Item label='Socket'>
|
|
||||||
<Paragraph>
|
|
||||||
<pre>{getSocketInfo()}</pre>
|
|
||||||
</Paragraph>
|
|
||||||
</Descriptions.Item>
|
|
||||||
</Descriptions>
|
|
||||||
</div>
|
|
||||||
</Flex>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default PrintServerContextDebug
|
|
||||||
@ -24,6 +24,16 @@ import config from '../../../config'
|
|||||||
import loglevel from 'loglevel'
|
import loglevel from 'loglevel'
|
||||||
import { ElectronContext } from './ElectronContext'
|
import { ElectronContext } from './ElectronContext'
|
||||||
import { useLocation, useNavigate } from 'react-router-dom'
|
import { useLocation, useNavigate } from 'react-router-dom'
|
||||||
|
import {
|
||||||
|
getAuthCookies,
|
||||||
|
setAuthCookies,
|
||||||
|
clearAuthCookies,
|
||||||
|
areCookiesEnabled,
|
||||||
|
validateAuthCookies,
|
||||||
|
setupCookieSync,
|
||||||
|
checkAuthCookiesExpiry
|
||||||
|
} from '../../../utils/cookies'
|
||||||
|
|
||||||
const logger = loglevel.getLogger('ApiServerContext')
|
const logger = loglevel.getLogger('ApiServerContext')
|
||||||
logger.setLevel(config.logLevel)
|
logger.setLevel(config.logLevel)
|
||||||
|
|
||||||
@ -37,7 +47,7 @@ const AuthProvider = ({ children }) => {
|
|||||||
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] =
|
const [retreivedTokenFromCookies, setRetreivedTokenFromCookies] =
|
||||||
useState(false)
|
useState(false)
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
const [token, setToken] = useState(null)
|
const [token, setToken] = useState(null)
|
||||||
@ -60,33 +70,98 @@ const AuthProvider = ({ children }) => {
|
|||||||
if (isElectron == true && import.meta.env.MODE != 'development') {
|
if (isElectron == true && import.meta.env.MODE != 'development') {
|
||||||
redirectType = 'app-scheme'
|
redirectType = 'app-scheme'
|
||||||
}
|
}
|
||||||
// Read token from session storage if present
|
|
||||||
|
// Check if cookies are enabled and show warning if not
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const storedToken = sessionStorage.getItem('authToken')
|
if (!areCookiesEnabled()) {
|
||||||
const storedUser = JSON.parse(sessionStorage.getItem('user'))
|
messageApi.warning(
|
||||||
const storedExpiresAt = sessionStorage.getItem('authExpiresAt')
|
'Cookies are disabled. Login state may not persist between tabs.'
|
||||||
console.log('stored user', storedUser, storedToken)
|
)
|
||||||
if (storedToken && storedExpiresAt && storedUser) {
|
}
|
||||||
setToken(storedToken)
|
}, [messageApi])
|
||||||
setUserProfile(storedUser)
|
|
||||||
setExpiresAt(storedExpiresAt)
|
// Read token from cookies if present
|
||||||
setAuthenticated(true)
|
useEffect(() => {
|
||||||
} else {
|
try {
|
||||||
|
// First validate existing cookies to clean up expired ones
|
||||||
|
if (validateAuthCookies()) {
|
||||||
|
const {
|
||||||
|
token: storedToken,
|
||||||
|
expiresAt: storedExpiresAt,
|
||||||
|
user: storedUser
|
||||||
|
} = getAuthCookies()
|
||||||
|
console.log('Retrieved from cookies:', {
|
||||||
|
storedUser,
|
||||||
|
storedToken,
|
||||||
|
storedExpiresAt
|
||||||
|
})
|
||||||
|
|
||||||
|
setToken(storedToken)
|
||||||
|
setUserProfile(storedUser)
|
||||||
|
setExpiresAt(storedExpiresAt)
|
||||||
|
setAuthenticated(true)
|
||||||
|
} else {
|
||||||
|
setAuthenticated(false)
|
||||||
|
setUserProfile(null)
|
||||||
|
setShowUnauthorizedModal(true)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error reading auth cookies:', error)
|
||||||
|
clearAuthCookies()
|
||||||
setAuthenticated(false)
|
setAuthenticated(false)
|
||||||
setUserProfile(null)
|
setUserProfile(null)
|
||||||
setShowUnauthorizedModal(true)
|
setShowUnauthorizedModal(true)
|
||||||
}
|
}
|
||||||
setRetreivedTokenFromSession(true)
|
setRetreivedTokenFromCookies(true)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
// Set up cookie synchronization between tabs
|
||||||
|
useEffect(() => {
|
||||||
|
const cleanupCookieSync = setupCookieSync(() => {
|
||||||
|
// When cookies change in another tab, re-validate and update state
|
||||||
|
try {
|
||||||
|
if (validateAuthCookies()) {
|
||||||
|
const {
|
||||||
|
token: newToken,
|
||||||
|
expiresAt: newExpiresAt,
|
||||||
|
user: newUser
|
||||||
|
} = getAuthCookies()
|
||||||
|
if (
|
||||||
|
newToken !== token ||
|
||||||
|
newExpiresAt !== expiresAt ||
|
||||||
|
JSON.stringify(newUser) !== JSON.stringify(userProfile)
|
||||||
|
) {
|
||||||
|
setToken(newToken)
|
||||||
|
setExpiresAt(newExpiresAt)
|
||||||
|
setUserProfile(newUser)
|
||||||
|
setAuthenticated(true)
|
||||||
|
console.log('Auth state synchronized from another tab')
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Cookies are invalid, clear state
|
||||||
|
setToken(null)
|
||||||
|
setExpiresAt(null)
|
||||||
|
setUserProfile(null)
|
||||||
|
setAuthenticated(false)
|
||||||
|
setShowUnauthorizedModal(true)
|
||||||
|
console.log(
|
||||||
|
'Auth state cleared due to invalid cookies from another tab'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error syncing auth state:', error)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return cleanupCookieSync
|
||||||
|
}, [token, expiresAt, userProfile])
|
||||||
|
|
||||||
const logout = useCallback((redirectUri = '/login') => {
|
const logout = useCallback((redirectUri = '/login') => {
|
||||||
setAuthenticated(false)
|
setAuthenticated(false)
|
||||||
setToken(null)
|
setToken(null)
|
||||||
setExpiresAt(null)
|
setExpiresAt(null)
|
||||||
setUserProfile(null)
|
setUserProfile(null)
|
||||||
sessionStorage.removeItem('authToken')
|
clearAuthCookies()
|
||||||
sessionStorage.removeItem('authExpiresAt')
|
|
||||||
sessionStorage.removeItem('user')
|
|
||||||
window.location.href = `${config.backendUrl}/auth/logout?redirect_uri=${encodeURIComponent(redirectUri)}`
|
window.location.href = `${config.backendUrl}/auth/logout?redirect_uri=${encodeURIComponent(redirectUri)}`
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
@ -128,18 +203,20 @@ const AuthProvider = ({ children }) => {
|
|||||||
|
|
||||||
if (response.status === 200 && response.data) {
|
if (response.status === 200 && response.data) {
|
||||||
logger.debug('Got auth token!')
|
logger.debug('Got auth token!')
|
||||||
setToken(response.data.access_token)
|
const authData = response.data
|
||||||
setExpiresAt(response.data.expires_at)
|
|
||||||
setUserProfile(response.data)
|
setToken(authData.access_token)
|
||||||
sessionStorage.setItem('authToken', response.data.access_token)
|
setExpiresAt(authData.expires_at)
|
||||||
sessionStorage.setItem('authExpiresAt', response.data.expires_at)
|
setUserProfile(authData)
|
||||||
const userObject = {
|
|
||||||
...response.data,
|
// Store in cookies for persistence between tabs
|
||||||
access_token: undefined,
|
const cookieSuccess = setAuthCookies(authData)
|
||||||
refresh_token: undefined,
|
if (!cookieSuccess) {
|
||||||
id_token: undefined
|
messageApi.warning(
|
||||||
|
'Authentication successful but failed to save login state. You may need to log in again if you close this tab.'
|
||||||
|
)
|
||||||
}
|
}
|
||||||
sessionStorage.setItem('user', JSON.stringify(userObject))
|
|
||||||
const searchParams = new URLSearchParams(location.search)
|
const searchParams = new URLSearchParams(location.search)
|
||||||
searchParams.delete('authCode')
|
searchParams.delete('authCode')
|
||||||
const newSearch = searchParams.toString()
|
const newSearch = searchParams.toString()
|
||||||
@ -167,8 +244,9 @@ const AuthProvider = ({ children }) => {
|
|||||||
setLoading(false)
|
setLoading(false)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[isElectron]
|
[isElectron, navigate, location.search, location.pathname, messageApi]
|
||||||
)
|
)
|
||||||
|
|
||||||
// Function to check if the user is logged in
|
// Function to check if the user is logged in
|
||||||
const checkAuthStatus = useCallback(async () => {
|
const checkAuthStatus = useCallback(async () => {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
@ -183,12 +261,19 @@ const AuthProvider = ({ children }) => {
|
|||||||
|
|
||||||
if (response.status === 200 && response.data) {
|
if (response.status === 200 && response.data) {
|
||||||
logger.debug('Got auth token!')
|
logger.debug('Got auth token!')
|
||||||
setToken(response.data.access_token)
|
const authData = response.data
|
||||||
setExpiresAt(response.data.expires_at)
|
|
||||||
setUserProfile(response.data)
|
setToken(authData.access_token)
|
||||||
sessionStorage.setItem('authToken', response.data.access_token)
|
setExpiresAt(authData.expires_at)
|
||||||
sessionStorage.setItem('authExpiresAt', response.data.expires_at)
|
setUserProfile(authData)
|
||||||
sessionStorage.setItem('user', response.data)
|
|
||||||
|
// Update cookies with fresh data
|
||||||
|
const cookieSuccess = setAuthCookies(authData)
|
||||||
|
if (!cookieSuccess) {
|
||||||
|
messageApi.warning(
|
||||||
|
'Failed to update login state. You may need to log in again if you close this tab.'
|
||||||
|
)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
setAuthenticated(false)
|
setAuthenticated(false)
|
||||||
setAuthError('Failed to authenticate user.')
|
setAuthError('Failed to authenticate user.')
|
||||||
@ -206,15 +291,13 @@ const AuthProvider = ({ children }) => {
|
|||||||
} finally {
|
} finally {
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
}
|
}
|
||||||
}, [token])
|
}, [token, messageApi])
|
||||||
|
|
||||||
const setUnauthenticated = () => {
|
const setUnauthenticated = () => {
|
||||||
setToken(null)
|
setToken(null)
|
||||||
setExpiresAt(null)
|
setExpiresAt(null)
|
||||||
setUserProfile(null)
|
setUserProfile(null)
|
||||||
sessionStorage.removeItem('authToken')
|
clearAuthCookies()
|
||||||
sessionStorage.removeItem('authExpiresAt')
|
|
||||||
sessionStorage.removeItem('user')
|
|
||||||
setShowUnauthorizedModal(true)
|
setShowUnauthorizedModal(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -227,15 +310,23 @@ const AuthProvider = ({ children }) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
if (response.status === 200 && response.data) {
|
if (response.status === 200 && response.data) {
|
||||||
setToken(response.data.access_token)
|
const authData = response.data
|
||||||
setExpiresAt(response.data.expires_at)
|
|
||||||
sessionStorage.setItem('authToken', response.data.access_token)
|
setToken(authData.access_token)
|
||||||
sessionStorage.setItem('authExpiresAt', response.data.expires_at)
|
setExpiresAt(authData.expires_at)
|
||||||
|
|
||||||
|
// Update cookies with fresh token data
|
||||||
|
const cookieSuccess = setAuthCookies(authData)
|
||||||
|
if (!cookieSuccess) {
|
||||||
|
messageApi.warning(
|
||||||
|
'Failed to update login state. You may need to log in again if you close this tab.'
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Token refresh failed', error)
|
console.error('Token refresh failed', error)
|
||||||
}
|
}
|
||||||
}, [token])
|
}, [token, messageApi])
|
||||||
|
|
||||||
const handleSessionExpiredModalOk = () => {
|
const handleSessionExpiredModalOk = () => {
|
||||||
setShowSessionExpiredModal(false)
|
setShowSessionExpiredModal(false)
|
||||||
@ -315,6 +406,55 @@ const AuthProvider = ({ children }) => {
|
|||||||
notificationApi.destroy('token-expiration')
|
notificationApi.destroy('token-expiration')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// Check cookies directly if expiresAt is not set in state
|
||||||
|
const expiryInfo = checkAuthCookiesExpiry(5) // Check if expiring within 5 minutes
|
||||||
|
if (expiryInfo.isExpiringSoon && expiryInfo.minutesRemaining <= 1) {
|
||||||
|
// Show notification for cookies expiring soon
|
||||||
|
const seconds = Math.floor((expiryInfo.timeRemaining % 60000) / 1000)
|
||||||
|
const totalSeconds = 60
|
||||||
|
const remainingSeconds = totalSeconds - seconds
|
||||||
|
const progress = (remainingSeconds / totalSeconds) * 100
|
||||||
|
|
||||||
|
notificationApi.info({
|
||||||
|
message: 'Session Expiring Soon',
|
||||||
|
description: (
|
||||||
|
<div>
|
||||||
|
<div style={{ marginBottom: 8 }}>
|
||||||
|
Your session will expire in {seconds} seconds
|
||||||
|
</div>
|
||||||
|
<Progress
|
||||||
|
percent={progress}
|
||||||
|
size='small'
|
||||||
|
status='active'
|
||||||
|
showInfo={false}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
duration: 0,
|
||||||
|
key: 'token-expiration',
|
||||||
|
icon: null,
|
||||||
|
placement: 'bottomRight',
|
||||||
|
style: {
|
||||||
|
width: 360
|
||||||
|
},
|
||||||
|
className: 'token-expiration-notification',
|
||||||
|
closeIcon: null,
|
||||||
|
onClose: () => {},
|
||||||
|
btn: (
|
||||||
|
<Button
|
||||||
|
type='primary'
|
||||||
|
size='small'
|
||||||
|
onClick={() => {
|
||||||
|
notificationApi.destroy('token-expiration')
|
||||||
|
refreshToken()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Reload Session
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -335,12 +475,12 @@ const AuthProvider = ({ children }) => {
|
|||||||
getLoginToken(authCode)
|
getLoginToken(authCode)
|
||||||
} else if (
|
} else if (
|
||||||
token == null &&
|
token == null &&
|
||||||
retreivedTokenFromSession == true &&
|
retreivedTokenFromCookies == true &&
|
||||||
initialized == false &&
|
initialized == false &&
|
||||||
authCode == null
|
authCode == null
|
||||||
) {
|
) {
|
||||||
setInitialized(true)
|
setInitialized(true)
|
||||||
console.log('Showing unauth')
|
console.log('Showing unauth', token, authCode)
|
||||||
setShowUnauthorizedModal(true)
|
setShowUnauthorizedModal(true)
|
||||||
setAuthenticated(false)
|
setAuthenticated(false)
|
||||||
}
|
}
|
||||||
@ -352,7 +492,7 @@ const AuthProvider = ({ children }) => {
|
|||||||
location.pathname,
|
location.pathname,
|
||||||
navigate,
|
navigate,
|
||||||
token,
|
token,
|
||||||
retreivedTokenFromSession
|
retreivedTokenFromCookies
|
||||||
])
|
])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { Route } from 'react-router-dom'
|
import { Route } from 'react-router-dom'
|
||||||
import SessionStorage from '../components/Dashboard/Developer/SessionStorage.jsx'
|
import SessionStorage from '../components/Dashboard/Developer/SessionStorage.jsx'
|
||||||
import AuthContextDebug from '../components/Dashboard/Developer/AuthContextDebug.jsx'
|
import AuthContextDebug from '../components/Dashboard/Developer/AuthContextDebug.jsx'
|
||||||
import PrintServerContextDebug from '../components/Dashboard/Developer/PrintServerContextDebug.jsx'
|
import ApiContextDebug from '../components/Dashboard/Developer/ApiContextDebug.jsx'
|
||||||
|
|
||||||
const DeveloperRoutes = [
|
const DeveloperRoutes = [
|
||||||
<Route
|
<Route
|
||||||
@ -15,9 +15,9 @@ const DeveloperRoutes = [
|
|||||||
element={<AuthContextDebug />}
|
element={<AuthContextDebug />}
|
||||||
/>,
|
/>,
|
||||||
<Route
|
<Route
|
||||||
key='printservercontextdebug'
|
key='apicontextdebug'
|
||||||
path='developer/printservercontextdebug'
|
path='developer/apicontextdebug'
|
||||||
element={<PrintServerContextDebug />}
|
element={<ApiContextDebug />}
|
||||||
/>
|
/>
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user