mirror of
				https://github.com/MHSanaei/3x-ui.git
				synced 2025-10-31 04:12:51 +00:00 
			
		
		
		
	Merge branch 'main' into periodic-traffic-reset
This commit is contained in:
		
						commit
						a7f460213d
					
				
					 41 changed files with 434 additions and 174 deletions
				
			
		
							
								
								
									
										12
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										12
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							|  | @ -7,8 +7,9 @@ on: | ||||||
|   push: |   push: | ||||||
|     branches: |     branches: | ||||||
|       - main |       - main | ||||||
|  |     tags: | ||||||
|  |       - "v*.*.*" | ||||||
|     paths: |     paths: | ||||||
|       - '.github/workflows/release.yml' |  | ||||||
|       - '**.js' |       - '**.js' | ||||||
|       - '**.css' |       - '**.css' | ||||||
|       - '**.html' |       - '**.html' | ||||||
|  | @ -38,7 +39,7 @@ jobs: | ||||||
|         uses: actions/checkout@v5 |         uses: actions/checkout@v5 | ||||||
| 
 | 
 | ||||||
|       - name: Setup Go |       - name: Setup Go | ||||||
|         uses: actions/setup-go@v5 |         uses: actions/setup-go@v6 | ||||||
|         with: |         with: | ||||||
|           go-version-file: go.mod |           go-version-file: go.mod | ||||||
|           check-latest: true |           check-latest: true | ||||||
|  | @ -84,7 +85,7 @@ jobs: | ||||||
|           cd x-ui/bin |           cd x-ui/bin | ||||||
|            |            | ||||||
|           # Download dependencies |           # Download dependencies | ||||||
|           Xray_URL="https://github.com/XTLS/Xray-core/releases/download/v25.8.29/" |           Xray_URL="https://github.com/XTLS/Xray-core/releases/download/v25.9.5/" | ||||||
|           if [ "${{ matrix.platform }}" == "amd64" ]; then |           if [ "${{ matrix.platform }}" == "amd64" ]; then | ||||||
|             wget -q ${Xray_URL}Xray-linux-64.zip |             wget -q ${Xray_URL}Xray-linux-64.zip | ||||||
|             unzip Xray-linux-64.zip |             unzip Xray-linux-64.zip | ||||||
|  | @ -135,10 +136,13 @@ jobs: | ||||||
| 
 | 
 | ||||||
|       - name: Upload files to GH release |       - name: Upload files to GH release | ||||||
|         uses: svenstaro/upload-release-action@v2 |         uses: svenstaro/upload-release-action@v2 | ||||||
|         if: github.event_name == 'release' && github.event.action == 'published' |         if: | | ||||||
|  |           (github.event_name == 'release' && github.event.action == 'published') || | ||||||
|  |           (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')) | ||||||
|         with: |         with: | ||||||
|           repo_token: ${{ secrets.GITHUB_TOKEN }} |           repo_token: ${{ secrets.GITHUB_TOKEN }} | ||||||
|           tag: ${{ github.ref }} |           tag: ${{ github.ref }} | ||||||
|           file: x-ui-linux-${{ matrix.platform }}.tar.gz |           file: x-ui-linux-${{ matrix.platform }}.tar.gz | ||||||
|           asset_name: x-ui-linux-${{ matrix.platform }}.tar.gz |           asset_name: x-ui-linux-${{ matrix.platform }}.tar.gz | ||||||
|  |           overwrite: true | ||||||
|           prerelease: true |           prerelease: true | ||||||
|  |  | ||||||
|  | @ -27,7 +27,7 @@ case $1 in | ||||||
| esac | esac | ||||||
| mkdir -p build/bin | mkdir -p build/bin | ||||||
| cd build/bin | cd build/bin | ||||||
| wget -q "https://github.com/XTLS/Xray-core/releases/download/v25.8.29/Xray-linux-${ARCH}.zip" | wget -q "https://github.com/XTLS/Xray-core/releases/download/v25.9.5/Xray-linux-${ARCH}.zip" | ||||||
| unzip "Xray-linux-${ARCH}.zip" | unzip "Xray-linux-${ARCH}.zip" | ||||||
| rm -f "Xray-linux-${ARCH}.zip" geoip.dat geosite.dat | rm -f "Xray-linux-${ARCH}.zip" geoip.dat geosite.dat | ||||||
| mv xray "xray-linux-${FNAME}" | mv xray "xray-linux-${FNAME}" | ||||||
|  |  | ||||||
|  | @ -1 +1 @@ | ||||||
| 2.6.7 | 2.6.8 | ||||||
|  | @ -107,3 +107,10 @@ type Client struct { | ||||||
| 	CreatedAt  int64  `json:"created_at,omitempty"` | 	CreatedAt  int64  `json:"created_at,omitempty"` | ||||||
| 	UpdatedAt  int64  `json:"updated_at,omitempty"` | 	UpdatedAt  int64  `json:"updated_at,omitempty"` | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | type VLESSSettings struct { | ||||||
|  | 	Clients    []Client `json:"clients"` | ||||||
|  | 	Decryption string   `json:"decryption"` | ||||||
|  | 	Encryption string   `json:"encryption"` | ||||||
|  | 	Fallbacks  []any    `json:"fallbacks"` | ||||||
|  | } | ||||||
|  |  | ||||||
							
								
								
									
										21
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										21
									
								
								go.mod
									
									
									
									
									
								
							|  | @ -14,10 +14,10 @@ require ( | ||||||
| 	github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 | 	github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 | ||||||
| 	github.com/pelletier/go-toml/v2 v2.2.4 | 	github.com/pelletier/go-toml/v2 v2.2.4 | ||||||
| 	github.com/robfig/cron/v3 v3.0.1 | 	github.com/robfig/cron/v3 v3.0.1 | ||||||
| 	github.com/shirou/gopsutil/v4 v4.25.7 | 	github.com/shirou/gopsutil/v4 v4.25.8 | ||||||
| 	github.com/valyala/fasthttp v1.65.0 | 	github.com/valyala/fasthttp v1.65.0 | ||||||
| 	github.com/xlzd/gotp v0.1.0 | 	github.com/xlzd/gotp v0.1.0 | ||||||
| 	github.com/xtls/xray-core v1.250803.1-0.20250829143322-81b7cd718ad5 | 	github.com/xtls/xray-core v1.250905.0 | ||||||
| 	go.uber.org/atomic v1.11.0 | 	go.uber.org/atomic v1.11.0 | ||||||
| 	golang.org/x/crypto v0.41.0 | 	golang.org/x/crypto v0.41.0 | ||||||
| 	golang.org/x/text v0.28.0 | 	golang.org/x/text v0.28.0 | ||||||
|  | @ -28,7 +28,8 @@ require ( | ||||||
| 
 | 
 | ||||||
| require ( | require ( | ||||||
| 	github.com/andybalholm/brotli v1.2.0 // indirect | 	github.com/andybalholm/brotli v1.2.0 // indirect | ||||||
| 	github.com/bytedance/sonic v1.14.0 // indirect | 	github.com/bytedance/gopkg v0.1.3 // indirect | ||||||
|  | 	github.com/bytedance/sonic v1.14.1 // indirect | ||||||
| 	github.com/bytedance/sonic/loader v0.3.0 // indirect | 	github.com/bytedance/sonic/loader v0.3.0 // indirect | ||||||
| 	github.com/cloudflare/circl v1.6.1 // indirect | 	github.com/cloudflare/circl v1.6.1 // indirect | ||||||
| 	github.com/cloudwego/base64x v0.1.6 // indirect | 	github.com/cloudwego/base64x v0.1.6 // indirect | ||||||
|  | @ -67,8 +68,8 @@ require ( | ||||||
| 	github.com/refraction-networking/utls v1.8.0 // indirect | 	github.com/refraction-networking/utls v1.8.0 // indirect | ||||||
| 	github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect | 	github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect | ||||||
| 	github.com/rogpeppe/go-internal v1.14.1 // indirect | 	github.com/rogpeppe/go-internal v1.14.1 // indirect | ||||||
| 	github.com/sagernet/sing v0.7.5 // indirect | 	github.com/sagernet/sing v0.7.7 // indirect | ||||||
| 	github.com/sagernet/sing-shadowsocks v0.2.8 // indirect | 	github.com/sagernet/sing-shadowsocks v0.2.9 // indirect | ||||||
| 	github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771 // indirect | 	github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771 // indirect | ||||||
| 	github.com/tklauser/go-sysconf v0.3.15 // indirect | 	github.com/tklauser/go-sysconf v0.3.15 // indirect | ||||||
| 	github.com/tklauser/numcpus v0.10.0 // indirect | 	github.com/tklauser/numcpus v0.10.0 // indirect | ||||||
|  | @ -79,16 +80,16 @@ require ( | ||||||
| 	github.com/valyala/fastjson v1.6.4 // indirect | 	github.com/valyala/fastjson v1.6.4 // indirect | ||||||
| 	github.com/vishvananda/netlink v1.3.1 // indirect | 	github.com/vishvananda/netlink v1.3.1 // indirect | ||||||
| 	github.com/vishvananda/netns v0.0.5 // indirect | 	github.com/vishvananda/netns v0.0.5 // indirect | ||||||
| 	github.com/xtls/reality v0.0.0-20250828044527-046fad5ab64f // indirect | 	github.com/xtls/reality v0.0.0-20250904214705-431b6ff8c67c // indirect | ||||||
| 	github.com/yusufpapurcu/wmi v1.2.4 // indirect | 	github.com/yusufpapurcu/wmi v1.2.4 // indirect | ||||||
| 	go.uber.org/mock v0.6.0 // indirect | 	go.uber.org/mock v0.6.0 // indirect | ||||||
| 	go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect | 	go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect | ||||||
| 	golang.org/x/arch v0.20.0 // indirect | 	golang.org/x/arch v0.21.0 // indirect | ||||||
| 	golang.org/x/mod v0.27.0 // indirect | 	golang.org/x/mod v0.27.0 // indirect | ||||||
| 	golang.org/x/net v0.43.0 // indirect | 	golang.org/x/net v0.43.0 // indirect | ||||||
| 	golang.org/x/sync v0.16.0 // indirect | 	golang.org/x/sync v0.17.0 // indirect | ||||||
| 	golang.org/x/sys v0.35.0 // indirect | 	golang.org/x/sys v0.36.0 // indirect | ||||||
| 	golang.org/x/time v0.12.0 // indirect | 	golang.org/x/time v0.13.0 // indirect | ||||||
| 	golang.org/x/tools v0.36.0 // indirect | 	golang.org/x/tools v0.36.0 // indirect | ||||||
| 	golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect | 	golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect | ||||||
| 	golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb // indirect | 	golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb // indirect | ||||||
|  |  | ||||||
							
								
								
									
										42
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										42
									
								
								go.sum
									
									
									
									
									
								
							|  | @ -2,8 +2,10 @@ github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg | ||||||
| github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= | github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= | ||||||
| github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ= | github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ= | ||||||
| github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= | github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= | ||||||
| github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ= | github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M= | ||||||
| github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA= | github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM= | ||||||
|  | github.com/bytedance/sonic v1.14.1 h1:FBMC0zVz5XUmE4z9wF4Jey0An5FueFvOsTKKKtwIl7w= | ||||||
|  | github.com/bytedance/sonic v1.14.1/go.mod h1:gi6uhQLMbTdeP0muCnrjHLeCUPyb70ujhnNlhOylAFc= | ||||||
| github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA= | github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA= | ||||||
| github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= | github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= | ||||||
| github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0= | github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0= | ||||||
|  | @ -132,14 +134,14 @@ github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= | ||||||
| github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= | github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= | ||||||
| github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= | github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= | ||||||
| github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= | github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= | ||||||
| github.com/sagernet/sing v0.7.5 h1:gNMwZCLPqR+4e0g6dwi0sSsrvOmoMjpZgqxKsuJZatc= | github.com/sagernet/sing v0.7.7 h1:o46FzVZS+wKbBMEkMEdEHoVZxyM9jvfRpKXc7pEgS/c= | ||||||
| github.com/sagernet/sing v0.7.5/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= | github.com/sagernet/sing v0.7.7/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= | ||||||
| github.com/sagernet/sing-shadowsocks v0.2.8 h1:PURj5PRoAkqeHh2ZW205RWzN9E9RtKCVCzByXruQWfE= | github.com/sagernet/sing-shadowsocks v0.2.9 h1:Paep5zCszRKsEn8587O0MnhFWKJwDW1Y4zOYYlIxMkM= | ||||||
| github.com/sagernet/sing-shadowsocks v0.2.8/go.mod h1:lo7TWEMDcN5/h5B8S0ew+r78ZODn6SwVaFhvB6H+PTI= | github.com/sagernet/sing-shadowsocks v0.2.9/go.mod h1:TE/Z6401Pi8tgr0nBZcM/xawAI6u3F6TTbz4nH/qw+8= | ||||||
| github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771 h1:emzAzMZ1L9iaKCTxdy3Em8Wv4ChIAGnfiz18Cda70g4= | github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771 h1:emzAzMZ1L9iaKCTxdy3Em8Wv4ChIAGnfiz18Cda70g4= | ||||||
| github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg= | github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg= | ||||||
| github.com/shirou/gopsutil/v4 v4.25.7 h1:bNb2JuqKuAu3tRlPv5piSmBZyMfecwQ+t/ILq+1JqVM= | github.com/shirou/gopsutil/v4 v4.25.8 h1:NnAsw9lN7587WHxjJA9ryDnqhJpFH6A+wagYWTOH970= | ||||||
| github.com/shirou/gopsutil/v4 v4.25.7/go.mod h1:XV/egmwJtd3ZQjBpJVY5kndsiOO4IRqy9TQnmm6VP7U= | github.com/shirou/gopsutil/v4 v4.25.8/go.mod h1:q9QdMmfAOVIw7a+eF86P7ISEU6ka+NLgkUxlopV4RwI= | ||||||
| github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | ||||||
| github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= | ||||||
| github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= | ||||||
|  | @ -172,10 +174,10 @@ github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zd | ||||||
| github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= | github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= | ||||||
| github.com/xlzd/gotp v0.1.0 h1:37blvlKCh38s+fkem+fFh7sMnceltoIEBYTVXyoa5Po= | github.com/xlzd/gotp v0.1.0 h1:37blvlKCh38s+fkem+fFh7sMnceltoIEBYTVXyoa5Po= | ||||||
| github.com/xlzd/gotp v0.1.0/go.mod h1:ndLJ3JKzi3xLmUProq4LLxCuECL93dG9WASNLpHz8qg= | github.com/xlzd/gotp v0.1.0/go.mod h1:ndLJ3JKzi3xLmUProq4LLxCuECL93dG9WASNLpHz8qg= | ||||||
| github.com/xtls/reality v0.0.0-20250828044527-046fad5ab64f h1:o1Kryl9qEYYzNep9RId9DM1kBn8tBrcK5UJnti/l0NI= | github.com/xtls/reality v0.0.0-20250904214705-431b6ff8c67c h1:LHLhQY3mKXSpTcQAkjFR4/6ar3rXjQryNeM7khK3AHU= | ||||||
| github.com/xtls/reality v0.0.0-20250828044527-046fad5ab64f/go.mod h1:XxvnCCgBee4WWE0bc4E+a7wbk8gkJ/rS0vNVNtC5qp0= | github.com/xtls/reality v0.0.0-20250904214705-431b6ff8c67c/go.mod h1:XxvnCCgBee4WWE0bc4E+a7wbk8gkJ/rS0vNVNtC5qp0= | ||||||
| github.com/xtls/xray-core v1.250803.1-0.20250829143322-81b7cd718ad5 h1:rBqCVgic8yIUVHB4h26K8JNuwJuNj45egsdXxwEvA7E= | github.com/xtls/xray-core v1.250905.0 h1:VNL3l/6fcwyeYXJTRbf+TYqPfJYkk0Wmmz7qoQNkxY8= | ||||||
| github.com/xtls/xray-core v1.250803.1-0.20250829143322-81b7cd718ad5/go.mod h1:WB/73DmN9Vs7lxtx4Xc/D0Ub1VUu06hAh1mMh8JN2uM= | github.com/xtls/xray-core v1.250905.0/go.mod h1:WB/73DmN9Vs7lxtx4Xc/D0Ub1VUu06hAh1mMh8JN2uM= | ||||||
| github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= | github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= | ||||||
| github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= | github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= | ||||||
| github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= | github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= | ||||||
|  | @ -198,28 +200,28 @@ go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= | ||||||
| go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= | go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= | ||||||
| go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M= | go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M= | ||||||
| go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= | go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= | ||||||
| golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c= | golang.org/x/arch v0.21.0 h1:iTC9o7+wP6cPWpDWkivCvQFGAHDQ59SrSxsLPcnkArw= | ||||||
| golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk= | golang.org/x/arch v0.21.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A= | ||||||
| golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= | golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= | ||||||
| golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= | golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= | ||||||
| golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= | golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= | ||||||
| golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= | golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= | ||||||
| golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= | golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= | ||||||
| golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= | golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= | ||||||
| golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= | golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= | ||||||
| golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= | golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= | ||||||
| golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||||
| golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||||
| golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||||
| golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||||
| golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||||
| golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||||
| golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= | golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= | ||||||
| golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= | golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= | ||||||
| golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= | golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= | ||||||
| golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= | golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= | ||||||
| golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= | golang.org/x/time v0.13.0 h1:eUlYslOIt32DgYD6utsuUeHs4d7AsEYLuIAdg7FlYgI= | ||||||
| golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= | golang.org/x/time v0.13.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= | ||||||
| golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= | golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= | ||||||
| golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= | golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= | ||||||
| golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg= | golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg= | ||||||
|  |  | ||||||
|  | @ -53,7 +53,6 @@ func (a *SUBController) initRouter(g *gin.RouterGroup) { | ||||||
| 	gJson := g.Group(a.subJsonPath) | 	gJson := g.Group(a.subJsonPath) | ||||||
| 
 | 
 | ||||||
| 	gLink.GET(":subid", a.subs) | 	gLink.GET(":subid", a.subs) | ||||||
| 
 |  | ||||||
| 	gJson.GET(":subid", a.subJsons) | 	gJson.GET(":subid", a.subJsons) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -184,8 +184,14 @@ func (s *SubJsonService) getConfig(inbound *model.Inbound, client model.Client, | ||||||
| 		var newOutbounds []json_util.RawMessage | 		var newOutbounds []json_util.RawMessage | ||||||
| 
 | 
 | ||||||
| 		switch inbound.Protocol { | 		switch inbound.Protocol { | ||||||
| 		case "vmess", "vless": | 		case "vmess": | ||||||
| 			newOutbounds = append(newOutbounds, s.genVnext(inbound, streamSettings, client)) | 			newOutbounds = append(newOutbounds, s.genVnext(inbound, streamSettings, client, "")) | ||||||
|  | 		case "vless": | ||||||
|  | 			var vlessSettings model.VLESSSettings | ||||||
|  | 			_ = json.Unmarshal([]byte(inbound.Settings), &vlessSettings) | ||||||
|  | 
 | ||||||
|  | 			newOutbounds = append(newOutbounds, | ||||||
|  | 				s.genVnext(inbound, streamSettings, client, vlessSettings.Encryption)) | ||||||
| 		case "trojan", "shadowsocks": | 		case "trojan", "shadowsocks": | ||||||
| 			newOutbounds = append(newOutbounds, s.genServer(inbound, streamSettings, client)) | 			newOutbounds = append(newOutbounds, s.genServer(inbound, streamSettings, client)) | ||||||
| 		} | 		} | ||||||
|  | @ -284,7 +290,7 @@ func (s *SubJsonService) realityData(rData map[string]any) map[string]any { | ||||||
| 	return rltyData | 	return rltyData | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (s *SubJsonService) genVnext(inbound *model.Inbound, streamSettings json_util.RawMessage, client model.Client) json_util.RawMessage { | func (s *SubJsonService) genVnext(inbound *model.Inbound, streamSettings json_util.RawMessage, client model.Client, encryption string) json_util.RawMessage { | ||||||
| 	outbound := Outbound{} | 	outbound := Outbound{} | ||||||
| 	usersData := make([]UserVnext, 1) | 	usersData := make([]UserVnext, 1) | ||||||
| 
 | 
 | ||||||
|  | @ -295,7 +301,7 @@ func (s *SubJsonService) genVnext(inbound *model.Inbound, streamSettings json_ut | ||||||
| 	} | 	} | ||||||
| 	if inbound.Protocol == model.VLESS { | 	if inbound.Protocol == model.VLESS { | ||||||
| 		usersData[0].Flow = client.Flow | 		usersData[0].Flow = client.Flow | ||||||
| 		usersData[0].Encryption = "none" | 		usersData[0].Encryption = encryption | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	vnextData := make([]VnextSetting, 1) | 	vnextData := make([]VnextSetting, 1) | ||||||
|  |  | ||||||
|  | @ -313,6 +313,9 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string { | ||||||
| 	if inbound.Protocol != model.VLESS { | 	if inbound.Protocol != model.VLESS { | ||||||
| 		return "" | 		return "" | ||||||
| 	} | 	} | ||||||
|  | 	var vlessSettings model.VLESSSettings | ||||||
|  | 	_ = json.Unmarshal([]byte(inbound.Settings), &vlessSettings) | ||||||
|  | 
 | ||||||
| 	var stream map[string]any | 	var stream map[string]any | ||||||
| 	json.Unmarshal([]byte(inbound.StreamSettings), &stream) | 	json.Unmarshal([]byte(inbound.StreamSettings), &stream) | ||||||
| 	clients, _ := s.inboundService.GetClients(inbound) | 	clients, _ := s.inboundService.GetClients(inbound) | ||||||
|  | @ -327,6 +330,9 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string { | ||||||
| 	port := inbound.Port | 	port := inbound.Port | ||||||
| 	streamNetwork := stream["network"].(string) | 	streamNetwork := stream["network"].(string) | ||||||
| 	params := make(map[string]string) | 	params := make(map[string]string) | ||||||
|  | 	if vlessSettings.Encryption != "" { | ||||||
|  | 		params["encryption"] = vlessSettings.Encryption | ||||||
|  | 	} | ||||||
| 	params["type"] = streamNetwork | 	params["type"] = streamNetwork | ||||||
| 
 | 
 | ||||||
| 	switch streamNetwork { | 	switch streamNetwork { | ||||||
|  |  | ||||||
|  | @ -11,10 +11,8 @@ const Protocols = { | ||||||
| 
 | 
 | ||||||
| const SSMethods = { | const SSMethods = { | ||||||
|     AES_256_GCM: 'aes-256-gcm', |     AES_256_GCM: 'aes-256-gcm', | ||||||
|     AES_128_GCM: 'aes-128-gcm', |  | ||||||
|     CHACHA20_POLY1305: 'chacha20-poly1305', |     CHACHA20_POLY1305: 'chacha20-poly1305', | ||||||
|     CHACHA20_IETF_POLY1305: 'chacha20-ietf-poly1305', |     CHACHA20_IETF_POLY1305: 'chacha20-ietf-poly1305', | ||||||
|     XCHACHA20_POLY1305: 'xchacha20-poly1305', |  | ||||||
|     XCHACHA20_IETF_POLY1305: 'xchacha20-ietf-poly1305', |     XCHACHA20_IETF_POLY1305: 'xchacha20-ietf-poly1305', | ||||||
|     BLAKE3_AES_128_GCM: '2022-blake3-aes-128-gcm', |     BLAKE3_AES_128_GCM: '2022-blake3-aes-128-gcm', | ||||||
|     BLAKE3_AES_256_GCM: '2022-blake3-aes-256-gcm', |     BLAKE3_AES_256_GCM: '2022-blake3-aes-256-gcm', | ||||||
|  | @ -1301,6 +1299,7 @@ class Inbound extends XrayCommonClass { | ||||||
|         const security = forceTls == 'same' ? this.stream.security : forceTls; |         const security = forceTls == 'same' ? this.stream.security : forceTls; | ||||||
|         const params = new Map(); |         const params = new Map(); | ||||||
|         params.set("type", this.stream.network); |         params.set("type", this.stream.network); | ||||||
|  |         params.set("encryption", this.settings.encryption); | ||||||
|         switch (type) { |         switch (type) { | ||||||
|             case "tcp": |             case "tcp": | ||||||
|                 const tcp = this.stream.tcp; |                 const tcp = this.stream.tcp; | ||||||
|  | @ -1859,13 +1858,17 @@ Inbound.VLESSSettings = class extends Inbound.Settings { | ||||||
|     constructor( |     constructor( | ||||||
|         protocol, |         protocol, | ||||||
|         vlesses = [new Inbound.VLESSSettings.VLESS()], |         vlesses = [new Inbound.VLESSSettings.VLESS()], | ||||||
|         decryption = 'none', |         decryption = "none", | ||||||
|         fallbacks = [] |         encryption = "none", | ||||||
|  |         fallbacks = [], | ||||||
|  |         selectedAuth = undefined, | ||||||
|     ) { |     ) { | ||||||
|         super(protocol); |         super(protocol); | ||||||
|         this.vlesses = vlesses; |         this.vlesses = vlesses; | ||||||
|         this.decryption = decryption; |         this.decryption = decryption; | ||||||
|  |         this.encryption = encryption; | ||||||
|         this.fallbacks = fallbacks; |         this.fallbacks = fallbacks; | ||||||
|  |         this.selectedAuth = selectedAuth; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     addFallback() { |     addFallback() { | ||||||
|  | @ -1876,22 +1879,43 @@ Inbound.VLESSSettings = class extends Inbound.Settings { | ||||||
|         this.fallbacks.splice(index, 1); |         this.fallbacks.splice(index, 1); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // decryption should be set to static value
 |  | ||||||
|     static fromJson(json = {}) { |     static fromJson(json = {}) { | ||||||
|         return new Inbound.VLESSSettings( |         const obj = new Inbound.VLESSSettings( | ||||||
|             Protocols.VLESS, |             Protocols.VLESS, | ||||||
|             json.clients.map(client => Inbound.VLESSSettings.VLESS.fromJson(client)), |             (json.clients || []).map(client => Inbound.VLESSSettings.VLESS.fromJson(client)), | ||||||
|             json.decryption || 'none', |             json.decryption, | ||||||
|             Inbound.VLESSSettings.Fallback.fromJson(json.fallbacks),); |             json.encryption, | ||||||
|  |             Inbound.VLESSSettings.Fallback.fromJson(json.fallbacks || []), | ||||||
|  |             json.selectedAuth | ||||||
|  |         ); | ||||||
|  |         return obj; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|     toJson() { |     toJson() { | ||||||
|         return { |         const json = { | ||||||
|             clients: Inbound.VLESSSettings.toJsonArray(this.vlesses), |             clients: Inbound.VLESSSettings.toJsonArray(this.vlesses), | ||||||
|             decryption: this.decryption, |  | ||||||
|             fallbacks: Inbound.VLESSSettings.toJsonArray(this.fallbacks), |  | ||||||
|         }; |         }; | ||||||
|  | 
 | ||||||
|  |         if (this.decryption) { | ||||||
|  |             json.decryption = this.decryption; | ||||||
|         } |         } | ||||||
|  | 
 | ||||||
|  |         if (this.encryption) { | ||||||
|  |             json.encryption = this.encryption; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if (this.fallbacks && this.fallbacks.length > 0) { | ||||||
|  |             json.fallbacks = Inbound.VLESSSettings.toJsonArray(this.fallbacks); | ||||||
|  |         } | ||||||
|  |         if (this.selectedAuth) { | ||||||
|  |             json.selectedAuth = this.selectedAuth; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return json; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| Inbound.VLESSSettings.VLESS = class extends XrayCommonClass { | Inbound.VLESSSettings.VLESS = class extends XrayCommonClass { | ||||||
|  |  | ||||||
|  | @ -813,7 +813,7 @@ class Outbound extends CommonClass { | ||||||
|         var settings; |         var settings; | ||||||
|         switch (protocol) { |         switch (protocol) { | ||||||
|             case Protocols.VLESS: |             case Protocols.VLESS: | ||||||
|                 settings = new Outbound.VLESSSettings(address, port, userData, url.searchParams.get('flow') ?? ''); |                 settings = new Outbound.VLESSSettings(address, port, userData, url.searchParams.get('flow') ?? '', url.searchParams.get('encryption') ?? 'none'); | ||||||
|                 break; |                 break; | ||||||
|             case Protocols.Trojan: |             case Protocols.Trojan: | ||||||
|                 settings = new Outbound.TrojanSettings(address, port, userData); |                 settings = new Outbound.TrojanSettings(address, port, userData); | ||||||
|  | @ -1046,13 +1046,13 @@ Outbound.VmessSettings = class extends CommonClass { | ||||||
|     } |     } | ||||||
| }; | }; | ||||||
| Outbound.VLESSSettings = class extends CommonClass { | Outbound.VLESSSettings = class extends CommonClass { | ||||||
|     constructor(address, port, id, flow, encryption = 'none') { |     constructor(address, port, id, flow, encryption) { | ||||||
|         super(); |         super(); | ||||||
|         this.address = address; |         this.address = address; | ||||||
|         this.port = port; |         this.port = port; | ||||||
|         this.id = id; |         this.id = id; | ||||||
|         this.flow = flow; |         this.flow = flow; | ||||||
|         this.encryption = encryption |         this.encryption = encryption; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     static fromJson(json = {}) { |     static fromJson(json = {}) { | ||||||
|  | @ -1071,7 +1071,7 @@ Outbound.VLESSSettings = class extends CommonClass { | ||||||
|             vnext: [{ |             vnext: [{ | ||||||
|                 address: this.address, |                 address: this.address, | ||||||
|                 port: this.port, |                 port: this.port, | ||||||
|                 users: [{ id: this.id, flow: this.flow, encryption: 'none', }], |                 users: [{ id: this.id, flow: this.flow, encryption: this.encryption }], | ||||||
|             }], |             }], | ||||||
|         }; |         }; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -134,7 +134,7 @@ class DateUtil { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     static formatMillis(millis) { |     static formatMillis(millis) { | ||||||
|         return moment(millis).format('YYYY-M-D H:m:s'); |         return moment(millis).format('YYYY-M-D HH:mm:ss'); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     static firstDayOfMonth() { |     static firstDayOfMonth() { | ||||||
|  |  | ||||||
|  | @ -47,6 +47,7 @@ func (a *APIController) initRouter(g *gin.RouterGroup) { | ||||||
| 		{"POST", "/resetAllClientTraffics/:id", a.inboundController.resetAllClientTraffics}, | 		{"POST", "/resetAllClientTraffics/:id", a.inboundController.resetAllClientTraffics}, | ||||||
| 		{"POST", "/delDepletedClients/:id", a.inboundController.delDepletedClients}, | 		{"POST", "/delDepletedClients/:id", a.inboundController.delDepletedClients}, | ||||||
| 		{"POST", "/onlines", a.inboundController.onlines}, | 		{"POST", "/onlines", a.inboundController.onlines}, | ||||||
|  | 		{"POST", "/lastOnline", a.inboundController.lastOnline}, | ||||||
| 		{"POST", "/updateClientTraffic/:email", a.inboundController.updateClientTraffic}, | 		{"POST", "/updateClientTraffic/:email", a.inboundController.updateClientTraffic}, | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -340,6 +340,11 @@ func (a *InboundController) onlines(c *gin.Context) { | ||||||
| 	jsonObj(c, a.inboundService.GetOnlineClients(), nil) | 	jsonObj(c, a.inboundService.GetOnlineClients(), nil) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func (a *InboundController) lastOnline(c *gin.Context) { | ||||||
|  | 	data, err := a.inboundService.GetClientsLastOnline() | ||||||
|  | 	jsonObj(c, data, err) | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func (a *InboundController) updateClientTraffic(c *gin.Context) { | func (a *InboundController) updateClientTraffic(c *gin.Context) { | ||||||
| 	email := c.Param("email") | 	email := c.Param("email") | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -55,6 +55,7 @@ func (a *ServerController) initRouter(g *gin.RouterGroup) { | ||||||
| 	g.POST("/getNewX25519Cert", a.getNewX25519Cert) | 	g.POST("/getNewX25519Cert", a.getNewX25519Cert) | ||||||
| 	g.POST("/getNewmldsa65", a.getNewmldsa65) | 	g.POST("/getNewmldsa65", a.getNewmldsa65) | ||||||
| 	g.POST("/getNewEchCert", a.getNewEchCert) | 	g.POST("/getNewEchCert", a.getNewEchCert) | ||||||
|  | 	g.POST("/getNewVlessEnc", a.getNewVlessEnc) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (a *ServerController) refreshStatus() { | func (a *ServerController) refreshStatus() { | ||||||
|  | @ -266,3 +267,12 @@ func (a *ServerController) getNewEchCert(c *gin.Context) { | ||||||
| 	} | 	} | ||||||
| 	jsonObj(c, cert, nil) | 	jsonObj(c, cert, nil) | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | func (a *ServerController) getNewVlessEnc(c *gin.Context) { | ||||||
|  | 	out, err := a.serverService.GetNewVlessEnc() | ||||||
|  | 	if err != nil { | ||||||
|  | 		jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.getNewVlessEncError"), err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	jsonObj(c, out, nil) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -33,12 +33,17 @@ | ||||||
|   <a-switch v-model="client.enable" @change="switchEnableClient(record.id,client)"></a-switch> |   <a-switch v-model="client.enable" @change="switchEnableClient(record.id,client)"></a-switch> | ||||||
| </template> | </template> | ||||||
| <template slot="online" slot-scope="text, client, index"> | <template slot="online" slot-scope="text, client, index"> | ||||||
|  |   <a-popover :overlay-class-name="themeSwitcher.currentTheme"> | ||||||
|  |     <template slot="content" > | ||||||
|  |       {{ i18n "lastOnline" }}: [[ formatLastOnline(client.email) ]] | ||||||
|  |     </template> | ||||||
|     <template v-if="client.enable && isClientOnline(client.email)"> |     <template v-if="client.enable && isClientOnline(client.email)"> | ||||||
|       <a-tag color="green">{{ i18n "online" }}</a-tag> |       <a-tag color="green">{{ i18n "online" }}</a-tag> | ||||||
|     </template> |     </template> | ||||||
|     <template v-else> |     <template v-else> | ||||||
|       <a-tag>{{ i18n "offline" }}</a-tag> |       <a-tag>{{ i18n "offline" }}</a-tag> | ||||||
|     </template> |     </template> | ||||||
|  |   </a-popover> | ||||||
| </template> | </template> | ||||||
| <template slot="client" slot-scope="text, client"> | <template slot="client" slot-scope="text, client"> | ||||||
|   <a-space direction="horizontal" :size="2"> |   <a-space direction="horizontal" :size="2"> | ||||||
|  |  | ||||||
|  | @ -226,6 +226,11 @@ | ||||||
|         </template> |         </template> | ||||||
| 
 | 
 | ||||||
|         <!-- vless settings --> |         <!-- vless settings --> | ||||||
|  |         <template v-if="outbound.protocol === Protocols.VLESS"> | ||||||
|  |           <a-form-item label='encryption'> | ||||||
|  |             <a-input v-model.trim="outbound.settings.encryption"></a-input> | ||||||
|  |           </a-form-item> | ||||||
|  |         </template> | ||||||
|         <template v-if="outbound.canEnableTlsFlow()"> |         <template v-if="outbound.canEnableTlsFlow()"> | ||||||
|           <a-form-item label='Flow'> |           <a-form-item label='Flow'> | ||||||
|             <a-select v-model="outbound.settings.flow" :dropdown-class-name="themeSwitcher.currentTheme"> |             <a-select v-model="outbound.settings.flow" :dropdown-class-name="themeSwitcher.currentTheme"> | ||||||
|  |  | ||||||
|  | @ -18,7 +18,30 @@ | ||||||
|     </table> |     </table> | ||||||
|   </a-collapse-panel> |   </a-collapse-panel> | ||||||
| </a-collapse> | </a-collapse> | ||||||
| <template v-if="inbound.isTcp"> | <template v-if="!inbound.stream.isTLS || !inbound.stream.isReality"> | ||||||
|  |   <a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }"> | ||||||
|  |     <a-form-item label="Authentication"> | ||||||
|  |       <a-select v-model="inbound.settings.selectedAuth" @change="getNewVlessEnc" :dropdown-class-name="themeSwitcher.currentTheme"> | ||||||
|  |         <a-select-option :value="undefined">None</a-select-option> | ||||||
|  |         <a-select-option value="X25519, not Post-Quantum">X25519 (not Post-Quantum)</a-select-option> | ||||||
|  |         <a-select-option value="ML-KEM-768, Post-Quantum">ML-KEM-768 (Post-Quantum)</a-select-option> | ||||||
|  |       </a-select> | ||||||
|  |     </a-form-item> | ||||||
|  |     <a-form-item label="decryption"> | ||||||
|  |       <a-input v-model.trim="inbound.settings.decryption"></a-input> | ||||||
|  |     </a-form-item> | ||||||
|  |     <a-form-item label="encryption"> | ||||||
|  |       <a-input v-model="inbound.settings.encryption" disabled></a-input> | ||||||
|  |     </a-form-item> | ||||||
|  |     <a-form-item label=" "> | ||||||
|  |       <a-space> | ||||||
|  |         <a-button type="primary" icon="import" @click="getNewVlessEnc">Get New keys</a-button> | ||||||
|  |         <a-button danger @click="clearKeys">Clear</a-button> | ||||||
|  |       </a-space> | ||||||
|  |     </a-form-item> | ||||||
|  |   </a-form> | ||||||
|  | </template> | ||||||
|  | <template v-if="inbound.isTcp && !inbound.settings.encryption"> | ||||||
|   <a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }"> |   <a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }"> | ||||||
|     <a-form-item label="Fallbacks"> |     <a-form-item label="Fallbacks"> | ||||||
|       <a-button icon="plus" type="primary" size="small" @click="inbound.settings.addFallback()"></a-button> |       <a-button icon="plus" type="primary" size="small" @click="inbound.settings.addFallback()"></a-button> | ||||||
|  |  | ||||||
|  | @ -5,13 +5,13 @@ | ||||||
|   <a-form-item label='{{ i18n "security" }}'> |   <a-form-item label='{{ i18n "security" }}'> | ||||||
|     <a-radio-group v-model="inbound.stream.security" button-style="solid"> |     <a-radio-group v-model="inbound.stream.security" button-style="solid"> | ||||||
|       <a-radio-button value="none">{{ i18n "none" }}</a-radio-button> |       <a-radio-button value="none">{{ i18n "none" }}</a-radio-button> | ||||||
|       <a-radio-button v-if="inbound.canEnableReality()" value="reality">Reality</a-radio-button> |       <a-radio-button v-if="inbound.canEnableReality() && !inbound.settings.encryption" value="reality">Reality</a-radio-button> | ||||||
|       <a-radio-button value="tls">TLS</a-radio-button> |       <a-radio-button v-if="!inbound.settings.encryption" value="tls">TLS</a-radio-button> | ||||||
|     </a-radio-group> |     </a-radio-group> | ||||||
|   </a-form-item> |   </a-form-item> | ||||||
| 
 | 
 | ||||||
|   <!-- tls settings --> |   <!-- tls settings --> | ||||||
|   <template v-if="inbound.stream.isTls"> |   <template v-if="inbound.stream.isTls && !inbound.settings.encryption"> | ||||||
|     <a-form-item label="SNI" placeholder="Server Name Indication"> |     <a-form-item label="SNI" placeholder="Server Name Indication"> | ||||||
|       <a-input v-model.trim="inbound.stream.tls.sni"></a-input> |       <a-input v-model.trim="inbound.stream.tls.sni"></a-input> | ||||||
|     </a-form-item> |     </a-form-item> | ||||||
|  | @ -121,7 +121,7 @@ | ||||||
|   </template> |   </template> | ||||||
| 
 | 
 | ||||||
|   <!-- reality settings --> |   <!-- reality settings --> | ||||||
|   <template v-if="inbound.stream.isReality"> |   <template v-if="inbound.stream.isReality && !inbound.settings.encryption"> | ||||||
|     {{template "form/realitySettings"}} |     {{template "form/realitySettings"}} | ||||||
|   </template> |   </template> | ||||||
| </a-form> | </a-form> | ||||||
|  |  | ||||||
|  | @ -712,7 +712,7 @@ | ||||||
|     }, { |     }, { | ||||||
|         title: '{{ i18n "pages.inbounds.enable" }}', |         title: '{{ i18n "pages.inbounds.enable" }}', | ||||||
|         align: 'center', |         align: 'center', | ||||||
|         width: 30, |         width: 35, | ||||||
|         scopedSlots: { customRender: 'enable' }, |         scopedSlots: { customRender: 'enable' }, | ||||||
|     }, { |     }, { | ||||||
|         title: '{{ i18n "pages.inbounds.remark" }}', |         title: '{{ i18n "pages.inbounds.remark" }}', | ||||||
|  | @ -776,8 +776,8 @@ | ||||||
| 
 | 
 | ||||||
|     const innerColumns = [ |     const innerColumns = [ | ||||||
|         { title: '{{ i18n "pages.inbounds.operate" }}', width: 65, scopedSlots: { customRender: 'actions' } }, |         { title: '{{ i18n "pages.inbounds.operate" }}', width: 65, scopedSlots: { customRender: 'actions' } }, | ||||||
|         { title: '{{ i18n "pages.inbounds.enable" }}', width: 30, scopedSlots: { customRender: 'enable' } }, |         { title: '{{ i18n "pages.inbounds.enable" }}', width: 35, scopedSlots: { customRender: 'enable' } }, | ||||||
|         { title: '{{ i18n "online" }}', width: 30, scopedSlots: { customRender: 'online' } }, |         { title: '{{ i18n "online" }}', width: 32, scopedSlots: { customRender: 'online' } }, | ||||||
|         { title: '{{ i18n "pages.inbounds.client" }}', width: 80, scopedSlots: { customRender: 'client' } }, |         { title: '{{ i18n "pages.inbounds.client" }}', width: 80, scopedSlots: { customRender: 'client' } }, | ||||||
|         { title: '{{ i18n "pages.inbounds.traffic" }}', width: 80, align: 'center', scopedSlots: { customRender: 'traffic' } }, |         { title: '{{ i18n "pages.inbounds.traffic" }}', width: 80, align: 'center', scopedSlots: { customRender: 'traffic' } }, | ||||||
|         { title: '{{ i18n "pages.inbounds.allTimeTraffic" }}', width: 80, align: 'center', scopedSlots: { customRender: 'allTime' } }, |         { title: '{{ i18n "pages.inbounds.allTimeTraffic" }}', width: 80, align: 'center', scopedSlots: { customRender: 'allTime' } }, | ||||||
|  | @ -813,6 +813,7 @@ | ||||||
|             defaultKey: '', |             defaultKey: '', | ||||||
|             clientCount: [], |             clientCount: [], | ||||||
|             onlineClients: [], |             onlineClients: [], | ||||||
|  |             lastOnlineMap: {}, | ||||||
|             isRefreshEnabled: localStorage.getItem("isRefreshEnabled") === "true" ? true : false, |             isRefreshEnabled: localStorage.getItem("isRefreshEnabled") === "true" ? true : false, | ||||||
|             refreshing: false, |             refreshing: false, | ||||||
|             refreshInterval: Number(localStorage.getItem("refreshInterval")) || 5000, |             refreshInterval: Number(localStorage.getItem("refreshInterval")) || 5000, | ||||||
|  | @ -841,6 +842,7 @@ | ||||||
|                     return; |                     return; | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|  |                 await this.getLastOnlineMap(); | ||||||
|                 await this.getOnlineUsers(); |                 await this.getOnlineUsers(); | ||||||
|                  |                  | ||||||
|                 this.setInbounds(msg.obj); |                 this.setInbounds(msg.obj); | ||||||
|  | @ -855,6 +857,11 @@ | ||||||
|                 } |                 } | ||||||
|                 this.onlineClients = msg.obj != null ? msg.obj : []; |                 this.onlineClients = msg.obj != null ? msg.obj : []; | ||||||
|             }, |             }, | ||||||
|  |             async getLastOnlineMap() { | ||||||
|  |                 const msg = await HttpUtil.post('/panel/api/inbounds/lastOnline'); | ||||||
|  |                 if (!msg.success || !msg.obj) return; | ||||||
|  |                 this.lastOnlineMap = msg.obj || {} | ||||||
|  |             }, | ||||||
|             async getDefaultSettings() { |             async getDefaultSettings() { | ||||||
|                 const msg = await HttpUtil.post('/panel/setting/defaultSettings'); |                 const msg = await HttpUtil.post('/panel/setting/defaultSettings'); | ||||||
|                 if (!msg.success) { |                 if (!msg.success) { | ||||||
|  | @ -1502,6 +1509,17 @@ | ||||||
|             isClientOnline(email) { |             isClientOnline(email) { | ||||||
|                 return this.onlineClients.includes(email); |                 return this.onlineClients.includes(email); | ||||||
|             }, |             }, | ||||||
|  |             getLastOnline(email) { | ||||||
|  |                 return this.lastOnlineMap[email] || null | ||||||
|  |             }, | ||||||
|  |             formatLastOnline(email) { | ||||||
|  |                 const ts = this.getLastOnline(email) | ||||||
|  |                 if (!ts) return '-' | ||||||
|  |                 if (this.datepicker === 'gregorian') { | ||||||
|  |                     return DateUtil.formatMillis(ts) | ||||||
|  |                 } | ||||||
|  |                 return DateUtil.convertToJalalian(moment(ts)) | ||||||
|  |             }, | ||||||
|             isRemovable(dbInboundId) { |             isRemovable(dbInboundId) { | ||||||
|                 return this.getInboundClients(this.dbInbounds.find(row => row.id === dbInboundId)).length > 1; |                 return this.getInboundClients(this.dbInbounds.find(row => row.id === dbInboundId)).length > 1; | ||||||
|             }, |             }, | ||||||
|  |  | ||||||
|  | @ -466,15 +466,24 @@ | ||||||
|           </g> |           </g> | ||||||
|         </svg> |         </svg> | ||||||
|       </div> |       </div> | ||||||
|       <a-row type="flex" justify="center" align="middle" :style="{ height: '100%', overflow: 'auto', overflowX: 'hidden' }"> |       <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' }"> |         <a-col :xs="22" :sm="12" :md="10" :lg="8" :xl="6" :xxl="5" id="login" :style="{ margin: '3rem 0' }"> | ||||||
|  |           <template v-if="!loadingStates.fetched"> | ||||||
|  |             <div :style="{ textAlign: 'center' }"> | ||||||
|  |               <a-spin size="large" /> | ||||||
|  |             </div> | ||||||
|  |           </template> | ||||||
|  |           <template v-else> | ||||||
|             <div class="setting-section"> |             <div class="setting-section"> | ||||||
|             <a-popover :overlay-class-name="themeSwitcher.currentTheme" title='{{ i18n "menu.settings" }}' placement="bottomRight" trigger="click"> |               <a-popover :overlay-class-name="themeSwitcher.currentTheme" title='{{ i18n "menu.settings" }}' | ||||||
|  |                 placement="bottomRight" trigger="click"> | ||||||
|                 <template slot="content"> |                 <template slot="content"> | ||||||
|                   <a-space direction="vertical" :size="10"> |                   <a-space direction="vertical" :size="10"> | ||||||
|                     <a-theme-switch-login></a-theme-switch-login> |                     <a-theme-switch-login></a-theme-switch-login> | ||||||
|                     <span>{{ i18n "pages.settings.language" }}</span> |                     <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 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"> |                       <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 role="img" aria-label="l.name" v-text="l.icon"></span> | ||||||
|                           <span v-text="l.name"></span> |                           <span v-text="l.name"></span> | ||||||
|  | @ -497,32 +506,34 @@ | ||||||
|             </a-row> |             </a-row> | ||||||
|             <a-row type="flex" justify="center"> |             <a-row type="flex" justify="center"> | ||||||
|               <a-col span="24"> |               <a-col span="24"> | ||||||
|               <a-form> |                 <a-form @submit.prevent="login"> | ||||||
|                   <a-space direction="vertical" size="middle"> |                   <a-space direction="vertical" size="middle"> | ||||||
|                     <a-form-item> |                     <a-form-item> | ||||||
|                       <a-input autocomplete="username" name="username" v-model.trim="user.username" |                       <a-input autocomplete="username" name="username" v-model.trim="user.username" | ||||||
|                       placeholder='{{ i18n "username" }}' @keydown.enter.native="login" autofocus> |                         placeholder='{{ i18n "username" }}' autofocus required> | ||||||
|                         <a-icon slot="prefix" type="user" :style="{ fontSize: '1rem' }"></a-icon> |                         <a-icon slot="prefix" type="user" :style="{ fontSize: '1rem' }"></a-icon> | ||||||
|                       </a-input> |                       </a-input> | ||||||
|                     </a-form-item> |                     </a-form-item> | ||||||
|                     <a-form-item> |                     <a-form-item> | ||||||
|                       <a-input-password autocomplete="password" name="password" v-model.trim="user.password" |                       <a-input-password autocomplete="password" name="password" v-model.trim="user.password" | ||||||
|                       placeholder='{{ i18n "password" }}' @keydown.enter.native="login"> |                         placeholder='{{ i18n "password" }}' required> | ||||||
|                         <a-icon slot="prefix" type="lock" :style="{ fontSize: '1rem' }"></a-icon> |                         <a-icon slot="prefix" type="lock" :style="{ fontSize: '1rem' }"></a-icon> | ||||||
|                       </a-input-password> |                       </a-input-password> | ||||||
|                     </a-form-item> |                     </a-form-item> | ||||||
|                     <a-form-item v-if="twoFactorEnable"> |                     <a-form-item v-if="twoFactorEnable"> | ||||||
|                       <a-input autocomplete="one-time-code" name="twoFactorCode" v-model.trim="user.twoFactorCode" |                       <a-input autocomplete="one-time-code" name="twoFactorCode" v-model.trim="user.twoFactorCode" | ||||||
|                       placeholder='{{ i18n "twoFactorCode" }}' @keydown.enter.native="login"> |                         placeholder='{{ i18n "twoFactorCode" }}' required> | ||||||
|                         <a-icon slot="prefix" type="key" :style="{ fontSize: '1rem' }"></a-icon> |                         <a-icon slot="prefix" type="key" :style="{ fontSize: '1rem' }"></a-icon> | ||||||
|                       </a-input> |                       </a-input> | ||||||
|                     </a-form-item> |                     </a-form-item> | ||||||
|                     <a-form-item> |                     <a-form-item> | ||||||
|                       <a-row justify="center" class="centered"> |                       <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"> |                         <div | ||||||
|                         <a-button class="ant-btn-primary-login" type="primary" :loading="loading" @click="login" |                           :style="{ height: '50px', marginTop: '1rem', ...loadingStates.spinning ? { width: '52px' } : { display: 'inline-block' } }" | ||||||
|                           :icon="loading ? 'poweroff' : undefined"> |                           class="wave-btn-bg wave-btn-bg-cl"> | ||||||
|                           [[ loading ? '' : '{{ i18n "login" }}' ]] |                           <a-button class="ant-btn-primary-login" type="primary" :loading="loadingStates.spinning" | ||||||
|  |                             :icon="loadingStates.spinning ? 'poweroff' : undefined" html-type="submit"> | ||||||
|  |                             [[ loadingStates.spinning ? '' : '{{ i18n "login" }}' ]] | ||||||
|                           </a-button> |                           </a-button> | ||||||
|                         </div> |                         </div> | ||||||
|                       </a-row> |                       </a-row> | ||||||
|  | @ -531,6 +542,7 @@ | ||||||
|                 </a-form> |                 </a-form> | ||||||
|               </a-col> |               </a-col> | ||||||
|             </a-row> |             </a-row> | ||||||
|  |           </template> | ||||||
|         </a-col> |         </a-col> | ||||||
|       </a-row> |       </a-row> | ||||||
|     </a-layout-content> |     </a-layout-content> | ||||||
|  | @ -544,7 +556,10 @@ | ||||||
|     el: '#app', |     el: '#app', | ||||||
|     data: { |     data: { | ||||||
|       themeSwitcher, |       themeSwitcher, | ||||||
|       loading: false, |       loadingStates: { | ||||||
|  |         fetched: false, | ||||||
|  |         spinning: false | ||||||
|  |       }, | ||||||
|       user: { |       user: { | ||||||
|         username: "", |         username: "", | ||||||
|         password: "", |         password: "", | ||||||
|  | @ -559,19 +574,23 @@ | ||||||
|     }, |     }, | ||||||
|     methods: { |     methods: { | ||||||
|       async login() { |       async login() { | ||||||
|         this.loading = true; |         this.loadingStates.spinning = true; | ||||||
|  | 
 | ||||||
|         const msg = await HttpUtil.post('/login', this.user); |         const msg = await HttpUtil.post('/login', this.user); | ||||||
|         this.loading = false; | 
 | ||||||
|         if (msg.success) { |         if (msg.success) { | ||||||
|           location.href = basePath + 'panel/'; |           location.href = basePath + 'panel/'; | ||||||
|         } |         } | ||||||
|  | 
 | ||||||
|  |         this.loadingStates.spinning = false; | ||||||
|       }, |       }, | ||||||
|       async getTwoFactorEnable() { |       async getTwoFactorEnable() { | ||||||
|         this.loading = true; |  | ||||||
|         const msg = await HttpUtil.post('/getTwoFactorEnable'); |         const msg = await HttpUtil.post('/getTwoFactorEnable'); | ||||||
|         this.loading = false; | 
 | ||||||
|         if (msg.success) { |         if (msg.success) { | ||||||
|           this.twoFactorEnable = msg.obj; |           this.twoFactorEnable = msg.obj; | ||||||
|  |           this.loadingStates.fetched = true; | ||||||
|  | 
 | ||||||
|           return msg.obj; |           return msg.obj; | ||||||
|         } |         } | ||||||
|       }, |       }, | ||||||
|  |  | ||||||
|  | @ -101,6 +101,12 @@ | ||||||
|       {{ i18n "security" }} |       {{ i18n "security" }} | ||||||
|       <a-tag :color="inbound.stream.security == 'none' ? 'red' : 'green'">[[ inbound.stream.security ]]</a-tag> |       <a-tag :color="inbound.stream.security == 'none' ? 'red' : 'green'">[[ inbound.stream.security ]]</a-tag> | ||||||
|       <br /> |       <br /> | ||||||
|  |       <td>Authentication</td> | ||||||
|  |         <a-tag :color="inbound.settings.selectedAuth ? 'green' : 'red'">[[ inbound.settings.selectedAuth ? inbound.settings.selectedAuth : '' ]]</a-tag> | ||||||
|  |       <br /> | ||||||
|  |       {{ i18n "encryption" }} | ||||||
|  |         <a-tag :color="inbound.settings.encryption ? 'green' : 'red'">[[ inbound.settings.encryption ? inbound.settings.encryption : '' ]]</a-tag> | ||||||
|  |       <br /> | ||||||
|       <template v-if="inbound.stream.security != 'none'"> |       <template v-if="inbound.stream.security != 'none'"> | ||||||
|         {{ i18n "domainName" }} |         {{ i18n "domainName" }} | ||||||
|         <a-tag v-if="inbound.serverName" :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : '' ]]</a-tag> |         <a-tag v-if="inbound.serverName" :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : '' ]]</a-tag> | ||||||
|  | @ -217,6 +223,12 @@ | ||||||
|             </template> |             </template> | ||||||
|           </td> |           </td> | ||||||
|         </tr> |         </tr> | ||||||
|  |         <tr> | ||||||
|  |           <td>{{ i18n "lastOnline" }}</td> | ||||||
|  |           <td> | ||||||
|  |             <a-tag>[[ app.formatLastOnline(infoModal.clientSettings && infoModal.clientSettings.email ? infoModal.clientSettings.email : '') ]]</a-tag> | ||||||
|  |           </td> | ||||||
|  |         </tr> | ||||||
|         <tr v-if="infoModal.clientSettings.comment"> |         <tr v-if="infoModal.clientSettings.comment"> | ||||||
|           <td>{{ i18n "comment" }}</td> |           <td>{{ i18n "comment" }}</td> | ||||||
|           <td> |           <td> | ||||||
|  |  | ||||||
|  | @ -1,9 +1,7 @@ | ||||||
| {{define "modals/inboundModal"}} | {{define "modals/inboundModal"}} | ||||||
| <a-modal id="inbound-modal" v-model="inModal.visible" :title="inModal.title" | <a-modal id="inbound-modal" v-model="inModal.visible" :title="inModal.title" :dialog-style="{ top: '20px' }" | ||||||
|         :dialog-style="{ top: '20px' }" @ok="inModal.ok" |     @ok="inModal.ok" :confirm-loading="inModal.confirmLoading" :closable="true" :mask-closable="false" | ||||||
|         :confirm-loading="inModal.confirmLoading" :closable="true" :mask-closable="false" |     :class="themeSwitcher.currentTheme" :ok-text="inModal.okText" cancel-text='{{ i18n "close" }}'> | ||||||
|         :class="themeSwitcher.currentTheme" |  | ||||||
|         :ok-text="inModal.okText" cancel-text='{{ i18n "close" }}'> |  | ||||||
|     {{template "form/inbound"}} |     {{template "form/inbound"}} | ||||||
| </a-modal> | </a-modal> | ||||||
| <script> | <script> | ||||||
|  | @ -162,6 +160,33 @@ | ||||||
|                 inModal.inbound.stream.tls.echServerKeys = msg.obj.echServerKeys; |                 inModal.inbound.stream.tls.echServerKeys = msg.obj.echServerKeys; | ||||||
|                 inModal.inbound.stream.tls.settings.echConfigList = msg.obj.echConfigList; |                 inModal.inbound.stream.tls.settings.echConfigList = msg.obj.echConfigList; | ||||||
|             }, |             }, | ||||||
|  |             async getNewVlessEnc() { | ||||||
|  |                 inModal.loading(true); | ||||||
|  |                 const msg = await HttpUtil.post('/server/getNewVlessEnc'); | ||||||
|  |                 inModal.loading(false); | ||||||
|  | 
 | ||||||
|  |                 if (!msg.success) { | ||||||
|  |                     return; | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 const auths = msg.obj.auths || []; | ||||||
|  |                 const selected = inModal.inbound.settings.selectedAuth; | ||||||
|  |                 const block = auths.find(a => a.label === selected); | ||||||
|  | 
 | ||||||
|  |                 if (!block) { | ||||||
|  |                     console.error("No auth block for", selected); | ||||||
|  |                     return; | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 inModal.inbound.settings.decryption = block.decryption; | ||||||
|  |                 inModal.inbound.settings.encryption = block.encryption; | ||||||
|  |             }, | ||||||
|  |             clearKeys() { | ||||||
|  |                 this.inbound.settings.decryption = 'none'; | ||||||
|  |                 this.inbound.settings.encryption = 'none'; | ||||||
|  |                 this.inbound.settings.selectedAuth = undefined; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|         }, |         }, | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,11 +1,6 @@ | ||||||
| {{define "modals/ruleModal"}} | {{define "modals/ruleModal"}} | ||||||
| <a-modal id="rule-modal" v-model="ruleModal.visible" :title="ruleModal.title" @ok="ruleModal.ok" :confirm-loading="ruleModal.confirmLoading" :closable="true" :mask-closable="false" :ok-text="ruleModal.okText" cancel-text='{{ i18n "close" }}' :class="themeSwitcher.currentTheme"> | <a-modal id="rule-modal" v-model="ruleModal.visible" :title="ruleModal.title" @ok="ruleModal.ok" :confirm-loading="ruleModal.confirmLoading" :closable="true" :mask-closable="false" :ok-text="ruleModal.okText" cancel-text='{{ i18n "close" }}' :class="themeSwitcher.currentTheme"> | ||||||
|   <a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }"> |   <a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }"> | ||||||
|     <a-form-item label='Domain Matcher'> |  | ||||||
|       <a-select v-model="ruleModal.rule.domainMatcher" :dropdown-class-name="themeSwitcher.currentTheme"> |  | ||||||
|         <a-select-option v-for="dm in ['','hybrid','linear']" :value="dm">[[ dm ]]</a-select-option> |  | ||||||
|       </a-select> |  | ||||||
|     </a-form-item> |  | ||||||
|     <a-form-item> |     <a-form-item> | ||||||
|       <template slot="label"> |       <template slot="label"> | ||||||
|         <a-tooltip> |         <a-tooltip> | ||||||
|  | @ -123,7 +118,6 @@ | ||||||
|     confirm: null, |     confirm: null, | ||||||
|     rule: { |     rule: { | ||||||
|       type: "field", |       type: "field", | ||||||
|       domainMatcher: "", |  | ||||||
|       domain: "", |       domain: "", | ||||||
|       ip: "", |       ip: "", | ||||||
|       port: "", |       port: "", | ||||||
|  | @ -157,7 +151,6 @@ | ||||||
|       this.confirm = confirm; |       this.confirm = confirm; | ||||||
|       this.visible = true; |       this.visible = true; | ||||||
|       if (isEdit) { |       if (isEdit) { | ||||||
|         this.rule.domainMatcher = rule.domainMatcher; |  | ||||||
|         this.rule.domain = rule.domain ? rule.domain.join(',') : []; |         this.rule.domain = rule.domain ? rule.domain.join(',') : []; | ||||||
|         this.rule.ip = rule.ip ? rule.ip.join(',') : []; |         this.rule.ip = rule.ip ? rule.ip.join(',') : []; | ||||||
|         this.rule.port = rule.port; |         this.rule.port = rule.port; | ||||||
|  | @ -172,7 +165,6 @@ | ||||||
|         this.rule.balancerTag = rule.balancerTag ? rule.balancerTag : ""; |         this.rule.balancerTag = rule.balancerTag ? rule.balancerTag : ""; | ||||||
|       } else { |       } else { | ||||||
|         this.rule = { |         this.rule = { | ||||||
|           domainMatcher: "", |  | ||||||
|           domain: "", |           domain: "", | ||||||
|           ip: "", |           ip: "", | ||||||
|           port: "", |           port: "", | ||||||
|  | @ -214,7 +206,6 @@ | ||||||
|       rule = {}; |       rule = {}; | ||||||
|       newRule = {}; |       newRule = {}; | ||||||
|       rule.type = "field"; |       rule.type = "field"; | ||||||
|       rule.domainMatcher = value.domainMatcher; |  | ||||||
|       rule.domain = value.domain.length > 0 ? value.domain.split(',') : []; |       rule.domain = value.domain.length > 0 ? value.domain.split(',') : []; | ||||||
|       rule.ip = value.ip.length > 0 ? value.ip.split(',') : []; |       rule.ip = value.ip.length > 0 ? value.ip.split(',') : []; | ||||||
|       rule.port = value.port; |       rule.port = value.port; | ||||||
|  |  | ||||||
|  | @ -359,6 +359,7 @@ func (s *InboundService) UpdateInbound(inbound *model.Inbound) (*model.Inbound, | ||||||
| 		var oldSettings map[string]any | 		var oldSettings map[string]any | ||||||
| 		_ = json.Unmarshal([]byte(oldInbound.Settings), &oldSettings) | 		_ = json.Unmarshal([]byte(oldInbound.Settings), &oldSettings) | ||||||
| 		emailToCreated := map[string]int64{} | 		emailToCreated := map[string]int64{} | ||||||
|  | 		emailToUpdated := map[string]int64{} | ||||||
| 		if oldSettings != nil { | 		if oldSettings != nil { | ||||||
| 			if oc, ok := oldSettings["clients"].([]any); ok { | 			if oc, ok := oldSettings["clients"].([]any); ok { | ||||||
| 				for _, it := range oc { | 				for _, it := range oc { | ||||||
|  | @ -370,6 +371,12 @@ func (s *InboundService) UpdateInbound(inbound *model.Inbound) (*model.Inbound, | ||||||
| 							case int64: | 							case int64: | ||||||
| 								emailToCreated[email] = v | 								emailToCreated[email] = v | ||||||
| 							} | 							} | ||||||
|  | 							switch v := m["updated_at"].(type) { | ||||||
|  | 							case float64: | ||||||
|  | 								emailToUpdated[email] = int64(v) | ||||||
|  | 							case int64: | ||||||
|  | 								emailToUpdated[email] = v | ||||||
|  | 							} | ||||||
| 						} | 						} | ||||||
| 					} | 					} | ||||||
| 				} | 				} | ||||||
|  | @ -389,7 +396,12 @@ func (s *InboundService) UpdateInbound(inbound *model.Inbound) (*model.Inbound, | ||||||
| 								m["created_at"] = now | 								m["created_at"] = now | ||||||
| 							} | 							} | ||||||
| 						} | 						} | ||||||
| 						m["updated_at"] = now | 						// Preserve client's updated_at if present; do not bump on parent inbound update
 | ||||||
|  | 						if _, hasUpdated := m["updated_at"]; !hasUpdated { | ||||||
|  | 							if v, ok4 := emailToUpdated[email]; ok4 && v > 0 { | ||||||
|  | 								m["updated_at"] = v | ||||||
|  | 							} | ||||||
|  | 						} | ||||||
| 						nSlice[i] = m | 						nSlice[i] = m | ||||||
| 					} | 					} | ||||||
| 				} | 				} | ||||||
|  | @ -978,6 +990,7 @@ func (s *InboundService) addClientTraffic(tx *gorm.DB, traffics []*xray.ClientTr | ||||||
| 				// Add user in onlineUsers array on traffic
 | 				// Add user in onlineUsers array on traffic
 | ||||||
| 				if traffics[traffic_index].Up+traffics[traffic_index].Down > 0 { | 				if traffics[traffic_index].Up+traffics[traffic_index].Down > 0 { | ||||||
| 					onlineClients = append(onlineClients, traffics[traffic_index].Email) | 					onlineClients = append(onlineClients, traffics[traffic_index].Email) | ||||||
|  | 					dbClientTraffics[dbTraffic_index].LastOnline = time.Now().UnixMilli() | ||||||
| 				} | 				} | ||||||
| 				break | 				break | ||||||
| 			} | 			} | ||||||
|  | @ -2198,6 +2211,20 @@ func (s *InboundService) GetOnlineClients() []string { | ||||||
| 	return p.GetOnlineClients() | 	return p.GetOnlineClients() | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func (s *InboundService) GetClientsLastOnline() (map[string]int64, error) { | ||||||
|  | 	db := database.GetDB() | ||||||
|  | 	var rows []xray.ClientTraffic | ||||||
|  | 	err := db.Model(&xray.ClientTraffic{}).Select("email, last_online").Find(&rows).Error | ||||||
|  | 	if err != nil && err != gorm.ErrRecordNotFound { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	result := make(map[string]int64, len(rows)) | ||||||
|  | 	for _, r := range rows { | ||||||
|  | 		result[r.Email] = r.LastOnline | ||||||
|  | 	} | ||||||
|  | 	return result, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func (s *InboundService) FilterAndSortClientEmails(emails []string) ([]string, []string, error) { | func (s *InboundService) FilterAndSortClientEmails(emails []string) ([]string, []string, error) { | ||||||
| 	db := database.GetDB() | 	db := database.GetDB() | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -871,3 +871,53 @@ func (s *ServerService) GetNewEchCert(sni string) (interface{}, error) { | ||||||
| 		"echConfigList": configList, | 		"echConfigList": configList, | ||||||
| 	}, nil | 	}, nil | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | type AuthBlock struct { | ||||||
|  | 	Label      string `json:"label"` | ||||||
|  | 	Decryption string `json:"decryption"` | ||||||
|  | 	Encryption string `json:"encryption"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *ServerService) GetNewVlessEnc() (any, error) { | ||||||
|  | 	cmd := exec.Command(xray.GetBinaryPath(), "vlessenc") | ||||||
|  | 	var out bytes.Buffer | ||||||
|  | 	cmd.Stdout = &out | ||||||
|  | 	if err := cmd.Run(); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	lines := strings.Split(out.String(), "\n") | ||||||
|  | 
 | ||||||
|  | 	var blocks []AuthBlock | ||||||
|  | 	var current *AuthBlock | ||||||
|  | 
 | ||||||
|  | 	for _, line := range lines { | ||||||
|  | 		line = strings.TrimSpace(line) | ||||||
|  | 		if strings.HasPrefix(line, "Authentication:") { | ||||||
|  | 			if current != nil { | ||||||
|  | 				blocks = append(blocks, *current) | ||||||
|  | 			} | ||||||
|  | 			current = &AuthBlock{Label: strings.TrimSpace(strings.TrimPrefix(line, "Authentication:"))} | ||||||
|  | 		} else if strings.HasPrefix(line, `"decryption"`) || strings.HasPrefix(line, `"encryption"`) { | ||||||
|  | 			parts := strings.SplitN(line, ":", 2) | ||||||
|  | 			if len(parts) == 2 && current != nil { | ||||||
|  | 				key := strings.Trim(parts[0], `" `) | ||||||
|  | 				val := strings.Trim(parts[1], `" `) | ||||||
|  | 				switch key { | ||||||
|  | 				case "decryption": | ||||||
|  | 					current.Decryption = val | ||||||
|  | 				case "encryption": | ||||||
|  | 					current.Encryption = val | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if current != nil { | ||||||
|  | 		blocks = append(blocks, *current) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return map[string]any{ | ||||||
|  | 		"auths": blocks, | ||||||
|  | 	}, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -50,6 +50,7 @@ | ||||||
| "fail" = "فشل" | "fail" = "فشل" | ||||||
| "comment" = "تعليق" | "comment" = "تعليق" | ||||||
| "success" = "تم بنجاح" | "success" = "تم بنجاح" | ||||||
|  | "lastOnline" = "آخر متصل" | ||||||
| "getVersion" = "جيب النسخة" | "getVersion" = "جيب النسخة" | ||||||
| "install" = "تثبيت" | "install" = "تثبيت" | ||||||
| "clients" = "عملاء" | "clients" = "عملاء" | ||||||
|  | @ -274,6 +275,7 @@ | ||||||
| "trafficGetError" = "خطأ في الحصول على حركات المرور" | "trafficGetError" = "خطأ في الحصول على حركات المرور" | ||||||
| "getNewX25519CertError" = "حدث خطأ أثناء الحصول على شهادة X25519." | "getNewX25519CertError" = "حدث خطأ أثناء الحصول على شهادة X25519." | ||||||
| "getNewmldsa65Error" = "حدث خطاء في الحصول على mldsa65." | "getNewmldsa65Error" = "حدث خطاء في الحصول على mldsa65." | ||||||
|  | "getNewVlessEncError" = "حدث خطأ أثناء الحصول على VlessEnc." | ||||||
| 
 | 
 | ||||||
| [pages.inbounds.stream.general] | [pages.inbounds.stream.general] | ||||||
| "request" = "طلب" | "request" = "طلب" | ||||||
|  |  | ||||||
|  | @ -50,6 +50,7 @@ | ||||||
| "fail" = "Failed" | "fail" = "Failed" | ||||||
| "comment" = "Comment" | "comment" = "Comment" | ||||||
| "success" = "Successfully" | "success" = "Successfully" | ||||||
|  | "lastOnline" = "Last Online" | ||||||
| "getVersion" = "Get Version" | "getVersion" = "Get Version" | ||||||
| "install" = "Install" | "install" = "Install" | ||||||
| "clients" = "Clients" | "clients" = "Clients" | ||||||
|  | @ -274,6 +275,7 @@ | ||||||
| "trafficGetError" = "Error getting traffics." | "trafficGetError" = "Error getting traffics." | ||||||
| "getNewX25519CertError" = "Error while obtaining the X25519 certificate." | "getNewX25519CertError" = "Error while obtaining the X25519 certificate." | ||||||
| "getNewmldsa65Error" = "Error while obtaining mldsa65." | "getNewmldsa65Error" = "Error while obtaining mldsa65." | ||||||
|  | "getNewVlessEncError" = "Error while obtaining VlessEnc." | ||||||
| 
 | 
 | ||||||
| [pages.inbounds.stream.general] | [pages.inbounds.stream.general] | ||||||
| "request" = "Request" | "request" = "Request" | ||||||
|  |  | ||||||
|  | @ -50,6 +50,7 @@ | ||||||
| "fail" = "Falló" | "fail" = "Falló" | ||||||
| "comment" = "Comentario" | "comment" = "Comentario" | ||||||
| "success" = "Éxito" | "success" = "Éxito" | ||||||
|  | "lastOnline" = "Última conexión" | ||||||
| "getVersion" = "Obtener versión" | "getVersion" = "Obtener versión" | ||||||
| "install" = "Instalar" | "install" = "Instalar" | ||||||
| "clients" = "Clientes" | "clients" = "Clientes" | ||||||
|  | @ -274,6 +275,7 @@ | ||||||
| "trafficGetError" = "Error al obtener los tráficos" | "trafficGetError" = "Error al obtener los tráficos" | ||||||
| "getNewX25519CertError" = "Error al obtener el certificado X25519." | "getNewX25519CertError" = "Error al obtener el certificado X25519." | ||||||
| "getNewmldsa65Error" = "Error al obtener el certificado mldsa65." | "getNewmldsa65Error" = "Error al obtener el certificado mldsa65." | ||||||
|  | "getNewVlessEncError" = "Error al obtener el certificado VlessEnc." | ||||||
| 
 | 
 | ||||||
| [pages.inbounds.stream.general] | [pages.inbounds.stream.general] | ||||||
| "request" = "Pedido" | "request" = "Pedido" | ||||||
|  |  | ||||||
|  | @ -50,6 +50,7 @@ | ||||||
| "fail" = "ناموفق" | "fail" = "ناموفق" | ||||||
| "comment" = "توضیحات" | "comment" = "توضیحات" | ||||||
| "success" = "موفق" | "success" = "موفق" | ||||||
|  | "lastOnline" = "آخرین فعالیت" | ||||||
| "getVersion" = "دریافت نسخه" | "getVersion" = "دریافت نسخه" | ||||||
| "install" = "نصب" | "install" = "نصب" | ||||||
| "clients" = "کاربران" | "clients" = "کاربران" | ||||||
|  | @ -274,6 +275,7 @@ | ||||||
| "trafficGetError" = "خطا در دریافت ترافیکها" | "trafficGetError" = "خطا در دریافت ترافیکها" | ||||||
| "getNewX25519CertError" = "خطا در دریافت گواهی X25519." | "getNewX25519CertError" = "خطا در دریافت گواهی X25519." | ||||||
| "getNewmldsa65Error" = "خطا در دریافت گواهی mldsa65." | "getNewmldsa65Error" = "خطا در دریافت گواهی mldsa65." | ||||||
|  | "getNewVlessEncError" = "خطا در دریافت گواهی VlessEnc." | ||||||
| 
 | 
 | ||||||
| [pages.inbounds.stream.general] | [pages.inbounds.stream.general] | ||||||
| "request" = "درخواست" | "request" = "درخواست" | ||||||
|  |  | ||||||
|  | @ -50,6 +50,7 @@ | ||||||
| "fail" = "Gagal" | "fail" = "Gagal" | ||||||
| "comment" = "Komentar" | "comment" = "Komentar" | ||||||
| "success" = "Berhasil" | "success" = "Berhasil" | ||||||
|  | "lastOnline" = "Terakhir online" | ||||||
| "getVersion" = "Dapatkan Versi" | "getVersion" = "Dapatkan Versi" | ||||||
| "install" = "Instal" | "install" = "Instal" | ||||||
| "clients" = "Klien" | "clients" = "Klien" | ||||||
|  | @ -274,6 +275,7 @@ | ||||||
| "trafficGetError" = "Gagal mendapatkan data lalu lintas" | "trafficGetError" = "Gagal mendapatkan data lalu lintas" | ||||||
| "getNewX25519CertError" = "Terjadi kesalahan saat mendapatkan sertifikat X25519." | "getNewX25519CertError" = "Terjadi kesalahan saat mendapatkan sertifikat X25519." | ||||||
| "getNewmldsa65Error" = "Terjadi kesalahan saat mendapatkan sertifikat mldsa65." | "getNewmldsa65Error" = "Terjadi kesalahan saat mendapatkan sertifikat mldsa65." | ||||||
|  | "getNewVlessEncError" = "Terjadi kesalahan saat mendapatkan sertifikat VlessEnc." | ||||||
| 
 | 
 | ||||||
| [pages.inbounds.stream.general] | [pages.inbounds.stream.general] | ||||||
| "request" = "Permintaan" | "request" = "Permintaan" | ||||||
|  |  | ||||||
|  | @ -50,6 +50,7 @@ | ||||||
| "fail" = "失敗" | "fail" = "失敗" | ||||||
| "comment" = "コメント" | "comment" = "コメント" | ||||||
| "success" = "成功" | "success" = "成功" | ||||||
|  | "lastOnline" = "最終オンライン" | ||||||
| "getVersion" = "バージョン取得" | "getVersion" = "バージョン取得" | ||||||
| "install" = "インストール" | "install" = "インストール" | ||||||
| "clients" = "クライアント" | "clients" = "クライアント" | ||||||
|  | @ -274,6 +275,7 @@ | ||||||
| "trafficGetError" = "トラフィックの取得中にエラーが発生しました" | "trafficGetError" = "トラフィックの取得中にエラーが発生しました" | ||||||
| "getNewX25519CertError" = "X25519証明書の取得中にエラーが発生しました。" | "getNewX25519CertError" = "X25519証明書の取得中にエラーが発生しました。" | ||||||
| "getNewmldsa65Error" = "mldsa65証明書の取得中にエラーが発生しました。" | "getNewmldsa65Error" = "mldsa65証明書の取得中にエラーが発生しました。" | ||||||
|  | "getNewVlessEncError" = "VlessEnc証明書の取得中にエラーが発生しました。" | ||||||
| 
 | 
 | ||||||
| [pages.inbounds.stream.general] | [pages.inbounds.stream.general] | ||||||
| "request" = "リクエスト" | "request" = "リクエスト" | ||||||
|  |  | ||||||
|  | @ -50,6 +50,7 @@ | ||||||
| "fail" = "Falhou" | "fail" = "Falhou" | ||||||
| "comment" = "Comentário" | "comment" = "Comentário" | ||||||
| "success" = "Com Sucesso" | "success" = "Com Sucesso" | ||||||
|  | "lastOnline" = "Última vez online" | ||||||
| "getVersion" = "Obter Versão" | "getVersion" = "Obter Versão" | ||||||
| "install" = "Instalar" | "install" = "Instalar" | ||||||
| "clients" = "Clientes" | "clients" = "Clientes" | ||||||
|  | @ -274,6 +275,7 @@ | ||||||
| "trafficGetError" = "Erro ao obter tráfegos" | "trafficGetError" = "Erro ao obter tráfegos" | ||||||
| "getNewX25519CertError" = "Erro ao obter o certificado X25519." | "getNewX25519CertError" = "Erro ao obter o certificado X25519." | ||||||
| "getNewmldsa65Error" = "Erro ao obter o certificado mldsa65." | "getNewmldsa65Error" = "Erro ao obter o certificado mldsa65." | ||||||
|  | "getNewVlessEncError" = "Erro ao obter o certificado VlessEnc." | ||||||
| 
 | 
 | ||||||
| [pages.inbounds.stream.general] | [pages.inbounds.stream.general] | ||||||
| "request" = "Requisição" | "request" = "Requisição" | ||||||
|  |  | ||||||
|  | @ -50,6 +50,7 @@ | ||||||
| "fail" = "Ошибка" | "fail" = "Ошибка" | ||||||
| "comment" = "Комментарий" | "comment" = "Комментарий" | ||||||
| "success" = "Успешно" | "success" = "Успешно" | ||||||
|  | "lastOnline" = "Был(а) в сети" | ||||||
| "getVersion" = "Узнать версию" | "getVersion" = "Узнать версию" | ||||||
| "install" = "Установка" | "install" = "Установка" | ||||||
| "clients" = "Клиенты" | "clients" = "Клиенты" | ||||||
|  | @ -274,6 +275,7 @@ | ||||||
| "trafficGetError" = "Ошибка получения данных о трафике" | "trafficGetError" = "Ошибка получения данных о трафике" | ||||||
| "getNewX25519CertError" = "Ошибка при получении сертификата X25519." | "getNewX25519CertError" = "Ошибка при получении сертификата X25519." | ||||||
| "getNewmldsa65Error" = "Ошибка при получении сертификата mldsa65." | "getNewmldsa65Error" = "Ошибка при получении сертификата mldsa65." | ||||||
|  | "getNewVlessEncError" = "Ошибка при получении сертификата VlessEnc." | ||||||
| 
 | 
 | ||||||
| [pages.inbounds.stream.general] | [pages.inbounds.stream.general] | ||||||
| "request" = "Запрос" | "request" = "Запрос" | ||||||
|  |  | ||||||
|  | @ -50,6 +50,7 @@ | ||||||
| "fail" = "Başarısız" | "fail" = "Başarısız" | ||||||
| "comment" = "Yorum" | "comment" = "Yorum" | ||||||
| "success" = "Başarılı" | "success" = "Başarılı" | ||||||
|  | "lastOnline" = "Son çevrimiçi" | ||||||
| "getVersion" = "Sürümü Al" | "getVersion" = "Sürümü Al" | ||||||
| "install" = "Yükle" | "install" = "Yükle" | ||||||
| "clients" = "Müşteriler" | "clients" = "Müşteriler" | ||||||
|  | @ -274,6 +275,7 @@ | ||||||
| "trafficGetError" = "Trafik bilgisi alınırken hata oluştu" | "trafficGetError" = "Trafik bilgisi alınırken hata oluştu" | ||||||
| "getNewX25519CertError" = "X25519 sertifikası alınırken hata oluştu." | "getNewX25519CertError" = "X25519 sertifikası alınırken hata oluştu." | ||||||
| "getNewmldsa65Error" = "mldsa65 sertifikası alınırken hata oluştu." | "getNewmldsa65Error" = "mldsa65 sertifikası alınırken hata oluştu." | ||||||
|  | "getNewVlessEncError" = "VlessEnc sertifikası alınırken hata oluştu." | ||||||
| 
 | 
 | ||||||
| [pages.inbounds.stream.general] | [pages.inbounds.stream.general] | ||||||
| "request" = "İstek" | "request" = "İstek" | ||||||
|  |  | ||||||
|  | @ -50,6 +50,7 @@ | ||||||
| "fail" = "Помилка" | "fail" = "Помилка" | ||||||
| "comment" = "Коментар" | "comment" = "Коментар" | ||||||
| "success" = "Успішно" | "success" = "Успішно" | ||||||
|  | "lastOnline" = "Був(ла) онлайн" | ||||||
| "getVersion" = "Отримати версію" | "getVersion" = "Отримати версію" | ||||||
| "install" = "Встановити" | "install" = "Встановити" | ||||||
| "clients" = "Клієнти" | "clients" = "Клієнти" | ||||||
|  | @ -274,6 +275,7 @@ | ||||||
| "trafficGetError" = "Помилка отримання даних про трафік" | "trafficGetError" = "Помилка отримання даних про трафік" | ||||||
| "getNewX25519CertError" = "Помилка при отриманні сертифіката X25519." | "getNewX25519CertError" = "Помилка при отриманні сертифіката X25519." | ||||||
| "getNewmldsa65Error" = "Помилка при отриманні сертифіката mldsa65." | "getNewmldsa65Error" = "Помилка при отриманні сертифіката mldsa65." | ||||||
|  | "getNewVlessEncError" = "Помилка при отриманні сертифіката VlessEnc." | ||||||
| 
 | 
 | ||||||
| [pages.inbounds.stream.general] | [pages.inbounds.stream.general] | ||||||
| "request" = "Запит" | "request" = "Запит" | ||||||
|  |  | ||||||
|  | @ -50,6 +50,7 @@ | ||||||
| "fail" = "Thất bại" | "fail" = "Thất bại" | ||||||
| "comment" = "Bình luận" | "comment" = "Bình luận" | ||||||
| "success" = "Thành công" | "success" = "Thành công" | ||||||
|  | "lastOnline" = "Lần online gần nhất" | ||||||
| "getVersion" = "Lấy phiên bản" | "getVersion" = "Lấy phiên bản" | ||||||
| "install" = "Cài đặt" | "install" = "Cài đặt" | ||||||
| "clients" = "Các khách hàng" | "clients" = "Các khách hàng" | ||||||
|  | @ -273,7 +274,8 @@ | ||||||
| "resetInboundClientTrafficSuccess" = "Đã đặt lại lưu lượng" | "resetInboundClientTrafficSuccess" = "Đã đặt lại lưu lượng" | ||||||
| "trafficGetError" = "Lỗi khi lấy thông tin lưu lượng" | "trafficGetError" = "Lỗi khi lấy thông tin lưu lượng" | ||||||
| "getNewX25519CertError" = "Lỗi khi lấy chứng chỉ X25519." | "getNewX25519CertError" = "Lỗi khi lấy chứng chỉ X25519." | ||||||
| "getNewmldsa65Error" = "Lỗi khi lấy chúng tôi mldsa65." | "getNewmldsa65Error" = "Lỗi khi lấy chứng chỉ mldsa65." | ||||||
|  | "getNewVlessEncError" = "Lỗi khi lấy chứng chỉ VlessEnc." | ||||||
| 
 | 
 | ||||||
| [pages.inbounds.stream.general] | [pages.inbounds.stream.general] | ||||||
| "request" = "Lời yêu cầu" | "request" = "Lời yêu cầu" | ||||||
|  |  | ||||||
|  | @ -50,6 +50,7 @@ | ||||||
| "fail" = "失败" | "fail" = "失败" | ||||||
| "comment" = "评论" | "comment" = "评论" | ||||||
| "success" = "成功" | "success" = "成功" | ||||||
|  | "lastOnline" = "上次在线" | ||||||
| "getVersion" = "获取版本" | "getVersion" = "获取版本" | ||||||
| "install" = "安装" | "install" = "安装" | ||||||
| "clients" = "客户端" | "clients" = "客户端" | ||||||
|  | @ -274,6 +275,7 @@ | ||||||
| "trafficGetError" = "获取流量数据时出错" | "trafficGetError" = "获取流量数据时出错" | ||||||
| "getNewX25519CertError" = "获取X25519证书时出错。" | "getNewX25519CertError" = "获取X25519证书时出错。" | ||||||
| "getNewmldsa65Error" = "获取mldsa65证书时出错。" | "getNewmldsa65Error" = "获取mldsa65证书时出错。" | ||||||
|  | "getNewVlessEncError" = "获取VlessEnc证书时出错。" | ||||||
| 
 | 
 | ||||||
| [pages.inbounds.stream.general] | [pages.inbounds.stream.general] | ||||||
| "request" = "请求" | "request" = "请求" | ||||||
|  |  | ||||||
|  | @ -50,6 +50,7 @@ | ||||||
| "fail" = "失敗" | "fail" = "失敗" | ||||||
| "comment" = "評論" | "comment" = "評論" | ||||||
| "success" = "成功" | "success" = "成功" | ||||||
|  | "lastOnline" = "上次上線" | ||||||
| "getVersion" = "獲取版本" | "getVersion" = "獲取版本" | ||||||
| "install" = "安裝" | "install" = "安裝" | ||||||
| "clients" = "客戶端" | "clients" = "客戶端" | ||||||
|  | @ -274,6 +275,7 @@ | ||||||
| "trafficGetError" = "取得流量資料時發生錯誤" | "trafficGetError" = "取得流量資料時發生錯誤" | ||||||
| "getNewX25519CertError" = "取得X25519憑證時發生錯誤。" | "getNewX25519CertError" = "取得X25519憑證時發生錯誤。" | ||||||
| "getNewmldsa65Error" = "取得mldsa65憑證時發生錯誤。" | "getNewmldsa65Error" = "取得mldsa65憑證時發生錯誤。" | ||||||
|  | "getNewVlessEncError" = "取得VlessEnc憑證時發生錯誤。" | ||||||
| 
 | 
 | ||||||
| [pages.inbounds.stream.general] | [pages.inbounds.stream.general] | ||||||
| "request" = "請求" | "request" = "請求" | ||||||
|  |  | ||||||
|  | @ -10,13 +10,6 @@ WorkingDirectory=/usr/local/x-ui/ | ||||||
| ExecStart=/usr/local/x-ui/x-ui | ExecStart=/usr/local/x-ui/x-ui | ||||||
| Restart=on-failure | Restart=on-failure | ||||||
| RestartSec=5s | RestartSec=5s | ||||||
| ProtectHome=tmpfs |  | ||||||
| ProtectKernelModules=true |  | ||||||
| ProtectControlGroups=true |  | ||||||
| ProtectKernelLogs=true |  | ||||||
| ProtectHostname=true |  | ||||||
| ProtectClock=true |  | ||||||
| MemoryDenyWriteExecute=true |  | ||||||
| 
 | 
 | ||||||
| [Install] | [Install] | ||||||
| WantedBy=multi-user.target | WantedBy=multi-user.target | ||||||
|  |  | ||||||
|  | @ -11,4 +11,5 @@ type ClientTraffic struct { | ||||||
| 	ExpiryTime int64  `json:"expiryTime" form:"expiryTime"` | 	ExpiryTime int64  `json:"expiryTime" form:"expiryTime"` | ||||||
| 	Total      int64  `json:"total" form:"total"` | 	Total      int64  `json:"total" form:"total"` | ||||||
| 	Reset      int    `json:"reset" form:"reset" gorm:"default:0"` | 	Reset      int    `json:"reset" form:"reset" gorm:"default:0"` | ||||||
|  | 	LastOnline int64  `json:"lastOnline" form:"lastOnline" gorm:"default:0"` | ||||||
| } | } | ||||||
|  |  | ||||||
		Loading…
	
		Reference in a new issue
	
	 Sanaei
						Sanaei