mirror of
https://github.com/Buriburizaem0n/admin-frontend-domain.git
synced 2026-02-04 12:40:08 +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),
|
rules: z.array(ruleSchema),
|
||||||
fail_trigger_tasks: z.array(z.number()),
|
fail_trigger_tasks: z.array(z.number()),
|
||||||
|
fail_trigger_tasks_raw: z.string(),
|
||||||
recover_trigger_tasks: z.array(z.number()),
|
recover_trigger_tasks: z.array(z.number()),
|
||||||
|
recover_trigger_tasks_raw: z.string(),
|
||||||
notification_group_id: z.coerce.number().int(),
|
notification_group_id: z.coerce.number().int(),
|
||||||
trigger_mode: z.coerce.number().int().min(0),
|
trigger_mode: z.coerce.number().int().min(0),
|
||||||
enable: asOptionalField(z.boolean()),
|
enable: asOptionalField(z.boolean()),
|
||||||
@@ -96,13 +98,17 @@ export const AlertRuleCard: React.FC<AlertRuleCardProps> = ({ data, mutate }) =>
|
|||||||
? {
|
? {
|
||||||
...data,
|
...data,
|
||||||
rules_raw: JSON.stringify(data.rules),
|
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: "",
|
name: "",
|
||||||
rules_raw: "",
|
rules_raw: "",
|
||||||
rules: [],
|
rules: [],
|
||||||
fail_trigger_tasks: [],
|
fail_trigger_tasks: [],
|
||||||
|
fail_trigger_tasks_raw: "",
|
||||||
recover_trigger_tasks: [],
|
recover_trigger_tasks: [],
|
||||||
|
recover_trigger_tasks_raw: "",
|
||||||
notification_group_id: 0,
|
notification_group_id: 0,
|
||||||
trigger_mode: 0,
|
trigger_mode: 0,
|
||||||
},
|
},
|
||||||
@@ -115,6 +121,8 @@ export const AlertRuleCard: React.FC<AlertRuleCardProps> = ({ data, mutate }) =>
|
|||||||
|
|
||||||
const onSubmit = async (values: z.infer<typeof alertRuleFormSchema>) => {
|
const onSubmit = async (values: z.infer<typeof alertRuleFormSchema>) => {
|
||||||
values.rules = JSON.parse(values.rules_raw)
|
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
|
const { rules_raw, ...requiredFields } = values
|
||||||
try {
|
try {
|
||||||
data?.id
|
data?.id
|
||||||
@@ -227,7 +235,7 @@ export const AlertRuleCard: React.FC<AlertRuleCardProps> = ({ data, mutate }) =>
|
|||||||
/>
|
/>
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="fail_trigger_tasks"
|
name="fail_trigger_tasks_raw"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>
|
<FormLabel>
|
||||||
@@ -235,17 +243,7 @@ export const AlertRuleCard: React.FC<AlertRuleCardProps> = ({ data, mutate }) =>
|
|||||||
t("SeparateWithComma")}
|
t("SeparateWithComma")}
|
||||||
</FormLabel>
|
</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<Input placeholder="1,2,3" {...field} />
|
||||||
placeholder="1,2,3"
|
|
||||||
{...field}
|
|
||||||
value={conv.arrToStr(field.value ?? [])}
|
|
||||||
onChange={(e) => {
|
|
||||||
const arr = conv
|
|
||||||
.strToArr(e.target.value)
|
|
||||||
.map(Number)
|
|
||||||
field.onChange(arr)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
@@ -253,7 +251,7 @@ export const AlertRuleCard: React.FC<AlertRuleCardProps> = ({ data, mutate }) =>
|
|||||||
/>
|
/>
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="recover_trigger_tasks"
|
name="recover_trigger_tasks_raw"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>
|
<FormLabel>
|
||||||
@@ -261,17 +259,7 @@ export const AlertRuleCard: React.FC<AlertRuleCardProps> = ({ data, mutate }) =>
|
|||||||
t("SeparateWithComma")}
|
t("SeparateWithComma")}
|
||||||
</FormLabel>
|
</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<Input placeholder="1,2,3" {...field} />
|
||||||
placeholder="1,2,3"
|
|
||||||
{...field}
|
|
||||||
value={conv.arrToStr(field.value ?? [])}
|
|
||||||
onChange={(e) => {
|
|
||||||
const arr = conv
|
|
||||||
.strToArr(e.target.value)
|
|
||||||
.map(Number)
|
|
||||||
field.onChange(arr)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ const ddnsFormSchema = z.object({
|
|||||||
name: z.string().min(1),
|
name: z.string().min(1),
|
||||||
provider: z.string(),
|
provider: z.string(),
|
||||||
domains: z.array(z.string()),
|
domains: z.array(z.string()),
|
||||||
|
domains_raw: z.string(),
|
||||||
access_id: asOptionalField(z.string()),
|
access_id: asOptionalField(z.string()),
|
||||||
access_secret: asOptionalField(z.string()),
|
access_secret: asOptionalField(z.string()),
|
||||||
webhook_url: asOptionalField(z.string().url()),
|
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>>({
|
const form = useForm<z.infer<typeof ddnsFormSchema>>({
|
||||||
resolver: zodResolver(ddnsFormSchema),
|
resolver: zodResolver(ddnsFormSchema),
|
||||||
defaultValues: data
|
defaultValues: data
|
||||||
? data
|
? {
|
||||||
|
...data,
|
||||||
|
domains_raw: conv.arrToStr(data.domains),
|
||||||
|
}
|
||||||
: {
|
: {
|
||||||
max_retries: 3,
|
max_retries: 3,
|
||||||
name: "",
|
name: "",
|
||||||
provider: "dummy",
|
provider: "dummy",
|
||||||
domains: [],
|
domains: [],
|
||||||
|
domains_raw: "",
|
||||||
},
|
},
|
||||||
resetOptions: {
|
resetOptions: {
|
||||||
keepDefaultValues: false,
|
keepDefaultValues: false,
|
||||||
@@ -87,6 +92,7 @@ export const DDNSCard: React.FC<DDNSCardProps> = ({ data, providers, mutate }) =
|
|||||||
|
|
||||||
const onSubmit = async (values: z.infer<typeof ddnsFormSchema>) => {
|
const onSubmit = async (values: z.infer<typeof ddnsFormSchema>) => {
|
||||||
try {
|
try {
|
||||||
|
values.domains = conv.strToArr(values.domains_raw)
|
||||||
data?.id ? await updateDDNSProfile(data.id, values) : await createDDNSProfile(values)
|
data?.id ? await updateDDNSProfile(data.id, values) : await createDDNSProfile(values)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e)
|
console.error(e)
|
||||||
@@ -156,22 +162,14 @@ export const DDNSCard: React.FC<DDNSCardProps> = ({ data, providers, mutate }) =
|
|||||||
/>
|
/>
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="domains"
|
name="domains_raw"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>
|
<FormLabel>
|
||||||
{t("Domains") + t("SeparateWithComma")}
|
{t("Domains") + t("SeparateWithComma")}
|
||||||
</FormLabel>
|
</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<Input placeholder="www.example.com" {...field} />
|
||||||
placeholder="www.example.com"
|
|
||||||
{...field}
|
|
||||||
value={conv.arrToStr(field.value ?? [])}
|
|
||||||
onChange={(e) => {
|
|
||||||
const arr = conv.strToArr(e.target.value)
|
|
||||||
field.onChange(arr)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
|||||||
@@ -48,13 +48,17 @@ const serverFormSchema = z.object({
|
|||||||
hide_for_guest: asOptionalField(z.boolean()),
|
hide_for_guest: asOptionalField(z.boolean()),
|
||||||
enable_ddns: asOptionalField(z.boolean()),
|
enable_ddns: asOptionalField(z.boolean()),
|
||||||
ddns_profiles: asOptionalField(z.array(z.number())),
|
ddns_profiles: asOptionalField(z.array(z.number())),
|
||||||
|
ddns_profiles_raw: asOptionalField(z.string()),
|
||||||
})
|
})
|
||||||
|
|
||||||
export const ServerCard: React.FC<ServerCardProps> = ({ data, mutate }) => {
|
export const ServerCard: React.FC<ServerCardProps> = ({ data, mutate }) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const form = useForm<z.infer<typeof serverFormSchema>>({
|
const form = useForm<z.infer<typeof serverFormSchema>>({
|
||||||
resolver: zodResolver(serverFormSchema),
|
resolver: zodResolver(serverFormSchema),
|
||||||
defaultValues: data,
|
defaultValues: {
|
||||||
|
...data,
|
||||||
|
ddns_profiles_raw: data.ddns_profiles ? conv.arrToStr(data.ddns_profiles) : undefined,
|
||||||
|
},
|
||||||
resetOptions: {
|
resetOptions: {
|
||||||
keepDefaultValues: false,
|
keepDefaultValues: false,
|
||||||
},
|
},
|
||||||
@@ -64,6 +68,9 @@ export const ServerCard: React.FC<ServerCardProps> = ({ data, mutate }) => {
|
|||||||
|
|
||||||
const onSubmit = async (values: z.infer<typeof serverFormSchema>) => {
|
const onSubmit = async (values: z.infer<typeof serverFormSchema>) => {
|
||||||
try {
|
try {
|
||||||
|
values.ddns_profiles = values.ddns_profiles_raw
|
||||||
|
? conv.strToArr(values.ddns_profiles_raw).map(Number)
|
||||||
|
: undefined
|
||||||
await updateServer(data!.id!, values)
|
await updateServer(data!.id!, values)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e)
|
console.error(e)
|
||||||
@@ -119,24 +126,14 @@ export const ServerCard: React.FC<ServerCardProps> = ({ data, mutate }) => {
|
|||||||
/>
|
/>
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="ddns_profiles"
|
name="ddns_profiles_raw"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>
|
<FormLabel>
|
||||||
{t("DDNSProfiles") + t("SeparateWithComma")}
|
{t("DDNSProfiles") + t("SeparateWithComma")}
|
||||||
</FormLabel>
|
</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<Input placeholder="1,2,3" {...field} />
|
||||||
placeholder="1,2,3"
|
|
||||||
{...field}
|
|
||||||
value={conv.arrToStr(field.value || [])}
|
|
||||||
onChange={(e) => {
|
|
||||||
const arr = conv
|
|
||||||
.strToArr(e.target.value)
|
|
||||||
.map(Number)
|
|
||||||
field.onChange(arr)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ const serviceFormSchema = z.object({
|
|||||||
enable_show_in_service: asOptionalField(z.boolean()),
|
enable_show_in_service: asOptionalField(z.boolean()),
|
||||||
enable_trigger_task: asOptionalField(z.boolean()),
|
enable_trigger_task: asOptionalField(z.boolean()),
|
||||||
fail_trigger_tasks: z.array(z.number()),
|
fail_trigger_tasks: z.array(z.number()),
|
||||||
|
fail_trigger_tasks_raw: z.string(),
|
||||||
latency_notify: asOptionalField(z.boolean()),
|
latency_notify: asOptionalField(z.boolean()),
|
||||||
max_latency: z.coerce.number().int().min(0),
|
max_latency: z.coerce.number().int().min(0),
|
||||||
min_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(),
|
notification_group_id: z.coerce.number().int(),
|
||||||
notify: asOptionalField(z.boolean()),
|
notify: asOptionalField(z.boolean()),
|
||||||
recover_trigger_tasks: z.array(z.number()),
|
recover_trigger_tasks: z.array(z.number()),
|
||||||
|
recover_trigger_tasks_raw: z.string(),
|
||||||
skip_servers: z.record(z.boolean()),
|
skip_servers: z.record(z.boolean()),
|
||||||
skip_servers_raw: z.array(z.string()),
|
skip_servers_raw: z.array(z.string()),
|
||||||
target: z.string(),
|
target: z.string(),
|
||||||
@@ -78,6 +80,8 @@ export const ServiceCard: React.FC<ServiceCardProps> = ({ data, mutate }) => {
|
|||||||
defaultValues: data
|
defaultValues: data
|
||||||
? {
|
? {
|
||||||
...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 : {}),
|
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,
|
duration: 30,
|
||||||
notification_group_id: 0,
|
notification_group_id: 0,
|
||||||
fail_trigger_tasks: [],
|
fail_trigger_tasks: [],
|
||||||
|
fail_trigger_tasks_raw: "",
|
||||||
recover_trigger_tasks: [],
|
recover_trigger_tasks: [],
|
||||||
|
recover_trigger_tasks_raw: "",
|
||||||
skip_servers: {},
|
skip_servers: {},
|
||||||
skip_servers_raw: [],
|
skip_servers_raw: [],
|
||||||
},
|
},
|
||||||
@@ -103,6 +109,8 @@ export const ServiceCard: React.FC<ServiceCardProps> = ({ data, mutate }) => {
|
|||||||
|
|
||||||
const onSubmit = async (values: z.infer<typeof serviceFormSchema>) => {
|
const onSubmit = async (values: z.infer<typeof serviceFormSchema>) => {
|
||||||
values.skip_servers = conv.arrToRecord(values.skip_servers_raw)
|
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
|
const { skip_servers_raw, ...requiredFields } = values
|
||||||
try {
|
try {
|
||||||
data?.id
|
data?.id
|
||||||
@@ -400,7 +408,7 @@ export const ServiceCard: React.FC<ServiceCardProps> = ({ data, mutate }) => {
|
|||||||
/>
|
/>
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="fail_trigger_tasks"
|
name="fail_trigger_tasks_raw"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>
|
<FormLabel>
|
||||||
@@ -408,17 +416,7 @@ export const ServiceCard: React.FC<ServiceCardProps> = ({ data, mutate }) => {
|
|||||||
t("SeparateWithComma")}
|
t("SeparateWithComma")}
|
||||||
</FormLabel>
|
</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<Input placeholder="1,2,3" {...field} />
|
||||||
placeholder="1,2,3"
|
|
||||||
{...field}
|
|
||||||
value={conv.arrToStr(field.value ?? [])}
|
|
||||||
onChange={(e) => {
|
|
||||||
const arr = conv
|
|
||||||
.strToArr(e.target.value)
|
|
||||||
.map(Number)
|
|
||||||
field.onChange(arr)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
@@ -426,7 +424,7 @@ export const ServiceCard: React.FC<ServiceCardProps> = ({ data, mutate }) => {
|
|||||||
/>
|
/>
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="recover_trigger_tasks"
|
name="recover_trigger_tasks_raw"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>
|
<FormLabel>
|
||||||
@@ -434,17 +432,7 @@ export const ServiceCard: React.FC<ServiceCardProps> = ({ data, mutate }) => {
|
|||||||
t("SeparateWithComma")}
|
t("SeparateWithComma")}
|
||||||
</FormLabel>
|
</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<Input placeholder="1,2,3" {...field} />
|
||||||
placeholder="1,2,3"
|
|
||||||
{...field}
|
|
||||||
value={conv.arrToStr(field.value ?? [])}
|
|
||||||
onChange={(e) => {
|
|
||||||
const arr = conv
|
|
||||||
.strToArr(e.target.value)
|
|
||||||
.map(Number)
|
|
||||||
field.onChange(arr)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import { AttachAddon } from "@xterm/addon-attach"
|
|||||||
import { FitAddon } from "@xterm/addon-fit"
|
import { FitAddon } from "@xterm/addon-fit"
|
||||||
import { Terminal } from "@xterm/xterm"
|
import { Terminal } from "@xterm/xterm"
|
||||||
import "@xterm/xterm/css/xterm.css"
|
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 { useParams } from "react-router-dom"
|
||||||
import { toast } from "sonner"
|
import { toast } from "sonner"
|
||||||
|
|
||||||
@@ -26,119 +26,134 @@ interface XtermProps {
|
|||||||
setClose: React.Dispatch<React.SetStateAction<boolean>>
|
setClose: React.Dispatch<React.SetStateAction<boolean>>
|
||||||
}
|
}
|
||||||
|
|
||||||
const XtermComponent: React.FC<XtermProps & JSX.IntrinsicElements["div"]> = ({
|
const XtermComponent = forwardRef<HTMLDivElement, XtermProps & JSX.IntrinsicElements["div"]>(
|
||||||
wsUrl,
|
({ wsUrl, setClose, ...props }, ref) => {
|
||||||
setClose,
|
const terminalIdRef = useRef<HTMLDivElement>(null)
|
||||||
...props
|
const terminalRef = useRef<Terminal | null>(null)
|
||||||
}) => {
|
const wsRef = useRef<WebSocket | null>(null)
|
||||||
const terminalIdRef = useRef<HTMLDivElement>(null)
|
|
||||||
const terminalRef = useRef<Terminal | null>(null)
|
|
||||||
const wsRef = useRef<WebSocket | null>(null)
|
|
||||||
|
|
||||||
useEffect(() => {
|
useImperativeHandle(ref, () => {
|
||||||
return () => {
|
return {
|
||||||
wsRef.current?.close()
|
...terminalIdRef.current!,
|
||||||
terminalRef.current?.dispose()
|
async requestFullscreen() {
|
||||||
}
|
await terminalIdRef.current?.requestFullscreen()
|
||||||
}, [])
|
},
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
terminalRef.current = new Terminal({
|
return () => {
|
||||||
cursorBlink: true,
|
wsRef.current?.close()
|
||||||
fontSize: 16,
|
terminalRef.current?.dispose()
|
||||||
})
|
}
|
||||||
const ws = new WebSocket(wsUrl)
|
}, [])
|
||||||
wsRef.current = ws
|
|
||||||
ws.binaryType = "arraybuffer"
|
useEffect(() => {
|
||||||
ws.onopen = () => {
|
terminalRef.current = new Terminal({
|
||||||
onResize()
|
cursorBlink: true,
|
||||||
}
|
fontSize: 16,
|
||||||
ws.onclose = () => {
|
|
||||||
terminalRef.current?.dispose()
|
|
||||||
setClose(true)
|
|
||||||
}
|
|
||||||
ws.onerror = (e) => {
|
|
||||||
console.error(e)
|
|
||||||
toast("Websocket error", {
|
|
||||||
description: "View console for details.",
|
|
||||||
})
|
})
|
||||||
}
|
const ws = new WebSocket(wsUrl)
|
||||||
}, [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 fitAddon = useRef(new FitAddon()).current
|
||||||
const sendResize = useRef(false)
|
const sendResize = useRef(false)
|
||||||
|
|
||||||
const doResize = () => {
|
const doResize = () => {
|
||||||
if (!terminalIdRef.current) return
|
if (!terminalIdRef.current) return
|
||||||
|
|
||||||
fitAddon.fit()
|
fitAddon.fit()
|
||||||
|
|
||||||
const dimensions = fitAddon.proposeDimensions()
|
const dimensions = fitAddon.proposeDimensions()
|
||||||
|
|
||||||
if (dimensions) {
|
if (dimensions) {
|
||||||
const prefix = new Int8Array([1])
|
const prefix = new Int8Array([1])
|
||||||
const resizeMessage = new TextEncoder().encode(
|
const resizeMessage = new TextEncoder().encode(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
Rows: dimensions.rows,
|
Rows: dimensions.rows,
|
||||||
Cols: dimensions.cols,
|
Cols: dimensions.cols,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
const msg = new Int8Array(prefix.length + resizeMessage.length)
|
const msg = new Int8Array(prefix.length + resizeMessage.length)
|
||||||
msg.set(prefix)
|
msg.set(prefix)
|
||||||
msg.set(resizeMessage, prefix.length)
|
msg.set(resizeMessage, prefix.length)
|
||||||
|
|
||||||
wsRef.current?.send(msg)
|
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, 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 = () => {
|
export const TerminalPage = () => {
|
||||||
const { id } = useParams<{ id: string }>()
|
const { id } = useParams<{ id: string }>()
|
||||||
const [open, setOpen] = useState(false)
|
const [open, setOpen] = useState(false)
|
||||||
const terminal = useTerminal(id ? parseInt(id) : undefined)
|
const terminal = useTerminal(id ? parseInt(id) : undefined)
|
||||||
|
const terminalIdRef = useRef<HTMLDivElement>(null)
|
||||||
return (
|
return (
|
||||||
<div className="px-8">
|
<div className="px-8">
|
||||||
<div className="flex mt-6 mb-4">
|
<div className="flex mt-6 mb-4">
|
||||||
<h1 className="flex-1 text-3xl font-bold tracking-tight">{`Terminal (${id})`}</h1>
|
<h1 className="flex-1 text-3xl font-bold tracking-tight">{`Terminal (${id})`}</h1>
|
||||||
<div className="flex-2 flex ml-auto gap-2">
|
<div className="flex-2 flex ml-auto gap-2">
|
||||||
|
<IconButton
|
||||||
|
icon="expand"
|
||||||
|
onClick={async () => {
|
||||||
|
await terminalIdRef.current?.requestFullscreen()
|
||||||
|
}}
|
||||||
|
/>
|
||||||
<FMCard id={id} />
|
<FMCard id={id} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{terminal?.session_id ? (
|
{terminal?.session_id ? (
|
||||||
<XtermComponent
|
<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}`}
|
wsUrl={`/api/v1/ws/terminal/${terminal?.session_id}`}
|
||||||
setClose={setOpen}
|
setClose={setOpen}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
Clipboard,
|
Clipboard,
|
||||||
Download,
|
Download,
|
||||||
Edit2,
|
Edit2,
|
||||||
|
Expand,
|
||||||
FolderClosed,
|
FolderClosed,
|
||||||
Menu,
|
Menu,
|
||||||
Play,
|
Play,
|
||||||
@@ -31,6 +32,7 @@ export interface IconButtonProps extends ButtonProps {
|
|||||||
| "upload"
|
| "upload"
|
||||||
| "menu"
|
| "menu"
|
||||||
| "ban"
|
| "ban"
|
||||||
|
| "expand"
|
||||||
}
|
}
|
||||||
|
|
||||||
export const IconButton = forwardRef<HTMLButtonElement, IconButtonProps>((props, ref) => {
|
export const IconButton = forwardRef<HTMLButtonElement, IconButtonProps>((props, ref) => {
|
||||||
@@ -82,6 +84,9 @@ export const IconButton = forwardRef<HTMLButtonElement, IconButtonProps>((props,
|
|||||||
case "ban": {
|
case "ban": {
|
||||||
return <BanIcon />
|
return <BanIcon />
|
||||||
}
|
}
|
||||||
|
case "expand": {
|
||||||
|
return <Expand />
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})()}
|
})()}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -138,7 +138,7 @@
|
|||||||
"InstallCommands": "Installationsbefehl",
|
"InstallCommands": "Installationsbefehl",
|
||||||
"Actions": "Aktionen",
|
"Actions": "Aktionen",
|
||||||
"Weight": "Gewicht (je größer die Zahl, desto höher wird es angezeigt)",
|
"Weight": "Gewicht (je größer die Zahl, desto höher wird es angezeigt)",
|
||||||
"TasksToTriggerOnAlert": "Die Aufgabe, die den Alarm ausgelöst hat",
|
"TasksToTriggerOnAlert": "Aufgaben, die bei Alarm ausgelöst werden sollen",
|
||||||
"NotifierGroup": "Benachrichtigungsgruppe",
|
"NotifierGroup": "Benachrichtigungsgruppe",
|
||||||
"EditNAT": "NAT-Konfiguration bearbeiten",
|
"EditNAT": "NAT-Konfiguration bearbeiten",
|
||||||
"BindHostname": "Bind Domain Name",
|
"BindHostname": "Bind Domain Name",
|
||||||
|
|||||||
@@ -64,12 +64,13 @@
|
|||||||
"Coverage": "Coverage",
|
"Coverage": "Coverage",
|
||||||
"CoverAll": "Cover All",
|
"CoverAll": "Cover All",
|
||||||
"IgnoreAll": "Ignore All",
|
"IgnoreAll": "Ignore All",
|
||||||
|
"OnAlert": "Alarmed Servers",
|
||||||
"SpecificServers": "Specific server",
|
"SpecificServers": "Specific server",
|
||||||
"Type": "Type",
|
"Type": "Type",
|
||||||
"Interval": "Interval",
|
"Interval": "Interval",
|
||||||
"NotifierGroupID": "Notifier Group ID",
|
"NotifierGroupID": "Notifier Group ID",
|
||||||
"Trigger": "On Trigger",
|
"Trigger": "On Trigger",
|
||||||
"TasksToTriggerOnAlert": "The task that triggered the alert",
|
"TasksToTriggerOnAlert": "Tasks to be triggered on alert",
|
||||||
"TasksToTriggerAfterRecovery": "Tasks to be triggered after recovery",
|
"TasksToTriggerAfterRecovery": "Tasks to be triggered after recovery",
|
||||||
"Confirm": "Confirm",
|
"Confirm": "Confirm",
|
||||||
"ConfirmDeletion": "Confirm Deletion?",
|
"ConfirmDeletion": "Confirm Deletion?",
|
||||||
|
|||||||
@@ -31,7 +31,7 @@
|
|||||||
"Weight": "Peso (cuanto mayor sea el número, más alto se mostrará)",
|
"Weight": "Peso (cuanto mayor sea el número, más alto se mostrará)",
|
||||||
"DDNSProfiles": "IDs de perfil de DDNS",
|
"DDNSProfiles": "IDs de perfil de DDNS",
|
||||||
"Target": "Objetivo",
|
"Target": "Objetivo",
|
||||||
"TasksToTriggerOnAlert": "La tarea que activó la alerta",
|
"TasksToTriggerOnAlert": "Tareas que se activarán en caso de alerta",
|
||||||
"Services": "Servicios",
|
"Services": "Servicios",
|
||||||
"MaximumLatency": "Retraso máximo (ms)",
|
"MaximumLatency": "Retraso máximo (ms)",
|
||||||
"Server": "Servidor",
|
"Server": "Servidor",
|
||||||
|
|||||||
@@ -68,7 +68,7 @@
|
|||||||
"Interval": "Intervallo",
|
"Interval": "Intervallo",
|
||||||
"NotifierGroupID": "ID del gruppo di notifiche",
|
"NotifierGroupID": "ID del gruppo di notifiche",
|
||||||
"Trigger": "Grilletto",
|
"Trigger": "Grilletto",
|
||||||
"TasksToTriggerOnAlert": "L'attività che ha attivato l'avviso",
|
"TasksToTriggerOnAlert": "Attività da attivare in caso di allarme",
|
||||||
"TasksToTriggerAfterRecovery": "Attività da attivare dopo il ripristino",
|
"TasksToTriggerAfterRecovery": "Attività da attivare dopo il ripristino",
|
||||||
"Confirm": "Confermo",
|
"Confirm": "Confermo",
|
||||||
"ConfirmDeletion": "Confermi l'eliminazione?",
|
"ConfirmDeletion": "Confermi l'eliminazione?",
|
||||||
|
|||||||
@@ -64,12 +64,13 @@
|
|||||||
"Coverage": "覆盖范围",
|
"Coverage": "覆盖范围",
|
||||||
"CoverAll": "覆盖全部",
|
"CoverAll": "覆盖全部",
|
||||||
"IgnoreAll": "忽略全部",
|
"IgnoreAll": "忽略全部",
|
||||||
|
"OnAlert": "告警服务器",
|
||||||
"SpecificServers": "特定服务器",
|
"SpecificServers": "特定服务器",
|
||||||
"Type": "类型",
|
"Type": "类型",
|
||||||
"Interval": "间隔",
|
"Interval": "间隔",
|
||||||
"NotifierGroupID": "通知组ID",
|
"NotifierGroupID": "通知组ID",
|
||||||
"Trigger": "触发",
|
"Trigger": "触发",
|
||||||
"TasksToTriggerOnAlert": "触发警报的任务",
|
"TasksToTriggerOnAlert": "告警时要触发的任务",
|
||||||
"TasksToTriggerAfterRecovery": "恢复后要触发的任务",
|
"TasksToTriggerAfterRecovery": "恢复后要触发的任务",
|
||||||
"Confirm": "确认",
|
"Confirm": "确认",
|
||||||
"ConfirmDeletion": "确认删除?",
|
"ConfirmDeletion": "确认删除?",
|
||||||
|
|||||||
@@ -64,12 +64,13 @@
|
|||||||
"Coverage": "覆蓋範圍",
|
"Coverage": "覆蓋範圍",
|
||||||
"CoverAll": "覆蓋全部",
|
"CoverAll": "覆蓋全部",
|
||||||
"IgnoreAll": "忽略全部",
|
"IgnoreAll": "忽略全部",
|
||||||
|
"OnAlert": "告警伺服器",
|
||||||
"SpecificServers": "特定伺服器",
|
"SpecificServers": "特定伺服器",
|
||||||
"Type": "類型",
|
"Type": "類型",
|
||||||
"Interval": "間隔",
|
"Interval": "間隔",
|
||||||
"NotifierGroupID": "通知群組ID",
|
"NotifierGroupID": "通知群組ID",
|
||||||
"Trigger": "觸發",
|
"Trigger": "觸發",
|
||||||
"TasksToTriggerOnAlert": "觸發警報的任務",
|
"TasksToTriggerOnAlert": "告警時要觸發的任務",
|
||||||
"TasksToTriggerAfterRecovery": "恢復後要觸發的任務",
|
"TasksToTriggerAfterRecovery": "恢復後要觸發的任務",
|
||||||
"Confirm": "確認",
|
"Confirm": "確認",
|
||||||
"ConfirmDeletion": "確認刪除?",
|
"ConfirmDeletion": "確認刪除?",
|
||||||
|
|||||||
@@ -111,13 +111,13 @@ export default function CronPage() {
|
|||||||
{(() => {
|
{(() => {
|
||||||
switch (s.cover) {
|
switch (s.cover) {
|
||||||
case 0: {
|
case 0: {
|
||||||
return <span>Ignore All</span>
|
return <span>{t("IgnoreAll")}</span>
|
||||||
}
|
}
|
||||||
case 1: {
|
case 1: {
|
||||||
return <span>Cover All</span>
|
return <span>{t("CoverAll")}</span>
|
||||||
}
|
}
|
||||||
case 2: {
|
case 2: {
|
||||||
return <span>On alert</span>
|
return <span>{t("OnAlert")}</span>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})()}
|
})()}
|
||||||
@@ -129,6 +129,14 @@ export default function CronPage() {
|
|||||||
header: t("SpecificServers"),
|
header: t("SpecificServers"),
|
||||||
accessorKey: "servers",
|
accessorKey: "servers",
|
||||||
accessorFn: (row) => row.servers,
|
accessorFn: (row) => row.servers,
|
||||||
|
cell: ({ row }) => {
|
||||||
|
const s = row.original
|
||||||
|
return (
|
||||||
|
<div className="max-w-16 whitespace-normal break-words">
|
||||||
|
<span>{s.servers.join(",")}</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: t("LastExecution"),
|
header: t("LastExecution"),
|
||||||
|
|||||||
@@ -78,6 +78,14 @@ export default function NotificationGroupPage() {
|
|||||||
header: t("Notifier") + "(ID)",
|
header: t("Notifier") + "(ID)",
|
||||||
accessorKey: "notifications",
|
accessorKey: "notifications",
|
||||||
accessorFn: (row) => row.notifications,
|
accessorFn: (row) => row.notifications,
|
||||||
|
cell: ({ row }) => {
|
||||||
|
const s = row.original
|
||||||
|
return (
|
||||||
|
<div className="max-w-48 whitespace-normal break-words">
|
||||||
|
<span>{s.notifications.join(",")}</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "actions",
|
id: "actions",
|
||||||
|
|||||||
@@ -78,6 +78,14 @@ export default function ServerGroupPage() {
|
|||||||
header: t("Server") + "(ID)",
|
header: t("Server") + "(ID)",
|
||||||
accessorKey: "servers",
|
accessorKey: "servers",
|
||||||
accessorFn: (row) => row.servers,
|
accessorFn: (row) => row.servers,
|
||||||
|
cell: ({ row }) => {
|
||||||
|
const s = row.original
|
||||||
|
return (
|
||||||
|
<div className="max-w-48 whitespace-normal break-words">
|
||||||
|
<span>{s.servers.join(",")}</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "actions",
|
id: "actions",
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import animatePlugin from "tailwindcss-animate"
|
||||||
|
|
||||||
/** @type {import('tailwindcss').Config} */
|
/** @type {import('tailwindcss').Config} */
|
||||||
export default {
|
export default {
|
||||||
darkMode: ["class"],
|
darkMode: ["class"],
|
||||||
@@ -56,5 +58,5 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: [require("tailwindcss-animate")],
|
plugins: [animatePlugin],
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user