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:
parent
ca6d39cb06
commit
1ecb8c71ee
20931
application_events.json
20931
application_events.json
File diff suppressed because it is too large
Load Diff
|
@ -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>
|
||||
|
|
|
@ -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' } })
|
||||
|
|
|
@ -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"
|
||||
}
|
Loading…
Reference in New Issue