feat: Add console logs display and export functionality

- Introduced ConsoleLogsDisplay component to replace EventsDisplay for showing console logs.
- Implemented log export feature to download console logs as a text file.
- Updated API to fetch console logs from the backend.
- Modified DashboardContent to load and display console logs instead of events.
- Adjusted UI elements for better user experience with logs.
- Added new endpoint in the backend to retrieve recent console logs.
- Updated system state and configuration files accordingly.
This commit is contained in:
Miguel 2025-08-16 23:55:27 +02:00
parent ca6d39cb06
commit 1ecb8c71ee
7 changed files with 11054 additions and 10725 deletions

File diff suppressed because it is too large Load Diff

View File

@ -1621,9 +1621,31 @@ function DatasetManager() {
)
}
// Events Display Component
function EventsDisplay({ events, loading, onRefresh }) {
// Console Logs Display Component
function ConsoleLogsDisplay({ logs, loading, onRefresh }) {
// All hooks must be called at the top level
const cardBg = useColorModeValue('white', 'gray.700')
const logBg = useColorModeValue('gray.50', 'gray.800')
const logColor = useColorModeValue('gray.800', 'gray.100')
const borderColor = useColorModeValue('gray.200', 'gray.600')
const scrollbarBg = useColorModeValue('gray.300', 'gray.600')
const scrollbarThumb = useColorModeValue('gray.500', 'gray.400')
const scrollbarHover = useColorModeValue('gray.600', 'gray.300')
const exportLogs = () => {
if (!logs || logs.length === 0) {
return
}
const logText = logs.join('\n')
const blob = new Blob([logText], { type: 'text/plain' })
const url = URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = `console_logs_${new Date().toISOString().slice(0, 19).replace(/:/g, '-')}.txt`
link.click()
URL.revokeObjectURL(url)
}
if (loading) {
return (
@ -1631,7 +1653,7 @@ function EventsDisplay({ events, loading, onRefresh }) {
<CardBody>
<Flex align="center" justify="center" py={4}>
<Spinner mr={3} />
<Text>Loading events...</Text>
<Text>Loading console logs...</Text>
</Flex>
</CardBody>
</Card>
@ -1642,51 +1664,80 @@ function EventsDisplay({ events, loading, onRefresh }) {
<Card bg={cardBg}>
<CardHeader>
<Flex align="center">
<Heading size="md">📋 Recent Events</Heading>
<Heading size="md">📋 Console Logs</Heading>
<Spacer />
<Button size="sm" variant="outline" onClick={onRefresh}>
🔄 Refresh
</Button>
<HStack spacing={2}>
<Button
size="sm"
variant="outline"
onClick={exportLogs}
isDisabled={!logs || logs.length === 0}
>
📄 Export
</Button>
<Button size="sm" variant="outline" onClick={onRefresh}>
🔄 Refresh
</Button>
</HStack>
</Flex>
</CardHeader>
<CardBody>
<TableContainer>
<Table size="sm">
<Thead>
<Tr>
<Th>Time</Th>
<Th>Type</Th>
<Th>Message</Th>
</Tr>
</Thead>
<Tbody>
{events?.map((event, index) => (
<Tr key={index}>
<Td>
<Code fontSize="xs">
{new Date(event.timestamp).toLocaleString()}
</Code>
</Td>
<Td>
<Badge
colorScheme={
event.level === 'ERROR' ? 'red' :
event.level === 'WARNING' ? 'orange' :
event.level === 'INFO' ? 'blue' : 'gray'
}
>
{event.level}
</Badge>
</Td>
<Td>{event.message}</Td>
</Tr>
))}
</Tbody>
</Table>
</TableContainer>
{(!events || events.length === 0) && (
{(!logs || logs.length === 0) ? (
<Text textAlign="center" py={4} color="gray.500">
No events found
No console logs found
</Text>
) : (
<Box
bg={logBg}
border="1px solid"
borderColor={borderColor}
borderRadius="md"
p={4}
height="600px"
overflowY="scroll"
fontFamily="mono"
fontSize="sm"
lineHeight="1.4"
color={logColor}
sx={{
'&::-webkit-scrollbar': {
width: '12px',
display: 'block !important'
},
'&::-webkit-scrollbar-track': {
background: scrollbarBg,
borderRadius: '6px'
},
'&::-webkit-scrollbar-thumb': {
background: scrollbarThumb,
borderRadius: '6px',
border: `2px solid ${scrollbarBg}`
},
'&::-webkit-scrollbar-thumb:hover': {
background: scrollbarHover
},
// Fallback for Firefox
'scrollbarWidth': 'thin',
'scrollbarColor': `${scrollbarThumb} ${scrollbarBg}`
}}
>
{logs.map((logLine, index) => (
<Box
key={index}
py={1}
borderBottom={index < logs.length - 1 ? "1px solid" : "none"}
borderColor={borderColor}
whiteSpace="pre-wrap"
wordBreak="break-word"
>
{logLine}
</Box>
))}
</Box>
)}
{logs && logs.length > 0 && (
<Text fontSize="xs" color="gray.500" mt={2}>
Showing {logs.length} most recent log lines
</Text>
)}
</CardBody>
@ -1711,8 +1762,8 @@ function DashboardContent() {
const [saving, setSaving] = useState(false)
const [message, setMessage] = useState('')
const [events, setEvents] = useState([])
const [eventsLoading, setEventsLoading] = useState(false)
const [consoleLogs, setConsoleLogs] = useState([])
const [consoleLogsLoading, setConsoleLogsLoading] = useState(false)
// Usar polling coordinado para el status
const { data: status, isLeader, isConnected } = useCoordinatedPolling(
@ -1751,24 +1802,24 @@ function DashboardContent() {
}
}, [])
// Load events
const loadEvents = useCallback(async () => {
// Load console logs
const loadConsoleLogs = useCallback(async () => {
try {
setEventsLoading(true)
const eventsData = await api.getEvents(50)
setEvents(eventsData.events || [])
setConsoleLogsLoading(true)
const logsData = await api.getConsoleLogs(1000)
setConsoleLogs(logsData.logs || [])
} catch (error) {
console.error('Failed to load events:', error)
console.error('Failed to load console logs:', error)
} finally {
setEventsLoading(false)
setConsoleLogsLoading(false)
}
}, [])
// Effects
useEffect(() => {
loadConfig()
loadEvents()
}, [loadConfig, loadEvents])
loadConsoleLogs()
}, [loadConfig, loadConsoleLogs])
if (!isConnected && !status) {
return (
@ -1807,7 +1858,7 @@ function DashboardContent() {
<Tab>📈 Plotting</Tab>
<Tab>📉 Historical Plots</Tab>
<Tab>📁 CSV Files</Tab>
<Tab>📋 Events</Tab>
<Tab>📋 Console Logs</Tab>
</TabList>
<TabPanels>
@ -1844,11 +1895,11 @@ function DashboardContent() {
</TabPanel>
<TabPanel p={0} pt={4}>
{/* Events Section */}
<EventsDisplay
events={events}
loading={eventsLoading}
onRefresh={loadEvents}
{/* Console Logs Section */}
<ConsoleLogsDisplay
logs={consoleLogs}
loading={consoleLogsLoading}
onRefresh={loadConsoleLogs}
/>
</TabPanel>
</TabPanels>

View File

@ -35,6 +35,11 @@ export async function getEvents(limit = 50) {
return toJsonOrThrow(res)
}
export async function getConsoleLogs(limit = 1000) {
const res = await fetch(`${BASE_URL}/api/console-logs?limit=${encodeURIComponent(limit)}`, { headers: { 'Accept': 'application/json' } })
return toJsonOrThrow(res)
}
// PLC control
export async function connectPlc() {
const res = await fetch(`${BASE_URL}/api/plc/connect`, { method: 'POST', headers: { 'Accept': 'application/json' } })

0
i18n.js Normal file
View File

666
main.py

File diff suppressed because it is too large Load Diff

View File

@ -4,11 +4,11 @@
"should_stream": false,
"active_datasets": [
"Fast",
"DAR",
"Test"
"Test",
"DAR"
]
},
"auto_recovery_enabled": true,
"last_update": "2025-08-16T21:54:24.471700",
"last_update": "2025-08-16T23:53:59.480554",
"plotjuggler_path": "C:\\Program Files\\PlotJuggler\\plotjuggler.exe"
}

0
translation.json Normal file
View File