1
0

QrCodeModal.tsx 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. import { useEffect, useMemo, useState } from 'react';
  2. import { useTranslation } from 'react-i18next';
  3. import { Collapse, Modal } from 'antd';
  4. import type { CollapseProps } from 'antd';
  5. import { Protocols } from '@/models/inbound';
  6. import QrPanel from './QrPanel';
  7. import type { SubSettings } from './useInbounds';
  8. interface ClientSetting {
  9. email?: string;
  10. subId?: string;
  11. [k: string]: unknown;
  12. }
  13. interface DBInboundLike {
  14. remark?: string;
  15. toInbound: () => InboundLike;
  16. }
  17. interface InboundLike {
  18. protocol: string;
  19. genWireguardConfigs: (remark: string, model: string, host: string) => string;
  20. genWireguardLinks: (remark: string, model: string, host: string) => string;
  21. genAllLinks: (remark: string, model: string, client: ClientSetting | null, host: string) => { remark?: string; link: string }[];
  22. }
  23. interface QrCodeModalProps {
  24. open: boolean;
  25. onClose: () => void;
  26. dbInbound: DBInboundLike | null;
  27. client?: ClientSetting | null;
  28. remarkModel?: string;
  29. nodeAddress?: string;
  30. subSettings?: SubSettings;
  31. }
  32. interface QrItem {
  33. key: string;
  34. header: string;
  35. value: string;
  36. downloadName?: string;
  37. }
  38. export default function QrCodeModal({
  39. open,
  40. onClose,
  41. dbInbound,
  42. client = null,
  43. remarkModel = '-ieo',
  44. nodeAddress = '',
  45. subSettings,
  46. }: QrCodeModalProps) {
  47. const { t } = useTranslation();
  48. const [links, setLinks] = useState<{ remark?: string; link: string }[]>([]);
  49. const [wireguardConfigs, setWireguardConfigs] = useState<string[]>([]);
  50. const [wireguardLinks, setWireguardLinks] = useState<string[]>([]);
  51. const [subLink, setSubLink] = useState('');
  52. const [subJsonLink, setSubJsonLink] = useState('');
  53. const [activeKey, setActiveKey] = useState<string[]>([]);
  54. useEffect(() => {
  55. if (!open || !dbInbound) return;
  56. const inbound = dbInbound.toInbound();
  57. if (inbound.protocol === Protocols.WIREGUARD) {
  58. const peerRemark = client?.email
  59. ? `${dbInbound.remark}-${client.email}`
  60. : dbInbound.remark || '';
  61. setWireguardConfigs(inbound.genWireguardConfigs(peerRemark, '-ieo', nodeAddress).split('\r\n'));
  62. setWireguardLinks(inbound.genWireguardLinks(peerRemark, '-ieo', nodeAddress).split('\r\n'));
  63. setLinks([]);
  64. } else {
  65. setLinks(inbound.genAllLinks(dbInbound.remark || '', remarkModel, client, nodeAddress) as { remark?: string; link: string }[]);
  66. setWireguardConfigs([]);
  67. setWireguardLinks([]);
  68. }
  69. const subId = client?.subId;
  70. let nextSub = '';
  71. let nextSubJson = '';
  72. if (subSettings?.enable && subId) {
  73. nextSub = (subSettings.subURI || '') + subId;
  74. nextSubJson = subSettings.subJsonEnable ? (subSettings.subJsonURI || '') + subId : '';
  75. }
  76. setSubLink(nextSub);
  77. setSubJsonLink(nextSubJson);
  78. }, [open, dbInbound, client, remarkModel, nodeAddress, subSettings]);
  79. const qrItems = useMemo<QrItem[]>(() => {
  80. const items: QrItem[] = [];
  81. if (subLink) {
  82. items.push({ key: 'sub', header: t('subscription.title'), value: subLink });
  83. }
  84. if (subJsonLink) {
  85. items.push({ key: 'sub-json', header: `${t('subscription.title')} (JSON)`, value: subJsonLink });
  86. }
  87. links.forEach((link, idx) => {
  88. items.push({ key: `l${idx}`, header: link.remark || `Link ${idx + 1}`, value: link.link });
  89. });
  90. wireguardConfigs.forEach((cfg, idx) => {
  91. items.push({
  92. key: `wc${idx}`,
  93. header: `Peer ${idx + 1} config`,
  94. value: cfg,
  95. downloadName: `peer-${idx + 1}.conf`,
  96. });
  97. if (wireguardLinks[idx]) {
  98. items.push({ key: `wl${idx}`, header: `Peer ${idx + 1} link`, value: wireguardLinks[idx] });
  99. }
  100. });
  101. return items;
  102. }, [subLink, subJsonLink, links, wireguardConfigs, wireguardLinks, t]);
  103. const collapseItems: CollapseProps['items'] = useMemo(
  104. () => qrItems.map((item) => ({
  105. key: item.key,
  106. label: item.header,
  107. children: (
  108. <QrPanel
  109. value={item.value}
  110. remark={item.header}
  111. downloadName={item.downloadName || ''}
  112. showQr={!item.value.includes('mldsa65') && !item.value.includes('ML-KEM-768')}
  113. />
  114. ),
  115. })),
  116. [qrItems],
  117. );
  118. useEffect(() => {
  119. if (!open) {
  120. setActiveKey([]);
  121. return;
  122. }
  123. setActiveKey(qrItems.length > 0 ? [qrItems[0].key] : []);
  124. }, [open, qrItems]);
  125. return (
  126. <Modal open={open} onCancel={onClose} title={t('qrCode')} footer={null} width={420} destroyOnHidden>
  127. {dbInbound && collapseItems && collapseItems.length > 0 && (
  128. <Collapse
  129. ghost
  130. activeKey={activeKey}
  131. onChange={(keys) => setActiveKey(typeof keys === 'string' ? [keys] : (keys as string[]))}
  132. items={collapseItems}
  133. />
  134. )}
  135. </Modal>
  136. );
  137. }