From ff11783945ee47a1fc0ce9f599f6b180730dc584 Mon Sep 17 00:00:00 2001 From: UUBulb <35923940+uubulb@users.noreply.github.com> Date: Sun, 19 Jan 2025 21:09:52 +0800 Subject: [PATCH] chore: update translations (#101) * chore: update translations * fix: form values type conversion * feat: terminal fullscreen mode * fix --- src/components/alert-rule.tsx | 36 ++---- src/components/ddns.tsx | 20 ++- src/components/server.tsx | 23 ++-- src/components/service.tsx | 36 ++---- src/components/terminal.tsx | 187 ++++++++++++++++------------- src/components/xui/icon-button.tsx | 5 + src/locales/de/translation.json | 2 +- src/locales/en/translation.json | 3 +- src/locales/es/translation.json | 2 +- src/locales/it/translation.json | 2 +- src/locales/zh-CN/translation.json | 3 +- src/locales/zh-TW/translation.json | 3 +- src/routes/cron.tsx | 14 ++- src/routes/notification-group.tsx | 8 ++ src/routes/server-group.tsx | 8 ++ tailwind.config.js | 4 +- 16 files changed, 188 insertions(+), 168 deletions(-) diff --git a/src/components/alert-rule.tsx b/src/components/alert-rule.tsx index b37b018..e86e53d 100644 --- a/src/components/alert-rule.tsx +++ b/src/components/alert-rule.tsx @@ -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 = ({ 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 = ({ data, mutate }) => const onSubmit = async (values: z.infer) => { 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 = ({ data, mutate }) => /> ( @@ -235,17 +243,7 @@ export const AlertRuleCard: React.FC = ({ data, mutate }) => t("SeparateWithComma")} - { - const arr = conv - .strToArr(e.target.value) - .map(Number) - field.onChange(arr) - }} - /> + @@ -253,7 +251,7 @@ export const AlertRuleCard: React.FC = ({ data, mutate }) => /> ( @@ -261,17 +259,7 @@ export const AlertRuleCard: React.FC = ({ data, mutate }) => t("SeparateWithComma")} - { - const arr = conv - .strToArr(e.target.value) - .map(Number) - field.onChange(arr) - }} - /> + diff --git a/src/components/ddns.tsx b/src/components/ddns.tsx index 4192dfd..3d56180 100644 --- a/src/components/ddns.tsx +++ b/src/components/ddns.tsx @@ -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 = ({ data, providers, mutate }) = const form = useForm>({ 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 = ({ data, providers, mutate }) = const onSubmit = async (values: z.infer) => { 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 = ({ data, providers, mutate }) = /> ( {t("Domains") + t("SeparateWithComma")} - { - const arr = conv.strToArr(e.target.value) - field.onChange(arr) - }} - /> + diff --git a/src/components/server.tsx b/src/components/server.tsx index aaf7ba2..99dee14 100644 --- a/src/components/server.tsx +++ b/src/components/server.tsx @@ -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 = ({ data, mutate }) => { const { t } = useTranslation() const form = useForm>({ 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 = ({ data, mutate }) => { const onSubmit = async (values: z.infer) => { 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 = ({ data, mutate }) => { /> ( {t("DDNSProfiles") + t("SeparateWithComma")} - { - const arr = conv - .strToArr(e.target.value) - .map(Number) - field.onChange(arr) - }} - /> + diff --git a/src/components/service.tsx b/src/components/service.tsx index 70fbd05..e0de617 100644 --- a/src/components/service.tsx +++ b/src/components/service.tsx @@ -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 = ({ 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 = ({ 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 = ({ data, mutate }) => { const onSubmit = async (values: z.infer) => { 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 = ({ data, mutate }) => { /> ( @@ -408,17 +416,7 @@ export const ServiceCard: React.FC = ({ data, mutate }) => { t("SeparateWithComma")} - { - const arr = conv - .strToArr(e.target.value) - .map(Number) - field.onChange(arr) - }} - /> + @@ -426,7 +424,7 @@ export const ServiceCard: React.FC = ({ data, mutate }) => { /> ( @@ -434,17 +432,7 @@ export const ServiceCard: React.FC = ({ data, mutate }) => { t("SeparateWithComma")} - { - const arr = conv - .strToArr(e.target.value) - .map(Number) - field.onChange(arr) - }} - /> + diff --git a/src/components/terminal.tsx b/src/components/terminal.tsx index 81c58cc..b42898c 100644 --- a/src/components/terminal.tsx +++ b/src/components/terminal.tsx @@ -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> } -const XtermComponent: React.FC = ({ - wsUrl, - setClose, - ...props -}) => { - const terminalIdRef = useRef(null) - const terminalRef = useRef(null) - const wsRef = useRef(null) +const XtermComponent = forwardRef( + ({ wsUrl, setClose, ...props }, ref) => { + const terminalIdRef = useRef(null) + const terminalRef = useRef(null) + const wsRef = useRef(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
-} + 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
+ }, +) export const TerminalPage = () => { const { id } = useParams<{ id: string }>() const [open, setOpen] = useState(false) const terminal = useTerminal(id ? parseInt(id) : undefined) + const terminalIdRef = useRef(null) return (

{`Terminal (${id})`}

+ { + await terminalIdRef.current?.requestFullscreen() + }} + />
{terminal?.session_id ? ( diff --git a/src/components/xui/icon-button.tsx b/src/components/xui/icon-button.tsx index fc62aad..1e34387 100644 --- a/src/components/xui/icon-button.tsx +++ b/src/components/xui/icon-button.tsx @@ -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((props, ref) => { @@ -82,6 +84,9 @@ export const IconButton = forwardRef((props, case "ban": { return } + case "expand": { + return + } } })()} diff --git a/src/locales/de/translation.json b/src/locales/de/translation.json index d18964d..3cc8e18 100644 --- a/src/locales/de/translation.json +++ b/src/locales/de/translation.json @@ -138,7 +138,7 @@ "InstallCommands": "Installationsbefehl", "Actions": "Aktionen", "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", "EditNAT": "NAT-Konfiguration bearbeiten", "BindHostname": "Bind Domain Name", diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index 7ff67f4..57207ac 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -64,12 +64,13 @@ "Coverage": "Coverage", "CoverAll": "Cover All", "IgnoreAll": "Ignore All", + "OnAlert": "Alarmed Servers", "SpecificServers": "Specific server", "Type": "Type", "Interval": "Interval", "NotifierGroupID": "Notifier Group ID", "Trigger": "On Trigger", - "TasksToTriggerOnAlert": "The task that triggered the alert", + "TasksToTriggerOnAlert": "Tasks to be triggered on alert", "TasksToTriggerAfterRecovery": "Tasks to be triggered after recovery", "Confirm": "Confirm", "ConfirmDeletion": "Confirm Deletion?", diff --git a/src/locales/es/translation.json b/src/locales/es/translation.json index ad17334..d6d8e33 100644 --- a/src/locales/es/translation.json +++ b/src/locales/es/translation.json @@ -31,7 +31,7 @@ "Weight": "Peso (cuanto mayor sea el número, más alto se mostrará)", "DDNSProfiles": "IDs de perfil de DDNS", "Target": "Objetivo", - "TasksToTriggerOnAlert": "La tarea que activó la alerta", + "TasksToTriggerOnAlert": "Tareas que se activarán en caso de alerta", "Services": "Servicios", "MaximumLatency": "Retraso máximo (ms)", "Server": "Servidor", diff --git a/src/locales/it/translation.json b/src/locales/it/translation.json index b4c17e3..c055dc5 100644 --- a/src/locales/it/translation.json +++ b/src/locales/it/translation.json @@ -68,7 +68,7 @@ "Interval": "Intervallo", "NotifierGroupID": "ID del gruppo di notifiche", "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", "Confirm": "Confermo", "ConfirmDeletion": "Confermi l'eliminazione?", diff --git a/src/locales/zh-CN/translation.json b/src/locales/zh-CN/translation.json index 19f872e..6eea357 100644 --- a/src/locales/zh-CN/translation.json +++ b/src/locales/zh-CN/translation.json @@ -64,12 +64,13 @@ "Coverage": "覆盖范围", "CoverAll": "覆盖全部", "IgnoreAll": "忽略全部", + "OnAlert": "告警服务器", "SpecificServers": "特定服务器", "Type": "类型", "Interval": "间隔", "NotifierGroupID": "通知组ID", "Trigger": "触发", - "TasksToTriggerOnAlert": "触发警报的任务", + "TasksToTriggerOnAlert": "告警时要触发的任务", "TasksToTriggerAfterRecovery": "恢复后要触发的任务", "Confirm": "确认", "ConfirmDeletion": "确认删除?", diff --git a/src/locales/zh-TW/translation.json b/src/locales/zh-TW/translation.json index c2aeb60..5243020 100644 --- a/src/locales/zh-TW/translation.json +++ b/src/locales/zh-TW/translation.json @@ -64,12 +64,13 @@ "Coverage": "覆蓋範圍", "CoverAll": "覆蓋全部", "IgnoreAll": "忽略全部", + "OnAlert": "告警伺服器", "SpecificServers": "特定伺服器", "Type": "類型", "Interval": "間隔", "NotifierGroupID": "通知群組ID", "Trigger": "觸發", - "TasksToTriggerOnAlert": "觸發警報的任務", + "TasksToTriggerOnAlert": "告警時要觸發的任務", "TasksToTriggerAfterRecovery": "恢復後要觸發的任務", "Confirm": "確認", "ConfirmDeletion": "確認刪除?", diff --git a/src/routes/cron.tsx b/src/routes/cron.tsx index d4b5975..f0b7b43 100644 --- a/src/routes/cron.tsx +++ b/src/routes/cron.tsx @@ -111,13 +111,13 @@ export default function CronPage() { {(() => { switch (s.cover) { case 0: { - return Ignore All + return {t("IgnoreAll")} } case 1: { - return Cover All + return {t("CoverAll")} } case 2: { - return On alert + return {t("OnAlert")} } } })()} @@ -129,6 +129,14 @@ export default function CronPage() { header: t("SpecificServers"), accessorKey: "servers", accessorFn: (row) => row.servers, + cell: ({ row }) => { + const s = row.original + return ( +
+ {s.servers.join(",")} +
+ ) + }, }, { header: t("LastExecution"), diff --git a/src/routes/notification-group.tsx b/src/routes/notification-group.tsx index ac02cdd..c95ec61 100644 --- a/src/routes/notification-group.tsx +++ b/src/routes/notification-group.tsx @@ -78,6 +78,14 @@ export default function NotificationGroupPage() { header: t("Notifier") + "(ID)", accessorKey: "notifications", accessorFn: (row) => row.notifications, + cell: ({ row }) => { + const s = row.original + return ( +
+ {s.notifications.join(",")} +
+ ) + }, }, { id: "actions", diff --git a/src/routes/server-group.tsx b/src/routes/server-group.tsx index e507dc8..92ddb29 100644 --- a/src/routes/server-group.tsx +++ b/src/routes/server-group.tsx @@ -78,6 +78,14 @@ export default function ServerGroupPage() { header: t("Server") + "(ID)", accessorKey: "servers", accessorFn: (row) => row.servers, + cell: ({ row }) => { + const s = row.original + return ( +
+ {s.servers.join(",")} +
+ ) + }, }, { id: "actions", diff --git a/tailwind.config.js b/tailwind.config.js index 0cbe2f4..3ac35cd 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -1,3 +1,5 @@ +import animatePlugin from "tailwindcss-animate" + /** @type {import('tailwindcss').Config} */ export default { darkMode: ["class"], @@ -56,5 +58,5 @@ export default { }, }, }, - plugins: [require("tailwindcss-animate")], + plugins: [animatePlugin], }