Compare commits

..
Author SHA1 Message Date
dependabot[bot]andGitHub 13c218d597 Update openai requirement from ==3.35.* to ==3.36.* in /docker/main
Updates the requirements on [openai](https://github.com/openai/openai-python) to permit the latest version.
- [Release notes](https://github.com/openai/openai-python/releases)
- [Changelog](https://github.com/openai/openai-python/blob/main/CHANGELOG.md)
- [Commits](https://github.com/openai/openai-python/compare/v3.35.0...v3.36.1)

---
updated-dependencies:
- dependency-name: openai
  dependency-version: 3.36.1
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-10-25 11:34:19 +00:00
6 changed files with 13 additions and 154 deletions
+1 -1
View File
@@ -49,7 +49,7 @@ transformers == 5.20.*
# Generative AI
google-genai == 2.31.*
ollama == 0.6.*
openai == 3.35.*
openai == 3.36.*
# push notifications
py-vapid == 1.9.6
pywebpush == 2.5.*
-92
View File
@@ -1,5 +1,4 @@
import { test, expect, type FrigateApp } from "../fixtures/frigate-test";
import { viewerProfile } from "../fixtures/mock-data/profile";
import {
expectBodyInteractive,
waitForBodyInteractive,
@@ -989,97 +988,6 @@ test.describe("Multi-Camera Export from History @high", () => {
"Front gate follow-up",
);
});
test("a blank case name shows an error and disables export", async ({
frigateApp,
}) => {
await frigateApp.installDefaults();
const form = await openMultiCameraTab(frigateApp);
const caseName = form.getByPlaceholder(/new case name/i);
await expect(caseName).toHaveValue(/^Multi-camera export - /, {
timeout: 5_000,
});
await expect(form.getByText("Case name is required")).toBeHidden();
await caseName.fill("");
await expect(form.getByText("Case name is required")).toBeVisible();
await expect(
form.getByRole("button", { name: /^export \d+ cameras?$/i }),
).toBeDisabled();
});
test("clearing a time field keeps the previous valid range", async ({
frigateApp,
}) => {
await frigateApp.installDefaults();
let capturedPayload: { items: { end_time: number }[] } | null = null;
await frigateApp.page.route("**/api/exports/batch", async (route) => {
capturedPayload = route.request().postDataJSON();
await route.fulfill({
status: 202,
json: { export_case_id: null, export_ids: [], results: [] },
});
});
const form = await openMultiCameraTab(frigateApp);
const firstCamera = form.locator("button[aria-pressed]").first();
await expect(firstCamera).toBeVisible({ timeout: 5_000 });
if ((await firstCamera.getAttribute("aria-pressed")) === "false") {
await firstCamera.click();
}
// the seeded end can equal the picker's fallback, so move it off that
// value before clearing
await form.getByRole("button", { name: /^end time$/i }).click();
const endInput = frigateApp.page.locator("#endTime");
const [hour, minute, second] = (await endInput.inputValue()).split(":");
const chosen = `${hour}:${minute}:${String((Number(second) + 30) % 60).padStart(2, "0")}`;
await endInput.fill(chosen);
await endInput.fill("");
await expect(endInput).toHaveValue(chosen);
await frigateApp.page.keyboard.press("Escape");
await form.getByRole("button", { name: /^export \d+ cameras?$/i }).click();
await expect.poll(() => capturedPayload).not.toBeNull();
const submittedClock = await frigateApp.page.evaluate(
(endTime) => new Date(endTime * 1000).toTimeString().slice(0, 8),
capturedPayload!.items[0].end_time,
);
expect(submittedClock).toBe(chosen);
});
test("viewers can export without a case", async ({ frigateApp }) => {
await frigateApp.installDefaults({ profile: viewerProfile() });
let capturedPayload: Record<string, unknown> | null = null;
await frigateApp.page.route("**/api/exports/batch", async (route) => {
capturedPayload = route.request().postDataJSON();
await route.fulfill({
status: 202,
json: { export_case_id: null, export_ids: [], results: [] },
});
});
const form = await openMultiCameraTab(frigateApp);
const firstCamera = form.locator("button[aria-pressed]").first();
await expect(firstCamera).toBeVisible({ timeout: 5_000 });
if ((await firstCamera.getAttribute("aria-pressed")) === "false") {
await firstCamera.click();
}
await expect(form.getByPlaceholder(/new case name/i)).toBeHidden();
await form.getByRole("button", { name: /^export \d+ cameras?$/i }).click();
await expect.poll(() => capturedPayload).not.toBeNull();
expect(capturedPayload).not.toHaveProperty("new_case_name");
expect(capturedPayload).not.toHaveProperty("export_case_id");
});
});
test.describe("Export Page - Active Job Progress @medium", () => {
+2 -4
View File
@@ -66,8 +66,8 @@
"newCaseNamePlaceholder": "New case name",
"newCaseDescriptionPlaceholder": "Case description",
"label": "Case",
"placeholder": "Select a case",
"nameRequired": "Case name is required"
"nonAdminHelp": "A new case will be created for these exports.",
"placeholder": "Select a case"
},
"select": "Select",
"export": "Export",
@@ -91,11 +91,9 @@
"selectedCount": "{{selected}} / {{total}} selected",
"checkingActivity": "Checking camera activity...",
"noCameras": "No cameras available",
"noCamerasSelected": "Select at least one camera",
"detectionCount_one": "1 tracked object",
"detectionCount_other": "{{count}} tracked objects",
"nameLabel": "Export name",
"caseNameDefault": "Multi-camera export - {{date}}",
"namePlaceholder": "Optional base name for these exports",
"queueingButton": "Queueing Exports...",
"exportButton_one": "Export 1 Camera",
@@ -151,11 +151,6 @@ export function CustomTimeSelector({
step={isIOS ? "60" : "1"}
onChange={(e) => {
const clock = e.target.value;
if (!clock) {
return;
}
const [hour, minute, second] = isIOS
? [...clock.split(":"), "00"]
: clock.split(":");
@@ -222,11 +217,6 @@ export function CustomTimeSelector({
step={isIOS ? "60" : "1"}
onChange={(e) => {
const clock = e.target.value;
if (!clock) {
return;
}
const [hour, minute, second] = isIOS
? [...clock.split(":"), "00"]
: clock.split(":");
+4 -46
View File
@@ -65,8 +65,6 @@ import { resolveCameraName } from "@/hooks/use-camera-friendly-name";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "../ui/tabs";
import { Textarea } from "../ui/textarea";
import { useIsAdmin } from "@/hooks/use-is-admin";
import { useDateLocale } from "@/hooks/use-date-locale";
import { formatUnixTimestampToDateTime } from "@/utils/dateUtil";
import { isReplayCamera } from "@/utils/cameraUtil";
import { isValidIconName } from "@/utils/iconUtil";
@@ -443,7 +441,6 @@ export function ExportContent({
}: ExportContentProps) {
const { t } = useTranslation(["components/dialog"]);
const isAdmin = useIsAdmin();
const locale = useDateLocale();
const [selectedOption, setSelectedOption] = useState<ExportOption>("1");
const { data: cases } = useSWR<ExportCase[]>(isAdmin ? "cases" : null);
const { data: config } = useSWR<FrigateConfig>("config");
@@ -666,25 +663,12 @@ export function ExportContent({
]);
const selectedCameraCount = selectedCameraIds.length;
const batchRangeError = !range
? t("export.toast.error.noValidTimeSelected")
: range.before <= range.after
? t("export.toast.error.endTimeMustAfterStartTime")
: undefined;
const batchCameraError =
!isEventsLoading && selectedCameraCount === 0
? t("export.multiCamera.noCamerasSelected")
: undefined;
const batchCaseNameError =
isAdmin && batchCaseSelection === "new" && newCaseName.trim().length === 0
? t("export.case.nameRequired")
: undefined;
const canStartBatchExport =
!batchRangeError &&
!batchCaseNameError &&
Boolean(range && range.before > range.after) &&
selectedCameraCount > 0 &&
!isStartingBatchExport;
!isStartingBatchExport &&
(batchCaseSelection !== "new" || newCaseName.trim().length > 0) &&
batchCaseSelection.length > 0;
const onSelectTime = useCallback(
(option: ExportOption) => {
@@ -981,17 +965,6 @@ export function ExportContent({
if (tab === "multi") {
setRange(buildRangeAroundCurrentTime(MULTI_CAMERA_RANGE_SECONDS));
setBatchCaseSelection(selectedCaseId ?? "new");
if (newCaseName.trim().length === 0) {
setNewCaseName(
t("export.multiCamera.caseNameDefault", {
date: formatUnixTimestampToDateTime(Date.now() / 1000, {
date_style: "medium",
time_style: "short",
locale,
}),
}),
);
}
} else {
onSelectTime(selectedOption);
}
@@ -1159,11 +1132,6 @@ export function ExportContent({
</TooltipPortal>
</Tooltip>
</div>
{batchRangeError && (
<p className="text-sm font-medium text-destructive">
{batchRangeError}
</p>
)}
</div>
<div className="space-y-2">
@@ -1347,11 +1315,6 @@ export function ExportContent({
);
})}
</div>
{batchCameraError && (
<p className="text-sm font-medium text-destructive">
{batchCameraError}
</p>
)}
</div>
<div className="space-y-2">
@@ -1404,11 +1367,6 @@ export function ExportContent({
value={newCaseName}
onChange={(event) => setNewCaseName(event.target.value)}
/>
{batchCaseNameError && (
<p className="text-sm font-medium text-destructive">
{batchCaseNameError}
</p>
)}
<Textarea
placeholder={t("export.case.newCaseDescriptionPlaceholder")}
value={newCaseDescription}
@@ -68,7 +68,9 @@ export default function MultiExportDialog({
const isAdmin = useIsAdmin();
const { data: config } = useSWR<FrigateConfig>("config");
// Only admins can assign exports to a case, so non-admins skip the case list
// Only admins can attach exports to an existing case (enforced server-side
// by POST /exports/batch). Skip fetching the case list entirely for
// non-admins — they can only ever use the "Create new case" branch.
const { data: cases } = useSWR<ExportCase[]>(isAdmin ? "cases" : null);
const [open, setOpen] = useState(false);
@@ -282,6 +284,9 @@ export default function MultiExportDialog({
t,
]);
// New-case inputs: rendered below the Select when caseSelection === "new",
// or rendered standalone for non-admins (who never see the Select since
// they cannot attach to an existing case).
const newCaseInputs = (
<div className="space-y-2 pt-1">
<Input