feat(frontend): split wireguard inbound export into config and links tabs

The per-inbound export modal only showed the joined .conf blocks for wireguard, with no way to grab the wireguard:// share links the QR modal already generates. TextModal gains an optional tabs prop (copy and download follow the active tab), and the wireguard export now offers a Config tab with the .conf blocks alongside a Links tab with the per-client wireguard:// URLs. Tab labels reuse the existing pages.clients.config / pages.clients.tabLinks locale keys. Other protocols keep the single untabbed view.
This commit is contained in:
MHSanaei
2026-07-12 15:39:00 +02:00
parent c4a1139d3f
commit 44f2f426d8
2 changed files with 51 additions and 14 deletions
+30 -6
View File
@@ -1,10 +1,17 @@
import { Button, Input, Modal, message } from 'antd';
import { useEffect, useState } from 'react';
import { Button, Input, Modal, Tabs, 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';
export interface TextModalTab {
key: string;
label: string;
content: string;
}
interface TextModalProps {
open: boolean;
onClose: () => void;
@@ -12,13 +19,23 @@ interface TextModalProps {
content: string;
fileName?: string;
json?: boolean;
tabs?: TextModalTab[];
}
export default function TextModal({ open, onClose, title, content, fileName = '', json = false }: TextModalProps) {
export default function TextModal({ open, onClose, title, content, fileName = '', json = false, tabs }: TextModalProps) {
const { t } = useTranslation();
const [messageApi, messageContextHolder] = message.useMessage();
const [activeKey, setActiveKey] = useState('');
useEffect(() => {
if (open && tabs && tabs.length > 0) setActiveKey(tabs[0].key);
}, [open, tabs]);
const activeTab = tabs?.find((tab) => tab.key === activeKey) ?? tabs?.[0];
const activeContent = activeTab ? activeTab.content : content;
async function copy() {
const ok = await ClipboardManager.copyText(content || '');
const ok = await ClipboardManager.copyText(activeContent || '');
if (ok) {
messageApi.success(t('copied'));
onClose();
@@ -27,7 +44,7 @@ export default function TextModal({ open, onClose, title, content, fileName = ''
function download() {
if (!fileName) return;
FileManager.downloadTextFile(content, fileName);
FileManager.downloadTextFile(activeContent, fileName);
}
return (
@@ -47,11 +64,18 @@ export default function TextModal({ open, onClose, title, content, fileName = ''
</>
)}
>
{tabs && tabs.length > 0 && (
<Tabs
activeKey={activeTab?.key}
onChange={setActiveKey}
items={tabs.map((tab) => ({ key: tab.key, label: tab.label }))}
/>
)}
{json ? (
<JsonEditor value={content} readOnly minHeight="240px" maxHeight="60vh" />
<JsonEditor value={activeContent} readOnly minHeight="240px" maxHeight="60vh" />
) : (
<Input.TextArea
value={content}
value={activeContent}
readOnly
autoSize={{ minRows: 10, maxRows: 20 }}
style={{