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 { 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 ? (
<JsonEditor value={value} onChange={setValue} minHeight="240px" maxHeight="60vh" />
) : type === 'textarea' ? (
<Input.TextArea
ref={(el) => { textareaRef.current = (el as unknown as { resizableTextArea?: { textArea: HTMLTextAreaElement } })?.resizableTextArea?.textArea ?? null; }}
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 { 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 = ''
</>
)}
>
<Input.TextArea
value={content}
readOnly
autoSize={{ minRows: 10, maxRows: 20 }}
style={{
fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace',
fontSize: 12,
overflowY: 'auto',
}}
/>
{json ? (
<JsonEditor value={content} readOnly minHeight="240px" maxHeight="60vh" />
) : (
<Input.TextArea
value={content}
readOnly
autoSize={{ minRows: 10, maxRows: 20 }}
style={{
fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace',
fontSize: 12,
overflowY: 'auto',
}}
/>
)}
</Modal>
</>
);
+10 -2
View File
@@ -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> | 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> | 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}
/>
</LazyMount>
<LazyMount when={promptOpen}>
@@ -716,6 +723,7 @@ export default function InboundsPage() {
type={promptType}
initialValue={promptInitial}
loading={promptLoading}
json={promptJson}
onConfirm={onPromptConfirm}
/>
</LazyMount>