mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-03-29 03:20:21 +03:00
tweaks
This commit is contained in:
parent
ad00001049
commit
23c6e5231f
@ -35,13 +35,13 @@ DETECT_FFMPEG_OUTPUT_ARGS_DEFAULT = [
|
|||||||
class FfmpegOutputArgsConfig(FrigateBaseModel):
|
class FfmpegOutputArgsConfig(FrigateBaseModel):
|
||||||
detect: Union[str, list[str]] = Field(
|
detect: Union[str, list[str]] = Field(
|
||||||
default=DETECT_FFMPEG_OUTPUT_ARGS_DEFAULT,
|
default=DETECT_FFMPEG_OUTPUT_ARGS_DEFAULT,
|
||||||
title="Detect output args",
|
title="Detect output arguments",
|
||||||
description="Default output args for detect role streams.",
|
description="Default output arguments for detect role streams.",
|
||||||
)
|
)
|
||||||
record: Union[str, list[str]] = Field(
|
record: Union[str, list[str]] = Field(
|
||||||
default=RECORD_FFMPEG_OUTPUT_ARGS_DEFAULT,
|
default=RECORD_FFMPEG_OUTPUT_ARGS_DEFAULT,
|
||||||
title="Record output args",
|
title="Record output arguments",
|
||||||
description="Default output args for record role streams.",
|
description="Default output arguments for record role streams.",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -124,17 +124,17 @@ class CameraInput(FrigateBaseModel):
|
|||||||
)
|
)
|
||||||
global_args: Union[str, list[str]] = Field(
|
global_args: Union[str, list[str]] = Field(
|
||||||
default_factory=list,
|
default_factory=list,
|
||||||
title="FFmpeg global args",
|
title="FFmpeg global arguments",
|
||||||
description="FFmpeg global arguments for this input stream.",
|
description="FFmpeg global arguments for this input stream.",
|
||||||
)
|
)
|
||||||
hwaccel_args: Union[str, list[str]] = Field(
|
hwaccel_args: Union[str, list[str]] = Field(
|
||||||
default_factory=list,
|
default_factory=list,
|
||||||
title="Hardware acceleration args",
|
title="Hardware acceleration arguments",
|
||||||
description="Hardware acceleration arguments for this input stream.",
|
description="Hardware acceleration arguments for this input stream.",
|
||||||
)
|
)
|
||||||
input_args: Union[str, list[str]] = Field(
|
input_args: Union[str, list[str]] = Field(
|
||||||
default_factory=list,
|
default_factory=list,
|
||||||
title="Input args",
|
title="Input arguments",
|
||||||
description="Input arguments specific to this stream.",
|
description="Input arguments specific to this stream.",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -448,7 +448,7 @@ class FrigateConfig(FrigateBaseModel):
|
|||||||
timestamp_style: TimestampStyleConfig = Field(
|
timestamp_style: TimestampStyleConfig = Field(
|
||||||
default_factory=TimestampStyleConfig,
|
default_factory=TimestampStyleConfig,
|
||||||
title="Timestamp style",
|
title="Timestamp style",
|
||||||
description="Styling options for in-feed timestamps applied to recordings and snapshots.",
|
description="Styling options for in-feed timestamps applied to debug view and snapshots.",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Classification Config
|
# Classification Config
|
||||||
|
|||||||
@ -170,12 +170,12 @@
|
|||||||
"label": "Output arguments",
|
"label": "Output arguments",
|
||||||
"description": "Default output arguments used for different FFmpeg roles such as detect and record.",
|
"description": "Default output arguments used for different FFmpeg roles such as detect and record.",
|
||||||
"detect": {
|
"detect": {
|
||||||
"label": "Detect output args",
|
"label": "Detect output arguments",
|
||||||
"description": "Default output args for detect role streams."
|
"description": "Default output arguments for detect role streams."
|
||||||
},
|
},
|
||||||
"record": {
|
"record": {
|
||||||
"label": "Record output args",
|
"label": "Record output arguments",
|
||||||
"description": "Default output args for record role streams."
|
"description": "Default output arguments for record role streams."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"retry_interval": {
|
"retry_interval": {
|
||||||
@ -202,15 +202,15 @@
|
|||||||
"description": "Roles for this input stream (for example: detect, record, audio)."
|
"description": "Roles for this input stream (for example: detect, record, audio)."
|
||||||
},
|
},
|
||||||
"global_args": {
|
"global_args": {
|
||||||
"label": "FFmpeg global args",
|
"label": "FFmpeg global arguments",
|
||||||
"description": "FFmpeg global arguments for this input stream."
|
"description": "FFmpeg global arguments for this input stream."
|
||||||
},
|
},
|
||||||
"hwaccel_args": {
|
"hwaccel_args": {
|
||||||
"label": "Hardware acceleration args",
|
"label": "Hardware acceleration arguments",
|
||||||
"description": "Hardware acceleration arguments for this input stream."
|
"description": "Hardware acceleration arguments for this input stream."
|
||||||
},
|
},
|
||||||
"input_args": {
|
"input_args": {
|
||||||
"label": "Input args",
|
"label": "Input arguments",
|
||||||
"description": "Input arguments specific to this stream."
|
"description": "Input arguments specific to this stream."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1313,12 +1313,12 @@
|
|||||||
"label": "Output arguments",
|
"label": "Output arguments",
|
||||||
"description": "Default output arguments used for different FFmpeg roles such as detect and record.",
|
"description": "Default output arguments used for different FFmpeg roles such as detect and record.",
|
||||||
"detect": {
|
"detect": {
|
||||||
"label": "Detect output args",
|
"label": "Detect output arguments",
|
||||||
"description": "Default output args for detect role streams."
|
"description": "Default output arguments for detect role streams."
|
||||||
},
|
},
|
||||||
"record": {
|
"record": {
|
||||||
"label": "Record output args",
|
"label": "Record output arguments",
|
||||||
"description": "Default output args for record role streams."
|
"description": "Default output arguments for record role streams."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"retry_interval": {
|
"retry_interval": {
|
||||||
@ -1345,15 +1345,15 @@
|
|||||||
"description": "Roles for this input stream (for example: detect, record, audio)."
|
"description": "Roles for this input stream (for example: detect, record, audio)."
|
||||||
},
|
},
|
||||||
"global_args": {
|
"global_args": {
|
||||||
"label": "FFmpeg global args",
|
"label": "FFmpeg global arguments",
|
||||||
"description": "FFmpeg global arguments for this input stream."
|
"description": "FFmpeg global arguments for this input stream."
|
||||||
},
|
},
|
||||||
"hwaccel_args": {
|
"hwaccel_args": {
|
||||||
"label": "Hardware acceleration args",
|
"label": "Hardware acceleration arguments",
|
||||||
"description": "Hardware acceleration arguments for this input stream."
|
"description": "Hardware acceleration arguments for this input stream."
|
||||||
},
|
},
|
||||||
"input_args": {
|
"input_args": {
|
||||||
"label": "Input args",
|
"label": "Input arguments",
|
||||||
"description": "Input arguments specific to this stream."
|
"description": "Input arguments specific to this stream."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1762,7 +1762,7 @@
|
|||||||
},
|
},
|
||||||
"timestamp_style": {
|
"timestamp_style": {
|
||||||
"label": "Timestamp style",
|
"label": "Timestamp style",
|
||||||
"description": "Styling options for in-feed timestamps applied to recordings and snapshots.",
|
"description": "Styling options for in-feed timestamps applied to debug view and snapshots.",
|
||||||
"position": {
|
"position": {
|
||||||
"label": "Timestamp position",
|
"label": "Timestamp position",
|
||||||
"description": "Position of the timestamp on the image (tl/tr/bl/br)."
|
"description": "Position of the timestamp on the image (tl/tr/bl/br)."
|
||||||
|
|||||||
@ -1298,6 +1298,15 @@
|
|||||||
"summary": "{{count}} selected",
|
"summary": "{{count}} selected",
|
||||||
"empty": "No zones available"
|
"empty": "No zones available"
|
||||||
},
|
},
|
||||||
|
"inputRoles": {
|
||||||
|
"summary": "{{count}} roles selected",
|
||||||
|
"empty": "No roles available",
|
||||||
|
"options": {
|
||||||
|
"detect": "Detect",
|
||||||
|
"record": "Record",
|
||||||
|
"audio": "Audio"
|
||||||
|
}
|
||||||
|
},
|
||||||
"review": {
|
"review": {
|
||||||
"title": "Review Settings"
|
"title": "Review Settings"
|
||||||
},
|
},
|
||||||
|
|||||||
@ -22,6 +22,11 @@ const birdseye: SectionConfigOverrides = {
|
|||||||
"idle_heartbeat_fps",
|
"idle_heartbeat_fps",
|
||||||
],
|
],
|
||||||
advancedFields: ["width", "height", "quality", "inactivity_threshold"],
|
advancedFields: ["width", "height", "quality", "inactivity_threshold"],
|
||||||
|
uiSchema: {
|
||||||
|
mode: {
|
||||||
|
"ui:size": "xs",
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -77,7 +77,12 @@ const ffmpeg: SectionConfigOverrides = {
|
|||||||
path: {
|
path: {
|
||||||
"ui:options": { size: "full" },
|
"ui:options": { size: "full" },
|
||||||
},
|
},
|
||||||
global_args: arrayAsTextWidget,
|
roles: {
|
||||||
|
"ui:widget": "inputRoles",
|
||||||
|
},
|
||||||
|
global_args: {
|
||||||
|
"ui:widget": "hidden",
|
||||||
|
},
|
||||||
hwaccel_args: ffmpegArgsWidget("hwaccel_args"),
|
hwaccel_args: ffmpegArgsWidget("hwaccel_args"),
|
||||||
input_args: ffmpegArgsWidget("input_args"),
|
input_args: ffmpegArgsWidget("input_args"),
|
||||||
output_args: {
|
output_args: {
|
||||||
|
|||||||
@ -48,23 +48,8 @@ const mqtt: SectionConfigOverrides = {
|
|||||||
],
|
],
|
||||||
liveValidate: true,
|
liveValidate: true,
|
||||||
uiSchema: {
|
uiSchema: {
|
||||||
host: {
|
password: {
|
||||||
"ui:options": { size: "sm" },
|
"ui:options": { size: "xs" },
|
||||||
},
|
|
||||||
topic_prefix: {
|
|
||||||
"ui:options": { size: "md" },
|
|
||||||
},
|
|
||||||
client_id: {
|
|
||||||
"ui:options": { size: "sm" },
|
|
||||||
},
|
|
||||||
tls_ca_certs: {
|
|
||||||
"ui:options": { size: "md" },
|
|
||||||
},
|
|
||||||
tls_client_cert: {
|
|
||||||
"ui:options": { size: "md" },
|
|
||||||
},
|
|
||||||
tls_client_key: {
|
|
||||||
"ui:options": { size: "md" },
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -7,6 +7,14 @@ const timestampStyle: SectionConfigOverrides = {
|
|||||||
fieldOrder: ["position", "format", "color", "thickness"],
|
fieldOrder: ["position", "format", "color", "thickness"],
|
||||||
hiddenFields: ["effect", "enabled_in_config"],
|
hiddenFields: ["effect", "enabled_in_config"],
|
||||||
advancedFields: [],
|
advancedFields: [],
|
||||||
|
uiSchema: {
|
||||||
|
position: {
|
||||||
|
"ui:size": "xs",
|
||||||
|
},
|
||||||
|
format: {
|
||||||
|
"ui:size": "xs",
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -128,7 +128,7 @@ export function AdvancedCollapsible({
|
|||||||
{label}
|
{label}
|
||||||
</Button>
|
</Button>
|
||||||
</CollapsibleTrigger>
|
</CollapsibleTrigger>
|
||||||
<CollapsibleContent className="mt-2 space-y-4 rounded-lg border border-border/60 bg-muted/20 p-4">
|
<CollapsibleContent className="mt-2 space-y-4 rounded-lg border border-border/60 bg-background_alt/70 p-4">
|
||||||
{children}
|
{children}
|
||||||
</CollapsibleContent>
|
</CollapsibleContent>
|
||||||
</Collapsible>
|
</Collapsible>
|
||||||
|
|||||||
@ -23,6 +23,7 @@ import { AudioLabelSwitchesWidget } from "./widgets/AudioLabelSwitchesWidget";
|
|||||||
import { ZoneSwitchesWidget } from "./widgets/ZoneSwitchesWidget";
|
import { ZoneSwitchesWidget } from "./widgets/ZoneSwitchesWidget";
|
||||||
import { ArrayAsTextWidget } from "./widgets/ArrayAsTextWidget";
|
import { ArrayAsTextWidget } from "./widgets/ArrayAsTextWidget";
|
||||||
import { FfmpegArgsWidget } from "./widgets/FfmpegArgsWidget";
|
import { FfmpegArgsWidget } from "./widgets/FfmpegArgsWidget";
|
||||||
|
import { InputRolesWidget } from "./widgets/InputRolesWidget";
|
||||||
import { TimezoneSelectWidget } from "./widgets/TimezoneSelectWidget";
|
import { TimezoneSelectWidget } from "./widgets/TimezoneSelectWidget";
|
||||||
|
|
||||||
import { FieldTemplate } from "./templates/FieldTemplate";
|
import { FieldTemplate } from "./templates/FieldTemplate";
|
||||||
@ -55,6 +56,7 @@ export const frigateTheme: FrigateTheme = {
|
|||||||
CheckboxWidget: SwitchWidget,
|
CheckboxWidget: SwitchWidget,
|
||||||
ArrayAsTextWidget: ArrayAsTextWidget,
|
ArrayAsTextWidget: ArrayAsTextWidget,
|
||||||
FfmpegArgsWidget: FfmpegArgsWidget,
|
FfmpegArgsWidget: FfmpegArgsWidget,
|
||||||
|
inputRoles: InputRolesWidget,
|
||||||
// Custom widgets
|
// Custom widgets
|
||||||
switch: SwitchWidget,
|
switch: SwitchWidget,
|
||||||
password: PasswordWidget,
|
password: PasswordWidget,
|
||||||
|
|||||||
@ -92,7 +92,8 @@ export function AudioLabelSwitchesWidget(props: WidgetProps) {
|
|||||||
getEntities,
|
getEntities,
|
||||||
getDisplayLabel: getAudioLabelDisplayName,
|
getDisplayLabel: getAudioLabelDisplayName,
|
||||||
i18nKey: "audioLabels",
|
i18nKey: "audioLabels",
|
||||||
listClassName: "max-h-64 overflow-y-auto scrollbar-container",
|
listClassName:
|
||||||
|
"max-h-none overflow-visible md:max-h-64 md:overflow-y-auto md:overscroll-contain md:scrollbar-container",
|
||||||
enableSearch: true,
|
enableSearch: true,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import useSWR from "swr";
|
|||||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { ConfigFormContext } from "@/types/configForm";
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
SelectContent,
|
SelectContent,
|
||||||
@ -86,7 +87,17 @@ const normalizeManualText = (value: unknown): string => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function FfmpegArgsWidget(props: WidgetProps) {
|
export function FfmpegArgsWidget(props: WidgetProps) {
|
||||||
const { t } = useTranslation(["views/settings"]);
|
const formContext = props.registry?.formContext as
|
||||||
|
| ConfigFormContext
|
||||||
|
| undefined;
|
||||||
|
const i18nNamespace = formContext?.i18nNamespace as string | undefined;
|
||||||
|
const isCameraLevel = formContext?.level === "camera";
|
||||||
|
const effectiveNamespace = isCameraLevel ? "config/cameras" : i18nNamespace;
|
||||||
|
const { t, i18n } = useTranslation([
|
||||||
|
effectiveNamespace || i18nNamespace || "common",
|
||||||
|
i18nNamespace || "common",
|
||||||
|
"views/settings",
|
||||||
|
]);
|
||||||
const {
|
const {
|
||||||
value,
|
value,
|
||||||
onChange,
|
onChange,
|
||||||
@ -165,6 +176,47 @@ export function FfmpegArgsWidget(props: WidgetProps) {
|
|||||||
const manualValue = normalizeManualText(value);
|
const manualValue = normalizeManualText(value);
|
||||||
const presetValue =
|
const presetValue =
|
||||||
typeof value === "string" && presetOptions.includes(value) ? value : "";
|
typeof value === "string" && presetOptions.includes(value) ? value : "";
|
||||||
|
const fallbackDescriptionKey = useMemo(() => {
|
||||||
|
if (!presetField) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isInputScoped = id.includes("_inputs_");
|
||||||
|
const prefix = isInputScoped ? "ffmpeg.inputs" : "ffmpeg";
|
||||||
|
|
||||||
|
if (presetField === "hwaccel_args") {
|
||||||
|
return `${prefix}.hwaccel_args.description`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (presetField === "input_args") {
|
||||||
|
return `${prefix}.input_args.description`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (presetField === "output_args.record") {
|
||||||
|
return isInputScoped
|
||||||
|
? "ffmpeg.inputs.output_args.record.description"
|
||||||
|
: "ffmpeg.output_args.record.description";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (presetField === "output_args.detect") {
|
||||||
|
return isInputScoped
|
||||||
|
? "ffmpeg.inputs.output_args.detect.description"
|
||||||
|
: "ffmpeg.output_args.detect.description";
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}, [id, presetField]);
|
||||||
|
|
||||||
|
const translatedDescription =
|
||||||
|
fallbackDescriptionKey &&
|
||||||
|
effectiveNamespace &&
|
||||||
|
i18n.exists(fallbackDescriptionKey, { ns: effectiveNamespace })
|
||||||
|
? t(fallbackDescriptionKey, { ns: effectiveNamespace })
|
||||||
|
: "";
|
||||||
|
const fieldDescription =
|
||||||
|
typeof schema.description === "string" && schema.description.length > 0
|
||||||
|
? schema.description
|
||||||
|
: translatedDescription;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
@ -244,6 +296,10 @@ export function FfmpegArgsWidget(props: WidgetProps) {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{fieldDescription ? (
|
||||||
|
<p className="text-xs text-muted-foreground">{fieldDescription}</p>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,65 @@
|
|||||||
|
import type { WidgetProps } from "@rjsf/utils";
|
||||||
|
import { useMemo } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { Switch } from "@/components/ui/switch";
|
||||||
|
|
||||||
|
const INPUT_ROLES = ["detect", "record", "audio"] as const;
|
||||||
|
|
||||||
|
function normalizeValue(value: unknown): string[] {
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
return value.filter((item): item is string => typeof item === "string");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof value === "string" && value.trim()) {
|
||||||
|
return [value.trim()];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function InputRolesWidget(props: WidgetProps) {
|
||||||
|
const { id, value, disabled, readonly, onChange } = props;
|
||||||
|
const { t } = useTranslation(["views/settings"]);
|
||||||
|
|
||||||
|
const selectedRoles = useMemo(() => normalizeValue(value), [value]);
|
||||||
|
|
||||||
|
const toggleRole = (role: string, enabled: boolean) => {
|
||||||
|
if (enabled) {
|
||||||
|
if (!selectedRoles.includes(role)) {
|
||||||
|
onChange([...selectedRoles, role]);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
onChange(selectedRoles.filter((item) => item !== role));
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-2 rounded-lg bg-secondary p-2 pr-0 md:max-w-md">
|
||||||
|
{INPUT_ROLES.map((role) => {
|
||||||
|
const checked = selectedRoles.includes(role);
|
||||||
|
const label = t(`configForm.inputRoles.options.${role}`, {
|
||||||
|
ns: "views/settings",
|
||||||
|
defaultValue: role,
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={role}
|
||||||
|
className="flex items-center justify-between rounded-md px-3 py-0"
|
||||||
|
>
|
||||||
|
<label htmlFor={`${id}-${role}`} className="text-sm">
|
||||||
|
{label}
|
||||||
|
</label>
|
||||||
|
<Switch
|
||||||
|
id={`${id}-${role}`}
|
||||||
|
checked={checked}
|
||||||
|
disabled={disabled || readonly}
|
||||||
|
onCheckedChange={(enabled) => toggleRole(role, !!enabled)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -93,7 +93,8 @@ export function ObjectLabelSwitchesWidget(props: WidgetProps) {
|
|||||||
getEntities: getObjectLabels,
|
getEntities: getObjectLabels,
|
||||||
getDisplayLabel: getObjectLabelDisplayName,
|
getDisplayLabel: getObjectLabelDisplayName,
|
||||||
i18nKey: "objectLabels",
|
i18nKey: "objectLabels",
|
||||||
listClassName: "max-h-64 overflow-y-auto scrollbar-container",
|
listClassName:
|
||||||
|
"max-h-none overflow-visible md:max-h-64 md:overflow-y-auto md:overscroll-contain md:scrollbar-container",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -222,7 +222,7 @@ export default function UiSettingsView() {
|
|||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex size-full flex-col md:flex-row">
|
<div className="flex size-full flex-col md:flex-row md:pb-8">
|
||||||
<Toaster position="top-center" closeButton={true} />
|
<Toaster position="top-center" closeButton={true} />
|
||||||
<div className="scrollbar-container order-last mb-2 mt-2 flex h-full w-full flex-col overflow-y-auto pb-2 md:order-none">
|
<div className="scrollbar-container order-last mb-2 mt-2 flex h-full w-full flex-col overflow-y-auto pb-2 md:order-none">
|
||||||
<div className="w-full max-w-5xl space-y-6">
|
<div className="w-full max-w-5xl space-y-6">
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user