mirror of
				https://github.com/MHSanaei/3x-ui.git
				synced 2025-10-31 04:12:51 +00:00 
			
		
		
		
	Merge branch 'main' into main
This commit is contained in:
		
						commit
						f7a3ebf2f3
					
				
					 15 changed files with 2785 additions and 2542 deletions
				
			
		|  | @ -2150,7 +2150,7 @@ Inbound.TrojanSettings.Fallback = class extends XrayCommonClass { | ||||||
| Inbound.ShadowsocksSettings = class extends Inbound.Settings { | Inbound.ShadowsocksSettings = class extends Inbound.Settings { | ||||||
|     constructor(protocol, |     constructor(protocol, | ||||||
|         method = SSMethods.BLAKE3_AES_256_GCM, |         method = SSMethods.BLAKE3_AES_256_GCM, | ||||||
|         password = RandomUtil.randomShadowsocksPassword(), |         password = '', | ||||||
|         network = 'tcp,udp', |         network = 'tcp,udp', | ||||||
|         shadowsockses = [new Inbound.ShadowsocksSettings.Shadowsocks()], |         shadowsockses = [new Inbound.ShadowsocksSettings.Shadowsocks()], | ||||||
|         ivCheck = false, |         ivCheck = false, | ||||||
|  | @ -2188,7 +2188,7 @@ Inbound.ShadowsocksSettings = class extends Inbound.Settings { | ||||||
| Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass { | Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass { | ||||||
|     constructor( |     constructor( | ||||||
|         method = '', |         method = '', | ||||||
|         password = RandomUtil.randomShadowsocksPassword(), |         password = '', | ||||||
|         email = RandomUtil.randomLowerAndNum(8), |         email = RandomUtil.randomLowerAndNum(8), | ||||||
|         limitIp = 0, |         limitIp = 0, | ||||||
|         totalGB = 0, |         totalGB = 0, | ||||||
|  |  | ||||||
|  | @ -138,8 +138,14 @@ class RandomUtil { | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     static randomShadowsocksPassword() { |     static randomShadowsocksPassword(method = SSMethods.BLAKE3_AES_256_GCM) { | ||||||
|         const array = new Uint8Array(32); |         let length = 32; | ||||||
|  | 
 | ||||||
|  |         if ([SSMethods.BLAKE3_AES_128_GCM].includes(method)) { | ||||||
|  |             length = 16;  | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         const array = new Uint8Array(length); | ||||||
| 
 | 
 | ||||||
|         window.crypto.getRandomValues(array); |         window.crypto.getRandomValues(array); | ||||||
| 
 | 
 | ||||||
|  | @ -789,6 +795,25 @@ class LanguageManager { | ||||||
|             if (window.navigator) { |             if (window.navigator) { | ||||||
|                 lang = window.navigator.language || window.navigator.userLanguage; |                 lang = window.navigator.language || window.navigator.userLanguage; | ||||||
| 
 | 
 | ||||||
|  |                 const simularLangs = [ | ||||||
|  |                     ["ar", this.supportedLanguages[0].value], | ||||||
|  |                     ["fa", this.supportedLanguages[2].value], | ||||||
|  |                     ["ja", this.supportedLanguages[5].value], | ||||||
|  |                     ["ru", this.supportedLanguages[6].value], | ||||||
|  |                     ["vi", this.supportedLanguages[7].value], | ||||||
|  |                     ["es", this.supportedLanguages[8].value], | ||||||
|  |                     ["id", this.supportedLanguages[9].value], | ||||||
|  |                     ["uk", this.supportedLanguages[10].value], | ||||||
|  |                     ["tr", this.supportedLanguages[11].value], | ||||||
|  |                     ["pt", this.supportedLanguages[12].value], | ||||||
|  |                 ] | ||||||
|  | 
 | ||||||
|  |                 simularLangs.forEach((pair) => { | ||||||
|  |                     if (lang === pair[0]) { | ||||||
|  |                         lang = pair[1]; | ||||||
|  |                     } | ||||||
|  |                 }); | ||||||
|  | 
 | ||||||
|                 if (LanguageManager.isSupportLanguage(lang)) { |                 if (LanguageManager.isSupportLanguage(lang)) { | ||||||
|                     CookieManager.setCookie("lang", lang, 150); |                     CookieManager.setCookie("lang", lang, 150); | ||||||
|                 } else { |                 } else { | ||||||
|  |  | ||||||
|  | @ -1,31 +0,0 @@ | ||||||
| {{define "head"}} |  | ||||||
| <head> |  | ||||||
|   <meta charset="UTF-8"> |  | ||||||
|   <meta name="renderer" content="webkit"> |  | ||||||
|   <meta name="viewport" content="width=device-width, initial-scale=1.0"> |  | ||||||
|   <meta name="robots" content="noindex,nofollow"> |  | ||||||
|   <link rel="stylesheet" href="{{ .base_path }}assets/ant-design-vue/antd.min.css"> |  | ||||||
|   <link rel="stylesheet" href="{{ .base_path }}assets/css/custom.min.css?{{ .cur_ver }}"> |  | ||||||
|   <style> |  | ||||||
|     [v-cloak] { |  | ||||||
|       display: none; |  | ||||||
|     } |  | ||||||
|     /* vazirmatn-regular - arabic_latin_latin-ext */ |  | ||||||
|     @font-face { |  | ||||||
|       font-display: swap; |  | ||||||
|       font-family: 'Vazirmatn'; |  | ||||||
|       font-style: normal; |  | ||||||
|       font-weight: 400; |  | ||||||
|       src: url('{{ .base_path }}assets/Vazirmatn-UI-NL-Regular.woff2') format('woff2'); |  | ||||||
|       unicode-range: U+0600-06FF, U+200C-200E, U+2010-2011, U+204F, U+2E41, U+FB50-FDFF, U+FE80-FEFC, U+0030-0039; |  | ||||||
|     } |  | ||||||
|     body { |  | ||||||
|       font-family: -apple-system, BlinkMacSystemFont, 'Vazirmatn', 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', |  | ||||||
|         'Microsoft YaHei', 'Helvetica Neue', Helvetica, Arial, sans-serif, 'Apple Color Emoji', |  | ||||||
|         'Segoe UI Emoji', 'Segoe UI Symbol'; |  | ||||||
|     } |  | ||||||
|   </style> |  | ||||||
|   <title>{{ .host }} – {{ i18n .title}}</title> |  | ||||||
| </head> |  | ||||||
| <div id="message"></div> |  | ||||||
| {{end}} |  | ||||||
|  | @ -1,14 +0,0 @@ | ||||||
| {{define "js"}} |  | ||||||
| <script src="{{ .base_path }}assets/vue/vue.min.js?{{ .cur_ver }}"></script> |  | ||||||
| <script src="{{ .base_path }}assets/moment/moment.min.js"></script> |  | ||||||
| <script src="{{ .base_path }}assets/ant-design-vue/antd.min.js"></script> |  | ||||||
| <script src="{{ .base_path }}assets/axios/axios.min.js?{{ .cur_ver }}"></script> |  | ||||||
| <script src="{{ .base_path }}assets/qs/qs.min.js"></script> |  | ||||||
| <script src="{{ .base_path }}assets/js/axios-init.js?{{ .cur_ver }}"></script> |  | ||||||
| <script src="{{ .base_path }}assets/js/util/date-util.js?{{ .cur_ver }}"></script> |  | ||||||
| <script src="{{ .base_path }}assets/js/util/index.js?{{ .cur_ver }}"></script> |  | ||||||
| <script> |  | ||||||
|     const basePath = '{{ .base_path }}'; |  | ||||||
|     axios.defaults.baseURL = basePath; |  | ||||||
| </script> |  | ||||||
| {{end}} |  | ||||||
							
								
								
									
										58
									
								
								web/html/common/page.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								web/html/common/page.html
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,58 @@ | ||||||
|  | {{ define "page/head_start" }} | ||||||
|  | <!DOCTYPE html> | ||||||
|  | <html> | ||||||
|  | <head> | ||||||
|  |   <meta charset="UTF-8"> | ||||||
|  |   <meta name="renderer" content="webkit"> | ||||||
|  |   <meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||||||
|  |   <meta name="robots" content="noindex,nofollow"> | ||||||
|  |   <link rel="stylesheet" href="{{ .base_path }}assets/ant-design-vue/antd.min.css"> | ||||||
|  |   <link rel="stylesheet" href="{{ .base_path }}assets/css/custom.min.css?{{ .cur_ver }}"> | ||||||
|  |   <style> | ||||||
|  |     [v-cloak] { | ||||||
|  |       display: none; | ||||||
|  |     } | ||||||
|  |     /* vazirmatn-regular - arabic_latin_latin-ext */ | ||||||
|  |     @font-face { | ||||||
|  |       font-display: swap; | ||||||
|  |       font-family: 'Vazirmatn'; | ||||||
|  |       font-style: normal; | ||||||
|  |       font-weight: 400; | ||||||
|  |       src: url('{{ .base_path }}assets/Vazirmatn-UI-NL-Regular.woff2') format('woff2'); | ||||||
|  |       unicode-range: U+0600-06FF, U+200C-200E, U+2010-2011, U+204F, U+2E41, U+FB50-FDFF, U+FE80-FEFC, U+0030-0039; | ||||||
|  |     } | ||||||
|  |     body { | ||||||
|  |       font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Vazirmatn', 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; | ||||||
|  |     } | ||||||
|  |   </style> | ||||||
|  |   <title>{{ .host }} – {{ i18n .title}}</title> | ||||||
|  | {{ end }} | ||||||
|  | 
 | ||||||
|  | {{ define "page/head_end" }} | ||||||
|  | </head> | ||||||
|  | {{ end }} | ||||||
|  | 
 | ||||||
|  | {{ define "page/body_start" }} | ||||||
|  | <body> | ||||||
|  |   <div id="message"></div> | ||||||
|  | {{ end }} | ||||||
|  | 
 | ||||||
|  | {{ define "page/body_scripts" }} | ||||||
|  | <script src="{{ .base_path }}assets/vue/vue.min.js?{{ .cur_ver }}"></script> | ||||||
|  | <script src="{{ .base_path }}assets/moment/moment.min.js"></script> | ||||||
|  | <script src="{{ .base_path }}assets/ant-design-vue/antd.min.js"></script> | ||||||
|  | <script src="{{ .base_path }}assets/axios/axios.min.js?{{ .cur_ver }}"></script> | ||||||
|  | <script src="{{ .base_path }}assets/qs/qs.min.js"></script> | ||||||
|  | <script src="{{ .base_path }}assets/js/axios-init.js?{{ .cur_ver }}"></script> | ||||||
|  | <script src="{{ .base_path }}assets/js/util/date-util.js?{{ .cur_ver }}"></script> | ||||||
|  | <script src="{{ .base_path }}assets/js/util/index.js?{{ .cur_ver }}"></script> | ||||||
|  | <script> | ||||||
|  |   const basePath = '{{ .base_path }}'; | ||||||
|  |   axios.defaults.baseURL = basePath; | ||||||
|  | </script> | ||||||
|  | {{ end }} | ||||||
|  |    | ||||||
|  | {{ define "page/body_end" }} | ||||||
|  | </body> | ||||||
|  | </html> | ||||||
|  | {{ end }} | ||||||
|  | @ -41,6 +41,7 @@ | ||||||
|   </template> |   </template> | ||||||
| </template> | </template> | ||||||
| <template slot="client" slot-scope="text, client"> | <template slot="client" slot-scope="text, client"> | ||||||
|  |   <a-space direction="horizontal" :size="2"> | ||||||
|     <a-tooltip> |     <a-tooltip> | ||||||
|       <template slot="title"> |       <template slot="title"> | ||||||
|         <template v-if="!isClientEnabled(record, client.email)">{{ i18n "depleted" }}</template> |         <template v-if="!isClientEnabled(record, client.email)">{{ i18n "depleted" }}</template> | ||||||
|  | @ -48,7 +49,20 @@ | ||||||
|         <template v-else-if="client.enable && isClientOnline(client.email)">{{ i18n "online" }}</template> |         <template v-else-if="client.enable && isClientOnline(client.email)">{{ i18n "online" }}</template> | ||||||
|       </template> |       </template> | ||||||
|       <a-badge :class="isClientOnline(client.email)? 'online-animation' : ''" :color="client.enable ? statsExpColor(record, client.email) : themeSwitcher.isDarkTheme ? '#2c3950' : '#bcbcbc'"></a-badge> |       <a-badge :class="isClientOnline(client.email)? 'online-animation' : ''" :color="client.enable ? statsExpColor(record, client.email) : themeSwitcher.isDarkTheme ? '#2c3950' : '#bcbcbc'"></a-badge> | ||||||
|   </a-tooltip> [[ client.email ]] |     </a-tooltip> | ||||||
|  |     <a-space direction="vertical" :size="2"> | ||||||
|  |       <span class="client-email">[[ client.email ]]</span> | ||||||
|  |       <template v-if="client.comment && client.comment.trim()"> | ||||||
|  |         <a-tooltip v-if="client.comment.length > 50" :overlay-class-name="themeSwitcher.currentTheme"> | ||||||
|  |           <template slot="title"> | ||||||
|  |             [[ client.comment ]] | ||||||
|  |           </template> | ||||||
|  |           <span class="client-comment">[[ client.comment.substring(0, 47) + '...' ]]</span> | ||||||
|  |         </a-tooltip> | ||||||
|  |         <span v-else class="client-comment">[[ client.comment ]]</span> | ||||||
|  |       </template> | ||||||
|  |     </a-space> | ||||||
|  |   </a-space> | ||||||
| </template> | </template> | ||||||
| <template slot="traffic" slot-scope="text, client"> | <template slot="traffic" slot-scope="text, client"> | ||||||
|   <a-popover :overlay-class-name="themeSwitcher.currentTheme"> |   <a-popover :overlay-class-name="themeSwitcher.currentTheme"> | ||||||
|  |  | ||||||
|  | @ -22,7 +22,7 @@ | ||||||
|                     <span>{{ i18n "reset" }}</span> |                     <span>{{ i18n "reset" }}</span> | ||||||
|                 </template> |                 </template> | ||||||
|                 {{ i18n "password" }} |                 {{ i18n "password" }} | ||||||
|                 <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" @click="client.password = RandomUtil.randomShadowsocksPassword(inbound.settings.method)" type="sync"></a-icon> | ||||||
|                 <a-icon v-if="inbound.protocol === Protocols.TROJAN" @click="client.password = RandomUtil.randomSeq(10)"type="sync"> </a-icon> |                 <a-icon v-if="inbound.protocol === Protocols.TROJAN" @click="client.password = RandomUtil.randomSeq(10)"type="sync"> </a-icon> | ||||||
|             </a-tooltip> |             </a-tooltip> | ||||||
|         </template> |         </template> | ||||||
|  |  | ||||||
|  | @ -31,7 +31,7 @@ | ||||||
|             <a-tooltip> |             <a-tooltip> | ||||||
|                 <template slot="title"> |                 <template slot="title"> | ||||||
|                     <span>{{ i18n "reset" }}</span> |                     <span>{{ i18n "reset" }}</span> | ||||||
|                 </template> Password <a-icon @click="inbound.settings.password = RandomUtil.randomShadowsocksPassword()" type="sync"></a-icon> |                 </template> Password <a-icon @click="inbound.settings.password = RandomUtil.randomShadowsocksPassword(inbound.settings.method)" type="sync"></a-icon> | ||||||
|             </a-tooltip> |             </a-tooltip> | ||||||
|         </template> |         </template> | ||||||
|         <a-input v-model.trim="inbound.settings.password"></a-input> |         <a-input v-model.trim="inbound.settings.password"></a-input> | ||||||
|  |  | ||||||
|  | @ -1,6 +1,4 @@ | ||||||
| <!DOCTYPE html> | {{ template "page/head_start" .}} | ||||||
| <html lang="en"> |  | ||||||
| {{template "head" .}} |  | ||||||
| <style> | <style> | ||||||
|   .ant-table:not(.ant-table-expanded-row .ant-table) { |   .ant-table:not(.ant-table-expanded-row .ant-table) { | ||||||
|     outline: 1px solid #f0f0f0; |     outline: 1px solid #f0f0f0; | ||||||
|  | @ -79,6 +77,19 @@ | ||||||
|     max-width: 200px; |     max-width: 200px; | ||||||
|     overflow: hidden; |     overflow: hidden; | ||||||
|   } |   } | ||||||
|  |   .client-comment { | ||||||
|  |     font-size: 12px; | ||||||
|  |     opacity: 0.75; | ||||||
|  |     cursor: help; | ||||||
|  |   } | ||||||
|  |   .client-email { | ||||||
|  |     font-weight: 500; | ||||||
|  |   } | ||||||
|  |   .client-popup-item { | ||||||
|  |     display: flex; | ||||||
|  |     align-items: center; | ||||||
|  |     gap: 5px; | ||||||
|  |   } | ||||||
|   .online-animation .ant-badge-status-dot { |   .online-animation .ant-badge-status-dot { | ||||||
|     animation: onlineAnimation 1.2s linear infinite; |     animation: onlineAnimation 1.2s linear infinite; | ||||||
|   } |   } | ||||||
|  | @ -130,15 +141,16 @@ | ||||||
|     padding: 12px 2px; |     padding: 12px 2px; | ||||||
|   } |   } | ||||||
| </style> | </style> | ||||||
|  | {{ template "page/head_end" .}} | ||||||
| 
 | 
 | ||||||
| <body> | {{ template "page/body_start" .}} | ||||||
| <a-layout id="app" v-cloak :class="themeSwitcher.currentTheme"> | <a-layout id="app" v-cloak :class="themeSwitcher.currentTheme"> | ||||||
|   <a-sidebar></a-sidebar> |   <a-sidebar></a-sidebar> | ||||||
|   <a-layout id="content-layout"> |   <a-layout id="content-layout"> | ||||||
|     <a-layout-content> |     <a-layout-content> | ||||||
|       <a-spin :spinning="spinning" :delay="500" tip='{{ i18n "loading"}}'> |       <a-spin :spinning="loadingStates.spinning" :delay="500" tip='{{ i18n "loading"}}'> | ||||||
|         <transition name="list" appear> |         <transition name="list" appear> | ||||||
|           <a-alert type="error" v-if="showAlert" :style="{ marginBottom: '10px' }" |           <a-alert type="error" v-if="showAlert && loadingStates.fetched" :style="{ marginBottom: '10px' }" | ||||||
|             message='{{ i18n "secAlertTitle" }}' |             message='{{ i18n "secAlertTitle" }}' | ||||||
|             color="red" |             color="red" | ||||||
|             description='{{ i18n "secAlertSsl" }}' |             description='{{ i18n "secAlertSsl" }}' | ||||||
|  | @ -146,6 +158,13 @@ | ||||||
|           </a-alert> |           </a-alert> | ||||||
|         </transition> |         </transition> | ||||||
|         <transition name="list" appear> |         <transition name="list" appear> | ||||||
|  |           <a-row v-if="!loadingStates.fetched"> | ||||||
|  |             <a-card :style="{ textAlign: 'center', padding: '30px 0', marginTop: '10px', background: 'transparent', border: 'none' }"> | ||||||
|  |               <a-spin tip='{{ i18n "loading" }}'></a-spin> | ||||||
|  |             </a-card> | ||||||
|  |           </a-row> | ||||||
|  |           <a-row :gutter="[isMobile ? 8 : 16, isMobile ? 0 : 12]" v-else> | ||||||
|  |             <a-col> | ||||||
|               <a-card size="small" :style="{ padding: '16px' }" hoverable> |               <a-card size="small" :style="{ padding: '16px' }" hoverable> | ||||||
|                 <a-row> |                 <a-row> | ||||||
|                   <a-col :sm="12" :md="6"> |                   <a-col :sm="12" :md="6"> | ||||||
|  | @ -208,8 +227,8 @@ | ||||||
|                   </a-col> |                   </a-col> | ||||||
|                 </a-row> |                 </a-row> | ||||||
|               </a-card> |               </a-card> | ||||||
|         </transition> |             </a-col> | ||||||
|         <transition name="list" appear> |             <a-col> | ||||||
|               <a-card hoverable> |               <a-card hoverable> | ||||||
|                 <template #title> |                 <template #title> | ||||||
|                   <a-space direction="horizontal"> |                   <a-space direction="horizontal"> | ||||||
|  | @ -382,25 +401,57 @@ | ||||||
|                         <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="themeSwitcher.currentTheme"> |                         <a-popover title='{{ i18n "disabled" }}' :overlay-class-name="themeSwitcher.currentTheme"> | ||||||
|                           <template slot="content"> |                           <template slot="content"> | ||||||
|                         <div v-for="clientEmail in clientCount[dbInbound.id].deactive"><span>[[ clientEmail ]]</span></div> |                             <div v-for="clientEmail in clientCount[dbInbound.id].deactive" :key="clientEmail" class="client-popup-item"> | ||||||
|  |                               <span>[[ clientEmail ]]</span> | ||||||
|  |                               <a-tooltip :overlay-class-name="themeSwitcher.currentTheme"> | ||||||
|  |                                 <template #title> | ||||||
|  |                                   [[ getClientWithComment(clientEmail, dbInbound.id).comment ]] | ||||||
|  |                                 </template> | ||||||
|  |                                 <a-icon type="message" v-if="getClientWithComment(clientEmail, dbInbound.id).comment"></a-icon> | ||||||
|  |                               </a-tooltip> | ||||||
|  |                             </div> | ||||||
|                           </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="themeSwitcher.currentTheme"> |                         <a-popover title='{{ i18n "depleted" }}' :overlay-class-name="themeSwitcher.currentTheme"> | ||||||
|                           <template slot="content"> |                           <template slot="content"> | ||||||
|                         <div v-for="clientEmail in clientCount[dbInbound.id].depleted"><span>[[ clientEmail ]]</span></div> |                             <div v-for="clientEmail in clientCount[dbInbound.id].depleted" :key="clientEmail" class="client-popup-item"> | ||||||
|  |                               <span>[[ clientEmail ]]</span> | ||||||
|  |                               <a-tooltip :overlay-class-name="themeSwitcher.currentTheme"> | ||||||
|  |                                 <template #title> | ||||||
|  |                                   [[ getClientWithComment(clientEmail, dbInbound.id).comment ]] | ||||||
|  |                                 </template> | ||||||
|  |                                 <a-icon type="message" v-if="getClientWithComment(clientEmail, dbInbound.id).comment"></a-icon> | ||||||
|  |                               </a-tooltip> | ||||||
|  |                             </div> | ||||||
|                           </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="themeSwitcher.currentTheme"> |                         <a-popover title='{{ i18n "depletingSoon" }}' :overlay-class-name="themeSwitcher.currentTheme"> | ||||||
|                           <template slot="content"> |                           <template slot="content"> | ||||||
|                         <div v-for="clientEmail in clientCount[dbInbound.id].expiring"><span>[[ clientEmail ]]</span></div> |                             <div v-for="clientEmail in clientCount[dbInbound.id].expiring" :key="clientEmail" class="client-popup-item"> | ||||||
|  |                               <span>[[ clientEmail ]]</span> | ||||||
|  |                               <a-tooltip :overlay-class-name="themeSwitcher.currentTheme"> | ||||||
|  |                                 <template #title> | ||||||
|  |                                   [[ getClientWithComment(clientEmail, dbInbound.id).comment ]] | ||||||
|  |                                 </template> | ||||||
|  |                                 <a-icon type="message" v-if="getClientWithComment(clientEmail, dbInbound.id).comment"></a-icon> | ||||||
|  |                               </a-tooltip> | ||||||
|  |                             </div> | ||||||
|                           </template> |                           </template> | ||||||
|                           <a-tag :style="{ margin: '0', padding: '0 2px' }" color="orange" v-if="clientCount[dbInbound.id].expiring.length">[[ clientCount[dbInbound.id].expiring.length ]]</a-tag> |                           <a-tag :style="{ margin: '0', padding: '0 2px' }" color="orange" v-if="clientCount[dbInbound.id].expiring.length">[[ clientCount[dbInbound.id].expiring.length ]]</a-tag> | ||||||
|                         </a-popover> |                         </a-popover> | ||||||
|                         <a-popover title='{{ i18n "online" }}' :overlay-class-name="themeSwitcher.currentTheme"> |                         <a-popover title='{{ i18n "online" }}' :overlay-class-name="themeSwitcher.currentTheme"> | ||||||
|                           <template slot="content"> |                           <template slot="content"> | ||||||
|                         <div v-for="clientEmail in clientCount[dbInbound.id].online"><span>[[ clientEmail ]]</span></div> |                             <div v-for="clientEmail in clientCount[dbInbound.id].online" :key="clientEmail" class="client-popup-item"> | ||||||
|  |                               <span>[[ clientEmail ]]</span> | ||||||
|  |                               <a-tooltip :overlay-class-name="themeSwitcher.currentTheme"> | ||||||
|  |                                 <template #title> | ||||||
|  |                                   [[ getClientWithComment(clientEmail, dbInbound.id).comment ]] | ||||||
|  |                                 </template> | ||||||
|  |                                 <a-icon type="message" v-if="getClientWithComment(clientEmail, dbInbound.id).comment"></a-icon> | ||||||
|  |                               </a-tooltip> | ||||||
|  |                             </div> | ||||||
|                           </template> |                           </template> | ||||||
|                           <a-tag :style="{ margin: '0', padding: '0 2px' }" color="blue" v-if="clientCount[dbInbound.id].online.length">[[ clientCount[dbInbound.id].online.length ]]</a-tag> |                           <a-tag :style="{ margin: '0', padding: '0 2px' }" color="blue" v-if="clientCount[dbInbound.id].online.length">[[ clientCount[dbInbound.id].online.length ]]</a-tag> | ||||||
|                         </a-popover> |                         </a-popover> | ||||||
|  | @ -479,25 +530,57 @@ | ||||||
|                                 <a-tag :style="{ margin: '0' }" color="blue">[[ clientCount[dbInbound.id].clients ]]</a-tag> |                                 <a-tag :style="{ margin: '0' }" color="blue">[[ clientCount[dbInbound.id].clients ]]</a-tag> | ||||||
|                                 <a-popover title='{{ i18n "disabled" }}' :overlay-class-name="themeSwitcher.currentTheme"> |                                 <a-popover title='{{ i18n "disabled" }}' :overlay-class-name="themeSwitcher.currentTheme"> | ||||||
|                                   <template slot="content"> |                                   <template slot="content"> | ||||||
|                                 <p v-for="clientEmail in clientCount[dbInbound.id].deactive">[[ clientEmail ]]</p> |                                     <div v-for="clientEmail in clientCount[dbInbound.id].deactive" :key="clientEmail" class="client-popup-item"> | ||||||
|  |                                       <span>[[ clientEmail ]]</span> | ||||||
|  |                                       <a-tooltip :overlay-class-name="themeSwitcher.currentTheme"> | ||||||
|  |                                         <template #title> | ||||||
|  |                                           [[ getClientWithComment(clientEmail, dbInbound.id).comment ]] | ||||||
|  |                                         </template> | ||||||
|  |                                         <a-icon type="message" v-if="getClientWithComment(clientEmail, dbInbound.id).comment"></a-icon> | ||||||
|  |                                       </a-tooltip> | ||||||
|  |                                     </div> | ||||||
|                                   </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="themeSwitcher.currentTheme"> |                                 <a-popover title='{{ i18n "depleted" }}' :overlay-class-name="themeSwitcher.currentTheme"> | ||||||
|                                   <template slot="content"> |                                   <template slot="content"> | ||||||
|                                 <p v-for="clientEmail in clientCount[dbInbound.id].depleted">[[ clientEmail ]]</p> |                                     <div v-for="clientEmail in clientCount[dbInbound.id].depleted" :key="clientEmail" class="client-popup-item"> | ||||||
|  |                                       <span>[[ clientEmail ]]</span> | ||||||
|  |                                       <a-tooltip :overlay-class-name="themeSwitcher.currentTheme"> | ||||||
|  |                                         <template #title> | ||||||
|  |                                           [[ getClientWithComment(clientEmail, dbInbound.id).comment ]] | ||||||
|  |                                         </template> | ||||||
|  |                                         <a-icon type="message" v-if="getClientWithComment(clientEmail, dbInbound.id).comment"></a-icon> | ||||||
|  |                                       </a-tooltip> | ||||||
|  |                                     </div> | ||||||
|                                   </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="themeSwitcher.currentTheme"> |                                 <a-popover title='{{ i18n "depletingSoon" }}' :overlay-class-name="themeSwitcher.currentTheme"> | ||||||
|                                   <template slot="content"> |                                   <template slot="content"> | ||||||
|                                 <p v-for="clientEmail in clientCount[dbInbound.id].expiring">[[ clientEmail ]]</p> |                                     <div v-for="clientEmail in clientCount[dbInbound.id].expiring" :key="clientEmail" class="client-popup-item"> | ||||||
|  |                                       <span>[[ clientEmail ]]</span> | ||||||
|  |                                       <a-tooltip :overlay-class-name="themeSwitcher.currentTheme"> | ||||||
|  |                                         <template #title> | ||||||
|  |                                           [[ getClientWithComment(clientEmail, dbInbound.id).comment ]] | ||||||
|  |                                         </template> | ||||||
|  |                                         <a-icon type="message" v-if="getClientWithComment(clientEmail, dbInbound.id).comment"></a-icon> | ||||||
|  |                                       </a-tooltip> | ||||||
|  |                                     </div> | ||||||
|                                   </template> |                                   </template> | ||||||
|                                   <a-tag :style="{ margin: '0', padding: '0 2px' }" color="orange" v-if="clientCount[dbInbound.id].expiring.length">[[ clientCount[dbInbound.id].expiring.length ]]</a-tag> |                                   <a-tag :style="{ margin: '0', padding: '0 2px' }" color="orange" v-if="clientCount[dbInbound.id].expiring.length">[[ clientCount[dbInbound.id].expiring.length ]]</a-tag> | ||||||
|                                 </a-popover> |                                 </a-popover> | ||||||
|                                 <a-popover title='{{ i18n "online" }}' :overlay-class-name="themeSwitcher.currentTheme"> |                                 <a-popover title='{{ i18n "online" }}' :overlay-class-name="themeSwitcher.currentTheme"> | ||||||
|                                   <template slot="content"> |                                   <template slot="content"> | ||||||
|                                 <p v-for="clientEmail in clientCount[dbInbound.id].online">[[ clientEmail ]]</p> |                                     <div v-for="clientEmail in clientCount[dbInbound.id].online" :key="clientEmail" class="client-popup-item"> | ||||||
|  |                                       <span>[[ clientEmail ]]</span> | ||||||
|  |                                       <a-tooltip :overlay-class-name="themeSwitcher.currentTheme"> | ||||||
|  |                                         <template #title> | ||||||
|  |                                           [[ getClientWithComment(clientEmail, dbInbound.id).comment ]] | ||||||
|  |                                         </template> | ||||||
|  |                                         <a-icon type="message" v-if="getClientWithComment(clientEmail, dbInbound.id).comment"></a-icon> | ||||||
|  |                                       </a-tooltip> | ||||||
|  |                                     </div> | ||||||
|                                   </template> |                                   </template> | ||||||
|                                   <a-tag :style="{ margin: '0', padding: '0 2px' }" color="green" v-if="clientCount[dbInbound.id].online.length">[[ clientCount[dbInbound.id].online.length ]]</a-tag> |                                   <a-tag :style="{ margin: '0', padding: '0 2px' }" color="green" v-if="clientCount[dbInbound.id].online.length">[[ clientCount[dbInbound.id].online.length ]]</a-tag> | ||||||
|                                 </a-popover> |                                 </a-popover> | ||||||
|  | @ -575,12 +658,14 @@ | ||||||
|                   </a-table> |                   </a-table> | ||||||
|                 </a-space> |                 </a-space> | ||||||
|               </a-card>  |               </a-card>  | ||||||
|  |             </a-col> | ||||||
|  |           </a-row> | ||||||
|         </transition> |         </transition> | ||||||
|       </a-spin> |       </a-spin> | ||||||
|     </a-layout-content> |     </a-layout-content> | ||||||
|   </a-layout> |   </a-layout> | ||||||
| </a-layout> | </a-layout> | ||||||
| {{template "js" .}} | {{template "page/body_scripts" .}} | ||||||
| <script src="{{ .base_path }}assets/qrcode/qrious2.min.js?{{ .cur_ver }}"></script> | <script src="{{ .base_path }}assets/qrcode/qrious2.min.js?{{ .cur_ver }}"></script> | ||||||
| <script src="{{ .base_path }}assets/uri/URI.min.js?{{ .cur_ver }}"></script> | <script src="{{ .base_path }}assets/uri/URI.min.js?{{ .cur_ver }}"></script> | ||||||
| <script src="{{ .base_path }}assets/js/model/inbound.js?{{ .cur_ver }}"></script> | <script src="{{ .base_path }}assets/js/model/inbound.js?{{ .cur_ver }}"></script> | ||||||
|  | @ -589,6 +674,13 @@ | ||||||
| {{template "component/aThemeSwitch" .}} | {{template "component/aThemeSwitch" .}} | ||||||
| {{template "component/aCustomStatistic" .}} | {{template "component/aCustomStatistic" .}} | ||||||
| {{template "component/aPersianDatepicker" .}} | {{template "component/aPersianDatepicker" .}} | ||||||
|  | {{template "modals/inboundModal"}} | ||||||
|  | {{template "modals/promptModal"}} | ||||||
|  | {{template "modals/qrcodeModal"}} | ||||||
|  | {{template "modals/textModal"}} | ||||||
|  | {{template "modals/inboundInfoModal"}} | ||||||
|  | {{template "modals/clientsModal"}} | ||||||
|  | {{template "modals/clientsBulkModal"}} | ||||||
| <script> | <script> | ||||||
|     const columns = [{ |     const columns = [{ | ||||||
|         title: "ID", |         title: "ID", | ||||||
|  | @ -683,7 +775,10 @@ | ||||||
|         data: { |         data: { | ||||||
|             themeSwitcher, |             themeSwitcher, | ||||||
|             persianDatepicker, |             persianDatepicker, | ||||||
|             spinning: false, |             loadingStates: { | ||||||
|  |               fetched: false, | ||||||
|  |               spinning: false | ||||||
|  |             }, | ||||||
|             inbounds: [], |             inbounds: [], | ||||||
|             dbInbounds: [], |             dbInbounds: [], | ||||||
|             searchKey: '', |             searchKey: '', | ||||||
|  | @ -714,7 +809,18 @@ | ||||||
|         }, |         }, | ||||||
|         methods: { |         methods: { | ||||||
|             loading(spinning = true) { |             loading(spinning = true) { | ||||||
|                 this.spinning = spinning; |                 this.loadingStates.spinning = spinning; | ||||||
|  |             }, | ||||||
|  |             getClientWithComment(email, inboundId) { | ||||||
|  |                 const dbInbound = this.dbInbounds.find(inbound => inbound.id === inboundId); | ||||||
|  |                 if (!dbInbound) return { email, comment: '' }; | ||||||
|  |                  | ||||||
|  |                 const inboundSettings = JSON.parse(dbInbound.settings); | ||||||
|  |                 if (inboundSettings.clients) { | ||||||
|  |                     const client = inboundSettings.clients.find(c => c.email === email); | ||||||
|  |                     return client ? { email: client.email, comment: client.comment || '' } : { email, comment: '' }; | ||||||
|  |                 } | ||||||
|  |                 return { email, comment: '' }; | ||||||
|             }, |             }, | ||||||
|             async getDBInbounds() { |             async getDBInbounds() { | ||||||
|                 this.refreshing = true; |                 this.refreshing = true; | ||||||
|  | @ -725,6 +831,7 @@ | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|                 await this.getOnlineUsers(); |                 await this.getOnlineUsers(); | ||||||
|  |                  | ||||||
|                 this.setInbounds(msg.obj); |                 this.setInbounds(msg.obj); | ||||||
|                 setTimeout(() => { |                 setTimeout(() => { | ||||||
|                     this.refreshing = false; |                     this.refreshing = false; | ||||||
|  | @ -776,6 +883,9 @@ | ||||||
|                         this.clientCount[inbound.id] = this.getClientCounts(inbound, to_inbound); |                         this.clientCount[inbound.id] = this.getClientCounts(inbound, to_inbound); | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|  |                 if (!this.loadingStates.fetched) { | ||||||
|  |                     this.loadingStates.fetched = true | ||||||
|  |                 } | ||||||
|                 if(this.enableFilter){ |                 if(this.enableFilter){ | ||||||
|                     this.filterInbounds(); |                     this.filterInbounds(); | ||||||
|                 } else { |                 } else { | ||||||
|  | @ -1447,9 +1557,9 @@ | ||||||
|             }, |             }, | ||||||
|             async manualRefresh() { |             async manualRefresh() { | ||||||
|                 if (!this.refreshing) { |                 if (!this.refreshing) { | ||||||
|                     this.spinning = true; |                     this.loadingStates.spinning = true; | ||||||
|                     await this.getDBInbounds(); |                     await this.getDBInbounds(); | ||||||
|                     this.spinning = false; |                     this.loadingStates.spinning = false; | ||||||
|                 } |                 } | ||||||
|             }, |             }, | ||||||
|             pagination(obj){ |             pagination(obj){ | ||||||
|  | @ -1519,13 +1629,4 @@ | ||||||
|         }, |         }, | ||||||
|     }); |     }); | ||||||
| </script> | </script> | ||||||
| 
 | {{ template "page/body_end" .}} | ||||||
| {{template "modals/inboundModal"}} |  | ||||||
| {{template "modals/promptModal"}} |  | ||||||
| {{template "modals/qrcodeModal"}} |  | ||||||
| {{template "modals/textModal"}} |  | ||||||
| {{template "modals/inboundInfoModal"}} |  | ||||||
| {{template "modals/clientsModal"}} |  | ||||||
| {{template "modals/clientsBulkModal"}} |  | ||||||
| </body> |  | ||||||
| </html> |  | ||||||
|  | @ -1,20 +1,9 @@ | ||||||
| <!DOCTYPE html> | {{ template "page/head_start" .}} | ||||||
| <html lang="en"> |  | ||||||
| {{template "head" .}} |  | ||||||
| <style> | <style> | ||||||
|   @media (min-width: 769px) { |   @media (min-width: 769px) { | ||||||
|     .ant-layout-content { |     .ant-layout-content { | ||||||
|       margin: 24px 16px; |       margin: 24px 16px; | ||||||
|     } |     } | ||||||
|     .ant-card-hoverable { |  | ||||||
|       margin-inline: 0.3rem; |  | ||||||
|     } |  | ||||||
|     .ant-alert-error { |  | ||||||
|       margin-inline: 0.3rem; |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|   .ant-col-sm-24 { |  | ||||||
|     margin-top: 10px; |  | ||||||
|   } |   } | ||||||
|   .ant-card-dark h2 { |   .ant-card-dark h2 { | ||||||
|     color: var(--dark-color-text-primary); |     color: var(--dark-color-text-primary); | ||||||
|  | @ -79,15 +68,16 @@ | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| </style> | </style> | ||||||
|  | {{ template "page/head_end" .}} | ||||||
| 
 | 
 | ||||||
| <body> | {{ template "page/body_start" .}} | ||||||
|   <a-layout id="app" v-cloak :class="themeSwitcher.currentTheme"> | <a-layout id="app" v-cloak :class="themeSwitcher.currentTheme"> | ||||||
|   <a-sidebar></a-sidebar> |   <a-sidebar></a-sidebar> | ||||||
|   <a-layout id="content-layout"> |   <a-layout id="content-layout"> | ||||||
|     <a-layout-content> |     <a-layout-content> | ||||||
|         <a-spin :spinning="spinning" :delay="200" :tip="loadingTip"> |       <a-spin :spinning="loadingStates.spinning" :delay="200" :tip="loadingTip"> | ||||||
|         <transition name="list" appear> |         <transition name="list" appear> | ||||||
|             <a-alert type="error" v-if="showAlert" :style="{ marginBottom: '10px' }" |           <a-alert type="error" v-if="showAlert && loadingStates.fetched" :style="{ marginBottom: '10px' }" | ||||||
|             message='{{ i18n "secAlertTitle" }}' |             message='{{ i18n "secAlertTitle" }}' | ||||||
|             color="red" |             color="red" | ||||||
|             description='{{ i18n "secAlertSsl" }}' |             description='{{ i18n "secAlertSsl" }}' | ||||||
|  | @ -96,15 +86,15 @@ | ||||||
|         </transition> |         </transition> | ||||||
|         <transition name="list" appear> |         <transition name="list" appear> | ||||||
|           <template> |           <template> | ||||||
|               <a-row v-if="!status.isLoaded"> |             <a-row v-if="!loadingStates.fetched"> | ||||||
|               <a-card :style="{ textAlign: 'center', padding: '30px 0', marginTop: '10px', background: 'transparent', border: 'none' }"> |               <a-card :style="{ textAlign: 'center', padding: '30px 0', marginTop: '10px', background: 'transparent', border: 'none' }"> | ||||||
|                 <a-spin tip='{{ i18n "loading" }}'></a-spin> |                 <a-spin tip='{{ i18n "loading" }}'></a-spin> | ||||||
|               </a-card> |               </a-card> | ||||||
|             </a-row> |             </a-row> | ||||||
|               <a-row v-else> |             <a-row :gutter="[isMobile ? 8 : 16, isMobile ? 0 : 12]" v-else> | ||||||
|                 <a-row> |               <a-col> | ||||||
|                 <a-card hoverable> |                 <a-card hoverable> | ||||||
|                     <a-row> |                   <a-row :gutter="[0, isMobile ? 16 : 0]"> | ||||||
|                     <a-col :sm="24" :md="12"> |                     <a-col :sm="24" :md="12"> | ||||||
|                       <a-row> |                       <a-row> | ||||||
|                         <a-col :span="12" :style="{ textAlign: 'center' }"> |                         <a-col :span="12" :style="{ textAlign: 'center' }"> | ||||||
|  | @ -154,7 +144,7 @@ | ||||||
|                     </a-col> |                     </a-col> | ||||||
|                   </a-row> |                   </a-row> | ||||||
|                 </a-card> |                 </a-card> | ||||||
|                 </a-row> |               </a-col> | ||||||
|               <a-col :sm="24" :lg="12"> |               <a-col :sm="24" :lg="12"> | ||||||
|                 <a-card hoverable> |                 <a-card hoverable> | ||||||
|                   <template #title> |                   <template #title> | ||||||
|  | @ -237,7 +227,7 @@ | ||||||
|                     </a-tag> |                     </a-tag> | ||||||
|                   </a> |                   </a> | ||||||
|                   <a rel="noopener" href="https://github.com/MHSanaei/3x-ui/wiki" target="_blank"> |                   <a rel="noopener" href="https://github.com/MHSanaei/3x-ui/wiki" target="_blank"> | ||||||
|                       <a-tag> |                     <a-tag color="purple"> | ||||||
|                       <span>{{ i18n "pages.index.documentation" }}</span> |                       <span>{{ i18n "pages.index.documentation" }}</span> | ||||||
|                     </a-tag> |                     </a-tag> | ||||||
|                   </a> |                   </a> | ||||||
|  | @ -455,8 +445,8 @@ | ||||||
|       </a-list-item> |       </a-list-item> | ||||||
|     </a-list> |     </a-list> | ||||||
|   </a-modal> |   </a-modal> | ||||||
|   </a-layout> | </a-layout> | ||||||
| {{template "js" .}} | {{template "page/body_scripts" .}} | ||||||
| {{template "component/aSidebar" .}} | {{template "component/aSidebar" .}} | ||||||
| {{template "component/aThemeSwitch" .}} | {{template "component/aThemeSwitch" .}} | ||||||
| {{template "component/aCustomStatistic" .}} | {{template "component/aCustomStatistic" .}} | ||||||
|  | @ -489,7 +479,7 @@ | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     class Status { |     class Status { | ||||||
|         constructor(data, isLoaded = false) { |         constructor(data) { | ||||||
|             this.cpu = new CurTotal(0, 0); |             this.cpu = new CurTotal(0, 0); | ||||||
|             this.cpuCores = 0; |             this.cpuCores = 0; | ||||||
|             this.logicalPro = 0; |             this.logicalPro = 0; | ||||||
|  | @ -513,7 +503,6 @@ | ||||||
|               return; |               return; | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             this.isLoaded = isLoaded; |  | ||||||
|             this.cpu = new CurTotal(data.cpu, 100); |             this.cpu = new CurTotal(data.cpu, 100); | ||||||
|             this.cpuCores = data.cpuCores; |             this.cpuCores = data.cpuCores; | ||||||
|             this.logicalPro = data.logicalPro; |             this.logicalPro = data.logicalPro; | ||||||
|  | @ -633,32 +622,39 @@ | ||||||
|         mixins: [MediaQueryMixin], |         mixins: [MediaQueryMixin], | ||||||
|         data: { |         data: { | ||||||
|             themeSwitcher, |             themeSwitcher, | ||||||
|  |             loadingStates: { | ||||||
|  |               fetched: false, | ||||||
|  |               spinning: false | ||||||
|  |             }, | ||||||
|             status: new Status(), |             status: new Status(), | ||||||
|             versionModal, |             versionModal, | ||||||
|             logModal, |             logModal, | ||||||
|             backupModal, |             backupModal, | ||||||
|             spinning: false, |  | ||||||
|             loadingTip: '{{ i18n "loading"}}', |             loadingTip: '{{ i18n "loading"}}', | ||||||
|             showAlert: false, |             showAlert: false, | ||||||
|             showIp: false |             showIp: false | ||||||
|         }, |         }, | ||||||
|         methods: { |         methods: { | ||||||
|             loading(spinning, tip = '{{ i18n "loading"}}') { |             loading(spinning, tip = '{{ i18n "loading"}}') { | ||||||
|                 this.spinning = spinning; |                 this.loadingStates.spinning = spinning; | ||||||
|                 this.loadingTip = tip; |                 this.loadingTip = tip; | ||||||
|             }, |             }, | ||||||
|             async getStatus() { |             async getStatus() { | ||||||
|                 try { |                 try { | ||||||
|                     const msg = await HttpUtil.post('/server/status'); |                     const msg = await HttpUtil.post('/server/status'); | ||||||
|                     if (msg.success) { |                     if (msg.success) { | ||||||
|  |                         if (!this.loadingStates.fetched) { | ||||||
|  |                             this.loadingStates.fetched = true; | ||||||
|  |                         } | ||||||
|  | 
 | ||||||
|                         this.setStatus(msg.obj, true); |                         this.setStatus(msg.obj, true); | ||||||
|                     } |                     } | ||||||
|                 } catch (e) { |                 } catch (e) { | ||||||
|                     console.error("Failed to get status:", e); |                     console.error("Failed to get status:", e); | ||||||
|                 } |                 } | ||||||
|             }, |             }, | ||||||
|             setStatus(data, isLoaded = false) { |             setStatus(data) { | ||||||
|               this.status = new Status(data, isLoaded); |                 this.status = new Status(data); | ||||||
|             }, |             }, | ||||||
|             async openSelectV2rayVersion() { |             async openSelectV2rayVersion() { | ||||||
|                 this.loading(true); |                 this.loading(true); | ||||||
|  | @ -788,5 +784,4 @@ | ||||||
|         }, |         }, | ||||||
|     }); |     }); | ||||||
| </script> | </script> | ||||||
| </body> | {{ template "page/body_end" .}} | ||||||
| </html> |  | ||||||
|  | @ -1,6 +1,4 @@ | ||||||
| <!DOCTYPE html> | {{ template "page/head_start" .}} | ||||||
| <html lang="en"> |  | ||||||
| {{template "head" .}} |  | ||||||
| <style> | <style> | ||||||
|   html * { |   html * { | ||||||
|     -webkit-font-smoothing: antialiased; |     -webkit-font-smoothing: antialiased; | ||||||
|  | @ -453,9 +451,10 @@ | ||||||
|     margin: 2px 0 4px; |     margin: 2px 0 4px; | ||||||
|   } |   } | ||||||
| </style> | </style> | ||||||
|  | {{ template "page/head_end" .}} | ||||||
| 
 | 
 | ||||||
| <body> | {{ template "page/body_start" .}} | ||||||
|   <a-layout id="app" v-cloak :class="themeSwitcher.currentTheme"> | <a-layout id="app" v-cloak :class="themeSwitcher.currentTheme"> | ||||||
|   <transition name="list" appear> |   <transition name="list" appear> | ||||||
|     <a-layout-content class="under" :style="{ minHeight: '0' }"> |     <a-layout-content class="under" :style="{ minHeight: '0' }"> | ||||||
|       <div class="waves-header"> |       <div class="waves-header"> | ||||||
|  | @ -542,10 +541,10 @@ | ||||||
|       </a-row> |       </a-row> | ||||||
|     </a-layout-content> |     </a-layout-content> | ||||||
|   </transition> |   </transition> | ||||||
|   </a-layout> | </a-layout> | ||||||
|   {{template "js" .}} | {{template "page/body_scripts" .}} | ||||||
|   {{template "component/aThemeSwitch" .}} | {{template "component/aThemeSwitch" .}} | ||||||
|   <script> | <script> | ||||||
|   const app = new Vue({ |   const app = new Vue({ | ||||||
|     delimiters: ['[[', ']]'], |     delimiters: ['[[', ']]'], | ||||||
|     el: '#app', |     el: '#app', | ||||||
|  | @ -621,6 +620,5 @@ | ||||||
|       newWord.classList.add('is-visible'); |       newWord.classList.add('is-visible'); | ||||||
|     } |     } | ||||||
|   }); |   }); | ||||||
|   </script> | </script> | ||||||
| </body> | {{ template "page/body_end" .}} | ||||||
| </html> |  | ||||||
|  | @ -1,6 +1,4 @@ | ||||||
| <!DOCTYPE html> | {{ template "page/head_start" .}} | ||||||
| <html lang="en"> |  | ||||||
| {{template "head" .}} |  | ||||||
| <style> | <style> | ||||||
|   @media (min-width: 769px) { |   @media (min-width: 769px) { | ||||||
|     .ant-layout-content { |     .ant-layout-content { | ||||||
|  | @ -60,14 +58,16 @@ | ||||||
|     margin-block-end: 12px; |     margin-block-end: 12px; | ||||||
|   } |   } | ||||||
| </style> | </style> | ||||||
| <body> | {{ template "page/head_end" .}} | ||||||
|   <a-layout id="app" v-cloak :class="themeSwitcher.currentTheme"> | 
 | ||||||
|  | {{ template "page/body_start" .}} | ||||||
|  | <a-layout id="app" v-cloak :class="themeSwitcher.currentTheme"> | ||||||
|   <a-sidebar></a-sidebar> |   <a-sidebar></a-sidebar> | ||||||
|   <a-layout id="content-layout"> |   <a-layout id="content-layout"> | ||||||
|     <a-layout-content> |     <a-layout-content> | ||||||
|         <a-spin :spinning="spinning" :delay="500" tip='{{ i18n "loading"}}'> |       <a-spin :spinning="loadingStates.spinning" :delay="500" tip='{{ i18n "loading"}}'> | ||||||
|         <transition name="list" appear> |         <transition name="list" appear> | ||||||
|             <a-alert type="error" v-if="confAlerts.length>0" :style="{ marginBottom: '10px' }" |           <a-alert type="error" v-if="confAlerts.length>0 && loadingStates.fetched" :style="{ marginBottom: '10px' }" | ||||||
|               message='{{ i18n "secAlertTitle" }}' |               message='{{ i18n "secAlertTitle" }}' | ||||||
|               color="red" |               color="red" | ||||||
|               show-icon closable> |               show-icon closable> | ||||||
|  | @ -77,8 +77,16 @@ | ||||||
|             </template> |             </template> | ||||||
|           </a-alert> |           </a-alert> | ||||||
|         </transition> |         </transition> | ||||||
|           <a-space direction="vertical"> |         <transition name="list" appear> | ||||||
|             <a-card hoverable :style="{ marginBottom: '.5rem', overflowX: 'hidden' }"> |           <template> | ||||||
|  |             <a-row v-if="!loadingStates.fetched"> | ||||||
|  |               <a-card :style="{ textAlign: 'center', padding: '30px 0', marginTop: '10px', background: 'transparent', border: 'none' }"> | ||||||
|  |                 <a-spin tip='{{ i18n "loading" }}'></a-spin> | ||||||
|  |               </a-card> | ||||||
|  |             </a-row> | ||||||
|  |             <a-row :gutter="[isMobile ? 8 : 16, isMobile ? 0 : 12]" v-else> | ||||||
|  |               <a-col> | ||||||
|  |                 <a-card hoverable> | ||||||
|                   <a-row :style="{ display: 'flex', flexWrap: 'wrap', alignItems: 'center' }"> |                   <a-row :style="{ display: 'flex', flexWrap: 'wrap', alignItems: 'center' }"> | ||||||
|                     <a-col :xs="24" :sm="10" :style="{ padding: '4px' }"> |                     <a-col :xs="24" :sm="10" :style="{ padding: '4px' }"> | ||||||
|                       <a-space direction="horizontal"> |                       <a-space direction="horizontal"> | ||||||
|  | @ -99,29 +107,54 @@ | ||||||
|                     </a-col> |                     </a-col> | ||||||
|                   </a-row> |                   </a-row> | ||||||
|                 </a-card> |                 </a-card> | ||||||
|  |               </a-col> | ||||||
|  |               <a-col> | ||||||
|                 <a-tabs default-active-key="1"> |                 <a-tabs default-active-key="1"> | ||||||
|               <a-tab-pane key="1" tab='{{ i18n "pages.settings.panelSettings" }}' :style="{ paddingTop: '20px' }"> |                   <a-tab-pane key="1" :style="{ paddingTop: '20px' }"> | ||||||
|  |                     <template #tab> | ||||||
|  |                       <a-icon type="setting"></a-icon> | ||||||
|  |                       <span>{{ i18n "pages.settings.panelSettings" }}</span> | ||||||
|  |                     </template> | ||||||
|                     {{ template "settings/panel/general" . }} |                     {{ template "settings/panel/general" . }} | ||||||
|                   </a-tab-pane> |                   </a-tab-pane> | ||||||
|               <a-tab-pane key="2" tab='{{ i18n "pages.settings.securitySettings" }}' :style="{ paddingTop: '20px' }"> |                   <a-tab-pane key="2" :style="{ paddingTop: '20px' }"> | ||||||
|  |                     <template #tab> | ||||||
|  |                       <a-icon type="safety"></a-icon> | ||||||
|  |                       <span>{{ i18n "pages.settings.securitySettings" }}</span> | ||||||
|  |                     </template> | ||||||
|                     {{ template "settings/panel/security" . }} |                     {{ template "settings/panel/security" . }} | ||||||
|                   </a-tab-pane> |                   </a-tab-pane> | ||||||
|               <a-tab-pane key="3" tab='{{ i18n "pages.settings.TGBotSettings" }}' :style="{ paddingTop: '20px' }"> |                   <a-tab-pane key="3" :style="{ paddingTop: '20px' }"> | ||||||
|  |                     <template #tab> | ||||||
|  |                       <a-icon type="message"></a-icon> | ||||||
|  |                       <span>{{ i18n "pages.settings.TGBotSettings" }}</span> | ||||||
|  |                     </template> | ||||||
|                     {{ template "settings/panel/telegram" . }} |                     {{ template "settings/panel/telegram" . }} | ||||||
|                   </a-tab-pane> |                   </a-tab-pane> | ||||||
|               <a-tab-pane key="4" tab='{{ i18n "pages.settings.subSettings" }}' :style="{ paddingTop: '20px' }"> |                   <a-tab-pane key="4" :style="{ paddingTop: '20px' }"> | ||||||
|  |                     <template #tab> | ||||||
|  |                       <a-icon type="cloud-server"></a-icon> | ||||||
|  |                       <span>{{ i18n "pages.settings.subSettings" }}</span> | ||||||
|  |                     </template> | ||||||
|                     {{ template "settings/panel/subscription/general" . }} |                     {{ template "settings/panel/subscription/general" . }} | ||||||
|                   </a-tab-pane> |                   </a-tab-pane> | ||||||
|               <a-tab-pane key="5" tab='{{ i18n "pages.settings.subSettings" }} Json' v-if="allSetting.subEnable" :style="{ paddingTop: '20px' }"> |                   <a-tab-pane key="5" v-if="allSetting.subEnable" :style="{ paddingTop: '20px' }"> | ||||||
|  |                     <template #tab> | ||||||
|  |                       <a-icon type="code"></a-icon> | ||||||
|  |                       <span>{{ i18n "pages.settings.subSettings" }} (JSON)</span> | ||||||
|  |                     </template> | ||||||
|                     {{ template "settings/panel/subscription/json" . }} |                     {{ template "settings/panel/subscription/json" . }} | ||||||
|                   </a-tab-pane> |                   </a-tab-pane> | ||||||
|                 </a-tabs> |                 </a-tabs> | ||||||
|           </a-space> |               </a-col> | ||||||
|  |             </a-row> | ||||||
|  |           </template> | ||||||
|  |         </transition> | ||||||
|       </a-spin> |       </a-spin> | ||||||
|     </a-layout-content> |     </a-layout-content> | ||||||
|   </a-layout> |   </a-layout> | ||||||
|   </a-layout> | </a-layout> | ||||||
| {{template "js" .}} | {{template "page/body_scripts" .}} | ||||||
| <script src="{{ .base_path }}assets/qrcode/qrious2.min.js?{{ .cur_ver }}"></script> | <script src="{{ .base_path }}assets/qrcode/qrious2.min.js?{{ .cur_ver }}"></script> | ||||||
| <script src="{{ .base_path }}assets/otpauth/otpauth.umd.min.js?{{ .cur_ver }}"></script> | <script src="{{ .base_path }}assets/otpauth/otpauth.umd.min.js?{{ .cur_ver }}"></script> | ||||||
| <script src="{{ .base_path }}assets/js/model/setting.js?{{ .cur_ver }}"></script> | <script src="{{ .base_path }}assets/js/model/setting.js?{{ .cur_ver }}"></script> | ||||||
|  | @ -132,10 +165,14 @@ | ||||||
| <script> | <script> | ||||||
|   const app = new Vue({ |   const app = new Vue({ | ||||||
|     delimiters: ['[[', ']]'], |     delimiters: ['[[', ']]'], | ||||||
|  |     mixins: [MediaQueryMixin], | ||||||
|     el: '#app', |     el: '#app', | ||||||
|     data: { |     data: { | ||||||
|       themeSwitcher, |       themeSwitcher, | ||||||
|       spinning: false, |       loadingStates: { | ||||||
|  |         fetched: false, | ||||||
|  |         spinning: false | ||||||
|  |       }, | ||||||
|       oldAllSetting: new AllSetting(), |       oldAllSetting: new AllSetting(), | ||||||
|       allSetting: new AllSetting(), |       allSetting: new AllSetting(), | ||||||
|       saveBtnDisable: true, |       saveBtnDisable: true, | ||||||
|  | @ -248,13 +285,16 @@ | ||||||
|     }, |     }, | ||||||
|     methods: { |     methods: { | ||||||
|       loading(spinning = true) { |       loading(spinning = true) { | ||||||
|         this.spinning = spinning; |         this.loadingStates.spinning = spinning; | ||||||
|       }, |       }, | ||||||
|       async getAllSetting() { |       async getAllSetting() { | ||||||
|         this.loading(true); |  | ||||||
|         const msg = await HttpUtil.post("/panel/setting/all"); |         const msg = await HttpUtil.post("/panel/setting/all"); | ||||||
|         this.loading(false); | 
 | ||||||
|         if (msg.success) { |         if (msg.success) { | ||||||
|  |           if (!this.loadingStates.fetched) { | ||||||
|  |             this.loadingStates.fetched = true | ||||||
|  |           } | ||||||
|  | 
 | ||||||
|           this.oldAllSetting = new AllSetting(msg.obj); |           this.oldAllSetting = new AllSetting(msg.obj); | ||||||
|           this.allSetting = new AllSetting(msg.obj); |           this.allSetting = new AllSetting(msg.obj); | ||||||
|           app.changeRemarkSample(); |           app.changeRemarkSample(); | ||||||
|  | @ -508,7 +548,7 @@ | ||||||
|           if (!this.allSetting) return []; |           if (!this.allSetting) return []; | ||||||
|           var alerts = [] |           var alerts = [] | ||||||
|           if (window.location.protocol !== "https:") alerts.push('{{ i18n "secAlertSSL" }}'); |           if (window.location.protocol !== "https:") alerts.push('{{ i18n "secAlertSSL" }}'); | ||||||
|           if (this.allSetting.webPort == 54321) alerts.push('{{ i18n "secAlertPanelPort" }}'); |           if (this.allSetting.webPort === 2053) alerts.push('{{ i18n "secAlertPanelPort" }}'); | ||||||
|           panelPath = window.location.pathname.split('/').length < 4 |           panelPath = window.location.pathname.split('/').length < 4 | ||||||
|           if (panelPath && this.allSetting.webBasePath == '/') alerts.push('{{ i18n "secAlertPanelURI" }}'); |           if (panelPath && this.allSetting.webBasePath == '/') alerts.push('{{ i18n "secAlertPanelURI" }}'); | ||||||
|           if (this.allSetting.subEnable) { |           if (this.allSetting.subEnable) { | ||||||
|  | @ -531,5 +571,4 @@ | ||||||
|     } |     } | ||||||
|   }); |   }); | ||||||
| </script> | </script> | ||||||
| </body> | {{ template "page/body_end" .}} | ||||||
| </html> |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| {{define "settings/xray/advanced"}} | {{define "settings/xray/advanced"}} | ||||||
| <a-space direction="vertical" size="small"> | <a-space direction="vertical" size="small" :style="{ marginTop: '20px' }"> | ||||||
|     <a-list-item-meta title='{{ i18n "pages.xray.Template"}}' |     <a-list-item-meta title='{{ i18n "pages.xray.Template"}}' | ||||||
|         description='{{ i18n "pages.xray.TemplateDesc"}}'></a-list-item-meta> |         description='{{ i18n "pages.xray.TemplateDesc"}}'></a-list-item-meta> | ||||||
|     <a-radio-group v-model="advSettings" @change="changeCode" button-style="solid" :style="{ margin: '10px 0' }" |     <a-radio-group v-model="advSettings" @change="changeCode" button-style="solid" :style="{ margin: '10px 0' }" | ||||||
|  |  | ||||||
|  | @ -1,74 +1,75 @@ | ||||||
| <!DOCTYPE html> | {{ template "page/head_start" .}} | ||||||
| <html lang="en"> |  | ||||||
| {{template "head" .}} |  | ||||||
| <link rel="stylesheet" href="{{ .base_path }}assets/codemirror/codemirror.min.css?{{ .cur_ver }}"> | <link rel="stylesheet" href="{{ .base_path }}assets/codemirror/codemirror.min.css?{{ .cur_ver }}"> | ||||||
| <link rel="stylesheet" href="{{ .base_path }}assets/codemirror/fold/foldgutter.css"> | <link rel="stylesheet" href="{{ .base_path }}assets/codemirror/fold/foldgutter.css"> | ||||||
| <link rel="stylesheet" href="{{ .base_path }}assets/codemirror/xq.min.css?{{ .cur_ver }}"> | <link rel="stylesheet" href="{{ .base_path }}assets/codemirror/xq.min.css?{{ .cur_ver }}"> | ||||||
| <link rel="stylesheet" href="{{ .base_path }}assets/codemirror/lint/lint.css"> | <link rel="stylesheet" href="{{ .base_path }}assets/codemirror/lint/lint.css"> | ||||||
| 
 |  | ||||||
| <script src="{{ .base_path }}assets/js/model/outbound.js?{{ .cur_ver }}"></script> |  | ||||||
| <script src="{{ .base_path }}assets/codemirror/codemirror.min.js?{{ .cur_ver }}"></script> |  | ||||||
| <script src="{{ .base_path }}assets/codemirror/javascript.js"></script> |  | ||||||
| <script src="{{ .base_path }}assets/codemirror/jshint.js"></script> |  | ||||||
| <script src="{{ .base_path }}assets/codemirror/jsonlint.js"></script> |  | ||||||
| <script src="{{ .base_path }}assets/codemirror/lint/lint.js"></script> |  | ||||||
| <script src="{{ .base_path }}assets/codemirror/lint/javascript-lint.js"></script> |  | ||||||
| <script src="{{ .base_path }}assets/codemirror/hint/javascript-hint.js"></script> |  | ||||||
| <script src="{{ .base_path }}assets/codemirror/fold/foldcode.js"></script> |  | ||||||
| <script src="{{ .base_path }}assets/codemirror/fold/foldgutter.js"></script> |  | ||||||
| <script src="{{ .base_path }}assets/codemirror/fold/brace-fold.js"></script> |  | ||||||
| <style> | <style> | ||||||
|   @media (min-width: 769px) { |   @media (min-width: 769px) { | ||||||
|     .ant-layout-content { |     .ant-layout-content { | ||||||
|       margin: 24px 16px; |       margin: 24px 16px; | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  | 
 | ||||||
|   @media (max-width: 768px) { |   @media (max-width: 768px) { | ||||||
|     .ant-tabs-nav .ant-tabs-tab { |     .ant-tabs-nav .ant-tabs-tab { | ||||||
|       margin: 0; |       margin: 0; | ||||||
|       padding: 12px .5rem; |       padding: 12px .5rem; | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|     .ant-table-thead>tr>th, |     .ant-table-thead>tr>th, | ||||||
|     .ant-table-tbody>tr>td { |     .ant-table-tbody>tr>td { | ||||||
|       padding: 10px 0px; |       padding: 10px 0px; | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  | 
 | ||||||
|   .ant-tabs-bar { |   .ant-tabs-bar { | ||||||
|     margin: 0; |     margin: 0; | ||||||
|   } |   } | ||||||
|  | 
 | ||||||
|   .ant-list-item { |   .ant-list-item { | ||||||
|     display: block; |     display: block; | ||||||
|   } |   } | ||||||
|  | 
 | ||||||
|   .ant-list-item>li { |   .ant-list-item>li { | ||||||
|     padding: 10px 20px !important; |     padding: 10px 20px !important; | ||||||
|   } |   } | ||||||
|  | 
 | ||||||
|   .ant-collapse-content-box .ant-alert { |   .ant-collapse-content-box .ant-alert { | ||||||
|     margin-block-end: 12px; |     margin-block-end: 12px; | ||||||
| } |   } | ||||||
| </style> | </style> | ||||||
| <body> | {{ template "page/head_end" .}} | ||||||
|   <a-layout id="app" v-cloak :class="themeSwitcher.currentTheme"> | 
 | ||||||
|  | {{ template "page/body_start" .}} | ||||||
|  | <a-layout id="app" v-cloak :class="themeSwitcher.currentTheme"> | ||||||
|   <a-sidebar></a-sidebar> |   <a-sidebar></a-sidebar> | ||||||
|   <a-layout id="content-layout"> |   <a-layout id="content-layout"> | ||||||
|     <a-layout-content> |     <a-layout-content> | ||||||
|         <a-spin :spinning="spinning" :delay="500" tip='{{ i18n "loading"}}'> |       <a-spin :spinning="loadingStates.spinning" :delay="500" tip='{{ i18n "loading"}}'> | ||||||
|         <transition name="list" appear> |         <transition name="list" appear> | ||||||
|             <a-alert type="error" v-if="showAlert" :style="{ marginBottom: '10px' }" |           <a-alert type="error" v-if="showAlert && loadingStates.fetched" :style="{ marginBottom: '10px' }" message='{{ i18n "secAlertTitle" }}' | ||||||
|               message='{{ i18n "secAlertTitle" }}' |             color="red" description='{{ i18n "secAlertSsl" }}' show-icon closable> | ||||||
|               color="red" |  | ||||||
|               description='{{ i18n "secAlertSsl" }}' |  | ||||||
|               show-icon closable> |  | ||||||
|           </a-alert> |           </a-alert> | ||||||
|         </transition> |         </transition> | ||||||
|           <a-space direction="vertical"> |         <transition name="list" appear> | ||||||
|             <a-card hoverable :style="{ marginBottom: '.5rem' }"> |           <a-row v-if="!loadingStates.fetched"> | ||||||
|  |             <a-card :style="{ textAlign: 'center', padding: '30px 0', marginTop: '10px', background: 'transparent', border: 'none' }"> | ||||||
|  |               <a-spin tip='{{ i18n "loading" }}'></a-spin> | ||||||
|  |             </a-card> | ||||||
|  |           </a-row> | ||||||
|  |           <a-row :gutter="[isMobile ? 8 : 16, isMobile ? 0 : 12]" v-else> | ||||||
|  |             <a-col> | ||||||
|  |               <a-card hoverable> | ||||||
|                 <a-row :style="{ display: 'flex', flexWrap: 'wrap', alignItems: 'center' }"> |                 <a-row :style="{ display: 'flex', flexWrap: 'wrap', alignItems: 'center' }"> | ||||||
|                   <a-col :xs="24" :sm="10" :style="{ padding: '4px' }"> |                   <a-col :xs="24" :sm="10" :style="{ padding: '4px' }"> | ||||||
|                     <a-space direction="horizontal"> |                     <a-space direction="horizontal"> | ||||||
|                     <a-button type="primary" :disabled="saveBtnDisable" @click="updateXraySetting">{{ i18n "pages.xray.save" }}</a-button> |                       <a-button type="primary" :disabled="saveBtnDisable" @click="updateXraySetting"> | ||||||
|                     <a-button type="danger" :disabled="!saveBtnDisable" @click="restartXray">{{ i18n "pages.xray.restart" }}</a-button> |                         {{ i18n "pages.xray.save" }} | ||||||
|                     <a-popover v-if="restartResult" |                       </a-button> | ||||||
|                         :overlay-class-name="themeSwitcher.currentTheme"> |                       <a-button type="danger" :disabled="!saveBtnDisable" @click="restartXray"> | ||||||
|  |                         {{ i18n "pages.xray.restart" }} | ||||||
|  |                       </a-button> | ||||||
|  |                       <a-popover v-if="restartResult" :overlay-class-name="themeSwitcher.currentTheme"> | ||||||
|                         <span slot="title">{{ i18n "pages.index.xrayErrorPopoverTitle" }}</span> |                         <span slot="title">{{ i18n "pages.index.xrayErrorPopoverTitle" }}</span> | ||||||
|                         <template slot="content"> |                         <template slot="content"> | ||||||
|                           <span :style="{ maxWidth: '400px' }" v-for="line in restartResult.split('\n')">[[ line ]]</span> |                           <span :style="{ maxWidth: '400px' }" v-for="line in restartResult.split('\n')">[[ line ]]</span> | ||||||
|  | @ -80,45 +81,89 @@ | ||||||
|                   <a-col :xs="24" :sm="14"> |                   <a-col :xs="24" :sm="14"> | ||||||
|                     <template> |                     <template> | ||||||
|                       <div> |                       <div> | ||||||
|                       <a-back-top :target="() => document.getElementById('content-layout')" visibility-height="200"></a-back-top> |                         <a-back-top :target="() => document.getElementById('content-layout')" | ||||||
|                       <a-alert type="warning" :style="{ float: 'right', width: 'fit-content' }" message='{{ i18n "pages.settings.infoDesc" }}' show-icon> |                           visibility-height="200"></a-back-top> | ||||||
|  |                         <a-alert type="warning" :style="{ float: 'right', width: 'fit-content' }" | ||||||
|  |                           message='{{ i18n "pages.settings.infoDesc" }}' show-icon> | ||||||
|                         </a-alert> |                         </a-alert> | ||||||
|                       </div> |                       </div> | ||||||
|                     </template> |                     </template> | ||||||
|                   </a-col> |                   </a-col> | ||||||
|                 </a-row> |                 </a-row> | ||||||
|               </a-card> |               </a-card> | ||||||
|             <a-tabs class="ant-card-dark-box-nohover" default-active-key="1" |             </a-col> | ||||||
|                 @change="(activeKey) => { this.changePage(activeKey); }" |             <a-col> | ||||||
|  |               <a-tabs default-active-key="tpl-basic" @change="(activeKey) => { this.changePage(activeKey); }" | ||||||
|                 :class="themeSwitcher.currentTheme"> |                 :class="themeSwitcher.currentTheme"> | ||||||
|               <a-tab-pane key="tpl-basic" tab='{{ i18n "pages.xray.basicTemplate"}}' :style="{ paddingTop: '20px' }"> |                 <a-tab-pane key="tpl-basic" :style="{ paddingTop: '20px' }"> | ||||||
|  |                   <template #tab> | ||||||
|  |                     <a-icon type="setting"></a-icon> | ||||||
|  |                     <span>{{ i18n "pages.xray.basicTemplate"}}</span> | ||||||
|  |                   </template> | ||||||
|                   {{ template "settings/xray/basics" . }} |                   {{ template "settings/xray/basics" . }} | ||||||
|                 </a-tab-pane> |                 </a-tab-pane> | ||||||
|               <a-tab-pane key="tpl-routing" tab='{{ i18n "pages.xray.Routings"}}' :style="{ paddingTop: '20px' }"> |                 <a-tab-pane key="tpl-routing" :style="{ paddingTop: '20px' }"> | ||||||
|  |                   <template #tab> | ||||||
|  |                     <a-icon type="swap"></a-icon> | ||||||
|  |                     <span>{{ i18n "pages.xray.Routings"}}</span> | ||||||
|  |                   </template> | ||||||
|                   {{ template "settings/xray/routing" . }} |                   {{ template "settings/xray/routing" . }} | ||||||
|                 </a-tab-pane> |                 </a-tab-pane> | ||||||
|               <a-tab-pane key="tpl-outbound" tab='{{ i18n "pages.xray.Outbounds"}}' force-render="true"> |                 <a-tab-pane key="tpl-outbound" force-render="true"> | ||||||
|  |                   <template #tab> | ||||||
|  |                     <a-icon type="upload"></a-icon> | ||||||
|  |                     <span>{{ i18n "pages.xray.Outbounds"}}</span> | ||||||
|  |                   </template> | ||||||
|                   {{ template "settings/xray/outbounds" . }} |                   {{ template "settings/xray/outbounds" . }} | ||||||
|                 </a-tab-pane> |                 </a-tab-pane> | ||||||
|               <a-tab-pane key="tpl-reverse" tab='{{ i18n "pages.xray.outbound.reverse"}}' :style="{ paddingTop: '20px' }" force-render="true"> |                 <a-tab-pane key="tpl-reverse" :style="{ paddingTop: '20px' }" force-render="true"> | ||||||
|  |                   <template #tab> | ||||||
|  |                     <a-icon type="import"></a-icon> | ||||||
|  |                     <span>{{ i18n "pages.xray.outbound.reverse"}}</span> | ||||||
|  |                   </template> | ||||||
|                   {{ template "settings/xray/reverse" . }} |                   {{ template "settings/xray/reverse" . }} | ||||||
|                 </a-tab-pane> |                 </a-tab-pane> | ||||||
|               <a-tab-pane key="tpl-balancer" tab='{{ i18n "pages.xray.Balancers"}}' :style="{ paddingTop: '20px' }" force-render="true"> |                 <a-tab-pane key="tpl-balancer" :style="{ paddingTop: '20px' }" force-render="true"> | ||||||
|  |                   <template #tab> | ||||||
|  |                     <a-icon type="cluster"></a-icon> | ||||||
|  |                     <span>{{ i18n "pages.xray.Balancers"}}</span> | ||||||
|  |                   </template> | ||||||
|                   {{ template "settings/xray/balancers" . }} |                   {{ template "settings/xray/balancers" . }} | ||||||
|                 </a-tab-pane> |                 </a-tab-pane> | ||||||
|               <a-tab-pane key="tpl-dns" tab='DNS' :style="{ paddingTop: '20px' }" force-render="true"> |                 <a-tab-pane key="tpl-dns" :style="{ paddingTop: '20px' }" force-render="true"> | ||||||
|  |                   <template #tab> | ||||||
|  |                     <a-icon type="database"></a-icon> | ||||||
|  |                     <span>DNS</span> | ||||||
|  |                   </template> | ||||||
|                   {{ template "settings/xray/dns" . }} |                   {{ template "settings/xray/dns" . }} | ||||||
|                 </a-tab-pane> |                 </a-tab-pane> | ||||||
|               <a-tab-pane key="tpl-advanced" tab='{{ i18n "pages.xray.advancedTemplate"}}' :style="{ paddingTop: '20px' }" force-render="true"> |                 <a-tab-pane key="tpl-advanced" force-render="true"> | ||||||
|  |                   <template #tab> | ||||||
|  |                     <a-icon type="code"></a-icon> | ||||||
|  |                     <span>{{ i18n "pages.xray.advancedTemplate"}}</span> | ||||||
|  |                   </template> | ||||||
|                   {{ template "settings/xray/advanced" . }} |                   {{ template "settings/xray/advanced" . }} | ||||||
|                 </a-tab-pane> |                 </a-tab-pane> | ||||||
|               </a-tabs> |               </a-tabs> | ||||||
|           </a-space> |             </a-col> | ||||||
|  |           </a-row> | ||||||
|  |         </transition> | ||||||
|       </a-spin> |       </a-spin> | ||||||
|     </a-layout-content> |     </a-layout-content> | ||||||
|   </a-layout> |   </a-layout> | ||||||
|   </a-layout> | </a-layout> | ||||||
| {{template "js" .}} | {{template "page/body_scripts" .}} | ||||||
|  | <script src="{{ .base_path }}assets/js/model/outbound.js?{{ .cur_ver }}"></script> | ||||||
|  | <script src="{{ .base_path }}assets/codemirror/codemirror.min.js?{{ .cur_ver }}"></script> | ||||||
|  | <script src="{{ .base_path }}assets/codemirror/javascript.js"></script> | ||||||
|  | <script src="{{ .base_path }}assets/codemirror/jshint.js"></script> | ||||||
|  | <script src="{{ .base_path }}assets/codemirror/jsonlint.js"></script> | ||||||
|  | <script src="{{ .base_path }}assets/codemirror/lint/lint.js"></script> | ||||||
|  | <script src="{{ .base_path }}assets/codemirror/lint/javascript-lint.js"></script> | ||||||
|  | <script src="{{ .base_path }}assets/codemirror/hint/javascript-hint.js"></script> | ||||||
|  | <script src="{{ .base_path }}assets/codemirror/fold/foldcode.js"></script> | ||||||
|  | <script src="{{ .base_path }}assets/codemirror/fold/foldgutter.js"></script> | ||||||
|  | <script src="{{ .base_path }}assets/codemirror/fold/brace-fold.js"></script> | ||||||
| {{template "component/aSidebar" .}} | {{template "component/aSidebar" .}} | ||||||
| {{template "component/aThemeSwitch" .}} | {{template "component/aThemeSwitch" .}} | ||||||
| {{template "component/aTableSortable" .}} | {{template "component/aTableSortable" .}} | ||||||
|  | @ -134,20 +179,28 @@ | ||||||
| <script> | <script> | ||||||
|   const rulesColumns = [ |   const rulesColumns = [ | ||||||
|     { title: "#", align: 'center', width: 15, scopedSlots: { customRender: 'action' } }, |     { title: "#", align: 'center', width: 15, scopedSlots: { customRender: 'action' } }, | ||||||
|         { title: '{{ i18n "pages.xray.rules.source"}}', children: [ |     { | ||||||
|  |       title: '{{ i18n "pages.xray.rules.source"}}', children: [ | ||||||
|         { title: 'IP', dataIndex: "source", align: 'center', width: 20, ellipsis: true }, |         { title: 'IP', dataIndex: "source", align: 'center', width: 20, ellipsis: true }, | ||||||
|             { title: '{{ i18n "pages.inbounds.port" }}', dataIndex: 'sourcePort', align: 'center', width: 10, ellipsis: true } ]}, |         { title: '{{ i18n "pages.inbounds.port" }}', dataIndex: 'sourcePort', align: 'center', width: 10, ellipsis: true }] | ||||||
|         { title: '{{ i18n "pages.inbounds.network"}}', children: [ |     }, | ||||||
|  |     { | ||||||
|  |       title: '{{ i18n "pages.inbounds.network"}}', children: [ | ||||||
|         { title: 'L4', dataIndex: 'network', align: 'center', width: 10 }, |         { title: 'L4', dataIndex: 'network', align: 'center', width: 10 }, | ||||||
|         { title: '{{ i18n "protocol" }}', dataIndex: 'protocol', align: 'center', width: 15, ellipsis: true }, |         { title: '{{ i18n "protocol" }}', dataIndex: 'protocol', align: 'center', width: 15, ellipsis: true }, | ||||||
|             { title: 'Attrs', dataIndex: 'attrs', align: 'center', width: 10, ellipsis: true } ]},  |         { title: 'Attrs', dataIndex: 'attrs', align: 'center', width: 10, ellipsis: true }] | ||||||
|         { title: '{{ i18n "pages.xray.rules.dest"}}', children: [ |     }, | ||||||
|  |     { | ||||||
|  |       title: '{{ i18n "pages.xray.rules.dest"}}', children: [ | ||||||
|         { title: 'IP', dataIndex: 'ip', align: 'center', width: 20, ellipsis: true }, |         { title: 'IP', dataIndex: 'ip', align: 'center', width: 20, ellipsis: true }, | ||||||
|         { title: '{{ i18n "pages.xray.outbound.domain" }}', dataIndex: 'domain', align: 'center', width: 20, ellipsis: true }, |         { title: '{{ i18n "pages.xray.outbound.domain" }}', dataIndex: 'domain', align: 'center', width: 20, ellipsis: true }, | ||||||
|             { title: '{{ i18n "pages.inbounds.port" }}', dataIndex: 'port', align: 'center', width: 10, ellipsis: true }]}, |         { title: '{{ i18n "pages.inbounds.port" }}', dataIndex: 'port', align: 'center', width: 10, ellipsis: true }] | ||||||
|         { title: '{{ i18n "pages.xray.rules.inbound"}}', children: [ |     }, | ||||||
|  |     { | ||||||
|  |       title: '{{ i18n "pages.xray.rules.inbound"}}', children: [ | ||||||
|         { title: '{{ i18n "pages.xray.outbound.tag" }}', dataIndex: 'inboundTag', align: 'center', width: 15, ellipsis: true }, |         { title: '{{ i18n "pages.xray.outbound.tag" }}', dataIndex: 'inboundTag', align: 'center', width: 15, ellipsis: true }, | ||||||
|             { title: '{{ i18n "pages.inbounds.client" }}', dataIndex: 'user', align: 'center', width: 20, ellipsis: true }]}, |         { title: '{{ i18n "pages.inbounds.client" }}', dataIndex: 'user', align: 'center', width: 20, ellipsis: true }] | ||||||
|  |     }, | ||||||
|     { title: '{{ i18n "pages.xray.rules.outbound"}}', dataIndex: 'outboundTag', align: 'center', width: 17 }, |     { title: '{{ i18n "pages.xray.rules.outbound"}}', dataIndex: 'outboundTag', align: 'center', width: 17 }, | ||||||
|     { title: '{{ i18n "pages.xray.rules.balancer"}}', dataIndex: 'balancerTag', align: 'center', width: 15 }, |     { title: '{{ i18n "pages.xray.rules.balancer"}}', dataIndex: 'balancerTag', align: 'center', width: 15 }, | ||||||
|   ]; |   ]; | ||||||
|  | @ -177,8 +230,8 @@ | ||||||
|   const balancerColumns = [ |   const balancerColumns = [ | ||||||
|     { title: "#", align: 'center', width: 20, scopedSlots: { customRender: 'action' } }, |     { title: "#", align: 'center', width: 20, scopedSlots: { customRender: 'action' } }, | ||||||
|     { title: '{{ i18n "pages.xray.balancer.tag"}}', dataIndex: 'tag', align: 'center', width: 50 }, |     { title: '{{ i18n "pages.xray.balancer.tag"}}', dataIndex: 'tag', align: 'center', width: 50 }, | ||||||
|         { title: '{{ i18n "pages.xray.balancer.balancerStrategy"}}', align: 'center', width: 50, scopedSlots: { customRender: 'strategy' }}, |     { title: '{{ i18n "pages.xray.balancer.balancerStrategy"}}', align: 'center', width: 50, scopedSlots: { customRender: 'strategy' } }, | ||||||
|         { title: '{{ i18n "pages.xray.balancer.balancerSelectors"}}', align: 'center', width: 100, scopedSlots: { customRender: 'selector' }}, |     { title: '{{ i18n "pages.xray.balancer.balancerSelectors"}}', align: 'center', width: 100, scopedSlots: { customRender: 'selector' } }, | ||||||
|   ]; |   ]; | ||||||
| 
 | 
 | ||||||
|   const dnsColumns = [ |   const dnsColumns = [ | ||||||
|  | @ -201,7 +254,10 @@ | ||||||
|     data: { |     data: { | ||||||
|       themeSwitcher, |       themeSwitcher, | ||||||
|       isDarkTheme: themeSwitcher.isDarkTheme, |       isDarkTheme: themeSwitcher.isDarkTheme, | ||||||
|             spinning: false, |       loadingStates: { | ||||||
|  |         fetched: false, | ||||||
|  |         spinning: false | ||||||
|  |       }, | ||||||
|       oldXraySetting: '', |       oldXraySetting: '', | ||||||
|       xraySetting: '', |       xraySetting: '', | ||||||
|       inboundTags: [], |       inboundTags: [], | ||||||
|  | @ -330,7 +386,7 @@ | ||||||
|     }, |     }, | ||||||
|     methods: { |     methods: { | ||||||
|       loading(spinning = true) { |       loading(spinning = true) { | ||||||
|                 this.spinning = spinning; |         this.loadingStates.spinning = spinning; | ||||||
|       }, |       }, | ||||||
|       async getOutboundsTraffic() { |       async getOutboundsTraffic() { | ||||||
|         const msg = await HttpUtil.get("/panel/xray/getOutboundsTraffic"); |         const msg = await HttpUtil.get("/panel/xray/getOutboundsTraffic"); | ||||||
|  | @ -339,10 +395,13 @@ | ||||||
|         } |         } | ||||||
|       }, |       }, | ||||||
|       async getXraySetting() { |       async getXraySetting() { | ||||||
|                 this.loading(true); |  | ||||||
|         const msg = await HttpUtil.post("/panel/xray/"); |         const msg = await HttpUtil.post("/panel/xray/"); | ||||||
|                 this.loading(false); | 
 | ||||||
|         if (msg.success) { |         if (msg.success) { | ||||||
|  |           if (!this.loadingStates.fetched) { | ||||||
|  |             this.loadingStates.fetched = true | ||||||
|  |           } | ||||||
|  | 
 | ||||||
|           result = JSON.parse(msg.obj); |           result = JSON.parse(msg.obj); | ||||||
|           xs = JSON.stringify(result.xraySetting, null, 2); |           xs = JSON.stringify(result.xraySetting, null, 2); | ||||||
|           this.oldXraySetting = xs; |           this.oldXraySetting = xs; | ||||||
|  | @ -353,7 +412,7 @@ | ||||||
|       }, |       }, | ||||||
|       async updateXraySetting() { |       async updateXraySetting() { | ||||||
|         this.loading(true); |         this.loading(true); | ||||||
|                 const msg = await HttpUtil.post("/panel/xray/update", {xraySetting : this.xraySetting}); |         const msg = await HttpUtil.post("/panel/xray/update", { xraySetting: this.xraySetting }); | ||||||
|         this.loading(false); |         this.loading(false); | ||||||
|         if (msg.success) { |         if (msg.success) { | ||||||
|           await this.getXraySetting(); |           await this.getXraySetting(); | ||||||
|  | @ -372,8 +431,8 @@ | ||||||
|       async getXrayResult() { |       async getXrayResult() { | ||||||
|         const msg = await HttpUtil.get("/panel/xray/getXrayResult"); |         const msg = await HttpUtil.get("/panel/xray/getXrayResult"); | ||||||
|         if (msg.success) { |         if (msg.success) { | ||||||
|                     this.restartResult=msg.obj; |           this.restartResult = msg.obj; | ||||||
|                     if(msg.obj.length > 1) Vue.prototype.$message.error(msg.obj); |           if (msg.obj.length > 1) Vue.prototype.$message.error(msg.obj); | ||||||
|         } |         } | ||||||
|       }, |       }, | ||||||
|       async resetXrayConfigToDefault() { |       async resetXrayConfigToDefault() { | ||||||
|  | @ -386,8 +445,8 @@ | ||||||
|         } |         } | ||||||
|       }, |       }, | ||||||
|       changePage(pageKey) { |       changePage(pageKey) { | ||||||
|                 if(pageKey == 'tpl-advanced') this.changeCode(); |         if (pageKey == 'tpl-advanced') this.changeCode(); | ||||||
|                 if(pageKey == 'tpl-balancer') this.changeObsCode(); |         if (pageKey == 'tpl-balancer') this.changeObsCode(); | ||||||
|       }, |       }, | ||||||
|       syncRulesWithOutbound(tag, setting) { |       syncRulesWithOutbound(tag, setting) { | ||||||
|         const newTemplateSettings = this.templateSettings; |         const newTemplateSettings = this.templateSettings; | ||||||
|  | @ -458,13 +517,13 @@ | ||||||
|         this.templateSettings = newTemplateSettings; |         this.templateSettings = newTemplateSettings; | ||||||
|       }, |       }, | ||||||
|       changeCode() { |       changeCode() { | ||||||
|                 if(this.cm != null) { |         if (this.cm != null) { | ||||||
|           this.cm.toTextArea(); |           this.cm.toTextArea(); | ||||||
|         } |         } | ||||||
|         textAreaObj = document.getElementById('xraySetting'); |         textAreaObj = document.getElementById('xraySetting'); | ||||||
|         textAreaObj.value = this[this.advSettings]; |         textAreaObj.value = this[this.advSettings]; | ||||||
|         this.cm = CodeMirror.fromTextArea(textAreaObj, this.cmOptions); |         this.cm = CodeMirror.fromTextArea(textAreaObj, this.cmOptions); | ||||||
|                 this.cm.on('change',editor => { |         this.cm.on('change', editor => { | ||||||
|           value = editor.getValue(); |           value = editor.getValue(); | ||||||
|           if (this.isJsonString(value)) { |           if (this.isJsonString(value)) { | ||||||
|             this[this.advSettings] = value; |             this[this.advSettings] = value; | ||||||
|  | @ -472,19 +531,19 @@ | ||||||
|         }); |         }); | ||||||
|       }, |       }, | ||||||
|       changeObsCode() { |       changeObsCode() { | ||||||
|                 if(this.cm != null) { |         if (this.cm != null) { | ||||||
|           this.cm.toTextArea(); |           this.cm.toTextArea(); | ||||||
|         } |         } | ||||||
|                 if (this.obsSettings == ''){ |         if (this.obsSettings == '') { | ||||||
|           this.cm = null; |           this.cm = null; | ||||||
|           return |           return | ||||||
|         } |         } | ||||||
|         textAreaObj = document.getElementById('obsSetting'); |         textAreaObj = document.getElementById('obsSetting'); | ||||||
|         textAreaObj.value = this[this.obsSettings]; |         textAreaObj.value = this[this.obsSettings]; | ||||||
|         this.cm = CodeMirror.fromTextArea(textAreaObj, this.cmOptions); |         this.cm = CodeMirror.fromTextArea(textAreaObj, this.cmOptions); | ||||||
|                 this.cm.on('change',editor => { |         this.cm.on('change', editor => { | ||||||
|           value = editor.getValue(); |           value = editor.getValue(); | ||||||
|                     if(this.isJsonString(value)){ |           if (this.isJsonString(value)) { | ||||||
|             this[this.obsSettings] = value; |             this[this.obsSettings] = value; | ||||||
|           } |           } | ||||||
|         }); |         }); | ||||||
|  | @ -507,7 +566,7 @@ | ||||||
|       }, |       }, | ||||||
|       findOutboundAddress(o) { |       findOutboundAddress(o) { | ||||||
|         serverObj = null; |         serverObj = null; | ||||||
|                 switch(o.protocol){ |         switch (o.protocol) { | ||||||
|           case Protocols.VMess: |           case Protocols.VMess: | ||||||
|           case Protocols.VLESS: |           case Protocols.VLESS: | ||||||
|             serverObj = o.settings.vnext; |             serverObj = o.settings.vnext; | ||||||
|  | @ -527,13 +586,13 @@ | ||||||
|         } |         } | ||||||
|         return serverObj ? serverObj.map(obj => obj.address + ':' + obj.port) : null; |         return serverObj ? serverObj.map(obj => obj.address + ':' + obj.port) : null; | ||||||
|       }, |       }, | ||||||
|             addOutbound(){ |       addOutbound() { | ||||||
|         outModal.show({ |         outModal.show({ | ||||||
|           title: '{{ i18n "pages.xray.outbound.addOutbound"}}', |           title: '{{ i18n "pages.xray.outbound.addOutbound"}}', | ||||||
|           okText: '{{ i18n "pages.xray.outbound.addOutbound" }}', |           okText: '{{ i18n "pages.xray.outbound.addOutbound" }}', | ||||||
|           confirm: (outbound) => { |           confirm: (outbound) => { | ||||||
|             outModal.loading(); |             outModal.loading(); | ||||||
|                         if(outbound.tag.length > 0){ |             if (outbound.tag.length > 0) { | ||||||
|               this.templateSettings.outbounds.push(outbound); |               this.templateSettings.outbounds.push(outbound); | ||||||
|               this.outboundSettings = JSON.stringify(this.templateSettings.outbounds); |               this.outboundSettings = JSON.stringify(this.templateSettings.outbounds); | ||||||
|             } |             } | ||||||
|  | @ -543,9 +602,9 @@ | ||||||
|           tags: this.templateSettings.outbounds.map(obj => obj.tag) |           tags: this.templateSettings.outbounds.map(obj => obj.tag) | ||||||
|         }); |         }); | ||||||
|       }, |       }, | ||||||
|             editOutbound(index){ |       editOutbound(index) { | ||||||
|         outModal.show({ |         outModal.show({ | ||||||
|                     title: '{{ i18n "pages.xray.outbound.editOutbound"}} ' + (index+1), |           title: '{{ i18n "pages.xray.outbound.editOutbound"}} ' + (index + 1), | ||||||
|           outbound: app.templateSettings.outbounds[index], |           outbound: app.templateSettings.outbounds[index], | ||||||
|           confirm: (outbound) => { |           confirm: (outbound) => { | ||||||
|             outModal.loading(); |             outModal.loading(); | ||||||
|  | @ -554,30 +613,30 @@ | ||||||
|             outModal.close(); |             outModal.close(); | ||||||
|           }, |           }, | ||||||
|           isEdit: true, |           isEdit: true, | ||||||
|                     tags: this.outboundData.filter((o) => o.key != index ).map(obj => obj.tag) |           tags: this.outboundData.filter((o) => o.key != index).map(obj => obj.tag) | ||||||
|         }); |         }); | ||||||
|       }, |       }, | ||||||
|             deleteOutbound(index){ |       deleteOutbound(index) { | ||||||
|         outbounds = this.templateSettings.outbounds; |         outbounds = this.templateSettings.outbounds; | ||||||
|                 outbounds.splice(index,1); |         outbounds.splice(index, 1); | ||||||
|         this.outboundSettings = JSON.stringify(outbounds); |         this.outboundSettings = JSON.stringify(outbounds); | ||||||
|       }, |       }, | ||||||
|             setFirstOutbound(index){ |       setFirstOutbound(index) { | ||||||
|         outbounds = this.templateSettings.outbounds; |         outbounds = this.templateSettings.outbounds; | ||||||
|         outbounds.splice(0, 0, outbounds.splice(index, 1)[0]); |         outbounds.splice(0, 0, outbounds.splice(index, 1)[0]); | ||||||
|         this.outboundSettings = JSON.stringify(outbounds); |         this.outboundSettings = JSON.stringify(outbounds); | ||||||
|       }, |       }, | ||||||
|             addReverse(){ |       addReverse() { | ||||||
|         reverseModal.show({ |         reverseModal.show({ | ||||||
|           title: '{{ i18n "pages.xray.outbound.addReverse"}}', |           title: '{{ i18n "pages.xray.outbound.addReverse"}}', | ||||||
|           okText: '{{ i18n "pages.xray.outbound.addReverse" }}', |           okText: '{{ i18n "pages.xray.outbound.addReverse" }}', | ||||||
|           confirm: (reverse, rules) => { |           confirm: (reverse, rules) => { | ||||||
|             reverseModal.loading(); |             reverseModal.loading(); | ||||||
|                         if(reverse.tag.length > 0){ |             if (reverse.tag.length > 0) { | ||||||
|               newTemplateSettings = this.templateSettings; |               newTemplateSettings = this.templateSettings; | ||||||
|                             if(newTemplateSettings.reverse == undefined) newTemplateSettings.reverse = {}; |               if (newTemplateSettings.reverse == undefined) newTemplateSettings.reverse = {}; | ||||||
|                             if(newTemplateSettings.reverse[reverse.type+'s']  == undefined) newTemplateSettings.reverse[reverse.type+'s'] = []; |               if (newTemplateSettings.reverse[reverse.type + 's'] == undefined) newTemplateSettings.reverse[reverse.type + 's'] = []; | ||||||
|                             newTemplateSettings.reverse[reverse.type+'s'].push({ tag: reverse.tag, domain: reverse.domain }); |               newTemplateSettings.reverse[reverse.type + 's'].push({ tag: reverse.tag, domain: reverse.domain }); | ||||||
|               this.templateSettings = newTemplateSettings; |               this.templateSettings = newTemplateSettings; | ||||||
| 
 | 
 | ||||||
|               // Add related rules |               // Add related rules | ||||||
|  | @ -589,33 +648,33 @@ | ||||||
|           isEdit: false |           isEdit: false | ||||||
|         }); |         }); | ||||||
|       }, |       }, | ||||||
|             editReverse(index){ |       editReverse(index) { | ||||||
|                 if(this.reverseData[index].type == "bridge") { |         if (this.reverseData[index].type == "bridge") { | ||||||
|           oldRules = this.templateSettings.routing.rules.filter(r => r.inboundTag && r.inboundTag[0] == this.reverseData[index].tag); |           oldRules = this.templateSettings.routing.rules.filter(r => r.inboundTag && r.inboundTag[0] == this.reverseData[index].tag); | ||||||
|         } else { |         } else { | ||||||
|           oldRules = this.templateSettings.routing.rules.filter(r => r.outboundTag && r.outboundTag == this.reverseData[index].tag); |           oldRules = this.templateSettings.routing.rules.filter(r => r.outboundTag && r.outboundTag == this.reverseData[index].tag); | ||||||
|         } |         } | ||||||
|         reverseModal.show({ |         reverseModal.show({ | ||||||
|                     title: '{{ i18n "pages.xray.outbound.editReverse"}} ' + (index+1), |           title: '{{ i18n "pages.xray.outbound.editReverse"}} ' + (index + 1), | ||||||
|           reverse: this.reverseData[index], |           reverse: this.reverseData[index], | ||||||
|           rules: oldRules, |           rules: oldRules, | ||||||
|           confirm: (reverse, rules) => { |           confirm: (reverse, rules) => { | ||||||
|             reverseModal.loading(); |             reverseModal.loading(); | ||||||
|                         if(reverse.tag.length > 0){ |             if (reverse.tag.length > 0) { | ||||||
|               oldData = this.reverseData[index]; |               oldData = this.reverseData[index]; | ||||||
|               newTemplateSettings = this.templateSettings; |               newTemplateSettings = this.templateSettings; | ||||||
|                             oldReverseIndex = newTemplateSettings.reverse[oldData.type+'s'].findIndex(rs => rs.tag == oldData.tag); |               oldReverseIndex = newTemplateSettings.reverse[oldData.type + 's'].findIndex(rs => rs.tag == oldData.tag); | ||||||
|                             oldRuleIndex0 = oldRules.length>0 ? newTemplateSettings.routing.rules.findIndex(r => JSON.stringify(r) == JSON.stringify(oldRules[0])) : -1; |               oldRuleIndex0 = oldRules.length > 0 ? newTemplateSettings.routing.rules.findIndex(r => JSON.stringify(r) == JSON.stringify(oldRules[0])) : -1; | ||||||
|                             oldRuleIndex1 = oldRules.length==2 ? newTemplateSettings.routing.rules.findIndex(r => JSON.stringify(r) == JSON.stringify(oldRules[1])) : -1; |               oldRuleIndex1 = oldRules.length == 2 ? newTemplateSettings.routing.rules.findIndex(r => JSON.stringify(r) == JSON.stringify(oldRules[1])) : -1; | ||||||
|                             if(oldData.type == reverse.type){ |               if (oldData.type == reverse.type) { | ||||||
|                 newTemplateSettings.reverse[oldData.type + 's'][oldReverseIndex] = { tag: reverse.tag, domain: reverse.domain }; |                 newTemplateSettings.reverse[oldData.type + 's'][oldReverseIndex] = { tag: reverse.tag, domain: reverse.domain }; | ||||||
|               } else { |               } else { | ||||||
|                                 newTemplateSettings.reverse[oldData.type+'s'].splice(oldReverseIndex,1); |                 newTemplateSettings.reverse[oldData.type + 's'].splice(oldReverseIndex, 1); | ||||||
|                 // delete empty object |                 // delete empty object | ||||||
|                                 if(newTemplateSettings.reverse[oldData.type+'s'].length == 0) Reflect.deleteProperty(newTemplateSettings.reverse, oldData.type+'s'); |                 if (newTemplateSettings.reverse[oldData.type + 's'].length == 0) Reflect.deleteProperty(newTemplateSettings.reverse, oldData.type + 's'); | ||||||
|                 // add other type of reverse if it is not exist |                 // add other type of reverse if it is not exist | ||||||
|                                 if(!newTemplateSettings.reverse[reverse.type+'s']) newTemplateSettings.reverse[reverse.type+'s'] = []; |                 if (!newTemplateSettings.reverse[reverse.type + 's']) newTemplateSettings.reverse[reverse.type + 's'] = []; | ||||||
|                                 newTemplateSettings.reverse[reverse.type+'s'].push({ tag: reverse.tag, domain: reverse.domain }); |                 newTemplateSettings.reverse[reverse.type + 's'].push({ tag: reverse.tag, domain: reverse.domain }); | ||||||
|               } |               } | ||||||
|               this.templateSettings = newTemplateSettings; |               this.templateSettings = newTemplateSettings; | ||||||
| 
 | 
 | ||||||
|  | @ -630,22 +689,22 @@ | ||||||
|           isEdit: true |           isEdit: true | ||||||
|         }); |         }); | ||||||
|       }, |       }, | ||||||
|             deleteReverse(index){ |       deleteReverse(index) { | ||||||
|         oldData = this.reverseData[index]; |         oldData = this.reverseData[index]; | ||||||
|         newTemplateSettings = this.templateSettings; |         newTemplateSettings = this.templateSettings; | ||||||
|                 reverseTypeObj = newTemplateSettings.reverse[oldData.type+'s']; |         reverseTypeObj = newTemplateSettings.reverse[oldData.type + 's']; | ||||||
|                 realIndex = reverseTypeObj.findIndex(r => r.tag==oldData.tag && r.domain==oldData.domain); |         realIndex = reverseTypeObj.findIndex(r => r.tag == oldData.tag && r.domain == oldData.domain); | ||||||
|                 newTemplateSettings.reverse[oldData.type+'s'].splice(realIndex,1); |         newTemplateSettings.reverse[oldData.type + 's'].splice(realIndex, 1); | ||||||
| 
 | 
 | ||||||
|         // delete empty objects |         // delete empty objects | ||||||
|                 if(reverseTypeObj.length == 0) Reflect.deleteProperty(newTemplateSettings.reverse, oldData.type+'s'); |         if (reverseTypeObj.length == 0) Reflect.deleteProperty(newTemplateSettings.reverse, oldData.type + 's'); | ||||||
|                 if(Object.keys(newTemplateSettings.reverse).length === 0) Reflect.deleteProperty(newTemplateSettings, 'reverse'); |         if (Object.keys(newTemplateSettings.reverse).length === 0) Reflect.deleteProperty(newTemplateSettings, 'reverse'); | ||||||
| 
 | 
 | ||||||
|         // delete related routing rules |         // delete related routing rules | ||||||
|         newRules = newTemplateSettings.routing.rules; |         newRules = newTemplateSettings.routing.rules; | ||||||
|                 if(oldData.type == "bridge"){ |         if (oldData.type == "bridge") { | ||||||
|                     newRules = newTemplateSettings.routing.rules.filter(r => !( r.inboundTag && r.inboundTag.length == 1 && r.inboundTag[0] == oldData.tag)); |           newRules = newTemplateSettings.routing.rules.filter(r => !(r.inboundTag && r.inboundTag.length == 1 && r.inboundTag[0] == oldData.tag)); | ||||||
|                 } else if(oldData.type == "portal"){ |         } else if (oldData.type == "portal") { | ||||||
|           newRules = newTemplateSettings.routing.rules.filter(r => r.outboundTag != oldData.tag); |           newRules = newTemplateSettings.routing.rules.filter(r => r.outboundTag != oldData.tag); | ||||||
|         } |         } | ||||||
|         newTemplateSettings.routing.rules = newRules; |         newTemplateSettings.routing.rules = newRules; | ||||||
|  | @ -660,7 +719,7 @@ | ||||||
|           data = [] |           data = [] | ||||||
|           if (this.templateSettings != null) { |           if (this.templateSettings != null) { | ||||||
|             this.templateSettings.outbounds.forEach((o, index) => { |             this.templateSettings.outbounds.forEach((o, index) => { | ||||||
|                             data.push({'key': index, ...o}); |               data.push({ 'key': index, ...o }); | ||||||
|             }); |             }); | ||||||
|           } |           } | ||||||
| 
 | 
 | ||||||
|  | @ -732,10 +791,10 @@ | ||||||
|             }; |             }; | ||||||
| 
 | 
 | ||||||
|             // Remove old tag |             // Remove old tag | ||||||
|                         if (newTemplateSettings.observatory){ |             if (newTemplateSettings.observatory) { | ||||||
|               newTemplateSettings.observatory.subjectSelector = newTemplateSettings.observatory.subjectSelector.filter(s => s != oldTag); |               newTemplateSettings.observatory.subjectSelector = newTemplateSettings.observatory.subjectSelector.filter(s => s != oldTag); | ||||||
|             } |             } | ||||||
|                         if (newTemplateSettings.burstObservatory){ |             if (newTemplateSettings.burstObservatory) { | ||||||
|               newTemplateSettings.burstObservatory.subjectSelector = newTemplateSettings.burstObservatory.subjectSelector.filter(s => s != oldTag); |               newTemplateSettings.burstObservatory.subjectSelector = newTemplateSettings.burstObservatory.subjectSelector.filter(s => s != oldTag); | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|  | @ -762,7 +821,7 @@ | ||||||
|           isEdit: true |           isEdit: true | ||||||
|         }); |         }); | ||||||
|       }, |       }, | ||||||
|             updateObservatorySelectors(){ |       updateObservatorySelectors() { | ||||||
|         newTemplateSettings = this.templateSettings; |         newTemplateSettings = this.templateSettings; | ||||||
|         const leastPings = this.balancersData.filter((b) => b.strategy == 'leastPing'); |         const leastPings = this.balancersData.filter((b) => b.strategy == 'leastPing'); | ||||||
|         const leastLoads = this.balancersData.filter((b) => |         const leastLoads = this.balancersData.filter((b) => | ||||||
|  | @ -770,7 +829,7 @@ | ||||||
|           b.strategy === 'roundRobin' || |           b.strategy === 'roundRobin' || | ||||||
|           b.strategy === 'random' |           b.strategy === 'random' | ||||||
|         ); |         ); | ||||||
|                 if (leastPings.length>0){ |         if (leastPings.length > 0) { | ||||||
|           if (!newTemplateSettings.observatory) |           if (!newTemplateSettings.observatory) | ||||||
|             newTemplateSettings.observatory = this.defaultObservatory; |             newTemplateSettings.observatory = this.defaultObservatory; | ||||||
|           newTemplateSettings.observatory.subjectSelector = []; |           newTemplateSettings.observatory.subjectSelector = []; | ||||||
|  | @ -783,7 +842,7 @@ | ||||||
|         } else { |         } else { | ||||||
|           delete newTemplateSettings.observatory |           delete newTemplateSettings.observatory | ||||||
|         } |         } | ||||||
|                 if (leastLoads.length>0){ |         if (leastLoads.length > 0) { | ||||||
|           if (!newTemplateSettings.burstObservatory) |           if (!newTemplateSettings.burstObservatory) | ||||||
|             newTemplateSettings.burstObservatory = this.defaultBurstObservatory; |             newTemplateSettings.burstObservatory = this.defaultBurstObservatory; | ||||||
|           newTemplateSettings.burstObservatory.subjectSelector = []; |           newTemplateSettings.burstObservatory.subjectSelector = []; | ||||||
|  | @ -828,7 +887,7 @@ | ||||||
|           } |           } | ||||||
|         }); |         }); | ||||||
|       }, |       }, | ||||||
|             addDNSServer(){ |       addDNSServer() { | ||||||
|         dnsModal.show({ |         dnsModal.show({ | ||||||
|           title: '{{ i18n "pages.xray.dns.add" }}', |           title: '{{ i18n "pages.xray.dns.add" }}', | ||||||
|           confirm: (dnsServer) => { |           confirm: (dnsServer) => { | ||||||
|  | @ -840,9 +899,9 @@ | ||||||
|           isEdit: false |           isEdit: false | ||||||
|         }); |         }); | ||||||
|       }, |       }, | ||||||
|             editDNSServer(index){ |       editDNSServer(index) { | ||||||
|         dnsModal.show({ |         dnsModal.show({ | ||||||
|                     title: '{{ i18n "pages.xray.dns.edit" }} #' + (index+1), |           title: '{{ i18n "pages.xray.dns.edit" }} #' + (index + 1), | ||||||
|           dnsServer: this.dnsServers[index], |           dnsServer: this.dnsServers[index], | ||||||
|           confirm: (dnsServer) => { |           confirm: (dnsServer) => { | ||||||
|             dnsServers = this.dnsServers; |             dnsServers = this.dnsServers; | ||||||
|  | @ -853,16 +912,16 @@ | ||||||
|           isEdit: true |           isEdit: true | ||||||
|         }); |         }); | ||||||
|       }, |       }, | ||||||
|             deleteDNSServer(index){ |       deleteDNSServer(index) { | ||||||
|         newDnsServers = this.dnsServers; |         newDnsServers = this.dnsServers; | ||||||
|                 newDnsServers.splice(index,1); |         newDnsServers.splice(index, 1); | ||||||
|         this.dnsServers = newDnsServers; |         this.dnsServers = newDnsServers; | ||||||
|       }, |       }, | ||||||
|       addFakedns() { |       addFakedns() { | ||||||
|         fakednsModal.show({ |         fakednsModal.show({ | ||||||
|           title: '{{ i18n "pages.xray.fakedns.add" }}', |           title: '{{ i18n "pages.xray.fakedns.add" }}', | ||||||
|           confirm: (item) => { |           confirm: (item) => { | ||||||
|                         fakeDns = this.fakeDns?? []; |             fakeDns = this.fakeDns ?? []; | ||||||
|             fakeDns.push(item); |             fakeDns.push(item); | ||||||
|             this.fakeDns = fakeDns; |             this.fakeDns = fakeDns; | ||||||
|             fakednsModal.close(); |             fakednsModal.close(); | ||||||
|  | @ -870,9 +929,9 @@ | ||||||
|           isEdit: false |           isEdit: false | ||||||
|         }); |         }); | ||||||
|       }, |       }, | ||||||
|             editFakedns(index){ |       editFakedns(index) { | ||||||
|         fakednsModal.show({ |         fakednsModal.show({ | ||||||
|                     title: '{{ i18n "pages.xray.fakedns.edit" }} #' + (index+1), |           title: '{{ i18n "pages.xray.fakedns.edit" }} #' + (index + 1), | ||||||
|           fakeDns: this.fakeDns[index], |           fakeDns: this.fakeDns[index], | ||||||
|           confirm: (item) => { |           confirm: (item) => { | ||||||
|             fakeDns = this.fakeDns; |             fakeDns = this.fakeDns; | ||||||
|  | @ -883,18 +942,18 @@ | ||||||
|           isEdit: true |           isEdit: true | ||||||
|         }); |         }); | ||||||
|       }, |       }, | ||||||
|             deleteFakedns(index){ |       deleteFakedns(index) { | ||||||
|         fakeDns = this.fakeDns; |         fakeDns = this.fakeDns; | ||||||
|                 fakeDns.splice(index,1); |         fakeDns.splice(index, 1); | ||||||
|         this.fakeDns = fakeDns; |         this.fakeDns = fakeDns; | ||||||
|       }, |       }, | ||||||
|             addRule(){ |       addRule() { | ||||||
|         ruleModal.show({ |         ruleModal.show({ | ||||||
|           title: '{{ i18n "pages.xray.rules.add"}}', |           title: '{{ i18n "pages.xray.rules.add"}}', | ||||||
|           okText: '{{ i18n "pages.xray.rules.add" }}', |           okText: '{{ i18n "pages.xray.rules.add" }}', | ||||||
|           confirm: (rule) => { |           confirm: (rule) => { | ||||||
|             ruleModal.loading(); |             ruleModal.loading(); | ||||||
|                         if(JSON.stringify(rule).length > 3){ |             if (JSON.stringify(rule).length > 3) { | ||||||
|               this.templateSettings.routing.rules.push(rule); |               this.templateSettings.routing.rules.push(rule); | ||||||
|               this.routingRuleSettings = JSON.stringify(this.templateSettings.routing.rules); |               this.routingRuleSettings = JSON.stringify(this.templateSettings.routing.rules); | ||||||
|             } |             } | ||||||
|  | @ -903,13 +962,13 @@ | ||||||
|           isEdit: false |           isEdit: false | ||||||
|         }); |         }); | ||||||
|       }, |       }, | ||||||
|             editRule(index){ |       editRule(index) { | ||||||
|         ruleModal.show({ |         ruleModal.show({ | ||||||
|                     title: '{{ i18n "pages.xray.rules.edit"}} ' + (index+1), |           title: '{{ i18n "pages.xray.rules.edit"}} ' + (index + 1), | ||||||
|           rule: app.templateSettings.routing.rules[index], |           rule: app.templateSettings.routing.rules[index], | ||||||
|           confirm: (rule) => { |           confirm: (rule) => { | ||||||
|             ruleModal.loading(); |             ruleModal.loading(); | ||||||
|                         if(JSON.stringify(rule).length > 3){ |             if (JSON.stringify(rule).length > 3) { | ||||||
|               this.templateSettings.routing.rules[index] = rule; |               this.templateSettings.routing.rules[index] = rule; | ||||||
|               this.routingRuleSettings = JSON.stringify(this.templateSettings.routing.rules); |               this.routingRuleSettings = JSON.stringify(this.templateSettings.routing.rules); | ||||||
|             } |             } | ||||||
|  | @ -918,18 +977,18 @@ | ||||||
|           isEdit: true |           isEdit: true | ||||||
|         }); |         }); | ||||||
|       }, |       }, | ||||||
|             replaceRule(old_index,new_index){ |       replaceRule(old_index, new_index) { | ||||||
|         rules = this.templateSettings.routing.rules; |         rules = this.templateSettings.routing.rules; | ||||||
|         if (new_index >= rules.length) rules.push(undefined); |         if (new_index >= rules.length) rules.push(undefined); | ||||||
|         rules.splice(new_index, 0, rules.splice(old_index, 1)[0]); |         rules.splice(new_index, 0, rules.splice(old_index, 1)[0]); | ||||||
|         this.routingRuleSettings = JSON.stringify(rules); |         this.routingRuleSettings = JSON.stringify(rules); | ||||||
|       }, |       }, | ||||||
|             deleteRule(index){ |       deleteRule(index) { | ||||||
|         rules = this.templateSettings.routing.rules; |         rules = this.templateSettings.routing.rules; | ||||||
|                 rules.splice(index,1); |         rules.splice(index, 1); | ||||||
|         this.routingRuleSettings = JSON.stringify(rules); |         this.routingRuleSettings = JSON.stringify(rules); | ||||||
|       }, |       }, | ||||||
|             showWarp(){ |       showWarp() { | ||||||
|         warpModal.show(); |         warpModal.show(); | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|  | @ -978,7 +1037,7 @@ | ||||||
|           data = [] |           data = [] | ||||||
|           if (this.templateSettings != null) { |           if (this.templateSettings != null) { | ||||||
|             this.templateSettings.outbounds.forEach((o, index) => { |             this.templateSettings.outbounds.forEach((o, index) => { | ||||||
|                             data.push({'key': index, ...o}); |               data.push({ 'key': index, ...o }); | ||||||
|             }); |             }); | ||||||
|           } |           } | ||||||
|           return data; |           return data; | ||||||
|  | @ -988,14 +1047,14 @@ | ||||||
|         get: function () { |         get: function () { | ||||||
|           data = [] |           data = [] | ||||||
|           if (this.templateSettings != null && this.templateSettings.reverse != null) { |           if (this.templateSettings != null && this.templateSettings.reverse != null) { | ||||||
|                         if(this.templateSettings.reverse.bridges) { |             if (this.templateSettings.reverse.bridges) { | ||||||
|               this.templateSettings.reverse.bridges.forEach((o, index) => { |               this.templateSettings.reverse.bridges.forEach((o, index) => { | ||||||
|                                 data.push({'key': index, 'type':'bridge', ...o}); |                 data.push({ 'key': index, 'type': 'bridge', ...o }); | ||||||
|               }); |               }); | ||||||
|             } |             } | ||||||
|                         if(this.templateSettings.reverse.portals){ |             if (this.templateSettings.reverse.portals) { | ||||||
|               this.templateSettings.reverse.portals.forEach((o, index) => { |               this.templateSettings.reverse.portals.forEach((o, index) => { | ||||||
|                                 data.push({'key': index, 'type':'portal', ...o}); |                 data.push({ 'key': index, 'type': 'portal', ...o }); | ||||||
|               }); |               }); | ||||||
|             } |             } | ||||||
|           } |           } | ||||||
|  | @ -1015,17 +1074,17 @@ | ||||||
|           data = []; |           data = []; | ||||||
|           if (this.templateSettings != null) { |           if (this.templateSettings != null) { | ||||||
|             this.templateSettings.routing.rules.forEach((r, index) => { |             this.templateSettings.routing.rules.forEach((r, index) => { | ||||||
|                             data.push({'key': index, ...r}); |               data.push({ 'key': index, ...r }); | ||||||
|             }); |             }); | ||||||
|             // Make rules readable |             // Make rules readable | ||||||
|             data.forEach(r => { |             data.forEach(r => { | ||||||
|                             if(r.domain) r.domain = r.domain.join(',') |               if (r.domain) r.domain = r.domain.join(',') | ||||||
|                             if(r.ip) r.ip = r.ip.join(',') |               if (r.ip) r.ip = r.ip.join(',') | ||||||
|                             if(r.source) r.source = r.source.join(','); |               if (r.source) r.source = r.source.join(','); | ||||||
|                             if(r.user) r.user = r.user.join(',') |               if (r.user) r.user = r.user.join(',') | ||||||
|                             if(r.inboundTag) r.inboundTag = r.inboundTag.join(',') |               if (r.inboundTag) r.inboundTag = r.inboundTag.join(',') | ||||||
|                             if(r.protocol) r.protocol = r.protocol.join(',') |               if (r.protocol) r.protocol = r.protocol.join(',') | ||||||
|                             if(r.attrs) r.attrs = JSON.stringify(r.attrs, null, 2) |               if (r.attrs) r.attrs = JSON.stringify(r.attrs, null, 2) | ||||||
|             }); |             }); | ||||||
|           } |           } | ||||||
|           return data; |           return data; | ||||||
|  | @ -1041,7 +1100,7 @@ | ||||||
|                 'tag': o.tag ? o.tag : "", |                 'tag': o.tag ? o.tag : "", | ||||||
|                 'strategy': o.strategy?.type ?? "random", |                 'strategy': o.strategy?.type ?? "random", | ||||||
|                 'selector': o.selector ? o.selector : [], |                 'selector': o.selector ? o.selector : [], | ||||||
|                                 'fallbackTag': o.fallbackTag?? '', |                 'fallbackTag': o.fallbackTag ?? '', | ||||||
|               }); |               }); | ||||||
|             }); |             }); | ||||||
|           } |           } | ||||||
|  | @ -1081,10 +1140,10 @@ | ||||||
|         set: function (newValue) { |         set: function (newValue) { | ||||||
|           newTemplateSettings = this.templateSettings; |           newTemplateSettings = this.templateSettings; | ||||||
|           freedomOutboundIndex = newTemplateSettings.outbounds.findIndex((o) => o.protocol === "freedom" && o.tag == "direct"); |           freedomOutboundIndex = newTemplateSettings.outbounds.findIndex((o) => o.protocol === "freedom" && o.tag == "direct"); | ||||||
|                     if(freedomOutboundIndex == -1){ |           if (freedomOutboundIndex == -1) { | ||||||
|                         newTemplateSettings.outbounds.push({protocol: "freedom", tag: "direct", settings: { "domainStrategy": newValue }}); |             newTemplateSettings.outbounds.push({ protocol: "freedom", tag: "direct", settings: { "domainStrategy": newValue } }); | ||||||
|           } else if (!newTemplateSettings.outbounds[freedomOutboundIndex].settings) { |           } else if (!newTemplateSettings.outbounds[freedomOutboundIndex].settings) { | ||||||
|                         newTemplateSettings.outbounds[freedomOutboundIndex].settings = {"domainStrategy": newValue}; |             newTemplateSettings.outbounds[freedomOutboundIndex].settings = { "domainStrategy": newValue }; | ||||||
|           } else { |           } else { | ||||||
|             newTemplateSettings.outbounds[freedomOutboundIndex].settings.domainStrategy = newValue; |             newTemplateSettings.outbounds[freedomOutboundIndex].settings.domainStrategy = newValue; | ||||||
|           } |           } | ||||||
|  | @ -1273,8 +1332,8 @@ | ||||||
|         }, |         }, | ||||||
|       }, |       }, | ||||||
|       WarpExist: { |       WarpExist: { | ||||||
|                 get: function() { |         get: function () { | ||||||
|                     return this.templateSettings ? this.templateSettings.outbounds.findIndex((o) => o.tag == "warp")>=0  : false; |           return this.templateSettings ? this.templateSettings.outbounds.findIndex((o) => o.tag == "warp") >= 0 : false; | ||||||
|         }, |         }, | ||||||
|       }, |       }, | ||||||
|       enableDNS: { |       enableDNS: { | ||||||
|  | @ -1410,5 +1469,4 @@ | ||||||
|     }, |     }, | ||||||
|   }); |   }); | ||||||
| </script> | </script> | ||||||
| </body> | {{ template "page/body_end" .}} | ||||||
| </html> |  | ||||||
|  | @ -33,7 +33,7 @@ | ||||||
| "sure" = "Да" | "sure" = "Да" | ||||||
| "encryption" = "Шифрование" | "encryption" = "Шифрование" | ||||||
| "useIPv4ForHost" = "Использовать IPv4 для хоста" | "useIPv4ForHost" = "Использовать IPv4 для хоста" | ||||||
| "transmission" = "Протокол" | "transmission" = "Транспорт" | ||||||
| "host" = "Хост" | "host" = "Хост" | ||||||
| "path" = "Путь" | "path" = "Путь" | ||||||
| "camouflage" = "Маскировка" | "camouflage" = "Маскировка" | ||||||
|  | @ -59,10 +59,10 @@ | ||||||
| "security" = "Безопасность" | "security" = "Безопасность" | ||||||
| "secAlertTitle" = "Предупреждение системы безопасности" | "secAlertTitle" = "Предупреждение системы безопасности" | ||||||
| "secAlertSsl" = "Это соединение не защищено. Пожалуйста, не вводите конфиденциальную информацию, пока не установите SSL сертификат для защиты соединения" | "secAlertSsl" = "Это соединение не защищено. Пожалуйста, не вводите конфиденциальную информацию, пока не установите SSL сертификат для защиты соединения" | ||||||
| "secAlertConf" = "Некоторые настройки уязвимы для атак. Рекомендуем усилить протоколы безопасности, чтобы предотвратить проблемы в будущем." | "secAlertConf" = "Некоторые настройки уязвимы для атак. Чтобы в будущем не было проблем, нужно усилить защиту." | ||||||
| "secAlertSSL" = "Ваше подключение к панели небезопасно. Пожалуйста, установите SSL сертификат для защиты данных." | "secAlertSSL" = "Ваше подключение к панели не защищено. Установите SSL сертификат для защиты данных." | ||||||
| "secAlertPanelPort" = "Порт, на котором работает панель небезопасен. Пожалуйста, установите случайный или просто другой порт." | "secAlertPanelPort" = "Порт панели по умолчанию небезопасен. Установите случайный или просто другой порт." | ||||||
| "secAlertPanelURI" = "URI-адрес панели по умолчанию небезопасен. Пожалуйста, настройте сложный URI-адрес." | "secAlertPanelURI" = "Адрес панели по умолчанию небезопасен. Сделайте адрес сложным." | ||||||
| "secAlertSubURI" = "URI-адрес подписки по умолчанию небезопасен. Пожалуйста, настройте сложный URI-адрес." | "secAlertSubURI" = "URI-адрес подписки по умолчанию небезопасен. Пожалуйста, настройте сложный URI-адрес." | ||||||
| "secAlertSubJsonURI" = "URI-адрес по умолчанию для JSON подписки небезопасен. Пожалуйста, настройте сложный URI-адрес." | "secAlertSubJsonURI" = "URI-адрес по умолчанию для JSON подписки небезопасен. Пожалуйста, настройте сложный URI-адрес." | ||||||
| "emptyDnsDesc" = "Нет добавленных DNS-серверов." | "emptyDnsDesc" = "Нет добавленных DNS-серверов." | ||||||
|  | @ -75,8 +75,8 @@ | ||||||
| "theme" = "Тема" | "theme" = "Тема" | ||||||
| "dark" = "Темная" | "dark" = "Темная" | ||||||
| "ultraDark" = "Очень темная" | "ultraDark" = "Очень темная" | ||||||
| "dashboard" = "Статус системы" | "dashboard" = "Дашборд" | ||||||
| "inbounds" = "Входящие подключения" | "inbounds" = "Инбаунды" | ||||||
| "settings" = "Настройки" | "settings" = "Настройки" | ||||||
| "xray" = "Настройки Xray" | "xray" = "Настройки Xray" | ||||||
| "logout" = "Выход" | "logout" = "Выход" | ||||||
|  | @ -91,15 +91,15 @@ | ||||||
| "invalidFormData" = "Недопустимый формат данных" | "invalidFormData" = "Недопустимый формат данных" | ||||||
| "emptyUsername" = "Введите имя пользователя" | "emptyUsername" = "Введите имя пользователя" | ||||||
| "emptyPassword" = "Введите пароль" | "emptyPassword" = "Введите пароль" | ||||||
| "wrongUsernameOrPassword" = "Неверное имя пользователя, пароль или код двухфакторной аутентификации." | "wrongUsernameOrPassword" = "Неверные данные учетной записи." | ||||||
| "successLogin" = "Успешный вход" | "successLogin" = "Вы успешно вошли в аккаунт" | ||||||
| 
 | 
 | ||||||
| [pages.index] | [pages.index] | ||||||
| "title" = "Статус системы" | "title" = "Дашборд" | ||||||
| "cpu" = "ЦП" | "cpu" = "ЦП" | ||||||
| "logicalProcessors" = "Логические процессоры" | "logicalProcessors" = "Логические процессоры" | ||||||
| "frequency" = "Частота" | "frequency" = "Частота" | ||||||
| "swap" = "Swap" | "swap" = "Файл подкачки" | ||||||
| "storage" = "Диск" | "storage" = "Диск" | ||||||
| "memory" = "ОЗУ" | "memory" = "ОЗУ" | ||||||
| "threads" = "Потоки" | "threads" = "Потоки" | ||||||
|  | @ -136,7 +136,7 @@ | ||||||
| "geofileUpdateDialogDesc" = "Это обновит файл #filename#." | "geofileUpdateDialogDesc" = "Это обновит файл #filename#." | ||||||
| "geofileUpdatePopover" = "Геофайл успешно обновлён" | "geofileUpdatePopover" = "Геофайл успешно обновлён" | ||||||
| "dontRefresh" = "Установка в процессе. Не обновляйте страницу" | "dontRefresh" = "Установка в процессе. Не обновляйте страницу" | ||||||
| "logs" = "Логи" | "logs" = "Журнал" | ||||||
| "config" = "Конфигурация" | "config" = "Конфигурация" | ||||||
| "backup" = "Резервная копия" | "backup" = "Резервная копия" | ||||||
| "backupTitle" = "Резервная копия базы данных" | "backupTitle" = "Резервная копия базы данных" | ||||||
|  | @ -151,10 +151,10 @@ | ||||||
| "getConfigError" = "Произошла ошибка при получении конфигурационного файла" | "getConfigError" = "Произошла ошибка при получении конфигурационного файла" | ||||||
| 
 | 
 | ||||||
| [pages.inbounds] | [pages.inbounds] | ||||||
| "title" = "Входящие подключения" | "title" = "Инбаунды" | ||||||
| "totalDownUp" = "Объем отправленного/полученного трафика" | "totalDownUp" = "Объем отправленного/полученного трафика" | ||||||
| "totalUsage" = "Всего трафика" | "totalUsage" = "Всего трафика" | ||||||
| "inboundCount" = "Всего подключений" | "inboundCount" = "Всего инбаундов" | ||||||
| "operate" = "Меню" | "operate" = "Меню" | ||||||
| "enable" = "Включить" | "enable" = "Включить" | ||||||
| "remark" = "Примечание" | "remark" = "Примечание" | ||||||
|  | @ -165,18 +165,18 @@ | ||||||
| "transportConfig" = "Транспорт" | "transportConfig" = "Транспорт" | ||||||
| "expireDate" = "Дата окончания" | "expireDate" = "Дата окончания" | ||||||
| "resetTraffic" = "Сброс трафика" | "resetTraffic" = "Сброс трафика" | ||||||
| "addInbound" = "Создать новое подключение" | "addInbound" = "Создать инбаунд" | ||||||
| "generalActions" = "Общие действия" | "generalActions" = "Общие действия" | ||||||
| "autoRefresh" = "Автообновление" | "autoRefresh" = "Автообновление" | ||||||
| "autoRefreshInterval" = "Интервал" | "autoRefreshInterval" = "Интервал" | ||||||
| "modifyInbound" = "Изменить входящее подключение" | "modifyInbound" = "Изменить инбаунд" | ||||||
| "deleteInbound" = "Удалить входящее подключение" | "deleteInbound" = "Удалить инбаунд" | ||||||
| "deleteInboundContent" = "Вы уверены, что хотите удалить входящее подключение?" | "deleteInboundContent" = "Вы уверены, что хотите удалить инбаунд?" | ||||||
| "deleteClient" = "Удалить клиента" | "deleteClient" = "Удалить клиента" | ||||||
| "deleteClientContent" = "Вы уверены, что хотите удалить клиента?" | "deleteClientContent" = "Вы уверены, что хотите удалить клиента?" | ||||||
| "resetTrafficContent" = "Вы уверены, что хотите сбросить трафик?" | "resetTrafficContent" = "Вы уверены, что хотите сбросить трафик?" | ||||||
| "inboundUpdateSuccess" = "Входящее подключение успешно обновлено." | "inboundUpdateSuccess" = "Инбаунд успешно обновлен." | ||||||
| "inboundCreateSuccess" = "Входящее подключение успешно создано." | "inboundCreateSuccess" = "Инбаунд успешно создан." | ||||||
| "copyLink" = "Копировать ссылку" | "copyLink" = "Копировать ссылку" | ||||||
| "address" = "Адрес" | "address" = "Адрес" | ||||||
| "network" = "Сеть" | "network" = "Сеть" | ||||||
|  | @ -196,11 +196,11 @@ | ||||||
| "export" = "Экспорт ссылок" | "export" = "Экспорт ссылок" | ||||||
| "clone" = "Клонировать" | "clone" = "Клонировать" | ||||||
| "cloneInbound" = "Клонировать" | "cloneInbound" = "Клонировать" | ||||||
| "cloneInboundContent" = "Будут клонированы все настройки входящих подключений, кроме списка клиентов, порта и IP-адреса прослушивания" | "cloneInboundContent" = "Будут клонированы все настройки инбаундов, кроме списка клиентов, порта и IP-адреса прослушивания" | ||||||
| "cloneInboundOk" = "Клонировано" | "cloneInboundOk" = "Клонировано" | ||||||
| "resetAllTraffic" = "Сброс трафика всех подключений" | "resetAllTraffic" = "Сброс трафика всех инбаундов" | ||||||
| "resetAllTrafficTitle" = "Сброс трафика всех подключений" | "resetAllTrafficTitle" = "Сброс трафика всех инбаундов" | ||||||
| "resetAllTrafficContent" = "Вы уверены, что хотите сбросить трафик всех подключений?" | "resetAllTrafficContent" = "Вы уверены, что хотите сбросить трафик всех инбаундов?" | ||||||
| "resetInboundClientTraffics" = "Сброс трафика клиента" | "resetInboundClientTraffics" = "Сброс трафика клиента" | ||||||
| "resetInboundClientTrafficTitle" = "Сброс трафика клиентов" | "resetInboundClientTrafficTitle" = "Сброс трафика клиентов" | ||||||
| "resetInboundClientTrafficContent" = "Вы уверены, что хотите сбросить трафик для этих клиентов?" | "resetInboundClientTrafficContent" = "Вы уверены, что хотите сбросить трафик для этих клиентов?" | ||||||
|  | @ -222,10 +222,10 @@ | ||||||
| "subscriptionDesc" = "Вы можете найти свою ссылку подписки в разделе 'Подробнее'" | "subscriptionDesc" = "Вы можете найти свою ссылку подписки в разделе 'Подробнее'" | ||||||
| "info" = "Информация" | "info" = "Информация" | ||||||
| "same" = "Тот же" | "same" = "Тот же" | ||||||
| "inboundData" = "Данные подключений" | "inboundData" = "Данные инбаундов" | ||||||
| "exportInbound" = "Экспорт входящих подключений" | "exportInbound" = "Экспорт инбаундов" | ||||||
| "import" = "Импортировать" | "import" = "Импортировать" | ||||||
| "importInbound" = "Импорт входящих подключений" | "importInbound" = "Импорт инбаундов" | ||||||
| 
 | 
 | ||||||
| [pages.client] | [pages.client] | ||||||
| "add" = "Создать клиента" | "add" = "Создать клиента" | ||||||
|  | @ -249,13 +249,13 @@ | ||||||
| "obtain" = "Получить" | "obtain" = "Получить" | ||||||
| "updateSuccess" = "Обновление прошло успешно" | "updateSuccess" = "Обновление прошло успешно" | ||||||
| "logCleanSuccess" = "Лог был очищен" | "logCleanSuccess" = "Лог был очищен" | ||||||
| "inboundsUpdateSuccess" = "Входящие подключения успешно обновлены" | "inboundsUpdateSuccess" = "Инбаунды успешно обновлены" | ||||||
| "inboundUpdateSuccess" = "Входящее подключение успешно обновлено" | "inboundUpdateSuccess" = "Инбаунд успешно обновлено" | ||||||
| "inboundCreateSuccess" = "Входящее подключение успешно создано" | "inboundCreateSuccess" = "Инбаунд успешно создано" | ||||||
| "inboundDeleteSuccess" = "Входящее подключение успешно удалено" | "inboundDeleteSuccess" = "Инбаунд успешно удалено" | ||||||
| "inboundClientAddSuccess" = "Клиент(ы) входящего подключения добавлен(ы)" | "inboundClientAddSuccess" = "Клиент(ы) инбаунда добавлен(ы)" | ||||||
| "inboundClientDeleteSuccess" = "Клиент входящего подключения удалён" | "inboundClientDeleteSuccess" = "Клиент инбаунда удалён" | ||||||
| "inboundClientUpdateSuccess" = "Клиент входящего подключения обновлён" | "inboundClientUpdateSuccess" = "Клиент инбаунда обновлён" | ||||||
| "delDepletedClientsSuccess" = "Все исчерпанные клиенты удалены" | "delDepletedClientsSuccess" = "Все исчерпанные клиенты удалены" | ||||||
| "resetAllClientTrafficSuccess" = "Весь трафик клиента сброшен" | "resetAllClientTrafficSuccess" = "Весь трафик клиента сброшен" | ||||||
| "resetAllTrafficSuccess" = "Весь трафик сброшен" | "resetAllTrafficSuccess" = "Весь трафик сброшен" | ||||||
|  | @ -281,15 +281,15 @@ | ||||||
| [pages.settings] | [pages.settings] | ||||||
| "title" = "Настройки" | "title" = "Настройки" | ||||||
| "save" = "Сохранить" | "save" = "Сохранить" | ||||||
| "infoDesc" = "Каждое выполненное изменение необходимо сохранить. Пожалуйста, перезапустите панель, чтобы изменения вступили в силу" | "infoDesc" = "Каждое внесённое изменение должно быть сохранено. Пожалуйста, перезапустите панель, чтобы изменения вступили в силу." | ||||||
| "restartPanel" = "Перезапуск панели" | "restartPanel" = "Перезапуск панели" | ||||||
| "restartPanelDesc" = "Вы уверены, что хотите перезапустить панель? Подтвердите, и перезапуск произойдёт через 3 секунды. Если панель будет недоступна, проверьте лог сервера" | "restartPanelDesc" = "Вы уверены, что хотите перезапустить панель? Подтвердите, и перезапуск произойдёт через 3 секунды. Если панель будет недоступна, проверьте лог сервера" | ||||||
| "restartPanelSuccess" = "Панель успешно перезапущена" | "restartPanelSuccess" = "Панель успешно перезапущена" | ||||||
| "actions" = "Действия" | "actions" = "Действия" | ||||||
| "resetDefaultConfig" = "Восстановить настройки по умолчанию" | "resetDefaultConfig" = "Восстановить настройки по умолчанию" | ||||||
| "panelSettings" = "Настройки панели" | "panelSettings" = "Панель" | ||||||
| "securitySettings" = "Настройки безопасности" | "securitySettings" = "Учетная запись" | ||||||
| "TGBotSettings" = "Настройки Telegram бота" | "TGBotSettings" = "Telegram" | ||||||
| "panelListeningIP" = "IP-адрес для управления панелью" | "panelListeningIP" = "IP-адрес для управления панелью" | ||||||
| "panelListeningIPDesc" = "Оставьте пустым для подключения с любого IP" | "panelListeningIPDesc" = "Оставьте пустым для подключения с любого IP" | ||||||
| "panelListeningDomain" = "Домен панели" | "panelListeningDomain" = "Домен панели" | ||||||
|  | @ -303,7 +303,7 @@ | ||||||
| "panelUrlPath" = "Корневой путь URL адреса панели" | "panelUrlPath" = "Корневой путь URL адреса панели" | ||||||
| "panelUrlPathDesc" = "Должен начинаться с '/' и заканчиваться '/'" | "panelUrlPathDesc" = "Должен начинаться с '/' и заканчиваться '/'" | ||||||
| "pageSize" = "Размер нумерации страниц" | "pageSize" = "Размер нумерации страниц" | ||||||
| "pageSizeDesc" = "Определить размер страницы для таблицы входящих подключений. Установите 0, чтобы отключить" | "pageSizeDesc" = "Определить размер страницы для таблицы инбаундов. Установите 0, чтобы отключить" | ||||||
| "remarkModel" = "Модель примечания и символ разделения" | "remarkModel" = "Модель примечания и символ разделения" | ||||||
| "datepicker" = "Выбор даты" | "datepicker" = "Выбор даты" | ||||||
| "datepickerPlaceholder" = "Выберите дату" | "datepickerPlaceholder" = "Выберите дату" | ||||||
|  | @ -391,7 +391,7 @@ | ||||||
| [pages.xray] | [pages.xray] | ||||||
| "title" = "Настройки Xray" | "title" = "Настройки Xray" | ||||||
| "save" = "Сохранить" | "save" = "Сохранить" | ||||||
| "restart" = "Перезапустить Xray" | "restart" = "Перезапуск Xray" | ||||||
| "restartSuccess" = "Xray успешно перезапущен" | "restartSuccess" = "Xray успешно перезапущен" | ||||||
| "stopSuccess" = "Xray успешно остановлен" | "stopSuccess" = "Xray успешно остановлен" | ||||||
| "restartError" = "Произошла ошибка при перезапуске Xray." | "restartError" = "Произошла ошибка при перезапуске Xray." | ||||||
|  | @ -402,7 +402,7 @@ | ||||||
| "generalConfigsDesc" = "Эти параметры описывают общие настройки" | "generalConfigsDesc" = "Эти параметры описывают общие настройки" | ||||||
| "logConfigs" = "Логи" | "logConfigs" = "Логи" | ||||||
| "logConfigsDesc" = "Логи могут замедлять работу сервера. Включайте только нужные вам виды логов при необходимости!" | "logConfigsDesc" = "Логи могут замедлять работу сервера. Включайте только нужные вам виды логов при необходимости!" | ||||||
| "blockConfigsDesc" = "Настройте, чтобы клиенты не имели доступа к определенным протоколам и веб-сайтами" | "blockConfigsDesc" = "Настройте, чтобы клиенты не имели доступа к определенным протоколам" | ||||||
| "basicRouting" = "Базовые соединения" | "basicRouting" = "Базовые соединения" | ||||||
| "blockConnectionsConfigsDesc" = "Эти параметры будут блокировать трафик в зависимости от страны назначения." | "blockConnectionsConfigsDesc" = "Эти параметры будут блокировать трафик в зависимости от страны назначения." | ||||||
| "directConnectionsConfigsDesc" = "Прямое соединение означает, что определенный трафик не будет перенаправлен через другой сервер." | "directConnectionsConfigsDesc" = "Прямое соединение означает, что определенный трафик не будет перенаправлен через другой сервер." | ||||||
|  | @ -413,19 +413,19 @@ | ||||||
| "ipv4Routing" = "Правила IPv4" | "ipv4Routing" = "Правила IPv4" | ||||||
| "ipv4RoutingDesc" = "Эти параметры позволят клиентам маршрутизироваться к целевым доменам только через IPv4" | "ipv4RoutingDesc" = "Эти параметры позволят клиентам маршрутизироваться к целевым доменам только через IPv4" | ||||||
| "warpRouting" = "Правила WARP" | "warpRouting" = "Правила WARP" | ||||||
| "warpRoutingDesc" = "Внимание: перед использованием этих параметров установите WARP в режиме прокси-сервера socks5 на свой сервер, следуя инструкциям на GitHub панели. WARP будет направлять трафик на веб-сайты через серверы Cloudflare" | "warpRoutingDesc" = " Эти опции будут направлять трафик в зависимости от конкретного пункта назначения через WARP." | ||||||
| "Template" = "Шаблон конфигурации Xray" | "Template" = "Шаблон конфигурации Xray" | ||||||
| "TemplateDesc" = "Создание файла конфигурации Xray на основе этого шаблона" | "TemplateDesc" = "На основе шаблона создаётся конфигурационный файл Xray." | ||||||
| "FreedomStrategy" = "Настройка стратегии протокола Freedom" | "FreedomStrategy" = "Настройка стратегии протокола Freedom" | ||||||
| "FreedomStrategyDesc" = "Установка стратегии вывода сети в протоколе Freedom" | "FreedomStrategyDesc" = "Установка стратегии вывода сети в протоколе Freedom" | ||||||
| "RoutingStrategy" = "Настройка маршрутизации доменов" | "RoutingStrategy" = "Настройка маршрутизации доменов" | ||||||
| "RoutingStrategyDesc" = "Установка общей стратегии маршрутизации разрешения DNS" | "RoutingStrategyDesc" = "Установка общей стратегии маршрутизации разрешения DNS" | ||||||
| "Torrent" = "Заблокировать BitTorrent" | "Torrent" = "Заблокировать BitTorrent" | ||||||
| "Inbounds" = "Входящее соединение" | "Inbounds" = "Инбаунды" | ||||||
| "InboundsDesc" = "Изменение шаблона конфигурации для подключения определенных клиентов" | "InboundsDesc" = "Изменение шаблона конфигурации для подключения определенных клиентов" | ||||||
| "Outbounds" = "Исходящее соединение" | "Outbounds" = "Аутбаунды" | ||||||
| "Balancers" = "Балансировщик" | "Balancers" = "Балансировщик" | ||||||
| "OutboundsDesc" = "Изменение шаблона конфигурации, чтобы определить исходящие соединения для этого сервера" | "OutboundsDesc" = "Изменение шаблона конфигурации, чтобы определить аутбаунды для этого сервера" | ||||||
| "Routings" = "Маршрутизация" | "Routings" = "Маршрутизация" | ||||||
| "RoutingsDesc" = "Важен приоритет каждого правила!" | "RoutingsDesc" = "Важен приоритет каждого правила!" | ||||||
| "completeTemplate" = "Все" | "completeTemplate" = "Все" | ||||||
|  | @ -456,8 +456,8 @@ | ||||||
| "down" = "Опустить вниз" | "down" = "Опустить вниз" | ||||||
| "source" = "Источник" | "source" = "Источник" | ||||||
| "dest" = "Пункт назначения" | "dest" = "Пункт назначения" | ||||||
| "inbound" = "Входящее соединение" | "inbound" = "Инбаунд" | ||||||
| "outbound" = "Исходящее соединение" | "outbound" = "Аутбаунд" | ||||||
| "balancer" = "Балансировщик" | "balancer" = "Балансировщик" | ||||||
| "info" = "Информация" | "info" = "Информация" | ||||||
| "add" = "Создать правило" | "add" = "Создать правило" | ||||||
|  | @ -465,9 +465,9 @@ | ||||||
| "useComma" = "Элементы, разделённые запятыми" | "useComma" = "Элементы, разделённые запятыми" | ||||||
| 
 | 
 | ||||||
| [pages.xray.outbound] | [pages.xray.outbound] | ||||||
| "addOutbound" = "Создать исходящее соединение" | "addOutbound" = "Создать аутбаунд" | ||||||
| "addReverse" = "Создать реверс-прокси" | "addReverse" = "Создать реверс-прокси" | ||||||
| "editOutbound" = "Изменить исходящее соединение" | "editOutbound" = "Изменить аутбаунд" | ||||||
| "editReverse" = "Редактировать реверс-прокси" | "editReverse" = "Редактировать реверс-прокси" | ||||||
| "tag" = "Тег" | "tag" = "Тег" | ||||||
| "tagDesc" = "Уникальный тег" | "tagDesc" = "Уникальный тег" | ||||||
|  | @ -481,7 +481,7 @@ | ||||||
| "intercon" = "Соединение" | "intercon" = "Соединение" | ||||||
| "settings" = "Настройки" | "settings" = "Настройки" | ||||||
| "accountInfo" = "Информация об учетной записи" | "accountInfo" = "Информация об учетной записи" | ||||||
| "outboundStatus" = "Исходящий статус" | "outboundStatus" = "Статус аутбаунда" | ||||||
| "sendThrough" = "Отправить через" | "sendThrough" = "Отправить через" | ||||||
| 
 | 
 | ||||||
| [pages.xray.balancer] | [pages.xray.balancer] | ||||||
|  | @ -494,7 +494,7 @@ | ||||||
| "balancerDesc" = "Невозможно одновременно использовать balancerTag и outboundTag. При одновременном использовании будет работать только outboundTag." | "balancerDesc" = "Невозможно одновременно использовать balancerTag и outboundTag. При одновременном использовании будет работать только outboundTag." | ||||||
| 
 | 
 | ||||||
| [pages.xray.wireguard] | [pages.xray.wireguard] | ||||||
| "secretKey" = "Приватный ключ" | "secretKey" = "Секретный ключ" | ||||||
| "publicKey" = "Публичный ключ" | "publicKey" = "Публичный ключ" | ||||||
| "allowedIPs" = "Разрешенные IP-адреса" | "allowedIPs" = "Разрешенные IP-адреса" | ||||||
| "endpoint" = "Конечная точка" | "endpoint" = "Конечная точка" | ||||||
|  | @ -555,8 +555,8 @@ | ||||||
| "modifyUser" = "Вы успешно изменили учетные данные администратора." | "modifyUser" = "Вы успешно изменили учетные данные администратора." | ||||||
| "originalUserPassIncorrect" = "Неверное имя пользователя или пароль" | "originalUserPassIncorrect" = "Неверное имя пользователя или пароль" | ||||||
| "userPassMustBeNotEmpty" = "Новое имя пользователя и новый пароль должны быть заполнены" | "userPassMustBeNotEmpty" = "Новое имя пользователя и новый пароль должны быть заполнены" | ||||||
| "getOutboundTrafficError" = "Ошибка получения исходящего трафика" | "getOutboundTrafficError" = "Ошибка получения трафика аутбаунда" | ||||||
| "resetOutboundTrafficError" = "Ошибка сброса исходящего трафика" | "resetOutboundTrafficError" = "Ошибка сброса трафика аутбаунда" | ||||||
| 
 | 
 | ||||||
| [tgbot] | [tgbot] | ||||||
| "keyboardClosed" = "❌ Клавиатура закрыта." | "keyboardClosed" = "❌ Клавиатура закрыта." | ||||||
|  | @ -564,7 +564,7 @@ | ||||||
| "noQuery" = "❌ Запрос не найден. Пожалуйста, повторите команду." | "noQuery" = "❌ Запрос не найден. Пожалуйста, повторите команду." | ||||||
| "wentWrong" = "❌ Что-то пошло не так..." | "wentWrong" = "❌ Что-то пошло не так..." | ||||||
| "noIpRecord" = "❗ Нет записей об IP-адресе." | "noIpRecord" = "❗ Нет записей об IP-адресе." | ||||||
| "noInbounds" = "❗ У вас не настроено ни одного подключения." | "noInbounds" = "❗ У вас не настроено ни одного инбаунда." | ||||||
| "unlimited" = "♾ Безлимит" | "unlimited" = "♾ Безлимит" | ||||||
| "add" = "Добавить" | "add" = "Добавить" | ||||||
| "month" = "Месяц" | "month" = "Месяц" | ||||||
|  | @ -573,7 +573,7 @@ | ||||||
| "days" = "Дней" | "days" = "Дней" | ||||||
| "hours" = "Часов" | "hours" = "Часов" | ||||||
| "unknown" = "Неизвестно" | "unknown" = "Неизвестно" | ||||||
| "inbounds" = "Подключения" | "inbounds" = "Инбаунды" | ||||||
| "clients" = "Клиенты" | "clients" = "Клиенты" | ||||||
| "offline" = "🔴 Офлайн" | "offline" = "🔴 Офлайн" | ||||||
| "online" = "🟢 Онлайн" | "online" = "🟢 Онлайн" | ||||||
|  | @ -587,7 +587,7 @@ | ||||||
| "status" = "✅ Бот функционирует нормально." | "status" = "✅ Бот функционирует нормально." | ||||||
| "usage" = "❗ Пожалуйста, укажите email для поиска." | "usage" = "❗ Пожалуйста, укажите email для поиска." | ||||||
| "getID" = "🆔 Ваш User ID: <code>{{ .ID }}</code>" | "getID" = "🆔 Ваш User ID: <code>{{ .ID }}</code>" | ||||||
| "helpAdminCommands" = "🔃 Для перезапуска Xray Core:\r\n<code>/restart</code>\r\n\r\n🔎 Для поиска клиента по email:\r\n<code>/usage [Email]</code>\r\n\r\n📊 Для поиска подключений (со статистикой клиентов):\r\n<code>/inbound [имя подключения]</code>\r\n\r\n🆔 Ваш Telegram User ID:\r\n<code>/id</code>" | "helpAdminCommands" = "🔃 Для перезапуска Xray Core:\r\n<code>/restart</code>\r\n\r\n🔎 Для поиска клиента по email:\r\n<code>/usage [Email]</code>\r\n\r\n📊 Для поиска инбаундов (со статистикой клиентов):\r\n<code>/inbound [имя подключения]</code>\r\n\r\n🆔 Ваш Telegram User ID:\r\n<code>/id</code>" | ||||||
| "helpClientCommands" = "💲 Для просмотра информации о вашей подписке используйте команду:\r\n<code>/usage [Email]</code>\r\n\r\n🆔 Ваш Telegram User ID:\r\n<code>/id</code>" | "helpClientCommands" = "💲 Для просмотра информации о вашей подписке используйте команду:\r\n<code>/usage [Email]</code>\r\n\r\n🆔 Ваш Telegram User ID:\r\n<code>/id</code>" | ||||||
| "restartUsage" = "\r\n\r\n<code>/restart</code>" | "restartUsage" = "\r\n\r\n<code>/restart</code>" | ||||||
| "restartSuccess" = "✅ Ядро Xray успешно перезапущено." | "restartSuccess" = "✅ Ядро Xray успешно перезапущено." | ||||||
|  | @ -653,8 +653,8 @@ | ||||||
| "pass_prompt" = "🔑 Стандартный пароль: {{ .ClientPassword }}\n\nВведите ваш пароль." | "pass_prompt" = "🔑 Стандартный пароль: {{ .ClientPassword }}\n\nВведите ваш пароль." | ||||||
| "email_prompt" = "📧 Стандартный email: {{ .ClientEmail }}\n\nВведите ваш email." | "email_prompt" = "📧 Стандартный email: {{ .ClientEmail }}\n\nВведите ваш email." | ||||||
| "comment_prompt" = "💬 Стандартный комментарий: {{ .ClientComment }}\n\nВведите ваш комментарий." | "comment_prompt" = "💬 Стандартный комментарий: {{ .ClientComment }}\n\nВведите ваш комментарий." | ||||||
| "inbound_client_data_id" = "🔄 Подключения: {{ .InboundRemark }}\n\n🔑 ID: {{ .ClientId }}\n📧 Email: {{ .ClientEmail }}\n📊 Трафик: {{ .ClientTraffic }}\n📅 Дата исчерпания: {{ .ClientExp }}\n💬 Комментарий: {{ .ClientComment }}\n\nТеперь вы можете добавить клиента в подключение!" | "inbound_client_data_id" = "🔄 Инбаунды: {{ .InboundRemark }}\n\n🔑 ID: {{ .ClientId }}\n📧 Email: {{ .ClientEmail }}\n📊 Трафик: {{ .ClientTraffic }}\n📅 Дата исчерпания: {{ .ClientExp }}\n💬 Комментарий: {{ .ClientComment }}\n\nТеперь вы можете добавить клиента в инбаунд!" | ||||||
| "inbound_client_data_pass" = "🔄 Подключения: {{ .InboundRemark }}\n\n🔑 Пароль: {{ .ClientPass }}\n📧 Email: {{ .ClientEmail }}\n📊 Трафик: {{ .ClientTraffic }}\n📅 Дата исчерпания: {{ .ClientExp }}\n💬 Комментарий: {{ .ClientComment }}\n\nТеперь вы можете добавить клиента в подключение!" | "inbound_client_data_pass" = "🔄 Инбаунды: {{ .InboundRemark }}\n\n🔑 Пароль: {{ .ClientPass }}\n📧 Email: {{ .ClientEmail }}\n📊 Трафик: {{ .ClientTraffic }}\n📅 Дата исчерпания: {{ .ClientExp }}\n💬 Комментарий: {{ .ClientComment }}\n\nТеперь вы можете добавить клиента в инбаунд!" | ||||||
| "cancel" = "❌ Процесс отменён! \n\nВы можете снова начать с /start в любое время. 🔄" | "cancel" = "❌ Процесс отменён! \n\nВы можете снова начать с /start в любое время. 🔄" | ||||||
| "error_add_client"  = "⚠️ Ошибка:\n\n {{ .error }}" | "error_add_client"  = "⚠️ Ошибка:\n\n {{ .error }}" | ||||||
| "using_default_value"  = "Используется значение по умолчанию👌" | "using_default_value"  = "Используется значение по умолчанию👌" | ||||||
|  | @ -676,7 +676,7 @@ | ||||||
| "confirmToggle" = "✅ Подтвердить вкл/выкл пользователя?" | "confirmToggle" = "✅ Подтвердить вкл/выкл пользователя?" | ||||||
| "dbBackup" = "📂 Бэкап БД" | "dbBackup" = "📂 Бэкап БД" | ||||||
| "serverUsage" = "💻 Состояние сервера" | "serverUsage" = "💻 Состояние сервера" | ||||||
| "getInbounds" = "🔌 Подключения" | "getInbounds" = "🔌 Инбаунды" | ||||||
| "depleteSoon" = "⚠️ Скоро конец" | "depleteSoon" = "⚠️ Скоро конец" | ||||||
| "clientUsage" = "Статистика клиента" | "clientUsage" = "Статистика клиента" | ||||||
| "onlines" = "🟢 Онлайн" | "onlines" = "🟢 Онлайн" | ||||||
|  | @ -714,7 +714,7 @@ | ||||||
| [tgbot.answers] | [tgbot.answers] | ||||||
| "successfulOperation" = "✅ Успешно!" | "successfulOperation" = "✅ Успешно!" | ||||||
| "errorOperation" = "❗ Ошибка в операции." | "errorOperation" = "❗ Ошибка в операции." | ||||||
| "getInboundsFailed" = "❌ Не удалось получить подключения." | "getInboundsFailed" = "❌ Не удалось получить инбаунды." | ||||||
| "getClientsFailed" = "❌ Не удалось получить клиентов." | "getClientsFailed" = "❌ Не удалось получить клиентов." | ||||||
| "canceled" = "❌ {{ .Email }}: Операция отменена." | "canceled" = "❌ {{ .Email }}: Операция отменена." | ||||||
| "clientRefreshSuccess" = "✅ {{ .Email }}: Клиент успешно обновлен." | "clientRefreshSuccess" = "✅ {{ .Email }}: Клиент успешно обновлен." | ||||||
|  | @ -731,5 +731,5 @@ | ||||||
| "enableSuccess" = "✅ {{ .Email }}: Включено успешно." | "enableSuccess" = "✅ {{ .Email }}: Включено успешно." | ||||||
| "disableSuccess" = "✅ {{ .Email }}: Отключено успешно." | "disableSuccess" = "✅ {{ .Email }}: Отключено успешно." | ||||||
| "askToAddUserId" = "❌ Ваша конфигурация не найдена!\r\n💭 Пожалуйста, попросите администратора использовать ваш Telegram User ID в конфигурации.\r\n\r\n🆔 Ваш User ID: <code>{{ .TgUserID }}</code>" | "askToAddUserId" = "❌ Ваша конфигурация не найдена!\r\n💭 Пожалуйста, попросите администратора использовать ваш Telegram User ID в конфигурации.\r\n\r\n🆔 Ваш User ID: <code>{{ .TgUserID }}</code>" | ||||||
| "chooseClient" = "Выберите клиента для подключения {{ .Inbound }}" | "chooseClient" = "Выберите клиента для инбаунда {{ .Inbound }}" | ||||||
| "chooseInbound" = "Выберите подключение" | "chooseInbound" = "Выберите инбаунд" | ||||||
|  |  | ||||||
		Loading…
	
		Reference in a new issue
	
	 Torotin
						Torotin