mirror of
				https://github.com/blakeblackshear/frigate.git
				synced 2025-10-27 10:52:11 +01: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"])
 | 
					@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)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -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}
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user