Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/MHSanaei/3x-ui.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAKILA INDUNIL <45641122+akilaid@users.noreply.github.com>2025-04-19 18:32:22 +0300
committerGitHub <noreply@github.com>2025-04-19 18:32:22 +0300
commit96fd7d0e7cb7520661a9a0b672fbdcc8fa6f7872 (patch)
treeb49adfd7d294e4e13f7699474b49861564f62ca4 /web/html/modals
parentcf02f02210b6501b8533a3cbbf96ed2077f71db5 (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.html159
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