Преглед изворни кода

feat: add a toggle to use public IPv4 in QR/URI

AKILA INDUNIL пре 5 дана
родитељ
комит
96fd7d0e7c

+ 148 - 11
web/html/modals/qrcode_modal.html

@@ -1,10 +1,9 @@
 {{define "modals/qrcodeModal"}}
 <a-modal id="qrcode-modal" v-model="qrModal.visible" :title="qrModal.title"
-    :dialog-style="isMobile ? { top: '18px' } : {}"
-    :closable="true"
-    :class="themeSwitcher.currentTheme"
-    :footer="null" width="fit-content">
+  :dialog-style="isMobile ? { top: '18px' } : {}" :closable="true" :class="themeSwitcher.currentTheme" :footer="null"
+  width="fit-content">
   <tr-qr-modal class="qr-modal">
+
     <template v-if="app.subSettings.enable && qrModal.subId">
       <tr-qr-box class="qr-box">
         <a-tag color="purple" class="qr-tag"><span>{{ i18n "pages.settings.subSettings"}}</span></a-tag>
@@ -25,6 +24,18 @@
     </template>
     <template v-for="(row, index) in qrModal.qrcodes">
       <tr-qr-box class="qr-box">
+        <div :style="{ display: 'flex', alignItems: 'center', marginBottom: '8px' }">
+          <span>{{ i18n "useIPv4ForHost" }}: </span>
+          <button
+            type="button"
+            role="switch"
+            :aria-checked="row.useIPv4"
+            :class="['ant-switch', 'ant-switch-small', { 'ant-switch-checked': row.useIPv4 }]"
+            @click="toggleIPv4(index)"
+            :style="{ marginLeft: '8px' }">
+            <span class="ant-switch-inner"></span>
+          </button>
+        </div>
         <a-tag color="green" class="qr-tag"><span>[[ row.remark ]]</span></a-tag>
         <tr-qr-bg class="qr-bg">
           <canvas @click="copy(row.link)" :id="'qrCode-'+index" class="qr-cv"></canvas>
@@ -34,6 +45,53 @@
   </tr-qr-modal>
 </a-modal>
 
+<style>
+  .ant-table:not(.ant-table-expanded-row .ant-table) {
+    outline: 1px solid #f0f0f0;
+    outline-offset: -1px;
+    border-radius: 1rem;
+    overflow-x: hidden;
+  }
+  
+  /* QR code transition effects */
+  .qr-cv {
+    transition: all 0.3s ease-in-out;
+  }
+  
+  .qr-transition-enter-active, .qr-transition-leave-active {
+    transition: opacity 0.3s, transform 0.3s;
+  }
+  
+  .qr-transition-enter, .qr-transition-leave-to {
+    opacity: 0;
+    transform: scale(0.9);
+  }
+  
+  .qr-transition-enter-to, .qr-transition-leave {
+    opacity: 1;
+    transform: scale(1);
+  }
+  
+  .qr-flash {
+    animation: qr-flash-animation 0.6s;
+  }
+  
+  @keyframes qr-flash-animation {
+    0% {
+      opacity: 1;
+      transform: scale(1);
+    }
+    50% {
+      opacity: 0.5;
+      transform: scale(0.95);
+    }
+    100% {
+      opacity: 1;
+      transform: scale(1);
+    }
+  }
+</style>
+
 <script>
   const qrModal = {
     title: '',
@@ -42,31 +100,37 @@
     qrcodes: [],
     visible: false,
     subId: '',
-    show: function(title = '', dbInbound, client) {
+    show: function (title = '', dbInbound, client) {
       this.title = title;
       this.dbInbound = dbInbound;
       this.inbound = dbInbound.toInbound();
       this.client = client;
       this.subId = '';
       this.qrcodes = [];
+      // Reset the status fetched flag when showing the modal
+      if (qrModalApp) qrModalApp.statusFetched = false;
       if (this.inbound.protocol == Protocols.WIREGUARD) {
         this.inbound.genInboundLinks(dbInbound.remark).split('\r\n').forEach((l, index) => {
           this.qrcodes.push({
             remark: "Peer " + (index + 1),
-            link: l
+            link: l,
+            useIPv4: false,
+            originalLink: l
           });
         });
       } else {
         this.inbound.genAllLinks(this.dbInbound.remark, app.remarkModel, client).forEach(l => {
           this.qrcodes.push({
             remark: l.remark,
-            link: l.link
+            link: l.link,
+            useIPv4: false,
+            originalLink: l.link
           });
         });
       }
       this.visible = true;
     },
-    close: function() {
+    close: function () {
       this.visible = false;
     },
   };
@@ -76,8 +140,72 @@
     mixins: [MediaQueryMixin],
     data: {
       qrModal: qrModal,
+      serverStatus: null,
+      statusFetched: false,
     },
     methods: {
+      async getStatus() {
+        try {
+          const msg = await HttpUtil.post('/server/status');
+          if (msg.success) {
+            this.serverStatus = msg.obj;
+          }
+        } catch (e) {
+          console.error("Failed to get status:", e);
+        }
+      },
+      
+      toggleIPv4(index) {
+        const row = qrModal.qrcodes[index];
+        row.useIPv4 = !row.useIPv4;
+        this.updateLink(index);
+      },
+      updateLink(index) {
+        const row = qrModal.qrcodes[index];
+        if (!this.serverStatus || !this.serverStatus.publicIP) {
+          return;
+        }
+        
+        if (row.useIPv4 && this.serverStatus.publicIP.ipv4) {
+          // Replace the hostname or IP in the link with the IPv4 address
+          const originalLink = row.originalLink;
+          const url = new URL(originalLink);
+          const ipv4 = this.serverStatus.publicIP.ipv4;
+          
+          if (qrModal.inbound.protocol == Protocols.WIREGUARD) {
+            // Special handling for WireGuard config
+            const endpointRegex = /Endpoint = ([^:]+):(\d+)/;
+            const match = originalLink.match(endpointRegex);
+            if (match) {
+              row.link = originalLink.replace(
+                `Endpoint = ${match[1]}:${match[2]}`,
+                `Endpoint = ${ipv4}:${match[2]}`
+              );
+            }
+          } else {
+            // For other protocols using URL format
+            url.hostname = ipv4;
+            row.link = url.toString();
+          }
+        } else {
+          // Restore original link
+          row.link = row.originalLink;
+        }
+        
+        // Update QR code with transition effect
+        const canvasElement = document.querySelector('#qrCode-' + index);
+        if (canvasElement) {
+          // Add flash animation class
+          canvasElement.classList.add('qr-flash');
+          
+          // Remove the class after animation completes
+          setTimeout(() => {
+            canvasElement.classList.remove('qr-flash');
+          }, 600);
+        }
+        
+        this.setQrCode("qrCode-" + index, row.link);
+      },
       copy(content) {
         ClipboardManager
           .copyText(content)
@@ -117,8 +245,14 @@
     updated() {
       if (this.qrModal.visible) {
         fixOverflow();
+        if (!this.statusFetched) {
+          this.getStatus();
+          this.statusFetched = true;
+        }
       } else {
         this.revertOverflow();
+        // Reset the flag when modal is closed so it will fetch again next time
+        this.statusFetched = false;
       }
       if (qrModal.client && qrModal.client.subId) {
         qrModal.subId = qrModal.client.subId;
@@ -127,6 +261,10 @@
       }
       qrModal.qrcodes.forEach((element, index) => {
         this.setQrCode("qrCode-" + index, element.link);
+        // Update links based on current toggle state
+        if (element.useIPv4 && this.serverStatus && this.serverStatus.publicIP) {
+          this.updateLink(index);
+        }
       });
     }
   });
@@ -142,8 +280,7 @@
 
       function wrapContentsInMarquee(element) {
         element.classList.add("tr-marquee");
-        element.children[0].style.animation = `move-ltr ${
-            (element.children[0].clientWidth / element.clientWidth) * 5
+        element.children[0].style.animation = `move-ltr ${(element.children[0].clientWidth / element.clientWidth) * 5
           }s ease-in-out infinite`;
         const marqueeText = element.children[0];
         if (element.children.length < 2) {
@@ -159,4 +296,4 @@
     });
   }
 </script>
-{{end}}
+{{end}}

+ 1 - 0
web/translation/translate.ar_EG.toml

@@ -29,6 +29,7 @@
 "copySuccess" = "اتنسخ بنجاح"
 "sure" = "متأكد؟"
 "encryption" = "تشفير"
+"useIPv4ForHost" = "استخدم IPv4 للمضيف"
 "transmission" = "نقل"
 "host" = "المستضيف"
 "path" = "مسار"

+ 1 - 0
web/translation/translate.en_US.toml

@@ -29,6 +29,7 @@
 "copySuccess" = "Copied Successful"
 "sure" = "Sure"
 "encryption" = "Encryption"
+"useIPv4ForHost" = "Use IPv4 for host"
 "transmission" = "Transmission"
 "host" = "Host"
 "path" = "Path"

+ 1 - 0
web/translation/translate.es_ES.toml

@@ -29,6 +29,7 @@
 "copySuccess" = "Copiado exitosamente"
 "sure" = "Seguro"
 "encryption" = "Encriptación"
+"useIPv4ForHost" = "Usar IPv4 para el host"
 "transmission" = "Transmisión"
 "host" = "Anfitrión"
 "path" = "Ruta"

+ 1 - 0
web/translation/translate.fa_IR.toml

@@ -29,6 +29,7 @@
 "copySuccess" = "باموفقیت کپی‌شد"
 "sure" = "مطمئن"
 "encryption" = "رمزگذاری"
+"useIPv4ForHost" = "از IPv4 برای میزبان استفاده کنید"
 "transmission" = "راه‌اتصال"
 "host" = "آدرس"
 "path" = "مسیر"

+ 1 - 0
web/translation/translate.id_ID.toml

@@ -29,6 +29,7 @@
 "copySuccess" = "Berhasil Disalin"
 "sure" = "Yakin"
 "encryption" = "Enkripsi"
+"useIPv4ForHost" = "Gunakan IPv4 untuk host"
 "transmission" = "Transmisi"
 "host" = "Host"
 "path" = "Jalur"

+ 1 - 0
web/translation/translate.ja_JP.toml

@@ -29,6 +29,7 @@
 "copySuccess" = "コピー成功"
 "sure" = "確定"
 "encryption" = "暗号化"
+"useIPv4ForHost" = "ホストにIPv4を使用"
 "transmission" = "伝送"
 "host" = "ホスト"
 "path" = "パス"

+ 1 - 0
web/translation/translate.pt_BR.toml

@@ -29,6 +29,7 @@
 "copySuccess" = "Copiado com Sucesso"
 "sure" = "Certo"
 "encryption" = "Criptografia"
+"useIPv4ForHost" = "Usar IPv4 para o host"
 "transmission" = "Transmissão"
 "host" = "Servidor"
 "path" = "Caminho"

+ 1 - 0
web/translation/translate.ru_RU.toml

@@ -29,6 +29,7 @@
 "copySuccess" = "Скопировано"
 "sure" = "Да"
 "encryption" = "Шифрование"
+"useIPv4ForHost" = "Использовать IPv4 для хоста"
 "transmission" = "Протокол"
 "host" = "Хост"
 "path" = "Путь"

+ 1 - 0
web/translation/translate.tr_TR.toml

@@ -29,6 +29,7 @@
 "copySuccess" = "Başarıyla Kopyalandı"
 "sure" = "Emin misiniz"
 "encryption" = "Şifreleme"
+"useIPv4ForHost" = "Ana bilgisayar için IPv4 kullan"
 "transmission" = "İletim"
 "host" = "Sunucu"
 "path" = "Yol"

+ 1 - 0
web/translation/translate.uk_UA.toml

@@ -29,6 +29,7 @@
 "copySuccess" = "Скопійовано успішно"
 "sure" = "Звичайно"
 "encryption" = "Шифрування"
+"useIPv4ForHost" = "Використовувати IPv4 для хоста"
 "transmission" = "Протокол передачи"
 "host" = "Хост"
 "path" = "Шлях"

+ 1 - 0
web/translation/translate.vi_VN.toml

@@ -29,6 +29,7 @@
 "copySuccess" = "Đã sao chép thành công"
 "sure" = "Chắc chắn"
 "encryption" = "Mã hóa"
+"useIPv4ForHost" = "Sử dụng IPv4 cho máy chủ"
 "transmission" = "Truyền tải"
 "host" = "Máy chủ"
 "path" = "Đường dẫn"

+ 1 - 0
web/translation/translate.zh_CN.toml

@@ -29,6 +29,7 @@
 "copySuccess" = "复制成功"
 "sure" = "确定"
 "encryption" = "加密"
+"useIPv4ForHost" = "使用 IPv4 连接主机"
 "transmission" = "传输"
 "host" = "主机"
 "path" = "路径"

+ 1 - 0
web/translation/translate.zh_TW.toml

@@ -29,6 +29,7 @@
 "copySuccess" = "複製成功"
 "sure" = "確定"
 "encryption" = "加密"
+"useIPv4ForHost" = "使用 IPv4 連接主機"
 "transmission" = "傳輸"
 "host" = "主機"
 "path" = "路徑"