Show blocklist import error badge in navigation
When the most recent scheduled import completed with errors, surface the failure in the persistent app shell: - A warning MessageBar appears at top of main content area - An amber badge is rendered on the Blocklists sidebar nav item Backend: add last_run_errors: bool | None to ScheduleInfo model and populate it in get_schedule_info() from the latest import_log row. Frontend: extend ScheduleInfo type, add useBlocklistStatus polling hook, wire both indicators into MainLayout. Tests: 3 new service tests + 1 new router test (433 total, all pass).
This commit is contained in:
@@ -8,6 +8,7 @@
|
||||
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import {
|
||||
Badge,
|
||||
Button,
|
||||
makeStyles,
|
||||
mergeClasses,
|
||||
@@ -31,6 +32,7 @@ import {
|
||||
import { NavLink, Outlet, useNavigate } from "react-router-dom";
|
||||
import { useAuth } from "../providers/AuthProvider";
|
||||
import { useServerStatus } from "../hooks/useServerStatus";
|
||||
import { useBlocklistStatus } from "../hooks/useBlocklist";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Styles
|
||||
@@ -100,6 +102,12 @@ const useStyles = makeStyles({
|
||||
flexGrow: 1,
|
||||
},
|
||||
|
||||
navLinkContent: {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: tokens.spacingHorizontalS,
|
||||
flexGrow: 1,
|
||||
},
|
||||
navLink: {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
@@ -199,6 +207,7 @@ export function MainLayout(): React.JSX.Element {
|
||||
// with the icon-only sidebar rather than the full-width one.
|
||||
const [collapsed, setCollapsed] = useState(() => window.innerWidth < 640);
|
||||
const { status } = useServerStatus();
|
||||
const { hasErrors: blocklistHasErrors } = useBlocklistStatus();
|
||||
|
||||
/** True only after the first successful poll and fail2ban is unreachable. */
|
||||
const serverOffline = status !== null && !status.online;
|
||||
@@ -249,32 +258,45 @@ export function MainLayout(): React.JSX.Element {
|
||||
|
||||
{/* Nav links */}
|
||||
<ul className={styles.navList} role="list" aria-label="Pages">
|
||||
{NAV_ITEMS.map((item) => (
|
||||
<li key={item.to} role="listitem">
|
||||
<Tooltip
|
||||
content={collapsed ? item.label : ""}
|
||||
relationship="label"
|
||||
positioning="after"
|
||||
>
|
||||
<NavLink
|
||||
to={item.to}
|
||||
end={item.end}
|
||||
className={({ isActive }) =>
|
||||
mergeClasses(
|
||||
styles.navLink,
|
||||
isActive && styles.navLinkActive,
|
||||
)
|
||||
}
|
||||
aria-label={collapsed ? item.label : undefined}
|
||||
{NAV_ITEMS.map((item) => {
|
||||
const showBadge = item.to === "/blocklists" && blocklistHasErrors;
|
||||
return (
|
||||
<li key={item.to} role="listitem">
|
||||
<Tooltip
|
||||
content={collapsed ? item.label : ""}
|
||||
relationship="label"
|
||||
positioning="after"
|
||||
>
|
||||
{item.icon}
|
||||
{!collapsed && (
|
||||
<Text className={styles.navLabel}>{item.label}</Text>
|
||||
)}
|
||||
</NavLink>
|
||||
</Tooltip>
|
||||
</li>
|
||||
))}
|
||||
<NavLink
|
||||
to={item.to}
|
||||
end={item.end}
|
||||
className={({ isActive }) =>
|
||||
mergeClasses(
|
||||
styles.navLink,
|
||||
isActive && styles.navLinkActive,
|
||||
)
|
||||
}
|
||||
aria-label={collapsed ? item.label : undefined}
|
||||
>
|
||||
<span className={styles.navLinkContent}>
|
||||
{item.icon}
|
||||
{!collapsed && (
|
||||
<Text className={styles.navLabel}>{item.label}</Text>
|
||||
)}
|
||||
</span>
|
||||
{showBadge && (
|
||||
<Badge
|
||||
appearance="filled"
|
||||
color="warning"
|
||||
size="extra-small"
|
||||
aria-label="Import errors"
|
||||
/>
|
||||
)}
|
||||
</NavLink>
|
||||
</Tooltip>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
|
||||
{/* Footer — Logout */}
|
||||
@@ -313,6 +335,18 @@ export function MainLayout(): React.JSX.Element {
|
||||
</MessageBar>
|
||||
</div>
|
||||
)}
|
||||
{/* Blocklist import error warning — shown when the last scheduled import had errors */}
|
||||
{blocklistHasErrors && (
|
||||
<div className={styles.warningBar} role="alert">
|
||||
<MessageBar intent="warning">
|
||||
<MessageBarBody>
|
||||
<MessageBarTitle>Blocklist Import Errors</MessageBarTitle>
|
||||
The most recent blocklist import encountered errors. Check the
|
||||
Blocklists page for details.
|
||||
</MessageBarBody>
|
||||
</MessageBar>
|
||||
</div>
|
||||
)}
|
||||
<div className={styles.content}>
|
||||
<Outlet />
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user