835 lines
24 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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'
import loglevel from 'loglevel'
import config from '../../../config'
const logger = loglevel.getLogger('ApiContextDebug')
logger.setLevel(config.logLevel)
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,
fetchFileContent,
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')
} catch (err) {
msgApi.destroy()
msgApi.error('fetchObject test failed')
logger.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')
} catch (err) {
msgApi.destroy()
msgApi.error('fetchObjects test failed')
logger.error('fetchObjects error:', err)
}
}
const testLockObject = () => {
try {
lockObject(testInputs.objectId, testInputs.objectType)
msgApi.success('Lock command sent')
} catch (err) {
msgApi.error('Lock command failed')
logger.error('Lock error:', err)
}
}
const testUnlockObject = () => {
try {
unlockObject(testInputs.objectId, testInputs.objectType)
msgApi.success('Unlock command sent')
} catch (err) {
msgApi.error('Unlock command failed')
logger.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')
} catch (err) {
msgApi.destroy()
msgApi.error('fetchObjectLock test failed')
logger.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')
} catch (err) {
msgApi.destroy()
msgApi.error('updateObject test failed')
logger.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')
} catch (err) {
msgApi.destroy()
msgApi.error('createObject test failed')
logger.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')
} catch (err) {
msgApi.destroy()
msgApi.error('deleteObject test failed')
logger.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')
} catch (err) {
msgApi.destroy()
msgApi.error('fetchObjectsByProperty test failed')
logger.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')
} catch (err) {
msgApi.destroy()
msgApi.error('fetchSpotlightData test failed')
logger.error('fetchSpotlightData error:', err)
}
}
const testfetchFileContent = async () => {
try {
msgApi.loading('Testing fetchFileContent...', 0)
await fetchFileContent(
testInputs.objectId,
'gcodefile',
testInputs.fileName
)
msgApi.destroy()
msgApi.success('fetchFileContent test completed')
} catch (err) {
msgApi.destroy()
msgApi.error('fetchFileContent test failed')
logger.error('fetchFileContent 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')
}
}
)
} catch (err) {
msgApi.destroy()
msgApi.error('fetchTemplatePreview test failed')
logger.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')
} catch (err) {
msgApi.destroy()
msgApi.error('fetchNotes test failed')
logger.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')
}
})
} catch (err) {
msgApi.destroy()
msgApi.error('fetchHostOTP test failed')
logger.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')
}
}
)
} catch (err) {
msgApi.destroy()
msgApi.error('sendObjectAction test failed')
logger.error('sendObjectAction error:', err)
}
}
const testSubscribeToObjectUpdates = () => {
try {
const callback = (data) => {
logger.debug('Object update received:', data)
}
const unsubscribe = subscribeToObjectUpdates(
testInputs.objectId,
testInputs.objectType,
callback
)
msgApi.success('Subscribed to object updates')
logger.debug('Subscribed to object updates for test-id')
// Store unsubscribe function for cleanup
setTimeout(() => {
if (unsubscribe) {
unsubscribe()
logger.debug('Unsubscribed from object updates')
}
}, 10000) // Auto-unsubscribe after 10 seconds
} catch (err) {
msgApi.error('Subscribe to object updates failed')
logger.error('Subscribe error:', err)
}
}
const testSubscribeToObjectEvent = () => {
try {
const callback = (event) => {
logger.debug('Object event received:', event)
}
const unsubscribe = subscribeToObjectEvent(
testInputs.objectId,
'printer',
testInputs.eventType,
callback
)
msgApi.success('Subscribed to object events')
logger.debug('Subscribed to object events for test-id')
// Store unsubscribe function for cleanup
setTimeout(() => {
if (unsubscribe) {
unsubscribe()
logger.debug('Unsubscribed from object events')
}
}, 10000) // Auto-unsubscribe after 10 seconds
} catch (err) {
msgApi.error('Subscribe to object events failed')
logger.error('Subscribe error:', err)
}
}
const testSubscribeToObjectTypeUpdates = () => {
try {
const callback = (data) => {
logger.debug('Object type update received:', data)
}
const unsubscribe = subscribeToObjectTypeUpdates(
testInputs.objectType,
callback
)
msgApi.success('Subscribed to object type updates')
logger.debug('Subscribed to object type updates for user')
// Store unsubscribe function for cleanup
setTimeout(() => {
if (unsubscribe) {
unsubscribe()
logger.debug('Unsubscribed from object type updates')
}
}, 10000) // Auto-unsubscribe after 10 seconds
} catch (err) {
msgApi.error('Subscribe to object type updates failed')
logger.error('Subscribe error:', err)
}
}
const testSubscribeToObjectLock = () => {
try {
const callback = (lockData) => {
logger.debug('Object lock update received:', lockData)
}
const unsubscribe = subscribeToObjectLock(
testInputs.objectId,
testInputs.objectType,
callback
)
msgApi.success('Subscribed to object lock updates')
logger.debug('Subscribed to object lock updates for test-id')
// Store unsubscribe function for cleanup
setTimeout(() => {
if (unsubscribe) {
unsubscribe()
logger.debug('Unsubscribed from object lock updates')
}
}, 10000) // Auto-unsubscribe after 10 seconds
} catch (err) {
msgApi.error('Subscribe to object lock updates failed')
logger.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={testfetchFileContent} block>
Test fetchFileContent
</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