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