mirror of
https://github.com/blakeblackshear/frigate.git
synced 2024-11-22 08:57:20 -06:00
Add button for downloading full set of logs (#13188)
This commit is contained in:
parent
c268a126dc
commit
a77436eec3
@ -456,6 +456,19 @@ def vainfo():
|
||||
|
||||
@bp.route("/logs/<service>", methods=["GET"])
|
||||
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 = {
|
||||
"frigate": "/dev/shm/logs/frigate/current",
|
||||
"go2rtc": "/dev/shm/logs/go2rtc/current",
|
||||
@ -470,6 +483,9 @@ def logs(service: str):
|
||||
404,
|
||||
)
|
||||
|
||||
if request.args.get("download", type=bool, default=False):
|
||||
return download_logs(service_location)
|
||||
|
||||
start = request.args.get("start", type=int, default=0)
|
||||
end = request.args.get("end", type=int)
|
||||
|
||||
|
@ -21,16 +21,34 @@ import { cn } from "@/lib/utils";
|
||||
import { MdVerticalAlignBottom } from "react-icons/md";
|
||||
import { parseLogLines } from "@/utils/logUtil";
|
||||
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 };
|
||||
|
||||
function Logs() {
|
||||
const [logService, setLogService] = useState<LogType>("frigate");
|
||||
const tabsRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
document.title = `${logService[0].toUpperCase()}${logService.substring(1)} Logs - Frigate`;
|
||||
}, [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
|
||||
|
||||
const logPageSize = useMemo(() => {
|
||||
@ -118,6 +136,27 @@ function Logs() {
|
||||
}
|
||||
}, [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
|
||||
|
||||
const [initialScroll, setInitialScroll] = useState(false);
|
||||
@ -266,33 +305,37 @@ function Logs() {
|
||||
<Toaster position="top-center" closeButton={true} />
|
||||
<LogInfoDialog logLine={selectedLog} setLogLine={setSelectedLog} />
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<ToggleGroup
|
||||
className="*:rounded-md *:px-3 *:py-4"
|
||||
type="single"
|
||||
size="sm"
|
||||
value={logService}
|
||||
onValueChange={(value: LogType) => {
|
||||
if (value) {
|
||||
setLogs([]);
|
||||
setLogLines([]);
|
||||
setFilterSeverity(undefined);
|
||||
setLogService(value);
|
||||
}
|
||||
}} // 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="relative flex h-11 w-full items-center justify-between">
|
||||
<ScrollArea className="w-full whitespace-nowrap">
|
||||
<div ref={tabsRef} className="flex flex-row">
|
||||
<ToggleGroup
|
||||
type="single"
|
||||
size="sm"
|
||||
value={logService}
|
||||
onValueChange={(value: LogType) => {
|
||||
if (value) {
|
||||
setLogs([]);
|
||||
setLogLines([]);
|
||||
setFilterSeverity(undefined);
|
||||
setLogService(value);
|
||||
}
|
||||
}} // don't allow the severity to be unselected
|
||||
>
|
||||
<div className="capitalize">{item}</div>
|
||||
</ToggleGroupItem>
|
||||
))}
|
||||
</ToggleGroup>
|
||||
{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>
|
||||
</ToggleGroupItem>
|
||||
))}
|
||||
</ToggleGroup>
|
||||
<ScrollBar orientation="horizontal" className="h-0" />
|
||||
</div>
|
||||
</ScrollArea>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
className="flex items-center justify-between gap-2"
|
||||
@ -304,6 +347,14 @@ function Logs() {
|
||||
Copy to Clipboard
|
||||
</div>
|
||||
</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
|
||||
selectedLabels={filterSeverity}
|
||||
updateLabelFilter={setFilterSeverity}
|
||||
|
Loading…
Reference in New Issue
Block a user