From 91af54abf10f8ee7352689e3bcd31ecf355b18e3 Mon Sep 17 00:00:00 2001 From: emirjorge <117597846+emirjorge@users.noreply.github.com> Date: Thu, 18 Jan 2024 01:59:43 -0500 Subject: [PATCH 01/26] Fix Spanish Translation (#1619) --- web/translation/translate.es_ES.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/web/translation/translate.es_ES.toml b/web/translation/translate.es_ES.toml index fb22b8a1..42a3a2b2 100644 --- a/web/translation/translate.es_ES.toml +++ b/web/translation/translate.es_ES.toml @@ -1,6 +1,6 @@ "username" = "Nombre de Usuario" "password" = "Contraseña" -"login" = "Acceso" +"login" = "Acceder" "confirm" = "Confirmar" "cancel" = "Cancelar" "close" = "Cerrar" @@ -59,10 +59,10 @@ "settings" = "Configuraciones" "xray" = "Configuración Xray" "logout" = "Cerrar Sesión" -"link" = "Otro" +"link" = "Gestionar" [pages.login] -"title" = "Grata" +"title" = "Bienvenido" "loginAgain" = "El límite de tiempo de inicio de sesión ha expirado. Por favor, inicia sesión nuevamente." [pages.login.toasts] From 589a8702aad5f882e3df261cc397a966ee09f674 Mon Sep 17 00:00:00 2001 From: Shahin <115543613+shahin-io@users.noreply.github.com> Date: Thu, 18 Jan 2024 07:00:38 +0000 Subject: [PATCH 02/26] Revert-back some edits (#1617) * Update index.html * Update translate.en_US.toml --- web/html/xui/index.html | 2 +- web/translation/translate.en_US.toml | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/web/html/xui/index.html b/web/html/xui/index.html index d3357a4d..3c01a29f 100644 --- a/web/html/xui/index.html +++ b/web/html/xui/index.html @@ -93,7 +93,7 @@ {{ i18n "pages.index.xrayStatus" }}: - [[ status.xray.state.toUpperCase() ]] + [[ status.xray.state ]] An error occurred while running Xray diff --git a/web/translation/translate.en_US.toml b/web/translation/translate.en_US.toml index b8fdcde1..2f1c0544 100644 --- a/web/translation/translate.en_US.toml +++ b/web/translation/translate.en_US.toml @@ -54,11 +54,11 @@ "security" = "Security" [menu] -"dashboard" = "OVERVIEW" -"inbounds" = "INBOUNDS" -"settings" = "PANEL SETTINGS" -"xray" = "XRAY CONFIGS" -"logout" = "LOG OUT" +"dashboard" = "Overview" +"inbounds" = "Inbounds" +"settings" = "Panel Settings" +"xray" = "Xray Configs" +"logout" = "Log Out" "link" = "Manage" [pages.login] @@ -319,7 +319,7 @@ "ipv4Configs" = "IPv4 Routing" "ipv4ConfigsDesc" = "These options will route traffic based on a specific destination via IPv4." "warpConfigs" = "WARP Routing" -"warpConfigsDesc" = "These options will route traffic based on a specific destination via WARP. (follow the guide on the Panel’s GitHub)" +"warpConfigsDesc" = "These options will route traffic based on a specific destination via WARP." "Template" = "Advanced Xray Configuration Template" "TemplateDesc" = "The final Xray config file will be generated based on this template." "FreedomStrategy" = "Freedom Protocol Strategy" From 7fd4015f174e4c529612dfeee534fb1f2842ba7d Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Fri, 19 Jan 2024 18:28:09 +0330 Subject: [PATCH 03/26] Update x-ui.sh Fail2ban installation failed --- x-ui.sh | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/x-ui.sh b/x-ui.sh index d245a247..2807bc4f 100644 --- a/x-ui.sh +++ b/x-ui.sh @@ -991,6 +991,12 @@ install_iplimit() { echo -e "${red}Unsupported operating system. Please check the script and install the necessary packages manually.${plain}\n" exit 1 ;; esac + + if ! command -v fail2ban-client &>/dev/null; then + echo -e "${red}Fail2ban installation failed.${plain}\n" + exit 1 + fi + echo -e "${green}Fail2ban installed successfully!${plain}\n" else echo -e "${yellow}Fail2ban is already installed.${plain}\n" From 74ba6d7825741fe7965c91e5ccd8ae6d0436fe59 Mon Sep 17 00:00:00 2001 From: quydang <81231812+quydang04@users.noreply.github.com> Date: Sat, 20 Jan 2024 16:32:35 +0700 Subject: [PATCH 04/26] Add support for ARMv5 and x86 (#1642) --- .github/workflows/docker.yml | 4 ++-- .github/workflows/release.yml | 31 ++++++++++++++++++++++++++-- DockerInit.sh | 10 ++++++++- install.sh | 8 +++++-- web/translation/translate.vi_VN.toml | 4 ++-- 5 files changed, 48 insertions(+), 9 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index b2dc0ca4..6afe8618 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -1,4 +1,4 @@ -name: Release 3X-UI dockerhub +name: Release 3X-UI for Docker on: push: tags: @@ -36,6 +36,6 @@ jobs: with: context: . push: ${{ github.event_name != 'pull_request' }} - platforms: linux/amd64, linux/arm64/v8, linux/arm/v7, linux/arm/v6 + platforms: linux/amd64, linux/arm64/v8, linux/arm/v7, linux/arm/v6, linux/386, linux/arm/v5 tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 91a7e093..62bc6e7c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -15,6 +15,8 @@ jobs: - arm64 - armv7 - armv6 + - 386 + - armv5 runs-on: ubuntu-20.04 steps: - name: Checkout repository @@ -30,8 +32,14 @@ jobs: sudo apt-get update if [ "${{ matrix.platform }}" == "arm64" ]; then sudo apt install gcc-aarch64-linux-gnu - elif [ "${{ matrix.platform }}" == "armv7" ] || [ "${{ matrix.platform }}" == "armv6" ]; then + elif [ "${{ matrix.platform }}" == "armv7" ]; then sudo apt install gcc-arm-linux-gnueabihf + elif [ "${{ matrix.platform }}" == "armv6" ]; then + sudo apt install gcc-arm-linux-gnueabihf + elif [ "${{ matrix.platform }}" == "386" ]; then + sudo apt install gcc-i686-linux-gnu + elif [ "${{ matrix.platform }}" == "armv5" ]; then + sudo apt install gcc-arm-linux-gnueabi fi - name: Build x-ui @@ -42,10 +50,21 @@ jobs: if [ "${{ matrix.platform }}" == "arm64" ]; then export GOARCH=arm64 export CC=aarch64-linux-gnu-gcc - elif [ "${{ matrix.platform }}" == "armv7" ] || [ "${{ matrix.platform }}" == "armv6" ]; then + elif [ "${{ matrix.platform }}" == "armv7" ]; then export GOARCH=arm export GOARM=7 export CC=arm-linux-gnueabihf-gcc + elif [ "${{ matrix.platform }}" == "armv6" ]; then + export GOARCH=arm + export GOARM=6 + export CC=arm-linux-gnueabihf-gcc + elif [ "${{ matrix.platform }}" == "386" ]; then + export GOARCH=386 + export CC=i686-linux-gnu-gcc + elif [ "${{ matrix.platform }}" == "armv5" ]; then + export GOARCH=arm + export GOARM=5 + export CC=arm-linux-gnueabi-gcc fi go build -o xui-release -v main.go @@ -75,6 +94,14 @@ jobs: wget ${Xray_URL}Xray-linux-arm32-v6.zip unzip Xray-linux-arm32-v6.zip rm -f Xray-linux-arm32-v6.zip + elif [ "${{ matrix.platform }}" == "386" ]; then + wget ${Xray_URL}Xray-linux-32.zip + unzip Xray-linux-32.zip + rm -f Xray-linux-32.zip + elif [ "${{ matrix.platform }}" == "armv5" ]; then + wget ${Xray_URL}Xray-linux-arm32-v5.zip + unzip Xray-linux-arm32-v5.zip + rm -f Xray-linux-arm32-v5.zip fi rm -f geoip.dat geosite.dat wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat diff --git a/DockerInit.sh b/DockerInit.sh index c62dc77e..bbfcb04e 100755 --- a/DockerInit.sh +++ b/DockerInit.sh @@ -5,6 +5,10 @@ case $1 in ARCH="64" FNAME="amd64" ;; + i386) + ARCH="32" + FNAME="i386" + ;; armv8 | arm64 | aarch64) ARCH="arm64-v8a" FNAME="arm64" @@ -17,6 +21,10 @@ case $1 in ARCH="arm32-v6" FNAME="armv6" ;; + armv5) + ARCH="arm32-v5" + FNAME="armv5" + ;; *) ARCH="64" FNAME="amd64" @@ -29,7 +37,7 @@ cd build/bin wget "https://github.com/XTLS/Xray-core/releases/download/v1.8.7/Xray-linux-${ARCH}.zip" unzip "Xray-linux-${ARCH}.zip" -rm -f "Xray-linux-${ARCH}.zip" geoip.dat geosite.dat geoip_IR.dat geosite_IR.dat geoip_VN.dat geosite_VN.dat +rm -f "Xray-linux-${ARCH}.zip" geoip.dat geosite.dat mv xray "xray-linux-${FNAME}" wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat diff --git a/install.sh b/install.sh index 8d4a0c0c..8fd42ab8 100644 --- a/install.sh +++ b/install.sh @@ -26,12 +26,15 @@ echo "The OS release is: $release" arch3xui() { 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 | arm) echo 'armv6' ;; + armv6* | armv6) echo 'armv6' ;; + armv5* | armv5) echo 'armv5' ;; *) echo -e "${green}Unsupported CPU architecture! ${plain}" && rm -f install.sh && exit 1 ;; esac } + echo "arch: $(arch3xui)" os_version="" @@ -163,7 +166,8 @@ install_x-ui() { chmod +x x-ui # Check the system's architecture and rename the file accordingly - if [[ $(arch3xui) == "armv6" || $(arch3xui) == "armv7" ]]; then + if [[ $(arch3xui) == "armv5" || $(arch3xui) == "armv6" || $(arch3xui) == "armv7" ]]; then + mv bin/xray-linux-$(arch3xui) bin/xray-linux-arm chmod +x bin/xray-linux-arm fi diff --git a/web/translation/translate.vi_VN.toml b/web/translation/translate.vi_VN.toml index d627254b..7491a70c 100644 --- a/web/translation/translate.vi_VN.toml +++ b/web/translation/translate.vi_VN.toml @@ -74,9 +74,9 @@ [pages.index] "title" = "Trạng thái hệ thống" -"memory" = "Bộ nhớ" +"memory" = "Ram" "hard" = "Dung lượng" -"xrayStatus" = "Trạng thái" +"xrayStatus" = "Trạng thái Xray" "stopXray" = "Dừng lại" "restartXray" = "Khởi động lại" "xraySwitch" = "Phiên bản" From 7f13adbd05b5a1fb5f71e99dcff41bb13814134f Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Sat, 20 Jan 2024 15:49:34 +0330 Subject: [PATCH 05/26] fail2ban - auto allowipv6 --- x-ui.sh | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/x-ui.sh b/x-ui.sh index 2807bc4f..de7e1f9f 100644 --- a/x-ui.sh +++ b/x-ui.sh @@ -862,8 +862,11 @@ run_speedtest() { } create_iplimit_jails() { - # Use default bantime if not passed => 5 minutes - local bantime="${1:-5}" + # Use default bantime if not passed => 30 minutes + local bantime="${1:-30}" + + # uncomment allowipv6 = auto in fail2ban.conf + sed -i 's/#allowipv6 = auto/allowipv6 = auto/g' /etc/fail2ban/fail2ban.conf cat << EOF > /etc/fail2ban/jail.d/3x-ipl.conf [3x-ipl] From 38378fe36fbcaa8ca500fbc1fe1e8ea873dc248f Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Sat, 20 Jan 2024 16:38:54 +0330 Subject: [PATCH 06/26] update fail2ban v1.0.2 for debian --- install.sh | 30 +++++++++++++++--------------- x-ui.sh | 19 ++++++++++++++----- 2 files changed, 29 insertions(+), 20 deletions(-) diff --git a/install.sh b/install.sh index 8fd42ab8..ae489c2a 100644 --- a/install.sh +++ b/install.sh @@ -81,19 +81,21 @@ fi install_base() { case "${release}" in - centos|fedora|almalinux|rocky) - yum -y update && yum install -y -q wget curl tar - ;; - arch|manjaro) - pacman -Syu && pacman -Syu --noconfirm wget curl tar - ;; - *) - apt-get update && apt install -y -q wget curl tar - ;; + centos | almalinux | rocky) + yum -y update && yum install -y -q wget curl tar + ;; + fedora) + dnf -y update && dnf install -y -q wget curl tar + ;; + arch | manjaro) + pacman -Syu && pacman -Syu --noconfirm wget curl tar + ;; + *) + apt-get update && apt install -y -q wget curl tar + ;; esac } - # This function will be called when user installed x-ui out of security config_after_install() { echo -e "${yellow}Install/update finished! For security it's recommended to modify panel settings ${plain}" @@ -167,11 +169,10 @@ install_x-ui() { # Check the system's architecture and rename the file accordingly if [[ $(arch3xui) == "armv5" || $(arch3xui) == "armv6" || $(arch3xui) == "armv7" ]]; then - - mv bin/xray-linux-$(arch3xui) bin/xray-linux-arm - chmod +x bin/xray-linux-arm + mv bin/xray-linux-$(arch3xui) bin/xray-linux-arm + chmod +x bin/xray-linux-arm fi - + chmod +x x-ui bin/xray-linux-$(arch3xui) 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 @@ -201,7 +202,6 @@ install_x-ui() { echo -e "----------------------------------------------" } - echo -e "${green}Running...${plain}" install_base install_x-ui $1 diff --git a/x-ui.sh b/x-ui.sh index de7e1f9f..af63b9b4 100644 --- a/x-ui.sh +++ b/x-ui.sh @@ -982,12 +982,16 @@ iplimit_main() { install_iplimit() { if ! command -v fail2ban-client &>/dev/null; then echo -e "${green}Fail2ban is not installed. Installing now...!${plain}\n" + # Check the OS and install necessary packages case "${release}" in ubuntu|debian) - apt update && apt install fail2ban -y ;; + wget -O fail2ban.deb https://github.com/fail2ban/fail2ban/releases/download/1.0.2/fail2ban_1.0.2-1.upstream1_all.deb + wget -O fail2ban.deb.asc https://github.com/fail2ban/fail2ban/releases/download/1.0.2/fail2ban_1.0.2-1.upstream1_all.deb.asc + dpkg -i fail2ban.deb ;; centos|almalinux|rocky) - yum -y update && yum -y install fail2ban ;; + yum update -y && yum install epel-release -y + yum -y install fail2ban ;; fedora) dnf -y update && dnf -y install fail2ban ;; *) @@ -1027,6 +1031,7 @@ install_iplimit() { # Launching fail2ban if ! systemctl is-active --quiet fail2ban; then systemctl start fail2ban + systemctl enable fail2ban else systemctl restart fail2ban fi @@ -1054,11 +1059,15 @@ remove_iplimit(){ systemctl stop fail2ban case "${release}" in ubuntu|debian) - apt-get purge fail2ban -y;; + apt-get remove -y fail2ban + apt-get purge -y fail2ban -y + apt-get autoremove -y;; centos|almalinux|rocky) - yum remove fail2ban -y;; + yum remove fail2ban -y + yum autoremove -y;; fedora) - dnf remove fail2ban -y;; + dnf remove fail2ban -y + dnf autoremove -y;; *) echo -e "${red}Unsupported operating system. Please uninstall Fail2ban manually.${plain}\n" exit 1 ;; From a7bea936c536c560183e4577996e9a56b4cfd08a Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Sat, 20 Jan 2024 17:28:44 +0330 Subject: [PATCH 07/26] Update x-ui.sh --- x-ui.sh | 366 ++++++++++++++++++++++++++++++-------------------------- 1 file changed, 195 insertions(+), 171 deletions(-) diff --git a/x-ui.sh b/x-ui.sh index af63b9b4..cabb546f 100644 --- a/x-ui.sh +++ b/x-ui.sh @@ -70,13 +70,11 @@ elif [[ "${release}" == "armbian" ]]; then echo "Your OS is Armbian" fi - # Declare Variables log_folder="${XUI_LOG_FOLDER:=/var/log}" iplimit_log_path="${log_folder}/3xipl.log" iplimit_banned_log_path="${log_folder}/3xipl-banned.log" - confirm() { if [[ $# > 1 ]]; then echo && read -p "$1 [Default $2]: " temp @@ -140,7 +138,7 @@ custom_version() { if [ -z "$panel_version" ]; then echo "Panel version cannot be empty. Exiting." - exit 1 + exit 1 fi download_link="https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh" @@ -329,15 +327,15 @@ show_log() { } show_banlog() { - if test -f "${iplimit_banned_log_path}"; then - if [[ -s "${iplimit_banned_log_path}" ]]; then - cat ${iplimit_banned_log_path} + if test -f "${iplimit_banned_log_path}"; then + if [[ -s "${iplimit_banned_log_path}" ]]; then + cat ${iplimit_banned_log_path} + else + echo -e "${red}Log file is empty.${plain}\n" + fi else - echo -e "${red}Log file is empty.${plain}\n" + echo -e "${red}Log file not found. Please Install Fail2ban and IP Limit first.${plain}\n" fi - else - echo -e "${red}Log file not found. Please Install Fail2ban and IP Limit first.${plain}\n" - fi } enable_bbr() { @@ -348,19 +346,19 @@ enable_bbr() { # Check the OS and install necessary packages case "${release}" in - ubuntu|debian) - apt-get update && apt-get install -yqq --no-install-recommends ca-certificates - ;; - centos|almalinux|rocky) - yum -y update && yum -y install ca-certificates - ;; - fedora) - dnf -y update && dnf -y install ca-certificates - ;; - *) - echo -e "${red}Unsupported operating system. Please check the script and install the necessary packages manually.${plain}\n" - exit 1 - ;; + ubuntu | debian) + apt-get update && apt-get install -yqq --no-install-recommends ca-certificates + ;; + centos | almalinux | rocky) + yum -y update && yum -y install ca-certificates + ;; + fedora) + dnf -y update && dnf -y install ca-certificates + ;; + *) + echo -e "${red}Unsupported operating system. Please check the script and install the necessary packages manually.${plain}\n" + exit 1 + ;; esac # Enable BBR @@ -581,21 +579,24 @@ ssl_cert_issue_main() { echo -e "${green}\t0.${plain} Back to Main Menu" read -p "Choose an option: " choice case "$choice" in - 0) - show_menu ;; - 1) - ssl_cert_issue ;; - 2) - local domain="" - read -p "Please enter your domain name to revoke the certificate: " domain - ~/.acme.sh/acme.sh --revoke -d ${domain} - LOGI "Certificate revoked" - ;; - 3) - local domain="" - read -p "Please enter your domain name to forcefully renew an SSL certificate: " domain - ~/.acme.sh/acme.sh --renew -d ${domain} --force ;; - *) echo "Invalid choice" ;; + 0) + show_menu + ;; + 1) + ssl_cert_issue + ;; + 2) + local domain="" + read -p "Please enter your domain name to revoke the certificate: " domain + ~/.acme.sh/acme.sh --revoke -d ${domain} + LOGI "Certificate revoked" + ;; + 3) + local domain="" + read -p "Please enter your domain name to forcefully renew an SSL certificate: " domain + ~/.acme.sh/acme.sh --renew -d ${domain} --force + ;; + *) echo "Invalid choice" ;; esac } @@ -611,15 +612,19 @@ ssl_cert_issue() { fi # install socat second case "${release}" in - ubuntu|debian|armbian) - apt update && apt install socat -y ;; - centos|almalinux|rocky) - yum -y update && yum -y install socat ;; - fedora) - dnf -y update && dnf -y install socat ;; - *) - echo -e "${red}Unsupported operating system. Please check the script and install the necessary packages manually.${plain}\n" - exit 1 ;; + ubuntu | debian | armbian) + apt update && apt install socat -y + ;; + centos | almalinux | rocky) + yum -y update && yum -y install socat + ;; + fedora) + dnf -y update && dnf -y install socat + ;; + *) + echo -e "${red}Unsupported operating system. Please check the script and install the necessary packages manually.${plain}\n" + exit 1 + ;; esac if [ $? -ne 0 ]; then LOGE "install socat failed, please check logs" @@ -750,8 +755,8 @@ ssl_cert_issue_CF() { LOGI "Certificate issued Successfully, Installing..." 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 + --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..." exit 1 @@ -782,21 +787,22 @@ warp_cloudflare() { echo -e "${green}\t0.${plain} Back to Main Menu" read -p "Choose an option: " 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 "Invalid choice" ;; + 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 "Invalid choice" ;; esac } @@ -809,45 +815,46 @@ multi_protocol() { echo -e "${green}\t0.${plain} Back to Main Menu" read -p "Choose an option: " choice case "$choice" in - 0) - show_menu ;; - 1) - bash <(curl -Ls https://raw.githubusercontent.com/M4mmad/3xui-multi-protocol/master/install.sh --ipv4) - ;; - 2) - bash <(curl -Ls https://raw.githubusercontent.com/M4mmad/3xui-multi-protocol/master/unistall.sh --ipv4) - ;; - 3) - systemctl start 3xui-multi-protocol - ;; - 4) - systemctl stop 3xui-multi-protocol - ;; - *) echo "Invalid choice" ;; + 0) + show_menu + ;; + 1) + bash <(curl -Ls https://raw.githubusercontent.com/M4mmad/3xui-multi-protocol/master/install.sh --ipv4) + ;; + 2) + bash <(curl -Ls https://raw.githubusercontent.com/M4mmad/3xui-multi-protocol/master/unistall.sh --ipv4) + ;; + 3) + systemctl start 3xui-multi-protocol + ;; + 4) + systemctl stop 3xui-multi-protocol + ;; + *) echo "Invalid choice" ;; esac } run_speedtest() { # Check if Speedtest is already installed - if ! command -v speedtest &> /dev/null; then + if ! command -v speedtest &>/dev/null; then # If not installed, install it local pkg_manager="" local speedtest_install_script="" - - if command -v dnf &> /dev/null; then + + if command -v dnf &>/dev/null; then pkg_manager="dnf" speedtest_install_script="https://packagecloud.io/install/repositories/ookla/speedtest-cli/script.rpm.sh" - elif command -v yum &> /dev/null; then + elif command -v yum &>/dev/null; then pkg_manager="yum" speedtest_install_script="https://packagecloud.io/install/repositories/ookla/speedtest-cli/script.rpm.sh" - elif command -v apt-get &> /dev/null; then + elif command -v apt-get &>/dev/null; then pkg_manager="apt-get" speedtest_install_script="https://packagecloud.io/install/repositories/ookla/speedtest-cli/script.deb.sh" - elif command -v apt &> /dev/null; then + elif command -v apt &>/dev/null; then pkg_manager="apt" speedtest_install_script="https://packagecloud.io/install/repositories/ookla/speedtest-cli/script.deb.sh" fi - + if [[ -z $pkg_manager ]]; then echo "Error: Package manager not found. You may need to install Speedtest manually." return 1 @@ -938,65 +945,74 @@ iplimit_main() { echo -e "${green}\t0.${plain} Back to Main Menu" read -p "Choose an option: " choice case "$choice" in - 0) - show_menu ;; - 1) - confirm "Proceed with installation of Fail2ban & IP Limit?" "y" - if [[ $? == 0 ]]; then - install_iplimit - else - iplimit_main - fi ;; - 2) - read -rp "Please enter new Ban Duration in Minutes [default 5]: " 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}" - fi - iplimit_main ;; - 3) - confirm "Proceed with Unbanning everyone from IP Limit jail?" "y" - if [[ $? == 0 ]]; then - fail2ban-client reload --restart --unban 3x-ipl - echo -e "${green}All users Unbanned successfully.${plain}" - iplimit_main - else - echo -e "${yellow}Cancelled.${plain}" - fi - iplimit_main ;; - 4) - show_banlog - ;; - 5) - service fail2ban status - ;; + 0) + show_menu + ;; + 1) + confirm "Proceed with installation of Fail2ban & IP Limit?" "y" + if [[ $? == 0 ]]; then + install_iplimit + else + iplimit_main + fi + ;; + 2) + read -rp "Please enter new Ban Duration in Minutes [default 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}" + fi + iplimit_main + ;; + 3) + confirm "Proceed with Unbanning everyone from IP Limit jail?" "y" + if [[ $? == 0 ]]; then + fail2ban-client reload --restart --unban 3x-ipl + echo -e "${green}All users Unbanned successfully.${plain}" + iplimit_main + else + echo -e "${yellow}Cancelled.${plain}" + fi + iplimit_main + ;; + 4) + show_banlog + ;; + 5) + service fail2ban status + ;; - 6) - remove_iplimit ;; - *) echo "Invalid choice" ;; + 6) + remove_iplimit + ;; + *) echo "Invalid choice" ;; esac } install_iplimit() { if ! command -v fail2ban-client &>/dev/null; then echo -e "${green}Fail2ban is not installed. Installing now...!${plain}\n" - + # Check the OS and install necessary packages case "${release}" in - ubuntu|debian) - wget -O fail2ban.deb https://github.com/fail2ban/fail2ban/releases/download/1.0.2/fail2ban_1.0.2-1.upstream1_all.deb - wget -O fail2ban.deb.asc https://github.com/fail2ban/fail2ban/releases/download/1.0.2/fail2ban_1.0.2-1.upstream1_all.deb.asc - dpkg -i fail2ban.deb ;; - centos|almalinux|rocky) - yum update -y && yum install epel-release -y - yum -y install fail2ban ;; - fedora) - dnf -y update && dnf -y install fail2ban ;; - *) - echo -e "${red}Unsupported operating system. Please check the script and install the necessary packages manually.${plain}\n" - exit 1 ;; + ubuntu | debian) + wget -O fail2ban.deb https://github.com/fail2ban/fail2ban/releases/download/1.0.2/fail2ban_1.0.2-1.upstream1_all.deb + wget -O fail2ban.deb.asc https://github.com/fail2ban/fail2ban/releases/download/1.0.2/fail2ban_1.0.2-1.upstream1_all.deb.asc + dpkg -i fail2ban.deb + ;; + centos | almalinux | rocky) + yum update -y && yum install epel-release -y + yum -y install fail2ban + ;; + fedora) + dnf -y update && dnf -y install fail2ban + ;; + *) + echo -e "${red}Unsupported operating system. Please check the script and install the necessary packages manually.${plain}\n" + exit 1 + ;; esac if ! command -v fail2ban-client &>/dev/null; then @@ -1041,45 +1057,53 @@ install_iplimit() { before_show_menu } -remove_iplimit(){ +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 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" - before_show_menu ;; - 2) - rm -rf /etc/fail2ban - systemctl stop fail2ban - case "${release}" in - ubuntu|debian) - apt-get remove -y fail2ban - apt-get purge -y fail2ban -y - apt-get autoremove -y;; - centos|almalinux|rocky) - yum remove fail2ban -y - yum autoremove -y;; - fedora) - dnf remove fail2ban -y - dnf autoremove -y;; - *) - echo -e "${red}Unsupported operating system. Please uninstall Fail2ban manually.${plain}\n" - exit 1 ;; - esac - echo -e "${green}Fail2ban and IP Limit removed successfully!${plain}\n" - before_show_menu ;; - 0) - echo -e "${yellow}Cancelled.${plain}\n" - iplimit_main ;; - *) - echo -e "${red}Invalid option. Please select a valid number.${plain}\n" - remove_iplimit ;; + 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" + before_show_menu + ;; + 2) + rm -rf /etc/fail2ban + systemctl stop fail2ban + case "${release}" in + ubuntu | debian) + apt-get remove -y fail2ban + apt-get purge -y fail2ban -y + apt-get autoremove -y + ;; + centos | almalinux | rocky) + yum remove fail2ban -y + yum autoremove -y + ;; + fedora) + dnf remove fail2ban -y + dnf autoremove -y + ;; + *) + echo -e "${red}Unsupported operating system. Please uninstall Fail2ban manually.${plain}\n" + exit 1 + ;; + esac + echo -e "${green}Fail2ban and IP Limit removed successfully!${plain}\n" + before_show_menu + ;; + 0) + echo -e "${yellow}Cancelled.${plain}\n" + iplimit_main + ;; + *) + echo -e "${red}Invalid option. Please select a valid number.${plain}\n" + remove_iplimit + ;; esac } @@ -1214,7 +1238,7 @@ show_menu() { ;; 24) run_speedtest - ;; + ;; *) LOGE "Please enter the correct number [0-24]" ;; From ebaabf61509a02090016acae886b2a7df216e814 Mon Sep 17 00:00:00 2001 From: quydang <81231812+quydang04@users.noreply.github.com> Date: Sat, 20 Jan 2024 23:41:58 +0700 Subject: [PATCH 08/26] Update README (#1648) --- README.md | 29 +++++++++++++++-------------- x-ui.sh | 4 ++-- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 60c6c044..6b0beab7 100644 --- a/README.md +++ b/README.md @@ -127,7 +127,22 @@ update to latest version - AlmaLinux 9+ - Rockylinux 9+ +## Compatible Architectures & Devices +Supports a variety of different architectures and devices. Here are some of the main architectures that we support: + +- **amd64**: This is the most common architecture for personal computers and servers. It supports most modern operating systems. + +- **x86 / i386**: This architecture is prevalent in desktop and laptop computers. It's widely supported by various operating systems and applications. (Ex: Most Windows, macOS, and Linux systems) + +- **armv8 / arm64 / aarch64**: This is the architecture for modern mobile and embedded devices, including smartphones and tablets. (Ex: Raspberry Pi 4, Raspberry Pi 3, Raspberry Pi Zero 2/Zero 2 W, Orange Pi 3 LTS,...) + +- **armv7 / arm / arm32**: This is the architecture for older mobile and embedded devices. It is still widely used in many devices. (Ex: Orange Pi Zero LTS, Orange Pi PC Plus, Raspberry Pi 2,...) + +- **armv6 / arm / arm32**: This is the architecture for very old embedded devices. While not as common as before, there are still some devices using this architecture. (Ex: Raspberry Pi 1, Raspberry Pi Zero/Zero W,...) + +- **armv5 / arm / arm32**: This is an older architecture primarily used in early embedded systems. While it's less common today, some legacy devices may still rely on this architecture. (Ex: Early versions of Raspberry Pi, some older smartphones) + ## Languages - English @@ -398,20 +413,6 @@ XUI_BIN_FOLDER="bin" XUI_DB_FOLDER="/etc/x-ui" go build main.go - -## Supported Architectures and Devices - -Supports a variety of different architectures and devices. Here are some of the main architectures that we support: - -- **amd64**: This is the most common architecture for personal computers and servers. It supports most modern operating systems. - -- **armv8 / arm64 / aarch64**: This is the architecture for modern mobile and embedded devices, including smartphones and tablets. (Ex: Raspberry Pi 4, Raspberry Pi 3, Raspberry Pi Zero 2/Zero 2 W, Orange Pi 3 LTS,...) - -- **armv7 / arm / arm32**: This is the architecture for older mobile and embedded devices. It is still widely used in many devices. (Ex: Orange Pi Zero LTS, Orange Pi PC Plus, Raspberry Pi 2,...) - -- **armv6 / arm / arm32**: This is the architecture for very old embedded devices. While not as common as before, there are still some devices using this architecture. (Ex: Raspberry Pi 1, Raspberry Pi Zero/Zero W,...) - - ## Preview ![1](./media/1.png) diff --git a/x-ui.sh b/x-ui.sh index cabb546f..9fa43960 100644 --- a/x-ui.sh +++ b/x-ui.sh @@ -872,7 +872,7 @@ create_iplimit_jails() { # Use default bantime if not passed => 30 minutes local bantime="${1:-30}" - # uncomment allowipv6 = auto in fail2ban.conf + # Uncomment 'allowipv6 = auto' in fail2ban.conf sed -i 's/#allowipv6 = auto/allowipv6 = auto/g' /etc/fail2ban/fail2ban.conf cat << EOF > /etc/fail2ban/jail.d/3x-ipl.conf @@ -917,7 +917,7 @@ actionunban = -D f2b- -s -j [Init] EOF - echo -e "${green}Created Ip Limit jail files with a bantime of ${bantime} minutes.${plain}" + echo -e "${green}Ip Limit jail files created with a bantime of ${bantime} minutes.${plain}" } iplimit_remove_conflicts() { From 820e302aac04312f8353b1bc839e39be70518441 Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Sun, 21 Jan 2024 04:00:58 +0330 Subject: [PATCH 09/26] revert fail2ban v1.0.2 it doesn't work well --- x-ui.sh | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/x-ui.sh b/x-ui.sh index 9fa43960..20336c18 100644 --- a/x-ui.sh +++ b/x-ui.sh @@ -998,9 +998,7 @@ install_iplimit() { # Check the OS and install necessary packages case "${release}" in ubuntu | debian) - wget -O fail2ban.deb https://github.com/fail2ban/fail2ban/releases/download/1.0.2/fail2ban_1.0.2-1.upstream1_all.deb - wget -O fail2ban.deb.asc https://github.com/fail2ban/fail2ban/releases/download/1.0.2/fail2ban_1.0.2-1.upstream1_all.deb.asc - dpkg -i fail2ban.deb + apt update && apt install fail2ban -y ;; centos | almalinux | rocky) yum update -y && yum install epel-release -y From 4b9bbbc34b3ed5563f9cb14420b768d241580cf3 Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Sun, 21 Jan 2024 04:45:17 +0330 Subject: [PATCH 10/26] clear banned log after unban everyone --- x-ui.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/x-ui.sh b/x-ui.sh index 20336c18..c7331e6d 100644 --- a/x-ui.sh +++ b/x-ui.sh @@ -970,6 +970,7 @@ iplimit_main() { confirm "Proceed with Unbanning everyone from IP Limit jail?" "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}" iplimit_main else From e7ce8c8ddb8472695b296eac305c5ac9b8c1d3d8 Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Sun, 21 Jan 2024 14:39:15 +0330 Subject: [PATCH 11/26] minor changes disAllowedIps doesn't show on debug if there is no ip change copy to clipboard to export inbound --- go.mod | 2 +- go.sum | 4 ++-- web/html/xui/inbounds.html | 2 +- web/job/check_client_ip_job.go | 23 ++++++++++++++--------- web/translation/translate.en_US.toml | 2 +- web/translation/translate.es_ES.toml | 2 +- web/translation/translate.fa_IR.toml | 2 +- web/translation/translate.ru_RU.toml | 2 +- web/translation/translate.vi_VN.toml | 2 +- web/translation/translate.zh_Hans.toml | 2 +- 10 files changed, 24 insertions(+), 19 deletions(-) diff --git a/go.mod b/go.mod index 6a116ba3..4e84c3e2 100644 --- a/go.mod +++ b/go.mod @@ -36,7 +36,7 @@ require ( github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/go-playground/validator/v10 v10.16.0 // indirect + github.com/go-playground/validator/v10 v10.17.0 // indirect github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/google/btree v1.1.2 // indirect diff --git a/go.sum b/go.sum index 0b593906..f5d3b46b 100644 --- a/go.sum +++ b/go.sum @@ -76,8 +76,8 @@ github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= -github.com/go-playground/validator/v10 v10.16.0 h1:x+plE831WK4vaKHO/jpgUGsvLKIqRRkz6M78GuJAfGE= -github.com/go-playground/validator/v10 v10.16.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= +github.com/go-playground/validator/v10 v10.17.0 h1:SmVVlfAOtlZncTxRuinDPomC2DkXJ4E5T9gDA0AIH74= +github.com/go-playground/validator/v10 v10.17.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= diff --git a/web/html/xui/inbounds.html b/web/html/xui/inbounds.html index 79326ca2..c986e3fd 100644 --- a/web/html/xui/inbounds.html +++ b/web/html/xui/inbounds.html @@ -230,7 +230,7 @@ - {{ i18n "pages.inbounds.copyToClipboard" }} + {{ i18n "pages.inbounds.exportInbound" }} {{ i18n "pages.inbounds.resetTraffic" }} diff --git a/web/job/check_client_ip_job.go b/web/job/check_client_ip_job.go index 65257170..b393e68d 100644 --- a/web/job/check_client_ip_job.go +++ b/web/job/check_client_ip_job.go @@ -16,17 +16,18 @@ import ( "x-ui/xray" ) -type CheckClientIpJob struct{} +type CheckClientIpJob struct { + disAllowedIps []string +} var job *CheckClientIpJob -var disAllowedIps []string var ipFiles = []string{ xray.GetIPLimitLogPath(), -xray.GetIPLimitPrevLogPath(), + xray.GetIPLimitPrevLogPath(), xray.GetIPLimitBannedLogPath(), -xray.GetIPLimitBannedPrevLogPath(), + xray.GetIPLimitBannedPrevLogPath(), xray.GetAccessPersistentLogPath(), -xray.GetAccessPersistentPrevLogPath(), + xray.GetAccessPersistentPrevLogPath(), } func NewCheckClientIpJob() *CheckClientIpJob { @@ -130,7 +131,6 @@ func (j *CheckClientIpJob) processLogFile() { } } - disAllowedIps = []string{} shouldCleanLog := false for clientEmail, ips := range InboundClientIps { @@ -237,6 +237,7 @@ func (j *CheckClientIpJob) updateInboundClientIps(inboundClientIps *model.Inboun json.Unmarshal([]byte(inbound.Settings), &settings) clients := settings["clients"] shouldCleanLog := false + j.disAllowedIps = []string{} // create iplimit log file channel logIpFile, err := os.OpenFile(xray.GetIPLimitLogPath(), os.O_CREATE|os.O_APPEND|os.O_RDWR, 0644) @@ -255,7 +256,7 @@ func (j *CheckClientIpJob) updateInboundClientIps(inboundClientIps *model.Inboun shouldCleanLog = true if limitIp < len(ips) && inbound.Enable { - disAllowedIps = append(disAllowedIps, ips[limitIp:]...) + j.disAllowedIps = append(j.disAllowedIps, ips[limitIp:]...) for i := limitIp; i < len(ips); i++ { log.Printf("[LIMIT_IP] Email = %s || SRC = %s", clientEmail, ips[i]) } @@ -263,8 +264,12 @@ func (j *CheckClientIpJob) updateInboundClientIps(inboundClientIps *model.Inboun } } } - logger.Debug("disAllowedIps ", disAllowedIps) - sort.Strings(disAllowedIps) + + sort.Strings(j.disAllowedIps) + + if len(j.disAllowedIps) > 0 { + logger.Debug("disAllowedIps ", j.disAllowedIps) + } db := database.GetDB() err = db.Save(inboundClientIps).Error diff --git a/web/translation/translate.en_US.toml b/web/translation/translate.en_US.toml index 2f1c0544..006378ab 100644 --- a/web/translation/translate.en_US.toml +++ b/web/translation/translate.en_US.toml @@ -178,7 +178,7 @@ "info" = "Info" "same" = "Same" "inboundData" = "Inbound's Data" -"copyToClipboard" = "Copy to Clipboard" +"exportInbound" = "Export Inbound" "import" = "Import" "importInbound" = "Import an Inbound" diff --git a/web/translation/translate.es_ES.toml b/web/translation/translate.es_ES.toml index 42a3a2b2..bbf50b6c 100644 --- a/web/translation/translate.es_ES.toml +++ b/web/translation/translate.es_ES.toml @@ -178,7 +178,7 @@ "info" = "Info" "same" = "misma" "inboundData" = "Datos de entrada" -"copyToClipboard" = "Copiar al portapapeles" +"exportInbound" = "Exportación entrante" "import" = "Importar" "importInbound" = "Importar un entrante" diff --git a/web/translation/translate.fa_IR.toml b/web/translation/translate.fa_IR.toml index 3edcb4cb..1de8538c 100644 --- a/web/translation/translate.fa_IR.toml +++ b/web/translation/translate.fa_IR.toml @@ -178,7 +178,7 @@ "info" = "اطلاعات" "same" = "همسان" "inboundData" = "داده‌های ورودی" -"copyToClipboard" = "کپی در حافظه" +"exportInbound" = "استخراج ورودی" "import" = "افزودن" "importInbound" = "افزودن یک ورودی" diff --git a/web/translation/translate.ru_RU.toml b/web/translation/translate.ru_RU.toml index d8211d8d..2f33311e 100644 --- a/web/translation/translate.ru_RU.toml +++ b/web/translation/translate.ru_RU.toml @@ -178,7 +178,7 @@ "info" = "Информация" "same" = "Тот же" "inboundData" = "Входящие данные" -"copyToClipboard" = "Копировать в буфер обмена" +"exportInbound" = "Экспорт входящих" "import" = "Импортировать" "importInbound" = "Импортировать входящее сообщение" diff --git a/web/translation/translate.vi_VN.toml b/web/translation/translate.vi_VN.toml index 7491a70c..1a2cfaa0 100644 --- a/web/translation/translate.vi_VN.toml +++ b/web/translation/translate.vi_VN.toml @@ -178,7 +178,7 @@ "info" = "Thông tin" "same" = "Giống nhau" "inboundData" = "Dữ liệu gửi đến" -"copyToClipboard" = "Sao chép vào bảng nhớ tạm" +"exportInbound" = "Xuất nhập khẩu" "import" = "Nhập" "importInbound" = "Nhập inbound" diff --git a/web/translation/translate.zh_Hans.toml b/web/translation/translate.zh_Hans.toml index 5a46ec73..966ef3f7 100644 --- a/web/translation/translate.zh_Hans.toml +++ b/web/translation/translate.zh_Hans.toml @@ -178,7 +178,7 @@ "info" = "信息" "same" = "相同" "inboundData" = "入站数据" -"copyToClipboard" = "复制到剪贴板" +"exportInbound" = "出口 入境" "import"="导入" "importInbound" = "导入入站" From 5c695ca6520c9cd9c44b18119a862f8f480969af Mon Sep 17 00:00:00 2001 From: Ali Rahimi Date: Sun, 21 Jan 2024 15:26:19 +0100 Subject: [PATCH 12/26] add group user with the same subscription id to all inbounds (#1650) --- web/assets/js/util/utils.js | 35 +++++++++++ web/controller/inbound.go | 39 +++++++------ web/html/common/qrcode_modal.html | 17 ++++-- web/html/xui/client_modal.html | 80 +++++++++++++++++++++----- web/html/xui/form/client.html | 4 +- web/html/xui/inbound_modal.html | 4 ++ web/html/xui/inbounds.html | 59 +++++++++++++++---- web/translation/translate.en_US.toml | 1 + web/translation/translate.es_ES.toml | 1 + web/translation/translate.fa_IR.toml | 1 + web/translation/translate.ru_RU.toml | 1 + web/translation/translate.vi_VN.toml | 1 + web/translation/translate.zh_Hans.toml | 1 + 13 files changed, 196 insertions(+), 48 deletions(-) diff --git a/web/assets/js/util/utils.js b/web/assets/js/util/utils.js index 61b322bd..48ff237d 100644 --- a/web/assets/js/util/utils.js +++ b/web/assets/js/util/utils.js @@ -83,6 +83,41 @@ class HttpUtil { } return msg; } + + static async jsonPost(url, data) { + let msg; + try { + const requestOptions = { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(data), + }; + const resp = await fetch(url, requestOptions); + const response = await resp.json(); + + msg = this._respToMsg({data : response}); + } catch (e) { + msg = new Msg(false, e.toString()); + } + this._handleMsg(msg); + return msg; + } + + static async postWithModalJson(url, data, modal) { + if (modal) { + modal.loading(true); + } + const msg = await this.jsonPost(url, data); + if (modal) { + modal.loading(false); + if (msg instanceof Msg && msg.success) { + modal.close(); + } + } + return msg; + } } class PromiseUtil { diff --git a/web/controller/inbound.go b/web/controller/inbound.go index 86da9813..b274be64 100644 --- a/web/controller/inbound.go +++ b/web/controller/inbound.go @@ -159,24 +159,31 @@ func (a *InboundController) clearClientIps(c *gin.Context) { } func (a *InboundController) addInboundClient(c *gin.Context) { - data := &model.Inbound{} - err := c.ShouldBind(data) - if err != nil { - jsonMsg(c, I18nWeb(c, "pages.inbounds.update"), err) - return - } + var requestData []model.Inbound - needRestart := true + err := c.ShouldBindJSON(&requestData) + + if err != nil { + jsonMsg(c, I18nWeb(c, "pages.inbounds.update"), err) + return + } + + needRestart := true + + for _, data := range requestData { + + needRestart, err = a.inboundService.AddInboundClient(&data) + if err != nil { + jsonMsg(c, "Something went wrong!", err) + return + } + } + + jsonMsg(c, "Client(s) added", nil) + if err == nil && needRestart { + a.xrayService.SetToNeedRestart() + } - needRestart, err = a.inboundService.AddInboundClient(data) - if err != nil { - jsonMsg(c, "Something went wrong!", err) - return - } - jsonMsg(c, "Client(s) added", nil) - if err == nil && needRestart { - a.xrayService.SetToNeedRestart() - } } func (a *InboundController) delInboundClient(c *gin.Context) { diff --git a/web/html/common/qrcode_modal.html b/web/html/common/qrcode_modal.html index 3c4fd929..31b3450c 100644 --- a/web/html/common/qrcode_modal.html +++ b/web/html/common/qrcode_modal.html @@ -11,10 +11,12 @@ Subscription
- {{ i18n "pages.inbounds.client" }} - - + + @@ -373,7 +377,7 @@

[[ clientEmail ]]

[[ clientCount[dbInbound.id].online.length ]] -
+ @@ -740,6 +744,9 @@ case "delDepletedClients": this.delDepletedClients(-1) break; + case "addGroupClient": + this.openGroupAddClient() + break; } }, clickAction(action, dbInbound) { @@ -883,6 +890,20 @@ await this.submit(`/panel/inbound/update/${dbInbound.id}`, data, inModal); }, + openGroupAddClient() { + clientModal.show({ + title: '{{ i18n "pages.client.groupAdd"}}', + okText: '{{ i18n "pages.client.submitAdd"}}', + dbInbound: this.dbInbounds, + confirm: async (clients, dbInboundIds) => { + clientModal.loading(); + await this.addGroupClient(clients, dbInboundIds); + clientModal.close(); + await this.showQrcode(dbInboundIds[0],clients[0], true) + }, + isEdit: false + }); + }, openAddClient(dbInboundId) { dbInbound = this.dbInbounds.find(row => row.id === dbInboundId); clientModal.show({ @@ -893,6 +914,7 @@ clientModal.loading(); await this.addClient(clients, dbInboundId); clientModal.close(); + await this.showQrcode(dbInboundId,clients) }, isEdit: false }); @@ -936,11 +958,24 @@ } }, async addClient(clients, dbInboundId) { - const data = { + const data = [{ id: dbInboundId, settings: '{"clients": [' + clients.toString() + ']}', - }; - await this.submit(`/panel/inbound/addClient`, data); + }]; + + await this.submit(`/panel/inbound/addClient`, data, true) + }, + + async addGroupClient(clients, dbInboundIds) { + const data = [] + dbInboundIds.forEach((dbInboundId, index) => { + data.push({ + id: dbInboundId, + settings: '{"clients": [' + clients[index].toString() + ']}', + }) + }) + + await this.submit(`/panel/inbound/addClient`, data, true) }, async updateClient(client, dbInboundId, clientId) { const data = { @@ -1001,8 +1036,8 @@ checkFallback(dbInbound) { newDbInbound = new DBInbound(dbInbound); if (dbInbound.listen.startsWith("@")){ - rootInbound = this.inbounds.find((i) => - i.isTcp && + rootInbound = this.inbounds.find((i) => + i.isTcp && ['trojan','vless'].includes(i.protocol) && i.settings.fallbacks.find(f => f.dest === dbInbound.listen) ); @@ -1018,10 +1053,10 @@ } return newDbInbound; }, - showQrcode(dbInboundId, client) { + showQrcode(dbInboundId, client, isJustSub = false) { dbInbound = this.dbInbounds.find(row => row.id === dbInboundId); newDbInbound = this.checkFallback(dbInbound); - qrModal.show('{{ i18n "qrCode"}}', newDbInbound, client); + qrModal.show('{{ i18n "qrCode"}}', newDbInbound, client, isJustSub); }, showInfo(dbInboundId, client) { dbInbound = this.dbInbounds.find(row => row.id === dbInboundId); @@ -1050,8 +1085,8 @@ await this.updateClient(clients[index], dbInboundId, clientId); this.loading(false); }, - async submit(url, data) { - const msg = await HttpUtil.postWithModal(url, data); + async submit(url, data, isJson = false) { + const msg = isJson ? await HttpUtil.postWithModalJson(url, data) : await HttpUtil.postWithModal(url, data); if (msg.success) { await this.getDBInbounds(); } diff --git a/web/translation/translate.en_US.toml b/web/translation/translate.en_US.toml index 006378ab..b2d74c3b 100644 --- a/web/translation/translate.en_US.toml +++ b/web/translation/translate.en_US.toml @@ -184,6 +184,7 @@ [pages.client] "add" = "Add Client" +"groupAdd" = "Add subscription user" "edit" = "Edit Client" "submitAdd" = "Add Client" "submitEdit" = "Save Changes" diff --git a/web/translation/translate.es_ES.toml b/web/translation/translate.es_ES.toml index bbf50b6c..83c9460b 100644 --- a/web/translation/translate.es_ES.toml +++ b/web/translation/translate.es_ES.toml @@ -184,6 +184,7 @@ [pages.client] "add" = "Agregar Cliente" +"groupAdd" = "Agregar usuario de suscripción" "edit" = "Editar Cliente" "submitAdd" = "Agregar Cliente" "submitEdit" = "Guardar Cambios" diff --git a/web/translation/translate.fa_IR.toml b/web/translation/translate.fa_IR.toml index 1de8538c..9613c12e 100644 --- a/web/translation/translate.fa_IR.toml +++ b/web/translation/translate.fa_IR.toml @@ -184,6 +184,7 @@ [pages.client] "add" = "کاربر جدید" +"groupAdd" = "کاربر جدید سابسکریپشن" "edit" = "ویرایش کاربر" "submitAdd" = "اضافه کردن" "submitEdit" = "ذخیره تغییرات" diff --git a/web/translation/translate.ru_RU.toml b/web/translation/translate.ru_RU.toml index 2f33311e..7cf2be0a 100644 --- a/web/translation/translate.ru_RU.toml +++ b/web/translation/translate.ru_RU.toml @@ -184,6 +184,7 @@ [pages.client] "add" = "Добавить пользователя" +"groupAdd" = "Добавить пользователя подписки" "edit" = "Редактировать пользователя" "submitAdd" = "Добавить пользователя" "submitEdit" = "Сохранить изменения" diff --git a/web/translation/translate.vi_VN.toml b/web/translation/translate.vi_VN.toml index 1a2cfaa0..9cd7bc46 100644 --- a/web/translation/translate.vi_VN.toml +++ b/web/translation/translate.vi_VN.toml @@ -184,6 +184,7 @@ [pages.client] "add" = "Thêm người dùng" +"groupAdd" = "Thêm người dùng đăng ký" "edit" = "Chỉnh sửa người dùng" "submitAdd" = "Thêm" "submitEdit" = "Lưu thay đổi" diff --git a/web/translation/translate.zh_Hans.toml b/web/translation/translate.zh_Hans.toml index 966ef3f7..c229dc29 100644 --- a/web/translation/translate.zh_Hans.toml +++ b/web/translation/translate.zh_Hans.toml @@ -184,6 +184,7 @@ [pages.client] "add" = "添加客户端" +"groupAdd" = "添加订阅用户" "edit" = "编辑客户端" "submitAdd" = "添加客户端" "submitEdit" = "保存修改" From b172d450e3a9dba8a56b2f6cd9e059559d232033 Mon Sep 17 00:00:00 2001 From: Ali Rahimi Date: Mon, 22 Jan 2024 12:36:01 +0100 Subject: [PATCH 13/26] Group editing feature of users with the same subscription (#1661) --- web/controller/inbound.go | 81 ++++++++++++++ web/html/xui/client_modal.html | 146 ++++++++++++++++++------- web/html/xui/form/client.html | 12 ++ web/html/xui/inbounds.html | 33 ++++-- web/translation/translate.en_US.toml | 2 + web/translation/translate.es_ES.toml | 2 + web/translation/translate.fa_IR.toml | 2 + web/translation/translate.ru_RU.toml | 2 + web/translation/translate.vi_VN.toml | 2 + web/translation/translate.zh_Hans.toml | 2 + 10 files changed, 237 insertions(+), 47 deletions(-) diff --git a/web/controller/inbound.go b/web/controller/inbound.go index b274be64..4d6e0af0 100644 --- a/web/controller/inbound.go +++ b/web/controller/inbound.go @@ -1,6 +1,7 @@ package controller import ( + "errors" "encoding/json" "fmt" "strconv" @@ -32,7 +33,9 @@ func (a *InboundController) initRouter(g *gin.RouterGroup) { g.POST("/clientIps/:email", a.getClientIps) g.POST("/clearClientIps/:email", a.clearClientIps) g.POST("/addClient", a.addInboundClient) + g.POST("/addGroupClient", a.addGroupInboundClient) g.POST("/:id/delClient/:clientId", a.delInboundClient) + g.POST("/updateClients", a.updateGroupInboundClient) g.POST("/updateClient/:clientId", a.updateInboundClient) g.POST("/:id/resetClientTraffic/:email", a.resetClientTraffic) g.POST("/resetAllTraffics", a.resetAllTraffics) @@ -186,6 +189,34 @@ func (a *InboundController) addInboundClient(c *gin.Context) { } +func (a *InboundController) addGroupInboundClient(c *gin.Context) { + var requestData []model.Inbound + + err := c.ShouldBindJSON(&requestData) + + if err != nil { + jsonMsg(c, I18nWeb(c, "pages.inbounds.update"), err) + return + } + + needRestart := true + + for _, data := range requestData { + + needRestart, err = a.inboundService.AddInboundClient(&data) + if err != nil { + jsonMsg(c, "Something went wrong!", err) + return + } + } + + jsonMsg(c, "Client(s) added", nil) + if err == nil && needRestart { + a.xrayService.SetToNeedRestart() + } + +} + func (a *InboundController) delInboundClient(c *gin.Context) { id, err := strconv.Atoi(c.Param("id")) if err != nil { @@ -230,6 +261,56 @@ func (a *InboundController) updateInboundClient(c *gin.Context) { } } +func (a *InboundController) updateGroupInboundClient(c *gin.Context) { + var requestData []map[string]interface{} + + if err := c.ShouldBindJSON(&requestData); err != nil { + jsonMsg(c, I18nWeb(c, "pages.inbounds.update"), err) + return + } + + needRestart := false + + for _, item := range requestData { + + inboundMap, ok := item["inbound"].(map[string]interface{}) + if !ok { + jsonMsg(c, "Something went wrong!", errors.New("Failed to convert 'inbound' to map")) + return + } + + clientId, ok := item["clientId"].(string) + if !ok { + jsonMsg(c, "Something went wrong!", errors.New("Failed to convert 'clientId' to string")) + return + } + + inboundJSON, err := json.Marshal(inboundMap) + if err != nil { + jsonMsg(c, "Something went wrong!", err) + return + } + + var inboundModel model.Inbound + if err := json.Unmarshal(inboundJSON, &inboundModel); err != nil { + jsonMsg(c, "Something went wrong!", err) + return + } + + if restart, err := a.inboundService.UpdateInboundClient(&inboundModel, clientId); err != nil { + jsonMsg(c, "Something went wrong!", err) + return + } else { + needRestart = needRestart || restart + } + } + + jsonMsg(c, "Client updated", nil) + if needRestart { + a.xrayService.SetToNeedRestart() + } +} + func (a *InboundController) resetClientTraffic(c *gin.Context) { id, err := strconv.Atoi(c.Param("id")) if err != nil { diff --git a/web/html/xui/client_modal.html b/web/html/xui/client_modal.html index 02c548e3..cb15e1e7 100644 --- a/web/html/xui/client_modal.html +++ b/web/html/xui/client_modal.html @@ -16,12 +16,15 @@ title: '', okText: '', group: { + canGroup: true, isGroup: false, currentClient: null, inbounds: [], clients: [], + editIds: [] }, dbInbound: new DBInbound(), + dbInbounds: null, inbound: new Inbound(), clients: [], clientStats: [], @@ -30,64 +33,95 @@ clientIps: null, delayedStart: false, ok() { - if (clientModal.isEdit) { - ObjectUtil.execute(clientModal.confirm, clientModalApp.client, clientModal.dbInbound.id, clientModal.oldClientId); - } else { - if (clientModal.group.isGroup) { - const currentClient = clientModal.group.currentClient; + if (clientModal.group.isGroup && clientModal.group.canGroup) { + const currentClient = clientModal.group.currentClient; - clientModal.group.clients.forEach((client, index) => { - const { email, limitIp, totalGB, expiryTime, reset, enable, subId, tgId, flow } = currentClient; + clientModal.group.clients.forEach((client, index) => { + const { email, limitIp, totalGB, expiryTime, reset, enable, subId, tgId, flow } = currentClient; - client.email = `${email}-${index + 1}`; - client.limitIp = limitIp; - client.totalGB = totalGB; - client.expiryTime = expiryTime; - client.reset = reset; - client.enable = enable; + const match = email.match(/^(.*?)__/); + const new_email = match ? match[1] : email; - if (subId) { - client.subId = subId; - } - if (tgId) { - client.tgId = tgId; - } - if (flow) { - client.flow = flow; - } - }); + client.email = `${new_email}__${index + 1}`; + client.limitIp = limitIp; + client.totalGB = totalGB; + client.expiryTime = expiryTime; + client.reset = reset; + client.enable = enable; + + if (subId) { + client.subId = subId; + } + if (tgId) { + client.tgId = tgId; + } + if (flow) { + client.flow = flow; + } + }); + + if (clientModal.isEdit) { + ObjectUtil.execute(clientModal.confirm, clientModal.group.clients, clientModal.group.inbounds, clientModal.group.editIds); + }else{ ObjectUtil.execute(clientModal.confirm, clientModal.group.clients, clientModal.group.inbounds); - } else { + } + } else { + if (clientModal.isEdit){ + ObjectUtil.execute(clientModal.confirm, clientModalApp.client, clientModal.dbInbound.id, clientModal.oldClientId); + }else{ ObjectUtil.execute(clientModal.confirm, clientModalApp.client, clientModal.dbInbound.id); } } }, - show({ title = '', okText = '{{ i18n "sure" }}', index = null, dbInbound = null, confirm = () => { }, isEdit = false }) { + show({ title = '', okText = '{{ i18n "sure" }}', index = null, dbInbound = null, dbInbounds = null, confirm = () => { }, isEdit = false }) { this.group = { + canGroup: true, isGroup: false, currentClient: null, inbounds: [], clients: [], + editIds: [] } + this.dbInbounds = dbInbounds; this.visible = true; this.title = title; this.okText = okText; this.isEdit = isEdit; - if (Array.isArray(dbInbound)) { - this.group.isGroup = true; - dbInbound.forEach((dbInboundItem) => { - this.showProcess(dbInboundItem); - this.group.inbounds.push(dbInboundItem.id) - this.group.clients.push(this.clients[this.index]) - }) - this.group.currentClient = this.clients[this.index] + if (dbInbounds !== null && Array.isArray(dbInbounds)) { + if (isEdit) { + this.showProcess(dbInbound, index); + let processSingleEdit = true + if (this.group.canGroup){ + this.group.currentClient = this.clients[this.index] + const response = this.getGroupInboundsClients(dbInbounds,this.group.currentClient) + if (response.clients.length > 1){ + this.group.isGroup = true; + this.group.inbounds = response.inbounds + this.group.clients = response.clients + this.group.editIds = response.editIds + if (this.clients[index].expiryTime < 0) { + this.delayedStart = true; + } + processSingleEdit = false + } + } + if(processSingleEdit){ + this.singleEditClientProcess(index) + } + } else { + this.group.isGroup = true; + dbInbounds.forEach((dbInboundItem) => { + this.showProcess(dbInboundItem); + this.addClient(this.inbound.protocol, this.clients); + this.group.inbounds.push(dbInboundItem.id) + this.group.clients.push(this.clients[this.index]) + }) + this.group.currentClient = this.clients[this.index] + } } else { this.showProcess(dbInbound, index); if (isEdit) { - if (this.clients[index].expiryTime < 0) { - this.delayedStart = true; - } - this.oldClientId = this.getClientId(dbInbound.protocol, clients[index]); + this.singleEditClientProcess(index) } else { this.addClient(this.inbound.protocol, this.clients); } @@ -101,7 +135,34 @@ this.clients = this.inbound.clients; this.index = index === null ? this.clients.length : index; this.delayedStart = false; - this.addClient(this.inbound.protocol, this.clients); + }, + singleEditClientProcess(index) { + if (this.clients[index].expiryTime < 0) { + this.delayedStart = true; + } + this.oldClientId = this.getClientId(this.dbInbound.protocol, this.clients[index]); + }, + getGroupInboundsClients(dbInbounds, currentClient) { + const response = { + inbounds: [], + clients: [], + editIds: [] + } + dbInbounds.forEach((dbInboundItem) => { + const dbInbound = new DBInbound(dbInboundItem); + const inbound = dbInbound.toInbound(); + const clients = inbound.clients; + if (clients.length > 0){ + clients.forEach((client) => { + if (client['subId'] === currentClient['subId']){ + response.inbounds.push(dbInboundItem.id) + response.clients.push(client) + response.editIds.push(this.getClientId(dbInbound.protocol, client)) + } + }) + } + }) + return response; }, getClientId(protocol, client) { switch (protocol) { @@ -148,6 +209,15 @@ get isGroup() { return this.clientModal.group.isGroup; }, + get isGroupEdit() { + return this.clientModal.group.canGroup; + }, + set isGroupEdit(value) { + this.clientModal.group.canGroup = value; + if (!value){ + this.clientModal.singleEditClientProcess(this.clientModal.index) + } + }, get datepicker() { return app.datepicker; }, diff --git a/web/html/xui/form/client.html b/web/html/xui/form/client.html index f4ac25f3..434a806e 100644 --- a/web/html/xui/form/client.html +++ b/web/html/xui/form/client.html @@ -3,6 +3,18 @@ + + + + - diff --git a/web/html/xui/component/persianDatepicker.html b/web/html/xui/component/persianDatepicker.html index 34ae4f8d..10ef8472 100644 --- a/web/html/xui/component/persianDatepicker.html +++ b/web/html/xui/component/persianDatepicker.html @@ -3,7 +3,7 @@
+ :placeholder="placeholder"> @@ -21,7 +21,7 @@ const persianDatepicker = {}; Vue.component('persian-datepicker', { - props: ['dropdown-class-name', 'format', 'value'], + props: ['placeholder', 'format', 'value'], template: `{{template "component/persianDatepickerTemplate"}}`, data() { return { @@ -48,7 +48,7 @@ listenToDatepicker() { jalaliDatepicker.startWatch({ time: true, - container: '.ant-modal-wrap', + zIndex: '9999', hideAfterChange: true, useDropDownYears: false, changeMonthRotateYear: true, diff --git a/web/html/xui/component/themeSwitch.html b/web/html/xui/component/themeSwitch.html index 4d5412e2..3bc1ad7d 100644 --- a/web/html/xui/component/themeSwitch.html +++ b/web/html/xui/component/themeSwitch.html @@ -11,6 +11,7 @@ function createThemeSwitcher() { const isDarkTheme = localStorage.getItem('dark-mode') === 'true'; const theme = isDarkTheme ? 'dark' : 'light'; + document.querySelector('body').setAttribute('class', theme) return { isDarkTheme, get currentTheme() { @@ -19,6 +20,7 @@ toggleTheme() { this.isDarkTheme = !this.isDarkTheme; localStorage.setItem('dark-mode', this.isDarkTheme); + document.querySelector('body').setAttribute('class', this.isDarkTheme ? 'dark' : 'light') }, }; } diff --git a/web/html/xui/form/client.html b/web/html/xui/form/client.html index 434a806e..526bbb8b 100644 --- a/web/html/xui/form/client.html +++ b/web/html/xui/form/client.html @@ -164,7 +164,7 @@ - Expired diff --git a/web/html/xui/form/inbound.html b/web/html/xui/form/inbound.html index 92ba6e75..048fc818 100644 --- a/web/html/xui/form/inbound.html +++ b/web/html/xui/form/inbound.html @@ -57,8 +57,8 @@ - + diff --git a/web/translation/translate.en_US.toml b/web/translation/translate.en_US.toml index 4fcb50c6..0b7ac5b2 100644 --- a/web/translation/translate.en_US.toml +++ b/web/translation/translate.en_US.toml @@ -251,6 +251,7 @@ "pageSizeDesc" = "Define page size for inbounds table. (0 = disable)" "remarkModel" = "Remark Model & Separation Character" "datepicker" = "Calendar Type" +"datepickerPlaceholder" = "Select date" "datepickerDescription" = "Scheduled tasks will run based on this calendar." "sampleRemark" = "Sample Remark" "oldUsername" = "Current Username" diff --git a/web/translation/translate.es_ES.toml b/web/translation/translate.es_ES.toml index a61689f9..926325cf 100644 --- a/web/translation/translate.es_ES.toml +++ b/web/translation/translate.es_ES.toml @@ -251,6 +251,7 @@ "pageSizeDesc" = "Defina el tamaño de página para la tabla de entradas. Establezca 0 para desactivar" "remarkModel" = "Modelo de observación y carácter de separación" "datepicker" = "selector de fechas" +"datepickerPlaceholder" = "Seleccionar fecha" "datepickerDescription" = "El tipo de calendario selector especifica la fecha de vencimiento" "sampleRemark" = "Observación de muestra" "oldUsername" = "Nombre de Usuario Actual" diff --git a/web/translation/translate.fa_IR.toml b/web/translation/translate.fa_IR.toml index 38c28579..17e6facc 100644 --- a/web/translation/translate.fa_IR.toml +++ b/web/translation/translate.fa_IR.toml @@ -251,6 +251,7 @@ "pageSizeDesc" = "(اندازه صفحه برای جدول ورودی‌ها.(0 = غیرفعال" "remarkModel" = "نام‌کانفیگ و جداکننده" "datepicker" = "نوع تقویم" +"datepickerPlaceholder" = "انتخاب تاریخ" "datepickerDescription" = "وظایف برنامه ریزی شده بر اساس این تقویم اجرا می‌شود" "sampleRemark" = "نمونه‌نام" "oldUsername" = "نام‌کاربری فعلی" diff --git a/web/translation/translate.ru_RU.toml b/web/translation/translate.ru_RU.toml index 4bfda2b9..c567f38b 100644 --- a/web/translation/translate.ru_RU.toml +++ b/web/translation/translate.ru_RU.toml @@ -251,6 +251,7 @@ "pageSizeDesc" = "Определить размер страницы для входящей таблицы. Установите 0, чтобы отключить" "remarkModel" = "Модель примечания и символ разделения" "datepicker" = "выбор даты" +"datepickerPlaceholder" = "Выберите дату" "datepickerDescription" = "Тип календаря выбора указывает дату истечения срока действия." "sampleRemark" = "Пример замечания" "oldUsername" = "Текущее имя пользователя" diff --git a/web/translation/translate.vi_VN.toml b/web/translation/translate.vi_VN.toml index cd19328d..5876a5b4 100644 --- a/web/translation/translate.vi_VN.toml +++ b/web/translation/translate.vi_VN.toml @@ -251,6 +251,7 @@ "pageSizeDesc" = "Xác định kích thước trang cho bảng gửi đến. Đặt 0 để tắt" "remarkModel" = "Ghi chú mô hình và ký tự phân tách" "datepicker" = "Kiểu lịch" +"datepickerPlaceholder" = "Chọn ngày" "datepickerDescription" = "Tác vụ chạy theo lịch trình sẽ chạy theo kiểu lịch này." "sampleRemark" = "Nhận xét mẫu" "oldUsername" = "Tên người dùng hiện tại" diff --git a/web/translation/translate.zh_Hans.toml b/web/translation/translate.zh_Hans.toml index a7399642..29dc8f4c 100644 --- a/web/translation/translate.zh_Hans.toml +++ b/web/translation/translate.zh_Hans.toml @@ -251,6 +251,7 @@ "pageSizeDesc" = "定义入站表的页面大小。设置 0 表示禁用" "remarkModel" = "备注模型和分隔符" "datepicker" = "日期选择器" +"datepickerPlaceholder" = "选择日期" "datepickerDescription" = "选择器日历类型指定到期日期" "sampleRemark" = "备注示例" "oldUsername" = "原用户名" From 251ceeedbad52fc05c84c431a3d982d558a59d7d Mon Sep 17 00:00:00 2001 From: Ali Rahimi Date: Tue, 23 Jan 2024 22:53:15 +0100 Subject: [PATCH 16/26] bug fixed (import inbounds) (#1670) * add single client bug fixed * bug fixed --- web/html/xui/inbounds.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/html/xui/inbounds.html b/web/html/xui/inbounds.html index faff7a0f..2707fc42 100644 --- a/web/html/xui/inbounds.html +++ b/web/html/xui/inbounds.html @@ -1243,7 +1243,7 @@ value: '', okText: '{{ i18n "pages.inbounds.import" }}', confirm: async (dbInboundText) => { - await this.submit('/panel/inbound/import', {data: dbInboundText}, promptModal); + await this.submit('/panel/inbound/import', {data: dbInboundText}); promptModal.close(); }, }); From ff547a258d4866440920a293a1e0eca75b2e43f3 Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Wed, 24 Jan 2024 01:24:17 +0330 Subject: [PATCH 17/26] gen button - publicKey & psk --- web/html/xui/form/outbound.html | 22 ++++++++++++++++++++-- web/html/xui/form/protocol/wireguard.html | 22 ++++++++++++++++++++-- 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/web/html/xui/form/outbound.html b/web/html/xui/form/outbound.html index 8bea1fe5..3f11907d 100644 --- a/web/html/xui/form/outbound.html +++ b/web/html/xui/form/outbound.html @@ -134,10 +134,28 @@ - + + - + + diff --git a/web/html/xui/form/protocol/wireguard.html b/web/html/xui/form/protocol/wireguard.html index 553f5d42..ea2c3427 100644 --- a/web/html/xui/form/protocol/wireguard.html +++ b/web/html/xui/form/protocol/wireguard.html @@ -32,10 +32,28 @@ - + + - + + From 2463b99479c3cb20ea8c1b907f498d665551621c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 24 Jan 2024 15:13:53 +0330 Subject: [PATCH 18/26] Bump google.golang.org/grpc from 1.60.1 to 1.61.0 (#1671) Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.60.1 to 1.61.0. - [Release notes](https://github.com/grpc/grpc-go/releases) - [Commits](https://github.com/grpc/grpc-go/compare/v1.60.1...v1.61.0) --- updated-dependencies: - dependency-name: google.golang.org/grpc dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 4e84c3e2..80fd416a 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( github.com/xtls/xray-core v1.8.7 go.uber.org/atomic v1.11.0 golang.org/x/text v0.14.0 - google.golang.org/grpc v1.60.1 + google.golang.org/grpc v1.61.0 gorm.io/driver/sqlite v1.5.4 gorm.io/gorm v1.25.5 ) diff --git a/go.sum b/go.sum index f5d3b46b..1663fce7 100644 --- a/go.sum +++ b/go.sum @@ -413,8 +413,8 @@ google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmE google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.60.1 h1:26+wFr+cNqSGFcOXcabYC0lUVJVRa2Sb2ortSK7VrEU= -google.golang.org/grpc v1.60.1/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM= +google.golang.org/grpc v1.61.0 h1:TOvOcuXn30kRao+gfcvsebNEa5iZIiLkisYEkf7R7o0= +google.golang.org/grpc v1.61.0/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= From 979fdedbbe1f36962bf72349ba21de4223286227 Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Wed, 24 Jan 2024 20:50:58 +0330 Subject: [PATCH 19/26] [bug] avoid empty inbound Co-Authored-By: Alireza Ahmadi --- web/service/inbound.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/web/service/inbound.go b/web/service/inbound.go index 2a4966ab..f3445101 100644 --- a/web/service/inbound.go +++ b/web/service/inbound.go @@ -317,7 +317,6 @@ func (s *InboundService) UpdateInbound(inbound *model.Inbound) (*model.Inbound, oldInbound.Tag = fmt.Sprintf("inbound-%v:%v", inbound.Listen, inbound.Port) } - needRestart := false s.xrayApi.Init(p.GetAPIPort()) if s.xrayApi.DelInbound(tag) == nil { @@ -509,6 +508,10 @@ func (s *InboundService) DelInboundClient(inboundId int, clientId string) (bool, } } + if len(newClients) == 0 { + return false, common.NewError("no client remained in Inbound") + } + settings["clients"] = newClients newSettings, err := json.MarshalIndent(settings, "", " ") if err != nil { From 5f9058c84f6a46a2233f323dcdac8281975accc0 Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Fri, 26 Jan 2024 16:50:22 +0330 Subject: [PATCH 20/26] fix downloaded log format Co-Authored-By: Alireza Ahmadi --- web/html/xui/index.html | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/web/html/xui/index.html b/web/html/xui/index.html index 3c01a29f..258d4154 100644 --- a/web/html/xui/index.html +++ b/web/html/xui/index.html @@ -299,12 +299,12 @@ + :href="'data:application/text;charset=utf-8,' + encodeURIComponent(logModal.logs.join('\n'))" download="x-ui.log"> {{ i18n "download" }} x-ui.log -
+
0 ? this.formatLogs(logs) : "No Record..."; }, formatLogs(logs) { let formattedLogs = ''; From 98384ac2365cabe6a4a200062a90b59f6ce5ad72 Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Fri, 26 Jan 2024 16:50:35 +0330 Subject: [PATCH 21/26] fix - direct tag --- web/service/config.json | 1 + 1 file changed, 1 insertion(+) diff --git a/web/service/config.json b/web/service/config.json index c846d590..82f7dddf 100644 --- a/web/service/config.json +++ b/web/service/config.json @@ -24,6 +24,7 @@ ], "outbounds": [ { + "tag": "direct", "protocol": "freedom", "settings": {} }, From 08e259327b8a7b606b95e3b145fe1959b426d7c4 Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Fri, 26 Jan 2024 17:06:54 +0330 Subject: [PATCH 22/26] unnecessary use of fmt.Sprintf --- web/service/xray_setting.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/service/xray_setting.go b/web/service/xray_setting.go index 1d2e696e..86bb1d7b 100644 --- a/web/service/xray_setting.go +++ b/web/service/xray_setting.go @@ -80,7 +80,7 @@ func (s *XraySettingService) RegWarp(secretKey string, publicKey string) (string hostName, _ := os.Hostname() data := fmt.Sprintf(`{"key":"%s","tos":"%s","type": "PC","model": "x-ui", "name": "%s"}`, publicKey, tos, hostName) - url := fmt.Sprintf("https://api.cloudflareclient.com/v0a2158/reg") + url := "https://api.cloudflareclient.com/v0a2158/reg" req, err := http.NewRequest("POST", url, bytes.NewBuffer([]byte(data))) if err != nil { From ec88053df0f2619bdb6285bcd2085675efbc8ca8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 26 Jan 2024 20:15:07 +0330 Subject: [PATCH 23/26] Bump gorm.io/gorm from 1.25.5 to 1.25.6 (#1683) Bumps [gorm.io/gorm](https://github.com/go-gorm/gorm) from 1.25.5 to 1.25.6. - [Release notes](https://github.com/go-gorm/gorm/releases) - [Commits](https://github.com/go-gorm/gorm/compare/v1.25.5...v1.25.6) --- updated-dependencies: - dependency-name: gorm.io/gorm dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 80fd416a..d46ed1fe 100644 --- a/go.mod +++ b/go.mod @@ -19,7 +19,7 @@ require ( golang.org/x/text v0.14.0 google.golang.org/grpc v1.61.0 gorm.io/driver/sqlite v1.5.4 - gorm.io/gorm v1.25.5 + gorm.io/gorm v1.25.6 ) require ( diff --git a/go.sum b/go.sum index 1663fce7..8b6465d7 100644 --- a/go.sum +++ b/go.sum @@ -437,8 +437,8 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gorm.io/driver/sqlite v1.5.4 h1:IqXwXi8M/ZlPzH/947tn5uik3aYQslP9BVveoax0nV0= gorm.io/driver/sqlite v1.5.4/go.mod h1:qxAuCol+2r6PannQDpOP1FP6ag3mKi4esLnB/jHed+4= -gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls= -gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= +gorm.io/gorm v1.25.6 h1:V92+vVda1wEISSOMtodHVRcUIOPYa2tgQtyF+DfFx+A= +gorm.io/gorm v1.25.6/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o= gvisor.dev/gvisor v0.0.0-20231104011432-48a6d7d5bd0b h1:yqkg3pTifuKukuWanp8spDsL4irJkHF5WI0J47hU87o= gvisor.dev/gvisor v0.0.0-20231104011432-48a6d7d5bd0b/go.mod h1:10sU+Uh5KKNv1+2x2A0Gvzt8FjD3ASIhorV3YsauXhk= From daa43540477e18ae5c6e19e887733b793c17ad72 Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Fri, 26 Jan 2024 21:57:46 +0330 Subject: [PATCH 24/26] ipv6 for familyProtect and some changes --- web/assets/js/model/outbound.js | 4 ++-- web/assets/js/model/xray.js | 2 +- web/html/xui/warp_modal.html | 1 + web/html/xui/xray.html | 6 ++++-- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/web/assets/js/model/outbound.js b/web/assets/js/model/outbound.js index 0191c4a4..42fb50a1 100644 --- a/web/assets/js/model/outbound.js +++ b/web/assets/js/model/outbound.js @@ -915,7 +915,7 @@ Outbound.HttpSettings = class extends CommonClass { Outbound.WireguardSettings = class extends CommonClass { constructor( mtu=1420, secretKey=Wireguard.generateKeypair().privateKey, - address='', workers=2, domainStrategy='', reserved='', + address=[''], workers=2, domainStrategy='ForceIPv6v4', reserved='', peers=[new Outbound.WireguardSettings.Peer()], kernelMode=false) { super(); this.mtu = mtu; @@ -965,7 +965,7 @@ Outbound.WireguardSettings = class extends CommonClass { }; Outbound.WireguardSettings.Peer = class extends CommonClass { - constructor(publicKey='', psk='', allowedIPs=['0.0.0.0/0','::/0'], endpoint='', keepAlive=0) { + constructor(publicKey=Wireguard.generateKeypair().publicKey, psk='', allowedIPs=['0.0.0.0/0','::/0'], endpoint='', keepAlive=0) { super(); this.publicKey = publicKey; this.psk = psk; diff --git a/web/assets/js/model/xray.js b/web/assets/js/model/xray.js index b2c8a2e5..e9676252 100644 --- a/web/assets/js/model/xray.js +++ b/web/assets/js/model/xray.js @@ -2297,7 +2297,7 @@ Inbound.WireguardSettings = class extends XrayCommonClass { }; Inbound.WireguardSettings.Peer = class extends XrayCommonClass { - constructor(publicKey='', psk='', allowedIPs=['0.0.0.0/0','::/0'], keepAlive=0) { + constructor(publicKey=Wireguard.generateKeypair().publicKey, psk='', allowedIPs=['0.0.0.0/0','::/0'], keepAlive=0) { super(); this.publicKey = publicKey; this.psk = psk; diff --git a/web/html/xui/warp_modal.html b/web/html/xui/warp_modal.html index d1663df0..4e6dd4fa 100644 --- a/web/html/xui/warp_modal.html +++ b/web/html/xui/warp_modal.html @@ -140,6 +140,7 @@ mtu: 1420, secretKey: warpModal.warpData.private_key, address: Object.values(config.interface.addresses), + domainStrategy: 'ForceIPv6v4', peers: [{ publicKey: peer.public_key, endpoint: peer.endpoint.host, diff --git a/web/html/xui/xray.html b/web/html/xui/xray.html index 05f5f178..d6f0c0f8 100644 --- a/web/html/xui/xray.html +++ b/web/html/xui/xray.html @@ -569,9 +569,11 @@ familyProtectDNS: { "servers": [ "1.1.1.3", // https://developers.cloudflare.com/1.1.1.1/setup/ - "1.0.0.3" + "1.0.0.3", + "2606:4700:4700::1113", + "2606:4700:4700::1003" ], - "queryStrategy": "UseIPv4" + "queryStrategy": "UseIP" }, } }, From 9fba92d879767394535f70ba83f0a58d6b063a7d Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Fri, 26 Jan 2024 22:07:15 +0330 Subject: [PATCH 25/26] v2.1.2 revert #1650 #1661 #1664 #1670 made panel full of bug --- .github/workflows/docker.yml | 2 +- README.md | 4 +- config/version | 2 +- web/assets/js/util/utils.js | 35 ------ web/controller/inbound.go | 111 +++--------------- web/html/common/qrcode_modal.html | 17 +-- web/html/xui/client_modal.html | 154 +++---------------------- web/html/xui/form/client.html | 16 +-- web/html/xui/inbound_modal.html | 4 - web/html/xui/inbounds.html | 82 +++---------- web/translation/translate.en_US.toml | 3 - web/translation/translate.es_ES.toml | 3 - web/translation/translate.fa_IR.toml | 3 - web/translation/translate.ru_RU.toml | 3 - web/translation/translate.vi_VN.toml | 3 - web/translation/translate.zh_Hans.toml | 3 - 16 files changed, 57 insertions(+), 388 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 6afe8618..1115de37 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -36,6 +36,6 @@ jobs: with: context: . push: ${{ github.event_name != 'pull_request' }} - platforms: linux/amd64, linux/arm64/v8, linux/arm/v7, linux/arm/v6, linux/386, linux/arm/v5 + platforms: linux/amd64, linux/arm64/v8, linux/arm/v7, linux/arm/v6, linux/386 tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} diff --git a/README.md b/README.md index 6b0beab7..389d6d1d 100644 --- a/README.md +++ b/README.md @@ -25,10 +25,10 @@ bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install. ## Install Custom Version -To install your desired version, add the version to the end of the installation command. e.g., ver `v2.1.1`: +To install your desired version, add the version to the end of the installation command. e.g., ver `v2.1.2`: ``` -bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) v2.1.1 +bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) v2.1.2 ``` ## Manual Install & Upgrade diff --git a/config/version b/config/version index 7c327287..8f9174b4 100644 --- a/config/version +++ b/config/version @@ -1 +1 @@ -2.1.1 \ No newline at end of file +2.1.2 \ No newline at end of file diff --git a/web/assets/js/util/utils.js b/web/assets/js/util/utils.js index 48ff237d..61b322bd 100644 --- a/web/assets/js/util/utils.js +++ b/web/assets/js/util/utils.js @@ -83,41 +83,6 @@ class HttpUtil { } return msg; } - - static async jsonPost(url, data) { - let msg; - try { - const requestOptions = { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(data), - }; - const resp = await fetch(url, requestOptions); - const response = await resp.json(); - - msg = this._respToMsg({data : response}); - } catch (e) { - msg = new Msg(false, e.toString()); - } - this._handleMsg(msg); - return msg; - } - - static async postWithModalJson(url, data, modal) { - if (modal) { - modal.loading(true); - } - const msg = await this.jsonPost(url, data); - if (modal) { - modal.loading(false); - if (msg instanceof Msg && msg.success) { - modal.close(); - } - } - return msg; - } } class PromiseUtil { diff --git a/web/controller/inbound.go b/web/controller/inbound.go index 0aa2e2df..86da9813 100644 --- a/web/controller/inbound.go +++ b/web/controller/inbound.go @@ -1,7 +1,6 @@ package controller import ( - "errors" "encoding/json" "fmt" "strconv" @@ -33,9 +32,7 @@ func (a *InboundController) initRouter(g *gin.RouterGroup) { g.POST("/clientIps/:email", a.getClientIps) g.POST("/clearClientIps/:email", a.clearClientIps) g.POST("/addClient", a.addInboundClient) - g.POST("/addGroupClient", a.addGroupInboundClient) g.POST("/:id/delClient/:clientId", a.delInboundClient) - g.POST("/updateClients", a.updateGroupInboundClient) g.POST("/updateClient/:clientId", a.updateInboundClient) g.POST("/:id/resetClientTraffic/:email", a.resetClientTraffic) g.POST("/resetAllTraffics", a.resetAllTraffics) @@ -163,51 +160,23 @@ func (a *InboundController) clearClientIps(c *gin.Context) { func (a *InboundController) addInboundClient(c *gin.Context) { data := &model.Inbound{} - err := c.ShouldBind(data) - if err != nil { - jsonMsg(c, I18nWeb(c, "pages.inbounds.update"), err) - return - } + err := c.ShouldBind(data) + if err != nil { + jsonMsg(c, I18nWeb(c, "pages.inbounds.update"), err) + return + } - needRestart := true - - needRestart, err = a.inboundService.AddInboundClient(data) - if err != nil { - jsonMsg(c, "Something went wrong!", err) - return - } - jsonMsg(c, "Client(s) added", nil) - if err == nil && needRestart { - a.xrayService.SetToNeedRestart() - } -} - -func (a *InboundController) addGroupInboundClient(c *gin.Context) { - var requestData []model.Inbound - - err := c.ShouldBindJSON(&requestData) - - if err != nil { - jsonMsg(c, I18nWeb(c, "pages.inbounds.update"), err) - return - } - - needRestart := true - - for _, data := range requestData { - - needRestart, err = a.inboundService.AddInboundClient(&data) - if err != nil { - jsonMsg(c, "Something went wrong!", err) - return - } - } - - jsonMsg(c, "Client(s) added", nil) - if err == nil && needRestart { - a.xrayService.SetToNeedRestart() - } + needRestart := true + needRestart, err = a.inboundService.AddInboundClient(data) + if err != nil { + jsonMsg(c, "Something went wrong!", err) + return + } + jsonMsg(c, "Client(s) added", nil) + if err == nil && needRestart { + a.xrayService.SetToNeedRestart() + } } func (a *InboundController) delInboundClient(c *gin.Context) { @@ -254,56 +223,6 @@ func (a *InboundController) updateInboundClient(c *gin.Context) { } } -func (a *InboundController) updateGroupInboundClient(c *gin.Context) { - var requestData []map[string]interface{} - - if err := c.ShouldBindJSON(&requestData); err != nil { - jsonMsg(c, I18nWeb(c, "pages.inbounds.update"), err) - return - } - - needRestart := false - - for _, item := range requestData { - - inboundMap, ok := item["inbound"].(map[string]interface{}) - if !ok { - jsonMsg(c, "Something went wrong!", errors.New("Failed to convert 'inbound' to map")) - return - } - - clientId, ok := item["clientId"].(string) - if !ok { - jsonMsg(c, "Something went wrong!", errors.New("Failed to convert 'clientId' to string")) - return - } - - inboundJSON, err := json.Marshal(inboundMap) - if err != nil { - jsonMsg(c, "Something went wrong!", err) - return - } - - var inboundModel model.Inbound - if err := json.Unmarshal(inboundJSON, &inboundModel); err != nil { - jsonMsg(c, "Something went wrong!", err) - return - } - - if restart, err := a.inboundService.UpdateInboundClient(&inboundModel, clientId); err != nil { - jsonMsg(c, "Something went wrong!", err) - return - } else { - needRestart = needRestart || restart - } - } - - jsonMsg(c, "Client updated", nil) - if needRestart { - a.xrayService.SetToNeedRestart() - } -} - func (a *InboundController) resetClientTraffic(c *gin.Context) { id, err := strconv.Atoi(c.Param("id")) if err != nil { diff --git a/web/html/common/qrcode_modal.html b/web/html/common/qrcode_modal.html index 31b3450c..3c4fd929 100644 --- a/web/html/common/qrcode_modal.html +++ b/web/html/common/qrcode_modal.html @@ -11,12 +11,10 @@ Subscription
- {{ i18n "pages.inbounds.client" }} - @@ -377,7 +373,7 @@

[[ clientEmail ]]

[[ clientCount[dbInbound.id].online.length ]] - + @@ -744,9 +740,6 @@ case "delDepletedClients": this.delDepletedClients(-1) break; - case "addGroupClient": - this.openGroupAddClient() - break; } }, clickAction(action, dbInbound) { @@ -890,20 +883,6 @@ await this.submit(`/panel/inbound/update/${dbInbound.id}`, data, inModal); }, - openGroupAddClient() { - clientModal.show({ - title: '{{ i18n "pages.client.groupAdd"}}', - okText: '{{ i18n "pages.client.submitAdd"}}', - dbInbounds: this.dbInbounds, - confirm: async (clients, dbInboundIds) => { - clientModal.loading(); - await this.addGroupClient(clients, dbInboundIds); - clientModal.close(); - await this.showQrcode(dbInboundIds[0],clients[0], true) - }, - isEdit: false - }); - }, openAddClient(dbInboundId) { dbInbound = this.dbInbounds.find(row => row.id === dbInboundId); clientModal.show({ @@ -914,7 +893,6 @@ clientModal.loading(); await this.addClient(clients, dbInboundId); clientModal.close(); - await this.showQrcode(dbInboundId,clients) }, isEdit: false }); @@ -939,7 +917,6 @@ clientModal.show({ title: '{{ i18n "pages.client.edit"}}', okText: '{{ i18n "pages.client.submitEdit"}}', - dbInbounds: this.dbInbounds, dbInbound: dbInbound, index: index, confirm: async (client, dbInboundId, clientId) => { @@ -963,41 +940,14 @@ id: dbInboundId, settings: '{"clients": [' + clients.toString() + ']}', }; - - await this.submit(`/panel/inbound/addClient`, data) - }, - - async addGroupClient(clients, dbInboundIds) { - const data = [] - dbInboundIds.forEach((dbInboundId, index) => { - data.push({ - id: dbInboundId, - settings: '{"clients": [' + clients[index].toString() + ']}', - }) - }) - - await this.submit(`/panel/inbound/addGroupClient`, data, true) + await this.submit(`/panel/inbound/addClient`, data); }, async updateClient(client, dbInboundId, clientId) { - if (Array.isArray(client) && Array.isArray(dbInboundId) && Array.isArray(clientId)){ - const data = [] - client.forEach((client, index) => { - data.push({ - clientId: clientId[index], - inbound: { - id: dbInboundId[index], - settings: '{"clients": [' + client.toString() + ']}', - } - }) - }) - await this.submit(`/panel/inbound/updateClients`, data, true); - }else{ - const data = { - id: dbInboundId, - settings: '{"clients": [' + client.toString() + ']}', - }; - await this.submit(`/panel/inbound/updateClient/${clientId}`, data); - } + const data = { + id: dbInboundId, + settings: '{"clients": [' + client.toString() + ']}', + }; + await this.submit(`/panel/inbound/updateClient/${clientId}`, data); }, resetTraffic(dbInboundId) { dbInbound = this.dbInbounds.find(row => row.id === dbInboundId); @@ -1051,8 +1001,8 @@ checkFallback(dbInbound) { newDbInbound = new DBInbound(dbInbound); if (dbInbound.listen.startsWith("@")){ - rootInbound = this.inbounds.find((i) => - i.isTcp && + rootInbound = this.inbounds.find((i) => + i.isTcp && ['trojan','vless'].includes(i.protocol) && i.settings.fallbacks.find(f => f.dest === dbInbound.listen) ); @@ -1068,10 +1018,10 @@ } return newDbInbound; }, - showQrcode(dbInboundId, client, isJustSub = false) { + showQrcode(dbInboundId, client) { dbInbound = this.dbInbounds.find(row => row.id === dbInboundId); newDbInbound = this.checkFallback(dbInbound); - qrModal.show('{{ i18n "qrCode"}}', newDbInbound, client, isJustSub); + qrModal.show('{{ i18n "qrCode"}}', newDbInbound, client); }, showInfo(dbInboundId, client) { dbInbound = this.dbInbounds.find(row => row.id === dbInboundId); @@ -1100,8 +1050,8 @@ await this.updateClient(clients[index], dbInboundId, clientId); this.loading(false); }, - async submit(url, data, isJson = false) { - const msg = isJson ? await HttpUtil.postWithModalJson(url, data) : await HttpUtil.postWithModal(url, data); + async submit(url, data) { + const msg = await HttpUtil.postWithModal(url, data); if (msg.success) { await this.getDBInbounds(); } @@ -1243,7 +1193,7 @@ value: '', okText: '{{ i18n "pages.inbounds.import" }}', confirm: async (dbInboundText) => { - await this.submit('/panel/inbound/import', {data: dbInboundText}); + await this.submit('/panel/inbound/import', {data: dbInboundText}, promptModal); promptModal.close(); }, }); diff --git a/web/translation/translate.en_US.toml b/web/translation/translate.en_US.toml index 0b7ac5b2..7e0f26c5 100644 --- a/web/translation/translate.en_US.toml +++ b/web/translation/translate.en_US.toml @@ -181,12 +181,9 @@ "exportInbound" = "Export Inbound" "import" = "Import" "importInbound" = "Import an Inbound" -"isGroupEdit" = "Group editing" -"isGroupEditDesc" = "All clients with the same subscription are edited" [pages.client] "add" = "Add Client" -"groupAdd" = "Add subscription user" "edit" = "Edit Client" "submitAdd" = "Add Client" "submitEdit" = "Save Changes" diff --git a/web/translation/translate.es_ES.toml b/web/translation/translate.es_ES.toml index 926325cf..6c817a30 100644 --- a/web/translation/translate.es_ES.toml +++ b/web/translation/translate.es_ES.toml @@ -181,12 +181,9 @@ "exportInbound" = "Exportación entrante" "import" = "Importar" "importInbound" = "Importar un entrante" -"isGroupEdit" = "Edición de grupo" -"isGroupEditDesc" = "Se editan todos los usuarios con la misma suscripción" [pages.client] "add" = "Agregar Cliente" -"groupAdd" = "Agregar usuario de suscripción" "edit" = "Editar Cliente" "submitAdd" = "Agregar Cliente" "submitEdit" = "Guardar Cambios" diff --git a/web/translation/translate.fa_IR.toml b/web/translation/translate.fa_IR.toml index 17e6facc..11b66788 100644 --- a/web/translation/translate.fa_IR.toml +++ b/web/translation/translate.fa_IR.toml @@ -181,12 +181,9 @@ "exportInbound" = "استخراج ورودی" "import" = "افزودن" "importInbound" = "افزودن یک ورودی" -"isGroupEdit" = "ویرایش گروهی" -"isGroupEditDesc" = "تمامی کاربران با سابسکریپشن یکسان ویرایش می‌شوند" [pages.client] "add" = "کاربر جدید" -"groupAdd" = "کاربر جدید سابسکریپشن" "edit" = "ویرایش کاربر" "submitAdd" = "اضافه کردن" "submitEdit" = "ذخیره تغییرات" diff --git a/web/translation/translate.ru_RU.toml b/web/translation/translate.ru_RU.toml index c567f38b..2e663e4c 100644 --- a/web/translation/translate.ru_RU.toml +++ b/web/translation/translate.ru_RU.toml @@ -181,12 +181,9 @@ "exportInbound" = "Экспорт входящих" "import" = "Импортировать" "importInbound" = "Импортировать входящее сообщение" -"isGroupEdit" = "Редактирование группы" -"isGroupEditDesc" = "Редактируются все пользователи с одной подпиской" [pages.client] "add" = "Добавить пользователя" -"groupAdd" = "Добавить пользователя подписки" "edit" = "Редактировать пользователя" "submitAdd" = "Добавить пользователя" "submitEdit" = "Сохранить изменения" diff --git a/web/translation/translate.vi_VN.toml b/web/translation/translate.vi_VN.toml index 5876a5b4..4c40adca 100644 --- a/web/translation/translate.vi_VN.toml +++ b/web/translation/translate.vi_VN.toml @@ -181,12 +181,9 @@ "exportInbound" = "Xuất nhập khẩu" "import" = "Nhập" "importInbound" = "Nhập inbound" -"isGroupEdit" = "Chỉnh sửa nhóm" -"isGroupEditDesc" = "Tất cả người dùng có cùng đăng ký đều được chỉnh sửa" [pages.client] "add" = "Thêm người dùng" -"groupAdd" = "Thêm người dùng đăng ký" "edit" = "Chỉnh sửa người dùng" "submitAdd" = "Thêm" "submitEdit" = "Lưu thay đổi" diff --git a/web/translation/translate.zh_Hans.toml b/web/translation/translate.zh_Hans.toml index 29dc8f4c..a8cd3aff 100644 --- a/web/translation/translate.zh_Hans.toml +++ b/web/translation/translate.zh_Hans.toml @@ -181,12 +181,9 @@ "exportInbound" = "出口 入境" "import"="导入" "importInbound" = "导入入站" -"isGroupEdit" = "分组编辑" -"isGroupEditDesc" = "编辑具有相同订阅的所有用户" [pages.client] "add" = "添加客户端" -"groupAdd" = "添加订阅用户" "edit" = "编辑客户端" "submitAdd" = "添加客户端" "submitEdit" = "保存修改" From e04283c1fb3e56867c7922bab5f3d7edb29fe13c Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Sat, 27 Jan 2024 12:56:10 +0330 Subject: [PATCH 26/26] remove multi protocol script --- .github/workflows/docker.yml | 4 +-- DockerInit.sh | 10 +------ Dockerfile | 12 ++++---- README.md | 17 +++++++++--- install.sh | 4 +-- x-ui.sh | 54 ++++++++---------------------------- 6 files changed, 35 insertions(+), 66 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 1115de37..a7a8c790 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -18,7 +18,7 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3.0.0 - - name: Log in to GitHub Container Registry + - name: Login to GHCR uses: docker/login-action@v3.0.0 with: registry: ghcr.io @@ -38,4 +38,4 @@ jobs: push: ${{ github.event_name != 'pull_request' }} platforms: linux/amd64, linux/arm64/v8, linux/arm/v7, linux/arm/v6, linux/386 tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} + labels: ${{ steps.meta.outputs.labels }} \ No newline at end of file diff --git a/DockerInit.sh b/DockerInit.sh index bbfcb04e..a2224999 100755 --- a/DockerInit.sh +++ b/DockerInit.sh @@ -1,5 +1,4 @@ #!/bin/sh - case $1 in amd64) ARCH="64" @@ -21,28 +20,21 @@ case $1 in ARCH="arm32-v6" FNAME="armv6" ;; - armv5) - ARCH="arm32-v5" - FNAME="armv5" - ;; *) ARCH="64" FNAME="amd64" ;; esac - - mkdir -p build/bin cd build/bin - wget "https://github.com/XTLS/Xray-core/releases/download/v1.8.7/Xray-linux-${ARCH}.zip" unzip "Xray-linux-${ARCH}.zip" rm -f "Xray-linux-${ARCH}.zip" geoip.dat geosite.dat mv xray "xray-linux-${FNAME}" - wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat wget -O geoip_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geoip.dat 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 diff --git a/Dockerfile b/Dockerfile index 951d463b..7db1a175 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,11 +1,9 @@ # ======================================================== # Stage: Builder # ======================================================== -FROM --platform=$BUILDPLATFORM golang:1.21-alpine AS builder +FROM golang:1.21-alpine AS builder WORKDIR /app ARG TARGETARCH -ENV CGO_ENABLED=1 -ENV CGO_CFLAGS="-D_LARGEFILE64_SOURCE" RUN apk --no-cache --update add \ build-base \ @@ -15,6 +13,8 @@ RUN apk --no-cache --update add \ COPY . . +ENV CGO_ENABLED=1 +ENV CGO_CFLAGS="-D_LARGEFILE64_SOURCE" RUN go build -o build/x-ui main.go RUN ./DockerInit.sh "$TARGETARCH" @@ -30,9 +30,9 @@ RUN apk add --no-cache --update \ tzdata \ fail2ban -COPY --from=builder /app/build/ /app/ -COPY --from=builder /app/DockerEntrypoint.sh /app/ -COPY --from=builder /app/x-ui.sh /usr/bin/x-ui +COPY --from=builder /app/build/ /app/ +COPY --from=builder /app/DockerEntrypoint.sh /app/ +COPY --from=builder /app/x-ui.sh /usr/bin/x-ui # Configure fail2ban RUN rm -f /etc/fail2ban/jail.d/alpine-ssh.conf \ diff --git a/README.md b/README.md index 389d6d1d..ec1e5a15 100644 --- a/README.md +++ b/README.md @@ -106,10 +106,19 @@ systemctl restart x-ui update to latest version ```sh - cd 3x-ui - docker compose down - docker compose pull 3x-ui - docker compose up -d + cd 3x-ui + docker compose down + docker compose pull 3x-ui + docker compose up -d + ``` + +remove 3x-ui from docker + + ```sh + docker stop 3x-ui + docker rm 3x-ui + cd -- + rm -r 3x-ui ``` diff --git a/install.sh b/install.sh index ae489c2a..9f46d4df 100644 --- a/install.sh +++ b/install.sh @@ -123,9 +123,9 @@ config_after_install() { echo -e "${green}username:${usernameTemp}${plain}" echo -e "${green}password:${passwordTemp}${plain}" echo -e "###############################################" - echo -e "${red}if you forgot your login info,you can type x-ui and then type 7 to check after installation${plain}" + echo -e "${red}if you forgot your login info,you can type x-ui and then type 8 to check after installation${plain}" else - echo -e "${red} this is your upgrade,will keep old settings,if you forgot your login info,you can type x-ui and then type 7 to check${plain}" + echo -e "${red} this is your upgrade,will keep old settings,if you forgot your login info,you can type x-ui and then type 8 to check${plain}" fi fi /usr/local/x-ui/x-ui migrate diff --git a/x-ui.sh b/x-ui.sh index c7331e6d..ffb20f06 100644 --- a/x-ui.sh +++ b/x-ui.sh @@ -806,34 +806,6 @@ warp_cloudflare() { esac } -multi_protocol() { - echo "This script only supports Vless and Vmess. if you use another protocols, DON'T INSTALL or get backup first! " - echo -e "${green}\t1.${plain} Install Multi Protocol Script" - echo -e "${green}\t2.${plain} Uninstall" - echo -e "${green}\t3.${plain} Start Service" - echo -e "${green}\t4.${plain} Stop Service" - echo -e "${green}\t0.${plain} Back to Main Menu" - read -p "Choose an option: " choice - case "$choice" in - 0) - show_menu - ;; - 1) - bash <(curl -Ls https://raw.githubusercontent.com/M4mmad/3xui-multi-protocol/master/install.sh --ipv4) - ;; - 2) - bash <(curl -Ls https://raw.githubusercontent.com/M4mmad/3xui-multi-protocol/master/unistall.sh --ipv4) - ;; - 3) - systemctl start 3xui-multi-protocol - ;; - 4) - systemctl stop 3xui-multi-protocol - ;; - *) echo "Invalid choice" ;; - esac -} - run_speedtest() { # Check if Speedtest is already installed if ! command -v speedtest &>/dev/null; then @@ -1145,22 +1117,21 @@ show_menu() { ${green}12.${plain} Check Status ${green}13.${plain} Check Logs ———————————————— - ${green}14.${plain} Enable x-ui On System Startup - ${green}15.${plain} Disable x-ui On System Startup + ${green}14.${plain} Enable Autostart + ${green}15.${plain} Disable Autostart ———————————————— ${green}16.${plain} SSL Certificate Management ${green}17.${plain} Cloudflare SSL Certificate ${green}18.${plain} IP Limit Management ${green}19.${plain} WARP Management - ${green}20.${plain} Multi Protocol Management ———————————————— - ${green}21.${plain} Enable BBR - ${green}22.${plain} Update Geo Files - ${green}23.${plain} Active Firewall and open ports - ${green}24.${plain} Speedtest by Ookla + ${green}20.${plain} Enable BBR + ${green}21.${plain} Update Geo Files + ${green}22.${plain} Active Firewall and open ports + ${green}23.${plain} Speedtest by Ookla " show_status - echo && read -p "Please enter your selection [0-24]: " num + echo && read -p "Please enter your selection [0-23]: " num case "${num}" in 0) @@ -1224,22 +1195,19 @@ show_menu() { warp_cloudflare ;; 20) - multi_protocol - ;; - 21) enable_bbr ;; - 22) + 21) update_geo ;; - 23) + 22) open_ports ;; - 24) + 23) run_speedtest ;; *) - LOGE "Please enter the correct number [0-24]" + LOGE "Please enter the correct number [0-23]" ;; esac }