mirror of
				https://github.com/MHSanaei/3x-ui.git
				synced 2025-10-30 20:02:51 +00:00 
			
		
		
		
	Merge pull request #381 from hamid-gh98/main
[FIX] bug logout path + [UPDATE] login UI and more ...
This commit is contained in:
		
						commit
						30a5f66f26
					
				
					 37 changed files with 1348 additions and 1208 deletions
				
			
		
							
								
								
									
										2
									
								
								web/assets/ant-design-vue@1.7.2/antd.min.css
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								web/assets/ant-design-vue@1.7.2/antd.min.css
									
									
									
									
										vendored
									
									
								
							|  | @ -992,7 +992,7 @@ to{transform:scale(0) translate(50%,-50%);opacity:0} | |||
| .ant-menu-item>.ant-badge>a{color:rgba(0,0,0,.65)} | ||||
| .ant-menu-item>.ant-badge>a:hover{color:#1890ff} | ||||
| .ant-menu-item-divider{height:1px;overflow:hidden;line-height:0;background-color:#e8e8e8} | ||||
| .ant-menu-item-active,.ant-menu-item:hover,.ant-menu-submenu-active,.ant-menu-submenu-title:hover,.ant-menu:not(.ant-menu-inline) .ant-menu-submenu-open{color:#fff;background-image: linear-gradient(90deg,#99999980 0,#8888889e 100%);border-radius: 0.5rem} | ||||
| .ant-menu-item-active,.ant-menu-item:hover,.ant-menu-submenu-active,.ant-menu-submenu-title:hover,.ant-menu:not(.ant-menu-inline) .ant-menu-submenu-open{color:#2d2d2d;background-image: linear-gradient(90deg,#99999980 0,#8888889e 100%);border-radius: 0.5rem} | ||||
| .ant-menu-horizontal .ant-menu-item,.ant-menu-horizontal .ant-menu-submenu{margin-top:-1px} | ||||
| .ant-menu-horizontal>.ant-menu-item-active,.ant-menu-horizontal>.ant-menu-item:hover,.ant-menu-horizontal>.ant-menu-submenu .ant-menu-submenu-title:hover{background-color:transparent} | ||||
| .ant-menu-item-selected,.ant-menu-item-selected>a,.ant-menu-item-selected>a:hover{color:#1890ff} | ||||
|  |  | |||
|  | @ -1,5 +1,23 @@ | |||
| #app { | ||||
| html, | ||||
| body { | ||||
|     height: 100vh; | ||||
|     width: 100vw; | ||||
|     margin: 0; | ||||
|     padding: 0; | ||||
|     overflow: hidden; | ||||
| } | ||||
| 
 | ||||
| #app { | ||||
|     height: 100%; | ||||
|     min-height: 100vh; | ||||
|     position: fixed; | ||||
|     top: 0; | ||||
|     left: 0; | ||||
|     right: 0; | ||||
|     bottom: 0; | ||||
|     margin: 0; | ||||
|     padding: 0; | ||||
|     overflow: auto; | ||||
| } | ||||
| 
 | ||||
| .ant-space { | ||||
|  | @ -180,12 +198,12 @@ | |||
| 
 | ||||
| .ant-card-dark:hover { | ||||
|     border-color: #e8e8e8; | ||||
|     box-shadow: 0 1px 10px -1px rgb(154 175 238); | ||||
|     box-shadow: 0 1px 10px -1px rgb(76, 88, 126); | ||||
| } | ||||
| 
 | ||||
| .ant-card-bordered:hover { | ||||
|     /*box-shadow: 0 3px 12px -0.8px #0000005c;*/ | ||||
| } | ||||
| /* .ant-card-bordered:hover { | ||||
|     box-shadow: 0 3px 12px -0.8px #0000005c; | ||||
| } */ | ||||
| 
 | ||||
| .ant-card-dark .ant-table-thead th { | ||||
|     color: hsla(0,0%,100%,.65); | ||||
|  | @ -203,6 +221,7 @@ | |||
| .ant-card-dark .ant-input-group-addon { | ||||
|     color: hsla(0,0%,100%,.65); | ||||
|     background-color: #262f3d; | ||||
|     border: 1px solid rgb(0 150 112 / 0%); | ||||
| } | ||||
| 
 | ||||
| .ant-card-dark .ant-list-item-meta-title, | ||||
|  |  | |||
|  | @ -2,7 +2,7 @@ axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded | |||
| axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'; | ||||
| 
 | ||||
| axios.interceptors.request.use( | ||||
|     config => { | ||||
|     (config) => { | ||||
|         if (config.data instanceof FormData) { | ||||
|             config.headers['Content-Type'] = 'multipart/form-data'; | ||||
|         } else { | ||||
|  | @ -12,5 +12,5 @@ axios.interceptors.request.use( | |||
|         } | ||||
|         return config; | ||||
|     }, | ||||
|     error => Promise.reject(error) | ||||
|     (error) => Promise.reject(error), | ||||
| ); | ||||
|  |  | |||
|  | @ -1,41 +1,41 @@ | |||
| const supportLangs = [ | ||||
|     { | ||||
|        name : "English", | ||||
|        value : "en-US", | ||||
|        icon : "🇺🇸" | ||||
|         name: 'English', | ||||
|         value: 'en-US', | ||||
|         icon: '🇺🇸', | ||||
|     }, | ||||
|     { | ||||
|         name : "Farsi", | ||||
|         value : "fa_IR", | ||||
|         icon : "🇮🇷" | ||||
|         name: 'Farsi', | ||||
|         value: 'fa_IR', | ||||
|         icon: '🇮🇷', | ||||
|     }, | ||||
|     { | ||||
|         name : "汉语", | ||||
|         value : "zh-Hans", | ||||
|         icon : "🇨🇳" | ||||
|         name: '汉语', | ||||
|         value: 'zh-Hans', | ||||
|         icon: '🇨🇳', | ||||
|     }, | ||||
|     { | ||||
|         name : "Russian", | ||||
|         value : "ru_RU", | ||||
|         icon : "🇷🇺" | ||||
|         name: 'Russian', | ||||
|         value: 'ru_RU', | ||||
|         icon: '🇷🇺', | ||||
|     }, | ||||
| ] | ||||
| ]; | ||||
| 
 | ||||
| function getLang(){ | ||||
|     let lang = getCookie('lang') | ||||
| function getLang() { | ||||
|     let lang = getCookie('lang'); | ||||
| 
 | ||||
|     if (! lang){ | ||||
|         if (window.navigator){ | ||||
|     if (!lang) { | ||||
|         if (window.navigator) { | ||||
|             lang = window.navigator.language || window.navigator.userLanguage; | ||||
| 
 | ||||
|             if (isSupportLang(lang)){ | ||||
|                 setCookie('lang' , lang , 150) | ||||
|             }else{ | ||||
|                 setCookie('lang' , 'en-US' , 150) | ||||
|             if (isSupportLang(lang)) { | ||||
|                 setCookie('lang', lang, 150); | ||||
|             } else { | ||||
|                 setCookie('lang', 'en-US', 150); | ||||
|                 window.location.reload(); | ||||
|             } | ||||
|         }else{ | ||||
|             setCookie('lang' , 'en-US' , 150) | ||||
|         } else { | ||||
|             setCookie('lang', 'en-US', 150); | ||||
|             window.location.reload(); | ||||
|         } | ||||
|     } | ||||
|  | @ -43,47 +43,21 @@ function getLang(){ | |||
|     return lang; | ||||
| } | ||||
| 
 | ||||
| function setLang(lang){ | ||||
| 
 | ||||
|     if (!isSupportLang(lang)){ | ||||
| function setLang(lang) { | ||||
|     if (!isSupportLang(lang)) { | ||||
|         lang = 'en-US'; | ||||
|     } | ||||
| 
 | ||||
|     setCookie('lang' , lang , 150) | ||||
|     setCookie('lang', lang, 150); | ||||
|     window.location.reload(); | ||||
| } | ||||
| 
 | ||||
| function isSupportLang(lang){ | ||||
|     for (l of supportLangs){ | ||||
|         if (l.value === lang){ | ||||
| function isSupportLang(lang) { | ||||
|     for (l of supportLangs) { | ||||
|         if (l.value === lang) { | ||||
|             return true; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     return false; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| function getCookie(cname) { | ||||
|     let name = cname + "="; | ||||
|     let decodedCookie = decodeURIComponent(document.cookie); | ||||
|     let ca = decodedCookie.split(';'); | ||||
|     for(let i = 0; i <ca.length; i++) { | ||||
|         let c = ca[i]; | ||||
|         while (c.charAt(0) == ' ') { | ||||
|             c = c.substring(1); | ||||
|         } | ||||
|         if (c.indexOf(name) == 0) { | ||||
|             return c.substring(name.length, c.length); | ||||
|         } | ||||
|     } | ||||
|     return ""; | ||||
| } | ||||
| 
 | ||||
| function setCookie(cname, cvalue, exdays) { | ||||
|     const d = new Date(); | ||||
|     d.setTime(d.getTime() + (exdays*24*60*60*1000)); | ||||
|     let expires = "expires="+ d.toUTCString(); | ||||
|     document.cookie =  cname + "=" + cvalue + ";" + expires + ";path=/"; | ||||
| } | ||||
|  | @ -56,14 +56,37 @@ function toFixed(num, n) { | |||
|     return Math.round(num * n) / n; | ||||
| } | ||||
| 
 | ||||
| function debounce (fn, delay) { | ||||
|     var timeoutID = null | ||||
| function debounce(fn, delay) { | ||||
|     var timeoutID = null; | ||||
|     return function () { | ||||
|       clearTimeout(timeoutID) | ||||
|       var args = arguments | ||||
|       var that = this | ||||
|       timeoutID = setTimeout(function () { | ||||
|         fn.apply(that, args) | ||||
|       }, delay) | ||||
|         clearTimeout(timeoutID); | ||||
|         var args = arguments; | ||||
|         var that = this; | ||||
|         timeoutID = setTimeout(function () { | ||||
|             fn.apply(that, args); | ||||
|         }, delay); | ||||
|     }; | ||||
| } | ||||
| 
 | ||||
| function getCookie(cname) { | ||||
|     let name = cname + '='; | ||||
|     let decodedCookie = decodeURIComponent(document.cookie); | ||||
|     let ca = decodedCookie.split(';'); | ||||
|     for (let i = 0; i < ca.length; i++) { | ||||
|         let c = ca[i]; | ||||
|         while (c.charAt(0) == ' ') { | ||||
|             c = c.substring(1); | ||||
|         } | ||||
|         if (c.indexOf(name) == 0) { | ||||
|             return c.substring(name.length, c.length); | ||||
|         } | ||||
|     } | ||||
|   } | ||||
|     return ''; | ||||
| } | ||||
| 
 | ||||
| function setCookie(cname, cvalue, exdays) { | ||||
|     const d = new Date(); | ||||
|     d.setTime(d.getTime() + exdays * 24 * 60 * 60 * 1000); | ||||
|     let expires = 'expires=' + d.toUTCString(); | ||||
|     document.cookie = cname + '=' + cvalue + ';' + expires + ';path=/'; | ||||
| } | ||||
|  |  | |||
|  | @ -128,14 +128,13 @@ Date.prototype.formatDateTime = function (split = ' ') { | |||
| }; | ||||
| 
 | ||||
| class DateUtil { | ||||
| 
 | ||||
|     // 字符串转 Date 对象
 | ||||
|     static parseDate(str) { | ||||
|         return new Date(str.replace(/-/g, '/')); | ||||
|     } | ||||
| 
 | ||||
|     static formatMillis(millis) { | ||||
|         return moment(millis).format('YYYY-M-D H:m:s') | ||||
|         return moment(millis).format('YYYY-M-D H:m:s'); | ||||
|     } | ||||
| 
 | ||||
|     static firstDayOfMonth() { | ||||
|  | @ -144,4 +143,4 @@ class DateUtil { | |||
|         date.setMinTime(); | ||||
|         return date; | ||||
|     } | ||||
| } | ||||
| } | ||||
|  |  | |||
|  | @ -68,13 +68,11 @@ class HttpUtil { | |||
| } | ||||
| 
 | ||||
| class PromiseUtil { | ||||
| 
 | ||||
|     static async sleep(timeout) { | ||||
|         await new Promise(resolve => { | ||||
|             setTimeout(resolve, timeout) | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| const seq = [ | ||||
|  | @ -95,7 +93,6 @@ const shortIdSeq = [ | |||
| ]; | ||||
| 
 | ||||
| class RandomUtil { | ||||
| 
 | ||||
|     static randomIntRange(min, max) { | ||||
|         return parseInt(Math.random() * (max - min) + min, 10); | ||||
|     } | ||||
|  | @ -153,8 +150,8 @@ class RandomUtil { | |||
|     static randomText() { | ||||
|         var chars = 'abcdefghijklmnopqrstuvwxyz1234567890'; | ||||
|         var string = ''; | ||||
|         var len = 6 + Math.floor(Math.random() * 5) | ||||
|         for(var ii=0; ii<len; ii++){ | ||||
|         var len = 6 + Math.floor(Math.random() * 5); | ||||
|         for (var ii = 0; ii < len; ii++) { | ||||
|             string += chars[Math.floor(Math.random() * chars.length)]; | ||||
|         } | ||||
|         return string; | ||||
|  | @ -162,11 +159,11 @@ class RandomUtil { | |||
| 
 | ||||
|     static randowShortId() { | ||||
|         let str = ''; | ||||
|         str += this.randomShortIdSeq(8) | ||||
|         str += this.randomShortIdSeq(8); | ||||
|         return str; | ||||
|     } | ||||
|      | ||||
|     static randomShadowsocksPassword(){ | ||||
| 
 | ||||
|     static randomShadowsocksPassword() { | ||||
|         let array = new Uint8Array(32); | ||||
|         window.crypto.getRandomValues(array); | ||||
|         return btoa(String.fromCharCode.apply(null, array)); | ||||
|  | @ -174,7 +171,6 @@ class RandomUtil { | |||
| } | ||||
| 
 | ||||
| class ObjectUtil { | ||||
| 
 | ||||
|     static getPropIgnoreCase(obj, prop) { | ||||
|         for (const name in obj) { | ||||
|             if (!obj.hasOwnProperty(name)) { | ||||
|  | @ -322,5 +318,4 @@ class ObjectUtil { | |||
|         } | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -1,7 +1,7 @@ | |||
| {{define "promptModal"}} | ||||
| <a-modal id="prompt-modal" v-model="promptModal.visible" :title="promptModal.title" | ||||
|          :closable="true" @ok="promptModal.ok" :mask-closable="false" | ||||
|          :class="siderDrawer.isDarkTheme ? darkClass : ''" | ||||
|          :class="themeSwitcher.darkCardClass" | ||||
|          :ok-text="promptModal.okText" cancel-text='{{ i18n "cancel" }}'> | ||||
|     <a-input id="prompt-modal-input" :type="promptModal.type" | ||||
|              v-model="promptModal.value" | ||||
|  | @ -36,12 +36,12 @@ | |||
|         }, | ||||
|         confirm() {}, | ||||
|         open({ | ||||
|                 title='', | ||||
|                 type='text', | ||||
|                 value='', | ||||
|                 okText='{{ i18n "sure"}}', | ||||
|                 confirm=() => {}, | ||||
|             }) { | ||||
|             title = '', | ||||
|             type = 'text', | ||||
|             value = '', | ||||
|             okText = '{{ i18n "sure"}}', | ||||
|             confirm = () => {}, | ||||
|         }) { | ||||
|             this.title = title; | ||||
|             this.type = type; | ||||
|             this.value = value; | ||||
|  |  | |||
|  | @ -1,10 +1,12 @@ | |||
| {{define "qrcodeModal"}} | ||||
| <a-modal id="qrcode-modal" v-model="qrModal.visible" :title="qrModal.title" | ||||
|          :closable="true" | ||||
|          :class="siderDrawer.isDarkTheme ? darkClass : ''" | ||||
|          :class="themeSwitcher.darkCardClass" | ||||
|          :footer="null" | ||||
|          width="300px"> | ||||
|     <a-tag color="green" style="margin-bottom: 10px;display: block;text-align: center;" >{{ i18n "pages.inbounds.clickOnQRcode" }}</a-tag> | ||||
|     <a-tag color="green" style="margin-bottom: 10px;display: block;text-align: center;"> | ||||
|         {{ i18n "pages.inbounds.clickOnQRcode" }} | ||||
|     </a-tag> | ||||
|     <canvas @click="copyToClipboard()" id="qrCode" style="width: 100%; height: 100%;"></canvas> | ||||
| </a-modal> | ||||
| 
 | ||||
|  | @ -19,7 +21,7 @@ | |||
|         qrcode: null, | ||||
|         clipboard: null, | ||||
|         visible: false, | ||||
|         show: function (title='', content='', dbInbound=new DBInbound(), copyText='') { | ||||
|         show: function (title = '', content = '', dbInbound = new DBInbound(), copyText = '') { | ||||
|             this.title = title; | ||||
|             this.content = content; | ||||
|             this.dbInbound = dbInbound; | ||||
|  |  | |||
|  | @ -1,7 +1,7 @@ | |||
| {{define "textModal"}} | ||||
| <a-modal id="text-modal" v-model="txtModal.visible" :title="txtModal.title" | ||||
|          :closable="true" ok-text='{{ i18n "copy" }}' cancel-text='{{ i18n "close" }}' | ||||
|          :class="siderDrawer.isDarkTheme ? darkClass : ''" | ||||
|          :class="themeSwitcher.darkCardClass" | ||||
|          :ok-button-props="{attrs:{id:'txt-modal-ok-btn'}}"> | ||||
|     <a-button v-if="!ObjectUtil.isEmpty(txtModal.fileName)" type="primary" style="margin-bottom: 10px;" | ||||
|               :href="'data:application/text;charset=utf-8,' + encodeURIComponent(txtModal.content)" | ||||
|  | @ -21,7 +21,7 @@ | |||
|         qrcode: null, | ||||
|         clipboard: null, | ||||
|         visible: false, | ||||
|         show: function (title='', content='', fileName='') { | ||||
|         show: function (title = '', content = '', fileName = '') { | ||||
|             this.title = title; | ||||
|             this.content = content; | ||||
|             this.fileName = fileName; | ||||
|  |  | |||
|  | @ -18,6 +18,12 @@ | |||
|         border-radius: 30px; | ||||
|     } | ||||
| 
 | ||||
|     .ant-input-group-addon { | ||||
|         border-radius: 0 30px 30px 0; | ||||
|         width: 50px; | ||||
|         font-size: 18px; | ||||
|     } | ||||
| 
 | ||||
|     .ant-input-affix-wrapper .ant-input-prefix { | ||||
|         left: 23px; | ||||
|     } | ||||
|  | @ -26,20 +32,26 @@ | |||
|         padding-left: 50px; | ||||
|     } | ||||
| 
 | ||||
|     .selectLang{ | ||||
|     .centered { | ||||
|         display: flex; | ||||
|         text-align: center; | ||||
|         align-items: center; | ||||
|         justify-content: center; | ||||
|     } | ||||
| 
 | ||||
|     .title { | ||||
|         font-size: 32px; | ||||
|         font-weight: bold; | ||||
|     } | ||||
| 
 | ||||
| </style> | ||||
| <body> | ||||
| <a-layout id="app" v-cloak> | ||||
| <a-layout id="app" v-cloak :class="themeSwitcher.darkClass"> | ||||
|     <transition name="list" appear> | ||||
|         <a-layout-content> | ||||
|             <a-row type="flex" justify="center"> | ||||
|                 <a-col :xs="22" :sm="20" :md="16" :lg="12" :xl="8"> | ||||
|                     <h1>{{ i18n "pages.login.title" }}</h1> | ||||
|                     <h1 class="title">{{ i18n "pages.login.title" }}</h1> | ||||
|                 </a-col> | ||||
|             </a-row> | ||||
|             <a-row type="flex" justify="center"> | ||||
|  | @ -48,35 +60,33 @@ | |||
|                         <a-form-item> | ||||
|                             <a-input v-model.trim="user.username" placeholder='{{ i18n "username" }}' | ||||
|                                      @keydown.enter.native="login" autofocus> | ||||
|                                 <a-icon slot="prefix" type="user" style="color: rgba(0,0,0,.25)"/> | ||||
|                                 <a-icon slot="prefix" type="user" :style="'font-size: 16px;' + themeSwitcher.textStyle" /> | ||||
|                             </a-input> | ||||
|                         </a-form-item> | ||||
|                         <a-form-item> | ||||
|                             <a-input type="password" v-model.trim="user.password" | ||||
|                                      placeholder='{{ i18n "password" }}' @keydown.enter.native="login"> | ||||
|                                 <a-icon slot="prefix" type="lock" style="color: rgba(0,0,0,.25)"/> | ||||
|                             </a-input> | ||||
|                             <password-input icon="lock" v-model.trim="user.password" | ||||
|                                             placeholder='{{ i18n "password" }}' @keydown.enter.native="login"> | ||||
|                             </password-input> | ||||
|                         </a-form-item> | ||||
|                         <a-form-item v-if="secretEnable"> | ||||
|                             <a-input type="text" placeholder='{{ i18n "secretToken" }}' v-model.trim="user.loginSecret" @keydown.enter.native="login"> | ||||
|                             <a-icon slot="prefix" type="key" style="color: rgba(0,0,0,.25)"/> | ||||
|                             <password-input icon="key" v-model.trim="user.loginSecret" | ||||
|                                             placeholder='{{ i18n "secretToken" }}' @keydown.enter.native="login"> | ||||
|                             </password-input> | ||||
|                         </a-input> | ||||
|                         </a-form-item> | ||||
|                         <a-form-item> | ||||
|                             <a-button block @click="login" :loading="loading">{{ i18n "login" }}</a-button> | ||||
|                             <a-row justify="center" class="centered"> | ||||
|                                 <a-button type="primary" :loading="loading" @click="login" :icon="loading ? 'poweroff' : undefined" | ||||
|                                           :style="loading ? { width: '50px' } : { display: 'block', width: '100%' }"> | ||||
|                                     [[ loading ? '' : '{{ i18n "login" }}' ]] | ||||
|                                 </a-button> | ||||
|                             </a-row> | ||||
|                         </a-form-item> | ||||
|                         <a-form-item> | ||||
| 
 | ||||
|                             <a-row justify="center" class="selectLang"> | ||||
|                                 <a-col :span="5"><span>Language :</span></a-col> | ||||
| 
 | ||||
|                                 <a-col :span="7"> | ||||
|                                     <a-select | ||||
|                                             ref="selectLang" | ||||
|                                             v-model="lang" | ||||
|                                             @change="setLang(lang)" | ||||
|                                     > | ||||
|                                         <a-select-option  :value="l.value" label="English" v-for="l in supportLangs" > | ||||
|                             <a-row justify="center" class="centered"> | ||||
|                                 <a-col :span="12"> | ||||
|                                     <a-select ref="selectLang" v-model="lang" @change="setLang(lang)" :dropdown-class-name="themeSwitcher.darkCardClass"> | ||||
|                                         <a-select-option :value="l.value" label="English" v-for="l in supportLangs"> | ||||
|                                             <span role="img" aria-label="l.name" v-text="l.icon"></span> | ||||
|                                               <span v-text="l.name"></span> | ||||
|                                         </a-select-option> | ||||
|  | @ -84,6 +94,11 @@ | |||
|                                 </a-col> | ||||
|                             </a-row> | ||||
|                         </a-form-item> | ||||
|                         <a-form-item> | ||||
|                             <a-row justify="center" class="centered"> | ||||
|                                 <theme-switch /> | ||||
|                             </a-row> | ||||
|                         </a-form-item> | ||||
|                     </a-form> | ||||
|                 </a-col> | ||||
|             </a-row> | ||||
|  | @ -91,24 +106,24 @@ | |||
|     </transition> | ||||
| </a-layout> | ||||
| {{template "js" .}} | ||||
| {{template "component/themeSwitcher" .}} | ||||
| {{template "component/password" .}} | ||||
| <script> | ||||
|     const leftColor = RandomUtil.randomIntRange(0x222222, 0xFFFFFF / 2).toString(16); | ||||
|     const rightColor = RandomUtil.randomIntRange(0xFFFFFF / 2, 0xDDDDDD).toString(16); | ||||
|     const deg = RandomUtil.randomIntRange(0, 360); | ||||
|     const background = `linear-gradient(${deg}deg, #${leftColor} 10%, #${rightColor} 100%)`; | ||||
|     document.querySelector('#app').style.background = background; | ||||
| 
 | ||||
|     const app = new Vue({ | ||||
|         delimiters: ['[[', ']]'], | ||||
|         el: '#app', | ||||
|         data: { | ||||
|             themeSwitcher, | ||||
|             loading: false, | ||||
|             user: new User(), | ||||
|             secretEnable: false, | ||||
|             lang : "" | ||||
|             lang: "" | ||||
|         }, | ||||
|         created(){ | ||||
|           this.lang = getLang(); | ||||
|           this.secretEnable = this.getSecretStatus(); | ||||
|         created() { | ||||
|             this.updateBackground(); | ||||
|             this.lang = getLang(); | ||||
|             this.secretEnable = this.getSecretStatus(); | ||||
|         }, | ||||
|         methods: { | ||||
|             async login() { | ||||
|  | @ -120,16 +135,29 @@ | |||
|                 } | ||||
|             }, | ||||
|             async getSecretStatus() { | ||||
|                 this.loading= true; | ||||
|                 this.loading = true; | ||||
|                 const msg = await HttpUtil.post('/getSecretStatus'); | ||||
|                 this.loading = false; | ||||
|                 if (msg.success){ | ||||
|                 if (msg.success) { | ||||
|                     this.secretEnable = msg.obj; | ||||
|                     return msg.obj; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|             }, | ||||
|             updateBackground() { | ||||
|                 const leftColor = RandomUtil.randomIntRange(0x222222, 0xFFFFFF / 2).toString(16); | ||||
|                 const rightColor = RandomUtil.randomIntRange(0xFFFFFF / 2, 0xDDDDDD).toString(16); | ||||
|                 const deg = RandomUtil.randomIntRange(0, 360); | ||||
|                 const background = `linear-gradient(${deg}deg, #${leftColor} 10%, #${rightColor} 100%)`; | ||||
|                 document.querySelector('#app').style.background = this.themeSwitcher.isDarkTheme ? colors.dark.bg : background; | ||||
|             }, | ||||
|         }, | ||||
|         watch: { | ||||
|             'themeSwitcher.isDarkTheme'(newVal, oldVal) { | ||||
|                 this.updateBackground(); | ||||
|             }, | ||||
|         }, | ||||
|     }); | ||||
| 
 | ||||
| </script> | ||||
| </body> | ||||
| </html> | ||||
|  | @ -1,11 +1,11 @@ | |||
| {{define "clientsBulkModal"}} | ||||
| <a-modal id="client-bulk-modal" v-model="clientsBulkModal.visible" :title="clientsBulkModal.title" @ok="clientsBulkModal.ok" | ||||
|          :confirm-loading="clientsBulkModal.confirmLoading" :closable="true" :mask-closable="false" | ||||
|          :class="siderDrawer.isDarkTheme ? darkClass : ''" | ||||
|          :class="themeSwitcher.darkCardClass" | ||||
|          :ok-text="clientsBulkModal.okText" cancel-text='{{ i18n "close" }}'> | ||||
|     <a-form layout="inline"> | ||||
|         <a-form-item label='{{ i18n "pages.client.method" }}'> | ||||
|             <a-select v-model="clientsBulkModal.emailMethod" buttonStyle="solid" style="width: 350px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"> | ||||
|             <a-select v-model="clientsBulkModal.emailMethod" buttonStyle="solid" style="width: 350px" :dropdown-class-name="themeSwitcher.darkCardClass"> | ||||
|                 <a-select-option :value="0">Random</a-select-option> | ||||
|                 <a-select-option :value="1">Random+Prefix</a-select-option> | ||||
|                 <a-select-option :value="2">Random+Prefix+Num</a-select-option> | ||||
|  | @ -71,20 +71,20 @@ | |||
|         </a-form-item> | ||||
|         <br> | ||||
|         <a-form-item v-if="clientsBulkModal.inbound.xtls" label="Flow"> | ||||
|             <a-select v-model="clientsBulkModal.flow" style="width: 200px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"> | ||||
|             <a-select v-model="clientsBulkModal.flow" style="width: 200px" :dropdown-class-name="themeSwitcher.darkCardClass"> | ||||
|                 <a-select-option value="">{{ i18n "none" }}</a-select-option> | ||||
|                 <a-select-option v-for="key in XTLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option> | ||||
|             </a-select> | ||||
|         </a-form-item> | ||||
|         <a-form-item v-if="clientsBulkModal.inbound.canEnableTlsFlow()" label="Flow" layout="inline"> | ||||
|             <a-select v-model="clientsBulkModal.flow" style="width: 200px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"> | ||||
|             <a-select v-model="clientsBulkModal.flow" style="width: 200px" :dropdown-class-name="themeSwitcher.darkCardClass"> | ||||
|                 <a-select-option value="" selected>{{ i18n "none" }}</a-select-option> | ||||
|                 <a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option> | ||||
|             </a-select> | ||||
|         </a-form-item> | ||||
|         <a-form-item> | ||||
|             <span slot="label"> | ||||
|                 <span >{{ i18n "pages.inbounds.totalFlow" }}</span> (GB) | ||||
|                 <span>{{ i18n "pages.inbounds.totalFlow" }}</span> (GB) | ||||
|                 <a-tooltip> | ||||
|                     <template slot="title"> | ||||
|                         0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span> | ||||
|  | @ -104,7 +104,7 @@ | |||
|         </a-form-item> | ||||
|         <a-form-item v-else> | ||||
|             <span slot="label"> | ||||
|                 <span >{{ i18n "pages.inbounds.expireDate" }}</span> | ||||
|                 <span>{{ i18n "pages.inbounds.expireDate" }}</span> | ||||
|                 <a-tooltip> | ||||
|                     <template slot="title"> | ||||
|                         <span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span> | ||||
|  | @ -113,7 +113,7 @@ | |||
|                 </a-tooltip> | ||||
|             </span> | ||||
|             <a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss" | ||||
|                            :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''" | ||||
|                            :dropdown-class-name="themeSwitcher.darkCardClass" | ||||
|                            v-model="clientsBulkModal.expiryTime" style="width: 300px;"></a-date-picker> | ||||
|         </a-form-item> | ||||
|     </a-form> | ||||
|  | @ -143,37 +143,42 @@ | |||
|         delayedStart: false, | ||||
|         ok() { | ||||
|             clients = []; | ||||
|             method=clientsBulkModal.emailMethod; | ||||
|             if(method>1){ | ||||
|                 start=clientsBulkModal.firstNum; | ||||
|                 end=clientsBulkModal.lastNum + 1; | ||||
|             method = clientsBulkModal.emailMethod; | ||||
|             if (method > 1) { | ||||
|                 start = clientsBulkModal.firstNum; | ||||
|                 end = clientsBulkModal.lastNum + 1; | ||||
|             } else { | ||||
|                 start=0; | ||||
|                 end=clientsBulkModal.quantity; | ||||
|                 start = 0; | ||||
|                 end = clientsBulkModal.quantity; | ||||
|             } | ||||
|             prefix = (method>0 && clientsBulkModal.emailPrefix.length>0) ? clientsBulkModal.emailPrefix : ""; | ||||
|             useNum=(method>1); | ||||
|             postfix = (method>2 && clientsBulkModal.emailPostfix.length>0) ? clientsBulkModal.emailPostfix : ""; | ||||
|             prefix = (method > 0 && clientsBulkModal.emailPrefix.length > 0) ? clientsBulkModal.emailPrefix : ""; | ||||
|             useNum = (method > 1); | ||||
|             postfix = (method > 2 && clientsBulkModal.emailPostfix.length > 0) ? clientsBulkModal.emailPostfix : ""; | ||||
|             for (let i = start; i < end; i++) { | ||||
|                 newClient = clientsBulkModal.newClient(clientsBulkModal.dbInbound.protocol); | ||||
|                 if(method==4) newClient.email = ""; | ||||
|                 if (method == 4) newClient.email = ""; | ||||
|                 newClient.email += useNum ? prefix + i.toString() + postfix : prefix + postfix; | ||||
|                 newClient.subId = clientsBulkModal.subId; | ||||
|                 newClient.tgId = clientsBulkModal.tgId; | ||||
|                 newClient.limitIp = clientsBulkModal.limitIp; | ||||
|                 newClient._totalGB = clientsBulkModal.totalGB; | ||||
|                 newClient._expiryTime = clientsBulkModal.expiryTime; | ||||
|                 if(clientsBulkModal.inbound.canEnableTlsFlow()){ | ||||
|                 if (clientsBulkModal.inbound.canEnableTlsFlow()) { | ||||
|                     newClient.flow = clientsBulkModal.flow; | ||||
|                 } | ||||
|                 if(clientsBulkModal.inbound.xtls){ | ||||
|                 if (clientsBulkModal.inbound.xtls) { | ||||
|                     newClient.flow = clientsBulkModal.flow; | ||||
|                 } | ||||
|                 clients.push(newClient); | ||||
|             } | ||||
|             ObjectUtil.execute(clientsBulkModal.confirm, clients, clientsBulkModal.dbInbound.id); | ||||
|         }, | ||||
|         show({ title='', okText='{{ i18n "sure" }}', dbInbound=null, confirm=(inbound, dbInbound)=>{} }) { | ||||
|         show({ | ||||
|             title = '', | ||||
|             okText = '{{ i18n "sure" }}', | ||||
|             dbInbound = null, | ||||
|             confirm = (inbound, dbInbound) => { } | ||||
|         }) { | ||||
|             this.visible = true; | ||||
|             this.title = title; | ||||
|             this.okText = okText; | ||||
|  | @ -181,21 +186,21 @@ | |||
|             this.quantity = 1; | ||||
|             this.totalGB = 0; | ||||
|             this.expiryTime = 0; | ||||
|             this.emailMethod= 0; | ||||
|             this.limitIp= 0; | ||||
|             this.firstNum= 1; | ||||
|             this.lastNum= 1; | ||||
|             this.emailPrefix= ""; | ||||
|             this.emailPostfix= ""; | ||||
|             this.subId= ""; | ||||
|             this.tgId= ""; | ||||
|             this.flow= ""; | ||||
|             this.emailMethod = 0; | ||||
|             this.limitIp = 0; | ||||
|             this.firstNum = 1; | ||||
|             this.lastNum = 1; | ||||
|             this.emailPrefix = ""; | ||||
|             this.emailPostfix = ""; | ||||
|             this.subId = ""; | ||||
|             this.tgId = ""; | ||||
|             this.flow = ""; | ||||
|             this.dbInbound = new DBInbound(dbInbound); | ||||
|             this.inbound = dbInbound.toInbound(); | ||||
|             this.delayedStart = false; | ||||
|         }, | ||||
|         getClients(protocol, clientSettings) { | ||||
|             switch(protocol){ | ||||
|             switch (protocol) { | ||||
|                 case Protocols.VMESS: return clientSettings.vmesses; | ||||
|                 case Protocols.VLESS: return clientSettings.vlesses; | ||||
|                 case Protocols.TROJAN: return clientSettings.trojans; | ||||
|  | @ -230,10 +235,11 @@ | |||
|             get delayedExpireDays() { | ||||
|                 return this.clientsBulkModal.expiryTime < 0 ? this.clientsBulkModal.expiryTime / -86400000 : 0; | ||||
|             }, | ||||
|             set delayedExpireDays(days){ | ||||
|             set delayedExpireDays(days) { | ||||
|                 this.clientsBulkModal.expiryTime = -86400000 * days; | ||||
|             }, | ||||
|         }, | ||||
|     }); | ||||
| 
 | ||||
| </script> | ||||
| {{end}} | ||||
|  | @ -1,7 +1,7 @@ | |||
| {{define "clientsModal"}} | ||||
| <a-modal id="client-modal" v-model="clientModal.visible" :title="clientModal.title" @ok="clientModal.ok" | ||||
|          :confirm-loading="clientModal.confirmLoading" :closable="true" :mask-closable="false" | ||||
|          :class="siderDrawer.isDarkTheme ? darkClass : ''" | ||||
|          :class="themeSwitcher.darkCardClass" | ||||
|          :ok-text="clientModal.okText" cancel-text='{{ i18n "close" }}'> | ||||
|     {{template "form/client"}} | ||||
| </a-modal> | ||||
|  | @ -23,13 +23,13 @@ | |||
|         isExpired: false, | ||||
|         delayedStart: false, | ||||
|         ok() { | ||||
|             if(clientModal.isEdit){ | ||||
|             if (clientModal.isEdit) { | ||||
|                 ObjectUtil.execute(clientModal.confirm, clientModalApp.client, clientModal.dbInbound.id, clientModal.oldClientId); | ||||
|             } else { | ||||
|                 ObjectUtil.execute(clientModal.confirm, clientModalApp.client, clientModal.dbInbound.id); | ||||
|             } | ||||
|         }, | ||||
|         show({ title='', okText='{{ i18n "sure" }}', index=null, dbInbound=null, confirm=()=>{}, isEdit=false  }) { | ||||
|         show({ title = '', okText = '{{ i18n "sure" }}', index = null, dbInbound = null, confirm = () => { }, isEdit = false }) { | ||||
|             this.visible = true; | ||||
|             this.title = title; | ||||
|             this.okText = okText; | ||||
|  | @ -40,11 +40,11 @@ | |||
|             this.index = index === null ? this.clients.length : index; | ||||
|             this.isExpired = isEdit ? this.inbound.isExpiry(this.index) : false; | ||||
|             this.delayedStart = false; | ||||
|             if (isEdit){ | ||||
|                 if (this.clients[index].expiryTime < 0){ | ||||
|             if (isEdit) { | ||||
|                 if (this.clients[index].expiryTime < 0) { | ||||
|                     this.delayedStart = true; | ||||
|                 } | ||||
|                 this.oldClientId = this.getClientId(dbInbound.protocol,clients[index]); | ||||
|                 this.oldClientId = this.getClientId(dbInbound.protocol, clients[index]); | ||||
|             } else { | ||||
|                 this.addClient(this.inbound.protocol, this.clients); | ||||
|             } | ||||
|  | @ -52,7 +52,7 @@ | |||
|             this.confirm = confirm; | ||||
|         }, | ||||
|         getClients(protocol, clientSettings) { | ||||
|             switch(protocol){ | ||||
|             switch (protocol) { | ||||
|                 case Protocols.VMESS: return clientSettings.vmesses; | ||||
|                 case Protocols.VLESS: return clientSettings.vlesses; | ||||
|                 case Protocols.TROJAN: return clientSettings.trojans; | ||||
|  | @ -61,7 +61,7 @@ | |||
|             } | ||||
|         }, | ||||
|         getClientId(protocol, client) { | ||||
|             switch(protocol){ | ||||
|             switch (protocol) { | ||||
|                 case Protocols.TROJAN: return client.password; | ||||
|                 case Protocols.SHADOWSOCKS: return client.email; | ||||
|                 default: return client.id; | ||||
|  | @ -103,24 +103,24 @@ | |||
|                 return this.clientModal.isEdit; | ||||
|             }, | ||||
|             get isTrafficExhausted() { | ||||
|                 if(!clientStats) return false | ||||
|                 if(clientStats.total <= 0) return false | ||||
|                 if(clientStats.up + clientStats.down < clientStats.total) return false | ||||
|                 if (!clientStats) return false | ||||
|                 if (clientStats.total <= 0) return false | ||||
|                 if (clientStats.up + clientStats.down < clientStats.total) return false | ||||
|                 return true | ||||
|             }, | ||||
|             get isExpiry() { | ||||
|                 return this.clientModal.isExpired | ||||
|             }, | ||||
|             get statsColor() { | ||||
|                 if(!clientStats) return 'blue' | ||||
|                 if(clientStats.total <= 0) return 'blue' | ||||
|                 else if(clientStats.total > 0 && (clientStats.down+clientStats.up) < clientStats.total) return 'cyan' | ||||
|                 if (!clientStats) return 'blue' | ||||
|                 if (clientStats.total <= 0) return 'blue' | ||||
|                 else if (clientStats.total > 0 && (clientStats.down + clientStats.up) < clientStats.total) return 'cyan' | ||||
|                 else return 'red' | ||||
|             }, | ||||
|             get delayedExpireDays() { | ||||
|                 return this.client && this.client.expiryTime < 0 ? this.client.expiryTime / -86400000 : 0; | ||||
|             }, | ||||
|             set delayedExpireDays(days){ | ||||
|             set delayedExpireDays(days) { | ||||
|                 this.client.expiryTime = -86400000 * days; | ||||
|             }, | ||||
|         }, | ||||
|  | @ -129,13 +129,13 @@ | |||
|                 var chars = 'abcdefghijklmnopqrstuvwxyz1234567890'; | ||||
|                 var string = ''; | ||||
|                 var len = 6 + Math.floor(Math.random() * 5); | ||||
|                 for(var ii=0; ii<len; ii++){ | ||||
|                 for (var ii = 0; ii < len; ii++) { | ||||
|                     string += chars[Math.floor(Math.random() * chars.length)]; | ||||
|                 } | ||||
|                 client.email = string; | ||||
|             }, | ||||
|             async getDBClientIps(email,event) { | ||||
|                 const msg = await HttpUtil.post('/xui/inbound/clientIps/'+ email); | ||||
|             async getDBClientIps(email, event) { | ||||
|                 const msg = await HttpUtil.post('/xui/inbound/clientIps/' + email); | ||||
|                 if (!msg.success) { | ||||
|                     return; | ||||
|                 } | ||||
|  | @ -149,22 +149,22 @@ | |||
|                 } | ||||
|             }, | ||||
|             async clearDBClientIps(email) { | ||||
|                 const msg = await HttpUtil.post('/xui/inbound/clearClientIps/'+ email); | ||||
|                 const msg = await HttpUtil.post('/xui/inbound/clearClientIps/' + email); | ||||
|                 if (!msg.success) { | ||||
|                     return; | ||||
|                 } | ||||
|                 document.getElementById("clientIPs").value = "" | ||||
|             }, | ||||
|             resetClientTraffic(email,dbInboundId,iconElement) { | ||||
|             resetClientTraffic(email, dbInboundId, iconElement) { | ||||
|                 this.$confirm({ | ||||
|                     title: '{{ i18n "pages.inbounds.resetTraffic"}}', | ||||
|                     content: '{{ i18n "pages.inbounds.resetTrafficContent"}}', | ||||
|                     class: siderDrawer.isDarkTheme ? darkClass : '', | ||||
|                     class: themeSwitcher.darkCardClass, | ||||
|                     okText: '{{ i18n "reset"}}', | ||||
|                     cancelText: '{{ i18n "cancel"}}', | ||||
|                     onOk: async () => { | ||||
|                         iconElement.disabled = true; | ||||
|                         const msg = await HttpUtil.postWithModal('/xui/inbound/' + dbInboundId + '/resetClientTraffic/'+ email); | ||||
|                         const msg = await HttpUtil.postWithModal('/xui/inbound/' + dbInboundId + '/resetClientTraffic/' + email); | ||||
|                         if (msg.success) { | ||||
|                             this.clientModal.clientStats.up = 0; | ||||
|                             this.clientModal.clientStats.down = 0; | ||||
|  | @ -175,5 +175,6 @@ | |||
|             }, | ||||
|         }, | ||||
|     }); | ||||
| 
 | ||||
| </script> | ||||
| {{end}} | ||||
|  |  | |||
|  | @ -23,17 +23,14 @@ | |||
| 
 | ||||
| 
 | ||||
| {{define "commonSider"}} | ||||
| <a-layout-sider :theme="siderDrawer.theme" id="sider" collapsible breakpoint="md" collapsed-width="0"> | ||||
|     <a-menu :theme="siderDrawer.theme" mode="inline" selected-keys=""> | ||||
| <a-layout-sider :theme="themeSwitcher.currentTheme" id="sider" collapsible breakpoint="md" collapsed-width="0"> | ||||
|     <a-menu :theme="themeSwitcher.currentTheme" mode="inline" selected-keys=""> | ||||
|         <a-menu-item mode="inline"> | ||||
|             <a-icon type="bg-colors"></a-icon> | ||||
|             <a-switch :default-checked="siderDrawer.isDarkTheme" | ||||
|             checked-children="☀" | ||||
|             un-checked-children="🌙" | ||||
|             @change="siderDrawer.changeTheme()"></a-switch> | ||||
|             <theme-switch /> | ||||
|         </a-menu-item> | ||||
|     </a-menu> | ||||
|     <a-menu :theme="siderDrawer.theme" mode="inline" :selected-keys="['{{ .request_uri }}']" | ||||
|     <a-menu :theme="themeSwitcher.currentTheme" mode="inline" :selected-keys="['{{ .request_uri }}']" | ||||
|             @click="({key}) => key.startsWith('http') ? window.open(key) : location.href = key"> | ||||
|         {{template "menuItems" .}} | ||||
|     </a-menu> | ||||
|  | @ -41,32 +38,25 @@ | |||
| <a-drawer id="sider-drawer" placement="left" :closable="false" | ||||
|           @close="siderDrawer.close()" | ||||
|           :visible="siderDrawer.visible" | ||||
|           :wrap-class-name="siderDrawer.isDarkTheme ? 'ant-drawer-dark' : ''" | ||||
|           :wrap-class-name="themeSwitcher.darkDrawerClass" | ||||
|           :wrap-style="{ padding: 0 }"> | ||||
|     <div class="drawer-handle" @click="siderDrawer.change()" slot="handle"> | ||||
|         <a-icon :type="siderDrawer.visible ? 'close' : 'menu-fold'"></a-icon> | ||||
|     </div> | ||||
|     <a-menu :theme="siderDrawer.theme" mode="inline" selected-keys=""> | ||||
|     <a-menu :theme="themeSwitcher.currentTheme" mode="inline" selected-keys=""> | ||||
|         <a-menu-item mode="inline"> | ||||
|             <a-icon type="bg-colors"></a-icon> | ||||
|             <a-switch :default-checked="siderDrawer.isDarkTheme" | ||||
|             checked-children="☀" | ||||
|             un-checked-children="🌙" | ||||
|             @change="siderDrawer.changeTheme()"></a-switch> | ||||
|             <theme-switch /> | ||||
|         </a-menu-item> | ||||
|     </a-menu> | ||||
|     <a-menu :theme="siderDrawer.theme" mode="inline" :selected-keys="['{{ .request_uri }}']" | ||||
|     <a-menu :theme="themeSwitcher.currentTheme" mode="inline" :selected-keys="['{{ .request_uri }}']" | ||||
|         @click="({key}) => key.startsWith('http') ? window.open(key) : location.href = key"> | ||||
|         {{template "menuItems" .}} | ||||
|     </a-menu> | ||||
| </a-drawer> | ||||
| <script> | ||||
|     const darkClass = "ant-card-dark"; | ||||
|     const bgDarkStyle = "background-color: #242c3a"; | ||||
|     const siderDrawer = { | ||||
|         visible: false, | ||||
|         collapsed: false, | ||||
|         isDarkTheme: localStorage.getItem("dark-mode") === 'false' ? false : true, | ||||
|         show() { | ||||
|             this.visible = true; | ||||
|         }, | ||||
|  | @ -76,17 +66,6 @@ | |||
|         change() { | ||||
|             this.visible = !this.visible; | ||||
|         }, | ||||
|         toggleCollapsed() { | ||||
|             this.collapsed = !this.collapsed; | ||||
|         }, | ||||
|         changeTheme() { | ||||
|             this.isDarkTheme = ! this.isDarkTheme; | ||||
|             localStorage.setItem("dark-mode", this.isDarkTheme); | ||||
|         }, | ||||
|         get theme() { | ||||
|             return this.isDarkTheme ? 'dark' : 'light'; | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
| </script> | ||||
| {{end}} | ||||
|  |  | |||
							
								
								
									
										35
									
								
								web/html/xui/component/password.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								web/html/xui/component/password.html
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,35 @@ | |||
| {{define "component/passwordInput"}} | ||||
| <template> | ||||
|     <a-input :value="value" :type="showPassword ? 'text' : 'password'" | ||||
|             :placeholder="placeholder" | ||||
|             @input="$emit('input', $event.target.value)"> | ||||
|         <template v-if="icon" #prefix> | ||||
|             <a-icon :type="icon" :style="'font-size: 16px;' + themeSwitcher.textStyle" /> | ||||
|         </template> | ||||
|         <template #addonAfter> | ||||
|             <a-icon :type="showPassword ? 'eye-invisible' : 'eye'" | ||||
|                     @click="toggleShowPassword" | ||||
|                     :style="'font-size: 16px;' + themeSwitcher.textStyle" /> | ||||
|         </template> | ||||
|     </a-input> | ||||
| </template> | ||||
| {{end}} | ||||
| 
 | ||||
| {{define "component/password"}} | ||||
| <script> | ||||
|   Vue.component('password-input', { | ||||
|     props: ["title", "value", "placeholder", "icon"], | ||||
|     template: `{{template "component/passwordInput"}}`, | ||||
|     data() { | ||||
|       return { | ||||
|         showPassword: false, | ||||
|       }; | ||||
|     }, | ||||
|     methods: { | ||||
|       toggleShowPassword() { | ||||
|         this.showPassword = !this.showPassword; | ||||
|       }, | ||||
|     }, | ||||
|   }); | ||||
| </script> | ||||
| {{end}} | ||||
							
								
								
									
										58
									
								
								web/html/xui/component/themeSwitch.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								web/html/xui/component/themeSwitch.html
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,58 @@ | |||
| {{define "component/themeSwitchTemplate"}} | ||||
| <template> | ||||
|   <a-switch :default-checked="themeSwitcher.isDarkTheme" | ||||
|             checked-children="☀" | ||||
|             un-checked-children="🌙" | ||||
|             @change="themeSwitcher.toggleTheme()"> | ||||
|   </a-switch> | ||||
| </template> | ||||
| {{end}} | ||||
| 
 | ||||
| {{define "component/themeSwitcher"}} | ||||
| <script> | ||||
|   const colors = { | ||||
|     dark: { | ||||
|       bg: "#242c3a", | ||||
|       text: "hsla(0,0%,100%,.65)" | ||||
|     }, | ||||
|     light: { | ||||
|       bg: '#f0f2f5', | ||||
|       text: "rgba(0, 0, 0, 0.7)", | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   function createThemeSwitcher() { | ||||
|     const isDarkTheme = localStorage.getItem('dark-mode') === 'true'; | ||||
|     const theme = isDarkTheme ? 'dark' : 'light'; | ||||
|     return { | ||||
|       isDarkTheme, | ||||
|       bgStyle: `background: ${colors[theme].bg};`, | ||||
|       textStyle: `color: ${colors[theme].text};`, | ||||
|       darkClass: isDarkTheme ? 'ant-card-dark' : '', | ||||
|       darkCardClass: isDarkTheme ? 'ant-card-dark' : '', | ||||
|       darkDrawerClass: isDarkTheme ? 'ant-drawer-dark' : '', | ||||
|       get currentTheme() { | ||||
|         return this.isDarkTheme ? 'dark' : 'light'; | ||||
|       }, | ||||
|       toggleTheme() { | ||||
|         this.isDarkTheme = !this.isDarkTheme; | ||||
|         this.theme = this.isDarkTheme ? 'dark' : 'light'; | ||||
|         localStorage.setItem('dark-mode', this.isDarkTheme); | ||||
|         this.bgStyle = `background: ${colors[this.theme].bg};`; | ||||
|         this.textStyle = `color: ${colors[this.theme].text};`; | ||||
|         this.darkClass = this.isDarkTheme ? 'ant-card-dark' : ''; | ||||
|         this.darkCardClass = this.isDarkTheme ? 'ant-card-dark' : ''; | ||||
|         this.darkDrawerClass = this.isDarkTheme ? 'ant-drawer-dark' : ''; | ||||
|       }, | ||||
|     }; | ||||
|   } | ||||
| 
 | ||||
|   const themeSwitcher = createThemeSwitcher(); | ||||
| 
 | ||||
|   Vue.component('theme-switch', { | ||||
|     props: [], | ||||
|     template: `{{template "component/themeSwitchTemplate"}}`, | ||||
|     data: () => ({ themeSwitcher }), | ||||
|   }); | ||||
| </script> | ||||
| {{end}} | ||||
|  | @ -1,7 +1,9 @@ | |||
| {{define "form/client"}} | ||||
| <a-form layout="inline" v-if="client"> | ||||
|     <template v-if="isEdit"> | ||||
|     <a-tag v-if="isExpiry || isTrafficExhausted" color="red" style="margin-bottom: 10px;display: block;text-align: center;">Account is (Expired|Traffic Ended) And Disabled</a-tag> | ||||
|         <a-tag v-if="isExpiry || isTrafficExhausted" color="red" style="margin-bottom: 10px;display: block;text-align: center;"> | ||||
|             Account is (Expired|Traffic Ended) And Disabled | ||||
|         </a-tag> | ||||
|     </template> | ||||
|     <a-form-item label='{{ i18n "pages.inbounds.enable" }}'> | ||||
|         <a-switch v-model="client.enable"></a-switch> | ||||
|  | @ -17,11 +19,12 @@ | |||
|                 <a-icon type="sync" @click="getNewEmail(client)"></a-icon> | ||||
|             </a-tooltip> | ||||
|         </span> | ||||
|         <a-input v-model.trim="client.email" style="width: 200px;" ></a-input> | ||||
|         <a-input v-model.trim="client.email" style="width: 200px;"></a-input> | ||||
|     </a-form-item> | ||||
|     <a-form-item label="Password" v-if="inbound.protocol === Protocols.TROJAN || inbound.protocol === Protocols.SHADOWSOCKS"> | ||||
|         <a-icon v-if="inbound.protocol === Protocols.SHADOWSOCKS" @click="client.password = RandomUtil.randomShadowsocksPassword()" type="sync"> </a-icon> | ||||
|         <a-input v-model.trim="client.password" style="width: 300px;" ></a-input> | ||||
|         <a-icon v-if="inbound.protocol === Protocols.SHADOWSOCKS" | ||||
|                 @click="client.password = RandomUtil.randomShadowsocksPassword()" type="sync"> </a-icon> | ||||
|         <a-input v-model.trim="client.password" style="width: 300px;"></a-input> | ||||
|     </a-form-item> | ||||
|     <br> | ||||
|     <a-form-item label='{{ i18n "additional" }} ID' v-if="inbound.protocol === Protocols.VMESS"> | ||||
|  | @ -85,26 +88,29 @@ | |||
| 			</a-tooltip> | ||||
| 		</span> | ||||
| 		<a-form layout="block"> | ||||
| 			<a-textarea id="clientIPs" readonly @click="getDBClientIps(client.email,$event)" placeholder="Click To Get IPs"  :auto-size="{ minRows: 2, maxRows: 10 }"> | ||||
| 			<a-textarea id="clientIPs" readonly  | ||||
|                         @click="getDBClientIps(client.email,$event)" | ||||
|                         placeholder="Click To Get IPs" | ||||
|                         :auto-size="{ minRows: 2, maxRows: 10 }"> | ||||
| 			</a-textarea> | ||||
| 		</a-form> | ||||
| 	</a-form-item> | ||||
|     <br> | ||||
|     <a-form-item v-if="inbound.xtls" label="Flow"> | ||||
|         <a-select v-model="client.flow" style="width: 200px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"> | ||||
|         <a-select v-model="client.flow" style="width: 200px" :dropdown-class-name="themeSwitcher.darkCardClass"> | ||||
|             <a-select-option value="">{{ i18n "none" }}</a-select-option> | ||||
|             <a-select-option v-for="key in XTLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option> | ||||
|         </a-select> | ||||
|     </a-form-item> | ||||
|     <a-form-item v-else-if="inbound.canEnableTlsFlow()" label="Flow"> | ||||
|         <a-select v-model="client.flow" style="width: 200px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"> | ||||
|         <a-select v-model="client.flow" style="width: 200px" :dropdown-class-name="themeSwitcher.darkCardClass"> | ||||
|             <a-select-option value="" selected>{{ i18n "none" }}</a-select-option> | ||||
|             <a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option> | ||||
|         </a-select> | ||||
|     </a-form-item> | ||||
|     <a-form-item> | ||||
|         <span slot="label"> | ||||
|             <span >{{ i18n "pages.inbounds.totalFlow" }}</span> (GB) | ||||
|             <span>{{ i18n "pages.inbounds.totalFlow" }}</span> (GB) | ||||
|             <a-tooltip> | ||||
|                 <template slot="title"> | ||||
|                     0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span> | ||||
|  | @ -112,9 +118,10 @@ | |||
|                 <a-icon type="question-circle" theme="filled"></a-icon> | ||||
|             </a-tooltip> | ||||
|         </span> | ||||
|         <a-input-number v-model="client._totalGB":min="0"></a-input-number> | ||||
|         <a-input-number v-model="client._totalGB" :min="0"></a-input-number> | ||||
|         <template v-if="isEdit && clientStats"> | ||||
|             <br><span> {{ i18n "usage" }}:</span> | ||||
|             <br> | ||||
|             <span> {{ i18n "usage" }}:</span> | ||||
|             <a-tag :color="statsColor"> | ||||
|                 [[ sizeFormat(clientStats.up) ]] /  | ||||
|                 [[ sizeFormat(clientStats.down) ]] | ||||
|  | @ -122,7 +129,8 @@ | |||
|             </a-tag> | ||||
|             <a-tooltip> | ||||
|                 <template slot="title">{{ i18n "pages.inbounds.resetTraffic" }}</template> | ||||
|                 <a-icon type="retweet" @click="resetClientTraffic(client.email,clientStats.inboundId,$event.target)" v-if="client.email.length > 0"></a-icon> | ||||
|                 <a-icon type="retweet" @click="resetClientTraffic(client.email,clientStats.inboundId,$event.target)" | ||||
|                         v-if="client.email.length > 0"></a-icon> | ||||
|             </a-tooltip> | ||||
|         </template>         | ||||
|     </a-form-item> | ||||
|  | @ -136,7 +144,7 @@ | |||
|     </a-form-item> | ||||
|     <a-form-item v-else> | ||||
|         <span slot="label"> | ||||
|             <span >{{ i18n "pages.inbounds.expireDate" }}</span> | ||||
|             <span>{{ i18n "pages.inbounds.expireDate" }}</span> | ||||
|             <a-tooltip> | ||||
|                 <template slot="title"> | ||||
|                     <span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span> | ||||
|  | @ -145,7 +153,7 @@ | |||
|             </a-tooltip> | ||||
|         </span> | ||||
|         <a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss" | ||||
|                        :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''" | ||||
|                        :dropdown-class-name="themeSwitcher.darkCardClass" | ||||
|                        v-model="client._expiryTime" style="width: 170px;"></a-date-picker> | ||||
|         <a-tag color="red" v-if="isExpiry">Expired</a-tag> | ||||
|     </a-form-item> | ||||
|  |  | |||
|  | @ -8,7 +8,7 @@ | |||
|         <a-switch v-model="dbInbound.enable"></a-switch> | ||||
|     </a-form-item> | ||||
|     <a-form-item label='{{ i18n "protocol" }}'> | ||||
|         <a-select v-model="inbound.protocol" style="width: 160px;" :disabled="isEdit" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"> | ||||
|         <a-select v-model="inbound.protocol" style="width: 160px;" :disabled="isEdit" :dropdown-class-name="themeSwitcher.darkCardClass"> | ||||
|             <a-select-option v-for="p in Protocols" :key="p" :value="p">[[ p ]]</a-select-option> | ||||
|         </a-select> | ||||
|     </a-form-item> | ||||
|  | @ -31,7 +31,7 @@ | |||
|     <br> | ||||
|     <a-form-item> | ||||
|         <span slot="label"> | ||||
|             <span >{{ i18n "pages.inbounds.totalFlow" }}</span> (GB) | ||||
|             <span>{{ i18n "pages.inbounds.totalFlow" }}</span> (GB) | ||||
|             <a-tooltip> | ||||
|                 <template slot="title"> | ||||
|                     0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span> | ||||
|  | @ -43,7 +43,7 @@ | |||
|     </a-form-item> | ||||
|     <a-form-item> | ||||
|         <span slot="label"> | ||||
|             <span >{{ i18n "pages.inbounds.expireDate" }}</span> | ||||
|             <span>{{ i18n "pages.inbounds.expireDate" }}</span> | ||||
|             <a-tooltip> | ||||
|                 <template slot="title"> | ||||
|                     <span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span> | ||||
|  | @ -52,7 +52,7 @@ | |||
|             </a-tooltip> | ||||
|         </span> | ||||
|         <a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss" | ||||
|                        :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''" | ||||
|                        :dropdown-class-name="themeSwitcher.darkCardClass" | ||||
|                        v-model="dbInbound._expiryTime" style="width: 300px;"></a-date-picker> | ||||
|     </a-form-item> | ||||
| </a-form> | ||||
|  |  | |||
|  | @ -8,7 +8,7 @@ | |||
|     </a-form-item> | ||||
|     <br> | ||||
|     <a-form-item label='{{ i18n "pages.inbounds.network"}}'> | ||||
|         <a-select v-model="inbound.settings.network" style="width: 100px;" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"> | ||||
|         <a-select v-model="inbound.settings.network" style="width: 100px;" :dropdown-class-name="themeSwitcher.darkCardClass"> | ||||
|             <a-select-option value="tcp,udp">TCP+UDP</a-select-option> | ||||
|             <a-select-option value="tcp">TCP</a-select-option> | ||||
|             <a-select-option value="udp">UDP</a-select-option> | ||||
|  |  | |||
|  | @ -57,7 +57,7 @@ | |||
|             <br> | ||||
|             <a-form-item> | ||||
|                 <span slot="label"> | ||||
|                     <span >{{ i18n "pages.inbounds.totalFlow" }}</span> (GB) | ||||
|                     <span>{{ i18n "pages.inbounds.totalFlow" }}</span> (GB) | ||||
|                     <a-tooltip> | ||||
|                         <template slot="title"> | ||||
|                             0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span> | ||||
|  | @ -77,7 +77,7 @@ | |||
|             </a-form-item> | ||||
|             <a-form-item v-else> | ||||
|                 <span slot="label"> | ||||
|                     <span >{{ i18n "pages.inbounds.expireDate" }}</span> | ||||
|                     <span>{{ i18n "pages.inbounds.expireDate" }}</span> | ||||
|                     <a-tooltip> | ||||
|                         <template slot="title"> | ||||
|                             <span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span> | ||||
|  | @ -86,7 +86,7 @@ | |||
|                     </a-tooltip> | ||||
|                 </span> | ||||
|                 <a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss" | ||||
|                                 :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''" | ||||
|                                 :dropdown-class-name="themeSwitcher.darkCardClass" | ||||
|                                 v-model="client._expiryTime" style="width: 170px;"></a-date-picker> | ||||
|             </a-form-item> | ||||
|         </a-collapse-panel> | ||||
|  | @ -106,7 +106,7 @@ | |||
| </a-form> | ||||
| <a-form layout="inline"> | ||||
|     <a-form-item label='{{ i18n "encryption" }}'> | ||||
|         <a-select v-model="inbound.settings.method" style="width: 250px;" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"> | ||||
|         <a-select v-model="inbound.settings.method" style="width: 250px;" :dropdown-class-name="themeSwitcher.darkCardClass"> | ||||
|             <a-select-option v-for="method in SSMethods" :value="method">[[ method ]]</a-select-option> | ||||
|         </a-select> | ||||
|     </a-form-item> | ||||
|  | @ -114,7 +114,7 @@ | |||
|         <a-input v-model.trim="inbound.settings.password" style="width: 250px;"></a-input> | ||||
|     </a-form-item> | ||||
|     <a-form-item label='{{ i18n "pages.inbounds.network" }}'> | ||||
|         <a-select v-model="inbound.settings.network" style="width: 100px;" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"> | ||||
|         <a-select v-model="inbound.settings.network" style="width: 100px;" :dropdown-class-name="themeSwitcher.darkCardClass"> | ||||
|             <a-select-option value="tcp,udp">TCP+UDP</a-select-option> | ||||
|             <a-select-option value="tcp">TCP</a-select-option> | ||||
|             <a-select-option value="udp">UDP</a-select-option> | ||||
|  |  | |||
|  | @ -17,8 +17,7 @@ | |||
|     <a-form-item label='{{ i18n "pages.inbounds.enable" }} udp'> | ||||
|         <a-switch v-model="inbound.settings.udp"></a-switch> | ||||
|     </a-form-item> | ||||
|     <a-form-item v-if="inbound.settings.udp" | ||||
|                  label="IP"> | ||||
|     <a-form-item v-if="inbound.settings.udp" label="IP"> | ||||
|         <a-input v-model.trim="inbound.settings.ip"></a-input> | ||||
|     </a-form-item> | ||||
| </a-form> | ||||
|  |  | |||
|  | @ -55,14 +55,14 @@ | |||
|             </a-form-item> | ||||
|             <br> | ||||
|             <a-form-item v-if="inbound.xtls" label="Flow"> | ||||
|                 <a-select v-model="client.flow" style="width: 150px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"> | ||||
|                 <a-select v-model="client.flow" style="width: 150px" :dropdown-class-name="themeSwitcher.darkCardClass"> | ||||
|                     <a-select-option value="">{{ i18n "none" }}</a-select-option> | ||||
|                     <a-select-option v-for="key in XTLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option> | ||||
|                 </a-select> | ||||
|             </a-form-item> | ||||
|             <a-form-item> | ||||
|                 <span slot="label"> | ||||
|                     <span >{{ i18n "pages.inbounds.totalFlow" }}</span> (GB) | ||||
|                     <span>{{ i18n "pages.inbounds.totalFlow" }}</span> (GB) | ||||
|                     <a-tooltip> | ||||
|                         <template slot="title"> | ||||
|                             0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span> | ||||
|  | @ -82,7 +82,7 @@ | |||
|             </a-form-item> | ||||
|             <a-form-item v-else> | ||||
|                 <span slot="label"> | ||||
|                     <span >{{ i18n "pages.inbounds.expireDate" }}</span> | ||||
|                     <span>{{ i18n "pages.inbounds.expireDate" }}</span> | ||||
|                     <a-tooltip> | ||||
|                         <template slot="title"> | ||||
|                             <span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span> | ||||
|  | @ -91,7 +91,7 @@ | |||
|                     </a-tooltip> | ||||
|                 </span> | ||||
|                 <a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss" | ||||
|                                 :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''" | ||||
|                                 :dropdown-class-name="themeSwitcher.darkCardClass" | ||||
|                                 v-model="client._expiryTime" style="width: 170px;"></a-date-picker> | ||||
|             </a-form-item> | ||||
|         </a-collapse-panel> | ||||
|  | @ -113,8 +113,7 @@ | |||
|     <a-form layout="inline"> | ||||
|         <a-form-item label="Fallbacks"> | ||||
|             <a-row> | ||||
|                 <a-button type="primary" size="small" | ||||
|                         @click="inbound.settings.addTrojanFallback()"> | ||||
|                 <a-button type="primary" size="small" @click="inbound.settings.addTrojanFallback()"> | ||||
|                     + | ||||
|                 </a-button> | ||||
|             </a-row> | ||||
|  |  | |||
|  | @ -15,7 +15,7 @@ | |||
|                 <a-input v-model.trim="client.email" style="width: 150px;"></a-input> | ||||
|             </a-form-item> | ||||
|             <a-form-item label="ID"> | ||||
|                 <a-input v-model.trim="client.id"  style="width: 300px;"></a-input> | ||||
|                 <a-input v-model.trim="client.id" style="width: 300px;"></a-input> | ||||
|             </a-form-item> | ||||
|             <a-form-item v-if="client.email"> | ||||
|                 <span slot="label"> | ||||
|  | @ -55,20 +55,20 @@ | |||
|             </a-form-item> | ||||
|             <br> | ||||
|             <a-form-item v-if="inbound.xtls" label="Flow"> | ||||
|                 <a-select v-model="inbound.settings.vlesses[index].flow" style="width: 200px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"> | ||||
|                 <a-select v-model="inbound.settings.vlesses[index].flow" style="width: 200px" :dropdown-class-name="themeSwitcher.darkCardClass"> | ||||
|                     <a-select-option value="" selected>{{ i18n "none" }}</a-select-option> | ||||
|                     <a-select-option v-for="key in XTLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option> | ||||
|                 </a-select> | ||||
|             </a-form-item> | ||||
|             <a-form-item v-else-if="inbound.canEnableTlsFlow()" label="Flow"> | ||||
|                 <a-select v-model="inbound.settings.vlesses[index].flow" style="width: 200px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"> | ||||
|                 <a-select v-model="inbound.settings.vlesses[index].flow" style="width: 200px" :dropdown-class-name="themeSwitcher.darkCardClass"> | ||||
|                     <a-select-option value="" selected>{{ i18n "none" }}</a-select-option> | ||||
|                     <a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option> | ||||
|                 </a-select> | ||||
|             </a-form-item> | ||||
|             <a-form-item> | ||||
|                 <span slot="label"> | ||||
|                     <span >{{ i18n "pages.inbounds.totalFlow" }}</span> (GB) | ||||
|                     <span>{{ i18n "pages.inbounds.totalFlow" }}</span> (GB) | ||||
|                     <a-tooltip> | ||||
|                         <template slot="title"> | ||||
|                             0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span> | ||||
|  | @ -88,7 +88,7 @@ | |||
|             </a-form-item> | ||||
|             <a-form-item v-else> | ||||
|                 <span slot="label"> | ||||
|                     <span >{{ i18n "pages.inbounds.expireDate" }}</span> | ||||
|                     <span>{{ i18n "pages.inbounds.expireDate" }}</span> | ||||
|                     <a-tooltip> | ||||
|                         <template slot="title"> | ||||
|                             <span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span> | ||||
|  | @ -97,7 +97,7 @@ | |||
|                     </a-tooltip> | ||||
|                 </span> | ||||
|                 <a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss" | ||||
|                                :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''" | ||||
|                                :dropdown-class-name="themeSwitcher.darkCardClass" | ||||
|                                v-model="client._expiryTime" style="width: 170px;"></a-date-picker> | ||||
|             </a-form-item> | ||||
|         </a-collapse-panel>      | ||||
|  | @ -119,8 +119,7 @@ | |||
|     <a-form layout="inline"> | ||||
|         <a-form-item label="Fallbacks"> | ||||
|             <a-row> | ||||
|                 <a-button type="primary" size="small" | ||||
|                         @click="inbound.settings.addFallback()"> | ||||
|                 <a-button type="primary" size="small" @click="inbound.settings.addFallback()"> | ||||
|                     + | ||||
|                 </a-button> | ||||
|             </a-row> | ||||
|  |  | |||
|  | @ -61,7 +61,7 @@ | |||
|             <br> | ||||
|             <a-form-item> | ||||
|                 <span slot="label"> | ||||
|                     <span >{{ i18n "pages.inbounds.totalFlow" }}</span> (GB) | ||||
|                     <span>{{ i18n "pages.inbounds.totalFlow" }}</span> (GB) | ||||
|                     <a-tooltip> | ||||
|                         <template slot="title"> | ||||
|                             0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span> | ||||
|  | @ -81,7 +81,7 @@ | |||
|             </a-form-item> | ||||
|             <a-form-item v-else> | ||||
|                 <span slot="label"> | ||||
|                     <span >{{ i18n "pages.inbounds.expireDate" }}</span> | ||||
|                     <span>{{ i18n "pages.inbounds.expireDate" }}</span> | ||||
|                     <a-tooltip> | ||||
|                         <template slot="title"> | ||||
|                             <span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span> | ||||
|  | @ -90,7 +90,7 @@ | |||
|                     </a-tooltip> | ||||
|                 </span> | ||||
|                 <a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss" | ||||
|                                 :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''" | ||||
|                                 :dropdown-class-name="themeSwitcher.darkCardClass" | ||||
|                                 v-model="client._expiryTime" style="width: 170px;"></a-date-picker> | ||||
|             </a-form-item> | ||||
|         </a-collapse-panel>      | ||||
|  |  | |||
|  | @ -1,21 +1,21 @@ | |||
| {{define "form/sniffing"}} | ||||
| <a-form layout="inline"> | ||||
|   <a-form-item> | ||||
|             <span slot="label"> | ||||
|                 Sniffing | ||||
|                 <a-tooltip> | ||||
|                     <template slot="title"> | ||||
|                         <span >{{ i18n "pages.inbounds.noRecommendKeepDefault" }}</span> | ||||
|                     </template> | ||||
|                     <a-icon type="question-circle" theme="filled"></a-icon> | ||||
|                 </a-tooltip> | ||||
|             </span> | ||||
|     <a-switch v-model="inbound.sniffing.enabled"></a-switch> | ||||
|   </a-form-item> | ||||
|   <a-form-item> | ||||
|     <a-checkbox-group v-model="inbound.sniffing.destOverride" v-if="inbound.sniffing.enabled"> | ||||
|       <a-checkbox v-for="key,value in SNIFFING_OPTION" :value="key">[[ value ]]</a-checkbox> | ||||
|     </a-checkbox-group> | ||||
|   </a-form-item> | ||||
|     <a-form-item> | ||||
|         <span slot="label"> | ||||
|             Sniffing | ||||
|             <a-tooltip> | ||||
|                 <template slot="title"> | ||||
|                     <span >{{ i18n "pages.inbounds.noRecommendKeepDefault" }}</span> | ||||
|                 </template> | ||||
|                 <a-icon type="question-circle" theme="filled"></a-icon> | ||||
|             </a-tooltip> | ||||
|         </span> | ||||
|         <a-switch v-model="inbound.sniffing.enabled"></a-switch> | ||||
|     </a-form-item> | ||||
|     <a-form-item> | ||||
|         <a-checkbox-group v-model="inbound.sniffing.destOverride" v-if="inbound.sniffing.enabled"> | ||||
|             <a-checkbox v-for="key,value in SNIFFING_OPTION" :value="key">[[ value ]]</a-checkbox> | ||||
|         </a-checkbox-group> | ||||
|     </a-form-item> | ||||
| </a-form> | ||||
| {{end}} | ||||
|  | @ -1,7 +1,7 @@ | |||
| {{define "form/streamKCP"}} | ||||
| <a-form layout="inline"> | ||||
|     <a-form-item label='{{ i18n "camouflage" }}'> | ||||
|         <a-select v-model="inbound.stream.kcp.type" style="width: 280px;" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"> | ||||
|         <a-select v-model="inbound.stream.kcp.type" style="width: 280px;" :dropdown-class-name="themeSwitcher.darkCardClass"> | ||||
|             <a-select-option value="none">None (Not Camouflage)</a-select-option> | ||||
|             <a-select-option value="srtp">SRTP (Camouflage Video Call)</a-select-option> | ||||
|             <a-select-option value="utp">UTP (Camouflage BT Download)</a-select-option> | ||||
|  |  | |||
|  | @ -1,7 +1,7 @@ | |||
| {{define "form/streamQUIC"}} | ||||
| <a-form layout="inline"> | ||||
|     <a-form-item label='{{ i18n "pages.inbounds.stream.quic.encryption" }}'> | ||||
|         <a-select v-model="inbound.stream.quic.security" style="width: 165px;" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"> | ||||
|         <a-select v-model="inbound.stream.quic.security" style="width: 165px;" :dropdown-class-name="themeSwitcher.darkCardClass"> | ||||
|             <a-select-option value="none">none</a-select-option> | ||||
|             <a-select-option value="aes-128-gcm">aes-128-gcm</a-select-option> | ||||
|             <a-select-option value="chacha20-poly1305">chacha20-poly1305</a-select-option> | ||||
|  | @ -11,7 +11,7 @@ | |||
|         <a-input v-model.trim="inbound.stream.quic.key"></a-input> | ||||
|     </a-form-item> | ||||
|     <a-form-item label='{{ i18n "camouflage" }}'> | ||||
|         <a-select v-model="inbound.stream.quic.type" style="width: 280px;" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"> | ||||
|         <a-select v-model="inbound.stream.quic.type" style="width: 280px;" :dropdown-class-name="themeSwitcher.darkCardClass"> | ||||
|             <a-select-option value="none">none (not camouflage)</a-select-option> | ||||
|             <a-select-option value="srtp">srtp (camouflage video call)</a-select-option> | ||||
|             <a-select-option value="utp">utp (camouflage BT download)</a-select-option> | ||||
|  |  | |||
|  | @ -2,7 +2,7 @@ | |||
| <!-- select stream network --> | ||||
| <a-form layout="inline"> | ||||
|     <a-form-item label='{{ i18n "transmission" }}'> | ||||
|         <a-select v-model="inbound.stream.network" @change="streamNetworkChange" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"> | ||||
|         <a-select v-model="inbound.stream.network" @change="streamNetworkChange" :dropdown-class-name="themeSwitcher.darkCardClass"> | ||||
|             <a-select-option value="tcp">TCP</a-select-option> | ||||
|             <a-select-option value="kcp">KCP</a-select-option> | ||||
|             <a-select-option value="ws">WS</a-select-option> | ||||
|  |  | |||
|  | @ -13,8 +13,7 @@ | |||
| </a-form> | ||||
| 
 | ||||
| <!-- tcp request --> | ||||
| <a-form v-if="inbound.stream.tcp.type === 'http'" | ||||
|         layout="inline"> | ||||
| <a-form v-if="inbound.stream.tcp.type === 'http'" layout="inline"> | ||||
|     <a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestVersion" }}'> | ||||
|         <a-input v-model.trim="inbound.stream.tcp.request.version"></a-input> | ||||
|     </a-form-item> | ||||
|  | @ -28,8 +27,7 @@ | |||
|     </a-form-item> | ||||
|     <a-form-item label='{{ i18n "pages.inbounds.stream.general.requestHeader" }}'> | ||||
|         <a-row> | ||||
|             <a-button size="small" | ||||
|                       @click="inbound.stream.tcp.request.addHeader('Host', 'xxx.com')"> | ||||
|             <a-button size="small" @click="inbound.stream.tcp.request.addHeader('Host', 'xxx.com')"> | ||||
|                 + | ||||
|             </a-button> | ||||
|         </a-row> | ||||
|  | @ -39,8 +37,7 @@ | |||
|             <a-input style="width: 50%" v-model.trim="header.value" | ||||
|                      addon-before='{{ i18n "pages.inbounds.stream.general.value" }}'> | ||||
|                 <template slot="addonAfter"> | ||||
|                     <a-button size="small" | ||||
|                               @click="inbound.stream.tcp.request.removeHeader(index)"> | ||||
|                     <a-button size="small" @click="inbound.stream.tcp.request.removeHeader(index)"> | ||||
|                         - | ||||
|                     </a-button> | ||||
|                 </template> | ||||
|  | @ -50,8 +47,7 @@ | |||
| </a-form> | ||||
| 
 | ||||
| <!-- tcp response --> | ||||
| <a-form v-if="inbound.stream.tcp.type === 'http'" | ||||
|         layout="inline"> | ||||
| <a-form v-if="inbound.stream.tcp.type === 'http'" layout="inline"> | ||||
|     <a-form-item label='{{ i18n "pages.inbounds.stream.tcp.responseVersion" }}'> | ||||
|         <a-input v-model.trim="inbound.stream.tcp.response.version"></a-input> | ||||
|     </a-form-item> | ||||
|  | @ -63,8 +59,7 @@ | |||
|     </a-form-item> | ||||
|     <a-form-item label='{{ i18n "pages.inbounds.stream.tcp.responseHeader" }}'> | ||||
|         <a-row> | ||||
|             <a-button size="small" | ||||
|                       @click="inbound.stream.tcp.response.addHeader('Content-Type', 'application/octet-stream')"> | ||||
|             <a-button size="small" @click="inbound.stream.tcp.response.addHeader('Content-Type', 'application/octet-stream')"> | ||||
|                 + | ||||
|             </a-button> | ||||
|         </a-row> | ||||
|  | @ -74,8 +69,7 @@ | |||
|             <a-input style="width: 50%" v-model.trim="header.value" | ||||
|                      addon-before='{{ i18n "pages.inbounds.stream.general.value" }}'> | ||||
|                 <template slot="addonAfter"> | ||||
|                     <a-button size="small" | ||||
|                               @click="inbound.stream.tcp.response.removeHeader(index)"> | ||||
|                     <a-button size="small" @click="inbound.stream.tcp.response.removeHeader(index)"> | ||||
|                         - | ||||
|                     </a-button> | ||||
|                 </template> | ||||
|  |  | |||
|  | @ -10,8 +10,7 @@ | |||
|     </a-form-item> | ||||
|     <a-form-item label='{{ i18n "pages.inbounds.stream.general.requestHeader" }}'> | ||||
|         <a-row> | ||||
|             <a-button size="small" | ||||
|                       @click="inbound.stream.ws.addHeader('Host', '')"> | ||||
|             <a-button size="small" @click="inbound.stream.ws.addHeader('Host', '')"> | ||||
|                 + | ||||
|             </a-button> | ||||
|         </a-row> | ||||
|  | @ -21,8 +20,7 @@ | |||
|             <a-input style="width: 50%" v-model.trim="header.value" | ||||
|                      addon-before='{{ i18n "pages.inbounds.stream.general.value" }}'> | ||||
|                 <template slot="addonAfter"> | ||||
|                     <a-button size="small" | ||||
|                               @click="inbound.stream.ws.removeHeader(index)"> | ||||
|                     <a-button size="small" @click="inbound.stream.ws.removeHeader(index)"> | ||||
|                         - | ||||
|                     </a-button> | ||||
|                 </template> | ||||
|  |  | |||
|  | @ -37,18 +37,18 @@ | |||
|         <a-input v-model.trim="inbound.stream.tls.server" style="width: 250px"></a-input> | ||||
|     </a-form-item> | ||||
|     <a-form-item label="CipherSuites"> | ||||
|         <a-select v-model="inbound.stream.tls.cipherSuites" style="width: 300px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"> | ||||
|         <a-select v-model="inbound.stream.tls.cipherSuites" style="width: 300px" :dropdown-class-name="themeSwitcher.darkCardClass"> | ||||
|             <a-select-option value="">auto</a-select-option> | ||||
|             <a-select-option v-for="key in TLS_CIPHER_OPTION" :value="key">[[ key ]]</a-select-option> | ||||
|         </a-select> | ||||
|     </a-form-item> | ||||
|     <a-form-item label="MinVersion"> | ||||
|         <a-select v-model="inbound.stream.tls.minVersion" style="width: 60px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"> | ||||
|         <a-select v-model="inbound.stream.tls.minVersion" style="width: 60px" :dropdown-class-name="themeSwitcher.darkCardClass"> | ||||
|             <a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option> | ||||
|         </a-select> | ||||
|     </a-form-item> | ||||
|     <a-form-item label="MaxVersion"> | ||||
|         <a-select v-model="inbound.stream.tls.maxVersion" style="width: 60px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"> | ||||
|         <a-select v-model="inbound.stream.tls.maxVersion" style="width: 60px" :dropdown-class-name="themeSwitcher.darkCardClass"> | ||||
|             <a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option> | ||||
|         </a-select> | ||||
|     </a-form-item> | ||||
|  | @ -57,7 +57,7 @@ | |||
|     </a-form-item> | ||||
|     <a-form-item label="uTLS"> | ||||
|         <a-select v-model="inbound.stream.tls.settings.fingerprint" | ||||
|                   style="width: 170px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"> | ||||
|                   style="width: 170px" :dropdown-class-name="themeSwitcher.darkCardClass"> | ||||
|             <a-select-option value=''>None</a-select-option> | ||||
|             <a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option> | ||||
|         </a-select> | ||||
|  | @ -145,9 +145,9 @@ | |||
|     <a-form-item label="xVer"> | ||||
|         <a-input-number v-model="inbound.stream.reality.xver" :min="0" style="width: 60px"></a-input-number> | ||||
|     </a-form-item> | ||||
|     <a-form-item label="uTLS" > | ||||
|     <a-form-item label="uTLS"> | ||||
|         <a-select v-model="inbound.stream.reality.settings.fingerprint"  | ||||
|                     style="width: 135px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"> | ||||
|                     style="width: 135px" :dropdown-class-name="themeSwitcher.darkCardClass"> | ||||
|             <a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option> | ||||
|         </a-select> | ||||
|     </a-form-item> | ||||
|  | @ -169,7 +169,7 @@ | |||
|     <a-form-item label="Public Key"> | ||||
|         <a-input v-model.trim="inbound.stream.reality.settings.publicKey" style="width: 300px"></a-input> | ||||
|     </a-form-item> | ||||
|     <a-form-item > | ||||
|     <a-form-item> | ||||
|         <a-button type="primary" icon="import" @click="getNewX25519Cert">Get New Key</a-button> | ||||
|     </a-form-item> | ||||
| </a-form> | ||||
|  |  | |||
|  | @ -29,10 +29,12 @@ | |||
|     <a-tag v-if="!isClientEnabled(record, client.email)" color="red">{{ i18n "depleted" }}</a-tag> | ||||
| </template>                                     | ||||
| <template slot="traffic" slot-scope="text, client"> | ||||
|     <a-tag color="blue">[[ sizeFormat(getUpStats(record, client.email)) ]] / [[ sizeFormat(getDownStats(record, client.email)) ]]</a-tag> | ||||
|     <a-tag color="blue"> | ||||
|         [[ sizeFormat(getUpStats(record, client.email)) ]] / [[ sizeFormat(getDownStats(record, client.email)) ]] | ||||
|     </a-tag> | ||||
|     <template v-if="client._totalGB > 0"> | ||||
|         <a-tag v-if="isTrafficExhausted(record, client.email)" color="red">[[client._totalGB]]GB</a-tag> | ||||
|         <a-tag v-else color="cyan">[[client._totalGB]]GB</a-tag> | ||||
|         <a-tag v-if="isTrafficExhausted(record, client.email)" color="red">[[client._totalGB]] GB</a-tag> | ||||
|         <a-tag v-else color="cyan">[[client._totalGB]] GB</a-tag> | ||||
|     </template> | ||||
|     <a-tag v-else color="green">{{ i18n "indefinite" }}</a-tag> | ||||
| </template>                                     | ||||
|  | @ -42,7 +44,9 @@ | |||
|             [[ DateUtil.formatMillis(client._expiryTime) ]] | ||||
|         </a-tag> | ||||
|     </template> | ||||
|     <a-tag v-else-if="client.expiryTime < 0" color="cyan">[[ client._expiryTime ]] {{ i18n "pages.client.days" }}</a-tag> | ||||
|     <a-tag v-else-if="client.expiryTime < 0" color="cyan"> | ||||
|         [[ client._expiryTime ]] {{ i18n "pages.client.days" }} | ||||
|     </a-tag> | ||||
|     <a-tag v-else color="green">{{ i18n "indefinite" }}</a-tag> | ||||
| </template> | ||||
| {{end}} | ||||
|  | @ -3,62 +3,66 @@ | |||
|     v-model="infoModal.visible" title='{{ i18n "pages.inbounds.details"}}' | ||||
|     :closable="true" | ||||
|     :mask-closable="true" | ||||
|     :class="siderDrawer.isDarkTheme ? darkClass : ''" | ||||
|     :class="themeSwitcher.darkCardClass" | ||||
|     :footer="null" | ||||
|     width="600px" | ||||
|     > | ||||
|     <table style="margin-bottom: 10px; width: 100%;"> | ||||
|         <tr><td> | ||||
|             <table> | ||||
|                 <tr><td>{{ i18n "protocol" }}</td><td><a-tag color="green">[[ dbInbound.protocol ]]</a-tag></td></tr> | ||||
|                 <tr><td>{{ i18n "pages.inbounds.address" }}</td><td><a-tag color="blue">[[ dbInbound.address ]]</a-tag></td></tr> | ||||
|                 <tr><td>{{ i18n "pages.inbounds.port" }}</td><td><a-tag color="green">[[ dbInbound.port ]]</a-tag></td></tr> | ||||
|             </table> | ||||
|         </td> | ||||
|         <td v-if="dbInbound.isVMess || dbInbound.isVLess || dbInbound.isTrojan || dbInbound.isSS"> | ||||
|             <table> | ||||
|                 <tr> | ||||
|                     <td>{{ i18n "transmission" }}</td><td><a-tag color="green">[[ inbound.network ]]</a-tag></td> | ||||
|                 </tr> | ||||
|             <template v-if="inbound.isTcp || inbound.isWs || inbound.isH2"> | ||||
|                 <tr v-if="inbound.host"><td>{{ i18n "host" }}</td><td><a-tag color="green">[[ inbound.host ]]</a-tag></td></tr> | ||||
|                 <tr v-else><td>{{ i18n "host" }}</td><td><a-tag color="orange">{{ i18n "none" }}</a-tag></td></tr> | ||||
|              | ||||
|                 <tr v-if="inbound.path"><td>{{ i18n "path" }}</td><td><a-tag color="green">[[ inbound.path ]]</a-tag></td></tr> | ||||
|                 <tr v-else><td>{{ i18n "path" }}</td><td><a-tag color="orange">{{ i18n "none" }}</a-tag></td></tr> | ||||
|             </template> | ||||
|              | ||||
|             <template v-if="inbound.isQuic"> | ||||
|                 <tr><td>quic {{ i18n "encryption" }}</td><td><a-tag color="green">[[ inbound.quicSecurity ]]</a-tag></td></tr> | ||||
|                 <tr><td>quic {{ i18n "password" }}</td><td><a-tag color="green">[[ inbound.quicKey ]]</a-tag></td></tr> | ||||
|                 <tr><td>quic {{ i18n "camouflage" }}</td><td><a-tag color="green">[[ inbound.quicType ]]</a-tag></td></tr> | ||||
|             </template> | ||||
|              | ||||
|             <template v-if="inbound.isKcp"> | ||||
|                 <tr><td>kcp {{ i18n "encryption" }}</td><td><a-tag color="green">[[ inbound.kcpType ]]</a-tag></td></tr> | ||||
|                 <tr><td>kcp {{ i18n "password" }}</td><td><a-tag color="green">[[ inbound.kcpSeed ]]</a-tag></td></tr> | ||||
|             </template> | ||||
|              | ||||
|             <template v-if="inbound.isGrpc"> | ||||
|                 <tr><td>grpc serviceName</td><td><a-tag color="green">[[ inbound.serviceName ]]</a-tag></td></tr> | ||||
|                 <tr><td>grpc multiMode</td><td><a-tag color="green">[[ inbound.stream.grpc.multiMode ]]</a-tag></td></tr> | ||||
|             </template> | ||||
|             </table> | ||||
|         </td></tr> | ||||
|             <tr colspan="2" v-if="dbInbound.hasLink()"> | ||||
|                 <td v-if="inbound.tls"> | ||||
|                     tls: <a-tag color="green">{{ i18n "enabled" }}</a-tag><br /> | ||||
|                     tls {{ i18n "domainName" }}: <a-tag :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : '' ]]</a-tag> | ||||
|                 </td> | ||||
|                 <td v-else-if="inbound.xtls"> | ||||
|                     xtls: <a-tag color="green">{{ i18n "enabled" }}</a-tag><br /> | ||||
|                     xtls {{ i18n "domainName" }}: <a-tag :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : '' ]]</a-tag> | ||||
|                 </td> | ||||
|                 <td v-else-if="inbound.reality"> | ||||
|                     reality: <a-tag color="green">{{ i18n "enabled" }}</a-tag><br /> | ||||
|                     reality {{ i18n "domainName" }}: <a-tag :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : '' ]]</a-tag> | ||||
|                 </td> | ||||
|                 <td v-else>tls: <a-tag color="red">{{ i18n "disabled" }}</a-tag> | ||||
|         <tr> | ||||
|             <td> | ||||
|                 <table> | ||||
|                     <tr><td>{{ i18n "protocol" }}</td><td><a-tag color="green">[[ dbInbound.protocol ]]</a-tag></td></tr> | ||||
|                     <tr><td>{{ i18n "pages.inbounds.address" }}</td><td><a-tag color="blue">[[ dbInbound.address ]]</a-tag></td></tr> | ||||
|                     <tr><td>{{ i18n "pages.inbounds.port" }}</td><td><a-tag color="green">[[ dbInbound.port ]]</a-tag></td></tr> | ||||
|                 </table> | ||||
|             </td> | ||||
|             <td v-if="dbInbound.isVMess || dbInbound.isVLess || dbInbound.isTrojan || dbInbound.isSS"> | ||||
|                 <table> | ||||
|                     <tr> | ||||
|                         <td>{{ i18n "transmission" }}</td><td><a-tag color="green">[[ inbound.network ]]</a-tag></td> | ||||
|                     </tr> | ||||
| 
 | ||||
|                     <template v-if="inbound.isTcp || inbound.isWs || inbound.isH2"> | ||||
|                         <tr v-if="inbound.host"><td>{{ i18n "host" }}</td><td><a-tag color="green">[[ inbound.host ]]</a-tag></td></tr> | ||||
|                         <tr v-else><td>{{ i18n "host" }}</td><td><a-tag color="orange">{{ i18n "none" }}</a-tag></td></tr> | ||||
| 
 | ||||
|                         <tr v-if="inbound.path"><td>{{ i18n "path" }}</td><td><a-tag color="green">[[ inbound.path ]]</a-tag></td></tr> | ||||
|                         <tr v-else><td>{{ i18n "path" }}</td><td><a-tag color="orange">{{ i18n "none" }}</a-tag></td></tr> | ||||
|                     </template> | ||||
| 
 | ||||
|                     <template v-if="inbound.isQuic"> | ||||
|                         <tr><td>quic {{ i18n "encryption" }}</td><td><a-tag color="green">[[ inbound.quicSecurity ]]</a-tag></td></tr> | ||||
|                         <tr><td>quic {{ i18n "password" }}</td><td><a-tag color="green">[[ inbound.quicKey ]]</a-tag></td></tr> | ||||
|                         <tr><td>quic {{ i18n "camouflage" }}</td><td><a-tag color="green">[[ inbound.quicType ]]</a-tag></td></tr> | ||||
|                     </template> | ||||
| 
 | ||||
|                     <template v-if="inbound.isKcp"> | ||||
|                         <tr><td>kcp {{ i18n "encryption" }}</td><td><a-tag color="green">[[ inbound.kcpType ]]</a-tag></td></tr> | ||||
|                         <tr><td>kcp {{ i18n "password" }}</td><td><a-tag color="green">[[ inbound.kcpSeed ]]</a-tag></td></tr> | ||||
|                     </template> | ||||
| 
 | ||||
|                     <template v-if="inbound.isGrpc"> | ||||
|                         <tr><td>grpc serviceName</td><td><a-tag color="green">[[ inbound.serviceName ]]</a-tag></td></tr> | ||||
|                         <tr><td>grpc multiMode</td><td><a-tag color="green">[[ inbound.stream.grpc.multiMode ]]</a-tag></td></tr> | ||||
|                     </template> | ||||
|                 </table> | ||||
|             </td> | ||||
|         </tr> | ||||
|         <tr colspan="2" v-if="dbInbound.hasLink()"> | ||||
|             <td v-if="inbound.tls"> | ||||
|                 tls: <a-tag color="green">{{ i18n "enabled" }}</a-tag><br /> | ||||
|                 tls {{ i18n "domainName" }}: <a-tag :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : '' ]]</a-tag> | ||||
|             </td> | ||||
|             <td v-else-if="inbound.xtls"> | ||||
|                 xtls: <a-tag color="green">{{ i18n "enabled" }}</a-tag><br /> | ||||
|                 xtls {{ i18n "domainName" }}: <a-tag :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : '' ]]</a-tag> | ||||
|             </td> | ||||
|             <td v-else-if="inbound.reality"> | ||||
|                 reality: <a-tag color="green">{{ i18n "enabled" }}</a-tag><br /> | ||||
|                 reality {{ i18n "domainName" }}: <a-tag :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : '' ]]</a-tag> | ||||
|             </td> | ||||
|             <td v-else> | ||||
|                 tls: <a-tag color="red">{{ i18n "disabled" }}</a-tag> | ||||
|             </td> | ||||
|         </tr> | ||||
|     </table> | ||||
|  | @ -124,7 +128,8 @@ | |||
|                 <th>{{ i18n "encryption" }}</th> | ||||
|                 <th>{{ i18n "password" }}</th> | ||||
|                 <th>{{ i18n "pages.inbounds.network" }}</th> | ||||
|             </tr><tr> | ||||
|             </tr> | ||||
|             <tr> | ||||
|                 <td><a-tag color="green">[[ inbound.settings.method ]]</a-tag></td> | ||||
|                 <td><a-tag color="blue">[[ inbound.settings.password ]]</a-tag></td> | ||||
|                 <td><a-tag color="green">[[ inbound.settings.network ]]</a-tag></td> | ||||
|  | @ -136,7 +141,8 @@ | |||
|                 <th>{{ i18n "pages.inbounds.destinationPort" }}</th> | ||||
|                 <th>{{ i18n "pages.inbounds.network" }}</th> | ||||
|                 <th>FollowRedirect</th> | ||||
|             </tr><tr> | ||||
|             </tr> | ||||
|             <tr> | ||||
|                 <td><a-tag color="green">[[ inbound.settings.address ]]</a-tag></td> | ||||
|                 <td><a-tag color="blue">[[ inbound.settings.port ]]</a-tag></td> | ||||
|                 <td><a-tag color="green">[[ inbound.settings.network ]]</a-tag></td> | ||||
|  | @ -149,15 +155,18 @@ | |||
|                 <th>{{ i18n "password" }} Auth</th> | ||||
|                 <th>{{ i18n "pages.inbounds.enable" }} udp</th> | ||||
|                 <th>IP</th> | ||||
|             </tr><tr> | ||||
|             </tr> | ||||
|             <tr> | ||||
|                 <td><a-tag color="green">[[ inbound.settings.auth ]]</a-tag></td> | ||||
|                 <td><a-tag color="blue">[[ inbound.settings.udp]]</a-tag></td> | ||||
|                 <td><a-tag color="green">[[ inbound.settings.ip ]]</a-tag></td> | ||||
|             </tr><tr v-if="inbound.settings.auth == 'password'"> | ||||
|             </tr> | ||||
|             <tr v-if="inbound.settings.auth == 'password'"> | ||||
|                 <td> </td> | ||||
|                 <td>{{ i18n "username" }}</td> | ||||
|                 <td>{{ i18n "password" }}</td> | ||||
|             </tr><tr v-for="account,index in inbound.settings.accounts"> | ||||
|             </tr> | ||||
|             <tr v-for="account,index in inbound.settings.accounts"> | ||||
|                 <td><a-tag color="green">[[ index ]]</a-tag></td> | ||||
|                 <td><a-tag color="blue">[[ account.user ]]</a-tag></td> | ||||
|                 <td><a-tag color="green">[[ account.pass ]]</a-tag></td> | ||||
|  | @ -169,7 +178,8 @@ | |||
|                 <th> </th> | ||||
|                 <th>{{ i18n "username" }}</th> | ||||
|                 <th>{{ i18n "password" }}</th> | ||||
|             </tr><tr v-for="account,index in inbound.settings.accounts"> | ||||
|             </tr> | ||||
|             <tr v-for="account,index in inbound.settings.accounts"> | ||||
|                 <td><a-tag color="green">[[ index ]]</a-tag></td> | ||||
|                 <td><a-tag color="blue">[[ account.user ]]</a-tag></td> | ||||
|                 <td><a-tag color="green">[[ account.pass ]]</a-tag></td> | ||||
|  | @ -184,6 +194,7 @@ | |||
|     </div> | ||||
| </a-modal> | ||||
| <script> | ||||
| 
 | ||||
|     const infoModal = { | ||||
|         visible: false, | ||||
|         inbound: new Inbound(), | ||||
|  | @ -233,42 +244,41 @@ | |||
|                 return this.infoModal.inbound; | ||||
|             }, | ||||
|             get isActive() { | ||||
|                 if(infoModal.clientStats){ | ||||
|                 if (infoModal.clientStats) { | ||||
|                     return infoModal.clientStats.enable; | ||||
|                 } | ||||
|                 return infoModal.dbInbound.isEnable; | ||||
|             }, | ||||
|             get isEnable() { | ||||
|                 if(infoModal.clientSettings){ | ||||
|                 if (infoModal.clientSettings) { | ||||
|                     return infoModal.clientSettings.enable; | ||||
|                 } | ||||
|                 return infoModal.dbInbound.isEnable; | ||||
|             }, | ||||
|             get subBase() { | ||||
|                 return window.location.protocol + "//" + window.location.hostname + (window.location.port ? ":" + window.location.port:"") + basePath + "sub/"; | ||||
|                 return window.location.protocol + "//" + window.location.hostname + (window.location.port ? ":" + window.location.port : "") + basePath + "sub/"; | ||||
|             }, | ||||
|             get tgBase() { | ||||
|                 return "https://t.me/" | ||||
|             }, | ||||
|         }, | ||||
|         methods: { | ||||
|             copyTextToClipboard(elmentId,content) { | ||||
|             copyTextToClipboard(elmentId, content) { | ||||
|                 this.infoModal.clipboard = new ClipboardJS('#' + elmentId, { | ||||
|                         text: () => content, | ||||
|                     }); | ||||
|                 this.infoModal.clipboard.on('success', () => {  | ||||
|                     text: () => content, | ||||
|                 }); | ||||
|                 this.infoModal.clipboard.on('success', () => { | ||||
|                     app.$message.success('{{ i18n "copied" }}') | ||||
|                     this.infoModal.clipboard.destroy(); | ||||
|                 }); | ||||
|             }, | ||||
|             statsColor(stats) { | ||||
|                 if(!stats) return 'blue' | ||||
|                 if(stats['total'] === 0) return 'blue' | ||||
|                 else if(stats['total'] > 0 && (stats['down']+stats['up']) < stats['total']) return 'cyan' | ||||
|                 if (!stats) return 'blue' | ||||
|                 if (stats['total'] === 0) return 'blue' | ||||
|                 else if (stats['total'] > 0 && (stats['down'] + stats['up']) < stats['total']) return 'cyan' | ||||
|                 else return 'red' | ||||
|             } | ||||
|         }, | ||||
|          | ||||
|     }); | ||||
| 
 | ||||
| </script> | ||||
|  |  | |||
|  | @ -1,7 +1,7 @@ | |||
| {{define "inboundModal"}} | ||||
| <a-modal id="inbound-modal" v-model="inModal.visible" :title="inModal.title" @ok="inModal.ok" | ||||
|          :confirm-loading="inModal.confirmLoading" :closable="true" :mask-closable="false" | ||||
|          :class="siderDrawer.isDarkTheme ? darkClass : ''" | ||||
|          :class="themeSwitcher.darkCardClass" | ||||
|          :ok-text="inModal.okText" cancel-text='{{ i18n "close" }}'> | ||||
|     {{template "form/inbound"}} | ||||
| </a-modal> | ||||
|  | @ -19,7 +19,7 @@ | |||
|         ok() { | ||||
|             ObjectUtil.execute(inModal.confirm, inModal.inbound, inModal.dbInbound); | ||||
|         }, | ||||
|         show({ title='', okText='{{ i18n "sure" }}', inbound=null, dbInbound=null, confirm=(inbound, dbInbound)=>{}, isEdit=false  }) { | ||||
|         show({ title = '', okText = '{{ i18n "sure" }}', inbound = null, dbInbound = null, confirm = (inbound, dbInbound) => {}, isEdit = false }) { | ||||
|             this.title = title; | ||||
|             this.okText = okText; | ||||
|             if (inbound) { | ||||
|  | @ -44,7 +44,7 @@ | |||
|             inModal.confirmLoading = loading; | ||||
|         }, | ||||
|         getClients(protocol, clientSettings) { | ||||
|             switch(protocol){ | ||||
|             switch (protocol) { | ||||
|                 case Protocols.VMESS: return clientSettings.vmesses; | ||||
|                 case Protocols.VLESS: return clientSettings.vlesses; | ||||
|                 case Protocols.TROJAN: return clientSettings.trojans; | ||||
|  | @ -87,7 +87,7 @@ | |||
|             get delayedExpireDays() { | ||||
|                 return this.client && this.client.expiryTime < 0 ? this.client.expiryTime / -86400000 : 0; | ||||
|             }, | ||||
|             set delayedExpireDays(days){ | ||||
|             set delayedExpireDays(days) { | ||||
|                 this.client.expiryTime = -86400000 * days; | ||||
|             }, | ||||
|         }, | ||||
|  | @ -100,15 +100,15 @@ | |||
|                     this.inModal.inbound.reality = false; | ||||
|                 } | ||||
|             }, | ||||
|             setDefaultCertData(){ | ||||
|             setDefaultCertData() { | ||||
|                 inModal.inbound.stream.tls.certs[0].certFile = app.defaultCert; | ||||
|                 inModal.inbound.stream.tls.certs[0].keyFile = app.defaultKey; | ||||
|             }, | ||||
|             setDefaultCertXtls(){ | ||||
|             setDefaultCertXtls() { | ||||
|                 inModal.inbound.stream.xtls.certs[0].certFile = app.defaultCert; | ||||
|                 inModal.inbound.stream.xtls.certs[0].keyFile = app.defaultKey; | ||||
|             }, | ||||
|             async getNewX25519Cert(){ | ||||
|             async getNewX25519Cert() { | ||||
|                 inModal.loading(true); | ||||
|                 const msg = await HttpUtil.post('/server/getNewX25519Cert'); | ||||
|                 inModal.loading(false); | ||||
|  | @ -122,7 +122,7 @@ | |||
|                 var chars = 'abcdefghijklmnopqrstuvwxyz1234567890'; | ||||
|                 var string = ''; | ||||
|                 var len = 6 + Math.floor(Math.random() * 5); | ||||
|                 for(var ii=0; ii<len; ii++){ | ||||
|                 for (var ii = 0; ii < len; ii++) { | ||||
|                     string += chars[Math.floor(Math.random() * chars.length)]; | ||||
|                 } | ||||
|                 client.email = string; | ||||
|  |  | |||
|  | @ -12,10 +12,11 @@ | |||
|         margin-top: 10px; | ||||
|     } | ||||
| </style> | ||||
| 
 | ||||
| <body> | ||||
| <a-layout id="app" v-cloak> | ||||
|     {{ template "commonSider" . }} | ||||
|     <a-layout id="content-layout" :style="siderDrawer.isDarkTheme ? bgDarkStyle : ''"> | ||||
|     <a-layout id="content-layout" :style="themeSwitcher.bgStyle"> | ||||
|         <a-layout-content> | ||||
|             <a-spin :spinning="spinning" :delay="500" tip="loading"> | ||||
|                 <transition name="list" appear> | ||||
|  | @ -24,7 +25,7 @@ | |||
|                     </a-tag> | ||||
|                 </transition> | ||||
|                 <transition name="list" appear> | ||||
|                     <a-card hoverable style="margin-bottom: 20px;" :class="siderDrawer.isDarkTheme ? darkClass : ''"> | ||||
|                     <a-card hoverable style="margin-bottom: 20px;" :class="themeSwitcher.darkCardClass"> | ||||
|                         <a-row> | ||||
|                             <a-col :xs="24" :sm="24" :lg="12"> | ||||
|                                 {{ i18n "pages.inbounds.totalDownUp" }}: | ||||
|  | @ -41,19 +42,19 @@ | |||
|                             <a-col :xs="24" :sm="24" :lg="12"> | ||||
|                                 {{ i18n "clients" }}: | ||||
|                                 <a-tag color="green">[[ total.clients ]]</a-tag> | ||||
|                                 <a-popover title='{{ i18n "disabled" }}' :overlay-class-name="siderDrawer.isDarkTheme ? 'ant-dark' : ''"> | ||||
|                                 <a-popover title='{{ i18n "disabled" }}' :overlay-class-name="themeSwitcher.darkClass"> | ||||
|                                     <template slot="content"> | ||||
|                                         <p v-for="clientEmail in total.deactive">[[ clientEmail ]]</p> | ||||
|                                     </template> | ||||
|                                     <a-tag v-if="total.deactive.length">[[ total.deactive.length ]]</a-tag> | ||||
|                                 </a-popover> | ||||
|                                 <a-popover title='{{ i18n "depleted" }}' :overlay-class-name="siderDrawer.isDarkTheme ? 'ant-dark' : ''"> | ||||
|                                 <a-popover title='{{ i18n "depleted" }}' :overlay-class-name="themeSwitcher.darkClass"> | ||||
|                                     <template slot="content"> | ||||
|                                         <p v-for="clientEmail in total.depleted">[[ clientEmail ]]</p> | ||||
|                                     </template> | ||||
|                                     <a-tag color="red" v-if="total.depleted.length">[[ total.depleted.length ]]</a-tag> | ||||
|                                 </a-popover> | ||||
|                                 <a-popover title='{{ i18n "depletingSoon" }}' :overlay-class-name="siderDrawer.isDarkTheme ? 'ant-dark' : ''"> | ||||
|                                 <a-popover title='{{ i18n "depletingSoon" }}' :overlay-class-name="themeSwitcher.darkClass"> | ||||
|                                     <template slot="content"> | ||||
|                                         <p v-for="clientEmail in total.expiring">[[ clientEmail ]]</p> | ||||
|                                     </template> | ||||
|  | @ -64,14 +65,14 @@ | |||
|                     </a-card> | ||||
|                 </transition> | ||||
|                 <transition name="list" appear> | ||||
|                     <a-card hoverable :class="siderDrawer.isDarkTheme ? darkClass : ''"> | ||||
|                     <a-card hoverable :class="themeSwitcher.darkCardClass"> | ||||
|                         <div slot="title"> | ||||
|                             <a-row> | ||||
|                                 <a-col :xs="24" :sm="24" :lg="12"> | ||||
|                                     <a-button type="primary" icon="plus" @click="openAddInbound">{{ i18n "pages.inbounds.addInbound" }}</a-button> | ||||
|                                     <a-dropdown :trigger="['click']"> | ||||
|                                         <a-button type="primary" icon="menu">{{ i18n "pages.inbounds.generalActions" }}</a-button> | ||||
|                                         <a-menu slot="overlay" @click="a => generalActions(a)" :theme="siderDrawer.theme"> | ||||
|                                         <a-menu slot="overlay" @click="a => generalActions(a)" :theme="themeSwitcher.currentTheme"> | ||||
|                                             <a-menu-item key="export"> | ||||
|                                                 <a-icon type="export"></a-icon> | ||||
|                                                 {{ i18n "pages.inbounds.export" }} | ||||
|  | @ -96,7 +97,7 @@ | |||
|                                               style="width: 65px;" | ||||
|                                               v-if="isRefreshEnabled" | ||||
|                                               @change="changeRefreshInterval" | ||||
|                                               :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"> | ||||
|                                               :dropdown-class-name="themeSwitcher.darkCardClass"> | ||||
|                                         <a-select-option v-for="key in [5,10,30,60]" :value="key*1000">[[ key ]]s</a-select-option> | ||||
|                                     </a-select> | ||||
|                                     <a-icon type="sync" :spin="refreshing" @click="manualRefresh" style="margin: 0 5px;"></a-icon> | ||||
|  | @ -115,7 +116,7 @@ | |||
|                                 <a-icon type="edit" style="font-size: 22px" @click="openEditInbound(dbInbound.id);"></a-icon> | ||||
|                                 <a-dropdown :trigger="['click']"> | ||||
|                                     <a @click="e => e.preventDefault()">{{ i18n "pages.inbounds.operate" }}</a> | ||||
|                                     <a-menu slot="overlay" @click="a => clickAction(a, dbInbound)" :theme="siderDrawer.theme"> | ||||
|                                     <a-menu slot="overlay" @click="a => clickAction(a, dbInbound)" :theme="themeSwitcher.currentTheme"> | ||||
|                                         <a-menu-item key="edit"> | ||||
|                                             <a-icon type="edit"></a-icon> | ||||
|                                             {{ i18n "edit" }} | ||||
|  | @ -174,19 +175,19 @@ | |||
|                             <template slot="clients" slot-scope="text, dbInbound"> | ||||
|                                 <template v-if="clientCount[dbInbound.id]"> | ||||
|                                     <a-tag style="margin:0;" color="green">[[ clientCount[dbInbound.id].clients ]]</a-tag> | ||||
|                                     <a-popover title='{{ i18n "disabled" }}' :overlay-class-name="siderDrawer.isDarkTheme ? 'ant-dark' : ''"> | ||||
|                                     <a-popover title='{{ i18n "disabled" }}' :overlay-class-name="themeSwitcher.darkClass"> | ||||
|                                         <template slot="content"> | ||||
|                                             <p v-for="clientEmail in clientCount[dbInbound.id].deactive">[[ clientEmail ]]</p> | ||||
|                                         </template> | ||||
|                                         <a-tag style="margin:0; padding: 0 2px;" v-if="clientCount[dbInbound.id].deactive.length">[[ clientCount[dbInbound.id].deactive.length ]]</a-tag> | ||||
|                                     </a-popover> | ||||
|                                     <a-popover title='{{ i18n "depleted" }}' :overlay-class-name="siderDrawer.isDarkTheme ? 'ant-dark' : ''"> | ||||
|                                     <a-popover title='{{ i18n "depleted" }}' :overlay-class-name="themeSwitcher.darkClass"> | ||||
|                                         <template slot="content"> | ||||
|                                             <p v-for="clientEmail in clientCount[dbInbound.id].depleted">[[ clientEmail ]]</p> | ||||
|                                         </template> | ||||
|                                         <a-tag style="margin:0; padding: 0 2px;" color="red" v-if="clientCount[dbInbound.id].depleted.length">[[ clientCount[dbInbound.id].depleted.length ]]</a-tag> | ||||
|                                     </a-popover> | ||||
|                                     <a-popover title='{{ i18n "depletingSoon" }}' :overlay-class-name="siderDrawer.isDarkTheme ? 'ant-dark' : ''"> | ||||
|                                     <a-popover title='{{ i18n "depletingSoon" }}' :overlay-class-name="themeSwitcher.darkClass"> | ||||
|                                         <template slot="content"> | ||||
|                                             <p v-for="clientEmail in clientCount[dbInbound.id].expiring">[[ clientEmail ]]</p> | ||||
|                                         </template> | ||||
|  | @ -244,6 +245,7 @@ | |||
|     </a-layout> | ||||
| </a-layout> | ||||
| {{template "js" .}} | ||||
| {{template "component/themeSwitcher" .}} | ||||
| <script> | ||||
| 
 | ||||
|     const columns = [{ | ||||
|  | @ -315,7 +317,7 @@ | |||
|         delimiters: ['[[', ']]'], | ||||
|         el: '#app', | ||||
|         data: { | ||||
|             siderDrawer, | ||||
|             themeSwitcher, | ||||
|             spinning: false, | ||||
|             inbounds: [], | ||||
|             dbInbounds: [], | ||||
|  | @ -331,7 +333,7 @@ | |||
|             refreshInterval: Number(localStorage.getItem("refreshInterval")) || 5000, | ||||
|         }, | ||||
|         methods: { | ||||
|             loading(spinning=true) { | ||||
|             loading(spinning = true) { | ||||
|                 this.spinning = spinning; | ||||
|             }, | ||||
|             async getDBInbounds() { | ||||
|  | @ -361,29 +363,29 @@ | |||
|                     to_inbound = dbInbound.toInbound() | ||||
|                     this.inbounds.push(to_inbound); | ||||
|                     this.dbInbounds.push(dbInbound); | ||||
|                     if([Protocols.VMESS, Protocols.VLESS, Protocols.TROJAN].includes(inbound.protocol) ){ | ||||
|                         this.clientCount[inbound.id] = this.getClientCounts(inbound,to_inbound); | ||||
|                     if ([Protocols.VMESS, Protocols.VLESS, Protocols.TROJAN].includes(inbound.protocol)) { | ||||
|                         this.clientCount[inbound.id] = this.getClientCounts(inbound, to_inbound); | ||||
|                     } | ||||
|                 } | ||||
|                 this.searchInbounds(this.searchKey); | ||||
|             }, | ||||
|             getClientCounts(dbInbound,inbound){ | ||||
|                 let clientCount = 0,active = [], deactive = [], depleted = [], expiring = []; | ||||
|             getClientCounts(dbInbound, inbound) { | ||||
|                 let clientCount = 0, active = [], deactive = [], depleted = [], expiring = []; | ||||
|                 clients = this.getClients(dbInbound.protocol, inbound.settings); | ||||
|                 clientStats = dbInbound.clientStats | ||||
|                 now = new Date().getTime() | ||||
|                 if(clients){ | ||||
|                 if (clients) { | ||||
|                     clientCount = clients.length; | ||||
|                     if(dbInbound.enable){ | ||||
|                     if (dbInbound.enable) { | ||||
|                         clients.forEach(client => { | ||||
|                             client.enable ? active.push(client.email) : deactive.push(client.email); | ||||
|                         }); | ||||
|                         clientStats.forEach(client => { | ||||
|                             if(!client.enable) { | ||||
|                             if (!client.enable) { | ||||
|                                 depleted.push(client.email); | ||||
|                             } else { | ||||
|                                 if ((client.expiryTime > 0 && (client.expiryTime-now < this.expireDiff)) || | ||||
|                                 (client.total > 0 && (client.total-(client.up+client.down) < this.trafficDiff ))) expiring.push(client.email); | ||||
|                                 if ((client.expiryTime > 0 && (client.expiryTime - now < this.expireDiff)) || | ||||
|                                     (client.total > 0 && (client.total - (client.up + client.down) < this.trafficDiff))) expiring.push(client.email); | ||||
|                             } | ||||
|                         }); | ||||
|                     } else { | ||||
|  | @ -409,10 +411,10 @@ | |||
|                         if (ObjectUtil.deepSearch(inbound, key)) { | ||||
|                             const newInbound = new DBInbound(inbound); | ||||
|                             const inboundSettings = JSON.parse(inbound.settings); | ||||
|                             if (inboundSettings.hasOwnProperty('clients')){ | ||||
|                             if (inboundSettings.hasOwnProperty('clients')) { | ||||
|                                 const searchedSettings = { "clients": [] }; | ||||
|                                 inboundSettings.clients.forEach(client => { | ||||
|                                     if (ObjectUtil.deepSearch(client, key)){ | ||||
|                                     if (ObjectUtil.deepSearch(client, key)) { | ||||
|                                         searchedSettings.clients.push(client); | ||||
|                                     } | ||||
|                                 }); | ||||
|  | @ -423,7 +425,7 @@ | |||
|                     }); | ||||
|                 } | ||||
|             }, | ||||
|             generalActions(action){ | ||||
|             generalActions(action) { | ||||
|                 switch (action.key) { | ||||
|                     case "export": | ||||
|                         this.exportAllLinks(); | ||||
|  | @ -476,7 +478,7 @@ | |||
|                         break; | ||||
|                 } | ||||
|             }, | ||||
| 			openCloneInbound(dbInbound) { | ||||
|             openCloneInbound(dbInbound) { | ||||
|                 this.$confirm({ | ||||
|                     title: '{{ i18n "pages.inbounds.cloneInbound"}} \"' + dbInbound.remark + '\"', | ||||
|                     content: '{{ i18n "pages.inbounds.cloneInboundContent"}}', | ||||
|  | @ -621,21 +623,21 @@ | |||
|                     isEdit: true | ||||
|                 }); | ||||
|             }, | ||||
|             findIndexOfClient(clients,client) { | ||||
|             findIndexOfClient(clients, client) { | ||||
|                 firstKey = Object.keys(client)[0]; | ||||
|                 return clients.findIndex(c => c[firstKey] === client[firstKey]); | ||||
|             }, | ||||
|             async addClient(clients, dbInboundId) { | ||||
|                 const data = { | ||||
|                     id: dbInboundId, | ||||
|                     settings: '{"clients": [' + clients.toString() +']}', | ||||
|                     settings: '{"clients": [' + clients.toString() + ']}', | ||||
|                 }; | ||||
|                 await this.submit(`/xui/inbound/addClient`, data); | ||||
|             }, | ||||
|             async updateClient(client, dbInboundId, clientId) { | ||||
|                 const data = { | ||||
|                     id: dbInboundId, | ||||
|                     settings: '{"clients": [' + client.toString() +']}', | ||||
|                     settings: '{"clients": [' + client.toString() + ']}', | ||||
|                 }; | ||||
|                 await this.submit(`/xui/inbound/updateClient/${clientId}`, data); | ||||
|             }, | ||||
|  | @ -644,7 +646,7 @@ | |||
|                 this.$confirm({ | ||||
|                     title: '{{ i18n "pages.inbounds.resetTraffic"}}', | ||||
|                     content: '{{ i18n "pages.inbounds.resetTrafficContent"}}', | ||||
|                     class: siderDrawer.isDarkTheme ? darkClass : '', | ||||
|                     class: themeSwitcher.darkCardClass, | ||||
|                     okText: '{{ i18n "reset"}}', | ||||
|                     cancelText: '{{ i18n "cancel"}}', | ||||
|                     onOk: () => { | ||||
|  | @ -659,26 +661,26 @@ | |||
|                 this.$confirm({ | ||||
|                     title: '{{ i18n "pages.inbounds.deleteInbound"}}', | ||||
|                     content: '{{ i18n "pages.inbounds.deleteInboundContent"}}', | ||||
|                     class: siderDrawer.isDarkTheme ? darkClass : '', | ||||
|                     class: themeSwitcher.darkCardClass, | ||||
|                     okText: '{{ i18n "delete"}}', | ||||
|                     cancelText: '{{ i18n "cancel"}}', | ||||
|                     onOk: () => this.submit('/xui/inbound/del/' + dbInboundId), | ||||
|                 }); | ||||
|             }, | ||||
|             delClient(dbInboundId,client) { | ||||
|             delClient(dbInboundId, client) { | ||||
|                 dbInbound = this.dbInbounds.find(row => row.id === dbInboundId); | ||||
|                 clientId = this.getClientId(dbInbound.protocol,client); | ||||
|                 clientId = this.getClientId(dbInbound.protocol, client); | ||||
|                 this.$confirm({ | ||||
|                     title: '{{ i18n "pages.inbounds.deleteInbound"}}', | ||||
|                     content: '{{ i18n "pages.inbounds.deleteInboundContent"}}', | ||||
|                     class: siderDrawer.isDarkTheme ? darkClass : '', | ||||
|                     class: themeSwitcher.darkCardClass, | ||||
|                     okText: '{{ i18n "delete"}}', | ||||
|                     cancelText: '{{ i18n "cancel"}}', | ||||
|                     onOk: () => this.submit(`/xui/inbound/${dbInboundId}/delClient/${clientId}`), | ||||
|                 }); | ||||
|             }, | ||||
|             getClients(protocol, clientSettings) { | ||||
|                 switch(protocol){ | ||||
|                 switch (protocol) { | ||||
|                     case Protocols.VMESS: return clientSettings.vmesses; | ||||
|                     case Protocols.VLESS: return clientSettings.vlesses; | ||||
|                     case Protocols.TROJAN: return clientSettings.trojans; | ||||
|  | @ -687,7 +689,7 @@ | |||
|                 } | ||||
|             }, | ||||
|             getClientId(protocol, client) { | ||||
|                 switch(protocol){ | ||||
|                 switch (protocol) { | ||||
|                     case Protocols.TROJAN: return client.password; | ||||
|                     case Protocols.SHADOWSOCKS: return client.email; | ||||
|                     default: return client.id; | ||||
|  | @ -711,8 +713,8 @@ | |||
|                 clients = this.getClients(dbInbound.protocol, inbound.settings); | ||||
|                 index = this.findIndexOfClient(clients, client); | ||||
|                 clients[index].enable = !clients[index].enable; | ||||
|                 clientId = this.getClientId(dbInbound.protocol,clients[index]); | ||||
|                 await this.updateClient(clients[index],dbInboundId, clientId); | ||||
|                 clientId = this.getClientId(dbInbound.protocol, clients[index]); | ||||
|                 await this.updateClient(clients[index], dbInboundId, clientId); | ||||
|                 this.loading(false); | ||||
|             }, | ||||
|             async submit(url, data) { | ||||
|  | @ -722,31 +724,31 @@ | |||
|                 } | ||||
|             }, | ||||
|             getInboundClients(dbInbound) { | ||||
|                 if(dbInbound.protocol == Protocols.VLESS) { | ||||
|                 if (dbInbound.protocol == Protocols.VLESS) { | ||||
|                     return dbInbound.toInbound().settings.vlesses; | ||||
|                 } else if(dbInbound.protocol == Protocols.VMESS) { | ||||
|                 } else if (dbInbound.protocol == Protocols.VMESS) { | ||||
|                     return dbInbound.toInbound().settings.vmesses; | ||||
|                 } else if(dbInbound.protocol == Protocols.TROJAN) { | ||||
|                 } else if (dbInbound.protocol == Protocols.TROJAN) { | ||||
|                     return dbInbound.toInbound().settings.trojans; | ||||
|                 } else if(dbInbound.protocol == Protocols.SHADOWSOCKS) { | ||||
|                 } else if (dbInbound.protocol == Protocols.SHADOWSOCKS) { | ||||
|                     return dbInbound.toInbound().settings.shadowsockses; | ||||
|                 } | ||||
|             }, | ||||
|             resetClientTraffic(client,dbInboundId) { | ||||
|             resetClientTraffic(client, dbInboundId) { | ||||
|                 this.$confirm({ | ||||
|                     title: '{{ i18n "pages.inbounds.resetTraffic"}}', | ||||
|                     content: '{{ i18n "pages.inbounds.resetTrafficContent"}}', | ||||
|                     class: siderDrawer.isDarkTheme ? darkClass : '', | ||||
|                     class: themeSwitcher.darkCardClass, | ||||
|                     okText: '{{ i18n "reset"}}', | ||||
|                     cancelText: '{{ i18n "cancel"}}', | ||||
|                     onOk: () => this.submit('/xui/inbound/' + dbInboundId + '/resetClientTraffic/'+ client.email), | ||||
|                     onOk: () => this.submit('/xui/inbound/' + dbInboundId + '/resetClientTraffic/' + client.email), | ||||
|                 }) | ||||
|             }, | ||||
|             resetAllTraffic() { | ||||
|                 this.$confirm({ | ||||
|                     title: '{{ i18n "pages.inbounds.resetAllTrafficTitle"}}', | ||||
|                     content: '{{ i18n "pages.inbounds.resetAllTrafficContent"}}', | ||||
|                     class: siderDrawer.isDarkTheme ? darkClass : '', | ||||
|                     class: themeSwitcher.darkCardClass, | ||||
|                     okText: '{{ i18n "reset"}}', | ||||
|                     cancelText: '{{ i18n "cancel"}}', | ||||
|                     onOk: () => this.submit('/xui/inbound/resetAllTraffics'), | ||||
|  | @ -754,9 +756,9 @@ | |||
|             }, | ||||
|             resetAllClientTraffics(dbInboundId) { | ||||
|                 this.$confirm({ | ||||
|                     title: dbInboundId>0 ? '{{ i18n "pages.inbounds.resetInboundClientTrafficTitle"}}' : '{{ i18n "pages.inbounds.resetAllClientTrafficTitle"}}', | ||||
|                     content: dbInboundId>0 ? '{{ i18n "pages.inbounds.resetInboundClientTrafficContent"}}' : '{{ i18n "pages.inbounds.resetAllClientTrafficContent"}}', | ||||
|                     class: siderDrawer.isDarkTheme ? darkClass : '', | ||||
|                     title: dbInboundId > 0 ? '{{ i18n "pages.inbounds.resetInboundClientTrafficTitle"}}' : '{{ i18n "pages.inbounds.resetAllClientTrafficTitle"}}', | ||||
|                     content: dbInboundId > 0 ? '{{ i18n "pages.inbounds.resetInboundClientTrafficContent"}}' : '{{ i18n "pages.inbounds.resetAllClientTrafficContent"}}', | ||||
|                     class: themeSwitcher.darkCardClass, | ||||
|                     okText: '{{ i18n "reset"}}', | ||||
|                     cancelText: '{{ i18n "cancel"}}', | ||||
|                     onOk: () => this.submit('/xui/inbound/resetAllClientTraffics/' + dbInboundId), | ||||
|  | @ -766,7 +768,7 @@ | |||
|                 this.$confirm({ | ||||
|                     title: '{{ i18n "pages.inbounds.delDepletedClientsTitle"}}', | ||||
|                     content: '{{ i18n "pages.inbounds.delDepletedClientsContent"}}', | ||||
|                     class: siderDrawer.isDarkTheme ? darkClass : '', | ||||
|                     class: themeSwitcher.darkCardClass, | ||||
|                     okText: '{{ i18n "reset"}}', | ||||
|                     cancelText: '{{ i18n "cancel"}}', | ||||
|                     onOk: () => this.submit('/xui/inbound/delDepletedClients/' + dbInboundId), | ||||
|  | @ -776,17 +778,17 @@ | |||
|                 return dbInbound.toInbound().isExpiry(index) | ||||
|             }, | ||||
|             getUpStats(dbInbound, email) { | ||||
|                 if(email.length == 0) return 0 | ||||
|                 if (email.length == 0) return 0 | ||||
|                 clientStats = dbInbound.clientStats.find(stats => stats.email === email) | ||||
|                 return clientStats ? clientStats.up : 0 | ||||
|             }, | ||||
|             getDownStats(dbInbound, email) { | ||||
|                 if(email.length == 0) return 0 | ||||
|                 if (email.length == 0) return 0 | ||||
|                 clientStats = dbInbound.clientStats.find(stats => stats.email === email) | ||||
|                 return clientStats ? clientStats.down : 0 | ||||
|             }, | ||||
|             isTrafficExhausted(dbInbound, email) { | ||||
|                 if(email.length == 0) return false | ||||
|                 if (email.length == 0) return false | ||||
|                 clientStats = dbInbound.clientStats.find(stats => stats.email === email) | ||||
|                 return clientStats ? clientStats.down + clientStats.up > clientStats.total : false | ||||
|             }, | ||||
|  | @ -794,28 +796,28 @@ | |||
|                 clientStats = dbInbound.clientStats ? dbInbound.clientStats.find(stats => stats.email === email) : null | ||||
|                 return clientStats ? clientStats['enable'] : true | ||||
|             }, | ||||
|             isRemovable(dbInbound_id){ | ||||
|             isRemovable(dbInbound_id) { | ||||
|                 return this.getInboundClients(this.dbInbounds.find(row => row.id === dbInbound_id)).length > 1 | ||||
|             }, | ||||
|             inboundLinks(dbInboundId) { | ||||
|                 dbInbound = this.dbInbounds.find(row => row.id === dbInboundId); | ||||
|                 txtModal.show('{{ i18n "pages.inbounds.export"}}',dbInbound.genInboundLinks,dbInbound.remark); | ||||
|                 txtModal.show('{{ i18n "pages.inbounds.export"}}', dbInbound.genInboundLinks, dbInbound.remark); | ||||
|             }, | ||||
| 			exportAllLinks() { | ||||
|             exportAllLinks() { | ||||
|                 let copyText = ''; | ||||
|                 for (const dbInbound of this.dbInbounds) { | ||||
|                     copyText += dbInbound.genInboundLinks | ||||
|                 } | ||||
|                 txtModal.show('{{ i18n "pages.inbounds.export"}}',copyText,'All-Inbounds'); | ||||
|                 txtModal.show('{{ i18n "pages.inbounds.export"}}', copyText, 'All-Inbounds'); | ||||
|             }, | ||||
|             async startDataRefreshLoop() { | ||||
|                 while (this.isRefreshEnabled) { | ||||
|                 try { | ||||
|                     await this.getDBInbounds(); | ||||
|                 } catch (e) { | ||||
|                     console.error(e); | ||||
|                 } | ||||
|                 await PromiseUtil.sleep(this.refreshInterval); | ||||
|                     try { | ||||
|                         await this.getDBInbounds(); | ||||
|                     } catch (e) { | ||||
|                         console.error(e); | ||||
|                     } | ||||
|                     await PromiseUtil.sleep(this.refreshInterval); | ||||
|                 } | ||||
|             }, | ||||
|             toggleRefresh() { | ||||
|  | @ -824,11 +826,11 @@ | |||
|                     this.startDataRefreshLoop(); | ||||
|                 } | ||||
|             }, | ||||
|             changeRefreshInterval(){ | ||||
|             changeRefreshInterval() { | ||||
|                 localStorage.setItem("refreshInterval", this.refreshInterval); | ||||
|             }, | ||||
|             async manualRefresh(){ | ||||
|                 if(!this.refreshing){ | ||||
|             async manualRefresh() { | ||||
|                 if (!this.refreshing) { | ||||
|                     this.spinning = true; | ||||
|                     await this.getDBInbounds(); | ||||
|                     this.spinning = false; | ||||
|  | @ -876,6 +878,7 @@ | |||
|             } | ||||
|         }, | ||||
|     }); | ||||
| 
 | ||||
| </script> | ||||
| 
 | ||||
| {{template "inboundModal"}} | ||||
|  | @ -885,5 +888,6 @@ | |||
| {{template "inboundInfoModal"}} | ||||
| {{template "clientsModal"}} | ||||
| {{template "clientsBulkModal"}} | ||||
| 
 | ||||
| </body> | ||||
| </html> | ||||
|  | @ -13,32 +13,33 @@ | |||
|     } | ||||
| 
 | ||||
|     .ant-card-dark h2 { | ||||
|         color: hsla(0,0%,100%,.65); | ||||
|         color: hsla(0, 0%, 100%, .65); | ||||
|     } | ||||
| </style> | ||||
| 
 | ||||
| <body> | ||||
| <a-layout id="app" v-cloak> | ||||
|     {{ template "commonSider" . }} | ||||
|     <a-layout id="content-layout" :style="siderDrawer.isDarkTheme ? bgDarkStyle : ''"> | ||||
|     <a-layout id="content-layout" :style="themeSwitcher.bgStyle"> | ||||
|         <a-layout-content> | ||||
|             <a-spin :spinning="spinning" :delay="200" :tip="loadingTip"/> | ||||
|             <transition name="list" appear> | ||||
|                 <a-row> | ||||
|                     <a-card hoverable :class="siderDrawer.isDarkTheme ? darkClass : ''"> | ||||
|                     <a-card hoverable :class="themeSwitcher.darkCardClass"> | ||||
|                         <a-row> | ||||
|                             <a-col :sm="24" :md="12"> | ||||
|                                 <a-row> | ||||
|                                     <a-col :span="12" style="text-align: center"> | ||||
|                                         <a-progress type="dashboard" status="normal" | ||||
|                                                     :stroke-color="status.cpu.color" | ||||
|                                                     :class="siderDrawer.isDarkTheme ? darkClass : ''" | ||||
|                                                     :class="themeSwitcher.darkCardClass" | ||||
|                                                     :percent="status.cpu.percent"></a-progress> | ||||
|                                         <div>CPU</div> | ||||
|                                     </a-col> | ||||
|                                     <a-col :span="12" style="text-align: center"> | ||||
|                                         <a-progress type="dashboard" status="normal" | ||||
|                                                     :stroke-color="status.mem.color" | ||||
|                                                     :class="siderDrawer.isDarkTheme ? darkClass : ''" | ||||
|                                                     :class="themeSwitcher.darkCardClass" | ||||
|                                                     :percent="status.mem.percent"></a-progress> | ||||
|                                         <div> | ||||
|                                             {{ i18n "pages.index.memory"}}: [[ sizeFormat(status.mem.current) ]] / [[ sizeFormat(status.mem.total) ]] | ||||
|  | @ -51,7 +52,7 @@ | |||
|                                     <a-col :span="12" style="text-align: center"> | ||||
|                                         <a-progress type="dashboard" status="normal" | ||||
|                                                     :stroke-color="status.swap.color" | ||||
|                                                     :class="siderDrawer.isDarkTheme ? darkClass : ''" | ||||
|                                                     :class="themeSwitcher.darkCardClass" | ||||
|                                                     :percent="status.swap.percent"></a-progress> | ||||
|                                         <div> | ||||
|                                             Swap: [[ sizeFormat(status.swap.current) ]] / [[ sizeFormat(status.swap.total) ]] | ||||
|  | @ -60,7 +61,7 @@ | |||
|                                     <a-col :span="12" style="text-align: center"> | ||||
|                                         <a-progress type="dashboard" status="normal" | ||||
|                                                     :stroke-color="status.disk.color" | ||||
|                                                     :class="siderDrawer.isDarkTheme ? darkClass : ''" | ||||
|                                                     :class="themeSwitcher.darkCardClass" | ||||
|                                                     :percent="status.disk.percent"></a-progress> | ||||
|                                         <div> | ||||
|                                             {{ i18n "pages.index.hard"}}: [[ sizeFormat(status.disk.current) ]] / [[ sizeFormat(status.disk.total) ]] | ||||
|  | @ -75,14 +76,14 @@ | |||
|             <transition name="list" appear> | ||||
|                 <a-row> | ||||
|                     <a-col :sm="24" :md="12"> | ||||
|                         <a-card hoverable :class="siderDrawer.isDarkTheme ? darkClass : ''"> | ||||
|                         <a-card hoverable :class="themeSwitcher.darkCardClass"> | ||||
|                             3x-ui: <a href="https://github.com/MHSanaei/3x-ui/releases" target="_blank"><a-tag color="green">v{{ .cur_ver }}</a-tag></a> | ||||
|                             Xray: <a-tag color="green" style="cursor: pointer;" @click="openSelectV2rayVersion">v[[ status.xray.version ]]</a-tag> | ||||
|                             Telegram: <a href="https://t.me/panel3xui" target="_blank"><a-tag color="green">@panel3xui</a-tag></a> | ||||
|                         </a-card> | ||||
|                     </a-col> | ||||
|                     <a-col :sm="24" :md="12"> | ||||
|                         <a-card hoverable :class="siderDrawer.isDarkTheme ? darkClass : ''"> | ||||
|                         <a-card hoverable :class="themeSwitcher.darkCardClass"> | ||||
|                             {{ i18n "pages.index.operationHours" }}: | ||||
|                             <a-tag color="green">[[ formatSecond(status.uptime) ]]</a-tag> | ||||
|                             <a-tooltip> | ||||
|  | @ -94,7 +95,7 @@ | |||
|                         </a-card> | ||||
|                     </a-col> | ||||
|                     <a-col :sm="24" :md="12"> | ||||
|                         <a-card hoverable :class="siderDrawer.isDarkTheme ? darkClass : ''"> | ||||
|                         <a-card hoverable :class="themeSwitcher.darkCardClass"> | ||||
|                             {{ i18n "pages.index.xrayStatus" }}: | ||||
|                             <a-tag :color="status.xray.color">[[ status.xray.state ]]</a-tag> | ||||
|                             <a-tooltip v-if="status.xray.state === State.Error"> | ||||
|  | @ -109,7 +110,7 @@ | |||
|                         </a-card> | ||||
|                     </a-col> | ||||
|                     <a-col :sm="24" :md="12"> | ||||
|                         <a-card hoverable :class="siderDrawer.isDarkTheme ? darkClass : ''"> | ||||
|                         <a-card hoverable :class="themeSwitcher.darkCardClass"> | ||||
|                             {{ i18n "menu.link" }}: | ||||
|                             <a-tag color="blue" style="cursor: pointer;" @click="openLogs(20)">{{ i18n "pages.index.logs" }}</a-tag> | ||||
|                             <a-tag color="blue" style="cursor: pointer;" @click="openConfig">{{ i18n "pages.index.config" }}</a-tag> | ||||
|  | @ -117,12 +118,12 @@ | |||
|                         </a-card> | ||||
|                     </a-col> | ||||
|                     <a-col :sm="24" :md="12"> | ||||
|                         <a-card hoverable :class="siderDrawer.isDarkTheme ? darkClass : ''"> | ||||
|                         <a-card hoverable :class="themeSwitcher.darkCardClass"> | ||||
|                             {{ i18n "pages.index.systemLoad" }}: [[ status.loads[0] ]] | [[ status.loads[1] ]] | [[ status.loads[2] ]] | ||||
|                         </a-card> | ||||
|                     </a-col> | ||||
|                     <a-col :sm="24" :md="12"> | ||||
|                         <a-card hoverable :class="siderDrawer.isDarkTheme ? darkClass : ''"> | ||||
|                         <a-card hoverable :class="themeSwitcher.darkCardClass"> | ||||
|                             TCP / UDP {{ i18n "pages.index.connectionCount" }}: [[ status.tcpCount ]] / [[ status.udpCount ]] | ||||
|                             <a-tooltip> | ||||
|                                 <template slot="title"> | ||||
|  | @ -133,7 +134,7 @@ | |||
|                         </a-card> | ||||
|                     </a-col> | ||||
|                     <a-col :sm="24" :md="12"> | ||||
|                         <a-card hoverable :class="siderDrawer.isDarkTheme ? darkClass : ''"> | ||||
|                         <a-card hoverable :class="themeSwitcher.darkCardClass"> | ||||
|                             <a-row> | ||||
|                                 <a-col :span="12"> | ||||
|                                     <a-icon type="arrow-up"></a-icon> | ||||
|  | @ -159,7 +160,7 @@ | |||
|                         </a-card> | ||||
|                     </a-col> | ||||
|                     <a-col :sm="24" :md="12"> | ||||
|                         <a-card hoverable :class="siderDrawer.isDarkTheme ? darkClass : ''"> | ||||
|                         <a-card hoverable :class="themeSwitcher.darkCardClass"> | ||||
|                             <a-row> | ||||
|                                 <a-col :span="12"> | ||||
|                                     <a-icon type="cloud-upload"></a-icon> | ||||
|  | @ -191,7 +192,7 @@ | |||
| 
 | ||||
|     <a-modal id="version-modal" v-model="versionModal.visible" title='{{ i18n "pages.index.xraySwitch" }}' | ||||
|              :closable="true" @ok="() => versionModal.visible = false" | ||||
|              :class="siderDrawer.isDarkTheme ? darkClass : ''" | ||||
|              :class="themeSwitcher.darkCardClass" | ||||
|              footer=""> | ||||
|         <h2>{{ i18n "pages.index.xraySwitchClick"}}</h2> | ||||
|         <h2>{{ i18n "pages.index.xraySwitchClickDesk"}}</h2> | ||||
|  | @ -205,7 +206,7 @@ | |||
| 
 | ||||
|     <a-modal id="log-modal" v-model="logModal.visible" title="X-UI logs" | ||||
|              :closable="true" @ok="() => logModal.visible = false" @cancel="() => logModal.visible = false" | ||||
|              :class="siderDrawer.isDarkTheme ? darkClass : ''" | ||||
|              :class="themeSwitcher.darkCardClass" | ||||
|              width="800px" | ||||
|              footer=""> | ||||
|         <a-form layout="inline"> | ||||
|  | @ -213,7 +214,7 @@ | |||
|                 <a-select v-model="logModal.rows" | ||||
|                 style="width: 80px" | ||||
|                 @change="openLogs(logModal.rows)" | ||||
|                 :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"> | ||||
|                 :dropdown-class-name="themeSwitcher.darkCardClass"> | ||||
|                     <a-select-option value="10">10</a-select-option> | ||||
|                     <a-select-option value="20">20</a-select-option> | ||||
|                     <a-select-option value="50">50</a-select-option> | ||||
|  | @ -235,7 +236,7 @@ | |||
|     </a-modal> | ||||
| 
 | ||||
|     <a-modal id="backup-modal" v-model="backupModal.visible" :title="backupModal.title" | ||||
|             :closable="true" :class="siderDrawer.isDarkTheme ? darkClass : ''" | ||||
|             :closable="true" :class="themeSwitcher.darkCardClass" | ||||
|             @ok="() => backupModal.hide()" @cancel="() => backupModal.hide()"> | ||||
|         <p style="color: inherit; font-size: 16px; padding: 4px 2px;"> | ||||
|             <a-icon type="warning" style="color: inherit; font-size: 20px;"></a-icon> | ||||
|  | @ -253,6 +254,7 @@ | |||
| 
 | ||||
| </a-layout> | ||||
| {{template "js" .}} | ||||
| {{template "component/themeSwitcher" .}} | ||||
| {{template "textModal"}} | ||||
| <script> | ||||
| 
 | ||||
|  | @ -295,13 +297,13 @@ | |||
|             this.disk = new CurTotal(0, 0); | ||||
|             this.loads = [0, 0, 0]; | ||||
|             this.mem = new CurTotal(0, 0); | ||||
|             this.netIO = {up: 0, down: 0}; | ||||
|             this.netTraffic = {sent: 0, recv: 0}; | ||||
|             this.netIO = { up: 0, down: 0 }; | ||||
|             this.netTraffic = { sent: 0, recv: 0 }; | ||||
|             this.swap = new CurTotal(0, 0); | ||||
|             this.tcpCount = 0; | ||||
|             this.udpCount = 0; | ||||
|             this.uptime = 0; | ||||
|             this.xray = {state: State.Stop, errorMsg: "", version: "", color: ""}; | ||||
|             this.xray = { state: State.Stop, errorMsg: "", version: "", color: "" }; | ||||
| 
 | ||||
|             if (data == null) { | ||||
|                 return; | ||||
|  | @ -386,7 +388,7 @@ | |||
|         delimiters: ['[[', ']]'], | ||||
|         el: '#app', | ||||
|         data: { | ||||
|             siderDrawer, | ||||
|             themeSwitcher, | ||||
|             status: new Status(), | ||||
|             versionModal, | ||||
|             logModal, | ||||
|  | @ -422,7 +424,7 @@ | |||
|                     title: '{{ i18n "pages.index.xraySwitchVersionDialog"}}', | ||||
|                     content: '{{ i18n "pages.index.xraySwitchVersionDialogDesc"}}' + ` ${version}?`, | ||||
|                     okText: '{{ i18n "confirm"}}', | ||||
|                     class: siderDrawer.isDarkTheme ? darkClass : '', | ||||
|                     class: themeSwitcher.darkCardClass, | ||||
|                     cancelText: '{{ i18n "cancel"}}', | ||||
|                     onOk: async () => { | ||||
|                         versionModal.hide(); | ||||
|  | @ -448,9 +450,9 @@ | |||
|                     return; | ||||
|                 } | ||||
|             }, | ||||
|             async openLogs(rows){ | ||||
|             async openLogs(rows) { | ||||
|                 this.loading(true); | ||||
|                 const msg = await HttpUtil.post('server/logs/'+rows); | ||||
|                 const msg = await HttpUtil.post('server/logs/' + rows); | ||||
|                 this.loading(false); | ||||
|                 if (!msg.success) { | ||||
|                     return; | ||||
|  |  | |||
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
		Loading…
	
		Reference in a new issue
	
	 Ho3ein
						Ho3ein