From 8b9a5dcdf792b6c53261d3e4957f358467f56b8c Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Tue, 2 Apr 2024 15:25:48 -0600 Subject: [PATCH 01/19] Redesign log page to have formatting --- web/src/pages/Logs.tsx | 150 ++++++++++++++++++++++++++++++++++++++++- web/src/types/log.ts | 8 +++ 2 files changed, 155 insertions(+), 3 deletions(-) create mode 100644 web/src/types/log.ts diff --git a/web/src/pages/Logs.tsx b/web/src/pages/Logs.tsx index e8ca9002f7..ec7cf45a18 100644 --- a/web/src/pages/Logs.tsx +++ b/web/src/pages/Logs.tsx @@ -1,21 +1,31 @@ import { Button } from "@/components/ui/button"; import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"; +import { LogLine, LogSeverity } from "@/types/log"; import copy from "copy-to-clipboard"; import { useCallback, useMemo, useRef, useState } from "react"; +import { IoIosAlert } from "react-icons/io"; +import { GoAlertFill } from "react-icons/go"; import { LuCopy } from "react-icons/lu"; import useSWR from "swr"; const logTypes = ["frigate", "go2rtc", "nginx"] as const; type LogType = (typeof logTypes)[number]; +const datestamp = /\[[\d\s-:]*]/; +const severity = /(DEBUG)|(INFO)|(WARNING)|(ERROR)/; +const section = /[\w.]*/; + function Logs() { const [logService, setLogService] = useState("frigate"); - const { data: frigateLogs } = useSWR("logs/frigate", { + const { data: frigateLogs } = useSWR("logs/frigate", { refreshInterval: 1000, }); const { data: go2rtcLogs } = useSWR("logs/go2rtc", { refreshInterval: 1000 }); const { data: nginxLogs } = useSWR("logs/nginx", { refreshInterval: 1000 }); + + // convert to log data + const logs = useMemo(() => { if (logService == "frigate") { return frigateLogs; @@ -28,6 +38,54 @@ function Logs() { } }, [logService, frigateLogs, go2rtcLogs, nginxLogs]); + const logLines = useMemo(() => { + if (logService == "frigate") { + if (!frigateLogs) { + return []; + } + + return frigateLogs + .split("\n") + .map((line) => { + const match = datestamp.exec(line); + + if (!match) { + return null; + } + + const sectionMatch = section.exec( + line.substring(match.index + match[0].length).trim(), + ); + + if (!sectionMatch) { + return null; + } + + return { + dateStamp: match.toString().slice(1, -1), + severity: severity + .exec(line) + ?.at(0) + ?.toString() + ?.toLowerCase() as LogSeverity, + section: sectionMatch.toString(), + content: line + .substring(line.indexOf(":", match.index + match[0].length) + 2) + .trim(), + }; + }) + .filter((value) => value != null) as LogLine[]; + } else if (logService == "go2rtc") { + return []; + } else if (logService == "nginx") { + return []; + } else { + return []; + } + }, [logService, frigateLogs, go2rtcLogs, nginxLogs]); + + //console.log(`the logs are ${JSON.stringify(logLines)}`); + const handleCopyLogs = useCallback(() => { copy(logs); }, [logs]); @@ -104,13 +162,99 @@ function Logs() {
- {logs} +
+
+ Severity +
+
+ Timestamp +
+
+ Tag +
+
+ Message +
+
+ {logLines.map((log, idx) => ( + + ))}
); } +type LogLineDataProps = { + line: LogLine; + offset: number; +}; +function LogLineData({ line, offset }: LogLineDataProps) { + // long log message + + const contentRef = useRef(null); + const [expanded, setExpanded] = useState(false); + + const contentOverflows = useMemo(() => { + if (!contentRef.current) { + return false; + } + + return contentRef.current.scrollWidth > contentRef.current.clientWidth; + // update on ref change + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [contentRef.current]); + + // severity coloring + + const severityClassName = useMemo(() => { + switch (line.severity) { + case "info": + return "text-secondary-foreground rounded-md"; + case "warning": + return "text-yellow-400 rounded-md"; + case "error": + return "text-danger rounded-md"; + } + }, [line]); + + return ( +
+
+ {line.severity == "error" ? ( + + ) : ( + + )} + {line.severity} +
+
+ {line.dateStamp} +
+
+ {line.section} +
+
+
+ {line.content} +
+ {contentOverflows && ( + + )} +
+
+ ); +} + export default Logs; diff --git a/web/src/types/log.ts b/web/src/types/log.ts new file mode 100644 index 0000000000..445e79c838 --- /dev/null +++ b/web/src/types/log.ts @@ -0,0 +1,8 @@ +export type LogSeverity = "info" | "warning" | "error" | "debug"; + +export type LogLine = { + dateStamp: string; + severity: LogSeverity; + section: string; + content: string; +}; From 079927a9fafb0ab29add7f823d69b3b26d744193 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Tue, 2 Apr 2024 15:50:29 -0600 Subject: [PATCH 02/19] Support other log types as well --- web/src/pages/Logs.tsx | 96 +++++++++++++++++++++++++++++++++++------- 1 file changed, 81 insertions(+), 15 deletions(-) diff --git a/web/src/pages/Logs.tsx b/web/src/pages/Logs.tsx index ec7cf45a18..31ac86b9db 100644 --- a/web/src/pages/Logs.tsx +++ b/web/src/pages/Logs.tsx @@ -11,9 +11,14 @@ import useSWR from "swr"; const logTypes = ["frigate", "go2rtc", "nginx"] as const; type LogType = (typeof logTypes)[number]; -const datestamp = /\[[\d\s-:]*]/; -const severity = /(DEBUG)|(INFO)|(WARNING)|(ERROR)/; -const section = /[\w.]*/; +const frigateDateStamp = /\[[\d\s-:]*]/; +const frigateSeverity = /(DEBUG)|(INFO)|(WARNING)|(ERROR)/; +const frigateSection = /[\w.]*/; + +const goSeverity = /(DEB )|(INF )|(WARN )|(ERR )/; +const goSection = /\[[\w]*]/; + +const ngSeverity = /(GET)|(POST)|(PATCH)|(DELETE)/; function Logs() { const [logService, setLogService] = useState("frigate"); @@ -21,8 +26,12 @@ function Logs() { const { data: frigateLogs } = useSWR("logs/frigate", { refreshInterval: 1000, }); - const { data: go2rtcLogs } = useSWR("logs/go2rtc", { refreshInterval: 1000 }); - const { data: nginxLogs } = useSWR("logs/nginx", { refreshInterval: 1000 }); + const { data: go2rtcLogs } = useSWR("logs/go2rtc", { + refreshInterval: 1000, + }); + const { data: nginxLogs } = useSWR("logs/nginx", { + refreshInterval: 1000, + }); // convert to log data @@ -47,13 +56,13 @@ function Logs() { return frigateLogs .split("\n") .map((line) => { - const match = datestamp.exec(line); + const match = frigateDateStamp.exec(line); if (!match) { return null; } - const sectionMatch = section.exec( + const sectionMatch = frigateSection.exec( line.substring(match.index + match[0].length).trim(), ); @@ -63,7 +72,7 @@ function Logs() { return { dateStamp: match.toString().slice(1, -1), - severity: severity + severity: frigateSeverity .exec(line) ?.at(0) ?.toString() @@ -76,18 +85,75 @@ function Logs() { }) .filter((value) => value != null) as LogLine[]; } else if (logService == "go2rtc") { - return []; + if (!go2rtcLogs) { + return []; + } + + return go2rtcLogs + .split("\n") + .map((line) => { + if (line.length == 0) { + return null; + } + + const severity = goSeverity.exec(line); + + let section = + goSection.exec(line)?.toString()?.slice(1, -1) ?? "startup"; + + if (frigateSeverity.exec(section)) { + section = "startup"; + } + + let contentStart; + + if (section == "startup") { + if (severity) { + contentStart = severity.index + severity[0].length; + } else { + contentStart = line.lastIndexOf("]") + 1; + } + } else { + contentStart = line.indexOf(section) + section.length + 2; + } + + return { + dateStamp: line.substring(0, 19), + severity: "INFO", + section: section, + content: line.substring(contentStart).trim(), + }; + }) + .filter((value) => value != null) as LogLine[]; } else if (logService == "nginx") { - return []; + if (!nginxLogs) { + return []; + } + + return nginxLogs + .split("\n") + .map((line) => { + if (line.length == 0) { + return null; + } + + return { + dateStamp: line.substring(0, 19), + severity: "INFO", + section: ngSeverity.exec(line)?.at(0)?.toString() ?? "META", + content: line.substring(line.indexOf(" ", 20)).trim(), + }; + }) + .filter((value) => value != null) as LogLine[]; } else { return []; } }, [logService, frigateLogs, go2rtcLogs, nginxLogs]); - //console.log(`the logs are ${JSON.stringify(logLines)}`); - const handleCopyLogs = useCallback(() => { - copy(logs); + if (logs) { + copy(logs); + } }, [logs]); // scroll to bottom button @@ -166,7 +232,7 @@ function Logs() { >
- Severity + Type
Timestamp @@ -179,7 +245,7 @@ function Logs() {
{logLines.map((log, idx) => ( - + ))}
From a77efde0da78d8aa705e38d3e3d79dca2e814984 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Tue, 2 Apr 2024 16:06:59 -0600 Subject: [PATCH 03/19] fix border --- web/src/pages/Logs.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/pages/Logs.tsx b/web/src/pages/Logs.tsx index 31ac86b9db..5396b3fffd 100644 --- a/web/src/pages/Logs.tsx +++ b/web/src/pages/Logs.tsx @@ -288,7 +288,7 @@ function LogLineData({ line, offset }: LogLineDataProps) { return (
Date: Wed, 3 Apr 2024 06:13:55 -0600 Subject: [PATCH 04/19] Support log data format --- frigate/api/app.py | 16 ++++++++++++---- web/src/pages/Logs.tsx | 21 +++++++++------------ web/src/types/log.ts | 5 +++++ 3 files changed, 26 insertions(+), 16 deletions(-) diff --git a/frigate/api/app.py b/frigate/api/app.py index 6fdedab900..e8becee737 100644 --- a/frigate/api/app.py +++ b/frigate/api/app.py @@ -159,9 +159,9 @@ def config(): config["plus"] = {"enabled": current_app.plus_api.is_active()} for detector, detector_config in config["detectors"].items(): - detector_config["model"]["labelmap"] = ( - current_app.frigate_config.model.merged_labelmap - ) + detector_config["model"][ + "labelmap" + ] = current_app.frigate_config.model.merged_labelmap return jsonify(config) @@ -425,11 +425,19 @@ def logs(service: str): 404, ) + start = request.args.get("start", type=int, default=0) + end = request.args.get("start", type=int) + try: file = open(service_location, "r") contents = file.read() file.close() - return contents, 200 + + lines = contents.splitlines() + return make_response( + jsonify({"totalLines": len(lines), "lines": lines[start:end]}), + 200, + ) except FileNotFoundError as e: logger.error(e) return make_response( diff --git a/web/src/pages/Logs.tsx b/web/src/pages/Logs.tsx index 5396b3fffd..4e66657d4a 100644 --- a/web/src/pages/Logs.tsx +++ b/web/src/pages/Logs.tsx @@ -1,6 +1,6 @@ import { Button } from "@/components/ui/button"; import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"; -import { LogLine, LogSeverity } from "@/types/log"; +import { LogData, LogLine, LogSeverity } from "@/types/log"; import copy from "copy-to-clipboard"; import { useCallback, useMemo, useRef, useState } from "react"; import { IoIosAlert } from "react-icons/io"; @@ -23,13 +23,13 @@ const ngSeverity = /(GET)|(POST)|(PATCH)|(DELETE)/; function Logs() { const [logService, setLogService] = useState("frigate"); - const { data: frigateLogs } = useSWR("logs/frigate", { + const { data: frigateLogs } = useSWR("logs/frigate", { refreshInterval: 1000, }); - const { data: go2rtcLogs } = useSWR("logs/go2rtc", { + const { data: go2rtcLogs } = useSWR("logs/go2rtc", { refreshInterval: 1000, }); - const { data: nginxLogs } = useSWR("logs/nginx", { + const { data: nginxLogs } = useSWR("logs/nginx", { refreshInterval: 1000, }); @@ -43,7 +43,7 @@ function Logs() { } else if (logService == "nginx") { return nginxLogs; } else { - return "unknown logs"; + return undefined; } }, [logService, frigateLogs, go2rtcLogs, nginxLogs]); @@ -53,8 +53,7 @@ function Logs() { return []; } - return frigateLogs - .split("\n") + return frigateLogs.lines .map((line) => { const match = frigateDateStamp.exec(line); @@ -89,8 +88,7 @@ function Logs() { return []; } - return go2rtcLogs - .split("\n") + return go2rtcLogs.lines .map((line) => { if (line.length == 0) { return null; @@ -130,8 +128,7 @@ function Logs() { return []; } - return nginxLogs - .split("\n") + return nginxLogs.lines .map((line) => { if (line.length == 0) { return null; @@ -152,7 +149,7 @@ function Logs() { const handleCopyLogs = useCallback(() => { if (logs) { - copy(logs); + copy(logs.lines.join("\n")); } }, [logs]); diff --git a/web/src/types/log.ts b/web/src/types/log.ts index 445e79c838..faf8115f20 100644 --- a/web/src/types/log.ts +++ b/web/src/types/log.ts @@ -1,3 +1,8 @@ +export type LogData = { + lineCount: number; + lines: string[]; +}; + export type LogSeverity = "info" | "warning" | "error" | "debug"; export type LogLine = { From 4a1758b0aec8cdb8044da3895981418293db5dde Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Wed, 3 Apr 2024 06:47:57 -0600 Subject: [PATCH 05/19] Only load necessary logs --- frigate/api/app.py | 2 +- web/src/pages/Logs.tsx | 88 ++++++++++++++++++++++-------------------- 2 files changed, 48 insertions(+), 42 deletions(-) diff --git a/frigate/api/app.py b/frigate/api/app.py index e8becee737..8c99fdd399 100644 --- a/frigate/api/app.py +++ b/frigate/api/app.py @@ -426,7 +426,7 @@ def logs(service: str): ) start = request.args.get("start", type=int, default=0) - end = request.args.get("start", type=int) + end = request.args.get("end", type=int) try: file = open(service_location, "r") diff --git a/web/src/pages/Logs.tsx b/web/src/pages/Logs.tsx index 4e66657d4a..05fd294e59 100644 --- a/web/src/pages/Logs.tsx +++ b/web/src/pages/Logs.tsx @@ -2,15 +2,17 @@ import { Button } from "@/components/ui/button"; import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"; import { LogData, LogLine, LogSeverity } from "@/types/log"; import copy from "copy-to-clipboard"; -import { useCallback, useMemo, useRef, useState } from "react"; +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { IoIosAlert } from "react-icons/io"; import { GoAlertFill } from "react-icons/go"; import { LuCopy } from "react-icons/lu"; -import useSWR from "swr"; +import axios from "axios"; const logTypes = ["frigate", "go2rtc", "nginx"] as const; type LogType = (typeof logTypes)[number]; +type LogRange = { start: number; end: number }; + const frigateDateStamp = /\[[\d\s-:]*]/; const frigateSeverity = /(DEBUG)|(INFO)|(WARNING)|(ERROR)/; const frigateSection = /[\w.]*/; @@ -23,37 +25,49 @@ const ngSeverity = /(GET)|(POST)|(PATCH)|(DELETE)/; function Logs() { const [logService, setLogService] = useState("frigate"); - const { data: frigateLogs } = useSWR("logs/frigate", { - refreshInterval: 1000, - }); - const { data: go2rtcLogs } = useSWR("logs/go2rtc", { - refreshInterval: 1000, - }); - const { data: nginxLogs } = useSWR("logs/nginx", { - refreshInterval: 1000, - }); + // log data handling - // convert to log data + const [logs, setLogs] = useState([]); - const logs = useMemo(() => { - if (logService == "frigate") { - return frigateLogs; - } else if (logService == "go2rtc") { - return go2rtcLogs; - } else if (logService == "nginx") { - return nginxLogs; - } else { - return undefined; + useEffect(() => { + axios.get(`logs/${logService}`).then((resp) => { + if (resp.status == 200) { + const data = resp.data as LogData; + setLogs(data.lines); + } + }); + }, [logService]); + + useEffect(() => { + if (!logs || logs.length == 0) { + return; } - }, [logService, frigateLogs, go2rtcLogs, nginxLogs]); - const logLines = useMemo(() => { - if (logService == "frigate") { - if (!frigateLogs) { - return []; + const id = setTimeout(() => { + axios.get(`logs/${logService}?start=${logs.length}`).then((resp) => { + if (resp.status == 200) { + const data = resp.data as LogData; + setLogs([...logs, ...data.lines]); + } + }); + }, 1000); + + return () => { + if (id) { + clearTimeout(id); } + }; + }, [logs, logService]); - return frigateLogs.lines + // convert to log data + + const logLines = useMemo(() => { + if (!logs) { + return []; + } + + if (logService == "frigate") { + return logs .map((line) => { const match = frigateDateStamp.exec(line); @@ -84,11 +98,7 @@ function Logs() { }) .filter((value) => value != null) as LogLine[]; } else if (logService == "go2rtc") { - if (!go2rtcLogs) { - return []; - } - - return go2rtcLogs.lines + return logs .map((line) => { if (line.length == 0) { return null; @@ -124,11 +134,7 @@ function Logs() { }) .filter((value) => value != null) as LogLine[]; } else if (logService == "nginx") { - if (!nginxLogs) { - return []; - } - - return nginxLogs.lines + return logs .map((line) => { if (line.length == 0) { return null; @@ -145,15 +151,15 @@ function Logs() { } else { return []; } - }, [logService, frigateLogs, go2rtcLogs, nginxLogs]); + }, [logs, logService]); const handleCopyLogs = useCallback(() => { if (logs) { - copy(logs.lines.join("\n")); + copy(logs.join("\n")); } }, [logs]); - // scroll to bottom button + // scroll to bottom const contentRef = useRef(null); const [endVisible, setEndVisible] = useState(true); @@ -244,7 +250,7 @@ function Logs() { {logLines.map((log, idx) => ( ))} -
+
); From 9a8f93d204a887098aa157bd164a78510fa36076 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Wed, 3 Apr 2024 07:39:25 -0600 Subject: [PATCH 06/19] Load incrementally --- web/src/pages/Logs.tsx | 136 +++++++++++++++++++++++++++++++++-------- web/src/types/log.ts | 2 +- 2 files changed, 112 insertions(+), 26 deletions(-) diff --git a/web/src/pages/Logs.tsx b/web/src/pages/Logs.tsx index 05fd294e59..daf0a3ade3 100644 --- a/web/src/pages/Logs.tsx +++ b/web/src/pages/Logs.tsx @@ -27,15 +27,23 @@ function Logs() { // log data handling + const [logRange, setLogRange] = useState({ start: 0, end: 0 }); const [logs, setLogs] = useState([]); useEffect(() => { - axios.get(`logs/${logService}`).then((resp) => { - if (resp.status == 200) { - const data = resp.data as LogData; - setLogs(data.lines); - } - }); + axios + .get(`logs/${logService}?start=-100`) + .then((resp) => { + if (resp.status == 200) { + const data = resp.data as LogData; + setLogRange({ + start: Math.max(0, data.totalLines - 100), + end: data.totalLines, + }); + setLogs(data.lines); + } + }) + .catch(() => {}); }, [logService]); useEffect(() => { @@ -44,20 +52,30 @@ function Logs() { } const id = setTimeout(() => { - axios.get(`logs/${logService}?start=${logs.length}`).then((resp) => { - if (resp.status == 200) { - const data = resp.data as LogData; - setLogs([...logs, ...data.lines]); - } - }); - }, 1000); + axios + .get(`logs/${logService}?start=${logRange.end}`) + .then((resp) => { + if (resp.status == 200) { + const data = resp.data as LogData; + + if (data.lines.length > 0) { + setLogRange({ + start: logRange.start, + end: data.totalLines, + }); + setLogs([...logs, ...data.lines]); + } + } + }) + .catch(() => {}); + }, 5000); return () => { if (id) { clearTimeout(id); } }; - }, [logs, logService]); + }, [logs, logService, logRange]); // convert to log data @@ -161,23 +179,81 @@ function Logs() { // scroll to bottom + const [atBottom, setAtBottom] = useState(false); + const contentRef = useRef(null); const [endVisible, setEndVisible] = useState(true); - const observer = useRef(null); + const endObserver = useRef(null); const endLogRef = useCallback( (node: HTMLElement | null) => { - if (observer.current) observer.current.disconnect(); + if (endObserver.current) endObserver.current.disconnect(); try { - observer.current = new IntersectionObserver((entries) => { + endObserver.current = new IntersectionObserver((entries) => { setEndVisible(entries[0].isIntersecting); }); - if (node) observer.current.observe(node); + if (node) endObserver.current.observe(node); } catch (e) { // no op } }, [setEndVisible], ); + const startObserver = useRef(null); + const startLogRef = useCallback( + (node: HTMLElement | null) => { + if (startObserver.current) startObserver.current.disconnect(); + + if (logs.length == 0 || !atBottom) { + return; + } + + try { + startObserver.current = new IntersectionObserver((entries) => { + if (entries[0].isIntersecting) { + const start = Math.max(0, logRange.start - 100); + + axios + .get(`logs/${logService}?start=${start}&end=${logRange.start}`) + .then((resp) => { + if (resp.status == 200) { + const data = resp.data as LogData; + + if (data.lines.length > 0) { + setLogRange({ + start: start, + end: logRange.end, + }); + setLogs([...data.lines, ...logs]); + } + } + }) + .catch(() => {}); + } + }); + if (node) startObserver.current.observe(node); + } catch (e) { + // no op + } + }, + [logRange], + ); + + useEffect(() => { + if (logLines.length == 0) { + setAtBottom(false); + return; + } + + if (!contentRef.current) { + return; + } + + contentRef.current?.scrollTo({ + top: contentRef.current?.scrollHeight, + behavior: "instant", + }); + setTimeout(() => setAtBottom(true), 300); + }, [logLines, logService]); return (
@@ -187,9 +263,12 @@ function Logs() { type="single" size="sm" value={logService} - onValueChange={(value: LogType) => - value ? setLogService(value) : null - } // don't allow the severity to be unselected + onValueChange={(value: LogType) => { + if (value) { + setLogs([]); + setLogService(value); + } + }} // don't allow the severity to be unselected > {Object.values(logTypes).map((item) => (
+ {logLines.length > 0 && logRange.start > 0 &&
} {logLines.map((log, idx) => ( - + ))} -
+ {logLines.length > 0 &&
}
); } type LogLineDataProps = { + className: string; line: LogLine; offset: number; }; -function LogLineData({ line, offset }: LogLineDataProps) { +function LogLineData({ className, line, offset }: LogLineDataProps) { // long log message const contentRef = useRef(null); @@ -291,7 +377,7 @@ function LogLineData({ line, offset }: LogLineDataProps) { return (
Date: Wed, 3 Apr 2024 07:45:58 -0600 Subject: [PATCH 07/19] Cleanup --- web/src/pages/Logs.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/web/src/pages/Logs.tsx b/web/src/pages/Logs.tsx index daf0a3ade3..7fab8bc461 100644 --- a/web/src/pages/Logs.tsx +++ b/web/src/pages/Logs.tsx @@ -235,6 +235,8 @@ function Logs() { // no op } }, + // we need to listen on the current range of visible items + // eslint-disable-next-line react-hooks/exhaustive-deps [logRange], ); From ff31cc18078478252f8df650bc0a5c8ebd16a471 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Wed, 3 Apr 2024 08:06:05 -0600 Subject: [PATCH 08/19] Cleanup --- frigate/api/app.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frigate/api/app.py b/frigate/api/app.py index 8c99fdd399..c6e58d9515 100644 --- a/frigate/api/app.py +++ b/frigate/api/app.py @@ -159,9 +159,9 @@ def config(): config["plus"] = {"enabled": current_app.plus_api.is_active()} for detector, detector_config in config["detectors"].items(): - detector_config["model"][ - "labelmap" - ] = current_app.frigate_config.model.merged_labelmap + detector_config["model"]["labelmap"] = ( + current_app.frigate_config.model.merged_labelmap + ) return jsonify(config) From 1184aa9ad5acf20c00c40f8879020bca4b3dacf8 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Wed, 3 Apr 2024 08:28:00 -0600 Subject: [PATCH 09/19] Render all items --- web/src/pages/Logs.tsx | 40 +++++++++++++++++++++++++++++----------- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/web/src/pages/Logs.tsx b/web/src/pages/Logs.tsx index 7fab8bc461..9925c5f487 100644 --- a/web/src/pages/Logs.tsx +++ b/web/src/pages/Logs.tsx @@ -20,7 +20,7 @@ const frigateSection = /[\w.]*/; const goSeverity = /(DEB )|(INF )|(WARN )|(ERR )/; const goSection = /\[[\w]*]/; -const ngSeverity = /(GET)|(POST)|(PATCH)|(DELETE)/; +const ngSeverity = /(GET)|(POST)|(PUT)|(PATCH)|(DELETE)/; function Logs() { const [logService, setLogService] = useState("frigate"); @@ -246,6 +246,15 @@ function Logs() { return; } + if (logLines.length < 100) { + setAtBottom(true); + return; + } + + if (atBottom) { + return; + } + if (!contentRef.current) { return; } @@ -255,6 +264,8 @@ function Logs() { behavior: "instant", }); setTimeout(() => setAtBottom(true), 300); + // we need to listen on the current range of visible items + // eslint-disable-next-line react-hooks/exhaustive-deps }, [logLines, logService]); return ( @@ -328,15 +339,20 @@ function Logs() { Message
- {logLines.length > 0 && logRange.start > 0 &&
} - {logLines.map((log, idx) => ( - - ))} + {logLines.length > 0 && + [...Array(logRange.end - 1).keys()].map((idx) => + idx >= logRange.start ? ( + + ) : ( +
+ ), + )} {logLines.length > 0 &&
}
@@ -344,11 +360,12 @@ function Logs() { } type LogLineDataProps = { + startRef?: (node: HTMLDivElement | null) => void; className: string; line: LogLine; offset: number; }; -function LogLineData({ className, line, offset }: LogLineDataProps) { +function LogLineData({ startRef, className, line, offset }: LogLineDataProps) { // long log message const contentRef = useRef(null); @@ -379,6 +396,7 @@ function LogLineData({ className, line, offset }: LogLineDataProps) { return (
Date: Wed, 3 Apr 2024 08:46:54 -0600 Subject: [PATCH 10/19] avoid flashing scroll to bottom --- web/src/pages/Logs.tsx | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/web/src/pages/Logs.tsx b/web/src/pages/Logs.tsx index 9925c5f487..0025582600 100644 --- a/web/src/pages/Logs.tsx +++ b/web/src/pages/Logs.tsx @@ -179,7 +179,7 @@ function Logs() { // scroll to bottom - const [atBottom, setAtBottom] = useState(false); + const [initialScroll, setInitialScroll] = useState(false); const contentRef = useRef(null); const [endVisible, setEndVisible] = useState(true); @@ -203,7 +203,7 @@ function Logs() { (node: HTMLElement | null) => { if (startObserver.current) startObserver.current.disconnect(); - if (logs.length == 0 || !atBottom) { + if (logs.length == 0 || !initialScroll) { return; } @@ -242,16 +242,16 @@ function Logs() { useEffect(() => { if (logLines.length == 0) { - setAtBottom(false); + setInitialScroll(false); return; } if (logLines.length < 100) { - setAtBottom(true); + setInitialScroll(true); return; } - if (atBottom) { + if (initialScroll) { return; } @@ -263,7 +263,7 @@ function Logs() { top: contentRef.current?.scrollHeight, behavior: "instant", }); - setTimeout(() => setAtBottom(true), 300); + setTimeout(() => setInitialScroll(true), 300); // we need to listen on the current range of visible items // eslint-disable-next-line react-hooks/exhaustive-deps }, [logLines, logService]); @@ -306,7 +306,7 @@ function Logs() {
- {!endVisible && ( + {initialScroll && !endVisible && (
{logLines.length > 0 && - [...Array(logRange.end - 1).keys()].map((idx) => - idx >= logRange.start ? ( - - ) : ( -
- ), - )} + [...Array(logRange.end - 1).keys()].map((idx) => { + const logLine = + idx >= logRange.start + ? logLines[idx - logRange.start] + : undefined; + + if (logLine) { + return ( + + ); + } + + return
; + })} {logLines.length > 0 &&
}
From 974ae1907a30172149a95846c8c1015ecaff676e Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Wed, 3 Apr 2024 10:00:04 -0600 Subject: [PATCH 13/19] Group logs based on timestamp --- frigate/api/app.py | 47 ++++++++++++++++++++++++++++++------------ web/src/pages/Logs.tsx | 8 +++---- 2 files changed, 38 insertions(+), 17 deletions(-) diff --git a/frigate/api/app.py b/frigate/api/app.py index c6e58d9515..61f8547842 100644 --- a/frigate/api/app.py +++ b/frigate/api/app.py @@ -9,14 +9,7 @@ from functools import reduce import requests -from flask import ( - Blueprint, - Flask, - current_app, - jsonify, - make_response, - request, -) +from flask import Blueprint, Flask, current_app, jsonify, make_response, request from markupsafe import escape from peewee import operator from playhouse.sqliteq import SqliteQueueDatabase @@ -159,9 +152,9 @@ def config(): config["plus"] = {"enabled": current_app.plus_api.is_active()} for detector, detector_config in config["detectors"].items(): - detector_config["model"]["labelmap"] = ( - current_app.frigate_config.model.merged_labelmap - ) + detector_config["model"][ + "labelmap" + ] = current_app.frigate_config.model.merged_labelmap return jsonify(config) @@ -433,9 +426,37 @@ def logs(service: str): contents = file.read() file.close() - lines = contents.splitlines() + # use the start timestamp to group logs together`` + logLines = [] + keyLength = 0 + dateEnd = 0 + currentKey = "" + currentLine = "" + + for rawLine in contents.splitlines(): + cleanLine = rawLine.strip() + + if len(cleanLine) < 10: + continue + + if dateEnd == 0: + dateEnd = cleanLine.index(" ") + keyLength = dateEnd - (6 if service_location == "frigate" else 0) + + newKey = cleanLine[0:keyLength] + + if newKey == currentKey: + currentLine += f"\n{cleanLine[dateEnd:].strip()}" + continue + else: + logLines.append(currentLine) + currentKey = newKey + currentLine = cleanLine + + logLines.append(currentLine) + return make_response( - jsonify({"totalLines": len(lines), "lines": lines[start:end]}), + jsonify({"totalLines": len(logLines), "lines": logLines[start:end]}), 200, ) except FileNotFoundError as e: diff --git a/web/src/pages/Logs.tsx b/web/src/pages/Logs.tsx index 0d8a6f026b..8b6115bd10 100644 --- a/web/src/pages/Logs.tsx +++ b/web/src/pages/Logs.tsx @@ -246,16 +246,16 @@ function Logs() { return; } - if (logLines.length < 100) { - setInitialScroll(true); + if (initialScroll) { return; } - if (initialScroll) { + if (!contentRef.current) { return; } - if (!contentRef.current) { + if (contentRef.current.scrollHeight <= contentRef.current.clientHeight) { + setInitialScroll(true); return; } From 6590ec1dabac7b756bea1661ce42898b5134b29d Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Wed, 3 Apr 2024 10:07:39 -0600 Subject: [PATCH 14/19] Formatting --- frigate/api/app.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frigate/api/app.py b/frigate/api/app.py index 61f8547842..22c6733593 100644 --- a/frigate/api/app.py +++ b/frigate/api/app.py @@ -152,9 +152,9 @@ def config(): config["plus"] = {"enabled": current_app.plus_api.is_active()} for detector, detector_config in config["detectors"].items(): - detector_config["model"][ - "labelmap" - ] = current_app.frigate_config.model.merged_labelmap + detector_config["model"]["labelmap"] = ( + current_app.frigate_config.model.merged_labelmap + ) return jsonify(config) From c5ecc522fdc08ee30137b98eb8e76ad782415448 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Wed, 3 Apr 2024 10:12:50 -0600 Subject: [PATCH 15/19] remove scrollbar --- web/src/pages/Logs.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/pages/Logs.tsx b/web/src/pages/Logs.tsx index 8b6115bd10..13b6d0a705 100644 --- a/web/src/pages/Logs.tsx +++ b/web/src/pages/Logs.tsx @@ -323,7 +323,7 @@ function Logs() {
From 57480154a1de872da928c06575bf4b80b995c22c Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Wed, 3 Apr 2024 10:14:19 -0600 Subject: [PATCH 16/19] Don't repull when there are no items to pull --- web/src/pages/Logs.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/pages/Logs.tsx b/web/src/pages/Logs.tsx index 13b6d0a705..99aaaafa37 100644 --- a/web/src/pages/Logs.tsx +++ b/web/src/pages/Logs.tsx @@ -209,7 +209,7 @@ function Logs() { try { startObserver.current = new IntersectionObserver((entries) => { - if (entries[0].isIntersecting) { + if (entries[0].isIntersecting && logRange.start > 0) { const start = Math.max(0, logRange.start - 100); axios From 892f45ce35214fa92e5de278025db920632ff647 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Wed, 3 Apr 2024 10:28:19 -0600 Subject: [PATCH 17/19] Add newline to end --- frigate/api/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frigate/api/app.py b/frigate/api/app.py index 22c6733593..c1f91c4991 100644 --- a/frigate/api/app.py +++ b/frigate/api/app.py @@ -446,7 +446,7 @@ def logs(service: str): newKey = cleanLine[0:keyLength] if newKey == currentKey: - currentLine += f"\n{cleanLine[dateEnd:].strip()}" + currentLine += f"{cleanLine[dateEnd:].strip()}\n" continue else: logLines.append(currentLine) From df2332b4e2f653a6c4e2450dbbeaeb51dcc0505f Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Wed, 3 Apr 2024 10:38:42 -0600 Subject: [PATCH 18/19] Fix log lines missing --- frigate/api/app.py | 6 ++++-- web/src/pages/Logs.tsx | 13 ++++++++++++- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/frigate/api/app.py b/frigate/api/app.py index c1f91c4991..3900aac058 100644 --- a/frigate/api/app.py +++ b/frigate/api/app.py @@ -446,10 +446,12 @@ def logs(service: str): newKey = cleanLine[0:keyLength] if newKey == currentKey: - currentLine += f"{cleanLine[dateEnd:].strip()}\n" + currentLine += f"\n{cleanLine[dateEnd:].strip()}" continue else: - logLines.append(currentLine) + if len(currentLine) > 0: + logLines.append(currentLine) + currentKey = newKey currentLine = cleanLine diff --git a/web/src/pages/Logs.tsx b/web/src/pages/Logs.tsx index 99aaaafa37..0a7e05ae38 100644 --- a/web/src/pages/Logs.tsx +++ b/web/src/pages/Logs.tsx @@ -90,6 +90,17 @@ function Logs() { const match = frigateDateStamp.exec(line); if (!match) { + const infoIndex = line.indexOf("[INFO]"); + + if (infoIndex != -1) { + return { + dateStamp: line.substring(0, 19), + severity: "info", + section: "starutp", + content: line.substring(infoIndex + 6).trim(), + }; + } + return null; } @@ -340,7 +351,7 @@ function Logs() {
{logLines.length > 0 && - [...Array(logRange.end - 1).keys()].map((idx) => { + [...Array(logRange.end).keys()].map((idx) => { const logLine = idx >= logRange.start ? logLines[idx - logRange.start] From 89ae3b5ef6b72eb35d68d011dffb951b6315c2f0 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Wed, 3 Apr 2024 10:40:28 -0600 Subject: [PATCH 19/19] typo --- web/src/pages/Logs.tsx | 2 +- web/vite.config.ts | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/web/src/pages/Logs.tsx b/web/src/pages/Logs.tsx index 0a7e05ae38..3983a38d24 100644 --- a/web/src/pages/Logs.tsx +++ b/web/src/pages/Logs.tsx @@ -96,7 +96,7 @@ function Logs() { return { dateStamp: line.substring(0, 19), severity: "info", - section: "starutp", + section: "startup", content: line.substring(infoIndex + 6).trim(), }; } diff --git a/web/vite.config.ts b/web/vite.config.ts index 5afefa3312..cc3ead7074 100644 --- a/web/vite.config.ts +++ b/web/vite.config.ts @@ -12,24 +12,24 @@ export default defineConfig({ server: { proxy: { "/api": { - target: "http://localhost:5000", + target: "http://192.168.50.106:5000", ws: true, }, "/vod": { - target: "http://localhost:5000", + target: "http://192.168.50.106:5000", }, "/clips": { - target: "http://localhost:5000", + target: "http://192.168.50.106:5000", }, "/exports": { - target: "http://localhost:5000", + target: "http://192.168.50.106:5000", }, "/ws": { - target: "ws://localhost:5000", + target: "ws://192.168.50.106:5000", ws: true, }, "/live": { - target: "ws://localhost:5000", + target: "ws://192.168.50.106:5000", changeOrigin: true, ws: true, },