| 
									
										
										
										
											2025-05-08 14:20:58 +00:00
										 |  |  | {{define "modals/twoFactorModal"}} | 
					
						
							|  |  |  | <a-modal id="two-factor-modal" v-model="twoFactorModal.visible" :title="twoFactorModal.title" :closable="true" | 
					
						
							|  |  |  |     :class="themeSwitcher.currentTheme"> | 
					
						
							|  |  |  |     <template v-if="twoFactorModal.type === 'set'"> | 
					
						
							|  |  |  |         <p>{{ i18n "pages.settings.security.twoFactorModalSteps" }}</p> | 
					
						
							|  |  |  |         <a-divider></a-divider> | 
					
						
							|  |  |  |         <p>{{ i18n "pages.settings.security.twoFactorModalFirstStep" }}</p> | 
					
						
							|  |  |  |         <div :style="{ display: 'flex', alignItems: 'center', flexDirection: 'column', gap: '12px' }"> | 
					
						
							| 
									
										
										
										
											2025-05-11 00:12:43 +00:00
										 |  |  |             <div class="qr-bg" :style="{ width: '180px', height: '180px' }"> | 
					
						
							|  |  |  |                 <canvas @click="copy(twoFactorModal.token)" id="twofactor-qrcode" class="qr-cv"></canvas> | 
					
						
							| 
									
										
										
										
											2025-05-08 14:20:58 +00:00
										 |  |  |             </div> | 
					
						
							|  |  |  |             <span :style="{ fontSize: '12px', fontFamily: 'monospace' }">[[ twoFactorModal.token ]]</span> | 
					
						
							|  |  |  |         </div> | 
					
						
							|  |  |  |         <a-divider></a-divider> | 
					
						
							|  |  |  |         <p>{{ i18n "pages.settings.security.twoFactorModalSecondStep" }}</p> | 
					
						
							|  |  |  |         <a-input v-model.trim="twoFactorModal.enteredCode" :style="{ width: '100%' }"></a-input> | 
					
						
							|  |  |  |     </template> | 
					
						
							| 
									
										
										
										
											2025-07-02 09:25:25 +00:00
										 |  |  |     <template v-if="twoFactorModal.type === 'confirm'"> | 
					
						
							|  |  |  |         <p>[[ twoFactorModal.description ]]</p> | 
					
						
							| 
									
										
										
										
											2025-05-08 14:20:58 +00:00
										 |  |  |         <a-input v-model.trim="twoFactorModal.enteredCode" :style="{ width: '100%' }"></a-input> | 
					
						
							|  |  |  |     </template> | 
					
						
							|  |  |  |     <template slot="footer"> | 
					
						
							|  |  |  |         <a-button @click="twoFactorModal.cancel"> | 
					
						
							|  |  |  |             <span>{{ i18n "cancel" }}</span> | 
					
						
							|  |  |  |         </a-button> | 
					
						
							|  |  |  |         <a-button type="primary" :disabled="twoFactorModal.enteredCode.length < 6" @click="twoFactorModal.ok"> | 
					
						
							|  |  |  |             <span>{{ i18n "confirm" }}</span> | 
					
						
							|  |  |  |         </a-button> | 
					
						
							|  |  |  |     </template> | 
					
						
							|  |  |  | </a-modal> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | <script> | 
					
						
							|  |  |  |     const twoFactorModal = { | 
					
						
							|  |  |  |         title: '', | 
					
						
							| 
									
										
										
										
											2025-07-02 09:25:25 +00:00
										 |  |  |         description: '', | 
					
						
							| 
									
										
										
										
											2025-05-08 14:20:58 +00:00
										 |  |  |         fileName: '', | 
					
						
							|  |  |  |         token: '', | 
					
						
							|  |  |  |         enteredCode: '', | 
					
						
							|  |  |  |         visible: false, | 
					
						
							|  |  |  |         type: 'set', | 
					
						
							|  |  |  |         confirm: null, | 
					
						
							|  |  |  |         totpObject: null, | 
					
						
							|  |  |  |         qrImage: "", | 
					
						
							|  |  |  |         ok() { | 
					
						
							|  |  |  |             if (twoFactorModal.totpObject.generate() === twoFactorModal.enteredCode) { | 
					
						
							|  |  |  |                 ObjectUtil.execute(twoFactorModal.confirm, true) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 twoFactorModal.close() | 
					
						
							|  |  |  |             } else { | 
					
						
							|  |  |  |                 Vue.prototype.$message['error']('{{ i18n "pages.settings.security.twoFactorModalError" }}') | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |         cancel() { | 
					
						
							|  |  |  |             ObjectUtil.execute(twoFactorModal.confirm, false) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             twoFactorModal.close() | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |         show: function ({ | 
					
						
							|  |  |  |             title = '', | 
					
						
							| 
									
										
										
										
											2025-07-02 09:25:25 +00:00
										 |  |  |             description = '', | 
					
						
							| 
									
										
										
										
											2025-05-08 14:20:58 +00:00
										 |  |  |             token = '', | 
					
						
							|  |  |  |             type = 'set', | 
					
						
							|  |  |  |             confirm = (success) => { } | 
					
						
							|  |  |  |         }) { | 
					
						
							|  |  |  |             this.title = title; | 
					
						
							| 
									
										
										
										
											2025-07-02 09:25:25 +00:00
										 |  |  |             this.description = description; | 
					
						
							| 
									
										
										
										
											2025-05-08 14:20:58 +00:00
										 |  |  |             this.token = token; | 
					
						
							|  |  |  |             this.visible = true; | 
					
						
							|  |  |  |             this.confirm = confirm; | 
					
						
							|  |  |  |             this.type = type; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             this.totpObject = new OTPAuth.TOTP({ | 
					
						
							|  |  |  |                 issuer: "3x-ui", | 
					
						
							|  |  |  |                 label: "Administrator", | 
					
						
							|  |  |  |                 algorithm: "SHA1", | 
					
						
							|  |  |  |                 digits: 6, | 
					
						
							|  |  |  |                 period: 30, | 
					
						
							|  |  |  |                 secret: twoFactorModal.token, | 
					
						
							|  |  |  |             }); | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |         close: function () { | 
					
						
							|  |  |  |             twoFactorModal.enteredCode = ""; | 
					
						
							|  |  |  |             twoFactorModal.visible = false; | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const twoFactorModalApp = new Vue({ | 
					
						
							|  |  |  |         delimiters: ['[[', ']]'], | 
					
						
							|  |  |  |         el: '#two-factor-modal', | 
					
						
							|  |  |  |         data: { | 
					
						
							|  |  |  |             twoFactorModal: twoFactorModal, | 
					
						
							|  |  |  |         }, | 
					
						
							| 
									
										
										
										
											2025-05-11 00:12:43 +00:00
										 |  |  |         updated() { | 
					
						
							|  |  |  |           if ( | 
					
						
							|  |  |  |             this.twoFactorModal.visible && | 
					
						
							|  |  |  |             this.twoFactorModal.type === 'set' && | 
					
						
							|  |  |  |             document.getElementById('twofactor-qrcode') | 
					
						
							|  |  |  |           ) { | 
					
						
							|  |  |  |             this.setQrCode('twofactor-qrcode', this.twoFactorModal.totpObject.toString()); | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |         methods: { | 
					
						
							|  |  |  |           setQrCode(elementId, content) { | 
					
						
							|  |  |  |             new QRious({ | 
					
						
							|  |  |  |               element: document.getElementById(elementId), | 
					
						
							|  |  |  |               size: 200, | 
					
						
							|  |  |  |               value: content, | 
					
						
							|  |  |  |               background: 'white', | 
					
						
							|  |  |  |               backgroundAlpha: 0, | 
					
						
							|  |  |  |               foreground: 'black', | 
					
						
							|  |  |  |               padding: 2, | 
					
						
							|  |  |  |               level: 'L' | 
					
						
							|  |  |  |             }); | 
					
						
							|  |  |  |           }, | 
					
						
							|  |  |  |           copy(content) { | 
					
						
							|  |  |  |             ClipboardManager | 
					
						
							|  |  |  |               .copyText(content) | 
					
						
							|  |  |  |               .then(() => { | 
					
						
							|  |  |  |                 app.$message.success('{{ i18n "copied" }}') | 
					
						
							|  |  |  |               }) | 
					
						
							|  |  |  |           }, | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2025-05-08 14:20:58 +00:00
										 |  |  |     }); | 
					
						
							|  |  |  | </script> | 
					
						
							|  |  |  | {{end}} |