diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index c6175c5999..a366864140 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -10,5 +10,5 @@ liberapay: # Replace with a single Liberapay username issuehunt: # Replace with a single IssueHunt username lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry polar: # Replace with a single Polar username -buy_me_a_coffee: mhsanaei +buy_me_a_coffee: xeefeiz custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] diff --git a/DockerInit.sh b/DockerInit.sh index b68af92fb3..42bf3160d6 100755 --- a/DockerInit.sh +++ b/DockerInit.sh @@ -37,4 +37,4 @@ wget -O geoip_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/la wget -O geosite_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geosite.dat wget -O geoip_VN.dat https://github.com/vuong2023/vn-v2ray-rules/releases/latest/download/geoip.dat wget -O geosite_VN.dat https://github.com/vuong2023/vn-v2ray-rules/releases/latest/download/geosite.dat -cd ../../ \ No newline at end of file +cd ../../ diff --git a/Dockerfile b/Dockerfile index ac09b5318e..cb6e2b8d0c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -22,7 +22,7 @@ RUN ./DockerInit.sh "$TARGETARCH" # Stage: Final Image of 3x-ui # ======================================================== FROM alpine -ENV TZ=Asia/Tehran +ENV TZ=Asia/Shanghai WORKDIR /app RUN apk add --no-cache --update \ diff --git a/README.es_ES.md b/README.es_ES.md index 5da2e5a627..26647b9b41 100644 --- a/README.es_ES.md +++ b/README.es_ES.md @@ -1,13 +1,13 @@ -[English](/README.md) | [中文](/README.zh_CN.md) | [Español](/README.es_ES.md) | [Русский](/README.ru_RU.md) +[English](/README.md) | [Chinese](/README.zh.md) | [Español](/README.es_ES.md)

Image

**Un Panel Web Avanzado • Construido sobre Xray Core** -[![](https://img.shields.io/github/v/release/mhsanaei/3x-ui.svg)](https://github.com/MHSanaei/3x-ui/releases) -[![](https://img.shields.io/github/actions/workflow/status/mhsanaei/3x-ui/release.yml.svg)](#) -[![GO Version](https://img.shields.io/github/go-mod/go-version/mhsanaei/3x-ui.svg)](#) -[![Downloads](https://img.shields.io/github/downloads/mhsanaei/3x-ui/total.svg)](#) +[![](https://img.shields.io/github/v/release/xeefei/3x-ui.svg)](https://github.com/xeefei/3x-ui/releases) +[![](https://img.shields.io/github/actions/workflow/status/xeefei/3x-ui/release.yml.svg)](#) +[![GO Version](https://img.shields.io/github/go-mod/go-version/xeefei/3x-ui.svg)](#) +[![Downloads](https://img.shields.io/github/downloads/xeefei/3x-ui/total.svg)](#) [![License](https://img.shields.io/badge/license-GPL%20V3-blue.svg?longCache=true)](https://www.gnu.org/licenses/gpl-3.0.en.html) > **Descargo de responsabilidad:** Este proyecto es solo para aprendizaje personal y comunicación, por favor no lo uses con fines ilegales, por favor no lo uses en un entorno de producción @@ -15,19 +15,17 @@ **Si este proyecto te es útil, podrías considerar darle una**:star2:

- + Image

-- USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC` -- MATIC (polygon): `0x41C9548675D044c6Bfb425786C765bc37427256A` -- LTC (Litecoin): `ltc1q2ach7x6d2zq0n4l0t4zl7d7xe2s6fs7a3vspwv` +- USDT (TRC20): `TYQEmQp1P65u9bG7KPehgJdvuokfb72YkZ` ## Instalar y Actualizar ``` -bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) +bash <(curl -Ls https://raw.githubusercontent.com/xeefei/3x-ui/master/install.sh) ``` ## Instalar una Versión Personalizada @@ -35,7 +33,7 @@ bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install. Para instalar la versión deseada, agrega la versión al final del comando de instalación. Por ejemplo, ver `v2.4.3`: ``` -bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) v2.4.3 +bash <(curl -Ls https://raw.githubusercontent.com/xeefei/3x-ui/master/install.sh) v2.4.3 ``` ## Certificado SSL @@ -87,7 +85,7 @@ case "${ARCH}" in esac -wget https://github.com/MHSanaei/3x-ui/releases/latest/download/x-ui-linux-${XUI_ARCH}.tar.gz +wget https://github.com/xeefei/3x-ui/releases/latest/download/x-ui-linux-${XUI_ARCH}.tar.gz ``` 2. Una vez que se haya descargado el paquete comprimido, ejecuta los siguientes comandos para instalar o actualizar x-ui: @@ -134,7 +132,7 @@ systemctl restart x-ui 2. Clona el Repositorio del Proyecto: ```sh - git clone https://github.com/MHSanaei/3x-ui.git + git clone https://github.com/xeefei/3x-ui.git cd 3x-ui ``` @@ -154,7 +152,7 @@ systemctl restart x-ui --network=host \ --restart=unless-stopped \ --name 3x-ui \ - ghcr.io/mhsanaei/3x-ui:latest + ghcr.io/xeefei/3x-ui:latest ``` actualizar a la última versión @@ -177,41 +175,6 @@ eliminar 3x-ui de docker -## Configuración de Nginx -
- Haga clic aquí para configurar el proxy inverso - -#### Proxy inverso Nginx -```nginx -location / { - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header Host $http_host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header Range $http_range; - proxy_set_header If-Range $http_if_range; - proxy_redirect off; - proxy_pass http://127.0.0.1:2053; -} -``` - -#### Nginx sub-path -- EAsegúrese de que la "Ruta Raíz de la URL del Panel" en la configuración del panel `/sub` es la misma. -- El `url` en la configuración del panel debe terminar con `/`. - -```nginx -location /sub { - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header Host $http_host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header Range $http_range; - proxy_set_header If-Range $http_if_range; - proxy_redirect off; - proxy_pass http://127.0.0.1:2053; -} -``` -
## SO Recomendados @@ -297,10 +260,17 @@ Si elige no modificar estas configuraciones, se generarán aleatoriamente (esto Puedes realizar copias de seguridad y restauraciones de la base de datos directamente desde el panel. -- **Ruta de la Base de Datos:** - - `/etc/x-ui/x-ui.db` - ### Ruta Base Web + - /etc/x-ui/x-ui.db +- **Ruta de Configuración de Xray:** + - /usr/local/x-ui/bin/config.json +- **Ruta del Panel Web sin Implementar SSL:** + - http://ip:2053/panel + - http://domain:2053/panel +- **Ruta del Panel Web con Implementación de SSL:** + - https://domain:2053/panel + + 1. **Restablecer la Ruta Base Web:** - Abre tu terminal. @@ -426,7 +396,7 @@ El panel web admite tráfico diario, inicio de sesión en el panel, copia de seg - Inicia [Botfather](https://t.me/BotFather) en tu cuenta de Telegram: ![Botfather](./media/botfather.png) - + - Crea un nuevo bot usando el comando /newbot: Te hará 2 preguntas, Un nombre y un nombre de usuario para tu bot. Ten en cuenta que el nombre de usuario debe terminar con la palabra "bot". ![Create new bot](./media/newbot.png) @@ -529,4 +499,4 @@ XUI_BIN_FOLDER="bin" XUI_DB_FOLDER="/etc/x-ui" go build main.go ## Estrellas a lo largo del tiempo -[![Stargazers over time](https://starchart.cc/MHSanaei/3x-ui.svg)](https://starchart.cc/MHSanaei/3x-ui) +[![Stargazers over time](https://starchart.cc/xeefei/3x-ui.svg)](https://starchart.cc/xeefei/3x-ui) diff --git a/README.md b/README.md index a2edad7f26..1c751813ac 100644 --- a/README.md +++ b/README.md @@ -1,63 +1,271 @@ -[English](/README.md) | [中文](/README.zh_CN.md) | [Español](/README.es_ES.md) | [Русский](/README.ru_RU.md) -

Image

-**An Advanced Web Panel • Built on Xray Core** +**------------------一个更好的面板 • 基于Xray Core构建----------------** -[![](https://img.shields.io/github/v/release/mhsanaei/3x-ui.svg)](https://github.com/MHSanaei/3x-ui/releases) -[![](https://img.shields.io/github/actions/workflow/status/mhsanaei/3x-ui/release.yml.svg)](#) -[![GO Version](https://img.shields.io/github/go-mod/go-version/mhsanaei/3x-ui.svg)](#) -[![Downloads](https://img.shields.io/github/downloads/mhsanaei/3x-ui/total.svg)](#) +[![](https://img.shields.io/github/v/release/xeefei/3x-ui.svg)](https://github.com/xeefei/3x-ui/releases) +[![](https://img.shields.io/github/actions/workflow/status/xeefei/3x-ui/release.yml.svg)](#) +[![GO Version](https://img.shields.io/github/go-mod/go-version/xeefei/3x-ui.svg)](#) +[![Downloads](https://img.shields.io/github/downloads/xeefei/3x-ui/total.svg)](#) [![License](https://img.shields.io/badge/license-GPL%20V3-blue.svg?longCache=true)](https://www.gnu.org/licenses/gpl-3.0.en.html) -> **Disclaimer:** This project is only for personal learning and communication, please do not use it for illegal purposes, please do not use it in a production environment +> **声明:** 此项目仅供个人学习、交流使用,请遵守当地法律法规,勿用于非法用途;请勿用于生产环境。 + +> **注意:** 在使用此项目和〔教程〕过程中,若因违反以上声明使用规则而产生的一切后果由使用者自负。 -**If this project is helpful to you, you may wish to give it a**:star2: +**如果此项目对你有用,请给一个**:star2:

- + Image

-- USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC` -- MATIC (polygon): `0x41C9548675D044c6Bfb425786C765bc37427256A` -- LTC (Litecoin): `ltc1q2ach7x6d2zq0n4l0t4zl7d7xe2s6fs7a3vspwv` - -## Install & Upgrade - +- 赞助地址(USDT/TRC20):`TYQEmQp1P65u9bG7KPehgJdvuokfb72YkZ` + +## [【3X-UI】中文交流群:https://t.me/XUI_CN](https://t.me/XUI_CN) +## [【3X-UI】详细安装流程步骤:https://xeefei.github.io/xufei/2024/05/3x-ui/](https://xeefei.github.io/xufei/2024/05/3x-ui/) + +------------ +## ✰〔3X-UI优化版〕跟原版3X-UI的区别?✰ +### 大部分功能基于原版3X-UI进行汉化优化,主要的优化内容如下: +#### 1、最大限度地汉化了面板项目,更适合中文宝宝体质,包括: +##### ①优化在VPS中进行〔脚本安装过程〕的汉化提示,增加相应的安装中文提示,让中文用户能明白清楚自己安装到了哪个环节?在细节方面,增加了安装成功之后的〔用户设置信息〕提示,在脚本中加入〔面板登录地址〕显示, +##### ②管理后台进行了相应的〔图标和按钮〕汉化,让中文宝宝能够看得懂, +##### ③安装成功后〔自动更改〕后台管理界面和电报机器人界面默认为〔中文〕, +##### ④在管理后台中〔设置证书处〕,增加了acme方式填入路径的提示; +#### 2、优化了电报机器人响应〔按钮〕的名称和排序; +#### 3、创建了〔3X-UI〕中文交流群,各位中文宝宝可以一起讨论交流; +#### 4、管理后台中增加了〔实用导航〕页面,里面包含实用内容; +#### 5、优化了后台〔二维码〕显示模式,点击打开会更加丝滑美观; +#### 6、在创建reality协议时,更改uTLS指纹默认使用chrome; +#### 7、更新README内容添加备份&恢复操作说明,以及更多其他图文介绍; +#### 8、管理后台中增加〔端口检测〕和〔网络测速〕,点击可以跳转直达; +#### 9、增加了详细的项目〔安装配置教程〕,解决小白用户不懂配置的烦恼。 + +------------ +## ✰如何从其他x-ui版本迁移到〔3X-UI优化版〕?✰ +#### 1、若你用的是伊朗老哥的原版3X-UI,是可以直接〔覆盖安装〕的,因为〔3X-UI优化版〕是fork了原版3X-UI的项目,基于原有的功能进行优化的,大功能是没有变化的,主要是进行了脚本的〔汉化处理〕,其他诸如数据库文件等位置是没有改变的,所以直接覆盖安装,并不会影响你〔原有节点及配置〕等数据;安装命令如下: +``` +bash <(curl -Ls https://raw.githubusercontent.com/xeefei/3x-ui/master/install.sh) +``` +#### 2、若你之前用的是Docker方式安装,那先进入容器里面/命令:docker exec -it 容器id /bin/sh,再执行以上脚本命令直接【覆盖安装】即可, +#### 3、若你用的是之前F佬的x-ui或者其他分支版本,那直接覆盖安装的话,并不能确保一定就能够兼容?建议你先去备份〔数据库〕配置文件,再进行安装〔3X-UI优化版〕。 + + +------------ +## 安装之前的准备 +- 购买一台性能还不错的VPS,可通过本页底部链接购买, +- PS:若你不想升级系统,则可以跳过此步骤。 +- 若你需要更新/升级系统,Debian系统可用如下命令: + ``` + apt update + apt upgrade -y + apt dist-upgrade -y + apt autoclean + apt autoremove -y + ``` +- 查看系统当前版本: + ``` + cat /etc/debian_version + ``` +- 查看内核版本: + ``` + uname -r + ``` +- 列出所有内核: + ``` + dpkg --list | grep linux-image + ``` +- 更新完成后执行重新引导: + ``` + update-grub + ``` +- 完成以上步骤之后输入reboot重启系统 + +------------ +## 【搬瓦工】重装/升级系统之后SSH连不上如何解决? +- 【搬瓦工】重装/升级系统会恢复默认22端口,如果需要修改SSH的端口号,您需要进行以下步骤: +- 以管理员身份使用默认22端口登录到SSH服务器 +- 打开SSH服务器的配置文件进行编辑,SSH配置文件通常位于/etc/ssh/sshd_config +- 找到"Port"选项,并将其更改为您想要的端口号 +- Port <新端口号>,请将<新端口号>替换为您想要使用的端口号 +- 保存文件并退出编辑器 +- 重启服务器以使更改生效 + +------------ +## 安装 & 升级 +- 使用3x-ui脚本一般情况下,安装完成创建入站之后,端口是默认关闭的,所以必须进入脚本选择【22】去放行端口 +- 要使用【自动续签】证书功能,也必须放行【80】端口,保持80端口是打开的,才会每3个月自动续签一次 + +- 【全新安装】请执行以下脚本: +``` +bash <(curl -Ls https://raw.githubusercontent.com/xeefei/3x-ui/master/install.sh) ``` -bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) +#### 如果执行了上面的代码但是报错,证明你的系统里面没有curl这个软件,请执行以下命令先安装curl软件,安装curl之后再去执行上面代码, ``` +apt update -y&&apt install -y curl&&apt install -y socat +``` + +- 若要对版本进行升级,可直接通过脚本选择【2】,如下图: +![8](./media/8.png) +![10](./media/10.png) +- 在到这一步必须要注意:要保留旧设置的话,需要输入【n】 +![11](./media/11.png) -## Install Custom Version +------------ +## 安装指定版本 -To install your desired version, add the version to the end of the installation command. e.g., ver `v2.4.3`: +若要安装指定的版本,请将该版本添加到安装命令的末尾。 e.g., ver `v2.4.3`: ``` -bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) v2.4.3 +bash <(curl -Ls https://raw.githubusercontent.com/xeefei/3x-ui/master/install.sh) v2.4.3 ``` - -## SSL Certificate +------------ +## 若你的VPS默认有防火墙,请在安装完成之后放行指定端口 +- 放行【面板登录端口】 +- 放行出入站管理协议端口 +- 如果要申请安装证书并每3个月【自动续签】证书,请确保80和443端口是放行打开的 +- 可通过此脚本的第【22】选项去安装防火墙进行管理,如下图: +![9](./media/9.png) +- 若要一次性放行多个端口或一整个段的端口,用英文逗号隔开。 +#### PS:若你的VPS没有防火墙,则所有端口都是能够ping通的,可自行选择是否进入脚本安装防火墙保证安全,但安装了防火墙必须放行相应端口。 + +------------ +## 安装证书开启https方式实现域名登录访问管理面板/偷自己 +#### PS:如果不需要以上功能或无域名,可以跳过这步, +##### 1、把自己的域名托管到CF,并解析到自己VPS的IP,不要开启【小云朵】, +##### 2、如果要申请安装证书并每3个月【自动续签】证书,请确保80和443端口是放行打开的, +##### 3、输入x-ui命令进入面板管理脚本,通过选择第【18】选项去进行安装, +##### 4、记录好已经安装证书的【路径】,位置在:/root/.acme.sh/(域名)_ecc,后续需要用到, +##### 5、进入后台【面板设置】—–>【常规】中,去分别填入刚才已经记录的证书公钥、私钥路径, +##### 6、点击左上角的【保存】和【重启面板】,即可用自己域名进行登录管理;也可按照后续方法实现【自己偷自己】。 + +------------ +## 登录面板进行【常规】设置 +### 特别是如果在安装过程中,全部都是默认【回车键】安装的话,用户名/密码/访问路径是随机的,而面板监听端口默认是2053,最好进入面板更改, +##### 1、填写自己想要设置的【面板监听端口】,并去登录SSH放行, +##### 2、更改自己想要设置的【面板登录访问路径】,后续加上路径登录访问, +![25](./media/25.png) +##### 3、其他:安全设定和电报机器人等配置,可自行根据需求去进行设置, +##### 4、若申请了证书须填写证书公钥/私钥路径,建议配置电报机器人方便管理, +![26](./media/26.png) +##### 5、面板设置【改动保存】之后,都需要点击左上角【重启面板】,才能生效。 +#### PS:若你在正确完成了上述步骤之后,你没有安装证书的情况下,去用IP+端口号/路径的方式却不能访问面板,那请检查一下是不是你的浏览器自动默认开启了https模式,需要手动调整一下改成http方式,把“s”去掉,即可访问成功。 + +------------ +## 创建【入站协议】和添加【客户端】,并测试上网 +##### 1、点击左边【入站列表】,然后【添加入站】,传输方式保持【TCP】不变,尽量选择主流的vless+reality+vision协议组合, +![23](./media/23.png) +##### 2、在选择reality协议时,偷的域名可以使用默认的,要使用其他的,请替换尽量保持一致就行,比如Apple、Yahoo,VPS所在地区的旅游、学校网站等;如果要实现【偷自己】,请参看后续【如何偷自己】的说明部分;而私钥/公钥部分,可以直接点击下方的【Get New Cert】获取一个随机的, +##### 3、在创建reality协议过程中,至于其他诸如:PROXY Protocol,HTTP 伪装,TPROXY,External Proxy等等选项,若无特殊要求,保持默认设置即可,不用去动它们, +![24](./media/24.png) +##### 4、创建好入站协议之后,默认只有一个客户端,可根据自己需求继续添加;重点:并编辑客户端,选择【Flow流控】为xtls-rprx-vision-udp443, +![19](./media/19.png) +##### 5、其他:流量限制,到期时间,客户TG的ID等选项根据自己需求填写, +![4](./media/4.png) +##### 6、一定要放行端口之后,确保端口能够ping通,再导入软件, +##### 7、点击二维码或者复制链接导入到v2rayN等软件中进行测试。 + +------------ +## 备份与恢复/迁移数据库(以Debian系统为例) +#### 一、备份:通过配置好电报管理机器人,并去设置开启【自动备份】,每天凌晨12点会通过VPS管理机器人获取【备份配置】文件,有x-ui.db和config.json两个文件,可自行下载保存到自己电脑里面, +![14](./media/14.png) +#### 二、搭建:在新的VPS中全新安装好3x-ui面板,通过脚本放行之前配置的所有端口,一次性放行多个端口请用【英文逗号】分隔, +#### 三、若需要安装证书,则提前把域名解析到新的VPS对应的IP,并且去输入x-ui选择第【18】选项去安装,并记录公钥/私钥的路径,无域名则跳过这一步, +#### 四、恢复:SSH登录服务器找到/etc/x-ui/x-ui.db和/usr/local/x-ui/bin/config.json文件位置,上传之前的两个备份文件,进行覆盖, +![12](./media/12.png) +##### PS:把之前通过自动备份下载得到的两个文件上传覆盖掉旧文件,重启3x-ui面板即可【迁移成功】;即使迁移过程中出现问题,你是有备份文件的,不用担心,多试几次。 +![13](./media/13.png) +#### 五、若安装了证书,去核对/更改一下证书的路径,一般是同一个域名的话,位置在:/root/.acme.sh/(域名)_ecc,路径是相同的就不用更改, +#### 六、重启面板/重启服务器,让上述步骤生效即可,这时可以看到所有配置都是之前自己常用的,包括面板用户名、密码,入站、客户端,电报机器人配置等。 + +------------ +## 安装完成后如何设置调整成【中文界面】? +- 方法一:通过管理后台【登录页面】调整,登录时可以选择,如下图: +![15](./media/15.png) +- 方法二:通过在管理后台-->【面板设置】中去选择设置,如下图: +![16](./media/16.png) +- 【TG机器人】设置中文:通过在管理后台-->【面板设置】-->【机器人配置】中去选择设置,并建议打开数据库备份和登录通知,如下图: +![17](./media/17.png) + +------------ +## 用3x-ui如何实现【自己偷自己】? +- 其实很简单,只要你为面板设置了证书, +- 开启了HTTPS登录,就可以将3x-ui自身作为Web Server, +- 无需Nginx等,这里给一个示例: +- 其中目标网站(Dest)请填写面板监听端口, +- 可选域名(SNI)填写面板登录域名, +- 如果您使用其他web server(如nginx)等, +- 将目标网站改为对应监听端口也可。 +- 需要说明的是,如果您处于白名单地区,自己“偷”自己并不适合你; +- 其次,可选域名一项实际上可以填写任意SNI,只要客户端保持一致即可,不过并不推荐这样做。 +- 配置方法如下图所示: +![18](./media/18.png) + +------------ +## 〔子域名〕被墙针对特征 +#### 网络表现: +##### 1、可以Ping通域名和IP地址, +##### 2、子域名无法打开3X-UI管理界面, +##### 3、什么都正常就是不能上网; + +#### 问题: +##### 你的子域名被墙针对了:无法上网! + +#### 解决方案: +##### 1、更换为新的子域名, +##### 2、解析新的子域名到VPS的IP, +##### 3、重新去安装新证书, +##### 4、重启3X-UI和服务器, +##### 5、重新去获取链接并测试上网。 +#### PS:若通过以上步骤还是不能正常上网,则重装VPS服务器OS系统,以及3X-UI面板全部重新安装,之后就正常了! + +------------ +## 在自己的VPS服务器部署【订阅转换】功能 +### 如何把vless/vmess等协议转换成Clash/Surge等软件支持的格式? +##### 1、进入脚本输入x-ui命令调取面板,选择第【24】选项安装订阅转换模块,如下图: +![21](./media/21.png) +##### 2、等待安装【订阅转换】成功之后,访问地址:你的IP:18080(端口号)进行转换, +![22](./media/22.png) +##### 3、因为在转换过程中需要调取后端API,所以请确保端口25500是打开放行的, +##### 4、在得到【转换链接】之后,只要你的VPS服务器25500端口是能ping通的,就能导入Clash/Surge等软件成功下载配置, +##### 5、此功能集成到3x-ui面板中,是为了保证安全,通过调取24选项把【订阅转换】功能部署在自己的VPS中,不会造成链接泄露。 +### 【订阅转换】功能在自己的VPS中安装部署成功之后的界面如下图所示: +![20](./media/20.png) + +------------ +## 如何保护自己的IP不被墙被封? +##### 1、使用的代理协议要安全,加密是必备,推荐使用vless+reality+vision协议组合, +##### 2、因为有时节点会共享,在不同的地区,多个省份之间不要共同连接同一个IP, +##### 3、连接同一个IP就算了,不要同一个端口,不要同IP+同端口到处漫游,要分开, +##### 4、同一台VPS,不要在一天内一直大流量去下载东西使用,不要流量过高要切换, +##### 5、创建【入站协议】的时候,尽量用【高位端口】,比如40000--65000之间的端口号。 +#### 提醒:为什么在特殊时期,比如:两会,春节等被封得最严重最惨? +##### 尼玛同一个IP+同一个端口号,多个省份去漫游,跟开飞机场一样!不封你,封谁的IP和端口? +#### 总结:不要多终端/多省份/多个朋友/共同使用同一个IP和端口号!使用3x-ui多创建几个【入站】, +#### 多做几条备用,各用各的!各行其道才比较安全!GFW的思维模式是干掉机场,机场的特征个人用户不要去沾染,自然IP就保护好了。 + +------------ +## SSL 认证
- Click for SSL Certificate details + 点击查看 SSL 认证 ### ACME -To manage SSL certificates using ACME: +要使用 ACME 管理 SSL 证书: -1. Ensure your domain is correctly resolved to the server. -2. Run the `x-ui` command in the terminal, then choose `SSL Certificate Management`. -3. You will be presented with the following options: +1. 确保您的域名已正确解析到服务器, +2. 输入“x-ui”命令并选择“SSL 证书管理”, +3. 您将看到以下选项: - - **Get SSL:** Obtain SSL certificates. - - **Revoke:** Revoke existing SSL certificates. - - **Force Renew:** Force renewal of SSL certificates. + - **获取证书** ----获取SSL证书 + - **吊销证书** ----吊销现有的SSL证书 + - **续签证书** ----强制续签SSL证书 ### Certbot -To install and use Certbot: +安装和使用 Certbot: ```sh apt-get install certbot -y @@ -67,35 +275,36 @@ certbot renew --dry-run ### Cloudflare -The management script includes a built-in SSL certificate application for Cloudflare. To use this script to apply for a certificate, you need the following: +管理脚本具有用于 Cloudflare 的内置 SSL 证书应用程序。若要使用此脚本申请证书,需要满足以下条件: -- Cloudflare registered email +- Cloudflare 邮箱地址 - Cloudflare Global API Key -- The domain name must be resolved to the current server through Cloudflare +- 域名已通过 cloudflare 解析到当前服务器 -**How to get the Cloudflare Global API Key:** +**如何获取 Cloudflare全局API密钥:** -1. Run the `x-ui` command in the terminal, then choose `Cloudflare SSL Certificate`. -2. Visit the link: [Cloudflare API Tokens](https://dash.cloudflare.com/profile/api-tokens). -3. Click on "View Global API Key" (see the screenshot below): +1. 在终端中输入“x-ui”命令,然后选择“CF SSL 证书”。 +2. 访问链接: [Cloudflare API Tokens](https://dash.cloudflare.com/profile/api-tokens). +3. 点击“查看全局 API 密钥”(如下图所示): ![](media/APIKey1.PNG) -4. You may need to re-authenticate your account. After that, the API Key will be shown (see the screenshot below): +4. 您可能需要重新验证您的帐户。之后,将显示 API 密钥(请参见下面的屏幕截图): ![](media/APIKey2.png) -When using, just enter your `domain name`, `email`, and `API KEY`. The diagram is as follows: +使用时,只需输入您的“域名”、“电子邮件”和“API KEY”即可。示意图如下: ![](media/DetailEnter.png)
-## Manual Install & Upgrade +------------ +## 手动安装 & 升级
- Click for manual install details + 点击查看 手动安装 & 升级 -#### Usage +#### 使用 -1. To download the latest version of the compressed package directly to your server, run the following command: +1. 若要将最新版本的压缩包直接下载到服务器,请运行以下命令: ```sh ARCH=$(uname -m) @@ -111,10 +320,10 @@ case "${ARCH}" in esac -wget https://github.com/MHSanaei/3x-ui/releases/latest/download/x-ui-linux-${XUI_ARCH}.tar.gz +wget https://github.com/xeefei/3x-ui/releases/latest/download/x-ui-linux-${XUI_ARCH}.tar.gz ``` -2. Once the compressed package is downloaded, execute the following commands to install or upgrade x-ui: +2. 下载压缩包后,执行以下命令安装或升级 x-ui: ```sh ARCH=$(uname -m) @@ -143,35 +352,38 @@ systemctl restart x-ui
-## Install with Docker +------------ +## 通过Docker安装
- Click for Docker details + 点击查看 通过Docker安装 -#### Usage +#### 使用 -1. **Install Docker:** + +1. **安装Docker** ```sh bash <(curl -sSL https://get.docker.com) ``` -2. **Clone the Project Repository:** + +2. **克隆项目仓库** ```sh - git clone https://github.com/MHSanaei/3x-ui.git + git clone https://github.com/xeefei/3x-ui.git cd 3x-ui ``` -3. **Start the Service:** +3. **启动服务**: ```sh docker compose up -d ``` - Add ```--pull always``` flag to make docker automatically recreate container if a newer image is pulled. See https://docs.docker.com/reference/cli/docker/container/run/#pull for more info. + 添加 ```--pull always``` 标志使 docker 在拉取更新的镜像时自动重新创建容器。有关更多信息,请参阅:https://docs.docker.com/reference/cli/docker/container/run/#pull - **OR** + **或** ```sh docker run -itd \ @@ -181,10 +393,10 @@ systemctl restart x-ui --network=host \ --restart=unless-stopped \ --name 3x-ui \ - ghcr.io/mhsanaei/3x-ui:latest + ghcr.io/xeefei/3x-ui:latest ``` -4. **Update to the Latest Version:** +4. **更新至最新版本** ```sh cd 3x-ui @@ -193,7 +405,7 @@ systemctl restart x-ui docker compose up -d ``` -5. **Remove 3x-ui from Docker:** +5. **从Docker中删除3x-ui ** ```sh docker stop 3x-ui @@ -204,50 +416,14 @@ systemctl restart x-ui
-## Nginx Settings -
- Click for Reverse Proxy Configuration - -#### Nginx Reverse Proxy -```nginx -location / { - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header Host $http_host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header Range $http_range; - proxy_set_header If-Range $http_if_range; - proxy_redirect off; - proxy_pass http://127.0.0.1:2053; -} -``` - -#### Nginx sub-path -- Ensure that the "URI Path" in the `/sub` panel settings is the same. -- The `url` in the panel settings needs to end with `/`. - -```nginx -location /sub { - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header Host $http_host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header Range $http_range; - proxy_set_header If-Range $http_if_range; - proxy_redirect off; - proxy_pass http://127.0.0.1:2053; -} -``` -
- -## Recommended OS +------------ +## 建议使用的操作系统 - Ubuntu 20.04+ - Debian 11+ - CentOS 8+ - Fedora 36+ - Arch Linux -- Parch Linux - Manjaro - Armbian - AlmaLinux 8.0+ @@ -256,276 +432,274 @@ location /sub { - OpenSUSE Tubleweed - Amazon Linux 2023 -## Supported Architectures and Devices - +------------ +## 支持的架构和设备
- Click for Supported Architectures and devices details - -Our platform offers compatibility with a diverse range of architectures and devices, ensuring flexibility across various computing environments. The following are key architectures that we support: + 点击查看 支持的架构和设备 -- **amd64**: This prevalent architecture is the standard for personal computers and servers, accommodating most modern operating systems seamlessly. +我们的平台提供与各种架构和设备的兼容性,确保在各种计算环境中的灵活性。以下是我们支持的关键架构: -- **x86 / i386**: Widely adopted in desktop and laptop computers, this architecture enjoys broad support from numerous operating systems and applications, including but not limited to Windows, macOS, and Linux systems. +- **amd64**: 这种流行的架构是个人计算机和服务器的标准,可以无缝地适应大多数现代操作系统。 -- **armv8 / arm64 / aarch64**: Tailored for contemporary mobile and embedded devices, such as smartphones and tablets, this architecture is exemplified by devices like Raspberry Pi 4, Raspberry Pi 3, Raspberry Pi Zero 2/Zero 2 W, Orange Pi 3 LTS, and more. +- **x86 / i386**: 这种架构在台式机和笔记本电脑中被广泛采用,得到了众多操作系统和应用程序的广泛支持,包括但不限于 Windows、macOS 和 Linux 系统。 -- **armv7 / arm / arm32**: Serving as the architecture for older mobile and embedded devices, it remains widely utilized in devices like Orange Pi Zero LTS, Orange Pi PC Plus, Raspberry Pi 2, among others. +- **armv8 / arm64 / aarch64**: 这种架构专为智能手机和平板电脑等当代移动和嵌入式设备量身定制,以 Raspberry Pi 4、Raspberry Pi 3、Raspberry Pi Zero 2/Zero 2 W、Orange Pi 3 LTS 等设备为例。 -- **armv6 / arm / arm32**: Geared towards very old embedded devices, this architecture, while less prevalent, is still in use. Devices such as Raspberry Pi 1, Raspberry Pi Zero/Zero W, rely on this architecture. +- **armv7 / arm / arm32**: 作为较旧的移动和嵌入式设备的架构,它仍然广泛用于Orange Pi Zero LTS、Orange Pi PC Plus、Raspberry Pi 2等设备。 -- **armv5 / arm / arm32**: An older architecture primarily associated with early embedded systems, it is less common today but may still be found in legacy devices like early Raspberry Pi versions and some older smartphones. +- **armv6 / arm / arm32**: 这种架构面向非常老旧的嵌入式设备,虽然不太普遍,但仍在使用中。Raspberry Pi 1、Raspberry Pi Zero/Zero W 等设备都依赖于这种架构。 -- **s390x**: This architecture is commonly used in IBM mainframe computers and offers high performance and reliability for enterprise workloads. +- **armv5 / arm / arm32**: 它是一种主要与早期嵌入式系统相关的旧架构,目前不太常见,但仍可能出现在早期 Raspberry Pi 版本和一些旧智能手机等传统设备中。
+------------ ## Languages - -- English -- Farsi -- Chinese -- Russian -- Vietnamese -- Spanish -- Indonesian -- Ukrainian -- Turkish -- Português (Brazil) - - -## Features - -- System Status Monitoring -- Search within all inbounds and clients -- Dark/Light theme -- Supports multi-user and multi-protocol -- Supports protocols, including VMESS, VLESS, Trojan, Shadowsocks, Dokodemo-door, Socks, HTTP, wireguard -- Supports XTLS native Protocols, including RPRX-Direct, Vision, REALITY -- Traffic statistics, traffic limit, expiration time limit -- Customizable Xray configuration templates -- Supports HTTPS access panel (self-provided domain name + SSL certificate) -- Supports One-Click SSL certificate application and automatic renewal -- For more advanced configuration items, please refer to the panel -- Fixes API routes (user setting will be created with API) -- Supports changing configs by different items provided in the panel. -- Supports export/import database from the panel - - -## Default Panel Settings + +- English(英语) +- Farsi(伊朗语) +- Chinese(中文) +- Russian(俄语) +- Vietnamese(越南语) +- Spanish(西班牙语) +- Indonesian (印度尼西亚语) +- Ukrainian(乌克兰语) +- Turkish(土耳其语) +- Português (葡萄牙语) + +------------ +## 项目特点 + +- 系统状态查看与监控 +- 可搜索所有入站和客户端信息 +- 深色/浅色主题随意切换 +- 支持多用户和多协议 +- 支持多种协议,包括 VMess、VLESS、Trojan、Shadowsocks、Dokodemo-door、Socks、HTTP、wireguard +- 支持 XTLS 原生协议,包括 RPRX-Direct、Vision、REALITY +- 流量统计、流量限制、过期时间限制 +- 可自定义的 Xray配置模板 +- 支持HTTPS访问面板(自备域名+SSL证书) +- 支持一键式SSL证书申请和自动续签证书 +- 更多高级配置项目请参考面板去进行设定 +- 修复了 API 路由(用户设置将使用 API 创建) +- 支持通过面板中提供的不同项目更改配置。 +- 支持从面板导出/导入数据库 + +------------ +## 默认面板设置
- Click for default settings details - -### Username, Password, Port, and Web Base Path -If you choose not to modify these settings, they will be generated randomly (this does not apply to Docker). + 点击查看 默认设置 -**Default Settings for Docker:** -- **Username:** admin -- **Password:** admin -- **Port:** 2053 + ### 默认信息 -### Database Management: +- **端口** + - 2053 +- **用户名 & 密码 & 访问路径** + - 当您跳过设置时,这些信息会随机生成, + - 您也可以在安装的时候自定义访问路径。 +- **数据库路径:** + - /etc/x-ui/x-ui.db +- **Xray 配置路径:** + - /usr/local/x-ui/bin/config.json +- **面板链接(无SSL):** + - http://ip:2053/访问路径/panel +- **面板链接(有SSL):** + - https://你的域名:2053/访问路径/panel - You can conveniently perform database Backups and Restores directly from the panel. +
-- **Database Path:** - - `/etc/x-ui/x-ui.db` +------------ +## [WARP 配置](https://gitlab.com/fscarmen/warp) +
+ 点击查看 WARP 配置 -### Web Base Path +#### 使用 -1. **Reset Web Base Path:** - - Open your terminal. - - Run the `x-ui` command. - - Select the option to `Reset Web Base Path`. +**对于版本 `v2.1.0` 及更高版本:** -2. **Generate or Customize Path:** - - The path will be randomly generated, or you can enter a custom path. +WARP 是内置的,无需额外安装;只需在面板中打开必要的配置即可。 -3. **View Current Settings:** - - To view your current settings, use the `x-ui settings` command in the terminal or `View Current Settings` in `x-ui` +**如果要在 v2.1.0 之前使用 WARP 路由**,请按照以下步骤操作: -### Security Recommendation: -- For enhanced security, use a long, random word in your URL structure. +**1.** 在 **SOCKS Proxy Mode** 模式中安装Wrap -**Examples:** -- `http://ip:port/*webbasepath*/panel` -- `http://domain:port/*webbasepath*/panel` + - **Account Type (free, plus, team):** Choose the appropriate account type. + - **Enable/Disable WireProxy:** Toggle WireProxy on or off. + - **Uninstall WARP:** Remove the WARP application. -
+**2.** 如果您已经安装了 warp,您可以使用以下命令卸载: -## WARP Configuration + ```sh + warp u + ``` -
- Click for WARP configuration details +**3.** 在面板中打开您需要的配置 -#### Usage + 配置: -**For versions `v2.1.0` and later:** + - Block Ads + - Route Google, Netflix, Spotify, and OpenAI (ChatGPT) traffic to WARP + - Fix Google 403 error -WARP is built-in, and no additional installation is required. Simply turn on the necessary configuration in the panel.
-## IP Limit +------------ +## IP 限制
- Click for IP limit details + 点击查看 IP 限制 -#### Usage +#### 使用 -**Note:** IP Limit won't work correctly when using IP Tunnel. +**注意:** 使用 IP 隧道时,IP 限制无法正常工作。 -- **For versions up to `v1.6.1`:** - - The IP limit is built-in to the panel +- 对于 `v1.6.1`之前的版本 : -**For versions `v1.7.0` and newer:** + - IP 限制 已被集成在面板中。 -To enable the IP Limit functionality, you need to install `fail2ban` and its required files by following these steps: +- 对于 `v1.7.0` 以及更新的版本: -1. Run the `x-ui` command in the terminal, then choose `IP Limit Management`. -2. You will see the following options: + - 要使 IP 限制正常工作,您需要按照以下步骤安装 fail2ban 及其所需的文件: - - **Change Ban Duration:** Adjust the duration of bans. - - **Unban Everyone:** Lift all current bans. - - **Check Logs:** Review the logs. - - **Fail2ban Status:** Check the status of `fail2ban`. - - **Restart Fail2ban:** Restart the `fail2ban` service. - - **Uninstall Fail2ban:** Uninstall Fail2ban with configuration. - -3. Add a path for the access log on the panel by setting `Xray Configs/log/Access log` to `./access.log` then save and restart xray. - -- **For versions before `v2.1.3`:** - - You need to set the access log path manually in your Xray configuration: - - ```sh + 1. 使用面板内置的 `x-ui` 指令 + 2. 选择 `IP Limit Management`. + 3. 根据您的需要选择合适的选项。 + + - 确保您的 Xray 配置上有 ./access.log 。在 v2.1.3 之后,我们有一个选项。 + + ```sh "log": { "access": "./access.log", "dnsLog": false, "loglevel": "warning" }, ``` - -- **For versions `v2.1.3` and newer:** - - There is an option for configuring `access.log` directly from the panel. + - 您需要在Xray配置中手动设置〔访问日志〕的路径。
-## Telegram Bot +------------ +## Telegram 机器人
- Click for Telegram bot details + 点击查看 Telegram 机器人 -#### Usage +#### 使用 -The web panel supports daily traffic, panel login, database backup, system status, client info, and other notification and functions through the Telegram Bot. To use the bot, you need to set the bot-related parameters in the panel, including: - -- Telegram Token -- Admin Chat ID(s) -- Notification Time (in cron syntax) -- Expiration Date Notification -- Traffic Cap Notification -- Database Backup -- CPU Load Notification - - -**Reference syntax:** - -- `30 \* \* \* \* \*` - Notify at the 30s of each point -- `0 \*/10 \* \* \* \*` - Notify at the first second of each 10 minutes -- `@hourly` - Hourly notification -- `@daily` - Daily notification (00:00 in the morning) -- `@weekly` - weekly notification -- `@every 8h` - Notify every 8 hours - -### Telegram Bot Features - -- Report periodic -- Login notification -- CPU threshold notification -- Threshold for Expiration time and Traffic to report in advance -- Support client report menu if client's telegram username added to the user's configurations -- Support telegram traffic report searched with UUID (VMESS/VLESS) or Password (TROJAN) - anonymously -- Menu-based bot -- Search client by email (only admin) -- Check all inbounds -- Check server status -- Check depleted users -- Receive backup by request and in periodic reports -- Multi-language bot - -### Setting up Telegram bot - -- Start [Botfather](https://t.me/BotFather) in your Telegram account: - ![Botfather](./media/botfather.png) +Web 面板通过 Telegram Bot 支持每日流量、面板登录、数据库备份、系统状态、客户端信息等通知和功能。要使用机器人,您需要在面板中设置机器人相关参数,包括: -- Create a new Bot using /newbot command: It will ask you 2 questions, A name and a username for your bot. Note that the username has to end with the word "bot". - ![Create new bot](./media/newbot.png) +- 电报令牌 +- 管理员聊天 ID +- 通知时间(cron 语法) +- 到期日期通知 +- 流量上限通知 +- 数据库备份 +- CPU 负载通知 -- Start the bot you've just created. You can find the link to your bot here. - ![token](./media/token.png) -- Enter your panel and config Telegram bot settings like below: -![Panel Config](./media/panel-bot-config.png) +**参考:** -Enter your bot token in input field number 3. -Enter the user ID in input field number 4. The Telegram accounts with this id will be the bot admin. (You can enter more than one, Just separate them with ,) +- `30 \* \* \* \* \*` - 在每个点的 30 秒处通知 +- `0 \*/10 \* \* \* \*` - 每 10 分钟的第一秒通知 +- `@hourly` - 每小时通知 +- `@daily` - 每天通知 (00:00) +- `@weekly` - 每周通知 +- `@every 8h` - 每8小时通知 -- How to get Telegram user ID? Use this [bot](https://t.me/useridinfobot), Start the bot and it will give you the Telegram user ID. -![User ID](./media/user-id.png) +### Telegram Bot 功能 + +- 定期报告 +- 登录通知 +- CPU 阈值通知 +- 提前报告的过期时间和流量阈值 +- 如果将客户的电报用户名添加到用户的配置中,则支持客户端报告菜单 +- 支持使用UUID(VMESS/VLESS)或密码(TROJAN)搜索报文流量报告 - 匿名 +- 基于菜单的机器人 +- 通过电子邮件搜索客户端(仅限管理员) +- 检查所有入库 +- 检查服务器状态 +- 检查耗尽的用户 +- 根据请求和定期报告接收备份 +- 多语言机器人 + +### 注册 Telegram bot + +- 与 [Botfather](https://t.me/BotFather) 对话: + ![Botfather](./media/botfather.png) + +- 使用 /newbot 创建新机器人:你需要提供机器人名称以及用户名,注意名称中末尾要包含“bot” + ![创建机器人](./media/newbot.png) + +- 启动您刚刚创建的机器人。可以在此处找到机器人的链接。 + ![令牌](./media/token.png) + +- 输入您的面板并配置 Telegram 机器人设置,如下所示: + ![面板设置](./media/panel-bot-config.png) + +在输入字段编号 3 中输入机器人令牌。 +在输入字段编号 4 中输入用户 ID。具有此 id 的 Telegram 帐户将是机器人管理员。 (您可以输入多个,只需将它们用“ ,”分开即可) + +- 如何获取TG ID? 使用 [bot](https://t.me/useridinfobot), 启动机器人,它会给你 Telegram 用户 ID。 +![用户 ID](./media/user-id.png)
-## API Routes +------------ +## API 路由
- Click for API routes details + 点击查看 API 路由 -#### Usage +#### 使用 -- `/login` with `POST` user data: `{username: '', password: ''}` for login -- `/panel/api/inbounds` base for following actions: +- `/login` 使用 `POST` 用户名称 & 密码: `{username: '', password: ''}` 登录 +- `/panel/api/inbounds` 以下操作的基础: -| Method | Path | Action | +| 方法 | 路径 | 操作 | | :----: | ---------------------------------- | ------------------------------------------- | -| `GET` | `"/list"` | Get all inbounds | -| `GET` | `"/get/:id"` | Get inbound with inbound.id | -| `GET` | `"/getClientTraffics/:email"` | Get Client Traffics with email | -| `GET` | `"/getClientTrafficsById/:id"` | Get client's traffic By ID | -| `GET` | `"/createbackup"` | Telegram bot sends backup to admins | -| `POST` | `"/add"` | Add inbound | -| `POST` | `"/del/:id"` | Delete Inbound | -| `POST` | `"/update/:id"` | Update Inbound | -| `POST` | `"/clientIps/:email"` | Client Ip address | -| `POST` | `"/clearClientIps/:email"` | Clear Client Ip address | -| `POST` | `"/addClient"` | Add Client to inbound | -| `POST` | `"/:id/delClient/:clientId"` | Delete Client by clientId\* | -| `POST` | `"/updateClient/:clientId"` | Update Client by clientId\* | -| `POST` | `"/:id/resetClientTraffic/:email"` | Reset Client's Traffic | -| `POST` | `"/resetAllTraffics"` | Reset traffics of all inbounds | -| `POST` | `"/resetAllClientTraffics/:id"` | Reset traffics of all clients in an inbound | -| `POST` | `"/delDepletedClients/:id"` | Delete inbound depleted clients (-1: all) | -| `POST` | `"/onlines"` | Get Online users ( list of emails ) | - -\*- The field `clientId` should be filled by: +| `GET` | `"/list"` | 获取所有入站 | +| `GET` | `"/get/:id"` | 获取所有入站以及inbound.id | +| `GET` | `"/getClientTraffics/:email"` | 通过电子邮件获取客户端流量 | +| `GET` | `"/getClientTrafficsById/:id"` | 通过用户ID获取客户端流量 | +| `GET` | `"/createbackup"` | Telegram 机器人向管理员发送备份 | +| `POST` | `"/add"` | 添加入站 | +| `POST` | `"/del/:id"` | 删除入站 | +| `POST` | `"/update/:id"` | 更新入站 | +| `POST` | `"/clientIps/:email"` | 客户端 IP 地址 | +| `POST` | `"/clearClientIps/:email"` | 清除客户端 IP 地址 | +| `POST` | `"/addClient"` | 将客户端添加到入站 | +| `POST` | `"/:id/delClient/:clientId"` | 通过 clientId\* 删除客户端 | +| `POST` | `"/updateClient/:clientId"` | 通过 clientId\* 更新客户端 | +| `POST` | `"/:id/resetClientTraffic/:email"` | 重置客户端的流量 | +| `POST` | `"/resetAllTraffics"` | 重置所有入站的流量 | +| `POST` | `"/resetAllClientTraffics/:id"` | 重置入站中所有客户端的流量 | +| `POST` | `"/delDepletedClients/:id"` | 删除入站耗尽的客户端 (-1: all) | +| `POST` | `"/onlines"` | 获取在线用户 ( 电子邮件列表 ) | + +- 使用`clientId` 项应该填写下列数据: - `client.id` for VMESS and VLESS - `client.password` for TROJAN - `client.email` for Shadowsocks -- [API Documentation](https://documenter.getpostman.com/view/16802678/2s9YkgD5jm) +- [API 文档](https://documenter.getpostman.com/view/16802678/2s9YkgD5jm) + - [Run In Postman](https://app.getpostman.com/run-collection/16802678-1a4c9270-ac77-40ed-959a-7aa56dc4a415?action=collection%2Ffork&source=rip_markdown&collection-url=entityId%3D16802678-1a4c9270-ac77-40ed-959a-7aa56dc4a415%26entityType%3Dcollection%26workspaceId%3D2cd38c01-c851-4a15-a972-f181c23359d9)
-## Environment Variables +------------ +## 环境变量
- Click for environment variables details + 点击查看 环境变量 #### Usage -| Variable | Type | Default | +| 变量 | Type | 默认 | | -------------- | :--------------------------------------------: | :------------ | | XUI_LOG_LEVEL | `"debug"` \| `"info"` \| `"warn"` \| `"error"` | `"info"` | | XUI_DEBUG | `boolean` | `false` | @@ -533,7 +707,7 @@ Enter the user ID in input field number 4. The Telegram accounts with this id wi | XUI_DB_FOLDER | `string` | `"/etc/x-ui"` | | XUI_LOG_FOLDER | `string` | `"/var/log"` | -Example: +例子: ```sh XUI_BIN_FOLDER="bin" XUI_DB_FOLDER="/etc/x-ui" go build main.go @@ -541,25 +715,35 @@ XUI_BIN_FOLDER="bin" XUI_DB_FOLDER="/etc/x-ui" go build main.go
-## Preview +------------ +## 预览 ![1](./media/1.png) ![2](./media/2.png) ![3](./media/3.png) -![4](./media/4.png) ![5](./media/5.png) ![6](./media/6.png) ![7](./media/7.png) -## A Special Thanks to +------------ +## 广告赞助 +- 如果你觉得本项目对你有用,而且你也恰巧有这方面的需求,你也可以选择通过我的购买链接赞助我。 +- [搬瓦工GIA高端线路,仅推荐购买GIA套餐](https://bandwagonhost.com/aff.php?aff=75015) +- [Dmit高端GIA线路](https://www.dmit.io/aff.php?aff=9326) +- [白丝云【4837线路】实惠量大管饱](https://cloudsilk.io/aff.php?aff=706) + +------------ +## 特别感谢 - [alireza0](https://github.com/alireza0/) -## Acknowledgment +------------ +## 致谢 - [Iran v2ray rules](https://github.com/chocolate4u/Iran-v2ray-rules) (License: **GPL-3.0**): _Enhanced v2ray/xray and v2ray/xray-clients routing rules with built-in Iranian domains and a focus on security and adblocking._ - [Vietnam Adblock rules](https://github.com/vuong2023/vn-v2ray-rules) (License: **GPL-3.0**): _A hosted domain hosted in Vietnam and blocklist with the most efficiency for Vietnamese._ -## Stargazers over Time +------------ +## Star 趋势 -[![Stargazers over time](https://starchart.cc/MHSanaei/3x-ui.svg)](https://starchart.cc/MHSanaei/3x-ui) +[![Stargazers over time](https://starchart.cc/xeefei/3x-ui.svg)](https://starchart.cc/xeefei/3x-ui) diff --git a/README.ru_RU.md b/README.ru_RU.md index 0da90fb6b5..8a93411ea1 100644 --- a/README.ru_RU.md +++ b/README.ru_RU.md @@ -15,14 +15,12 @@ **Если этот проект оказался полезным для вас, вы можете оценить его, поставив звёздочку** :star2:

- + Image

-- USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC` -- MATIC (polygon): `0x41C9548675D044c6Bfb425786C765bc37427256A` -- LTC (Litecoin): `ltc1q2ach7x6d2zq0n4l0t4zl7d7xe2s6fs7a3vspwv` +- USDT (TRC20): `TYQEmQp1P65u9bG7KPehgJdvuokfb72YkZ` ## Установка и обновление diff --git a/README.zh.md b/README.zh.md new file mode 100644 index 0000000000..8164409092 --- /dev/null +++ b/README.zh.md @@ -0,0 +1,476 @@ +[English](/README.md) | [Chinese](/README.zh.md) | [Español](/README.es_ES.md) + +

Image

+ +**一个更好的面板 • 基于Xray Core构建** + +[![](https://img.shields.io/github/v/release/xeefei/3x-ui.svg)](https://github.com/xeefei/3x-ui/releases) +[![](https://img.shields.io/github/actions/workflow/status/xeefei/3x-ui/release.yml.svg)](#) +[![GO Version](https://img.shields.io/github/go-mod/go-version/xeefei/3x-ui.svg)](#) +[![Downloads](https://img.shields.io/github/downloads/xeefei/3x-ui/total.svg)](#) +[![License](https://img.shields.io/badge/license-GPL%20V3-blue.svg?longCache=true)](https://www.gnu.org/licenses/gpl-3.0.en.html) + +> **Disclaimer:** 此项目仅供个人学习交流,请不要用于非法目的,请不要在生产环境中使用。 + +**如果此项目对你有用,请给一个**:star2: + +

+ + Image + +

+ +- USDT (TRC20): `TYQEmQp1P65u9bG7KPehgJdvuokfb72YkZ` + +## 安装 & 升级 + +``` +bash <(curl -Ls https://raw.githubusercontent.com/xeefei/3x-ui/master/install.sh) +``` + +## 安装指定版本 + +若需要安装指定的版本,请将该版本添加到安装命令的末尾。 e.g., ver `v2.3.13`: + +``` +bash <(curl -Ls https://raw.githubusercontent.com/xeefei/3x-ui/master/install.sh) v2.3.13 +``` + +## SSL 认证 + +
+ 点击查看 SSL 认证 + +### Cloudflare + +管理脚本具有用于 Cloudflare 的内置 SSL 证书应用程序。若要使用此脚本申请证书,需要满足以下条件: + +- Cloudflare 邮箱地址 +- Cloudflare Global API Key +- 域名已通过 cloudflare 解析到当前服务器 + +**1:** 在终端中运行`x-ui`, 选择 `Cloudflare SSL Certificate`. + + +### Certbot +``` +apt-get install certbot -y +certbot certonly --standalone --agree-tos --register-unsafely-without-email -d yourdomain.com +certbot renew --dry-run +``` + +***Tip:*** *管理脚本具有 Certbot 。使用 `x-ui` 命令, 选择 `SSL Certificate Management`.* + +
+ +## 手动安装 & 升级 + +
+ 点击查看 手动安装 & 升级 + +#### 使用 + +1. 若要将最新版本的压缩包直接下载到服务器,请运行以下命令: + +```sh +ARCH=$(uname -m) +case "${ARCH}" in + x86_64 | x64 | amd64) XUI_ARCH="amd64" ;; + i*86 | x86) XUI_ARCH="386" ;; + armv8* | armv8 | arm64 | aarch64) XUI_ARCH="arm64" ;; + armv7* | armv7) XUI_ARCH="armv7" ;; + armv6* | armv6) XUI_ARCH="armv6" ;; + armv5* | armv5) XUI_ARCH="armv5" ;; + *) XUI_ARCH="amd64" ;; +esac + + +wget https://github.com/xeefei/3x-ui/releases/latest/download/x-ui-linux-${XUI_ARCH}.tar.gz +``` + +2. 下载压缩包后,执行以下命令安装或升级 x-ui: + +```sh +ARCH=$(uname -m) +case "${ARCH}" in + x86_64 | x64 | amd64) XUI_ARCH="amd64" ;; + i*86 | x86) XUI_ARCH="386" ;; + armv8* | armv8 | arm64 | aarch64) XUI_ARCH="arm64" ;; + armv7* | armv7) XUI_ARCH="armv7" ;; + armv6* | armv6) XUI_ARCH="armv6" ;; + armv5* | armv5) XUI_ARCH="armv5" ;; + *) XUI_ARCH="amd64" ;; +esac + +cd /root/ +rm -rf x-ui/ /usr/local/x-ui/ /usr/bin/x-ui +tar zxvf x-ui-linux-${XUI_ARCH}.tar.gz +chmod +x x-ui/x-ui x-ui/bin/xray-linux-* x-ui/x-ui.sh +cp x-ui/x-ui.sh /usr/bin/x-ui +cp -f x-ui/x-ui.service /etc/systemd/system/ +mv x-ui/ /usr/local/ +systemctl daemon-reload +systemctl enable x-ui +systemctl restart x-ui +``` + +
+ +## 通过Docker安装 + +
+ 点击查看 通过Docker安装 + +#### 使用 + +1. 安装Docker: + + ```sh + bash <(curl -sSL https://get.docker.com) + ``` + +2. 克隆仓库: + + ```sh + git clone https://github.com/xeefei/3x-ui.git + cd 3x-ui + ``` + +3. 运行服务: + + ```sh + docker compose up -d + ``` + + 或 + + ```sh + docker run -itd \ + -e XRAY_VMESS_AEAD_FORCED=false \ + -v $PWD/db/:/etc/x-ui/ \ + -v $PWD/cert/:/root/cert/ \ + --network=host \ + --restart=unless-stopped \ + --name 3x-ui \ + ghcr.io/xeefei/3x-ui:latest + ``` + +更新至最新版本 + + ```sh + cd 3x-ui + docker compose down + docker compose pull 3x-ui + docker compose up -d + ``` + +从Docker中删除3x-ui + + ```sh + docker stop 3x-ui + docker rm 3x-ui + cd -- + rm -r 3x-ui + ``` + +
+ + +## 建议使用的操作系统 + +- Ubuntu 20.04+ +- Debian 11+ +- CentOS 8+ +- Fedora 36+ +- Arch Linux +- Manjaro +- Armbian +- AlmaLinux 9+ +- Rockylinux 9+ +- OpenSUSE Tubleweed + +## 支持的架构和设备 +
+ 点击查看 支持的架构和设备 + +我们的平台提供与各种架构和设备的兼容性,确保在各种计算环境中的灵活性。以下是我们支持的关键架构: + +- **amd64**: 这种流行的架构是个人计算机和服务器的标准,可以无缝地适应大多数现代操作系统。 + +- **x86 / i386**: 这种架构在台式机和笔记本电脑中被广泛采用,得到了众多操作系统和应用程序的广泛支持,包括但不限于 Windows、macOS 和 Linux 系统。 + +- **armv8 / arm64 / aarch64**: 这种架构专为智能手机和平板电脑等当代移动和嵌入式设备量身定制,以 Raspberry Pi 4、Raspberry Pi 3、Raspberry Pi Zero 2/Zero 2 W、Orange Pi 3 LTS 等设备为例。 + +- **armv7 / arm / arm32**: 作为较旧的移动和嵌入式设备的架构,它仍然广泛用于Orange Pi Zero LTS、Orange Pi PC Plus、Raspberry Pi 2等设备。 + +- **armv6 / arm / arm32**: 这种架构面向非常老旧的嵌入式设备,虽然不太普遍,但仍在使用中。Raspberry Pi 1、Raspberry Pi Zero/Zero W 等设备都依赖于这种架构。 + +- **armv5 / arm / arm32**: 它是一种主要与早期嵌入式系统相关的旧架构,目前不太常见,但仍可能出现在早期 Raspberry Pi 版本和一些旧智能手机等传统设备中。 +
+ +## Languages + +- English(英语) +- Farsi(伊朗语) +- Chinese(中文) +- Russian(俄语) +- Vietnamese(越南语) +- Spanish(西班牙语) +- Indonesian (印度尼西亚语) +- Ukrainian(乌克兰语) + + +## Features + +- 系统状态监控 +- 在所有入站和客户端中搜索 +- 深色/浅色主题 +- 支持多用户和多协议 +- 支持多种协议,包括 VMess、VLESS、Trojan、Shadowsocks、Dokodemo-door、Socks、HTTP、wireguard +- 支持 XTLS 原生协议,包括 RPRX-Direct、Vision、REALITY +- 流量统计、流量限制、过期时间限制 +- 可自定义的 Xray配置模板 +- 支持HTTPS访问面板(自建域名+SSL证书) +- 支持一键式SSL证书申请和自动续费 +- 更多高级配置项目请参考面板 +- 修复了 API 路由(用户设置将使用 API 创建) +- 支持通过面板中提供的不同项目更改配置。 +- 支持从面板导出/导入数据库 + + +## 默认设置 + +
+ 点击查看 默认设置 + + ### 信息 + +- **端口:** 2053 +- **用户名 & 密码:** 当您跳过设置时,此项会随机生成。 +- **数据库路径:** + - /etc/x-ui/x-ui.db +- **Xray 配置路径:** + - /usr/local/x-ui/bin/config.json +- **面板链接(无SSL):** + - http://ip:2053/panel + - http://domain:2053/panel +- **面板链接(有SSL):** + - https://domain:2053/panel + +
+ +## WARP 配置 + +
+ 点击查看 WARP 配置 + +#### 使用 + +如果要在 v2.1.0 之前使用 WARP 路由,请按照以下步骤操作: + +**1.** 在 **SOCKS Proxy Mode** 模式中安装Wrap + + ```sh + bash <(curl -sSL https://raw.githubusercontent.com/hamid-gh98/x-ui-scripts/main/install_warp_proxy.sh) + ``` + +**2.** 如果您已经安装了 warp,您可以使用以下命令卸载: + + ```sh + warp u + ``` + +**3.** 在面板中打开您需要的配置 + + 配置: + + - Block Ads + - Route Google + Netflix + Spotify + OpenAI (ChatGPT) to WARP + - Fix Google 403 error + +
+ +## IP 限制 + +
+ 点击查看 IP 限制 + +#### 使用 + +**注意:** 使用 IP 隧道时,IP 限制无法正常工作。 + +- 适用于最高 `v1.6.1` : + + - IP 限制 已被集成在面板中。 + +- 适用于 `v1.7.0` 以及更新的版本: + + - 要使 IP 限制正常工作,您需要按照以下步骤安装 fail2ban 及其所需的文件: + + 1. 使用面板内置的 `x-ui` 指令 + 2. 选择 `IP Limit Management`. + 3. 根据您的需要选择合适的选项。 + + - 确保您的 Xray 配置上有 ./access.log 。在 v2.1.3 之后,我们有一个选项。 + + ```sh + "log": { + "access": "./access.log", + "dnsLog": false, + "loglevel": "warning" + }, + ``` + +
+ +## Telegram 机器人 + +
+ 点击查看 Telegram 机器人 + +#### 使用 + +Web 面板通过 Telegram Bot 支持每日流量、面板登录、数据库备份、系统状态、客户端信息等通知和功能。要使用机器人,您需要在面板中设置机器人相关参数,包括: + +- 电报令牌 +- 管理员聊天 ID +- 通知时间(cron 语法) +- 到期日期通知 +- 流量上限通知 +- 数据库备份 +- CPU 负载通知 + + +**参考:** + +- `30 \* \* \* \* \*` - 在每个点的 30 秒处通知 +- `0 \*/10 \* \* \* \*` - 每 10 分钟的第一秒通知 +- `@hourly` - 每小时通知 +- `@daily` - 每天通知 (00:00) +- `@weekly` - 每周通知 +- `@every 8h` - 每8小时通知 + +### Telegram Bot 功能 + +- 定期报告 +- 登录通知 +- CPU 阈值通知 +- 提前报告的过期时间和流量阈值 +- 如果将客户的电报用户名添加到用户的配置中,则支持客户端报告菜单 +- 支持使用UUID(VMESS/VLESS)或密码(TROJAN)搜索报文流量报告 - 匿名 +- 基于菜单的机器人 +- 通过电子邮件搜索客户端(仅限管理员) +- 检查所有入库 +- 检查服务器状态 +- 检查耗尽的用户 +- 根据请求和定期报告接收备份 +- 多语言机器人 + +### 注册 Telegram bot + +- 与 [Botfather](https://t.me/BotFather) 对话: + ![Botfather](./media/botfather.png) + +- 使用 /newbot 创建新机器人:你需要提供机器人名称以及用户名,注意名称中末尾要包含“bot” + ![创建机器人](./media/newbot.png) + +- 启动您刚刚创建的机器人。可以在此处找到机器人的链接。 + ![令牌](./media/token.png) + +- 输入您的面板并配置 Telegram 机器人设置,如下所示: + ![面板设置](./media/panel-bot-config.png) + +在输入字段编号 3 中输入机器人令牌。 +在输入字段编号 4 中输入用户 ID。具有此 id 的 Telegram 帐户将是机器人管理员。 (您可以输入多个,只需将它们用“ ,”分开即可) + +- 如何获取TG ID? 使用 [bot](https://t.me/useridinfobot), 启动机器人,它会给你 Telegram 用户 ID。 +![用户 ID](./media/user-id.png) + +
+ +## API 路由 + +
+ 点击查看 API 路由 + +#### 使用 + +- `/login` 使用 `POST` 用户名称 & 密码: `{username: '', password: ''}` 登录 +- `/panel/api/inbounds` 以下操作的基础: + +| 方法 | 路径 | 操作 | +| :----: | ---------------------------------- | --------------------------------- | +| `GET` | `"/list"` | 获取所有入站 | +| `GET` | `"/get/:id"` | 获取所有入站以及inbound.id | +| `GET` | `"/getClientTraffics/:email"` | 通过电子邮件获取客户端流量 | +| `GET` | `"/createbackup"` | Telegram 机器人向管理员发送备份 | +| `POST` | `"/add"` | 添加入站 | +| `POST` | `"/del/:id"` | 删除入站 | +| `POST` | `"/update/:id"` | 更新入站 | +| `POST` | `"/clientIps/:email"` | 客户端 IP 地址 | +| `POST` | `"/clearClientIps/:email"` | 清除客户端 IP 地址 | +| `POST` | `"/addClient"` | 将客户端添加到入站 | +| `POST` | `"/:id/delClient/:clientId"` | 通过 clientId\* 删除客户端 | +| `POST` | `"/updateClient/:clientId"` | 通过 clientId\* 更新客户端 | +| `POST` | `"/:id/resetClientTraffic/:email"` | 重置客户端的流量 | +| `POST` | `"/resetAllTraffics"` | 重置所有入站的流量 | +| `POST` | `"/resetAllClientTraffics/:id"` | 重置入站中所有客户端的流量 | +| `POST` | `"/delDepletedClients/:id"` | 删除入站耗尽的客户端 (-1: all) | +| `POST` | `"/onlines"` | 获取在线用户 ( 电子邮件列表 ) | + +\*- `clientId` 项应该使用下列数据 + +- `client.id` VMESS and VLESS +- `client.password` TROJAN +- `client.email` Shadowsocks + + +- [API 文档](https://documenter.getpostman.com/view/16802678/2s9YkgD5jm) +- [Run In Postman](https://app.getpostman.com/run-collection/16802678-1a4c9270-ac77-40ed-959a-7aa56dc4a415?action=collection%2Ffork&source=rip_markdown&collection-url=entityId%3D16802678-1a4c9270-ac77-40ed-959a-7aa56dc4a415%26entityType%3Dcollection%26workspaceId%3D2cd38c01-c851-4a15-a972-f181c23359d9) +
+ +## 环境变量 + +
+ 点击查看 环境变量 + +#### Usage + +| 变量 | Type | 默认 | +| -------------- | :--------------------------------------------: | :------------ | +| XUI_LOG_LEVEL | `"debug"` \| `"info"` \| `"warn"` \| `"error"` | `"info"` | +| XUI_DEBUG | `boolean` | `false` | +| XUI_BIN_FOLDER | `string` | `"bin"` | +| XUI_DB_FOLDER | `string` | `"/etc/x-ui"` | +| XUI_LOG_FOLDER | `string` | `"/var/log"` | + +例子: + +```sh +XUI_BIN_FOLDER="bin" XUI_DB_FOLDER="/etc/x-ui" go build main.go +``` + +
+ +## 预览 + +![1](./media/1.png) +![2](./media/2.png) +![3](./media/3.png) +![4](./media/4.png) +![5](./media/5.png) +![6](./media/6.png) +![7](./media/7.png) + +## 特别感谢 + +- [alireza0](https://github.com/alireza0/) + +## 致谢 + +- [Iran v2ray rules](https://github.com/chocolate4u/Iran-v2ray-rules) (License: **GPL-3.0**): _Enhanced v2ray/xray and v2ray/xray-clients routing rules with built-in Iranian domains and a focus on security and adblocking._ +- [Vietnam Adblock rules](https://github.com/vuong2023/vn-v2ray-rules) (License: **GPL-3.0**): _A hosted domain hosted in Vietnam and blocklist with the most efficiency for Vietnamese._ + +## Star趋势 + +[![Stargazers over time](https://starchart.cc/xeefei/3x-ui.svg)](https://starchart.cc/xeefei/3x-ui) diff --git a/README.zh_CN.md b/README.zh_CN.md index 0aa7efcfec..a8c62d8df5 100644 --- a/README.zh_CN.md +++ b/README.zh_CN.md @@ -15,14 +15,12 @@ **如果此项目对你有用,请给一个**:star2:

- + Image

-- USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC` -- MATIC (polygon): `0x41C9548675D044c6Bfb425786C765bc37427256A` -- LTC (Litecoin): `ltc1q2ach7x6d2zq0n4l0t4zl7d7xe2s6fs7a3vspwv` +- USDT (TRC20): `TYQEmQp1P65u9bG7KPehgJdvuokfb72YkZ` ## 安装 & 升级 diff --git a/README.zhzh.md b/README.zhzh.md new file mode 100644 index 0000000000..8164409092 --- /dev/null +++ b/README.zhzh.md @@ -0,0 +1,476 @@ +[English](/README.md) | [Chinese](/README.zh.md) | [Español](/README.es_ES.md) + +

Image

+ +**一个更好的面板 • 基于Xray Core构建** + +[![](https://img.shields.io/github/v/release/xeefei/3x-ui.svg)](https://github.com/xeefei/3x-ui/releases) +[![](https://img.shields.io/github/actions/workflow/status/xeefei/3x-ui/release.yml.svg)](#) +[![GO Version](https://img.shields.io/github/go-mod/go-version/xeefei/3x-ui.svg)](#) +[![Downloads](https://img.shields.io/github/downloads/xeefei/3x-ui/total.svg)](#) +[![License](https://img.shields.io/badge/license-GPL%20V3-blue.svg?longCache=true)](https://www.gnu.org/licenses/gpl-3.0.en.html) + +> **Disclaimer:** 此项目仅供个人学习交流,请不要用于非法目的,请不要在生产环境中使用。 + +**如果此项目对你有用,请给一个**:star2: + +

+ + Image + +

+ +- USDT (TRC20): `TYQEmQp1P65u9bG7KPehgJdvuokfb72YkZ` + +## 安装 & 升级 + +``` +bash <(curl -Ls https://raw.githubusercontent.com/xeefei/3x-ui/master/install.sh) +``` + +## 安装指定版本 + +若需要安装指定的版本,请将该版本添加到安装命令的末尾。 e.g., ver `v2.3.13`: + +``` +bash <(curl -Ls https://raw.githubusercontent.com/xeefei/3x-ui/master/install.sh) v2.3.13 +``` + +## SSL 认证 + +
+ 点击查看 SSL 认证 + +### Cloudflare + +管理脚本具有用于 Cloudflare 的内置 SSL 证书应用程序。若要使用此脚本申请证书,需要满足以下条件: + +- Cloudflare 邮箱地址 +- Cloudflare Global API Key +- 域名已通过 cloudflare 解析到当前服务器 + +**1:** 在终端中运行`x-ui`, 选择 `Cloudflare SSL Certificate`. + + +### Certbot +``` +apt-get install certbot -y +certbot certonly --standalone --agree-tos --register-unsafely-without-email -d yourdomain.com +certbot renew --dry-run +``` + +***Tip:*** *管理脚本具有 Certbot 。使用 `x-ui` 命令, 选择 `SSL Certificate Management`.* + +
+ +## 手动安装 & 升级 + +
+ 点击查看 手动安装 & 升级 + +#### 使用 + +1. 若要将最新版本的压缩包直接下载到服务器,请运行以下命令: + +```sh +ARCH=$(uname -m) +case "${ARCH}" in + x86_64 | x64 | amd64) XUI_ARCH="amd64" ;; + i*86 | x86) XUI_ARCH="386" ;; + armv8* | armv8 | arm64 | aarch64) XUI_ARCH="arm64" ;; + armv7* | armv7) XUI_ARCH="armv7" ;; + armv6* | armv6) XUI_ARCH="armv6" ;; + armv5* | armv5) XUI_ARCH="armv5" ;; + *) XUI_ARCH="amd64" ;; +esac + + +wget https://github.com/xeefei/3x-ui/releases/latest/download/x-ui-linux-${XUI_ARCH}.tar.gz +``` + +2. 下载压缩包后,执行以下命令安装或升级 x-ui: + +```sh +ARCH=$(uname -m) +case "${ARCH}" in + x86_64 | x64 | amd64) XUI_ARCH="amd64" ;; + i*86 | x86) XUI_ARCH="386" ;; + armv8* | armv8 | arm64 | aarch64) XUI_ARCH="arm64" ;; + armv7* | armv7) XUI_ARCH="armv7" ;; + armv6* | armv6) XUI_ARCH="armv6" ;; + armv5* | armv5) XUI_ARCH="armv5" ;; + *) XUI_ARCH="amd64" ;; +esac + +cd /root/ +rm -rf x-ui/ /usr/local/x-ui/ /usr/bin/x-ui +tar zxvf x-ui-linux-${XUI_ARCH}.tar.gz +chmod +x x-ui/x-ui x-ui/bin/xray-linux-* x-ui/x-ui.sh +cp x-ui/x-ui.sh /usr/bin/x-ui +cp -f x-ui/x-ui.service /etc/systemd/system/ +mv x-ui/ /usr/local/ +systemctl daemon-reload +systemctl enable x-ui +systemctl restart x-ui +``` + +
+ +## 通过Docker安装 + +
+ 点击查看 通过Docker安装 + +#### 使用 + +1. 安装Docker: + + ```sh + bash <(curl -sSL https://get.docker.com) + ``` + +2. 克隆仓库: + + ```sh + git clone https://github.com/xeefei/3x-ui.git + cd 3x-ui + ``` + +3. 运行服务: + + ```sh + docker compose up -d + ``` + + 或 + + ```sh + docker run -itd \ + -e XRAY_VMESS_AEAD_FORCED=false \ + -v $PWD/db/:/etc/x-ui/ \ + -v $PWD/cert/:/root/cert/ \ + --network=host \ + --restart=unless-stopped \ + --name 3x-ui \ + ghcr.io/xeefei/3x-ui:latest + ``` + +更新至最新版本 + + ```sh + cd 3x-ui + docker compose down + docker compose pull 3x-ui + docker compose up -d + ``` + +从Docker中删除3x-ui + + ```sh + docker stop 3x-ui + docker rm 3x-ui + cd -- + rm -r 3x-ui + ``` + +
+ + +## 建议使用的操作系统 + +- Ubuntu 20.04+ +- Debian 11+ +- CentOS 8+ +- Fedora 36+ +- Arch Linux +- Manjaro +- Armbian +- AlmaLinux 9+ +- Rockylinux 9+ +- OpenSUSE Tubleweed + +## 支持的架构和设备 +
+ 点击查看 支持的架构和设备 + +我们的平台提供与各种架构和设备的兼容性,确保在各种计算环境中的灵活性。以下是我们支持的关键架构: + +- **amd64**: 这种流行的架构是个人计算机和服务器的标准,可以无缝地适应大多数现代操作系统。 + +- **x86 / i386**: 这种架构在台式机和笔记本电脑中被广泛采用,得到了众多操作系统和应用程序的广泛支持,包括但不限于 Windows、macOS 和 Linux 系统。 + +- **armv8 / arm64 / aarch64**: 这种架构专为智能手机和平板电脑等当代移动和嵌入式设备量身定制,以 Raspberry Pi 4、Raspberry Pi 3、Raspberry Pi Zero 2/Zero 2 W、Orange Pi 3 LTS 等设备为例。 + +- **armv7 / arm / arm32**: 作为较旧的移动和嵌入式设备的架构,它仍然广泛用于Orange Pi Zero LTS、Orange Pi PC Plus、Raspberry Pi 2等设备。 + +- **armv6 / arm / arm32**: 这种架构面向非常老旧的嵌入式设备,虽然不太普遍,但仍在使用中。Raspberry Pi 1、Raspberry Pi Zero/Zero W 等设备都依赖于这种架构。 + +- **armv5 / arm / arm32**: 它是一种主要与早期嵌入式系统相关的旧架构,目前不太常见,但仍可能出现在早期 Raspberry Pi 版本和一些旧智能手机等传统设备中。 +
+ +## Languages + +- English(英语) +- Farsi(伊朗语) +- Chinese(中文) +- Russian(俄语) +- Vietnamese(越南语) +- Spanish(西班牙语) +- Indonesian (印度尼西亚语) +- Ukrainian(乌克兰语) + + +## Features + +- 系统状态监控 +- 在所有入站和客户端中搜索 +- 深色/浅色主题 +- 支持多用户和多协议 +- 支持多种协议,包括 VMess、VLESS、Trojan、Shadowsocks、Dokodemo-door、Socks、HTTP、wireguard +- 支持 XTLS 原生协议,包括 RPRX-Direct、Vision、REALITY +- 流量统计、流量限制、过期时间限制 +- 可自定义的 Xray配置模板 +- 支持HTTPS访问面板(自建域名+SSL证书) +- 支持一键式SSL证书申请和自动续费 +- 更多高级配置项目请参考面板 +- 修复了 API 路由(用户设置将使用 API 创建) +- 支持通过面板中提供的不同项目更改配置。 +- 支持从面板导出/导入数据库 + + +## 默认设置 + +
+ 点击查看 默认设置 + + ### 信息 + +- **端口:** 2053 +- **用户名 & 密码:** 当您跳过设置时,此项会随机生成。 +- **数据库路径:** + - /etc/x-ui/x-ui.db +- **Xray 配置路径:** + - /usr/local/x-ui/bin/config.json +- **面板链接(无SSL):** + - http://ip:2053/panel + - http://domain:2053/panel +- **面板链接(有SSL):** + - https://domain:2053/panel + +
+ +## WARP 配置 + +
+ 点击查看 WARP 配置 + +#### 使用 + +如果要在 v2.1.0 之前使用 WARP 路由,请按照以下步骤操作: + +**1.** 在 **SOCKS Proxy Mode** 模式中安装Wrap + + ```sh + bash <(curl -sSL https://raw.githubusercontent.com/hamid-gh98/x-ui-scripts/main/install_warp_proxy.sh) + ``` + +**2.** 如果您已经安装了 warp,您可以使用以下命令卸载: + + ```sh + warp u + ``` + +**3.** 在面板中打开您需要的配置 + + 配置: + + - Block Ads + - Route Google + Netflix + Spotify + OpenAI (ChatGPT) to WARP + - Fix Google 403 error + +
+ +## IP 限制 + +
+ 点击查看 IP 限制 + +#### 使用 + +**注意:** 使用 IP 隧道时,IP 限制无法正常工作。 + +- 适用于最高 `v1.6.1` : + + - IP 限制 已被集成在面板中。 + +- 适用于 `v1.7.0` 以及更新的版本: + + - 要使 IP 限制正常工作,您需要按照以下步骤安装 fail2ban 及其所需的文件: + + 1. 使用面板内置的 `x-ui` 指令 + 2. 选择 `IP Limit Management`. + 3. 根据您的需要选择合适的选项。 + + - 确保您的 Xray 配置上有 ./access.log 。在 v2.1.3 之后,我们有一个选项。 + + ```sh + "log": { + "access": "./access.log", + "dnsLog": false, + "loglevel": "warning" + }, + ``` + +
+ +## Telegram 机器人 + +
+ 点击查看 Telegram 机器人 + +#### 使用 + +Web 面板通过 Telegram Bot 支持每日流量、面板登录、数据库备份、系统状态、客户端信息等通知和功能。要使用机器人,您需要在面板中设置机器人相关参数,包括: + +- 电报令牌 +- 管理员聊天 ID +- 通知时间(cron 语法) +- 到期日期通知 +- 流量上限通知 +- 数据库备份 +- CPU 负载通知 + + +**参考:** + +- `30 \* \* \* \* \*` - 在每个点的 30 秒处通知 +- `0 \*/10 \* \* \* \*` - 每 10 分钟的第一秒通知 +- `@hourly` - 每小时通知 +- `@daily` - 每天通知 (00:00) +- `@weekly` - 每周通知 +- `@every 8h` - 每8小时通知 + +### Telegram Bot 功能 + +- 定期报告 +- 登录通知 +- CPU 阈值通知 +- 提前报告的过期时间和流量阈值 +- 如果将客户的电报用户名添加到用户的配置中,则支持客户端报告菜单 +- 支持使用UUID(VMESS/VLESS)或密码(TROJAN)搜索报文流量报告 - 匿名 +- 基于菜单的机器人 +- 通过电子邮件搜索客户端(仅限管理员) +- 检查所有入库 +- 检查服务器状态 +- 检查耗尽的用户 +- 根据请求和定期报告接收备份 +- 多语言机器人 + +### 注册 Telegram bot + +- 与 [Botfather](https://t.me/BotFather) 对话: + ![Botfather](./media/botfather.png) + +- 使用 /newbot 创建新机器人:你需要提供机器人名称以及用户名,注意名称中末尾要包含“bot” + ![创建机器人](./media/newbot.png) + +- 启动您刚刚创建的机器人。可以在此处找到机器人的链接。 + ![令牌](./media/token.png) + +- 输入您的面板并配置 Telegram 机器人设置,如下所示: + ![面板设置](./media/panel-bot-config.png) + +在输入字段编号 3 中输入机器人令牌。 +在输入字段编号 4 中输入用户 ID。具有此 id 的 Telegram 帐户将是机器人管理员。 (您可以输入多个,只需将它们用“ ,”分开即可) + +- 如何获取TG ID? 使用 [bot](https://t.me/useridinfobot), 启动机器人,它会给你 Telegram 用户 ID。 +![用户 ID](./media/user-id.png) + +
+ +## API 路由 + +
+ 点击查看 API 路由 + +#### 使用 + +- `/login` 使用 `POST` 用户名称 & 密码: `{username: '', password: ''}` 登录 +- `/panel/api/inbounds` 以下操作的基础: + +| 方法 | 路径 | 操作 | +| :----: | ---------------------------------- | --------------------------------- | +| `GET` | `"/list"` | 获取所有入站 | +| `GET` | `"/get/:id"` | 获取所有入站以及inbound.id | +| `GET` | `"/getClientTraffics/:email"` | 通过电子邮件获取客户端流量 | +| `GET` | `"/createbackup"` | Telegram 机器人向管理员发送备份 | +| `POST` | `"/add"` | 添加入站 | +| `POST` | `"/del/:id"` | 删除入站 | +| `POST` | `"/update/:id"` | 更新入站 | +| `POST` | `"/clientIps/:email"` | 客户端 IP 地址 | +| `POST` | `"/clearClientIps/:email"` | 清除客户端 IP 地址 | +| `POST` | `"/addClient"` | 将客户端添加到入站 | +| `POST` | `"/:id/delClient/:clientId"` | 通过 clientId\* 删除客户端 | +| `POST` | `"/updateClient/:clientId"` | 通过 clientId\* 更新客户端 | +| `POST` | `"/:id/resetClientTraffic/:email"` | 重置客户端的流量 | +| `POST` | `"/resetAllTraffics"` | 重置所有入站的流量 | +| `POST` | `"/resetAllClientTraffics/:id"` | 重置入站中所有客户端的流量 | +| `POST` | `"/delDepletedClients/:id"` | 删除入站耗尽的客户端 (-1: all) | +| `POST` | `"/onlines"` | 获取在线用户 ( 电子邮件列表 ) | + +\*- `clientId` 项应该使用下列数据 + +- `client.id` VMESS and VLESS +- `client.password` TROJAN +- `client.email` Shadowsocks + + +- [API 文档](https://documenter.getpostman.com/view/16802678/2s9YkgD5jm) +- [Run In Postman](https://app.getpostman.com/run-collection/16802678-1a4c9270-ac77-40ed-959a-7aa56dc4a415?action=collection%2Ffork&source=rip_markdown&collection-url=entityId%3D16802678-1a4c9270-ac77-40ed-959a-7aa56dc4a415%26entityType%3Dcollection%26workspaceId%3D2cd38c01-c851-4a15-a972-f181c23359d9) +
+ +## 环境变量 + +
+ 点击查看 环境变量 + +#### Usage + +| 变量 | Type | 默认 | +| -------------- | :--------------------------------------------: | :------------ | +| XUI_LOG_LEVEL | `"debug"` \| `"info"` \| `"warn"` \| `"error"` | `"info"` | +| XUI_DEBUG | `boolean` | `false` | +| XUI_BIN_FOLDER | `string` | `"bin"` | +| XUI_DB_FOLDER | `string` | `"/etc/x-ui"` | +| XUI_LOG_FOLDER | `string` | `"/var/log"` | + +例子: + +```sh +XUI_BIN_FOLDER="bin" XUI_DB_FOLDER="/etc/x-ui" go build main.go +``` + +
+ +## 预览 + +![1](./media/1.png) +![2](./media/2.png) +![3](./media/3.png) +![4](./media/4.png) +![5](./media/5.png) +![6](./media/6.png) +![7](./media/7.png) + +## 特别感谢 + +- [alireza0](https://github.com/alireza0/) + +## 致谢 + +- [Iran v2ray rules](https://github.com/chocolate4u/Iran-v2ray-rules) (License: **GPL-3.0**): _Enhanced v2ray/xray and v2ray/xray-clients routing rules with built-in Iranian domains and a focus on security and adblocking._ +- [Vietnam Adblock rules](https://github.com/vuong2023/vn-v2ray-rules) (License: **GPL-3.0**): _A hosted domain hosted in Vietnam and blocklist with the most efficiency for Vietnamese._ + +## Star趋势 + +[![Stargazers over time](https://starchart.cc/xeefei/3x-ui.svg)](https://starchart.cc/xeefei/3x-ui) diff --git a/README.zhzh_Hans.md b/README.zhzh_Hans.md new file mode 100644 index 0000000000..804b5e730f --- /dev/null +++ b/README.zhzh_Hans.md @@ -0,0 +1,476 @@ +[English](/README.md) | [汉语](/README.zh_Hans.md) | [Español](/README.es_ES.md) | [Русский](/README.ru_RU.md) + +

Image

+ +**一个更好的面板 • 基于Xray Core构建** + +[![](https://img.shields.io/github/v/release/mhsanaei/3x-ui.svg)](https://github.com/MHSanaei/3x-ui/releases) +[![](https://img.shields.io/github/actions/workflow/status/mhsanaei/3x-ui/release.yml.svg)](#) +[![GO Version](https://img.shields.io/github/go-mod/go-version/mhsanaei/3x-ui.svg)](#) +[![Downloads](https://img.shields.io/github/downloads/mhsanaei/3x-ui/total.svg)](#) +[![License](https://img.shields.io/badge/license-GPL%20V3-blue.svg?longCache=true)](https://www.gnu.org/licenses/gpl-3.0.en.html) + +> **Disclaimer:** 此项目仅供个人学习交流,请不要用于非法目的,请不要在生产环境中使用。 + +**如果此项目对你有用,请给一个**:star2: + +

+ + Image + +

+ +- USDT (TRC20): `TYQEmQp1P65u9bG7KPehgJdvuokfb72YkZ` + +## 安装 & 升级 + +``` +bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) +``` + +## 安装指定版本 + +要安装所需的版本,请将该版本添加到安装命令的末尾。 e.g., ver `v2.3.13`: + +``` +bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) v2.3.13 +``` + +## SSL 认证 + +
+ 点击查看 SSL 认证 + +### Cloudflare + +管理脚本具有用于 Cloudflare 的内置 SSL 证书应用程序。若要使用此脚本申请证书,需要满足以下条件: + +- Cloudflare 邮箱地址 +- Cloudflare Global API Key +- 域名已通过 cloudflare 解析到当前服务器 + +**1:** 在终端中运行`x-ui`, 选择 `Cloudflare SSL Certificate`. + + +### Certbot +``` +apt-get install certbot -y +certbot certonly --standalone --agree-tos --register-unsafely-without-email -d yourdomain.com +certbot renew --dry-run +``` + +***Tip:*** *管理脚本具有 Certbot 。使用 `x-ui` 命令, 选择 `SSL Certificate Management`.* + +
+ +## 手动安装 & 升级 + +
+ 点击查看 手动安装 & 升级 + +#### 使用 + +1. 若要将最新版本的压缩包直接下载到服务器,请运行以下命令: + +```sh +ARCH=$(uname -m) +case "${ARCH}" in + x86_64 | x64 | amd64) XUI_ARCH="amd64" ;; + i*86 | x86) XUI_ARCH="386" ;; + armv8* | armv8 | arm64 | aarch64) XUI_ARCH="arm64" ;; + armv7* | armv7) XUI_ARCH="armv7" ;; + armv6* | armv6) XUI_ARCH="armv6" ;; + armv5* | armv5) XUI_ARCH="armv5" ;; + *) XUI_ARCH="amd64" ;; +esac + + +wget https://github.com/MHSanaei/3x-ui/releases/latest/download/x-ui-linux-${XUI_ARCH}.tar.gz +``` + +2. 下载压缩包后,执行以下命令安装或升级 x-ui: + +```sh +ARCH=$(uname -m) +case "${ARCH}" in + x86_64 | x64 | amd64) XUI_ARCH="amd64" ;; + i*86 | x86) XUI_ARCH="386" ;; + armv8* | armv8 | arm64 | aarch64) XUI_ARCH="arm64" ;; + armv7* | armv7) XUI_ARCH="armv7" ;; + armv6* | armv6) XUI_ARCH="armv6" ;; + armv5* | armv5) XUI_ARCH="armv5" ;; + *) XUI_ARCH="amd64" ;; +esac + +cd /root/ +rm -rf x-ui/ /usr/local/x-ui/ /usr/bin/x-ui +tar zxvf x-ui-linux-${XUI_ARCH}.tar.gz +chmod +x x-ui/x-ui x-ui/bin/xray-linux-* x-ui/x-ui.sh +cp x-ui/x-ui.sh /usr/bin/x-ui +cp -f x-ui/x-ui.service /etc/systemd/system/ +mv x-ui/ /usr/local/ +systemctl daemon-reload +systemctl enable x-ui +systemctl restart x-ui +``` + +
+ +## 通过Docker安装 + +
+ 点击查看 通过Docker安装 + +#### 使用 + +1. 安装Docker: + + ```sh + bash <(curl -sSL https://get.docker.com) + ``` + +2. 克隆仓库: + + ```sh + git clone https://github.com/MHSanaei/3x-ui.git + cd 3x-ui + ``` + +3. 运行服务: + + ```sh + docker compose up -d + ``` + + 或 + + ```sh + docker run -itd \ + -e XRAY_VMESS_AEAD_FORCED=false \ + -v $PWD/db/:/etc/x-ui/ \ + -v $PWD/cert/:/root/cert/ \ + --network=host \ + --restart=unless-stopped \ + --name 3x-ui \ + ghcr.io/mhsanaei/3x-ui:latest + ``` + +更新至最新版本 + + ```sh + cd 3x-ui + docker compose down + docker compose pull 3x-ui + docker compose up -d + ``` + +从Docker中删除3x-ui + + ```sh + docker stop 3x-ui + docker rm 3x-ui + cd -- + rm -r 3x-ui + ``` + +
+ + +## 建议使用的操作系统 + +- Ubuntu 20.04+ +- Debian 11+ +- CentOS 8+ +- Fedora 36+ +- Arch Linux +- Manjaro +- Armbian +- AlmaLinux 9+ +- Rockylinux 9+ +- OpenSUSE Tubleweed + +## 支持的架构和设备 +
+ 点击查看 支持的架构和设备 + +我们的平台提供与各种架构和设备的兼容性,确保在各种计算环境中的灵活性。以下是我们支持的关键架构: + +- **amd64**: 这种流行的架构是个人计算机和服务器的标准,可以无缝地适应大多数现代操作系统。 + +- **x86 / i386**: 这种架构在台式机和笔记本电脑中被广泛采用,得到了众多操作系统和应用程序的广泛支持,包括但不限于 Windows、macOS 和 Linux 系统。 + +- **armv8 / arm64 / aarch64**: 这种架构专为智能手机和平板电脑等当代移动和嵌入式设备量身定制,以 Raspberry Pi 4、Raspberry Pi 3、Raspberry Pi Zero 2/Zero 2 W、Orange Pi 3 LTS 等设备为例。 + +- **armv7 / arm / arm32**: 作为较旧的移动和嵌入式设备的架构,它仍然广泛用于Orange Pi Zero LTS、Orange Pi PC Plus、Raspberry Pi 2等设备。 + +- **armv6 / arm / arm32**: 这种架构面向非常老旧的嵌入式设备,虽然不太普遍,但仍在使用中。Raspberry Pi 1、Raspberry Pi Zero/Zero W 等设备都依赖于这种架构。 + +- **armv5 / arm / arm32**: 它是一种主要与早期嵌入式系统相关的旧架构,目前不太常见,但仍可能出现在早期 Raspberry Pi 版本和一些旧智能手机等传统设备中。 +
+ +## Languages + +- English(英语) +- Farsi(伊朗语) +- Chinese(中文) +- Russian(俄语) +- Vietnamese(越南语) +- Spanish(西班牙语) +- Indonesian (印度尼西亚语) +- Ukrainian(乌克兰语) + + +## Features + +- 系统状态监控 +- 在所有入站和客户端中搜索 +- 深色/浅色主题 +- 支持多用户和多协议 +- 支持多种协议,包括 VMess、VLESS、Trojan、Shadowsocks、Dokodemo-door、Socks、HTTP、wireguard +- 支持 XTLS 原生协议,包括 RPRX-Direct、Vision、REALITY +- 流量统计、流量限制、过期时间限制 +- 可自定义的 Xray配置模板 +- 支持HTTPS访问面板(自建域名+SSL证书) +- 支持一键式SSL证书申请和自动续费 +- 更多高级配置项目请参考面板 +- 修复了 API 路由(用户设置将使用 API 创建) +- 支持通过面板中提供的不同项目更改配置。 +- 支持从面板导出/导入数据库 + + +## 默认设置 + +
+ 点击查看 默认设置 + + ### 信息 + +- **端口:** 2053 +- **用户名 & 密码:** 当您跳过设置时,此项会随机生成。 +- **数据库路径:** + - /etc/x-ui/x-ui.db +- **Xray 配置路径:** + - /usr/local/x-ui/bin/config.json +- **面板链接(无SSL):** + - http://ip:2053/panel + - http://domain:2053/panel +- **面板链接(有SSL):** + - https://domain:2053/panel + +
+ +## WARP 配置 + +
+ 点击查看 WARP 配置 + +#### 使用 + +如果要在 v2.1.0 之前使用 WARP 路由,请按照以下步骤操作: + +**1.** 在 **SOCKS Proxy Mode** 模式中安装Wrap + + ```sh + bash <(curl -sSL https://raw.githubusercontent.com/hamid-gh98/x-ui-scripts/main/install_warp_proxy.sh) + ``` + +**2.** 如果您已经安装了 warp,您可以使用以下命令卸载: + + ```sh + warp u + ``` + +**3.** 在面板中打开您需要的配置 + + 配置: + + - Block Ads + - Route Google + Netflix + Spotify + OpenAI (ChatGPT) to WARP + - Fix Google 403 error + +
+ +## IP 限制 + +
+ 点击查看 IP 限制 + +#### 使用 + +**注意:** 使用 IP 隧道时,IP 限制无法正常工作。 + +- 适用于最高 `v1.6.1` : + + - IP 限制 已被集成在面板中。 + +- 适用于 `v1.7.0` 以及更新的版本: + + - 要使 IP 限制正常工作,您需要按照以下步骤安装 fail2ban 及其所需的文件: + + 1. 使用面板内置的 `x-ui` 指令 + 2. 选择 `IP Limit Management`. + 3. 根据您的需要选择合适的选项。 + + - 确保您的 Xray 配置上有 ./access.log 。在 v2.1.3 之后,我们有一个选项。 + + ```sh + "log": { + "access": "./access.log", + "dnsLog": false, + "loglevel": "warning" + }, + ``` + +
+ +## Telegram 机器人 + +
+ 点击查看 Telegram 机器人 + +#### 使用 + +Web 面板通过 Telegram Bot 支持每日流量、面板登录、数据库备份、系统状态、客户端信息等通知和功能。要使用机器人,您需要在面板中设置机器人相关参数,包括: + +- 电报令牌 +- 管理员聊天 ID +- 通知时间(cron 语法) +- 到期日期通知 +- 流量上限通知 +- 数据库备份 +- CPU 负载通知 + + +**参考:** + +- `30 \* \* \* \* \*` - 在每个点的 30 秒处通知 +- `0 \*/10 \* \* \* \*` - 每 10 分钟的第一秒通知 +- `@hourly` - 每小时通知 +- `@daily` - 每天通知 (00:00) +- `@weekly` - 每周通知 +- `@every 8h` - 每8小时通知 + +### Telegram Bot 功能 + +- 定期报告 +- 登录通知 +- CPU 阈值通知 +- 提前报告的过期时间和流量阈值 +- 如果将客户的电报用户名添加到用户的配置中,则支持客户端报告菜单 +- 支持使用UUID(VMESS/VLESS)或密码(TROJAN)搜索报文流量报告 - 匿名 +- 基于菜单的机器人 +- 通过电子邮件搜索客户端(仅限管理员) +- 检查所有入库 +- 检查服务器状态 +- 检查耗尽的用户 +- 根据请求和定期报告接收备份 +- 多语言机器人 + +### 注册 Telegram bot + +- 与 [Botfather](https://t.me/BotFather) 对话: + ![Botfather](./media/botfather.png) + +- 使用 /newbot 创建新机器人:你需要提供机器人名称以及用户名,注意名称中末尾要包含“bot” + ![创建机器人](./media/newbot.png) + +- 启动您刚刚创建的机器人。可以在此处找到机器人的链接。 + ![令牌](./media/token.png) + +- 输入您的面板并配置 Telegram 机器人设置,如下所示: + ![面板设置](./media/panel-bot-config.png) + +在输入字段编号 3 中输入机器人令牌。 +在输入字段编号 4 中输入用户 ID。具有此 id 的 Telegram 帐户将是机器人管理员。 (您可以输入多个,只需将它们用“ ,”分开即可) + +- 如何获取TG ID? 使用 [bot](https://t.me/useridinfobot), 启动机器人,它会给你 Telegram 用户 ID。 +![用户 ID](./media/user-id.png) + +
+ +## API 路由 + +
+ 点击查看 API 路由 + +#### 使用 + +- `/login` 使用 `POST` 用户名称 & 密码: `{username: '', password: ''}` 登录 +- `/panel/api/inbounds` 以下操作的基础: + +| 方法 | 路径 | 操作 | +| :----: | ---------------------------------- | --------------------------------- | +| `GET` | `"/list"` | 获取所有入站 | +| `GET` | `"/get/:id"` | 获取所有入站以及inbound.id | +| `GET` | `"/getClientTraffics/:email"` | 通过电子邮件获取客户端流量 | +| `GET` | `"/createbackup"` | Telegram 机器人向管理员发送备份 | +| `POST` | `"/add"` | 添加入站 | +| `POST` | `"/del/:id"` | 删除入站 | +| `POST` | `"/update/:id"` | 更新入站 | +| `POST` | `"/clientIps/:email"` | 客户端 IP 地址 | +| `POST` | `"/clearClientIps/:email"` | 清除客户端 IP 地址 | +| `POST` | `"/addClient"` | 将客户端添加到入站 | +| `POST` | `"/:id/delClient/:clientId"` | 通过 clientId\* 删除客户端 | +| `POST` | `"/updateClient/:clientId"` | 通过 clientId\* 更新客户端 | +| `POST` | `"/:id/resetClientTraffic/:email"` | 重置客户端的流量 | +| `POST` | `"/resetAllTraffics"` | 重置所有入站的流量 | +| `POST` | `"/resetAllClientTraffics/:id"` | 重置入站中所有客户端的流量 | +| `POST` | `"/delDepletedClients/:id"` | 删除入站耗尽的客户端 (-1: all) | +| `POST` | `"/onlines"` | 获取在线用户 ( 电子邮件列表 ) | + +\*- `clientId` 项应该使用下列数据 + +- `client.id` VMESS and VLESS +- `client.password` TROJAN +- `client.email` Shadowsocks + + +- [API 文档](https://documenter.getpostman.com/view/16802678/2s9YkgD5jm) +- [Run In Postman](https://app.getpostman.com/run-collection/16802678-1a4c9270-ac77-40ed-959a-7aa56dc4a415?action=collection%2Ffork&source=rip_markdown&collection-url=entityId%3D16802678-1a4c9270-ac77-40ed-959a-7aa56dc4a415%26entityType%3Dcollection%26workspaceId%3D2cd38c01-c851-4a15-a972-f181c23359d9) +
+ +## 环境变量 + +
+ 点击查看 环境变量 + +#### Usage + +| 变量 | Type | 默认 | +| -------------- | :--------------------------------------------: | :------------ | +| XUI_LOG_LEVEL | `"debug"` \| `"info"` \| `"warn"` \| `"error"` | `"info"` | +| XUI_DEBUG | `boolean` | `false` | +| XUI_BIN_FOLDER | `string` | `"bin"` | +| XUI_DB_FOLDER | `string` | `"/etc/x-ui"` | +| XUI_LOG_FOLDER | `string` | `"/var/log"` | + +例子: + +```sh +XUI_BIN_FOLDER="bin" XUI_DB_FOLDER="/etc/x-ui" go build main.go +``` + +
+ +## 预览 + +![1](./media/1.png) +![2](./media/2.png) +![3](./media/3.png) +![4](./media/4.png) +![5](./media/5.png) +![6](./media/6.png) +![7](./media/7.png) + +## 特别感谢 + +- [alireza0](https://github.com/alireza0/) + +## 致谢 + +- [Iran v2ray rules](https://github.com/chocolate4u/Iran-v2ray-rules) (License: **GPL-3.0**): _Enhanced v2ray/xray and v2ray/xray-clients routing rules with built-in Iranian domains and a focus on security and adblocking._ +- [Vietnam Adblock rules](https://github.com/vuong2023/vn-v2ray-rules) (License: **GPL-3.0**): _A hosted domain hosted in Vietnam and blocklist with the most efficiency for Vietnamese._ + +## Star趋势 + +[![Stargazers over time](https://starchart.cc/MHSanaei/3x-ui.svg)](https://starchart.cc/MHSanaei/3x-ui) diff --git a/docker-compose.yml b/docker-compose.yml index b0a89399c1..cfabaca2f2 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,7 +3,7 @@ version: "3" services: 3x-ui: - image: ghcr.io/mhsanaei/3x-ui:latest + image: ghcr.io/xeefei/3x-ui:latest container_name: 3x-ui hostname: yourhostname volumes: diff --git a/install.sh b/install.sh index 375adc9d8f..2be12ae43b 100644 --- a/install.sh +++ b/install.sh @@ -8,7 +8,7 @@ plain='\033[0m' cur_dir=$(pwd) # check root -[[ $EUID -ne 0 ]] && echo -e "${red}Fatal error: ${plain} Please run this script with root privilege \n " && exit 1 +[[ $EUID -ne 0 ]] && echo -e "${red}致命错误: ${plain} 请使用 root 权限运行此脚本\n" && exit 1 # Check OS and set release variable if [[ -f /etc/os-release ]]; then @@ -18,50 +18,79 @@ elif [[ -f /usr/lib/os-release ]]; then source /usr/lib/os-release release=$ID else - echo "Failed to check the system OS, please contact the author!" >&2 + echo "" + echo -e "${red}检查服务器操作系统失败,请联系作者!${plain}" >&2 exit 1 fi -echo "The OS release is: $release" +echo "" +echo -e "${green}---------->>>>>目前服务器的操作系统为: $release${plain}" arch() { case "$(uname -m)" in - x86_64 | x64 | amd64) echo 'amd64' ;; - i*86 | x86) echo '386' ;; - armv8* | armv8 | arm64 | aarch64) echo 'arm64' ;; - armv7* | armv7 | arm) echo 'armv7' ;; - armv6* | armv6) echo 'armv6' ;; - armv5* | armv5) echo 'armv5' ;; - s390x) echo 's390x' ;; - *) echo -e "${green}Unsupported CPU architecture! ${plain}" && rm -f install.sh && exit 1 ;; + x86_64 | x64 | amd64 ) echo 'amd64' ;; + i*86 | x86 ) echo '386' ;; + armv8* | armv8 | arm64 | aarch64 ) echo 'arm64' ;; + armv7* | armv7 | arm ) echo 'armv7' ;; + armv6* | armv6 ) echo 'armv6' ;; + armv5* | armv5 ) echo 'armv5' ;; + armv5* | armv5 ) echo 's390x' ;; + *) echo -e "${green}不支持的CPU架构! ${plain}" && rm -f install.sh && exit 1 ;; esac } -echo "arch: $(arch)" +echo "" +echo -e "${yellow}---------->>>>>当前系统的架构为: $(arch)${plain}" +echo "" +last_version=$(curl -Ls "https://api.github.com/repos/xeefei/3x-ui/releases/latest" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/') +# 获取 x-ui 版本 +xui_version=$(/usr/local/x-ui/x-ui -v) + +# 检查 xui_version 是否为空 +if [[ -z "$xui_version" ]]; then + echo "" + echo -e "${red}------>>>当前服务器没有安装任何 x-ui 系列代理面板${plain}" + echo "" + echo -e "${green}-------->>>>片刻之后脚本将会自动引导安装〔3X-UI优化版〕${plain}" +else + # 检查版本号中是否包含冒号 + if [[ "$xui_version" == *:* ]]; then + echo -e "${green}---------->>>>>当前代理面板的版本为: ${red}其他 x-ui 分支版本${plain}" + echo "" + echo -e "${green}-------->>>>片刻之后脚本将会自动引导安装〔3X-UI优化版〕${plain}" + else + echo -e "${green}---------->>>>>当前代理面板的版本为: ${red}〔3X-UI优化版〕v${xui_version}${plain}" + fi +fi +echo "" +echo -e "${yellow}---------------------->>>>>〔3X-UI优化版〕最新版为:${last_version}${plain}" +sleep 4 os_version="" os_version=$(grep "^VERSION_ID" /etc/os-release | cut -d '=' -f2 | tr -d '"' | tr -d '.') if [[ "${release}" == "arch" ]]; then - echo "Your OS is Arch Linux" + echo "您的操作系统是 ArchLinux" elif [[ "${release}" == "parch" ]]; then - echo "Your OS is Parch Linux" + echo "您的操作系统是 Parch" elif [[ "${release}" == "manjaro" ]]; then - echo "Your OS is Manjaro" + echo "您的操作系统是 Manjaro" elif [[ "${release}" == "armbian" ]]; then - echo "Your OS is Armbian" + echo "您的操作系统是 Armbian" +elif [[ "${release}" == "alpine" ]]; then + echo "您的操作系统是 Alpine Linux" elif [[ "${release}" == "opensuse-tumbleweed" ]]; then - echo "Your OS is OpenSUSE Tumbleweed" + echo "您的操作系统是 OpenSUSE Tumbleweed" elif [[ "${release}" == "centos" ]]; then if [[ ${os_version} -lt 8 ]]; then - echo -e "${red} Please use CentOS 8 or higher ${plain}\n" && exit 1 + echo -e "${red} 请使用 CentOS 8 或更高版本 ${plain}\n" && exit 1 fi elif [[ "${release}" == "ubuntu" ]]; then if [[ ${os_version} -lt 2004 ]]; then - echo -e "${red} Please use Ubuntu 20 or higher version!${plain}\n" && exit 1 + echo -e "${red} 请使用 Ubuntu 20 或更高版本!${plain}\n" && exit 1 fi elif [[ "${release}" == "fedora" ]]; then if [[ ${os_version} -lt 36 ]]; then - echo -e "${red} Please use Fedora 36 or higher version!${plain}\n" && exit 1 + echo -e "${red} 请使用 Fedora 36 或更高版本!${plain}\n" && exit 1 fi elif [[ "${release}" == "amzn" ]]; then if [[ ${os_version} != "2023" ]]; then @@ -69,31 +98,31 @@ elif [[ "${release}" == "amzn" ]]; then fi elif [[ "${release}" == "debian" ]]; then if [[ ${os_version} -lt 11 ]]; then - echo -e "${red} Please use Debian 11 or higher ${plain}\n" && exit 1 + echo -e "${red} 请使用 Debian 11 或更高版本 ${plain}\n" && exit 1 fi elif [[ "${release}" == "almalinux" ]]; then if [[ ${os_version} -lt 80 ]]; then - echo -e "${red} Please use AlmaLinux 8.0 or higher ${plain}\n" && exit 1 + echo -e "${red} 请使用 AlmaLinux 8.0 或更高版本 ${plain}\n" && exit 1 fi elif [[ "${release}" == "rocky" ]]; then if [[ ${os_version} -lt 8 ]]; then - echo -e "${red} Please use Rocky Linux 8 or higher ${plain}\n" && exit 1 + echo -e "${red} 请使用 RockyLinux 8 或更高版本 ${plain}\n" && exit 1 fi elif [[ "${release}" == "oracle" ]]; then if [[ ${os_version} -lt 8 ]]; then - echo -e "${red} Please use Oracle Linux 8 or higher ${plain}\n" && exit 1 + echo -e "${red} 请使用 Oracle Linux 8 或更高版本 ${plain}\n" && exit 1 fi else - echo -e "${red}Your operating system is not supported by this script.${plain}\n" - echo "Please ensure you are using one of the following supported operating systems:" + echo -e "${red}此脚本不支持您的操作系统。${plain}\n" + echo "请确保您使用的是以下受支持的操作系统之一:" echo "- Ubuntu 20.04+" echo "- Debian 11+" echo "- CentOS 8+" echo "- Fedora 36+" echo "- Arch Linux" - echo "- Parch Linux" echo "- Manjaro" echo "- Armbian" + echo "- Alpine Linux" echo "- AlmaLinux 8.0+" echo "- Rocky Linux 8+" echo "- Oracle Linux 8+" @@ -113,9 +142,12 @@ install_base() { fedora | amzn) dnf -y update && dnf install -y -q wget curl tar tzdata ;; - arch | manjaro | parch) + arch | manjaro) pacman -Syu && pacman -Syu --noconfirm wget curl tar tzdata ;; + alpine) + apk update && apk add --no-cache wget curl tar tzdata + ;; opensuse-tumbleweed) zypper refresh && zypper -q install -y wget curl tar timezone ;; @@ -132,102 +164,102 @@ gen_random_string() { } config_after_install() { - echo -e "${yellow}Install/update finished! For security, it's recommended to modify panel settings ${plain}" - read -p "Would you like to customize the panel settings? (If not, random settings will be applied) [y/n]: " config_confirm - - local config_webBasePath=$(gen_random_string 10) - + echo -e "${yellow}安装/更新完成! 为了您的面板安全,建议修改面板设置 ${plain}" + echo "" + read -p "$(echo -e "${green}想继续修改吗?${red}选择“n”以保留旧设置${plain} [y/n]?--->>请输入:")" config_confirm if [[ "${config_confirm}" == "y" || "${config_confirm}" == "Y" ]]; then - - read -p "Please set up your username: " config_account - echo -e "${yellow}Your username will be: ${config_account}${plain}" - - read -p "Please set up your password: " config_password - echo -e "${yellow}Your password will be: ${config_password}${plain}" - - read -p "Please set up the panel port: " config_port - echo -e "${yellow}Your panel port is: ${config_port}${plain}" - - echo -e "${yellow}Your web base path will be generated randomly: ${config_webBasePath}${plain}" - - echo -e "${yellow}Initializing, please wait...${plain}" - - /usr/local/x-ui/x-ui setting -username "${config_account}" -password "${config_password}" -port "${config_port}" -webBasePath "${config_webBasePath}" - echo -e "${yellow}Settings applied successfully!${plain}" - - echo -e "###############################################" - echo -e "${green}Username: ${config_account}${plain}" - echo -e "${green}Password: ${config_password}${plain}" - echo -e "${green}Port: ${config_port}${plain}" - echo -e "${green}WebBasePath: ${config_webBasePath}${plain}" - echo -e "###############################################" - + read -p "请设置您的用户名: " config_account + echo -e "${yellow}您的用户名将是: ${config_account}${plain}" + read -p "请设置您的密码: " config_password + echo -e "${yellow}您的密码将是: ${config_password}${plain}" + read -p "请设置面板端口: " config_port + echo -e "${yellow}您的面板端口号为: ${config_port}${plain}" + read -p "请设置面板登录访问路径(访问方式演示:ip:端口号/路径/): " config_webBasePath + echo -e "${yellow}您的面板访问路径为: ${config_webBasePath}${plain}" + echo -e "${yellow}正在初始化,请稍候...${plain}" + /usr/local/x-ui/x-ui setting -username ${config_account} -password ${config_password} + echo -e "${yellow}用户名和密码设置成功!${plain}" + /usr/local/x-ui/x-ui setting -port ${config_port} + echo -e "${yellow}面板端口号设置成功!${plain}" + /usr/local/x-ui/x-ui setting -webBasePath ${config_webBasePath} + echo -e "${yellow}面板登录访问路径设置成功!${plain}" + echo "" else - - echo -e "${red}Cancel...${plain}" - + echo "" + sleep 1 + echo -e "${red}--------------->>>>Cancel...--------------->>>>>>>取消修改...${plain}" + echo "" if [[ ! -f "/etc/x-ui/x-ui.db" ]]; then - - local usernameTemp=$(gen_random_string 10) - local passwordTemp=$(gen_random_string 10) - local portTemp=$(shuf -i 1024-62000 -n 1) - - /usr/local/x-ui/x-ui setting -username "${usernameTemp}" -password "${passwordTemp}" -port "${portTemp}" -webBasePath "${config_webBasePath}" - echo -e "This is a fresh installation, generating random login info for security concerns:" + local usernameTemp=$(head -c 6 /dev/urandom | base64) + local passwordTemp=$(head -c 6 /dev/urandom | base64) + local webBasePathTemp=$(gen_random_string 10) + /usr/local/x-ui/x-ui setting -username ${usernameTemp} -password ${passwordTemp} -webBasePath ${webBasePathTemp} + echo -e "${yellow}检测到为全新安装,出于安全考虑将生成随机登录信息:${plain}" echo -e "###############################################" - echo -e "${green}Username: ${usernameTemp}${plain}" - echo -e "${green}Password: ${passwordTemp}${plain}" - echo -e "${green}Port: ${portTemp}${plain}" - echo -e "${green}WebBasePath: ${config_webBasePath}${plain}" + echo -e "${green}用户名: ${usernameTemp}${plain}" + echo -e "${green}密 码: ${passwordTemp}${plain}" + echo -e "${green}访问路径: ${webBasePathTemp}${plain}" echo -e "###############################################" - echo -e "${yellow}If you forgot your login info, you can type 'x-ui settings' to check after installation${plain}" + echo -e "${green}如果您忘记了登录信息,可以在安装后通过 x-ui 命令然后输入${red}数字 10 选项${green}进行查看${plain}" else - echo -e "${yellow}This is your upgrade, keeping old settings. If you forgot your login info, you can type 'x-ui settings' to check${plain}" - - local existing_webBasePath=$(/usr/local/x-ui/x-ui setting -show true | grep -Eo 'webBasePath: .+' | awk '{print $2}') - - if [[ ${#existing_webBasePath} -lt 4 ]]; then - echo -e "${yellow}WebBasePath is empty, generating a random one...${plain}" - - /usr/local/x-ui/x-ui setting -webBasePath "${config_webBasePath}" - echo -e "${green}New webBasePath: ${config_webBasePath}${plain}" - fi + echo -e "${green}此次操作属于版本升级,保留之前旧设置项,登录方式保持不变${plain}" + echo "" + echo -e "${green}如果您忘记了登录信息,您可以通过 x-ui 命令然后输入${red}数字 10 选项${green}进行查看${plain}" + echo "" + echo "" fi fi - + sleep 1 + echo -e ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>" + echo "" /usr/local/x-ui/x-ui migrate } +echo "" install_x-ui() { cd /usr/local/ if [ $# == 0 ]; then - tag_version=$(curl -Ls "https://api.github.com/repos/MHSanaei/3x-ui/releases/latest" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/') - if [[ ! -n "$tag_version" ]]; then - echo -e "${red}Failed to fetch x-ui version, it may be due to GitHub API restrictions, please try it later${plain}" + last_version=$(curl -Ls "https://api.github.com/repos/xeefei/3x-ui/releases/latest" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/') + if [[ ! -n "$last_version" ]]; then + echo -e "${red}获取 3x-ui 版本失败,可能是 Github API 限制,请稍后再试${plain}" exit 1 fi - echo -e "Got x-ui latest version: ${tag_version}, beginning the installation..." - wget -N --no-check-certificate -O /usr/local/x-ui-linux-$(arch).tar.gz https://github.com/MHSanaei/3x-ui/releases/download/${tag_version}/x-ui-linux-$(arch).tar.gz + echo "" + echo -e "-----------------------------------------------------" + echo -e "${green}--------->>获取 3x-ui 最新版本:${yellow}${last_version}${plain}${green},开始安装...${plain}" + echo -e "-----------------------------------------------------" + echo "" + sleep 2 + echo -e "${green}---------------->>>>>>>>>安装进度50%${plain}" + sleep 3 + echo "" + echo -e "${green}---------------->>>>>>>>>>>>>>>>>>>>>安装进度100%${plain}" + echo "" + sleep 2 + wget -N --no-check-certificate -O /usr/local/x-ui-linux-$(arch).tar.gz https://github.com/xeefei/3x-ui/releases/download/${last_version}/x-ui-linux-$(arch).tar.gz if [[ $? -ne 0 ]]; then - echo -e "${red}Downloading x-ui failed, please be sure that your server can access GitHub ${plain}" + echo -e "${red}下载 3x-ui 失败, 请检查服务器是否可以连接至 GitHub? ${plain}" exit 1 fi else - tag_version=$1 - tag_version_numeric=${tag_version#v} - min_version="2.3.5" - - if [[ "$(printf '%s\n' "$min_version" "$tag_version_numeric" | sort -V | head -n1)" != "$min_version" ]]; then - echo -e "${red}Please use a newer version (at least v2.3.5). Exiting installation.${plain}" - exit 1 - fi - - url="https://github.com/MHSanaei/3x-ui/releases/download/${tag_version}/x-ui-linux-$(arch).tar.gz" - echo -e "Beginning to install x-ui $1" + last_version=$1 + url="https://github.com/xeefei/3x-ui/releases/download/${last_version}/x-ui-linux-$(arch).tar.gz" + echo "" + echo -e "--------------------------------------------" + echo -e "${green}---------------->>>>开始安装 3x-ui $1${plain}" + echo -e "--------------------------------------------" + echo "" + sleep 2 + echo -e "${green}---------------->>>>>>>>>安装进度50%${plain}" + sleep 3 + echo "" + echo -e "${green}---------------->>>>>>>>>>>>>>>>>>>>>安装进度100%${plain}" + echo "" + sleep 2 wget -N --no-check-certificate -O /usr/local/x-ui-linux-$(arch).tar.gz ${url} if [[ $? -ne 0 ]]; then - echo -e "${red}Download x-ui $1 failed, please check if the version exists ${plain}" + echo -e "${red}下载 3x-ui $1 失败, 请检查此版本是否存在 ${plain}" exit 1 fi fi @@ -236,7 +268,10 @@ install_x-ui() { systemctl stop x-ui rm /usr/local/x-ui/ -rf fi - + + sleep 3 + echo -e "${green}------->>>>>>>>>>>检查并保存安装目录${plain}" + echo "" tar zxvf x-ui-linux-$(arch).tar.gz rm x-ui-linux-$(arch).tar.gz -f cd x-ui @@ -250,36 +285,84 @@ install_x-ui() { chmod +x x-ui bin/xray-linux-$(arch) cp -f x-ui.service /etc/systemd/system/ - wget --no-check-certificate -O /usr/bin/x-ui https://raw.githubusercontent.com/MHSanaei/3x-ui/main/x-ui.sh + wget --no-check-certificate -O /usr/bin/x-ui https://raw.githubusercontent.com/xeefei/3x-ui/main/x-ui.sh chmod +x /usr/local/x-ui/x-ui.sh chmod +x /usr/bin/x-ui + sleep 2 + echo -e "${green}------->>>>>>>>>>>保存成功${plain}" + sleep 2 + echo "" config_after_install systemctl daemon-reload systemctl enable x-ui systemctl start x-ui - echo -e "${green}x-ui ${tag_version}${plain} installation finished, it is running now..." - echo -e "" - echo -e "x-ui control menu usages: " - echo -e "----------------------------------------------" - echo -e "SUBCOMMANDS:" - echo -e "x-ui - Admin Management Script" - echo -e "x-ui start - Start" - echo -e "x-ui stop - Stop" - echo -e "x-ui restart - Restart" - echo -e "x-ui status - Current Status" - echo -e "x-ui settings - Current Settings" - echo -e "x-ui enable - Enable Autostart on OS Startup" - echo -e "x-ui disable - Disable Autostart on OS Startup" - echo -e "x-ui log - Check logs" - echo -e "x-ui banlog - Check Fail2ban ban logs" - echo -e "x-ui update - Update" - echo -e "x-ui custom - custom version" - echo -e "x-ui install - Install" - echo -e "x-ui uninstall - Uninstall" - echo -e "----------------------------------------------" + systemctl stop warp-go >/dev/null 2>&1 + wg-quick down wgcf >/dev/null 2>&1 + ipv4=$(curl -s4m8 ip.p3terx.com -k | sed -n 1p) + ipv6=$(curl -s6m8 ip.p3terx.com -k | sed -n 1p) + systemctl start warp-go >/dev/null 2>&1 + wg-quick up wgcf >/dev/null 2>&1 + + echo "" + echo -e "------->>>>${green}3x-ui ${last_version}${plain}<<<<安装成功,正在启动..." + sleep 1 + echo "" + echo -e " ---------------------" + echo -e " |${green}3X-UI 控制菜单用法 ${plain}|${plain}" + echo -e " | ${yellow}一个更好的面板 ${plain}|${plain}" + echo -e " | ${yellow}基于Xray Core构建 ${plain}|${plain}" + echo -e "--------------------------------------------" + echo -e "x-ui - 进入管理脚本" + echo -e "x-ui start - 启动 3x-ui 面板" + echo -e "x-ui stop - 关闭 3x-ui 面板" + echo -e "x-ui restart - 重启 3x-ui 面板" + echo -e "x-ui status - 查看 3x-ui 状态" + echo -e "x-ui settings - 查看当前设置信息" + echo -e "x-ui enable - 启用 3x-ui 开机启动" + echo -e "x-ui disable - 禁用 3x-ui 开机启动" + echo -e "x-ui log - 查看 3x-ui 运行日志" + echo -e "x-ui banlog - 检查 Fail2ban 禁止日志" + echo -e "x-ui update - 更新 3x-ui 面板" + echo -e "x-ui custom - 自定义 3x-ui 版本" + echo -e "x-ui install - 安装 3x-ui 面板" + echo -e "x-ui uninstall - 卸载 3x-ui 面板" + echo -e "--------------------------------------------" + echo "" + # if [[ -n $ipv4 ]]; then + # echo -e "${yellow}面板 IPv4 访问地址为:${green}http://$ipv4:${config_port}/${config_webBasePath}${plain}" + # fi + # if [[ -n $ipv6 ]]; then + # echo -e "${yellow}面板 IPv6 访问地址为:${green}http://[$ipv6]:${config_port}/${config_webBasePath}${plain}" + # fi + # echo -e "请自行确保此端口没有被其他程序占用,${yellow}并且确保${red} ${config_port} ${yellow}端口已放行${plain}" + sleep 3 + echo -e ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>" + echo "" + echo -e "${yellow}----->>>3X-UI面板和Xray启动成功<<<-----${plain}" } - -echo -e "${green}Running...${plain}" install_base install_x-ui $1 +echo "" +echo -e "----------------------------------------------" +sleep 4 +info=$(/usr/local/x-ui/x-ui setting -show true) +echo -e "${info}${plain}" +echo "" +echo -e "若您忘记了上述面板信息,后期可通过x-ui命令进入脚本${red}输入数字〔10〕选项获取${plain}" +echo "" +echo -e "----------------------------------------------" +echo "" +sleep 2 +echo -e "${green}安装/更新完成,若在使用过程中有任何问题${plain}" +echo -e "${yellow}请先描述清楚所遇问题加〔3X-UI〕中文交流群${plain}" +echo -e "${yellow}在TG群中${red} https://t.me/XUI_CN ${yellow}截图进行反馈${plain}" +echo "" +echo -e "----------------------------------------------" +echo "" +echo -e "${green}〔3X-UI〕优化版项目地址:${yellow}https://github.com/xeefei/3x-ui${plain}" +echo "" +echo -e "${green} 详细安装教程:${yellow}https://xeefei.github.io/xufei/2024/05/3x-ui/${plain}" +echo "" +echo -e "----------------------------------------------" +echo "" diff --git a/main.go b/main.go index cd8989fab3..25c5154c45 100644 --- a/main.go +++ b/main.go @@ -6,6 +6,8 @@ import ( "log" "os" "os/signal" + "os/exec" + "strings" "syscall" _ "unsafe" @@ -40,7 +42,7 @@ func runWebServer() { err := database.InitDB(config.GetDBPath()) if err != nil { - log.Fatalf("Error initializing database: %v", err) + log.Fatalf("Error initializing database(初始化数据库出错): %v", err) } var server *web.Server @@ -110,53 +112,119 @@ func runWebServer() { func resetSetting() { err := database.InitDB(config.GetDBPath()) if err != nil { - fmt.Println("Failed to initialize database:", err) + fmt.Println("Failed to initialize database(初始化数据库失败):", err) return } settingService := service.SettingService{} err = settingService.ResetSettings() if err != nil { - fmt.Println("Failed to reset settings:", err) + fmt.Println("reset setting failed(重置设置失败):", err) } else { - fmt.Println("Settings successfully reset.") + fmt.Println("reset setting success---->>重置设置成功") } } func showSetting(show bool) { + // 执行 shell 命令获取 IPv4 地址 + cmdIPv4 := exec.Command("sh", "-c", "curl -s4m8 ip.p3terx.com -k | sed -n 1p") + outputIPv4, err := cmdIPv4.Output() + if err != nil { + log.Fatal(err) + } + + // 执行 shell 命令获取 IPv6 地址 + cmdIPv6 := exec.Command("sh", "-c", "curl -s6m8 ip.p3terx.com -k | sed -n 1p") + outputIPv6, err := cmdIPv6.Output() + if err != nil { + log.Fatal(err) + } + + // 去除命令输出中的换行符 + ipv4 := strings.TrimSpace(string(outputIPv4)) + ipv6 := strings.TrimSpace(string(outputIPv6)) + // 定义转义字符,定义不同颜色的转义字符 + const ( + Reset = "\033[0m" + Red = "\033[31m" + Green = "\033[32m" + Yellow = "\033[33m" + ) + if show { settingService := service.SettingService{} port, err := settingService.GetPort() if err != nil { - fmt.Println("get current port failed, error info:", err) + fmt.Println("get current port failed, error info(获取当前端口失败,错误信息):", err) } webBasePath, err := settingService.GetBasePath() if err != nil { - fmt.Println("get webBasePath failed, error info:", err) + fmt.Println("get webBasePath failed, error info(获取访问路径失败,错误信息):", err) } userService := service.UserService{} userModel, err := userService.GetFirstUser() if err != nil { - fmt.Println("get current user info failed, error info:", err) + 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("current username or password is empty--->>当前用户名或密码为空") } - - fmt.Println("current panel settings as follows:") - fmt.Println("username:", username) - fmt.Println("password:", userpasswd) - fmt.Println("port:", port) + fmt.Println("") + fmt.Println(Yellow + "----->>>以下为面板重要信息,请自行记录保存<<<-----" + Reset) + fmt.Println(Green + "Current panel settings as follows (当前面板设置如下):" + Reset) + fmt.Println("") + fmt.Println(Green + fmt.Sprintf("username(用户名): %s", username) + Reset) + fmt.Println(Green + fmt.Sprintf("password(密 码): %s", userpasswd) + Reset) + fmt.Println(Green + fmt.Sprintf("port(端口号): %d", port) + Reset) if webBasePath != "" { - fmt.Println("webBasePath:", webBasePath) + fmt.Println(Green + fmt.Sprintf("webBasePath(访问路径): %s", webBasePath) + Reset) } else { - fmt.Println("webBasePath is not set") + fmt.Println("webBasePath is not set----->>未设置访问路径") } + fmt.Println("") + fmt.Println("--------------------------------------------------") + // 根据条件打印带颜色的字符串 + if ipv4 != "" { + fmt.Println("") + formattedIPv4 := fmt.Sprintf("%s %s%s:%d%s" + Reset, + Green+"面板 IPv4 访问地址------>>", + Yellow+"http://", + ipv4, + port, + Yellow+webBasePath + Reset) + fmt.Println(formattedIPv4) + fmt.Println("") + } + + if ipv6 != "" { + fmt.Println("") + formattedIPv6 := fmt.Sprintf("%s %s[%s%s%s]:%d%s%s", + Green+"面板 IPv6 访问地址------>>", // 绿色的提示信息 + Yellow+"http://", // 黄色的 http:// 部分 + Yellow, // 黄色的[ 左方括号 + ipv6, // IPv6 地址 + Yellow, // 黄色的] 右方括号 + port, // 端口号 + Yellow+webBasePath, // 黄色的 Web 基础路径 + Reset) // 重置颜色 + fmt.Println(formattedIPv6) + fmt.Println("") + } + fmt.Println(Green + ">>>>>>>>注:若您安装了〔证书〕,请把IP换成您的域名用https方式登录" + Reset) + fmt.Println("") + fmt.Println("--------------------------------------------------") + fmt.Println("↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑") + fmt.Println(fmt.Sprintf("%s请确保 %s%d%s 端口已打开放行%s",Green, Red, port, Green, Reset)) + fmt.Println("请自行确保此端口没有被其他程序占用") + fmt.Println(Green + "若要登录访问面板,请复制上面的地址到浏览器" + Reset) + fmt.Println("") + fmt.Println("--------------------------------------------------") + fmt.Println("") } } @@ -182,7 +250,7 @@ func updateTgbotEnableSts(status bool) { func updateTgbotSetting(tgBotToken string, tgBotChatid string, tgBotRuntime string) { err := database.InitDB(config.GetDBPath()) if err != nil { - fmt.Println("Error initializing database:", err) + fmt.Println("Error initializing database(初始化数据库出错):", err) return } @@ -191,35 +259,35 @@ func updateTgbotSetting(tgBotToken string, tgBotChatid string, tgBotRuntime stri if tgBotToken != "" { err := settingService.SetTgBotToken(tgBotToken) if err != nil { - fmt.Printf("Error setting Telegram bot token: %v\n", err) + fmt.Printf("Error setting Telegram bot token(设置TG电报机器人令牌出错): %v\n", err) return } - logger.Info("Successfully updated Telegram bot token.") + logger.Info("Successfully updated Telegram bot token----->>已成功更新TG电报机器人令牌") } if tgBotRuntime != "" { err := settingService.SetTgbotRuntime(tgBotRuntime) if err != nil { - fmt.Printf("Error setting Telegram bot runtime: %v\n", err) + fmt.Printf("Error setting Telegram bot runtime(设置TG电报机器人通知周期出错): %v\n", err) return } - logger.Infof("Successfully updated Telegram bot runtime to [%s].", tgBotRuntime) + logger.Infof("Successfully updated Telegram bot runtime to(已成功将TG电报机器人通知周期设置为) [%s].", tgBotRuntime) } if tgBotChatid != "" { err := settingService.SetTgBotChatId(tgBotChatid) if err != nil { - fmt.Printf("Error setting Telegram bot chat ID: %v\n", err) + fmt.Printf("Error setting Telegram bot chat ID(设置TG电报机器人管理者聊天ID出错): %v\n", err) return } - logger.Info("Successfully updated Telegram bot chat ID.") + logger.Info("Successfully updated Telegram bot chat ID----->>已成功更新TG电报机器人管理者聊天ID") } } func updateSetting(port int, username string, password string, webBasePath string) { err := database.InitDB(config.GetDBPath()) if err != nil { - fmt.Println("Database initialization failed:", err) + fmt.Println("Database initialization failed(初始化数据库失败):", err) return } @@ -229,27 +297,27 @@ func updateSetting(port int, username string, password string, webBasePath strin if port > 0 { err := settingService.SetPort(port) if err != nil { - fmt.Println("Failed to set port:", err) + fmt.Println("Failed to set port(设置端口失败):", err) } else { - fmt.Printf("Port set successfully: %v\n", port) + fmt.Printf("Port set successfully(端口设置成功): %v\n", port) } } if username != "" || password != "" { err := userService.UpdateFirstUser(username, password) if err != nil { - fmt.Println("Failed to update username and password:", err) + fmt.Println("Failed to update username and password(更新用户名和密码失败):", err) } else { - fmt.Println("Username and password updated successfully") + fmt.Println("Username and password updated successfully------>>用户名和密码更新成功") } } if webBasePath != "" { err := settingService.SetBasePath(webBasePath) if err != nil { - fmt.Println("Failed to set base URI path:", err) + fmt.Println("Failed to set base URI path(设置访问路径失败):", err) } else { - fmt.Println("Base URI path set successfully") + fmt.Println("Base URI path set successfully------>>设置访问路径成功") } } } @@ -265,19 +333,19 @@ func updateCert(publicKey string, privateKey string) { settingService := service.SettingService{} err = settingService.SetCertFile(publicKey) if err != nil { - fmt.Println("set certificate public key failed:", err) + fmt.Println("set certificate public key failed(设置证书公钥失败):", err) } else { - fmt.Println("set certificate public key success") + fmt.Println("set certificate public key success--------->>设置证书公钥成功") } err = settingService.SetKeyFile(privateKey) if err != nil { - fmt.Println("set certificate private key failed:", err) + fmt.Println("set certificate private key failed(设置证书私钥失败):", err) } else { - fmt.Println("set certificate private key success") + fmt.Println("set certificate private key success--------->>设置证书私钥成功") } } else { - fmt.Println("both public and private key should be entered.") + fmt.Println("both public and private key should be entered.------>>必须同时输入证书公钥和私钥") } } @@ -288,9 +356,10 @@ func migrateDb() { if err != nil { log.Fatal(err) } - fmt.Println("Start migrating database...") + fmt.Println("Start migrating database...---->>开始迁移数据库...") inboundService.MigrateDB() - fmt.Println("Migration done!") + fmt.Println("") + fmt.Println("Migration done!------------>>迁移完成!") } func removeSecret() { @@ -424,7 +493,7 @@ func main() { } default: - fmt.Println("Invalid subcommands") + fmt.Println("Invalid subcommands----->>无效命令") fmt.Println() runCmd.Usage() fmt.Println() diff --git a/media/1.png b/media/1.png index 301d4789a2..c2ca585fb7 100644 Binary files a/media/1.png and b/media/1.png differ diff --git a/media/10.png b/media/10.png new file mode 100644 index 0000000000..f8012493fa Binary files /dev/null and b/media/10.png differ diff --git a/media/11.png b/media/11.png new file mode 100644 index 0000000000..f1e51635fb Binary files /dev/null and b/media/11.png differ diff --git a/media/12.png b/media/12.png new file mode 100644 index 0000000000..e0bb2d1db2 Binary files /dev/null and b/media/12.png differ diff --git a/media/13.png b/media/13.png new file mode 100644 index 0000000000..4bb16b2106 Binary files /dev/null and b/media/13.png differ diff --git a/media/14.png b/media/14.png new file mode 100644 index 0000000000..9dd5b05241 Binary files /dev/null and b/media/14.png differ diff --git a/media/15.png b/media/15.png new file mode 100644 index 0000000000..d5165f19de Binary files /dev/null and b/media/15.png differ diff --git a/media/16.png b/media/16.png new file mode 100644 index 0000000000..ccc279ab56 Binary files /dev/null and b/media/16.png differ diff --git a/media/17.png b/media/17.png new file mode 100644 index 0000000000..5fd2713d6e Binary files /dev/null and b/media/17.png differ diff --git a/media/18.png b/media/18.png new file mode 100644 index 0000000000..3c92b90e5d Binary files /dev/null and b/media/18.png differ diff --git a/media/19.png b/media/19.png new file mode 100644 index 0000000000..6a93e9a693 Binary files /dev/null and b/media/19.png differ diff --git a/media/2.png b/media/2.png index 607e2e3756..32f78193ae 100644 Binary files a/media/2.png and b/media/2.png differ diff --git a/media/20.png b/media/20.png new file mode 100644 index 0000000000..c65047d615 Binary files /dev/null and b/media/20.png differ diff --git a/media/21.png b/media/21.png new file mode 100644 index 0000000000..2ffae77221 Binary files /dev/null and b/media/21.png differ diff --git a/media/22.png b/media/22.png new file mode 100644 index 0000000000..d3e557343c Binary files /dev/null and b/media/22.png differ diff --git a/media/23.png b/media/23.png new file mode 100644 index 0000000000..eaee47bc20 Binary files /dev/null and b/media/23.png differ diff --git a/media/24.png b/media/24.png new file mode 100644 index 0000000000..0bd08b67d8 Binary files /dev/null and b/media/24.png differ diff --git a/media/25.png b/media/25.png new file mode 100644 index 0000000000..1c4ed00ae8 Binary files /dev/null and b/media/25.png differ diff --git a/media/26.png b/media/26.png new file mode 100644 index 0000000000..3e8d98d0e8 Binary files /dev/null and b/media/26.png differ diff --git a/media/3.png b/media/3.png index 3c4a2bae15..d97e4ffdb6 100644 Binary files a/media/3.png and b/media/3.png differ diff --git a/media/3X-UI.png b/media/3X-UI.png index e5f76b197b..7aa7c3ef9c 100644 Binary files a/media/3X-UI.png and b/media/3X-UI.png differ diff --git a/media/4.png b/media/4.png index ab8773a823..3762a345b8 100644 Binary files a/media/4.png and b/media/4.png differ diff --git a/media/5.png b/media/5.png index c30356de8c..a18cb4b357 100644 Binary files a/media/5.png and b/media/5.png differ diff --git a/media/6.png b/media/6.png index 030fa9318e..704726bf3b 100644 Binary files a/media/6.png and b/media/6.png differ diff --git a/media/7.png b/media/7.png index 4f12ceaf78..b3cb8ebdf6 100644 Binary files a/media/7.png and b/media/7.png differ diff --git a/media/8.png b/media/8.png new file mode 100644 index 0000000000..23cde0fd34 Binary files /dev/null and b/media/8.png differ diff --git a/media/9.png b/media/9.png new file mode 100644 index 0000000000..b5b510c980 Binary files /dev/null and b/media/9.png differ diff --git a/media/botfather.png b/media/botfather.png index fa57eff4a3..2cbc0ebeb8 100644 Binary files a/media/botfather.png and b/media/botfather.png differ diff --git a/media/newbot.png b/media/newbot.png index 061924aabd..08ef64f371 100644 Binary files a/media/newbot.png and b/media/newbot.png differ diff --git a/media/panel-bot-config.png b/media/panel-bot-config.png index 1238736d4d..05c2cc1b18 100644 Binary files a/media/panel-bot-config.png and b/media/panel-bot-config.png differ diff --git a/media/token.png b/media/token.png index 66fcd5510c..ac7ae1fe56 100644 Binary files a/media/token.png and b/media/token.png differ diff --git a/media/user-id.png b/media/user-id.png index d7d618f3b8..fda9e32d46 100644 Binary files a/media/user-id.png and b/media/user-id.png differ diff --git a/web/assets/ant-design-vue/antd-with-locales2.min.js b/web/assets/ant-design-vue/antd-with-locales2.min.js new file mode 100644 index 0000000000..f719426382 --- /dev/null +++ b/web/assets/ant-design-vue/antd-with-locales2.min.js @@ -0,0 +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=545)}([function(e,t,n){"use strict";var i=n(14),r=n.n(i),a=n(35),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 H})),n.d(t,"q",(function(){return j})),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 w})),n.d(t,"o",(function(){return x})),n.d(t,"m",(function(){return z})),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 $})),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(35),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,w=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===w?{}:w,k.propsData)},w=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=z(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},z=function(e){var t=e.componentOptions;return e.$vnode&&(t=e.$vnode.componentOptions),t&&t.propsData||{}},S=function(e,t){return z(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 H(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 j(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 $(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(245),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;i1&&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 w=n(13),z={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).filter((function(e){return"default"!==e})).map((function(e){return f[e]})))),V("#1890ff");function H(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(w.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()({},z,{viewBox:f}),class:T,style:V};f||delete P.attrs.viewBox;var H=y;void 0===H&&"click"in l&&(H=-1);var j={attrs:{"aria-label":u&&t.icon+": "+u,tabIndex:H},on:l,class:k,staticClass:""};return e("i",j,[function(){if(h)return e(h,P,[C]);if(C){Object(w.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()({},z),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(w.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(w.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(w.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 j={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 H(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}};j.setTwoToneColor=V,j.install=function(e){e.use(P.a),e.component(j.name,j)};var _=t.a=j},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={},w=t.attrs,z=void 0===w?{}:w,S=t.ref,O=t.domProps,M=void 0===O?{}:O,T=t.style,V=void 0===T?{}:T,P=t.class,H=void 0===P?{}:P,j=t.scopedSlots,_=void 0===j?{}:j;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 H&&""!==H.trim()?H.split(" ").forEach((function(e){x[e.trim()]=!0})):x=o()({},x,H),a.data=o()({},C,{style:k,attrs:o()({},C.attrs,z),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(112),o=n(77);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(274),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(251)),r=o(n(261)),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 w(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[];return e.filter(k)}function z(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=w(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 H(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=H(e,i,n);return e(p.a,r(a),[o])}))}function j(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(!z(i.node)){var l=!0,u=!1;(o||[]).filter((function(e){return!z(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;z(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),z(l)||((a||[]).filter((function(e){return!z(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,w=!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],H=V[1];!r.get(P)&&H&&h.push(P)}}catch(e){w=!0,S=e}finally{try{!k&&M.return&&M.return()}finally{if(w)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,t,n){(function(t){for(var i=n(287),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(36);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){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 se(e){var t,n,i;if(ne.isWindow(e)||9===e.nodeType){var r=ne.getWindow(e);t={left:ne.getWindowScrollLeft(r),top:ne.getWindowScrollTop(r)},n=ne.viewportWidth(r),i=ne.viewportHeight(r)}else t=ne.offset(e),n=ne.outerWidth(e),i=ne.outerHeight(e);return t.width=n,t.height=i,t}function ce(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 le(e,t,n,i,r){var a=ce(t,n[1]),o=ce(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 ue(e,t,n){return e.leftn.right}function he(e,t,n){return e.topn.bottom}function de(e,t,n){var i=[];return ne.each(e,(function(e){i.push(e.replace(t,(function(e){return n[e]})))})),i}function fe(e,t){return e[t]=-e[t],e}function pe(e,t){return(/%$/.test(e)?parseInt(e.substring(0,e.length-1),10)/100*t:parseInt(e,10))||0}function ve(e,t){e[0]=pe(e[0],t.width),e[1]=pe(e[1],t.height)}function me(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=oe(c,!(!(s=s||{})||!s.alwaysByViewport)),d=se(c);ve(a,d),ve(o,t);var f=le(d,t,r,a,o),p=ne.merge(d,f);if(h&&(s.adjustX||s.adjustY)&&i){if(s.adjustX&&ue(f,d,h)){var v=de(r,/[lr]/gi,{l:"r",r:"l"}),m=fe(a,0),g=fe(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)),ne.mix(r,a)}(f,d,h,l))}return p.width!==d.width&&ne.css(c,"width",ne.width(c)+p.width-d.width),p.height!==d.height&&ne.css(c,"height",ne.height(c)+p.height-d.height),ne.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 ge(e,t,n){var i=n.target||t;return me(e,se(i),n,!function(e,t){var n=oe(e,t),i=se(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))}ge.__getOffsetParent=re,ge.__getVisibleRectForElement=oe;function be(e){return e&&"object"===(void 0===e?"undefined":g()(e))&&e.window===e}function ye(e,t){var n=Math.floor(e),i=Math.floor(t);return Math.abs(n-i)<=1}var Ce=n(9),xe=n(114),ke=n.n(xe);function we(e){return"function"==typeof e&&e?e():null}function ze(e){return"object"===(void 0===e?"undefined":g()(e))&&e?e:null}var Se={props:{childrenProps:u.a.object,align:u.a.object.isRequired,target:u.a.oneOfType([u.a.func,u.a.object]).def((function(){return window})),monitorBufferTime:u.a.number.def(50),monitorWindowResize:u.a.bool.def(!1),disabled:u.a.bool.def(!1)},data:function(){return this.aligned=!1,{}},mounted:function(){var e=this;this.$nextTick((function(){e.prevProps=a()({},e.$props);var t=e.$props;!e.aligned&&e.forceAlign(),!t.disabled&&t.monitorWindowResize&&e.startMonitorWindowResize()}))},updated:function(){var e=this;this.$nextTick((function(){var t,n,i=e.prevProps,r=e.$props,o=!1;if(!r.disabled){var s=e.$el,c=s?s.getBoundingClientRect():null;if(i.disabled)o=!0;else{var l=we(i.target),u=we(r.target),h=ze(i.target),d=ze(r.target);be(l)&&be(u)?o=!1:(l!==u||l&&!u&&d||h&&d&&u||d&&!((t=h)===(n=d)||t&&n&&("pageX"in n&&"pageY"in n?t.pageX===n.pageX&&t.pageY===n.pageY:"clientX"in n&&"clientY"in n&&t.clientX===n.clientX&&t.clientY===n.clientY)))&&(o=!0);var f=e.sourceRect||{};o||!s||ye(f.width,c.width)&&ye(f.height,c.height)||(o=!0)}e.sourceRect=c}o&&e.forceAlign(),r.monitorWindowResize&&!r.disabled?e.startMonitorWindowResize():e.stopMonitorWindowResize(),e.prevProps=a()({},e.$props,{align:ke()(e.$props.align)})}))},beforeDestroy:function(){this.stopMonitorWindowResize()},methods:{startMonitorWindowResize:function(){this.resizeHandler||(this.bufferMonitor=function(e,t){var n=void 0;function i(){n&&(clearTimeout(n),n=null)}function r(){i(),n=setTimeout(e,t)}return r.clear=i,r}(this.forceAlign,this.$props.monitorBufferTime),this.resizeHandler=Object(p.a)(window,"resize",this.bufferMonitor))},stopMonitorWindowResize:function(){this.resizeHandler&&(this.bufferMonitor.clear(),this.resizeHandler.remove(),this.resizeHandler=null)},forceAlign:function(){var e=this.$props,t=e.disabled,n=e.target,i=e.align;if(!t&&n){var r=this.$el,a=Object(d.k)(this),o=void 0,s=we(n),c=ze(n),l=document.activeElement;s?o=ge(r,s,i):c&&(o=function(e,t,n){var i,r,a=ne.getDocument(e),o=a.defaultView||a.parentWindow,s=ne.getWindowScrollLeft(o),c=ne.getWindowScrollTop(o),l=ne.viewportWidth(o),u=ne.viewportHeight(o),h={left:i="pageX"in t?t.pageX:s+t.clientX,top:r="pageY"in t?t.pageY:c+t.clientY,width:0,height:0},d=i>=0&&i<=s+l&&r>=0&&r<=c+u,f=[n.points[0],"cc"];return me(e,h,y(y({},n),{},{points:f}),d)}(r,c,i)),function(e,t){e!==document.activeElement&&Object(h.a)(t,e)&&e.focus()}(l,r),this.aligned=!0,a.align&&a.align(r,o)}}},render:function(){var e=this.$props.childrenProps,t=Object(d.n)(this)[0];return t&&e?Object(Ce.a)(t,{props:e}):t}},Oe=n(6),Me=n.n(Oe),Te={props:{visible:u.a.bool,hiddenClassName:u.a.string},render:function(){var e=arguments[0],t=this.$props,n=t.hiddenClassName,i=(t.visible,null);if(n||!this.$slots.default||this.$slots.default.length>1){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(46),He=n(4),je={name:"VCTriggerPopup",mixins:[He.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())},w={props:{appear:!0,css:!1}},z=r(),S=!!z,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,z+"-enter",n)})):n()}))},beforeLeave:function(){e.domEl=null},leave:function(e,t){Object(Pe.a)(e,z+"-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;w.props=a()({},w.props,P),w.on=a()({},O,T)}else w.on=O;return S||(w={}),t("transition",w,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:[He.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,w=x.point,z={props:{prefixCls:s,destroyPopupOnHide:c,visible:k,point:C&&w,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(je,z,[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(33),r=n(347),a=n(196),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 w(){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),w.cancel=function(){void 0!==d&&clearTimeout(d),p=0,c=f=l=d=void 0},w.flush=function(){return void 0===d?h:k(r())},w}},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(28),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"]),w=o()({},k);Object(m.s)(this,"visible")&&(w.popupVisible=this.$props.visible);var z=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},w),on:o()({},z,{popupVisibleChange:z.visibleChange||g,popupAlign:z.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 w=n(7),z=n(53),S=Object(z.a)(),O={name:"ATooltip",model:{prop:"visible",event:"visibleChange"},props:o()({},S,{title:u.a.any}),inject:{configProvider:{default:function(){return w.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(215);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(61),r=n(135),a=n(44),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(81);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 w=void 0!==r.ResizeObserver?r.ResizeObserver:k;t.a=w}).call(this,n(134))},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){try{var i=n(180)}catch(e){i=n(180)}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(97))},function(e,t){var n={}.hasOwnProperty;e.exports=function(e,t){return n.call(e,t)}},function(e,t,n){var i=n(71),r=n(272),a=n(273),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(304),r=n(307);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(45),o=n(65),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(49),r=n(86);e.exports=n(50)?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(84);e.exports=function(e){if(!i(e))throw TypeError(e+" is not an object!");return e}},function(e,t,n){var i=n(170),r=n(124);e.exports=function(e){return i(r(e))}},function(e,t){e.exports={}},function(e,t,n){var i=n(39).Symbol;e.exports=i},function(e,t,n){var i=n(139),r=n(140);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(17),w=n.n(k),z=n(25),S=n.n(z);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"});w.a.use(S.a,{name:"ant-ref"});var H=0;function j(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, opacity .3s ease 0s"};return{pathString:h,pathStyle:f}}var F=O({props:Object(u.t)(V,P),created:function(){this.paths={},this.gradientId=H,H+=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":o,opacity:0===n?0:1,"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 j(e)-j(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 $={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,w=(a=(i=c).progressStatus,o=i.successPercent,s=i.strokeColor||E[a],o?[E.success,s]:s),z="[object Object]"===Object.prototype.toString.call(w);return e("div",{class:(n={},r()(n,u+"-inner",!0),r()(n,u+"-circle-gradient",z),n),style:y},[e(F,{attrs:{percent:A(c),strokeWidth:C,trailWidth:C,strokeColor:w,strokeLinecap:p,trailColor:f,prefixCls:u,gapDegree:k,gapPosition:x}}),l])}},D=["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(D),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 D.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($,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(64);t.a=i.a},function(e,t,n){var i=n(169),r=n(128);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(124);e.exports=function(e){return Object(i(e))}},function(e,t,n){"use strict";var i=n(253)(!0);n(172)(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(106),a=n(107),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 w={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(null==t&&!e.required)return n();x.required(e,t,i,a,r,"array"),null!=t&&(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 z(){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=z();function O(e){this.rules=null,this._messages=S,this.define(e)}O.prototype={messages:function(e){return e&&(this._messages=g(z(),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=z()),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({},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&&void 0!==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 void 0!==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({},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(127)("keys"),r=n(93);e.exports=function(e){return i[e]||(i[e]=r(e))}},function(e,t,n){var i=n(43),r=n(48),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(92)?"pure":"global",copyright:"© 2020 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(49).f,r=n(60),a=n(38)("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(258);for(var i=n(48),r=n(67),a=n(70),o=n(38)("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(332),r=n(187),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])}},w=n(13),z={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(w.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=' {{end}} diff --git a/web/html/login.html b/web/html/login.html index 717adc5625..2c22dad2af 100644 --- a/web/html/login.html +++ b/web/html/login.html @@ -400,7 +400,7 @@ - + @@ -461,7 +461,7 @@

- + @@ -476,82 +476,83 @@

{{template "component/themeSwitcher" .}} {{template "component/password" .}} diff --git a/web/html/xui/common_sider.html b/web/html/xui/common_sider.html index df65948956..0158b9f8a4 100644 --- a/web/html/xui/common_sider.html +++ b/web/html/xui/common_sider.html @@ -23,6 +23,15 @@ {{ i18n "menu.xray"}} + + + + + + {{ i18n "menu.navigation"}} + + + @@ -33,33 +42,39 @@ {{define "commonSider"}} - - - - {{template "menuItems" .}} - + + + + {{template "menuItems" .}} + - -
- -
- - - {{template "menuItems" .}} - + +
+ +
+ + + {{template "menuItems" .}} +
{{end}} diff --git a/web/html/xui/component/sortableTable.html b/web/html/xui/component/sortableTable.html index f62eba4460..5eb2e1cc02 100644 --- a/web/html/xui/component/sortableTable.html +++ b/web/html/xui/component/sortableTable.html @@ -1,216 +1,236 @@ {{define "component/sortableTableTrigger"}} - + {{end}} {{define "component/sortableTable"}} + + {{end}} diff --git a/web/html/xui/component/themeSwitch.html b/web/html/xui/component/themeSwitch.html index 0de64a8407..28fe3e112b 100644 --- a/web/html/xui/component/themeSwitch.html +++ b/web/html/xui/component/themeSwitch.html @@ -1,23 +1,6 @@ {{define "component/themeSwitchTemplate"}} -{{end}} - -{{define "component/themeSwitchTemplateLogin"}} - -{{end}} +{{end}} \ No newline at end of file diff --git a/web/html/xui/inbounds.html b/web/html/xui/inbounds.html index 1aad5d919e..6e6ae8f9cd 100644 --- a/web/html/xui/inbounds.html +++ b/web/html/xui/inbounds.html @@ -2,34 +2,6 @@ {{template "head" .}} @@ -391,8 +324,8 @@ [[ sizeFormat(dbInbound.total) ]] @@ -413,11 +346,7 @@ [[ remainedDays(dbInbound._expiryTime) ]] - - - - - + - - - - - + @@ -533,7 +454,7 @@ :columns="isMobile ? innerMobileColumns : innerColumns" :data-source="getInboundClients(record)" :pagination=pagination(getInboundClients(record)) - :style="isMobile ? 'margin: -10px 2px -11px;' : 'margin: -10px 22px -11px;'"> + :style="isMobile ? 'margin: -12px 2px -13px;' : 'margin: -12px 22px -13px;'"> {{template "client_table"}} @@ -631,7 +552,7 @@ { title: '{{ i18n "online" }}', width: 30, scopedSlots: { customRender: 'online' } }, { title: '{{ i18n "pages.inbounds.client" }}', width: 80, scopedSlots: { customRender: 'client' } }, { title: '{{ i18n "pages.inbounds.traffic" }}', width: 80, align: 'center', scopedSlots: { customRender: 'traffic' } }, - { title: '{{ i18n "pages.inbounds.expireDate" }}', width: 80, align: 'center', scopedSlots: { customRender: 'expiryTime' } }, + { title: '{{ i18n "pages.inbounds.expireDate" }}', width: 100, align: 'center', scopedSlots: { customRender: 'expiryTime' } }, ]; const innerMobileColumns = [ diff --git a/web/html/xui/index.html b/web/html/xui/index.html index f1583a8fd1..21e61f3872 100644 --- a/web/html/xui/index.html +++ b/web/html/xui/index.html @@ -2,23 +2,20 @@ {{template "head" .}} @@ -92,8 +89,9 @@ 3X-UI: - v{{ .cur_ver }} - @XrayUI + v{{ .cur_ver }} + TG私聊交流 + 〔3X-UI〕中文交流群 @@ -140,6 +138,8 @@ + 端口检测 + 网络测速 diff --git a/web/html/xui/navigation.html b/web/html/xui/navigation.html new file mode 100644 index 0000000000..389c0066d9 --- /dev/null +++ b/web/html/xui/navigation.html @@ -0,0 +1,167 @@ + + + + + + 实用导航&技巧 + + + + +
+
+

一、【3x-ui】中文交流群:https://t.me/XUI_CN

+

【3x-ui】详细安装流程步骤:https://xeefei.github.io/xufei/2024/05/3x-ui/

+ +

二、判断VPS服务器的IP是否【送中】?

+

***点击打开:https://music.youtube.com/,能正常打开访问,就代表【没送中】,反之就是送中了。

+

***如果送中了如何解决去【拉回来】?

+

1:关闭/取消登录了谷歌账户的APP定位权限/授权;2:将常用的一个谷歌账号的位置记录功能打开;3:在电脑上打开Chrome/谷歌浏览器,登录开了位置记录功能的谷歌账号,安装Location Guard拓展插件https://chrome.google.com/webstore/detail/location-guard/cfohepagpmnodfdmjliccbbigdkfcgia(也可在其他支持此插件的浏览器使用);4:打开Location Guard插件,选择Fixed Location,并在给出的地图上单击,即可标记上你想要IP所处的国家/地区 +Google IP定位错误,使用Location Guard修改;5:转换到Options选项,Default level默认设置为Use fixed location;6:打开谷歌地图google.com/maps,点击右下角定位授权图标,使google maps获取当前“我的GPS位置” +Google IP定位错误,使用Location Guard修改GPS位置地址;7:谷歌搜索my ip,即可看到谷歌IP定位到了刚才地图上标记的位置;8:在此网页向谷歌报告IP问题:https://support.google.com/websearch/workflow/9308722

+ +

三、在自己的VPS服务器部署【订阅转换】功能

+

如何把vless/vmess等协议转换成Clash/Surge等软件支持的格式? + 1、进入脚本输入x-ui命令调取面板,选择第【24】选项安装订阅转换模块, + 2、等待安装【订阅转换】成功之后,访问地址:你的IP:18080(端口号)进行转换, + 3、因为在转换过程中需要调取后端API,所以请确保端口25500是打开放行的, +4、在得到【转换链接】之后,只要你的VPS服务器25500端口是能ping通的,就能导入Clash/Surge等软件成功下载配置, +5、此功能集成到3x-ui面板中,是为了保证安全,通过调取24选项把【订阅转换】功能部署在自己的VPS中,不会造成链接泄露。

+ +

四、如何保护自己的IP不被墙被封?

+

1、使用的代理协议要安全,加密是必备,推荐使用vless+reality+vision协议组合, +2、因为有时节点会共享,在不同的地区,多个省份之间不要共同连接同一个IP, +3、连接同一个IP就算了,不要同一个端口,不要同IP+同端口到处漫游,要分开, +4、同一台VPS,不要在一天内一直大流量去下载东西使用,不要流量过高要切换, +5、创建【入站协议】的时候,尽量用【高位端口】,比如40000--65000之间的端口号。 +提醒:为什么在特殊时期,比如:两会,春节等被封得最严重最惨? +尼玛同一个IP+同一个端口号,多个省份去漫游,跟开飞机场一样!不封你,封谁的IP和端口? +总结:不要多终端/多省份/多个朋友/共同使用同一个IP和端口号!使用3x-ui多创建几个【入站】, +多做几条备用,各用各的!各行其道才比较安全!GFW的思维模式是干掉机场,机场的特征个人用户不要去沾染,自然IP就保护好了。

+ +

五、检测IP纯净度的方法:

+

网址:https://scamalytics.com/,输入IP进行检测,看【欺诈分数】,分数越高IP越脏。

+ +

六、常见的软件工具:

+
    +

    1、Windows系统v2rayN:https://github.com/2dust/v2rayN

    +

    2、安卓手机版【v2rayNG】:https://github.com/2dust/v2rayNG

    +

    3、苹果手机IOS【小火箭】:https://apple02.com/

    +

    4、苹果MacOS电脑【Clash Verge】:https://github.com/clash-verge-rev/clash-verge-rev/releases

    +
+ +

七、查看节点【指定端口】的网络连接数/命令:

+

netstat -ntu | grep :节点端口 | grep ESTABLISHED | awk '{print $5}'

+ +

八、用3x-ui如何实现【自己偷自己】?

+

其实很简单,只要你为面板设置了证书, + 开启了HTTPS登录,就可以将3x-ui自身作为web server, + 无需Nginx等,这里给一个示例: + 其中目标网站(Dest)请填写面板监听端口, + 可选域名(SNI)填写面板登录域名, + 如果您使用其他web server(如nginx)等, + 将目标网站改为对应监听端口也可。 + 需要说明的是,如果您处于白名单地区,自己“偷”自己并不适合你;其次,可选域名一项实际上可以填写任意SNI,只要客户端保持一致即可,不过并不推荐这样做。

+ +

九、【接码】网站:

+

网址:https://sms-activate.org/cn,直接注册账号购买。

+ +

十、一些MJJ经常逛的网站和群组:

+
    +

    1、NodeSeek论坛:https://www.nodeseek.com/

    +

    2、V2EX论坛:https://www.v2ex.com/

    +

    3、搬瓦工TG群:https://t.me/BWHOfficial

    +

    4、Xray的官方群:https://t.me/projectXray

    +

    5、Dmit交流群:https://t.me/DmitChat

    +

    6、白丝云用户群:https://t.me/+VHZLKELTQyzPNgOV

    +

    7、NameSilo域名注册:https://www.namesilo.com/

    +
+ +

十一、若此项目对你有帮助,你正想购买VPS的话,可以走一下我的AFF:

+
    +

    1、搬瓦工GIA线路:https://bandwagonhost.com/aff.php?aff=75015

    +

    2、Dmit高端GIA:https://www.dmit.io/aff.php?aff=9326

    +

    3、白丝云【4837】:https://cloudsilk.io/aff.php?aff=706

    +
+ +

十二、若需要进行GV保号,请主动发信息到:+1 215 346 6666

+ +

十三、项目〔声明和注意〕

+
    +

    1、声明: 此项目仅供个人学习、交流使用,请遵守当地法律法规,勿用于非法用途;请勿用于生产环境;

    +

    2、注意: 在使用此项目和〔教程〕过程中,若因违反以上声明使用规则而产生的一切后果由使用者自负。

    +
+
+ + diff --git a/web/html/xui/settings.html b/web/html/xui/settings.html index 803459a003..447525df82 100644 --- a/web/html/xui/settings.html +++ b/web/html/xui/settings.html @@ -67,6 +67,9 @@ .ant-collapse-content-box .ant-list-item { border-bottom: none !important; } + .red-placeholder input::placeholder { + color: red; + } @@ -135,8 +138,8 @@ - - + + diff --git a/web/html/xui/xray.html b/web/html/xui/xray.html index f467c97a0e..f3f418d3ac 100644 --- a/web/html/xui/xray.html +++ b/web/html/xui/xray.html @@ -1,14 +1,14 @@ {{template "head" .}} - + - + - + @@ -19,44 +19,44 @@ @@ -495,7 +495,7 @@ diff --git a/web/html/xui/xray_rule_modal.html b/web/html/xui/xray_rule_modal.html index c2d84dc540..58ee5aa68a 100644 --- a/web/html/xui/xray_rule_modal.html +++ b/web/html/xui/xray_rule_modal.html @@ -1,246 +1,261 @@ {{define "ruleModal"}} - - - - - [[ dm ]] - - - - - - - - - - - - - [[ x ]] - - - - - [[ x ]] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - [[ tag ]] - - - - - [[ tag ]] - - - - - - [[ tag ]] - - - + + + + + [[ dm ]] + + + + + + + + + + + + + [[ x ]] + + + + + [[ x ]] + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + [[ tag ]] + + + + + [[ tag ]] + + + + + + [[ tag ]] + + + + {{end}} diff --git a/web/service/inbound.go b/web/service/inbound.go index 5213fce6fd..9017342bd3 100644 --- a/web/service/inbound.go +++ b/web/service/inbound.go @@ -636,7 +636,7 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId strin oldEmail := "" newClientId := "" - clientIndex := -1 + clientIndex := 0 for index, oldClient := range oldClients { oldClientId := "" if oldInbound.Protocol == "trojan" { @@ -657,7 +657,7 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId strin } // Validate new client ID - if newClientId == "" || clientIndex == -1 { + if newClientId == "" { return false, common.NewError("empty client ID") } diff --git a/web/service/setting.go b/web/service/setting.go index 90bf8fc444..aafdc01647 100644 --- a/web/service/setting.go +++ b/web/service/setting.go @@ -37,7 +37,7 @@ var defaultValueMap = map[string]string{ "expireDiff": "0", "trafficDiff": "0", "remarkModel": "-ieo", - "timeLocation": "Asia/Tehran", + "timeLocation": "Asia/Shanghai", "tgBotEnable": "false", "tgBotToken": "", "tgBotProxy": "", @@ -45,8 +45,8 @@ var defaultValueMap = map[string]string{ "tgRunTime": "@daily", "tgBotBackup": "false", "tgBotLoginNotify": "true", - "tgCpu": "80", - "tgLang": "en-US", + "tgCpu": "30", + "tgLang": "zh-Hans", "secretEnable": "false", "subEnable": "false", "subListen": "", diff --git a/web/service/tgbot.go b/web/service/tgbot.go index b829462059..dffe65a469 100644 --- a/web/service/tgbot.go +++ b/web/service/tgbot.go @@ -857,20 +857,20 @@ func checkAdmin(tgId int64) bool { func (t *Tgbot) SendAnswer(chatId int64, msg string, isAdmin bool) { numericKeyboard := tu.InlineKeyboard( tu.InlineKeyboardRow( + tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.allClients")).WithCallbackData(t.encodeQuery("get_inbounds")), tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.serverUsage")).WithCallbackData(t.encodeQuery("get_usage")), ), tu.InlineKeyboardRow( - tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.dbBackup")).WithCallbackData(t.encodeQuery("get_backup")), - tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.getBanLogs")).WithCallbackData(t.encodeQuery("get_banlogs")), + tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.commands")).WithCallbackData(t.encodeQuery("commands")), + tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.onlines")).WithCallbackData(t.encodeQuery("onlines")), ), tu.InlineKeyboardRow( - tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.getInbounds")).WithCallbackData(t.encodeQuery("inbounds")), tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.depleteSoon")).WithCallbackData(t.encodeQuery("deplete_soon")), + tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.getInbounds")).WithCallbackData(t.encodeQuery("inbounds")), ), tu.InlineKeyboardRow( - tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.commands")).WithCallbackData(t.encodeQuery("commands")), - tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.onlines")).WithCallbackData(t.encodeQuery("onlines")), - tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.allClients")).WithCallbackData(t.encodeQuery("get_inbounds")), + tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.getBanLogs")).WithCallbackData(t.encodeQuery("get_banlogs")), + tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.dbBackup")).WithCallbackData(t.encodeQuery("get_backup")), ), ) numericKeyboardClient := tu.InlineKeyboard( diff --git a/web/translation/translate.en_US.toml b/web/translation/translate.en_US.toml index de8a11a559..7b97e3d448 100644 --- a/web/translation/translate.en_US.toml +++ b/web/translation/translate.en_US.toml @@ -36,7 +36,7 @@ "status" = "Status" "enabled" = "Enabled" "disabled" = "Disabled" -"depleted" = "Ended" +"depleted" = "Depleted" "depletingSoon" = "Depleting" "offline" = "Offline" "online" = "Online" @@ -68,6 +68,7 @@ "xray" = "Xray Configs" "logout" = "Log Out" "link" = "Manage" +"navigation" = "navigation" [pages.login] "hello" = "Hello" @@ -399,6 +400,9 @@ "maskAddress" = "Mask Address" "maskAddressDesc" = "IP address mask, when enabled, will automatically replace the IP address that appears in the log." +[pages.navigation] +"title" = "navigation" + [pages.xray.rules] "first" = "First" "last" = "Last" diff --git a/web/translation/translate.zh_CN.toml b/web/translation/translate.zh_CN.toml index 00b7684c12..42cd8a37dc 100644 --- a/web/translation/translate.zh_CN.toml +++ b/web/translation/translate.zh_CN.toml @@ -65,9 +65,10 @@ "dashboard" = "系统状态" "inbounds" = "入站列表" "settings" = "面板设置" -"xray" = "Xray 设置" +"xray" = "Xray设置" "logout" = "退出登录" "link" = "管理" +"navigation" = "实用导航" [pages.login] "hello" = "你好" @@ -171,7 +172,7 @@ "delDepletedClientsTitle" = "删除流量耗尽的客户端" "delDepletedClientsContent" = "确定要删除所有流量耗尽的客户端吗?" "email" = "电子邮件" -"emailDesc" = "电子邮件必须完全唯一" +"emailDesc" = "电子邮件必须确保唯一" "IPLimit" = "IP 限制" "IPLimitDesc" = "如果数量超过设置值,则禁用入站流量。(0 = 禁用)" "IPLimitlog" = "IP 日志" @@ -180,7 +181,7 @@ "setDefaultCert" = "从面板设置证书" "xtlsDesc" = "Xray 核心需要 1.7.5" "realityDesc" = "Xray 核心需要 1.8.0 及以上版本" -"telegramDesc" = "请提供Telegram聊天ID。(在机器人中使用'/id'命令)或(@userinfobot" +"telegramDesc" = "请提供Telegram聊天ID。(跟@userinfobot机器人对话获取)" "subscriptionDesc" = "要找到你的订阅 URL,请导航到“详细信息”。此外,你可以为多个客户端使用相同的名称。" "info" = "信息" "same" = "相同" @@ -235,7 +236,7 @@ "resetDefaultConfig" = "重置为默认配置" "panelSettings" = "常规" "securitySettings" = "安全设定" -"TGBotSettings" = "Telegram 机器人配置" +"TGBotSettings" = "Telegram机器人配置" "panelListeningIP" = "面板监听 IP" "panelListeningIPDesc" = "默认留空监听所有 IP" "panelListeningDomain" = "面板监听域名" @@ -243,10 +244,12 @@ "panelPort" = "面板监听端口" "panelPortDesc" = "重启面板生效" "publicKeyPath" = "面板证书公钥文件路径" -"publicKeyPathDesc" = "填写一个 '/' 开头的绝对路径" +"DefaultpublicKeyPath" = "/root/.acme.sh/域名_ecc/域名.cer" +"publicKeyPathDesc" = "填写一个 '/' 开头的绝对路径,〔acme方式〕请自行在填入时修改域名" "privateKeyPath" = "面板证书密钥文件路径" -"privateKeyPathDesc" = "填写一个 '/' 开头的绝对路径" -"panelUrlPath" = "面板 url 根路径" +"DefaultprivateKeyPath" = "/root/.acme.sh/域名_ecc/域名.key" +"privateKeyPathDesc" = "填写一个 '/' 开头的绝对路径,〔acme方式〕请自行在填入时修改域名" +"panelUrlPath" = "面板登录访问路径" "panelUrlPathDesc" = "必须以 '/' 开头,以 '/' 结尾" "pageSize" = "分页大小" "pageSizeDesc" = "定义入站表的页面大小。设置 0 表示禁用" @@ -262,7 +265,7 @@ "telegramBotEnable" = "启用 Telegram 机器人" "telegramBotEnableDesc" = "启用 Telegram 机器人功能" "telegramToken" = "Telegram 机器人令牌(token)" -"telegramTokenDesc" = "从 '@BotFather' 获取的 Telegram 机器人令牌" +"telegramTokenDesc" = "跟 '@BotFather' 对话获取的 Telegram 机器人令牌" "telegramProxy" = "SOCKS5 Proxy" "telegramProxyDesc" = "启用 SOCKS5 代理连接到 Telegram(根据指南调整设置)" "telegramChatId" = "管理员聊天 ID" @@ -399,13 +402,16 @@ "maskAddress" = "隐藏地址" "maskAddressDesc" = "IP 地址掩码,启用时会自动替换日志中出现的 IP 地址。" +[pages.navigation] +"title" = "实用导航" + [pages.xray.rules] "first" = "置顶" "last" = "置底" "up" = "向上" "down" = "向下" "source" = "来源" -"dest" = "目的地址" +"dest" = "目标地址" "inbound" = "入站" "outbound" = "出站" "balancer" = "负载均衡" @@ -413,6 +419,18 @@ "add" = "添加规则" "edit" = "编辑规则" "useComma" = "逗号分隔的项目" +"DomainMatcher" = "域匹配类型" +"SourceIPs" = "源IP" +"SourcePort" = "源端口" +"Network" = "网络类型" +"Protocol" = "传输协议" +"Attributes" = "属性" +"Domain" = "域地址" +"User" = "用户" +"Port" = "端口" +"InboundTag" = "入站 Tag" +"OutboundTag" = "出站 Tag" +"BalancerTag" = "负载均衡 Tag" [pages.xray.outbound] "addOutbound" = "添加出站" @@ -505,14 +523,14 @@ [tgbot.commands] "unknown" = "❗ 未知命令" -"pleaseChoose" = "👇 请选择:\r\n" +"pleaseChoose" = "👇请〔按照需求〕选择下方按钮:\r\n" "help" = "🤖 欢迎使用本机器人!它旨在为您提供来自服务器的特定数据,并允许您进行必要的修改。\r\n\r\n" "start" = "👋 你好,{{ .Firstname }}。\r\n" "welcome" = "🤖 欢迎来到 {{ .Hostname }} 管理机器人。\r\n" "status" = "✅ 机器人正常运行!" "usage" = "❗ 请输入要搜索的文本!" "getID" = "🆔 您的 ID 为:{{ .ID }}" -"helpAdminCommands" = "要搜索客户电子邮件:\r\n/usage [电子邮件]\r\n\r\n要搜索入站(带有客户统计数据):\r\n/inbound [备注]\r\n\r\nTelegram聊天ID:\r\n/id" +"helpAdminCommands" = "要搜索客户电子邮件:\r\n/usage [电子邮件]\r\n\r\n要搜索入站(带有客户统计数据):\r\n/inbound [备注]\r\n\r\n要获取Telegram聊天ID:\r\n/id" "helpClientCommands" = "要搜索统计数据,请使用以下命令:\r\n/usage [电子邮件]\r\n\r\nTelegram聊天ID:\r\n/id" [tgbot.messages] @@ -524,7 +542,7 @@ "report" = "🕰 定时报告:{{ .RunTime }}\r\n" "datetime" = "⏰ 日期时间:{{ .DateTime }}\r\n" "hostname" = "💻 主机名:{{ .Hostname }}\r\n" -"version" = "🚀 X-UI 版本:{{ .Version }}\r\n" +"version" = "🚀 3X-UI 版本:{{ .Version }}\r\n" "xrayVersion" = "📡 Xray 版本: {{ .XrayVersion }}\r\n" "ipv6" = "🌐 IPv6:{{ .IPv6 }}\r\n" "ipv4" = "🌐 IPv4:{{ .IPv4 }}\r\n" @@ -547,7 +565,7 @@ "active" = "💡 激活:{{ .Enable }}\r\n" "enabled" = "🚨 已启用:{{ .Enable }}\r\n" "online" = "🌐 连接状态:{{ .Status }}\r\n" -"email" = "📧 邮箱:{{ .Email }}\r\n" +"email" = "📧 邮箱(用户):{{ .Email }}\r\n" "upload" = "🔼 上传↑:{{ .Upload }}\r\n" "download" = "🔽 下载↓:{{ .Download }}\r\n" "total" = "📊 总计:{{ .UpDown }} / {{ .Total }}\r\n" @@ -572,12 +590,12 @@ "confirmRemoveTGUser" = "✅ 确认移除 Telegram 用户?" "confirmToggle" = "✅ 确认启用/禁用用户?" "dbBackup" = "获取数据库备份" -"serverUsage" = "服务器使用情况" +"serverUsage" = "服务器状态" "getInbounds" = "获取入站信息" "depleteSoon" = "即将耗尽" "clientUsage" = "获取使用情况" "onlines" = "在线客户端" -"commands" = "命令" +"commands" = "常用命令" "refresh" = "🔄 刷新" "clearIPs" = "❌ 清除 IP" "removeTGUser" = "❌ 移除 Telegram 用户" @@ -589,7 +607,7 @@ "ipLimit" = "🔢 IP 限制" "setTGUser" = "👤 设置 Telegram 用户" "toggle" = "🔘 启用/禁用" -"custom" = "🔢 风俗" +"custom" = "🔢 自定义输入" "confirmNumber" = "✅ 确认: {{ .Num }}" "confirmNumberAdd" = "✅ 确认添加:{{ .Num }}" "limitTraffic" = "🚧 流量限制" @@ -617,4 +635,4 @@ "disableSuccess" = "✅ {{ .Email }}:已成功禁用。" "askToAddUserId" = "未找到您的配置!\r\n请向管理员询问,在您的配置中使用您的 Telegram 用户 ChatID。\r\n\r\n您的用户 ChatID:{{ .TgUserID }}" "chooseClient" = "为入站 {{ .Inbound }} 选择一个客户" -"chooseInbound" = "选择一个入站" \ No newline at end of file +"chooseInbound" = "选择一个入站" diff --git a/web/translation/translate.zhcn_Hans.toml b/web/translation/translate.zhcn_Hans.toml new file mode 100644 index 0000000000..e50716329c --- /dev/null +++ b/web/translation/translate.zhcn_Hans.toml @@ -0,0 +1,664 @@ +"username" = "用户名" +"password" = "密码" +"login" = "登录" +"confirm" = "确定" +"cancel" = "取消" +"close" = "关闭" +"copy" = "复制" +"copied" = "已复制" +"download" = "下载" +"remark" = "备注" +"enable" = "启用" +"protocol" = "协议" +"search" = "搜索" +"filter" = "筛选" +"loading" = "加载中..." +"second" = "秒" +"minute" = "分钟" +"hour" = "小时" +"day" = "天" +"check" = "查看" +"indefinite" = "无限期" +"unlimited" = "无限制" +"none" = "无" +"qrCode" = "二维码" +"info" = "更多信息" +"edit" = "编辑" +"delete" = "删除" +"reset" = "重置" +"copySuccess" = "复制成功" +"sure" = "确定" +"encryption" = "加密" +"transmission" = "传输" +"host" = "主机" +"path" = "路径" +"camouflage" = "伪装" +"status" = "状态" +"enabled" = "开启" +"disabled" = "关闭" +"depleted" = "耗尽" +"depletingSoon" = "即将耗尽" +"offline" = "离线" +"online" = "在线" +"domainName" = "域名" +"monitor" = "监听" +"certificate" = "数字证书" +"fail" = "失败" +"success" = "成功" +"getVersion" = "获取版本" +"install" = "安装" +"clients" = "客户端" +"usage" = "使用情况" +"secretToken" = "安全密钥" +"remained" = "剩余" +"security" = "安全" +"secAlertTitle" = "安全警报" +"secAlertSsl" = "此连接不安全。在激活 TLS 进行数据保护之前,请勿输入敏感信息。" +"secAlertConf" = "某些设置易受攻击。建议加强安全协议以防止潜在漏洞。" +"secAlertSSL" = "面板缺少安全连接。请安装 TLS 证书以保护数据安全。" +"secAlertPanelPort" = "面板默认端口存在安全风险。请配置随机端口或特定端口。" +"secAlertPanelURI" = "面板默认 URI 路径不安全。请配置复杂的 URI 路径。" +"secAlertSubURI" = "订阅默认 URI 路径不安全。请配置复杂的 URI 路径。" +"secAlertSubJsonURI" = "订阅 JSON 默认 URI 路径不安全。请配置复杂的 URI 路径。" + +[menu] +"dashboard" = "系统状态" +"inbounds" = "入站列表" +"settings" = "面板设置" +"xray" = "Xray设置" +"logout" = "退出登录" +"link" = "管理" +"navigation" = "实用导航" + +[pages.login] +"hello" = "你好" +"title" = "欢迎" +"loginAgain" = "登录时效已过,请重新登录" + +[pages.login.toasts] +"invalidFormData" = "数据格式错误" +"emptyUsername" = "请输入用户名" +"emptyPassword" = "请输入密码" +"wrongUsernameOrPassword" = "用户名或密码错误" +"successLogin" = "登录" + +[pages.index] +"title" = "系统状态" +"memory" = "内存" +"hard" = "磁盘" +"xrayStatus" = "Xray" +"stopXray" = "停止" +"restartXray" = "重启" +"xraySwitch" = "版本" +"xraySwitchClick" = "选择你要切换到的版本" +"xraySwitchClickDesk" = "请谨慎选择,因为较旧版本可能与当前配置不兼容" +"operationHours" = "系统正常运行时间" +"systemLoad" = "系统负载" +"systemLoadDesc" = "过去 1、5 和 15 分钟的系统平均负载" +"connectionTcpCountDesc" = "系统中所有 TCP 连接数" +"connectionUdpCountDesc" = "系统中所有 UDP 连接数" +"connectionCount" = "连接数" +"upSpeed" = "总上传速度" +"downSpeed" = "总下载速度" +"totalSent" = "系统启动以来发送的总数据量" +"totalReceive" = "系统启动以来接收的总数据量" +"xraySwitchVersionDialog" = "切换 Xray 版本" +"xraySwitchVersionDialogDesc" = "是否切换 Xray 版本至" +"dontRefresh" = "安装中,请勿刷新此页面" +"logs" = "日志" +"config" = "配置" +"backup" = "备份和恢复" +"backupTitle" = "备份和恢复数据库" +"backupDescription" = "恢复数据库之前建议进行备份" +"exportDatabase" = "备份" +"importDatabase" = "恢复" + +[pages.inbounds] +"title" = "入站列表" +"totalDownUp" = "总上传 / 下载" +"totalUsage" = "总用量" +"inboundCount" = "入站数量" +"operate" = "菜单" +"enable" = "启用" +"remark" = "备注" +"protocol" = "协议" +"port" = "端口" +"traffic" = "流量" +"details" = "详细信息" +"transportConfig" = "传输配置" +"expireDate" = "到期时间" +"resetTraffic" = "重置流量" +"addInbound" = "添加入站" +"generalActions" = "通用操作" +"create" = "添加" +"update" = "修改" +"modifyInbound" = "修改入站" +"deleteInbound" = "删除入站" +"deleteInboundContent" = "确定要删除入站吗?" +"deleteClient" = "删除客户端" +"deleteClientContent" = "确定要删除客户端吗?" +"resetTrafficContent" = "确定要重置流量吗?" +"copyLink" = "复制链接" +"address" = "地址" +"network" = "网络" +"destinationPort" = "目标端口" +"targetAddress" = "目标地址" +"monitorDesc" = "留空表示监听所有 IP" +"meansNoLimit" = " = 无限制(单位:GB)" +"totalFlow" = "总流量" +"leaveBlankToNeverExpire" = "留空表示永不过期" +"noRecommendKeepDefault" = "建议保留默认值" +"certificatePath" = "文件路径" +"certificateContent" = "文件内容" +"publicKey" = "公钥" +"privatekey" = "私钥" +"clickOnQRcode" = "点击二维码复制" +"client" = "客户" +"export" = "导出链接" +"clone" = "克隆" +"cloneInbound" = "克隆" +"cloneInboundContent" = "此入站规则除端口(Port)、监听 IP(Listening IP)和客户端(Clients)以外的所有配置都将应用于克隆" +"cloneInboundOk" = "创建克隆" +"resetAllTraffic" = "重置所有入站流量" +"resetAllTrafficTitle" = "重置所有入站流量" +"resetAllTrafficContent" = "确定要重置所有入站流量吗?" +"resetInboundClientTraffics" = "重置客户端流量" +"resetInboundClientTrafficTitle" = "重置所有客户端流量" +"resetInboundClientTrafficContent" = "确定要重置此入站客户端的所有流量吗?" +"resetAllClientTraffics" = "重置所有客户端流量" +"resetAllClientTrafficTitle" = "重置所有客户端流量" +"resetAllClientTrafficContent" = "确定要重置所有客户端的所有流量吗?" +"delDepletedClients" = "删除流量耗尽的客户端" +"delDepletedClientsTitle" = "删除流量耗尽的客户端" +"delDepletedClientsContent" = "确定要删除所有流量耗尽的客户端吗?" +"email" = "电子邮件" +"emailDesc" = "电子邮件必须确保唯一" +"IPLimit" = "IP 限制" +"IPLimitDesc" = "如果数量超过设置值,则禁用入站流量。(0 = 禁用)" +"IPLimitlog" = "IP 日志" +"IPLimitlogDesc" = "IP 历史日志(要启用被禁用的入站流量,请清除日志)" +"IPLimitlogclear" = "清除日志" +"setDefaultCert" = "从面板设置证书" +"xtlsDesc" = "Xray 核心需要 1.7.5" +"realityDesc" = "Xray 核心需要 1.8.0 及以上版本" +"telegramDesc" = "请提供Telegram聊天ID。(跟@userinfobot机器人对话获取)" +"subscriptionDesc" = "要找到你的订阅 URL,请导航到“详细信息”。此外,你可以为多个客户端使用相同的名称。" +"info" = "信息" +"same" = "相同" +"inboundData" = "入站数据" +"exportInbound" = "导出入站规则" +"import"="导入" +"importInbound" = "导入入站规则" + +[pages.client] +"add" = "添加客户端" +"edit" = "编辑客户端" +"submitAdd" = "添加客户端" +"submitEdit" = "保存修改" +"clientCount" = "客户端数量" +"bulk" = "批量创建" +"method" = "方法" +"first" = "置顶" +"last" = "置底" +"prefix" = "前缀" +"postfix" = "后缀" +"delayedStart" = "首次使用后开始" +"expireDays" = "期间" +"days" = "天" +"renew" = "自动续订" +"renewDesc" = "到期后自动续订。(0 = 禁用)(单位: 天)" + +[pages.inbounds.toasts] +"obtain" = "获取" + +[pages.inbounds.stream.general] +"request" = "请求" +"response" = "响应" +"name" = "名称" +"value" = "值" + +[pages.inbounds.stream.tcp] +"version" = "版本" +"method" = "方法" +"path" = "路径" +"status" = "状态" +"statusDescription" = "状态说明" +"requestHeader" = "请求头" +"responseHeader" = "响应头" + +[pages.inbounds.stream.quic] +"encryption" = "加密" + +[pages.settings] +"title" = "面板设置" +"save" = "保存" +"infoDesc" = "此处的所有更改都需要保存并重启面板才能生效" +"restartPanel" = "重启面板" +"restartPanelDesc" = "确定要重启面板吗?若重启后无法访问面板,请前往服务器查看面板日志信息" +"actions" = "操作" +"resetDefaultConfig" = "重置为默认配置" +"panelSettings" = "常规" +"securitySettings" = "安全设定" +"TGBotSettings" = "Telegram机器人配置" +"panelListeningIP" = "面板监听 IP" +"panelListeningIPDesc" = "默认留空监听所有 IP" +"panelListeningDomain" = "面板监听域名" +"panelListeningDomainDesc" = "默认情况下留空以监视所有域名和 IP 地址" +"panelPort" = "面板监听端口" +"panelPortDesc" = "重启面板生效" +"publicKeyPath" = "面板证书公钥文件路径" +"DefaultpublicKeyPath" = "/root/.acme.sh/域名_ecc/域名.cer" +"publicKeyPathDesc" = "填写一个 '/' 开头的绝对路径,〔acme方式〕请自行在填入时修改域名" +"privateKeyPath" = "面板证书密钥文件路径" +"DefaultprivateKeyPath" = "/root/.acme.sh/域名_ecc/域名.key" +"privateKeyPathDesc" = "填写一个 '/' 开头的绝对路径,〔acme方式〕请自行在填入时修改域名" +"panelUrlPath" = "面板登录访问路径" +"panelUrlPathDesc" = "必须以 '/' 开头,以 '/' 结尾" +"pageSize" = "分页大小" +"pageSizeDesc" = "定义入站表的页面大小。设置 0 表示禁用" +"remarkModel" = "备注模型和分隔符" +"datepicker" = "日期选择器" +"datepickerPlaceholder" = "选择日期" +"datepickerDescription" = "选择器日历类型指定到期日期" +"sampleRemark" = "备注示例" +"oldUsername" = "原用户名" +"currentPassword" = "原密码" +"newUsername" = "新用户名" +"newPassword" = "新密码" +"telegramBotEnable" = "启用 Telegram 机器人" +"telegramBotEnableDesc" = "启用 Telegram 机器人功能" +"telegramToken" = "Telegram 机器人令牌(token)" +"telegramTokenDesc" = "跟 '@BotFather' 对话获取的 Telegram 机器人令牌" +"telegramProxy" = "SOCKS5 Proxy" +"telegramProxyDesc" = "启用 SOCKS5 代理连接到 Telegram(根据指南调整设置)" +"telegramChatId" = "管理员聊天 ID" +"telegramChatIdDesc" = "Telegram 管理员聊天 ID (多个以逗号分隔)(可通过 @userinfobot 获取,或在机器人中使用 '/id' 命令获取)" +"telegramNotifyTime" = "通知时间" +"telegramNotifyTimeDesc" = "设置周期性的 Telegram 机器人通知时间(使用 crontab 时间格式)" +"tgNotifyBackup" = "数据库备份" +"tgNotifyBackupDesc" = "发送带有报告的数据库备份文件" +"tgNotifyLogin" = "登录通知" +"tgNotifyLoginDesc" = "当有人试图登录你的面板时显示用户名、IP 地址和时间" +"sessionMaxAge" = "会话时长" +"sessionMaxAgeDesc" = "保持登录状态的时长(单位:分钟)" +"expireTimeDiff" = "到期通知阈值" +"expireTimeDiffDesc" = "达到此阈值时,将收到有关到期时间的通知(单位:天)" +"trafficDiff" = "流量耗尽阈值" +"trafficDiffDesc" = "达到此阈值时,将收到有关流量耗尽的通知(单位:GB)" +"tgNotifyCpu" = "CPU 负载通知阈值" +"tgNotifyCpuDesc" = "CPU 负载超过此阈值时,将收到通知(单位:%)" +"timeZone" = "时区" +"timeZoneDesc" = "定时任务将按照该时区的时间运行" +"subSettings" = "订阅设置" +"subEnable" = "启用订阅服务" +"subEnableDesc" = "启用订阅服务功能" +"subListen" = "监听 IP" +"subListenDesc" = "订阅服务监听的 IP 地址(留空表示监听所有 IP)" +"subPort" = "监听端口" +"subPortDesc" = "订阅服务监听的端口号(必须是未使用的端口)" +"subCertPath" = "公钥路径" +"subCertPathDesc" = "订阅服务使用的公钥文件路径(以 '/' 开头)" +"subKeyPath" = "私钥路径" +"subKeyPathDesc" = "订阅服务使用的私钥文件路径(以 '/' 开头)" +"subPath" = "URI 路径" +"subPathDesc" = "订阅服务使用的 URI 路径(以 '/' 开头,以 '/' 结尾)" +"subDomain" = "监听域名" +"subDomainDesc" = "订阅服务监听的域名(留空表示监听所有域名和 IP)" +"subUpdates" = "更新间隔" +"subUpdatesDesc" = "客户端应用中订阅 URL 的更新间隔(单位:小时)" +"subEncrypt" = "编码" +"subEncryptDesc" = "订阅服务返回的内容将采用 Base64 编码" +"subShowInfo" = "显示使用信息" +"subShowInfoDesc" = "客户端应用中将显示剩余流量和日期信息" +"subURI" = "反向代理 URI" +"subURIDesc" = "用于代理后面的订阅 URL 的 URI 路径" +"fragment" = "分片" +"fragmentDesc" = "启用 TLS hello 数据包分片" +"fragmentSett" = "设置" +"mux" = "多路复用器" +"noiseDesc" = "启用 Noise." +"noiseSett" = "Noise 设置" +"muxDesc" = "在已建立的数据流内传输多个独立的数据流" +"muxSett" = "复用器设置" +"direct" = "直接连接" +"directDesc" = "直接与特定国家的域或IP范围建立连接" +"directSett" = "直接连接选项" + +[pages.xray] +"title" = "Xray 配置" +"save" = "保存" +"restart" = "重新启动 Xray" +"basicTemplate" = "基础配置" +"advancedTemplate" = "高级配置" +"generalConfigs" = "常规配置" +"generalConfigsDesc" = "这些选项将决定常规配置" +"logConfigs" = "日志" +"logConfigsDesc" = "日志可能会影响服务器的性能,建议仅在需要时启用" +"blockConfigs" = "防护屏蔽" +"blockConfigsDesc" = "这些选项将阻止用户连接到特定协议和网站" +"blockCountryConfigs" = "屏蔽国家/地区" +"blockCountryConfigsDesc" = "这些选项将阻止用户连接到特定国家/地区" +"directCountryConfigs" = "直连国家/地区" +"directCountryConfigsDesc" = "直接连接可确保特定流量不会通过其他服务器路由" +"ipv4Configs" = "IPv4 路由" +"ipv4ConfigsDesc" = "此选项将仅通过 IPv4 路由到目标域" +"warpConfigs" = "WARP 路由" +"warpConfigsDesc" = "注意:在使用这些选项之前,请按照面板 GitHub 上的步骤在你的服务器上以 socks5 代理模式安装 WARP。WARP 将通过 Cloudflare 服务器将流量路由到网站。" +"Template" = "高级 Xray 配置模板" +"TemplateDesc" = "最终的 Xray 配置文件将基于此模板生成" +"FreedomStrategy" = "Freedom 协议策略" +"FreedomStrategyDesc" = "设置 Freedom 协议中网络的输出策略" +"RoutingStrategy" = "配置路由域策略" +"RoutingStrategyDesc" = "设置 DNS 解析的整体路由策略" +"Torrent" = "屏蔽 BitTorrent 协议" +"TorrentDesc" = "禁止使用 BitTorrent" +"PrivateIp" = "屏蔽私有 IP" +"PrivateIpDesc" = "阻止连接到私有 IP" +"Ads" = "屏蔽广告" +"AdsDesc" = "屏蔽广告网站" +"Family" = "家庭保护" +"FamilyDesc" = "屏蔽成人内容和恶意网站" +"Security" = "安全防护" +"SecurityDesc" = "屏蔽恶意软件、网络钓鱼和挖矿网站" +"Speedtest" = "屏蔽测速网站" +"SpeedtestDesc" = "阻止连接到测速网站" +"IRIp" = "屏蔽连接到伊朗 IP" +"IRIpDesc" = "阻止建立到伊朗 IP 范围的连接" +"IRDomain" = "屏蔽连接到伊朗域名" +"IRDomainDesc" = "阻止建立到伊朗域名的连接" +"ChinaIp" = "屏蔽连接到中国 IP" +"ChinaIpDesc" = "阻止建立到中国 IP 范围的连接" +"ChinaDomain" = "屏蔽连接到中国域名" +"ChinaDomainDesc" = "阻止建立到中国域名的连接" +"RussiaIp" = "屏蔽连接到俄罗斯 IP" +"RussiaIpDesc" = "阻止建立到俄罗斯 IP 范围的连接" +"RussiaDomain" = "屏蔽连接到俄罗斯域名" +"RussiaDomainDesc" = "阻止建立到俄罗斯域名的连接" +"VNIp" = "屏蔽连接到越南 IP" +"VNIpDesc" = "阻止建立到越南 IP 范围的连接" +"VNDomain" = "屏蔽连接到越南域名" +"VNDomainDesc" = "阻止建立到越南域名的连接" +"DirectIRIp" = "直连伊朗 IP" +"DirectIRIpDesc" = "直接建立到伊朗 IP 范围的连接" +"DirectIRDomain" = "直连伊朗域名" +"DirectIRDomainDesc" = "直接建立到伊朗域名的连接" +"DirectChinaIp" = "直连中国 IP" +"DirectChinaIpDesc" = "直接建立到中国 IP 范围的连接" +"DirectChinaDomain" = "直连中国域名" +"DirectChinaDomainDesc" = "直接建立到中国域名的连接" +"DirectRussiaIp" = "直连俄罗斯 IP" +"DirectRussiaIpDesc" = "直接建立到俄罗斯 IP 范围的连接" +"DirectRussiaDomain" = "直连俄罗斯域名" +"DirectRussiaDomainDesc" = "直接建立到俄罗斯域名的连接" +"DirectVNIp" = "直连越南 IP" +"DirectVNIpDesc" = "直接建立到越南 IP 范围的连接" +"DirectVNDomain" = "直连越南域名" +"DirectVNDomainDesc" = "直接建立到越南域名的连接" +"GoogleIPv4" = "Google" +"GoogleIPv4Desc" = "通过 IPv4 将流量路由到谷歌" +"NetflixIPv4" = "Netflix" +"NetflixIPv4Desc" = "通过 IPv4 将流量路由到 Netflix" +"GoogleWARP" = "Google" +"GoogleWARPDesc" = "通过 WARP 将流量路由到 Google" +"OpenAIWARP" = "OpenAI (ChatGPT)" +"OpenAIWARPDesc" = "通过 WARP 将流量路由到 OpenAI (ChatGPT)" +"NetflixWARP" = "Netflix" +"NetflixWARPDesc" = "通过 WARP 将流量路由到 Netflix" +"MetaWARP"="Meta" +"MetaWARPDesc" = "通过 WARP 将流量路由到 Meta(Instagram、Facebook、WhatsApp、Threads...)" +"AppleWARP" = "Apple" +"AppleWARPDesc" = "通过 WARP 将流量路由到 Apple" +"RedditWARP" = "Reddit" +"RedditWARPDesc" = "通过 WARP 将流量路由到 Reddit" +"SpotifyWARP" = "Spotify" +"SpotifyWARPDesc" = "通过 WARP 将流量路由到 Spotify" +"IRWARP" = "伊朗域名" +"IRWARPDesc" = "通过 WARP 将流量路由到伊朗域名" +"Inbounds" = "入站规则" +"InboundsDesc" = "接受来自特定客户端的流量" +"Outbounds" = "出站规则" +"Balancers" = "负载均衡" +"OutboundsDesc" = "设置出站流量传出方式" +"Routings" = "路由规则" +"RoutingsDesc" = "每条规则的优先级都很重要" +"completeTemplate" = "全部" +"logLevel" = "日志级别" +"logLevelDesc" = "错误日志的日志级别,用于指示需要记录的信息" +"accessLog" = "访问日志" +"accessLogDesc" = "访问日志的文件路径。特殊值 'none' 禁用访问日志" +"errorLog" = "错误日志" +"errorLogDesc" = "错误日志的文件路径。特殊值 'none' 禁用错误日志" + +[pages.navigation] +"title" = "实用导航" + +[pages.xray.rules] +"first" = "置顶" +"last" = "置底" +"up" = "向上" +"down" = "向下" +"source" = "来源" +"dest" = "目标地址" +"inbound" = "入站" +"outbound" = "出站" +"balancer" = "负载均衡" +"info" = "信息" +"add" = "添加规则" +"edit" = "编辑规则" +"useComma" = "逗号分隔的项目" +"DomainMatcher" = "域匹配类型" +"SourceIPs" = "源IP" +"SourcePort" = "源端口" +"Network" = "网络类型" +"Protocol" = "传输协议" +"Attributes" = "属性" +"Domain" = "域地址" +"User" = "用户" +"Port" = "端口" +"InboundTag" = "入站 Tag" +"OutboundTag" = "出站 Tag" +"BalancerTag" = "负载均衡 Tag" + +[pages.xray.outbound] +"addOutbound" = "添加出站" +"addReverse" = "添加反向" +"editOutbound" = "编辑出站" +"editReverse" = "编辑反向" +"tag" = "标签" +"tagDesc" = "唯一标签" +"address" = "地址" +"reverse" = "反向" +"domain" = "域名" +"type" = "类型" +"bridge" = "Bridge" +"portal" = "Portal" +"intercon" = "互连" +"settings" = "设置" +"accountInfo" = "帐户信息" +"outboundStatus" = "出站状态" +"sendThrough" = "发送通过" + +[pages.xray.balancer] +"addBalancer" = "添加负载均衡" +"editBalancer" = "编辑负载均衡" +"balancerStrategy" = "策略" +"balancerSelectors" = "选择器" +"tag" = "标签" +"tagDesc" = "唯一标签" +"balancerDesc" = "无法同时使用 balancerTag 和 outboundTag。如果同时使用,则只有 outboundTag 会生效。" + +[pages.xray.wireguard] +"secretKey" = "密钥" +"publicKey" = "公钥" +"allowedIPs" = "允许的 IP" +"endpoint" = "端点" +"psk" = "共享密钥" +"domainStrategy" = "域策略" + +[pages.xray.dns] +"enable" = "启用 DNS" +"enableDesc" = "启用内置 DNS 服务器" +"tag" = "DNS 入站标签" +"tagDesc" = "此标签将在路由规则中可用作入站标签" +"strategy" = "查询策略" +"strategyDesc" = "解析域名的总体策略" +"add" = "添加服务器" +"edit" = "编辑服务器" +"domains" = "域" + +[pages.xray.fakedns] +"add" = "添加假 DNS" +"edit" = "编辑假 DNS" +"ipPool" = "IP 池子网" +"poolSize" = "池大小" + +[pages.settings.security] +"admin" = "管理员" +"secret" = "安全令牌" +"loginSecurity" = "登录安全" +"loginSecurityDesc" = "添加额外的身份验证以提高安全性" +"secretToken" = "安全令牌" +"secretTokenDesc" = "请将此令牌存储在安全的地方。此令牌用于登录,丢失无法恢复。" + +[pages.settings.toasts] +"modifySettings" = "修改设置" +"getSettings" = "获取设置" +"modifyUser" = "修改管理员" +"originalUserPassIncorrect" = "原用户名或原密码错误" +"userPassMustBeNotEmpty" = "新用户名和新密码不能为空" + +[tgbot] +"keyboardClosed" = "❌ 自定义键盘已关闭!" +"noResult" = "❗ 没有结果!" +"noQuery" = "❌ 未找到查询!请重新使用命令!" +"wentWrong" = "❌ 出了点问题!" +"noIpRecord" = "❗ 没有 IP 记录!" +"noInbounds" = "❗ 没有找到入站连接!" +"unlimited" = "♾ 无限制" +"add" = "添加" +"month" = "月" +"months" = "月" +"day" = "天" +"days" = "天" +"hours" = "小时" +"unknown" = "未知" +"inbounds" = "入站连接" +"clients" = "客户端" +"offline" = "🔴 离线" +"online" = "🟢 在线" + +[tgbot.commands] +"unknown" = "❗ 未知命令" +"pleaseChoose" = "👇请〔按照需求〕选择下方按钮:\r\n" +"help" = "🤖 欢迎使用本机器人!它旨在为您提供来自服务器的特定数据,并允许您进行必要的修改。\r\n\r\n" +"start" = "👋 你好,{{ .Firstname }}。\r\n" +"welcome" = "🤖 欢迎来到 {{ .Hostname }} 管理机器人。\r\n" +"status" = "✅ 机器人正常运行!" +"usage" = "❗ 请输入要搜索的文本!" +"getID" = "🆔 您的 ID 为:{{ .ID }}" +"helpAdminCommands" = "要搜索客户电子邮件:\r\n/usage [电子邮件]\r\n\r\n要搜索入站(带有客户统计数据):\r\n/inbound [备注]\r\n\r\n要获取Telegram聊天ID:\r\n/id" +"helpClientCommands" = "要搜索统计数据,请使用以下命令:\r\n/usage [电子邮件]\r\n\r\nTelegram聊天ID:\r\n/id" + +[tgbot.messages] +"cpuThreshold" = "🔴 CPU 使用率为 {{ .Percent }}%,超过阈值 {{ .Threshold }}%" +"selectUserFailed" = "❌ 用户选择错误!" +"userSaved" = "✅ 电报用户已保存。" +"loginSuccess" = "✅ 成功登录到面板。\r\n" +"loginFailed" = "❗️ 面板登录失败。\r\n" +"report" = "🕰 定时报告:{{ .RunTime }}\r\n" +"datetime" = "⏰ 日期时间:{{ .DateTime }}\r\n" +"hostname" = "💻 主机名:{{ .Hostname }}\r\n" +"version" = "🚀 3X-UI 版本:{{ .Version }}\r\n" +"xrayVersion" = "📡 Xray 版本: {{ .XrayVersion }}\r\n" +"ipv6" = "🌐 IPv6:{{ .IPv6 }}\r\n" +"ipv4" = "🌐 IPv4:{{ .IPv4 }}\r\n" +"ip" = "🌐 IP:{{ .IP }}\r\n" +"ips" = "🔢 IP 地址:\r\n{{ .IPs }}\r\n" +"serverUpTime" = "⏳ 服务器运行时间:{{ .UpTime }} {{ .Unit }}\r\n" +"serverLoad" = "📈 服务器负载:{{ .Load1 }}, {{ .Load2 }}, {{ .Load3 }}\r\n" +"serverMemory" = "📋 服务器内存:{{ .Current }}/{{ .Total }}\r\n" +"tcpCount" = "🔹 TCP 连接数:{{ .Count }}\r\n" +"udpCount" = "🔸 UDP 连接数:{{ .Count }}\r\n" +"traffic" = "🚦 流量:{{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n" +"xrayStatus" = "ℹ️ Xray 状态:{{ .State }}\r\n" +"username" = "👤 用户名:{{ .Username }}\r\n" +"password" = "👤 密码: {{ .Password }}\r\n" +"time" = "⏰ 时间:{{ .Time }}\r\n" +"inbound" = "📍 入站:{{ .Remark }}\r\n" +"port" = "🔌 端口:{{ .Port }}\r\n" +"expire" = "📅 过期日期:{{ .Time }}\r\n" +"expireIn" = "📅 剩余时间:{{ .Time }}\r\n" +"active" = "💡 激活:{{ .Enable }}\r\n" +"enabled" = "🚨 已启用:{{ .Enable }}\r\n" +"online" = "🌐 连接状态:{{ .Status }}\r\n" +"email" = "📧 邮箱(用户):{{ .Email }}\r\n" +"upload" = "🔼 上传↑:{{ .Upload }}\r\n" +"download" = "🔽 下载↓:{{ .Download }}\r\n" +"total" = "📊 总计:{{ .UpDown }} / {{ .Total }}\r\n" +"TGUser" = "👤 电报用户:{{ .TelegramID }}\r\n" +"exhaustedMsg" = "🚨 耗尽的 {{ .Type }}:\r\n" +"exhaustedCount" = "🚨 耗尽的 {{ .Type }} 数量:\r\n" +"onlinesCount" = "🌐 在线客户:{{ .Count }}\r\n" +"disabled" = "🛑 禁用:{{ .Disabled }}\r\n" +"depleteSoon" = "🔜 即将耗尽:{{ .Deplete }}\r\n\r\n" +"backupTime" = "🗄 备份时间:{{ .Time }}\r\n" +"refreshedOn" = "\r\n📋🔄 刷新时间:{{ .Time }}\r\n\r\n" +"yes" = "✅ 是的" +"no" = "❌ 没有" + +[tgbot.buttons] +"closeKeyboard" = "❌ 关闭键盘" +"cancel" = "❌ 取消" +"cancelReset" = "❌ 取消重置" +"cancelIpLimit" = "❌ 取消 IP 限制" +"confirmResetTraffic" = "✅ 确认重置流量?" +"confirmClearIps" = "✅ 确认清除 IP?" +"confirmRemoveTGUser" = "✅ 确认移除 Telegram 用户?" +"confirmToggle" = "✅ 确认启用/禁用用户?" +"dbBackup" = "获取数据库备份" +"serverUsage" = "服务器状态" +"getInbounds" = "获取入站信息" +"depleteSoon" = "即将耗尽" +"clientUsage" = "获取使用情况" +"onlines" = "在线客户端" +"commands" = "常用命令" +"refresh" = "🔄 刷新" +"clearIPs" = "❌ 清除 IP" +"removeTGUser" = "❌ 移除 Telegram 用户" +"selectTGUser" = "👤 选择 Telegram 用户" +"selectOneTGUser" = "👤 选择一个 Telegram 用户:" +"resetTraffic" = "📈 重置流量" +"resetExpire" = "📅 更改到期日期" +"ipLog" = "🔢 IP 日志" +"ipLimit" = "🔢 IP 限制" +"setTGUser" = "👤 设置 Telegram 用户" +"toggle" = "🔘 启用/禁用" +"custom" = "🔢 自定义输入" +"confirmNumber" = "✅ 确认: {{ .Num }}" +"confirmNumberAdd" = "✅ 确认添加:{{ .Num }}" +"limitTraffic" = "🚧 流量限制" +"getBanLogs" = "禁止日志" +"allClients" = "所有客户" + +[tgbot.answers] +"successfulOperation" = "✅ 成功!" +"errorOperation" = "❗ 操作错误。" +"getInboundsFailed" = "❌ 获取入站信息失败。" +"getClientsFailed" = "❌ 获取客户失败。" +"canceled" = "❌ {{ .Email }}:操作已取消。" +"clientRefreshSuccess" = "✅ {{ .Email }}:客户端刷新成功。" +"IpRefreshSuccess" = "✅ {{ .Email }}:IP 刷新成功。" +"TGIdRefreshSuccess" = "✅ {{ .Email }}:客户端的 Telegram 用户刷新成功。" +"resetTrafficSuccess" = "✅ {{ .Email }}:流量已重置成功。" +"setTrafficLimitSuccess" = "✅ {{ .Email }}: 流量限制保存成功。" +"expireResetSuccess" = "✅ {{ .Email }}:过期天数已重置成功。" +"resetIpSuccess" = "✅ {{ .Email }}:成功保存 IP 限制数量为 {{ .Count }}。" +"clearIpSuccess" = "✅ {{ .Email }}:IP 已成功清除。" +"getIpLog" = "✅ {{ .Email }}:获取 IP 日志。" +"getUserInfo" = "✅ {{ .Email }}:获取 Telegram 用户信息。" +"removedTGUserSuccess" = "✅ {{ .Email }}:Telegram 用户已成功移除。" +"enableSuccess" = "✅ {{ .Email }}:已成功启用。" +"disableSuccess" = "✅ {{ .Email }}:已成功禁用。" +"askToAddUserId" = "未找到您的配置!\r\n请向管理员询问,在您的配置中使用您的 Telegram 用户 ChatID。\r\n\r\n您的用户 ChatID:{{ .TgUserID }}" +"chooseClient" = "为入站 {{ .Inbound }} 选择一个客户" +"chooseInbound" = "选择一个入站" diff --git a/x-ui.sh b/x-ui.sh index 3219453e7e..c0a8e29a7b 100644 --- a/x-ui.sh +++ b/x-ui.sh @@ -19,7 +19,7 @@ function LOGI() { } # check root -[[ $EUID -ne 0 ]] && LOGE "ERROR: You must be root to run this script! \n" && exit 1 +[[ $EUID -ne 0 ]] && echo -e "${red}致命错误: ${plain} 请使用 root 权限运行此脚本\n" && exit 1 # Check OS and set release variable if [[ -f /etc/os-release ]]; then @@ -29,36 +29,44 @@ elif [[ -f /usr/lib/os-release ]]; then source /usr/lib/os-release release=$ID else - echo "Failed to check the system OS, please contact the author!" >&2 + echo -e "${red}检查服务器操作系统失败,请联系作者!${plain}" >&2 exit 1 fi -echo "The OS release is: $release" +echo -e "——————————————————————" +echo -e "当前服务器的操作系统为:${red} $release${plain}" +echo "" +xui_version=$(/usr/local/x-ui/x-ui -v) +last_version=$(curl -Ls "https://api.github.com/repos/xeefei/3x-ui/releases/latest" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/') +echo -e "${green}当前代理面板的版本为: ${red}〔3X-UI优化版〕v${xui_version}${plain}" +echo "" +echo -e "${yellow}〔3X-UI优化版〕最新版为---------->>> ${last_version}${plain}" os_version="" os_version=$(grep "^VERSION_ID" /etc/os-release | cut -d '=' -f2 | tr -d '"' | tr -d '.') if [[ "${release}" == "arch" ]]; then - echo "Your OS is Arch Linux" + echo "您的操作系统是 ArchLinux" elif [[ "${release}" == "parch" ]]; then - echo "Your OS is Parch Linux" + echo "您的操作系统是 ParchLinux" elif [[ "${release}" == "manjaro" ]]; then - echo "Your OS is Manjaro" + echo "您的操作系统是 Manjaro" elif [[ "${release}" == "armbian" ]]; then - echo "Your OS is Armbian" + echo "您的操作系统是 Armbian" elif [[ "${release}" == "opensuse-tumbleweed" ]]; then - echo "Your OS is OpenSUSE Tumbleweed" + echo "您的操作系统是 OpenSUSE Tumbleweed" elif [[ "${release}" == "centos" ]]; then if [[ ${os_version} -lt 8 ]]; then - echo -e "${red} Please use CentOS 8 or higher ${plain}\n" && exit 1 + echo -e "${red} 请使用 CentOS 8 或更高版本 ${plain}\n" && exit 1 fi elif [[ "${release}" == "ubuntu" ]]; then if [[ ${os_version} -lt 2004 ]]; then - echo -e "${red} Please use Ubuntu 20 or higher version!${plain}\n" && exit 1 + echo -e "${red} 请使用 Ubuntu 20 或更高版本!${plain}\n" && exit 1 fi + elif [[ "${release}" == "fedora" ]]; then if [[ ${os_version} -lt 36 ]]; then - echo -e "${red} Please use Fedora 36 or higher version!${plain}\n" && exit 1 + echo -e "${red} 请使用 Fedora 36 或更高版本!${plain}\n" && exit 1 fi elif [[ "${release}" == "amzn" ]]; then if [[ ${os_version} != "2023" ]]; then @@ -66,23 +74,35 @@ elif [[ "${release}" == "amzn" ]]; then fi elif [[ "${release}" == "debian" ]]; then if [[ ${os_version} -lt 11 ]]; then - echo -e "${red} Please use Debian 11 or higher ${plain}\n" && exit 1 + echo -e "${red} 请使用 Debian 11 或更高版本 ${plain}\n" && exit 1 fi + elif [[ "${release}" == "almalinux" ]]; then if [[ ${os_version} -lt 80 ]]; then - echo -e "${red} Please use AlmaLinux 8.0 or higher ${plain}\n" && exit 1 + echo -e "${red} 请使用 AlmaLinux 8.0 或更高版本 ${plain}\n" && exit 1 fi + elif [[ "${release}" == "rocky" ]]; then if [[ ${os_version} -lt 8 ]]; then - echo -e "${red} Please use Rocky Linux 8 or higher ${plain}\n" && exit 1 + echo -e "${red} 请使用 RockyLinux 8 或更高版本 ${plain}\n" && exit 1 fi +elif [[ "${release}" == "arch" ]]; then + echo "您的操作系统是 ArchLinux" +elif [[ "${release}" == "manjaro" ]]; then + echo "您的操作系统是 Manjaro" +elif [[ "${release}" == "armbian" ]]; then + echo "您的操作系统是 Armbian" +elif [[ "${release}" == "alpine" ]]; then + echo "您的操作系统是 Alpine Linux" +elif [[ "${release}" == "opensuse-tumbleweed" ]]; then + echo "您的操作系统是 OpenSUSE Tumbleweed" elif [[ "${release}" == "oracle" ]]; then if [[ ${os_version} -lt 8 ]]; then - echo -e "${red} Please use Oracle Linux 8 or higher ${plain}\n" && exit 1 + echo -e "${red} 请使用 Oracle Linux 8 或更高版本 ${plain}\n" && exit 1 fi else - echo -e "${red}Your operating system is not supported by this script.${plain}\n" - echo "Please ensure you are using one of the following supported operating systems:" + echo -e "${red}此脚本不支持您的操作系统。${plain}\n" + echo "请确保您使用的是以下受支持的操作系统之一:" echo "- Ubuntu 20.04+" echo "- Debian 11+" echo "- CentOS 8+" @@ -91,6 +111,7 @@ else echo "- Parch Linux" echo "- Manjaro" echo "- Armbian" + echo "- Alpine Linux" echo "- AlmaLinux 8.0+" echo "- Rocky Linux 8+" echo "- Oracle Linux 8+" @@ -121,7 +142,7 @@ confirm() { } confirm_restart() { - confirm "Restart the panel, Attention: Restarting the panel will also restart xray" "y" + confirm "重启面板,注意:重启面板也会重启 Xray" "y" if [[ $? == 0 ]]; then restart else @@ -130,12 +151,12 @@ confirm_restart() { } before_show_menu() { - echo && echo -n -e "${yellow}Press enter to return to the main menu: ${plain}" && read temp + echo && echo -n -e "${yellow}按 Enter 键返回主菜单:${plain}" && read temp show_menu } install() { - bash <(curl -Ls https://raw.githubusercontent.com/MHSanaei/3x-ui/main/install.sh) + bash <(curl -Ls https://raw.githubusercontent.com/xeefei/3x-ui/main/install.sh) if [[ $? == 0 ]]; then if [[ $# == 0 ]]; then start @@ -146,24 +167,24 @@ install() { } update() { - confirm "This function will forcefully reinstall the latest version, and the data will not be lost. Do you want to continue?" "y" + confirm "$(echo -e "${green}该功能将强制安装最新版本,并且数据不会丢失。${red}你想继续吗?${plain}---->>请输入")" "y" if [[ $? != 0 ]]; then - LOGE "Cancelled" + LOGE "已取消" if [[ $# == 0 ]]; then before_show_menu fi return 0 fi - bash <(curl -Ls https://raw.githubusercontent.com/MHSanaei/3x-ui/main/install.sh) + bash <(curl -Ls https://raw.githubusercontent.com/xeefei/3x-ui/main/install.sh) if [[ $? == 0 ]]; then - LOGI "Update is complete, Panel has automatically restarted " + LOGI "更新完成,面板已自动重启" exit 0 fi } update_menu() { - echo -e "${yellow}Updating Menu${plain}" - confirm "This function will update the menu to the latest changes." "y" + echo -e "${yellow}更新菜单项${plain}" + confirm "此功能会将所有菜单项更新为最新显示状态" "y" if [[ $? != 0 ]]; then LOGE "Cancelled" if [[ $# == 0 ]]; then @@ -172,40 +193,34 @@ update_menu() { return 0 fi - wget --no-check-certificate -O /usr/bin/x-ui https://raw.githubusercontent.com/MHSanaei/3x-ui/main/x-ui.sh + wget --no-check-certificate -O /usr/bin/x-ui https://raw.githubusercontent.com/xeefei/3x-ui/main/x-ui.sh chmod +x /usr/local/x-ui/x-ui.sh chmod +x /usr/bin/x-ui if [[ $? == 0 ]]; then - echo -e "${green}Update successful. The panel has automatically restarted.${plain}" + echo -e "${green}更新成功,面板已自动重启${plain}" exit 0 else - echo -e "${red}Failed to update the menu.${plain}" + echo -e "${red}更新菜单项失败${plain}" return 1 fi } custom_version() { - echo "Enter the panel version (like 2.4.0):" - read tag_version + echo "输入面板版本 (例: 2.3.8):" + read panel_version - if [ -z "$tag_version" ]; then - echo "Panel version cannot be empty. Exiting." + if [ -z "$panel_version" ]; then + echo "面板版本不能为空。" exit 1 fi - min_version="2.3.5" - if [[ "$(printf '%s\n' "$tag_version" "$min_version" | sort -V | head -n1)" == "$tag_version" && "$tag_version" != "$min_version" ]]; then - echo "Please use a newer version (at least 2.3.5). Exiting." - exit 1 - fi - - download_link="https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh" + download_link="https://raw.githubusercontent.com/xeefei/3x-ui/master/install.sh" # Use the entered panel version in the download link install_command="bash <(curl -Ls $download_link) v$tag_version" - echo "Downloading and installing panel version $tag_version..." + echo "下载并安装面板版本 $panel_version..." eval $install_command } @@ -216,7 +231,7 @@ delete_script() { } uninstall() { - confirm "Are you sure you want to uninstall the panel? xray will also uninstalled!" "n" + confirm "您确定要卸载面板吗? Xray 也将被卸载!" "n" if [[ $? != 0 ]]; then if [[ $# == 0 ]]; then show_menu @@ -232,9 +247,9 @@ uninstall() { rm /usr/local/x-ui/ -rf echo "" - echo -e "Uninstalled Successfully.\n" - echo "If you need to install this panel again, you can use below command:" - echo -e "${green}bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh)${plain}" + echo -e "卸载成功\n" + echo "如果您需要再次安装此面板,可以使用以下命令:" + echo -e "${green}bash <(curl -Ls https://raw.githubusercontent.com/xeefei/3x-ui/master/install.sh)${plain}" echo "" # Trap the SIGTERM signal trap delete_script SIGTERM @@ -242,23 +257,23 @@ uninstall() { } reset_user() { - confirm "Are you sure to reset the username and password of the panel?" "n" + confirm "您确定重置面板的用户名和密码吗?" "n" if [[ $? != 0 ]]; then if [[ $# == 0 ]]; then show_menu fi return 0 fi - read -rp "Please set the login username [default is a random username]: " config_account + read -rp "请设置用户名 [默认为随机用户名]: " config_account [[ -z $config_account ]] && config_account=$(date +%s%N | md5sum | cut -c 1-8) - read -rp "Please set the login password [default is a random password]: " config_password + read -rp "请设置密码 [默认为随机密码]: " config_password [[ -z $config_password ]] && config_password=$(date +%s%N | md5sum | cut -c 1-8) /usr/local/x-ui/x-ui setting -username ${config_account} -password ${config_password} >/dev/null 2>&1 /usr/local/x-ui/x-ui setting -remove_secret >/dev/null 2>&1 - echo -e "Panel login username has been reset to: ${green} ${config_account} ${plain}" - echo -e "Panel login password has been reset to: ${green} ${config_password} ${plain}" - echo -e "${yellow} Panel login secret token disabled ${plain}" - echo -e "${green} Please use the new login username and password to access the X-UI panel. Also remember them! ${plain}" + echo -e "面板登录用户名已重置为:${green} ${config_account} ${plain}" + echo -e "面板登录密码已重置为:${green} ${config_password} ${plain}" + echo -e "${yellow} 面板 Secret Token 已禁用 ${plain}" + echo -e "${green} 请使用新的登录用户名和密码访问 3X-UI 面板。也请记住它们!${plain}" confirm_restart } @@ -269,12 +284,13 @@ gen_random_string() { } reset_webbasepath() { - echo -e "${yellow}Resetting Web Base Path${plain}" + echo -e "${yellow}修改访问路径${plain}" + + # Prompt user to set a new web base path + read -rp "请设置新的访问路径(若回车默认或输入y则为随机路径): " config_webBasePath - read -rp "Are you sure you want to reset the web base path? (y/n): " confirm - if [[ $confirm != "y" && $confirm != "Y" ]]; then - echo -e "${yellow}Operation canceled.${plain}" - return + if [[ $config_webBasePath == "y" ]]; then + config_webBasePath=$(gen_random_string 10) fi config_webBasePath=$(gen_random_string 10) @@ -284,12 +300,12 @@ reset_webbasepath() { systemctl restart x-ui # Display confirmation message - echo -e "Web base path has been reset to: ${green}${config_webBasePath}${plain}" - echo -e "${green}Please use the new web base path to access the panel.${plain}" + echo -e "面板访问路径已重置为: ${green}${config_webBasePath}${plain}" + echo -e "${green}请使用新的路径登录访问面板${plain}" } reset_config() { - confirm "Are you sure you want to reset all panel settings, Account data will not be lost, Username and password will not change" "n" + confirm "您确定要重置所有面板设置,帐户数据不会丢失,用户名和密码不会更改" "n" if [[ $? != 0 ]]; then if [[ $# == 0 ]]; then show_menu @@ -297,27 +313,27 @@ reset_config() { return 0 fi /usr/local/x-ui/x-ui setting -reset - echo -e "All panel settings have been reset to default, Please restart the panel now, and use the default ${green}2053${plain} Port to Access the web Panel" + echo -e "所有面板设置已重置为默认,请立即重新启动面板,并使用默认的${green}2053${plain}端口访问网页面板" confirm_restart } check_config() { info=$(/usr/local/x-ui/x-ui setting -show true) if [[ $? != 0 ]]; then - LOGE "get current settings error, please check logs" + LOGE "获取当前设置错误,请检查日志" show_menu fi - LOGI "${info}" + echo -e "${info}${plain}" } set_port() { - echo && echo -n -e "Enter port number[1-65535]: " && read port + echo && echo -n -e "输入端口号 [1-65535]: " && read port if [[ -z "${port}" ]]; then LOGD "Cancelled" before_show_menu else /usr/local/x-ui/x-ui setting -port ${port} - echo -e "The port is set, Please restart the panel now, and use the new port ${green}${port}${plain} to access web panel" + echo -e "端口已设置,请立即重启面板,并使用新端口 ${green}${port}${plain} 以访问面板" confirm_restart fi } @@ -326,15 +342,15 @@ start() { check_status if [[ $? == 0 ]]; then echo "" - LOGI "Panel is running, No need to start again, If you need to restart, please select restart" + LOGI "面板正在运行,无需再次启动,如需重新启动,请选择重新启动" else systemctl start x-ui sleep 2 check_status if [[ $? == 0 ]]; then - LOGI "x-ui Started Successfully" + LOGI "x-ui 已成功启动" else - LOGE "panel Failed to start, Probably because it takes longer than two seconds to start, Please check the log information later" + LOGE "面板启动失败,可能是启动时间超过两秒,请稍后查看日志信息" fi fi @@ -347,15 +363,15 @@ stop() { check_status if [[ $? == 1 ]]; then echo "" - LOGI "Panel stopped, No need to stop again!" + LOGI "面板已关闭,无需再次关闭!" else systemctl stop x-ui sleep 2 check_status if [[ $? == 1 ]]; then - LOGI "x-ui and xray stopped successfully" + LOGI "x-ui 和 Xray 已成功关闭" else - LOGE "Panel stop failed, Probably because the stop time exceeds two seconds, Please check the log information later" + LOGE "面板关闭失败,可能是停止时间超过两秒,请稍后查看日志信息" fi fi @@ -369,9 +385,9 @@ restart() { sleep 2 check_status if [[ $? == 0 ]]; then - LOGI "x-ui and xray Restarted successfully" + LOGI "x-ui and Xray 已成功重启" else - LOGE "Panel restart failed, Probably because it takes longer than two seconds to start, Please check the log information later" + LOGE "面板重启失败,可能是启动时间超过两秒,请稍后查看日志信息" fi if [[ $# == 0 ]]; then before_show_menu @@ -388,9 +404,9 @@ status() { enable() { systemctl enable x-ui if [[ $? == 0 ]]; then - LOGI "x-ui Set to boot automatically on startup successfully" + LOGI "x-ui 已成功设置开机启动" else - LOGE "x-ui Failed to set Autostart" + LOGE "x-ui 设置开机启动失败" fi if [[ $# == 0 ]]; then @@ -401,9 +417,9 @@ enable() { disable() { systemctl disable x-ui if [[ $? == 0 ]]; then - LOGI "x-ui Autostart Cancelled successfully" + LOGI "x-ui 已成功取消开机启动" else - LOGE "x-ui Failed to cancel autostart" + LOGE "x-ui 取消开机启动失败" fi if [[ $# == 0 ]]; then @@ -423,18 +439,18 @@ show_banlog() { if [[ -s "${iplimit_banned_log_path}" ]]; then cat ${iplimit_banned_log_path} else - echo -e "${red}Log file is empty.${plain}\n" + echo -e "${red}日志文件为空${plain}\n" fi else - echo -e "${red}Log file not found. Please Install Fail2ban and IP Limit first.${plain}\n" + echo -e "${red}未找到日志文件。 请先安装 Fail2ban 和 IP Limit${plain}\n" fi } bbr_menu() { - echo -e "${green}\t1.${plain} Enable BBR" - echo -e "${green}\t2.${plain} Disable BBR" - echo -e "${green}\t0.${plain} Back to Main Menu" - read -p "Choose an option: " choice + echo -e "${green}\t1.${plain} 启用 BBR" + echo -e "${green}\t2.${plain} 禁用 BBR" + echo -e "${green}\t0.${plain} 返回主菜单" + read -p "请输入选项: " choice case "$choice" in 0) show_menu @@ -445,14 +461,13 @@ bbr_menu() { 2) disable_bbr ;; - *) echo "Invalid choice" ;; + *) echo "无效选项" ;; esac } disable_bbr() { - if ! grep -q "net.core.default_qdisc=fq" /etc/sysctl.conf || ! grep -q "net.ipv4.tcp_congestion_control=bbr" /etc/sysctl.conf; then - echo -e "${yellow}BBR is not currently enabled.${plain}" + echo -e "${yellow}BBR 当前未启用${plain}" exit 0 fi @@ -465,15 +480,15 @@ disable_bbr() { # Verify that BBR is replaced with CUBIC if [[ $(sysctl net.ipv4.tcp_congestion_control | awk '{print $3}') == "cubic" ]]; then - echo -e "${green}BBR has been replaced with CUBIC successfully.${plain}" + echo -e "${green}BBR 已成功替换为 CUBIC${plain}" else - echo -e "${red}Failed to replace BBR with CUBIC. Please check your system configuration.${plain}" + echo -e "${red}用 CUBIC 替换 BBR 失败,请检查您的系统配置。${plain}" fi } enable_bbr() { if grep -q "net.core.default_qdisc=fq" /etc/sysctl.conf && grep -q "net.ipv4.tcp_congestion_control=bbr" /etc/sysctl.conf; then - echo -e "${green}BBR is already enabled!${plain}" + echo -e "${green}BBR 已经启用!${plain}" exit 0 fi @@ -488,11 +503,11 @@ enable_bbr() { fedora | amzn) dnf -y update && dnf -y install ca-certificates ;; - arch | manjaro | parch) + arch | manjaro) pacman -Sy --noconfirm ca-certificates ;; *) - echo -e "${red}Unsupported operating system. Please check the script and install the necessary packages manually.${plain}\n" + echo -e "${red}不支持的操作系统。请检查脚本并手动安装必要的软件包${plain}\n" exit 1 ;; esac @@ -506,21 +521,21 @@ enable_bbr() { # Verify that BBR is enabled if [[ $(sysctl net.ipv4.tcp_congestion_control | awk '{print $3}') == "bbr" ]]; then - echo -e "${green}BBR has been enabled successfully.${plain}" + echo -e "${green}BBR 已成功启用${plain}" else - echo -e "${red}Failed to enable BBR. Please check your system configuration.${plain}" + echo -e "${red}启用 BBR 失败,请检查您的系统配置${plain}" fi } update_shell() { - wget -O /usr/bin/x-ui -N --no-check-certificate https://github.com/MHSanaei/3x-ui/raw/main/x-ui.sh + wget -O /usr/bin/x-ui -N --no-check-certificate https://github.com/xeefei/3x-ui/raw/main/x-ui.sh if [[ $? != 0 ]]; then echo "" - LOGE "Failed to download script, Please check whether the machine can connect Github" + LOGE "下载脚本失败,请检查机器是否可以连接至 GitHub" before_show_menu else chmod +x /usr/bin/x-ui - LOGI "Upgrade script succeeded, Please rerun the script" && exit 0 + LOGI "升级脚本成功,请重新运行脚本" && exit 0 fi } @@ -550,7 +565,7 @@ check_uninstall() { check_status if [[ $? != 2 ]]; then echo "" - LOGE "Panel installed, Please do not reinstall" + LOGE "面板已安装,请勿重新安装" if [[ $# == 0 ]]; then before_show_menu fi @@ -564,7 +579,7 @@ check_install() { check_status if [[ $? == 2 ]]; then echo "" - LOGE "Please install the panel first" + LOGE "请先安装面板" if [[ $# == 0 ]]; then before_show_menu fi @@ -578,15 +593,15 @@ show_status() { check_status case $? in 0) - echo -e "Panel state: ${green}Running${plain}" + echo -e "面板状态: ${green}运行中${plain}" show_enable_status ;; 1) - echo -e "Panel state: ${yellow}Not Running${plain}" + echo -e "面板状态: ${yellow}未运行${plain}" show_enable_status ;; 2) - echo -e "Panel state: ${red}Not Installed${plain}" + echo -e "面板状态: ${red}未安装${plain}" ;; esac show_xray_status @@ -595,9 +610,9 @@ show_status() { show_enable_status() { check_enabled if [[ $? == 0 ]]; then - echo -e "Start automatically: ${green}Yes${plain}" + echo -e "开机启动: ${green}是${plain}" else - echo -e "Start automatically: ${red}No${plain}" + echo -e "开机启动: ${red}否${plain}" fi } @@ -613,19 +628,19 @@ check_xray_status() { show_xray_status() { check_xray_status if [[ $? == 0 ]]; then - echo -e "xray state: ${green}Running${plain}" + echo -e "Xray状态: ${green}运行中${plain}" else - echo -e "xray state: ${red}Not Running${plain}" + echo -e "Xray状态: ${red}未运行${plain}" fi } firewall_menu() { - echo -e "${green}\t1.${plain} Install Firewall & open ports" - echo -e "${green}\t2.${plain} Allowed List" - echo -e "${green}\t3.${plain} Delete Ports from List" - echo -e "${green}\t4.${plain} Disable Firewall" - echo -e "${green}\t0.${plain} Back to Main Menu" - read -p "Choose an option: " choice + echo -e "${green}\t1.${plain} 安装防火墙并开放端口" + echo -e "${green}\t2.${plain} 允许列表" + echo -e "${green}\t3.${plain} 从列表中删除端口" + echo -e "${green}\t4.${plain} 禁用防火墙" + echo -e "${green}\t0.${plain} 返回主菜单" + read -p "请输入选项: " choice case "$choice" in 0) show_menu @@ -642,24 +657,23 @@ firewall_menu() { 4) sudo ufw disable ;; - *) echo "Invalid choice" ;; + *) echo "无效选项" ;; esac } open_ports() { if ! command -v ufw &>/dev/null; then - echo "ufw firewall is not installed. Installing now..." + echo "ufw 防火墙未安装,正在安装..." apt-get update apt-get install -y ufw else - echo "ufw firewall is already installed" + echo "ufw 防火墙已安装" fi # Check if the firewall is inactive if ufw status | grep -q "Status: active"; then - echo "Firewall is already active" + echo "防火墙已经激活" else - echo "Activating firewall..." # Open the necessary ports ufw allow ssh ufw allow http @@ -671,11 +685,11 @@ open_ports() { fi # Prompt the user to enter a list of ports - read -p "Enter the ports you want to open (e.g. 80,443,2053 or range 400-500): " ports + read -p "输入您要打开的端口(例如 80,443,2053 或端口范围 400-500): " ports # Check if the input is valid if ! [[ $ports =~ ^([0-9]+|[0-9]+-[0-9]+)(,([0-9]+|[0-9]+-[0-9]+))*$ ]]; then - echo "Error: Invalid input. Please enter a comma-separated list of ports or a range of ports (e.g. 80,443,2053 or 400-500)." >&2 + echo "错误:输入无效。请输入以英文逗号分隔的端口列表或端口范围(例如 80,443,2053 或 400-500)" >&2 exit 1 fi @@ -686,28 +700,26 @@ open_ports() { # Split the range into start and end ports start_port=$(echo $port | cut -d'-' -f1) end_port=$(echo $port | cut -d'-' -f2) - ufw allow $start_port:$end_port/tcp - ufw allow $start_port:$end_port/udp + # Loop through the range and open each port + for ((i = start_port; i <= end_port; i++)); do + ufw allow $i + done else ufw allow "$port" fi done # Confirm that the ports are open - echo "The following ports are now open:" - ufw status | grep "ALLOW" | grep -Eo "[0-9]+(/[a-z]+)?" - - echo "Firewall status:" - ufw status verbose + ufw status | grep $ports } delete_ports() { # Prompt the user to enter the ports they want to delete - read -p "Enter the ports you want to delete (e.g. 80,443,2053 or range 400-500): " ports + read -p "输入要删除的端口(例如 80,443,2053 或范围 400-500): " ports # Check if the input is valid if ! [[ $ports =~ ^([0-9]+|[0-9]+-[0-9]+)(,([0-9]+|[0-9]+-[0-9]+))*$ ]]; then - echo "Error: Invalid input. Please enter a comma-separated list of ports or a range of ports (e.g. 80,443,2053 or 400-500)." >&2 + echo "错误:输入无效。请输入以英文逗号分隔的端口列表或端口范围(例如 80,443,2053 或 400-500)" >&2 exit 1 fi @@ -718,37 +730,27 @@ delete_ports() { # Split the range into start and end ports start_port=$(echo $port | cut -d'-' -f1) end_port=$(echo $port | cut -d'-' -f2) - # Delete the port range - ufw delete allow $start_port:$end_port/tcp - ufw delete allow $start_port:$end_port/udp + # Loop through the range and delete each port + for ((i = start_port; i <= end_port; i++)); do + ufw delete allow $i + done else ufw delete allow "$port" fi done # Confirm that the ports are deleted - - echo "Deleted the specified ports:" - for port in "${PORT_LIST[@]}"; do - if [[ $port == *-* ]]; then - start_port=$(echo $port | cut -d'-' -f1) - end_port=$(echo $port | cut -d'-' -f2) - # Check if the port range has been successfully deleted - (ufw status | grep -q "$start_port:$end_port") || echo "$start_port-$end_port" - else - # Check if the individual port has been successfully deleted - (ufw status | grep -q "$port") || echo "$port" - fi - done + echo "删除指定端口:" + ufw status | grep $ports } update_geo() { local defaultBinFolder="/usr/local/x-ui/bin" - read -p "Please enter x-ui bin folder path. Leave blank for default. (Default: '${defaultBinFolder}')" binFolder + read -p "请输入 x-ui bin 文件夹路径,默认留空。(默认值:'${defaultBinFolder}')" binFolder binFolder=${binFolder:-${defaultBinFolder}} if [[ ! -d ${binFolder} ]]; then - LOGE "Folder ${binFolder} not exists!" - LOGI "making bin folder: ${binFolder}..." + LOGE "文件夹 ${binFolder} 不存在!" + LOGI "制作 bin 文件夹:${binFolder}..." mkdir -p ${binFolder} fi @@ -762,7 +764,7 @@ update_geo() { wget -O geoip_VN.dat https://github.com/vuong2023/vn-v2ray-rules/releases/latest/download/geoip.dat wget -O geosite_VN.dat https://github.com/vuong2023/vn-v2ray-rules/releases/latest/download/geosite.dat systemctl start x-ui - echo -e "${green}Geosite.dat + Geoip.dat + geoip_IR.dat + geosite_IR.dat have been updated successfully in bin folder '${binfolder}'!${plain}" + echo -e "${green}Geosite.dat + Geoip.dat + geoip_IR.dat + geosite_IR.dat 在 bin 文件夹: '${binfolder}' 中已经更新成功 !${plain}" before_show_menu } @@ -771,20 +773,20 @@ install_acme() { LOGI "install acme..." curl https://get.acme.sh | sh if [ $? -ne 0 ]; then - LOGE "install acme failed" + LOGE "安装 acme 失败" return 1 else - LOGI "install acme succeed" + LOGI "安装 acme 成功" fi return 0 } ssl_cert_issue_main() { - echo -e "${green}\t1.${plain} Get SSL" - echo -e "${green}\t2.${plain} Revoke" - echo -e "${green}\t3.${plain} Force Renew" - echo -e "${green}\t0.${plain} Back to Main Menu" - read -p "Choose an option: " choice + echo -e "${green}\t1.${plain} 获取 SSL 证书" + echo -e "${green}\t2.${plain} 吊销证书" + echo -e "${green}\t3.${plain} 续签证书" + echo -e "${green}\t0.${plain} 返回主菜单" + read -p "请输入选项: " choice case "$choice" in 0) show_menu @@ -794,26 +796,26 @@ ssl_cert_issue_main() { ;; 2) local domain="" - read -p "Please enter your domain name to revoke the certificate: " domain + read -p "请输入您的域名以吊销证书: " domain ~/.acme.sh/acme.sh --revoke -d ${domain} - LOGI "Certificate revoked" + LOGI "证书吊销成功" ;; 3) local domain="" - read -p "Please enter your domain name to forcefully renew an SSL certificate: " domain + read -p "请输入您的域名以续签 SSL 证书: " domain ~/.acme.sh/acme.sh --renew -d ${domain} --force ;; - *) echo "Invalid choice" ;; + *) echo "无效选项" ;; esac } ssl_cert_issue() { # check for acme.sh first if ! command -v ~/.acme.sh/acme.sh &>/dev/null; then - echo "acme.sh could not be found. we will install it" + echo "未找到 acme.sh, 正在安装" install_acme if [ $? -ne 0 ]; then - LOGE "install acme failed, please check logs" + LOGE "安装 acme 失败,请检查日志" exit 1 fi fi @@ -828,35 +830,37 @@ ssl_cert_issue() { fedora | amzn) dnf -y update && dnf -y install socat ;; - arch | manjaro | parch) + arch | manjaro) pacman -Sy --noconfirm socat ;; *) - echo -e "${red}Unsupported operating system. Please check the script and install the necessary packages manually.${plain}\n" + echo -e "${red}不支持的操作系统,请检查脚本并手动安装必要的软件包。${plain}\n" exit 1 ;; esac if [ $? -ne 0 ]; then - LOGE "install socat failed, please check logs" + LOGE "安装 socat 失败,请检查日志" exit 1 else - LOGI "install socat succeed..." + LOGI "安装 socat 成功..." fi # get the domain here,and we need verify it local domain="" - read -p "Please enter your domain name:" domain - LOGD "your domain is:${domain},check it..." + read -p "请输入您的域名:" domain + LOGD "您的域名是:${domain},正在检查..." # here we need to judge whether there exists cert already local currentCert=$(~/.acme.sh/acme.sh --list | tail -1 | awk '{print $1}') if [ ${currentCert} == ${domain} ]; then local certInfo=$(~/.acme.sh/acme.sh --list) - LOGE "system already has certs here,can not issue again,current certs details:" + LOGE "系统已经有证书,无法再次颁发,当前证书详细信息:" LOGI "$certInfo" + echo "" + echo -e "${green}如果要申请安装证书并每3个月〔自动续签〕证书,请确保${red} 80 ${green}和 ${red}443 ${green}端口已打开放行${plain}" exit 1 else - LOGI "your domain is ready for issuing cert now..." + LOGI "您的域现在已准备好颁发证书..." fi # create a directory for install cert @@ -870,21 +874,21 @@ ssl_cert_issue() { # get needed port here local WebPort=80 - read -p "please choose which port do you use,default will be 80 port:" WebPort + read -p "请选择您使用的端口,默认为 80 端口:" WebPort if [[ ${WebPort} -gt 65535 || ${WebPort} -lt 1 ]]; then - LOGE "your input ${WebPort} is invalid,will use default port" + LOGE "您输入的端口 ${WebPort} 无效,将使用默认端口" fi - LOGI "will use port:${WebPort} to issue certs,please make sure this port is open..." + LOGI "将使用端口:${WebPort} 来颁发证书,请确保该端口已开启..." # NOTE:This should be handled by user # open the port and kill the occupied progress ~/.acme.sh/acme.sh --set-default-ca --server letsencrypt ~/.acme.sh/acme.sh --issue -d ${domain} --listen-v6 --standalone --httpport ${WebPort} if [ $? -ne 0 ]; then - LOGE "issue certs failed,please check logs" + LOGE "颁发证书失败,请检查日志" rm -rf ~/.acme.sh/${domain} exit 1 else - LOGE "issue certs succeed,installing certs..." + LOGE "颁发证书成功,安装证书..." fi # install cert ~/.acme.sh/acme.sh --installcert -d ${domain} \ @@ -892,42 +896,46 @@ ssl_cert_issue() { --fullchain-file /root/cert/${domain}/fullchain.pem if [ $? -ne 0 ]; then - LOGE "install certs failed,exit" + LOGE "安装证书失败" rm -rf ~/.acme.sh/${domain} exit 1 else - LOGI "install certs succeed,enable auto renew..." + LOGI "安装证书成功,启用自动续订..." + echo "" + echo -e "${green}如果要申请安装证书并每3个月〔自动续签〕证书,请确保${red} 80 ${green}和 ${red}443 ${green}端口已打开放行${plain}" fi ~/.acme.sh/acme.sh --upgrade --auto-upgrade if [ $? -ne 0 ]; then - LOGE "auto renew failed, certs details:" + LOGE "自动续订失败,证书详细信息:" ls -lah cert/* chmod 755 $certPath/* exit 1 else - LOGI "auto renew succeed, certs details:" + LOGI "自动续订成功,证书详细信息:" ls -lah cert/* chmod 755 $certPath/* + echo "" + echo -e "${green}如果要申请安装证书并每3个月〔自动续签〕证书,请确保${red} 80 ${green}和 ${red}443 ${green}端口已打开放行${plain}" fi } ssl_cert_issue_CF() { echo -E "" - LOGD "******Instructions for use******" - LOGI "This Acme script requires the following data:" - LOGI "1.Cloudflare Registered e-mail" - LOGI "2.Cloudflare Global API Key" - LOGI "3.The domain name that has been resolved dns to the current server by Cloudflare" - LOGI "4.The script applies for a certificate. The default installation path is /root/cert " - confirm "Confirmed?[y/n]" "y" + LOGD "******使用说明******" + LOGI "此 Acme 脚本需要以下数据:" + LOGI "1. Cloudflare 注册邮箱" + LOGI "2. Cloudflare 全局 API 密钥" + LOGI "3. Cloudflare 已解析 dns 到当前服务器的域名" + LOGI "4. 脚本申请证书,默认安装路径为 /root/cert " + confirm "确认申请? [y/n]" "y" if [ $? -eq 0 ]; then # check for acme.sh first if ! command -v ~/.acme.sh/acme.sh &>/dev/null; then - echo "acme.sh could not be found. we will install it" + echo "未找到 acme.sh, 正在安装" install_acme if [ $? -ne 0 ]; then - LOGE "install acme failed, please check logs" + LOGE "安装 acme 失败,请检查日志" exit 1 fi fi @@ -941,54 +949,95 @@ ssl_cert_issue_CF() { rm -rf $certPath mkdir $certPath fi - LOGD "Please set a domain name:" - read -p "Input your domain here:" CF_Domain - LOGD "Your domain name is set to:${CF_Domain}" - LOGD "Please set the API key:" - read -p "Input your key here:" CF_GlobalKey - LOGD "Your API key is:${CF_GlobalKey}" - LOGD "Please set up registered email:" - read -p "Input your email here:" CF_AccountEmail - LOGD "Your registered email address is:${CF_AccountEmail}" + LOGD "请设置域名:" + read -p "在此输入您的域名:" CF_Domain + LOGD "您的域名为: ${CF_Domain}" + LOGD "请设置 CF Global API Key:" + read -p "在此输入您的 API Key:" CF_GlobalKey + LOGD "您的 API 密钥是: ${CF_GlobalKey}" + LOGD "请设置注册邮箱:" + read -p "在此输入您的邮箱:" CF_AccountEmail + LOGD "您的账号邮箱地址是: ${CF_AccountEmail}" ~/.acme.sh/acme.sh --set-default-ca --server letsencrypt if [ $? -ne 0 ]; then - LOGE "Default CA, Lets'Encrypt fail, script exiting..." + LOGE "默认 CA: Lets'Encrypt 失败,脚本退出..." exit 1 fi export CF_Key="${CF_GlobalKey}" export CF_Email=${CF_AccountEmail} ~/.acme.sh/acme.sh --issue --dns dns_cf -d ${CF_Domain} -d *.${CF_Domain} --log if [ $? -ne 0 ]; then - LOGE "Certificate issuance failed, script exiting..." + LOGE "证书颁发失败,脚本退出..." exit 1 else - LOGI "Certificate issued Successfully, Installing..." + LOGI "证书颁发成功,正在安装..." fi ~/.acme.sh/acme.sh --installcert -d ${CF_Domain} -d *.${CF_Domain} --ca-file /root/cert/ca.cer \ --cert-file /root/cert/${CF_Domain}.cer --key-file /root/cert/${CF_Domain}.key \ --fullchain-file /root/cert/fullchain.cer if [ $? -ne 0 ]; then - LOGE "Certificate installation failed, script exiting..." + LOGE "证书安装失败,脚本退出..." exit 1 else - LOGI "Certificate installed Successfully,Turning on automatic updates..." + LOGI "证书安装成功,开启自动更新..." + echo "" + echo -e "${green}如果要申请安装证书并每3个月〔自动续签〕证书,请确保${red} 80 ${green}和 ${red}443 ${green}端口已打开放行${plain}" fi ~/.acme.sh/acme.sh --upgrade --auto-upgrade if [ $? -ne 0 ]; then - LOGE "Auto update setup Failed, script exiting..." + LOGE "自动更新设置失败,脚本退出..." ls -lah cert chmod 755 $certPath exit 1 else - LOGI "The certificate is installed and auto-renewal is turned on, Specific information is as follows" + LOGI "证书已安装并开启自动续订,具体信息如下:" ls -lah cert chmod 755 $certPath + echo "" + echo -e "${green}如果要申请安装证书并每3个月〔自动续签〕证书,请确保${red} 80 ${green}和 ${red}443 ${green}端口已打开放行${plain}" fi else show_menu fi } +warp_cloudflare() { + echo -e "${green}\t1.${plain} 安装 WARP socks5 代理" + echo -e "${green}\t2.${plain} 账户类型 (free, plus, team)" + echo -e "${green}\t3.${plain} 开启 / 关闭 WireProxy" + echo -e "${green}\t4.${plain} 卸载 WARP" + echo -e "${green}\t0.${plain} 返回主菜单" + read -p "请输入选项: " choice + case "$choice" in + 0) + show_menu + ;; + 1) + bash <(curl -sSL https://raw.githubusercontent.com/hamid-gh98/x-ui-scripts/main/install_warp_proxy.sh) + ;; + 2) + warp a + ;; + 3) + warp y + ;; + 4) + warp u + ;; + *) echo "无效选项" ;; + esac +} + +subconverter() { + bash <(curl -fsSL https://get.docker.com | bash -s docker) + ipv4=$(curl -s4m8 ip.p3terx.com -k | sed -n 1p) + docker run -d --name sub --restart always -p 18080:80 -p 25500:25500 -v /PATH/sub/conf:/usr/share/nginx/html/conf stilleshan/sub + echo -e "${yellow}【链接转换模块】安装完成!!!" + echo -e "${green}【订阅转换功能】访问地址为:${plain}${green}http://$ipv4:18080" + echo -e "${green}【后端服务】拉取地址为:${plain}${green}http://$ipv4:25500" + show_menu +} + run_speedtest() { # Check if Speedtest is already installed if ! command -v speedtest &>/dev/null; then @@ -1011,7 +1060,7 @@ run_speedtest() { fi if [[ -z $pkg_manager ]]; then - echo "Error: Package manager not found. You may need to install Speedtest manually." + echo "错误:找不到包管理器。 您可能需要手动安装 Speedtest" return 1 else curl -s $speedtest_install_script | bash @@ -1078,7 +1127,7 @@ actionunban = -D f2b- -s -j [Init] EOF - echo -e "${green}Ip Limit jail files created with a bantime of ${bantime} minutes.${plain}" + echo -e "${green}使用 ${bantime} 分钟的禁止时间以创建的 IP Limit 限制文件。${plain}" } iplimit_remove_conflicts() { @@ -1091,27 +1140,27 @@ iplimit_remove_conflicts() { # Check for [3x-ipl] config in jail file then remove it if test -f "${file}" && grep -qw '3x-ipl' ${file}; then sed -i "/\[3x-ipl\]/,/^$/d" ${file} - echo -e "${yellow}Removing conflicts of [3x-ipl] in jail (${file})!${plain}\n" + echo -e "${yellow}消除系统环境中 [3x-ipl] 的冲突 (${file})!${plain}\n" fi done } iplimit_main() { - echo -e "\n${green}\t1.${plain} Install Fail2ban and configure IP Limit" - echo -e "${green}\t2.${plain} Change Ban Duration" - echo -e "${green}\t3.${plain} Unban Everyone" - echo -e "${green}\t4.${plain} Check Logs" - echo -e "${green}\t5.${plain} Fail2ban Status" - echo -e "${green}\t6.${plain} Restart Fail2ban" - echo -e "${green}\t7.${plain} Uninstall Fail2ban" - echo -e "${green}\t0.${plain} Back to Main Menu" - read -p "Choose an option: " choice + echo -e "\n${green}\t1.${plain} 安装 Fail2ban 并配置 IP 限制" + echo -e "${green}\t2.${plain} 更改禁止期限" + echo -e "${green}\t3.${plain} 解禁所有 IP" + echo -e "${green}\t4.${plain} 查看日志" + echo -e "${green}\t5.${plain} Fail2ban 状态" + echo -e "${green}\t6.${plain} 重启 Fail2ban" + echo -e "${green}\t7.${plain} 卸载 Fail2ban" + echo -e "${green}\t0.${plain} 返回主菜单" + read -p "请输入选项: " choice case "$choice" in 0) show_menu ;; 1) - confirm "Proceed with installation of Fail2ban & IP Limit?" "y" + confirm "继续安装 Fail2ban 和 IP 限制?" "y" if [[ $? == 0 ]]; then install_iplimit else @@ -1119,24 +1168,24 @@ iplimit_main() { fi ;; 2) - read -rp "Please enter new Ban Duration in Minutes [default 30]: " NUM + read -rp "请输入新的禁令持续时间(以分钟为单位)[默认 30]: " NUM if [[ $NUM =~ ^[0-9]+$ ]]; then create_iplimit_jails ${NUM} systemctl restart fail2ban else - echo -e "${red}${NUM} is not a number! Please, try again.${plain}" + echo -e "${red}${NUM} 不是一个数字! 请再试一次.${plain}" fi iplimit_main ;; 3) - confirm "Proceed with Unbanning everyone from IP Limit jail?" "y" + confirm "继续解除所有人的 IP 限制禁令?" "y" if [[ $? == 0 ]]; then fail2ban-client reload --restart --unban 3x-ipl truncate -s 0 "${iplimit_banned_log_path}" - echo -e "${green}All users Unbanned successfully.${plain}" + echo -e "${green}所有用户已成功解封${plain}" iplimit_main else - echo -e "${yellow}Cancelled.${plain}" + echo -e "${yellow}已取消${plain}" fi iplimit_main ;; @@ -1152,13 +1201,13 @@ iplimit_main() { 7) remove_iplimit ;; - *) echo "Invalid choice" ;; + *) echo "无效选项" ;; esac } install_iplimit() { if ! command -v fail2ban-client &>/dev/null; then - echo -e "${green}Fail2ban is not installed. Installing now...!${plain}\n" + echo -e "${green}未安装 Fail2ban。正在安装...!${plain}\n" # Check the OS and install necessary packages case "${release}" in @@ -1183,22 +1232,22 @@ install_iplimit() { pacman -Syu --noconfirm fail2ban ;; *) - echo -e "${red}Unsupported operating system. Please check the script and install the necessary packages manually.${plain}\n" + echo -e "${red}不支持的操作系统,请检查脚本并手动安装必要的软件包.${plain}\n" exit 1 ;; esac if ! command -v fail2ban-client &>/dev/null; then - echo -e "${red}Fail2ban installation failed.${plain}\n" + echo -e "${red}Fail2ban 安装失败${plain}\n" exit 1 fi - echo -e "${green}Fail2ban installed successfully!${plain}\n" + echo -e "${green}Fail2ban 安装成功!${plain}\n" else - echo -e "${yellow}Fail2ban is already installed.${plain}\n" + echo -e "${yellow}Fail2ban 已安装${plain}\n" fi - echo -e "${green}Configuring IP Limit...${plain}\n" + echo -e "${green}配置 IP 限制中...${plain}\n" # make sure there's no conflict for jail files iplimit_remove_conflicts @@ -1226,22 +1275,22 @@ install_iplimit() { fi systemctl enable fail2ban - echo -e "${green}IP Limit installed and configured successfully!${plain}\n" + echo -e "${green}IP 限制安装并配置成功!${plain}\n" before_show_menu } remove_iplimit() { - echo -e "${green}\t1.${plain} Only remove IP Limit configurations" - echo -e "${green}\t2.${plain} Uninstall Fail2ban and IP Limit" - echo -e "${green}\t0.${plain} Abort" - read -p "Choose an option: " num + echo -e "${green}\t1.${plain} 仅删除 IP 限制配置" + echo -e "${green}\t2.${plain} 卸载 Fail2ban 和 IP 限制" + echo -e "${green}\t0.${plain} 终止" + read -p "请输入选项: " num case "$num" in 1) rm -f /etc/fail2ban/filter.d/3x-ipl.conf rm -f /etc/fail2ban/action.d/3x-ipl.conf rm -f /etc/fail2ban/jail.d/3x-ipl.conf systemctl restart fail2ban - echo -e "${green}IP Limit removed successfully!${plain}\n" + echo -e "${green}IP 限制成功解除!${plain}\n" before_show_menu ;; 2) @@ -1261,86 +1310,101 @@ remove_iplimit() { dnf remove fail2ban -y dnf autoremove -y ;; - arch | manjaro | parch) + arch | manjaro) pacman -Rns --noconfirm fail2ban ;; *) - echo -e "${red}Unsupported operating system. Please uninstall Fail2ban manually.${plain}\n" + echo -e "${red}不支持的操作系统,请手动卸载 Fail2ban.${plain}\n" exit 1 ;; esac - echo -e "${green}Fail2ban and IP Limit removed successfully!${plain}\n" + echo -e "${green}Fail2ban 和 IP 限制已成功删除!${plain}\n" before_show_menu ;; 0) - echo -e "${yellow}Cancelled.${plain}\n" + echo -e "${yellow}已取消${plain}\n" iplimit_main ;; *) - echo -e "${red}Invalid option. Please select a valid number.${plain}\n" + echo -e "${red}无效选项。 请选择一个有效的选项。${plain}\n" remove_iplimit ;; esac } show_usage() { - echo "x-ui control menu usages: " - echo "------------------------------------------" - echo -e "SUBCOMMANDS:" - echo -e "x-ui - Admin Management Script" - echo -e "x-ui start - Start" - echo -e "x-ui stop - Stop" - echo -e "x-ui restart - Restart" - echo -e "x-ui status - Current Status" - echo -e "x-ui settings - Current Settings" - echo -e "x-ui enable - Enable Autostart on OS Startup" - echo -e "x-ui disable - Disable Autostart on OS Startup" - echo -e "x-ui log - Check logs" - echo -e "x-ui banlog - Check Fail2ban ban logs" - echo -e "x-ui update - Update" - echo -e "x-ui custom - custom version" - echo -e "x-ui install - Install" - echo -e "x-ui uninstall - Uninstall" - echo "------------------------------------------" + echo -e " ---------------------" + echo -e " |${green}3X-UI 控制菜单用法 ${plain}|${plain}" + echo -e " | ${yellow}一个更好的面板 ${plain}|${plain}" + echo -e " | ${yellow}基于Xray Core构建 ${plain}|${plain}" + echo -e "--------------------------------------------" + echo -e "x-ui - 进入管理脚本" + echo -e "x-ui start - 启动 3x-ui 面板" + echo -e "x-ui stop - 关闭 3x-ui 面板" + echo -e "x-ui restart - 重启 3x-ui 面板" + echo -e "x-ui status - 查看 3x-ui 状态" + echo -e "x-ui settings - 查看当前设置信息" + echo -e "x-ui enable - 启用 3x-ui 开机启动" + echo -e "x-ui disable - 禁用 3x-ui 开机启动" + echo -e "x-ui log - 查看 3x-ui 运行日志" + echo -e "x-ui banlog - 检查 Fail2ban 禁止日志" + echo -e "x-ui update - 更新 3x-ui 面板" + echo -e "x-ui custom - 自定义 3x-ui 版本" + echo -e "x-ui install - 安装 3x-ui 面板" + echo -e "x-ui uninstall - 卸载 3x-ui 面板" + echo -e "--------------------------------------------" } show_menu() { echo -e " - ${green}3X-UI Panel Management Script${plain} - ${green}0.${plain} Exit Script -———————————————— - ${green}1.${plain} Install - ${green}2.${plain} Update - ${green}3.${plain} Update Menu - ${green}4.${plain} Custom Version - ${green}5.${plain} Uninstall -———————————————— - ${green}6.${plain} Reset Username & Password & Secret Token - ${green}7.${plain} Reset Web Base Path - ${green}8.${plain} Reset Settings - ${green}9.${plain} Change Port - ${green}10.${plain} View Current Settings -———————————————— - ${green}11.${plain} Start - ${green}12.${plain} Stop - ${green}13.${plain} Restart - ${green}14.${plain} Check Status - ${green}15.${plain} Check Logs -———————————————— - ${green}16.${plain} Enable Autostart - ${green}17.${plain} Disable Autostart -———————————————— - ${green}18.${plain} SSL Certificate Management - ${green}19.${plain} Cloudflare SSL Certificate - ${green}20.${plain} IP Limit Management - ${green}21.${plain} Firewall Management -———————————————— - ${green}22.${plain} Enable BBR - ${green}23.${plain} Update Geo Files +—————————————————————— + ${green}3X-UI 面板管理脚本${plain} + ${yellow} 一个更好的面板${plain} + ${yellow} 基于Xray Core构建${plain} +—————————————————————— + ${green}0.${plain} 退出脚本 + ${green}1.${plain} 安装面板 + ${green}2.${plain} 更新面板 + ${green}3.${plain} 更新菜单项 + ${green}4.${plain} 自定义版本 + ${green}5.${plain} 卸载面板 +—————————————————————— + ${green}6.${plain} 重置用户名、密码和Secret Token + ${green}7.${plain} 修改访问路径 + ${green}8.${plain} 重置面板设置 + ${green}9.${plain} 修改面板端口 + ${green}10.${plain} 查看面板设置 +—————————————————————— + ${green}11.${plain} 启动面板 + ${green}12.${plain} 关闭面板 + ${green}13.${plain} 重启面板 + ${green}14.${plain} 检查面板状态 + ${green}15.${plain} 检查面板日志 +—————————————————————— + ${green}16.${plain} 启用开机启动 + ${green}17.${plain} 禁用开机启动 +—————————————————————— + ${green}18.${plain} SSL 证书管理 + ${green}19.${plain} CF SSL 证书 + ${green}20.${plain} IP 限制管理 + ${green}21.${plain} 防火墙管理 +—————————————————————— + ${green}22.${plain} 启用 BBR + ${green}23.${plain} 更新 Geo 文件 ${green}24.${plain} Speedtest by Ookla + ${green}25.${plain} 安装订阅转换 +—————————————————————— + ${green}若在使用过程中有任何问题${plain} + ${yellow}请加入〔3X-UI〕中文交流群${plain} + ${red}https://t.me/XUI_CN ${yellow}截图进行反馈${plain} + ${green}〔3X-UI〕优化版项目地址${plain} + ${yellow}https://github.com/xeefei/3x-ui${plain} + ${green}详细〔安装配置〕教程${plain} + ${yellow}https://xeefei.github.io/xufei/2024/05/3x-ui${plain} +—————————————————————— " show_status - echo && read -p "Please enter your selection [0-24]: " num + echo && read -p "请输入选项 [0-25]: " num case "${num}" in 0) @@ -1418,8 +1482,11 @@ show_menu() { 24) run_speedtest ;; + 25) + subconverter + ;; *) - LOGE "Please enter the correct number [0-24]" + LOGE "请输入正确的数字选项 [0-25]" ;; esac }