Enhance Dashboard UI and Status Indicators
- Added a Popover component to provide information about Backend & Tabs status. - Improved layout and styling of the System Status section, including connection status indicators. - Updated PLC connection buttons for better user experience. - Refactored CPU Status and UDP Streaming cards for consistency and clarity. - Adjusted performance metrics display with improved loading states and error handling. - Updated last_update timestamp in system_state.json for accurate tracking.
This commit is contained in:
parent
4eed5d2687
commit
f979817876
37640
application_events.json
37640
application_events.json
File diff suppressed because it is too large
Load Diff
|
@ -51,9 +51,17 @@ import {
|
|||
ModalHeader,
|
||||
ModalFooter,
|
||||
ModalBody,
|
||||
ModalCloseButton
|
||||
ModalCloseButton,
|
||||
IconButton,
|
||||
Popover,
|
||||
PopoverTrigger,
|
||||
PopoverContent,
|
||||
PopoverArrow,
|
||||
PopoverCloseButton,
|
||||
PopoverHeader,
|
||||
PopoverBody
|
||||
} from '@chakra-ui/react'
|
||||
import { ChevronDownIcon, ChevronUpIcon } from '@chakra-ui/icons'
|
||||
import { ChevronDownIcon, ChevronUpIcon, InfoIcon } from '@chakra-ui/icons'
|
||||
import { FiUpload } from 'react-icons/fi'
|
||||
import Form from '@rjsf/chakra-ui'
|
||||
import validator from '@rjsf/validator-ajv8'
|
||||
|
@ -915,208 +923,226 @@ function StatusBar({ status, isConnected, isLeader, connectionError }) {
|
|||
}
|
||||
|
||||
return (
|
||||
<SimpleGrid columns={{ base: 1, md: 2, lg: 3, xl: 5 }} spacing={4} mb={6}>
|
||||
<Card>
|
||||
<CardBody>
|
||||
<Stat>
|
||||
<StatLabel>🔌 {t('status.plcConnection')}</StatLabel>
|
||||
<StatNumber fontSize="lg" color={plcConnected ? 'green.500' : 'red.500'}>
|
||||
{plcConnected ? t('status.connected') : t('status.disconnected')}
|
||||
</StatNumber>
|
||||
{status?.plc_reconnection?.enabled && (
|
||||
<StatHelpText>
|
||||
🔄 {t('status.autoReconnection')}: {status?.plc_reconnection?.active ? t('status.reconnecting') : t('status.enabled')}
|
||||
</StatHelpText>
|
||||
)}
|
||||
<Box mt={2}>
|
||||
{plcConnected ? (
|
||||
<Button
|
||||
size="sm"
|
||||
colorScheme="red"
|
||||
variant="outline"
|
||||
onClick={handleDisconnectPlc}
|
||||
isLoading={actionLoading.disconnect}
|
||||
loadingText={t('status.disconnecting')}
|
||||
>
|
||||
❌ {t('status.disconnect')}
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
size="sm"
|
||||
colorScheme="blue"
|
||||
variant="outline"
|
||||
onClick={handleConnectPlc}
|
||||
isLoading={actionLoading.connect}
|
||||
loadingText={t('status.connecting')}
|
||||
>
|
||||
🔗 {t('status.connect')}
|
||||
</Button>
|
||||
)}
|
||||
</Box>
|
||||
</Stat>
|
||||
</CardBody>
|
||||
</Card>
|
||||
|
||||
{/* CPU Status Card - Shows when PLC is connected */}
|
||||
{plcConnected && cpuStatus && (
|
||||
<Card>
|
||||
<CardBody>
|
||||
<Stat>
|
||||
<StatLabel>🖥️ CPU Status</StatLabel>
|
||||
<StatNumber fontSize="lg" color={cpuStatus.state === 'RUN' ? 'green.500' : 'orange.500'}>
|
||||
{cpuStatus.state || 'UNKNOWN'}
|
||||
</StatNumber>
|
||||
{cpuStatus.cycle_time_ms !== undefined && (
|
||||
<StatHelpText>
|
||||
⏱️ Cycle: {cpuStatus.cycle_time_ms}ms
|
||||
{cpuStatus.cpu_info?.module_type_name && (
|
||||
<><br/>📟 {cpuStatus.cpu_info.module_type_name}</>
|
||||
<Box mb={6}>
|
||||
<Flex align="center" justify="space-between" mb={4}>
|
||||
<Text fontSize="lg" fontWeight="bold">System Status</Text>
|
||||
<Popover>
|
||||
<PopoverTrigger>
|
||||
<IconButton
|
||||
aria-label="Backend & Tabs Info"
|
||||
icon={<InfoIcon />}
|
||||
size="sm"
|
||||
variant="outline"
|
||||
colorScheme="blue"
|
||||
/>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent>
|
||||
<PopoverArrow />
|
||||
<PopoverCloseButton />
|
||||
<PopoverHeader fontWeight="bold">🔗 Backend & Tabs</PopoverHeader>
|
||||
<PopoverBody>
|
||||
<VStack spacing={2} align="stretch">
|
||||
<Text fontSize="md" fontWeight="bold" color={
|
||||
connectionError ? 'red.500' :
|
||||
isConnected ? 'green.500' : 'orange.500'
|
||||
}>
|
||||
{connectionError ? '❌ Backend offline' :
|
||||
isLeader ? `👑 ${t('status.leader')}` : `👥 ${t('status.follower')}`}
|
||||
</Text>
|
||||
<Text fontSize="sm" color="gray.600">
|
||||
{connectionError ? (
|
||||
<>❌ Backend connection lost<br/>🔄 Retrying connection...</>
|
||||
) : (
|
||||
<>
|
||||
{isConnected ? `✅ ${t('status.connected_status')}` : `⏳ ${t('status.connecting_status')}`}<br/>
|
||||
{isLeader ? t('status.makingConnections') : t('status.receivingData')}
|
||||
</>
|
||||
)}
|
||||
</Text>
|
||||
</VStack>
|
||||
</PopoverBody>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</Flex>
|
||||
|
||||
<SimpleGrid columns={{ base: 1, sm: 2, md: 3, lg: 4, xl: 5 }} spacing={2} mb={6}>
|
||||
<Card size="sm">
|
||||
<CardBody py={3}>
|
||||
<Stat>
|
||||
<StatLabel fontSize="sm">🔌 {t('status.plcConnection')}</StatLabel>
|
||||
<StatNumber fontSize="md" color={plcConnected ? 'green.500' : 'red.500'}>
|
||||
{plcConnected ? t('status.connected') : t('status.disconnected')}
|
||||
</StatNumber>
|
||||
{status?.plc_reconnection?.enabled && (
|
||||
<StatHelpText fontSize="xs">
|
||||
🔄 {t('status.autoReconnection')}: {status?.plc_reconnection?.active ? t('status.reconnecting') : t('status.enabled')}
|
||||
</StatHelpText>
|
||||
)}
|
||||
<Box mt={2}>
|
||||
{plcConnected ? (
|
||||
<Button
|
||||
size="xs"
|
||||
colorScheme="red"
|
||||
variant="outline"
|
||||
onClick={handleDisconnectPlc}
|
||||
isLoading={actionLoading.disconnect}
|
||||
loadingText={t('status.disconnecting')}
|
||||
>
|
||||
❌ {t('status.disconnect')}
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
size="xs"
|
||||
colorScheme="blue"
|
||||
variant="outline"
|
||||
onClick={handleConnectPlc}
|
||||
isLoading={actionLoading.connect}
|
||||
loadingText={t('status.connecting')}
|
||||
>
|
||||
🔗 {t('status.connect')}
|
||||
</Button>
|
||||
)}
|
||||
</Box>
|
||||
</Stat>
|
||||
</CardBody>
|
||||
</Card>
|
||||
|
||||
{/* CPU Status Card - Shows when PLC is connected */}
|
||||
{plcConnected && cpuStatus && (
|
||||
<Card size="sm">
|
||||
<CardBody py={3}>
|
||||
<Stat>
|
||||
<StatLabel fontSize="sm">🖥️ CPU Status</StatLabel>
|
||||
<StatNumber fontSize="sm" color={cpuStatus.state === 'RUN' || cpuStatus.state?.includes('Run') ? 'green.500' : 'orange.500'}>
|
||||
{cpuStatus.state?.replace('STATE_S7CpuStatus', '').toUpperCase() || 'UNKNOWN'}
|
||||
</StatNumber>
|
||||
{cpuStatus.cycle_time_ms !== undefined && (
|
||||
<StatHelpText fontSize="xs">
|
||||
⏱️ Cycle: {cpuStatus.cycle_time_ms}ms
|
||||
{cpuStatus.cpu_info?.module_type_name && (
|
||||
<><br/>📟 {cpuStatus.cpu_info.module_type_name}</>
|
||||
)}
|
||||
</StatHelpText>
|
||||
)}
|
||||
</Stat>
|
||||
</CardBody>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
<Card size="sm">
|
||||
<CardBody py={3}>
|
||||
<Stat>
|
||||
<StatLabel fontSize="sm">📡 {t('status.udpStreaming')}</StatLabel>
|
||||
<StatNumber fontSize="md" color={streaming ? 'green.500' : 'gray.500'}>
|
||||
{streaming ? t('status.active') : t('status.inactive')}
|
||||
</StatNumber>
|
||||
<Box mt={2}>
|
||||
<VStack spacing={1} align="stretch">
|
||||
{streaming ? (
|
||||
<Button
|
||||
size="xs"
|
||||
colorScheme="red"
|
||||
variant="outline"
|
||||
onClick={handleStopStreaming}
|
||||
isLoading={actionLoading.stopStream}
|
||||
loadingText={t('status.stopping')}
|
||||
>
|
||||
⏹️ {t('status.stop')}
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
size="xs"
|
||||
colorScheme="blue"
|
||||
variant="outline"
|
||||
onClick={handleStartStreaming}
|
||||
isLoading={actionLoading.startStream}
|
||||
loadingText={t('status.starting')}
|
||||
>
|
||||
▶️ {t('status.start')}
|
||||
</Button>
|
||||
)}
|
||||
{plotJugglerFound && (
|
||||
<Button
|
||||
size="xs"
|
||||
colorScheme="green"
|
||||
variant="outline"
|
||||
onClick={handleLaunchPlotJuggler}
|
||||
isLoading={actionLoading.launchPlotJuggler}
|
||||
loadingText={t('status.launching')}
|
||||
title="Launch PlotJuggler with UDP streamer configured"
|
||||
>
|
||||
🚀 {t('status.plotJuggler')}
|
||||
</Button>
|
||||
)}
|
||||
{!plotJugglerFound && (
|
||||
<Text fontSize="xs" color="gray.500" textAlign="center">
|
||||
{t('status.notFound')}
|
||||
</Text>
|
||||
)}
|
||||
</VStack>
|
||||
</Box>
|
||||
</Stat>
|
||||
</CardBody>
|
||||
</Card>
|
||||
|
||||
<Card size="sm">
|
||||
<CardBody py={3}>
|
||||
<Stat>
|
||||
<StatLabel fontSize="sm">💾 {t('status.csvRecording')}</StatLabel>
|
||||
<StatNumber fontSize="md" color={csvRecording ? 'green.500' : 'gray.500'}>
|
||||
{csvRecording ? t('status.recording') : t('status.inactive')}
|
||||
</StatNumber>
|
||||
{status?.disk_space_info && (
|
||||
<StatHelpText fontSize="xs">
|
||||
💽 {status.disk_space_info.free_space} {t('status.free')}<br/>
|
||||
⏱️ ~{status.disk_space_info.recording_time_left}
|
||||
</StatHelpText>
|
||||
)}
|
||||
</Stat>
|
||||
</CardBody>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
<Card>
|
||||
<CardBody>
|
||||
<Stat>
|
||||
<StatLabel>📡 {t('status.udpStreaming')}</StatLabel>
|
||||
<StatNumber fontSize="lg" color={streaming ? 'green.500' : 'gray.500'}>
|
||||
{streaming ? t('status.active') : t('status.inactive')}
|
||||
</StatNumber>
|
||||
<Box mt={2}>
|
||||
<VStack spacing={2} align="stretch">
|
||||
{streaming ? (
|
||||
<Button
|
||||
size="sm"
|
||||
colorScheme="red"
|
||||
variant="outline"
|
||||
onClick={handleStopStreaming}
|
||||
isLoading={actionLoading.stopStream}
|
||||
loadingText={t('status.stopping')}
|
||||
>
|
||||
⏹️ {t('status.stop')}
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
size="sm"
|
||||
colorScheme="blue"
|
||||
variant="outline"
|
||||
onClick={handleStartStreaming}
|
||||
isLoading={actionLoading.startStream}
|
||||
loadingText={t('status.starting')}
|
||||
>
|
||||
▶️ {t('status.start')}
|
||||
</Button>
|
||||
)}
|
||||
{plotJugglerFound && (
|
||||
<Button
|
||||
size="sm"
|
||||
colorScheme="green"
|
||||
variant="outline"
|
||||
onClick={handleLaunchPlotJuggler}
|
||||
isLoading={actionLoading.launchPlotJuggler}
|
||||
loadingText={t('status.launching')}
|
||||
title="Launch PlotJuggler with UDP streamer configured"
|
||||
>
|
||||
🚀 {t('status.plotJuggler')}
|
||||
</Button>
|
||||
)}
|
||||
{!plotJugglerFound && (
|
||||
<Text fontSize="xs" color="gray.500" textAlign="center">
|
||||
{t('status.notFound')}
|
||||
</Text>
|
||||
)}
|
||||
</VStack>
|
||||
</Box>
|
||||
</Stat>
|
||||
</CardBody>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardBody>
|
||||
<Stat>
|
||||
<StatLabel>💾 {t('status.csvRecording')}</StatLabel>
|
||||
<StatNumber fontSize="lg" color={csvRecording ? 'green.500' : 'gray.500'}>
|
||||
{csvRecording ? t('status.recording') : t('status.inactive')}
|
||||
</StatNumber>
|
||||
{status?.disk_space_info && (
|
||||
<StatHelpText>
|
||||
💽 {status.disk_space_info.free_space} {t('status.free')}<br/>
|
||||
⏱️ ~{status.disk_space_info.recording_time_left}
|
||||
</StatHelpText>
|
||||
)}
|
||||
</Stat>
|
||||
</CardBody>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardBody>
|
||||
<Stat>
|
||||
<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>
|
||||
<Card size="sm">
|
||||
<CardBody py={3}>
|
||||
<Stat>
|
||||
<StatLabel fontSize="sm">📊 Performance</StatLabel>
|
||||
{connectionError ? (
|
||||
<>❌ Backend connection lost<br/>🔄 Retrying connection...</>
|
||||
) : (
|
||||
<StatNumber fontSize="sm" color="red.500">
|
||||
Backend disconnected
|
||||
</StatNumber>
|
||||
) : performanceLoading ? (
|
||||
<Flex align="center" justify="center" py={1}>
|
||||
<Spinner size="sm" mr={2} />
|
||||
<Text fontSize="xs">Loading...</Text>
|
||||
</Flex>
|
||||
) : performanceData ? (
|
||||
<>
|
||||
{isConnected ? `✅ ${t('status.connected_status')}` : `⏳ ${t('status.connecting_status')}`}<br/>
|
||||
{isLeader ? t('status.makingConnections') : t('status.receivingData')}
|
||||
<StatNumber fontSize="md" 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 fontSize="xs">
|
||||
🧠 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>
|
||||
)}
|
||||
</StatHelpText>
|
||||
</Stat>
|
||||
</CardBody>
|
||||
</Card>
|
||||
</SimpleGrid>
|
||||
</Stat>
|
||||
</CardBody>
|
||||
</Card>
|
||||
</SimpleGrid>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -7,6 +7,6 @@
|
|||
]
|
||||
},
|
||||
"auto_recovery_enabled": true,
|
||||
"last_update": "2025-08-28T13:30:03.511773",
|
||||
"last_update": "2025-08-28T15:37:08.750644",
|
||||
"plotjuggler_path": "C:\\Program Files\\PlotJuggler\\plotjuggler.exe"
|
||||
}
|
Loading…
Reference in New Issue