mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-06-02 07:00:32 +00:00
Debug replay fixes (#23276)
* filter replay camera from camera selectors * add face rec and lpr to replay configuration sheet * add missing config topic subscriptions in embeddings maintainer * pop replay camera from config object when stopping
This commit is contained in:
parent
01c82d6921
commit
555ef89800
@ -169,6 +169,7 @@ class DebugReplayManager:
|
||||
CameraConfigUpdateTopic(CameraConfigUpdateEnum.remove, replay_name),
|
||||
frigate_config.cameras[replay_name],
|
||||
)
|
||||
frigate_config.cameras.pop(replay_name, None)
|
||||
|
||||
if replay_name is not None:
|
||||
self._cleanup_db(replay_name)
|
||||
|
||||
@ -98,10 +98,17 @@ class EmbeddingMaintainer(threading.Thread):
|
||||
[
|
||||
CameraConfigUpdateEnum.add,
|
||||
CameraConfigUpdateEnum.remove,
|
||||
CameraConfigUpdateEnum.detect,
|
||||
CameraConfigUpdateEnum.face_recognition,
|
||||
CameraConfigUpdateEnum.ffmpeg,
|
||||
CameraConfigUpdateEnum.lpr,
|
||||
CameraConfigUpdateEnum.motion,
|
||||
CameraConfigUpdateEnum.objects,
|
||||
CameraConfigUpdateEnum.object_genai,
|
||||
CameraConfigUpdateEnum.review,
|
||||
CameraConfigUpdateEnum.review_genai,
|
||||
CameraConfigUpdateEnum.semantic_search,
|
||||
CameraConfigUpdateEnum.zones,
|
||||
],
|
||||
)
|
||||
self.enrichment_config_subscriber = ConfigSubscriber("config/")
|
||||
|
||||
@ -14,6 +14,7 @@ import Konva from "konva";
|
||||
import { useResizeObserver } from "@/hooks/resize-observer";
|
||||
import { useApiHost } from "@/api";
|
||||
import { resolveCameraName } from "@/hooks/use-camera-friendly-name";
|
||||
import { isReplayCamera } from "@/utils/cameraUtil";
|
||||
import Heading from "@/components/ui/heading";
|
||||
import { isMobile } from "react-device-detect";
|
||||
import { cn } from "@/lib/utils";
|
||||
@ -67,6 +68,7 @@ export default function Step2StateArea({
|
||||
([name, cam]) =>
|
||||
cam.enabled &&
|
||||
cam.enabled_in_config &&
|
||||
!isReplayCamera(name) &&
|
||||
!selectedCameraNames.includes(name),
|
||||
)
|
||||
.map(([name]) => ({
|
||||
|
||||
@ -57,6 +57,7 @@ import isEqual from "lodash/isEqual";
|
||||
import set from "lodash/set";
|
||||
import type { ConfigSectionData, JsonObject } from "@/types/configForm";
|
||||
import { sanitizeSectionData } from "@/utils/configUtil";
|
||||
import { isReplayCamera } from "@/utils/cameraUtil";
|
||||
import type { SectionRendererProps } from "./registry";
|
||||
|
||||
const NOTIFICATION_SERVICE_WORKER = "/notifications-worker.js";
|
||||
@ -94,7 +95,7 @@ export default function NotificationsSettingsExtras({
|
||||
|
||||
return Object.values(config.cameras)
|
||||
.sort((aConf, bConf) => aConf.ui.order - bConf.ui.order)
|
||||
.filter((c) => c.enabled_in_config);
|
||||
.filter((c) => c.enabled_in_config && !isReplayCamera(c.name));
|
||||
}, [config]);
|
||||
|
||||
const notificationCameras = useMemo(() => {
|
||||
@ -106,6 +107,7 @@ export default function NotificationsSettingsExtras({
|
||||
.filter(
|
||||
(conf) =>
|
||||
conf.enabled_in_config &&
|
||||
!isReplayCamera(conf.name) &&
|
||||
conf.notifications &&
|
||||
conf.notifications.enabled_in_config,
|
||||
)
|
||||
@ -359,6 +361,7 @@ export default function NotificationsSettingsExtras({
|
||||
Object.values(config.cameras).some(
|
||||
(c) =>
|
||||
c.enabled_in_config &&
|
||||
!isReplayCamera(c.name) &&
|
||||
c.notifications &&
|
||||
c.notifications.enabled_in_config,
|
||||
),
|
||||
|
||||
@ -26,6 +26,7 @@ import {
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { FrigateConfig } from "@/types/frigateConfig";
|
||||
import { CameraNameLabel } from "../camera/FriendlyNameLabel";
|
||||
import { isReplayCamera } from "@/utils/cameraUtil";
|
||||
import { isDesktop, isMobile } from "react-device-detect";
|
||||
import { cn } from "@/lib/utils";
|
||||
import {
|
||||
@ -52,7 +53,9 @@ export default function CreateRoleDialog({
|
||||
const { t } = useTranslation(["views/settings"]);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
|
||||
const cameras = Object.keys(config.cameras || {});
|
||||
const cameras = Object.keys(config.cameras || {}).filter(
|
||||
(name) => !isReplayCamera(name),
|
||||
);
|
||||
|
||||
const existingRoles = Object.keys(config.auth?.roles || {});
|
||||
|
||||
|
||||
@ -25,6 +25,7 @@ import {
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import { FrigateConfig } from "@/types/frigateConfig";
|
||||
import { CameraNameLabel } from "@/components/camera/FriendlyNameLabel";
|
||||
import { isReplayCamera } from "@/utils/cameraUtil";
|
||||
|
||||
type EditRoleCamerasOverlayProps = {
|
||||
show: boolean;
|
||||
@ -46,7 +47,9 @@ export default function EditRoleCamerasDialog({
|
||||
const { t } = useTranslation(["views/settings"]);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
|
||||
const cameras = Object.keys(config.cameras || {});
|
||||
const cameras = Object.keys(config.cameras || {}).filter(
|
||||
(name) => !isReplayCamera(name),
|
||||
);
|
||||
|
||||
const formSchema = z.object({
|
||||
cameras: z
|
||||
|
||||
@ -54,6 +54,7 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "../ui/tabs";
|
||||
import { Textarea } from "../ui/textarea";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useIsAdmin } from "@/hooks/use-is-admin";
|
||||
import { isReplayCamera } from "@/utils/cameraUtil";
|
||||
|
||||
const EXPORT_OPTIONS = [
|
||||
"1",
|
||||
@ -448,7 +449,9 @@ export function ExportContent({
|
||||
);
|
||||
|
||||
const cameraActivities = useMemo<CameraActivity[]>(() => {
|
||||
const allCameraIds = Object.keys(config?.cameras ?? {});
|
||||
const allCameraIds = Object.keys(config?.cameras ?? {}).filter(
|
||||
(name) => !isReplayCamera(name),
|
||||
);
|
||||
const byCamera = new Map<string, Event[]>();
|
||||
|
||||
events?.forEach((event) => {
|
||||
|
||||
@ -13,6 +13,7 @@ import {
|
||||
} from "@/components/ui/select";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { formatUnixTimestampToDateTime } from "@/utils/dateUtil";
|
||||
import { isReplayCamera } from "@/utils/cameraUtil";
|
||||
import { useTimezone } from "@/hooks/use-date-utils";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { LuX } from "react-icons/lu";
|
||||
@ -36,11 +37,16 @@ export default function ObjectPathPlotter() {
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const eventsPerPage = 20;
|
||||
|
||||
const cameraNames = useMemo(() => {
|
||||
if (!config) return [];
|
||||
return Object.keys(config.cameras).filter((name) => !isReplayCamera(name));
|
||||
}, [config]);
|
||||
|
||||
useEffect(() => {
|
||||
if (config && !selectedCamera) {
|
||||
setSelectedCamera(Object.keys(config.cameras)[0]);
|
||||
if (cameraNames.length > 0 && !selectedCamera) {
|
||||
setSelectedCamera(cameraNames[0]);
|
||||
}
|
||||
}, [config, selectedCamera]);
|
||||
}, [cameraNames, selectedCamera]);
|
||||
|
||||
const searchQuery = useMemo(() => {
|
||||
if (!selectedCamera) return null;
|
||||
@ -143,12 +149,11 @@ export default function ObjectPathPlotter() {
|
||||
<SelectValue placeholder="Select camera" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{config &&
|
||||
Object.keys(config.cameras).map((cameraName) => (
|
||||
<SelectItem key={cameraName} value={cameraName}>
|
||||
{cameraName}
|
||||
</SelectItem>
|
||||
))}
|
||||
{cameraNames.map((cameraName) => (
|
||||
<SelectItem key={cameraName} value={cameraName}>
|
||||
{cameraName}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Select value={timeRange} onValueChange={setTimeRange}>
|
||||
|
||||
@ -18,6 +18,7 @@ import {
|
||||
} from "@/utils/configUtil";
|
||||
import { extractSectionSchema } from "@/hooks/use-config-schema";
|
||||
import { applySchemaDefaults } from "@/lib/config-schema";
|
||||
import { isReplayCamera } from "@/utils/cameraUtil";
|
||||
|
||||
const INTERNAL_FIELD_SUFFIXES = ["enabled_in_config", "raw_mask"];
|
||||
|
||||
@ -602,9 +603,13 @@ function getEffectiveGlobalBaseline(
|
||||
return normalizeConfigValue(defaults as JsonValue);
|
||||
}
|
||||
}
|
||||
const cameraSectionValues = Object.keys(config.cameras ?? {}).map((name) =>
|
||||
normalizeConfigValue(getBaseCameraSectionValue(config, name, sectionPath)),
|
||||
);
|
||||
const cameraSectionValues = Object.keys(config.cameras ?? {})
|
||||
.filter((name) => !isReplayCamera(name))
|
||||
.map((name) =>
|
||||
normalizeConfigValue(
|
||||
getBaseCameraSectionValue(config, name, sectionPath),
|
||||
),
|
||||
);
|
||||
return deriveSyntheticGlobalValue(cameraSectionValues, compareFields);
|
||||
}
|
||||
|
||||
@ -684,7 +689,9 @@ export function useCamerasOverridingSection(
|
||||
const sectionMeta = OVERRIDABLE_SECTIONS.find((s) => s.key === sectionPath);
|
||||
const compareFields = sectionMeta?.compareFields;
|
||||
|
||||
const cameraNames = Object.keys(config.cameras);
|
||||
const cameraNames = Object.keys(config.cameras).filter(
|
||||
(name) => !isReplayCamera(name),
|
||||
);
|
||||
const cameraSectionValues = cameraNames.map((name) =>
|
||||
normalizeConfigValue(
|
||||
getBaseCameraSectionValue(config, name, sectionPath),
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { useAllowedCameras } from "@/hooks/use-allowed-cameras";
|
||||
import useSWR from "swr";
|
||||
import { FrigateConfig } from "@/types/frigateConfig";
|
||||
import { isReplayCamera } from "@/utils/cameraUtil";
|
||||
|
||||
/**
|
||||
* Returns true if the current user has access to all cameras.
|
||||
@ -16,7 +17,7 @@ export function useHasFullCameraAccess() {
|
||||
if (!config?.cameras) return false;
|
||||
|
||||
const enabledCameraNames = Object.entries(config.cameras)
|
||||
.filter(([, cam]) => cam.enabled_in_config)
|
||||
.filter(([name, cam]) => cam.enabled_in_config && !isReplayCamera(name))
|
||||
.map(([name]) => name);
|
||||
|
||||
return (
|
||||
|
||||
@ -637,7 +637,7 @@ export default function Events() {
|
||||
}
|
||||
|
||||
setStartTime(recording.startTime);
|
||||
const allCameras = reviewFilter?.cameras ?? Object.keys(config.cameras);
|
||||
const allCameras = reviewFilter?.cameras ?? allowedCameras;
|
||||
|
||||
return {
|
||||
camera: recording.camera,
|
||||
|
||||
@ -378,6 +378,34 @@ export default function Replay() {
|
||||
showTitle
|
||||
showOverrideIndicator={false}
|
||||
/>
|
||||
{config?.face_recognition?.enabled && (
|
||||
<ConfigSectionTemplate
|
||||
sectionKey="face_recognition"
|
||||
level="replay"
|
||||
cameraName={status.replay_camera ?? undefined}
|
||||
skipSave
|
||||
noStickyButtons
|
||||
requiresRestart={false}
|
||||
collapsible
|
||||
defaultCollapsed={false}
|
||||
showTitle
|
||||
showOverrideIndicator={false}
|
||||
/>
|
||||
)}
|
||||
{config?.lpr?.enabled && (
|
||||
<ConfigSectionTemplate
|
||||
sectionKey="lpr"
|
||||
level="replay"
|
||||
cameraName={status.replay_camera ?? undefined}
|
||||
skipSave
|
||||
noStickyButtons
|
||||
requiresRestart={false}
|
||||
collapsible
|
||||
defaultCollapsed={false}
|
||||
showTitle
|
||||
showOverrideIndicator={false}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
|
||||
@ -100,6 +100,7 @@ import {
|
||||
} from "@/utils/configUtil";
|
||||
import type { ProfileState, ProfilesApiResponse } from "@/types/profile";
|
||||
import { getProfileColor } from "@/utils/profileColors";
|
||||
import { isReplayCamera } from "@/utils/cameraUtil";
|
||||
import { ProfileSectionDropdown } from "@/components/settings/ProfileSectionDropdown";
|
||||
import ActivityIndicator from "@/components/indicators/activity-indicator";
|
||||
import RestartDialog from "@/components/overlay/dialog/RestartDialog";
|
||||
@ -661,7 +662,12 @@ export default function Settings() {
|
||||
}
|
||||
|
||||
return Object.values(config.cameras)
|
||||
.filter((conf) => conf.ui.dashboard && conf.enabled_in_config)
|
||||
.filter(
|
||||
(conf) =>
|
||||
conf.ui.dashboard &&
|
||||
conf.enabled_in_config &&
|
||||
!isReplayCamera(conf.name),
|
||||
)
|
||||
.sort((aConf, bConf) => aConf.ui.order - bConf.ui.order);
|
||||
}, [config]);
|
||||
|
||||
|
||||
@ -32,6 +32,7 @@ import {
|
||||
ZoomLevel,
|
||||
} from "@/types/review";
|
||||
import { getChunkedTimeRange } from "@/utils/timelineUtil";
|
||||
import { isReplayCamera } from "@/utils/cameraUtil";
|
||||
import { getEndOfDayTimestamp } from "@/utils/dateUtil";
|
||||
import axios from "axios";
|
||||
import {
|
||||
@ -1015,12 +1016,14 @@ function MotionReview({
|
||||
|
||||
let cameras;
|
||||
if (!filter || !filter.cameras) {
|
||||
cameras = Object.values(config.cameras);
|
||||
cameras = Object.values(config.cameras).filter(
|
||||
(cam) => !isReplayCamera(cam.name),
|
||||
);
|
||||
} else {
|
||||
const filteredCams = filter.cameras;
|
||||
|
||||
cameras = Object.values(config.cameras).filter((cam) =>
|
||||
filteredCams.includes(cam.name),
|
||||
cameras = Object.values(config.cameras).filter(
|
||||
(cam) => filteredCams.includes(cam.name) && !isReplayCamera(cam.name),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -44,6 +44,7 @@ import {
|
||||
} from "@/components/ui/tooltip";
|
||||
import type { ProfileState } from "@/types/profile";
|
||||
import { getProfileColor } from "@/utils/profileColors";
|
||||
import { isReplayCamera } from "@/utils/cameraUtil";
|
||||
import { cn } from "@/lib/utils";
|
||||
import {
|
||||
Select,
|
||||
@ -87,7 +88,10 @@ export default function CameraManagementView({
|
||||
const enabledCameras = useMemo(() => {
|
||||
if (config) {
|
||||
return Object.keys(config.cameras)
|
||||
.filter((camera) => config.cameras[camera].enabled_in_config)
|
||||
.filter(
|
||||
(camera) =>
|
||||
config.cameras[camera].enabled_in_config && !isReplayCamera(camera),
|
||||
)
|
||||
.sort((a, b) => {
|
||||
const orderA = config.cameras[a].ui?.order ?? 0;
|
||||
const orderB = config.cameras[b].ui?.order ?? 0;
|
||||
@ -180,7 +184,11 @@ export default function CameraManagementView({
|
||||
const disabledCameras = useMemo(() => {
|
||||
if (config) {
|
||||
return Object.keys(config.cameras)
|
||||
.filter((camera) => !config.cameras[camera].enabled_in_config)
|
||||
.filter(
|
||||
(camera) =>
|
||||
!config.cameras[camera].enabled_in_config &&
|
||||
!isReplayCamera(camera),
|
||||
)
|
||||
.sort();
|
||||
}
|
||||
return [];
|
||||
@ -188,7 +196,9 @@ export default function CameraManagementView({
|
||||
|
||||
const allCameras = useMemo(() => {
|
||||
if (config) {
|
||||
return Object.keys(config.cameras).sort();
|
||||
return Object.keys(config.cameras)
|
||||
.filter((camera) => !isReplayCamera(camera))
|
||||
.sort();
|
||||
}
|
||||
return [];
|
||||
}, [config]);
|
||||
|
||||
@ -16,6 +16,7 @@ import FrigatePlusCurrentModelSummary from "@/views/settings/components/FrigateP
|
||||
import { useDocDomain } from "@/hooks/use-doc-domain";
|
||||
import { CameraNameLabel } from "@/components/camera/FriendlyNameLabel";
|
||||
import { FrigateConfig } from "@/types/frigateConfig";
|
||||
import { isReplayCamera } from "@/utils/cameraUtil";
|
||||
import type { SettingsPageProps } from "@/views/settings/SingleSectionPage";
|
||||
|
||||
export default function FrigatePlusSettingsView(_props: SettingsPageProps) {
|
||||
@ -139,8 +140,9 @@ export default function FrigatePlusSettingsView(_props: SettingsPageProps) {
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{Object.entries(config.cameras).map(
|
||||
([name, camera]) => (
|
||||
{Object.entries(config.cameras)
|
||||
.filter(([name]) => !isReplayCamera(name))
|
||||
.map(([name, camera]) => (
|
||||
<tr
|
||||
key={name}
|
||||
className="border-b border-secondary"
|
||||
@ -156,8 +158,7 @@ export default function FrigatePlusSettingsView(_props: SettingsPageProps) {
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
),
|
||||
)}
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@ -19,6 +19,7 @@ import type { JsonObject } from "@/types/configForm";
|
||||
import type { ProfileState, ProfilesApiResponse } from "@/types/profile";
|
||||
import { getProfileColor } from "@/utils/profileColors";
|
||||
import { PROFILE_ELIGIBLE_SECTIONS } from "@/utils/configUtil";
|
||||
import { isReplayCamera } from "@/utils/cameraUtil";
|
||||
import { resolveCameraName } from "@/hooks/use-camera-friendly-name";
|
||||
import { useDocDomain } from "@/hooks/use-doc-domain";
|
||||
import { cn } from "@/lib/utils";
|
||||
@ -145,7 +146,9 @@ export default function ProfilesView({
|
||||
if (!config || allProfileNames.length === 0) return {};
|
||||
|
||||
const data: Record<string, Record<string, string[]>> = {};
|
||||
const cameras = Object.keys(config.cameras).sort();
|
||||
const cameras = Object.keys(config.cameras)
|
||||
.filter((name) => !isReplayCamera(name))
|
||||
.sort();
|
||||
|
||||
for (const profile of allProfileNames) {
|
||||
data[profile] = {};
|
||||
|
||||
@ -25,6 +25,7 @@ import useSWR from "swr";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { CameraNameLabel } from "@/components/camera/FriendlyNameLabel";
|
||||
import { resolveCameraName } from "@/hooks/use-camera-friendly-name";
|
||||
import { isReplayCamera } from "@/utils/cameraUtil";
|
||||
|
||||
type CameraMetricsProps = {
|
||||
lastUpdated: number;
|
||||
@ -316,7 +317,7 @@ export default function CameraMetrics({
|
||||
<div className="grid grid-cols-1 gap-6 md:grid-cols-2">
|
||||
{config &&
|
||||
Object.values(config.cameras).map((camera) => {
|
||||
if (camera.enabled) {
|
||||
if (camera.enabled && !isReplayCamera(camera.name)) {
|
||||
return (
|
||||
<Fragment key={camera.name}>
|
||||
{probeCameraName == camera.name && (
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user