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:
Miguel 2025-08-17 10:39:57 +02:00
parent 030b691064
commit 3015fe4391
5 changed files with 3501 additions and 4006 deletions

File diff suppressed because it is too large Load Diff

View File

@ -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 }
}
/**

View File

@ -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>

View File

@ -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)
}

View File

@ -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"
}