diff options
| author | AKILA INDUNIL <45641122+akilaid@users.noreply.github.com> | 2025-04-19 18:32:22 +0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-04-19 18:32:22 +0300 |
| commit | 96fd7d0e7cb7520661a9a0b672fbdcc8fa6f7872 (patch) | |
| tree | b49adfd7d294e4e13f7699474b49861564f62ca4 /web/html/modals | |
| parent | cf02f02210b6501b8533a3cbbf96ed2077f71db5 (diff) | |
feat: add a toggle to use public IPv4 in QR/URI
Diffstat (limited to 'web/html/modals')
| -rw-r--r-- | web/html/modals/qrcode_modal.html | 159 |
1 files changed, 148 insertions, 11 deletions
diff --git a/web/html/modals/qrcode_modal.html b/web/html/modals/qrcode_modal.html index 92d30e93..e5b27841 100644 --- a/web/html/modals/qrcode_modal.html +++ b/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}}
\ No newline at end of file |
