Compare commits

...

40 Commits
0.4.1 ... 0.4.3

Author SHA1 Message Date
Alireza Ahmadi
8f0a701d9e v0.4.3 2023-03-26 13:38:32 +02:00
Alireza Ahmadi
921a72b5cf v0.4.3 2023-03-24 12:52:00 +01:00
Alireza Ahmadi
9e902fc82f Add version and log 2023-03-24 12:24:12 +01:00
Alireza Ahmadi
6ed8ff8e05 [infoModal] better display 2023-03-24 12:23:23 +01:00
Alireza Ahmadi
f333ad23d9 Merge pull request #92 from alireza0/dependabot/go_modules/google.golang.org/grpc-1.54.0
Bump google.golang.org/grpc from 1.53.0 to 1.54.0
2023-03-24 12:06:58 +01:00
Alireza Ahmadi
3f49aa5541 Merge pull request #89 from alireza0/TLS-enhancements
TLS-enhancements
2023-03-24 12:06:33 +01:00
Alireza Ahmadi
0f487dc10f [tgbot] fix admins input 2023-03-24 08:43:09 +01:00
dependabot[bot]
f3c0edac07 Bump google.golang.org/grpc from 1.53.0 to 1.54.0
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.53.0 to 1.54.0.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.53.0...v1.54.0)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-23 22:07:30 +00:00
Alireza Ahmadi
394c857bb6 [tgbot] fix exhausted report 2023-03-23 22:55:23 +01:00
Alireza Ahmadi
6080709fc7 Enable fallback in xtls as well 2023-03-23 20:13:21 +01:00
Alireza Ahmadi
17c47f0711 Remove ugly-bugy qrCode footer 2023-03-23 18:29:12 +01:00
Alireza Ahmadi
dac234357a Fix input-group dark theme 2023-03-23 12:53:36 +01:00
Alireza Ahmadi
2d15073a90 Fix vmess link 2023-03-23 11:16:48 +01:00
Alireza Ahmadi
1b2e165a65 Fix TLS and ALPN 2023-03-23 10:03:09 +01:00
Alireza Ahmadi
e1f8e8efd2 Add allowInsecure option 2023-03-23 10:02:18 +01:00
Alireza Ahmadi
738c7064a9 Default TLS version change to 1.0-1.2 2023-03-23 10:01:28 +01:00
Alireza Ahmadi
c24483ad44 [tgbot] fix client search #85 2023-03-23 09:59:34 +01:00
Alireza Ahmadi
046f071378 [bug] Fix Transport column in search table 2023-03-21 15:03:26 +01:00
Alireza Ahmadi
d40a16e29d Update README 2023-03-20 13:50:39 +01:00
Alireza Ahmadi
2f1f28cc0b v0.4.2 2023-03-20 12:03:52 +01:00
Alireza Ahmadi
70ff51d629 [Bulk client] add option: without random email #72 2023-03-20 11:50:44 +01:00
Alireza Ahmadi
7472c31c34 [TGBOT] add seach inbound #75 2023-03-20 11:18:47 +01:00
Alireza Ahmadi
04a8d5c177 [DarkMode] better dark theme based on antdv2 2023-03-20 10:50:31 +01:00
Alireza Ahmadi
7b2bd4247d TGBOT: Add xray config to backup #75 2023-03-20 10:22:41 +01:00
Alireza Ahmadi
5aee1c886b Merge branch 'main' of https://github.com/alireza0/x-ui 2023-03-19 20:19:04 +01:00
Alireza Ahmadi
7a9a30a038 Dark-Mode: Better contrast 2023-03-19 19:59:35 +01:00
Alireza Ahmadi
eb17cec289 Remove chat answer 2023-03-19 19:07:40 +01:00
Alireza Ahmadi
22e107c746 Update install.sh
Fix centos7+ message
2023-03-19 11:56:42 +01:00
Alireza Ahmadi
36a93e2959 Merge pull request #77 from forkeer/main
Fedora OS Support
2023-03-19 11:56:13 +01:00
Alireza Ahmadi
073247f054 Fix Link bug 2023-03-19 11:51:08 +01:00
Leonardo
96c8932961 Merge branch 'alireza0:main' into main 2023-03-19 00:14:23 +03:30
Leonardo
5fac7df174 Merge branch 'alireza0:main' into main 2023-03-18 13:45:41 +03:30
Leonardo
8bba9f3c25 Update install.sh
Fix s390x
2023-03-18 13:43:54 +03:30
Leonardo
268e8c7eb1 Update x-ui.sh
Fix Debian 8 Support
2023-03-18 13:39:54 +03:30
Leonardo
14e8f6cbfa Update x-ui.sh
Fix OS Version
2023-03-18 13:38:37 +03:30
Leonardo
7f0ba495db Update install.sh
Fix OS Version
2023-03-18 13:36:01 +03:30
Leonardo
f829213dd1 go-version
go-version update to stable
2023-03-14 14:17:52 +03:30
Leonardo
4d5a73512e Update README.md
Fedora 36+
2023-03-14 14:01:52 +03:30
Leonardo
6dccc5b891 Update install.sh
Fedora OS Fix
2023-03-14 14:00:14 +03:30
Leonardo
f642830dc8 Update x-ui.sh
Fix Fedora
2023-03-14 13:56:56 +03:30
26 changed files with 649 additions and 445 deletions

View File

@@ -14,7 +14,7 @@ jobs:
- name: Set up Go - name: Set up Go
uses: actions/setup-go@v4 uses: actions/setup-go@v4
with: with:
go-version: '1.20' go-version: 'stable'
- name: build linux amd64 version - name: build linux amd64 version
run: | run: |
CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -o xui-release -v main.go CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -o xui-release -v main.go
@@ -123,4 +123,4 @@ jobs:
tag: ${{ github.ref }} tag: ${{ github.ref }}
file: x-ui-linux-s390x.tar.gz file: x-ui-linux-s390x.tar.gz
asset_name: x-ui-linux-s390x.tar.gz asset_name: x-ui-linux-s390x.tar.gz
prerelease: true prerelease: true

View File

@@ -40,12 +40,18 @@ xray panel supporting multi-protocol, **Multi-lang (English,Farsi,Chinese)**
![inbounds](./media/inbounds.png) ![inbounds](./media/inbounds.png)
![Dark inbounds](./media/inbounds-dark.png) ![Dark inbounds](./media/inbounds-dark.png)
# Install & Upgrade # Install & Upgrade to latest version
``` ```
bash <(curl -Ls https://raw.githubusercontent.com/alireza0/x-ui/master/install.sh) bash <(curl -Ls https://raw.githubusercontent.com/alireza0/x-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 `0.4.0`:
```
bash <(curl -Ls https://raw.githubusercontent.com/alireza0/x-ui/master/install.sh) 0.4.0
```
## Manual install & upgrade ## Manual install & upgrade
1. First download the latest compressed package from https://github.com/alireza0/x-ui/releases , generally choose Architecture `amd64` 1. First download the latest compressed package from https://github.com/alireza0/x-ui/releases , generally choose Architecture `amd64`
@@ -153,6 +159,7 @@ Reference syntax:
- CentOS 7+ - CentOS 7+
- Ubuntu 16+ - Ubuntu 16+
- Debian 8+ - Debian 8+
- Fedora 36+
# common problem # common problem

View File

@@ -1 +1 @@
0.4.1 0.4.3

2
go.mod
View File

@@ -15,7 +15,7 @@ require (
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
google.golang.org/grpc v1.53.0 google.golang.org/grpc v1.54.0
gorm.io/driver/sqlite v1.4.4 gorm.io/driver/sqlite v1.4.4
gorm.io/gorm v1.24.6 gorm.io/gorm v1.24.6
) )

4
go.sum
View File

@@ -229,8 +229,8 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 h1:DdoeryqhaXp1LtT/emMP1BRJPHHKFi5akj/nbx/zNTA= google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 h1:DdoeryqhaXp1LtT/emMP1BRJPHHKFi5akj/nbx/zNTA=
google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s= google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s=
google.golang.org/grpc v1.53.0 h1:LAv2ds7cmFV/XTS3XG1NneeENYrXGmorPxsBbptIjNc= google.golang.org/grpc v1.54.0 h1:EhTqbhiYeixwWQtAEZAxmV9MGqcjEU2mFx52xCzNyag=
google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw= google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.29.0 h1:44S3JjaKmLEE4YIkjzexaP+NzZsudE3Zin5Njn/pYX0= google.golang.org/protobuf v1.29.0 h1:44S3JjaKmLEE4YIkjzexaP+NzZsudE3Zin5Njn/pYX0=

View File

@@ -10,24 +10,18 @@ cur_dir=$(pwd)
# check root # check root
[[ $EUID -ne 0 ]] && echo -e "${red}Fatal error${plain} Please run this script with root privilege \n " && exit 1 [[ $EUID -ne 0 ]] && echo -e "${red}Fatal error${plain} Please run this script with root privilege \n " && exit 1
# check os # Check OS and set release variable
if [[ -f /etc/redhat-release ]]; then if [[ -f /etc/os-release ]]; then
release="centos" source /etc/os-release
elif cat /etc/issue | grep -Eqi "debian"; then release=$ID
release="debian" elif [[ -f /usr/lib/os-release ]]; then
elif cat /etc/issue | grep -Eqi "ubuntu"; then source /usr/lib/os-release
release="ubuntu" release=$ID
elif cat /etc/issue | grep -Eqi "centos|red hat|redhat"; then
release="centos"
elif cat /proc/version | grep -Eqi "debian"; then
release="debian"
elif cat /proc/version | grep -Eqi "ubuntu"; then
release="ubuntu"
elif cat /proc/version | grep -Eqi "centos|red hat|redhat"; then
release="centos"
else else
echo -e "${red} Check system OS failed, please contact the author! ${plain}\n" && exit 1 echo "Failed to check the system OS, please contact the author!" >&2
exit 1
fi fi
echo "The OS release is: $release"
arch=$(arch) arch=$(arch)
@@ -39,42 +33,44 @@ elif [[ $arch == "s390x" ]]; then
arch="s390x" arch="s390x"
else else
arch="amd64" arch="amd64"
echo -e "${red} Fail to check system arch, will use default arch: ${arch}${plain}" echo -e "${red} Failed to check system arch, will use default arch: ${arch}${plain}"
fi fi
echo "arch: ${arch}" echo "arch: ${arch}"
if [ $(getconf WORD_BIT) != '32' ] && [ $(getconf LONG_BIT) != '64' ]; then if [ $(getconf WORD_BIT) != '32' ] && [ $(getconf LONG_BIT) != '64' ]; then
echo "x-ui dosen't support 32-bit(x86) system, please use 64 bit operating system(x86_64) instead,if there is something wrong, plz let me know" echo "x-ui dosen't support 32-bit(x86) system, please use 64 bit operating system(x86_64) instead, if there is something wrong, please get in touch with me!"
exit -1 exit -1
fi fi
os_version="" os_version=""
os_version=$(grep -i version_id /etc/os-release | cut -d \" -f2 | cut -d . -f1)
# os version if [[ "${release}" == "centos" ]]; then
if [[ -f /etc/os-release ]]; then if [[ ${os_version} -lt 7 ]]; then
os_version=$(awk -F'[= ."]' '/VERSION_ID/{print $3}' /etc/os-release) echo -e "${red} Please use CentOS 7 or higher ${plain}\n" && exit 1
fi fi
if [[ -z "$os_version" && -f /etc/lsb-release ]]; then elif [[ "${release}" == "ubuntu" ]]; then
os_version=$(awk -F'[= ."]+' '/DISTRIB_RELEASE/{print $2}' /etc/lsb-release) if [[ ${os_version} -lt 18 ]]; then
fi echo -e "${red}please use Ubuntu 18 or higher version${plain}\n" && exit 1
fi
if [[ x"${release}" == x"centos" ]]; then elif [[ "${release}" == "fedora" ]]; then
if [[ ${os_version} -le 6 ]]; then if [[ ${os_version} -lt 36 ]]; then
echo -e "${red} Please use CentOS 7 or higher version ${plain}\n" && exit 1 echo -e "${red}please use Fedora 36 or higher version${plain}\n" && exit 1
fi fi
elif [[ x"${release}" == x"ubuntu" ]]; then
if [[ ${os_version} -lt 16 ]]; then elif [[ "${release}" == "debian" ]]; then
echo -e "${red} Please use Ubuntu 16 or higher version ${plain}\n" && exit 1
fi
elif [[ x"${release}" == x"debian" ]]; then
if [[ ${os_version} -lt 8 ]]; then if [[ ${os_version} -lt 8 ]]; then
echo -e "${red} Please use Debian 8 or higher version ${plain}\n" && exit 1 echo -e "${red} Please use Debian 8 or higher ${plain}\n" && exit 1
fi fi
else
echo -e "${red}Failed to check the OS version, please contact the author!${plain}" && exit 1
fi fi
install_base() { install_base() {
if [[ x"${release}" == x"centos" ]]; then if [[ "${release}" == "centos" ]]; then
yum install wget curl tar -y yum install wget curl tar -y
else else
apt install wget curl tar -y apt install wget curl tar -y

View File

@@ -1,269 +1,339 @@
#app { #app {
height: 100%; height: 100%;
} }
.ant-space { .ant-space {
width: 100%; width: 100%;
} }
.ant-layout-sider-zero-width-trigger { .ant-layout-sider-zero-width-trigger {
display: none; display: none;
} }
.ant-card { .ant-card {
border-radius: 30px; border-radius: 30px;
} }
.ant-card-hoverable { .ant-card-hoverable {
cursor: auto; cursor: auto;
} }
.ant-card+.ant-card { .ant-card+.ant-card {
margin-top: 20px; margin-top: 20px;
} }
.drawer-handle { .drawer-handle {
position: absolute; position: absolute;
top: 72px; top: 72px;
width: 41px; width: 41px;
height: 40px; height: 40px;
cursor: pointer; cursor: pointer;
z-index: 0; z-index: 0;
text-align: center; text-align: center;
line-height: 40px; line-height: 40px;
font-size: 16px; font-size: 16px;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
background: #fff; background: #fff;
right: -40px; right: -40px;
box-shadow: 2px 0 8px rgba(0, 0, 0, 0.15); box-shadow: 2px 0 8px rgba(0, 0, 0, 0.15);
border-radius: 0 4px 4px 0; border-radius: 0 4px 4px 0;
} }
@media (min-width: 769px) { @media (min-width: 769px) {
.drawer-handle { .drawer-handle {
display: none; display: none;
} }
} }
.fade-in-enter, .fade-in-leave-active, .fade-in-linear-enter, .fade-in-linear-leave, .fade-in-linear-leave-active, .fade-in-linear-enter, .fade-in-linear-leave, .fade-in-linear-leave-active { .fade-in-enter, .fade-in-leave-active, .fade-in-linear-enter, .fade-in-linear-leave, .fade-in-linear-leave-active, .fade-in-linear-enter, .fade-in-linear-leave, .fade-in-linear-leave-active {
opacity: 0 opacity: 0
} }
.fade-in-linear-enter-active, .fade-in-linear-leave-active { .fade-in-linear-enter-active, .fade-in-linear-leave-active {
-webkit-transition: opacity .2s linear; -webkit-transition: opacity .2s linear;
transition: opacity .2s linear transition: opacity .2s linear
} }
.fade-in-linear-enter-active, .fade-in-linear-leave-active { .fade-in-linear-enter-active, .fade-in-linear-leave-active {
-webkit-transition: opacity .2s linear; -webkit-transition: opacity .2s linear;
transition: opacity .2s linear transition: opacity .2s linear
} }
.fade-in-enter-active, .fade-in-leave-active { .fade-in-enter-active, .fade-in-leave-active {
-webkit-transition: all .3s cubic-bezier(.55, 0, .1, 1); -webkit-transition: all .3s cubic-bezier(.55, 0, .1, 1);
transition: all .3s cubic-bezier(.55, 0, .1, 1) transition: all .3s cubic-bezier(.55, 0, .1, 1)
} }
.zoom-in-center-enter-active, .zoom-in-center-leave-active { .zoom-in-center-enter-active, .zoom-in-center-leave-active {
-webkit-transition: all .3s cubic-bezier(.55, 0, .1, 1); -webkit-transition: all .3s cubic-bezier(.55, 0, .1, 1);
transition: all .3s cubic-bezier(.55, 0, .1, 1) transition: all .3s cubic-bezier(.55, 0, .1, 1)
} }
.zoom-in-center-enter, .zoom-in-center-leave-active { .zoom-in-center-enter, .zoom-in-center-leave-active {
opacity: 0; opacity: 0;
-webkit-transform: scaleX(0); -webkit-transform: scaleX(0);
transform: scaleX(0) transform: scaleX(0)
} }
.zoom-in-top-enter-active, .zoom-in-top-leave-active { .zoom-in-top-enter-active, .zoom-in-top-leave-active {
opacity: 1; opacity: 1;
-webkit-transform: scaleY(1); -webkit-transform: scaleY(1);
transform: scaleY(1); transform: scaleY(1);
-webkit-transition: opacity .3s cubic-bezier(.23, 1, .32, 1), -webkit-transform .3s cubic-bezier(.23, 1, .32, 1); -webkit-transition: opacity .3s cubic-bezier(.23, 1, .32, 1), -webkit-transform .3s cubic-bezier(.23, 1, .32, 1);
transition: opacity .3s cubic-bezier(.23, 1, .32, 1), -webkit-transform .3s cubic-bezier(.23, 1, .32, 1); transition: opacity .3s cubic-bezier(.23, 1, .32, 1), -webkit-transform .3s cubic-bezier(.23, 1, .32, 1);
transition: transform .3s cubic-bezier(.23, 1, .32, 1), opacity .3s cubic-bezier(.23, 1, .32, 1); transition: transform .3s cubic-bezier(.23, 1, .32, 1), opacity .3s cubic-bezier(.23, 1, .32, 1);
transition: transform .3s cubic-bezier(.23, 1, .32, 1), opacity .3s cubic-bezier(.23, 1, .32, 1), -webkit-transform .3s cubic-bezier(.23, 1, .32, 1); transition: transform .3s cubic-bezier(.23, 1, .32, 1), opacity .3s cubic-bezier(.23, 1, .32, 1), -webkit-transform .3s cubic-bezier(.23, 1, .32, 1);
-webkit-transform-origin: center top; -webkit-transform-origin: center top;
transform-origin: center top transform-origin: center top
} }
.zoom-in-top-enter, .zoom-in-top-leave-active { .zoom-in-top-enter, .zoom-in-top-leave-active {
opacity: 0; opacity: 0;
-webkit-transform: scaleY(0); -webkit-transform: scaleY(0);
transform: scaleY(0) transform: scaleY(0)
} }
.zoom-in-bottom-enter-active, .zoom-in-bottom-leave-active { .zoom-in-bottom-enter-active, .zoom-in-bottom-leave-active {
opacity: 1; opacity: 1;
-webkit-transform: scaleY(1); -webkit-transform: scaleY(1);
transform: scaleY(1); transform: scaleY(1);
-webkit-transition: opacity .3s cubic-bezier(.23, 1, .32, 1), -webkit-transform .3s cubic-bezier(.23, 1, .32, 1); -webkit-transition: opacity .3s cubic-bezier(.23, 1, .32, 1), -webkit-transform .3s cubic-bezier(.23, 1, .32, 1);
transition: opacity .3s cubic-bezier(.23, 1, .32, 1), -webkit-transform .3s cubic-bezier(.23, 1, .32, 1); transition: opacity .3s cubic-bezier(.23, 1, .32, 1), -webkit-transform .3s cubic-bezier(.23, 1, .32, 1);
transition: transform .3s cubic-bezier(.23, 1, .32, 1), opacity .3s cubic-bezier(.23, 1, .32, 1); transition: transform .3s cubic-bezier(.23, 1, .32, 1), opacity .3s cubic-bezier(.23, 1, .32, 1);
transition: transform .3s cubic-bezier(.23, 1, .32, 1), opacity .3s cubic-bezier(.23, 1, .32, 1), -webkit-transform .3s cubic-bezier(.23, 1, .32, 1); transition: transform .3s cubic-bezier(.23, 1, .32, 1), opacity .3s cubic-bezier(.23, 1, .32, 1), -webkit-transform .3s cubic-bezier(.23, 1, .32, 1);
-webkit-transform-origin: center bottom; -webkit-transform-origin: center bottom;
transform-origin: center bottom transform-origin: center bottom
} }
.zoom-in-bottom-enter, .zoom-in-bottom-leave-active { .zoom-in-bottom-enter, .zoom-in-bottom-leave-active {
opacity: 0; opacity: 0;
-webkit-transform: scaleY(0); -webkit-transform: scaleY(0);
transform: scaleY(0) transform: scaleY(0)
} }
.zoom-in-left-enter-active, .zoom-in-left-leave-active { .zoom-in-left-enter-active, .zoom-in-left-leave-active {
opacity: 1; opacity: 1;
-webkit-transform: scale(1, 1); -webkit-transform: scale(1, 1);
transform: scale(1, 1); transform: scale(1, 1);
-webkit-transition: opacity .3s cubic-bezier(.23, 1, .32, 1), -webkit-transform .3s cubic-bezier(.23, 1, .32, 1); -webkit-transition: opacity .3s cubic-bezier(.23, 1, .32, 1), -webkit-transform .3s cubic-bezier(.23, 1, .32, 1);
transition: opacity .3s cubic-bezier(.23, 1, .32, 1), -webkit-transform .3s cubic-bezier(.23, 1, .32, 1); transition: opacity .3s cubic-bezier(.23, 1, .32, 1), -webkit-transform .3s cubic-bezier(.23, 1, .32, 1);
transition: transform .3s cubic-bezier(.23, 1, .32, 1), opacity .3s cubic-bezier(.23, 1, .32, 1); transition: transform .3s cubic-bezier(.23, 1, .32, 1), opacity .3s cubic-bezier(.23, 1, .32, 1);
transition: transform .3s cubic-bezier(.23, 1, .32, 1), opacity .3s cubic-bezier(.23, 1, .32, 1), -webkit-transform .3s cubic-bezier(.23, 1, .32, 1); transition: transform .3s cubic-bezier(.23, 1, .32, 1), opacity .3s cubic-bezier(.23, 1, .32, 1), -webkit-transform .3s cubic-bezier(.23, 1, .32, 1);
-webkit-transform-origin: top left; -webkit-transform-origin: top left;
transform-origin: top left transform-origin: top left
} }
.zoom-in-left-enter, .zoom-in-left-leave-active { .zoom-in-left-enter, .zoom-in-left-leave-active {
opacity: 0; opacity: 0;
-webkit-transform: scale(.45, .45); -webkit-transform: scale(.45, .45);
transform: scale(.45, .45) transform: scale(.45, .45)
} }
.list-enter-active, .list-leave-active { .list-enter-active, .list-leave-active {
-webkit-transition: all .3s; -webkit-transition: all .3s;
transition: all .3s transition: all .3s
} }
.list-enter, .list-leave-active { .list-enter, .list-leave-active {
opacity: 0; opacity: 0;
-webkit-transform: translateY(-30px); -webkit-transform: translateY(-30px);
transform: translateY(-30px) transform: translateY(-30px)
} }
.ant-progress-inner { .ant-progress-inner {
background-color: #EBEEF5; background-color: #EBEEF5;
} }
.deactive-client .ant-collapse-header{ .deactive-client .ant-collapse-header{
color:rgb(255, 255, 255) !important; color:rgb(255, 255, 255) !important;
background-color: rgb(255, 127, 127); background-color: rgb(255, 127, 127);
} }
.ant-table-tbody>tr>td, .ant-table-tbody>tr>td,
.ant-table-thead>tr>th{ .ant-table-thead>tr>th{
padding:16px; padding:16px;
} }
.ant-card-dark { .ant-menu-dark,
color: hsla(0,0%,100%,.65); .ant-menu-dark .ant-menu-sub,
background-color: #001529; .ant-layout-header,
border-color:rgba(0,0,0,.09); .ant-layout-sider-dark,
} .ant-layout-sider-zero-width-trigger,
.ant-dropdown-menu-dark,.ant-dropdown-menu-dark .ant-dropdown-menu,
.ant-card-dark:hover { .ant-menu-dark.ant-menu-horizontal>.ant-menu-item,.ant-menu-dark.ant-menu-horizontal>.ant-menu-submenu {
border-color: #e8e8e8; background:#161b22
} }
.ant-card-dark .ant-table-thead th { .ant-card-dark {
color: hsla(0,0%,100%,.65); color: hsla(0,0%,100%,.65);
background-color: #000c17; background-color: #1a212a;
} border-color:rgba(0,0,0,.09);
}
.ant-card-dark .ant-table-tbody tr td,
.ant-card-dark .ant-modal-title { .ant-card-dark:hover {
color: hsla(0,0%,100%,.65); border-color: #e8e8e8;
} }
.ant-card-dark .ant-collapse-content, .ant-card-dark .ant-table-thead th {
.ant-card-dark .ant-calendar, color: hsla(0,0%,100%,.65);
.ant-card-dark .ant-table-placeholder { background-color: #161b22;
color: hsla(0,0%,100%,.65); }
background-color: #001529;
} .ant-card-dark .ant-table-tbody tr td,
.ant-card-dark .ant-modal-title {
.ant-card-dark .ant-list-item-meta-title, color: hsla(0,0%,100%,.65);
.ant-card-dark .ant-list-item-meta-description, }
.ant-card-dark .ant-form-item-label>label,
.ant-card-dark .ant-form-item, .ant-card-dark .ant-collapse-content,
.ant-card-dark .ant-divider-inner-text, .ant-card-dark .ant-calendar,
.ant-card-dark .ant-modal-confirm-content, .ant-card-dark .ant-table-placeholder,
.ant-card-dark .ant-modal-confirm-title, .ant-card-dark .ant-input-group-addon {
.ant-card-dark .ant-progress-text, color: hsla(0,0%,100%,.65);
.ant-card-dark .ant-modal-close, background-color: #262f3d;
.ant-card-dark i, }
.ant-card-dark .ant-select-dropdown-menu-item,
.ant-card-dark .ant-calendar-month-select, .ant-card-dark .ant-list-item-meta-title,
.ant-card-dark .ant-calendar-year-select, .ant-card-dark .ant-list-item-meta-description,
.ant-card-dark .ant-calendar-date, .ant-card-dark .ant-form-item-label>label,
.ant-card-dark .ant-collapse>.ant-collapse-item>.ant-collapse-header, .ant-card-dark .ant-form-item,
.ant-card-dark .ant-empty-normal { .ant-card-dark .ant-divider-inner-text,
color: hsla(0,0%,100%,.65); .ant-card-dark .ant-modal-confirm-content,
} .ant-card-dark .ant-modal-confirm-title,
.ant-card-dark .ant-progress-text,
.ant-card-dark .ant-table-tbody>tr:hover:not(.ant-table-expanded-row):not(.ant-table-row-selected)>td, .ant-card-dark .ant-modal-close,
.ant-card-dark .ant-select-dropdown-menu-item:hover:not(.ant-select-dropdown-menu-item-disabled), .ant-card-dark i,
.ant-card-dark .ant-calendar-date:hover { .ant-card-dark .ant-select-dropdown-menu-item,
background-color: #004488; .ant-card-dark .ant-calendar-month-select,
} .ant-card-dark .ant-calendar-year-select,
.ant-card-dark .ant-calendar-date,
.ant-card-dark tbody .ant-table-expanded-row { .ant-card-dark .ant-collapse>.ant-collapse-item>.ant-collapse-header,
color: hsla(0,0%,100%,.65); .ant-card-dark .ant-empty-normal,
background-color: #023366; .ant-card-dark .ant-checkbox+span {
} color: hsla(0,0%,100%,.65);
}
.ant-card-dark .ant-input,
.ant-card-dark .ant-input-number, .ant-card-dark .ant-table-tbody>tr:hover:not(.ant-table-expanded-row):not(.ant-table-row-selected)>td,
.ant-card-dark .ant-calendar-input, .ant-card-dark .ant-select-dropdown-menu-item:hover:not(.ant-select-dropdown-menu-item-disabled),
.ant-card-dark .ant-select-dropdown-menu-item-selected, .ant-card-dark .ant-calendar-date:hover {
.ant-card-dark .ant-select-selection { background-color: #004488;
color: hsla(0,0%,100%,.65); }
background-color: #023366;
} .ant-card-dark tbody .ant-table-expanded-row {
color: hsla(0,0%,100%,.65);
.ant-card-dark .ant-collapse-item { background-color: #1a212a;
color: hsla(0,0%,100%,.65); }
background-color: #000c17;
} .ant-card-dark .ant-input,
.ant-card-dark .ant-input-number,
.ant-card-dark .ant-modal-content, .ant-card-dark .ant-calendar-input,
.ant-card-dark .ant-modal-body, .ant-card-dark .ant-select-dropdown-menu-item-selected,
.ant-card-dark .ant-modal-header, .ant-card-dark .ant-select-selection {
.ant-card-dark .ant-calendar-selected-day .ant-calendar-date { color: hsla(0,0%,100%,.65);
color: hsla(0,0%,100%,.65); background-color: #2e3b52;
background-color: #1C262D; }
}
.ant-card-dark .ant-collapse-item {
.client-table-header { color: hsla(0,0%,100%,.65);
background-color: #f0f2f5; background-color: #161b22;
} }
.client-table-odd-row { .ant-card-dark .ant-modal-content,
background-color: #fafafa; .ant-card-dark .ant-modal-body,
} .ant-card-dark .ant-modal-header,
.ant-card-dark .ant-calendar-selected-day .ant-calendar-date {
.ant-card-dark .client-table-header { color: hsla(0,0%,100%,.65);
background-color: #023366; background-color: #222a37;
color: hsla(0,0%,100%,.65); }
}
.client-table-header {
.ant-card-dark .client-table-odd-row { background-color: #f0f2f5;
color: hsla(0,0%,100%,.65); }
background-color: #1C262D;
} .client-table-odd-row {
background-color: #fafafa;
.ant-card-dark .ant-calendar-last-month-cell .ant-calendar-date, }
.ant-card-dark .ant-calendar-next-month-btn-day .ant-calendar-date {
color: hsla(0,0%,100%,.30); .ant-card-dark .client-table-header {
} background-color: #1a212a;
color: hsla(0,0%,100%,.65);
.ant-drawer-dark { }
color: hsla(0,0%,100%,.65);
} .ant-card-dark .client-table-odd-row {
color: hsla(0,0%,100%,.65);
.ant-drawer-dark .ant-drawer-wrapper-body, background-color: #242c3a;
.ant-drawer-dark .drawer-handle { }
background-color: #001529;
border: 1px solid hsla(0,0%,100%,.30); .ant-card-dark .ant-calendar-last-month-cell .ant-calendar-date,
.ant-card-dark .ant-calendar-next-month-btn-day .ant-calendar-date {
color: hsla(0,0%,100%,.30);
}
.ant-drawer-dark {
color: hsla(0,0%,100%,.65);
}
.ant-drawer-dark .ant-drawer-wrapper-body,
.ant-drawer-dark .drawer-handle {
background-color: #1a212a;
border: 1px solid hsla(0,0%,100%,.30);
}
.ant-card-dark .ant-tag-blue {
color: #3c9ae8;
background: #111d2c;
border-color: #15395b;
}
.ant-card-dark .ant-tag-green {
color: #6abe39;
background: #162312;
border-color: #274916;
}
.ant-card-dark .ant-tag-cyan {
color: #33bcb7;
background: #112123;
border-color: #144848;
}
.ant-card-dark .ant-tag-red {
color: #e84749;
background: #2a1215;
border-color: #58181c;
}
.ant-card-dark .ant-tag-orange {
color: #e89a3c;
background: #2b1d11;
border-color: #593815;
}
.ant-card-dark .ant-table-row-expand-icon,
.ant-card-dark .ant-checkbox-inner {
background: none;
}
.ant-card-dark .ant-switch-checked {
background-color: #0c61b0;
}
.ant-card-dark .ant-btn,
.ant-card-dark .ant-radio-button-wrapper {
color: hsla(0,0%,100%,.65);
background: none;
border: 1px solid hsla(0,0%,100%,.65);
}
.ant-card-dark .ant-radio-button-wrapper:hover {
color: #177ddc;
}
.ant-card-dark .ant-btn-primary {
color: hsla(0,0%,100%,.65);
background-color: #073763;
border-color: #1890ff;
text-shadow: 0 -1px 0 rgba(0,0,0,.12);
box-shadow: 0 2px 0 rgba(0,0,0,.045);
} }

View File

@@ -95,7 +95,6 @@ const UTLS_FINGERPRINT = {
const ALPN_OPTION = { const ALPN_OPTION = {
H2: "h2", H2: "h2",
HTTP1: "http/1.1", HTTP1: "http/1.1",
BOTH: "h2,http/1.1",
}; };
Object.freeze(Protocols); Object.freeze(Protocols);
@@ -474,8 +473,8 @@ class GrpcStreamSettings extends XrayCommonClass {
class TlsStreamSettings extends XrayCommonClass { class TlsStreamSettings extends XrayCommonClass {
constructor(serverName='', constructor(serverName='',
minVersion = TLS_VERSION_OPTION.TLS12, minVersion = TLS_VERSION_OPTION.TLS10,
maxVersion = TLS_VERSION_OPTION.TLS13, maxVersion = TLS_VERSION_OPTION.TLS12,
cipherSuites = '', cipherSuites = '',
certificates=[new TlsStreamSettings.Cert()], certificates=[new TlsStreamSettings.Cert()],
alpn=[''], alpn=[''],
@@ -575,9 +574,9 @@ TlsStreamSettings.Cert = class extends XrayCommonClass {
}; };
TlsStreamSettings.Settings = class extends XrayCommonClass { TlsStreamSettings.Settings = class extends XrayCommonClass {
constructor(insecure = false, fingerprint = '', serverName = '') { constructor(allowInsecure = false, fingerprint = '', serverName = '') {
super(); super();
this.inSecure = insecure; this.allowInsecure = allowInsecure;
this.fingerprint = fingerprint; this.fingerprint = fingerprint;
this.serverName = serverName; this.serverName = serverName;
} }
@@ -590,7 +589,7 @@ TlsStreamSettings.Settings = class extends XrayCommonClass {
} }
toJson() { toJson() {
return { return {
allowInsecure: this.inSecure, allowInsecure: this.allowInsecure,
fingerprint: this.fingerprint, fingerprint: this.fingerprint,
serverName: this.serverName, serverName: this.serverName,
}; };
@@ -1081,6 +1080,10 @@ class Inbound extends XrayCommonClass {
host: host, host: host,
path: path, path: path,
tls: this.stream.security, tls: this.stream.security,
sni: this.stream.tls.settings[0]['serverName'],
fp: this.stream.tls.settings[0]['fingerprint'],
alpn: this.stream.tls.alpn.join(','),
allowInsecure: this.stream.tls.settings[0].allowInsecure,
}; };
return 'vmess://' + base64(JSON.stringify(obj, null, 2)); return 'vmess://' + base64(JSON.stringify(obj, null, 2));
} }
@@ -1092,7 +1095,6 @@ class Inbound extends XrayCommonClass {
const type = this.stream.network; const type = this.stream.network;
const params = new Map(); const params = new Map();
params.set("type", this.stream.network); params.set("type", this.stream.network);
params.set("security", this.stream.security);
switch (type) { switch (type) {
case "tcp": case "tcp":
const tcp = this.stream.tcp; const tcp = this.stream.tcp;
@@ -1139,8 +1141,12 @@ class Inbound extends XrayCommonClass {
} }
if (this.tls) { if (this.tls) {
params.set("security", "tls");
params.set("fp" , this.stream.tls.settings[0]['fingerprint']); params.set("fp" , this.stream.tls.settings[0]['fingerprint']);
params.set("alpn", this.stream.tls.alpn[0]); params.set("alpn", this.stream.tls.alpn);
if(this.stream.tls.settings[0].allowInsecure){
params.set("allowInsecure", "1");
}
if (!ObjectUtil.isEmpty(this.stream.tls.server)) { if (!ObjectUtil.isEmpty(this.stream.tls.server)) {
address = this.stream.tls.server; address = this.stream.tls.server;
} }
@@ -1153,12 +1159,15 @@ class Inbound extends XrayCommonClass {
} }
if (this.xtls) { if (this.xtls) {
params.set("security", "xtls");
params.set("alpn", this.stream.tls.alpn);
if(this.stream.tls.settings[0].allowInsecure){
params.set("allowInsecure", "1");
}
if (!ObjectUtil.isEmpty(this.stream.tls.server)) { if (!ObjectUtil.isEmpty(this.stream.tls.server)) {
address = this.stream.tls.server; address = this.stream.tls.server;
if (type === "tcp") {
params.set("flow", this.settings.vlesses[clientIndex].flow);
}
} }
params.set("flow", this.settings.vlesses[clientIndex].flow);
} }
const link = `vless://${uuid}@${address}:${port}`; const link = `vless://${uuid}@${address}:${port}`;
@@ -1190,7 +1199,6 @@ class Inbound extends XrayCommonClass {
const type = this.stream.network; const type = this.stream.network;
const params = new Map(); const params = new Map();
params.set("type", this.stream.network); params.set("type", this.stream.network);
params.set("security", this.stream.security);
switch (type) { switch (type) {
case "tcp": case "tcp":
const tcp = this.stream.tcp; const tcp = this.stream.tcp;
@@ -1237,8 +1245,12 @@ class Inbound extends XrayCommonClass {
} }
if (this.tls) { if (this.tls) {
params.set("security", "tls");
params.set("fp" , this.stream.tls.settings[0]['fingerprint']); params.set("fp" , this.stream.tls.settings[0]['fingerprint']);
params.set("alpn", this.stream.tls.alpn[0]); params.set("alpn", this.stream.tls.alpn);
if(this.stream.tls.settings[0].allowInsecure){
params.set("allowInsecure", "1");
}
if (!ObjectUtil.isEmpty(this.stream.tls.server)) { if (!ObjectUtil.isEmpty(this.stream.tls.server)) {
address = this.stream.tls.server; address = this.stream.tls.server;
} }
@@ -1248,12 +1260,15 @@ class Inbound extends XrayCommonClass {
} }
if (this.xtls) { if (this.xtls) {
params.set("security", "xtls");
params.set("alpn", this.stream.tls.alpn);
if(this.stream.tls.settings[0].allowInsecure){
params.set("allowInsecure", "1");
}
if (!ObjectUtil.isEmpty(this.stream.tls.server)) { if (!ObjectUtil.isEmpty(this.stream.tls.server)) {
address = this.stream.tls.server; address = this.stream.tls.server;
if (type === "tcp" && this.settings.trojans[clientIndex].flow.length > 0) {
params.set("flow", this.settings.trojans[clientIndex].flow);
}
} }
params.set("flow", this.settings.trojans[clientIndex].flow);
} }
const link = `trojan://${settings.trojans[clientIndex].password}@${address}:${this.port}#${encodeURIComponent(remark)}`; const link = `trojan://${settings.trojans[clientIndex].password}@${address}:${this.port}#${encodeURIComponent(remark)}`;

View File

@@ -1,10 +1,11 @@
package controller package controller
import ( import (
"github.com/gin-gonic/gin"
"time" "time"
"x-ui/web/global" "x-ui/web/global"
"x-ui/web/service" "x-ui/web/service"
"github.com/gin-gonic/gin"
) )
type ServerController struct { type ServerController struct {
@@ -37,6 +38,7 @@ func (a *ServerController) initRouter(g *gin.RouterGroup) {
g.POST("/stopXrayService", a.stopXrayService) g.POST("/stopXrayService", a.stopXrayService)
g.POST("/restartXrayService", a.restartXrayService) g.POST("/restartXrayService", a.restartXrayService)
g.POST("/installXray/:version", a.installXray) g.POST("/installXray/:version", a.installXray)
g.POST("/logs", a.getLogs)
} }
func (a *ServerController) refreshStatus() { func (a *ServerController) refreshStatus() {
@@ -87,13 +89,13 @@ func (a *ServerController) installXray(c *gin.Context) {
} }
func (a *ServerController) stopXrayService(c *gin.Context) { func (a *ServerController) stopXrayService(c *gin.Context) {
a.lastGetStatusTime = time.Now() a.lastGetStatusTime = time.Now()
err := a.serverService.StopXrayService() err := a.serverService.StopXrayService()
if err != nil { if err != nil {
jsonMsg(c, "", err) jsonMsg(c, "", err)
return return
} }
jsonMsg(c, "Xray stoped",err) jsonMsg(c, "Xray stoped", err)
} }
func (a *ServerController) restartXrayService(c *gin.Context) { func (a *ServerController) restartXrayService(c *gin.Context) {
@@ -102,6 +104,15 @@ func (a *ServerController) restartXrayService(c *gin.Context) {
jsonMsg(c, "", err) jsonMsg(c, "", err)
return return
} }
jsonMsg(c, "Xray restarted",err) jsonMsg(c, "Xray restarted", err)
} }
func (a *ServerController) getLogs(c *gin.Context) {
logs, err := a.serverService.GetLogs()
if err != nil {
jsonMsg(c, I18n(c, "getLogs"), err)
return
}
jsonObj(c, logs, nil)
}

View File

@@ -1,9 +1,10 @@
{{define "qrcodeModal"}} {{define "qrcodeModal"}}
<a-modal id="qrcode-modal" v-model="qrModal.visible" :title="qrModal.title" <a-modal id="qrcode-modal" v-model="qrModal.visible" :title="qrModal.title"
:closable="true" width="300px" :ok-text="qrModal.okText" :closable="true"
:class="siderDrawer.isDarkTheme ? darkClass : ''" :class="siderDrawer.isDarkTheme ? darkClass : ''"
cancel-text='{{ i18n "close" }}' :ok-button-props="{attrs:{id:'qr-modal-ok-btn'}}"> :footer="null"
<a-tag color="green" style="margin-bottom: 10px;display: block;text-align: center;" >{{ i18n "pages.inbounds.clickOnQRcode" }}</a-tag> width="300px">
<a-tag color="green" style="margin-bottom: 10px;display: block;text-align: center;" >{{ i18n "pages.inbounds.clickOnQRcode" }}</a-tag>
<canvas @click="copyToClipboard()" id="qrCode" style="width: 100%; height: 100%;"></canvas> <canvas @click="copyToClipboard()" id="qrCode" style="width: 100%; height: 100%;"></canvas>
</a-modal> </a-modal>
@@ -14,17 +15,15 @@
content: '', content: '',
inbound: new Inbound(), inbound: new Inbound(),
dbInbound: new DBInbound(), dbInbound: new DBInbound(),
okText: '',
copyText: '', copyText: '',
qrcode: null, qrcode: null,
clipboard: null, clipboard: null,
visible: false, visible: false,
show: function (title='', content='', dbInbound=new DBInbound(),okText='{{ i18n "copy" }}', copyText='') { show: function (title='', content='', dbInbound=new DBInbound(), copyText='') {
this.title = title; this.title = title;
this.content = content; this.content = content;
this.dbInbound = dbInbound; this.dbInbound = dbInbound;
this.inbound = dbInbound.toInbound(); this.inbound = dbInbound.toInbound();
this.okText = okText;
if (ObjectUtil.isEmpty(copyText)) { if (ObjectUtil.isEmpty(copyText)) {
this.copyText = content; this.copyText = content;
} else { } else {
@@ -32,13 +31,6 @@
} }
this.visible = true; this.visible = true;
qrModalApp.$nextTick(() => { qrModalApp.$nextTick(() => {
this.clipboard = new ClipboardJS('#qr-modal-ok-btn', {
text: () => this.copyText,
});
this.clipboard.on('success', () => {
app.$message.success('{{ i18n "copied" }}')
this.clipboard.destroy();
});
if (this.qrcode === null) { if (this.qrcode === null) {
this.qrcode = new QRious({ this.qrcode = new QRious({
element: document.querySelector('#qrCode'), element: document.querySelector('#qrCode'),

View File

@@ -7,10 +7,11 @@
<a-form-item label='{{ i18n "pages.client.method" }}'> <a-form-item label='{{ i18n "pages.client.method" }}'>
<a-select v-model="clientsBulkModal.emailMethod" buttonStyle="solid" style="width: 350px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"> <a-select v-model="clientsBulkModal.emailMethod" buttonStyle="solid" style="width: 350px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
<a-select-option :value="0">Random</a-select-option> <a-select-option :value="0">Random</a-select-option>
<a-select-option :value="1">Random_Prefix</a-select-option> <a-select-option :value="1">Random+Prefix</a-select-option>
<a-select-option :value="2">Random_Prefix+Num</a-select-option> <a-select-option :value="2">Random+Prefix+Num</a-select-option>
<a-select-option :value="3">Random_Prefix+Num+Postfix</a-select-option> <a-select-option :value="3">Random+Prefix+Num+Postfix</a-select-option>
<a-select-option :value="4">Random_Prefix+Num@Telegram Username</a-select-option> <a-select-option :value="4">Random+Prefix+Num@Telegram Username</a-select-option>
<a-select-option :value="5">Prefix+Num+Postfix [ BE CAREFUL! ]</a-select-option>
</a-select> </a-select>
</a-form-item><br /> </a-form-item><br />
<a-form-item v-if="clientsBulkModal.emailMethod>1"> <a-form-item v-if="clientsBulkModal.emailMethod>1">
@@ -91,11 +92,12 @@
start=0; start=0;
end=clientsBulkModal.quantity; end=clientsBulkModal.quantity;
} }
prefix = (method>0 && clientsBulkModal.emailPrefix.length>0) ? "_" + clientsBulkModal.emailPrefix : ""; prefix = (method>0 && clientsBulkModal.emailPrefix.length>0) ? clientsBulkModal.emailPrefix : "";
useNum=(method>1); useNum=(method>1);
postfix = (method>2 && clientsBulkModal.emailPostfix.length>0) ? (method == 4 ? "@" : "") + clientsBulkModal.emailPostfix : ""; postfix = (method>2 && clientsBulkModal.emailPostfix.length>0) ? (method == 4 ? "@" : "") + clientsBulkModal.emailPostfix : "";
for (let i = start; i < end; i++) { for (let i = start; i < end; i++) {
newClient = clientsBulkModal.newClient(clientsBulkModal.dbInbound.protocol); newClient = clientsBulkModal.newClient(clientsBulkModal.dbInbound.protocol);
if(method==5) newClient.email = "";
newClient.email += useNum ? prefix + i.toString() + postfix : prefix + postfix; newClient.email += useNum ? prefix + i.toString() + postfix : prefix + postfix;
newClient._totalGB = clientsBulkModal.totalGB; newClient._totalGB = clientsBulkModal.totalGB;
newClient._expiryTime = clientsBulkModal.expiryTime; newClient._expiryTime = clientsBulkModal.expiryTime;

View File

@@ -72,7 +72,7 @@
</a-drawer> </a-drawer>
<script> <script>
const darkClass = "ant-card-dark"; const darkClass = "ant-card-dark";
const bgDarkStyle = "background-color: #1C262D"; const bgDarkStyle = "background-color: #242c3a";
const siderDrawer = { const siderDrawer = {
visible: false, visible: false,
collapsed: false, collapsed: false,

View File

@@ -65,7 +65,7 @@
</table> </table>
</a-collapse-panel> </a-collapse-panel>
</a-collapse> </a-collapse>
<template v-if="inbound.isTcp && inbound.tls"> <template v-if="inbound.isTcp && (inbound.tls || inbound.xtls)">
<a-form layout="inline"> <a-form layout="inline">
<a-form-item label="Fallbacks"> <a-form-item label="Fallbacks">
<a-row> <a-row>

View File

@@ -71,7 +71,7 @@
</table> </table>
</a-collapse-panel> </a-collapse-panel>
</a-collapse> </a-collapse>
<template v-if="inbound.isTcp && inbound.tls"> <template v-if="inbound.isTcp && (inbound.tls || inbound.xtls)">
<a-form layout="inline"> <a-form layout="inline">
<a-form-item label="Fallbacks"> <a-form-item label="Fallbacks">
<a-row> <a-row>

View File

@@ -40,11 +40,13 @@
<a-form-item label='{{ i18n "domainName" }}'> <a-form-item label='{{ i18n "domainName" }}'>
<a-input v-model.trim="inbound.stream.tls.server"></a-input> <a-input v-model.trim="inbound.stream.tls.server"></a-input>
</a-form-item> </a-form-item>
<a-form-item label="Alpn" v-if="inbound.tls"> <a-form-item label="Alpn">
<a-select v-model="inbound.stream.tls.alpn[0]" style="width:200px"> <a-checkbox-group v-model="inbound.stream.tls.alpn" style="width:200px">
<a-select-option value=''>auto</a-select-option> <a-checkbox v-for="key in ALPN_OPTION" :value="key">[[ key ]]</a-checkbox>
<a-select-option v-for="key in ALPN_OPTION" :value="key">[[ key ]]</a-select-option> </a-checkbox-group>
</a-select> </a-form-item>
<a-form-item label="Allow insecure">
<a-switch v-model="inbound.stream.tls.settings[0].allowInsecure"></a-switch>
</a-form-item> </a-form-item>
<a-form-item label='{{ i18n "certificate" }}'> <a-form-item label='{{ i18n "certificate" }}'>
<a-radio-group v-model="inbound.stream.tls.certs[0].useFile" button-style="solid"> <a-radio-group v-model="inbound.stream.tls.certs[0].useFile" button-style="solid">

View File

@@ -59,12 +59,10 @@
</table> </table>
<template v-if="infoModal.clientSettings"> <template v-if="infoModal.clientSettings">
<a-divider>{{ i18n "pages.inbounds.client" }}</a-divider> <a-divider>{{ i18n "pages.inbounds.client" }}</a-divider>
<table style="margin-bottom: 10px; width: 100%;"> <table style="margin-bottom: 10px;">
<tr> <tr v-for="col,index in Object.keys(infoModal.clientSettings).slice(0, 3)">
<th v-for="col in Object.keys(infoModal.clientSettings).slice(0, 3)">[[ col ]]</th> <td>[[ col ]]</td>
</tr> <td><a-tag color="green">[[ infoModal.clientSettings[col] ]]</a-tag></td>
<tr>
<td v-for="col in Object.values(infoModal.clientSettings).slice(0, 3)"><a-tag color="green">[[ col ]]</a-tag></td>
</table> </table>
<table style="margin-bottom: 10px; width: 100%;"> <table style="margin-bottom: 10px; width: 100%;">
<tr><th>{{ i18n "usage" }}</th><th>{{ i18n "pages.inbounds.totalFlow" }}</th><th>{{ i18n "pages.inbounds.expireDate" }}</th><th>{{ i18n "enable" }}</th></tr> <tr><th>{{ i18n "usage" }}</th><th>{{ i18n "pages.inbounds.totalFlow" }}</th><th>{{ i18n "pages.inbounds.expireDate" }}</th><th>{{ i18n "enable" }}</th></tr>

View File

@@ -11,10 +11,6 @@
.ant-col-sm-24 { .ant-col-sm-24 {
margin-top: 10px; margin-top: 10px;
} }
.ant-table-row-expand-icon {
color: rgba(0,0,0,.65);
}
</style> </style>
<body> <body>
<a-layout id="app" v-cloak> <a-layout id="app" v-cloak>
@@ -118,11 +114,11 @@
</template> </template>
<a-tag v-else color="green">{{ i18n "unlimited" }}</a-tag> <a-tag v-else color="green">{{ i18n "unlimited" }}</a-tag>
</template> </template>
<template slot="stream" slot-scope="text, dbInbound, index"> <template slot="stream" slot-scope="text, dbInbound">
<template v-if="dbInbound.isVMess || dbInbound.isVLess || dbInbound.isTrojan || dbInbound.isSS"> <template v-if="dbInbound.isVMess || dbInbound.isVLess || dbInbound.isTrojan || dbInbound.isSS">
<a-tag color="green">[[ inbounds[index].stream.network ]]</a-tag> <a-tag color="green">[[ dbInbound.toInbound().stream.network ]]</a-tag>
<a-tag v-if="inbounds[index].stream.isTls" color="blue">tls</a-tag> <a-tag v-if="dbInbound.toInbound().stream.isTls" color="blue">tls</a-tag>
<a-tag v-if="inbounds[index].stream.isXTls" color="blue">xtls</a-tag> <a-tag v-if="dbInbound.toInbound().stream.isXTls" color="blue">xtls</a-tag>
</template> </template>
<template v-else>{{ i18n "none" }}</template> <template v-else>{{ i18n "none" }}</template>
</template> </template>

View File

@@ -84,16 +84,18 @@
</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-tag color="green" @click="openSelectV2rayVersion">[[ status.xray.version ]]</a-tag> <a-tag color="green" style="cursor: pointer;" @click="openSelectV2rayVersion">[[ status.xray.version ]]</a-tag>
<a-tag color="blue" @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" @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" @click="openSelectV2rayVersion">{{ i18n "pages.index.xraySwitch" }}</a-tag> <a-tag color="blue" style="cursor: pointer;" @click="openSelectV2rayVersion">{{ i18n "pages.index.xraySwitch" }}</a-tag>
</a-card> </a-card>
</a-col> </a-col>
<a-col :sm="24" :md="12"> <a-col :sm="24" :md="12">
<a-card hoverable :class="siderDrawer.isDarkTheme ? darkClass : ''"> <a-card hoverable :class="siderDrawer.isDarkTheme ? darkClass : ''">
x-ui: <a-tag color="green">{{ .cur_ver }}</a-tag>
<a-tag color="blue" style="cursor: pointer;" @click="openLogs">Logs</a-tag>
{{ i18n "pages.index.operationHours" }}: {{ i18n "pages.index.operationHours" }}:
<a-tag color="#87d068">[[ formatSecond(status.uptime) ]]</a-tag> <a-tag color="green">[[ formatSecond(status.uptime) ]]</a-tag>
<a-tooltip> <a-tooltip>
<template slot="title"> <template slot="title">
{{ i18n "pages.index.operationHoursDesc" }} {{ i18n "pages.index.operationHoursDesc" }}
@@ -177,7 +179,7 @@
<a-modal id="version-modal" v-model="versionModal.visible" title='{{ i18n "pages.index.xraySwitch" }}' <a-modal id="version-modal" v-model="versionModal.visible" title='{{ i18n "pages.index.xraySwitch" }}'
:closable="true" @ok="() => versionModal.visible = false" :closable="true" @ok="() => versionModal.visible = false"
:class="siderDrawer.isDarkTheme ? darkClass : ''" :class="siderDrawer.isDarkTheme ? darkClass : ''"
ok-text='{{ i18n "confirm" }}' cancel-text='{{ i18n "cancel"}}'> footer="">
<h2>{{ i18n "pages.index.xraySwitchClick"}}</h2> <h2>{{ i18n "pages.index.xraySwitchClick"}}</h2>
<h2>{{ i18n "pages.index.xraySwitchClickDesk"}}</h2> <h2>{{ i18n "pages.index.xraySwitchClickDesk"}}</h2>
<template v-for="version, index in versionModal.versions"> <template v-for="version, index in versionModal.versions">
@@ -187,6 +189,17 @@
</a-tag> </a-tag>
</template> </template>
</a-modal> </a-modal>
<a-modal id="log-modal" v-model="logModal.visible" title="X-UI logs"
:closable="true" @ok="() => logModal.visible = false" @cancel="() => logModal.visible = false"
:class="siderDrawer.isDarkTheme ? darkClass : ''"
width="800px"
footer="">
<table style="margin: 0px; width: 100%; background-color: black; color: hsla(0,0%,100%,.65);">
<tr v-for="log , index in logModal.logs">
<td style="vertical-align: top;">[[ index ]]</td><td>[[ log ]]</td>
</tr>
</table>
</a-modal>
</a-layout> </a-layout>
{{template "js" .}} {{template "js" .}}
<script> <script>
@@ -280,6 +293,18 @@
}, },
}; };
const logModal = {
visible: false,
logs: '',
show(logs) {
this.visible = true;
this.logs = logs;
},
hide() {
this.visible = false;
},
};
const app = new Vue({ const app = new Vue({
delimiters: ['[[', ']]'], delimiters: ['[[', ']]'],
el: '#app', el: '#app',
@@ -287,6 +312,7 @@
siderDrawer, siderDrawer,
status: new Status(), status: new Status(),
versionModal, versionModal,
logModal,
spinning: false, spinning: false,
loadingTip: '{{ i18n "loading"}}', loadingTip: '{{ i18n "loading"}}',
}, },
@@ -346,6 +372,15 @@
return; return;
} }
}, },
async openLogs(){
this.loading(true);
const msg = await HttpUtil.post('server/logs');
this.loading(false);
if (!msg.success) {
return;
}
logModal.show(msg.obj);
}
}, },
async mounted() { async mounted() {
while (true) { while (true) {

View File

@@ -117,7 +117,7 @@
<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.telegramBotEnable" }}' desc='{{ i18n "pages.setting.telegramBotEnableDesc" }}' v-model="allSetting.tgBotEnable"></setting-list-item> <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.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.number="allSetting.tgBotChatId"></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="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="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.tgNotifyExpireTimeDiff" }}' desc='{{ i18n "pages.setting.tgNotifyExpireTimeDiffDesc" }}' v-model="allSetting.tgExpireDiff" :min="0"></setting-list-item> <setting-list-item type="number" title='{{ i18n "pages.setting.tgNotifyExpireTimeDiff" }}' desc='{{ i18n "pages.setting.tgNotifyExpireTimeDiffDesc" }}' v-model="allSetting.tgExpireDiff" :min="0"></setting-list-item>

View File

@@ -571,3 +571,13 @@ func (s *InboundService) SearchClientTraffic(query string) (traffic *xray.Client
} }
return traffic, err return traffic, err
} }
func (s *InboundService) SearchInbounds(query string) ([]*model.Inbound, error) {
db := database.GetDB()
var inbounds []*model.Inbound
err := db.Model(model.Inbound{}).Preload("ClientStats").Where("remark like ?", "%"+query+"%").Find(&inbounds).Error
if err != nil && err != gorm.ErrRecordNotFound {
return nil, err
}
return inbounds, nil
}

View File

@@ -9,7 +9,9 @@ import (
"io/fs" "io/fs"
"net/http" "net/http"
"os" "os"
"os/exec"
"runtime" "runtime"
"strings"
"time" "time"
"x-ui/logger" "x-ui/logger"
"x-ui/util/sys" "x-ui/util/sys"
@@ -200,24 +202,24 @@ func (s *ServerService) GetXrayVersions() ([]string, error) {
func (s *ServerService) StopXrayService() (string error) { func (s *ServerService) StopXrayService() (string error) {
err := s.xrayService.StopXray() err := s.xrayService.StopXray()
if err != nil { if err != nil {
logger.Error("stop xray failed:", err) logger.Error("stop xray failed:", err)
return err return err
} }
return nil return nil
} }
func (s *ServerService) RestartXrayService() (string error) { func (s *ServerService) RestartXrayService() (string error) {
s.xrayService.StopXray() s.xrayService.StopXray()
defer func() { defer func() {
err := s.xrayService.RestartXray(true) err := s.xrayService.RestartXray(true)
if err != nil { if err != nil {
logger.Error("start xray failed:", err) logger.Error("start xray failed:", err)
} }
}() }()
return nil return nil
} }
@@ -324,3 +326,26 @@ func (s *ServerService) UpdateXray(version string) error {
return nil return nil
} }
func (s *ServerService) GetLogs() ([]string, error) {
// Define the journalctl command and its arguments
var cmdArgs []string
if runtime.GOOS == "linux" {
cmdArgs = []string{"journalctl", "-u", "x-ui", "--no-pager", "-n", "100"}
} else {
return []string{"Unsupported operating system"}, nil
}
// Run the command
cmd := exec.Command(cmdArgs[0], cmdArgs[1:]...)
var out bytes.Buffer
cmd.Stdout = &out
err := cmd.Run()
if err != nil {
return nil, err
}
lines := strings.Split(out.String(), "\n")
return lines, nil
}

View File

@@ -105,8 +105,6 @@ func (t *Tgbot) OnReceive() {
} else { } else {
if update.Message.IsCommand() { if update.Message.IsCommand() {
t.answerCommand(update.Message, chatId, isAdmin) t.answerCommand(update.Message, chatId, isAdmin)
} else {
t.aswerChat(update.Message.Text, chatId, isAdmin)
} }
} }
} }
@@ -128,10 +126,20 @@ func (t *Tgbot) answerCommand(message *tgbotapi.Message, chatId int64, isAdmin b
case "status": case "status":
msg = "bot is ok ✅" msg = "bot is ok ✅"
case "usage": case "usage":
if isAdmin { if len(message.CommandArguments()) > 1 {
t.searchClient(chatId, message.CommandArguments()) if isAdmin {
t.searchClient(chatId, message.CommandArguments())
} else {
t.searchForClient(chatId, message.CommandArguments())
}
} else { } else {
t.searchForClient(chatId, message.CommandArguments()) msg = "❗Please provide a text for search!"
}
case "inbound":
if isAdmin {
t.searchInbound(chatId, message.CommandArguments())
} else {
msg = "❗ Unknown command"
} }
default: default:
msg = "❗ Unknown command" msg = "❗ Unknown command"
@@ -139,10 +147,6 @@ func (t *Tgbot) answerCommand(message *tgbotapi.Message, chatId int64, isAdmin b
t.SendAnswer(chatId, msg, isAdmin) t.SendAnswer(chatId, msg, isAdmin)
} }
func (t *Tgbot) aswerChat(message string, chatId int64, isAdmin bool) {
t.SendAnswer(chatId, "❗ Unknown message", isAdmin)
}
func (t *Tgbot) asnwerCallback(callbackQuery *tgbotapi.CallbackQuery, isAdmin bool) { func (t *Tgbot) asnwerCallback(callbackQuery *tgbotapi.CallbackQuery, isAdmin bool) {
// Respond to the callback query, telling Telegram to show the user // Respond to the callback query, telling Telegram to show the user
// a message with the data received. // a message with the data received.
@@ -165,7 +169,7 @@ func (t *Tgbot) asnwerCallback(callbackQuery *tgbotapi.CallbackQuery, isAdmin bo
case "client_commands": case "client_commands":
t.SendMsgToTgbot(callbackQuery.From.ID, "To search for statistics, just use folowing command:\r\n \r\n<code>/usage [UID|Passowrd]</code>\r\n \r\nUse UID for vmess and vless and Password for Trojan.") t.SendMsgToTgbot(callbackQuery.From.ID, "To search for statistics, just use folowing command:\r\n \r\n<code>/usage [UID|Passowrd]</code>\r\n \r\nUse UID for vmess and vless and Password for Trojan.")
case "commands": case "commands":
t.SendMsgToTgbot(callbackQuery.From.ID, "To search for a client email, just use folowing command:\r\n \r\n<code>/usage email</code>") t.SendMsgToTgbot(callbackQuery.From.ID, "Search for a client email:\r\n<code>/usage email</code>\r\n \r\nSearch for inbounds (with client stats):\r\n<code>/inbound [remark]</code>")
} }
} }
@@ -272,6 +276,7 @@ func (t *Tgbot) getServerUsage() string {
name = "" name = ""
} }
info = fmt.Sprintf("💻 Hostname: %s\r\n", name) info = fmt.Sprintf("💻 Hostname: %s\r\n", name)
info += fmt.Sprintf("🚀X-UI Version: %s\r\n", config.GetVersion())
//get ip address //get ip address
var ip string var ip string
var ipv6 string var ipv6 string
@@ -423,6 +428,45 @@ func (t *Tgbot) searchClient(chatId int64, email string) {
} }
} }
func (t *Tgbot) searchInbound(chatId int64, remark string) {
inbouds, err := t.inboundService.SearchInbounds(remark)
if err != nil {
logger.Warning(err)
msg := "❌ Something went wrong!"
t.SendMsgToTgbot(chatId, msg)
return
}
for _, inbound := range inbouds {
info := ""
info += fmt.Sprintf("📍Inbound:%s\r\nPort:%d\r\n", inbound.Remark, inbound.Port)
info += fmt.Sprintf("Traffic: %s (↑%s,↓%s)\r\n", common.FormatTraffic((inbound.Up + inbound.Down)), common.FormatTraffic(inbound.Up), common.FormatTraffic(inbound.Down))
if inbound.ExpiryTime == 0 {
info += "Expire date: ♾ Unlimited\r\n \r\n"
} else {
info += fmt.Sprintf("Expire date:%s\r\n \r\n", time.Unix((inbound.ExpiryTime/1000), 0).Format("2006-01-02 15:04:05"))
}
t.SendMsgToTgbot(chatId, info)
for _, traffic := range inbound.ClientStats {
expiryTime := ""
if traffic.ExpiryTime == 0 {
expiryTime = "♾Unlimited"
} else {
expiryTime = time.Unix((traffic.ExpiryTime / 1000), 0).Format("2006-01-02 15:04:05")
}
total := ""
if traffic.Total == 0 {
total = "♾Unlimited"
} else {
total = common.FormatTraffic((traffic.Total))
}
output := fmt.Sprintf("💡 Active: %t\r\n📧 Email: %s\r\n🔼 Upload↑: %s\r\n🔽 Download↓: %s\r\n🔄 Total: %s / %s\r\n📅 Expire in: %s\r\n",
traffic.Enable, traffic.Email, common.FormatTraffic(traffic.Up), common.FormatTraffic(traffic.Down), common.FormatTraffic((traffic.Up + traffic.Down)),
total, expiryTime)
t.SendMsgToTgbot(chatId, output)
}
}
}
func (t *Tgbot) searchForClient(chatId int64, query string) { func (t *Tgbot) searchForClient(chatId int64, query string) {
traffic, err := t.inboundService.SearchClientTraffic(query) traffic, err := t.inboundService.SearchClientTraffic(query)
if err != nil { if err != nil {
@@ -469,7 +513,7 @@ func (t *Tgbot) getExhausted() string {
} }
ExpireThreshold, err := t.settingService.GetTgExpireDiff() ExpireThreshold, err := t.settingService.GetTgExpireDiff()
if err == nil && ExpireThreshold > 0 { if err == nil && ExpireThreshold > 0 {
exDiff = int64(ExpireThreshold) * 84600 exDiff = int64(ExpireThreshold) * 84600000
} }
inbounds, err := t.inboundService.GetAllInbounds() inbounds, err := t.inboundService.GetAllInbounds()
if err != nil { if err != nil {
@@ -477,14 +521,14 @@ func (t *Tgbot) getExhausted() string {
} }
for _, inbound := range inbounds { for _, inbound := range inbounds {
if inbound.Enable { if inbound.Enable {
if (inbound.ExpiryTime > 0 && (now-inbound.ExpiryTime < exDiff)) || if (inbound.ExpiryTime > 0 && (inbound.ExpiryTime-now < exDiff)) ||
(inbound.Total > 0 && (inbound.Total-inbound.Up+inbound.Down < trDiff)) { (inbound.Total > 0 && (inbound.Total-inbound.Up+inbound.Down < trDiff)) {
exhaustedInbounds = append(exhaustedInbounds, *inbound) exhaustedInbounds = append(exhaustedInbounds, *inbound)
} }
if len(inbound.ClientStats) > 0 { if len(inbound.ClientStats) > 0 {
for _, client := range inbound.ClientStats { for _, client := range inbound.ClientStats {
if client.Enable { if client.Enable {
if (client.ExpiryTime > 0 && (now-client.ExpiryTime < exDiff)) || if (client.ExpiryTime > 0 && (client.ExpiryTime-now < exDiff)) ||
(client.Total > 0 && (client.Total-client.Up+client.Down < trDiff)) { (client.Total > 0 && (client.Total-client.Up+client.Down < trDiff)) {
exhaustedClients = append(exhaustedClients, client) exhaustedClients = append(exhaustedClients, client)
} }
@@ -498,7 +542,7 @@ func (t *Tgbot) getExhausted() string {
} }
} }
output += fmt.Sprintf("Exhausted Inbounds count:\r\n🛑 Disabled: %d\r\n🔜 Exhaust soon: %d\r\n \r\n", len(disabledInbounds), len(exhaustedInbounds)) output += fmt.Sprintf("Exhausted Inbounds count:\r\n🛑 Disabled: %d\r\n🔜 Exhaust soon: %d\r\n \r\n", len(disabledInbounds), len(exhaustedInbounds))
if len(disabledInbounds)+len(exhaustedInbounds) > 0 { if len(exhaustedInbounds) > 0 {
output += "Exhausted Inbounds:\r\n" output += "Exhausted Inbounds:\r\n"
for _, inbound := range exhaustedInbounds { for _, inbound := range exhaustedInbounds {
output += fmt.Sprintf("📍Inbound:%s\r\nPort:%d\r\nTraffic: %s (↑%s,↓%s)\r\n", inbound.Remark, inbound.Port, common.FormatTraffic((inbound.Up + inbound.Down)), common.FormatTraffic(inbound.Up), common.FormatTraffic(inbound.Down)) output += fmt.Sprintf("📍Inbound:%s\r\nPort:%d\r\nTraffic: %s (↑%s,↓%s)\r\n", inbound.Remark, inbound.Port, common.FormatTraffic((inbound.Up + inbound.Down)), common.FormatTraffic(inbound.Up), common.FormatTraffic(inbound.Down))
@@ -510,7 +554,7 @@ func (t *Tgbot) getExhausted() string {
} }
} }
output += fmt.Sprintf("Exhausted Clients count:\r\n🛑 Disabled: %d\r\n🔜 Exhaust soon: %d\r\n \r\n", len(disabledClients), len(exhaustedClients)) output += fmt.Sprintf("Exhausted Clients count:\r\n🛑 Disabled: %d\r\n🔜 Exhaust soon: %d\r\n \r\n", len(disabledClients), len(exhaustedClients))
if len(disabledClients)+len(exhaustedClients) > 0 { if len(exhaustedClients) > 0 {
output += "Exhausted Clients:\r\n" output += "Exhausted Clients:\r\n"
for _, traffic := range exhaustedClients { for _, traffic := range exhaustedClients {
expiryTime := "" expiryTime := ""
@@ -525,7 +569,7 @@ func (t *Tgbot) getExhausted() string {
} else { } else {
total = common.FormatTraffic((traffic.Total)) total = common.FormatTraffic((traffic.Total))
} }
output += fmt.Sprintf("💡 Active: %t\r\n📧 Email: %s\r\n🔼 Upload↑: %s\r\n🔽 Download↓: %s\r\n🔄 Total: %s / %s\r\n📅 Expire in: %s\r\n", output += fmt.Sprintf("💡 Active: %t\r\n📧 Email: %s\r\n🔼 Upload↑: %s\r\n🔽 Download↓: %s\r\n🔄 Total: %s / %s\r\n📅 Expire date: %s\r\n \r\n",
traffic.Enable, traffic.Email, common.FormatTraffic(traffic.Up), common.FormatTraffic(traffic.Down), common.FormatTraffic((traffic.Up + traffic.Down)), traffic.Enable, traffic.Email, common.FormatTraffic(traffic.Up), common.FormatTraffic(traffic.Down), common.FormatTraffic((traffic.Up + traffic.Down)),
total, expiryTime) total, expiryTime)
} }
@@ -543,4 +587,10 @@ func (t *Tgbot) sendBackup(chatId int64) {
if err != nil { if err != nil {
logger.Warning("Error in uploading backup: ", err) logger.Warning("Error in uploading backup: ", err)
} }
file = tgbotapi.FilePath(xray.GetConfigPath())
msg = tgbotapi.NewDocument(chatId, file)
_, err = bot.Send(msg)
if err != nil {
logger.Warning("Error in uploading config.json: ", err)
}
} }

View File

@@ -69,8 +69,8 @@
"memory" = "Memory" "memory" = "Memory"
"hard" = "Hard disk" "hard" = "Hard disk"
"xrayStatus" = "xray Status" "xrayStatus" = "xray Status"
"stopXray" = "Stop xray" "stopXray" = "Stop"
"restartXray" = "Restart xray" "restartXray" = "Restart"
"xraySwitch" = "Switch Version" "xraySwitch" = "Switch Version"
"xraySwitchClick" = "Click on the version you want to switch" "xraySwitchClick" = "Click on the version you want to switch"
"xraySwitchClickDesk" = "Please choose carefully, older versions may have incompatible configurations" "xraySwitchClickDesk" = "Please choose carefully, older versions may have incompatible configurations"

View File

@@ -69,13 +69,13 @@
"memory" = "حافظه رم" "memory" = "حافظه رم"
"hard" = "حافظه دیسک" "hard" = "حافظه دیسک"
"xrayStatus" = "وضعیت Xray" "xrayStatus" = "وضعیت Xray"
"stopXray" = "توقف xray" "stopXray" = "توقف"
"restartXray" = "شروع مجدد xray" "restartXray" = "شروع مجدد"
"xraySwitch" = "تغییر ورژن" "xraySwitch" = "تغییر ورژن"
"xraySwitchClick" = "ورژن مورد نظر را انتخاب کنید" "xraySwitchClick" = "ورژن مورد نظر را انتخاب کنید"
"xraySwitchClickDesk" = "لطفا با دقت انتخاب کنید ، در صورت انتخاب اشتباه امکان قطعی سیستم وجود دارد ." "xraySwitchClickDesk" = "لطفا با دقت انتخاب کنید ، در صورت انتخاب اشتباه امکان قطعی سیستم وجود دارد ."
"operationHours" = "ساعت فعال" "operationHours" = "مدت فعالیت"
"operationHoursDesc" = "ساعت فعال بعد از شروع سیستم" "operationHoursDesc" = "مدت فعالیت سیستم بعد از روشن شدن"
"systemLoad" = "بار روی سیستم" "systemLoad" = "بار روی سیستم"
"connectionCount" = "تعداد کانکشن ها" "connectionCount" = "تعداد کانکشن ها"
"connectionCountDesc" = "تعداد کانکشن ها برای کل شبکه" "connectionCountDesc" = "تعداد کانکشن ها برای کل شبکه"

View File

@@ -69,8 +69,8 @@
"memory" = "内存" "memory" = "内存"
"hard" = "硬盘" "hard" = "硬盘"
"xrayStatus" = "xray 状态" "xrayStatus" = "xray 状态"
"stopXray" = "停止 Xray" "stopXray" = "停止"
"restartXray" = "重启 Xray" "restartXray" = "重启"
"xraySwitch" = "切换版本" "xraySwitch" = "切换版本"
"xraySwitchClick" = "点击你想切换的版本" "xraySwitchClick" = "点击你想切换的版本"
"xraySwitchClickDesk" = "请谨慎选择,旧版本可能配置不兼容" "xraySwitchClickDesk" = "请谨慎选择,旧版本可能配置不兼容"

59
x-ui.sh
View File

@@ -20,46 +20,41 @@ function LOGI() {
# check root # check root
[[ $EUID -ne 0 ]] && LOGE "ERROR: You must be root to run this script! \n" && exit 1 [[ $EUID -ne 0 ]] && LOGE "ERROR: You must be root to run this script! \n" && exit 1
# check os # Check OS and set release variable
if [[ -f /etc/redhat-release ]]; then if [[ -f /etc/os-release ]]; then
release="centos" source /etc/os-release
elif cat /etc/issue | grep -Eqi "debian"; then release=$ID
release="debian" elif [[ -f /usr/lib/os-release ]]; then
elif cat /etc/issue | grep -Eqi "ubuntu"; then source /usr/lib/os-release
release="ubuntu" release=$ID
elif cat /etc/issue | grep -Eqi "centos|red hat|redhat"; then
release="centos"
elif cat /proc/version | grep -Eqi "debian"; then
release="debian"
elif cat /proc/version | grep -Eqi "ubuntu"; then
release="ubuntu"
elif cat /proc/version | grep -Eqi "centos|red hat|redhat"; then
release="centos"
else else
LOGE "check system OS failed,please contact with author! \n" && exit 1 echo "Failed to check the system OS, please contact the author!" >&2
exit 1
fi fi
echo "The OS release is: $release"
os_version="" os_version=""
os_version=$(grep -i version_id /etc/os-release | cut -d \" -f2 | cut -d . -f1)
# os version if [[ "${release}" == "centos" ]]; then
if [[ -f /etc/os-release ]]; then if [[ ${os_version} -lt 7 ]]; then
os_version=$(awk -F'[= ."]' '/VERSION_ID/{print $3}' /etc/os-release) echo -e "${red} Please use CentOS 7 or higher ${plain}\n" && exit 1
fi fi
if [[ -z "$os_version" && -f /etc/lsb-release ]]; then elif [[ "${release}" == "ubuntu" ]]; then
os_version=$(awk -F'[= ."]+' '/DISTRIB_RELEASE/{print $2}' /etc/lsb-release) if [[ ${os_version} -lt 18 ]]; then
fi echo -e "${red}please use Ubuntu 18 or higher version${plain}\n" && exit 1
fi
if [[ x"${release}" == x"centos" ]]; then elif [[ "${release}" == "fedora" ]]; then
if [[ ${os_version} -le 6 ]]; then if [[ ${os_version} -lt 36 ]]; then
LOGE "please use CentOS 7 or higher version! \n" && exit 1 echo -e "${red}please use Fedora 36 or higher version${plain}\n" && exit 1
fi fi
elif [[ x"${release}" == x"ubuntu" ]]; then
if [[ ${os_version} -lt 16 ]]; then elif [[ "${release}" == "debian" ]]; then
LOGE "please use Ubuntu 16 or higher version\n" && exit 1
fi
elif [[ x"${release}" == x"debian" ]]; then
if [[ ${os_version} -lt 8 ]]; then if [[ ${os_version} -lt 8 ]]; then
LOGE "please use Debian 8 or higher version\n" && exit 1 echo -e "${red} Please use Debian 8 or higher ${plain}\n" && exit 1
fi fi
fi fi