Skip to content

Commit

Permalink
fix: Implement basic log filtering
Browse files Browse the repository at this point in the history
  • Loading branch information
mman committed Nov 28, 2024
1 parent 8b60d91 commit e0bff62
Show file tree
Hide file tree
Showing 2 changed files with 115 additions and 49 deletions.
26 changes: 23 additions & 3 deletions src/client/views/dashboard/Dashboard.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import React from "react"
import { useSelector } from "react-redux"
import { useNavigate } from "react-router-dom"
import {
CCard,
CCardBody,
Expand All @@ -13,6 +15,7 @@ import {
CTableRow,
CContainer,
CBadge,
CLink,
} from "@coreui/react"

import CIcon from "@coreui/icons-react"
Expand Down Expand Up @@ -44,6 +47,16 @@ function Dashboard() {
return <WebSocketStatus websocketStatus={websocketStatus} />
}

const navigate = useNavigate()

const handleLabelFilterChange = (
event: React.MouseEvent<HTMLAnchorElement | HTMLButtonElement, MouseEvent>,
label: string,
) => {
event.preventDefault()
navigate("../troubleshooting", { replace: true, state: { filter: label } })
}

return (
<>
<CCard>
Expand Down Expand Up @@ -75,6 +88,7 @@ function Dashboard() {
<CTableBody>
{deviceKeys.map((key) => {
const deviceStats = deviceStatistics[key]
const logLabel = `${deviceStats.type}:${deviceStats.address}`
return (
<CTableRow key={key}>
<CTableDataCell>
Expand All @@ -91,9 +105,15 @@ function Dashboard() {
{deviceStats.name || deviceStats.address}
&nbsp; &nbsp;
</strong>
<CBadge textBgColor="light" textColor="secondary" shape="rounded-pill">
{deviceStats.type} {deviceStats.address}
</CBadge>
<CLink
href="#"
onClick={(event) => handleLabelFilterChange(event, logLabel)}
className="text-decoration-none"
>
<CBadge textBgColor="light" textColor="secondary" shape="rounded-pill">
{deviceStats.type} {deviceStats.address}
</CBadge>
</CLink>
&nbsp; &nbsp;
{deviceStats.expiry !== undefined && deviceStats.expiry > 0 && (
<CBadge textBgColor="light" textColor="secondary" shape="rounded-pill">
Expand Down
138 changes: 92 additions & 46 deletions src/client/views/troubleshooting/Troubleshooting.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useSelector } from "react-redux"
import { useLocation } from "react-router-dom"
import {
CCard,
CCardBody,
Expand All @@ -14,6 +15,7 @@ import {
CTableRow,
CBadge,
CCardFooter,
CLink,
} from "@coreui/react"

import { usePutDebug } from "../../hooks/useAdminApi"
Expand All @@ -34,13 +36,27 @@ function Troubleshooting() {
return <WebSocketStatus websocketStatus={websocketStatus} />
}

const location = useLocation()
const { filter } = location.state || {}

// label filter
const [labelFilter, setLabelFilter] = useState<string | undefined>(filter)

const handleLabelFilterChange = (label?: string) => {
if (label === labelFilter) {
setLabelFilter(undefined)
} else {
setLabelFilter(label)
}
}

// scroll to bottom on new messages
const containerRef = useRef<HTMLDivElement>(null)
useEffect(() => {
if (containerRef.current) {
containerRef.current.scrollTop = containerRef.current.scrollHeight
}
}, [log])
}, [log, labelFilter])

// re-render every 5 seconds to update timestamps
const [lastUpdated, setLastUpdated] = useState<Date>(new Date())
Expand All @@ -58,82 +74,112 @@ function Troubleshooting() {
<CCard className="flex-grow-1 d-flex flex-column">
<CCardHeader>
<CForm>
<CInputGroup>
<CInputGroup className="d-flex align-items-center">
<CFormCheck
id="debug"
label="Enable debugging"
checked={isDebugLevelEnabled}
onChange={(event) => toggleDebugLevel({ data: { debug: event.target.checked } })}
className="flex-grow-1"
/>
{labelFilter && (
<CLink
href="#troubleshooting"
onClick={() => handleLabelFilterChange()}
className="text-decoration-none"
>
<CBadge color="primary" shape="rounded-pill" size="sm">
{labelFilter}
</CBadge>
</CLink>
)}
</CInputGroup>
</CForm>
</CCardHeader>
<CCardBody className="d-flex flex-column p-0" style={{ height: "1rem" }}>
<div className="overflow-auto always-scroll" ref={containerRef}>
<LogList entries={log.entries} />
<LogList
entries={log.entries}
labelFilter={labelFilter}
handleLabelFilterChange={handleLabelFilterChange}
/>
</div>
</CCardBody>
<CCardFooter>Last update: {lastUpdated.toLocaleString()}</CCardFooter>
<CCardFooter className="text-secondary">Last update: {lastUpdated.toLocaleString()}</CCardFooter>
</CCard>
)
)
}

interface LogListProps {
entries: LogEntry[]
labelFilter?: string
handleLabelFilterChange: (_label?: string) => void
}

function LogList(props: LogListProps) {
return (
<CTable hover className="m-0 mb-3 p-0 lh-1">
<CTableHead color="primary" style={{ position: "sticky", top: "0" }}>
<CTableRow>
<CTableHeaderCell>Time</CTableHeaderCell>
<CTableHeaderCell>Label</CTableHeaderCell>
<CTableHeaderCell>Message</CTableHeaderCell>
<CTableHeaderCell className="small text-nowrap" style={{ width: "1%" }}>
Time
</CTableHeaderCell>
<CTableHeaderCell className="small text-nowrap" style={{ width: "8rem" }}>
Label
</CTableHeaderCell>
<CTableHeaderCell className="small">Message</CTableHeaderCell>
</CTableRow>
</CTableHead>
<CTableBody>
{props.entries &&
props.entries.map((entry, index) => {
let levelColor
if (entry.level === "error") {
levelColor = "danger"
} else if (entry.level === "info") {
levelColor = "info"
} else if (entry.level === "warn") {
levelColor = "warning"
} else {
levelColor = "success"
}

let timeStamp = new Date(entry.timestamp)
let relative = timeStamp.getTime() - Date.now()

return (
<CTableRow key={index}>
<CTableDataCell>
<CBadge shape="rounded-pill" color={levelColor} size="sm" className="me-1">
{ms(relative)}
</CBadge>
<CBadge shape="rounded-pill" color={levelColor} size="sm" className="me-1">
{entry.level}
</CBadge>
<CBadge textBgColor="light" textColor="secondary" shape="rounded-pill" size="sm">
{entry.timestamp}
</CBadge>
</CTableDataCell>
<CTableDataCell>
<CBadge textBgColor="light" textColor="secondary" shape="rounded-pill" size="sm">
{entry.label}
</CBadge>
</CTableDataCell>
<CTableDataCell>
<div className="small text-body-primary">{entry.message}</div>
</CTableDataCell>
</CTableRow>
)
})}
props.entries
.filter((entry) => (props.labelFilter ? entry.label == props.labelFilter : entry))
.map((entry, index) => {
let levelColor
if (entry.level === "error") {
levelColor = "danger"
} else if (entry.level === "info") {
levelColor = "info"
} else if (entry.level === "warn") {
levelColor = "warning"
} else {
levelColor = "success"
}

let timeStamp = new Date(entry.timestamp)
let relative = timeStamp.getTime() - Date.now()

return (
<CTableRow key={index}>
<CTableDataCell>
<CBadge shape="rounded-pill" color={levelColor} size="sm" className="me-1">
{ms(relative)}
</CBadge>
<CBadge shape="rounded-pill" color={levelColor} size="sm" className="me-1">
{entry.level}
</CBadge>
<CBadge textBgColor="light" textColor="secondary" shape="rounded-pill" size="sm">
{entry.timestamp}
</CBadge>
</CTableDataCell>
<CTableDataCell>
<CLink
href="#troubleshooting"
onClick={() => props.handleLabelFilterChange(entry.label)}
className="text-decoration-none"
>
<CBadge textBgColor="light" textColor="secondary" shape="rounded-pill" size="sm">
{entry.label}
</CBadge>
</CLink>
</CTableDataCell>
<CTableDataCell>
<div className="small text-body-primary">{entry.message}</div>
</CTableDataCell>
</CTableRow>
)
})}
</CTableBody>
</CTable>
)
Expand Down

0 comments on commit e0bff62

Please sign in to comment.