1088 lines
31 KiB
JavaScript
1088 lines
31 KiB
JavaScript
// src/contexts/ApiServerContext.js
|
|
import {
|
|
createContext,
|
|
useEffect,
|
|
useState,
|
|
useContext,
|
|
useRef,
|
|
useCallback
|
|
} from 'react'
|
|
import io from 'socket.io-client'
|
|
import { message, notification, Modal, Space, Button } from 'antd'
|
|
import PropTypes from 'prop-types'
|
|
import { AuthContext } from './AuthContext'
|
|
|
|
import axios from 'axios'
|
|
import ExclamationOctagonIcon from '../../Icons/ExclamationOctagonIcon'
|
|
import ReloadIcon from '../../Icons/ReloadIcon'
|
|
import config from '../../../config'
|
|
import loglevel from 'loglevel'
|
|
const logger = loglevel.getLogger('ApiServerContext')
|
|
logger.setLevel(config.logLevel)
|
|
|
|
const ApiServerContext = createContext()
|
|
|
|
const ApiServerProvider = ({ children }) => {
|
|
const { token, userProfile, authenticated, setUnauthenticated } =
|
|
useContext(AuthContext)
|
|
const socketRef = useRef(null)
|
|
const [connected, setConnected] = useState(false)
|
|
const [connecting, setConnecting] = useState(false)
|
|
const [error, setError] = useState(null)
|
|
const [messageApi, contextHolder] = message.useMessage()
|
|
const [notificationApi] = notification.useNotification()
|
|
const [fetchLoading, setFetchLoading] = useState(false)
|
|
const [showErrorModal, setShowErrorModal] = useState(false)
|
|
const [errorModalContent, setErrorModalContent] = useState('')
|
|
const [retryCallback, setRetryCallback] = useState(null)
|
|
const subscribedCallbacksRef = useRef(new Map())
|
|
const subscribedLockCallbacksRef = useRef(new Map())
|
|
|
|
const handleLockUpdate = useCallback(
|
|
async (lockData) => {
|
|
logger.debug('Notifying lock update:', lockData)
|
|
const objectId = lockData._id || lockData.id
|
|
|
|
if (
|
|
objectId &&
|
|
subscribedLockCallbacksRef.current.has(objectId) &&
|
|
lockData.user != userProfile?._id
|
|
) {
|
|
const callbacks = subscribedLockCallbacksRef.current.get(objectId)
|
|
logger.debug(
|
|
`Calling ${callbacks.length} lock callbacks for object:`,
|
|
objectId
|
|
)
|
|
callbacks.forEach((callback) => {
|
|
try {
|
|
callback(lockData)
|
|
} catch (error) {
|
|
logger.error('Error in lock update callback:', error)
|
|
}
|
|
})
|
|
} else {
|
|
logger.debug(
|
|
`No lock callbacks found for object: ${objectId}, subscribed lock callbacks:`,
|
|
Array.from(subscribedLockCallbacksRef.current.keys())
|
|
)
|
|
}
|
|
},
|
|
[userProfile?._id]
|
|
)
|
|
|
|
const connectToServer = useCallback(() => {
|
|
if (token && authenticated == true) {
|
|
logger.debug('Token is available, connecting to api server...')
|
|
|
|
const newSocket = io(config.apiServerUrl, {
|
|
reconnectionAttempts: 3,
|
|
timeout: 3000,
|
|
auth: { type: 'user' }
|
|
})
|
|
|
|
setConnecting(true)
|
|
|
|
newSocket.on('connect', () => {
|
|
logger.debug('Api Server connected')
|
|
newSocket.emit('authenticate', { token: token }, (result) => {
|
|
console.log('Auth result', result)
|
|
setConnecting(false)
|
|
setConnected(true)
|
|
setError(null)
|
|
})
|
|
})
|
|
|
|
newSocket.on('objectUpdate', handleObjectUpdate)
|
|
newSocket.on('objectEvent', handleObjectEvent)
|
|
newSocket.on('objectNew', handleObjectNew)
|
|
newSocket.on('objectDelete', handleObjectDelete)
|
|
newSocket.on('lockUpdate', handleLockUpdate)
|
|
|
|
newSocket.on('disconnect', () => {
|
|
logger.debug('Api Server disconnected')
|
|
setError('Api Server disconnected')
|
|
setConnected(false)
|
|
})
|
|
|
|
newSocket.on('connect_error', (err) => {
|
|
logger.error('Api Server connection error:', err)
|
|
messageApi.error('Api Server connection error: ' + err.message)
|
|
setError('Api Server connection error')
|
|
setConnected(false)
|
|
})
|
|
|
|
newSocket.on('bridge.notification', (data) => {
|
|
notificationApi[data.type]({
|
|
title: data.title,
|
|
message: data.message
|
|
})
|
|
})
|
|
|
|
newSocket.on('error', (err) => {
|
|
logger.error('Api Server error:', err)
|
|
setError('Api Server error')
|
|
})
|
|
|
|
socketRef.current = newSocket
|
|
}
|
|
}, [token, authenticated, messageApi, notificationApi, handleLockUpdate])
|
|
|
|
useEffect(() => {
|
|
if (token && authenticated == true) {
|
|
connectToServer()
|
|
} else if (!token && socketRef.current) {
|
|
logger.debug('Token not available, disconnecting api server...')
|
|
socketRef.current.disconnect()
|
|
socketRef.current = null
|
|
}
|
|
|
|
// Clean up function
|
|
return () => {
|
|
if (socketRef.current) {
|
|
logger.debug('Cleaning up api server connection...')
|
|
socketRef.current.disconnect()
|
|
socketRef.current = null
|
|
}
|
|
}
|
|
}, [token, authenticated, connectToServer])
|
|
|
|
const lockObject = (id, type) => {
|
|
logger.debug('Locking ' + id)
|
|
if (socketRef.current && socketRef.current.connected) {
|
|
socketRef.current.emit('lock', { _id: id, type: type })
|
|
logger.debug('Sent lock command for object:', id)
|
|
}
|
|
}
|
|
|
|
const unlockObject = (id, type) => {
|
|
logger.debug('Unlocking ' + id)
|
|
if (socketRef.current && socketRef.current.connected == true) {
|
|
socketRef.current.emit('unlock', { _id: id, type: type })
|
|
logger.debug('Sent unlock command for object:', id)
|
|
}
|
|
}
|
|
|
|
const fetchObjectLock = async (id, type) => {
|
|
if (socketRef.current && socketRef.current.connected == true) {
|
|
logger.debug('Fetching lock status for ' + id)
|
|
return new Promise((resolve) => {
|
|
socketRef.current.emit(
|
|
'getLock',
|
|
{
|
|
_id: id,
|
|
type: type
|
|
},
|
|
(lockEvent) => {
|
|
logger.debug('Received lock status for object:', id, lockEvent)
|
|
if (lockEvent.user != userProfile?._id) {
|
|
resolve(lockEvent)
|
|
} else {
|
|
resolve(null)
|
|
}
|
|
}
|
|
)
|
|
logger.debug('Sent fetch lock command for object:', id)
|
|
})
|
|
}
|
|
}
|
|
|
|
const handleObjectUpdate = async (data) => {
|
|
logger.debug('Notifying object update:', data)
|
|
const id = data._id
|
|
const objectType = data.objectType
|
|
|
|
const callbacksRefKey = `${objectType}:${id}`
|
|
|
|
if (
|
|
id &&
|
|
objectType &&
|
|
subscribedCallbacksRef.current.has(callbacksRefKey)
|
|
) {
|
|
const callbacks = subscribedCallbacksRef.current.get(callbacksRefKey)
|
|
logger.debug(
|
|
`Calling ${callbacks.length} callbacks for object:`,
|
|
callbacksRefKey
|
|
)
|
|
callbacks.forEach((callback) => {
|
|
try {
|
|
callback(data.object)
|
|
} catch (error) {
|
|
logger.error('Error in object update callback:', error)
|
|
}
|
|
})
|
|
} else {
|
|
logger.debug(
|
|
`No callbacks found for object: ${callbacksRefKey}, subscribed callbacks:`,
|
|
Array.from(subscribedCallbacksRef.current.keys())
|
|
)
|
|
}
|
|
}
|
|
|
|
const handleObjectEvent = async (data) => {
|
|
const id = data._id
|
|
const objectType = data.objectType
|
|
|
|
const callbacksRefKey = `${objectType}:${id}:events:${data.event.type}`
|
|
logger.debug('Notifying object event:', data)
|
|
if (
|
|
id &&
|
|
objectType &&
|
|
subscribedCallbacksRef.current.has(callbacksRefKey)
|
|
) {
|
|
const callbacks = subscribedCallbacksRef.current.get(callbacksRefKey)
|
|
logger.debug(
|
|
`Calling ${callbacks.length} callbacks for object:`,
|
|
callbacksRefKey
|
|
)
|
|
callbacks.forEach((callback) => {
|
|
try {
|
|
callback(data.event)
|
|
} catch (error) {
|
|
logger.error('Error in object event callback:', error)
|
|
}
|
|
})
|
|
} else {
|
|
logger.debug(
|
|
`No callbacks found for object: ${callbacksRefKey}, subscribed callbacks:`,
|
|
Array.from(subscribedCallbacksRef.current.keys())
|
|
)
|
|
}
|
|
}
|
|
|
|
const handleObjectNew = async (data) => {
|
|
logger.debug('Notifying object new:', data)
|
|
const objectType = data.objectType || 'unknown'
|
|
|
|
if (objectType && subscribedCallbacksRef.current.has(objectType)) {
|
|
const callbacks = subscribedCallbacksRef.current.get(objectType)
|
|
logger.debug(
|
|
`Calling ${callbacks.length} callbacks for type:`,
|
|
objectType
|
|
)
|
|
callbacks.forEach((callback) => {
|
|
try {
|
|
callback(data.object)
|
|
} catch (error) {
|
|
logger.error('Error in object new callback:', error)
|
|
}
|
|
})
|
|
} else {
|
|
logger.debug(
|
|
`No callbacks found for object: ${objectType}, subscribed callbacks:`,
|
|
Array.from(subscribedCallbacksRef.current.keys())
|
|
)
|
|
}
|
|
}
|
|
|
|
const handleObjectDelete = async (data) => {
|
|
logger.debug('Notifying object delete:', data)
|
|
const objectType = data.objectType || 'unknown'
|
|
|
|
if (objectType && subscribedCallbacksRef.current.has(objectType)) {
|
|
const callbacks = subscribedCallbacksRef.current.get(objectType)
|
|
logger.debug(
|
|
`Calling ${callbacks.length} callbacks for type:`,
|
|
objectType
|
|
)
|
|
callbacks.forEach((callback) => {
|
|
try {
|
|
callback(data.object)
|
|
} catch (error) {
|
|
logger.error('Error in object new callback:', error)
|
|
}
|
|
})
|
|
} else {
|
|
logger.debug(
|
|
`No callbacks found for object: ${objectType}, subscribed callbacks:`,
|
|
Array.from(subscribedCallbacksRef.current.keys())
|
|
)
|
|
}
|
|
}
|
|
|
|
const offObjectUpdatesEvent = useCallback((id, objectType, callback) => {
|
|
if (socketRef.current && socketRef.current.connected == true) {
|
|
const callbacksRefKey = `${objectType}:${id}`
|
|
// Remove callback from the subscribed callbacks map
|
|
if (subscribedCallbacksRef.current.has(callbacksRefKey)) {
|
|
const callbacks = subscribedCallbacksRef.current
|
|
.get(callbacksRefKey)
|
|
.filter((cb) => cb !== callback)
|
|
if (callbacks.length === 0) {
|
|
logger.debug(
|
|
'No callbacks found for object:',
|
|
callbacksRefKey,
|
|
'unsubscribing from object update...'
|
|
)
|
|
subscribedCallbacksRef.current.delete(callbacksRefKey)
|
|
socketRef.current.emit('unsubscribeObjectUpdate', {
|
|
_id: id,
|
|
objectType: objectType
|
|
})
|
|
} else {
|
|
subscribedCallbacksRef.current.set(callbacksRefKey, callbacks)
|
|
}
|
|
}
|
|
}
|
|
}, [])
|
|
|
|
const offObjectTypeUpdatesEvent = useCallback((objectType, callback) => {
|
|
if (socketRef.current && socketRef.current.connected == true) {
|
|
// Remove callback from the subscribed callbacks map
|
|
console.log(
|
|
'Unsubscribing from type',
|
|
objectType,
|
|
subscribedCallbacksRef.current.has(objectType)
|
|
)
|
|
if (subscribedCallbacksRef.current.has(objectType)) {
|
|
const callbacks = subscribedCallbacksRef.current
|
|
.get(objectType)
|
|
.filter((cb) => cb !== callback)
|
|
console.log('API: CALLBACKS', callbacks)
|
|
|
|
if (callbacks.length === 0) {
|
|
subscribedCallbacksRef.current.delete(objectType)
|
|
socketRef.current.emit('unsubscribeObjectTypeUpdate', {
|
|
objectType: objectType
|
|
})
|
|
} else {
|
|
subscribedCallbacksRef.current.set(objectType, callbacks)
|
|
}
|
|
}
|
|
}
|
|
}, [])
|
|
|
|
const subscribeToObjectUpdates = useCallback(
|
|
(id, objectType, callback) => {
|
|
if (socketRef.current && socketRef.current.connected == true) {
|
|
const callbacksRefKey = `${objectType}:${id}`
|
|
// Add callback to the subscribed callbacks map immediately
|
|
if (!subscribedCallbacksRef.current.has(callbacksRefKey)) {
|
|
subscribedCallbacksRef.current.set(callbacksRefKey, [])
|
|
}
|
|
|
|
const callbacksLength =
|
|
subscribedCallbacksRef.current.get(callbacksRefKey).length
|
|
|
|
if (callbacksLength <= 0) {
|
|
socketRef.current.emit(
|
|
'subscribeToObjectUpdate',
|
|
{
|
|
_id: id,
|
|
objectType: objectType
|
|
},
|
|
(result) => {
|
|
if (result.success) {
|
|
logger.info('Subscribed to id:', id, 'objectType:', objectType)
|
|
}
|
|
}
|
|
)
|
|
}
|
|
logger.info(
|
|
'Adding callback id:',
|
|
id,
|
|
'objectType:',
|
|
objectType,
|
|
'callbacks length:',
|
|
callbacksLength + 1
|
|
)
|
|
subscribedCallbacksRef.current.get(callbacksRefKey).push(callback)
|
|
|
|
// Return cleanup function
|
|
return () => offObjectUpdatesEvent(id, objectType, callback)
|
|
}
|
|
},
|
|
[offObjectUpdatesEvent]
|
|
)
|
|
|
|
const subscribeToObjectTypeUpdates = useCallback(
|
|
(objectType, callback) => {
|
|
logger.debug('Subscribing to type updates:', objectType)
|
|
if (socketRef.current && socketRef.current.connected == true) {
|
|
// Add callback to the subscribed callbacks map immediately
|
|
if (!subscribedCallbacksRef.current.has(objectType)) {
|
|
subscribedCallbacksRef.current.set(objectType, [])
|
|
}
|
|
subscribedCallbacksRef.current.get(objectType).push(callback)
|
|
logger.debug(
|
|
`Added callback for type ${objectType}, total callbacks: ${subscribedCallbacksRef.current.get(objectType).length}`
|
|
)
|
|
|
|
socketRef.current.emit(
|
|
'subscribeToObjectTypeUpdate',
|
|
{ objectType: objectType },
|
|
(result) => {
|
|
if (result.success) {
|
|
logger.info('Subscribed to objectType:', objectType)
|
|
}
|
|
}
|
|
)
|
|
logger.debug('Registered type event listener for object:', objectType)
|
|
|
|
// Return cleanup function
|
|
return () => offObjectTypeUpdatesEvent(objectType, callback)
|
|
}
|
|
},
|
|
[offObjectTypeUpdatesEvent]
|
|
)
|
|
|
|
const offLockEvent = useCallback((id, callback) => {
|
|
if (socketRef.current && socketRef.current.connected == true) {
|
|
// Remove callback from the subscribed lock callbacks map
|
|
if (subscribedLockCallbacksRef.current.has(id)) {
|
|
const callbacks = subscribedLockCallbacksRef.current
|
|
.get(id)
|
|
.filter((cb) => cb !== callback)
|
|
if (callbacks.length === 0) {
|
|
subscribedLockCallbacksRef.current.delete(id)
|
|
} else {
|
|
subscribedLockCallbacksRef.current.set(id, callbacks)
|
|
}
|
|
}
|
|
|
|
logger.debug('Removed lock event listener for object:', id)
|
|
}
|
|
}, [])
|
|
|
|
const offObjectEventEvent = useCallback(
|
|
(id, objectType, eventType, callback) => {
|
|
if (socketRef.current && socketRef.current.connected == true) {
|
|
const callbacksRefKey = `${objectType}:${id}:events:${eventType}`
|
|
// Remove callback from the subscribed callbacks map
|
|
if (subscribedCallbacksRef.current.has(callbacksRefKey)) {
|
|
const callbacks = subscribedCallbacksRef.current
|
|
.get(callbacksRefKey)
|
|
.filter((cb) => cb !== callback)
|
|
if (callbacks.length === 0) {
|
|
subscribedCallbacksRef.current.delete(callbacksRefKey)
|
|
socketRef.current.emit('unsubscribeObjectEvent', {
|
|
_id: id,
|
|
objectType,
|
|
eventType
|
|
})
|
|
} else {
|
|
subscribedCallbacksRef.current.set(callbacksRefKey, callbacks)
|
|
}
|
|
}
|
|
}
|
|
},
|
|
[]
|
|
)
|
|
|
|
const subscribeToObjectEvent = useCallback(
|
|
(id, objectType, eventType, callback) => {
|
|
if (socketRef.current && socketRef.current.connected == true) {
|
|
const callbacksRefKey = `${objectType}:${id}:events:${eventType}`
|
|
// Add callback to the subscribed callbacks map immediately
|
|
if (!subscribedCallbacksRef.current.has(callbacksRefKey)) {
|
|
subscribedCallbacksRef.current.set(callbacksRefKey, [])
|
|
}
|
|
|
|
const callbacksLength =
|
|
subscribedCallbacksRef.current.get(callbacksRefKey).length
|
|
|
|
if (callbacksLength <= 0) {
|
|
socketRef.current.emit(
|
|
'subscribeToObjectEvent',
|
|
{
|
|
_id: id,
|
|
objectType: objectType,
|
|
eventType: eventType
|
|
},
|
|
(result) => {
|
|
if (result.success) {
|
|
logger.info(
|
|
'Subscribed to event id:',
|
|
id,
|
|
'objectType:',
|
|
objectType,
|
|
'eventType:',
|
|
eventType
|
|
)
|
|
}
|
|
}
|
|
)
|
|
}
|
|
logger.info(
|
|
'Adding event callback id:',
|
|
id,
|
|
'objectType:',
|
|
objectType,
|
|
'eventType:',
|
|
eventType,
|
|
'callbacks length:',
|
|
callbacksLength + 1
|
|
)
|
|
subscribedCallbacksRef.current.get(callbacksRefKey).push(callback)
|
|
|
|
// Return cleanup function
|
|
return () => offObjectEventEvent(id, objectType, eventType, callback)
|
|
}
|
|
},
|
|
[offObjectUpdatesEvent]
|
|
)
|
|
|
|
const subscribeToObjectLock = useCallback(
|
|
(id, type, callback) => {
|
|
logger.debug('Subscribing to lock for object:', id, 'type:', type)
|
|
if (socketRef.current && socketRef.current.connected == true) {
|
|
// Add callback to the subscribed lock callbacks map immediately
|
|
if (!subscribedLockCallbacksRef.current.has(id)) {
|
|
subscribedLockCallbacksRef.current.set(id, [])
|
|
}
|
|
subscribedLockCallbacksRef.current.get(id).push(callback)
|
|
logger.debug(
|
|
`Added lock callback for object ${id}, total lock callbacks: ${subscribedLockCallbacksRef.current.get(id).length}`
|
|
)
|
|
|
|
socketRef.current.emit('subscribe_lock', { _id: id, objectType: type })
|
|
logger.debug('Registered lock event listener for object:', id)
|
|
|
|
// Return cleanup function
|
|
return () => offLockEvent(id, callback)
|
|
}
|
|
},
|
|
[offLockEvent]
|
|
)
|
|
|
|
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})`
|
|
}
|
|
setErrorModalContent(content)
|
|
setRetryCallback(() => callback)
|
|
setShowErrorModal(true)
|
|
}
|
|
|
|
const handleRetry = () => {
|
|
setShowErrorModal(false)
|
|
setErrorModalContent('')
|
|
if (retryCallback) {
|
|
retryCallback()
|
|
}
|
|
setRetryCallback(null)
|
|
}
|
|
|
|
// Generalized fetchObject function
|
|
const fetchObject = async (id, type) => {
|
|
const fetchUrl = `${config.backendUrl}/${type}s/${id}`
|
|
setFetchLoading(true)
|
|
logger.debug('Fetching from ' + fetchUrl)
|
|
try {
|
|
const response = await axios.get(fetchUrl, {
|
|
headers: {
|
|
Accept: 'application/json',
|
|
Authorization: `Bearer ${token}`
|
|
}
|
|
})
|
|
setFetchLoading(false)
|
|
return response.data
|
|
} catch (err) {
|
|
console.error(err)
|
|
showError(err, () => {
|
|
fetchObject(id, type)
|
|
})
|
|
return {}
|
|
}
|
|
}
|
|
|
|
// Fetch table data with pagination, filtering, and sorting
|
|
const fetchObjects = async (type, params = {}) => {
|
|
const {
|
|
page = 1,
|
|
limit = 25,
|
|
filter = {},
|
|
sorter = {},
|
|
onDataChange
|
|
} = params
|
|
logger.debug('Fetching table data from:', type, {
|
|
page,
|
|
limit,
|
|
filter,
|
|
sorter
|
|
})
|
|
|
|
try {
|
|
const response = await axios.get(
|
|
`${config.backendUrl}/${type.toLowerCase()}s`,
|
|
{
|
|
params: {
|
|
page,
|
|
limit,
|
|
...filter,
|
|
sort: sorter.field,
|
|
order: sorter.order
|
|
},
|
|
headers: {
|
|
Accept: 'application/json',
|
|
Authorization: `Bearer ${token}`
|
|
}
|
|
}
|
|
)
|
|
|
|
const newData = response.data
|
|
const totalCount = parseInt(response.headers['x-total-count'] || '0', 10)
|
|
const totalPages = Math.ceil(totalCount / limit)
|
|
const hasMore = newData.length >= limit
|
|
|
|
if (onDataChange) {
|
|
onDataChange(newData)
|
|
}
|
|
|
|
return {
|
|
data: newData,
|
|
totalCount,
|
|
totalPages,
|
|
hasMore,
|
|
page
|
|
}
|
|
} catch (err) {
|
|
console.error(err)
|
|
showError(err, () => {
|
|
fetchObjects(type, params)
|
|
})
|
|
return []
|
|
}
|
|
}
|
|
|
|
// Fetch table data with pagination, filtering, and sorting
|
|
const fetchObjectsByProperty = async (type, params = {}) => {
|
|
const { filter = {}, properties = [], masterFilter = {} } = params
|
|
|
|
logger.debug('Fetching property object data from:', type, {
|
|
properties,
|
|
filter
|
|
})
|
|
|
|
try {
|
|
const response = await axios.get(
|
|
`${config.backendUrl}/${type.toLowerCase()}s/properties`,
|
|
{
|
|
params: {
|
|
...filter,
|
|
properties: properties.join(','), // Convert array to comma-separated string
|
|
masterFilter: JSON.stringify(masterFilter)
|
|
},
|
|
headers: {
|
|
Accept: 'application/json',
|
|
Authorization: `Bearer ${token}`
|
|
}
|
|
}
|
|
)
|
|
|
|
const newData = response.data
|
|
|
|
return newData
|
|
} catch (err) {
|
|
console.error(err)
|
|
showError(err, () => {
|
|
fetchObjects(type, params)
|
|
})
|
|
|
|
return []
|
|
}
|
|
}
|
|
|
|
// Update filament information
|
|
const updateObject = async (id, type, value) => {
|
|
const updateUrl = `${config.backendUrl}/${type.toLowerCase()}s/${id}`
|
|
logger.debug('Updating info for ' + id)
|
|
try {
|
|
const response = await axios.put(updateUrl, value, {
|
|
headers: {
|
|
Accept: 'application/json',
|
|
Authorization: `Bearer ${token}`
|
|
}
|
|
})
|
|
logger.debug('Object updated successfully')
|
|
return response.data
|
|
} catch (err) {
|
|
console.error(err)
|
|
setError(err, () => {
|
|
updateObject(id, type, value)
|
|
})
|
|
return {}
|
|
}
|
|
}
|
|
|
|
// Update filament information
|
|
const deleteObject = async (id, type) => {
|
|
const deleteUrl = `${config.backendUrl}/${type.toLowerCase()}s/${id}`
|
|
logger.debug('Deleting object ID: ' + id)
|
|
try {
|
|
const response = await axios.delete(deleteUrl, {
|
|
headers: {
|
|
Accept: 'application/json',
|
|
Authorization: `Bearer ${token}`
|
|
}
|
|
})
|
|
logger.debug('Object deleted successfully')
|
|
return response.data
|
|
} catch (err) {
|
|
console.error(err)
|
|
showError(err, () => {
|
|
deleteObject(id, type)
|
|
})
|
|
return {}
|
|
}
|
|
}
|
|
|
|
// Update filament information
|
|
const createObject = async (type, value) => {
|
|
const createUrl = `${config.backendUrl}/${type.toLowerCase()}s`
|
|
logger.debug('Creating object...')
|
|
try {
|
|
const response = await axios.post(createUrl, value, {
|
|
headers: {
|
|
Accept: 'application/json',
|
|
Authorization: `Bearer ${token}`
|
|
}
|
|
})
|
|
return response.data
|
|
} catch (err) {
|
|
console.error(err)
|
|
showError(err, () => {
|
|
createObject(type, value)
|
|
})
|
|
return {}
|
|
}
|
|
}
|
|
|
|
// Download GCode file content
|
|
const fetchFileContent = async (file, download = false) => {
|
|
try {
|
|
const response = await axios.get(
|
|
`${config.backendUrl}/files/${file._id}/content`,
|
|
{
|
|
headers: {
|
|
Accept: '*/*',
|
|
Authorization: `Bearer ${token}`
|
|
},
|
|
responseType: 'blob'
|
|
}
|
|
)
|
|
const blob = new Blob([response.data], {
|
|
type: response.headers['content-type']
|
|
})
|
|
const fileURL = window.URL.createObjectURL(blob)
|
|
if (download == true) {
|
|
const fileLink = document.createElement('a')
|
|
fileLink.href = fileURL
|
|
fileLink.setAttribute('download', `${file.name}${file.extension}`)
|
|
document.body.appendChild(fileLink)
|
|
fileLink.click()
|
|
fileLink.parentNode.removeChild(fileLink)
|
|
return
|
|
}
|
|
return fileURL
|
|
} catch (err) {
|
|
console.error(err)
|
|
showError(err, () => {
|
|
fetchFileContent(file, download)
|
|
})
|
|
}
|
|
}
|
|
|
|
// Fetch notes for a specific parent
|
|
const fetchNotes = async (parentId) => {
|
|
logger.debug('Fetching notes for parent:', parentId)
|
|
try {
|
|
const response = await axios.get(`${config.backendUrl}/notes`, {
|
|
params: {
|
|
'parent._id': parentId,
|
|
sort: 'createdAt',
|
|
order: 'ascend'
|
|
},
|
|
headers: {
|
|
Accept: 'application/json',
|
|
Authorization: `Bearer ${token}`
|
|
}
|
|
})
|
|
|
|
const notesData = response.data || []
|
|
logger.debug('Fetched notes:', notesData.length)
|
|
return notesData
|
|
} catch (err) {
|
|
console.error(err)
|
|
showError(err, () => {
|
|
fetchNotes(parentId)
|
|
})
|
|
}
|
|
}
|
|
|
|
const fetchSpotlightData = async (query) => {
|
|
logger.debug('Fetching spotlight data with query:', query)
|
|
try {
|
|
const response = await axios.get(
|
|
`${config.backendUrl}/spotlight/${query}`,
|
|
{
|
|
headers: {
|
|
Accept: 'application/json',
|
|
Authorization: `Bearer ${token}`
|
|
}
|
|
}
|
|
)
|
|
return response.data
|
|
} catch (err) {
|
|
console.error(err)
|
|
showError(err, () => {
|
|
fetchSpotlightData(query)
|
|
})
|
|
}
|
|
}
|
|
|
|
const fetchTemplatePreview = async (
|
|
id,
|
|
content,
|
|
testObject,
|
|
scale,
|
|
callback
|
|
) => {
|
|
logger.debug('Fetching preview...')
|
|
if (socketRef.current && socketRef.current.connected) {
|
|
return socketRef.current.emit(
|
|
'previewTemplate',
|
|
{
|
|
_id: id,
|
|
content: content,
|
|
testObject: testObject,
|
|
scale: scale
|
|
},
|
|
callback
|
|
)
|
|
}
|
|
}
|
|
|
|
const fetchTemplatePDF = async (id, content, testObject, callback) => {
|
|
logger.debug('Fetching pdf template...')
|
|
if (socketRef.current && socketRef.current.connected) {
|
|
return socketRef.current.emit(
|
|
'renderTemplatePDF',
|
|
{
|
|
_id: id,
|
|
content: content,
|
|
object: testObject
|
|
},
|
|
callback
|
|
)
|
|
}
|
|
}
|
|
|
|
const downloadTemplatePDF = async (
|
|
id,
|
|
content,
|
|
object,
|
|
filename,
|
|
callback
|
|
) => {
|
|
logger.debug('Downloading template PDF...')
|
|
|
|
fetchTemplatePDF(id, content, object, (result) => {
|
|
logger.debug('Downloading template PDF result:', result)
|
|
if (result?.error) {
|
|
console.error(result.error)
|
|
if (callback) {
|
|
callback(result.error)
|
|
}
|
|
} else {
|
|
const pdfBlob = new Blob([result.pdf], { type: 'application/pdf' })
|
|
const pdfUrl = URL.createObjectURL(pdfBlob)
|
|
const fileLink = document.createElement('a')
|
|
fileLink.href = pdfUrl
|
|
fileLink.setAttribute('download', `${filename}.pdf`)
|
|
document.body.appendChild(fileLink)
|
|
fileLink.click()
|
|
fileLink.parentNode.removeChild(fileLink)
|
|
if (callback) {
|
|
callback()
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
const fetchHostOTP = async (id, callback) => {
|
|
logger.debug('Fetching host OTP...')
|
|
if (socketRef.current && socketRef.current.connected) {
|
|
return socketRef.current.emit(
|
|
'generateHostOtp',
|
|
{
|
|
_id: id
|
|
},
|
|
callback
|
|
)
|
|
}
|
|
}
|
|
|
|
const sendObjectAction = async (id, objectType, action, callback) => {
|
|
logger.debug('Sending object action...')
|
|
if (socketRef.current && socketRef.current.connected) {
|
|
return socketRef.current.emit(
|
|
'objectAction',
|
|
{
|
|
_id: id,
|
|
objectType: objectType,
|
|
action: action
|
|
},
|
|
callback
|
|
)
|
|
}
|
|
}
|
|
|
|
// Upload file to the API
|
|
const uploadFile = async (file, additionalData = {}) => {
|
|
const uploadUrl = `${config.backendUrl}/files`
|
|
logger.debug('Uploading file:', file.name, 'to:', uploadUrl)
|
|
|
|
try {
|
|
const formData = new FormData()
|
|
formData.append('file', file)
|
|
|
|
// Add any additional data to the form
|
|
Object.keys(additionalData).forEach((key) => {
|
|
formData.append(key, additionalData[key])
|
|
})
|
|
|
|
const response = await axios.post(uploadUrl, formData, {
|
|
headers: {
|
|
'Content-Type': 'multipart/form-data',
|
|
Authorization: `Bearer ${token}`
|
|
},
|
|
onUploadProgress: (progressEvent) => {
|
|
const percentCompleted = Math.round(
|
|
(progressEvent.loaded * 100) / progressEvent.total
|
|
)
|
|
logger.debug(`Upload progress: ${percentCompleted}%`)
|
|
}
|
|
})
|
|
|
|
logger.debug('File uploaded successfully:', response.data)
|
|
return response.data
|
|
} catch (err) {
|
|
console.error('File upload error:', err)
|
|
showError(err, () => {
|
|
uploadFile(file, additionalData)
|
|
})
|
|
return null
|
|
}
|
|
}
|
|
|
|
const flushFile = async (id) => {
|
|
logger.debug('Flushing file...')
|
|
try {
|
|
const response = await axios.delete(
|
|
`${config.backendUrl}/files/${id}/flush`,
|
|
{
|
|
headers: {
|
|
Accept: 'application/json',
|
|
Authorization: `Bearer ${token}`
|
|
}
|
|
}
|
|
)
|
|
|
|
logger.debug('Flushed file:', response.data)
|
|
return true
|
|
} catch (err) {
|
|
console.error(err)
|
|
showError(err, () => {
|
|
flushFile(id)
|
|
})
|
|
}
|
|
}
|
|
|
|
// Sanitize a string so it is safe to use as a filename on most file systems
|
|
const formatFileName = (name) => {
|
|
if (!name || typeof name !== 'string') {
|
|
return ''
|
|
}
|
|
|
|
// Remove characters that are problematic on most common file systems
|
|
const cleaned = name.replace(/[^a-zA-Z0-9.\-_\s]/g, '')
|
|
|
|
// Normalize whitespace to single underscores
|
|
const normalized = cleaned.trim().replace(/\s+/g, '_')
|
|
|
|
// Most file systems limit filenames to 255 characters
|
|
return normalized.slice(0, 255)
|
|
}
|
|
|
|
return (
|
|
<ApiServerContext.Provider
|
|
value={{
|
|
apiServer: socketRef.current,
|
|
error,
|
|
connecting,
|
|
connected,
|
|
lockObject,
|
|
unlockObject,
|
|
fetchObjectLock,
|
|
updateObject,
|
|
createObject,
|
|
deleteObject,
|
|
subscribeToObjectUpdates,
|
|
subscribeToObjectEvent,
|
|
subscribeToObjectTypeUpdates,
|
|
subscribeToObjectLock,
|
|
fetchObject,
|
|
fetchObjects,
|
|
fetchObjectsByProperty,
|
|
fetchSpotlightData,
|
|
fetchLoading,
|
|
showError,
|
|
fetchFileContent,
|
|
fetchTemplatePreview,
|
|
fetchTemplatePDF,
|
|
fetchNotes,
|
|
downloadTemplatePDF,
|
|
fetchHostOTP,
|
|
sendObjectAction,
|
|
uploadFile,
|
|
flushFile,
|
|
formatFileName
|
|
}}
|
|
>
|
|
{contextHolder}
|
|
{children}
|
|
<Modal
|
|
title={
|
|
<Space size={'middle'}>
|
|
<ExclamationOctagonIcon />
|
|
Error
|
|
</Space>
|
|
}
|
|
open={showErrorModal}
|
|
okText='OK'
|
|
style={{ maxWidth: 430 }}
|
|
closable={false}
|
|
centered
|
|
maskClosable={true}
|
|
footer={[
|
|
<Button
|
|
key='retry'
|
|
onClick={() => {
|
|
setShowErrorModal(false)
|
|
}}
|
|
>
|
|
Close
|
|
</Button>,
|
|
<Button key='retry' icon={<ReloadIcon />} onClick={handleRetry}>
|
|
Retry
|
|
</Button>
|
|
]}
|
|
>
|
|
{errorModalContent}
|
|
</Modal>
|
|
</ApiServerContext.Provider>
|
|
)
|
|
}
|
|
|
|
ApiServerProvider.propTypes = {
|
|
children: PropTypes.node.isRequired
|
|
}
|
|
|
|
export { ApiServerContext, ApiServerProvider }
|