mirror of
				https://github.com/blakeblackshear/frigate.git
				synced 2025-10-27 10:52:11 +01: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 { IoIosWarning } from "react-icons/io"; | ||||
| import { MdCircle } from "react-icons/md"; | ||||
| import { Link } from "react-router-dom"; | ||||
| import useSWR from "swr"; | ||||
| 
 | ||||
| export default function Statusbar() { | ||||
| @ -43,7 +44,13 @@ export default function Statusbar() { | ||||
|   useEffect(() => { | ||||
|     clearMessages("stats"); | ||||
|     potentialProblems.forEach((problem) => { | ||||
|       addMessage("stats", problem.text, problem.color); | ||||
|       addMessage( | ||||
|         "stats", | ||||
|         problem.text, | ||||
|         problem.color, | ||||
|         undefined, | ||||
|         problem.relevantLink, | ||||
|       ); | ||||
|     }); | ||||
|   }, [potentialProblems, addMessage, clearMessages]); | ||||
| 
 | ||||
| @ -110,14 +117,25 @@ export default function Statusbar() { | ||||
|         ) : ( | ||||
|           Object.entries(messages).map(([key, messageArray]) => ( | ||||
|             <div key={key} className="h-full flex items-center gap-2"> | ||||
|               {messageArray.map(({ id, text, color }: StatusMessage) => ( | ||||
|                 <div key={id} className="flex items-center text-sm gap-2"> | ||||
|                   <IoIosWarning | ||||
|                     className={`size-5 ${color || "text-danger"}`} | ||||
|                   /> | ||||
|                   {text} | ||||
|                 </div> | ||||
|               ))} | ||||
|               {messageArray.map(({ id, text, color, link }: StatusMessage) => { | ||||
|                 const message = ( | ||||
|                   <div | ||||
|                     key={id} | ||||
|                     className={`flex items-center text-sm gap-2 ${link ? "hover:underline cursor-pointer" : ""}`} | ||||
|                   > | ||||
|                     <IoIosWarning | ||||
|                       className={`size-5 ${color || "text-danger"}`} | ||||
|                     /> | ||||
|                     {text} | ||||
|                   </div> | ||||
|                 ); | ||||
| 
 | ||||
|                 if (link) { | ||||
|                   return <Link to={link}>{message}</Link>; | ||||
|                 } else { | ||||
|                   return message; | ||||
|                 } | ||||
|               })} | ||||
|             </div> | ||||
|           )) | ||||
|         )} | ||||
|  | ||||
| @ -139,7 +139,7 @@ export default function GeneralSettings({ className }: GeneralSettings) { | ||||
|               <DropdownMenuLabel>System</DropdownMenuLabel> | ||||
|               <DropdownMenuSeparator /> | ||||
|               <DropdownMenuGroup className={isDesktop ? "" : "flex flex-col"}> | ||||
|                 <Link to="/system"> | ||||
|                 <Link to="/system#general"> | ||||
|                   <MenuItem | ||||
|                     className={ | ||||
|                       isDesktop | ||||
|  | ||||
| @ -13,6 +13,7 @@ import { | ||||
|   StatusBarMessagesContext, | ||||
|   StatusMessage, | ||||
| } from "@/context/statusbar-provider"; | ||||
| import { Link } from "react-router-dom"; | ||||
| 
 | ||||
| function Bottombar() { | ||||
|   const navItems = useNavigation("secondary"); | ||||
| @ -51,7 +52,13 @@ function StatusAlertNav() { | ||||
|   useEffect(() => { | ||||
|     clearMessages("stats"); | ||||
|     potentialProblems.forEach((problem) => { | ||||
|       addMessage("stats", problem.text, problem.color); | ||||
|       addMessage( | ||||
|         "stats", | ||||
|         problem.text, | ||||
|         problem.color, | ||||
|         undefined, | ||||
|         problem.relevantLink, | ||||
|       ); | ||||
|     }); | ||||
|   }, [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"> | ||||
|           {Object.entries(messages).map(([key, messageArray]) => ( | ||||
|             <div key={key} className="w-full flex items-center gap-2"> | ||||
|               {messageArray.map(({ id, text, color }: StatusMessage) => ( | ||||
|                 <div key={id} className="flex items-center text-xs gap-2"> | ||||
|                   <IoIosWarning | ||||
|                     className={`size-5 ${color || "text-danger"}`} | ||||
|                   /> | ||||
|                   {text} | ||||
|                 </div> | ||||
|               ))} | ||||
|               {messageArray.map(({ id, text, color, link }: StatusMessage) => { | ||||
|                 const message = ( | ||||
|                   <div key={id} className="flex items-center text-xs gap-2"> | ||||
|                     <IoIosWarning | ||||
|                       className={`size-5 ${color || "text-danger"}`} | ||||
|                     /> | ||||
|                     {text} | ||||
|                   </div> | ||||
|                 ); | ||||
| 
 | ||||
|                 if (link) { | ||||
|                   return <Link to={link}>{message}</Link>; | ||||
|                 } else { | ||||
|                   return message; | ||||
|                 } | ||||
|               })} | ||||
|             </div> | ||||
|           ))} | ||||
|         </div> | ||||
|  | ||||
| @ -10,6 +10,7 @@ export type StatusMessage = { | ||||
|   id: string; | ||||
|   text: string; | ||||
|   color?: string; | ||||
|   link?: string; | ||||
| }; | ||||
| 
 | ||||
| export type StatusMessagesState = { | ||||
| @ -27,6 +28,7 @@ type StatusBarMessagesContextValue = { | ||||
|     message: string, | ||||
|     color?: string, | ||||
|     messageId?: string, | ||||
|     link?: string, | ||||
|   ) => string; | ||||
|   removeMessage: (key: string, messageId: string) => void; | ||||
|   clearMessages: (key: string) => void; | ||||
| @ -43,14 +45,20 @@ export function StatusBarMessagesProvider({ | ||||
|   const messages = useMemo(() => messagesState, [messagesState]); | ||||
| 
 | ||||
|   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 msgColor = color || "text-danger"; | ||||
|       setMessagesState((prevMessages) => ({ | ||||
|         ...prevMessages, | ||||
|         [key]: [ | ||||
|           ...(prevMessages[key] || []), | ||||
|           { id, text: message, color: msgColor }, | ||||
|           { id, text: message, color: msgColor, link }, | ||||
|         ], | ||||
|       })); | ||||
|       return id; | ||||
|  | ||||
| @ -34,11 +34,13 @@ export default function useStats(stats: FrigateStats | undefined) { | ||||
|         problems.push({ | ||||
|           text: `${capitalizeFirstLetter(key)} is very slow (${det["inference_speed"]} ms)`, | ||||
|           color: "text-danger", | ||||
|           relevantLink: "/system#general", | ||||
|         }); | ||||
|       } else if (det["inference_speed"] > InferenceThreshold.warning) { | ||||
|         problems.push({ | ||||
|           text: `${capitalizeFirstLetter(key)} is slow (${det["inference_speed"]} ms)`, | ||||
|           color: "text-orange-400", | ||||
|           relevantLink: "/system#general", | ||||
|         }); | ||||
|       } | ||||
|     }); | ||||
| @ -53,6 +55,7 @@ export default function useStats(stats: FrigateStats | undefined) { | ||||
|         problems.push({ | ||||
|           text: `${capitalizeFirstLetter(name.replaceAll("_", " "))} is offline`, | ||||
|           color: "text-danger", | ||||
|           relevantLink: "logs", | ||||
|         }); | ||||
|       } | ||||
|     }); | ||||
| @ -70,6 +73,7 @@ export default function useStats(stats: FrigateStats | undefined) { | ||||
|         problems.push({ | ||||
|           text: `${capitalizeFirstLetter(name.replaceAll("_", " "))} has high FFMPEG CPU usage (${ffmpegAvg}%)`, | ||||
|           color: "text-danger", | ||||
|           relevantLink: "/system#cameras", | ||||
|         }); | ||||
|       } | ||||
| 
 | ||||
| @ -77,6 +81,7 @@ export default function useStats(stats: FrigateStats | undefined) { | ||||
|         problems.push({ | ||||
|           text: `${capitalizeFirstLetter(name.replaceAll("_", " "))} has high detect CPU usage (${detectAvg}%)`, | ||||
|           color: "text-danger", | ||||
|           relevantLink: "/system#cameras", | ||||
|         }); | ||||
|       } | ||||
|     }); | ||||
|  | ||||
| @ -11,6 +11,8 @@ import { FaVideo } from "react-icons/fa"; | ||||
| import Logo from "@/components/Logo"; | ||||
| import useOptimisticState from "@/hooks/use-optimistic-state"; | ||||
| 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; | ||||
| type SystemMetric = (typeof metrics)[number]; | ||||
| @ -18,12 +20,18 @@ type SystemMetric = (typeof metrics)[number]; | ||||
| function System() { | ||||
|   // stats page
 | ||||
| 
 | ||||
|   const [page, setPage] = useState<SystemMetric>("general"); | ||||
|   const [pageToggle, setPageToggle] = useOptimisticState(page, setPage, 100); | ||||
|   const [page, setPage] = useHashState<SystemMetric>(); | ||||
|   const [pageToggle, setPageToggle] = useOptimisticState( | ||||
|     page ?? "general", | ||||
|     setPage, | ||||
|     100, | ||||
|   ); | ||||
|   const [lastUpdated, setLastUpdated] = useState<number>(Date.now() / 1000); | ||||
| 
 | ||||
|   useEffect(() => { | ||||
|     document.title = `${pageToggle[0].toUpperCase()}${pageToggle.substring(1)} Stats - Frigate`; | ||||
|     if (pageToggle) { | ||||
|       document.title = `${capitalizeFirstLetter(pageToggle)} Stats - Frigate`; | ||||
|     } | ||||
|   }, [pageToggle]); | ||||
| 
 | ||||
|   // stats collection
 | ||||
|  | ||||
| @ -62,6 +62,7 @@ export type StorageStats = { | ||||
| export type PotentialProblem = { | ||||
|   text: string; | ||||
|   color: string; | ||||
|   relevantLink?: string; | ||||
| }; | ||||
| 
 | ||||
| export type Vainfo = { | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user