frigate/web/src/components/config-form/theme/widgets/CameraPathWidget.tsx

167 lines
4.5 KiB
TypeScript
Raw Normal View History

Full UI configuration (#22151) * use react-jsonschema-form for UI config * don't use properties wrapper when generating config i18n json * configure for full i18n support * section fields * add descriptions to all fields for i18n * motion i18n * fix nullable fields * sanitize internal fields * add switches widgets and use friendly names * fix nullable schema entries * ensure update_topic is added to api calls this needs further backend implementation to work correctly * add global sections, camera config overrides, and reset button * i18n * add reset logic to global config view * tweaks * fix sections and live validation * fix validation for schema objects that can be null * generic and custom per-field validation * improve generic error validation messages * remove show advanced fields switch * tweaks * use shadcn theme * fix array field template * i18n tweaks * remove collapsible around root section * deep merge schema for advanced fields * add array field item template and fix ffmpeg section * add missing i18n keys * tweaks * comment out api call for testing * add config groups as a separate i18n namespace * add descriptions to all pydantic fields * make titles more concise * new titles as i18n * update i18n config generation script to use json schema * tweaks * tweaks * rebase * clean up * form tweaks * add wildcards and fix object filter fields * add field template for additionalproperties schema objects * improve typing * add section description from schema and clarify global vs camera level descriptions * separate and consolidate global and camera i18n namespaces * clean up now obsolete namespaces * tweaks * refactor sections and overrides * add ability to render components before and after fields * fix titles * chore(sections): remove legacy single-section components replaced by template * refactor configs to use individual files with a template * fix review description * apply hidden fields after ui schema * move util * remove unused i18n * clean up error messages * fix fast refresh * add custom validation and use it for ffmpeg input roles * update nav tree * remove unused * re-add override and modified indicators * mark pending changes and add confirmation dialog for resets * fix red unsaved dot * tweaks * add docs links, readonly keys, and restart required per field * add special case and comments for global motion section * add section form special cases * combine review sections * tweaks * add audio labels endpoint * add audio label switches and input to filter list * fix type * remove key from config when resetting to default/global * don't show description for new key/val fields * tweaks * spacing tweaks * add activity indicator and scrollbar tweaks * add docs to filter fields * wording changes * fix global ffmpeg section * add review classification zones to review form * add backend endpoint and frontend widget for ffmpeg presets and manual args * improve wording * hide descriptions for additional properties arrays * add warning log about incorrectly nested model config * spacing and language tweaks * fix i18n keys * networking section docs and description * small wording tweaks * add layout grid field * refactor with shared utilities * field order * add individual detectors to schema add detector titles and descriptions (docstrings in pydantic are used for descriptions) and add i18n keys to globals * clean up detectors section and i18n * don't save model config back to yaml when saving detectors * add full detectors config to api model dump works around the way we use detector plugins so we can have the full detector config for the frontend * add restart button to toast when restart is required * add ui option to remove inner cards * fix buttons * section tweaks * don't zoom into text on mobile * make buttons sticky at bottom of sections * small tweaks * highlight label of changed fields * add null to enum list when unwrapping * refactor to shared utils and add save all button * add undo all button * add RJSF to dictionary * consolidate utils * preserve form data when changing cameras * add mono fonts * add popover to show what fields will be saved * fix mobile menu not re-rendering with unsaved dots * tweaks * fix logger and env vars config section saving use escaped periods in keys to retain them in the config file (eg "frigate.embeddings") * add timezone widget * role map field with validation * fix validation for model section * add another hidden field * add footer message for required restart * use rjsf for notifications view * fix config saving * add replace rules field * default column layout and add field sizing * clean up field template * refactor profile settings to match rjsf forms * tweaks * refactor frigate+ view and make tweaks to sections * show frigate+ model info in detection model settings when using a frigate+ model * update restartRequired for all fields * fix restart fields * tweaks and add ability enable disabled cameras more backend changes required * require restart when enabling camera that is disabled in config * disable save when form is invalid * refactor ffmpeg section for readability * change label * clean up camera inputs fields * misc tweaks to ffmpeg section - add raw paths endpoint to ensure credentials get saved - restart required tooltip * maintenance settings tweaks * don't mutate with lodash * fix description re-rendering for nullable object fields * hide reindex field * update rjsf * add frigate+ description to settings pane * disable save all when any section is invalid * show translated field name in validation error pane * clean up * remove unused * fix genai merge * fix genai
2026-02-27 18:55:36 +03:00
import type { WidgetProps } from "@rjsf/utils";
import useSWR from "swr";
import { useMemo, useState, type FocusEvent } from "react";
import { useTranslation } from "react-i18next";
import { LuEye, LuEyeOff } from "react-icons/lu";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import type { ConfigFormContext } from "@/types/configForm";
import { cn } from "@/lib/utils";
import { getSizedFieldClassName } from "../utils";
import {
isMaskedPath,
hasCredentials,
maskCredentials,
} from "@/utils/credentialMask";
Full UI configuration (#22151) * use react-jsonschema-form for UI config * don't use properties wrapper when generating config i18n json * configure for full i18n support * section fields * add descriptions to all fields for i18n * motion i18n * fix nullable fields * sanitize internal fields * add switches widgets and use friendly names * fix nullable schema entries * ensure update_topic is added to api calls this needs further backend implementation to work correctly * add global sections, camera config overrides, and reset button * i18n * add reset logic to global config view * tweaks * fix sections and live validation * fix validation for schema objects that can be null * generic and custom per-field validation * improve generic error validation messages * remove show advanced fields switch * tweaks * use shadcn theme * fix array field template * i18n tweaks * remove collapsible around root section * deep merge schema for advanced fields * add array field item template and fix ffmpeg section * add missing i18n keys * tweaks * comment out api call for testing * add config groups as a separate i18n namespace * add descriptions to all pydantic fields * make titles more concise * new titles as i18n * update i18n config generation script to use json schema * tweaks * tweaks * rebase * clean up * form tweaks * add wildcards and fix object filter fields * add field template for additionalproperties schema objects * improve typing * add section description from schema and clarify global vs camera level descriptions * separate and consolidate global and camera i18n namespaces * clean up now obsolete namespaces * tweaks * refactor sections and overrides * add ability to render components before and after fields * fix titles * chore(sections): remove legacy single-section components replaced by template * refactor configs to use individual files with a template * fix review description * apply hidden fields after ui schema * move util * remove unused i18n * clean up error messages * fix fast refresh * add custom validation and use it for ffmpeg input roles * update nav tree * remove unused * re-add override and modified indicators * mark pending changes and add confirmation dialog for resets * fix red unsaved dot * tweaks * add docs links, readonly keys, and restart required per field * add special case and comments for global motion section * add section form special cases * combine review sections * tweaks * add audio labels endpoint * add audio label switches and input to filter list * fix type * remove key from config when resetting to default/global * don't show description for new key/val fields * tweaks * spacing tweaks * add activity indicator and scrollbar tweaks * add docs to filter fields * wording changes * fix global ffmpeg section * add review classification zones to review form * add backend endpoint and frontend widget for ffmpeg presets and manual args * improve wording * hide descriptions for additional properties arrays * add warning log about incorrectly nested model config * spacing and language tweaks * fix i18n keys * networking section docs and description * small wording tweaks * add layout grid field * refactor with shared utilities * field order * add individual detectors to schema add detector titles and descriptions (docstrings in pydantic are used for descriptions) and add i18n keys to globals * clean up detectors section and i18n * don't save model config back to yaml when saving detectors * add full detectors config to api model dump works around the way we use detector plugins so we can have the full detector config for the frontend * add restart button to toast when restart is required * add ui option to remove inner cards * fix buttons * section tweaks * don't zoom into text on mobile * make buttons sticky at bottom of sections * small tweaks * highlight label of changed fields * add null to enum list when unwrapping * refactor to shared utils and add save all button * add undo all button * add RJSF to dictionary * consolidate utils * preserve form data when changing cameras * add mono fonts * add popover to show what fields will be saved * fix mobile menu not re-rendering with unsaved dots * tweaks * fix logger and env vars config section saving use escaped periods in keys to retain them in the config file (eg "frigate.embeddings") * add timezone widget * role map field with validation * fix validation for model section * add another hidden field * add footer message for required restart * use rjsf for notifications view * fix config saving * add replace rules field * default column layout and add field sizing * clean up field template * refactor profile settings to match rjsf forms * tweaks * refactor frigate+ view and make tweaks to sections * show frigate+ model info in detection model settings when using a frigate+ model * update restartRequired for all fields * fix restart fields * tweaks and add ability enable disabled cameras more backend changes required * require restart when enabling camera that is disabled in config * disable save when form is invalid * refactor ffmpeg section for readability * change label * clean up camera inputs fields * misc tweaks to ffmpeg section - add raw paths endpoint to ensure credentials get saved - restart required tooltip * maintenance settings tweaks * don't mutate with lodash * fix description re-rendering for nullable object fields * hide reindex field * update rjsf * add frigate+ description to settings pane * disable save all when any section is invalid * show translated field name in validation error pane * clean up * remove unused * fix genai merge * fix genai
2026-02-27 18:55:36 +03:00
type RawPathsResponse = {
cameras?: Record<
string,
{
ffmpeg?: {
inputs?: Array<{
path?: string;
}>;
};
}
>;
};
const getInputIndexFromWidgetId = (id: string): number | undefined => {
const match = id.match(/_inputs_(\d+)_path$/);
if (!match) {
return undefined;
}
const index = Number(match[1]);
return Number.isNaN(index) ? undefined : index;
};
export function CameraPathWidget(props: WidgetProps) {
const {
id,
value,
disabled,
readonly,
onChange,
onBlur,
onFocus,
placeholder,
schema,
options,
} = props;
const { t } = useTranslation(["common", "views/settings"]);
const [showCredentials, setShowCredentials] = useState(false);
const formContext = props.registry?.formContext as
| ConfigFormContext
| undefined;
const isCameraLevel = formContext?.level === "camera";
const cameraName = formContext?.cameraName;
const inputIndex = useMemo(() => getInputIndexFromWidgetId(id), [id]);
const shouldFetchRawPaths =
isCameraLevel && !!cameraName && inputIndex !== undefined;
const { data: rawPaths } = useSWR<RawPathsResponse>(
shouldFetchRawPaths ? "config/raw_paths" : null,
);
const rawPath = useMemo(() => {
if (!cameraName || inputIndex === undefined) {
return undefined;
}
const path =
rawPaths?.cameras?.[cameraName]?.ffmpeg?.inputs?.[inputIndex]?.path;
return typeof path === "string" ? path : undefined;
}, [cameraName, inputIndex, rawPaths]);
const rawValue = typeof value === "string" ? value : "";
const resolvedValue =
isMaskedPath(rawValue) && rawPath ? rawPath : (rawValue ?? "");
const canReveal =
hasCredentials(resolvedValue) && !isMaskedPath(resolvedValue);
const canToggle = canReveal || isMaskedPath(rawValue);
const isMaskedView = canToggle && !showCredentials;
const displayValue = isMaskedView
? maskCredentials(resolvedValue)
: resolvedValue;
const isNullable = Array.isArray(schema.type)
? schema.type.includes("null")
: false;
const fieldClassName = getSizedFieldClassName(options, "xs");
const uriLabel = t("cameraWizard.step3.url", {
ns: "views/settings",
defaultValue: schema.title,
});
const toggleLabel = showCredentials
? t("label.hide", { ns: "common", item: uriLabel })
: t("label.show", { ns: "common", item: uriLabel });
const handleFocus = (event: FocusEvent<HTMLInputElement>) => {
if (isMaskedView && canReveal) {
setShowCredentials(true);
onFocus(id, resolvedValue);
return;
}
onFocus(id, event.target.value);
};
const handleBlur = (event: FocusEvent<HTMLInputElement>) => {
if (canToggle) {
setShowCredentials(false);
}
onBlur(id, event.target.value);
};
return (
<div className={cn("relative", fieldClassName)}>
<Input
id={id}
className={cn("text-md", canToggle ? "pr-10" : undefined)}
type="text"
value={displayValue}
disabled={disabled || readonly}
placeholder={placeholder || (options.placeholder as string) || ""}
onChange={(e) =>
onChange(
e.target.value === ""
? isNullable
? null
: undefined
: e.target.value,
)
}
onBlur={handleBlur}
onFocus={handleFocus}
aria-label={schema.title}
/>
{canToggle ? (
<Button
type="button"
variant="ghost"
size="sm"
className="absolute right-0 top-0 h-full px-3 hover:bg-transparent"
onClick={() => setShowCredentials((previous) => !previous)}
disabled={disabled || (!canReveal && !showCredentials)}
aria-label={toggleLabel}
title={toggleLabel}
>
{showCredentials ? (
<LuEyeOff className="h-4 w-4" />
) : (
<LuEye className="h-4 w-4" />
)}
</Button>
) : null}
</div>
);
}