mirror of
				https://github.com/MHSanaei/3x-ui.git
				synced 2025-10-30 20:02:51 +00:00 
			
		
		
		
	Merge branch 'main' into periodic-traffic-reset
This commit is contained in:
		
						commit
						a1c0231abc
					
				
					 30 changed files with 317 additions and 170 deletions
				
			
		
							
								
								
									
										76
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										76
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							|  | @ -146,3 +146,79 @@ jobs: | |||
|           asset_name: x-ui-linux-${{ matrix.platform }}.tar.gz | ||||
|           overwrite: true | ||||
|           prerelease: true | ||||
| 
 | ||||
|   # ================================= | ||||
|   #  Windows Build | ||||
|   # ================================= | ||||
|   build-windows: | ||||
|     name: Build for Windows | ||||
|     permissions: | ||||
|       contents: write | ||||
|     strategy: | ||||
|       matrix: | ||||
|         platform: | ||||
|           - amd64 | ||||
|     runs-on: windows-latest | ||||
|     steps: | ||||
|       - name: Checkout repository | ||||
|         uses: actions/checkout@v5 | ||||
| 
 | ||||
|       - name: Setup Go | ||||
|         uses: actions/setup-go@v6 | ||||
|         with: | ||||
|           go-version-file: go.mod | ||||
|           check-latest: true | ||||
| 
 | ||||
|       - name: Build 3X-UI for Windows | ||||
|         shell: pwsh | ||||
|         run: | | ||||
|           $env:CGO_ENABLED="1" | ||||
|           $env:GOOS="windows" | ||||
|           $env:GOARCH="amd64" | ||||
|           go build -ldflags "-w -s" -o xui-release.exe -v main.go | ||||
|            | ||||
|           mkdir x-ui | ||||
|           Copy-Item xui-release.exe x-ui\ | ||||
|           mkdir x-ui\bin | ||||
|           cd x-ui\bin | ||||
|            | ||||
|           # Download Xray for Windows | ||||
|           $Xray_URL = "https://github.com/XTLS/Xray-core/releases/download/v25.6.8/" | ||||
|           Invoke-WebRequest -Uri "${Xray_URL}Xray-windows-64.zip" -OutFile "Xray-windows-64.zip" | ||||
|           Expand-Archive -Path "Xray-windows-64.zip" -DestinationPath . | ||||
|           Remove-Item "Xray-windows-64.zip" | ||||
|           Remove-Item geoip.dat, geosite.dat -ErrorAction SilentlyContinue | ||||
|           Invoke-WebRequest -Uri "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat" -OutFile "geoip.dat" | ||||
|           Invoke-WebRequest -Uri "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat" -OutFile "geosite.dat" | ||||
|           Invoke-WebRequest -Uri "https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geoip.dat" -OutFile "geoip_IR.dat" | ||||
|           Invoke-WebRequest -Uri "https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geosite.dat" -OutFile "geosite_IR.dat" | ||||
|           Invoke-WebRequest -Uri "https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geoip.dat" -OutFile "geoip_RU.dat" | ||||
|           Invoke-WebRequest -Uri "https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geosite.dat" -OutFile "geosite_RU.dat" | ||||
|           Rename-Item xray.exe xray-windows-amd64.exe | ||||
|           cd .. | ||||
|           Copy-Item -Path ..\windows_files\* -Destination . -Recurse | ||||
|           cd .. | ||||
| 
 | ||||
|       - name: Package to Zip | ||||
|         shell: pwsh | ||||
|         run: | | ||||
|           Compress-Archive -Path .\x-ui -DestinationPath "x-ui-windows-amd64.zip" | ||||
| 
 | ||||
|       - name: Upload files to Artifacts | ||||
|         uses: actions/upload-artifact@v4 | ||||
|         with: | ||||
|           name: x-ui-windows-amd64 | ||||
|           path: ./x-ui-windows-amd64.zip | ||||
| 
 | ||||
|       - name: Upload files to GH release | ||||
|         uses: svenstaro/upload-release-action@v2 | ||||
|         if: | | ||||
|           (github.event_name == 'release' && github.event.action == 'published') || | ||||
|           (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')) | ||||
|         with: | ||||
|           repo_token: ${{ secrets.GITHUB_TOKEN }} | ||||
|           tag: ${{ github.ref }} | ||||
|           file: x-ui-windows-amd64.zip | ||||
|           asset_name: x-ui-windows-amd64.zip | ||||
|           overwrite: true | ||||
|           prerelease: true | ||||
|  | @ -12,11 +12,11 @@ type Protocol string | |||
| const ( | ||||
| 	VMESS       Protocol = "vmess" | ||||
| 	VLESS       Protocol = "vless" | ||||
| 	DOKODEMO    Protocol = "dokodemo-door" | ||||
| 	Tunnel      Protocol = "tunnel" | ||||
| 	HTTP        Protocol = "http" | ||||
| 	Trojan      Protocol = "trojan" | ||||
| 	Shadowsocks Protocol = "shadowsocks" | ||||
| 	Socks       Protocol = "socks" | ||||
| 	Mixed       Protocol = "mixed" | ||||
| 	WireGuard   Protocol = "wireguard" | ||||
| ) | ||||
| 
 | ||||
|  |  | |||
|  | @ -13,7 +13,7 @@ | |||
|   "inbounds": [ | ||||
|     { | ||||
|       "port": 10808, | ||||
|       "protocol": "socks", | ||||
|       "protocol": "mixed", | ||||
|       "settings": { | ||||
|         "auth": "noauth", | ||||
|         "udp": true, | ||||
|  | @ -28,7 +28,7 @@ | |||
|         ], | ||||
|         "enabled": true | ||||
|       }, | ||||
|       "tag": "socks" | ||||
|       "tag": "mixed" | ||||
|     }, | ||||
|     { | ||||
|       "port": 10809, | ||||
|  |  | |||
|  | @ -50,8 +50,8 @@ class DBInbound { | |||
|         return this.protocol === Protocols.SHADOWSOCKS; | ||||
|     } | ||||
| 
 | ||||
|     get isSocks() { | ||||
|         return this.protocol === Protocols.SOCKS; | ||||
|     get isMixed() { | ||||
|         return this.protocol === Protocols.MIXED; | ||||
|     } | ||||
| 
 | ||||
|     get isHTTP() { | ||||
|  |  | |||
|  | @ -3,8 +3,8 @@ const Protocols = { | |||
|     VLESS: 'vless', | ||||
|     TROJAN: 'trojan', | ||||
|     SHADOWSOCKS: 'shadowsocks', | ||||
|     DOKODEMO: 'dokodemo-door', | ||||
|     SOCKS: 'socks', | ||||
|     TUNNEL: 'tunnel', | ||||
|     MIXED: 'mixed', | ||||
|     HTTP: 'http', | ||||
|     WIREGUARD: 'wireguard', | ||||
| }; | ||||
|  | @ -729,7 +729,7 @@ class RealityStreamSettings extends XrayCommonClass { | |||
|     constructor( | ||||
|         show = false, | ||||
|         xver = 0, | ||||
|         dest = 'google.com:443', | ||||
|         target = 'google.com:443', | ||||
|         serverNames = 'google.com,www.google.com', | ||||
|         privateKey = '', | ||||
|         minClientVer = '', | ||||
|  | @ -742,7 +742,7 @@ class RealityStreamSettings extends XrayCommonClass { | |||
|         super(); | ||||
|         this.show = show; | ||||
|         this.xver = xver; | ||||
|         this.dest = dest; | ||||
|         this.target = target; | ||||
|         this.serverNames = Array.isArray(serverNames) ? serverNames.join(",") : serverNames; | ||||
|         this.privateKey = privateKey; | ||||
|         this.minClientVer = minClientVer; | ||||
|  | @ -767,7 +767,7 @@ class RealityStreamSettings extends XrayCommonClass { | |||
|         return new RealityStreamSettings( | ||||
|             json.show, | ||||
|             json.xver, | ||||
|             json.dest, | ||||
|             json.target, | ||||
|             json.serverNames, | ||||
|             json.privateKey, | ||||
|             json.minClientVer, | ||||
|  | @ -783,7 +783,7 @@ class RealityStreamSettings extends XrayCommonClass { | |||
|         return { | ||||
|             show: this.show, | ||||
|             xver: this.xver, | ||||
|             dest: this.dest, | ||||
|             target: this.target, | ||||
|             serverNames: this.serverNames.split(","), | ||||
|             privateKey: this.privateKey, | ||||
|             minClientVer: this.minClientVer, | ||||
|  | @ -1712,8 +1712,8 @@ Inbound.Settings = class extends XrayCommonClass { | |||
|             case Protocols.VLESS: return new Inbound.VLESSSettings(protocol); | ||||
|             case Protocols.TROJAN: return new Inbound.TrojanSettings(protocol); | ||||
|             case Protocols.SHADOWSOCKS: return new Inbound.ShadowsocksSettings(protocol); | ||||
|             case Protocols.DOKODEMO: return new Inbound.DokodemoSettings(protocol); | ||||
|             case Protocols.SOCKS: return new Inbound.SocksSettings(protocol); | ||||
|             case Protocols.TUNNEL: return new Inbound.TunnelSettings(protocol); | ||||
|             case Protocols.MIXED: return new Inbound.MixedSettings(protocol); | ||||
|             case Protocols.HTTP: return new Inbound.HttpSettings(protocol); | ||||
|             case Protocols.WIREGUARD: return new Inbound.WireguardSettings(protocol); | ||||
|             default: return null; | ||||
|  | @ -1726,8 +1726,8 @@ Inbound.Settings = class extends XrayCommonClass { | |||
|             case Protocols.VLESS: return Inbound.VLESSSettings.fromJson(json); | ||||
|             case Protocols.TROJAN: return Inbound.TrojanSettings.fromJson(json); | ||||
|             case Protocols.SHADOWSOCKS: return Inbound.ShadowsocksSettings.fromJson(json); | ||||
|             case Protocols.DOKODEMO: return Inbound.DokodemoSettings.fromJson(json); | ||||
|             case Protocols.SOCKS: return Inbound.SocksSettings.fromJson(json); | ||||
|             case Protocols.TUNNEL: return Inbound.TunnelSettings.fromJson(json); | ||||
|             case Protocols.MIXED: return Inbound.MixedSettings.fromJson(json); | ||||
|             case Protocols.HTTP: return Inbound.HttpSettings.fromJson(json); | ||||
|             case Protocols.WIREGUARD: return Inbound.WireguardSettings.fromJson(json); | ||||
|             default: return null; | ||||
|  | @ -2327,7 +2327,7 @@ Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass { | |||
| 
 | ||||
| }; | ||||
| 
 | ||||
| Inbound.DokodemoSettings = class extends Inbound.Settings { | ||||
| Inbound.TunnelSettings = class extends Inbound.Settings { | ||||
|     constructor( | ||||
|         protocol, | ||||
|         address, | ||||
|  | @ -2345,8 +2345,8 @@ Inbound.DokodemoSettings = class extends Inbound.Settings { | |||
|     } | ||||
| 
 | ||||
|     static fromJson(json = {}) { | ||||
|         return new Inbound.DokodemoSettings( | ||||
|             Protocols.DOKODEMO, | ||||
|         return new Inbound.TunnelSettings( | ||||
|             Protocols.TUNNEL, | ||||
|             json.address, | ||||
|             json.port, | ||||
|             XrayCommonClass.toHeaders(json.portMap), | ||||
|  | @ -2366,8 +2366,8 @@ Inbound.DokodemoSettings = class extends Inbound.Settings { | |||
|     } | ||||
| }; | ||||
| 
 | ||||
| Inbound.SocksSettings = class extends Inbound.Settings { | ||||
|     constructor(protocol, auth = 'password', accounts = [new Inbound.SocksSettings.SocksAccount()], udp = false, ip = '127.0.0.1') { | ||||
| Inbound.MixedSettings = class extends Inbound.Settings { | ||||
|     constructor(protocol, auth = 'password', accounts = [new Inbound.MixedSettings.SocksAccount()], udp = false, ip = '127.0.0.1') { | ||||
|         super(protocol); | ||||
|         this.auth = auth; | ||||
|         this.accounts = accounts; | ||||
|  | @ -2387,11 +2387,11 @@ Inbound.SocksSettings = class extends Inbound.Settings { | |||
|         let accounts; | ||||
|         if (json.auth === 'password') { | ||||
|             accounts = json.accounts.map( | ||||
|                 account => Inbound.SocksSettings.SocksAccount.fromJson(account) | ||||
|                 account => Inbound.MixedSettings.SocksAccount.fromJson(account) | ||||
|             ) | ||||
|         } | ||||
|         return new Inbound.SocksSettings( | ||||
|             Protocols.SOCKS, | ||||
|         return new Inbound.MixedSettings( | ||||
|             Protocols.MIXED, | ||||
|             json.auth, | ||||
|             accounts, | ||||
|             json.udp, | ||||
|  | @ -2408,7 +2408,7 @@ Inbound.SocksSettings = class extends Inbound.Settings { | |||
|         }; | ||||
|     } | ||||
| }; | ||||
| Inbound.SocksSettings.SocksAccount = class extends XrayCommonClass { | ||||
| Inbound.MixedSettings.SocksAccount = class extends XrayCommonClass { | ||||
|     constructor(user = RandomUtil.randomSeq(10), pass = RandomUtil.randomSeq(10)) { | ||||
|         super(); | ||||
|         this.user = user; | ||||
|  | @ -2416,7 +2416,7 @@ Inbound.SocksSettings.SocksAccount = class extends XrayCommonClass { | |||
|     } | ||||
| 
 | ||||
|     static fromJson(json = {}) { | ||||
|         return new Inbound.SocksSettings.SocksAccount(json.user, json.pass); | ||||
|         return new Inbound.MixedSettings.SocksAccount(json.user, json.pass); | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
|  |  | |||
|  | @ -6,7 +6,7 @@ const Protocols = { | |||
|     VLESS: "vless", | ||||
|     Trojan: "trojan", | ||||
|     Shadowsocks: "shadowsocks", | ||||
|     Socks: "socks", | ||||
|     Mixed: "mixed", | ||||
|     HTTP: "http", | ||||
|     Wireguard: "wireguard" | ||||
| }; | ||||
|  | @ -643,7 +643,7 @@ class Outbound extends CommonClass { | |||
|             Protocols.Trojan, | ||||
|             Protocols.Shadowsocks, | ||||
|             Protocols.HTTP, | ||||
|             Protocols.Socks | ||||
|             Protocols.Mixed | ||||
|         ].includes(this.protocol); | ||||
|     } | ||||
| 
 | ||||
|  | @ -652,7 +652,7 @@ class Outbound extends CommonClass { | |||
|     } | ||||
| 
 | ||||
|     hasServers() { | ||||
|         return [Protocols.Trojan, Protocols.Shadowsocks, Protocols.Socks, Protocols.HTTP].includes(this.protocol); | ||||
|         return [Protocols.Trojan, Protocols.Shadowsocks, Protocols.Mixed, Protocols.HTTP].includes(this.protocol); | ||||
|     } | ||||
| 
 | ||||
|     hasAddressPort() { | ||||
|  | @ -662,13 +662,13 @@ class Outbound extends CommonClass { | |||
|             Protocols.VLESS, | ||||
|             Protocols.Trojan, | ||||
|             Protocols.Shadowsocks, | ||||
|             Protocols.Socks, | ||||
|             Protocols.Mixed, | ||||
|             Protocols.HTTP | ||||
|         ].includes(this.protocol); | ||||
|     } | ||||
| 
 | ||||
|     hasUsername() { | ||||
|         return [Protocols.Socks, Protocols.HTTP].includes(this.protocol); | ||||
|         return [Protocols.Mixed, Protocols.HTTP].includes(this.protocol); | ||||
|     } | ||||
| 
 | ||||
|     static fromJson(json = {}) { | ||||
|  | @ -847,7 +847,7 @@ Outbound.Settings = class extends CommonClass { | |||
|             case Protocols.VLESS: return new Outbound.VLESSSettings(); | ||||
|             case Protocols.Trojan: return new Outbound.TrojanSettings(); | ||||
|             case Protocols.Shadowsocks: return new Outbound.ShadowsocksSettings(); | ||||
|             case Protocols.Socks: return new Outbound.SocksSettings(); | ||||
|             case Protocols.Mixed: return new Outbound.MixedSettings(); | ||||
|             case Protocols.HTTP: return new Outbound.HttpSettings(); | ||||
|             case Protocols.Wireguard: return new Outbound.WireguardSettings(); | ||||
|             default: return null; | ||||
|  | @ -863,7 +863,7 @@ Outbound.Settings = class extends CommonClass { | |||
|             case Protocols.VLESS: return Outbound.VLESSSettings.fromJson(json); | ||||
|             case Protocols.Trojan: return Outbound.TrojanSettings.fromJson(json); | ||||
|             case Protocols.Shadowsocks: return Outbound.ShadowsocksSettings.fromJson(json); | ||||
|             case Protocols.Socks: return Outbound.SocksSettings.fromJson(json); | ||||
|             case Protocols.Mixed: return Outbound.MixedSettings.fromJson(json); | ||||
|             case Protocols.HTTP: return Outbound.HttpSettings.fromJson(json); | ||||
|             case Protocols.Wireguard: return Outbound.WireguardSettings.fromJson(json); | ||||
|             default: return null; | ||||
|  | @ -1141,7 +1141,7 @@ Outbound.ShadowsocksSettings = class extends CommonClass { | |||
|     } | ||||
| }; | ||||
| 
 | ||||
| Outbound.SocksSettings = class extends CommonClass { | ||||
| Outbound.MixedSettings = class extends CommonClass { | ||||
|     constructor(address, port, user, pass) { | ||||
|         super(); | ||||
|         this.address = address; | ||||
|  | @ -1153,7 +1153,7 @@ Outbound.SocksSettings = class extends CommonClass { | |||
|     static fromJson(json = {}) { | ||||
|         let servers = json.servers; | ||||
|         if (ObjectUtil.isArrEmpty(servers)) servers = [{ users: [{}] }]; | ||||
|         return new Outbound.SocksSettings( | ||||
|         return new Outbound.MixedSettings( | ||||
|             servers[0].address, | ||||
|             servers[0].port, | ||||
|             ObjectUtil.isArrEmpty(servers[0].users) ? '' : servers[0].users[0].user, | ||||
|  |  | |||
|  | @ -9,6 +9,7 @@ import ( | |||
| type APIController struct { | ||||
| 	BaseController | ||||
| 	inboundController *InboundController | ||||
| 	serverController  *ServerController | ||||
| 	Tgbot             service.Tgbot | ||||
| } | ||||
| 
 | ||||
|  | @ -19,43 +20,22 @@ func NewAPIController(g *gin.RouterGroup) *APIController { | |||
| } | ||||
| 
 | ||||
| func (a *APIController) initRouter(g *gin.RouterGroup) { | ||||
| 	g = g.Group("/panel/api/inbounds") | ||||
| 	g.Use(a.checkLogin) | ||||
| 	// Main API group
 | ||||
| 	api := g.Group("/panel/api") | ||||
| 	api.Use(a.checkLogin) | ||||
| 
 | ||||
| 	a.inboundController = NewInboundController(g) | ||||
| 	// Inbounds API
 | ||||
| 	inbounds := api.Group("/inbounds") | ||||
| 	a.inboundController = NewInboundController(inbounds) | ||||
| 
 | ||||
| 	inboundRoutes := []struct { | ||||
| 		Method  string | ||||
| 		Path    string | ||||
| 		Handler gin.HandlerFunc | ||||
| 	}{ | ||||
| 		{"GET", "/createbackup", a.createBackup}, | ||||
| 		{"GET", "/list", a.inboundController.getInbounds}, | ||||
| 		{"GET", "/get/:id", a.inboundController.getInbound}, | ||||
| 		{"GET", "/getClientTraffics/:email", a.inboundController.getClientTraffics}, | ||||
| 		{"GET", "/getClientTrafficsById/:id", a.inboundController.getClientTrafficsById}, | ||||
| 		{"POST", "/add", a.inboundController.addInbound}, | ||||
| 		{"POST", "/del/:id", a.inboundController.delInbound}, | ||||
| 		{"POST", "/update/:id", a.inboundController.updateInbound}, | ||||
| 		{"POST", "/clientIps/:email", a.inboundController.getClientIps}, | ||||
| 		{"POST", "/clearClientIps/:email", a.inboundController.clearClientIps}, | ||||
| 		{"POST", "/addClient", a.inboundController.addInboundClient}, | ||||
| 		{"POST", "/:id/delClient/:clientId", a.inboundController.delInboundClient}, | ||||
| 		{"POST", "/updateClient/:clientId", a.inboundController.updateInboundClient}, | ||||
| 		{"POST", "/:id/resetClientTraffic/:email", a.inboundController.resetClientTraffic}, | ||||
| 		{"POST", "/resetAllTraffics", a.inboundController.resetAllTraffics}, | ||||
| 		{"POST", "/resetAllClientTraffics/:id", a.inboundController.resetAllClientTraffics}, | ||||
| 		{"POST", "/delDepletedClients/:id", a.inboundController.delDepletedClients}, | ||||
| 		{"POST", "/onlines", a.inboundController.onlines}, | ||||
| 		{"POST", "/lastOnline", a.inboundController.lastOnline}, | ||||
| 		{"POST", "/updateClientTraffic/:email", a.inboundController.updateClientTraffic}, | ||||
| 	} | ||||
| 	// Server API
 | ||||
| 	server := api.Group("/server") | ||||
| 	a.serverController = NewServerController(server) | ||||
| 
 | ||||
| 	for _, route := range inboundRoutes { | ||||
| 		g.Handle(route.Method, route.Path, route.Handler) | ||||
| 	} | ||||
| 	// Extra routes
 | ||||
| 	api.GET("/backuptotgbot", a.BackuptoTgbot) | ||||
| } | ||||
| 
 | ||||
| func (a *APIController) createBackup(c *gin.Context) { | ||||
| func (a *APIController) BackuptoTgbot(c *gin.Context) { | ||||
| 	a.Tgbot.SendBackupToAdmins() | ||||
| } | ||||
|  |  | |||
|  | @ -24,9 +24,12 @@ func NewInboundController(g *gin.RouterGroup) *InboundController { | |||
| } | ||||
| 
 | ||||
| func (a *InboundController) initRouter(g *gin.RouterGroup) { | ||||
| 	g = g.Group("/inbound") | ||||
| 
 | ||||
| 	g.POST("/list", a.getInbounds) | ||||
| 	g.GET("/list", a.getInbounds) | ||||
| 	g.GET("/get/:id", a.getInbound) | ||||
| 	g.GET("/getClientTraffics/:email", a.getClientTraffics) | ||||
| 	g.GET("/getClientTrafficsById/:id", a.getClientTrafficsById) | ||||
| 
 | ||||
| 	g.POST("/add", a.addInbound) | ||||
| 	g.POST("/del/:id", a.delInbound) | ||||
| 	g.POST("/update/:id", a.updateInbound) | ||||
|  | @ -41,6 +44,8 @@ func (a *InboundController) initRouter(g *gin.RouterGroup) { | |||
| 	g.POST("/delDepletedClients/:id", a.delDepletedClients) | ||||
| 	g.POST("/import", a.importInbound) | ||||
| 	g.POST("/onlines", a.onlines) | ||||
| 	g.POST("/lastOnline", a.lastOnline) | ||||
| 	g.POST("/updateClientTraffic/:email", a.updateClientTraffic) | ||||
| } | ||||
| 
 | ||||
| func (a *InboundController) getInbounds(c *gin.Context) { | ||||
|  |  | |||
|  | @ -37,11 +37,17 @@ func NewServerController(g *gin.RouterGroup) *ServerController { | |||
| } | ||||
| 
 | ||||
| func (a *ServerController) initRouter(g *gin.RouterGroup) { | ||||
| 	g = g.Group("/server") | ||||
| 
 | ||||
| 	g.Use(a.checkLogin) | ||||
| 	g.POST("/status", a.status) | ||||
| 	g.POST("/getXrayVersion", a.getXrayVersion) | ||||
| 	g.GET("/status", a.status) | ||||
| 	g.GET("/getXrayVersion", a.getXrayVersion) | ||||
| 	g.GET("/getConfigJson", a.getConfigJson) | ||||
| 	g.GET("/getDb", a.getDb) | ||||
| 	g.GET("/getNewUUID", a.getNewUUID) | ||||
| 	g.GET("/getNewX25519Cert", a.getNewX25519Cert) | ||||
| 	g.GET("/getNewmldsa65", a.getNewmldsa65) | ||||
| 	g.GET("/getNewmlkem768", a.getNewmlkem768) | ||||
| 	g.GET("/getNewVlessEnc", a.getNewVlessEnc) | ||||
| 
 | ||||
| 	g.POST("/stopXrayService", a.stopXrayService) | ||||
| 	g.POST("/restartXrayService", a.restartXrayService) | ||||
| 	g.POST("/installXray/:version", a.installXray) | ||||
|  | @ -49,13 +55,8 @@ func (a *ServerController) initRouter(g *gin.RouterGroup) { | |||
| 	g.POST("/updateGeofile/:fileName", a.updateGeofile) | ||||
| 	g.POST("/logs/:count", a.getLogs) | ||||
| 	g.POST("/xraylogs/:count", a.getXrayLogs) | ||||
| 	g.POST("/getConfigJson", a.getConfigJson) | ||||
| 	g.GET("/getDb", a.getDb) | ||||
| 	g.POST("/importDB", a.importDB) | ||||
| 	g.POST("/getNewX25519Cert", a.getNewX25519Cert) | ||||
| 	g.POST("/getNewmldsa65", a.getNewmldsa65) | ||||
| 	g.POST("/getNewEchCert", a.getNewEchCert) | ||||
| 	g.POST("/getNewVlessEnc", a.getNewVlessEnc) | ||||
| } | ||||
| 
 | ||||
| func (a *ServerController) refreshStatus() { | ||||
|  | @ -276,3 +277,22 @@ func (a *ServerController) getNewVlessEnc(c *gin.Context) { | |||
| 	} | ||||
| 	jsonObj(c, out, nil) | ||||
| } | ||||
| 
 | ||||
| func (a *ServerController) getNewUUID(c *gin.Context) { | ||||
| 	uuidResp, err := a.serverService.GetNewUUID() | ||||
| 	if err != nil { | ||||
| 		jsonMsg(c, "Failed to generate UUID", err) | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	jsonObj(c, uuidResp, nil) | ||||
| } | ||||
| 
 | ||||
| func (a *ServerController) getNewmlkem768(c *gin.Context) { | ||||
| 	out, err := a.serverService.GetNewmlkem768() | ||||
| 	if err != nil { | ||||
| 		jsonMsg(c, "Failed to generate mlkem768 keys", err) | ||||
| 		return | ||||
| 	} | ||||
| 	jsonObj(c, out, nil) | ||||
| } | ||||
|  |  | |||
|  | @ -8,6 +8,7 @@ type XUIController struct { | |||
| 	BaseController | ||||
| 
 | ||||
| 	inboundController     *InboundController | ||||
| 	serverController      *ServerController | ||||
| 	settingController     *SettingController | ||||
| 	xraySettingController *XraySettingController | ||||
| } | ||||
|  | @ -28,6 +29,7 @@ func (a *XUIController) initRouter(g *gin.RouterGroup) { | |||
| 	g.GET("/xray", a.xraySettings) | ||||
| 
 | ||||
| 	a.inboundController = NewInboundController(g) | ||||
| 	a.serverController = NewServerController(g) | ||||
| 	a.settingController = NewSettingController(g) | ||||
| 	a.xraySettingController = NewXraySettingController(g) | ||||
| } | ||||
|  |  | |||
|  | @ -101,14 +101,14 @@ | |||
|     {{template "form/shadowsocks"}} | ||||
| </template> | ||||
| 
 | ||||
| <!-- dokodemo-door --> | ||||
| <template v-if="inbound.protocol === Protocols.DOKODEMO"> | ||||
|     {{template "form/dokodemo"}} | ||||
| <!-- tunnel --> | ||||
| <template v-if="inbound.protocol === Protocols.TUNNEL"> | ||||
|     {{template "form/tunnel"}} | ||||
| </template> | ||||
| 
 | ||||
| <!-- socks --> | ||||
| <template v-if="inbound.protocol === Protocols.SOCKS"> | ||||
|     {{template "form/socks"}} | ||||
| <!-- mixed --> | ||||
| <template v-if="inbound.protocol === Protocols.MIXED"> | ||||
|     {{template "form/mixed"}} | ||||
| </template> | ||||
| 
 | ||||
| <!-- http --> | ||||
|  |  | |||
|  | @ -241,9 +241,9 @@ | |||
|         </template> | ||||
|       </template> | ||||
| 
 | ||||
|       <!-- Servers (trojan/shadowsocks/socks/http) settings --> | ||||
|       <!-- Servers (trojan/shadowsocks/mixed/http) settings --> | ||||
|       <template v-if="outbound.hasServers()"> | ||||
|         <!-- http / socks --> | ||||
|         <!-- http / mixed --> | ||||
|         <template v-if="outbound.hasUsername()"> | ||||
|           <a-form-item label='{{ i18n "username" }}'> | ||||
|             <a-input v-model.trim="outbound.settings.user"></a-input> | ||||
|  | @ -441,6 +441,9 @@ | |||
|               <a-select-option v-for="alpn in ALPN_OPTION" :value="alpn">[[ alpn ]]</a-select-option> | ||||
|             </a-select> | ||||
|           </a-form-item> | ||||
|           <a-form-item label="ECH Config List"> | ||||
|             <a-input v-model.trim="outbound.stream.tls.echConfigList"></a-input> | ||||
|           </a-form-item> | ||||
|           <a-form-item label="Allow Insecure"> | ||||
|             <a-switch v-model="outbound.stream.tls.allowInsecure"></a-switch> | ||||
|           </a-form-item> | ||||
|  |  | |||
|  | @ -1,4 +1,4 @@ | |||
| {{define "form/dokodemo"}} | ||||
| {{define "form/tunnel"}} | ||||
| <a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }"> | ||||
|     <a-form-item label='{{ i18n "pages.inbounds.targetAddress"}}'> | ||||
|         <a-input v-model.trim="inbound.settings.address"></a-input> | ||||
|  |  | |||
|  | @ -1,4 +1,4 @@ | |||
| {{define "form/socks"}} | ||||
| {{define "form/mixed"}} | ||||
| <a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }"> | ||||
|   <a-form-item label='{{ i18n "pages.inbounds.enable" }} UDP'> | ||||
|     <a-switch v-model="inbound.settings.udp"></a-switch> | ||||
|  | @ -15,7 +15,7 @@ | |||
|         <td width="45%">{{ i18n "username" }}</td> | ||||
|         <td width="45%">{{ i18n "password" }}</td> | ||||
|         <td> | ||||
|           <a-button icon="plus" size="small" @click="inbound.settings.addAccount(new Inbound.SocksSettings.SocksAccount())"></a-button> | ||||
|           <a-button icon="plus" size="small" @click="inbound.settings.addAccount(new Inbound.MixedSettings.SocksAccount())"></a-button> | ||||
|         </td> | ||||
|       </tr> | ||||
|     </table> | ||||
|  |  | |||
|  | @ -22,7 +22,6 @@ | |||
|   <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> | ||||
|  | @ -31,17 +30,17 @@ | |||
|       <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-input v-model="inbound.settings.encryption"></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-button danger @click="clearVlessEnc">Clear</a-button> | ||||
|       </a-space> | ||||
|     </a-form-item> | ||||
|   </a-form> | ||||
| </template> | ||||
| <template v-if="inbound.isTcp && !inbound.settings.encryption"> | ||||
| <template v-if="inbound.isTcp && !inbound.settings.selectedAuth"> | ||||
|   <a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }"> | ||||
|     <a-form-item label="Fallbacks"> | ||||
|       <a-button icon="plus" type="primary" size="small" @click="inbound.settings.addFallback()"></a-button> | ||||
|  |  | |||
|  | @ -12,8 +12,8 @@ | |||
|             <a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option> | ||||
|         </a-select> | ||||
|     </a-form-item> | ||||
|     <a-form-item label='Dest (Target)'> | ||||
|         <a-input v-model.trim="inbound.stream.reality.dest"></a-input> | ||||
|     <a-form-item label='Target'> | ||||
|         <a-input v-model.trim="inbound.stream.reality.target"></a-input> | ||||
|     </a-form-item> | ||||
|     <a-form-item label='SNI'> | ||||
|         <a-input v-model.trim="inbound.stream.reality.serverNames"></a-input> | ||||
|  | @ -48,7 +48,10 @@ | |||
|         <a-textarea v-model="inbound.stream.reality.privateKey"></a-textarea> | ||||
|     </a-form-item> | ||||
|     <a-form-item label=" "> | ||||
|         <a-space> | ||||
|             <a-button type="primary" icon="import" @click="getNewX25519Cert">Get New Cert</a-button> | ||||
|             <a-button danger @click="clearX25519Cert">Clear</a-button> | ||||
|         </a-space> | ||||
|     </a-form-item> | ||||
|     <a-form-item label="mldsa65 Seed"> | ||||
|         <a-textarea v-model="inbound.stream.reality.mldsa65Seed"></a-textarea> | ||||
|  | @ -57,7 +60,10 @@ | |||
|         <a-textarea v-model="inbound.stream.reality.settings.mldsa65Verify"></a-textarea> | ||||
|     </a-form-item> | ||||
|     <a-form-item label=" "> | ||||
|         <a-space> | ||||
|             <a-button type="primary" icon="import" @click="getNewmldsa65">Get New Seed</a-button> | ||||
|             <a-button danger @click="clearMldsa65">Clear</a-button> | ||||
|         </a-space> | ||||
|     </a-form-item> | ||||
| </template> | ||||
| {{end}} | ||||
|  | @ -5,13 +5,13 @@ | |||
|   <a-form-item label='{{ i18n "security" }}'> | ||||
|     <a-radio-group v-model="inbound.stream.security" button-style="solid"> | ||||
|       <a-radio-button value="none">{{ i18n "none" }}</a-radio-button> | ||||
|       <a-radio-button v-if="inbound.canEnableReality() && !inbound.settings.encryption" value="reality">Reality</a-radio-button> | ||||
|       <a-radio-button v-if="!inbound.settings.encryption" value="tls">TLS</a-radio-button> | ||||
|       <a-radio-button v-if="inbound.canEnableReality()" value="reality">Reality</a-radio-button> | ||||
|       <a-radio-button value="tls">TLS</a-radio-button> | ||||
|     </a-radio-group> | ||||
|   </a-form-item> | ||||
| 
 | ||||
|   <!-- tls settings --> | ||||
|   <template v-if="inbound.stream.isTls && !inbound.settings.encryption"> | ||||
|   <template v-if="inbound.stream.isTls"> | ||||
|     <a-form-item label="SNI" placeholder="Server Name Indication"> | ||||
|       <a-input v-model.trim="inbound.stream.tls.sni"></a-input> | ||||
|     </a-form-item> | ||||
|  | @ -116,12 +116,15 @@ | |||
|         </a-select> | ||||
|     </a-form-item> | ||||
|     <a-form-item label=" "> | ||||
|       <a-space> | ||||
|         <a-button type="primary" icon="import" @click="getNewEchCert">Get New ECH Cert</a-button> | ||||
|         <a-button danger @click="clearEchCert">Clear</a-button> | ||||
|       </a-space> | ||||
|     </a-form-item> | ||||
|   </template> | ||||
| 
 | ||||
|   <!-- reality settings --> | ||||
|   <template v-if="inbound.stream.isReality && !inbound.settings.encryption"> | ||||
|   <template v-if="inbound.stream.isReality"> | ||||
|     {{template "form/realitySettings"}} | ||||
|   </template> | ||||
| </a-form> | ||||
|  |  | |||
|  | @ -836,7 +836,7 @@ | |||
|             }, | ||||
|             async getDBInbounds() { | ||||
|                 this.refreshing = true; | ||||
|                 const msg = await HttpUtil.post('/panel/inbound/list'); | ||||
|                 const msg = await HttpUtil.get('/panel/api/inbounds/list'); | ||||
|                 if (!msg.success) { | ||||
|                     this.refreshing = false; | ||||
|                     return; | ||||
|  | @ -851,7 +851,7 @@ | |||
|                 }, 500); | ||||
|             }, | ||||
|             async getOnlineUsers() { | ||||
|                 const msg = await HttpUtil.post('/panel/inbound/onlines'); | ||||
|                 const msg = await HttpUtil.post('/panel/api/inbounds/onlines'); | ||||
|                 if (!msg.success) { | ||||
|                     return; | ||||
|                 } | ||||
|  | @ -1106,7 +1106,7 @@ | |||
|                     streamSettings: baseInbound.stream.toString(), | ||||
|                     sniffing: baseInbound.sniffing.toString(), | ||||
|                 }; | ||||
|                 await this.submit('/panel/inbound/add', data, inModal); | ||||
|                 await this.submit('/panel/api/inbounds/add', data, inModal); | ||||
|             }, | ||||
|             openAddInbound() { | ||||
|                 inModal.show({ | ||||
|  | @ -1156,7 +1156,7 @@ | |||
|                 } | ||||
|                 data.sniffing = inbound.sniffing.toString(); | ||||
| 
 | ||||
|                 await this.submit('/panel/inbound/add', data, inModal); | ||||
|                 await this.submit('/panel/api/inbounds/add', data, inModal); | ||||
|             }, | ||||
|             async updateInbound(inbound, dbInbound) { | ||||
|                 const data = { | ||||
|  | @ -1180,7 +1180,7 @@ | |||
|                 } | ||||
|                 data.sniffing = inbound.sniffing.toString(); | ||||
| 
 | ||||
|                 await this.submit(`/panel/inbound/update/${dbInbound.id}`, data, inModal); | ||||
|                 await this.submit(`/panel/api/inbounds/update/${dbInbound.id}`, data, inModal); | ||||
|             }, | ||||
|             openAddClient(dbInboundId) { | ||||
|                 dbInbound = this.dbInbounds.find(row => row.id === dbInboundId); | ||||
|  | @ -1235,14 +1235,14 @@ | |||
|                     id: dbInboundId, | ||||
|                     settings: '{"clients": [' + clients.toString() + ']}', | ||||
|                 }; | ||||
|                 await this.submit(`/panel/inbound/addClient`, data, modal); | ||||
|                 await this.submit(`/panel/api/inbounds/addClient`, data, modal); | ||||
|             }, | ||||
|             async updateClient(client, dbInboundId, clientId) { | ||||
|                 const data = { | ||||
|                     id: dbInboundId, | ||||
|                     settings: '{"clients": [' + client.toString() + ']}', | ||||
|                 }; | ||||
|                 await this.submit(`/panel/inbound/updateClient/${clientId}`, data, clientModal); | ||||
|                 await this.submit(`/panel/api/inbounds/updateClient/${clientId}`, data, clientModal); | ||||
|             }, | ||||
|             resetTraffic(dbInboundId) { | ||||
|                 dbInbound = this.dbInbounds.find(row => row.id === dbInboundId); | ||||
|  | @ -1267,7 +1267,7 @@ | |||
|                     class: themeSwitcher.currentTheme, | ||||
|                     okText: '{{ i18n "delete"}}', | ||||
|                     cancelText: '{{ i18n "cancel"}}', | ||||
|                     onOk: () => this.submit('/panel/inbound/del/' + dbInboundId), | ||||
|                     onOk: () => this.submit('/panel/api/inbounds/del/' + dbInboundId), | ||||
|                 }); | ||||
|             }, | ||||
|             delClient(dbInboundId, client,confirmation = true) { | ||||
|  | @ -1280,10 +1280,10 @@ | |||
|                         class: themeSwitcher.currentTheme, | ||||
|                         okText: '{{ i18n "delete"}}', | ||||
|                         cancelText: '{{ i18n "cancel"}}', | ||||
|                         onOk: () => this.submit(`/panel/inbound/${dbInboundId}/delClient/${clientId}`), | ||||
|                         onOk: () => this.submit(`/panel/api/inbounds/${dbInboundId}/delClient/${clientId}`), | ||||
|                     }); | ||||
|                 } else { | ||||
|                     this.submit(`/panel/inbound/${dbInboundId}/delClient/${clientId}`); | ||||
|                     this.submit(`/panel/api/inbounds/${dbInboundId}/delClient/${clientId}`); | ||||
|                 } | ||||
|             }, | ||||
|             getSubGroupClients(dbInbounds, currentClient) { | ||||
|  | @ -1362,7 +1362,7 @@ | |||
|             switchEnable(dbInboundId,state) { | ||||
|               dbInbound = this.dbInbounds.find(row => row.id === dbInboundId); | ||||
|               dbInbound.enable = state; | ||||
|               this.submit(`/panel/inbound/update/${dbInboundId}`, dbInbound); | ||||
|               this.submit(`/panel/api/inbounds/update/${dbInboundId}`, dbInbound); | ||||
|             }, | ||||
|             async switchEnableClient(dbInboundId, client) { | ||||
|                 this.loading() | ||||
|  | @ -1392,10 +1392,10 @@ | |||
|                         class: themeSwitcher.currentTheme, | ||||
|                         okText: '{{ i18n "reset"}}', | ||||
|                         cancelText: '{{ i18n "cancel"}}', | ||||
|                         onOk: () => this.submit('/panel/inbound/' + dbInboundId + '/resetClientTraffic/' + client.email), | ||||
|                         onOk: () => this.submit('/panel/api/inbounds/' + dbInboundId + '/resetClientTraffic/' + client.email), | ||||
|                     }) | ||||
|                 } else { | ||||
|                     this.submit('/panel/inbound/' + dbInboundId + '/resetClientTraffic/' + client.email); | ||||
|                     this.submit('/panel/api/inbounds/' + dbInboundId + '/resetClientTraffic/' + client.email); | ||||
|                 } | ||||
|             }, | ||||
|             resetAllTraffic() { | ||||
|  | @ -1405,7 +1405,7 @@ | |||
|                     class: themeSwitcher.currentTheme, | ||||
|                     okText: '{{ i18n "reset"}}', | ||||
|                     cancelText: '{{ i18n "cancel"}}', | ||||
|                     onOk: () => this.submit('/panel/inbound/resetAllTraffics'), | ||||
|                     onOk: () => this.submit('/panel/api/inbounds/resetAllTraffics'), | ||||
|                 }); | ||||
|             }, | ||||
|             resetAllClientTraffics(dbInboundId) { | ||||
|  | @ -1415,7 +1415,7 @@ | |||
|                     class: themeSwitcher.currentTheme, | ||||
|                     okText: '{{ i18n "reset"}}', | ||||
|                     cancelText: '{{ i18n "cancel"}}', | ||||
|                     onOk: () => this.submit('/panel/inbound/resetAllClientTraffics/' + dbInboundId), | ||||
|                     onOk: () => this.submit('/panel/api/inbounds/resetAllClientTraffics/' + dbInboundId), | ||||
|                 }) | ||||
|             }, | ||||
|             delDepletedClients(dbInboundId) { | ||||
|  | @ -1425,7 +1425,7 @@ | |||
|                     class: themeSwitcher.currentTheme, | ||||
|                     okText: '{{ i18n "delete"}}', | ||||
|                     cancelText: '{{ i18n "cancel"}}', | ||||
|                     onOk: () => this.submit('/panel/inbound/delDepletedClients/' + dbInboundId), | ||||
|                     onOk: () => this.submit('/panel/api/inbounds/delDepletedClients/' + dbInboundId), | ||||
|                 }) | ||||
|             }, | ||||
|             isExpiry(dbInbound, index) { | ||||
|  | @ -1551,7 +1551,7 @@ | |||
|                     value: '', | ||||
|                     okText: '{{ i18n "pages.inbounds.import" }}', | ||||
|                     confirm: async (dbInboundText) => { | ||||
|                         await this.submit('/panel/inbound/import', {data: dbInboundText}, promptModal); | ||||
|                         await this.submit('/panel/api/inbounds/import', {data: dbInboundText}, promptModal); | ||||
|                     }, | ||||
|                 }); | ||||
|             }, | ||||
|  |  | |||
|  | @ -746,7 +746,7 @@ ${dateTime} | |||
|             }, | ||||
|             async getStatus() { | ||||
|                 try { | ||||
|                     const msg = await HttpUtil.post('/server/status'); | ||||
|                     const msg = await HttpUtil.get('/panel/api/server/status'); | ||||
|                     if (msg.success) { | ||||
|                         if (!this.loadingStates.fetched) { | ||||
|                             this.loadingStates.fetched = true; | ||||
|  | @ -763,7 +763,7 @@ ${dateTime} | |||
|             }, | ||||
|             async openSelectV2rayVersion() { | ||||
|                 this.loading(true); | ||||
|                 const msg = await HttpUtil.post('server/getXrayVersion'); | ||||
|                 const msg = await HttpUtil.get('/panel/api/server/getXrayVersion'); | ||||
|                 this.loading(false); | ||||
|                 if (!msg.success) { | ||||
|                     return; | ||||
|  | @ -780,7 +780,7 @@ ${dateTime} | |||
|                     onOk: async () => { | ||||
|                         versionModal.hide(); | ||||
|                         this.loading(true, '{{ i18n "pages.index.dontRefresh"}}'); | ||||
|                         await HttpUtil.post(`/server/installXray/${version}`); | ||||
|                         await HttpUtil.post(`/panel/api/server/installXray/${version}`); | ||||
|                         this.loading(false); | ||||
|                     }, | ||||
|                 }); | ||||
|  | @ -799,8 +799,8 @@ ${dateTime} | |||
|                         versionModal.hide(); | ||||
|                         this.loading(true, '{{ i18n "pages.index.dontRefresh"}}'); | ||||
|                         const url = isSingleFile | ||||
|                             ? `/server/updateGeofile/${fileName}`  | ||||
|                             : `/server/updateGeofile`; | ||||
|                             ? `/panel/api/server/updateGeofile/${fileName}` | ||||
|                             : `/panel/api/server/updateGeofile`; | ||||
|                         await HttpUtil.post(url); | ||||
|                         this.loading(false); | ||||
|                     }, | ||||
|  | @ -808,7 +808,7 @@ ${dateTime} | |||
|             }, | ||||
|             async stopXrayService() { | ||||
|                 this.loading(true); | ||||
|                 const msg = await HttpUtil.post('server/stopXrayService'); | ||||
|                 const msg = await HttpUtil.post('/panel/api/server/stopXrayService'); | ||||
|                 this.loading(false); | ||||
|                 if (!msg.success) { | ||||
|                     return; | ||||
|  | @ -816,7 +816,7 @@ ${dateTime} | |||
|             }, | ||||
|             async restartXrayService() { | ||||
|                 this.loading(true); | ||||
|                 const msg = await HttpUtil.post('server/restartXrayService'); | ||||
|                 const msg = await HttpUtil.post('/panel/api/server/restartXrayService'); | ||||
|                 this.loading(false); | ||||
|                 if (!msg.success) { | ||||
|                     return; | ||||
|  | @ -824,7 +824,7 @@ ${dateTime} | |||
|             }, | ||||
|             async openLogs(){ | ||||
|                 logModal.loading = true; | ||||
|                 const msg = await HttpUtil.post('server/logs/'+logModal.rows,{level: logModal.level, syslog: logModal.syslog}); | ||||
|                 const msg = await HttpUtil.post('/panel/api/server/logs/'+logModal.rows,{level: logModal.level, syslog: logModal.syslog}); | ||||
|                 if (!msg.success) { | ||||
|                     return; | ||||
|                 } | ||||
|  | @ -834,7 +834,7 @@ ${dateTime} | |||
|             }, | ||||
|             async openXrayLogs(){ | ||||
|               xraylogModal.loading = true; | ||||
|                 const msg = await HttpUtil.post('server/xraylogs/'+xraylogModal.rows,{filter: xraylogModal.filter, showDirect: xraylogModal.showDirect, showBlocked: xraylogModal.showBlocked, showProxy: xraylogModal.showProxy}); | ||||
|                 const msg = await HttpUtil.post('/panel/api/server/xraylogs/'+xraylogModal.rows,{filter: xraylogModal.filter, showDirect: xraylogModal.showDirect, showBlocked: xraylogModal.showBlocked, showProxy: xraylogModal.showProxy}); | ||||
|                 if (!msg.success) { | ||||
|                     return; | ||||
|                 } | ||||
|  | @ -844,7 +844,7 @@ ${dateTime} | |||
|             }, | ||||
|             async openConfig() { | ||||
|                 this.loading(true); | ||||
|                 const msg = await HttpUtil.post('server/getConfigJson'); | ||||
|                 const msg = await HttpUtil.get('/panel/api/server/getConfigJson'); | ||||
|                 this.loading(false); | ||||
|                 if (!msg.success) { | ||||
|                     return; | ||||
|  | @ -855,7 +855,7 @@ ${dateTime} | |||
|               backupModal.show(); | ||||
|             }, | ||||
|             exportDatabase() { | ||||
|                 window.location = basePath + 'server/getDb'; | ||||
|                 window.location = basePath + 'panel/api/server/getDb'; | ||||
|             }, | ||||
|             importDatabase() { | ||||
|                 const fileInput = document.createElement('input'); | ||||
|  | @ -868,7 +868,7 @@ ${dateTime} | |||
|                         formData.append('db', dbFile); | ||||
|                         backupModal.hide(); | ||||
|                         this.loading(true); | ||||
|                         const uploadMsg = await HttpUtil.post('server/importDB', formData, { | ||||
|                         const uploadMsg = await HttpUtil.post('/panel/api/server/importDB', formData, { | ||||
|                             headers: { | ||||
|                                 'Content-Type': 'multipart/form-data', | ||||
|                             } | ||||
|  |  | |||
|  | @ -121,7 +121,7 @@ | |||
|         }, | ||||
|         methods: { | ||||
|             async getDBClientIps(email) { | ||||
|                 const msg = await HttpUtil.post(`/panel/inbound/clientIps/${email}`); | ||||
|                 const msg = await HttpUtil.post(`/panel/api/inbounds/clientIps/${email}`); | ||||
|                 if (!msg.success) { | ||||
|                     document.getElementById("clientIPs").value = msg.obj; | ||||
|                     return; | ||||
|  | @ -139,7 +139,7 @@ | |||
|             }, | ||||
|             async clearDBClientIps(email) { | ||||
|                 try { | ||||
|                     const msg = await HttpUtil.post(`/panel/inbound/clearClientIps/${email}`); | ||||
|                     const msg = await HttpUtil.post(`/panel/api/inbounds/clearClientIps/${email}`); | ||||
|                     if (!msg.success) { | ||||
|                         return; | ||||
|                     } | ||||
|  | @ -156,7 +156,7 @@ | |||
|                     cancelText: '{{ i18n "cancel"}}', | ||||
|                     onOk: async () => { | ||||
|                         iconElement.disabled = true; | ||||
|                         const msg = await HttpUtil.postWithModal('/panel/inbound/' + dbInboundId + '/resetClientTraffic/' + email); | ||||
|                         const msg = await HttpUtil.postWithModal('/panel/api/inbounds/' + dbInboundId + '/resetClientTraffic/' + email); | ||||
|                         if (msg.success) { | ||||
|                             this.clientModal.clientStats.up = 0; | ||||
|                             this.clientModal.clientStats.down = 0; | ||||
|  |  | |||
|  | @ -360,7 +360,7 @@ | |||
|           <code>[[ link.link ]]</code> | ||||
|         </tr-info-row> | ||||
|       </template> | ||||
|       <table v-if="inbound.protocol == Protocols.DOKODEMO" class="tr-info-table"> | ||||
|       <table v-if="inbound.protocol == Protocols.TUNNEL" class="tr-info-table"> | ||||
|         <tr> | ||||
|           <th>{{ i18n "pages.inbounds.targetAddress" }}</th> | ||||
|           <th>{{ i18n "pages.inbounds.destinationPort" }}</th> | ||||
|  | @ -382,7 +382,7 @@ | |||
|           </td> | ||||
|         </tr> | ||||
|       </table> | ||||
|       <table v-if="dbInbound.isSocks" class="tr-info-table"> | ||||
|       <table v-if="dbInbound.isMixed" class="tr-info-table"> | ||||
|         <tr> | ||||
|           <th>{{ i18n "password" }} Auth</th> | ||||
|           <th>{{ i18n "pages.inbounds.enable" }} udp</th> | ||||
|  | @ -498,7 +498,7 @@ | |||
| </a-modal> | ||||
| <script> | ||||
|   function refreshIPs(email) { | ||||
|     return HttpUtil.post(`/panel/inbound/clientIps/${email}`).then((msg) => { | ||||
|     return HttpUtil.post(`/panel/api/inbounds/clientIps/${email}`).then((msg) => { | ||||
|       if (msg.success) { | ||||
|         try { | ||||
|           return JSON.parse(msg.obj).join(', '); | ||||
|  | @ -619,7 +619,7 @@ | |||
|           }); | ||||
|       }, | ||||
|       clearClientIps() { | ||||
|         HttpUtil.post(`/panel/inbound/clearClientIps/${this.infoModal.clientStats.email}`) | ||||
|         HttpUtil.post(`/panel/api/inbounds/clearClientIps/${this.infoModal.clientStats.email}`) | ||||
|           .then((msg) => { | ||||
|             if (!msg.success) { | ||||
|               return; | ||||
|  |  | |||
|  | @ -132,7 +132,7 @@ | |||
|             }, | ||||
|             async getNewX25519Cert() { | ||||
|                 inModal.loading(true); | ||||
|                 const msg = await HttpUtil.post('/server/getNewX25519Cert'); | ||||
|                 const msg = await HttpUtil.get('/panel/api/server/getNewX25519Cert'); | ||||
|                 inModal.loading(false); | ||||
|                 if (!msg.success) { | ||||
|                     return; | ||||
|  | @ -140,9 +140,13 @@ | |||
|                 inModal.inbound.stream.reality.privateKey = msg.obj.privateKey; | ||||
|                 inModal.inbound.stream.reality.settings.publicKey = msg.obj.publicKey; | ||||
|             }, | ||||
|             clearX25519Cert() { | ||||
|                 this.inbound.stream.reality.privateKey = ''; | ||||
|                 this.inbound.stream.reality.settings.publicKey = ''; | ||||
|             }, | ||||
|             async getNewmldsa65() { | ||||
|                 inModal.loading(true); | ||||
|                 const msg = await HttpUtil.post('/server/getNewmldsa65'); | ||||
|                 const msg = await HttpUtil.get('/panel/api/server/getNewmldsa65'); | ||||
|                 inModal.loading(false); | ||||
|                 if (!msg.success) { | ||||
|                     return; | ||||
|  | @ -150,9 +154,13 @@ | |||
|                 inModal.inbound.stream.reality.mldsa65Seed = msg.obj.seed; | ||||
|                 inModal.inbound.stream.reality.settings.mldsa65Verify = msg.obj.verify; | ||||
|             }, | ||||
|             clearMldsa65() { | ||||
|                 this.inbound.stream.reality.mldsa65Seed = ''; | ||||
|                 this.inbound.stream.reality.settings.mldsa65Verify = ''; | ||||
|             }, | ||||
|             async getNewEchCert() { | ||||
|                 inModal.loading(true); | ||||
|                 const msg = await HttpUtil.post('/server/getNewEchCert', { sni: inModal.inbound.stream.tls.sni }); | ||||
|                 const msg = await HttpUtil.post('/panel/api/server/getNewEchCert', { sni: inModal.inbound.stream.tls.sni }); | ||||
|                 inModal.loading(false); | ||||
|                 if (!msg.success) { | ||||
|                     return; | ||||
|  | @ -160,9 +168,13 @@ | |||
|                 inModal.inbound.stream.tls.echServerKeys = msg.obj.echServerKeys; | ||||
|                 inModal.inbound.stream.tls.settings.echConfigList = msg.obj.echConfigList; | ||||
|             }, | ||||
|             clearEchCert() { | ||||
|                 this.inbound.stream.tls.echServerKeys = ''; | ||||
|                 this.inbound.stream.tls.settings.echConfigList = ''; | ||||
|             }, | ||||
|             async getNewVlessEnc() { | ||||
|                 inModal.loading(true); | ||||
|                 const msg = await HttpUtil.post('/server/getNewVlessEnc'); | ||||
|                 const msg = await HttpUtil.get('/panel/api/server/getNewVlessEnc'); | ||||
|                 inModal.loading(false); | ||||
| 
 | ||||
|                 if (!msg.success) { | ||||
|  | @ -181,7 +193,7 @@ | |||
|                 inModal.inbound.settings.decryption = block.decryption; | ||||
|                 inModal.inbound.settings.encryption = block.encryption; | ||||
|             }, | ||||
|             clearKeys() { | ||||
|             clearVlessEnc() { | ||||
|                 this.inbound.settings.decryption = 'none'; | ||||
|                 this.inbound.settings.encryption = 'none'; | ||||
|                 this.inbound.settings.selectedAuth = undefined; | ||||
|  |  | |||
|  | @ -151,7 +151,7 @@ | |||
|     methods: { | ||||
|       async getStatus() { | ||||
|         try { | ||||
|           const msg = await HttpUtil.post('/server/status'); | ||||
|           const msg = await HttpUtil.get('/panel/api/server/status'); | ||||
|           if (msg.success) { | ||||
|             this.serverStatus = msg.obj; | ||||
|           } | ||||
|  |  | |||
|  | @ -420,7 +420,7 @@ | |||
|       }, | ||||
|       async restartXray() { | ||||
|         this.loading(true); | ||||
|         const msg = await HttpUtil.post("server/restartXrayService"); | ||||
|         const msg = await HttpUtil.post("/panel/api/server/restartXrayService"); | ||||
|         this.loading(false); | ||||
|         if (msg.success) { | ||||
|           await PromiseUtil.sleep(500); | ||||
|  | @ -572,7 +572,7 @@ | |||
|             serverObj = o.settings.vnext; | ||||
|             break; | ||||
|           case Protocols.HTTP: | ||||
|           case Protocols.Socks: | ||||
|           case Protocols.Mixed: | ||||
|           case Protocols.Shadowsocks: | ||||
|           case Protocols.Trojan: | ||||
|             serverObj = o.settings.servers; | ||||
|  |  | |||
|  | @ -19,7 +19,7 @@ | |||
|       "tag": "api", | ||||
|       "listen": "127.0.0.1", | ||||
|       "port": 62789, | ||||
|       "protocol": "dokodemo-door", | ||||
|       "protocol": "tunnel", | ||||
|       "settings": { | ||||
|         "address": "127.0.0.1" | ||||
|       } | ||||
|  |  | |||
|  | @ -24,6 +24,7 @@ import ( | |||
| 	"x-ui/util/sys" | ||||
| 	"x-ui/xray" | ||||
| 
 | ||||
| 	"github.com/google/uuid" | ||||
| 	"github.com/shirou/gopsutil/v4/cpu" | ||||
| 	"github.com/shirou/gopsutil/v4/disk" | ||||
| 	"github.com/shirou/gopsutil/v4/host" | ||||
|  | @ -872,12 +873,6 @@ func (s *ServerService) GetNewEchCert(sni string) (interface{}, error) { | |||
| 	}, 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 | ||||
|  | @ -887,37 +882,70 @@ func (s *ServerService) GetNewVlessEnc() (any, error) { | |||
| 	} | ||||
| 
 | ||||
| 	lines := strings.Split(out.String(), "\n") | ||||
| 
 | ||||
| 	var blocks []AuthBlock | ||||
| 	var current *AuthBlock | ||||
| 	var auths []map[string]string | ||||
| 	var current map[string]string | ||||
| 
 | ||||
| 	for _, line := range lines { | ||||
| 		line = strings.TrimSpace(line) | ||||
| 		if strings.HasPrefix(line, "Authentication:") { | ||||
| 			if current != nil { | ||||
| 				blocks = append(blocks, *current) | ||||
| 				auths = append(auths, current) | ||||
| 			} | ||||
| 			current = map[string]string{ | ||||
| 				"label": strings.TrimSpace(strings.TrimPrefix(line, "Authentication:")), | ||||
| 			} | ||||
| 			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 | ||||
| 				} | ||||
| 				current[key] = val | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	if current != nil { | ||||
| 		blocks = append(blocks, *current) | ||||
| 		auths = append(auths, current) | ||||
| 	} | ||||
| 
 | ||||
| 	return map[string]any{ | ||||
| 		"auths": blocks, | ||||
| 		"auths": auths, | ||||
| 	}, nil | ||||
| } | ||||
| 
 | ||||
| func (s *ServerService) GetNewUUID() (map[string]string, error) { | ||||
| 	newUUID, err := uuid.NewRandom() | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("failed to generate UUID: %w", err) | ||||
| 	} | ||||
| 
 | ||||
| 	return map[string]string{ | ||||
| 		"uuid": newUUID.String(), | ||||
| 	}, nil | ||||
| } | ||||
| 
 | ||||
| func (s *ServerService) GetNewmlkem768() (any, error) { | ||||
| 	// Run the command
 | ||||
| 	cmd := exec.Command(xray.GetBinaryPath(), "mlkem768") | ||||
| 	var out bytes.Buffer | ||||
| 	cmd.Stdout = &out | ||||
| 	err := cmd.Run() | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	lines := strings.Split(out.String(), "\n") | ||||
| 
 | ||||
| 	SeedLine := strings.Split(lines[0], ":") | ||||
| 	ClientLine := strings.Split(lines[1], ":") | ||||
| 
 | ||||
| 	seed := strings.TrimSpace(SeedLine[1]) | ||||
| 	client := strings.TrimSpace(ClientLine[1]) | ||||
| 
 | ||||
| 	keyPair := map[string]any{ | ||||
| 		"seed":   seed, | ||||
| 		"client": client, | ||||
| 	} | ||||
| 
 | ||||
| 	return keyPair, nil | ||||
| } | ||||
|  |  | |||
|  | @ -2129,8 +2129,8 @@ func (t *Tgbot) getInboundsAddClient() (*telego.InlineKeyboardMarkup, error) { | |||
| 	} | ||||
| 
 | ||||
| 	excludedProtocols := map[model.Protocol]bool{ | ||||
| 		model.DOKODEMO:  true, | ||||
| 		model.Socks:     true, | ||||
| 		model.Tunnel:    true, | ||||
| 		model.Mixed:     true, | ||||
| 		model.WireGuard: true, | ||||
| 		model.HTTP:      true, | ||||
| 	} | ||||
|  |  | |||
|  | @ -176,7 +176,7 @@ func (s *Server) initRouter() (*gin.Engine, error) { | |||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	engine.Use(gzip.Gzip(gzip.DefaultCompression, gzip.WithExcludedPaths([]string{basePath + "panel/API/"}))) | ||||
| 	engine.Use(gzip.Gzip(gzip.DefaultCompression, gzip.WithExcludedPaths([]string{basePath + "panel/api/"}))) | ||||
| 	assetsBasePath := basePath + "assets/" | ||||
| 
 | ||||
| 	store := cookie.NewStore(secret) | ||||
|  |  | |||
							
								
								
									
										
											BIN
										
									
								
								windows_files/SSL/Win64OpenSSL_Light-3_5_2.exe
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								windows_files/SSL/Win64OpenSSL_Light-3_5_2.exe
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										13
									
								
								windows_files/readme.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								windows_files/readme.txt
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,13 @@ | |||
| you can't install fail2ban on windows | ||||
| we don't have bash menu for windows | ||||
| if you forgot your password you need to check your database with https://sqlitebrowser.org/ | ||||
| the app need to be open all the time | ||||
| 
 | ||||
| default setting: | ||||
| http://localhost:2053/ | ||||
| user: admin | ||||
| pass: admin | ||||
| port: 2053 | ||||
| 
 | ||||
| 
 | ||||
| openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout localhost.key -out localhost.crt | ||||
		Loading…
	
		Reference in a new issue
	
	 Sanaei
						Sanaei