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