diff --git a/.gitignore b/.gitignore index 57a67d3b..688b4a47 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ xray-ui/ bin/ release.sh .sync* +xray-ui diff --git a/LICENSE b/LICENSE index 46879517..7997c379 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,21 @@ -MIT License - -Copyright (c) 2022 qist - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +MIT License + +Copyright (c) 2022 qist + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index fd8decf3..ad4b482f 100644 --- a/README.md +++ b/README.md @@ -1,191 +1,191 @@ -本项目基于上游X-UI项目进行略微的功能改动!后续将紧跟上游X-UI版本更新!在此感谢[vaxilu](https://github.com/vaxilu/x-ui)及各位为此项目做出贡献 - ----------------------------------------------------------------------------------------------------------------------------------------------- -### 功能介绍 - -系统状态监控 -支持多协议,网页可视化操作 -支持的协议:vmess、vless、trojan、shadowsocks、dokodemo-door、socks、http -支持配置更多传输配置 -流量统计,限制流量,限制到期时间 -可自定义 xray 配置模板 -支持 https 访问面板(自备域名 + ssl 证书) -更多高级配置项,详见面板 - ----------------------------------------------------------------------------------------------------------------------------------------------- - -本脚本显示功能更加人性化!已解决各种新老系统安装失败问题,并会长期更新,欢迎大家提建议!! - - -更新日志: - -2023.4.23 添加docker镜像 - -```bash -# juestnow/xray-ui:latest 最新版本 - docker run -d --net=host -v/etc/xray-ui:/etc/xray-ui --restart=unless-stopped juestnow/xray-ui:latest -# 查看默认账号密码 - docker run --rm juestnow/xray-ui /root/xray-ui setting -show -``` - -2023.4.20 添加 配置文件下载本地,DB文件下载到本地,更新依赖到最新! - -2023.4.17 添加uTLS REALITY x25519 使用go原生生成公钥私钥 - -2023.4.12 升级依赖模块 sockopt 可以在 REALITY TLS NONE 可用!增加REALITY分享连接shortId随机选择 - -2023.4.11 REALITY 配置 生成 x25519 shortIds等 ! - -2023.4.7 添加 xray-ui x25519 生成REALITY公私钥 ! - -[xray-ui 面板配置 reality](./reality.md) - -2023.3.13 添加reality 支持 ! - -* [reality 配置参考](./media/reality.png) - -2023.3.10 删除旧版XTLS配置以便支持xray1.8.0版本 旧trojan配置请关闭然后打开编辑从新保存即可正常,旧VLESS配置可能需要删除重新创建xray才能启动成功 - -2023.1.7 添加VLESS-TCP-XTLS-Vision 支持 - -2022.10.19 更新xray时不更新geoip.dat geosite.dat . geoip.dat geosite.dat 使用[Loyalsoldier](https://github.com/Loyalsoldier/geoip)提供版本单独更新 - -2022.10.17 更改trojan 可以关闭tls配置可以使用nginx 对外代理 - -------------------------------------------------------------------------------------------------------------------------------------------------- -### 手动安装 - -```bash -# 下载 -wget -N --no-check-certificate -O /usr/local/xray-ui-linux-amd64.tar.gz https://github.com/qist/xray-ui/releases/latest/download/xray-ui-linux-amd64.tar.gz - -# 解压 - cd /usr/local/ - tar -xvf xray-ui-linux-amd64.tar.gz - rm xray-ui-linux-amd64.tar.gz -f - cd xray-ui - chmod +x xray-ui bin/xray-linux-amd64 - cp -f xray-ui.service /etc/systemd/system/ - wget --no-check-certificate -O /usr/bin/xray-ui https://raw.githubusercontent.com/qist/xray-ui/main/xray-ui.sh - chmod +x /usr/bin/xray-ui - systemctl daemon-reload - systemctl enable xray-ui - systemctl start xray-ui - # 设置账号密码: - /usr/local/xray-ui/xray-ui setting -username admin -password admin123 - # 设置端口 - /usr/local/xray-ui/xray-ui setting -port 5432 -``` - -### VPS直接运行一键脚本 - -```bash -bash <(curl -Ls https://raw.githubusercontent.com/qist/xray-ui/main/install.sh) -``` - -#### 编译 - -```bash -git clone https://github.com/qist/xray-ui.git - -cd xray-ui -debian/ubuntu解决方案:sudo apt-get install libc6-dev -redhat/centos解决方案:yum install glibc-static.x86_64 -y 或者 sudo yum install glibc-static -CGO_ENABLED=1 go build -o xray-ui/xray-ui -ldflags '-linkmode "external" -extldflags "-static"' main.go -# 交叉编译 -在centos7中安装,yum install gcc-aarch64-linux-gnu -去https://releases.linaro.org/components/toolchain/binaries/ 找 latest-7 -下载 aarch64-linux-gnu/sysroot-glibc-linaro-2.25-2019.02-aarch64-linux-gnu.tar.xz -自己找个目录, 解压 tar Jxvf sysroot-glibc-linaro-2.25-2019.02-aarch64-linux-gnu.tar.xz -build时,指定 sysroot 的位置。 - -用 CGO_ENABLED=1 GOOS=linux GOARCH=arm64 CC="aarch64-linux-gnu-gcc" CGO_CFLAGS="-g -O2 --sysroot=/..../sysroot-glibc-linaro-2.25-2019.02-aarch64-linux-gnu/" CGO_LDFLAGS="-g -O2 --sysroot=/..../sysroot-glibc-linaro-2.25-2019.02-aarch64-linux-gnu/" go build -v -ldflags "-w -s" -o xray-ui/xray-ui main.go 编译成功。 -debian/ubuntu解决方案 -apt install gcc-aarch64-linux-gnu -CGO_ENABLED=1 GOARCH=arm64 CC="aarch64-linux-gnu-gcc" go build -o xray-ui/xray-ui -ldflags '-linkmode "external" -extldflags "-static"' main.go -``` - --------------------------------------------------------------------------------------------------------------------------------------------------- -### nginx 代理设置 - -```nginx -upstream xray-ui { - least_conn; - server 127.0.0.1:54321 max_fails=3 fail_timeout=30s; - keepalive 1000; -} -server { - listen 80; - server_name xray.test.com; - location / { - proxy_redirect off; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_ssl_session_reuse off; - proxy_ssl_server_name on; - proxy_buffering off; - proxy_connect_timeout 90; - proxy_send_timeout 90; - proxy_read_timeout 90; - proxy_buffer_size 4k; - proxy_buffers 4 32k; - proxy_busy_buffers_size 64k; - proxy_http_version 1.1; - proxy_set_header Accept-Encoding ""; - proxy_pass http://xray-ui; - #proxy_pass_request_headers on; - proxy_set_header Connection "keep-alive"; - proxy_store off; - } - } - # vpn代理nginx 配置参考 -https://github.com/qist/xray/tree/main/xray/nginx -``` - --------------------------------------------------------------------------------------------------------------------------------------------------- -### 关于TG通知(上游内容) - -使用说明:在面板后台设置机器人相关参数 - -Tg机器人Token - -Tg机器人ChatId - -#### Tg机器人周期运行时间,采用crontab语法参考语法: - -30 * * * * * //每一分的第30s进行通知 - -@hourly //每小时通知 - -@daily //每天通知(凌晨零点整) - -@every 8h //每8小时通知 - -@every 30s //每30s通知一次 - -#### TG通知内容: - -节点流量使用 - -面板登录提醒 - -节点到期提醒 - -流量预警提醒 - -#### TG机器人可输入内容: - -/delete port将会删除对应端口的节点 - -/restart 将会重启xray服务,该命令不会重启xray-ui面板自身 - -/status 将会获取当前系统状态 - -/enable port将会开启对应端口的节点 - -/disable port将会关闭对应端口的节点 - -/version 0.1.1.1 xray升级到1.6.0版本 - -/help 获取帮助信息 +本项目基于上游X-UI项目进行略微的功能改动!后续将紧跟上游X-UI版本更新!在此感谢[vaxilu](https://github.com/vaxilu/x-ui)及各位为此项目做出贡献 + +---------------------------------------------------------------------------------------------------------------------------------------------- +### 功能介绍 + +系统状态监控 +支持多协议,网页可视化操作 +支持的协议:vmess、vless、trojan、shadowsocks、dokodemo-door、socks、http +支持配置更多传输配置 +流量统计,限制流量,限制到期时间 +可自定义 xray 配置模板 +支持 https 访问面板(自备域名 + ssl 证书) +更多高级配置项,详见面板 + +---------------------------------------------------------------------------------------------------------------------------------------------- + +本脚本显示功能更加人性化!已解决各种新老系统安装失败问题,并会长期更新,欢迎大家提建议!! + + +更新日志: + +2023.4.23 添加docker镜像 + +```bash +# juestnow/xray-ui:latest 最新版本 + docker run -d --net=host -v/etc/xray-ui:/etc/xray-ui --restart=unless-stopped juestnow/xray-ui:latest +# 查看默认账号密码 + docker run --rm juestnow/xray-ui /root/xray-ui setting -show +``` + +2023.4.20 添加 配置文件下载本地,DB文件下载到本地,更新依赖到最新! + +2023.4.17 添加uTLS REALITY x25519 使用go原生生成公钥私钥 + +2023.4.12 升级依赖模块 sockopt 可以在 REALITY TLS NONE 可用!增加REALITY分享连接shortId随机选择 + +2023.4.11 REALITY 配置 生成 x25519 shortIds等 ! + +2023.4.7 添加 xray-ui x25519 生成REALITY公私钥 ! + +[xray-ui 面板配置 reality](./reality.md) + +2023.3.13 添加reality 支持 ! + +* [reality 配置参考](./media/reality.png) + +2023.3.10 删除旧版XTLS配置以便支持xray1.8.0版本 旧trojan配置请关闭然后打开编辑从新保存即可正常,旧VLESS配置可能需要删除重新创建xray才能启动成功 + +2023.1.7 添加VLESS-TCP-XTLS-Vision 支持 + +2022.10.19 更新xray时不更新geoip.dat geosite.dat . geoip.dat geosite.dat 使用[Loyalsoldier](https://github.com/Loyalsoldier/geoip)提供版本单独更新 + +2022.10.17 更改trojan 可以关闭tls配置可以使用nginx 对外代理 + +------------------------------------------------------------------------------------------------------------------------------------------------- +### 手动安装 + +```bash +# 下载 +wget -N --no-check-certificate -O /usr/local/xray-ui-linux-amd64.tar.gz https://github.com/qist/xray-ui/releases/latest/download/xray-ui-linux-amd64.tar.gz + +# 解压 + cd /usr/local/ + tar -xvf xray-ui-linux-amd64.tar.gz + rm xray-ui-linux-amd64.tar.gz -f + cd xray-ui + chmod +x xray-ui bin/xray-linux-amd64 + cp -f xray-ui.service /etc/systemd/system/ + wget --no-check-certificate -O /usr/bin/xray-ui https://raw.githubusercontent.com/qist/xray-ui/main/xray-ui.sh + chmod +x /usr/bin/xray-ui + systemctl daemon-reload + systemctl enable xray-ui + systemctl start xray-ui + # 设置账号密码: + /usr/local/xray-ui/xray-ui setting -username admin -password admin123 + # 设置端口 + /usr/local/xray-ui/xray-ui setting -port 5432 +``` + +### VPS直接运行一键脚本 + +```bash +bash <(curl -Ls https://raw.githubusercontent.com/qist/xray-ui/main/install.sh) +``` + +#### 编译 + +```bash +git clone https://github.com/qist/xray-ui.git + +cd xray-ui +debian/ubuntu解决方案:sudo apt-get install libc6-dev +redhat/centos解决方案:yum install glibc-static.x86_64 -y 或者 sudo yum install glibc-static +CGO_ENABLED=1 go build -o xray-ui/xray-ui -ldflags '-linkmode "external" -extldflags "-static"' main.go +# 交叉编译 +在centos7中安装,yum install gcc-aarch64-linux-gnu +去https://releases.linaro.org/components/toolchain/binaries/ 找 latest-7 +下载 aarch64-linux-gnu/sysroot-glibc-linaro-2.25-2019.02-aarch64-linux-gnu.tar.xz +自己找个目录, 解压 tar Jxvf sysroot-glibc-linaro-2.25-2019.02-aarch64-linux-gnu.tar.xz +build时,指定 sysroot 的位置。 + +用 CGO_ENABLED=1 GOOS=linux GOARCH=arm64 CC="aarch64-linux-gnu-gcc" CGO_CFLAGS="-g -O2 --sysroot=/..../sysroot-glibc-linaro-2.25-2019.02-aarch64-linux-gnu/" CGO_LDFLAGS="-g -O2 --sysroot=/..../sysroot-glibc-linaro-2.25-2019.02-aarch64-linux-gnu/" go build -v -ldflags "-w -s" -o xray-ui/xray-ui main.go 编译成功。 +debian/ubuntu解决方案 +apt install gcc-aarch64-linux-gnu +CGO_ENABLED=1 GOARCH=arm64 CC="aarch64-linux-gnu-gcc" go build -o xray-ui/xray-ui -ldflags '-linkmode "external" -extldflags "-static"' main.go +``` + +-------------------------------------------------------------------------------------------------------------------------------------------------- +### nginx 代理设置 + +```nginx +upstream xray-ui { + least_conn; + server 127.0.0.1:54321 max_fails=3 fail_timeout=30s; + keepalive 1000; +} +server { + listen 80; + server_name xray.test.com; + location / { + proxy_redirect off; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_ssl_session_reuse off; + proxy_ssl_server_name on; + proxy_buffering off; + proxy_connect_timeout 90; + proxy_send_timeout 90; + proxy_read_timeout 90; + proxy_buffer_size 4k; + proxy_buffers 4 32k; + proxy_busy_buffers_size 64k; + proxy_http_version 1.1; + proxy_set_header Accept-Encoding ""; + proxy_pass http://xray-ui; + #proxy_pass_request_headers on; + proxy_set_header Connection "keep-alive"; + proxy_store off; + } + } + # vpn代理nginx 配置参考 +https://github.com/qist/xray/tree/main/xray/nginx +``` + +-------------------------------------------------------------------------------------------------------------------------------------------------- +### 关于TG通知(上游内容) + +使用说明:在面板后台设置机器人相关参数 + +Tg机器人Token + +Tg机器人ChatId + +#### Tg机器人周期运行时间,采用crontab语法参考语法: + +30 * * * * * //每一分的第30s进行通知 + +@hourly //每小时通知 + +@daily //每天通知(凌晨零点整) + +@every 8h //每8小时通知 + +@every 30s //每30s通知一次 + +#### TG通知内容: + +节点流量使用 + +面板登录提醒 + +节点到期提醒 + +流量预警提醒 + +#### TG机器人可输入内容: + +/delete port将会删除对应端口的节点 + +/restart 将会重启xray服务,该命令不会重启xray-ui面板自身 + +/status 将会获取当前系统状态 + +/enable port将会开启对应端口的节点 + +/disable port将会关闭对应端口的节点 + +/version 0.1.1.1 xray升级到1.6.0版本 + +/help 获取帮助信息 diff --git a/config/config.go b/config/config.go index e756399c..b22f2afa 100644 --- a/config/config.go +++ b/config/config.go @@ -1,65 +1,65 @@ -package config - -import ( - _ "embed" - "fmt" - "os" - "strings" -) - -//go:embed version -var version string - -//go:embed name -var name string - -type LogLevel string - -const ( - Debug LogLevel = "debug" - Info LogLevel = "info" - Warn LogLevel = "warn" - Error LogLevel = "error" -) - -func GetVersion() string { - return strings.TrimSpace(version) -} - -func GetName() string { - return strings.TrimSpace(name) -} - -func GetLogLevel() LogLevel { - if IsDebug() { - return Debug - } - logLevel := os.Getenv("XUI_LOG_LEVEL") - if logLevel == "" { - return Info - } - return LogLevel(logLevel) -} - -func IsDebug() bool { - return os.Getenv("XUI_DEBUG") == "true" -} - -func GetBinFolderPath() string { - binFolderPath := os.Getenv("XUI_BIN_FOLDER") - if binFolderPath == "" { - binFolderPath = "bin" - } - return binFolderPath -} - -func GetDBFolderPath() string { - dbFolderPath := os.Getenv("XUI_DB_FOLDER") - if dbFolderPath == "" { - dbFolderPath = "/etc/xray-ui" - } - return dbFolderPath -} -func GetDBPath() string { - return fmt.Sprintf("/etc/%s/%s.db", GetName(), GetName()) -} +package config + +import ( + _ "embed" + "fmt" + "os" + "strings" +) + +//go:embed version +var version string + +//go:embed name +var name string + +type LogLevel string + +const ( + Debug LogLevel = "debug" + Info LogLevel = "info" + Warn LogLevel = "warn" + Error LogLevel = "error" +) + +func GetVersion() string { + return strings.TrimSpace(version) +} + +func GetName() string { + return strings.TrimSpace(name) +} + +func GetLogLevel() LogLevel { + if IsDebug() { + return Debug + } + logLevel := os.Getenv("XUI_LOG_LEVEL") + if logLevel == "" { + return Info + } + return LogLevel(logLevel) +} + +func IsDebug() bool { + return os.Getenv("XUI_DEBUG") == "true" +} + +func GetBinFolderPath() string { + binFolderPath := os.Getenv("XUI_BIN_FOLDER") + if binFolderPath == "" { + binFolderPath = "bin" + } + return binFolderPath +} + +func GetDBFolderPath() string { + dbFolderPath := os.Getenv("XUI_DB_FOLDER") + if dbFolderPath == "" { + dbFolderPath = "/etc/xray-ui" + } + return dbFolderPath +} +func GetDBPath() string { + return fmt.Sprintf("/etc/%s/%s.db", GetName(), GetName()) +} diff --git a/database/model/model.go b/database/model/model.go index d7d68181..b5af36a1 100644 --- a/database/model/model.go +++ b/database/model/model.go @@ -1,66 +1,66 @@ -package model - -import ( - "fmt" - "xray-ui/util/json_util" - "xray-ui/xray" -) - -type Protocol string - -const ( - VMess Protocol = "vmess" - VLESS Protocol = "vless" - Dokodemo Protocol = "Dokodemo-door" - Http Protocol = "http" - Trojan Protocol = "trojan" - Shadowsocks Protocol = "shadowsocks" -) - -type User struct { - Id int `json:"id" gorm:"primaryKey;autoIncrement"` - Username string `json:"username"` - Password string `json:"password"` -} - -type Inbound struct { - Id int `json:"id" form:"id" gorm:"primaryKey;autoIncrement"` - UserId int `json:"-"` - Up int64 `json:"up" form:"up"` - Down int64 `json:"down" form:"down"` - Total int64 `json:"total" form:"total"` - Remark string `json:"remark" form:"remark"` - Enable bool `json:"enable" form:"enable"` - ExpiryTime int64 `json:"expiryTime" form:"expiryTime"` - - // config part - Listen string `json:"listen" form:"listen"` - Port int `json:"port" form:"port" gorm:"unique"` - Protocol Protocol `json:"protocol" form:"protocol"` - Settings string `json:"settings" form:"settings"` - StreamSettings string `json:"streamSettings" form:"streamSettings"` - Tag string `json:"tag" form:"tag" gorm:"unique"` - Sniffing string `json:"sniffing" form:"sniffing"` -} - -func (i *Inbound) GenXrayInboundConfig() *xray.InboundConfig { - listen := i.Listen - if listen != "" { - listen = fmt.Sprintf("\"%v\"", listen) - } - return &xray.InboundConfig{ - Listen: json_util.RawMessage(listen), - Port: i.Port, - Protocol: string(i.Protocol), - Settings: json_util.RawMessage(i.Settings), - StreamSettings: json_util.RawMessage(i.StreamSettings), - Tag: i.Tag, - Sniffing: json_util.RawMessage(i.Sniffing), - } -} - -type Setting struct { - Id int `json:"id" form:"id" gorm:"primaryKey;autoIncrement"` - Key string `json:"key" form:"key"` - Value string `json:"value" form:"value"` -} +package model + +import ( + "fmt" + "xray-ui/util/json_util" + "xray-ui/xray" +) + +type Protocol string + +const ( + VMess Protocol = "vmess" + VLESS Protocol = "vless" + Dokodemo Protocol = "Dokodemo-door" + Http Protocol = "http" + Trojan Protocol = "trojan" + Shadowsocks Protocol = "shadowsocks" +) + +type User struct { + Id int `json:"id" gorm:"primaryKey;autoIncrement"` + Username string `json:"username"` + Password string `json:"password"` +} + +type Inbound struct { + Id int `json:"id" form:"id" gorm:"primaryKey;autoIncrement"` + UserId int `json:"-"` + Up int64 `json:"up" form:"up"` + Down int64 `json:"down" form:"down"` + Total int64 `json:"total" form:"total"` + Remark string `json:"remark" form:"remark"` + Enable bool `json:"enable" form:"enable"` + ExpiryTime int64 `json:"expiryTime" form:"expiryTime"` + + // config part + Listen string `json:"listen" form:"listen"` + Port int `json:"port" form:"port" gorm:"unique"` + Protocol Protocol `json:"protocol" form:"protocol"` + Settings string `json:"settings" form:"settings"` + StreamSettings string `json:"streamSettings" form:"streamSettings"` + Tag string `json:"tag" form:"tag" gorm:"unique"` + Sniffing string `json:"sniffing" form:"sniffing"` +} + +func (i *Inbound) GenXrayInboundConfig() *xray.InboundConfig { + listen := i.Listen + if listen != "" { + listen = fmt.Sprintf("\"%v\"", listen) + } + return &xray.InboundConfig{ + Listen: json_util.RawMessage(listen), + Port: i.Port, + Protocol: string(i.Protocol), + Settings: json_util.RawMessage(i.Settings), + StreamSettings: json_util.RawMessage(i.StreamSettings), + Tag: i.Tag, + Sniffing: json_util.RawMessage(i.Sniffing), + } +} + +type Setting struct { + Id int `json:"id" form:"id" gorm:"primaryKey;autoIncrement"` + Key string `json:"key" form:"key"` + Value string `json:"value" form:"value"` +} diff --git a/logger/logger.go b/logger/logger.go index ecdfc25a..fda8e720 100644 --- a/logger/logger.go +++ b/logger/logger.go @@ -1,58 +1,58 @@ -package logger - -import ( - "github.com/op/go-logging" - "os" -) - -var logger *logging.Logger - -func init() { - InitLogger(logging.INFO) -} - -func InitLogger(level logging.Level) { - format := logging.MustStringFormatter( - `%{time:2006/01/02 15:04:05} %{level} - %{message}`, - ) - newLogger := logging.MustGetLogger("xray-ui") - backend := logging.NewLogBackend(os.Stderr, "", 0) - backendFormatter := logging.NewBackendFormatter(backend, format) - backendLeveled := logging.AddModuleLevel(backendFormatter) - backendLeveled.SetLevel(level, "") - newLogger.SetBackend(backendLeveled) - - logger = newLogger -} - -func Debug(args ...interface{}) { - logger.Debug(args...) -} - -func Debugf(format string, args ...interface{}) { - logger.Debugf(format, args...) -} - -func Info(args ...interface{}) { - logger.Info(args...) -} - -func Infof(format string, args ...interface{}) { - logger.Infof(format, args...) -} - -func Warning(args ...interface{}) { - logger.Warning(args...) -} - -func Warningf(format string, args ...interface{}) { - logger.Warningf(format, args...) -} - -func Error(args ...interface{}) { - logger.Error(args...) -} - -func Errorf(format string, args ...interface{}) { - logger.Errorf(format, args...) -} +package logger + +import ( + "github.com/op/go-logging" + "os" +) + +var logger *logging.Logger + +func init() { + InitLogger(logging.INFO) +} + +func InitLogger(level logging.Level) { + format := logging.MustStringFormatter( + `%{time:2006/01/02 15:04:05} %{level} - %{message}`, + ) + newLogger := logging.MustGetLogger("xray-ui") + backend := logging.NewLogBackend(os.Stderr, "", 0) + backendFormatter := logging.NewBackendFormatter(backend, format) + backendLeveled := logging.AddModuleLevel(backendFormatter) + backendLeveled.SetLevel(level, "") + newLogger.SetBackend(backendLeveled) + + logger = newLogger +} + +func Debug(args ...interface{}) { + logger.Debug(args...) +} + +func Debugf(format string, args ...interface{}) { + logger.Debugf(format, args...) +} + +func Info(args ...interface{}) { + logger.Info(args...) +} + +func Infof(format string, args ...interface{}) { + logger.Infof(format, args...) +} + +func Warning(args ...interface{}) { + logger.Warning(args...) +} + +func Warningf(format string, args ...interface{}) { + logger.Warningf(format, args...) +} + +func Error(args ...interface{}) { + logger.Error(args...) +} + +func Errorf(format string, args ...interface{}) { + logger.Errorf(format, args...) +} diff --git a/main.go b/main.go index ab9619e3..8b661019 100644 --- a/main.go +++ b/main.go @@ -1,312 +1,312 @@ -package main - -import ( - "flag" - "fmt" - "log" - "os" - "os/signal" - "syscall" - _ "unsafe" - "xray-ui/config" - "xray-ui/database" - "xray-ui/logger" - "xray-ui/v2ui" - "xray-ui/web" - "xray-ui/web/global" - "xray-ui/web/service" - - "github.com/op/go-logging" -) - -func runWebServer() { - log.Printf("%v %v", config.GetName(), config.GetVersion()) - - switch config.GetLogLevel() { - case config.Debug: - logger.InitLogger(logging.DEBUG) - case config.Info: - logger.InitLogger(logging.INFO) - case config.Warn: - logger.InitLogger(logging.WARNING) - case config.Error: - logger.InitLogger(logging.ERROR) - default: - log.Fatal("unknown log level:", config.GetLogLevel()) - } - - err := database.InitDB(config.GetDBPath()) - if err != nil { - log.Fatal(err) - } - - var server *web.Server - - server = web.NewServer() - global.SetWebServer(server) - err = server.Start() - if err != nil { - log.Println(err) - return - } - - sigCh := make(chan os.Signal, 1) - //信号量捕获处理 - signal.Notify(sigCh, syscall.SIGHUP, syscall.SIGTERM, syscall.SIGKILL) - for { - sig := <-sigCh - - switch sig { - case syscall.SIGHUP: - err := server.Stop() - if err != nil { - logger.Warning("stop server err:", err) - } - server = web.NewServer() - global.SetWebServer(server) - err = server.Start() - if err != nil { - log.Println(err) - return - } - default: - server.Stop() - return - } - } -} - -func resetSetting() { - err := database.InitDB(config.GetDBPath()) - if err != nil { - fmt.Println(err) - return - } - - settingService := service.SettingService{} - err = settingService.ResetSettings() - if err != nil { - fmt.Println("reset setting failed:", err) - } else { - fmt.Println("reset setting success") - } -} - -func showSetting(show bool) { - if show { - settingService := service.SettingService{} - port, err := settingService.GetPort() - if err != nil { - fmt.Println("get current port fialed,error info:", err) - } - userService := service.UserService{} - userModel, err := userService.GetFirstUser() - if err != nil { - fmt.Println("get current user info failed,error info:", err) - } - username := userModel.Username - userpasswd := userModel.Password - if (username == "") || (userpasswd == "") { - fmt.Println("current username or password is empty") - } - fmt.Println("当前面板信息设置如下:") - fmt.Println("登录用户名:", username) - fmt.Println("登录密码:", userpasswd) - fmt.Println("登录端口:", port) - } -} - -func updateTgbotEnableSts(status bool) { - settingService := service.SettingService{} - currentTgSts, err := settingService.GetTgbotenabled() - if err != nil { - fmt.Println(err) - return - } - logger.Infof("current enabletgbot status[%v],need update to status[%v]", currentTgSts, status) - if currentTgSts != status { - err := settingService.SetTgbotenabled(status) - if err != nil { - fmt.Println(err) - return - } else { - logger.Infof("SetTgbotenabled[%v] success", status) - } - } - return -} - -func updateTgbotSetting(tgBotToken string, tgBotChatid int, tgBotRuntime string) { - err := database.InitDB(config.GetDBPath()) - if err != nil { - fmt.Println(err) - return - } - - settingService := service.SettingService{} - - if tgBotToken != "" { - err := settingService.SetTgBotToken(tgBotToken) - if err != nil { - fmt.Println(err) - return - } else { - logger.Info("updateTgbotSetting tgBotToken success") - } - } - - if tgBotRuntime != "" { - err := settingService.SetTgbotRuntime(tgBotRuntime) - if err != nil { - fmt.Println(err) - return - } else { - logger.Infof("updateTgbotSetting tgBotRuntime[%s] success", tgBotRuntime) - } - } - - if tgBotChatid != 0 { - err := settingService.SetTgBotChatId(tgBotChatid) - if err != nil { - fmt.Println(err) - return - } else { - logger.Info("updateTgbotSetting tgBotChatid success") - } - } -} - -func updateSetting(port int, username string, password string, listen string) { - err := database.InitDB(config.GetDBPath()) - if err != nil { - fmt.Println(err) - return - } - - settingService := service.SettingService{} - - if port > 0 { - err := settingService.SetPort(port) - if err != nil { - fmt.Println("set port failed:", err) - } else { - fmt.Printf("set port %v success", port) - } - } - if username != "" || password != "" { - userService := service.UserService{} - err := userService.UpdateFirstUser(username, password) - if err != nil { - fmt.Println("set username and password failed:", err) - } else { - fmt.Println("set username and password success") - } - } - if listen != "" { - err := settingService.SetListen(listen) - if err != nil { - fmt.Println("set listen failed:", err) - } else { - fmt.Printf("set listen %v success", listen) - } - } -} - -func main() { - if len(os.Args) < 2 { - runWebServer() - return - } - - var showVersion bool - flag.BoolVar(&showVersion, "v", false, "show version") - - runCmd := flag.NewFlagSet("run", flag.ExitOnError) - - v2uiCmd := flag.NewFlagSet("v2-ui", flag.ExitOnError) - var dbPath string - v2uiCmd.StringVar(&dbPath, "db", "/etc/v2-ui/v2-ui.db", "set v2-ui db file path") - - settingCmd := flag.NewFlagSet("setting", flag.ExitOnError) - var port int - var listen string - var username string - var password string - var tgbottoken string - var tgbotchatid int - var enabletgbot bool - var tgbotRuntime string - var reset bool - var show bool - settingCmd.BoolVar(&reset, "reset", false, "reset all settings") - settingCmd.BoolVar(&show, "show", false, "show current settings") - settingCmd.IntVar(&port, "port", 0, "set panel port") - settingCmd.StringVar(&listen, "listen", "", "set panel listen") - settingCmd.StringVar(&username, "username", "", "set login username") - settingCmd.StringVar(&password, "password", "", "set login password") - settingCmd.StringVar(&tgbottoken, "tgbottoken", "", "set telegrame bot token") - settingCmd.StringVar(&tgbotRuntime, "tgbotRuntime", "", "set telegrame bot cron time") - settingCmd.IntVar(&tgbotchatid, "tgbotchatid", 0, "set telegrame bot chat id") - settingCmd.BoolVar(&enabletgbot, "enabletgbot", false, "enable telegram bot notify") - - oldUsage := flag.Usage - flag.Usage = func() { - oldUsage() - fmt.Println() - fmt.Println("Commands:") - fmt.Println(" run run web panel") - fmt.Println(" v2-ui migrate form v2-ui") - fmt.Println(" setting set settings") - } - - flag.Parse() - if showVersion { - fmt.Println(config.GetVersion()) - return - } - - switch os.Args[1] { - case "run": - err := runCmd.Parse(os.Args[2:]) - if err != nil { - fmt.Println(err) - return - } - runWebServer() - case "v2-ui": - err := v2uiCmd.Parse(os.Args[2:]) - if err != nil { - fmt.Println(err) - return - } - err = v2ui.MigrateFromV2UI(dbPath) - if err != nil { - fmt.Println("migrate from v2-ui failed:", err) - } - case "setting": - err := settingCmd.Parse(os.Args[2:]) - if err != nil { - fmt.Println(err) - return - } - if reset { - resetSetting() - } else { - updateSetting(port, username, password, listen) - } - if show { - showSetting(show) - } - if (tgbottoken != "") || (tgbotchatid != 0) || (tgbotRuntime != "") { - updateTgbotSetting(tgbottoken, tgbotchatid, tgbotRuntime) - } - default: - fmt.Println("except 'run' or 'v2-ui' or 'setting' subcommands") - fmt.Println() - runCmd.Usage() - fmt.Println() - v2uiCmd.Usage() - fmt.Println() - settingCmd.Usage() - } -} +package main + +import ( + "flag" + "fmt" + "log" + "os" + "os/signal" + "syscall" + _ "unsafe" + "xray-ui/config" + "xray-ui/database" + "xray-ui/logger" + "xray-ui/v2ui" + "xray-ui/web" + "xray-ui/web/global" + "xray-ui/web/service" + + "github.com/op/go-logging" +) + +func runWebServer() { + log.Printf("%v %v", config.GetName(), config.GetVersion()) + + switch config.GetLogLevel() { + case config.Debug: + logger.InitLogger(logging.DEBUG) + case config.Info: + logger.InitLogger(logging.INFO) + case config.Warn: + logger.InitLogger(logging.WARNING) + case config.Error: + logger.InitLogger(logging.ERROR) + default: + log.Fatal("unknown log level:", config.GetLogLevel()) + } + + err := database.InitDB(config.GetDBPath()) + if err != nil { + log.Fatal(err) + } + + var server *web.Server + + server = web.NewServer() + global.SetWebServer(server) + err = server.Start() + if err != nil { + log.Println(err) + return + } + + sigCh := make(chan os.Signal, 1) + //信号量捕获处理 + signal.Notify(sigCh, syscall.SIGHUP, syscall.SIGTERM, syscall.SIGKILL) + for { + sig := <-sigCh + + switch sig { + case syscall.SIGHUP: + err := server.Stop() + if err != nil { + logger.Warning("stop server err:", err) + } + server = web.NewServer() + global.SetWebServer(server) + err = server.Start() + if err != nil { + log.Println(err) + return + } + default: + server.Stop() + return + } + } +} + +func resetSetting() { + err := database.InitDB(config.GetDBPath()) + if err != nil { + fmt.Println(err) + return + } + + settingService := service.SettingService{} + err = settingService.ResetSettings() + if err != nil { + fmt.Println("reset setting failed:", err) + } else { + fmt.Println("reset setting success") + } +} + +func showSetting(show bool) { + if show { + settingService := service.SettingService{} + port, err := settingService.GetPort() + if err != nil { + fmt.Println("get current port fialed,error info:", err) + } + userService := service.UserService{} + userModel, err := userService.GetFirstUser() + if err != nil { + fmt.Println("get current user info failed,error info:", err) + } + username := userModel.Username + userpasswd := userModel.Password + if (username == "") || (userpasswd == "") { + fmt.Println("current username or password is empty") + } + fmt.Println("当前面板信息设置如下:") + fmt.Println("登录用户名:", username) + fmt.Println("登录密码:", userpasswd) + fmt.Println("登录端口:", port) + } +} + +func updateTgbotEnableSts(status bool) { + settingService := service.SettingService{} + currentTgSts, err := settingService.GetTgbotenabled() + if err != nil { + fmt.Println(err) + return + } + logger.Infof("current enabletgbot status[%v],need update to status[%v]", currentTgSts, status) + if currentTgSts != status { + err := settingService.SetTgbotenabled(status) + if err != nil { + fmt.Println(err) + return + } else { + logger.Infof("SetTgbotenabled[%v] success", status) + } + } + return +} + +func updateTgbotSetting(tgBotToken string, tgBotChatid int, tgBotRuntime string) { + err := database.InitDB(config.GetDBPath()) + if err != nil { + fmt.Println(err) + return + } + + settingService := service.SettingService{} + + if tgBotToken != "" { + err := settingService.SetTgBotToken(tgBotToken) + if err != nil { + fmt.Println(err) + return + } else { + logger.Info("updateTgbotSetting tgBotToken success") + } + } + + if tgBotRuntime != "" { + err := settingService.SetTgbotRuntime(tgBotRuntime) + if err != nil { + fmt.Println(err) + return + } else { + logger.Infof("updateTgbotSetting tgBotRuntime[%s] success", tgBotRuntime) + } + } + + if tgBotChatid != 0 { + err := settingService.SetTgBotChatId(tgBotChatid) + if err != nil { + fmt.Println(err) + return + } else { + logger.Info("updateTgbotSetting tgBotChatid success") + } + } +} + +func updateSetting(port int, username string, password string, listen string) { + err := database.InitDB(config.GetDBPath()) + if err != nil { + fmt.Println(err) + return + } + + settingService := service.SettingService{} + + if port > 0 { + err := settingService.SetPort(port) + if err != nil { + fmt.Println("set port failed:", err) + } else { + fmt.Printf("set port %v success", port) + } + } + if username != "" || password != "" { + userService := service.UserService{} + err := userService.UpdateFirstUser(username, password) + if err != nil { + fmt.Println("set username and password failed:", err) + } else { + fmt.Println("set username and password success") + } + } + if listen != "" { + err := settingService.SetListen(listen) + if err != nil { + fmt.Println("set listen failed:", err) + } else { + fmt.Printf("set listen %v success", listen) + } + } +} + +func main() { + if len(os.Args) < 2 { + runWebServer() + return + } + + var showVersion bool + flag.BoolVar(&showVersion, "v", false, "show version") + + runCmd := flag.NewFlagSet("run", flag.ExitOnError) + + v2uiCmd := flag.NewFlagSet("v2-ui", flag.ExitOnError) + var dbPath string + v2uiCmd.StringVar(&dbPath, "db", "/etc/v2-ui/v2-ui.db", "set v2-ui db file path") + + settingCmd := flag.NewFlagSet("setting", flag.ExitOnError) + var port int + var listen string + var username string + var password string + var tgbottoken string + var tgbotchatid int + var enabletgbot bool + var tgbotRuntime string + var reset bool + var show bool + settingCmd.BoolVar(&reset, "reset", false, "reset all settings") + settingCmd.BoolVar(&show, "show", false, "show current settings") + settingCmd.IntVar(&port, "port", 0, "set panel port") + settingCmd.StringVar(&listen, "listen", "", "set panel listen") + settingCmd.StringVar(&username, "username", "", "set login username") + settingCmd.StringVar(&password, "password", "", "set login password") + settingCmd.StringVar(&tgbottoken, "tgbottoken", "", "set telegrame bot token") + settingCmd.StringVar(&tgbotRuntime, "tgbotRuntime", "", "set telegrame bot cron time") + settingCmd.IntVar(&tgbotchatid, "tgbotchatid", 0, "set telegrame bot chat id") + settingCmd.BoolVar(&enabletgbot, "enabletgbot", false, "enable telegram bot notify") + + oldUsage := flag.Usage + flag.Usage = func() { + oldUsage() + fmt.Println() + fmt.Println("Commands:") + fmt.Println(" run run web panel") + fmt.Println(" v2-ui migrate form v2-ui") + fmt.Println(" setting set settings") + } + + flag.Parse() + if showVersion { + fmt.Println(config.GetVersion()) + return + } + + switch os.Args[1] { + case "run": + err := runCmd.Parse(os.Args[2:]) + if err != nil { + fmt.Println(err) + return + } + runWebServer() + case "v2-ui": + err := v2uiCmd.Parse(os.Args[2:]) + if err != nil { + fmt.Println(err) + return + } + err = v2ui.MigrateFromV2UI(dbPath) + if err != nil { + fmt.Println("migrate from v2-ui failed:", err) + } + case "setting": + err := settingCmd.Parse(os.Args[2:]) + if err != nil { + fmt.Println(err) + return + } + if reset { + resetSetting() + } else { + updateSetting(port, username, password, listen) + } + if show { + showSetting(show) + } + if (tgbottoken != "") || (tgbotchatid != 0) || (tgbotRuntime != "") { + updateTgbotSetting(tgbottoken, tgbotchatid, tgbotRuntime) + } + default: + fmt.Println("except 'run' or 'v2-ui' or 'setting' subcommands") + fmt.Println() + runCmd.Usage() + fmt.Println() + v2uiCmd.Usage() + fmt.Println() + settingCmd.Usage() + } +} diff --git a/media/config.json b/media/config.json index 6039dd41..d492c765 100644 --- a/media/config.json +++ b/media/config.json @@ -25,11 +25,6 @@ }, { "protocol": "blackhole", - "settings": { - "response": { - "type": "http" - } - }, "tag": "blocked" } ], diff --git a/util/common/err.go b/util/common/err.go index 9e13b96b..803dc584 100644 --- a/util/common/err.go +++ b/util/common/err.go @@ -1,29 +1,29 @@ -package common - -import ( - "errors" - "fmt" - "xray-ui/logger" -) - -var CtxDone = errors.New("context done") - -func NewErrorf(format string, a ...interface{}) error { - msg := fmt.Sprintf(format, a...) - return errors.New(msg) -} - -func NewError(a ...interface{}) error { - msg := fmt.Sprintln(a...) - return errors.New(msg) -} - -func Recover(msg string) interface{} { - panicErr := recover() - if panicErr != nil { - if msg != "" { - logger.Error(msg, "panic:", panicErr) - } - } - return panicErr -} +package common + +import ( + "errors" + "fmt" + "xray-ui/logger" +) + +var CtxDone = errors.New("context done") + +func NewErrorf(format string, a ...interface{}) error { + msg := fmt.Sprintf(format, a...) + return errors.New(msg) +} + +func NewError(a ...interface{}) error { + msg := fmt.Sprintln(a...) + return errors.New(msg) +} + +func Recover(msg string) interface{} { + panicErr := recover() + if panicErr != nil { + if msg != "" { + logger.Error(msg, "panic:", panicErr) + } + } + return panicErr +} diff --git a/util/common/format.go b/util/common/format.go index 2b4378f7..c0714c5c 100644 --- a/util/common/format.go +++ b/util/common/format.go @@ -1,33 +1,33 @@ -package common - -import ( - "fmt" -) - -func FormatTraffic(trafficBytes int64) (size string) { - if trafficBytes < 1024 { - return fmt.Sprintf("%.2fB", float64(trafficBytes)/float64(1)) - } else if trafficBytes < (1024 * 1024) { - return fmt.Sprintf("%.2fKB", float64(trafficBytes)/float64(1024)) - } else if trafficBytes < (1024 * 1024 * 1024) { - return fmt.Sprintf("%.2fMB", float64(trafficBytes)/float64(1024*1024)) - } else if trafficBytes < (1024 * 1024 * 1024 * 1024) { - return fmt.Sprintf("%.2fGB", float64(trafficBytes)/float64(1024*1024*1024)) - } else if trafficBytes < (1024 * 1024 * 1024 * 1024 * 1024) { - return fmt.Sprintf("%.2fTB", float64(trafficBytes)/float64(1024*1024*1024*1024)) - } else { - return fmt.Sprintf("%.2fEB", float64(trafficBytes)/float64(1024*1024*1024*1024*1024)) - } -} - -func FormatTime(timeseconds uint64) (timeStr string) { - if timeseconds < 60 { - return fmt.Sprintf("%d seconds", timeseconds) - } else if timeseconds < 60*60 { - return fmt.Sprintf("%d minutes", timeseconds/(60)) - } else if timeseconds < 60*60*24 { - return fmt.Sprintf("%d hours", timeseconds/(60*60)) - } else { - return fmt.Sprintf("%d days", timeseconds/(60*60*24)) - } -} +package common + +import ( + "fmt" +) + +func FormatTraffic(trafficBytes int64) (size string) { + if trafficBytes < 1024 { + return fmt.Sprintf("%.2fB", float64(trafficBytes)/float64(1)) + } else if trafficBytes < (1024 * 1024) { + return fmt.Sprintf("%.2fKB", float64(trafficBytes)/float64(1024)) + } else if trafficBytes < (1024 * 1024 * 1024) { + return fmt.Sprintf("%.2fMB", float64(trafficBytes)/float64(1024*1024)) + } else if trafficBytes < (1024 * 1024 * 1024 * 1024) { + return fmt.Sprintf("%.2fGB", float64(trafficBytes)/float64(1024*1024*1024)) + } else if trafficBytes < (1024 * 1024 * 1024 * 1024 * 1024) { + return fmt.Sprintf("%.2fTB", float64(trafficBytes)/float64(1024*1024*1024*1024)) + } else { + return fmt.Sprintf("%.2fEB", float64(trafficBytes)/float64(1024*1024*1024*1024*1024)) + } +} + +func FormatTime(timeseconds uint64) (timeStr string) { + if timeseconds < 60 { + return fmt.Sprintf("%d seconds", timeseconds) + } else if timeseconds < 60*60 { + return fmt.Sprintf("%d minutes", timeseconds/(60)) + } else if timeseconds < 60*60*24 { + return fmt.Sprintf("%d hours", timeseconds/(60*60)) + } else { + return fmt.Sprintf("%d days", timeseconds/(60*60*24)) + } +} diff --git a/util/common/multi_error.go b/util/common/multi_error.go index a1efce55..ff9ff628 100644 --- a/util/common/multi_error.go +++ b/util/common/multi_error.go @@ -1,30 +1,30 @@ -package common - -import ( - "strings" -) - -type multiError []error - -func (e multiError) Error() string { - var r strings.Builder - r.WriteString("multierr: ") - for _, err := range e { - r.WriteString(err.Error()) - r.WriteString(" | ") - } - return r.String() -} - -func Combine(maybeError ...error) error { - var errs multiError - for _, err := range maybeError { - if err != nil { - errs = append(errs, err) - } - } - if len(errs) == 0 { - return nil - } - return errs -} +package common + +import ( + "strings" +) + +type multiError []error + +func (e multiError) Error() string { + var r strings.Builder + r.WriteString("multierr: ") + for _, err := range e { + r.WriteString(err.Error()) + r.WriteString(" | ") + } + return r.String() +} + +func Combine(maybeError ...error) error { + var errs multiError + for _, err := range maybeError { + if err != nil { + errs = append(errs, err) + } + } + if len(errs) == 0 { + return nil + } + return errs +} diff --git a/util/common/network.go b/util/common/network.go index 1f5979f6..96836e0d 100644 --- a/util/common/network.go +++ b/util/common/network.go @@ -1,16 +1,16 @@ -package common - -import ( - "io/ioutil" - "net/http" -) - -func GetMyIpAddr() string { - resp, err := http.Get("https://api64.ipify.org") - if err != nil { - resp, _ = http.Get("http://ip.cip.cc") - } - defer resp.Body.Close() - s, _ := ioutil.ReadAll(resp.Body) - return string(s) -} +package common + +import ( + "io/ioutil" + "net/http" +) + +func GetMyIpAddr() string { + resp, err := http.Get("https://api64.ipify.org") + if err != nil { + resp, _ = http.Get("http://ip.cip.cc") + } + defer resp.Body.Close() + s, _ := ioutil.ReadAll(resp.Body) + return string(s) +} diff --git a/util/common/stringUtil.go b/util/common/stringUtil.go index 4a515f05..0927b07d 100644 --- a/util/common/stringUtil.go +++ b/util/common/stringUtil.go @@ -1,29 +1,29 @@ -package common - -import ( - "bytes" - "sort" -) - -func IsSubString(target string, str_array []string) bool { - sort.Strings(str_array) - index := sort.SearchStrings(str_array, target) - return index < len(str_array) && str_array[index] == target -} - -func ByteToString(p []byte) string { - for i := 0; i < len(p); i++ { - if p[i] == '\n' { - return string(p[0:i]) - } - } - return string(p) -} - -/* -* if some byte slice have the '\n' we need clear these special characters -* to get a standard string - */ -func ByteToStringWithOutNewLine(p []byte) string { - return string(bytes.Replace(p, []byte("\n"), []byte(""), 1)) -} +package common + +import ( + "bytes" + "sort" +) + +func IsSubString(target string, str_array []string) bool { + sort.Strings(str_array) + index := sort.SearchStrings(str_array, target) + return index < len(str_array) && str_array[index] == target +} + +func ByteToString(p []byte) string { + for i := 0; i < len(p); i++ { + if p[i] == '\n' { + return string(p[0:i]) + } + } + return string(p) +} + +/* +* if some byte slice have the '\n' we need clear these special characters +* to get a standard string + */ +func ByteToStringWithOutNewLine(p []byte) string { + return string(bytes.Replace(p, []byte("\n"), []byte(""), 1)) +} diff --git a/util/context.go b/util/context.go index a2662840..b768f05c 100644 --- a/util/context.go +++ b/util/context.go @@ -1,12 +1,12 @@ -package util - -import "context" - -func IsDone(ctx context.Context) bool { - select { - case <-ctx.Done(): - return true - default: - return false - } -} +package util + +import "context" + +func IsDone(ctx context.Context) bool { + select { + case <-ctx.Done(): + return true + default: + return false + } +} diff --git a/util/json_util/json.go b/util/json_util/json.go index 8d418b2e..65ad789e 100644 --- a/util/json_util/json.go +++ b/util/json_util/json.go @@ -1,24 +1,24 @@ -package json_util - -import ( - "errors" -) - -type RawMessage []byte - -// MarshalJSON 自定义 json.RawMessage 默认行为 -func (m RawMessage) MarshalJSON() ([]byte, error) { - if len(m) == 0 { - return []byte("null"), nil - } - return m, nil -} - -// UnmarshalJSON sets *m to a copy of data. -func (m *RawMessage) UnmarshalJSON(data []byte) error { - if m == nil { - return errors.New("json.RawMessage: UnmarshalJSON on nil pointer") - } - *m = append((*m)[0:0], data...) - return nil -} +package json_util + +import ( + "errors" +) + +type RawMessage []byte + +// MarshalJSON 自定义 json.RawMessage 默认行为 +func (m RawMessage) MarshalJSON() ([]byte, error) { + if len(m) == 0 { + return []byte("null"), nil + } + return m, nil +} + +// UnmarshalJSON sets *m to a copy of data. +func (m *RawMessage) UnmarshalJSON(data []byte) error { + if m == nil { + return errors.New("json.RawMessage: UnmarshalJSON on nil pointer") + } + *m = append((*m)[0:0], data...) + return nil +} diff --git a/util/random/random.go b/util/random/random.go index b5fbbfb4..b1dd2e09 100644 --- a/util/random/random.go +++ b/util/random/random.go @@ -1,43 +1,43 @@ -package random - -import ( - "math/rand" - "time" -) - -var numSeq [10]rune -var lowerSeq [26]rune -var upperSeq [26]rune -var numLowerSeq [36]rune -var numUpperSeq [36]rune -var allSeq [62]rune - -func init() { - rand.Seed(time.Now().UnixNano()) - - for i := 0; i < 10; i++ { - numSeq[i] = rune('0' + i) - } - for i := 0; i < 26; i++ { - lowerSeq[i] = rune('a' + i) - upperSeq[i] = rune('A' + i) - } - - copy(numLowerSeq[:], numSeq[:]) - copy(numLowerSeq[len(numSeq):], lowerSeq[:]) - - copy(numUpperSeq[:], numSeq[:]) - copy(numUpperSeq[len(numSeq):], upperSeq[:]) - - copy(allSeq[:], numSeq[:]) - copy(allSeq[len(numSeq):], lowerSeq[:]) - copy(allSeq[len(numSeq)+len(lowerSeq):], upperSeq[:]) -} - -func Seq(n int) string { - runes := make([]rune, n) - for i := 0; i < n; i++ { - runes[i] = allSeq[rand.Intn(len(allSeq))] - } - return string(runes) -} +package random + +import ( + "math/rand" + "time" +) + +var numSeq [10]rune +var lowerSeq [26]rune +var upperSeq [26]rune +var numLowerSeq [36]rune +var numUpperSeq [36]rune +var allSeq [62]rune + +func init() { + rand.Seed(time.Now().UnixNano()) + + for i := 0; i < 10; i++ { + numSeq[i] = rune('0' + i) + } + for i := 0; i < 26; i++ { + lowerSeq[i] = rune('a' + i) + upperSeq[i] = rune('A' + i) + } + + copy(numLowerSeq[:], numSeq[:]) + copy(numLowerSeq[len(numSeq):], lowerSeq[:]) + + copy(numUpperSeq[:], numSeq[:]) + copy(numUpperSeq[len(numSeq):], upperSeq[:]) + + copy(allSeq[:], numSeq[:]) + copy(allSeq[len(numSeq):], lowerSeq[:]) + copy(allSeq[len(numSeq)+len(lowerSeq):], upperSeq[:]) +} + +func Seq(n int) string { + runes := make([]rune, n) + for i := 0; i < n; i++ { + runes[i] = allSeq[rand.Intn(len(allSeq))] + } + return string(runes) +} diff --git a/util/reflect_util/reflect.go b/util/reflect_util/reflect.go index 1320ea0d..1fdaec50 100644 --- a/util/reflect_util/reflect.go +++ b/util/reflect_util/reflect.go @@ -1,21 +1,21 @@ -package reflect_util - -import "reflect" - -func GetFields(t reflect.Type) []reflect.StructField { - num := t.NumField() - fields := make([]reflect.StructField, 0, num) - for i := 0; i < num; i++ { - fields = append(fields, t.Field(i)) - } - return fields -} - -func GetFieldValues(v reflect.Value) []reflect.Value { - num := v.NumField() - fields := make([]reflect.Value, 0, num) - for i := 0; i < num; i++ { - fields = append(fields, v.Field(i)) - } - return fields -} +package reflect_util + +import "reflect" + +func GetFields(t reflect.Type) []reflect.StructField { + num := t.NumField() + fields := make([]reflect.StructField, 0, num) + for i := 0; i < num; i++ { + fields = append(fields, t.Field(i)) + } + return fields +} + +func GetFieldValues(v reflect.Value) []reflect.Value { + num := v.NumField() + fields := make([]reflect.Value, 0, num) + for i := 0; i < num; i++ { + fields = append(fields, v.Field(i)) + } + return fields +} diff --git a/util/sys/psutil.go b/util/sys/psutil.go index 2a7fc342..5137b5aa 100644 --- a/util/sys/psutil.go +++ b/util/sys/psutil.go @@ -1,8 +1,8 @@ -package sys - -import ( - _ "unsafe" -) - -//go:linkname HostProc github.com/shirou/gopsutil/v3/internal/common.HostProc -func HostProc(combineWith ...string) string +package sys + +import ( + _ "unsafe" +) + +//go:linkname HostProc github.com/shirou/gopsutil/v3/internal/common.HostProc +func HostProc(combineWith ...string) string diff --git a/util/sys/sys_darwin.go b/util/sys/sys_darwin.go index 2c5f5607..e110c0c7 100644 --- a/util/sys/sys_darwin.go +++ b/util/sys/sys_darwin.go @@ -1,23 +1,23 @@ -// +build darwin - -package sys - -import ( - "github.com/shirou/gopsutil/v3/net" -) - -func GetTCPCount() (int, error) { - stats, err := net.Connections("tcp") - if err != nil { - return 0, err - } - return len(stats), nil -} - -func GetUDPCount() (int, error) { - stats, err := net.Connections("udp") - if err != nil { - return 0, err - } - return len(stats), nil -} +// +build darwin + +package sys + +import ( + "github.com/shirou/gopsutil/v3/net" +) + +func GetTCPCount() (int, error) { + stats, err := net.Connections("tcp") + if err != nil { + return 0, err + } + return len(stats), nil +} + +func GetUDPCount() (int, error) { + stats, err := net.Connections("udp") + if err != nil { + return 0, err + } + return len(stats), nil +} diff --git a/util/sys/sys_linux.go b/util/sys/sys_linux.go index cf4201ca..843d9b00 100644 --- a/util/sys/sys_linux.go +++ b/util/sys/sys_linux.go @@ -1,70 +1,70 @@ -// +build linux - -package sys - -import ( - "bytes" - "fmt" - "io" - "os" -) - -func getLinesNum(filename string) (int, error) { - file, err := os.Open(filename) - if err != nil { - return 0, err - } - defer file.Close() - - sum := 0 - buf := make([]byte, 8192) - for { - n, err := file.Read(buf) - - var buffPosition int - for { - i := bytes.IndexByte(buf[buffPosition:], '\n') - if i < 0 || n == buffPosition { - break - } - buffPosition += i + 1 - sum++ - } - - if err == io.EOF { - return sum, nil - } else if err != nil { - return sum, err - } - } -} - -func GetTCPCount() (int, error) { - root := HostProc() - - tcp4, err := getLinesNum(fmt.Sprintf("%v/net/tcp", root)) - if err != nil { - return tcp4, err - } - tcp6, err := getLinesNum(fmt.Sprintf("%v/net/tcp6", root)) - if err != nil { - return tcp4 + tcp6, nil - } - - return tcp4 + tcp6, nil -} - -func GetUDPCount() (int, error) { - root := HostProc() - - udp4, err := getLinesNum(fmt.Sprintf("%v/net/udp", root)) - if err != nil { - return udp4, err - } - udp6, err := getLinesNum(fmt.Sprintf("%v/net/udp6", root)) - if err != nil { - return udp4 + udp6, nil - } - - return udp4 + udp6, nil -} +// +build linux + +package sys + +import ( + "bytes" + "fmt" + "io" + "os" +) + +func getLinesNum(filename string) (int, error) { + file, err := os.Open(filename) + if err != nil { + return 0, err + } + defer file.Close() + + sum := 0 + buf := make([]byte, 8192) + for { + n, err := file.Read(buf) + + var buffPosition int + for { + i := bytes.IndexByte(buf[buffPosition:], '\n') + if i < 0 || n == buffPosition { + break + } + buffPosition += i + 1 + sum++ + } + + if err == io.EOF { + return sum, nil + } else if err != nil { + return sum, err + } + } +} + +func GetTCPCount() (int, error) { + root := HostProc() + + tcp4, err := getLinesNum(fmt.Sprintf("%v/net/tcp", root)) + if err != nil { + return tcp4, err + } + tcp6, err := getLinesNum(fmt.Sprintf("%v/net/tcp6", root)) + if err != nil { + return tcp4 + tcp6, nil + } + + return tcp4 + tcp6, nil +} + +func GetUDPCount() (int, error) { + root := HostProc() + + udp4, err := getLinesNum(fmt.Sprintf("%v/net/udp", root)) + if err != nil { + return udp4, err + } + udp6, err := getLinesNum(fmt.Sprintf("%v/net/udp6", root)) + if err != nil { + return udp4 + udp6, nil + } + + return udp4 + udp6, nil +} diff --git a/v2ui/db.go b/v2ui/db.go index c0e85cf1..2745b9de 100644 --- a/v2ui/db.go +++ b/v2ui/db.go @@ -1,28 +1,28 @@ -package v2ui - -import ( - "gorm.io/driver/sqlite" - "gorm.io/gorm" - "gorm.io/gorm/logger" -) - -var v2db *gorm.DB - -func initDB(dbPath string) error { - c := &gorm.Config{ - Logger: logger.Discard, - } - var err error - v2db, err = gorm.Open(sqlite.Open(dbPath), c) - if err != nil { - return err - } - - return nil -} - -func getV2Inbounds() ([]*V2Inbound, error) { - inbounds := make([]*V2Inbound, 0) - err := v2db.Model(V2Inbound{}).Find(&inbounds).Error - return inbounds, err -} +package v2ui + +import ( + "gorm.io/driver/sqlite" + "gorm.io/gorm" + "gorm.io/gorm/logger" +) + +var v2db *gorm.DB + +func initDB(dbPath string) error { + c := &gorm.Config{ + Logger: logger.Discard, + } + var err error + v2db, err = gorm.Open(sqlite.Open(dbPath), c) + if err != nil { + return err + } + + return nil +} + +func getV2Inbounds() ([]*V2Inbound, error) { + inbounds := make([]*V2Inbound, 0) + err := v2db.Model(V2Inbound{}).Find(&inbounds).Error + return inbounds, err +} diff --git a/v2ui/models.go b/v2ui/models.go index 40ed1144..386cb277 100644 --- a/v2ui/models.go +++ b/v2ui/models.go @@ -1,41 +1,41 @@ -package v2ui - -import "xray-ui/database/model" - -type V2Inbound struct { - Id int `gorm:"primaryKey;autoIncrement"` - Port int `gorm:"unique"` - Listen string - Protocol string - Settings string - StreamSettings string - Tag string `gorm:"unique"` - Sniffing string - Remark string - Up int64 - Down int64 - Enable bool -} - -func (i *V2Inbound) TableName() string { - return "inbound" -} - -func (i *V2Inbound) ToInbound(userId int) *model.Inbound { - return &model.Inbound{ - UserId: userId, - Up: i.Up, - Down: i.Down, - Total: 0, - Remark: i.Remark, - Enable: i.Enable, - ExpiryTime: 0, - Listen: i.Listen, - Port: i.Port, - Protocol: model.Protocol(i.Protocol), - Settings: i.Settings, - StreamSettings: i.StreamSettings, - Tag: i.Tag, - Sniffing: i.Sniffing, - } -} +package v2ui + +import "xray-ui/database/model" + +type V2Inbound struct { + Id int `gorm:"primaryKey;autoIncrement"` + Port int `gorm:"unique"` + Listen string + Protocol string + Settings string + StreamSettings string + Tag string `gorm:"unique"` + Sniffing string + Remark string + Up int64 + Down int64 + Enable bool +} + +func (i *V2Inbound) TableName() string { + return "inbound" +} + +func (i *V2Inbound) ToInbound(userId int) *model.Inbound { + return &model.Inbound{ + UserId: userId, + Up: i.Up, + Down: i.Down, + Total: 0, + Remark: i.Remark, + Enable: i.Enable, + ExpiryTime: 0, + Listen: i.Listen, + Port: i.Port, + Protocol: model.Protocol(i.Protocol), + Settings: i.Settings, + StreamSettings: i.StreamSettings, + Tag: i.Tag, + Sniffing: i.Sniffing, + } +} diff --git a/v2ui/v2ui.go b/v2ui/v2ui.go index bb16c295..50b07cb4 100644 --- a/v2ui/v2ui.go +++ b/v2ui/v2ui.go @@ -1,51 +1,51 @@ -package v2ui - -import ( - "fmt" - "xray-ui/config" - "xray-ui/database" - "xray-ui/database/model" - "xray-ui/util/common" - "xray-ui/web/service" -) - -func MigrateFromV2UI(dbPath string) error { - err := initDB(dbPath) - if err != nil { - return common.NewError("init v2-ui database failed:", err) - } - err = database.InitDB(config.GetDBPath()) - if err != nil { - return common.NewError("init xray-ui database failed:", err) - } - - v2Inbounds, err := getV2Inbounds() - if err != nil { - return common.NewError("get v2-ui inbounds failed:", err) - } - if len(v2Inbounds) == 0 { - fmt.Println("migrate v2-ui inbounds success: 0") - return nil - } - - userService := service.UserService{} - user, err := userService.GetFirstUser() - if err != nil { - return common.NewError("get xray-ui user failed:", err) - } - - inbounds := make([]*model.Inbound, 0) - for _, v2inbound := range v2Inbounds { - inbounds = append(inbounds, v2inbound.ToInbound(user.Id)) - } - - inboundService := service.InboundService{} - err = inboundService.AddInbounds(inbounds) - if err != nil { - return common.NewError("add xray-ui inbounds failed:", err) - } - - fmt.Println("migrate v2-ui inbounds success:", len(inbounds)) - - return nil -} +package v2ui + +import ( + "fmt" + "xray-ui/config" + "xray-ui/database" + "xray-ui/database/model" + "xray-ui/util/common" + "xray-ui/web/service" +) + +func MigrateFromV2UI(dbPath string) error { + err := initDB(dbPath) + if err != nil { + return common.NewError("init v2-ui database failed:", err) + } + err = database.InitDB(config.GetDBPath()) + if err != nil { + return common.NewError("init xray-ui database failed:", err) + } + + v2Inbounds, err := getV2Inbounds() + if err != nil { + return common.NewError("get v2-ui inbounds failed:", err) + } + if len(v2Inbounds) == 0 { + fmt.Println("migrate v2-ui inbounds success: 0") + return nil + } + + userService := service.UserService{} + user, err := userService.GetFirstUser() + if err != nil { + return common.NewError("get xray-ui user failed:", err) + } + + inbounds := make([]*model.Inbound, 0) + for _, v2inbound := range v2Inbounds { + inbounds = append(inbounds, v2inbound.ToInbound(user.Id)) + } + + inboundService := service.InboundService{} + err = inboundService.AddInbounds(inbounds) + if err != nil { + return common.NewError("add xray-ui inbounds failed:", err) + } + + fmt.Println("migrate v2-ui inbounds success:", len(inbounds)) + + return nil +} diff --git a/web/assets/ant-design-vue@1.7.2/antd-with-locales.min.js b/web/assets/ant-design-vue@1.7.2/antd-with-locales.min.js index ac8063f6..3bf52de5 100644 --- a/web/assets/ant-design-vue@1.7.2/antd-with-locales.min.js +++ b/web/assets/ant-design-vue@1.7.2/antd-with-locales.min.js @@ -1,3 +1,3 @@ -/*! For license information please see antd-with-locales.min.js.LICENSE.txt */ -!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("moment"),require("vue")):"function"==typeof define&&define.amd?define(["moment","vue"],t):"object"==typeof exports?exports.antd=t(require("moment"),require("vue")):e.antd=t(e.moment,e.Vue)}(window,(function(e,t){return function(e){var t={};function n(i){if(t[i])return t[i].exports;var r=t[i]={i:i,l:!1,exports:{}};return e[i].call(r.exports,r,r.exports,n),r.l=!0,r.exports}return n.m=e,n.c=t,n.d=function(e,t,i){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:i})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var i=Object.create(null);if(n.r(i),Object.defineProperty(i,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var r in e)n.d(i,r,function(t){return e[t]}.bind(null,r));return i},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=547)}([function(e,t,n){"use strict";var i=n(14),r=n.n(i),a=n(36),o=n.n(a),s=Object.prototype,c=s.toString,l=s.hasOwnProperty,u=/^\s*function (\w+)/,h=function(e){var t=null!=e?e.type?e.type:e:null,n=t&&t.toString().match(u);return n&&n[1]},d=function(e){if(null==e)return null;var t=e.constructor.toString().match(u);return t&&t[1]},f=Number.isInteger||function(e){return"number"==typeof e&&isFinite(e)&&Math.floor(e)===e},p=Array.isArray||function(e){return"[object Array]"===c.call(e)},v=function(e){return"[object Function]"===c.call(e)},m=function(e,t){var n;return Object.defineProperty(t,"_vueTypes_name",{enumerable:!1,writable:!1,value:e}),n=t,Object.defineProperty(n,"isRequired",{get:function(){return this.required=!0,this},enumerable:!1}),function(e){Object.defineProperty(e,"def",{value:function(e){return void 0===e&&void 0===this.default?(this.default=void 0,this):v(e)||g(this,e)?(this.default=p(e)||o()(e)?function(){return e}:e,this):(b(this._vueTypes_name+' - invalid default value: "'+e+'"',e),this)},enumerable:!1,writable:!1})}(t),v(t.validator)&&(t.validator=t.validator.bind(t)),t},g=function e(t,n){var i=arguments.length>2&&void 0!==arguments[2]&&arguments[2],r=t,a=!0,s=void 0;o()(t)||(r={type:t});var c=r._vueTypes_name?r._vueTypes_name+" - ":"";return l.call(r,"type")&&null!==r.type&&(p(r.type)?(a=r.type.some((function(t){return e(t,n,!0)})),s=r.type.map((function(e){return h(e)})).join(" or ")):a="Array"===(s=h(r))?p(n):"Object"===s?o()(n):"String"===s||"Number"===s||"Boolean"===s||"Function"===s?d(n)===s:n instanceof r.type),a?l.call(r,"validator")&&v(r.validator)?((a=r.validator(n))||!1!==i||b(c+"custom validation failed"),a):a:(!1===i&&b(c+'value "'+n+'" should be of type "'+s+'"'),!1)},b=function(){},y={get any(){return m("any",{type:null})},get func(){return m("function",{type:Function}).def(C.func)},get bool(){return m("boolean",{type:Boolean}).def(C.bool)},get string(){return m("string",{type:String}).def(C.string)},get number(){return m("number",{type:Number}).def(C.number)},get array(){return m("array",{type:Array}).def(C.array)},get object(){return m("object",{type:Object}).def(C.object)},get integer(){return m("integer",{type:Number,validator:function(e){return f(e)}}).def(C.integer)},get symbol(){return m("symbol",{type:null,validator:function(e){return"symbol"===(void 0===e?"undefined":r()(e))}})},custom:function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"custom validation failed";if("function"!=typeof e)throw new TypeError("[VueTypes error]: You must provide a function as argument");return m(e.name||"<>",{validator:function(){var n=e.apply(void 0,arguments);return n||b(this._vueTypes_name+" - "+t),n}})},oneOf:function(e){if(!p(e))throw new TypeError("[VueTypes error]: You must provide an array as argument");var t='oneOf - value should be one of "'+e.join('", "')+'"',n=e.reduce((function(e,t){return null!=t&&-1===e.indexOf(t.constructor)&&e.push(t.constructor),e}),[]);return m("oneOf",{type:n.length>0?n:null,validator:function(n){var i=-1!==e.indexOf(n);return i||b(t),i}})},instanceOf:function(e){return m("instanceOf",{type:e})},oneOfType:function(e){if(!p(e))throw new TypeError("[VueTypes error]: You must provide an array as argument");var t=!1,n=e.reduce((function(e,n){if(o()(n)){if("oneOf"===n._vueTypes_name)return e.concat(n.type||[]);if(n.type&&!v(n.validator)){if(p(n.type))return e.concat(n.type);e.push(n.type)}else v(n.validator)&&(t=!0);return e}return e.push(n),e}),[]);if(!t)return m("oneOfType",{type:n}).def(void 0);var i=e.map((function(e){return e&&p(e.type)?e.type.map(h):h(e)})).reduce((function(e,t){return e.concat(p(t)?t:[t])}),[]).join('", "');return this.custom((function(t){var n=e.some((function(e){return"oneOf"===e._vueTypes_name?!e.type||g(e.type,t,!0):g(e,t,!0)}));return n||b('oneOfType - value type should be one of "'+i+'"'),n})).def(void 0)},arrayOf:function(e){return m("arrayOf",{type:Array,validator:function(t){var n=t.every((function(t){return g(e,t)}));return n||b('arrayOf - value must be an array of "'+h(e)+'"'),n}})},objectOf:function(e){return m("objectOf",{type:Object,validator:function(t){var n=Object.keys(t).every((function(n){return g(e,t[n])}));return n||b('objectOf - value must be an object of "'+h(e)+'"'),n}})},shape:function(e){var t=Object.keys(e),n=t.filter((function(t){return e[t]&&!0===e[t].required})),i=m("shape",{type:Object,validator:function(i){var r=this;if(!o()(i))return!1;var a=Object.keys(i);return n.length>0&&n.some((function(e){return-1===a.indexOf(e)}))?(b('shape - at least one of required properties "'+n.join('", "')+'" is not present'),!1):a.every((function(n){if(-1===t.indexOf(n))return!0===r._vueTypes_isLoose||(b('shape - object is missing "'+n+'" property'),!1);var a=e[n];return g(a,i[n])}))}});return Object.defineProperty(i,"_vueTypes_isLoose",{enumerable:!1,writable:!0,value:!1}),Object.defineProperty(i,"loose",{get:function(){return this._vueTypes_isLoose=!0,this},enumerable:!1}),i}},C={func:void 0,bool:void 0,string:void 0,number:void 0,array:void 0,object:void 0,integer:void 0};Object.defineProperty(y,"sensibleDefaults",{enumerable:!1,set:function(e){!1===e?C={}:!0===e?C={func:void 0,bool:void 0,string:void 0,number:void 0,array:void 0,object:void 0,integer:void 0}:o()(e)&&(C=e)},get:function(){return C}});t.a=y},function(e,t,n){"use strict";n.d(t,"i",(function(){return T})),n.d(t,"h",(function(){return V})),n.d(t,"k",(function(){return P})),n.d(t,"f",(function(){return j})),n.d(t,"q",(function(){return H})),n.d(t,"u",(function(){return _})),n.d(t,"v",(function(){return L})),n.d(t,"c",(function(){return F})),n.d(t,"x",(function(){return A})),n.d(t,"s",(function(){return m})),n.d(t,"l",(function(){return k})),n.d(t,"g",(function(){return z})),n.d(t,"o",(function(){return x})),n.d(t,"m",(function(){return w})),n.d(t,"j",(function(){return M})),n.d(t,"e",(function(){return O})),n.d(t,"r",(function(){return S})),n.d(t,"y",(function(){return v})),n.d(t,"t",(function(){return E})),n.d(t,"w",(function(){return D})),n.d(t,"a",(function(){return p})),n.d(t,"p",(function(){return b})),n.d(t,"n",(function(){return y})),n.d(t,"d",(function(){return C}));var i=n(14),r=n.n(i),a=n(22),o=n.n(a),s=n(2),c=n.n(s),l=n(36),u=n.n(l),h=n(5),d=n.n(h);var f=/-(\w)/g,p=function(e){return e.replace(f,(function(e,t){return t?t.toUpperCase():""}))},v=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"",t=arguments[1],n={},i=/;(?![^(]*\))/g,r=/:(.+)/;return e.split(i).forEach((function(e){if(e){var i=e.split(r);if(i.length>1){var a=t?p(i[0].trim()):i[0].trim();n[a]=i[1].trim()}}})),n},m=function(e,t){return t in((e.$options||{}).propsData||{})},g=function(e){return e.data&&e.data.scopedSlots||{}},b=function(e){var t=e.componentOptions||{};e.$vnode&&(t=e.$vnode.componentOptions||{});var n=e.children||t.children||[],i={};return n.forEach((function(e){if(!_(e)){var t=e.data&&e.data.slot||"default";i[t]=i[t]||[],i[t].push(e)}})),c()({},i,g(e))},y=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"default",n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};return e.$scopedSlots&&e.$scopedSlots[t]&&e.$scopedSlots[t](n)||e.$slots[t]||[]},C=function(e){var t=e.componentOptions||{};return e.$vnode&&(t=e.$vnode.componentOptions||{}),e.children||t.children||[]},x=function(e){if(e.fnOptions)return e.fnOptions;var t=e.componentOptions;return e.$vnode&&(t=e.$vnode.componentOptions),t&&t.Ctor.options||{}},k=function(e){if(e.componentOptions){var t=e.componentOptions,n=t.propsData,i=void 0===n?{}:n,r=t.Ctor,a=((void 0===r?{}:r).options||{}).props||{},s={},l=!0,u=!1,h=void 0;try{for(var d,f=Object.entries(a)[Symbol.iterator]();!(l=(d=f.next()).done);l=!0){var p=d.value,v=o()(p,2),m=v[0],g=v[1],b=g.default;void 0!==b&&(s[m]="function"==typeof b&&"Function"!==(y=g.type,C=void 0,(C=y&&y.toString().match(/^\s*function (\w+)/))?C[1]:"")?b.call(e):b)}}catch(e){u=!0,h=e}finally{try{!l&&f.return&&f.return()}finally{if(u)throw h}}return c()({},s,i)}var y,C,x=e.$options,k=void 0===x?{}:x,z=e.$props;return function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n={};return Object.keys(e).forEach((function(i){(i in t||void 0!==e[i])&&(n[i]=e[i])})),n}(void 0===z?{}:z,k.propsData)},z=function(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:e,i=!(arguments.length>3&&void 0!==arguments[3])||arguments[3];if(e.$createElement){var r=e.$createElement,a=e[t];return void 0!==a?"function"==typeof a&&i?a(r,n):a:e.$scopedSlots[t]&&i&&e.$scopedSlots[t](n)||e.$scopedSlots[t]||e.$slots[t]||void 0}var o=e.context.$createElement,s=w(e)[t];if(void 0!==s)return"function"==typeof s&&i?s(o,n):s;var c=g(e)[t];if(void 0!==c)return"function"==typeof c&&i?c(o,n):c;var l=[],u=e.componentOptions||{};return(u.children||[]).forEach((function(e){e.data&&e.data.slot===t&&(e.data.attrs&&delete e.data.attrs.slot,"template"===e.tag?l.push(e.children):l.push(e))})),l.length?l:void 0},w=function(e){var t=e.componentOptions;return e.$vnode&&(t=e.$vnode.componentOptions),t&&t.propsData||{}},S=function(e,t){return w(e)[t]},O=function(e){var t=e.data;return e.$vnode&&(t=e.$vnode.data),t&&t.attrs||{}},M=function(e){var t=e.key;return e.$vnode&&(t=e.$vnode.key),t};function T(e){var t={};return e.componentOptions&&e.componentOptions.listeners?t=e.componentOptions.listeners:e.data&&e.data.on&&(t=e.data.on),c()({},t)}function V(e){var t={};return e.data&&e.data.on&&(t=e.data.on),c()({},t)}function P(e){return(e.$vnode?e.$vnode.componentOptions.listeners:e.$listeners)||{}}function j(e){var t={};e.data?t=e.data:e.$vnode&&e.$vnode.data&&(t=e.$vnode.data);var n=t.class||{},i=t.staticClass,r={};return i&&i.split(" ").forEach((function(e){r[e.trim()]=!0})),"string"==typeof n?n.split(" ").forEach((function(e){r[e.trim()]=!0})):Array.isArray(n)?d()(n).split(" ").forEach((function(e){r[e.trim()]=!0})):r=c()({},r,n),r}function H(e,t){var n={};e.data?n=e.data:e.$vnode&&e.$vnode.data&&(n=e.$vnode.data);var i=n.style||n.staticStyle;if("string"==typeof i)i=v(i,t);else if(t&&i){var r={};return Object.keys(i).forEach((function(e){return r[p(e)]=i[e]})),r}return i}function _(e){return!(e.tag||e.text&&""!==e.text.trim())}function L(e){return!e.tag}function F(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[];return e.filter((function(e){return!_(e)}))}var E=function(e,t){return Object.keys(t).forEach((function(n){if(!e[n])throw new Error("not have "+n+" prop");e[n].def&&(e[n]=e[n].def(t[n]))})),e};function A(){var e=[].slice.call(arguments,0),t={};return e.forEach((function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},n=!0,i=!1,r=void 0;try{for(var a,s=Object.entries(e)[Symbol.iterator]();!(n=(a=s.next()).done);n=!0){var l=a.value,h=o()(l,2),d=h[0],f=h[1];t[d]=t[d]||{},u()(f)?c()(t[d],f):t[d]=f}}catch(e){i=!0,r=e}finally{try{!n&&s.return&&s.return()}finally{if(i)throw r}}})),t}function D(e){return e&&"object"===(void 0===e?"undefined":r()(e))&&"componentOptions"in e&&"context"in e&&void 0!==e.tag}t.b=m},function(e,t,n){"use strict";t.__esModule=!0;var i,r=n(247),a=(i=r)&&i.__esModule?i:{default:i};t.default=a.default||function(e){for(var t=1;t0&&void 0!==arguments[0]?arguments[0]:{},t=arguments[1],n="function"==typeof e?e(this.$data,this.$props):e;if(this.getDerivedStateFromProps){var i=this.getDerivedStateFromProps(Object(s.l)(this),o()({},this.$data,n));if(null===i)return;n=o()({},n,i||{})}o()(this.$data,n),this.$forceUpdate(),this.$nextTick((function(){t&&t()}))},__emit:function(){var e=[].slice.call(arguments,0),t=e[0],n=this.$listeners[t];if(e.length&&n)if(Array.isArray(n))for(var i=0,a=n.length;i0&&void 0!==arguments[0]?arguments[0]:[],t={};return e.forEach((function(e){t[e]=function(t){this._proxyVm._data[e]=t}})),t}(["prefixCls","csp","autoInsertSpaceInButton","locale","pageHeader","transformCellText"])),methods:{renderEmptyComponent:function(e,t){return(Object(c.g)(this,"renderEmpty",{},!1)||h)(e,t)},getPrefixCls:function(e,t){var n=this.$props.prefixCls,i=void 0===n?"ant":n;return t||(e?i+"-"+e:i)},renderProvider:function(e){return(0,this.$createElement)(f.b,{attrs:{locale:this.locale||e,_ANT_MARK__:f.a}},[this.$slots.default?Object(c.c)(this.$slots.default)[0]:null])}},render:function(){var e=this,t=arguments[0];return t(p.a,{scopedSlots:{default:function(t,n,i){return e.renderProvider(i)}}})}},m={getPrefixCls:function(e,t){return t||"ant-"+e},renderEmpty:h};v.install=function(e){e.use(d.a),e.component(v.name,v)};t.b=v},function(e,t,n){"use strict";var i=n(6),r=n.n(i),a=n(2),o=n.n(a),s=n(3),c=n.n(s),l=n(11),u=n.n(l),h=n(5),d=n.n(h),f=n(160),p=n(62),v={primaryColor:"#333",secondaryColor:"#E6E6E6"},m={name:"AntdIcon",props:["type","primaryColor","secondaryColor"],displayName:"IconVue",definitions:new p.a,data:function(){return{twoToneColorPalette:v}},add:function(){for(var e=arguments.length,t=Array(e),n=0;n1&&void 0!==arguments[1]?arguments[1]:v;if(e){var n=m.definitions.get(e);return n&&"function"==typeof n.icon&&(n=o()({},n,{icon:n.icon(t.primaryColor,t.secondaryColor)})),n}},setTwoToneColors:function(e){var t=e.primaryColor,n=e.secondaryColor;v.primaryColor=t,v.secondaryColor=n||Object(p.c)(t)},getTwoToneColors:function(){return o()({},v)},render:function(e){var t=this.$props,n=t.type,i=t.primaryColor,r=t.secondaryColor,a=void 0,s=v;if(i&&(s={primaryColor:i,secondaryColor:r||Object(p.c)(i)}),Object(p.d)(n))a=n;else if("string"==typeof n&&!(a=m.get(n,s)))return null;return a?(a&&"function"==typeof a.icon&&(a=o()({},a,{icon:a.icon(s.primaryColor,s.secondaryColor)})),Object(p.b)(e,a.icon,"svg-"+a.name,{attrs:{"data-icon":a.name,width:"1em",height:"1em",fill:"currentColor","aria-hidden":"true"},on:this.$listeners})):(Object(p.e)("type should be string or icon definiton, but got "+n),null)},install:function(e){e.component(m.name,m)}},g=m,b=n(0),y=n(12),C=n.n(y),x=n(1),k=new Set;var z=n(13),w={width:"1em",height:"1em",fill:"currentColor","aria-hidden":"true",focusable:"false"},S=/-fill$/,O=/-o$/,M=/-twotone$/;var T=n(26);function V(e){return g.setTwoToneColors({primaryColor:e})}var P=n(10);g.add.apply(g,u()(Object.keys(f).map((function(e){return f[e]})))),V("#1890ff");function j(e,t,n){var i,a=n.$props,s=n.$slots,l=Object(x.k)(n),u=a.type,h=a.component,f=a.viewBox,p=a.spin,v=a.theme,m=a.twoToneColor,b=a.rotate,y=a.tabIndex,C=Object(x.c)(s.default);C=0===C.length?void 0:C,Object(z.a)(Boolean(u||h||C),"Icon","Icon should have `type` prop or `component` prop or `children`.");var k=d()((i={},c()(i,"anticon",!0),c()(i,"anticon-"+u,!!u),i)),T=d()(c()({},"anticon-spin",!!p||"loading"===u)),V=b?{msTransform:"rotate("+b+"deg)",transform:"rotate("+b+"deg)"}:void 0,P={attrs:o()({},w,{viewBox:f}),class:T,style:V};f||delete P.attrs.viewBox;var j=y;void 0===j&&"click"in l&&(j=-1);var H={attrs:{"aria-label":u&&t.icon+": "+u,tabIndex:j},on:l,class:k,staticClass:""};return e("i",H,[function(){if(h)return e(h,P,[C]);if(C){Object(z.a)(Boolean(f)||1===C.length&&"use"===C[0].tag,"Icon","Make sure that you provide correct `viewBox` prop (default `0 0 1024 1024`) to the icon.");var t={attrs:o()({},w),class:T,style:V};return e("svg",r()([t,{attrs:{viewBox:f}}]),[C])}if("string"==typeof u){var n=u;if(v){var i=function(e){var t=null;return S.test(e)?t="filled":O.test(e)?t="outlined":M.test(e)&&(t="twoTone"),t}(u);Object(z.a)(!i||v===i,"Icon","The icon name '"+u+"' already specify a theme '"+i+"', the 'theme' prop '"+v+"' will be ignored.")}return n=function(e,t){var n=e;return"filled"===t?n+="-fill":"outlined"===t?n+="-o":"twoTone"===t?n+="-twotone":Object(z.a)(!1,"Icon","This icon '"+e+"' has unknown theme '"+t+"'"),n}(function(e){return e.replace(S,"").replace(O,"").replace(M,"")}(function(e){var t=e;switch(e){case"cross":t="close";break;case"interation":t="interaction";break;case"canlendar":t="calendar";break;case"colum-height":t="column-height"}return Object(z.a)(t===e,"Icon","Icon '"+e+"' was a typo and is now deprecated, please use '"+t+"' instead."),t}(n)),v||"outlined"),e(g,{attrs:{focusable:"false",type:n,primaryColor:m},class:T,style:V})}}()])}var H={name:"AIcon",props:{tabIndex:b.a.number,type:b.a.string,component:b.a.any,viewBox:b.a.any,spin:b.a.bool.def(!1),rotate:b.a.number,theme:b.a.oneOf(["filled","outlined","twoTone"]),twoToneColor:b.a.string,role:b.a.string},render:function(e){var t=this;return e(T.a,{attrs:{componentName:"Icon"},scopedSlots:{default:function(n){return j(e,n,t)}}})},createFromIconfontCN:function(e){var t=e.scriptUrl,n=e.extraCommonProps,i=void 0===n?{}:n;if("undefined"!=typeof document&&"undefined"!=typeof window&&"function"==typeof document.createElement&&"string"==typeof t&&t.length&&!k.has(t)){var r=document.createElement("script");r.setAttribute("src",t),r.setAttribute("data-namespace",t),k.add(t),document.body.appendChild(r)}return{functional:!0,name:"AIconfont",props:_.props,render:function(e,t){var n=t.props,r=t.slots,a=t.listeners,o=t.data,s=n.type,c=C()(n,["type"]),l=r().default,u=null;s&&(u=e("use",{attrs:{"xlink:href":"#"+s}})),l&&(u=l);var h=Object(x.x)(i,o,{props:c,on:a});return e(_,h,[u])}}},getTwoToneColor:function(){return g.getTwoToneColors().primaryColor}};H.setTwoToneColor=V,H.install=function(e){e.use(P.a),e.component(H.name,H)};var _=t.a=H},function(e,t,n){"use strict";n.d(t,"b",(function(){return h})),n.d(t,"a",(function(){return d}));var i=n(11),r=n.n(i),a=n(2),o=n.n(a),s=n(1),c=n(5),l=n.n(c);function u(e,t){var n=e.componentOptions,i=e.data,r={};n&&n.listeners&&(r=o()({},n.listeners));var a={};i&&i.on&&(a=o()({},i.on));var s=new e.constructor(e.tag,i?o()({},i,{on:a}):i,e.children,e.text,e.elm,e.context,n?o()({},n,{listeners:r}):n,e.asyncFactory);return s.ns=e.ns,s.isStatic=e.isStatic,s.key=e.key,s.isComment=e.isComment,s.fnContext=e.fnContext,s.fnOptions=e.fnOptions,s.fnScopeId=e.fnScopeId,s.isCloned=!0,t&&(e.children&&(s.children=h(e.children,!0)),n&&n.children&&(n.children=h(n.children,!0))),s}function h(e,t){for(var n=e.length,i=new Array(n),r=0;r1&&void 0!==arguments[1]?arguments[1]:{},n=arguments[2],i=e;if(Array.isArray(e)&&(i=Object(s.c)(e)[0]),!i)return null;var a=u(i,n),c=t.props,h=void 0===c?{}:c,d=t.key,f=t.on,p=void 0===f?{}:f,v=t.nativeOn,m=void 0===v?{}:v,g=t.children,b=t.directives,y=void 0===b?[]:b,C=a.data||{},x={},k={},z=t.attrs,w=void 0===z?{}:z,S=t.ref,O=t.domProps,M=void 0===O?{}:O,T=t.style,V=void 0===T?{}:T,P=t.class,j=void 0===P?{}:P,H=t.scopedSlots,_=void 0===H?{}:H;return k="string"==typeof C.style?Object(s.y)(C.style):o()({},C.style,k),k="string"==typeof V?o()({},k,Object(s.y)(k)):o()({},k,V),"string"==typeof C.class&&""!==C.class.trim()?C.class.split(" ").forEach((function(e){x[e.trim()]=!0})):Array.isArray(C.class)?l()(C.class).split(" ").forEach((function(e){x[e.trim()]=!0})):x=o()({},C.class,x),"string"==typeof j&&""!==j.trim()?j.split(" ").forEach((function(e){x[e.trim()]=!0})):x=o()({},x,j),a.data=o()({},C,{style:k,attrs:o()({},C.attrs,w),class:x,domProps:o()({},C.domProps,M),scopedSlots:o()({},C.scopedSlots,_),directives:[].concat(r()(C.directives||[]),r()(y))}),a.componentOptions?(a.componentOptions.propsData=a.componentOptions.propsData||{},a.componentOptions.listeners=a.componentOptions.listeners||{},a.componentOptions.propsData=o()({},a.componentOptions.propsData,h),a.componentOptions.listeners=o()({},a.componentOptions.listeners,p),g&&(a.componentOptions.children=g)):(g&&(a.children=g),a.data.on=o()({},a.data.on||{},p)),a.data.on=o()({},a.data.on||{},m),void 0!==d&&(a.key=d,a.data.key=d),"string"==typeof S&&(a.data.ref=S),a}},function(e,t,n){"use strict";var i=n(25),r=n.n(i),a=n(116),o=n(80);function s(e){return e.directive("ant-portal",{inserted:function(e,t){var n=t.value,i="function"==typeof n?n(e):n;i!==e.parentNode&&i.appendChild(e)},componentUpdated:function(e,t){var n=t.value,i="function"==typeof n?n(e):n;i!==e.parentNode&&i.appendChild(e)}})}var c={install:function(e){e.use(r.a,{name:"ant-ref"}),Object(a.a)(e),Object(o.a)(e),s(e)}},l={};l.install=function(e){l.Vue=e,e.use(c)};t.a=l},function(e,t,n){"use strict";t.__esModule=!0;var i,r=n(276),a=(i=r)&&i.__esModule?i:{default:i};t.default=function(e){if(Array.isArray(e)){for(var t=0,n=Array(e.length);t=0||Object.prototype.hasOwnProperty.call(e,i)&&(n[i]=e[i]);return n}},function(e,t,n){"use strict";var i={};function r(e,t){0}function a(e,t,n){t||i[n]||(e(!1,n),i[n]=!0)}var o=function(e,t){a(r,e,t)};t.a=function(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:"";o(e,"[antdv: "+t+"] "+n)}},function(e,t,n){"use strict";t.__esModule=!0;var i=o(n(253)),r=o(n(263)),a="function"==typeof r.default&&"symbol"==typeof i.default?function(e){return typeof e}:function(e){return e&&"function"==typeof r.default&&e.constructor===r.default&&e!==r.default.prototype?"symbol":typeof e};function o(e){return e&&e.__esModule?e:{default:e}}t.default="function"==typeof r.default&&"symbol"===a(i.default)?function(e){return void 0===e?"undefined":a(e)}:function(e){return e&&"function"==typeof r.default&&e.constructor===r.default&&e!==r.default.prototype?"symbol":void 0===e?"undefined":a(e)}},function(t,n){t.exports=e},function(e,t,n){"use strict";var i=n(2),r=n.n(i);t.a=function(e,t){for(var n=r()({},e),i=0;i=0&&n.splice(i,1),n}function y(e,t){var n=e.slice();return-1===n.indexOf(t)&&n.push(t),n}function C(e){return e.split("-")}function x(e,t){return e+"-"+t}function k(e){return Object(v.o)(e).isTreeNode}function z(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[];return e.filter(k)}function w(e){var t=Object(v.l)(e)||{},n=t.disabled,i=t.disableCheckbox,r=t.checkable;return!(!n&&!i)||!1===r}function S(e,t){!function n(i,r,a){var o=i?i.componentOptions.children:e,s=i?x(a.pos,r):0,c=z(o);if(i){var l=i.key;l||null!=l||(l=s);var u={node:i,index:r,pos:s,key:l,parentPos:a.node?a.pos:null};t(u)}c.forEach((function(e,t){n(e,t,{node:i,pos:s})}))}(null)}function O(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[],t=arguments[1],n=e.map(t);return 1===n.length?n[0]:n}function M(e,t){var n=Object(v.l)(t),i=n.eventKey,r=n.pos,a=[];return S(e,(function(e){var t=e.key;a.push(t)})),a.push(i||r),a}function T(e,t){var n=e.clientY,i=t.$refs.selectHandle.getBoundingClientRect(),r=i.top,a=i.bottom,o=i.height,s=Math.max(.25*o,2);return n<=r+s?-1:n>=a-s?1:0}function V(e,t){if(e)return t.multiple?e.slice():e.length?[e[0]]:e}var P=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return{props:Object(f.a)(e,["on","key","class","className","style"]),on:e.on||{},class:e.class||e.className,style:e.style,key:e.key}};function j(e,t,n){if(!t)return[];var i=(n||{}).processProps,r=void 0===i?P:i;return(Array.isArray(t)?t:[t]).map((function(t){var i=t.children,a=u()(t,["children"]),o=j(e,i,n);return e(p.a,r(a),[o])}))}function H(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=t.initWrapper,i=t.processEntity,r=t.onProcessFinished,a=new Map,o=new Map,s={posEntities:a,keyEntities:o};return n&&(s=n(s)||s),S(e,(function(e){var t=e.node,n=e.index,r=e.pos,c=e.key,l=e.parentPos,u={node:t,index:n,key:c,pos:r};a.set(r,u),o.set(c,u),u.parent=a.get(l),u.parent&&(u.parent.children=u.parent.children||[],u.parent.children.push(u)),i&&i(u,s)})),r&&r(s),s}function _(e){if(!e)return null;var t=void 0;if(Array.isArray(e))t={checkedKeys:e,halfCheckedKeys:void 0};else{if("object"!==(void 0===e?"undefined":c()(e)))return d()(!1,"`checkedKeys` is not an array or an object"),null;t={checkedKeys:e.checked||void 0,halfCheckedKeys:e.halfChecked||void 0}}return t}function L(e,t,n){var i=arguments.length>3&&void 0!==arguments[3]?arguments[3]:{},r=new Map,a=new Map;function s(e){if(r.get(e)!==t){var i=n.get(e);if(i){var o=i.children,c=i.parent;if(!w(i.node)){var l=!0,u=!1;(o||[]).filter((function(e){return!w(e.node)})).forEach((function(e){var t=e.key,n=r.get(t),i=a.get(t);(n||i)&&(u=!0),n||(l=!1)})),t?r.set(e,l):r.set(e,!1),a.set(e,u),c&&s(c.key)}}}}function c(e){if(r.get(e)!==t){var i=n.get(e);if(i){var a=i.children;w(i.node)||(r.set(e,t),(a||[]).forEach((function(e){c(e.key)})))}}}function l(e){var i=n.get(e);if(i){var a=i.children,o=i.parent,l=i.node;r.set(e,t),w(l)||((a||[]).filter((function(e){return!w(e.node)})).forEach((function(e){c(e.key)})),o&&s(o.key))}else d()(!1,"'"+e+"' does not exist in the tree.")}(i.checkedKeys||[]).forEach((function(e){r.set(e,!0)})),(i.halfCheckedKeys||[]).forEach((function(e){a.set(e,!0)})),(e||[]).forEach((function(e){l(e)}));var u=[],h=[],f=!0,p=!1,v=void 0;try{for(var m,g=r[Symbol.iterator]();!(f=(m=g.next()).done);f=!0){var b=m.value,y=o()(b,2),C=y[0],x=y[1];x&&u.push(C)}}catch(e){p=!0,v=e}finally{try{!f&&g.return&&g.return()}finally{if(p)throw v}}var k=!0,z=!1,S=void 0;try{for(var O,M=a[Symbol.iterator]();!(k=(O=M.next()).done);k=!0){var T=O.value,V=o()(T,2),P=V[0],j=V[1];!r.get(P)&&j&&h.push(P)}}catch(e){z=!0,S=e}finally{try{!k&&M.return&&M.return()}finally{if(z)throw S}}return{checkedKeys:u,halfCheckedKeys:h}}function F(e,t){var n=new Map;return(e||[]).forEach((function(e){!function e(i){if(!n.get(i)){var r=t.get(i);if(r){n.set(i,!0);var a=r.parent,o=r.node,s=Object(v.l)(o);s&&s.disabled||a&&e(a.key)}}}(e)})),[].concat(r()(n.keys()))}},function(e,n){e.exports=t},function(e,t,n){(function(t){for(var i=n(289),r="undefined"==typeof window?t:window,a=["moz","webkit"],o="AnimationFrame",s=r["request"+o],c=r["cancel"+o]||r["cancelRequest"+o],l=0;!s&&l1&&void 0!==arguments[1]?arguments[1]:{},n=t.beforeEnter,a=t.enter,o=t.afterEnter,s=t.leave,c=t.afterLeave,l=t.appear,u=void 0===l||l,h=t.tag,d=t.nativeOn,f={props:{appear:u,css:!1},on:{beforeEnter:n||r,enter:a||function(t,n){Object(i.a)(t,e+"-enter",n)},afterEnter:o||r,leave:s||function(t,n){Object(i.a)(t,e+"-leave",n)},afterLeave:c||r},nativeOn:d};return h&&(f.tag=h),f}},function(e,t,n){"use strict";var i=function(){};e.exports=i},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default={install:function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=t.name||"ref";e.directive(n,{bind:function(t,n,i){e.nextTick((function(){n.value(i.componentInstance||t,i.key)})),n.value(i.componentInstance||t,i.key)},update:function(e,t,i,r){if(r.data&&r.data.directives){var a=r.data.directives.find((function(e){return e.name===n}));if(a&&a.value!==t.value)return a&&a.value(null,r.key),void t.value(i.componentInstance||e,i.key)}i.componentInstance===r.componentInstance&&i.elm===r.elm||t.value(i.componentInstance||e,i.key)},unbind:function(e,t,n){t.value(null,n.key)}})}}},function(e,t,n){"use strict";var i=n(2),r=n.n(i),a=n(0),o=n(37);t.a={name:"LocaleReceiver",props:{componentName:a.a.string.def("global"),defaultLocale:a.a.oneOfType([a.a.object,a.a.func]),children:a.a.func},inject:{localeData:{default:function(){return{}}}},methods:{getLocale:function(){var e=this.componentName,t=this.defaultLocale||o.a[e||"global"],n=this.localeData.antLocale,i=e&&n?n[e]:{};return r()({},"function"==typeof t?t():t,i||{})},getLocaleCode:function(){var e=this.localeData.antLocale,t=e&&e.locale;return e&&e.exist&&!t?o.a.locale:t}},render:function(){var e=this.$scopedSlots,t=this.children||e.default,n=this.localeData.antLocale;return t(this.getLocale(),this.getLocaleCode(),n)}}},function(e,t,n){"use strict";function i(e){return e.default||e}n.d(t,"a",(function(){return i}))},function(e,t){e.exports=function(e,t,n,i){var r=n?n.call(i,e,t):void 0;if(void 0!==r)return!!r;if(e===t)return!0;if("object"!=typeof e||!e||"object"!=typeof t||!t)return!1;var a=Object.keys(e),o=Object.keys(t);if(a.length!==o.length)return!1;for(var s=Object.prototype.hasOwnProperty.bind(t),c=0;c=0&&n.left>=0&&n.bottom>n.top&&n.right>n.left?n:null}function oe(e){var t,n,i;if(te.isWindow(e)||9===e.nodeType){var r=te.getWindow(e);t={left:te.getWindowScrollLeft(r),top:te.getWindowScrollTop(r)},n=te.viewportWidth(r),i=te.viewportHeight(r)}else t=te.offset(e),n=te.outerWidth(e),i=te.outerHeight(e);return t.width=n,t.height=i,t}function se(e,t){var n=t.charAt(0),i=t.charAt(1),r=e.width,a=e.height,o=e.left,s=e.top;return"c"===n?s+=a/2:"b"===n&&(s+=a),"c"===i?o+=r/2:"r"===i&&(o+=r),{left:o,top:s}}function ce(e,t,n,i,r){var a=se(t,n[1]),o=se(e,n[0]),s=[o.left-a.left,o.top-a.top];return{left:Math.round(e.left-s[0]+i[0]-r[0]),top:Math.round(e.top-s[1]+i[1]-r[1])}}function le(e,t,n){return e.leftn.right}function ue(e,t,n){return e.topn.bottom}function he(e,t,n){var i=[];return te.each(e,(function(e){i.push(e.replace(t,(function(e){return n[e]})))})),i}function de(e,t){return e[t]=-e[t],e}function fe(e,t){return(/%$/.test(e)?parseInt(e.substring(0,e.length-1),10)/100*t:parseInt(e,10))||0}function pe(e,t){e[0]=fe(e[0],t.width),e[1]=fe(e[1],t.height)}function ve(e,t,n,i){var r=n.points,a=n.offset||[0,0],o=n.targetOffset||[0,0],s=n.overflow,c=n.source||e;a=[].concat(a),o=[].concat(o);var l={},u=0,h=ae(c,!(!(s=s||{})||!s.alwaysByViewport)),d=oe(c);pe(a,d),pe(o,t);var f=ce(d,t,r,a,o),p=te.merge(d,f);if(h&&(s.adjustX||s.adjustY)&&i){if(s.adjustX&&le(f,d,h)){var v=he(r,/[lr]/gi,{l:"r",r:"l"}),m=de(a,0),g=de(o,0);(function(e,t,n){return e.left>n.right||e.left+t.widthn.bottom||e.top+t.height=n.left&&r.left+a.width>n.right&&(a.width-=r.left+a.width-n.right),i.adjustX&&r.left+a.width>n.right&&(r.left=Math.max(n.right-a.width,n.left)),i.adjustY&&r.top=n.top&&r.top+a.height>n.bottom&&(a.height-=r.top+a.height-n.bottom),i.adjustY&&r.top+a.height>n.bottom&&(r.top=Math.max(n.bottom-a.height,n.top)),te.mix(r,a)}(f,d,h,l))}return p.width!==d.width&&te.css(c,"width",te.width(c)+p.width-d.width),p.height!==d.height&&te.css(c,"height",te.height(c)+p.height-d.height),te.offset(c,{left:p.left,top:p.top},{useCssRight:n.useCssRight,useCssBottom:n.useCssBottom,useCssTransform:n.useCssTransform,ignoreShake:n.ignoreShake}),{points:r,offset:a,targetOffset:o,overflow:l}}function me(e,t,n){var i=n.target||t;return ve(e,oe(i),n,!function(e,t){var n=ae(e,t),i=oe(e);return!n||i.left+i.width<=n.left||i.top+i.height<=n.top||i.left>=n.right||i.top>=n.bottom}(i,n.overflow&&n.overflow.alwaysByViewport))}function ge(e,t,n){var i,r,a=te.getDocument(e),o=a.defaultView||a.parentWindow,s=te.getWindowScrollLeft(o),c=te.getWindowScrollTop(o),l=te.viewportWidth(o),u=te.viewportHeight(o);i="pageX"in t?t.pageX:s+t.clientX,r="pageY"in t?t.pageY:c+t.clientY;var h=i>=0&&i<=s+l&&r>=0&&r<=c+u;return ve(e,{left:i,top:r,width:0,height:0},function(e){for(var t=1;t1){var r="";i=e("div",{class:r},[this.$slots.default])}else i=this.$slots.default[0];return i}},Ve={props:{hiddenClassName:u.a.string.def(""),prefixCls:u.a.string,visible:u.a.bool},render:function(){var e=arguments[0],t=this.$props,n=t.prefixCls,i=t.visible,r=t.hiddenClassName,a={on:Object(d.k)(this)};return e("div",Me()([a,{class:i?"":r}]),[e(Te,{class:n+"-content",attrs:{visible:i}},[this.$slots.default])])}},Pe=n(47),je=n(4),He={name:"VCTriggerPopup",mixins:[je.a],props:{visible:u.a.bool,getClassNameFromAlign:u.a.func,getRootDomNode:u.a.func,align:u.a.any,destroyPopupOnHide:u.a.bool,prefixCls:u.a.string,getContainer:u.a.func,transitionName:u.a.string,animation:u.a.any,maskAnimation:u.a.string,maskTransitionName:u.a.string,mask:u.a.bool,zIndex:u.a.number,popupClassName:u.a.any,popupStyle:u.a.object.def((function(){return{}})),stretch:u.a.string,point:u.a.shape({pageX:u.a.number,pageY:u.a.number})},data:function(){return this.domEl=null,{stretchChecked:!1,targetWidth:void 0,targetHeight:void 0}},mounted:function(){var e=this;this.$nextTick((function(){e.rootNode=e.getPopupDomNode(),e.setStretchSize()}))},updated:function(){var e=this;this.$nextTick((function(){e.setStretchSize()}))},beforeDestroy:function(){this.$el.parentNode?this.$el.parentNode.removeChild(this.$el):this.$el.remove&&this.$el.remove()},methods:{onAlign:function(e,t){var n=this.$props.getClassNameFromAlign(t);this.currentAlignClassName!==n&&(this.currentAlignClassName=n,e.className=this.getClassName(n));var i=Object(d.k)(this);i.align&&i.align(e,t)},setStretchSize:function(){var e=this.$props,t=e.stretch,n=e.getRootDomNode,i=e.visible,r=this.$data,a=r.stretchChecked,o=r.targetHeight,s=r.targetWidth;if(t&&i){var c=n();if(c){var l=c.offsetHeight,u=c.offsetWidth;o===l&&s===u&&a||this.setState({stretchChecked:!0,targetHeight:l,targetWidth:u})}}else a&&this.setState({stretchChecked:!1})},getPopupDomNode:function(){return this.$refs.popupInstance?this.$refs.popupInstance.$el:null},getTargetElement:function(){return this.$props.getRootDomNode()},getAlignTarget:function(){var e=this.$props.point;return e||this.getTargetElement},getMaskTransitionName:function(){var e=this.$props,t=e.maskTransitionName,n=e.maskAnimation;return!t&&n&&(t=e.prefixCls+"-"+n),t},getTransitionName:function(){var e=this.$props,t=e.transitionName,n=e.animation;return t||("string"==typeof n?t=""+n:n&&n.props&&n.props.name&&(t=n.props.name)),t},getClassName:function(e){return this.$props.prefixCls+" "+this.$props.popupClassName+" "+e},getPopupElement:function(){var e=this,t=this.$createElement,n=this.$props,i=this.$slots,r=this.getTransitionName,o=this.$data,s=o.stretchChecked,c=o.targetHeight,l=o.targetWidth,u=n.align,h=n.visible,f=n.prefixCls,p=n.animation,v=n.popupStyle,m=n.getClassNameFromAlign,b=n.destroyPopupOnHide,y=n.stretch,C=this.getClassName(this.currentAlignClassName||m(u));h||(this.currentAlignClassName=null);var x={};y&&(-1!==y.indexOf("height")?x.height="number"==typeof c?c+"px":c:-1!==y.indexOf("minHeight")&&(x.minHeight="number"==typeof c?c+"px":c),-1!==y.indexOf("width")?x.width="number"==typeof l?l+"px":l:-1!==y.indexOf("minWidth")&&(x.minWidth="number"==typeof l?l+"px":l),s||setTimeout((function(){e.$refs.alignInstance&&e.$refs.alignInstance.forceAlign()}),0));var k={props:{prefixCls:f,visible:h},class:C,on:Object(d.k)(this),ref:"popupInstance",style:a()({},x,v,this.getZIndexStyle())},z={props:{appear:!0,css:!1}},w=r(),S=!!w,O={beforeEnter:function(){},enter:function(t,n){e.$nextTick((function(){e.$refs.alignInstance?e.$refs.alignInstance.$nextTick((function(){e.domEl=t,Object(Pe.a)(t,w+"-enter",n)})):n()}))},beforeLeave:function(){e.domEl=null},leave:function(e,t){Object(Pe.a)(e,w+"-leave",t)}};if("object"===(void 0===p?"undefined":g()(p))){S=!0;var M=p.on,T=void 0===M?{}:M,V=p.props,P=void 0===V?{}:V;z.props=a()({},z.props,P),z.on=a()({},O,T)}else z.on=O;return S||(z={}),t("transition",z,b?[h?t(Se,{attrs:{target:this.getAlignTarget(),monitorWindowResize:!0,align:u},key:"popup",ref:"alignInstance",on:{align:this.onAlign}},[t(Ve,k,[i.default])]):null]:[t(Se,{directives:[{name:"show",value:h}],attrs:{target:this.getAlignTarget(),monitorWindowResize:!0,disabled:!h,align:u},key:"popup",ref:"alignInstance",on:{align:this.onAlign}},[t(Ve,k,[i.default])])])},getZIndexStyle:function(){var e={},t=this.$props;return void 0!==t.zIndex&&(e.zIndex=t.zIndex),e},getMaskElement:function(){var e=this.$createElement,t=this.$props,n=null;if(t.mask){var i=this.getMaskTransitionName();n=e(Te,{directives:[{name:"show",value:t.visible}],style:this.getZIndexStyle(),key:"mask",class:t.prefixCls+"-mask",attrs:{visible:t.visible}}),i&&(n=e("transition",{attrs:{appear:!0,name:i}},[n]))}return n}},render:function(){var e=arguments[0],t=this.getMaskElement,n=this.getPopupElement;return e("div",[t(),n()])}};function _e(e,t,n){return n?e[0]===t[0]:e[0]===t[0]&&e[1]===t[1]}function Le(){}var Fe={props:{autoMount:u.a.bool.def(!0),autoDestroy:u.a.bool.def(!0),visible:u.a.bool,forceRender:u.a.bool.def(!1),parent:u.a.any,getComponent:u.a.func.isRequired,getContainer:u.a.func.isRequired,children:u.a.func.isRequired},mounted:function(){this.autoMount&&this.renderComponent()},updated:function(){this.autoMount&&this.renderComponent()},beforeDestroy:function(){this.autoDestroy&&this.removeContainer()},methods:{removeContainer:function(){this.container&&(this._component&&this._component.$destroy(),this.container.parentNode.removeChild(this.container),this.container=null,this._component=null)},renderComponent:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=arguments[1],n=this.visible,i=this.forceRender,r=this.getContainer,a=this.parent,o=this;if(n||a._component||a.$refs._component||i){var s=this.componentEl;this.container||(this.container=r(),s=document.createElement("div"),this.componentEl=s,this.container.appendChild(s));var c={component:o.getComponent(e)};this._component?this._component.setComponent(c):this._component=new this.$root.constructor({el:s,parent:o,data:{_com:c},mounted:function(){this.$nextTick((function(){t&&t.call(o)}))},updated:function(){this.$nextTick((function(){t&&t.call(o)}))},methods:{setComponent:function(e){this.$data._com=e}},render:function(){return this.$data._com.component}})}}},render:function(){return this.children({renderComponent:this.renderComponent,removeContainer:this.removeContainer})}};s.a.use(l.a,{name:"ant-ref"});var Ee=["click","mousedown","touchstart","mouseenter","mouseleave","focus","blur","contextmenu"],Ae={name:"Trigger",mixins:[je.a],props:{action:u.a.oneOfType([u.a.string,u.a.arrayOf(u.a.string)]).def([]),showAction:u.a.any.def([]),hideAction:u.a.any.def([]),getPopupClassNameFromAlign:u.a.any.def((function(){return""})),afterPopupVisibleChange:u.a.func.def(Le),popup:u.a.any,popupStyle:u.a.object.def((function(){return{}})),prefixCls:u.a.string.def("rc-trigger-popup"),popupClassName:u.a.string.def(""),popupPlacement:u.a.string,builtinPlacements:u.a.object,popupTransitionName:u.a.oneOfType([u.a.string,u.a.object]),popupAnimation:u.a.any,mouseEnterDelay:u.a.number.def(0),mouseLeaveDelay:u.a.number.def(.1),zIndex:u.a.number,focusDelay:u.a.number.def(0),blurDelay:u.a.number.def(.15),getPopupContainer:u.a.func,getDocument:u.a.func.def((function(){return window.document})),forceRender:u.a.bool,destroyPopupOnHide:u.a.bool.def(!1),mask:u.a.bool.def(!1),maskClosable:u.a.bool.def(!0),popupAlign:u.a.object.def((function(){return{}})),popupVisible:u.a.bool,defaultPopupVisible:u.a.bool.def(!1),maskTransitionName:u.a.oneOfType([u.a.string,u.a.object]),maskAnimation:u.a.string,stretch:u.a.string,alignPoint:u.a.bool},provide:function(){return{vcTriggerContext:this}},inject:{vcTriggerContext:{default:function(){return{}}},savePopupRef:{default:function(){return Le}},dialogContext:{default:function(){return null}}},data:function(){var e=this,t=this.$props,n=void 0;return n=Object(d.s)(this,"popupVisible")?!!t.popupVisible:!!t.defaultPopupVisible,Ee.forEach((function(t){e["fire"+t]=function(n){e.fireEvents(t,n)}})),{prevPopupVisible:n,sPopupVisible:n,point:null}},watch:{popupVisible:function(e){void 0!==e&&(this.prevPopupVisible=this.sPopupVisible,this.sPopupVisible=e)}},deactivated:function(){this.setPopupVisible(!1)},mounted:function(){var e=this;this.$nextTick((function(){e.renderComponent(null),e.updatedCal()}))},updated:function(){var e=this;this.renderComponent(null,(function(){e.sPopupVisible!==e.prevPopupVisible&&e.afterPopupVisibleChange(e.sPopupVisible),e.prevPopupVisible=e.sPopupVisible})),this.$nextTick((function(){e.updatedCal()}))},beforeDestroy:function(){this.clearDelayTimer(),this.clearOutsideHandler(),clearTimeout(this.mouseDownTimeout)},methods:{updatedCal:function(){var e=this.$props;if(this.$data.sPopupVisible){var t=void 0;this.clickOutsideHandler||!this.isClickToHide()&&!this.isContextmenuToShow()||(t=e.getDocument(),this.clickOutsideHandler=Object(p.a)(t,"mousedown",this.onDocumentClick)),this.touchOutsideHandler||(t=t||e.getDocument(),this.touchOutsideHandler=Object(p.a)(t,"touchstart",this.onDocumentClick)),!this.contextmenuOutsideHandler1&&this.isContextmenuToShow()&&(t=t||e.getDocument(),this.contextmenuOutsideHandler1=Object(p.a)(t,"scroll",this.onContextmenuClose)),!this.contextmenuOutsideHandler2&&this.isContextmenuToShow()&&(this.contextmenuOutsideHandler2=Object(p.a)(window,"blur",this.onContextmenuClose))}else this.clearOutsideHandler()},onMouseenter:function(e){var t=this.$props.mouseEnterDelay;this.fireEvents("mouseenter",e),this.delaySetPopupVisible(!0,t,t?null:e)},onMouseMove:function(e){this.fireEvents("mousemove",e),this.setPoint(e)},onMouseleave:function(e){this.fireEvents("mouseleave",e),this.delaySetPopupVisible(!1,this.$props.mouseLeaveDelay)},onPopupMouseenter:function(){this.clearDelayTimer()},onPopupMouseleave:function(e){e&&e.relatedTarget&&!e.relatedTarget.setTimeout&&this._component&&this._component.getPopupDomNode&&Object(h.a)(this._component.getPopupDomNode(),e.relatedTarget)||this.delaySetPopupVisible(!1,this.$props.mouseLeaveDelay)},onFocus:function(e){this.fireEvents("focus",e),this.clearDelayTimer(),this.isFocusToShow()&&(this.focusTime=Date.now(),this.delaySetPopupVisible(!0,this.$props.focusDelay))},onMousedown:function(e){this.fireEvents("mousedown",e),this.preClickTime=Date.now()},onTouchstart:function(e){this.fireEvents("touchstart",e),this.preTouchTime=Date.now()},onBlur:function(e){Object(h.a)(e.target,e.relatedTarget||document.activeElement)||(this.fireEvents("blur",e),this.clearDelayTimer(),this.isBlurToHide()&&this.delaySetPopupVisible(!1,this.$props.blurDelay))},onContextmenu:function(e){e.preventDefault(),this.fireEvents("contextmenu",e),this.setPopupVisible(!0,e)},onContextmenuClose:function(){this.isContextmenuToShow()&&this.close()},onClick:function(e){if(this.fireEvents("click",e),this.focusTime){var t=void 0;if(this.preClickTime&&this.preTouchTime?t=Math.min(this.preClickTime,this.preTouchTime):this.preClickTime?t=this.preClickTime:this.preTouchTime&&(t=this.preTouchTime),Math.abs(t-this.focusTime)<20)return;this.focusTime=0}this.preClickTime=0,this.preTouchTime=0,this.isClickToShow()&&(this.isClickToHide()||this.isBlurToHide())&&e&&e.preventDefault&&e.preventDefault(),e&&e.domEvent&&e.domEvent.preventDefault();var n=!this.$data.sPopupVisible;(this.isClickToHide()&&!n||n&&this.isClickToShow())&&this.setPopupVisible(!this.$data.sPopupVisible,e)},onPopupMouseDown:function(){var e=this,t=this.vcTriggerContext,n=void 0===t?{}:t;this.hasPopupMouseDown=!0,clearTimeout(this.mouseDownTimeout),this.mouseDownTimeout=setTimeout((function(){e.hasPopupMouseDown=!1}),0),n.onPopupMouseDown&&n.onPopupMouseDown.apply(n,arguments)},onDocumentClick:function(e){if(!this.$props.mask||this.$props.maskClosable){var t=e.target,n=this.$el;Object(h.a)(n,t)||this.hasPopupMouseDown||this.close()}},getPopupDomNode:function(){return this._component&&this._component.getPopupDomNode?this._component.getPopupDomNode():null},getRootDomNode:function(){return this.$el},handleGetPopupClassFromAlign:function(e){var t=[],n=this.$props,i=n.popupPlacement,r=n.builtinPlacements,a=n.prefixCls,o=n.alignPoint,s=n.getPopupClassNameFromAlign;return i&&r&&t.push(function(e,t,n,i){var r=n.points;for(var a in e)if(e.hasOwnProperty(a)&&_e(e[a].points,r,i))return t+"-placement-"+a;return""}(r,a,e,o)),s&&t.push(s(e)),t.join(" ")},getPopupAlign:function(){var e=this.$props,t=e.popupPlacement,n=e.popupAlign,i=e.builtinPlacements;return t&&i?function(e,t,n){var i=e[t]||{};return a()({},i,n)}(i,t,n):n},savePopup:function(e){this._component=e,this.savePopupRef(e)},getComponent:function(){var e=this.$createElement,t={};this.isMouseEnterToShow()&&(t.mouseenter=this.onPopupMouseenter),this.isMouseLeaveToHide()&&(t.mouseleave=this.onPopupMouseleave),t.mousedown=this.onPopupMouseDown,t.touchstart=this.onPopupMouseDown;var n=this.handleGetPopupClassFromAlign,i=this.getRootDomNode,r=this.getContainer,o=this.$props,s=o.prefixCls,c=o.destroyPopupOnHide,l=o.popupClassName,u=o.action,h=o.popupAnimation,f=o.popupTransitionName,p=o.popupStyle,v=o.mask,m=o.maskAnimation,g=o.maskTransitionName,b=o.zIndex,y=o.stretch,C=o.alignPoint,x=this.$data,k=x.sPopupVisible,z=x.point,w={props:{prefixCls:s,destroyPopupOnHide:c,visible:k,point:C&&z,action:u,align:this.getPopupAlign(),animation:h,getClassNameFromAlign:n,stretch:y,getRootDomNode:i,mask:v,zIndex:b,transitionName:f,maskAnimation:m,maskTransitionName:g,getContainer:r,popupClassName:l,popupStyle:p},on:a()({align:Object(d.k)(this).popupAlign||Le},t),directives:[{name:"ant-ref",value:this.savePopup}]};return e(He,w,[Object(d.g)(this,"popup")])},getContainer:function(){var e=this.$props,t=this.dialogContext,n=document.createElement("div");return n.style.position="absolute",n.style.top="0",n.style.left="0",n.style.width="100%",(e.getPopupContainer?e.getPopupContainer(this.$el,t):e.getDocument().body).appendChild(n),this.popupContainer=n,n},setPopupVisible:function(e,t){var n=this.alignPoint,i=this.sPopupVisible;if(this.clearDelayTimer(),i!==e){Object(d.s)(this,"popupVisible")||this.setState({sPopupVisible:e,prevPopupVisible:i});var r=Object(d.k)(this);r.popupVisibleChange&&r.popupVisibleChange(e)}n&&t&&this.setPoint(t)},setPoint:function(e){this.$props.alignPoint&&e&&this.setState({point:{pageX:e.pageX,pageY:e.pageY}})},delaySetPopupVisible:function(e,t,n){var i=this,r=1e3*t;if(this.clearDelayTimer(),r){var a=n?{pageX:n.pageX,pageY:n.pageY}:null;this.delayTimer=Object(f.b)((function(){i.setPopupVisible(e,a),i.clearDelayTimer()}),r)}else this.setPopupVisible(e,n)},clearDelayTimer:function(){this.delayTimer&&(Object(f.a)(this.delayTimer),this.delayTimer=null)},clearOutsideHandler:function(){this.clickOutsideHandler&&(this.clickOutsideHandler.remove(),this.clickOutsideHandler=null),this.contextmenuOutsideHandler1&&(this.contextmenuOutsideHandler1.remove(),this.contextmenuOutsideHandler1=null),this.contextmenuOutsideHandler2&&(this.contextmenuOutsideHandler2.remove(),this.contextmenuOutsideHandler2=null),this.touchOutsideHandler&&(this.touchOutsideHandler.remove(),this.touchOutsideHandler=null)},createTwoChains:function(e){var t=function(){},n=Object(d.k)(this);return this.childOriginEvents[e]&&n[e]?this["fire"+e]:t=this.childOriginEvents[e]||n[e]||t},isClickToShow:function(){var e=this.$props,t=e.action,n=e.showAction;return-1!==t.indexOf("click")||-1!==n.indexOf("click")},isContextmenuToShow:function(){var e=this.$props,t=e.action,n=e.showAction;return-1!==t.indexOf("contextmenu")||-1!==n.indexOf("contextmenu")},isClickToHide:function(){var e=this.$props,t=e.action,n=e.hideAction;return-1!==t.indexOf("click")||-1!==n.indexOf("click")},isMouseEnterToShow:function(){var e=this.$props,t=e.action,n=e.showAction;return-1!==t.indexOf("hover")||-1!==n.indexOf("mouseenter")},isMouseLeaveToHide:function(){var e=this.$props,t=e.action,n=e.hideAction;return-1!==t.indexOf("hover")||-1!==n.indexOf("mouseleave")},isFocusToShow:function(){var e=this.$props,t=e.action,n=e.showAction;return-1!==t.indexOf("focus")||-1!==n.indexOf("focus")},isBlurToHide:function(){var e=this.$props,t=e.action,n=e.hideAction;return-1!==t.indexOf("focus")||-1!==n.indexOf("blur")},forcePopupAlign:function(){this.$data.sPopupVisible&&this._component&&this._component.$refs.alignInstance&&this._component.$refs.alignInstance.forceAlign()},fireEvents:function(e,t){this.childOriginEvents[e]&&this.childOriginEvents[e](t),this.__emit(e,t)},close:function(){this.setPopupVisible(!1)}},render:function(){var e=this,t=arguments[0],n=this.sPopupVisible,i=Object(d.c)(this.$slots.default),r=this.$props,a=r.forceRender,o=r.alignPoint;i.length>1&&Object(v.a)(!1,"Trigger $slots.default.length > 1, just support only one default",!0);var s=i[0];this.childOriginEvents=Object(d.h)(s);var c={props:{},nativeOn:{},key:"trigger"};return this.isContextmenuToShow()?c.nativeOn.contextmenu=this.onContextmenu:c.nativeOn.contextmenu=this.createTwoChains("contextmenu"),this.isClickToHide()||this.isClickToShow()?(c.nativeOn.click=this.onClick,c.nativeOn.mousedown=this.onMousedown,c.nativeOn.touchstart=this.onTouchstart):(c.nativeOn.click=this.createTwoChains("click"),c.nativeOn.mousedown=this.createTwoChains("mousedown"),c.nativeOn.touchstart=this.createTwoChains("onTouchstart")),this.isMouseEnterToShow()?(c.nativeOn.mouseenter=this.onMouseenter,o&&(c.nativeOn.mousemove=this.onMouseMove)):c.nativeOn.mouseenter=this.createTwoChains("mouseenter"),this.isMouseLeaveToHide()?c.nativeOn.mouseleave=this.onMouseleave:c.nativeOn.mouseleave=this.createTwoChains("mouseleave"),this.isFocusToShow()||this.isBlurToHide()?(c.nativeOn.focus=this.onFocus,c.nativeOn.blur=this.onBlur):(c.nativeOn.focus=this.createTwoChains("focus"),c.nativeOn.blur=function(t){!t||t.relatedTarget&&Object(h.a)(t.target,t.relatedTarget)||e.createTwoChains("blur")(t)}),this.trigger=Object(Ce.a)(s,c),t(Fe,{attrs:{parent:this,visible:n,autoMount:!1,forceRender:a,getComponent:this.getComponent,getContainer:this.getContainer,children:function(t){var n=t.renderComponent;return e.renderComponent=n,e.trigger}}})}};t.a=Ae},function(e,t,n){var i=n(34),r=n(349),a=n(198),o=Math.max,s=Math.min;e.exports=function(e,t,n){var c,l,u,h,d,f,p=0,v=!1,m=!1,g=!0;if("function"!=typeof e)throw new TypeError("Expected a function");function b(t){var n=c,i=l;return c=l=void 0,p=t,h=e.apply(i,n)}function y(e){return p=e,d=setTimeout(x,t),v?b(e):h}function C(e){var n=e-f;return void 0===f||n>=t||n<0||m&&e-p>=u}function x(){var e=r();if(C(e))return k(e);d=setTimeout(x,function(e){var n=t-(e-f);return m?s(n,u-(e-p)):n}(e))}function k(e){return d=void 0,g&&c?b(e):(c=l=void 0,h)}function z(){var e=r(),n=C(e);if(c=arguments,l=this,f=e,n){if(void 0===d)return y(f);if(m)return clearTimeout(d),d=setTimeout(x,t),b(f)}return void 0===d&&(d=setTimeout(x,t)),h}return t=a(t)||0,i(n)&&(v=!!n.leading,u=(m="maxWait"in n)?o(a(n.maxWait)||0,t):u,g="trailing"in n?!!n.trailing:g),z.cancel=function(){void 0!==d&&clearTimeout(d),p=0,c=f=l=d=void 0},z.flush=function(){return void 0===d?h:k(r())},z}},function(e,t,n){"use strict";var i=n(3),r=n.n(i),a=n(2),o=n.n(a),s=n(9),c=n(12),l=n.n(c),u=n(0),h=n(29),d={adjustX:1,adjustY:1},f=[0,0],p={left:{points:["cr","cl"],overflow:d,offset:[-4,0],targetOffset:f},right:{points:["cl","cr"],overflow:d,offset:[4,0],targetOffset:f},top:{points:["bc","tc"],overflow:d,offset:[0,-4],targetOffset:f},bottom:{points:["tc","bc"],overflow:d,offset:[0,4],targetOffset:f},topLeft:{points:["bl","tl"],overflow:d,offset:[0,-4],targetOffset:f},leftTop:{points:["tr","tl"],overflow:d,offset:[-4,0],targetOffset:f},topRight:{points:["br","tr"],overflow:d,offset:[0,-4],targetOffset:f},rightTop:{points:["tl","tr"],overflow:d,offset:[4,0],targetOffset:f},bottomRight:{points:["tr","br"],overflow:d,offset:[0,4],targetOffset:f},rightBottom:{points:["bl","br"],overflow:d,offset:[4,0],targetOffset:f},bottomLeft:{points:["tl","bl"],overflow:d,offset:[0,4],targetOffset:f},leftBottom:{points:["br","bl"],overflow:d,offset:[-4,0],targetOffset:f}},v={props:{prefixCls:u.a.string,overlay:u.a.any,trigger:u.a.any},updated:function(){var e=this.trigger;e&&e.forcePopupAlign()},render:function(){var e=arguments[0],t=this.overlay,n=this.prefixCls;return e("div",{class:n+"-inner",attrs:{role:"tooltip"}},["function"==typeof t?t():t])}},m=n(1);function g(){}var b={props:{trigger:u.a.any.def(["hover"]),defaultVisible:u.a.bool,visible:u.a.bool,placement:u.a.string.def("right"),transitionName:u.a.oneOfType([u.a.string,u.a.object]),animation:u.a.any,afterVisibleChange:u.a.func.def((function(){})),overlay:u.a.any,overlayStyle:u.a.object,overlayClassName:u.a.string,prefixCls:u.a.string.def("rc-tooltip"),mouseEnterDelay:u.a.number.def(0),mouseLeaveDelay:u.a.number.def(.1),getTooltipContainer:u.a.func,destroyTooltipOnHide:u.a.bool.def(!1),align:u.a.object.def((function(){return{}})),arrowContent:u.a.any.def(null),tipId:u.a.string,builtinPlacements:u.a.object},methods:{getPopupElement:function(){var e=this.$createElement,t=this.$props,n=t.prefixCls,i=t.tipId;return[e("div",{class:n+"-arrow",key:"arrow"},[Object(m.g)(this,"arrowContent")]),e(v,{key:"content",attrs:{trigger:this.$refs.trigger,prefixCls:n,id:i,overlay:Object(m.g)(this,"overlay")}})]},getPopupDomNode:function(){return this.$refs.trigger.getPopupDomNode()}},render:function(e){var t=Object(m.l)(this),n=t.overlayClassName,i=t.trigger,r=t.mouseEnterDelay,a=t.mouseLeaveDelay,s=t.overlayStyle,c=t.prefixCls,u=t.afterVisibleChange,d=t.transitionName,f=t.animation,v=t.placement,b=t.align,y=t.destroyTooltipOnHide,C=t.defaultVisible,x=t.getTooltipContainer,k=l()(t,["overlayClassName","trigger","mouseEnterDelay","mouseLeaveDelay","overlayStyle","prefixCls","afterVisibleChange","transitionName","animation","placement","align","destroyTooltipOnHide","defaultVisible","getTooltipContainer"]),z=o()({},k);Object(m.s)(this,"visible")&&(z.popupVisible=this.$props.visible);var w=Object(m.k)(this),S={props:o()({popupClassName:n,prefixCls:c,action:i,builtinPlacements:p,popupPlacement:v,popupAlign:b,getPopupContainer:x,afterPopupVisibleChange:u,popupTransitionName:d,popupAnimation:f,defaultPopupVisible:C,destroyPopupOnHide:y,mouseLeaveDelay:a,popupStyle:s,mouseEnterDelay:r},z),on:o()({},w,{popupVisibleChange:w.visibleChange||g,popupAlign:w.popupAlign||g}),ref:"trigger"};return e(h.a,S,[e("template",{slot:"popup"},[this.getPopupElement(e)]),this.$slots.default])}},y={adjustX:1,adjustY:1},C={adjustX:0,adjustY:0},x=[0,0];function k(e){return"boolean"==typeof e?e?y:C:o()({},C,e)}var z=n(7),w=n(55),S=Object(w.a)(),O={name:"ATooltip",model:{prop:"visible",event:"visibleChange"},props:o()({},S,{title:u.a.any}),inject:{configProvider:{default:function(){return z.a}}},data:function(){return{sVisible:!!this.$props.visible||!!this.$props.defaultVisible}},watch:{visible:function(e){this.sVisible=e}},methods:{onVisibleChange:function(e){Object(m.s)(this,"visible")||(this.sVisible=!this.isNoTitle()&&e),this.isNoTitle()||this.$emit("visibleChange",e)},getPopupDomNode:function(){return this.$refs.tooltip.getPopupDomNode()},getPlacements:function(){var e=this.$props,t=e.builtinPlacements,n=e.arrowPointAtCenter,i=e.autoAdjustOverflow;return t||function(e){var t=e.arrowWidth,n=void 0===t?5:t,i=e.horizontalArrowShift,r=void 0===i?16:i,a=e.verticalArrowShift,s=void 0===a?12:a,c=e.autoAdjustOverflow,l=void 0===c||c,u={left:{points:["cr","cl"],offset:[-4,0]},right:{points:["cl","cr"],offset:[4,0]},top:{points:["bc","tc"],offset:[0,-4]},bottom:{points:["tc","bc"],offset:[0,4]},topLeft:{points:["bl","tc"],offset:[-(r+n),-4]},leftTop:{points:["tr","cl"],offset:[-4,-(s+n)]},topRight:{points:["br","tc"],offset:[r+n,-4]},rightTop:{points:["tl","cr"],offset:[4,-(s+n)]},bottomRight:{points:["tr","bc"],offset:[r+n,4]},rightBottom:{points:["bl","cr"],offset:[4,s+n]},bottomLeft:{points:["tl","bc"],offset:[-(r+n),4]},leftBottom:{points:["br","cl"],offset:[-4,s+n]}};return Object.keys(u).forEach((function(t){u[t]=e.arrowPointAtCenter?o()({},u[t],{overflow:k(l),targetOffset:x}):o()({},p[t],{overflow:k(l)}),u[t].ignoreShake=!0})),u}({arrowPointAtCenter:n,verticalArrowShift:8,autoAdjustOverflow:i})},getDisabledCompatibleChildren:function(e){var t=this.$createElement,n=e.componentOptions&&e.componentOptions.Ctor.options||{};if((!0===n.__ANT_BUTTON||!0===n.__ANT_SWITCH||!0===n.__ANT_CHECKBOX)&&(e.componentOptions.propsData.disabled||""===e.componentOptions.propsData.disabled)||"button"===e.tag&&e.data&&e.data.attrs&&void 0!==e.data.attrs.disabled){var i=function(e,t){var n={},i=o()({},e);return t.forEach((function(t){e&&t in e&&(n[t]=e[t],delete i[t])})),{picked:n,omitted:i}}(Object(m.q)(e),["position","left","right","top","bottom","float","display","zIndex"]),r=i.picked,a=i.omitted,c=o()({display:"inline-block"},r,{cursor:"not-allowed",width:e.componentOptions.propsData.block?"100%":null}),l=o()({},a,{pointerEvents:"none"});return t("span",{style:c,class:Object(m.f)(e)},[Object(s.a)(e,{style:l,class:null})])}return e},isNoTitle:function(){var e=Object(m.g)(this,"title");return!e&&0!==e},getOverlay:function(){var e=Object(m.g)(this,"title");return 0===e?e:e||""},onPopupAlign:function(e,t){var n=this.getPlacements(),i=Object.keys(n).filter((function(e){return n[e].points[0]===t.points[0]&&n[e].points[1]===t.points[1]}))[0];if(i){var r=e.getBoundingClientRect(),a={top:"50%",left:"50%"};i.indexOf("top")>=0||i.indexOf("Bottom")>=0?a.top=r.height-t.offset[1]+"px":(i.indexOf("Top")>=0||i.indexOf("bottom")>=0)&&(a.top=-t.offset[1]+"px"),i.indexOf("left")>=0||i.indexOf("Right")>=0?a.left=r.width-t.offset[0]+"px":(i.indexOf("right")>=0||i.indexOf("Left")>=0)&&(a.left=-t.offset[0]+"px"),e.style.transformOrigin=a.left+" "+a.top}}},render:function(){var e=arguments[0],t=this.$props,n=this.$data,i=this.$slots,a=t.prefixCls,c=t.openClassName,l=t.getPopupContainer,u=this.configProvider.getPopupContainer,h=this.configProvider.getPrefixCls,d=h("tooltip",a),f=(i.default||[]).filter((function(e){return e.tag||""!==e.text.trim()}));f=1===f.length?f[0]:f;var p=n.sVisible;if(!Object(m.s)(this,"visible")&&this.isNoTitle()&&(p=!1),!f)return null;var v=this.getDisabledCompatibleChildren(Object(m.w)(f)?f:e("span",[f])),g=r()({},c||d+"-open",!0),y={props:o()({},t,{prefixCls:d,getTooltipContainer:l||u,builtinPlacements:this.getPlacements(),overlay:this.getOverlay(),visible:p}),ref:"tooltip",on:o()({},Object(m.k)(this),{visibleChange:this.onVisibleChange,popupAlign:this.onPopupAlign})};return e(b,y,[p?Object(s.a)(v,{class:g}):v])}},M=n(10);O.install=function(e){e.use(M.a),e.component(O.name,O)};t.a=O},function(e,t,n){"use strict";n.d(t,"a",(function(){return a})),n.d(t,"b",(function(){return o}));var i=["moz","ms","webkit"];var r=function(){if("undefined"==typeof window)return function(){};if(window.requestAnimationFrame)return window.requestAnimationFrame.bind(window);var e,t=i.filter((function(e){return e+"RequestAnimationFrame"in window}))[0];return t?window[t+"RequestAnimationFrame"]:(e=0,function(t){var n=(new Date).getTime(),i=Math.max(0,16-(n-e)),r=window.setTimeout((function(){t(n+i)}),i);return e=n+i,r})}(),a=function(e){return function(e){if("undefined"==typeof window)return null;if(window.cancelAnimationFrame)return window.cancelAnimationFrame(e);var t=i.filter((function(e){return e+"CancelAnimationFrame"in window||e+"CancelRequestAnimationFrame"in window}))[0];return t?(window[t+"CancelAnimationFrame"]||window[t+"CancelRequestAnimationFrame"]).call(this,e):clearTimeout(e)}(e.id)},o=function(e,t){var n=Date.now();var i={id:r((function a(){Date.now()-n>=t?e.call():i.id=r(a)}))};return i}},function(e,t,n){var i=n(217);e.exports=function(e,t,n){return null==e?e:i(e,t,n)}},function(e,t){e.exports=function(e){var t=typeof e;return null!=e&&("object"==t||"function"==t)}},function(e,t){var n=Array.isArray;e.exports=n},function(e,t,n){var i=n(64),r=n(138),a=n(45),o=Function.prototype,s=Object.prototype,c=o.toString,l=s.hasOwnProperty,u=c.call(Object);e.exports=function(e){if(!a(e)||"[object Object]"!=i(e))return!1;var t=r(e);if(null===t)return!0;var n=l.call(t,"constructor")&&t.constructor;return"function"==typeof n&&n instanceof n&&c.call(n)==u}},function(e,t,n){"use strict";var i=n(85);t.a=i.a},function(e,t,n){"use strict";n.d(t,"a",(function(){return a})),n.d(t,"b",(function(){return o})),n.d(t,"c",(function(){return s})),n.d(t,"d",(function(){return c})),n.d(t,"g",(function(){return l})),n.d(t,"e",(function(){return h})),n.d(t,"f",(function(){return d}));var i=n(2),r=n.n(i);function a(){return!0}function o(e){return r()({},e,{lastModified:e.lastModified,lastModifiedDate:e.lastModifiedDate,name:e.name,size:e.size,type:e.type,uid:e.uid,percent:0,originFileObj:e})}function s(){var e=.1;return function(t){var n=t;return n>=.98||(n+=e,(e-=.01)<.001&&(e=.001)),n}}function c(e,t){var n=void 0!==e.uid?"uid":"name";return t.filter((function(t){return t[n]===e[n]}))[0]}function l(e,t){var n=void 0!==e.uid?"uid":"name",i=t.filter((function(t){return t[n]!==e[n]}));return i.length===t.length?null:i}var u=function(e){return!!e&&0===e.indexOf("image/")},h=function(e){if(u(e.type))return!0;var t=e.thumbUrl||e.url,n=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"",t=e.split("/"),n=t[t.length-1],i=n.split(/#|\?/)[0];return(/\.[^./\\]*$/.exec(i)||[""])[0]}(t);return!(!/^data:image\//.test(t)&&!/(webp|svg|png|gif|jpg|jpeg|jfif|bmp|dpg|ico)$/i.test(n))||!/^data:/.test(t)&&!n};function d(e){return new Promise((function(t){if(u(e.type)){var n=document.createElement("canvas");n.width=200,n.height=200,n.style.cssText="position: fixed; left: 0; top: 0; width: 200px; height: 200px; z-index: 9999; display: none;",document.body.appendChild(n);var i=n.getContext("2d"),r=new Image;r.onload=function(){var e=r.width,a=r.height,o=200,s=200,c=0,l=0;e0},e.prototype.connect_=function(){i&&!this.connected_&&(document.addEventListener("transitionend",this.onTransitionEnd_),window.addEventListener("resize",this.refresh),s?(this.mutationsObserver_=new MutationObserver(this.refresh),this.mutationsObserver_.observe(document,{attributes:!0,childList:!0,characterData:!0,subtree:!0})):(document.addEventListener("DOMSubtreeModified",this.refresh),this.mutationEventsAdded_=!0),this.connected_=!0)},e.prototype.disconnect_=function(){i&&this.connected_&&(document.removeEventListener("transitionend",this.onTransitionEnd_),window.removeEventListener("resize",this.refresh),this.mutationsObserver_&&this.mutationsObserver_.disconnect(),this.mutationEventsAdded_&&document.removeEventListener("DOMSubtreeModified",this.refresh),this.mutationsObserver_=null,this.mutationEventsAdded_=!1,this.connected_=!1)},e.prototype.onTransitionEnd_=function(e){var t=e.propertyName,n=void 0===t?"":t;o.some((function(e){return!!~n.indexOf(e)}))&&this.refresh()},e.getInstance=function(){return this.instance_||(this.instance_=new e),this.instance_},e.instance_=null,e}(),l=function(e,t){for(var n=0,i=Object.keys(t);n0},e}(),x="undefined"!=typeof WeakMap?new WeakMap:new n,k=function e(t){if(!(this instanceof e))throw new TypeError("Cannot call a class as a function.");if(!arguments.length)throw new TypeError("1 argument required, but only 0 present.");var n=c.getInstance(),i=new C(t,n,this);x.set(this,i)};["observe","unobserve","disconnect"].forEach((function(e){k.prototype[e]=function(){var t;return(t=x.get(this))[e].apply(t,arguments)}}));var z=void 0!==r.ResizeObserver?r.ResizeObserver:k;t.a=z}).call(this,n(137))},function(e,t,n){"use strict";t.__esModule=!0,t.default=function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}},function(e,t,n){"use strict";var i=n(0),r=i.a.oneOf(["hover","focus","click","contextmenu"]);t.a=function(){return{trigger:i.a.oneOfType([r,i.a.arrayOf(r)]).def("hover"),visible:i.a.bool,defaultVisible:i.a.bool,placement:i.a.oneOf(["top","left","right","bottom","topLeft","topRight","bottomLeft","bottomRight","leftTop","leftBottom","rightTop","rightBottom"]).def("top"),transitionName:i.a.string.def("zoom-big-fast"),overlayStyle:i.a.object.def((function(){return{}})),overlayClassName:i.a.string,prefixCls:i.a.string,mouseEnterDelay:i.a.number.def(.1),mouseLeaveDelay:i.a.number.def(.1),getPopupContainer:i.a.func,arrowPointAtCenter:i.a.bool.def(!1),autoAdjustOverflow:i.a.oneOfType([i.a.bool,i.a.object]).def(!0),destroyTooltipOnHide:i.a.bool.def(!1),align:i.a.object.def((function(){return{}})),builtinPlacements:i.a.object}}},function(e,t,n){"use strict";n.d(t,"a",(function(){return s})),n.d(t,"b",(function(){return c}));var i=n(2),r=n.n(i),a=n(85),o=r()({},a.a.Modal);function s(e){o=e?r()({},o,e):r()({},a.a.Modal)}function c(){return o}},function(e,t,n){try{var i=n(182)}catch(e){i=n(182)}var r=/\s+/,a=Object.prototype.toString;function o(e){if(!e||!e.nodeType)throw new Error("A DOM element reference is required");this.el=e,this.list=e.classList}e.exports=function(e){return new o(e)},o.prototype.add=function(e){if(this.list)return this.list.add(e),this;var t=this.array();return~i(t,e)||t.push(e),this.el.className=t.join(" "),this},o.prototype.remove=function(e){if("[object RegExp]"==a.call(e))return this.removeMatching(e);if(this.list)return this.list.remove(e),this;var t=this.array(),n=i(t,e);return~n&&t.splice(n,1),this.el.className=t.join(" "),this},o.prototype.removeMatching=function(e){for(var t=this.array(),n=0;n0&&void 0!==arguments[0]?arguments[0]:{};return Object.keys(e).reduce((function(t,n){var i=e[n];switch(n){case"class":t.className=i,delete t.class;break;default:t[n]=i}return t}),{})}var f=function(){function e(){o()(this,e),this.collection={}}return c()(e,[{key:"clear",value:function(){this.collection={}}},{key:"delete",value:function(e){return delete this.collection[e]}},{key:"get",value:function(e){return this.collection[e]}},{key:"has",value:function(e){return Boolean(this.collection[e])}},{key:"set",value:function(e,t){return this.collection[e]=t,this}},{key:"size",get:function(){return Object.keys(this.collection).length}}]),e}();function p(e,t,n,i){return e(t.tag,i?r()({key:n},i,{attrs:r()({},d(t.attrs),i.attrs)}):{key:n,attrs:r()({},d(t.attrs))},(t.children||[]).map((function(i,r){return p(e,i,n+"-"+t.tag+"-"+r)})))}function v(e){return Object(l.generate)(e)[0]}function m(e,t){switch(t){case"fill":return e+"-fill";case"outline":return e+"-o";case"twotone":return e+"-twotone";default:throw new TypeError("Unknown theme type: "+t+", name: "+e)}}}).call(this,n(101))},function(e,t){var n={}.hasOwnProperty;e.exports=function(e,t){return n.call(e,t)}},function(e,t,n){var i=n(74),r=n(274),a=n(275),o=i?i.toStringTag:void 0;e.exports=function(e){return null==e?void 0===e?"[object Undefined]":"[object Null]":o&&o in Object(e)?r(e):a(e)}},function(e,t,n){var i=n(306),r=n(309);e.exports=function(e,t){var n=r(e,t);return i(n)?n:void 0}},function(e,t){e.exports=function(e,t){return e===t||e!=e&&t!=t}},function(e,t,n){"use strict";var i=n(2),r=n.n(i),a=n(46),o=n(68),s={lang:r()({placeholder:"Select date",rangePlaceholder:["Start date","End date"]},a.a),timePickerLocale:r()({},o.a)};t.a=s},function(e,t,n){"use strict";t.a={placeholder:"Select time"}},function(e,t,n){e.exports=function(){"use strict";return function(e,t,n){(n=n||{}).childrenKeyName=n.childrenKeyName||"children";var i=e||[],r=[],a=0;do{var o=i.filter((function(e){return t(e,a)}))[0];if(!o)break;r.push(o),i=o[n.childrenKeyName]||[],a+=1}while(i.length>0);return r}}()},function(e,t,n){var i=n(51),r=n(90);e.exports=n(52)?function(e,t,n){return i.f(e,t,r(1,n))}:function(e,t,n){return e[t]=n,e}},function(e,t,n){var i=n(88);e.exports=function(e){if(!i(e))throw TypeError(e+" is not an object!");return e}},function(e,t,n){var i=n(172),r=n(127);e.exports=function(e){return i(r(e))}},function(e,t){e.exports={}},function(e,t,n){var i=n(40).Symbol;e.exports=i},function(e,t,n){var i=n(142),r=n(143);e.exports=function(e,t,n,a){var o=!n;n||(n={});for(var s=-1,c=t.length;++s100?100:e}var b=function(e){var t=e.from,n=void 0===t?"#1890ff":t,i=e.to,r=void 0===i?"#1890ff":i,a=e.direction,o=void 0===a?"to right":a,s=p()(e,["from","to","direction"]);return 0!==Object.keys(s).length?{backgroundImage:"linear-gradient("+o+", "+function(e){var t=[],n=!0,i=!1,r=void 0;try{for(var a,o=Object.entries(e)[Symbol.iterator]();!(n=(a=o.next()).done);n=!0){var s=a.value,c=m()(s,2),l=c[0],u=c[1],h=parseFloat(l.replace(/%/g,""));if(isNaN(h))return{};t.push({key:h,value:u})}}catch(e){i=!0,r=e}finally{try{!n&&o.return&&o.return()}finally{if(i)throw r}}return(t=t.sort((function(e,t){return e.key-t.key}))).map((function(e){var t=e.key;return e.value+" "+t+"%"})).join(", ")}(s)+")"}:{backgroundImage:"linear-gradient("+o+", "+n+", "+r+")"}},y={functional:!0,render:function(e,t){var n=t.props,i=t.children,r=n.prefixCls,a=n.percent,s=n.successPercent,c=n.strokeWidth,l=n.size,u=n.strokeColor,h=n.strokeLinecap,d=void 0;d=u&&"string"!=typeof u?b(u):{background:u};var f=o()({width:g(a)+"%",height:(c||("small"===l?6:8))+"px",background:u,borderRadius:"square"===h?0:"100px"},d),p={width:g(s)+"%",height:(c||("small"===l?6:8))+"px",borderRadius:"square"===h?0:""},v=void 0!==s?e("div",{class:r+"-success-bg",style:p}):null;return e("div",[e("div",{class:r+"-outer"},[e("div",{class:r+"-inner"},[e("div",{class:r+"-bg",style:f}),v])]),i])}},C=n(6),x=n.n(C),k=n(18),z=n.n(k),w=n(25),S=n.n(w);var O=function(e){return{mixins:[e],updated:function(){var e=this,t=Date.now(),n=!1;Object.keys(this.paths).forEach((function(i){var r=e.paths[i];if(r){n=!0;var a=r.style;a.transitionDuration=".3s, .3s, .3s, .06s",e.prevTimeStamp&&t-e.prevTimeStamp<100&&(a.transitionDuration="0s, 0s")}})),n&&(this.prevTimeStamp=Date.now())}}},M=l.a.oneOfType([l.a.number,l.a.string]),T={percent:l.a.oneOfType([M,l.a.arrayOf(M)]),prefixCls:l.a.string,strokeColor:l.a.oneOfType([l.a.string,l.a.arrayOf(l.a.oneOfType([l.a.string,l.a.object])),l.a.object]),strokeLinecap:l.a.oneOf(["butt","round","square"]),strokeWidth:M,trailColor:l.a.string,trailWidth:M},V=o()({},T,{gapPosition:l.a.oneOf(["top","bottom","left","right"]),gapDegree:l.a.oneOfType([l.a.number,l.a.string,l.a.bool])}),P=o()({},{percent:0,prefixCls:"rc-progress",strokeColor:"#2db7f5",strokeLinecap:"round",strokeWidth:1,trailColor:"#D9D9D9",trailWidth:1},{gapPosition:"top"});z.a.use(S.a,{name:"ant-ref"});var j=0;function H(e){return+e.replace("%","")}function _(e){return Array.isArray(e)?e:[e]}function L(e,t,n,i){var r=arguments.length>4&&void 0!==arguments[4]?arguments[4]:0,a=arguments[5],o=50-i/2,s=0,c=-o,l=0,u=-2*o;switch(a){case"left":s=-o,c=0,l=2*o,u=0;break;case"right":s=o,c=0,l=-2*o,u=0;break;case"bottom":c=o,u=2*o}var h="M 50,50 m "+s+","+c+"\n a "+o+","+o+" 0 1 1 "+l+","+-u+"\n a "+o+","+o+" 0 1 1 "+-l+","+u,d=2*Math.PI*o,f={stroke:n,strokeDasharray:t/100*(d-r)+"px "+d+"px",strokeDashoffset:"-"+(r/2+e/100*(d-r))+"px",transition:"stroke-dashoffset .3s ease 0s, stroke-dasharray .3s ease 0s, stroke .3s, stroke-width .06s ease .3s"};return{pathString:h,pathStyle:f}}var F=O({props:Object(u.t)(V,P),created:function(){this.paths={},this.gradientId=j,j+=1},methods:{getStokeList:function(){var e=this,t=this.$createElement,n=this.$props,i=n.prefixCls,r=n.percent,a=n.strokeColor,o=n.strokeWidth,s=n.strokeLinecap,c=n.gapDegree,l=n.gapPosition,u=_(r),h=_(a),d=0;return u.map((function(n,r){var a=h[r]||h[h.length-1],u="[object Object]"===Object.prototype.toString.call(a)?"url(#"+i+"-gradient-"+e.gradientId+")":"",f=L(d,n,a,o,c,l),p=f.pathString,v=f.pathStyle;return d+=n,t("path",{key:r,attrs:{d:p,stroke:u,"stroke-linecap":s,"stroke-width":0===n?0:o,"fill-opacity":"0"},class:i+"-circle-path",style:v,directives:[{name:"ant-ref",value:function(t){e.paths[r]=t}}]})}))}},render:function(){var e=arguments[0],t=this.$props,n=t.prefixCls,i=t.strokeWidth,r=t.trailWidth,a=t.gapDegree,o=t.gapPosition,s=t.trailColor,c=t.strokeLinecap,l=t.strokeColor,u=p()(t,["prefixCls","strokeWidth","trailWidth","gapDegree","gapPosition","trailColor","strokeLinecap","strokeColor"]),h=L(0,100,s,i,a,o),d=h.pathString,f=h.pathStyle;delete u.percent;var v=_(l),m=v.find((function(e){return"[object Object]"===Object.prototype.toString.call(e)})),g={attrs:{d:d,stroke:s,"stroke-linecap":c,"stroke-width":r||i,"fill-opacity":"0"},class:n+"-circle-trail",style:f};return e("svg",x()([{class:n+"-circle",attrs:{viewBox:"0 0 100 100"}},u]),[m&&e("defs",[e("linearGradient",{attrs:{id:n+"-gradient-"+this.gradientId,x1:"100%",y1:"0%",x2:"0%",y2:"0%"}},[Object.keys(m).sort((function(e,t){return H(e)-H(t)})).map((function(t,n){return e("stop",{key:n,attrs:{offset:t,"stop-color":m[t]}})}))])]),e("path",g),this.getStokeList().reverse()])}}),E={normal:"#108ee9",exception:"#ff5500",success:"#87d068"};function A(e){var t=e.percent,n=e.successPercent,i=g(t);if(!n)return i;var r=g(n);return[n,g(i-r)]}var D={functional:!0,render:function(e,t){var n,i,a,o,s,c=t.props,l=t.children,u=c.prefixCls,h=c.width,d=c.strokeWidth,f=c.trailColor,p=c.strokeLinecap,v=c.gapPosition,m=c.gapDegree,g=c.type,b=h||120,y={width:"number"==typeof b?b+"px":b,height:"number"==typeof b?b+"px":b,fontSize:.15*b+6},C=d||6,x=v||"dashboard"===g&&"bottom"||"top",k=m||"dashboard"===g&&75,z=(a=(i=c).progressStatus,o=i.successPercent,s=i.strokeColor||E[a],o?[E.success,s]:s),w="[object Object]"===Object.prototype.toString.call(z);return e("div",{class:(n={},r()(n,u+"-inner",!0),r()(n,u+"-circle-gradient",w),n),style:y},[e(F,{attrs:{percent:A(c),strokeWidth:C,trailWidth:C,strokeColor:z,strokeLinecap:p,trailColor:f,prefixCls:u,gapDegree:k,gapPosition:x}}),l])}},$=["normal","exception","active","success"],I=l.a.oneOf(["line","circle","dashboard"]),R=l.a.oneOf(["default","small"]),N={prefixCls:l.a.string,type:I,percent:l.a.number,successPercent:l.a.number,format:l.a.func,status:l.a.oneOf($),showInfo:l.a.bool,strokeWidth:l.a.number,strokeLinecap:l.a.oneOf(["butt","round","square"]),strokeColor:l.a.oneOfType([l.a.string,l.a.object]),trailColor:l.a.string,width:l.a.number,gapDegree:l.a.number,gapPosition:l.a.oneOf(["top","bottom","left","right"]),size:R},K={name:"AProgress",props:Object(u.t)(N,{type:"line",percent:0,showInfo:!0,trailColor:"#f3f3f3",size:"default",gapDegree:0,strokeLinecap:"round"}),inject:{configProvider:{default:function(){return h.a}}},methods:{getPercentNumber:function(){var e=this.$props,t=e.successPercent,n=e.percent,i=void 0===n?0:n;return parseInt(void 0!==t?t.toString():i.toString(),10)},getProgressStatus:function(){var e=this.$props.status;return $.indexOf(e)<0&&this.getPercentNumber()>=100?"success":e||"normal"},renderProcessInfo:function(e,t){var n=this.$createElement,i=this.$props,r=i.showInfo,a=i.format,o=i.type,s=i.percent,c=i.successPercent;if(!r)return null;var l=void 0,u=a||this.$scopedSlots.format||function(e){return e+"%"},h="circle"===o||"dashboard"===o?"":"-circle";return a||this.$scopedSlots.format||"exception"!==t&&"success"!==t?l=u(g(s),g(c)):"exception"===t?l=n(d.a,{attrs:{type:"close"+h,theme:"line"===o?"filled":"outlined"}}):"success"===t&&(l=n(d.a,{attrs:{type:"check"+h,theme:"line"===o?"filled":"outlined"}})),n("span",{class:e+"-text",attrs:{title:"string"==typeof l?l:void 0}},[l])}},render:function(){var e,t=arguments[0],n=Object(u.l)(this),i=n.prefixCls,a=n.size,s=n.type,l=n.showInfo,h=this.configProvider.getPrefixCls,d=h("progress",i),f=this.getProgressStatus(),p=this.renderProcessInfo(d,f),v=void 0;if("line"===s){var m={props:o()({},n,{prefixCls:d})};v=t(y,m,[p])}else if("circle"===s||"dashboard"===s){var g={props:o()({},n,{prefixCls:d,progressStatus:f})};v=t(D,g,[p])}var b=c()(d,(e={},r()(e,d+"-"+("dashboard"===s?"circle":s),!0),r()(e,d+"-status-"+f,!0),r()(e,d+"-show-info",l),r()(e,d+"-"+a,a),e)),C={on:Object(u.k)(this),class:b};return t("div",C,[v])}},Y=n(10);K.install=function(e){e.use(Y.a),e.component(K.name,K)};t.a=K},function(e,t,n){"use strict";t.a={items_per_page:"/ page",jump_to:"Go to",jump_to_confirm:"confirm",page:"",prev_page:"Previous Page",next_page:"Next Page",prev_5:"Previous 5 Pages",next_5:"Next 5 Pages",prev_3:"Previous 3 Pages",next_3:"Next 3 Pages"}},function(e,t,n){"use strict";var i=n(67);t.a=i.a},function(e,t,n){var i=n(171),r=n(131);e.exports=Object.keys||function(e){return i(e,r)}},function(e,t){e.exports=!0},function(e,t){var n=0,i=Math.random();e.exports=function(e){return"Symbol(".concat(void 0===e?"":e,")_",(++n+i).toString(36))}},function(e,t){t.f={}.propertyIsEnumerable},function(e,t,n){var i=n(127);e.exports=function(e){return Object(i(e))}},function(e,t,n){"use strict";var i=n(255)(!0);n(174)(String,"String",(function(e){this._t=String(e),this._i=0}),(function(){var e,t=this._t,n=this._i;return n>=t.length?{value:void 0,done:!0}:(e=i(t,n),this._i+=e.length,{value:e,done:!1})}))},function(e,t){var n,i,r=e.exports={};function a(){throw new Error("setTimeout has not been defined")}function o(){throw new Error("clearTimeout has not been defined")}function s(e){if(n===setTimeout)return setTimeout(e,0);if((n===a||!n)&&setTimeout)return n=setTimeout,setTimeout(e,0);try{return n(e,0)}catch(t){try{return n.call(null,e,0)}catch(t){return n.call(this,e,0)}}}!function(){try{n="function"==typeof setTimeout?setTimeout:a}catch(e){n=a}try{i="function"==typeof clearTimeout?clearTimeout:o}catch(e){i=o}}();var c,l=[],u=!1,h=-1;function d(){u&&c&&(u=!1,c.length?l=c.concat(l):h=-1,l.length&&f())}function f(){if(!u){var e=s(d);u=!0;for(var t=l.length;t;){for(c=l,l=[];++h1)for(var n=1;n-1&&e%1==0&&e0;var a=function(e,t){for(var n=Object.create(null),i=e.split(","),r=0;r1),t})),s(e,u(e),n),l&&(n=r(n,7,c));for(var h=t.length;h--;)a(n,t[h]);return n}));e.exports=h},function(e,t,n){var i=n(368),r=n(110),a=n(111),o=a&&a.isRegExp,s=o?r(o):i;e.exports=s},function(e,t,n){"use strict";(function(e){function n(){return(n=Object.assign||function(e){for(var t=1;t=a)return e;switch(e){case"%s":return String(t[i++]);case"%d":return Number(t[i++]);case"%j":try{return JSON.stringify(t[i++])}catch(e){return"[Circular]"}break;default:return e}}));return o}return r}function d(e,t){return null==e||(!("array"!==t||!Array.isArray(e)||e.length)||!(!function(e){return"string"===e||"url"===e||"hex"===e||"email"===e||"date"===e||"pattern"===e}(t)||"string"!=typeof e||e))}function f(e,t,n){var i=0,r=e.length;!function a(o){if(o&&o.length)n(o);else{var s=i;i+=1,s()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/,url:new RegExp("^(?!mailto:)(?:(?:http|https|ftp)://|//)(?:\\S+(?::\\S*)?@)?(?:(?:(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}(?:\\.(?:[0-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))|(?:(?:[a-z\\u00a1-\\uffff0-9]+-*)*[a-z\\u00a1-\\uffff0-9]+)(?:\\.(?:[a-z\\u00a1-\\uffff0-9]+-*)*[a-z\\u00a1-\\uffff0-9]+)*(?:\\.(?:[a-z\\u00a1-\\uffff]{2,})))|localhost)(?::\\d{2,5})?(?:(/|\\?|#)[^\\s]*)?$","i"),hex:/^#?([a-f0-9]{6}|[a-f0-9]{3})$/i},C={integer:function(e){return C.number(e)&&parseInt(e,10)===e},float:function(e){return C.number(e)&&!C.integer(e)},array:function(e){return Array.isArray(e)},regexp:function(e){if(e instanceof RegExp)return!0;try{return!!new RegExp(e)}catch(e){return!1}},date:function(e){return"function"==typeof e.getTime&&"function"==typeof e.getMonth&&"function"==typeof e.getYear&&!isNaN(e.getTime())},number:function(e){return!isNaN(e)&&"number"==typeof e},object:function(e){return"object"==typeof e&&!C.array(e)},method:function(e){return"function"==typeof e},email:function(e){return"string"==typeof e&&!!e.match(y.email)&&e.length<255},url:function(e){return"string"==typeof e&&!!e.match(y.url)},hex:function(e){return"string"==typeof e&&!!e.match(y.hex)}};var x={required:b,whitespace:function(e,t,n,i,r){(/^\s+$/.test(t)||""===t)&&i.push(h(r.messages.whitespace,e.fullField))},type:function(e,t,n,i,r){if(e.required&&void 0===t)b(e,t,n,i,r);else{var a=e.type;["integer","float","array","regexp","object","method","email","number","date","url","hex"].indexOf(a)>-1?C[a](t)||i.push(h(r.messages.types[a],e.fullField,e.type)):a&&typeof t!==e.type&&i.push(h(r.messages.types[a],e.fullField,e.type))}},range:function(e,t,n,i,r){var a="number"==typeof e.len,o="number"==typeof e.min,s="number"==typeof e.max,c=t,l=null,u="number"==typeof t,d="string"==typeof t,f=Array.isArray(t);if(u?l="number":d?l="string":f&&(l="array"),!l)return!1;f&&(c=t.length),d&&(c=t.replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g,"_").length),a?c!==e.len&&i.push(h(r.messages[l].len,e.fullField,e.len)):o&&!s&&ce.max?i.push(h(r.messages[l].max,e.fullField,e.max)):o&&s&&(ce.max)&&i.push(h(r.messages[l].range,e.fullField,e.min,e.max))},enum:function(e,t,n,i,r){e.enum=Array.isArray(e.enum)?e.enum:[],-1===e.enum.indexOf(t)&&i.push(h(r.messages.enum,e.fullField,e.enum.join(", ")))},pattern:function(e,t,n,i,r){if(e.pattern)if(e.pattern instanceof RegExp)e.pattern.lastIndex=0,e.pattern.test(t)||i.push(h(r.messages.pattern.mismatch,e.fullField,t,e.pattern));else if("string"==typeof e.pattern){new RegExp(e.pattern).test(t)||i.push(h(r.messages.pattern.mismatch,e.fullField,t,e.pattern))}}};function k(e,t,n,i,r){var a=e.type,o=[];if(e.required||!e.required&&i.hasOwnProperty(e.field)){if(d(t,a)&&!e.required)return n();x.required(e,t,i,o,r,a),d(t,a)||x.type(e,t,i,o,r)}n(o)}var z={string:function(e,t,n,i,r){var a=[];if(e.required||!e.required&&i.hasOwnProperty(e.field)){if(d(t,"string")&&!e.required)return n();x.required(e,t,i,a,r,"string"),d(t,"string")||(x.type(e,t,i,a,r),x.range(e,t,i,a,r),x.pattern(e,t,i,a,r),!0===e.whitespace&&x.whitespace(e,t,i,a,r))}n(a)},method:function(e,t,n,i,r){var a=[];if(e.required||!e.required&&i.hasOwnProperty(e.field)){if(d(t)&&!e.required)return n();x.required(e,t,i,a,r),void 0!==t&&x.type(e,t,i,a,r)}n(a)},number:function(e,t,n,i,r){var a=[];if(e.required||!e.required&&i.hasOwnProperty(e.field)){if(""===t&&(t=void 0),d(t)&&!e.required)return n();x.required(e,t,i,a,r),void 0!==t&&(x.type(e,t,i,a,r),x.range(e,t,i,a,r))}n(a)},boolean:function(e,t,n,i,r){var a=[];if(e.required||!e.required&&i.hasOwnProperty(e.field)){if(d(t)&&!e.required)return n();x.required(e,t,i,a,r),void 0!==t&&x.type(e,t,i,a,r)}n(a)},regexp:function(e,t,n,i,r){var a=[];if(e.required||!e.required&&i.hasOwnProperty(e.field)){if(d(t)&&!e.required)return n();x.required(e,t,i,a,r),d(t)||x.type(e,t,i,a,r)}n(a)},integer:function(e,t,n,i,r){var a=[];if(e.required||!e.required&&i.hasOwnProperty(e.field)){if(d(t)&&!e.required)return n();x.required(e,t,i,a,r),void 0!==t&&(x.type(e,t,i,a,r),x.range(e,t,i,a,r))}n(a)},float:function(e,t,n,i,r){var a=[];if(e.required||!e.required&&i.hasOwnProperty(e.field)){if(d(t)&&!e.required)return n();x.required(e,t,i,a,r),void 0!==t&&(x.type(e,t,i,a,r),x.range(e,t,i,a,r))}n(a)},array:function(e,t,n,i,r){var a=[];if(e.required||!e.required&&i.hasOwnProperty(e.field)){if(d(t,"array")&&!e.required)return n();x.required(e,t,i,a,r,"array"),d(t,"array")||(x.type(e,t,i,a,r),x.range(e,t,i,a,r))}n(a)},object:function(e,t,n,i,r){var a=[];if(e.required||!e.required&&i.hasOwnProperty(e.field)){if(d(t)&&!e.required)return n();x.required(e,t,i,a,r),void 0!==t&&x.type(e,t,i,a,r)}n(a)},enum:function(e,t,n,i,r){var a=[];if(e.required||!e.required&&i.hasOwnProperty(e.field)){if(d(t)&&!e.required)return n();x.required(e,t,i,a,r),void 0!==t&&x.enum(e,t,i,a,r)}n(a)},pattern:function(e,t,n,i,r){var a=[];if(e.required||!e.required&&i.hasOwnProperty(e.field)){if(d(t,"string")&&!e.required)return n();x.required(e,t,i,a,r),d(t,"string")||x.pattern(e,t,i,a,r)}n(a)},date:function(e,t,n,i,r){var a=[];if(e.required||!e.required&&i.hasOwnProperty(e.field)){if(d(t,"date")&&!e.required)return n();var o;if(x.required(e,t,i,a,r),!d(t,"date"))o=t instanceof Date?t:new Date(t),x.type(e,o,i,a,r),o&&x.range(e,o.getTime(),i,a,r)}n(a)},url:k,hex:k,email:k,required:function(e,t,n,i,r){var a=[],o=Array.isArray(t)?"array":typeof t;x.required(e,t,i,a,r,o),n(a)},any:function(e,t,n,i,r){var a=[];if(e.required||!e.required&&i.hasOwnProperty(e.field)){if(d(t)&&!e.required)return n();x.required(e,t,i,a,r)}n(a)}};function w(){return{default:"Validation error on field %s",required:"%s is required",enum:"%s must be one of %s",whitespace:"%s cannot be empty",date:{format:"%s date %s is invalid for format %s",parse:"%s date could not be parsed, %s is invalid ",invalid:"%s date %s is invalid"},types:{string:"%s is not a %s",method:"%s is not a %s (function)",array:"%s is not an %s",object:"%s is not an %s",number:"%s is not a %s",date:"%s is not a %s",boolean:"%s is not a %s",integer:"%s is not an %s",float:"%s is not a %s",regexp:"%s is not a valid %s",email:"%s is not a valid %s",url:"%s is not a valid %s",hex:"%s is not a valid %s"},string:{len:"%s must be exactly %s characters",min:"%s must be at least %s characters",max:"%s cannot be longer than %s characters",range:"%s must be between %s and %s characters"},number:{len:"%s must equal %s",min:"%s cannot be less than %s",max:"%s cannot be greater than %s",range:"%s must be between %s and %s"},array:{len:"%s must be exactly %s in length",min:"%s cannot be less than %s in length",max:"%s cannot be greater than %s in length",range:"%s must be between %s and %s in length"},pattern:{mismatch:"%s value %s does not match pattern %s"},clone:function(){var e=JSON.parse(JSON.stringify(this));return e.clone=this.clone,e}}}var S=w();function O(e){this.rules=null,this._messages=S,this.define(e)}O.prototype={messages:function(e){return e&&(this._messages=g(w(),e)),this._messages},define:function(e){if(!e)throw new Error("Cannot configure a schema with no rules");if("object"!=typeof e||Array.isArray(e))throw new Error("Rules must be an object");var t,n;for(t in this.rules={},e)e.hasOwnProperty(t)&&(n=e[t],this.rules[t]=Array.isArray(n)?n:[n])},validate:function(e,t,i){var r=this;void 0===t&&(t={}),void 0===i&&(i=function(){});var a,o,s=e,c=t,l=i;if("function"==typeof c&&(l=c,c={}),!this.rules||0===Object.keys(this.rules).length)return l&&l(),Promise.resolve();if(c.messages){var d=this.messages();d===S&&(d=w()),g(d,c.messages),c.messages=d}else c.messages=this.messages();var f={};(c.keys||Object.keys(this.rules)).forEach((function(t){a=r.rules[t],o=s[t],a.forEach((function(i){var a=i;"function"==typeof a.transform&&(s===e&&(s=n({},s)),o=s[t]=a.transform(o)),(a="function"==typeof a?{validator:a}:n({},a)).validator=r.getValidationMethod(a),a.field=t,a.fullField=a.fullField||t,a.type=r.getType(a),a.validator&&(f[t]=f[t]||[],f[t].push({rule:a,value:o,source:s,field:t}))}))}));var p={};return v(f,c,(function(e,t){var i,r=e.rule,a=!("object"!==r.type&&"array"!==r.type||"object"!=typeof r.fields&&"object"!=typeof r.defaultField);function o(e,t){return n(n({},t),{},{fullField:r.fullField+"."+e})}function s(i){void 0===i&&(i=[]);var s=i;if(Array.isArray(s)||(s=[s]),!c.suppressWarning&&s.length&&O.warning("async-validator:",s),s.length&&r.message&&(s=[].concat(r.message)),s=s.map(m(r)),c.first&&s.length)return p[r.field]=1,t(s);if(a){if(r.required&&!e.value)return r.message?s=[].concat(r.message).map(m(r)):c.error&&(s=[c.error(r,h(c.messages.required,r.field))]),t(s);var l={};if(r.defaultField)for(var u in e.value)e.value.hasOwnProperty(u)&&(l[u]=r.defaultField);for(var d in l=n(n({},l),e.rule.fields))if(l.hasOwnProperty(d)){var f=Array.isArray(l[d])?l[d]:[l[d]];l[d]=f.map(o.bind(null,d))}var v=new O(l);v.messages(c.messages),e.rule.options&&(e.rule.options.messages=c.messages,e.rule.options.error=c.error),v.validate(e.value,e.rule.options||c,(function(e){var n=[];s&&s.length&&n.push.apply(n,s),e&&e.length&&n.push.apply(n,e),t(n.length?n:null)}))}else t(s)}a=a&&(r.required||!r.required&&e.value),r.field=e.field,r.asyncValidator?i=r.asyncValidator(r,e.value,s,e.source,c):r.validator&&(!0===(i=r.validator(r,e.value,s,e.source,c))?s():!1===i?s(r.message||r.field+" fails"):i instanceof Array?s(i):i instanceof Error&&s(i.message)),i&&i.then&&i.then((function(){return s()}),(function(e){return s(e)}))}),(function(e){!function(e){var t,n,i,r=[],a={};for(t=0;t0?i:n)(e)}},function(e,t,n){var i=n(130)("keys"),r=n(97);e.exports=function(e){return i[e]||(i[e]=r(e))}},function(e,t,n){var i=n(44),r=n(50),a=r["__core-js_shared__"]||(r["__core-js_shared__"]={});(e.exports=function(e,t){return a[e]||(a[e]=void 0!==t?t:{})})("versions",[]).push({version:i.version,mode:n(96)?"pure":"global",copyright:"© 2019 Denis Pushkarev (zloirock.ru)"})},function(e,t){e.exports="constructor,hasOwnProperty,isPrototypeOf,propertyIsEnumerable,toLocaleString,toString,valueOf".split(",")},function(e,t){t.f=Object.getOwnPropertySymbols},function(e,t,n){var i=n(51).f,r=n(63),a=n(39)("toStringTag");e.exports=function(e,t,n){e&&!r(e=n?e:e.prototype,a)&&i(e,a,{configurable:!0,value:t})}},function(e,t,n){n(260);for(var i=n(50),r=n(70),a=n(73),o=n(39)("toStringTag"),s="CSSRuleList,CSSStyleDeclaration,CSSValueList,ClientRectList,DOMRectList,DOMStringList,DOMTokenList,DataTransferItemList,FileList,HTMLAllCollection,HTMLCollection,HTMLFormElement,HTMLSelectElement,MediaList,MimeTypeArray,NamedNodeMap,NodeList,PaintRequestList,Plugin,PluginArray,SVGLengthList,SVGNumberList,SVGPathSegList,SVGPointList,SVGStringList,SVGTransformList,SourceBufferList,StyleSheetList,TextTrackCueList,TextTrackList,TouchList".split(","),c=0;c-1&&e%1==0&&e<=9007199254740991}},function(e,t){var n=Object.prototype;e.exports=function(e){var t=e&&e.constructor;return e===("function"==typeof t&&t.prototype||n)}},function(e,t,n){var i=n(334),r=n(189),a=Object.prototype.propertyIsEnumerable,o=Object.getOwnPropertySymbols,s=o?function(e){return null==e?[]:(e=Object(e),i(o(e),(function(t){return a.call(e,t)})))}:r;e.exports=s},function(e,t){e.exports=function(e,t){for(var n=-1,i=t.length,r=e.length;++n0&&(t.percent=t.loaded/t.total*100),e.onProgress(t)});var n=new window.FormData;e.data&&Object.keys(e.data).forEach((function(t){var i=e.data[t];Array.isArray(i)?i.forEach((function(e){n.append(t+"[]",e)})):n.append(t,e.data[t])})),n.append(e.filename,e.file),t.onerror=function(t){e.onError(t)},t.onload=function(){if(t.status<200||t.status>=300)return e.onError(function(e, t){var n="cannot "+e.method+" "+e.action+" "+t.status+"'",i=new Error(n);return i.status=t.status,i.method=e.method,i.url=e.action,i}(e,t),p(t));e.onSuccess(p(t),t)},t.open(e.method,e.action,!0),e.withCredentials&&"withCredentials"in t&&(t.withCredentials=!0);var i=e.headers||{};for(var r in null!==i["X-Requested-With"]&&t.setRequestHeader("X-Requested-With","XMLHttpRequest"),i)i.hasOwnProperty(r)&&null!==i[r]&&t.setRequestHeader(r,i[r]);return t.send(n),{abort:function(){t.abort()}}}var m=+new Date,g=0;function b(){return"vc-upload-"+m+"-"+ ++g}var y=function(e, t){if(e&&t){var n=Array.isArray(t)?t:t.split(","),i=e.name||"",r=e.type||"",a=r.replace(/\/.*$/,"");return n.some((function(e){var t,n,o=e.trim();return"."===o.charAt(0)?(t=i.toLowerCase(),n=o.toLowerCase(),-1!==t.indexOf(n,t.length-n.length)):/\/\*$/.test(o)?a===o.replace(/\/.*$/,""):r===o}))}return!0};var C=function(e, t, n){var i=function e(i, r){r=r||"",i.isFile?i.file((function(e){n(e)&&(i.fullPath&&!e.webkitRelativePath&&(Object.defineProperties(e,{webkitRelativePath:{writable:!0}}),e.webkitRelativePath=i.fullPath.replace(/^\//,""),Object.defineProperties(e,{webkitRelativePath:{writable:!1}})),t([e]))})):i.isDirectory&&function(e, t){var n=e.createReader(),i=[];!function e(){n.readEntries((function(n){var r=Array.prototype.slice.apply(n);i=i.concat(r),!r.length?t(i):e()}))}()}(i,(function(t){t.forEach((function(t){e(t,""+r+i.name+"/")}))}))},r=!0,a=!1,o=void 0;try{for(var s,c=e[Symbol.iterator](); !(r=(s=c.next()).done); r=!0){i(s.value.webkitGetAsEntry())}}catch(e){a=!0,o=e}finally{try{!r&&c.return&&c.return()}finally{if(a)throw o}}},x={componentTag:a.a.string,prefixCls:a.a.string,name:a.a.string,multiple:a.a.bool,directory:a.a.bool,disabled:a.a.bool,accept:a.a.string,data:a.a.oneOfType([a.a.object,a.a.func]),action:a.a.oneOfType([a.a.string,a.a.func]),headers:a.a.object,beforeUpload:a.a.func,customRequest:a.a.func,withCredentials:a.a.bool,openFileDialogOnClick:a.a.bool,transformFile:a.a.func,method:a.a.string},k={inheritAttrs:!1,name:"ajaxUploader",mixins:[s.a],props:x,data:function(){return this.reqs={},{uid:b()}},mounted:function(){this._isMounted=!0},beforeDestroy:function(){this._isMounted=!1,this.abort()},methods:{onChange:function(e){var t=e.target.files;this.uploadFiles(t),this.reset()},onClick:function(){var e=this.$refs.fileInputRef;e&&e.click()},onKeyDown:function(e){"Enter"===e.key&&this.onClick()},onFileDrop:function(e){var t=this,n=this.$props.multiple;if(e.preventDefault(),"dragover"!==e.type)if(this.directory)C(e.dataTransfer.items,this.uploadFiles,(function(e){return y(e,t.accept)}));else{var i=h()(Array.prototype.slice.call(e.dataTransfer.files),(function(e){return y(e,t.accept)})),r=i[0],a=i[1];!1===n&&(r=r.slice(0,1)),this.uploadFiles(r),a.length&&this.$emit("reject",a)}},uploadFiles:function(e){var t=this,n=Array.prototype.slice.call(e);n.map((function(e){return e.uid=b(),e})).forEach((function(e){t.upload(e,n)}))},upload:function(e, t){var n=this;if(!this.beforeUpload)return setTimeout((function(){return n.post(e)}),0);var i=this.beforeUpload(e,t);i&&i.then?i.then((function(t){var i=Object.prototype.toString.call(t);return"[object File]"===i||"[object Blob]"===i?n.post(t):n.post(e)})).catch((function(e){console&&console.log(e)})):!1!==i&&setTimeout((function(){return n.post(e)}),0)},post:function(e){var t=this;if(this._isMounted){var n=this.$props,i=n.data,r=n.transformFile,a=void 0===r?function(e){return e}:r;new Promise((function(n){var i=t.action;if("function"==typeof i)return n(i(e));n(i)})).then((function(r){var o=e.uid,s=t.customRequest||v;Promise.resolve(a(e)).catch((function(e){console.error(e)})).then((function(a){"function"==typeof i&&(i=i(e));var c={action:r,filename:t.name,data:i,file:a,headers:t.headers,withCredentials:t.withCredentials,method:n.method||"post",onProgress:function(n){t.$emit("progress",n,e)},onSuccess:function(n, i){delete t.reqs[o],t.$emit("success",n,e,i)},onError:function(n, i){delete t.reqs[o],t.$emit("error",n,i,e)}};t.reqs[o]=s(c),t.$emit("start",e)}))}))}},reset:function(){this.setState({uid:b()})},abort:function(e){var t=this.reqs;if(e){var n=e;e&&e.uid&&(n=e.uid),t[n]&&t[n].abort&&t[n].abort(),delete t[n]}else Object.keys(t).forEach((function(e){t[e]&&t[e].abort&&t[e].abort(),delete t[e]}))}},render:function(){var e,t=arguments[0],n=this.$props,i=this.$attrs,a=n.componentTag,s=n.prefixCls,c=n.disabled,u=n.multiple,h=n.accept,d=n.directory,p=n.openFileDialogOnClick,v=f()((e={},l()(e,s,!0),l()(e,s+"-disabled",c),e)),m=c?{}:{click:p?this.onClick:function(){},keydown:p?this.onKeyDown:function(){},drop:this.onFileDrop,dragover:this.onFileDrop},g={on:r()({},Object(o.k)(this),m),attrs:{role:"button",tabIndex:c?null:"0"},class:v};return t(a,g,[t("input",{attrs:{id:i.id,type:"file",accept:h,directory:d?"directory":null,webkitdirectory:d?"webkitdirectory":null,multiple:u},ref:"fileInputRef",on:{click:function(e){return e.stopPropagation()},change:this.onChange},key:this.uid,style:{display:"none"}}),this.$slots.default])}},z=n(13),w={position:"absolute",top:0,opacity:0,filter:"alpha(opacity=0)",left:0,zIndex:9999},S={mixins:[s.a],props:{componentTag:a.a.string,disabled:a.a.bool,prefixCls:a.a.string,accept:a.a.string,multiple:a.a.bool,data:a.a.oneOfType([a.a.object,a.a.func]),action:a.a.oneOfType([a.a.string,a.a.func]),name:a.a.string},data:function(){return this.file={},{uploading:!1}},methods:{onLoad:function(){if(this.uploading){var e=this.file,t=void 0;try{var n=this.getIframeDocument(),i=n.getElementsByTagName("script")[0];i&&i.parentNode===n.body&&n.body.removeChild(i),t=n.body.innerHTML,this.$emit("success",t,e)}catch(n){Object(z.a)(!1,"cross domain error for Upload. Maybe server should return document.domain script. see Note from https://github.com/react-component/upload"),t="cross-domain",this.$emit("error",n,null,e)}this.endUpload()}},onChange:function(){var e=this,t=this.getFormInputNode(),n=this.file={uid:b(),name:t.value&&t.value.substring(t.value.lastIndexOf("\\")+1,t.value.length)};this.startUpload();var i=this.$props;if(!i.beforeUpload)return this.post(n);var r=i.beforeUpload(n);r&&r.then?r.then((function(){e.post(n)}),(function(){e.endUpload()})):!1!==r?this.post(n):this.endUpload()},getIframeNode:function(){return this.$refs.iframeRef},getIframeDocument:function(){return this.getIframeNode().contentDocument},getFormNode:function(){return this.getIframeDocument().getElementById("form")},getFormInputNode:function(){return this.getIframeDocument().getElementById("input")},getFormDataNode:function(){return this.getIframeDocument().getElementById("data")},getFileForMultiple:function(e){return this.multiple?[e]:e},getIframeHTML:function(e){var t="",n="";if(e){t=' - - - - - - - - - - - - - - - +{{define "js"}} + + + + + + + + + + + + + + + + {{end}} \ No newline at end of file diff --git a/web/html/common/prompt_modal.html b/web/html/common/prompt_modal.html index 99d8acd8..14f65500 100644 --- a/web/html/common/prompt_modal.html +++ b/web/html/common/prompt_modal.html @@ -1,67 +1,67 @@ -{{define "promptModal"}} - - - - - +{{define "promptModal"}} + + + + + {{end}} \ No newline at end of file diff --git a/web/html/common/qrcode_modal.html b/web/html/common/qrcode_modal.html index 4f102556..9a3359d5 100644 --- a/web/html/common/qrcode_modal.html +++ b/web/html/common/qrcode_modal.html @@ -1,59 +1,59 @@ -{{define "qrcodeModal"}} - - - - - +{{define "qrcodeModal"}} + + + + + {{end}} \ No newline at end of file diff --git a/web/html/common/text_modal.html b/web/html/common/text_modal.html index 50a555aa..b2da6160 100644 --- a/web/html/common/text_modal.html +++ b/web/html/common/text_modal.html @@ -1,51 +1,51 @@ -{{define "textModal"}} - - - {{ i18n "download" }} [[ txtModal.fileName ]] - - - - - +{{define "textModal"}} + + + {{ i18n "download" }} [[ txtModal.fileName ]] + + + + + {{end}} \ No newline at end of file diff --git a/web/html/login.html b/web/html/login.html index c94dc5d6..6ecd5d2c 100644 --- a/web/html/login.html +++ b/web/html/login.html @@ -1,90 +1,90 @@ - - -{{template "head" .}} - - - - - - - -

{{ .title }}

-
-
- - - - - - - - - - - - - - - {{ i18n "login" }} - - - - -
-
-
-{{template "js" .}} - - + + +{{template "head" .}} + + + + + + + +

{{ .title }}

+
+
+ + + + + + + + + + + + + + + {{ i18n "login" }} + + + + +
+
+
+{{template "js" .}} + + \ No newline at end of file diff --git a/web/html/xui/common_sider.html b/web/html/xui/common_sider.html index 3518749c..2a837fdc 100644 --- a/web/html/xui/common_sider.html +++ b/web/html/xui/common_sider.html @@ -1,70 +1,70 @@ -{{define "menuItems"}} - - - 系统状态 - - - - 入站列表 - - - - 面板设置 - - - - - - - - - - - 退出登录 - -{{end}} - - -{{define "commonSider"}} - - - {{template "menuItems" .}} - - - -
- -
- - {{template "menuItems" .}} - -
- -{{end}} +{{define "menuItems"}} + + + 系统状态 + + + + 入站列表 + + + + 面板设置 + + + + + + + + + + + 退出登录 + +{{end}} + + +{{define "commonSider"}} + + + {{template "menuItems" .}} + + + +
+ +
+ + {{template "menuItems" .}} + +
+ +{{end}} diff --git a/web/html/xui/component/inbound_info.html b/web/html/xui/component/inbound_info.html index c9d8c25e..2b0e5b9a 100644 --- a/web/html/xui/component/inbound_info.html +++ b/web/html/xui/component/inbound_info.html @@ -1,92 +1,92 @@ -{{define "inboundInfoStream"}} -

传输: [[ inbound.network ]]

- - - - - - - - - - - - - -

- tls域名: [[ inbound.serverName ? inbound.serverName : "无" ]] -

-{{end}} - - -{{define "component/inboundInfoComponent"}} -
-

协议: [[ dbInbound.protocol ]]

-

地址: [[ dbInbound.address ]]

-

端口: [[ dbInbound.port ]]

- - - - - - - - - - - - - - -
-{{end}} - -{{define "component/inboundInfo"}} - +{{define "inboundInfoStream"}} +

传输: [[ inbound.network ]]

+ + + + + + + + + + + + + +

+ tls域名: [[ inbound.serverName ? inbound.serverName : "无" ]] +

+{{end}} + + +{{define "component/inboundInfoComponent"}} +
+

协议: [[ dbInbound.protocol ]]

+

地址: [[ dbInbound.address ]]

+

端口: [[ dbInbound.port ]]

+ + + + + + + + + + + + + + +
+{{end}} + +{{define "component/inboundInfo"}} + {{end}} \ No newline at end of file diff --git a/web/html/xui/component/setting.html b/web/html/xui/component/setting.html index 56ae0ef6..9f8e8cbc 100644 --- a/web/html/xui/component/setting.html +++ b/web/html/xui/component/setting.html @@ -1,32 +1,32 @@ -{{define "component/settingListItem"}} - - - - - - - - - - - - - -{{end}} - -{{define "component/setting"}} - +{{define "component/settingListItem"}} + + + + + + + + + + + + + +{{end}} + +{{define "component/setting"}} + {{end}} \ No newline at end of file diff --git a/web/html/xui/form/inbound.html b/web/html/xui/form/inbound.html index 6f97a0b5..b998f581 100644 --- a/web/html/xui/form/inbound.html +++ b/web/html/xui/form/inbound.html @@ -1,116 +1,116 @@ -{{define "form/inbound"}} - - - - - - - - - - - [[ p ]] - - - - - 监听 IP - - - - - - - - - - - - - 总流量(GB) - - - - - - - - - - 到期时间 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +{{define "form/inbound"}} + + + + + + + + + + + [[ p ]] + + + + + 监听 IP + + + + + + + + + + + + + 总流量(GB) + + + + + + + + + + 到期时间 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {{end}} \ No newline at end of file diff --git a/web/html/xui/form/protocol/dokodemo.html b/web/html/xui/form/protocol/dokodemo.html index 0ccb2aa8..e11bded0 100644 --- a/web/html/xui/form/protocol/dokodemo.html +++ b/web/html/xui/form/protocol/dokodemo.html @@ -1,17 +1,17 @@ -{{define "form/dokodemo"}} - - - - - - - - - - tcp+udp - tcp - udp - - - +{{define "form/dokodemo"}} + + + + + + + + + + tcp+udp + tcp + udp + + + {{end}} \ No newline at end of file diff --git a/web/html/xui/form/protocol/http.html b/web/html/xui/form/protocol/http.html index aee482b9..d359b5da 100644 --- a/web/html/xui/form/protocol/http.html +++ b/web/html/xui/form/protocol/http.html @@ -1,10 +1,10 @@ -{{define "form/http"}} - - - - - - - - +{{define "form/http"}} + + + + + + + + {{end}} \ No newline at end of file diff --git a/web/html/xui/form/protocol/shadowsocks.html b/web/html/xui/form/protocol/shadowsocks.html index 734f8186..7667bd6f 100644 --- a/web/html/xui/form/protocol/shadowsocks.html +++ b/web/html/xui/form/protocol/shadowsocks.html @@ -1,19 +1,19 @@ -{{define "form/shadowsocks"}} - - - - [[ method ]] - - - - - - - - tcp+udp - tcp - udp - - - +{{define "form/shadowsocks"}} + + + + [[ method ]] + + + + + + + + tcp+udp + tcp + udp + + + {{end}} \ No newline at end of file diff --git a/web/html/xui/form/protocol/socks.html b/web/html/xui/form/protocol/socks.html index 30d133ed..b0267b3f 100644 --- a/web/html/xui/form/protocol/socks.html +++ b/web/html/xui/form/protocol/socks.html @@ -1,23 +1,23 @@ -{{define "form/socks"}} - - - - - - - - - - - - +{{define "form/socks"}} + + + + + + + + + + + + {{end}} \ No newline at end of file diff --git a/web/html/xui/form/protocol/trojan.html b/web/html/xui/form/protocol/trojan.html index 161be641..dcb81208 100644 --- a/web/html/xui/form/protocol/trojan.html +++ b/web/html/xui/form/protocol/trojan.html @@ -1,42 +1,42 @@ -{{define "form/trojan"}} - - - - - - - - - - + - - - - - - - - - fallback[[ index + 1 ]] - - - - - - - - - - - - - - - - - - - +{{define "form/trojan"}} + + + + + + + + + + + + + + + + + + + + fallback[[ index + 1 ]] + + + + + + + + + + + + + + + + + + + {{end}} \ No newline at end of file diff --git a/web/html/xui/form/protocol/vless.html b/web/html/xui/form/protocol/vless.html index 2576d5d6..bd9cecf2 100644 --- a/web/html/xui/form/protocol/vless.html +++ b/web/html/xui/form/protocol/vless.html @@ -1,56 +1,56 @@ -{{define "form/vless"}} - - - - - - - - - [[ key ]] - - - - - - [[ key ]] - - - - - - - - - + - - - - - - - - - fallback[[ index + 1 ]] - - - - - - - - - - - - - - - - - - - +{{define "form/vless"}} + + + + + + + + + [[ key ]] + + + + + + [[ key ]] + + + + + + + + + + + + + + + + + + + fallback[[ index + 1 ]] + + + + + + + + + + + + + + + + + + + {{end}} \ No newline at end of file diff --git a/web/html/xui/form/protocol/vmess.html b/web/html/xui/form/protocol/vmess.html index 70f00ada..280e6a6a 100644 --- a/web/html/xui/form/protocol/vmess.html +++ b/web/html/xui/form/protocol/vmess.html @@ -1,13 +1,13 @@ -{{define "form/vmess"}} - - - - - - - - - - - +{{define "form/vmess"}} + + + + + + + + + + + {{end}} \ No newline at end of file diff --git a/web/html/xui/form/reality_settings.html b/web/html/xui/form/reality_settings.html index 49d75cb6..bb6a066d 100644 --- a/web/html/xui/form/reality_settings.html +++ b/web/html/xui/form/reality_settings.html @@ -1,54 +1,54 @@ -{{define "form/realitySettings"}} - - - - - - - - - - - - - - - - - - - - [[ key ]] - - - - - - - - - - - - - - - - - - - - - - - - +{{define "form/realitySettings"}} + + + + + + + + + + + + + + + + + + + + [[ key ]] + + + + + + + + + + + + + + + + + + + + + + + + {{end}} \ No newline at end of file diff --git a/web/html/xui/form/sniffing.html b/web/html/xui/form/sniffing.html index 5ec35595..1d25cb21 100644 --- a/web/html/xui/form/sniffing.html +++ b/web/html/xui/form/sniffing.html @@ -1,16 +1,16 @@ -{{define "form/sniffing"}} - - - - sniffing - - - - - - - - +{{define "form/sniffing"}} + + + + sniffing + + + + + + + + {{end}} \ No newline at end of file diff --git a/web/html/xui/form/stream/stream_grpc.html b/web/html/xui/form/stream/stream_grpc.html index 4d386b06..205a4c84 100644 --- a/web/html/xui/form/stream/stream_grpc.html +++ b/web/html/xui/form/stream/stream_grpc.html @@ -1,7 +1,7 @@ -{{define "form/streamGRPC"}} - - - - - +{{define "form/streamGRPC"}} + + + + + {{end}} \ No newline at end of file diff --git a/web/html/xui/form/stream/stream_http.html b/web/html/xui/form/stream/stream_http.html index 6e21e79a..d2a4bf95 100644 --- a/web/html/xui/form/stream/stream_http.html +++ b/web/html/xui/form/stream/stream_http.html @@ -1,12 +1,12 @@ -{{define "form/streamHTTP"}} - - - - - - - - - - +{{define "form/streamHTTP"}} + + + + + + + + + + {{end}} \ No newline at end of file diff --git a/web/html/xui/form/stream/stream_kcp.html b/web/html/xui/form/stream/stream_kcp.html index 07c43431..18f89435 100644 --- a/web/html/xui/form/stream/stream_kcp.html +++ b/web/html/xui/form/stream/stream_kcp.html @@ -1,38 +1,38 @@ -{{define "form/streamKCP"}} - - - - none(not camouflage) - srtp(camouflage video call) - utp(camouflage BT download) - wechat-video(camouflage WeChat video) - dtls(camouflage DTLS 1.2 packages) - wireguard(camouflage wireguard packages) - - - - - - - - - - - - - - - - - - - - - - - - - - - +{{define "form/streamKCP"}} + + + + none(not camouflage) + srtp(camouflage video call) + utp(camouflage BT download) + wechat-video(camouflage WeChat video) + dtls(camouflage DTLS 1.2 packages) + wireguard(camouflage wireguard packages) + + + + + + + + + + + + + + + + + + + + + + + + + + + {{end}} \ No newline at end of file diff --git a/web/html/xui/form/stream/stream_quic.html b/web/html/xui/form/stream/stream_quic.html index 7bac2483..8d57d662 100644 --- a/web/html/xui/form/stream/stream_quic.html +++ b/web/html/xui/form/stream/stream_quic.html @@ -1,24 +1,24 @@ -{{define "form/streamQUIC"}} - - - - none - aes-128-gcm - chacha20-poly1305 - - - - - - - - none(not camouflage) - srtp(camouflage video call) - utp(camouflage BT download) - wechat-video(camouflage WeChat video) - dtls(camouflage DTLS 1.2 packages) - wireguard(camouflage wireguard packages) - - - +{{define "form/streamQUIC"}} + + + + none + aes-128-gcm + chacha20-poly1305 + + + + + + + + none(not camouflage) + srtp(camouflage video call) + utp(camouflage BT download) + wechat-video(camouflage WeChat video) + dtls(camouflage DTLS 1.2 packages) + wireguard(camouflage wireguard packages) + + + {{end}} \ No newline at end of file diff --git a/web/html/xui/form/stream/stream_settings.html b/web/html/xui/form/stream/stream_settings.html index e9572d25..021c587e 100644 --- a/web/html/xui/form/stream/stream_settings.html +++ b/web/html/xui/form/stream/stream_settings.html @@ -1,45 +1,45 @@ -{{define "form/streamSettings"}} - - - - - tcp - kcp - ws - http - quic - grpc - - - - - - - - - - - - - - - - - - - - - +{{define "form/streamSettings"}} + + + + + tcp + kcp + ws + http + quic + grpc + + + + + + + + + + + + + + + + + + + + + {{end}} \ No newline at end of file diff --git a/web/html/xui/form/stream/stream_tcp.html b/web/html/xui/form/stream/stream_tcp.html index 37188538..de9ce07f 100644 --- a/web/html/xui/form/stream/stream_tcp.html +++ b/web/html/xui/form/stream/stream_tcp.html @@ -1,83 +1,83 @@ -{{define "form/streamTCP"}} - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - - - - - - +{{define "form/streamTCP"}} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {{end}} \ No newline at end of file diff --git a/web/html/xui/form/stream/stream_ws.html b/web/html/xui/form/stream/stream_ws.html index 68f355ab..487c2c4c 100644 --- a/web/html/xui/form/stream/stream_ws.html +++ b/web/html/xui/form/stream/stream_ws.html @@ -1,28 +1,28 @@ -{{define "form/streamWS"}} - - - - - - - - + - - - - - - - - - - +{{define "form/streamWS"}} + + + + + + + + + + + + + + + + + + + {{end}} \ No newline at end of file diff --git a/web/html/xui/form/stream_sockopt.html b/web/html/xui/form/stream_sockopt.html index 6af01dea..65f5d2b9 100644 --- a/web/html/xui/form/stream_sockopt.html +++ b/web/html/xui/form/stream_sockopt.html @@ -1,54 +1,54 @@ -{{define "form/streamSOCKOPT"}} - - - - - - - - - - - - - - - - - - [[ key ]] - - - - - - - - - - - - - - - - - - 系统默认 - [[ key ]] - - - - - - - - +{{define "form/streamSOCKOPT"}} + + + + + + + + + + + + + + + + + + [[ key ]] + + + + + + + + + + + + + + + + + + 系统默认 + [[ key ]] + + + + + + + + {{end}} \ No newline at end of file diff --git a/web/html/xui/form/tls_settings.html b/web/html/xui/form/tls_settings.html index 66ec6f4e..9e1eba01 100644 --- a/web/html/xui/form/tls_settings.html +++ b/web/html/xui/form/tls_settings.html @@ -1,93 +1,93 @@ -{{define "form/tlsSettings"}} - - - - - - - - - - - - - - - - - - - - - - - - - - auto - [[ key ]] - - - - - [[ key ]] - - - - - [[ key ]] - - - - - None - [[ key ]] - - - - - - - - - - [[ key ]] - - - - - - - - certificate file path - certificate file content - - - - - +{{define "form/tlsSettings"}} + + + + + + + + + + + + + + + + + + + + + + + + + + auto + [[ key ]] + + + + + [[ key ]] + + + + + [[ key ]] + + + + + None + [[ key ]] + + + + + + + + + + [[ key ]] + + + + + + + + certificate file path + certificate file content + + + + + {{end}} \ No newline at end of file diff --git a/web/html/xui/inbound_info_modal.html b/web/html/xui/inbound_info_modal.html index d1ff67e0..3a837ff0 100644 --- a/web/html/xui/inbound_info_modal.html +++ b/web/html/xui/inbound_info_modal.html @@ -1,61 +1,61 @@ -{{define "inboundInfoModal"}} -{{template "component/inboundInfo"}} - - - - +{{define "inboundInfoModal"}} +{{template "component/inboundInfo"}} + + + + {{end}} \ No newline at end of file diff --git a/web/html/xui/inbound_modal.html b/web/html/xui/inbound_modal.html index b905f9bc..050b4a00 100644 --- a/web/html/xui/inbound_modal.html +++ b/web/html/xui/inbound_modal.html @@ -1,79 +1,79 @@ -{{define "inboundModal"}} - - {{template "form/inbound"}} - - +{{define "inboundModal"}} + + {{template "form/inbound"}} + + {{end}} \ No newline at end of file diff --git a/web/html/xui/inbounds.html b/web/html/xui/inbounds.html index 7e0f654c..264fa6b6 100644 --- a/web/html/xui/inbounds.html +++ b/web/html/xui/inbounds.html @@ -1,384 +1,384 @@ - - -{{template "head" .}} - - - - {{ template "commonSider" . }} - - - - - - Please go to the panel settings as soon as possible to modify the username and password, otherwise there may be a risk of leaking account information - - - - - - - 总上传 / 下载: - [[ sizeFormat(total.up) ]] / [[ sizeFormat(total.down) ]] - - - 总用量: - [[ sizeFormat(total.up + total.down) ]] - - - 入站数量: - [[ dbInbounds.length ]] - - - - - - -
- 添加入站 - 流量重置 -
- - - - - - - - - - -
-
-
-
-
-
-{{template "js" .}} - -{{template "inboundModal"}} -{{template "promptModal"}} -{{template "qrcodeModal"}} -{{template "textModal"}} -{{template "inboundInfoModal"}} - + + +{{template "head" .}} + + + + {{ template "commonSider" . }} + + + + + + Please go to the panel settings as soon as possible to modify the username and password, otherwise there may be a risk of leaking account information + + + + + + + 总上传 / 下载: + [[ sizeFormat(total.up) ]] / [[ sizeFormat(total.down) ]] + + + 总用量: + [[ sizeFormat(total.up + total.down) ]] + + + 入站数量: + [[ dbInbounds.length ]] + + + + + + +
+ 添加入站 + 流量重置 +
+ + + + + + + + + + +
+
+
+
+
+
+{{template "js" .}} + +{{template "inboundModal"}} +{{template "promptModal"}} +{{template "qrcodeModal"}} +{{template "textModal"}} +{{template "inboundInfoModal"}} + \ No newline at end of file diff --git a/web/html/xui/index.html b/web/html/xui/index.html index c5169693..2f1b1097 100644 --- a/web/html/xui/index.html +++ b/web/html/xui/index.html @@ -1,466 +1,466 @@ - - -{{template "head" .}} - - - - {{ template "commonSider" . }} - - - - - - - - - - - -
CPU
-
- - -
- 内存: [[ sizeFormat(status.mem.current) ]] / [[ sizeFormat(status.mem.total) ]] -
-
-
-
- - - - -
- swap: [[ sizeFormat(status.swap.current) ]] / [[ sizeFormat(status.swap.total) ]] -
-
- - -
- 硬盘: [[ sizeFormat(status.disk.current) ]] / [[ sizeFormat(status.disk.total) ]] -
-
-
-
-
-
-
-
- - - - - Xray-ui: v{{ .cur_ver }} - Xray: v[[ status.xray.version ]] - github: 教程 - - - - - 运行时间: - [[ formatSecond(status.uptime) ]] - - - - - - - - - Xray 状态: - [[ status.xray.state ]] - - - - - [[ status.xray.version ]] - Xray版本切换 - geoip更新 - geosite更新 - 停止 - 重启 - - - - - 其他: - 配置文件下载 - DB下载 - - - - - 系统负载: [[ status.loads[0] ]] | [[ status.loads[1] ]] | [[ status.loads[2] ]] - - - - - tcp / udp 连接数: [[ status.tcpCount ]] / [[ status.udpCount ]] - - - - - - - - - - - - [[ sizeFormat(status.netIO.up) ]] / S - - - - - - - - [[ sizeFormat(status.netIO.down) ]] / S - - - - - - - - - - - - - - [[ sizeFormat(status.netTraffic.sent) ]] - - - - - - - - [[ sizeFormat(status.netTraffic.recv) ]] - - - - - - - - - - -
-
- -

点击你想切换的版本

-

请谨慎选择,旧版本可能配置不兼容

- -
- -

点击你想更新的版本

- - -
- -

点击你想更新的版本

- - -
- -
-{{template "js" .}} -{{template "textModal"}} - - - + + +{{template "head" .}} + + + + {{ template "commonSider" . }} + + + + + + + + + + + +
CPU
+
+ + +
+ 内存: [[ sizeFormat(status.mem.current) ]] / [[ sizeFormat(status.mem.total) ]] +
+
+
+
+ + + + +
+ swap: [[ sizeFormat(status.swap.current) ]] / [[ sizeFormat(status.swap.total) ]] +
+
+ + +
+ 硬盘: [[ sizeFormat(status.disk.current) ]] / [[ sizeFormat(status.disk.total) ]] +
+
+
+
+
+
+
+
+ + + + + Xray-ui: v{{ .cur_ver }} + Xray: v[[ status.xray.version ]] + github: 教程 + + + + + 运行时间: + [[ formatSecond(status.uptime) ]] + + + + + + + + + Xray 状态: + [[ status.xray.state ]] + + + + + [[ status.xray.version ]] + Xray版本切换 + geoip更新 + geosite更新 + 停止 + 重启 + + + + + 其他: + 配置文件下载 + DB下载 + + + + + 系统负载: [[ status.loads[0] ]] | [[ status.loads[1] ]] | [[ status.loads[2] ]] + + + + + tcp / udp 连接数: [[ status.tcpCount ]] / [[ status.udpCount ]] + + + + + + + + + + + + [[ sizeFormat(status.netIO.up) ]] / S + + + + + + + + [[ sizeFormat(status.netIO.down) ]] / S + + + + + + + + + + + + + + [[ sizeFormat(status.netTraffic.sent) ]] + + + + + + + + [[ sizeFormat(status.netTraffic.recv) ]] + + + + + + + + + + +
+
+ +

点击你想切换的版本

+

请谨慎选择,旧版本可能配置不兼容

+ +
+ +

点击你想更新的版本

+ + +
+ +

点击你想更新的版本

+ + +
+ +
+{{template "js" .}} +{{template "textModal"}} + + + diff --git a/web/html/xui/setting.html b/web/html/xui/setting.html index c46cdacb..4c10c41a 100644 --- a/web/html/xui/setting.html +++ b/web/html/xui/setting.html @@ -1,169 +1,169 @@ - - -{{template "head" .}} - - - - {{ template "commonSider" . }} - - - - - - 保存配置 - 重启面板 - - - - - - - - - - - - - - - - - - - - - - - - - - - 修改 - - - - - - - - - - - - - - - - - - - - - - - - - - - -{{template "js" .}} -{{template "component/setting"}} - - + + +{{template "head" .}} + + + + {{ template "commonSider" . }} + + + + + + 保存配置 + 重启面板 + + + + + + + + + + + + + + + + + + + + + + + + + + + 修改 + + + + + + + + + + + + + + + + + + + + + + + + + + + +{{template "js" .}} +{{template "component/setting"}} + + \ No newline at end of file diff --git a/web/job/check_inbound_job.go b/web/job/check_inbound_job.go index 80dcbdb0..f73db5a7 100644 --- a/web/job/check_inbound_job.go +++ b/web/job/check_inbound_job.go @@ -1,25 +1,25 @@ -package job - -import ( - "xray-ui/logger" - "xray-ui/web/service" -) - -type CheckInboundJob struct { - xrayService service.XrayService - inboundService service.InboundService -} - -func NewCheckInboundJob() *CheckInboundJob { - return new(CheckInboundJob) -} - -func (j *CheckInboundJob) Run() { - count, err := j.inboundService.DisableInvalidInbounds() - if err != nil { - logger.Warning("disable invalid inbounds err:", err) - } else if count > 0 { - logger.Debugf("disabled %v inbounds", count) - j.xrayService.SetToNeedRestart() - } -} +package job + +import ( + "xray-ui/logger" + "xray-ui/web/service" +) + +type CheckInboundJob struct { + xrayService service.XrayService + inboundService service.InboundService +} + +func NewCheckInboundJob() *CheckInboundJob { + return new(CheckInboundJob) +} + +func (j *CheckInboundJob) Run() { + count, err := j.inboundService.DisableInvalidInbounds() + if err != nil { + logger.Warning("disable invalid inbounds err:", err) + } else if count > 0 { + logger.Debugf("disabled %v inbounds", count) + j.xrayService.SetToNeedRestart() + } +} diff --git a/web/job/check_xray_running_job.go b/web/job/check_xray_running_job.go index 47174385..4e2c278f 100644 --- a/web/job/check_xray_running_job.go +++ b/web/job/check_xray_running_job.go @@ -1,25 +1,25 @@ -package job - -import "xray-ui/web/service" - -type CheckXrayRunningJob struct { - xrayService service.XrayService - - checkTime int -} - -func NewCheckXrayRunningJob() *CheckXrayRunningJob { - return new(CheckXrayRunningJob) -} - -func (j *CheckXrayRunningJob) Run() { - if j.xrayService.IsXrayRunning() { - j.checkTime = 0 - return - } - j.checkTime++ - if j.checkTime < 2 { - return - } - j.xrayService.SetToNeedRestart() -} +package job + +import "xray-ui/web/service" + +type CheckXrayRunningJob struct { + xrayService service.XrayService + + checkTime int +} + +func NewCheckXrayRunningJob() *CheckXrayRunningJob { + return new(CheckXrayRunningJob) +} + +func (j *CheckXrayRunningJob) Run() { + if j.xrayService.IsXrayRunning() { + j.checkTime = 0 + return + } + j.checkTime++ + if j.checkTime < 2 { + return + } + j.xrayService.SetToNeedRestart() +} diff --git a/web/job/stats_notify_job.go b/web/job/stats_notify_job.go index cd4398a3..d6fa7179 100644 --- a/web/job/stats_notify_job.go +++ b/web/job/stats_notify_job.go @@ -1,165 +1,165 @@ -package job - -import ( - "fmt" - "os" - "os/exec" - "strconv" - "time" - "xray-ui/logger" - "xray-ui/util/common" - "xray-ui/web/service" -) - -var SSHLoginUser int - -type LoginStatus byte - -const ( - LoginSuccess LoginStatus = 1 - LoginFail LoginStatus = 0 -) - -type StatsNotifyJob struct { - enable bool - telegramService service.TelegramService - xrayService service.XrayService - inboundService service.InboundService - settingService service.SettingService -} - -func NewStatsNotifyJob() *StatsNotifyJob { - return new(StatsNotifyJob) -} - -//Here run is a interface method of Job interface -func (j *StatsNotifyJob) Run() { - if !j.xrayService.IsXrayRunning() { - return - } - var info string - info = j.GetsystemStatus() - j.telegramService.SendMsgToTgbot(info) -} - -func (j *StatsNotifyJob) UserLoginNotify(username string, ip string, time string, status LoginStatus) { - if username == "" || ip == "" || time == "" { - logger.Warning("UserLoginNotify failed, invalid info") - return - } - var msg string - //get hostname - name, err := os.Hostname() - if err != nil { - fmt.Println("get hostname error: ", err) - return - } - if status == LoginSuccess { - msg = fmt.Sprintf("面板登录成功提醒\r\n主机名称: %s\r\n", name) - } else if status == LoginFail { - msg = fmt.Sprintf("面板登录失败提醒\r\n主机名称: %s\r\n", name) - } - msg += fmt.Sprintf("时间: %s\r\n", time) - msg += fmt.Sprintf("用户: %s\r\n", username) - msg += fmt.Sprintf("IP: %s\r\n", ip) - j.telegramService.SendMsgToTgbot(msg) -} - -func (j *StatsNotifyJob) SSHStatusLoginNotify(xuiStartTime string) { - getSSHUserNumber, error := exec.Command("bash", "-c", "who | awk '{print $1}' | wc -l").Output() - if error != nil { - fmt.Println("getSSHUserNumber error: ", error) - return - } - var numberInt int - numberInt, error = strconv.Atoi(common.ByteToString(getSSHUserNumber)) - if error != nil { - return - } - if numberInt > SSHLoginUser { - var SSHLoginInfo string - SSHLoginUser = numberInt - //hostname - name, err := os.Hostname() - if err != nil { - fmt.Println("get hostname error: ", err) - return - } - //Time compare,need if xray-ui got restart while ssh already exist users - SSHLoginTime, error := exec.Command("bash", "-c", "who | awk '{print $3,$4}' | tail -n 1 ").Output() - if error != nil { - fmt.Println("getLoginTime error: ", error.Error()) - return - } - /* - //TODO:time compare if xray-ui get restart and there exist logging users - XUIRunTime, error := exec.Command("bash", "-c", " systemctl status xray-ui | grep Active| tail -n 1 | awk '{print $6,$7}' ").Output() - if error != nil { - fmt.Println("getXUIRunTime error:", error.Error()) - return - } - */ - var SSHLoginTimeStr string - SSHLoginTimeStr = common.ByteToString(SSHLoginTime) - t1, err := time.Parse("2006-01-02 15:04:05", SSHLoginTimeStr) - t2, err := time.Parse("2006-01-02 15:04:05", xuiStartTime) - if t1.Before(t2) || err != nil { - fmt.Printf("SSHLogin[%s] early than XRAY-UI start[%s]\r\n", SSHLoginTimeStr, xuiStartTime) - } - - SSHLoginUserName, error := exec.Command("bash", "-c", "who | awk '{print $1}'| tail -n 1").Output() - if error != nil { - fmt.Println("getSSHLoginUserName error: ", error.Error()) - return - } - - SSHLoginIpAddr, error := exec.Command("bash", "-c", "who | awk -F [\\(\\)] 'NR==1 {print $2}'").Output() - if error != nil { - fmt.Println("getSSHLoginIpAddr error: ", error) - return - } - - SSHLoginInfo = fmt.Sprintf("新用户登录提醒:\r\n") - SSHLoginInfo += fmt.Sprintf("主机名称:%s\r\n", name) - SSHLoginInfo += fmt.Sprintf("SSH登录用户:%s", SSHLoginUserName) - SSHLoginInfo += fmt.Sprintf("SSH登录时间:%s", SSHLoginTime) - SSHLoginInfo += fmt.Sprintf("SSH登录IP:%s", SSHLoginIpAddr) - SSHLoginInfo += fmt.Sprintf("当前SSH登录用户数:%s", getSSHUserNumber) - j.telegramService.SendMsgToTgbot(SSHLoginInfo) - } else { - SSHLoginUser = numberInt - } -} - -func (j *StatsNotifyJob) GetsystemStatus() string { - var info string - //get hostname - name, err := os.Hostname() - if err != nil { - fmt.Println("get hostname error:", err) - return "" - } - info = fmt.Sprintf("主机名称: %s\r\n", name) - //get ip address - var ip string - ip = common.GetMyIpAddr() - info += fmt.Sprintf("IP地址: %s\r\n \r\n", ip) - - //get traffic - inbouds, err := j.inboundService.GetAllInbounds() - if err != nil { - logger.Warning("StatsNotifyJob run failed: ", err) - return "" - } - //NOTE:If there no any sessions here,need to notify here - //TODO:分节点推送,自动转化格式 - for _, inbound := range inbouds { - info += fmt.Sprintf("节点名称: %s\r\n端口: %d\r\n上行流量↑: %s\r\n下行流量↓: %s\r\n总流量: %s\r\n", inbound.Remark, inbound.Port, common.FormatTraffic(inbound.Up), common.FormatTraffic(inbound.Down), common.FormatTraffic((inbound.Up + inbound.Down))) - if inbound.ExpiryTime == 0 { - info += fmt.Sprintf("到期时间: 无限期\r\n \r\n") - } else { - info += fmt.Sprintf("到期时间: %s\r\n \r\n", time.Unix((inbound.ExpiryTime/1000), 0).Format("2006-01-02 15:04:05")) - } - } - return info -} +package job + +import ( + "fmt" + "os" + "os/exec" + "strconv" + "time" + "xray-ui/logger" + "xray-ui/util/common" + "xray-ui/web/service" +) + +var SSHLoginUser int + +type LoginStatus byte + +const ( + LoginSuccess LoginStatus = 1 + LoginFail LoginStatus = 0 +) + +type StatsNotifyJob struct { + enable bool + telegramService service.TelegramService + xrayService service.XrayService + inboundService service.InboundService + settingService service.SettingService +} + +func NewStatsNotifyJob() *StatsNotifyJob { + return new(StatsNotifyJob) +} + +//Here run is a interface method of Job interface +func (j *StatsNotifyJob) Run() { + if !j.xrayService.IsXrayRunning() { + return + } + var info string + info = j.GetsystemStatus() + j.telegramService.SendMsgToTgbot(info) +} + +func (j *StatsNotifyJob) UserLoginNotify(username string, ip string, time string, status LoginStatus) { + if username == "" || ip == "" || time == "" { + logger.Warning("UserLoginNotify failed, invalid info") + return + } + var msg string + //get hostname + name, err := os.Hostname() + if err != nil { + fmt.Println("get hostname error: ", err) + return + } + if status == LoginSuccess { + msg = fmt.Sprintf("面板登录成功提醒\r\n主机名称: %s\r\n", name) + } else if status == LoginFail { + msg = fmt.Sprintf("面板登录失败提醒\r\n主机名称: %s\r\n", name) + } + msg += fmt.Sprintf("时间: %s\r\n", time) + msg += fmt.Sprintf("用户: %s\r\n", username) + msg += fmt.Sprintf("IP: %s\r\n", ip) + j.telegramService.SendMsgToTgbot(msg) +} + +func (j *StatsNotifyJob) SSHStatusLoginNotify(xuiStartTime string) { + getSSHUserNumber, error := exec.Command("bash", "-c", "who | awk '{print $1}' | wc -l").Output() + if error != nil { + fmt.Println("getSSHUserNumber error: ", error) + return + } + var numberInt int + numberInt, error = strconv.Atoi(common.ByteToString(getSSHUserNumber)) + if error != nil { + return + } + if numberInt > SSHLoginUser { + var SSHLoginInfo string + SSHLoginUser = numberInt + //hostname + name, err := os.Hostname() + if err != nil { + fmt.Println("get hostname error: ", err) + return + } + //Time compare,need if xray-ui got restart while ssh already exist users + SSHLoginTime, error := exec.Command("bash", "-c", "who | awk '{print $3,$4}' | tail -n 1 ").Output() + if error != nil { + fmt.Println("getLoginTime error: ", error.Error()) + return + } + /* + //TODO:time compare if xray-ui get restart and there exist logging users + XUIRunTime, error := exec.Command("bash", "-c", " systemctl status xray-ui | grep Active| tail -n 1 | awk '{print $6,$7}' ").Output() + if error != nil { + fmt.Println("getXUIRunTime error:", error.Error()) + return + } + */ + var SSHLoginTimeStr string + SSHLoginTimeStr = common.ByteToString(SSHLoginTime) + t1, err := time.Parse("2006-01-02 15:04:05", SSHLoginTimeStr) + t2, err := time.Parse("2006-01-02 15:04:05", xuiStartTime) + if t1.Before(t2) || err != nil { + fmt.Printf("SSHLogin[%s] early than XRAY-UI start[%s]\r\n", SSHLoginTimeStr, xuiStartTime) + } + + SSHLoginUserName, error := exec.Command("bash", "-c", "who | awk '{print $1}'| tail -n 1").Output() + if error != nil { + fmt.Println("getSSHLoginUserName error: ", error.Error()) + return + } + + SSHLoginIpAddr, error := exec.Command("bash", "-c", "who | awk -F [\\(\\)] 'NR==1 {print $2}'").Output() + if error != nil { + fmt.Println("getSSHLoginIpAddr error: ", error) + return + } + + SSHLoginInfo = fmt.Sprintf("新用户登录提醒:\r\n") + SSHLoginInfo += fmt.Sprintf("主机名称:%s\r\n", name) + SSHLoginInfo += fmt.Sprintf("SSH登录用户:%s", SSHLoginUserName) + SSHLoginInfo += fmt.Sprintf("SSH登录时间:%s", SSHLoginTime) + SSHLoginInfo += fmt.Sprintf("SSH登录IP:%s", SSHLoginIpAddr) + SSHLoginInfo += fmt.Sprintf("当前SSH登录用户数:%s", getSSHUserNumber) + j.telegramService.SendMsgToTgbot(SSHLoginInfo) + } else { + SSHLoginUser = numberInt + } +} + +func (j *StatsNotifyJob) GetsystemStatus() string { + var info string + //get hostname + name, err := os.Hostname() + if err != nil { + fmt.Println("get hostname error:", err) + return "" + } + info = fmt.Sprintf("主机名称: %s\r\n", name) + //get ip address + var ip string + ip = common.GetMyIpAddr() + info += fmt.Sprintf("IP地址: %s\r\n \r\n", ip) + + //get traffic + inbouds, err := j.inboundService.GetAllInbounds() + if err != nil { + logger.Warning("StatsNotifyJob run failed: ", err) + return "" + } + //NOTE:If there no any sessions here,need to notify here + //TODO:分节点推送,自动转化格式 + for _, inbound := range inbouds { + info += fmt.Sprintf("节点名称: %s\r\n端口: %d\r\n上行流量↑: %s\r\n下行流量↓: %s\r\n总流量: %s\r\n", inbound.Remark, inbound.Port, common.FormatTraffic(inbound.Up), common.FormatTraffic(inbound.Down), common.FormatTraffic((inbound.Up + inbound.Down))) + if inbound.ExpiryTime == 0 { + info += fmt.Sprintf("到期时间: 无限期\r\n \r\n") + } else { + info += fmt.Sprintf("到期时间: %s\r\n \r\n", time.Unix((inbound.ExpiryTime/1000), 0).Format("2006-01-02 15:04:05")) + } + } + return info +} diff --git a/web/job/xray_traffic_job.go b/web/job/xray_traffic_job.go index baef535a..4f66811f 100644 --- a/web/job/xray_traffic_job.go +++ b/web/job/xray_traffic_job.go @@ -1,30 +1,30 @@ -package job - -import ( - "xray-ui/logger" - "xray-ui/web/service" -) - -type XrayTrafficJob struct { - xrayService service.XrayService - inboundService service.InboundService -} - -func NewXrayTrafficJob() *XrayTrafficJob { - return new(XrayTrafficJob) -} - -func (j *XrayTrafficJob) Run() { - if !j.xrayService.IsXrayRunning() { - return - } - traffics, err := j.xrayService.GetXrayTraffic() - if err != nil { - logger.Warning("get xray traffic failed:", err) - return - } - err = j.inboundService.AddTraffic(traffics) - if err != nil { - logger.Warning("add traffic failed:", err) - } -} +package job + +import ( + "xray-ui/logger" + "xray-ui/web/service" +) + +type XrayTrafficJob struct { + xrayService service.XrayService + inboundService service.InboundService +} + +func NewXrayTrafficJob() *XrayTrafficJob { + return new(XrayTrafficJob) +} + +func (j *XrayTrafficJob) Run() { + if !j.xrayService.IsXrayRunning() { + return + } + traffics, err := j.xrayService.GetXrayTraffic() + if err != nil { + logger.Warning("get xray traffic failed:", err) + return + } + err = j.inboundService.AddTraffic(traffics) + if err != nil { + logger.Warning("add traffic failed:", err) + } +} diff --git a/web/network/auto_https_listener.go b/web/network/auto_https_listener.go index 87fee83a..26614696 100644 --- a/web/network/auto_https_listener.go +++ b/web/network/auto_https_listener.go @@ -1,21 +1,21 @@ -package network - -import "net" - -type AutoHttpsListener struct { - net.Listener -} - -func NewAutoHttpsListener(listener net.Listener) net.Listener { - return &AutoHttpsListener{ - Listener: listener, - } -} - -func (l *AutoHttpsListener) Accept() (net.Conn, error) { - conn, err := l.Listener.Accept() - if err != nil { - return nil, err - } - return NewAutoHttpsConn(conn), nil -} +package network + +import "net" + +type AutoHttpsListener struct { + net.Listener +} + +func NewAutoHttpsListener(listener net.Listener) net.Listener { + return &AutoHttpsListener{ + Listener: listener, + } +} + +func (l *AutoHttpsListener) Accept() (net.Conn, error) { + conn, err := l.Listener.Accept() + if err != nil { + return nil, err + } + return NewAutoHttpsConn(conn), nil +} diff --git a/web/network/autp_https_conn.go b/web/network/autp_https_conn.go index f0b700b6..d1a9d521 100644 --- a/web/network/autp_https_conn.go +++ b/web/network/autp_https_conn.go @@ -1,67 +1,67 @@ -package network - -import ( - "bufio" - "bytes" - "fmt" - "net" - "net/http" - "sync" -) - -type AutoHttpsConn struct { - net.Conn - - firstBuf []byte - bufStart int - - readRequestOnce sync.Once -} - -func NewAutoHttpsConn(conn net.Conn) net.Conn { - return &AutoHttpsConn{ - Conn: conn, - } -} - -func (c *AutoHttpsConn) readRequest() bool { - c.firstBuf = make([]byte, 2048) - n, err := c.Conn.Read(c.firstBuf) - c.firstBuf = c.firstBuf[:n] - if err != nil { - return false - } - reader := bytes.NewReader(c.firstBuf) - bufReader := bufio.NewReader(reader) - request, err := http.ReadRequest(bufReader) - if err != nil { - return false - } - resp := http.Response{ - Header: http.Header{}, - } - resp.StatusCode = http.StatusTemporaryRedirect - location := fmt.Sprintf("https://%v%v", request.Host, request.RequestURI) - resp.Header.Set("Location", location) - resp.Write(c.Conn) - c.Close() - c.firstBuf = nil - return true -} - -func (c *AutoHttpsConn) Read(buf []byte) (int, error) { - c.readRequestOnce.Do(func() { - c.readRequest() - }) - - if c.firstBuf != nil { - n := copy(buf, c.firstBuf[c.bufStart:]) - c.bufStart += n - if c.bufStart >= len(c.firstBuf) { - c.firstBuf = nil - } - return n, nil - } - - return c.Conn.Read(buf) -} +package network + +import ( + "bufio" + "bytes" + "fmt" + "net" + "net/http" + "sync" +) + +type AutoHttpsConn struct { + net.Conn + + firstBuf []byte + bufStart int + + readRequestOnce sync.Once +} + +func NewAutoHttpsConn(conn net.Conn) net.Conn { + return &AutoHttpsConn{ + Conn: conn, + } +} + +func (c *AutoHttpsConn) readRequest() bool { + c.firstBuf = make([]byte, 2048) + n, err := c.Conn.Read(c.firstBuf) + c.firstBuf = c.firstBuf[:n] + if err != nil { + return false + } + reader := bytes.NewReader(c.firstBuf) + bufReader := bufio.NewReader(reader) + request, err := http.ReadRequest(bufReader) + if err != nil { + return false + } + resp := http.Response{ + Header: http.Header{}, + } + resp.StatusCode = http.StatusTemporaryRedirect + location := fmt.Sprintf("https://%v%v", request.Host, request.RequestURI) + resp.Header.Set("Location", location) + resp.Write(c.Conn) + c.Close() + c.firstBuf = nil + return true +} + +func (c *AutoHttpsConn) Read(buf []byte) (int, error) { + c.readRequestOnce.Do(func() { + c.readRequest() + }) + + if c.firstBuf != nil { + n := copy(buf, c.firstBuf[c.bufStart:]) + c.bufStart += n + if c.bufStart >= len(c.firstBuf) { + c.firstBuf = nil + } + return n, nil + } + + return c.Conn.Read(buf) +} diff --git a/web/service/config.json b/web/service/config.json index 173fa6ad..45ca48b7 100644 --- a/web/service/config.json +++ b/web/service/config.json @@ -1,84 +1,79 @@ -{ - "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": { - "response": { - "type": "http" - } - }, - "tag": "blocked" - } - ], - "policy": { - "system": { - "statsInboundDownlink": true, - "statsInboundUplink": true - }, - "levels": { - "0": { - "handshake": 2, - "connIdle": 120, - "uplinkOnly": 1, - "downlinkOnly": 1 - } - } - }, - "routing": { - "domainStrategy": "IPIfNonMatch", - "rules": [ - { - "inboundTag": [ - "api" - ], - "outboundTag": "api", - "type": "field" - }, - { - "type": "field", - "domain": [ - "www.gstatic.com" - ], - "outboundTag": "direct" - }, - { - "ip": [ - "geoip:private" - ], - "outboundTag": "blocked", - "type": "field" - }, - { - "outboundTag": "blocked", - "protocol": [ - "bittorrent" - ], - "type": "field" - } - ] - }, - "stats": {} +{ + "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", + "tag": "blocked" + } + ], + "policy": { + "system": { + "statsInboundDownlink": true, + "statsInboundUplink": true + }, + "levels": { + "0": { + "handshake": 2, + "connIdle": 120, + "uplinkOnly": 1, + "downlinkOnly": 1 + } + } + }, + "routing": { + "domainStrategy": "IPIfNonMatch", + "rules": [ + { + "inboundTag": [ + "api" + ], + "outboundTag": "api", + "type": "field" + }, + { + "type": "field", + "domain": [ + "www.gstatic.com" + ], + "outboundTag": "direct" + }, + { + "ip": [ + "geoip:private" + ], + "outboundTag": "blocked", + "type": "field" + }, + { + "outboundTag": "blocked", + "protocol": [ + "bittorrent" + ], + "type": "field" + } + ] + }, + "stats": {} } \ No newline at end of file diff --git a/web/service/inbound.go b/web/service/inbound.go index ac7f1a71..0d35b2ed 100644 --- a/web/service/inbound.go +++ b/web/service/inbound.go @@ -1,221 +1,221 @@ -package service - -import ( - "fmt" - "time" - "xray-ui/database" - "xray-ui/database/model" - "xray-ui/util/common" - "xray-ui/xray" - - "gorm.io/gorm" -) - -type InboundService struct { -} - -func (s *InboundService) GetInbounds(userId int) ([]*model.Inbound, error) { - db := database.GetDB() - var inbounds []*model.Inbound - err := db.Model(model.Inbound{}).Where("user_id = ?", userId).Find(&inbounds).Error - if err != nil && err != gorm.ErrRecordNotFound { - return nil, err - } - return inbounds, nil -} - -func (s *InboundService) GetAllInbounds() ([]*model.Inbound, error) { - db := database.GetDB() - var inbounds []*model.Inbound - err := db.Model(model.Inbound{}).Find(&inbounds).Error - if err != nil && err != gorm.ErrRecordNotFound { - return nil, err - } - return inbounds, nil -} - -func (s *InboundService) checkPortExist(port int, ignoreId int) (bool, error) { - db := database.GetDB() - db = db.Model(model.Inbound{}).Where("port = ?", port) - if ignoreId > 0 { - db = db.Where("id != ?", ignoreId) - } - var count int64 - err := db.Count(&count).Error - if err != nil { - return false, err - } - return count > 0, nil -} - -func (s *InboundService) AddInbound(inbound *model.Inbound) error { - exist, err := s.checkPortExist(inbound.Port, 0) - if err != nil { - return err - } - if exist { - return common.NewError("端口已存在:", inbound.Port) - } - db := database.GetDB() - return db.Save(inbound).Error -} - -func (s *InboundService) AddInbounds(inbounds []*model.Inbound) error { - for _, inbound := range inbounds { - exist, err := s.checkPortExist(inbound.Port, 0) - if err != nil { - return err - } - if exist { - return common.NewError("端口已存在:", inbound.Port) - } - } - - db := database.GetDB() - tx := db.Begin() - var err error - defer func() { - if err == nil { - tx.Commit() - } else { - tx.Rollback() - } - }() - - for _, inbound := range inbounds { - err = tx.Save(inbound).Error - if err != nil { - return err - } - } - - return nil -} - -func (s *InboundService) DelInbound(id int) error { - db := database.GetDB() - return db.Delete(model.Inbound{}, id).Error -} - -func (s *InboundService) DelInboundByPort(port int) error { - db := database.GetDB() - var inbound model.Inbound - db.First(&inbound, "port = ?", port) - return db.Delete(&inbound).Error -} - -func (s *InboundService) GetInbound(id int) (*model.Inbound, error) { - db := database.GetDB() - inbound := &model.Inbound{} - err := db.Model(model.Inbound{}).First(inbound, id).Error - if err != nil { - return nil, err - } - return inbound, nil -} - -func (s *InboundService) UpdateInbound(inbound *model.Inbound) error { - exist, err := s.checkPortExist(inbound.Port, inbound.Id) - if err != nil { - return err - } - if exist { - return common.NewError("端口已存在:", inbound.Port) - } - - oldInbound, err := s.GetInbound(inbound.Id) - if err != nil { - return err - } - oldInbound.Up = inbound.Up - oldInbound.Down = inbound.Down - oldInbound.Total = inbound.Total - oldInbound.Remark = inbound.Remark - oldInbound.Enable = inbound.Enable - oldInbound.ExpiryTime = inbound.ExpiryTime - oldInbound.Listen = inbound.Listen - oldInbound.Port = inbound.Port - oldInbound.Protocol = inbound.Protocol - oldInbound.Settings = inbound.Settings - oldInbound.StreamSettings = inbound.StreamSettings - oldInbound.Sniffing = inbound.Sniffing - oldInbound.Tag = fmt.Sprintf("inbound-%v", inbound.Port) - - db := database.GetDB() - return db.Save(oldInbound).Error -} - -func (s *InboundService) ClearTrafficByPort(port int) error { - db := database.GetDB() - Uperr := db.Model(model.Inbound{}).Where("port = ?", port).Update("up", 0).Error - if Uperr != nil { - fmt.Println("ClearTrafficByPort error:clear up failed") - return Uperr - } - Downerr := db.Model(model.Inbound{}).Where("port = ?", port).Update("down", 0).Error - if Downerr != nil { - fmt.Println("ClearTrafficByPort error:clear down failed") - return Downerr - } - return nil -} - -func (s *InboundService) ClearAllInboundTraffic() error { - inbounds, _ := s.GetAllInbounds() - for _, inbound := range inbounds { - err := s.ClearTrafficByPort(inbound.Port) - if err != nil { - fmt.Printf("ClearAllInboundTraffic error,ClearTrafficByPort port %d fail", inbound.Port) - continue - } - } - return nil -} - -func (s *InboundService) AddTraffic(traffics []*xray.Traffic) (err error) { - if len(traffics) == 0 { - return nil - } - db := database.GetDB() - db = db.Model(model.Inbound{}) - tx := db.Begin() - defer func() { - if err != nil { - tx.Rollback() - } else { - tx.Commit() - } - }() - for _, traffic := range traffics { - if traffic.IsInbound { - err = tx.Where("tag = ?", traffic.Tag). - UpdateColumns(map[string]interface{}{ - "up": gorm.Expr("up + ?", traffic.Up), - "down": gorm.Expr("down + ?", traffic.Down)}).Error - if err != nil { - return - } - } - } - return -} - -func (s *InboundService) DisableInvalidInbounds() (int64, error) { - db := database.GetDB() - now := time.Now().Unix() * 1000 - result := db.Model(model.Inbound{}). - Where("((total > 0 and up + down >= total) or (expiry_time > 0 and expiry_time <= ?)) and enable = ?", now, true). - Update("enable", false) - err := result.Error - count := result.RowsAffected - return count, err -} - -func (s *InboundService) DisableInboundByPort(port int) error { - db := database.GetDB() - return db.Model(model.Inbound{}).Where("port = ?", port).Update("enable", false).Error -} -func (s *InboundService) EnableInboundByPort(port int) error { - db := database.GetDB() - return db.Model(model.Inbound{}).Where("port = ?", port).Update("enable", true).Error -} +package service + +import ( + "fmt" + "time" + "xray-ui/database" + "xray-ui/database/model" + "xray-ui/util/common" + "xray-ui/xray" + + "gorm.io/gorm" +) + +type InboundService struct { +} + +func (s *InboundService) GetInbounds(userId int) ([]*model.Inbound, error) { + db := database.GetDB() + var inbounds []*model.Inbound + err := db.Model(model.Inbound{}).Where("user_id = ?", userId).Find(&inbounds).Error + if err != nil && err != gorm.ErrRecordNotFound { + return nil, err + } + return inbounds, nil +} + +func (s *InboundService) GetAllInbounds() ([]*model.Inbound, error) { + db := database.GetDB() + var inbounds []*model.Inbound + err := db.Model(model.Inbound{}).Find(&inbounds).Error + if err != nil && err != gorm.ErrRecordNotFound { + return nil, err + } + return inbounds, nil +} + +func (s *InboundService) checkPortExist(port int, ignoreId int) (bool, error) { + db := database.GetDB() + db = db.Model(model.Inbound{}).Where("port = ?", port) + if ignoreId > 0 { + db = db.Where("id != ?", ignoreId) + } + var count int64 + err := db.Count(&count).Error + if err != nil { + return false, err + } + return count > 0, nil +} + +func (s *InboundService) AddInbound(inbound *model.Inbound) error { + exist, err := s.checkPortExist(inbound.Port, 0) + if err != nil { + return err + } + if exist { + return common.NewError("端口已存在:", inbound.Port) + } + db := database.GetDB() + return db.Save(inbound).Error +} + +func (s *InboundService) AddInbounds(inbounds []*model.Inbound) error { + for _, inbound := range inbounds { + exist, err := s.checkPortExist(inbound.Port, 0) + if err != nil { + return err + } + if exist { + return common.NewError("端口已存在:", inbound.Port) + } + } + + db := database.GetDB() + tx := db.Begin() + var err error + defer func() { + if err == nil { + tx.Commit() + } else { + tx.Rollback() + } + }() + + for _, inbound := range inbounds { + err = tx.Save(inbound).Error + if err != nil { + return err + } + } + + return nil +} + +func (s *InboundService) DelInbound(id int) error { + db := database.GetDB() + return db.Delete(model.Inbound{}, id).Error +} + +func (s *InboundService) DelInboundByPort(port int) error { + db := database.GetDB() + var inbound model.Inbound + db.First(&inbound, "port = ?", port) + return db.Delete(&inbound).Error +} + +func (s *InboundService) GetInbound(id int) (*model.Inbound, error) { + db := database.GetDB() + inbound := &model.Inbound{} + err := db.Model(model.Inbound{}).First(inbound, id).Error + if err != nil { + return nil, err + } + return inbound, nil +} + +func (s *InboundService) UpdateInbound(inbound *model.Inbound) error { + exist, err := s.checkPortExist(inbound.Port, inbound.Id) + if err != nil { + return err + } + if exist { + return common.NewError("端口已存在:", inbound.Port) + } + + oldInbound, err := s.GetInbound(inbound.Id) + if err != nil { + return err + } + oldInbound.Up = inbound.Up + oldInbound.Down = inbound.Down + oldInbound.Total = inbound.Total + oldInbound.Remark = inbound.Remark + oldInbound.Enable = inbound.Enable + oldInbound.ExpiryTime = inbound.ExpiryTime + oldInbound.Listen = inbound.Listen + oldInbound.Port = inbound.Port + oldInbound.Protocol = inbound.Protocol + oldInbound.Settings = inbound.Settings + oldInbound.StreamSettings = inbound.StreamSettings + oldInbound.Sniffing = inbound.Sniffing + oldInbound.Tag = fmt.Sprintf("inbound-%v", inbound.Port) + + db := database.GetDB() + return db.Save(oldInbound).Error +} + +func (s *InboundService) ClearTrafficByPort(port int) error { + db := database.GetDB() + Uperr := db.Model(model.Inbound{}).Where("port = ?", port).Update("up", 0).Error + if Uperr != nil { + fmt.Println("ClearTrafficByPort error:clear up failed") + return Uperr + } + Downerr := db.Model(model.Inbound{}).Where("port = ?", port).Update("down", 0).Error + if Downerr != nil { + fmt.Println("ClearTrafficByPort error:clear down failed") + return Downerr + } + return nil +} + +func (s *InboundService) ClearAllInboundTraffic() error { + inbounds, _ := s.GetAllInbounds() + for _, inbound := range inbounds { + err := s.ClearTrafficByPort(inbound.Port) + if err != nil { + fmt.Printf("ClearAllInboundTraffic error,ClearTrafficByPort port %d fail", inbound.Port) + continue + } + } + return nil +} + +func (s *InboundService) AddTraffic(traffics []*xray.Traffic) (err error) { + if len(traffics) == 0 { + return nil + } + db := database.GetDB() + db = db.Model(model.Inbound{}) + tx := db.Begin() + defer func() { + if err != nil { + tx.Rollback() + } else { + tx.Commit() + } + }() + for _, traffic := range traffics { + if traffic.IsInbound { + err = tx.Where("tag = ?", traffic.Tag). + UpdateColumns(map[string]interface{}{ + "up": gorm.Expr("up + ?", traffic.Up), + "down": gorm.Expr("down + ?", traffic.Down)}).Error + if err != nil { + return + } + } + } + return +} + +func (s *InboundService) DisableInvalidInbounds() (int64, error) { + db := database.GetDB() + now := time.Now().Unix() * 1000 + result := db.Model(model.Inbound{}). + Where("((total > 0 and up + down >= total) or (expiry_time > 0 and expiry_time <= ?)) and enable = ?", now, true). + Update("enable", false) + err := result.Error + count := result.RowsAffected + return count, err +} + +func (s *InboundService) DisableInboundByPort(port int) error { + db := database.GetDB() + return db.Model(model.Inbound{}).Where("port = ?", port).Update("enable", false).Error +} +func (s *InboundService) EnableInboundByPort(port int) error { + db := database.GetDB() + return db.Model(model.Inbound{}).Where("port = ?", port).Update("enable", true).Error +} diff --git a/web/service/panel.go b/web/service/panel.go index c24d61f7..5b52ab6b 100644 --- a/web/service/panel.go +++ b/web/service/panel.go @@ -1,26 +1,26 @@ -package service - -import ( - "os" - "syscall" - "time" - "xray-ui/logger" -) - -type PanelService struct { -} - -func (s *PanelService) RestartPanel(delay time.Duration) error { - p, err := os.FindProcess(syscall.Getpid()) - if err != nil { - return err - } - go func() { - time.Sleep(delay) - err := p.Signal(syscall.SIGHUP) - if err != nil { - logger.Error("send signal SIGHUP failed:", err) - } - }() - return nil -} +package service + +import ( + "os" + "syscall" + "time" + "xray-ui/logger" +) + +type PanelService struct { +} + +func (s *PanelService) RestartPanel(delay time.Duration) error { + p, err := os.FindProcess(syscall.Getpid()) + if err != nil { + return err + } + go func() { + time.Sleep(delay) + err := p.Signal(syscall.SIGHUP) + if err != nil { + logger.Error("send signal SIGHUP failed:", err) + } + }() + return nil +} diff --git a/web/service/server.go b/web/service/server.go index b1987fe9..9db0d500 100644 --- a/web/service/server.go +++ b/web/service/server.go @@ -1,507 +1,507 @@ -package service - -import ( - "archive/zip" - "bytes" - "encoding/json" - "fmt" - "io" - "io/fs" - "net/http" - "os" - "runtime" - "time" - "xray-ui/logger" - "xray-ui/config" - "xray-ui/util/sys" - "xray-ui/xray" - - "github.com/shirou/gopsutil/v3/cpu" - "github.com/shirou/gopsutil/v3/disk" - "github.com/shirou/gopsutil/v3/host" - "github.com/shirou/gopsutil/v3/load" - "github.com/shirou/gopsutil/v3/mem" - "github.com/shirou/gopsutil/v3/net" -) - -type ProcessState string - -const ( - Running ProcessState = "running" - Stop ProcessState = "stop" - Error ProcessState = "error" -) - -type Status struct { - T time.Time `json:"-"` - Cpu float64 `json:"cpu"` - Mem struct { - Current uint64 `json:"current"` - Total uint64 `json:"total"` - } `json:"mem"` - Swap struct { - Current uint64 `json:"current"` - Total uint64 `json:"total"` - } `json:"swap"` - Disk struct { - Current uint64 `json:"current"` - Total uint64 `json:"total"` - } `json:"disk"` - Xray struct { - State ProcessState `json:"state"` - ErrorMsg string `json:"errorMsg"` - Version string `json:"version"` - } `json:"xray"` - Uptime uint64 `json:"uptime"` - Loads []float64 `json:"loads"` - TcpCount int `json:"tcpCount"` - UdpCount int `json:"udpCount"` - NetIO struct { - Up uint64 `json:"up"` - Down uint64 `json:"down"` - } `json:"netIO"` - NetTraffic struct { - Sent uint64 `json:"sent"` - Recv uint64 `json:"recv"` - } `json:"netTraffic"` -} - -type Release struct { - TagName string `json:"tag_name"` -} - -type ServerService struct { - xrayService XrayService -} - -func (s *ServerService) GetStatus(lastStatus *Status) *Status { - now := time.Now() - status := &Status{ - T: now, - } - - percents, err := cpu.Percent(0, false) - if err != nil { - logger.Warning("get cpu percent failed:", err) - } else { - status.Cpu = percents[0] - } - - upTime, err := host.Uptime() - if err != nil { - logger.Warning("get uptime failed:", err) - } else { - status.Uptime = upTime - } - - memInfo, err := mem.VirtualMemory() - if err != nil { - logger.Warning("get virtual memory failed:", err) - } else { - status.Mem.Current = memInfo.Used - status.Mem.Total = memInfo.Total - } - - swapInfo, err := mem.SwapMemory() - if err != nil { - logger.Warning("get swap memory failed:", err) - } else { - status.Swap.Current = swapInfo.Used - status.Swap.Total = swapInfo.Total - } - - distInfo, err := disk.Usage("/") - if err != nil { - logger.Warning("get dist usage failed:", err) - } else { - status.Disk.Current = distInfo.Used - status.Disk.Total = distInfo.Total - } - - avgState, err := load.Avg() - if err != nil { - logger.Warning("get load avg failed:", err) - } else { - status.Loads = []float64{avgState.Load1, avgState.Load5, avgState.Load15} - } - - ioStats, err := net.IOCounters(false) - if err != nil { - logger.Warning("get io counters failed:", err) - } else if len(ioStats) > 0 { - ioStat := ioStats[0] - status.NetTraffic.Sent = ioStat.BytesSent - status.NetTraffic.Recv = ioStat.BytesRecv - - if lastStatus != nil { - duration := now.Sub(lastStatus.T) - seconds := float64(duration) / float64(time.Second) - up := uint64(float64(status.NetTraffic.Sent-lastStatus.NetTraffic.Sent) / seconds) - down := uint64(float64(status.NetTraffic.Recv-lastStatus.NetTraffic.Recv) / seconds) - status.NetIO.Up = up - status.NetIO.Down = down - } - } else { - logger.Warning("can not find io counters") - } - - status.TcpCount, err = sys.GetTCPCount() - if err != nil { - logger.Warning("get tcp connections failed:", err) - } - - status.UdpCount, err = sys.GetUDPCount() - if err != nil { - logger.Warning("get udp connections failed:", err) - } - - if s.xrayService.IsXrayRunning() { - status.Xray.State = Running - status.Xray.ErrorMsg = "" - } else { - err := s.xrayService.GetXrayErr() - if err != nil { - status.Xray.State = Error - } else { - status.Xray.State = Stop - } - status.Xray.ErrorMsg = s.xrayService.GetXrayResult() - } - status.Xray.Version = s.xrayService.GetXrayVersion() - - return status -} - -func (s *ServerService) GetXrayVersions() ([]string, error) { - url := "https://api.github.com/repos/XTLS/Xray-core/releases" - resp, err := http.Get(url) - if err != nil { - return nil, err - } - - defer resp.Body.Close() - buffer := bytes.NewBuffer(make([]byte, 8192)) - buffer.Reset() - _, err = buffer.ReadFrom(resp.Body) - if err != nil { - return nil, err - } - - releases := make([]Release, 0) - err = json.Unmarshal(buffer.Bytes(), &releases) - if err != nil { - return nil, err - } - versions := make([]string, 0, len(releases)) - for _, release := range releases { - versions = append(versions, release.TagName) - } - return versions, nil -} - -func (s *ServerService) StopXrayService() (string error) { - - err := s.xrayService.StopXray() - if err != nil { - logger.Error("stop xray failed:", err) - return err - } - - return nil -} - -func (s *ServerService) RestartXrayService() (string error) { - - s.xrayService.StopXray() - defer func() { - err := s.xrayService.RestartXray(true) - if err != nil { - logger.Error("start xray failed:", err) - } - }() - - return nil -} - -func (s *ServerService) downloadXRay(version string) (string, error) { - osName := runtime.GOOS - arch := runtime.GOARCH - switch osName { - case "darwin": - osName = "macos" - } - - switch arch { - case "amd64": - arch = "64" - case "arm64": - arch = "arm64-v8a" - } - - fileName := fmt.Sprintf("Xray-%s-%s.zip", osName, arch) - url := fmt.Sprintf("https://github.com/XTLS/Xray-core/releases/download/%s/%s", version, fileName) - resp, err := http.Get(url) - if err != nil { - return "", err - } - defer resp.Body.Close() - - os.Remove(fileName) - file, err := os.Create(fileName) - if err != nil { - return "", err - } - defer file.Close() - - _, err = io.Copy(file, resp.Body) - if err != nil { - return "", err - } - - return fileName, nil -} - -func (s *ServerService) UpdateXray(version string) error { - zipFileName, err := s.downloadXRay(version) - if err != nil { - return err - } - - zipFile, err := os.Open(zipFileName) - if err != nil { - return err - } - defer func() { - zipFile.Close() - os.Remove(zipFileName) - }() - - stat, err := zipFile.Stat() - if err != nil { - return err - } - reader, err := zip.NewReader(zipFile, stat.Size()) - if err != nil { - return err - } - - s.xrayService.StopXray() - defer func() { - err := s.xrayService.RestartXray(true) - if err != nil { - logger.Error("start xray failed:", err) - } - }() - - copyZipFile := func(zipName string, fileName string) error { - zipFile, err := reader.Open(zipName) - if err != nil { - return err - } - os.Remove(fileName) - file, err := os.OpenFile(fileName, os.O_CREATE|os.O_RDWR|os.O_TRUNC, fs.ModePerm) - if err != nil { - return err - } - defer file.Close() - _, err = io.Copy(file, zipFile) - return err - } - - err = copyZipFile("xray", xray.GetBinaryPath()) - if err != nil { - return err - } - // err = copyZipFile("geosite.dat", xray.GetGeositePath()) - // if err != nil { - // return err - // } - // err = copyZipFile("geoip.dat", xray.GetGeoipPath()) - // if err != nil { - // return err - // } - - return nil - -} - - -func (s *ServerService) GetGeoipVersions() ([]string, error) { - url := "https://api.github.com/repos/Loyalsoldier/v2ray-rules-dat/releases" - resp, err := http.Get(url) - if err != nil { - return nil, err - } - - defer resp.Body.Close() - buffer := bytes.NewBuffer(make([]byte, 8192)) - buffer.Reset() - _, err = buffer.ReadFrom(resp.Body) - if err != nil { - return nil, err - } - - releases := make([]Release, 0) - err = json.Unmarshal(buffer.Bytes(), &releases) - if err != nil { - return nil, err - } - versions := make([]string, 0, len(releases)) - for _, release := range releases { - versions = append(versions, release.TagName) - } - return versions, nil -} - -func (s *ServerService) downloadGeoip(version string) (string, error) { - - fileName := fmt.Sprintf("geoip.dat") - url := fmt.Sprintf("https://github.com/Loyalsoldier/v2ray-rules-dat/releases/download/%s/%s", version, fileName) - resp, err := http.Get(url) - if err != nil { - return "", err - } - defer resp.Body.Close() - - os.Remove(fileName) - file, err := os.Create(xray.GetGeoipPath()) - if err != nil { - return "", err - } - defer file.Close() - - _, err = io.Copy(file, resp.Body) - if err != nil { - return "", err - } - - return fileName, nil -} - -func (s *ServerService) UpdateGeoip(version string) error { - _, err := s.downloadGeoip(version) - if err != nil { - return err - } - - s.xrayService.StopXray() - defer func() { - err := s.xrayService.RestartXray(true) - if err != nil { - logger.Error("start xray failed:", err) - } - }() - - return nil - -} - -func (s *ServerService) GetGeositeVersions() ([]string, error) { - url := "https://api.github.com/repos/Loyalsoldier/v2ray-rules-dat/releases" - resp, err := http.Get(url) - if err != nil { - return nil, err - } - - defer resp.Body.Close() - buffer := bytes.NewBuffer(make([]byte, 8192)) - buffer.Reset() - _, err = buffer.ReadFrom(resp.Body) - if err != nil { - return nil, err - } - - releases := make([]Release, 0) - err = json.Unmarshal(buffer.Bytes(), &releases) - if err != nil { - return nil, err - } - versions := make([]string, 0, len(releases)) - for _, release := range releases { - versions = append(versions, release.TagName) - } - return versions, nil -} - -func (s *ServerService) downloadGeosite(version string) (string, error) { - - fileName := fmt.Sprintf("geosite.dat") - url := fmt.Sprintf("https://github.com/Loyalsoldier/v2ray-rules-dat/releases/download/%s/%s", version, fileName) - resp, err := http.Get(url) - if err != nil { - return "", err - } - defer resp.Body.Close() - - os.Remove(fileName) - file, err := os.Create(xray.GetGeositePath()) - if err != nil { - return "", err - } - defer file.Close() - - _, err = io.Copy(file, resp.Body) - if err != nil { - return "", err - } - - return fileName, nil -} - -func (s *ServerService) UpdateGeosite(version string) error { - _, err := s.downloadGeosite(version) - if err != nil { - return err - } - - s.xrayService.StopXray() - defer func() { - err := s.xrayService.RestartXray(true) - if err != nil { - logger.Error("start xray failed:", err) - } - }() - - return nil - -} - -func (s *ServerService) GetConfigJson() (interface{}, error) { - // Open the file for reading - file, err := os.Open(xray.GetConfigPath()) - if err != nil { - return nil, err - } - defer file.Close() - - // Read the file contents - fileContents, err := io.ReadAll(file) - if err != nil { - return nil, err - } - - var jsonData interface{} - err = json.Unmarshal(fileContents, &jsonData) - if err != nil { - return nil, err - } - - return jsonData, nil -} - -func (s *ServerService) GetDb() ([]byte, error) { - // Open the file for reading - file, err := os.Open(config.GetDBPath()) - if err != nil { - return nil, err - } - defer file.Close() - - // Read the file contents - fileContents, err := io.ReadAll(file) - if err != nil { - return nil, err - } - - return fileContents, nil +package service + +import ( + "archive/zip" + "bytes" + "encoding/json" + "fmt" + "io" + "io/fs" + "net/http" + "os" + "runtime" + "time" + "xray-ui/logger" + "xray-ui/config" + "xray-ui/util/sys" + "xray-ui/xray" + + "github.com/shirou/gopsutil/v3/cpu" + "github.com/shirou/gopsutil/v3/disk" + "github.com/shirou/gopsutil/v3/host" + "github.com/shirou/gopsutil/v3/load" + "github.com/shirou/gopsutil/v3/mem" + "github.com/shirou/gopsutil/v3/net" +) + +type ProcessState string + +const ( + Running ProcessState = "running" + Stop ProcessState = "stop" + Error ProcessState = "error" +) + +type Status struct { + T time.Time `json:"-"` + Cpu float64 `json:"cpu"` + Mem struct { + Current uint64 `json:"current"` + Total uint64 `json:"total"` + } `json:"mem"` + Swap struct { + Current uint64 `json:"current"` + Total uint64 `json:"total"` + } `json:"swap"` + Disk struct { + Current uint64 `json:"current"` + Total uint64 `json:"total"` + } `json:"disk"` + Xray struct { + State ProcessState `json:"state"` + ErrorMsg string `json:"errorMsg"` + Version string `json:"version"` + } `json:"xray"` + Uptime uint64 `json:"uptime"` + Loads []float64 `json:"loads"` + TcpCount int `json:"tcpCount"` + UdpCount int `json:"udpCount"` + NetIO struct { + Up uint64 `json:"up"` + Down uint64 `json:"down"` + } `json:"netIO"` + NetTraffic struct { + Sent uint64 `json:"sent"` + Recv uint64 `json:"recv"` + } `json:"netTraffic"` +} + +type Release struct { + TagName string `json:"tag_name"` +} + +type ServerService struct { + xrayService XrayService +} + +func (s *ServerService) GetStatus(lastStatus *Status) *Status { + now := time.Now() + status := &Status{ + T: now, + } + + percents, err := cpu.Percent(0, false) + if err != nil { + logger.Warning("get cpu percent failed:", err) + } else { + status.Cpu = percents[0] + } + + upTime, err := host.Uptime() + if err != nil { + logger.Warning("get uptime failed:", err) + } else { + status.Uptime = upTime + } + + memInfo, err := mem.VirtualMemory() + if err != nil { + logger.Warning("get virtual memory failed:", err) + } else { + status.Mem.Current = memInfo.Used + status.Mem.Total = memInfo.Total + } + + swapInfo, err := mem.SwapMemory() + if err != nil { + logger.Warning("get swap memory failed:", err) + } else { + status.Swap.Current = swapInfo.Used + status.Swap.Total = swapInfo.Total + } + + distInfo, err := disk.Usage("/") + if err != nil { + logger.Warning("get dist usage failed:", err) + } else { + status.Disk.Current = distInfo.Used + status.Disk.Total = distInfo.Total + } + + avgState, err := load.Avg() + if err != nil { + logger.Warning("get load avg failed:", err) + } else { + status.Loads = []float64{avgState.Load1, avgState.Load5, avgState.Load15} + } + + ioStats, err := net.IOCounters(false) + if err != nil { + logger.Warning("get io counters failed:", err) + } else if len(ioStats) > 0 { + ioStat := ioStats[0] + status.NetTraffic.Sent = ioStat.BytesSent + status.NetTraffic.Recv = ioStat.BytesRecv + + if lastStatus != nil { + duration := now.Sub(lastStatus.T) + seconds := float64(duration) / float64(time.Second) + up := uint64(float64(status.NetTraffic.Sent-lastStatus.NetTraffic.Sent) / seconds) + down := uint64(float64(status.NetTraffic.Recv-lastStatus.NetTraffic.Recv) / seconds) + status.NetIO.Up = up + status.NetIO.Down = down + } + } else { + logger.Warning("can not find io counters") + } + + status.TcpCount, err = sys.GetTCPCount() + if err != nil { + logger.Warning("get tcp connections failed:", err) + } + + status.UdpCount, err = sys.GetUDPCount() + if err != nil { + logger.Warning("get udp connections failed:", err) + } + + if s.xrayService.IsXrayRunning() { + status.Xray.State = Running + status.Xray.ErrorMsg = "" + } else { + err := s.xrayService.GetXrayErr() + if err != nil { + status.Xray.State = Error + } else { + status.Xray.State = Stop + } + status.Xray.ErrorMsg = s.xrayService.GetXrayResult() + } + status.Xray.Version = s.xrayService.GetXrayVersion() + + return status +} + +func (s *ServerService) GetXrayVersions() ([]string, error) { + url := "https://api.github.com/repos/XTLS/Xray-core/releases" + resp, err := http.Get(url) + if err != nil { + return nil, err + } + + defer resp.Body.Close() + buffer := bytes.NewBuffer(make([]byte, 8192)) + buffer.Reset() + _, err = buffer.ReadFrom(resp.Body) + if err != nil { + return nil, err + } + + releases := make([]Release, 0) + err = json.Unmarshal(buffer.Bytes(), &releases) + if err != nil { + return nil, err + } + versions := make([]string, 0, len(releases)) + for _, release := range releases { + versions = append(versions, release.TagName) + } + return versions, nil +} + +func (s *ServerService) StopXrayService() (string error) { + + err := s.xrayService.StopXray() + if err != nil { + logger.Error("stop xray failed:", err) + return err + } + + return nil +} + +func (s *ServerService) RestartXrayService() (string error) { + + s.xrayService.StopXray() + defer func() { + err := s.xrayService.RestartXray(true) + if err != nil { + logger.Error("start xray failed:", err) + } + }() + + return nil +} + +func (s *ServerService) downloadXRay(version string) (string, error) { + osName := runtime.GOOS + arch := runtime.GOARCH + switch osName { + case "darwin": + osName = "macos" + } + + switch arch { + case "amd64": + arch = "64" + case "arm64": + arch = "arm64-v8a" + } + + fileName := fmt.Sprintf("Xray-%s-%s.zip", osName, arch) + url := fmt.Sprintf("https://github.com/XTLS/Xray-core/releases/download/%s/%s", version, fileName) + resp, err := http.Get(url) + if err != nil { + return "", err + } + defer resp.Body.Close() + + os.Remove(fileName) + file, err := os.Create(fileName) + if err != nil { + return "", err + } + defer file.Close() + + _, err = io.Copy(file, resp.Body) + if err != nil { + return "", err + } + + return fileName, nil +} + +func (s *ServerService) UpdateXray(version string) error { + zipFileName, err := s.downloadXRay(version) + if err != nil { + return err + } + + zipFile, err := os.Open(zipFileName) + if err != nil { + return err + } + defer func() { + zipFile.Close() + os.Remove(zipFileName) + }() + + stat, err := zipFile.Stat() + if err != nil { + return err + } + reader, err := zip.NewReader(zipFile, stat.Size()) + if err != nil { + return err + } + + s.xrayService.StopXray() + defer func() { + err := s.xrayService.RestartXray(true) + if err != nil { + logger.Error("start xray failed:", err) + } + }() + + copyZipFile := func(zipName string, fileName string) error { + zipFile, err := reader.Open(zipName) + if err != nil { + return err + } + os.Remove(fileName) + file, err := os.OpenFile(fileName, os.O_CREATE|os.O_RDWR|os.O_TRUNC, fs.ModePerm) + if err != nil { + return err + } + defer file.Close() + _, err = io.Copy(file, zipFile) + return err + } + + err = copyZipFile("xray", xray.GetBinaryPath()) + if err != nil { + return err + } + // err = copyZipFile("geosite.dat", xray.GetGeositePath()) + // if err != nil { + // return err + // } + // err = copyZipFile("geoip.dat", xray.GetGeoipPath()) + // if err != nil { + // return err + // } + + return nil + +} + + +func (s *ServerService) GetGeoipVersions() ([]string, error) { + url := "https://api.github.com/repos/Loyalsoldier/v2ray-rules-dat/releases" + resp, err := http.Get(url) + if err != nil { + return nil, err + } + + defer resp.Body.Close() + buffer := bytes.NewBuffer(make([]byte, 8192)) + buffer.Reset() + _, err = buffer.ReadFrom(resp.Body) + if err != nil { + return nil, err + } + + releases := make([]Release, 0) + err = json.Unmarshal(buffer.Bytes(), &releases) + if err != nil { + return nil, err + } + versions := make([]string, 0, len(releases)) + for _, release := range releases { + versions = append(versions, release.TagName) + } + return versions, nil +} + +func (s *ServerService) downloadGeoip(version string) (string, error) { + + fileName := fmt.Sprintf("geoip.dat") + url := fmt.Sprintf("https://github.com/Loyalsoldier/v2ray-rules-dat/releases/download/%s/%s", version, fileName) + resp, err := http.Get(url) + if err != nil { + return "", err + } + defer resp.Body.Close() + + os.Remove(fileName) + file, err := os.Create(xray.GetGeoipPath()) + if err != nil { + return "", err + } + defer file.Close() + + _, err = io.Copy(file, resp.Body) + if err != nil { + return "", err + } + + return fileName, nil +} + +func (s *ServerService) UpdateGeoip(version string) error { + _, err := s.downloadGeoip(version) + if err != nil { + return err + } + + s.xrayService.StopXray() + defer func() { + err := s.xrayService.RestartXray(true) + if err != nil { + logger.Error("start xray failed:", err) + } + }() + + return nil + +} + +func (s *ServerService) GetGeositeVersions() ([]string, error) { + url := "https://api.github.com/repos/Loyalsoldier/v2ray-rules-dat/releases" + resp, err := http.Get(url) + if err != nil { + return nil, err + } + + defer resp.Body.Close() + buffer := bytes.NewBuffer(make([]byte, 8192)) + buffer.Reset() + _, err = buffer.ReadFrom(resp.Body) + if err != nil { + return nil, err + } + + releases := make([]Release, 0) + err = json.Unmarshal(buffer.Bytes(), &releases) + if err != nil { + return nil, err + } + versions := make([]string, 0, len(releases)) + for _, release := range releases { + versions = append(versions, release.TagName) + } + return versions, nil +} + +func (s *ServerService) downloadGeosite(version string) (string, error) { + + fileName := fmt.Sprintf("geosite.dat") + url := fmt.Sprintf("https://github.com/Loyalsoldier/v2ray-rules-dat/releases/download/%s/%s", version, fileName) + resp, err := http.Get(url) + if err != nil { + return "", err + } + defer resp.Body.Close() + + os.Remove(fileName) + file, err := os.Create(xray.GetGeositePath()) + if err != nil { + return "", err + } + defer file.Close() + + _, err = io.Copy(file, resp.Body) + if err != nil { + return "", err + } + + return fileName, nil +} + +func (s *ServerService) UpdateGeosite(version string) error { + _, err := s.downloadGeosite(version) + if err != nil { + return err + } + + s.xrayService.StopXray() + defer func() { + err := s.xrayService.RestartXray(true) + if err != nil { + logger.Error("start xray failed:", err) + } + }() + + return nil + +} + +func (s *ServerService) GetConfigJson() (interface{}, error) { + // Open the file for reading + file, err := os.Open(xray.GetConfigPath()) + if err != nil { + return nil, err + } + defer file.Close() + + // Read the file contents + fileContents, err := io.ReadAll(file) + if err != nil { + return nil, err + } + + var jsonData interface{} + err = json.Unmarshal(fileContents, &jsonData) + if err != nil { + return nil, err + } + + return jsonData, nil +} + +func (s *ServerService) GetDb() ([]byte, error) { + // Open the file for reading + file, err := os.Open(config.GetDBPath()) + if err != nil { + return nil, err + } + defer file.Close() + + // Read the file contents + fileContents, err := io.ReadAll(file) + if err != nil { + return nil, err + } + + return fileContents, nil } \ No newline at end of file diff --git a/web/service/setting.go b/web/service/setting.go index 5dad46e3..19b8e1bc 100644 --- a/web/service/setting.go +++ b/web/service/setting.go @@ -1,307 +1,307 @@ -package service - -import ( - _ "embed" - "errors" - "fmt" - "reflect" - "strconv" - "strings" - "time" - "xray-ui/database" - "xray-ui/database/model" - "xray-ui/logger" - "xray-ui/util/common" - "xray-ui/util/random" - "xray-ui/util/reflect_util" - "xray-ui/web/entity" -) - -//go:embed config.json -var xrayTemplateConfig string - -var defaultValueMap = map[string]string{ - "xrayTemplateConfig": xrayTemplateConfig, - "webListen": "", - "webPort": "54321", - "webCertFile": "", - "webKeyFile": "", - "secret": random.Seq(32), - "webBasePath": "/", - "timeLocation": "Asia/Shanghai", - "tgBotEnable": "false", - "tgBotToken": "", - "tgBotChatId": "0", - "tgRunTime": "", -} - -type SettingService struct { -} - -func (s *SettingService) GetAllSetting() (*entity.AllSetting, error) { - db := database.GetDB() - settings := make([]*model.Setting, 0) - err := db.Model(model.Setting{}).Find(&settings).Error - if err != nil { - return nil, err - } - allSetting := &entity.AllSetting{} - t := reflect.TypeOf(allSetting).Elem() - v := reflect.ValueOf(allSetting).Elem() - fields := reflect_util.GetFields(t) - - setSetting := func(key, value string) (err error) { - defer func() { - panicErr := recover() - if panicErr != nil { - err = errors.New(fmt.Sprint(panicErr)) - } - }() - - var found bool - var field reflect.StructField - for _, f := range fields { - if f.Tag.Get("json") == key { - field = f - found = true - break - } - } - - if !found { - // 有些设置自动生成,不需要返回到前端给用户修改 - return nil - } - - fieldV := v.FieldByName(field.Name) - switch t := fieldV.Interface().(type) { - case int: - n, err := strconv.ParseInt(value, 10, 64) - if err != nil { - return err - } - fieldV.SetInt(n) - case string: - fieldV.SetString(value) - case bool: - fieldV.SetBool(value == "true") - default: - return common.NewErrorf("unknown field %v type %v", key, t) - } - return - } - - keyMap := map[string]bool{} - for _, setting := range settings { - err := setSetting(setting.Key, setting.Value) - if err != nil { - return nil, err - } - keyMap[setting.Key] = true - } - - for key, value := range defaultValueMap { - if keyMap[key] { - continue - } - err := setSetting(key, value) - if err != nil { - return nil, err - } - } - - return allSetting, nil -} - -func (s *SettingService) ResetSettings() error { - db := database.GetDB() - return db.Where("1 = 1").Delete(model.Setting{}).Error -} - -func (s *SettingService) getSetting(key string) (*model.Setting, error) { - db := database.GetDB() - setting := &model.Setting{} - err := db.Model(model.Setting{}).Where("key = ?", key).First(setting).Error - if err != nil { - return nil, err - } - return setting, nil -} - -func (s *SettingService) saveSetting(key string, value string) error { - setting, err := s.getSetting(key) - db := database.GetDB() - if database.IsNotFound(err) { - return db.Create(&model.Setting{ - Key: key, - Value: value, - }).Error - } else if err != nil { - return err - } - setting.Key = key - setting.Value = value - return db.Save(setting).Error -} - -func (s *SettingService) getString(key string) (string, error) { - setting, err := s.getSetting(key) - if database.IsNotFound(err) { - value, ok := defaultValueMap[key] - if !ok { - return "", common.NewErrorf("key <%v> not in defaultValueMap", key) - } - return value, nil - } else if err != nil { - return "", err - } - return setting.Value, nil -} - -func (s *SettingService) setString(key string, value string) error { - return s.saveSetting(key, value) -} - -func (s *SettingService) getBool(key string) (bool, error) { - str, err := s.getString(key) - if err != nil { - return false, err - } - return strconv.ParseBool(str) -} - -func (s *SettingService) setBool(key string, value bool) error { - return s.setString(key, strconv.FormatBool(value)) -} - -func (s *SettingService) getInt(key string) (int, error) { - str, err := s.getString(key) - if err != nil { - return 0, err - } - return strconv.Atoi(str) -} - -func (s *SettingService) setInt(key string, value int) error { - return s.setString(key, strconv.Itoa(value)) -} - -func (s *SettingService) GetXrayConfigTemplate() (string, error) { - return s.getString("xrayTemplateConfig") -} - -func (s *SettingService) GetListen() (string, error) { - return s.getString("webListen") -} - -func (s *SettingService) SetListen(listen string) error { - return s.setString("webListen", listen) -} - -func (s *SettingService) GetTgBotToken() (string, error) { - return s.getString("tgBotToken") -} - -func (s *SettingService) SetTgBotToken(token string) error { - return s.setString("tgBotToken", token) -} - -func (s *SettingService) GetTgBotChatId() (int, error) { - return s.getInt("tgBotChatId") -} - -func (s *SettingService) SetTgBotChatId(chatId int) error { - return s.setInt("tgBotChatId", chatId) -} - -func (s *SettingService) SetTgbotenabled(value bool) error { - return s.setBool("tgBotEnable", value) -} - -func (s *SettingService) GetTgbotenabled() (bool, error) { - return s.getBool("tgBotEnable") -} - -func (s *SettingService) SetTgbotRuntime(time string) error { - return s.setString("tgRunTime", time) -} - -func (s *SettingService) GetTgbotRuntime() (string, error) { - return s.getString("tgRunTime") -} - -func (s *SettingService) GetPort() (int, error) { - return s.getInt("webPort") -} - -func (s *SettingService) SetPort(port int) error { - return s.setInt("webPort", port) -} - -func (s *SettingService) GetCertFile() (string, error) { - return s.getString("webCertFile") -} - -func (s *SettingService) GetKeyFile() (string, error) { - return s.getString("webKeyFile") -} - -func (s *SettingService) GetSecret() ([]byte, error) { - secret, err := s.getString("secret") - if secret == defaultValueMap["secret"] { - err := s.saveSetting("secret", secret) - if err != nil { - logger.Warning("save secret failed:", err) - } - } - return []byte(secret), err -} - -func (s *SettingService) GetBasePath() (string, error) { - basePath, err := s.getString("webBasePath") - if err != nil { - return "", err - } - if !strings.HasPrefix(basePath, "/") { - basePath = "/" + basePath - } - if !strings.HasSuffix(basePath, "/") { - basePath += "/" - } - return basePath, nil -} - -func (s *SettingService) GetTimeLocation() (*time.Location, error) { - l, err := s.getString("timeLocation") - if err != nil { - return nil, err - } - location, err := time.LoadLocation(l) - if err != nil { - defaultLocation := defaultValueMap["timeLocation"] - logger.Errorf("location <%v> not exist, using default location: %v", l, defaultLocation) - return time.LoadLocation(defaultLocation) - } - return location, nil -} - -func (s *SettingService) UpdateAllSetting(allSetting *entity.AllSetting) error { - if err := allSetting.CheckValid(); err != nil { - return err - } - - v := reflect.ValueOf(allSetting).Elem() - t := reflect.TypeOf(allSetting).Elem() - fields := reflect_util.GetFields(t) - errs := make([]error, 0) - for _, field := range fields { - key := field.Tag.Get("json") - fieldV := v.FieldByName(field.Name) - value := fmt.Sprint(fieldV.Interface()) - err := s.saveSetting(key, value) - if err != nil { - errs = append(errs, err) - } - } - return common.Combine(errs...) -} +package service + +import ( + _ "embed" + "errors" + "fmt" + "reflect" + "strconv" + "strings" + "time" + "xray-ui/database" + "xray-ui/database/model" + "xray-ui/logger" + "xray-ui/util/common" + "xray-ui/util/random" + "xray-ui/util/reflect_util" + "xray-ui/web/entity" +) + +//go:embed config.json +var xrayTemplateConfig string + +var defaultValueMap = map[string]string{ + "xrayTemplateConfig": xrayTemplateConfig, + "webListen": "", + "webPort": "54321", + "webCertFile": "", + "webKeyFile": "", + "secret": random.Seq(32), + "webBasePath": "/", + "timeLocation": "Asia/Shanghai", + "tgBotEnable": "false", + "tgBotToken": "", + "tgBotChatId": "0", + "tgRunTime": "", +} + +type SettingService struct { +} + +func (s *SettingService) GetAllSetting() (*entity.AllSetting, error) { + db := database.GetDB() + settings := make([]*model.Setting, 0) + err := db.Model(model.Setting{}).Find(&settings).Error + if err != nil { + return nil, err + } + allSetting := &entity.AllSetting{} + t := reflect.TypeOf(allSetting).Elem() + v := reflect.ValueOf(allSetting).Elem() + fields := reflect_util.GetFields(t) + + setSetting := func(key, value string) (err error) { + defer func() { + panicErr := recover() + if panicErr != nil { + err = errors.New(fmt.Sprint(panicErr)) + } + }() + + var found bool + var field reflect.StructField + for _, f := range fields { + if f.Tag.Get("json") == key { + field = f + found = true + break + } + } + + if !found { + // 有些设置自动生成,不需要返回到前端给用户修改 + return nil + } + + fieldV := v.FieldByName(field.Name) + switch t := fieldV.Interface().(type) { + case int: + n, err := strconv.ParseInt(value, 10, 64) + if err != nil { + return err + } + fieldV.SetInt(n) + case string: + fieldV.SetString(value) + case bool: + fieldV.SetBool(value == "true") + default: + return common.NewErrorf("unknown field %v type %v", key, t) + } + return + } + + keyMap := map[string]bool{} + for _, setting := range settings { + err := setSetting(setting.Key, setting.Value) + if err != nil { + return nil, err + } + keyMap[setting.Key] = true + } + + for key, value := range defaultValueMap { + if keyMap[key] { + continue + } + err := setSetting(key, value) + if err != nil { + return nil, err + } + } + + return allSetting, nil +} + +func (s *SettingService) ResetSettings() error { + db := database.GetDB() + return db.Where("1 = 1").Delete(model.Setting{}).Error +} + +func (s *SettingService) getSetting(key string) (*model.Setting, error) { + db := database.GetDB() + setting := &model.Setting{} + err := db.Model(model.Setting{}).Where("key = ?", key).First(setting).Error + if err != nil { + return nil, err + } + return setting, nil +} + +func (s *SettingService) saveSetting(key string, value string) error { + setting, err := s.getSetting(key) + db := database.GetDB() + if database.IsNotFound(err) { + return db.Create(&model.Setting{ + Key: key, + Value: value, + }).Error + } else if err != nil { + return err + } + setting.Key = key + setting.Value = value + return db.Save(setting).Error +} + +func (s *SettingService) getString(key string) (string, error) { + setting, err := s.getSetting(key) + if database.IsNotFound(err) { + value, ok := defaultValueMap[key] + if !ok { + return "", common.NewErrorf("key <%v> not in defaultValueMap", key) + } + return value, nil + } else if err != nil { + return "", err + } + return setting.Value, nil +} + +func (s *SettingService) setString(key string, value string) error { + return s.saveSetting(key, value) +} + +func (s *SettingService) getBool(key string) (bool, error) { + str, err := s.getString(key) + if err != nil { + return false, err + } + return strconv.ParseBool(str) +} + +func (s *SettingService) setBool(key string, value bool) error { + return s.setString(key, strconv.FormatBool(value)) +} + +func (s *SettingService) getInt(key string) (int, error) { + str, err := s.getString(key) + if err != nil { + return 0, err + } + return strconv.Atoi(str) +} + +func (s *SettingService) setInt(key string, value int) error { + return s.setString(key, strconv.Itoa(value)) +} + +func (s *SettingService) GetXrayConfigTemplate() (string, error) { + return s.getString("xrayTemplateConfig") +} + +func (s *SettingService) GetListen() (string, error) { + return s.getString("webListen") +} + +func (s *SettingService) SetListen(listen string) error { + return s.setString("webListen", listen) +} + +func (s *SettingService) GetTgBotToken() (string, error) { + return s.getString("tgBotToken") +} + +func (s *SettingService) SetTgBotToken(token string) error { + return s.setString("tgBotToken", token) +} + +func (s *SettingService) GetTgBotChatId() (int, error) { + return s.getInt("tgBotChatId") +} + +func (s *SettingService) SetTgBotChatId(chatId int) error { + return s.setInt("tgBotChatId", chatId) +} + +func (s *SettingService) SetTgbotenabled(value bool) error { + return s.setBool("tgBotEnable", value) +} + +func (s *SettingService) GetTgbotenabled() (bool, error) { + return s.getBool("tgBotEnable") +} + +func (s *SettingService) SetTgbotRuntime(time string) error { + return s.setString("tgRunTime", time) +} + +func (s *SettingService) GetTgbotRuntime() (string, error) { + return s.getString("tgRunTime") +} + +func (s *SettingService) GetPort() (int, error) { + return s.getInt("webPort") +} + +func (s *SettingService) SetPort(port int) error { + return s.setInt("webPort", port) +} + +func (s *SettingService) GetCertFile() (string, error) { + return s.getString("webCertFile") +} + +func (s *SettingService) GetKeyFile() (string, error) { + return s.getString("webKeyFile") +} + +func (s *SettingService) GetSecret() ([]byte, error) { + secret, err := s.getString("secret") + if secret == defaultValueMap["secret"] { + err := s.saveSetting("secret", secret) + if err != nil { + logger.Warning("save secret failed:", err) + } + } + return []byte(secret), err +} + +func (s *SettingService) GetBasePath() (string, error) { + basePath, err := s.getString("webBasePath") + if err != nil { + return "", err + } + if !strings.HasPrefix(basePath, "/") { + basePath = "/" + basePath + } + if !strings.HasSuffix(basePath, "/") { + basePath += "/" + } + return basePath, nil +} + +func (s *SettingService) GetTimeLocation() (*time.Location, error) { + l, err := s.getString("timeLocation") + if err != nil { + return nil, err + } + location, err := time.LoadLocation(l) + if err != nil { + defaultLocation := defaultValueMap["timeLocation"] + logger.Errorf("location <%v> not exist, using default location: %v", l, defaultLocation) + return time.LoadLocation(defaultLocation) + } + return location, nil +} + +func (s *SettingService) UpdateAllSetting(allSetting *entity.AllSetting) error { + if err := allSetting.CheckValid(); err != nil { + return err + } + + v := reflect.ValueOf(allSetting).Elem() + t := reflect.TypeOf(allSetting).Elem() + fields := reflect_util.GetFields(t) + errs := make([]error, 0) + for _, field := range fields { + key := field.Tag.Get("json") + fieldV := v.FieldByName(field.Name) + value := fmt.Sprint(fieldV.Interface()) + err := s.saveSetting(key, value) + if err != nil { + errs = append(errs, err) + } + } + return common.Combine(errs...) +} diff --git a/web/service/telegram.go b/web/service/telegram.go index b4d7fe51..94767602 100644 --- a/web/service/telegram.go +++ b/web/service/telegram.go @@ -1,248 +1,248 @@ -package service - -import ( - "fmt" - "log" - "os" - "runtime" - "strconv" - "time" - "xray-ui/logger" - "xray-ui/util/common" - - tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5" - "github.com/shirou/gopsutil/v3/host" - "github.com/shirou/gopsutil/v3/load" -) - -//This should be global variable,and only one instance -var botInstace *tgbotapi.BotAPI - -//结构体类型大写表示可以被其他包访问 -type TelegramService struct { - xrayService XrayService - serverService ServerService - inboundService InboundService - settingService SettingService -} - -func (s *TelegramService) GetsystemStatus() string { - var status string - //get hostname - name, err := os.Hostname() - if err != nil { - fmt.Println("get hostname error:", err) - return "" - } - status = fmt.Sprintf("主机名称:%s\r\n", name) - status += fmt.Sprintf("系统类型:%s\r\n", runtime.GOOS) - status += fmt.Sprintf("系统架构:%s\r\n", runtime.GOARCH) - avgState, err := load.Avg() - if err != nil { - logger.Warning("get load avg failed:", err) - } else { - status += fmt.Sprintf("系统负载:%.2f,%.2f,%.2f\r\n", avgState.Load1, avgState.Load5, avgState.Load15) - } - upTime, err := host.Uptime() - if err != nil { - logger.Warning("get uptime failed:", err) - } else { - status += fmt.Sprintf("运行时间:%s\r\n", common.FormatTime(upTime)) - } - //xray version - status += fmt.Sprintf("xray版本:%s\r\n", s.xrayService.GetXrayVersion()) - //ip address - var ip string - ip = common.GetMyIpAddr() - status += fmt.Sprintf("IP地址:%s\r\n \r\n", ip) - //get traffic - inbouds, err := s.inboundService.GetAllInbounds() - if err != nil { - logger.Warning("StatsNotifyJob run error:", err) - } - for _, inbound := range inbouds { - status += fmt.Sprintf("节点名称:%s\r\n端口:%d\r\n上行流量↑:%s\r\n下行流量↓:%s\r\n总流量:%s\r\n", inbound.Remark, inbound.Port, common.FormatTraffic(inbound.Up), common.FormatTraffic(inbound.Down), common.FormatTraffic((inbound.Up + inbound.Down))) - if inbound.ExpiryTime == 0 { - status += fmt.Sprintf("到期时间:无限期\r\n \r\n") - } else { - status += fmt.Sprintf("到期时间:%s\r\n \r\n", time.Unix((inbound.ExpiryTime/1000), 0).Format("2006-01-02 15:04:05")) - } - } - return status -} - -func (s *TelegramService) StartRun() { - logger.Info("telegram service ready to run") - s.settingService = SettingService{} - tgBottoken, err := s.settingService.GetTgBotToken() - if err != nil || tgBottoken == "" { - logger.Infof("telegram service start run failed,GetTgBotToken fail,err:%v,tgBottoken:%s", err, tgBottoken) - return - } - logger.Infof("TelegramService GetTgBotToken:%s", tgBottoken) - botInstace, err = tgbotapi.NewBotAPI(tgBottoken) - if err != nil { - logger.Infof("telegram service start run failed,NewBotAPI fail:%v,tgBottoken:%s", err, tgBottoken) - return - } - botInstace.Debug = false - fmt.Printf("Authorized on account %s", botInstace.Self.UserName) - //get all my commands - commands, err := botInstace.GetMyCommands() - if err != nil { - logger.Warning("telegram service start run error,GetMyCommandsfail:", err) - } - for _, command := range commands { - fmt.Printf("command %s,Description:%s \r\n", command.Command, command.Description) - } - //get update - chanMessage := tgbotapi.NewUpdate(0) - chanMessage.Timeout = 60 - - updates := botInstace.GetUpdatesChan(chanMessage) - - for update := range updates { - if update.Message == nil { - //NOTE:may ther are different bot instance,we could use different bot endApiPoint - updates.Clear() - continue - } - - if !update.Message.IsCommand() { - continue - } - - msg := tgbotapi.NewMessage(update.Message.Chat.ID, "") - - // Extract the command from the Message. - switch update.Message.Command() { - case "delete": - inboundPortStr := update.Message.CommandArguments() - inboundPortValue, err := strconv.Atoi(inboundPortStr) - if err != nil { - msg.Text = "Invalid inbound port,please check it" - break - } - //logger.Infof("Will delete port:%d inbound", inboundPortValue) - error := s.inboundService.DelInboundByPort(inboundPortValue) - if error != nil { - msg.Text = fmt.Sprintf("delete inbound whoes port is %d failed", inboundPortValue) - } else { - msg.Text = fmt.Sprintf("delete inbound whoes port is %d success", inboundPortValue) - } - case "restart": - err := s.xrayService.RestartXray(true) - if err != nil { - msg.Text = fmt.Sprintln("Restart xray failed,error:", err) - } else { - msg.Text = "Restart xray success" - } - case "disable": - inboundPortStr := update.Message.CommandArguments() - inboundPortValue, err := strconv.Atoi(inboundPortStr) - if err != nil { - msg.Text = "Invalid inbound port,please check it" - break - } - //logger.Infof("Will delete port:%d inbound", inboundPortValue) - error := s.inboundService.DisableInboundByPort(inboundPortValue) - if error != nil { - msg.Text = fmt.Sprintf("disable inbound whoes port is %d failed,err:%s", inboundPortValue, error) - } else { - msg.Text = fmt.Sprintf("disable inbound whoes port is %d success", inboundPortValue) - } - case "enable": - inboundPortStr := update.Message.CommandArguments() - inboundPortValue, err := strconv.Atoi(inboundPortStr) - if err != nil { - msg.Text = "Invalid inbound port,please check it" - break - } - //logger.Infof("Will delete port:%d inbound", inboundPortValue) - error := s.inboundService.EnableInboundByPort(inboundPortValue) - if error != nil { - msg.Text = fmt.Sprintf("enable inbound whoes port is %d failed,err:%s", inboundPortValue, error) - } else { - msg.Text = fmt.Sprintf("enable inbound whoes port is %d success", inboundPortValue) - } - case "clear": - inboundPortStr := update.Message.CommandArguments() - inboundPortValue, err := strconv.Atoi(inboundPortStr) - if err != nil { - msg.Text = "Invalid inbound port,please check it" - break - } - error := s.inboundService.ClearTrafficByPort(inboundPortValue) - if error != nil { - msg.Text = fmt.Sprintf("Clear Traffic whose port is %d failed,err:%s", inboundPortValue, error) - } else { - msg.Text = fmt.Sprintf("Clear Traffic whose port is %d success", inboundPortValue) - } - - case "clearall": - error := s.inboundService.ClearAllInboundTraffic() - if error != nil { - msg.Text = fmt.Sprintf("Clear All inbound Traffic failed,err:%s", error) - } else { - msg.Text = fmt.Sprintf("Clear All inbound Traffic success") - } - case "version": - versionStr := update.Message.CommandArguments() - currentVersion, _ := s.serverService.GetXrayVersions() - if currentVersion[0] == versionStr { - msg.Text = fmt.Sprintf("can't change same version to %s", versionStr) - } - error := s.serverService.UpdateXray(versionStr) - if error != nil { - msg.Text = fmt.Sprintf("change version to %s failed,err:%s", versionStr, error) - } else { - msg.Text = fmt.Sprintf("change version to %s success", versionStr) - } - case "status": - msg.Text = s.GetsystemStatus() - default: - //NOTE:here we need string as a new line each one,we should use `` - msg.Text = `/delete will help you delete inbound according port -/restart will restart xray,this command will not restart xray-ui -/status will get current system info -/enable will enable inbound according port -/disable will disable inbound according port -/clear will clear inbound traffic accoring port -/clearall will cleal all inbouns traffic -/version will change xray version to specific one -You can input /help to see more commands` - } - - if _, err := botInstace.Send(msg); err != nil { - log.Panic(err) - } - } - -} - -func (s *TelegramService) SendMsgToTgbot(msg string) { - logger.Info("SendMsgToTgbot entered") - tgBotid, err := s.settingService.GetTgBotChatId() - if err != nil { - logger.Warning("sendMsgToTgbot failed,GetTgBotChatId fail:", err) - return - } - if tgBotid == 0 { - logger.Warning("sendMsgToTgbot failed,GetTgBotChatId illegal") - return - } - - info := tgbotapi.NewMessage(int64(tgBotid), msg) - if botInstace != nil { - botInstace.Send(info) - } else { - logger.Warning("bot instance is nil") - } -} - -//NOTE:This function can't be called repeatly -func (s *TelegramService) StopRunAndClose() { - if botInstace != nil { - botInstace.StopReceivingUpdates() - } -} +package service + +import ( + "fmt" + "log" + "os" + "runtime" + "strconv" + "time" + "xray-ui/logger" + "xray-ui/util/common" + + tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5" + "github.com/shirou/gopsutil/v3/host" + "github.com/shirou/gopsutil/v3/load" +) + +//This should be global variable,and only one instance +var botInstace *tgbotapi.BotAPI + +//结构体类型大写表示可以被其他包访问 +type TelegramService struct { + xrayService XrayService + serverService ServerService + inboundService InboundService + settingService SettingService +} + +func (s *TelegramService) GetsystemStatus() string { + var status string + //get hostname + name, err := os.Hostname() + if err != nil { + fmt.Println("get hostname error:", err) + return "" + } + status = fmt.Sprintf("主机名称:%s\r\n", name) + status += fmt.Sprintf("系统类型:%s\r\n", runtime.GOOS) + status += fmt.Sprintf("系统架构:%s\r\n", runtime.GOARCH) + avgState, err := load.Avg() + if err != nil { + logger.Warning("get load avg failed:", err) + } else { + status += fmt.Sprintf("系统负载:%.2f,%.2f,%.2f\r\n", avgState.Load1, avgState.Load5, avgState.Load15) + } + upTime, err := host.Uptime() + if err != nil { + logger.Warning("get uptime failed:", err) + } else { + status += fmt.Sprintf("运行时间:%s\r\n", common.FormatTime(upTime)) + } + //xray version + status += fmt.Sprintf("xray版本:%s\r\n", s.xrayService.GetXrayVersion()) + //ip address + var ip string + ip = common.GetMyIpAddr() + status += fmt.Sprintf("IP地址:%s\r\n \r\n", ip) + //get traffic + inbouds, err := s.inboundService.GetAllInbounds() + if err != nil { + logger.Warning("StatsNotifyJob run error:", err) + } + for _, inbound := range inbouds { + status += fmt.Sprintf("节点名称:%s\r\n端口:%d\r\n上行流量↑:%s\r\n下行流量↓:%s\r\n总流量:%s\r\n", inbound.Remark, inbound.Port, common.FormatTraffic(inbound.Up), common.FormatTraffic(inbound.Down), common.FormatTraffic((inbound.Up + inbound.Down))) + if inbound.ExpiryTime == 0 { + status += fmt.Sprintf("到期时间:无限期\r\n \r\n") + } else { + status += fmt.Sprintf("到期时间:%s\r\n \r\n", time.Unix((inbound.ExpiryTime/1000), 0).Format("2006-01-02 15:04:05")) + } + } + return status +} + +func (s *TelegramService) StartRun() { + logger.Info("telegram service ready to run") + s.settingService = SettingService{} + tgBottoken, err := s.settingService.GetTgBotToken() + if err != nil || tgBottoken == "" { + logger.Infof("telegram service start run failed,GetTgBotToken fail,err:%v,tgBottoken:%s", err, tgBottoken) + return + } + logger.Infof("TelegramService GetTgBotToken:%s", tgBottoken) + botInstace, err = tgbotapi.NewBotAPI(tgBottoken) + if err != nil { + logger.Infof("telegram service start run failed,NewBotAPI fail:%v,tgBottoken:%s", err, tgBottoken) + return + } + botInstace.Debug = false + fmt.Printf("Authorized on account %s", botInstace.Self.UserName) + //get all my commands + commands, err := botInstace.GetMyCommands() + if err != nil { + logger.Warning("telegram service start run error,GetMyCommandsfail:", err) + } + for _, command := range commands { + fmt.Printf("command %s,Description:%s \r\n", command.Command, command.Description) + } + //get update + chanMessage := tgbotapi.NewUpdate(0) + chanMessage.Timeout = 60 + + updates := botInstace.GetUpdatesChan(chanMessage) + + for update := range updates { + if update.Message == nil { + //NOTE:may ther are different bot instance,we could use different bot endApiPoint + updates.Clear() + continue + } + + if !update.Message.IsCommand() { + continue + } + + msg := tgbotapi.NewMessage(update.Message.Chat.ID, "") + + // Extract the command from the Message. + switch update.Message.Command() { + case "delete": + inboundPortStr := update.Message.CommandArguments() + inboundPortValue, err := strconv.Atoi(inboundPortStr) + if err != nil { + msg.Text = "Invalid inbound port,please check it" + break + } + //logger.Infof("Will delete port:%d inbound", inboundPortValue) + error := s.inboundService.DelInboundByPort(inboundPortValue) + if error != nil { + msg.Text = fmt.Sprintf("delete inbound whoes port is %d failed", inboundPortValue) + } else { + msg.Text = fmt.Sprintf("delete inbound whoes port is %d success", inboundPortValue) + } + case "restart": + err := s.xrayService.RestartXray(true) + if err != nil { + msg.Text = fmt.Sprintln("Restart xray failed,error:", err) + } else { + msg.Text = "Restart xray success" + } + case "disable": + inboundPortStr := update.Message.CommandArguments() + inboundPortValue, err := strconv.Atoi(inboundPortStr) + if err != nil { + msg.Text = "Invalid inbound port,please check it" + break + } + //logger.Infof("Will delete port:%d inbound", inboundPortValue) + error := s.inboundService.DisableInboundByPort(inboundPortValue) + if error != nil { + msg.Text = fmt.Sprintf("disable inbound whoes port is %d failed,err:%s", inboundPortValue, error) + } else { + msg.Text = fmt.Sprintf("disable inbound whoes port is %d success", inboundPortValue) + } + case "enable": + inboundPortStr := update.Message.CommandArguments() + inboundPortValue, err := strconv.Atoi(inboundPortStr) + if err != nil { + msg.Text = "Invalid inbound port,please check it" + break + } + //logger.Infof("Will delete port:%d inbound", inboundPortValue) + error := s.inboundService.EnableInboundByPort(inboundPortValue) + if error != nil { + msg.Text = fmt.Sprintf("enable inbound whoes port is %d failed,err:%s", inboundPortValue, error) + } else { + msg.Text = fmt.Sprintf("enable inbound whoes port is %d success", inboundPortValue) + } + case "clear": + inboundPortStr := update.Message.CommandArguments() + inboundPortValue, err := strconv.Atoi(inboundPortStr) + if err != nil { + msg.Text = "Invalid inbound port,please check it" + break + } + error := s.inboundService.ClearTrafficByPort(inboundPortValue) + if error != nil { + msg.Text = fmt.Sprintf("Clear Traffic whose port is %d failed,err:%s", inboundPortValue, error) + } else { + msg.Text = fmt.Sprintf("Clear Traffic whose port is %d success", inboundPortValue) + } + + case "clearall": + error := s.inboundService.ClearAllInboundTraffic() + if error != nil { + msg.Text = fmt.Sprintf("Clear All inbound Traffic failed,err:%s", error) + } else { + msg.Text = fmt.Sprintf("Clear All inbound Traffic success") + } + case "version": + versionStr := update.Message.CommandArguments() + currentVersion, _ := s.serverService.GetXrayVersions() + if currentVersion[0] == versionStr { + msg.Text = fmt.Sprintf("can't change same version to %s", versionStr) + } + error := s.serverService.UpdateXray(versionStr) + if error != nil { + msg.Text = fmt.Sprintf("change version to %s failed,err:%s", versionStr, error) + } else { + msg.Text = fmt.Sprintf("change version to %s success", versionStr) + } + case "status": + msg.Text = s.GetsystemStatus() + default: + //NOTE:here we need string as a new line each one,we should use `` + msg.Text = `/delete will help you delete inbound according port +/restart will restart xray,this command will not restart xray-ui +/status will get current system info +/enable will enable inbound according port +/disable will disable inbound according port +/clear will clear inbound traffic accoring port +/clearall will cleal all inbouns traffic +/version will change xray version to specific one +You can input /help to see more commands` + } + + if _, err := botInstace.Send(msg); err != nil { + log.Panic(err) + } + } + +} + +func (s *TelegramService) SendMsgToTgbot(msg string) { + logger.Info("SendMsgToTgbot entered") + tgBotid, err := s.settingService.GetTgBotChatId() + if err != nil { + logger.Warning("sendMsgToTgbot failed,GetTgBotChatId fail:", err) + return + } + if tgBotid == 0 { + logger.Warning("sendMsgToTgbot failed,GetTgBotChatId illegal") + return + } + + info := tgbotapi.NewMessage(int64(tgBotid), msg) + if botInstace != nil { + botInstace.Send(info) + } else { + logger.Warning("bot instance is nil") + } +} + +//NOTE:This function can't be called repeatly +func (s *TelegramService) StopRunAndClose() { + if botInstace != nil { + botInstace.StopReceivingUpdates() + } +} diff --git a/web/service/user.go b/web/service/user.go index a5317b84..de73b1c4 100644 --- a/web/service/user.go +++ b/web/service/user.go @@ -1,73 +1,73 @@ -package service - -import ( - "errors" - "xray-ui/database" - "xray-ui/database/model" - "xray-ui/logger" - - "gorm.io/gorm" -) - -type UserService struct { -} - -func (s *UserService) GetFirstUser() (*model.User, error) { - db := database.GetDB() - - user := &model.User{} - err := db.Model(model.User{}). - First(user). - Error - if err != nil { - return nil, err - } - return user, nil -} - -func (s *UserService) CheckUser(username string, password string) *model.User { - db := database.GetDB() - - user := &model.User{} - err := db.Model(model.User{}). - Where("username = ? and password = ?", username, password). - First(user). - Error - if err == gorm.ErrRecordNotFound { - return nil - } else if err != nil { - logger.Warning("check user err:", err) - return nil - } - return user -} - -func (s *UserService) UpdateUser(id int, username string, password string) error { - db := database.GetDB() - return db.Model(model.User{}). - Where("id = ?", id). - Update("username", username). - Update("password", password). - Error -} - -func (s *UserService) UpdateFirstUser(username string, password string) error { - if username == "" { - return errors.New("username can not be empty") - } else if password == "" { - return errors.New("password can not be empty") - } - db := database.GetDB() - user := &model.User{} - err := db.Model(model.User{}).First(user).Error - if database.IsNotFound(err) { - user.Username = username - user.Password = password - return db.Model(model.User{}).Create(user).Error - } else if err != nil { - return err - } - user.Username = username - user.Password = password - return db.Save(user).Error -} +package service + +import ( + "errors" + "xray-ui/database" + "xray-ui/database/model" + "xray-ui/logger" + + "gorm.io/gorm" +) + +type UserService struct { +} + +func (s *UserService) GetFirstUser() (*model.User, error) { + db := database.GetDB() + + user := &model.User{} + err := db.Model(model.User{}). + First(user). + Error + if err != nil { + return nil, err + } + return user, nil +} + +func (s *UserService) CheckUser(username string, password string) *model.User { + db := database.GetDB() + + user := &model.User{} + err := db.Model(model.User{}). + Where("username = ? and password = ?", username, password). + First(user). + Error + if err == gorm.ErrRecordNotFound { + return nil + } else if err != nil { + logger.Warning("check user err:", err) + return nil + } + return user +} + +func (s *UserService) UpdateUser(id int, username string, password string) error { + db := database.GetDB() + return db.Model(model.User{}). + Where("id = ?", id). + Update("username", username). + Update("password", password). + Error +} + +func (s *UserService) UpdateFirstUser(username string, password string) error { + if username == "" { + return errors.New("username can not be empty") + } else if password == "" { + return errors.New("password can not be empty") + } + db := database.GetDB() + user := &model.User{} + err := db.Model(model.User{}).First(user).Error + if database.IsNotFound(err) { + user.Username = username + user.Password = password + return db.Model(model.User{}).Create(user).Error + } else if err != nil { + return err + } + user.Username = username + user.Password = password + return db.Save(user).Error +} diff --git a/web/service/xray.go b/web/service/xray.go index 14399d1c..6c9ab843 100644 --- a/web/service/xray.go +++ b/web/service/xray.go @@ -1,127 +1,127 @@ -package service - -import ( - "encoding/json" - "errors" - "sync" - "xray-ui/logger" - "xray-ui/xray" - - "go.uber.org/atomic" -) - -var p *xray.Process -var lock sync.Mutex -var isNeedXrayRestart atomic.Bool -var result string - -type XrayService struct { - inboundService InboundService - settingService SettingService -} - -func (s *XrayService) IsXrayRunning() bool { - return p != nil && p.IsRunning() -} - -func (s *XrayService) GetXrayErr() error { - if p == nil { - return nil - } - return p.GetErr() -} - -func (s *XrayService) GetXrayResult() string { - if result != "" { - return result - } - if s.IsXrayRunning() { - return "" - } - if p == nil { - return "" - } - result = p.GetResult() - return result -} - -func (s *XrayService) GetXrayVersion() string { - if p == nil { - return "Unknown" - } - return p.GetVersion() -} - -func (s *XrayService) GetXrayConfig() (*xray.Config, error) { - templateConfig, err := s.settingService.GetXrayConfigTemplate() - if err != nil { - return nil, err - } - - xrayConfig := &xray.Config{} - err = json.Unmarshal([]byte(templateConfig), xrayConfig) - if err != nil { - return nil, err - } - - inbounds, err := s.inboundService.GetAllInbounds() - if err != nil { - return nil, err - } - for _, inbound := range inbounds { - if !inbound.Enable { - continue - } - inboundConfig := inbound.GenXrayInboundConfig() - xrayConfig.InboundConfigs = append(xrayConfig.InboundConfigs, *inboundConfig) - } - return xrayConfig, nil -} - -func (s *XrayService) GetXrayTraffic() ([]*xray.Traffic, error) { - if !s.IsXrayRunning() { - return nil, errors.New("xray is not running") - } - return p.GetTraffic(true) -} - -func (s *XrayService) RestartXray(isForce bool) error { - lock.Lock() - defer lock.Unlock() - logger.Debug("restart xray, force:", isForce) - - xrayConfig, err := s.GetXrayConfig() - if err != nil { - return err - } - - if p != nil && p.IsRunning() { - if !isForce && p.GetConfig().Equals(xrayConfig) { - logger.Debug("not need to restart xray") - return nil - } - p.Stop() - } - - p = xray.NewProcess(xrayConfig) - result = "" - return p.Start() -} - -func (s *XrayService) StopXray() error { - lock.Lock() - defer lock.Unlock() - logger.Debug("stop xray") - if s.IsXrayRunning() { - return p.Stop() - } - return errors.New("xray is not running") -} - -func (s *XrayService) SetToNeedRestart() { - isNeedXrayRestart.Store(true) -} - -func (s *XrayService) IsNeedRestartAndSetFalse() bool { - return isNeedXrayRestart.CompareAndSwap(true, false) -} +package service + +import ( + "encoding/json" + "errors" + "sync" + "xray-ui/logger" + "xray-ui/xray" + + "go.uber.org/atomic" +) + +var p *xray.Process +var lock sync.Mutex +var isNeedXrayRestart atomic.Bool +var result string + +type XrayService struct { + inboundService InboundService + settingService SettingService +} + +func (s *XrayService) IsXrayRunning() bool { + return p != nil && p.IsRunning() +} + +func (s *XrayService) GetXrayErr() error { + if p == nil { + return nil + } + return p.GetErr() +} + +func (s *XrayService) GetXrayResult() string { + if result != "" { + return result + } + if s.IsXrayRunning() { + return "" + } + if p == nil { + return "" + } + result = p.GetResult() + return result +} + +func (s *XrayService) GetXrayVersion() string { + if p == nil { + return "Unknown" + } + return p.GetVersion() +} + +func (s *XrayService) GetXrayConfig() (*xray.Config, error) { + templateConfig, err := s.settingService.GetXrayConfigTemplate() + if err != nil { + return nil, err + } + + xrayConfig := &xray.Config{} + err = json.Unmarshal([]byte(templateConfig), xrayConfig) + if err != nil { + return nil, err + } + + inbounds, err := s.inboundService.GetAllInbounds() + if err != nil { + return nil, err + } + for _, inbound := range inbounds { + if !inbound.Enable { + continue + } + inboundConfig := inbound.GenXrayInboundConfig() + xrayConfig.InboundConfigs = append(xrayConfig.InboundConfigs, *inboundConfig) + } + return xrayConfig, nil +} + +func (s *XrayService) GetXrayTraffic() ([]*xray.Traffic, error) { + if !s.IsXrayRunning() { + return nil, errors.New("xray is not running") + } + return p.GetTraffic(true) +} + +func (s *XrayService) RestartXray(isForce bool) error { + lock.Lock() + defer lock.Unlock() + logger.Debug("restart xray, force:", isForce) + + xrayConfig, err := s.GetXrayConfig() + if err != nil { + return err + } + + if p != nil && p.IsRunning() { + if !isForce && p.GetConfig().Equals(xrayConfig) { + logger.Debug("not need to restart xray") + return nil + } + p.Stop() + } + + p = xray.NewProcess(xrayConfig) + result = "" + return p.Start() +} + +func (s *XrayService) StopXray() error { + lock.Lock() + defer lock.Unlock() + logger.Debug("stop xray") + if s.IsXrayRunning() { + return p.Stop() + } + return errors.New("xray is not running") +} + +func (s *XrayService) SetToNeedRestart() { + isNeedXrayRestart.Store(true) +} + +func (s *XrayService) IsNeedRestartAndSetFalse() bool { + return isNeedXrayRestart.CompareAndSwap(true, false) +} diff --git a/web/session/session.go b/web/session/session.go index 3d9c7c8a..79ba1e1b 100644 --- a/web/session/session.go +++ b/web/session/session.go @@ -1,47 +1,47 @@ -package session - -import ( - "encoding/gob" - "xray-ui/database/model" - - "github.com/gin-contrib/sessions" - "github.com/gin-gonic/gin" -) - -const ( - loginUser = "LOGIN_USER" -) - -func init() { - gob.Register(model.User{}) -} - -func SetLoginUser(c *gin.Context, user *model.User) error { - s := sessions.Default(c) - s.Set(loginUser, user) - return s.Save() -} - -func GetLoginUser(c *gin.Context) *model.User { - s := sessions.Default(c) - obj := s.Get(loginUser) - if obj == nil { - return nil - } - user := obj.(model.User) - return &user -} - -func IsLogin(c *gin.Context) bool { - return GetLoginUser(c) != nil -} - -func ClearSession(c *gin.Context) { - s := sessions.Default(c) - s.Clear() - s.Options(sessions.Options{ - Path: "/", - MaxAge: -1, - }) - s.Save() -} +package session + +import ( + "encoding/gob" + "xray-ui/database/model" + + "github.com/gin-contrib/sessions" + "github.com/gin-gonic/gin" +) + +const ( + loginUser = "LOGIN_USER" +) + +func init() { + gob.Register(model.User{}) +} + +func SetLoginUser(c *gin.Context, user *model.User) error { + s := sessions.Default(c) + s.Set(loginUser, user) + return s.Save() +} + +func GetLoginUser(c *gin.Context) *model.User { + s := sessions.Default(c) + obj := s.Get(loginUser) + if obj == nil { + return nil + } + user := obj.(model.User) + return &user +} + +func IsLogin(c *gin.Context) bool { + return GetLoginUser(c) != nil +} + +func ClearSession(c *gin.Context) { + s := sessions.Default(c) + s.Clear() + s.Options(sessions.Options{ + Path: "/", + MaxAge: -1, + }) + s.Save() +} diff --git a/web/translation/translate.en_US.toml b/web/translation/translate.en_US.toml index 599e4539..0a288e7f 100644 --- a/web/translation/translate.en_US.toml +++ b/web/translation/translate.en_US.toml @@ -1,12 +1,12 @@ -"username" = "username" -"password" = "password" -"login" = "login" -"confirm" = "confirm" -"cancel" = "cancel" -"close" = "close" -"copy" = "copy" -"copied" = "copied" -"download" = "download" -"remark" = "remark" -"enable" = "enable" +"username" = "username" +"password" = "password" +"login" = "login" +"confirm" = "confirm" +"cancel" = "cancel" +"close" = "close" +"copy" = "copy" +"copied" = "copied" +"download" = "download" +"remark" = "remark" +"enable" = "enable" "protocol" = "protocol" \ No newline at end of file diff --git a/web/translation/translate.zh_Hans.toml b/web/translation/translate.zh_Hans.toml index 84d10639..1c751cfb 100644 --- a/web/translation/translate.zh_Hans.toml +++ b/web/translation/translate.zh_Hans.toml @@ -1,12 +1,12 @@ -"username" = "用户名" -"password" = "密码" -"login" = "登录" -"confirm" = "确定" -"cancel" = "取消" -"close" = "关闭" -"copy" = "复制" -"copied" = "已复制" -"download" = "下载" -"remark" = "备注" -"enable" = "启用" +"username" = "用户名" +"password" = "密码" +"login" = "登录" +"confirm" = "确定" +"cancel" = "取消" +"close" = "关闭" +"copy" = "复制" +"copied" = "已复制" +"download" = "下载" +"remark" = "备注" +"enable" = "启用" "protocol" = "协议" \ No newline at end of file diff --git a/web/translation/translate.zh_Hant.toml b/web/translation/translate.zh_Hant.toml index 04a380a3..ceda43cb 100644 --- a/web/translation/translate.zh_Hant.toml +++ b/web/translation/translate.zh_Hant.toml @@ -1,12 +1,12 @@ -"username" = "用戶名" -"password" = "密碼" -"login" = "登錄" -"confirm" = "確定" -"cancel" = "取消" -"close" = "關閉" -"copy" = "複製" -"copied" = "已複製" -"download" = "下載" -"remark" = "備註" -"enable" = "啟用" +"username" = "用戶名" +"password" = "密碼" +"login" = "登錄" +"confirm" = "確定" +"cancel" = "取消" +"close" = "關閉" +"copy" = "複製" +"copied" = "已複製" +"download" = "下載" +"remark" = "備註" +"enable" = "啟用" "protocol" = "協議" \ No newline at end of file diff --git a/web/web.go b/web/web.go index 0ea0714d..f4579240 100644 --- a/web/web.go +++ b/web/web.go @@ -1,447 +1,447 @@ -package web - -import ( - "context" - "crypto/tls" - "embed" - "html/template" - "io" - "io/fs" - "net" - "net/http" - "os" - "strconv" - "strings" - "time" - "xray-ui/config" - "xray-ui/logger" - "xray-ui/util/common" - "xray-ui/web/controller" - "xray-ui/web/job" - "xray-ui/web/network" - "xray-ui/web/service" - - "github.com/BurntSushi/toml" - "github.com/gin-contrib/sessions" - "github.com/gin-contrib/sessions/cookie" - "github.com/gin-gonic/gin" - "github.com/nicksnyder/go-i18n/v2/i18n" - "github.com/robfig/cron/v3" - "golang.org/x/text/language" -) - -//go:embed assets/* -var assetsFS embed.FS - -//go:embed html/* -var htmlFS embed.FS - -//go:embed translation/* -var i18nFS embed.FS - -var startTime = time.Now() - -var xuiBeginRunTime string - -var isTelegramEnable bool - -type wrapAssetsFS struct { - embed.FS -} - -func GetXuiStarttime() string { - return xuiBeginRunTime -} - -func (f *wrapAssetsFS) Open(name string) (fs.File, error) { - file, err := f.FS.Open("assets/" + name) - if err != nil { - return nil, err - } - return &wrapAssetsFile{ - File: file, - }, nil -} - -type wrapAssetsFile struct { - fs.File -} - -func (f *wrapAssetsFile) Stat() (fs.FileInfo, error) { - info, err := f.File.Stat() - if err != nil { - return nil, err - } - return &wrapAssetsFileInfo{ - FileInfo: info, - }, nil -} - -type wrapAssetsFileInfo struct { - fs.FileInfo -} - -func (f *wrapAssetsFileInfo) ModTime() time.Time { - return startTime -} - -type Server struct { - httpServer *http.Server - listener net.Listener - - index *controller.IndexController - server *controller.ServerController - xui *controller.XUIController - - xrayService service.XrayService - settingService service.SettingService - inboundService service.InboundService - telegramService service.TelegramService - - cron *cron.Cron - - ctx context.Context - cancel context.CancelFunc -} - -func NewServer() *Server { - ctx, cancel := context.WithCancel(context.Background()) - return &Server{ - ctx: ctx, - cancel: cancel, - } -} - -func (s *Server) getHtmlFiles() ([]string, error) { - files := make([]string, 0) - dir, _ := os.Getwd() - err := fs.WalkDir(os.DirFS(dir), "web/html", func(path string, d fs.DirEntry, err error) error { - if err != nil { - return err - } - if d.IsDir() { - return nil - } - files = append(files, path) - return nil - }) - if err != nil { - return nil, err - } - return files, nil -} - -func (s *Server) getHtmlTemplate(funcMap template.FuncMap) (*template.Template, error) { - t := template.New("").Funcs(funcMap) - err := fs.WalkDir(htmlFS, "html", func(path string, d fs.DirEntry, err error) error { - if err != nil { - return err - } - - if d.IsDir() { - newT, err := t.ParseFS(htmlFS, path+"/*.html") - if err != nil { - // ignore - return nil - } - t = newT - } - return nil - }) - if err != nil { - return nil, err - } - return t, nil -} - -func (s *Server) initRouter() (*gin.Engine, error) { - if config.IsDebug() { - gin.SetMode(gin.DebugMode) - } else { - gin.DefaultWriter = io.Discard - gin.DefaultErrorWriter = io.Discard - gin.SetMode(gin.ReleaseMode) - } - - engine := gin.Default() - - // Add favicon - engine.StaticFile("/favicon.ico", "web/assets/favicon.ico") - - secret, err := s.settingService.GetSecret() - if err != nil { - return nil, err - } - - basePath, err := s.settingService.GetBasePath() - if err != nil { - return nil, err - } - assetsBasePath := basePath + "assets/" - - store := cookie.NewStore(secret) - engine.Use(sessions.Sessions("session", store)) - engine.Use(func(c *gin.Context) { - c.Set("base_path", basePath) - }) - engine.Use(func(c *gin.Context) { - uri := c.Request.RequestURI - if strings.HasPrefix(uri, assetsBasePath) { - c.Header("Cache-Control", "max-age=31536000") - } - }) - err = s.initI18n(engine) - if err != nil { - return nil, err - } - - if config.IsDebug() { - // for develop - files, err := s.getHtmlFiles() - if err != nil { - return nil, err - } - engine.LoadHTMLFiles(files...) - engine.StaticFS(basePath+"assets", http.FS(os.DirFS("web/assets"))) - } else { - // for prod - t, err := s.getHtmlTemplate(engine.FuncMap) - if err != nil { - return nil, err - } - engine.SetHTMLTemplate(t) - engine.StaticFS(basePath+"assets", http.FS(&wrapAssetsFS{FS: assetsFS})) - } - - g := engine.Group(basePath) - - s.index = controller.NewIndexController(g) - s.server = controller.NewServerController(g) - s.xui = controller.NewXUIController(g) - - return engine, nil -} - -func (s *Server) initI18n(engine *gin.Engine) error { - bundle := i18n.NewBundle(language.SimplifiedChinese) - bundle.RegisterUnmarshalFunc("toml", toml.Unmarshal) - err := fs.WalkDir(i18nFS, "translation", func(path string, d fs.DirEntry, err error) error { - if err != nil { - return err - } - if d.IsDir() { - return nil - } - data, err := i18nFS.ReadFile(path) - if err != nil { - return err - } - _, err = bundle.ParseMessageFileBytes(data, path) - return err - }) - if err != nil { - return err - } - - findI18nParamNames := func(key string) []string { - names := make([]string, 0) - keyLen := len(key) - for i := 0; i < keyLen-1; i++ { - if key[i:i+2] == "{{" { // 判断开头 "{{" - j := i + 2 - isFind := false - for ; j < keyLen-1; j++ { - if key[j:j+2] == "}}" { // 结尾 "}}" - isFind = true - break - } - } - if isFind { - names = append(names, key[i+3:j]) - } - } - } - return names - } - - var localizer *i18n.Localizer - - engine.FuncMap["i18n"] = func(key string, params ...string) (string, error) { - names := findI18nParamNames(key) - if len(names) != len(params) { - return "", common.NewError("find names:", names, "---------- params:", params, "---------- num not equal") - } - templateData := map[string]interface{}{} - for i := range names { - templateData[names[i]] = params[i] - } - return localizer.Localize(&i18n.LocalizeConfig{ - MessageID: key, - TemplateData: templateData, - }) - } - - engine.Use(func(c *gin.Context) { - accept := c.GetHeader("Accept-Language") - localizer = i18n.NewLocalizer(bundle, accept) - c.Set("localizer", localizer) - c.Next() - }) - - return nil -} - -func (s *Server) startTask() { - err := s.xrayService.RestartXray(true) - if err != nil { - logger.Warning("start xray failed:", err) - } - // 每 30 秒检查一次 xray 是否在运行 - s.cron.AddJob("@every 30s", job.NewCheckXrayRunningJob()) - - go func() { - time.Sleep(time.Second * 5) - // 每 10 秒统计一次流量,首次启动延迟 5 秒,与重启 xray 的时间错开 - s.cron.AddJob("@every 10s", job.NewXrayTrafficJob()) - }() - - // 每 30 秒检查一次 inbound 流量超出和到期的情况 - s.cron.AddJob("@every 30s", job.NewCheckInboundJob()) - //每2s检查一次SSH信息 - s.cron.AddFunc("@every 2s", func() { job.NewStatsNotifyJob().SSHStatusLoginNotify(xuiBeginRunTime) }) - // 每一天提示一次流量情况,上海时间8点30 - var entry cron.EntryID - - if isTelegramEnable { - runtime, err := s.settingService.GetTgbotRuntime() - if err != nil || runtime == "" { - logger.Errorf("Add NewStatsNotifyJob error[%s],Runtime[%s] invalid,wil run default", err, runtime) - runtime = "@daily" - } - logger.Infof("Tg notify enabled,run at %s", runtime) - entry, err = s.cron.AddJob(runtime, job.NewStatsNotifyJob()) - if err != nil { - logger.Warning("Add NewStatsNotifyJob error", err) - return - } - } else { - s.cron.Remove(entry) - } -} - -func (s *Server) Start() (err error) { - //这是一个匿名函数,没没有函数名 - defer func() { - if err != nil { - s.Stop() - } - }() - - loc, err := s.settingService.GetTimeLocation() - if err != nil { - return err - } - s.cron = cron.New(cron.WithLocation(loc), cron.WithSeconds()) - s.cron.Start() - - engine, err := s.initRouter() - if err != nil { - return err - } - - certFile, err := s.settingService.GetCertFile() - if err != nil { - return err - } - keyFile, err := s.settingService.GetKeyFile() - if err != nil { - return err - } - listen, err := s.settingService.GetListen() - if err != nil { - return err - } - port, err := s.settingService.GetPort() - if err != nil { - return err - } - listenAddr := net.JoinHostPort(listen, strconv.Itoa(port)) - listener, err := net.Listen("tcp", listenAddr) - if err != nil { - return err - } - if certFile != "" || keyFile != "" { - cert, err := tls.LoadX509KeyPair(certFile, keyFile) - if err != nil { - listener.Close() - return err - } - c := &tls.Config{ - Certificates: []tls.Certificate{cert}, - } - listener = network.NewAutoHttpsListener(listener) - listener = tls.NewListener(listener, c) - } - - if certFile != "" || keyFile != "" { - logger.Info("web server run https on", listener.Addr()) - } else { - logger.Info("web server run http on", listener.Addr()) - } - s.listener = listener - - xuiBeginRunTime = time.Now().Format("2006-01-02 15:04:05") - - isTgbotenabled, err := s.settingService.GetTgbotenabled() - if (err == nil) && (isTgbotenabled) { - isTelegramEnable = true - - go func() { - s.telegramService.StartRun() - time.Sleep(time.Second * 2) - }() - - } else { - isTelegramEnable = false - } - - s.startTask() - - s.httpServer = &http.Server{ - Handler: engine, - } - - go func() { - s.httpServer.Serve(listener) - }() - - return nil -} - -func (s *Server) Stop() error { - s.cancel() - if isTelegramEnable { - s.telegramService.StopRunAndClose() - } - s.xrayService.StopXray() - if s.cron != nil { - s.cron.Stop() - } - var err1 error - var err2 error - if s.httpServer != nil { - err1 = s.httpServer.Shutdown(s.ctx) - } - if s.listener != nil { - err2 = s.listener.Close() - } - return common.Combine(err1, err2) -} - -func (s *Server) GetCtx() context.Context { - return s.ctx -} - -func (s *Server) GetCron() *cron.Cron { - return s.cron -} +package web + +import ( + "context" + "crypto/tls" + "embed" + "html/template" + "io" + "io/fs" + "net" + "net/http" + "os" + "strconv" + "strings" + "time" + "xray-ui/config" + "xray-ui/logger" + "xray-ui/util/common" + "xray-ui/web/controller" + "xray-ui/web/job" + "xray-ui/web/network" + "xray-ui/web/service" + + "github.com/BurntSushi/toml" + "github.com/gin-contrib/sessions" + "github.com/gin-contrib/sessions/cookie" + "github.com/gin-gonic/gin" + "github.com/nicksnyder/go-i18n/v2/i18n" + "github.com/robfig/cron/v3" + "golang.org/x/text/language" +) + +//go:embed assets/* +var assetsFS embed.FS + +//go:embed html/* +var htmlFS embed.FS + +//go:embed translation/* +var i18nFS embed.FS + +var startTime = time.Now() + +var xuiBeginRunTime string + +var isTelegramEnable bool + +type wrapAssetsFS struct { + embed.FS +} + +func GetXuiStarttime() string { + return xuiBeginRunTime +} + +func (f *wrapAssetsFS) Open(name string) (fs.File, error) { + file, err := f.FS.Open("assets/" + name) + if err != nil { + return nil, err + } + return &wrapAssetsFile{ + File: file, + }, nil +} + +type wrapAssetsFile struct { + fs.File +} + +func (f *wrapAssetsFile) Stat() (fs.FileInfo, error) { + info, err := f.File.Stat() + if err != nil { + return nil, err + } + return &wrapAssetsFileInfo{ + FileInfo: info, + }, nil +} + +type wrapAssetsFileInfo struct { + fs.FileInfo +} + +func (f *wrapAssetsFileInfo) ModTime() time.Time { + return startTime +} + +type Server struct { + httpServer *http.Server + listener net.Listener + + index *controller.IndexController + server *controller.ServerController + xui *controller.XUIController + + xrayService service.XrayService + settingService service.SettingService + inboundService service.InboundService + telegramService service.TelegramService + + cron *cron.Cron + + ctx context.Context + cancel context.CancelFunc +} + +func NewServer() *Server { + ctx, cancel := context.WithCancel(context.Background()) + return &Server{ + ctx: ctx, + cancel: cancel, + } +} + +func (s *Server) getHtmlFiles() ([]string, error) { + files := make([]string, 0) + dir, _ := os.Getwd() + err := fs.WalkDir(os.DirFS(dir), "web/html", func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + if d.IsDir() { + return nil + } + files = append(files, path) + return nil + }) + if err != nil { + return nil, err + } + return files, nil +} + +func (s *Server) getHtmlTemplate(funcMap template.FuncMap) (*template.Template, error) { + t := template.New("").Funcs(funcMap) + err := fs.WalkDir(htmlFS, "html", func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + + if d.IsDir() { + newT, err := t.ParseFS(htmlFS, path+"/*.html") + if err != nil { + // ignore + return nil + } + t = newT + } + return nil + }) + if err != nil { + return nil, err + } + return t, nil +} + +func (s *Server) initRouter() (*gin.Engine, error) { + if config.IsDebug() { + gin.SetMode(gin.DebugMode) + } else { + gin.DefaultWriter = io.Discard + gin.DefaultErrorWriter = io.Discard + gin.SetMode(gin.ReleaseMode) + } + + engine := gin.Default() + + // Add favicon + engine.StaticFile("/favicon.ico", "web/assets/favicon.ico") + + secret, err := s.settingService.GetSecret() + if err != nil { + return nil, err + } + + basePath, err := s.settingService.GetBasePath() + if err != nil { + return nil, err + } + assetsBasePath := basePath + "assets/" + + store := cookie.NewStore(secret) + engine.Use(sessions.Sessions("session", store)) + engine.Use(func(c *gin.Context) { + c.Set("base_path", basePath) + }) + engine.Use(func(c *gin.Context) { + uri := c.Request.RequestURI + if strings.HasPrefix(uri, assetsBasePath) { + c.Header("Cache-Control", "max-age=31536000") + } + }) + err = s.initI18n(engine) + if err != nil { + return nil, err + } + + if config.IsDebug() { + // for develop + files, err := s.getHtmlFiles() + if err != nil { + return nil, err + } + engine.LoadHTMLFiles(files...) + engine.StaticFS(basePath+"assets", http.FS(os.DirFS("web/assets"))) + } else { + // for prod + t, err := s.getHtmlTemplate(engine.FuncMap) + if err != nil { + return nil, err + } + engine.SetHTMLTemplate(t) + engine.StaticFS(basePath+"assets", http.FS(&wrapAssetsFS{FS: assetsFS})) + } + + g := engine.Group(basePath) + + s.index = controller.NewIndexController(g) + s.server = controller.NewServerController(g) + s.xui = controller.NewXUIController(g) + + return engine, nil +} + +func (s *Server) initI18n(engine *gin.Engine) error { + bundle := i18n.NewBundle(language.SimplifiedChinese) + bundle.RegisterUnmarshalFunc("toml", toml.Unmarshal) + err := fs.WalkDir(i18nFS, "translation", func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + if d.IsDir() { + return nil + } + data, err := i18nFS.ReadFile(path) + if err != nil { + return err + } + _, err = bundle.ParseMessageFileBytes(data, path) + return err + }) + if err != nil { + return err + } + + findI18nParamNames := func(key string) []string { + names := make([]string, 0) + keyLen := len(key) + for i := 0; i < keyLen-1; i++ { + if key[i:i+2] == "{{" { // 判断开头 "{{" + j := i + 2 + isFind := false + for ; j < keyLen-1; j++ { + if key[j:j+2] == "}}" { // 结尾 "}}" + isFind = true + break + } + } + if isFind { + names = append(names, key[i+3:j]) + } + } + } + return names + } + + var localizer *i18n.Localizer + + engine.FuncMap["i18n"] = func(key string, params ...string) (string, error) { + names := findI18nParamNames(key) + if len(names) != len(params) { + return "", common.NewError("find names:", names, "---------- params:", params, "---------- num not equal") + } + templateData := map[string]interface{}{} + for i := range names { + templateData[names[i]] = params[i] + } + return localizer.Localize(&i18n.LocalizeConfig{ + MessageID: key, + TemplateData: templateData, + }) + } + + engine.Use(func(c *gin.Context) { + accept := c.GetHeader("Accept-Language") + localizer = i18n.NewLocalizer(bundle, accept) + c.Set("localizer", localizer) + c.Next() + }) + + return nil +} + +func (s *Server) startTask() { + err := s.xrayService.RestartXray(true) + if err != nil { + logger.Warning("start xray failed:", err) + } + // 每 30 秒检查一次 xray 是否在运行 + s.cron.AddJob("@every 30s", job.NewCheckXrayRunningJob()) + + go func() { + time.Sleep(time.Second * 5) + // 每 10 秒统计一次流量,首次启动延迟 5 秒,与重启 xray 的时间错开 + s.cron.AddJob("@every 10s", job.NewXrayTrafficJob()) + }() + + // 每 30 秒检查一次 inbound 流量超出和到期的情况 + s.cron.AddJob("@every 30s", job.NewCheckInboundJob()) + //每2s检查一次SSH信息 + s.cron.AddFunc("@every 2s", func() { job.NewStatsNotifyJob().SSHStatusLoginNotify(xuiBeginRunTime) }) + // 每一天提示一次流量情况,上海时间8点30 + var entry cron.EntryID + + if isTelegramEnable { + runtime, err := s.settingService.GetTgbotRuntime() + if err != nil || runtime == "" { + logger.Errorf("Add NewStatsNotifyJob error[%s],Runtime[%s] invalid,wil run default", err, runtime) + runtime = "@daily" + } + logger.Infof("Tg notify enabled,run at %s", runtime) + entry, err = s.cron.AddJob(runtime, job.NewStatsNotifyJob()) + if err != nil { + logger.Warning("Add NewStatsNotifyJob error", err) + return + } + } else { + s.cron.Remove(entry) + } +} + +func (s *Server) Start() (err error) { + //这是一个匿名函数,没没有函数名 + defer func() { + if err != nil { + s.Stop() + } + }() + + loc, err := s.settingService.GetTimeLocation() + if err != nil { + return err + } + s.cron = cron.New(cron.WithLocation(loc), cron.WithSeconds()) + s.cron.Start() + + engine, err := s.initRouter() + if err != nil { + return err + } + + certFile, err := s.settingService.GetCertFile() + if err != nil { + return err + } + keyFile, err := s.settingService.GetKeyFile() + if err != nil { + return err + } + listen, err := s.settingService.GetListen() + if err != nil { + return err + } + port, err := s.settingService.GetPort() + if err != nil { + return err + } + listenAddr := net.JoinHostPort(listen, strconv.Itoa(port)) + listener, err := net.Listen("tcp", listenAddr) + if err != nil { + return err + } + if certFile != "" || keyFile != "" { + cert, err := tls.LoadX509KeyPair(certFile, keyFile) + if err != nil { + listener.Close() + return err + } + c := &tls.Config{ + Certificates: []tls.Certificate{cert}, + } + listener = network.NewAutoHttpsListener(listener) + listener = tls.NewListener(listener, c) + } + + if certFile != "" || keyFile != "" { + logger.Info("web server run https on", listener.Addr()) + } else { + logger.Info("web server run http on", listener.Addr()) + } + s.listener = listener + + xuiBeginRunTime = time.Now().Format("2006-01-02 15:04:05") + + isTgbotenabled, err := s.settingService.GetTgbotenabled() + if (err == nil) && (isTgbotenabled) { + isTelegramEnable = true + + go func() { + s.telegramService.StartRun() + time.Sleep(time.Second * 2) + }() + + } else { + isTelegramEnable = false + } + + s.startTask() + + s.httpServer = &http.Server{ + Handler: engine, + } + + go func() { + s.httpServer.Serve(listener) + }() + + return nil +} + +func (s *Server) Stop() error { + s.cancel() + if isTelegramEnable { + s.telegramService.StopRunAndClose() + } + s.xrayService.StopXray() + if s.cron != nil { + s.cron.Stop() + } + var err1 error + var err2 error + if s.httpServer != nil { + err1 = s.httpServer.Shutdown(s.ctx) + } + if s.listener != nil { + err2 = s.listener.Close() + } + return common.Combine(err1, err2) +} + +func (s *Server) GetCtx() context.Context { + return s.ctx +} + +func (s *Server) GetCron() *cron.Cron { + return s.cron +} diff --git a/xray-ui b/xray-ui deleted file mode 100644 index 17ab26a2..00000000 Binary files a/xray-ui and /dev/null differ diff --git a/xray-ui.service b/xray-ui.service index 963b7d70..88466c43 100644 --- a/xray-ui.service +++ b/xray-ui.service @@ -1,12 +1,12 @@ -[Unit] -Description=xray-ui Service -After=network.target -Wants=network.target - -[Service] -Type=simple -WorkingDirectory=/usr/local/xray-ui/ -ExecStart=/usr/local/xray-ui/xray-ui - -[Install] +[Unit] +Description=xray-ui Service +After=network.target +Wants=network.target + +[Service] +Type=simple +WorkingDirectory=/usr/local/xray-ui/ +ExecStart=/usr/local/xray-ui/xray-ui + +[Install] WantedBy=multi-user.target \ No newline at end of file diff --git a/xray/config.go b/xray/config.go index 3f76a4dc..38e4f678 100644 --- a/xray/config.go +++ b/xray/config.go @@ -1,62 +1,62 @@ -package xray - -import ( - "bytes" - "xray-ui/util/json_util" -) - -type Config struct { - LogConfig json_util.RawMessage `json:"log"` - RouterConfig json_util.RawMessage `json:"routing"` - DNSConfig json_util.RawMessage `json:"dns"` - InboundConfigs []InboundConfig `json:"inbounds"` - OutboundConfigs json_util.RawMessage `json:"outbounds"` - Transport json_util.RawMessage `json:"transport"` - Policy json_util.RawMessage `json:"policy"` - API json_util.RawMessage `json:"api"` - Stats json_util.RawMessage `json:"stats"` - Reverse json_util.RawMessage `json:"reverse"` - FakeDNS json_util.RawMessage `json:"fakeDns"` -} - -func (c *Config) Equals(other *Config) bool { - if len(c.InboundConfigs) != len(other.InboundConfigs) { - return false - } - for i, inbound := range c.InboundConfigs { - if !inbound.Equals(&other.InboundConfigs[i]) { - return false - } - } - if !bytes.Equal(c.LogConfig, other.LogConfig) { - return false - } - if !bytes.Equal(c.RouterConfig, other.RouterConfig) { - return false - } - if !bytes.Equal(c.DNSConfig, other.DNSConfig) { - return false - } - if !bytes.Equal(c.OutboundConfigs, other.OutboundConfigs) { - return false - } - if !bytes.Equal(c.Transport, other.Transport) { - return false - } - if !bytes.Equal(c.Policy, other.Policy) { - return false - } - if !bytes.Equal(c.API, other.API) { - return false - } - if !bytes.Equal(c.Stats, other.Stats) { - return false - } - if !bytes.Equal(c.Reverse, other.Reverse) { - return false - } - if !bytes.Equal(c.FakeDNS, other.FakeDNS) { - return false - } - return true -} +package xray + +import ( + "bytes" + "xray-ui/util/json_util" +) + +type Config struct { + LogConfig json_util.RawMessage `json:"log"` + RouterConfig json_util.RawMessage `json:"routing"` + DNSConfig json_util.RawMessage `json:"dns"` + InboundConfigs []InboundConfig `json:"inbounds"` + OutboundConfigs json_util.RawMessage `json:"outbounds"` + Transport json_util.RawMessage `json:"transport"` + Policy json_util.RawMessage `json:"policy"` + API json_util.RawMessage `json:"api"` + Stats json_util.RawMessage `json:"stats"` + Reverse json_util.RawMessage `json:"reverse"` + FakeDNS json_util.RawMessage `json:"fakeDns"` +} + +func (c *Config) Equals(other *Config) bool { + if len(c.InboundConfigs) != len(other.InboundConfigs) { + return false + } + for i, inbound := range c.InboundConfigs { + if !inbound.Equals(&other.InboundConfigs[i]) { + return false + } + } + if !bytes.Equal(c.LogConfig, other.LogConfig) { + return false + } + if !bytes.Equal(c.RouterConfig, other.RouterConfig) { + return false + } + if !bytes.Equal(c.DNSConfig, other.DNSConfig) { + return false + } + if !bytes.Equal(c.OutboundConfigs, other.OutboundConfigs) { + return false + } + if !bytes.Equal(c.Transport, other.Transport) { + return false + } + if !bytes.Equal(c.Policy, other.Policy) { + return false + } + if !bytes.Equal(c.API, other.API) { + return false + } + if !bytes.Equal(c.Stats, other.Stats) { + return false + } + if !bytes.Equal(c.Reverse, other.Reverse) { + return false + } + if !bytes.Equal(c.FakeDNS, other.FakeDNS) { + return false + } + return true +} diff --git a/xray/inbound.go b/xray/inbound.go index 3db45908..48aff66e 100644 --- a/xray/inbound.go +++ b/xray/inbound.go @@ -1,41 +1,41 @@ -package xray - -import ( - "bytes" - "xray-ui/util/json_util" -) - -type InboundConfig struct { - Listen json_util.RawMessage `json:"listen"` // listen 不能为空字符串 - Port int `json:"port"` - Protocol string `json:"protocol"` - Settings json_util.RawMessage `json:"settings"` - StreamSettings json_util.RawMessage `json:"streamSettings"` - Tag string `json:"tag"` - Sniffing json_util.RawMessage `json:"sniffing"` -} - -func (c *InboundConfig) Equals(other *InboundConfig) bool { - if !bytes.Equal(c.Listen, other.Listen) { - return false - } - if c.Port != other.Port { - return false - } - if c.Protocol != other.Protocol { - return false - } - if !bytes.Equal(c.Settings, other.Settings) { - return false - } - if !bytes.Equal(c.StreamSettings, other.StreamSettings) { - return false - } - if c.Tag != other.Tag { - return false - } - if !bytes.Equal(c.Sniffing, other.Sniffing) { - return false - } - return true -} +package xray + +import ( + "bytes" + "xray-ui/util/json_util" +) + +type InboundConfig struct { + Listen json_util.RawMessage `json:"listen"` // listen 不能为空字符串 + Port int `json:"port"` + Protocol string `json:"protocol"` + Settings json_util.RawMessage `json:"settings"` + StreamSettings json_util.RawMessage `json:"streamSettings"` + Tag string `json:"tag"` + Sniffing json_util.RawMessage `json:"sniffing"` +} + +func (c *InboundConfig) Equals(other *InboundConfig) bool { + if !bytes.Equal(c.Listen, other.Listen) { + return false + } + if c.Port != other.Port { + return false + } + if c.Protocol != other.Protocol { + return false + } + if !bytes.Equal(c.Settings, other.Settings) { + return false + } + if !bytes.Equal(c.StreamSettings, other.StreamSettings) { + return false + } + if c.Tag != other.Tag { + return false + } + if !bytes.Equal(c.Sniffing, other.Sniffing) { + return false + } + return true +} diff --git a/xray/process.go b/xray/process.go index c36aaf20..a3570a31 100644 --- a/xray/process.go +++ b/xray/process.go @@ -1,279 +1,279 @@ -package xray - -import ( - "bufio" - "bytes" - "context" - "encoding/json" - "errors" - "fmt" - "io/fs" - "os" - "os/exec" - "regexp" - "runtime" - "strings" - "time" - "xray-ui/util/common" - - "github.com/Workiva/go-datastructures/queue" - statsservice "github.com/xtls/xray-core/app/stats/command" - "google.golang.org/grpc" -) - -var trafficRegex = regexp.MustCompile("(inbound|outbound)>>>([^>]+)>>>traffic>>>(downlink|uplink)") - -func GetBinaryName() string { - return fmt.Sprintf("xray-%s-%s", runtime.GOOS, runtime.GOARCH) -} - -func GetBinaryPath() string { - return "bin/" + GetBinaryName() -} - -func GetConfigPath() string { - return "bin/config.json" -} - -func GetGeositePath() string { - return "bin/geosite.dat" -} - -func GetGeoipPath() string { - return "bin/geoip.dat" -} - -func stopProcess(p *Process) { - p.Stop() -} - -type Process struct { - *process -} - -func NewProcess(xrayConfig *Config) *Process { - p := &Process{newProcess(xrayConfig)} - runtime.SetFinalizer(p, stopProcess) - return p -} - -type process struct { - cmd *exec.Cmd - - version string - apiPort int - - config *Config - lines *queue.Queue - exitErr error -} - -func newProcess(config *Config) *process { - return &process{ - version: "Unknown", - config: config, - lines: queue.New(100), - } -} - -func (p *process) IsRunning() bool { - if p.cmd == nil || p.cmd.Process == nil { - return false - } - if p.cmd.ProcessState == nil { - return true - } - return false -} - -func (p *process) GetErr() error { - return p.exitErr -} - -func (p *process) GetResult() string { - if p.lines.Empty() && p.exitErr != nil { - return p.exitErr.Error() - } - items, _ := p.lines.TakeUntil(func(item interface{}) bool { - return true - }) - lines := make([]string, 0, len(items)) - for _, item := range items { - lines = append(lines, item.(string)) - } - return strings.Join(lines, "\n") -} - -func (p *process) GetVersion() string { - return p.version -} - -func (p *Process) GetAPIPort() int { - return p.apiPort -} - -func (p *Process) GetConfig() *Config { - return p.config -} - -func (p *process) refreshAPIPort() { - for _, inbound := range p.config.InboundConfigs { - if inbound.Tag == "api" { - p.apiPort = inbound.Port - break - } - } -} - -func (p *process) refreshVersion() { - cmd := exec.Command(GetBinaryPath(), "-version") - data, err := cmd.Output() - if err != nil { - p.version = "Unknown" - } else { - datas := bytes.Split(data, []byte(" ")) - if len(datas) <= 1 { - p.version = "Unknown" - } else { - p.version = string(datas[1]) - } - } -} - -func (p *process) Start() (err error) { - if p.IsRunning() { - return errors.New("xray is already running") - } - - defer func() { - if err != nil { - p.exitErr = err - } - }() - - data, err := json.MarshalIndent(p.config, "", " ") - if err != nil { - return common.NewErrorf("生成 xray 配置文件失败: %v", err) - } - configPath := GetConfigPath() - err = os.WriteFile(configPath, data, fs.ModePerm) - if err != nil { - return common.NewErrorf("写入配置文件失败: %v", err) - } - - cmd := exec.Command(GetBinaryPath(), "-c", configPath) - p.cmd = cmd - - stdReader, err := cmd.StdoutPipe() - if err != nil { - return err - } - errReader, err := cmd.StderrPipe() - if err != nil { - return err - } - - go func() { - defer func() { - common.Recover("") - stdReader.Close() - }() - reader := bufio.NewReaderSize(stdReader, 8192) - for { - line, _, err := reader.ReadLine() - if err != nil { - return - } - if p.lines.Len() >= 100 { - p.lines.Get(1) - } - p.lines.Put(string(line)) - } - }() - - go func() { - defer func() { - common.Recover("") - errReader.Close() - }() - reader := bufio.NewReaderSize(errReader, 8192) - for { - line, _, err := reader.ReadLine() - if err != nil { - return - } - if p.lines.Len() >= 100 { - p.lines.Get(1) - } - p.lines.Put(string(line)) - } - }() - - go func() { - err := cmd.Run() - if err != nil { - p.exitErr = err - } - }() - - p.refreshVersion() - p.refreshAPIPort() - - return nil -} - -func (p *process) Stop() error { - if !p.IsRunning() { - return errors.New("xray is not running") - } - return p.cmd.Process.Kill() -} - -func (p *process) GetTraffic(reset bool) ([]*Traffic, error) { - if p.apiPort == 0 { - return nil, common.NewError("xray api port wrong:", p.apiPort) - } - conn, err := grpc.Dial(fmt.Sprintf("127.0.0.1:%v", p.apiPort), grpc.WithInsecure()) - if err != nil { - return nil, err - } - defer conn.Close() - - client := statsservice.NewStatsServiceClient(conn) - ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) - defer cancel() - request := &statsservice.QueryStatsRequest{ - Reset_: reset, - } - resp, err := client.QueryStats(ctx, request) - if err != nil { - return nil, err - } - tagTrafficMap := map[string]*Traffic{} - traffics := make([]*Traffic, 0) - for _, stat := range resp.GetStat() { - matchs := trafficRegex.FindStringSubmatch(stat.Name) - isInbound := matchs[1] == "inbound" - tag := matchs[2] - isDown := matchs[3] == "downlink" - if tag == "api" { - continue - } - traffic, ok := tagTrafficMap[tag] - if !ok { - traffic = &Traffic{ - IsInbound: isInbound, - Tag: tag, - } - tagTrafficMap[tag] = traffic - traffics = append(traffics, traffic) - } - if isDown { - traffic.Down = stat.Value - } else { - traffic.Up = stat.Value - } - } - - return traffics, nil -} +package xray + +import ( + "bufio" + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io/fs" + "os" + "os/exec" + "regexp" + "runtime" + "strings" + "time" + "xray-ui/util/common" + + "github.com/Workiva/go-datastructures/queue" + statsservice "github.com/xtls/xray-core/app/stats/command" + "google.golang.org/grpc" +) + +var trafficRegex = regexp.MustCompile("(inbound|outbound)>>>([^>]+)>>>traffic>>>(downlink|uplink)") + +func GetBinaryName() string { + return fmt.Sprintf("xray-%s-%s", runtime.GOOS, runtime.GOARCH) +} + +func GetBinaryPath() string { + return "bin/" + GetBinaryName() +} + +func GetConfigPath() string { + return "bin/config.json" +} + +func GetGeositePath() string { + return "bin/geosite.dat" +} + +func GetGeoipPath() string { + return "bin/geoip.dat" +} + +func stopProcess(p *Process) { + p.Stop() +} + +type Process struct { + *process +} + +func NewProcess(xrayConfig *Config) *Process { + p := &Process{newProcess(xrayConfig)} + runtime.SetFinalizer(p, stopProcess) + return p +} + +type process struct { + cmd *exec.Cmd + + version string + apiPort int + + config *Config + lines *queue.Queue + exitErr error +} + +func newProcess(config *Config) *process { + return &process{ + version: "Unknown", + config: config, + lines: queue.New(100), + } +} + +func (p *process) IsRunning() bool { + if p.cmd == nil || p.cmd.Process == nil { + return false + } + if p.cmd.ProcessState == nil { + return true + } + return false +} + +func (p *process) GetErr() error { + return p.exitErr +} + +func (p *process) GetResult() string { + if p.lines.Empty() && p.exitErr != nil { + return p.exitErr.Error() + } + items, _ := p.lines.TakeUntil(func(item interface{}) bool { + return true + }) + lines := make([]string, 0, len(items)) + for _, item := range items { + lines = append(lines, item.(string)) + } + return strings.Join(lines, "\n") +} + +func (p *process) GetVersion() string { + return p.version +} + +func (p *Process) GetAPIPort() int { + return p.apiPort +} + +func (p *Process) GetConfig() *Config { + return p.config +} + +func (p *process) refreshAPIPort() { + for _, inbound := range p.config.InboundConfigs { + if inbound.Tag == "api" { + p.apiPort = inbound.Port + break + } + } +} + +func (p *process) refreshVersion() { + cmd := exec.Command(GetBinaryPath(), "-version") + data, err := cmd.Output() + if err != nil { + p.version = "Unknown" + } else { + datas := bytes.Split(data, []byte(" ")) + if len(datas) <= 1 { + p.version = "Unknown" + } else { + p.version = string(datas[1]) + } + } +} + +func (p *process) Start() (err error) { + if p.IsRunning() { + return errors.New("xray is already running") + } + + defer func() { + if err != nil { + p.exitErr = err + } + }() + + data, err := json.MarshalIndent(p.config, "", " ") + if err != nil { + return common.NewErrorf("生成 xray 配置文件失败: %v", err) + } + configPath := GetConfigPath() + err = os.WriteFile(configPath, data, fs.ModePerm) + if err != nil { + return common.NewErrorf("写入配置文件失败: %v", err) + } + + cmd := exec.Command(GetBinaryPath(), "-c", configPath) + p.cmd = cmd + + stdReader, err := cmd.StdoutPipe() + if err != nil { + return err + } + errReader, err := cmd.StderrPipe() + if err != nil { + return err + } + + go func() { + defer func() { + common.Recover("") + stdReader.Close() + }() + reader := bufio.NewReaderSize(stdReader, 8192) + for { + line, _, err := reader.ReadLine() + if err != nil { + return + } + if p.lines.Len() >= 100 { + p.lines.Get(1) + } + p.lines.Put(string(line)) + } + }() + + go func() { + defer func() { + common.Recover("") + errReader.Close() + }() + reader := bufio.NewReaderSize(errReader, 8192) + for { + line, _, err := reader.ReadLine() + if err != nil { + return + } + if p.lines.Len() >= 100 { + p.lines.Get(1) + } + p.lines.Put(string(line)) + } + }() + + go func() { + err := cmd.Run() + if err != nil { + p.exitErr = err + } + }() + + p.refreshVersion() + p.refreshAPIPort() + + return nil +} + +func (p *process) Stop() error { + if !p.IsRunning() { + return errors.New("xray is not running") + } + return p.cmd.Process.Kill() +} + +func (p *process) GetTraffic(reset bool) ([]*Traffic, error) { + if p.apiPort == 0 { + return nil, common.NewError("xray api port wrong:", p.apiPort) + } + conn, err := grpc.Dial(fmt.Sprintf("127.0.0.1:%v", p.apiPort), grpc.WithInsecure()) + if err != nil { + return nil, err + } + defer conn.Close() + + client := statsservice.NewStatsServiceClient(conn) + ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) + defer cancel() + request := &statsservice.QueryStatsRequest{ + Reset_: reset, + } + resp, err := client.QueryStats(ctx, request) + if err != nil { + return nil, err + } + tagTrafficMap := map[string]*Traffic{} + traffics := make([]*Traffic, 0) + for _, stat := range resp.GetStat() { + matchs := trafficRegex.FindStringSubmatch(stat.Name) + isInbound := matchs[1] == "inbound" + tag := matchs[2] + isDown := matchs[3] == "downlink" + if tag == "api" { + continue + } + traffic, ok := tagTrafficMap[tag] + if !ok { + traffic = &Traffic{ + IsInbound: isInbound, + Tag: tag, + } + tagTrafficMap[tag] = traffic + traffics = append(traffics, traffic) + } + if isDown { + traffic.Down = stat.Value + } else { + traffic.Up = stat.Value + } + } + + return traffics, nil +} diff --git a/xray/traffic.go b/xray/traffic.go index b533afdf..a1ef5186 100644 --- a/xray/traffic.go +++ b/xray/traffic.go @@ -1,8 +1,8 @@ -package xray - -type Traffic struct { - IsInbound bool - Tag string - Up int64 - Down int64 -} +package xray + +type Traffic struct { + IsInbound bool + Tag string + Up int64 + Down int64 +}