diff --git a/web/public/locales/en/views/settings.json b/web/public/locales/en/views/settings.json index ae3066014..eb83ff717 100644 --- a/web/public/locales/en/views/settings.json +++ b/web/public/locales/en/views/settings.json @@ -1286,6 +1286,7 @@ }, "toast": { "success": "Settings saved successfully", + "successRestartRequired": "Settings saved successfully. Restart Frigate to apply your changes.", "error": "Failed to save settings", "validationError": "Validation failed: {{message}}", "resetSuccess": "Reset to global defaults", diff --git a/web/src/components/config-form/section-configs/audio.ts b/web/src/components/config-form/section-configs/audio.ts index 99e6131a0..b2a33f82c 100644 --- a/web/src/components/config-form/section-configs/audio.ts +++ b/web/src/components/config-form/section-configs/audio.ts @@ -2,6 +2,8 @@ import type { SectionConfigOverrides } from "./types"; const audio: SectionConfigOverrides = { base: { + sectionDocs: "/configuration/audio_detectors", + restartRequired: [], fieldOrder: [ "enabled", "listen", diff --git a/web/src/components/config-form/section-configs/audio_transcription.ts b/web/src/components/config-form/section-configs/audio_transcription.ts index f9d822a0d..3cc41dff1 100644 --- a/web/src/components/config-form/section-configs/audio_transcription.ts +++ b/web/src/components/config-form/section-configs/audio_transcription.ts @@ -2,6 +2,8 @@ import type { SectionConfigOverrides } from "./types"; const audioTranscription: SectionConfigOverrides = { base: { + sectionDocs: "/configuration/audio_detectors#audio-transcription", + restartRequired: [], fieldOrder: ["enabled", "language", "device", "model_size", "live_enabled"], hiddenFields: ["enabled_in_config"], advancedFields: ["language", "device", "model_size"], diff --git a/web/src/components/config-form/section-configs/auth.ts b/web/src/components/config-form/section-configs/auth.ts index bc7374c6c..b2c218d7b 100644 --- a/web/src/components/config-form/section-configs/auth.ts +++ b/web/src/components/config-form/section-configs/auth.ts @@ -2,6 +2,8 @@ import type { SectionConfigOverrides } from "./types"; const auth: SectionConfigOverrides = { base: { + sectionDocs: "/configuration/authentication", + restartRequired: [], fieldOrder: [ "enabled", "reset_admin_password", diff --git a/web/src/components/config-form/section-configs/birdseye.ts b/web/src/components/config-form/section-configs/birdseye.ts index 9b0fa5ce7..f8affe660 100644 --- a/web/src/components/config-form/section-configs/birdseye.ts +++ b/web/src/components/config-form/section-configs/birdseye.ts @@ -2,6 +2,8 @@ import type { SectionConfigOverrides } from "./types"; const birdseye: SectionConfigOverrides = { base: { + sectionDocs: "/configuration/birdseye", + restartRequired: [], fieldOrder: ["enabled", "mode", "order"], hiddenFields: [], advancedFields: [], diff --git a/web/src/components/config-form/section-configs/classification.ts b/web/src/components/config-form/section-configs/classification.ts index c76697a4f..f2890762f 100644 --- a/web/src/components/config-form/section-configs/classification.ts +++ b/web/src/components/config-form/section-configs/classification.ts @@ -2,6 +2,8 @@ import type { SectionConfigOverrides } from "./types"; const classification: SectionConfigOverrides = { base: { + sectionDocs: "/configuration/custom_classification/object_classification", + restartRequired: [], hiddenFields: ["custom"], advancedFields: [], }, diff --git a/web/src/components/config-form/section-configs/database.ts b/web/src/components/config-form/section-configs/database.ts index 6170a157f..697735646 100644 --- a/web/src/components/config-form/section-configs/database.ts +++ b/web/src/components/config-form/section-configs/database.ts @@ -2,6 +2,8 @@ import type { SectionConfigOverrides } from "./types"; const database: SectionConfigOverrides = { base: { + sectionDocs: "/configuration/advanced#database", + restartRequired: [], fieldOrder: ["path"], advancedFields: [], }, diff --git a/web/src/components/config-form/section-configs/detect.ts b/web/src/components/config-form/section-configs/detect.ts index 29353ab3f..722d81ef3 100644 --- a/web/src/components/config-form/section-configs/detect.ts +++ b/web/src/components/config-form/section-configs/detect.ts @@ -2,6 +2,8 @@ import type { SectionConfigOverrides } from "./types"; const detect: SectionConfigOverrides = { base: { + sectionDocs: "/configuration/camera_specific", + restartRequired: [], fieldOrder: [ "enabled", "fps", diff --git a/web/src/components/config-form/section-configs/detectors.ts b/web/src/components/config-form/section-configs/detectors.ts index 40f8c20de..e7301c75a 100644 --- a/web/src/components/config-form/section-configs/detectors.ts +++ b/web/src/components/config-form/section-configs/detectors.ts @@ -2,6 +2,8 @@ import type { SectionConfigOverrides } from "./types"; const detectors: SectionConfigOverrides = { base: { + sectionDocs: "/configuration/object_detectors", + restartRequired: [], fieldOrder: [], advancedFields: [], hiddenFields: [ diff --git a/web/src/components/config-form/section-configs/environment_vars.ts b/web/src/components/config-form/section-configs/environment_vars.ts index e8bdcad1e..afeac4b4b 100644 --- a/web/src/components/config-form/section-configs/environment_vars.ts +++ b/web/src/components/config-form/section-configs/environment_vars.ts @@ -2,6 +2,8 @@ import type { SectionConfigOverrides } from "./types"; const environmentVars: SectionConfigOverrides = { base: { + sectionDocs: "/configuration/advanced#environment_vars", + restartRequired: [], fieldOrder: [], advancedFields: [], }, diff --git a/web/src/components/config-form/section-configs/face_recognition.ts b/web/src/components/config-form/section-configs/face_recognition.ts index ab76379c7..91f9c1a8e 100644 --- a/web/src/components/config-form/section-configs/face_recognition.ts +++ b/web/src/components/config-form/section-configs/face_recognition.ts @@ -2,6 +2,8 @@ import type { SectionConfigOverrides } from "./types"; const faceRecognition: SectionConfigOverrides = { base: { + sectionDocs: "/configuration/face_recognition", + restartRequired: [], fieldOrder: ["enabled", "min_area"], hiddenFields: [], advancedFields: ["min_area"], diff --git a/web/src/components/config-form/section-configs/ffmpeg.ts b/web/src/components/config-form/section-configs/ffmpeg.ts index e725a88c0..def8c3d37 100644 --- a/web/src/components/config-form/section-configs/ffmpeg.ts +++ b/web/src/components/config-form/section-configs/ffmpeg.ts @@ -2,6 +2,8 @@ import type { SectionConfigOverrides } from "./types"; const ffmpeg: SectionConfigOverrides = { base: { + sectionDocs: "/configuration/ffmpeg_presets", + restartRequired: [], fieldOrder: [ "inputs", "path", diff --git a/web/src/components/config-form/section-configs/genai.ts b/web/src/components/config-form/section-configs/genai.ts index d6627fbac..fbabac898 100644 --- a/web/src/components/config-form/section-configs/genai.ts +++ b/web/src/components/config-form/section-configs/genai.ts @@ -2,6 +2,8 @@ import type { SectionConfigOverrides } from "./types"; const genai: SectionConfigOverrides = { base: { + sectionDocs: "/configuration/genai/config", + restartRequired: [], fieldOrder: [ "provider", "api_key", diff --git a/web/src/components/config-form/section-configs/live.ts b/web/src/components/config-form/section-configs/live.ts index ac8e52472..5bdf34c8b 100644 --- a/web/src/components/config-form/section-configs/live.ts +++ b/web/src/components/config-form/section-configs/live.ts @@ -2,6 +2,8 @@ import type { SectionConfigOverrides } from "./types"; const live: SectionConfigOverrides = { base: { + sectionDocs: "/configuration/live", + restartRequired: [], fieldOrder: ["stream_name", "height", "quality"], fieldGroups: {}, hiddenFields: ["enabled_in_config"], diff --git a/web/src/components/config-form/section-configs/logger.ts b/web/src/components/config-form/section-configs/logger.ts index 75322e7b0..5d28ca6d8 100644 --- a/web/src/components/config-form/section-configs/logger.ts +++ b/web/src/components/config-form/section-configs/logger.ts @@ -2,6 +2,8 @@ import type { SectionConfigOverrides } from "./types"; const logger: SectionConfigOverrides = { base: { + sectionDocs: "/configuration/advanced#logger", + restartRequired: [], fieldOrder: ["default", "logs"], advancedFields: ["logs"], }, diff --git a/web/src/components/config-form/section-configs/lpr.ts b/web/src/components/config-form/section-configs/lpr.ts index 15c5ec7a8..17da1a670 100644 --- a/web/src/components/config-form/section-configs/lpr.ts +++ b/web/src/components/config-form/section-configs/lpr.ts @@ -2,6 +2,8 @@ import type { SectionConfigOverrides } from "./types"; const lpr: SectionConfigOverrides = { base: { + sectionDocs: "/configuration/license_plate_recognition", + restartRequired: [], fieldOrder: ["enabled", "expire_time", "min_area", "enhancement"], hiddenFields: [], advancedFields: ["expire_time", "min_area", "enhancement"], diff --git a/web/src/components/config-form/section-configs/model.ts b/web/src/components/config-form/section-configs/model.ts index 81867dc0a..c82ff9dc4 100644 --- a/web/src/components/config-form/section-configs/model.ts +++ b/web/src/components/config-form/section-configs/model.ts @@ -2,6 +2,8 @@ import type { SectionConfigOverrides } from "./types"; const model: SectionConfigOverrides = { base: { + sectionDocs: "/configuration/object_detectors#model", + restartRequired: [], fieldOrder: [ "path", "labelmap_path", diff --git a/web/src/components/config-form/section-configs/motion.ts b/web/src/components/config-form/section-configs/motion.ts index b22bebb0a..74ebdf423 100644 --- a/web/src/components/config-form/section-configs/motion.ts +++ b/web/src/components/config-form/section-configs/motion.ts @@ -2,6 +2,8 @@ import type { SectionConfigOverrides } from "./types"; const motion: SectionConfigOverrides = { base: { + sectionDocs: "/configuration/motion_detection", + restartRequired: [], fieldOrder: [ "enabled", "threshold", diff --git a/web/src/components/config-form/section-configs/mqtt.ts b/web/src/components/config-form/section-configs/mqtt.ts index 0c9e3c34e..ec19459bf 100644 --- a/web/src/components/config-form/section-configs/mqtt.ts +++ b/web/src/components/config-form/section-configs/mqtt.ts @@ -2,6 +2,8 @@ import type { SectionConfigOverrides } from "./types"; const mqtt: SectionConfigOverrides = { base: { + sectionDocs: "/integrations/mqtt", + restartRequired: [], fieldOrder: [ "enabled", "timestamp", diff --git a/web/src/components/config-form/section-configs/networking.ts b/web/src/components/config-form/section-configs/networking.ts index 4abe60451..c360bbaf5 100644 --- a/web/src/components/config-form/section-configs/networking.ts +++ b/web/src/components/config-form/section-configs/networking.ts @@ -2,6 +2,8 @@ import type { SectionConfigOverrides } from "./types"; const networking: SectionConfigOverrides = { base: { + sectionDocs: "/configuration/reference", + restartRequired: [], fieldOrder: [], advancedFields: [], }, diff --git a/web/src/components/config-form/section-configs/notifications.ts b/web/src/components/config-form/section-configs/notifications.ts index b0e436cba..043b6c154 100644 --- a/web/src/components/config-form/section-configs/notifications.ts +++ b/web/src/components/config-form/section-configs/notifications.ts @@ -2,6 +2,8 @@ import type { SectionConfigOverrides } from "./types"; const notifications: SectionConfigOverrides = { base: { + sectionDocs: "/configuration/notifications", + restartRequired: [], fieldOrder: ["enabled", "email"], fieldGroups: {}, hiddenFields: ["enabled_in_config"], diff --git a/web/src/components/config-form/section-configs/objects.ts b/web/src/components/config-form/section-configs/objects.ts index 8c6fe4097..ce251d195 100644 --- a/web/src/components/config-form/section-configs/objects.ts +++ b/web/src/components/config-form/section-configs/objects.ts @@ -2,6 +2,8 @@ import type { SectionConfigOverrides } from "./types"; const objects: SectionConfigOverrides = { base: { + sectionDocs: "/configuration/object_filters", + restartRequired: [], fieldOrder: ["track", "alert", "detect", "filters"], fieldGroups: { tracking: ["track", "alert", "detect"], @@ -14,6 +16,8 @@ const objects: SectionConfigOverrides = { "genai.enabled_in_config", "filters.*.mask", "filters.*.raw_mask", + "filters.mask", + "filters.raw_mask", ], advancedFields: ["filters"], uiSchema: { @@ -22,6 +26,11 @@ const objects: SectionConfigOverrides = { suppressMultiSchema: true, }, }, + "filters.*": { + "ui:options": { + additionalPropertyKeyReadonly: true, + }, + }, "filters.*.max_area": { "ui:options": { suppressMultiSchema: true, @@ -56,7 +65,17 @@ const objects: SectionConfigOverrides = { }, }, global: { - hiddenFields: ["genai.required_zones"], + hiddenFields: [ + "enabled_in_config", + "mask", + "raw_mask", + "genai.enabled_in_config", + "filters.*.mask", + "filters.*.raw_mask", + "filters.mask", + "filters.raw_mask", + "genai.required_zones", + ], }, }; diff --git a/web/src/components/config-form/section-configs/onvif.ts b/web/src/components/config-form/section-configs/onvif.ts index 973945275..0e4976598 100644 --- a/web/src/components/config-form/section-configs/onvif.ts +++ b/web/src/components/config-form/section-configs/onvif.ts @@ -2,6 +2,8 @@ import type { SectionConfigOverrides } from "./types"; const onvif: SectionConfigOverrides = { base: { + sectionDocs: "/configuration/cameras#setting-up-camera-ptz-controls", + restartRequired: [], fieldOrder: [ "host", "port", diff --git a/web/src/components/config-form/section-configs/proxy.ts b/web/src/components/config-form/section-configs/proxy.ts index 2530f0a98..8b245cf52 100644 --- a/web/src/components/config-form/section-configs/proxy.ts +++ b/web/src/components/config-form/section-configs/proxy.ts @@ -2,6 +2,8 @@ import type { SectionConfigOverrides } from "./types"; const proxy: SectionConfigOverrides = { base: { + sectionDocs: "/configuration/authentication#proxy", + restartRequired: [], fieldOrder: [ "header_map", "logout_url", diff --git a/web/src/components/config-form/section-configs/record.ts b/web/src/components/config-form/section-configs/record.ts index 1a15631d6..04c0227ee 100644 --- a/web/src/components/config-form/section-configs/record.ts +++ b/web/src/components/config-form/section-configs/record.ts @@ -2,6 +2,8 @@ import type { SectionConfigOverrides } from "./types"; const record: SectionConfigOverrides = { base: { + sectionDocs: "/configuration/record", + restartRequired: [], fieldOrder: [ "enabled", "expire_interval", diff --git a/web/src/components/config-form/section-configs/review.ts b/web/src/components/config-form/section-configs/review.ts index 98301512f..24a969143 100644 --- a/web/src/components/config-form/section-configs/review.ts +++ b/web/src/components/config-form/section-configs/review.ts @@ -2,6 +2,8 @@ import type { SectionConfigOverrides } from "./types"; const review: SectionConfigOverrides = { base: { + sectionDocs: "/configuration/review", + restartRequired: [], fieldOrder: ["alerts", "detections", "genai"], fieldGroups: {}, hiddenFields: [ diff --git a/web/src/components/config-form/section-configs/semantic_search.ts b/web/src/components/config-form/section-configs/semantic_search.ts index a85a5bc5b..ee6d4c7fc 100644 --- a/web/src/components/config-form/section-configs/semantic_search.ts +++ b/web/src/components/config-form/section-configs/semantic_search.ts @@ -2,6 +2,8 @@ import type { SectionConfigOverrides } from "./types"; const semanticSearch: SectionConfigOverrides = { base: { + sectionDocs: "/configuration/semantic_search", + restartRequired: [], fieldOrder: ["triggers"], hiddenFields: [], advancedFields: [], diff --git a/web/src/components/config-form/section-configs/snapshots.ts b/web/src/components/config-form/section-configs/snapshots.ts index 0897d31f7..793f6f896 100644 --- a/web/src/components/config-form/section-configs/snapshots.ts +++ b/web/src/components/config-form/section-configs/snapshots.ts @@ -2,6 +2,8 @@ import type { SectionConfigOverrides } from "./types"; const snapshots: SectionConfigOverrides = { base: { + sectionDocs: "/configuration/snapshots", + restartRequired: [], fieldOrder: [ "enabled", "bounding_box", diff --git a/web/src/components/config-form/section-configs/telemetry.ts b/web/src/components/config-form/section-configs/telemetry.ts index 838258d59..3ceea9872 100644 --- a/web/src/components/config-form/section-configs/telemetry.ts +++ b/web/src/components/config-form/section-configs/telemetry.ts @@ -2,6 +2,8 @@ import type { SectionConfigOverrides } from "./types"; const telemetry: SectionConfigOverrides = { base: { + sectionDocs: "/configuration/reference", + restartRequired: [], fieldOrder: ["network_interfaces", "stats", "version_check"], advancedFields: [], }, diff --git a/web/src/components/config-form/section-configs/timestamp_style.ts b/web/src/components/config-form/section-configs/timestamp_style.ts index 85d4fd9f5..c056d7d79 100644 --- a/web/src/components/config-form/section-configs/timestamp_style.ts +++ b/web/src/components/config-form/section-configs/timestamp_style.ts @@ -2,6 +2,8 @@ import type { SectionConfigOverrides } from "./types"; const timestampStyle: SectionConfigOverrides = { base: { + sectionDocs: "/configuration/reference", + restartRequired: [], fieldOrder: ["position", "format", "color", "thickness"], hiddenFields: ["effect", "enabled_in_config"], advancedFields: [], diff --git a/web/src/components/config-form/section-configs/tls.ts b/web/src/components/config-form/section-configs/tls.ts index 6f8b64c0b..870e7e2c2 100644 --- a/web/src/components/config-form/section-configs/tls.ts +++ b/web/src/components/config-form/section-configs/tls.ts @@ -2,6 +2,8 @@ import type { SectionConfigOverrides } from "./types"; const tls: SectionConfigOverrides = { base: { + sectionDocs: "/configuration/tls", + restartRequired: [], fieldOrder: ["enabled", "cert", "key"], advancedFields: [], }, diff --git a/web/src/components/config-form/section-configs/ui.ts b/web/src/components/config-form/section-configs/ui.ts index 4f2e47a98..398ed359b 100644 --- a/web/src/components/config-form/section-configs/ui.ts +++ b/web/src/components/config-form/section-configs/ui.ts @@ -2,6 +2,8 @@ import type { SectionConfigOverrides } from "./types"; const ui: SectionConfigOverrides = { base: { + sectionDocs: "/configuration/reference", + restartRequired: [], fieldOrder: ["dashboard", "order"], hiddenFields: [], advancedFields: [], diff --git a/web/src/components/config-form/sections/BaseSection.tsx b/web/src/components/config-form/sections/BaseSection.tsx index 23575082f..433f6218d 100644 --- a/web/src/components/config-form/sections/BaseSection.tsx +++ b/web/src/components/config-form/sections/BaseSection.tsx @@ -61,6 +61,12 @@ export interface SectionConfig { advancedFields?: string[]; /** Fields to compare for override detection */ overrideFields?: string[]; + /** Documentation link for the section */ + sectionDocs?: string; + /** Per-field documentation links */ + fieldDocs?: Record; + /** Fields that require restart when modified (empty means all fields) */ + restartRequired?: string[]; /** Whether to enable live validation */ liveValidate?: boolean; /** Additional uiSchema overrides */ @@ -402,6 +408,27 @@ export function ConfigSection({ ], ); + const requiresRestartForOverrides = useCallback( + (overrides: unknown) => { + if (sectionConfig.restartRequired === undefined) { + return requiresRestart; + } + + if (sectionConfig.restartRequired.length === 0) { + return true; + } + + if (!overrides || typeof overrides !== "object") { + return false; + } + + return sectionConfig.restartRequired.some( + (path) => get(overrides as JsonObject, path) !== undefined, + ); + }, + [requiresRestart, sectionConfig.restartRequired], + ); + const handleReset = useCallback(() => { isResettingRef.current = true; setPendingData(null); @@ -426,8 +453,10 @@ export function ConfigSection({ return; } + const needsRestart = requiresRestartForOverrides(overrides); + await axios.put("config/sett", { - requires_restart: requiresRestart ? 0 : 1, + requires_restart: needsRestart ? 1 : 0, update_topic: updateTopic, config_data: { [basePath]: overrides, @@ -438,13 +467,15 @@ export function ConfigSection({ console.log("Saved config data:", { [basePath]: overrides, update_topic: updateTopic, - requires_restart: requiresRestart ? 0 : 1, + requires_restart: needsRestart ? 1 : 0, }); toast.success( - t("toast.success", { + t(needsRestart ? "toast.successRestartRequired" : "toast.success", { ns: "views/settings", - defaultValue: "Settings saved successfully", + defaultValue: needsRestart + ? "Settings saved successfully. Restart Frigate to apply your changes." + : "Settings saved successfully", }), ); @@ -494,7 +525,6 @@ export function ConfigSection({ pendingData, level, cameraName, - requiresRestart, t, refreshConfig, onSave, @@ -504,6 +534,7 @@ export function ConfigSection({ schemaDefaults, updateTopic, setPendingData, + requiresRestartForOverrides, ]); // Handle reset to global/defaults - removes camera-level override or resets global to defaults @@ -662,6 +693,8 @@ export function ConfigSection({ t, renderers: sectionConfig?.renderers ?? sectionRenderers?.[sectionPath], + sectionDocs: sectionConfig.sectionDocs, + fieldDocs: sectionConfig.fieldDocs, }} /> diff --git a/web/src/components/config-form/theme/templates/FieldTemplate.tsx b/web/src/components/config-form/theme/templates/FieldTemplate.tsx index f0a89cc01..d97974978 100644 --- a/web/src/components/config-form/theme/templates/FieldTemplate.tsx +++ b/web/src/components/config-form/theme/templates/FieldTemplate.tsx @@ -13,6 +13,9 @@ import { useTranslation } from "react-i18next"; import { isNullableUnionSchema } from "../fields/nullableUtils"; import { getTranslatedLabel } from "@/utils/i18n"; import { ConfigFormContext } from "@/types/configForm"; +import { Link } from "react-router-dom"; +import { LuExternalLink } from "react-icons/lu"; +import { useDocDomain } from "@/hooks/use-doc-domain"; /** * Build the i18n translation key path for nested fields using the field path @@ -114,6 +117,7 @@ export function FieldTemplate(props: FieldTemplateProps) { i18nNamespace || "common", "views/settings", ]); + const { getLocaleDocUrl } = useDocDomain(); if (hidden) { return
{children}
; @@ -148,6 +152,13 @@ export function FieldTemplate(props: FieldTemplateProps) { const translatedFilterObjectLabel = filterObjectLabel ? getTranslatedLabel(filterObjectLabel, "object") : undefined; + const fieldDocsKey = translationPath || pathSegments.join("."); + const fieldDocsPath = fieldDocsKey + ? formContext?.fieldDocs?.[fieldDocsKey] + : undefined; + const fieldDocsUrl = fieldDocsPath + ? getLocaleDocUrl(fieldDocsPath) + : undefined; // Use schema title/description as primary source (from JSON Schema) const schemaTitle = schema.title; @@ -394,6 +405,19 @@ export function FieldTemplate(props: FieldTemplateProps) { {finalDescription}

)} + {fieldDocsUrl && !isMultiSchemaWrapper && !isObjectField && ( +
+ + {t("readTheDocumentation", { ns: "common" })} + + +
+ )}
{children}
@@ -406,6 +430,19 @@ export function FieldTemplate(props: FieldTemplateProps) { {finalDescription}

)} + {fieldDocsUrl && !isMultiSchemaWrapper && !isObjectField && ( +
+ + {t("readTheDocumentation", { ns: "common" })} + + +
+ )} )} diff --git a/web/src/components/config-form/theme/templates/WrapIfAdditionalTemplate.tsx b/web/src/components/config-form/theme/templates/WrapIfAdditionalTemplate.tsx index 284b713d9..f20511d1b 100644 --- a/web/src/components/config-form/theme/templates/WrapIfAdditionalTemplate.tsx +++ b/web/src/components/config-form/theme/templates/WrapIfAdditionalTemplate.tsx @@ -1,6 +1,7 @@ import { ADDITIONAL_PROPERTY_FLAG, FormContextType, + getUiOptions, RJSFSchema, StrictRJSFSchema, WrapIfAdditionalTemplateProps, @@ -30,6 +31,7 @@ export function WrapIfAdditionalTemplate< readonly, required, schema, + uiSchema, } = props; const { t } = useTranslation(["views/settings"]); @@ -57,41 +59,63 @@ export function WrapIfAdditionalTemplate< const removeLabel = t("configForm.additionalProperties.remove", { ns: "views/settings", }); + const uiOptions = getUiOptions(uiSchema); + const keyIsReadonly = uiOptions.additionalPropertyKeyReadonly === true; return (
-
- {displayLabel && } - -
-
- {displayLabel && } + {!keyIsReadonly && ( +
+ {displayLabel && } + {keyIsReadonly ? ( +
+ {label} +
+ ) : ( + + )} +
+ )} +
+ {!keyIsReadonly && displayLabel && ( + + )}
{children}
-
- -
+ {!keyIsReadonly && ( +
+ +
+ )}
); } diff --git a/web/src/types/configForm.ts b/web/src/types/configForm.ts index b8408eec9..ca4ea25d4 100644 --- a/web/src/types/configForm.ts +++ b/web/src/types/configForm.ts @@ -22,6 +22,8 @@ export type ConfigFormContext = { fullConfig?: FrigateConfig; i18nNamespace?: string; sectionI18nPrefix?: string; + sectionDocs?: string; + fieldDocs?: Record; t?: (key: string, options?: Record) => string; renderers?: Record; }; diff --git a/web/src/views/settings/SingleSectionPage.tsx b/web/src/views/settings/SingleSectionPage.tsx index 7ec3330a6..27926444a 100644 --- a/web/src/views/settings/SingleSectionPage.tsx +++ b/web/src/views/settings/SingleSectionPage.tsx @@ -1,10 +1,14 @@ -import { useCallback, useState } from "react"; +import { useCallback, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import type { SectionConfig } from "@/components/config-form/sections"; import { ConfigSectionTemplate } from "@/components/config-form/sections"; import type { PolygonType } from "@/types/canvas"; import { Badge } from "@/components/ui/badge"; import type { ConfigSectionData } from "@/types/configForm"; +import { getSectionConfig } from "@/utils/sectionConfigsUtils"; +import { useDocDomain } from "@/hooks/use-doc-domain"; +import { Link } from "react-router-dom"; +import { LuExternalLink } from "react-icons/lu"; export type SettingsPageProps = { selectedCamera?: string; @@ -58,10 +62,18 @@ export function SingleSectionPage({ "views/settings", "common", ]); + const { getLocaleDocUrl } = useDocDomain(); const [sectionStatus, setSectionStatus] = useState({ hasChanges: false, isOverridden: false, }); + const resolvedSectionConfig = useMemo( + () => sectionConfig ?? getSectionConfig(sectionKey, level), + [level, sectionConfig, sectionKey], + ); + const sectionDocsUrl = resolvedSectionConfig.sectionDocs + ? getLocaleDocUrl(resolvedSectionConfig.sectionDocs) + : undefined; const handleSectionStatusChange = useCallback( (status: SectionStatus) => { @@ -93,6 +105,19 @@ export function SingleSectionPage({ {t(`${sectionKey}.description`, { ns: sectionNamespace })}
)} + {sectionDocsUrl && ( +
+ + {t("readTheDocumentation", { ns: "common" })} + + +
+ )}
@@ -127,7 +152,7 @@ export function SingleSectionPage({ showOverrideIndicator={showOverrideIndicator} onSave={() => setUnsavedChanges?.(false)} showTitle={false} - sectionConfig={sectionConfig} + sectionConfig={resolvedSectionConfig} pendingDataBySection={pendingDataBySection} onPendingDataChange={onPendingDataChange} requiresRestart={requiresRestart}