mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-07-02 18:11:13 +03:00
reorder sections for save and collapse to single put for new camera
This commit is contained in:
parent
f9f7d6e8fc
commit
b852b65024
@ -1,5 +1,6 @@
|
|||||||
import cloneDeep from "lodash/cloneDeep";
|
import cloneDeep from "lodash/cloneDeep";
|
||||||
import isEqual from "lodash/isEqual";
|
import isEqual from "lodash/isEqual";
|
||||||
|
import merge from "lodash/merge";
|
||||||
import type { RJSFSchema } from "@rjsf/utils";
|
import type { RJSFSchema } from "@rjsf/utils";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -133,6 +134,37 @@ function stripResetMarkers(
|
|||||||
return Object.keys(result).length > 0 ? result : undefined;
|
return Object.keys(result).length > 0 ? result : undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collapse per-section payloads into one camera-level payload + `…/add`
|
||||||
|
* topic. New cameras are created atomically by the backend's `add`
|
||||||
|
* handler, so a single PUT avoids the intermediate-validation ordering
|
||||||
|
* problem (e.g. a `review` required_zone referencing zones not yet written)
|
||||||
|
* that the per-section path is subject to.
|
||||||
|
*/
|
||||||
|
function bundleNewCameraPayload(
|
||||||
|
payloads: SectionSavePayload[],
|
||||||
|
target: string,
|
||||||
|
): SectionSavePayload {
|
||||||
|
const prefix = `cameras.${target}`;
|
||||||
|
const camera: JsonObject = {};
|
||||||
|
for (const p of payloads) {
|
||||||
|
if (p.basePath === prefix) {
|
||||||
|
merge(camera, p.sanitizedOverrides);
|
||||||
|
} else if (p.basePath.startsWith(`${prefix}.`)) {
|
||||||
|
merge(camera, {
|
||||||
|
[p.basePath.slice(prefix.length + 1)]: p.sanitizedOverrides,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
basePath: prefix,
|
||||||
|
sanitizedOverrides: camera,
|
||||||
|
updateTopic: `config/cameras/${target}/add`,
|
||||||
|
needsRestart: true,
|
||||||
|
pendingDataKey: `${target}::add`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Drop empty `*_args` arrays from ffmpeg inputs. Mirrors
|
* Drop empty `*_args` arrays from ffmpeg inputs. Mirrors
|
||||||
* `sanitizeOverridesForSection`'s ffmpeg cleanup, which we don't go
|
* `sanitizeOverridesForSection`'s ffmpeg cleanup, which we don't go
|
||||||
@ -442,13 +474,19 @@ export function buildClonedCameraPayloads({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Section-backed categories — flow through prepareSectionSavePayload
|
// Section-backed categories — flow through prepareSectionSavePayload
|
||||||
// for matching restart/update-topic behavior.
|
// for matching restart/update-topic behavior. Order matters for the
|
||||||
|
// existing-camera multi-PUT path: each PUT re-validates the whole
|
||||||
|
// config, so dependency providers must precede consumers — `detect`
|
||||||
|
// (resolution) then `zones` before sections that reference zones via
|
||||||
|
// `required_zones` (review, objects, snapshots, mqtt).
|
||||||
const SECTION_KEYS: Array<{ key: CloneCategoryKey; section: string }> = [
|
const SECTION_KEYS: Array<{ key: CloneCategoryKey; section: string }> = [
|
||||||
|
{ key: "detect", section: "detect" },
|
||||||
|
{ key: "zones", section: "zones" },
|
||||||
|
{ key: "motion", section: "motion" },
|
||||||
|
{ key: "objects", section: "objects" },
|
||||||
{ key: "record", section: "record" },
|
{ key: "record", section: "record" },
|
||||||
{ key: "snapshots", section: "snapshots" },
|
{ key: "snapshots", section: "snapshots" },
|
||||||
{ key: "review", section: "review" },
|
{ key: "review", section: "review" },
|
||||||
{ key: "motion", section: "motion" },
|
|
||||||
{ key: "objects", section: "objects" },
|
|
||||||
{ key: "audio", section: "audio" },
|
{ key: "audio", section: "audio" },
|
||||||
{ key: "audio_transcription", section: "audio_transcription" },
|
{ key: "audio_transcription", section: "audio_transcription" },
|
||||||
{ key: "notifications", section: "notifications" },
|
{ key: "notifications", section: "notifications" },
|
||||||
@ -460,8 +498,6 @@ export function buildClonedCameraPayloads({
|
|||||||
{ key: "face_recognition", section: "face_recognition" },
|
{ key: "face_recognition", section: "face_recognition" },
|
||||||
{ key: "semantic_search", section: "semantic_search" },
|
{ key: "semantic_search", section: "semantic_search" },
|
||||||
{ key: "genai", section: "genai" },
|
{ key: "genai", section: "genai" },
|
||||||
{ key: "detect", section: "detect" },
|
|
||||||
{ key: "zones", section: "zones" },
|
|
||||||
];
|
];
|
||||||
|
|
||||||
// Synthetic target so we can reuse prepareSectionSavePayload unchanged.
|
// Synthetic target so we can reuse prepareSectionSavePayload unchanged.
|
||||||
@ -673,15 +709,19 @@ export function buildClonedCameraPayloads({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset markers are meaningless for a new camera; see stripResetMarkers.
|
// New camera: scrub Reset markers (see stripResetMarkers), then bundle
|
||||||
|
// into one atomic `…/add` PUT so the backend validates the full camera
|
||||||
|
// at once (avoids per-section ordering issues).
|
||||||
if (targetIsNew) {
|
if (targetIsNew) {
|
||||||
return payloads
|
const scrubbed = payloads
|
||||||
.map((p) => {
|
.map((p) => {
|
||||||
const cleaned = stripResetMarkers(p.sanitizedOverrides as JsonValue);
|
const cleaned = stripResetMarkers(p.sanitizedOverrides as JsonValue);
|
||||||
if (cleaned === undefined) return null;
|
return cleaned === undefined
|
||||||
return { ...p, sanitizedOverrides: cleaned as JsonObject };
|
? null
|
||||||
|
: { ...p, sanitizedOverrides: cleaned as JsonObject };
|
||||||
})
|
})
|
||||||
.filter((p): p is SectionSavePayload => p !== null);
|
.filter((p): p is SectionSavePayload => p !== null);
|
||||||
|
return [bundleNewCameraPayload(scrubbed, target)];
|
||||||
}
|
}
|
||||||
|
|
||||||
return payloads;
|
return payloads;
|
||||||
@ -695,12 +735,15 @@ export function buildClonePreviewItems(
|
|||||||
payloads: SectionSavePayload[],
|
payloads: SectionSavePayload[],
|
||||||
targetCamera: string,
|
targetCamera: string,
|
||||||
): SaveAllPreviewItem[] {
|
): SaveAllPreviewItem[] {
|
||||||
const cameraPrefix = `cameras.${targetCamera}.`;
|
const cameraBase = `cameras.${targetCamera}`;
|
||||||
return payloads.flatMap((p) => {
|
return payloads.flatMap((p) => {
|
||||||
const flattened = flattenOverrides(p.sanitizedOverrides as JsonValue);
|
const flattened = flattenOverrides(p.sanitizedOverrides as JsonValue);
|
||||||
const sectionRelativeBase = p.basePath.startsWith(cameraPrefix)
|
const sectionRelativeBase =
|
||||||
? p.basePath.slice(cameraPrefix.length)
|
p.basePath === cameraBase
|
||||||
: p.basePath;
|
? ""
|
||||||
|
: p.basePath.startsWith(`${cameraBase}.`)
|
||||||
|
? p.basePath.slice(cameraBase.length + 1)
|
||||||
|
: p.basePath;
|
||||||
return flattened.map(({ path, value }) => ({
|
return flattened.map(({ path, value }) => ({
|
||||||
scope: "camera" as const,
|
scope: "camera" as const,
|
||||||
cameraName: targetCamera,
|
cameraName: targetCamera,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user