mirror of
https://github.com/Buriburizaem0n/admin-frontend-domain.git
synced 2026-05-06 05:38:51 +00:00
chore: update translations (#101)
* chore: update translations * fix: form values type conversion * feat: terminal fullscreen mode * fix
This commit is contained in:
@@ -82,7 +82,9 @@ const alertRuleFormSchema = z.object({
|
||||
),
|
||||
rules: z.array(ruleSchema),
|
||||
fail_trigger_tasks: z.array(z.number()),
|
||||
fail_trigger_tasks_raw: z.string(),
|
||||
recover_trigger_tasks: z.array(z.number()),
|
||||
recover_trigger_tasks_raw: z.string(),
|
||||
notification_group_id: z.coerce.number().int(),
|
||||
trigger_mode: z.coerce.number().int().min(0),
|
||||
enable: asOptionalField(z.boolean()),
|
||||
@@ -96,13 +98,17 @@ export const AlertRuleCard: React.FC<AlertRuleCardProps> = ({ data, mutate }) =>
|
||||
? {
|
||||
...data,
|
||||
rules_raw: JSON.stringify(data.rules),
|
||||
fail_trigger_tasks_raw: conv.arrToStr(data.fail_trigger_tasks),
|
||||
recover_trigger_tasks_raw: conv.arrToStr(data.recover_trigger_tasks),
|
||||
}
|
||||
: {
|
||||
name: "",
|
||||
rules_raw: "",
|
||||
rules: [],
|
||||
fail_trigger_tasks: [],
|
||||
fail_trigger_tasks_raw: "",
|
||||
recover_trigger_tasks: [],
|
||||
recover_trigger_tasks_raw: "",
|
||||
notification_group_id: 0,
|
||||
trigger_mode: 0,
|
||||
},
|
||||
@@ -115,6 +121,8 @@ export const AlertRuleCard: React.FC<AlertRuleCardProps> = ({ data, mutate }) =>
|
||||
|
||||
const onSubmit = async (values: z.infer<typeof alertRuleFormSchema>) => {
|
||||
values.rules = JSON.parse(values.rules_raw)
|
||||
values.fail_trigger_tasks = conv.strToArr(values.fail_trigger_tasks_raw).map(Number)
|
||||
values.recover_trigger_tasks = conv.strToArr(values.recover_trigger_tasks_raw).map(Number)
|
||||
const { rules_raw, ...requiredFields } = values
|
||||
try {
|
||||
data?.id
|
||||
@@ -227,7 +235,7 @@ export const AlertRuleCard: React.FC<AlertRuleCardProps> = ({ data, mutate }) =>
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="fail_trigger_tasks"
|
||||
name="fail_trigger_tasks_raw"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
@@ -235,17 +243,7 @@ export const AlertRuleCard: React.FC<AlertRuleCardProps> = ({ data, mutate }) =>
|
||||
t("SeparateWithComma")}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="1,2,3"
|
||||
{...field}
|
||||
value={conv.arrToStr(field.value ?? [])}
|
||||
onChange={(e) => {
|
||||
const arr = conv
|
||||
.strToArr(e.target.value)
|
||||
.map(Number)
|
||||
field.onChange(arr)
|
||||
}}
|
||||
/>
|
||||
<Input placeholder="1,2,3" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@@ -253,7 +251,7 @@ export const AlertRuleCard: React.FC<AlertRuleCardProps> = ({ data, mutate }) =>
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="recover_trigger_tasks"
|
||||
name="recover_trigger_tasks_raw"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
@@ -261,17 +259,7 @@ export const AlertRuleCard: React.FC<AlertRuleCardProps> = ({ data, mutate }) =>
|
||||
t("SeparateWithComma")}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="1,2,3"
|
||||
{...field}
|
||||
value={conv.arrToStr(field.value ?? [])}
|
||||
onChange={(e) => {
|
||||
const arr = conv
|
||||
.strToArr(e.target.value)
|
||||
.map(Number)
|
||||
field.onChange(arr)
|
||||
}}
|
||||
/>
|
||||
<Input placeholder="1,2,3" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
|
||||
+9
-11
@@ -57,6 +57,7 @@ const ddnsFormSchema = z.object({
|
||||
name: z.string().min(1),
|
||||
provider: z.string(),
|
||||
domains: z.array(z.string()),
|
||||
domains_raw: z.string(),
|
||||
access_id: asOptionalField(z.string()),
|
||||
access_secret: asOptionalField(z.string()),
|
||||
webhook_url: asOptionalField(z.string().url()),
|
||||
@@ -71,12 +72,16 @@ export const DDNSCard: React.FC<DDNSCardProps> = ({ data, providers, mutate }) =
|
||||
const form = useForm<z.infer<typeof ddnsFormSchema>>({
|
||||
resolver: zodResolver(ddnsFormSchema),
|
||||
defaultValues: data
|
||||
? data
|
||||
? {
|
||||
...data,
|
||||
domains_raw: conv.arrToStr(data.domains),
|
||||
}
|
||||
: {
|
||||
max_retries: 3,
|
||||
name: "",
|
||||
provider: "dummy",
|
||||
domains: [],
|
||||
domains_raw: "",
|
||||
},
|
||||
resetOptions: {
|
||||
keepDefaultValues: false,
|
||||
@@ -87,6 +92,7 @@ export const DDNSCard: React.FC<DDNSCardProps> = ({ data, providers, mutate }) =
|
||||
|
||||
const onSubmit = async (values: z.infer<typeof ddnsFormSchema>) => {
|
||||
try {
|
||||
values.domains = conv.strToArr(values.domains_raw)
|
||||
data?.id ? await updateDDNSProfile(data.id, values) : await createDDNSProfile(values)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
@@ -156,22 +162,14 @@ export const DDNSCard: React.FC<DDNSCardProps> = ({ data, providers, mutate }) =
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="domains"
|
||||
name="domains_raw"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
{t("Domains") + t("SeparateWithComma")}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="www.example.com"
|
||||
{...field}
|
||||
value={conv.arrToStr(field.value ?? [])}
|
||||
onChange={(e) => {
|
||||
const arr = conv.strToArr(e.target.value)
|
||||
field.onChange(arr)
|
||||
}}
|
||||
/>
|
||||
<Input placeholder="www.example.com" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
|
||||
+10
-13
@@ -48,13 +48,17 @@ const serverFormSchema = z.object({
|
||||
hide_for_guest: asOptionalField(z.boolean()),
|
||||
enable_ddns: asOptionalField(z.boolean()),
|
||||
ddns_profiles: asOptionalField(z.array(z.number())),
|
||||
ddns_profiles_raw: asOptionalField(z.string()),
|
||||
})
|
||||
|
||||
export const ServerCard: React.FC<ServerCardProps> = ({ data, mutate }) => {
|
||||
const { t } = useTranslation()
|
||||
const form = useForm<z.infer<typeof serverFormSchema>>({
|
||||
resolver: zodResolver(serverFormSchema),
|
||||
defaultValues: data,
|
||||
defaultValues: {
|
||||
...data,
|
||||
ddns_profiles_raw: data.ddns_profiles ? conv.arrToStr(data.ddns_profiles) : undefined,
|
||||
},
|
||||
resetOptions: {
|
||||
keepDefaultValues: false,
|
||||
},
|
||||
@@ -64,6 +68,9 @@ export const ServerCard: React.FC<ServerCardProps> = ({ data, mutate }) => {
|
||||
|
||||
const onSubmit = async (values: z.infer<typeof serverFormSchema>) => {
|
||||
try {
|
||||
values.ddns_profiles = values.ddns_profiles_raw
|
||||
? conv.strToArr(values.ddns_profiles_raw).map(Number)
|
||||
: undefined
|
||||
await updateServer(data!.id!, values)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
@@ -119,24 +126,14 @@ export const ServerCard: React.FC<ServerCardProps> = ({ data, mutate }) => {
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="ddns_profiles"
|
||||
name="ddns_profiles_raw"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
{t("DDNSProfiles") + t("SeparateWithComma")}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="1,2,3"
|
||||
{...field}
|
||||
value={conv.arrToStr(field.value || [])}
|
||||
onChange={(e) => {
|
||||
const arr = conv
|
||||
.strToArr(e.target.value)
|
||||
.map(Number)
|
||||
field.onChange(arr)
|
||||
}}
|
||||
/>
|
||||
<Input placeholder="1,2,3" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
|
||||
+12
-24
@@ -58,6 +58,7 @@ const serviceFormSchema = z.object({
|
||||
enable_show_in_service: asOptionalField(z.boolean()),
|
||||
enable_trigger_task: asOptionalField(z.boolean()),
|
||||
fail_trigger_tasks: z.array(z.number()),
|
||||
fail_trigger_tasks_raw: z.string(),
|
||||
latency_notify: asOptionalField(z.boolean()),
|
||||
max_latency: z.coerce.number().int().min(0),
|
||||
min_latency: z.coerce.number().int().min(0),
|
||||
@@ -65,6 +66,7 @@ const serviceFormSchema = z.object({
|
||||
notification_group_id: z.coerce.number().int(),
|
||||
notify: asOptionalField(z.boolean()),
|
||||
recover_trigger_tasks: z.array(z.number()),
|
||||
recover_trigger_tasks_raw: z.string(),
|
||||
skip_servers: z.record(z.boolean()),
|
||||
skip_servers_raw: z.array(z.string()),
|
||||
target: z.string(),
|
||||
@@ -78,6 +80,8 @@ export const ServiceCard: React.FC<ServiceCardProps> = ({ data, mutate }) => {
|
||||
defaultValues: data
|
||||
? {
|
||||
...data,
|
||||
fail_trigger_tasks_raw: conv.arrToStr(data.fail_trigger_tasks),
|
||||
recover_trigger_tasks_raw: conv.arrToStr(data.recover_trigger_tasks),
|
||||
skip_servers_raw: conv.recordToStrArr(data.skip_servers ? data.skip_servers : {}),
|
||||
}
|
||||
: {
|
||||
@@ -90,7 +94,9 @@ export const ServiceCard: React.FC<ServiceCardProps> = ({ data, mutate }) => {
|
||||
duration: 30,
|
||||
notification_group_id: 0,
|
||||
fail_trigger_tasks: [],
|
||||
fail_trigger_tasks_raw: "",
|
||||
recover_trigger_tasks: [],
|
||||
recover_trigger_tasks_raw: "",
|
||||
skip_servers: {},
|
||||
skip_servers_raw: [],
|
||||
},
|
||||
@@ -103,6 +109,8 @@ export const ServiceCard: React.FC<ServiceCardProps> = ({ data, mutate }) => {
|
||||
|
||||
const onSubmit = async (values: z.infer<typeof serviceFormSchema>) => {
|
||||
values.skip_servers = conv.arrToRecord(values.skip_servers_raw)
|
||||
values.fail_trigger_tasks = conv.strToArr(values.fail_trigger_tasks_raw).map(Number)
|
||||
values.recover_trigger_tasks = conv.strToArr(values.recover_trigger_tasks_raw).map(Number)
|
||||
const { skip_servers_raw, ...requiredFields } = values
|
||||
try {
|
||||
data?.id
|
||||
@@ -400,7 +408,7 @@ export const ServiceCard: React.FC<ServiceCardProps> = ({ data, mutate }) => {
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="fail_trigger_tasks"
|
||||
name="fail_trigger_tasks_raw"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
@@ -408,17 +416,7 @@ export const ServiceCard: React.FC<ServiceCardProps> = ({ data, mutate }) => {
|
||||
t("SeparateWithComma")}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="1,2,3"
|
||||
{...field}
|
||||
value={conv.arrToStr(field.value ?? [])}
|
||||
onChange={(e) => {
|
||||
const arr = conv
|
||||
.strToArr(e.target.value)
|
||||
.map(Number)
|
||||
field.onChange(arr)
|
||||
}}
|
||||
/>
|
||||
<Input placeholder="1,2,3" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@@ -426,7 +424,7 @@ export const ServiceCard: React.FC<ServiceCardProps> = ({ data, mutate }) => {
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="recover_trigger_tasks"
|
||||
name="recover_trigger_tasks_raw"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
@@ -434,17 +432,7 @@ export const ServiceCard: React.FC<ServiceCardProps> = ({ data, mutate }) => {
|
||||
t("SeparateWithComma")}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="1,2,3"
|
||||
{...field}
|
||||
value={conv.arrToStr(field.value ?? [])}
|
||||
onChange={(e) => {
|
||||
const arr = conv
|
||||
.strToArr(e.target.value)
|
||||
.map(Number)
|
||||
field.onChange(arr)
|
||||
}}
|
||||
/>
|
||||
<Input placeholder="1,2,3" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
|
||||
+101
-86
@@ -13,7 +13,7 @@ import { AttachAddon } from "@xterm/addon-attach"
|
||||
import { FitAddon } from "@xterm/addon-fit"
|
||||
import { Terminal } from "@xterm/xterm"
|
||||
import "@xterm/xterm/css/xterm.css"
|
||||
import { useEffect, useMemo, useRef, useState } from "react"
|
||||
import { forwardRef, useEffect, useImperativeHandle, useRef, useState } from "react"
|
||||
import { useParams } from "react-router-dom"
|
||||
import { toast } from "sonner"
|
||||
|
||||
@@ -26,119 +26,134 @@ interface XtermProps {
|
||||
setClose: React.Dispatch<React.SetStateAction<boolean>>
|
||||
}
|
||||
|
||||
const XtermComponent: React.FC<XtermProps & JSX.IntrinsicElements["div"]> = ({
|
||||
wsUrl,
|
||||
setClose,
|
||||
...props
|
||||
}) => {
|
||||
const terminalIdRef = useRef<HTMLDivElement>(null)
|
||||
const terminalRef = useRef<Terminal | null>(null)
|
||||
const wsRef = useRef<WebSocket | null>(null)
|
||||
const XtermComponent = forwardRef<HTMLDivElement, XtermProps & JSX.IntrinsicElements["div"]>(
|
||||
({ wsUrl, setClose, ...props }, ref) => {
|
||||
const terminalIdRef = useRef<HTMLDivElement>(null)
|
||||
const terminalRef = useRef<Terminal | null>(null)
|
||||
const wsRef = useRef<WebSocket | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
wsRef.current?.close()
|
||||
terminalRef.current?.dispose()
|
||||
}
|
||||
}, [])
|
||||
useImperativeHandle(ref, () => {
|
||||
return {
|
||||
...terminalIdRef.current!,
|
||||
async requestFullscreen() {
|
||||
await terminalIdRef.current?.requestFullscreen()
|
||||
},
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
terminalRef.current = new Terminal({
|
||||
cursorBlink: true,
|
||||
fontSize: 16,
|
||||
})
|
||||
const ws = new WebSocket(wsUrl)
|
||||
wsRef.current = ws
|
||||
ws.binaryType = "arraybuffer"
|
||||
ws.onopen = () => {
|
||||
onResize()
|
||||
}
|
||||
ws.onclose = () => {
|
||||
terminalRef.current?.dispose()
|
||||
setClose(true)
|
||||
}
|
||||
ws.onerror = (e) => {
|
||||
console.error(e)
|
||||
toast("Websocket error", {
|
||||
description: "View console for details.",
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
wsRef.current?.close()
|
||||
terminalRef.current?.dispose()
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
terminalRef.current = new Terminal({
|
||||
cursorBlink: true,
|
||||
fontSize: 16,
|
||||
})
|
||||
}
|
||||
}, [wsUrl])
|
||||
const ws = new WebSocket(wsUrl)
|
||||
wsRef.current = ws
|
||||
ws.binaryType = "arraybuffer"
|
||||
ws.onopen = () => {
|
||||
onResize()
|
||||
}
|
||||
ws.onclose = () => {
|
||||
terminalRef.current?.dispose()
|
||||
setClose(true)
|
||||
}
|
||||
ws.onerror = (e) => {
|
||||
console.error(e)
|
||||
toast("Websocket error", {
|
||||
description: "View console for details.",
|
||||
})
|
||||
}
|
||||
}, [wsUrl])
|
||||
|
||||
const fitAddon = useRef(new FitAddon()).current
|
||||
const sendResize = useRef(false)
|
||||
const fitAddon = useRef(new FitAddon()).current
|
||||
const sendResize = useRef(false)
|
||||
|
||||
const doResize = () => {
|
||||
if (!terminalIdRef.current) return
|
||||
const doResize = () => {
|
||||
if (!terminalIdRef.current) return
|
||||
|
||||
fitAddon.fit()
|
||||
fitAddon.fit()
|
||||
|
||||
const dimensions = fitAddon.proposeDimensions()
|
||||
const dimensions = fitAddon.proposeDimensions()
|
||||
|
||||
if (dimensions) {
|
||||
const prefix = new Int8Array([1])
|
||||
const resizeMessage = new TextEncoder().encode(
|
||||
JSON.stringify({
|
||||
Rows: dimensions.rows,
|
||||
Cols: dimensions.cols,
|
||||
}),
|
||||
)
|
||||
if (dimensions) {
|
||||
const prefix = new Int8Array([1])
|
||||
const resizeMessage = new TextEncoder().encode(
|
||||
JSON.stringify({
|
||||
Rows: dimensions.rows,
|
||||
Cols: dimensions.cols,
|
||||
}),
|
||||
)
|
||||
|
||||
const msg = new Int8Array(prefix.length + resizeMessage.length)
|
||||
msg.set(prefix)
|
||||
msg.set(resizeMessage, prefix.length)
|
||||
const msg = new Int8Array(prefix.length + resizeMessage.length)
|
||||
msg.set(prefix)
|
||||
msg.set(resizeMessage, prefix.length)
|
||||
|
||||
wsRef.current?.send(msg)
|
||||
}
|
||||
}
|
||||
|
||||
const onResize = async () => {
|
||||
if (sendResize.current) return
|
||||
|
||||
sendResize.current = true
|
||||
try {
|
||||
await sleep(1500)
|
||||
doResize()
|
||||
} catch (error) {
|
||||
console.error("resize error", error)
|
||||
} finally {
|
||||
sendResize.current = false
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!wsRef.current || !terminalIdRef.current || !terminalRef.current) return
|
||||
const attachAddon = new AttachAddon(wsRef.current)
|
||||
terminalRef.current.loadAddon(attachAddon)
|
||||
terminalRef.current.loadAddon(fitAddon)
|
||||
terminalRef.current.open(terminalIdRef.current)
|
||||
window.addEventListener("resize", onResize)
|
||||
return () => {
|
||||
window.removeEventListener("resize", onResize)
|
||||
if (wsRef.current) {
|
||||
wsRef.current.close()
|
||||
wsRef.current?.send(msg)
|
||||
}
|
||||
}
|
||||
}, [wsRef.current, terminalRef.current, terminalIdRef.current])
|
||||
|
||||
return <div ref={terminalIdRef} {...props} />
|
||||
}
|
||||
const onResize = async () => {
|
||||
if (sendResize.current) return
|
||||
|
||||
sendResize.current = true
|
||||
try {
|
||||
await sleep(1500)
|
||||
doResize()
|
||||
} catch (error) {
|
||||
console.error("resize error", error)
|
||||
} finally {
|
||||
sendResize.current = false
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!wsRef.current || !terminalIdRef.current || !terminalRef.current) return
|
||||
const attachAddon = new AttachAddon(wsRef.current)
|
||||
terminalRef.current.loadAddon(attachAddon)
|
||||
terminalRef.current.loadAddon(fitAddon)
|
||||
terminalRef.current.open(terminalIdRef.current)
|
||||
window.addEventListener("resize", onResize)
|
||||
return () => {
|
||||
window.removeEventListener("resize", onResize)
|
||||
if (wsRef.current) {
|
||||
wsRef.current.close()
|
||||
}
|
||||
}
|
||||
}, [wsRef.current, terminalRef.current, terminalIdRef.current])
|
||||
|
||||
return <div ref={terminalIdRef} {...props} />
|
||||
},
|
||||
)
|
||||
|
||||
export const TerminalPage = () => {
|
||||
const { id } = useParams<{ id: string }>()
|
||||
const [open, setOpen] = useState(false)
|
||||
const terminal = useTerminal(id ? parseInt(id) : undefined)
|
||||
const terminalIdRef = useRef<HTMLDivElement>(null)
|
||||
return (
|
||||
<div className="px-8">
|
||||
<div className="flex mt-6 mb-4">
|
||||
<h1 className="flex-1 text-3xl font-bold tracking-tight">{`Terminal (${id})`}</h1>
|
||||
<div className="flex-2 flex ml-auto gap-2">
|
||||
<IconButton
|
||||
icon="expand"
|
||||
onClick={async () => {
|
||||
await terminalIdRef.current?.requestFullscreen()
|
||||
}}
|
||||
/>
|
||||
<FMCard id={id} />
|
||||
</div>
|
||||
</div>
|
||||
{terminal?.session_id ? (
|
||||
<XtermComponent
|
||||
className="max-h-[60%] mb-5"
|
||||
ref={terminalIdRef}
|
||||
className="max-h-[60%] mb-5 overflow-auto"
|
||||
wsUrl={`/api/v1/ws/terminal/${terminal?.session_id}`}
|
||||
setClose={setOpen}
|
||||
/>
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
Clipboard,
|
||||
Download,
|
||||
Edit2,
|
||||
Expand,
|
||||
FolderClosed,
|
||||
Menu,
|
||||
Play,
|
||||
@@ -31,6 +32,7 @@ export interface IconButtonProps extends ButtonProps {
|
||||
| "upload"
|
||||
| "menu"
|
||||
| "ban"
|
||||
| "expand"
|
||||
}
|
||||
|
||||
export const IconButton = forwardRef<HTMLButtonElement, IconButtonProps>((props, ref) => {
|
||||
@@ -82,6 +84,9 @@ export const IconButton = forwardRef<HTMLButtonElement, IconButtonProps>((props,
|
||||
case "ban": {
|
||||
return <BanIcon />
|
||||
}
|
||||
case "expand": {
|
||||
return <Expand />
|
||||
}
|
||||
}
|
||||
})()}
|
||||
</Button>
|
||||
|
||||
Reference in New Issue
Block a user