From 1b3fb08306e398e11c19eb2c394378c50e89cb27 Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Tue, 19 May 2026 08:12:02 -0500 Subject: [PATCH] extend schema modification --- .../config-form/section-configs/objects.ts | 36 +++++++++++++--- .../config-form/sections/BaseSection.tsx | 43 +++++++++++++++++-- 2 files changed, 69 insertions(+), 10 deletions(-) diff --git a/web/src/components/config-form/section-configs/objects.ts b/web/src/components/config-form/section-configs/objects.ts index 3d27abb012..48b764df1a 100644 --- a/web/src/components/config-form/section-configs/objects.ts +++ b/web/src/components/config-form/section-configs/objects.ts @@ -1,12 +1,36 @@ -import type { FrigateConfig } from "@/types/frigateConfig"; +import type { HiddenFieldContext } from "@/types/configForm"; import type { SectionConfigOverrides } from "./types"; // Attribute labels (face, license_plate, Frigate+ couriers like DHL/Amazon, -// etc.) are populated into objects.filters by the backend even when the -// model can't actually detect them. They aren't user-settable, so hide any -// `filters.` patterns from forms and override comparisons. -const hideAttributeFilters = (config: FrigateConfig): string[] => - (config.model?.all_attributes ?? []).map((attr) => `filters.${attr}`); +// etc.) are populated into objects.filters by the backend for every +// attribute the model knows about. Hide the filter collapsible for an +// attribute unless it's in the effective objects.track list at this scope. +// When an attribute IS tracked, only a subset of fields are exposed — see the +// schema-modification path in modifySchemaForSection (objects branch) which +// promotes tracked attribute keys to explicit `properties` with a +// restricted FilterConfig shape so RJSF renders just that one field. +const hideAttributeFilters = ({ + fullConfig, + fullCameraConfig, + level, + formData, +}: HiddenFieldContext): string[] => { + const trackFromForm = Array.isArray( + (formData as { track?: unknown } | undefined)?.track, + ) + ? (formData as { track: string[] }).track + : undefined; + + const track = + trackFromForm ?? + (level !== "global" ? fullCameraConfig?.objects?.track : undefined) ?? + fullConfig.objects?.track ?? + []; + + return (fullConfig.model?.all_attributes ?? []) + .filter((attr) => !track.includes(attr)) + .map((attr) => `filters.${attr}`); +}; const objects: SectionConfigOverrides = { base: { diff --git a/web/src/components/config-form/sections/BaseSection.tsx b/web/src/components/config-form/sections/BaseSection.tsx index 9245fa2409..e61ac8a6a7 100644 --- a/web/src/components/config-form/sections/BaseSection.tsx +++ b/web/src/components/config-form/sections/BaseSection.tsx @@ -308,11 +308,30 @@ export function ConfigSection({ // Get section schema using cached hook const sectionSchema = useSectionSchema(sectionPath, effectiveLevel); - // Apply special case handling for sections with problematic schema defaults + // Apply special case handling for sections with problematic schema defaults. + // The HiddenFieldContext is built from `config` (saved state) only — not the + // in-flight raw section value — because the schema is computed before + // rawFormData is derived. The objects-branch fallback in + // modifySchemaForSection reads `track` from fullCameraConfig / fullConfig. const modifiedSchema = useMemo( () => - modifySchemaForSection(sectionPath, level, sectionSchema ?? undefined), - [sectionPath, level, sectionSchema], + modifySchemaForSection( + sectionPath, + level, + sectionSchema ?? undefined, + config + ? { + fullConfig: config, + fullCameraConfig: + effectiveLevel === "camera" && cameraName + ? config.cameras?.[cameraName] + : undefined, + level, + cameraName, + } + : undefined, + ), + [sectionPath, level, sectionSchema, config, effectiveLevel, cameraName], ); // Get override status (camera vs global) @@ -384,7 +403,19 @@ export function ConfigSection({ // When editing a profile, hide fields that require a restart since they // cannot take effect via profile switching alone. const effectiveHiddenFields = useMemo(() => { - const base = resolveHiddenFieldEntries(sectionConfig.hiddenFields, config); + const ctx = config + ? { + fullConfig: config, + fullCameraConfig: + effectiveLevel === "camera" && cameraName + ? config.cameras?.[cameraName] + : undefined, + level, + cameraName, + formData: rawFormData, + } + : undefined; + const base = resolveHiddenFieldEntries(sectionConfig.hiddenFields, ctx); if (!profileName || !sectionConfig.restartRequired?.length) { return base; } @@ -394,6 +425,10 @@ export function ConfigSection({ sectionConfig.hiddenFields, sectionConfig.restartRequired, config, + effectiveLevel, + cameraName, + level, + rawFormData, ]); const sanitizeSectionData = useCallback(