feat(ui): use CodeMirror editor for Import Inbound and Inbound JSON

This commit is contained in:
MHSanaei
2026-06-12 12:38:18 +02:00
parent 0766e16684
commit 0cefadd166
3 changed files with 34 additions and 14 deletions
@@ -3,6 +3,8 @@ import { Input, Modal } from 'antd';
import type { InputRef } from 'antd'; import type { InputRef } from 'antd';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import JsonEditor from '@/components/form/JsonEditor';
interface PromptModalProps { interface PromptModalProps {
open: boolean; open: boolean;
onClose: () => void; onClose: () => void;
@@ -11,6 +13,7 @@ interface PromptModalProps {
type?: 'input' | 'textarea'; type?: 'input' | 'textarea';
initialValue?: string; initialValue?: string;
loading?: boolean; loading?: boolean;
json?: boolean;
onConfirm: (value: string) => void; onConfirm: (value: string) => void;
} }
@@ -22,6 +25,7 @@ export default function PromptModal({
type = 'input', type = 'input',
initialValue = '', initialValue = '',
loading = false, loading = false,
json = false,
onConfirm, onConfirm,
}: PromptModalProps) { }: PromptModalProps) {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -63,7 +67,9 @@ export default function PromptModal({
onCancel={onClose} onCancel={onClose}
destroyOnHidden destroyOnHidden
> >
{type === 'textarea' ? ( {json ? (
<JsonEditor value={value} onChange={setValue} minHeight="240px" maxHeight="60vh" />
) : type === 'textarea' ? (
<Input.TextArea <Input.TextArea
ref={(el) => { textareaRef.current = (el as unknown as { resizableTextArea?: { textArea: HTMLTextAreaElement } })?.resizableTextArea?.textArea ?? null; }} ref={(el) => { textareaRef.current = (el as unknown as { resizableTextArea?: { textArea: HTMLTextAreaElement } })?.resizableTextArea?.textArea ?? null; }}
value={value} value={value}
+17 -11
View File
@@ -2,6 +2,7 @@ import { Button, Input, Modal, message } from 'antd';
import { CopyOutlined, DownloadOutlined } from '@ant-design/icons'; import { CopyOutlined, DownloadOutlined } from '@ant-design/icons';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import JsonEditor from '@/components/form/JsonEditor';
import { ClipboardManager, FileManager } from '@/utils'; import { ClipboardManager, FileManager } from '@/utils';
interface TextModalProps { interface TextModalProps {
@@ -10,9 +11,10 @@ interface TextModalProps {
title: string; title: string;
content: string; content: string;
fileName?: 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 { t } = useTranslation();
const [messageApi, messageContextHolder] = message.useMessage(); const [messageApi, messageContextHolder] = message.useMessage();
async function copy() { async function copy() {
@@ -45,16 +47,20 @@ export default function TextModal({ open, onClose, title, content, fileName = ''
</> </>
)} )}
> >
<Input.TextArea {json ? (
value={content} <JsonEditor value={content} readOnly minHeight="240px" maxHeight="60vh" />
readOnly ) : (
autoSize={{ minRows: 10, maxRows: 20 }} <Input.TextArea
style={{ value={content}
fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace', readOnly
fontSize: 12, autoSize={{ minRows: 10, maxRows: 20 }}
overflowY: 'auto', style={{
}} fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace',
/> fontSize: 12,
overflowY: 'auto',
}}
/>
)}
</Modal> </Modal>
</> </>
); );
+10 -2
View File
@@ -146,12 +146,14 @@ export default function InboundsPage() {
const [textTitle, setTextTitle] = useState(''); const [textTitle, setTextTitle] = useState('');
const [textContent, setTextContent] = useState(''); const [textContent, setTextContent] = useState('');
const [textFileName, setTextFileName] = useState(''); const [textFileName, setTextFileName] = useState('');
const [textJson, setTextJson] = useState(false);
const [promptOpen, setPromptOpen] = useState(false); const [promptOpen, setPromptOpen] = useState(false);
const [promptTitle, setPromptTitle] = useState(''); const [promptTitle, setPromptTitle] = useState('');
const [promptOkText, setPromptOkText] = useState('OK'); const [promptOkText, setPromptOkText] = useState('OK');
const [promptType, setPromptType] = useState<'textarea' | 'input'>('textarea'); const [promptType, setPromptType] = useState<'textarea' | 'input'>('textarea');
const [promptInitial, setPromptInitial] = useState(''); const [promptInitial, setPromptInitial] = useState('');
const [promptJson, setPromptJson] = useState(false);
const [promptLoading, setPromptLoading] = useState(false); const [promptLoading, setPromptLoading] = useState(false);
const [promptHandler, setPromptHandler] = useState<((value: string) => Promise<boolean | void> | boolean | void) | null>(null); const [promptHandler, setPromptHandler] = useState<((value: string) => Promise<boolean | void> | boolean | void) | null>(null);
@@ -163,10 +165,11 @@ export default function InboundsPage() {
const infoNodeAddress = useMemo(() => hostOverrideFor(infoDbInbound), [infoDbInbound, hostOverrideFor]); const infoNodeAddress = useMemo(() => hostOverrideFor(infoDbInbound), [infoDbInbound, hostOverrideFor]);
const qrNodeAddress = useMemo(() => hostOverrideFor(qrDbInbound), [qrDbInbound, 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); setTextTitle(opts.title);
setTextContent(opts.content); setTextContent(opts.content);
setTextFileName(opts.fileName || ''); setTextFileName(opts.fileName || '');
setTextJson(opts.json || false);
setTextOpen(true); setTextOpen(true);
}, []); }, []);
@@ -175,12 +178,14 @@ export default function InboundsPage() {
okText?: string; okText?: string;
type?: 'textarea' | 'input'; type?: 'textarea' | 'input';
value?: string; value?: string;
json?: boolean;
confirm: (value: string) => Promise<boolean | void> | boolean | void; confirm: (value: string) => Promise<boolean | void> | boolean | void;
}) => { }) => {
setPromptTitle(opts.title); setPromptTitle(opts.title);
setPromptOkText(opts.okText || t('confirm')); setPromptOkText(opts.okText || t('confirm'));
setPromptType(opts.type || 'textarea'); setPromptType(opts.type || 'textarea');
setPromptInitial(opts.value || ''); setPromptInitial(opts.value || '');
setPromptJson(opts.json || false);
setPromptHandler(() => opts.confirm); setPromptHandler(() => opts.confirm);
setPromptOpen(true); setPromptOpen(true);
}, [t]); }, [t]);
@@ -267,7 +272,7 @@ export default function InboundsPage() {
}, [checkFallback, remarkModel, hostOverrideFor, subSettings.publicHost, openText, t]); }, [checkFallback, remarkModel, hostOverrideFor, subSettings.publicHost, openText, t]);
const exportInboundClipboard = useCallback((dbInbound: DBInbound) => { 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]); }, [openText, t]);
const exportInboundSubs = useCallback((dbInbound: DBInbound) => { const exportInboundSubs = useCallback((dbInbound: DBInbound) => {
@@ -327,6 +332,7 @@ export default function InboundsPage() {
okText: t('pages.inbounds.import'), okText: t('pages.inbounds.import'),
type: 'textarea', type: 'textarea',
value: '', value: '',
json: true,
confirm: async (value) => { confirm: async (value) => {
const msg = await HttpUtil.post('/panel/api/inbounds/import', { data: value }); const msg = await HttpUtil.post('/panel/api/inbounds/import', { data: value });
if (msg?.success) { if (msg?.success) {
@@ -705,6 +711,7 @@ export default function InboundsPage() {
title={textTitle} title={textTitle}
content={textContent} content={textContent}
fileName={textFileName} fileName={textFileName}
json={textJson}
/> />
</LazyMount> </LazyMount>
<LazyMount when={promptOpen}> <LazyMount when={promptOpen}>
@@ -716,6 +723,7 @@ export default function InboundsPage() {
type={promptType} type={promptType}
initialValue={promptInitial} initialValue={promptInitial}
loading={promptLoading} loading={promptLoading}
json={promptJson}
onConfirm={onPromptConfirm} onConfirm={onPromptConfirm}
/> />
</LazyMount> </LazyMount>