mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-07-02 01:51:14 +03:00
Rename to Detectors and model, float Modified badge, use ConfigMessageBanner for mismatch
This commit is contained in:
parent
7126428307
commit
73e0d1b0eb
@ -1,19 +1,19 @@
|
|||||||
/**
|
/**
|
||||||
* Detectors & Model settings page tests -- HIGH tier.
|
* Detectors and model settings page tests -- HIGH tier.
|
||||||
*
|
*
|
||||||
* Tests rendering of the merged page and navigation from the Frigate+ page.
|
* Tests rendering of the merged page and navigation from the Frigate+ page.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { test, expect } from "../../fixtures/frigate-test";
|
import { test, expect } from "../../fixtures/frigate-test";
|
||||||
|
|
||||||
test.describe("Detectors & Model Settings @high", () => {
|
test.describe("Detectors and model Settings @high", () => {
|
||||||
test("page renders with detector and model cards", async ({ frigateApp }) => {
|
test("page renders with detector and model cards", async ({ frigateApp }) => {
|
||||||
await frigateApp.goto("/settings?page=systemDetectorsAndModel");
|
await frigateApp.goto("/settings?page=systemDetectorsAndModel");
|
||||||
await frigateApp.page.waitForTimeout(2000);
|
await frigateApp.page.waitForTimeout(2000);
|
||||||
await expect(frigateApp.page.locator("#pageRoot")).toBeVisible();
|
await expect(frigateApp.page.locator("#pageRoot")).toBeVisible();
|
||||||
|
|
||||||
const text = await frigateApp.page.textContent("#pageRoot");
|
const text = await frigateApp.page.textContent("#pageRoot");
|
||||||
expect(text).toContain("Detectors & Model");
|
expect(text).toContain("Detectors and model");
|
||||||
expect(text?.toLowerCase()).toContain("detector hardware");
|
expect(text?.toLowerCase()).toContain("detector hardware");
|
||||||
expect(text?.toLowerCase()).toContain("detection model");
|
expect(text?.toLowerCase()).toContain("detection model");
|
||||||
});
|
});
|
||||||
@ -23,7 +23,7 @@ test.describe("Detectors & Model Settings @high", () => {
|
|||||||
await frigateApp.page.waitForTimeout(2000);
|
await frigateApp.page.waitForTimeout(2000);
|
||||||
|
|
||||||
const button = frigateApp.page.getByRole("button", {
|
const button = frigateApp.page.getByRole("button", {
|
||||||
name: /Change in Detectors & Model/,
|
name: /Change in Detectors and model/,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Button only appears when Frigate+ is enabled in the test config; skip
|
// Button only appears when Frigate+ is enabled in the test config; skip
|
||||||
@ -32,7 +32,7 @@ test.describe("Detectors & Model Settings @high", () => {
|
|||||||
await button.first().click();
|
await button.first().click();
|
||||||
await frigateApp.page.waitForURL(/page=systemDetectorsAndModel/);
|
await frigateApp.page.waitForURL(/page=systemDetectorsAndModel/);
|
||||||
await expect(frigateApp.page.locator("#pageRoot")).toContainText(
|
await expect(frigateApp.page.locator("#pageRoot")).toContainText(
|
||||||
"Detectors & Model",
|
"Detectors and model",
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
test.skip(
|
test.skip(
|
||||||
|
|||||||
@ -12,7 +12,7 @@
|
|||||||
"globalConfig": "Global Configuration - Frigate",
|
"globalConfig": "Global Configuration - Frigate",
|
||||||
"cameraConfig": "Camera Configuration - Frigate",
|
"cameraConfig": "Camera Configuration - Frigate",
|
||||||
"frigatePlus": "Frigate+ Settings - Frigate",
|
"frigatePlus": "Frigate+ Settings - Frigate",
|
||||||
"detectorsAndModel": "Detectors & Model - Frigate",
|
"detectorsAndModel": "Detectors and model - Frigate",
|
||||||
"notifications": "Notification Settings - Frigate",
|
"notifications": "Notification Settings - Frigate",
|
||||||
"maintenance": "Maintenance - Frigate",
|
"maintenance": "Maintenance - Frigate",
|
||||||
"profiles": "Profiles - Frigate"
|
"profiles": "Profiles - Frigate"
|
||||||
@ -70,7 +70,7 @@
|
|||||||
"systemTelemetry": "Telemetry",
|
"systemTelemetry": "Telemetry",
|
||||||
"systemBirdseye": "Birdseye",
|
"systemBirdseye": "Birdseye",
|
||||||
"systemFfmpeg": "FFmpeg",
|
"systemFfmpeg": "FFmpeg",
|
||||||
"systemDetectorsAndModel": "Detectors & Model",
|
"systemDetectorsAndModel": "Detectors and model",
|
||||||
"systemMqtt": "MQTT",
|
"systemMqtt": "MQTT",
|
||||||
"systemGo2rtcStreams": "go2rtc streams",
|
"systemGo2rtcStreams": "go2rtc streams",
|
||||||
"integrationSemanticSearch": "Semantic search",
|
"integrationSemanticSearch": "Semantic search",
|
||||||
@ -1146,7 +1146,7 @@
|
|||||||
},
|
},
|
||||||
"modelSelect": "Your available models on Frigate+ can be selected here. Note that only models compatible with your current detector configuration can be selected."
|
"modelSelect": "Your available models on Frigate+ can be selected here. Note that only models compatible with your current detector configuration can be selected."
|
||||||
},
|
},
|
||||||
"changeInDetectorsAndModel": "Change in Detectors & Model →",
|
"changeInDetectorsAndModel": "Change model",
|
||||||
"unsavedChanges": "Unsaved Frigate+ settings changes",
|
"unsavedChanges": "Unsaved Frigate+ settings changes",
|
||||||
"restart_required": "Restart required (Frigate+ model changed)",
|
"restart_required": "Restart required (Frigate+ model changed)",
|
||||||
"toast": {
|
"toast": {
|
||||||
@ -1155,7 +1155,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"detectorsAndModel": {
|
"detectorsAndModel": {
|
||||||
"title": "Detectors & Model",
|
"title": "Detectors and model",
|
||||||
"description": "Configure the detector backend that runs object detection and the model it uses. Changes are saved together so the detector and model stay in sync.",
|
"description": "Configure the detector backend that runs object detection and the model it uses. Changes are saved together so the detector and model stay in sync.",
|
||||||
"cardTitles": {
|
"cardTitles": {
|
||||||
"detector": "Detector Hardware",
|
"detector": "Detector Hardware",
|
||||||
@ -1166,7 +1166,7 @@
|
|||||||
"custom": "Custom Model"
|
"custom": "Custom Model"
|
||||||
},
|
},
|
||||||
"mismatch": {
|
"mismatch": {
|
||||||
"warning": "The current Frigate+ model <0>{{model}}</0> requires the <1>{{required}}</1> detector. Pick a compatible model below or switch to Custom Model before saving."
|
"warning": "The current Frigate+ model \"{{model}}\" requires the {{required}} detector. Pick a compatible model below or switch to Custom Model before saving."
|
||||||
},
|
},
|
||||||
"plusModel": {
|
"plusModel": {
|
||||||
"requiresDetector": "Requires: {{detector}}",
|
"requiresDetector": "Requires: {{detector}}",
|
||||||
|
|||||||
@ -44,7 +44,7 @@ export function ConfigMessageBanner({ messages }: ConfigMessageBannerProps) {
|
|||||||
className="flex items-center [&>svg+div]:translate-y-0 [&>svg]:static [&>svg~*]:pl-2"
|
className="flex items-center [&>svg+div]:translate-y-0 [&>svg]:static [&>svg~*]:pl-2"
|
||||||
>
|
>
|
||||||
<SeverityIcon severity={msg.severity} />
|
<SeverityIcon severity={msg.severity} />
|
||||||
<AlertDescription>{t(msg.messageKey)}</AlertDescription>
|
<AlertDescription>{t(msg.messageKey, msg.values)}</AlertDescription>
|
||||||
</Alert>
|
</Alert>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -24,6 +24,8 @@ export type ConditionalMessage = {
|
|||||||
severity: MessageSeverity;
|
severity: MessageSeverity;
|
||||||
/** Function returning true when the message should be shown */
|
/** Function returning true when the message should be shown */
|
||||||
condition: (ctx: MessageConditionContext) => boolean;
|
condition: (ctx: MessageConditionContext) => boolean;
|
||||||
|
/** Optional interpolation values passed to t() for {{var}} substitution */
|
||||||
|
values?: Record<string, unknown>;
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Field-level conditional message, adds field targeting */
|
/** Field-level conditional message, adds field targeting */
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { useCallback, useContext, useEffect, useMemo, useState } from "react";
|
import { useCallback, useContext, useEffect, useMemo, useState } from "react";
|
||||||
import { Trans, useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { LuExternalLink, LuFilter } from "react-icons/lu";
|
import { LuExternalLink, LuFilter } from "react-icons/lu";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
@ -39,6 +39,7 @@ import type {
|
|||||||
import type { ConfigSectionData } from "@/types/configForm";
|
import type { ConfigSectionData } from "@/types/configForm";
|
||||||
import { SettingsGroupCard } from "@/components/card/SettingsGroupCard";
|
import { SettingsGroupCard } from "@/components/card/SettingsGroupCard";
|
||||||
import { ConfigSectionTemplate } from "@/components/config-form/sections";
|
import { ConfigSectionTemplate } from "@/components/config-form/sections";
|
||||||
|
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";
|
||||||
|
|
||||||
type ModelTab = "plus" | "custom";
|
type ModelTab = "plus" | "custom";
|
||||||
@ -358,35 +359,34 @@ export default function DetectorsAndModelSettingsView({
|
|||||||
return (
|
return (
|
||||||
<div className="flex size-full flex-col md:pr-2">
|
<div className="flex size-full flex-col md:pr-2">
|
||||||
<Toaster position="top-center" closeButton={true} />
|
<Toaster position="top-center" closeButton={true} />
|
||||||
<div className="w-full max-w-5xl space-y-6 pt-2">
|
<div className="mb-1 flex items-center justify-between gap-4 pt-2">
|
||||||
<div className="mb-1 flex items-center justify-between gap-4">
|
<div className="flex max-w-5xl flex-col">
|
||||||
<div className="flex flex-col">
|
<Heading as="h4">{t("detectorsAndModel.title")}</Heading>
|
||||||
<Heading as="h4">{t("detectorsAndModel.title")}</Heading>
|
<div className="my-1 text-sm text-muted-foreground">
|
||||||
<div className="my-1 text-sm text-muted-foreground">
|
{t("detectorsAndModel.description")}
|
||||||
{t("detectorsAndModel.description")}
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center text-sm text-primary-variant">
|
|
||||||
<Link
|
|
||||||
to={getLocaleDocUrl("/configuration/object_detectors")}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="inline"
|
|
||||||
>
|
|
||||||
{t("readTheDocumentation", { ns: "common" })}
|
|
||||||
<LuExternalLink className="ml-2 inline-flex size-3" />
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{isDirty && (
|
<div className="flex items-center text-sm text-primary-variant">
|
||||||
<Badge
|
<Link
|
||||||
variant="secondary"
|
to={getLocaleDocUrl("/configuration/object_detectors")}
|
||||||
className="cursor-default bg-unsaved text-xs text-black hover:bg-unsaved"
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="inline"
|
||||||
>
|
>
|
||||||
{t("button.modified", { ns: "common", defaultValue: "Modified" })}
|
{t("readTheDocumentation", { ns: "common" })}
|
||||||
</Badge>
|
<LuExternalLink className="ml-2 inline-flex size-3" />
|
||||||
)}
|
</Link>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{isDirty && (
|
||||||
|
<Badge
|
||||||
|
variant="secondary"
|
||||||
|
className="cursor-default bg-unsaved text-xs text-black hover:bg-unsaved"
|
||||||
|
>
|
||||||
|
{t("button.modified", { ns: "common", defaultValue: "Modified" })}
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="w-full max-w-5xl space-y-6 pt-4">
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<SettingsGroupCard title={t("detectorsAndModel.cardTitles.detector")}>
|
<SettingsGroupCard title={t("detectorsAndModel.cardTitles.detector")}>
|
||||||
<ConfigSectionTemplate
|
<ConfigSectionTemplate
|
||||||
@ -401,20 +401,20 @@ export default function DetectorsAndModelSettingsView({
|
|||||||
/>
|
/>
|
||||||
</SettingsGroupCard>
|
</SettingsGroupCard>
|
||||||
{plusMismatch && selectedPlusModel && (
|
{plusMismatch && selectedPlusModel && (
|
||||||
<div className="rounded-md border border-danger bg-danger/10 px-4 py-3 text-sm text-danger">
|
<ConfigMessageBanner
|
||||||
<Trans
|
messages={[
|
||||||
ns="views/settings"
|
{
|
||||||
i18nKey="detectorsAndModel.mismatch.warning"
|
key: "plus-mismatch",
|
||||||
values={{
|
messageKey: "detectorsAndModel.mismatch.warning",
|
||||||
model: selectedPlusModel.name,
|
severity: "warning",
|
||||||
required: selectedPlusModel.supportedDetectors.join(", "),
|
condition: () => true,
|
||||||
}}
|
values: {
|
||||||
components={{
|
model: selectedPlusModel.name,
|
||||||
0: <strong />,
|
required: selectedPlusModel.supportedDetectors.join(", "),
|
||||||
1: <strong />,
|
},
|
||||||
}}
|
},
|
||||||
/>
|
]}
|
||||||
</div>
|
/>
|
||||||
)}
|
)}
|
||||||
<SettingsGroupCard title={t("detectorsAndModel.cardTitles.model")}>
|
<SettingsGroupCard title={t("detectorsAndModel.cardTitles.model")}>
|
||||||
<Tabs
|
<Tabs
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user