feat: Enhance coordinated polling with error handling and performance monitoring
- Updated `useCoordinatedPolling` to track connection errors and stop polling after consecutive failures. - Modified `StatusBar` component to display connection status and performance data, including loading states and error messages. - Implemented performance data fetching with automatic updates every 10 seconds when conditions are met. - Added new API functions for retrieving current and historical performance data. - Adjusted `system_state.json` to change the order of active datasets and updated the last update timestamp.
This commit is contained in:
parent
030b691064
commit
3015fe4391
File diff suppressed because it is too large
Load Diff
|
@ -104,21 +104,37 @@ export function useCoordinatedConnection(source, connectionFactory, dependencies
|
|||
* Hook simplificado para polling coordinado
|
||||
*/
|
||||
export function useCoordinatedPolling(source, fetchFunction, interval = 5000, dependencies = []) {
|
||||
return useCoordinatedConnection(
|
||||
const [connectionError, setConnectionError] = useState(null)
|
||||
|
||||
const result = useCoordinatedConnection(
|
||||
source,
|
||||
useCallback((onData) => {
|
||||
let intervalId = null
|
||||
let isActive = true
|
||||
let consecutiveErrors = 0
|
||||
const maxConsecutiveErrors = 3
|
||||
|
||||
const poll = async () => {
|
||||
if (!isActive) return
|
||||
try {
|
||||
const data = await fetchFunction()
|
||||
if (isActive) {
|
||||
consecutiveErrors = 0
|
||||
setConnectionError(null)
|
||||
onData(data)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Polling error for ${source}:`, error)
|
||||
consecutiveErrors++
|
||||
|
||||
if (consecutiveErrors >= maxConsecutiveErrors) {
|
||||
setConnectionError(error)
|
||||
// Stop polling after too many consecutive errors
|
||||
if (intervalId) {
|
||||
clearInterval(intervalId)
|
||||
intervalId = null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -139,6 +155,8 @@ export function useCoordinatedPolling(source, fetchFunction, interval = 5000, de
|
|||
}, [fetchFunction, interval]),
|
||||
dependencies
|
||||
)
|
||||
|
||||
return { ...result, connectionError }
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -683,19 +683,63 @@ function CollapsibleArrayForm({ data, schema, uiSchema, onSave, title, icon, get
|
|||
}
|
||||
|
||||
// StatusBar Component - Real-time PLC status with action buttons
|
||||
function StatusBar({ status, isConnected, isLeader }) {
|
||||
function StatusBar({ status, isConnected, isLeader, connectionError }) {
|
||||
const { t } = useTranslation()
|
||||
const plcConnected = !!status?.plc_connected
|
||||
const streaming = !!status?.streaming
|
||||
const csvRecording = !!status?.csv_recording
|
||||
// If there's a connection error, treat everything as disconnected
|
||||
const plcConnected = connectionError ? false : !!status?.plc_connected
|
||||
const streaming = connectionError ? false : !!status?.streaming
|
||||
const csvRecording = connectionError ? false : !!status?.csv_recording
|
||||
const [actionLoading, setActionLoading] = useState({})
|
||||
const [plotJugglerFound, setPlotJugglerFound] = useState(false)
|
||||
const [performanceData, setPerformanceData] = useState(null)
|
||||
const [performanceLoading, setPerformanceLoading] = useState(false)
|
||||
const toast = useToast()
|
||||
|
||||
const setLoading = (action, loading) => {
|
||||
setActionLoading(prev => ({ ...prev, [action]: loading }))
|
||||
}
|
||||
|
||||
// Load performance data
|
||||
const loadPerformanceData = useCallback(async () => {
|
||||
if (!plcConnected || !csvRecording) {
|
||||
setPerformanceData(null)
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
setPerformanceLoading(true)
|
||||
const response = await api.getCurrentPerformance()
|
||||
if (response.success) {
|
||||
setPerformanceData(response.current_performance)
|
||||
}
|
||||
} catch (error) {
|
||||
// Silently fail - performance data is optional
|
||||
console.warn('Failed to load performance data:', error)
|
||||
setPerformanceData(null)
|
||||
} finally {
|
||||
setPerformanceLoading(false)
|
||||
}
|
||||
}, [plcConnected, csvRecording])
|
||||
|
||||
// Load performance data when CSV recording is active
|
||||
useEffect(() => {
|
||||
if (plcConnected && csvRecording && !connectionError) {
|
||||
loadPerformanceData()
|
||||
// Set up interval to refresh performance data every 10 seconds
|
||||
const interval = setInterval(loadPerformanceData, 10000)
|
||||
return () => clearInterval(interval)
|
||||
} else {
|
||||
setPerformanceData(null)
|
||||
}
|
||||
}, [plcConnected, csvRecording, connectionError, loadPerformanceData])
|
||||
|
||||
// Clear performance data when there's a connection error
|
||||
useEffect(() => {
|
||||
if (connectionError) {
|
||||
setPerformanceData(null)
|
||||
}
|
||||
}, [connectionError])
|
||||
|
||||
// Check if PlotJuggler is available on component mount
|
||||
useEffect(() => {
|
||||
const checkPlotJuggler = async () => {
|
||||
|
@ -831,7 +875,7 @@ function StatusBar({ status, isConnected, isLeader }) {
|
|||
}
|
||||
|
||||
return (
|
||||
<SimpleGrid columns={{ base: 1, md: 2, lg: 4 }} spacing={4} mb={6}>
|
||||
<SimpleGrid columns={{ base: 1, md: 2, lg: 3, xl: 5 }} spacing={4} mb={6}>
|
||||
<Card>
|
||||
<CardBody>
|
||||
<Stat>
|
||||
|
@ -949,13 +993,63 @@ function StatusBar({ status, isConnected, isLeader }) {
|
|||
<Card>
|
||||
<CardBody>
|
||||
<Stat>
|
||||
<StatLabel>🔗 {t('status.tabCoordination')}</StatLabel>
|
||||
<StatNumber fontSize="lg" color={isConnected ? 'green.500' : 'orange.500'}>
|
||||
{isLeader ? `👑 ${t('status.leader')}` : `👥 ${t('status.follower')}`}
|
||||
<StatLabel>📊 Performance</StatLabel>
|
||||
{connectionError ? (
|
||||
<StatNumber fontSize="sm" color="red.500">
|
||||
Backend disconnected
|
||||
</StatNumber>
|
||||
) : performanceLoading ? (
|
||||
<Flex align="center" justify="center" py={2}>
|
||||
<Spinner size="sm" mr={2} />
|
||||
<Text fontSize="sm">Loading...</Text>
|
||||
</Flex>
|
||||
) : performanceData ? (
|
||||
<>
|
||||
<StatNumber fontSize="lg" color={
|
||||
performanceData.points_lost > 0 ? 'red.500' :
|
||||
performanceData.cpu_avg > 50 ? 'orange.500' : 'green.500'
|
||||
}>
|
||||
{performanceData.points_rate?.toFixed(1) || '0'} pts/s
|
||||
</StatNumber>
|
||||
<StatHelpText>
|
||||
🧠 CPU: {performanceData.cpu_avg?.toFixed(1) || '0'}%<br/>
|
||||
📦 Lost: {performanceData.points_lost || 0}<br/>
|
||||
⚠️ Errors: {(performanceData.read_errors || 0) + (performanceData.csv_errors || 0) + (performanceData.udp_errors || 0)}
|
||||
</StatHelpText>
|
||||
</>
|
||||
) : (plcConnected && csvRecording) ? (
|
||||
<StatNumber fontSize="sm" color="orange.500">
|
||||
No data
|
||||
</StatNumber>
|
||||
) : (
|
||||
<StatNumber fontSize="sm" color="gray.500">
|
||||
Inactive
|
||||
</StatNumber>
|
||||
)}
|
||||
</Stat>
|
||||
</CardBody>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardBody>
|
||||
<Stat>
|
||||
<StatLabel>🔗 Backend & Tabs</StatLabel>
|
||||
<StatNumber fontSize="lg" color={
|
||||
connectionError ? 'red.500' :
|
||||
isConnected ? 'green.500' : 'orange.500'
|
||||
}>
|
||||
{connectionError ? '❌ Backend offline' :
|
||||
isLeader ? `👑 ${t('status.leader')}` : `👥 ${t('status.follower')}`}
|
||||
</StatNumber>
|
||||
<StatHelpText>
|
||||
{isConnected ? `✅ ${t('status.connected_status')}` : `⏳ ${t('status.connecting_status')}`}<br/>
|
||||
{isLeader ? t('status.makingConnections') : t('status.receivingData')}
|
||||
{connectionError ? (
|
||||
<>❌ Backend connection lost<br/>🔄 Retrying connection...</>
|
||||
) : (
|
||||
<>
|
||||
{isConnected ? `✅ ${t('status.connected_status')}` : `⏳ ${t('status.connecting_status')}`}<br/>
|
||||
{isLeader ? t('status.makingConnections') : t('status.receivingData')}
|
||||
</>
|
||||
)}
|
||||
</StatHelpText>
|
||||
</Stat>
|
||||
</CardBody>
|
||||
|
@ -1772,7 +1866,7 @@ function DashboardContent() {
|
|||
const [consoleLogsLoading, setConsoleLogsLoading] = useState(false)
|
||||
|
||||
// Usar polling coordinado para el status
|
||||
const { data: status, isLeader, isConnected } = useCoordinatedPolling(
|
||||
const { data: status, isLeader, isConnected, connectionError } = useCoordinatedPolling(
|
||||
'dashboard_status',
|
||||
api.getStatus,
|
||||
5000 // 5 segundos
|
||||
|
@ -1855,7 +1949,7 @@ function DashboardContent() {
|
|||
)}
|
||||
</Flex>
|
||||
|
||||
<StatusBar status={status} isConnected={isConnected} isLeader={isLeader} />
|
||||
<StatusBar status={status} isConnected={isConnected} isLeader={isLeader} connectionError={connectionError} />
|
||||
|
||||
<Tabs variant="enclosed" colorScheme="orange" defaultIndex={2}>
|
||||
<TabList>
|
||||
|
|
|
@ -348,4 +348,19 @@ export async function getConfigSchema(schemaId) {
|
|||
return toJsonOrThrow(res)
|
||||
}
|
||||
|
||||
// Performance Monitoring API
|
||||
export async function getCurrentPerformance() {
|
||||
const res = await fetch(`${BASE_URL}/api/performance/current`, {
|
||||
headers: { 'Accept': 'application/json' }
|
||||
})
|
||||
return toJsonOrThrow(res)
|
||||
}
|
||||
|
||||
export async function getHistoricalPerformance(windows = 6) {
|
||||
const res = await fetch(`${BASE_URL}/api/performance/historical?windows=${encodeURIComponent(windows)}`, {
|
||||
headers: { 'Accept': 'application/json' }
|
||||
})
|
||||
return toJsonOrThrow(res)
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -3,12 +3,12 @@
|
|||
"should_connect": true,
|
||||
"should_stream": false,
|
||||
"active_datasets": [
|
||||
"DAR",
|
||||
"Fast",
|
||||
"Test"
|
||||
"Test",
|
||||
"DAR"
|
||||
]
|
||||
},
|
||||
"auto_recovery_enabled": true,
|
||||
"last_update": "2025-08-17T00:10:20.359642",
|
||||
"last_update": "2025-08-17T10:38:11.919417",
|
||||
"plotjuggler_path": "C:\\Program Files\\PlotJuggler\\plotjuggler.exe"
|
||||
}
|
Loading…
Reference in New Issue