Compare commits
33 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8ba46a99ba | ||
|
|
472694a611 | ||
|
|
7c980343f1 | ||
|
|
2dd203e174 | ||
|
|
7084812515 | ||
|
|
64df14f8d5 | ||
|
|
838d0c2625 | ||
|
|
e51c59995c | ||
|
|
c07b2c73d7 | ||
|
|
87acb81496 | ||
|
|
16be454f6d | ||
|
|
ef24174a38 | ||
|
|
f2c28822c1 | ||
|
|
48d6362a69 | ||
|
|
3f2adbd70a | ||
|
|
706c39452b | ||
|
|
8b855a7cb5 | ||
|
|
80759c8951 | ||
|
|
e55f3c37fd | ||
|
|
c87c1017d8 | ||
|
|
43aea38641 | ||
|
|
88744d92b3 | ||
|
|
7b38d02ff0 | ||
|
|
3da6c4d7d9 | ||
|
|
606360ae03 | ||
|
|
e2fd84a6ae | ||
|
|
f56dd43999 | ||
|
|
f0f5163a83 | ||
|
|
373628a6a3 | ||
|
|
a790efb18d | ||
|
|
868224ae97 | ||
|
|
60169bd055 | ||
|
|
33db9d0f90 |
24
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,24 +0,0 @@
|
|||||||
---
|
|
||||||
name: Bug report
|
|
||||||
about: Create a report to help us improve
|
|
||||||
title: ''
|
|
||||||
labels: bug
|
|
||||||
assignees: ''
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Describe the 3x-ui bug**
|
|
||||||
A clear and concise description of what the bug is.
|
|
||||||
|
|
||||||
**Expected behavior**
|
|
||||||
A clear and concise description of what you expected to happen.
|
|
||||||
|
|
||||||
**Screenshots**
|
|
||||||
If applicable, add screenshots to help explain your problem.
|
|
||||||
|
|
||||||
**Server (please complete the following information):**
|
|
||||||
- OS: [e.g. iOS]
|
|
||||||
- Version [e.g. 22]
|
|
||||||
|
|
||||||
**Additional context**
|
|
||||||
Add any other context about the problem here.
|
|
||||||
56
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
name: Issue Report
|
||||||
|
description: "Create a report to help us improve."
|
||||||
|
body:
|
||||||
|
- type: checkboxes
|
||||||
|
id: terms
|
||||||
|
attributes:
|
||||||
|
label: Welcome
|
||||||
|
options:
|
||||||
|
- label: Yes, I'm using the latest major release. Only such installations are supported.
|
||||||
|
required: true
|
||||||
|
- label: Yes, I'm using the supported system. Only such systems are supported.
|
||||||
|
required: true
|
||||||
|
- label: Yes, I have read all WIKI document,nothing can help me in my problem.
|
||||||
|
required: true
|
||||||
|
- label: Yes, I've searched similar issues on GitHub and didn't find any.
|
||||||
|
required: true
|
||||||
|
- label: Yes, I've included all information below (version, config, log, etc).
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: problem
|
||||||
|
attributes:
|
||||||
|
label: Description of the problem,screencshot would be good
|
||||||
|
placeholder: Your problem description
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: version
|
||||||
|
attributes:
|
||||||
|
label: Version of 3x-ui
|
||||||
|
value: |-
|
||||||
|
<details>
|
||||||
|
|
||||||
|
```console
|
||||||
|
# Paste here
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: log
|
||||||
|
attributes:
|
||||||
|
label: x-ui log reports or xray log
|
||||||
|
value: |-
|
||||||
|
<details>
|
||||||
|
|
||||||
|
```console
|
||||||
|
# paste log here
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
1
.github/workflows/release.yml
vendored
@@ -27,6 +27,7 @@ jobs:
|
|||||||
rm -f Xray-linux-64.zip geoip.dat geosite.dat
|
rm -f Xray-linux-64.zip geoip.dat geosite.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/geoip.dat
|
||||||
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.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
|
||||||
mv xray xray-linux-amd64
|
mv xray xray-linux-amd64
|
||||||
cd ..
|
cd ..
|
||||||
cd ..
|
cd ..
|
||||||
|
|||||||
14
README.md
@@ -1,8 +1,8 @@
|
|||||||
# 3x-ui
|
# 3x-ui
|
||||||

|
[](https://github.com/MHSanaei/3x-ui/releases)
|
||||||

|
[](#)
|
||||||

|
[](#)
|
||||||

|
[](#)
|
||||||
[](https://www.gnu.org/licenses/gpl-3.0.en.html)
|
[](https://www.gnu.org/licenses/gpl-3.0.en.html)
|
||||||
|
|
||||||
|
|
||||||
@@ -34,6 +34,7 @@ certbot renew --dry-run
|
|||||||
- Port: 2053
|
- Port: 2053
|
||||||
- username and password will be generated randomly if you skip to modify your own security(x-ui "7")
|
- username and password will be generated randomly if you skip to modify your own security(x-ui "7")
|
||||||
- database path: /etc/x-ui/x-ui.db
|
- database path: /etc/x-ui/x-ui.db
|
||||||
|
- xray config path: /usr/local/x-ui/bin/config.json
|
||||||
|
|
||||||
before you set ssl on settings
|
before you set ssl on settings
|
||||||
- http:// ip or domain:2053/xui
|
- http:// ip or domain:2053/xui
|
||||||
@@ -44,8 +45,9 @@ After you set ssl on settings
|
|||||||
# Enable Traffic For Users:
|
# Enable Traffic For Users:
|
||||||
|
|
||||||
**copy and paste to xray Configuration :** (you don't need to do this if you have a fresh install)
|
**copy and paste to xray Configuration :** (you don't need to do this if you have a fresh install)
|
||||||
- [for enable traffic](https://raw.githubusercontent.com/mhsanaei/3x-ui/main/media/for%20enable%20traffic.txt)
|
- [enable traffic](./media/enable-traffic.txt)
|
||||||
- [for enable traffic+block all iran ip address](https://raw.githubusercontent.com/mhsanaei/3x-ui/main/media/for%20enable%20traffic%2Bblock%20all%20iran%20ip.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)
|
||||||
|
|
||||||
# Features
|
# Features
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
1.1.2
|
1.1.4
|
||||||
|
|||||||
3
go.mod
@@ -12,7 +12,7 @@ require (
|
|||||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
|
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
|
||||||
github.com/pelletier/go-toml/v2 v2.0.7
|
github.com/pelletier/go-toml/v2 v2.0.7
|
||||||
github.com/robfig/cron/v3 v3.0.1
|
github.com/robfig/cron/v3 v3.0.1
|
||||||
github.com/shirou/gopsutil/v3 v3.23.2
|
github.com/shirou/gopsutil/v3 v3.23.3
|
||||||
github.com/xtls/xray-core v1.8.0
|
github.com/xtls/xray-core v1.8.0
|
||||||
go.uber.org/atomic v1.10.0
|
go.uber.org/atomic v1.10.0
|
||||||
golang.org/x/text v0.8.0
|
golang.org/x/text v0.8.0
|
||||||
@@ -47,6 +47,7 @@ require (
|
|||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/pires/go-proxyproto v0.6.2 // indirect
|
github.com/pires/go-proxyproto v0.6.2 // indirect
|
||||||
github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b // indirect
|
github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b // indirect
|
||||||
|
github.com/shoenig/go-m1cpu v0.1.4 // indirect
|
||||||
github.com/tklauser/go-sysconf v0.3.11 // indirect
|
github.com/tklauser/go-sysconf v0.3.11 // indirect
|
||||||
github.com/tklauser/numcpus v0.6.0 // indirect
|
github.com/tklauser/numcpus v0.6.0 // indirect
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
|
|||||||
9
go.sum
@@ -137,8 +137,12 @@ github.com/sagernet/sing v0.1.7 h1:g4vjr3q8SUlBZSx97Emz5OBfSMBxxW5Q8C2PfdoSo08=
|
|||||||
github.com/sagernet/sing-shadowsocks v0.1.1 h1:uFK2rlVeD/b1xhDwSMbUI2goWc6fOKxp+ZeKHZq6C9Q=
|
github.com/sagernet/sing-shadowsocks v0.1.1 h1:uFK2rlVeD/b1xhDwSMbUI2goWc6fOKxp+ZeKHZq6C9Q=
|
||||||
github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c h1:vK2wyt9aWYHHvNLWniwijBu/n4pySypiKRhN32u/JGo=
|
github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c h1:vK2wyt9aWYHHvNLWniwijBu/n4pySypiKRhN32u/JGo=
|
||||||
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb h1:XfLJSPIOUX+osiMraVgIrMR27uMXnRJWGm1+GL8/63U=
|
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb h1:XfLJSPIOUX+osiMraVgIrMR27uMXnRJWGm1+GL8/63U=
|
||||||
github.com/shirou/gopsutil/v3 v3.23.2 h1:PAWSuiAszn7IhPMBtXsbSCafej7PqUOvY6YywlQUExU=
|
github.com/shirou/gopsutil/v3 v3.23.3 h1:Syt5vVZXUDXPEXpIBt5ziWsJ4LdSAAxF4l/xZeQgSEE=
|
||||||
github.com/shirou/gopsutil/v3 v3.23.2/go.mod h1:gv0aQw33GLo3pG8SiWKiQrbDzbRY1K80RyZJ7V4Th1M=
|
github.com/shirou/gopsutil/v3 v3.23.3/go.mod h1:lSBNN6t3+D6W5e5nXTxc8KIMMVxAcS+6IJlffjRRlMU=
|
||||||
|
github.com/shoenig/go-m1cpu v0.1.4 h1:SZPIgRM2sEF9NJy50mRHu9PKGwxyyTTJIWvCtgVbozs=
|
||||||
|
github.com/shoenig/go-m1cpu v0.1.4/go.mod h1:Wwvst4LR89UxjeFtLRMrpgRiyY4xPsejnVZym39dbAQ=
|
||||||
|
github.com/shoenig/test v0.6.3 h1:GVXWJFk9PiOjN0KoJ7VrJGH6uLPnqxR7/fe3HUPfE0c=
|
||||||
|
github.com/shoenig/test v0.6.3/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
@@ -211,7 +215,6 @@ golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
|
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
|||||||
27
install.sh
@@ -66,14 +66,33 @@ else
|
|||||||
echo -e "${red}Failed to check the OS version, please contact the author!${plain}" && exit 1
|
echo -e "${red}Failed to check the OS version, please contact the author!${plain}" && exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# This function installs the base packages required for most scripts
|
||||||
install_base() {
|
install_base() {
|
||||||
if [[ "${release}" == "centos" ]]; then
|
# Store the package names in a variable for easy modification
|
||||||
yum install wget curl tar -y
|
local packages="wget curl tar"
|
||||||
|
|
||||||
|
# Check for the package managers and install the packages if they are not already installed
|
||||||
|
if ! command -v wget >/dev/null 2>&1 || ! command -v curl >/dev/null 2>&1 || ! command -v tar >/dev/null 2>&1; then
|
||||||
|
if command -v apt >/dev/null 2>&1; then
|
||||||
|
apt-get update && apt-get install -y $packages
|
||||||
|
elif command -v dnf >/dev/null 2>&1; then
|
||||||
|
dnf install -y $packages
|
||||||
|
elif command -v yum >/dev/null 2>&1; then
|
||||||
|
yum install -y $packages
|
||||||
|
else
|
||||||
|
echo "ERROR: No package managers found. Please install wget, curl, and tar manually."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Print a confirmation message after the installation is complete
|
||||||
|
echo "The following packages have been successfully installed: $packages"
|
||||||
else
|
else
|
||||||
apt install wget curl tar -y
|
# Print a message confirming that the packages are already installed
|
||||||
|
echo "The following packages are already installed: $packages"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#This function will be called when user installed x-ui out of sercurity
|
#This function will be called when user installed x-ui out of sercurity
|
||||||
config_after_install() {
|
config_after_install() {
|
||||||
echo -e "${yellow}Install/update finished! For security it's recommended to modify panel settings ${plain}"
|
echo -e "${yellow}Install/update finished! For security it's recommended to modify panel settings ${plain}"
|
||||||
|
|||||||
BIN
media/1.png
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 50 KiB |
BIN
media/2.png
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 36 KiB |
BIN
media/3.png
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 32 KiB |
BIN
media/4.png
|
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 159 KiB |
@@ -3,7 +3,6 @@
|
|||||||
"loglevel": "warning",
|
"loglevel": "warning",
|
||||||
"access": "./access.log"
|
"access": "./access.log"
|
||||||
},
|
},
|
||||||
|
|
||||||
"api": {
|
"api": {
|
||||||
"services": [
|
"services": [
|
||||||
"HandlerService",
|
"HandlerService",
|
||||||
81
media/enable-traffic+block-IR-domain.txt
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
{
|
||||||
|
"log": {
|
||||||
|
"loglevel": "warning",
|
||||||
|
"access": "./access.log"
|
||||||
|
},
|
||||||
|
"api": {
|
||||||
|
"services": [
|
||||||
|
"HandlerService",
|
||||||
|
"LoggerService",
|
||||||
|
"StatsService"
|
||||||
|
],
|
||||||
|
"tag": "api"
|
||||||
|
},
|
||||||
|
"inbounds": [
|
||||||
|
{
|
||||||
|
"listen": "127.0.0.1",
|
||||||
|
"port": 62789,
|
||||||
|
"protocol": "dokodemo-door",
|
||||||
|
"settings": {
|
||||||
|
"address": "127.0.0.1"
|
||||||
|
},
|
||||||
|
"tag": "api"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outbounds": [
|
||||||
|
{
|
||||||
|
"protocol": "freedom",
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"protocol": "blackhole",
|
||||||
|
"settings": {},
|
||||||
|
"tag": "blocked"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"policy": {
|
||||||
|
"levels": {
|
||||||
|
"0": {
|
||||||
|
"statsUserUplink": true,
|
||||||
|
"statsUserDownlink": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"system": {
|
||||||
|
"statsInboundDownlink": true,
|
||||||
|
"statsInboundUplink": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"routing": {
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"inboundTag": [
|
||||||
|
"api"
|
||||||
|
],
|
||||||
|
"outboundTag": "api",
|
||||||
|
"type": "field"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"domain": [
|
||||||
|
"regexp:.+.ir$"
|
||||||
|
],
|
||||||
|
"outboundTag": "blocked",
|
||||||
|
"type": "field"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"outboundTag": "blocked",
|
||||||
|
"protocol": [
|
||||||
|
"bittorrent"
|
||||||
|
],
|
||||||
|
"type": "field"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"outboundTag": "blocked",
|
||||||
|
"ip": [
|
||||||
|
"geoip:private"
|
||||||
|
],
|
||||||
|
"type": "field"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"stats": {}
|
||||||
|
}
|
||||||
@@ -3,7 +3,6 @@
|
|||||||
"loglevel": "warning",
|
"loglevel": "warning",
|
||||||
"access": "./access.log"
|
"access": "./access.log"
|
||||||
},
|
},
|
||||||
|
|
||||||
"api": {
|
"api": {
|
||||||
"services": [
|
"services": [
|
||||||
"HandlerService",
|
"HandlerService",
|
||||||
4490
web/assets/ant-design-vue@1.7.2/antd.min.css
vendored
BIN
web/assets/favicon.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |
@@ -17,16 +17,15 @@ const VmessMethods = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const SSMethods = {
|
const SSMethods = {
|
||||||
// AES_256_CFB: 'aes-256-cfb',
|
AES_128_GCM: 'aes-128-gcm',
|
||||||
// AES_128_CFB: 'aes-128-cfb',
|
AES_256_GCM: 'aes-256-gcm',
|
||||||
// CHACHA20: 'chacha20',
|
CHACHA20_POLY1305: 'chacha20-poly1305',
|
||||||
// CHACHA20_IETF: 'chacha20-ietf',
|
CHACHA20_IETF_POLY1305: 'chacha20-ietf-poly1305',
|
||||||
CHACHA20_POLY1305: 'chacha20-poly1305',
|
XCHACHA20_POLY1305: 'xchacha20-poly1305',
|
||||||
AES_256_GCM: 'aes-256-gcm',
|
XCHACHA20_IETF_POLY1305: 'xchacha20-ietf-poly1305',
|
||||||
AES_128_GCM: 'aes-128-gcm',
|
|
||||||
BLAKE3_AES_128_GCM: '2022-blake3-aes-128-gcm',
|
BLAKE3_AES_128_GCM: '2022-blake3-aes-128-gcm',
|
||||||
BLAKE3_AES_256_GCM: '2022-blake3-aes-256-gcm',
|
BLAKE3_AES_256_GCM: '2022-blake3-aes-256-gcm',
|
||||||
BLAKE3_CHACHA20_POLY1305: '2022-blake3-chacha20-poly1305',
|
BLAKE3_CHACHA20_POLY1305: '2022-blake3-chacha20-poly1305',
|
||||||
};
|
};
|
||||||
|
|
||||||
const RULE_IP = {
|
const RULE_IP = {
|
||||||
@@ -940,7 +939,6 @@ class Inbound extends XrayCommonClass {
|
|||||||
case Protocols.VMESS:
|
case Protocols.VMESS:
|
||||||
case Protocols.VLESS:
|
case Protocols.VLESS:
|
||||||
case Protocols.TROJAN:
|
case Protocols.TROJAN:
|
||||||
case Protocols.SHADOWSOCKS:
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
@@ -992,7 +990,6 @@ class Inbound extends XrayCommonClass {
|
|||||||
case Protocols.VMESS:
|
case Protocols.VMESS:
|
||||||
case Protocols.VLESS:
|
case Protocols.VLESS:
|
||||||
case Protocols.TROJAN:
|
case Protocols.TROJAN:
|
||||||
case Protocols.SHADOWSOCKS:
|
|
||||||
return true;
|
return true;
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
@@ -1160,7 +1157,7 @@ class Inbound extends XrayCommonClass {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.XTLS) {
|
if (this.XTLS) {
|
||||||
params.set("security", "tls");
|
params.set("security", "xtls");
|
||||||
params.set("alpn", this.stream.tls.alpn);
|
params.set("alpn", this.stream.tls.alpn);
|
||||||
if(this.stream.tls.settings[0].allowInsecure){
|
if(this.stream.tls.settings[0].allowInsecure){
|
||||||
params.set("allowInsecure", "1");
|
params.set("allowInsecure", "1");
|
||||||
@@ -1186,12 +1183,7 @@ class Inbound extends XrayCommonClass {
|
|||||||
if (!ObjectUtil.isEmpty(server)) {
|
if (!ObjectUtil.isEmpty(server)) {
|
||||||
address = server;
|
address = server;
|
||||||
}
|
}
|
||||||
if (settings.method == SSMethods.BLAKE3_AES_128_GCM || settings.method == SSMethods.BLAKE3_AES_256_GCM || settings.method == SSMethods.BLAKE3_CHACHA20_POLY1305) {
|
return 'ss://' + safeBase64(settings.method + ':' + settings.password) + `@${address}:${this.port}#${encodeURIComponent(remark)}`;
|
||||||
return `ss://${settings.method}:${settings.password}@${address}:${this.port}#${encodeURIComponent(remark)}`;
|
|
||||||
} else {
|
|
||||||
return 'ss://' + safeBase64(settings.method + ':' + settings.password + '@' + address + ':' + this.port)
|
|
||||||
+ '#' + encodeURIComponent(remark);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
genTrojanLink(address = '', remark = '', clientIndex = 0) {
|
genTrojanLink(address = '', remark = '', clientIndex = 0) {
|
||||||
@@ -1261,7 +1253,7 @@ class Inbound extends XrayCommonClass {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.XTLS) {
|
if (this.XTLS) {
|
||||||
params.set("security", "tls");
|
params.set("security", "xtls");
|
||||||
params.set("alpn", this.stream.tls.alpn);
|
params.set("alpn", this.stream.tls.alpn);
|
||||||
if(this.stream.tls.settings[0].allowInsecure){
|
if(this.stream.tls.settings[0].allowInsecure){
|
||||||
params.set("allowInsecure", "1");
|
params.set("allowInsecure", "1");
|
||||||
|
|||||||
@@ -15,20 +15,6 @@
|
|||||||
<!-- <a-icon type="laptop"></a-icon>-->
|
<!-- <a-icon type="laptop"></a-icon>-->
|
||||||
<!-- <span>Client</span>-->
|
<!-- <span>Client</span>-->
|
||||||
<!--</a-menu-item>-->
|
<!--</a-menu-item>-->
|
||||||
<a-sub-menu>
|
|
||||||
<template slot="title">
|
|
||||||
<a-icon type="link"></a-icon>
|
|
||||||
<span>{{ i18n "menu.link"}}</span>
|
|
||||||
</template>
|
|
||||||
<a-menu-item key="https://github.com/mhsanaei/3x-ui/">
|
|
||||||
<a-icon type="github"></a-icon>
|
|
||||||
<span>Github</span>
|
|
||||||
</a-menu-item>
|
|
||||||
<a-menu-item key="https://t.me/panel3xui">
|
|
||||||
<a-icon type="usergroup-add"></a-icon>
|
|
||||||
<span>Telegram</span>
|
|
||||||
</a-menu-item>
|
|
||||||
</a-sub-menu>
|
|
||||||
<a-menu-item key="{{ .base_path }}logout">
|
<a-menu-item key="{{ .base_path }}logout">
|
||||||
<a-icon type="logout"></a-icon>
|
<a-icon type="logout"></a-icon>
|
||||||
<span>{{ i18n "menu.logout"}}</span>
|
<span>{{ i18n "menu.logout"}}</span>
|
||||||
|
|||||||
@@ -5,10 +5,10 @@
|
|||||||
</template>
|
</template>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<span slot="label">
|
<span slot="label">
|
||||||
Email
|
<span>{{ i18n "pages.inbounds.Email" }}</span>
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
The Email Must Be Completely Unique
|
<span>{{ i18n "pages.inbounds.EmailDesc" }}</span>
|
||||||
</template>
|
</template>
|
||||||
<a-icon type="sync" @click="getNewEmail(client)"></a-icon>
|
<a-icon type="sync" @click="getNewEmail(client)"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
@@ -26,10 +26,10 @@
|
|||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<span slot="label">
|
<span slot="label">
|
||||||
IP Count Limit
|
<span>{{ i18n "pages.inbounds.IPLimit" }}</span>
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
Disable inbound if more than entered count (0 for disable limit ip)
|
<span>{{ i18n "pages.inbounds.IPLimitDesc" }}</span>
|
||||||
</template>
|
</template>
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
@@ -38,16 +38,16 @@
|
|||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item v-if="client.email && client.limitIp > 0 && isEdit">
|
<a-form-item v-if="client.email && client.limitIp > 0 && isEdit">
|
||||||
<span slot="label">
|
<span slot="label">
|
||||||
IP Log
|
<span>{{ i18n "pages.inbounds.IPLimitlog" }}</span>
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
IPs history Log (before enabling inbound after it has been disabled by IP limit, you should clear the log)
|
<span>{{ i18n "pages.inbounds.IPLimitlogDesc" }}</span>
|
||||||
</template>
|
</template>
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
Clear The Log
|
<span>{{ i18n "pages.inbounds.IPLimitlogclear" }}</span>
|
||||||
</template>
|
</template>
|
||||||
<span style="color: #FF4D4F">
|
<span style="color: #FF4D4F">
|
||||||
<a-icon type="delete" @click="clearDBClientIps(client.email)"></a-icon>
|
<a-icon type="delete" @click="clearDBClientIps(client.email)"></a-icon>
|
||||||
|
|||||||
@@ -8,9 +8,9 @@
|
|||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.network"}}'>
|
<a-form-item label='{{ i18n "pages.inbounds.network"}}'>
|
||||||
<a-select v-model="inbound.settings.network" style="width: 100px;" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
<a-select v-model="inbound.settings.network" style="width: 100px;" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||||
<a-select-option value="tcp,udp">tcp+udp</a-select-option>
|
<a-select-option value="tcp,udp">TCP+UDP</a-select-option>
|
||||||
<a-select-option value="tcp">tcp</a-select-option>
|
<a-select-option value="tcp">TCP</a-select-option>
|
||||||
<a-select-option value="udp">udp</a-select-option>
|
<a-select-option value="udp">UDP</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="FollowRedirect">
|
<a-form-item label="FollowRedirect">
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
{{define "form/shadowsocks"}}
|
{{define "form/shadowsocks"}}
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<a-form-item label='{{ i18n "encryption" }}'>
|
<a-form-item label='{{ i18n "encryption" }}'>
|
||||||
<a-select v-model="inbound.settings.method" style="width: 165px;" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
<a-select v-model="inbound.settings.method" style="width: 250px;" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||||
<a-select-option v-for="method in SSMethods" :value="method">[[ method ]]</a-select-option>
|
<a-select-option v-for="method in SSMethods" :value="method">[[ method ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "password" }}'>
|
<a-form-item label='{{ i18n "password" }}'>
|
||||||
<a-input v-model.trim="inbound.settings.password"></a-input>
|
<a-input v-model.trim="inbound.settings.password" style="width: 250px;"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.network" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.network" }}'>
|
||||||
<a-select v-model="inbound.settings.network" style="width: 100px;" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
<a-select v-model="inbound.settings.network" style="width: 100px;" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||||
<a-select-option value="tcp,udp">tcp+udp</a-select-option>
|
<a-select-option value="tcp,udp">TCP+UDP</a-select-option>
|
||||||
<a-select-option value="tcp">tcp</a-select-option>
|
<a-select-option value="tcp">TCP</a-select-option>
|
||||||
<a-select-option value="udp">udp</a-select-option>
|
<a-select-option value="udp">UDP</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
|
|||||||
@@ -5,10 +5,10 @@
|
|||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<span slot="label">
|
<span slot="label">
|
||||||
Email
|
<span>{{ i18n "pages.inbounds.Email" }}</span>
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
The Email Must Be Completely Unique
|
<span>{{ i18n "pages.inbounds.EmailDesc" }}</span>
|
||||||
</template>
|
</template>
|
||||||
<a-icon @click="getNewEmail(client)" type="sync"> </a-icon>
|
<a-icon @click="getNewEmail(client)" type="sync"> </a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
@@ -21,10 +21,10 @@
|
|||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<span slot="label">
|
<span slot="label">
|
||||||
IP Count Limit
|
<span>{{ i18n "pages.inbounds.IPLimit" }}</span>
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
disable inbound if more than entered count (0 for disable limit ip)
|
<span>{{ i18n "pages.inbounds.IPLimitDesc" }}</span>
|
||||||
</template>
|
</template>
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
|
|||||||
@@ -5,10 +5,10 @@
|
|||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<span slot="label">
|
<span slot="label">
|
||||||
Email
|
<span>{{ i18n "pages.inbounds.Email" }}</span>
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
The Email Must Be Completely Unique
|
<span>{{ i18n "pages.inbounds.EmailDesc" }}</span>
|
||||||
</template>
|
</template>
|
||||||
<a-icon type="sync" @click="getNewEmail(client)"></a-icon>
|
<a-icon type="sync" @click="getNewEmail(client)"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
@@ -21,10 +21,10 @@
|
|||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<span slot="label">
|
<span slot="label">
|
||||||
IP Count Limit
|
<span>{{ i18n "pages.inbounds.IPLimit" }}</span>
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
disable inbound if more than entered count (0 for disable limit ip)
|
<span>{{ i18n "pages.inbounds.IPLimitDesc" }}</span>
|
||||||
</template>
|
</template>
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
|
|||||||
@@ -5,10 +5,10 @@
|
|||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<span slot="label">
|
<span slot="label">
|
||||||
Email
|
<span>{{ i18n "pages.inbounds.Email" }}</span>
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
The Email Must Be Completely Unique
|
<span>{{ i18n "pages.inbounds.EmailDesc" }}</span>
|
||||||
</template>
|
</template>
|
||||||
<a-icon type="sync" @click="getNewEmail(client)"></a-icon>
|
<a-icon type="sync" @click="getNewEmail(client)"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
@@ -24,10 +24,10 @@
|
|||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<span slot="label">
|
<span slot="label">
|
||||||
IP Count Limit
|
<span>{{ i18n "pages.inbounds.IPLimit" }}</span>
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
Disable inbound if more than entered count (0 for disable limit ip)
|
<span>{{ i18n "pages.inbounds.IPLimitDesc" }}</span>
|
||||||
</template>
|
</template>
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<a-select-option value="ws">WS</a-select-option>
|
<a-select-option value="ws">WS</a-select-option>
|
||||||
<a-select-option value="http">HTTP</a-select-option>
|
<a-select-option value="http">HTTP</a-select-option>
|
||||||
<a-select-option value="quic">QUIC</a-select-option>
|
<a-select-option value="quic">QUIC</a-select-option>
|
||||||
<a-select-option value="grpc">GRPC</a-select-option>
|
<a-select-option value="grpc">gRPC</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
|
|||||||
@@ -52,6 +52,7 @@
|
|||||||
<div slot="title">
|
<div slot="title">
|
||||||
<a-button type="primary" icon="plus" @click="openAddInbound">{{ i18n "pages.inbounds.addInbound" }}</a-button>
|
<a-button type="primary" icon="plus" @click="openAddInbound">{{ i18n "pages.inbounds.addInbound" }}</a-button>
|
||||||
<a-button type="primary" icon="export" @click="exportAllLinks">{{ i18n "pages.inbounds.export" }}</a-button>
|
<a-button type="primary" icon="export" @click="exportAllLinks">{{ i18n "pages.inbounds.export" }}</a-button>
|
||||||
|
<a-button type="primary" icon="reload" @click="resetAllTraffic">{{ i18n "pages.inbounds.resetAllTraffic" }}</a-button>
|
||||||
</div>
|
</div>
|
||||||
<a-input v-model.lazy="searchKey" placeholder="{{ i18n "search" }}" autofocus style="max-width: 300px"></a-input>
|
<a-input v-model.lazy="searchKey" placeholder="{{ i18n "search" }}" autofocus style="max-width: 300px"></a-input>
|
||||||
<a-table :columns="columns" :row-key="dbInbound => dbInbound.id"
|
<a-table :columns="columns" :row-key="dbInbound => dbInbound.id"
|
||||||
@@ -95,6 +96,9 @@
|
|||||||
</template>
|
</template>
|
||||||
<a-menu-item key="resetTraffic">
|
<a-menu-item key="resetTraffic">
|
||||||
<a-icon type="retweet"></a-icon> {{ i18n "pages.inbounds.resetTraffic" }}
|
<a-icon type="retweet"></a-icon> {{ i18n "pages.inbounds.resetTraffic" }}
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item key="clone">
|
||||||
|
<a-icon type="block"></a-icon> {{ i18n "pages.inbounds.Clone"}}
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
<a-menu-item key="delete">
|
<a-menu-item key="delete">
|
||||||
<span style="color: #FF4D4F">
|
<span style="color: #FF4D4F">
|
||||||
@@ -310,11 +314,47 @@
|
|||||||
break;
|
break;
|
||||||
case "resetTraffic":
|
case "resetTraffic":
|
||||||
this.resetTraffic(dbInbound.id);
|
this.resetTraffic(dbInbound.id);
|
||||||
|
break;
|
||||||
|
case "clone":
|
||||||
|
this.openCloneInbound(dbInbound);
|
||||||
break;
|
break;
|
||||||
case "delete":
|
case "delete":
|
||||||
this.delInbound(dbInbound.id);
|
this.delInbound(dbInbound.id);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
openCloneInbound(dbInbound) {
|
||||||
|
this.$confirm({
|
||||||
|
title: '{{ i18n "pages.inbounds.cloneInbound"}} ' + dbInbound.remark,
|
||||||
|
content: '{{ i18n "pages.inbounds.cloneInboundContent"}}',
|
||||||
|
okText: '{{ i18n "pages.inbounds.cloneInboundOk"}}',
|
||||||
|
cancelText: '{{ i18n "cancel" }}',
|
||||||
|
onOk: () => {
|
||||||
|
const baseInbound = dbInbound.toInbound();
|
||||||
|
dbInbound.up = 0;
|
||||||
|
dbInbound.down = 0;
|
||||||
|
this.cloneInbound(baseInbound, dbInbound);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
async cloneInbound(baseInbound, dbInbound) {
|
||||||
|
const inbound = new Inbound();
|
||||||
|
const data = {
|
||||||
|
up: dbInbound.up,
|
||||||
|
down: dbInbound.down,
|
||||||
|
total: dbInbound.total,
|
||||||
|
remark: dbInbound.remark + " - Cloned",
|
||||||
|
enable: dbInbound.enable,
|
||||||
|
expiryTime: dbInbound.expiryTime,
|
||||||
|
|
||||||
|
listen: inbound.listen,
|
||||||
|
port: inbound.port,
|
||||||
|
protocol: baseInbound.protocol,
|
||||||
|
settings: inbound.settings.toString(),
|
||||||
|
streamSettings: baseInbound.stream.toString(),
|
||||||
|
sniffing: baseInbound.canSniffing() ? baseInbound.sniffing.toString() : '{}',
|
||||||
|
};
|
||||||
|
await this.submit('/xui/inbound/add', data, inModal);
|
||||||
},
|
},
|
||||||
openAddInbound() {
|
openAddInbound() {
|
||||||
inModal.show({
|
inModal.show({
|
||||||
@@ -461,6 +501,22 @@
|
|||||||
this.updateInbound(inbound, dbInbound);
|
this.updateInbound(inbound, dbInbound);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
resetAllTraffic() {
|
||||||
|
this.$confirm({
|
||||||
|
title: '{{ i18n "pages.inbounds.resetAllTrafficTitle"}}',
|
||||||
|
content: '{{ i18n "pages.inbounds.resetAllTrafficContent"}}',
|
||||||
|
okText: '{{ i18n "pages.inbounds.resetAllTrafficOkText"}}',
|
||||||
|
cancelText: '{{ i18n "pages.inbounds.resetAllTrafficCancelText"}}',
|
||||||
|
onOk: async () => {
|
||||||
|
for (const dbInbound of this.dbInbounds) {
|
||||||
|
const inbound = dbInbound.toInbound();
|
||||||
|
dbInbound.up = 0;
|
||||||
|
dbInbound.down = 0;
|
||||||
|
this.updateInbound(inbound, dbInbound);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
},
|
},
|
||||||
delInbound(dbInboundId) {
|
delInbound(dbInboundId) {
|
||||||
this.$confirm({
|
this.$confirm({
|
||||||
|
|||||||
@@ -88,8 +88,6 @@
|
|||||||
<a-tag color="blue" style="cursor: pointer;" @click="stopXrayService">{{ i18n "pages.index.stopXray" }}</a-tag>
|
<a-tag color="blue" style="cursor: pointer;" @click="stopXrayService">{{ i18n "pages.index.stopXray" }}</a-tag>
|
||||||
<a-tag color="blue" style="cursor: pointer;" @click="restartXrayService">{{ i18n "pages.index.restartXray" }}</a-tag>
|
<a-tag color="blue" style="cursor: pointer;" @click="restartXrayService">{{ i18n "pages.index.restartXray" }}</a-tag>
|
||||||
<a-tag color="blue" style="cursor: pointer;" @click="openSelectV2rayVersion">{{ i18n "pages.index.xraySwitch" }}</a-tag>
|
<a-tag color="blue" style="cursor: pointer;" @click="openSelectV2rayVersion">{{ i18n "pages.index.xraySwitch" }}</a-tag>
|
||||||
<a-tag color="blue" style="cursor: pointer;" @click="openLogs">Logs</a-tag>
|
|
||||||
<a-tag color="green">3x-ui v{{ .cur_ver }}</a-tag>
|
|
||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :sm="24" :md="12">
|
<a-col :sm="24" :md="12">
|
||||||
@@ -171,6 +169,13 @@
|
|||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
</a-card>
|
</a-card>
|
||||||
|
</a-col>
|
||||||
|
<a-col :sm="24" :md="12">
|
||||||
|
<a-card hoverable :class="siderDrawer.isDarkTheme ? darkClass : ''">
|
||||||
|
3x-ui: <a href="https://github.com/MHSanaei/3x-ui/releases" target="_blank"><a-tag color="green">v{{ .cur_ver }}</a-tag></a>
|
||||||
|
<a href="https://t.me/panel3xui" target="_blank"><a-tag color="green">Telegram</a-tag></a>
|
||||||
|
<a-tag color="blue" style="cursor: pointer;" @click="openLogs">Log Reports</a-tag>
|
||||||
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
</transition>
|
</transition>
|
||||||
|
|||||||
@@ -97,7 +97,9 @@
|
|||||||
<a-list item-layout="horizontal" :style="siderDrawer.isDarkTheme ? 'color: hsla(0,0%,100%,.65);': 'background: white;'">
|
<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.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.xrayConfigPrivateIp"}}' desc='{{ i18n "pages.setting.xrayConfigPrivateIpDesc"}}' v-model="privateIpSettings"></setting-list-item>
|
||||||
<a-divider>{{ i18n "pages.setting.advancedTemplate"}}</a-divider>
|
<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>
|
||||||
<a-collapse-panel header="{{ i18n "pages.setting.xrayConfigInbounds"}}">
|
<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>
|
<setting-list-item type="textarea" title='{{ i18n "pages.setting.xrayConfigInbounds"}}' desc='{{ i18n "pages.setting.xrayConfigInboundsDesc"}}' v-model ="inboundSettings"></setting-list-item>
|
||||||
@@ -304,6 +306,73 @@
|
|||||||
this.templateSettings = newTemplateSettings
|
this.templateSettings = newTemplateSettings
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return localIpFilter
|
||||||
|
},
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.templateSettings = newTemplateSettings
|
||||||
|
},
|
||||||
|
},
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return localdomainFilter
|
||||||
|
},
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.templateSettings = newTemplateSettings
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -154,14 +154,16 @@ func GetInboundClientIps(clientEmail string) (*model.InboundClientIps, error) {
|
|||||||
}
|
}
|
||||||
return InboundClientIps, nil
|
return InboundClientIps, nil
|
||||||
}
|
}
|
||||||
func addInboundClientIps(clientEmail string,ips []string) error {
|
func addInboundClientIps(clientEmail string, ips []string) error {
|
||||||
inboundClientIps := &model.InboundClientIps{}
|
inboundClientIps := &model.InboundClientIps{}
|
||||||
jsonIps, err := json.Marshal(ips)
|
jsonIps, err := json.Marshal(ips)
|
||||||
checkError(err)
|
checkError(err)
|
||||||
|
|
||||||
|
// Trim any leading/trailing whitespace from clientEmail
|
||||||
|
clientEmail = strings.TrimSpace(clientEmail)
|
||||||
|
|
||||||
inboundClientIps.ClientEmail = clientEmail
|
inboundClientIps.ClientEmail = clientEmail
|
||||||
inboundClientIps.Ips = string(jsonIps)
|
inboundClientIps.Ips = string(jsonIps)
|
||||||
|
|
||||||
|
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
tx := db.Begin()
|
tx := db.Begin()
|
||||||
@@ -247,47 +249,46 @@ func GetInboundByEmail(clientEmail string) (*model.Inbound, error) {
|
|||||||
return inbounds, nil
|
return inbounds, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func LimitDevice(){
|
func LimitDevice() {
|
||||||
|
var destIp, destPort, srcIp, srcPort string
|
||||||
localIp,err := LocalIP()
|
|
||||||
checkError(err)
|
localIp,err := LocalIP()
|
||||||
|
checkError(err)
|
||||||
|
|
||||||
c := cmd.NewCmd("bash","-c","ss --tcp | grep -E '" + IPsToRegex(localIp) + "'| awk '{if($1==\"ESTAB\") print $4,$5;}'","| sort | uniq -c | sort -nr | head")
|
c := cmd.NewCmd("bash","-c","ss --tcp | grep -E '" + IPsToRegex(localIp) + "'| awk '{if($1==\"ESTAB\") print $4,$5;}'","| sort | uniq -c | sort -nr | head")
|
||||||
|
|
||||||
<-c.Start()
|
<-c.Start()
|
||||||
if len(c.Status().Stdout) > 0 {
|
if len(c.Status().Stdout) > 0 {
|
||||||
ipRegx, _ := regexp.Compile(`[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+`)
|
ipRegx, _ := regexp.Compile(`[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+`)
|
||||||
portRegx, _ := regexp.Compile(`(?:(:))([0-9]..[^.][0-9]+)`)
|
portRegx, _ := regexp.Compile(`(?:(:))([0-9]..[^.][0-9]+)`)
|
||||||
|
|
||||||
for _, row := range c.Status().Stdout {
|
for _, row := range c.Status().Stdout {
|
||||||
|
|
||||||
data := strings.Split(row," ")
|
|
||||||
|
|
||||||
destIp,destPort,srcIp,srcPort := "","","",""
|
|
||||||
|
|
||||||
|
|
||||||
destIp = string(ipRegx.FindString(data[0]))
|
data := strings.Split(row," ")
|
||||||
|
|
||||||
destPort = portRegx.FindString(data[0])
|
if len(data) < 2 {
|
||||||
destPort = strings.Replace(destPort,":","",-1)
|
continue // Skip this row if it doesn't have at least two elements
|
||||||
|
}
|
||||||
|
|
||||||
srcIp = string(ipRegx.FindString(data[1]))
|
|
||||||
|
|
||||||
srcPort = portRegx.FindString(data[1])
|
destIp = string(ipRegx.FindString(data[0]))
|
||||||
srcPort = strings.Replace(srcPort,":","",-1)
|
destPort = portRegx.FindString(data[0])
|
||||||
|
destPort = strings.Replace(destPort,":","",-1)
|
||||||
|
|
||||||
if(contains(disAllowedIps,srcIp)){
|
srcIp = string(ipRegx.FindString(data[1]))
|
||||||
dropCmd := cmd.NewCmd("bash","-c","ss -K dport = " + srcPort)
|
srcPort = portRegx.FindString(data[1])
|
||||||
dropCmd.Start()
|
srcPort = strings.Replace(srcPort,":","",-1)
|
||||||
|
|
||||||
logger.Debug("request droped : ",srcIp,srcPort,"to",destIp,destPort)
|
if contains(disAllowedIps,srcIp){
|
||||||
}
|
dropCmd := cmd.NewCmd("bash","-c","ss -K dport = " + srcPort)
|
||||||
}
|
dropCmd.Start()
|
||||||
}
|
|
||||||
|
|
||||||
|
logger.Debug("request droped : ",srcIp,srcPort,"to",destIp,destPort)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func LocalIP() ([]string, error) {
|
func LocalIP() ([]string, error) {
|
||||||
// get machine ips
|
// get machine ips
|
||||||
|
|
||||||
|
|||||||
@@ -129,6 +129,22 @@
|
|||||||
"clickOnQRcode" = "Click on QR Code to Copy"
|
"clickOnQRcode" = "Click on QR Code to Copy"
|
||||||
"client" = "Client"
|
"client" = "Client"
|
||||||
"export" = "Export links"
|
"export" = "Export links"
|
||||||
|
"Clone" = "Clone"
|
||||||
|
"cloneInbound" = "Create"
|
||||||
|
"cloneInboundContent" = "All items of this inbound except Port, Listening IP, Clients will be applied to the clone"
|
||||||
|
"cloneInboundOk" = "Creating a clone from"
|
||||||
|
"resetAllTraffic" = "Reset All Inbounds Traffic"
|
||||||
|
"resetAllTrafficTitle" = "Reset all inbounds traffic"
|
||||||
|
"resetAllTrafficContent" = "Are you sure to reset all inbounds traffic ?"
|
||||||
|
"resetAllTrafficOkText" = "Confirm"
|
||||||
|
"resetAllTrafficCancelText" = "Cancel"
|
||||||
|
"IPLimit" = "IP Limit"
|
||||||
|
"IPLimitDesc" = "disable inbound if more than entered count (0 for disable limit ip)"
|
||||||
|
"Email" = "Email"
|
||||||
|
"EmailDesc" = "The Email Must Be Completely Unique"
|
||||||
|
"IPLimitlog" = "IP Log"
|
||||||
|
"IPLimitlogDesc" = "IPs history Log (before enabling inbound after it has been disabled by IP limit, you should clear the log)"
|
||||||
|
"IPLimitlogclear" = "Clear The Log"
|
||||||
|
|
||||||
[pages.client]
|
[pages.client]
|
||||||
"add" = "Add client"
|
"add" = "Add client"
|
||||||
@@ -170,7 +186,7 @@
|
|||||||
"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"
|
"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"
|
||||||
"panelConfig" = "Panel Configuration"
|
"panelConfig" = "Panel Configuration"
|
||||||
"userSetting" = "User Setting"
|
"userSetting" = "User Setting"
|
||||||
"xrayConfiguration" = "xray Configuration"
|
"xrayConfiguration" = "Xray Configuration"
|
||||||
"TGReminder" = "TG Reminder Related Settings"
|
"TGReminder" = "TG Reminder Related Settings"
|
||||||
"otherSetting" = "Other Setting"
|
"otherSetting" = "Other Setting"
|
||||||
"panelListeningIP" = "Panel listening IP"
|
"panelListeningIP" = "Panel listening IP"
|
||||||
@@ -179,7 +195,7 @@
|
|||||||
"panelPortDesc" = "Restart the panel to take effect"
|
"panelPortDesc" = "Restart the panel to take effect"
|
||||||
"publicKeyPath" = "Panel certificate public key file path"
|
"publicKeyPath" = "Panel certificate public key file path"
|
||||||
"publicKeyPathDesc" = "Fill in an absolute path starting with '/', restart the panel to take effect"
|
"publicKeyPathDesc" = "Fill in an absolute path starting with '/', restart the panel to take effect"
|
||||||
"privateKeyPath" = "Panel certificate key file path"
|
"privateKeyPath" = "Panel certificate private key file path"
|
||||||
"privateKeyPathDesc" = "Fill in an absolute path starting with '/', restart the panel to take effect"
|
"privateKeyPathDesc" = "Fill in an absolute path starting with '/', restart the panel to take effect"
|
||||||
"panelUrlPath" = "panel url root path"
|
"panelUrlPath" = "panel url root path"
|
||||||
"panelUrlPathDesc" = "Must start with '/' and end with '/', restart the panel to take effect"
|
"panelUrlPathDesc" = "Must start with '/' and end with '/', restart the panel to take effect"
|
||||||
@@ -193,8 +209,12 @@
|
|||||||
"xrayConfigTemplateDesc" = "Generate the final xray configuration file based on this template, restart the panel to take effect."
|
"xrayConfigTemplateDesc" = "Generate the final xray configuration file based on this template, restart the panel to take effect."
|
||||||
"xrayConfigTorrent" = "Ban bittorrent usage"
|
"xrayConfigTorrent" = "Ban bittorrent usage"
|
||||||
"xrayConfigTorrentDesc" = "Change the configuration temlate to avoid using bittorrent by users, restart the panel to take effect"
|
"xrayConfigTorrentDesc" = "Change the configuration temlate to avoid using bittorrent by users, restart the panel to take effect"
|
||||||
"xrayConfigPrivateIp" = "Ban private ip range to connect"
|
"xrayConfigPrivateIp" = "Ban private IP ranges to connect"
|
||||||
"xrayConfigPrivateIpDesc" = "Change the configuration temlate to avoid connecting with private IP ranges, restart the panel to take effect"
|
"xrayConfigPrivateIpDesc" = "Change the configuration temlate to avoid connecting with private IP ranges, restart the panel to take effect"
|
||||||
|
"xrayConfigIRIp" = "Ban Iran IP ranges to connect"
|
||||||
|
"xrayConfigIRIpDesc" = "Change the configuration temlate to avoid connecting with Iran IP ranges, restart the panel to take effect"
|
||||||
|
"xrayConfigIRdomain" = "Ban IR domains to connect"
|
||||||
|
"xrayConfigIRdomainDesc" = "Change the configuration temlate to avoid connecting with IR domains, restart the panel to take effect"
|
||||||
"xrayConfigInbounds" = "Configuration of Inbounds"
|
"xrayConfigInbounds" = "Configuration of Inbounds"
|
||||||
"xrayConfigInboundsDesc" = "Change the configuration temlate to accept special clients, restart the panel to take effect"
|
"xrayConfigInboundsDesc" = "Change the configuration temlate to accept special clients, restart the panel to take effect"
|
||||||
"xrayConfigOutbounds" = "Configuration of Outbounds"
|
"xrayConfigOutbounds" = "Configuration of Outbounds"
|
||||||
|
|||||||
@@ -38,7 +38,7 @@
|
|||||||
"domainName" = "آدرس دامنه"
|
"domainName" = "آدرس دامنه"
|
||||||
"additional" = "آی دی جایگزین"
|
"additional" = "آی دی جایگزین"
|
||||||
"monitor" = "آی پی اتصال"
|
"monitor" = "آی پی اتصال"
|
||||||
"certificate" = "سرتیفیکیت"
|
"certificate" = "گواهی دیجیتال"
|
||||||
"fail" = "خطا"
|
"fail" = "خطا"
|
||||||
"success" = " موفق"
|
"success" = " موفق"
|
||||||
"getVersion" = "دریافت ورژن"
|
"getVersion" = "دریافت ورژن"
|
||||||
@@ -74,8 +74,8 @@
|
|||||||
"xraySwitch" = "تغییر ورژن"
|
"xraySwitch" = "تغییر ورژن"
|
||||||
"xraySwitchClick" = "ورژن مورد نظر را انتخاب کنید"
|
"xraySwitchClick" = "ورژن مورد نظر را انتخاب کنید"
|
||||||
"xraySwitchClickDesk" = "لطفا با دقت انتخاب کنید ، در صورت انتخاب اشتباه امکان قطعی سیستم وجود دارد ."
|
"xraySwitchClickDesk" = "لطفا با دقت انتخاب کنید ، در صورت انتخاب اشتباه امکان قطعی سیستم وجود دارد ."
|
||||||
"operationHours" = "ساعت فعال"
|
"operationHours" = "مدت فعالیت"
|
||||||
"operationHoursDesc" = "ساعت فعال بعد از شروع سیستم"
|
"operationHoursDesc" = "مدت فعالیت سیستم بعد از روشن شدن"
|
||||||
"systemLoad" = "بار روی سیستم"
|
"systemLoad" = "بار روی سیستم"
|
||||||
"connectionCount" = "تعداد کانکشن ها"
|
"connectionCount" = "تعداد کانکشن ها"
|
||||||
"connectionCountDesc" = "تعداد کانکشن ها برای کل شبکه"
|
"connectionCountDesc" = "تعداد کانکشن ها برای کل شبکه"
|
||||||
@@ -83,7 +83,7 @@
|
|||||||
"downSpeed" = "سرعت دانلود در حال حاضر سیستم"
|
"downSpeed" = "سرعت دانلود در حال حاضر سیستم"
|
||||||
"totalSent" = "جمع کل ترافیک آپلود مصرفی"
|
"totalSent" = "جمع کل ترافیک آپلود مصرفی"
|
||||||
"totalReceive" = "جمع کل ترافیک دانلود مصرفی"
|
"totalReceive" = "جمع کل ترافیک دانلود مصرفی"
|
||||||
"xraySwitchVersionDialog" = "تغییر ورژن Xray"
|
"xraySwitchVersionDialog" = "تغییر ورژن"
|
||||||
"xraySwitchVersionDialogDesc" = "آیا از تغییر ورژن مطمئن هستین"
|
"xraySwitchVersionDialogDesc" = "آیا از تغییر ورژن مطمئن هستین"
|
||||||
"dontRefreshh" = "در حال نصب ، لطفا رفرش نکنید "
|
"dontRefreshh" = "در حال نصب ، لطفا رفرش نکنید "
|
||||||
|
|
||||||
@@ -122,13 +122,29 @@
|
|||||||
"noRecommendKeepDefault" = "توصیه می شود به عنوان پیش فرض حفظ شود"
|
"noRecommendKeepDefault" = "توصیه می شود به عنوان پیش فرض حفظ شود"
|
||||||
"certificatePath" = "مسیر فایل گواهی"
|
"certificatePath" = "مسیر فایل گواهی"
|
||||||
"certificateContent" = "محتوای فایل گواهی"
|
"certificateContent" = "محتوای فایل گواهی"
|
||||||
"publicKeyPath" = "مسیر فایل Certificate.crt"
|
"publicKeyPath" = "مسیر کلید عمومی"
|
||||||
"publicKeyContent" = "محتوای Certificate.crt"
|
"publicKeyContent" = "محتوای کلید عمومی"
|
||||||
"keyPath" = "مسیر فایل Private.key"
|
"keyPath" = "مسیر کلید خصوصی"
|
||||||
"keyContent" = "محتوای Private.key"
|
"keyContent" = "محتوای کلید خصوصی"
|
||||||
"clickOnQRcode" = "برای کپی بر روی کد تصویری کلیک کنید"
|
"clickOnQRcode" = "برای کپی بر روی کد تصویری کلیک کنید"
|
||||||
"client" = "کاربر"
|
"client" = "کاربر"
|
||||||
"export" = "استخراج لینکها"
|
"export" = "استخراج لینکها"
|
||||||
|
"Clone" = "شبیه سازی"
|
||||||
|
"cloneInbound" = "ایجاد"
|
||||||
|
"cloneInboundContent" = "همه موارد این ورودی بجز پورت ، ای پی و کلاینت ها شبیه سازی خواهند شد"
|
||||||
|
"cloneInboundOk" = "ساختن شبیه ساز"
|
||||||
|
"resetAllTraffic" = "ریست ترافیک کل ورودی ها"
|
||||||
|
"resetAllTrafficTitle" = "ریست ترافیک کل ورودی ها"
|
||||||
|
"resetAllTrafficContent" = "آیا مطمئن هستید که تمام ترافیک ورودی ها را ریست می کنید؟"
|
||||||
|
"resetAllTrafficOkText" = "بله"
|
||||||
|
"resetAllTrafficCancelText" = "انصراف"
|
||||||
|
"IPLimit" = "محدودیت ای پی"
|
||||||
|
"IPLimitDesc" = "غیرفعال کردن ورودی در صورت بیش از تعداد وارد شده (0 برای غیرفعال کردن محدودیت ای پی )"
|
||||||
|
"Email" = "ایمیل"
|
||||||
|
"EmailDesc" = "ایمیل باید کاملا منحصر به فرد باشد"
|
||||||
|
"IPLimitlog" = "گزارش ها"
|
||||||
|
"IPLimitlogDesc" = "گزارش سابقه ای پی (قبل از فعال کردن ورودی پس از غیرفعال شدن توسط محدودیت ای پی، باید گزارش را پاک کنید)"
|
||||||
|
"IPLimitlogclear" = "پاک کردن گزارش ها"
|
||||||
|
|
||||||
[pages.client]
|
[pages.client]
|
||||||
"add" = "کاربر جدید"
|
"add" = "کاربر جدید"
|
||||||
@@ -177,9 +193,9 @@
|
|||||||
"panelListeningIPDesc" = "برای استفاده از تمام IP ها به طور پیش فرض خالی بگذارید. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
"panelListeningIPDesc" = "برای استفاده از تمام IP ها به طور پیش فرض خالی بگذارید. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
||||||
"panelPort" = "پورت پنل"
|
"panelPort" = "پورت پنل"
|
||||||
"panelPortDesc" = "پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
"panelPortDesc" = "پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
||||||
"publicKeyPath" = "مسیر فایل پنل Certificate.crt"
|
"publicKeyPath" = "مسیر فایل گواهی کلید عمومی پنل"
|
||||||
"publicKeyPathDesc" = "باید یک مسیر مطلق باشد که با / شروع می شود . پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
"publicKeyPathDesc" = "باید یک مسیر مطلق باشد که با / شروع می شود . پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
||||||
"privateKeyPath" = "مسیر فایل پنل private.key"
|
"privateKeyPath" = "مسیر فایل گواهی کلید خصوصی پنل"
|
||||||
"privateKeyPathDesc" = "باید یک مسیر مطلق باشد که با / شروع می شود . پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
"privateKeyPathDesc" = "باید یک مسیر مطلق باشد که با / شروع می شود . پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
||||||
"panelUrlPath" = "آدرس روت پنل"
|
"panelUrlPath" = "آدرس روت پنل"
|
||||||
"panelUrlPathDesc" = "باید با '/' شروع شود و با '/' تمام شود. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
"panelUrlPathDesc" = "باید با '/' شروع شود و با '/' تمام شود. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
||||||
@@ -195,6 +211,10 @@
|
|||||||
"xrayConfigTorrentDesc" = "الگوی تنظیمات را برای فیلتر کردن پروتکل بیت تورنت برای کاربران تغییر میدهد. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
"xrayConfigTorrentDesc" = "الگوی تنظیمات را برای فیلتر کردن پروتکل بیت تورنت برای کاربران تغییر میدهد. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
||||||
"xrayConfigPrivateIp" = "جلوگیری از اتصال آی پی های نامعتبر"
|
"xrayConfigPrivateIp" = "جلوگیری از اتصال آی پی های نامعتبر"
|
||||||
"xrayConfigPrivateIpDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال آی پی های نامعتبر و بسته های سرگردان تغییر میدهد. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
"xrayConfigPrivateIpDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال آی پی های نامعتبر و بسته های سرگردان تغییر میدهد. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
||||||
|
"xrayConfigIRIp" = "جلوگیری از اتصال آی پی های ایران"
|
||||||
|
"xrayConfigIRIpDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال آی پی های ایران تغییر میدهد. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
||||||
|
"xrayConfigIRdomain" = "جلوگیری از اتصال دامنه های ایران"
|
||||||
|
"xrayConfigIRdomainDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال دامنه های ایران تغییر میدهد. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
||||||
"xrayConfigInbounds" = "تنظیمات ورودی"
|
"xrayConfigInbounds" = "تنظیمات ورودی"
|
||||||
"xrayConfigInboundsDesc" = "میتوانید الگوی تنظیمات را برای ورودی های خاص تنظیم نمایید. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
"xrayConfigInboundsDesc" = "میتوانید الگوی تنظیمات را برای ورودی های خاص تنظیم نمایید. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
||||||
"xrayConfigOutbounds" = "تنظیمات خروجی"
|
"xrayConfigOutbounds" = "تنظیمات خروجی"
|
||||||
|
|||||||
@@ -129,6 +129,22 @@
|
|||||||
"clickOnQRcode" = "点击二维码复制"
|
"clickOnQRcode" = "点击二维码复制"
|
||||||
"client" = "客户"
|
"client" = "客户"
|
||||||
"export" = "导出链接"
|
"export" = "导出链接"
|
||||||
|
"Clone" = "克隆"
|
||||||
|
"cloneInbound" = "创造"
|
||||||
|
"cloneInboundContent" = "此入站的所有项目除 Port、Listening IP、Clients 将应用于克隆"
|
||||||
|
"cloneInboundOk" = "从创建克隆"
|
||||||
|
"resetAllTraffic" = "重置所有入站流量"
|
||||||
|
"resetAllTrafficTitle" = "重置所有入站流量"
|
||||||
|
"resetAllTrafficContent" = "您确定要重置所有入站流量吗?"
|
||||||
|
"resetAllTrafficOkText" = "确认"
|
||||||
|
"resetAllTrafficCancelText" = "取消"
|
||||||
|
"IPLimit" = "IP限制"
|
||||||
|
"IPLimitDesc" = "如果超过输入的计数则禁用入站(0 表示禁用限制 ip)"
|
||||||
|
"Email" = "电子邮件"
|
||||||
|
"EmailDesc" = "电子邮件必须完全唯"
|
||||||
|
"IPLimitlog" = "IP日志"
|
||||||
|
"IPLimitlogDesc" = "IP 历史日志 (通过IP限制禁用inbound之前,需要清空日志)"
|
||||||
|
"IPLimitlogclear" = "清除日志"
|
||||||
|
|
||||||
[pages.client]
|
[pages.client]
|
||||||
"add" = "添加客户端"
|
"add" = "添加客户端"
|
||||||
@@ -195,6 +211,10 @@
|
|||||||
"xrayConfigTorrentDesc" = "更改配置模板避免用户使用bittorrent,重启面板生效"
|
"xrayConfigTorrentDesc" = "更改配置模板避免用户使用bittorrent,重启面板生效"
|
||||||
"xrayConfigPrivateIp" = "禁止私人 ip 范围连接"
|
"xrayConfigPrivateIp" = "禁止私人 ip 范围连接"
|
||||||
"xrayConfigPrivateIpDesc" = "更改配置模板以避免连接私有 IP 范围,重启面板生效"
|
"xrayConfigPrivateIpDesc" = "更改配置模板以避免连接私有 IP 范围,重启面板生效"
|
||||||
|
"xrayConfigIRIp" = "禁止伊朗 IP 范围连接"
|
||||||
|
"xrayConfigIRIpDesc" = "修改配置模板避免连接伊朗IP范围,重启面板生效"
|
||||||
|
"xrayConfigIRdomain" = "禁止伊朗域连接"
|
||||||
|
"xrayConfigIRdomainDesc" = "修改配置模板避免连接伊朗域名,重启面板生效"
|
||||||
"xrayConfigInbounds" = "入站配置"
|
"xrayConfigInbounds" = "入站配置"
|
||||||
"xrayConfigInboundsDesc" = "更改配置模板接受特殊客户端,重启面板生效"
|
"xrayConfigInboundsDesc" = "更改配置模板接受特殊客户端,重启面板生效"
|
||||||
"xrayConfigOutbounds" = "出站配置"
|
"xrayConfigOutbounds" = "出站配置"
|
||||||
|
|||||||
@@ -156,6 +156,9 @@ func (s *Server) initRouter() (*gin.Engine, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
engine := gin.Default()
|
engine := gin.Default()
|
||||||
|
|
||||||
|
// Add favicon
|
||||||
|
engine.StaticFile("/favicon.ico", "web/assets/favicon.ico")
|
||||||
|
|
||||||
secret, err := s.settingService.GetSecret()
|
secret, err := s.settingService.GetSecret()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
214
x-ui.sh
@@ -454,6 +454,64 @@ ssl_cert_issue() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
open_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(){
|
||||||
|
systemctl stop x-ui
|
||||||
|
cd /usr/local/x-ui/bin
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
install_acme() {
|
install_acme() {
|
||||||
cd ~
|
cd ~
|
||||||
LOGI "install acme..."
|
LOGI "install acme..."
|
||||||
@@ -490,14 +548,7 @@ ssl_cert_issue_standalone() {
|
|||||||
else
|
else
|
||||||
LOGI "install socat succeed..."
|
LOGI "install socat succeed..."
|
||||||
fi
|
fi
|
||||||
#creat a directory for install cert
|
|
||||||
certPath=/root/cert
|
|
||||||
if [ ! -d "$certPath" ]; then
|
|
||||||
mkdir $certPath
|
|
||||||
else
|
|
||||||
rm -rf $certPath
|
|
||||||
mkdir $certPath
|
|
||||||
fi
|
|
||||||
#get the domain here,and we need verify it
|
#get the domain here,and we need verify it
|
||||||
local domain=""
|
local domain=""
|
||||||
read -p "please input your domain:" domain
|
read -p "please input your domain:" domain
|
||||||
@@ -512,6 +563,16 @@ ssl_cert_issue_standalone() {
|
|||||||
else
|
else
|
||||||
LOGI "your domain is ready for issuing cert now..."
|
LOGI "your domain is ready for issuing cert now..."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
#create a directory for install cert
|
||||||
|
certPath="/root/cert/${domain}"
|
||||||
|
if [ ! -d "$certPath" ]; then
|
||||||
|
mkdir -p "$certPath"
|
||||||
|
else
|
||||||
|
rm -rf "$certPath"
|
||||||
|
mkdir -p "$certPath"
|
||||||
|
fi
|
||||||
|
|
||||||
#get needed port here
|
#get needed port here
|
||||||
local WebPort=80
|
local WebPort=80
|
||||||
read -p "please choose which port do you use,default will be 80 port:" WebPort
|
read -p "please choose which port do you use,default will be 80 port:" WebPort
|
||||||
@@ -531,9 +592,9 @@ ssl_cert_issue_standalone() {
|
|||||||
LOGE "issue certs succeed,installing certs..."
|
LOGE "issue certs succeed,installing certs..."
|
||||||
fi
|
fi
|
||||||
#install cert
|
#install cert
|
||||||
~/.acme.sh/acme.sh --installcert -d ${domain} --ca-file /root/cert/ca.cer \
|
~/.acme.sh/acme.sh --installcert -d ${domain} \
|
||||||
--cert-file /root/cert/${domain}.cer --key-file /root/cert/${domain}.key \
|
--key-file /root/cert/${domain}/privkey.pem \
|
||||||
--fullchain-file /root/cert/fullchain.cer
|
--fullchain-file /root/cert/${domain}/fullchain.pem
|
||||||
|
|
||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ]; then
|
||||||
LOGE "install certs failed,exit"
|
LOGE "install certs failed,exit"
|
||||||
@@ -542,17 +603,18 @@ ssl_cert_issue_standalone() {
|
|||||||
else
|
else
|
||||||
LOGI "install certs succeed,enable auto renew..."
|
LOGI "install certs succeed,enable auto renew..."
|
||||||
fi
|
fi
|
||||||
~/.acme.sh/acme.sh --upgrade --auto-upgrade
|
|
||||||
if [ $? -ne 0 ]; then
|
~/.acme.sh/acme.sh --upgrade --auto-upgrade
|
||||||
LOGE "auto renew failed,certs details:"
|
if [ $? -ne 0 ]; then
|
||||||
ls -lah cert
|
LOGE "auto renew failed, certs details:"
|
||||||
chmod 755 $certPath
|
ls -lah cert/*
|
||||||
exit 1
|
chmod 755 $certPath/*
|
||||||
else
|
exit 1
|
||||||
LOGI "auto renew succeed,certs details:"
|
else
|
||||||
ls -lah cert
|
LOGI "auto renew succeed, certs details:"
|
||||||
chmod 755 $certPath
|
ls -lah cert/*
|
||||||
fi
|
chmod 755 $certPath/*
|
||||||
|
fi
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -573,13 +635,7 @@ ssl_cert_issue_by_cloudflare() {
|
|||||||
CF_Domain=""
|
CF_Domain=""
|
||||||
CF_GlobalKey=""
|
CF_GlobalKey=""
|
||||||
CF_AccountEmail=""
|
CF_AccountEmail=""
|
||||||
certPath=/root/cert
|
|
||||||
if [ ! -d "$certPath" ]; then
|
|
||||||
mkdir $certPath
|
|
||||||
else
|
|
||||||
rm -rf $certPath
|
|
||||||
mkdir $certPath
|
|
||||||
fi
|
|
||||||
LOGD "please input your domain:"
|
LOGD "please input your domain:"
|
||||||
read -p "Input your domain here:" CF_Domain
|
read -p "Input your domain here:" CF_Domain
|
||||||
LOGD "your domain is:${CF_Domain},check it..."
|
LOGD "your domain is:${CF_Domain},check it..."
|
||||||
@@ -593,6 +649,16 @@ ssl_cert_issue_by_cloudflare() {
|
|||||||
else
|
else
|
||||||
LOGI "your domain is ready for issuing cert now..."
|
LOGI "your domain is ready for issuing cert now..."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
#create a directory for install cert
|
||||||
|
certPath="/root/cert/${CF_Domain}"
|
||||||
|
if [ ! -d "$certPath" ]; then
|
||||||
|
mkdir -p "$certPath"
|
||||||
|
else
|
||||||
|
rm -rf "$certPath"
|
||||||
|
mkdir -p "$certPath"
|
||||||
|
fi
|
||||||
|
|
||||||
LOGD "please inout your cloudflare global API key:"
|
LOGD "please inout your cloudflare global API key:"
|
||||||
read -p "Input your key here:" CF_GlobalKey
|
read -p "Input your key here:" CF_GlobalKey
|
||||||
LOGD "your cloudflare global API key is:${CF_GlobalKey}"
|
LOGD "your cloudflare global API key is:${CF_GlobalKey}"
|
||||||
@@ -611,34 +677,54 @@ ssl_cert_issue_by_cloudflare() {
|
|||||||
LOGE "issue cert failed,exit"
|
LOGE "issue cert failed,exit"
|
||||||
rm -rf ~/.acme.sh/${CF_Domain}
|
rm -rf ~/.acme.sh/${CF_Domain}
|
||||||
exit 1
|
exit 1
|
||||||
else
|
else
|
||||||
LOGI "Certificate issued Successfully, Installing..."
|
LOGI "Certificate issued Successfully, Installing..."
|
||||||
fi
|
fi
|
||||||
~/.acme.sh/acme.sh --installcert -d ${CF_Domain} -d *.${CF_Domain} --ca-file /root/cert/ca.cer \
|
~/.acme.sh/acme.sh --installcert -d ${CF_Domain} -d *.${CF_Domain} \
|
||||||
--cert-file /root/cert/${CF_Domain}.cer --key-file /root/cert/${CF_Domain}.key \
|
--key-file /root/cert/${CF_Domain}/privkey.pem \
|
||||||
--fullchain-file /root/cert/fullchain.cer
|
--fullchain-file /root/cert/${CF_Domain}/fullchain.pem
|
||||||
if [ $? -ne 0 ]; then
|
|
||||||
LOGE "install cert failed,exit"
|
if [ $? -ne 0 ]; then
|
||||||
rm -rf ~/.acme.sh/${CF_Domain}
|
LOGE "install cert failed,exit"
|
||||||
exit 1
|
rm -rf ~/.acme.sh/${CF_Domain}
|
||||||
else
|
exit 1
|
||||||
LOGI "Certificate installed Successfully,Turning on automatic updates..."
|
else
|
||||||
fi
|
LOGI "Certificate installed Successfully,Turning on automatic updates..."
|
||||||
~/.acme.sh/acme.sh --upgrade --auto-upgrade
|
fi
|
||||||
if [ $? -ne 0 ]; then
|
~/.acme.sh/acme.sh --upgrade --auto-upgrade
|
||||||
LOGE "Auto update setup Failed, script exiting..."
|
if [ $? -ne 0 ]; then
|
||||||
ls -lah cert
|
LOGE "auto renew failed, certs details:"
|
||||||
chmod 755 $certPath
|
ls -lah cert/*
|
||||||
exit 1
|
chmod 755 $certPath/*
|
||||||
else
|
exit 1
|
||||||
LOGI "The certificate is installed and auto-renewal is turned on, Specific information is as follows"
|
else
|
||||||
ls -lah cert
|
LOGI "auto renew succeed, certs details:"
|
||||||
chmod 755 $certPath
|
ls -lah cert/*
|
||||||
fi
|
chmod 755 $certPath/*
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
show_menu
|
show_menu
|
||||||
fi
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
run_speedtest() {
|
||||||
|
# Check if Speedtest is already installed
|
||||||
|
if ! command -v speedtest &> /dev/null; then
|
||||||
|
# If not installed, install it
|
||||||
|
sudo apt-get update && sudo apt-get install -y curl
|
||||||
|
curl -s https://install.speedtest.net/app/cli/install.deb.sh | sudo bash
|
||||||
|
sudo apt-get install -y speedtest
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Run Speedtest
|
||||||
|
speedtest
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
show_usage() {
|
show_usage() {
|
||||||
echo "x-ui control menu usages: "
|
echo "x-ui control menu usages: "
|
||||||
@@ -681,10 +767,14 @@ show_menu() {
|
|||||||
${green}14.${plain} Disabel x-ui On System Startup
|
${green}14.${plain} Disabel x-ui On System Startup
|
||||||
————————————————
|
————————————————
|
||||||
${green}15.${plain} Enable BBR
|
${green}15.${plain} Enable BBR
|
||||||
${green}16.${plain} Issuse Certs
|
${green}16.${plain} Apply for an SSL Certificate
|
||||||
|
${green}17.${plain} Update Geo Files
|
||||||
|
${green}18.${plain} Active Firewall and open ports
|
||||||
|
${green}19.${plain} Fixing Google reCAPTCHA
|
||||||
|
${green}20.${plain} Speedtest by Ookla
|
||||||
"
|
"
|
||||||
show_status
|
show_status
|
||||||
echo && read -p "Please enter your selection [0-16]: " num
|
echo && read -p "Please enter your selection [0-20]: " num
|
||||||
|
|
||||||
case "${num}" in
|
case "${num}" in
|
||||||
0)
|
0)
|
||||||
@@ -738,8 +828,20 @@ show_menu() {
|
|||||||
16)
|
16)
|
||||||
ssl_cert_issue
|
ssl_cert_issue
|
||||||
;;
|
;;
|
||||||
|
17)
|
||||||
|
update_geo
|
||||||
|
;;
|
||||||
|
18)
|
||||||
|
open_ports
|
||||||
|
;;
|
||||||
|
19)
|
||||||
|
google_recaptcha
|
||||||
|
;;
|
||||||
|
20)
|
||||||
|
run_speedtest
|
||||||
|
;;
|
||||||
*)
|
*)
|
||||||
LOGE "Please enter the correct number [0-16]"
|
LOGE "Please enter the correct number [0-20]"
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|||||||