mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-06-23 12:51:54 +03:00
frontend
This commit is contained in:
parent
cabe7dc1b1
commit
35a4e86a39
@ -3,8 +3,10 @@ import type { WidgetProps } from "@rjsf/utils";
|
|||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
import { LuEye, LuEyeOff } from "react-icons/lu";
|
import { LuEye, LuEyeOff } from "react-icons/lu";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
import { REDACTED_CREDENTIAL_SENTINEL } from "@/lib/const";
|
||||||
import { getSizedFieldClassName } from "../utils";
|
import { getSizedFieldClassName } from "../utils";
|
||||||
|
|
||||||
export function PasswordWidget(props: WidgetProps) {
|
export function PasswordWidget(props: WidgetProps) {
|
||||||
@ -21,17 +23,31 @@ export function PasswordWidget(props: WidgetProps) {
|
|||||||
options,
|
options,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
|
const { t } = useTranslation(["common"]);
|
||||||
const [showPassword, setShowPassword] = useState(false);
|
const [showPassword, setShowPassword] = useState(false);
|
||||||
const fieldClassName = getSizedFieldClassName(options, "sm");
|
const fieldClassName = getSizedFieldClassName(options, "sm");
|
||||||
|
|
||||||
|
// When the backend returns the sentinel, hide it visually and prompt the
|
||||||
|
// user that a value is already saved. The value stays as the sentinel in
|
||||||
|
// form state — backend /config/set strips it so the saved YAML is
|
||||||
|
// preserved when the user doesn't touch the field.
|
||||||
|
const isRedacted = value === REDACTED_CREDENTIAL_SENTINEL;
|
||||||
|
const displayValue = isRedacted ? "" : (value ?? "");
|
||||||
|
const effectivePlaceholder = isRedacted
|
||||||
|
? t("credentialField.savedPlaceholder", {
|
||||||
|
ns: "common",
|
||||||
|
defaultValue: "Saved — leave blank to keep current",
|
||||||
|
})
|
||||||
|
: placeholder || "";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cn("relative", fieldClassName)}>
|
<div className={cn("relative", fieldClassName)}>
|
||||||
<Input
|
<Input
|
||||||
id={id}
|
id={id}
|
||||||
type={showPassword ? "text" : "password"}
|
type={showPassword ? "text" : "password"}
|
||||||
value={value ?? ""}
|
value={displayValue}
|
||||||
disabled={disabled || readonly}
|
disabled={disabled || readonly}
|
||||||
placeholder={placeholder || ""}
|
placeholder={effectivePlaceholder}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
onChange(e.target.value === "" ? undefined : e.target.value)
|
onChange(e.target.value === "" ? undefined : e.target.value)
|
||||||
}
|
}
|
||||||
@ -46,7 +62,7 @@ export function PasswordWidget(props: WidgetProps) {
|
|||||||
size="sm"
|
size="sm"
|
||||||
className="absolute right-0 top-0 h-full px-3 hover:bg-transparent"
|
className="absolute right-0 top-0 h-full px-3 hover:bg-transparent"
|
||||||
onClick={() => setShowPassword(!showPassword)}
|
onClick={() => setShowPassword(!showPassword)}
|
||||||
disabled={disabled}
|
disabled={disabled || isRedacted}
|
||||||
>
|
>
|
||||||
{showPassword ? (
|
{showPassword ? (
|
||||||
<LuEyeOff className="h-4 w-4" />
|
<LuEyeOff className="h-4 w-4" />
|
||||||
|
|||||||
@ -1,6 +1,16 @@
|
|||||||
/** ONNX embedding models that require local model downloads. GenAI providers are not in this list. */
|
/** ONNX embedding models that require local model downloads. GenAI providers are not in this list. */
|
||||||
export const JINA_EMBEDDING_MODELS = ["jinav1", "jinav2"] as const;
|
export const JINA_EMBEDDING_MODELS = ["jinav1", "jinav2"] as const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sentinel the backend substitutes for saved credentials (api keys,
|
||||||
|
* passwords, secrets) in /config responses. The credential widget renders
|
||||||
|
* this value as an empty input with a "saved — leave blank to keep" hint,
|
||||||
|
* and stripRedactedCredentials() removes any field still equal to this
|
||||||
|
* value before sending a config/set payload so the saved YAML value is
|
||||||
|
* preserved. Mirror of frigate.const.REDACTED_CREDENTIAL_SENTINEL.
|
||||||
|
*/
|
||||||
|
export const REDACTED_CREDENTIAL_SENTINEL = "__FRIGATE_SAVED_CREDENTIAL__";
|
||||||
|
|
||||||
export const ANNOTATION_OFFSET_MIN = -10000;
|
export const ANNOTATION_OFFSET_MIN = -10000;
|
||||||
export const ANNOTATION_OFFSET_MAX = 5000;
|
export const ANNOTATION_OFFSET_MAX = 5000;
|
||||||
export const ANNOTATION_OFFSET_STEP = 50;
|
export const ANNOTATION_OFFSET_STEP = 50;
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import isEqual from "lodash/isEqual";
|
|||||||
import mergeWith from "lodash/mergeWith";
|
import mergeWith from "lodash/mergeWith";
|
||||||
import set from "lodash/set";
|
import set from "lodash/set";
|
||||||
import { isJsonObject } from "@/lib/utils";
|
import { isJsonObject } from "@/lib/utils";
|
||||||
|
import { REDACTED_CREDENTIAL_SENTINEL } from "@/lib/const";
|
||||||
import { applySchemaDefaults } from "@/lib/config-schema";
|
import { applySchemaDefaults } from "@/lib/config-schema";
|
||||||
import { normalizeConfigValue } from "@/hooks/use-config-override";
|
import { normalizeConfigValue } from "@/hooks/use-config-override";
|
||||||
import {
|
import {
|
||||||
@ -29,6 +30,33 @@ import type {
|
|||||||
import type { SectionConfig } from "../components/config-form/sections/BaseSection";
|
import type { SectionConfig } from "../components/config-form/sections/BaseSection";
|
||||||
import { sectionConfigs } from "../components/config-form/sectionConfigs";
|
import { sectionConfigs } from "../components/config-form/sectionConfigs";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursively strip any key whose value is the redaction sentinel from a
|
||||||
|
* config_data payload. Use just before sending to /config/set so untouched
|
||||||
|
* credential placeholder fields don't clobber the saved YAML value. Mutates
|
||||||
|
* and returns the input.
|
||||||
|
*/
|
||||||
|
export function stripRedactedCredentials<T>(value: T): T {
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
for (const item of value) {
|
||||||
|
stripRedactedCredentials(item);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
if (value && typeof value === "object") {
|
||||||
|
const obj = value as Record<string, unknown>;
|
||||||
|
for (const key of Object.keys(obj)) {
|
||||||
|
const v = obj[key];
|
||||||
|
if (v === REDACTED_CREDENTIAL_SENTINEL) {
|
||||||
|
delete obj[key];
|
||||||
|
} else if (v && typeof v === "object") {
|
||||||
|
stripRedactedCredentials(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// cameraUpdateTopicMap — maps config section paths to MQTT/WS update topics
|
// cameraUpdateTopicMap — maps config section paths to MQTT/WS update topics
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user