mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-07-20 18:59:01 +03:00
Miscellaneous fixes (0.17 beta) (#21699)
CI / AMD64 Build (push) Has been cancelled
CI / ARM Build (push) Has been cancelled
CI / Jetson Jetpack 6 (push) Has been cancelled
CI / AMD64 Extra Build (push) Has been cancelled
CI / ARM Extra Build (push) Has been cancelled
CI / Synaptics Build (push) Has been cancelled
CI / Assemble and push default build (push) Has been cancelled
CI / AMD64 Build (push) Has been cancelled
CI / ARM Build (push) Has been cancelled
CI / Jetson Jetpack 6 (push) Has been cancelled
CI / AMD64 Extra Build (push) Has been cancelled
CI / ARM Extra Build (push) Has been cancelled
CI / Synaptics Build (push) Has been cancelled
CI / Assemble and push default build (push) Has been cancelled
* tracking details tweaks - fix 4:3 layout - get and use aspect of record stream if different from detect stream * aspect ratio docs tip * spacing * fix * i18n fix * additional logs on ffmpeg exit * improve no camera view instead of showing an "add camera" message, show a specific message for empty camera groups when frigate already has cameras added * add note about separate onvif accounts in some camera firmware * clarify review summary report docs * review settings tweaks - remove horizontal divider - update description language for switches - keep save button disabled until review classification settings change * use correct Toaster component from shadcn * clarify support for intel b-series (battlemage) gpus * add clarifying comment to dummy camera docs
This commit is contained in:
@@ -181,6 +181,16 @@
|
||||
"restricted": {
|
||||
"title": "No Cameras Available",
|
||||
"description": "You don't have permission to view any cameras in this group."
|
||||
},
|
||||
"default": {
|
||||
"title": "No Cameras Configured",
|
||||
"description": "Get started by connecting a camera to Frigate.",
|
||||
"buttonText": "Add Camera"
|
||||
},
|
||||
"group": {
|
||||
"title": "No Cameras in Group",
|
||||
"description": "This camera group has no assigned or enabled cameras.",
|
||||
"buttonText": "Manage Groups"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -386,11 +386,11 @@
|
||||
"title": "Camera Review Settings",
|
||||
"object_descriptions": {
|
||||
"title": "Generative AI Object Descriptions",
|
||||
"desc": "Temporarily enable/disable Generative AI object descriptions for this camera. When disabled, AI generated descriptions will not be requested for tracked objects on this camera."
|
||||
"desc": "Temporarily enable/disable Generative AI object descriptions for this camera until Frigate restarts. When disabled, AI generated descriptions will not be requested for tracked objects on this camera."
|
||||
},
|
||||
"review_descriptions": {
|
||||
"title": "Generative AI Review Descriptions",
|
||||
"desc": "Temporarily enable/disable Generative AI review descriptions for this camera. When disabled, AI generated descriptions will not be requested for review items on this camera."
|
||||
"desc": "Temporarily enable/disable Generative AI review descriptions for this camera until Frigate restarts. When disabled, AI generated descriptions will not be requested for review items on this camera."
|
||||
},
|
||||
"review": {
|
||||
"title": "Review",
|
||||
|
||||
@@ -35,7 +35,9 @@ export function EmptyCard({
|
||||
{icon}
|
||||
{TitleComponent}
|
||||
{description && (
|
||||
<div className="mb-3 text-secondary-foreground">{description}</div>
|
||||
<div className="mb-3 text-center text-secondary-foreground">
|
||||
{description}
|
||||
</div>
|
||||
)}
|
||||
{buttonText?.length && (
|
||||
<Button size="sm" variant="select">
|
||||
|
||||
@@ -13,7 +13,7 @@ import HlsVideoPlayer from "@/components/player/HlsVideoPlayer";
|
||||
import { baseUrl } from "@/api/baseUrl";
|
||||
import { REVIEW_PADDING } from "@/types/review";
|
||||
import {
|
||||
ASPECT_VERTICAL_LAYOUT,
|
||||
ASPECT_PORTRAIT_LAYOUT,
|
||||
ASPECT_WIDE_LAYOUT,
|
||||
Recording,
|
||||
} from "@/types/record";
|
||||
@@ -39,6 +39,7 @@ import { useApiHost } from "@/api";
|
||||
import ImageLoadingIndicator from "@/components/indicators/ImageLoadingIndicator";
|
||||
import ObjectTrackOverlay from "../ObjectTrackOverlay";
|
||||
import { useIsAdmin } from "@/hooks/use-is-admin";
|
||||
import { VideoResolutionType } from "@/types/live";
|
||||
|
||||
type TrackingDetailsProps = {
|
||||
className?: string;
|
||||
@@ -253,16 +254,25 @@ export function TrackingDetails({
|
||||
|
||||
const [timelineSize] = useResizeObserver(timelineContainerRef);
|
||||
|
||||
const [fullResolution, setFullResolution] = useState<VideoResolutionType>({
|
||||
width: 0,
|
||||
height: 0,
|
||||
});
|
||||
|
||||
const aspectRatio = useMemo(() => {
|
||||
if (!config) {
|
||||
return 16 / 9;
|
||||
}
|
||||
|
||||
if (fullResolution.width && fullResolution.height) {
|
||||
return fullResolution.width / fullResolution.height;
|
||||
}
|
||||
|
||||
return (
|
||||
config.cameras[event.camera].detect.width /
|
||||
config.cameras[event.camera].detect.height
|
||||
);
|
||||
}, [config, event]);
|
||||
}, [config, event, fullResolution]);
|
||||
|
||||
const label = event.sub_label
|
||||
? event.sub_label
|
||||
@@ -460,7 +470,7 @@ export function TrackingDetails({
|
||||
return "normal";
|
||||
} else if (aspectRatio > ASPECT_WIDE_LAYOUT) {
|
||||
return "wide";
|
||||
} else if (aspectRatio < ASPECT_VERTICAL_LAYOUT) {
|
||||
} else if (aspectRatio < ASPECT_PORTRAIT_LAYOUT) {
|
||||
return "tall";
|
||||
} else {
|
||||
return "normal";
|
||||
@@ -556,6 +566,7 @@ export function TrackingDetails({
|
||||
onSeekToTime={handleSeekToTime}
|
||||
onUploadFrame={onUploadFrameToPlus}
|
||||
onPlaying={() => setIsVideoLoading(false)}
|
||||
setFullResolution={setFullResolution}
|
||||
isDetailMode={true}
|
||||
camera={event.camera}
|
||||
currentTimeOverride={currentTime}
|
||||
@@ -623,7 +634,7 @@ export function TrackingDetails({
|
||||
<div
|
||||
className={cn(
|
||||
isDesktop && "justify-start overflow-hidden",
|
||||
aspectRatio > 1 && aspectRatio < 1.5
|
||||
aspectRatio > 1 && aspectRatio < ASPECT_PORTRAIT_LAYOUT
|
||||
? "lg:basis-3/5"
|
||||
: "lg:basis-2/5",
|
||||
)}
|
||||
|
||||
@@ -16,7 +16,7 @@ import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useForm, useFieldArray } from "react-hook-form";
|
||||
import { z } from "zod";
|
||||
import axios from "axios";
|
||||
import { toast, Toaster } from "sonner";
|
||||
import { toast } from "sonner";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useState, useMemo, useEffect } from "react";
|
||||
import { LuTrash2, LuPlus } from "react-icons/lu";
|
||||
@@ -26,6 +26,7 @@ import useSWR from "swr";
|
||||
import { processCameraName } from "@/utils/cameraUtil";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { ConfigSetBody } from "@/types/cameraWizard";
|
||||
import { Toaster } from "../ui/sonner";
|
||||
|
||||
const RoleEnum = z.enum(["audio", "detect", "record"]);
|
||||
type Role = z.infer<typeof RoleEnum>;
|
||||
|
||||
@@ -44,4 +44,5 @@ export type RecordingStartingPoint = {
|
||||
export type RecordingPlayerError = "stalled" | "startup";
|
||||
|
||||
export const ASPECT_VERTICAL_LAYOUT = 1.5;
|
||||
export const ASPECT_PORTRAIT_LAYOUT = 1.333;
|
||||
export const ASPECT_WIDE_LAYOUT = 2;
|
||||
|
||||
@@ -447,7 +447,7 @@ export default function LiveDashboardView({
|
||||
)}
|
||||
|
||||
{cameras.length == 0 && !includeBirdseye ? (
|
||||
<NoCameraView />
|
||||
<NoCameraView cameraGroup={cameraGroup} />
|
||||
) : (
|
||||
<>
|
||||
{!fullscreen && events && events.length > 0 && (
|
||||
@@ -666,28 +666,39 @@ export default function LiveDashboardView({
|
||||
);
|
||||
}
|
||||
|
||||
function NoCameraView() {
|
||||
function NoCameraView({ cameraGroup }: { cameraGroup?: string }) {
|
||||
const { t } = useTranslation(["views/live"]);
|
||||
const { auth } = useContext(AuthContext);
|
||||
const isAdmin = useIsAdmin();
|
||||
|
||||
// Check if this is a restricted user with no cameras in this group
|
||||
const isDefault = cameraGroup === "default";
|
||||
const isRestricted = !isAdmin && auth.isAuthenticated;
|
||||
|
||||
let type: "default" | "group" | "restricted";
|
||||
if (isRestricted) {
|
||||
type = "restricted";
|
||||
} else if (isDefault) {
|
||||
type = "default";
|
||||
} else {
|
||||
type = "group";
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex size-full items-center justify-center">
|
||||
<EmptyCard
|
||||
icon={<BsFillCameraVideoOffFill className="size-8" />}
|
||||
title={
|
||||
isRestricted ? t("noCameras.restricted.title") : t("noCameras.title")
|
||||
title={t(`noCameras.${type}.title`)}
|
||||
description={t(`noCameras.${type}.description`)}
|
||||
buttonText={
|
||||
type !== "restricted" && isDefault
|
||||
? t(`noCameras.${type}.buttonText`)
|
||||
: undefined
|
||||
}
|
||||
description={
|
||||
isRestricted
|
||||
? t("noCameras.restricted.description")
|
||||
: t("noCameras.description")
|
||||
link={
|
||||
type !== "restricted" && isDefault
|
||||
? "/settings?page=cameraManagement"
|
||||
: undefined
|
||||
}
|
||||
buttonText={!isRestricted ? t("noCameras.buttonText") : undefined}
|
||||
link={!isRestricted ? "/settings?page=cameraManagement" : undefined}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import Heading from "@/components/ui/heading";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { Toaster } from "sonner";
|
||||
import { Toaster } from "@/components/ui/sonner";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import useSWR from "swr";
|
||||
import { FrigateConfig } from "@/types/frigateConfig";
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import Heading from "@/components/ui/heading";
|
||||
import { useCallback, useContext, useEffect, useMemo, useState } from "react";
|
||||
import { Toaster, toast } from "sonner";
|
||||
import { toast } from "sonner";
|
||||
import { Toaster } from "@/components/ui/sonner";
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
@@ -158,11 +159,12 @@ export default function CameraReviewSettingsView({
|
||||
});
|
||||
}
|
||||
setChangedValue(true);
|
||||
setUnsavedChanges(true);
|
||||
setSelectDetections(isChecked as boolean);
|
||||
},
|
||||
// we know that these deps are correct
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[watchedAlertsZones],
|
||||
[watchedAlertsZones, setUnsavedChanges],
|
||||
);
|
||||
|
||||
const saveToConfig = useCallback(
|
||||
@@ -197,6 +199,8 @@ export default function CameraReviewSettingsView({
|
||||
position: "top-center",
|
||||
},
|
||||
);
|
||||
setChangedValue(false);
|
||||
setUnsavedChanges(false);
|
||||
updateConfig();
|
||||
} else {
|
||||
toast.error(
|
||||
@@ -229,7 +233,14 @@ export default function CameraReviewSettingsView({
|
||||
setIsLoading(false);
|
||||
});
|
||||
},
|
||||
[updateConfig, setIsLoading, selectedCamera, cameraConfig, t],
|
||||
[
|
||||
updateConfig,
|
||||
setIsLoading,
|
||||
selectedCamera,
|
||||
cameraConfig,
|
||||
t,
|
||||
setUnsavedChanges,
|
||||
],
|
||||
);
|
||||
|
||||
const onCancel = useCallback(() => {
|
||||
@@ -495,6 +506,7 @@ export default function CameraReviewSettingsView({
|
||||
)}
|
||||
onCheckedChange={(checked) => {
|
||||
setChangedValue(true);
|
||||
setUnsavedChanges(true);
|
||||
return checked
|
||||
? field.onChange([
|
||||
...field.value,
|
||||
@@ -600,6 +612,8 @@ export default function CameraReviewSettingsView({
|
||||
zone.name,
|
||||
)}
|
||||
onCheckedChange={(checked) => {
|
||||
setChangedValue(true);
|
||||
setUnsavedChanges(true);
|
||||
return checked
|
||||
? field.onChange([
|
||||
...field.value,
|
||||
@@ -699,7 +713,6 @@ export default function CameraReviewSettingsView({
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<Separator className="my-2 flex bg-secondary" />
|
||||
|
||||
<div className="flex w-full flex-row items-center gap-2 pt-2 md:w-[25%]">
|
||||
<Button
|
||||
@@ -712,7 +725,7 @@ export default function CameraReviewSettingsView({
|
||||
</Button>
|
||||
<Button
|
||||
variant="select"
|
||||
disabled={isLoading}
|
||||
disabled={!changedValue || isLoading}
|
||||
className="flex flex-1"
|
||||
aria-label={t("button.save", { ns: "common" })}
|
||||
type="submit"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import Heading from "@/components/ui/heading";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { useCallback, useContext, useEffect, useState } from "react";
|
||||
import { Toaster } from "sonner";
|
||||
import { Toaster } from "@/components/ui/sonner";
|
||||
import { Separator } from "../../components/ui/separator";
|
||||
import ActivityIndicator from "@/components/indicators/activity-indicator";
|
||||
import { toast } from "sonner";
|
||||
|
||||
@@ -664,9 +664,7 @@ export default function TriggerView({
|
||||
<TableHeader className="sticky top-0 bg-muted/50">
|
||||
<TableRow>
|
||||
<TableHead className="w-4"></TableHead>
|
||||
<TableHead>
|
||||
{t("name", { ns: "triggers.table.name" })}
|
||||
</TableHead>
|
||||
<TableHead>{t("triggers.table.name")}</TableHead>
|
||||
<TableHead>{t("triggers.table.type")}</TableHead>
|
||||
<TableHead>
|
||||
{t("triggers.table.lastTriggered")}
|
||||
|
||||
@@ -2,7 +2,7 @@ import Heading from "@/components/ui/heading";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { useCallback, useContext, useEffect } from "react";
|
||||
import { Toaster } from "sonner";
|
||||
import { Toaster } from "@/components/ui/sonner";
|
||||
import { toast } from "sonner";
|
||||
import { Separator } from "../../components/ui/separator";
|
||||
import { Button } from "../../components/ui/button";
|
||||
|
||||
Reference in New Issue
Block a user