mirror of
https://github.com/blakeblackshear/frigate.git
synced 2025-07-21 13:46:56 +02:00
Link to relevant page from status bar warnings / errors (#11140)
* Use hash state for system pages * Add links to items * Add stats to other types * Link on mobile as well * Use link * Cleanup using util
This commit is contained in:
parent
c2c6113299
commit
acf37f9920
@ -9,6 +9,7 @@ import { useContext, useEffect, useMemo } from "react";
|
|||||||
import { FaCheck } from "react-icons/fa";
|
import { FaCheck } from "react-icons/fa";
|
||||||
import { IoIosWarning } from "react-icons/io";
|
import { IoIosWarning } from "react-icons/io";
|
||||||
import { MdCircle } from "react-icons/md";
|
import { MdCircle } from "react-icons/md";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
|
|
||||||
export default function Statusbar() {
|
export default function Statusbar() {
|
||||||
@ -43,7 +44,13 @@ export default function Statusbar() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
clearMessages("stats");
|
clearMessages("stats");
|
||||||
potentialProblems.forEach((problem) => {
|
potentialProblems.forEach((problem) => {
|
||||||
addMessage("stats", problem.text, problem.color);
|
addMessage(
|
||||||
|
"stats",
|
||||||
|
problem.text,
|
||||||
|
problem.color,
|
||||||
|
undefined,
|
||||||
|
problem.relevantLink,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}, [potentialProblems, addMessage, clearMessages]);
|
}, [potentialProblems, addMessage, clearMessages]);
|
||||||
|
|
||||||
@ -110,14 +117,25 @@ export default function Statusbar() {
|
|||||||
) : (
|
) : (
|
||||||
Object.entries(messages).map(([key, messageArray]) => (
|
Object.entries(messages).map(([key, messageArray]) => (
|
||||||
<div key={key} className="h-full flex items-center gap-2">
|
<div key={key} className="h-full flex items-center gap-2">
|
||||||
{messageArray.map(({ id, text, color }: StatusMessage) => (
|
{messageArray.map(({ id, text, color, link }: StatusMessage) => {
|
||||||
<div key={id} className="flex items-center text-sm gap-2">
|
const message = (
|
||||||
<IoIosWarning
|
<div
|
||||||
className={`size-5 ${color || "text-danger"}`}
|
key={id}
|
||||||
/>
|
className={`flex items-center text-sm gap-2 ${link ? "hover:underline cursor-pointer" : ""}`}
|
||||||
{text}
|
>
|
||||||
</div>
|
<IoIosWarning
|
||||||
))}
|
className={`size-5 ${color || "text-danger"}`}
|
||||||
|
/>
|
||||||
|
{text}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (link) {
|
||||||
|
return <Link to={link}>{message}</Link>;
|
||||||
|
} else {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
))
|
))
|
||||||
)}
|
)}
|
||||||
|
@ -139,7 +139,7 @@ export default function GeneralSettings({ className }: GeneralSettings) {
|
|||||||
<DropdownMenuLabel>System</DropdownMenuLabel>
|
<DropdownMenuLabel>System</DropdownMenuLabel>
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
<DropdownMenuGroup className={isDesktop ? "" : "flex flex-col"}>
|
<DropdownMenuGroup className={isDesktop ? "" : "flex flex-col"}>
|
||||||
<Link to="/system">
|
<Link to="/system#general">
|
||||||
<MenuItem
|
<MenuItem
|
||||||
className={
|
className={
|
||||||
isDesktop
|
isDesktop
|
||||||
|
@ -13,6 +13,7 @@ import {
|
|||||||
StatusBarMessagesContext,
|
StatusBarMessagesContext,
|
||||||
StatusMessage,
|
StatusMessage,
|
||||||
} from "@/context/statusbar-provider";
|
} from "@/context/statusbar-provider";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
|
||||||
function Bottombar() {
|
function Bottombar() {
|
||||||
const navItems = useNavigation("secondary");
|
const navItems = useNavigation("secondary");
|
||||||
@ -51,7 +52,13 @@ function StatusAlertNav() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
clearMessages("stats");
|
clearMessages("stats");
|
||||||
potentialProblems.forEach((problem) => {
|
potentialProblems.forEach((problem) => {
|
||||||
addMessage("stats", problem.text, problem.color);
|
addMessage(
|
||||||
|
"stats",
|
||||||
|
problem.text,
|
||||||
|
problem.color,
|
||||||
|
undefined,
|
||||||
|
problem.relevantLink,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}, [potentialProblems, addMessage, clearMessages]);
|
}, [potentialProblems, addMessage, clearMessages]);
|
||||||
|
|
||||||
@ -68,14 +75,22 @@ function StatusAlertNav() {
|
|||||||
<div className="w-full h-auto py-4 overflow-y-auto overflow-x-hidden flex flex-col items-center gap-2">
|
<div className="w-full h-auto py-4 overflow-y-auto overflow-x-hidden flex flex-col items-center gap-2">
|
||||||
{Object.entries(messages).map(([key, messageArray]) => (
|
{Object.entries(messages).map(([key, messageArray]) => (
|
||||||
<div key={key} className="w-full flex items-center gap-2">
|
<div key={key} className="w-full flex items-center gap-2">
|
||||||
{messageArray.map(({ id, text, color }: StatusMessage) => (
|
{messageArray.map(({ id, text, color, link }: StatusMessage) => {
|
||||||
<div key={id} className="flex items-center text-xs gap-2">
|
const message = (
|
||||||
<IoIosWarning
|
<div key={id} className="flex items-center text-xs gap-2">
|
||||||
className={`size-5 ${color || "text-danger"}`}
|
<IoIosWarning
|
||||||
/>
|
className={`size-5 ${color || "text-danger"}`}
|
||||||
{text}
|
/>
|
||||||
</div>
|
{text}
|
||||||
))}
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (link) {
|
||||||
|
return <Link to={link}>{message}</Link>;
|
||||||
|
} else {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
@ -10,6 +10,7 @@ export type StatusMessage = {
|
|||||||
id: string;
|
id: string;
|
||||||
text: string;
|
text: string;
|
||||||
color?: string;
|
color?: string;
|
||||||
|
link?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type StatusMessagesState = {
|
export type StatusMessagesState = {
|
||||||
@ -27,6 +28,7 @@ type StatusBarMessagesContextValue = {
|
|||||||
message: string,
|
message: string,
|
||||||
color?: string,
|
color?: string,
|
||||||
messageId?: string,
|
messageId?: string,
|
||||||
|
link?: string,
|
||||||
) => string;
|
) => string;
|
||||||
removeMessage: (key: string, messageId: string) => void;
|
removeMessage: (key: string, messageId: string) => void;
|
||||||
clearMessages: (key: string) => void;
|
clearMessages: (key: string) => void;
|
||||||
@ -43,14 +45,20 @@ export function StatusBarMessagesProvider({
|
|||||||
const messages = useMemo(() => messagesState, [messagesState]);
|
const messages = useMemo(() => messagesState, [messagesState]);
|
||||||
|
|
||||||
const addMessage = useCallback(
|
const addMessage = useCallback(
|
||||||
(key: string, message: string, color?: string, messageId?: string) => {
|
(
|
||||||
|
key: string,
|
||||||
|
message: string,
|
||||||
|
color?: string,
|
||||||
|
messageId?: string,
|
||||||
|
link?: string,
|
||||||
|
) => {
|
||||||
const id = messageId || Date.now().toString();
|
const id = messageId || Date.now().toString();
|
||||||
const msgColor = color || "text-danger";
|
const msgColor = color || "text-danger";
|
||||||
setMessagesState((prevMessages) => ({
|
setMessagesState((prevMessages) => ({
|
||||||
...prevMessages,
|
...prevMessages,
|
||||||
[key]: [
|
[key]: [
|
||||||
...(prevMessages[key] || []),
|
...(prevMessages[key] || []),
|
||||||
{ id, text: message, color: msgColor },
|
{ id, text: message, color: msgColor, link },
|
||||||
],
|
],
|
||||||
}));
|
}));
|
||||||
return id;
|
return id;
|
||||||
|
@ -34,11 +34,13 @@ export default function useStats(stats: FrigateStats | undefined) {
|
|||||||
problems.push({
|
problems.push({
|
||||||
text: `${capitalizeFirstLetter(key)} is very slow (${det["inference_speed"]} ms)`,
|
text: `${capitalizeFirstLetter(key)} is very slow (${det["inference_speed"]} ms)`,
|
||||||
color: "text-danger",
|
color: "text-danger",
|
||||||
|
relevantLink: "/system#general",
|
||||||
});
|
});
|
||||||
} else if (det["inference_speed"] > InferenceThreshold.warning) {
|
} else if (det["inference_speed"] > InferenceThreshold.warning) {
|
||||||
problems.push({
|
problems.push({
|
||||||
text: `${capitalizeFirstLetter(key)} is slow (${det["inference_speed"]} ms)`,
|
text: `${capitalizeFirstLetter(key)} is slow (${det["inference_speed"]} ms)`,
|
||||||
color: "text-orange-400",
|
color: "text-orange-400",
|
||||||
|
relevantLink: "/system#general",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -53,6 +55,7 @@ export default function useStats(stats: FrigateStats | undefined) {
|
|||||||
problems.push({
|
problems.push({
|
||||||
text: `${capitalizeFirstLetter(name.replaceAll("_", " "))} is offline`,
|
text: `${capitalizeFirstLetter(name.replaceAll("_", " "))} is offline`,
|
||||||
color: "text-danger",
|
color: "text-danger",
|
||||||
|
relevantLink: "logs",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -70,6 +73,7 @@ export default function useStats(stats: FrigateStats | undefined) {
|
|||||||
problems.push({
|
problems.push({
|
||||||
text: `${capitalizeFirstLetter(name.replaceAll("_", " "))} has high FFMPEG CPU usage (${ffmpegAvg}%)`,
|
text: `${capitalizeFirstLetter(name.replaceAll("_", " "))} has high FFMPEG CPU usage (${ffmpegAvg}%)`,
|
||||||
color: "text-danger",
|
color: "text-danger",
|
||||||
|
relevantLink: "/system#cameras",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,6 +81,7 @@ export default function useStats(stats: FrigateStats | undefined) {
|
|||||||
problems.push({
|
problems.push({
|
||||||
text: `${capitalizeFirstLetter(name.replaceAll("_", " "))} has high detect CPU usage (${detectAvg}%)`,
|
text: `${capitalizeFirstLetter(name.replaceAll("_", " "))} has high detect CPU usage (${detectAvg}%)`,
|
||||||
color: "text-danger",
|
color: "text-danger",
|
||||||
|
relevantLink: "/system#cameras",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -11,6 +11,8 @@ import { FaVideo } from "react-icons/fa";
|
|||||||
import Logo from "@/components/Logo";
|
import Logo from "@/components/Logo";
|
||||||
import useOptimisticState from "@/hooks/use-optimistic-state";
|
import useOptimisticState from "@/hooks/use-optimistic-state";
|
||||||
import CameraMetrics from "@/views/system/CameraMetrics";
|
import CameraMetrics from "@/views/system/CameraMetrics";
|
||||||
|
import { useHashState } from "@/hooks/use-overlay-state";
|
||||||
|
import { capitalizeFirstLetter } from "@/utils/stringUtil";
|
||||||
|
|
||||||
const metrics = ["general", "storage", "cameras"] as const;
|
const metrics = ["general", "storage", "cameras"] as const;
|
||||||
type SystemMetric = (typeof metrics)[number];
|
type SystemMetric = (typeof metrics)[number];
|
||||||
@ -18,12 +20,18 @@ type SystemMetric = (typeof metrics)[number];
|
|||||||
function System() {
|
function System() {
|
||||||
// stats page
|
// stats page
|
||||||
|
|
||||||
const [page, setPage] = useState<SystemMetric>("general");
|
const [page, setPage] = useHashState<SystemMetric>();
|
||||||
const [pageToggle, setPageToggle] = useOptimisticState(page, setPage, 100);
|
const [pageToggle, setPageToggle] = useOptimisticState(
|
||||||
|
page ?? "general",
|
||||||
|
setPage,
|
||||||
|
100,
|
||||||
|
);
|
||||||
const [lastUpdated, setLastUpdated] = useState<number>(Date.now() / 1000);
|
const [lastUpdated, setLastUpdated] = useState<number>(Date.now() / 1000);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.title = `${pageToggle[0].toUpperCase()}${pageToggle.substring(1)} Stats - Frigate`;
|
if (pageToggle) {
|
||||||
|
document.title = `${capitalizeFirstLetter(pageToggle)} Stats - Frigate`;
|
||||||
|
}
|
||||||
}, [pageToggle]);
|
}, [pageToggle]);
|
||||||
|
|
||||||
// stats collection
|
// stats collection
|
||||||
|
@ -62,6 +62,7 @@ export type StorageStats = {
|
|||||||
export type PotentialProblem = {
|
export type PotentialProblem = {
|
||||||
text: string;
|
text: string;
|
||||||
color: string;
|
color: string;
|
||||||
|
relevantLink?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Vainfo = {
|
export type Vainfo = {
|
||||||
|
Loading…
Reference in New Issue
Block a user