mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-09-30 11:56:49 +03:00
Cleanup
This commit is contained in:
@@ -2,8 +2,8 @@
|
|||||||
* Camera detect scene tests -- MEDIUM tier.
|
* Camera detect scene tests -- MEDIUM tier.
|
||||||
*
|
*
|
||||||
* Scenes are free-form names declared by the configured models, so a camera
|
* Scenes are free-form names declared by the configured models, so a camera
|
||||||
* only needs to pick one when there is more than one model, and the choices
|
* only needs to pick one when there is a choice to make. The choices are the
|
||||||
* are the default scene plus the scenes of those models.
|
* scenes of the configured models, plus a saved scene that no model uses.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { readFileSync } from "node:fs";
|
import { readFileSync } from "node:fs";
|
||||||
@@ -26,8 +26,14 @@ const SETTINGS_URL = "/settings?page=cameraDetect&camera=front_door";
|
|||||||
async function installRoutes(
|
async function installRoutes(
|
||||||
page: Page,
|
page: Page,
|
||||||
models: { scene: string; devices: string[] }[],
|
models: { scene: string; devices: string[] }[],
|
||||||
|
cameraScene?: string,
|
||||||
) {
|
) {
|
||||||
const config = configFactory({ models } as never);
|
const config = configFactory({
|
||||||
|
models,
|
||||||
|
...(cameraScene
|
||||||
|
? { cameras: { front_door: { detect: { scene: cameraScene } } } }
|
||||||
|
: {}),
|
||||||
|
} as never);
|
||||||
|
|
||||||
await page.route("**/api/config/schema.json", (route) =>
|
await page.route("**/api/config/schema.json", (route) =>
|
||||||
route.fulfill({ json: CONFIG_SCHEMA }),
|
route.fulfill({ json: CONFIG_SCHEMA }),
|
||||||
@@ -70,4 +76,60 @@ test.describe("camera detect scene @medium", () => {
|
|||||||
const options = frigateApp.page.getByRole("option");
|
const options = frigateApp.page.getByRole("option");
|
||||||
await expect(options).toHaveText(["Default", "thermal"]);
|
await expect(options).toHaveText(["Default", "thermal"]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("a saved scene no model uses stays editable", async ({ frigateApp }) => {
|
||||||
|
// the camera falls back to the default model, but its saved scene would
|
||||||
|
// silently take effect if a model for it were added later
|
||||||
|
await installRoutes(
|
||||||
|
frigateApp.page,
|
||||||
|
[{ scene: "default", devices: ["cpu"] }],
|
||||||
|
"garage",
|
||||||
|
);
|
||||||
|
await frigateApp.goto(SETTINGS_URL);
|
||||||
|
|
||||||
|
await expect(frigateApp.page.locator("#pageRoot")).toContainText(
|
||||||
|
"Detect scene",
|
||||||
|
);
|
||||||
|
|
||||||
|
await frigateApp.page.locator("#root_scene").click();
|
||||||
|
await expect(frigateApp.page.getByRole("option")).toHaveText([
|
||||||
|
"Default",
|
||||||
|
"garage",
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("default is only offered when a default model exists", async ({
|
||||||
|
frigateApp,
|
||||||
|
}) => {
|
||||||
|
await installRoutes(
|
||||||
|
frigateApp.page,
|
||||||
|
[
|
||||||
|
{ scene: "thermal", devices: ["cpu"] },
|
||||||
|
{ scene: "visible", devices: ["openvino:GPU.0"] },
|
||||||
|
],
|
||||||
|
"thermal",
|
||||||
|
);
|
||||||
|
await frigateApp.goto(SETTINGS_URL);
|
||||||
|
|
||||||
|
await frigateApp.page.locator("#root_scene").click();
|
||||||
|
await expect(frigateApp.page.getByRole("option")).toHaveText([
|
||||||
|
"thermal",
|
||||||
|
"visible",
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("one model every camera selects needs no choice", async ({
|
||||||
|
frigateApp,
|
||||||
|
}) => {
|
||||||
|
await installRoutes(
|
||||||
|
frigateApp.page,
|
||||||
|
[{ scene: "thermal", devices: ["cpu"] }],
|
||||||
|
"thermal",
|
||||||
|
);
|
||||||
|
await frigateApp.goto(SETTINGS_URL);
|
||||||
|
|
||||||
|
const root = frigateApp.page.locator("#pageRoot");
|
||||||
|
await expect(root).toContainText("Detect FPS");
|
||||||
|
await expect(root).not.toContainText("Detect scene");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import type { HiddenFieldContext } from "@/types/configForm";
|
import type { HiddenFieldContext } from "@/types/configForm";
|
||||||
import { DEFAULT_SCENE, getModelScenes } from "@/utils/modelUtil";
|
import { DEFAULT_SCENE, getSceneChoices } from "@/utils/modelUtil";
|
||||||
import type { SectionConfigOverrides } from "./types";
|
import type { SectionConfigOverrides } from "./types";
|
||||||
|
|
||||||
// picking a scene only means something once there is more than one model
|
// picking a scene only means something when there is more than one choice,
|
||||||
const hideSceneWithOneModel = ({ fullConfig }: HiddenFieldContext): string[] =>
|
// which includes a saved scene that no model uses
|
||||||
getModelScenes(fullConfig).length > 1 ? [] : ["scene"];
|
const hideSceneWithoutChoice = (ctx: HiddenFieldContext): string[] =>
|
||||||
|
getSceneChoices(ctx).length > 1 ? [] : ["scene"];
|
||||||
|
|
||||||
const detect: SectionConfigOverrides = {
|
const detect: SectionConfigOverrides = {
|
||||||
base: {
|
base: {
|
||||||
@@ -209,7 +210,7 @@ const detect: SectionConfigOverrides = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
hiddenFields: ["enabled_in_config", hideSceneWithOneModel],
|
hiddenFields: ["enabled_in_config", hideSceneWithoutChoice],
|
||||||
advancedFields: [
|
advancedFields: [
|
||||||
"min_initialized",
|
"min_initialized",
|
||||||
"max_disappeared",
|
"max_disappeared",
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { applySchemaDefaults } from "@/lib/config-schema";
|
|||||||
import { isJsonObject } from "@/lib/utils";
|
import { isJsonObject } from "@/lib/utils";
|
||||||
import { HiddenFieldContext, JsonObject, JsonValue } from "@/types/configForm";
|
import { HiddenFieldContext, JsonObject, JsonValue } from "@/types/configForm";
|
||||||
import { getEffectiveAttributeLabels } from "@/utils/configUtil";
|
import { getEffectiveAttributeLabels } from "@/utils/configUtil";
|
||||||
import { getModelScenes } from "@/utils/modelUtil";
|
import { getSceneChoices } from "@/utils/modelUtil";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sections that require special handling at the global level.
|
* Sections that require special handling at the global level.
|
||||||
@@ -40,7 +40,7 @@ export function isSpecialCaseSection(
|
|||||||
*
|
*
|
||||||
* - genai: Inject a default provider value on the additionalProperties shape.
|
* - genai: Inject a default provider value on the additionalProperties shape.
|
||||||
* - detect: Scenes are free-form names, so offer the configured model scenes
|
* - detect: Scenes are free-form names, so offer the configured model scenes
|
||||||
* as the choices for `scene`.
|
* (and a saved scene no model uses) as the choices for `scene`.
|
||||||
* - objects: Promote tracked attribute labels (face, license_plate, courier
|
* - objects: Promote tracked attribute labels (face, license_plate, courier
|
||||||
* logos) from `filters.additionalProperties` to explicit
|
* logos) from `filters.additionalProperties` to explicit
|
||||||
* `filters.properties.<attr>` entries with a restricted FilterConfig
|
* `filters.properties.<attr>` entries with a restricted FilterConfig
|
||||||
@@ -104,23 +104,11 @@ function modifyDetectSchema(
|
|||||||
|
|
||||||
if (!ctx || !properties?.scene) return schema;
|
if (!ctx || !properties?.scene) return schema;
|
||||||
|
|
||||||
// keep a saved scene that no model uses selectable, so the form doesn't
|
|
||||||
// silently swap it for another value
|
|
||||||
const saved =
|
|
||||||
(ctx.level !== "global"
|
|
||||||
? ctx.fullCameraConfig?.detect?.scene
|
|
||||||
: undefined) ?? ctx.fullConfig.detect?.scene;
|
|
||||||
const scenes = getModelScenes(ctx.fullConfig);
|
|
||||||
|
|
||||||
if (saved && !scenes.includes(saved)) {
|
|
||||||
scenes.push(saved);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...schema,
|
...schema,
|
||||||
properties: {
|
properties: {
|
||||||
...properties,
|
...properties,
|
||||||
scene: { ...properties.scene, enum: scenes },
|
scene: { ...properties.scene, enum: getSceneChoices(ctx) },
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import type { TFunction } from "i18next";
|
import type { TFunction } from "i18next";
|
||||||
|
import type { HiddenFieldContext } from "@/types/configForm";
|
||||||
import { DetectionModelConfig, FrigateConfig } from "@/types/frigateConfig";
|
import { DetectionModelConfig, FrigateConfig } from "@/types/frigateConfig";
|
||||||
|
|
||||||
/** The scene of the model used by cameras that don't name one. */
|
/** The scene of the model used by cameras that don't name one. */
|
||||||
@@ -13,11 +14,29 @@ export function getSceneLabel(t: TFunction, scene: string | undefined): string {
|
|||||||
return scene;
|
return scene;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** The distinct scenes of the configured models, default first. */
|
/**
|
||||||
export function getModelScenes(config?: FrigateConfig): string[] {
|
* The scenes a detect section can choose from: those of the configured models,
|
||||||
const scenes = new Set<string>([DEFAULT_SCENE]);
|
* default first when a default model exists, plus the saved scene when no model
|
||||||
config?.models?.forEach((model) => scenes.add(model.scene || DEFAULT_SCENE));
|
* uses it, so it can still be seen and changed.
|
||||||
return [...scenes];
|
*/
|
||||||
|
export function getSceneChoices(
|
||||||
|
ctx: Pick<HiddenFieldContext, "fullConfig" | "fullCameraConfig" | "level">,
|
||||||
|
): string[] {
|
||||||
|
const scenes = [
|
||||||
|
...new Set(
|
||||||
|
ctx.fullConfig.models?.map((model) => model.scene || DEFAULT_SCENE),
|
||||||
|
),
|
||||||
|
].sort((a, b) => Number(b === DEFAULT_SCENE) - Number(a === DEFAULT_SCENE));
|
||||||
|
const saved =
|
||||||
|
(ctx.level !== "global"
|
||||||
|
? ctx.fullCameraConfig?.detect?.scene
|
||||||
|
: undefined) ?? ctx.fullConfig.detect?.scene;
|
||||||
|
|
||||||
|
if (saved && !scenes.includes(saved)) {
|
||||||
|
scenes.push(saved);
|
||||||
|
}
|
||||||
|
|
||||||
|
return scenes;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user