mirror of
				https://github.com/MHSanaei/3x-ui.git
				synced 2025-10-31 04:12:51 +00:00 
			
		
		
		
	Compare commits
	
		
			9 commits
		
	
	
		
			bacbcc61a4
			...
			f7a3ebf2f3
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | f7a3ebf2f3 | ||
|   | c8c0e77714 | ||
|   | 49b8f46864 | ||
|   | cad07be847 | ||
|   | 4b20f16024 | ||
|   | d642774a44 | ||
|   | 1644904755 | ||
|   | 5c10035bd9 | ||
|   | 2e6faf69e6 | 
					 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> | ||||||
|  | @ -456,7 +446,7 @@ | ||||||
|     </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,8 +451,9 @@ | ||||||
|     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' }"> | ||||||
|  | @ -543,7 +542,7 @@ | ||||||
|     </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({ | ||||||
|  | @ -622,5 +621,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> | ||||||
|   @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" .}} | ||||||
|  | 
 | ||||||
|  | {{ 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="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" .}} | ||||||
|  | 
 | ||||||
|  | {{ 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" 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 }, | ||||||
|   ]; |   ]; | ||||||
|  | @ -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; | ||||||
|  | @ -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