Compare commits

...

42 Commits

Author SHA1 Message Date
mhsanaei
b6bb0b1787 v2.3.4 2024-05-24 11:12:49 +02:00
mhsanaei
c0b5d5506f View Current Settings - show webBasePath 2024-05-24 11:08:16 +02:00
mhsanaei
a2f6d3b8dc new - (TLS) Session Resumption 2024-05-24 10:17:20 +02:00
mhsanaei
80cd793154 new - sockopt : all features #2293 2024-05-24 09:57:14 +02:00
Ahmad Thoriq Najahi
d070a82b3d feat: Enhance host extraction from headers (#2292)
- Refactor SUBController subs and subJsons methods to extract host from X-Forwarded-Host header, falling back to X-Real-IP header and then to the request host if unavailable.
- Update html function to extract host from X-Forwarded-Host header, falling back to X-Real-IP header and then to the request host if unavailable.
- Update DomainValidatorMiddleware to first attempt to extract host from X-Forwarded-Host header, falling back to X-Real-IP header and then to the request host.

Fixes: #2284

Signed-off-by: Ahmad Thoriq Najahi <najahi@zephyrus.id>
2024-05-23 23:51:19 +02:00
mhsanaei
5ec16301a6 Add X-Real-IP Support for Client IP (login page) 2024-05-23 15:16:13 +02:00
mhsanaei
07245d614a Upgrade - Xray Core v1.8.13 2024-05-23 10:52:25 +02:00
mhsanaei
6e734553e2 v2.3.3 2024-05-22 21:48:41 +02:00
mhsanaei
275370e32c new - (Sockopt) tcpMptcp , tcpNoDelay
https://xtls.github.io/en/config/transport.html#sockoptobject
2024-05-22 20:06:49 +02:00
mhsanaei
e7c59adc59 Ensure IPv6 compliant host
Co-Authored-By: vnxme <46669194+vnxme@users.noreply.github.com>
2024-05-22 20:06:49 +02:00
mhsanaei
68c9b55447 new - (TLS) disable System Root 2024-05-22 20:06:49 +02:00
mhsanaei
70f9e32f28 new - Cert (TLS-XTLS) - oneTimeLoading , usage 2024-05-22 20:06:48 +02:00
mhsanaei
5aae32c888 update - xray core v1.8.12 2024-05-22 20:06:48 +02:00
dependabot[bot]
36cade2a02 Bump github.com/goccy/go-json from 0.10.2 to 0.10.3 #2273
updated-dependencies:
- dependency-name: github.com/goccy/go-json
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-22 09:54:24 +02:00
mhsanaei
907c30f743 bug fix - bulk Telegram ChatID 2024-05-16 14:58:32 +02:00
dependabot[bot]
5202fb2df4 Bump google.golang.org/grpc from 1.63.2 to 1.64.0 (#2259)
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.63.2 to 1.64.0.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.63.2...v1.64.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-15 12:18:22 +02:00
Ahmad Thoriq Najahi
73a19a45d7 feat(tgbot): Add refresh button to server usage (#2253)
- Added a refresh button to the server usage interface to allow users to refresh the displayed information.
 - Updated the sendReport function to use the sendServerUsage function instead of getServerUsage to ensure consistency in functionality.

Signed-off-by: Ahmad Thoriq Najahi <najahi@zephyrus.id>
2024-05-14 14:00:10 +02:00
dependabot[bot]
0fb4094fe6 Bump github.com/valyala/fasthttp from 1.52.0 to 1.53.0 (#2258)
Bumps [github.com/valyala/fasthttp](https://github.com/valyala/fasthttp) from 1.52.0 to 1.53.0.
- [Release notes](https://github.com/valyala/fasthttp/releases)
- [Commits](https://github.com/valyala/fasthttp/compare/v1.52.0...v1.53.0)

---
updated-dependencies:
- dependency-name: github.com/valyala/fasthttp
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-14 13:59:37 +02:00
mhsanaei
d9254fec03 v2.3.1 2024-05-09 19:49:26 +02:00
mhsanaei
2f02b71ed4 OS - Parch , OpenSUSE 2024-05-09 19:49:15 +02:00
Hamidreza
559aad9967 [fix] always serve panel even if xray failed to run (#2248) 2024-05-09 19:45:12 +02:00
mhsanaei
a1c15e9578 [dns] skipFallback option
Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
2024-05-08 21:43:15 +02:00
mhsanaei
be9747dcbc sniffing - metadataOnly, routeOnly
Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
2024-05-08 21:38:08 +02:00
mhsanaei
94eb27d2c4 add dokodemo timeout
Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
2024-05-08 21:31:58 +02:00
Lxtend
d1f67f7f2f feat: add support for install on opensuse tumbleweed (#2244)
* feat: add support for opensuse tumbleweed

* doc(readme): add opensuse tumbleweed to recommended os

---------

Co-authored-by: lixiangwuxian <lixiangwuxian@lxtend.com>
2024-05-07 12:03:32 +02:00
dependabot[bot]
bb6757df0f Bump github.com/gin-gonic/gin from 1.9.1 to 1.10.0 (#2247)
Bumps [github.com/gin-gonic/gin](https://github.com/gin-gonic/gin) from 1.9.1 to 1.10.0.
- [Release notes](https://github.com/gin-gonic/gin/releases)
- [Changelog](https://github.com/gin-gonic/gin/blob/master/CHANGELOG.md)
- [Commits](https://github.com/gin-gonic/gin/compare/v1.9.1...v1.10.0)

---
updated-dependencies:
- dependency-name: github.com/gin-gonic/gin
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-07 11:59:38 +02:00
mhsanaei
e094c351a3 remove version - folder name 2024-05-07 08:45:05 +02:00
dependabot[bot]
fb31ecfa55 Bump github.com/gin-contrib/sessions from 1.0.0 to 1.0.1 (#2245)
Bumps [github.com/gin-contrib/sessions](https://github.com/gin-contrib/sessions) from 1.0.0 to 1.0.1.
- [Release notes](https://github.com/gin-contrib/sessions/releases)
- [Changelog](https://github.com/gin-contrib/sessions/blob/master/.goreleaser.yaml)
- [Commits](https://github.com/gin-contrib/sessions/compare/v1.0.0...v1.0.1)

---
updated-dependencies:
- dependency-name: github.com/gin-contrib/sessions
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-06 13:18:50 +02:00
dependabot[bot]
93a865f010 Bump golang.org/x/text from 0.14.0 to 0.15.0 (#2246)
Bumps [golang.org/x/text](https://github.com/golang/text) from 0.14.0 to 0.15.0.
- [Release notes](https://github.com/golang/text/releases)
- [Commits](https://github.com/golang/text/compare/v0.14.0...v0.15.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-06 13:18:35 +02:00
dependabot[bot]
01a3a7b862 Bump github.com/pelletier/go-toml/v2 from 2.2.1 to 2.2.2 (#2242)
Bumps [github.com/pelletier/go-toml/v2](https://github.com/pelletier/go-toml) from 2.2.1 to 2.2.2.
- [Release notes](https://github.com/pelletier/go-toml/releases)
- [Changelog](https://github.com/pelletier/go-toml/blob/v2/.goreleaser.yaml)
- [Commits](https://github.com/pelletier/go-toml/compare/v2.2.1...v2.2.2)

---
updated-dependencies:
- dependency-name: github.com/pelletier/go-toml/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-02 16:08:10 +02:00
dependabot[bot]
e233fdb092 Bump github.com/gin-contrib/gzip from 1.0.0 to 1.0.1 (#2243)
Bumps [github.com/gin-contrib/gzip](https://github.com/gin-contrib/gzip) from 1.0.0 to 1.0.1.
- [Release notes](https://github.com/gin-contrib/gzip/releases)
- [Changelog](https://github.com/gin-contrib/gzip/blob/master/.goreleaser.yaml)
- [Commits](https://github.com/gin-contrib/gzip/compare/v1.0.0...v1.0.1)

---
updated-dependencies:
- dependency-name: github.com/gin-contrib/gzip
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-02 16:07:33 +02:00
dependabot[bot]
55bf0c3e55 Bump github.com/shirou/gopsutil/v3 from 3.24.3 to 3.24.4 (#2241)
Bumps [github.com/shirou/gopsutil/v3](https://github.com/shirou/gopsutil) from 3.24.3 to 3.24.4.
- [Release notes](https://github.com/shirou/gopsutil/releases)
- [Commits](https://github.com/shirou/gopsutil/compare/v3.24.3...v3.24.4)

---
updated-dependencies:
- dependency-name: github.com/shirou/gopsutil/v3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-01 12:35:42 +02:00
dependabot[bot]
3129fdc103 Bump gorm.io/gorm from 1.25.9 to 1.25.10 (#2240)
Bumps [gorm.io/gorm](https://github.com/go-gorm/gorm) from 1.25.9 to 1.25.10.
- [Release notes](https://github.com/go-gorm/gorm/releases)
- [Commits](https://github.com/go-gorm/gorm/compare/v1.25.9...v1.25.10)

---
updated-dependencies:
- dependency-name: gorm.io/gorm
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-29 11:14:23 +02:00
Ahmad Thoriq Najahi
2d20983690 feat(tgbot): Add xray-core version into server status (#2236)
Signed-off-by: Ahmad Thoriq Najahi <najahi@zephyrus.id>
2024-04-29 08:44:16 +02:00
mhsanaei
490048a975 upgrade xray to v1.8.11 2024-04-26 10:06:13 +02:00
dependabot[bot]
3950f596d8 Bump github.com/xtls/xray-core from 1.8.10 to 1.8.11 (#2234)
Bumps [github.com/xtls/xray-core](https://github.com/xtls/xray-core) from 1.8.10 to 1.8.11.
- [Release notes](https://github.com/xtls/xray-core/releases)
- [Commits](https://github.com/xtls/xray-core/compare/v1.8.10...v1.8.11)

---
updated-dependencies:
- dependency-name: github.com/xtls/xray-core
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-26 10:03:17 +02:00
Gzngskxgr20
835cf2801c Correction of Previous Mistaken PR and the delete_ports function (#2231)
* Update x-ui.sh with ufw port settings

It really costs time when adding rules for a large range, if for loop is used in bash. Changed it to built-in port range support in ufw.


This commit is to correct the previous one, which cannot handle port range settings correctly.

Corrected the confirmation of the deleted ports.
2024-04-23 00:35:05 +03:30
mhsanaei
e794d3d87f fix empty client ID on request
Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
2024-04-21 00:58:37 +03:30
mhsanaei
2cf762642b gin-contrib v1.0.0 2024-04-21 00:56:55 +03:30
Tara Rostami
db24d21621 UI Improvements (#2228)
* UI Improvements

Better Table
Update QR Code Modal
Better Info Modal
Compression HTML files
Better Dropdown Menu
Better Calendar
and more ..
Remove files
Minor Fixes
2024-04-20 22:15:36 +03:30
Gzngskxgr20
3d5c06bf08 Update x-ui.sh with ufw port settings (#2230)
* Update x-ui.sh with ufw port settings

It really costs time when adding rules for a large range, if for loop is used in bash. Changed it to built-in port range support in ufw.
2024-04-20 21:59:27 +03:30
Rammiah
a3a2d7a6a3 fix dns ui (#2229)
fix domains for nameserver only record, domain will keep after edit a
server with domains list
2024-04-20 14:46:08 +03:30
73 changed files with 3161 additions and 12625 deletions

View File

@@ -83,7 +83,7 @@ jobs:
cd x-ui/bin cd x-ui/bin
# Download dependencies # Download dependencies
Xray_URL="https://github.com/XTLS/Xray-core/releases/download/v1.8.10/" Xray_URL="https://github.com/XTLS/Xray-core/releases/download/v1.8.13/"
if [ "${{ matrix.platform }}" == "amd64" ]; then if [ "${{ matrix.platform }}" == "amd64" ]; then
wget ${Xray_URL}Xray-linux-64.zip wget ${Xray_URL}Xray-linux-64.zip
unzip Xray-linux-64.zip unzip Xray-linux-64.zip

View File

@@ -27,7 +27,7 @@ case $1 in
esac esac
mkdir -p build/bin mkdir -p build/bin
cd build/bin cd build/bin
wget "https://github.com/XTLS/Xray-core/releases/download/v1.8.10/Xray-linux-${ARCH}.zip" wget "https://github.com/XTLS/Xray-core/releases/download/v1.8.13/Xray-linux-${ARCH}.zip"
unzip "Xray-linux-${ARCH}.zip" unzip "Xray-linux-${ARCH}.zip"
rm -f "Xray-linux-${ARCH}.zip" geoip.dat geosite.dat rm -f "Xray-linux-${ARCH}.zip" geoip.dat geosite.dat
mv xray "xray-linux-${FNAME}" mv xray "xray-linux-${FNAME}"

View File

@@ -28,10 +28,10 @@ bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.
## Instalar una Versión Personalizada ## Instalar una Versión Personalizada
Para instalar la versión deseada, agrega la versión al final del comando de instalación. Por ejemplo, ver `v2.2.8`: Para instalar la versión deseada, agrega la versión al final del comando de instalación. Por ejemplo, ver `v2.3.4`:
``` ```
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) v2.2.8 bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) v2.3.4
``` ```
## Certificado SSL ## Certificado SSL
@@ -185,6 +185,7 @@ eliminar 3x-ui de docker
- Armbian - Armbian
- AlmaLinux 9+ - AlmaLinux 9+
- Rockylinux 9+ - Rockylinux 9+
- OpenSUSE Tubleweed
## Arquitecturas y Dispositivos Compatibles ## Arquitecturas y Dispositivos Compatibles
@@ -396,25 +397,25 @@ Ingresa el ID de chat de usuario en el campo de entrada número 4. Las cuentas d
- `/login` con `POST` datos de usuario: `{username: '', password: ''}` para iniciar sesión - `/login` con `POST` datos de usuario: `{username: '', password: ''}` para iniciar sesión
- `/panel/api/inbounds` base para las siguientes acciones: - `/panel/api/inbounds` base para las siguientes acciones:
| Método | Ruta | Acción | | Método | Ruta | Acción |
| :----: | ---------------------------------- | -------------------------------------------------------- | | :----: | ---------------------------------- | --------------------------------------------------------- |
| `GET` | `"/list"` | Obtener todas los Entradas | | `GET` | `"/list"` | Obtener todas los Entradas |
| `GET` | `"/get/:id"` | Obtener Entrada con inbound.id | | `GET` | `"/get/:id"` | Obtener Entrada con inbound.id |
| `GET` | `"/getClientTraffics/:email"` | Obtener Tráficos del Cliente con email | | `GET` | `"/getClientTraffics/:email"` | Obtener Tráficos del Cliente con email |
| `GET` | `"/createbackup"` | El bot de Telegram envía copia de seguridad a los admins | | `GET` | `"/createbackup"` | El bot de Telegram envía copia de seguridad a los admins |
| `POST` | `"/add"` | Agregar Entrada | | `POST` | `"/add"` | Agregar Entrada |
| `POST` | `"/del/:id"` | Eliminar Entrada | | `POST` | `"/del/:id"` | Eliminar Entrada |
| `POST` | `"/update/:id"` | Actualizar Entrada | | `POST` | `"/update/:id"` | Actualizar Entrada |
| `POST` | `"/clientIps/:email"` | Dirección IP del Cliente | | `POST` | `"/clientIps/:email"` | Dirección IP del Cliente |
| `POST` | `"/clearClientIps/:email"` | Borrar Dirección IP del Cliente | | `POST` | `"/clearClientIps/:email"` | Borrar Dirección IP del Cliente |
| `POST` | `"/addClient"` | Agregar Cliente a la Entrada | | `POST` | `"/addClient"` | Agregar Cliente a la Entrada |
| `POST` | `"/:id/delClient/:clientId"` | Eliminar Cliente por clientId\* | | `POST` | `"/:id/delClient/:clientId"` | Eliminar Cliente por clientId\* |
| `POST` | `"/updateClient/:clientId"` | Actualizar Cliente por clientId\* | | `POST` | `"/updateClient/:clientId"` | Actualizar Cliente por clientId\* |
| `POST` | `"/:id/resetClientTraffic/:email"` | Restablecer Tráfico del Cliente | | `POST` | `"/:id/resetClientTraffic/:email"` | Restablecer Tráfico del Cliente |
| `POST` | `"/resetAllTraffics"` | Restablecer tráfico de todos las Entradas | | `POST` | `"/resetAllTraffics"` | Restablecer tráfico de todos las Entradas |
| `POST` | `"/resetAllClientTraffics/:id"` | Restablecer tráfico de todos los clientes en una Entrada | | `POST` | `"/resetAllClientTraffics/:id"` | Restablecer tráfico de todos los clientes en una Entrada |
| `POST` | `"/delDepletedClients/:id"` | Eliminar clientes agotados de la entrada (-1: todos) | | `POST` | `"/delDepletedClients/:id"` | Eliminar clientes agotados de la entrada (-1: todos) |
| `POST` | `"/onlines"` | Obtener usuarios en línea (lista de correos electrónicos)| | `POST` | `"/onlines"` | Obtener usuarios en línea (lista de correos electrónicos) |
\*- El campo `clientId` debe llenarse por: \*- El campo `clientId` debe llenarse por:
@@ -434,13 +435,13 @@ Ingresa el ID de chat de usuario en el campo de entrada número 4. Las cuentas d
#### Uso #### Uso
| Variable | Tipo | Predeterminado| | Variable | Tipo | Predeterminado |
| -------------- | :--------------------------------------------: | :------------ | | -------------- | :--------------------------------------------: | :------------- |
| XUI_LOG_LEVEL | `"debug"` \| `"info"` \| `"warn"` \| `"error"` | `"info"` | | XUI_LOG_LEVEL | `"debug"` \| `"info"` \| `"warn"` \| `"error"` | `"info"` |
| XUI_DEBUG | `boolean` | `false` | | XUI_DEBUG | `boolean` | `false` |
| XUI_BIN_FOLDER | `string` | `"bin"` | | XUI_BIN_FOLDER | `string` | `"bin"` |
| XUI_DB_FOLDER | `string` | `"/etc/x-ui"` | | XUI_DB_FOLDER | `string` | `"/etc/x-ui"` |
| XUI_LOG_FOLDER | `string` | `"/var/log"` | | XUI_LOG_FOLDER | `string` | `"/var/log"` |
Ejemplo: Ejemplo:

View File

@@ -28,10 +28,10 @@ bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.
## Install Custom Version ## Install Custom Version
To install your desired version, add the version to the end of the installation command. e.g., ver `v2.3.0`: To install your desired version, add the version to the end of the installation command. e.g., ver `v2.3.4`:
``` ```
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) v2.3.0 bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) v2.3.4
``` ```
## SSL Certificate ## SSL Certificate
@@ -201,6 +201,7 @@ remove 3x-ui from docker
- AlmaLinux 9+ - AlmaLinux 9+
- Rocky Linux 9+ - Rocky Linux 9+
- Oracle Linux 8+ - Oracle Linux 8+
- OpenSUSE Tubleweed
## Supported Architectures and Devices ## Supported Architectures and Devices
@@ -432,7 +433,7 @@ Enter the user ID in input field number 4. The Telegram accounts with this id wi
| `POST` | `"/resetAllTraffics"` | Reset traffics of all inbounds | | `POST` | `"/resetAllTraffics"` | Reset traffics of all inbounds |
| `POST` | `"/resetAllClientTraffics/:id"` | Reset traffics of all clients in an inbound | | `POST` | `"/resetAllClientTraffics/:id"` | Reset traffics of all clients in an inbound |
| `POST` | `"/delDepletedClients/:id"` | Delete inbound depleted clients (-1: all) | | `POST` | `"/delDepletedClients/:id"` | Delete inbound depleted clients (-1: all) |
| `POST` | `"/onlines"` | Get Online users ( list of emails ) | | `POST` | `"/onlines"` | Get Online users ( list of emails ) |
\*- The field `clientId` should be filled by: \*- The field `clientId` should be filled by:

View File

@@ -28,10 +28,10 @@ bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.
## 安装指定版本 ## 安装指定版本
要安装所需的版本,请将该版本添加到安装命令的末尾。 e.g., ver `v2.2.8`: 要安装所需的版本,请将该版本添加到安装命令的末尾。 e.g., ver `v2.3.4`:
``` ```
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) v2.2.8 bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) v2.3.4
``` ```
## SSL 认证 ## SSL 认证
@@ -185,6 +185,7 @@ systemctl restart x-ui
- Armbian - Armbian
- AlmaLinux 9+ - AlmaLinux 9+
- Rockylinux 9+ - Rockylinux 9+
- OpenSUSE Tubleweed
## 支持的架构和设备 ## 支持的架构和设备
<details> <details>
@@ -395,25 +396,25 @@ Web 面板通过 Telegram Bot 支持每日流量、面板登录、数据库备
- `/login` 使用 `POST` 用户名称 & 密码: `{username: '', password: ''}` 登录 - `/login` 使用 `POST` 用户名称 & 密码: `{username: '', password: ''}` 登录
- `/panel/api/inbounds` 以下操作的基础: - `/panel/api/inbounds` 以下操作的基础:
| 方法 | 路径 | 操作 | | 方法 | 路径 | 操作 |
| :----: | ---------------------------------- | ------------------------------------------- | | :----: | ---------------------------------- | --------------------------------- |
| `GET` | `"/list"` | 获取所有入站 | | `GET` | `"/list"` | 获取所有入站 |
| `GET` | `"/get/:id"` | 获取所有入站以及inbound.id | | `GET` | `"/get/:id"` | 获取所有入站以及inbound.id |
| `GET` | `"/getClientTraffics/:email"` | 通过电子邮件获取客户端流量 | | `GET` | `"/getClientTraffics/:email"` | 通过电子邮件获取客户端流量 |
| `GET` | `"/createbackup"` | Telegram 机器人向管理员发送备份 | | `GET` | `"/createbackup"` | Telegram 机器人向管理员发送备份 |
| `POST` | `"/add"` | 添加入站 | | `POST` | `"/add"` | 添加入站 |
| `POST` | `"/del/:id"` | 删除入站 | | `POST` | `"/del/:id"` | 删除入站 |
| `POST` | `"/update/:id"` | 更新入站 | | `POST` | `"/update/:id"` | 更新入站 |
| `POST` | `"/clientIps/:email"` | 客户端 IP 地址 | | `POST` | `"/clientIps/:email"` | 客户端 IP 地址 |
| `POST` | `"/clearClientIps/:email"` | 清除客户端 IP 地址 | | `POST` | `"/clearClientIps/:email"` | 清除客户端 IP 地址 |
| `POST` | `"/addClient"` | 将客户端添加到入站 | | `POST` | `"/addClient"` | 将客户端添加到入站 |
| `POST` | `"/:id/delClient/:clientId"` | 通过 clientId\* 删除客户端 | | `POST` | `"/:id/delClient/:clientId"` | 通过 clientId\* 删除客户端 |
| `POST` | `"/updateClient/:clientId"` | 通过 clientId\* 更新客户端 | | `POST` | `"/updateClient/:clientId"` | 通过 clientId\* 更新客户端 |
| `POST` | `"/:id/resetClientTraffic/:email"` | 重置客户端的流量 | | `POST` | `"/:id/resetClientTraffic/:email"` | 重置客户端的流量 |
| `POST` | `"/resetAllTraffics"` | 重置所有入站的流量 | | `POST` | `"/resetAllTraffics"` | 重置所有入站的流量 |
| `POST` | `"/resetAllClientTraffics/:id"` | 重置入站中所有客户端的流量 | | `POST` | `"/resetAllClientTraffics/:id"` | 重置入站中所有客户端的流量 |
| `POST` | `"/delDepletedClients/:id"` | 删除入站耗尽的客户端 -1 all | | `POST` | `"/delDepletedClients/:id"` | 删除入站耗尽的客户端 -1 all |
| `POST` | `"/onlines"` | 获取在线用户 电子邮件列表 | | `POST` | `"/onlines"` | 获取在线用户 电子邮件列表 |
\*- `clientId` 项应该使用下列数据 \*- `clientId` 项应该使用下列数据
@@ -433,7 +434,7 @@ Web 面板通过 Telegram Bot 支持每日流量、面板登录、数据库备
#### Usage #### Usage
| 变量 | Type | 默认 | | 变量 | Type | 默认 |
| -------------- | :--------------------------------------------: | :------------ | | -------------- | :--------------------------------------------: | :------------ |
| XUI_LOG_LEVEL | `"debug"` \| `"info"` \| `"warn"` \| `"error"` | `"info"` | | XUI_LOG_LEVEL | `"debug"` \| `"info"` \| `"warn"` \| `"error"` | `"info"` |
| XUI_DEBUG | `boolean` | `false` | | XUI_DEBUG | `boolean` | `false` |

View File

@@ -1 +1 @@
2.3.0 2.3.4

61
go.mod
View File

@@ -1,33 +1,34 @@
module x-ui module x-ui
go 1.22.2 go 1.22.3
require ( require (
github.com/Calidity/gin-sessions v1.3.1 github.com/gin-contrib/gzip v1.0.1
github.com/gin-contrib/gzip v1.0.0 github.com/gin-contrib/sessions v1.0.1
github.com/gin-gonic/gin v1.9.1 github.com/gin-gonic/gin v1.10.0
github.com/goccy/go-json v0.10.2 github.com/goccy/go-json v0.10.3
github.com/mymmrac/telego v0.29.2 github.com/mymmrac/telego v0.29.2
github.com/nicksnyder/go-i18n/v2 v2.4.0 github.com/nicksnyder/go-i18n/v2 v2.4.0
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
github.com/pelletier/go-toml/v2 v2.2.1 github.com/pelletier/go-toml/v2 v2.2.2
github.com/robfig/cron/v3 v3.0.1 github.com/robfig/cron/v3 v3.0.1
github.com/shirou/gopsutil/v3 v3.24.3 github.com/shirou/gopsutil/v3 v3.24.4
github.com/valyala/fasthttp v1.52.0 github.com/valyala/fasthttp v1.53.0
github.com/xtls/xray-core v1.8.10 github.com/xtls/xray-core v1.8.13
go.uber.org/atomic v1.11.0 go.uber.org/atomic v1.11.0
golang.org/x/text v0.14.0 golang.org/x/text v0.15.0
google.golang.org/grpc v1.63.2 google.golang.org/grpc v1.64.0
gorm.io/driver/sqlite v1.5.5 gorm.io/driver/sqlite v1.5.5
gorm.io/gorm v1.25.9 gorm.io/gorm v1.25.10
) )
require ( require (
github.com/andybalholm/brotli v1.1.0 // indirect github.com/andybalholm/brotli v1.1.0 // indirect
github.com/bytedance/sonic v1.11.3 // indirect github.com/bytedance/sonic v1.11.6 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect github.com/bytedance/sonic/loader v0.1.1 // indirect
github.com/chenzhuoyu/iasm v0.9.1 // indirect github.com/cloudflare/circl v1.3.8 // indirect
github.com/cloudflare/circl v1.3.7 // indirect github.com/cloudwego/base64x v0.1.4 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 // indirect github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 // indirect
github.com/fasthttp/router v1.5.0 // indirect github.com/fasthttp/router v1.5.0 // indirect
github.com/francoispqt/gojay v1.2.13 // indirect github.com/francoispqt/gojay v1.2.13 // indirect
@@ -36,7 +37,7 @@ require (
github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.19.0 // indirect github.com/go-playground/validator/v10 v10.20.0 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/google/btree v1.1.2 // indirect github.com/google/btree v1.1.2 // indirect
github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7 // indirect github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7 // indirect
@@ -59,8 +60,8 @@ require (
github.com/onsi/ginkgo/v2 v2.16.0 // indirect github.com/onsi/ginkgo/v2 v2.16.0 // indirect
github.com/pires/go-proxyproto v0.7.0 // indirect github.com/pires/go-proxyproto v0.7.0 // indirect
github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b // indirect github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b // indirect
github.com/quic-go/quic-go v0.42.0 // indirect github.com/quic-go/quic-go v0.44.0 // indirect
github.com/refraction-networking/utls v1.6.3 // indirect github.com/refraction-networking/utls v1.6.6 // indirect
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
github.com/rogpeppe/go-internal v1.11.0 // indirect github.com/rogpeppe/go-internal v1.11.0 // indirect
github.com/sagernet/sing v0.3.8 // indirect github.com/sagernet/sing v0.3.8 // indirect
@@ -77,23 +78,23 @@ require (
github.com/valyala/fastjson v1.6.4 // indirect github.com/valyala/fastjson v1.6.4 // indirect
github.com/vishvananda/netlink v1.2.1-beta.2.0.20230316163032-ced5aaba43e3 // indirect github.com/vishvananda/netlink v1.2.1-beta.2.0.20230316163032-ced5aaba43e3 // indirect
github.com/vishvananda/netns v0.0.4 // indirect github.com/vishvananda/netns v0.0.4 // indirect
github.com/xtls/reality v0.0.0-20231112171332-de1173cf2b19 // indirect github.com/xtls/reality v0.0.0-20240429224917-ecc4401070cc // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect
go.uber.org/mock v0.4.0 // indirect go.uber.org/mock v0.4.0 // indirect
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
golang.org/x/arch v0.7.0 // indirect golang.org/x/arch v0.8.0 // indirect
golang.org/x/crypto v0.22.0 // indirect golang.org/x/crypto v0.23.0 // indirect
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect
golang.org/x/mod v0.16.0 // indirect golang.org/x/mod v0.17.0 // indirect
golang.org/x/net v0.24.0 // indirect golang.org/x/net v0.25.0 // indirect
golang.org/x/sys v0.19.0 // indirect golang.org/x/sys v0.20.0 // indirect
golang.org/x/time v0.5.0 // indirect golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.19.0 // indirect golang.org/x/tools v0.21.0 // indirect
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 // indirect golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be // indirect
google.golang.org/protobuf v1.33.0 // indirect google.golang.org/protobuf v1.34.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
gvisor.dev/gvisor v0.0.0-20231104011432-48a6d7d5bd0b // indirect gvisor.dev/gvisor v0.0.0-20231202080848-1f7806d17489 // indirect
lukechampine.com/blake3 v1.2.1 // indirect lukechampine.com/blake3 v1.3.0 // indirect
) )

133
go.sum
View File

@@ -10,28 +10,25 @@ git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGy
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/Calidity/gin-sessions v1.3.1 h1:nF3dCBWa7TZ4j26iYLwGRmzZy9YODhWoOS3fmi+snyE= github.com/OmarTariq612/goech v0.0.0-20240405204721-8e2e1dafd3a0 h1:Wo41lDOevRJSGpevP+8Pk5bANX7fJacO2w04aqLiC5I=
github.com/Calidity/gin-sessions v1.3.1/go.mod h1:I0+QE6qkO50TeN/n6If6novvxHk4Isvr23U8EdvPdns= github.com/OmarTariq612/goech v0.0.0-20240405204721-8e2e1dafd3a0/go.mod h1:FVGavL/QEBQDcBpr3fAojoK17xX5k9bicBphrOpP7uM=
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM= github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
github.com/bytedance/sonic v1.11.3 h1:jRN+yEjakWh8aK5FzrciUHG8OFXK+4/KrAX/ysEtHAA= github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
github.com/bytedance/sonic v1.11.3/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0=
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA=
github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
github.com/chenzhuoyu/iasm v0.9.1 h1:tUHQJXo3NhBqw6s33wkGn9SP3bvrWLdlVIJ3hQBL7P0=
github.com/chenzhuoyu/iasm v0.9.1/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= github.com/cloudflare/circl v1.3.8 h1:j+V8jJt09PoeMFIu2uh5JUyEaIHTXVOHslFoLNAKqwI=
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= github.com/cloudflare/circl v1.3.8/go.mod h1:PDRU+oXvdD7KCtgKxW95M5Z8BpSCJXQORiZFnBQS5QU=
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@@ -51,12 +48,14 @@ github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcP
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 h1:Arcl6UOIS/kgO2nW3A65HN+7CMjSDP/gofXL4CZt1V4= github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 h1:Arcl6UOIS/kgO2nW3A65HN+7CMjSDP/gofXL4CZt1V4=
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I= github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I=
github.com/gin-contrib/gzip v1.0.0 h1:UKN586Po/92IDX6ie5CWLgMI81obiIp5nSP85T3wlTk= github.com/gin-contrib/gzip v1.0.1 h1:HQ8ENHODeLY7a4g1Au/46Z92bdGFl74OhxcZble9WJE=
github.com/gin-contrib/gzip v1.0.0/go.mod h1:CtG7tQrPB3vIBo6Gat9FVUsis+1emjvQqd66ME5TdnE= github.com/gin-contrib/gzip v1.0.1/go.mod h1:njt428fdUNRvjuJf16tZMYZ2Yl+WQB53X5wmhDwXvC4=
github.com/gin-contrib/sessions v1.0.1 h1:3hsJyNs7v7N8OtelFmYXFrulAf6zSR7nW/putcPEHxI=
github.com/gin-contrib/sessions v1.0.1/go.mod h1:ouxSFM24/OgIud5MJYQJLpy6AwxQ5EYO9yLhbtObGkM=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
@@ -70,12 +69,12 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.19.0 h1:ol+5Fu+cSq9JD7SoSqe04GMI92cbn0+wvQ3bZ8b/AU4= github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
github.com/go-playground/validator/v10 v10.19.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
@@ -155,8 +154,8 @@ github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4= github.com/miekg/dns v1.1.59 h1:C9EXc/UToRwKLhK5wKU/I4QVsBUc8kE6MkHBkeypWZs=
github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY= github.com/miekg/dns v1.1.59/go.mod h1:nZpewl5p6IvctfgrckopVx2OlSEHPRO/U4SYkRklrEk=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -178,8 +177,8 @@ github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWEr
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/pelletier/go-toml/v2 v2.2.1 h1:9TA9+T8+8CUCO2+WYnDLCgrYi9+omqKXyjDtosvtEhg= github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
github.com/pelletier/go-toml/v2 v2.2.1/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/pires/go-proxyproto v0.7.0 h1:IukmRewDQFWC7kfnb66CSomk2q/seBuilHBYFwyq0Hs= github.com/pires/go-proxyproto v0.7.0 h1:IukmRewDQFWC7kfnb66CSomk2q/seBuilHBYFwyq0Hs=
github.com/pires/go-proxyproto v0.7.0/go.mod h1:Vz/1JPY/OACxWGQNIRY2BeyDmpoaWmEP40O9LbuiFR4= github.com/pires/go-proxyproto v0.7.0/go.mod h1:Vz/1JPY/OACxWGQNIRY2BeyDmpoaWmEP40O9LbuiFR4=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -192,10 +191,10 @@ github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXP
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/quic-go/quic-go v0.42.0 h1:uSfdap0eveIl8KXnipv9K7nlwZ5IqLlYOpJ58u5utpM= github.com/quic-go/quic-go v0.44.0 h1:So5wOr7jyO4vzL2sd8/pD9Kesciv91zSk8BoFngItQ0=
github.com/quic-go/quic-go v0.42.0/go.mod h1:132kz4kL3F9vxhW3CtQJLDVwcFe5wdWeJXXijhsO57M= github.com/quic-go/quic-go v0.44.0/go.mod h1:z4cx/9Ny9UtGITIPzmPTXh1ULfOyWh4qGQlpnPcWmek=
github.com/refraction-networking/utls v1.6.3 h1:MFOfRN35sSx6K5AZNIoESsBuBxS2LCgRilRIdHb6fDc= github.com/refraction-networking/utls v1.6.6 h1:igFsYBUJPYM8Rno9xUuDoM5GQrVEqY4llzEXOkL43Ig=
github.com/refraction-networking/utls v1.6.3/go.mod h1:yil9+7qSl+gBwJqztoQseO6Pr3h62pQoY1lXiNR/FPs= github.com/refraction-networking/utls v1.6.6/go.mod h1:BC3O4vQzye5hqpmDTWUqi4P5DDhzJfkV1tdqtawQIH0=
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg= github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s= github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
@@ -212,8 +211,8 @@ github.com/savsgio/gotils v0.0.0-20240303185622-093b76447511/go.mod h1:sM7Mt7uEo
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb h1:XfLJSPIOUX+osiMraVgIrMR27uMXnRJWGm1+GL8/63U= github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb h1:XfLJSPIOUX+osiMraVgIrMR27uMXnRJWGm1+GL8/63U=
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg= github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/shirou/gopsutil/v3 v3.24.3 h1:eoUGJSmdfLzJ3mxIhmOAhgKEKgQkeOwKpz1NbhVnuPE= github.com/shirou/gopsutil/v3 v3.24.4 h1:dEHgzZXt4LMNm+oYELpzl9YCqV65Yr/6SfrvgRBtXeU=
github.com/shirou/gopsutil/v3 v3.24.3/go.mod h1:JpND7O217xa72ewWz9zN2eIIkPWsDN/3pl0H8Qt0uwg= github.com/shirou/gopsutil/v3 v3.24.4/go.mod h1:lTd2mdiOspcqLgAnr9/nGi71NkeMpWKdmhuxm9GusH8=
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
@@ -269,8 +268,8 @@ github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e h1:5QefA066A1tF
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e/go.mod h1:5t19P9LBIrNamL6AcMQOncg/r10y3Pc01AbHeMhwlpU= github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e/go.mod h1:5t19P9LBIrNamL6AcMQOncg/r10y3Pc01AbHeMhwlpU=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.52.0 h1:wqBQpxH71XW0e2g+Og4dzQM8pk34aFYlA1Ga8db7gU0= github.com/valyala/fasthttp v1.53.0 h1:lW/+SUkOxCx2vlIu0iaImv4JLrVRnbbkpCoaawvA4zc=
github.com/valyala/fasthttp v1.52.0/go.mod h1:hf5C4QnVMkNXMspnsUlfM3WitlgYflyhHYoKol/szxQ= github.com/valyala/fasthttp v1.53.0/go.mod h1:6dt4/8olwq9QARP/TDuPmWyWcl4byhpvTJ4AAtcz+QM=
github.com/valyala/fastjson v1.6.4 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ= github.com/valyala/fastjson v1.6.4 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ=
github.com/valyala/fastjson v1.6.4/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY= github.com/valyala/fastjson v1.6.4/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU= github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
@@ -280,10 +279,10 @@ github.com/vishvananda/netlink v1.2.1-beta.2.0.20230316163032-ced5aaba43e3/go.mo
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8= github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
github.com/xtls/reality v0.0.0-20231112171332-de1173cf2b19 h1:capMfFYRgH9BCLd6A3Er/cH3A9Nz3CU2KwxwOQZIePI= github.com/xtls/reality v0.0.0-20240429224917-ecc4401070cc h1:0Nj8T1n7F7+v4vRVroaJIvY6R0vNABLfPH+lzPHRJvI=
github.com/xtls/reality v0.0.0-20231112171332-de1173cf2b19/go.mod h1:dm4y/1QwzjGaK17ofi0Vs6NpKAHegZky8qk6J2JJZAE= github.com/xtls/reality v0.0.0-20240429224917-ecc4401070cc/go.mod h1:dm4y/1QwzjGaK17ofi0Vs6NpKAHegZky8qk6J2JJZAE=
github.com/xtls/xray-core v1.8.10 h1:qxae6gSteonpPI7EZyOyqw5HmRVRzmU07qs0l1GNqz4= github.com/xtls/xray-core v1.8.13 h1:x1wG7iC8xjVnlayGA+S4c7EUobx6Gg0ttSm2SFwLrik=
github.com/xtls/xray-core v1.8.10/go.mod h1:Mc1t+kLBPE5a1EpsUNKjMLviGz3Y0XywxeEraJZAMlI= github.com/xtls/xray-core v1.8.13/go.mod h1:h4A21+wcMIFDAqHzSKTXMZQr3lto6WOTUWodamVi/kM=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
@@ -295,22 +294,22 @@ go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M= go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.7.0 h1:pskyeJh/3AmoQ8CPE95vxHLqp1G1GfGNXTmcl9NEKTc= golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw= golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw=
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ= golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -319,8 +318,8 @@ golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73r
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -330,8 +329,8 @@ golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -346,13 +345,13 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
@@ -361,8 +360,8 @@ golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGm
golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw= golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw=
golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc= golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
@@ -386,10 +385,10 @@ google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmE
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM= google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY=
google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
@@ -404,16 +403,16 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/sqlite v1.5.5 h1:7MDMtUZhV065SilG62E0MquljeArQZNfJnjd9i9gx3E= gorm.io/driver/sqlite v1.5.5 h1:7MDMtUZhV065SilG62E0MquljeArQZNfJnjd9i9gx3E=
gorm.io/driver/sqlite v1.5.5/go.mod h1:6NgQ7sQWAIFsPrJJl1lSNSu2TABh0ZZ/zm5fosATavE= gorm.io/driver/sqlite v1.5.5/go.mod h1:6NgQ7sQWAIFsPrJJl1lSNSu2TABh0ZZ/zm5fosATavE=
gorm.io/gorm v1.25.9 h1:wct0gxZIELDk8+ZqF/MVnHLkA1rvYlBWUMv2EdsK1g8= gorm.io/gorm v1.25.10 h1:dQpO+33KalOA+aFYGlK+EfxcI5MbO7EP2yYygwh9h+s=
gorm.io/gorm v1.25.9/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= gorm.io/gorm v1.25.10/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o= grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
gvisor.dev/gvisor v0.0.0-20231104011432-48a6d7d5bd0b h1:yqkg3pTifuKukuWanp8spDsL4irJkHF5WI0J47hU87o= gvisor.dev/gvisor v0.0.0-20231202080848-1f7806d17489 h1:ze1vwAdliUAr68RQ5NtufWaXaOg8WUO2OACzEV+TNdE=
gvisor.dev/gvisor v0.0.0-20231104011432-48a6d7d5bd0b/go.mod h1:10sU+Uh5KKNv1+2x2A0Gvzt8FjD3ASIhorV3YsauXhk= gvisor.dev/gvisor v0.0.0-20231202080848-1f7806d17489/go.mod h1:10sU+Uh5KKNv1+2x2A0Gvzt8FjD3ASIhorV3YsauXhk=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
lukechampine.com/blake3 v1.2.1 h1:YuqqRuaqsGV71BV/nm9xlI0MKUv4QC54jQnBChWbGnI= lukechampine.com/blake3 v1.3.0 h1:sJ3XhFINmHSrYCgl958hscfIa3bw8x4DqMP3u1YvoYE=
lukechampine.com/blake3 v1.2.1/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k= lukechampine.com/blake3 v1.3.0/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k=
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck= sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck=

View File

@@ -49,6 +49,8 @@ elif [[ "${release}" == "manjaro" ]]; then
echo "Your OS is Manjaro" echo "Your OS is Manjaro"
elif [[ "${release}" == "armbian" ]]; then elif [[ "${release}" == "armbian" ]]; then
echo "Your OS is Armbian" echo "Your OS is Armbian"
elif [[ "${release}" == "opensuse-tumbleweed" ]]; then
echo "Your OS is OpenSUSE Tumbleweed"
elif [[ "${release}" == "centos" ]]; then elif [[ "${release}" == "centos" ]]; then
if [[ ${os_version} -lt 8 ]]; then if [[ ${os_version} -lt 8 ]]; then
echo -e "${red} Please use CentOS 8 or higher ${plain}\n" && exit 1 echo -e "${red} Please use CentOS 8 or higher ${plain}\n" && exit 1
@@ -91,12 +93,16 @@ else
echo "- AlmaLinux 9+" echo "- AlmaLinux 9+"
echo "- Rocky Linux 9+" echo "- Rocky Linux 9+"
echo "- Oracle Linux 8+" echo "- Oracle Linux 8+"
echo "- OpenSUSE Tumbleweed"
exit 1 exit 1
fi fi
install_base() { install_base() {
case "${release}" in case "${release}" in
ubuntu | debian | armbian)
apt-get update && apt-get install -y -q wget curl tar tzdata
;;
centos | almalinux | rocky | oracle) centos | almalinux | rocky | oracle)
yum -y update && yum install -y -q wget curl tar tzdata yum -y update && yum install -y -q wget curl tar tzdata
;; ;;
@@ -106,6 +112,9 @@ install_base() {
arch | manjaro | parch) arch | manjaro | parch)
pacman -Syu && pacman -Syu --noconfirm wget curl tar tzdata pacman -Syu && pacman -Syu --noconfirm wget curl tar tzdata
;; ;;
opensuse-tumbleweed)
zypper refresh && zypper -q install -y wget curl tar timezone
;;
*) *)
apt-get update && apt install -y -q wget curl tar tzdata apt-get update && apt install -y -q wget curl tar tzdata
;; ;;

22
main.go
View File

@@ -125,22 +125,35 @@ func showSetting(show bool) {
settingService := service.SettingService{} settingService := service.SettingService{}
port, err := settingService.GetPort() port, err := settingService.GetPort()
if err != nil { if err != nil {
fmt.Println("get current port failed,error info:", err) fmt.Println("get current port failed, error info:", err)
} }
webBasePath, err := settingService.GetBasePath()
if err != nil {
fmt.Println("get webBasePath failed, error info:", err)
}
userService := service.UserService{} userService := service.UserService{}
userModel, err := userService.GetFirstUser() userModel, err := userService.GetFirstUser()
if err != nil { if err != nil {
fmt.Println("get current user info failed,error info:", err) fmt.Println("get current user info failed, error info:", err)
} }
username := userModel.Username username := userModel.Username
userpasswd := userModel.Password userpasswd := userModel.Password
if (username == "") || (userpasswd == "") { if username == "" || userpasswd == "" {
fmt.Println("current username or password is empty") fmt.Println("current username or password is empty")
} }
fmt.Println("current panel settings as follows:") fmt.Println("current panel settings as follows:")
fmt.Println("username:", username) fmt.Println("username:", username)
fmt.Println("userpasswd:", userpasswd) fmt.Println("userpasswd:", userpasswd)
fmt.Println("port:", port) fmt.Println("port:", port)
if webBasePath != "" {
fmt.Println("webBasePath:", webBasePath)
} else {
fmt.Println("webBasePath is not set")
}
} }
} }
@@ -220,6 +233,7 @@ func updateSetting(port int, username string, password string) {
fmt.Printf("set port %v success", port) fmt.Printf("set port %v success", port)
} }
} }
if username != "" || password != "" { if username != "" || password != "" {
userService := service.UserService{} userService := service.UserService{}
err := userService.UpdateFirstUser(username, password) err := userService.UpdateFirstUser(username, password)
@@ -288,6 +302,7 @@ func main() {
var port int var port int
var username string var username string
var password string var password string
var webBasePath string
var tgbottoken string var tgbottoken string
var tgbotchatid string var tgbotchatid string
var enabletgbot bool var enabletgbot bool
@@ -301,6 +316,7 @@ func main() {
settingCmd.IntVar(&port, "port", 0, "set panel port") settingCmd.IntVar(&port, "port", 0, "set panel port")
settingCmd.StringVar(&username, "username", "", "set login username") settingCmd.StringVar(&username, "username", "", "set login username")
settingCmd.StringVar(&password, "password", "", "set login password") settingCmd.StringVar(&password, "password", "", "set login password")
settingCmd.StringVar(&webBasePath, "webBasePath", "", "set web base path")
settingCmd.StringVar(&tgbottoken, "tgbottoken", "", "set telegram bot token") settingCmd.StringVar(&tgbottoken, "tgbottoken", "", "set telegram bot token")
settingCmd.StringVar(&tgbotRuntime, "tgbotRuntime", "", "set telegram bot cron time") settingCmd.StringVar(&tgbotRuntime, "tgbotRuntime", "", "set telegram bot cron time")
settingCmd.StringVar(&tgbotchatid, "tgbotchatid", "", "set telegram bot chat id") settingCmd.StringVar(&tgbotchatid, "tgbotchatid", "", "set telegram bot chat id")

View File

@@ -2,7 +2,7 @@ package sub
import ( import (
"encoding/base64" "encoding/base64"
"strings" "net"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
@@ -54,7 +54,18 @@ func (a *SUBController) initRouter(g *gin.RouterGroup) {
func (a *SUBController) subs(c *gin.Context) { func (a *SUBController) subs(c *gin.Context) {
subId := c.Param("subid") subId := c.Param("subid")
host := strings.Split(c.Request.Host, ":")[0] host := c.GetHeader("X-Forwarded-Host")
if host == "" {
host = c.GetHeader("X-Real-IP")
}
if host == "" {
var err error
host, _, err = net.SplitHostPort(c.Request.Host)
if err != nil {
host = c.Request.Host
}
}
host = host
subs, header, err := a.subService.GetSubs(subId, host) subs, header, err := a.subService.GetSubs(subId, host)
if err != nil || len(subs) == 0 { if err != nil || len(subs) == 0 {
c.String(400, "Error!") c.String(400, "Error!")
@@ -79,7 +90,18 @@ func (a *SUBController) subs(c *gin.Context) {
func (a *SUBController) subJsons(c *gin.Context) { func (a *SUBController) subJsons(c *gin.Context) {
subId := c.Param("subid") subId := c.Param("subid")
host := strings.Split(c.Request.Host, ":")[0] host := c.GetHeader("X-Forwarded-Host")
if host == "" {
host = c.GetHeader("X-Real-IP")
}
if host == "" {
var err error
host, _, err = net.SplitHostPort(c.Request.Host)
if err != nil {
host = c.Request.Host
}
}
host = host
jsonSub, header, err := a.subJsonService.GetJson(subId, host) jsonSub, header, err := a.subJsonService.GetJson(subId, host)
if err != nil || len(jsonSub) == 0 { if err != nil || len(jsonSub) == 0 {
c.String(400, "Error!") c.String(400, "Error!")

View File

@@ -211,7 +211,7 @@ func (s *SubJsonService) streamData(stream string) map[string]interface{} {
delete(streamSettings, "sockopt") delete(streamSettings, "sockopt")
if s.fragment != "" { if s.fragment != "" {
streamSettings["sockopt"] = json_util.RawMessage(`{"dialerProxy": "fragment", "tcpKeepAliveIdle": 100, "tcpNoDelay": true}`) streamSettings["sockopt"] = json_util.RawMessage(`{"dialerProxy": "fragment", "tcpKeepAliveIdle": 100, "tcpMptcp": true, "tcpNoDelay": true}`)
} }
// remove proxy protocol // remove proxy protocol

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -1,86 +0,0 @@
/*
Copyright (C) 2011 by MarkLogic Corporation
Author: Mike Brevoort <mike@brevoort.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
.cm-s-xq.CodeMirror { border-radius: 1.5rem; border: 1px solid #d9d9d9; height: auto; }
.cm-s-xq.CodeMirror:hover { background-color: rgb(232 244 242); border-color: #18947b; transition: all .3s; }
.cm-s-xq .CodeMirror-gutters { border-right: 1px solid #ddd; background-color: rgb(221 221 221 / 20%); white-space: nowrap; }
.cm-s-xq span.cm-keyword { line-height: 1em; font-weight: bold; color: #5A5CAD; }
.cm-s-xq span.cm-atom { color: #7A316F; font-weight:bold; }
.cm-s-xq span.cm-number { color: #e36209; }
.cm-s-xq span.cm-def { text-decoration:underline; }
.cm-s-xq span.cm-variable { color: black; }
.cm-s-xq span.cm-variable-2 { color:black; }
.cm-s-xq span.cm-variable-3, .cm-s-xq span.cm-type { color: black; }
.cm-s-xq span.cm-property { color: #008771; }
.cm-s-xq span.cm-operator {}
.cm-s-xq span.cm-comment { color: #bbbbbb; font-style: italic; }
.cm-s-xq span.cm-string {}
.cm-s-xq span.cm-meta { color: yellow; }
.cm-s-xq span.cm-qualifier { color: grey; }
.cm-s-xq span.cm-builtin { color: #7EA656; }
.cm-s-xq span.cm-bracket { color: #cc7; }
.cm-s-xq span.cm-tag { color: #3F7F7F; }
.cm-s-xq span.cm-attribute { color: #7F007F; }
.cm-s-xq span.cm-error { color: #e04141; }
.cm-s-xq .CodeMirror-activeline-background { background: #e8f2ff; }
.cm-s-xq .CodeMirror-matchingbracket { outline:1px solid grey;color:black !important;background:yellow; }
.dark .cm-s-xq.CodeMirror { background-color: var(--dark-color-surface-200); border-color: var(--dark-color-surface-300); color: rgb(255 255 255 / 65%); }
.dark .cm-s-xq.CodeMirror:hover { background-color: rgb(0 50 42 / 30%); border-color: #008771; transition: all .3s; }
.dark .cm-s-xq div.CodeMirror-selected { background: var(--dark-color-codemirror-line-selection); }
.dark .cm-s-xq .CodeMirror-line::selection, .dark .cm-s-xq .CodeMirror-line > span::selection, .dark .cm-s-xq .CodeMirror-line > span > span::selection { background: var(--dark-color-codemirror-line-selection); }
.dark .cm-s-xq .CodeMirror-line::-moz-selection, .dark .cm-s-xq .CodeMirror-line > span::-moz-selection, .dark .cm-s-xq .CodeMirror-line > span > span::-moz-selection { background: var(--dark-color-codemirror-line-selection); }
.dark .cm-s-xq .CodeMirror-gutters { background: rgb(0 0 0 / 30%); border-right: 1px solid var(--dark-color-surface-300); }
.dark .cm-s-xq .CodeMirror-guttermarker { color: #FFBD40; }
.dark .cm-s-xq .CodeMirror-guttermarker-subtle { color: rgb(255 255 255 / 70%); }
.dark .cm-s-xq .CodeMirror-linenumber { color: rgb(255 255 255 / 50%); }
.dark .cm-s-xq .CodeMirror-cursor { border-left: 1px solid white; }
.dark .cm-s-xq span.cm-keyword { color: #FFBD40; }
.dark .cm-s-xq span.cm-atom { color: #c099ff; }
.dark .cm-s-xq span.cm-number { color: #9ccfd8; }
.dark .cm-s-xq span.cm-def { color: #FFF; text-decoration:underline; }
.dark .cm-s-xq span.cm-variable { color: #FFF; }
.dark .cm-s-xq span.cm-variable-2 { color: #EEE; }
.dark .cm-s-xq span.cm-variable-3, .dark .cm-s-xq span.cm-type { color: #DDD; }
.dark .cm-s-xq span.cm-property { color: #f6c177; }
.dark .cm-s-xq span.cm-operator {}
.dark .cm-s-xq span.cm-comment { color: gray; }
.dark .cm-s-xq span.cm-string {}
.dark .cm-s-xq span.cm-meta { color: yellow; }
.dark .cm-s-xq span.cm-qualifier { color: #FFF700; }
.dark .cm-s-xq span.cm-builtin { color: #30a; }
.dark .cm-s-xq span.cm-bracket { color: #cc7; }
.dark .cm-s-xq span.cm-tag { color: #FFBD40; }
.dark .cm-s-xq span.cm-attribute { color: #FFF700; }
.dark .cm-s-xq span.cm-error { color: #e04141; }
.dark .cm-s-xq .CodeMirror-activeline-background { background: #27282E; }
.dark .cm-s-xq .CodeMirror-matchingbracket { outline:1px solid grey; color:white !important; }
.Line-Hover{transition: all .2s;}
.Line-Hover:hover{ background-color: rgba(0, 102, 85, 0.05) !important; }
.dark .Line-Hover:hover{ background-color: var(--dark-color-codemirror-line-hover) !important; }
.CodeMirror-foldmarker { color: #fc8800; text-shadow: #ffd8aa 1px 1px 2px, #ffd8aa -1px -1px 2px, #ffd8aa 1px -1px 2px, #ffd8aa -1px 1px 2px; font-family: arial; line-height: .3; cursor: pointer; }
.dark .CodeMirror-foldmarker { color: #ffffff; text-shadow: #bbb 1px 1px 2px, #bbb -1px -1px 2px, #bbb 1px -1px 2px, #bbb -1px 1px 2px; font-family: arial; line-height: .3; cursor: pointer; }

1
web/assets/codemirror/xq.min.css vendored Normal file
View File

@@ -0,0 +1 @@
.CodeMirror{background-color:#f6fbfa;border:1px solid #d9d9d9}.cm-s-xq.CodeMirror{border-radius:1.5rem;height:auto;transition:background-color .3s,border-color 0.3s}.cm-s-xq.CodeMirror:hover{background-color:#e8f4f2;border-color:#18947b}.cm-s-xq div.CodeMirror-selected{background:rgb(0 102 85 / .1)}.cm-s-xq .CodeMirror-line::selection,.cm-s-xq .CodeMirror-line>span::selection,.cm-s-xq .CodeMirror-line>span>span::selection{background:rgb(0 102 85 / .1)}.cm-s-xq .CodeMirror-line::-moz-selection,.cm-s-xq .CodeMirror-line>span::-moz-selection,.cm-s-xq .CodeMirror-line>span>span::-moz-selection{background:rgb(0 102 85 / .1)}.cm-s-xq .CodeMirror-gutters{border-right:1px solid #ddd;background-color:rgb(221 221 221 / 20%);white-space:nowrap}.cm-s-xq span.cm-keyword{line-height:1em;font-weight:700;color:#5A5CAD}.cm-s-xq span.cm-atom{color:#7A316F;font-weight:700}.cm-s-xq span.cm-number{color:#e36209}.cm-s-xq span.cm-def{text-decoration:underline}.cm-s-xq span.cm-variable{color:#000}.cm-s-xq span.cm-variable-2{color:#000}.cm-s-xq span.cm-variable-3,.cm-s-xq span.cm-type{color:#000}.cm-s-xq span.cm-property{color:#008771}.cm-s-xq span.cm-comment{color:#bbb;font-style:italic}.cm-s-xq span.cm-meta{color:#ff0}.cm-s-xq span.cm-qualifier{color:grey}.cm-s-xq span.cm-builtin{color:#7EA656}.cm-s-xq span.cm-bracket{color:#cc7}.cm-s-xq span.cm-tag{color:#3F7F7F}.cm-s-xq span.cm-attribute{color:#7F007F}.cm-s-xq span.cm-error{color:#e04141}.cm-s-xq .CodeMirror-activeline-background{background:#e8f2ff}.cm-s-xq .CodeMirror-matchingbracket{outline:1px solid grey;color:black!important;background:#ff0}.dark .CodeMirror{background-color:var(--dark-color-surface-200);border-color:var(--dark-color-surface-300)}.dark .cm-s-xq.CodeMirror{background-color:var(--dark-color-surface-200);border-color:var(--dark-color-surface-300);color:rgb(255 255 255 / 65%)}.dark .cm-s-xq.CodeMirror:hover{background-color:rgb(0 50 42 / 30%);border-color:#008771}.dark .cm-s-xq div.CodeMirror-selected{background:var(--dark-color-codemirror-line-selection)}.dark .cm-s-xq .CodeMirror-line::selection,.dark .cm-s-xq .CodeMirror-line>span::selection,.dark .cm-s-xq .CodeMirror-line>span>span::selection{background:var(--dark-color-codemirror-line-selection)}.dark .cm-s-xq .CodeMirror-line::-moz-selection,.dark .cm-s-xq .CodeMirror-line>span::-moz-selection,.dark .cm-s-xq .CodeMirror-line>span>span::-moz-selection{background:var(--dark-color-codemirror-line-selection)}.dark .cm-s-xq .CodeMirror-gutters{background:rgb(0 0 0 / 30%);border-right:1px solid var(--dark-color-surface-300)}.dark .cm-s-xq .CodeMirror-guttermarker{color:#FFBD40}.dark .cm-s-xq .CodeMirror-guttermarker-subtle{color:rgb(255 255 255 / 70%)}.dark .cm-s-xq .CodeMirror-linenumber{color:rgb(255 255 255 / 50%)}.dark .cm-s-xq .CodeMirror-cursor{border-left:1px solid #fff}.dark .cm-s-xq span.cm-keyword{color:#FFBD40}.dark .cm-s-xq span.cm-atom{color:#c099ff}.dark .cm-s-xq span.cm-number{color:#9ccfd8}.dark .cm-s-xq span.cm-def{color:#FFF;text-decoration:underline}.dark .cm-s-xq span.cm-variable{color:#FFF}.dark .cm-s-xq span.cm-variable-2{color:#EEE}.dark .cm-s-xq span.cm-variable-3,.dark .cm-s-xq span.cm-type{color:#DDD}.dark .cm-s-xq span.cm-property{color:#f6c177}.dark .cm-s-xq span.cm-comment{color:gray}.dark .cm-s-xq span.cm-meta{color:#ff0}.dark .cm-s-xq span.cm-qualifier{color:#FFF700}.dark .cm-s-xq span.cm-builtin{color:#30a}.dark .cm-s-xq span.cm-bracket{color:#cc7}.dark .cm-s-xq span.cm-tag{color:#FFBD40}.dark .cm-s-xq span.cm-attribute{color:#FFF700}.dark .cm-s-xq span.cm-error{color:#e04141}.dark .cm-s-xq .CodeMirror-activeline-background{background:#27282E}.dark .cm-s-xq .CodeMirror-matchingbracket{outline:1px solid grey;color:white!important}.Line-Hover{transition:all .2s}.CodeMirror pre.CodeMirror-line:hover,.CodeMirror pre.CodeMirror-line-like:hover{background-color:rgb(0 102 85 / .05)}.dark .CodeMirror pre.CodeMirror-line:hover,.CodeMirror pre.CodeMirror-line-like:hover{background-color:var(--dark-color-codemirror-line-hover)}.CodeMirror-foldmarker{color:#fc8800;text-shadow:#ffd8aa 1px 1px 2px,#ffd8aa -1px -1px 2px,#ffd8aa 1px -1px 2px,#ffd8aa -1px 1px 2px;font-family:arial;line-height:.3;cursor:pointer}.dark .CodeMirror-foldmarker{color:#fff;text-shadow:#bbb 1px 1px 2px,#bbb -1px -1px 2px,#bbb 1px -1px 2px,#bbb -1px 1px 2px;font-family:arial;line-height:.3;cursor:pointer}

File diff suppressed because one or more lines are too long

1
web/assets/css/custom.min.css vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -361,11 +361,12 @@ class RealityStreamSettings extends CommonClass {
} }
}; };
class SockoptStreamSettings extends CommonClass { class SockoptStreamSettings extends CommonClass {
constructor(dialerProxy = "", tcpFastOpen = false, tcpKeepAliveInterval = 0, tcpNoDelay = false) { constructor(dialerProxy = "", tcpFastOpen = false, tcpKeepAliveInterval = 0, tcpMptcp = false, tcpNoDelay = false) {
super(); super();
this.dialerProxy = dialerProxy; this.dialerProxy = dialerProxy;
this.tcpFastOpen = tcpFastOpen; this.tcpFastOpen = tcpFastOpen;
this.tcpKeepAliveInterval = tcpKeepAliveInterval; this.tcpKeepAliveInterval = tcpKeepAliveInterval;
this.tcpMptcp = tcpMptcp;
this.tcpNoDelay = tcpNoDelay; this.tcpNoDelay = tcpNoDelay;
} }
@@ -375,6 +376,7 @@ class SockoptStreamSettings extends CommonClass {
json.dialerProxy, json.dialerProxy,
json.tcpFastOpen, json.tcpFastOpen,
json.tcpKeepAliveInterval, json.tcpKeepAliveInterval,
json.tcpMptcp,
json.tcpNoDelay, json.tcpNoDelay,
); );
} }
@@ -384,6 +386,7 @@ class SockoptStreamSettings extends CommonClass {
dialerProxy: this.dialerProxy, dialerProxy: this.dialerProxy,
tcpFastOpen: this.tcpFastOpen, tcpFastOpen: this.tcpFastOpen,
tcpKeepAliveInterval: this.tcpKeepAliveInterval, tcpKeepAliveInterval: this.tcpKeepAliveInterval,
tcpMptcp: this.tcpMptcp,
tcpNoDelay: this.tcpNoDelay, tcpNoDelay: this.tcpNoDelay,
}; };
} }
@@ -690,12 +693,12 @@ class Outbound extends CommonClass {
url.searchParams.get('quicSecurity') ?? 'none', url.searchParams.get('quicSecurity') ?? 'none',
url.searchParams.get('key') ?? '', url.searchParams.get('key') ?? '',
headerType ?? 'none'); headerType ?? 'none');
} else if (type === 'grpc') { } else if (type === 'grpc') {
stream.grpc = new GrpcStreamSettings( stream.grpc = new GrpcStreamSettings(
url.searchParams.get('serviceName') ?? '', url.searchParams.get('serviceName') ?? '',
url.searchParams.get('authority') ?? '', url.searchParams.get('authority') ?? '',
url.searchParams.get('mode') == 'multi'); url.searchParams.get('mode') == 'multi');
} else if (type === 'httpupgrade') { } else if (type === 'httpupgrade') {
stream.httpupgrade = new HttpUpgradeStreamSettings(path,host); stream.httpupgrade = new HttpUpgradeStreamSettings(path,host);
} }

View File

@@ -84,14 +84,43 @@ const SNIFFING_OPTION = {
FAKEDNS: "fakedns" FAKEDNS: "fakedns"
}; };
const USAGE_OPTION = {
ENCIPHERMENT: "encipherment",
VERIFY: "verify",
ISSUE: "issue",
};
const DOMAIN_STRATEGY_OPTION = {
AS_IS: "AsIs",
USE_IP: "UseIP",
USE_IPV6V4: "UseIPv6v4",
USE_IPV6: "UseIPv6",
USE_IPV4V6: "UseIPv4v6",
USE_IPV4: "UseIPv4",
FORCE_IP: "ForceIP",
FORCE_IPV6V4: "ForceIPv6v4",
FORCE_IPV6: "ForceIPv6",
FORCE_IPV4V6: "ForceIPv4v6",
FORCE_IPV4: "ForceIPv4",
};
const TCP_CONGESTION_OPTION = {
BBR: "bbr",
CUBIC: "cubic",
RENO: "reno",
};
Object.freeze(Protocols); Object.freeze(Protocols);
Object.freeze(SSMethods); Object.freeze(SSMethods);
Object.freeze(XTLS_FLOW_CONTROL); Object.freeze(XTLS_FLOW_CONTROL);
Object.freeze(TLS_FLOW_CONTROL); Object.freeze(TLS_FLOW_CONTROL);
Object.freeze(TLS_VERSION_OPTION); Object.freeze(TLS_VERSION_OPTION);
Object.freeze(TLS_CIPHER_OPTION); Object.freeze(TLS_CIPHER_OPTION);
Object.freeze(UTLS_FINGERPRINT);
Object.freeze(ALPN_OPTION); Object.freeze(ALPN_OPTION);
Object.freeze(SNIFFING_OPTION); Object.freeze(SNIFFING_OPTION);
Object.freeze(USAGE_OPTION);
Object.freeze(DOMAIN_STRATEGY_OPTION);
Object.freeze(TCP_CONGESTION_OPTION);
class XrayCommonClass { class XrayCommonClass {
@@ -526,6 +555,8 @@ class TlsStreamSettings extends XrayCommonClass {
maxVersion = TLS_VERSION_OPTION.TLS13, maxVersion = TLS_VERSION_OPTION.TLS13,
cipherSuites = '', cipherSuites = '',
rejectUnknownSni = false, rejectUnknownSni = false,
disableSystemRoot = false,
enableSessionResumption = false,
certificates=[new TlsStreamSettings.Cert()], certificates=[new TlsStreamSettings.Cert()],
alpn=[ALPN_OPTION.H2,ALPN_OPTION.HTTP1], alpn=[ALPN_OPTION.H2,ALPN_OPTION.HTTP1],
settings=new TlsStreamSettings.Settings()) { settings=new TlsStreamSettings.Settings()) {
@@ -535,6 +566,8 @@ class TlsStreamSettings extends XrayCommonClass {
this.maxVersion = maxVersion; this.maxVersion = maxVersion;
this.cipherSuites = cipherSuites; this.cipherSuites = cipherSuites;
this.rejectUnknownSni = rejectUnknownSni; this.rejectUnknownSni = rejectUnknownSni;
this.disableSystemRoot = disableSystemRoot;
this.enableSessionResumption = enableSessionResumption;
this.certs = certificates; this.certs = certificates;
this.alpn = alpn; this.alpn = alpn;
this.settings = settings; this.settings = settings;
@@ -564,6 +597,8 @@ class TlsStreamSettings extends XrayCommonClass {
json.maxVersion, json.maxVersion,
json.cipherSuites, json.cipherSuites,
json.rejectUnknownSni, json.rejectUnknownSni,
json.disableSystemRoot,
json.enableSessionResumption,
certs, certs,
json.alpn, json.alpn,
settings, settings,
@@ -577,6 +612,8 @@ class TlsStreamSettings extends XrayCommonClass {
maxVersion: this.maxVersion, maxVersion: this.maxVersion,
cipherSuites: this.cipherSuites, cipherSuites: this.cipherSuites,
rejectUnknownSni: this.rejectUnknownSni, rejectUnknownSni: this.rejectUnknownSni,
disableSystemRoot: this.disableSystemRoot,
enableSessionResumption: this.enableSessionResumption,
certificates: TlsStreamSettings.toJsonArray(this.certs), certificates: TlsStreamSettings.toJsonArray(this.certs),
alpn: this.alpn, alpn: this.alpn,
settings: this.settings, settings: this.settings,
@@ -585,7 +622,7 @@ class TlsStreamSettings extends XrayCommonClass {
} }
TlsStreamSettings.Cert = class extends XrayCommonClass { TlsStreamSettings.Cert = class extends XrayCommonClass {
constructor(useFile=true, certificateFile='', keyFile='', certificate='', key='', ocspStapling=3600) { constructor(useFile=true, certificateFile='', keyFile='', certificate='', key='', ocspStapling=3600, oneTimeLoading=false, usage=USAGE_OPTION.ENCIPHERMENT) {
super(); super();
this.useFile = useFile; this.useFile = useFile;
this.certFile = certificateFile; this.certFile = certificateFile;
@@ -593,6 +630,8 @@ TlsStreamSettings.Cert = class extends XrayCommonClass {
this.cert = certificate instanceof Array ? certificate.join('\n') : certificate; this.cert = certificate instanceof Array ? certificate.join('\n') : certificate;
this.key = key instanceof Array ? key.join('\n') : key; this.key = key instanceof Array ? key.join('\n') : key;
this.ocspStapling = ocspStapling; this.ocspStapling = ocspStapling;
this.oneTimeLoading = oneTimeLoading;
this.usage = usage;
} }
static fromJson(json={}) { static fromJson(json={}) {
@@ -602,6 +641,8 @@ TlsStreamSettings.Cert = class extends XrayCommonClass {
json.certificateFile, json.certificateFile,
json.keyFile, '', '', json.keyFile, '', '',
json.ocspStapling, json.ocspStapling,
json.oneTimeLoading,
json.usage,
); );
} else { } else {
return new TlsStreamSettings.Cert( return new TlsStreamSettings.Cert(
@@ -609,6 +650,8 @@ TlsStreamSettings.Cert = class extends XrayCommonClass {
json.certificate.join('\n'), json.certificate.join('\n'),
json.key.join('\n'), json.key.join('\n'),
json.ocspStapling, json.ocspStapling,
json.oneTimeLoading,
json.usage,
); );
} }
} }
@@ -619,12 +662,16 @@ TlsStreamSettings.Cert = class extends XrayCommonClass {
certificateFile: this.certFile, certificateFile: this.certFile,
keyFile: this.keyFile, keyFile: this.keyFile,
ocspStapling: this.ocspStapling, ocspStapling: this.ocspStapling,
oneTimeLoading: this.oneTimeLoading,
usage: this.usage,
}; };
} else { } else {
return { return {
certificate: this.cert.split('\n'), certificate: this.cert.split('\n'),
key: this.key.split('\n'), key: this.key.split('\n'),
ocspStapling: this.ocspStapling, ocspStapling: this.ocspStapling,
oneTimeLoading: this.oneTimeLoading,
usage: this.usage,
}; };
} }
} }
@@ -649,6 +696,7 @@ TlsStreamSettings.Settings = class extends XrayCommonClass {
}; };
} }
}; };
class XtlsStreamSettings extends XrayCommonClass { class XtlsStreamSettings extends XrayCommonClass {
constructor(serverName='', constructor(serverName='',
certificates=[new XtlsStreamSettings.Cert()], certificates=[new XtlsStreamSettings.Cert()],
@@ -698,13 +746,16 @@ class XtlsStreamSettings extends XrayCommonClass {
} }
XtlsStreamSettings.Cert = class extends XrayCommonClass { XtlsStreamSettings.Cert = class extends XrayCommonClass {
constructor(useFile=true, certificateFile='', keyFile='', certificate='', key='') { constructor(useFile=true, certificateFile='', keyFile='', certificate='', key='', ocspStapling=3600, oneTimeLoading=false, usage=USAGE_OPTION.ENCIPHERMENT) {
super(); super();
this.useFile = useFile; this.useFile = useFile;
this.certFile = certificateFile; this.certFile = certificateFile;
this.keyFile = keyFile; this.keyFile = keyFile;
this.cert = certificate instanceof Array ? certificate.join('\n') : certificate; this.cert = certificate instanceof Array ? certificate.join('\n') : certificate;
this.key = key instanceof Array ? key.join('\n') : key; this.key = key instanceof Array ? key.join('\n') : key;
this.ocspStapling = ocspStapling;
this.oneTimeLoading = oneTimeLoading;
this.usage = usage;
} }
static fromJson(json={}) { static fromJson(json={}) {
@@ -712,13 +763,19 @@ XtlsStreamSettings.Cert = class extends XrayCommonClass {
return new XtlsStreamSettings.Cert( return new XtlsStreamSettings.Cert(
true, true,
json.certificateFile, json.certificateFile,
json.keyFile, json.keyFile, '', '',
json.ocspStapling,
json.oneTimeLoading,
json.usage,
); );
} else { } else {
return new XtlsStreamSettings.Cert( return new XtlsStreamSettings.Cert(
false, '', '', false, '', '',
json.certificate.join('\n'), json.certificate.join('\n'),
json.key.join('\n'), json.key.join('\n'),
json.ocspStapling,
json.oneTimeLoading,
json.usage,
); );
} }
} }
@@ -728,11 +785,17 @@ XtlsStreamSettings.Cert = class extends XrayCommonClass {
return { return {
certificateFile: this.certFile, certificateFile: this.certFile,
keyFile: this.keyFile, keyFile: this.keyFile,
ocspStapling: this.ocspStapling,
oneTimeLoading: this.oneTimeLoading,
usage: this.usage,
}; };
} else { } else {
return { return {
certificate: this.cert.split('\n'), certificate: this.cert.split('\n'),
key: this.key.split('\n'), key: this.key.split('\n'),
ocspStapling: this.ocspStapling,
oneTimeLoading: this.oneTimeLoading,
usage: this.usage,
}; };
} }
} }
@@ -843,12 +906,41 @@ RealityStreamSettings.Settings = class extends XrayCommonClass {
}; };
class SockoptStreamSettings extends XrayCommonClass { class SockoptStreamSettings extends XrayCommonClass {
constructor(acceptProxyProtocol = false, tcpFastOpen = false, mark = 0, tproxy="off") { constructor(
acceptProxyProtocol = false,
tcpFastOpen = false,
mark = 0,
tproxy="off",
tcpMptcp = false,
tcpNoDelay = false,
domainStrategy = DOMAIN_STRATEGY_OPTION.USE_IP,
tcpMaxSeg = 1440,
dialerProxy = "",
tcpKeepAliveInterval = 0,
tcpKeepAliveIdle = 300,
tcpUserTimeout = 10000,
tcpcongestion = TCP_CONGESTION_OPTION.BBR,
V6Only = false,
tcpWindowClamp = 600,
interfaceName = "",
) {
super(); super();
this.acceptProxyProtocol = acceptProxyProtocol; this.acceptProxyProtocol = acceptProxyProtocol;
this.tcpFastOpen = tcpFastOpen; this.tcpFastOpen = tcpFastOpen;
this.mark = mark; this.mark = mark;
this.tproxy = tproxy; this.tproxy = tproxy;
this.tcpMptcp = tcpMptcp;
this.tcpNoDelay = tcpNoDelay;
this.domainStrategy = domainStrategy;
this.tcpMaxSeg = tcpMaxSeg;
this.dialerProxy = dialerProxy;
this.tcpKeepAliveInterval = tcpKeepAliveInterval;
this.tcpKeepAliveIdle = tcpKeepAliveIdle;
this.tcpUserTimeout = tcpUserTimeout;
this.tcpcongestion = tcpcongestion;
this.V6Only = V6Only;
this.tcpWindowClamp = tcpWindowClamp;
this.interfaceName = interfaceName;
} }
static fromJson(json = {}) { static fromJson(json = {}) {
@@ -858,6 +950,18 @@ class SockoptStreamSettings extends XrayCommonClass {
json.tcpFastOpen, json.tcpFastOpen,
json.mark, json.mark,
json.tproxy, json.tproxy,
json.tcpMptcp,
json.tcpNoDelay,
json.domainStrategy,
json.tcpMaxSeg,
json.dialerProxy,
json.tcpKeepAliveInterval,
json.tcpKeepAliveIdle,
json.tcpUserTimeout,
json.tcpcongestion,
json.V6Only,
json.tcpWindowClamp,
json.interface,
); );
} }
@@ -867,6 +971,18 @@ class SockoptStreamSettings extends XrayCommonClass {
tcpFastOpen: this.tcpFastOpen, tcpFastOpen: this.tcpFastOpen,
mark: this.mark, mark: this.mark,
tproxy: this.tproxy, tproxy: this.tproxy,
tcpMptcp: this.tcpMptcp,
tcpNoDelay: this.tcpNoDelay,
domainStrategy: this.domainStrategy,
tcpMaxSeg: this.tcpMaxSeg,
dialerProxy: this.dialerProxy,
tcpKeepAliveInterval: this.tcpKeepAliveInterval,
tcpKeepAliveIdle: this.tcpKeepAliveIdle,
tcpUserTimeout: this.tcpUserTimeout,
tcpcongestion: this.tcpcongestion,
V6Only: this.V6Only,
tcpWindowClamp: this.tcpWindowClamp,
interface: this.interfaceName,
}; };
} }
} }
@@ -990,10 +1106,16 @@ class StreamSettings extends XrayCommonClass {
} }
class Sniffing extends XrayCommonClass { class Sniffing extends XrayCommonClass {
constructor(enabled=true, destOverride=['http', 'tls', 'quic', 'fakedns']) { constructor(
enabled=true,
destOverride=['http', 'tls', 'quic', 'fakedns'],
metadataOnly=false,
routeOnly=false) {
super(); super();
this.enabled = enabled; this.enabled = enabled;
this.destOverride = destOverride; this.destOverride = destOverride;
this.metadataOnly = metadataOnly;
this.routeOnly = routeOnly;
} }
static fromJson(json={}) { static fromJson(json={}) {
@@ -1006,6 +1128,8 @@ class Sniffing extends XrayCommonClass {
return new Sniffing( return new Sniffing(
!!json.enabled, !!json.enabled,
destOverride, destOverride,
json.metadataOnly,
json.routeOnly,
); );
} }
} }
@@ -1848,7 +1972,7 @@ Inbound.VmessSettings = class extends Inbound.Settings {
} }
}; };
Inbound.VmessSettings.Vmess = class extends XrayCommonClass { Inbound.VmessSettings.Vmess = class extends XrayCommonClass {
constructor(id=RandomUtil.randomUUID(), email=RandomUtil.randomLowerAndNum(8),limitIp=0, totalGB=0, expiryTime=0, enable=true, tgId=0, subId=RandomUtil.randomLowerAndNum(16), reset=0) { constructor(id=RandomUtil.randomUUID(), email=RandomUtil.randomLowerAndNum(8),limitIp=0, totalGB=0, expiryTime=0, enable=true, tgId='', subId=RandomUtil.randomLowerAndNum(16), reset=0) {
super(); super();
this.id = id; this.id = id;
this.email = email; this.email = email;
@@ -1939,7 +2063,7 @@ Inbound.VLESSSettings = class extends Inbound.Settings {
}; };
Inbound.VLESSSettings.VLESS = class extends XrayCommonClass { Inbound.VLESSSettings.VLESS = class extends XrayCommonClass {
constructor(id=RandomUtil.randomUUID(), flow='', email=RandomUtil.randomLowerAndNum(8),limitIp=0, totalGB=0, expiryTime=0, enable=true, tgId=0, subId=RandomUtil.randomLowerAndNum(16), reset=0) { constructor(id=RandomUtil.randomUUID(), flow='', email=RandomUtil.randomLowerAndNum(8),limitIp=0, totalGB=0, expiryTime=0, enable=true, tgId='', subId=RandomUtil.randomLowerAndNum(16), reset=0) {
super(); super();
this.id = id; this.id = id;
this.flow = flow; this.flow = flow;
@@ -2064,7 +2188,7 @@ Inbound.TrojanSettings = class extends Inbound.Settings {
} }
}; };
Inbound.TrojanSettings.Trojan = class extends XrayCommonClass { Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
constructor(password=RandomUtil.randomSeq(10), flow='', email=RandomUtil.randomLowerAndNum(8),limitIp=0, totalGB=0, expiryTime=0, enable=true, tgId=0, subId=RandomUtil.randomLowerAndNum(16), reset=0) { constructor(password=RandomUtil.randomSeq(10), flow='', email=RandomUtil.randomLowerAndNum(8),limitIp=0, totalGB=0, expiryTime=0, enable=true, tgId='', subId=RandomUtil.randomLowerAndNum(16), reset=0) {
super(); super();
this.password = password; this.password = password;
this.flow = flow; this.flow = flow;
@@ -2209,7 +2333,7 @@ Inbound.ShadowsocksSettings = class extends Inbound.Settings {
}; };
Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass { Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass {
constructor(method='', password=RandomUtil.randomShadowsocksPassword(), email=RandomUtil.randomLowerAndNum(8),limitIp=0, totalGB=0, expiryTime=0, enable=true, tgId=0, subId=RandomUtil.randomLowerAndNum(16), reset=0) { constructor(method='', password=RandomUtil.randomShadowsocksPassword(), email=RandomUtil.randomLowerAndNum(8),limitIp=0, totalGB=0, expiryTime=0, enable=true, tgId='', subId=RandomUtil.randomLowerAndNum(16), reset=0) {
super(); super();
this.method = method; this.method = method;
this.password = password; this.password = password;
@@ -2281,12 +2405,13 @@ Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass {
}; };
Inbound.DokodemoSettings = class extends Inbound.Settings { Inbound.DokodemoSettings = class extends Inbound.Settings {
constructor(protocol, address, port, network='tcp,udp', followRedirect=false) { constructor(protocol, address, port, network='tcp,udp', followRedirect=false, timeout=0) {
super(protocol); super(protocol);
this.address = address; this.address = address;
this.port = port; this.port = port;
this.network = network; this.network = network;
this.followRedirect = followRedirect; this.followRedirect = followRedirect;
this.timeout = timeout;
} }
static fromJson(json={}) { static fromJson(json={}) {
@@ -2296,6 +2421,7 @@ Inbound.DokodemoSettings = class extends Inbound.Settings {
json.port, json.port,
json.network, json.network,
json.followRedirect, json.followRedirect,
json.timeout,
); );
} }
@@ -2305,6 +2431,7 @@ Inbound.DokodemoSettings = class extends Inbound.Settings {
port: this.port, port: this.port,
network: this.network, network: this.network,
followRedirect: this.followRedirect, followRedirect: this.followRedirect,
timeout: this.timeout,
}; };
} }
}; };

View File

@@ -13,15 +13,18 @@ import (
) )
func getRemoteIp(c *gin.Context) string { func getRemoteIp(c *gin.Context) string {
value := c.GetHeader("X-Forwarded-For") value := c.GetHeader("X-Real-IP")
if value != "" {
return value
}
value = c.GetHeader("X-Forwarded-For")
if value != "" { if value != "" {
ips := strings.Split(value, ",") ips := strings.Split(value, ",")
return ips[0] return ips[0]
} else {
addr := c.Request.RemoteAddr
ip, _, _ := net.SplitHostPort(addr)
return ip
} }
addr := c.Request.RemoteAddr
ip, _, _ := net.SplitHostPort(addr)
return ip
} }
func jsonMsg(c *gin.Context, msg string, err error) { func jsonMsg(c *gin.Context, msg string, err error) {
@@ -61,7 +64,18 @@ func html(c *gin.Context, name string, title string, data gin.H) {
data = gin.H{} data = gin.H{}
} }
data["title"] = title data["title"] = title
data["host"] = strings.Split(c.Request.Host, ":")[0] host := c.GetHeader("X-Forwarded-Host")
if host == "" {
host = c.GetHeader("X-Real-IP")
}
if host == "" {
var err error
host, _, err = net.SplitHostPort(c.Request.Host)
if err != nil {
host = c.Request.Host
}
}
data["host"] = host
data["request_uri"] = c.Request.RequestURI data["request_uri"] = c.Request.RequestURI
data["base_path"] = c.GetString("base_path") data["base_path"] = c.GetString("base_path")
c.HTML(http.StatusOK, name, getContext(data)) c.HTML(http.StatusOK, name, getContext(data))

View File

@@ -1,32 +1,32 @@
{{define "head"}} {{define "head"}}
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="renderer" content="webkit"> <meta name="renderer" content="webkit">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="{{ .base_path }}assets/ant-design-vue@1.7.8/antd.min.css"> <link rel="stylesheet" href="{{ .base_path }}assets/ant-design-vue/antd.min.css">
<link rel="stylesheet" href="{{ .base_path }}assets/element-ui@2.15.0/theme-chalk/display.css"> <link rel="stylesheet" href="{{ .base_path }}assets/element-ui/theme-chalk/display.css">
<link rel="stylesheet" href="{{ .base_path }}assets/css/custom.css?{{ .cur_ver }}"> <link rel="stylesheet" href="{{ .base_path }}assets/css/custom.min.css?{{ .cur_ver }}">
<style> <style>
[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;
font-family: 'Vazirmatn'; font-family: 'Vazirmatn';
font-style: normal; font-style: normal;
font-weight: 400; font-weight: 400;
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: -apple-system, BlinkMacSystemFont, 'Vazirmatn', 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', font-family: -apple-system, BlinkMacSystemFont, 'Vazirmatn', 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB',
'Microsoft YaHei', 'Helvetica Neue', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Microsoft YaHei', 'Helvetica Neue', Helvetica, Arial, sans-serif, 'Apple Color Emoji',
'Segoe UI Emoji', 'Segoe UI Symbol'; 'Segoe UI Emoji', 'Segoe UI Symbol';
} }
</style> </style>
<title>{{ .host }}-{{ i18n .title}}</title> <title>{{ .host }}-{{ i18n .title}}</title>
</head> </head>
<div id="message"></div> <div id="message"></div>
{{end}} {{end}}

View File

@@ -1,7 +1,7 @@
{{define "js"}} {{define "js"}}
<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@1.7.8/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>

View File

@@ -1,110 +1,165 @@
{{define "qrcodeModal"}} {{define "qrcodeModal"}}
<a-modal id="qrcode-modal" v-model="qrModal.visible" :title="qrModal.title" <a-modal id="qrcode-modal" v-model="qrModal.visible" :title="qrModal.title"
:dialog-style="{ top: '20px' }" :dialog-style="isMobileQr ? { top: '18px' } : {}"
:closable="true" :closable="true"
:class="themeSwitcher.currentTheme" :class="themeSwitcher.currentTheme"
:footer="null" width="300px"> :footer="null" width="fit-content">
<a-tag color="green" style="margin-bottom: 10px;display: block;text-align: center;"> <tr-qr-modal class="qr-modal">
{{ i18n "pages.inbounds.clickOnQRcode" }} <template v-if="app.subSettings.enable && qrModal.subId">
</a-tag> <tr-qr-box class="qr-box">
<template v-if="app.subSettings.enable && qrModal.subId"> <a-tag color="purple" class="qr-tag"><span>{{ i18n "pages.settings.subSettings"}}</span></a-tag>
<a-divider>{{ i18n "pages.settings.subSettings"}}</a-divider> <tr-qr-bg class="qr-bg-sub">
<div class="qr-bg"><canvas @click="copyToClipboard('qrCode-sub',genSubLink(qrModal.client.subId))" id="qrCode-sub" class="qr-cv"></canvas></div> <tr-qr-bg-inner class="qr-bg-sub-inner">
<a-divider>{{ i18n "pages.settings.subSettings"}} Json</a-divider> <canvas @click="copyToClipboard('qrCode-sub',genSubLink(qrModal.client.subId))" id="qrCode-sub" class="qr-cv"></canvas>
<div class="qr-bg"><canvas @click="copyToClipboard('qrCode-subJson',genSubJsonLink(qrModal.client.subId))" id="qrCode-subJson" class="qr-cv"></canvas></div> </tr-qr-bg-inner>
</template> </tr-qr-bg>
<a-divider>{{ i18n "pages.inbounds.client" }}</a-divider> </tr-qr-box>
<template v-for="(row, index) in qrModal.qrcodes"> <tr-qr-box class="qr-box">
<a-tag color="green" style="margin: 10px 0; display: block; text-align: center;">[[ row.remark ]]</a-tag> <a-tag color="purple" class="qr-tag"><span>{{ i18n "pages.settings.subSettings"}} Json</span></a-tag>
<div class="qr-bg"><canvas @click="copyToClipboard('qrCode-'+index, row.link)" :id="'qrCode-'+index" class="qr-cv"></canvas></div> <tr-qr-bg class="qr-bg-sub">
</template> <tr-qr-bg-inner class="qr-bg-sub-inner">
<canvas @click="copyToClipboard('qrCode-subJson',genSubJsonLink(qrModal.client.subId))" id="qrCode-subJson" class="qr-cv"></canvas>
</tr-qr-bg-inner>
</tr-qr-bg>
</tr-qr-box>
</template>
<template v-for="(row, index) in qrModal.qrcodes">
<tr-qr-box class="qr-box">
<a-tag color="green" class="qr-tag"><span>[[ row.remark ]]</span></a-tag>
<tr-qr-bg class="qr-bg">
<canvas @click="copyToClipboard('qrCode-'+index, row.link)" :id="'qrCode-'+index" class="qr-cv"></canvas>
</tr-qr-bg>
</tr-qr-box>
</template>
</tr-qr-modal>
</a-modal> </a-modal>
<script> <script>
const isMobileQr = window.innerWidth <= 768;
const qrModal = {
title: '',
dbInbound: new DBInbound(),
client: null,
qrcodes: [],
clipboard: null,
visible: false,
subId: '',
show: function(title = '', dbInbound, client) {
this.title = title;
this.dbInbound = dbInbound;
this.inbound = dbInbound.toInbound();
this.client = client;
this.subId = '';
this.qrcodes = [];
if (this.inbound.protocol == Protocols.WIREGUARD) {
this.inbound.genInboundLinks(dbInbound.remark).split('\r\n').forEach((l, index) => {
this.qrcodes.push({
remark: "Peer " + (index + 1),
link: l
});
});
} else {
this.inbound.genAllLinks(this.dbInbound.remark, app.remarkModel, client).forEach(l => {
this.qrcodes.push({
remark: l.remark,
link: l.link
});
});
}
this.visible = true;
},
close: function() {
this.visible = false;
},
};
const qrModalApp = new Vue({
delimiters: ['[[', ']]'],
el: '#qrcode-modal',
data: {
qrModal: qrModal,
},
methods: {
copyToClipboard(elmentId, content) {
this.qrModal.clipboard = new ClipboardJS('#' + elmentId, {
text: () => content,
});
this.qrModal.clipboard.on('success', () => {
app.$message.success('{{ i18n "copied" }}')
this.qrModal.clipboard.destroy();
});
},
setQrCode(elmentId, content) {
new QRious({
element: document.querySelector('#' + elmentId),
size: 400,
value: content,
background: 'white',
backgroundAlpha: 0,
foreground: 'black',
padding: 2,
level: 'L'
});
},
genSubLink(subID) {
return app.subSettings.subURI + subID;
},
genSubJsonLink(subID) {
return app.subSettings.subJsonURI + subID;
},
revertOverflow() {
const elements = document.querySelectorAll(".qr-tag");
elements.forEach((element) => {
element.classList.remove("tr-marquee");
element.children[0].style.animation = '';
while (element.children.length > 1) {
element.removeChild(element.lastChild);
}
});
}
},
updated() {
if (this.qrModal.visible) {
fixOverflow();
} else {
this.revertOverflow();
}
if (qrModal.client && qrModal.client.subId) {
qrModal.subId = qrModal.client.subId;
this.setQrCode("qrCode-sub", this.genSubLink(qrModal.subId));
this.setQrCode("qrCode-subJson", this.genSubJsonLink(qrModal.subId));
}
qrModal.qrcodes.forEach((element, index) => {
this.setQrCode("qrCode-" + index, element.link);
});
}
});
const qrModal = { function fixOverflow() {
title: '', const elements = document.querySelectorAll(".qr-tag");
dbInbound: new DBInbound(), elements.forEach((element) => {
client: null, function isElementOverflowing(element) {
qrcodes: [], const overflowX = element.offsetWidth < element.scrollWidth,
clipboard: null, overflowY = element.offsetHeight < element.scrollHeight;
visible: false, return overflowX || overflowY;
subId: '', }
show: function (title = '', dbInbound, client) {
this.title = title;
this.dbInbound = dbInbound;
this.inbound = dbInbound.toInbound();
this.client = client;
this.subId = '';
this.qrcodes = [];
if (this.inbound.protocol == Protocols.WIREGUARD){
this.inbound.genInboundLinks(dbInbound.remark).split('\r\n').forEach((l,index) =>{
this.qrcodes.push({
remark: "Peer " + (index+1),
link: l
});
});
} else {
this.inbound.genAllLinks(this.dbInbound.remark, app.remarkModel, client).forEach(l => {
this.qrcodes.push({
remark: l.remark,
link: l.link
});
});
}
this.visible = true;
},
close: function () {
this.visible = false;
},
};
const qrModalApp = new Vue({ function wrapContentsInMarquee(element) {
delimiters: ['[[', ']]'], element.classList.add("tr-marquee");
el: '#qrcode-modal', element.children[0].style.animation = `move-ltr ${
data: { (element.children[0].clientWidth / element.clientWidth) * 5
qrModal: qrModal, }s ease-in-out infinite`;
}, const marqueeText = element.children[0];
methods: { if (element.children.length < 2) {
copyToClipboard(elmentId, content) { for (let i = 0; i < 1; i++) {
this.qrModal.clipboard = new ClipboardJS('#' + elmentId, { const marqueeText = element.children[0].cloneNode(true);
text: () => content, element.children[0].after(marqueeText);
}); }
this.qrModal.clipboard.on('success', () => {
app.$message.success('{{ i18n "copied" }}')
this.qrModal.clipboard.destroy();
});
},
setQrCode(elmentId, content) {
new QRious({
element: document.querySelector('#' + elmentId),
size: 400,
value: content,
background: 'white',
backgroundAlpha: 0,
foreground: 'black',
padding: 2,
level: 'L'
});
},
genSubLink(subID) {
return app.subSettings.subURI+subID;
},
genSubJsonLink(subID) {
return app.subSettings.subJsonURI+subID;
}
},
updated() {
if (qrModal.client && qrModal.client.subId) {
qrModal.subId = qrModal.client.subId;
this.setQrCode("qrCode-sub", this.genSubLink(qrModal.subId));
this.setQrCode("qrCode-subJson", this.genSubJsonLink(qrModal.subId));
}
qrModal.qrcodes.forEach((element, index) => {
this.setQrCode("qrCode-" + index, element.link);
});
} }
}
if (isElementOverflowing(element)) {
wrapContentsInMarquee(element);
}
}); });
}
</script> </script>
{{end}} {{end}}

View File

@@ -400,7 +400,7 @@
</g> </g>
</svg> </svg>
</div> </div>
<a-row type="flex" justify="center" align="middle" style="height: 100%; overflow: auto;"> <a-row type="flex" justify="center" align="middle" style="height: 100%; overflow: auto; overflow-x: hidden;">
<a-col :xs="22" :sm="20" :md="14" :lg="10" :xl="8" :xxl="6" id="login" style="margin: 3rem 0;"> <a-col :xs="22" :sm="20" :md="14" :lg="10" :xl="8" :xxl="6" id="login" style="margin: 3rem 0;">
<a-row type="flex" justify="center"> <a-row type="flex" justify="center">
<a-col style="width: 100%;"> <a-col style="width: 100%;">
@@ -461,7 +461,7 @@
</a-form-item> </a-form-item>
<a-form-item> <a-form-item>
<a-row justify="center" class="centered"> <a-row justify="center" class="centered">
<theme-switch></theme-switch> <theme-switch-login></theme-switch-login>
</a-row> </a-row>
</a-form-item> </a-form-item>
</a-form> </a-form>
@@ -476,83 +476,82 @@
{{template "component/themeSwitcher" .}} {{template "component/themeSwitcher" .}}
{{template "component/password" .}} {{template "component/password" .}}
<script> <script>
class User { class User {
constructor() { constructor() {
this.username = ""; this.username = "";
this.password = ""; this.password = "";
}
}
const app = new Vue({
delimiters: ['[[', ']]'],
el: '#app',
data: {
themeSwitcher,
loading: false,
user: new User(),
secretEnable: false,
lang: ""
},
async created() {
this.lang = getLang();
this.secretEnable = await this.getSecretStatus();
},
methods: {
async login() {
this.loading = true;
const msg = await HttpUtil.post('/login', this.user);
this.loading = false;
if (msg.success) {
location.href = basePath + 'panel/';
} }
},
async getSecretStatus() {
this.loading = true;
const msg = await HttpUtil.post('/getSecretStatus');
this.loading = false;
if (msg.success) {
this.secretEnable = msg.obj;
return msg.obj;
}
},
},
});
document.addEventListener("DOMContentLoaded", function() {
var animationDelay = 2000;
initHeadline();
function initHeadline() {
animateHeadline(document.querySelectorAll('.headline'));
} }
const app = new Vue({ function animateHeadline(headlines) {
delimiters: ['[[', ']]'], var duration = animationDelay;
el: '#app', headlines.forEach(function(headline) {
data: { setTimeout(function() {
themeSwitcher, hideWord(headline.querySelector('.is-visible'));
loading: false, }, duration);
user: new User(), });
secretEnable: false, }
lang: ""
},
async created() {
this.lang = getLang();
this.secretEnable = await this.getSecretStatus();
},
methods: {
async login() {
this.loading = true;
const msg = await HttpUtil.post('/login', this.user);
this.loading = false;
if (msg.success) {
location.href = basePath + 'panel/';
}
},
async getSecretStatus() {
this.loading = true;
const msg = await HttpUtil.post('/getSecretStatus');
this.loading = false;
if (msg.success) {
this.secretEnable = msg.obj;
return msg.obj;
}
},
},
});
document.addEventListener("DOMContentLoaded", function() {
var animationDelay = 2000;
initHeadline();
function initHeadline() { function hideWord(word) {
animateHeadline(document.querySelectorAll('.headline')); var nextWord = takeNext(word);
} switchWord(word, nextWord);
setTimeout(function() {
hideWord(nextWord);
}, animationDelay);
}
function animateHeadline(headlines) { function takeNext(word) {
var duration = animationDelay; return (word.nextElementSibling) ? word.nextElementSibling : word.parentElement.firstElementChild;
headlines.forEach(function(headline) { }
setTimeout(function() {
hideWord(headline.querySelector('.is-visible'));
}, duration);
});
}
function hideWord(word) { function switchWord(oldWord, newWord) {
var nextWord = takeNext(word); oldWord.classList.remove('is-visible');
switchWord(word, nextWord); oldWord.classList.add('is-hidden');
setTimeout(function() { newWord.classList.remove('is-hidden');
hideWord(nextWord); newWord.classList.add('is-visible');
}, animationDelay); }
} });
function takeNext(word) {
return (word.nextElementSibling) ? word.nextElementSibling : word.parentElement.firstElementChild;
}
function switchWord(oldWord, newWord) {
oldWord.classList.remove('is-visible');
oldWord.classList.add('is-hidden');
newWord.classList.remove('is-hidden');
newWord.classList.add('is-visible');
}
});
</script> </script>
</body> </body>
</html> </html>

View File

@@ -58,13 +58,13 @@
<template slot="title"> <template slot="title">
<span>{{ i18n "pages.inbounds.telegramDesc" }}</span> <span>{{ i18n "pages.inbounds.telegramDesc" }}</span>
</template> </template>
Telegram ID Telegram ChatID
<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%" v-model.trim="clientsBulkModal.tgId" min="0"></a-input-number> <a-input-number style="width: 50%" v-model="clientsBulkModal.tgId" min="0"></a-input-number>
</a-form-item> </a-form-item>
<a-form-item> <a-form-item v-if="app.ipLimitEnable">
<template slot="label"> <template slot="label">
<a-tooltip> <a-tooltip>
<template slot="title"> <template slot="title">
@@ -104,10 +104,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'" :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss" <a-date-picker v-if="datepicker == 'gregorian'" :show-time="{ format: 'HH:mm:ss' }"
:dropdown-class-name="themeSwitcher.currentTheme" v-model="clientsBulkModal.expiryTime"></a-date-picker> format="YYYY-MM-DD HH:mm:ss" :dropdown-class-name="themeSwitcher.currentTheme"
v-model="clientsBulkModal.expiryTime"></a-date-picker>
<persian-datepicker v-else placeholder='{{ i18n "pages.settings.datepickerPlaceholder" }}' <persian-datepicker v-else placeholder='{{ i18n "pages.settings.datepickerPlaceholder" }}'
value="clientsBulkModal.expiryTime" v-model="clientsBulkModal.expiryTime"></persian-datepicker> value="clientsBulkModal.expiryTime" v-model="clientsBulkModal.expiryTime"></persian-datepicker>
</a-form-item> </a-form-item>
<a-form-item v-if="clientsBulkModal.expiryTime != 0"> <a-form-item v-if="clientsBulkModal.expiryTime != 0">
<template slot="label"> <template slot="label">
@@ -143,7 +144,7 @@
emailPrefix: "", emailPrefix: "",
emailPostfix: "", emailPostfix: "",
subId: "", subId: "",
tgId: 0, tgId: '',
flow: "", flow: "",
delayedStart: false, delayedStart: false,
reset: 0, reset: 0,
@@ -165,7 +166,7 @@
if (method == 4) newClient.email = ""; if (method == 4) newClient.email = "";
newClient.email += useNum ? prefix + i.toString() + postfix : prefix + postfix; newClient.email += useNum ? prefix + i.toString() + postfix : prefix + postfix;
if (clientsBulkModal.subId.length > 0) newClient.subId = clientsBulkModal.subId; if (clientsBulkModal.subId.length > 0) newClient.subId = clientsBulkModal.subId;
if (clientsBulkModal.tgId.length > 0) newClient.tgId = clientsBulkModal.tgId; newClient.tgId = clientsBulkModal.tgId;
newClient.limitIp = clientsBulkModal.limitIp; newClient.limitIp = clientsBulkModal.limitIp;
newClient._totalGB = clientsBulkModal.totalGB; newClient._totalGB = clientsBulkModal.totalGB;
newClient._expiryTime = clientsBulkModal.expiryTime; newClient._expiryTime = clientsBulkModal.expiryTime;
@@ -200,7 +201,7 @@
this.emailPrefix = ""; this.emailPrefix = "";
this.emailPostfix = ""; this.emailPostfix = "";
this.subId = ""; this.subId = "";
this.tgId = 0; this.tgId = '';
this.flow = ""; this.flow = "";
this.dbInbound = new DBInbound(dbInbound); this.dbInbound = new DBInbound(dbInbound);
this.inbound = dbInbound.toInbound(); this.inbound = dbInbound.toInbound();
@@ -220,7 +221,7 @@
clientsBulkModal.visible = false; clientsBulkModal.visible = false;
clientsBulkModal.loading(false); clientsBulkModal.loading(false);
}, },
loading(loading=true) { loading(loading = true) {
clientsBulkModal.confirmLoading = loading; clientsBulkModal.confirmLoading = loading;
}, },
}; };

View File

@@ -1,61 +1,65 @@
{{define "menuItems"}} {{define "menuItems"}}
<a-menu-item key="{{ .base_path }}panel/"> <a-menu-item key="{{ .base_path }}panel/">
<a-icon type="dashboard"></a-icon> <a-icon type="dashboard"></a-icon>
<span><b>{{ i18n "menu.dashboard"}}</b></span> <span>
<b>{{ i18n "menu.dashboard"}}</b>
</span>
</a-menu-item> </a-menu-item>
<a-menu-item key="{{ .base_path }}panel/inbounds"> <a-menu-item key="{{ .base_path }}panel/inbounds">
<a-icon type="user"></a-icon> <a-icon type="user"></a-icon>
<span><b>{{ i18n "menu.inbounds"}}</b></span> <span>
<b>{{ i18n "menu.inbounds"}}</b>
</span>
</a-menu-item> </a-menu-item>
<a-menu-item key="{{ .base_path }}panel/settings"> <a-menu-item key="{{ .base_path }}panel/settings">
<a-icon type="setting"></a-icon> <a-icon type="setting"></a-icon>
<span><b>{{ i18n "menu.settings"}}</b></span> <span>
<b>{{ i18n "menu.settings"}}</b>
</span>
</a-menu-item> </a-menu-item>
<a-menu-item key="{{ .base_path }}panel/xray"> <a-menu-item key="{{ .base_path }}panel/xray">
<a-icon type="tool"></a-icon> <a-icon type="tool"></a-icon>
<span><b>{{ i18n "menu.xray"}}</b></span> <span>
<b>{{ i18n "menu.xray"}}</b>
</span>
</a-menu-item> </a-menu-item>
<a-menu-item key="{{ .base_path }}logout"> <a-menu-item key="{{ .base_path }}logout">
<a-icon type="logout"></a-icon> <a-icon type="logout"></a-icon>
<span><b>{{ i18n "menu.logout"}}</b></span> <span>
<b>{{ i18n "menu.logout"}}</b>
</span>
</a-menu-item> </a-menu-item>
{{end}} {{end}}
{{define "commonSider"}} {{define "commonSider"}}
<a-layout-sider :theme="themeSwitcher.currentTheme" id="sider" collapsible breakpoint="md" collapsed-width="0"> <a-layout-sider :theme="themeSwitcher.currentTheme" id="sider" collapsible breakpoint="md">
<theme-switch></theme-switch> <theme-switch></theme-switch>
<a-menu :theme="themeSwitcher.currentTheme" mode="inline" :selected-keys="['{{ .request_uri }}']" <a-menu :theme="themeSwitcher.currentTheme" mode="inline" :selected-keys="['{{ .request_uri }}']" @click="({key}) => key.startsWith('http') ? window.open(key) : location.href = key">
@click="({key}) => key.startsWith('http') ? window.open(key) : location.href = key"> {{template "menuItems" .}}
{{template "menuItems" .}} </a-menu>
</a-menu>
</a-layout-sider> </a-layout-sider>
<a-drawer id="sider-drawer" placement="left" :closable="false" <a-drawer id="sider-drawer" placement="left" :closable="false" @close="siderDrawer.close()" :visible="siderDrawer.visible" :wrap-class-name="themeSwitcher.currentTheme" :wrap-style="{ padding: 0 }">
@close="siderDrawer.close()" <div class="drawer-handle" @click="siderDrawer.change()" slot="handle">
:visible="siderDrawer.visible" <a-icon :type="siderDrawer.visible ? 'close' : 'menu-fold'"></a-icon>
:wrap-class-name="themeSwitcher.currentTheme" </div>
:wrap-style="{ padding: 0 }"> <theme-switch></theme-switch>
<div class="drawer-handle" @click="siderDrawer.change()" slot="handle"> <a-menu :theme="themeSwitcher.currentTheme" mode="inline" :selected-keys="['{{ .request_uri }}']" @click="({key}) => key.startsWith('http') ? window.open(key) : location.href = key">
<a-icon :type="siderDrawer.visible ? 'close' : 'menu-fold'"></a-icon> {{template "menuItems" .}}
</div> </a-menu>
<theme-switch></theme-switch>
<a-menu :theme="themeSwitcher.currentTheme" mode="inline" :selected-keys="['{{ .request_uri }}']"
@click="({key}) => key.startsWith('http') ? window.open(key) : location.href = key">
{{template "menuItems" .}}
</a-menu>
</a-drawer> </a-drawer>
<script> <script>
const siderDrawer = { const siderDrawer = {
visible: false, visible: false,
show() { show() {
this.visible = true; this.visible = true;
}, },
close() { close() {
this.visible = false; this.visible = false;
}, },
change() { change() {
this.visible = !this.visible; this.visible = !this.visible;
}, },
}; };
</script> </script>
{{end}} {{end}}

View File

@@ -1,236 +1,216 @@
{{define "component/sortableTableTrigger"}} {{define "component/sortableTableTrigger"}}
<a-icon type="drag" <a-icon type="drag"
class="sortable-icon" class="sortable-icon"
style="cursor: move;" style="cursor: move;"
@mouseup="mouseUpHandler" @mouseup="mouseUpHandler"
@mousedown="mouseDownHandler" @mousedown="mouseDownHandler"
@click="clickHandler" /> @click="clickHandler" />
{{end}} {{end}}
{{define "component/sortableTable"}} {{define "component/sortableTable"}}
<script> <script>
const DRAGGABLE_ROW_CLASS = 'draggable-row'; const DRAGGABLE_ROW_CLASS = 'draggable-row';
const findParentRowElement = (el) => {
const findParentRowElement = (el) => { if (!el || !el.tagName) {
if (!el || !el.tagName) { return null;
return null; } else if (el.classList.contains(DRAGGABLE_ROW_CLASS)) {
} else if (el.classList.contains(DRAGGABLE_ROW_CLASS)) { return el;
return el; } else if (el.parentNode) {
} else if (el.parentNode) { return findParentRowElement(el.parentNode);
return findParentRowElement(el.parentNode); } else {
return null;
}
}
Vue.component('a-table-sortable', {
data() {
return {
sortingElementIndex: null,
newElementIndex: null,
};
},
props: ['data-source', 'customRow'],
inheritAttrs: false,
provide() {
const sortable = {}
Object.defineProperty(sortable, "setSortableIndex", {
enumerable: true,
get: () => this.setCurrentSortableIndex,
});
Object.defineProperty(sortable, "resetSortableIndex", {
enumerable: true,
get: () => this.resetSortableIndex,
});
return {
sortable,
}
},
render: function(createElement) {
return createElement('a-table', {
class: {
'ant-table-is-sorting': this.isDragging(),
},
props: {
...this.$attrs,
'data-source': this.records,
customRow: (record, index) => this.customRowRender(record, index),
},
on: this.$listeners,
nativeOn: {
drop: (e) => this.dropHandler(e),
},
scopedSlots: this.$scopedSlots,
}, this.$slots.default, )
},
created() {
this.$memoSort = {};
},
methods: {
isDragging() {
const currentIndex = this.sortingElementIndex;
return currentIndex !== null && currentIndex !== undefined;
},
resetSortableIndex(e, index) {
this.sortingElementIndex = null;
this.newElementIndex = null;
this.$memoSort = {};
},
setCurrentSortableIndex(e, index) {
this.sortingElementIndex = index;
},
dragStartHandler(e, index) {
if (!this.isDragging()) {
e.preventDefault();
return;
}
const hideDragImage = this.$el.cloneNode(true);
hideDragImage.id = "hideDragImage-hide";
hideDragImage.style.opacity = 0;
e.dataTransfer.setDragImage(hideDragImage, 0, 0);
},
dragStopHandler(e, index) {
const hideDragImage = document.getElementById('hideDragImage-hide');
if (hideDragImage) hideDragImage.remove();
this.resetSortableIndex(e, index);
},
dragOverHandler(e, index) {
if (!this.isDragging()) {
return;
}
e.preventDefault();
const currentIndex = this.sortingElementIndex;
if (index === currentIndex) {
this.newElementIndex = null;
return;
}
const row = findParentRowElement(e.target);
if (!row) {
return;
}
const rect = row.getBoundingClientRect();
const offsetTop = e.pageY - rect.top;
if (offsetTop < rect.height / 2) {
this.newElementIndex = Math.max(index - 1, 0);
} else { } else {
return null; this.newElementIndex = index;
} }
},
dropHandler(e) {
if (this.isDragging()) {
this.$emit('onsort', this.sortingElementIndex, this.newElementIndex);
}
},
customRowRender(record, index) {
const parentMethodResult = this.customRow?.(record, index) || {};
const newIndex = this.newElementIndex;
const currentIndex = this.sortingElementIndex;
return {
...parentMethodResult,
attrs: {
...(parentMethodResult?.attrs || {}),
draggable: true,
},
on: {
...(parentMethodResult?.on || {}),
dragstart: (e) => this.dragStartHandler(e, index),
dragend: (e) => this.dragStopHandler(e, index),
dragover: (e) => this.dragOverHandler(e, index),
},
class: {
...(parentMethodResult?.class || {}),
[DRAGGABLE_ROW_CLASS]: true,
['dragging']: this.isDragging() ? (newIndex === null ? index === currentIndex : index === newIndex) : false,
},
};
}
},
computed: {
records() {
const newIndex = this.newElementIndex;
const currentIndex = this.sortingElementIndex;
if (!this.isDragging() || newIndex === null || currentIndex === newIndex) {
return this.dataSource;
}
if (this.$memoSort.newIndex === newIndex) {
return this.$memoSort.list;
}
let list = [...this.dataSource];
list.splice(newIndex, 0, list.splice(currentIndex, 1)[0]);
this.$memoSort = {
newIndex,
list,
};
return list;
}
} }
});
Vue.component('a-table-sortable', { Vue.component('table-sort-trigger', {
data() { template: `{{template "component/sortableTableTrigger"}}`,
return { props: ['item-index'],
sortingElementIndex: null, inject: ['sortable'],
newElementIndex: null, methods: {
}; mouseDownHandler(e) {
}, if (this.sortable) {
props: ['data-source', 'customRow'], this.sortable.setSortableIndex(e, this.itemIndex);
inheritAttrs: false,
provide() {
const sortable = {}
Object.defineProperty(sortable, "setSortableIndex", {
enumerable: true,
get: () => this.setCurrentSortableIndex,
});
Object.defineProperty(sortable, "resetSortableIndex", {
enumerable: true,
get: () => this.resetSortableIndex,
});
return {
sortable,
}
},
render: function (createElement) {
return createElement('a-table', {
class: {
'ant-table-is-sorting': this.isDragging(),
},
props: {
...this.$attrs,
'data-source': this.records,
customRow: (record, index) => this.customRowRender(record, index),
},
on: this.$listeners,
nativeOn: {
drop: (e) => this.dropHandler(e),
},
scopedSlots: this.$scopedSlots,
}, this.$slots.default, )
},
created() {
this.$memoSort = {};
},
methods: {
isDragging() {
const currentIndex = this.sortingElementIndex;
return currentIndex !== null && currentIndex !== undefined;
},
resetSortableIndex(e, index) {
this.sortingElementIndex = null;
this.newElementIndex = null;
this.$memoSort = {};
},
setCurrentSortableIndex(e, index) {
this.sortingElementIndex = index;
},
dragStartHandler(e, index) {
if (!this.isDragging()) {
e.preventDefault();
return;
}
const hideDragImage = this.$el.cloneNode(true);
hideDragImage.id = "hideDragImage-hide";
hideDragImage.style.opacity = 0;
e.dataTransfer.setDragImage(hideDragImage, 0, 0);
},
dragStopHandler(e, index) {
const hideDragImage = document.getElementById('hideDragImage-hide');
if (hideDragImage) hideDragImage.remove();
this.resetSortableIndex(e, index);
},
dragOverHandler(e, index) {
if (!this.isDragging()) {
return;
}
e.preventDefault();
const currentIndex = this.sortingElementIndex;
if (index === currentIndex) {
this.newElementIndex = null;
return;
}
const row = findParentRowElement(e.target);
if (!row) {
return;
}
const rect = row.getBoundingClientRect();
const offsetTop = e.pageY - rect.top;
if (offsetTop < rect.height / 2) {
this.newElementIndex = Math.max(index - 1, 0);
} else {
this.newElementIndex = index;
}
},
dropHandler(e) {
if (this.isDragging()) {
this.$emit('onsort', this.sortingElementIndex, this.newElementIndex);
}
},
customRowRender(record, index) {
const parentMethodResult = this.customRow?.(record, index) || {};
const newIndex = this.newElementIndex;
const currentIndex = this.sortingElementIndex;
return {
...parentMethodResult,
attrs: {
...(parentMethodResult?.attrs || {}),
draggable: true,
},
on: {
...(parentMethodResult?.on || {}),
dragstart: (e) => this.dragStartHandler(e, index),
dragend: (e) => this.dragStopHandler(e, index),
dragover: (e) => this.dragOverHandler(e, index),
},
class: {
...(parentMethodResult?.class || {}),
[DRAGGABLE_ROW_CLASS]: true,
['dragging']: this.isDragging()
? (newIndex === null ? index === currentIndex : index === newIndex)
: false,
},
};
}
},
computed: {
records() {
const newIndex = this.newElementIndex;
const currentIndex = this.sortingElementIndex;
if (!this.isDragging() || newIndex === null || currentIndex === newIndex) {
return this.dataSource;
}
if (this.$memoSort.newIndex === newIndex) {
return this.$memoSort.list;
}
let list = [...this.dataSource];
list.splice(newIndex, 0, list.splice(currentIndex, 1)[0]);
this.$memoSort = {
newIndex,
list,
};
return list;
}
} }
}); },
mouseUpHandler(e) {
Vue.component('table-sort-trigger', { if (this.sortable) {
template: `{{template "component/sortableTableTrigger"}}`, this.sortable.resetSortableIndex(e, this.itemIndex);
props: ['item-index'],
inject: ['sortable'],
methods: {
mouseDownHandler(e) {
if (this.sortable) {
this.sortable.setSortableIndex(e, this.itemIndex);
}
},
mouseUpHandler(e) {
if (this.sortable) {
this.sortable.resetSortableIndex(e, this.itemIndex);
}
},
clickHandler(e) {
e.preventDefault();
},
} }
}) },
clickHandler(e) {
e.preventDefault();
},
}
})
</script> </script>
<style> <style>
@media only screen and (max-width: 767px) { @media only screen and (max-width: 767px) {
.sortable-icon { .sortable-icon {
display: none; display: none;
}
}
.ant-table-is-sorting .draggable-row td {
background-color: #ffffff !important;
}
.dark .ant-table-is-sorting .draggable-row td {
background-color: var(--dark-color-surface-100) !important;
}
.ant-table-is-sorting .dragging td {
background-color: rgb(232 244 242) !important;
color: rgba(0, 0, 0, 0.3);
}
.dark .ant-table-is-sorting .dragging td {
background-color: var(--dark-color-table-hover) !important;
color: rgba(255, 255, 255, 0.3);
}
.ant-table-is-sorting .dragging {
opacity: 1;
box-shadow: 1px -2px 2px #008771;
transition: all 0.2s;
}
.ant-table-is-sorting .dragging .ant-table-row-index {
opacity: 0.3;
} }
}
.ant-table-is-sorting .draggable-row td {
background-color: #ffffff !important;
}
.dark .ant-table-is-sorting .draggable-row td {
background-color: var(--dark-color-surface-100) !important;
}
.ant-table-is-sorting .dragging td {
background-color: rgb(232 244 242) !important;
color: rgba(0, 0, 0, 0.3);
}
.dark .ant-table-is-sorting .dragging td {
background-color: var(--dark-color-table-hover) !important;
color: rgba(255, 255, 255, 0.3);
}
.ant-table-is-sorting .dragging {
opacity: 1;
box-shadow: 1px -2px 2px #008771;
transition: all 0.2s;
}
.ant-table-is-sorting .dragging .ant-table-row-index {
opacity: 0.3;
}
</style> </style>
{{end}} {{end}}

View File

@@ -1,6 +1,23 @@
{{define "component/themeSwitchTemplate"}} {{define "component/themeSwitchTemplate"}}
<template> <template>
<a-menu class="change-theme" :theme="themeSwitcher.currentTheme" mode="inline" selected-keys=""> <a-menu :theme="themeSwitcher.currentTheme" mode="inline" selected-keys="">
<a-sub-menu>
<span slot="title">
<a-icon type="bulb" :theme="themeSwitcher.isDarkTheme ? 'filled' : 'outlined'"></a-icon>
<span>Theme</span>
</span>
<a-menu-item id="change-theme" class="ant-menu-theme-switch" @mousedown="themeSwitcher.animationsOff()"> Dark <a-switch style="margin-left: 2px;" size="small" :default-checked="themeSwitcher.isDarkTheme" @change="themeSwitcher.toggleTheme()"></a-switch>
</a-menu-item>
<a-menu-item id="change-theme-ultra" v-if="themeSwitcher.isDarkTheme" class="ant-menu-theme-switch" @mousedown="themeSwitcher.animationsOffUltra()"> Ultra <a-checkbox style="margin-left: 2px;" :checked="themeSwitcher.isUltra" @click="themeSwitcher.toggleUltra()"></a-checkbox>
</a-menu-item>
</a-sub-menu>
</a-menu>
</template>
{{end}}
{{define "component/themeSwitchTemplateLogin"}}
<template>
<a-menu @mousedown="themeSwitcher.animationsOff()" id="change-theme" :theme="themeSwitcher.currentTheme" mode="inline" selected-keys="">
<a-menu-item mode="inline" class="ant-menu-theme-switch"> <a-menu-item mode="inline" class="ant-menu-theme-switch">
<a-icon type="bulb" :theme="themeSwitcher.isDarkTheme ? 'filled' : 'outlined'"></a-icon> <a-icon type="bulb" :theme="themeSwitcher.isDarkTheme ? 'filled' : 'outlined'"></a-icon>
<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>
@@ -23,6 +40,26 @@
const theme = isDarkTheme ? 'dark' : 'light'; const theme = isDarkTheme ? 'dark' : 'light';
document.querySelector('body').setAttribute('class', theme); document.querySelector('body').setAttribute('class', theme);
return { return {
animationsOff() {
document.documentElement.setAttribute('data-theme-animations', 'off');
const themeAnimations = document.querySelector('#change-theme');
themeAnimations.addEventListener('mouseleave', () => {
document.documentElement.removeAttribute('data-theme-animations');
});
themeAnimations.addEventListener('touchend', () => {
document.documentElement.removeAttribute('data-theme-animations');
});
},
animationsOffUltra() {
document.documentElement.setAttribute('data-theme-animations', 'off');
const themeAnimationsUltra = document.querySelector('#change-theme-ultra');
themeAnimationsUltra.addEventListener('mouseleave', () => {
document.documentElement.removeAttribute('data-theme-animations');
});
themeAnimationsUltra.addEventListener('touchend', () => {
document.documentElement.removeAttribute('data-theme-animations');
});
},
isDarkTheme, isDarkTheme,
isUltra, isUltra,
get currentTheme() { get currentTheme() {
@@ -57,13 +94,19 @@
getContainer: () => document.getElementById('message') getContainer: () => document.getElementById('message')
}); });
document.getElementById('message').className = themeSwitcher.currentTheme; document.getElementById('message').className = themeSwitcher.currentTheme;
const themeAnimations = document.querySelector('.change-theme'); }
themeAnimations.addEventListener('mousedown', () => { });
document.documentElement.setAttribute('data-theme-animations', 'off'); Vue.component('theme-switch-login', {
}); props: [],
themeAnimations.addEventListener('mouseleave', () => { template: `{{template "component/themeSwitchTemplateLogin"}}`,
document.documentElement.removeAttribute('data-theme-animations'); data: () => ({
themeSwitcher
}),
mounted() {
this.$message.config({
getContainer: () => document.getElementById('message')
}); });
document.getElementById('message').className = themeSwitcher.currentTheme;
} }
}); });
</script> </script>

View File

@@ -1,86 +1,95 @@
{{define "dnsModal"}} {{define "dnsModal"}}
<a-modal id="dns-modal" v-model="dnsModal.visible" :title="dnsModal.title" @ok="dnsModal.ok" <a-modal id="dns-modal" v-model="dnsModal.visible" :title="dnsModal.title" @ok="dnsModal.ok" :closable="true" :mask-closable="false" :ok-text="dnsModal.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="dnsModal.okText" cancel-text='{{ i18n "close" }}' :class="themeSwitcher.currentTheme"> <a-form-item label='{{ i18n "pages.xray.outbound.address" }}'>
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }"> <a-input v-model.trim="dnsModal.dnsServer.address"></a-input>
<a-form-item label='{{ i18n "pages.xray.outbound.address" }}'> </a-form-item>
<a-input v-model.trim="dnsModal.dnsServer.address"></a-input> <a-form-item label='{{ i18n "pages.xray.dns.domains" }}'>
</a-form-item> <a-button icon="plus" size="small" type="primary" @click="dnsModal.dnsServer.domains.push('')"></a-button>
<a-form-item label='{{ i18n "pages.xray.dns.domains" }}'> <template v-for="(domain, index) in dnsModal.dnsServer.domains">
<a-button size="small" type="primary" @click="dnsModal.dnsServer.domains.push('')">+</a-button> <a-input v-model.trim="dnsModal.dnsServer.domains[index]">
<template v-for="(domain, index) in dnsModal.dnsServer.domains"> <a-button icon="minus" size="small" slot="addonAfter" @click="dnsModal.dnsServer.domains.splice(index,1)"></a-button>
<a-input v-model.trim="dnsModal.dnsServer.domains[index]"> </a-input>
<a-button size="small" slot="addonAfter" @click="dnsModal.dnsServer.domains.splice(index,1)">-</a-button> </template>
</a-input> </a-form-item>
</template> <a-form-item label='{{ i18n "pages.xray.dns.strategy" }}' v-if="isAdvanced">
</a-form-item> <a-select v-model="dnsModal.dnsServer.queryStrategy" style="width: 100%" :dropdown-class-name="themeSwitcher.currentTheme">
<a-form-item label='{{ i18n "pages.xray.dns.strategy" }}' v-if="isAdvanced"> <a-select-option :value="l" :label="l" v-for="l in ['UseIP', 'UseIPv4', 'UseIPv6']"> [[ l ]] </a-select-option>
<a-select </a-select>
v-model="dnsModal.dnsServer.queryStrategy" </a-form-item>
style="width: 100%" <a-form-item label='Skip Fallback' v-if="isAdvanced">
:dropdown-class-name="themeSwitcher.currentTheme"> <a-switch v-model="dnsModal.dnsServer.skipFallback"></a-switch>
<a-select-option :value="l" :label="l" v-for="l in ['UseIP', 'UseIPv4', 'UseIPv6']"> </a-form-item>
[[ l ]] </a-form>
</a-select-option>
</a-select>
</a-form-item>
</a-form>
</a-modal> </a-modal>
<script> <script>
const dnsModal = { const dnsModal = {
title: '', title: '',
visible: false, visible: false,
okText: '{{ i18n "confirm" }}', okText: '{{ i18n "confirm" }}',
isEdit: false, isEdit: false,
confirm: null, confirm: null,
dnsServer: { dnsServer: {
address: "localhost",
domains: [],
queryStrategy: 'UseIP',
skipFallback: true,
},
ok() {
domains = dnsModal.dnsServer.domains.filter(d => d.length > 0);
dnsModal.dnsServer.domains = domains;
newDnsServer = domains.length > 0 ? dnsModal.dnsServer : dnsModal.dnsServer.address;
ObjectUtil.execute(dnsModal.confirm, newDnsServer);
},
show({
title = '',
okText = '{{ i18n "confirm" }}',
dnsServer,
confirm = (dnsServer) => {},
isEdit = false
}) {
this.title = title;
this.okText = okText;
this.confirm = confirm;
this.visible = true;
if (isEdit) {
if (typeof dnsServer == 'object') {
this.dnsServer = dnsServer;
} else {
this.dnsServer = {
address: dnsServer ?? "",
domains: [],
queryStrategy: 'UseIP',
skipFallback: true,
}
}
} else {
this.dnsServer = {
address: "localhost", address: "localhost",
domains: [], domains: [],
queryStrategy: 'UseIP', queryStrategy: 'UseIP',
}, skipFallback: true,
ok() {
domains = dnsModal.dnsServer.domains.filter(d => d.length>0);
dnsModal.dnsServer.domains = domains;
newDnsServer = domains.length > 0 ? dnsModal.dnsServer : dnsModal.dnsServer.address;
ObjectUtil.execute(dnsModal.confirm, newDnsServer);
},
show({ title='', okText='{{ i18n "confirm" }}', dnsServer, confirm=(dnsServer)=>{}, isEdit=false }) {
this.title = title;
this.okText = okText;
this.confirm = confirm;
this.visible = true;
if(isEdit) {
if (typeof dnsServer == 'object'){
this.dnsServer = dnsServer;
} else {
this.dnsServer.address = dnsServer?? '';
}
} else {
this.dnsServer = {
address: "localhost",
domains: [],
queryStrategy: 'UseIP',
}
}
this.isEdit = isEdit;
},
close() {
dnsModal.visible = false;
},
};
new Vue({
delimiters: ['[[', ']]'],
el: '#dns-modal',
data: {
dnsModal: dnsModal,
},
computed: {
isAdvanced: {
get: function () { return dnsModal.dnsServer.domains.length>0 }
}
} }
}); }
this.isEdit = isEdit;
},
close() {
dnsModal.visible = false;
},
};
new Vue({
delimiters: ['[[', ']]'],
el: '#dns-modal',
data: {
dnsModal: dnsModal,
},
computed: {
isAdvanced: {
get: function() {
return dnsModal.dnsServer.domains.length > 0
}
}
}
});
</script> </script>
{{end}} {{end}}

View File

@@ -1,225 +1,211 @@
{{define "form/outbound"}} {{define "form/outbound"}}
<!-- base --> <!-- base -->
<a-tabs :active-key="outModal.activeKey" style="padding: 0; background-color: transparent;" @change="(activeKey) => {outModal.toggleJson(activeKey == '2'); }"> <a-tabs :active-key="outModal.activeKey" style="padding: 0; background-color: transparent;" @change="(activeKey) => {outModal.toggleJson(activeKey == '2'); }">
<a-tab-pane key="1" tab="Form"> <a-tab-pane key="1" tab="Form">
<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 "protocol" }}'> <a-form-item label='{{ i18n "protocol" }}'>
<a-select v-model="outbound.protocol" :dropdown-class-name="themeSwitcher.currentTheme"> <a-select v-model="outbound.protocol" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="x,y in Protocols" :value="x">[[ y ]]</a-select-option> <a-select-option v-for="x,y in Protocols" :value="x">[[ y ]]</a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
<a-form-item label='{{ i18n "pages.xray.outbound.tag" }}' has-feedback :validate-status="outModal.duplicateTag? 'warning' : 'success'"> <a-form-item label='{{ i18n "pages.xray.outbound.tag" }}' has-feedback :validate-status="outModal.duplicateTag? 'warning' : 'success'">
<a-input v-model.trim="outbound.tag" @change="outModal.check()" placeholder='{{ i18n "pages.xray.outbound.tagDesc" }}'></a-input> <a-input v-model.trim="outbound.tag" @change="outModal.check()" placeholder='{{ i18n "pages.xray.outbound.tagDesc" }}'></a-input>
</a-form-item> </a-form-item>
<a-form-item label='{{ i18n "pages.xray.outbound.sendThrough" }}'> <a-form-item label='{{ i18n "pages.xray.outbound.sendThrough" }}'>
<a-input v-model="outbound.sendThrough"></a-input> <a-input v-model="outbound.sendThrough"></a-input>
</a-form-item> </a-form-item>
<!-- freedom settings--> <!-- freedom settings-->
<template v-if="outbound.protocol === Protocols.Freedom"> <template v-if="outbound.protocol === Protocols.Freedom">
<a-form-item label='Strategy'> <a-form-item label='Strategy'>
<a-select <a-select v-model="outbound.settings.domainStrategy" :dropdown-class-name="themeSwitcher.currentTheme">
v-model="outbound.settings.domainStrategy" <a-select-option v-for="s in OutboundDomainStrategies" :value="s">[[ s ]]</a-select-option>
:dropdown-class-name="themeSwitcher.currentTheme"> </a-select>
<a-select-option v-for="s in OutboundDomainStrategies" :value="s">[[ s ]]</a-select-option>
</a-select>
</a-form-item> </a-form-item>
<a-form-item label='Fragment'> <a-form-item label='Fragment'>
<a-switch <a-switch :checked="Object.keys(outbound.settings.fragment).length >0" @change="checked => outbound.settings.fragment = checked ? new Outbound.FreedomSettings.Fragment() : {}"></a-switch>
:checked="Object.keys(outbound.settings.fragment).length >0"
@change="checked => outbound.settings.fragment = checked ? new Outbound.FreedomSettings.Fragment() : {}">
</a-switch>
</a-form-item> </a-form-item>
<template v-if="Object.keys(outbound.settings.fragment).length >0"> <template v-if="Object.keys(outbound.settings.fragment).length >0">
<a-form-item label='Packets'> <a-form-item label='Packets'>
<a-select <a-select v-model="outbound.settings.fragment.packets" :dropdown-class-name="themeSwitcher.currentTheme">
v-model="outbound.settings.fragment.packets" <a-select-option v-for="s in ['1-3','tlshello']" :value="s">[[ s ]]</a-select-option>
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="s in ['1-3','tlshello']" :value="s">[[ s ]]</a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
<a-form-item label='Length'> <a-form-item label='Length'>
<a-input v-model.trim="outbound.settings.fragment.length"></a-input> <a-input v-model.trim="outbound.settings.fragment.length"></a-input>
</a-form-item> </a-form-item>
<a-form-item label='Interval'> <a-form-item label='Interval'>
<a-input v-model.trim="outbound.settings.fragment.interval"></a-input> <a-input v-model.trim="outbound.settings.fragment.interval"></a-input>
</a-form-item> </a-form-item>
</template> </template>
</template> </template>
<!-- blackhole settings --> <!-- blackhole settings -->
<template v-if="outbound.protocol === Protocols.Blackhole"> <template v-if="outbound.protocol === Protocols.Blackhole">
<a-form-item label='Response Type'> <a-form-item label='Response Type'>
<a-select <a-select v-model="outbound.settings.type" :dropdown-class-name="themeSwitcher.currentTheme">
v-model="outbound.settings.type"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="s in ['', 'none','http']" :value="s">[[ s ]]</a-select-option> <a-select-option v-for="s in ['', 'none','http']" :value="s">[[ s ]]</a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
</template> </template>
<!-- dns settings --> <!-- dns settings -->
<template v-if="outbound.protocol === Protocols.DNS"> <template v-if="outbound.protocol === Protocols.DNS">
<a-form-item label='{{ i18n "pages.inbounds.network" }}'> <a-form-item label='{{ i18n "pages.inbounds.network" }}'>
<a-select <a-select v-model="outbound.settings.network" :dropdown-class-name="themeSwitcher.currentTheme">
v-model="outbound.settings.network"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="s in ['udp','tcp']" :value="s">[[ s ]]</a-select-option> <a-select-option v-for="s in ['udp','tcp']" :value="s">[[ s ]]</a-select-option>
</a-select> </a-select>
</a-form-item>
</template>
<!-- wireguard settings -->
<template v-if="outbound.protocol === Protocols.Wireguard">
<a-form-item>
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.xray.rules.useComma" }}</span>
</template>
{{ i18n "pages.xray.outbound.address" }} <a-icon type="question-circle"></a-icon>
</a-tooltip>
</template>
<a-input v-model.trim="outbound.settings.address"></a-input>
</a-form-item>
<a-form-item>
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "reset" }}</span>
</template>
{{ i18n "pages.xray.wireguard.secretKey" }}
<a-icon type="sync"
@click="[outbound.settings.pubKey, outbound.settings.secretKey] = Object.values(Wireguard.generateKeypair())">
</a-icon>
</a-tooltip>
</template>
<a-input v-model.trim="outbound.settings.secretKey"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "pages.xray.wireguard.publicKey" }}'>
<a-input disabled v-model="outbound.settings.pubKey"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "pages.xray.wireguard.domainStrategy" }}'>
<a-select v-model="outbound.settings.domainStrategy" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="wds in ['', ...WireguardDomainStrategy]" :value="wds">[[ wds ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item label='MTU'>
<a-input-number v-model.number="outbound.settings.mtu"></a-input-number>
</a-form-item>
<a-form-item label='Workers'>
<a-input-number min="0" v-model.number="outbound.settings.workers"></a-input>
</a-form-item>
<a-form-item label='Kernel Mode'>
<a-switch v-model="outbound.settings.kernelMode"></a-switch>
</a-form-item>
<a-form-item>
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.xray.rules.useComma" }}</span>
</template>
Reserved <a-icon type="question-circle"></a-icon>
</a-tooltip>
</template>
<a-input v-model="outbound.settings.reserved"></a-input>
</a-form-item>
<a-form-item label="Peers">
<a-button type="primary" size="small" @click="outbound.settings.addPeer()">+</a-button>
</a-form-item>
<a-form v-for="(peer, index) in outbound.settings.peers" :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
<a-divider style="margin:0;">
Peer [[ index + 1 ]]
<a-icon v-if="outbound.settings.peers.length>1" type="delete" @click="() => outbound.settings.delPeer(index)"
style="color: rgb(255, 77, 79);cursor: pointer;"/>
</a-divider>
<a-form-item label='{{ i18n "pages.xray.wireguard.endpoint" }}'>
<a-input v-model.trim="peer.endpoint"></a-input>
</a-form-item> </a-form-item>
<a-form-item label='{{ i18n "pages.xray.wireguard.publicKey" }}'> </template>
<a-input v-model.trim="peer.publicKey"></a-input>
</a-form-item> <!-- wireguard settings -->
<a-form-item label='{{ i18n "pages.xray.wireguard.psk" }}'> <template v-if="outbound.protocol === Protocols.Wireguard">
<a-input v-model.trim="peer.psk"></a-input> <a-form-item>
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.xray.rules.useComma" }}</span>
</template>
{{ i18n "pages.xray.outbound.address" }}
<a-icon type="question-circle"></a-icon>
</a-tooltip>
</template>
<a-input v-model.trim="outbound.settings.address"></a-input>
</a-form-item> </a-form-item>
<a-form-item> <a-form-item>
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "reset" }}</span>
</template>
{{ i18n "pages.xray.wireguard.secretKey" }}
<a-icon type="sync" @click="[outbound.settings.pubKey, outbound.settings.secretKey] = Object.values(Wireguard.generateKeypair())"></a-icon>
</a-tooltip>
</template>
<a-input v-model.trim="outbound.settings.secretKey"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "pages.xray.wireguard.publicKey" }}'>
<a-input disabled v-model="outbound.settings.pubKey"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "pages.xray.wireguard.domainStrategy" }}'>
<a-select v-model="outbound.settings.domainStrategy" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="wds in ['', ...WireguardDomainStrategy]" :value="wds">[[ wds ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item label='MTU'>
<a-input-number v-model.number="outbound.settings.mtu"></a-input-number>
</a-form-item>
<a-form-item label='Workers'>
<a-input-number min="0" v-model.number="outbound.settings.workers"></a-input-number>
</a-form-item>
<a-form-item label='Kernel Mode'>
<a-switch v-model="outbound.settings.kernelMode"></a-switch>
</a-form-item>
<a-form-item>
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.xray.rules.useComma" }}</span>
</template> Reserved <a-icon type="question-circle"></a-icon>
</a-tooltip>
</template>
<a-input v-model="outbound.settings.reserved"></a-input>
</a-form-item>
<a-form-item label="Peers">
<a-button icon="plus" type="primary" size="small" @click="outbound.settings.addPeer()"></a-button>
</a-form-item>
<a-form v-for="(peer, index) in outbound.settings.peers" :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
<a-divider style="margin:0;"> Peer [[ index + 1 ]] <a-icon v-if="outbound.settings.peers.length>1" type="delete" @click="() => outbound.settings.delPeer(index)" style="color: rgb(255, 77, 79);cursor: pointer;"></a-icon>
</a-divider>
<a-form-item label='{{ i18n "pages.xray.wireguard.endpoint" }}'>
<a-input v-model.trim="peer.endpoint"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "pages.xray.wireguard.publicKey" }}'>
<a-input v-model.trim="peer.publicKey"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "pages.xray.wireguard.psk" }}'>
<a-input v-model.trim="peer.psk"></a-input>
</a-form-item>
<a-form-item>
<template slot="label"> <template slot="label">
{{ i18n "pages.xray.wireguard.allowedIPs" }} <a-button type="primary" size="small" @click="peer.allowedIPs.push('')">+</a-button> {{ i18n "pages.xray.wireguard.allowedIPs" }}
<a-button icon="plus" type="primary" size="small" @click="peer.allowedIPs.push('')"></a-button>
</template> </template>
<template v-for="(aip, index) in peer.allowedIPs" style="margin-bottom: 10px;"> <template v-for="(aip, index) in peer.allowedIPs" style="margin-bottom: 10px;">
<a-input v-model.trim="peer.allowedIPs[index]"> <a-input v-model.trim="peer.allowedIPs[index]">
<a-button v-if="peer.allowedIPs.length>1" slot="addonAfter" size="small" @click="peer.allowedIPs.splice(index, 1)">-</a-button> <a-button icon="minus" v-if="peer.allowedIPs.length>1" slot="addonAfter" size="small" @click="peer.allowedIPs.splice(index, 1)"></a-button>
</a-input> </a-input>
</template> </template>
</a-form-item> </a-form-item>
<a-form-item label='Keep Alive'> <a-form-item label='Keep Alive'>
<a-input-number v-model.number="peer.keepAlive" :min="0"></a-input> <a-input-number v-model.number="peer.keepAlive" :min="0"></a-input-number>
</a-form-item> </a-form-item>
</a-form> </a-form>
</template> </template>
<!-- Address + Port --> <!-- Address + Port -->
<template v-if="outbound.hasAddressPort()"> <template v-if="outbound.hasAddressPort()">
<a-form-item label='{{ i18n "pages.inbounds.address" }}'> <a-form-item label='{{ i18n "pages.inbounds.address" }}'>
<a-input v-model.trim="outbound.settings.address"></a-input> <a-input v-model.trim="outbound.settings.address"></a-input>
</a-form-item> </a-form-item>
<a-form-item label='{{ i18n "pages.inbounds.port" }}'> <a-form-item label='{{ i18n "pages.inbounds.port" }}'>
<a-input-number v-model.number="outbound.settings.port" :min="1" :max="65532"></a-input-number> <a-input-number v-model.number="outbound.settings.port" :min="1" :max="65532"></a-input-number>
</a-form-item> </a-form-item>
</template> </template>
<!-- Vnext (vless/vmess) settings --> <!-- Vnext (vless/vmess) settings -->
<template v-if="[Protocols.VMess, Protocols.VLESS].includes(outbound.protocol)"> <template v-if="[Protocols.VMess, Protocols.VLESS].includes(outbound.protocol)">
<a-form-item label='ID'> <a-form-item label='ID'>
<a-input v-model.trim="outbound.settings.id"></a-input> <a-input v-model.trim="outbound.settings.id"></a-input>
</a-form-item> </a-form-item>
<!-- vless settings -->
<template v-if="outbound.canEnableTlsFlow()"> <!-- vless settings -->
<a-form-item label='Flow'> <template v-if="outbound.canEnableTlsFlow()">
<a-form-item label='Flow'>
<a-select v-model="outbound.settings.flow" :dropdown-class-name="themeSwitcher.currentTheme"> <a-select v-model="outbound.settings.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">[[ key ]]</a-select-option> <a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
</template> </template>
</template> </template>
<!-- Servers (trojan/shadowsocks/socks/http) settings --> <!-- Servers (trojan/shadowsocks/socks/http) settings -->
<template v-if="outbound.hasServers()"> <template v-if="outbound.hasServers()">
<!-- http / socks --> <!-- http / socks -->
<template v-if="outbound.hasUsername()"> <template v-if="outbound.hasUsername()">
<a-form-item label='{{ i18n "username" }}'> <a-form-item label='{{ i18n "username" }}'>
<a-input v-model.trim="outbound.settings.user"></a-input> <a-input v-model.trim="outbound.settings.user"></a-input>
</a-form-item> </a-form-item>
<a-form-item label='{{ i18n "password" }}'> <a-form-item label='{{ i18n "password" }}'>
<a-input v-model.trim="outbound.settings.pass"></a-input> <a-input v-model.trim="outbound.settings.pass"></a-input>
</a-form-item> </a-form-item>
</template> </template>
<!-- trojan/shadowsocks -->
<template v-if="[Protocols.Trojan, Protocols.Shadowsocks].includes(outbound.protocol)">
<a-form-item label='{{ i18n "password" }}'>
<a-input v-model.trim="outbound.settings.password"></a-input>
</a-form-item>
</template>
<!-- shadowsocks -->
<template v-if="outbound.protocol === Protocols.Shadowsocks">
<a-form-item label='{{ i18n "encryption" }}'>
<a-select v-model="outbound.settings.method" :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-form-item>
<a-form-item label='UDP over TCP'>
<a-switch v-model="outbound.settings.uot"></a-switch>
</a-form-item>
</template>
</template>
<!-- stream settings --> <!-- trojan/shadowsocks -->
<template v-if="outbound.canEnableStream()"> <template v-if="[Protocols.Trojan, Protocols.Shadowsocks].includes(outbound.protocol)">
<a-form-item label='{{ i18n "transmission" }}'> <a-form-item label='{{ i18n "password" }}'>
<a-select v-model="outbound.stream.network" @change="streamNetworkChange" <a-input v-model.trim="outbound.settings.password"></a-input>
:dropdown-class-name="themeSwitcher.currentTheme"> </a-form-item>
</template>
<!-- shadowsocks -->
<template v-if="outbound.protocol === Protocols.Shadowsocks">
<a-form-item label='{{ i18n "encryption" }}'>
<a-select v-model="outbound.settings.method" :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-form-item>
<a-form-item label='UDP over TCP'>
<a-switch v-model="outbound.settings.uot"></a-switch>
</a-form-item>
</template>
</template>
<!-- stream settings -->
<template v-if="outbound.canEnableStream()">
<a-form-item label='{{ i18n "transmission" }}'>
<a-select v-model="outbound.stream.network" @change="streamNetworkChange" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="tcp">TCP</a-select-option> <a-select-option value="tcp">TCP</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>
@@ -227,235 +213,232 @@
<a-select-option value="quic">QUIC</a-select-option> <a-select-option value="quic">QUIC</a-select-option>
<a-select-option value="grpc">gRPC</a-select-option> <a-select-option value="grpc">gRPC</a-select-option>
<a-select-option value="httpupgrade">HTTPUpgrade</a-select-option> <a-select-option value="httpupgrade">HTTPUpgrade</a-select-option>
</a-select> </a-select>
</a-form-item>
<template v-if="outbound.stream.network === 'tcp'">
<a-form-item label='HTTP {{ i18n "camouflage" }}'>
<a-switch :checked="outbound.stream.tcp.type === 'http'"
@change="checked => outbound.stream.tcp.type = checked ? 'http' : 'none'">
</a-switch>
</a-form-item> </a-form-item>
<template v-if="outbound.stream.tcp.type == 'http'"> <template v-if="outbound.stream.network === 'tcp'">
<a-form-item label='HTTP {{ i18n "camouflage" }}'>
<a-switch :checked="outbound.stream.tcp.type === 'http'" @change="checked => outbound.stream.tcp.type = checked ? 'http' : 'none'"></a-switch>
</a-form-item>
<template v-if="outbound.stream.tcp.type == 'http'">
<a-form-item label='{{ i18n "host" }}'> <a-form-item label='{{ i18n "host" }}'>
<a-input v-model.trim="outbound.stream.tcp.host"></a-input> <a-input v-model.trim="outbound.stream.tcp.host"></a-input>
</a-form-item> </a-form-item>
<a-form-item label='{{ i18n "path" }}'> <a-form-item label='{{ i18n "path" }}'>
<a-input v-model.trim="outbound.stream.tcp.path"></a-input> <a-input v-model.trim="outbound.stream.tcp.path"></a-input>
</a-form-item> </a-form-item>
</template>
</template> </template>
</template>
<!-- kcp --> <!-- kcp -->
<template v-if="outbound.stream.network === 'kcp'"> <template v-if="outbound.stream.network === 'kcp'">
<a-form-item label='{{ i18n "camouflage" }}'> <a-form-item label='{{ i18n "camouflage" }}'>
<a-select v-model="outbound.stream.kcp.type" :dropdown-class-name="themeSwitcher.currentTheme"> <a-select v-model="outbound.stream.kcp.type" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="none">None</a-select-option> <a-select-option value="none">None</a-select-option>
<a-select-option value="srtp">SRTP</a-select-option> <a-select-option value="srtp">SRTP</a-select-option>
<a-select-option value="utp">uTP</a-select-option> <a-select-option value="utp">uTP</a-select-option>
<a-select-option value="wechat-video">WeChat</a-select-option> <a-select-option value="wechat-video">WeChat</a-select-option>
<a-select-option value="dtls">DTLS 1.2</a-select-option> <a-select-option value="dtls">DTLS 1.2</a-select-option>
<a-select-option value="wireguard">WireGuard</a-select-option> <a-select-option value="wireguard">WireGuard</a-select-option>
<a-select-option value="dns">DNS</a-select-option> <a-select-option value="dns">DNS</a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
<a-form-item label='{{ i18n "password" }}'> <a-form-item label='{{ i18n "password" }}'>
<a-input v-model="outbound.stream.kcp.seed"></a-input> <a-input v-model="outbound.stream.kcp.seed"></a-input>
</a-form-item> </a-form-item>
<a-form-item label='MTU'> <a-form-item label='MTU'>
<a-input-number v-model.number="outbound.stream.kcp.mtu"></a-input-number> <a-input-number v-model.number="outbound.stream.kcp.mtu"></a-input-number>
</a-form-item> </a-form-item>
<a-form-item label='TTI (ms)'> <a-form-item label='TTI (ms)'>
<a-input-number v-model.number="outbound.stream.kcp.tti"></a-input-number> <a-input-number v-model.number="outbound.stream.kcp.tti"></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 v-model.number="outbound.stream.kcp.upCap"></a-input-number> <a-input-number v-model.number="outbound.stream.kcp.upCap"></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 v-model.number="outbound.stream.kcp.downCap"></a-input-number> <a-input-number v-model.number="outbound.stream.kcp.downCap"></a-input-number>
</a-form-item> </a-form-item>
<a-form-item label='Congestion'> <a-form-item label='Congestion'>
<a-switch v-model="outbound.stream.kcp.congestion"></a-switch> <a-switch v-model="outbound.stream.kcp.congestion"></a-switch>
</a-form-item> </a-form-item>
<a-form-item label='Read Buffer (MB)'> <a-form-item label='Read Buffer (MB)'>
<a-input-number v-model.number="outbound.stream.kcp.readBuffer"></a-input-number> <a-input-number v-model.number="outbound.stream.kcp.readBuffer"></a-input-number>
</a-form-item> </a-form-item>
<a-form-item label='Write Buffer (MB)'> <a-form-item label='Write Buffer (MB)'>
<a-input-number v-model.number="outbound.stream.kcp.writeBuffer"></a-input-number> <a-input-number v-model.number="outbound.stream.kcp.writeBuffer"></a-input-number>
</a-form-item> </a-form-item>
</template> </template>
<!-- ws --> <!-- ws -->
<template v-if="outbound.stream.network === 'ws'"> <template v-if="outbound.stream.network === 'ws'">
<a-form-item label='{{ i18n "host" }}'> <a-form-item label='{{ i18n "host" }}'>
<a-input v-model="outbound.stream.ws.host"></a-input> <a-input v-model="outbound.stream.ws.host"></a-input>
</a-form-item> </a-form-item>
<a-form-item label='{{ i18n "path" }}'> <a-form-item label='{{ i18n "path" }}'>
<a-input v-model.trim="outbound.stream.ws.path"></a-input> <a-input v-model.trim="outbound.stream.ws.path"></a-input>
</a-form-item> </a-form-item>
</template> </template>
<!-- http --> <!-- http -->
<template v-if="outbound.stream.network === 'http'"> <template v-if="outbound.stream.network === 'http'">
<a-form-item label='{{ i18n "host" }}'> <a-form-item label='{{ i18n "host" }}'>
<a-input v-model.trim="outbound.stream.http.host"></a-input> <a-input v-model.trim="outbound.stream.http.host"></a-input>
</a-form-item> </a-form-item>
<a-form-item label='{{ i18n "path" }}'> <a-form-item label='{{ i18n "path" }}'>
<a-input v-model.trim="outbound.stream.http.path"></a-input> <a-input v-model.trim="outbound.stream.http.path"></a-input>
</a-form-item> </a-form-item>
</template> </template>
<!-- quic --> <!-- quic -->
<template v-if="outbound.stream.network === 'quic'"> <template v-if="outbound.stream.network === 'quic'">
<a-form-item label='{{ i18n "pages.inbounds.stream.quic.encryption" }}'> <a-form-item label='{{ i18n "pages.inbounds.stream.quic.encryption" }}'>
<a-select v-model="outbound.stream.quic.security" :dropdown-class-name="themeSwitcher.currentTheme"> <a-select v-model="outbound.stream.quic.security" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="none">None</a-select-option> <a-select-option value="none">None</a-select-option>
<a-select-option value="aes-128-gcm">AES-128-GCM</a-select-option> <a-select-option value="aes-128-gcm">AES-128-GCM</a-select-option>
<a-select-option value="chacha20-poly1305">CHACHA20-POLY1305</a-select-option> <a-select-option value="chacha20-poly1305">CHACHA20-POLY1305</a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
<a-form-item label='{{ i18n "password" }}'> <a-form-item label='{{ i18n "password" }}'>
<a-input v-model.trim="outbound.stream.quic.key"></a-input> <a-input v-model.trim="outbound.stream.quic.key"></a-input>
</a-form-item> </a-form-item>
<a-form-item label='{{ i18n "camouflage" }}'> <a-form-item label='{{ i18n "camouflage" }}'>
<a-select v-model="outbound.stream.quic.type" :dropdown-class-name="themeSwitcher.currentTheme"> <a-select v-model="outbound.stream.quic.type" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="none">None</a-select-option> <a-select-option value="none">None</a-select-option>
<a-select-option value="srtp">SRTP</a-select-option> <a-select-option value="srtp">SRTP</a-select-option>
<a-select-option value="utp">uTP</a-select-option> <a-select-option value="utp">uTP</a-select-option>
<a-select-option value="wechat-video">WeChat</a-select-option> <a-select-option value="wechat-video">WeChat</a-select-option>
<a-select-option value="dtls">DTLS 1.2</a-select-option> <a-select-option value="dtls">DTLS 1.2</a-select-option>
<a-select-option value="wireguard">WireGuard</a-select-option> <a-select-option value="wireguard">WireGuard</a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
</template> </template>
<!-- grpc --> <!-- grpc -->
<template v-if="outbound.stream.network === 'grpc'"> <template v-if="outbound.stream.network === 'grpc'">
<a-form-item label='Service Name'> <a-form-item label='Service Name'>
<a-input v-model.trim="outbound.stream.grpc.serviceName"></a-input> <a-input v-model.trim="outbound.stream.grpc.serviceName"></a-input>
</a-form-item> </a-form-item>
<a-form-item label="Authority"> <a-form-item label="Authority">
<a-input v-model.trim="outbound.stream.grpc.authority"></a-input> <a-input v-model.trim="outbound.stream.grpc.authority"></a-input>
</a-form-item> </a-form-item>
<a-form-item label='Multi Mode'> <a-form-item label='Multi Mode'>
<a-switch v-model="outbound.stream.grpc.multiMode"></a-switch> <a-switch v-model="outbound.stream.grpc.multiMode"></a-switch>
</a-form-item> </a-form-item>
</template> </template>
<!-- httpupgrade --> <!-- httpupgrade -->
<template v-if="outbound.stream.network === 'httpupgrade'"> <template v-if="outbound.stream.network === 'httpupgrade'">
<a-form-item label='{{ i18n "host" }}'> <a-form-item label='{{ i18n "host" }}'>
<a-input v-model="outbound.stream.httpupgrade.host"></a-input> <a-input v-model="outbound.stream.httpupgrade.host"></a-input>
</a-form-item> </a-form-item>
<a-form-item label='{{ i18n "path" }}'> <a-form-item label='{{ i18n "path" }}'>
<a-input v-model.trim="outbound.stream.httpupgrade.path"></a-input> <a-input v-model.trim="outbound.stream.httpupgrade.path"></a-input>
</a-form-item> </a-form-item>
</template> </template>
</template> </template>
<!-- tls settings --> <!-- tls settings -->
<template v-if="outbound.canEnableTls()"> <template v-if="outbound.canEnableTls()">
<a-form-item label='{{ i18n "security" }}'> <a-form-item label='{{ i18n "security" }}'>
<a-radio-group v-model="outbound.stream.security" button-style="solid"> <a-radio-group v-model="outbound.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 value="tls">TLS</a-radio-button> <a-radio-button value="tls">TLS</a-radio-button>
<a-radio-button v-if="outbound.canEnableReality()" value="reality">Reality</a-radio-button> <a-radio-button v-if="outbound.canEnableReality()" value="reality">Reality</a-radio-button>
</a-radio-group> </a-radio-group>
</a-form-item> </a-form-item>
<template v-if="outbound.stream.isTls"> <template v-if="outbound.stream.isTls">
<a-form-item label="SNI" placeholder="Server Name Indication"> <a-form-item label="SNI" placeholder="Server Name Indication">
<a-input v-model.trim="outbound.stream.tls.serverName"></a-input> <a-input v-model.trim="outbound.stream.tls.serverName"></a-input>
</a-form-item> </a-form-item>
<a-form-item label="uTLS"> <a-form-item label="uTLS">
<a-select v-model="outbound.stream.tls.fingerprint" <a-select v-model="outbound.stream.tls.fingerprint" :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>
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
</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="outbound.stream.tls.alpn">
:dropdown-class-name="themeSwitcher.currentTheme" <a-select-option v-for="alpn in ALPN_OPTION" :value="alpn">[[ alpn ]]</a-select-option>
v-model="outbound.stream.tls.alpn">
<a-select-option v-for="alpn in ALPN_OPTION" :value="alpn">[[ alpn ]]</a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
<a-form-item label="Allow Insecure"> <a-form-item label="Allow Insecure">
<a-switch v-model="outbound.stream.tls.allowInsecure"></a-switch> <a-switch v-model="outbound.stream.tls.allowInsecure"></a-switch>
</a-form-item> </a-form-item>
</template> </template>
<!-- reality settings --> <!-- reality settings -->
<template v-if="outbound.stream.isReality"> <template v-if="outbound.stream.isReality">
<a-form-item label="SNI"> <a-form-item label="SNI">
<a-input v-model.trim="outbound.stream.reality.serverName"></a-input> <a-input v-model.trim="outbound.stream.reality.serverName"></a-input>
</a-form-item> </a-form-item>
<a-form-item label="uTLS"> <a-form-item label="uTLS">
<a-select v-model="outbound.stream.reality.fingerprint" <a-select v-model="outbound.stream.reality.fingerprint" :dropdown-class-name="themeSwitcher.currentTheme">
:dropdown-class-name="themeSwitcher.currentTheme"> <a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
<a-form-item label="Short ID"> <a-form-item label="Short ID">
<a-input v-model.trim="outbound.stream.reality.shortId" style="width:250px"></a-input> <a-input v-model.trim="outbound.stream.reality.shortId" style="width:250px"></a-input>
</a-form-item> </a-form-item>
<a-form-item label="SpiderX"> <a-form-item label="SpiderX">
<a-input v-model.trim="outbound.stream.reality.spiderX" style="width:250px"></a-input> <a-input v-model.trim="outbound.stream.reality.spiderX" style="width:250px"></a-input>
</a-form-item> </a-form-item>
<a-form-item label="Public Key"> <a-form-item label="Public Key">
<a-input v-model.trim="outbound.stream.reality.publicKey"></a-input> <a-input v-model.trim="outbound.stream.reality.publicKey"></a-input>
</a-form-item> </a-form-item>
</template> </template>
</template> </template>
<!-- sockopt settings --> <!-- sockopt settings -->
<a-form-item label="Sockopts"> <a-form-item label="Sockopts">
<a-switch v-model="outbound.stream.sockoptSwitch"></a-switch> <a-switch v-model="outbound.stream.sockoptSwitch"></a-switch>
</a-form-item> </a-form-item>
<template v-if="outbound.stream.sockoptSwitch"> <template v-if="outbound.stream.sockoptSwitch">
<a-form-item label="Dialer Proxy"> <a-form-item label="Dialer Proxy">
<a-select v-model="outbound.stream.sockopt.dialerProxy" :dropdown-class-name="themeSwitcher.currentTheme"> <a-select v-model="outbound.stream.sockopt.dialerProxy" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="tag in ['', ...outModal.tags]" :value="tag">[[ tag ]]</a-select-option> <a-select-option v-for="tag in ['', ...outModal.tags]" :value="tag">[[ tag ]]</a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
<a-form-item label="TCP Fast Open"> <a-form-item label="TCP Fast Open">
<a-switch v-model="outbound.stream.sockopt.tcpFastOpen"></a-switch> <a-switch v-model="outbound.stream.sockopt.tcpFastOpen"></a-switch>
</a-form-item> </a-form-item>
<a-form-item label="Keep Alive Interval"> <a-form-item label="Keep Alive Interval">
<a-input-number v-model="outbound.stream.sockopt.tcpKeepAliveInterval" :min="0"></a-input-number> <a-input-number v-model="outbound.stream.sockopt.tcpKeepAliveInterval" :min="0"></a-input-number>
</a-form-item> </a-form-item>
<a-form-item label="Multipath TCP">
<a-switch v-model.trim="outbound.stream.sockopt.tcpMptcp"></a-switch>
</a-form-item>
<a-form-item label="TCP No-Delay"> <a-form-item label="TCP No-Delay">
<a-switch v-model="outbound.stream.sockopt.tcpNoDelay"></a-switch> <a-switch v-model="outbound.stream.sockopt.tcpNoDelay"></a-switch>
</a-form-item> </a-form-item>
</template> </template>
<!-- mux settings --> <!-- mux settings -->
<template v-if="outbound.canEnableMux()"> <template v-if="outbound.canEnableMux()">
<a-form-item label="Mux"> <a-form-item label="Mux">
<a-switch v-model="outbound.mux.enabled"></a-switch> <a-switch v-model="outbound.mux.enabled"></a-switch>
</a-form-item> </a-form-item>
<template v-if="outbound.mux.enabled"> <template v-if="outbound.mux.enabled">
<a-form-item label="Concurrency"> <a-form-item label="Concurrency">
<a-input-number v-model="outbound.mux.concurrency" :min="-1" :max="1024"></a-input-number> <a-input-number v-model="outbound.mux.concurrency" :min="-1" :max="1024"></a-input-number>
</a-form-item> </a-form-item>
<a-form-item label="xudp Concurrency"> <a-form-item label="xudp Concurrency">
<a-input-number v-model="outbound.mux.xudpConcurrency" :min="-1" :max="1024"></a-input-number> <a-input-number v-model="outbound.mux.xudpConcurrency" :min="-1" :max="1024"></a-input-number>
</a-form-item> </a-form-item>
<a-form-item label="xudp UDP 443"> <a-form-item label="xudp UDP 443">
<a-select v-model="outbound.mux.xudpProxyUDP443" :dropdown-class-name="themeSwitcher.currentTheme"> <a-select v-model="outbound.mux.xudpProxyUDP443" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="c in ['reject', 'allow', 'skip']" :value="c">[[ c ]]</a-select-option> <a-select-option v-for="c in ['reject', 'allow', 'skip']" :value="c">[[ c ]]</a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
</template> </template>
</template> </template>
</a-form>
</a-form> </a-tab-pane>
</a-tab-pane> <a-tab-pane key="2" tab="JSON" force-render="true">
<a-tab-pane key="2" tab="JSON" force-render="true"> <a-form-item style="margin: 10px 0"> Link: <a-input v-model.trim="outModal.link" style="width: 300px; margin-right: 5px;" placeholder="vmess:// vless:// trojan:// ss://"></a-input>
<a-form-item style="margin: 10px 0"> <a-button @click="convertLink" type="primary">
Link: <a-input v-model.trim="outModal.link" style="width: 300px; margin-right: 5px;" placeholder="vmess:// vless:// trojan:// ss://"></a-input> <a-icon type="form"></a-icon>
<a-button @click="convertLink" type="primary"><a-icon type="form"></a-icon></a-button> </a-button>
</a-form-item> </a-form-item>
<textarea style="position:absolute; left: -800px;" id="outboundJson"></textarea> <textarea style="position:absolute; left: -800px;" id="outboundJson"></textarea>
</a-tab-pane> </a-tab-pane>
</a-tabs> </a-tabs>
{{end}} {{end}}

View File

@@ -16,5 +16,8 @@
<a-form-item label='Follow Redirect'> <a-form-item label='Follow Redirect'>
<a-switch v-model="inbound.settings.followRedirect"></a-switch> <a-switch v-model="inbound.settings.followRedirect"></a-switch>
</a-form-item> </a-form-item>
<a-form-item label='Timeout'>
<a-input-number v-model.number="inbound.settings.timeout" :min="0"></a-input-number>
</a-form-item>
</a-form> </a-form>
{{end}} {{end}}

View File

@@ -1,21 +1,23 @@
{{define "form/http"}} {{define "form/http"}}
<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} }">
<table style="width: 100%; text-align: center; margin: 1rem 0;"> <table style="width: 100%; text-align: center; margin: 1rem 0;">
<tr> <tr>
<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><a-button size="small" @click="inbound.settings.addAccount(new Inbound.HttpSettings.HttpAccount())">+</a-button></td> <td>
</tr> <a-button icon="plus" size="small" @click="inbound.settings.addAccount(new Inbound.HttpSettings.HttpAccount())"></a-button>
</table> </td>
<a-input-group compact v-for="(account, index) in inbound.settings.accounts" style="margin-bottom: 10px;"> </tr>
<a-input style="width: 50%" v-model.trim="account.user" placeholder='{{ i18n "username" }}'> </table>
<template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template> <a-input-group compact v-for="(account, index) in inbound.settings.accounts" style="margin-bottom: 10px;">
</a-input> <a-input style="width: 50%" v-model.trim="account.user" placeholder='{{ i18n "username" }}'>
<a-input style="width: 50%" v-model.trim="account.pass" placeholder='{{ i18n "password" }}'> <template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template>
<template slot="addonAfter"> </a-input>
<a-button size="small" @click="inbound.settings.delAccount(index)">-</a-button> <a-input style="width: 50%" v-model.trim="account.pass" placeholder='{{ i18n "password" }}'>
</template> <template slot="addonAfter">
</a-input> <a-button icon="minus" size="small" @click="inbound.settings.delAccount(index)"></a-button>
</a-input-group> </template>
</a-input>
</a-input-group>
</a-form> </a-form>
{{end}} {{end}}

View File

@@ -1,33 +1,34 @@
{{define "form/socks"}} {{define "form/socks"}}
<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.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>
<a-form-item label="IP" v-if="inbound.settings.udp"> <a-form-item label="IP" v-if="inbound.settings.udp">
<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 :checked="inbound.settings.auth === 'password'" <a-switch :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%; text-align: center; margin: 1rem 0;">
<table style="width: 100%; text-align: center; margin: 1rem 0;"> <tr>
<tr> <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 size="small" @click="inbound.settings.addAccount(new Inbound.SocksSettings.SocksAccount())">+</a-button></td> <a-button icon="plus" size="small" @click="inbound.settings.addAccount(new Inbound.SocksSettings.SocksAccount())"></a-button>
</tr> </td>
</table> </tr>
<a-input-group compact v-for="(account, index) in inbound.settings.accounts" style="margin-bottom: 10px;"> </table>
<a-input style="width: 50%" v-model.trim="account.user" placeholder='{{ i18n "username" }}'> <a-input-group compact v-for="(account, index) in inbound.settings.accounts" style="margin-bottom: 10px;">
<template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template> <a-input style="width: 50%" v-model.trim="account.user" placeholder='{{ i18n "username" }}'>
</a-input> <template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template>
<a-input style="width: 50%" v-model.trim="account.pass" placeholder='{{ i18n "password" }}'> </a-input>
<template slot="addonAfter"> <a-input style="width: 50%" v-model.trim="account.pass" placeholder='{{ i18n "password" }}'>
<a-button size="small" @click="inbound.settings.delAccount(index)">-</a-button> <template slot="addonAfter">
</template> <a-button icon="minus" size="small" @click="inbound.settings.delAccount(index)"></a-button>
</a-input> </template>
</a-input-group> </a-input>
</template> </a-input-group>
</template>
</a-form> </a-form>
{{end}} {{end}}

View File

@@ -1,54 +1,50 @@
{{define "form/trojan"}} {{define "form/trojan"}}
<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.trojans.slice(0,1)" v-if="!isEdit"> <a-collapse activeKey="0" v-for="(client, index) in inbound.settings.trojans.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>
</a-collapse> </a-collapse>
<a-collapse v-else> <a-collapse v-else>
<a-collapse-panel :header="'{{ i18n "pages.client.clientCount"}} : ' + inbound.settings.trojans.length"> <a-collapse-panel :header="'{{ i18n "pages.client.clientCount"}} : ' + inbound.settings.trojans.length">
<table width="100%"> <table width="100%">
<tr class="client-table-header"> <tr class="client-table-header">
<th>{{ i18n "pages.inbounds.email" }}</th> <th>{{ i18n "pages.inbounds.email" }}</th>
<th>Password</th> <th>Password</th>
</tr> </tr>
<tr v-for="(client, index) in inbound.settings.trojans" :class="index % 2 == 1 ? 'client-table-odd-row' : ''"> <tr v-for="(client, index) in inbound.settings.trojans" :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>
</table> </table>
</a-collapse-panel> </a-collapse-panel>
</a-collapse> </a-collapse>
<template v-if="inbound.isTcp && !inbound.stream.isReality"> <template v-if="inbound.isTcp && !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="Fallbacks"> <a-form-item label="Fallbacks">
<a-button 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" @click="() => inbound.settings.delFallback(index)" style="color: rgb(255, 77, 79);cursor: pointer;"></a-icon>
<a-divider style="margin:0;"> </a-divider>
Fallback [[ index + 1 ]] <a-form-item label='SNI'>
<a-icon type="delete" @click="() => inbound.settings.delFallback(index)" <a-input v-model="fallback.name"></a-input>
style="color: rgb(255, 77, 79);cursor: pointer;" /> </a-form-item>
</a-divider> <a-form-item label='ALPN'>
<a-form-item label='SNI'> <a-input v-model="fallback.alpn"></a-input>
<a-input v-model="fallback.name"></a-input> </a-form-item>
</a-form-item> <a-form-item label='Path'>
<a-form-item label='ALPN'> <a-input v-model="fallback.path"></a-input>
<a-input v-model="fallback.alpn"></a-input> </a-form-item>
</a-form-item> <a-form-item label='Dest'>
<a-form-item label='Path'> <a-input v-model="fallback.dest"></a-input>
<a-input v-model="fallback.path"></a-input> </a-form-item>
</a-form-item> <a-form-item label='xVer'>
<a-form-item label='Dest'> <a-input-number v-model="fallback.xver" :min="0" :max="2"></a-input-number>
<a-input v-model="fallback.dest"></a-input> </a-form-item>
</a-form-item> </a-form>
<a-form-item label='xVer'> <a-divider style="margin:5px 0;"></a-divider>
<a-input-number v-model="fallback.xver" :min="0" :max="2"></a-input-number>
</a-form-item>
</a-form>
<a-divider style="margin:5px 0;"></a-divider>
</template> </template>
{{end}} {{end}}

View File

@@ -1,56 +1,52 @@
{{define "form/vless"}} {{define "form/vless"}}
<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.vlesses.slice(0,1)" v-if="!isEdit"> <a-collapse activeKey="0" v-for="(client, index) in inbound.settings.vlesses.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>
</a-collapse> </a-collapse>
<a-collapse v-else> <a-collapse v-else>
<a-collapse-panel :header="'{{ i18n "pages.client.clientCount"}} : ' + inbound.settings.vlesses.length"> <a-collapse-panel :header="'{{ i18n "pages.client.clientCount"}} : ' + inbound.settings.vlesses.length">
<table width="100%"> <table width="100%">
<tr class="client-table-header"> <tr class="client-table-header">
<th>{{ i18n "pages.inbounds.email" }}</th> <th>{{ i18n "pages.inbounds.email" }}</th>
<th>Flow</th> <th>Flow</th>
<th>ID</th> <th>ID</th>
</tr> </tr>
<tr v-for="(client, index) in inbound.settings.vlesses" :class="index % 2 == 1 ? 'client-table-odd-row' : ''"> <tr v-for="(client, index) in inbound.settings.vlesses" :class="index % 2 == 1 ? 'client-table-odd-row' : ''">
<td>[[ client.email ]]</td> <td>[[ client.email ]]</td>
<td>[[ client.flow ]]</td> <td>[[ client.flow ]]</td>
<td>[[ client.id ]]</td> <td>[[ client.id ]]</td>
</tr> </tr>
</table> </table>
</a-collapse-panel> </a-collapse-panel>
</a-collapse> </a-collapse>
<template v-if="inbound.isTcp && !inbound.stream.isReality"> <template v-if="inbound.isTcp && !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="Fallbacks"> <a-form-item label="Fallbacks">
<a-button 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" @click="() => inbound.settings.delFallback(index)" style="color: rgb(255, 77, 79);cursor: pointer;"></a-icon>
<a-divider style="margin:0;"> </a-divider>
Fallback [[ index + 1 ]] <a-form-item label='SNI'>
<a-icon type="delete" @click="() => inbound.settings.delFallback(index)" <a-input v-model="fallback.name"></a-input>
style="color: rgb(255, 77, 79);cursor: pointer;" /> </a-form-item>
</a-divider> <a-form-item label='ALPN'>
<a-form-item label='SNI'> <a-input v-model="fallback.alpn"></a-input>
<a-input v-model="fallback.name"></a-input> </a-form-item>
</a-form-item> <a-form-item label='Path'>
<a-form-item label='ALPN'> <a-input v-model="fallback.path"></a-input>
<a-input v-model="fallback.alpn"></a-input> </a-form-item>
</a-form-item> <a-form-item label='Dest'>
<a-form-item label='Path'> <a-input v-model="fallback.dest"></a-input>
<a-input v-model="fallback.path"></a-input> </a-form-item>
</a-form-item> <a-form-item label='xVer'>
<a-form-item label='Dest'> <a-input-number v-model="fallback.xver" :min="0" :max="2"></a-input-number>
<a-input v-model="fallback.dest"></a-input> </a-form-item>
</a-form-item> </a-form>
<a-form-item label='xVer'> <a-divider style="margin:5px 0;"></a-divider>
<a-input-number v-model="fallback.xver" :min="0" :max="2"></a-input-number>
</a-form-item>
</a-form>
<a-divider style="margin:5px 0;"></a-divider>
</template> </template>
{{end}} {{end}}

View File

@@ -1,80 +1,76 @@
{{define "form/wireguard"}} {{define "form/wireguard"}}
<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">
<a-tooltip> <a-tooltip>
<template slot="title"> <template slot="title">
<span>{{ i18n "reset" }}</span> <span>{{ i18n "reset" }}</span>
</template>
{{ i18n "pages.xray.wireguard.secretKey" }}
<a-icon type="sync"
@click="[inbound.settings.pubKey, inbound.settings.secretKey] = Object.values(Wireguard.generateKeypair())">
</a-icon>
</a-tooltip>
</template> </template>
<a-input v-model.trim="inbound.settings.secretKey"></a-input> {{ i18n "pages.xray.wireguard.secretKey" }}
<a-icon type="sync" @click="[inbound.settings.pubKey, inbound.settings.secretKey] = Object.values(Wireguard.generateKeypair())"></a-icon>
</a-tooltip>
</template>
<a-input v-model.trim="inbound.settings.secretKey"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "pages.xray.wireguard.publicKey" }}'>
<a-input disabled v-model="inbound.settings.pubKey"></a-input>
</a-form-item>
<a-form-item label='MTU'>
<a-input-number v-model.number="inbound.settings.mtu"></a-input-number>
</a-form-item>
<a-form-item label='Kernel Mode'>
<a-switch v-model="inbound.settings.kernelMode"></a-switch>
</a-form-item>
<a-form-item label="Peers">
<a-button icon="plus" type="primary" size="small" @click="inbound.settings.addPeer()"></a-button>
</a-form-item>
<a-form v-for="(peer, index) in inbound.settings.peers" :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
<a-divider style="margin:0;"> Peer [[ index + 1 ]] <a-icon v-if="inbound.settings.peers.length>1" type="delete" @click="() => inbound.settings.delPeer(index)" style="color: rgb(255, 77, 79);cursor: pointer;"></a-icon>
</a-divider>
<a-form-item>
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "reset" }}</span>
</template>
{{ i18n "pages.xray.wireguard.secretKey" }}
<a-icon @click="[peer.publicKey, peer.privateKey] = Object.values(Wireguard.generateKeypair())" type="sync"></a-icon>
</a-tooltip>
</template>
<a-input v-model.trim="peer.privateKey"></a-input>
</a-form-item> </a-form-item>
<a-form-item label='{{ i18n "pages.xray.wireguard.publicKey" }}'> <a-form-item>
<a-input disabled v-model="inbound.settings.pubKey"></a-input> <template slot="label">
{{ i18n "pages.xray.wireguard.publicKey" }}
</template>
<a-input v-model.trim="peer.publicKey"></a-input>
</a-form-item> </a-form-item>
<a-form-item label='MTU'> <a-form-item>
<a-input-number v-model.number="inbound.settings.mtu"></a-input-number> <template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "reset" }}</span>
</template>
{{ i18n "pages.xray.wireguard.psk" }}
<a-icon @click="peer.psk = Wireguard.keyToBase64(Wireguard.generatePresharedKey())" type="sync"></a-icon>
</a-tooltip>
</template>
<a-input v-model.trim="peer.psk"></a-input>
</a-form-item> </a-form-item>
<a-form-item label='Kernel Mode'> <a-form-item>
<a-switch v-model="inbound.settings.kernelMode"></a-switch> <template slot="label">
{{ i18n "pages.xray.wireguard.allowedIPs" }}
<a-button icon="plus" type="primary" size="small" @click="peer.allowedIPs.push('')"></a-button>
</template>
<template v-for="(aip, index) in peer.allowedIPs" style="margin-bottom: 10px;">
<a-input v-model.trim="peer.allowedIPs[index]">
<a-button icon="minus" v-if="peer.allowedIPs.length>1" slot="addonAfter" size="small" @click="peer.allowedIPs.splice(index, 1)"></a-button>
</a-input>
</template>
</a-form-item> </a-form-item>
<a-form-item label="Peers"> <a-form-item label='Keep Alive'>
<a-button type="primary" size="small" @click="inbound.settings.addPeer()">+</a-button> <a-input-number v-model.number="peer.keepAlive" :min="0"></a-input-number>
</a-form-item> </a-form-item>
<a-form v-for="(peer, index) in inbound.settings.peers" :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }"> </a-form>
<a-divider style="margin:0;">
Peer [[ index + 1 ]]
<a-icon v-if="inbound.settings.peers.length>1" type="delete" @click="() => inbound.settings.delPeer(index)"
style="color: rgb(255, 77, 79);cursor: pointer;"/>
</a-divider>
<a-form-item>
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "reset" }}</span>
</template>
{{ i18n "pages.xray.wireguard.secretKey" }}
<a-icon @click="[peer.publicKey, peer.privateKey] = Object.values(Wireguard.generateKeypair())"type="sync"> </a-icon>
</a-tooltip>
</template>
<a-input v-model.trim="peer.privateKey"></a-input>
</a-form-item>
<a-form-item>
<template slot="label">
{{ i18n "pages.xray.wireguard.publicKey" }}
</template>
<a-input v-model.trim="peer.publicKey"></a-input>
</a-form-item>
<a-form-item>
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "reset" }}</span>
</template>
{{ i18n "pages.xray.wireguard.psk" }}
<a-icon @click="peer.psk = Wireguard.keyToBase64(Wireguard.generatePresharedKey())"type="sync"> </a-icon>
</a-tooltip>
</template>
<a-input v-model.trim="peer.psk"></a-input>
</a-form-item>
<a-form-item>
<template slot="label">
{{ i18n "pages.xray.wireguard.allowedIPs" }} <a-button type="primary" size="small" @click="peer.allowedIPs.push('')">+</a-button>
</template>
<template v-for="(aip, index) in peer.allowedIPs" style="margin-bottom: 10px;">
<a-input v-model.trim="peer.allowedIPs[index]">
<a-button v-if="peer.allowedIPs.length>1" slot="addonAfter" size="small" @click="peer.allowedIPs.splice(index, 1)">-</a-button>
</a-input>
</template>
</a-form-item>
<a-form-item label='Keep Alive'>
<a-input-number v-model.number="peer.keepAlive" :min="0"></a-input>
</a-form-item>
</a-form>
</a-form> </a-form>
{{end}} {{end}}

View File

@@ -13,10 +13,18 @@
</span> </span>
<a-switch v-model="inbound.sniffing.enabled"></a-switch> <a-switch v-model="inbound.sniffing.enabled"></a-switch>
</a-form-item> </a-form-item>
<a-form-item :wrapper-col="{span:24}"> <template v-if="inbound.sniffing.enabled">
<a-checkbox-group v-model="inbound.sniffing.destOverride" v-if="inbound.sniffing.enabled"> <a-form-item :wrapper-col="{span:24}">
<a-checkbox v-for="key,value in SNIFFING_OPTION" :value="key">[[ value ]]</a-checkbox> <a-checkbox-group v-model="inbound.sniffing.destOverride">
</a-checkbox-group> <a-checkbox v-for="key,value in SNIFFING_OPTION" :value="key">[[ value ]]</a-checkbox>
</a-form-item> </a-checkbox-group>
</a-form-item>
<a-form-item label='Metadata Only'>
<a-switch v-model="inbound.sniffing.metadataOnly"></a-switch>
</a-form-item>
<a-form-item label='Route Only'>
<a-switch v-model="inbound.sniffing.routeOnly"></a-switch>
</a-form-item>
</template>
</a-form> </a-form>
{{end}} {{end}}

View File

@@ -1,26 +1,29 @@
{{define "form/externalProxy"}} {{define "form/externalProxy"}}
<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-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 v-if="externalProxy" type="primary" style="margin-left: 10px" size="small" @click="inbound.stream.externalProxy.push({forceTls: 'same', dest: '', port: 443, remark: ''})">+</a-button> <a-button icon="plus" v-if="externalProxy" type="primary" style="margin-left: 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 style="margin: 8px 0;" compact v-for="(row, index) in inbound.stream.externalProxy"> <a-input-group 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 v-model="row.forceTls" style="width:20%; margin: 0px" :dropdown-class-name="themeSwitcher.currentTheme"> <a-select v-model="row.forceTls" style="width:20%; margin: 0px" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="same">{{ i18n "pages.inbounds.same" }}</a-select-option> <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 style="width: 35%" v-model.trim="row.dest" placeholder='{{ i18n "host" }}'></a-input> <a-input style="width: 35%" 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 style="width: 15%;" v-model.number="row.port" min="1" max="65531"></a-input-number> <a-input-number style="width: 15%;" v-model.number="row.port" min="1" max="65531"></a-input-number>
</a-tooltip> </a-tooltip>
<a-input style="width: 20%" v-model.trim="row.remark" placeholder='{{ i18n "remark" }}'></a-input> <a-input style="width: 30%; top: 0;" v-model.trim="row.remark" placeholder='{{ i18n "remark" }}'>
<a-button style="width: 10%; margin: 0px; border-radius: 0 1rem 1rem 0;" @click="inbound.stream.externalProxy.splice(index, 1)">-</a-button> <template slot="addonAfter">
</a-input-group> <a-button icon="minus" size="small" @click="inbound.stream.externalProxy.splice(index, 1)"></a-button>
</template>
</a-input>
</a-input-group>
</a-form> </a-form>
{{end}} {{end}}

View File

@@ -1,19 +1,17 @@
{{define "form/streamHTTP"}} {{define "form/streamHTTP"}}
<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 "path" }}'> <a-form-item label='{{ i18n "path" }}'>
<a-input v-model.trim="inbound.stream.http.path"></a-input> <a-input v-model.trim="inbound.stream.http.path"></a-input>
</a-form-item> </a-form-item>
<a-form-item> <a-form-item>
<template slot="label">{{ i18n "host" }} <template slot="label">{{ i18n "host" }}
<a-button size="small" @click="inbound.stream.http.addHost()">+</a-button> <a-button icon="plus" size="small" @click="inbound.stream.http.addHost()"></a-button>
</template> </template>
<template v-for="(host, index) in inbound.stream.http.host"> <template v-for="(host, index) in inbound.stream.http.host">
<a-input v-model.trim="inbound.stream.http.host[index]"> <a-input v-model.trim="inbound.stream.http.host[index]">
<a-button size="small" slot="addonAfter" <a-button icon="minus" size="small" slot="addonAfter" @click="inbound.stream.http.removeHost(index)" v-if="inbound.stream.http.host.length>1"></a-button>
@click="inbound.stream.http.removeHost(index)" </a-input>
v-if="inbound.stream.http.host.length>1">-</a-button> </template>
</a-input> </a-form-item>
</template>
</a-form-item>
</a-form> </a-form>
{{end}} {{end}}

View File

@@ -1,26 +1,26 @@
{{define "form/streamHTTPUpgrade"}} {{define "form/streamHTTPUpgrade"}}
<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="PROXY Protocol"> <a-form-item label="Proxy Protocol">
<a-switch v-model="inbound.stream.httpupgrade.acceptProxyProtocol"></a-switch> <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>
</a-form-item> </a-form-item>
<a-form-item label='{{ i18n "path" }}'> <a-form-item label='{{ i18n "path" }}'>
<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 size="small" @click="inbound.stream.httpupgrade.addHeader('host', '')">+</a-button> <a-button icon="plus" size="small" @click="inbound.stream.httpupgrade.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 compact v-for="(header, index) in inbound.stream.httpupgrade.headers"> <a-input-group compact v-for="(header, index) in inbound.stream.httpupgrade.headers">
<a-input style="width: 50%" v-model.trim="header.name" placeholder='{{ i18n "pages.inbounds.stream.general.name"}}'> <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> <template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template>
</a-input> </a-input>
<a-input style="width: 50%" v-model.trim="header.value" placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'> <a-input style="width: 50%" v-model.trim="header.value" placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'>
<a-button slot="addonAfter" size="small" @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>
</a-form> </a-form>
{{end}} {{end}}

View File

@@ -1,26 +1,66 @@
{{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 :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="TPROXY"> <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="PROXY Protocol"> <a-form-item label="Route Mark">
<a-input-number v-model="inbound.stream.sockopt.mark" :min="0"></a-input-number>
</a-form-item>
<a-form-item label="TCP Keep Alive Interval">
<a-input-number v-model="inbound.stream.sockopt.tcpKeepAliveInterval" :min="0"></a-input-number>
</a-form-item>
<a-form-item label="TCP Keep Alive Idle">
<a-input-number v-model="inbound.stream.sockopt.tcpKeepAliveIdle" :min="0"></a-input-number>
</a-form-item>
<a-form-item label="TCP Max Seg">
<a-input-number v-model="inbound.stream.sockopt.tcpMaxSeg" :min="0"></a-input-number>
</a-form-item>
<a-form-item label="TCP User Timeout">
<a-input-number v-model="inbound.stream.sockopt.tcpUserTimeout" :min="0"></a-input-number>
</a-form-item>
<a-form-item label="TCP Window Clamp">
<a-input-number v-model="inbound.stream.sockopt.tcpWindowClamp" :min="0"></a-input-number>
</a-form-item>
<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>
</a-form-item> </a-form-item>
<a-form-item label="TCP Fast Open"> <a-form-item label="TCP Fast Open">
<a-switch v-model.trim="inbound.stream.sockopt.tcpFastOpen"></a-switch> <a-switch v-model.trim="inbound.stream.sockopt.tcpFastOpen"></a-switch>
</a-form-item> </a-form-item>
<a-form-item label="Route Mark"> <a-form-item label="Multipath TCP">
<a-input-number v-model="inbound.stream.sockopt.mark" :min="0"></a-input-number> <a-switch v-model.trim="inbound.stream.sockopt.tcpMptcp"></a-switch>
</a-form-item> </a-form-item>
<a-form-item label="TPROXY"> <a-form-item label="TCP No-Delay">
<a-select v-model="inbound.stream.sockopt.tproxy" :dropdown-class-name="themeSwitcher.currentTheme"> <a-switch v-model.trim="inbound.stream.sockopt.tcpNoDelay"></a-switch>
</a-form-item>
<a-form-item label="V6 Only">
<a-switch v-model.trim="inbound.stream.sockopt.V6Only"></a-switch>
</a-form-item>
<a-form-item label='Domain Strategy'>
<a-select v-model="inbound.stream.sockopt.domainStrategy" style="width: 50%" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="key in DOMAIN_STRATEGY_OPTION" :value="key">[[ key ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item label='TCP Congestion'>
<a-select v-model="inbound.stream.sockopt.tcpcongestion" style="width: 50%" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="key in TCP_CONGESTION_OPTION" :value="key">[[ key ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="TProxy">
<a-select v-model="inbound.stream.sockopt.tproxy" 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>
</a-select> </a-select>
</a-form-item> </a-form-item>
<a-form-item label="Dialer Proxy">
<a-input v-model="inbound.stream.sockopt.dialerProxy"></a-input>
</a-form-item>
<a-form-item label="Interface Name">
<a-input v-model="inbound.stream.sockopt.interfaceName"></a-input>
</a-form-item>
</template> </template>
</a-form> </a-form>
{{end}} {{end}}

View File

@@ -1,82 +1,72 @@
{{define "form/streamTCP"}} {{define "form/streamTCP"}}
<!-- tcp type --> <!-- tcp type -->
<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="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 :checked="inbound.stream.tcp.type === 'http'" <a-switch :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-form-item>
</a-switch>
</a-form-item>
</a-form> </a-form>
<a-form v-if="inbound.stream.tcp.type === 'http'" :colon="false" :label-col="{ md: {span:8} }" <a-form v-if="inbound.stream.tcp.type === 'http'" :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
:wrapper-col="{ md: {span:14} }"> <!-- tcp request -->
<!-- tcp request --> <a-divider style="margin:0;">{{ i18n "pages.inbounds.stream.general.request" }}</a-divider>
<a-divider style="margin:0;">{{ 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> <a-form-item label='{{ i18n "pages.inbounds.stream.tcp.method" }}'>
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.method" }}'> <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">{{ i18n "pages.inbounds.stream.tcp.path" }}
<template slot="label">{{ i18n "pages.inbounds.stream.tcp.path" }} <a-button icon="plus" size="small" @click="inbound.stream.tcp.request.addPath('/')"></a-button>
<a-button 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 icon="minus" size="small" slot="addonAfter" @click="inbound.stream.tcp.request.removePath(index)" v-if="inbound.stream.tcp.request.path.length>1"></a-button>
<a-button size="small" slot="addonAfter" @click="inbound.stream.tcp.request.removePath(index)" </a-input>
v-if="inbound.stream.tcp.request.path.length>1">-</a-button> </template>
</a-input> </a-form-item>
</template> <a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestHeader" }}'>
</a-form-item> <a-button icon="plus" size="small" @click="inbound.stream.tcp.request.addHeader('Host', '')"></a-button>
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestHeader" }}'> </a-form-item>
<a-button size="small" @click="inbound.stream.tcp.request.addHeader('Host', '')">+</a-button> <a-form-item :wrapper-col="{span:24}">
</a-form-item> <a-input-group compact v-for="(header, index) in inbound.stream.tcp.request.headers">
<a-form-item :wrapper-col="{span:24}"> <a-input style="width: 50%" v-model.trim="header.name" placeholder='{{ i18n "pages.inbounds.stream.general.name" }}'>
<a-input-group compact v-for="(header, index) in inbound.stream.tcp.request.headers"> <template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template>
<a-input style="width: 50%" v-model.trim="header.name" </a-input>
placeholder='{{ i18n "pages.inbounds.stream.general.name" }}'> <a-input style="width: 50%" v-model.trim="header.value" placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'>
<template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template> <a-button icon="minus" slot="addonAfter" size="small" @click="inbound.stream.tcp.request.removeHeader(index)"></a-button>
</a-input> </a-input>
<a-input style="width: 50%" v-model.trim="header.value" </a-input-group>
placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'> </a-form-item>
<a-button slot="addonAfter" size="small"
@click="inbound.stream.tcp.request.removeHeader(index)">-</a-button>
</a-input>
</a-input-group>
</a-form-item>
<!-- tcp response --> <!-- tcp response -->
<a-divider style="margin:0;">{{ i18n "pages.inbounds.stream.general.response" }}</a-divider> <a-divider style="margin:0;">{{ 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>
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.status" }}'> <a-form-item label='{{ i18n "pages.inbounds.stream.tcp.status" }}'>
<a-input v-model.trim="inbound.stream.tcp.response.status"></a-input> <a-input v-model.trim="inbound.stream.tcp.response.status"></a-input>
</a-form-item> </a-form-item>
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.statusDescription" }}'> <a-form-item label='{{ i18n "pages.inbounds.stream.tcp.statusDescription" }}'>
<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 size="small" <a-button icon="plus" size="small" @click="inbound.stream.tcp.response.addHeader('Content-Type', 'application/octet-stream')"></a-button>
@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 compact v-for="(header, index) in inbound.stream.tcp.response.headers">
<a-input-group compact v-for="(header, index) in inbound.stream.tcp.response.headers"> <a-input style="width: 50%" v-model.trim="header.name" placeholder='{{ i18n "pages.inbounds.stream.general.name" }}'>
<a-input style="width: 50%" v-model.trim="header.name" <template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template>
placeholder='{{ i18n "pages.inbounds.stream.general.name" }}'> </a-input>
<template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template> <a-input style="width: 50%" v-model.trim="header.value" placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'>
</a-input> <template slot="addonAfter">
<a-input style="width: 50%" v-model.trim="header.value" <a-button icon="minus" size="small" @click="inbound.stream.tcp.response.removeHeader(index)"></a-button>
placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'> </template>
<template slot="addonAfter"> </a-input>
<a-button size="small" @click="inbound.stream.tcp.response.removeHeader(index)">-</a-button> </a-input-group>
</template> </a-form-item>
</a-input>
</a-input-group>
</a-form-item>
</a-form> </a-form>
{{end}} {{end}}

View File

@@ -1,26 +1,26 @@
{{define "form/streamWS"}} {{define "form/streamWS"}}
<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="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>
<a-form-item label='{{ i18n "host" }}'> <a-form-item label='{{ i18n "host" }}'>
<a-input v-model.trim="inbound.stream.ws.host"></a-input> <a-input v-model.trim="inbound.stream.ws.host"></a-input>
</a-form-item> </a-form-item>
<a-form-item label='{{ i18n "path" }}'> <a-form-item label='{{ i18n "path" }}'>
<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='{{ i18n "pages.inbounds.stream.tcp.requestHeader" }}'> <a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestHeader" }}'>
<a-button size="small" @click="inbound.stream.ws.addHeader('host', '')">+</a-button> <a-button icon="plus" size="small" @click="inbound.stream.ws.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 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 style="width: 50%" v-model.trim="header.name" placeholder='{{ i18n "pages.inbounds.stream.general.name"}}'> <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> <template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template>
</a-input> </a-input>
<a-input style="width: 50%" v-model.trim="header.value" placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'> <a-input style="width: 50%" v-model.trim="header.value" placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'>
<a-button slot="addonAfter" size="small" @click="inbound.stream.ws.removeHeader(index)">-</a-button> <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>
</a-form> </a-form>
{{end}} {{end}}

View File

@@ -1,194 +1,208 @@
{{define "form/tlsSettings"}} {{define "form/tlsSettings"}}
<!-- tls enable --> <!-- tls enable -->
<a-form v-if="inbound.canEnableTls()" :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }"> <a-form v-if="inbound.canEnableTls()" :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
<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-tooltip> <a-tooltip>
<template slot="title"> <template slot="title">
<span>{{ i18n "pages.inbounds.xtlsDesc" }}</span> <span>{{ i18n "pages.inbounds.xtlsDesc" }}</span>
</template> </template>
<a-radio-button v-if="inbound.canEnableXtls()" value="xtls">XTLS</a-radio-button> <a-radio-button v-if="inbound.canEnableXtls()" value="xtls">XTLS</a-radio-button>
</a-tooltip> </a-tooltip>
<a-tooltip> <a-tooltip>
<template slot="title"> <template slot="title">
<span>{{ i18n "pages.inbounds.realityDesc" }}</span> <span>{{ i18n "pages.inbounds.realityDesc" }}</span>
</template> </template>
<a-radio-button v-if="inbound.canEnableReality()" value="reality">REALITY</a-radio-button> <a-radio-button v-if="inbound.canEnableReality()" value="reality">REALITY</a-radio-button>
</a-tooltip> </a-tooltip>
<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>
<!-- tls settings -->
<template v-if="inbound.stream.isTls">
<a-form-item label="SNI" placeholder="Server Name Indication">
<a-input v-model.trim="inbound.stream.tls.sni"></a-input>
</a-form-item> </a-form-item>
<a-form-item label="Cipher Suites">
<!-- tls settings --> <a-select v-model="inbound.stream.tls.cipherSuites" :dropdown-class-name="themeSwitcher.currentTheme">
<template v-if="inbound.stream.isTls"> <a-select-option value="">Auto</a-select-option>
<a-form-item label="SNI" placeholder="Server Name Indication"> <a-select-option v-for="key,value in TLS_CIPHER_OPTION" :value="key">[[ value ]]</a-select-option>
<a-input v-model.trim="inbound.stream.tls.sni"></a-input> </a-select>
</a-form-item> </a-form-item>
<a-form-item label="Cipher Suites"> <a-form-item label="Min/Max Version">
<a-select v-model="inbound.stream.tls.cipherSuites" :dropdown-class-name="themeSwitcher.currentTheme"> <a-input-group compact>
<a-select-option value="">Auto</a-select-option> <a-select v-model="inbound.stream.tls.minVersion" style="width: 50%" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="key,value in TLS_CIPHER_OPTION" :value="key">[[ value ]]</a-select-option> <a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option>
</a-select> </a-select>
</a-form-item> <a-select v-model="inbound.stream.tls.maxVersion" style="width: 50%" :dropdown-class-name="themeSwitcher.currentTheme">
<a-form-item label="Min/Max Version"> <a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option>
<a-input-group compact> </a-select>
<a-select v-model="inbound.stream.tls.minVersion" style="width: 50%" :dropdown-class-name="themeSwitcher.currentTheme"> </a-input-group>
<a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option> </a-form-item>
</a-select> <a-form-item label="uTLS">
<a-select v-model="inbound.stream.tls.maxVersion" style="width: 50%" :dropdown-class-name="themeSwitcher.currentTheme"> <a-select v-model="inbound.stream.tls.settings.fingerprint" style="width: 50%" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option> <a-select-option value=''>None</a-select-option>
</a-select> <a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
</a-input-group> </a-select>
</a-form-item> </a-form-item>
<a-form-item label="uTLS"> <a-form-item label="ALPN">
<a-select v-model="inbound.stream.tls.settings.fingerprint" style="width: 50%" <a-select mode="multiple" :dropdown-class-name="themeSwitcher.currentTheme" v-model="inbound.stream.tls.alpn">
:dropdown-class-name="themeSwitcher.currentTheme"> <a-select-option v-for="alpn in ALPN_OPTION" :value="alpn">[[ alpn ]]</a-select-option>
<a-select-option value=''>None</a-select-option> </a-select>
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option> </a-form-item>
</a-select> <a-form-item label="Allow Insecure">
</a-form-item> <a-switch v-model="inbound.stream.tls.settings.allowInsecure"></a-switch>
<a-form-item label="ALPN"> </a-form-item>
<a-select mode="multiple" :dropdown-class-name="themeSwitcher.currentTheme" <a-form-item label="Reject Unknown SNI">
v-model="inbound.stream.tls.alpn"> <a-switch v-model="inbound.stream.tls.rejectUnknownSni"></a-switch>
<a-select-option v-for="alpn in ALPN_OPTION" :value="alpn">[[ alpn ]]</a-select-option> </a-form-item>
</a-select> <a-form-item label="Disable System Root">
</a-form-item> <a-switch v-model="inbound.stream.tls.settings.disableSystemRoot"></a-switch>
<a-form-item label="Allow Insecure"> </a-form-item>
<a-switch v-model="inbound.stream.tls.settings.allowInsecure"></a-switch> <a-form-item label="Session Resumption">
</a-form-item> <a-switch v-model="inbound.stream.tls.settings.enableSessionResumption"></a-switch>
<a-form-item label="Reject Unknown SNI"> </a-form-item>
<a-switch v-model="inbound.stream.tls.rejectUnknownSni"></a-switch> <template v-for="cert,index in inbound.stream.tls.certs">
</a-form-item> <a-form-item label='{{ i18n "certificate" }}'>
<template v-for="cert,index in inbound.stream.tls.certs"> <a-radio-group v-model="cert.useFile" button-style="solid">
<a-form-item label='{{ i18n "certificate" }}'> <a-radio-button :value="true">{{ i18n "pages.inbounds.certificatePath" }}</a-radio-button>
<a-radio-group v-model="cert.useFile" button-style="solid"> <a-radio-button :value="false">{{ i18n "pages.inbounds.certificateContent" }}</a-radio-button>
<a-radio-button :value="true">{{ i18n "pages.inbounds.certificatePath" }}</a-radio-button> </a-radio-group>
<a-radio-button :value="false">{{ i18n "pages.inbounds.certificateContent" }}</a-radio-button> <a-button icon="plus" v-if="index === 0" type="primary" size="small" @click="inbound.stream.tls.addCert()" style="margin-left: 10px"></a-button>
</a-radio-group> <a-button icon="minus" v-if="inbound.stream.tls.certs.length>1" type="primary" size="small" @click="inbound.stream.tls.removeCert(index)" style="margin-left: 10px"></a-button>
<a-button v-if="index === 0" type="primary" size="small" @click="inbound.stream.tls.addCert()" </a-form-item>
style="margin-left: 10px">+</a-button> <template v-if="cert.useFile">
<a-button v-if="inbound.stream.tls.certs.length>1" type="primary" size="small" <a-form-item label='{{ i18n "pages.inbounds.publicKey" }}'>
@click="inbound.stream.tls.removeCert(index)" style="margin-left: 10px">-</a-button> <a-input v-model.trim="cert.certFile"></a-input>
</a-form-item>
<template v-if="cert.useFile">
<a-form-item label='{{ i18n "pages.inbounds.publicKey" }}'>
<a-input v-model.trim="cert.certFile"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "pages.inbounds.privatekey" }}'>
<a-input v-model.trim="cert.keyFile"></a-input>
</a-form-item>
<a-form-item label=" ">
<a-button type="primary" icon="import" @click="setDefaultCertData(index)">{{ i18n
"pages.inbounds.setDefaultCert" }}</a-button>
</a-form-item>
</template>
<template v-else>
<a-form-item label='{{ i18n "pages.inbounds.publicKey" }}'>
<a-input type="textarea" :rows="3" v-model="cert.cert"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "pages.inbounds.privatekey" }}'>
<a-input type="textarea" :rows="3" v-model="cert.key"></a-input>
</a-form-item>
</template>
<a-form-item label='OCSP stapling'>
<a-input-number v-model.number="cert.ocspStapling" :min="0"></a-input-number>
</a-form-item>
</template>
</template>
<!-- xtls settings -->
<template v-else-if="inbound.xtls">
<a-form-item label="SNI" placeholder="Server Name Indication">
<a-input v-model.trim="inbound.stream.xtls.sni"></a-input>
</a-form-item>
<a-form-item label="ALPN">
<a-select mode="multiple" :dropdown-class-name="themeSwitcher.currentTheme"
v-model="inbound.stream.xtls.alpn">
<a-select-option v-for="alpn in ALPN_OPTION" :value="alpn">[[ alpn ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="Allow Insecure">
<a-switch v-model="inbound.stream.xtls.settings.allowInsecure"></a-switch>
</a-form-item>
<template v-for="cert,index in inbound.stream.xtls.certs">
<a-form-item label='{{ i18n "certificate" }}'>
<a-radio-group v-model="cert.useFile" button-style="solid">
<a-radio-button :value="true">{{ i18n "pages.inbounds.certificatePath" }}</a-radio-button>
<a-radio-button :value="false">{{ i18n "pages.inbounds.certificateContent" }}</a-radio-button>
</a-radio-group>
<a-button v-if="index === 0" type="primary" size="small" @click="inbound.stream.xtls.addCert()"
style="margin-left: 10px">+</a-button>
<a-button v-if="inbound.stream.xtls.certs.length>1" type="primary" size="small"
@click="inbound.stream.xtls.removeCert(index)" style="margin-left: 10px">-</a-button>
</a-form-item>
<template v-if="cert.useFile">
<a-form-item label='{{ i18n "pages.inbounds.publicKey" }}'>
<a-input v-model.trim="cert.certFile"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "pages.inbounds.privatekey" }}'>
<a-input v-model.trim="cert.keyFile"></a-input>
</a-form-item>
<a-form-item label=" ">
<a-button type="primary" icon="import" @click="setDefaultCertXtls(index)">{{ i18n
"pages.inbounds.setDefaultCert" }}</a-button>
</a-form-item>
</template>
<template v-else>
<a-form-item label='{{ i18n "pages.inbounds.publicKey" }}'>
<a-input type="textarea" :rows="3" v-model="cert.cert"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "pages.inbounds.privatekey" }}'>
<a-input type="textarea" :rows="3" v-model="cert.key"></a-input>
</a-form-item>
</template>
</template>
</template>
<!-- reality settings -->
<template v-if="inbound.stream.isReality">
<a-form-item label='Show'>
<a-switch v-model="inbound.stream.reality.show"></a-switch>
</a-form-item>
<a-form-item label='Xver'>
<a-input-number v-model.number="inbound.stream.reality.xver" :min="0"></a-input-number>
</a-form-item>
<a-form-item label='uTLS'>
<a-select v-model="inbound.stream.reality.settings.fingerprint" style="width: 50%"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item label='Dest'>
<a-input v-model.trim="inbound.stream.reality.dest"></a-input>
</a-form-item>
<a-form-item label='SNI'>
<a-input v-model.trim="inbound.stream.reality.serverNames"></a-input>
</a-form-item>
<a-form-item>
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "reset" }}</span>
</template>
Short ID
<a-icon @click="inbound.stream.reality.shortIds = RandomUtil.randomShortId()" type="sync"> </a-icon>
</a-icon>
</template>
<a-input v-model.trim="inbound.stream.reality.shortIds"></a-input>
</a-form-item>
<a-form-item label='SpiderX'>
<a-input v-model.trim="inbound.stream.reality.settings.spiderX"></a-input>
</a-form-item> </a-form-item>
<a-form-item label='{{ i18n "pages.inbounds.privatekey" }}'> <a-form-item label='{{ i18n "pages.inbounds.privatekey" }}'>
<a-input v-model.trim="inbound.stream.reality.privateKey"></a-input> <a-input v-model.trim="cert.keyFile"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "pages.inbounds.publicKey" }}'>
<a-input v-model.trim="inbound.stream.reality.settings.publicKey"></a-input>
</a-form-item> </a-form-item>
<a-form-item label=" "> <a-form-item label=" ">
<a-button type="primary" icon="import" @click="getNewX25519Cert">Get New Cert</a-button> <a-button type="primary" icon="import" @click="setDefaultCertData(index)">{{ i18n "pages.inbounds.setDefaultCert" }}</a-button>
</a-form-item> </a-form-item>
</template>
<template v-else>
<a-form-item label='{{ i18n "pages.inbounds.publicKey" }}'>
<a-input type="textarea" :rows="3" v-model="cert.cert"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "pages.inbounds.privatekey" }}'>
<a-input type="textarea" :rows="3" v-model="cert.key"></a-input>
</a-form-item>
</template>
<a-form-item label='OCSP stapling'>
<a-input-number v-model.number="cert.ocspStapling" :min="0"></a-input-number>
</a-form-item>
<a-form-item label="One Time Loading">
<a-switch v-model="cert.oneTimeLoading"></a-switch>
</a-form-item>
<a-form-item label='Usage Option'>
<a-select v-model="cert.usage" style="width: 50%" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="key in USAGE_OPTION" :value="key">[[ key ]]</a-select-option>
</a-select>
</a-form-item>
</template> </template>
</template>
<!-- xtls settings -->
<template v-else-if="inbound.xtls">
<a-form-item label="SNI" placeholder="Server Name Indication">
<a-input v-model.trim="inbound.stream.xtls.sni"></a-input>
</a-form-item>
<a-form-item label="ALPN">
<a-select mode="multiple" :dropdown-class-name="themeSwitcher.currentTheme" v-model="inbound.stream.xtls.alpn">
<a-select-option v-for="alpn in ALPN_OPTION" :value="alpn">[[ alpn ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="Allow Insecure">
<a-switch v-model="inbound.stream.xtls.settings.allowInsecure"></a-switch>
</a-form-item>
<template v-for="cert,index in inbound.stream.xtls.certs">
<a-form-item label='{{ i18n "certificate" }}'>
<a-radio-group v-model="cert.useFile" button-style="solid">
<a-radio-button :value="true">{{ i18n "pages.inbounds.certificatePath" }}</a-radio-button>
<a-radio-button :value="false">{{ i18n "pages.inbounds.certificateContent" }}</a-radio-button>
</a-radio-group>
<a-button icon="plus" v-if="index === 0" type="primary" size="small" @click="inbound.stream.xtls.addCert()" style="margin-left: 10px"></a-button>
<a-button icon="minus" v-if="inbound.stream.xtls.certs.length>1" type="primary" size="small" @click="inbound.stream.xtls.removeCert(index)" style="margin-left: 10px"></a-button>
</a-form-item>
<template v-if="cert.useFile">
<a-form-item label='{{ i18n "pages.inbounds.publicKey" }}'>
<a-input v-model.trim="cert.certFile"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "pages.inbounds.privatekey" }}'>
<a-input v-model.trim="cert.keyFile"></a-input>
</a-form-item>
<a-form-item label=" ">
<a-button type="primary" icon="import" @click="setDefaultCertXtls(index)">{{ i18n "pages.inbounds.setDefaultCert" }}</a-button>
</a-form-item>
</template>
<template v-else>
<a-form-item label='{{ i18n "pages.inbounds.publicKey" }}'>
<a-input type="textarea" :rows="3" v-model="cert.cert"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "pages.inbounds.privatekey" }}'>
<a-input type="textarea" :rows="3" v-model="cert.key"></a-input>
</a-form-item>
</template>
<a-form-item label='OCSP stapling'>
<a-input-number v-model.number="cert.ocspStapling" :min="0"></a-input-number>
</a-form-item>
<a-form-item label="One Time Loading">
<a-switch v-model="cert.oneTimeLoading"></a-switch>
</a-form-item>
<a-form-item label='Usage Option'>
<a-select v-model="cert.usage" style="width: 50%" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="key in USAGE_OPTION" :value="key">[[ key ]]</a-select-option>
</a-select>
</a-form-item>
</template>
</template>
<!-- reality settings -->
<template v-if="inbound.stream.isReality">
<a-form-item label='Show'>
<a-switch v-model="inbound.stream.reality.show"></a-switch>
</a-form-item>
<a-form-item label='Xver'>
<a-input-number v-model.number="inbound.stream.reality.xver" :min="0"></a-input-number>
</a-form-item>
<a-form-item label='uTLS'>
<a-select v-model="inbound.stream.reality.settings.fingerprint" style="width: 50%" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item label='Dest'>
<a-input v-model.trim="inbound.stream.reality.dest"></a-input>
</a-form-item>
<a-form-item label='SNI'>
<a-input v-model.trim="inbound.stream.reality.serverNames"></a-input>
</a-form-item>
<a-form-item>
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "reset" }}</span>
</template> Short ID <a-icon @click="inbound.stream.reality.shortIds = RandomUtil.randomShortId()" type="sync"></a-icon>
</a-tooltip>
</template>
<a-input v-model.trim="inbound.stream.reality.shortIds"></a-input>
</a-form-item>
<a-form-item label='SpiderX'>
<a-input v-model.trim="inbound.stream.reality.settings.spiderX"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "pages.inbounds.privatekey" }}'>
<a-input v-model.trim="inbound.stream.reality.privateKey"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "pages.inbounds.publicKey" }}'>
<a-input v-model.trim="inbound.stream.reality.settings.publicKey"></a-input>
</a-form-item>
<a-form-item label=" ">
<a-button type="primary" icon="import" @click="getNewX25519Cert">Get New Cert</a-button>
</a-form-item>
</template>
</a-form> </a-form>
{{end}} {{end}}

View File

@@ -1,266 +1,239 @@
{{define "client_table"}} {{define "client_table"}}
<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="font-size: 24px;" class="normal-icon" type="qrcode" v-if="record.hasLink()" @click="showQrcode(record.id,client);"></a-icon> <a-icon style="font-size: 24px;" 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="font-size: 24px;" class="normal-icon" type="edit" @click="openEditClient(record.id,client);"></a-icon> <a-icon style="font-size: 24px;" 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="font-size: 24px;" class="normal-icon" type="info-circle" @click="showInfo(record.id,client);"></a-icon> <a-icon style="font-size: 24px;" 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)" <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"}}'>
title='{{ i18n "pages.inbounds.resetTrafficContent"}}' <a-icon slot="icon" type="question-circle-o" :style="themeSwitcher.isDarkTheme ? 'color: var(--color-primary-100)' : 'color: var(--color-primary-100)'"></a-icon>
:overlay-class-name="themeSwitcher.currentTheme" <a-icon style="font-size: 24px; cursor: pointer;" class="normal-icon" type="retweet" v-if="client.email.length > 0"></a-icon>
ok-text='{{ i18n "reset"}}' </a-popconfirm>
cancel-text='{{ i18n "cancel"}}'> </a-tooltip>
<a-icon slot="icon" type="question-circle-o" :style="themeSwitcher.isDarkTheme ? 'color: var(--color-primary-100)' : 'color: var(--color-primary-100)'"></a-icon> <a-tooltip>
<a-icon style="font-size: 24px; cursor: pointer;" class="normal-icon" type="retweet" v-if="client.email.length > 0"></a-icon> <template slot="title">
</a-popconfirm> <span style="color: #FF4D4F"> {{ i18n "delete"}}</span>
</a-tooltip> </template>
<a-tooltip> <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"}}'>
<template slot="title"><span style="color: #FF4D4F"> {{ i18n "delete"}}</span></template> <a-icon slot="icon" type="question-circle-o" style="color: #e04141"></a-icon>
<a-popconfirm @confirm="delClient(record.id,client,false)" <a-icon style="font-size: 24px; cursor: pointer;" class="delete-icon" type="delete" v-if="isRemovable(record.id)"></a-icon>
title='{{ i18n "pages.inbounds.deleteClientContent"}}' </a-popconfirm>
:overlay-class-name="themeSwitcher.currentTheme" </a-tooltip>
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 style="font-size: 24px; cursor: pointer;" class="delete-icon" type="delete" v-if="isRemovable(record.id)"></a-icon>
</a-popconfirm>
</a-tooltip>
</template> </template>
<template slot="enable" slot-scope="text, client, index"> <template slot="enable" slot-scope="text, client, index">
<a-switch v-model="client.enable" @change="switchEnableClient(record.id,client)"></a-switch> <a-switch v-model="client.enable" @change="switchEnableClient(record.id,client)"></a-switch>
</template> </template>
<template slot="online" slot-scope="text, client, index"> <template slot="online" slot-scope="text, client, index">
<template v-if="client.enable && isClientOnline(client.email)"> <template v-if="client.enable && isClientOnline(client.email)">
<a-tag color="green">{{ i18n "online" }}</a-tag> <a-tag color="green">{{ i18n "online" }}</a-tag>
</template> </template>
<template v-else> <template v-else>
<a-tag>{{ i18n "offline" }}</a-tag> <a-tag>{{ i18n "offline" }}</a-tag>
</template> </template>
</template> </template>
<template slot="client" slot-scope="text, client"> <template slot="client" slot-scope="text, client">
<a-tooltip> <a-tooltip>
<template slot="title"> <template slot="title">
<template v-if="!isClientEnabled(record, client.email)">{{ i18n "depleted" }}</template> <template v-if="!isClientEnabled(record, client.email)">{{ i18n "depleted" }}</template>
<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 :class="isClientOnline(client.email)? 'online-animation' : ''" :color="client.enable ? statsExpColor(record, client.email) : themeSwitcher.isDarkTheme ? '#2c3950' : '#bcbcbc'"></a-badge>
</a-badge> </a-tooltip> [[ client.email ]]
</a-tooltip>
[[ client.email ]]
</template> </template>
<template slot="traffic" slot-scope="text, client"> <template slot="traffic" slot-scope="text, client">
<a-popover :overlay-class-name="themeSwitcher.currentTheme"> <a-popover :overlay-class-name="themeSwitcher.currentTheme">
<template slot="content" v-if="client.email"> <template slot="content" v-if="client.email">
<table cellpadding="2" width="100%"> <table cellpadding="2" width="100%">
<tr> <tr>
<td>↑[[ sizeFormat(getUpStats(record, client.email)) ]]</td> <td>↑[[ sizeFormat(getUpStats(record, client.email)) ]]</td>
<td>↓[[ sizeFormat(getDownStats(record, client.email)) ]]</td> <td>↓[[ sizeFormat(getDownStats(record, client.email)) ]]</td>
</tr> </tr>
<tr v-if="client.totalGB > 0"> <tr v-if="client.totalGB > 0">
<td>{{ i18n "remained" }}</td> <td>{{ i18n "remained" }}</td>
<td>[[ sizeFormat(getRemStats(record, client.email)) ]]</td> <td>[[ sizeFormat(getRemStats(record, client.email)) ]]</td>
</tr> </tr>
</table> </table>
</template> </template>
<table> <table>
<tr> <tr class="tr-table-box">
<td width="80px" style="margin:0; text-align: right;font-size: 1em;"> <td class="tr-table-rt"> [[ sizeFormat(getSumStats(record, client.email)) ]] </td>
[[ sizeFormat(getSumStats(record, client.email)) ]] <td class="tr-table-bar" v-if="!client.enable">
</td> <a-progress :stroke-color="themeSwitcher.isDarkTheme ? 'rgb(72 84 105)' : '#bcbcbc'" :show-info="false" :percent="statsProgress(record, client.email)" />
<td width="120px" v-if="!client.enable"> </td>
<a-progress :stroke-color="themeSwitcher.isDarkTheme ? 'rgb(72 84 105)' : '#bcbcbc'" <td class="tr-table-bar" v-else-if="client.totalGB > 0">
:show-info="false" <a-progress :stroke-color="clientStatsColor(record, client.email)" :show-info="false" :status="isClientEnabled(record, client.email)? 'exception' : ''" :percent="statsProgress(record, client.email)" />
:percent="statsProgress(record, client.email)"/> </td>
</td> <td v-else class="infinite-bar tr-table-bar">
<td width="120px" v-else-if="client.totalGB > 0"> <a-progress :show-info="false" :percent="100"></a-progress>
<a-progress :stroke-color="clientStatsColor(record, client.email)" </td>
:show-info="false" <td class="tr-table-lt">
:status="isClientEnabled(record, client.email)? 'exception' : ''" <template v-if="client.totalGB > 0">[[ client._totalGB + "GB" ]]</template>
:percent="statsProgress(record, client.email)"/> <span v-else class="tr-infinity-ch">&infin;</span>
</td> </td>
<td width="120px" v-else class="infinite-bar"> </tr>
<a-progress </table>
:show-info="false" </a-popover>
:percent="100"></a-progress>
</td>
<td width="60px">
<template v-if="client.totalGB > 0">[[ client._totalGB + "GB" ]]</template>
<span v-else style="font-weight: 100;font-size: 14pt;">&infin;</span>
</td>
</tr>
</table>
</a-popover>
</template> </template>
<template slot="expiryTime" slot-scope="text, client, index"> <template slot="expiryTime" slot-scope="text, client, index">
<template v-if="client.expiryTime !=0 && client.reset >0"> <template v-if="client.expiryTime !=0 && client.reset >0">
<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 v-else>[[ DateUtil.formatMillis(client._expiryTime) ]]</span> </span>
</template> <span v-else>[[ DateUtil.formatMillis(client._expiryTime) ]]</span>
<table> </template>
<tr> <table>
<td width="80px" style="margin:0; text-align: right;font-size: 1em;"> <tr class="tr-table-box">
[[ remainedDays(client.expiryTime) ]] <td class="tr-table-rt"> [[ remainedDays(client.expiryTime) ]] </td>
</td> <td class="infinite-bar tr-table-bar">
<td width="120px" class="infinite-bar"> <a-progress :show-info="false" :status="isClientEnabled(record, client.email)? 'exception' : ''" :percent="expireProgress(client.expiryTime, client.reset)" />
<a-progress :show-info="false" </td>
:status="isClientEnabled(record, client.email)? 'exception' : ''" <td class="tr-table-lt">[[ client.reset + "d" ]]</td>
:percent="expireProgress(client.expiryTime, client.reset)"/> </tr>
</td> </table>
<td width="60px">[[ client.reset + "d" ]]</td> </a-popover>
</tr> </template>
</table> <template v-else>
</a-popover> <a-popover v-if="client.expiryTime != 0" :overlay-class-name="themeSwitcher.currentTheme">
</template> <template slot="content">
<template v-else> <span v-if="client.expiryTime < 0">{{ i18n "pages.client.delayedStart" }}
<a-popover v-if="client.expiryTime != 0" :overlay-class-name="themeSwitcher.currentTheme"> </span>
<template slot="content"> <span v-else>[[ DateUtil.formatMillis(client._expiryTime) ]]</span>
<span v-if="client.expiryTime < 0">{{ i18n "pages.client.delayedStart" }}</span> </template>
<span v-else>[[ DateUtil.formatMillis(client._expiryTime) ]]</span> <a-tag style="min-width: 50px; border: none;" :color="userExpiryColor(app.expireDiff, client, themeSwitcher.isDarkTheme)"> [[ remainedDays(client.expiryTime) ]] </a-tag>
</template> </a-popover>
<a-tag style="min-width: 50px; border: none;" :color="userExpiryColor(app.expireDiff, client, themeSwitcher.isDarkTheme)"> <a-tag v-else :color="userExpiryColor(app.expireDiff, client, themeSwitcher.isDarkTheme)" style="border: none;" class="infinite-tag">
[[ remainedDays(client.expiryTime) ]] <svg height="10px" width="14px" viewBox="0 0 640 512" fill="currentColor">
</a-tag> <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>
</a-popover> </svg>
<a-tag v-else :color="userExpiryColor(app.expireDiff, client, themeSwitcher.isDarkTheme)" style="border: none;" class="infinite-tag">&infin;</a-tag> </a-tag>
</template> </template>
</template> </template>
<template slot="actionMenu" slot-scope="text, client, index"> <template slot="actionMenu" slot-scope="text, client, index">
<a-dropdown :trigger="['click']"> <a-dropdown :trigger="['click']">
<a-icon @click="e => e.preventDefault()" type="ellipsis" style="font-size: 20px;"></a-icon> <a-icon @click="e => e.preventDefault()" type="ellipsis" style="font-size: 20px;"></a-icon>
<a-menu slot="overlay" :theme="themeSwitcher.currentTheme"> <a-menu slot="overlay" :theme="themeSwitcher.currentTheme">
<a-menu-item v-if="record.hasLink()" @click="showQrcode(record.id,client);"> <a-menu-item v-if="record.hasLink()" @click="showQrcode(record.id,client);">
<a-icon style="font-size: 14px;" type="qrcode"></a-icon> <a-icon style="font-size: 14px;" type="qrcode"></a-icon>
{{ i18n "qrCode" }} {{ i18n "qrCode" }}
</a-menu-item> </a-menu-item>
<a-menu-item @click="openEditClient(record.id,client);"> <a-menu-item @click="openEditClient(record.id,client);">
<a-icon style="font-size: 14px;" type="edit"></a-icon> <a-icon style="font-size: 14px;" type="edit"></a-icon>
{{ i18n "pages.client.edit" }} {{ i18n "pages.client.edit" }}
</a-menu-item> </a-menu-item>
<a-menu-item @click="showInfo(record.id,client);"> <a-menu-item @click="showInfo(record.id,client);">
<a-icon style="font-size: 14px;" type="info-circle"></a-icon> <a-icon style="font-size: 14px;" type="info-circle"></a-icon>
{{ i18n "info" }} {{ i18n "info" }}
</a-menu-item> </a-menu-item>
<a-menu-item @click="resetClientTraffic(client,record.id)" v-if="client.email.length > 0"> <a-menu-item @click="resetClientTraffic(client,record.id)" v-if="client.email.length > 0">
<a-icon style="font-size: 14px;" type="retweet"></a-icon> <a-icon style="font-size: 14px;" type="retweet"></a-icon>
{{ i18n "pages.inbounds.resetTraffic" }} {{ i18n "pages.inbounds.resetTraffic" }}
</a-menu-item> </a-menu-item>
<a-menu-item v-if="isRemovable(record.id)" @click="delClient(record.id,client)"> <a-menu-item v-if="isRemovable(record.id)" @click="delClient(record.id,client)">
<a-icon style="font-size: 14px;" type="delete"></a-icon> <a-icon style="font-size: 14px;" type="delete"></a-icon>
<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)"> <a-switch v-model="client.enable" size="small" @change="switchEnableClient(record.id,client)"></a-switch>
</a-switch> {{ i18n "enable"}}
{{ i18n "enable"}} </a-menu-item>
</a-menu-item> </a-menu>
</a-menu> </a-dropdown>
</a-dropdown>
</template> </template>
<template slot="info" slot-scope="text, client, index"> <template slot="info" slot-scope="text, client, index">
<a-popover placement="bottomRight" :overlay-class-name="themeSwitcher.currentTheme" trigger="click"> <a-popover placement="bottomRight" :overlay-class-name="themeSwitcher.currentTheme" trigger="click">
<template slot="content"> <template slot="content">
<table> <table>
<tr> <tr>
<td colspan="3" style="text-align: center;">{{ i18n "pages.inbounds.traffic" }}</td> <td colspan="3" style="text-align: center;">{{ i18n "pages.inbounds.traffic" }}</td>
</tr> </tr>
<tr> <tr>
<td width="80px" style="margin:0; text-align: right;font-size: 1em;"> <td width="80px" style="margin:0; text-align: right;font-size: 1em;"> [[ sizeFormat(getUpStats(record, client.email) + getDownStats(record, client.email)) ]] </td>
[[ sizeFormat(getUpStats(record, client.email) + getDownStats(record, client.email)) ]] <td width="120px" v-if="!client.enable">
</td> <a-progress :stroke-color="themeSwitcher.isDarkTheme ? 'rgb(72 84 105)' : '#bcbcbc'" :show-info="false" :percent="statsProgress(record, client.email)" />
<td width="120px" v-if="!client.enable"> </td>
<a-progress :stroke-color="themeSwitcher.isDarkTheme ? 'rgb(72 84 105)' : '#bcbcbc'" <td width="120px" v-else-if="client.totalGB > 0">
:show-info="false" <a-popover :overlay-class-name="themeSwitcher.currentTheme">
:percent="statsProgress(record, client.email)"/> <template slot="content" v-if="client.email">
</td> <table cellpadding="2" width="100%">
<td width="120px" v-else-if="client.totalGB > 0"> <tr>
<a-popover :overlay-class-name="themeSwitcher.currentTheme"> <td>↑[[ sizeFormat(getUpStats(record, client.email)) ]]</td>
<template slot="content" v-if="client.email"> <td>↓[[ sizeFormat(getDownStats(record, client.email)) ]]</td>
<table cellpadding="2" width="100%"> </tr>
<tr> <tr>
<td>↑[[ sizeFormat(getUpStats(record, client.email)) ]]</td> <td>{{ i18n "remained" }}</td>
<td>[[ sizeFormat(getDownStats(record, client.email)) ]]</td> <td>[[ sizeFormat(getRemStats(record, client.email)) ]]</td>
</tr> </tr>
<tr> </table>
<td>{{ i18n "remained" }}</td> </template>
<td>[[ sizeFormat(getRemStats(record, client.email)) ]]</td> <a-progress :stroke-color="clientStatsColor(record, client.email)" :show-info="false" :status="isClientEnabled(record, client.email)? 'exception' : ''" :percent="statsProgress(record, client.email)" />
</tr> </a-popover>
</table> </td>
</template> <td width="120px" v-else class="infinite-bar">
<a-progress :stroke-color="clientStatsColor(record, client.email)" <a-progress :stroke-color="themeSwitcher.isDarkTheme ? '#2c1e32':'#F2EAF1'" :show-info="false" :percent="100"></a-progress>
:show-info="false" </td>
:status="isClientEnabled(record, client.email)? 'exception' : ''" <td width="80px">
:percent="statsProgress(record, client.email)"/> <template v-if="client.totalGB > 0">[[ client._totalGB + "GB" ]]</template>
</a-popover> <span v-else class="tr-infinity-ch">&infin;</span>
</td> </td>
<td width="120px" v-else class="infinite-bar"> </tr>
<a-progress :stroke-color="themeSwitcher.isDarkTheme ? '#2c1e32':'#F2EAF1'" <tr>
:show-info="false" <td colspan="3" style="text-align: center;">
:percent="100"></a-progress> <a-divider style="margin: 0; border-collapse: separate;"></a-divider>
</td> {{ i18n "pages.inbounds.expireDate" }}
<td width="80px"> </td>
<template v-if="client.totalGB > 0">[[ client._totalGB + "GB" ]]</template> </tr>
<span v-else style="font-weight: 100;font-size: 14pt;">&infin;</span> <tr>
</td> <template v-if="client.expiryTime !=0 && client.reset >0">
</tr> <td width="80px" style="margin:0; text-align: right;font-size: 1em;"> [[ remainedDays(client.expiryTime) ]] </td>
<tr> <td width="120px" class="infinite-bar">
<td colspan="3" style="text-align: center;"> <a-popover :overlay-class-name="themeSwitcher.currentTheme">
<a-divider style="margin: 0; border-collapse: separate;"></a-divider> <template slot="content">
{{ i18n "pages.inbounds.expireDate" }} <span v-if="client.expiryTime < 0">{{ i18n "pages.client.delayedStart" }}
</td> </span>
</tr> <span v-else>[[ DateUtil.formatMillis(client._expiryTime) ]]</span>
<tr>
<template v-if="client.expiryTime !=0 && client.reset >0">
<td width="80px" style="margin:0; text-align: right;font-size: 1em;">
[[ remainedDays(client.expiryTime) ]]
</td>
<td width="120px" class="infinite-bar">
<a-popover :overlay-class-name="themeSwitcher.currentTheme">
<template slot="content">
<span v-if="client.expiryTime < 0">{{ i18n "pages.client.delayedStart" }}</span>
<span v-else>[[ DateUtil.formatMillis(client._expiryTime) ]]</span>
</template>
<a-progress :show-info="false"
:status="isClientEnabled(record, client.email)? 'exception' : ''"
:percent="expireProgress(client.expiryTime, client.reset)"/>
</a-popover>
</td>
<td width="60px">[[ client.reset + "d" ]]</td>
</template> </template>
<template v-else> <a-progress :show-info="false" :status="isClientEnabled(record, client.email)? 'exception' : ''" :percent="expireProgress(client.expiryTime, client.reset)" />
<td colspan="3" style="text-align: center;"> </a-popover>
<a-popover v-if="client.expiryTime != 0" :overlay-class-name="themeSwitcher.currentTheme"> </td>
<template slot="content"> <td width="60px">[[ client.reset + "d" ]]</td>
<span v-if="client.expiryTime < 0">{{ i18n "pages.client.delayedStart" }}</span> </template>
<span v-else>[[ DateUtil.formatMillis(client._expiryTime) ]]</span> <template v-else>
</template> <td colspan="3" style="text-align: center;">
<a-tag style="min-width: 50px; border: none;" <a-popover v-if="client.expiryTime != 0" :overlay-class-name="themeSwitcher.currentTheme">
:color="userExpiryColor(app.expireDiff, client, themeSwitcher.isDarkTheme)"> <template slot="content">
[[ remainedDays(client.expiryTime) ]] <span v-if="client.expiryTime < 0">{{ i18n "pages.client.delayedStart" }}
</a-tag> </span>
</a-popover> <span v-else>[[ DateUtil.formatMillis(client._expiryTime) ]]</span>
<a-tag v-else :color="client.enable ? 'purple' : themeSwitcher.isDarkTheme ? '#2c3950' : '#bcbcbc'" class="infinite-tag">&infin;</a-tag> </template>
</template> <a-tag style="min-width: 50px; border: none;" :color="userExpiryColor(app.expireDiff, client, themeSwitcher.isDarkTheme)"> [[ remainedDays(client.expiryTime) ]] </a-tag>
</td> </a-popover>
</tr> <a-tag v-else :color="client.enable ? 'purple' : themeSwitcher.isDarkTheme ? '#2c3950' : '#bcbcbc'" class="infinite-tag">
</table> <svg height="10px" width="14px" viewBox="0 0 640 512" fill="currentColor">
</template> <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>
<a-badge> </svg>
<a-icon v-if="!client.enable" slot="count" type="pause-circle" theme="filled" :style="'color: ' + themeSwitcher.isDarkTheme ? '#2c3950' : '#bcbcbc'"></a-icon> </a-tag>
<a-button shape="round" size="small" style="font-size: 14px; padding: 0 10px;"><a-icon type="solution"></a-icon></a-button> </template>
</a-badge> </td>
</a-popover> </tr>
</table>
</template>
<a-badge>
<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="font-size: 14px; padding: 0 10px;">
<a-icon type="solution"></a-icon>
</a-button>
</a-badge>
</a-popover>
</template> </template>
{{end}} {{end}}

View File

@@ -1,436 +1,516 @@
{{define "inboundInfoModal"}} {{define "inboundInfoModal"}}
<a-modal id="inbound-info-modal" <a-modal id="inbound-info-modal" v-model="infoModal.visible" title='{{ i18n "pages.inbounds.details"}}' :closable="true" :mask-closable="true" :footer="null" width="600px" :class="themeSwitcher.currentTheme">
v-model="infoModal.visible" title='{{ i18n "pages.inbounds.details"}}' <a-row>
:closable="true" <a-col :xs="24" :md="12">
:mask-closable="true" <table>
:footer="null"
width="600px"
:class="themeSwitcher.currentTheme"
>
<a-row>
<a-col :xs="24" :md="12">
<table>
<tr><td>{{ i18n "protocol" }}</td><td><a-tag color="purple">[[ dbInbound.protocol ]]</a-tag></td></tr>
<tr><td>{{ i18n "pages.inbounds.address" }}</td><td>
<a-tooltip :title="[[ dbInbound.address ]]">
<a-tag class="info-large-tag">[[ dbInbound.address ]]</a-tag>
</a-tooltip>
</td></tr>
<tr><td>{{ i18n "pages.inbounds.port" }}</td><td><a-tag>[[ dbInbound.port ]]</a-tag></td></tr>
</table>
</a-col>
<a-col :xs="24" :md="12">
<template v-if="dbInbound.isVMess || dbInbound.isVLess || dbInbound.isTrojan || dbInbound.isSS">
<table>
<tr>
<td>{{ i18n "transmission" }}</td><td><a-tag color="green">[[ inbound.network ]]</a-tag></td>
</tr>
<template v-if="inbound.isTcp || inbound.isWs || inbound.isH2 || inbound.isHttpupgrade ">
<tr>
<td>{{ i18n "host" }}</td>
<td v-if="inbound.host">
<a-tooltip :title="[[ inbound.host ]]">
<a-tag class="info-large-tag">[[ inbound.host ]]</a-tag>
</a-tooltip>
</td>
<td v-else><a-tag color="orange">{{ i18n "none" }}</a-tag></td></tr>
</tr>
<tr>
<td>{{ i18n "path" }}</td>
<td v-if="inbound.path">
<a-tooltip :title="[[ inbound.path ]]">
<a-tag class="info-large-tag">[[ inbound.path ]]</a-tag>
</a-tooltip>
<td v-else><a-tag color="orange">{{ i18n "none" }}</a-tag></td>
</tr>
</template>
<template v-if="inbound.isQuic">
<tr><td>quic {{ i18n "encryption" }}</td><td><a-tag>[[ inbound.quicSecurity ]]</a-tag></td></tr>
<tr><td>quic {{ i18n "password" }}</td><td><a-tag>[[ inbound.quicKey ]]</a-tag></td></tr>
<tr><td>quic {{ i18n "camouflage" }}</td><td><a-tag>[[ inbound.quicType ]]</a-tag></td></tr>
</template>
<template v-if="inbound.isKcp">
<tr><td>kcp {{ i18n "encryption" }}</td><td><a-tag>[[ inbound.kcpType ]]</a-tag></td></tr>
<tr><td>kcp {{ i18n "password" }}</td><td><a-tag>[[ inbound.kcpSeed ]]</a-tag></td></tr>
</template>
<template v-if="inbound.isGrpc">
<tr><td>grpc serviceName</td><td>
<a-tooltip :title="[[ inbound.serviceName ]]">
<a-tag class="info-large-tag">[[ inbound.serviceName ]]</a-tag>
</a-tooltip>
<tr><td>grpc multiMode</td><td><a-tag>[[ inbound.stream.grpc.multiMode ]]</a-tag></td></tr>
</template>
</table>
</template>
</a-col>
<template v-if="dbInbound.hasLink()">
{{ i18n "security" }}
<a-tag :color="inbound.stream.security == 'none' ? 'red' : 'green'">[[ inbound.stream.security ]]</a-tag>
<br />
<template v-if="inbound.stream.security != 'none'">
{{ i18n "domainName" }}
<a-tag :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : '' ]]</a-tag>
</template>
</template>
<table v-if="dbInbound.isSS" style="margin-bottom: 10px; width: 100%;">
<tr> <tr>
<td>{{ i18n "encryption" }}</td> <td>{{ i18n "protocol" }}</td>
<td><a-tag color="green">[[ inbound.settings.method ]]</a-tag></td> <td>
</tr><tr v-if="inbound.isSS2022"> <a-tag color="purple">[[ dbInbound.protocol ]]</a-tag>
<td>{{ i18n "password" }}</td> </td>
<td>
<a-tooltip :title="[[ inbound.settings.password ]]">
<a-tag class="info-large-tag">[[ inbound.settings.password ]]</a-tag>
</a-tooltip>
</td>
</tr><tr>
<td>{{ i18n "pages.inbounds.network" }}</td>
<td><a-tag color="green">[[ inbound.settings.network ]]</a-tag></td>
</tr> </tr>
<tr>
<td>{{ i18n "pages.inbounds.address" }}</td>
<td>
<a-tooltip :title="[[ dbInbound.address ]]">
<a-tag class="info-large-tag">[[ dbInbound.address ]]</a-tag>
</a-tooltip>
</td>
</tr>
<tr>
<td>{{ i18n "pages.inbounds.port" }}</td>
<td>
<a-tag>[[ dbInbound.port ]]</a-tag>
</td>
</tr>
</table>
</a-col>
<a-col :xs="24" :md="12">
<template v-if="dbInbound.isVMess || dbInbound.isVLess || dbInbound.isTrojan || dbInbound.isSS">
<table>
<tr>
<td>{{ i18n "transmission" }}</td>
<td>
<a-tag color="green">[[ inbound.network ]]</a-tag>
</td>
</tr>
<template v-if="inbound.isTcp || inbound.isWs || inbound.isH2 || inbound.isHttpupgrade ">
<tr>
<td>{{ i18n "host" }}</td>
<td v-if="inbound.host">
<a-tooltip :title="[[ inbound.host ]]">
<a-tag class="info-large-tag">[[ inbound.host ]]</a-tag>
</a-tooltip>
</td>
<td v-else>
<a-tag color="orange">{{ i18n "none" }}</a-tag>
</td>
</tr>
</tr>
<tr>
<td>{{ i18n "path" }}</td>
<td v-if="inbound.path">
<a-tooltip :title="[[ inbound.path ]]">
<a-tag class="info-large-tag">[[ inbound.path ]]</a-tag>
</a-tooltip>
<td v-else>
<a-tag color="orange">{{ i18n "none" }}</a-tag>
</td>
</tr>
</template>
<template v-if="inbound.isQuic">
<tr>
<td>quic {{ i18n "encryption" }}</td>
<td>
<a-tag>[[ inbound.quicSecurity ]]</a-tag>
</td>
</tr>
<tr>
<td>quic {{ i18n "password" }}</td>
<td>
<a-tag>[[ inbound.quicKey ]]</a-tag>
</td>
</tr>
<tr>
<td>quic {{ i18n "camouflage" }}</td>
<td>
<a-tag>[[ inbound.quicType ]]</a-tag>
</td>
</tr>
</template>
<template v-if="inbound.isKcp">
<tr>
<td>kcp {{ i18n "encryption" }}</td>
<td>
<a-tag>[[ inbound.kcpType ]]</a-tag>
</td>
</tr>
<tr>
<td>kcp {{ i18n "password" }}</td>
<td>
<a-tag>[[ inbound.kcpSeed ]]</a-tag>
</td>
</tr>
</template>
<template v-if="inbound.isGrpc">
<tr>
<td>grpc serviceName</td>
<td>
<a-tooltip :title="[[ inbound.serviceName ]]">
<a-tag class="info-large-tag">[[ inbound.serviceName ]]</a-tag>
</a-tooltip>
<tr>
<td>grpc multiMode</td>
<td>
<a-tag>[[ inbound.stream.grpc.multiMode ]]</a-tag>
</td>
</tr>
</template>
</table>
</template>
</a-col>
<template v-if="dbInbound.hasLink()">
{{ i18n "security" }}
<a-tag :color="inbound.stream.security == 'none' ? 'red' : 'green'">[[ inbound.stream.security ]]</a-tag>
<br />
<template v-if="inbound.stream.security != 'none'">
{{ i18n "domainName" }}
<a-tag v-if="inbound.serverName" :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : '' ]]</a-tag>
<a-tag v-else color="orange">{{ i18n "none" }}</a-tag>
</template>
</template>
<table v-if="dbInbound.isSS" style="margin-bottom: 10px; width: 100%;">
<tr>
<td>{{ i18n "encryption" }}</td>
<td>
<a-tag color="green">[[ inbound.settings.method ]]</a-tag>
</td>
</tr>
<tr v-if="inbound.isSS2022">
<td>{{ i18n "password" }}</td>
<td>
<a-tooltip :title="[[ inbound.settings.password ]]">
<a-tag class="info-large-tag">[[ inbound.settings.password ]]</a-tag>
</a-tooltip>
</td>
</tr>
<tr>
<td>{{ i18n "pages.inbounds.network" }}</td>
<td>
<a-tag color="green">[[ inbound.settings.network ]]</a-tag>
</td>
</tr>
</table> </table>
<template v-if="infoModal.clientSettings"> <template v-if="infoModal.clientSettings">
<a-divider>{{ i18n "pages.inbounds.client" }}</a-divider> <a-divider>{{ i18n "pages.inbounds.client" }}</a-divider>
<table style="margin-bottom: 10px;"> <table style="margin-bottom: 10px;">
<tr> <tr>
<td>{{ i18n "pages.inbounds.email" }}</td> <td>{{ i18n "pages.inbounds.email" }}</td>
<td><a-tag color="green">[[ infoModal.clientSettings.email ]]</a-tag></td> <td v-if="infoModal.clientSettings.email">
</tr> <a-tag color="green">[[ infoModal.clientSettings.email ]]</a-tag>
<tr v-if="infoModal.clientSettings.id"> </td>
<td>ID</td> <td v-else>
<td><a-tag>[[ infoModal.clientSettings.id ]]</a-tag></td> <a-tag color="red">{{ i18n "none" }}</a-tag>
</tr> </td>
<tr v-if="infoModal.inbound.canEnableTlsFlow()"> </tr>
<td>Flow</td> <tr v-if="infoModal.clientSettings.id">
<td><a-tag>[[ infoModal.clientSettings.flow ]]</a-tag></td> <td>ID</td>
</tr> <td>
<tr v-if="infoModal.inbound.xtls"> <a-tag>[[ infoModal.clientSettings.id ]]</a-tag>
<td>Flow</td> </td>
<td><a-tag>[[ infoModal.clientSettings.flow ]]</a-tag></td> </tr>
</tr> <tr v-if="infoModal.inbound.canEnableTlsFlow()">
<tr v-if="infoModal.clientSettings.password"> <td>Flow</td>
<td>{{ i18n "password" }}</td> <td v-if="infoModal.clientSettings.flow">
<td> <a-tag>[[ infoModal.clientSettings.flow ]]</a-tag>
<a-tooltip :title="[[ infoModal.clientSettings.password ]]"> </td>
<a-tag class="info-large-tag">[[ infoModal.clientSettings.password ]]</a-tag> <td v-else>
</a-tooltip> <a-tag color="orange">{{ i18n "none" }}</a-tag>
</td> </td>
</tr> </tr>
<tr> <tr v-if="infoModal.inbound.xtls">
<td>{{ i18n "status" }}</td> <td>Flow</td>
<td> <td v-if="infoModal.clientSettings.flow">
<a-tag v-if="isEnable" color="green">{{ i18n "enabled" }}</a-tag> <a-tag>[[ infoModal.clientSettings.flow ]]</a-tag>
<a-tag v-else>{{ i18n "disabled" }}</a-tag> </td>
<a-tag v-if="!isActive" color="red">{{ i18n "depleted" }}</a-tag> <td v-else>
</td> <a-tag color="orange">{{ i18n "none" }}</a-tag>
</tr> </td>
<tr v-if="infoModal.clientStats"> </tr>
<td>{{ i18n "usage" }}</td> <tr v-if="infoModal.clientSettings.password">
<td> <td>{{ i18n "password" }}</td>
<a-tag color="green">[[ sizeFormat(infoModal.clientStats.up + infoModal.clientStats.down) ]]</a-tag> <td>
<a-tag>↑ [[ sizeFormat(infoModal.clientStats.up) ]] / [[ sizeFormat(infoModal.clientStats.down) ]] ↓</a-tag> <a-tooltip :title="[[ infoModal.clientSettings.password ]]">
</td> <a-tag class="info-large-tag">[[ infoModal.clientSettings.password ]]</a-tag>
</tr> </a-tooltip>
</table> </td>
<table style="margin-bottom: 10px; width: 100%; text-align: center;"> </tr>
<tr> <tr>
<th>{{ i18n "remained" }}</th> <td>{{ i18n "status" }}</td>
<th>{{ i18n "pages.inbounds.totalFlow" }}</th> <td>
<th>{{ i18n "pages.inbounds.expireDate" }}</th> <a-tag v-if="isEnable" color="green">{{ i18n "enabled" }}</a-tag>
</tr> <a-tag v-else>{{ i18n "disabled" }}</a-tag>
<tr> <a-tag v-if="!isActive" color="red">{{ i18n "depleted" }}</a-tag>
<td> </td>
<a-tag v-if="infoModal.clientStats && infoModal.clientSettings.totalGB > 0" :color="statsColor(infoModal.clientStats)"> </tr>
[[ getRemStats() ]] <tr v-if="infoModal.clientStats">
</a-tag> <td>{{ i18n "usage" }}</td>
</td> <td>
<td> <a-tag color="green">[[ sizeFormat(infoModal.clientStats.up + infoModal.clientStats.down) ]]</a-tag>
<a-tag v-if="infoModal.clientSettings.totalGB > 0" :color="statsColor(infoModal.clientStats)"> <a-tag>↑ [[ sizeFormat(infoModal.clientStats.up) ]] / [[ sizeFormat(infoModal.clientStats.down) ]] ↓</a-tag>
[[ sizeFormat(infoModal.clientSettings.totalGB) ]] </td>
</a-tag> </tr>
<a-tag v-else color="purple" class="infinite-tag">&infin;</a-tag> </table>
</td> <table style="display: inline-table; margin-block: 10px; width: 100%; text-align: center;">
<td> <tr>
<template v-if="infoModal.clientSettings.expiryTime > 0"> <th>{{ i18n "remained" }}</th>
<a-tag :color="usageColor(new Date().getTime(), app.expireDiff, infoModal.clientSettings.expiryTime)"> <th>{{ i18n "pages.inbounds.totalFlow" }}</th>
[[ DateUtil.formatMillis(infoModal.clientSettings.expiryTime) ]] <th>{{ i18n "pages.inbounds.expireDate" }}</th>
</a-tag> </tr>
</template> <tr>
<a-tag v-else-if="infoModal.clientSettings.expiryTime < 0" color="green">[[ infoModal.clientSettings.expiryTime / -86400000 ]] {{ i18n "pages.client.days" }}</a-tag> <td>
<a-tag v-else color="purple" class="infinite-tag">&infin;</a-tag> <a-tag v-if="infoModal.clientStats && infoModal.clientSettings.totalGB > 0" :color="statsColor(infoModal.clientStats)"> [[ getRemStats() ]] </a-tag>
</td> </td>
</tr> <td>
</table> <a-tag v-if="infoModal.clientSettings.totalGB > 0" :color="statsColor(infoModal.clientStats)"> [[ sizeFormat(infoModal.clientSettings.totalGB) ]] </a-tag>
<template v-if="app.subSettings.enable && infoModal.clientSettings.subId"> <a-tag v-else color="purple" class="infinite-tag">
<a-divider>Subscription URL</a-divider> <svg height="10px" width="14px" viewBox="0 0 640 512" fill="currentColor">
<a-row> <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>
<a-col :sx="24" :md="22">SUB: <a :href="[[ infoModal.subLink ]]" target="_blank">[[ infoModal.subLink ]]</a></a-col> </svg>
<a-col :sx="24" :md="2" style="text-align: right;"> </a-tag>
<a-tooltip title='{{ i18n "copy" }}'> </td>
<button class="ant-btn ant-btn-primary" id="copy-sub-link" @click="copyToClipboard('copy-sub-link', infoModal.subLink)"> <td>
<a-icon type="snippets"></a-icon> <template v-if="infoModal.clientSettings.expiryTime > 0">
</button> <a-tag :color="usageColor(new Date().getTime(), app.expireDiff, infoModal.clientSettings.expiryTime)"> [[ DateUtil.formatMillis(infoModal.clientSettings.expiryTime) ]] </a-tag>
</a-tooltip> </template>
</a-col> <a-tag v-else-if="infoModal.clientSettings.expiryTime < 0" color="green">[[ infoModal.clientSettings.expiryTime / -86400000 ]] {{ i18n "pages.client.days" }}
</a-row> </a-tag>
<a-row> <a-tag v-else color="purple" class="infinite-tag">
<a-col :sx="24" :md="22">JSON: <a :href="[[ infoModal.subJsonLink ]]" target="_blank">[[ infoModal.subJsonLink ]]</a></a-col> <svg height="10px" width="14px" viewBox="0 0 640 512" fill="currentColor">
<a-col :sx="24" :md="2" style="text-align: right; margin-top: 5px;"> <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>
<a-tooltip title='{{ i18n "copy" }}'> </svg>
<button class="ant-btn ant-btn-primary" id="copy-subJson-link" @click="copyToClipboard('copy-subJson-link', infoModal.subJsonLink)"> </a-tag>
<a-icon type="snippets"></a-icon> </td>
</button> </tr>
</a-tooltip> </table>
</a-col> <template v-if="app.subSettings.enable && infoModal.clientSettings.subId">
</a-row> <a-divider>Subscription URL</a-divider>
</template> <tr-info-row class="tr-info-row">
<template v-if="app.tgBotEnable && infoModal.clientSettings.tgId"> <tr-info-title class="tr-info-title">
<a-divider>Telegram ID</a-divider> <a-tag color="purple">Subscription Link</a-tag>
<a-row> <a-tooltip title='{{ i18n "copy" }}'>
<a-col :sx="24" :md="22">[[ infoModal.clientSettings.tgId ]]</a-col> <a-button size="small" icon="snippets" id="copy-sub-link" @click="copyToClipboard('copy-sub-link', infoModal.subLink)"></a-button>
<a-col :sx="24" :md="2" style="text-align: right;"> </a-tooltip>
<a-tooltip title='{{ i18n "copy" }}'> </tr-info-title>
<button class="ant-btn ant-btn-primary" id="copy-tg-link" @click="copyToClipboard('copy-tg-link', infoModal.clientSettings.tgId)"> <a :href="[[ infoModal.subLink ]]" target="_blank">[[ infoModal.subLink ]]</a>
<a-icon type="snippets"></a-icon> </tr-info-row>
</button> <tr-info-row class="tr-info-row">
</a-tooltip> <tr-info-title class="tr-info-title">
</a-col> <a-tag color="purple">Json Link</a-tag>
</a-row> <a-tooltip title='{{ i18n "copy" }}'>
</template> <a-button size="small" icon="snippets" id="copy-subJson-link" @click="copyToClipboard('copy-subJson-link', infoModal.subJsonLink)"></a-button>
<template v-if="dbInbound.hasLink()"> </a-tooltip>
<a-divider>URL</a-divider> </tr-info-title>
<a-row v-for="(link,index) in infoModal.links"> <a :href="[[ infoModal.subJsonLink ]]" target="_blank">[[ infoModal.subJsonLink ]]</a>
<a-col :sx="24" :md="22"><a-tag color="green">[[ link.remark ]]</a-tag><br />[[ link.link ]]</a-col> </tr-info-row>
<a-col :sx="24" :md="2" style="text-align: right;"> </template>
<a-tooltip title='{{ i18n "copy" }}'> <template v-if="app.tgBotEnable && infoModal.clientSettings.tgId">
<button class="ant-btn ant-btn-primary" :id="'copy-url-link-'+index" @click="copyToClipboard('copy-url-link-'+index, link.link)"> <a-divider>Telegram ChatID</a-divider>
<a-icon type="snippets"></a-icon> <tr-info-row class="tr-info-row">
</button> <tr-info-title class="tr-info-title">
</a-tooltip> <a-tag color="blue">[[ infoModal.clientSettings.tgId ]]</a-tag>
</a-col> <a-tooltip title='{{ i18n "copy" }}'>
</a-row> <a-button size="small" icon="snippets" id="copy-tg-link" @click="copyToClipboard('copy-tg-link', infoModal.clientSettings.tgId)"></a-button>
</template> </a-tooltip>
</tr-info-title>
</tr-info-row>
</template>
<template v-if="dbInbound.hasLink()">
<a-divider>URL</a-divider>
<tr-info-row v-for="(link,index) in infoModal.links" class="tr-info-row">
<tr-info-title class="tr-info-title">
<a-tag class="tr-info-tag" color="green">[[ link.remark ]]</a-tag>
<a-tooltip title='{{ i18n "copy" }}'>
<a-button style="min-width: 24px;" size="small" icon="snippets" :id="'copy-url-link-'+index" @click="copyToClipboard('copy-url-link-'+index, link.link)"></a-button>
</a-tooltip>
</tr-info-title>
<code>[[ link.link ]]</code>
</tr-info-row>
</template>
</template> </template>
<template v-else> <template v-else>
<template v-if="dbInbound.isSS && !inbound.isSSMultiUser"> <template v-if="dbInbound.isSS && !inbound.isSSMultiUser">
<a-divider>URL</a-divider> <a-divider>URL</a-divider>
<a-row v-for="(link,index) in infoModal.links"> <tr-info-row v-for="(link,index) in infoModal.links" class="tr-info-row">
<a-col :span="22"><a-tag color="green">[[ link.remark ]]</a-tag><br />[[ link.link ]]</a-col> <tr-info-title class="tr-info-title">
<a-col :span="2" style="text-align: right;"> <a-tag class="tr-info-tag" color="green">[[ link.remark ]]</a-tag>
<a-tooltip title='{{ i18n "copy" }}'> <a-tooltip title='{{ i18n "copy" }}'>
<button class="ant-btn ant-btn-primary" :id="'copy-url-link-'+index" @click="copyToClipboard('copy-url-link-'+index, link.link)"> <a-button style="min-width: 24px;" size="small" icon="snippets" :id="'copy-url-link-'+index" @click="copyToClipboard('copy-url-link-'+index, link.link)"></a-button>
<a-icon type="snippets"></a-icon> </a-tooltip>
</button> </tr-info-title>
</a-tooltip> <code>[[ link.link ]]</code>
</a-col> </tr-info-row>
</a-row> </template>
<table v-if="inbound.protocol == Protocols.DOKODEMO" class="tr-info-table">
<tr>
<th>{{ i18n "pages.inbounds.targetAddress" }}</th>
<th>{{ i18n "pages.inbounds.destinationPort" }}</th>
<th>{{ i18n "pages.inbounds.network" }}</th>
<th>FollowRedirect</th>
</tr>
<tr>
<td>
<a-tag color="green">[[ inbound.settings.address ]]</a-tag>
</td>
<td>
<a-tag color="green">[[ inbound.settings.port ]]</a-tag>
</td>
<td>
<a-tag color="green">[[ inbound.settings.network ]]</a-tag>
</td>
<td>
<a-tag color="green">[[ inbound.settings.followRedirect ]]</a-tag>
</td>
</tr>
</table>
<table v-if="dbInbound.isSocks" class="tr-info-table">
<tr>
<th>{{ i18n "password" }} Auth</th>
<th>{{ i18n "pages.inbounds.enable" }} udp</th>
<th>IP</th>
</tr>
<tr>
<td>
<a-tag color="green">[[ inbound.settings.auth ]]</a-tag>
</td>
<td>
<a-tag color="green">[[ inbound.settings.udp]]</a-tag>
</td>
<td>
<a-tag color="green">[[ inbound.settings.ip ]]</a-tag>
</td>
</tr>
<template v-if="inbound.settings.auth == 'password'">
<tr>
<td></td>
<td>{{ i18n "username" }}</td>
<td>{{ i18n "password" }}</td>
</tr>
<tr v-for="account,index in inbound.settings.accounts">
<td>[[ index ]]</td>
<td>
<a-tag color="green">[[ account.user ]]</a-tag>
</td>
<td>
<a-tag color="green">[[ account.pass ]]</a-tag>
</td>
</tr>
</template> </template>
<table v-if="inbound.protocol == Protocols.DOKODEMO" style="margin-bottom: 10px; width: 100%;"> </table>
<tr> <table v-if="dbInbound.isHTTP" class="tr-info-table">
<th>{{ i18n "pages.inbounds.targetAddress" }}</th> <tr>
<th>{{ i18n "pages.inbounds.destinationPort" }}</th> <th></th>
<th>{{ i18n "pages.inbounds.network" }}</th> <th>{{ i18n "username" }}</th>
<th>FollowRedirect</th> <th>{{ i18n "password" }}</th>
</tr><tr> </tr>
<td><a-tag color="green">[[ inbound.settings.address ]]</a-tag></td> <tr v-for="account,index in inbound.settings.accounts">
<td><a-tag color="green">[[ inbound.settings.port ]]</a-tag></td> <td>[[ index ]]</td>
<td><a-tag color="green">[[ inbound.settings.network ]]</a-tag></td> <td>
<td><a-tag color="green">[[ inbound.settings.followRedirect ]]</a-tag></td> <a-tag color="green">[[ account.user ]]</a-tag>
</tr> </td>
</table> <td>
<table v-if="dbInbound.isSocks" style="margin-bottom: 10px; width: 100%;"> <a-tag color="green">[[ account.pass ]]</a-tag>
<tr> </td>
<th>{{ i18n "password" }} Auth</th> </tr>
<th>{{ i18n "pages.inbounds.enable" }} udp</th> </table>
<th>IP</th> <table v-if="dbInbound.isWireguard" class="tr-info-table">
</tr> <tr class="client-table-odd-row">
<tr> <td>{{ i18n "pages.xray.wireguard.secretKey" }}</td>
<td><a-tag color="green">[[ inbound.settings.auth ]]</a-tag></td> <td>[[ inbound.settings.secretKey ]]</td>
<td><a-tag color="green">[[ inbound.settings.udp]]</a-tag></td> </tr>
<td><a-tag color="green">[[ inbound.settings.ip ]]</a-tag></td> <tr>
</tr> <td>{{ i18n "pages.xray.wireguard.publicKey" }}</td>
<template v-if="inbound.settings.auth == 'password'"> <td>[[ inbound.settings.pubKey ]]</td>
<tr> </tr>
<td> </td> <tr class="client-table-odd-row">
<td>{{ i18n "username" }}</td> <td>MTU</td>
<td>{{ i18n "password" }}</td> <td>[[ inbound.settings.mtu ]]</td>
</tr><tr v-for="account,index in inbound.settings.accounts"> </tr>
<td>[[ index ]]</td> <tr>
<td><a-tag color="green">[[ account.user ]]</a-tag></td> <td>Kernel Mode</td>
<td><a-tag color="green">[[ account.pass ]]</a-tag></td> <td>[[ inbound.settings.kernelMode ]]</td>
</tr> </tr>
</template>
</table>
<table v-if="dbInbound.isHTTP" style="margin-bottom: 10px; width: 100%;">
<tr>
<th> </th>
<th>{{ i18n "username" }}</th>
<th>{{ i18n "password" }}</th>
</tr><tr v-for="account,index in inbound.settings.accounts">
<td>[[ index ]]</td>
<td><a-tag color="green">[[ account.user ]]</a-tag></td>
<td><a-tag color="green">[[ account.pass ]]</a-tag></td>
</tr>
</table>
<table v-if="dbInbound.isWireguard" style="margin-bottom: 10px; width: 100%;">
<tr class="client-table-odd-row">
<td>{{ i18n "pages.xray.wireguard.secretKey" }}</td>
<td>[[ inbound.settings.secretKey ]]</td>
</tr>
<tr>
<td>{{ i18n "pages.xray.wireguard.publicKey" }}</td>
<td>[[ inbound.settings.pubKey ]]</td>
</tr>
<tr class="client-table-odd-row">
<td>MTU</td>
<td>[[ inbound.settings.mtu ]]</td>
</tr>
<tr>
<td>Kernel Mode</td>
<td>[[ inbound.settings.kernelMode ]]</td>
</tr>
<template v-for="(peer, index) in inbound.settings.peers"> <template v-for="(peer, index) in inbound.settings.peers">
<tr> <tr>
<td colspan="2"><a-divider>Peer [[ index + 1 ]]</a-divider></td> <td colspan="2">
</tr> <a-divider>Peer [[ index + 1 ]]</a-divider>
<tr class="client-table-odd-row"> </td>
<td>{{ i18n "pages.xray.wireguard.secretKey" }}</td> </tr>
<td>[[ peer.privateKey ]]</td> <tr class="client-table-odd-row">
</tr> <td>{{ i18n "pages.xray.wireguard.secretKey" }}</td>
<tr> <td>[[ peer.privateKey ]]</td>
<td>{{ i18n "pages.xray.wireguard.publicKey" }}</td> </tr>
<td>[[ peer.publicKey ]]</td> <tr>
</tr> <td>{{ i18n "pages.xray.wireguard.publicKey" }}</td>
<tr class="client-table-odd-row"> <td>[[ peer.publicKey ]]</td>
<td>{{ i18n "pages.xray.wireguard.psk" }}</td> </tr>
<td>[[ peer.psk ]]</td> <tr class="client-table-odd-row">
</tr> <td>{{ i18n "pages.xray.wireguard.psk" }}</td>
<tr> <td>[[ peer.psk ]]</td>
<td>{{ i18n "pages.xray.wireguard.allowedIPs" }}</td> </tr>
<td>[[ peer.allowedIPs.join(",") ]]</td> <tr>
</tr> <td>{{ i18n "pages.xray.wireguard.allowedIPs" }}</td>
<tr class="client-table-odd-row"> <td>[[ peer.allowedIPs.join(",") ]]</td>
<td>Keep Alive</td> </tr>
<td>[[ peer.keepAlive ]]</td> <tr class="client-table-odd-row">
</tr> <td>Keep Alive</td>
<tr> <td>[[ peer.keepAlive ]]</td>
<td colspan="2"> </tr>
<a-row> <tr>
<a-col :span="22" style="overflow-wrap: anywhere;"> <td colspan="2">
<a-tag color="blue">Config</a-tag> <tr-info-row v-for="(link,index) in infoModal.links" class="tr-info-row">
<div <tr-info-title class="tr-info-title">
v-html="infoModal.links[index].replaceAll(`\n`,`<br />`)" <a-tag color="blue">Config</a-tag>
style="border-radius: 1rem; padding: 0.5rem;" <a-tooltip title='{{ i18n "copy" }}'>
class="client-table-odd-row"></div> <a-button style="min-width: 24px;" size="small" icon="snippets" :id="'copy-url-link-'+index" @click="copyToClipboard('copy-url-link-'+index, infoModal.links[index])"></a-button>
</a-col> </a-tooltip>
<a-col :span="2" style="text-align: right;"> </tr-info-title>
<a-tooltip title='{{ i18n "copy" }}'> <div v-html="infoModal.links[index].replaceAll(`\n`,`<br />`)" style="border-radius: 1rem; padding: 0.5rem;" class="client-table-odd-row">
<button class="ant-btn ant-btn-primary" </div>
:id="'copy-url-link-'+index" </tr-info-row>
@click="copyToClipboard('copy-url-link-'+index, infoModal.links[index])"> </td>
<a-icon type="snippets"></a-icon> </tr>
</button> </table>
</a-tooltip> </template>
</a-col>
</a-row>
</td>
</tr>
</table>
</template>
</template> </template>
</a-modal> </a-modal>
<script> <script>
const infoModal = { const infoModal = {
visible: false, visible: false,
inbound: new Inbound(), inbound: new Inbound(),
dbInbound: new DBInbound(), dbInbound: new DBInbound(),
clientSettings: null, clientSettings: null,
clientStats: [], clientStats: [],
upStats: 0, upStats: 0,
downStats: 0, downStats: 0,
clipboard: null, clipboard: null,
links: [], links: [],
index: null, index: null,
isExpired: false, isExpired: false,
subLink: '', subLink: '',
subJsonLink: '', subJsonLink: '',
show(dbInbound, index) { show(dbInbound, index) {
this.index = index; this.index = index;
this.inbound = dbInbound.toInbound(); this.inbound = dbInbound.toInbound();
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) : []; this.clientStats = this.inbound.clients ? this.dbInbound.clientStats.find(row => row.email === this.clientSettings.email) : [];
if (this.inbound.protocol == Protocols.WIREGUARD){ if (this.inbound.protocol == Protocols.WIREGUARD) {
this.links = this.inbound.genInboundLinks(dbInbound.remark).split('\r\n') this.links = this.inbound.genInboundLinks(dbInbound.remark).split('\r\n')
} else { } else {
this.links = this.inbound.genAllLinks(this.dbInbound.remark, app.remarkModel, this.clientSettings); this.links = this.inbound.genAllLinks(this.dbInbound.remark, app.remarkModel, this.clientSettings);
} }
if (this.clientSettings) { if (this.clientSettings) {
if (this.clientSettings.subId) { if (this.clientSettings.subId) {
this.subLink = this.genSubLink(this.clientSettings.subId); this.subLink = this.genSubLink(this.clientSettings.subId);
this.subJsonLink = this.genSubJsonLink(this.clientSettings.subId); this.subJsonLink = this.genSubJsonLink(this.clientSettings.subId);
}
}
this.visible = true;
},
close() {
infoModal.visible = false;
},
genSubLink(subID) {
return app.subSettings.subURI+subID;
},
genSubJsonLink(subID) {
return app.subSettings.subJsonURI+subID;
} }
}; }
this.visible = true;
const infoModalApp = new Vue({ },
delimiters: ['[[', ']]'], close() {
el: '#inbound-info-modal', infoModal.visible = false;
data: { },
infoModal, genSubLink(subID) {
get dbInbound() { return app.subSettings.subURI + subID;
return this.infoModal.dbInbound; },
}, genSubJsonLink(subID) {
get inbound() { return app.subSettings.subJsonURI + subID;
return this.infoModal.inbound; }
}, };
get isActive() { const infoModalApp = new Vue({
if(infoModal.clientStats){ delimiters: ['[[', ']]'],
return infoModal.clientStats.enable; el: '#inbound-info-modal',
} data: {
return true; infoModal,
}, get dbInbound() {
get isEnable() { return this.infoModal.dbInbound;
if(infoModal.clientSettings){ },
return infoModal.clientSettings.enable; get inbound() {
} return this.infoModal.inbound;
return infoModal.dbInbound.isEnable; },
}, get isActive() {
}, if (infoModal.clientStats) {
methods: { return infoModal.clientStats.enable;
copyToClipboard(elmentId,content) { }
this.infoModal.clipboard = new ClipboardJS('#' + elmentId, { return true;
text: () => content, },
}); get isEnable() {
this.infoModal.clipboard.on('success', () => { if (infoModal.clientSettings) {
app.$message.success('{{ i18n "copied" }}') return infoModal.clientSettings.enable;
this.infoModal.clipboard.destroy(); }
}); return infoModal.dbInbound.isEnable;
}, },
statsColor(stats) { },
return usageColor(stats.up + stats.down, app.trafficDiff, stats.total); methods: {
}, copyToClipboard(elmentId, content) {
getRemStats() { this.infoModal.clipboard = new ClipboardJS('#' + elmentId, {
remained = this.infoModal.clientStats.total - this.infoModal.clientStats.up - this.infoModal.clientStats.down; text: () => content,
return remained>0 ? sizeFormat(remained) : '-'; });
}, this.infoModal.clipboard.on('success', () => {
}, app.$message.success('{{ i18n "copied" }}')
this.infoModal.clipboard.destroy();
}); });
},
statsColor(stats) {
return usageColor(stats.up + stats.down, app.trafficDiff, stats.total);
},
getRemStats() {
remained = this.infoModal.clientStats.total - this.infoModal.clientStats.up - this.infoModal.clientStats.down;
return remained > 0 ? sizeFormat(remained) : '-';
},
},
});
</script> </script>
{{end}} {{end}}

View File

@@ -2,6 +2,34 @@
<html lang="en"> <html lang="en">
{{template "head" .}} {{template "head" .}}
<style> <style>
.ant-table:not(.ant-table-expanded-row .ant-table) {
outline: 1px solid #f0f0f0;
outline-offset: -1px;
border-radius: 1rem;
overflow-x: hidden;
}
.dark .ant-table:not(.ant-table-expanded-row .ant-table) {
outline-color: var(--dark-color-table-ring);
}
.ant-table .ant-table-content .ant-table-scroll .ant-table-body {
overflow-y: hidden;
}
.ant-table .ant-table-content .ant-table-tbody tr:last-child .ant-table-wrapper {
margin:-10px 22px -10px !important;
}
.ant-table .ant-table-content .ant-table-tbody tr:last-child .ant-table-wrapper .ant-table {
border-bottom-left-radius: 1rem;
border-bottom-right-radius: 1rem;
}
.ant-table .ant-table-content .ant-table-tbody tr:last-child tr:last-child td {
border-bottom-color: transparent;
}
.ant-table .ant-table-tbody tr:last-child.ant-table-expanded-row .ant-table-wrapper .ant-table-tbody>tr:last-child>td:first-child {
border-bottom-left-radius: 6px;
}
.ant-table .ant-table-tbody tr:last-child.ant-table-expanded-row .ant-table-wrapper .ant-table-tbody>tr:last-child>td:last-child {
border-bottom-right-radius: 6px;
}
@media (min-width: 769px) { @media (min-width: 769px) {
.ant-layout-content { .ant-layout-content {
margin: 24px 16px; margin: 24px 16px;
@@ -11,6 +39,9 @@
.ant-card-body { .ant-card-body {
padding: .5rem; padding: .5rem;
} }
.ant-table .ant-table-content .ant-table-tbody tr:last-child .ant-table-wrapper {
margin:-10px 2px -10px !important;
}
} }
.ant-col-sm-24 { .ant-col-sm-24 {
margin: 0.5rem -2rem 0.5rem 2rem; margin: 0.5rem -2rem 0.5rem 2rem;
@@ -22,13 +53,14 @@
padding: 0 5px; padding: 0 5px;
border-radius: 2rem; border-radius: 2rem;
min-width: 50px; min-width: 50px;
min-height: 22px;
} }
.infinite-bar .ant-progress-inner .ant-progress-bg { .infinite-bar .ant-progress-inner .ant-progress-bg {
background-color: #F2EAF1; background-color: #F2EAF1;
border: #D5BED2 solid 1px; border: #D5BED2 solid 1px;
} }
.dark .infinite-bar .ant-progress-inner .ant-progress-bg { .dark .infinite-bar .ant-progress-inner .ant-progress-bg {
background-color: #7a316f; background-color: #7a316f !important;
border: #7a316f solid 1px; border: #7a316f solid 1px;
} }
.ant-collapse { .ant-collapse {
@@ -53,6 +85,41 @@
opacity: .2; opacity: .2;
} }
} }
.tr-table-box {
display: flex;
gap: 4px;
justify-content: center;
align-items: center;
}
.tr-table-rt {
flex-basis: 70px;
min-width: 70px;
text-align: end;
}
.tr-table-lt {
flex-basis: 70px;
min-width: 70px;
text-align: start;
}
.tr-table-bar {
flex-basis: 160px;
min-width: 60px;
}
.tr-infinity-ch {
font-size: 14pt;
max-height: 24px;
display: inline-flex;
align-items: center;
}
.ant-table-expanded-row .ant-table .ant-table-body {
overflow-x: hidden;
}
.ant-table-expanded-row .ant-table-tbody>tr>td {
padding: 10px 2px;
}
.ant-table-expanded-row .ant-table-thead>tr>th {
padding: 12px 2px;
}
</style> </style>
<body> <body>
@@ -324,8 +391,8 @@
[[ sizeFormat(dbInbound.total) ]] [[ sizeFormat(dbInbound.total) ]]
</template> </template>
<template v-else> <template v-else>
<svg style="fill: currentColor; height: 10px;" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"> <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" /> <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>
</template> </template>
</a-tag> </a-tag>
@@ -343,7 +410,11 @@
[[ remainedDays(dbInbound._expiryTime) ]] [[ remainedDays(dbInbound._expiryTime) ]]
</a-tag> </a-tag>
</a-popover> </a-popover>
<a-tag v-else color="purple" class="infinite-tag">&infin;</a-tag> <a-tag v-else color="purple" class="infinite-tag">
<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>
</svg>
</a-tag>
</template> </template>
<template slot="info" slot-scope="text, dbInbound"> <template slot="info" slot-scope="text, dbInbound">
<a-popover placement="bottomRight" :overlay-class-name="themeSwitcher.currentTheme" trigger="click"> <a-popover placement="bottomRight" :overlay-class-name="themeSwitcher.currentTheme" trigger="click">
@@ -415,7 +486,11 @@
<template v-if="dbInbound.total > 0"> <template v-if="dbInbound.total > 0">
[[ sizeFormat(dbInbound.total) ]] [[ sizeFormat(dbInbound.total) ]]
</template> </template>
<template v-else>&infin;</template> <template v-else>
<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>
</svg>
</template>
</a-tag> </a-tag>
</a-popover> </a-popover>
</td> </td>
@@ -426,7 +501,11 @@
<a-tag style="min-width: 50px; text-align: center;" v-if="dbInbound.expiryTime > 0" :color="dbInbound.isExpiry? 'red': 'blue'"> <a-tag style="min-width: 50px; text-align: center;" v-if="dbInbound.expiryTime > 0" :color="dbInbound.isExpiry? 'red': 'blue'">
[[ DateUtil.formatMillis(dbInbound.expiryTime) ]] [[ DateUtil.formatMillis(dbInbound.expiryTime) ]]
</a-tag> </a-tag>
<a-tag v-else style="text-align: center;" color="purple" class="infinite-tag">&infin;</a-tag> <a-tag v-else style="text-align: center;" color="purple" class="infinite-tag">
<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>
</svg>
</a-tag>
</td> </td>
</tr> </tr>
</table> </table>
@@ -445,7 +524,7 @@
:columns="isMobile ? innerMobileColumns : innerColumns" :columns="isMobile ? innerMobileColumns : innerColumns"
:data-source="getInboundClients(record)" :data-source="getInboundClients(record)"
:pagination=pagination(getInboundClients(record)) :pagination=pagination(getInboundClients(record))
:style="isMobile ? 'margin: -12px 2px -13px;' : 'margin: -12px 22px -13px;'"> :style="isMobile ? 'margin: -10px 2px -11px;' : 'margin: -10px 22px -11px;'">
{{template "client_table"}} {{template "client_table"}}
</a-table> </a-table>
</template> </template>
@@ -543,7 +622,7 @@
{ title: '{{ i18n "online" }}', width: 30, scopedSlots: { customRender: 'online' } }, { title: '{{ i18n "online" }}', width: 30, scopedSlots: { customRender: 'online' } },
{ title: '{{ i18n "pages.inbounds.client" }}', width: 80, scopedSlots: { customRender: 'client' } }, { title: '{{ i18n "pages.inbounds.client" }}', width: 80, scopedSlots: { customRender: 'client' } },
{ title: '{{ i18n "pages.inbounds.traffic" }}', width: 80, align: 'center', scopedSlots: { customRender: 'traffic' } }, { title: '{{ i18n "pages.inbounds.traffic" }}', width: 80, align: 'center', scopedSlots: { customRender: 'traffic' } },
{ title: '{{ i18n "pages.inbounds.expireDate" }}', width: 100, align: 'center', scopedSlots: { customRender: 'expiryTime' } }, { title: '{{ i18n "pages.inbounds.expireDate" }}', width: 80, align: 'center', scopedSlots: { customRender: 'expiryTime' } },
]; ];
const innerMobileColumns = [ const innerMobileColumns = [

View File

@@ -2,20 +2,23 @@
<html lang="en"> <html lang="en">
{{template "head" .}} {{template "head" .}}
<style> <style>
@media (min-width: 769px) { @media (min-width: 769px) {
.ant-layout-content { .ant-layout-content {
margin: 24px 16px; margin: 24px 16px;
}
.ant-card-hoverable {
margin-inline: 0.3rem;
}
} }
.ant-col-sm-24 { .ant-card-hoverable {
margin-top: 10px; margin-inline: 0.3rem;
} }
.ant-card-dark h2 { .ant-alert-error {
color: var(--dark-color-text-primary); margin-inline: 0.3rem;
} }
}
.ant-col-sm-24 {
margin-top: 10px;
}
.ant-card-dark h2 {
color: var(--dark-color-text-primary);
}
</style> </style>
<body> <body>

View File

@@ -407,6 +407,7 @@
streamSettings: { streamSettings: {
sockopt: { sockopt: {
tcpKeepAliveIdle: 100, tcpKeepAliveIdle: 100,
tcpMptcp: true,
tcpNoDelay: true tcpNoDelay: true
} }
} }

View File

@@ -1,14 +1,14 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
{{template "head" .}} {{template "head" .}}
<link rel="stylesheet" href="{{ .base_path }}assets/codemirror/codemirror.css?{{ .cur_ver }}"> <link rel="stylesheet" href="{{ .base_path }}assets/codemirror/codemirror.min.css?{{ .cur_ver }}">
<link rel="stylesheet" href="{{ .base_path }}assets/codemirror/fold/foldgutter.css"> <link rel="stylesheet" href="{{ .base_path }}assets/codemirror/fold/foldgutter.css">
<link rel="stylesheet" href="{{ .base_path }}assets/codemirror/xq.css?{{ .cur_ver }}"> <link rel="stylesheet" href="{{ .base_path }}assets/codemirror/xq.min.css?{{ .cur_ver }}">
<link rel="stylesheet" href="{{ .base_path }}assets/codemirror/lint/lint.css"> <link rel="stylesheet" href="{{ .base_path }}assets/codemirror/lint/lint.css">
<script src="{{ .base_path }}assets/base64/base64.min.js"></script> <script src="{{ .base_path }}assets/base64/base64.min.js"></script>
<script src="{{ .base_path }}assets/js/model/outbound.js?{{ .cur_ver }}"></script> <script src="{{ .base_path }}assets/js/model/outbound.js?{{ .cur_ver }}"></script>
<script src="{{ .base_path }}assets/codemirror/codemirror.js?{{ .cur_ver }}"></script> <script src="{{ .base_path }}assets/codemirror/codemirror.min.js?{{ .cur_ver }}"></script>
<script src="{{ .base_path }}assets/codemirror/javascript.js"></script> <script src="{{ .base_path }}assets/codemirror/javascript.js"></script>
<script src="{{ .base_path }}assets/codemirror/jshint.js"></script> <script src="{{ .base_path }}assets/codemirror/jshint.js"></script>
<script src="{{ .base_path }}assets/codemirror/jsonlint.js"></script> <script src="{{ .base_path }}assets/codemirror/jsonlint.js"></script>
@@ -19,44 +19,44 @@
<script src="{{ .base_path }}assets/codemirror/fold/foldgutter.js"></script> <script src="{{ .base_path }}assets/codemirror/fold/foldgutter.js"></script>
<script src="{{ .base_path }}assets/codemirror/fold/brace-fold.js"></script> <script src="{{ .base_path }}assets/codemirror/fold/brace-fold.js"></script>
<style> <style>
@media (min-width: 769px) { @media (min-width: 769px) {
.ant-layout-content { .ant-layout-content {
margin: 24px 16px; margin: 24px 16px;
}
} }
@media (max-width: 768px) { }
.ant-tabs-nav .ant-tabs-tab { @media (max-width: 768px) {
margin: 0; .ant-tabs-nav .ant-tabs-tab {
padding: 12px .5rem; margin: 0;
} padding: 12px .5rem;
.ant-table-thead > tr > th,
.ant-table-tbody > tr > td {
padding: 10px 0px;
}
} }
.ant-tabs-bar { .ant-table-thead>tr>th,
margin: 0; .ant-table-tbody>tr>td {
} padding: 10px 0px;
.ant-list-item {
display: block;
}
.collapse-title {
color: inherit;
font-weight: bold;
font-size: 18px;
padding: 10px 20px;
border-bottom: 2px solid;
}
.collapse-title > i {
color: inherit;
font-size: 24px;
}
.ant-collapse-content-box > li {
padding: 12px 0 0 0 !important;
}
.ant-list-item > li {
padding: 10px 20px !important;
} }
}
.ant-tabs-bar {
margin: 0;
}
.ant-list-item {
display: block;
}
.collapse-title {
color: inherit;
font-weight: bold;
font-size: 18px;
padding: 10px 20px;
border-bottom: 2px solid;
}
.collapse-title>i {
color: inherit;
font-size: 24px;
}
.ant-collapse-content-box>li {
padding: 12px 0 0 0 !important;
}
.ant-list-item>li {
padding: 10px 20px !important;
}
</style> </style>
<body> <body>
<a-layout id="app" v-cloak :class="themeSwitcher.currentTheme"> <a-layout id="app" v-cloak :class="themeSwitcher.currentTheme">
@@ -443,7 +443,7 @@
<a-table :columns="outboundColumns" bordered <a-table :columns="outboundColumns" bordered
:row-key="r => r.key" :row-key="r => r.key"
:data-source="outboundData" :data-source="outboundData"
:scroll="isMobile ? {} : { x: 200 }" :scroll="isMobile ? {} : { x: 800 }"
:pagination="false" :pagination="false"
:indent-size="0" :indent-size="0"
:style="isMobile ? 'padding: 5px 5px' : 'margin-right: 1px;'"> :style="isMobile ? 'padding: 5px 5px' : 'margin-right: 1px;'">

View File

@@ -1,261 +1,246 @@
{{define "ruleModal"}} {{define "ruleModal"}}
<a-modal id="rule-modal" v-model="ruleModal.visible" :title="ruleModal.title" @ok="ruleModal.ok" <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">
:confirm-loading="ruleModal.confirmLoading" :closable="true" :mask-closable="false" <a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
:ok-text="ruleModal.okText" cancel-text='{{ i18n "close" }}' :class="themeSwitcher.currentTheme"> <a-form-item label='Domain Matcher'>
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }"> <a-select v-model="ruleModal.rule.domainMatcher" :dropdown-class-name="themeSwitcher.currentTheme">
<a-form-item label='Domain Matcher'> <a-select-option v-for="dm in ['','hybrid','linear']" :value="dm">[[ dm ]]</a-select-option>
<a-select v-model="ruleModal.rule.domainMatcher" :dropdown-class-name="themeSwitcher.currentTheme"> </a-select>
<a-select-option v-for="dm in ['','hybrid','linear']" :value="dm">[[ dm ]]</a-select-option> </a-form-item>
</a-select> <a-form-item>
</a-form-item> <template slot="label">
<a-form-item> <a-tooltip>
<template slot="label"> <template slot="title">
<a-tooltip> <span>{{ i18n "pages.xray.rules.useComma" }}</span>
<template slot="title"> </template> Source IPs <a-icon type="question-circle"></a-icon>
<span>{{ i18n "pages.xray.rules.useComma" }}</span> </a-tooltip>
</template> </template>
Source IPs <a-icon type="question-circle"></a-icon> <a-input v-model.trim="ruleModal.rule.source"></a-input>
</a-tooltip> </a-form-item>
</template> <a-form-item>
<a-input v-model.trim="ruleModal.rule.source"></a-input> <template slot="label">
</a-form-item> <a-tooltip>
<a-form-item> <template slot="title">
<template slot="label"> <span>{{ i18n "pages.xray.rules.useComma" }}</span>
<a-tooltip> </template> Source Port <a-icon type="question-circle"></a-icon>
<template slot="title"> </a-tooltip>
<span>{{ i18n "pages.xray.rules.useComma" }}</span> </template>
</template> <a-input v-model.trim="ruleModal.rule.sourcePort"></a-input>
Source Port <a-icon type="question-circle"></a-icon> </a-form-item>
</a-tooltip> <a-form-item label='Network'>
</template> <a-select v-model="ruleModal.rule.network" :dropdown-class-name="themeSwitcher.currentTheme">
<a-input v-model.trim="ruleModal.rule.sourcePort"></a-input> <a-select-option v-for="x in ['','TCP','UDP','TCP,UDP']" :value="x">[[ x ]]</a-select-option>
</a-form-item> </a-select>
<a-form-item label='Network'> </a-form-item>
<a-select v-model="ruleModal.rule.network" :dropdown-class-name="themeSwitcher.currentTheme"> <a-form-item label='Protocol'>
<a-select-option v-for="x in ['','TCP','UDP','TCP,UDP']" :value="x">[[ x ]]</a-select-option> <a-select v-model="ruleModal.rule.protocol" mode="multiple" :dropdown-class-name="themeSwitcher.currentTheme">
</a-select> <a-select-option v-for="x in ['http','tls','bittorrent']" :value="x">[[ x ]]</a-select-option>
</a-form-item> </a-select>
<a-form-item label='Protocol'> </a-form-item>
<a-select v-model="ruleModal.rule.protocol" mode="multiple" :dropdown-class-name="themeSwitcher.currentTheme"> <a-form-item label='Attributes'>
<a-select-option v-for="x in ['http','tls','bittorrent']" :value="x">[[ x ]]</a-select-option> <a-button icon="plus" size="small" style="margin-left: 10px" @click="ruleModal.rule.attrs.push(['', ''])"></a-button>
</a-select> </a-form-item>
</a-form-item> <a-form-item :wrapper-col="{span: 24}">
<a-form-item label='Attributes'> <a-input-group compact v-for="(attr,index) in ruleModal.rule.attrs">
<a-button size="small" style="margin-left: 10px" @click="ruleModal.rule.attrs.push(['', ''])">+</a-button> <a-input style="width: 50%" v-model="attr[0]" placeholder='{{ i18n "pages.inbounds.stream.general.name" }}'>
</a-form-item> <template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template>
<a-form-item :wrapper-col="{span: 24}"> </a-input>
<a-input-group compact v-for="(attr,index) in ruleModal.rule.attrs"> <a-input style="width: 50%" v-model="attr[1]" placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'>
<a-input style="width: 50%" v-model="attr[0]" placeholder='{{ i18n "pages.inbounds.stream.general.name" }}'> <a-button icon="minus" slot="addonAfter" size="small" @click="ruleModal.rule.attrs.splice(index,1)"></a-button>
<template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template> </a-input>
</a-input> </a-input-group>
<a-input style="width: 50%" v-model="attr[1]" placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'> </a-form-item>
<a-button slot="addonAfter" size="small" @click="ruleModal.rule.attrs.splice(index,1)">-</a-button> <a-form-item>
</a-input> <template slot="label">
</a-input-group> <a-tooltip>
</a-form-item> <template slot="title">
<a-form-item> <span>{{ i18n "pages.xray.rules.useComma" }}</span>
<template slot="label"> </template> IP <a-icon type="question-circle"></a-icon>
<a-tooltip> </a-tooltip>
<template slot="title"> </template>
<span>{{ i18n "pages.xray.rules.useComma" }}</span> <a-input v-model.trim="ruleModal.rule.ip"></a-input>
</template> </a-form-item>
IP <a-icon type="question-circle"></a-icon> <a-form-item>
</a-tooltip> <template slot="label">
</template> <a-tooltip>
<a-input v-model.trim="ruleModal.rule.ip"></a-input> <template slot="title">
</a-form-item> <span>{{ i18n "pages.xray.rules.useComma" }}</span>
<a-form-item> </template> Domain <a-icon type="question-circle"></a-icon>
<template slot="label"> </a-tooltip>
<a-tooltip> </template>
<template slot="title"> <a-input v-model.trim="ruleModal.rule.domain"></a-input>
<span>{{ i18n "pages.xray.rules.useComma" }}</span> </a-form-item>
</template> <a-form-item>
Domain <a-icon type="question-circle"></a-icon> <template slot="label">
</a-tooltip> <a-tooltip>
</template> <template slot="title">
<a-input v-model.trim="ruleModal.rule.domain"></a-input> <span>{{ i18n "pages.xray.rules.useComma" }}</span>
</a-form-item> </template> User <a-icon type="question-circle"></a-icon>
<a-form-item> </a-tooltip>
<template slot="label"> </template>
<a-tooltip> <a-input v-model.trim="ruleModal.rule.user"></a-input>
<template slot="title"> </a-form-item>
<span>{{ i18n "pages.xray.rules.useComma" }}</span> <a-form-item>
</template> <template slot="label">
User <a-icon type="question-circle"></a-icon> <a-tooltip>
</a-tooltip> <template slot="title">
</template> <span>{{ i18n "pages.xray.rules.useComma" }}</span>
<a-input v-model.trim="ruleModal.rule.user"></a-input> </template> Port <a-icon type="question-circle"></a-icon>
</a-form-item> </a-tooltip>
<a-form-item> </template>
<template slot="label"> <a-input v-model.trim="ruleModal.rule.port"></a-input>
<a-tooltip> </a-form-item>
<template slot="title"> <a-form-item label='Inbound Tags'>
<span>{{ i18n "pages.xray.rules.useComma" }}</span> <a-select v-model="ruleModal.rule.inboundTag" mode="multiple" :dropdown-class-name="themeSwitcher.currentTheme">
</template> <a-select-option v-for="tag in ruleModal.inboundTags" :value="tag">[[ tag ]]</a-select-option>
Port <a-icon type="question-circle"></a-icon> </a-select>
</a-tooltip> </a-form-item>
</template> <a-form-item label='Outbound Tag'>
<a-input v-model.trim="ruleModal.rule.port"></a-input> <a-select v-model="ruleModal.rule.outboundTag" :dropdown-class-name="themeSwitcher.currentTheme">
</a-form-item> <a-select-option v-for="tag in ruleModal.outboundTags" :value="tag">[[ tag ]]</a-select-option>
<a-form-item label='Inbound Tags'> </a-select>
<a-select v-model="ruleModal.rule.inboundTag" mode="multiple" :dropdown-class-name="themeSwitcher.currentTheme"> </a-form-item>
<a-select-option v-for="tag in ruleModal.inboundTags" :value="tag">[[ tag ]]</a-select-option> <a-form-item>
</a-select> <template slot="label">
</a-form-item> <a-tooltip>
<a-form-item label='Outbound Tag'> <template slot="title">
<a-select v-model="ruleModal.rule.outboundTag" :dropdown-class-name="themeSwitcher.currentTheme"> <span>{{ i18n "pages.xray.balancer.balancerDesc" }}</span>
<a-select-option v-for="tag in ruleModal.outboundTags" :value="tag">[[ tag ]]</a-select-option> </template> Balancer Tag <a-icon type="question-circle"></a-icon>
</a-select> </a-tooltip>
</a-form-item> </template>
<a-form-item> <a-select v-model="ruleModal.rule.balancerTag" :dropdown-class-name="themeSwitcher.currentTheme">
<template slot="label"> <a-select-option v-for="tag in ruleModal.balancerTags" :value="tag">[[ tag ]]</a-select-option>
<a-tooltip> </a-select>
<template slot="title"> </a-form-item>
<span>{{ i18n "pages.xray.balancer.balancerDesc" }}</span> </a-form>
</template>
Balancer Tag <a-icon type="question-circle"></a-icon>
</a-tooltip>
</template>
<a-select v-model="ruleModal.rule.balancerTag" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="tag in ruleModal.balancerTags" :value="tag">[[ tag ]]</a-select-option>
</a-select>
</a-form-item>
</table>
</a-form>
</a-modal> </a-modal>
<script> <script>
const ruleModal = {
const ruleModal = { title: '',
title: '', visible: false,
visible: false, confirmLoading: false,
confirmLoading: false, okText: '{{ i18n "sure" }}',
okText: '{{ i18n "sure" }}', isEdit: false,
isEdit: false, confirm: null,
confirm: null, rule: {
rule: { type: "field",
type: "field", domainMatcher: "",
domainMatcher: "", domain: "",
domain: "", ip: "",
ip: "", port: "",
port: "", sourcePort: "",
sourcePort: "", network: "",
network: "", source: "",
source: "", user: "",
user: "", inboundTag: [],
inboundTag: [], protocol: [],
protocol: [], attrs: [],
attrs: [], outboundTag: "",
outboundTag: "", balancerTag: "",
balancerTag: "", },
}, inboundTags: [],
inboundTags: [], outboundTags: [],
outboundTags: [], users: [],
users: [], balancerTags: [],
balancerTags: [], ok() {
ok() { newRule = ruleModal.getResult();
newRule = ruleModal.getResult(); ObjectUtil.execute(ruleModal.confirm, newRule);
ObjectUtil.execute(ruleModal.confirm, newRule); },
}, show({
show({ title='', okText='{{ i18n "sure" }}', rule, confirm=(rule)=>{}, isEdit=false }) { title = '',
this.title = title; okText = '{{ i18n "sure" }}',
this.okText = okText; rule,
this.confirm = confirm; confirm = (rule) => {},
this.visible = true; isEdit = false
if(isEdit) { }) {
this.rule.domainMatcher = rule.domainMatcher; this.title = title;
this.rule.domain = rule.domain ? rule.domain.join(',') : []; this.okText = okText;
this.rule.ip = rule.ip ? rule.ip.join(',') : []; this.confirm = confirm;
this.rule.port = rule.port; this.visible = true;
this.rule.sourcePort = rule.sourcePort; if (isEdit) {
this.rule.network = rule.network; this.rule.domainMatcher = rule.domainMatcher;
this.rule.source = rule.source ? rule.source.join(',') : []; this.rule.domain = rule.domain ? rule.domain.join(',') : [];
this.rule.user = rule.user ? rule.user.join(',') : []; this.rule.ip = rule.ip ? rule.ip.join(',') : [];
this.rule.inboundTag = rule.inboundTag; this.rule.port = rule.port;
this.rule.protocol = rule.protocol; this.rule.sourcePort = rule.sourcePort;
this.rule.attrs = rule.attrs ? Object.entries(rule.attrs) : []; this.rule.network = rule.network;
this.rule.outboundTag = rule.outboundTag; this.rule.source = rule.source ? rule.source.join(',') : [];
this.rule.balancerTag = rule.balancerTag ? rule.balancerTag : ""; this.rule.user = rule.user ? rule.user.join(',') : [];
} else { this.rule.inboundTag = rule.inboundTag;
this.rule = { this.rule.protocol = rule.protocol;
domainMatcher: "", this.rule.attrs = rule.attrs ? Object.entries(rule.attrs) : [];
domain: "", this.rule.outboundTag = rule.outboundTag;
ip: "", this.rule.balancerTag = rule.balancerTag ? rule.balancerTag : "";
port: "", } else {
sourcePort: "", this.rule = {
network: "", domainMatcher: "",
source: "", domain: "",
user: "", ip: "",
inboundTag: [], port: "",
protocol: [], sourcePort: "",
attrs: [], network: "",
outboundTag: "", source: "",
balancerTag: "", user: "",
} inboundTag: [],
} protocol: [],
this.isEdit = isEdit; attrs: [],
this.inboundTags = app.templateSettings.inbounds.filter((i) => !ObjectUtil.isEmpty(i.tag)).map(obj => obj.tag); outboundTag: "",
this.inboundTags.push(...app.inboundTags); balancerTag: "",
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)];
if(app.templateSettings.reverse){
if(app.templateSettings.reverse.bridges) {
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.routing && app.templateSettings.routing.balancers) {
this.balancerTags = [ "", ...app.templateSettings.routing.balancers.filter((o) => !ObjectUtil.isEmpty(o.tag)).map(obj => obj.tag)];
}
},
close() {
ruleModal.visible = false;
ruleModal.loading(false);
},
loading(loading=true) {
ruleModal.confirmLoading = loading;
},
getResult() {
value = ruleModal.rule;
rule = {};
newRule = {};
rule.type = "field";
rule.domainMatcher = value.domainMatcher;
rule.domain = value.domain.length>0 ? value.domain.split(',') : [];
rule.ip = value.ip.length>0 ? value.ip.split(',') : [];
rule.port = value.port;
rule.sourcePort = value.sourcePort;
rule.network = value.network;
rule.source = value.source.length>0 ? value.source.split(',') : [];
rule.user = value.user.length>0 ? value.user.split(',') : [];
rule.inboundTag = value.inboundTag;
rule.protocol = value.protocol;
rule.attrs = Object.fromEntries(value.attrs);
rule.outboundTag = value.outboundTag == "" ? undefined : value.outboundTag;
rule.balancerTag = value.balancerTag == "" ? undefined : value.balancerTag;
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 !== ''
) {
newRule[key] = value;
}
}
return newRule;
} }
}; }
this.isEdit = isEdit;
new Vue({ this.inboundTags = app.templateSettings.inbounds.filter((i) => !ObjectUtil.isEmpty(i.tag)).map(obj => obj.tag);
delimiters: ['[[', ']]'], this.inboundTags.push(...app.inboundTags);
el: '#rule-modal', if (app.enableDNS && !ObjectUtil.isEmpty(app.dnsTag)) this.inboundTags.push(app.dnsTag)
data: { this.outboundTags = ["", ...app.templateSettings.outbounds.filter((o) => !ObjectUtil.isEmpty(o.tag)).map(obj => obj.tag)];
ruleModal: ruleModal, if (app.templateSettings.reverse) {
if (app.templateSettings.reverse.bridges) {
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.routing && app.templateSettings.routing.balancers) {
this.balancerTags = ["", ...app.templateSettings.routing.balancers.filter((o) => !ObjectUtil.isEmpty(o.tag)).map(obj => obj.tag)];
}
},
close() {
ruleModal.visible = false;
ruleModal.loading(false);
},
loading(loading = true) {
ruleModal.confirmLoading = loading;
},
getResult() {
value = ruleModal.rule;
rule = {};
newRule = {};
rule.type = "field";
rule.domainMatcher = value.domainMatcher;
rule.domain = value.domain.length > 0 ? value.domain.split(',') : [];
rule.ip = value.ip.length > 0 ? value.ip.split(',') : [];
rule.port = value.port;
rule.sourcePort = value.sourcePort;
rule.network = value.network;
rule.source = value.source.length > 0 ? value.source.split(',') : [];
rule.user = value.user.length > 0 ? value.user.split(',') : [];
rule.inboundTag = value.inboundTag;
rule.protocol = value.protocol;
rule.attrs = Object.fromEntries(value.attrs);
rule.outboundTag = value.outboundTag == "" ? undefined : value.outboundTag;
rule.balancerTag = value.balancerTag == "" ? undefined : value.balancerTag;
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 !== '') {
newRule[key] = value;
}
}
return newRule;
}
};
new Vue({
delimiters: ['[[', ']]'],
el: '#rule-modal',
data: {
ruleModal: ruleModal,
}
});
</script> </script>
{{end}} {{end}}

View File

@@ -1,21 +1,25 @@
package middleware package middleware
import ( import (
"net"
"net/http" "net/http"
"strings"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
func DomainValidatorMiddleware(domain string) gin.HandlerFunc { func DomainValidatorMiddleware(domain string) gin.HandlerFunc {
return func(c *gin.Context) { return func(c *gin.Context) {
host := strings.Split(c.Request.Host, ":")[0] host := c.GetHeader("X-Forwarded-Host")
if host == "" {
if host != domain { host = c.GetHeader("X-Real-IP")
c.AbortWithStatus(http.StatusForbidden)
return
} }
if host == "" {
host, _, _ := net.SplitHostPort(c.Request.Host)
if host != domain {
c.AbortWithStatus(http.StatusForbidden)
return
}
c.Next() c.Next()
}
} }
} }

View File

@@ -609,7 +609,7 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId strin
oldEmail := "" oldEmail := ""
newClientId := "" newClientId := ""
clientIndex := 0 clientIndex := -1
for index, oldClient := range oldClients { for index, oldClient := range oldClients {
oldClientId := "" oldClientId := ""
if oldInbound.Protocol == "trojan" { if oldInbound.Protocol == "trojan" {
@@ -630,7 +630,7 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId strin
} }
// Validate new client ID // Validate new client ID
if newClientId == "" { if newClientId == "" || clientIndex == -1 {
return false, common.NewError("empty client ID") return false, common.NewError("empty client ID")
} }

View File

@@ -770,7 +770,10 @@ func (t *Tgbot) asnwerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
switch callbackQuery.Data { switch callbackQuery.Data {
case "get_usage": case "get_usage":
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.buttons.serverUsage")) t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.buttons.serverUsage"))
t.SendMsgToTgbot(chatId, t.getServerUsage()) t.getServerUsage(chatId)
case "usage_refresh":
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.successfulOperation"))
t.getServerUsage(chatId, callbackQuery.Message.GetMessageID())
case "inbounds": case "inbounds":
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.buttons.getInbounds")) t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.buttons.getInbounds"))
t.SendMsgToTgbot(chatId, t.getInboundUsages()) t.SendMsgToTgbot(chatId, t.getInboundUsages())
@@ -916,7 +919,7 @@ func (t *Tgbot) SendReport() {
t.SendMsgToTgbotAdmins(msg) t.SendMsgToTgbotAdmins(msg)
} }
info := t.getServerUsage() info := t.sendServerUsage()
t.SendMsgToTgbotAdmins(info) t.SendMsgToTgbotAdmins(info)
t.sendExhaustedToAdmins() t.sendExhaustedToAdmins()
@@ -946,10 +949,37 @@ func (t *Tgbot) sendExhaustedToAdmins() {
} }
} }
func (t *Tgbot) getServerUsage() string { func (t *Tgbot) getServerUsage(chatId int64, messageID ...int) string {
info := t.prepareServerUsageInfo()
keyboard := tu.InlineKeyboard(tu.InlineKeyboardRow(
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.refresh")).WithCallbackData(t.encodeQuery("usage_refresh"))))
if len(messageID) > 0 {
t.editMessageTgBot(chatId, messageID[0], info, keyboard)
} else {
t.SendMsgToTgbot(chatId, info, keyboard)
}
return info
}
// Send server usage without an inline keyborad
func (t *Tgbot) sendServerUsage() string {
info := t.prepareServerUsageInfo()
return info
}
func (t *Tgbot) prepareServerUsageInfo() string {
info, ipv4, ipv6 := "", "", "" info, ipv4, ipv6 := "", "", ""
// get latest status of server
t.lastStatus = t.serverService.GetStatus(t.lastStatus)
onlines := p.GetOnlineClients()
info += t.I18nBot("tgbot.messages.hostname", "Hostname=="+hostname) info += t.I18nBot("tgbot.messages.hostname", "Hostname=="+hostname)
info += t.I18nBot("tgbot.messages.version", "Version=="+config.GetVersion()) info += t.I18nBot("tgbot.messages.version", "Version=="+config.GetVersion())
info += t.I18nBot("tgbot.messages.xrayVersion", "XrayVersion=="+fmt.Sprint(t.lastStatus.Xray.Version))
// get ip address // get ip address
netInterfaces, err := net.Interfaces() netInterfaces, err := net.Interfaces()
@@ -978,9 +1008,6 @@ func (t *Tgbot) getServerUsage() string {
info += t.I18nBot("tgbot.messages.ipv6", "IPv6=="+ipv6) info += t.I18nBot("tgbot.messages.ipv6", "IPv6=="+ipv6)
} }
// get latest status of server
t.lastStatus = t.serverService.GetStatus(t.lastStatus)
onlines := p.GetOnlineClients()
info += t.I18nBot("tgbot.messages.serverUpTime", "UpTime=="+strconv.FormatUint(t.lastStatus.Uptime/86400, 10), "Unit=="+t.I18nBot("tgbot.days")) info += t.I18nBot("tgbot.messages.serverUpTime", "UpTime=="+strconv.FormatUint(t.lastStatus.Uptime/86400, 10), "Unit=="+t.I18nBot("tgbot.days"))
info += t.I18nBot("tgbot.messages.serverLoad", "Load1=="+strconv.FormatFloat(t.lastStatus.Loads[0], 'f', 2, 64), "Load2=="+strconv.FormatFloat(t.lastStatus.Loads[1], 'f', 2, 64), "Load3=="+strconv.FormatFloat(t.lastStatus.Loads[2], 'f', 2, 64)) info += t.I18nBot("tgbot.messages.serverLoad", "Load1=="+strconv.FormatFloat(t.lastStatus.Loads[0], 'f', 2, 64), "Load2=="+strconv.FormatFloat(t.lastStatus.Loads[1], 'f', 2, 64), "Load3=="+strconv.FormatFloat(t.lastStatus.Loads[2], 'f', 2, 64))
info += t.I18nBot("tgbot.messages.serverMemory", "Current=="+common.FormatTraffic(int64(t.lastStatus.Mem.Current)), "Total=="+common.FormatTraffic(int64(t.lastStatus.Mem.Total))) info += t.I18nBot("tgbot.messages.serverMemory", "Current=="+common.FormatTraffic(int64(t.lastStatus.Mem.Current)), "Total=="+common.FormatTraffic(int64(t.lastStatus.Mem.Total)))

View File

@@ -5,7 +5,7 @@ import (
"x-ui/database/model" "x-ui/database/model"
sessions "github.com/Calidity/gin-sessions" "github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )

View File

@@ -36,7 +36,7 @@
"status" = "Status" "status" = "Status"
"enabled" = "Enabled" "enabled" = "Enabled"
"disabled" = "Disabled" "disabled" = "Disabled"
"depleted" = "Depleted" "depleted" = "Ended"
"depletingSoon" = "Depleting" "depletingSoon" = "Depleting"
"offline" = "Offline" "offline" = "Offline"
"online" = "Online" "online" = "Online"
@@ -549,6 +549,7 @@
"datetime" = "⏰ Date&Time: {{ .DateTime }}\r\n" "datetime" = "⏰ Date&Time: {{ .DateTime }}\r\n"
"hostname" = "💻 Host: {{ .Hostname }}\r\n" "hostname" = "💻 Host: {{ .Hostname }}\r\n"
"version" = "🚀 3X-UI Version: {{ .Version }}\r\n" "version" = "🚀 3X-UI Version: {{ .Version }}\r\n"
"xrayVersion" = "📡 Xray Version: {{ .XrayVersion }}\r\n"
"ipv6" = "🌐 IPv6: {{ .IPv6 }}\r\n" "ipv6" = "🌐 IPv6: {{ .IPv6 }}\r\n"
"ipv4" = "🌐 IPv4: {{ .IPv4 }}\r\n" "ipv4" = "🌐 IPv4: {{ .IPv4 }}\r\n"
"ip" = "🌐 IP: {{ .IP }}\r\n" "ip" = "🌐 IP: {{ .IP }}\r\n"

View File

@@ -547,6 +547,7 @@
"datetime" = "⏰ Fecha y Hora: {{ .DateTime }}\r\n" "datetime" = "⏰ Fecha y Hora: {{ .DateTime }}\r\n"
"hostname" = "💻 Nombre del Host: {{ .Hostname }}\r\n" "hostname" = "💻 Nombre del Host: {{ .Hostname }}\r\n"
"version" = "🚀 Versión de X-UI: {{ .Version }}\r\n" "version" = "🚀 Versión de X-UI: {{ .Version }}\r\n"
"xrayVersion" = "📡 Versión de Xray: {{ .XrayVersion }}\r\n"
"ipv6" = "🌐 IPv6: {{ .IPv6 }}\r\n" "ipv6" = "🌐 IPv6: {{ .IPv6 }}\r\n"
"ipv4" = "🌐 IPv4: {{ .IPv4 }}\r\n" "ipv4" = "🌐 IPv4: {{ .IPv4 }}\r\n"
"ip" = "🌐 IP: {{ .IP }}\r\n" "ip" = "🌐 IP: {{ .IP }}\r\n"

View File

@@ -549,6 +549,7 @@
"datetime" = "⏰ تاریخ‌وزمان: {{ .DateTime }}\r\n" "datetime" = "⏰ تاریخ‌وزمان: {{ .DateTime }}\r\n"
"hostname" = "💻 نام‌میزبان: {{ .Hostname }}\r\n" "hostname" = "💻 نام‌میزبان: {{ .Hostname }}\r\n"
"version" = "🚀 نسخه‌پنل: {{ .Version }}\r\n" "version" = "🚀 نسخه‌پنل: {{ .Version }}\r\n"
"xrayVersion" = "📡 نسخه‌هسته: {{ .XrayVersion }}\r\n"
"ipv6" = "🌐 IPv6: {{ .IPv6 }}\r\n" "ipv6" = "🌐 IPv6: {{ .IPv6 }}\r\n"
"ipv4" = "🌐 IPv4: {{ .IPv4 }}\r\n" "ipv4" = "🌐 IPv4: {{ .IPv4 }}\r\n"
"ip" = "🌐 آدرس‌آی‌پی: {{ .IP }}\r\n" "ip" = "🌐 آدرس‌آی‌پی: {{ .IP }}\r\n"

View File

@@ -549,6 +549,7 @@
"datetime" = "⏰ Tanggal & Waktu: {{ .DateTime }}\r\n" "datetime" = "⏰ Tanggal & Waktu: {{ .DateTime }}\r\n"
"hostname" = "💻 Host: {{ .Hostname }}\r\n" "hostname" = "💻 Host: {{ .Hostname }}\r\n"
"version" = "🚀 Versi 3X-UI: {{ .Version }}\r\n" "version" = "🚀 Versi 3X-UI: {{ .Version }}\r\n"
"xrayVersion" = "📡 Versi Xray: {{ .XrayVersion }}\r\n"
"ipv6" = "🌐 IPv6: {{ .IPv6 }}\r\n" "ipv6" = "🌐 IPv6: {{ .IPv6 }}\r\n"
"ipv4" = "🌐 IPv4: {{ .IPv4 }}\r\n" "ipv4" = "🌐 IPv4: {{ .IPv4 }}\r\n"
"ip" = "🌐 IP: {{ .IP }}\r\n" "ip" = "🌐 IP: {{ .IP }}\r\n"

View File

@@ -549,6 +549,7 @@
"datetime" = "⏰ Дата и время: {{ .DateTime }}\r\n" "datetime" = "⏰ Дата и время: {{ .DateTime }}\r\n"
"hostname" = "💻 Имя хоста: {{ .Hostname }}\r\n" "hostname" = "💻 Имя хоста: {{ .Hostname }}\r\n"
"version" = "🚀 Версия X-UI: {{ .Version }}\r\n" "version" = "🚀 Версия X-UI: {{ .Version }}\r\n"
"xrayVersion" = "📡 Версия Xray: {{ .XrayVersion }}\r\n"
"ipv6" = "🌐 IPv6: {{ .IPv6 }}\r\n" "ipv6" = "🌐 IPv6: {{ .IPv6 }}\r\n"
"ipv4" = "🌐 IPv4: {{ .IPv4 }}\r\n" "ipv4" = "🌐 IPv4: {{ .IPv4 }}\r\n"
"ip" = "🌐 IP: {{ .IP }}\r\n" "ip" = "🌐 IP: {{ .IP }}\r\n"

View File

@@ -549,6 +549,7 @@
"datetime" = "⏰ Дата й час: {{ .DateTime }}\r\n" "datetime" = "⏰ Дата й час: {{ .DateTime }}\r\n"
"hostname" = "💻 Хост: {{ .Hostname }}\r\n" "hostname" = "💻 Хост: {{ .Hostname }}\r\n"
"version" = "🚀 3X-UI Версія: {{ .Version }}\r\n" "version" = "🚀 3X-UI Версія: {{ .Version }}\r\n"
"xrayVersion" = "📡 Xray Версія: {{ .XrayVersion }}\r\n"
"ipv6" = "🌐 IPv6: {{ .IPv6 }}\r\n" "ipv6" = "🌐 IPv6: {{ .IPv6 }}\r\n"
"ipv4" = "🌐 IPv4: {{ .IPv4 }}\r\n" "ipv4" = "🌐 IPv4: {{ .IPv4 }}\r\n"
"ip" = "🌐 IP: {{ .IP }}\r\n" "ip" = "🌐 IP: {{ .IP }}\r\n"

View File

@@ -549,6 +549,7 @@
"datetime" = "⏰ Ngày-Giờ: {{ .DateTime }}\r\n" "datetime" = "⏰ Ngày-Giờ: {{ .DateTime }}\r\n"
"hostname" = "💻 Tên máy chủ: {{ .Hostname }}\r\n" "hostname" = "💻 Tên máy chủ: {{ .Hostname }}\r\n"
"version" = "🚀 Phiên bản X-UI: {{ .Version }}\r\n" "version" = "🚀 Phiên bản X-UI: {{ .Version }}\r\n"
"xrayVersion" = "📡 Phiên bản Xray: {{ .XrayVersion }}\r\n"
"ipv6" = "🌐 IPv6: {{ .IPv6 }}\r\n" "ipv6" = "🌐 IPv6: {{ .IPv6 }}\r\n"
"ipv4" = "🌐 IPv4: {{ .IPv4 }}\r\n" "ipv4" = "🌐 IPv4: {{ .IPv4 }}\r\n"
"ip" = "🌐 IP: {{ .IP }}\r\n" "ip" = "🌐 IP: {{ .IP }}\r\n"

View File

@@ -549,6 +549,7 @@
"datetime" = "⏰ 日期时间:{{ .DateTime }}\r\n" "datetime" = "⏰ 日期时间:{{ .DateTime }}\r\n"
"hostname" = "💻 主机名:{{ .Hostname }}\r\n" "hostname" = "💻 主机名:{{ .Hostname }}\r\n"
"version" = "🚀 X-UI 版本:{{ .Version }}\r\n" "version" = "🚀 X-UI 版本:{{ .Version }}\r\n"
"xrayVersion" = "📡 Xray 版本: {{ .XrayVersion }}\r\n"
"ipv6" = "🌐 IPv6{{ .IPv6 }}\r\n" "ipv6" = "🌐 IPv6{{ .IPv6 }}\r\n"
"ipv4" = "🌐 IPv4{{ .IPv4 }}\r\n" "ipv4" = "🌐 IPv4{{ .IPv4 }}\r\n"
"ip" = "🌐 IP{{ .IP }}\r\n" "ip" = "🌐 IP{{ .IP }}\r\n"

View File

@@ -24,9 +24,9 @@ import (
"x-ui/web/network" "x-ui/web/network"
"x-ui/web/service" "x-ui/web/service"
sessions "github.com/Calidity/gin-sessions"
"github.com/Calidity/gin-sessions/cookie"
"github.com/gin-contrib/gzip" "github.com/gin-contrib/gzip"
"github.com/gin-contrib/sessions"
"github.com/gin-contrib/sessions/cookie"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/robfig/cron/v3" "github.com/robfig/cron/v3"
) )
@@ -354,8 +354,6 @@ func (s *Server) Start() (err error) {
} }
s.listener = listener s.listener = listener
s.startTask()
s.httpServer = &http.Server{ s.httpServer = &http.Server{
Handler: engine, Handler: engine,
} }
@@ -364,6 +362,8 @@ func (s *Server) Start() (err error) {
s.httpServer.Serve(listener) s.httpServer.Serve(listener)
}() }()
s.startTask()
isTgbotenabled, err := s.settingService.GetTgbotenabled() isTgbotenabled, err := s.settingService.GetTgbotenabled()
if (err == nil) && (isTgbotenabled) { if (err == nil) && (isTgbotenabled) {
tgBot := s.tgbotService.NewTgbot() tgBot := s.tgbotService.NewTgbot()

41
x-ui.sh
View File

@@ -40,10 +40,14 @@ os_version=$(grep -i version_id /etc/os-release | cut -d \" -f2 | cut -d . -f1)
if [[ "${release}" == "arch" ]]; then if [[ "${release}" == "arch" ]]; then
echo "Your OS is Arch Linux" echo "Your OS is Arch Linux"
elif [[ "${release}" == "parch" ]]; then
echo "Your OS is Parch linux"
elif [[ "${release}" == "manjaro" ]]; then elif [[ "${release}" == "manjaro" ]]; then
echo "Your OS is Manjaro" echo "Your OS is Manjaro"
elif [[ "${release}" == "armbian" ]]; then elif [[ "${release}" == "armbian" ]]; then
echo "Your OS is Armbian" echo "Your OS is Armbian"
elif [[ "${release}" == "opensuse-tumbleweed" ]]; then
echo "Your OS is OpenSUSE Tumbleweed"
elif [[ "${release}" == "centos" ]]; then elif [[ "${release}" == "centos" ]]; then
if [[ ${os_version} -lt 8 ]]; then if [[ ${os_version} -lt 8 ]]; then
echo -e "${red} Please use CentOS 8 or higher ${plain}\n" && exit 1 echo -e "${red} Please use CentOS 8 or higher ${plain}\n" && exit 1
@@ -80,11 +84,13 @@ else
echo "- CentOS 8+" echo "- CentOS 8+"
echo "- Fedora 36+" echo "- Fedora 36+"
echo "- Arch Linux" echo "- Arch Linux"
echo "- Parch Linux"
echo "- Manjaro" echo "- Manjaro"
echo "- Armbian" echo "- Armbian"
echo "- AlmaLinux 9+" echo "- AlmaLinux 9+"
echo "- Rocky Linux 9+" echo "- Rocky Linux 9+"
echo "- Oracle Linux 8+" echo "- Oracle Linux 8+"
echo "- OpenSUSE Tumbleweed"
exit 1 exit 1
fi fi
@@ -591,8 +597,9 @@ open_ports() {
# Check if the firewall is inactive # Check if the firewall is inactive
if ufw status | grep -q "Status: active"; then if ufw status | grep -q "Status: active"; then
echo "firewall is already active" echo "Firewall is already active"
else else
echo "Activating firewall..."
# Open the necessary ports # Open the necessary ports
ufw allow ssh ufw allow ssh
ufw allow http ufw allow http
@@ -619,17 +626,19 @@ open_ports() {
# Split the range into start and end ports # Split the range into start and end ports
start_port=$(echo $port | cut -d'-' -f1) start_port=$(echo $port | cut -d'-' -f1)
end_port=$(echo $port | cut -d'-' -f2) end_port=$(echo $port | cut -d'-' -f2)
# Loop through the range and open each port ufw allow $start_port:$end_port/tcp
for ((i = start_port; i <= end_port; i++)); do ufw allow $start_port:$end_port/udp
ufw allow $i
done
else else
ufw allow "$port" ufw allow "$port"
fi fi
done done
# Confirm that the ports are open # Confirm that the ports are open
ufw status | grep $ports echo "The following ports are now open:"
ufw status | grep "ALLOW" | grep -Eo "[0-9]+(/[a-z]+)?"
echo "Firewall status:"
ufw status verbose
} }
delete_ports() { delete_ports() {
@@ -649,18 +658,28 @@ delete_ports() {
# Split the range into start and end ports # Split the range into start and end ports
start_port=$(echo $port | cut -d'-' -f1) start_port=$(echo $port | cut -d'-' -f1)
end_port=$(echo $port | cut -d'-' -f2) end_port=$(echo $port | cut -d'-' -f2)
# Loop through the range and delete each port # Delete the port range
for ((i = start_port; i <= end_port; i++)); do ufw delete allow $start_port:$end_port/tcp
ufw delete allow $i ufw delete allow $start_port:$end_port/udp
done
else else
ufw delete allow "$port" ufw delete allow "$port"
fi fi
done done
# Confirm that the ports are deleted # Confirm that the ports are deleted
echo "Deleted the specified ports:" echo "Deleted the specified ports:"
ufw status | grep $ports for port in "${PORT_LIST[@]}"; do
if [[ $port == *-* ]]; then
start_port=$(echo $port | cut -d'-' -f1)
end_port=$(echo $port | cut -d'-' -f2)
# Check if the port range has been successfully deleted
(ufw status | grep -q "$start_port:$end_port") || echo "$start_port-$end_port"
else
# Check if the individual port has been successfully deleted
(ufw status | grep -q "$port") || echo "$port"
fi
done
} }
update_geo() { update_geo() {