diff --git a/frigate/app.py b/frigate/app.py index 78a2b0132..d0d5b76c7 100644 --- a/frigate/app.py +++ b/frigate/app.py @@ -62,6 +62,7 @@ from frigate.stats.util import stats_init from frigate.storage import StorageMaintainer from frigate.timeline import TimelineProcessor from frigate.types import CameraMetricsTypes, PTZMetricsTypes +from frigate.util.builtin import save_default_config from frigate.util.object import get_camera_regions_grid from frigate.version import VERSION from frigate.video import capture_camera, track_camera @@ -120,6 +121,11 @@ class FrigateApp: if os.path.isfile(config_file_yaml): config_file = config_file_yaml + if not os.path.isfile(config_file): + print("No config file found, saving default config") + config_file = config_file_yaml + save_default_config(config_file) + user_config = FrigateConfig.parse_file(config_file) self.config = user_config.runtime_config(self.plus_api) @@ -499,7 +505,7 @@ class FrigateApp: ) audio_process.daemon = True audio_process.start() - self.processes["audioDetector"] = audio_process.pid or 0 + self.processes["audio_detector"] = audio_process.pid or 0 logger.info(f"Audio process started: {audio_process.pid}") def start_timeline_processor(self) -> None: diff --git a/frigate/util/builtin.py b/frigate/util/builtin.py index 556b91ede..be91421f9 100644 --- a/frigate/util/builtin.py +++ b/frigate/util/builtin.py @@ -281,6 +281,32 @@ def find_by_key(dictionary, target_key): return None +def save_default_config(location: str): + try: + with open(location, "w") as f: + f.write( + """ +mqtt: + enabled: False + +cameras: + name_of_your_camera: # <------ Name the camera + enabled: True + ffmpeg: + inputs: + - path: rtsp://10.0.10.10:554/rtsp # <----- The stream you want to use for detection + roles: + - detect + detect: + enabled: False # <---- disable detection until you have a working camera feed + width: 1280 + height: 720 + """ + ) + except PermissionError: + logger.error("Unable to write default config to /config") + + def get_tomorrow_at_time(hour: int) -> datetime.datetime: """Returns the datetime of the following day at 2am.""" try: diff --git a/web/src/components/filter/CameraGroupSelector.tsx b/web/src/components/filter/CameraGroupSelector.tsx index b8ed05b7b..ec0ba89f4 100644 --- a/web/src/components/filter/CameraGroupSelector.tsx +++ b/web/src/components/filter/CameraGroupSelector.tsx @@ -87,7 +87,7 @@ export function CameraGroupSelector({ className }: CameraGroupSelectorProps) { className={ group == "default" ? "text-selected bg-blue-900 focus:bg-blue-900 bg-opacity-60 focus:bg-opacity-60" - : "text-muted-foreground bg-secondary focus:text-muted-foreground focus:bg-secondary" + : "text-secondary-foreground bg-secondary focus:text-secondary-foreground focus:bg-secondary" } size="xs" onClick={() => (group ? setGroup("default", true) : null)} @@ -109,7 +109,7 @@ export function CameraGroupSelector({ className }: CameraGroupSelectorProps) { className={ group == name ? "text-selected bg-blue-900 focus:bg-blue-900 bg-opacity-60 focus:bg-opacity-60" - : "text-muted-foreground bg-secondary" + : "text-secondary-foreground bg-secondary" } size="xs" onClick={() => setGroup(name, group != "default")} diff --git a/web/src/components/filter/ReviewActionGroup.tsx b/web/src/components/filter/ReviewActionGroup.tsx index bc1951f75..3ab8684f4 100644 --- a/web/src/components/filter/ReviewActionGroup.tsx +++ b/web/src/components/filter/ReviewActionGroup.tsx @@ -35,7 +35,7 @@ export default function ReviewActionGroup({ }, [selectedReviews, setSelectedReviews, pullLatestData]); return ( -
+
{`${selectedReviews.length} selected`}
{"|"}
@@ -58,7 +58,7 @@ export default function ReviewActionGroup({ }} > - {isDesktop && "Export"} + {isDesktop &&
Export
} )}
diff --git a/web/src/components/filter/ReviewFilterGroup.tsx b/web/src/components/filter/ReviewFilterGroup.tsx index f5b09836b..a3c3fadbc 100644 --- a/web/src/components/filter/ReviewFilterGroup.tsx +++ b/web/src/components/filter/ReviewFilterGroup.tsx @@ -223,8 +223,8 @@ function CamerasFilterButton({ variant="secondary" size="sm" > - -
+ +
{selectedCameras == undefined ? "All Cameras" : `${selectedCameras.length} Cameras`} @@ -368,7 +368,7 @@ function ShowReviewFilter({ ); return ( <> -
+
- -
+ +
{day == undefined ? "Last 24 Hours" : selectedDate}
@@ -473,8 +473,8 @@ function GeneralFilterButton({ const trigger = ( ); const content = ( @@ -546,7 +546,7 @@ export function GeneralFilterContent({
); } + +const GRAPH_COLORS = ["#5C7CFA", "#ED5CFA", "#FAD75C"]; + +type CameraLineGraphProps = { + graphId: string; + unit: string; + dataLabels: string[]; + updateTimes: number[]; + data: ApexAxisChartSeries; +}; +export function CameraLineGraph({ + graphId, + unit, + dataLabels, + updateTimes, + data, +}: CameraLineGraphProps) { + const { data: config } = useSWR("config", { + revalidateOnFocus: false, + }); + + const lastValues = useMemo(() => { + if (!dataLabels || !data || data.length == 0) { + return undefined; + } + + return dataLabels.map( + (_, labelIdx) => + // @ts-expect-error y is valid + data[labelIdx].data[data[labelIdx].data.length - 1]?.y ?? 0, + ) as number[]; + }, [data, dataLabels]); + + const { theme, systemTheme } = useTheme(); + + const formatTime = useCallback( + (val: unknown) => { + if (val == 0) { + return; + } + + const date = new Date(updateTimes[Math.round(val as number)] * 1000); + return date.toLocaleTimeString([], { + hour12: config?.ui.time_format != "24hour", + hour: "2-digit", + minute: "2-digit", + }); + }, + [config, updateTimes], + ); + + const options = useMemo(() => { + return { + chart: { + id: graphId, + selection: { + enabled: false, + }, + toolbar: { + show: false, + }, + zoom: { + enabled: false, + }, + }, + colors: GRAPH_COLORS, + grid: { + show: false, + }, + legend: { + show: false, + }, + dataLabels: { + enabled: false, + }, + stroke: { + width: 1, + }, + tooltip: { + theme: systemTheme || theme, + }, + markers: { + size: 0, + }, + xaxis: { + tickAmount: 4, + tickPlacement: "between", + labels: { + offsetX: -30, + formatter: formatTime, + }, + axisBorder: { + show: false, + }, + axisTicks: { + show: false, + }, + }, + yaxis: { + show: false, + min: 0, + }, + } as ApexCharts.ApexOptions; + }, [graphId, systemTheme, theme, formatTime]); + + useEffect(() => { + ApexCharts.exec(graphId, "updateOptions", options, true, true); + }, [graphId, options]); + + return ( +
+ {lastValues && ( +
+ {dataLabels.map((label, labelIdx) => ( +
+ +
{label}
+
+ {lastValues[labelIdx]} + {unit} +
+
+ ))} +
+ )} + +
+ ); +} diff --git a/web/src/components/graph/TimelineGraph.tsx b/web/src/components/graph/TimelineGraph.tsx deleted file mode 100644 index 73b9b5fb6..000000000 --- a/web/src/components/graph/TimelineGraph.tsx +++ /dev/null @@ -1,101 +0,0 @@ -import { GraphData } from "@/types/graph"; -import Chart from "react-apexcharts"; - -type TimelineGraphProps = { - id: string; - data: GraphData[]; - start: number; - end: number; - objects: number[]; -}; - -/** - * A graph meant to be overlaid on top of a timeline - */ -export default function TimelineGraph({ - id, - data, - start, - end, - objects, -}: TimelineGraphProps) { - return ( - { - if (objects.includes(dataPointIndex)) { - return "#06b6d4"; - } else { - return "#991b1b"; - } - }, - ], - chart: { - id: id, - selection: { - enabled: false, - }, - toolbar: { - show: false, - }, - zoom: { - enabled: false, - }, - }, - dataLabels: { enabled: false }, - grid: { - show: false, - padding: { - bottom: 2, - top: -12, - left: -20, - right: 0, - }, - }, - legend: { - show: false, - position: "top", - }, - plotOptions: { - bar: { - columnWidth: "100%", - barHeight: "100%", - hideZeroBarsWhenGrouped: true, - }, - }, - stroke: { - width: 0, - }, - tooltip: { - enabled: false, - }, - xaxis: { - type: "datetime", - axisBorder: { - show: false, - }, - axisTicks: { - show: false, - }, - labels: { - show: false, - }, - min: start, - max: end, - }, - yaxis: { - axisBorder: { - show: false, - }, - labels: { - show: false, - }, - }, - }} - series={data} - height="100%" - /> - ); -} diff --git a/web/src/components/icons/LiveIcons.tsx b/web/src/components/icons/LiveIcons.tsx index dc491b612..fbac271ef 100644 --- a/web/src/components/icons/LiveIcons.tsx +++ b/web/src/components/icons/LiveIcons.tsx @@ -32,10 +32,10 @@ export function LiveListIcon({ layout }: LiveIconProps) { return (
); diff --git a/web/src/components/navigation/NavItem.tsx b/web/src/components/navigation/NavItem.tsx index 37436ffde..1b42e5f7b 100644 --- a/web/src/components/navigation/NavItem.tsx +++ b/web/src/components/navigation/NavItem.tsx @@ -12,11 +12,11 @@ import { TooltipPortal } from "@radix-ui/react-tooltip"; const variants = { primary: { active: "font-bold text-white bg-selected", - inactive: "text-muted-foreground bg-secondary", + inactive: "text-secondary-foreground bg-secondary", }, secondary: { active: "font-bold text-selected", - inactive: "text-muted-foreground", + inactive: "text-secondary-foreground", }, }; diff --git a/web/src/components/overlay/ExportDialog.tsx b/web/src/components/overlay/ExportDialog.tsx index 5475dd00c..6468c2d16 100644 --- a/web/src/components/overlay/ExportDialog.tsx +++ b/web/src/components/overlay/ExportDialog.tsx @@ -122,8 +122,8 @@ export default function ExportDialog({ setMode("select"); }} > - - {isDesktop && "Export"} + + {isDesktop &&
Export
} diff --git a/web/src/components/overlay/MobileReviewSettingsDrawer.tsx b/web/src/components/overlay/MobileReviewSettingsDrawer.tsx index 0a3319918..26c6d9bbc 100644 --- a/web/src/components/overlay/MobileReviewSettingsDrawer.tsx +++ b/web/src/components/overlay/MobileReviewSettingsDrawer.tsx @@ -137,7 +137,7 @@ export default function MobileReviewSettingsDrawer({ className="w-full flex justify-center items-center gap-2" onClick={() => setDrawerMode("export")} > - + Export )} @@ -146,7 +146,7 @@ export default function MobileReviewSettingsDrawer({ className="w-full flex justify-center items-center gap-2" onClick={() => setDrawerMode("calendar")} > - + Calendar )} @@ -155,7 +155,7 @@ export default function MobileReviewSettingsDrawer({ className="w-full flex justify-center items-center gap-2" onClick={() => setDrawerMode("filter")} > - + Filter )} @@ -282,7 +282,7 @@ export default function MobileReviewSettingsDrawer({ variant="secondary" onClick={() => setDrawerMode("select")} > - + diff --git a/web/src/components/overlay/MobileTimelineDrawer.tsx b/web/src/components/overlay/MobileTimelineDrawer.tsx index b29fde559..806651f1a 100644 --- a/web/src/components/overlay/MobileTimelineDrawer.tsx +++ b/web/src/components/overlay/MobileTimelineDrawer.tsx @@ -23,7 +23,7 @@ export default function MobileTimelineDrawer({ diff --git a/web/src/components/player/HlsVideoPlayer.tsx b/web/src/components/player/HlsVideoPlayer.tsx index b6480d6bd..2bbf4d801 100644 --- a/web/src/components/player/HlsVideoPlayer.tsx +++ b/web/src/components/player/HlsVideoPlayer.tsx @@ -92,7 +92,7 @@ export default function HlsVideoPlayer({ return (
{ @@ -112,9 +112,11 @@ export default function HlsVideoPlayer({