Add button for downloading full set of logs (#13188)

This commit is contained in:
Nicolas Mowen 2024-08-19 08:53:33 -06:00
parent c268a126dc
commit a77436eec3
2 changed files with 93 additions and 26 deletions

View File

@ -456,6 +456,19 @@ def vainfo():
@bp.route("/logs/<service>", methods=["GET"]) @bp.route("/logs/<service>", methods=["GET"])
def logs(service: str): def logs(service: str):
def download_logs(service_location: str):
try:
file = open(service_location, "r")
contents = file.read()
file.close()
return jsonify(contents)
except FileNotFoundError as e:
logger.error(e)
return make_response(
jsonify({"success": False, "message": "Could not find log file"}),
500,
)
log_locations = { log_locations = {
"frigate": "/dev/shm/logs/frigate/current", "frigate": "/dev/shm/logs/frigate/current",
"go2rtc": "/dev/shm/logs/go2rtc/current", "go2rtc": "/dev/shm/logs/go2rtc/current",
@ -470,6 +483,9 @@ def logs(service: str):
404, 404,
) )
if request.args.get("download", type=bool, default=False):
return download_logs(service_location)
start = request.args.get("start", type=int, default=0) start = request.args.get("start", type=int, default=0)
end = request.args.get("end", type=int) end = request.args.get("end", type=int)

View File

@ -21,16 +21,34 @@ import { cn } from "@/lib/utils";
import { MdVerticalAlignBottom } from "react-icons/md"; import { MdVerticalAlignBottom } from "react-icons/md";
import { parseLogLines } from "@/utils/logUtil"; import { parseLogLines } from "@/utils/logUtil";
import useKeyboardListener from "@/hooks/use-keyboard-listener"; import useKeyboardListener from "@/hooks/use-keyboard-listener";
import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
import scrollIntoView from "scroll-into-view-if-needed";
import { FaDownload } from "react-icons/fa";
type LogRange = { start: number; end: number }; type LogRange = { start: number; end: number };
function Logs() { function Logs() {
const [logService, setLogService] = useState<LogType>("frigate"); const [logService, setLogService] = useState<LogType>("frigate");
const tabsRef = useRef<HTMLDivElement | null>(null);
useEffect(() => { useEffect(() => {
document.title = `${logService[0].toUpperCase()}${logService.substring(1)} Logs - Frigate`; document.title = `${logService[0].toUpperCase()}${logService.substring(1)} Logs - Frigate`;
}, [logService]); }, [logService]);
useEffect(() => {
if (tabsRef.current) {
const element = tabsRef.current.querySelector(
`[data-nav-item="${logService}"]`,
);
if (element instanceof HTMLElement) {
scrollIntoView(element, {
behavior: "smooth",
inline: "start",
});
}
}
}, [tabsRef, logService]);
// log data handling // log data handling
const logPageSize = useMemo(() => { const logPageSize = useMemo(() => {
@ -118,6 +136,27 @@ function Logs() {
} }
}, [logs, logRange]); }, [logs, logRange]);
const handleDownloadLogs = useCallback(() => {
axios
.get(`logs/${logService}?download=true`)
.then((resp) => {
const element = document.createElement("a");
element.setAttribute(
"href",
"data:text/plain;charset=utf-8," + encodeURIComponent(resp.data),
);
element.setAttribute("download", `${logService}-logs.txt`);
element.style.display = "none";
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
})
.catch(() => {});
}, [logService]);
// scroll to bottom // scroll to bottom
const [initialScroll, setInitialScroll] = useState(false); const [initialScroll, setInitialScroll] = useState(false);
@ -266,33 +305,37 @@ function Logs() {
<Toaster position="top-center" closeButton={true} /> <Toaster position="top-center" closeButton={true} />
<LogInfoDialog logLine={selectedLog} setLogLine={setSelectedLog} /> <LogInfoDialog logLine={selectedLog} setLogLine={setSelectedLog} />
<div className="flex items-center justify-between"> <div className="relative flex h-11 w-full items-center justify-between">
<ToggleGroup <ScrollArea className="w-full whitespace-nowrap">
className="*:rounded-md *:px-3 *:py-4" <div ref={tabsRef} className="flex flex-row">
type="single" <ToggleGroup
size="sm" type="single"
value={logService} size="sm"
onValueChange={(value: LogType) => { value={logService}
if (value) { onValueChange={(value: LogType) => {
setLogs([]); if (value) {
setLogLines([]); setLogs([]);
setFilterSeverity(undefined); setLogLines([]);
setLogService(value); setFilterSeverity(undefined);
} setLogService(value);
}} // don't allow the severity to be unselected }
> }} // don't allow the severity to be unselected
{Object.values(logTypes).map((item) => (
<ToggleGroupItem
key={item}
className={`flex items-center justify-between gap-2 ${logService == item ? "" : "text-muted-foreground"}`}
value={item}
data-nav-item={item}
aria-label={`Select ${item}`}
> >
<div className="capitalize">{item}</div> {Object.values(logTypes).map((item) => (
</ToggleGroupItem> <ToggleGroupItem
))} key={item}
</ToggleGroup> className={`flex items-center justify-between gap-2 ${logService == item ? "" : "text-muted-foreground"}`}
value={item}
data-nav-item={item}
aria-label={`Select ${item}`}
>
<div className="capitalize">{item}</div>
</ToggleGroupItem>
))}
</ToggleGroup>
<ScrollBar orientation="horizontal" className="h-0" />
</div>
</ScrollArea>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Button <Button
className="flex items-center justify-between gap-2" className="flex items-center justify-between gap-2"
@ -304,6 +347,14 @@ function Logs() {
Copy to Clipboard Copy to Clipboard
</div> </div>
</Button> </Button>
<Button
className="flex items-center justify-between gap-2"
size="sm"
onClick={handleDownloadLogs}
>
<FaDownload className="text-secondary-foreground" />
<div className="hidden text-primary md:block">Download</div>
</Button>
<LogLevelFilterButton <LogLevelFilterButton
selectedLabels={filterSeverity} selectedLabels={filterSeverity}
updateLabelFilter={setFilterSeverity} updateLabelFilter={setFilterSeverity}