mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-06-29 16:41:16 +03:00
add expanded hidden field context
This commit is contained in:
parent
b2ea1fe592
commit
702cc7133d
@ -20,6 +20,7 @@ import type { ProfilesApiResponse } from "@/types/profile";
|
|||||||
import { useCameraFriendlyName } from "@/hooks/use-camera-friendly-name";
|
import { useCameraFriendlyName } from "@/hooks/use-camera-friendly-name";
|
||||||
import { formatList } from "@/utils/stringUtil";
|
import { formatList } from "@/utils/stringUtil";
|
||||||
import {
|
import {
|
||||||
|
buildHiddenFieldContext,
|
||||||
getEffectiveHiddenFields,
|
getEffectiveHiddenFields,
|
||||||
pathMatchesHiddenPattern,
|
pathMatchesHiddenPattern,
|
||||||
} from "@/utils/configUtil";
|
} from "@/utils/configUtil";
|
||||||
@ -187,7 +188,7 @@ export function CameraOverridesBadge({ sectionPath, className }: Props) {
|
|||||||
const hiddenFields = getEffectiveHiddenFields(
|
const hiddenFields = getEffectiveHiddenFields(
|
||||||
sectionPath,
|
sectionPath,
|
||||||
"global",
|
"global",
|
||||||
config,
|
buildHiddenFieldContext(config, "global"),
|
||||||
);
|
);
|
||||||
if (hiddenFields.length === 0) return rawEntries;
|
if (hiddenFields.length === 0) return rawEntries;
|
||||||
return rawEntries
|
return rawEntries
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import { FrigateConfig } from "@/types/frigateConfig";
|
|||||||
import { JsonObject, JsonValue } from "@/types/configForm";
|
import { JsonObject, JsonValue } from "@/types/configForm";
|
||||||
import { isJsonObject } from "@/lib/utils";
|
import { isJsonObject } from "@/lib/utils";
|
||||||
import {
|
import {
|
||||||
|
buildHiddenFieldContext,
|
||||||
getBaseCameraSectionValue,
|
getBaseCameraSectionValue,
|
||||||
getEffectiveHiddenFields,
|
getEffectiveHiddenFields,
|
||||||
pathMatchesHiddenPattern,
|
pathMatchesHiddenPattern,
|
||||||
@ -286,7 +287,7 @@ export function useConfigOverride({
|
|||||||
const hiddenFields = getEffectiveHiddenFields(
|
const hiddenFields = getEffectiveHiddenFields(
|
||||||
sectionPath,
|
sectionPath,
|
||||||
"camera",
|
"camera",
|
||||||
config,
|
buildHiddenFieldContext(config, "camera", cameraName),
|
||||||
);
|
);
|
||||||
const collapsedGlobal = stripHiddenPaths(
|
const collapsedGlobal = stripHiddenPaths(
|
||||||
collapseEmpty(normalizedGlobalValue),
|
collapseEmpty(normalizedGlobalValue),
|
||||||
@ -439,7 +440,11 @@ export function useAllCameraOverrides(
|
|||||||
getBaseCameraSectionValue(config, cameraName, key),
|
getBaseCameraSectionValue(config, cameraName, key),
|
||||||
);
|
);
|
||||||
|
|
||||||
const hiddenFields = getEffectiveHiddenFields(key, "camera", config);
|
const hiddenFields = getEffectiveHiddenFields(
|
||||||
|
key,
|
||||||
|
"camera",
|
||||||
|
buildHiddenFieldContext(config, "camera", cameraName),
|
||||||
|
);
|
||||||
const collapsedGlobal = stripHiddenPaths(
|
const collapsedGlobal = stripHiddenPaths(
|
||||||
collapseEmpty(globalValue),
|
collapseEmpty(globalValue),
|
||||||
hiddenFields,
|
hiddenFields,
|
||||||
@ -795,7 +800,7 @@ export function useCameraSectionDeltas(
|
|||||||
const hiddenFields = getEffectiveHiddenFields(
|
const hiddenFields = getEffectiveHiddenFields(
|
||||||
sectionPath,
|
sectionPath,
|
||||||
"camera",
|
"camera",
|
||||||
config,
|
buildHiddenFieldContext(config, "camera", cameraName),
|
||||||
);
|
);
|
||||||
|
|
||||||
const deltas: FieldDelta[] = [];
|
const deltas: FieldDelta[] = [];
|
||||||
@ -864,7 +869,7 @@ export function useProfileSectionDeltas(
|
|||||||
const hiddenFields = getEffectiveHiddenFields(
|
const hiddenFields = getEffectiveHiddenFields(
|
||||||
sectionPath,
|
sectionPath,
|
||||||
"camera",
|
"camera",
|
||||||
config,
|
buildHiddenFieldContext(config, "camera", cameraName),
|
||||||
);
|
);
|
||||||
|
|
||||||
const deltas: FieldDelta[] = [];
|
const deltas: FieldDelta[] = [];
|
||||||
|
|||||||
@ -89,6 +89,7 @@ import { mutate } from "swr";
|
|||||||
import { RJSFSchema } from "@rjsf/utils";
|
import { RJSFSchema } from "@rjsf/utils";
|
||||||
import {
|
import {
|
||||||
buildConfigDataForPath,
|
buildConfigDataForPath,
|
||||||
|
buildHiddenFieldContext,
|
||||||
flattenOverrides,
|
flattenOverrides,
|
||||||
getSectionConfig,
|
getSectionConfig,
|
||||||
parseProfileFromSectionPath,
|
parseProfileFromSectionPath,
|
||||||
@ -851,11 +852,11 @@ export default function Settings() {
|
|||||||
// they stay in sync with what the embedded forms strip on render
|
// they stay in sync with what the embedded forms strip on render
|
||||||
const detectorHiddenFields = resolveHiddenFieldEntries(
|
const detectorHiddenFields = resolveHiddenFieldEntries(
|
||||||
getSectionConfig("detectors", "global").hiddenFields,
|
getSectionConfig("detectors", "global").hiddenFields,
|
||||||
config,
|
buildHiddenFieldContext(config, "global"),
|
||||||
);
|
);
|
||||||
const modelHiddenFields = resolveHiddenFieldEntries(
|
const modelHiddenFields = resolveHiddenFieldEntries(
|
||||||
getSectionConfig("model", "global").hiddenFields,
|
getSectionConfig("model", "global").hiddenFields,
|
||||||
config,
|
buildHiddenFieldContext(config, "global"),
|
||||||
);
|
);
|
||||||
const sanitizedDetectors =
|
const sanitizedDetectors =
|
||||||
pendingDetectors !== undefined
|
pendingDetectors !== undefined
|
||||||
|
|||||||
@ -13,7 +13,19 @@ export type JsonArray = JsonValue[];
|
|||||||
|
|
||||||
export type ConfigSectionData = JsonObject;
|
export type ConfigSectionData = JsonObject;
|
||||||
|
|
||||||
export type HiddenFieldEntry = string | ((config: FrigateConfig) => string[]);
|
export type HiddenFieldContext = {
|
||||||
|
fullConfig: FrigateConfig;
|
||||||
|
fullCameraConfig?: CameraConfig;
|
||||||
|
level: "global" | "camera" | "replay";
|
||||||
|
cameraName?: string;
|
||||||
|
// Saved form data for the current section/scope (i.e. rawFormData in
|
||||||
|
// BaseSection.tsx). Not the user's in-flight RJSF edits. Optional because
|
||||||
|
// most hidden-field callsites compute patterns without a specific section
|
||||||
|
// value on hand; resolvers fall back to fullCameraConfig / fullConfig.
|
||||||
|
formData?: ConfigSectionData;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type HiddenFieldEntry = string | ((ctx: HiddenFieldContext) => string[]);
|
||||||
|
|
||||||
export type ConfigFormContext = {
|
export type ConfigFormContext = {
|
||||||
level?: "global" | "camera";
|
level?: "global" | "camera";
|
||||||
|
|||||||
@ -22,6 +22,7 @@ import type { RJSFSchema } from "@rjsf/utils";
|
|||||||
import type { FrigateConfig } from "@/types/frigateConfig";
|
import type { FrigateConfig } from "@/types/frigateConfig";
|
||||||
import type {
|
import type {
|
||||||
ConfigSectionData,
|
ConfigSectionData,
|
||||||
|
HiddenFieldContext,
|
||||||
JsonObject,
|
JsonObject,
|
||||||
JsonValue,
|
JsonValue,
|
||||||
} from "@/types/configForm";
|
} from "@/types/configForm";
|
||||||
@ -568,6 +569,17 @@ export function prepareSectionSavePayload(opts: {
|
|||||||
schemaSection,
|
schemaSection,
|
||||||
level,
|
level,
|
||||||
sectionSchema,
|
sectionSchema,
|
||||||
|
config
|
||||||
|
? {
|
||||||
|
fullConfig: config,
|
||||||
|
fullCameraConfig:
|
||||||
|
level === "camera" && cameraName
|
||||||
|
? config.cameras?.[cameraName]
|
||||||
|
: undefined,
|
||||||
|
level,
|
||||||
|
cameraName,
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Compute rawFormData (the current stored value for this section)
|
// Compute rawFormData (the current stored value for this section)
|
||||||
@ -615,10 +627,16 @@ export function prepareSectionSavePayload(opts: {
|
|||||||
// For profile sections, also hide restart-required fields to match
|
// For profile sections, also hide restart-required fields to match
|
||||||
// effectiveHiddenFields in BaseSection (prevents spurious deletion markers
|
// effectiveHiddenFields in BaseSection (prevents spurious deletion markers
|
||||||
// for fields that are hidden from the form during profile editing).
|
// for fields that are hidden from the form during profile editing).
|
||||||
const resolvedHidden = resolveHiddenFieldEntries(
|
const resolvedHidden = resolveHiddenFieldEntries(sectionConfig.hiddenFields, {
|
||||||
sectionConfig.hiddenFields,
|
fullConfig: config,
|
||||||
config,
|
fullCameraConfig:
|
||||||
);
|
level === "camera" && cameraName
|
||||||
|
? config.cameras?.[cameraName]
|
||||||
|
: undefined,
|
||||||
|
level,
|
||||||
|
cameraName,
|
||||||
|
formData: rawFormData as ConfigSectionData,
|
||||||
|
});
|
||||||
const hiddenFieldsForSanitize =
|
const hiddenFieldsForSanitize =
|
||||||
profileInfo.isProfile && sectionConfig.restartRequired?.length
|
profileInfo.isProfile && sectionConfig.restartRequired?.length
|
||||||
? [...new Set([...resolvedHidden, ...sectionConfig.restartRequired])]
|
? [...new Set([...resolvedHidden, ...sectionConfig.restartRequired])]
|
||||||
@ -731,32 +749,57 @@ export function getSectionConfig(
|
|||||||
return mergeSectionConfig(entry.base, overrides);
|
return mergeSectionConfig(entry.base, overrides);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build a `HiddenFieldContext` for the common case where a callsite has
|
||||||
|
* `config`, an optional `cameraName`, and a level, but no per-section
|
||||||
|
* saved form data to thread through. Resolvers that don't read `formData`
|
||||||
|
* (which is most of them) just fall through to `fullCameraConfig` /
|
||||||
|
* `fullConfig`.
|
||||||
|
*/
|
||||||
|
export function buildHiddenFieldContext(
|
||||||
|
config: FrigateConfig | undefined,
|
||||||
|
level: "global" | "camera" | "replay",
|
||||||
|
cameraName?: string,
|
||||||
|
): HiddenFieldContext | undefined {
|
||||||
|
if (!config) return undefined;
|
||||||
|
return {
|
||||||
|
fullConfig: config,
|
||||||
|
fullCameraConfig:
|
||||||
|
level !== "global" && cameraName
|
||||||
|
? config.cameras?.[cameraName]
|
||||||
|
: undefined,
|
||||||
|
level,
|
||||||
|
cameraName,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolve the effective hidden-field patterns for a section. Each entry in
|
* Resolve the effective hidden-field patterns for a section. Each entry in
|
||||||
* `hiddenFields` is either a literal pattern or a function that produces
|
* `hiddenFields` is either a literal pattern or a function that produces
|
||||||
* patterns from the loaded config (e.g. `filters.<attr>` for each
|
* patterns from the loaded config and scope (e.g. `filters.<attr>` for each
|
||||||
* `model.all_attributes` entry on the objects section).
|
* `model.all_attributes` entry on the objects section, gated by the
|
||||||
|
* effective `objects.track` list at the current scope).
|
||||||
*/
|
*/
|
||||||
export function getEffectiveHiddenFields(
|
export function getEffectiveHiddenFields(
|
||||||
sectionKey: string,
|
sectionKey: string,
|
||||||
level: "global" | "camera" | "replay",
|
level: "global" | "camera" | "replay",
|
||||||
config: FrigateConfig | undefined,
|
ctx: HiddenFieldContext | undefined,
|
||||||
): string[] {
|
): string[] {
|
||||||
return resolveHiddenFieldEntries(
|
return resolveHiddenFieldEntries(
|
||||||
getSectionConfig(sectionKey, level).hiddenFields,
|
getSectionConfig(sectionKey, level).hiddenFields,
|
||||||
config,
|
ctx,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function resolveHiddenFieldEntries(
|
export function resolveHiddenFieldEntries(
|
||||||
entries: SectionConfig["hiddenFields"] | undefined,
|
entries: SectionConfig["hiddenFields"] | undefined,
|
||||||
config: FrigateConfig | undefined,
|
ctx: HiddenFieldContext | undefined,
|
||||||
): string[] {
|
): string[] {
|
||||||
if (!entries || entries.length === 0) return [];
|
if (!entries || entries.length === 0) return [];
|
||||||
const result: string[] = [];
|
const result: string[] = [];
|
||||||
for (const entry of entries) {
|
for (const entry of entries) {
|
||||||
if (typeof entry === "function") {
|
if (typeof entry === "function") {
|
||||||
if (config) result.push(...entry(config));
|
if (ctx) result.push(...entry(ctx));
|
||||||
} else {
|
} else {
|
||||||
result.push(entry);
|
result.push(entry);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -51,6 +51,7 @@ import { ConfigSectionTemplate } from "@/components/config-form/sections";
|
|||||||
import { ConfigMessageBanner } from "@/components/config-form/ConfigMessageBanner";
|
import { ConfigMessageBanner } from "@/components/config-form/ConfigMessageBanner";
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||||
import {
|
import {
|
||||||
|
buildHiddenFieldContext,
|
||||||
getSectionConfig,
|
getSectionConfig,
|
||||||
resolveHiddenFieldEntries,
|
resolveHiddenFieldEntries,
|
||||||
sanitizeSectionData,
|
sanitizeSectionData,
|
||||||
@ -226,7 +227,7 @@ export default function DetectorsAndModelSettingsView({
|
|||||||
() =>
|
() =>
|
||||||
resolveHiddenFieldEntries(
|
resolveHiddenFieldEntries(
|
||||||
getSectionConfig("detectors", "global").hiddenFields,
|
getSectionConfig("detectors", "global").hiddenFields,
|
||||||
config,
|
buildHiddenFieldContext(config, "global"),
|
||||||
),
|
),
|
||||||
[config],
|
[config],
|
||||||
);
|
);
|
||||||
@ -234,7 +235,7 @@ export default function DetectorsAndModelSettingsView({
|
|||||||
() =>
|
() =>
|
||||||
resolveHiddenFieldEntries(
|
resolveHiddenFieldEntries(
|
||||||
getSectionConfig("model", "global").hiddenFields,
|
getSectionConfig("model", "global").hiddenFields,
|
||||||
config,
|
buildHiddenFieldContext(config, "global"),
|
||||||
),
|
),
|
||||||
[config],
|
[config],
|
||||||
);
|
);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user