mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-05-08 14:36:13 +00:00
TLS: Remove ECH Force Query
This commit is contained in:
354
install.sh
354
install.sh
@@ -18,7 +18,7 @@ xui_service="${XUI_SERVICE:=/etc/systemd/system}"
|
|||||||
if [[ -f /etc/os-release ]]; then
|
if [[ -f /etc/os-release ]]; then
|
||||||
source /etc/os-release
|
source /etc/os-release
|
||||||
release=$ID
|
release=$ID
|
||||||
elif [[ -f /usr/lib/os-release ]]; then
|
elif [[ -f /usr/lib/os-release ]]; then
|
||||||
source /usr/lib/os-release
|
source /usr/lib/os-release
|
||||||
release=$ID
|
release=$ID
|
||||||
else
|
else
|
||||||
@@ -59,16 +59,16 @@ is_domain() {
|
|||||||
# Port helpers
|
# Port helpers
|
||||||
is_port_in_use() {
|
is_port_in_use() {
|
||||||
local port="$1"
|
local port="$1"
|
||||||
if command -v ss >/dev/null 2>&1; then
|
if command -v ss > /dev/null 2>&1; then
|
||||||
ss -ltn 2>/dev/null | awk -v p=":${port}$" '$4 ~ p {exit 0} END {exit 1}'
|
ss -ltn 2> /dev/null | awk -v p=":${port}$" '$4 ~ p {exit 0} END {exit 1}'
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
if command -v netstat >/dev/null 2>&1; then
|
if command -v netstat > /dev/null 2>&1; then
|
||||||
netstat -lnt 2>/dev/null | awk -v p=":${port} " '$4 ~ p {exit 0} END {exit 1}'
|
netstat -lnt 2> /dev/null | awk -v p=":${port} " '$4 ~ p {exit 0} END {exit 1}'
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
if command -v lsof >/dev/null 2>&1; then
|
if command -v lsof > /dev/null 2>&1; then
|
||||||
lsof -nP -iTCP:${port} -sTCP:LISTEN >/dev/null 2>&1 && return 0
|
lsof -nP -iTCP:${port} -sTCP:LISTEN > /dev/null 2>&1 && return 0
|
||||||
fi
|
fi
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
@@ -77,35 +77,35 @@ install_base() {
|
|||||||
case "${release}" in
|
case "${release}" in
|
||||||
ubuntu | debian | armbian)
|
ubuntu | debian | armbian)
|
||||||
apt-get update && apt-get install -y -q cron curl tar tzdata socat ca-certificates openssl
|
apt-get update && apt-get install -y -q cron curl tar tzdata socat ca-certificates openssl
|
||||||
;;
|
;;
|
||||||
fedora | amzn | virtuozzo | rhel | almalinux | rocky | ol)
|
fedora | amzn | virtuozzo | rhel | almalinux | rocky | ol)
|
||||||
dnf -y update && dnf install -y -q cronie curl tar tzdata socat ca-certificates openssl
|
dnf -y update && dnf install -y -q cronie curl tar tzdata socat ca-certificates openssl
|
||||||
;;
|
;;
|
||||||
centos)
|
centos)
|
||||||
if [[ "${VERSION_ID}" =~ ^7 ]]; then
|
if [[ "${VERSION_ID}" =~ ^7 ]]; then
|
||||||
yum -y update && yum install -y cronie curl tar tzdata socat ca-certificates openssl
|
yum -y update && yum install -y cronie curl tar tzdata socat ca-certificates openssl
|
||||||
else
|
else
|
||||||
dnf -y update && dnf install -y -q cronie curl tar tzdata socat ca-certificates openssl
|
dnf -y update && dnf install -y -q cronie curl tar tzdata socat ca-certificates openssl
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
arch | manjaro | parch)
|
arch | manjaro | parch)
|
||||||
pacman -Syu && pacman -Syu --noconfirm cronie curl tar tzdata socat ca-certificates openssl
|
pacman -Syu && pacman -Syu --noconfirm cronie curl tar tzdata socat ca-certificates openssl
|
||||||
;;
|
;;
|
||||||
opensuse-tumbleweed | opensuse-leap)
|
opensuse-tumbleweed | opensuse-leap)
|
||||||
zypper refresh && zypper -q install -y cron curl tar timezone socat ca-certificates openssl
|
zypper refresh && zypper -q install -y cron curl tar timezone socat ca-certificates openssl
|
||||||
;;
|
;;
|
||||||
alpine)
|
alpine)
|
||||||
apk update && apk add dcron curl tar tzdata socat ca-certificates openssl
|
apk update && apk add dcron curl tar tzdata socat ca-certificates openssl
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
apt-get update && apt-get install -y -q cron curl tar tzdata socat ca-certificates openssl
|
apt-get update && apt-get install -y -q cron curl tar tzdata socat ca-certificates openssl
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
gen_random_string() {
|
gen_random_string() {
|
||||||
local length="$1"
|
local length="$1"
|
||||||
openssl rand -base64 $(( length * 2 )) \
|
openssl rand -base64 $((length * 2)) \
|
||||||
| tr -dc 'a-zA-Z0-9' \
|
| tr -dc 'a-zA-Z0-9' \
|
||||||
| head -c "$length"
|
| head -c "$length"
|
||||||
}
|
}
|
||||||
@@ -113,7 +113,7 @@ gen_random_string() {
|
|||||||
install_acme() {
|
install_acme() {
|
||||||
echo -e "${green}Installing acme.sh for SSL certificate management...${plain}"
|
echo -e "${green}Installing acme.sh for SSL certificate management...${plain}"
|
||||||
cd ~ || return 1
|
cd ~ || return 1
|
||||||
curl -s https://get.acme.sh | sh >/dev/null 2>&1
|
curl -s https://get.acme.sh | sh > /dev/null 2>&1
|
||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ]; then
|
||||||
echo -e "${red}Failed to install acme.sh${plain}"
|
echo -e "${red}Failed to install acme.sh${plain}"
|
||||||
return 1
|
return 1
|
||||||
@@ -132,7 +132,7 @@ setup_ssl_certificate() {
|
|||||||
echo -e "${green}Setting up SSL certificate...${plain}"
|
echo -e "${green}Setting up SSL certificate...${plain}"
|
||||||
|
|
||||||
# Check if acme.sh is installed
|
# Check if acme.sh is installed
|
||||||
if ! command -v ~/.acme.sh/acme.sh &>/dev/null; then
|
if ! command -v ~/.acme.sh/acme.sh &> /dev/null; then
|
||||||
install_acme
|
install_acme
|
||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ]; then
|
||||||
echo -e "${yellow}Failed to install acme.sh, skipping SSL setup${plain}"
|
echo -e "${yellow}Failed to install acme.sh, skipping SSL setup${plain}"
|
||||||
@@ -148,14 +148,14 @@ setup_ssl_certificate() {
|
|||||||
echo -e "${green}Issuing SSL certificate for ${domain}...${plain}"
|
echo -e "${green}Issuing SSL certificate for ${domain}...${plain}"
|
||||||
echo -e "${yellow}Note: Port 80 must be open and accessible from the internet${plain}"
|
echo -e "${yellow}Note: Port 80 must be open and accessible from the internet${plain}"
|
||||||
|
|
||||||
~/.acme.sh/acme.sh --set-default-ca --server letsencrypt --force >/dev/null 2>&1
|
~/.acme.sh/acme.sh --set-default-ca --server letsencrypt --force > /dev/null 2>&1
|
||||||
~/.acme.sh/acme.sh --issue -d ${domain} --listen-v6 --standalone --httpport 80 --force
|
~/.acme.sh/acme.sh --issue -d ${domain} --listen-v6 --standalone --httpport 80 --force
|
||||||
|
|
||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ]; then
|
||||||
echo -e "${yellow}Failed to issue certificate for ${domain}${plain}"
|
echo -e "${yellow}Failed to issue certificate for ${domain}${plain}"
|
||||||
echo -e "${yellow}Please ensure port 80 is open and try again later with: x-ui${plain}"
|
echo -e "${yellow}Please ensure port 80 is open and try again later with: x-ui${plain}"
|
||||||
rm -rf ~/.acme.sh/${domain} 2>/dev/null
|
rm -rf ~/.acme.sh/${domain} 2> /dev/null
|
||||||
rm -rf "$certPath" 2>/dev/null
|
rm -rf "$certPath" 2> /dev/null
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -163,7 +163,7 @@ setup_ssl_certificate() {
|
|||||||
~/.acme.sh/acme.sh --installcert -d ${domain} \
|
~/.acme.sh/acme.sh --installcert -d ${domain} \
|
||||||
--key-file /root/cert/${domain}/privkey.pem \
|
--key-file /root/cert/${domain}/privkey.pem \
|
||||||
--fullchain-file /root/cert/${domain}/fullchain.pem \
|
--fullchain-file /root/cert/${domain}/fullchain.pem \
|
||||||
--reloadcmd "systemctl restart x-ui" >/dev/null 2>&1
|
--reloadcmd "systemctl restart x-ui" > /dev/null 2>&1
|
||||||
|
|
||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ]; then
|
||||||
echo -e "${yellow}Failed to install certificate${plain}"
|
echo -e "${yellow}Failed to install certificate${plain}"
|
||||||
@@ -171,17 +171,17 @@ setup_ssl_certificate() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Enable auto-renew
|
# Enable auto-renew
|
||||||
~/.acme.sh/acme.sh --upgrade --auto-upgrade >/dev/null 2>&1
|
~/.acme.sh/acme.sh --upgrade --auto-upgrade > /dev/null 2>&1
|
||||||
# Secure permissions: private key readable only by owner
|
# Secure permissions: private key readable only by owner
|
||||||
chmod 600 $certPath/privkey.pem 2>/dev/null
|
chmod 600 $certPath/privkey.pem 2> /dev/null
|
||||||
chmod 644 $certPath/fullchain.pem 2>/dev/null
|
chmod 644 $certPath/fullchain.pem 2> /dev/null
|
||||||
|
|
||||||
# Set certificate for panel
|
# Set certificate for panel
|
||||||
local webCertFile="/root/cert/${domain}/fullchain.pem"
|
local webCertFile="/root/cert/${domain}/fullchain.pem"
|
||||||
local webKeyFile="/root/cert/${domain}/privkey.pem"
|
local webKeyFile="/root/cert/${domain}/privkey.pem"
|
||||||
|
|
||||||
if [[ -f "$webCertFile" && -f "$webKeyFile" ]]; then
|
if [[ -f "$webCertFile" && -f "$webKeyFile" ]]; then
|
||||||
${xui_folder}/x-ui cert -webCert "$webCertFile" -webCertKey "$webKeyFile" >/dev/null 2>&1
|
${xui_folder}/x-ui cert -webCert "$webCertFile" -webCertKey "$webKeyFile" > /dev/null 2>&1
|
||||||
echo -e "${green}SSL certificate installed and configured successfully!${plain}"
|
echo -e "${green}SSL certificate installed and configured successfully!${plain}"
|
||||||
return 0
|
return 0
|
||||||
else
|
else
|
||||||
@@ -194,14 +194,14 @@ setup_ssl_certificate() {
|
|||||||
# Requires acme.sh and port 80 open for HTTP-01 challenge
|
# Requires acme.sh and port 80 open for HTTP-01 challenge
|
||||||
setup_ip_certificate() {
|
setup_ip_certificate() {
|
||||||
local ipv4="$1"
|
local ipv4="$1"
|
||||||
local ipv6="$2" # optional
|
local ipv6="$2" # optional
|
||||||
|
|
||||||
echo -e "${green}Setting up Let's Encrypt IP certificate (shortlived profile)...${plain}"
|
echo -e "${green}Setting up Let's Encrypt IP certificate (shortlived profile)...${plain}"
|
||||||
echo -e "${yellow}Note: IP certificates are valid for ~6 days and will auto-renew.${plain}"
|
echo -e "${yellow}Note: IP certificates are valid for ~6 days and will auto-renew.${plain}"
|
||||||
echo -e "${yellow}Default listener is port 80. If you choose another port, ensure external port 80 forwards to it.${plain}"
|
echo -e "${yellow}Default listener is port 80. If you choose another port, ensure external port 80 forwards to it.${plain}"
|
||||||
|
|
||||||
# Check for acme.sh
|
# Check for acme.sh
|
||||||
if ! command -v ~/.acme.sh/acme.sh &>/dev/null; then
|
if ! command -v ~/.acme.sh/acme.sh &> /dev/null; then
|
||||||
install_acme
|
install_acme
|
||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ]; then
|
||||||
echo -e "${red}Failed to install acme.sh${plain}"
|
echo -e "${red}Failed to install acme.sh${plain}"
|
||||||
@@ -273,7 +273,7 @@ setup_ip_certificate() {
|
|||||||
|
|
||||||
# Issue certificate with shortlived profile
|
# Issue certificate with shortlived profile
|
||||||
echo -e "${green}Issuing IP certificate for ${ipv4}...${plain}"
|
echo -e "${green}Issuing IP certificate for ${ipv4}...${plain}"
|
||||||
~/.acme.sh/acme.sh --set-default-ca --server letsencrypt --force >/dev/null 2>&1
|
~/.acme.sh/acme.sh --set-default-ca --server letsencrypt --force > /dev/null 2>&1
|
||||||
|
|
||||||
~/.acme.sh/acme.sh --issue \
|
~/.acme.sh/acme.sh --issue \
|
||||||
${domain_args} \
|
${domain_args} \
|
||||||
@@ -288,9 +288,9 @@ setup_ip_certificate() {
|
|||||||
echo -e "${red}Failed to issue IP certificate${plain}"
|
echo -e "${red}Failed to issue IP certificate${plain}"
|
||||||
echo -e "${yellow}Please ensure port ${WebPort} is reachable (or forwarded from external port 80)${plain}"
|
echo -e "${yellow}Please ensure port ${WebPort} is reachable (or forwarded from external port 80)${plain}"
|
||||||
# Cleanup acme.sh data for both IPv4 and IPv6 if specified
|
# Cleanup acme.sh data for both IPv4 and IPv6 if specified
|
||||||
rm -rf ~/.acme.sh/${ipv4} 2>/dev/null
|
rm -rf ~/.acme.sh/${ipv4} 2> /dev/null
|
||||||
[[ -n "$ipv6" ]] && rm -rf ~/.acme.sh/${ipv6} 2>/dev/null
|
[[ -n "$ipv6" ]] && rm -rf ~/.acme.sh/${ipv6} 2> /dev/null
|
||||||
rm -rf ${certDir} 2>/dev/null
|
rm -rf ${certDir} 2> /dev/null
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -308,20 +308,20 @@ setup_ip_certificate() {
|
|||||||
if [[ ! -f "${certDir}/fullchain.pem" || ! -f "${certDir}/privkey.pem" ]]; then
|
if [[ ! -f "${certDir}/fullchain.pem" || ! -f "${certDir}/privkey.pem" ]]; then
|
||||||
echo -e "${red}Certificate files not found after installation${plain}"
|
echo -e "${red}Certificate files not found after installation${plain}"
|
||||||
# Cleanup acme.sh data for both IPv4 and IPv6 if specified
|
# Cleanup acme.sh data for both IPv4 and IPv6 if specified
|
||||||
rm -rf ~/.acme.sh/${ipv4} 2>/dev/null
|
rm -rf ~/.acme.sh/${ipv4} 2> /dev/null
|
||||||
[[ -n "$ipv6" ]] && rm -rf ~/.acme.sh/${ipv6} 2>/dev/null
|
[[ -n "$ipv6" ]] && rm -rf ~/.acme.sh/${ipv6} 2> /dev/null
|
||||||
rm -rf ${certDir} 2>/dev/null
|
rm -rf ${certDir} 2> /dev/null
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo -e "${green}Certificate files installed successfully${plain}"
|
echo -e "${green}Certificate files installed successfully${plain}"
|
||||||
|
|
||||||
# Enable auto-upgrade for acme.sh (ensures cron job runs)
|
# Enable auto-upgrade for acme.sh (ensures cron job runs)
|
||||||
~/.acme.sh/acme.sh --upgrade --auto-upgrade >/dev/null 2>&1
|
~/.acme.sh/acme.sh --upgrade --auto-upgrade > /dev/null 2>&1
|
||||||
|
|
||||||
# Secure permissions: private key readable only by owner
|
# Secure permissions: private key readable only by owner
|
||||||
chmod 600 ${certDir}/privkey.pem 2>/dev/null
|
chmod 600 ${certDir}/privkey.pem 2> /dev/null
|
||||||
chmod 644 ${certDir}/fullchain.pem 2>/dev/null
|
chmod 644 ${certDir}/fullchain.pem 2> /dev/null
|
||||||
|
|
||||||
# Configure panel to use the certificate
|
# Configure panel to use the certificate
|
||||||
echo -e "${green}Setting certificate paths for the panel...${plain}"
|
echo -e "${green}Setting certificate paths for the panel...${plain}"
|
||||||
@@ -348,7 +348,7 @@ ssl_cert_issue() {
|
|||||||
local existing_port=$(${xui_folder}/x-ui setting -show true | grep 'port:' | awk -F': ' '{print $2}' | tr -d '[:space:]')
|
local existing_port=$(${xui_folder}/x-ui setting -show true | grep 'port:' | awk -F': ' '{print $2}' | tr -d '[:space:]')
|
||||||
|
|
||||||
# check for acme.sh first
|
# check for acme.sh first
|
||||||
if ! command -v ~/.acme.sh/acme.sh &>/dev/null; then
|
if ! command -v ~/.acme.sh/acme.sh &> /dev/null; then
|
||||||
echo "acme.sh could not be found. Installing now..."
|
echo "acme.sh could not be found. Installing now..."
|
||||||
cd ~ || return 1
|
cd ~ || return 1
|
||||||
curl -s https://get.acme.sh | sh
|
curl -s https://get.acme.sh | sh
|
||||||
@@ -364,7 +364,7 @@ ssl_cert_issue() {
|
|||||||
local domain=""
|
local domain=""
|
||||||
while true; do
|
while true; do
|
||||||
read -rp "Please enter your domain name: " domain
|
read -rp "Please enter your domain name: " domain
|
||||||
domain="${domain// /}" # Trim whitespace
|
domain="${domain// /}" # Trim whitespace
|
||||||
|
|
||||||
if [[ -z "$domain" ]]; then
|
if [[ -z "$domain" ]]; then
|
||||||
echo -e "${red}Domain name cannot be empty. Please try again.${plain}"
|
echo -e "${red}Domain name cannot be empty. Please try again.${plain}"
|
||||||
@@ -383,9 +383,9 @@ ssl_cert_issue() {
|
|||||||
|
|
||||||
# detect existing certificate and reuse it if present
|
# detect existing certificate and reuse it if present
|
||||||
local cert_exists=0
|
local cert_exists=0
|
||||||
if ~/.acme.sh/acme.sh --list 2>/dev/null | awk '{print $1}' | grep -Fxq "${domain}"; then
|
if ~/.acme.sh/acme.sh --list 2> /dev/null | awk '{print $1}' | grep -Fxq "${domain}"; then
|
||||||
cert_exists=1
|
cert_exists=1
|
||||||
local certInfo=$(~/.acme.sh/acme.sh --list 2>/dev/null | grep -F "${domain}")
|
local certInfo=$(~/.acme.sh/acme.sh --list 2> /dev/null | grep -F "${domain}")
|
||||||
echo -e "${yellow}Existing certificate found for ${domain}, will reuse it.${plain}"
|
echo -e "${yellow}Existing certificate found for ${domain}, will reuse it.${plain}"
|
||||||
[[ -n "${certInfo}" ]] && echo "$certInfo"
|
[[ -n "${certInfo}" ]] && echo "$certInfo"
|
||||||
else
|
else
|
||||||
@@ -412,7 +412,7 @@ ssl_cert_issue() {
|
|||||||
|
|
||||||
# Stop panel temporarily
|
# Stop panel temporarily
|
||||||
echo -e "${yellow}Stopping panel temporarily...${plain}"
|
echo -e "${yellow}Stopping panel temporarily...${plain}"
|
||||||
systemctl stop x-ui 2>/dev/null || rc-service x-ui stop 2>/dev/null
|
systemctl stop x-ui 2> /dev/null || rc-service x-ui stop 2> /dev/null
|
||||||
|
|
||||||
if [[ ${cert_exists} -eq 0 ]]; then
|
if [[ ${cert_exists} -eq 0 ]]; then
|
||||||
# issue the certificate
|
# issue the certificate
|
||||||
@@ -421,7 +421,7 @@ ssl_cert_issue() {
|
|||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ]; then
|
||||||
echo -e "${red}Issuing certificate failed, please check logs.${plain}"
|
echo -e "${red}Issuing certificate failed, please check logs.${plain}"
|
||||||
rm -rf ~/.acme.sh/${domain}
|
rm -rf ~/.acme.sh/${domain}
|
||||||
systemctl start x-ui 2>/dev/null || rc-service x-ui start 2>/dev/null
|
systemctl start x-ui 2> /dev/null || rc-service x-ui start 2> /dev/null
|
||||||
return 1
|
return 1
|
||||||
else
|
else
|
||||||
echo -e "${green}Issuing certificate succeeded, installing certificates...${plain}"
|
echo -e "${green}Issuing certificate succeeded, installing certificates...${plain}"
|
||||||
@@ -441,18 +441,18 @@ ssl_cert_issue() {
|
|||||||
echo -e "${green}\t0.${plain} Keep default reloadcmd"
|
echo -e "${green}\t0.${plain} Keep default reloadcmd"
|
||||||
read -rp "Choose an option: " choice
|
read -rp "Choose an option: " choice
|
||||||
case "$choice" in
|
case "$choice" in
|
||||||
1)
|
1)
|
||||||
echo -e "${green}Reloadcmd is: systemctl reload nginx ; systemctl restart x-ui${plain}"
|
echo -e "${green}Reloadcmd is: systemctl reload nginx ; systemctl restart x-ui${plain}"
|
||||||
reloadCmd="systemctl reload nginx ; systemctl restart x-ui"
|
reloadCmd="systemctl reload nginx ; systemctl restart x-ui"
|
||||||
;;
|
;;
|
||||||
2)
|
2)
|
||||||
echo -e "${yellow}It's recommended to put x-ui restart at the end${plain}"
|
echo -e "${yellow}It's recommended to put x-ui restart at the end${plain}"
|
||||||
read -rp "Please enter your custom reloadcmd: " reloadCmd
|
read -rp "Please enter your custom reloadcmd: " reloadCmd
|
||||||
echo -e "${green}Reloadcmd is: ${reloadCmd}${plain}"
|
echo -e "${green}Reloadcmd is: ${reloadCmd}${plain}"
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
echo -e "${green}Keeping default reloadcmd${plain}"
|
echo -e "${green}Keeping default reloadcmd${plain}"
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -469,14 +469,14 @@ ssl_cert_issue() {
|
|||||||
installWroteFiles=1
|
installWroteFiles=1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -f "/root/cert/${domain}/privkey.pem" && -f "/root/cert/${domain}/fullchain.pem" && ( ${installRc} -eq 0 || ${installWroteFiles} -eq 1 ) ]]; then
|
if [[ -f "/root/cert/${domain}/privkey.pem" && -f "/root/cert/${domain}/fullchain.pem" && (${installRc} -eq 0 || ${installWroteFiles} -eq 1) ]]; then
|
||||||
echo -e "${green}Installing certificate succeeded, enabling auto renew...${plain}"
|
echo -e "${green}Installing certificate succeeded, enabling auto renew...${plain}"
|
||||||
else
|
else
|
||||||
echo -e "${red}Installing certificate failed, exiting.${plain}"
|
echo -e "${red}Installing certificate failed, exiting.${plain}"
|
||||||
if [[ ${cert_exists} -eq 0 ]]; then
|
if [[ ${cert_exists} -eq 0 ]]; then
|
||||||
rm -rf ~/.acme.sh/${domain}
|
rm -rf ~/.acme.sh/${domain}
|
||||||
fi
|
fi
|
||||||
systemctl start x-ui 2>/dev/null || rc-service x-ui start 2>/dev/null
|
systemctl start x-ui 2> /dev/null || rc-service x-ui start 2> /dev/null
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -486,18 +486,18 @@ ssl_cert_issue() {
|
|||||||
echo -e "${yellow}Auto renew setup had issues, certificate details:${plain}"
|
echo -e "${yellow}Auto renew setup had issues, certificate details:${plain}"
|
||||||
ls -lah /root/cert/${domain}/
|
ls -lah /root/cert/${domain}/
|
||||||
# Secure permissions: private key readable only by owner
|
# Secure permissions: private key readable only by owner
|
||||||
chmod 600 $certPath/privkey.pem 2>/dev/null
|
chmod 600 $certPath/privkey.pem 2> /dev/null
|
||||||
chmod 644 $certPath/fullchain.pem 2>/dev/null
|
chmod 644 $certPath/fullchain.pem 2> /dev/null
|
||||||
else
|
else
|
||||||
echo -e "${green}Auto renew succeeded, certificate details:${plain}"
|
echo -e "${green}Auto renew succeeded, certificate details:${plain}"
|
||||||
ls -lah /root/cert/${domain}/
|
ls -lah /root/cert/${domain}/
|
||||||
# Secure permissions: private key readable only by owner
|
# Secure permissions: private key readable only by owner
|
||||||
chmod 600 $certPath/privkey.pem 2>/dev/null
|
chmod 600 $certPath/privkey.pem 2> /dev/null
|
||||||
chmod 644 $certPath/fullchain.pem 2>/dev/null
|
chmod 644 $certPath/fullchain.pem 2> /dev/null
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# start panel
|
# start panel
|
||||||
systemctl start x-ui 2>/dev/null || rc-service x-ui start 2>/dev/null
|
systemctl start x-ui 2> /dev/null || rc-service x-ui start 2> /dev/null
|
||||||
|
|
||||||
# Prompt user to set panel paths after successful certificate installation
|
# Prompt user to set panel paths after successful certificate installation
|
||||||
read -rp "Would you like to set this certificate for the panel? (y/n): " setPanel
|
read -rp "Would you like to set this certificate for the panel? (y/n): " setPanel
|
||||||
@@ -513,7 +513,7 @@ ssl_cert_issue() {
|
|||||||
echo ""
|
echo ""
|
||||||
echo -e "${green}Access URL: https://${domain}:${existing_port}/${existing_webBasePath}${plain}"
|
echo -e "${green}Access URL: https://${domain}:${existing_port}/${existing_webBasePath}${plain}"
|
||||||
echo -e "${yellow}Panel will restart to apply SSL certificate...${plain}"
|
echo -e "${yellow}Panel will restart to apply SSL certificate...${plain}"
|
||||||
systemctl restart x-ui 2>/dev/null || rc-service x-ui restart 2>/dev/null
|
systemctl restart x-ui 2> /dev/null || rc-service x-ui restart 2> /dev/null
|
||||||
else
|
else
|
||||||
echo -e "${red}Error: Certificate or private key file not found for domain: $domain.${plain}"
|
echo -e "${red}Error: Certificate or private key file not found for domain: $domain.${plain}"
|
||||||
fi
|
fi
|
||||||
@@ -528,7 +528,7 @@ ssl_cert_issue() {
|
|||||||
# Sets global `SSL_HOST` to the chosen domain/IP for Access URL usage
|
# Sets global `SSL_HOST` to the chosen domain/IP for Access URL usage
|
||||||
prompt_and_setup_ssl() {
|
prompt_and_setup_ssl() {
|
||||||
local panel_port="$1"
|
local panel_port="$1"
|
||||||
local web_base_path="$2" # expected without leading slash
|
local web_base_path="$2" # expected without leading slash
|
||||||
local server_ip="$3"
|
local server_ip="$3"
|
||||||
|
|
||||||
local ssl_choice=""
|
local ssl_choice=""
|
||||||
@@ -539,7 +539,7 @@ prompt_and_setup_ssl() {
|
|||||||
echo -e "${green}3.${plain} Custom SSL Certificate (Path to existing files)"
|
echo -e "${green}3.${plain} Custom SSL Certificate (Path to existing files)"
|
||||||
echo -e "${blue}Note:${plain} Options 1 & 2 require port 80 open. Option 3 requires manual paths."
|
echo -e "${blue}Note:${plain} Options 1 & 2 require port 80 open. Option 3 requires manual paths."
|
||||||
read -rp "Choose an option (default 2 for IP): " ssl_choice
|
read -rp "Choose an option (default 2 for IP): " ssl_choice
|
||||||
ssl_choice="${ssl_choice// /}" # Trim whitespace
|
ssl_choice="${ssl_choice// /}" # Trim whitespace
|
||||||
|
|
||||||
# Default to 2 (IP cert) if input is empty or invalid (not 1 or 3)
|
# Default to 2 (IP cert) if input is empty or invalid (not 1 or 3)
|
||||||
if [[ "$ssl_choice" != "1" && "$ssl_choice" != "3" ]]; then
|
if [[ "$ssl_choice" != "1" && "$ssl_choice" != "3" ]]; then
|
||||||
@@ -547,116 +547,116 @@ prompt_and_setup_ssl() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
case "$ssl_choice" in
|
case "$ssl_choice" in
|
||||||
1)
|
1)
|
||||||
# User chose Let's Encrypt domain option
|
# User chose Let's Encrypt domain option
|
||||||
echo -e "${green}Using Let's Encrypt for domain certificate...${plain}"
|
echo -e "${green}Using Let's Encrypt for domain certificate...${plain}"
|
||||||
if ssl_cert_issue; then
|
if ssl_cert_issue; then
|
||||||
local cert_domain="${SSL_ISSUED_DOMAIN}"
|
local cert_domain="${SSL_ISSUED_DOMAIN}"
|
||||||
if [[ -z "${cert_domain}" ]]; then
|
if [[ -z "${cert_domain}" ]]; then
|
||||||
cert_domain=$(~/.acme.sh/acme.sh --list 2>/dev/null | tail -1 | awk '{print $1}')
|
cert_domain=$(~/.acme.sh/acme.sh --list 2> /dev/null | tail -1 | awk '{print $1}')
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -n "${cert_domain}" ]]; then
|
if [[ -n "${cert_domain}" ]]; then
|
||||||
SSL_HOST="${cert_domain}"
|
SSL_HOST="${cert_domain}"
|
||||||
echo -e "${green}✓ SSL certificate configured successfully with domain: ${cert_domain}${plain}"
|
echo -e "${green}✓ SSL certificate configured successfully with domain: ${cert_domain}${plain}"
|
||||||
|
else
|
||||||
|
echo -e "${yellow}SSL setup may have completed, but domain extraction failed${plain}"
|
||||||
|
SSL_HOST="${server_ip}"
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
echo -e "${yellow}SSL setup may have completed, but domain extraction failed${plain}"
|
echo -e "${red}SSL certificate setup failed for domain mode.${plain}"
|
||||||
SSL_HOST="${server_ip}"
|
SSL_HOST="${server_ip}"
|
||||||
fi
|
fi
|
||||||
else
|
;;
|
||||||
echo -e "${red}SSL certificate setup failed for domain mode.${plain}"
|
2)
|
||||||
SSL_HOST="${server_ip}"
|
# User chose Let's Encrypt IP certificate option
|
||||||
fi
|
echo -e "${green}Using Let's Encrypt for IP certificate (shortlived profile)...${plain}"
|
||||||
;;
|
|
||||||
2)
|
|
||||||
# User chose Let's Encrypt IP certificate option
|
|
||||||
echo -e "${green}Using Let's Encrypt for IP certificate (shortlived profile)...${plain}"
|
|
||||||
|
|
||||||
# Ask for optional IPv6
|
# Ask for optional IPv6
|
||||||
local ipv6_addr=""
|
local ipv6_addr=""
|
||||||
read -rp "Do you have an IPv6 address to include? (leave empty to skip): " ipv6_addr
|
read -rp "Do you have an IPv6 address to include? (leave empty to skip): " ipv6_addr
|
||||||
ipv6_addr="${ipv6_addr// /}" # Trim whitespace
|
ipv6_addr="${ipv6_addr// /}" # Trim whitespace
|
||||||
|
|
||||||
# Stop panel if running (port 80 needed)
|
# Stop panel if running (port 80 needed)
|
||||||
if [[ $release == "alpine" ]]; then
|
if [[ $release == "alpine" ]]; then
|
||||||
rc-service x-ui stop >/dev/null 2>&1
|
rc-service x-ui stop > /dev/null 2>&1
|
||||||
else
|
|
||||||
systemctl stop x-ui >/dev/null 2>&1
|
|
||||||
fi
|
|
||||||
|
|
||||||
setup_ip_certificate "${server_ip}" "${ipv6_addr}"
|
|
||||||
if [ $? -eq 0 ]; then
|
|
||||||
SSL_HOST="${server_ip}"
|
|
||||||
echo -e "${green}✓ Let's Encrypt IP certificate configured successfully${plain}"
|
|
||||||
else
|
|
||||||
echo -e "${red}✗ IP certificate setup failed. Please check port 80 is open.${plain}"
|
|
||||||
SSL_HOST="${server_ip}"
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
3)
|
|
||||||
# User chose Custom Paths (User Provided) option
|
|
||||||
echo -e "${green}Using custom existing certificate...${plain}"
|
|
||||||
local custom_cert=""
|
|
||||||
local custom_key=""
|
|
||||||
local custom_domain=""
|
|
||||||
|
|
||||||
# 3.1 Request Domain to compose Panel URL later
|
|
||||||
read -rp "Please enter domain name certificate issued for: " custom_domain
|
|
||||||
custom_domain="${custom_domain// /}" # Remove spaces
|
|
||||||
|
|
||||||
# 3.2 Loop for Certificate Path
|
|
||||||
while true; do
|
|
||||||
read -rp "Input certificate path (keywords: .crt / fullchain): " custom_cert
|
|
||||||
# Strip quotes if present
|
|
||||||
custom_cert=$(echo "$custom_cert" | tr -d '"' | tr -d "'")
|
|
||||||
|
|
||||||
if [[ -f "$custom_cert" && -r "$custom_cert" && -s "$custom_cert" ]]; then
|
|
||||||
break
|
|
||||||
elif [[ ! -f "$custom_cert" ]]; then
|
|
||||||
echo -e "${red}Error: File does not exist! Try again.${plain}"
|
|
||||||
elif [[ ! -r "$custom_cert" ]]; then
|
|
||||||
echo -e "${red}Error: File exists but is not readable (check permissions)!${plain}"
|
|
||||||
else
|
else
|
||||||
echo -e "${red}Error: File is empty!${plain}"
|
systemctl stop x-ui > /dev/null 2>&1
|
||||||
fi
|
fi
|
||||||
done
|
|
||||||
|
|
||||||
# 3.3 Loop for Private Key Path
|
setup_ip_certificate "${server_ip}" "${ipv6_addr}"
|
||||||
while true; do
|
if [ $? -eq 0 ]; then
|
||||||
read -rp "Input private key path (keywords: .key / privatekey): " custom_key
|
SSL_HOST="${server_ip}"
|
||||||
# Strip quotes if present
|
echo -e "${green}✓ Let's Encrypt IP certificate configured successfully${plain}"
|
||||||
custom_key=$(echo "$custom_key" | tr -d '"' | tr -d "'")
|
|
||||||
|
|
||||||
if [[ -f "$custom_key" && -r "$custom_key" && -s "$custom_key" ]]; then
|
|
||||||
break
|
|
||||||
elif [[ ! -f "$custom_key" ]]; then
|
|
||||||
echo -e "${red}Error: File does not exist! Try again.${plain}"
|
|
||||||
elif [[ ! -r "$custom_key" ]]; then
|
|
||||||
echo -e "${red}Error: File exists but is not readable (check permissions)!${plain}"
|
|
||||||
else
|
else
|
||||||
echo -e "${red}Error: File is empty!${plain}"
|
echo -e "${red}✗ IP certificate setup failed. Please check port 80 is open.${plain}"
|
||||||
|
SSL_HOST="${server_ip}"
|
||||||
fi
|
fi
|
||||||
done
|
;;
|
||||||
|
3)
|
||||||
|
# User chose Custom Paths (User Provided) option
|
||||||
|
echo -e "${green}Using custom existing certificate...${plain}"
|
||||||
|
local custom_cert=""
|
||||||
|
local custom_key=""
|
||||||
|
local custom_domain=""
|
||||||
|
|
||||||
# 3.4 Apply Settings via x-ui binary
|
# 3.1 Request Domain to compose Panel URL later
|
||||||
${xui_folder}/x-ui cert -webCert "$custom_cert" -webCertKey "$custom_key" >/dev/null 2>&1
|
read -rp "Please enter domain name certificate issued for: " custom_domain
|
||||||
|
custom_domain="${custom_domain// /}" # Remove spaces
|
||||||
|
|
||||||
# Set SSL_HOST for composing Panel URL
|
# 3.2 Loop for Certificate Path
|
||||||
if [[ -n "$custom_domain" ]]; then
|
while true; do
|
||||||
SSL_HOST="$custom_domain"
|
read -rp "Input certificate path (keywords: .crt / fullchain): " custom_cert
|
||||||
else
|
# Strip quotes if present
|
||||||
|
custom_cert=$(echo "$custom_cert" | tr -d '"' | tr -d "'")
|
||||||
|
|
||||||
|
if [[ -f "$custom_cert" && -r "$custom_cert" && -s "$custom_cert" ]]; then
|
||||||
|
break
|
||||||
|
elif [[ ! -f "$custom_cert" ]]; then
|
||||||
|
echo -e "${red}Error: File does not exist! Try again.${plain}"
|
||||||
|
elif [[ ! -r "$custom_cert" ]]; then
|
||||||
|
echo -e "${red}Error: File exists but is not readable (check permissions)!${plain}"
|
||||||
|
else
|
||||||
|
echo -e "${red}Error: File is empty!${plain}"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# 3.3 Loop for Private Key Path
|
||||||
|
while true; do
|
||||||
|
read -rp "Input private key path (keywords: .key / privatekey): " custom_key
|
||||||
|
# Strip quotes if present
|
||||||
|
custom_key=$(echo "$custom_key" | tr -d '"' | tr -d "'")
|
||||||
|
|
||||||
|
if [[ -f "$custom_key" && -r "$custom_key" && -s "$custom_key" ]]; then
|
||||||
|
break
|
||||||
|
elif [[ ! -f "$custom_key" ]]; then
|
||||||
|
echo -e "${red}Error: File does not exist! Try again.${plain}"
|
||||||
|
elif [[ ! -r "$custom_key" ]]; then
|
||||||
|
echo -e "${red}Error: File exists but is not readable (check permissions)!${plain}"
|
||||||
|
else
|
||||||
|
echo -e "${red}Error: File is empty!${plain}"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# 3.4 Apply Settings via x-ui binary
|
||||||
|
${xui_folder}/x-ui cert -webCert "$custom_cert" -webCertKey "$custom_key" > /dev/null 2>&1
|
||||||
|
|
||||||
|
# Set SSL_HOST for composing Panel URL
|
||||||
|
if [[ -n "$custom_domain" ]]; then
|
||||||
|
SSL_HOST="$custom_domain"
|
||||||
|
else
|
||||||
|
SSL_HOST="${server_ip}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${green}✓ Custom certificate paths applied.${plain}"
|
||||||
|
echo -e "${yellow}Note: You are responsible for renewing these files externally.${plain}"
|
||||||
|
|
||||||
|
systemctl restart x-ui > /dev/null 2>&1 || rc-service x-ui restart > /dev/null 2>&1
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo -e "${red}Invalid option. Skipping SSL setup.${plain}"
|
||||||
SSL_HOST="${server_ip}"
|
SSL_HOST="${server_ip}"
|
||||||
fi
|
;;
|
||||||
|
|
||||||
echo -e "${green}✓ Custom certificate paths applied.${plain}"
|
|
||||||
echo -e "${yellow}Note: You are responsible for renewing these files externally.${plain}"
|
|
||||||
|
|
||||||
systemctl restart x-ui >/dev/null 2>&1 || rc-service x-ui restart >/dev/null 2>&1
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
echo -e "${red}Invalid option. Skipping SSL setup.${plain}"
|
|
||||||
SSL_HOST="${server_ip}"
|
|
||||||
;;
|
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -676,7 +676,7 @@ config_after_install() {
|
|||||||
)
|
)
|
||||||
local server_ip=""
|
local server_ip=""
|
||||||
for ip_address in "${URL_lists[@]}"; do
|
for ip_address in "${URL_lists[@]}"; do
|
||||||
local response=$(curl -s -w "\n%{http_code}" --max-time 3 "${ip_address}" 2>/dev/null)
|
local response=$(curl -s -w "\n%{http_code}" --max-time 3 "${ip_address}" 2> /dev/null)
|
||||||
local http_code=$(echo "$response" | tail -n1)
|
local http_code=$(echo "$response" | tail -n1)
|
||||||
local ip_result=$(echo "$response" | head -n-1 | tr -d '[:space:]')
|
local ip_result=$(echo "$response" | head -n-1 | tr -d '[:space:]')
|
||||||
if [[ "${http_code}" == "200" && -n "${ip_result}" ]]; then
|
if [[ "${http_code}" == "200" && -n "${ip_result}" ]]; then
|
||||||
@@ -886,7 +886,7 @@ install_x-ui() {
|
|||||||
|
|
||||||
if [ -f "x-ui.service" ]; then
|
if [ -f "x-ui.service" ]; then
|
||||||
echo -e "${green}Found x-ui.service in extracted files, installing...${plain}"
|
echo -e "${green}Found x-ui.service in extracted files, installing...${plain}"
|
||||||
cp -f x-ui.service ${xui_service}/ >/dev/null 2>&1
|
cp -f x-ui.service ${xui_service}/ > /dev/null 2>&1
|
||||||
if [[ $? -eq 0 ]]; then
|
if [[ $? -eq 0 ]]; then
|
||||||
service_installed=true
|
service_installed=true
|
||||||
fi
|
fi
|
||||||
@@ -897,30 +897,30 @@ install_x-ui() {
|
|||||||
ubuntu | debian | armbian)
|
ubuntu | debian | armbian)
|
||||||
if [ -f "x-ui.service.debian" ]; then
|
if [ -f "x-ui.service.debian" ]; then
|
||||||
echo -e "${green}Found x-ui.service.debian in extracted files, installing...${plain}"
|
echo -e "${green}Found x-ui.service.debian in extracted files, installing...${plain}"
|
||||||
cp -f x-ui.service.debian ${xui_service}/x-ui.service >/dev/null 2>&1
|
cp -f x-ui.service.debian ${xui_service}/x-ui.service > /dev/null 2>&1
|
||||||
if [[ $? -eq 0 ]]; then
|
if [[ $? -eq 0 ]]; then
|
||||||
service_installed=true
|
service_installed=true
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
arch | manjaro | parch)
|
arch | manjaro | parch)
|
||||||
if [ -f "x-ui.service.arch" ]; then
|
if [ -f "x-ui.service.arch" ]; then
|
||||||
echo -e "${green}Found x-ui.service.arch in extracted files, installing...${plain}"
|
echo -e "${green}Found x-ui.service.arch in extracted files, installing...${plain}"
|
||||||
cp -f x-ui.service.arch ${xui_service}/x-ui.service >/dev/null 2>&1
|
cp -f x-ui.service.arch ${xui_service}/x-ui.service > /dev/null 2>&1
|
||||||
if [[ $? -eq 0 ]]; then
|
if [[ $? -eq 0 ]]; then
|
||||||
service_installed=true
|
service_installed=true
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
if [ -f "x-ui.service.rhel" ]; then
|
if [ -f "x-ui.service.rhel" ]; then
|
||||||
echo -e "${green}Found x-ui.service.rhel in extracted files, installing...${plain}"
|
echo -e "${green}Found x-ui.service.rhel in extracted files, installing...${plain}"
|
||||||
cp -f x-ui.service.rhel ${xui_service}/x-ui.service >/dev/null 2>&1
|
cp -f x-ui.service.rhel ${xui_service}/x-ui.service > /dev/null 2>&1
|
||||||
if [[ $? -eq 0 ]]; then
|
if [[ $? -eq 0 ]]; then
|
||||||
service_installed=true
|
service_installed=true
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -929,14 +929,14 @@ install_x-ui() {
|
|||||||
echo -e "${yellow}Service files not found in tar.gz, downloading from GitHub...${plain}"
|
echo -e "${yellow}Service files not found in tar.gz, downloading from GitHub...${plain}"
|
||||||
case "${release}" in
|
case "${release}" in
|
||||||
ubuntu | debian | armbian)
|
ubuntu | debian | armbian)
|
||||||
curl -4fLRo ${xui_service}/x-ui.service https://raw.githubusercontent.com/MHSanaei/3x-ui/main/x-ui.service.debian >/dev/null 2>&1
|
curl -4fLRo ${xui_service}/x-ui.service https://raw.githubusercontent.com/MHSanaei/3x-ui/main/x-ui.service.debian > /dev/null 2>&1
|
||||||
;;
|
;;
|
||||||
arch | manjaro | parch)
|
arch | manjaro | parch)
|
||||||
curl -4fLRo ${xui_service}/x-ui.service https://raw.githubusercontent.com/MHSanaei/3x-ui/main/x-ui.service.arch >/dev/null 2>&1
|
curl -4fLRo ${xui_service}/x-ui.service https://raw.githubusercontent.com/MHSanaei/3x-ui/main/x-ui.service.arch > /dev/null 2>&1
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
curl -4fLRo ${xui_service}/x-ui.service https://raw.githubusercontent.com/MHSanaei/3x-ui/main/x-ui.service.rhel >/dev/null 2>&1
|
curl -4fLRo ${xui_service}/x-ui.service https://raw.githubusercontent.com/MHSanaei/3x-ui/main/x-ui.service.rhel > /dev/null 2>&1
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
if [[ $? -ne 0 ]]; then
|
if [[ $? -ne 0 ]]; then
|
||||||
@@ -948,8 +948,8 @@ install_x-ui() {
|
|||||||
|
|
||||||
if [ "$service_installed" = true ]; then
|
if [ "$service_installed" = true ]; then
|
||||||
echo -e "${green}Setting up systemd unit...${plain}"
|
echo -e "${green}Setting up systemd unit...${plain}"
|
||||||
chown root:root ${xui_service}/x-ui.service >/dev/null 2>&1
|
chown root:root ${xui_service}/x-ui.service > /dev/null 2>&1
|
||||||
chmod 644 ${xui_service}/x-ui.service >/dev/null 2>&1
|
chmod 644 ${xui_service}/x-ui.service > /dev/null 2>&1
|
||||||
systemctl daemon-reload
|
systemctl daemon-reload
|
||||||
systemctl enable x-ui
|
systemctl enable x-ui
|
||||||
systemctl start x-ui
|
systemctl start x-ui
|
||||||
|
|||||||
480
update.sh
480
update.sh
@@ -12,16 +12,16 @@ xui_service="${XUI_SERVICE:=/etc/systemd/system}"
|
|||||||
# Don't edit this config
|
# Don't edit this config
|
||||||
b_source="${BASH_SOURCE[0]}"
|
b_source="${BASH_SOURCE[0]}"
|
||||||
while [ -h "$b_source" ]; do
|
while [ -h "$b_source" ]; do
|
||||||
b_dir="$(cd -P "$(dirname "$b_source")" >/dev/null 2>&1 && pwd || pwd -P)"
|
b_dir="$(cd -P "$(dirname "$b_source")" > /dev/null 2>&1 && pwd || pwd -P)"
|
||||||
b_source="$(readlink "$b_source")"
|
b_source="$(readlink "$b_source")"
|
||||||
[[ $b_source != /* ]] && b_source="$b_dir/$b_source"
|
[[ $b_source != /* ]] && b_source="$b_dir/$b_source"
|
||||||
done
|
done
|
||||||
cur_dir="$(cd -P "$(dirname "$b_source")" >/dev/null 2>&1 && pwd || pwd -P)"
|
cur_dir="$(cd -P "$(dirname "$b_source")" > /dev/null 2>&1 && pwd || pwd -P)"
|
||||||
script_name=$(basename "$0")
|
script_name=$(basename "$0")
|
||||||
|
|
||||||
# Check command exist function
|
# Check command exist function
|
||||||
_command_exists() {
|
_command_exists() {
|
||||||
type "$1" &>/dev/null
|
type "$1" &> /dev/null
|
||||||
}
|
}
|
||||||
|
|
||||||
# Fail, log and exit script function
|
# Fail, log and exit script function
|
||||||
@@ -44,7 +44,7 @@ fi
|
|||||||
if [[ -f /etc/os-release ]]; then
|
if [[ -f /etc/os-release ]]; then
|
||||||
source /etc/os-release
|
source /etc/os-release
|
||||||
release=$ID
|
release=$ID
|
||||||
elif [[ -f /usr/lib/os-release ]]; then
|
elif [[ -f /usr/lib/os-release ]]; then
|
||||||
source /usr/lib/os-release
|
source /usr/lib/os-release
|
||||||
release=$ID
|
release=$ID
|
||||||
else
|
else
|
||||||
@@ -61,7 +61,7 @@ arch() {
|
|||||||
armv6* | armv6) echo 'armv6' ;;
|
armv6* | armv6) echo 'armv6' ;;
|
||||||
armv5* | armv5) echo 'armv5' ;;
|
armv5* | armv5) echo 'armv5' ;;
|
||||||
s390x) echo 's390x' ;;
|
s390x) echo 's390x' ;;
|
||||||
*) echo -e "${red}Unsupported CPU architecture!${plain}" && rm -f "${cur_dir}/${script_name}" >/dev/null 2>&1 && exit 2;;
|
*) echo -e "${red}Unsupported CPU architecture!${plain}" && rm -f "${cur_dir}/${script_name}" > /dev/null 2>&1 && exit 2 ;;
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,23 +84,23 @@ is_domain() {
|
|||||||
# Port helpers
|
# Port helpers
|
||||||
is_port_in_use() {
|
is_port_in_use() {
|
||||||
local port="$1"
|
local port="$1"
|
||||||
if command -v ss >/dev/null 2>&1; then
|
if command -v ss > /dev/null 2>&1; then
|
||||||
ss -ltn 2>/dev/null | awk -v p=":${port}$" '$4 ~ p {exit 0} END {exit 1}'
|
ss -ltn 2> /dev/null | awk -v p=":${port}$" '$4 ~ p {exit 0} END {exit 1}'
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
if command -v netstat >/dev/null 2>&1; then
|
if command -v netstat > /dev/null 2>&1; then
|
||||||
netstat -lnt 2>/dev/null | awk -v p=":${port} " '$4 ~ p {exit 0} END {exit 1}'
|
netstat -lnt 2> /dev/null | awk -v p=":${port} " '$4 ~ p {exit 0} END {exit 1}'
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
if command -v lsof >/dev/null 2>&1; then
|
if command -v lsof > /dev/null 2>&1; then
|
||||||
lsof -nP -iTCP:${port} -sTCP:LISTEN >/dev/null 2>&1 && return 0
|
lsof -nP -iTCP:${port} -sTCP:LISTEN > /dev/null 2>&1 && return 0
|
||||||
fi
|
fi
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
gen_random_string() {
|
gen_random_string() {
|
||||||
local length="$1"
|
local length="$1"
|
||||||
openssl rand -base64 $(( length * 2 )) \
|
openssl rand -base64 $((length * 2)) \
|
||||||
| tr -dc 'a-zA-Z0-9' \
|
| tr -dc 'a-zA-Z0-9' \
|
||||||
| head -c "$length"
|
| head -c "$length"
|
||||||
}
|
}
|
||||||
@@ -109,37 +109,37 @@ install_base() {
|
|||||||
echo -e "${green}Updating and install dependency packages...${plain}"
|
echo -e "${green}Updating and install dependency packages...${plain}"
|
||||||
case "${release}" in
|
case "${release}" in
|
||||||
ubuntu | debian | armbian)
|
ubuntu | debian | armbian)
|
||||||
apt-get update >/dev/null 2>&1 && apt-get install -y -q cron curl tar tzdata socat openssl >/dev/null 2>&1
|
apt-get update > /dev/null 2>&1 && apt-get install -y -q cron curl tar tzdata socat openssl > /dev/null 2>&1
|
||||||
;;
|
;;
|
||||||
fedora | amzn | virtuozzo | rhel | almalinux | rocky | ol)
|
fedora | amzn | virtuozzo | rhel | almalinux | rocky | ol)
|
||||||
dnf -y update >/dev/null 2>&1 && dnf install -y -q cronie curl tar tzdata socat openssl >/dev/null 2>&1
|
dnf -y update > /dev/null 2>&1 && dnf install -y -q cronie curl tar tzdata socat openssl > /dev/null 2>&1
|
||||||
;;
|
;;
|
||||||
centos)
|
centos)
|
||||||
if [[ "${VERSION_ID}" =~ ^7 ]]; then
|
if [[ "${VERSION_ID}" =~ ^7 ]]; then
|
||||||
yum -y update >/dev/null 2>&1 && yum install -y -q cronie curl tar tzdata socat openssl >/dev/null 2>&1
|
yum -y update > /dev/null 2>&1 && yum install -y -q cronie curl tar tzdata socat openssl > /dev/null 2>&1
|
||||||
else
|
else
|
||||||
dnf -y update >/dev/null 2>&1 && dnf install -y -q cronie curl tar tzdata socat openssl >/dev/null 2>&1
|
dnf -y update > /dev/null 2>&1 && dnf install -y -q cronie curl tar tzdata socat openssl > /dev/null 2>&1
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
arch | manjaro | parch)
|
arch | manjaro | parch)
|
||||||
pacman -Syu >/dev/null 2>&1 && pacman -Syu --noconfirm cronie curl tar tzdata socat openssl >/dev/null 2>&1
|
pacman -Syu > /dev/null 2>&1 && pacman -Syu --noconfirm cronie curl tar tzdata socat openssl > /dev/null 2>&1
|
||||||
;;
|
;;
|
||||||
opensuse-tumbleweed | opensuse-leap)
|
opensuse-tumbleweed | opensuse-leap)
|
||||||
zypper refresh >/dev/null 2>&1 && zypper -q install -y cron curl tar timezone socat openssl >/dev/null 2>&1
|
zypper refresh > /dev/null 2>&1 && zypper -q install -y cron curl tar timezone socat openssl > /dev/null 2>&1
|
||||||
;;
|
;;
|
||||||
alpine)
|
alpine)
|
||||||
apk update >/dev/null 2>&1 && apk add dcron curl tar tzdata socat openssl>/dev/null 2>&1
|
apk update > /dev/null 2>&1 && apk add dcron curl tar tzdata socat openssl > /dev/null 2>&1
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
apt-get update >/dev/null 2>&1 && apt install -y -q cron curl tar tzdata socat openssl >/dev/null 2>&1
|
apt-get update > /dev/null 2>&1 && apt install -y -q cron curl tar tzdata socat openssl > /dev/null 2>&1
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
install_acme() {
|
install_acme() {
|
||||||
echo -e "${green}Installing acme.sh for SSL certificate management...${plain}"
|
echo -e "${green}Installing acme.sh for SSL certificate management...${plain}"
|
||||||
cd ~ || return 1
|
cd ~ || return 1
|
||||||
curl -s https://get.acme.sh | sh >/dev/null 2>&1
|
curl -s https://get.acme.sh | sh > /dev/null 2>&1
|
||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ]; then
|
||||||
echo -e "${red}Failed to install acme.sh${plain}"
|
echo -e "${red}Failed to install acme.sh${plain}"
|
||||||
return 1
|
return 1
|
||||||
@@ -158,7 +158,7 @@ setup_ssl_certificate() {
|
|||||||
echo -e "${green}Setting up SSL certificate...${plain}"
|
echo -e "${green}Setting up SSL certificate...${plain}"
|
||||||
|
|
||||||
# Check if acme.sh is installed
|
# Check if acme.sh is installed
|
||||||
if ! command -v ~/.acme.sh/acme.sh &>/dev/null; then
|
if ! command -v ~/.acme.sh/acme.sh &> /dev/null; then
|
||||||
install_acme
|
install_acme
|
||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ]; then
|
||||||
echo -e "${yellow}Failed to install acme.sh, skipping SSL setup${plain}"
|
echo -e "${yellow}Failed to install acme.sh, skipping SSL setup${plain}"
|
||||||
@@ -174,14 +174,14 @@ setup_ssl_certificate() {
|
|||||||
echo -e "${green}Issuing SSL certificate for ${domain}...${plain}"
|
echo -e "${green}Issuing SSL certificate for ${domain}...${plain}"
|
||||||
echo -e "${yellow}Note: Port 80 must be open and accessible from the internet${plain}"
|
echo -e "${yellow}Note: Port 80 must be open and accessible from the internet${plain}"
|
||||||
|
|
||||||
~/.acme.sh/acme.sh --set-default-ca --server letsencrypt --force >/dev/null 2>&1
|
~/.acme.sh/acme.sh --set-default-ca --server letsencrypt --force > /dev/null 2>&1
|
||||||
~/.acme.sh/acme.sh --issue -d ${domain} --listen-v6 --standalone --httpport 80 --force
|
~/.acme.sh/acme.sh --issue -d ${domain} --listen-v6 --standalone --httpport 80 --force
|
||||||
|
|
||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ]; then
|
||||||
echo -e "${yellow}Failed to issue certificate for ${domain}${plain}"
|
echo -e "${yellow}Failed to issue certificate for ${domain}${plain}"
|
||||||
echo -e "${yellow}Please ensure port 80 is open and try again later with: x-ui${plain}"
|
echo -e "${yellow}Please ensure port 80 is open and try again later with: x-ui${plain}"
|
||||||
rm -rf ~/.acme.sh/${domain} 2>/dev/null
|
rm -rf ~/.acme.sh/${domain} 2> /dev/null
|
||||||
rm -rf "$certPath" 2>/dev/null
|
rm -rf "$certPath" 2> /dev/null
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -189,7 +189,7 @@ setup_ssl_certificate() {
|
|||||||
~/.acme.sh/acme.sh --installcert -d ${domain} \
|
~/.acme.sh/acme.sh --installcert -d ${domain} \
|
||||||
--key-file /root/cert/${domain}/privkey.pem \
|
--key-file /root/cert/${domain}/privkey.pem \
|
||||||
--fullchain-file /root/cert/${domain}/fullchain.pem \
|
--fullchain-file /root/cert/${domain}/fullchain.pem \
|
||||||
--reloadcmd "systemctl restart x-ui" >/dev/null 2>&1
|
--reloadcmd "systemctl restart x-ui" > /dev/null 2>&1
|
||||||
|
|
||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ]; then
|
||||||
echo -e "${yellow}Failed to install certificate${plain}"
|
echo -e "${yellow}Failed to install certificate${plain}"
|
||||||
@@ -197,16 +197,16 @@ setup_ssl_certificate() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Enable auto-renew
|
# Enable auto-renew
|
||||||
~/.acme.sh/acme.sh --upgrade --auto-upgrade >/dev/null 2>&1
|
~/.acme.sh/acme.sh --upgrade --auto-upgrade > /dev/null 2>&1
|
||||||
chmod 600 $certPath/privkey.pem 2>/dev/null
|
chmod 600 $certPath/privkey.pem 2> /dev/null
|
||||||
chmod 644 $certPath/fullchain.pem 2>/dev/null
|
chmod 644 $certPath/fullchain.pem 2> /dev/null
|
||||||
|
|
||||||
# Set certificate for panel
|
# Set certificate for panel
|
||||||
local webCertFile="/root/cert/${domain}/fullchain.pem"
|
local webCertFile="/root/cert/${domain}/fullchain.pem"
|
||||||
local webKeyFile="/root/cert/${domain}/privkey.pem"
|
local webKeyFile="/root/cert/${domain}/privkey.pem"
|
||||||
|
|
||||||
if [[ -f "$webCertFile" && -f "$webKeyFile" ]]; then
|
if [[ -f "$webCertFile" && -f "$webKeyFile" ]]; then
|
||||||
${xui_folder}/x-ui cert -webCert "$webCertFile" -webCertKey "$webKeyFile" >/dev/null 2>&1
|
${xui_folder}/x-ui cert -webCert "$webCertFile" -webCertKey "$webKeyFile" > /dev/null 2>&1
|
||||||
echo -e "${green}SSL certificate installed and configured successfully!${plain}"
|
echo -e "${green}SSL certificate installed and configured successfully!${plain}"
|
||||||
return 0
|
return 0
|
||||||
else
|
else
|
||||||
@@ -219,14 +219,14 @@ setup_ssl_certificate() {
|
|||||||
# Requires acme.sh and port 80 open for HTTP-01 challenge
|
# Requires acme.sh and port 80 open for HTTP-01 challenge
|
||||||
setup_ip_certificate() {
|
setup_ip_certificate() {
|
||||||
local ipv4="$1"
|
local ipv4="$1"
|
||||||
local ipv6="$2" # optional
|
local ipv6="$2" # optional
|
||||||
|
|
||||||
echo -e "${green}Setting up Let's Encrypt IP certificate (shortlived profile)...${plain}"
|
echo -e "${green}Setting up Let's Encrypt IP certificate (shortlived profile)...${plain}"
|
||||||
echo -e "${yellow}Note: IP certificates are valid for ~6 days and will auto-renew.${plain}"
|
echo -e "${yellow}Note: IP certificates are valid for ~6 days and will auto-renew.${plain}"
|
||||||
echo -e "${yellow}Default listener is port 80. If you choose another port, ensure external port 80 forwards to it.${plain}"
|
echo -e "${yellow}Default listener is port 80. If you choose another port, ensure external port 80 forwards to it.${plain}"
|
||||||
|
|
||||||
# Check for acme.sh
|
# Check for acme.sh
|
||||||
if ! command -v ~/.acme.sh/acme.sh &>/dev/null; then
|
if ! command -v ~/.acme.sh/acme.sh &> /dev/null; then
|
||||||
install_acme
|
install_acme
|
||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ]; then
|
||||||
echo -e "${red}Failed to install acme.sh${plain}"
|
echo -e "${red}Failed to install acme.sh${plain}"
|
||||||
@@ -298,7 +298,7 @@ setup_ip_certificate() {
|
|||||||
|
|
||||||
# Issue certificate with shortlived profile
|
# Issue certificate with shortlived profile
|
||||||
echo -e "${green}Issuing IP certificate for ${ipv4}...${plain}"
|
echo -e "${green}Issuing IP certificate for ${ipv4}...${plain}"
|
||||||
~/.acme.sh/acme.sh --set-default-ca --server letsencrypt --force >/dev/null 2>&1
|
~/.acme.sh/acme.sh --set-default-ca --server letsencrypt --force > /dev/null 2>&1
|
||||||
|
|
||||||
~/.acme.sh/acme.sh --issue \
|
~/.acme.sh/acme.sh --issue \
|
||||||
${domain_args} \
|
${domain_args} \
|
||||||
@@ -313,9 +313,9 @@ setup_ip_certificate() {
|
|||||||
echo -e "${red}Failed to issue IP certificate${plain}"
|
echo -e "${red}Failed to issue IP certificate${plain}"
|
||||||
echo -e "${yellow}Please ensure port ${WebPort} is reachable (or forwarded from external port 80)${plain}"
|
echo -e "${yellow}Please ensure port ${WebPort} is reachable (or forwarded from external port 80)${plain}"
|
||||||
# Cleanup acme.sh data for both IPv4 and IPv6 if specified
|
# Cleanup acme.sh data for both IPv4 and IPv6 if specified
|
||||||
rm -rf ~/.acme.sh/${ipv4} 2>/dev/null
|
rm -rf ~/.acme.sh/${ipv4} 2> /dev/null
|
||||||
[[ -n "$ipv6" ]] && rm -rf ~/.acme.sh/${ipv6} 2>/dev/null
|
[[ -n "$ipv6" ]] && rm -rf ~/.acme.sh/${ipv6} 2> /dev/null
|
||||||
rm -rf ${certDir} 2>/dev/null
|
rm -rf ${certDir} 2> /dev/null
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -333,19 +333,19 @@ setup_ip_certificate() {
|
|||||||
if [[ ! -f "${certDir}/fullchain.pem" || ! -f "${certDir}/privkey.pem" ]]; then
|
if [[ ! -f "${certDir}/fullchain.pem" || ! -f "${certDir}/privkey.pem" ]]; then
|
||||||
echo -e "${red}Certificate files not found after installation${plain}"
|
echo -e "${red}Certificate files not found after installation${plain}"
|
||||||
# Cleanup acme.sh data for both IPv4 and IPv6 if specified
|
# Cleanup acme.sh data for both IPv4 and IPv6 if specified
|
||||||
rm -rf ~/.acme.sh/${ipv4} 2>/dev/null
|
rm -rf ~/.acme.sh/${ipv4} 2> /dev/null
|
||||||
[[ -n "$ipv6" ]] && rm -rf ~/.acme.sh/${ipv6} 2>/dev/null
|
[[ -n "$ipv6" ]] && rm -rf ~/.acme.sh/${ipv6} 2> /dev/null
|
||||||
rm -rf ${certDir} 2>/dev/null
|
rm -rf ${certDir} 2> /dev/null
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo -e "${green}Certificate files installed successfully${plain}"
|
echo -e "${green}Certificate files installed successfully${plain}"
|
||||||
|
|
||||||
# Enable auto-upgrade for acme.sh (ensures cron job runs)
|
# Enable auto-upgrade for acme.sh (ensures cron job runs)
|
||||||
~/.acme.sh/acme.sh --upgrade --auto-upgrade >/dev/null 2>&1
|
~/.acme.sh/acme.sh --upgrade --auto-upgrade > /dev/null 2>&1
|
||||||
|
|
||||||
chmod 600 ${certDir}/privkey.pem 2>/dev/null
|
chmod 600 ${certDir}/privkey.pem 2> /dev/null
|
||||||
chmod 644 ${certDir}/fullchain.pem 2>/dev/null
|
chmod 644 ${certDir}/fullchain.pem 2> /dev/null
|
||||||
|
|
||||||
# Configure panel to use the certificate
|
# Configure panel to use the certificate
|
||||||
echo -e "${green}Setting certificate paths for the panel...${plain}"
|
echo -e "${green}Setting certificate paths for the panel...${plain}"
|
||||||
@@ -371,7 +371,7 @@ ssl_cert_issue() {
|
|||||||
local existing_port=$(${xui_folder}/x-ui setting -show true | grep 'port:' | awk -F': ' '{print $2}' | tr -d '[:space:]')
|
local existing_port=$(${xui_folder}/x-ui setting -show true | grep 'port:' | awk -F': ' '{print $2}' | tr -d '[:space:]')
|
||||||
|
|
||||||
# check for acme.sh first
|
# check for acme.sh first
|
||||||
if ! command -v ~/.acme.sh/acme.sh &>/dev/null; then
|
if ! command -v ~/.acme.sh/acme.sh &> /dev/null; then
|
||||||
echo "acme.sh could not be found. Installing now..."
|
echo "acme.sh could not be found. Installing now..."
|
||||||
cd ~ || return 1
|
cd ~ || return 1
|
||||||
curl -s https://get.acme.sh | sh
|
curl -s https://get.acme.sh | sh
|
||||||
@@ -387,7 +387,7 @@ ssl_cert_issue() {
|
|||||||
local domain=""
|
local domain=""
|
||||||
while true; do
|
while true; do
|
||||||
read -rp "Please enter your domain name: " domain
|
read -rp "Please enter your domain name: " domain
|
||||||
domain="${domain// /}" # Trim whitespace
|
domain="${domain// /}" # Trim whitespace
|
||||||
|
|
||||||
if [[ -z "$domain" ]]; then
|
if [[ -z "$domain" ]]; then
|
||||||
echo -e "${red}Domain name cannot be empty. Please try again.${plain}"
|
echo -e "${red}Domain name cannot be empty. Please try again.${plain}"
|
||||||
@@ -406,9 +406,9 @@ ssl_cert_issue() {
|
|||||||
|
|
||||||
# detect existing certificate and reuse it if present
|
# detect existing certificate and reuse it if present
|
||||||
local cert_exists=0
|
local cert_exists=0
|
||||||
if ~/.acme.sh/acme.sh --list 2>/dev/null | awk '{print $1}' | grep -Fxq "${domain}"; then
|
if ~/.acme.sh/acme.sh --list 2> /dev/null | awk '{print $1}' | grep -Fxq "${domain}"; then
|
||||||
cert_exists=1
|
cert_exists=1
|
||||||
local certInfo=$(~/.acme.sh/acme.sh --list 2>/dev/null | grep -F "${domain}")
|
local certInfo=$(~/.acme.sh/acme.sh --list 2> /dev/null | grep -F "${domain}")
|
||||||
echo -e "${yellow}Existing certificate found for ${domain}, will reuse it.${plain}"
|
echo -e "${yellow}Existing certificate found for ${domain}, will reuse it.${plain}"
|
||||||
[[ -n "${certInfo}" ]] && echo "$certInfo"
|
[[ -n "${certInfo}" ]] && echo "$certInfo"
|
||||||
else
|
else
|
||||||
@@ -435,7 +435,7 @@ ssl_cert_issue() {
|
|||||||
|
|
||||||
# Stop panel temporarily
|
# Stop panel temporarily
|
||||||
echo -e "${yellow}Stopping panel temporarily...${plain}"
|
echo -e "${yellow}Stopping panel temporarily...${plain}"
|
||||||
systemctl stop x-ui 2>/dev/null || rc-service x-ui stop 2>/dev/null
|
systemctl stop x-ui 2> /dev/null || rc-service x-ui stop 2> /dev/null
|
||||||
|
|
||||||
if [[ ${cert_exists} -eq 0 ]]; then
|
if [[ ${cert_exists} -eq 0 ]]; then
|
||||||
# issue the certificate
|
# issue the certificate
|
||||||
@@ -444,7 +444,7 @@ ssl_cert_issue() {
|
|||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ]; then
|
||||||
echo -e "${red}Issuing certificate failed, please check logs.${plain}"
|
echo -e "${red}Issuing certificate failed, please check logs.${plain}"
|
||||||
rm -rf ~/.acme.sh/${domain}
|
rm -rf ~/.acme.sh/${domain}
|
||||||
systemctl start x-ui 2>/dev/null || rc-service x-ui start 2>/dev/null
|
systemctl start x-ui 2> /dev/null || rc-service x-ui start 2> /dev/null
|
||||||
return 1
|
return 1
|
||||||
else
|
else
|
||||||
echo -e "${green}Issuing certificate succeeded, installing certificates...${plain}"
|
echo -e "${green}Issuing certificate succeeded, installing certificates...${plain}"
|
||||||
@@ -464,18 +464,18 @@ ssl_cert_issue() {
|
|||||||
echo -e "${green}\t0.${plain} Keep default reloadcmd"
|
echo -e "${green}\t0.${plain} Keep default reloadcmd"
|
||||||
read -rp "Choose an option: " choice
|
read -rp "Choose an option: " choice
|
||||||
case "$choice" in
|
case "$choice" in
|
||||||
1)
|
1)
|
||||||
echo -e "${green}Reloadcmd is: systemctl reload nginx ; systemctl restart x-ui${plain}"
|
echo -e "${green}Reloadcmd is: systemctl reload nginx ; systemctl restart x-ui${plain}"
|
||||||
reloadCmd="systemctl reload nginx ; systemctl restart x-ui"
|
reloadCmd="systemctl reload nginx ; systemctl restart x-ui"
|
||||||
;;
|
;;
|
||||||
2)
|
2)
|
||||||
echo -e "${yellow}It's recommended to put x-ui restart at the end${plain}"
|
echo -e "${yellow}It's recommended to put x-ui restart at the end${plain}"
|
||||||
read -rp "Please enter your custom reloadcmd: " reloadCmd
|
read -rp "Please enter your custom reloadcmd: " reloadCmd
|
||||||
echo -e "${green}Reloadcmd is: ${reloadCmd}${plain}"
|
echo -e "${green}Reloadcmd is: ${reloadCmd}${plain}"
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
echo -e "${green}Keeping default reloadcmd${plain}"
|
echo -e "${green}Keeping default reloadcmd${plain}"
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -492,14 +492,14 @@ ssl_cert_issue() {
|
|||||||
installWroteFiles=1
|
installWroteFiles=1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -f "/root/cert/${domain}/privkey.pem" && -f "/root/cert/${domain}/fullchain.pem" && ( ${installRc} -eq 0 || ${installWroteFiles} -eq 1 ) ]]; then
|
if [[ -f "/root/cert/${domain}/privkey.pem" && -f "/root/cert/${domain}/fullchain.pem" && (${installRc} -eq 0 || ${installWroteFiles} -eq 1) ]]; then
|
||||||
echo -e "${green}Installing certificate succeeded, enabling auto renew...${plain}"
|
echo -e "${green}Installing certificate succeeded, enabling auto renew...${plain}"
|
||||||
else
|
else
|
||||||
echo -e "${red}Installing certificate failed, exiting.${plain}"
|
echo -e "${red}Installing certificate failed, exiting.${plain}"
|
||||||
if [[ ${cert_exists} -eq 0 ]]; then
|
if [[ ${cert_exists} -eq 0 ]]; then
|
||||||
rm -rf ~/.acme.sh/${domain}
|
rm -rf ~/.acme.sh/${domain}
|
||||||
fi
|
fi
|
||||||
systemctl start x-ui 2>/dev/null || rc-service x-ui start 2>/dev/null
|
systemctl start x-ui 2> /dev/null || rc-service x-ui start 2> /dev/null
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -518,7 +518,7 @@ ssl_cert_issue() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Restart panel
|
# Restart panel
|
||||||
systemctl start x-ui 2>/dev/null || rc-service x-ui start 2>/dev/null
|
systemctl start x-ui 2> /dev/null || rc-service x-ui start 2> /dev/null
|
||||||
|
|
||||||
# Prompt user to set panel paths after successful certificate installation
|
# Prompt user to set panel paths after successful certificate installation
|
||||||
read -rp "Would you like to set this certificate for the panel? (y/n): " setPanel
|
read -rp "Would you like to set this certificate for the panel? (y/n): " setPanel
|
||||||
@@ -534,7 +534,7 @@ ssl_cert_issue() {
|
|||||||
echo ""
|
echo ""
|
||||||
echo -e "${green}Access URL: https://${domain}:${existing_port}/${existing_webBasePath}${plain}"
|
echo -e "${green}Access URL: https://${domain}:${existing_port}/${existing_webBasePath}${plain}"
|
||||||
echo -e "${yellow}Panel will restart to apply SSL certificate...${plain}"
|
echo -e "${yellow}Panel will restart to apply SSL certificate...${plain}"
|
||||||
systemctl restart x-ui 2>/dev/null || rc-service x-ui restart 2>/dev/null
|
systemctl restart x-ui 2> /dev/null || rc-service x-ui restart 2> /dev/null
|
||||||
else
|
else
|
||||||
echo -e "${red}Error: Certificate or private key file not found for domain: $domain.${plain}"
|
echo -e "${red}Error: Certificate or private key file not found for domain: $domain.${plain}"
|
||||||
fi
|
fi
|
||||||
@@ -548,7 +548,7 @@ ssl_cert_issue() {
|
|||||||
# Sets global `SSL_HOST` to the chosen domain/IP
|
# Sets global `SSL_HOST` to the chosen domain/IP
|
||||||
prompt_and_setup_ssl() {
|
prompt_and_setup_ssl() {
|
||||||
local panel_port="$1"
|
local panel_port="$1"
|
||||||
local web_base_path="$2" # expected without leading slash
|
local web_base_path="$2" # expected without leading slash
|
||||||
local server_ip="$3"
|
local server_ip="$3"
|
||||||
|
|
||||||
local ssl_choice=""
|
local ssl_choice=""
|
||||||
@@ -559,7 +559,7 @@ prompt_and_setup_ssl() {
|
|||||||
echo -e "${green}3.${plain} Custom SSL Certificate (Path to existing files)"
|
echo -e "${green}3.${plain} Custom SSL Certificate (Path to existing files)"
|
||||||
echo -e "${blue}Note:${plain} Options 1 & 2 require port 80 open. Option 3 requires manual paths."
|
echo -e "${blue}Note:${plain} Options 1 & 2 require port 80 open. Option 3 requires manual paths."
|
||||||
read -rp "Choose an option (default 2 for IP): " ssl_choice
|
read -rp "Choose an option (default 2 for IP): " ssl_choice
|
||||||
ssl_choice="${ssl_choice// /}" # Trim whitespace
|
ssl_choice="${ssl_choice// /}" # Trim whitespace
|
||||||
|
|
||||||
# Default to 2 (IP cert) if input is empty or invalid (not 1 or 3)
|
# Default to 2 (IP cert) if input is empty or invalid (not 1 or 3)
|
||||||
if [[ "$ssl_choice" != "1" && "$ssl_choice" != "3" ]]; then
|
if [[ "$ssl_choice" != "1" && "$ssl_choice" != "3" ]]; then
|
||||||
@@ -567,124 +567,124 @@ prompt_and_setup_ssl() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
case "$ssl_choice" in
|
case "$ssl_choice" in
|
||||||
1)
|
1)
|
||||||
# User chose Let's Encrypt domain option
|
# User chose Let's Encrypt domain option
|
||||||
echo -e "${green}Using Let's Encrypt for domain certificate...${plain}"
|
echo -e "${green}Using Let's Encrypt for domain certificate...${plain}"
|
||||||
if ssl_cert_issue; then
|
if ssl_cert_issue; then
|
||||||
local cert_domain="${SSL_ISSUED_DOMAIN}"
|
local cert_domain="${SSL_ISSUED_DOMAIN}"
|
||||||
if [[ -z "${cert_domain}" ]]; then
|
if [[ -z "${cert_domain}" ]]; then
|
||||||
cert_domain=$(~/.acme.sh/acme.sh --list 2>/dev/null | tail -1 | awk '{print $1}')
|
cert_domain=$(~/.acme.sh/acme.sh --list 2> /dev/null | tail -1 | awk '{print $1}')
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -n "${cert_domain}" ]]; then
|
if [[ -n "${cert_domain}" ]]; then
|
||||||
SSL_HOST="${cert_domain}"
|
SSL_HOST="${cert_domain}"
|
||||||
echo -e "${green}✓ SSL certificate configured successfully with domain: ${cert_domain}${plain}"
|
echo -e "${green}✓ SSL certificate configured successfully with domain: ${cert_domain}${plain}"
|
||||||
|
else
|
||||||
|
echo -e "${yellow}SSL setup may have completed, but domain extraction failed${plain}"
|
||||||
|
SSL_HOST="${server_ip}"
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
echo -e "${yellow}SSL setup may have completed, but domain extraction failed${plain}"
|
echo -e "${red}SSL certificate setup failed for domain mode.${plain}"
|
||||||
SSL_HOST="${server_ip}"
|
SSL_HOST="${server_ip}"
|
||||||
fi
|
fi
|
||||||
else
|
;;
|
||||||
echo -e "${red}SSL certificate setup failed for domain mode.${plain}"
|
2)
|
||||||
SSL_HOST="${server_ip}"
|
# User chose Let's Encrypt IP certificate option
|
||||||
fi
|
echo -e "${green}Using Let's Encrypt for IP certificate (shortlived profile)...${plain}"
|
||||||
;;
|
|
||||||
2)
|
|
||||||
# User chose Let's Encrypt IP certificate option
|
|
||||||
echo -e "${green}Using Let's Encrypt for IP certificate (shortlived profile)...${plain}"
|
|
||||||
|
|
||||||
# Ask for optional IPv6
|
# Ask for optional IPv6
|
||||||
local ipv6_addr=""
|
local ipv6_addr=""
|
||||||
read -rp "Do you have an IPv6 address to include? (leave empty to skip): " ipv6_addr
|
read -rp "Do you have an IPv6 address to include? (leave empty to skip): " ipv6_addr
|
||||||
ipv6_addr="${ipv6_addr// /}" # Trim whitespace
|
ipv6_addr="${ipv6_addr// /}" # Trim whitespace
|
||||||
|
|
||||||
# Stop panel if running (port 80 needed)
|
# Stop panel if running (port 80 needed)
|
||||||
if [[ $release == "alpine" ]]; then
|
if [[ $release == "alpine" ]]; then
|
||||||
rc-service x-ui stop >/dev/null 2>&1
|
rc-service x-ui stop > /dev/null 2>&1
|
||||||
else
|
|
||||||
systemctl stop x-ui >/dev/null 2>&1
|
|
||||||
fi
|
|
||||||
|
|
||||||
setup_ip_certificate "${server_ip}" "${ipv6_addr}"
|
|
||||||
if [ $? -eq 0 ]; then
|
|
||||||
SSL_HOST="${server_ip}"
|
|
||||||
echo -e "${green}✓ Let's Encrypt IP certificate configured successfully${plain}"
|
|
||||||
else
|
|
||||||
echo -e "${red}✗ IP certificate setup failed. Please check port 80 is open.${plain}"
|
|
||||||
SSL_HOST="${server_ip}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Restart panel after SSL is configured (restart applies new cert settings)
|
|
||||||
if [[ $release == "alpine" ]]; then
|
|
||||||
rc-service x-ui restart >/dev/null 2>&1
|
|
||||||
else
|
|
||||||
systemctl restart x-ui >/dev/null 2>&1
|
|
||||||
fi
|
|
||||||
|
|
||||||
;;
|
|
||||||
3)
|
|
||||||
# User chose Custom Paths (User Provided) option
|
|
||||||
echo -e "${green}Using custom existing certificate...${plain}"
|
|
||||||
local custom_cert=""
|
|
||||||
local custom_key=""
|
|
||||||
local custom_domain=""
|
|
||||||
|
|
||||||
# 3.1 Request Domain to compose Panel URL later
|
|
||||||
read -rp "Please enter domain name certificate issued for: " custom_domain
|
|
||||||
custom_domain="${custom_domain// /}" # Remove spaces
|
|
||||||
|
|
||||||
# 3.2 Loop for Certificate Path
|
|
||||||
while true; do
|
|
||||||
read -rp "Input certificate path (keywords: .crt / fullchain): " custom_cert
|
|
||||||
# Strip quotes if present
|
|
||||||
custom_cert=$(echo "$custom_cert" | tr -d '"' | tr -d "'")
|
|
||||||
|
|
||||||
if [[ -f "$custom_cert" && -r "$custom_cert" && -s "$custom_cert" ]]; then
|
|
||||||
break
|
|
||||||
elif [[ ! -f "$custom_cert" ]]; then
|
|
||||||
echo -e "${red}Error: File does not exist! Try again.${plain}"
|
|
||||||
elif [[ ! -r "$custom_cert" ]]; then
|
|
||||||
echo -e "${red}Error: File exists but is not readable (check permissions)!${plain}"
|
|
||||||
else
|
else
|
||||||
echo -e "${red}Error: File is empty!${plain}"
|
systemctl stop x-ui > /dev/null 2>&1
|
||||||
fi
|
fi
|
||||||
done
|
|
||||||
|
|
||||||
# 3.3 Loop for Private Key Path
|
setup_ip_certificate "${server_ip}" "${ipv6_addr}"
|
||||||
while true; do
|
if [ $? -eq 0 ]; then
|
||||||
read -rp "Input private key path (keywords: .key / privatekey): " custom_key
|
SSL_HOST="${server_ip}"
|
||||||
# Strip quotes if present
|
echo -e "${green}✓ Let's Encrypt IP certificate configured successfully${plain}"
|
||||||
custom_key=$(echo "$custom_key" | tr -d '"' | tr -d "'")
|
|
||||||
|
|
||||||
if [[ -f "$custom_key" && -r "$custom_key" && -s "$custom_key" ]]; then
|
|
||||||
break
|
|
||||||
elif [[ ! -f "$custom_key" ]]; then
|
|
||||||
echo -e "${red}Error: File does not exist! Try again.${plain}"
|
|
||||||
elif [[ ! -r "$custom_key" ]]; then
|
|
||||||
echo -e "${red}Error: File exists but is not readable (check permissions)!${plain}"
|
|
||||||
else
|
else
|
||||||
echo -e "${red}Error: File is empty!${plain}"
|
echo -e "${red}✗ IP certificate setup failed. Please check port 80 is open.${plain}"
|
||||||
|
SSL_HOST="${server_ip}"
|
||||||
fi
|
fi
|
||||||
done
|
|
||||||
|
|
||||||
# 3.4 Apply Settings via x-ui binary
|
# Restart panel after SSL is configured (restart applies new cert settings)
|
||||||
${xui_folder}/x-ui cert -webCert "$custom_cert" -webCertKey "$custom_key" >/dev/null 2>&1
|
if [[ $release == "alpine" ]]; then
|
||||||
|
rc-service x-ui restart > /dev/null 2>&1
|
||||||
|
else
|
||||||
|
systemctl restart x-ui > /dev/null 2>&1
|
||||||
|
fi
|
||||||
|
|
||||||
# Set SSL_HOST for composing Panel URL
|
;;
|
||||||
if [[ -n "$custom_domain" ]]; then
|
3)
|
||||||
SSL_HOST="$custom_domain"
|
# User chose Custom Paths (User Provided) option
|
||||||
else
|
echo -e "${green}Using custom existing certificate...${plain}"
|
||||||
|
local custom_cert=""
|
||||||
|
local custom_key=""
|
||||||
|
local custom_domain=""
|
||||||
|
|
||||||
|
# 3.1 Request Domain to compose Panel URL later
|
||||||
|
read -rp "Please enter domain name certificate issued for: " custom_domain
|
||||||
|
custom_domain="${custom_domain// /}" # Remove spaces
|
||||||
|
|
||||||
|
# 3.2 Loop for Certificate Path
|
||||||
|
while true; do
|
||||||
|
read -rp "Input certificate path (keywords: .crt / fullchain): " custom_cert
|
||||||
|
# Strip quotes if present
|
||||||
|
custom_cert=$(echo "$custom_cert" | tr -d '"' | tr -d "'")
|
||||||
|
|
||||||
|
if [[ -f "$custom_cert" && -r "$custom_cert" && -s "$custom_cert" ]]; then
|
||||||
|
break
|
||||||
|
elif [[ ! -f "$custom_cert" ]]; then
|
||||||
|
echo -e "${red}Error: File does not exist! Try again.${plain}"
|
||||||
|
elif [[ ! -r "$custom_cert" ]]; then
|
||||||
|
echo -e "${red}Error: File exists but is not readable (check permissions)!${plain}"
|
||||||
|
else
|
||||||
|
echo -e "${red}Error: File is empty!${plain}"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# 3.3 Loop for Private Key Path
|
||||||
|
while true; do
|
||||||
|
read -rp "Input private key path (keywords: .key / privatekey): " custom_key
|
||||||
|
# Strip quotes if present
|
||||||
|
custom_key=$(echo "$custom_key" | tr -d '"' | tr -d "'")
|
||||||
|
|
||||||
|
if [[ -f "$custom_key" && -r "$custom_key" && -s "$custom_key" ]]; then
|
||||||
|
break
|
||||||
|
elif [[ ! -f "$custom_key" ]]; then
|
||||||
|
echo -e "${red}Error: File does not exist! Try again.${plain}"
|
||||||
|
elif [[ ! -r "$custom_key" ]]; then
|
||||||
|
echo -e "${red}Error: File exists but is not readable (check permissions)!${plain}"
|
||||||
|
else
|
||||||
|
echo -e "${red}Error: File is empty!${plain}"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# 3.4 Apply Settings via x-ui binary
|
||||||
|
${xui_folder}/x-ui cert -webCert "$custom_cert" -webCertKey "$custom_key" > /dev/null 2>&1
|
||||||
|
|
||||||
|
# Set SSL_HOST for composing Panel URL
|
||||||
|
if [[ -n "$custom_domain" ]]; then
|
||||||
|
SSL_HOST="$custom_domain"
|
||||||
|
else
|
||||||
|
SSL_HOST="${server_ip}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${green}✓ Custom certificate paths applied.${plain}"
|
||||||
|
echo -e "${yellow}Note: You are responsible for renewing these files externally.${plain}"
|
||||||
|
|
||||||
|
systemctl restart x-ui > /dev/null 2>&1 || rc-service x-ui restart > /dev/null 2>&1
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo -e "${red}Invalid option. Skipping SSL setup.${plain}"
|
||||||
SSL_HOST="${server_ip}"
|
SSL_HOST="${server_ip}"
|
||||||
fi
|
;;
|
||||||
|
|
||||||
echo -e "${green}✓ Custom certificate paths applied.${plain}"
|
|
||||||
echo -e "${yellow}Note: You are responsible for renewing these files externally.${plain}"
|
|
||||||
|
|
||||||
systemctl restart x-ui >/dev/null 2>&1 || rc-service x-ui restart >/dev/null 2>&1
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
echo -e "${red}Invalid option. Skipping SSL setup.${plain}"
|
|
||||||
SSL_HOST="${server_ip}"
|
|
||||||
;;
|
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -694,7 +694,7 @@ config_after_update() {
|
|||||||
${xui_folder}/x-ui migrate
|
${xui_folder}/x-ui migrate
|
||||||
|
|
||||||
# Properly detect empty cert by checking if cert: line exists and has content after it
|
# Properly detect empty cert by checking if cert: line exists and has content after it
|
||||||
local existing_cert=$(${xui_folder}/x-ui setting -getCert true 2>/dev/null | grep 'cert:' | awk -F': ' '{print $2}' | tr -d '[:space:]')
|
local existing_cert=$(${xui_folder}/x-ui setting -getCert true 2> /dev/null | grep 'cert:' | awk -F': ' '{print $2}' | tr -d '[:space:]')
|
||||||
local existing_port=$(${xui_folder}/x-ui setting -show true | grep -Eo 'port: .+' | awk '{print $2}')
|
local existing_port=$(${xui_folder}/x-ui setting -show true | grep -Eo 'port: .+' | awk '{print $2}')
|
||||||
local existing_webBasePath=$(${xui_folder}/x-ui setting -show true | grep -Eo 'webBasePath: .+' | awk '{print $2}' | sed 's#^/##')
|
local existing_webBasePath=$(${xui_folder}/x-ui setting -show true | grep -Eo 'webBasePath: .+' | awk '{print $2}' | sed 's#^/##')
|
||||||
|
|
||||||
@@ -709,7 +709,7 @@ config_after_update() {
|
|||||||
)
|
)
|
||||||
local server_ip=""
|
local server_ip=""
|
||||||
for ip_address in "${URL_lists[@]}"; do
|
for ip_address in "${URL_lists[@]}"; do
|
||||||
local response=$(curl -s -w "\n%{http_code}" --max-time 3 "${ip_address}" 2>/dev/null)
|
local response=$(curl -s -w "\n%{http_code}" --max-time 3 "${ip_address}" 2> /dev/null)
|
||||||
local http_code=$(echo "$response" | tail -n1)
|
local http_code=$(echo "$response" | tail -n1)
|
||||||
local ip_result=$(echo "$response" | head -n-1 | tr -d '[:space:]')
|
local ip_result=$(echo "$response" | head -n-1 | tr -d '[:space:]')
|
||||||
if [[ "${http_code}" == "200" && -n "${ip_result}" ]]; then
|
if [[ "${http_code}" == "200" && -n "${ip_result}" ]]; then
|
||||||
@@ -778,7 +778,7 @@ update_x-ui() {
|
|||||||
|
|
||||||
echo -e "${green}Downloading new x-ui version...${plain}"
|
echo -e "${green}Downloading new x-ui version...${plain}"
|
||||||
|
|
||||||
tag_version=$(${curl_bin} -Ls "https://api.github.com/repos/MHSanaei/3x-ui/releases/latest" 2>/dev/null | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/')
|
tag_version=$(${curl_bin} -Ls "https://api.github.com/repos/MHSanaei/3x-ui/releases/latest" 2> /dev/null | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/')
|
||||||
if [[ ! -n "$tag_version" ]]; then
|
if [[ ! -n "$tag_version" ]]; then
|
||||||
echo -e "${yellow}Trying to fetch version with IPv4...${plain}"
|
echo -e "${yellow}Trying to fetch version with IPv4...${plain}"
|
||||||
tag_version=$(${curl_bin} -4 -Ls "https://api.github.com/repos/MHSanaei/3x-ui/releases/latest" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/')
|
tag_version=$(${curl_bin} -4 -Ls "https://api.github.com/repos/MHSanaei/3x-ui/releases/latest" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/')
|
||||||
@@ -787,10 +787,10 @@ update_x-ui() {
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
echo -e "Got x-ui latest version: ${tag_version}, beginning the installation..."
|
echo -e "Got x-ui latest version: ${tag_version}, beginning the installation..."
|
||||||
${curl_bin} -fLRo ${xui_folder}-linux-$(arch).tar.gz https://github.com/MHSanaei/3x-ui/releases/download/${tag_version}/x-ui-linux-$(arch).tar.gz 2>/dev/null
|
${curl_bin} -fLRo ${xui_folder}-linux-$(arch).tar.gz https://github.com/MHSanaei/3x-ui/releases/download/${tag_version}/x-ui-linux-$(arch).tar.gz 2> /dev/null
|
||||||
if [[ $? -ne 0 ]]; then
|
if [[ $? -ne 0 ]]; then
|
||||||
echo -e "${yellow}Trying to fetch version with IPv4...${plain}"
|
echo -e "${yellow}Trying to fetch version with IPv4...${plain}"
|
||||||
${curl_bin} -4fLRo ${xui_folder}-linux-$(arch).tar.gz https://github.com/MHSanaei/3x-ui/releases/download/${tag_version}/x-ui-linux-$(arch).tar.gz 2>/dev/null
|
${curl_bin} -4fLRo ${xui_folder}-linux-$(arch).tar.gz https://github.com/MHSanaei/3x-ui/releases/download/${tag_version}/x-ui-linux-$(arch).tar.gz 2> /dev/null
|
||||||
if [[ $? -ne 0 ]]; then
|
if [[ $? -ne 0 ]]; then
|
||||||
_fail "ERROR: Failed to download x-ui, please be sure that your server can access GitHub"
|
_fail "ERROR: Failed to download x-ui, please be sure that your server can access GitHub"
|
||||||
fi
|
fi
|
||||||
@@ -800,97 +800,97 @@ update_x-ui() {
|
|||||||
echo -e "${green}Stopping x-ui...${plain}"
|
echo -e "${green}Stopping x-ui...${plain}"
|
||||||
if [[ $release == "alpine" ]]; then
|
if [[ $release == "alpine" ]]; then
|
||||||
if [ -f "/etc/init.d/x-ui" ]; then
|
if [ -f "/etc/init.d/x-ui" ]; then
|
||||||
rc-service x-ui stop >/dev/null 2>&1
|
rc-service x-ui stop > /dev/null 2>&1
|
||||||
rc-update del x-ui >/dev/null 2>&1
|
rc-update del x-ui > /dev/null 2>&1
|
||||||
echo -e "${green}Removing old service unit version...${plain}"
|
echo -e "${green}Removing old service unit version...${plain}"
|
||||||
rm -f /etc/init.d/x-ui >/dev/null 2>&1
|
rm -f /etc/init.d/x-ui > /dev/null 2>&1
|
||||||
else
|
else
|
||||||
rm x-ui-linux-$(arch).tar.gz -f >/dev/null 2>&1
|
rm x-ui-linux-$(arch).tar.gz -f > /dev/null 2>&1
|
||||||
_fail "ERROR: x-ui service unit not installed."
|
_fail "ERROR: x-ui service unit not installed."
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
if [ -f "${xui_service}/x-ui.service" ]; then
|
if [ -f "${xui_service}/x-ui.service" ]; then
|
||||||
systemctl stop x-ui >/dev/null 2>&1
|
systemctl stop x-ui > /dev/null 2>&1
|
||||||
systemctl disable x-ui >/dev/null 2>&1
|
systemctl disable x-ui > /dev/null 2>&1
|
||||||
echo -e "${green}Removing old systemd unit version...${plain}"
|
echo -e "${green}Removing old systemd unit version...${plain}"
|
||||||
rm ${xui_service}/x-ui.service -f >/dev/null 2>&1
|
rm ${xui_service}/x-ui.service -f > /dev/null 2>&1
|
||||||
systemctl daemon-reload >/dev/null 2>&1
|
systemctl daemon-reload > /dev/null 2>&1
|
||||||
else
|
else
|
||||||
rm x-ui-linux-$(arch).tar.gz -f >/dev/null 2>&1
|
rm x-ui-linux-$(arch).tar.gz -f > /dev/null 2>&1
|
||||||
_fail "ERROR: x-ui systemd unit not installed."
|
_fail "ERROR: x-ui systemd unit not installed."
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
echo -e "${green}Removing old x-ui version...${plain}"
|
echo -e "${green}Removing old x-ui version...${plain}"
|
||||||
rm ${xui_folder} -f >/dev/null 2>&1
|
rm ${xui_folder} -f > /dev/null 2>&1
|
||||||
rm ${xui_folder}/x-ui.service -f >/dev/null 2>&1
|
rm ${xui_folder}/x-ui.service -f > /dev/null 2>&1
|
||||||
rm ${xui_folder}/x-ui.service.debian -f >/dev/null 2>&1
|
rm ${xui_folder}/x-ui.service.debian -f > /dev/null 2>&1
|
||||||
rm ${xui_folder}/x-ui.service.arch -f >/dev/null 2>&1
|
rm ${xui_folder}/x-ui.service.arch -f > /dev/null 2>&1
|
||||||
rm ${xui_folder}/x-ui.service.rhel -f >/dev/null 2>&1
|
rm ${xui_folder}/x-ui.service.rhel -f > /dev/null 2>&1
|
||||||
rm ${xui_folder}/x-ui -f >/dev/null 2>&1
|
rm ${xui_folder}/x-ui -f > /dev/null 2>&1
|
||||||
rm ${xui_folder}/x-ui.sh -f >/dev/null 2>&1
|
rm ${xui_folder}/x-ui.sh -f > /dev/null 2>&1
|
||||||
echo -e "${green}Removing old xray version...${plain}"
|
echo -e "${green}Removing old xray version...${plain}"
|
||||||
rm ${xui_folder}/bin/xray-linux-amd64 -f >/dev/null 2>&1
|
rm ${xui_folder}/bin/xray-linux-amd64 -f > /dev/null 2>&1
|
||||||
echo -e "${green}Removing old README and LICENSE file...${plain}"
|
echo -e "${green}Removing old README and LICENSE file...${plain}"
|
||||||
rm ${xui_folder}/bin/README.md -f >/dev/null 2>&1
|
rm ${xui_folder}/bin/README.md -f > /dev/null 2>&1
|
||||||
rm ${xui_folder}/bin/LICENSE -f >/dev/null 2>&1
|
rm ${xui_folder}/bin/LICENSE -f > /dev/null 2>&1
|
||||||
else
|
else
|
||||||
rm x-ui-linux-$(arch).tar.gz -f >/dev/null 2>&1
|
rm x-ui-linux-$(arch).tar.gz -f > /dev/null 2>&1
|
||||||
_fail "ERROR: x-ui not installed."
|
_fail "ERROR: x-ui not installed."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo -e "${green}Installing new x-ui version...${plain}"
|
echo -e "${green}Installing new x-ui version...${plain}"
|
||||||
tar zxvf x-ui-linux-$(arch).tar.gz >/dev/null 2>&1
|
tar zxvf x-ui-linux-$(arch).tar.gz > /dev/null 2>&1
|
||||||
rm x-ui-linux-$(arch).tar.gz -f >/dev/null 2>&1
|
rm x-ui-linux-$(arch).tar.gz -f > /dev/null 2>&1
|
||||||
cd x-ui >/dev/null 2>&1
|
cd x-ui > /dev/null 2>&1
|
||||||
chmod +x x-ui >/dev/null 2>&1
|
chmod +x x-ui > /dev/null 2>&1
|
||||||
|
|
||||||
# Check the system's architecture and rename the file accordingly
|
# Check the system's architecture and rename the file accordingly
|
||||||
if [[ $(arch) == "armv5" || $(arch) == "armv6" || $(arch) == "armv7" ]]; then
|
if [[ $(arch) == "armv5" || $(arch) == "armv6" || $(arch) == "armv7" ]]; then
|
||||||
mv bin/xray-linux-$(arch) bin/xray-linux-arm >/dev/null 2>&1
|
mv bin/xray-linux-$(arch) bin/xray-linux-arm > /dev/null 2>&1
|
||||||
chmod +x bin/xray-linux-arm >/dev/null 2>&1
|
chmod +x bin/xray-linux-arm > /dev/null 2>&1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
chmod +x x-ui bin/xray-linux-$(arch) >/dev/null 2>&1
|
chmod +x x-ui bin/xray-linux-$(arch) > /dev/null 2>&1
|
||||||
|
|
||||||
echo -e "${green}Downloading and installing x-ui.sh script...${plain}"
|
echo -e "${green}Downloading and installing x-ui.sh script...${plain}"
|
||||||
${curl_bin} -fLRo /usr/bin/x-ui https://raw.githubusercontent.com/MHSanaei/3x-ui/main/x-ui.sh >/dev/null 2>&1
|
${curl_bin} -fLRo /usr/bin/x-ui https://raw.githubusercontent.com/MHSanaei/3x-ui/main/x-ui.sh > /dev/null 2>&1
|
||||||
if [[ $? -ne 0 ]]; then
|
if [[ $? -ne 0 ]]; then
|
||||||
echo -e "${yellow}Trying to fetch x-ui with IPv4...${plain}"
|
echo -e "${yellow}Trying to fetch x-ui with IPv4...${plain}"
|
||||||
${curl_bin} -4fLRo /usr/bin/x-ui https://raw.githubusercontent.com/MHSanaei/3x-ui/main/x-ui.sh >/dev/null 2>&1
|
${curl_bin} -4fLRo /usr/bin/x-ui https://raw.githubusercontent.com/MHSanaei/3x-ui/main/x-ui.sh > /dev/null 2>&1
|
||||||
if [[ $? -ne 0 ]]; then
|
if [[ $? -ne 0 ]]; then
|
||||||
_fail "ERROR: Failed to download x-ui.sh script, please be sure that your server can access GitHub"
|
_fail "ERROR: Failed to download x-ui.sh script, please be sure that your server can access GitHub"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
chmod +x ${xui_folder}/x-ui.sh >/dev/null 2>&1
|
chmod +x ${xui_folder}/x-ui.sh > /dev/null 2>&1
|
||||||
chmod +x /usr/bin/x-ui >/dev/null 2>&1
|
chmod +x /usr/bin/x-ui > /dev/null 2>&1
|
||||||
mkdir -p /var/log/x-ui >/dev/null 2>&1
|
mkdir -p /var/log/x-ui > /dev/null 2>&1
|
||||||
|
|
||||||
echo -e "${green}Changing owner...${plain}"
|
echo -e "${green}Changing owner...${plain}"
|
||||||
chown -R root:root ${xui_folder} >/dev/null 2>&1
|
chown -R root:root ${xui_folder} > /dev/null 2>&1
|
||||||
|
|
||||||
if [ -f "${xui_folder}/bin/config.json" ]; then
|
if [ -f "${xui_folder}/bin/config.json" ]; then
|
||||||
echo -e "${green}Changing on config file permissions...${plain}"
|
echo -e "${green}Changing on config file permissions...${plain}"
|
||||||
chmod 640 ${xui_folder}/bin/config.json >/dev/null 2>&1
|
chmod 640 ${xui_folder}/bin/config.json > /dev/null 2>&1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ $release == "alpine" ]]; then
|
if [[ $release == "alpine" ]]; then
|
||||||
echo -e "${green}Downloading and installing startup unit x-ui.rc...${plain}"
|
echo -e "${green}Downloading and installing startup unit x-ui.rc...${plain}"
|
||||||
${curl_bin} -fLRo /etc/init.d/x-ui https://raw.githubusercontent.com/MHSanaei/3x-ui/main/x-ui.rc >/dev/null 2>&1
|
${curl_bin} -fLRo /etc/init.d/x-ui https://raw.githubusercontent.com/MHSanaei/3x-ui/main/x-ui.rc > /dev/null 2>&1
|
||||||
if [[ $? -ne 0 ]]; then
|
if [[ $? -ne 0 ]]; then
|
||||||
${curl_bin} -4fLRo /etc/init.d/x-ui https://raw.githubusercontent.com/MHSanaei/3x-ui/main/x-ui.rc >/dev/null 2>&1
|
${curl_bin} -4fLRo /etc/init.d/x-ui https://raw.githubusercontent.com/MHSanaei/3x-ui/main/x-ui.rc > /dev/null 2>&1
|
||||||
if [[ $? -ne 0 ]]; then
|
if [[ $? -ne 0 ]]; then
|
||||||
_fail "ERROR: Failed to download startup unit x-ui.rc, please be sure that your server can access GitHub"
|
_fail "ERROR: Failed to download startup unit x-ui.rc, please be sure that your server can access GitHub"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
chmod +x /etc/init.d/x-ui >/dev/null 2>&1
|
chmod +x /etc/init.d/x-ui > /dev/null 2>&1
|
||||||
chown root:root /etc/init.d/x-ui >/dev/null 2>&1
|
chown root:root /etc/init.d/x-ui > /dev/null 2>&1
|
||||||
rc-update add x-ui >/dev/null 2>&1
|
rc-update add x-ui > /dev/null 2>&1
|
||||||
rc-service x-ui start >/dev/null 2>&1
|
rc-service x-ui start > /dev/null 2>&1
|
||||||
else
|
else
|
||||||
if [ -f "x-ui.service" ]; then
|
if [ -f "x-ui.service" ]; then
|
||||||
echo -e "${green}Installing systemd unit...${plain}"
|
echo -e "${green}Installing systemd unit...${plain}"
|
||||||
cp -f x-ui.service ${xui_service}/ >/dev/null 2>&1
|
cp -f x-ui.service ${xui_service}/ > /dev/null 2>&1
|
||||||
if [[ $? -ne 0 ]]; then
|
if [[ $? -ne 0 ]]; then
|
||||||
echo -e "${red}Failed to copy x-ui.service${plain}"
|
echo -e "${red}Failed to copy x-ui.service${plain}"
|
||||||
exit 1
|
exit 1
|
||||||
@@ -901,30 +901,30 @@ update_x-ui() {
|
|||||||
ubuntu | debian | armbian)
|
ubuntu | debian | armbian)
|
||||||
if [ -f "x-ui.service.debian" ]; then
|
if [ -f "x-ui.service.debian" ]; then
|
||||||
echo -e "${green}Installing debian-like systemd unit...${plain}"
|
echo -e "${green}Installing debian-like systemd unit...${plain}"
|
||||||
cp -f x-ui.service.debian ${xui_service}/x-ui.service >/dev/null 2>&1
|
cp -f x-ui.service.debian ${xui_service}/x-ui.service > /dev/null 2>&1
|
||||||
if [[ $? -eq 0 ]]; then
|
if [[ $? -eq 0 ]]; then
|
||||||
service_installed=true
|
service_installed=true
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
arch | manjaro | parch)
|
arch | manjaro | parch)
|
||||||
if [ -f "x-ui.service.arch" ]; then
|
if [ -f "x-ui.service.arch" ]; then
|
||||||
echo -e "${green}Installing arch-like systemd unit...${plain}"
|
echo -e "${green}Installing arch-like systemd unit...${plain}"
|
||||||
cp -f x-ui.service.arch ${xui_service}/x-ui.service >/dev/null 2>&1
|
cp -f x-ui.service.arch ${xui_service}/x-ui.service > /dev/null 2>&1
|
||||||
if [[ $? -eq 0 ]]; then
|
if [[ $? -eq 0 ]]; then
|
||||||
service_installed=true
|
service_installed=true
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
if [ -f "x-ui.service.rhel" ]; then
|
if [ -f "x-ui.service.rhel" ]; then
|
||||||
echo -e "${green}Installing rhel-like systemd unit...${plain}"
|
echo -e "${green}Installing rhel-like systemd unit...${plain}"
|
||||||
cp -f x-ui.service.rhel ${xui_service}/x-ui.service >/dev/null 2>&1
|
cp -f x-ui.service.rhel ${xui_service}/x-ui.service > /dev/null 2>&1
|
||||||
if [[ $? -eq 0 ]]; then
|
if [[ $? -eq 0 ]]; then
|
||||||
service_installed=true
|
service_installed=true
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
# If service file not found in tar.gz, download from GitHub
|
# If service file not found in tar.gz, download from GitHub
|
||||||
@@ -932,14 +932,14 @@ update_x-ui() {
|
|||||||
echo -e "${yellow}Service files not found in tar.gz, downloading from GitHub...${plain}"
|
echo -e "${yellow}Service files not found in tar.gz, downloading from GitHub...${plain}"
|
||||||
case "${release}" in
|
case "${release}" in
|
||||||
ubuntu | debian | armbian)
|
ubuntu | debian | armbian)
|
||||||
${curl_bin} -4fLRo ${xui_service}/x-ui.service https://raw.githubusercontent.com/MHSanaei/3x-ui/main/x-ui.service.debian >/dev/null 2>&1
|
${curl_bin} -4fLRo ${xui_service}/x-ui.service https://raw.githubusercontent.com/MHSanaei/3x-ui/main/x-ui.service.debian > /dev/null 2>&1
|
||||||
;;
|
;;
|
||||||
arch | manjaro | parch)
|
arch | manjaro | parch)
|
||||||
${curl_bin} -4fLRo ${xui_service}/x-ui.service https://raw.githubusercontent.com/MHSanaei/3x-ui/main/x-ui.service.arch >/dev/null 2>&1
|
${curl_bin} -4fLRo ${xui_service}/x-ui.service https://raw.githubusercontent.com/MHSanaei/3x-ui/main/x-ui.service.arch > /dev/null 2>&1
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
${curl_bin} -4fLRo ${xui_service}/x-ui.service https://raw.githubusercontent.com/MHSanaei/3x-ui/main/x-ui.service.rhel >/dev/null 2>&1
|
${curl_bin} -4fLRo ${xui_service}/x-ui.service https://raw.githubusercontent.com/MHSanaei/3x-ui/main/x-ui.service.rhel > /dev/null 2>&1
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
if [[ $? -ne 0 ]]; then
|
if [[ $? -ne 0 ]]; then
|
||||||
@@ -948,11 +948,11 @@ update_x-ui() {
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
chown root:root ${xui_service}/x-ui.service >/dev/null 2>&1
|
chown root:root ${xui_service}/x-ui.service > /dev/null 2>&1
|
||||||
chmod 644 ${xui_service}/x-ui.service >/dev/null 2>&1
|
chmod 644 ${xui_service}/x-ui.service > /dev/null 2>&1
|
||||||
systemctl daemon-reload >/dev/null 2>&1
|
systemctl daemon-reload > /dev/null 2>&1
|
||||||
systemctl enable x-ui >/dev/null 2>&1
|
systemctl enable x-ui > /dev/null 2>&1
|
||||||
systemctl start x-ui >/dev/null 2>&1
|
systemctl start x-ui > /dev/null 2>&1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
config_after_update
|
config_after_update
|
||||||
|
|||||||
@@ -697,7 +697,6 @@ class TlsStreamSettings extends XrayCommonClass {
|
|||||||
certificates = [new TlsStreamSettings.Cert()],
|
certificates = [new TlsStreamSettings.Cert()],
|
||||||
alpn = [ALPN_OPTION.H2, ALPN_OPTION.HTTP1],
|
alpn = [ALPN_OPTION.H2, ALPN_OPTION.HTTP1],
|
||||||
echServerKeys = '',
|
echServerKeys = '',
|
||||||
echForceQuery = 'none',
|
|
||||||
settings = new TlsStreamSettings.Settings()
|
settings = new TlsStreamSettings.Settings()
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
@@ -711,7 +710,6 @@ class TlsStreamSettings extends XrayCommonClass {
|
|||||||
this.certs = certificates;
|
this.certs = certificates;
|
||||||
this.alpn = alpn;
|
this.alpn = alpn;
|
||||||
this.echServerKeys = echServerKeys;
|
this.echServerKeys = echServerKeys;
|
||||||
this.echForceQuery = echForceQuery;
|
|
||||||
this.settings = settings;
|
this.settings = settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -744,7 +742,6 @@ class TlsStreamSettings extends XrayCommonClass {
|
|||||||
certs,
|
certs,
|
||||||
json.alpn,
|
json.alpn,
|
||||||
json.echServerKeys,
|
json.echServerKeys,
|
||||||
json.echForceQuery,
|
|
||||||
settings,
|
settings,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -761,7 +758,6 @@ class TlsStreamSettings extends XrayCommonClass {
|
|||||||
certificates: TlsStreamSettings.toJsonArray(this.certs),
|
certificates: TlsStreamSettings.toJsonArray(this.certs),
|
||||||
alpn: this.alpn,
|
alpn: this.alpn,
|
||||||
echServerKeys: this.echServerKeys,
|
echServerKeys: this.echServerKeys,
|
||||||
echForceQuery: this.echForceQuery,
|
|
||||||
settings: this.settings,
|
settings: this.settings,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
{{ define "page/head_start" }}
|
{{ define "page/head_start" }}
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="renderer" content="webkit">
|
<meta name="renderer" content="webkit">
|
||||||
@@ -12,6 +13,7 @@
|
|||||||
[v-cloak] {
|
[v-cloak] {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* vazirmatn-regular - arabic_latin_latin-ext */
|
/* vazirmatn-regular - arabic_latin_latin-ext */
|
||||||
@font-face {
|
@font-face {
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
@@ -21,6 +23,7 @@
|
|||||||
src: url('{{ .base_path }}assets/Vazirmatn-UI-NL-Regular.woff2') format('woff2');
|
src: url('{{ .base_path }}assets/Vazirmatn-UI-NL-Regular.woff2') format('woff2');
|
||||||
unicode-range: U+0600-06FF, U+200C-200E, U+2010-2011, U+204F, U+2E41, U+FB50-FDFF, U+FE80-FEFC, U+0030-0039;
|
unicode-range: U+0600-06FF, U+200C-200E, U+2010-2011, U+204F, U+2E41, U+FB50-FDFF, U+FE80-FEFC, U+0030-0039;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Vazirmatn', 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Vazirmatn', 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
||||||
}
|
}
|
||||||
@@ -34,59 +37,69 @@
|
|||||||
overscroll-behavior-x: contain;
|
overscroll-behavior-x: contain;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
padding: 0 !important; /* Remove padding for arrows */
|
padding: 0 !important;
|
||||||
|
/* Remove padding for arrows */
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-tabs-nav-wrap {
|
.ant-tabs-nav-wrap {
|
||||||
overflow: visible !important;
|
overflow: visible !important;
|
||||||
padding: 0 !important;
|
padding: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-tabs-nav-scroll {
|
.ant-tabs-nav-scroll {
|
||||||
overflow: visible !important;
|
overflow: visible !important;
|
||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-tabs-nav {
|
.ant-tabs-nav {
|
||||||
display: flex !important;
|
display: flex !important;
|
||||||
transform: none !important; /* Disable JS transform */
|
transform: none !important;
|
||||||
width: auto !important;
|
/* Disable JS transform */
|
||||||
margin: 0 !important;
|
width: auto !important;
|
||||||
|
margin: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-tabs-tab-prev,
|
.ant-tabs-tab-prev,
|
||||||
.ant-tabs-tab-next {
|
.ant-tabs-tab-next {
|
||||||
display: none !important; /* Hide arrows */
|
display: none !important;
|
||||||
|
/* Hide arrows */
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-tabs-nav-container::-webkit-scrollbar {
|
.ant-tabs-nav-container::-webkit-scrollbar {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<title>{{ .host }} – {{ i18n .title}}</title>
|
<title>{{ .host }} – {{ i18n .title}}</title>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
{{ define "page/head_end" }}
|
{{ define "page/head_end" }}
|
||||||
</head>
|
</head>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
{{ define "page/body_start" }}
|
{{ define "page/body_start" }}
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div id="message"></div>
|
<div id="message"></div>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
{{ define "page/body_scripts" }}
|
{{ define "page/body_scripts" }}
|
||||||
<script src="{{ .base_path }}assets/vue/vue.min.js?{{ .cur_ver }}"></script>
|
<script src="{{ .base_path }}assets/vue/vue.min.js?{{ .cur_ver }}"></script>
|
||||||
<script src="{{ .base_path }}assets/moment/moment.min.js"></script>
|
<script src="{{ .base_path }}assets/moment/moment.min.js"></script>
|
||||||
<script src="{{ .base_path }}assets/ant-design-vue/antd.min.js"></script>
|
<script src="{{ .base_path }}assets/ant-design-vue/antd.min.js"></script>
|
||||||
<script src="{{ .base_path }}assets/axios/axios.min.js?{{ .cur_ver }}"></script>
|
<script src="{{ .base_path }}assets/axios/axios.min.js?{{ .cur_ver }}"></script>
|
||||||
<script src="{{ .base_path }}assets/qs/qs.min.js"></script>
|
<script src="{{ .base_path }}assets/qs/qs.min.js"></script>
|
||||||
<script src="{{ .base_path }}assets/js/axios-init.js?{{ .cur_ver }}"></script>
|
<script src="{{ .base_path }}assets/js/axios-init.js?{{ .cur_ver }}"></script>
|
||||||
<script src="{{ .base_path }}assets/js/util/index.js?{{ .cur_ver }}"></script>
|
<script src="{{ .base_path }}assets/js/util/index.js?{{ .cur_ver }}"></script>
|
||||||
<script>
|
<script>
|
||||||
const basePath = '{{ .base_path }}';
|
const basePath = '{{ .base_path }}';
|
||||||
axios.defaults.baseURL = basePath;
|
axios.defaults.baseURL = basePath;
|
||||||
</script>
|
</script>
|
||||||
<script src="{{ .base_path }}assets/js/websocket.js?{{ .cur_ver }}"></script>
|
<script src="{{ .base_path }}assets/js/websocket.js?{{ .cur_ver }}"></script>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
{{ define "page/body_end" }}
|
{{ define "page/body_end" }}
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
@@ -2,30 +2,39 @@
|
|||||||
<template slot="actions" slot-scope="text, client, index">
|
<template slot="actions" slot-scope="text, client, index">
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">{{ i18n "qrCode" }}</template>
|
<template slot="title">{{ i18n "qrCode" }}</template>
|
||||||
<a-icon :style="{ fontSize: '22px', marginInlineStart: '14px' }" class="normal-icon" type="qrcode" v-if="record.hasLink()" @click="showQrcode(record.id,client);"></a-icon>
|
<a-icon :style="{ fontSize: '22px', marginInlineStart: '14px' }" class="normal-icon" type="qrcode"
|
||||||
|
v-if="record.hasLink()" @click="showQrcode(record.id,client);"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">{{ i18n "pages.client.edit" }}</template>
|
<template slot="title">{{ i18n "pages.client.edit" }}</template>
|
||||||
<a-icon :style="{ fontSize: '22px' }" class="normal-icon" type="edit" @click="openEditClient(record.id,client);"></a-icon>
|
<a-icon :style="{ fontSize: '22px' }" class="normal-icon" type="edit"
|
||||||
|
@click="openEditClient(record.id,client);"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">{{ i18n "info" }}</template>
|
<template slot="title">{{ i18n "info" }}</template>
|
||||||
<a-icon :style="{ fontSize: '22px' }" class="normal-icon" type="info-circle" @click="showInfo(record.id,client);"></a-icon>
|
<a-icon :style="{ fontSize: '22px' }" class="normal-icon" type="info-circle"
|
||||||
|
@click="showInfo(record.id,client);"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">{{ i18n "pages.inbounds.resetTraffic" }}</template>
|
<template slot="title">{{ i18n "pages.inbounds.resetTraffic" }}</template>
|
||||||
<a-popconfirm @confirm="resetClientTraffic(client,record.id,false)" title='{{ i18n "pages.inbounds.resetTrafficContent"}}' :overlay-class-name="themeSwitcher.currentTheme" ok-text='{{ i18n "reset"}}' cancel-text='{{ i18n "cancel"}}'>
|
<a-popconfirm @confirm="resetClientTraffic(client,record.id,false)"
|
||||||
|
title='{{ i18n "pages.inbounds.resetTrafficContent"}}' :overlay-class-name="themeSwitcher.currentTheme"
|
||||||
|
ok-text='{{ i18n "reset"}}' cancel-text='{{ i18n "cancel"}}'>
|
||||||
<a-icon slot="icon" type="question-circle-o" :style="{ color: 'var(--color-primary-100)'}"></a-icon>
|
<a-icon slot="icon" type="question-circle-o" :style="{ color: 'var(--color-primary-100)'}"></a-icon>
|
||||||
<a-icon :style="{ fontSize: '22px', cursor: 'pointer' }" class="normal-icon" type="retweet" v-if="client.email.length > 0"></a-icon>
|
<a-icon :style="{ fontSize: '22px', cursor: 'pointer' }" class="normal-icon" type="retweet"
|
||||||
|
v-if="client.email.length > 0"></a-icon>
|
||||||
</a-popconfirm>
|
</a-popconfirm>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
<span :style="{ color: '#FF4D4F' }"> {{ i18n "delete"}}</span>
|
<span :style="{ color: '#FF4D4F' }"> {{ i18n "delete"}}</span>
|
||||||
</template>
|
</template>
|
||||||
<a-popconfirm @confirm="delClient(record.id,client,false)" title='{{ i18n "pages.inbounds.deleteClientContent"}}' :overlay-class-name="themeSwitcher.currentTheme" ok-text='{{ i18n "delete"}}' ok-type="danger" cancel-text='{{ i18n "cancel"}}'>
|
<a-popconfirm @confirm="delClient(record.id,client,false)" title='{{ i18n "pages.inbounds.deleteClientContent"}}'
|
||||||
|
:overlay-class-name="themeSwitcher.currentTheme" ok-text='{{ i18n "delete"}}' ok-type="danger"
|
||||||
|
cancel-text='{{ i18n "cancel"}}'>
|
||||||
<a-icon slot="icon" type="question-circle-o" :style="{ color: '#e04141' }"></a-icon>
|
<a-icon slot="icon" type="question-circle-o" :style="{ color: '#e04141' }"></a-icon>
|
||||||
<a-icon :style="{ fontSize: '22px', cursor: 'pointer' }" class="delete-icon" type="delete" v-if="isRemovable(record.id)"></a-icon>
|
<a-icon :style="{ fontSize: '22px', cursor: 'pointer' }" class="delete-icon" type="delete"
|
||||||
|
v-if="isRemovable(record.id)"></a-icon>
|
||||||
</a-popconfirm>
|
</a-popconfirm>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</template>
|
</template>
|
||||||
@@ -34,7 +43,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<template slot="online" slot-scope="text, client, index">
|
<template slot="online" slot-scope="text, client, index">
|
||||||
<a-popover :overlay-class-name="themeSwitcher.currentTheme">
|
<a-popover :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
<template slot="content" >
|
<template slot="content">
|
||||||
{{ i18n "lastOnline" }}: [[ formatLastOnline(client.email) ]]
|
{{ i18n "lastOnline" }}: [[ formatLastOnline(client.email) ]]
|
||||||
</template>
|
</template>
|
||||||
<template v-if="client.enable && isClientOnline(client.email)">
|
<template v-if="client.enable && isClientOnline(client.email)">
|
||||||
@@ -53,7 +62,8 @@
|
|||||||
<template v-else-if="!client.enable">{{ i18n "disabled" }}</template>
|
<template v-else-if="!client.enable">{{ i18n "disabled" }}</template>
|
||||||
<template v-else-if="client.enable && isClientOnline(client.email)">{{ i18n "online" }}</template>
|
<template v-else-if="client.enable && isClientOnline(client.email)">{{ i18n "online" }}</template>
|
||||||
</template>
|
</template>
|
||||||
<a-badge :class="isClientOnline(client.email)? 'online-animation' : ''" :color="client.enable ? statsExpColor(record, client.email) : themeSwitcher.isDarkTheme ? '#2c3950' : '#bcbcbc'"></a-badge>
|
<a-badge :class="isClientOnline(client.email)? 'online-animation' : ''"
|
||||||
|
:color="client.enable ? statsExpColor(record, client.email) : themeSwitcher.isDarkTheme ? '#2c3950' : '#bcbcbc'"></a-badge>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-space direction="vertical" :size="2">
|
<a-space direction="vertical" :size="2">
|
||||||
<span class="client-email">[[ client.email ]]</span>
|
<span class="client-email">[[ client.email ]]</span>
|
||||||
@@ -87,10 +97,13 @@
|
|||||||
<tr class="tr-table-box">
|
<tr class="tr-table-box">
|
||||||
<td class="tr-table-rt"> [[ SizeFormatter.sizeFormat(getSumStats(record, client.email)) ]] </td>
|
<td class="tr-table-rt"> [[ SizeFormatter.sizeFormat(getSumStats(record, client.email)) ]] </td>
|
||||||
<td class="tr-table-bar" v-if="!client.enable">
|
<td class="tr-table-bar" v-if="!client.enable">
|
||||||
<a-progress :stroke-color="themeSwitcher.isDarkTheme ? 'rgb(72 84 105)' : '#bcbcbc'" :show-info="false" :percent="statsProgress(record, client.email)" />
|
<a-progress :stroke-color="themeSwitcher.isDarkTheme ? 'rgb(72 84 105)' : '#bcbcbc'" :show-info="false"
|
||||||
|
:percent="statsProgress(record, client.email)" />
|
||||||
</td>
|
</td>
|
||||||
<td class="tr-table-bar" v-else-if="client.totalGB > 0">
|
<td class="tr-table-bar" v-else-if="client.totalGB > 0">
|
||||||
<a-progress :stroke-color="clientStatsColor(record, client.email)" :show-info="false" :status="isClientDepleted(record, client.email)? 'exception' : ''" :percent="statsProgress(record, client.email)" />
|
<a-progress :stroke-color="clientStatsColor(record, client.email)" :show-info="false"
|
||||||
|
:status="isClientDepleted(record, client.email)? 'exception' : ''"
|
||||||
|
:percent="statsProgress(record, client.email)" />
|
||||||
</td>
|
</td>
|
||||||
<td v-else class="infinite-bar tr-table-bar">
|
<td v-else class="infinite-bar tr-table-bar">
|
||||||
<a-progress :show-info="false" :percent="100"></a-progress>
|
<a-progress :show-info="false" :percent="100"></a-progress>
|
||||||
@@ -118,7 +131,8 @@
|
|||||||
<tr class="tr-table-box">
|
<tr class="tr-table-box">
|
||||||
<td class="tr-table-rt"> [[ IntlUtil.formatRelativeTime(client.expiryTime) ]] </td>
|
<td class="tr-table-rt"> [[ IntlUtil.formatRelativeTime(client.expiryTime) ]] </td>
|
||||||
<td class="infinite-bar tr-table-bar">
|
<td class="infinite-bar tr-table-bar">
|
||||||
<a-progress :show-info="false" :status="isClientDepleted(record, client.email)? 'exception' : ''" :percent="expireProgress(client.expiryTime, client.reset)" />
|
<a-progress :show-info="false" :status="isClientDepleted(record, client.email)? 'exception' : ''"
|
||||||
|
:percent="expireProgress(client.expiryTime, client.reset)" />
|
||||||
</td>
|
</td>
|
||||||
<td class="tr-table-lt">[[ client.reset + "d" ]]</td>
|
<td class="tr-table-lt">[[ client.reset + "d" ]]</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -131,11 +145,16 @@
|
|||||||
<span v-if="client.expiryTime < 0">{{ i18n "pages.client.delayedStart" }}</span>
|
<span v-if="client.expiryTime < 0">{{ i18n "pages.client.delayedStart" }}</span>
|
||||||
<span v-else>[[ IntlUtil.formatDate(client.expiryTime) ]]</span>
|
<span v-else>[[ IntlUtil.formatDate(client.expiryTime) ]]</span>
|
||||||
</template>
|
</template>
|
||||||
<a-tag :style="{ minWidth: '50px', border: 'none' }" :color="ColorUtils.userExpiryColor(app.expireDiff, client, themeSwitcher.isDarkTheme)"> [[ IntlUtil.formatRelativeTime(client.expiryTime) ]] </a-tag>
|
<a-tag :style="{ minWidth: '50px', border: 'none' }"
|
||||||
|
:color="ColorUtils.userExpiryColor(app.expireDiff, client, themeSwitcher.isDarkTheme)"> [[
|
||||||
|
IntlUtil.formatRelativeTime(client.expiryTime) ]] </a-tag>
|
||||||
</a-popover>
|
</a-popover>
|
||||||
<a-tag v-else :color="ColorUtils.userExpiryColor(app.expireDiff, client, themeSwitcher.isDarkTheme)" :style="{ border: 'none' }" class="infinite-tag">
|
<a-tag v-else :color="ColorUtils.userExpiryColor(app.expireDiff, client, themeSwitcher.isDarkTheme)"
|
||||||
|
:style="{ border: 'none' }" class="infinite-tag">
|
||||||
<svg height="10px" width="14px" viewBox="0 0 640 512" fill="currentColor">
|
<svg height="10px" width="14px" viewBox="0 0 640 512" fill="currentColor">
|
||||||
<path d="M484.4 96C407 96 349.2 164.1 320 208.5C290.8 164.1 233 96 155.6 96C69.75 96 0 167.8 0 256s69.75 160 155.6 160C233.1 416 290.8 347.9 320 303.5C349.2 347.9 407 416 484.4 416C570.3 416 640 344.2 640 256S570.3 96 484.4 96zM155.6 368C96.25 368 48 317.8 48 256s48.25-112 107.6-112c67.75 0 120.5 82.25 137.1 112C276 285.8 223.4 368 155.6 368zM484.4 368c-67.75 0-120.5-82.25-137.1-112C364 226.2 416.6 144 484.4 144C543.8 144 592 194.2 592 256S543.8 368 484.4 368z" fill="currentColor"></path>
|
<path
|
||||||
|
d="M484.4 96C407 96 349.2 164.1 320 208.5C290.8 164.1 233 96 155.6 96C69.75 96 0 167.8 0 256s69.75 160 155.6 160C233.1 416 290.8 347.9 320 303.5C349.2 347.9 407 416 484.4 416C570.3 416 640 344.2 640 256S570.3 96 484.4 96zM155.6 368C96.25 368 48 317.8 48 256s48.25-112 107.6-112c67.75 0 120.5 82.25 137.1 112C276 285.8 223.4 368 155.6 368zM484.4 368c-67.75 0-120.5-82.25-137.1-112C364 226.2 416.6 144 484.4 144C543.8 144 592 194.2 592 256S543.8 368 484.4 368z"
|
||||||
|
fill="currentColor"></path>
|
||||||
</svg>
|
</svg>
|
||||||
</a-tag>
|
</a-tag>
|
||||||
</template>
|
</template>
|
||||||
@@ -165,7 +184,8 @@
|
|||||||
<span :style="{ color: '#FF4D4F' }"> {{ i18n "delete"}}</span>
|
<span :style="{ color: '#FF4D4F' }"> {{ i18n "delete"}}</span>
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
<a-menu-item>
|
<a-menu-item>
|
||||||
<a-switch v-model="client.enable" size="small" @change="switchEnableClient(record.id, client, $event)"></a-switch>
|
<a-switch v-model="client.enable" size="small"
|
||||||
|
@change="switchEnableClient(record.id, client, $event)"></a-switch>
|
||||||
{{ i18n "enable"}}
|
{{ i18n "enable"}}
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
</a-menu>
|
</a-menu>
|
||||||
@@ -179,9 +199,11 @@
|
|||||||
<td colspan="3" :style="{ textAlign: 'center' }">{{ i18n "pages.inbounds.traffic" }}</td>
|
<td colspan="3" :style="{ textAlign: 'center' }">{{ i18n "pages.inbounds.traffic" }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td width="80px" :style="{ margin: '0', textAlign: 'right', fontSize: '1em' }"> [[ SizeFormatter.sizeFormat(getUpStats(record, client.email) + getDownStats(record, client.email)) ]] </td>
|
<td width="80px" :style="{ margin: '0', textAlign: 'right', fontSize: '1em' }"> [[
|
||||||
|
SizeFormatter.sizeFormat(getUpStats(record, client.email) + getDownStats(record, client.email)) ]] </td>
|
||||||
<td width="120px" v-if="!client.enable">
|
<td width="120px" v-if="!client.enable">
|
||||||
<a-progress :stroke-color="themeSwitcher.isDarkTheme ? 'rgb(72 84 105)' : '#bcbcbc'" :show-info="false" :percent="statsProgress(record, client.email)" />
|
<a-progress :stroke-color="themeSwitcher.isDarkTheme ? 'rgb(72 84 105)' : '#bcbcbc'" :show-info="false"
|
||||||
|
:percent="statsProgress(record, client.email)" />
|
||||||
</td>
|
</td>
|
||||||
<td width="120px" v-else-if="client.totalGB > 0">
|
<td width="120px" v-else-if="client.totalGB > 0">
|
||||||
<a-popover :overlay-class-name="themeSwitcher.currentTheme">
|
<a-popover :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
@@ -197,11 +219,14 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</template>
|
</template>
|
||||||
<a-progress :stroke-color="clientStatsColor(record, client.email)" :show-info="false" :status="isClientDepleted(record, client.email)? 'exception' : ''" :percent="statsProgress(record, client.email)" />
|
<a-progress :stroke-color="clientStatsColor(record, client.email)" :show-info="false"
|
||||||
|
:status="isClientDepleted(record, client.email)? 'exception' : ''"
|
||||||
|
:percent="statsProgress(record, client.email)" />
|
||||||
</a-popover>
|
</a-popover>
|
||||||
</td>
|
</td>
|
||||||
<td width="120px" v-else class="infinite-bar">
|
<td width="120px" v-else class="infinite-bar">
|
||||||
<a-progress :stroke-color="themeSwitcher.isDarkTheme ? '#2c1e32':'#F2EAF1'" :show-info="false" :percent="100"></a-progress>
|
<a-progress :stroke-color="themeSwitcher.isDarkTheme ? '#2c1e32':'#F2EAF1'" :show-info="false"
|
||||||
|
:percent="100"></a-progress>
|
||||||
</td>
|
</td>
|
||||||
<td width="80px">
|
<td width="80px">
|
||||||
<template v-if="client.totalGB > 0">[[ client._totalGB + "GB" ]]</template>
|
<template v-if="client.totalGB > 0">[[ client._totalGB + "GB" ]]</template>
|
||||||
@@ -216,14 +241,16 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<template v-if="client.expiryTime !=0 && client.reset >0">
|
<template v-if="client.expiryTime !=0 && client.reset >0">
|
||||||
<td width="80px" :style="{ margin: '0', textAlign: 'right', fontSize: '1em' }"> [[ IntlUtil.formatRelativeTime(client.expiryTime) ]] </td>
|
<td width="80px" :style="{ margin: '0', textAlign: 'right', fontSize: '1em' }"> [[
|
||||||
|
IntlUtil.formatRelativeTime(client.expiryTime) ]] </td>
|
||||||
<td width="120px" class="infinite-bar">
|
<td width="120px" class="infinite-bar">
|
||||||
<a-popover :overlay-class-name="themeSwitcher.currentTheme">
|
<a-popover :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
<template slot="content">
|
<template slot="content">
|
||||||
<span v-if="client.expiryTime < 0">{{ i18n "pages.client.delayedStart" }}</span>
|
<span v-if="client.expiryTime < 0">{{ i18n "pages.client.delayedStart" }}</span>
|
||||||
<span v-else>[[ IntlUtil.formatDate(client.expiryTime) ]]</span>
|
<span v-else>[[ IntlUtil.formatDate(client.expiryTime) ]]</span>
|
||||||
</template>
|
</template>
|
||||||
<a-progress :show-info="false" :status="isClientDepleted(record, client.email)? 'exception' : ''" :percent="expireProgress(client.expiryTime, client.reset)" />
|
<a-progress :show-info="false" :status="isClientDepleted(record, client.email)? 'exception' : ''"
|
||||||
|
:percent="expireProgress(client.expiryTime, client.reset)" />
|
||||||
</a-popover>
|
</a-popover>
|
||||||
</td>
|
</td>
|
||||||
<td width="60px">[[ client.reset + "d" ]]</td>
|
<td width="60px">[[ client.reset + "d" ]]</td>
|
||||||
@@ -235,11 +262,16 @@
|
|||||||
<span v-if="client.expiryTime < 0">{{ i18n "pages.client.delayedStart" }}</span>
|
<span v-if="client.expiryTime < 0">{{ i18n "pages.client.delayedStart" }}</span>
|
||||||
<span v-else>[[ IntlUtil.formatDate(client.expiryTime) ]]</span>
|
<span v-else>[[ IntlUtil.formatDate(client.expiryTime) ]]</span>
|
||||||
</template>
|
</template>
|
||||||
<a-tag :style="{ minWidth: '50px', border: 'none' }" :color="ColorUtils.userExpiryColor(app.expireDiff, client, themeSwitcher.isDarkTheme)"> [[ IntlUtil.formatRelativeTime(client.expiryTime) ]] </a-tag>
|
<a-tag :style="{ minWidth: '50px', border: 'none' }"
|
||||||
|
:color="ColorUtils.userExpiryColor(app.expireDiff, client, themeSwitcher.isDarkTheme)"> [[
|
||||||
|
IntlUtil.formatRelativeTime(client.expiryTime) ]] </a-tag>
|
||||||
</a-popover>
|
</a-popover>
|
||||||
<a-tag v-else :color="client.enable ? 'purple' : themeSwitcher.isDarkTheme ? '#2c3950' : '#bcbcbc'" class="infinite-tag">
|
<a-tag v-else :color="client.enable ? 'purple' : themeSwitcher.isDarkTheme ? '#2c3950' : '#bcbcbc'"
|
||||||
|
class="infinite-tag">
|
||||||
<svg height="10px" width="14px" viewBox="0 0 640 512" fill="currentColor">
|
<svg height="10px" width="14px" viewBox="0 0 640 512" fill="currentColor">
|
||||||
<path d="M484.4 96C407 96 349.2 164.1 320 208.5C290.8 164.1 233 96 155.6 96C69.75 96 0 167.8 0 256s69.75 160 155.6 160C233.1 416 290.8 347.9 320 303.5C349.2 347.9 407 416 484.4 416C570.3 416 640 344.2 640 256S570.3 96 484.4 96zM155.6 368C96.25 368 48 317.8 48 256s48.25-112 107.6-112c67.75 0 120.5 82.25 137.1 112C276 285.8 223.4 368 155.6 368zM484.4 368c-67.75 0-120.5-82.25-137.1-112C364 226.2 416.6 144 484.4 144C543.8 144 592 194.2 592 256S543.8 368 484.4 368z" fill="currentColor"></path>
|
<path
|
||||||
|
d="M484.4 96C407 96 349.2 164.1 320 208.5C290.8 164.1 233 96 155.6 96C69.75 96 0 167.8 0 256s69.75 160 155.6 160C233.1 416 290.8 347.9 320 303.5C349.2 347.9 407 416 484.4 416C570.3 416 640 344.2 640 256S570.3 96 484.4 96zM155.6 368C96.25 368 48 317.8 48 256s48.25-112 107.6-112c67.75 0 120.5 82.25 137.1 112C276 285.8 223.4 368 155.6 368zM484.4 368c-67.75 0-120.5-82.25-137.1-112C364 226.2 416.6 144 484.4 144C543.8 144 592 194.2 592 256S543.8 368 484.4 368z"
|
||||||
|
fill="currentColor"></path>
|
||||||
</svg>
|
</svg>
|
||||||
</a-tag>
|
</a-tag>
|
||||||
</template>
|
</template>
|
||||||
@@ -248,7 +280,8 @@
|
|||||||
</table>
|
</table>
|
||||||
</template>
|
</template>
|
||||||
<a-badge>
|
<a-badge>
|
||||||
<a-icon v-if="!client.enable" slot="count" type="pause-circle" theme="filled" :style="{ color: themeSwitcher.isDarkTheme ? '#2c3950' : '#bcbcbc' }"></a-icon>
|
<a-icon v-if="!client.enable" slot="count" type="pause-circle" theme="filled"
|
||||||
|
:style="{ color: themeSwitcher.isDarkTheme ? '#2c3950' : '#bcbcbc' }"></a-icon>
|
||||||
<a-button shape="round" size="small" :style="{ fontSize: '14px', padding: '0 10px' }">
|
<a-button shape="round" size="small" :style="{ fontSize: '14px', padding: '0 10px' }">
|
||||||
<a-icon type="solution"></a-icon>
|
<a-icon type="solution"></a-icon>
|
||||||
</a-button>
|
</a-button>
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
{{define "component/customStatistic"}}
|
{{define "component/customStatistic"}}
|
||||||
<template>
|
<template>
|
||||||
<a-statistic :title="title" :value="value">
|
<a-statistic :title="title" :value="value">
|
||||||
<template #prefix>
|
<template #prefix>
|
||||||
<slot name="prefix"></slot>
|
<slot name="prefix"></slot>
|
||||||
</template>
|
</template>
|
||||||
<template #suffix>
|
<template #suffix>
|
||||||
<slot name="suffix"></slot>
|
<slot name="suffix"></slot>
|
||||||
</template>
|
</template>
|
||||||
</a-statistic>
|
</a-statistic>
|
||||||
</template>
|
</template>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
@@ -16,9 +16,11 @@
|
|||||||
.dark .ant-statistic-content {
|
.dark .ant-statistic-content {
|
||||||
color: var(--dark-color-text-primary)
|
color: var(--dark-color-text-primary)
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-statistic-title {
|
.dark .ant-statistic-title {
|
||||||
color: rgba(255, 255, 255, 0.55)
|
color: rgba(255, 255, 255, 0.55)
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-statistic-content {
|
.ant-statistic-content {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,7 +42,7 @@
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
value: function (date) {
|
value: function(date) {
|
||||||
this.date = this.convertToJalalian(date)
|
this.date = this.convertToJalalian(date)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -52,7 +52,8 @@
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
convertToGregorian(date) {
|
convertToGregorian(date) {
|
||||||
return date ? moment(moment(date, 'jYYYY/jMM/jDD HH:mm:ss').format('YYYY-MM-DD HH:mm:ss')) : null
|
return date ? moment(moment(date, 'jYYYY/jMM/jDD HH:mm:ss').format('YYYY-MM-DD HH:mm:ss')) :
|
||||||
|
null
|
||||||
},
|
},
|
||||||
convertToJalalian(date) {
|
convertToJalalian(date) {
|
||||||
return date && moment.isMoment(date) ? date.format('jYYYY/jMM/jDD HH:mm:ss') : null
|
return date && moment.isMoment(date) ? date.format('jYYYY/jMM/jDD HH:mm:ss') : null
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
type: String,
|
type: String,
|
||||||
required: false,
|
required: false,
|
||||||
defaultValue: "default",
|
defaultValue: "default",
|
||||||
validator: function (value) {
|
validator: function(value) {
|
||||||
return ['small', 'default'].includes(value)
|
return ['small', 'default'].includes(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,8 +43,7 @@
|
|||||||
Vue.component('a-sidebar', {
|
Vue.component('a-sidebar', {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
tabs: [
|
tabs: [{
|
||||||
{
|
|
||||||
key: '{{ .base_path }}panel/',
|
key: '{{ .base_path }}panel/',
|
||||||
icon: 'dashboard',
|
icon: 'dashboard',
|
||||||
title: '{{ i18n "menu.dashboard"}}'
|
title: '{{ i18n "menu.dashboard"}}'
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{{define "component/sortableTableTrigger"}}
|
{{define "component/sortableTableTrigger"}}
|
||||||
<a-icon type="drag" class="sortable-icon" :style="{ cursor: 'move' }" @mouseup="mouseUpHandler" @mousedown="mouseDownHandler"
|
<a-icon type="drag" class="sortable-icon" :style="{ cursor: 'move' }" @mouseup="mouseUpHandler"
|
||||||
@click="clickHandler" />
|
@mousedown="mouseDownHandler" @click="clickHandler" />
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{define "component/aTableSortable"}}
|
{{define "component/aTableSortable"}}
|
||||||
@@ -49,7 +49,7 @@
|
|||||||
sortable,
|
sortable,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
render: function (createElement) {
|
render: function(createElement) {
|
||||||
return createElement('a-table', {
|
return createElement('a-table', {
|
||||||
class: {
|
class: {
|
||||||
'ant-table-is-sorting': this.isDragging(),
|
'ant-table-is-sorting': this.isDragging(),
|
||||||
@@ -69,7 +69,7 @@
|
|||||||
filterReset: `{{ i18n "reset" }}`,
|
filterReset: `{{ i18n "reset" }}`,
|
||||||
emptyText: `{{ i18n "noData" }}`
|
emptyText: `{{ i18n "noData" }}`
|
||||||
}
|
}
|
||||||
}, this.$slots.default,)
|
}, this.$slots.default, )
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
this.$memoSort = {};
|
this.$memoSort = {};
|
||||||
@@ -148,7 +148,8 @@
|
|||||||
class: {
|
class: {
|
||||||
...(parentMethodResult?.class || {}),
|
...(parentMethodResult?.class || {}),
|
||||||
[DRAGGABLE_ROW_CLASS]: true,
|
[DRAGGABLE_ROW_CLASS]: true,
|
||||||
['dragging']: this.isDragging() ? (newIndex === null ? index === currentIndex : index === newIndex) : false,
|
['dragging']: this.isDragging() ? (newIndex === null ? index === currentIndex : index === newIndex) :
|
||||||
|
false,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,9 +24,11 @@
|
|||||||
|
|
||||||
{{define "component/themeSwitchTemplateLogin"}}
|
{{define "component/themeSwitchTemplateLogin"}}
|
||||||
<template>
|
<template>
|
||||||
<a-space @mousedown="themeSwitcher.animationsOff()" id="change-theme" direction="vertical" :size="10" :style="{ width: '100%' }">
|
<a-space @mousedown="themeSwitcher.animationsOff()" id="change-theme" direction="vertical" :size="10"
|
||||||
|
:style="{ width: '100%' }">
|
||||||
<a-space direction="horizontal" size="small">
|
<a-space direction="horizontal" size="small">
|
||||||
<a-switch size="small" :default-checked="themeSwitcher.isDarkTheme" @change="themeSwitcher.toggleTheme()"></a-switch>
|
<a-switch size="small" :default-checked="themeSwitcher.isDarkTheme"
|
||||||
|
@change="themeSwitcher.toggleTheme()"></a-switch>
|
||||||
<span>{{ i18n "menu.dark" }}</span>
|
<span>{{ i18n "menu.dark" }}</span>
|
||||||
</a-space>
|
</a-space>
|
||||||
<a-space v-if="themeSwitcher.isDarkTheme" direction="horizontal" size="small">
|
<a-space v-if="themeSwitcher.isDarkTheme" direction="horizontal" size="small">
|
||||||
|
|||||||
@@ -1,11 +1,5 @@
|
|||||||
{{define "form/client"}}
|
{{define "form/client"}}
|
||||||
<a-form
|
<a-form layout="horizontal" v-if="client" :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
layout="horizontal"
|
|
||||||
v-if="client"
|
|
||||||
:colon="false"
|
|
||||||
:label-col="{ md: {span:8} }"
|
|
||||||
:wrapper-col="{ md: {span:14} }"
|
|
||||||
>
|
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.enable" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.enable" }}'>
|
||||||
<a-switch v-model="client.enable"></a-switch>
|
<a-switch v-model="client.enable"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
@@ -16,33 +10,22 @@
|
|||||||
<span>{{ i18n "pages.inbounds.emailDesc" }}</span>
|
<span>{{ i18n "pages.inbounds.emailDesc" }}</span>
|
||||||
</template>
|
</template>
|
||||||
{{ i18n "pages.inbounds.email" }}
|
{{ i18n "pages.inbounds.email" }}
|
||||||
<a-icon
|
<a-icon type="sync" @click="client.email = RandomUtil.randomLowerAndNum(9)"></a-icon>
|
||||||
type="sync"
|
|
||||||
@click="client.email = RandomUtil.randomLowerAndNum(9)"
|
|
||||||
></a-icon>
|
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</template>
|
</template>
|
||||||
<a-input v-model.trim="client.email"></a-input>
|
<a-input v-model.trim="client.email"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item
|
<a-form-item v-if="inbound.protocol === Protocols.TROJAN || inbound.protocol === Protocols.SHADOWSOCKS">
|
||||||
v-if="inbound.protocol === Protocols.TROJAN || inbound.protocol === Protocols.SHADOWSOCKS"
|
|
||||||
>
|
|
||||||
<template slot="label">
|
<template slot="label">
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
<span>{{ i18n "reset" }}</span>
|
<span>{{ i18n "reset" }}</span>
|
||||||
</template>
|
</template>
|
||||||
{{ i18n "password" }}
|
{{ i18n "password" }}
|
||||||
<a-icon
|
<a-icon v-if="inbound.protocol === Protocols.SHADOWSOCKS"
|
||||||
v-if="inbound.protocol === Protocols.SHADOWSOCKS"
|
@click="client.password = RandomUtil.randomShadowsocksPassword(inbound.settings.method)" type="sync"></a-icon>
|
||||||
@click="client.password = RandomUtil.randomShadowsocksPassword(inbound.settings.method)"
|
<a-icon v-if="inbound.protocol === Protocols.TROJAN" @click="client.password = RandomUtil.randomSeq(10)"
|
||||||
type="sync"
|
type="sync">
|
||||||
></a-icon>
|
|
||||||
<a-icon
|
|
||||||
v-if="inbound.protocol === Protocols.TROJAN"
|
|
||||||
@click="client.password = RandomUtil.randomSeq(10)"
|
|
||||||
type="sync"
|
|
||||||
>
|
|
||||||
</a-icon>
|
</a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</template>
|
</template>
|
||||||
@@ -55,42 +38,26 @@
|
|||||||
<span>{{ i18n "reset" }}</span>
|
<span>{{ i18n "reset" }}</span>
|
||||||
</template>
|
</template>
|
||||||
Auth Password
|
Auth Password
|
||||||
<a-icon
|
<a-icon @click="client.auth = RandomUtil.randomSeq(10)" type="sync"></a-icon>
|
||||||
@click="client.auth = RandomUtil.randomSeq(10)"
|
|
||||||
type="sync"
|
|
||||||
></a-icon>
|
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</template>
|
</template>
|
||||||
<a-input v-model.trim="client.auth"></a-input>
|
<a-input v-model.trim="client.auth"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item
|
<a-form-item v-if="inbound.protocol === Protocols.VMESS || inbound.protocol === Protocols.VLESS">
|
||||||
v-if="inbound.protocol === Protocols.VMESS || inbound.protocol === Protocols.VLESS"
|
|
||||||
>
|
|
||||||
<template slot="label">
|
<template slot="label">
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
<span>{{ i18n "reset" }}</span>
|
<span>{{ i18n "reset" }}</span>
|
||||||
</template>
|
</template>
|
||||||
ID
|
ID
|
||||||
<a-icon
|
<a-icon @click="client.id = RandomUtil.randomUUID()" type="sync"></a-icon>
|
||||||
@click="client.id = RandomUtil.randomUUID()"
|
|
||||||
type="sync"
|
|
||||||
></a-icon>
|
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</template>
|
</template>
|
||||||
<a-input v-model.trim="client.id"></a-input>
|
<a-input v-model.trim="client.id"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item
|
<a-form-item v-if="inbound.protocol === Protocols.VMESS" label='{{ i18n "security" }}'>
|
||||||
v-if="inbound.protocol === Protocols.VMESS"
|
<a-select v-model="client.security" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
label='{{ i18n "security" }}'
|
<a-select-option v-for="key in USERS_SECURITY" :value="key">[[ key ]]</a-select-option>
|
||||||
>
|
|
||||||
<a-select
|
|
||||||
v-model="client.security"
|
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme"
|
|
||||||
>
|
|
||||||
<a-select-option v-for="key in USERS_SECURITY" :value="key"
|
|
||||||
>[[ key ]]</a-select-option
|
|
||||||
>
|
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item v-if="client.email && app.subSettings?.enable">
|
<a-form-item v-if="client.email && app.subSettings?.enable">
|
||||||
@@ -100,10 +67,7 @@
|
|||||||
<span>{{ i18n "pages.inbounds.subscriptionDesc" }}</span>
|
<span>{{ i18n "pages.inbounds.subscriptionDesc" }}</span>
|
||||||
</template>
|
</template>
|
||||||
Subscription
|
Subscription
|
||||||
<a-icon
|
<a-icon @click="client.subId = RandomUtil.randomLowerAndNum(16)" type="sync"></a-icon>
|
||||||
@click="client.subId = RandomUtil.randomLowerAndNum(16)"
|
|
||||||
type="sync"
|
|
||||||
></a-icon>
|
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</template>
|
</template>
|
||||||
<a-input v-model.trim="client.subId"></a-input>
|
<a-input v-model.trim="client.subId"></a-input>
|
||||||
@@ -118,11 +82,7 @@
|
|||||||
<a-icon type="question-circle"></a-icon>
|
<a-icon type="question-circle"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</template>
|
</template>
|
||||||
<a-input-number
|
<a-input-number :style="{ width: '50%' }" v-model.number="client.tgId" min="0"></a-input-number>
|
||||||
:style="{ width: '50%' }"
|
|
||||||
v-model.number="client.tgId"
|
|
||||||
min="0"
|
|
||||||
></a-input-number>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item v-if="client.email" label='{{ i18n "comment" }}'>
|
<a-form-item v-if="client.email" label='{{ i18n "comment" }}'>
|
||||||
<a-input v-model.trim="client.comment"></a-input>
|
<a-input v-model.trim="client.comment"></a-input>
|
||||||
@@ -139,9 +99,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<a-input-number v-model.number="client.limitIp" min="0"></a-input-number>
|
<a-input-number v-model.number="client.limitIp" min="0"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item
|
<a-form-item v-if="app.ipLimitEnable && client.limitIp > 0 && client.email && isEdit">
|
||||||
v-if="app.ipLimitEnable && client.limitIp > 0 && client.email && isEdit"
|
|
||||||
>
|
|
||||||
<template slot="label">
|
<template slot="label">
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
@@ -160,25 +118,15 @@
|
|||||||
</span>
|
</span>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-form layout="block">
|
<a-form layout="block">
|
||||||
<a-textarea
|
<a-textarea id="clientIPs" readonly @click="getDBClientIps(client.email)" placeholder="Click To Get IPs"
|
||||||
id="clientIPs"
|
:auto-size="{ minRows: 5, maxRows: 10 }">
|
||||||
readonly
|
|
||||||
@click="getDBClientIps(client.email)"
|
|
||||||
placeholder="Click To Get IPs"
|
|
||||||
:auto-size="{ minRows: 5, maxRows: 10 }"
|
|
||||||
>
|
|
||||||
</a-textarea>
|
</a-textarea>
|
||||||
</a-form>
|
</a-form>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item v-if="inbound.canEnableTlsFlow()" label="Flow">
|
<a-form-item v-if="inbound.canEnableTlsFlow()" label="Flow">
|
||||||
<a-select
|
<a-select v-model="client.flow" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
v-model="client.flow"
|
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme"
|
|
||||||
>
|
|
||||||
<a-select-option value selected>{{ i18n "none" }}</a-select-option>
|
<a-select-option value selected>{{ i18n "none" }}</a-select-option>
|
||||||
<a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key"
|
<a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
||||||
>[[ key ]]</a-select-option
|
|
||||||
>
|
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
@@ -201,45 +149,28 @@
|
|||||||
</a-tag>
|
</a-tag>
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">{{ i18n "pages.inbounds.resetTraffic" }}</template>
|
<template slot="title">{{ i18n "pages.inbounds.resetTraffic" }}</template>
|
||||||
<a-icon
|
<a-icon type="retweet" @click="resetClientTraffic(client.email,clientStats.inboundId,$event.target)"
|
||||||
type="retweet"
|
v-if="client.email.length > 0"></a-icon>
|
||||||
@click="resetClientTraffic(client.email,clientStats.inboundId,$event.target)"
|
|
||||||
v-if="client.email.length > 0"
|
|
||||||
></a-icon>
|
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "pages.client.delayedStart" }}'>
|
<a-form-item label='{{ i18n "pages.client.delayedStart" }}'>
|
||||||
<a-switch v-model="delayedStart" @click="client._expiryTime=0"></a-switch>
|
<a-switch v-model="delayedStart" @click="client._expiryTime=0"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item v-if="delayedStart" label='{{ i18n "pages.client.expireDays" }}'>
|
<a-form-item v-if="delayedStart" label='{{ i18n "pages.client.expireDays" }}'>
|
||||||
<a-input-number
|
<a-input-number v-model.number="delayedExpireDays" :min="0"></a-input-number>
|
||||||
v-model.number="delayedExpireDays"
|
|
||||||
:min="0"
|
|
||||||
></a-input-number>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item v-else>
|
<a-form-item v-else>
|
||||||
<template slot="label">
|
<template slot="label">
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title"
|
<template slot="title">{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</template>
|
||||||
>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</template
|
|
||||||
>
|
|
||||||
{{ i18n "pages.inbounds.expireDate" }}
|
{{ i18n "pages.inbounds.expireDate" }}
|
||||||
<a-icon type="question-circle"></a-icon>
|
<a-icon type="question-circle"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</template>
|
</template>
|
||||||
<a-date-picker
|
<a-date-picker v-if="datepicker == 'gregorian'" :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
||||||
v-if="datepicker == 'gregorian'"
|
:dropdown-class-name="themeSwitcher.currentTheme" v-model="client._expiryTime"></a-date-picker>
|
||||||
:show-time="{ format: 'HH:mm:ss' }"
|
<a-persian-datepicker v-else placeholder='{{ i18n "pages.settings.datepickerPlaceholder" }}'
|
||||||
format="YYYY-MM-DD HH:mm:ss"
|
value="client._expiryTime" v-model="client._expiryTime"></a-persian-datepicker>
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme"
|
|
||||||
v-model="client._expiryTime"
|
|
||||||
></a-date-picker>
|
|
||||||
<a-persian-datepicker
|
|
||||||
v-else
|
|
||||||
placeholder='{{ i18n "pages.settings.datepickerPlaceholder" }}'
|
|
||||||
value="client._expiryTime"
|
|
||||||
v-model="client._expiryTime"
|
|
||||||
></a-persian-datepicker>
|
|
||||||
<a-tag color="red" v-if="isEdit && isExpiry">Expired</a-tag>
|
<a-tag color="red" v-if="isEdit && isExpiry">Expired</a-tag>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item v-if="client.expiryTime != 0">
|
<a-form-item v-if="client.expiryTime != 0">
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -5,7 +5,8 @@
|
|||||||
<td width="45%">{{ i18n "username" }}</td>
|
<td width="45%">{{ i18n "username" }}</td>
|
||||||
<td width="45%">{{ i18n "password" }}</td>
|
<td width="45%">{{ i18n "password" }}</td>
|
||||||
<td>
|
<td>
|
||||||
<a-button icon="plus" size="small" @click="inbound.settings.addAccount(new Inbound.HttpSettings.HttpAccount())"></a-button>
|
<a-button icon="plus" size="small"
|
||||||
|
@click="inbound.settings.addAccount(new Inbound.HttpSettings.HttpAccount())"></a-button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
{{define "form/hysteria"}}
|
{{define "form/hysteria"}}
|
||||||
<a-collapse activeKey="0"
|
<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.hysterias.slice(0,1)" v-if="!isEdit">
|
||||||
v-for="(client, index) in inbound.settings.hysterias.slice(0,1)"
|
|
||||||
v-if="!isEdit">
|
|
||||||
<a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
|
<a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
|
||||||
{{template "form/client"}}
|
{{template "form/client"}}
|
||||||
</a-collapse-panel>
|
</a-collapse-panel>
|
||||||
@@ -22,11 +20,9 @@
|
|||||||
</table>
|
</table>
|
||||||
</a-collapse-panel>
|
</a-collapse-panel>
|
||||||
</a-collapse>
|
</a-collapse>
|
||||||
<a-form :colon="false" :label-col="{ md: {span:8} }"
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
:wrapper-col="{ md: {span:14} }">
|
|
||||||
<a-form-item :label="'{{ i18n "pages.inbounds.stream.tcp.version" }}'">
|
<a-form-item :label="'{{ i18n "pages.inbounds.stream.tcp.version" }}'">
|
||||||
<a-input-number v-model.number="inbound.settings.version" :min="2"
|
<a-input-number v-model.number="inbound.settings.version" :min="2" :max="2" disabled></a-input-number>
|
||||||
:max="2" disabled></a-input-number>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
{{end}}
|
{{end}}
|
||||||
@@ -1,10 +1,6 @@
|
|||||||
{{define "form/shadowsocks"}}
|
{{define "form/shadowsocks"}}
|
||||||
<template v-if="inbound.isSSMultiUser">
|
<template v-if="inbound.isSSMultiUser">
|
||||||
<a-collapse
|
<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.shadowsockses.slice(0,1)" v-if="!isEdit">
|
||||||
activeKey="0"
|
|
||||||
v-for="(client, index) in inbound.settings.shadowsockses.slice(0,1)"
|
|
||||||
v-if="!isEdit"
|
|
||||||
>
|
|
||||||
<a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
|
<a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
|
||||||
{{template "form/client"}}
|
{{template "form/client"}}
|
||||||
</a-collapse-panel>
|
</a-collapse-panel>
|
||||||
@@ -16,10 +12,8 @@
|
|||||||
<th>{{ i18n "pages.inbounds.email" }}</th>
|
<th>{{ i18n "pages.inbounds.email" }}</th>
|
||||||
<th>Password</th>
|
<th>Password</th>
|
||||||
</tr>
|
</tr>
|
||||||
<tr
|
<tr v-for="(client, index) in inbound.settings.shadowsockses"
|
||||||
v-for="(client, index) in inbound.settings.shadowsockses"
|
:class="index % 2 == 1 ? ' client-table-odd-row' : ''">
|
||||||
:class="index % 2 == 1 ? ' client-table-odd-row' : ''"
|
|
||||||
>
|
|
||||||
<td>[[ client.email ]]</td>
|
<td>[[ client.email ]]</td>
|
||||||
<td>[[ client.password ]]</td>
|
<td>[[ client.password ]]</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -27,20 +21,11 @@
|
|||||||
</a-collapse-panel>
|
</a-collapse-panel>
|
||||||
</a-collapse>
|
</a-collapse>
|
||||||
</template>
|
</template>
|
||||||
<a-form
|
<a-form :colon=" false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
:colon=" false"
|
|
||||||
:label-col="{ md: {span:8} }"
|
|
||||||
:wrapper-col="{ md: {span:14} }"
|
|
||||||
>
|
|
||||||
<a-form-item label='{{ i18n "encryption" }}'>
|
<a-form-item label='{{ i18n "encryption" }}'>
|
||||||
<a-select
|
<a-select v-model="inbound.settings.method" @change="SSMethodChange"
|
||||||
v-model="inbound.settings.method"
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
@change="SSMethodChange"
|
<a-select-option v-for="(method,method_name) in SSMethods" :value="method">[[ method_name ]]</a-select-option>
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme"
|
|
||||||
>
|
|
||||||
<a-select-option v-for="(method,method_name) in SSMethods" :value="method"
|
|
||||||
>[[ method_name ]]</a-select-option
|
|
||||||
>
|
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item v-if="inbound.isSS2022">
|
<a-form-item v-if="inbound.isSS2022">
|
||||||
@@ -50,20 +35,15 @@
|
|||||||
<span>{{ i18n "reset" }}</span>
|
<span>{{ i18n "reset" }}</span>
|
||||||
</template>
|
</template>
|
||||||
Password
|
Password
|
||||||
<a-icon
|
<a-icon @click="inbound.settings.password = RandomUtil.randomShadowsocksPassword(inbound.settings.method)"
|
||||||
@click="inbound.settings.password = RandomUtil.randomShadowsocksPassword(inbound.settings.method)"
|
type="sync"></a-icon>
|
||||||
type="sync"
|
|
||||||
></a-icon>
|
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</template>
|
</template>
|
||||||
<a-input v-model.trim="inbound.settings.password"></a-input>
|
<a-input v-model.trim="inbound.settings.password"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.network" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.network" }}'>
|
||||||
<a-select
|
<a-select v-model="inbound.settings.network" :style="{ width: '100px' }"
|
||||||
v-model="inbound.settings.network"
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
:style="{ width: '100px' }"
|
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme"
|
|
||||||
>
|
|
||||||
<a-select-option value="tcp,udp">TCP,UDP</a-select-option>
|
<a-select-option value="tcp,udp">TCP,UDP</a-select-option>
|
||||||
<a-select-option value="tcp">TCP</a-select-option>
|
<a-select-option value="tcp">TCP</a-select-option>
|
||||||
<a-select-option value="udp">UDP</a-select-option>
|
<a-select-option value="udp">UDP</a-select-option>
|
||||||
|
|||||||
@@ -1,9 +1,5 @@
|
|||||||
{{define "form/mixed"}}
|
{{define "form/mixed"}}
|
||||||
<a-form
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
:colon="false"
|
|
||||||
:label-col="{ md: {span:8} }"
|
|
||||||
:wrapper-col="{ md: {span:14} }"
|
|
||||||
>
|
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.enable" }} UDP'>
|
<a-form-item label='{{ i18n "pages.inbounds.enable" }} UDP'>
|
||||||
<a-switch v-model="inbound.settings.udp"></a-switch>
|
<a-switch v-model="inbound.settings.udp"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
@@ -11,10 +7,8 @@
|
|||||||
<a-input v-model.trim="inbound.settings.ip"></a-input>
|
<a-input v-model.trim="inbound.settings.ip"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "password" }}'>
|
<a-form-item label='{{ i18n "password" }}'>
|
||||||
<a-switch
|
<a-switch :checked="inbound.settings.auth === 'password'"
|
||||||
:checked="inbound.settings.auth === 'password'"
|
@change="checked => inbound.settings.auth = checked ? 'password' : 'noauth'"></a-switch>
|
||||||
@change="checked => inbound.settings.auth = checked ? 'password' : 'noauth'"
|
|
||||||
></a-switch>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<template v-if="inbound.settings.auth === 'password'">
|
<template v-if="inbound.settings.auth === 'password'">
|
||||||
<table :style="{ width: '100%', textAlign: 'center', margin: '1rem 0' }">
|
<table :style="{ width: '100%', textAlign: 'center', margin: '1rem 0' }">
|
||||||
@@ -22,39 +16,18 @@
|
|||||||
<td width="45%">{{ i18n "username" }}</td>
|
<td width="45%">{{ i18n "username" }}</td>
|
||||||
<td width="45%">{{ i18n "password" }}</td>
|
<td width="45%">{{ i18n "password" }}</td>
|
||||||
<td>
|
<td>
|
||||||
<a-button
|
<a-button icon="plus" size="small"
|
||||||
icon="plus"
|
@click="inbound.settings.addAccount(new Inbound.MixedSettings.SocksAccount())"></a-button>
|
||||||
size="small"
|
|
||||||
@click="inbound.settings.addAccount(new Inbound.MixedSettings.SocksAccount())"
|
|
||||||
></a-button>
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
<a-input-group
|
<a-input-group compact v-for="(account, index) in inbound.settings.accounts" :style="{ marginBottom: '10px' }">
|
||||||
compact
|
<a-input :style="{ width: '50%' }" v-model.trim="account.user" placeholder='{{ i18n "username" }}'>
|
||||||
v-for="(account, index) in inbound.settings.accounts"
|
<template slot="addonBefore" :style="{ margin: '0' }">[[ index+1 ]]</template>
|
||||||
:style="{ marginBottom: '10px' }"
|
|
||||||
>
|
|
||||||
<a-input
|
|
||||||
:style="{ width: '50%' }"
|
|
||||||
v-model.trim="account.user"
|
|
||||||
placeholder='{{ i18n "username" }}'
|
|
||||||
>
|
|
||||||
<template slot="addonBefore" :style="{ margin: '0' }"
|
|
||||||
>[[ index+1 ]]</template
|
|
||||||
>
|
|
||||||
</a-input>
|
</a-input>
|
||||||
<a-input
|
<a-input :style="{ width: '50%' }" v-model.trim="account.pass" placeholder='{{ i18n "password" }}'>
|
||||||
:style="{ width: '50%' }"
|
|
||||||
v-model.trim="account.pass"
|
|
||||||
placeholder='{{ i18n "password" }}'
|
|
||||||
>
|
|
||||||
<template slot="addonAfter">
|
<template slot="addonAfter">
|
||||||
<a-button
|
<a-button icon="minus" size="small" @click="inbound.settings.delAccount(index)"></a-button>
|
||||||
icon="minus"
|
|
||||||
size="small"
|
|
||||||
@click="inbound.settings.delAccount(index)"
|
|
||||||
></a-button>
|
|
||||||
</template>
|
</template>
|
||||||
</a-input>
|
</a-input>
|
||||||
</a-input-group>
|
</a-input-group>
|
||||||
|
|||||||
@@ -19,35 +19,35 @@
|
|||||||
</a-collapse-panel>
|
</a-collapse-panel>
|
||||||
</a-collapse>
|
</a-collapse>
|
||||||
<template v-if=" inbound.isTcp">
|
<template v-if=" inbound.isTcp">
|
||||||
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item label="Fallbacks">
|
<a-form-item label="Fallbacks">
|
||||||
<a-button icon="plus" type="primary" size="small" @click="inbound.settings.addFallback()"></a-button>
|
<a-button icon="plus" type="primary" size="small" @click="inbound.settings.addFallback()"></a-button>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
|
|
||||||
<!-- trojan fallbacks -->
|
<!-- trojan fallbacks -->
|
||||||
<a-form v-for="(fallback, index) in inbound.settings.fallbacks" :colon="false" :label-col="{ md: {span:8} }"
|
<a-form v-for="(fallback, index) in inbound.settings.fallbacks" :colon="false" :label-col="{ md: {span:8} }"
|
||||||
:wrapper-col="{ md: {span:14} }">
|
:wrapper-col="{ md: {span:14} }">
|
||||||
<a-divider :style="{ margin: '0' }"> Fallback [[ index + 1 ]] <a-icon type="delete"
|
<a-divider :style="{ margin: '0' }"> Fallback [[ index + 1 ]] <a-icon type="delete"
|
||||||
@click="() => inbound.settings.delFallback(index)"
|
@click="() => inbound.settings.delFallback(index)"
|
||||||
:style="{ color: 'rgb(255, 77, 79)', cursor: 'pointer' }"></a-icon>
|
:style="{ color: 'rgb(255, 77, 79)', cursor: 'pointer' }"></a-icon>
|
||||||
</a-divider>
|
</a-divider>
|
||||||
<a-form-item label='SNI'>
|
<a-form-item label='SNI'>
|
||||||
<a-input v-model="fallback.name"></a-input>
|
<a-input v-model="fallback.name"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='ALPN'>
|
<a-form-item label='ALPN'>
|
||||||
<a-input v-model="fallback.alpn"></a-input>
|
<a-input v-model="fallback.alpn"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='Path'>
|
<a-form-item label='Path'>
|
||||||
<a-input v-model="fallback.path"></a-input>
|
<a-input v-model="fallback.path"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='Dest'>
|
<a-form-item label='Dest'>
|
||||||
<a-input v-model="fallback.dest"></a-input>
|
<a-input v-model="fallback.dest"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='xVer'>
|
<a-form-item label='xVer'>
|
||||||
<a-input-number v-model.number="fallback.xver" :min="0" :max="2"></a-input-number>
|
<a-input-number v-model.number="fallback.xver" :min="0" :max="2"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
<a-divider style="margin:5px 0;"></a-divider>
|
<a-divider style="margin:5px 0;"></a-divider>
|
||||||
</template>
|
</template>
|
||||||
{{end}}
|
{{end}}
|
||||||
@@ -1,9 +1,5 @@
|
|||||||
{{define "form/tun"}}
|
{{define "form/tun"}}
|
||||||
<a-form
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
:colon="false"
|
|
||||||
:label-col="{ md: {span:8} }"
|
|
||||||
:wrapper-col="{ md: {span:14} }"
|
|
||||||
>
|
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<template slot="label">
|
<template slot="label">
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
@@ -26,38 +22,18 @@
|
|||||||
<a-icon type="question-circle"></a-icon>
|
<a-icon type="question-circle"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</template>
|
</template>
|
||||||
<a-input-number
|
<a-input-number v-model.number="inbound.settings.mtu[0]" :min="1" :max="9000" placeholder="1500"></a-input-number>
|
||||||
v-model.number="inbound.settings.mtu[0]"
|
|
||||||
:min="1"
|
|
||||||
:max="9000"
|
|
||||||
placeholder="1500"
|
|
||||||
></a-input-number>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="MTU IPv6">
|
<a-form-item label="MTU IPv6">
|
||||||
<a-input-number
|
<a-input-number v-model.number="inbound.settings.mtu[1]" :min="1" :max="9000" placeholder="1280"></a-input-number>
|
||||||
v-model.number="inbound.settings.mtu[1]"
|
|
||||||
:min="1"
|
|
||||||
:max="9000"
|
|
||||||
placeholder="1280"
|
|
||||||
></a-input-number>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="Gateway">
|
<a-form-item label="Gateway">
|
||||||
<a-select
|
<a-select mode="tags" v-model="inbound.settings.gateway" :style="{ width: '100%' }" :token-separators="[',']"
|
||||||
mode="tags"
|
placeholder="IPv4/IPv6 gateway"></a-select>
|
||||||
v-model="inbound.settings.gateway"
|
|
||||||
:style="{ width: '100%' }"
|
|
||||||
:token-separators="[',']"
|
|
||||||
placeholder="IPv4/IPv6 gateway"
|
|
||||||
></a-select>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="DNS">
|
<a-form-item label="DNS">
|
||||||
<a-select
|
<a-select mode="tags" v-model="inbound.settings.dns" :style="{ width: '100%' }" :token-separators="[',']"
|
||||||
mode="tags"
|
placeholder="DNS servers"></a-select>
|
||||||
v-model="inbound.settings.dns"
|
|
||||||
:style="{ width: '100%' }"
|
|
||||||
:token-separators="[',']"
|
|
||||||
placeholder="DNS servers"
|
|
||||||
></a-select>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<template slot="label">
|
<template slot="label">
|
||||||
@@ -69,26 +45,14 @@
|
|||||||
<a-icon type="question-circle"></a-icon>
|
<a-icon type="question-circle"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</template>
|
</template>
|
||||||
<a-input-number
|
<a-input-number v-model.number="inbound.settings.userLevel" :min="0" placeholder="0"></a-input-number>
|
||||||
v-model.number="inbound.settings.userLevel"
|
|
||||||
:min="0"
|
|
||||||
placeholder="0"
|
|
||||||
></a-input-number>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="Auto Routing Table">
|
<a-form-item label="Auto Routing Table">
|
||||||
<a-select
|
<a-select mode="tags" v-model="inbound.settings.autoSystemRoutingTable" :style="{ width: '100%' }"
|
||||||
mode="tags"
|
:token-separators="[',']" placeholder="e.g. vpn, proxy"></a-select>
|
||||||
v-model="inbound.settings.autoSystemRoutingTable"
|
|
||||||
:style="{ width: '100%' }"
|
|
||||||
:token-separators="[',']"
|
|
||||||
placeholder="e.g. vpn, proxy"
|
|
||||||
></a-select>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="Auto Outbounds">
|
<a-form-item label="Auto Outbounds">
|
||||||
<a-input
|
<a-input v-model.trim="inbound.settings.autoOutboundsInterface" placeholder="auto"></a-input>
|
||||||
v-model.trim="inbound.settings.autoOutboundsInterface"
|
|
||||||
placeholder="auto"
|
|
||||||
></a-input>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
{{end}}
|
{{end}}
|
||||||
@@ -12,8 +12,7 @@
|
|||||||
<th>{{ i18n "pages.inbounds.email" }}</th>
|
<th>{{ i18n "pages.inbounds.email" }}</th>
|
||||||
<th>ID</th>
|
<th>ID</th>
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-for="(client, index) in inbound.settings.vlesses"
|
<tr v-for="(client, index) in inbound.settings.vlesses" :class="index % 2 == 1 ? ' client-table-odd-row' : ''">
|
||||||
:class="index % 2 == 1 ? ' client-table-odd-row' : ''">
|
|
||||||
<td>[[ client.email ]]</td>
|
<td>[[ client.email ]]</td>
|
||||||
<td>[[ client.id ]]</td>
|
<td>[[ client.id ]]</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -21,104 +20,104 @@
|
|||||||
</a-collapse-panel>
|
</a-collapse-panel>
|
||||||
</a-collapse>
|
</a-collapse>
|
||||||
<template v-if=" !inbound.stream.isTLS || !inbound.stream.isReality">
|
<template v-if=" !inbound.stream.isTLS || !inbound.stream.isReality">
|
||||||
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item label="Authentication">
|
<a-form-item label="Authentication">
|
||||||
<a-select v-model="inbound.settings.selectedAuth" @change="getNewVlessEnc"
|
<a-select v-model="inbound.settings.selectedAuth" @change="getNewVlessEnc"
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option :value="undefined">None</a-select-option>
|
<a-select-option :value="undefined">None</a-select-option>
|
||||||
<a-select-option value="X25519, not Post-Quantum">X25519 (not
|
<a-select-option value="X25519, not Post-Quantum">X25519 (not
|
||||||
Post-Quantum)</a-select-option>
|
Post-Quantum)</a-select-option>
|
||||||
<a-select-option value="ML-KEM-768, Post-Quantum">ML-KEM-768
|
<a-select-option value="ML-KEM-768, Post-Quantum">ML-KEM-768
|
||||||
(Post-Quantum)</a-select-option>
|
(Post-Quantum)</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="decryption">
|
<a-form-item label="decryption">
|
||||||
<a-input v-model.trim="inbound.settings.decryption"></a-input>
|
<a-input v-model.trim="inbound.settings.decryption"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="encryption">
|
<a-form-item label="encryption">
|
||||||
<a-input v-model="inbound.settings.encryption"></a-input>
|
<a-input v-model="inbound.settings.encryption"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label=" ">
|
<a-form-item label=" ">
|
||||||
<a-space>
|
<a-space>
|
||||||
<a-button type="primary" icon="import" @click="getNewVlessEnc">Get New
|
<a-button type="primary" icon="import" @click="getNewVlessEnc">Get New
|
||||||
keys</a-button>
|
keys</a-button>
|
||||||
<a-button danger @click="clearVlessEnc">Clear</a-button>
|
<a-button danger @click="clearVlessEnc">Clear</a-button>
|
||||||
</a-space>
|
</a-space>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
<a-divider :style="{ margin: '5px 0' }"></a-divider>
|
<a-divider :style="{ margin: '5px 0' }"></a-divider>
|
||||||
</template>
|
</template>
|
||||||
<template v-if="inbound.isTcp && !inbound.settings.selectedAuth">
|
<template v-if="inbound.isTcp && !inbound.settings.selectedAuth">
|
||||||
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item label="Fallbacks">
|
<a-form-item label="Fallbacks">
|
||||||
<a-button icon="plus" type="primary" size="small" @click="inbound.settings.addFallback()"></a-button>
|
<a-button icon="plus" type="primary" size="small" @click="inbound.settings.addFallback()"></a-button>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
|
|
||||||
<!-- vless fallbacks -->
|
<!-- vless fallbacks -->
|
||||||
<a-form v-for="(fallback, index) in inbound.settings.fallbacks" :colon="false" :label-col="{ md: {span:8} }"
|
<a-form v-for="(fallback, index) in inbound.settings.fallbacks" :colon="false" :label-col="{ md: {span:8} }"
|
||||||
:wrapper-col="{ md: {span:14} }">
|
:wrapper-col="{ md: {span:14} }">
|
||||||
<a-divider :style="{ margin: '0' }"> Fallback [[ index + 1 ]] <a-icon type="delete"
|
<a-divider :style="{ margin: '0' }"> Fallback [[ index + 1 ]] <a-icon type="delete"
|
||||||
@click="() => inbound.settings.delFallback(index)"
|
@click="() => inbound.settings.delFallback(index)"
|
||||||
:style="{ color: 'rgb(255, 77, 79)', cursor: 'pointer' }"></a-icon>
|
:style="{ color: 'rgb(255, 77, 79)', cursor: 'pointer' }"></a-icon>
|
||||||
</a-divider>
|
</a-divider>
|
||||||
<a-form-item label='SNI'>
|
<a-form-item label='SNI'>
|
||||||
<a-input v-model="fallback.name"></a-input>
|
<a-input v-model="fallback.name"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='ALPN'>
|
<a-form-item label='ALPN'>
|
||||||
<a-input v-model="fallback.alpn"></a-input>
|
<a-input v-model="fallback.alpn"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='Path'>
|
<a-form-item label='Path'>
|
||||||
<a-input v-model="fallback.path"></a-input>
|
<a-input v-model="fallback.path"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='Dest'>
|
<a-form-item label='Dest'>
|
||||||
<a-input v-model="fallback.dest"></a-input>
|
<a-input v-model="fallback.dest"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='xVer'>
|
<a-form-item label='xVer'>
|
||||||
<a-input-number v-model.number="fallback.xver" :min="0" :max="2"></a-input-number>
|
<a-input-number v-model.number="fallback.xver" :min="0" :max="2"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
<a-divider :style="{ margin: '5px 0' }"></a-divider>
|
<a-divider :style="{ margin: '5px 0' }"></a-divider>
|
||||||
</template>
|
</template>
|
||||||
<template v-if="inbound.canEnableVisionSeed()">
|
<template v-if="inbound.canEnableVisionSeed()">
|
||||||
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item label="Vision Seed">
|
<a-form-item label="Vision Seed">
|
||||||
<a-row :gutter="8">
|
<a-row :gutter="8">
|
||||||
<a-col :span="6">
|
<a-col :span="6">
|
||||||
<a-input-number
|
<a-input-number
|
||||||
:value="(inbound.settings.testseed && inbound.settings.testseed[0] !== undefined) ? inbound.settings.testseed[0] : 900"
|
:value="(inbound.settings.testseed && inbound.settings.testseed[0] !== undefined) ? inbound.settings.testseed[0] : 900"
|
||||||
@change="(val) => updateTestseed(0, val)" :min="0" :max="9999" :style="{ width: '100%' }"
|
@change="(val) => updateTestseed(0, val)" :min="0" :max="9999" :style="{ width: '100%' }" placeholder="900"
|
||||||
placeholder="900" addon-before="[0]"></a-input-number>
|
addon-before="[0]"></a-input-number>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="6">
|
<a-col :span="6">
|
||||||
<a-input-number
|
<a-input-number
|
||||||
:value="(inbound.settings.testseed && inbound.settings.testseed[1] !== undefined) ? inbound.settings.testseed[1] : 500"
|
:value="(inbound.settings.testseed && inbound.settings.testseed[1] !== undefined) ? inbound.settings.testseed[1] : 500"
|
||||||
@change="(val) => updateTestseed(1, val)" :min="0" :max="9999" :style="{ width: '100%' }"
|
@change="(val) => updateTestseed(1, val)" :min="0" :max="9999" :style="{ width: '100%' }" placeholder="500"
|
||||||
placeholder="500" addon-before="[1]"></a-input-number>
|
addon-before="[1]"></a-input-number>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="6">
|
<a-col :span="6">
|
||||||
<a-input-number
|
<a-input-number
|
||||||
:value="(inbound.settings.testseed && inbound.settings.testseed[2] !== undefined) ? inbound.settings.testseed[2] : 900"
|
:value="(inbound.settings.testseed && inbound.settings.testseed[2] !== undefined) ? inbound.settings.testseed[2] : 900"
|
||||||
@change="(val) => updateTestseed(2, val)" :min="0" :max="9999" :style="{ width: '100%' }"
|
@change="(val) => updateTestseed(2, val)" :min="0" :max="9999" :style="{ width: '100%' }" placeholder="900"
|
||||||
placeholder="900" addon-before="[2]"></a-input-number>
|
addon-before="[2]"></a-input-number>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="6">
|
<a-col :span="6">
|
||||||
<a-input-number
|
<a-input-number
|
||||||
:value="(inbound.settings.testseed && inbound.settings.testseed[3] !== undefined) ? inbound.settings.testseed[3] : 256"
|
:value="(inbound.settings.testseed && inbound.settings.testseed[3] !== undefined) ? inbound.settings.testseed[3] : 256"
|
||||||
@change="(val) => updateTestseed(3, val)" :min="0" :max="9999" :style="{ width: '100%' }"
|
@change="(val) => updateTestseed(3, val)" :min="0" :max="9999" :style="{ width: '100%' }" placeholder="256"
|
||||||
placeholder="256" addon-before="[3]"></a-input-number>
|
addon-before="[3]"></a-input-number>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
<a-space :size="8" :style="{ marginTop: '8px' }">
|
<a-space :size="8" :style="{ marginTop: '8px' }">
|
||||||
<a-button type="primary" @click="setRandomTestseed">
|
<a-button type="primary" @click="setRandomTestseed">
|
||||||
Rand
|
Rand
|
||||||
</a-button>
|
</a-button>
|
||||||
<a-button @click="resetTestseed">
|
<a-button @click="resetTestseed">
|
||||||
Reset
|
Reset
|
||||||
</a-button>
|
</a-button>
|
||||||
</a-space>
|
</a-space>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
<a-divider :style="{ margin: '5px 0' }"></a-divider>
|
<a-divider :style="{ margin: '5px 0' }"></a-divider>
|
||||||
</template>
|
</template>
|
||||||
{{end}}
|
{{end}}
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
<th>{{ i18n "security" }}</th>
|
<th>{{ i18n "security" }}</th>
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-for="(client, index) in inbound.settings.vmesses"
|
<tr v-for="(client, index) in inbound.settings.vmesses"
|
||||||
:class="index % 2 == 1 ? ' client-table-odd-row' : ''">
|
:class="index % 2 == 1 ? ' client-table-odd-row' : ''">
|
||||||
<td>[[ client.email ]]</td>
|
<td>[[ client.email ]]</td>
|
||||||
<td>[[ client.id ]]</td>
|
<td>[[ client.id ]]</td>
|
||||||
<td>[[ client.security ]]</td>
|
<td>[[ client.security ]]</td>
|
||||||
|
|||||||
@@ -1,9 +1,5 @@
|
|||||||
{{define "form/sniffing"}}
|
{{define "form/sniffing"}}
|
||||||
<a-form
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
:colon="false"
|
|
||||||
:label-col="{ md: {span:8} }"
|
|
||||||
:wrapper-col="{ md: {span:14} }"
|
|
||||||
>
|
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<span slot="label">
|
<span slot="label">
|
||||||
{{ i18n "enabled" }}
|
{{ i18n "enabled" }}
|
||||||
@@ -19,9 +15,7 @@
|
|||||||
<template v-if="inbound.sniffing.enabled">
|
<template v-if="inbound.sniffing.enabled">
|
||||||
<a-form-item :wrapper-col="{span:24}">
|
<a-form-item :wrapper-col="{span:24}">
|
||||||
<a-checkbox-group v-model="inbound.sniffing.destOverride">
|
<a-checkbox-group v-model="inbound.sniffing.destOverride">
|
||||||
<a-checkbox v-for="key,value in SNIFFING_OPTION" :value="key"
|
<a-checkbox v-for="key,value in SNIFFING_OPTION" :value="key">[[ value ]]</a-checkbox>
|
||||||
>[[ value ]]</a-checkbox
|
|
||||||
>
|
|
||||||
</a-checkbox-group>
|
</a-checkbox-group>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="Metadata Only">
|
<a-form-item label="Metadata Only">
|
||||||
@@ -31,22 +25,12 @@
|
|||||||
<a-switch v-model="inbound.sniffing.routeOnly"></a-switch>
|
<a-switch v-model="inbound.sniffing.routeOnly"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="IPs Excluded">
|
<a-form-item label="IPs Excluded">
|
||||||
<a-select
|
<a-select mode="tags" v-model="inbound.sniffing.ipsExcluded" :style="{ width: '100%' }" :token-separators="[',']"
|
||||||
mode="tags"
|
placeholder="IP/CIDR/geoip:*/ext:*"></a-select>
|
||||||
v-model="inbound.sniffing.ipsExcluded"
|
|
||||||
:style="{ width: '100%' }"
|
|
||||||
:token-separators="[',']"
|
|
||||||
placeholder="IP/CIDR/geoip:*/ext:*"
|
|
||||||
></a-select>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="Domains Excluded">
|
<a-form-item label="Domains Excluded">
|
||||||
<a-select
|
<a-select mode="tags" v-model="inbound.sniffing.domainsExcluded" :style="{ width: '100%' }"
|
||||||
mode="tags"
|
:token-separators="[',']" placeholder="domain:*/ext:*"></a-select>
|
||||||
v-model="inbound.sniffing.domainsExcluded"
|
|
||||||
:style="{ width: '100%' }"
|
|
||||||
:token-separators="[',']"
|
|
||||||
placeholder="domain:*/ext:*"
|
|
||||||
></a-select>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
</a-form>
|
</a-form>
|
||||||
|
|||||||
@@ -1,65 +1,29 @@
|
|||||||
{{define "form/externalProxy"}}
|
{{define "form/externalProxy"}}
|
||||||
<a-form
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
:colon="false"
|
|
||||||
:label-col="{ md: {span:8} }"
|
|
||||||
:wrapper-col="{ md: {span:14} }"
|
|
||||||
>
|
|
||||||
<a-divider :style="{ margin: '5px 0 0' }"></a-divider>
|
<a-divider :style="{ margin: '5px 0 0' }"></a-divider>
|
||||||
<a-form-item label="External Proxy">
|
<a-form-item label="External Proxy">
|
||||||
<a-switch v-model="externalProxy"></a-switch>
|
<a-switch v-model="externalProxy"></a-switch>
|
||||||
<a-button
|
<a-button icon="plus" v-if="externalProxy" type="primary" :style="{ marginLeft: '10px' }" size="small"
|
||||||
icon="plus"
|
@click="inbound.stream.externalProxy.push({forceTls: 'same', dest: '', port: 443, remark: ''})"></a-button>
|
||||||
v-if="externalProxy"
|
|
||||||
type="primary"
|
|
||||||
:style="{ marginLeft: '10px' }"
|
|
||||||
size="small"
|
|
||||||
@click="inbound.stream.externalProxy.push({forceTls: 'same', dest: '', port: 443, remark: ''})"
|
|
||||||
></a-button>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-input-group
|
<a-input-group :style="{ margin: '8px 0' }" compact v-for="(row, index) in inbound.stream.externalProxy">
|
||||||
:style="{ margin: '8px 0' }"
|
|
||||||
compact
|
|
||||||
v-for="(row, index) in inbound.stream.externalProxy"
|
|
||||||
>
|
|
||||||
<template>
|
<template>
|
||||||
<a-tooltip title="Force TLS">
|
<a-tooltip title="Force TLS">
|
||||||
<a-select
|
<a-select v-model="row.forceTls" :style="{ width: '20%', margin: '0px' }"
|
||||||
v-model="row.forceTls"
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
:style="{ width: '20%', margin: '0px' }"
|
<a-select-option value="same">{{ i18n "pages.inbounds.same" }}</a-select-option>
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme"
|
|
||||||
>
|
|
||||||
<a-select-option value="same"
|
|
||||||
>{{ i18n "pages.inbounds.same" }}</a-select-option
|
|
||||||
>
|
|
||||||
<a-select-option value="none">{{ i18n "none" }}</a-select-option>
|
<a-select-option value="none">{{ i18n "none" }}</a-select-option>
|
||||||
<a-select-option value="tls">TLS</a-select-option>
|
<a-select-option value="tls">TLS</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</template>
|
</template>
|
||||||
<a-input
|
<a-input :style="{ width: '30%' }" v-model.trim="row.dest" placeholder='{{ i18n "host" }}'></a-input>
|
||||||
:style="{ width: '30%' }"
|
|
||||||
v-model.trim="row.dest"
|
|
||||||
placeholder='{{ i18n "host" }}'
|
|
||||||
></a-input>
|
|
||||||
<a-tooltip title='{{ i18n "pages.inbounds.port" }}'>
|
<a-tooltip title='{{ i18n "pages.inbounds.port" }}'>
|
||||||
<a-input-number
|
<a-input-number :style="{ width: '15%' }" v-model.number="row.port" min="1" max="65535"></a-input-number>
|
||||||
:style="{ width: '15%' }"
|
|
||||||
v-model.number="row.port"
|
|
||||||
min="1"
|
|
||||||
max="65535"
|
|
||||||
></a-input-number>
|
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-input
|
<a-input :style="{ width: '30%', top: '0' }" v-model.trim="row.remark" placeholder='{{ i18n "remark" }}'>
|
||||||
:style="{ width: '30%', top: '0' }"
|
|
||||||
v-model.trim="row.remark"
|
|
||||||
placeholder='{{ i18n "remark" }}'
|
|
||||||
>
|
|
||||||
<template slot="addonAfter">
|
<template slot="addonAfter">
|
||||||
<a-button
|
<a-button icon="minus" size="small" @click="inbound.stream.externalProxy.splice(index, 1)"></a-button>
|
||||||
icon="minus"
|
|
||||||
size="small"
|
|
||||||
@click="inbound.stream.externalProxy.splice(index, 1)"
|
|
||||||
></a-button>
|
|
||||||
</template>
|
</template>
|
||||||
</a-input>
|
</a-input>
|
||||||
</a-input-group>
|
</a-input-group>
|
||||||
|
|||||||
@@ -1,446 +1,339 @@
|
|||||||
{{define "form/streamFinalMask"}}
|
{{define "form/streamFinalMask"}}
|
||||||
<a-form
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }"
|
||||||
:colon="false"
|
v-if="inbound.protocol == Protocols.HYSTERIA || ['kcp', 'xhttp', 'raw', 'tcp', 'httpupgrade', 'ws', 'grpc'].includes(inbound.stream.network)">
|
||||||
:label-col="{ md: {span:8} }"
|
|
||||||
:wrapper-col="{ md: {span:14} }"
|
|
||||||
v-if="inbound.protocol == Protocols.HYSTERIA || ['kcp', 'xhttp', 'raw', 'tcp', 'httpupgrade', 'ws', 'grpc'].includes(inbound.stream.network)"
|
|
||||||
>
|
|
||||||
<a-divider :style="{ margin: '5px 0 0' }"></a-divider>
|
<a-divider :style="{ margin: '5px 0 0' }"></a-divider>
|
||||||
|
|
||||||
<!-- TCP Masks – for raw/tcp/httpupgrade/ws/grpc/xhttp -->
|
<!-- TCP Masks – for raw/tcp/httpupgrade/ws/grpc/xhttp -->
|
||||||
<template v-if="['raw', 'tcp', 'httpupgrade', 'ws', 'grpc', 'xhttp'].includes(inbound.stream.network)">
|
<template v-if="['raw', 'tcp', 'httpupgrade', 'ws', 'grpc', 'xhttp'].includes(inbound.stream.network)">
|
||||||
<a-form-item label="TCP Masks">
|
<a-form-item label="TCP Masks">
|
||||||
<a-button
|
<a-button icon="plus" type="primary" size="small" @click="inbound.stream.addTcpMask('fragment')"></a-button>
|
||||||
icon="plus"
|
</a-form-item>
|
||||||
type="primary"
|
<template v-if="inbound.stream.finalmask.tcp && inbound.stream.finalmask.tcp.length > 0">
|
||||||
size="small"
|
<a-form v-for="(mask, index) in inbound.stream.finalmask.tcp" :key="index" :colon="false"
|
||||||
@click="inbound.stream.addTcpMask('fragment')"
|
:label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
></a-button>
|
<a-divider :style="{ margin: '0' }">
|
||||||
</a-form-item>
|
TCP Mask [[ index + 1 ]]
|
||||||
<template v-if="inbound.stream.finalmask.tcp && inbound.stream.finalmask.tcp.length > 0">
|
<a-icon type="delete" @click="() => inbound.stream.delTcpMask(index)"
|
||||||
<a-form
|
:style="{ color: 'rgb(255, 77, 79)', cursor: 'pointer' }"></a-icon>
|
||||||
v-for="(mask, index) in inbound.stream.finalmask.tcp"
|
</a-divider>
|
||||||
:key="index"
|
<a-form-item label='{{ i18n "pages.xray.outbound.type" }}'>
|
||||||
:colon="false"
|
<a-select v-model="mask.type" @change="(type) => { mask.settings = mask._getDefaultSettings(type, {}); }"
|
||||||
:label-col="{ md: {span:8} }"
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
:wrapper-col="{ md: {span:14} }"
|
<a-select-option value="fragment">Fragment</a-select-option>
|
||||||
>
|
<a-select-option value="header-custom">Header Custom</a-select-option>
|
||||||
<a-divider :style="{ margin: '0' }">
|
<a-select-option value="sudoku">Sudoku</a-select-option>
|
||||||
TCP Mask [[ index + 1 ]]
|
|
||||||
<a-icon
|
|
||||||
type="delete"
|
|
||||||
@click="() => inbound.stream.delTcpMask(index)"
|
|
||||||
:style="{ color: 'rgb(255, 77, 79)', cursor: 'pointer' }"
|
|
||||||
></a-icon>
|
|
||||||
</a-divider>
|
|
||||||
<a-form-item label='{{ i18n "pages.xray.outbound.type" }}'>
|
|
||||||
<a-select
|
|
||||||
v-model="mask.type"
|
|
||||||
@change="(type) => { mask.settings = mask._getDefaultSettings(type, {}); }"
|
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme"
|
|
||||||
>
|
|
||||||
<a-select-option value="fragment">Fragment</a-select-option>
|
|
||||||
<a-select-option value="header-custom">Header Custom</a-select-option>
|
|
||||||
<a-select-option value="sudoku">Sudoku</a-select-option>
|
|
||||||
</a-select>
|
|
||||||
</a-form-item>
|
|
||||||
|
|
||||||
<!-- Fragment settings -->
|
|
||||||
<template v-if="mask.type === 'fragment'">
|
|
||||||
<a-form-item label="Packets">
|
|
||||||
<a-select
|
|
||||||
v-model="mask.settings.packets"
|
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme"
|
|
||||||
>
|
|
||||||
<a-select-option value="tlshello">tlshello</a-select-option>
|
|
||||||
<a-select-option value="1-3">1-3</a-select-option>
|
|
||||||
<a-select-option value="1-5">1-5</a-select-option>
|
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="Length">
|
|
||||||
<a-input v-model.trim="mask.settings.length" placeholder="e.g. 100-200" />
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label="Delay">
|
|
||||||
<a-input v-model.trim="mask.settings.delay" placeholder="e.g. 10-20" />
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label="Max Split">
|
|
||||||
<a-input v-model.trim="mask.settings.maxSplit" placeholder="e.g. 3-6" />
|
|
||||||
</a-form-item>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- Sudoku settings (TCP) -->
|
<!-- Fragment settings -->
|
||||||
<template v-if="mask.type === 'sudoku'">
|
<template v-if="mask.type === 'fragment'">
|
||||||
<a-form-item label="Password">
|
<a-form-item label="Packets">
|
||||||
<a-input v-model.trim="mask.settings.password" placeholder="Obfuscation password" />
|
<a-select v-model="mask.settings.packets" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
</a-form-item>
|
<a-select-option value="tlshello">tlshello</a-select-option>
|
||||||
<a-form-item label="ASCII">
|
<a-select-option value="1-3">1-3</a-select-option>
|
||||||
<a-input v-model.trim="mask.settings.ascii" placeholder="ASCII" />
|
<a-select-option value="1-5">1-5</a-select-option>
|
||||||
</a-form-item>
|
</a-select>
|
||||||
<a-form-item label="Custom Table">
|
</a-form-item>
|
||||||
<a-input v-model.trim="mask.settings.customTable" placeholder="Custom Table" />
|
<a-form-item label="Length">
|
||||||
</a-form-item>
|
<a-input v-model.trim="mask.settings.length" placeholder="e.g. 100-200" />
|
||||||
<a-form-item label="Custom Tables">
|
</a-form-item>
|
||||||
<a-input v-model.trim="mask.settings.customTables" placeholder="Custom Tables" />
|
<a-form-item label="Delay">
|
||||||
</a-form-item>
|
<a-input v-model.trim="mask.settings.delay" placeholder="e.g. 10-20" />
|
||||||
<a-form-item label="Padding Min">
|
</a-form-item>
|
||||||
<a-input-number v-model.number="mask.settings.paddingMin" :min="0" />
|
<a-form-item label="Max Split">
|
||||||
</a-form-item>
|
<a-input v-model.trim="mask.settings.maxSplit" placeholder="e.g. 3-6" />
|
||||||
<a-form-item label="Padding Max">
|
</a-form-item>
|
||||||
<a-input-number v-model.number="mask.settings.paddingMax" :min="0" />
|
</template>
|
||||||
</a-form-item>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- Header Custom (TCP) – clients/servers/errors are 2D arrays of groups -->
|
<!-- Sudoku settings (TCP) -->
|
||||||
<template v-if="mask.type === 'header-custom'">
|
<template v-if="mask.type === 'sudoku'">
|
||||||
<!-- Clients -->
|
<a-form-item label="Password">
|
||||||
<a-form-item label="Clients">
|
<a-input v-model.trim="mask.settings.password" placeholder="Obfuscation password" />
|
||||||
<a-button
|
</a-form-item>
|
||||||
icon="plus"
|
<a-form-item label="ASCII">
|
||||||
type="primary"
|
<a-input v-model.trim="mask.settings.ascii" placeholder="ASCII" />
|
||||||
size="small"
|
</a-form-item>
|
||||||
@click="mask.settings.clients.push([{delay: 0, rand: 0, randRange: '0-255', type: 'array', packet: []}])"
|
<a-form-item label="Custom Table">
|
||||||
></a-button>
|
<a-input v-model.trim="mask.settings.customTable" placeholder="Custom Table" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<template v-for="(group, gi) in mask.settings.clients" :key="'cg'+gi">
|
<a-form-item label="Custom Tables">
|
||||||
<a-divider :style="{ margin: '0' }">
|
<a-input v-model.trim="mask.settings.customTables" placeholder="Custom Tables" />
|
||||||
Clients Group [[ gi + 1 ]]
|
</a-form-item>
|
||||||
<a-icon
|
<a-form-item label="Padding Min">
|
||||||
type="delete"
|
<a-input-number v-model.number="mask.settings.paddingMin" :min="0" />
|
||||||
@click="mask.settings.clients.splice(gi, 1)"
|
</a-form-item>
|
||||||
:style="{ color: 'rgb(255, 77, 79)', cursor: 'pointer', marginLeft: '8px' }"
|
<a-form-item label="Padding Max">
|
||||||
></a-icon>
|
<a-input-number v-model.number="mask.settings.paddingMax" :min="0" />
|
||||||
</a-divider>
|
</a-form-item>
|
||||||
<template v-for="(item, ii) in group" :key="'ci'+ii">
|
</template>
|
||||||
<a-form-item label='{{ i18n "pages.xray.outbound.type" }}'>
|
|
||||||
<a-select v-model="item.type" :dropdown-class-name="themeSwitcher.currentTheme"
|
<!-- Header Custom (TCP) – clients/servers/errors are 2D arrays of groups -->
|
||||||
@change="t => { if(t === 'base64') item.packet = RandomUtil.randomBase64(); else if(t === 'array') { item.rand = 0; item.packet = []; } else { item.packet = ''; } }">
|
<template v-if="mask.type === 'header-custom'">
|
||||||
<a-select-option value="array">Array</a-select-option>
|
<!-- Clients -->
|
||||||
<a-select-option value="str">String</a-select-option>
|
<a-form-item label="Clients">
|
||||||
<a-select-option value="hex">Hex</a-select-option>
|
<a-button icon="plus" type="primary" size="small"
|
||||||
<a-select-option value="base64">Base64</a-select-option>
|
@click="mask.settings.clients.push([{delay: 0, rand: 0, randRange: '0-255', type: 'array', packet: []}])"></a-button>
|
||||||
</a-select>
|
</a-form-item>
|
||||||
</a-form-item>
|
<template v-for="(group, gi) in mask.settings.clients" :key="'cg'+gi">
|
||||||
<a-form-item label="Delay (ms)">
|
<a-divider :style="{ margin: '0' }">
|
||||||
<a-input-number v-model.number="item.delay" :min="0" />
|
Clients Group [[ gi + 1 ]]
|
||||||
</a-form-item>
|
<a-icon type="delete" @click="mask.settings.clients.splice(gi, 1)"
|
||||||
<template v-if="item.type === 'array'">
|
:style="{ color: 'rgb(255, 77, 79)', cursor: 'pointer', marginLeft: '8px' }"></a-icon>
|
||||||
<a-form-item label="Rand">
|
</a-divider>
|
||||||
<a-input-number v-model.number="item.rand" :min="0" />
|
<template v-for="(item, ii) in group" :key="'ci'+ii">
|
||||||
|
<a-form-item label='{{ i18n "pages.xray.outbound.type" }}'>
|
||||||
|
<a-select v-model="item.type" :dropdown-class-name="themeSwitcher.currentTheme"
|
||||||
|
@change="t => { if(t === 'base64') item.packet = RandomUtil.randomBase64(); else if(t === 'array') { item.rand = 0; item.packet = []; } else { item.packet = ''; } }">
|
||||||
|
<a-select-option value="array">Array</a-select-option>
|
||||||
|
<a-select-option value="str">String</a-select-option>
|
||||||
|
<a-select-option value="hex">Hex</a-select-option>
|
||||||
|
<a-select-option value="base64">Base64</a-select-option>
|
||||||
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="Rand Range">
|
<a-form-item label="Delay (ms)">
|
||||||
<a-input v-model.trim="item.randRange" placeholder="0-255" />
|
<a-input-number v-model.number="item.delay" :min="0" />
|
||||||
|
</a-form-item>
|
||||||
|
<template v-if="item.type === 'array'">
|
||||||
|
<a-form-item label="Rand">
|
||||||
|
<a-input-number v-model.number="item.rand" :min="0" />
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="Rand Range">
|
||||||
|
<a-input v-model.trim="item.randRange" placeholder="0-255" />
|
||||||
|
</a-form-item>
|
||||||
|
</template>
|
||||||
|
<a-form-item v-else label="Packet">
|
||||||
|
<a-input-group compact v-if="item.type === 'base64'">
|
||||||
|
<a-input v-model.trim="item.packet" placeholder="binary data"
|
||||||
|
:style="{ width: 'calc(100% - 32px)' }" />
|
||||||
|
<a-button icon="reload" @click="item.packet = RandomUtil.randomBase64()" />
|
||||||
|
</a-input-group>
|
||||||
|
<a-input v-else v-model.trim="item.packet" placeholder="binary data" />
|
||||||
|
</a-form-item>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- Servers -->
|
||||||
|
<a-form-item label="Servers">
|
||||||
|
<a-button icon="plus" type="primary" size="small"
|
||||||
|
@click="mask.settings.servers.push([{delay: 0, rand: 0, randRange: '0-255', type: 'array', packet: []}])"></a-button>
|
||||||
|
</a-form-item>
|
||||||
|
<template v-for="(group, gi) in mask.settings.servers" :key="'sg'+gi">
|
||||||
|
<a-divider :style="{ margin: '0' }">
|
||||||
|
Servers Group [[ gi + 1 ]]
|
||||||
|
<a-icon type="delete" @click="mask.settings.servers.splice(gi, 1)"
|
||||||
|
:style="{ color: 'rgb(255, 77, 79)', cursor: 'pointer', marginLeft: '8px' }"></a-icon>
|
||||||
|
</a-divider>
|
||||||
|
<template v-for="(item, ii) in group" :key="'si'+ii">
|
||||||
|
<a-form-item label='{{ i18n "pages.xray.outbound.type" }}'>
|
||||||
|
<a-select v-model="item.type" :dropdown-class-name="themeSwitcher.currentTheme"
|
||||||
|
@change="t => { if(t === 'base64') item.packet = RandomUtil.randomBase64(); else if(t === 'array') { item.rand = 0; item.packet = []; } else { item.packet = ''; } }">
|
||||||
|
<a-select-option value="array">Array</a-select-option>
|
||||||
|
<a-select-option value="str">String</a-select-option>
|
||||||
|
<a-select-option value="hex">Hex</a-select-option>
|
||||||
|
<a-select-option value="base64">Base64</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="Delay (ms)">
|
||||||
|
<a-input-number v-model.number="item.delay" :min="0" />
|
||||||
|
</a-form-item>
|
||||||
|
<template v-if="item.type === 'array'">
|
||||||
|
<a-form-item label="Rand">
|
||||||
|
<a-input-number v-model.number="item.rand" :min="0" />
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="Rand Range">
|
||||||
|
<a-input v-model.trim="item.randRange" placeholder="0-255" />
|
||||||
|
</a-form-item>
|
||||||
|
</template>
|
||||||
|
<a-form-item v-else label="Packet">
|
||||||
|
<a-input-group compact v-if="item.type === 'base64'">
|
||||||
|
<a-input v-model.trim="item.packet" placeholder="binary data"
|
||||||
|
:style="{ width: 'calc(100% - 32px)' }" />
|
||||||
|
<a-button icon="reload" @click="item.packet = RandomUtil.randomBase64()" />
|
||||||
|
</a-input-group>
|
||||||
|
<a-input v-else v-model.trim="item.packet" placeholder="binary data" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
<a-form-item v-else label="Packet">
|
|
||||||
<a-input-group compact v-if="item.type === 'base64'">
|
|
||||||
<a-input v-model.trim="item.packet" placeholder="binary data" :style="{ width: 'calc(100% - 32px)' }" />
|
|
||||||
<a-button icon="reload" @click="item.packet = RandomUtil.randomBase64()" />
|
|
||||||
</a-input-group>
|
|
||||||
<a-input v-else v-model.trim="item.packet" placeholder="binary data" />
|
|
||||||
</a-form-item>
|
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- Servers -->
|
</a-form>
|
||||||
<a-form-item label="Servers">
|
</template>
|
||||||
<a-button
|
|
||||||
icon="plus"
|
|
||||||
type="primary"
|
|
||||||
size="small"
|
|
||||||
@click="mask.settings.servers.push([{delay: 0, rand: 0, randRange: '0-255', type: 'array', packet: []}])"
|
|
||||||
></a-button>
|
|
||||||
</a-form-item>
|
|
||||||
<template v-for="(group, gi) in mask.settings.servers" :key="'sg'+gi">
|
|
||||||
<a-divider :style="{ margin: '0' }">
|
|
||||||
Servers Group [[ gi + 1 ]]
|
|
||||||
<a-icon
|
|
||||||
type="delete"
|
|
||||||
@click="mask.settings.servers.splice(gi, 1)"
|
|
||||||
:style="{ color: 'rgb(255, 77, 79)', cursor: 'pointer', marginLeft: '8px' }"
|
|
||||||
></a-icon>
|
|
||||||
</a-divider>
|
|
||||||
<template v-for="(item, ii) in group" :key="'si'+ii">
|
|
||||||
<a-form-item label='{{ i18n "pages.xray.outbound.type" }}'>
|
|
||||||
<a-select v-model="item.type" :dropdown-class-name="themeSwitcher.currentTheme"
|
|
||||||
@change="t => { if(t === 'base64') item.packet = RandomUtil.randomBase64(); else if(t === 'array') { item.rand = 0; item.packet = []; } else { item.packet = ''; } }">
|
|
||||||
<a-select-option value="array">Array</a-select-option>
|
|
||||||
<a-select-option value="str">String</a-select-option>
|
|
||||||
<a-select-option value="hex">Hex</a-select-option>
|
|
||||||
<a-select-option value="base64">Base64</a-select-option>
|
|
||||||
</a-select>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label="Delay (ms)">
|
|
||||||
<a-input-number v-model.number="item.delay" :min="0" />
|
|
||||||
</a-form-item>
|
|
||||||
<template v-if="item.type === 'array'">
|
|
||||||
<a-form-item label="Rand">
|
|
||||||
<a-input-number v-model.number="item.rand" :min="0" />
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label="Rand Range">
|
|
||||||
<a-input v-model.trim="item.randRange" placeholder="0-255" />
|
|
||||||
</a-form-item>
|
|
||||||
</template>
|
|
||||||
<a-form-item v-else label="Packet">
|
|
||||||
<a-input-group compact v-if="item.type === 'base64'">
|
|
||||||
<a-input v-model.trim="item.packet" placeholder="binary data" :style="{ width: 'calc(100% - 32px)' }" />
|
|
||||||
<a-button icon="reload" @click="item.packet = RandomUtil.randomBase64()" />
|
|
||||||
</a-input-group>
|
|
||||||
<a-input v-else v-model.trim="item.packet" placeholder="binary data" />
|
|
||||||
</a-form-item>
|
|
||||||
</template>
|
|
||||||
</template>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
</a-form>
|
|
||||||
</template>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-if="inbound.protocol == Protocols.HYSTERIA || inbound.stream.network == 'kcp'">
|
<template v-if="inbound.protocol == Protocols.HYSTERIA || inbound.stream.network == 'kcp'">
|
||||||
<a-form-item label="UDP Masks">
|
<a-form-item label="UDP Masks">
|
||||||
<a-button
|
<a-button icon="plus" type="primary" size="small"
|
||||||
icon="plus"
|
@click="inbound.stream.addUdpMask(inbound.protocol === Protocols.HYSTERIA ? 'salamander' : 'mkcp-aes128gcm')"></a-button>
|
||||||
type="primary"
|
</a-form-item>
|
||||||
size="small"
|
<template v-if="inbound.stream.finalmask.udp && inbound.stream.finalmask.udp.length > 0">
|
||||||
@click="inbound.stream.addUdpMask(inbound.protocol === Protocols.HYSTERIA ? 'salamander' : 'mkcp-aes128gcm')"
|
<a-form v-for="(mask, index) in inbound.stream.finalmask.udp" :key="index" :colon="false"
|
||||||
></a-button>
|
:label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
</a-form-item>
|
<a-divider :style="{ margin: '0' }">
|
||||||
<template
|
UDP Mask [[ index + 1 ]]
|
||||||
v-if="inbound.stream.finalmask.udp && inbound.stream.finalmask.udp.length > 0"
|
<a-icon type="delete" @click="() => inbound.stream.delUdpMask(index)"
|
||||||
>
|
:style="{ color: 'rgb(255, 77, 79)', cursor: 'pointer' }"></a-icon>
|
||||||
<a-form
|
</a-divider>
|
||||||
v-for="(mask, index) in inbound.stream.finalmask.udp"
|
<a-form-item label='{{ i18n "pages.xray.outbound.type" }}'>
|
||||||
:key="index"
|
<a-select v-model="mask.type"
|
||||||
:colon="false"
|
@change="(type) => { mask.settings = mask._getDefaultSettings(type, {}); if(inbound.stream.network === 'kcp') { inbound.stream.kcp.mtu = type === 'xdns' ? 900 : 1350; } }"
|
||||||
:label-col="{ md: {span:8} }"
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
:wrapper-col="{ md: {span:14} }"
|
<template v-if="inbound.protocol === Protocols.HYSTERIA">
|
||||||
>
|
<a-select-option value="salamander">Salamander (Hysteria2)</a-select-option>
|
||||||
<a-divider :style="{ margin: '0' }">
|
</template>
|
||||||
UDP Mask [[ index + 1 ]]
|
<template v-else>
|
||||||
<a-icon
|
<a-select-option value="mkcp-aes128gcm">mKCP AES-128-GCM</a-select-option>
|
||||||
type="delete"
|
<a-select-option value="header-dns">Header DNS</a-select-option>
|
||||||
@click="() => inbound.stream.delUdpMask(index)"
|
<a-select-option value="header-dtls">Header DTLS 1.2</a-select-option>
|
||||||
:style="{ color: 'rgb(255, 77, 79)', cursor: 'pointer' }"
|
<a-select-option value="header-srtp">Header SRTP</a-select-option>
|
||||||
></a-icon>
|
<a-select-option value="header-utp">Header uTP</a-select-option>
|
||||||
</a-divider>
|
<a-select-option value="header-wechat">Header WeChat Video</a-select-option>
|
||||||
<a-form-item label='{{ i18n "pages.xray.outbound.type" }}'>
|
<a-select-option value="header-wireguard">Header WireGuard</a-select-option>
|
||||||
<a-select
|
<a-select-option value="mkcp-original">mKCP Original</a-select-option>
|
||||||
v-model="mask.type"
|
<a-select-option value="xdns">xDNS</a-select-option>
|
||||||
@change="(type) => { mask.settings = mask._getDefaultSettings(type, {}); if(inbound.stream.network === 'kcp') { inbound.stream.kcp.mtu = type === 'xdns' ? 900 : 1350; } }"
|
<a-select-option value="xicmp">xICMP</a-select-option>
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme"
|
<a-select-option value="header-custom">Header Custom</a-select-option>
|
||||||
>
|
<a-select-option value="noise">Noise</a-select-option>
|
||||||
<template v-if="inbound.protocol === Protocols.HYSTERIA">
|
</template>
|
||||||
<a-select-option value="salamander"
|
</a-select>
|
||||||
>Salamander (Hysteria2)</a-select-option
|
|
||||||
>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<a-select-option value="mkcp-aes128gcm"
|
|
||||||
>mKCP AES-128-GCM</a-select-option
|
|
||||||
>
|
|
||||||
<a-select-option value="header-dns">Header DNS</a-select-option>
|
|
||||||
<a-select-option value="header-dtls"
|
|
||||||
>Header DTLS 1.2</a-select-option
|
|
||||||
>
|
|
||||||
<a-select-option value="header-srtp">Header SRTP</a-select-option>
|
|
||||||
<a-select-option value="header-utp">Header uTP</a-select-option>
|
|
||||||
<a-select-option value="header-wechat"
|
|
||||||
>Header WeChat Video</a-select-option
|
|
||||||
>
|
|
||||||
<a-select-option value="header-wireguard"
|
|
||||||
>Header WireGuard</a-select-option
|
|
||||||
>
|
|
||||||
<a-select-option value="mkcp-original"
|
|
||||||
>mKCP Original</a-select-option
|
|
||||||
>
|
|
||||||
<a-select-option value="xdns">xDNS</a-select-option>
|
|
||||||
<a-select-option value="xicmp">xICMP</a-select-option>
|
|
||||||
<a-select-option value="header-custom">Header Custom</a-select-option>
|
|
||||||
<a-select-option value="noise">Noise</a-select-option>
|
|
||||||
</template>
|
|
||||||
</a-select>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item
|
|
||||||
label="Password"
|
|
||||||
v-if="['mkcp-aes128gcm', 'salamander'].includes(mask.type)"
|
|
||||||
>
|
|
||||||
<a-input
|
|
||||||
v-model.trim="mask.settings.password"
|
|
||||||
placeholder="Obfuscation password"
|
|
||||||
></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label="Domain" v-if="mask.type === 'header-dns'">
|
|
||||||
<a-input
|
|
||||||
v-model.trim="mask.settings.domain"
|
|
||||||
placeholder="e.g., www.example.com"
|
|
||||||
></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
<template v-if="mask.type === 'xdns'">
|
|
||||||
<a-form-item label="Domains">
|
|
||||||
<a-select
|
|
||||||
mode="tags"
|
|
||||||
v-model="mask.settings.domains"
|
|
||||||
:style="{ width: '100%' }"
|
|
||||||
:token-separators="[',']"
|
|
||||||
placeholder="e.g., www.example.com"
|
|
||||||
></a-select>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</template>
|
<a-form-item label="Password" v-if="['mkcp-aes128gcm', 'salamander'].includes(mask.type)">
|
||||||
<template v-if="mask.type === 'noise'">
|
<a-input v-model.trim="mask.settings.password" placeholder="Obfuscation password"></a-input>
|
||||||
<a-form-item label="Reset">
|
|
||||||
<a-input-number v-model.number="mask.settings.reset" :min="0" />
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="Noise">
|
<a-form-item label="Domain" v-if="mask.type === 'header-dns'">
|
||||||
<a-button
|
<a-input v-model.trim="mask.settings.domain" placeholder="e.g., www.example.com"></a-input>
|
||||||
icon="plus"
|
|
||||||
type="primary"
|
|
||||||
size="small"
|
|
||||||
@click="mask.settings.noise.push({rand: '1-8192', randRange: '0-255', type: 'array', packet: [], delay: '10-20'})"
|
|
||||||
></a-button>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<template v-for="(n, index) in mask.settings.noise" :key="index">
|
<template v-if="mask.type === 'xdns'">
|
||||||
<a-divider :style="{ margin: '0' }">
|
<a-form-item label="Domains">
|
||||||
Noise [[ index + 1 ]]
|
<a-select mode="tags" v-model="mask.settings.domains" :style="{ width: '100%' }" :token-separators="[',']"
|
||||||
<a-icon
|
placeholder="e.g., www.example.com"></a-select>
|
||||||
type="delete"
|
|
||||||
@click="() => mask.settings.noise.splice(index, 1)"
|
|
||||||
:style="{ color: 'rgb(255, 77, 79)', cursor: 'pointer' }"
|
|
||||||
></a-icon>
|
|
||||||
</a-divider>
|
|
||||||
<a-form-item label='{{ i18n "pages.xray.outbound.type" }}'>
|
|
||||||
<a-select
|
|
||||||
v-model="n.type"
|
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme"
|
|
||||||
@change="t => { if(t === 'base64') n.packet = RandomUtil.randomBase64(); else if(t === 'array') n.packet = []; else n.packet = ''; }"
|
|
||||||
>
|
|
||||||
<a-select-option value="array">Array</a-select-option>
|
|
||||||
<a-select-option value="str">String</a-select-option>
|
|
||||||
<a-select-option value="hex">Hex</a-select-option>
|
|
||||||
<a-select-option value="base64">Base64</a-select-option>
|
|
||||||
</a-select>
|
|
||||||
</a-form-item>
|
|
||||||
<template v-if="n.type === 'array'">
|
|
||||||
<a-form-item label="Rand">
|
|
||||||
<a-input v-model.trim="n.rand" placeholder="0 or 1-8192" />
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label="Rand Range">
|
|
||||||
<a-input v-model.trim="n.randRange" placeholder="0-255" />
|
|
||||||
</a-form-item>
|
|
||||||
</template>
|
|
||||||
<a-form-item v-else label="Packet">
|
|
||||||
<a-input-group compact v-if="n.type === 'base64'">
|
|
||||||
<a-input v-model.trim="n.packet" placeholder="binary data" :style="{ width: 'calc(100% - 32px)' }" />
|
|
||||||
<a-button icon="reload" @click="n.packet = RandomUtil.randomBase64()" />
|
|
||||||
</a-input-group>
|
|
||||||
<a-input v-else v-model.trim="n.packet" placeholder="binary data" />
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label="Delay">
|
|
||||||
<a-input v-model.trim="n.delay" placeholder="10-20" />
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
<template v-if="mask.type === 'noise'">
|
||||||
<template v-if="mask.type === 'header-custom'">
|
<a-form-item label="Reset">
|
||||||
<a-form-item label="Client">
|
<a-input-number v-model.number="mask.settings.reset" :min="0" />
|
||||||
<a-button
|
|
||||||
icon="plus"
|
|
||||||
type="primary"
|
|
||||||
size="small"
|
|
||||||
@click="mask.settings.client.push({rand: 0, randRange: '0-255', type: 'array', packet: []})"
|
|
||||||
></a-button>
|
|
||||||
</a-form-item>
|
|
||||||
<template v-for="(c, index) in mask.settings.client" :key="index">
|
|
||||||
<a-divider :style="{ margin: '0' }">
|
|
||||||
Client [[ index + 1 ]]
|
|
||||||
<a-icon
|
|
||||||
type="delete"
|
|
||||||
@click="mask.settings.client.splice(index, 1)"
|
|
||||||
:style="{ color: 'rgb(255, 77, 79)', cursor: 'pointer' }"
|
|
||||||
></a-icon>
|
|
||||||
</a-divider>
|
|
||||||
<a-form-item label='{{ i18n "pages.xray.outbound.type" }}'>
|
|
||||||
<a-select
|
|
||||||
v-model="c.type"
|
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme"
|
|
||||||
@change="t => { if(t === 'base64') c.packet = RandomUtil.randomBase64(); else if(t === 'array') c.packet = []; else c.packet = ''; }"
|
|
||||||
>
|
|
||||||
<a-select-option value="array">Array</a-select-option>
|
|
||||||
<a-select-option value="str">String</a-select-option>
|
|
||||||
<a-select-option value="hex">Hex</a-select-option>
|
|
||||||
<a-select-option value="base64">Base64</a-select-option>
|
|
||||||
</a-select>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<template v-if="c.type === 'array'">
|
<a-form-item label="Noise">
|
||||||
<a-form-item label="Rand">
|
<a-button icon="plus" type="primary" size="small"
|
||||||
<a-input-number v-model.number="c.rand" />
|
@click="mask.settings.noise.push({rand: '1-8192', randRange: '0-255', type: 'array', packet: [], delay: '10-20'})"></a-button>
|
||||||
|
</a-form-item>
|
||||||
|
<template v-for="(n, index) in mask.settings.noise" :key="index">
|
||||||
|
<a-divider :style="{ margin: '0' }">
|
||||||
|
Noise [[ index + 1 ]]
|
||||||
|
<a-icon type="delete" @click="() => mask.settings.noise.splice(index, 1)"
|
||||||
|
:style="{ color: 'rgb(255, 77, 79)', cursor: 'pointer' }"></a-icon>
|
||||||
|
</a-divider>
|
||||||
|
<a-form-item label='{{ i18n "pages.xray.outbound.type" }}'>
|
||||||
|
<a-select v-model="n.type" :dropdown-class-name="themeSwitcher.currentTheme"
|
||||||
|
@change="t => { if(t === 'base64') n.packet = RandomUtil.randomBase64(); else if(t === 'array') n.packet = []; else n.packet = ''; }">
|
||||||
|
<a-select-option value="array">Array</a-select-option>
|
||||||
|
<a-select-option value="str">String</a-select-option>
|
||||||
|
<a-select-option value="hex">Hex</a-select-option>
|
||||||
|
<a-select-option value="base64">Base64</a-select-option>
|
||||||
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="Rand Range">
|
<template v-if="n.type === 'array'">
|
||||||
<a-input v-model.trim="c.randRange" placeholder="0-255" />
|
<a-form-item label="Rand">
|
||||||
|
<a-input v-model.trim="n.rand" placeholder="0 or 1-8192" />
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="Rand Range">
|
||||||
|
<a-input v-model.trim="n.randRange" placeholder="0-255" />
|
||||||
|
</a-form-item>
|
||||||
|
</template>
|
||||||
|
<a-form-item v-else label="Packet">
|
||||||
|
<a-input-group compact v-if="n.type === 'base64'">
|
||||||
|
<a-input v-model.trim="n.packet" placeholder="binary data" :style="{ width: 'calc(100% - 32px)' }" />
|
||||||
|
<a-button icon="reload" @click="n.packet = RandomUtil.randomBase64()" />
|
||||||
|
</a-input-group>
|
||||||
|
<a-input v-else v-model.trim="n.packet" placeholder="binary data" />
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="Delay">
|
||||||
|
<a-input v-model.trim="n.delay" placeholder="10-20" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
<a-form-item v-else label="Packet">
|
|
||||||
<a-input-group compact v-if="c.type === 'base64'">
|
|
||||||
<a-input v-model.trim="c.packet" placeholder="binary data" :style="{ width: 'calc(100% - 32px)' }" />
|
|
||||||
<a-button icon="reload" @click="c.packet = RandomUtil.randomBase64()" />
|
|
||||||
</a-input-group>
|
|
||||||
<a-input v-else v-model.trim="c.packet" placeholder="binary data" />
|
|
||||||
</a-form-item>
|
|
||||||
</template>
|
</template>
|
||||||
<a-divider :style="{ margin: '0' }"></a-divider>
|
<template v-if="mask.type === 'header-custom'">
|
||||||
<a-form-item label="Server">
|
<a-form-item label="Client">
|
||||||
<a-button
|
<a-button icon="plus" type="primary" size="small"
|
||||||
icon="plus"
|
@click="mask.settings.client.push({rand: 0, randRange: '0-255', type: 'array', packet: []})"></a-button>
|
||||||
type="primary"
|
|
||||||
size="small"
|
|
||||||
@click="mask.settings.server.push({rand: 0, randRange: '0-255', type: 'array', packet: []})"
|
|
||||||
></a-button>
|
|
||||||
</a-form-item>
|
|
||||||
<template v-for="(s, index) in mask.settings.server" :key="index">
|
|
||||||
<a-divider :style="{ margin: '0' }">
|
|
||||||
Server [[ index + 1 ]]
|
|
||||||
<a-icon
|
|
||||||
type="delete"
|
|
||||||
@click="mask.settings.server.splice(index, 1)"
|
|
||||||
:style="{ color: 'rgb(255, 77, 79)', cursor: 'pointer' }"
|
|
||||||
></a-icon>
|
|
||||||
</a-divider>
|
|
||||||
<a-form-item label='{{ i18n "pages.xray.outbound.type" }}'>
|
|
||||||
<a-select
|
|
||||||
v-model="s.type"
|
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme"
|
|
||||||
@change="t => { if(t === 'base64') s.packet = RandomUtil.randomBase64(); else if(t === 'array') s.packet = []; else s.packet = ''; }"
|
|
||||||
>
|
|
||||||
<a-select-option value="array">Array</a-select-option>
|
|
||||||
<a-select-option value="str">String</a-select-option>
|
|
||||||
<a-select-option value="hex">Hex</a-select-option>
|
|
||||||
<a-select-option value="base64">Base64</a-select-option>
|
|
||||||
</a-select>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<template v-if="s.type === 'array'">
|
<template v-for="(c, index) in mask.settings.client" :key="index">
|
||||||
<a-form-item label="Rand">
|
<a-divider :style="{ margin: '0' }">
|
||||||
<a-input-number v-model.number="s.rand" />
|
Client [[ index + 1 ]]
|
||||||
|
<a-icon type="delete" @click="mask.settings.client.splice(index, 1)"
|
||||||
|
:style="{ color: 'rgb(255, 77, 79)', cursor: 'pointer' }"></a-icon>
|
||||||
|
</a-divider>
|
||||||
|
<a-form-item label='{{ i18n "pages.xray.outbound.type" }}'>
|
||||||
|
<a-select v-model="c.type" :dropdown-class-name="themeSwitcher.currentTheme"
|
||||||
|
@change="t => { if(t === 'base64') c.packet = RandomUtil.randomBase64(); else if(t === 'array') c.packet = []; else c.packet = ''; }">
|
||||||
|
<a-select-option value="array">Array</a-select-option>
|
||||||
|
<a-select-option value="str">String</a-select-option>
|
||||||
|
<a-select-option value="hex">Hex</a-select-option>
|
||||||
|
<a-select-option value="base64">Base64</a-select-option>
|
||||||
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="Rand Range">
|
<template v-if="c.type === 'array'">
|
||||||
<a-input v-model.trim="s.randRange" placeholder="0-255" />
|
<a-form-item label="Rand">
|
||||||
|
<a-input-number v-model.number="c.rand" />
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="Rand Range">
|
||||||
|
<a-input v-model.trim="c.randRange" placeholder="0-255" />
|
||||||
|
</a-form-item>
|
||||||
|
</template>
|
||||||
|
<a-form-item v-else label="Packet">
|
||||||
|
<a-input-group compact v-if="c.type === 'base64'">
|
||||||
|
<a-input v-model.trim="c.packet" placeholder="binary data" :style="{ width: 'calc(100% - 32px)' }" />
|
||||||
|
<a-button icon="reload" @click="c.packet = RandomUtil.randomBase64()" />
|
||||||
|
</a-input-group>
|
||||||
|
<a-input v-else v-model.trim="c.packet" placeholder="binary data" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
<a-form-item v-else label="Packet">
|
<a-divider :style="{ margin: '0' }"></a-divider>
|
||||||
<a-input-group compact v-if="s.type === 'base64'">
|
<a-form-item label="Server">
|
||||||
<a-input v-model.trim="s.packet" placeholder="binary data" :style="{ width: 'calc(100% - 32px)' }" />
|
<a-button icon="plus" type="primary" size="small"
|
||||||
<a-button icon="reload" @click="s.packet = RandomUtil.randomBase64()" />
|
@click="mask.settings.server.push({rand: 0, randRange: '0-255', type: 'array', packet: []})"></a-button>
|
||||||
</a-input-group>
|
</a-form-item>
|
||||||
<a-input v-else v-model.trim="s.packet" placeholder="binary data" />
|
<template v-for="(s, index) in mask.settings.server" :key="index">
|
||||||
|
<a-divider :style="{ margin: '0' }">
|
||||||
|
Server [[ index + 1 ]]
|
||||||
|
<a-icon type="delete" @click="mask.settings.server.splice(index, 1)"
|
||||||
|
:style="{ color: 'rgb(255, 77, 79)', cursor: 'pointer' }"></a-icon>
|
||||||
|
</a-divider>
|
||||||
|
<a-form-item label='{{ i18n "pages.xray.outbound.type" }}'>
|
||||||
|
<a-select v-model="s.type" :dropdown-class-name="themeSwitcher.currentTheme"
|
||||||
|
@change="t => { if(t === 'base64') s.packet = RandomUtil.randomBase64(); else if(t === 'array') s.packet = []; else s.packet = ''; }">
|
||||||
|
<a-select-option value="array">Array</a-select-option>
|
||||||
|
<a-select-option value="str">String</a-select-option>
|
||||||
|
<a-select-option value="hex">Hex</a-select-option>
|
||||||
|
<a-select-option value="base64">Base64</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
<template v-if="s.type === 'array'">
|
||||||
|
<a-form-item label="Rand">
|
||||||
|
<a-input-number v-model.number="s.rand" />
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="Rand Range">
|
||||||
|
<a-input v-model.trim="s.randRange" placeholder="0-255" />
|
||||||
|
</a-form-item>
|
||||||
|
</template>
|
||||||
|
<a-form-item v-else label="Packet">
|
||||||
|
<a-input-group compact v-if="s.type === 'base64'">
|
||||||
|
<a-input v-model.trim="s.packet" placeholder="binary data" :style="{ width: 'calc(100% - 32px)' }" />
|
||||||
|
<a-button icon="reload" @click="s.packet = RandomUtil.randomBase64()" />
|
||||||
|
</a-input-group>
|
||||||
|
<a-input v-else v-model.trim="s.packet" placeholder="binary data" />
|
||||||
|
</a-form-item>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
<template v-if="mask.type === 'xicmp'">
|
||||||
|
<a-form-item label="IP">
|
||||||
|
<a-input v-model.trim="mask.settings.ip" placeholder="0.0.0.0" />
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="ID">
|
||||||
|
<a-input-number v-model.number="mask.settings.id" :min="0" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</a-form>
|
||||||
<template v-if="mask.type === 'xicmp'">
|
</template>
|
||||||
<a-form-item label="IP">
|
|
||||||
<a-input v-model.trim="mask.settings.ip" placeholder="0.0.0.0" />
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label="ID">
|
|
||||||
<a-input-number v-model.number="mask.settings.id" :min="0" />
|
|
||||||
</a-form-item>
|
|
||||||
</template>
|
|
||||||
</a-form>
|
|
||||||
</template>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- quicParams – only for xhttp H3 and hysteria -->
|
<!-- quicParams – only for xhttp H3 and hysteria -->
|
||||||
@@ -450,10 +343,8 @@
|
|||||||
</a-form-item>
|
</a-form-item>
|
||||||
<template v-if="inbound.stream.finalmask.enableQuicParams">
|
<template v-if="inbound.stream.finalmask.enableQuicParams">
|
||||||
<a-form-item label="Congestion">
|
<a-form-item label="Congestion">
|
||||||
<a-select
|
<a-select v-model="inbound.stream.finalmask.quicParams.congestion"
|
||||||
v-model="inbound.stream.finalmask.quicParams.congestion"
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme"
|
|
||||||
>
|
|
||||||
<a-select-option value="reno">Reno</a-select-option>
|
<a-select-option value="reno">Reno</a-select-option>
|
||||||
<a-select-option value="bbr">BBR</a-select-option>
|
<a-select-option value="bbr">BBR</a-select-option>
|
||||||
<a-select-option value="brutal">Brutal</a-select-option>
|
<a-select-option value="brutal">Brutal</a-select-option>
|
||||||
@@ -492,19 +383,24 @@
|
|||||||
<a-switch v-model="inbound.stream.finalmask.quicParams.disablePathMTUDiscovery"></a-switch>
|
<a-switch v-model="inbound.stream.finalmask.quicParams.disablePathMTUDiscovery"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="Max Incoming Streams">
|
<a-form-item label="Max Incoming Streams">
|
||||||
<a-input-number v-model.number="inbound.stream.finalmask.quicParams.maxIncomingStreams" :min="0" placeholder="0 = default" />
|
<a-input-number v-model.number="inbound.stream.finalmask.quicParams.maxIncomingStreams" :min="0"
|
||||||
|
placeholder="0 = default" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="Init Stream Window">
|
<a-form-item label="Init Stream Window">
|
||||||
<a-input-number v-model.number="inbound.stream.finalmask.quicParams.initStreamReceiveWindow" :min="0" placeholder="0 = default" />
|
<a-input-number v-model.number="inbound.stream.finalmask.quicParams.initStreamReceiveWindow" :min="0"
|
||||||
|
placeholder="0 = default" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="Max Stream Window">
|
<a-form-item label="Max Stream Window">
|
||||||
<a-input-number v-model.number="inbound.stream.finalmask.quicParams.maxStreamReceiveWindow" :min="0" placeholder="0 = default" />
|
<a-input-number v-model.number="inbound.stream.finalmask.quicParams.maxStreamReceiveWindow" :min="0"
|
||||||
|
placeholder="0 = default" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="Init Conn Window">
|
<a-form-item label="Init Conn Window">
|
||||||
<a-input-number v-model.number="inbound.stream.finalmask.quicParams.initConnectionReceiveWindow" :min="0" placeholder="0 = default" />
|
<a-input-number v-model.number="inbound.stream.finalmask.quicParams.initConnectionReceiveWindow" :min="0"
|
||||||
|
placeholder="0 = default" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="Max Conn Window">
|
<a-form-item label="Max Conn Window">
|
||||||
<a-input-number v-model.number="inbound.stream.finalmask.quicParams.maxConnectionReceiveWindow" :min="0" placeholder="0 = default" />
|
<a-input-number v-model.number="inbound.stream.finalmask.quicParams.maxConnectionReceiveWindow" :min="0"
|
||||||
|
placeholder="0 = default" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,9 +1,5 @@
|
|||||||
{{define "form/streamGRPC"}}
|
{{define "form/streamGRPC"}}
|
||||||
<a-form
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
:colon="false"
|
|
||||||
:label-col="{ md: {span:8} }"
|
|
||||||
:wrapper-col="{ md: {span:14} }"
|
|
||||||
>
|
|
||||||
<a-form-item label="Service Name">
|
<a-form-item label="Service Name">
|
||||||
<a-input v-model.trim="inbound.stream.grpc.serviceName"></a-input>
|
<a-input v-model.trim="inbound.stream.grpc.serviceName"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|||||||
@@ -1,13 +1,7 @@
|
|||||||
{{define "form/streamHTTPUpgrade"}}
|
{{define "form/streamHTTPUpgrade"}}
|
||||||
<a-form
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
:colon="false"
|
|
||||||
:label-col="{ md: {span:8} }"
|
|
||||||
:wrapper-col="{ md: {span:14} }"
|
|
||||||
>
|
|
||||||
<a-form-item label="Proxy Protocol">
|
<a-form-item label="Proxy Protocol">
|
||||||
<a-switch
|
<a-switch v-model="inbound.stream.httpupgrade.acceptProxyProtocol"></a-switch>
|
||||||
v-model="inbound.stream.httpupgrade.acceptProxyProtocol"
|
|
||||||
></a-switch>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "host" }}'>
|
<a-form-item label='{{ i18n "host" }}'>
|
||||||
<a-input v-model.trim="inbound.stream.httpupgrade.host"></a-input>
|
<a-input v-model.trim="inbound.stream.httpupgrade.host"></a-input>
|
||||||
@@ -16,37 +10,18 @@
|
|||||||
<a-input v-model.trim="inbound.stream.httpupgrade.path"></a-input>
|
<a-input v-model.trim="inbound.stream.httpupgrade.path"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestHeader" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestHeader" }}'>
|
||||||
<a-button
|
<a-button icon="plus" size="small" @click="inbound.stream.httpupgrade.addHeader('', '')"></a-button>
|
||||||
icon="plus"
|
|
||||||
size="small"
|
|
||||||
@click="inbound.stream.httpupgrade.addHeader('', '')"
|
|
||||||
></a-button>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item :wrapper-col="{span:24}">
|
<a-form-item :wrapper-col="{span:24}">
|
||||||
<a-input-group
|
<a-input-group compact v-for="(header, index) in inbound.stream.httpupgrade.headers">
|
||||||
compact
|
<a-input :style="{ width: '50%' }" v-model.trim="header.name"
|
||||||
v-for="(header, index) in inbound.stream.httpupgrade.headers"
|
placeholder='{{ i18n "pages.inbounds.stream.general.name"}}'>
|
||||||
>
|
<template slot="addonBefore" :style="{ margin: '0' }">[[ index+1 ]]</template>
|
||||||
<a-input
|
|
||||||
:style="{ width: '50%' }"
|
|
||||||
v-model.trim="header.name"
|
|
||||||
placeholder='{{ i18n "pages.inbounds.stream.general.name"}}'
|
|
||||||
>
|
|
||||||
<template slot="addonBefore" :style="{ margin: '0' }"
|
|
||||||
>[[ index+1 ]]</template
|
|
||||||
>
|
|
||||||
</a-input>
|
</a-input>
|
||||||
<a-input
|
<a-input :style="{ width: '50%' }" v-model.trim="header.value"
|
||||||
:style="{ width: '50%' }"
|
placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'>
|
||||||
v-model.trim="header.value"
|
<a-button icon="minus" slot="addonAfter" size="small"
|
||||||
placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'
|
@click="inbound.stream.httpupgrade.removeHeader(index)"></a-button>
|
||||||
>
|
|
||||||
<a-button
|
|
||||||
icon="minus"
|
|
||||||
slot="addonAfter"
|
|
||||||
size="small"
|
|
||||||
@click="inbound.stream.httpupgrade.removeHeader(index)"
|
|
||||||
></a-button>
|
|
||||||
</a-input>
|
</a-input>
|
||||||
</a-input-group>
|
</a-input-group>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|||||||
@@ -1,14 +1,7 @@
|
|||||||
{{define "form/streamHysteria"}}
|
{{define "form/streamHysteria"}}
|
||||||
<a-form
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
:colon="false"
|
|
||||||
:label-col="{ md: {span:8} }"
|
|
||||||
:wrapper-col="{ md: {span:14} }"
|
|
||||||
>
|
|
||||||
<a-form-item label="UDP Idle Timeout">
|
<a-form-item label="UDP Idle Timeout">
|
||||||
<a-input-number
|
<a-input-number v-model.number="inbound.stream.hysteria.udpIdleTimeout" :min="0"></a-input-number>
|
||||||
v-model.number="inbound.stream.hysteria.udpIdleTimeout"
|
|
||||||
:min="0"
|
|
||||||
></a-input-number>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="Masquerade">
|
<a-form-item label="Masquerade">
|
||||||
<a-switch v-model="inbound.stream.hysteria.masqueradeSwitch"></a-switch>
|
<a-switch v-model="inbound.stream.hysteria.masqueradeSwitch"></a-switch>
|
||||||
@@ -16,83 +9,48 @@
|
|||||||
<template v-if="inbound.stream.hysteria.masqueradeSwitch">
|
<template v-if="inbound.stream.hysteria.masqueradeSwitch">
|
||||||
<a-divider :style="{ margin: '5px 0 0' }">Masquerade</a-divider>
|
<a-divider :style="{ margin: '5px 0 0' }">Masquerade</a-divider>
|
||||||
<a-form-item label="Type">
|
<a-form-item label="Type">
|
||||||
<a-select
|
<a-select v-model="inbound.stream.hysteria.masquerade.type" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
v-model="inbound.stream.hysteria.masquerade.type"
|
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme"
|
|
||||||
>
|
|
||||||
<a-select-option value="file">File</a-select-option>
|
<a-select-option value="file">File</a-select-option>
|
||||||
<a-select-option value="proxy">Proxy</a-select-option>
|
<a-select-option value="proxy">Proxy</a-select-option>
|
||||||
<a-select-option value="string">String</a-select-option>
|
<a-select-option value="string">String</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item
|
<a-form-item label="Dir" v-if="inbound.stream.hysteria.masquerade.type === 'file'">
|
||||||
label="Dir"
|
|
||||||
v-if="inbound.stream.hysteria.masquerade.type === 'file'"
|
|
||||||
>
|
|
||||||
<a-input v-model.trim="inbound.stream.hysteria.masquerade.dir"></a-input>
|
<a-input v-model.trim="inbound.stream.hysteria.masquerade.dir"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<template v-if="inbound.stream.hysteria.masquerade.type === 'proxy'">
|
<template v-if="inbound.stream.hysteria.masquerade.type === 'proxy'">
|
||||||
<a-form-item label="URL">
|
<a-form-item label="URL">
|
||||||
<a-input
|
<a-input v-model.trim="inbound.stream.hysteria.masquerade.url"></a-input>
|
||||||
v-model.trim="inbound.stream.hysteria.masquerade.url"
|
|
||||||
></a-input>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="Rewrite Host">
|
<a-form-item label="Rewrite Host">
|
||||||
<a-switch
|
<a-switch v-model="inbound.stream.hysteria.masquerade.rewriteHost"></a-switch>
|
||||||
v-model="inbound.stream.hysteria.masquerade.rewriteHost"
|
|
||||||
></a-switch>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="Insecure">
|
<a-form-item label="Insecure">
|
||||||
<a-switch
|
<a-switch v-model="inbound.stream.hysteria.masquerade.insecure"></a-switch>
|
||||||
v-model="inbound.stream.hysteria.masquerade.insecure"
|
|
||||||
></a-switch>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
<template v-if="inbound.stream.hysteria.masquerade.type === 'string'">
|
<template v-if="inbound.stream.hysteria.masquerade.type === 'string'">
|
||||||
<a-form-item label="Content">
|
<a-form-item label="Content">
|
||||||
<a-input
|
<a-input v-model.trim="inbound.stream.hysteria.masquerade.content"></a-input>
|
||||||
v-model.trim="inbound.stream.hysteria.masquerade.content"
|
|
||||||
></a-input>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestHeader" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestHeader" }}'>
|
||||||
<a-button
|
<a-button size="small" @click="inbound.stream.hysteria.masquerade.addHeader('', '')">+</a-button>
|
||||||
size="small"
|
|
||||||
@click="inbound.stream.hysteria.masquerade.addHeader('', '')"
|
|
||||||
>+</a-button
|
|
||||||
>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item :wrapper-col="{span:24}">
|
<a-form-item :wrapper-col="{span:24}">
|
||||||
<a-input-group
|
<a-input-group compact v-for="(header, index) in inbound.stream.hysteria.masquerade.headers">
|
||||||
compact
|
<a-input style="width: 50%" v-model.trim="header.name"
|
||||||
v-for="(header, index) in inbound.stream.hysteria.masquerade.headers"
|
placeholder='{{ i18n "pages.inbounds.stream.general.name"}}'>
|
||||||
>
|
<template slot="addonBefore" style="margin: 0">[[ index+1 ]]</template>
|
||||||
<a-input
|
|
||||||
style="width: 50%"
|
|
||||||
v-model.trim="header.name"
|
|
||||||
placeholder='{{ i18n "pages.inbounds.stream.general.name"}}'
|
|
||||||
>
|
|
||||||
<template slot="addonBefore" style="margin: 0"
|
|
||||||
>[[ index+1 ]]</template
|
|
||||||
>
|
|
||||||
</a-input>
|
</a-input>
|
||||||
<a-input
|
<a-input style="width: 50%" v-model.trim="header.value"
|
||||||
style="width: 50%"
|
placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'>
|
||||||
v-model.trim="header.value"
|
<a-button slot="addonAfter" size="small"
|
||||||
placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'
|
@click="inbound.stream.hysteria.masquerade.removeHeader(index)">-</a-button>
|
||||||
>
|
|
||||||
<a-button
|
|
||||||
slot="addonAfter"
|
|
||||||
size="small"
|
|
||||||
@click="inbound.stream.hysteria.masquerade.removeHeader(index)"
|
|
||||||
>-</a-button
|
|
||||||
>
|
|
||||||
</a-input>
|
</a-input>
|
||||||
</a-input-group>
|
</a-input-group>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="Status Code">
|
<a-form-item label="Status Code">
|
||||||
<a-input-number
|
<a-input-number v-model.number="inbound.stream.hysteria.masquerade.statusCode"></a-input-number>
|
||||||
v-model.number="inbound.stream.hysteria.masquerade.statusCode"
|
|
||||||
></a-input-number>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,46 +1,22 @@
|
|||||||
{{define "form/streamKCP"}}
|
{{define "form/streamKCP"}}
|
||||||
<a-form
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
:colon="false"
|
|
||||||
:label-col="{ md: {span:8} }"
|
|
||||||
:wrapper-col="{ md: {span:14} }"
|
|
||||||
>
|
|
||||||
<a-form-item label="MTU">
|
<a-form-item label="MTU">
|
||||||
<a-input-number
|
<a-input-number v-model.number="inbound.stream.kcp.mtu" :min="576" :max="1460"></a-input-number>
|
||||||
v-model.number="inbound.stream.kcp.mtu"
|
|
||||||
:min="576"
|
|
||||||
:max="1460"
|
|
||||||
></a-input-number>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="TTI (ms)">
|
<a-form-item label="TTI (ms)">
|
||||||
<a-input-number
|
<a-input-number v-model.number="inbound.stream.kcp.tti" :min="10" :max="100"></a-input-number>
|
||||||
v-model.number="inbound.stream.kcp.tti"
|
|
||||||
:min="10"
|
|
||||||
:max="100"
|
|
||||||
></a-input-number>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="Uplink (MB/s)">
|
<a-form-item label="Uplink (MB/s)">
|
||||||
<a-input-number
|
<a-input-number v-model.number="inbound.stream.kcp.upCap" :min="0"></a-input-number>
|
||||||
v-model.number="inbound.stream.kcp.upCap"
|
|
||||||
:min="0"
|
|
||||||
></a-input-number>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="Downlink (MB/s)">
|
<a-form-item label="Downlink (MB/s)">
|
||||||
<a-input-number
|
<a-input-number v-model.number="inbound.stream.kcp.downCap" :min="0"></a-input-number>
|
||||||
v-model.number="inbound.stream.kcp.downCap"
|
|
||||||
:min="0"
|
|
||||||
></a-input-number>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="CWND Multiplier">
|
<a-form-item label="CWND Multiplier">
|
||||||
<a-input-number
|
<a-input-number v-model.number="inbound.stream.kcp.cwndMultiplier" :min="1"></a-input-number>
|
||||||
v-model.number="inbound.stream.kcp.cwndMultiplier"
|
|
||||||
:min="1"
|
|
||||||
></a-input-number>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="Max Sending Window">
|
<a-form-item label="Max Sending Window">
|
||||||
<a-input-number
|
<a-input-number v-model.number="inbound.stream.kcp.maxSendingWindow" :min="0"></a-input-number>
|
||||||
v-model.number="inbound.stream.kcp.maxSendingWindow"
|
|
||||||
:min="0"
|
|
||||||
></a-input-number>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
{{end}}
|
{{end}}
|
||||||
@@ -1,18 +1,10 @@
|
|||||||
{{define "form/streamSettings"}}
|
{{define "form/streamSettings"}}
|
||||||
<!-- select stream network -->
|
<!-- select stream network -->
|
||||||
<a-form
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }"
|
||||||
:colon="false"
|
v-if="inbound.protocol != Protocols.HYSTERIA">
|
||||||
:label-col="{ md: {span:8} }"
|
|
||||||
:wrapper-col="{ md: {span:14} }"
|
|
||||||
v-if="inbound.protocol != Protocols.HYSTERIA"
|
|
||||||
>
|
|
||||||
<a-form-item label='{{ i18n "transmission" }}'>
|
<a-form-item label='{{ i18n "transmission" }}'>
|
||||||
<a-select
|
<a-select v-model="inbound.stream.network" :style="{ width: '75%' }" @change="streamNetworkChange"
|
||||||
v-model="inbound.stream.network"
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
:style="{ width: '75%' }"
|
|
||||||
@change="streamNetworkChange"
|
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme"
|
|
||||||
>
|
|
||||||
<a-select-option value="tcp">TCP (RAW)</a-select-option>
|
<a-select-option value="tcp">TCP (RAW)</a-select-option>
|
||||||
<a-select-option value="kcp">mKCP</a-select-option>
|
<a-select-option value="kcp">mKCP</a-select-option>
|
||||||
<a-select-option value="ws">WebSocket</a-select-option>
|
<a-select-option value="ws">WebSocket</a-select-option>
|
||||||
|
|||||||
@@ -1,49 +1,27 @@
|
|||||||
{{define "form/streamSockopt"}}
|
{{define "form/streamSockopt"}}
|
||||||
<a-divider :style="{ margin: '5px 0 0' }"></a-divider>
|
<a-divider :style="{ margin: '5px 0 0' }"></a-divider>
|
||||||
<a-form
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
:colon="false"
|
|
||||||
:label-col="{ md: {span:8} }"
|
|
||||||
:wrapper-col="{ md: {span:14} }"
|
|
||||||
>
|
|
||||||
<a-form-item label="Sockopt">
|
<a-form-item label="Sockopt">
|
||||||
<a-switch v-model="inbound.stream.sockoptSwitch"></a-switch>
|
<a-switch v-model="inbound.stream.sockoptSwitch"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<template v-if="inbound.stream.sockoptSwitch">
|
<template v-if="inbound.stream.sockoptSwitch">
|
||||||
<a-form-item label="Route Mark">
|
<a-form-item label="Route Mark">
|
||||||
<a-input-number
|
<a-input-number v-model.number="inbound.stream.sockopt.mark" :min="0"></a-input-number>
|
||||||
v-model.number="inbound.stream.sockopt.mark"
|
|
||||||
:min="0"
|
|
||||||
></a-input-number>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="TCP Keep Alive Interval">
|
<a-form-item label="TCP Keep Alive Interval">
|
||||||
<a-input-number
|
<a-input-number v-model.number="inbound.stream.sockopt.tcpKeepAliveInterval" :min="0"></a-input-number>
|
||||||
v-model.number="inbound.stream.sockopt.tcpKeepAliveInterval"
|
|
||||||
:min="0"
|
|
||||||
></a-input-number>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="TCP Keep Alive Idle">
|
<a-form-item label="TCP Keep Alive Idle">
|
||||||
<a-input-number
|
<a-input-number v-model.number="inbound.stream.sockopt.tcpKeepAliveIdle" :min="0"></a-input-number>
|
||||||
v-model.number="inbound.stream.sockopt.tcpKeepAliveIdle"
|
|
||||||
:min="0"
|
|
||||||
></a-input-number>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="TCP Max Seg">
|
<a-form-item label="TCP Max Seg">
|
||||||
<a-input-number
|
<a-input-number v-model.number="inbound.stream.sockopt.tcpMaxSeg" :min="0"></a-input-number>
|
||||||
v-model.number="inbound.stream.sockopt.tcpMaxSeg"
|
|
||||||
:min="0"
|
|
||||||
></a-input-number>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="TCP User Timeout">
|
<a-form-item label="TCP User Timeout">
|
||||||
<a-input-number
|
<a-input-number v-model.number="inbound.stream.sockopt.tcpUserTimeout" :min="0"></a-input-number>
|
||||||
v-model.number="inbound.stream.sockopt.tcpUserTimeout"
|
|
||||||
:min="0"
|
|
||||||
></a-input-number>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="TCP Window Clamp">
|
<a-form-item label="TCP Window Clamp">
|
||||||
<a-input-number
|
<a-input-number v-model.number="inbound.stream.sockopt.tcpWindowClamp" :min="0"></a-input-number>
|
||||||
v-model.number="inbound.stream.sockopt.tcpWindowClamp"
|
|
||||||
:min="0"
|
|
||||||
></a-input-number>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="Proxy Protocol">
|
<a-form-item label="Proxy Protocol">
|
||||||
<a-switch v-model="inbound.stream.sockopt.acceptProxyProtocol"></a-switch>
|
<a-switch v-model="inbound.stream.sockopt.acceptProxyProtocol"></a-switch>
|
||||||
@@ -61,33 +39,20 @@
|
|||||||
<a-switch v-model.trim="inbound.stream.sockopt.V6Only"></a-switch>
|
<a-switch v-model.trim="inbound.stream.sockopt.V6Only"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="Domain Strategy">
|
<a-form-item label="Domain Strategy">
|
||||||
<a-select
|
<a-select v-model="inbound.stream.sockopt.domainStrategy" :style="{ width: '50%' }"
|
||||||
v-model="inbound.stream.sockopt.domainStrategy"
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
:style="{ width: '50%' }"
|
<a-select-option v-for="key in DOMAIN_STRATEGY_OPTION" :value="key">[[ key ]]</a-select-option>
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme"
|
|
||||||
>
|
|
||||||
<a-select-option v-for="key in DOMAIN_STRATEGY_OPTION" :value="key"
|
|
||||||
>[[ key ]]</a-select-option
|
|
||||||
>
|
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="TCP Congestion">
|
<a-form-item label="TCP Congestion">
|
||||||
<a-select
|
<a-select v-model="inbound.stream.sockopt.tcpcongestion" :style="{ width: '50%' }"
|
||||||
v-model="inbound.stream.sockopt.tcpcongestion"
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
:style="{ width: '50%' }"
|
<a-select-option v-for="key in TCP_CONGESTION_OPTION" :value="key">[[ key ]]</a-select-option>
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme"
|
|
||||||
>
|
|
||||||
<a-select-option v-for="key in TCP_CONGESTION_OPTION" :value="key"
|
|
||||||
>[[ key ]]</a-select-option
|
|
||||||
>
|
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="TProxy">
|
<a-form-item label="TProxy">
|
||||||
<a-select
|
<a-select v-model="inbound.stream.sockopt.tproxy" :style="{ width: '50%' }"
|
||||||
v-model="inbound.stream.sockopt.tproxy"
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
:style="{ width: '50%' }"
|
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme"
|
|
||||||
>
|
|
||||||
<a-select-option value="off">Off</a-select-option>
|
<a-select-option value="off">Off</a-select-option>
|
||||||
<a-select-option value="redirect">Redirect</a-select-option>
|
<a-select-option value="redirect">Redirect</a-select-option>
|
||||||
<a-select-option value="tproxy">TProxy</a-select-option>
|
<a-select-option value="tproxy">TProxy</a-select-option>
|
||||||
@@ -100,15 +65,9 @@
|
|||||||
<a-input v-model="inbound.stream.sockopt.interfaceName"></a-input>
|
<a-input v-model="inbound.stream.sockopt.interfaceName"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="Trusted X-Forwarded-For">
|
<a-form-item label="Trusted X-Forwarded-For">
|
||||||
<a-select
|
<a-select mode="tags" v-model="inbound.stream.sockopt.trustedXForwardedFor" :style="{ width: '100%' }"
|
||||||
mode="tags"
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
v-model="inbound.stream.sockopt.trustedXForwardedFor"
|
<a-select-option value="CF-Connecting-IP">CF-Connecting-IP</a-select-option>
|
||||||
:style="{ width: '100%' }"
|
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme"
|
|
||||||
>
|
|
||||||
<a-select-option value="CF-Connecting-IP"
|
|
||||||
>CF-Connecting-IP</a-select-option
|
|
||||||
>
|
|
||||||
<a-select-option value="X-Real-IP">X-Real-IP</a-select-option>
|
<a-select-option value="X-Real-IP">X-Real-IP</a-select-option>
|
||||||
<a-select-option value="True-Client-IP">True-Client-IP</a-select-option>
|
<a-select-option value="True-Client-IP">True-Client-IP</a-select-option>
|
||||||
<a-select-option value="X-Client-IP">X-Client-IP</a-select-option>
|
<a-select-option value="X-Client-IP">X-Client-IP</a-select-option>
|
||||||
|
|||||||
@@ -1,31 +1,19 @@
|
|||||||
{{define "form/streamTCP"}}
|
{{define "form/streamTCP"}}
|
||||||
<!-- tcp type -->
|
<!-- tcp type -->
|
||||||
<a-form
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
:colon="false"
|
|
||||||
:label-col="{ md: {span:8} }"
|
|
||||||
:wrapper-col="{ md: {span:14} }"
|
|
||||||
>
|
|
||||||
<a-form-item label="Proxy Protocol" v-if="inbound.canEnableTls()">
|
<a-form-item label="Proxy Protocol" v-if="inbound.canEnableTls()">
|
||||||
<a-switch v-model="inbound.stream.tcp.acceptProxyProtocol"></a-switch>
|
<a-switch v-model="inbound.stream.tcp.acceptProxyProtocol"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='HTTP {{ i18n "camouflage" }}'>
|
<a-form-item label='HTTP {{ i18n "camouflage" }}'>
|
||||||
<a-switch
|
<a-switch :checked="inbound.stream.tcp.type === 'http'"
|
||||||
:checked="inbound.stream.tcp.type === 'http'"
|
@change="checked => inbound.stream.tcp.type = checked ? 'http' : 'none'"></a-switch>
|
||||||
@change="checked => inbound.stream.tcp.type = checked ? 'http' : 'none'"
|
|
||||||
></a-switch>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
|
|
||||||
<a-form
|
<a-form v-if="inbound.stream.tcp.type === 'http'" :colon="false" :label-col="{ md: {span:8} }"
|
||||||
v-if="inbound.stream.tcp.type === 'http'"
|
:wrapper-col="{ md: {span:14} }">
|
||||||
:colon="false"
|
|
||||||
:label-col="{ md: {span:8} }"
|
|
||||||
:wrapper-col="{ md: {span:14} }"
|
|
||||||
>
|
|
||||||
<!-- tcp request -->
|
<!-- tcp request -->
|
||||||
<a-divider :style="{ margin: '0' }"
|
<a-divider :style="{ margin: '0' }">{{ i18n "pages.inbounds.stream.general.request" }}</a-divider>
|
||||||
>{{ i18n "pages.inbounds.stream.general.request" }}</a-divider
|
|
||||||
>
|
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.version" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.version" }}'>
|
||||||
<a-input v-model.trim="inbound.stream.tcp.request.version"></a-input>
|
<a-input v-model.trim="inbound.stream.tcp.request.version"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
@@ -33,66 +21,35 @@
|
|||||||
<a-input v-model.trim="inbound.stream.tcp.request.method"></a-input>
|
<a-input v-model.trim="inbound.stream.tcp.request.method"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<template slot="label"
|
<template slot="label">{{ i18n "pages.inbounds.stream.tcp.path" }}
|
||||||
>{{ i18n "pages.inbounds.stream.tcp.path" }}
|
<a-button icon="plus" size="small" @click="inbound.stream.tcp.request.addPath('/')"></a-button>
|
||||||
<a-button
|
|
||||||
icon="plus"
|
|
||||||
size="small"
|
|
||||||
@click="inbound.stream.tcp.request.addPath('/')"
|
|
||||||
></a-button>
|
|
||||||
</template>
|
</template>
|
||||||
<template v-for="(path, index) in inbound.stream.tcp.request.path">
|
<template v-for="(path, index) in inbound.stream.tcp.request.path">
|
||||||
<a-input v-model.trim="inbound.stream.tcp.request.path[index]">
|
<a-input v-model.trim="inbound.stream.tcp.request.path[index]">
|
||||||
<a-button
|
<a-button icon="minus" size="small" slot="addonAfter" @click="inbound.stream.tcp.request.removePath(index)"
|
||||||
icon="minus"
|
v-if="inbound.stream.tcp.request.path.length>1"></a-button>
|
||||||
size="small"
|
|
||||||
slot="addonAfter"
|
|
||||||
@click="inbound.stream.tcp.request.removePath(index)"
|
|
||||||
v-if="inbound.stream.tcp.request.path.length>1"
|
|
||||||
></a-button>
|
|
||||||
</a-input>
|
</a-input>
|
||||||
</template>
|
</template>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestHeader" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestHeader" }}'>
|
||||||
<a-button
|
<a-button icon="plus" size="small" @click="inbound.stream.tcp.request.addHeader('Host', '')"></a-button>
|
||||||
icon="plus"
|
|
||||||
size="small"
|
|
||||||
@click="inbound.stream.tcp.request.addHeader('Host', '')"
|
|
||||||
></a-button>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item :wrapper-col="{span:24}">
|
<a-form-item :wrapper-col="{span:24}">
|
||||||
<a-input-group
|
<a-input-group compact v-for="(header, index) in inbound.stream.tcp.request.headers">
|
||||||
compact
|
<a-input :style="{ width: '50%' }" v-model.trim="header.name"
|
||||||
v-for="(header, index) in inbound.stream.tcp.request.headers"
|
placeholder='{{ i18n "pages.inbounds.stream.general.name" }}'>
|
||||||
>
|
<template slot="addonBefore" :style="{ margin: '0' }">[[ index+1 ]]</template>
|
||||||
<a-input
|
|
||||||
:style="{ width: '50%' }"
|
|
||||||
v-model.trim="header.name"
|
|
||||||
placeholder='{{ i18n "pages.inbounds.stream.general.name" }}'
|
|
||||||
>
|
|
||||||
<template slot="addonBefore" :style="{ margin: '0' }"
|
|
||||||
>[[ index+1 ]]</template
|
|
||||||
>
|
|
||||||
</a-input>
|
</a-input>
|
||||||
<a-input
|
<a-input :style="{ width: '50%' }" v-model.trim="header.value"
|
||||||
:style="{ width: '50%' }"
|
placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'>
|
||||||
v-model.trim="header.value"
|
<a-button icon="minus" slot="addonAfter" size="small"
|
||||||
placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'
|
@click="inbound.stream.tcp.request.removeHeader(index)"></a-button>
|
||||||
>
|
|
||||||
<a-button
|
|
||||||
icon="minus"
|
|
||||||
slot="addonAfter"
|
|
||||||
size="small"
|
|
||||||
@click="inbound.stream.tcp.request.removeHeader(index)"
|
|
||||||
></a-button>
|
|
||||||
</a-input>
|
</a-input>
|
||||||
</a-input-group>
|
</a-input-group>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
<!-- tcp response -->
|
<!-- tcp response -->
|
||||||
<a-divider :style="{ margin: '0' }"
|
<a-divider :style="{ margin: '0' }">{{ i18n "pages.inbounds.stream.general.response" }}</a-divider>
|
||||||
>{{ i18n "pages.inbounds.stream.general.response" }}</a-divider
|
|
||||||
>
|
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.version" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.version" }}'>
|
||||||
<a-input v-model.trim="inbound.stream.tcp.response.version"></a-input>
|
<a-input v-model.trim="inbound.stream.tcp.response.version"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
@@ -103,37 +60,19 @@
|
|||||||
<a-input v-model.trim="inbound.stream.tcp.response.reason"></a-input>
|
<a-input v-model.trim="inbound.stream.tcp.response.reason"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.responseHeader" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.responseHeader" }}'>
|
||||||
<a-button
|
<a-button icon="plus" size="small"
|
||||||
icon="plus"
|
@click="inbound.stream.tcp.response.addHeader('Content-Type', 'application/octet-stream')"></a-button>
|
||||||
size="small"
|
|
||||||
@click="inbound.stream.tcp.response.addHeader('Content-Type', 'application/octet-stream')"
|
|
||||||
></a-button>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item :wrapper-col="{span:24}">
|
<a-form-item :wrapper-col="{span:24}">
|
||||||
<a-input-group
|
<a-input-group compact v-for="(header, index) in inbound.stream.tcp.response.headers">
|
||||||
compact
|
<a-input :style="{ width: '50%' }" v-model.trim="header.name"
|
||||||
v-for="(header, index) in inbound.stream.tcp.response.headers"
|
placeholder='{{ i18n "pages.inbounds.stream.general.name" }}'>
|
||||||
>
|
<template slot="addonBefore" :style="{ margin: '0' }">[[ index+1 ]]</template>
|
||||||
<a-input
|
|
||||||
:style="{ width: '50%' }"
|
|
||||||
v-model.trim="header.name"
|
|
||||||
placeholder='{{ i18n "pages.inbounds.stream.general.name" }}'
|
|
||||||
>
|
|
||||||
<template slot="addonBefore" :style="{ margin: '0' }"
|
|
||||||
>[[ index+1 ]]</template
|
|
||||||
>
|
|
||||||
</a-input>
|
</a-input>
|
||||||
<a-input
|
<a-input :style="{ width: '50%' }" v-model.trim="header.value"
|
||||||
:style="{ width: '50%' }"
|
placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'>
|
||||||
v-model.trim="header.value"
|
|
||||||
placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'
|
|
||||||
>
|
|
||||||
<template slot="addonAfter">
|
<template slot="addonAfter">
|
||||||
<a-button
|
<a-button icon="minus" size="small" @click="inbound.stream.tcp.response.removeHeader(index)"></a-button>
|
||||||
icon="minus"
|
|
||||||
size="small"
|
|
||||||
@click="inbound.stream.tcp.response.removeHeader(index)"
|
|
||||||
></a-button>
|
|
||||||
</template>
|
</template>
|
||||||
</a-input>
|
</a-input>
|
||||||
</a-input-group>
|
</a-input-group>
|
||||||
|
|||||||
@@ -1,9 +1,5 @@
|
|||||||
{{define "form/streamWS"}}
|
{{define "form/streamWS"}}
|
||||||
<a-form
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
:colon="false"
|
|
||||||
:label-col="{ md: {span:8} }"
|
|
||||||
:wrapper-col="{ md: {span:14} }"
|
|
||||||
>
|
|
||||||
<a-form-item label="Proxy Protocol">
|
<a-form-item label="Proxy Protocol">
|
||||||
<a-switch v-model="inbound.stream.ws.acceptProxyProtocol"></a-switch>
|
<a-switch v-model="inbound.stream.ws.acceptProxyProtocol"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
@@ -14,40 +10,20 @@
|
|||||||
<a-input v-model.trim="inbound.stream.ws.path"></a-input>
|
<a-input v-model.trim="inbound.stream.ws.path"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="Heartbeat Period">
|
<a-form-item label="Heartbeat Period">
|
||||||
<a-input-number
|
<a-input-number v-model.number="inbound.stream.ws.heartbeatPeriod" :min="0"></a-input-number>
|
||||||
v-model.number="inbound.stream.ws.heartbeatPeriod"
|
|
||||||
:min="0"
|
|
||||||
></a-input-number>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestHeader" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestHeader" }}'>
|
||||||
<a-button
|
<a-button icon="plus" size="small" @click="inbound.stream.ws.addHeader('', '')"></a-button>
|
||||||
icon="plus"
|
|
||||||
size="small"
|
|
||||||
@click="inbound.stream.ws.addHeader('', '')"
|
|
||||||
></a-button>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item :wrapper-col="{span:24}">
|
<a-form-item :wrapper-col="{span:24}">
|
||||||
<a-input-group compact v-for="(header, index) in inbound.stream.ws.headers">
|
<a-input-group compact v-for="(header, index) in inbound.stream.ws.headers">
|
||||||
<a-input
|
<a-input :style="{ width: '50%' }" v-model.trim="header.name"
|
||||||
:style="{ width: '50%' }"
|
placeholder='{{ i18n "pages.inbounds.stream.general.name"}}'>
|
||||||
v-model.trim="header.name"
|
<template slot="addonBefore" :style="{ margin: '0' }">[[ index+1 ]]</template>
|
||||||
placeholder='{{ i18n "pages.inbounds.stream.general.name"}}'
|
|
||||||
>
|
|
||||||
<template slot="addonBefore" :style="{ margin: '0' }"
|
|
||||||
>[[ index+1 ]]</template
|
|
||||||
>
|
|
||||||
</a-input>
|
</a-input>
|
||||||
<a-input
|
<a-input :style="{ width: '50%' }" v-model.trim="header.value"
|
||||||
:style="{ width: '50%' }"
|
placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'>
|
||||||
v-model.trim="header.value"
|
<a-button icon="minus" slot="addonAfter" size="small" @click="inbound.stream.ws.removeHeader(index)"></a-button>
|
||||||
placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'
|
|
||||||
>
|
|
||||||
<a-button
|
|
||||||
icon="minus"
|
|
||||||
slot="addonAfter"
|
|
||||||
size="small"
|
|
||||||
@click="inbound.stream.ws.removeHeader(index)"
|
|
||||||
></a-button>
|
|
||||||
</a-input>
|
</a-input>
|
||||||
</a-input-group>
|
</a-input-group>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|||||||
@@ -1,9 +1,5 @@
|
|||||||
{{define "form/streamXHTTP"}}
|
{{define "form/streamXHTTP"}}
|
||||||
<a-form
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
:colon="false"
|
|
||||||
:label-col="{ md: {span:8} }"
|
|
||||||
:wrapper-col="{ md: {span:14} }"
|
|
||||||
>
|
|
||||||
<a-form-item label='{{ i18n "host" }}'>
|
<a-form-item label='{{ i18n "host" }}'>
|
||||||
<a-input v-model.trim="inbound.stream.xhttp.host"></a-input>
|
<a-input v-model.trim="inbound.stream.xhttp.host"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
@@ -11,69 +7,34 @@
|
|||||||
<a-input v-model.trim="inbound.stream.xhttp.path"></a-input>
|
<a-input v-model.trim="inbound.stream.xhttp.path"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestHeader" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestHeader" }}'>
|
||||||
<a-button
|
<a-button icon="plus" size="small" @click="inbound.stream.xhttp.addHeader('', '')"></a-button>
|
||||||
icon="plus"
|
|
||||||
size="small"
|
|
||||||
@click="inbound.stream.xhttp.addHeader('', '')"
|
|
||||||
></a-button>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item :wrapper-col="{span:24}">
|
<a-form-item :wrapper-col="{span:24}">
|
||||||
<a-input-group
|
<a-input-group compact v-for="(header, index) in inbound.stream.xhttp.headers">
|
||||||
compact
|
<a-input :style="{ width: '50%' }" v-model.trim="header.name"
|
||||||
v-for="(header, index) in inbound.stream.xhttp.headers"
|
placeholder='{{ i18n "pages.inbounds.stream.general.name"}}'>
|
||||||
>
|
<template slot="addonBefore" :style="{ margin: '0' }">[[ index+1 ]]</template>
|
||||||
<a-input
|
|
||||||
:style="{ width: '50%' }"
|
|
||||||
v-model.trim="header.name"
|
|
||||||
placeholder='{{ i18n "pages.inbounds.stream.general.name"}}'
|
|
||||||
>
|
|
||||||
<template slot="addonBefore" :style="{ margin: '0' }"
|
|
||||||
>[[ index+1 ]]</template
|
|
||||||
>
|
|
||||||
</a-input>
|
</a-input>
|
||||||
<a-input
|
<a-input :style="{ width: '50%' }" v-model.trim="header.value"
|
||||||
:style="{ width: '50%' }"
|
placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'>
|
||||||
v-model.trim="header.value"
|
<a-button icon="minus" slot="addonAfter" size="small"
|
||||||
placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'
|
@click="inbound.stream.xhttp.removeHeader(index)"></a-button>
|
||||||
>
|
|
||||||
<a-button
|
|
||||||
icon="minus"
|
|
||||||
slot="addonAfter"
|
|
||||||
size="small"
|
|
||||||
@click="inbound.stream.xhttp.removeHeader(index)"
|
|
||||||
></a-button>
|
|
||||||
</a-input>
|
</a-input>
|
||||||
</a-input-group>
|
</a-input-group>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="Mode">
|
<a-form-item label="Mode">
|
||||||
<a-select
|
<a-select v-model="inbound.stream.xhttp.mode" :style="{ width: '50%' }"
|
||||||
v-model="inbound.stream.xhttp.mode"
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
:style="{ width: '50%' }"
|
<a-select-option v-for="key in MODE_OPTION" :value="key">[[ key ]]</a-select-option>
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme"
|
|
||||||
>
|
|
||||||
<a-select-option v-for="key in MODE_OPTION" :value="key"
|
|
||||||
>[[ key ]]</a-select-option
|
|
||||||
>
|
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item
|
<a-form-item label="Max Buffered Upload" v-if="inbound.stream.xhttp.mode === 'packet-up'">
|
||||||
label="Max Buffered Upload"
|
<a-input-number v-model.number="inbound.stream.xhttp.scMaxBufferedPosts"></a-input-number>
|
||||||
v-if="inbound.stream.xhttp.mode === 'packet-up'"
|
|
||||||
>
|
|
||||||
<a-input-number
|
|
||||||
v-model.number="inbound.stream.xhttp.scMaxBufferedPosts"
|
|
||||||
></a-input-number>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item
|
<a-form-item label="Max Upload Size (Byte)" v-if="inbound.stream.xhttp.mode === 'packet-up'">
|
||||||
label="Max Upload Size (Byte)"
|
|
||||||
v-if="inbound.stream.xhttp.mode === 'packet-up'"
|
|
||||||
>
|
|
||||||
<a-input v-model.trim="inbound.stream.xhttp.scMaxEachPostBytes"></a-input>
|
<a-input v-model.trim="inbound.stream.xhttp.scMaxEachPostBytes"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item
|
<a-form-item label="Stream-Up Server" v-if="inbound.stream.xhttp.mode === 'stream-up'">
|
||||||
label="Stream-Up Server"
|
|
||||||
v-if="inbound.stream.xhttp.mode === 'stream-up'"
|
|
||||||
>
|
|
||||||
<a-input v-model.trim="inbound.stream.xhttp.scStreamUpServerSecs"></a-input>
|
<a-input v-model.trim="inbound.stream.xhttp.scStreamUpServerSecs"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="Padding Bytes">
|
<a-form-item label="Padding Bytes">
|
||||||
@@ -84,22 +45,13 @@
|
|||||||
</a-form-item>
|
</a-form-item>
|
||||||
<template v-if="inbound.stream.xhttp.xPaddingObfsMode">
|
<template v-if="inbound.stream.xhttp.xPaddingObfsMode">
|
||||||
<a-form-item label="Padding Key">
|
<a-form-item label="Padding Key">
|
||||||
<a-input
|
<a-input v-model.trim="inbound.stream.xhttp.xPaddingKey" placeholder="x_padding"></a-input>
|
||||||
v-model.trim="inbound.stream.xhttp.xPaddingKey"
|
|
||||||
placeholder="x_padding"
|
|
||||||
></a-input>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="Padding Header">
|
<a-form-item label="Padding Header">
|
||||||
<a-input
|
<a-input v-model.trim="inbound.stream.xhttp.xPaddingHeader" placeholder="X-Padding"></a-input>
|
||||||
v-model.trim="inbound.stream.xhttp.xPaddingHeader"
|
|
||||||
placeholder="X-Padding"
|
|
||||||
></a-input>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="Padding Placement">
|
<a-form-item label="Padding Placement">
|
||||||
<a-select
|
<a-select v-model="inbound.stream.xhttp.xPaddingPlacement" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
v-model="inbound.stream.xhttp.xPaddingPlacement"
|
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme"
|
|
||||||
>
|
|
||||||
<a-select-option value>Default (queryInHeader)</a-select-option>
|
<a-select-option value>Default (queryInHeader)</a-select-option>
|
||||||
<a-select-option value="queryInHeader">queryInHeader</a-select-option>
|
<a-select-option value="queryInHeader">queryInHeader</a-select-option>
|
||||||
<a-select-option value="header">header</a-select-option>
|
<a-select-option value="header">header</a-select-option>
|
||||||
@@ -108,10 +60,7 @@
|
|||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="Padding Method">
|
<a-form-item label="Padding Method">
|
||||||
<a-select
|
<a-select v-model="inbound.stream.xhttp.xPaddingMethod" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
v-model="inbound.stream.xhttp.xPaddingMethod"
|
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme"
|
|
||||||
>
|
|
||||||
<a-select-option value>Default (repeat-x)</a-select-option>
|
<a-select-option value>Default (repeat-x)</a-select-option>
|
||||||
<a-select-option value="repeat-x">repeat-x</a-select-option>
|
<a-select-option value="repeat-x">repeat-x</a-select-option>
|
||||||
<a-select-option value="tokenish">tokenish</a-select-option>
|
<a-select-option value="tokenish">tokenish</a-select-option>
|
||||||
@@ -119,10 +68,7 @@
|
|||||||
</a-form-item>
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
<a-form-item label="Uplink HTTP Method">
|
<a-form-item label="Uplink HTTP Method">
|
||||||
<a-select
|
<a-select v-model="inbound.stream.xhttp.uplinkHTTPMethod" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
v-model="inbound.stream.xhttp.uplinkHTTPMethod"
|
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme"
|
|
||||||
>
|
|
||||||
<a-select-option value>Default (POST)</a-select-option>
|
<a-select-option value>Default (POST)</a-select-option>
|
||||||
<a-select-option value="POST">POST</a-select-option>
|
<a-select-option value="POST">POST</a-select-option>
|
||||||
<a-select-option value="PUT">PUT</a-select-option>
|
<a-select-option value="PUT">PUT</a-select-option>
|
||||||
@@ -130,10 +76,7 @@
|
|||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="Session Placement">
|
<a-form-item label="Session Placement">
|
||||||
<a-select
|
<a-select v-model="inbound.stream.xhttp.sessionPlacement" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
v-model="inbound.stream.xhttp.sessionPlacement"
|
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme"
|
|
||||||
>
|
|
||||||
<a-select-option value>Default (path)</a-select-option>
|
<a-select-option value>Default (path)</a-select-option>
|
||||||
<a-select-option value="path">path</a-select-option>
|
<a-select-option value="path">path</a-select-option>
|
||||||
<a-select-option value="header">header</a-select-option>
|
<a-select-option value="header">header</a-select-option>
|
||||||
@@ -141,20 +84,12 @@
|
|||||||
<a-select-option value="query">query</a-select-option>
|
<a-select-option value="query">query</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item
|
<a-form-item label="Session Key"
|
||||||
label="Session Key"
|
v-if="inbound.stream.xhttp.sessionPlacement && inbound.stream.xhttp.sessionPlacement !== 'path'">
|
||||||
v-if="inbound.stream.xhttp.sessionPlacement && inbound.stream.xhttp.sessionPlacement !== 'path'"
|
<a-input v-model.trim="inbound.stream.xhttp.sessionKey" placeholder="x_session"></a-input>
|
||||||
>
|
|
||||||
<a-input
|
|
||||||
v-model.trim="inbound.stream.xhttp.sessionKey"
|
|
||||||
placeholder="x_session"
|
|
||||||
></a-input>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="Sequence Placement">
|
<a-form-item label="Sequence Placement">
|
||||||
<a-select
|
<a-select v-model="inbound.stream.xhttp.seqPlacement" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
v-model="inbound.stream.xhttp.seqPlacement"
|
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme"
|
|
||||||
>
|
|
||||||
<a-select-option value>Default (path)</a-select-option>
|
<a-select-option value>Default (path)</a-select-option>
|
||||||
<a-select-option value="path">path</a-select-option>
|
<a-select-option value="path">path</a-select-option>
|
||||||
<a-select-option value="header">header</a-select-option>
|
<a-select-option value="header">header</a-select-option>
|
||||||
@@ -162,23 +97,12 @@
|
|||||||
<a-select-option value="query">query</a-select-option>
|
<a-select-option value="query">query</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item
|
<a-form-item label="Sequence Key"
|
||||||
label="Sequence Key"
|
v-if="inbound.stream.xhttp.seqPlacement && inbound.stream.xhttp.seqPlacement !== 'path'">
|
||||||
v-if="inbound.stream.xhttp.seqPlacement && inbound.stream.xhttp.seqPlacement !== 'path'"
|
<a-input v-model.trim="inbound.stream.xhttp.seqKey" placeholder="x_seq"></a-input>
|
||||||
>
|
|
||||||
<a-input
|
|
||||||
v-model.trim="inbound.stream.xhttp.seqKey"
|
|
||||||
placeholder="x_seq"
|
|
||||||
></a-input>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item
|
<a-form-item label="Uplink Data Placement" v-if="inbound.stream.xhttp.mode === 'packet-up'">
|
||||||
label="Uplink Data Placement"
|
<a-select v-model="inbound.stream.xhttp.uplinkDataPlacement" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
v-if="inbound.stream.xhttp.mode === 'packet-up'"
|
|
||||||
>
|
|
||||||
<a-select
|
|
||||||
v-model="inbound.stream.xhttp.uplinkDataPlacement"
|
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme"
|
|
||||||
>
|
|
||||||
<a-select-option value>Default (body)</a-select-option>
|
<a-select-option value>Default (body)</a-select-option>
|
||||||
<a-select-option value="body">body</a-select-option>
|
<a-select-option value="body">body</a-select-option>
|
||||||
<a-select-option value="header">header</a-select-option>
|
<a-select-option value="header">header</a-select-option>
|
||||||
@@ -186,24 +110,14 @@
|
|||||||
<a-select-option value="query">query</a-select-option>
|
<a-select-option value="query">query</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item
|
<a-form-item label="Uplink Data Key"
|
||||||
label="Uplink Data Key"
|
v-if="inbound.stream.xhttp.mode === 'packet-up' && inbound.stream.xhttp.uplinkDataPlacement && inbound.stream.xhttp.uplinkDataPlacement !== 'body'">
|
||||||
v-if="inbound.stream.xhttp.mode === 'packet-up' && inbound.stream.xhttp.uplinkDataPlacement && inbound.stream.xhttp.uplinkDataPlacement !== 'body'"
|
<a-input v-model.trim="inbound.stream.xhttp.uplinkDataKey" placeholder="x_data"></a-input>
|
||||||
>
|
|
||||||
<a-input
|
|
||||||
v-model.trim="inbound.stream.xhttp.uplinkDataKey"
|
|
||||||
placeholder="x_data"
|
|
||||||
></a-input>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item
|
<a-form-item label="Uplink Chunk Size"
|
||||||
label="Uplink Chunk Size"
|
v-if="inbound.stream.xhttp.mode === 'packet-up' && inbound.stream.xhttp.uplinkDataPlacement && inbound.stream.xhttp.uplinkDataPlacement !== 'body'">
|
||||||
v-if="inbound.stream.xhttp.mode === 'packet-up' && inbound.stream.xhttp.uplinkDataPlacement && inbound.stream.xhttp.uplinkDataPlacement !== 'body'"
|
<a-input-number v-model.number="inbound.stream.xhttp.uplinkChunkSize" :min="0"
|
||||||
>
|
placeholder="0 (unlimited)"></a-input-number>
|
||||||
<a-input-number
|
|
||||||
v-model.number="inbound.stream.xhttp.uplinkChunkSize"
|
|
||||||
:min="0"
|
|
||||||
placeholder="0 (unlimited)"
|
|
||||||
></a-input-number>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="No SSE Header">
|
<a-form-item label="No SSE Header">
|
||||||
<a-switch v-model="inbound.stream.xhttp.noSSEHeader"></a-switch>
|
<a-switch v-model="inbound.stream.xhttp.noSSEHeader"></a-switch>
|
||||||
|
|||||||
@@ -1,14 +1,12 @@
|
|||||||
{{define "form/tlsSettings"}}
|
{{define "form/tlsSettings"}}
|
||||||
<!-- tls enable -->
|
<!-- tls enable -->
|
||||||
<a-form v-if="inbound.canEnableTls()" :colon="false"
|
<a-form v-if="inbound.canEnableTls()" :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
:label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
|
||||||
<template v-if="inbound.protocol !== Protocols.HYSTERIA">
|
<template v-if="inbound.protocol !== Protocols.HYSTERIA">
|
||||||
<a-divider :style="{ margin: '3px 0' }"></a-divider>
|
<a-divider :style="{ margin: '3px 0' }"></a-divider>
|
||||||
<a-form-item label='{{ i18n "security" }}'>
|
<a-form-item label='{{ i18n "security" }}'>
|
||||||
<a-radio-group v-model="inbound.stream.security" button-style="solid">
|
<a-radio-group v-model="inbound.stream.security" button-style="solid">
|
||||||
<a-radio-button value="none">{{ i18n "none" }}</a-radio-button>
|
<a-radio-button value="none">{{ i18n "none" }}</a-radio-button>
|
||||||
<a-radio-button v-if="inbound.canEnableReality()"
|
<a-radio-button v-if="inbound.canEnableReality()" value="reality">Reality</a-radio-button>
|
||||||
value="reality">Reality</a-radio-button>
|
|
||||||
<a-radio-button value="tls">TLS</a-radio-button>
|
<a-radio-button value="tls">TLS</a-radio-button>
|
||||||
</a-radio-group>
|
</a-radio-group>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
@@ -21,8 +19,7 @@
|
|||||||
<a-input v-model.trim="inbound.stream.tls.sni"></a-input>
|
<a-input v-model.trim="inbound.stream.tls.sni"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="Cipher Suites">
|
<a-form-item label="Cipher Suites">
|
||||||
<a-select v-model="inbound.stream.tls.cipherSuites"
|
<a-select v-model="inbound.stream.tls.cipherSuites" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
|
||||||
<a-select-option value>Auto</a-select-option>
|
<a-select-option value>Auto</a-select-option>
|
||||||
<a-select-option v-for="key,value in TLS_CIPHER_OPTION" :value="key">[[
|
<a-select-option v-for="key,value in TLS_CIPHER_OPTION" :value="key">[[
|
||||||
value ]]</a-select-option>
|
value ]]</a-select-option>
|
||||||
@@ -30,14 +27,12 @@
|
|||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="Min/Max Version">
|
<a-form-item label="Min/Max Version">
|
||||||
<a-input-group compact>
|
<a-input-group compact>
|
||||||
<a-select v-model="inbound.stream.tls.minVersion"
|
<a-select v-model="inbound.stream.tls.minVersion" :style="{ width: '50%' }"
|
||||||
:style="{ width: '50%' }"
|
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key
|
<a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key
|
||||||
]]</a-select-option>
|
]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
<a-select v-model="inbound.stream.tls.maxVersion"
|
<a-select v-model="inbound.stream.tls.maxVersion" :style="{ width: '50%' }"
|
||||||
:style="{ width: '50%' }"
|
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key
|
<a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key
|
||||||
]]</a-select-option>
|
]]</a-select-option>
|
||||||
@@ -45,8 +40,7 @@
|
|||||||
</a-input-group>
|
</a-input-group>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="uTLS">
|
<a-form-item label="uTLS">
|
||||||
<a-select v-model="inbound.stream.tls.settings.fingerprint"
|
<a-select v-model="inbound.stream.tls.settings.fingerprint" :style="{ width: '100%' }"
|
||||||
:style="{ width: '100%' }"
|
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option value>None</a-select-option>
|
<a-select-option value>None</a-select-option>
|
||||||
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key
|
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key
|
||||||
@@ -54,9 +48,7 @@
|
|||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="ALPN">
|
<a-form-item label="ALPN">
|
||||||
<a-select mode="multiple"
|
<a-select mode="multiple" :dropdown-class-name="themeSwitcher.currentTheme" v-model="inbound.stream.tls.alpn">
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme"
|
|
||||||
v-model="inbound.stream.tls.alpn">
|
|
||||||
<a-select-option v-for="alpn in ALPN_OPTION" :value="alpn">[[ alpn
|
<a-select-option v-for="alpn in ALPN_OPTION" :value="alpn">[[ alpn
|
||||||
]]</a-select-option>
|
]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
@@ -75,8 +67,7 @@
|
|||||||
<a-form-item label='{{ i18n "certificate" }}'>
|
<a-form-item label='{{ i18n "certificate" }}'>
|
||||||
<a-radio-group v-model="cert.useFile" button-style="solid"
|
<a-radio-group v-model="cert.useFile" button-style="solid"
|
||||||
:style="{ display: 'inline-flex', whiteSpace: 'nowrap', maxWidth: '100%' }">
|
:style="{ display: 'inline-flex', whiteSpace: 'nowrap', maxWidth: '100%' }">
|
||||||
<a-radio-button :value="true"
|
<a-radio-button :value="true" :style="{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }">{{
|
||||||
:style="{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }">{{
|
|
||||||
i18n "pages.inbounds.certificatePath" }}</a-radio-button>
|
i18n "pages.inbounds.certificatePath" }}</a-radio-button>
|
||||||
<a-radio-button :value="false"
|
<a-radio-button :value="false"
|
||||||
:style="{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }">{{
|
:style="{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }">{{
|
||||||
@@ -87,8 +78,7 @@
|
|||||||
<a-space>
|
<a-space>
|
||||||
<a-button icon="plus" v-if="index === 0" type="primary" size="small"
|
<a-button icon="plus" v-if="index === 0" type="primary" size="small"
|
||||||
@click="inbound.stream.tls.addCert()"></a-button>
|
@click="inbound.stream.tls.addCert()"></a-button>
|
||||||
<a-button icon="minus" v-if="inbound.stream.tls.certs.length>1"
|
<a-button icon="minus" v-if="inbound.stream.tls.certs.length>1" type="primary" size="small"
|
||||||
type="primary" size="small"
|
|
||||||
@click="inbound.stream.tls.removeCert(index)"></a-button>
|
@click="inbound.stream.tls.removeCert(index)"></a-button>
|
||||||
</a-space>
|
</a-space>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
@@ -100,8 +90,7 @@
|
|||||||
<a-input v-model.trim="cert.keyFile"></a-input>
|
<a-input v-model.trim="cert.keyFile"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label=" ">
|
<a-form-item label=" ">
|
||||||
<a-button type="primary" icon="import"
|
<a-button type="primary" icon="import" @click="setDefaultCertData(index)">
|
||||||
@click="setDefaultCertData(index)">
|
|
||||||
{{ i18n "pages.inbounds.setDefaultCert" }}</a-button>
|
{{ i18n "pages.inbounds.setDefaultCert" }}</a-button>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
@@ -117,8 +106,7 @@
|
|||||||
<a-switch v-model="cert.oneTimeLoading"></a-switch>
|
<a-switch v-model="cert.oneTimeLoading"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='Usage Option'>
|
<a-form-item label='Usage Option'>
|
||||||
<a-select v-model="cert.usage" :style="{ width: '50%' }"
|
<a-select v-model="cert.usage" :style="{ width: '50%' }" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
|
||||||
<a-select-option v-for="key in USAGE_OPTION" :value="key">[[ key
|
<a-select-option v-for="key in USAGE_OPTION" :value="key">[[ key
|
||||||
]]</a-select-option>
|
]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
@@ -133,13 +121,6 @@
|
|||||||
<a-form-item label='ECH config'>
|
<a-form-item label='ECH config'>
|
||||||
<a-input v-model="inbound.stream.tls.settings.echConfigList"></a-input>
|
<a-input v-model="inbound.stream.tls.settings.echConfigList"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='ECH force query'>
|
|
||||||
<a-select v-model="inbound.stream.tls.echForceQuery"
|
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
|
||||||
<a-select-option v-for="key in ['none', 'half', 'full']" :value="key">[[
|
|
||||||
key ]]</a-select-option>
|
|
||||||
</a-select>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label=" ">
|
<a-form-item label=" ">
|
||||||
<a-space>
|
<a-space>
|
||||||
<a-button type="primary" icon="import" @click="getNewEchCert">Get New
|
<a-button type="primary" icon="import" @click="getNewEchCert">Get New
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -10,6 +10,7 @@
|
|||||||
padding: 2px 6px;
|
padding: 2px 6px;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
html[data-theme="ultra-dark"] body.dark .custom-geo-section code.custom-geo-ext-code {
|
html[data-theme="ultra-dark"] body.dark .custom-geo-section code.custom-geo-ext-code {
|
||||||
color: var(--dark-color-text-primary, rgba(255, 255, 255, 0.88));
|
color: var(--dark-color-text-primary, rgba(255, 255, 255, 0.88));
|
||||||
background: var(--dark-color-surface-700, #111929);
|
background: var(--dark-color-surface-700, #111929);
|
||||||
@@ -119,7 +120,8 @@
|
|||||||
</a-row>
|
</a-row>
|
||||||
</span>
|
</span>
|
||||||
<template slot="content">
|
<template slot="content">
|
||||||
<span class="max-w-400" v-for="line in (status.xray.errorMsg || '').split('\n')">[[ line ]]</span>
|
<span class="max-w-400" v-for="line in (status.xray.errorMsg || '').split('\n')">[[ line
|
||||||
|
]]</span>
|
||||||
</template>
|
</template>
|
||||||
<a-badge :text="status.xray.stateMsg" :color="status.xray.color"
|
<a-badge :text="status.xray.stateMsg" :color="status.xray.color"
|
||||||
:class="status.xray.color === 'red' ? 'xray-error-animation' : ''" />
|
:class="status.xray.color === 'red' ? 'xray-error-animation' : ''" />
|
||||||
@@ -170,10 +172,11 @@
|
|||||||
<a-col :sm="24" :lg="12">
|
<a-col :sm="24" :lg="12">
|
||||||
<a-card title='3X-UI' hoverable>
|
<a-card title='3X-UI' hoverable>
|
||||||
<template v-if="panelUpdateModal.info.updateAvailable" #extra>
|
<template v-if="panelUpdateModal.info.updateAvailable" #extra>
|
||||||
<a-tooltip :overlay-class-name="themeSwitcher.currentTheme" :title='`{{ i18n "pages.index.updatePanel" }}: ${panelUpdateModal.info.latestVersion}`'>
|
<a-tooltip :overlay-class-name="themeSwitcher.currentTheme"
|
||||||
|
:title='`{{ i18n "pages.index.updatePanel" }}: ${panelUpdateModal.info.latestVersion}`'>
|
||||||
<a-tag color="orange" style="cursor:pointer;margin:0" @click="openPanelUpdate">
|
<a-tag color="orange" style="cursor:pointer;margin:0" @click="openPanelUpdate">
|
||||||
<a-icon type="cloud-download"></a-icon>[[ panelUpdateModal.info.latestVersion ]]
|
<a-icon type="cloud-download"></a-icon>[[ panelUpdateModal.info.latestVersion ]]
|
||||||
<span v-if="!isMobile">{{ i18n "pages.index.updatePanel" }}</span>
|
<span v-if="!isMobile">{{ i18n "pages.index.updatePanel" }}</span>
|
||||||
</a-tag>
|
</a-tag>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</template>
|
</template>
|
||||||
@@ -327,8 +330,7 @@
|
|||||||
</a-layout>
|
</a-layout>
|
||||||
<a-modal id="panel-update-modal" v-model="panelUpdateModal.visible" title='{{ i18n "pages.index.updatePanel" }}'
|
<a-modal id="panel-update-modal" v-model="panelUpdateModal.visible" title='{{ i18n "pages.index.updatePanel" }}'
|
||||||
:closable="true" @ok="() => panelUpdateModal.visible = false" :class="themeSwitcher.currentTheme" footer="">
|
:closable="true" @ok="() => panelUpdateModal.visible = false" :class="themeSwitcher.currentTheme" footer="">
|
||||||
<a-alert type="warning" class="mb-12 w-100" message='{{ i18n "pages.index.panelUpdateDesc" }}'
|
<a-alert type="warning" class="mb-12 w-100" message='{{ i18n "pages.index.panelUpdateDesc" }}' show-icon></a-alert>
|
||||||
show-icon></a-alert>
|
|
||||||
<a-list class="ant-version-list w-100" bordered>
|
<a-list class="ant-version-list w-100" bordered>
|
||||||
<a-list-item class="ant-version-list-item">
|
<a-list-item class="ant-version-list-item">
|
||||||
<span>{{ i18n "pages.index.currentPanelVersion" }}</span>
|
<span>{{ i18n "pages.index.currentPanelVersion" }}</span>
|
||||||
@@ -379,57 +381,62 @@
|
|||||||
</a-collapse-panel>
|
</a-collapse-panel>
|
||||||
<a-collapse-panel key="3" header='{{ i18n "pages.index.customGeoTitle" }}'>
|
<a-collapse-panel key="3" header='{{ i18n "pages.index.customGeoTitle" }}'>
|
||||||
<div class="custom-geo-section">
|
<div class="custom-geo-section">
|
||||||
<a-alert type="info" show-icon class="mb-10"
|
<a-alert type="info" show-icon class="mb-10"
|
||||||
message='{{ i18n "pages.index.customGeoRoutingHint" }}'></a-alert>
|
message='{{ i18n "pages.index.customGeoRoutingHint" }}'></a-alert>
|
||||||
<div class="mb-10">
|
<div class="mb-10">
|
||||||
<a-button type="primary" icon="plus" @click="openCustomGeoModal(null)" :loading="customGeoLoading">
|
<a-button type="primary" icon="plus" @click="openCustomGeoModal(null)" :loading="customGeoLoading">
|
||||||
{{ i18n "pages.index.customGeoAdd" }}
|
{{ i18n "pages.index.customGeoAdd" }}
|
||||||
</a-button>
|
</a-button>
|
||||||
<a-button class="ml-8" icon="reload" @click="updateAllCustomGeo" :loading="customGeoUpdatingAll">{{ i18n
|
<a-button class="ml-8" icon="reload" @click="updateAllCustomGeo" :loading="customGeoUpdatingAll">{{ i18n
|
||||||
"pages.index.geofilesUpdateAll" }}</a-button>
|
"pages.index.geofilesUpdateAll" }}</a-button>
|
||||||
</div>
|
</div>
|
||||||
<a-table :columns="customGeoColumns" :data-source="customGeoList" :pagination="false" :row-key="r => r.id"
|
<a-table :columns="customGeoColumns" :data-source="customGeoList" :pagination="false" :row-key="r => r.id"
|
||||||
:loading="customGeoLoading" size="small" :scroll="{ x: 520 }">
|
:loading="customGeoLoading" size="small" :scroll="{ x: 520 }">
|
||||||
<template slot="extDat" slot-scope="text, record">
|
<template slot="extDat" slot-scope="text, record">
|
||||||
<code class="custom-geo-ext-code">[[ customGeoExtDisplay(record) ]]</code>
|
<code class="custom-geo-ext-code">[[ customGeoExtDisplay(record) ]]</code>
|
||||||
</template>
|
</template>
|
||||||
<template slot="lastUpdatedAt" slot-scope="text, record">
|
<template slot="lastUpdatedAt" slot-scope="text, record">
|
||||||
<span v-if="record.lastUpdatedAt">[[ customGeoFormatTime(record.lastUpdatedAt) ]]</span>
|
<span v-if="record.lastUpdatedAt">[[ customGeoFormatTime(record.lastUpdatedAt) ]]</span>
|
||||||
<span v-else>—</span>
|
<span v-else>—</span>
|
||||||
</template>
|
</template>
|
||||||
<template slot="action" slot-scope="text, record">
|
<template slot="action" slot-scope="text, record">
|
||||||
<a-space size="small">
|
<a-space size="small">
|
||||||
<a-tooltip :overlay-class-name="themeSwitcher.currentTheme">
|
<a-tooltip :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
<template slot="title">{{ i18n "pages.index.customGeoEdit" }}</template>
|
<template slot="title">{{ i18n "pages.index.customGeoEdit" }}</template>
|
||||||
<a-button type="link" size="small" icon="edit" @click="openCustomGeoModal(record)"></a-button>
|
<a-button type="link" size="small" icon="edit" @click="openCustomGeoModal(record)"></a-button>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-tooltip :overlay-class-name="themeSwitcher.currentTheme">
|
<a-tooltip :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
<template slot="title">{{ i18n "pages.index.customGeoDownload" }}</template>
|
<template slot="title">{{ i18n "pages.index.customGeoDownload" }}</template>
|
||||||
<a-button type="link" size="small" icon="reload" @click="downloadCustomGeo(record.id)" :loading="customGeoActionId === record.id"></a-button>
|
<a-button type="link" size="small" icon="reload" @click="downloadCustomGeo(record.id)"
|
||||||
</a-tooltip>
|
:loading="customGeoActionId === record.id"></a-button>
|
||||||
<a-tooltip :overlay-class-name="themeSwitcher.currentTheme">
|
</a-tooltip>
|
||||||
<template slot="title">{{ i18n "pages.index.customGeoDelete" }}</template>
|
<a-tooltip :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
<a-button type="link" size="small" icon="delete" @click="confirmDeleteCustomGeo(record)"></a-button>
|
<template slot="title">{{ i18n "pages.index.customGeoDelete" }}</template>
|
||||||
</a-tooltip>
|
<a-button type="link" size="small" icon="delete" @click="confirmDeleteCustomGeo(record)"></a-button>
|
||||||
</a-space>
|
</a-tooltip>
|
||||||
</template>
|
</a-space>
|
||||||
</a-table>
|
</template>
|
||||||
|
</a-table>
|
||||||
</div>
|
</div>
|
||||||
</a-collapse-panel>
|
</a-collapse-panel>
|
||||||
</a-collapse>
|
</a-collapse>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
<a-modal v-model="customGeoModal.visible" :title="customGeoModal.editId ? '{{ i18n "pages.index.customGeoModalEdit" }}' : '{{ i18n "pages.index.customGeoModalAdd" }}'"
|
<a-modal v-model="customGeoModal.visible"
|
||||||
:confirm-loading="customGeoModal.saving" @ok="submitCustomGeo" :ok-text="'{{ i18n "pages.index.customGeoModalSave" }}'" :cancel-text="'{{ i18n "close" }}'"
|
:title="customGeoModal.editId ? '{{ i18n "pages.index.customGeoModalEdit" }}' : '{{ i18n "pages.index.customGeoModalAdd" }}'"
|
||||||
|
:confirm-loading="customGeoModal.saving" @ok="submitCustomGeo"
|
||||||
|
:ok-text="'{{ i18n "pages.index.customGeoModalSave" }}'" :cancel-text="'{{ i18n "close" }}'"
|
||||||
:class="themeSwitcher.currentTheme">
|
:class="themeSwitcher.currentTheme">
|
||||||
<a-form layout="vertical">
|
<a-form layout="vertical">
|
||||||
<a-form-item label='{{ i18n "pages.index.customGeoType" }}'>
|
<a-form-item label='{{ i18n "pages.index.customGeoType" }}'>
|
||||||
<a-select v-model="customGeoModal.form.type" :disabled="!!customGeoModal.editId" :dropdown-class-name="themeSwitcher.currentTheme">
|
<a-select v-model="customGeoModal.form.type" :disabled="!!customGeoModal.editId"
|
||||||
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option value="geosite">geosite</a-select-option>
|
<a-select-option value="geosite">geosite</a-select-option>
|
||||||
<a-select-option value="geoip">geoip</a-select-option>
|
<a-select-option value="geoip">geoip</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "pages.index.customGeoAlias" }}'>
|
<a-form-item label='{{ i18n "pages.index.customGeoAlias" }}'>
|
||||||
<a-input v-model.trim="customGeoModal.form.alias" :disabled="!!customGeoModal.editId" placeholder='{{ i18n "pages.index.customGeoAliasPlaceholder" }}'></a-input>
|
<a-input v-model.trim="customGeoModal.form.alias" :disabled="!!customGeoModal.editId"
|
||||||
|
placeholder='{{ i18n "pages.index.customGeoAliasPlaceholder" }}'></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "pages.index.customGeoUrl" }}'>
|
<a-form-item label='{{ i18n "pages.index.customGeoUrl" }}'>
|
||||||
<a-input v-model.trim="customGeoModal.form.url" placeholder="https://"></a-input>
|
<a-input v-model.trim="customGeoModal.form.url" placeholder="https://"></a-input>
|
||||||
@@ -469,7 +476,8 @@
|
|||||||
<a-checkbox v-model="logModal.syslog" @change="openLogs()">SysLog</a-checkbox>
|
<a-checkbox v-model="logModal.syslog" @change="openLogs()">SysLog</a-checkbox>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item style="float: right;">
|
<a-form-item style="float: right;">
|
||||||
<a-button type="primary" icon="download" @click="FileManager.downloadTextFile(logModal.logs?.join('\n'), 'x-ui.log')"></a-button>
|
<a-button type="primary" icon="download"
|
||||||
|
@click="FileManager.downloadTextFile(logModal.logs?.join('\n'), 'x-ui.log')"></a-button>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
<div class="ant-input log-container" v-html="logModal.formattedLogs"></div>
|
<div class="ant-input log-container" v-html="logModal.formattedLogs"></div>
|
||||||
@@ -547,7 +555,8 @@
|
|||||||
<sparkline :data="cpuHistoryLong" :labels="cpuHistoryLabels" :vb-width="840" :height="220"
|
<sparkline :data="cpuHistoryLong" :labels="cpuHistoryLabels" :vb-width="840" :height="220"
|
||||||
:stroke="status.cpu.color" :stroke-width="2.2" :show-grid="true" :show-axes="true" :tick-count-x="5"
|
:stroke="status.cpu.color" :stroke-width="2.2" :show-grid="true" :show-axes="true" :tick-count-x="5"
|
||||||
:max-points="cpuHistoryLong.length" :fill-opacity="0.18" :marker-radius="3.2" :show-tooltip="true" />
|
:max-points="cpuHistoryLong.length" :fill-opacity="0.18" :marker-radius="3.2" :show-tooltip="true" />
|
||||||
<div style="margin-top:4px;font-size:11px;opacity:0.65">Timeframe: [[ cpuHistoryModal.bucket ]] sec per point (total [[ cpuHistoryLong.length ]] points)</div>
|
<div style="margin-top:4px;font-size:11px;opacity:0.65">Timeframe: [[ cpuHistoryModal.bucket ]] sec per point
|
||||||
|
(total [[ cpuHistoryLong.length ]] points)</div>
|
||||||
</div>
|
</div>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
</a-layout>
|
</a-layout>
|
||||||
@@ -560,28 +569,88 @@
|
|||||||
// Tiny Sparkline component using an inline SVG polyline
|
// Tiny Sparkline component using an inline SVG polyline
|
||||||
Vue.component('sparkline', {
|
Vue.component('sparkline', {
|
||||||
props: {
|
props: {
|
||||||
data: { type: Array, required: true },
|
data: {
|
||||||
|
type: Array,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
// viewBox width for drawing space; SVG width will be 100% of container
|
// viewBox width for drawing space; SVG width will be 100% of container
|
||||||
vbWidth: { type: Number, default: 320 },
|
vbWidth: {
|
||||||
height: { type: Number, default: 80 },
|
type: Number,
|
||||||
stroke: { type: String, default: '#008771' },
|
default: 320
|
||||||
strokeWidth: { type: Number, default: 2 },
|
},
|
||||||
maxPoints: { type: Number, default: 120 },
|
height: {
|
||||||
showGrid: { type: Boolean, default: true },
|
type: Number,
|
||||||
gridColor: { type: String, default: 'rgba(0,0,0,0.1)' },
|
default: 80
|
||||||
fillOpacity: { type: Number, default: 0.15 },
|
},
|
||||||
showMarker: { type: Boolean, default: true },
|
stroke: {
|
||||||
markerRadius: { type: Number, default: 2.8 },
|
type: String,
|
||||||
|
default: '#008771'
|
||||||
|
},
|
||||||
|
strokeWidth: {
|
||||||
|
type: Number,
|
||||||
|
default: 2
|
||||||
|
},
|
||||||
|
maxPoints: {
|
||||||
|
type: Number,
|
||||||
|
default: 120
|
||||||
|
},
|
||||||
|
showGrid: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
gridColor: {
|
||||||
|
type: String,
|
||||||
|
default: 'rgba(0,0,0,0.1)'
|
||||||
|
},
|
||||||
|
fillOpacity: {
|
||||||
|
type: Number,
|
||||||
|
default: 0.15
|
||||||
|
},
|
||||||
|
showMarker: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
markerRadius: {
|
||||||
|
type: Number,
|
||||||
|
default: 2.8
|
||||||
|
},
|
||||||
// New opts for axes/labels/tooltip
|
// New opts for axes/labels/tooltip
|
||||||
labels: { type: Array, default: () => [] }, // same length as data for x labels (e.g., timestamps)
|
labels: {
|
||||||
showAxes: { type: Boolean, default: false },
|
type: Array,
|
||||||
yTickStep: { type: Number, default: 25 }, // percent ticks
|
default: () => []
|
||||||
tickCountX: { type: Number, default: 4 },
|
}, // same length as data for x labels (e.g., timestamps)
|
||||||
paddingLeft: { type: Number, default: 32 },
|
showAxes: {
|
||||||
paddingRight: { type: Number, default: 6 },
|
type: Boolean,
|
||||||
paddingTop: { type: Number, default: 6 },
|
default: false
|
||||||
paddingBottom: { type: Number, default: 20 },
|
},
|
||||||
showTooltip: { type: Boolean, default: false },
|
yTickStep: {
|
||||||
|
type: Number,
|
||||||
|
default: 25
|
||||||
|
}, // percent ticks
|
||||||
|
tickCountX: {
|
||||||
|
type: Number,
|
||||||
|
default: 4
|
||||||
|
},
|
||||||
|
paddingLeft: {
|
||||||
|
type: Number,
|
||||||
|
default: 32
|
||||||
|
},
|
||||||
|
paddingRight: {
|
||||||
|
type: Number,
|
||||||
|
default: 6
|
||||||
|
},
|
||||||
|
paddingTop: {
|
||||||
|
type: Number,
|
||||||
|
default: 6
|
||||||
|
},
|
||||||
|
paddingBottom: {
|
||||||
|
type: Number,
|
||||||
|
default: 20
|
||||||
|
},
|
||||||
|
showTooltip: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@@ -644,7 +713,12 @@
|
|||||||
// draw at 25%, 50%, 75%
|
// draw at 25%, 50%, 75%
|
||||||
return [0, 0.25, 0.5, 0.75, 1]
|
return [0, 0.25, 0.5, 0.75, 1]
|
||||||
.map(r => Math.round(this.paddingTop + h * r))
|
.map(r => Math.round(this.paddingTop + h * r))
|
||||||
.map(y => ({ x1: this.paddingLeft, y1: y, x2: this.paddingLeft + w, y2: y }))
|
.map(y => ({
|
||||||
|
x1: this.paddingLeft,
|
||||||
|
y1: y,
|
||||||
|
x2: this.paddingLeft + w,
|
||||||
|
y2: y
|
||||||
|
}))
|
||||||
},
|
},
|
||||||
lastPoint() {
|
lastPoint() {
|
||||||
if (this.pointsArr.length === 0) return null
|
if (this.pointsArr.length === 0) return null
|
||||||
@@ -656,7 +730,10 @@
|
|||||||
const ticks = []
|
const ticks = []
|
||||||
for (let p = 0; p <= 100; p += step) {
|
for (let p = 0; p <= 100; p += step) {
|
||||||
const y = Math.round(this.paddingTop + (this.drawHeight - (p / 100) * this.drawHeight))
|
const y = Math.round(this.paddingTop + (this.drawHeight - (p / 100) * this.drawHeight))
|
||||||
ticks.push({ y, label: `${p}%` })
|
ticks.push({
|
||||||
|
y,
|
||||||
|
label: `${p}%`
|
||||||
|
})
|
||||||
}
|
}
|
||||||
return ticks
|
return ticks
|
||||||
},
|
},
|
||||||
@@ -677,7 +754,10 @@
|
|||||||
positions.forEach(idx => {
|
positions.forEach(idx => {
|
||||||
const label = labels[idx] != null ? String(labels[idx]) : String(idx)
|
const label = labels[idx] != null ? String(labels[idx]) : String(idx)
|
||||||
const x = Math.round(this.paddingLeft + idx * dx)
|
const x = Math.round(this.paddingLeft + idx * dx)
|
||||||
ticks.push({ x, label })
|
ticks.push({
|
||||||
|
x,
|
||||||
|
label
|
||||||
|
})
|
||||||
})
|
})
|
||||||
return ticks
|
return ticks
|
||||||
},
|
},
|
||||||
@@ -778,17 +858,36 @@
|
|||||||
this.disk = new CurTotal(0, 0);
|
this.disk = new CurTotal(0, 0);
|
||||||
this.loads = [0, 0, 0];
|
this.loads = [0, 0, 0];
|
||||||
this.mem = new CurTotal(0, 0);
|
this.mem = new CurTotal(0, 0);
|
||||||
this.netIO = { up: 0, down: 0 };
|
this.netIO = {
|
||||||
this.netTraffic = { sent: 0, recv: 0 };
|
up: 0,
|
||||||
this.publicIP = { ipv4: 0, ipv6: 0 };
|
down: 0
|
||||||
|
};
|
||||||
|
this.netTraffic = {
|
||||||
|
sent: 0,
|
||||||
|
recv: 0
|
||||||
|
};
|
||||||
|
this.publicIP = {
|
||||||
|
ipv4: 0,
|
||||||
|
ipv6: 0
|
||||||
|
};
|
||||||
this.swap = new CurTotal(0, 0);
|
this.swap = new CurTotal(0, 0);
|
||||||
this.tcpCount = 0;
|
this.tcpCount = 0;
|
||||||
this.udpCount = 0;
|
this.udpCount = 0;
|
||||||
this.uptime = 0;
|
this.uptime = 0;
|
||||||
this.appUptime = 0;
|
this.appUptime = 0;
|
||||||
this.appStats = { threads: 0, mem: 0, uptime: 0 };
|
this.appStats = {
|
||||||
|
threads: 0,
|
||||||
|
mem: 0,
|
||||||
|
uptime: 0
|
||||||
|
};
|
||||||
|
|
||||||
this.xray = { state: 'stop', stateMsg: "", errorMsg: "", version: "", color: "" };
|
this.xray = {
|
||||||
|
state: 'stop',
|
||||||
|
stateMsg: "",
|
||||||
|
errorMsg: "",
|
||||||
|
version: "",
|
||||||
|
color: ""
|
||||||
|
};
|
||||||
|
|
||||||
if (data == null) {
|
if (data == null) {
|
||||||
return;
|
return;
|
||||||
@@ -918,20 +1017,20 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
const xraylogModal = {
|
const xraylogModal = {
|
||||||
visible: false,
|
visible: false,
|
||||||
logs: [],
|
logs: [],
|
||||||
rows: 20,
|
rows: 20,
|
||||||
showDirect: true,
|
showDirect: true,
|
||||||
showBlocked: true,
|
showBlocked: true,
|
||||||
showProxy: true,
|
showProxy: true,
|
||||||
loading: false,
|
loading: false,
|
||||||
show(logs) {
|
show(logs) {
|
||||||
this.visible = true;
|
this.visible = true;
|
||||||
this.logs = logs;
|
this.logs = logs;
|
||||||
this.formattedLogs = this.logs?.length > 0 ? this.formatLogs(this.logs) : "No Record...";
|
this.formattedLogs = this.logs?.length > 0 ? this.formatLogs(this.logs) : "No Record...";
|
||||||
},
|
},
|
||||||
formatLogs(logs) {
|
formatLogs(logs) {
|
||||||
let formattedLogs = `
|
let formattedLogs = `
|
||||||
<style>
|
<style>
|
||||||
table {
|
table {
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
@@ -954,21 +1053,20 @@
|
|||||||
</tr>
|
</tr>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
logs.reverse().forEach((log, index) => {
|
logs.reverse().forEach((log, index) => {
|
||||||
let outboundColor = '';
|
let outboundColor = '';
|
||||||
if (log.Event === 1) {
|
if (log.Event === 1) {
|
||||||
outboundColor = ' style="color: #e04141;"'; //red for blocked
|
outboundColor = ' style="color: #e04141;"'; //red for blocked
|
||||||
}
|
} else if (log.Event === 2) {
|
||||||
else if (log.Event === 2) {
|
outboundColor = ' style="color: #3c89e8;"'; //blue for proxies
|
||||||
outboundColor = ' style="color: #3c89e8;"'; //blue for proxies
|
}
|
||||||
}
|
|
||||||
|
|
||||||
let text = ``;
|
let text = ``;
|
||||||
if (log.Email !== "") {
|
if (log.Email !== "") {
|
||||||
text = `<td>${log.Email}</td>`;
|
text = `<td>${log.Email}</td>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
formattedLogs += `
|
formattedLogs += `
|
||||||
<tr ${outboundColor}>
|
<tr ${outboundColor}>
|
||||||
<td><b>${IntlUtil.formatDate(log.DateTime)}</b></td>
|
<td><b>${IntlUtil.formatDate(log.DateTime)}</b></td>
|
||||||
<td>${log.FromAddress}</td>
|
<td>${log.FromAddress}</td>
|
||||||
@@ -978,14 +1076,14 @@
|
|||||||
${text}
|
${text}
|
||||||
</tr>
|
</tr>
|
||||||
`;
|
`;
|
||||||
});
|
});
|
||||||
|
|
||||||
return formattedLogs += "</table>";
|
return formattedLogs += "</table>";
|
||||||
},
|
},
|
||||||
hide() {
|
hide() {
|
||||||
this.visible = false;
|
this.visible = false;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const backupModal = {
|
const backupModal = {
|
||||||
visible: false,
|
visible: false,
|
||||||
show() {
|
show() {
|
||||||
@@ -996,10 +1094,31 @@
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const customGeoColumns = [
|
const customGeoColumns = [{
|
||||||
{ title: '{{ i18n "pages.index.customGeoExtColumn" }}', key: 'extDat', scopedSlots: { customRender: 'extDat' }, ellipsis: true },
|
title: '{{ i18n "pages.index.customGeoExtColumn" }}',
|
||||||
{ title: '{{ i18n "pages.index.customGeoLastUpdated" }}', key: 'lastUpdatedAt', scopedSlots: { customRender: 'lastUpdatedAt' }, width: 160 },
|
key: 'extDat',
|
||||||
{ title: '{{ i18n "pages.index.customGeoActions" }}', key: 'action', scopedSlots: { customRender: 'action' }, width: 120, fixed: 'right' },
|
scopedSlots: {
|
||||||
|
customRender: 'extDat'
|
||||||
|
},
|
||||||
|
ellipsis: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '{{ i18n "pages.index.customGeoLastUpdated" }}',
|
||||||
|
key: 'lastUpdatedAt',
|
||||||
|
scopedSlots: {
|
||||||
|
customRender: 'lastUpdatedAt'
|
||||||
|
},
|
||||||
|
width: 160
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '{{ i18n "pages.index.customGeoActions" }}',
|
||||||
|
key: 'action',
|
||||||
|
scopedSlots: {
|
||||||
|
customRender: 'action'
|
||||||
|
},
|
||||||
|
width: 120,
|
||||||
|
fixed: 'right'
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const app = new Vue({
|
const app = new Vue({
|
||||||
@@ -1016,7 +1135,10 @@
|
|||||||
cpuHistory: [], // small live widget history
|
cpuHistory: [], // small live widget history
|
||||||
cpuHistoryLong: [], // aggregated points from backend
|
cpuHistoryLong: [], // aggregated points from backend
|
||||||
cpuHistoryLabels: [],
|
cpuHistoryLabels: [],
|
||||||
cpuHistoryModal: { visible: false, bucket: 2 },
|
cpuHistoryModal: {
|
||||||
|
visible: false,
|
||||||
|
bucket: 2
|
||||||
|
},
|
||||||
versionModal,
|
versionModal,
|
||||||
panelUpdateModal,
|
panelUpdateModal,
|
||||||
logModal,
|
logModal,
|
||||||
@@ -1092,16 +1214,16 @@
|
|||||||
const labels = []
|
const labels = []
|
||||||
for (const p of msg.obj) {
|
for (const p of msg.obj) {
|
||||||
const d = new Date(p.t * 1000)
|
const d = new Date(p.t * 1000)
|
||||||
const hh = String(d.getHours()).padStart(2,'0')
|
const hh = String(d.getHours()).padStart(2, '0')
|
||||||
const mm = String(d.getMinutes()).padStart(2,'0')
|
const mm = String(d.getMinutes()).padStart(2, '0')
|
||||||
const ss = String(d.getSeconds()).padStart(2,'0')
|
const ss = String(d.getSeconds()).padStart(2, '0')
|
||||||
labels.push(bucket>=60 ? `${hh}:${mm}` : `${hh}:${mm}:${ss}`)
|
labels.push(bucket >= 60 ? `${hh}:${mm}` : `${hh}:${mm}:${ss}`)
|
||||||
vals.push(Math.max(0, Math.min(100, p.cpu)))
|
vals.push(Math.max(0, Math.min(100, p.cpu)))
|
||||||
}
|
}
|
||||||
this.cpuHistoryLabels = labels
|
this.cpuHistoryLabels = labels
|
||||||
this.cpuHistoryLong = vals
|
this.cpuHistoryLong = vals
|
||||||
}
|
}
|
||||||
} catch(e) {
|
} catch (e) {
|
||||||
console.error('Failed to fetch bucketed cpu history', e)
|
console.error('Failed to fetch bucketed cpu history', e)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -1129,9 +1251,9 @@
|
|||||||
return typeof moment !== 'undefined' ? moment(ts * 1000).format('YYYY-MM-DD HH:mm') : String(ts);
|
return typeof moment !== 'undefined' ? moment(ts * 1000).format('YYYY-MM-DD HH:mm') : String(ts);
|
||||||
},
|
},
|
||||||
customGeoExtDisplay(record) {
|
customGeoExtDisplay(record) {
|
||||||
const fn = record.type === 'geoip'
|
const fn = record.type === 'geoip' ?
|
||||||
? `geoip_${record.alias}.dat`
|
`geoip_${record.alias}.dat` :
|
||||||
: `geosite_${record.alias}.dat`;
|
`geosite_${record.alias}.dat`;
|
||||||
return `ext:${fn}:tag`;
|
return `ext:${fn}:tag`;
|
||||||
},
|
},
|
||||||
async loadCustomGeo() {
|
async loadCustomGeo() {
|
||||||
@@ -1285,18 +1407,18 @@
|
|||||||
const isSingleFile = !!fileName;
|
const isSingleFile = !!fileName;
|
||||||
this.$confirm({
|
this.$confirm({
|
||||||
title: '{{ i18n "pages.index.geofileUpdateDialog" }}',
|
title: '{{ i18n "pages.index.geofileUpdateDialog" }}',
|
||||||
content: isSingleFile
|
content: isSingleFile ?
|
||||||
? '{{ i18n "pages.index.geofileUpdateDialogDesc" }}'.replace("#filename#", fileName)
|
'{{ i18n "pages.index.geofileUpdateDialogDesc" }}'.replace("#filename#", fileName) :
|
||||||
: '{{ i18n "pages.index.geofilesUpdateDialogDesc" }}',
|
'{{ i18n "pages.index.geofilesUpdateDialogDesc" }}',
|
||||||
okText: '{{ i18n "confirm"}}',
|
okText: '{{ i18n "confirm"}}',
|
||||||
class: themeSwitcher.currentTheme,
|
class: themeSwitcher.currentTheme,
|
||||||
cancelText: '{{ i18n "cancel"}}',
|
cancelText: '{{ i18n "cancel"}}',
|
||||||
onOk: async () => {
|
onOk: async () => {
|
||||||
versionModal.hide();
|
versionModal.hide();
|
||||||
this.loading(true, '{{ i18n "pages.index.dontRefresh"}}');
|
this.loading(true, '{{ i18n "pages.index.dontRefresh"}}');
|
||||||
const url = isSingleFile
|
const url = isSingleFile ?
|
||||||
? `/panel/api/server/updateGeofile/${fileName}`
|
`/panel/api/server/updateGeofile/${fileName}` :
|
||||||
: `/panel/api/server/updateGeofile`;
|
`/panel/api/server/updateGeofile`;
|
||||||
await HttpUtil.post(url);
|
await HttpUtil.post(url);
|
||||||
this.loading(false);
|
this.loading(false);
|
||||||
},
|
},
|
||||||
@@ -1320,7 +1442,10 @@
|
|||||||
},
|
},
|
||||||
async openLogs() {
|
async openLogs() {
|
||||||
logModal.loading = true;
|
logModal.loading = true;
|
||||||
const msg = await HttpUtil.post('/panel/api/server/logs/' + logModal.rows, { level: logModal.level, syslog: logModal.syslog });
|
const msg = await HttpUtil.post('/panel/api/server/logs/' + logModal.rows, {
|
||||||
|
level: logModal.level,
|
||||||
|
syslog: logModal.syslog
|
||||||
|
});
|
||||||
if (!msg.success) {
|
if (!msg.success) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1330,7 +1455,12 @@
|
|||||||
},
|
},
|
||||||
async openXrayLogs() {
|
async openXrayLogs() {
|
||||||
xraylogModal.loading = true;
|
xraylogModal.loading = true;
|
||||||
const msg = await HttpUtil.post('/panel/api/server/xraylogs/' + xraylogModal.rows, { filter: xraylogModal.filter, showDirect: xraylogModal.showDirect, showBlocked: xraylogModal.showBlocked, showProxy: xraylogModal.showProxy });
|
const msg = await HttpUtil.post('/panel/api/server/xraylogs/' + xraylogModal.rows, {
|
||||||
|
filter: xraylogModal.filter,
|
||||||
|
showDirect: xraylogModal.showDirect,
|
||||||
|
showBlocked: xraylogModal.showBlocked,
|
||||||
|
showProxy: xraylogModal.showProxy
|
||||||
|
});
|
||||||
if (!msg.success) {
|
if (!msg.success) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1347,10 +1477,15 @@
|
|||||||
try {
|
try {
|
||||||
const dt = l.DateTime ? new Date(l.DateTime) : null;
|
const dt = l.DateTime ? new Date(l.DateTime) : null;
|
||||||
const dateStr = dt && !isNaN(dt.getTime()) ? dt.toISOString() : '';
|
const dateStr = dt && !isNaN(dt.getTime()) ? dt.toISOString() : '';
|
||||||
const eventMap = { 0: 'DIRECT', 1: 'BLOCKED', 2: 'PROXY' };
|
const eventMap = {
|
||||||
|
0: 'DIRECT',
|
||||||
|
1: 'BLOCKED',
|
||||||
|
2: 'PROXY'
|
||||||
|
};
|
||||||
const eventText = eventMap[l.Event] || String(l.Event ?? '');
|
const eventText = eventMap[l.Event] || String(l.Event ?? '');
|
||||||
const emailPart = l.Email ? ` Email=${l.Email}` : '';
|
const emailPart = l.Email ? ` Email=${l.Email}` : '';
|
||||||
return `${dateStr} FROM=${l.FromAddress || ''} TO=${l.ToAddress || ''} INBOUND=${l.Inbound || ''} OUTBOUND=${l.Outbound || ''}${emailPart} EVENT=${eventText}`.trim();
|
return `${dateStr} FROM=${l.FromAddress || ''} TO=${l.ToAddress || ''} INBOUND=${l.Inbound || ''} OUTBOUND=${l.Outbound || ''}${emailPart} EVENT=${eventText}`
|
||||||
|
.trim();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return JSON.stringify(l);
|
return JSON.stringify(l);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -108,8 +108,15 @@
|
|||||||
el: '#app',
|
el: '#app',
|
||||||
data: {
|
data: {
|
||||||
themeSwitcher,
|
themeSwitcher,
|
||||||
loadingStates: { fetched: false, spinning: false },
|
loadingStates: {
|
||||||
user: { username: "", password: "", twoFactorCode: "" },
|
fetched: false,
|
||||||
|
spinning: false
|
||||||
|
},
|
||||||
|
user: {
|
||||||
|
username: "",
|
||||||
|
password: "",
|
||||||
|
twoFactorCode: ""
|
||||||
|
},
|
||||||
twoFactorEnable: false,
|
twoFactorEnable: false,
|
||||||
lang: "",
|
lang: "",
|
||||||
animationStarted: false
|
animationStarted: false
|
||||||
@@ -233,11 +240,16 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
pm_wait_for_forms.observe(pm_host, { childList: true, subtree: true });
|
pm_wait_for_forms.observe(pm_host, {
|
||||||
|
childList: true,
|
||||||
|
subtree: true
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (document.readyState === 'loading') {
|
if (document.readyState === 'loading') {
|
||||||
document.addEventListener('DOMContentLoaded', pm_init, { once: true });
|
document.addEventListener('DOMContentLoaded', pm_init, {
|
||||||
|
once: true
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
pm_init();
|
pm_init();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,58 +1,41 @@
|
|||||||
{{define "modals/clientsBulkModal"}}
|
{{define "modals/clientsBulkModal"}}
|
||||||
<a-modal id="client-bulk-modal" v-model="clientsBulkModal.visible"
|
<a-modal id="client-bulk-modal" v-model="clientsBulkModal.visible" :title="clientsBulkModal.title"
|
||||||
:title="clientsBulkModal.title"
|
@ok="clientsBulkModal.ok" :confirm-loading="clientsBulkModal.confirmLoading" :closable="true" :mask-closable="false"
|
||||||
@ok="clientsBulkModal.ok" :confirm-loading="clientsBulkModal.confirmLoading"
|
:ok-text="clientsBulkModal.okText" cancel-text='{{ i18n "close" }}' :class="themeSwitcher.currentTheme">
|
||||||
:closable="true" :mask-closable="false"
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
:ok-text="clientsBulkModal.okText" cancel-text='{{ i18n "close" }}'
|
|
||||||
:class="themeSwitcher.currentTheme">
|
|
||||||
<a-form :colon="false" :label-col="{ md: {span:8} }"
|
|
||||||
:wrapper-col="{ md: {span:14} }">
|
|
||||||
<a-form-item label='{{ i18n "pages.client.method" }}'>
|
<a-form-item label='{{ i18n "pages.client.method" }}'>
|
||||||
<a-select v-model="clientsBulkModal.emailMethod" buttonStyle="solid"
|
<a-select v-model="clientsBulkModal.emailMethod" buttonStyle="solid"
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option :value="0">Random</a-select-option>
|
<a-select-option :value="0">Random</a-select-option>
|
||||||
<a-select-option :value="1">Random+Prefix</a-select-option>
|
<a-select-option :value="1">Random+Prefix</a-select-option>
|
||||||
<a-select-option :value="2">Random+Prefix+Num</a-select-option>
|
<a-select-option :value="2">Random+Prefix+Num</a-select-option>
|
||||||
<a-select-option
|
<a-select-option :value="3">Random+Prefix+Num+Postfix</a-select-option>
|
||||||
:value="3">Random+Prefix+Num+Postfix</a-select-option>
|
|
||||||
<a-select-option :value="4">Prefix+Num+Postfix</a-select-option>
|
<a-select-option :value="4">Prefix+Num+Postfix</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "pages.client.first" }}'
|
<a-form-item label='{{ i18n "pages.client.first" }}' v-if="clientsBulkModal.emailMethod>1">
|
||||||
v-if="clientsBulkModal.emailMethod>1">
|
<a-input-number v-model.number="clientsBulkModal.firstNum" :min="1"></a-input-number>
|
||||||
<a-input-number v-model.number="clientsBulkModal.firstNum"
|
|
||||||
:min="1"></a-input-number>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "pages.client.last" }}'
|
<a-form-item label='{{ i18n "pages.client.last" }}' v-if="clientsBulkModal.emailMethod>1">
|
||||||
v-if="clientsBulkModal.emailMethod>1">
|
<a-input-number v-model.number="clientsBulkModal.lastNum" :min="clientsBulkModal.firstNum"></a-input-number>
|
||||||
<a-input-number v-model.number="clientsBulkModal.lastNum"
|
|
||||||
:min="clientsBulkModal.firstNum"></a-input-number>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "pages.client.prefix" }}'
|
<a-form-item label='{{ i18n "pages.client.prefix" }}' v-if="clientsBulkModal.emailMethod>0">
|
||||||
v-if="clientsBulkModal.emailMethod>0">
|
|
||||||
<a-input v-model.trim="clientsBulkModal.emailPrefix"></a-input>
|
<a-input v-model.trim="clientsBulkModal.emailPrefix"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "pages.client.postfix" }}'
|
<a-form-item label='{{ i18n "pages.client.postfix" }}' v-if="clientsBulkModal.emailMethod>2">
|
||||||
v-if="clientsBulkModal.emailMethod>2">
|
|
||||||
<a-input v-model.trim="clientsBulkModal.emailPostfix"></a-input>
|
<a-input v-model.trim="clientsBulkModal.emailPostfix"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "pages.client.clientCount" }}'
|
<a-form-item label='{{ i18n "pages.client.clientCount" }}' v-if="clientsBulkModal.emailMethod < 2">
|
||||||
v-if="clientsBulkModal.emailMethod < 2">
|
<a-input-number v-model.number="clientsBulkModal.quantity" :min="1" :max="500"></a-input-number>
|
||||||
<a-input-number v-model.number="clientsBulkModal.quantity" :min="1"
|
|
||||||
:max="500"></a-input-number>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "security" }}'
|
<a-form-item label='{{ i18n "security" }}' v-if="inbound.protocol === Protocols.VMESS">
|
||||||
v-if="inbound.protocol === Protocols.VMESS">
|
<a-select v-model="clientsBulkModal.security" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select v-model="clientsBulkModal.security"
|
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
|
||||||
<a-select-option v-for="key in USERS_SECURITY" :value="key">[[
|
<a-select-option v-for="key in USERS_SECURITY" :value="key">[[
|
||||||
key ]]</a-select-option>
|
key ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='Flow'
|
<a-form-item label='Flow' v-if="clientsBulkModal.inbound.canEnableTlsFlow()">
|
||||||
v-if="clientsBulkModal.inbound.canEnableTlsFlow()">
|
<a-select v-model="clientsBulkModal.flow" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select v-model="clientsBulkModal.flow"
|
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
|
||||||
<a-select-option value selected>{{ i18n "none"
|
<a-select-option value selected>{{ i18n "none"
|
||||||
}}</a-select-option>
|
}}</a-select-option>
|
||||||
<a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[
|
<a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[
|
||||||
@@ -67,9 +50,7 @@
|
|||||||
}}</span>
|
}}</span>
|
||||||
</template>
|
</template>
|
||||||
Subscription
|
Subscription
|
||||||
<a-icon
|
<a-icon @click="clientsBulkModal.subId = RandomUtil.randomLowerAndNum(16)" type="sync"></a-icon>
|
||||||
@click="clientsBulkModal.subId = RandomUtil.randomLowerAndNum(16)"
|
|
||||||
type="sync"></a-icon>
|
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</template>
|
</template>
|
||||||
<a-input v-model.trim="clientsBulkModal.subId"></a-input>
|
<a-input v-model.trim="clientsBulkModal.subId"></a-input>
|
||||||
@@ -84,8 +65,7 @@
|
|||||||
<a-icon type="question-circle"></a-icon>
|
<a-icon type="question-circle"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</template>
|
</template>
|
||||||
<a-input-number :style="{ width: '50%' }"
|
<a-input-number :style="{ width: '50%' }" v-model.number="clientsBulkModal.tgId" min="0"></a-input-number>
|
||||||
v-model.number="clientsBulkModal.tgId" min="0"></a-input-number>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item v-if="app.ipLimitEnable">
|
<a-form-item v-if="app.ipLimitEnable">
|
||||||
<template slot="label">
|
<template slot="label">
|
||||||
@@ -97,8 +77,7 @@
|
|||||||
<a-icon type="question-circle"></a-icon>
|
<a-icon type="question-circle"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</template>
|
</template>
|
||||||
<a-input-number v-model.number="clientsBulkModal.limitIp"
|
<a-input-number v-model.number="clientsBulkModal.limitIp" min="0"></a-input-number>
|
||||||
min="0"></a-input-number>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<template slot="label">
|
<template slot="label">
|
||||||
@@ -110,17 +89,13 @@
|
|||||||
<a-icon type="question-circle"></a-icon>
|
<a-icon type="question-circle"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</template>
|
</template>
|
||||||
<a-input-number v-model.number="clientsBulkModal.totalGB"
|
<a-input-number v-model.number="clientsBulkModal.totalGB" :min="0"></a-input-number>
|
||||||
:min="0"></a-input-number>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "pages.client.delayedStart" }}'>
|
<a-form-item label='{{ i18n "pages.client.delayedStart" }}'>
|
||||||
<a-switch v-model="clientsBulkModal.delayedStart"
|
<a-switch v-model="clientsBulkModal.delayedStart" @click="clientsBulkModal.expiryTime=0"></a-switch>
|
||||||
@click="clientsBulkModal.expiryTime=0"></a-switch>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "pages.client.expireDays" }}'
|
<a-form-item label='{{ i18n "pages.client.expireDays" }}' v-if="clientsBulkModal.delayedStart">
|
||||||
v-if="clientsBulkModal.delayedStart">
|
<a-input-number v-model.number="delayedExpireDays" :min="0"></a-input-number>
|
||||||
<a-input-number v-model.number="delayedExpireDays"
|
|
||||||
:min="0"></a-input-number>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item v-else>
|
<a-form-item v-else>
|
||||||
<template slot="label">
|
<template slot="label">
|
||||||
@@ -133,15 +108,11 @@
|
|||||||
<a-icon type="question-circle"></a-icon>
|
<a-icon type="question-circle"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</template>
|
</template>
|
||||||
<a-date-picker v-if="datepicker == 'gregorian'"
|
<a-date-picker v-if="datepicker == 'gregorian'" :show-time="{ format: 'HH:mm:ss' }"
|
||||||
:show-time="{ format: 'HH:mm:ss' }"
|
format="YYYY-MM-DD HH:mm:ss" :dropdown-class-name="themeSwitcher.currentTheme"
|
||||||
format="YYYY-MM-DD HH:mm:ss"
|
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme"
|
|
||||||
v-model="clientsBulkModal.expiryTime"></a-date-picker>
|
v-model="clientsBulkModal.expiryTime"></a-date-picker>
|
||||||
<a-persian-datepicker v-else
|
<a-persian-datepicker v-else placeholder='{{ i18n "pages.settings.datepickerPlaceholder" }}'
|
||||||
placeholder='{{ i18n "pages.settings.datepickerPlaceholder" }}'
|
value="clientsBulkModal.expiryTime" v-model="clientsBulkModal.expiryTime">
|
||||||
value="clientsBulkModal.expiryTime"
|
|
||||||
v-model="clientsBulkModal.expiryTime">
|
|
||||||
</a-persian-datepicker>
|
</a-persian-datepicker>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item v-if="clientsBulkModal.expiryTime != 0">
|
<a-form-item v-if="clientsBulkModal.expiryTime != 0">
|
||||||
@@ -154,13 +125,11 @@
|
|||||||
<a-icon type="question-circle"></a-icon>
|
<a-icon type="question-circle"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</template>
|
</template>
|
||||||
<a-input-number v-model.number="clientsBulkModal.reset"
|
<a-input-number v-model.number="clientsBulkModal.reset" :min="0"></a-input-number>
|
||||||
:min="0"></a-input-number>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
const clientsBulkModal = {
|
const clientsBulkModal = {
|
||||||
visible: false,
|
visible: false,
|
||||||
confirmLoading: false,
|
confirmLoading: false,
|
||||||
@@ -219,7 +188,7 @@
|
|||||||
title = '',
|
title = '',
|
||||||
okText = '{{ i18n "sure" }}',
|
okText = '{{ i18n "sure" }}',
|
||||||
dbInbound = null,
|
dbInbound = null,
|
||||||
confirm = (inbound, dbInbound) => { }
|
confirm = (inbound, dbInbound) => {}
|
||||||
}) {
|
}) {
|
||||||
this.visible = true;
|
this.visible = true;
|
||||||
this.title = title;
|
this.title = title;
|
||||||
@@ -245,12 +214,19 @@
|
|||||||
},
|
},
|
||||||
newClient(protocol) {
|
newClient(protocol) {
|
||||||
switch (protocol) {
|
switch (protocol) {
|
||||||
case Protocols.VMESS: return new Inbound.VmessSettings.VMESS();
|
case Protocols.VMESS:
|
||||||
case Protocols.VLESS: return new Inbound.VLESSSettings.VLESS();
|
return new Inbound.VmessSettings.VMESS();
|
||||||
case Protocols.TROJAN: return new Inbound.TrojanSettings.Trojan();
|
case Protocols.VLESS:
|
||||||
case Protocols.SHADOWSOCKS: return new Inbound.ShadowsocksSettings.Shadowsocks(clientsBulkModal.inbound.settings.shadowsockses[0].method);
|
return new Inbound.VLESSSettings.VLESS();
|
||||||
case Protocols.HYSTERIA: return new Inbound.HysteriaSettings.Hysteria();
|
case Protocols.TROJAN:
|
||||||
default: return null;
|
return new Inbound.TrojanSettings.Trojan();
|
||||||
|
case Protocols.SHADOWSOCKS:
|
||||||
|
return new Inbound.ShadowsocksSettings.Shadowsocks(clientsBulkModal.inbound.settings
|
||||||
|
.shadowsockses[0].method);
|
||||||
|
case Protocols.HYSTERIA:
|
||||||
|
return new Inbound.HysteriaSettings.Hysteria();
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
close() {
|
close() {
|
||||||
@@ -271,7 +247,8 @@
|
|||||||
return this.clientsBulkModal.inbound;
|
return this.clientsBulkModal.inbound;
|
||||||
},
|
},
|
||||||
get delayedExpireDays() {
|
get delayedExpireDays() {
|
||||||
return this.clientsBulkModal.expiryTime < 0 ? this.clientsBulkModal.expiryTime / -86400000 : 0;
|
return this.clientsBulkModal.expiryTime < 0 ? this.clientsBulkModal.expiryTime / -86400000 :
|
||||||
|
0;
|
||||||
},
|
},
|
||||||
get datepicker() {
|
get datepicker() {
|
||||||
return app.datepicker;
|
return app.datepicker;
|
||||||
@@ -281,6 +258,5 @@
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
{{end}}
|
{{end}}
|
||||||
@@ -1,10 +1,7 @@
|
|||||||
{{define "modals/clientsModal"}}
|
{{define "modals/clientsModal"}}
|
||||||
<a-modal id="client-modal" v-model="clientModal.visible"
|
<a-modal id="client-modal" v-model="clientModal.visible" :title="clientModal.title" @ok="clientModal.ok"
|
||||||
:title="clientModal.title" @ok="clientModal.ok"
|
:confirm-loading="clientModal.confirmLoading" :closable="true" :mask-closable="false"
|
||||||
:confirm-loading="clientModal.confirmLoading" :closable="true"
|
:class="themeSwitcher.currentTheme" :ok-text="clientModal.okText" cancel-text='{{ i18n "close" }}'>
|
||||||
:mask-closable="false"
|
|
||||||
:class="themeSwitcher.currentTheme"
|
|
||||||
:ok-text="clientModal.okText" cancel-text='{{ i18n "close" }}'>
|
|
||||||
<template v-if="isEdit">
|
<template v-if="isEdit">
|
||||||
<a-tag v-if="isExpiry || isTrafficExhausted" color="red"
|
<a-tag v-if="isExpiry || isTrafficExhausted" color="red"
|
||||||
:style="{ marginBottom: '10px', display: 'block', textAlign: 'center' }">Account
|
:style="{ marginBottom: '10px', display: 'block', textAlign: 'center' }">Account
|
||||||
@@ -13,7 +10,6 @@
|
|||||||
{{template "form/client"}}
|
{{template "form/client"}}
|
||||||
</a-modal>
|
</a-modal>
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
const clientModal = {
|
const clientModal = {
|
||||||
visible: false,
|
visible: false,
|
||||||
confirmLoading: false,
|
confirmLoading: false,
|
||||||
@@ -30,12 +26,20 @@
|
|||||||
delayedStart: false,
|
delayedStart: false,
|
||||||
ok() {
|
ok() {
|
||||||
if (clientModal.isEdit) {
|
if (clientModal.isEdit) {
|
||||||
ObjectUtil.execute(clientModal.confirm, clientModalApp.client, clientModal.dbInbound.id, clientModal.oldClientId);
|
ObjectUtil.execute(clientModal.confirm, clientModalApp.client, clientModal.dbInbound.id, clientModal
|
||||||
|
.oldClientId);
|
||||||
} else {
|
} else {
|
||||||
ObjectUtil.execute(clientModal.confirm, clientModalApp.client, clientModal.dbInbound.id);
|
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,
|
||||||
|
confirm = () => {},
|
||||||
|
isEdit = false
|
||||||
|
}) {
|
||||||
this.visible = true;
|
this.visible = true;
|
||||||
this.title = title;
|
this.title = title;
|
||||||
this.okText = okText;
|
this.okText = okText;
|
||||||
@@ -58,27 +62,38 @@
|
|||||||
},
|
},
|
||||||
getClientId(protocol, client) {
|
getClientId(protocol, client) {
|
||||||
switch (protocol) {
|
switch (protocol) {
|
||||||
case Protocols.TROJAN: return client.password;
|
case Protocols.TROJAN:
|
||||||
case Protocols.SHADOWSOCKS: return client.email;
|
return client.password;
|
||||||
case Protocols.HYSTERIA: return client.auth;
|
case Protocols.SHADOWSOCKS:
|
||||||
default: return client.id;
|
return client.email;
|
||||||
|
case Protocols.HYSTERIA:
|
||||||
|
return client.auth;
|
||||||
|
default:
|
||||||
|
return client.id;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
addClient(inbound, clients) {
|
addClient(inbound, clients) {
|
||||||
switch (inbound.protocol) {
|
switch (inbound.protocol) {
|
||||||
case Protocols.VMESS: return clients.push(new Inbound.VmessSettings.VMESS());
|
case Protocols.VMESS:
|
||||||
case Protocols.VLESS: return clients.push(new Inbound.VLESSSettings.VLESS());
|
return clients.push(new Inbound.VmessSettings.VMESS());
|
||||||
case Protocols.TROJAN: return clients.push(new Inbound.TrojanSettings.Trojan());
|
case Protocols.VLESS:
|
||||||
case Protocols.SHADOWSOCKS: return clients.push(new Inbound.ShadowsocksSettings.Shadowsocks(clients[0].method, RandomUtil.randomShadowsocksPassword(inbound.settings.method)));
|
return clients.push(new Inbound.VLESSSettings.VLESS());
|
||||||
case Protocols.HYSTERIA: return clients.push(new Inbound.HysteriaSettings.Hysteria());
|
case Protocols.TROJAN:
|
||||||
default: return null;
|
return clients.push(new Inbound.TrojanSettings.Trojan());
|
||||||
|
case Protocols.SHADOWSOCKS:
|
||||||
|
return clients.push(new Inbound.ShadowsocksSettings.Shadowsocks(clients[0].method, RandomUtil
|
||||||
|
.randomShadowsocksPassword(inbound.settings.method)));
|
||||||
|
case Protocols.HYSTERIA:
|
||||||
|
return clients.push(new Inbound.HysteriaSettings.Hysteria());
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
close() {
|
close() {
|
||||||
clientModal.visible = false;
|
clientModal.visible = false;
|
||||||
clientModal.loading(false);
|
clientModal.loading(false);
|
||||||
},
|
},
|
||||||
loading(loading=true) {
|
loading(loading = true) {
|
||||||
clientModal.confirmLoading = loading;
|
clientModal.confirmLoading = loading;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -110,7 +125,8 @@
|
|||||||
return true
|
return true
|
||||||
},
|
},
|
||||||
get isExpiry() {
|
get isExpiry() {
|
||||||
return this.clientModal.isEdit && this.client.expiryTime >0 ? (this.client.expiryTime < new Date().getTime()) : false;
|
return this.clientModal.isEdit && this.client.expiryTime > 0 ? (this.client.expiryTime <
|
||||||
|
new Date().getTime()) : false;
|
||||||
},
|
},
|
||||||
get delayedStart() {
|
get delayedStart() {
|
||||||
return this.clientModal.delayedStart;
|
return this.clientModal.delayedStart;
|
||||||
@@ -150,8 +166,7 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
document.getElementById("clientIPs").value = "";
|
document.getElementById("clientIPs").value = "";
|
||||||
} catch (error) {
|
} catch (error) {}
|
||||||
}
|
|
||||||
},
|
},
|
||||||
resetClientTraffic(email, dbInboundId, iconElement) {
|
resetClientTraffic(email, dbInboundId, iconElement) {
|
||||||
this.$confirm({
|
this.$confirm({
|
||||||
@@ -162,7 +177,8 @@
|
|||||||
cancelText: '{{ i18n "cancel"}}',
|
cancelText: '{{ i18n "cancel"}}',
|
||||||
onOk: async () => {
|
onOk: async () => {
|
||||||
iconElement.disabled = true;
|
iconElement.disabled = true;
|
||||||
const msg = await HttpUtil.postWithModal('/panel/api/inbounds/' + dbInboundId + '/resetClientTraffic/' + email);
|
const msg = await HttpUtil.postWithModal('/panel/api/inbounds/' +
|
||||||
|
dbInboundId + '/resetClientTraffic/' + email);
|
||||||
if (msg.success) {
|
if (msg.success) {
|
||||||
this.clientModal.clientStats.up = 0;
|
this.clientModal.clientStats.up = 0;
|
||||||
this.clientModal.clientStats.down = 0;
|
this.clientModal.clientStats.down = 0;
|
||||||
@@ -173,6 +189,5 @@
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
{{end}}
|
{{end}}
|
||||||
@@ -5,10 +5,12 @@
|
|||||||
<a-list-item v-for="dns in dnsPresetsDatabase" :style="{ padding: '12px 16px' }">
|
<a-list-item v-for="dns in dnsPresetsDatabase" :style="{ padding: '12px 16px' }">
|
||||||
<div class="ant-dns-presets-line">
|
<div class="ant-dns-presets-line">
|
||||||
<a-space direction="horizontal" size="small" align="center">
|
<a-space direction="horizontal" size="small" align="center">
|
||||||
<a-tag :color="dns.family ? 'purple' : 'green'">[[ dns.family ? '{{ i18n "pages.xray.dns.dnsPresetFamily" }}' : 'DNS' ]]</a-tag>
|
<a-tag :color="dns.family ? 'purple' : 'green'">[[ dns.family ? '{{ i18n "pages.xray.dns.dnsPresetFamily" }}'
|
||||||
|
: 'DNS' ]]</a-tag>
|
||||||
<span class="ant-dns-presets-list-name">[[ dns.name ]]</span>
|
<span class="ant-dns-presets-list-name">[[ dns.name ]]</span>
|
||||||
</a-space>
|
</a-space>
|
||||||
<a-button class="ant-dns-presets-install" type="primary" @click="dnsPresetsModal.install(dns.data)">{{ i18n "install" }}</a-button>
|
<a-button class="ant-dns-presets-install" type="primary"
|
||||||
|
@click="dnsPresetsModal.install(dns.data)">{{ i18n "install" }}</a-button>
|
||||||
</div>
|
</div>
|
||||||
</a-list-item>
|
</a-list-item>
|
||||||
</a-list>
|
</a-list>
|
||||||
@@ -36,8 +38,7 @@
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const dnsPresetsDatabase = [
|
const dnsPresetsDatabase = [{
|
||||||
{
|
|
||||||
name: 'Google DNS',
|
name: 'Google DNS',
|
||||||
family: false,
|
family: false,
|
||||||
data: [
|
data: [
|
||||||
@@ -96,7 +97,11 @@
|
|||||||
install(selectedPreset) {
|
install(selectedPreset) {
|
||||||
return ObjectUtil.execute(dnsPresetsModal.selected, selectedPreset);
|
return ObjectUtil.execute(dnsPresetsModal.selected, selectedPreset);
|
||||||
},
|
},
|
||||||
show({ title = '', selected = (selectedPreset) => { }, isEdit = false }) {
|
show({
|
||||||
|
title = '',
|
||||||
|
selected = (selectedPreset) => {},
|
||||||
|
isEdit = false
|
||||||
|
}) {
|
||||||
this.title = title;
|
this.title = title;
|
||||||
this.selected = selected;
|
this.selected = selected;
|
||||||
this.visible = true;
|
this.visible = true;
|
||||||
|
|||||||
@@ -521,7 +521,8 @@
|
|||||||
@click="copy(infoModal.wireguardLinks[index])"></a-button>
|
@click="copy(infoModal.wireguardLinks[index])"></a-button>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</tr-info-title>
|
</tr-info-title>
|
||||||
<code :style="{ display: 'block', whiteSpace: 'normal', wordBreak: 'break-all' }">[[ infoModal.wireguardLinks[index] ]]</code>
|
<code :style="{ display: 'block', whiteSpace: 'normal', wordBreak: 'break-all' }">[[
|
||||||
|
infoModal.wireguardLinks[index] ]]</code>
|
||||||
</tr-info-row>
|
</tr-info-row>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -534,7 +535,10 @@
|
|||||||
function refreshIPs(email) {
|
function refreshIPs(email) {
|
||||||
return HttpUtil.post(`/panel/api/inbounds/clientIps/${email}`).then((msg) => {
|
return HttpUtil.post(`/panel/api/inbounds/clientIps/${email}`).then((msg) => {
|
||||||
if (!msg.success) {
|
if (!msg.success) {
|
||||||
return { text: 'No IP Record', array: [] };
|
return {
|
||||||
|
text: 'No IP Record',
|
||||||
|
array: []
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const formatIpRecord = (record) => {
|
const formatIpRecord = (record) => {
|
||||||
@@ -574,7 +578,10 @@
|
|||||||
try {
|
try {
|
||||||
ips = JSON.parse(ips);
|
ips = JSON.parse(ips);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return { text: String(ips), array: [String(ips)] };
|
return {
|
||||||
|
text: String(ips),
|
||||||
|
array: [String(ips)]
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -586,20 +593,32 @@
|
|||||||
// New format or object array
|
// New format or object array
|
||||||
if (Array.isArray(ips) && ips.length > 0 && typeof ips[0] === 'object') {
|
if (Array.isArray(ips) && ips.length > 0 && typeof ips[0] === 'object') {
|
||||||
const result = ips.map((item) => formatIpRecord(item)).filter(Boolean);
|
const result = ips.map((item) => formatIpRecord(item)).filter(Boolean);
|
||||||
return { text: result.join(' | '), array: result };
|
return {
|
||||||
|
text: result.join(' | '),
|
||||||
|
array: result
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Old format - simple array of IPs
|
// Old format - simple array of IPs
|
||||||
if (Array.isArray(ips) && ips.length > 0) {
|
if (Array.isArray(ips) && ips.length > 0) {
|
||||||
const result = ips.map((ip) => String(ip));
|
const result = ips.map((ip) => String(ip));
|
||||||
return { text: result.join(', '), array: result };
|
return {
|
||||||
|
text: result.join(', '),
|
||||||
|
array: result
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback for any other format
|
// Fallback for any other format
|
||||||
return { text: String(ips), array: [String(ips)] };
|
return {
|
||||||
|
text: String(ips),
|
||||||
|
array: [String(ips)]
|
||||||
|
};
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return { text: 'Error loading IPs', array: [] };
|
return {
|
||||||
|
text: 'Error loading IPs',
|
||||||
|
array: []
|
||||||
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -626,7 +645,8 @@
|
|||||||
this.dbInbound = new DBInbound(dbInbound);
|
this.dbInbound = new DBInbound(dbInbound);
|
||||||
this.clientSettings = this.inbound.clients ? this.inbound.clients[index] : null;
|
this.clientSettings = this.inbound.clients ? this.inbound.clients[index] : null;
|
||||||
this.isExpired = this.inbound.clients ? this.inbound.isExpiry(index) : this.dbInbound.isExpiry;
|
this.isExpired = this.inbound.clients ? this.inbound.isExpiry(index) : this.dbInbound.isExpiry;
|
||||||
this.clientStats = this.inbound.clients ? (this.dbInbound.clientStats.find(row => row.email === this.clientSettings.email) || null) : null;
|
this.clientStats = this.inbound.clients ? (this.dbInbound.clientStats.find(row => row.email === this
|
||||||
|
.clientSettings.email) || null) : null;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
[
|
[
|
||||||
@@ -752,7 +772,8 @@
|
|||||||
return ColorUtils.usageColor(stats.up + stats.down, app.trafficDiff, stats.total);
|
return ColorUtils.usageColor(stats.up + stats.down, app.trafficDiff, stats.total);
|
||||||
},
|
},
|
||||||
getRemStats() {
|
getRemStats() {
|
||||||
remained = this.infoModal.clientStats.total - this.infoModal.clientStats.up - this.infoModal.clientStats.down;
|
remained = this.infoModal.clientStats.total - this.infoModal.clientStats.up - this.infoModal.clientStats
|
||||||
|
.down;
|
||||||
return remained > 0 ? SizeFormatter.sizeFormat(remained) : '-';
|
return remained > 0 ? SizeFormatter.sizeFormat(remained) : '-';
|
||||||
},
|
},
|
||||||
refreshIPs() {
|
refreshIPs() {
|
||||||
@@ -775,7 +796,7 @@
|
|||||||
this.infoModal.clientIps = 'No IP Record';
|
this.infoModal.clientIps = 'No IP Record';
|
||||||
this.infoModal.clientIpsArray = [];
|
this.infoModal.clientIpsArray = [];
|
||||||
})
|
})
|
||||||
.catch(() => { });
|
.catch(() => {});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
okText = '{{ i18n "sure" }}',
|
okText = '{{ i18n "sure" }}',
|
||||||
inbound = null,
|
inbound = null,
|
||||||
dbInbound = null,
|
dbInbound = null,
|
||||||
confirm = (inbound, dbInbound) => { },
|
confirm = (inbound, dbInbound) => {},
|
||||||
isEdit = false,
|
isEdit = false,
|
||||||
}) {
|
}) {
|
||||||
this.title = title;
|
this.title = title;
|
||||||
@@ -127,17 +127,17 @@
|
|||||||
get client() {
|
get client() {
|
||||||
return inModal.inbound &&
|
return inModal.inbound &&
|
||||||
inModal.inbound.clients &&
|
inModal.inbound.clients &&
|
||||||
inModal.inbound.clients.length > 0
|
inModal.inbound.clients.length > 0 ?
|
||||||
? inModal.inbound.clients[0]
|
inModal.inbound.clients[0] :
|
||||||
: null;
|
null;
|
||||||
},
|
},
|
||||||
get datepicker() {
|
get datepicker() {
|
||||||
return app.datepicker;
|
return app.datepicker;
|
||||||
},
|
},
|
||||||
get delayedExpireDays() {
|
get delayedExpireDays() {
|
||||||
return this.client && this.client.expiryTime < 0
|
return this.client && this.client.expiryTime < 0 ?
|
||||||
? this.client.expiryTime / -86400000
|
this.client.expiryTime / -86400000 :
|
||||||
: 0;
|
0;
|
||||||
},
|
},
|
||||||
set delayedExpireDays(days) {
|
set delayedExpireDays(days) {
|
||||||
this.client.expiryTime = -86400000 * days;
|
this.client.expiryTime = -86400000 * days;
|
||||||
@@ -147,14 +147,12 @@
|
|||||||
},
|
},
|
||||||
set externalProxy(value) {
|
set externalProxy(value) {
|
||||||
if (value) {
|
if (value) {
|
||||||
inModal.inbound.stream.externalProxy = [
|
inModal.inbound.stream.externalProxy = [{
|
||||||
{
|
forceTls: "same",
|
||||||
forceTls: "same",
|
dest: window.location.hostname,
|
||||||
dest: window.location.hostname,
|
port: inModal.inbound.port,
|
||||||
port: inModal.inbound.port,
|
remark: "",
|
||||||
remark: "",
|
}, ];
|
||||||
},
|
|
||||||
];
|
|
||||||
} else {
|
} else {
|
||||||
inModal.inbound.stream.externalProxy = [];
|
inModal.inbound.stream.externalProxy = [];
|
||||||
}
|
}
|
||||||
@@ -182,8 +180,8 @@
|
|||||||
) {
|
) {
|
||||||
const hasVisionFlow = inModal.inbound.settings.vlesses.some(
|
const hasVisionFlow = inModal.inbound.settings.vlesses.some(
|
||||||
(c) =>
|
(c) =>
|
||||||
c.flow === "xtls-rprx-vision" ||
|
c.flow === "xtls-rprx-vision" ||
|
||||||
c.flow === "xtls-rprx-vision-udp443",
|
c.flow === "xtls-rprx-vision-udp443",
|
||||||
);
|
);
|
||||||
if (
|
if (
|
||||||
hasVisionFlow &&
|
hasVisionFlow &&
|
||||||
|
|||||||
@@ -1,25 +1,31 @@
|
|||||||
{{define "modals/nordModal"}}
|
{{define "modals/nordModal"}}
|
||||||
<a-modal id="nord-modal" v-model="nordModal.visible" title="NordVPN NordLynx"
|
<a-modal id="nord-modal" v-model="nordModal.visible" title="NordVPN NordLynx"
|
||||||
:confirm-loading="nordModal.confirmLoading" :closable="true" :mask-closable="true"
|
:confirm-loading="nordModal.confirmLoading" :closable="true" :mask-closable="true" :footer="null"
|
||||||
:footer="null" :class="themeSwitcher.currentTheme">
|
:class="themeSwitcher.currentTheme">
|
||||||
<template v-if="nordModal.nordData == null">
|
<template v-if="nordModal.nordData == null">
|
||||||
<a-tabs default-active-key="token" :class="themeSwitcher.currentTheme">
|
<a-tabs default-active-key="token" :class="themeSwitcher.currentTheme">
|
||||||
<a-tab-pane key="token" tab='{{ i18n "pages.xray.outbound.accessToken" }}'>
|
<a-tab-pane key="token" tab='{{ i18n "pages.xray.outbound.accessToken" }}'>
|
||||||
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:18} }" :style="{ marginTop: '20px' }">
|
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:18} }"
|
||||||
|
:style="{ marginTop: '20px' }">
|
||||||
<a-form-item label='{{ i18n "pages.xray.outbound.accessToken" }}'>
|
<a-form-item label='{{ i18n "pages.xray.outbound.accessToken" }}'>
|
||||||
<a-input v-model="nordModal.token" placeholder='{{ i18n "pages.xray.outbound.accessToken" }}'></a-input>
|
<a-input v-model="nordModal.token"
|
||||||
|
placeholder='{{ i18n "pages.xray.outbound.accessToken" }}'></a-input>
|
||||||
<div :style="{ marginTop: '10px' }">
|
<div :style="{ marginTop: '10px' }">
|
||||||
<a-button type="primary" icon="login" @click="login()" :loading="nordModal.confirmLoading">{{ i18n "login" }}</a-button>
|
<a-button type="primary" icon="login" @click="login()"
|
||||||
|
:loading="nordModal.confirmLoading">{{ i18n "login" }}</a-button>
|
||||||
</div>
|
</div>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
<a-tab-pane key="key" tab='{{ i18n "pages.xray.outbound.privateKey" }}'>
|
<a-tab-pane key="key" tab='{{ i18n "pages.xray.outbound.privateKey" }}'>
|
||||||
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:18} }" :style="{ marginTop: '20px' }">
|
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:18} }"
|
||||||
|
:style="{ marginTop: '20px' }">
|
||||||
<a-form-item label='{{ i18n "pages.xray.outbound.privateKey" }}'>
|
<a-form-item label='{{ i18n "pages.xray.outbound.privateKey" }}'>
|
||||||
<a-input v-model="nordModal.manualKey" placeholder='{{ i18n "pages.xray.outbound.privateKey" }}'></a-input>
|
<a-input v-model="nordModal.manualKey"
|
||||||
|
placeholder='{{ i18n "pages.xray.outbound.privateKey" }}'></a-input>
|
||||||
<div :style="{ marginTop: '10px' }">
|
<div :style="{ marginTop: '10px' }">
|
||||||
<a-button type="primary" icon="save" @click="saveKey()" :loading="nordModal.confirmLoading">{{ i18n "save" }}</a-button>
|
<a-button type="primary" icon="save" @click="saveKey()"
|
||||||
|
:loading="nordModal.confirmLoading">{{ i18n "save" }}</a-button>
|
||||||
</div>
|
</div>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
@@ -39,7 +45,8 @@
|
|||||||
</table>
|
</table>
|
||||||
<a-button @click="logout" :loading="nordModal.confirmLoading" type="danger">{{ i18n "logout" }}</a-button>
|
<a-button @click="logout" :loading="nordModal.confirmLoading" type="danger">{{ i18n "logout" }}</a-button>
|
||||||
<a-divider :style="{ margin: '0' }">{{ i18n "pages.xray.outbound.settings" }}</a-divider>
|
<a-divider :style="{ margin: '0' }">{{ i18n "pages.xray.outbound.settings" }}</a-divider>
|
||||||
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:18} }" :style="{ marginTop: '10px' }">
|
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:18} }"
|
||||||
|
:style="{ marginTop: '10px' }">
|
||||||
<a-form-item label='{{ i18n "pages.xray.outbound.country" }}'>
|
<a-form-item label='{{ i18n "pages.xray.outbound.country" }}'>
|
||||||
<a-select v-model="nordModal.countryId" @change="fetchServers" show-search option-filter-prop="label">
|
<a-select v-model="nordModal.countryId" @change="fetchServers" show-search option-filter-prop="label">
|
||||||
<a-select-option v-for="c in nordModal.countries" :key="c.id" :value="c.id" :label="c.name">
|
<a-select-option v-for="c in nordModal.countries" :key="c.id" :value="c.id" :label="c.name">
|
||||||
@@ -69,11 +76,13 @@
|
|||||||
<a-form :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
<a-form :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<template v-if="nordOutboundIndex>=0">
|
<template v-if="nordOutboundIndex>=0">
|
||||||
<a-tag color="green" :style="{ lineHeight: '31px' }">{{ i18n "enabled" }}</a-tag>
|
<a-tag color="green" :style="{ lineHeight: '31px' }">{{ i18n "enabled" }}</a-tag>
|
||||||
<a-button @click="resetOutbound" :loading="nordModal.confirmLoading" type="danger">{{ i18n "reset" }}</a-button>
|
<a-button @click="resetOutbound" :loading="nordModal.confirmLoading"
|
||||||
|
type="danger">{{ i18n "reset" }}</a-button>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<a-tag color="orange" :style="{ lineHeight: '31px' }">{{ i18n "disabled" }}</a-tag>
|
<a-tag color="orange" :style="{ lineHeight: '31px' }">{{ i18n "disabled" }}</a-tag>
|
||||||
<a-button @click="addOutbound" :disabled="!nordModal.serverId" :loading="nordModal.confirmLoading" type="primary">{{ i18n "pages.xray.outbound.addOutbound" }}</a-button>
|
<a-button @click="addOutbound" :disabled="!nordModal.serverId" :loading="nordModal.confirmLoading"
|
||||||
|
type="primary">{{ i18n "pages.xray.outbound.addOutbound" }}</a-button>
|
||||||
</template>
|
</template>
|
||||||
</a-form>
|
</a-form>
|
||||||
</template>
|
</template>
|
||||||
@@ -115,7 +124,9 @@
|
|||||||
},
|
},
|
||||||
async login() {
|
async login() {
|
||||||
this.loading(true);
|
this.loading(true);
|
||||||
const msg = await HttpUtil.post('/panel/xray/nord/reg', { token: this.token });
|
const msg = await HttpUtil.post('/panel/xray/nord/reg', {
|
||||||
|
token: this.token
|
||||||
|
});
|
||||||
if (msg.success) {
|
if (msg.success) {
|
||||||
this.nordData = JSON.parse(msg.obj);
|
this.nordData = JSON.parse(msg.obj);
|
||||||
await this.fetchCountries();
|
await this.fetchCountries();
|
||||||
@@ -124,7 +135,9 @@
|
|||||||
},
|
},
|
||||||
async saveKey() {
|
async saveKey() {
|
||||||
this.loading(true);
|
this.loading(true);
|
||||||
const msg = await HttpUtil.post('/panel/xray/nord/setKey', { key: this.manualKey });
|
const msg = await HttpUtil.post('/panel/xray/nord/setKey', {
|
||||||
|
key: this.manualKey
|
||||||
|
});
|
||||||
if (msg.success) {
|
if (msg.success) {
|
||||||
this.nordData = JSON.parse(msg.obj);
|
this.nordData = JSON.parse(msg.obj);
|
||||||
await this.fetchCountries();
|
await this.fetchCountries();
|
||||||
@@ -160,7 +173,9 @@
|
|||||||
this.cities = [];
|
this.cities = [];
|
||||||
this.serverId = null;
|
this.serverId = null;
|
||||||
this.cityId = null;
|
this.cityId = null;
|
||||||
const msg = await HttpUtil.post('/panel/xray/nord/servers', { countryId: this.countryId });
|
const msg = await HttpUtil.post('/panel/xray/nord/servers', {
|
||||||
|
countryId: this.countryId
|
||||||
|
});
|
||||||
if (msg.success) {
|
if (msg.success) {
|
||||||
const data = JSON.parse(msg.obj);
|
const data = JSON.parse(msg.obj);
|
||||||
const locations = data.locations || [];
|
const locations = data.locations || [];
|
||||||
@@ -262,7 +277,8 @@
|
|||||||
},
|
},
|
||||||
delRouting() {
|
delRouting() {
|
||||||
if (app.templateSettings && app.templateSettings.routing) {
|
if (app.templateSettings && app.templateSettings.routing) {
|
||||||
app.templateSettings.routing.rules = app.templateSettings.routing.rules.filter(r => !r.outboundTag.startsWith("nord-"));
|
app.templateSettings.routing.rules = app.templateSettings.routing.rules.filter(r => !r.outboundTag
|
||||||
|
.startsWith("nord-"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -276,10 +292,14 @@
|
|||||||
methods: {
|
methods: {
|
||||||
login: () => nordModal.login(),
|
login: () => nordModal.login(),
|
||||||
saveKey: () => nordModal.saveKey(),
|
saveKey: () => nordModal.saveKey(),
|
||||||
logout() { nordModal.logout(this.nordOutboundIndex) },
|
logout() {
|
||||||
|
nordModal.logout(this.nordOutboundIndex)
|
||||||
|
},
|
||||||
fetchServers: () => nordModal.fetchServers(),
|
fetchServers: () => nordModal.fetchServers(),
|
||||||
addOutbound: () => nordModal.addOutbound(),
|
addOutbound: () => nordModal.addOutbound(),
|
||||||
resetOutbound() { nordModal.resetOutbound(this.nordOutboundIndex) },
|
resetOutbound() {
|
||||||
|
nordModal.resetOutbound(this.nordOutboundIndex)
|
||||||
|
},
|
||||||
onCityChange() {
|
onCityChange() {
|
||||||
if (this.filteredServers.length > 0) {
|
if (this.filteredServers.length > 0) {
|
||||||
this.nordModal.serverId = this.filteredServers[0].id;
|
this.nordModal.serverId = this.filteredServers[0].id;
|
||||||
@@ -290,8 +310,9 @@
|
|||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
nordOutboundIndex: {
|
nordOutboundIndex: {
|
||||||
get: function () {
|
get: function() {
|
||||||
return app.templateSettings ? app.templateSettings.outbounds.findIndex((o) => o.tag.startsWith("nord-")) : -1;
|
return app.templateSettings ? app.templateSettings.outbounds.findIndex((o) => o.tag
|
||||||
|
.startsWith("nord-")) : -1;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
filteredServers: function() {
|
filteredServers: function() {
|
||||||
|
|||||||
@@ -1,17 +1,13 @@
|
|||||||
{{define "modals/promptModal"}}
|
{{define "modals/promptModal"}}
|
||||||
<a-modal id="prompt-modal" v-model="promptModal.visible" :title="promptModal.title"
|
<a-modal id="prompt-modal" v-model="promptModal.visible" :title="promptModal.title" :closable="true"
|
||||||
:closable="true" @ok="promptModal.ok" :mask-closable="false"
|
@ok="promptModal.ok" :mask-closable="false" :confirm-loading="promptModal.confirmLoading"
|
||||||
:confirm-loading="promptModal.confirmLoading"
|
:ok-text="promptModal.okText" cancel-text='{{ i18n "cancel" }}' :class="themeSwitcher.currentTheme">
|
||||||
:ok-text="promptModal.okText" cancel-text='{{ i18n "cancel" }}' :class="themeSwitcher.currentTheme">
|
<a-input id="prompt-modal-input" :type="promptModal.type" v-model="promptModal.value"
|
||||||
<a-input id="prompt-modal-input" :type="promptModal.type"
|
:autosize="{minRows: 10, maxRows: 20}" @keydown.enter.native="promptModal.keyEnter"
|
||||||
v-model="promptModal.value"
|
@keydown.ctrl.83="promptModal.ctrlS"></a-input>
|
||||||
:autosize="{minRows: 10, maxRows: 20}"
|
|
||||||
@keydown.enter.native="promptModal.keyEnter"
|
|
||||||
@keydown.ctrl.83="promptModal.ctrlS"></a-input>
|
|
||||||
</a-modal>
|
</a-modal>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
const promptModal = {
|
const promptModal = {
|
||||||
title: '',
|
title: '',
|
||||||
type: '',
|
type: '',
|
||||||
@@ -55,7 +51,7 @@
|
|||||||
close() {
|
close() {
|
||||||
this.visible = false;
|
this.visible = false;
|
||||||
},
|
},
|
||||||
loading(loading=true) {
|
loading(loading = true) {
|
||||||
this.confirmLoading = loading;
|
this.confirmLoading = loading;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -66,6 +62,5 @@
|
|||||||
promptModal: promptModal,
|
promptModal: promptModal,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
{{end}}
|
{{end}}
|
||||||
@@ -63,16 +63,19 @@
|
|||||||
transition: all 0.3s ease-in-out;
|
transition: all 0.3s ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
.qr-transition-enter-active, .qr-transition-leave-active {
|
.qr-transition-enter-active,
|
||||||
|
.qr-transition-leave-active {
|
||||||
transition: opacity 0.3s, transform 0.3s;
|
transition: opacity 0.3s, transform 0.3s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.qr-transition-enter, .qr-transition-leave-to {
|
.qr-transition-enter,
|
||||||
|
.qr-transition-leave-to {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transform: scale(0.9);
|
transform: scale(0.9);
|
||||||
}
|
}
|
||||||
|
|
||||||
.qr-transition-enter-to, .qr-transition-leave {
|
.qr-transition-enter-to,
|
||||||
|
.qr-transition-leave {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
transform: scale(1);
|
transform: scale(1);
|
||||||
}
|
}
|
||||||
@@ -86,10 +89,12 @@
|
|||||||
opacity: 1;
|
opacity: 1;
|
||||||
transform: scale(1);
|
transform: scale(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
50% {
|
50% {
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
transform: scale(0.95);
|
transform: scale(0.95);
|
||||||
}
|
}
|
||||||
|
|
||||||
100% {
|
100% {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
transform: scale(1);
|
transform: scale(1);
|
||||||
@@ -105,7 +110,7 @@
|
|||||||
qrcodes: [],
|
qrcodes: [],
|
||||||
visible: false,
|
visible: false,
|
||||||
subId: '',
|
subId: '',
|
||||||
show: function (title = '', dbInbound, client) {
|
show: function(title = '', dbInbound, client) {
|
||||||
this.title = title;
|
this.title = title;
|
||||||
this.dbInbound = dbInbound;
|
this.dbInbound = dbInbound;
|
||||||
this.inbound = dbInbound.toInbound();
|
this.inbound = dbInbound.toInbound();
|
||||||
@@ -135,7 +140,7 @@
|
|||||||
}
|
}
|
||||||
this.visible = true;
|
this.visible = true;
|
||||||
},
|
},
|
||||||
close: function () {
|
close: function() {
|
||||||
this.visible = false;
|
this.visible = false;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -21,13 +21,13 @@
|
|||||||
fileName: '',
|
fileName: '',
|
||||||
qrcode: null,
|
qrcode: null,
|
||||||
visible: false,
|
visible: false,
|
||||||
show: function (title = '', content = '', fileName = '') {
|
show: function(title = '', content = '', fileName = '') {
|
||||||
this.title = title;
|
this.title = title;
|
||||||
this.content = content;
|
this.content = content;
|
||||||
this.fileName = fileName;
|
this.fileName = fileName;
|
||||||
this.visible = true;
|
this.visible = true;
|
||||||
},
|
},
|
||||||
copy: function (content = '') {
|
copy: function(content = '') {
|
||||||
ClipboardManager
|
ClipboardManager
|
||||||
.copyText(content)
|
.copyText(content)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
this.close();
|
this.close();
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
close: function () {
|
close: function() {
|
||||||
this.visible = false;
|
this.visible = false;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -55,12 +55,12 @@
|
|||||||
|
|
||||||
twoFactorModal.close()
|
twoFactorModal.close()
|
||||||
},
|
},
|
||||||
show: function ({
|
show: function({
|
||||||
title = '',
|
title = '',
|
||||||
description = '',
|
description = '',
|
||||||
token = '',
|
token = '',
|
||||||
type = 'set',
|
type = 'set',
|
||||||
confirm = (success) => { }
|
confirm = (success) => {}
|
||||||
}) {
|
}) {
|
||||||
this.title = title;
|
this.title = title;
|
||||||
this.description = description;
|
this.description = description;
|
||||||
@@ -78,7 +78,7 @@
|
|||||||
secret: twoFactorModal.token,
|
secret: twoFactorModal.token,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
close: function () {
|
close: function() {
|
||||||
twoFactorModal.enteredCode = "";
|
twoFactorModal.enteredCode = "";
|
||||||
twoFactorModal.visible = false;
|
twoFactorModal.visible = false;
|
||||||
},
|
},
|
||||||
@@ -91,34 +91,34 @@
|
|||||||
twoFactorModal: twoFactorModal,
|
twoFactorModal: twoFactorModal,
|
||||||
},
|
},
|
||||||
updated() {
|
updated() {
|
||||||
if (
|
if (
|
||||||
this.twoFactorModal.visible &&
|
this.twoFactorModal.visible &&
|
||||||
this.twoFactorModal.type === 'set' &&
|
this.twoFactorModal.type === 'set' &&
|
||||||
document.getElementById('twofactor-qrcode')
|
document.getElementById('twofactor-qrcode')
|
||||||
) {
|
) {
|
||||||
this.setQrCode('twofactor-qrcode', this.twoFactorModal.totpObject.toString());
|
this.setQrCode('twofactor-qrcode', this.twoFactorModal.totpObject.toString());
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
setQrCode(elementId, content) {
|
setQrCode(elementId, content) {
|
||||||
new QRious({
|
new QRious({
|
||||||
element: document.getElementById(elementId),
|
element: document.getElementById(elementId),
|
||||||
size: 200,
|
size: 200,
|
||||||
value: content,
|
value: content,
|
||||||
background: 'white',
|
background: 'white',
|
||||||
backgroundAlpha: 0,
|
backgroundAlpha: 0,
|
||||||
foreground: 'black',
|
foreground: 'black',
|
||||||
padding: 2,
|
padding: 2,
|
||||||
level: 'L'
|
level: 'L'
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
copy(content) {
|
copy(content) {
|
||||||
ClipboardManager
|
ClipboardManager
|
||||||
.copyText(content)
|
.copyText(content)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
app.$message.success('{{ i18n "copied" }}')
|
app.$message.success('{{ i18n "copied" }}')
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
{{define "modals/warpModal"}}
|
{{define "modals/warpModal"}}
|
||||||
<a-modal id="warp-modal" v-model="warpModal.visible" title="Cloudflare WARP"
|
<a-modal id="warp-modal" v-model="warpModal.visible" title="Cloudflare WARP" :confirm-loading="warpModal.confirmLoading"
|
||||||
:confirm-loading="warpModal.confirmLoading" :closable="true" :mask-closable="true"
|
:closable="true" :mask-closable="true" :footer="null" :class="themeSwitcher.currentTheme">
|
||||||
:footer="null" :class="themeSwitcher.currentTheme">
|
|
||||||
<template v-if="ObjectUtil.isEmpty(warpModal.warpData)">
|
<template v-if="ObjectUtil.isEmpty(warpModal.warpData)">
|
||||||
<a-button icon="api" @click="register" :loading="warpModal.confirmLoading">{{ i18n "create" }}</a-button>
|
<a-button icon="api" @click="register" :loading="warpModal.confirmLoading">{{ i18n "create" }}</a-button>
|
||||||
</template>
|
</template>
|
||||||
@@ -81,11 +80,13 @@
|
|||||||
<a-form :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
<a-form :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<template v-if="warpOutboundIndex>=0">
|
<template v-if="warpOutboundIndex>=0">
|
||||||
<a-tag color="green" :style="{ lineHeight: '31px' }">{{ i18n "enabled" }}</a-tag>
|
<a-tag color="green" :style="{ lineHeight: '31px' }">{{ i18n "enabled" }}</a-tag>
|
||||||
<a-button @click="resetOutbound" :loading="warpModal.confirmLoading" type="danger">{{ i18n "reset" }}</a-button>
|
<a-button @click="resetOutbound" :loading="warpModal.confirmLoading"
|
||||||
|
type="danger">{{ i18n "reset" }}</a-button>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<a-tag color="orange" :style="{ lineHeight: '31px' }">{{ i18n "disabled" }}</a-tag>
|
<a-tag color="orange" :style="{ lineHeight: '31px' }">{{ i18n "disabled" }}</a-tag>
|
||||||
<a-button @click="addOutbound" :loading="warpModal.confirmLoading" type="primary">{{ i18n "pages.xray.outbound.addOutbound" }}</a-button>
|
<a-button @click="addOutbound" :loading="warpModal.confirmLoading"
|
||||||
|
type="primary">{{ i18n "pages.xray.outbound.addOutbound" }}</a-button>
|
||||||
</template>
|
</template>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
@@ -93,7 +94,6 @@
|
|||||||
</template>
|
</template>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
const warpModal = {
|
const warpModal = {
|
||||||
visible: false,
|
visible: false,
|
||||||
confirmLoading: false,
|
confirmLoading: false,
|
||||||
@@ -188,7 +188,9 @@
|
|||||||
},
|
},
|
||||||
async updateLicense(l) {
|
async updateLicense(l) {
|
||||||
warpModal.loading(true);
|
warpModal.loading(true);
|
||||||
const msg = await HttpUtil.post('/panel/xray/warp/license', { license: l });
|
const msg = await HttpUtil.post('/panel/xray/warp/license', {
|
||||||
|
license: l
|
||||||
|
});
|
||||||
if (msg.success) {
|
if (msg.success) {
|
||||||
warpModal.warpData = JSON.parse(msg.obj);
|
warpModal.warpData = JSON.parse(msg.obj);
|
||||||
warpModal.warpConfig = null;
|
warpModal.warpConfig = null;
|
||||||
@@ -235,12 +237,12 @@
|
|||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
warpOutboundIndex: {
|
warpOutboundIndex: {
|
||||||
get: function () {
|
get: function() {
|
||||||
return app.templateSettings ? app.templateSettings.outbounds.findIndex((o) => o.tag == 'warp') : -1;
|
return app.templateSettings ? app.templateSettings.outbounds.findIndex((o) => o.tag ==
|
||||||
|
'warp') : -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
{{end}}
|
{{end}}
|
||||||
@@ -1,15 +1,7 @@
|
|||||||
{{define "modals/balancerModal"}}
|
{{define "modals/balancerModal"}}
|
||||||
<a-modal
|
<a-modal id="balancer-modal" v-model="balancerModal.visible" :title="balancerModal.title" @ok="balancerModal.ok"
|
||||||
id="balancer-modal"
|
:confirm-loading="balancerModal.confirmLoading" :ok-button-props="{ props: { disabled: !balancerModal.isValid } }"
|
||||||
v-model="balancerModal.visible"
|
:closable="true" :mask-closable="false" :ok-text="balancerModal.okText" cancel-text='{{ i18n "close" }}'
|
||||||
:title="balancerModal.title"
|
|
||||||
@ok="balancerModal.ok"
|
|
||||||
:confirm-loading="balancerModal.confirmLoading"
|
|
||||||
:ok-button-props="{ props: { disabled: !balancerModal.isValid } }"
|
|
||||||
:closable="true"
|
|
||||||
:mask-closable="false"
|
|
||||||
:ok-text="balancerModal.okText"
|
|
||||||
cancel-text='{{ i18n "close" }}'
|
|
||||||
:class="themeSwitcher.currentTheme">
|
:class="themeSwitcher.currentTheme">
|
||||||
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item label='{{ i18n "pages.xray.balancer.tag" }}' has-feedback
|
<a-form-item label='{{ i18n "pages.xray.balancer.tag" }}' has-feedback
|
||||||
@@ -35,7 +27,8 @@
|
|||||||
<a-form-item label="Fallback">
|
<a-form-item label="Fallback">
|
||||||
<a-select v-model="balancerModal.balancer.fallbackTag" clearable
|
<a-select v-model="balancerModal.balancer.fallbackTag" clearable
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option v-for="tag in [ '', ...balancerModal.outboundTags]" :value="tag">[[ tag ]]</a-select-option>
|
<a-select-option v-for="tag in [ '', ...balancerModal.outboundTags]" :value="tag">[[ tag
|
||||||
|
]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</table>
|
</table>
|
||||||
@@ -58,7 +51,7 @@
|
|||||||
fallbackTag: ''
|
fallbackTag: ''
|
||||||
},
|
},
|
||||||
outboundTags: [],
|
outboundTags: [],
|
||||||
balancerTags:[],
|
balancerTags: [],
|
||||||
ok() {
|
ok() {
|
||||||
if (balancerModal.balancer.selector.length == 0) {
|
if (balancerModal.balancer.selector.length == 0) {
|
||||||
balancerModal.emptySelector = true;
|
balancerModal.emptySelector = true;
|
||||||
@@ -67,7 +60,14 @@
|
|||||||
balancerModal.emptySelector = false;
|
balancerModal.emptySelector = false;
|
||||||
ObjectUtil.execute(balancerModal.confirm, balancerModal.balancer);
|
ObjectUtil.execute(balancerModal.confirm, balancerModal.balancer);
|
||||||
},
|
},
|
||||||
show({ title = '', okText = '{{ i18n "sure" }}', balancerTags = [], balancer, confirm = (balancer) => { }, isEdit = false }) {
|
show({
|
||||||
|
title = '',
|
||||||
|
okText = '{{ i18n "sure" }}',
|
||||||
|
balancerTags = [],
|
||||||
|
balancer,
|
||||||
|
confirm = (balancer) => {},
|
||||||
|
isEdit = false
|
||||||
|
}) {
|
||||||
this.title = title;
|
this.title = title;
|
||||||
this.okText = okText;
|
this.okText = okText;
|
||||||
this.confirm = confirm;
|
this.confirm = confirm;
|
||||||
@@ -83,7 +83,8 @@
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
this.balancerTags = balancerTags.filter((tag) => tag != balancer.tag);
|
this.balancerTags = balancerTags.filter((tag) => tag != balancer.tag);
|
||||||
this.outboundTags = app.templateSettings.outbounds.filter((o) => !ObjectUtil.isEmpty(o.tag)).map(obj => obj.tag);
|
this.outboundTags = app.templateSettings.outbounds.filter((o) => !ObjectUtil.isEmpty(o.tag)).map(obj =>
|
||||||
|
obj.tag);
|
||||||
this.isEdit = isEdit;
|
this.isEdit = isEdit;
|
||||||
this.check();
|
this.check();
|
||||||
this.checkSelector();
|
this.checkSelector();
|
||||||
@@ -92,7 +93,7 @@
|
|||||||
this.visible = false;
|
this.visible = false;
|
||||||
this.loading(false);
|
this.loading(false);
|
||||||
},
|
},
|
||||||
loading(loading=true) {
|
loading(loading = true) {
|
||||||
this.confirmLoading = loading;
|
this.confirmLoading = loading;
|
||||||
},
|
},
|
||||||
check() {
|
check() {
|
||||||
@@ -115,9 +116,7 @@
|
|||||||
data: {
|
data: {
|
||||||
balancerModal: balancerModal
|
balancerModal: balancerModal
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {}
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
{{end}}
|
{{end}}
|
||||||
@@ -75,15 +75,19 @@
|
|||||||
okText: '{{ i18n "confirm" }}',
|
okText: '{{ i18n "confirm" }}',
|
||||||
isEdit: false,
|
isEdit: false,
|
||||||
confirm: null,
|
confirm: null,
|
||||||
dnsServer: { ...defaultDnsObject },
|
dnsServer: {
|
||||||
|
...defaultDnsObject
|
||||||
|
},
|
||||||
ok() {
|
ok() {
|
||||||
ObjectUtil.execute(dnsModal.confirm, { ...dnsModal.dnsServer });
|
ObjectUtil.execute(dnsModal.confirm, {
|
||||||
|
...dnsModal.dnsServer
|
||||||
|
});
|
||||||
},
|
},
|
||||||
show({
|
show({
|
||||||
title = '',
|
title = '',
|
||||||
okText = '{{ i18n "confirm" }}',
|
okText = '{{ i18n "confirm" }}',
|
||||||
dnsServer,
|
dnsServer,
|
||||||
confirm = (dnsServer) => { },
|
confirm = (dnsServer) => {},
|
||||||
isEdit = false
|
isEdit = false
|
||||||
}) {
|
}) {
|
||||||
this.title = title;
|
this.title = title;
|
||||||
@@ -95,7 +99,9 @@
|
|||||||
if (isEdit) {
|
if (isEdit) {
|
||||||
switch (typeof dnsServer) {
|
switch (typeof dnsServer) {
|
||||||
case 'string':
|
case 'string':
|
||||||
const dnsObj = { ...defaultDnsObject };
|
const dnsObj = {
|
||||||
|
...defaultDnsObject
|
||||||
|
};
|
||||||
|
|
||||||
dnsObj.address = dnsServer;
|
dnsObj.address = dnsServer;
|
||||||
|
|
||||||
@@ -106,7 +112,9 @@
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.dnsServer = { ...defaultDnsObject };
|
this.dnsServer = {
|
||||||
|
...defaultDnsObject
|
||||||
|
};
|
||||||
|
|
||||||
this.dnsServer.domains = [];
|
this.dnsServer.domains = [];
|
||||||
this.dnsServer.expectIPs = [];
|
this.dnsServer.expectIPs = [];
|
||||||
|
|||||||
@@ -23,11 +23,19 @@
|
|||||||
okText: '{{ i18n "confirm" }}',
|
okText: '{{ i18n "confirm" }}',
|
||||||
isEdit: false,
|
isEdit: false,
|
||||||
confirm: null,
|
confirm: null,
|
||||||
fakeDns: { ...fakednsDefaultData },
|
fakeDns: {
|
||||||
|
...fakednsDefaultData
|
||||||
|
},
|
||||||
ok() {
|
ok() {
|
||||||
ObjectUtil.execute(fakednsModal.confirm, fakednsModal.fakeDns);
|
ObjectUtil.execute(fakednsModal.confirm, fakednsModal.fakeDns);
|
||||||
},
|
},
|
||||||
show({ title = '', okText = '{{ i18n "confirm" }}', fakeDns, confirm = (fakeDns) => { }, isEdit = false }) {
|
show({
|
||||||
|
title = '',
|
||||||
|
okText = '{{ i18n "confirm" }}',
|
||||||
|
fakeDns,
|
||||||
|
confirm = (fakeDns) => {},
|
||||||
|
isEdit = false
|
||||||
|
}) {
|
||||||
this.title = title;
|
this.title = title;
|
||||||
this.okText = okText;
|
this.okText = okText;
|
||||||
this.confirm = confirm;
|
this.confirm = confirm;
|
||||||
@@ -35,7 +43,9 @@
|
|||||||
if (isEdit) {
|
if (isEdit) {
|
||||||
this.fakeDns = fakeDns;
|
this.fakeDns = fakeDns;
|
||||||
} else {
|
} else {
|
||||||
this.fakeDns = { ...fakednsDefaultData }
|
this.fakeDns = {
|
||||||
|
...fakednsDefaultData
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this.isEdit = isEdit;
|
this.isEdit = isEdit;
|
||||||
},
|
},
|
||||||
@@ -51,6 +61,5 @@
|
|||||||
fakednsModal: fakednsModal,
|
fakednsModal: fakednsModal,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
{{end}}
|
{{end}}
|
||||||
@@ -1,12 +1,11 @@
|
|||||||
{{define "modals/outModal"}}
|
{{define "modals/outModal"}}
|
||||||
<a-modal id="out-modal" v-model="outModal.visible" :title="outModal.title" @ok="outModal.ok"
|
<a-modal id="out-modal" v-model="outModal.visible" :title="outModal.title" @ok="outModal.ok"
|
||||||
:confirm-loading="outModal.confirmLoading" :closable="true" :mask-closable="false"
|
:confirm-loading="outModal.confirmLoading" :closable="true" :mask-closable="false"
|
||||||
:ok-button-props="{ props: { disabled: !outModal.isValid } }" :style="{ overflow: 'hidden' }"
|
:ok-button-props="{ props: { disabled: !outModal.isValid } }" :style="{ overflow: 'hidden' }"
|
||||||
:ok-text="outModal.okText" cancel-text='{{ i18n "close" }}' :class="themeSwitcher.currentTheme">
|
:ok-text="outModal.okText" cancel-text='{{ i18n "close" }}' :class="themeSwitcher.currentTheme">
|
||||||
{{template "form/outbound"}}
|
{{template "form/outbound"}}
|
||||||
</a-modal>
|
</a-modal>
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
const outModal = {
|
const outModal = {
|
||||||
title: '',
|
title: '',
|
||||||
visible: false,
|
visible: false,
|
||||||
@@ -25,7 +24,14 @@
|
|||||||
ok() {
|
ok() {
|
||||||
ObjectUtil.execute(outModal.confirm, outModal.outbound.toJson());
|
ObjectUtil.execute(outModal.confirm, outModal.outbound.toJson());
|
||||||
},
|
},
|
||||||
show({ title='', okText='{{ i18n "sure" }}', outbound, confirm=(outbound)=>{}, isEdit=false, tags=[] }) {
|
show({
|
||||||
|
title = '',
|
||||||
|
okText = '{{ i18n "sure" }}',
|
||||||
|
outbound,
|
||||||
|
confirm = (outbound) => {},
|
||||||
|
isEdit = false,
|
||||||
|
tags = []
|
||||||
|
}) {
|
||||||
this.title = title;
|
this.title = title;
|
||||||
this.okText = okText;
|
this.okText = okText;
|
||||||
this.confirm = confirm;
|
this.confirm = confirm;
|
||||||
@@ -42,11 +48,11 @@
|
|||||||
outModal.visible = false;
|
outModal.visible = false;
|
||||||
outModal.loading(false);
|
outModal.loading(false);
|
||||||
},
|
},
|
||||||
loading(loading=true) {
|
loading(loading = true) {
|
||||||
outModal.confirmLoading = loading;
|
outModal.confirmLoading = loading;
|
||||||
},
|
},
|
||||||
check(){
|
check() {
|
||||||
if(outModal.outbound.tag == '' || outModal.tags.includes(outModal.outbound.tag)){
|
if (outModal.outbound.tag == '' || outModal.tags.includes(outModal.outbound.tag)) {
|
||||||
this.duplicateTag = true;
|
this.duplicateTag = true;
|
||||||
this.isValid = false;
|
this.isValid = false;
|
||||||
} else {
|
} else {
|
||||||
@@ -56,25 +62,25 @@
|
|||||||
},
|
},
|
||||||
toggleJson(jsonTab) {
|
toggleJson(jsonTab) {
|
||||||
textAreaObj = document.getElementById('outboundJson');
|
textAreaObj = document.getElementById('outboundJson');
|
||||||
if(jsonTab){
|
if (jsonTab) {
|
||||||
if(this.cm != null) {
|
if (this.cm != null) {
|
||||||
this.cm.toTextArea();
|
this.cm.toTextArea();
|
||||||
this.cm=null;
|
this.cm = null;
|
||||||
}
|
}
|
||||||
textAreaObj.value = JSON.stringify(this.outbound.toJson(), null, 2);
|
textAreaObj.value = JSON.stringify(this.outbound.toJson(), null, 2);
|
||||||
this.cm = CodeMirror.fromTextArea(textAreaObj, app.cmOptions);
|
this.cm = CodeMirror.fromTextArea(textAreaObj, app.cmOptions);
|
||||||
this.cm.on('change',editor => {
|
this.cm.on('change', editor => {
|
||||||
value = editor.getValue();
|
value = editor.getValue();
|
||||||
if(this.isJsonString(value)){
|
if (this.isJsonString(value)) {
|
||||||
this.outbound = Outbound.fromJson(JSON.parse(value));
|
this.outbound = Outbound.fromJson(JSON.parse(value));
|
||||||
this.check();
|
this.check();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.activeKey = '2';
|
this.activeKey = '2';
|
||||||
} else {
|
} else {
|
||||||
if(this.cm != null) {
|
if (this.cm != null) {
|
||||||
this.cm.toTextArea();
|
this.cm.toTextArea();
|
||||||
this.cm=null;
|
this.cm = null;
|
||||||
}
|
}
|
||||||
this.activeKey = '1';
|
this.activeKey = '1';
|
||||||
}
|
}
|
||||||
@@ -100,16 +106,17 @@
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
streamNetworkChange() {
|
streamNetworkChange() {
|
||||||
if (this.outModal.outbound.protocol == Protocols.VLESS && !outModal.outbound.canEnableTlsFlow()) {
|
if (this.outModal.outbound.protocol == Protocols.VLESS && !outModal.outbound
|
||||||
|
.canEnableTlsFlow()) {
|
||||||
delete this.outModal.outbound.settings.flow;
|
delete this.outModal.outbound.settings.flow;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
canEnableTls() {
|
canEnableTls() {
|
||||||
return this.outModal.outbound.canEnableTls();
|
return this.outModal.outbound.canEnableTls();
|
||||||
},
|
},
|
||||||
convertLink(){
|
convertLink() {
|
||||||
newOutbound = Outbound.fromLink(outModal.link);
|
newOutbound = Outbound.fromLink(outModal.link);
|
||||||
if(newOutbound){
|
if (newOutbound) {
|
||||||
this.outModal.outbound = newOutbound;
|
this.outModal.outbound = newOutbound;
|
||||||
this.outModal.toggleJson(true);
|
this.outModal.toggleJson(true);
|
||||||
this.outModal.check();
|
this.outModal.check();
|
||||||
@@ -122,6 +129,5 @@
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
{{end}}
|
{{end}}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
{{define "modals/reverseModal"}}
|
{{define "modals/reverseModal"}}
|
||||||
<a-modal id="reverse-modal" v-model="reverseModal.visible" :title="reverseModal.title" @ok="reverseModal.ok"
|
<a-modal id="reverse-modal" v-model="reverseModal.visible" :title="reverseModal.title" @ok="reverseModal.ok"
|
||||||
:confirm-loading="reverseModal.confirmLoading" :closable="true" :mask-closable="false"
|
:confirm-loading="reverseModal.confirmLoading" :closable="true" :mask-closable="false"
|
||||||
:ok-text="reverseModal.okText" cancel-text='{{ i18n "close" }}' :class="themeSwitcher.currentTheme">
|
:ok-text="reverseModal.okText" cancel-text='{{ i18n "close" }}' :class="themeSwitcher.currentTheme">
|
||||||
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item label='{{ i18n "pages.xray.outbound.type" }}'>
|
<a-form-item label='{{ i18n "pages.xray.outbound.type" }}'>
|
||||||
<a-select v-model="reverseModal.reverse.type" :dropdown-class-name="themeSwitcher.currentTheme">
|
<a-select v-model="reverseModal.reverse.type" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
@@ -15,26 +15,24 @@
|
|||||||
<a-input v-model.trim="reverseModal.reverse.domain"></a-input>
|
<a-input v-model.trim="reverseModal.reverse.domain"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<template v-if="reverseModal.reverse.type=='bridge'">
|
<template v-if="reverseModal.reverse.type=='bridge'">
|
||||||
<a-form-item label='{{ i18n "pages.xray.outbound.intercon" }}'>
|
<a-form-item label='{{ i18n "pages.xray.outbound.intercon" }}'>
|
||||||
<a-select v-model="reverseModal.rules[0].outboundTag" :dropdown-class-name="themeSwitcher.currentTheme">
|
<a-select v-model="reverseModal.rules[0].outboundTag" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option v-for="x in reverseModal.outboundTags" :value="x">[[ x ]]</a-select-option>
|
<a-select-option v-for="x in reverseModal.outboundTags" :value="x">[[ x ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "pages.xray.rules.outbound" }}'>
|
<a-form-item label='{{ i18n "pages.xray.rules.outbound" }}'>
|
||||||
<a-select v-model="reverseModal.rules[1].outboundTag" :dropdown-class-name="themeSwitcher.currentTheme">
|
<a-select v-model="reverseModal.rules[1].outboundTag" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option v-for="x in reverseModal.outboundTags" :value="x">[[ x ]]</a-select-option>
|
<a-select-option v-for="x in reverseModal.outboundTags" :value="x">[[ x ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<a-form-item label='{{ i18n "pages.xray.outbound.intercon" }}'>
|
<a-form-item label='{{ i18n "pages.xray.outbound.intercon" }}'>
|
||||||
<a-checkbox-group
|
<a-checkbox-group v-model="reverseModal.rules[0].inboundTag"
|
||||||
v-model="reverseModal.rules[0].inboundTag"
|
|
||||||
:options="reverseModal.inboundTags"></a-checkbox-group>
|
:options="reverseModal.inboundTags"></a-checkbox-group>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "pages.xray.rules.inbound" }}'>
|
<a-form-item label='{{ i18n "pages.xray.rules.inbound" }}'>
|
||||||
<a-checkbox-group
|
<a-checkbox-group v-model="reverseModal.rules[1].inboundTag"
|
||||||
v-model="reverseModal.rules[1].inboundTag"
|
|
||||||
:options="reverseModal.inboundTags"></a-checkbox-group>
|
:options="reverseModal.inboundTags"></a-checkbox-group>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
@@ -53,9 +51,14 @@
|
|||||||
type: "",
|
type: "",
|
||||||
domain: ""
|
domain: ""
|
||||||
},
|
},
|
||||||
rules: [
|
rules: [{
|
||||||
{ outboundTag: '', inboundTag: []},
|
outboundTag: '',
|
||||||
{ outboundTag: '', inboundTag: []}
|
inboundTag: []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
outboundTag: '',
|
||||||
|
inboundTag: []
|
||||||
|
}
|
||||||
],
|
],
|
||||||
inboundTags: [],
|
inboundTags: [],
|
||||||
outboundTags: [],
|
outboundTags: [],
|
||||||
@@ -64,7 +67,7 @@
|
|||||||
reverseModal.rules[0].type = 'field';
|
reverseModal.rules[0].type = 'field';
|
||||||
reverseModal.rules[1].type = 'field';
|
reverseModal.rules[1].type = 'field';
|
||||||
|
|
||||||
if(reverseModal.reverse.type == 'bridge'){
|
if (reverseModal.reverse.type == 'bridge') {
|
||||||
reverseModal.rules[0].inboundTag = [reverseModal.reverse.tag];
|
reverseModal.rules[0].inboundTag = [reverseModal.reverse.tag];
|
||||||
reverseModal.rules[1].inboundTag = [reverseModal.reverse.tag];
|
reverseModal.rules[1].inboundTag = [reverseModal.reverse.tag];
|
||||||
} else {
|
} else {
|
||||||
@@ -73,22 +76,36 @@
|
|||||||
}
|
}
|
||||||
ObjectUtil.execute(reverseModal.confirm, reverseModal.reverse, reverseModal.rules);
|
ObjectUtil.execute(reverseModal.confirm, reverseModal.reverse, reverseModal.rules);
|
||||||
},
|
},
|
||||||
show({ title='', okText='{{ i18n "sure" }}', reverse, rules, confirm=(reverse, rules)=>{}, isEdit=false }) {
|
show({
|
||||||
|
title = '',
|
||||||
|
okText = '{{ i18n "sure" }}',
|
||||||
|
reverse,
|
||||||
|
rules,
|
||||||
|
confirm = (reverse, rules) => {},
|
||||||
|
isEdit = false
|
||||||
|
}) {
|
||||||
this.title = title;
|
this.title = title;
|
||||||
this.okText = okText;
|
this.okText = okText;
|
||||||
this.confirm = confirm;
|
this.confirm = confirm;
|
||||||
this.visible = true;
|
this.visible = true;
|
||||||
if(isEdit) {
|
if (isEdit) {
|
||||||
this.reverse = {
|
this.reverse = {
|
||||||
tag: reverse.tag,
|
tag: reverse.tag,
|
||||||
type: reverse.type,
|
type: reverse.type,
|
||||||
domain: reverse.domain,
|
domain: reverse.domain,
|
||||||
};
|
};
|
||||||
reverse;
|
reverse;
|
||||||
rules0 = rules.filter(r => r.domain != null);
|
rules0 = rules.filter(r => r.domain != null);
|
||||||
if(rules0.length == 0) rules0 = [{ outboundTag: '', domain: ["full:" + this.reverse.domain], inboundTag: []}];
|
if (rules0.length == 0) rules0 = [{
|
||||||
|
outboundTag: '',
|
||||||
|
domain: ["full:" + this.reverse.domain],
|
||||||
|
inboundTag: []
|
||||||
|
}];
|
||||||
rules1 = rules.filter(r => r.domain == null);
|
rules1 = rules.filter(r => r.domain == null);
|
||||||
if(rules1.length == 0) rules1 = [{ outboundTag: '', inboundTag: []}];
|
if (rules1.length == 0) rules1 = [{
|
||||||
|
outboundTag: '',
|
||||||
|
inboundTag: []
|
||||||
|
}];
|
||||||
this.rules = [];
|
this.rules = [];
|
||||||
this.rules.push({
|
this.rules.push({
|
||||||
domain: rules0[0].domain,
|
domain: rules0[0].domain,
|
||||||
@@ -105,22 +122,29 @@
|
|||||||
type: "bridge",
|
type: "bridge",
|
||||||
domain: "reverse.xui"
|
domain: "reverse.xui"
|
||||||
}
|
}
|
||||||
this.rules = [
|
this.rules = [{
|
||||||
{ outboundTag: '', inboundTag: []},
|
outboundTag: '',
|
||||||
{ outboundTag: '', inboundTag: []}
|
inboundTag: []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
outboundTag: '',
|
||||||
|
inboundTag: []
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
this.isEdit = isEdit;
|
this.isEdit = isEdit;
|
||||||
this.inboundTags = app.templateSettings.inbounds.filter((i) => !ObjectUtil.isEmpty(i.tag)).map(obj => obj.tag);
|
this.inboundTags = app.templateSettings.inbounds.filter((i) => !ObjectUtil.isEmpty(i.tag)).map(obj =>
|
||||||
|
obj.tag);
|
||||||
this.inboundTags.push(...app.inboundTags);
|
this.inboundTags.push(...app.inboundTags);
|
||||||
if (app.enableDNS && !ObjectUtil.isEmpty(app.dnsTag)) this.inboundTags.push(app.dnsTag)
|
if (app.enableDNS && !ObjectUtil.isEmpty(app.dnsTag)) this.inboundTags.push(app.dnsTag)
|
||||||
this.outboundTags = app.templateSettings.outbounds.filter((o) => !ObjectUtil.isEmpty(o.tag)).map(obj => obj.tag);
|
this.outboundTags = app.templateSettings.outbounds.filter((o) => !ObjectUtil.isEmpty(o.tag)).map(obj =>
|
||||||
|
obj.tag);
|
||||||
},
|
},
|
||||||
close() {
|
close() {
|
||||||
reverseModal.visible = false;
|
reverseModal.visible = false;
|
||||||
reverseModal.loading(false);
|
reverseModal.loading(false);
|
||||||
},
|
},
|
||||||
loading(loading=true) {
|
loading(loading = true) {
|
||||||
reverseModal.confirmLoading = loading;
|
reverseModal.confirmLoading = loading;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -130,9 +154,11 @@
|
|||||||
el: '#reverse-modal',
|
el: '#reverse-modal',
|
||||||
data: {
|
data: {
|
||||||
reverseModal: reverseModal,
|
reverseModal: reverseModal,
|
||||||
reverseTypes: { bridge: '{{ i18n "pages.xray.outbound.bridge" }}', portal:'{{ i18n "pages.xray.outbound.portal" }}'},
|
reverseTypes: {
|
||||||
|
bridge: '{{ i18n "pages.xray.outbound.bridge" }}',
|
||||||
|
portal: '{{ i18n "pages.xray.outbound.portal" }}'
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
{{end}}
|
{{end}}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
{{define "modals/ruleModal"}}
|
{{define "modals/ruleModal"}}
|
||||||
<a-modal id="rule-modal" v-model="ruleModal.visible" :title="ruleModal.title" @ok="ruleModal.ok" :confirm-loading="ruleModal.confirmLoading" :closable="true" :mask-closable="false" :ok-text="ruleModal.okText" cancel-text='{{ i18n "close" }}' :class="themeSwitcher.currentTheme">
|
<a-modal id="rule-modal" v-model="ruleModal.visible" :title="ruleModal.title" @ok="ruleModal.ok"
|
||||||
|
:confirm-loading="ruleModal.confirmLoading" :closable="true" :mask-closable="false" :ok-text="ruleModal.okText"
|
||||||
|
cancel-text='{{ i18n "close" }}' :class="themeSwitcher.currentTheme">
|
||||||
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<template slot="label">
|
<template slot="label">
|
||||||
@@ -42,15 +44,19 @@
|
|||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='Attributes'>
|
<a-form-item label='Attributes'>
|
||||||
<a-button icon="plus" size="small" :style="{ marginLeft: '10px' }" @click="ruleModal.rule.attrs.push(['', ''])"></a-button>
|
<a-button icon="plus" size="small" :style="{ marginLeft: '10px' }"
|
||||||
|
@click="ruleModal.rule.attrs.push(['', ''])"></a-button>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item :wrapper-col="{span: 24}">
|
<a-form-item :wrapper-col="{span: 24}">
|
||||||
<a-input-group compact v-for="(attr,index) in ruleModal.rule.attrs">
|
<a-input-group compact v-for="(attr,index) in ruleModal.rule.attrs">
|
||||||
<a-input :style="{ width: '50%' }" v-model="attr[0]" placeholder='{{ i18n "pages.inbounds.stream.general.name" }}'>
|
<a-input :style="{ width: '50%' }" v-model="attr[0]"
|
||||||
|
placeholder='{{ i18n "pages.inbounds.stream.general.name" }}'>
|
||||||
<template slot="addonBefore" :style="{ margin: '0' }">[[ index+1 ]]</template>
|
<template slot="addonBefore" :style="{ margin: '0' }">[[ index+1 ]]</template>
|
||||||
</a-input>
|
</a-input>
|
||||||
<a-input :style="{ width: '50%' }" v-model="attr[1]" placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'>
|
<a-input :style="{ width: '50%' }" v-model="attr[1]"
|
||||||
<a-button icon="minus" slot="addonAfter" size="small" @click="ruleModal.rule.attrs.splice(index,1)"></a-button>
|
placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'>
|
||||||
|
<a-button icon="minus" slot="addonAfter" size="small"
|
||||||
|
@click="ruleModal.rule.attrs.splice(index,1)"></a-button>
|
||||||
</a-input>
|
</a-input>
|
||||||
</a-input-group>
|
</a-input-group>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
@@ -196,16 +202,20 @@
|
|||||||
this.inboundTags = app.templateSettings.inbounds.filter((i) => !ObjectUtil.isEmpty(i.tag)).map(obj => obj.tag);
|
this.inboundTags = app.templateSettings.inbounds.filter((i) => !ObjectUtil.isEmpty(i.tag)).map(obj => obj.tag);
|
||||||
this.inboundTags.push(...app.inboundTags);
|
this.inboundTags.push(...app.inboundTags);
|
||||||
if (app.enableDNS && !ObjectUtil.isEmpty(app.dnsTag)) this.inboundTags.push(app.dnsTag)
|
if (app.enableDNS && !ObjectUtil.isEmpty(app.dnsTag)) this.inboundTags.push(app.dnsTag)
|
||||||
this.outboundTags = ["", ...app.templateSettings.outbounds.filter((o) => !ObjectUtil.isEmpty(o.tag)).map(obj => obj.tag)];
|
this.outboundTags = ["", ...app.templateSettings.outbounds.filter((o) => !ObjectUtil.isEmpty(o.tag)).map(obj =>
|
||||||
|
obj.tag)];
|
||||||
if (app.templateSettings.reverse) {
|
if (app.templateSettings.reverse) {
|
||||||
if (app.templateSettings.reverse.bridges) {
|
if (app.templateSettings.reverse.bridges) {
|
||||||
this.inboundTags.push(...app.templateSettings.reverse.bridges.map(b => b.tag));
|
this.inboundTags.push(...app.templateSettings.reverse.bridges.map(b => b.tag));
|
||||||
}
|
}
|
||||||
if (app.templateSettings.reverse.portals) this.outboundTags.push(...app.templateSettings.reverse.portals.map(b => b.tag));
|
if (app.templateSettings.reverse.portals) this.outboundTags.push(...app.templateSettings.reverse.portals.map(
|
||||||
|
b => b.tag));
|
||||||
}
|
}
|
||||||
this.balancerTags = [""];
|
this.balancerTags = [""];
|
||||||
if (app.templateSettings.routing && app.templateSettings.routing.balancers) {
|
if (app.templateSettings.routing && app.templateSettings.routing.balancers) {
|
||||||
this.balancerTags = ["", ...app.templateSettings.routing.balancers.filter((o) => !ObjectUtil.isEmpty(o.tag)).map(obj => obj.tag)];
|
this.balancerTags = ["", ...app.templateSettings.routing.balancers.filter((o) => !ObjectUtil.isEmpty(o.tag))
|
||||||
|
.map(obj => obj.tag)
|
||||||
|
];
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
close() {
|
close() {
|
||||||
@@ -234,7 +244,8 @@
|
|||||||
rule.outboundTag = value.outboundTag == "" ? undefined : value.outboundTag;
|
rule.outboundTag = value.outboundTag == "" ? undefined : value.outboundTag;
|
||||||
rule.balancerTag = value.balancerTag == "" ? undefined : value.balancerTag;
|
rule.balancerTag = value.balancerTag == "" ? undefined : value.balancerTag;
|
||||||
for (const [key, value] of Object.entries(rule)) {
|
for (const [key, value] of Object.entries(rule)) {
|
||||||
if (value !== null && value !== undefined && !(Array.isArray(value) && value.length === 0) && !(typeof value === 'object' && Object.keys(value).length === 0) && value !== '') {
|
if (value !== null && value !== undefined && !(Array.isArray(value) && value.length === 0) && !(
|
||||||
|
typeof value === 'object' && Object.keys(value).length === 0) && value !== '') {
|
||||||
newRule[key] = value;
|
newRule[key] = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,7 +79,8 @@
|
|||||||
</template>
|
</template>
|
||||||
{{ template "settings/panel/subscription/general" . }}
|
{{ template "settings/panel/subscription/general" . }}
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
<a-tab-pane key="5" v-if="allSetting.subJsonEnable || allSetting.subClashEnable" :style="{ paddingTop: '20px' }">
|
<a-tab-pane key="5" v-if="allSetting.subJsonEnable || allSetting.subClashEnable"
|
||||||
|
:style="{ paddingTop: '20px' }">
|
||||||
<template #tab>
|
<template #tab>
|
||||||
<a-icon type="code"></a-icon>
|
<a-icon type="code"></a-icon>
|
||||||
<span>{{ i18n "pages.settings.subSettings" }} (Formats)</span>
|
<span>{{ i18n "pages.settings.subSettings" }} (Formats)</span>
|
||||||
@@ -124,9 +125,19 @@
|
|||||||
user: {},
|
user: {},
|
||||||
lang: LanguageManager.getLanguage(),
|
lang: LanguageManager.getLanguage(),
|
||||||
inboundOptions: [],
|
inboundOptions: [],
|
||||||
remarkModels: { i: 'Inbound', e: 'Email', o: 'Other' },
|
remarkModels: {
|
||||||
|
i: 'Inbound',
|
||||||
|
e: 'Email',
|
||||||
|
o: 'Other'
|
||||||
|
},
|
||||||
remarkSeparators: [' ', '-', '_', '@', ':', '~', '|', ',', '.', '/'],
|
remarkSeparators: [' ', '-', '_', '@', ':', '~', '|', ',', '.', '/'],
|
||||||
datepickerList: [{ name: 'Gregorian (Standard)', value: 'gregorian' }, { name: 'Jalalian (شمسی)', value: 'jalalian' }],
|
datepickerList: [{
|
||||||
|
name: 'Gregorian (Standard)',
|
||||||
|
value: 'gregorian'
|
||||||
|
}, {
|
||||||
|
name: 'Jalalian (شمسی)',
|
||||||
|
value: 'jalalian'
|
||||||
|
}],
|
||||||
remarkSample: '',
|
remarkSample: '',
|
||||||
defaultFragment: {
|
defaultFragment: {
|
||||||
packets: "tlshello",
|
packets: "tlshello",
|
||||||
@@ -134,17 +145,19 @@
|
|||||||
interval: "10-20",
|
interval: "10-20",
|
||||||
maxSplit: "300-400"
|
maxSplit: "300-400"
|
||||||
},
|
},
|
||||||
defaultNoises: [
|
defaultNoises: [{
|
||||||
{ type: "rand", packet: "10-20", delay: "10-16", applyTo: "ip" }
|
type: "rand",
|
||||||
],
|
packet: "10-20",
|
||||||
|
delay: "10-16",
|
||||||
|
applyTo: "ip"
|
||||||
|
}],
|
||||||
defaultMux: {
|
defaultMux: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
concurrency: 8,
|
concurrency: 8,
|
||||||
xudpConcurrency: 16,
|
xudpConcurrency: 16,
|
||||||
xudpProxyUDP443: "reject"
|
xudpProxyUDP443: "reject"
|
||||||
},
|
},
|
||||||
defaultRules: [
|
defaultRules: [{
|
||||||
{
|
|
||||||
type: "field",
|
type: "field",
|
||||||
outboundTag: "direct",
|
outboundTag: "direct",
|
||||||
domain: [
|
domain: [
|
||||||
@@ -160,26 +173,75 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
directIPsOptions: [
|
directIPsOptions: [{
|
||||||
{ label: 'Private IP', value: 'geoip:private' },
|
label: 'Private IP',
|
||||||
{ label: '🇮🇷 Iran', value: 'geoip:ir' },
|
value: 'geoip:private'
|
||||||
{ label: '🇨🇳 China', value: 'geoip:cn' },
|
},
|
||||||
{ label: '🇷🇺 Russia', value: 'geoip:ru' },
|
{
|
||||||
{ label: '🇻🇳 Vietnam', value: 'geoip:vn' },
|
label: '🇮🇷 Iran',
|
||||||
{ label: '🇪🇸 Spain', value: 'geoip:es' },
|
value: 'geoip:ir'
|
||||||
{ label: '🇮🇩 Indonesia', value: 'geoip:id' },
|
},
|
||||||
{ label: '🇺🇦 Ukraine', value: 'geoip:ua' },
|
{
|
||||||
{ label: '🇹🇷 Türkiye', value: 'geoip:tr' },
|
label: '🇨🇳 China',
|
||||||
{ label: '🇧🇷 Brazil', value: 'geoip:br' },
|
value: 'geoip:cn'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '🇷🇺 Russia',
|
||||||
|
value: 'geoip:ru'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '🇻🇳 Vietnam',
|
||||||
|
value: 'geoip:vn'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '🇪🇸 Spain',
|
||||||
|
value: 'geoip:es'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '🇮🇩 Indonesia',
|
||||||
|
value: 'geoip:id'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '🇺🇦 Ukraine',
|
||||||
|
value: 'geoip:ua'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '🇹🇷 Türkiye',
|
||||||
|
value: 'geoip:tr'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '🇧🇷 Brazil',
|
||||||
|
value: 'geoip:br'
|
||||||
|
},
|
||||||
],
|
],
|
||||||
diretDomainsOptions: [
|
diretDomainsOptions: [{
|
||||||
{ label: 'Private DNS', value: 'geosite:private' },
|
label: 'Private DNS',
|
||||||
{ label: '🇮🇷 Iran', value: 'geosite:category-ir' },
|
value: 'geosite:private'
|
||||||
{ label: '🇨🇳 China', value: 'geosite:cn' },
|
},
|
||||||
{ label: '🇷🇺 Russia', value: 'geosite:category-ru' },
|
{
|
||||||
{ label: 'Apple', value: 'geosite:apple' },
|
label: '🇮🇷 Iran',
|
||||||
{ label: 'Meta', value: 'geosite:meta' },
|
value: 'geosite:category-ir'
|
||||||
{ label: 'Google', value: 'geosite:google' },
|
},
|
||||||
|
{
|
||||||
|
label: '🇨🇳 China',
|
||||||
|
value: 'geosite:cn'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '🇷🇺 Russia',
|
||||||
|
value: 'geosite:category-ru'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Apple',
|
||||||
|
value: 'geosite:apple'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Meta',
|
||||||
|
value: 'geosite:meta'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Google',
|
||||||
|
value: 'geosite:google'
|
||||||
|
},
|
||||||
],
|
],
|
||||||
get remarkModel() {
|
get remarkModel() {
|
||||||
rm = this.allSetting.remarkModel;
|
rm = this.allSetting.remarkModel;
|
||||||
@@ -317,7 +379,13 @@
|
|||||||
this.loading(true);
|
this.loading(true);
|
||||||
await PromiseUtil.sleep(5000);
|
await PromiseUtil.sleep(5000);
|
||||||
|
|
||||||
const { webDomain, webPort, webBasePath, webCertFile, webKeyFile } = this.allSetting;
|
const {
|
||||||
|
webDomain,
|
||||||
|
webPort,
|
||||||
|
webBasePath,
|
||||||
|
webCertFile,
|
||||||
|
webKeyFile
|
||||||
|
} = this.allSetting;
|
||||||
const newProtocol = (webCertFile || webKeyFile) ? "https:" : "http:";
|
const newProtocol = (webCertFile || webKeyFile) ? "https:" : "http:";
|
||||||
|
|
||||||
let base = webBasePath ? webBasePath.replace(/^\//, "") : "";
|
let base = webBasePath ? webBasePath.replace(/^\//, "") : "";
|
||||||
@@ -358,7 +426,8 @@
|
|||||||
type: 'set',
|
type: 'set',
|
||||||
confirm: (success) => {
|
confirm: (success) => {
|
||||||
if (success) {
|
if (success) {
|
||||||
Vue.prototype.$message['success']('{{ i18n "pages.settings.security.twoFactorModalSetSuccess" }}')
|
Vue.prototype.$message['success'](
|
||||||
|
'{{ i18n "pages.settings.security.twoFactorModalSetSuccess" }}')
|
||||||
|
|
||||||
this.allSetting.twoFactorToken = newTwoFactorToken
|
this.allSetting.twoFactorToken = newTwoFactorToken
|
||||||
}
|
}
|
||||||
@@ -374,7 +443,8 @@
|
|||||||
type: 'confirm',
|
type: 'confirm',
|
||||||
confirm: (success) => {
|
confirm: (success) => {
|
||||||
if (success) {
|
if (success) {
|
||||||
Vue.prototype.$message['success']('{{ i18n "pages.settings.security.twoFactorModalDeleteSuccess" }}')
|
Vue.prototype.$message['success'](
|
||||||
|
'{{ i18n "pages.settings.security.twoFactorModalDeleteSuccess" }}')
|
||||||
|
|
||||||
this.allSetting.twoFactorEnable = false
|
this.allSetting.twoFactorEnable = false
|
||||||
this.allSetting.twoFactorToken = ""
|
this.allSetting.twoFactorToken = ""
|
||||||
@@ -384,7 +454,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
addNoise() {
|
addNoise() {
|
||||||
const newNoise = { type: "rand", packet: "10-20", delay: "10-16", applyTo: "ip" };
|
const newNoise = {
|
||||||
|
type: "rand",
|
||||||
|
packet: "10-20",
|
||||||
|
delay: "10-16",
|
||||||
|
applyTo: "ip"
|
||||||
|
};
|
||||||
this.noisesArray = [...this.noisesArray, newNoise];
|
this.noisesArray = [...this.noisesArray, newNoise];
|
||||||
},
|
},
|
||||||
removeNoise(index) {
|
removeNoise(index) {
|
||||||
@@ -394,44 +469,60 @@
|
|||||||
},
|
},
|
||||||
updateNoiseType(index, value) {
|
updateNoiseType(index, value) {
|
||||||
const updatedNoises = [...this.noisesArray];
|
const updatedNoises = [...this.noisesArray];
|
||||||
updatedNoises[index] = { ...updatedNoises[index], type: value };
|
updatedNoises[index] = {
|
||||||
|
...updatedNoises[index],
|
||||||
|
type: value
|
||||||
|
};
|
||||||
this.noisesArray = updatedNoises;
|
this.noisesArray = updatedNoises;
|
||||||
},
|
},
|
||||||
updateNoisePacket(index, value) {
|
updateNoisePacket(index, value) {
|
||||||
const updatedNoises = [...this.noisesArray];
|
const updatedNoises = [...this.noisesArray];
|
||||||
updatedNoises[index] = { ...updatedNoises[index], packet: value };
|
updatedNoises[index] = {
|
||||||
|
...updatedNoises[index],
|
||||||
|
packet: value
|
||||||
|
};
|
||||||
this.noisesArray = updatedNoises;
|
this.noisesArray = updatedNoises;
|
||||||
},
|
},
|
||||||
updateNoiseDelay(index, value) {
|
updateNoiseDelay(index, value) {
|
||||||
const updatedNoises = [...this.noisesArray];
|
const updatedNoises = [...this.noisesArray];
|
||||||
updatedNoises[index] = { ...updatedNoises[index], delay: value };
|
updatedNoises[index] = {
|
||||||
|
...updatedNoises[index],
|
||||||
|
delay: value
|
||||||
|
};
|
||||||
this.noisesArray = updatedNoises;
|
this.noisesArray = updatedNoises;
|
||||||
},
|
},
|
||||||
updateNoiseApplyTo(index, value) {
|
updateNoiseApplyTo(index, value) {
|
||||||
const updatedNoises = [...this.noisesArray];
|
const updatedNoises = [...this.noisesArray];
|
||||||
updatedNoises[index] = { ...updatedNoises[index], applyTo: value };
|
updatedNoises[index] = {
|
||||||
|
...updatedNoises[index],
|
||||||
|
applyTo: value
|
||||||
|
};
|
||||||
this.noisesArray = updatedNoises;
|
this.noisesArray = updatedNoises;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
ldapInboundTagList: {
|
ldapInboundTagList: {
|
||||||
get: function () {
|
get: function() {
|
||||||
const csv = this.allSetting.ldapInboundTags || "";
|
const csv = this.allSetting.ldapInboundTags || "";
|
||||||
return csv.length ? csv.split(',').map(s => s.trim()).filter(Boolean) : [];
|
return csv.length ? csv.split(',').map(s => s.trim()).filter(Boolean) : [];
|
||||||
},
|
},
|
||||||
set: function (list) {
|
set: function(list) {
|
||||||
this.allSetting.ldapInboundTags = Array.isArray(list) ? list.join(',') : '';
|
this.allSetting.ldapInboundTags = Array.isArray(list) ? list.join(',') : '';
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
fragment: {
|
fragment: {
|
||||||
get: function () { return this.allSetting?.subJsonFragment != ""; },
|
get: function() {
|
||||||
set: function (v) {
|
return this.allSetting?.subJsonFragment != "";
|
||||||
|
},
|
||||||
|
set: function(v) {
|
||||||
this.allSetting.subJsonFragment = v ? JSON.stringify(this.defaultFragment) : "";
|
this.allSetting.subJsonFragment = v ? JSON.stringify(this.defaultFragment) : "";
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
fragmentPackets: {
|
fragmentPackets: {
|
||||||
get: function () { return this.fragment ? JSON.parse(this.allSetting.subJsonFragment).packets : ""; },
|
get: function() {
|
||||||
set: function (v) {
|
return this.fragment ? JSON.parse(this.allSetting.subJsonFragment).packets : "";
|
||||||
|
},
|
||||||
|
set: function(v) {
|
||||||
if (v != "") {
|
if (v != "") {
|
||||||
newFragment = JSON.parse(this.allSetting.subJsonFragment);
|
newFragment = JSON.parse(this.allSetting.subJsonFragment);
|
||||||
newFragment.packets = v;
|
newFragment.packets = v;
|
||||||
@@ -440,8 +531,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
fragmentLength: {
|
fragmentLength: {
|
||||||
get: function () { return this.fragment ? JSON.parse(this.allSetting.subJsonFragment).length : ""; },
|
get: function() {
|
||||||
set: function (v) {
|
return this.fragment ? JSON.parse(this.allSetting.subJsonFragment).length : "";
|
||||||
|
},
|
||||||
|
set: function(v) {
|
||||||
if (v != "") {
|
if (v != "") {
|
||||||
newFragment = JSON.parse(this.allSetting.subJsonFragment);
|
newFragment = JSON.parse(this.allSetting.subJsonFragment);
|
||||||
newFragment.length = v;
|
newFragment.length = v;
|
||||||
@@ -450,8 +543,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
fragmentInterval: {
|
fragmentInterval: {
|
||||||
get: function () { return this.fragment ? JSON.parse(this.allSetting.subJsonFragment).interval : ""; },
|
get: function() {
|
||||||
set: function (v) {
|
return this.fragment ? JSON.parse(this.allSetting.subJsonFragment).interval : "";
|
||||||
|
},
|
||||||
|
set: function(v) {
|
||||||
if (v != "") {
|
if (v != "") {
|
||||||
newFragment = JSON.parse(this.allSetting.subJsonFragment);
|
newFragment = JSON.parse(this.allSetting.subJsonFragment);
|
||||||
newFragment.interval = v;
|
newFragment.interval = v;
|
||||||
@@ -460,8 +555,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
fragmentMaxSplit: {
|
fragmentMaxSplit: {
|
||||||
get: function () { return this.fragment ? JSON.parse(this.allSetting.subJsonFragment).maxSplit : ""; },
|
get: function() {
|
||||||
set: function (v) {
|
return this.fragment ? JSON.parse(this.allSetting.subJsonFragment).maxSplit : "";
|
||||||
|
},
|
||||||
|
set: function(v) {
|
||||||
if (v != "") {
|
if (v != "") {
|
||||||
newFragment = JSON.parse(this.allSetting.subJsonFragment);
|
newFragment = JSON.parse(this.allSetting.subJsonFragment);
|
||||||
newFragment.maxSplit = v;
|
newFragment.maxSplit = v;
|
||||||
@@ -492,50 +589,60 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
enableMux: {
|
enableMux: {
|
||||||
get: function () { return this.allSetting?.subJsonMux != ""; },
|
get: function() {
|
||||||
set: function (v) {
|
return this.allSetting?.subJsonMux != "";
|
||||||
|
},
|
||||||
|
set: function(v) {
|
||||||
this.allSetting.subJsonMux = v ? JSON.stringify(this.defaultMux) : "";
|
this.allSetting.subJsonMux = v ? JSON.stringify(this.defaultMux) : "";
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
muxConcurrency: {
|
muxConcurrency: {
|
||||||
get: function () { return this.enableMux ? JSON.parse(this.allSetting.subJsonMux).concurrency : -1; },
|
get: function() {
|
||||||
set: function (v) {
|
return this.enableMux ? JSON.parse(this.allSetting.subJsonMux).concurrency : -1;
|
||||||
|
},
|
||||||
|
set: function(v) {
|
||||||
newMux = JSON.parse(this.allSetting.subJsonMux);
|
newMux = JSON.parse(this.allSetting.subJsonMux);
|
||||||
newMux.concurrency = v;
|
newMux.concurrency = v;
|
||||||
this.allSetting.subJsonMux = JSON.stringify(newMux);
|
this.allSetting.subJsonMux = JSON.stringify(newMux);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
muxXudpConcurrency: {
|
muxXudpConcurrency: {
|
||||||
get: function () { return this.enableMux ? JSON.parse(this.allSetting.subJsonMux).xudpConcurrency : -1; },
|
get: function() {
|
||||||
set: function (v) {
|
return this.enableMux ? JSON.parse(this.allSetting.subJsonMux).xudpConcurrency : -1;
|
||||||
|
},
|
||||||
|
set: function(v) {
|
||||||
newMux = JSON.parse(this.allSetting.subJsonMux);
|
newMux = JSON.parse(this.allSetting.subJsonMux);
|
||||||
newMux.xudpConcurrency = v;
|
newMux.xudpConcurrency = v;
|
||||||
this.allSetting.subJsonMux = JSON.stringify(newMux);
|
this.allSetting.subJsonMux = JSON.stringify(newMux);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
muxXudpProxyUDP443: {
|
muxXudpProxyUDP443: {
|
||||||
get: function () { return this.enableMux ? JSON.parse(this.allSetting.subJsonMux).xudpProxyUDP443 : "reject"; },
|
get: function() {
|
||||||
set: function (v) {
|
return this.enableMux ? JSON.parse(this.allSetting.subJsonMux).xudpProxyUDP443 : "reject";
|
||||||
|
},
|
||||||
|
set: function(v) {
|
||||||
newMux = JSON.parse(this.allSetting.subJsonMux);
|
newMux = JSON.parse(this.allSetting.subJsonMux);
|
||||||
newMux.xudpProxyUDP443 = v;
|
newMux.xudpProxyUDP443 = v;
|
||||||
this.allSetting.subJsonMux = JSON.stringify(newMux);
|
this.allSetting.subJsonMux = JSON.stringify(newMux);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
enableDirect: {
|
enableDirect: {
|
||||||
get: function () { return this.allSetting?.subJsonRules != ""; },
|
get: function() {
|
||||||
set: function (v) {
|
return this.allSetting?.subJsonRules != "";
|
||||||
|
},
|
||||||
|
set: function(v) {
|
||||||
this.allSetting.subJsonRules = v ? JSON.stringify(this.defaultRules) : "";
|
this.allSetting.subJsonRules = v ? JSON.stringify(this.defaultRules) : "";
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
directIPs: {
|
directIPs: {
|
||||||
get: function () {
|
get: function() {
|
||||||
if (!this.enableDirect) return [];
|
if (!this.enableDirect) return [];
|
||||||
const rules = JSON.parse(this.allSetting.subJsonRules);
|
const rules = JSON.parse(this.allSetting.subJsonRules);
|
||||||
if (!Array.isArray(rules)) return [];
|
if (!Array.isArray(rules)) return [];
|
||||||
const ipRule = rules.find(r => r.ip);
|
const ipRule = rules.find(r => r.ip);
|
||||||
return ipRule?.ip ?? [];
|
return ipRule?.ip ?? [];
|
||||||
},
|
},
|
||||||
set: function (v) {
|
set: function(v) {
|
||||||
let rules = JSON.parse(this.allSetting.subJsonRules);
|
let rules = JSON.parse(this.allSetting.subJsonRules);
|
||||||
if (!Array.isArray(rules)) return;
|
if (!Array.isArray(rules)) return;
|
||||||
|
|
||||||
@@ -554,14 +661,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
directDomains: {
|
directDomains: {
|
||||||
get: function () {
|
get: function() {
|
||||||
if (!this.enableDirect) return [];
|
if (!this.enableDirect) return [];
|
||||||
const rules = JSON.parse(this.allSetting.subJsonRules);
|
const rules = JSON.parse(this.allSetting.subJsonRules);
|
||||||
if (!Array.isArray(rules)) return [];
|
if (!Array.isArray(rules)) return [];
|
||||||
const domainRule = rules.find(r => r.domain);
|
const domainRule = rules.find(r => r.domain);
|
||||||
return domainRule?.domain ?? [];
|
return domainRule?.domain ?? [];
|
||||||
},
|
},
|
||||||
set: function (v) {
|
set: function(v) {
|
||||||
let rules = JSON.parse(this.allSetting.subJsonRules);
|
let rules = JSON.parse(this.allSetting.subJsonRules);
|
||||||
if (!Array.isArray(rules)) return;
|
if (!Array.isArray(rules)) return;
|
||||||
if (v.length == 0) {
|
if (v.length == 0) {
|
||||||
@@ -576,7 +683,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
confAlerts: {
|
confAlerts: {
|
||||||
get: function () {
|
get: function() {
|
||||||
if (!this.allSetting) return [];
|
if (!this.allSetting) return [];
|
||||||
var alerts = []
|
var alerts = []
|
||||||
if (window.location.protocol !== "https:") alerts.push('{{ i18n "secAlertSSL" }}');
|
if (window.location.protocol !== "https:") alerts.push('{{ i18n "secAlertSSL" }}');
|
||||||
@@ -584,11 +691,13 @@
|
|||||||
panelPath = window.location.pathname.split('/').length < 4
|
panelPath = window.location.pathname.split('/').length < 4
|
||||||
if (panelPath && this.allSetting.webBasePath == '/') alerts.push('{{ i18n "secAlertPanelURI" }}');
|
if (panelPath && this.allSetting.webBasePath == '/') alerts.push('{{ i18n "secAlertPanelURI" }}');
|
||||||
if (this.allSetting.subEnable) {
|
if (this.allSetting.subEnable) {
|
||||||
subPath = this.allSetting.subURI.length > 0 ? new URL(this.allSetting.subURI).pathname : this.allSetting.subPath;
|
subPath = this.allSetting.subURI.length > 0 ? new URL(this.allSetting.subURI).pathname : this
|
||||||
|
.allSetting.subPath;
|
||||||
if (subPath == '/sub/') alerts.push('{{ i18n "secAlertSubURI" }}');
|
if (subPath == '/sub/') alerts.push('{{ i18n "secAlertSubURI" }}');
|
||||||
}
|
}
|
||||||
if (this.allSetting.subJsonEnable) {
|
if (this.allSetting.subJsonEnable) {
|
||||||
subJsonPath = this.allSetting.subJsonURI.length > 0 ? new URL(this.allSetting.subJsonURI).pathname : this.allSetting.subJsonPath;
|
subJsonPath = this.allSetting.subJsonURI.length > 0 ? new URL(this.allSetting.subJsonURI).pathname :
|
||||||
|
this.allSetting.subJsonPath;
|
||||||
if (subJsonPath == '/json/') alerts.push('{{ i18n "secAlertSubJsonURI" }}');
|
if (subJsonPath == '/json/') alerts.push('{{ i18n "secAlertSubJsonURI" }}');
|
||||||
}
|
}
|
||||||
return alerts
|
return alerts
|
||||||
|
|||||||
@@ -162,7 +162,8 @@
|
|||||||
<a-setting-list-item paddings="small">
|
<a-setting-list-item paddings="small">
|
||||||
<template #title>LDAP Port</template>
|
<template #title>LDAP Port</template>
|
||||||
<template #control>
|
<template #control>
|
||||||
<a-input-number :min="1" :max="65535" v-model="allSetting.ldapPort" :style="{ width: '100%' }"></a-input-number>
|
<a-input-number :min="1" :max="65535" v-model="allSetting.ldapPort"
|
||||||
|
:style="{ width: '100%' }"></a-input-number>
|
||||||
</template>
|
</template>
|
||||||
</a-setting-list-item>
|
</a-setting-list-item>
|
||||||
<a-setting-list-item paddings="small">
|
<a-setting-list-item paddings="small">
|
||||||
@@ -239,10 +240,13 @@
|
|||||||
<template #title>Inbound tags</template>
|
<template #title>Inbound tags</template>
|
||||||
<template #description>Select inbounds to manage (auto create/delete)</template>
|
<template #description>Select inbounds to manage (auto create/delete)</template>
|
||||||
<template #control>
|
<template #control>
|
||||||
<a-select mode="multiple" :dropdown-class-name="themeSwitcher.currentTheme" :style="{ width: '100%' }" v-model="ldapInboundTagList">
|
<a-select mode="multiple" :dropdown-class-name="themeSwitcher.currentTheme" :style="{ width: '100%' }"
|
||||||
<a-select-option v-for="opt in inboundOptions" :key="opt.value" :value="opt.value">[[ opt.label ]]</a-select-option>
|
v-model="ldapInboundTagList">
|
||||||
|
<a-select-option v-for="opt in inboundOptions" :key="opt.value" :value="opt.value">[[ opt.label
|
||||||
|
]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
<div v-if="inboundOptions.length==0" style="margin-top:6px;color:#999">No inbounds found. Please create one in Inbounds.</div>
|
<div v-if="inboundOptions.length==0" style="margin-top:6px;color:#999">No inbounds found. Please create
|
||||||
|
one in Inbounds.</div>
|
||||||
</template>
|
</template>
|
||||||
</a-setting-list-item>
|
</a-setting-list-item>
|
||||||
<a-setting-list-item paddings="small">
|
<a-setting-list-item paddings="small">
|
||||||
@@ -260,19 +264,22 @@
|
|||||||
<a-setting-list-item paddings="small">
|
<a-setting-list-item paddings="small">
|
||||||
<template #title>Default total (GB)</template>
|
<template #title>Default total (GB)</template>
|
||||||
<template #control>
|
<template #control>
|
||||||
<a-input-number :min="0" v-model="allSetting.ldapDefaultTotalGB" :style="{ width: '100%' }"></a-input-number>
|
<a-input-number :min="0" v-model="allSetting.ldapDefaultTotalGB"
|
||||||
|
:style="{ width: '100%' }"></a-input-number>
|
||||||
</template>
|
</template>
|
||||||
</a-setting-list-item>
|
</a-setting-list-item>
|
||||||
<a-setting-list-item paddings="small">
|
<a-setting-list-item paddings="small">
|
||||||
<template #title>Default expiry (days)</template>
|
<template #title>Default expiry (days)</template>
|
||||||
<template #control>
|
<template #control>
|
||||||
<a-input-number :min="0" v-model="allSetting.ldapDefaultExpiryDays" :style="{ width: '100%' }"></a-input-number>
|
<a-input-number :min="0" v-model="allSetting.ldapDefaultExpiryDays"
|
||||||
|
:style="{ width: '100%' }"></a-input-number>
|
||||||
</template>
|
</template>
|
||||||
</a-setting-list-item>
|
</a-setting-list-item>
|
||||||
<a-setting-list-item paddings="small">
|
<a-setting-list-item paddings="small">
|
||||||
<template #title>Default Limit IP</template>
|
<template #title>Default Limit IP</template>
|
||||||
<template #control>
|
<template #control>
|
||||||
<a-input-number :min="0" v-model="allSetting.ldapDefaultLimitIP" :style="{ width: '100%' }"></a-input-number>
|
<a-input-number :min="0" v-model="allSetting.ldapDefaultLimitIP"
|
||||||
|
:style="{ width: '100%' }"></a-input-number>
|
||||||
</template>
|
</template>
|
||||||
</a-setting-list-item>
|
</a-setting-list-item>
|
||||||
</a-collapse-panel>
|
</a-collapse-panel>
|
||||||
|
|||||||
@@ -46,8 +46,7 @@
|
|||||||
<template #description>{{ i18n
|
<template #description>{{ i18n
|
||||||
"pages.settings.subPortDesc"}}</template>
|
"pages.settings.subPortDesc"}}</template>
|
||||||
<template #control>
|
<template #control>
|
||||||
<a-input-number v-model="allSetting.subPort" :min="1"
|
<a-input-number v-model="allSetting.subPort" :min="1" :min="65535"
|
||||||
:min="65535"
|
|
||||||
:style="{ width: '100%' }"></a-input-number>
|
:style="{ width: '100%' }"></a-input-number>
|
||||||
</template>
|
</template>
|
||||||
</a-setting-list-item>
|
</a-setting-list-item>
|
||||||
@@ -67,8 +66,7 @@
|
|||||||
<template #description>{{ i18n
|
<template #description>{{ i18n
|
||||||
"pages.settings.subURIDesc"}}</template>
|
"pages.settings.subURIDesc"}}</template>
|
||||||
<template #control>
|
<template #control>
|
||||||
<a-input type="text"
|
<a-input type="text" placeholder="(http|https)://domain[:port]/path/"
|
||||||
placeholder="(http|https)://domain[:port]/path/"
|
|
||||||
v-model="allSetting.subURI"></a-input>
|
v-model="allSetting.subURI"></a-input>
|
||||||
</template>
|
</template>
|
||||||
</a-setting-list-item>
|
</a-setting-list-item>
|
||||||
@@ -104,8 +102,7 @@
|
|||||||
<template #description>{{ i18n
|
<template #description>{{ i18n
|
||||||
"pages.settings.subSupportUrlDesc"}}</template>
|
"pages.settings.subSupportUrlDesc"}}</template>
|
||||||
<template #control>
|
<template #control>
|
||||||
<a-input type="text" v-model="allSetting.subSupportUrl"
|
<a-input type="text" v-model="allSetting.subSupportUrl" placeholder="https://example.com"></a-input>
|
||||||
placeholder="https://example.com"></a-input>
|
|
||||||
</template>
|
</template>
|
||||||
</a-setting-list-item>
|
</a-setting-list-item>
|
||||||
<a-setting-list-item paddings="small">
|
<a-setting-list-item paddings="small">
|
||||||
@@ -113,8 +110,7 @@
|
|||||||
<template #description>{{ i18n
|
<template #description>{{ i18n
|
||||||
"pages.settings.subProfileUrlDesc"}}</template>
|
"pages.settings.subProfileUrlDesc"}}</template>
|
||||||
<template #control>
|
<template #control>
|
||||||
<a-input type="text" v-model="allSetting.subProfileUrl"
|
<a-input type="text" v-model="allSetting.subProfileUrl" placeholder="https://example.com"></a-input>
|
||||||
placeholder="https://example.com"></a-input>
|
|
||||||
</template>
|
</template>
|
||||||
</a-setting-list-item>
|
</a-setting-list-item>
|
||||||
<a-setting-list-item paddings="small">
|
<a-setting-list-item paddings="small">
|
||||||
@@ -141,8 +137,7 @@
|
|||||||
<template #description>{{ i18n
|
<template #description>{{ i18n
|
||||||
"pages.settings.subRoutingRulesDesc"}}</template>
|
"pages.settings.subRoutingRulesDesc"}}</template>
|
||||||
<template #control>
|
<template #control>
|
||||||
<a-textarea v-model="allSetting.subRoutingRules"
|
<a-textarea v-model="allSetting.subRoutingRules" placeholder="happ://routing/add/..."></a-textarea>
|
||||||
placeholder="happ://routing/add/..."></a-textarea>
|
|
||||||
</template>
|
</template>
|
||||||
</a-setting-list-item>
|
</a-setting-list-item>
|
||||||
</a-collapse-panel>
|
</a-collapse-panel>
|
||||||
@@ -170,8 +165,7 @@
|
|||||||
<template #description>{{ i18n
|
<template #description>{{ i18n
|
||||||
"pages.settings.subUpdatesDesc"}}</template>
|
"pages.settings.subUpdatesDesc"}}</template>
|
||||||
<template #control>
|
<template #control>
|
||||||
<a-input-number :min="1" v-model="allSetting.subUpdates"
|
<a-input-number :min="1" v-model="allSetting.subUpdates" :style="{ width: '100%' }"></a-input-number>
|
||||||
:style="{ width: '100%' }"></a-input-number>
|
|
||||||
</template>
|
</template>
|
||||||
</a-setting-list-item>
|
</a-setting-list-item>
|
||||||
</a-collapse-panel>
|
</a-collapse-panel>
|
||||||
|
|||||||
@@ -100,7 +100,8 @@
|
|||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-space direction="vertical" align="center">
|
<a-space direction="vertical" align="center">
|
||||||
<a-row type="flex" :gutter="[8,8]" justify="center" style="width:100%">
|
<a-row type="flex" :gutter="[8,8]" justify="center" style="width:100%">
|
||||||
<a-col :xs="24" :sm="app.subJsonUrl || app.subClashUrl ? 12 : 24" style="text-align:center;">
|
<a-col :xs="24" :sm="app.subJsonUrl || app.subClashUrl ? 12 : 24"
|
||||||
|
style="text-align:center;">
|
||||||
<tr-qr-box class="qr-box">
|
<tr-qr-box class="qr-box">
|
||||||
<a-tag color="purple" class="qr-tag">
|
<a-tag color="purple" class="qr-tag">
|
||||||
<span>{{ i18n
|
<span>{{ i18n
|
||||||
@@ -270,11 +271,11 @@
|
|||||||
</a-layout>
|
</a-layout>
|
||||||
|
|
||||||
<!-- Bootstrap data for external JS -->
|
<!-- Bootstrap data for external JS -->
|
||||||
<template id="subscription-data" data-sid="{{ .sId }}" data-sub-url="{{ .subUrl }}" data-subjson-url="{{ .subJsonUrl }}" data-subclash-url="{{ .subClashUrl }}"
|
<template id="subscription-data" data-sid="{{ .sId }}" data-sub-url="{{ .subUrl }}" data-subjson-url="{{ .subJsonUrl }}"
|
||||||
data-download="{{ .download }}" data-upload="{{ .upload }}" data-used="{{ .used }}" data-total="{{ .total }}"
|
data-subclash-url="{{ .subClashUrl }}" data-download="{{ .download }}" data-upload="{{ .upload }}"
|
||||||
data-remained="{{ .remained }}" data-expire="{{ .expire }}" data-lastonline="{{ .lastOnline }}"
|
data-used="{{ .used }}" data-total="{{ .total }}" data-remained="{{ .remained }}" data-expire="{{ .expire }}"
|
||||||
data-downloadbyte="{{ .downloadByte }}" data-uploadbyte="{{ .uploadByte }}" data-totalbyte="{{ .totalByte }}"
|
data-lastonline="{{ .lastOnline }}" data-downloadbyte="{{ .downloadByte }}" data-uploadbyte="{{ .uploadByte }}"
|
||||||
data-datepicker="{{ .datepicker }}"></template>
|
data-totalbyte="{{ .totalByte }}" data-datepicker="{{ .datepicker }}"></template>
|
||||||
<textarea id="subscription-links" style="display:none">{{ range .result }}{{ . }}
|
<textarea id="subscription-links" style="display:none">{{ range .result }}{{ . }}
|
||||||
{{ end }}</textarea>
|
{{ end }}</textarea>
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,8 @@
|
|||||||
<span>{{ i18n "pages.xray.balancer.addBalancer"}}</span>
|
<span>{{ i18n "pages.xray.balancer.addBalancer"}}</span>
|
||||||
</a-button>
|
</a-button>
|
||||||
<a-table :columns="balancerColumns" bordered :row-key="r => r.key" :data-source="balancersData"
|
<a-table :columns="balancerColumns" bordered :row-key="r => r.key" :data-source="balancersData"
|
||||||
:scroll="isMobile ? {} : { x: 200 }" :pagination="false" :indent-size="0" :locale='{ filterConfirm: `{{ i18n "confirm" }}`, filterReset: `{{ i18n "reset" }}` }'>
|
:scroll="isMobile ? {} : { x: 200 }" :pagination="false" :indent-size="0"
|
||||||
|
:locale='{ filterConfirm: `{{ i18n "confirm" }}`, filterReset: `{{ i18n "reset" }}` }'>
|
||||||
<template slot="action" slot-scope="text, balancer, index">
|
<template slot="action" slot-scope="text, balancer, index">
|
||||||
<span>[[ index+1 ]]</span>
|
<span>[[ index+1 ]]</span>
|
||||||
<a-dropdown :trigger="['click']">
|
<a-dropdown :trigger="['click']">
|
||||||
@@ -32,7 +33,8 @@
|
|||||||
<a-tag :style="{ margin: '0' }" v-if="balancer.strategy=='leastPing'" color="green">Least Ping</a-tag>
|
<a-tag :style="{ margin: '0' }" v-if="balancer.strategy=='leastPing'" color="green">Least Ping</a-tag>
|
||||||
</template>
|
</template>
|
||||||
<template slot="selector" slot-scope="text, balancer, index">
|
<template slot="selector" slot-scope="text, balancer, index">
|
||||||
<a-tag class="info-large-tag" :style="{ margin: '1' }" v-for="sel in balancer.selector">[[ sel ]]</a-tag>
|
<a-tag class="info-large-tag" :style="{ margin: '1' }" v-for="sel in balancer.selector">[[ sel
|
||||||
|
]]</a-tag>
|
||||||
</template>
|
</template>
|
||||||
</a-table>
|
</a-table>
|
||||||
<a-radio-group v-if="observatoryEnable || burstObservatoryEnable" v-model="obsSettings" @change="changeObsCode"
|
<a-radio-group v-if="observatoryEnable || burstObservatoryEnable" v-model="obsSettings" @change="changeObsCode"
|
||||||
|
|||||||
@@ -4,8 +4,7 @@
|
|||||||
<a-row :xs="24" :sm="24" :lg="12">
|
<a-row :xs="24" :sm="24" :lg="12">
|
||||||
<a-alert type="warning" :style="{ textAlign: 'center' }">
|
<a-alert type="warning" :style="{ textAlign: 'center' }">
|
||||||
<template slot="message">
|
<template slot="message">
|
||||||
<a-icon type="exclamation-circle" theme="filled"
|
<a-icon type="exclamation-circle" theme="filled" :style="{ color: '#FFA031' }"></a-icon>
|
||||||
:style="{ color: '#FFA031' }"></a-icon>
|
|
||||||
<span>{{ i18n "pages.xray.generalConfigsDesc" }}</span>
|
<span>{{ i18n "pages.xray.generalConfigsDesc" }}</span>
|
||||||
</template>
|
</template>
|
||||||
</a-alert>
|
</a-alert>
|
||||||
@@ -15,11 +14,9 @@
|
|||||||
<template #description>{{ i18n "pages.xray.FreedomStrategyDesc"
|
<template #description>{{ i18n "pages.xray.FreedomStrategyDesc"
|
||||||
}}</template>
|
}}</template>
|
||||||
<template #control>
|
<template #control>
|
||||||
<a-select v-model="freedomStrategy"
|
<a-select v-model="freedomStrategy" :dropdown-class-name="themeSwitcher.currentTheme"
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme"
|
|
||||||
:style="{ width: '100%' }">
|
:style="{ width: '100%' }">
|
||||||
<a-select-option v-for="s in OutboundDomainStrategies"
|
<a-select-option v-for="s in OutboundDomainStrategies" :value="s">
|
||||||
:value="s">
|
|
||||||
<span>[[ s ]]</span>
|
<span>[[ s ]]</span>
|
||||||
</a-select-option>
|
</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
@@ -30,11 +27,9 @@
|
|||||||
<template #description>{{ i18n "pages.xray.RoutingStrategyDesc"
|
<template #description>{{ i18n "pages.xray.RoutingStrategyDesc"
|
||||||
}}</template>
|
}}</template>
|
||||||
<template #control>
|
<template #control>
|
||||||
<a-select v-model="routingStrategy"
|
<a-select v-model="routingStrategy" :dropdown-class-name="themeSwitcher.currentTheme"
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme"
|
|
||||||
:style="{ width: '100%' }">
|
:style="{ width: '100%' }">
|
||||||
<a-select-option v-for="s in routingDomainStrategies"
|
<a-select-option v-for="s in routingDomainStrategies" :value="s">
|
||||||
:value="s">
|
|
||||||
<span>[[ s ]]</span>
|
<span>[[ s ]]</span>
|
||||||
</a-select-option>
|
</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
@@ -45,8 +40,7 @@
|
|||||||
<template #description>{{ i18n "pages.xray.outboundTestUrlDesc"
|
<template #description>{{ i18n "pages.xray.outboundTestUrlDesc"
|
||||||
}}</template>
|
}}</template>
|
||||||
<template #control>
|
<template #control>
|
||||||
<a-input v-model="outboundTestUrl"
|
<a-input v-model="outboundTestUrl" :placeholder="'https://www.google.com/generate_204'"
|
||||||
:placeholder="'https://www.google.com/generate_204'"
|
|
||||||
:style="{ width: '100%' }"></a-input>
|
:style="{ width: '100%' }"></a-input>
|
||||||
</template>
|
</template>
|
||||||
</a-setting-list-item>
|
</a-setting-list-item>
|
||||||
@@ -93,8 +87,7 @@
|
|||||||
<a-row :xs="24" :sm="24" :lg="12">
|
<a-row :xs="24" :sm="24" :lg="12">
|
||||||
<a-alert type="warning" :style="{ textAlign: 'center' }">
|
<a-alert type="warning" :style="{ textAlign: 'center' }">
|
||||||
<template slot="message">
|
<template slot="message">
|
||||||
<a-icon type="exclamation-circle" theme="filled"
|
<a-icon type="exclamation-circle" theme="filled" :style="{ color: '#FFA031' }"></a-icon>
|
||||||
:style="{ color: '#FFA031' }"></a-icon>
|
|
||||||
<span>{{ i18n "pages.xray.logConfigsDesc" }}</span>
|
<span>{{ i18n "pages.xray.logConfigsDesc" }}</span>
|
||||||
</template>
|
</template>
|
||||||
</a-alert>
|
</a-alert>
|
||||||
@@ -104,8 +97,7 @@
|
|||||||
<template #description>{{ i18n "pages.xray.logLevelDesc"
|
<template #description>{{ i18n "pages.xray.logLevelDesc"
|
||||||
}}</template>
|
}}</template>
|
||||||
<template #control>
|
<template #control>
|
||||||
<a-select v-model="logLevel"
|
<a-select v-model="logLevel" :dropdown-class-name="themeSwitcher.currentTheme"
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme"
|
|
||||||
:style="{ width: '100%' }">
|
:style="{ width: '100%' }">
|
||||||
<a-select-option v-for="s in log.loglevel" :value="s">
|
<a-select-option v-for="s in log.loglevel" :value="s">
|
||||||
<span>[[ s ]]</span>
|
<span>[[ s ]]</span>
|
||||||
@@ -118,8 +110,7 @@
|
|||||||
<template #description>{{ i18n "pages.xray.accessLogDesc"
|
<template #description>{{ i18n "pages.xray.accessLogDesc"
|
||||||
}}</template>
|
}}</template>
|
||||||
<template #control>
|
<template #control>
|
||||||
<a-select v-model="accessLog"
|
<a-select v-model="accessLog" :dropdown-class-name="themeSwitcher.currentTheme"
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme"
|
|
||||||
:style="{ width: '100%' }">
|
:style="{ width: '100%' }">
|
||||||
<a-select-option value>
|
<a-select-option value>
|
||||||
<span>Empty</span>
|
<span>Empty</span>
|
||||||
@@ -135,8 +126,7 @@
|
|||||||
<template #description>{{ i18n "pages.xray.errorLogDesc"
|
<template #description>{{ i18n "pages.xray.errorLogDesc"
|
||||||
}}</template>
|
}}</template>
|
||||||
<template #control>
|
<template #control>
|
||||||
<a-select v-model="errorLog"
|
<a-select v-model="errorLog" :dropdown-class-name="themeSwitcher.currentTheme"
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme"
|
|
||||||
:style="{ width: '100%' }">
|
:style="{ width: '100%' }">
|
||||||
<a-select-option value>
|
<a-select-option value>
|
||||||
<span>Empty</span>
|
<span>Empty</span>
|
||||||
@@ -152,8 +142,7 @@
|
|||||||
<template #description>{{ i18n "pages.xray.maskAddressDesc"
|
<template #description>{{ i18n "pages.xray.maskAddressDesc"
|
||||||
}}</template>
|
}}</template>
|
||||||
<template #control>
|
<template #control>
|
||||||
<a-select v-model="maskAddressLog"
|
<a-select v-model="maskAddressLog" :dropdown-class-name="themeSwitcher.currentTheme"
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme"
|
|
||||||
:style="{ width: '100%' }">
|
:style="{ width: '100%' }">
|
||||||
<a-select-option value>
|
<a-select-option value>
|
||||||
<span>Empty</span>
|
<span>Empty</span>
|
||||||
@@ -176,8 +165,7 @@
|
|||||||
<a-row :xs="24" :sm="24" :lg="12">
|
<a-row :xs="24" :sm="24" :lg="12">
|
||||||
<a-alert type="warning" :style="{ textAlign: 'center' }">
|
<a-alert type="warning" :style="{ textAlign: 'center' }">
|
||||||
<template slot="message">
|
<template slot="message">
|
||||||
<a-icon type="exclamation-circle" theme="filled"
|
<a-icon type="exclamation-circle" theme="filled" :style="{ color: '#FFA031' }"></a-icon>
|
||||||
:style="{ color: '#FFA031' }"></a-icon>
|
|
||||||
<span>{{ i18n "pages.xray.blockConfigsDesc" }}</span>
|
<span>{{ i18n "pages.xray.blockConfigsDesc" }}</span>
|
||||||
</template>
|
</template>
|
||||||
</a-alert>
|
</a-alert>
|
||||||
@@ -191,8 +179,7 @@
|
|||||||
<a-row :xs="24" :sm="24" :lg="12">
|
<a-row :xs="24" :sm="24" :lg="12">
|
||||||
<a-alert type="warning" :style="{ textAlign: 'center' }">
|
<a-alert type="warning" :style="{ textAlign: 'center' }">
|
||||||
<template slot="message">
|
<template slot="message">
|
||||||
<a-icon type="exclamation-circle" theme="filled"
|
<a-icon type="exclamation-circle" theme="filled" :style="{ color: '#FFA031' }"></a-icon>
|
||||||
:style="{ color: '#FFA031' }"></a-icon>
|
|
||||||
<span>{{ i18n "pages.xray.blockConnectionsConfigsDesc"
|
<span>{{ i18n "pages.xray.blockConnectionsConfigsDesc"
|
||||||
}}</span>
|
}}</span>
|
||||||
</template>
|
</template>
|
||||||
@@ -201,11 +188,9 @@
|
|||||||
<a-setting-list-item paddings="small">
|
<a-setting-list-item paddings="small">
|
||||||
<template #title>{{ i18n "pages.xray.blockips" }}</template>
|
<template #title>{{ i18n "pages.xray.blockips" }}</template>
|
||||||
<template #control>
|
<template #control>
|
||||||
<a-select mode="tags" v-model="blockedIPs"
|
<a-select mode="tags" v-model="blockedIPs" :style="{ width: '100%' }"
|
||||||
:style="{ width: '100%' }"
|
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option :value="p.value" :label="p.label"
|
<a-select-option :value="p.value" :label="p.label" v-for="p in settingsData.IPsOptions">
|
||||||
v-for="p in settingsData.IPsOptions">
|
|
||||||
<span>[[ p.label ]]</span>
|
<span>[[ p.label ]]</span>
|
||||||
</a-select-option>
|
</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
@@ -214,22 +199,18 @@
|
|||||||
<a-setting-list-item paddings="small">
|
<a-setting-list-item paddings="small">
|
||||||
<template #title>{{ i18n "pages.xray.blockdomains" }}</template>
|
<template #title>{{ i18n "pages.xray.blockdomains" }}</template>
|
||||||
<template #control>
|
<template #control>
|
||||||
<a-select mode="tags" v-model="blockedDomains"
|
<a-select mode="tags" v-model="blockedDomains" :style="{ width: '100%' }"
|
||||||
:style="{ width: '100%' }"
|
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option :value="p.value" :label="p.label"
|
<a-select-option :value="p.value" :label="p.label" v-for="p in settingsData.BlockDomainsOptions">
|
||||||
v-for="p in settingsData.BlockDomainsOptions">
|
|
||||||
<span>[[ p.label ]]</span>
|
<span>[[ p.label ]]</span>
|
||||||
</a-select-option>
|
</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</template>
|
</template>
|
||||||
</a-setting-list-item>
|
</a-setting-list-item>
|
||||||
<a-row :xs="24" :sm="24" :lg="12">
|
<a-row :xs="24" :sm="24" :lg="12">
|
||||||
<a-alert type="warning"
|
<a-alert type="warning" :style="{ textAlign: 'center', marginTop: '20px' }">
|
||||||
:style="{ textAlign: 'center', marginTop: '20px' }">
|
|
||||||
<template slot="message">
|
<template slot="message">
|
||||||
<a-icon type="exclamation-circle" theme="filled"
|
<a-icon type="exclamation-circle" theme="filled" :style="{ color: '#FFA031' }"></a-icon>
|
||||||
:style="{ color: '#FFA031' }"></a-icon>
|
|
||||||
<span>{{ i18n "pages.xray.directConnectionsConfigsDesc"
|
<span>{{ i18n "pages.xray.directConnectionsConfigsDesc"
|
||||||
}}</span>
|
}}</span>
|
||||||
</template>
|
</template>
|
||||||
@@ -238,11 +219,9 @@
|
|||||||
<a-setting-list-item paddings="small">
|
<a-setting-list-item paddings="small">
|
||||||
<template #title>{{ i18n "pages.xray.directips" }}</template>
|
<template #title>{{ i18n "pages.xray.directips" }}</template>
|
||||||
<template #control>
|
<template #control>
|
||||||
<a-select mode="tags" :style="{ width: '100%' }"
|
<a-select mode="tags" :style="{ width: '100%' }" v-model="directIPs"
|
||||||
v-model="directIPs"
|
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option :value="p.value" :label="p.label"
|
<a-select-option :value="p.value" :label="p.label" v-for="p in settingsData.IPsOptions">
|
||||||
v-for="p in settingsData.IPsOptions">
|
|
||||||
<span>[[ p.label ]]</span>
|
<span>[[ p.label ]]</span>
|
||||||
</a-select-option>
|
</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
@@ -251,22 +230,18 @@
|
|||||||
<a-setting-list-item paddings="small">
|
<a-setting-list-item paddings="small">
|
||||||
<template #title>{{ i18n "pages.xray.directdomains" }}</template>
|
<template #title>{{ i18n "pages.xray.directdomains" }}</template>
|
||||||
<template #control>
|
<template #control>
|
||||||
<a-select mode="tags" :style="{ width: '100%' }"
|
<a-select mode="tags" :style="{ width: '100%' }" v-model="directDomains"
|
||||||
v-model="directDomains"
|
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option :value="p.value" :label="p.label"
|
<a-select-option :value="p.value" :label="p.label" v-for="p in settingsData.DomainsOptions">
|
||||||
v-for="p in settingsData.DomainsOptions">
|
|
||||||
<span>[[ p.label ]]</span>
|
<span>[[ p.label ]]</span>
|
||||||
</a-select-option>
|
</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</template>
|
</template>
|
||||||
</a-setting-list-item>
|
</a-setting-list-item>
|
||||||
<a-row :xs="24" :sm="24" :lg="12">
|
<a-row :xs="24" :sm="24" :lg="12">
|
||||||
<a-alert type="warning"
|
<a-alert type="warning" :style="{ textAlign: 'center', marginTop: '20px' }">
|
||||||
:style="{ textAlign: 'center', marginTop: '20px' }">
|
|
||||||
<template slot="message">
|
<template slot="message">
|
||||||
<a-icon type="exclamation-circle" theme="filled"
|
<a-icon type="exclamation-circle" theme="filled" :style="{ color: '#FFA031' }"></a-icon>
|
||||||
:style="{ color: '#FFA031' }"></a-icon>
|
|
||||||
<span>{{ i18n "pages.xray.ipv4RoutingDesc" }}</span>
|
<span>{{ i18n "pages.xray.ipv4RoutingDesc" }}</span>
|
||||||
</template>
|
</template>
|
||||||
</a-alert>
|
</a-alert>
|
||||||
@@ -274,22 +249,18 @@
|
|||||||
<a-setting-list-item paddings="small">
|
<a-setting-list-item paddings="small">
|
||||||
<template #title>{{ i18n "pages.xray.ipv4Routing" }}</template>
|
<template #title>{{ i18n "pages.xray.ipv4Routing" }}</template>
|
||||||
<template #control>
|
<template #control>
|
||||||
<a-select mode="tags" :style="{ width: '100%' }"
|
<a-select mode="tags" :style="{ width: '100%' }" v-model="ipv4Domains"
|
||||||
v-model="ipv4Domains"
|
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option :value="p.value" :label="p.label"
|
<a-select-option :value="p.value" :label="p.label" v-for="p in settingsData.ServicesOptions">
|
||||||
v-for="p in settingsData.ServicesOptions">
|
|
||||||
<span>[[ p.label ]]</span>
|
<span>[[ p.label ]]</span>
|
||||||
</a-select-option>
|
</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</template>
|
</template>
|
||||||
</a-setting-list-item>
|
</a-setting-list-item>
|
||||||
<a-row :xs="24" :sm="24" :lg="12">
|
<a-row :xs="24" :sm="24" :lg="12">
|
||||||
<a-alert type="warning"
|
<a-alert type="warning" :style="{ textAlign: 'center', marginTop: '20px' }">
|
||||||
:style="{ textAlign: 'center', marginTop: '20px' }">
|
|
||||||
<template slot="message">
|
<template slot="message">
|
||||||
<a-icon type="exclamation-circle" theme="filled"
|
<a-icon type="exclamation-circle" theme="filled" :style="{ color: '#FFA031' }"></a-icon>
|
||||||
:style="{ color: '#FFA031' }"></a-icon>
|
|
||||||
{{ i18n "pages.xray.warpRoutingDesc" }}
|
{{ i18n "pages.xray.warpRoutingDesc" }}
|
||||||
</template>
|
</template>
|
||||||
</a-alert>
|
</a-alert>
|
||||||
@@ -298,18 +269,15 @@
|
|||||||
<template #title>{{ i18n "pages.xray.warpRouting" }}</template>
|
<template #title>{{ i18n "pages.xray.warpRouting" }}</template>
|
||||||
<template #control>
|
<template #control>
|
||||||
<template v-if="WarpExist">
|
<template v-if="WarpExist">
|
||||||
<a-select mode="tags" :style="{ width: '100%' }"
|
<a-select mode="tags" :style="{ width: '100%' }" v-model="warpDomains"
|
||||||
v-model="warpDomains"
|
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option :value="p.value" :label="p.label"
|
<a-select-option :value="p.value" :label="p.label" v-for="p in settingsData.ServicesOptions">
|
||||||
v-for="p in settingsData.ServicesOptions">
|
|
||||||
<span>[[ p.label ]]</span>
|
<span>[[ p.label ]]</span>
|
||||||
</a-select-option>
|
</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<a-button type="primary" icon="cloud"
|
<a-button type="primary" icon="cloud" @click="showWarp()">WARP</a-button>
|
||||||
@click="showWarp()">WARP</a-button>
|
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
</a-setting-list-item>
|
</a-setting-list-item>
|
||||||
@@ -317,11 +285,9 @@
|
|||||||
<template #title>{{ i18n "pages.xray.nordRouting" }}</template>
|
<template #title>{{ i18n "pages.xray.nordRouting" }}</template>
|
||||||
<template #control>
|
<template #control>
|
||||||
<template v-if="NordExist">
|
<template v-if="NordExist">
|
||||||
<a-select mode="tags" :style="{ width: '100%' }"
|
<a-select mode="tags" :style="{ width: '100%' }" v-model="nordDomains"
|
||||||
v-model="nordDomains"
|
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option :value="p.value" :label="p.label"
|
<a-select-option :value="p.value" :label="p.label" v-for="p in settingsData.ServicesOptions">
|
||||||
v-for="p in settingsData.ServicesOptions">
|
|
||||||
<span>[[ p.label ]]</span>
|
<span>[[ p.label ]]</span>
|
||||||
</a-select-option>
|
</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
@@ -333,8 +299,7 @@
|
|||||||
</template>
|
</template>
|
||||||
</a-setting-list-item>
|
</a-setting-list-item>
|
||||||
</a-collapse-panel>
|
</a-collapse-panel>
|
||||||
<a-collapse-panel key="6"
|
<a-collapse-panel key="6" header='{{ i18n "pages.settings.resetDefaultConfig"}}'>
|
||||||
header='{{ i18n "pages.settings.resetDefaultConfig"}}'>
|
|
||||||
<a-space direction="horizontal" :style="{ padding: '0 20px' }">
|
<a-space direction="horizontal" :style="{ padding: '0 20px' }">
|
||||||
<a-button type="danger" @click="resetXrayConfigToDefault">
|
<a-button type="danger" @click="resetXrayConfigToDefault">
|
||||||
<span>{{ i18n "pages.settings.resetDefaultConfig" }}</span>
|
<span>{{ i18n "pages.settings.resetDefaultConfig" }}</span>
|
||||||
|
|||||||
@@ -29,7 +29,8 @@
|
|||||||
<template #control>
|
<template #control>
|
||||||
<a-select v-model="dnsStrategy" :style="{ width: '100%' }"
|
<a-select v-model="dnsStrategy" :style="{ width: '100%' }"
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option :value="l" :label="l" v-for="l in ['UseSystem', 'UseIP', 'UseIPv4', 'UseIPv6']">
|
<a-select-option :value="l" :label="l"
|
||||||
|
v-for="l in ['UseSystem', 'UseIP', 'UseIPv4', 'UseIPv6']">
|
||||||
<span>[[ l ]]</span>
|
<span>[[ l ]]</span>
|
||||||
</a-select-option>
|
</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
|
|||||||
@@ -7,21 +7,16 @@
|
|||||||
<span v-if="!isMobile">{{ i18n
|
<span v-if="!isMobile">{{ i18n
|
||||||
"pages.xray.outbound.addOutbound" }}</span>
|
"pages.xray.outbound.addOutbound" }}</span>
|
||||||
</a-button>
|
</a-button>
|
||||||
<a-button type="primary" icon="cloud"
|
<a-button type="primary" icon="cloud" @click="showWarp()">WARP</a-button>
|
||||||
@click="showWarp()">WARP</a-button>
|
<a-button type="primary" icon="api" @click="showNord()">NordVPN</a-button>
|
||||||
<a-button type="primary" icon="api"
|
|
||||||
@click="showNord()">NordVPN</a-button>
|
|
||||||
</a-space>
|
</a-space>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :xs="12" :sm="12" :lg="12" :style="{ textAlign: 'right' }">
|
<a-col :xs="12" :sm="12" :lg="12" :style="{ textAlign: 'right' }">
|
||||||
<a-button-group>
|
<a-button-group>
|
||||||
<a-button icon="sync" @click="refreshOutboundTraffic()"
|
<a-button icon="sync" @click="refreshOutboundTraffic()" :loading="refreshing"></a-button>
|
||||||
:loading="refreshing"></a-button>
|
<a-popconfirm placement="topRight" @confirm="resetOutboundTraffic(-1)"
|
||||||
<a-popconfirm placement="topRight"
|
|
||||||
@confirm="resetOutboundTraffic(-1)"
|
|
||||||
title='{{ i18n "pages.inbounds.resetTrafficContent"}}'
|
title='{{ i18n "pages.inbounds.resetTrafficContent"}}'
|
||||||
:overlay-class-name="themeSwitcher.currentTheme"
|
:overlay-class-name="themeSwitcher.currentTheme" ok-text='{{ i18n "reset"}}'
|
||||||
ok-text='{{ i18n "reset"}}'
|
|
||||||
cancel-text='{{ i18n "cancel"}}'>
|
cancel-text='{{ i18n "cancel"}}'>
|
||||||
<a-icon slot="icon" type="question-circle-o"
|
<a-icon slot="icon" type="question-circle-o"
|
||||||
:style="{ color: themeSwitcher.isDarkTheme ? '#008771' : '#008771' }"></a-icon>
|
:style="{ color: themeSwitcher.isDarkTheme ? '#008771' : '#008771' }"></a-icon>
|
||||||
@@ -30,10 +25,8 @@
|
|||||||
</a-button-group>
|
</a-button-group>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
<a-table :columns="outboundColumns" bordered :row-key="r => r.key"
|
<a-table :columns="outboundColumns" bordered :row-key="r => r.key" :data-source="outboundData"
|
||||||
:data-source="outboundData"
|
:scroll="isMobile ? {} : { x: 800 }" :pagination="false" :indent-size="0"
|
||||||
:scroll="isMobile ? {} : { x: 800 }" :pagination="false"
|
|
||||||
:indent-size="0"
|
|
||||||
:locale='{ filterConfirm: `{{ i18n "confirm" }}`, filterReset: `{{ i18n "reset" }}` }'>
|
:locale='{ filterConfirm: `{{ i18n "confirm" }}`, filterReset: `{{ i18n "reset" }}` }'>
|
||||||
<template slot="action" slot-scope="text, outbound, index">
|
<template slot="action" slot-scope="text, outbound, index">
|
||||||
<span>[[ index+1 ]]</span>
|
<span>[[ index+1 ]]</span>
|
||||||
@@ -41,8 +34,7 @@
|
|||||||
<a-icon @click="e => e.preventDefault()" type="more"
|
<a-icon @click="e => e.preventDefault()" type="more"
|
||||||
:style="{ fontSize: '16px', textDecoration: 'bold' }"></a-icon>
|
:style="{ fontSize: '16px', textDecoration: 'bold' }"></a-icon>
|
||||||
<a-menu slot="overlay" :theme="themeSwitcher.currentTheme">
|
<a-menu slot="overlay" :theme="themeSwitcher.currentTheme">
|
||||||
<a-menu-item v-if="index>0"
|
<a-menu-item v-if="index>0" @click="setFirstOutbound(index)">
|
||||||
@click="setFirstOutbound(index)">
|
|
||||||
<a-icon type="vertical-align-top"></a-icon>
|
<a-icon type="vertical-align-top"></a-icon>
|
||||||
<span>{{ i18n "pages.xray.rules.first"}}</span>
|
<span>{{ i18n "pages.xray.rules.first"}}</span>
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
@@ -66,8 +58,7 @@
|
|||||||
</a-dropdown>
|
</a-dropdown>
|
||||||
</template>
|
</template>
|
||||||
<template slot="address" slot-scope="text, outbound, index">
|
<template slot="address" slot-scope="text, outbound, index">
|
||||||
<p :style="{ margin: '0 5px' }"
|
<p :style="{ margin: '0 5px' }" v-for="addr in findOutboundAddress(outbound)">[[ addr ]]</p>
|
||||||
v-for="addr in findOutboundAddress(outbound)">[[ addr ]]</p>
|
|
||||||
</template>
|
</template>
|
||||||
<template slot="protocol" slot-scope="text, outbound, index">
|
<template slot="protocol" slot-scope="text, outbound, index">
|
||||||
<a-tag :style="{ margin: '0' }" color="purple">[[ outbound.protocol
|
<a-tag :style="{ margin: '0' }" color="purple">[[ outbound.protocol
|
||||||
@@ -76,11 +67,8 @@
|
|||||||
v-if="[Protocols.VMess, Protocols.VLESS, Protocols.Trojan, Protocols.Shadowsocks].includes(outbound.protocol)">
|
v-if="[Protocols.VMess, Protocols.VLESS, Protocols.Trojan, Protocols.Shadowsocks].includes(outbound.protocol)">
|
||||||
<a-tag :style="{ margin: '0' }" color="blue">[[
|
<a-tag :style="{ margin: '0' }" color="blue">[[
|
||||||
outbound.streamSettings.network ]]</a-tag>
|
outbound.streamSettings.network ]]</a-tag>
|
||||||
<a-tag :style="{ margin: '0' }"
|
<a-tag :style="{ margin: '0' }" v-if="outbound.streamSettings.security=='tls'" color="green">tls</a-tag>
|
||||||
v-if="outbound.streamSettings.security=='tls'"
|
<a-tag :style="{ margin: '0' }" v-if="outbound.streamSettings.security=='reality'"
|
||||||
color="green">tls</a-tag>
|
|
||||||
<a-tag :style="{ margin: '0' }"
|
|
||||||
v-if="outbound.streamSettings.security=='reality'"
|
|
||||||
color="green">reality</a-tag>
|
color="green">reality</a-tag>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
@@ -91,10 +79,7 @@
|
|||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">{{ i18n "pages.xray.outbound.test"
|
<template slot="title">{{ i18n "pages.xray.outbound.test"
|
||||||
}}</template>
|
}}</template>
|
||||||
<a-button
|
<a-button type="primary" shape="circle" icon="thunderbolt"
|
||||||
type="primary"
|
|
||||||
shape="circle"
|
|
||||||
icon="thunderbolt"
|
|
||||||
:loading="outboundTestStates[index] && outboundTestStates[index].testing"
|
:loading="outboundTestStates[index] && outboundTestStates[index].testing"
|
||||||
@click="testOutbound(index)"
|
@click="testOutbound(index)"
|
||||||
:disabled="(outbound.protocol === 'blackhole' || outbound.tag === 'blocked') || (outboundTestStates[index] && outboundTestStates[index].testing)">
|
:disabled="(outbound.protocol === 'blackhole' || outbound.tag === 'blocked') || (outboundTestStates[index] && outboundTestStates[index].testing)">
|
||||||
@@ -102,24 +87,20 @@
|
|||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</template>
|
</template>
|
||||||
<template slot="testResult" slot-scope="text, outbound, index">
|
<template slot="testResult" slot-scope="text, outbound, index">
|
||||||
<div
|
<div v-if="outboundTestStates[index] && outboundTestStates[index].result">
|
||||||
v-if="outboundTestStates[index] && outboundTestStates[index].result">
|
<a-tag v-if="outboundTestStates[index].result.success" color="green">
|
||||||
<a-tag v-if="outboundTestStates[index].result.success"
|
|
||||||
color="green">
|
|
||||||
[[ outboundTestStates[index].result.delay ]]ms
|
[[ outboundTestStates[index].result.delay ]]ms
|
||||||
<span v-if="outboundTestStates[index].result.statusCode">
|
<span v-if="outboundTestStates[index].result.statusCode">
|
||||||
([[ outboundTestStates[index].result.statusCode
|
([[ outboundTestStates[index].result.statusCode
|
||||||
]])</span>
|
]])</span>
|
||||||
</a-tag>
|
</a-tag>
|
||||||
<a-tooltip v-else
|
<a-tooltip v-else :title="outboundTestStates[index].result.error">
|
||||||
:title="outboundTestStates[index].result.error">
|
|
||||||
<a-tag color="red">
|
<a-tag color="red">
|
||||||
Failed
|
Failed
|
||||||
</a-tag>
|
</a-tag>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</div>
|
</div>
|
||||||
<span
|
<span v-else-if="outboundTestStates[index] && outboundTestStates[index].testing">
|
||||||
v-else-if="outboundTestStates[index] && outboundTestStates[index].testing">
|
|
||||||
<a-icon type="loading" />
|
<a-icon type="loading" />
|
||||||
</span>
|
</span>
|
||||||
<span v-else>-</span>
|
<span v-else>-</span>
|
||||||
|
|||||||
1128
web/html/xray.html
1128
web/html/xray.html
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user