| 
									
										
										
										
											2025-03-24 09:57:37 +00:00
										 |  |  | {{define "modals/qrcodeModal"}} | 
					
						
							| 
									
										
										
										
											2025-04-19 15:36:17 +00:00
										 |  |  | <a-modal id="qrcode-modal" v-model="qrModal.visible" :closable="true" :class="themeSwitcher.currentTheme" | 
					
						
							|  |  |  |   width="fit-content" :dialog-style="isMobile ? { top: '18px' } : {}" :footer="null"> | 
					
						
							| 
									
										
										
										
											2025-04-19 15:43:24 +00:00
										 |  |  |   <template #title> | 
					
						
							|  |  |  |     <a-space direction="horizontal"> | 
					
						
							|  |  |  |       <span>[[ qrModal.title ]]</span> | 
					
						
							|  |  |  |       <a-popover :overlay-class-name="themeSwitcher.currentTheme" trigger="click" placement="bottom"> | 
					
						
							|  |  |  |         <template slot="content"> | 
					
						
							|  |  |  |           <a-space direction="vertical"> | 
					
						
							|  |  |  |             <template v-for="(row, index) in qrModal.qrcodes"> | 
					
						
							|  |  |  |               <b>[[ row.remark ]]</b> | 
					
						
							|  |  |  |               <a-space direction="horizontal"> | 
					
						
							|  |  |  |                 <a-switch size="small" :checked="row.useIPv4" @click="toggleIPv4(index)"></a-switch> | 
					
						
							|  |  |  |                 <span>{{ i18n "useIPv4ForHost" }}</span> | 
					
						
							|  |  |  |               </a-space> | 
					
						
							|  |  |  |             </template> | 
					
						
							|  |  |  |           </a-space> | 
					
						
							|  |  |  |         </template> | 
					
						
							|  |  |  |         <a-icon type="setting"></a-icon> | 
					
						
							|  |  |  |       </a-popover> | 
					
						
							|  |  |  |     </a-space> | 
					
						
							|  |  |  |   </template> | 
					
						
							| 
									
										
										
										
											2024-04-20 18:45:36 +00:00
										 |  |  |   <tr-qr-modal class="qr-modal"> | 
					
						
							| 
									
										
										
										
											2025-08-04 17:09:01 +00:00
										 |  |  |     <template v-if="app.subSettings?.enable && qrModal.subId"> | 
					
						
							| 
									
										
										
										
											2024-04-20 18:45:36 +00:00
										 |  |  |       <tr-qr-box class="qr-box"> | 
					
						
							|  |  |  |         <a-tag color="purple" class="qr-tag"><span>{{ i18n "pages.settings.subSettings"}}</span></a-tag> | 
					
						
							|  |  |  |         <tr-qr-bg class="qr-bg-sub"> | 
					
						
							|  |  |  |           <tr-qr-bg-inner class="qr-bg-sub-inner"> | 
					
						
							| 
									
										
										
										
											2025-03-08 15:41:27 +00:00
										 |  |  |             <canvas @click="copy(genSubLink(qrModal.client.subId))" id="qrCode-sub" class="qr-cv"></canvas> | 
					
						
							| 
									
										
										
										
											2024-04-20 18:45:36 +00:00
										 |  |  |           </tr-qr-bg-inner> | 
					
						
							|  |  |  |         </tr-qr-bg> | 
					
						
							|  |  |  |       </tr-qr-box> | 
					
						
							|  |  |  |       <tr-qr-box class="qr-box"> | 
					
						
							|  |  |  |         <a-tag color="purple" class="qr-tag"><span>{{ i18n "pages.settings.subSettings"}} Json</span></a-tag> | 
					
						
							|  |  |  |         <tr-qr-bg class="qr-bg-sub"> | 
					
						
							|  |  |  |           <tr-qr-bg-inner class="qr-bg-sub-inner"> | 
					
						
							| 
									
										
										
										
											2025-03-08 15:41:27 +00:00
										 |  |  |             <canvas @click="copy(genSubJsonLink(qrModal.client.subId))" id="qrCode-subJson" class="qr-cv"></canvas> | 
					
						
							| 
									
										
										
										
											2024-04-20 18:45:36 +00:00
										 |  |  |           </tr-qr-bg-inner> | 
					
						
							|  |  |  |         </tr-qr-bg> | 
					
						
							|  |  |  |       </tr-qr-box> | 
					
						
							|  |  |  |     </template> | 
					
						
							| 
									
										
										
										
											2025-02-04 10:27:58 +00:00
										 |  |  |     <template v-for="(row, index) in qrModal.qrcodes"> | 
					
						
							|  |  |  |       <tr-qr-box class="qr-box"> | 
					
						
							|  |  |  |         <a-tag color="green" class="qr-tag"><span>[[ row.remark ]]</span></a-tag> | 
					
						
							|  |  |  |         <tr-qr-bg class="qr-bg"> | 
					
						
							| 
									
										
										
										
											2025-03-08 15:41:27 +00:00
										 |  |  |           <canvas @click="copy(row.link)" :id="'qrCode-'+index" class="qr-cv"></canvas> | 
					
						
							| 
									
										
										
										
											2025-02-04 10:27:58 +00:00
										 |  |  |         </tr-qr-bg> | 
					
						
							|  |  |  |       </tr-qr-box> | 
					
						
							| 
									
										
										
										
											2024-04-20 18:45:36 +00:00
										 |  |  |     </template> | 
					
						
							|  |  |  |   </tr-qr-modal> | 
					
						
							| 
									
										
										
										
											2023-02-09 19:18:06 +00:00
										 |  |  | </a-modal> | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-19 15:32:22 +00:00
										 |  |  | <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> | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-09 19:18:06 +00:00
										 |  |  | <script> | 
					
						
							| 
									
										
										
										
											2024-04-20 18:45:36 +00:00
										 |  |  |   const qrModal = { | 
					
						
							|  |  |  |     title: '', | 
					
						
							|  |  |  |     dbInbound: new DBInbound(), | 
					
						
							|  |  |  |     client: null, | 
					
						
							|  |  |  |     qrcodes: [], | 
					
						
							|  |  |  |     visible: false, | 
					
						
							|  |  |  |     subId: '', | 
					
						
							| 
									
										
										
										
											2025-04-19 15:32:22 +00:00
										 |  |  |     show: function (title = '', dbInbound, client) { | 
					
						
							| 
									
										
										
										
											2024-04-20 18:45:36 +00:00
										 |  |  |       this.title = title; | 
					
						
							|  |  |  |       this.dbInbound = dbInbound; | 
					
						
							|  |  |  |       this.inbound = dbInbound.toInbound(); | 
					
						
							|  |  |  |       this.client = client; | 
					
						
							|  |  |  |       this.subId = ''; | 
					
						
							|  |  |  |       this.qrcodes = []; | 
					
						
							| 
									
										
										
										
											2025-04-19 15:32:22 +00:00
										 |  |  |       // Reset the status fetched flag when showing the modal | 
					
						
							|  |  |  |       if (qrModalApp) qrModalApp.statusFetched = false; | 
					
						
							| 
									
										
										
										
											2024-04-20 18:45:36 +00:00
										 |  |  |       if (this.inbound.protocol == Protocols.WIREGUARD) { | 
					
						
							|  |  |  |         this.inbound.genInboundLinks(dbInbound.remark).split('\r\n').forEach((l, index) => { | 
					
						
							|  |  |  |           this.qrcodes.push({ | 
					
						
							|  |  |  |             remark: "Peer " + (index + 1), | 
					
						
							| 
									
										
										
										
											2025-04-19 15:32:22 +00:00
										 |  |  |             link: l, | 
					
						
							|  |  |  |             useIPv4: false, | 
					
						
							|  |  |  |             originalLink: l | 
					
						
							| 
									
										
										
										
											2024-04-20 18:45:36 +00:00
										 |  |  |           }); | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |       } else { | 
					
						
							|  |  |  |         this.inbound.genAllLinks(this.dbInbound.remark, app.remarkModel, client).forEach(l => { | 
					
						
							|  |  |  |           this.qrcodes.push({ | 
					
						
							|  |  |  |             remark: l.remark, | 
					
						
							| 
									
										
										
										
											2025-04-19 15:32:22 +00:00
										 |  |  |             link: l.link, | 
					
						
							|  |  |  |             useIPv4: false, | 
					
						
							|  |  |  |             originalLink: l.link | 
					
						
							| 
									
										
										
										
											2024-04-20 18:45:36 +00:00
										 |  |  |           }); | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       this.visible = true; | 
					
						
							|  |  |  |     }, | 
					
						
							| 
									
										
										
										
											2025-04-19 15:32:22 +00:00
										 |  |  |     close: function () { | 
					
						
							| 
									
										
										
										
											2024-04-20 18:45:36 +00:00
										 |  |  |       this.visible = false; | 
					
						
							|  |  |  |     }, | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  |   const qrModalApp = new Vue({ | 
					
						
							|  |  |  |     delimiters: ['[[', ']]'], | 
					
						
							|  |  |  |     el: '#qrcode-modal', | 
					
						
							| 
									
										
										
										
											2025-04-06 09:40:33 +00:00
										 |  |  |     mixins: [MediaQueryMixin], | 
					
						
							| 
									
										
										
										
											2024-04-20 18:45:36 +00:00
										 |  |  |     data: { | 
					
						
							| 
									
										
										
										
											2025-02-04 10:27:58 +00:00
										 |  |  |       qrModal: qrModal, | 
					
						
							| 
									
										
										
										
											2025-04-19 15:32:22 +00:00
										 |  |  |       serverStatus: null, | 
					
						
							|  |  |  |       statusFetched: false, | 
					
						
							| 
									
										
										
										
											2024-04-20 18:45:36 +00:00
										 |  |  |     }, | 
					
						
							|  |  |  |     methods: { | 
					
						
							| 
									
										
										
										
											2025-04-19 15:32:22 +00:00
										 |  |  |       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); | 
					
						
							|  |  |  |       }, | 
					
						
							| 
									
										
										
										
											2025-03-07 07:27:33 +00:00
										 |  |  |       copy(content) { | 
					
						
							|  |  |  |         ClipboardManager | 
					
						
							|  |  |  |           .copyText(content) | 
					
						
							|  |  |  |           .then(() => { | 
					
						
							|  |  |  |             app.$message.success('{{ i18n "copied" }}') | 
					
						
							|  |  |  |           }) | 
					
						
							| 
									
										
										
										
											2024-04-20 18:45:36 +00:00
										 |  |  |       }, | 
					
						
							| 
									
										
										
										
											2024-07-07 09:55:59 +00:00
										 |  |  |       setQrCode(elementId, content) { | 
					
						
							| 
									
										
										
										
											2024-04-20 18:45:36 +00:00
										 |  |  |         new QRious({ | 
					
						
							| 
									
										
										
										
											2024-07-07 09:55:59 +00:00
										 |  |  |           element: document.querySelector('#' + elementId), | 
					
						
							| 
									
										
										
										
											2024-04-20 18:45:36 +00:00
										 |  |  |           size: 400, | 
					
						
							|  |  |  |           value: content, | 
					
						
							|  |  |  |           background: 'white', | 
					
						
							|  |  |  |           backgroundAlpha: 0, | 
					
						
							|  |  |  |           foreground: 'black', | 
					
						
							|  |  |  |           padding: 2, | 
					
						
							|  |  |  |           level: 'L' | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  |       genSubLink(subID) { | 
					
						
							|  |  |  |         return app.subSettings.subURI + subID; | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  |       genSubJsonLink(subID) { | 
					
						
							|  |  |  |         return app.subSettings.subJsonURI + subID; | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  |       revertOverflow() { | 
					
						
							|  |  |  |         const elements = document.querySelectorAll(".qr-tag"); | 
					
						
							|  |  |  |         elements.forEach((element) => { | 
					
						
							|  |  |  |           element.classList.remove("tr-marquee"); | 
					
						
							|  |  |  |           element.children[0].style.animation = ''; | 
					
						
							|  |  |  |           while (element.children.length > 1) { | 
					
						
							|  |  |  |             element.removeChild(element.lastChild); | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     }, | 
					
						
							|  |  |  |     updated() { | 
					
						
							|  |  |  |       if (this.qrModal.visible) { | 
					
						
							|  |  |  |         fixOverflow(); | 
					
						
							| 
									
										
										
										
											2025-04-19 15:32:22 +00:00
										 |  |  |         if (!this.statusFetched) { | 
					
						
							|  |  |  |           this.getStatus(); | 
					
						
							|  |  |  |           this.statusFetched = true; | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2024-04-20 18:45:36 +00:00
										 |  |  |       } else { | 
					
						
							|  |  |  |         this.revertOverflow(); | 
					
						
							| 
									
										
										
										
											2025-04-19 15:32:22 +00:00
										 |  |  |         // Reset the flag when modal is closed so it will fetch again next time | 
					
						
							|  |  |  |         this.statusFetched = false; | 
					
						
							| 
									
										
										
										
											2024-04-20 18:45:36 +00:00
										 |  |  |       } | 
					
						
							|  |  |  |       if (qrModal.client && qrModal.client.subId) { | 
					
						
							|  |  |  |         qrModal.subId = qrModal.client.subId; | 
					
						
							|  |  |  |         this.setQrCode("qrCode-sub", this.genSubLink(qrModal.subId)); | 
					
						
							|  |  |  |         this.setQrCode("qrCode-subJson", this.genSubJsonLink(qrModal.subId)); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       qrModal.qrcodes.forEach((element, index) => { | 
					
						
							|  |  |  |         this.setQrCode("qrCode-" + index, element.link); | 
					
						
							| 
									
										
										
										
											2025-04-19 15:32:22 +00:00
										 |  |  |         // Update links based on current toggle state | 
					
						
							|  |  |  |         if (element.useIPv4 && this.serverStatus && this.serverStatus.publicIP) { | 
					
						
							|  |  |  |           this.updateLink(index); | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2024-04-20 18:45:36 +00:00
										 |  |  |       }); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   }); | 
					
						
							| 
									
										
										
										
											2023-02-09 19:18:06 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-20 18:45:36 +00:00
										 |  |  |   function fixOverflow() { | 
					
						
							|  |  |  |     const elements = document.querySelectorAll(".qr-tag"); | 
					
						
							|  |  |  |     elements.forEach((element) => { | 
					
						
							|  |  |  |       function isElementOverflowing(element) { | 
					
						
							|  |  |  |         const overflowX = element.offsetWidth < element.scrollWidth, | 
					
						
							|  |  |  |           overflowY = element.offsetHeight < element.scrollHeight; | 
					
						
							|  |  |  |         return overflowX || overflowY; | 
					
						
							|  |  |  |       } | 
					
						
							| 
									
										
										
										
											2023-02-09 19:18:06 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-20 18:45:36 +00:00
										 |  |  |       function wrapContentsInMarquee(element) { | 
					
						
							|  |  |  |         element.classList.add("tr-marquee"); | 
					
						
							| 
									
										
										
										
											2025-04-19 15:32:22 +00:00
										 |  |  |         element.children[0].style.animation = `move-ltr ${(element.children[0].clientWidth / element.clientWidth) * 5 | 
					
						
							| 
									
										
										
										
											2024-04-20 18:45:36 +00:00
										 |  |  |           }s ease-in-out infinite`; | 
					
						
							|  |  |  |         const marqueeText = element.children[0]; | 
					
						
							|  |  |  |         if (element.children.length < 2) { | 
					
						
							|  |  |  |           for (let i = 0; i < 1; i++) { | 
					
						
							|  |  |  |             const marqueeText = element.children[0].cloneNode(true); | 
					
						
							|  |  |  |             element.children[0].after(marqueeText); | 
					
						
							|  |  |  |           } | 
					
						
							| 
									
										
										
										
											2023-05-22 14:01:41 +00:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2024-04-20 18:45:36 +00:00
										 |  |  |       } | 
					
						
							|  |  |  |       if (isElementOverflowing(element)) { | 
					
						
							|  |  |  |         wrapContentsInMarquee(element); | 
					
						
							|  |  |  |       } | 
					
						
							| 
									
										
										
										
											2023-02-09 19:18:06 +00:00
										 |  |  |     }); | 
					
						
							| 
									
										
										
										
											2024-04-20 18:45:36 +00:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2023-02-09 19:18:06 +00:00
										 |  |  | </script> | 
					
						
							| 
									
										
										
										
											2025-04-19 15:32:22 +00:00
										 |  |  | {{end}} |