Browse Source

Merge pull request #266 from hamid-gh98/main

[Support] change settings by different items in panel
Ho3ein 1 year ago
parent
commit
dc7dbae14a

+ 4 - 4
.github/workflows/release.yml

@@ -14,7 +14,7 @@ jobs:
       - name: Set up Go
         uses: actions/[email protected]
         with:
-          go-version: 'stable'
+          go-version: "stable"
       - name: build linux amd64 version
         run: |
           CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -o xui-release -v main.go
@@ -28,7 +28,7 @@ jobs:
           cd bin
           wget https://github.com/mhsanaei/Xray-core/releases/latest/download/Xray-linux-64.zip
           unzip Xray-linux-64.zip
-          rm -f Xray-linux-64.zip geoip.dat geosite.dat
+          rm -f Xray-linux-64.zip geoip.dat geosite.dat iran.dat
           wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
           wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat
           wget https://github.com/bootmortis/iran-hosted-domains/releases/latest/download/iran.dat
@@ -54,7 +54,7 @@ jobs:
       - name: Set up Go
         uses: actions/[email protected]
         with:
-          go-version: 'stable'
+          go-version: "stable"
       - name: build linux arm64 version
         run: |
           sudo apt-get update
@@ -70,7 +70,7 @@ jobs:
           cd bin
           wget https://github.com/mhsanaei/xray-core/releases/latest/download/Xray-linux-arm64-v8a.zip
           unzip Xray-linux-arm64-v8a.zip
-          rm -f Xray-linux-arm64-v8a.zip geoip.dat geosite.dat
+          rm -f Xray-linux-arm64-v8a.zip geoip.dat geosite.dat iran.dat
           wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
           wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat
           wget https://github.com/bootmortis/iran-hosted-domains/releases/latest/download/iran.dat

+ 2 - 0
.gitignore

@@ -1,5 +1,6 @@
 .idea
 tmp
+backup/
 bin/
 dist/
 x-ui-*.tar.gz
@@ -9,4 +10,5 @@ x-ui-*.tar.gz
 main
 release/
 access.log
+error.log
 .cache

+ 75 - 30
README.md

@@ -1,14 +1,14 @@
 # 3x-ui
+
 [![](https://img.shields.io/github/v/release/mhsanaei/3x-ui.svg)](https://github.com/MHSanaei/3x-ui/releases)
 [![](https://img.shields.io/github/actions/workflow/status/mhsanaei/3x-ui/release.yml.svg)](#)
 [![GO Version](https://img.shields.io/github/go-mod/go-version/mhsanaei/3x-ui.svg)](#)
 [![Downloads](https://img.shields.io/github/downloads/mhsanaei/3x-ui/total.svg)](#)
 [![License](https://img.shields.io/badge/license-GPL%20V3-blue.svg?longCache=true)](https://www.gnu.org/licenses/gpl-3.0.en.html)
 
-
 > **Disclaimer: This project is only for personal learning and communication, please do not use it for illegal purposes, please do not use it in a production environment**
 
-**If you think this project is helpful to you, you may wish to give a** :star2: 
+**If you think this project is helpful to you, you may wish to give a** :star2:
 
 xray panel supporting multi-protocol, **Multi-lang (English,Farsi,Chinese)**
 
@@ -17,20 +17,23 @@ xray panel supporting multi-protocol, **Multi-lang (English,Farsi,Chinese)**
 ```
 bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh)
 ```
+
 ## Install custom version
+
 To install your desired version you can add the version to the end of install command. Example for ver `v1.0.9`:
+
 ```
 bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) v1.0.9
 ```
+
 # SSL
+
 ```
 apt-get install certbot -y
 certbot certonly --standalone --agree-tos --register-unsafely-without-email -d yourdomain.com
 certbot renew --dry-run
 ```
 
-
-
 # Default settings
 
 - Port: 2053
@@ -38,18 +41,57 @@ certbot renew --dry-run
 - database path: /etc/x-ui/x-ui.db
 - xray config path: /usr/local/x-ui/bin/config.json
 
-before you set ssl on settings
-- http:// ip or domain:2053/xui
+Before you set ssl on settings
+
+- http://ip:2053/xui
+- http://domain:2053/xui
+
+After you set ssl on settings
 
-After you set ssl on settings 
 - https://yourdomain:2053/xui
 
-# Enable Traffic For Users:
+# Environment Variables
+
+| Variable       |                      Type                      | Default       |
+| -------------- | :--------------------------------------------: | :------------ |
+| XUI_LOG_LEVEL  | `"debug"` \| `"info"` \| `"warn"` \| `"error"` | `"info"`      |
+| XUI_DEBUG      |                   `boolean`                    | `false`       |
+| XUI_BIN_FOLDER |                    `string`                    | `"bin"`       |
+| XUI_DB_FOLDER  |                    `string`                    | `"/etc/x-ui"` |
+
+Example:
+
+```sh
+XUI_BIN_FOLDER="bin" XUI_DB_FOLDER="/etc/x-ui" go build main.go
+```
+
+# Xray Configurations:
 
 **copy and paste to xray Configuration :** (you don't need to do this if you have a fresh install)
-- [enable traffic](./media/enable-traffic.txt)
-- [enable traffic+block all IR IP address](./media/enable-traffic+block-IR-IP.txt)
-- [enable traffic+block all IR domain](./media/enable-traffic+block-IR-domain.txt)
+
+- [traffic](./media/configs/traffic.json)
+- [traffic + Block all Iran IP address](./media/configs/traffic+block-iran-ip.json)
+- [traffic + Block all Iran Domains](./media/configs/traffic+block-iran-domains.json)
+- [traffic + Block Ads + Use IPv4 for Google](./media/configs/traffic+block-ads+ipv4-google.json)
+- [traffic + Block Ads + Route Google + Netflix + Spotify + OpenAI (ChatGPT) to WARP](./media/configs/traffic+block-ads+warp.json)
+
+# [WARP Configuration](https://github.com/fscarmen/warp) (Optional)
+
+If you want to use routing to WARP follow steps as below:
+
+1. Install WARP on **socks proxy mode**:
+
+   ```sh
+   curl -fsSL https://gist.githubusercontent.com/hamid-gh98/dc5dd9b0cc5b0412af927b1ccdb294c7/raw/install_warp_proxy.sh | bash
+   ```
+
+2. Turn on the config you need in panel or [Copy and paste this file to Xray Configuration](./media/configs/traffic+block-ads+warp.json)
+
+   Config Features:
+
+   - Block Ads
+   - Route Google + Netflix + Spotify + OpenAI (ChatGPT) to WARP
+   - Fix Google 403 error
 
 # Features
 
@@ -64,7 +106,8 @@ After you set ssl on settings
 - Support https access panel (self-provided domain name + ssl certificate)
 - Support one-click SSL certificate application and automatic renewal
 - For more advanced configuration items, please refer to the panel
-- fix api routes (user setting will create with api)
+- Fix api routes (user setting will create with api)
+- Support to change configs by different items provided in panel
 
 # Tg robot use
 
@@ -81,8 +124,8 @@ Set the robot-related parameters in the panel background, including:
 
 Reference syntax:
 
-- 30 * * * * * //Notify at the 30s of each point
-- 0 */10 * * * * //Notify at the first second of each 10 minutes
+- 30 \* \* \* \* \* //Notify at the 30s of each point
+- 0 \*/10 \* \* \* \* //Notify at the first second of each 10 minutes
 - @hourly // hourly notification
 - @daily // Daily notification (00:00 in the morning)
 - @every 8h // notify every 8 hours
@@ -102,33 +145,34 @@ Reference syntax:
 - Check depleted users
 - Receive backup by request and in periodic reports
 
-
 ## API routes
 
 - `/login` with `PUSH` user data: `{username: '', password: ''}` for login
 - `/xui/API/inbounds` base for following actions:
 
-| Method | Path | Action |
-| ------------- | ------------- | ------------- |
-| GET | "/list" | Get all inbounds |
-| GET | "/get/:id" | Get inbound with inbound.id |
-| POST | "/add" | Add inbound |
-| POST | "/del/:id" | Delete Inbound |
-| POST | "/update/:id" | Update Inbound |
-| POST | "/clientIps/:email" | Client Ip address |
-| POST | "/clearClientIps/:email" | Clear Client Ip address |
-| POST | "/addClient/" | Add Client to inbound |
-| POST | "/delClient/:email" | Delete Client |
-| POST | "/updateClient/:index" | Update Client |
-| POST | "/:id/resetClientTraffic/:email" | Reset Client's Traffic |
-| POST | "/resetAllTraffics" | Reset traffics of all inbounds |
-| POST | "/resetAllClientTraffics/:id" | Reset traffics of all clients in an inbound |
+| Method | Path                               | Action                                      |
+| :----: | ---------------------------------- | ------------------------------------------- |
+| `GET`  | `"/list"`                          | Get all inbounds                            |
+| `GET`  | `"/get/:id"`                       | Get inbound with inbound.id                 |
+| `POST` | `"/add"`                           | Add inbound                                 |
+| `POST` | `"/del/:id"`                       | Delete Inbound                              |
+| `POST` | `"/update/:id"`                    | Update Inbound                              |
+| `POST` | `"/clientIps/:email"`              | Client Ip address                           |
+| `POST` | `"/clearClientIps/:email"`         | Clear Client Ip address                     |
+| `POST` | `"/addClient/"`                    | Add Client to inbound                       |
+| `POST` | `"/delClient/:email"`              | Delete Client                               |
+| `POST` | `"/updateClient/:index"`           | Update Client                               |
+| `POST` | `"/:id/resetClientTraffic/:email"` | Reset Client's Traffic                      |
+| `POST` | `"/resetAllTraffics"`              | Reset traffics of all inbounds              |
+| `POST` | `"/resetAllClientTraffics/:id"`    | Reset traffics of all clients in an inbound |
 
 # A Special Thanks To
+
 - [alireza0](https://github.com/alireza0/)
 - [FranzKafkaYu](https://github.com/FranzKafkaYu)
 
 # Suggestion System
+
 - Ubuntu 20.04+
 - Debian 10+
 - CentOS 8+
@@ -137,6 +181,7 @@ Reference syntax:
 # Buy Me a Coffee
 
 [![](https://img.shields.io/badge/Wallet-USDT__TRC20-green.svg)](#)
+
 ```
 TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC
 ```

+ 88 - 0
media/configs/traffic+block-ads+ipv4-google.json

@@ -0,0 +1,88 @@
+{
+  "log": {
+    "loglevel": "warning",
+    "access": "./access.log",
+    "error": "./error.log"
+  },
+  "api": {
+    "tag": "api",
+    "services": ["HandlerService", "LoggerService", "StatsService"]
+  },
+  "inbounds": [
+    {
+      "tag": "api",
+      "listen": "127.0.0.1",
+      "port": 62789,
+      "protocol": "dokodemo-door",
+      "settings": {
+        "address": "127.0.0.1"
+      }
+    }
+  ],
+  "outbounds": [
+    {
+      "protocol": "freedom",
+      "settings": {}
+    },
+    {
+      "tag": "blocked",
+      "protocol": "blackhole",
+      "settings": {}
+    },
+    {
+      "tag": "IPv4",
+      "protocol": "freedom",
+      "settings": {
+        "domainStrategy": "UseIPv4"
+      }
+    }
+  ],
+  "policy": {
+    "levels": {
+      "0": {
+        "statsUserDownlink": true,
+        "statsUserUplink": true
+      }
+    },
+    "system": {
+      "statsInboundDownlink": true,
+      "statsInboundUplink": true
+    }
+  },
+  "routing": {
+    "domainStrategy": "IPIfNonMatch",
+    "rules": [
+      {
+        "type": "field",
+        "inboundTag": ["api"],
+        "outboundTag": "api"
+      },
+      {
+        "type": "field",
+        "outboundTag": "blocked",
+        "ip": ["geoip:private"]
+      },
+      {
+        "type": "field",
+        "outboundTag": "blocked",
+        "protocol": ["bittorrent"]
+      },
+      {
+        "type": "field",
+        "outboundTag": "blocked",
+        "domain": [
+          "geosite:category-ads-all",
+          "geosite:category-ads",
+          "geosite:google-ads",
+          "geosite:spotify-ads"
+        ]
+      },
+      {
+        "type": "field",
+        "outboundTag": "IPv4",
+        "domain": ["geosite:google"]
+      }
+    ]
+  },
+  "stats": {}
+}

+ 98 - 0
media/configs/traffic+block-ads+warp.json

@@ -0,0 +1,98 @@
+{
+  "log": {
+    "loglevel": "warning",
+    "access": "./access.log",
+    "error": "./error.log"
+  },
+  "api": {
+    "tag": "api",
+    "services": ["HandlerService", "LoggerService", "StatsService"]
+  },
+  "inbounds": [
+    {
+      "tag": "api",
+      "listen": "127.0.0.1",
+      "port": 62789,
+      "protocol": "dokodemo-door",
+      "settings": {
+        "address": "127.0.0.1"
+      }
+    }
+  ],
+  "outbounds": [
+    {
+      "protocol": "freedom",
+      "settings": {}
+    },
+    {
+      "tag": "blocked",
+      "protocol": "blackhole",
+      "settings": {}
+    },
+    {
+      "tag": "WARP",
+      "protocol": "socks",
+      "settings": {
+        "servers": [
+          {
+            "address": "127.0.0.1",
+            "port": 40000
+          }
+        ]
+      }
+    }
+  ],
+  "policy": {
+    "levels": {
+      "0": {
+        "statsUserDownlink": true,
+        "statsUserUplink": true
+      }
+    },
+    "system": {
+      "statsInboundDownlink": true,
+      "statsInboundUplink": true
+    }
+  },
+  "routing": {
+    "domainStrategy": "IPIfNonMatch",
+    "rules": [
+      {
+        "type": "field",
+        "inboundTag": ["api"],
+        "outboundTag": "api"
+      },
+      {
+        "type": "field",
+        "outboundTag": "blocked",
+        "ip": ["geoip:private"]
+      },
+      {
+        "type": "field",
+        "outboundTag": "blocked",
+        "protocol": ["bittorrent"]
+      },
+      {
+        "type": "field",
+        "outboundTag": "blocked",
+        "domain": [
+          "geosite:category-ads-all",
+          "geosite:category-ads",
+          "geosite:google-ads",
+          "geosite:spotify-ads"
+        ]
+      },
+      {
+        "type": "field",
+        "outboundTag": "WARP",
+        "domain": [
+          "geosite:google",
+          "geosite:netflix",
+          "geosite:spotify",
+          "geosite:openai"
+        ]
+      }
+    ]
+  },
+  "stats": {}
+}

+ 23 - 31
media/enable-traffic+block-IR-domain.txt → media/configs/traffic+block-iran-domains.json

@@ -1,25 +1,22 @@
 {
   "log": {
     "loglevel": "warning",
-    "access": "./access.log"
+    "access": "./access.log",
+    "error": "./error.log"
   },
   "api": {
-    "services": [
-      "HandlerService",
-      "LoggerService",
-      "StatsService"
-    ],
-    "tag": "api"
+    "tag": "api",
+    "services": ["HandlerService", "LoggerService", "StatsService"]
   },
   "inbounds": [
     {
+      "tag": "api",
       "listen": "127.0.0.1",
       "port": 62789,
       "protocol": "dokodemo-door",
       "settings": {
         "address": "127.0.0.1"
-      },
-      "tag": "api"
+      }
     }
   ],
   "outbounds": [
@@ -28,16 +25,16 @@
       "settings": {}
     },
     {
+      "tag": "blocked",
       "protocol": "blackhole",
-      "settings": {},
-      "tag": "blocked"
+      "settings": {}
     }
   ],
   "policy": {
     "levels": {
       "0": {
-        "statsUserUplink": true,
-        "statsUserDownlink": true
+        "statsUserDownlink": true,
+        "statsUserUplink": true
       }
     },
     "system": {
@@ -49,36 +46,31 @@
     "domainStrategy": "IPIfNonMatch",
     "rules": [
       {
-        "inboundTag": [
-          "api"
-        ],
-        "outboundTag": "api",
-        "type": "field"
+        "type": "field",
+        "inboundTag": ["api"],
+        "outboundTag": "api"
       },
       {
-        "ip": [
-          "geoip:private"
-        ],
+        "type": "field",
         "outboundTag": "blocked",
-        "type": "field"
+        "ip": ["geoip:private"]
       },
       {
+        "type": "field",
         "outboundTag": "blocked",
-        "protocol": [
-          "bittorrent"
-        ],
-        "type": "field"
+        "protocol": ["bittorrent"]
       },
       {
+        "type": "field",
         "outboundTag": "blocked",
         "domain": [
-          "regexp:.+.ir$",
+          "regexp:.*\\.ir$",
           "ext:iran.dat:ir",
-          "ext:iran.dat:other"
-        ],
-        "type": "field"
+          "ext:iran.dat:other",
+          "geosite:category-ir"
+        ]
       }
     ]
   },
   "stats": {}
-}
+}

+ 25 - 31
media/enable-traffic+block-IR-IP.txt → media/configs/traffic+block-iran-ip.json

@@ -1,25 +1,22 @@
 {
   "log": {
     "loglevel": "warning",
-    "access": "./access.log"
+    "access": "./access.log",
+    "error": "./error.log"
   },
   "api": {
-    "services": [
-      "HandlerService",
-      "LoggerService",
-      "StatsService"
-    ],
-    "tag": "api"
+    "tag": "api",
+    "services": ["HandlerService", "LoggerService", "StatsService"]
   },
   "inbounds": [
     {
+      "tag": "api",
       "listen": "127.0.0.1",
       "port": 62789,
       "protocol": "dokodemo-door",
       "settings": {
         "address": "127.0.0.1"
-      },
-      "tag": "api"
+      }
     }
   ],
   "outbounds": [
@@ -28,16 +25,16 @@
       "settings": {}
     },
     {
+      "tag": "blocked",
       "protocol": "blackhole",
-      "settings": {},
-      "tag": "blocked"
+      "settings": {}
     }
   ],
   "policy": {
     "levels": {
       "0": {
-        "statsUserUplink": true,
-        "statsUserDownlink": true
+        "statsUserDownlink": true,
+        "statsUserUplink": true
       }
     },
     "system": {
@@ -49,34 +46,31 @@
     "domainStrategy": "IPIfNonMatch",
     "rules": [
       {
-        "inboundTag": [
-          "api"
-        ],
-        "outboundTag": "api",
-        "type": "field"
+        "type": "field",
+        "inboundTag": ["api"],
+        "outboundTag": "api"
+      },
+      {
+        "type": "field",
+        "outboundTag": "blocked",
+        "ip": ["geoip:private"]
       },
       {
+        "type": "field",
         "outboundTag": "blocked",
-        "protocol": [
-          "bittorrent"
-        ],
-        "type": "field"
+        "protocol": ["bittorrent"]
       },
       {
+        "type": "field",
         "outboundTag": "blocked",
-        "ip": [
-          "geoip:private"
-        ],
-        "type": "field"
+        "ip": ["geoip:private"]
       },
       {
+        "type": "field",
         "outboundTag": "blocked",
-        "ip": [
-          "geoip:ir"
-        ],
-        "type": "field"
+        "ip": ["geoip:ir"]
       }
     ]
   },
   "stats": {}
-}
+}

+ 18 - 27
media/enable-traffic.txt → media/configs/traffic.json

@@ -1,25 +1,22 @@
 {
   "log": {
     "loglevel": "warning",
-    "access": "./access.log"
+    "access": "./access.log",
+    "error": "./error.log"
   },
   "api": {
-    "services": [
-      "HandlerService",
-      "LoggerService",
-      "StatsService"
-    ],
-    "tag": "api"
+    "tag": "api",
+    "services": ["HandlerService", "LoggerService", "StatsService"]
   },
   "inbounds": [
     {
+      "tag": "api",
       "listen": "127.0.0.1",
       "port": 62789,
       "protocol": "dokodemo-door",
       "settings": {
         "address": "127.0.0.1"
-      },
-      "tag": "api"
+      }
     }
   ],
   "outbounds": [
@@ -28,16 +25,16 @@
       "settings": {}
     },
     {
+      "tag": "blocked",
       "protocol": "blackhole",
-      "settings": {},
-      "tag": "blocked"
+      "settings": {}
     }
   ],
   "policy": {
     "levels": {
       "0": {
-        "statsUserUplink": true,
-        "statsUserDownlink": true
+        "statsUserDownlink": true,
+        "statsUserUplink": true
       }
     },
     "system": {
@@ -49,27 +46,21 @@
     "domainStrategy": "IPIfNonMatch",
     "rules": [
       {
-        "inboundTag": [
-          "api"
-        ],
-        "outboundTag": "api",
-        "type": "field"
+        "type": "field",
+        "inboundTag": ["api"],
+        "outboundTag": "api"
       },
       {
+        "type": "field",
         "outboundTag": "blocked",
-        "ip": [
-          "geoip:private"
-        ],
-        "type": "field"
+        "ip": ["geoip:private"]
       },
       {
+        "type": "field",
         "outboundTag": "blocked",
-        "protocol": [
-          "bittorrent"
-        ],
-        "type": "field"
+        "protocol": ["bittorrent"]
       }
     ]
   },
   "stats": {}
-}
+}

+ 10 - 0
web/controller/setting.go

@@ -37,6 +37,7 @@ func (a *SettingController) initRouter(g *gin.RouterGroup) {
 	g.POST("/update", a.updateSetting)
 	g.POST("/updateUser", a.updateUser)
 	g.POST("/restartPanel", a.restartPanel)
+	g.GET("/getDefaultJsonConfig", a.getDefaultJsonConfig)
 }
 
 func (a *SettingController) getAllSetting(c *gin.Context) {
@@ -48,6 +49,15 @@ func (a *SettingController) getAllSetting(c *gin.Context) {
 	jsonObj(c, allSetting, nil)
 }
 
+func (a *SettingController) getDefaultJsonConfig(c *gin.Context) {
+	defaultJsonConfig, err := a.settingService.GetDefaultJsonConfig()
+	if err != nil {
+		jsonMsg(c, I18n(c, "pages.setting.toasts.getSetting"), err)
+		return
+	}
+	jsonObj(c, defaultJsonConfig, nil)
+}
+
 func (a *SettingController) getDefaultSettings(c *gin.Context) {
 	expireDiff, err := a.settingService.GetExpireDiff()
 	if err != nil {

+ 631 - 312
web/html/xui/setting.html

@@ -24,356 +24,675 @@
         background: white;
     }
 </style>
+
 <body>
-<a-layout id="app" v-cloak>
-    {{ template "commonSider" . }}
-    <a-layout id="content-layout" :style="siderDrawer.isDarkTheme ? bgDarkStyle : ''">
-        <a-layout-content>
-            <a-spin :spinning="spinning" :delay="500" tip="loading">
-                <a-space direction="vertical">
-                    <a-space direction="horizontal">
-                        <a-button type="primary" :disabled="saveBtnDisable" @click="updateAllSetting">{{ i18n "pages.setting.save" }}</a-button>
-                        <a-button type="danger" :disabled="!saveBtnDisable" @click="restartPanel">{{ i18n "pages.setting.restartPanel" }}</a-button>
-                    </a-space>
-                    <a-tabs default-active-key="1" :class="siderDrawer.isDarkTheme ? darkClass : ''">
-                        <a-tab-pane key="1" tab='{{ i18n "pages.setting.panelConfig"}}'>
+    <a-layout id="app" v-cloak>
+        {{ template "commonSider" . }}
+        <a-layout id="content-layout" :style="siderDrawer.isDarkTheme ? bgDarkStyle : ''">
+            <a-layout-content>
+                <a-spin :spinning="spinning" :delay="500" tip="loading">
+                    <a-space direction="vertical">
+                        <a-space direction="horizontal">
+                            <a-button type="primary" :disabled="saveBtnDisable" @click="updateAllSetting">{{ i18n "pages.setting.save" }}</a-button>
+                            <a-button type="danger" :disabled="!saveBtnDisable" @click="restartPanel">{{ i18n "pages.setting.restartPanel" }}</a-button>
+                        </a-space>
 
-                            <a-list item-layout="horizontal" :style="siderDrawer.isDarkTheme ? 'color: hsla(0,0%,100%,.65);': 'background: white;'">
-                                <setting-list-item type="text" title='{{ i18n "pages.setting.panelListeningIP"}}' desc='{{ i18n "pages.setting.panelListeningIPDesc"}}' v-model="allSetting.webListen"></setting-list-item>
-                                <setting-list-item type="number" title='{{ i18n "pages.setting.panelPort"}}' desc='{{ i18n "pages.setting.panelPortDesc"}}' v-model.number="allSetting.webPort"></setting-list-item>
-                                <setting-list-item type="text" title='{{ i18n "pages.setting.publicKeyPath"}}' desc='{{ i18n "pages.setting.publicKeyPathDesc"}}' v-model="allSetting.webCertFile"></setting-list-item>
-                                <setting-list-item type="text" title='{{ i18n "pages.setting.privateKeyPath"}}' desc='{{ i18n "pages.setting.privateKeyPathDesc"}}' v-model="allSetting.webKeyFile"></setting-list-item>
-                                <setting-list-item type="text" title='{{ i18n "pages.setting.panelUrlPath"}}' desc='{{ i18n "pages.setting.panelUrlPathDesc"}}' v-model="allSetting.webBasePath"></setting-list-item>
-                                <setting-list-item type="number" title='{{ i18n "pages.setting.expireTimeDiff" }}' desc='{{ i18n "pages.setting.expireTimeDiffDesc" }}'  v-model="allSetting.expireDiff" :min="0"></setting-list-item>
-                                <setting-list-item type="number" title='{{ i18n "pages.setting.trafficDiff" }}' desc='{{ i18n "pages.setting.trafficDiffDesc" }}'  v-model="allSetting.trafficDiff" :min="0"></setting-list-item>
-                                <a-list-item>
-                                    <a-row style="padding: 20px">
-                                        <a-col :lg="24" :xl="12">
-                                            <a-list-item-meta title="Language"/>
-                                        </a-col>
-                                        <a-col :lg="24" :xl="12">
-                                            <template>
-                                                <a-select
+                        <a-tabs default-active-key="1" :class="siderDrawer.isDarkTheme ? darkClass : ''">
+                            <a-tab-pane key="1" tab='{{ i18n "pages.setting.panelConfig"}}'>
+                                <a-list item-layout="horizontal" :style="siderDrawer.isDarkTheme ? 'color: hsla(0,0%,100%,.65);': 'background: white;'">
+                                    <setting-list-item type="text" title='{{ i18n "pages.setting.panelListeningIP"}}' desc='{{ i18n "pages.setting.panelListeningIPDesc"}}' v-model="allSetting.webListen"></setting-list-item>
+                                    <setting-list-item type="number" title='{{ i18n "pages.setting.panelPort"}}' desc='{{ i18n "pages.setting.panelPortDesc"}}' v-model.number="allSetting.webPort"></setting-list-item>
+                                    <setting-list-item type="text" title='{{ i18n "pages.setting.publicKeyPath"}}' desc='{{ i18n "pages.setting.publicKeyPathDesc"}}' v-model="allSetting.webCertFile"></setting-list-item>
+                                    <setting-list-item type="text" title='{{ i18n "pages.setting.privateKeyPath"}}' desc='{{ i18n "pages.setting.privateKeyPathDesc"}}' v-model="allSetting.webKeyFile"></setting-list-item>
+                                    <setting-list-item type="text" title='{{ i18n "pages.setting.panelUrlPath"}}' desc='{{ i18n "pages.setting.panelUrlPathDesc"}}' v-model="allSetting.webBasePath"></setting-list-item>
+                                    <setting-list-item type="number" title='{{ i18n "pages.setting.expireTimeDiff" }}' desc='{{ i18n "pages.setting.expireTimeDiffDesc" }}'  v-model="allSetting.expireDiff" :min="0"></setting-list-item>
+                                    <setting-list-item type="number" title='{{ i18n "pages.setting.trafficDiff" }}' desc='{{ i18n "pages.setting.trafficDiffDesc" }}'  v-model="allSetting.trafficDiff" :min="0"></setting-list-item>
+                                    <a-list-item>
+                                        <a-row style="padding: 20px">
+                                            <a-col :lg="24" :xl="12">
+                                                <a-list-item-meta title="Language" />
+                                            </a-col>
+
+                                            <a-col :lg="24" :xl="12">
+                                                <template>
+                                                    <a-select
                                                         ref="selectLang"
                                                         v-model="lang"
                                                         @change="setLang(lang)"
                                                         :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"
                                                         style="width: 100%"
-                                                >
-                                                    <a-select-option :value="l.value" :label="l.value" v-for="l in supportLangs">
-                                                        <span role="img" aria-label="l.name" v-text="l.icon"></span>
-                                                        &nbsp;&nbsp;<span v-text="l.name"></span>
-                                                    </a-select-option>
-                                                </a-select>
-                                            </template>
-                                        </a-col>
-                                    </a-row>
-                                </a-list-item>
-                            </a-list>
-                        </a-tab-pane>
-                        <a-tab-pane key="2" tab='{{ i18n "pages.setting.userSetting"}}'>
-                            <a-form :style="siderDrawer.isDarkTheme ? 'color: hsla(0,0%,100%,.65); padding: 20px;': 'background: white; padding: 20px;'">
-                                <a-form-item label='{{ i18n "pages.setting.oldUsername"}}'>
-                                    <a-input v-model="user.oldUsername" style="max-width: 300px"></a-input>
-                                </a-form-item>
-                                <a-form-item label='{{ i18n "pages.setting.currentPassword"}}'>
-                                    <a-input type="password" v-model="user.oldPassword"
-                                             style="max-width: 300px"></a-input>
-                                </a-form-item>
-                                <a-form-item label='{{ i18n "pages.setting.newUsername"}}'>
-                                    <a-input v-model="user.newUsername" style="max-width: 300px"></a-input>
-                                </a-form-item>
-                                <a-form-item label='{{ i18n "pages.setting.newPassword"}}'>
-                                    <a-input type="password" v-model="user.newPassword"
-                                             style="max-width: 300px"></a-input>
-                                </a-form-item>
-                                <a-form-item>
-<!--                                    <a-button type="primary" @click="updateUser">Revise</a-button>-->
-                                    <a-button type="primary" @click="updateUser">{{ i18n "confirm" }}</a-button>
-                                </a-form-item>
-                            </a-form>
-                        </a-tab-pane>
-                        <a-tab-pane key="3" tab='{{ i18n "pages.setting.xrayConfiguration"}}'>
-                            <a-list item-layout="horizontal" :style="siderDrawer.isDarkTheme ? 'color: hsla(0,0%,100%,.65);': 'background: white;'">
-                                <setting-list-item type="switch" title='{{ i18n "pages.setting.xrayConfigTorrent"}}' desc='{{ i18n "pages.setting.xrayConfigTorrentDesc"}}'  v-model="torrentSettings"></setting-list-item>
-                                <setting-list-item type="switch" title='{{ i18n "pages.setting.xrayConfigPrivateIp"}}' desc='{{ i18n "pages.setting.xrayConfigPrivateIpDesc"}}'  v-model="privateIpSettings"></setting-list-item>
-                                <setting-list-item type="switch" title='{{ i18n "pages.setting.xrayConfigIRIp"}}' desc='{{ i18n "pages.setting.xrayConfigIRIpDesc"}}'  v-model="IRIpSettings"></setting-list-item>
-								<setting-list-item type="switch" title='{{ i18n "pages.setting.xrayConfigIRdomain"}}' desc='{{ i18n "pages.setting.xrayConfigIRdomainDesc"}}'  v-model="IRdomainSettings"></setting-list-item>
-								<a-divider>{{ i18n "pages.setting.advancedTemplate"}}</a-divider>
-                                <a-collapse>
-                                    <a-collapse-panel header="{{ i18n "pages.setting.xrayConfigInbounds"}}">
-                                        <setting-list-item type="textarea" title='{{ i18n "pages.setting.xrayConfigInbounds"}}' desc='{{ i18n "pages.setting.xrayConfigInboundsDesc"}}' v-model ="inboundSettings"></setting-list-item>
-                                    </a-collapse-panel>
-                                    <a-collapse-panel header="{{ i18n "pages.setting.xrayConfigOutbounds"}}">
-                                        <setting-list-item type="textarea" title='{{ i18n "pages.setting.xrayConfigOutbounds"}}' desc='{{ i18n "pages.setting.xrayConfigOutboundsDesc"}}' v-model ="outboundSettings"></setting-list-item>
-                                    </a-collapse-panel>
-                                    <a-collapse-panel header="{{ i18n "pages.setting.xrayConfigRoutings"}}">
-                                        <setting-list-item type="textarea" title='{{ i18n "pages.setting.xrayConfigRoutings"}}' desc='{{ i18n "pages.setting.xrayConfigRoutingsDesc"}}' v-model ="routingRuleSettings"></setting-list-item>
-                                    </a-collapse-panel>
-                                </a-collapse>
-                                <a-divider>{{ i18n "pages.setting.completeTemplate"}}</a-divider>
-                                <setting-list-item type="textarea" title='{{ i18n "pages.setting.xrayConfigTemplate"}}' desc='{{ i18n "pages.setting.xrayConfigTemplateDesc"}}' v-model="allSetting.xrayTemplateConfig"></setting-list-item>
-                            </a-list>
-                        </a-tab-pane>
-                        <a-tab-pane key="4" tab='{{ i18n "pages.setting.TGReminder"}}'>
-                            <a-list item-layout="horizontal" :style="siderDrawer.isDarkTheme ? 'color: hsla(0,0%,100%,.65);': 'background: white;'">
-                                <setting-list-item type="switch" title='{{ i18n "pages.setting.telegramBotEnable" }}' desc='{{ i18n "pages.setting.telegramBotEnableDesc" }}'  v-model="allSetting.tgBotEnable"></setting-list-item>
-                                <setting-list-item type="text" title='{{ i18n "pages.setting.telegramToken"}}' desc='{{ i18n "pages.setting.telegramTokenDesc"}}'  v-model="allSetting.tgBotToken"></setting-list-item>
-                                <setting-list-item type="text" title='{{ i18n "pages.setting.telegramChatId"}}' desc='{{ i18n "pages.setting.telegramChatIdDesc"}}'  v-model="allSetting.tgBotChatId"></setting-list-item>
-                                <setting-list-item type="text" title='{{ i18n "pages.setting.telegramNotifyTime"}}' desc='{{ i18n "pages.setting.telegramNotifyTimeDesc"}}'  v-model="allSetting.tgRunTime"></setting-list-item>
-                                <setting-list-item type="switch" title='{{ i18n "pages.setting.tgNotifyBackup" }}' desc='{{ i18n "pages.setting.tgNotifyBackupDesc" }}'  v-model="allSetting.tgBotBackup"></setting-list-item>
-                                <setting-list-item type="number" title='{{ i18n "pages.setting.tgNotifyCpu" }}' desc='{{ i18n "pages.setting.tgNotifyCpuDesc" }}'  v-model="allSetting.tgCpu" :min="0" :max="100"></setting-list-item>
-                            </a-list>
-                        </a-tab-pane>
-                        <a-tab-pane key="5" tab='{{ i18n "pages.setting.otherSetting"}}'>
-                            <a-list item-layout="horizontal" :style="siderDrawer.isDarkTheme ? 'color: hsla(0,0%,100%,.65);': 'background: white;'">
-                                <setting-list-item type="text" title='{{ i18n "pages.setting.timeZonee"}}' desc='{{ i18n "pages.setting.timeZoneDesc"}}' v-model="allSetting.timeLocation"></setting-list-item>
-                            </a-list>
-                        </a-tab-pane>
-                    </a-tabs>
-                </a-space>
-            </a-spin>
-        </a-layout-content>
+                                                    >
+                                                        <a-select-option :value="l.value" :label="l.value" v-for="l in supportLangs">
+                                                            <span role="img" aria-label="l.name" v-text="l.icon"></span>&nbsp;&nbsp;<span v-text="l.name"></span>
+                                                        </a-select-option>
+                                                    </a-select>
+                                                </template>
+                                            </a-col>
+                                        </a-row>
+                                    </a-list-item>
+                                </a-list>
+                            </a-tab-pane>
+
+                            <a-tab-pane key="2" tab='{{ i18n "pages.setting.userSetting"}}'>
+                                <a-form :style="siderDrawer.isDarkTheme ? 'color: hsla(0,0%,100%,.65); padding: 20px;': 'background: white; padding: 20px;'">
+                                    <a-form-item label='{{ i18n "pages.setting.oldUsername"}}'>
+                                        <a-input v-model="user.oldUsername" style="max-width: 300px"></a-input>
+                                    </a-form-item>
+                                    <a-form-item label='{{ i18n "pages.setting.currentPassword"}}'>
+                                        <a-input type="password" v-model="user.oldPassword" style="max-width: 300px"></a-input>
+                                    </a-form-item>
+                                    <a-form-item label='{{ i18n "pages.setting.newUsername"}}'>
+                                        <a-input v-model="user.newUsername" style="max-width: 300px"></a-input>
+                                    </a-form-item>
+                                    <a-form-item label='{{ i18n "pages.setting.newPassword"}}'>
+                                        <a-input type="password" v-model="user.newPassword" style="max-width: 300px"></a-input>
+                                    </a-form-item>
+                                    <a-form-item>
+                                        <a-button type="primary" @click="updateUser">{{ i18n "confirm" }}</a-button>
+                                    </a-form-item>
+                                </a-form>
+                            </a-tab-pane>
+
+                            <a-tab-pane key="3" tab='{{ i18n "pages.setting.xrayConfiguration"}}'>
+                                <a-list item-layout="horizontal" :style="siderDrawer.isDarkTheme ? 'color: hsla(0,0%,100%,.65);': 'background: white;'">
+                                    <a-divider>{{ i18n "pages.setting.actions"}}</a-divider>
+                                    <a-space direction="horizontal" style="padding: 0 20px">
+                                        <a-button type="primary" @click="resetXrayConfigToDefault">{{ i18n "pages.setting.resetDefaultConfig" }}</a-button>
+                                    </a-space>
+
+                                    <a-divider>{{ i18n "pages.setting.basicTemplate"}}</a-divider>
+                                    <a-collapse>
+                                        <a-collapse-panel header='{{ i18n "pages.setting.generalConfigs"}}'>
+                                            <setting-list-item type="switch" title='{{ i18n "pages.setting.xrayConfigTorrent"}}' desc='{{ i18n "pages.setting.xrayConfigTorrentDesc"}}'  v-model="torrentSettings"></setting-list-item>
+                                            <setting-list-item type="switch" title='{{ i18n "pages.setting.xrayConfigPrivateIp"}}' desc='{{ i18n "pages.setting.xrayConfigPrivateIpDesc"}}'  v-model="privateIpSettings"></setting-list-item>
+                                            <setting-list-item type="switch" title='{{ i18n "pages.setting.xrayConfigAds"}}' desc='{{ i18n "pages.setting.xrayConfigAdsDesc"}}'  v-model="AdsSettings"></setting-list-item>
+                                            <setting-list-item type="switch" title='{{ i18n "pages.setting.xrayConfigPorn"}}' desc='{{ i18n "pages.setting.xrayConfigPornDesc"}}'  v-model="PornSettings"></setting-list-item>
+                                        </a-collapse-panel>
+                                        <a-collapse-panel header='{{ i18n "pages.setting.countryConfigs"}}'>
+                                            <setting-list-item type="switch" title='{{ i18n "pages.setting.xrayConfigIRIp"}}' desc='{{ i18n "pages.setting.xrayConfigIRIpDesc"}}'  v-model="IRIpSettings"></setting-list-item>
+                                            <setting-list-item type="switch" title='{{ i18n "pages.setting.xrayConfigIRDomain"}}' desc='{{ i18n "pages.setting.xrayConfigIRDomainDesc"}}'  v-model="IRDomainSettings"></setting-list-item>
+                                            <setting-list-item type="switch" title='{{ i18n "pages.setting.xrayConfigChinaIp"}}' desc='{{ i18n "pages.setting.xrayConfigChinaIpDesc"}}'  v-model="ChinaIpSettings"></setting-list-item>
+                                            <setting-list-item type="switch" title='{{ i18n "pages.setting.xrayConfigChinaDomain"}}' desc='{{ i18n "pages.setting.xrayConfigChinaDomainDesc"}}'  v-model="ChinaDomainSettings"></setting-list-item>
+                                            <setting-list-item type="switch" title='{{ i18n "pages.setting.xrayConfigRussiaIp"}}' desc='{{ i18n "pages.setting.xrayConfigRussiaIpDesc"}}'  v-model="RussiaIpSettings"></setting-list-item>
+                                            <setting-list-item type="switch" title='{{ i18n "pages.setting.xrayConfigRussiaDomain"}}' desc='{{ i18n "pages.setting.xrayConfigRussiaDomainDesc"}}'  v-model="RussiaDomainSettings"></setting-list-item>
+                                        </a-collapse-panel>
+                                        <a-collapse-panel header='{{ i18n "pages.setting.ipv4Configs"}}'>
+                                            <setting-list-item type="switch" title='{{ i18n "pages.setting.xrayConfigGoogleIPv4"}}' desc='{{ i18n "pages.setting.xrayConfigGoogleIPv4Desc"}}'  v-model="GoogleIPv4Settings"></setting-list-item>
+                                            <setting-list-item type="switch" title='{{ i18n "pages.setting.xrayConfigNetflixIPv4"}}' desc='{{ i18n "pages.setting.xrayConfigNetflixIPv4Desc"}}'  v-model="NetflixIPv4Settings"></setting-list-item>
+                                        </a-collapse-panel>
+                                        <a-collapse-panel header='{{ i18n "pages.setting.warpConfigs"}}'>
+                                            <setting-list-item type="switch" title='{{ i18n "pages.setting.xrayConfigGoogleWARP"}}' desc='{{ i18n "pages.setting.xrayConfigGoogleWARPDesc"}}'  v-model="GoogleWARPSettings"></setting-list-item>
+                                            <setting-list-item type="switch" title='{{ i18n "pages.setting.xrayConfigOpenAIWARP"}}' desc='{{ i18n "pages.setting.xrayConfigOpenAIWARPDesc"}}'  v-model="OpenAIWARPSettings"></setting-list-item>
+                                            <setting-list-item type="switch" title='{{ i18n "pages.setting.xrayConfigNetflixWARP"}}' desc='{{ i18n "pages.setting.xrayConfigNetflixWARPDesc"}}'  v-model="NetflixWARPSettings"></setting-list-item>
+                                            <setting-list-item type="switch" title='{{ i18n "pages.setting.xrayConfigSpotifyWARP"}}' desc='{{ i18n "pages.setting.xrayConfigSpotifyWARPDesc"}}'  v-model="SpotifyWARPSettings"></setting-list-item>
+                                            <setting-list-item type="switch" title='{{ i18n "pages.setting.xrayConfigIRWARP"}}' desc='{{ i18n "pages.setting.xrayConfigIRWARPDesc"}}'  v-model="IRWARPSettings"></setting-list-item>
+                                        </a-collapse-panel>
+                                    </a-collapse>
+
+                                    <a-divider>{{ i18n "pages.setting.advancedTemplate"}}</a-divider>
+                                    <a-collapse>
+                                        <a-collapse-panel header='{{ i18n "pages.setting.xrayConfigInbounds"}}'>
+                                            <setting-list-item type="textarea" title='{{ i18n "pages.setting.xrayConfigInbounds"}}' desc='{{ i18n "pages.setting.xrayConfigInboundsDesc"}}' v-model="inboundSettings"></setting-list-item>
+                                        </a-collapse-panel>
+                                        <a-collapse-panel header='{{ i18n "pages.setting.xrayConfigOutbounds"}}'>
+                                            <setting-list-item type="textarea" title='{{ i18n "pages.setting.xrayConfigOutbounds"}}' desc='{{ i18n "pages.setting.xrayConfigOutboundsDesc"}}' v-model="outboundSettings"></setting-list-item>
+                                        </a-collapse-panel>
+                                        <a-collapse-panel header='{{ i18n "pages.setting.xrayConfigRoutings"}}'>
+                                            <setting-list-item type="textarea" title='{{ i18n "pages.setting.xrayConfigRoutings"}}' desc='{{ i18n "pages.setting.xrayConfigRoutingsDesc"}}' v-model="routingRuleSettings"></setting-list-item>
+                                        </a-collapse-panel>
+                                    </a-collapse>
+
+                                    <a-divider>{{ i18n "pages.setting.completeTemplate"}}</a-divider>
+                                    <setting-list-item type="textarea" title='{{ i18n "pages.setting.xrayConfigTemplate"}}' desc='{{ i18n "pages.setting.xrayConfigTemplateDesc"}}' v-model="allSetting.xrayTemplateConfig"></setting-list-item>
+                                </a-list>
+                            </a-tab-pane>
+
+                            <a-tab-pane key="4" tab='{{ i18n "pages.setting.TGReminder"}}'>
+                                <a-list item-layout="horizontal" :style="siderDrawer.isDarkTheme ? 'color: hsla(0,0%,100%,.65);': 'background: white;'">
+                                    <setting-list-item type="switch" title='{{ i18n "pages.setting.telegramBotEnable" }}' desc='{{ i18n "pages.setting.telegramBotEnableDesc" }}' v-model="allSetting.tgBotEnable"></setting-list-item>
+                                    <setting-list-item type="text" title='{{ i18n "pages.setting.telegramToken"}}' desc='{{ i18n "pages.setting.telegramTokenDesc"}}' v-model="allSetting.tgBotToken"></setting-list-item>
+                                    <setting-list-item type="text" title='{{ i18n "pages.setting.telegramChatId"}}' desc='{{ i18n "pages.setting.telegramChatIdDesc"}}' v-model="allSetting.tgBotChatId"></setting-list-item>
+                                    <setting-list-item type="text" title='{{ i18n "pages.setting.telegramNotifyTime"}}' desc='{{ i18n "pages.setting.telegramNotifyTimeDesc"}}' v-model="allSetting.tgRunTime"></setting-list-item>
+                                    <setting-list-item type="switch" title='{{ i18n "pages.setting.tgNotifyBackup" }}' desc='{{ i18n "pages.setting.tgNotifyBackupDesc" }}' v-model="allSetting.tgBotBackup"></setting-list-item>
+                                    <setting-list-item type="number" title='{{ i18n "pages.setting.tgNotifyCpu" }}' desc='{{ i18n "pages.setting.tgNotifyCpuDesc" }}' v-model="allSetting.tgCpu" :min="0" :max="100"></setting-list-item>
+                                </a-list>
+                            </a-tab-pane>
+
+                            <a-tab-pane key="5" tab='{{ i18n "pages.setting.otherSetting"}}'>
+                                <a-list item-layout="horizontal" :style="siderDrawer.isDarkTheme ? 'color: hsla(0,0%,100%,.65);': 'background: white;'">
+                                    <setting-list-item type="text" title='{{ i18n "pages.setting.timeZonee"}}' desc='{{ i18n "pages.setting.timeZoneDesc"}}' v-model="allSetting.timeLocation"></setting-list-item>
+                                </a-list>
+                            </a-tab-pane>
+                        </a-tabs>
+                    </a-space>
+                </a-spin>
+            </a-layout-content>
+        </a-layout>
     </a-layout>
-</a-layout>
-{{template "js" .}}
-{{template "component/setting"}}
-<script>
+    {{template "js" .}}
+    {{template "component/setting"}}
+    <script>
 
-    const app = new Vue({
-        delimiters: ['[[', ']]'],
-        el: '#app',
-        data: {
-            siderDrawer,
-            spinning: false,
-            oldAllSetting: new AllSetting(),
-            allSetting: new AllSetting(),
-            saveBtnDisable: true,
-            user: {},
-            lang : getLang()
-        },
-        methods: {
-            loading(spinning = true) {
-                this.spinning = spinning;
-            },
-            async getAllSetting() {
-                this.loading(true);
-                const msg = await HttpUtil.post("/xui/setting/all");
-                this.loading(false);
-                if (msg.success) {
-                    this.oldAllSetting = new AllSetting(msg.obj);
-                    this.allSetting = new AllSetting(msg.obj);
-                    this.saveBtnDisable = true;
-                }
-            },
-            async updateAllSetting() {
-                this.loading(true);
-                const msg = await HttpUtil.post("/xui/setting/update", this.allSetting);
-                this.loading(false);
-                if (msg.success) {
-                    await this.getAllSetting();
-                }
-            },
-            async updateUser() {
-                this.loading(true);
-                const msg = await HttpUtil.post("/xui/setting/updateUser", this.user);
-                this.loading(false);
-                if (msg.success) {
-                    this.user = {};
+        const app = new Vue({
+            delimiters: ['[[', ']]'],
+            el: '#app',
+            data: {
+                siderDrawer,
+                spinning: false,
+                oldAllSetting: new AllSetting(),
+                allSetting: new AllSetting(),
+                saveBtnDisable: true,
+                user: {},
+                lang: getLang(),
+                ipv4Settings: {
+                    tag: "IPv4",
+                    protocol: "freedom",
+                    settings: {
+                        domainStrategy: "UseIPv4"
+                    }
+                },
+                warpSettings: {
+                    tag: "WARP",
+                    protocol: "socks",
+                    settings: {
+                        servers: [
+                            {
+                                address: "127.0.0.1",
+                                port: 40000
+                            }
+                        ]
+                    }
+                },
+                settingsData: {
+                    protocols: {
+                        bittorrent: ["bittorrent"],
+                    },
+                    ips: {
+                        local: ["geoip:private"],
+                        google: ["geoip:google"],
+                        cn: ["geoip:cn"],
+                        ir: ["geoip:ir"],
+                        ru: ["geoip:ru"],
+                    },
+                    domains: {
+                        ads: [
+                            "geosite:category-ads-all",
+                            "geosite:category-ads",
+                            "geosite:google-ads",
+                            "geosite:spotify-ads"
+                        ],
+                        porn: ["geosite:category-porn"],
+                        openai: ["geosite:openai"],
+                        google: ["geosite:google"],
+                        spotify: ["geosite:spotify"],
+                        netflix: ["geosite:netflix"],
+                        cn: ["geosite:cn"],
+                        ru: ["geosite:category-ru-gov"],
+                        ir: [
+                            "regexp:.*\\.ir$",
+                            "ext:iran.dat:ir",
+                            "ext:iran.dat:other",
+                            "ext:iran.dat:ads",
+                            "geosite:category-ir"
+                        ]
+                    },
                 }
             },
-            async restartPanel() {
-                await new Promise(resolve => {
-                    this.$confirm({
-                        title: '{{ i18n "pages.setting.restartPanel" }}',
-                        content: '{{ i18n "pages.setting.restartPanelDesc" }}',
-                        okText: '{{ i18n "sure" }}',
-                        cancelText: '{{ i18n "cancel" }}',
-                        onOk: () => resolve(),
-                    });
-                });
-                this.loading(true);
-                const msg = await HttpUtil.post("/xui/setting/restartPanel");
-                this.loading(false);
-                if (msg.success) {
+            methods: {
+                loading(spinning = true) {
+                    this.spinning = spinning;
+                },
+                async getAllSetting() {
                     this.loading(true);
-                    await PromiseUtil.sleep(5000);
-                    location.reload();
-                }
-            }
-        },
-        async mounted() {
-            await this.getAllSetting();
-            while (true) {
-                await PromiseUtil.sleep(1000);
-                this.saveBtnDisable = this.oldAllSetting.equals(this.allSetting);
-            }
-        },
-        computed: {
-            templateSettings: {
-                get: function () { return this.allSetting.xrayTemplateConfig ? JSON.parse(this.allSetting.xrayTemplateConfig) : null ; },
-                set: function (newValue) { this.allSetting.xrayTemplateConfig = JSON.stringify(newValue, null, 2) },
-            },
-            inboundSettings: {
-                get: function () { return this.templateSettings ? JSON.stringify(this.templateSettings.inbounds, null, 2) : null; },
-                set: function (newValue) {
-                    newTemplateSettings = this.templateSettings;
-                    newTemplateSettings.inbounds = JSON.parse(newValue)
-                    this.templateSettings = newTemplateSettings
+                    const msg = await HttpUtil.post("/xui/setting/all");
+                    this.loading(false);
+                    if (msg.success) {
+                        this.oldAllSetting = new AllSetting(msg.obj);
+                        this.allSetting = new AllSetting(msg.obj);
+                        this.saveBtnDisable = true;
+                    }
                 },
-            },
-            outboundSettings: {
-                get: function () { return this.templateSettings ? JSON.stringify(this.templateSettings.outbounds, null, 2) : null; },
-                set: function (newValue) {
-                    newTemplateSettings = this.templateSettings;
-                    newTemplateSettings.outbounds = JSON.parse(newValue)
-                    this.templateSettings = newTemplateSettings
+                async updateAllSetting() {
+                    this.loading(true);
+                    const msg = await HttpUtil.post("/xui/setting/update", this.allSetting);
+                    this.loading(false);
+                    if (msg.success) {
+                        await this.getAllSetting();
+                    }
                 },
-            },
-            routingRuleSettings: {
-                get: function () { return this.templateSettings ? JSON.stringify(this.templateSettings.routing.rules, null, 2) : null; },
-                set: function (newValue) {
-                    newTemplateSettings = this.templateSettings;
-                    newTemplateSettings.routing.rules = JSON.parse(newValue)
-                    this.templateSettings = newTemplateSettings
+                async updateUser() {
+                    this.loading(true);
+                    const msg = await HttpUtil.post("/xui/setting/updateUser", this.user);
+                    this.loading(false);
+                    if (msg.success) {
+                        this.user = {};
+                    }
                 },
-            },
-            torrentSettings: {
-                get: function () {
-                    torrentFilter = false
-                    if(this.templateSettings != null){
-                        this.templateSettings.routing.rules.forEach(routingRule => {
-                            if(routingRule.hasOwnProperty("protocol")){
-                                if (routingRule.protocol[0] === "bittorrent" && routingRule.outboundTag == "blocked"){
-                                    torrentFilter = true
-                                }
-                            }
+                async restartPanel() {
+                    await new Promise(resolve => {
+                        this.$confirm({
+                            title: '{{ i18n "pages.setting.restartPanel" }}',
+                            content: '{{ i18n "pages.setting.restartPanelDesc" }}',
+                            okText: '{{ i18n "sure" }}',
+                            cancelText: '{{ i18n "cancel" }}',
+                            onOk: () => resolve(),
                         });
+                    });
+                    this.loading(true);
+                    const msg = await HttpUtil.post("/xui/setting/restartPanel");
+                    this.loading(false);
+                    if (msg.success) {
+                        this.loading(true);
+                        await PromiseUtil.sleep(5000);
+                        location.reload();
                     }
-                    return torrentFilter
                 },
-                set: function (newValue) {
-                    newTemplateSettings = JSON.parse(this.allSetting.xrayTemplateConfig);
-                    if (newValue){
-                        newTemplateSettings.routing.rules.push(JSON.parse("{\"outboundTag\": \"blocked\",\"protocol\": [\"bittorrent\"],\"type\": \"field\"}"))
+                async resetXrayConfigToDefault() {
+                    this.loading(true);
+                    const msg = await HttpUtil.get("/xui/setting/getDefaultJsonConfig");
+                    this.loading(false);
+                    if (msg.success) {
+                        this.templateSettings = JSON.parse(JSON.stringify(msg.obj, null, 2));
+                        this.saveBtnDisable = true;
                     }
-                    else {
-                        newTemplateSettings.routing.rules = [];
-                        this.templateSettings.routing.rules.forEach(routingRule => {
-                            if (routingRule.hasOwnProperty('protocol')){
-                                if (routingRule.protocol[0] === "bittorrent" && routingRule.outboundTag == "blocked"){
-                                    return;
-                                }
-                            }
-                            newTemplateSettings.routing.rules.push(routingRule);
-                        });
+                },
+                checkRequiredOutbounds() {
+                    const newTemplateSettings = this.templateSettings;
+                    const haveIPv4Outbounds = newTemplateSettings.outbounds.some((o) => o?.tag === "IPv4");
+                    const haveIPv4Rules = newTemplateSettings.routing.rules.some((r) => r?.outboundTag === "IPv4");
+                    const haveWARPOutbounds = newTemplateSettings.outbounds.some((o) => o?.tag === "WARP");
+                    const haveWARPRules = newTemplateSettings.routing.rules.some((r) => r?.outboundTag === "WARP");
+                    if (haveWARPRules && !haveWARPOutbounds) {
+                        newTemplateSettings.outbounds.push(this.warpSettings);
+                    }
+                    if (haveIPv4Rules && !haveIPv4Outbounds) {
+                        newTemplateSettings.outbounds.push(this.ipv4Settings);
                     }
-                    this.templateSettings = newTemplateSettings
+                    this.templateSettings = newTemplateSettings;
                 },
-            },
-            privateIpSettings: {
-                get: function () {
-                    localIpFilter = false
-                    if(this.templateSettings != null){
-                        this.templateSettings.routing.rules.forEach(routingRule => {
-                            if(routingRule.hasOwnProperty("ip")){
-                                if (routingRule.ip[0] === "geoip:private" && routingRule.outboundTag == "blocked"){
-                                    localIpFilter = true
+                templateRuleGetter(routeSettings) {
+                    const { data, property, outboundTag } = routeSettings;
+                    let result = false;
+                    if (this.templateSettings != null) {
+                        this.templateSettings.routing.rules.forEach(
+                            (routingRule) => {
+                                if (
+                                    routingRule.hasOwnProperty(property) &&
+                                    routingRule.hasOwnProperty("outboundTag") &&
+                                    routingRule.outboundTag === outboundTag
+                                ) {
+                                    if (data.includes(routingRule[property][0])) {
+                                        result = true;
+                                    }
                                 }
                             }
-                        });
+                        );
                     }
-                    return localIpFilter
+                    return result;
                 },
-                set: function (newValue) {
-                    newTemplateSettings = JSON.parse(this.allSetting.xrayTemplateConfig);
-                    if (newValue){
-                        newTemplateSettings.routing.rules.push(JSON.parse("{\"outboundTag\": \"blocked\",\"ip\": [\"geoip:private\"],\"type\": \"field\"}"))
+                templateRuleSetter(routeSettings) {
+                    const { newValue, data, property, outboundTag } = routeSettings;
+                    const oldTemplateSettings = this.templateSettings;
+                    const newTemplateSettings = oldTemplateSettings;
+                    if (newValue) {
+                        const propertyRule = {
+                            type: "field",
+                            outboundTag,
+                            [property]: data
+                        };
+                        newTemplateSettings.routing.rules.push(propertyRule);
                     }
                     else {
-                        newTemplateSettings.routing.rules = [];
-                        this.templateSettings.routing.rules.forEach(routingRule => {
-                            if (routingRule.hasOwnProperty('ip')){
-                                if (routingRule.ip[0] === "geoip:private" && routingRule.outboundTag == "blocked"){
-                                    return;
+                        const newRules = [];
+                        newTemplateSettings.routing.rules.forEach(
+                            (routingRule) => {
+                                if (
+                                    routingRule.hasOwnProperty(property) &&
+                                    routingRule.hasOwnProperty("outboundTag") &&
+                                    routingRule.outboundTag === outboundTag
+                                ) {
+                                    if (data.includes(routingRule[property][0])) {
+                                        return;
+                                    }
                                 }
+                                newRules.push(routingRule);
                             }
-                            newTemplateSettings.routing.rules.push(routingRule);
-                        });
+                        );
+                        newTemplateSettings.routing.rules = newRules;
                     }
-                    this.templateSettings = newTemplateSettings
-                },
+                    this.templateSettings = newTemplateSettings;
+                    this.checkRequiredOutbounds();
+                }
             },
-			IRIpSettings: {
-                get: function () {
-                    localIpFilter = false
-                    if(this.templateSettings != null){
-                        this.templateSettings.routing.rules.forEach(routingRule => {
-                            if(routingRule.hasOwnProperty("ip")){
-                                if (routingRule.ip[0] === "geoip:ir" && routingRule.outboundTag == "blocked"){
-                                    localIpFilter = true
-                                }
-                            }
+            async mounted() {
+                await this.getAllSetting();
+                while (true) {
+                    await PromiseUtil.sleep(1000);
+                    this.saveBtnDisable = this.oldAllSetting.equals(this.allSetting);
+                }
+            },
+            computed: {
+                templateSettings: {
+                    get: function () { return this.allSetting.xrayTemplateConfig ? JSON.parse(this.allSetting.xrayTemplateConfig) : null; },
+                    set: function (newValue) { this.allSetting.xrayTemplateConfig = JSON.stringify(newValue, null, 2) },
+                },
+                inboundSettings: {
+                    get: function () { return this.templateSettings ? JSON.stringify(this.templateSettings.inbounds, null, 2) : null; },
+                    set: function (newValue) {
+                        newTemplateSettings = this.templateSettings;
+                        newTemplateSettings.inbounds = JSON.parse(newValue)
+                        this.templateSettings = newTemplateSettings
+                    },
+                },
+                outboundSettings: {
+                    get: function () { return this.templateSettings ? JSON.stringify(this.templateSettings.outbounds, null, 2) : null; },
+                    set: function (newValue) {
+                        newTemplateSettings = this.templateSettings;
+                        newTemplateSettings.outbounds = JSON.parse(newValue)
+                        this.templateSettings = newTemplateSettings
+                    },
+                },
+                routingRuleSettings: {
+                    get: function () { return this.templateSettings ? JSON.stringify(this.templateSettings.routing.rules, null, 2) : null; },
+                    set: function (newValue) {
+                        newTemplateSettings = this.templateSettings;
+                        newTemplateSettings.routing.rules = JSON.parse(newValue)
+                        this.templateSettings = newTemplateSettings
+                    },
+                },
+                torrentSettings: {
+                    get: function () {
+                        return this.templateRuleGetter({
+                            outboundTag: "blocked",
+                            property: "protocol",
+                            data: this.settingsData.protocols.bittorrent
                         });
-                    }
-                    return localIpFilter
+                    },
+                    set: function (newValue) {
+                        this.templateRuleSetter({
+                            newValue,
+                            outboundTag: "blocked",
+                            property: "protocol",
+                            data: this.settingsData.protocols.bittorrent
+                        });
+                    },
                 },
-                set: function (newValue) {
-                    newTemplateSettings = JSON.parse(this.allSetting.xrayTemplateConfig);
-                    if (newValue){
-                        newTemplateSettings.routing.rules.push(JSON.parse("{\"outboundTag\": \"blocked\",\"ip\": [\"geoip:ir\"],\"type\": \"field\"}"))
-                    }
-                    else {
-                        newTemplateSettings.routing.rules = [];
-                        this.templateSettings.routing.rules.forEach(routingRule => {
-                            if (routingRule.hasOwnProperty('ip')){
-                                if (routingRule.ip[0] === "geoip:ir" && routingRule.outboundTag == "blocked"){
-                                    return;
-                                }
-                            }
-                            newTemplateSettings.routing.rules.push(routingRule);
+                privateIpSettings: {
+                    get: function () {
+                        return this.templateRuleGetter({
+                            outboundTag: "blocked",
+                            property: "ip",
+                            data: this.settingsData.ips.local
                         });
-                    }
-                    this.templateSettings = newTemplateSettings
+                    },
+                    set: function (newValue) {
+                        this.templateRuleSetter({
+                            newValue,
+                            outboundTag: "blocked",
+                            property: "ip",
+                            data: this.settingsData.ips.local
+                        });
+                    },
                 },
-            },
-			IRdomainSettings: {
-                get: function () {
-                    localdomainFilter = false
-                    if(this.templateSettings != null){
-                        this.templateSettings.routing.rules.forEach(routingRule => {
-                            if(routingRule.hasOwnProperty("domain")){
-                                if ((routingRule.domain[0] === "regexp:.+.ir$" || routingRule.domain[0] === "ext:iran.dat:ir" || routingRule.domain[0] === "ext:iran.dat:other") && routingRule.outboundTag == "blocked") {
-                                    localdomainFilter = true
-                                }
-                            }
+                AdsSettings: {
+                    get: function () {
+                        return this.templateRuleGetter({
+                            outboundTag: "blocked",
+                            property: "domain",
+                            data: this.settingsData.domains.ads
                         });
-                    }
-                    return localdomainFilter
+                    },
+                    set: function (newValue) {
+                        this.templateRuleSetter({
+                            newValue,
+                            outboundTag: "blocked",
+                            property: "domain",
+                            data: this.settingsData.domains.ads
+                        });
+                    },
                 },
-                set: function (newValue) {
-                    newTemplateSettings = JSON.parse(this.allSetting.xrayTemplateConfig);
-                    if (newValue){
-                        newTemplateSettings.routing.rules.push(JSON.parse("{\"outboundTag\": \"blocked\",\"domain\": [\"regexp:.+.ir$\", \"ext:iran.dat:ir\", \"ext:iran.dat:other\"],\"type\": \"field\"}"))
-                    }
-                    else {
-                        newTemplateSettings.routing.rules = [];
-                        this.templateSettings.routing.rules.forEach(routingRule => {
-                            if (routingRule.hasOwnProperty('domain')){
-                                if ((routingRule.domain[0] === "regexp:.+.ir$" || routingRule.domain[0] === "ext:iran.dat:ir" || routingRule.domain[0] === "ext:iran.dat:other") && routingRule.outboundTag == "blocked"){
-                                    return;
-                                }
-                            }
-                            newTemplateSettings.routing.rules.push(routingRule);
+                PornSettings: {
+                    get: function () {
+                        return this.templateRuleGetter({
+                            outboundTag: "blocked",
+                            property: "domain",
+                            data: this.settingsData.domains.porn
                         });
-                    }
-                    this.templateSettings = newTemplateSettings
+                    },
+                    set: function (newValue) {
+                        this.templateRuleSetter({
+                            newValue,
+                            outboundTag: "blocked",
+                            property: "domain",
+                            data: this.settingsData.domains.porn
+                        });
+                    },
                 },
-            },
-			
-        }
-    });
+                GoogleIPv4Settings: {
+                    get: function () {
+                        return this.templateRuleGetter({
+                            outboundTag: "IPv4",
+                            property: "domain",
+                            data: this.settingsData.domains.google
+                        });
+                    },
+                    set: function (newValue) {
+                        this.templateRuleSetter({
+                            newValue,
+                            outboundTag: "IPv4",
+                            property: "domain",
+                            data: this.settingsData.domains.google
+                        });
+                    },
+                },
+                NetflixIPv4Settings: {
+                    get: function () {
+                        return this.templateRuleGetter({
+                            outboundTag: "IPv4",
+                            property: "domain",
+                            data: this.settingsData.domains.netflix
+                        });
+                    },
+                    set: function (newValue) {
+                        this.templateRuleSetter({
+                            newValue,
+                            outboundTag: "IPv4",
+                            property: "domain",
+                            data: this.settingsData.domains.netflix
+                        });
+                    },
+                },
+                IRIpSettings: {
+                    get: function () {
+                        return this.templateRuleGetter({
+                            outboundTag: "blocked",
+                            property: "ip",
+                            data: this.settingsData.ips.ir
+                        });
+                    },
+                    set: function (newValue) {
+                        this.templateRuleSetter({
+                            newValue,
+                            outboundTag: "blocked",
+                            property: "ip",
+                            data: this.settingsData.ips.ir
+                        });
+                    },
+                },
+                IRDomainSettings: {
+                    get: function () {
+                        return this.templateRuleGetter({
+                            outboundTag: "blocked",
+                            property: "domain",
+                            data: this.settingsData.domains.ir
+                        });
+                    },
+                    set: function (newValue) {
+                        this.templateRuleSetter({
+                            newValue,
+                            outboundTag: "blocked",
+                            property: "domain",
+                            data: this.settingsData.domains.ir
+                        });
+                    },
+                },
+                ChinaIpSettings: {
+                    get: function () {
+                        return this.templateRuleGetter({
+                            outboundTag: "blocked",
+                            property: "ip",
+                            data: this.settingsData.ips.cn
+                        });
+                    },
+                    set: function (newValue) {
+                        this.templateRuleSetter({
+                            newValue,
+                            outboundTag: "blocked",
+                            property: "ip",
+                            data: this.settingsData.ips.cn
+                        });
+                    },
+                },
+                ChinaDomainSettings: {
+                    get: function () {
+                        return this.templateRuleGetter({
+                            outboundTag: "blocked",
+                            property: "domain",
+                            data: this.settingsData.domains.cn
+                        });
+                    },
+                    set: function (newValue) {
+                        this.templateRuleSetter({
+                            newValue,
+                            outboundTag: "blocked",
+                            property: "domain",
+                            data: this.settingsData.domains.cn
+                        });
+                    },
+                },
+                RussiaIpSettings: {
+                    get: function () {
+                        return this.templateRuleGetter({
+                            outboundTag: "blocked",
+                            property: "ip",
+                            data: this.settingsData.ips.ru
+                        });
+                    },
+                    set: function (newValue) {
+                        this.templateRuleSetter({
+                            newValue,
+                            outboundTag: "blocked",
+                            property: "ip",
+                            data: this.settingsData.ips.ru
+                        });
+                    },
+                },
+                RussiaDomainSettings: {
+                    get: function () {
+                        return this.templateRuleGetter({
+                            outboundTag: "blocked",
+                            property: "domain",
+                            data: this.settingsData.domains.ru
+                        });
+                    },
+                    set: function (newValue) {
+                        this.templateRuleSetter({
+                            newValue,
+                            outboundTag: "blocked",
+                            property: "domain",
+                            data: this.settingsData.domains.ru
+                        });
+                    },
+                },
+                GoogleWARPSettings: {
+                    get: function () {
+                        return this.templateRuleGetter({
+                            outboundTag: "WARP",
+                            property: "domain",
+                            data: this.settingsData.domains.google
+                        });
+                    },
+                    set: function (newValue) {
+                        this.templateRuleSetter({
+                            newValue,
+                            outboundTag: "WARP",
+                            property: "domain",
+                            data: this.settingsData.domains.google
+                        });
+                    },
+                },
+                OpenAIWARPSettings: {
+                    get: function () {
+                        return this.templateRuleGetter({
+                            outboundTag: "WARP",
+                            property: "domain",
+                            data: this.settingsData.domains.openai
+                        });
+                    },
+                    set: function (newValue) {
+                        this.templateRuleSetter({
+                            newValue,
+                            outboundTag: "WARP",
+                            property: "domain",
+                            data: this.settingsData.domains.openai
+                        });
+                    },
+                },
+                NetflixWARPSettings: {
+                    get: function () {
+                        return this.templateRuleGetter({
+                            outboundTag: "WARP",
+                            property: "domain",
+                            data: this.settingsData.domains.netflix
+                        });
+                    },
+                    set: function (newValue) {
+                        this.templateRuleSetter({
+                            newValue,
+                            outboundTag: "WARP",
+                            property: "domain",
+                            data: this.settingsData.domains.netflix
+                        });
+                    },
+                },
+                SpotifyWARPSettings: {
+                    get: function () {
+                        return this.templateRuleGetter({
+                            outboundTag: "WARP",
+                            property: "domain",
+                            data: this.settingsData.domains.spotify
+                        });
+                    },
+                    set: function (newValue) {
+                        this.templateRuleSetter({
+                            newValue,
+                            outboundTag: "WARP",
+                            property: "domain",
+                            data: this.settingsData.domains.spotify
+                        });
+                    },
+                },
+                IRWARPSettings: {
+                    get: function () {
+                        return this.templateRuleGetter({
+                            outboundTag: "WARP",
+                            property: "domain",
+                            data: this.settingsData.domains.ir
+                        });
+                    },
+                    set: function (newValue) {
+                        this.templateRuleSetter({
+                            newValue,
+                            outboundTag: "WARP",
+                            property: "domain",
+                            data: this.settingsData.domains.ir
+                        });
+                    },
+                },
+            }
+        });
 
-</script>
+    </script>
 </body>
-</html>
+
+</html>

+ 18 - 27
web/service/config.json

@@ -1,25 +1,22 @@
 {
   "log": {
     "loglevel": "warning",
-    "access": "./access.log"
+    "access": "./access.log",
+    "error": "./error.log"
   },
   "api": {
-    "services": [
-      "HandlerService",
-      "LoggerService",
-      "StatsService"
-    ],
-    "tag": "api"
+    "tag": "api",
+    "services": ["HandlerService", "LoggerService", "StatsService"]
   },
   "inbounds": [
     {
+      "tag": "api",
       "listen": "127.0.0.1",
       "port": 62789,
       "protocol": "dokodemo-door",
       "settings": {
         "address": "127.0.0.1"
-      },
-      "tag": "api"
+      }
     }
   ],
   "outbounds": [
@@ -28,16 +25,16 @@
       "settings": {}
     },
     {
+      "tag": "blocked",
       "protocol": "blackhole",
-      "settings": {},
-      "tag": "blocked"
+      "settings": {}
     }
   ],
   "policy": {
     "levels": {
       "0": {
-        "statsUserUplink": true,
-        "statsUserDownlink": true
+        "statsUserDownlink": true,
+        "statsUserUplink": true
       }
     },
     "system": {
@@ -49,27 +46,21 @@
     "domainStrategy": "IPIfNonMatch",
     "rules": [
       {
-        "inboundTag": [
-          "api"
-        ],
-        "outboundTag": "api",
-        "type": "field"
+        "type": "field",
+        "inboundTag": ["api"],
+        "outboundTag": "api"
       },
       {
+        "type": "field",
         "outboundTag": "blocked",
-        "ip": [
-          "geoip:private"
-        ],
-        "type": "field"
+        "ip": ["geoip:private"]
       },
       {
+        "type": "field",
         "outboundTag": "blocked",
-        "protocol": [
-          "bittorrent"
-        ],
-        "type": "field"
+        "protocol": ["bittorrent"]
       }
     ]
   },
   "stats": {}
-}
+}

+ 10 - 0
web/service/setting.go

@@ -2,6 +2,7 @@ package service
 
 import (
 	_ "embed"
+	"encoding/json"
 	"errors"
 	"fmt"
 	"reflect"
@@ -42,6 +43,15 @@ var defaultValueMap = map[string]string{
 type SettingService struct {
 }
 
+func (s *SettingService) GetDefaultJsonConfig() (interface{}, error) {
+	var jsonData interface{}
+	err := json.Unmarshal([]byte(xrayTemplateConfig), &jsonData)
+	if err != nil {
+		return nil, err
+	}
+	return jsonData, nil
+}
+
 func (s *SettingService) GetAllSetting() (*entity.AllSetting, error) {
 	db := database.GetDB()
 	settings := make([]*model.Setting, 0)

+ 38 - 5
web/translation/translate.en_US.toml

@@ -196,6 +196,8 @@
 "save" = "Save"
 "restartPanel" = "Restart Panel"
 "restartPanelDesc" = "Are you sure you want to restart the panel? Click OK to restart after 3 seconds. If you cannot access the panel after restarting, please go to the server to view the panel log information"
+"actions" = "Actions"
+"resetDefaultConfig" = "Reset to default config"
 "panelConfig" = "Panel Configuration"
 "userSetting" = "User Setting"
 "xrayConfiguration" = "Xray Configuration"
@@ -215,18 +217,49 @@
 "currentPassword" = "Current Password"
 "newUsername" = "New Username"
 "newPassword" = "New Password"
-"advancedTemplate" = "Advanced template parts"
-"completeTemplate" = "Complete template of Xray configuration"
+"basicTemplate" = "Basic Template"
+"advancedTemplate" = "Advanced Template parts"
+"completeTemplate" = "Complete Template of Xray configuration"
+"generalConfigs" = "General Configs"
+"countryConfigs" = "Country Configs"
+"ipv4Configs" = "IPv4 Configs"
+"warpConfigs" = "WARP Configs"
 "xrayConfigTemplate" = "Xray Configuration Template"
 "xrayConfigTemplateDesc" = "Generate the final xray configuration file based on this template, restart the panel to take effect."
 "xrayConfigTorrent" = "Ban bittorrent usage"
 "xrayConfigTorrentDesc" = "Change the configuration template to avoid using bittorrent by users, restart the panel to take effect"
 "xrayConfigPrivateIp" = "Ban private IP ranges to connect"
 "xrayConfigPrivateIpDesc" = "Change the configuration template to avoid connecting with private IP ranges, restart the panel to take effect"
+"xrayConfigAds" = "Block Ads"
+"xrayConfigAdsDesc" = "Change the configuration template to block Ads, restart the panel to take effect"
+"xrayConfigPorn" = "Ban Porn websites to connect"
+"xrayConfigPornDesc" = "Change the configuration template to avoid connecting to Porn websites, restart the panel to take effect"
 "xrayConfigIRIp" = "Ban Iran IP ranges to connect"
 "xrayConfigIRIpDesc" = "Change the configuration template to avoid connecting with Iran IP ranges, restart the panel to take effect"
-"xrayConfigIRdomain" = "Ban IR domains to connect"
-"xrayConfigIRdomainDesc" = "Change the configuration template to avoid connecting with IR domains, restart the panel to take effect"
+"xrayConfigIRDomain" = "Ban Iran Domains to connect"
+"xrayConfigIRDomainDesc" = "Change the configuration template to avoid connecting with Iran domains, restart the panel to take effect"
+"xrayConfigChinaIp" = "Ban China IP ranges to connect"
+"xrayConfigChinaIpDesc" = "Change the configuration template to avoid connecting with China IP ranges, restart the panel to take effect"
+"xrayConfigChinaDomain" = "Ban China Domains to connect"
+"xrayConfigChinaDomainDesc" = "Change the configuration template to avoid connecting with China domains, restart the panel to take effect"
+"xrayConfigRussiaIp" = "Ban Russia IP ranges to connect"
+"xrayConfigRussiaIpDesc" = "Change the configuration template to avoid connecting with Russia IP ranges, restart the panel to take effect"
+"xrayConfigRussiaDomain" = "Ban Russia Domains to connect"
+"xrayConfigRussiaDomainDesc" = "Change the configuration template to avoid connecting with Russia domains, restart the panel to take effect"
+"xrayConfigGoogleIPv4" = "Use IPv4 for Google"
+"xrayConfigGoogleIPv4Desc" = "Add routing for google to connect with IPv4, restart the panel to take effect"
+"xrayConfigNetflixIPv4" = "Use IPv4 for Netflix"
+"xrayConfigNetflixIPv4Desc" = "Add routing for Netflix to connect with IPv4, restart the panel to take effect"
+"xrayConfigGoogleWARP" = "Route Google to WARP"
+"xrayConfigGoogleWARPDesc" = "Add routing for Google to WARP, restart the panel to take effect"
+"xrayConfigOpenAIWARP" = "Route OpenAI (ChatGPT) to WARP"
+"xrayConfigOpenAIWARPDesc" = "Add routing for OpenAI (ChatGPT) to WARP, restart the panel to take effect"
+"xrayConfigNetflixWARP" = "Route Netflix to WARP"
+"xrayConfigNetflixWARPDesc" = "Add routing for Netflix to WARP, restart the panel to take effect"
+"xrayConfigSpotifyWARP" = "Route Spotify to WARP"
+"xrayConfigSpotifyWARPDesc" = "Add routing for Spotify to WARP, restart the panel to take effect"
+"xrayConfigIRWARP" = "Route Iran Domains to WARP"
+"xrayConfigIRWARPDesc" = "Add routing for Iran Domains to WARP. restart the panel to take effect"
 "xrayConfigInbounds" = "Configuration of Inbounds"
 "xrayConfigInboundsDesc" = "Change the configuration template to accept special clients, restart the panel to take effect"
 "xrayConfigOutbounds" = "Configuration of Outbounds"
@@ -257,4 +290,4 @@
 "getSetting" = "Get setting"
 "modifyUser" = "Modify user"
 "originalUserPassIncorrect" = "The original user name or original password is incorrect"
-"userPassMustBeNotEmpty" = "New username and new password cannot be empty"
+"userPassMustBeNotEmpty" = "New username and new password cannot be empty"

+ 40 - 7
web/translation/translate.fa_IR.toml

@@ -194,6 +194,8 @@
 "save" = "ذخیره"
 "restartPanel" = "ریستارت پنل"
 "restartPanelDesc" = "آیا مطمئن هستید که می خواهید پنل را دوباره راه اندازی کنید؟ برای راه اندازی مجدد روی OK کلیک کنید. اگر بعد از 3 ثانیه نمی توانید به پنل دسترسی پیدا کنید، لطفاً برای مشاهده اطلاعات گزارش پانل به سرور برگردید"
+"actions" = "عملیات ها"
+"resetDefaultConfig" = "برگشت به تنظیمات پیشفرض"
 "panelConfig" = "تنظیمات پنل"
 "userSetting" = "تنظیمات مدیر"
 "xrayConfiguration" = "تنظیمات Xray"
@@ -213,18 +215,49 @@
 "currentPassword" = "رمز عبور فعلی"
 "newUsername" = "نام کاربری جدید"
 "newPassword" = "رمز عبور جدید"
+"basicTemplate" = "بخش پایه"
 "advancedTemplate" = "بخش های پیشرفته الگو"
 "completeTemplate" = "الگوی کامل تنظیمات ایکس ری"
+"generalConfigs" = "تنظیمات عمومی"
+"countryConfigs" = "تنظیمات برای کشورها"
+"ipv4Configs" = "تنظیمات برای IPv4"
+"warpConfigs" = "تنظیمات برای WARP"
 "xrayConfigTemplate" = "تنظیمات الگو ایکس ری"
 "xrayConfigTemplateDesc" = "فایل پیکربندی ایکس ری نهایی بر اساس این الگو ایجاد میشود. لطفاً این را تغییر ندهید مگر اینکه دقیقاً بدانید که چه کاری انجام می دهید! پنل را مجدداً راه اندازی کنید تا اعمال شود"
 "xrayConfigTorrent" = "فیلتر کردن بیت تورنت"
 "xrayConfigTorrentDesc" = "الگوی تنظیمات را برای فیلتر کردن پروتکل بیت تورنت برای کاربران تغییر میدهد.  پنل را مجدداً راه اندازی کنید تا اعمال شود"
-"xrayConfigPrivateIp" = "جلوگیری از اتصال آی پی های نامعتبر"
-"xrayConfigPrivateIpDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال آی پی های نامعتبر و بسته های سرگردان تغییر میدهد.  پنل را مجدداً راه اندازی کنید تا اعمال شود"
-"xrayConfigIRIp" = "جلوگیری از اتصال آی پی های ایران"
-"xrayConfigIRIpDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال آی پی های ایران تغییر میدهد.  پنل را مجدداً راه اندازی کنید تا اعمال شود"
-"xrayConfigIRdomain" = "جلوگیری از اتصال دامنه های ایران"
-"xrayConfigIRdomainDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال دامنه های ایران تغییر میدهد.  پنل را مجدداً راه اندازی کنید تا اعمال شود"
+"xrayConfigPrivateIp" = "جلوگیری از اتصال آیپی های خصوصی یا محلی"
+"xrayConfigPrivateIpDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال آیپی های خصوصی یا محلی و بسته های سرگردان تغییر میدهد.  پنل را مجدداً راه اندازی کنید تا اعمال شود"
+"xrayConfigAds" = "مسدود کردن تبلیغات"
+"xrayConfigAdsDesc" = "الگوی تنظیمات را برای مسدود کردن تبلیغات تغییر میدهد.  پنل را مجدداً راه اندازی کنید تا اعمال شود"
+"xrayConfigPorn" = "جلوگیری از اتصال به سایت های پورن"
+"xrayConfigPornDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال به سایت های پورن تغییر میدهد.  پنل را مجدداً راه اندازی کنید تا اعمال شود"
+"xrayConfigIRIp" = "جلوگیری از اتصال آیپی های ایران"
+"xrayConfigIRIpDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال آیپی های ایران تغییر میدهد.  پنل را مجدداً راه اندازی کنید تا اعمال شود"
+"xrayConfigIRDomain" = "جلوگیری از اتصال دامنه های ایران"
+"xrayConfigIRDomainDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال دامنه های ایران تغییر میدهد.  پنل را مجدداً راه اندازی کنید تا اعمال شود"
+"xrayConfigChinaIp" = "جلوگیری از اتصال آیپی های چین"
+"xrayConfigChinaIpDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال آیپی های چین تغییر میدهد.  پنل را مجدداً راه اندازی کنید تا اعمال شود"
+"xrayConfigChinaDomain" = "جلوگیری از اتصال دامنه های چین"
+"xrayConfigChinaDomainDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال دامنه های چین تغییر میدهد.  پنل را مجدداً راه اندازی کنید تا اعمال شود"
+"xrayConfigRussiaIp" = "جلوگیری از اتصال آیپی های روسیه"
+"xrayConfigRussiaIpDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال آیپی های روسیه تغییر میدهد.  پنل را مجدداً راه اندازی کنید تا اعمال شود"
+"xrayConfigRussiaDomain" = "جلوگیری از اتصال دامنه های روسیه"
+"xrayConfigRussiaDomainDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال دامنه های روسیه تغییر میدهد.  پنل را مجدداً راه اندازی کنید تا اعمال شود"
+"xrayConfigGoogleIPv4" = "استفاده از آیپی ورژن 4 برای اتصال به گوگل"
+"xrayConfigGoogleIPv4Desc" = "مسیردهی جدید برای اتصال به گوگل با آیپی ورژن 4 اضافه میکند.  پنل را مجدداً راه اندازی کنید تا اعمال شود"
+"xrayConfigNetflixIPv4" = "استفاده از آیپی ورژن 4 برای اتصال به نتفلیکس"
+"xrayConfigNetflixIPv4Desc" = "مسیردهی جدید برای اتصال به نتفلیکس با آیپی ورژن 4 اضافه میکند.  پنل را مجدداً راه اندازی کنید تا اعمال شود"
+"xrayConfigGoogleWARP" = "مسیردهی گوگل به WARP"
+"xrayConfigGoogleWARPDesc" = "مسیردهی جدید برای اتصال به گوگل به WARP اضافه میکند. پنل را مجدداً راه اندازی کنید تا اعمال شود"
+"xrayConfigOpenAIWARP" = "مسیردهی OpenAI (ChatGPT) به WARP"
+"xrayConfigOpenAIWARPDesc" = "مسیردهی جدید برای اتصال به OpenAI (ChatGPT) به WARP اضافه میکند. پنل را مجدداً راه اندازی کنید تا اعمال شود"
+"xrayConfigNetflixWARP" = "مسیردهی نتفلیکس به WARP"
+"xrayConfigNetflixWARPDesc" = "مسیردهی جدید برای اتصال به نتفلیکس به WARP اضافه میکند. پنل را مجدداً راه اندازی کنید تا اعمال شود"
+"xrayConfigSpotifyWARP" = "مسیردهی اسپاتیفای به WARP"
+"xrayConfigSpotifyWARPDesc" = "مسیردهی جدید برای اتصال به اسپاتیفای به WARP اضافه میکند. پنل را مجدداً راه اندازی کنید تا اعمال شود"
+"xrayConfigIRWARP" = "مسیردهی دامنه های ایران به WARP"
+"xrayConfigIRWARPDesc" = "مسیردهی جدید برای اتصال به دامنه های ایران به WARP اضافه میکند. پنل را مجدداً راه اندازی کنید تا اعمال شود"
 "xrayConfigInbounds" = "تنظیمات ورودی"
 "xrayConfigInboundsDesc" = "میتوانید الگوی تنظیمات را برای ورودی های خاص تنظیم نمایید.  پنل را مجدداً راه اندازی کنید تا اعمال شود"
 "xrayConfigOutbounds" = "تنظیمات خروجی"
@@ -255,4 +288,4 @@
 "getSetting" = "دریافت تنظیمات"
 "modifyUser" = "ویرایش کاربر"
 "originalUserPassIncorrect" = "نام کاربری و رمز عبور فعلی اشتباه می باشد ."
-"userPassMustBeNotEmpty" = "نام کاربری و رمز عبور جدید نمیتواند خالی باشد ."
+"userPassMustBeNotEmpty" = "نام کاربری و رمز عبور جدید نمیتواند خالی باشد ."

+ 38 - 5
web/translation/translate.zh_Hans.toml

@@ -194,6 +194,8 @@
 "save" = "保存配置"
 "restartPanel" = "重启面板"
 "restartPanelDesc" = "确定要重启面板吗?点击确定将于 3 秒后重启,若重启后无法访问面板,请前往服务器查看面板日志信息"
+"actions" = "动作"
+"resetDefaultConfig" = "重置为默认配置"
 "panelConfig" = "面板配置"
 "userSetting" = "用户设置"
 "xrayConfiguration" = "xray 相关设置"
@@ -213,18 +215,49 @@
 "currentPassword" = "原密码"
 "newUsername" = "新用户名"
 "newPassword" = "新密码"
+"basicTemplate" = "基本模板"
 "advancedTemplate" = "高级模板部件"
 "completeTemplate" = "Xray 配置的完整模板"
+"generalConfigs" = "一般配置"
+"countryConfigs" = "国家配置"
+"ipv4Configs" = "IPv4 配置"
+"warpConfigs" = "WARP 配置"
 "xrayConfigTemplate" = "xray 配置模板"
 "xrayConfigTemplateDesc" = "以该模型为基础生成最终的xray配置文件,重新启动面板生成效率"
 "xrayConfigTorrent" = "禁止使用 bittorrent"
 "xrayConfigTorrentDesc" = "更改配置模板避免用户使用bittorrent,重启面板生效"
-"xrayConfigPrivateIp" = "禁止私人 ip 范围连接"
+"xrayConfigPrivateIp" = "禁止私人 IP 范围连接"
 "xrayConfigPrivateIpDesc" = "更改配置模板以避免连接私有 IP 范围,重启面板生效"
+"xrayConfigAds" = "屏蔽广告"
+"xrayConfigAdsDesc" = "修改配置模板屏蔽广告,重启面板生效"
+"xrayConfigPorn" = "禁止色情网站连接"
+"xrayConfigPornDesc" = "更改配置模板避免连接色情网站,重启面板生效"
 "xrayConfigIRIp" = "禁止伊朗 IP 范围连接"
-"xrayConfigIRIpDesc" = "修改配置模板避免连接伊朗IP范围,重启面板生效"
-"xrayConfigIRdomain" = "禁止伊朗域连接"
-"xrayConfigIRdomainDesc" = "修改配置模板避免连接伊朗域名,重启面板生效"
+"xrayConfigIRIpDesc" = "修改配置模板避免连接伊朗IP段,重启面板生效"
+"xrayConfigIRDomain" = "禁止伊朗域连接"
+"xrayConfigIRDomainDesc" = "更改配置模板避免连接伊朗域名,重启面板生效"
+"xrayConfigChinaIp" = "禁止中国 IP 范围连接"
+"xrayConfigChinaIpDesc" = "修改配置模板避免连接中国IP段,重启面板生效"
+"xrayConfigChinaDomain" = "禁止中国域名连接"
+"xrayConfigChinaDomainDesc" = "更改配置模板避免连接中国域,重启面板生效"
+"xrayConfigRussiaIp" = "禁止俄罗斯 IP 范围连接"
+"xrayConfigRussiaIpDesc" = "修改配置模板避免连接俄罗斯IP范围,重启面板生效"
+"xrayConfigRussiaDomain" = "禁止俄罗斯域连接"
+"xrayConfigRussiaDomainDesc" = "更改配置模板避免连接俄罗斯域,重启面板生效"
+"xrayConfigGoogleIPv4" = "为谷歌使用 IPv4"
+"xrayConfigGoogleIPv4Desc" = "添加谷歌连接IPv4的路由,重启面板生效"
+"xrayConfigNetflixIPv4" = "为 Netflix 使用 IPv4"
+"xrayConfigNetflixIPv4Desc" = "添加Netflix连接IPv4的路由,重启面板生效"
+"xrayConfigGoogleWARP" = "将谷歌路由到 WARP"
+"xrayConfigGoogleWARPDesc" = "为谷歌添加路由到WARP,重启面板生效"
+"xrayConfigOpenAIWARP" = "将 OpenAI (ChatGPT) 路由到 WARP"
+"xrayConfigOpenAIWARPDesc" = "将OpenAI(ChatGPT)路由添加到WARP,重启面板生效"
+"xrayConfigNetflixWARP" = "将 Netflix 路由到 WARP"
+"xrayConfigNetflixWARPDesc" = "为Netflix添加路由到WARP,重启面板生效"
+"xrayConfigSpotifyWARP" = "将 Spotify 路由到 WARP"
+"xrayConfigSpotifyWARPDesc" = "为Spotify添加路由到WARP,重启面板生效"
+"xrayConfigIRWARP" = "将伊朗域名路由到 WARP"
+"xrayConfigIRWARPDesc" = "将伊朗域的路由添加到 WARP。 重启面板生效"
 "xrayConfigInbounds" = "入站配置"
 "xrayConfigInboundsDesc" = "更改配置模板接受特殊客户端,重启面板生效"
 "xrayConfigOutbounds" = "出站配置"
@@ -255,4 +288,4 @@
 "getSetting" = "获取设置"
 "modifyUser" = "修改用户"
 "originalUserPassIncorrect" = "原用户名或原密码错误"
-"userPassMustBeNotEmpty" = "新用户名和新密码不能为空"
+"userPassMustBeNotEmpty" = "新用户名和新密码不能为空"

+ 107 - 104
x-ui.sh

@@ -17,6 +17,7 @@ function LOGE() {
 function LOGI() {
     echo -e "${green}[INF] $* ${plain}"
 }
+
 # check root
 [[ $EUID -ne 0 ]] && LOGE "ERROR: You must be root to run this script! \n" && exit 1
 
@@ -34,7 +35,6 @@ fi
 
 echo "The OS release is: $release"
 
-
 os_version=""
 os_version=$(grep -i version_id /etc/os-release | cut -d \" -f2 | cut -d . -f1)
 
@@ -44,21 +44,18 @@ if [[ "${release}" == "centos" ]]; then
     fi
 elif [[ "${release}" ==  "ubuntu" ]]; then
     if [[ ${os_version} -lt 20 ]]; then
-        echo -e "${red}please use Ubuntu 20 or higher version${plain}\n" && exit 1
+        echo -e "${red}please use Ubuntu 20 or higher version! ${plain}\n" && exit 1
     fi
-
 elif [[ "${release}" == "fedora" ]]; then
     if [[ ${os_version} -lt 36 ]]; then
-        echo -e "${red}please use Fedora 36 or higher version${plain}\n" && exit 1
+        echo -e "${red}please use Fedora 36 or higher version! ${plain}\n" && exit 1
     fi
-
 elif [[ "${release}" == "debian" ]]; then
     if [[ ${os_version} -lt 10 ]]; then
         echo -e "${red} Please use Debian 10 or higher ${plain}\n" && exit 1
     fi
 fi
 
-
 confirm() {
     if [[ $# > 1 ]]; then
         echo && read -p "$1 [Default$2]: " temp
@@ -133,7 +130,7 @@ uninstall() {
     rm /usr/local/x-ui/ -rf
 
     echo ""
-    echo -e "Uninstalled Successfully,If you want to remove this script,then after exiting the script run ${green}rm /usr/bin/x-ui -f${plain} to delete it."
+    echo -e "Uninstalled Successfully, If you want to remove this script, then after exiting the script run ${green}rm /usr/bin/x-ui -f${plain} to delete it."
     echo ""
 
     if [[ $# == 0 ]]; then
@@ -150,12 +147,12 @@ reset_user() {
         return 0
     fi
     /usr/local/x-ui/x-ui setting -username admin -password admin
-    echo -e "Username and password have been reset to ${green}admin${plain}Please restart the panel now."
+    echo -e "Username and password have been reset to ${green}admin${plain}, Please restart the panel now."
     confirm_restart
 }
 
 reset_config() {
-    confirm "Are you sure you want to reset all panel settings,Account data will not be lost,Username and password will not change" "n"
+    confirm "Are you sure you want to reset all panel settings, Account data will not be lost, Username and password will not change" "n"
     if [[ $? != 0 ]]; then
         if [[ $# == 0 ]]; then
             show_menu
@@ -163,14 +160,14 @@ reset_config() {
         return 0
     fi
     /usr/local/x-ui/x-ui setting -reset
-    echo -e "All panel settings have been reset to default,Please restart the panel now,and use the default ${green}2053${plain} Port to Access the web Panel"
+    echo -e "All panel settings have been reset to default, Please restart the panel now, and use the default ${green}2053${plain} Port to Access the web Panel"
     confirm_restart
 }
 
 check_config() {
     info=$(/usr/local/x-ui/x-ui setting -show true)
     if [[ $? != 0 ]]; then
-        LOGE "get current settings error,please check logs"
+        LOGE "get current settings error, please check logs"
         show_menu
     fi
     LOGI "${info}"
@@ -183,7 +180,7 @@ set_port() {
         before_show_menu
     else
         /usr/local/x-ui/x-ui setting -port ${port}
-        echo -e "The port is set,Please restart the panel now,and use the new port ${green}${port}${plain} to access web panel"
+        echo -e "The port is set, Please restart the panel now, and use the new port ${green}${port}${plain} to access web panel"
         confirm_restart
     fi
 }
@@ -192,7 +189,7 @@ start() {
     check_status
     if [[ $? == 0 ]]; then
         echo ""
-        LOGI "Panel is running,No need to start again,If you need to restart, please select restart"
+        LOGI "Panel is running, No need to start again, If you need to restart, please select restart"
     else
         systemctl start x-ui
         sleep 2
@@ -200,7 +197,7 @@ start() {
         if [[ $? == 0 ]]; then
             LOGI "x-ui Started Successfully"
         else
-            LOGE "panel Failed to start,Probably because it takes longer than two seconds to start,Please check the log information later"
+            LOGE "panel Failed to start, Probably because it takes longer than two seconds to start, Please check the log information later"
         fi
     fi
 
@@ -213,7 +210,7 @@ stop() {
     check_status
     if [[ $? == 1 ]]; then
         echo ""
-        LOGI "Panel stoppedNo need to stop again!"
+        LOGI "Panel stopped, No need to stop again!"
     else
         systemctl stop x-ui
         sleep 2
@@ -221,7 +218,7 @@ stop() {
         if [[ $? == 1 ]]; then
             LOGI "x-ui and xray stopped successfully"
         else
-            LOGE "Panel stop failed,Probably because the stop time exceeds two seconds,Please check the log information later"
+            LOGE "Panel stop failed, Probably because the stop time exceeds two seconds, Please check the log information later"
         fi
     fi
 
@@ -237,7 +234,7 @@ restart() {
     if [[ $? == 0 ]]; then
         LOGI "x-ui and xray Restarted successfully"
     else
-        LOGE "Panel restart failed,Probably because it takes longer than two seconds to start,Please check the log information later"
+        LOGE "Panel restart failed, Probably because it takes longer than two seconds to start, Please check the log information later"
     fi
     if [[ $# == 0 ]]; then
         before_show_menu
@@ -285,51 +282,49 @@ show_log() {
 }
 
 enable_bbr() {
+    if grep -q "net.core.default_qdisc=fq" /etc/sysctl.conf && grep -q "net.ipv4.tcp_congestion_control=bbr" /etc/sysctl.conf; then
+        echo -e "${green}BBR is already enabled!${plain}"
+        exit 0
+    fi
 
-if grep -q "net.core.default_qdisc=fq" /etc/sysctl.conf && grep -q "net.ipv4.tcp_congestion_control=bbr" /etc/sysctl.conf; then
-  echo -e "${green}BBR is already enabled!${plain}"
-  exit 0
-fi
-
-# Check the OS and install necessary packages
-if [[ "$(cat /etc/os-release | grep -E '^ID=' | awk -F '=' '{print $2}')" == "ubuntu" ]]; then
-  sudo apt-get update && sudo apt-get install -yqq --no-install-recommends ca-certificates
-elif [[ "$(cat /etc/os-release | grep -E '^ID=' | awk -F '=' '{print $2}')" == "debian" ]]; then
-  sudo apt-get update && sudo apt-get install -yqq --no-install-recommends ca-certificates
-elif [[ "$(cat /etc/os-release | grep -E '^ID=' | awk -F '=' '{print $2}')" == "fedora" ]]; then
-  sudo dnf -y update && sudo dnf -y install ca-certificates
-elif [[ "$(cat /etc/os-release | grep -E '^ID=' | awk -F '=' '{print $2}')" == "centos" ]]; then
-  sudo yum -y update && sudo yum -y install ca-certificates
-else
-  echo "Unsupported operating system. Please check the script and install the necessary packages manually."
-  exit 1
-fi
-
-# Enable BBR
-echo "net.core.default_qdisc=fq" | sudo tee -a /etc/sysctl.conf
-echo "net.ipv4.tcp_congestion_control=bbr" | sudo tee -a /etc/sysctl.conf
+    # Check the OS and install necessary packages
+    if [[ "$(cat /etc/os-release | grep -E '^ID=' | awk -F '=' '{print $2}')" == "ubuntu" ]]; then
+        sudo apt-get update && sudo apt-get install -yqq --no-install-recommends ca-certificates
+    elif [[ "$(cat /etc/os-release | grep -E '^ID=' | awk -F '=' '{print $2}')" == "debian" ]]; then
+        sudo apt-get update && sudo apt-get install -yqq --no-install-recommends ca-certificates
+    elif [[ "$(cat /etc/os-release | grep -E '^ID=' | awk -F '=' '{print $2}')" == "fedora" ]]; then
+        sudo dnf -y update && sudo dnf -y install ca-certificates
+    elif [[ "$(cat /etc/os-release | grep -E '^ID=' | awk -F '=' '{print $2}')" == "centos" ]]; then
+        sudo yum -y update && sudo yum -y install ca-certificates
+    else
+        echo "Unsupported operating system. Please check the script and install the necessary packages manually."
+        exit 1
+    fi
 
-# Apply changes
-sudo sysctl -p
+    # Enable BBR
+    echo "net.core.default_qdisc=fq" | sudo tee -a /etc/sysctl.conf
+    echo "net.ipv4.tcp_congestion_control=bbr" | sudo tee -a /etc/sysctl.conf
 
-# Verify that BBR is enabled
-if [[ $(sysctl net.ipv4.tcp_congestion_control | awk '{print $3}') == "bbr" ]]; then
-  echo -e "${green}BBR has been enabled successfully.${plain}"
-else
-  echo -e "${red}Failed to enable BBR. Please check your system configuration.${plain}"
-fi
+    # Apply changes
+    sudo sysctl -p
 
+    # Verify that BBR is enabled
+    if [[ $(sysctl net.ipv4.tcp_congestion_control | awk '{print $3}') == "bbr" ]]; then
+        echo -e "${green}BBR has been enabled successfully.${plain}"
+    else
+        echo -e "${red}Failed to enable BBR. Please check your system configuration.${plain}"
+    fi
 }
 
 update_shell() {
     wget -O /usr/bin/x-ui -N --no-check-certificate https://github.com/MHSanaei/3x-ui/raw/main/x-ui.sh
     if [[ $? != 0 ]]; then
         echo ""
-        LOGE "Failed to download scriptPlease check whether the machine can connect Github"
+        LOGE "Failed to download script, Please check whether the machine can connect Github"
         before_show_menu
     else
         chmod +x /usr/bin/x-ui
-        LOGI "Upgrade script succeededPlease rerun the script" && exit 0
+        LOGI "Upgrade script succeeded, Please rerun the script" && exit 0
     fi
 }
 
@@ -359,7 +354,7 @@ check_uninstall() {
     check_status
     if [[ $? != 2 ]]; then
         echo ""
-        LOGE "Panel installedPlease do not reinstall"
+        LOGE "Panel installed, Please do not reinstall"
         if [[ $# == 0 ]]; then
             before_show_menu
         fi
@@ -455,69 +450,76 @@ ssl_cert_issue() {
 }
 
 open_ports() {
-if ! command -v ufw &> /dev/null
-then
-    echo "ufw firewall is not installed. Installing now..."
-    sudo apt-get update
-    sudo apt-get install -y ufw
-else
-    echo "ufw firewall is already installed"
-fi
-
-  # Check if the firewall is inactive
-  if sudo ufw status | grep -q "Status: active"; then
-    echo "firewall is already active"
-  else
-    # Open the necessary ports
-    sudo ufw allow ssh
-    sudo ufw allow http
-    sudo ufw allow https
-    sudo ufw allow 2053/tcp
-
-    # Enable the firewall
-    sudo ufw --force enable
-  fi
-
-  # Prompt the user to enter a list of ports
-  read -p "Enter the ports you want to open (e.g. 80,443,2053 or range 400-500): " ports
-
-  # Check if the input is valid
-  if ! [[ $ports =~ ^([0-9]+|[0-9]+-[0-9]+)(,([0-9]+|[0-9]+-[0-9]+))*$ ]]; then
-     echo "Error: Invalid input. Please enter a comma-separated list of ports or a range of ports (e.g. 80,443,2053 or 400-500)." >&2; exit 1
-  fi
-
-  # Open the specified ports using ufw
-  IFS=',' read -ra PORT_LIST <<< "$ports"
-  for port in "${PORT_LIST[@]}"; do
-    if [[ $port == *-* ]]; then
-      # Split the range into start and end ports
-      start_port=$(echo $port | cut -d'-' -f1)
-      end_port=$(echo $port | cut -d'-' -f2)
-      # Loop through the range and open each port
-      for ((i=start_port; i<=end_port; i++)); do
-        sudo ufw allow $i
-      done
+    if ! command -v ufw &> /dev/null
+    then
+        echo "ufw firewall is not installed. Installing now..."
+        sudo apt-get update
+        sudo apt-get install -y ufw
     else
-      sudo ufw allow "$port"
+        echo "ufw firewall is already installed"
     fi
-  done
 
-  # Confirm that the ports are open
-  sudo ufw status | grep $ports
-}
+    # Check if the firewall is inactive
+    if sudo ufw status | grep -q "Status: active"; then
+        echo "firewall is already active"
+    else
+        # Open the necessary ports
+        sudo ufw allow ssh
+        sudo ufw allow http
+        sudo ufw allow https
+        sudo ufw allow 2053/tcp
+
+        # Enable the firewall
+        sudo ufw --force enable
+    fi
+
+    # Prompt the user to enter a list of ports
+    read -p "Enter the ports you want to open (e.g. 80,443,2053 or range 400-500): " ports
+
+    # Check if the input is valid
+    if ! [[ $ports =~ ^([0-9]+|[0-9]+-[0-9]+)(,([0-9]+|[0-9]+-[0-9]+))*$ ]]; then
+        echo "Error: Invalid input. Please enter a comma-separated list of ports or a range of ports (e.g. 80,443,2053 or 400-500)." >&2; exit 1
+    fi
+
+    # Open the specified ports using ufw
+    IFS=',' read -ra PORT_LIST <<< "$ports"
+    for port in "${PORT_LIST[@]}"; do
+        if [[ $port == *-* ]]; then
+        # Split the range into start and end ports
+        start_port=$(echo $port | cut -d'-' -f1)
+        end_port=$(echo $port | cut -d'-' -f2)
+        # Loop through the range and open each port
+        for ((i=start_port; i<=end_port; i++)); do
+            sudo ufw allow $i
+        done
+        else
+        sudo ufw allow "$port"
+        fi
+    done
 
+    # Confirm that the ports are open
+    sudo ufw status | grep $ports
+}
 
+update_geo() {
+    local defaultBinFolder="/usr/local/x-ui/bin"
+    read -p "Please enter x-ui bin folder path. Leave blank for default. (Default: '${defaultBinFolder}')" binFolder
+    binFolder=${binFolder:-${defaultBinFolder}}
+    if [[ ! -d ${binFolder} ]]; then
+        LOGE "Folder ${binFolder} not exists!"
+        LOGI "making bin folder: ${binFolder}..."
+        mkdir -p ${binFolder}
+    fi
 
-update_geo(){
     systemctl stop x-ui
-    cd /usr/local/x-ui/bin
+    cd ${binFolder}
     rm -f geoip.dat geosite.dat iran.dat
     wget -N https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
     wget -N https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat
     wget -N https://github.com/bootmortis/iran-hosted-domains/releases/latest/download/iran.dat
     systemctl start x-ui
-    echo -e "${green}Geosite and Geoip have been updated successfully!${plain}"
-before_show_menu
+    echo -e "${green}Geosite.dat + Geoip.dat + Iran.dat have been updated successfully in bin folder '${binfolder}'!${plain}"
+    before_show_menu
 }
 
 install_acme() {
@@ -714,10 +716,11 @@ ssl_cert_issue_by_cloudflare() {
         show_menu
     fi
 }
+
 google_recaptcha() {
-  curl -O https://raw.githubusercontent.com/jinwyp/one_click_script/master/install_kernel.sh && chmod +x ./install_kernel.sh && ./install_kernel.sh
-  echo ""
-  before_show_menu
+    curl -O https://raw.githubusercontent.com/jinwyp/one_click_script/master/install_kernel.sh && chmod +x ./install_kernel.sh && ./install_kernel.sh
+    echo ""
+    before_show_menu
 }
 
 run_speedtest() {