mirror of
				https://github.com/MHSanaei/3x-ui.git
				synced 2025-10-31 04:12:51 +00:00 
			
		
		
		
	Compare commits
	
		
			No commits in common. "f7a3ebf2f3c28d40c1ae126f73ac6a8c9e22c2c6" and "bacbcc61a436112c720f6c2920b844ab0eebb987" have entirely different histories.
		
	
	
		
			f7a3ebf2f3
			...
			bacbcc61a4
		
	
		
					 15 changed files with 2537 additions and 2780 deletions
				
			
		|  | @ -2150,7 +2150,7 @@ Inbound.TrojanSettings.Fallback = class extends XrayCommonClass { | |||
| Inbound.ShadowsocksSettings = class extends Inbound.Settings { | ||||
|     constructor(protocol, | ||||
|         method = SSMethods.BLAKE3_AES_256_GCM, | ||||
|         password = '', | ||||
|         password = RandomUtil.randomShadowsocksPassword(), | ||||
|         network = 'tcp,udp', | ||||
|         shadowsockses = [new Inbound.ShadowsocksSettings.Shadowsocks()], | ||||
|         ivCheck = false, | ||||
|  | @ -2188,7 +2188,7 @@ Inbound.ShadowsocksSettings = class extends Inbound.Settings { | |||
| Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass { | ||||
|     constructor( | ||||
|         method = '', | ||||
|         password = '', | ||||
|         password = RandomUtil.randomShadowsocksPassword(), | ||||
|         email = RandomUtil.randomLowerAndNum(8), | ||||
|         limitIp = 0, | ||||
|         totalGB = 0, | ||||
|  |  | |||
|  | @ -138,14 +138,8 @@ class RandomUtil { | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     static randomShadowsocksPassword(method = SSMethods.BLAKE3_AES_256_GCM) { | ||||
|         let length = 32; | ||||
| 
 | ||||
|         if ([SSMethods.BLAKE3_AES_128_GCM].includes(method)) { | ||||
|             length = 16;  | ||||
|         } | ||||
| 
 | ||||
|         const array = new Uint8Array(length); | ||||
|     static randomShadowsocksPassword() { | ||||
|         const array = new Uint8Array(32); | ||||
| 
 | ||||
|         window.crypto.getRandomValues(array); | ||||
| 
 | ||||
|  | @ -795,25 +789,6 @@ class LanguageManager { | |||
|             if (window.navigator) { | ||||
|                 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)) { | ||||
|                     CookieManager.setCookie("lang", lang, 150); | ||||
|                 } else { | ||||
|  |  | |||
							
								
								
									
										31
									
								
								web/html/common/head.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								web/html/common/head.html
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,31 @@ | |||
| {{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}} | ||||
							
								
								
									
										14
									
								
								web/html/common/js.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								web/html/common/js.html
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,14 @@ | |||
| {{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}} | ||||
|  | @ -1,58 +0,0 @@ | |||
| {{ 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,28 +41,14 @@ | |||
|   </template> | ||||
| </template> | ||||
| <template slot="client" slot-scope="text, client"> | ||||
|   <a-space direction="horizontal" :size="2"> | ||||
|     <a-tooltip> | ||||
|       <template slot="title"> | ||||
|         <template v-if="!isClientEnabled(record, client.email)">{{ i18n "depleted" }}</template> | ||||
|         <template v-else-if="!client.enable">{{ i18n "disabled" }}</template> | ||||
|         <template v-else-if="client.enable && isClientOnline(client.email)">{{ i18n "online" }}</template> | ||||
|       </template> | ||||
|       <a-badge :class="isClientOnline(client.email)? 'online-animation' : ''" :color="client.enable ? statsExpColor(record, client.email) : themeSwitcher.isDarkTheme ? '#2c3950' : '#bcbcbc'"></a-badge> | ||||
|     </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> | ||||
|   <a-tooltip> | ||||
|     <template slot="title"> | ||||
|       <template v-if="!isClientEnabled(record, client.email)">{{ i18n "depleted" }}</template> | ||||
|       <template v-else-if="!client.enable">{{ i18n "disabled" }}</template> | ||||
|       <template v-else-if="client.enable && isClientOnline(client.email)">{{ i18n "online" }}</template> | ||||
|     </template> | ||||
|     <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 ]] | ||||
| </template> | ||||
| <template slot="traffic" slot-scope="text, client"> | ||||
|   <a-popover :overlay-class-name="themeSwitcher.currentTheme"> | ||||
|  |  | |||
|  | @ -22,7 +22,7 @@ | |||
|                     <span>{{ i18n "reset" }}</span> | ||||
|                 </template> | ||||
|                 {{ i18n "password" }} | ||||
|                 <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.SHADOWSOCKS"@click="client.password = RandomUtil.randomShadowsocksPassword()" type="sync"></a-icon> | ||||
|                 <a-icon v-if="inbound.protocol === Protocols.TROJAN" @click="client.password = RandomUtil.randomSeq(10)"type="sync"> </a-icon> | ||||
|             </a-tooltip> | ||||
|         </template> | ||||
|  |  | |||
|  | @ -31,7 +31,7 @@ | |||
|             <a-tooltip> | ||||
|                 <template slot="title"> | ||||
|                     <span>{{ i18n "reset" }}</span> | ||||
|                 </template> Password <a-icon @click="inbound.settings.password = RandomUtil.randomShadowsocksPassword(inbound.settings.method)" type="sync"></a-icon> | ||||
|                 </template> Password <a-icon @click="inbound.settings.password = RandomUtil.randomShadowsocksPassword()" type="sync"></a-icon> | ||||
|             </a-tooltip> | ||||
|         </template> | ||||
|         <a-input v-model.trim="inbound.settings.password"></a-input> | ||||
|  |  | |||
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							|  | @ -1,9 +1,20 @@ | |||
| {{ template "page/head_start" .}} | ||||
| <!DOCTYPE html> | ||||
| <html lang="en"> | ||||
| {{template "head" .}} | ||||
| <style> | ||||
|   @media (min-width: 769px) { | ||||
|     .ant-layout-content { | ||||
|       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 { | ||||
|     color: var(--dark-color-text-primary); | ||||
|  | @ -68,385 +79,384 @@ | |||
|     } | ||||
|   } | ||||
| </style> | ||||
| {{ template "page/head_end" .}} | ||||
| 
 | ||||
| {{ template "page/body_start" .}} | ||||
| <a-layout id="app" v-cloak :class="themeSwitcher.currentTheme"> | ||||
|   <a-sidebar></a-sidebar> | ||||
|   <a-layout id="content-layout"> | ||||
|     <a-layout-content> | ||||
|       <a-spin :spinning="loadingStates.spinning" :delay="200" :tip="loadingTip"> | ||||
|         <transition name="list" appear> | ||||
|           <a-alert type="error" v-if="showAlert && loadingStates.fetched" :style="{ marginBottom: '10px' }" | ||||
|             message='{{ i18n "secAlertTitle" }}' | ||||
|             color="red" | ||||
|             description='{{ i18n "secAlertSsl" }}' | ||||
|             show-icon closable> | ||||
|           </a-alert> | ||||
|         </transition> | ||||
|         <transition name="list" appear> | ||||
|           <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 :gutter="[0, isMobile ? 16 : 0]"> | ||||
|                     <a-col :sm="24" :md="12"> | ||||
|                       <a-row> | ||||
|                         <a-col :span="12" :style="{ textAlign: 'center' }"> | ||||
|                           <a-progress type="dashboard" status="normal" | ||||
|                             :stroke-color="status.cpu.color" | ||||
|                             :percent="status.cpu.percent"></a-progress> | ||||
|                           <div> | ||||
|                             <b>{{ i18n "pages.index.cpu" }}:</b> [[ CPUFormatter.cpuCoreFormat(status.cpuCores) ]]  | ||||
|                             <a-tooltip> | ||||
|                               <a-icon type="area-chart"></a-icon>  | ||||
|                               <template slot="title"> | ||||
|                                 <div><b>{{ i18n "pages.index.logicalProcessors" }}:</b> [[ (status.logicalPro) ]]</div> | ||||
|                                 <div><b>{{ i18n "pages.index.frequency" }}:</b> [[ CPUFormatter.cpuSpeedFormat(status.cpuSpeedMhz) ]]</div> | ||||
|                               </template> | ||||
|                             </a-tooltip> | ||||
|                           </div> | ||||
|                         </a-col> | ||||
|                         <a-col :span="12" :style="{ textAlign: 'center' }"> | ||||
|                           <a-progress type="dashboard" status="normal" | ||||
|                             :stroke-color="status.mem.color" | ||||
|                             :percent="status.mem.percent"></a-progress> | ||||
|                           <div> | ||||
|                             <b>{{ i18n "pages.index.memory"}}:</b> [[ SizeFormatter.sizeFormat(status.mem.current) ]] / [[ SizeFormatter.sizeFormat(status.mem.total) ]] | ||||
|                           </div> | ||||
|                         </a-col> | ||||
|                       </a-row> | ||||
|                     </a-col> | ||||
|                     <a-col :sm="24" :md="12"> | ||||
|                       <a-row> | ||||
|                         <a-col :span="12" :style="{ textAlign: 'center' }"> | ||||
|                           <a-progress type="dashboard" status="normal" | ||||
|                             :stroke-color="status.swap.color" | ||||
|                             :percent="status.swap.percent"></a-progress> | ||||
|                           <div> | ||||
|                             <b>{{ i18n "pages.index.swap" }}:</b> [[ SizeFormatter.sizeFormat(status.swap.current) ]] / [[ SizeFormatter.sizeFormat(status.swap.total) ]] | ||||
|                           </div> | ||||
|                         </a-col> | ||||
|                         <a-col :span="12" :style="{ textAlign: 'center' }"> | ||||
|                           <a-progress type="dashboard" status="normal" | ||||
|                             :stroke-color="status.disk.color" | ||||
|                             :percent="status.disk.percent"></a-progress> | ||||
|                           <div> | ||||
|                             <b>{{ i18n "pages.index.storage"}}:</b> [[ SizeFormatter.sizeFormat(status.disk.current) ]] / [[ SizeFormatter.sizeFormat(status.disk.total) ]] | ||||
|                           </div> | ||||
|                         </a-col> | ||||
|                       </a-row> | ||||
|                     </a-col> | ||||
|                   </a-row> | ||||
| <body> | ||||
|   <a-layout id="app" v-cloak :class="themeSwitcher.currentTheme"> | ||||
|     <a-sidebar></a-sidebar> | ||||
|     <a-layout id="content-layout"> | ||||
|       <a-layout-content> | ||||
|         <a-spin :spinning="spinning" :delay="200" :tip="loadingTip"> | ||||
|           <transition name="list" appear> | ||||
|             <a-alert type="error" v-if="showAlert" :style="{ marginBottom: '10px' }" | ||||
|               message='{{ i18n "secAlertTitle" }}' | ||||
|               color="red" | ||||
|               description='{{ i18n "secAlertSsl" }}' | ||||
|               show-icon closable> | ||||
|             </a-alert> | ||||
|           </transition> | ||||
|           <transition name="list" appear> | ||||
|             <template> | ||||
|               <a-row v-if="!status.isLoaded"> | ||||
|                 <a-card :style="{ textAlign: 'center', padding: '30px 0', marginTop: '10px', background: 'transparent', border: 'none' }"> | ||||
|                   <a-spin tip='{{ i18n "loading" }}'></a-spin> | ||||
|                 </a-card> | ||||
|               </a-col> | ||||
|               <a-col :sm="24" :lg="12"> | ||||
|                 <a-card hoverable> | ||||
|                   <template #title> | ||||
|                     <a-space direction="horizontal"> | ||||
|                       <span>{{ i18n "pages.index.xrayStatus" }}</span> | ||||
|                       <a-tag v-if="isMobile && status.xray.version != 'Unknown'" color="green"> | ||||
|                         v[[ status.xray.version ]] | ||||
|                       </a-tag> | ||||
|                     </a-space> | ||||
|                   </template> | ||||
|                   <template #extra> | ||||
|                     <template v-if="status.xray.state != 'error'"> | ||||
|                       <a-badge status="processing" class="running-animation" :text="status.xray.stateMsg" :color="status.xray.color"/> | ||||
|               </a-row> | ||||
|               <a-row v-else> | ||||
|                 <a-row> | ||||
|                   <a-card hoverable> | ||||
|                     <a-row> | ||||
|                       <a-col :sm="24" :md="12"> | ||||
|                         <a-row> | ||||
|                           <a-col :span="12" :style="{ textAlign: 'center' }"> | ||||
|                             <a-progress type="dashboard" status="normal" | ||||
|                               :stroke-color="status.cpu.color" | ||||
|                               :percent="status.cpu.percent"></a-progress> | ||||
|                             <div> | ||||
|                               <b>{{ i18n "pages.index.cpu" }}:</b> [[ CPUFormatter.cpuCoreFormat(status.cpuCores) ]]  | ||||
|                               <a-tooltip> | ||||
|                                 <a-icon type="area-chart"></a-icon>  | ||||
|                                 <template slot="title"> | ||||
|                                   <div><b>{{ i18n "pages.index.logicalProcessors" }}:</b> [[ (status.logicalPro) ]]</div> | ||||
|                                   <div><b>{{ i18n "pages.index.frequency" }}:</b> [[ CPUFormatter.cpuSpeedFormat(status.cpuSpeedMhz) ]]</div> | ||||
|                                 </template> | ||||
|                               </a-tooltip> | ||||
|                             </div> | ||||
|                           </a-col> | ||||
|                           <a-col :span="12" :style="{ textAlign: 'center' }"> | ||||
|                             <a-progress type="dashboard" status="normal" | ||||
|                               :stroke-color="status.mem.color" | ||||
|                               :percent="status.mem.percent"></a-progress> | ||||
|                             <div> | ||||
|                               <b>{{ i18n "pages.index.memory"}}:</b> [[ SizeFormatter.sizeFormat(status.mem.current) ]] / [[ SizeFormatter.sizeFormat(status.mem.total) ]] | ||||
|                             </div> | ||||
|                           </a-col> | ||||
|                         </a-row> | ||||
|                       </a-col> | ||||
|                       <a-col :sm="24" :md="12"> | ||||
|                         <a-row> | ||||
|                           <a-col :span="12" :style="{ textAlign: 'center' }"> | ||||
|                             <a-progress type="dashboard" status="normal" | ||||
|                               :stroke-color="status.swap.color" | ||||
|                               :percent="status.swap.percent"></a-progress> | ||||
|                             <div> | ||||
|                               <b>{{ i18n "pages.index.swap" }}:</b> [[ SizeFormatter.sizeFormat(status.swap.current) ]] / [[ SizeFormatter.sizeFormat(status.swap.total) ]] | ||||
|                             </div> | ||||
|                           </a-col> | ||||
|                           <a-col :span="12" :style="{ textAlign: 'center' }"> | ||||
|                             <a-progress type="dashboard" status="normal" | ||||
|                               :stroke-color="status.disk.color" | ||||
|                               :percent="status.disk.percent"></a-progress> | ||||
|                             <div> | ||||
|                               <b>{{ i18n "pages.index.storage"}}:</b> [[ SizeFormatter.sizeFormat(status.disk.current) ]] / [[ SizeFormatter.sizeFormat(status.disk.total) ]] | ||||
|                             </div> | ||||
|                           </a-col> | ||||
|                         </a-row> | ||||
|                       </a-col> | ||||
|                     </a-row> | ||||
|                   </a-card> | ||||
|                 </a-row> | ||||
|                 <a-col :sm="24" :lg="12"> | ||||
|                   <a-card hoverable> | ||||
|                     <template #title> | ||||
|                       <a-space direction="horizontal"> | ||||
|                         <span>{{ i18n "pages.index.xrayStatus" }}</span> | ||||
|                         <a-tag v-if="isMobile && status.xray.version != 'Unknown'" color="green"> | ||||
|                           v[[ status.xray.version ]] | ||||
|                         </a-tag> | ||||
|                       </a-space> | ||||
|                     </template> | ||||
|                     <template v-else> | ||||
|                       <a-popover :overlay-class-name="themeSwitcher.currentTheme"> | ||||
|                         <span slot="title"> | ||||
|                           <a-row type="flex" align="middle" justify="space-between"> | ||||
|                             <a-col> | ||||
|                               <span>{{ i18n "pages.index.xrayErrorPopoverTitle" }}</span> | ||||
|                             </a-col> | ||||
|                             <a-col> | ||||
|                               <a-icon type="bars" :style="{ cursor: 'pointer', float: 'right' }" @click="openLogs()"></a-tag> | ||||
|                             </a-col> | ||||
|                           </a-row> | ||||
|                     <template #extra> | ||||
|                       <template v-if="status.xray.state != 'error'"> | ||||
|                         <a-badge status="processing" class="running-animation" :text="status.xray.stateMsg" :color="status.xray.color"/> | ||||
|                       </template> | ||||
|                       <template v-else> | ||||
|                         <a-popover :overlay-class-name="themeSwitcher.currentTheme"> | ||||
|                           <span slot="title"> | ||||
|                             <a-row type="flex" align="middle" justify="space-between"> | ||||
|                               <a-col> | ||||
|                                 <span>{{ i18n "pages.index.xrayErrorPopoverTitle" }}</span> | ||||
|                               </a-col> | ||||
|                               <a-col> | ||||
|                                 <a-icon type="bars" :style="{ cursor: 'pointer', float: 'right' }" @click="openLogs()"></a-tag> | ||||
|                               </a-col> | ||||
|                             </a-row> | ||||
|                           </span> | ||||
|                           <template slot="content"> | ||||
|                             <span :style="{ maxWidth: '400px' }" v-for="line in status.xray.errorMsg.split('\n')">[[ line ]]</span> | ||||
|                           </template> | ||||
|                           <a-badge :text="status.xray.stateMsg" :color="status.xray.color"/> | ||||
|                         </a-popover> | ||||
|                       </template> | ||||
|                     </template> | ||||
|                     <template #actions> | ||||
|                       <a-space direction="horizontal" @click="stopXrayService" :style="{ justifyContent: 'center' }"> | ||||
|                         <a-icon type="poweroff"></a-icon> | ||||
|                         <span v-if="!isMobile">{{ i18n "pages.index.stopXray" }}</span> | ||||
|                       </a-space> | ||||
|                       <a-space direction="horizontal" @click="restartXrayService" :style="{ justifyContent: 'center' }"> | ||||
|                         <a-icon type="reload"></a-icon> | ||||
|                         <span v-if="!isMobile">{{ i18n "pages.index.restartXray" }}</span> | ||||
|                       </a-space> | ||||
|                       <a-space direction="horizontal" @click="openSelectV2rayVersion" :style="{ justifyContent: 'center' }"> | ||||
|                         <a-icon type="tool"></a-icon> | ||||
|                         <span v-if="!isMobile"> | ||||
|                           [[ status.xray.version != 'Unknown' ? `v${status.xray.version}` : '{{ i18n "pages.index.xraySwitch" }}' ]] | ||||
|                         </span> | ||||
|                         <template slot="content"> | ||||
|                           <span :style="{ maxWidth: '400px' }" v-for="line in status.xray.errorMsg.split('\n')">[[ line ]]</span> | ||||
|                         </template> | ||||
|                         <a-badge :text="status.xray.stateMsg" :color="status.xray.color"/> | ||||
|                       </a-popover> | ||||
|                       </a-space> | ||||
|                     </template> | ||||
|                   </template> | ||||
|                   <template #actions> | ||||
|                     <a-space direction="horizontal" @click="stopXrayService" :style="{ justifyContent: 'center' }"> | ||||
|                       <a-icon type="poweroff"></a-icon> | ||||
|                       <span v-if="!isMobile">{{ i18n "pages.index.stopXray" }}</span> | ||||
|                     </a-space> | ||||
|                     <a-space direction="horizontal" @click="restartXrayService" :style="{ justifyContent: 'center' }"> | ||||
|                       <a-icon type="reload"></a-icon> | ||||
|                       <span v-if="!isMobile">{{ i18n "pages.index.restartXray" }}</span> | ||||
|                     </a-space> | ||||
|                     <a-space direction="horizontal" @click="openSelectV2rayVersion" :style="{ justifyContent: 'center' }"> | ||||
|                       <a-icon type="tool"></a-icon> | ||||
|                       <span v-if="!isMobile"> | ||||
|                         [[ status.xray.version != 'Unknown' ? `v${status.xray.version}` : '{{ i18n "pages.index.xraySwitch" }}' ]] | ||||
|                       </span> | ||||
|                     </a-space> | ||||
|                   </template> | ||||
|                 </a-card> | ||||
|               </a-col> | ||||
|               <a-col :sm="24" :lg="12"> | ||||
|                 <a-card title='{{ i18n "menu.link" }}' hoverable> | ||||
|                   <template #actions> | ||||
|                     <a-space direction="horizontal" @click="openLogs()" :style="{ justifyContent: 'center' }"> | ||||
|                       <a-icon type="bars"></a-icon> | ||||
|                       <span v-if="!isMobile">{{ i18n "pages.index.logs" }}</span> | ||||
|                     </a-space> | ||||
|                     <a-space direction="horizontal" @click="openConfig" :style="{ justifyContent: 'center' }"> | ||||
|                       <a-icon type="control"></a-icon> | ||||
|                       <span v-if="!isMobile">{{ i18n "pages.index.config" }}</span> | ||||
|                     </a-space> | ||||
|                     <a-space direction="horizontal" @click="openBackup" :style="{ justifyContent: 'center' }"> | ||||
|                       <a-icon type="cloud-server"></a-icon> | ||||
|                       <span v-if="!isMobile">{{ i18n "pages.index.backup" }}</span> | ||||
|                     </a-space> | ||||
|                   </template> | ||||
|                 </a-card> | ||||
|               </a-col> | ||||
|               <a-col :sm="24" :lg="12"> | ||||
|                 <a-card title='3X-UI' hoverable> | ||||
|                   <a rel="noopener" href="https://github.com/MHSanaei/3x-ui/releases" target="_blank"> | ||||
|                   </a-card> | ||||
|                 </a-col> | ||||
|                 <a-col :sm="24" :lg="12"> | ||||
|                   <a-card title='{{ i18n "menu.link" }}' hoverable> | ||||
|                     <template #actions> | ||||
|                       <a-space direction="horizontal" @click="openLogs()" :style="{ justifyContent: 'center' }"> | ||||
|                         <a-icon type="bars"></a-icon> | ||||
|                         <span v-if="!isMobile">{{ i18n "pages.index.logs" }}</span> | ||||
|                       </a-space> | ||||
|                       <a-space direction="horizontal" @click="openConfig" :style="{ justifyContent: 'center' }"> | ||||
|                         <a-icon type="control"></a-icon> | ||||
|                         <span v-if="!isMobile">{{ i18n "pages.index.config" }}</span> | ||||
|                       </a-space> | ||||
|                       <a-space direction="horizontal" @click="openBackup" :style="{ justifyContent: 'center' }"> | ||||
|                         <a-icon type="cloud-server"></a-icon> | ||||
|                         <span v-if="!isMobile">{{ i18n "pages.index.backup" }}</span> | ||||
|                       </a-space> | ||||
|                     </template> | ||||
|                   </a-card> | ||||
|                 </a-col> | ||||
|                 <a-col :sm="24" :lg="12"> | ||||
|                   <a-card title='3X-UI' hoverable> | ||||
|                     <a rel="noopener" href="https://github.com/MHSanaei/3x-ui/releases" target="_blank"> | ||||
|                       <a-tag color="green"> | ||||
|                         <span>v{{ .cur_ver }}</span> | ||||
|                       </a-tag> | ||||
|                     </a> | ||||
|                     <a rel="noopener" href="https://t.me/XrayUI" target="_blank"> | ||||
|                       <a-tag color="green"> | ||||
|                         <span>@XrayUI</span> | ||||
|                       </a-tag> | ||||
|                     </a> | ||||
|                     <a rel="noopener" href="https://github.com/MHSanaei/3x-ui/wiki" target="_blank"> | ||||
|                       <a-tag> | ||||
|                         <span>{{ i18n "pages.index.documentation" }}</span> | ||||
|                       </a-tag> | ||||
|                     </a> | ||||
|                   </a-card> | ||||
|                 </a-col> | ||||
|                 <a-col :sm="24" :lg="12"> | ||||
|                   <a-card title='{{ i18n "pages.index.operationHours" }}' hoverable> | ||||
|                     <a-tag :color="status.xray.color">Xray: [[ TimeFormatter.formatSecond(status.appStats.uptime) ]]</a-tag> | ||||
|                     <a-tag color="green">OS: [[ TimeFormatter.formatSecond(status.uptime) ]]</a-tag> | ||||
|                   </a-card> | ||||
|                 </a-col> | ||||
|                 <a-col :sm="24" :lg="12"> | ||||
|                   <a-card title='{{ i18n "pages.index.systemLoad" }}' hoverable> | ||||
|                     <a-tag color="green"> | ||||
|                       <span>v{{ .cur_ver }}</span> | ||||
|                       <a-tooltip> | ||||
|                         [[ status.loads[0] ]] | [[ status.loads[1] ]] | [[ status.loads[2] ]] | ||||
|                         <template slot="title"> | ||||
|                           {{ i18n "pages.index.systemLoadDesc" }} | ||||
|                         </template> | ||||
|                       </a-tooltip> | ||||
|                     </a-tag> | ||||
|                   </a> | ||||
|                   <a rel="noopener" href="https://t.me/XrayUI" target="_blank"> | ||||
|                     <a-tag color="green"> | ||||
|                       <span>@XrayUI</span> | ||||
|                     </a-tag> | ||||
|                   </a> | ||||
|                   <a rel="noopener" href="https://github.com/MHSanaei/3x-ui/wiki" target="_blank"> | ||||
|                     <a-tag color="purple"> | ||||
|                       <span>{{ i18n "pages.index.documentation" }}</span> | ||||
|                     </a-tag> | ||||
|                   </a> | ||||
|                 </a-card> | ||||
|               </a-col> | ||||
|               <a-col :sm="24" :lg="12"> | ||||
|                 <a-card title='{{ i18n "pages.index.operationHours" }}' hoverable> | ||||
|                   <a-tag :color="status.xray.color">Xray: [[ TimeFormatter.formatSecond(status.appStats.uptime) ]]</a-tag> | ||||
|                   <a-tag color="green">OS: [[ TimeFormatter.formatSecond(status.uptime) ]]</a-tag> | ||||
|                 </a-card> | ||||
|               </a-col> | ||||
|               <a-col :sm="24" :lg="12"> | ||||
|                 <a-card title='{{ i18n "pages.index.systemLoad" }}' hoverable> | ||||
|                   <a-tag color="green"> | ||||
|                     <a-tooltip> | ||||
|                       [[ status.loads[0] ]] | [[ status.loads[1] ]] | [[ status.loads[2] ]] | ||||
|                       <template slot="title"> | ||||
|                         {{ i18n "pages.index.systemLoadDesc" }} | ||||
|                       </template> | ||||
|                     </a-tooltip> | ||||
|                   </a-tag> | ||||
|                 </a-card> | ||||
|               </a-col> | ||||
|               <a-col :sm="24" :lg="12"> | ||||
|                 <a-card title='{{ i18n "usage"}}' hoverable> | ||||
|                   <a-tag color="green"> {{ i18n "pages.index.memory" }}: [[ SizeFormatter.sizeFormat(status.appStats.mem) ]] </a-tag> | ||||
|                   <a-tag color="green"> {{ i18n "pages.index.threads" }}: [[ status.appStats.threads ]] </a-tag> | ||||
|                 </a-card> | ||||
|               </a-col> | ||||
|               <a-col :sm="24" :lg="12"> | ||||
|                 <a-card title='{{ i18n "pages.index.overallSpeed" }}' hoverable> | ||||
|                   <a-row :gutter="isMobile ? [8,8] : 0"> | ||||
|                     <a-col :span="12"> | ||||
|                       <a-custom-statistic title='{{ i18n "pages.index.upload" }}' :value="SizeFormatter.sizeFormat(status.netIO.up)"> | ||||
|                         <template #prefix> | ||||
|                           <a-icon type="arrow-up" /> | ||||
|                   </a-card> | ||||
|                 </a-col> | ||||
|                 <a-col :sm="24" :lg="12"> | ||||
|                   <a-card title='{{ i18n "usage"}}' hoverable> | ||||
|                     <a-tag color="green"> {{ i18n "pages.index.memory" }}: [[ SizeFormatter.sizeFormat(status.appStats.mem) ]] </a-tag> | ||||
|                     <a-tag color="green"> {{ i18n "pages.index.threads" }}: [[ status.appStats.threads ]] </a-tag> | ||||
|                   </a-card> | ||||
|                 </a-col> | ||||
|                 <a-col :sm="24" :lg="12"> | ||||
|                   <a-card title='{{ i18n "pages.index.overallSpeed" }}' hoverable> | ||||
|                     <a-row :gutter="isMobile ? [8,8] : 0"> | ||||
|                       <a-col :span="12"> | ||||
|                         <a-custom-statistic title='{{ i18n "pages.index.upload" }}' :value="SizeFormatter.sizeFormat(status.netIO.up)"> | ||||
|                           <template #prefix> | ||||
|                             <a-icon type="arrow-up" /> | ||||
|                           </template> | ||||
|                           <template #suffix> | ||||
|                             /s | ||||
|                           </template> | ||||
|                         </a-custom-statistic> | ||||
|                       </a-col> | ||||
|                       <a-col :span="12"> | ||||
|                         <a-custom-statistic title='{{ i18n "pages.index.download" }}' :value="SizeFormatter.sizeFormat(status.netIO.down)"> | ||||
|                           <template #prefix> | ||||
|                             <a-icon type="arrow-down" /> | ||||
|                           </template> | ||||
|                           <template #suffix> | ||||
|                             /s | ||||
|                           </template> | ||||
|                         </a-custom-statistic> | ||||
|                       </a-col> | ||||
|                     </a-row> | ||||
|                   </a-card> | ||||
|                 </a-col> | ||||
|                 <a-col :sm="24" :lg="12"> | ||||
|                   <a-card title='{{ i18n "pages.index.totalData" }}' hoverable> | ||||
|                     <a-row :gutter="isMobile ? [8,8] : 0"> | ||||
|                       <a-col :span="12"> | ||||
|                         <a-custom-statistic title='{{ i18n "pages.index.sent" }}' :value="SizeFormatter.sizeFormat(status.netTraffic.sent)"> | ||||
|                           <template #prefix> | ||||
|                             <a-icon type="cloud-upload" /> | ||||
|                           </template> | ||||
|                         </a-custom-statistic> | ||||
|                       </a-col> | ||||
|                       <a-col :span="12"> | ||||
|                         <a-custom-statistic title='{{ i18n "pages.index.received" }}' :value="SizeFormatter.sizeFormat(status.netTraffic.recv)"> | ||||
|                           <template #prefix> | ||||
|                             <a-icon type="cloud-download" /> | ||||
|                           </template> | ||||
|                         </a-custom-statistic> | ||||
|                       </a-col> | ||||
|                     </a-row> | ||||
|                   </a-card> | ||||
|                 </a-col> | ||||
|                 <a-col :sm="24" :lg="12"> | ||||
|                   <a-card title='{{ i18n "pages.index.ipAddresses" }}' hoverable> | ||||
|                     <template #extra> | ||||
|                       <a-tooltip :placement="isMobile ? 'topRight' : 'top'"> | ||||
|                         <template #title> | ||||
|                           {{ i18n "pages.index.toggleIpVisibility" }} | ||||
|                         </template> | ||||
|                         <template #suffix> | ||||
|                           /s | ||||
|                         </template> | ||||
|                       </a-custom-statistic> | ||||
|                     </a-col> | ||||
|                     <a-col :span="12"> | ||||
|                       <a-custom-statistic title='{{ i18n "pages.index.download" }}' :value="SizeFormatter.sizeFormat(status.netIO.down)"> | ||||
|                         <template #prefix> | ||||
|                           <a-icon type="arrow-down" /> | ||||
|                         </template> | ||||
|                         <template #suffix> | ||||
|                           /s | ||||
|                         </template> | ||||
|                       </a-custom-statistic> | ||||
|                     </a-col> | ||||
|                   </a-row> | ||||
|                 </a-card> | ||||
|               </a-col> | ||||
|               <a-col :sm="24" :lg="12"> | ||||
|                 <a-card title='{{ i18n "pages.index.totalData" }}' hoverable> | ||||
|                   <a-row :gutter="isMobile ? [8,8] : 0"> | ||||
|                     <a-col :span="12"> | ||||
|                       <a-custom-statistic title='{{ i18n "pages.index.sent" }}' :value="SizeFormatter.sizeFormat(status.netTraffic.sent)"> | ||||
|                         <template #prefix> | ||||
|                           <a-icon type="cloud-upload" /> | ||||
|                         </template> | ||||
|                       </a-custom-statistic> | ||||
|                     </a-col> | ||||
|                     <a-col :span="12"> | ||||
|                       <a-custom-statistic title='{{ i18n "pages.index.received" }}' :value="SizeFormatter.sizeFormat(status.netTraffic.recv)"> | ||||
|                         <template #prefix> | ||||
|                           <a-icon type="cloud-download" /> | ||||
|                         </template> | ||||
|                       </a-custom-statistic> | ||||
|                     </a-col> | ||||
|                   </a-row> | ||||
|                 </a-card> | ||||
|               </a-col> | ||||
|               <a-col :sm="24" :lg="12"> | ||||
|                 <a-card title='{{ i18n "pages.index.ipAddresses" }}' hoverable> | ||||
|                   <template #extra> | ||||
|                     <a-tooltip :placement="isMobile ? 'topRight' : 'top'"> | ||||
|                       <template #title> | ||||
|                         {{ i18n "pages.index.toggleIpVisibility" }} | ||||
|                       </template> | ||||
|                       <a-icon :type="showIp ? 'eye' : 'eye-invisible'" :style="{ fontSize: '1rem' }" @click="showIp = !showIp"></a-icon> | ||||
|                     </a-tooltip> | ||||
|                   </template> | ||||
|                   <a-row :class="showIp ? 'ip-visible' : 'ip-hidden'" :gutter="isMobile ? [8,8] : 0"> | ||||
|                     <a-col :span="isMobile ? 24 : 12"> | ||||
|                       <a-custom-statistic title="IPv4" :value="status.publicIP.ipv4"> | ||||
|                         <template #prefix> | ||||
|                           <a-icon type="global" /> | ||||
|                         </template> | ||||
|                       </a-custom-statistic> | ||||
|                     </a-col> | ||||
|                     <a-col :span="isMobile ? 24 : 12"> | ||||
|                       <a-custom-statistic title="IPv6" :value="status.publicIP.ipv6"> | ||||
|                         <template #prefix> | ||||
|                           <a-icon type="global" /> | ||||
|                         </template> | ||||
|                       </a-custom-statistic> | ||||
|                     </a-col> | ||||
|                   </a-row> | ||||
|                 </a-card> | ||||
|               </a-col> | ||||
|               <a-col :sm="24" :lg="12"> | ||||
|                 <a-card title='{{ i18n "pages.index.connectionCount" }}' hoverable> | ||||
|                   <a-row :gutter="isMobile ? [8,8] : 0"> | ||||
|                     <a-col :span="12"> | ||||
|                       <a-custom-statistic title="TCP" :value="status.tcpCount"> | ||||
|                         <template #prefix> | ||||
|                           <a-icon type="swap" /> | ||||
|                         </template> | ||||
|                       </a-custom-statistic> | ||||
|                     </a-col> | ||||
|                     <a-col :span="12"> | ||||
|                       <a-custom-statistic title="UDP" :value="status.udpCount"> | ||||
|                         <template #prefix> | ||||
|                           <a-icon type="swap" /> | ||||
|                         </template> | ||||
|                       </a-custom-statistic> | ||||
|                     </a-col> | ||||
|                   </a-row> | ||||
|                 </a-card> | ||||
|               </a-col> | ||||
|             </a-row> | ||||
|           </template> | ||||
|         </transition> | ||||
|       </a-spin> | ||||
|     </a-layout-content> | ||||
|                         <a-icon :type="showIp ? 'eye' : 'eye-invisible'" :style="{ fontSize: '1rem' }" @click="showIp = !showIp"></a-icon> | ||||
|                       </a-tooltip> | ||||
|                     </template> | ||||
|                     <a-row :class="showIp ? 'ip-visible' : 'ip-hidden'" :gutter="isMobile ? [8,8] : 0"> | ||||
|                       <a-col :span="isMobile ? 24 : 12"> | ||||
|                         <a-custom-statistic title="IPv4" :value="status.publicIP.ipv4"> | ||||
|                           <template #prefix> | ||||
|                             <a-icon type="global" /> | ||||
|                           </template> | ||||
|                         </a-custom-statistic> | ||||
|                       </a-col> | ||||
|                       <a-col :span="isMobile ? 24 : 12"> | ||||
|                         <a-custom-statistic title="IPv6" :value="status.publicIP.ipv6"> | ||||
|                           <template #prefix> | ||||
|                             <a-icon type="global" /> | ||||
|                           </template> | ||||
|                         </a-custom-statistic> | ||||
|                       </a-col> | ||||
|                     </a-row> | ||||
|                   </a-card> | ||||
|                 </a-col> | ||||
|                 <a-col :sm="24" :lg="12"> | ||||
|                   <a-card title='{{ i18n "pages.index.connectionCount" }}' hoverable> | ||||
|                     <a-row :gutter="isMobile ? [8,8] : 0"> | ||||
|                       <a-col :span="12"> | ||||
|                         <a-custom-statistic title="TCP" :value="status.tcpCount"> | ||||
|                           <template #prefix> | ||||
|                             <a-icon type="swap" /> | ||||
|                           </template> | ||||
|                         </a-custom-statistic> | ||||
|                       </a-col> | ||||
|                       <a-col :span="12"> | ||||
|                         <a-custom-statistic title="UDP" :value="status.udpCount"> | ||||
|                           <template #prefix> | ||||
|                             <a-icon type="swap" /> | ||||
|                           </template> | ||||
|                         </a-custom-statistic> | ||||
|                       </a-col> | ||||
|                     </a-row> | ||||
|                   </a-card> | ||||
|                 </a-col> | ||||
|               </a-row> | ||||
|             </template> | ||||
|           </transition> | ||||
|         </a-spin> | ||||
|       </a-layout-content> | ||||
|     </a-layout> | ||||
|     <a-modal id="version-modal" v-model="versionModal.visible" title='{{ i18n "pages.index.xraySwitch" }}' :closable="true" | ||||
|         @ok="() => versionModal.visible = false" :class="themeSwitcher.currentTheme" footer=""> | ||||
|       <a-collapse default-active-key="1"> | ||||
|         <a-collapse-panel key="1" header='Xray'> | ||||
|           <a-alert type="warning" :style="{ marginBottom: '12px', width: '100%' }" message='{{ i18n "pages.index.xraySwitchClickDesk" }}' show-icon></a-alert> | ||||
|           <a-list class="ant-version-list" bordered :style="{ width: '100%' }"> | ||||
|             <a-list-item class="ant-version-list-item" v-for="version, index in versionModal.versions"> | ||||
|               <a-tag :color="index % 2 == 0 ? 'purple' : 'green'">[[ version ]]</a-tag> | ||||
|               <a-radio :class="themeSwitcher.currentTheme" :checked="version === `v${status.xray.version}`" @click="switchV2rayVersion(version)"></a-radio> | ||||
|             </a-list-item> | ||||
|           </a-list> | ||||
|         </a-collapse-panel> | ||||
|         <a-collapse-panel key="2" header='Geofiles'> | ||||
|           <a-list class="ant-version-list" bordered :style="{ width: '100%' }"> | ||||
|             <a-list-item class="ant-version-list-item" v-for="file, index in ['geosite.dat', 'geoip.dat', 'geosite_IR.dat', 'geoip_IR.dat', 'geosite_RU.dat', 'geoip_RU.dat']"> | ||||
|               <a-tag :color="index % 2 == 0 ? 'purple' : 'green'">[[ file ]]</a-tag> | ||||
|               <a-icon type="reload" @click="updateGeofile(file)" :style="{ marginRight: '8px' }"/> | ||||
|             </a-list-item> | ||||
|           </a-list> | ||||
|         </a-collapse-panel> | ||||
|       </a-collapse> | ||||
|     </a-modal> | ||||
|     <a-modal id="log-modal" v-model="logModal.visible" | ||||
|         :closable="true" @cancel="() => logModal.visible = false" | ||||
|         :class="themeSwitcher.currentTheme" | ||||
|         width="800px" footer=""> | ||||
|       <template slot="title"> | ||||
|         {{ i18n "pages.index.logs" }} | ||||
|         <a-icon :spin="logModal.loading" | ||||
|           type="sync" | ||||
|           :style="{ verticalAlign: 'middle', marginLeft: '10px' }" | ||||
|           :disabled="logModal.loading" | ||||
|           @click="openLogs()"> | ||||
|         </a-icon> | ||||
|       </template> | ||||
|       <a-form layout="inline"> | ||||
|         <a-form-item :style="{ marginRight: '0.5rem' }"> | ||||
|           <a-input-group compact> | ||||
|             <a-select size="small" v-model="logModal.rows" :style="{ width: '70px' }" | ||||
|                 @change="openLogs()" :dropdown-class-name="themeSwitcher.currentTheme"> | ||||
|               <a-select-option value="10">10</a-select-option> | ||||
|               <a-select-option value="20">20</a-select-option> | ||||
|               <a-select-option value="50">50</a-select-option> | ||||
|               <a-select-option value="100">100</a-select-option> | ||||
|               <a-select-option value="500">500</a-select-option> | ||||
|             </a-select> | ||||
|             <a-select size="small" v-model="logModal.level" :style="{ width: '95px' }" | ||||
|                 @change="openLogs()" :dropdown-class-name="themeSwitcher.currentTheme"> | ||||
|               <a-select-option value="debug">Debug</a-select-option> | ||||
|               <a-select-option value="info">Info</a-select-option> | ||||
|               <a-select-option value="notice">Notice</a-select-option> | ||||
|               <a-select-option value="warning">Warning</a-select-option> | ||||
|               <a-select-option value="err">Error</a-select-option> | ||||
|             </a-select> | ||||
|           </a-input-group> | ||||
|         </a-form-item> | ||||
|         <a-form-item> | ||||
|           <a-checkbox v-model="logModal.syslog" @change="openLogs()">SysLog</a-checkbox> | ||||
|         </a-form-item> | ||||
|         <a-form-item :style="{ float: 'right' }"> | ||||
|           <a-button type="primary" icon="download" @click="FileManager.downloadTextFile(logModal.logs?.join('\n'), 'x-ui.log')"></a-button> | ||||
|         </a-form-item> | ||||
|       </a-form> | ||||
|       <div class="ant-input" :style="{ height: 'auto', maxHeight: '500px', overflow: 'auto', marginTop: '0.5rem' }" v-html="logModal.formattedLogs"></div> | ||||
|     </a-modal> | ||||
|     <a-modal id="backup-modal"  | ||||
|         v-model="backupModal.visible"  | ||||
|         title='{{ i18n "pages.index.backupTitle" }}' | ||||
|         :closable="true" | ||||
|         footer="" | ||||
|         :class="themeSwitcher.currentTheme"> | ||||
|       <a-list class="ant-backup-list" bordered :style="{ width: '100%' }"> | ||||
|         <a-list-item class="ant-backup-list-item"> | ||||
|           <a-list-item-meta> | ||||
|             <template #title>{{ i18n "pages.index.exportDatabase" }}</template> | ||||
|             <template #description>{{ i18n "pages.index.exportDatabaseDesc" }}</template> | ||||
|           </a-list-item-meta> | ||||
|           <a-button @click="exportDatabase()" type="primary" icon="download"/> | ||||
|         </a-list-item> | ||||
|         <a-list-item class="ant-backup-list-item"> | ||||
|           <a-list-item-meta> | ||||
|             <template #title>{{ i18n "pages.index.importDatabase" }}</template> | ||||
|             <template #description>{{ i18n "pages.index.importDatabaseDesc" }}</template> | ||||
|           </a-list-item-meta> | ||||
|           <a-button @click="importDatabase()" type="primary" icon="upload" /> | ||||
|         </a-list-item> | ||||
|       </a-list> | ||||
|     </a-modal> | ||||
|   </a-layout> | ||||
|   <a-modal id="version-modal" v-model="versionModal.visible" title='{{ i18n "pages.index.xraySwitch" }}' :closable="true" | ||||
|       @ok="() => versionModal.visible = false" :class="themeSwitcher.currentTheme" footer=""> | ||||
|     <a-collapse default-active-key="1"> | ||||
|       <a-collapse-panel key="1" header='Xray'> | ||||
|         <a-alert type="warning" :style="{ marginBottom: '12px', width: '100%' }" message='{{ i18n "pages.index.xraySwitchClickDesk" }}' show-icon></a-alert> | ||||
|         <a-list class="ant-version-list" bordered :style="{ width: '100%' }"> | ||||
|           <a-list-item class="ant-version-list-item" v-for="version, index in versionModal.versions"> | ||||
|             <a-tag :color="index % 2 == 0 ? 'purple' : 'green'">[[ version ]]</a-tag> | ||||
|             <a-radio :class="themeSwitcher.currentTheme" :checked="version === `v${status.xray.version}`" @click="switchV2rayVersion(version)"></a-radio> | ||||
|           </a-list-item> | ||||
|         </a-list> | ||||
|       </a-collapse-panel> | ||||
|       <a-collapse-panel key="2" header='Geofiles'> | ||||
|         <a-list class="ant-version-list" bordered :style="{ width: '100%' }"> | ||||
|           <a-list-item class="ant-version-list-item" v-for="file, index in ['geosite.dat', 'geoip.dat', 'geosite_IR.dat', 'geoip_IR.dat', 'geosite_RU.dat', 'geoip_RU.dat']"> | ||||
|             <a-tag :color="index % 2 == 0 ? 'purple' : 'green'">[[ file ]]</a-tag> | ||||
|             <a-icon type="reload" @click="updateGeofile(file)" :style="{ marginRight: '8px' }"/> | ||||
|           </a-list-item> | ||||
|         </a-list> | ||||
|       </a-collapse-panel> | ||||
|     </a-collapse> | ||||
|   </a-modal> | ||||
|   <a-modal id="log-modal" v-model="logModal.visible" | ||||
|       :closable="true" @cancel="() => logModal.visible = false" | ||||
|       :class="themeSwitcher.currentTheme" | ||||
|       width="800px" footer=""> | ||||
|     <template slot="title"> | ||||
|       {{ i18n "pages.index.logs" }} | ||||
|       <a-icon :spin="logModal.loading" | ||||
|         type="sync" | ||||
|         :style="{ verticalAlign: 'middle', marginLeft: '10px' }" | ||||
|         :disabled="logModal.loading" | ||||
|         @click="openLogs()"> | ||||
|       </a-icon> | ||||
|     </template> | ||||
|     <a-form layout="inline"> | ||||
|       <a-form-item :style="{ marginRight: '0.5rem' }"> | ||||
|         <a-input-group compact> | ||||
|           <a-select size="small" v-model="logModal.rows" :style="{ width: '70px' }" | ||||
|               @change="openLogs()" :dropdown-class-name="themeSwitcher.currentTheme"> | ||||
|             <a-select-option value="10">10</a-select-option> | ||||
|             <a-select-option value="20">20</a-select-option> | ||||
|             <a-select-option value="50">50</a-select-option> | ||||
|             <a-select-option value="100">100</a-select-option> | ||||
|             <a-select-option value="500">500</a-select-option> | ||||
|           </a-select> | ||||
|           <a-select size="small" v-model="logModal.level" :style="{ width: '95px' }" | ||||
|               @change="openLogs()" :dropdown-class-name="themeSwitcher.currentTheme"> | ||||
|             <a-select-option value="debug">Debug</a-select-option> | ||||
|             <a-select-option value="info">Info</a-select-option> | ||||
|             <a-select-option value="notice">Notice</a-select-option> | ||||
|             <a-select-option value="warning">Warning</a-select-option> | ||||
|             <a-select-option value="err">Error</a-select-option> | ||||
|           </a-select> | ||||
|         </a-input-group> | ||||
|       </a-form-item> | ||||
|       <a-form-item> | ||||
|         <a-checkbox v-model="logModal.syslog" @change="openLogs()">SysLog</a-checkbox> | ||||
|       </a-form-item> | ||||
|       <a-form-item :style="{ float: 'right' }"> | ||||
|         <a-button type="primary" icon="download" @click="FileManager.downloadTextFile(logModal.logs?.join('\n'), 'x-ui.log')"></a-button> | ||||
|       </a-form-item> | ||||
|     </a-form> | ||||
|     <div class="ant-input" :style="{ height: 'auto', maxHeight: '500px', overflow: 'auto', marginTop: '0.5rem' }" v-html="logModal.formattedLogs"></div> | ||||
|   </a-modal> | ||||
|   <a-modal id="backup-modal"  | ||||
|       v-model="backupModal.visible"  | ||||
|       title='{{ i18n "pages.index.backupTitle" }}' | ||||
|       :closable="true" | ||||
|       footer="" | ||||
|       :class="themeSwitcher.currentTheme"> | ||||
|     <a-list class="ant-backup-list" bordered :style="{ width: '100%' }"> | ||||
|       <a-list-item class="ant-backup-list-item"> | ||||
|         <a-list-item-meta> | ||||
|           <template #title>{{ i18n "pages.index.exportDatabase" }}</template> | ||||
|           <template #description>{{ i18n "pages.index.exportDatabaseDesc" }}</template> | ||||
|         </a-list-item-meta> | ||||
|         <a-button @click="exportDatabase()" type="primary" icon="download"/> | ||||
|       </a-list-item> | ||||
|       <a-list-item class="ant-backup-list-item"> | ||||
|         <a-list-item-meta> | ||||
|           <template #title>{{ i18n "pages.index.importDatabase" }}</template> | ||||
|           <template #description>{{ i18n "pages.index.importDatabaseDesc" }}</template> | ||||
|         </a-list-item-meta> | ||||
|         <a-button @click="importDatabase()" type="primary" icon="upload" /> | ||||
|       </a-list-item> | ||||
|     </a-list> | ||||
|   </a-modal> | ||||
| </a-layout> | ||||
| {{template "page/body_scripts" .}} | ||||
| {{template "js" .}} | ||||
| {{template "component/aSidebar" .}} | ||||
| {{template "component/aThemeSwitch" .}} | ||||
| {{template "component/aCustomStatistic" .}} | ||||
|  | @ -479,7 +489,7 @@ | |||
|     } | ||||
| 
 | ||||
|     class Status { | ||||
|         constructor(data) { | ||||
|         constructor(data, isLoaded = false) { | ||||
|             this.cpu = new CurTotal(0, 0); | ||||
|             this.cpuCores = 0; | ||||
|             this.logicalPro = 0; | ||||
|  | @ -503,6 +513,7 @@ | |||
|               return; | ||||
|             } | ||||
| 
 | ||||
|             this.isLoaded = isLoaded; | ||||
|             this.cpu = new CurTotal(data.cpu, 100); | ||||
|             this.cpuCores = data.cpuCores; | ||||
|             this.logicalPro = data.logicalPro; | ||||
|  | @ -622,39 +633,32 @@ | |||
|         mixins: [MediaQueryMixin], | ||||
|         data: { | ||||
|             themeSwitcher, | ||||
|             loadingStates: { | ||||
|               fetched: false, | ||||
|               spinning: false | ||||
|             }, | ||||
|             status: new Status(), | ||||
|             versionModal, | ||||
|             logModal, | ||||
|             backupModal, | ||||
|             spinning: false, | ||||
|             loadingTip: '{{ i18n "loading"}}', | ||||
|             showAlert: false, | ||||
|             showIp: false | ||||
|         }, | ||||
|         methods: { | ||||
|             loading(spinning, tip = '{{ i18n "loading"}}') { | ||||
|                 this.loadingStates.spinning = spinning; | ||||
|                 this.spinning = spinning; | ||||
|                 this.loadingTip = tip; | ||||
|             }, | ||||
|             async getStatus() { | ||||
|                 try { | ||||
|                     const msg = await HttpUtil.post('/server/status'); | ||||
|                     if (msg.success) { | ||||
|                         if (!this.loadingStates.fetched) { | ||||
|                             this.loadingStates.fetched = true; | ||||
|                         } | ||||
| 
 | ||||
|                         this.setStatus(msg.obj, true); | ||||
|                     } | ||||
|                 } catch (e) { | ||||
|                     console.error("Failed to get status:", e); | ||||
|                 } | ||||
|             }, | ||||
|             setStatus(data) { | ||||
|                 this.status = new Status(data); | ||||
|             setStatus(data, isLoaded = false) { | ||||
|               this.status = new Status(data, isLoaded); | ||||
|             }, | ||||
|             async openSelectV2rayVersion() { | ||||
|                 this.loading(true); | ||||
|  | @ -784,4 +788,5 @@ | |||
|         }, | ||||
|     }); | ||||
| </script> | ||||
| {{ template "page/body_end" .}} | ||||
| </body> | ||||
| </html> | ||||
|  |  | |||
|  | @ -1,4 +1,6 @@ | |||
| {{ template "page/head_start" .}} | ||||
| <!DOCTYPE html> | ||||
| <html lang="en"> | ||||
| {{template "head" .}} | ||||
| <style> | ||||
|   html * { | ||||
|     -webkit-font-smoothing: antialiased; | ||||
|  | @ -451,174 +453,174 @@ | |||
|     margin: 2px 0 4px; | ||||
|   } | ||||
| </style> | ||||
| {{ template "page/head_end" .}} | ||||
| 
 | ||||
| {{ template "page/body_start" .}} | ||||
| <a-layout id="app" v-cloak :class="themeSwitcher.currentTheme"> | ||||
|   <transition name="list" appear> | ||||
|     <a-layout-content class="under" :style="{ minHeight: '0' }"> | ||||
|       <div class="waves-header"> | ||||
|         <div class="waves-inner-header"></div> | ||||
|         <svg class="waves" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" | ||||
|           viewBox="0 24 150 28" preserveAspectRatio="none" shape-rendering="auto"> | ||||
|           <defs> | ||||
|             <path id="gentle-wave" d="M-160 44c30 0 58-18 88-18s 58 18 88 18 58-18 88-18 58 18 88 18 v44h-352z" /> | ||||
|           </defs> | ||||
|           <g class="parallax"> | ||||
|             <use xlink:href="#gentle-wave" x="48" y="0" fill="rgba(0, 135, 113, 0.08)" /> | ||||
|             <use xlink:href="#gentle-wave" x="48" y="3" fill="rgba(0, 135, 113, 0.08)" /> | ||||
|             <use xlink:href="#gentle-wave" x="48" y="5" fill="rgba(0, 135, 113, 0.08)" /> | ||||
|             <use xlink:href="#gentle-wave" x="48" y="7" fill="#c7ebe2" /> | ||||
|           </g> | ||||
|         </svg> | ||||
|       </div> | ||||
|       <a-row type="flex" justify="center" align="middle" :style="{ height: '100%', overflow: 'auto', overflowX: 'hidden' }"> | ||||
|         <a-col :xs="22" :sm="12" :md="10" :lg="8" :xl="6" :xxl="5" id="login" :style="{ margin: '3rem 0' }"> | ||||
|           <div class="setting-section"> | ||||
|             <a-popover :overlay-class-name="themeSwitcher.currentTheme" title='{{ i18n "menu.settings" }}' placement="bottomRight" trigger="click"> | ||||
|               <template slot="content"> | ||||
|                 <a-space direction="vertical" :size="10"> | ||||
|                   <a-theme-switch-login></a-theme-switch-login> | ||||
|                   <span>{{ i18n "pages.settings.language" }}</span> | ||||
|                   <a-select ref="selectLang" :style="{ width: '100%' }" v-model="lang" @change="LanguageManager.setLanguage(lang)" :dropdown-class-name="themeSwitcher.currentTheme"> | ||||
|                     <a-select-option :value="l.value" label="English" v-for="l in LanguageManager.supportedLanguages"> | ||||
|                       <span role="img" aria-label="l.name" v-text="l.icon"></span> | ||||
|                         <span v-text="l.name"></span> | ||||
|                     </a-select-option> | ||||
|                   </a-select> | ||||
|                 </a-space> | ||||
|               </template> | ||||
|               <a-button shape="circle" icon="setting"></a-button> | ||||
|             </a-popover> | ||||
|           </div> | ||||
|           <a-row type="flex" justify="center"> | ||||
|             <a-col :style="{ width: '100%' }"> | ||||
|               <h2 class="title headline zoom"> | ||||
|                 <span class="words-wrapper"> | ||||
|                   <b class="is-visible">{{ i18n "pages.login.hello" }}</b> | ||||
|                   <b>{{ i18n "pages.login.title" }}</b> | ||||
|                 </span> | ||||
|               </h2> | ||||
|             </a-col> | ||||
|           </a-row> | ||||
|           <a-row type="flex" justify="center"> | ||||
|             <a-col span="24"> | ||||
|               <a-form> | ||||
|                 <a-space direction="vertical" size="middle"> | ||||
|                   <a-form-item> | ||||
|                     <a-input autocomplete="username" name="username" v-model.trim="user.username" | ||||
|                       placeholder='{{ i18n "username" }}' @keydown.enter.native="login" autofocus> | ||||
|                       <a-icon slot="prefix" type="user" :style="{ fontSize: '1rem' }"></a-icon> | ||||
|                     </a-input> | ||||
|                   </a-form-item> | ||||
|                   <a-form-item> | ||||
|                     <a-input-password autocomplete="password" name="password" v-model.trim="user.password" | ||||
|                       placeholder='{{ i18n "password" }}' @keydown.enter.native="login"> | ||||
|                       <a-icon slot="prefix" type="lock" :style="{ fontSize: '1rem' }"></a-icon> | ||||
|                     </a-input-password> | ||||
|                   </a-form-item> | ||||
|                   <a-form-item v-if="twoFactorEnable"> | ||||
|                     <a-input autocomplete="totp" name="twoFactorCode" v-model.trim="user.twoFactorCode" | ||||
|                       placeholder='{{ i18n "twoFactorCode" }}' @keydown.enter.native="login"> | ||||
|                       <a-icon slot="prefix" type="key" :style="{ fontSize: '1rem' }"></a-icon> | ||||
|                     </a-input> | ||||
|                   </a-form-item> | ||||
|                   <a-form-item> | ||||
|                     <a-row justify="center" class="centered"> | ||||
|                       <div :style="{ height: '50px', marginTop: '1rem', ...loading ? { width: '52px' } : { display: 'inline-block' } }" class="wave-btn-bg wave-btn-bg-cl"> | ||||
|                         <a-button class="ant-btn-primary-login" type="primary" :loading="loading" @click="login" | ||||
|                           :icon="loading ? 'poweroff' : undefined"> | ||||
|                           [[ loading ? '' : '{{ i18n "login" }}' ]] | ||||
|                         </a-button> | ||||
|                       </div> | ||||
|                     </a-row> | ||||
|                   </a-form-item> | ||||
|                 </a-space> | ||||
|               </a-form> | ||||
|             </a-col> | ||||
|           </a-row> | ||||
|         </a-col> | ||||
|       </a-row> | ||||
|     </a-layout-content> | ||||
|   </transition> | ||||
| </a-layout> | ||||
| {{template "page/body_scripts" .}} | ||||
| {{template "component/aThemeSwitch" .}} | ||||
| <script> | ||||
|   const app = new Vue({ | ||||
|     delimiters: ['[[', ']]'], | ||||
|     el: '#app', | ||||
|     data: { | ||||
|       themeSwitcher, | ||||
|       loading: false, | ||||
|       user: { | ||||
|         username: "", | ||||
|         password: "", | ||||
|         twoFactorCode: "" | ||||
| <body> | ||||
|   <a-layout id="app" v-cloak :class="themeSwitcher.currentTheme"> | ||||
|     <transition name="list" appear> | ||||
|       <a-layout-content class="under" :style="{ minHeight: '0' }"> | ||||
|         <div class="waves-header"> | ||||
|           <div class="waves-inner-header"></div> | ||||
|           <svg class="waves" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" | ||||
|             viewBox="0 24 150 28" preserveAspectRatio="none" shape-rendering="auto"> | ||||
|             <defs> | ||||
|               <path id="gentle-wave" d="M-160 44c30 0 58-18 88-18s 58 18 88 18 58-18 88-18 58 18 88 18 v44h-352z" /> | ||||
|             </defs> | ||||
|             <g class="parallax"> | ||||
|               <use xlink:href="#gentle-wave" x="48" y="0" fill="rgba(0, 135, 113, 0.08)" /> | ||||
|               <use xlink:href="#gentle-wave" x="48" y="3" fill="rgba(0, 135, 113, 0.08)" /> | ||||
|               <use xlink:href="#gentle-wave" x="48" y="5" fill="rgba(0, 135, 113, 0.08)" /> | ||||
|               <use xlink:href="#gentle-wave" x="48" y="7" fill="#c7ebe2" /> | ||||
|             </g> | ||||
|           </svg> | ||||
|         </div> | ||||
|         <a-row type="flex" justify="center" align="middle" :style="{ height: '100%', overflow: 'auto', overflowX: 'hidden' }"> | ||||
|           <a-col :xs="22" :sm="12" :md="10" :lg="8" :xl="6" :xxl="5" id="login" :style="{ margin: '3rem 0' }"> | ||||
|             <div class="setting-section"> | ||||
|               <a-popover :overlay-class-name="themeSwitcher.currentTheme" title='{{ i18n "menu.settings" }}' placement="bottomRight" trigger="click"> | ||||
|                 <template slot="content"> | ||||
|                   <a-space direction="vertical" :size="10"> | ||||
|                     <a-theme-switch-login></a-theme-switch-login> | ||||
|                     <span>{{ i18n "pages.settings.language" }}</span> | ||||
|                     <a-select ref="selectLang" :style="{ width: '100%' }" v-model="lang" @change="LanguageManager.setLanguage(lang)" :dropdown-class-name="themeSwitcher.currentTheme"> | ||||
|                       <a-select-option :value="l.value" label="English" v-for="l in LanguageManager.supportedLanguages"> | ||||
|                         <span role="img" aria-label="l.name" v-text="l.icon"></span> | ||||
|                           <span v-text="l.name"></span> | ||||
|                       </a-select-option> | ||||
|                     </a-select> | ||||
|                   </a-space> | ||||
|                 </template> | ||||
|                 <a-button shape="circle" icon="setting"></a-button> | ||||
|               </a-popover> | ||||
|             </div> | ||||
|             <a-row type="flex" justify="center"> | ||||
|               <a-col :style="{ width: '100%' }"> | ||||
|                 <h2 class="title headline zoom"> | ||||
|                   <span class="words-wrapper"> | ||||
|                     <b class="is-visible">{{ i18n "pages.login.hello" }}</b> | ||||
|                     <b>{{ i18n "pages.login.title" }}</b> | ||||
|                   </span> | ||||
|                 </h2> | ||||
|               </a-col> | ||||
|             </a-row> | ||||
|             <a-row type="flex" justify="center"> | ||||
|               <a-col span="24"> | ||||
|                 <a-form> | ||||
|                   <a-space direction="vertical" size="middle"> | ||||
|                     <a-form-item> | ||||
|                       <a-input autocomplete="username" name="username" v-model.trim="user.username" | ||||
|                         placeholder='{{ i18n "username" }}' @keydown.enter.native="login" autofocus> | ||||
|                         <a-icon slot="prefix" type="user" :style="{ fontSize: '1rem' }"></a-icon> | ||||
|                       </a-input> | ||||
|                     </a-form-item> | ||||
|                     <a-form-item> | ||||
|                       <a-input-password autocomplete="password" name="password" v-model.trim="user.password" | ||||
|                         placeholder='{{ i18n "password" }}' @keydown.enter.native="login"> | ||||
|                         <a-icon slot="prefix" type="lock" :style="{ fontSize: '1rem' }"></a-icon> | ||||
|                       </a-input-password> | ||||
|                     </a-form-item> | ||||
|                     <a-form-item v-if="twoFactorEnable"> | ||||
|                       <a-input autocomplete="totp" name="twoFactorCode" v-model.trim="user.twoFactorCode" | ||||
|                         placeholder='{{ i18n "twoFactorCode" }}' @keydown.enter.native="login"> | ||||
|                         <a-icon slot="prefix" type="key" :style="{ fontSize: '1rem' }"></a-icon> | ||||
|                       </a-input> | ||||
|                     </a-form-item> | ||||
|                     <a-form-item> | ||||
|                       <a-row justify="center" class="centered"> | ||||
|                         <div :style="{ height: '50px', marginTop: '1rem', ...loading ? { width: '52px' } : { display: 'inline-block' } }" class="wave-btn-bg wave-btn-bg-cl"> | ||||
|                           <a-button class="ant-btn-primary-login" type="primary" :loading="loading" @click="login" | ||||
|                             :icon="loading ? 'poweroff' : undefined"> | ||||
|                             [[ loading ? '' : '{{ i18n "login" }}' ]] | ||||
|                           </a-button> | ||||
|                         </div> | ||||
|                       </a-row> | ||||
|                     </a-form-item> | ||||
|                   </a-space> | ||||
|                 </a-form> | ||||
|               </a-col> | ||||
|             </a-row> | ||||
|           </a-col> | ||||
|         </a-row> | ||||
|       </a-layout-content> | ||||
|     </transition> | ||||
|   </a-layout> | ||||
|   {{template "js" .}} | ||||
|   {{template "component/aThemeSwitch" .}} | ||||
|   <script> | ||||
|     const app = new Vue({ | ||||
|       delimiters: ['[[', ']]'], | ||||
|       el: '#app', | ||||
|       data: { | ||||
|         themeSwitcher, | ||||
|         loading: false, | ||||
|         user: { | ||||
|           username: "", | ||||
|           password: "", | ||||
|           twoFactorCode: "" | ||||
|         }, | ||||
|         twoFactorEnable: false, | ||||
|         lang: "" | ||||
|       }, | ||||
|       twoFactorEnable: false, | ||||
|       lang: "" | ||||
|     }, | ||||
|     async mounted() { | ||||
|       this.lang = LanguageManager.getLanguage(); | ||||
|       this.twoFactorEnable = await this.getTwoFactorEnable(); | ||||
|     }, | ||||
|     methods: { | ||||
|       async login() { | ||||
|         this.loading = true; | ||||
|         const msg = await HttpUtil.post('/login', this.user); | ||||
|         this.loading = false; | ||||
|         if (msg.success) { | ||||
|           location.href = basePath + 'panel/'; | ||||
|         } | ||||
|       async mounted() { | ||||
|         this.lang = LanguageManager.getLanguage(); | ||||
|         this.twoFactorEnable = await this.getTwoFactorEnable(); | ||||
|       }, | ||||
|       async getTwoFactorEnable() { | ||||
|         this.loading = true; | ||||
|         const msg = await HttpUtil.post('/getTwoFactorEnable'); | ||||
|         this.loading = false; | ||||
|         if (msg.success) { | ||||
|           this.twoFactorEnable = msg.obj; | ||||
|           return msg.obj; | ||||
|         } | ||||
|       methods: { | ||||
|         async login() { | ||||
|           this.loading = true; | ||||
|           const msg = await HttpUtil.post('/login', this.user); | ||||
|           this.loading = false; | ||||
|           if (msg.success) { | ||||
|             location.href = basePath + 'panel/'; | ||||
|           } | ||||
|         }, | ||||
|         async getTwoFactorEnable() { | ||||
|           this.loading = true; | ||||
|           const msg = await HttpUtil.post('/getTwoFactorEnable'); | ||||
|           this.loading = false; | ||||
|           if (msg.success) { | ||||
|             this.twoFactorEnable = msg.obj; | ||||
|             return msg.obj; | ||||
|           } | ||||
|         }, | ||||
|       }, | ||||
|     }, | ||||
|   }); | ||||
|     }); | ||||
| 
 | ||||
|   document.addEventListener("DOMContentLoaded", function () { | ||||
|     var animationDelay = 2000; | ||||
|     initHeadline(); | ||||
|     document.addEventListener("DOMContentLoaded", function () { | ||||
|       var animationDelay = 2000; | ||||
|       initHeadline(); | ||||
| 
 | ||||
|     function initHeadline() { | ||||
|       animateHeadline(document.querySelectorAll('.headline')); | ||||
|     } | ||||
|       function initHeadline() { | ||||
|         animateHeadline(document.querySelectorAll('.headline')); | ||||
|       } | ||||
| 
 | ||||
|     function animateHeadline(headlines) { | ||||
|       var duration = animationDelay; | ||||
|       headlines.forEach(function (headline) { | ||||
|       function animateHeadline(headlines) { | ||||
|         var duration = animationDelay; | ||||
|         headlines.forEach(function (headline) { | ||||
|           setTimeout(function () { | ||||
|             hideWord(headline.querySelector('.is-visible')); | ||||
|           }, duration); | ||||
|         }); | ||||
|       } | ||||
| 
 | ||||
|       function hideWord(word) { | ||||
|         var nextWord = takeNext(word); | ||||
|         switchWord(word, nextWord); | ||||
|         setTimeout(function () { | ||||
|           hideWord(headline.querySelector('.is-visible')); | ||||
|         }, duration); | ||||
|       }); | ||||
|     } | ||||
|           hideWord(nextWord); | ||||
|         }, animationDelay); | ||||
|       } | ||||
| 
 | ||||
|     function hideWord(word) { | ||||
|       var nextWord = takeNext(word); | ||||
|       switchWord(word, nextWord); | ||||
|       setTimeout(function () { | ||||
|         hideWord(nextWord); | ||||
|       }, animationDelay); | ||||
|     } | ||||
|       function takeNext(word) { | ||||
|         return word.nextElementSibling ? word.nextElementSibling : word.parentElement.firstElementChild; | ||||
|       } | ||||
| 
 | ||||
|     function takeNext(word) { | ||||
|       return word.nextElementSibling ? word.nextElementSibling : word.parentElement.firstElementChild; | ||||
|     } | ||||
| 
 | ||||
|     function switchWord(oldWord, newWord) { | ||||
|       oldWord.classList.remove('is-visible'); | ||||
|       oldWord.classList.add('is-hidden'); | ||||
|       newWord.classList.remove('is-hidden'); | ||||
|       newWord.classList.add('is-visible'); | ||||
|     } | ||||
|   }); | ||||
| </script> | ||||
| {{ template "page/body_end" .}} | ||||
|       function switchWord(oldWord, newWord) { | ||||
|         oldWord.classList.remove('is-visible'); | ||||
|         oldWord.classList.add('is-hidden'); | ||||
|         newWord.classList.remove('is-hidden'); | ||||
|         newWord.classList.add('is-visible'); | ||||
|       } | ||||
|     }); | ||||
|   </script> | ||||
| </body> | ||||
| </html> | ||||
|  |  | |||
|  | @ -1,4 +1,6 @@ | |||
| {{ template "page/head_start" .}} | ||||
| <!DOCTYPE html> | ||||
| <html lang="en"> | ||||
| {{template "head" .}} | ||||
| <style> | ||||
|   @media (min-width: 769px) { | ||||
|     .ant-layout-content { | ||||
|  | @ -58,103 +60,68 @@ | |||
|     margin-block-end: 12px; | ||||
|   } | ||||
| </style> | ||||
| {{ template "page/head_end" .}} | ||||
| 
 | ||||
| {{ template "page/body_start" .}} | ||||
| <a-layout id="app" v-cloak :class="themeSwitcher.currentTheme"> | ||||
|   <a-sidebar></a-sidebar> | ||||
|   <a-layout id="content-layout"> | ||||
|     <a-layout-content> | ||||
|       <a-spin :spinning="loadingStates.spinning" :delay="500" tip='{{ i18n "loading"}}'> | ||||
|         <transition name="list" appear> | ||||
|           <a-alert type="error" v-if="confAlerts.length>0 && loadingStates.fetched" :style="{ marginBottom: '10px' }" | ||||
|               message='{{ i18n "secAlertTitle" }}' | ||||
|               color="red" | ||||
|               show-icon closable> | ||||
|             <template slot="description"> | ||||
|               <b>{{ i18n "secAlertConf" }}</b> | ||||
|               <ul><li v-for="a in confAlerts">[[ a ]]</li></ul> | ||||
|             </template> | ||||
|           </a-alert> | ||||
|         </transition> | ||||
|         <transition name="list" appear> | ||||
|           <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-col :xs="24" :sm="10" :style="{ padding: '4px' }"> | ||||
|                       <a-space direction="horizontal"> | ||||
|                         <a-button type="primary" :disabled="saveBtnDisable" @click="updateAllSetting">{{ i18n "pages.settings.save" }}</a-button> | ||||
|                         <a-button type="danger" :disabled="!saveBtnDisable" @click="restartPanel">{{ i18n "pages.settings.restartPanel" }}</a-button> | ||||
|                       </a-space> | ||||
|                     </a-col> | ||||
|                     <a-col :xs="24" :sm="14"> | ||||
|                       <template> | ||||
|                         <div> | ||||
|                           <a-back-top :target="() => document.getElementById('content-layout')" 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> | ||||
|                         </div> | ||||
|                       </template> | ||||
|                     </a-col> | ||||
|                   </a-row> | ||||
|                 </a-card> | ||||
|               </a-col> | ||||
|               <a-col> | ||||
|                 <a-tabs default-active-key="1"> | ||||
|                   <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" . }} | ||||
|                   </a-tab-pane> | ||||
|                   <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" . }} | ||||
|                   </a-tab-pane> | ||||
|                   <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" . }} | ||||
|                   </a-tab-pane> | ||||
|                   <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" . }} | ||||
|                   </a-tab-pane> | ||||
|                   <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" . }} | ||||
|                   </a-tab-pane> | ||||
|                 </a-tabs> | ||||
|               </a-col> | ||||
|             </a-row> | ||||
|           </template> | ||||
|         </transition> | ||||
|       </a-spin> | ||||
|     </a-layout-content> | ||||
| <body> | ||||
|   <a-layout id="app" v-cloak :class="themeSwitcher.currentTheme"> | ||||
|     <a-sidebar></a-sidebar> | ||||
|     <a-layout id="content-layout"> | ||||
|       <a-layout-content> | ||||
|         <a-spin :spinning="spinning" :delay="500" tip='{{ i18n "loading"}}'> | ||||
|           <transition name="list" appear> | ||||
|             <a-alert type="error" v-if="confAlerts.length>0" :style="{ marginBottom: '10px' }" | ||||
|                 message='{{ i18n "secAlertTitle" }}' | ||||
|                 color="red" | ||||
|                 show-icon closable> | ||||
|               <template slot="description"> | ||||
|                 <b>{{ i18n "secAlertConf" }}</b> | ||||
|                 <ul><li v-for="a in confAlerts">[[ a ]]</li></ul> | ||||
|               </template> | ||||
|             </a-alert> | ||||
|           </transition> | ||||
|           <a-space direction="vertical"> | ||||
|             <a-card hoverable :style="{ marginBottom: '.5rem', overflowX: 'hidden' }"> | ||||
|               <a-row :style="{ display: 'flex', flexWrap: 'wrap', alignItems: 'center' }"> | ||||
|                 <a-col :xs="24" :sm="10" :style="{ padding: '4px' }"> | ||||
|                   <a-space direction="horizontal"> | ||||
|                     <a-button type="primary" :disabled="saveBtnDisable" @click="updateAllSetting">{{ i18n "pages.settings.save" }}</a-button> | ||||
|                     <a-button type="danger" :disabled="!saveBtnDisable" @click="restartPanel">{{ i18n "pages.settings.restartPanel" }}</a-button> | ||||
|                   </a-space> | ||||
|                 </a-col> | ||||
|                 <a-col :xs="24" :sm="14"> | ||||
|                   <template> | ||||
|                     <div> | ||||
|                       <a-back-top :target="() => document.getElementById('content-layout')" 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> | ||||
|                     </div> | ||||
|                   </template> | ||||
|                 </a-col> | ||||
|               </a-row> | ||||
|             </a-card> | ||||
|             <a-tabs default-active-key="1"> | ||||
|               <a-tab-pane key="1" tab='{{ i18n "pages.settings.panelSettings" }}' :style="{ paddingTop: '20px' }"> | ||||
|                 {{ template "settings/panel/general" . }} | ||||
|               </a-tab-pane> | ||||
|               <a-tab-pane key="2" tab='{{ i18n "pages.settings.securitySettings" }}' :style="{ paddingTop: '20px' }"> | ||||
|                 {{ template "settings/panel/security" . }} | ||||
|               </a-tab-pane> | ||||
|               <a-tab-pane key="3" tab='{{ i18n "pages.settings.TGBotSettings" }}' :style="{ paddingTop: '20px' }"> | ||||
|                 {{ template "settings/panel/telegram" . }} | ||||
|               </a-tab-pane> | ||||
|               <a-tab-pane key="4" tab='{{ i18n "pages.settings.subSettings" }}' :style="{ paddingTop: '20px' }"> | ||||
|                 {{ template "settings/panel/subscription/general" . }} | ||||
|               </a-tab-pane> | ||||
|               <a-tab-pane key="5" tab='{{ i18n "pages.settings.subSettings" }} Json' v-if="allSetting.subEnable" :style="{ paddingTop: '20px' }"> | ||||
|                 {{ template "settings/panel/subscription/json" . }} | ||||
|               </a-tab-pane> | ||||
|             </a-tabs> | ||||
|           </a-space> | ||||
|         </a-spin> | ||||
|       </a-layout-content> | ||||
|     </a-layout> | ||||
|   </a-layout> | ||||
| </a-layout> | ||||
| {{template "page/body_scripts" .}} | ||||
| {{template "js" .}} | ||||
| <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/js/model/setting.js?{{ .cur_ver }}"></script> | ||||
|  | @ -165,14 +132,10 @@ | |||
| <script> | ||||
|   const app = new Vue({ | ||||
|     delimiters: ['[[', ']]'], | ||||
|     mixins: [MediaQueryMixin], | ||||
|     el: '#app', | ||||
|     data: { | ||||
|       themeSwitcher, | ||||
|       loadingStates: { | ||||
|         fetched: false, | ||||
|         spinning: false | ||||
|       }, | ||||
|       spinning: false, | ||||
|       oldAllSetting: new AllSetting(), | ||||
|       allSetting: new AllSetting(), | ||||
|       saveBtnDisable: true, | ||||
|  | @ -285,16 +248,13 @@ | |||
|     }, | ||||
|     methods: { | ||||
|       loading(spinning = true) { | ||||
|         this.loadingStates.spinning = spinning; | ||||
|         this.spinning = spinning; | ||||
|       }, | ||||
|       async getAllSetting() { | ||||
|         this.loading(true); | ||||
|         const msg = await HttpUtil.post("/panel/setting/all"); | ||||
| 
 | ||||
|         this.loading(false); | ||||
|         if (msg.success) { | ||||
|           if (!this.loadingStates.fetched) { | ||||
|             this.loadingStates.fetched = true | ||||
|           } | ||||
| 
 | ||||
|           this.oldAllSetting = new AllSetting(msg.obj); | ||||
|           this.allSetting = new AllSetting(msg.obj); | ||||
|           app.changeRemarkSample(); | ||||
|  | @ -548,7 +508,7 @@ | |||
|           if (!this.allSetting) return []; | ||||
|           var alerts = [] | ||||
|           if (window.location.protocol !== "https:") alerts.push('{{ i18n "secAlertSSL" }}'); | ||||
|           if (this.allSetting.webPort === 2053) alerts.push('{{ i18n "secAlertPanelPort" }}'); | ||||
|           if (this.allSetting.webPort == 54321) alerts.push('{{ i18n "secAlertPanelPort" }}'); | ||||
|           panelPath = window.location.pathname.split('/').length < 4 | ||||
|           if (panelPath && this.allSetting.webBasePath == '/') alerts.push('{{ i18n "secAlertPanelURI" }}'); | ||||
|           if (this.allSetting.subEnable) { | ||||
|  | @ -571,4 +531,5 @@ | |||
|     } | ||||
|   }); | ||||
| </script> | ||||
| {{ template "page/body_end" .}} | ||||
| </body> | ||||
| </html> | ||||
|  | @ -1,5 +1,5 @@ | |||
| {{define "settings/xray/advanced"}} | ||||
| <a-space direction="vertical" size="small" :style="{ marginTop: '20px' }"> | ||||
| <a-space direction="vertical" size="small"> | ||||
|     <a-list-item-meta title='{{ i18n "pages.xray.Template"}}' | ||||
|         description='{{ i18n "pages.xray.TemplateDesc"}}'></a-list-item-meta> | ||||
|     <a-radio-group v-model="advSettings" @change="changeCode" button-style="solid" :style="{ margin: '10px 0' }" | ||||
|  |  | |||
							
								
								
									
										2788
									
								
								web/html/xray.html
									
									
									
									
									
								
							
							
						
						
									
										2788
									
								
								web/html/xray.html
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							|  | @ -33,7 +33,7 @@ | |||
| "sure" = "Да" | ||||
| "encryption" = "Шифрование" | ||||
| "useIPv4ForHost" = "Использовать IPv4 для хоста" | ||||
| "transmission" = "Транспорт" | ||||
| "transmission" = "Протокол" | ||||
| "host" = "Хост" | ||||
| "path" = "Путь" | ||||
| "camouflage" = "Маскировка" | ||||
|  | @ -59,10 +59,10 @@ | |||
| "security" = "Безопасность" | ||||
| "secAlertTitle" = "Предупреждение системы безопасности" | ||||
| "secAlertSsl" = "Это соединение не защищено. Пожалуйста, не вводите конфиденциальную информацию, пока не установите SSL сертификат для защиты соединения" | ||||
| "secAlertConf" = "Некоторые настройки уязвимы для атак. Чтобы в будущем не было проблем, нужно усилить защиту." | ||||
| "secAlertSSL" = "Ваше подключение к панели не защищено. Установите SSL сертификат для защиты данных." | ||||
| "secAlertPanelPort" = "Порт панели по умолчанию небезопасен. Установите случайный или просто другой порт." | ||||
| "secAlertPanelURI" = "Адрес панели по умолчанию небезопасен. Сделайте адрес сложным." | ||||
| "secAlertConf" = "Некоторые настройки уязвимы для атак. Рекомендуем усилить протоколы безопасности, чтобы предотвратить проблемы в будущем." | ||||
| "secAlertSSL" = "Ваше подключение к панели небезопасно. Пожалуйста, установите SSL сертификат для защиты данных." | ||||
| "secAlertPanelPort" = "Порт, на котором работает панель небезопасен. Пожалуйста, установите случайный или просто другой порт." | ||||
| "secAlertPanelURI" = "URI-адрес панели по умолчанию небезопасен. Пожалуйста, настройте сложный URI-адрес." | ||||
| "secAlertSubURI" = "URI-адрес подписки по умолчанию небезопасен. Пожалуйста, настройте сложный URI-адрес." | ||||
| "secAlertSubJsonURI" = "URI-адрес по умолчанию для JSON подписки небезопасен. Пожалуйста, настройте сложный URI-адрес." | ||||
| "emptyDnsDesc" = "Нет добавленных DNS-серверов." | ||||
|  | @ -75,8 +75,8 @@ | |||
| "theme" = "Тема" | ||||
| "dark" = "Темная" | ||||
| "ultraDark" = "Очень темная" | ||||
| "dashboard" = "Дашборд" | ||||
| "inbounds" = "Инбаунды" | ||||
| "dashboard" = "Статус системы" | ||||
| "inbounds" = "Входящие подключения" | ||||
| "settings" = "Настройки" | ||||
| "xray" = "Настройки Xray" | ||||
| "logout" = "Выход" | ||||
|  | @ -91,15 +91,15 @@ | |||
| "invalidFormData" = "Недопустимый формат данных" | ||||
| "emptyUsername" = "Введите имя пользователя" | ||||
| "emptyPassword" = "Введите пароль" | ||||
| "wrongUsernameOrPassword" = "Неверные данные учетной записи." | ||||
| "successLogin" = "Вы успешно вошли в аккаунт" | ||||
| "wrongUsernameOrPassword" = "Неверное имя пользователя, пароль или код двухфакторной аутентификации." | ||||
| "successLogin" = "Успешный вход" | ||||
| 
 | ||||
| [pages.index] | ||||
| "title" = "Дашборд" | ||||
| "title" = "Статус системы" | ||||
| "cpu" = "ЦП" | ||||
| "logicalProcessors" = "Логические процессоры" | ||||
| "frequency" = "Частота" | ||||
| "swap" = "Файл подкачки" | ||||
| "swap" = "Swap" | ||||
| "storage" = "Диск" | ||||
| "memory" = "ОЗУ" | ||||
| "threads" = "Потоки" | ||||
|  | @ -136,7 +136,7 @@ | |||
| "geofileUpdateDialogDesc" = "Это обновит файл #filename#." | ||||
| "geofileUpdatePopover" = "Геофайл успешно обновлён" | ||||
| "dontRefresh" = "Установка в процессе. Не обновляйте страницу" | ||||
| "logs" = "Журнал" | ||||
| "logs" = "Логи" | ||||
| "config" = "Конфигурация" | ||||
| "backup" = "Резервная копия" | ||||
| "backupTitle" = "Резервная копия базы данных" | ||||
|  | @ -151,10 +151,10 @@ | |||
| "getConfigError" = "Произошла ошибка при получении конфигурационного файла" | ||||
| 
 | ||||
| [pages.inbounds] | ||||
| "title" = "Инбаунды" | ||||
| "title" = "Входящие подключения" | ||||
| "totalDownUp" = "Объем отправленного/полученного трафика" | ||||
| "totalUsage" = "Всего трафика" | ||||
| "inboundCount" = "Всего инбаундов" | ||||
| "inboundCount" = "Всего подключений" | ||||
| "operate" = "Меню" | ||||
| "enable" = "Включить" | ||||
| "remark" = "Примечание" | ||||
|  | @ -165,18 +165,18 @@ | |||
| "transportConfig" = "Транспорт" | ||||
| "expireDate" = "Дата окончания" | ||||
| "resetTraffic" = "Сброс трафика" | ||||
| "addInbound" = "Создать инбаунд" | ||||
| "addInbound" = "Создать новое подключение" | ||||
| "generalActions" = "Общие действия" | ||||
| "autoRefresh" = "Автообновление" | ||||
| "autoRefreshInterval" = "Интервал" | ||||
| "modifyInbound" = "Изменить инбаунд" | ||||
| "deleteInbound" = "Удалить инбаунд" | ||||
| "deleteInboundContent" = "Вы уверены, что хотите удалить инбаунд?" | ||||
| "modifyInbound" = "Изменить входящее подключение" | ||||
| "deleteInbound" = "Удалить входящее подключение" | ||||
| "deleteInboundContent" = "Вы уверены, что хотите удалить входящее подключение?" | ||||
| "deleteClient" = "Удалить клиента" | ||||
| "deleteClientContent" = "Вы уверены, что хотите удалить клиента?" | ||||
| "resetTrafficContent" = "Вы уверены, что хотите сбросить трафик?" | ||||
| "inboundUpdateSuccess" = "Инбаунд успешно обновлен." | ||||
| "inboundCreateSuccess" = "Инбаунд успешно создан." | ||||
| "inboundUpdateSuccess" = "Входящее подключение успешно обновлено." | ||||
| "inboundCreateSuccess" = "Входящее подключение успешно создано." | ||||
| "copyLink" = "Копировать ссылку" | ||||
| "address" = "Адрес" | ||||
| "network" = "Сеть" | ||||
|  | @ -196,11 +196,11 @@ | |||
| "export" = "Экспорт ссылок" | ||||
| "clone" = "Клонировать" | ||||
| "cloneInbound" = "Клонировать" | ||||
| "cloneInboundContent" = "Будут клонированы все настройки инбаундов, кроме списка клиентов, порта и IP-адреса прослушивания" | ||||
| "cloneInboundContent" = "Будут клонированы все настройки входящих подключений, кроме списка клиентов, порта и IP-адреса прослушивания" | ||||
| "cloneInboundOk" = "Клонировано" | ||||
| "resetAllTraffic" = "Сброс трафика всех инбаундов" | ||||
| "resetAllTrafficTitle" = "Сброс трафика всех инбаундов" | ||||
| "resetAllTrafficContent" = "Вы уверены, что хотите сбросить трафик всех инбаундов?" | ||||
| "resetAllTraffic" = "Сброс трафика всех подключений" | ||||
| "resetAllTrafficTitle" = "Сброс трафика всех подключений" | ||||
| "resetAllTrafficContent" = "Вы уверены, что хотите сбросить трафик всех подключений?" | ||||
| "resetInboundClientTraffics" = "Сброс трафика клиента" | ||||
| "resetInboundClientTrafficTitle" = "Сброс трафика клиентов" | ||||
| "resetInboundClientTrafficContent" = "Вы уверены, что хотите сбросить трафик для этих клиентов?" | ||||
|  | @ -222,10 +222,10 @@ | |||
| "subscriptionDesc" = "Вы можете найти свою ссылку подписки в разделе 'Подробнее'" | ||||
| "info" = "Информация" | ||||
| "same" = "Тот же" | ||||
| "inboundData" = "Данные инбаундов" | ||||
| "exportInbound" = "Экспорт инбаундов" | ||||
| "inboundData" = "Данные подключений" | ||||
| "exportInbound" = "Экспорт входящих подключений" | ||||
| "import" = "Импортировать" | ||||
| "importInbound" = "Импорт инбаундов" | ||||
| "importInbound" = "Импорт входящих подключений" | ||||
| 
 | ||||
| [pages.client] | ||||
| "add" = "Создать клиента" | ||||
|  | @ -249,13 +249,13 @@ | |||
| "obtain" = "Получить" | ||||
| "updateSuccess" = "Обновление прошло успешно" | ||||
| "logCleanSuccess" = "Лог был очищен" | ||||
| "inboundsUpdateSuccess" = "Инбаунды успешно обновлены" | ||||
| "inboundUpdateSuccess" = "Инбаунд успешно обновлено" | ||||
| "inboundCreateSuccess" = "Инбаунд успешно создано" | ||||
| "inboundDeleteSuccess" = "Инбаунд успешно удалено" | ||||
| "inboundClientAddSuccess" = "Клиент(ы) инбаунда добавлен(ы)" | ||||
| "inboundClientDeleteSuccess" = "Клиент инбаунда удалён" | ||||
| "inboundClientUpdateSuccess" = "Клиент инбаунда обновлён" | ||||
| "inboundsUpdateSuccess" = "Входящие подключения успешно обновлены" | ||||
| "inboundUpdateSuccess" = "Входящее подключение успешно обновлено" | ||||
| "inboundCreateSuccess" = "Входящее подключение успешно создано" | ||||
| "inboundDeleteSuccess" = "Входящее подключение успешно удалено" | ||||
| "inboundClientAddSuccess" = "Клиент(ы) входящего подключения добавлен(ы)" | ||||
| "inboundClientDeleteSuccess" = "Клиент входящего подключения удалён" | ||||
| "inboundClientUpdateSuccess" = "Клиент входящего подключения обновлён" | ||||
| "delDepletedClientsSuccess" = "Все исчерпанные клиенты удалены" | ||||
| "resetAllClientTrafficSuccess" = "Весь трафик клиента сброшен" | ||||
| "resetAllTrafficSuccess" = "Весь трафик сброшен" | ||||
|  | @ -281,15 +281,15 @@ | |||
| [pages.settings] | ||||
| "title" = "Настройки" | ||||
| "save" = "Сохранить" | ||||
| "infoDesc" = "Каждое внесённое изменение должно быть сохранено. Пожалуйста, перезапустите панель, чтобы изменения вступили в силу." | ||||
| "infoDesc" = "Каждое выполненное изменение необходимо сохранить. Пожалуйста, перезапустите панель, чтобы изменения вступили в силу" | ||||
| "restartPanel" = "Перезапуск панели" | ||||
| "restartPanelDesc" = "Вы уверены, что хотите перезапустить панель? Подтвердите, и перезапуск произойдёт через 3 секунды. Если панель будет недоступна, проверьте лог сервера" | ||||
| "restartPanelSuccess" = "Панель успешно перезапущена" | ||||
| "actions" = "Действия" | ||||
| "resetDefaultConfig" = "Восстановить настройки по умолчанию" | ||||
| "panelSettings" = "Панель" | ||||
| "securitySettings" = "Учетная запись" | ||||
| "TGBotSettings" = "Telegram" | ||||
| "panelSettings" = "Настройки панели" | ||||
| "securitySettings" = "Настройки безопасности" | ||||
| "TGBotSettings" = "Настройки Telegram бота" | ||||
| "panelListeningIP" = "IP-адрес для управления панелью" | ||||
| "panelListeningIPDesc" = "Оставьте пустым для подключения с любого IP" | ||||
| "panelListeningDomain" = "Домен панели" | ||||
|  | @ -303,7 +303,7 @@ | |||
| "panelUrlPath" = "Корневой путь URL адреса панели" | ||||
| "panelUrlPathDesc" = "Должен начинаться с '/' и заканчиваться '/'" | ||||
| "pageSize" = "Размер нумерации страниц" | ||||
| "pageSizeDesc" = "Определить размер страницы для таблицы инбаундов. Установите 0, чтобы отключить" | ||||
| "pageSizeDesc" = "Определить размер страницы для таблицы входящих подключений. Установите 0, чтобы отключить" | ||||
| "remarkModel" = "Модель примечания и символ разделения" | ||||
| "datepicker" = "Выбор даты" | ||||
| "datepickerPlaceholder" = "Выберите дату" | ||||
|  | @ -391,7 +391,7 @@ | |||
| [pages.xray] | ||||
| "title" = "Настройки Xray" | ||||
| "save" = "Сохранить" | ||||
| "restart" = "Перезапуск Xray" | ||||
| "restart" = "Перезапустить Xray" | ||||
| "restartSuccess" = "Xray успешно перезапущен" | ||||
| "stopSuccess" = "Xray успешно остановлен" | ||||
| "restartError" = "Произошла ошибка при перезапуске Xray." | ||||
|  | @ -402,7 +402,7 @@ | |||
| "generalConfigsDesc" = "Эти параметры описывают общие настройки" | ||||
| "logConfigs" = "Логи" | ||||
| "logConfigsDesc" = "Логи могут замедлять работу сервера. Включайте только нужные вам виды логов при необходимости!" | ||||
| "blockConfigsDesc" = "Настройте, чтобы клиенты не имели доступа к определенным протоколам" | ||||
| "blockConfigsDesc" = "Настройте, чтобы клиенты не имели доступа к определенным протоколам и веб-сайтами" | ||||
| "basicRouting" = "Базовые соединения" | ||||
| "blockConnectionsConfigsDesc" = "Эти параметры будут блокировать трафик в зависимости от страны назначения." | ||||
| "directConnectionsConfigsDesc" = "Прямое соединение означает, что определенный трафик не будет перенаправлен через другой сервер." | ||||
|  | @ -413,19 +413,19 @@ | |||
| "ipv4Routing" = "Правила IPv4" | ||||
| "ipv4RoutingDesc" = "Эти параметры позволят клиентам маршрутизироваться к целевым доменам только через IPv4" | ||||
| "warpRouting" = "Правила WARP" | ||||
| "warpRoutingDesc" = " Эти опции будут направлять трафик в зависимости от конкретного пункта назначения через WARP." | ||||
| "warpRoutingDesc" = "Внимание: перед использованием этих параметров установите WARP в режиме прокси-сервера socks5 на свой сервер, следуя инструкциям на GitHub панели. WARP будет направлять трафик на веб-сайты через серверы Cloudflare" | ||||
| "Template" = "Шаблон конфигурации Xray" | ||||
| "TemplateDesc" = "На основе шаблона создаётся конфигурационный файл Xray." | ||||
| "TemplateDesc" = "Создание файла конфигурации Xray на основе этого шаблона" | ||||
| "FreedomStrategy" = "Настройка стратегии протокола Freedom" | ||||
| "FreedomStrategyDesc" = "Установка стратегии вывода сети в протоколе Freedom" | ||||
| "RoutingStrategy" = "Настройка маршрутизации доменов" | ||||
| "RoutingStrategyDesc" = "Установка общей стратегии маршрутизации разрешения DNS" | ||||
| "Torrent" = "Заблокировать BitTorrent" | ||||
| "Inbounds" = "Инбаунды" | ||||
| "Inbounds" = "Входящее соединение" | ||||
| "InboundsDesc" = "Изменение шаблона конфигурации для подключения определенных клиентов" | ||||
| "Outbounds" = "Аутбаунды" | ||||
| "Outbounds" = "Исходящее соединение" | ||||
| "Balancers" = "Балансировщик" | ||||
| "OutboundsDesc" = "Изменение шаблона конфигурации, чтобы определить аутбаунды для этого сервера" | ||||
| "OutboundsDesc" = "Изменение шаблона конфигурации, чтобы определить исходящие соединения для этого сервера" | ||||
| "Routings" = "Маршрутизация" | ||||
| "RoutingsDesc" = "Важен приоритет каждого правила!" | ||||
| "completeTemplate" = "Все" | ||||
|  | @ -456,8 +456,8 @@ | |||
| "down" = "Опустить вниз" | ||||
| "source" = "Источник" | ||||
| "dest" = "Пункт назначения" | ||||
| "inbound" = "Инбаунд" | ||||
| "outbound" = "Аутбаунд" | ||||
| "inbound" = "Входящее соединение" | ||||
| "outbound" = "Исходящее соединение" | ||||
| "balancer" = "Балансировщик" | ||||
| "info" = "Информация" | ||||
| "add" = "Создать правило" | ||||
|  | @ -465,9 +465,9 @@ | |||
| "useComma" = "Элементы, разделённые запятыми" | ||||
| 
 | ||||
| [pages.xray.outbound] | ||||
| "addOutbound" = "Создать аутбаунд" | ||||
| "addOutbound" = "Создать исходящее соединение" | ||||
| "addReverse" = "Создать реверс-прокси" | ||||
| "editOutbound" = "Изменить аутбаунд" | ||||
| "editOutbound" = "Изменить исходящее соединение" | ||||
| "editReverse" = "Редактировать реверс-прокси" | ||||
| "tag" = "Тег" | ||||
| "tagDesc" = "Уникальный тег" | ||||
|  | @ -481,7 +481,7 @@ | |||
| "intercon" = "Соединение" | ||||
| "settings" = "Настройки" | ||||
| "accountInfo" = "Информация об учетной записи" | ||||
| "outboundStatus" = "Статус аутбаунда" | ||||
| "outboundStatus" = "Исходящий статус" | ||||
| "sendThrough" = "Отправить через" | ||||
| 
 | ||||
| [pages.xray.balancer] | ||||
|  | @ -494,7 +494,7 @@ | |||
| "balancerDesc" = "Невозможно одновременно использовать balancerTag и outboundTag. При одновременном использовании будет работать только outboundTag." | ||||
| 
 | ||||
| [pages.xray.wireguard] | ||||
| "secretKey" = "Секретный ключ" | ||||
| "secretKey" = "Приватный ключ" | ||||
| "publicKey" = "Публичный ключ" | ||||
| "allowedIPs" = "Разрешенные IP-адреса" | ||||
| "endpoint" = "Конечная точка" | ||||
|  | @ -555,8 +555,8 @@ | |||
| "modifyUser" = "Вы успешно изменили учетные данные администратора." | ||||
| "originalUserPassIncorrect" = "Неверное имя пользователя или пароль" | ||||
| "userPassMustBeNotEmpty" = "Новое имя пользователя и новый пароль должны быть заполнены" | ||||
| "getOutboundTrafficError" = "Ошибка получения трафика аутбаунда" | ||||
| "resetOutboundTrafficError" = "Ошибка сброса трафика аутбаунда" | ||||
| "getOutboundTrafficError" = "Ошибка получения исходящего трафика" | ||||
| "resetOutboundTrafficError" = "Ошибка сброса исходящего трафика" | ||||
| 
 | ||||
| [tgbot] | ||||
| "keyboardClosed" = "❌ Клавиатура закрыта." | ||||
|  | @ -564,7 +564,7 @@ | |||
| "noQuery" = "❌ Запрос не найден. Пожалуйста, повторите команду." | ||||
| "wentWrong" = "❌ Что-то пошло не так..." | ||||
| "noIpRecord" = "❗ Нет записей об IP-адресе." | ||||
| "noInbounds" = "❗ У вас не настроено ни одного инбаунда." | ||||
| "noInbounds" = "❗ У вас не настроено ни одного подключения." | ||||
| "unlimited" = "♾ Безлимит" | ||||
| "add" = "Добавить" | ||||
| "month" = "Месяц" | ||||
|  | @ -573,7 +573,7 @@ | |||
| "days" = "Дней" | ||||
| "hours" = "Часов" | ||||
| "unknown" = "Неизвестно" | ||||
| "inbounds" = "Инбаунды" | ||||
| "inbounds" = "Подключения" | ||||
| "clients" = "Клиенты" | ||||
| "offline" = "🔴 Офлайн" | ||||
| "online" = "🟢 Онлайн" | ||||
|  | @ -587,7 +587,7 @@ | |||
| "status" = "✅ Бот функционирует нормально." | ||||
| "usage" = "❗ Пожалуйста, укажите email для поиска." | ||||
| "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>" | ||||
| "restartUsage" = "\r\n\r\n<code>/restart</code>" | ||||
| "restartSuccess" = "✅ Ядро Xray успешно перезапущено." | ||||
|  | @ -653,8 +653,8 @@ | |||
| "pass_prompt" = "🔑 Стандартный пароль: {{ .ClientPassword }}\n\nВведите ваш пароль." | ||||
| "email_prompt" = "📧 Стандартный email: {{ .ClientEmail }}\n\nВведите ваш email." | ||||
| "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_pass" = "🔄 Инбаунды: {{ .InboundRemark }}\n\n🔑 Пароль: {{ .ClientPass }}\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Теперь вы можете добавить клиента в подключение!" | ||||
| "cancel" = "❌ Процесс отменён! \n\nВы можете снова начать с /start в любое время. 🔄" | ||||
| "error_add_client"  = "⚠️ Ошибка:\n\n {{ .error }}" | ||||
| "using_default_value"  = "Используется значение по умолчанию👌" | ||||
|  | @ -676,7 +676,7 @@ | |||
| "confirmToggle" = "✅ Подтвердить вкл/выкл пользователя?" | ||||
| "dbBackup" = "📂 Бэкап БД" | ||||
| "serverUsage" = "💻 Состояние сервера" | ||||
| "getInbounds" = "🔌 Инбаунды" | ||||
| "getInbounds" = "🔌 Подключения" | ||||
| "depleteSoon" = "⚠️ Скоро конец" | ||||
| "clientUsage" = "Статистика клиента" | ||||
| "onlines" = "🟢 Онлайн" | ||||
|  | @ -714,7 +714,7 @@ | |||
| [tgbot.answers] | ||||
| "successfulOperation" = "✅ Успешно!" | ||||
| "errorOperation" = "❗ Ошибка в операции." | ||||
| "getInboundsFailed" = "❌ Не удалось получить инбаунды." | ||||
| "getInboundsFailed" = "❌ Не удалось получить подключения." | ||||
| "getClientsFailed" = "❌ Не удалось получить клиентов." | ||||
| "canceled" = "❌ {{ .Email }}: Операция отменена." | ||||
| "clientRefreshSuccess" = "✅ {{ .Email }}: Клиент успешно обновлен." | ||||
|  | @ -731,5 +731,5 @@ | |||
| "enableSuccess" = "✅ {{ .Email }}: Включено успешно." | ||||
| "disableSuccess" = "✅ {{ .Email }}: Отключено успешно." | ||||
| "askToAddUserId" = "❌ Ваша конфигурация не найдена!\r\n💭 Пожалуйста, попросите администратора использовать ваш Telegram User ID в конфигурации.\r\n\r\n🆔 Ваш User ID: <code>{{ .TgUserID }}</code>" | ||||
| "chooseClient" = "Выберите клиента для инбаунда {{ .Inbound }}" | ||||
| "chooseInbound" = "Выберите инбаунд" | ||||
| "chooseClient" = "Выберите клиента для подключения {{ .Inbound }}" | ||||
| "chooseInbound" = "Выберите подключение" | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue