From 0cefadd16616925f05c7a7e39c831d2c8547f15f Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Fri, 12 Jun 2026 12:38:18 +0200 Subject: [PATCH] feat(ui): use CodeMirror editor for Import Inbound and Inbound JSON --- .../src/components/feedback/PromptModal.tsx | 8 +++++- .../src/components/feedback/TextModal.tsx | 28 +++++++++++-------- frontend/src/pages/inbounds/InboundsPage.tsx | 12 ++++++-- 3 files changed, 34 insertions(+), 14 deletions(-) diff --git a/frontend/src/components/feedback/PromptModal.tsx b/frontend/src/components/feedback/PromptModal.tsx index e6bf9bd73..eda131dd4 100644 --- a/frontend/src/components/feedback/PromptModal.tsx +++ b/frontend/src/components/feedback/PromptModal.tsx @@ -3,6 +3,8 @@ import { Input, Modal } from 'antd'; import type { InputRef } from 'antd'; import { useTranslation } from 'react-i18next'; +import JsonEditor from '@/components/form/JsonEditor'; + interface PromptModalProps { open: boolean; onClose: () => void; @@ -11,6 +13,7 @@ interface PromptModalProps { type?: 'input' | 'textarea'; initialValue?: string; loading?: boolean; + json?: boolean; onConfirm: (value: string) => void; } @@ -22,6 +25,7 @@ export default function PromptModal({ type = 'input', initialValue = '', loading = false, + json = false, onConfirm, }: PromptModalProps) { const { t } = useTranslation(); @@ -63,7 +67,9 @@ export default function PromptModal({ onCancel={onClose} destroyOnHidden > - {type === 'textarea' ? ( + {json ? ( + + ) : type === 'textarea' ? ( { textareaRef.current = (el as unknown as { resizableTextArea?: { textArea: HTMLTextAreaElement } })?.resizableTextArea?.textArea ?? null; }} value={value} diff --git a/frontend/src/components/feedback/TextModal.tsx b/frontend/src/components/feedback/TextModal.tsx index 59653efdb..19586fb82 100644 --- a/frontend/src/components/feedback/TextModal.tsx +++ b/frontend/src/components/feedback/TextModal.tsx @@ -2,6 +2,7 @@ import { Button, Input, Modal, message } from 'antd'; import { CopyOutlined, DownloadOutlined } from '@ant-design/icons'; import { useTranslation } from 'react-i18next'; +import JsonEditor from '@/components/form/JsonEditor'; import { ClipboardManager, FileManager } from '@/utils'; interface TextModalProps { @@ -10,9 +11,10 @@ interface TextModalProps { title: string; content: string; fileName?: string; + json?: boolean; } -export default function TextModal({ open, onClose, title, content, fileName = '' }: TextModalProps) { +export default function TextModal({ open, onClose, title, content, fileName = '', json = false }: TextModalProps) { const { t } = useTranslation(); const [messageApi, messageContextHolder] = message.useMessage(); async function copy() { @@ -45,16 +47,20 @@ export default function TextModal({ open, onClose, title, content, fileName = '' )} > - + {json ? ( + + ) : ( + + )} ); diff --git a/frontend/src/pages/inbounds/InboundsPage.tsx b/frontend/src/pages/inbounds/InboundsPage.tsx index 9c385d173..5accac5c9 100644 --- a/frontend/src/pages/inbounds/InboundsPage.tsx +++ b/frontend/src/pages/inbounds/InboundsPage.tsx @@ -146,12 +146,14 @@ export default function InboundsPage() { const [textTitle, setTextTitle] = useState(''); const [textContent, setTextContent] = useState(''); const [textFileName, setTextFileName] = useState(''); + const [textJson, setTextJson] = useState(false); const [promptOpen, setPromptOpen] = useState(false); const [promptTitle, setPromptTitle] = useState(''); const [promptOkText, setPromptOkText] = useState('OK'); const [promptType, setPromptType] = useState<'textarea' | 'input'>('textarea'); const [promptInitial, setPromptInitial] = useState(''); + const [promptJson, setPromptJson] = useState(false); const [promptLoading, setPromptLoading] = useState(false); const [promptHandler, setPromptHandler] = useState<((value: string) => Promise | boolean | void) | null>(null); @@ -163,10 +165,11 @@ export default function InboundsPage() { const infoNodeAddress = useMemo(() => hostOverrideFor(infoDbInbound), [infoDbInbound, hostOverrideFor]); const qrNodeAddress = useMemo(() => hostOverrideFor(qrDbInbound), [qrDbInbound, hostOverrideFor]); - const openText = useCallback((opts: { title: string; content: string; fileName?: string }) => { + const openText = useCallback((opts: { title: string; content: string; fileName?: string; json?: boolean }) => { setTextTitle(opts.title); setTextContent(opts.content); setTextFileName(opts.fileName || ''); + setTextJson(opts.json || false); setTextOpen(true); }, []); @@ -175,12 +178,14 @@ export default function InboundsPage() { okText?: string; type?: 'textarea' | 'input'; value?: string; + json?: boolean; confirm: (value: string) => Promise | boolean | void; }) => { setPromptTitle(opts.title); setPromptOkText(opts.okText || t('confirm')); setPromptType(opts.type || 'textarea'); setPromptInitial(opts.value || ''); + setPromptJson(opts.json || false); setPromptHandler(() => opts.confirm); setPromptOpen(true); }, [t]); @@ -267,7 +272,7 @@ export default function InboundsPage() { }, [checkFallback, remarkModel, hostOverrideFor, subSettings.publicHost, openText, t]); const exportInboundClipboard = useCallback((dbInbound: DBInbound) => { - openText({ title: t('pages.inbounds.inboundJsonTitle'), content: JSON.stringify(dbInbound, null, 2) }); + openText({ title: t('pages.inbounds.inboundJsonTitle'), content: JSON.stringify(dbInbound, null, 2), json: true }); }, [openText, t]); const exportInboundSubs = useCallback((dbInbound: DBInbound) => { @@ -327,6 +332,7 @@ export default function InboundsPage() { okText: t('pages.inbounds.import'), type: 'textarea', value: '', + json: true, confirm: async (value) => { const msg = await HttpUtil.post('/panel/api/inbounds/import', { data: value }); if (msg?.success) { @@ -705,6 +711,7 @@ export default function InboundsPage() { title={textTitle} content={textContent} fileName={textFileName} + json={textJson} /> @@ -716,6 +723,7 @@ export default function InboundsPage() { type={promptType} initialValue={promptInitial} loading={promptLoading} + json={promptJson} onConfirm={onPromptConfirm} />