mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-03-19 14:48:22 +03:00
remove unused
This commit is contained in:
parent
88578284a5
commit
f8eee8ed0b
@ -1,274 +0,0 @@
|
|||||||
// Camera Configuration View
|
|
||||||
// Per-camera configuration with tab navigation and override indicators
|
|
||||||
|
|
||||||
import { useMemo, useCallback, useState, memo } from "react";
|
|
||||||
import useSWR from "swr";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { ConfigSectionTemplate } from "@/components/config-form/sections";
|
|
||||||
import { useAllCameraOverrides } from "@/hooks/use-config-override";
|
|
||||||
import type { FrigateConfig } from "@/types/frigateConfig";
|
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
|
||||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
|
||||||
import { Badge } from "@/components/ui/badge";
|
|
||||||
import ActivityIndicator from "@/components/indicators/activity-indicator";
|
|
||||||
import Heading from "@/components/ui/heading";
|
|
||||||
import { cn } from "@/lib/utils";
|
|
||||||
|
|
||||||
interface CameraConfigViewProps {
|
|
||||||
/** Currently selected camera (from parent) */
|
|
||||||
selectedCamera?: string;
|
|
||||||
/** Callback when unsaved changes state changes */
|
|
||||||
setUnsavedChanges?: React.Dispatch<React.SetStateAction<boolean>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function CameraConfigView({
|
|
||||||
selectedCamera: externalSelectedCamera,
|
|
||||||
setUnsavedChanges,
|
|
||||||
}: CameraConfigViewProps) {
|
|
||||||
const { t } = useTranslation(["views/settings"]);
|
|
||||||
|
|
||||||
const { data: config, mutate: refreshConfig } =
|
|
||||||
useSWR<FrigateConfig>("config");
|
|
||||||
|
|
||||||
// Get list of cameras
|
|
||||||
const cameras = useMemo(() => {
|
|
||||||
if (!config?.cameras) return [];
|
|
||||||
return Object.keys(config.cameras).sort();
|
|
||||||
}, [config]);
|
|
||||||
|
|
||||||
// Selected camera state (use external if provided, else internal)
|
|
||||||
const [internalSelectedCamera, setInternalSelectedCamera] = useState<string>(
|
|
||||||
cameras[0] || "",
|
|
||||||
);
|
|
||||||
const selectedCamera = externalSelectedCamera || internalSelectedCamera;
|
|
||||||
|
|
||||||
// Get overridden sections for current camera
|
|
||||||
const overriddenSections = useAllCameraOverrides(config, selectedCamera);
|
|
||||||
|
|
||||||
const handleSave = useCallback(() => {
|
|
||||||
refreshConfig();
|
|
||||||
setUnsavedChanges?.(false);
|
|
||||||
}, [refreshConfig, setUnsavedChanges]);
|
|
||||||
|
|
||||||
const handleCameraChange = useCallback((camera: string) => {
|
|
||||||
setInternalSelectedCamera(camera);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
if (!config) {
|
|
||||||
return (
|
|
||||||
<div className="flex h-full items-center justify-center">
|
|
||||||
<ActivityIndicator />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cameras.length === 0) {
|
|
||||||
return (
|
|
||||||
<div className="flex h-full items-center justify-center text-muted-foreground">
|
|
||||||
{t("configForm.camera.noCameras", {
|
|
||||||
defaultValue: "No cameras configured",
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="flex size-full flex-col">
|
|
||||||
<div className="mb-4">
|
|
||||||
<Heading as="h2">
|
|
||||||
{t("configForm.camera.title", {
|
|
||||||
defaultValue: "Camera Configuration",
|
|
||||||
})}
|
|
||||||
</Heading>
|
|
||||||
<p className="text-sm text-muted-foreground">
|
|
||||||
{t("configForm.camera.description", {
|
|
||||||
defaultValue:
|
|
||||||
"Configure settings for individual cameras. Overridden settings are highlighted.",
|
|
||||||
})}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Camera Tabs - Only show if not externally controlled */}
|
|
||||||
{!externalSelectedCamera && (
|
|
||||||
<Tabs
|
|
||||||
value={selectedCamera}
|
|
||||||
onValueChange={handleCameraChange}
|
|
||||||
className="flex flex-1 flex-col"
|
|
||||||
>
|
|
||||||
<ScrollArea className="w-full">
|
|
||||||
<TabsList className="inline-flex w-max">
|
|
||||||
{cameras.map((camera) => {
|
|
||||||
const cameraOverrides = overriddenSections.filter((s) =>
|
|
||||||
s.startsWith(camera),
|
|
||||||
);
|
|
||||||
const hasOverrides = cameraOverrides.length > 0;
|
|
||||||
const cameraConfig = config.cameras[camera];
|
|
||||||
const displayName = cameraConfig?.name || camera;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<TabsTrigger
|
|
||||||
key={camera}
|
|
||||||
value={camera}
|
|
||||||
className="relative gap-2"
|
|
||||||
>
|
|
||||||
{displayName}
|
|
||||||
{hasOverrides && (
|
|
||||||
<Badge variant="secondary" className="ml-1 h-5 px-1.5">
|
|
||||||
{cameraOverrides.length}
|
|
||||||
</Badge>
|
|
||||||
)}
|
|
||||||
</TabsTrigger>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</TabsList>
|
|
||||||
</ScrollArea>
|
|
||||||
|
|
||||||
{cameras.map((camera) => (
|
|
||||||
<TabsContent key={camera} value={camera} className="mt-4 flex-1">
|
|
||||||
<CameraConfigContent
|
|
||||||
cameraName={camera}
|
|
||||||
config={config}
|
|
||||||
overriddenSections={overriddenSections}
|
|
||||||
onSave={handleSave}
|
|
||||||
/>
|
|
||||||
</TabsContent>
|
|
||||||
))}
|
|
||||||
</Tabs>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Direct content when externally controlled */}
|
|
||||||
{externalSelectedCamera && (
|
|
||||||
<CameraConfigContent
|
|
||||||
cameraName={externalSelectedCamera}
|
|
||||||
config={config}
|
|
||||||
overriddenSections={overriddenSections}
|
|
||||||
onSave={handleSave}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
interface CameraConfigContentProps {
|
|
||||||
cameraName: string;
|
|
||||||
config: FrigateConfig;
|
|
||||||
overriddenSections: string[];
|
|
||||||
onSave: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const CameraConfigContent = memo(function CameraConfigContent({
|
|
||||||
cameraName,
|
|
||||||
config,
|
|
||||||
overriddenSections,
|
|
||||||
onSave,
|
|
||||||
}: CameraConfigContentProps) {
|
|
||||||
const { t } = useTranslation([
|
|
||||||
"config/cameras",
|
|
||||||
"config/cameras",
|
|
||||||
"views/settings",
|
|
||||||
"common",
|
|
||||||
]);
|
|
||||||
|
|
||||||
const [activeSection, setActiveSection] = useState("detect");
|
|
||||||
|
|
||||||
const cameraConfig = config.cameras?.[cameraName];
|
|
||||||
|
|
||||||
if (!cameraConfig) {
|
|
||||||
return (
|
|
||||||
<div className="text-muted-foreground">
|
|
||||||
{t("configForm.camera.notFound", {
|
|
||||||
ns: "views/settings",
|
|
||||||
defaultValue: "Camera not found",
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const sections: Array<{
|
|
||||||
key: string;
|
|
||||||
showOverrideIndicator?: boolean;
|
|
||||||
}> = [
|
|
||||||
{ key: "detect" },
|
|
||||||
{ key: "ffmpeg", showOverrideIndicator: true },
|
|
||||||
{ key: "record" },
|
|
||||||
{ key: "snapshots" },
|
|
||||||
{ key: "motion" },
|
|
||||||
{ key: "objects" },
|
|
||||||
{ key: "review" },
|
|
||||||
{ key: "audio" },
|
|
||||||
{ key: "audio_transcription", showOverrideIndicator: true },
|
|
||||||
{ key: "notifications" },
|
|
||||||
{ key: "live" },
|
|
||||||
{ key: "birdseye", showOverrideIndicator: true },
|
|
||||||
{ key: "face_recognition", showOverrideIndicator: true },
|
|
||||||
{ key: "lpr", showOverrideIndicator: true },
|
|
||||||
{ key: "mqtt", showOverrideIndicator: false },
|
|
||||||
{ key: "onvif", showOverrideIndicator: false },
|
|
||||||
{ key: "ui", showOverrideIndicator: false },
|
|
||||||
{ key: "timestamp_style" },
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="flex flex-1 gap-6 overflow-hidden">
|
|
||||||
{/* Section Navigation */}
|
|
||||||
<nav className="w-64 shrink-0">
|
|
||||||
<ul className="space-y-1">
|
|
||||||
{sections.map((section) => {
|
|
||||||
const isOverridden = overriddenSections.includes(section.key);
|
|
||||||
const defaultSectionLabel =
|
|
||||||
section.key.charAt(0).toUpperCase() +
|
|
||||||
section.key.slice(1).replace(/_/g, " ");
|
|
||||||
|
|
||||||
const sectionLabel = t(`${section.key}.label`, {
|
|
||||||
ns: "config/cameras",
|
|
||||||
defaultValue: defaultSectionLabel,
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<li key={section.key}>
|
|
||||||
<button
|
|
||||||
onClick={() => setActiveSection(section.key)}
|
|
||||||
className={cn(
|
|
||||||
"flex w-full items-center justify-between rounded-md px-3 py-2 text-left text-sm transition-colors",
|
|
||||||
activeSection === section.key
|
|
||||||
? "bg-accent text-accent-foreground"
|
|
||||||
: "hover:bg-muted",
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<span>{sectionLabel}</span>
|
|
||||||
{isOverridden && (
|
|
||||||
<Badge variant="secondary" className="h-5 px-1.5 text-xs">
|
|
||||||
{t("button.overridden", {
|
|
||||||
ns: "common",
|
|
||||||
defaultValue: "Overridden",
|
|
||||||
})}
|
|
||||||
</Badge>
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
{/* Section Content */}
|
|
||||||
<div className="scrollbar-container flex-1 overflow-y-auto pr-4">
|
|
||||||
{sections.map((section) => (
|
|
||||||
<div
|
|
||||||
key={section.key}
|
|
||||||
className={cn(activeSection === section.key ? "block" : "hidden")}
|
|
||||||
>
|
|
||||||
<ConfigSectionTemplate
|
|
||||||
sectionKey={section.key}
|
|
||||||
level="camera"
|
|
||||||
cameraName={cameraName}
|
|
||||||
showOverrideIndicator={section.showOverrideIndicator !== false}
|
|
||||||
onSave={onSave}
|
|
||||||
showTitle={true}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
@ -1,194 +0,0 @@
|
|||||||
// Global Configuration View
|
|
||||||
// Main view for configuring global Frigate settings
|
|
||||||
|
|
||||||
import { useMemo, useCallback, useState } from "react";
|
|
||||||
import useSWR from "swr";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { ConfigSectionTemplate } from "@/components/config-form/sections";
|
|
||||||
import type { FrigateConfig } from "@/types/frigateConfig";
|
|
||||||
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
|
||||||
import ActivityIndicator from "@/components/indicators/activity-indicator";
|
|
||||||
import Heading from "@/components/ui/heading";
|
|
||||||
import { cn } from "@/lib/utils";
|
|
||||||
// Shared sections that can be overridden at camera level
|
|
||||||
const sharedSections = [
|
|
||||||
{ key: "detect" },
|
|
||||||
{ key: "record" },
|
|
||||||
{ key: "snapshots" },
|
|
||||||
{ key: "motion" },
|
|
||||||
{ key: "objects" },
|
|
||||||
{ key: "review" },
|
|
||||||
{ key: "audio" },
|
|
||||||
{ key: "live" },
|
|
||||||
{ key: "timestamp_style" },
|
|
||||||
];
|
|
||||||
|
|
||||||
// System sections (global only)
|
|
||||||
const systemSections = [
|
|
||||||
{ key: "database" },
|
|
||||||
{ key: "tls" },
|
|
||||||
{ key: "auth" },
|
|
||||||
{ key: "networking" },
|
|
||||||
{ key: "proxy" },
|
|
||||||
{ key: "ui" },
|
|
||||||
{ key: "logger" },
|
|
||||||
{ key: "environment_vars" },
|
|
||||||
{ key: "telemetry" },
|
|
||||||
{ key: "birdseye" },
|
|
||||||
{ key: "ffmpeg" },
|
|
||||||
{ key: "detectors" },
|
|
||||||
{ key: "model" },
|
|
||||||
];
|
|
||||||
|
|
||||||
// Integration sections (global only)
|
|
||||||
const integrationSections = [
|
|
||||||
{ key: "mqtt" },
|
|
||||||
{ key: "semantic_search" },
|
|
||||||
{ key: "genai" },
|
|
||||||
{ key: "face_recognition" },
|
|
||||||
{ key: "lpr" },
|
|
||||||
{ key: "classification" },
|
|
||||||
{ key: "audio_transcription" },
|
|
||||||
];
|
|
||||||
|
|
||||||
export default function GlobalConfigView() {
|
|
||||||
const { t } = useTranslation(["views/settings", "config/global", "common"]);
|
|
||||||
const defaultSharedSection = sharedSections[0]?.key ?? "";
|
|
||||||
const defaultSystemSection = systemSections[0]?.key ?? "";
|
|
||||||
const defaultIntegrationSection = integrationSections[0]?.key ?? "";
|
|
||||||
const [activeTab, setActiveTab] = useState("shared");
|
|
||||||
const [activeSection, setActiveSection] = useState(defaultSharedSection);
|
|
||||||
|
|
||||||
const { data: config, mutate: refreshConfig } =
|
|
||||||
useSWR<FrigateConfig>("config");
|
|
||||||
|
|
||||||
const handleSave = useCallback(() => {
|
|
||||||
refreshConfig();
|
|
||||||
}, [refreshConfig]);
|
|
||||||
|
|
||||||
// Get the sections for the current tab
|
|
||||||
const currentSections = useMemo(() => {
|
|
||||||
if (activeTab === "shared") {
|
|
||||||
return sharedSections;
|
|
||||||
}
|
|
||||||
if (activeTab === "system") {
|
|
||||||
return systemSections;
|
|
||||||
}
|
|
||||||
return integrationSections;
|
|
||||||
}, [activeTab]);
|
|
||||||
|
|
||||||
// Reset active section when tab changes
|
|
||||||
const handleTabChange = useCallback(
|
|
||||||
(tab: string) => {
|
|
||||||
setActiveTab(tab);
|
|
||||||
if (tab === "shared") {
|
|
||||||
setActiveSection(defaultSharedSection);
|
|
||||||
} else if (tab === "system") {
|
|
||||||
setActiveSection(defaultSystemSection);
|
|
||||||
} else {
|
|
||||||
setActiveSection(defaultIntegrationSection);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[defaultSharedSection, defaultSystemSection, defaultIntegrationSection],
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!config) {
|
|
||||||
return (
|
|
||||||
<div className="flex h-full items-center justify-center">
|
|
||||||
<ActivityIndicator />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="flex size-full flex-col">
|
|
||||||
<div className="mb-4">
|
|
||||||
<Heading as="h2">
|
|
||||||
{t("configForm.global.title", {
|
|
||||||
defaultValue: "Global Configuration",
|
|
||||||
})}
|
|
||||||
</Heading>
|
|
||||||
<p className="text-sm text-muted-foreground">
|
|
||||||
{t("configForm.global.description", {
|
|
||||||
defaultValue:
|
|
||||||
"Configure global settings that apply to all cameras by default.",
|
|
||||||
})}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Tabs
|
|
||||||
value={activeTab}
|
|
||||||
onValueChange={handleTabChange}
|
|
||||||
className="flex flex-1 flex-col overflow-hidden"
|
|
||||||
>
|
|
||||||
<TabsList className="grid w-full grid-cols-3">
|
|
||||||
<TabsTrigger value="shared">
|
|
||||||
{t("configForm.global.tabs.shared", {
|
|
||||||
defaultValue: "Shared Defaults",
|
|
||||||
})}
|
|
||||||
</TabsTrigger>
|
|
||||||
<TabsTrigger value="system">
|
|
||||||
{t("configForm.global.tabs.system", { defaultValue: "System" })}
|
|
||||||
</TabsTrigger>
|
|
||||||
<TabsTrigger value="integrations">
|
|
||||||
{t("configForm.global.tabs.integrations", {
|
|
||||||
defaultValue: "Integrations",
|
|
||||||
})}
|
|
||||||
</TabsTrigger>
|
|
||||||
</TabsList>
|
|
||||||
|
|
||||||
<div className="mt-4 flex flex-1 gap-6 overflow-hidden">
|
|
||||||
{/* Section Navigation */}
|
|
||||||
<nav className="w-64 shrink-0">
|
|
||||||
<ul className="space-y-1">
|
|
||||||
{currentSections.map((section) => {
|
|
||||||
const defaultLabel =
|
|
||||||
section.key.charAt(0).toUpperCase() +
|
|
||||||
section.key.slice(1).replace(/_/g, " ");
|
|
||||||
const sectionLabel = t(`${section.key}.label`, {
|
|
||||||
ns: "config/global",
|
|
||||||
defaultValue: defaultLabel,
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<li key={section.key}>
|
|
||||||
<button
|
|
||||||
onClick={() => setActiveSection(section.key)}
|
|
||||||
className={cn(
|
|
||||||
"flex w-full items-center justify-between rounded-md px-3 py-2 text-left text-sm transition-colors",
|
|
||||||
activeSection === section.key
|
|
||||||
? "bg-accent text-accent-foreground"
|
|
||||||
: "hover:bg-muted",
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<span>{sectionLabel}</span>
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
{/* Section Content */}
|
|
||||||
<div className="scrollbar-container flex-1 overflow-y-auto pr-4">
|
|
||||||
{currentSections.map((section) => (
|
|
||||||
<div
|
|
||||||
key={section.key}
|
|
||||||
className={cn(
|
|
||||||
activeSection === section.key ? "block" : "hidden",
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<ConfigSectionTemplate
|
|
||||||
sectionKey={section.key}
|
|
||||||
level="global"
|
|
||||||
onSave={handleSave}
|
|
||||||
showTitle={true}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Tabs>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue
Block a user