Notices and status bar improvements (#24459)

* Notice and status bar improvements

Status bar problems added in the same pass got the same `Date.now()` id and overwrote each other, so usually only one showed. Messages now fall back to their text as the id. The desktop status bar shows the most severe message with a count of the rest that opens a popover listing all of them, and the mobile drawer stacks them vertically instead of placing them side by side.

Dismissing a notice hid it for good, so a detector that restarted again after a dismissal was never shown. Dismiss is replaced by acknowledge, which hides a notice until it happens again, and mute, which hides it permanently. Kinds that never repeat (config and stream checks, the update notice) can only be muted. `reopen_at_count` is removed since acknowledge covers the failed login case.

* move camera CPU warnings to notices

High ffmpeg and detect CPU warnings sat in the status bar with no way to dismiss them. They're now `ffmpeg_high_cpu` and `detect_high_cpu` notices, raised per episode by the same tracker as skipped detections. Also stop failed login attempts held from before an acknowledgement from reopening the notice.

* fix mypy and handle missing cpu stats in notices
This commit is contained in:
Josh Hawkins
2026-09-24 15:39:18 -06:00
committed by GitHub
parent c959df32c9
commit 9664d9ceae
30 changed files with 1307 additions and 707 deletions
+46 -6
View File
@@ -2,7 +2,11 @@ import { Link } from "react-router-dom";
import { useTranslation } from "react-i18next";
import { FaTriangleExclamation } from "react-icons/fa6";
import {
LuBell,
LuBellOff,
LuCheck,
LuExternalLink,
LuEye,
LuInfo,
LuSlidersHorizontal,
LuX,
@@ -51,7 +55,13 @@ export default function HealthProblemRow({ problem }: HealthProblemRowProps) {
problem.link ||
problem.docLink ||
problem.externalLink ||
problem.onDismiss;
problem.onAcknowledge ||
problem.onMute ||
problem.onUnhide;
const unhideLabel =
problem.hidden === "muted"
? t("health.notices.unmute")
: t("health.notices.showAgain");
return (
<div
@@ -150,16 +160,46 @@ export default function HealthProblemRow({ problem }: HealthProblemRowProps) {
</Button>
</RowAction>
)}
{problem.onDismiss && (
<RowAction label={t("health.notices.dismiss")}>
{problem.onAcknowledge && (
<RowAction label={t("health.notices.acknowledgeHint")}>
<Button
variant="ghost"
size="icon"
className={ICON_BUTTON_CLASS}
aria-label={t("health.notices.dismiss")}
onClick={problem.onDismiss}
aria-label={t("health.notices.acknowledge")}
onClick={problem.onAcknowledge}
>
<LuX className="size-3.5" />
<LuCheck className="size-3.5" />
</Button>
</RowAction>
)}
{problem.onMute && (
<RowAction label={t("health.notices.muteHint")}>
<Button
variant="ghost"
size="icon"
className={ICON_BUTTON_CLASS}
aria-label={t("health.notices.mute")}
onClick={problem.onMute}
>
<LuBellOff className="size-3.5" />
</Button>
</RowAction>
)}
{problem.onUnhide && (
<RowAction label={unhideLabel}>
<Button
variant="ghost"
size="icon"
className={ICON_BUTTON_CLASS}
aria-label={unhideLabel}
onClick={problem.onUnhide}
>
{problem.hidden === "muted" ? (
<LuBell className="size-3.5" />
) : (
<LuEye className="size-3.5" />
)}
</Button>
</RowAction>
)}