mirror of
https://github.com/blakeblackshear/frigate.git
synced 2025-12-13 00:26:42 +03:00
Compare commits
No commits in common. "24a1874225cf6260136c9f5f768af3a013e99603" and "c61bb8f8ae9bd955ecff95af1d0b099be9cdb6b7" have entirely different histories.
24a1874225
...
c61bb8f8ae
@ -50,27 +50,6 @@ export function AnimatedEventCard({
|
|||||||
fetchPreviews: !currentHour,
|
fetchPreviews: !currentHour,
|
||||||
});
|
});
|
||||||
|
|
||||||
const tooltipText = useMemo(() => {
|
|
||||||
if (event?.data?.metadata?.title) {
|
|
||||||
return event.data.metadata.title;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
`${[
|
|
||||||
...new Set([
|
|
||||||
...(event.data.objects || []),
|
|
||||||
...(event.data.sub_labels || []),
|
|
||||||
...(event.data.audio || []),
|
|
||||||
]),
|
|
||||||
]
|
|
||||||
.filter((item) => item !== undefined && !item.includes("-verified"))
|
|
||||||
.map((text) => text.charAt(0).toUpperCase() + text.substring(1))
|
|
||||||
.sort()
|
|
||||||
.join(", ")
|
|
||||||
.replaceAll("-verified", "")} ` + t("detected")
|
|
||||||
);
|
|
||||||
}, [event, t]);
|
|
||||||
|
|
||||||
// visibility
|
// visibility
|
||||||
|
|
||||||
const [windowVisible, setWindowVisible] = useState(true);
|
const [windowVisible, setWindowVisible] = useState(true);
|
||||||
@ -241,7 +220,20 @@ export function AnimatedEventCard({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>{tooltipText}</TooltipContent>
|
<TooltipContent>
|
||||||
|
{`${[
|
||||||
|
...new Set([
|
||||||
|
...(event.data.objects || []),
|
||||||
|
...(event.data.sub_labels || []),
|
||||||
|
...(event.data.audio || []),
|
||||||
|
]),
|
||||||
|
]
|
||||||
|
.filter((item) => item !== undefined && !item.includes("-verified"))
|
||||||
|
.map((text) => text.charAt(0).toUpperCase() + text.substring(1))
|
||||||
|
.sort()
|
||||||
|
.join(", ")
|
||||||
|
.replaceAll("-verified", "")} ` + t("detected")}
|
||||||
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -355,7 +355,9 @@ export default function LiveContextMenu({
|
|||||||
<div
|
<div
|
||||||
className="flex w-full cursor-pointer items-center justify-start gap-2"
|
className="flex w-full cursor-pointer items-center justify-start gap-2"
|
||||||
onClick={
|
onClick={
|
||||||
isEnabled ? () => navigate(`?debug=true#${camera}`) : undefined
|
isEnabled
|
||||||
|
? () => navigate(`/settings?page=debug&camera=${camera}`)
|
||||||
|
: undefined
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div className="text-primary">
|
<div className="text-primary">
|
||||||
|
|||||||
@ -222,7 +222,7 @@ export function MobilePageHeader({
|
|||||||
type MobilePageTitleProps = React.HTMLAttributes<HTMLHeadingElement>;
|
type MobilePageTitleProps = React.HTMLAttributes<HTMLHeadingElement>;
|
||||||
|
|
||||||
export function MobilePageTitle({ className, ...props }: MobilePageTitleProps) {
|
export function MobilePageTitle({ className, ...props }: MobilePageTitleProps) {
|
||||||
return <h2 className={cn("text-lg", className)} {...props} />;
|
return <h2 className={cn("text-lg font-semibold", className)} {...props} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
type MobilePageDescriptionProps = React.HTMLAttributes<HTMLParagraphElement>;
|
type MobilePageDescriptionProps = React.HTMLAttributes<HTMLParagraphElement>;
|
||||||
|
|||||||
@ -42,7 +42,7 @@ export default function RoleChangeDialog({
|
|||||||
<Dialog open={show} onOpenChange={onCancel}>
|
<Dialog open={show} onOpenChange={onCancel}>
|
||||||
<DialogContent className="sm:max-w-[425px]">
|
<DialogContent className="sm:max-w-[425px]">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle className="text-xl">
|
<DialogTitle className="text-xl font-semibold">
|
||||||
{t("users.dialog.changeRole.title")}
|
{t("users.dialog.changeRole.title")}
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
|
|||||||
@ -1170,7 +1170,11 @@ export function ObjectSnapshotTab({
|
|||||||
<Card className="p-1 text-sm md:p-2">
|
<Card className="p-1 text-sm md:p-2">
|
||||||
<CardContent className="flex flex-col items-center justify-between gap-3 p-2 md:flex-row">
|
<CardContent className="flex flex-col items-center justify-between gap-3 p-2 md:flex-row">
|
||||||
<div className={cn("flex flex-col space-y-3")}>
|
<div className={cn("flex flex-col space-y-3")}>
|
||||||
<div className={"text-lg leading-none"}>
|
<div
|
||||||
|
className={
|
||||||
|
"text-lg font-semibold leading-none tracking-tight"
|
||||||
|
}
|
||||||
|
>
|
||||||
{t("explore.plus.submitToPlus.label")}
|
{t("explore.plus.submitToPlus.label")}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-sm text-muted-foreground">
|
<div className="text-sm text-muted-foreground">
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import * as React from "react";
|
import * as React from "react"
|
||||||
import { cva, type VariantProps } from "class-variance-authority";
|
import { cva, type VariantProps } from "class-variance-authority"
|
||||||
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
const alertVariants = cva(
|
const alertVariants = cva(
|
||||||
"relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground",
|
"relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground",
|
||||||
@ -16,8 +16,8 @@ const alertVariants = cva(
|
|||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
variant: "default",
|
variant: "default",
|
||||||
},
|
},
|
||||||
},
|
}
|
||||||
);
|
)
|
||||||
|
|
||||||
const Alert = React.forwardRef<
|
const Alert = React.forwardRef<
|
||||||
HTMLDivElement,
|
HTMLDivElement,
|
||||||
@ -29,8 +29,8 @@ const Alert = React.forwardRef<
|
|||||||
className={cn(alertVariants({ variant }), className)}
|
className={cn(alertVariants({ variant }), className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
));
|
))
|
||||||
Alert.displayName = "Alert";
|
Alert.displayName = "Alert"
|
||||||
|
|
||||||
const AlertTitle = React.forwardRef<
|
const AlertTitle = React.forwardRef<
|
||||||
HTMLParagraphElement,
|
HTMLParagraphElement,
|
||||||
@ -38,11 +38,11 @@ const AlertTitle = React.forwardRef<
|
|||||||
>(({ className, ...props }, ref) => (
|
>(({ className, ...props }, ref) => (
|
||||||
<h5
|
<h5
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn("mb-1 font-medium leading-none", className)}
|
className={cn("mb-1 font-medium leading-none tracking-tight", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
));
|
))
|
||||||
AlertTitle.displayName = "AlertTitle";
|
AlertTitle.displayName = "AlertTitle"
|
||||||
|
|
||||||
const AlertDescription = React.forwardRef<
|
const AlertDescription = React.forwardRef<
|
||||||
HTMLParagraphElement,
|
HTMLParagraphElement,
|
||||||
@ -53,7 +53,7 @@ const AlertDescription = React.forwardRef<
|
|||||||
className={cn("text-sm [&_p]:leading-relaxed", className)}
|
className={cn("text-sm [&_p]:leading-relaxed", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
));
|
))
|
||||||
AlertDescription.displayName = "AlertDescription";
|
AlertDescription.displayName = "AlertDescription"
|
||||||
|
|
||||||
export { Alert, AlertTitle, AlertDescription };
|
export { Alert, AlertTitle, AlertDescription }
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import * as React from "react";
|
import * as React from "react"
|
||||||
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
const Card = React.forwardRef<
|
const Card = React.forwardRef<
|
||||||
HTMLDivElement,
|
HTMLDivElement,
|
||||||
@ -10,12 +10,12 @@ const Card = React.forwardRef<
|
|||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"rounded-lg border bg-card text-card-foreground shadow-sm",
|
"rounded-lg border bg-card text-card-foreground shadow-sm",
|
||||||
className,
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
));
|
))
|
||||||
Card.displayName = "Card";
|
Card.displayName = "Card"
|
||||||
|
|
||||||
const CardHeader = React.forwardRef<
|
const CardHeader = React.forwardRef<
|
||||||
HTMLDivElement,
|
HTMLDivElement,
|
||||||
@ -26,8 +26,8 @@ const CardHeader = React.forwardRef<
|
|||||||
className={cn("flex flex-col space-y-1.5 p-6", className)}
|
className={cn("flex flex-col space-y-1.5 p-6", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
));
|
))
|
||||||
CardHeader.displayName = "CardHeader";
|
CardHeader.displayName = "CardHeader"
|
||||||
|
|
||||||
const CardTitle = React.forwardRef<
|
const CardTitle = React.forwardRef<
|
||||||
HTMLParagraphElement,
|
HTMLParagraphElement,
|
||||||
@ -35,11 +35,14 @@ const CardTitle = React.forwardRef<
|
|||||||
>(({ className, ...props }, ref) => (
|
>(({ className, ...props }, ref) => (
|
||||||
<h3
|
<h3
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn("text-2xl font-semibold leading-none", className)}
|
className={cn(
|
||||||
|
"text-2xl font-semibold leading-none tracking-tight",
|
||||||
|
className
|
||||||
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
));
|
))
|
||||||
CardTitle.displayName = "CardTitle";
|
CardTitle.displayName = "CardTitle"
|
||||||
|
|
||||||
const CardDescription = React.forwardRef<
|
const CardDescription = React.forwardRef<
|
||||||
HTMLParagraphElement,
|
HTMLParagraphElement,
|
||||||
@ -50,16 +53,16 @@ const CardDescription = React.forwardRef<
|
|||||||
className={cn("text-sm text-muted-foreground", className)}
|
className={cn("text-sm text-muted-foreground", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
));
|
))
|
||||||
CardDescription.displayName = "CardDescription";
|
CardDescription.displayName = "CardDescription"
|
||||||
|
|
||||||
const CardContent = React.forwardRef<
|
const CardContent = React.forwardRef<
|
||||||
HTMLDivElement,
|
HTMLDivElement,
|
||||||
React.HTMLAttributes<HTMLDivElement>
|
React.HTMLAttributes<HTMLDivElement>
|
||||||
>(({ className, ...props }, ref) => (
|
>(({ className, ...props }, ref) => (
|
||||||
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
|
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
|
||||||
));
|
))
|
||||||
CardContent.displayName = "CardContent";
|
CardContent.displayName = "CardContent"
|
||||||
|
|
||||||
const CardFooter = React.forwardRef<
|
const CardFooter = React.forwardRef<
|
||||||
HTMLDivElement,
|
HTMLDivElement,
|
||||||
@ -70,14 +73,7 @@ const CardFooter = React.forwardRef<
|
|||||||
className={cn("flex items-center p-6 pt-0", className)}
|
className={cn("flex items-center p-6 pt-0", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
));
|
))
|
||||||
CardFooter.displayName = "CardFooter";
|
CardFooter.displayName = "CardFooter"
|
||||||
|
|
||||||
export {
|
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
|
||||||
Card,
|
|
||||||
CardHeader,
|
|
||||||
CardFooter,
|
|
||||||
CardTitle,
|
|
||||||
CardDescription,
|
|
||||||
CardContent,
|
|
||||||
};
|
|
||||||
|
|||||||
@ -149,7 +149,10 @@ const DialogTitle = React.forwardRef<
|
|||||||
>(({ className, ...props }, ref) => (
|
>(({ className, ...props }, ref) => (
|
||||||
<DialogPrimitive.Title
|
<DialogPrimitive.Title
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn("text-lg font-semibold leading-none", className)}
|
className={cn(
|
||||||
|
"text-lg font-semibold leading-none tracking-tight",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
));
|
));
|
||||||
|
|||||||
@ -85,7 +85,10 @@ const DrawerTitle = React.forwardRef<
|
|||||||
>(({ className, ...props }, ref) => (
|
>(({ className, ...props }, ref) => (
|
||||||
<DrawerPrimitive.Title
|
<DrawerPrimitive.Title
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn("text-lg font-semibold leading-none", className)}
|
className={cn(
|
||||||
|
"text-lg font-semibold leading-none tracking-tight",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
));
|
));
|
||||||
|
|||||||
@ -14,7 +14,12 @@ const Heading = ({
|
|||||||
switch (as) {
|
switch (as) {
|
||||||
case "h1":
|
case "h1":
|
||||||
return (
|
return (
|
||||||
<h1 className={cn("scroll-m-20 text-3xl font-extrabold", className)}>
|
<h1
|
||||||
|
className={cn(
|
||||||
|
"scroll-m-20 text-3xl font-extrabold tracking-tight",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
>
|
||||||
{children}
|
{children}
|
||||||
</h1>
|
</h1>
|
||||||
);
|
);
|
||||||
@ -22,8 +27,8 @@ const Heading = ({
|
|||||||
return (
|
return (
|
||||||
<h2
|
<h2
|
||||||
className={cn(
|
className={cn(
|
||||||
"mb-3 scroll-m-20 text-3xl font-semibold transition-colors first:mt-0",
|
"scroll-m-20 text-3xl font-semibold tracking-tight transition-colors first:mt-0 mb-3",
|
||||||
className,
|
className
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
@ -31,19 +36,34 @@ const Heading = ({
|
|||||||
);
|
);
|
||||||
case "h3":
|
case "h3":
|
||||||
return (
|
return (
|
||||||
<h3 className={cn("mb-3 scroll-m-20 text-2xl font-medium", className)}>
|
<h3
|
||||||
|
className={cn(
|
||||||
|
"scroll-m-20 text-2xl font-semibold tracking-tight mb-3",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
>
|
||||||
{children}
|
{children}
|
||||||
</h3>
|
</h3>
|
||||||
);
|
);
|
||||||
case "h4":
|
case "h4":
|
||||||
return (
|
return (
|
||||||
<h4 className={cn("scroll-m-20 text-xl font-medium", className)}>
|
<h4
|
||||||
|
className={cn(
|
||||||
|
"scroll-m-20 text-xl font-semibold tracking-tight",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
>
|
||||||
{children}
|
{children}
|
||||||
</h4>
|
</h4>
|
||||||
);
|
);
|
||||||
default:
|
default:
|
||||||
return (
|
return (
|
||||||
<h1 className={cn("scroll-m-20 text-3xl font-extrabold", className)}>
|
<h1
|
||||||
|
className={cn(
|
||||||
|
"scroll-m-20 text-3xl font-extrabold tracking-tight",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
>
|
||||||
{children}
|
{children}
|
||||||
</h1>
|
</h1>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -112,8 +112,7 @@ export function useSearchEffect(
|
|||||||
callback: (value: string) => boolean,
|
callback: (value: string) => boolean,
|
||||||
) {
|
) {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const navigate = useNavigate();
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
const [searchParams] = useSearchParams();
|
|
||||||
|
|
||||||
const param = useMemo(() => {
|
const param = useMemo(() => {
|
||||||
const param = searchParams.get(key);
|
const param = searchParams.get(key);
|
||||||
@ -133,17 +132,7 @@ export function useSearchEffect(
|
|||||||
const remove = callback(param[1]);
|
const remove = callback(param[1]);
|
||||||
|
|
||||||
if (remove) {
|
if (remove) {
|
||||||
navigate(location.pathname + location.hash, {
|
setSearchParams(undefined, { state: location.state, replace: true });
|
||||||
state: location.state,
|
|
||||||
replace: true,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}, [
|
}, [param, location.state, callback, setSearchParams]);
|
||||||
param,
|
|
||||||
location.state,
|
|
||||||
location.pathname,
|
|
||||||
location.hash,
|
|
||||||
callback,
|
|
||||||
navigate,
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -292,7 +292,7 @@ export default function Settings() {
|
|||||||
<Logo className="h-8" />
|
<Logo className="h-8" />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-row text-center">
|
<div className="flex flex-row text-center">
|
||||||
<h2 className="ml-2 text-lg">
|
<h2 className="ml-2 text-lg font-semibold">
|
||||||
{t("menu.settings", { ns: "common" })}
|
{t("menu.settings", { ns: "common" })}
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -30,7 +30,7 @@ type FrigateObjectState = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export interface FrigateReview {
|
export interface FrigateReview {
|
||||||
type: "new" | "update" | "end" | "genai";
|
type: "new" | "update" | "end";
|
||||||
before: ReviewSegment;
|
before: ReviewSegment;
|
||||||
after: ReviewSegment;
|
after: ReviewSegment;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -111,7 +111,6 @@ import { Trans, useTranslation } from "react-i18next";
|
|||||||
import { useDocDomain } from "@/hooks/use-doc-domain";
|
import { useDocDomain } from "@/hooks/use-doc-domain";
|
||||||
import PtzControlPanel from "@/components/overlay/PtzControlPanel";
|
import PtzControlPanel from "@/components/overlay/PtzControlPanel";
|
||||||
import ObjectSettingsView from "../settings/ObjectSettingsView";
|
import ObjectSettingsView from "../settings/ObjectSettingsView";
|
||||||
import { useSearchEffect } from "@/hooks/use-overlay-state";
|
|
||||||
|
|
||||||
type LiveCameraViewProps = {
|
type LiveCameraViewProps = {
|
||||||
config?: FrigateConfig;
|
config?: FrigateConfig;
|
||||||
@ -275,14 +274,6 @@ export default function LiveCameraView({
|
|||||||
const [showStats, setShowStats] = useState(false);
|
const [showStats, setShowStats] = useState(false);
|
||||||
const [debug, setDebug] = useState(false);
|
const [debug, setDebug] = useState(false);
|
||||||
|
|
||||||
useSearchEffect("debug", (value: string) => {
|
|
||||||
if (value === "true") {
|
|
||||||
setDebug(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
const [fullResolution, setFullResolution] = useState<VideoResolutionType>({
|
const [fullResolution, setFullResolution] = useState<VideoResolutionType>({
|
||||||
width: 0,
|
width: 0,
|
||||||
height: 0,
|
height: 0,
|
||||||
|
|||||||
@ -114,11 +114,7 @@ export default function LiveDashboardView({
|
|||||||
|
|
||||||
// if event is ended and was saved, update events list
|
// if event is ended and was saved, update events list
|
||||||
if (eventUpdate.after.severity == "alert") {
|
if (eventUpdate.after.severity == "alert") {
|
||||||
if (
|
if (eventUpdate.type == "end" || eventUpdate.type == "new") {
|
||||||
eventUpdate.type == "end" ||
|
|
||||||
eventUpdate.type == "new" ||
|
|
||||||
eventUpdate.type == "genai"
|
|
||||||
) {
|
|
||||||
setTimeout(
|
setTimeout(
|
||||||
() => updateEvents(),
|
() => updateEvents(),
|
||||||
eventUpdate.type == "end" ? 1000 : 6000,
|
eventUpdate.type == "end" ? 1000 : 6000,
|
||||||
|
|||||||
@ -162,9 +162,9 @@ export default function ObjectSettingsView({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mt-1 flex size-full flex-col md:flex-row">
|
<div className="flex size-full flex-col md:flex-row">
|
||||||
<Toaster position="top-center" closeButton={true} />
|
<Toaster position="top-center" closeButton={true} />
|
||||||
<div className="scrollbar-container order-last mb-10 mt-2 flex h-full w-full flex-col overflow-y-auto rounded-lg border-[1px] border-secondary-foreground bg-background_alt p-2 md:order-none md:mb-0 md:mr-2 md:mt-0 md:w-3/12">
|
<div className="scrollbar-container order-last mb-10 mt-2 flex h-full w-full flex-col overflow-y-auto rounded-lg border-[1px] border-secondary-foreground bg-background_alt p-2 md:order-none md:w-3/12">
|
||||||
<Heading as="h4" className="mb-2">
|
<Heading as="h4" className="mb-2">
|
||||||
{t("debug.title")}
|
{t("debug.title")}
|
||||||
</Heading>
|
</Heading>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user