mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-03-20 01:25:49 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f9c703ea44 |
33
.github/workflows/docker.yml
vendored
33
.github/workflows/docker.yml
vendored
@@ -1,4 +1,5 @@
|
|||||||
name: Release 3X-UI for Docker
|
name: Release 3X-UI for Docker
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
@@ -6,12 +7,26 @@ on:
|
|||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build_and_push:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check out the code
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4.1.1
|
uses: actions/checkout@v4.1.1
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
|
||||||
|
- name: Docker meta
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@v5
|
||||||
|
with:
|
||||||
|
images: |
|
||||||
|
ghcr.io/mhsanaei/3x-ui
|
||||||
|
tags: |
|
||||||
|
type=ref,event=branch
|
||||||
|
type=ref,event=tag
|
||||||
|
type=pep440,pattern={{version}}
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3.0.0
|
uses: docker/setup-qemu-action@v3.0.0
|
||||||
|
|
||||||
@@ -22,20 +37,14 @@ jobs:
|
|||||||
uses: docker/login-action@v3.0.0
|
uses: docker/login-action@v3.0.0
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.repository_owner }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Docker meta
|
- name: Build and push
|
||||||
id: meta
|
|
||||||
uses: docker/metadata-action@v5.5.1
|
|
||||||
with:
|
|
||||||
images: ghcr.io/${{ github.repository }}
|
|
||||||
|
|
||||||
- name: Build and push Docker image
|
|
||||||
uses: docker/build-push-action@v5.1.0
|
uses: docker/build-push-action@v5.1.0
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
push: ${{ github.event_name != 'pull_request' }}
|
push: true
|
||||||
platforms: linux/amd64, linux/arm64/v8, linux/arm/v7, linux/arm/v6, linux/386
|
platforms: linux/amd64,linux/arm64/v8,linux/arm/v7,linux/arm/v6,linux/386
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
@@ -28,14 +28,12 @@ WORKDIR /app
|
|||||||
RUN apk add --no-cache --update \
|
RUN apk add --no-cache --update \
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
tzdata \
|
tzdata \
|
||||||
fail2ban \
|
fail2ban
|
||||||
bash
|
|
||||||
|
|
||||||
COPY --from=builder /app/build/ /app/
|
COPY --from=builder /app/build/ /app/
|
||||||
COPY --from=builder /app/DockerEntrypoint.sh /app/
|
COPY --from=builder /app/DockerEntrypoint.sh /app/
|
||||||
COPY --from=builder /app/x-ui.sh /usr/bin/x-ui
|
COPY --from=builder /app/x-ui.sh /usr/bin/x-ui
|
||||||
|
|
||||||
|
|
||||||
# Configure fail2ban
|
# Configure fail2ban
|
||||||
RUN rm -f /etc/fail2ban/jail.d/alpine-ssh.conf \
|
RUN rm -f /etc/fail2ban/jail.d/alpine-ssh.conf \
|
||||||
&& cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local \
|
&& cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local \
|
||||||
@@ -49,5 +47,4 @@ RUN chmod +x \
|
|||||||
/usr/bin/x-ui
|
/usr/bin/x-ui
|
||||||
|
|
||||||
VOLUME [ "/etc/x-ui" ]
|
VOLUME [ "/etc/x-ui" ]
|
||||||
CMD [ "./x-ui" ]
|
|
||||||
ENTRYPOINT [ "/app/DockerEntrypoint.sh" ]
|
ENTRYPOINT [ "/app/DockerEntrypoint.sh" ]
|
||||||
|
|||||||
66
README.md
66
README.md
@@ -25,39 +25,11 @@ 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.1.3`:
|
To install your desired version, add the version to the end of the installation command. e.g., ver `v2.1.2`:
|
||||||
|
|
||||||
```
|
```
|
||||||
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) v2.1.3
|
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) v2.1.2
|
||||||
```
|
```
|
||||||
|
|
||||||
## SSL Certificate
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>Click for SSL Certificate</summary>
|
|
||||||
|
|
||||||
### Cloudflare
|
|
||||||
|
|
||||||
The Management script has a built-in SSL certificate application for Cloudflare. To use this script to apply for a certificate, you need the following:
|
|
||||||
|
|
||||||
- Cloudflare registered email
|
|
||||||
- Cloudflare Global API Key
|
|
||||||
- The domain name has been resolved to the current server through cloudflare
|
|
||||||
|
|
||||||
**1:** Run the`x-ui`command on the terminal, then choose `Cloudflare SSL Certificate`.
|
|
||||||
|
|
||||||
|
|
||||||
### Certbot
|
|
||||||
```
|
|
||||||
apt-get install certbot -y
|
|
||||||
certbot certonly --standalone --agree-tos --register-unsafely-without-email -d yourdomain.com
|
|
||||||
certbot renew --dry-run
|
|
||||||
```
|
|
||||||
|
|
||||||
***Tip:*** *Certbot is also built into the Management script. You can run the `x-ui` command, then choose `SSL Certificate Management`.*
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
## Manual Install & Upgrade
|
## Manual Install & Upgrade
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
@@ -229,6 +201,34 @@ Supports a variety of different architectures and devices. Here are some of the
|
|||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
|
||||||
|
## SSL Certificate
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Click for SSL Certificate</summary>
|
||||||
|
|
||||||
|
### Cloudflare
|
||||||
|
|
||||||
|
The Management script has a built-in SSL certificate application for Cloudflare. To use this script to apply for a certificate, you need the following:
|
||||||
|
|
||||||
|
- Cloudflare registered email
|
||||||
|
- Cloudflare Global API Key
|
||||||
|
- The domain name has been resolved to the current server through cloudflare
|
||||||
|
|
||||||
|
**1:** Run the`x-ui`command on the terminal, then choose `Cloudflare SSL Certificate`.
|
||||||
|
|
||||||
|
|
||||||
|
### Certbot
|
||||||
|
```
|
||||||
|
apt-get install certbot -y
|
||||||
|
certbot certonly --standalone --agree-tos --register-unsafely-without-email -d yourdomain.com
|
||||||
|
certbot renew --dry-run
|
||||||
|
```
|
||||||
|
|
||||||
|
***Tip:*** *Certbot is also built into the Management script. You can run the `x-ui` command, then choose `SSL Certificate Management`.*
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
## [WARP Configuration](https://gitlab.com/fscarmen/warp)
|
## [WARP Configuration](https://gitlab.com/fscarmen/warp)
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
@@ -281,13 +281,13 @@ If you want to use routing to WARP before v2.1.0 follow steps as below:
|
|||||||
2. Select `IP Limit Management`.
|
2. Select `IP Limit Management`.
|
||||||
3. Choose the appropriate options based on your needs.
|
3. Choose the appropriate options based on your needs.
|
||||||
|
|
||||||
- make sure you have ./access.log on your Xray Configuration after v2.1.3 we have an option for it
|
- make sure you have access.log on your Xray Configuration
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
"log": {
|
"log": {
|
||||||
|
"loglevel": "warning",
|
||||||
"access": "./access.log",
|
"access": "./access.log",
|
||||||
"dnsLog": false,
|
"error": "./error.log"
|
||||||
"loglevel": "warning"
|
|
||||||
},
|
},
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
2.1.3
|
2.1.2
|
||||||
@@ -21,7 +21,6 @@ var db *gorm.DB
|
|||||||
var initializers = []func() error{
|
var initializers = []func() error{
|
||||||
initUser,
|
initUser,
|
||||||
initInbound,
|
initInbound,
|
||||||
initOutbound,
|
|
||||||
initSetting,
|
initSetting,
|
||||||
initInboundClientIps,
|
initInboundClientIps,
|
||||||
initClientTraffic,
|
initClientTraffic,
|
||||||
@@ -52,10 +51,6 @@ func initInbound() error {
|
|||||||
return db.AutoMigrate(&model.Inbound{})
|
return db.AutoMigrate(&model.Inbound{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func initOutbound() error {
|
|
||||||
return db.AutoMigrate(&model.OutboundTraffics{})
|
|
||||||
}
|
|
||||||
|
|
||||||
func initSetting() error {
|
func initSetting() error {
|
||||||
return db.AutoMigrate(&model.Setting{})
|
return db.AutoMigrate(&model.Setting{})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,15 +44,6 @@ type Inbound struct {
|
|||||||
Tag string `json:"tag" form:"tag" gorm:"unique"`
|
Tag string `json:"tag" form:"tag" gorm:"unique"`
|
||||||
Sniffing string `json:"sniffing" form:"sniffing"`
|
Sniffing string `json:"sniffing" form:"sniffing"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type OutboundTraffics struct {
|
|
||||||
Id int `json:"id" form:"id" gorm:"primaryKey;autoIncrement"`
|
|
||||||
Tag string `json:"tag" form:"tag" gorm:"unique"`
|
|
||||||
Up int64 `json:"up" form:"up" gorm:"default:0"`
|
|
||||||
Down int64 `json:"down" form:"down" gorm:"default:0"`
|
|
||||||
Total int64 `json:"total" form:"total" gorm:"default:0"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type InboundClientIps struct {
|
type InboundClientIps struct {
|
||||||
Id int `json:"id" gorm:"primaryKey;autoIncrement"`
|
Id int `json:"id" gorm:"primaryKey;autoIncrement"`
|
||||||
ClientEmail string `json:"clientEmail" form:"clientEmail" gorm:"unique"`
|
ClientEmail string `json:"clientEmail" form:"clientEmail" gorm:"unique"`
|
||||||
|
|||||||
4
go.mod
4
go.mod
@@ -8,11 +8,11 @@ require (
|
|||||||
github.com/gin-gonic/gin v1.9.1
|
github.com/gin-gonic/gin v1.9.1
|
||||||
github.com/goccy/go-json v0.10.2
|
github.com/goccy/go-json v0.10.2
|
||||||
github.com/mymmrac/telego v0.28.0
|
github.com/mymmrac/telego v0.28.0
|
||||||
github.com/nicksnyder/go-i18n/v2 v2.4.0
|
github.com/nicksnyder/go-i18n/v2 v2.3.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.1.1
|
github.com/pelletier/go-toml/v2 v2.1.1
|
||||||
github.com/robfig/cron/v3 v3.0.1
|
github.com/robfig/cron/v3 v3.0.1
|
||||||
github.com/shirou/gopsutil/v3 v3.24.1
|
github.com/shirou/gopsutil/v3 v3.23.12
|
||||||
github.com/valyala/fasthttp v1.51.0
|
github.com/valyala/fasthttp v1.51.0
|
||||||
github.com/xtls/xray-core v1.8.7
|
github.com/xtls/xray-core v1.8.7
|
||||||
go.uber.org/atomic v1.11.0
|
go.uber.org/atomic v1.11.0
|
||||||
|
|||||||
9
go.sum
9
go.sum
@@ -179,8 +179,8 @@ github.com/mymmrac/telego v0.28.0 h1:DNXaYISeZw1J9oB81vCNdskLow8gCRRUJxufqLuH3XE
|
|||||||
github.com/mymmrac/telego v0.28.0/go.mod h1:oRperySNzJq8dRTl24+uBF1Uy7tlQGIjid/JQtHDsZg=
|
github.com/mymmrac/telego v0.28.0/go.mod h1:oRperySNzJq8dRTl24+uBF1Uy7tlQGIjid/JQtHDsZg=
|
||||||
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
|
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
|
||||||
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
|
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
|
||||||
github.com/nicksnyder/go-i18n/v2 v2.4.0 h1:3IcvPOAvnCKwNm0TB0dLDTuawWEj+ax/RERNC+diLMM=
|
github.com/nicksnyder/go-i18n/v2 v2.3.0 h1:2NPsCsNFCVd7i+Su0xYsBrIhS3bE2XMv5gNTft2O+PQ=
|
||||||
github.com/nicksnyder/go-i18n/v2 v2.4.0/go.mod h1:nxYSZE9M0bf3Y70gPQjN9ha7XNHX7gMc814+6wVyEI4=
|
github.com/nicksnyder/go-i18n/v2 v2.3.0/go.mod h1:nxYSZE9M0bf3Y70gPQjN9ha7XNHX7gMc814+6wVyEI4=
|
||||||
github.com/onsi/ginkgo/v2 v2.13.2 h1:Bi2gGVkfn6gQcjNjZJVO8Gf0FHzMPf2phUei9tejVMs=
|
github.com/onsi/ginkgo/v2 v2.13.2 h1:Bi2gGVkfn6gQcjNjZJVO8Gf0FHzMPf2phUei9tejVMs=
|
||||||
github.com/onsi/ginkgo/v2 v2.13.2/go.mod h1:XStQ8QcGwLyF4HdfcZB8SFOS/MWCgDuXMSBe6zrvLgM=
|
github.com/onsi/ginkgo/v2 v2.13.2/go.mod h1:XStQ8QcGwLyF4HdfcZB8SFOS/MWCgDuXMSBe6zrvLgM=
|
||||||
github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg=
|
github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg=
|
||||||
@@ -230,8 +230,8 @@ github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee/go.mod h1:qwtSXrKuJ
|
|||||||
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.1 h1:R3t6ondCEvmARp3wxODhXMTLC/klMa87h2PHUw5m7QI=
|
github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4=
|
||||||
github.com/shirou/gopsutil/v3 v3.24.1/go.mod h1:UU7a2MSBQa+kW1uuDq8DeEBS8kmrnQwsv2b5O513rwU=
|
github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM=
|
||||||
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=
|
||||||
@@ -369,6 +369,7 @@ 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.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
|
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
|
||||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
|||||||
@@ -65,16 +65,6 @@ func Infof(format string, args ...interface{}) {
|
|||||||
addToBuffer("INFO", fmt.Sprintf(format, args...))
|
addToBuffer("INFO", fmt.Sprintf(format, args...))
|
||||||
}
|
}
|
||||||
|
|
||||||
func Notice(args ...interface{}) {
|
|
||||||
logger.Notice(args...)
|
|
||||||
addToBuffer("NOTICE", fmt.Sprint(args...))
|
|
||||||
}
|
|
||||||
|
|
||||||
func Noticef(format string, args ...interface{}) {
|
|
||||||
logger.Noticef(format, args...)
|
|
||||||
addToBuffer("NOTICE", fmt.Sprintf(format, args...))
|
|
||||||
}
|
|
||||||
|
|
||||||
func Warning(args ...interface{}) {
|
func Warning(args ...interface{}) {
|
||||||
logger.Warning(args...)
|
logger.Warning(args...)
|
||||||
addToBuffer("WARNING", fmt.Sprint(args...))
|
addToBuffer("WARNING", fmt.Sprint(args...))
|
||||||
|
|||||||
@@ -861,13 +861,13 @@ Outbound.SocksSettings = class extends CommonClass {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static fromJson(json={}) {
|
static fromJson(json={}) {
|
||||||
let servers = json.servers;
|
servers = json.servers;
|
||||||
if(ObjectUtil.isArrEmpty(servers)) servers=[{users: [{}]}];
|
if(ObjectUtil.isArrEmpty(servers)) servers=[{users: [{}]}];
|
||||||
return new Outbound.SocksSettings(
|
return new Outbound.SocksSettings(
|
||||||
servers[0].address,
|
servers[0].address,
|
||||||
servers[0].port,
|
servers[0].port,
|
||||||
ObjectUtil.isArrEmpty(servers[0].users) ? '' : servers[0].users[0].user,
|
ObjectUtil.isArrEmpty(servers[0].users) ? '' : servers[0].users[0].user,
|
||||||
ObjectUtil.isArrEmpty(servers[0].users) ? '' : servers[0].users[0].pass,
|
ObjectUtil.isArrEmpty(servers[0].pass) ? '' : servers[0].users[0].pass,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -891,13 +891,13 @@ Outbound.HttpSettings = class extends CommonClass {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static fromJson(json={}) {
|
static fromJson(json={}) {
|
||||||
let servers = json.servers;
|
servers = json.servers;
|
||||||
if(ObjectUtil.isArrEmpty(servers)) servers=[{users: [{}]}];
|
if(ObjectUtil.isArrEmpty(servers)) servers=[{users: [{}]}];
|
||||||
return new Outbound.HttpSettings(
|
return new Outbound.HttpSettings(
|
||||||
servers[0].address,
|
servers[0].address,
|
||||||
servers[0].port,
|
servers[0].port,
|
||||||
ObjectUtil.isArrEmpty(servers[0].users) ? '' : servers[0].users[0].user,
|
ObjectUtil.isArrEmpty(servers[0].users) ? '' : servers[0].users[0].user,
|
||||||
ObjectUtil.isArrEmpty(servers[0].users) ? '' : servers[0].users[0].pass,
|
ObjectUtil.isArrEmpty(servers[0].pass) ? '' : servers[0].users[0].pass,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -914,8 +914,8 @@ Outbound.HttpSettings = class extends CommonClass {
|
|||||||
|
|
||||||
Outbound.WireguardSettings = class extends CommonClass {
|
Outbound.WireguardSettings = class extends CommonClass {
|
||||||
constructor(
|
constructor(
|
||||||
mtu=1420, secretKey='',
|
mtu=1420, secretKey=Wireguard.generateKeypair().privateKey,
|
||||||
address=[''], workers=2, domainStrategy='', reserved='',
|
address=[''], workers=2, domainStrategy='ForceIPv6v4', reserved='',
|
||||||
peers=[new Outbound.WireguardSettings.Peer()], kernelMode=false) {
|
peers=[new Outbound.WireguardSettings.Peer()], kernelMode=false) {
|
||||||
super();
|
super();
|
||||||
this.mtu = mtu;
|
this.mtu = mtu;
|
||||||
@@ -965,7 +965,7 @@ Outbound.WireguardSettings = class extends CommonClass {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Outbound.WireguardSettings.Peer = class extends CommonClass {
|
Outbound.WireguardSettings.Peer = class extends CommonClass {
|
||||||
constructor(publicKey='', psk='', allowedIPs=['0.0.0.0/0','::/0'], endpoint='', keepAlive=0) {
|
constructor(publicKey=Wireguard.generateKeypair().publicKey, psk='', allowedIPs=['0.0.0.0/0','::/0'], endpoint='', keepAlive=0) {
|
||||||
super();
|
super();
|
||||||
this.publicKey = publicKey;
|
this.publicKey = publicKey;
|
||||||
this.psk = psk;
|
this.psk = psk;
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ type XraySettingController struct {
|
|||||||
XraySettingService service.XraySettingService
|
XraySettingService service.XraySettingService
|
||||||
SettingService service.SettingService
|
SettingService service.SettingService
|
||||||
InboundService service.InboundService
|
InboundService service.InboundService
|
||||||
OutboundService service.OutboundService
|
|
||||||
XrayService service.XrayService
|
XrayService service.XrayService
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,7 +27,6 @@ func (a *XraySettingController) initRouter(g *gin.RouterGroup) {
|
|||||||
g.GET("/getXrayResult", a.getXrayResult)
|
g.GET("/getXrayResult", a.getXrayResult)
|
||||||
g.GET("/getDefaultJsonConfig", a.getDefaultXrayConfig)
|
g.GET("/getDefaultJsonConfig", a.getDefaultXrayConfig)
|
||||||
g.POST("/warp/:action", a.warp)
|
g.POST("/warp/:action", a.warp)
|
||||||
g.GET("/getOutboundsTraffic", a.getOutboundsTraffic)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *XraySettingController) getXraySetting(c *gin.Context) {
|
func (a *XraySettingController) getXraySetting(c *gin.Context) {
|
||||||
@@ -86,12 +84,3 @@ func (a *XraySettingController) warp(c *gin.Context) {
|
|||||||
|
|
||||||
jsonObj(c, resp, err)
|
jsonObj(c, resp, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *XraySettingController) getOutboundsTraffic(c *gin.Context) {
|
|
||||||
outboundsTraffic, err := a.OutboundService.GetOutboundsTraffic()
|
|
||||||
if err != nil {
|
|
||||||
jsonMsg(c, "Error getting traffics", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
jsonObj(c, outboundsTraffic, nil)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
<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 size="small" @click="inbound.stream.ws.addHeader()">+</a-button>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item :wrapper-col="{span:24}">
|
<a-form-item :wrapper-col="{span:24}">
|
||||||
<a-input-group compact v-for="(header, index) in inbound.stream.ws.headers">
|
<a-input-group compact v-for="(header, index) in inbound.stream.ws.headers">
|
||||||
|
|||||||
@@ -140,7 +140,7 @@
|
|||||||
mtu: 1420,
|
mtu: 1420,
|
||||||
secretKey: warpModal.warpData.private_key,
|
secretKey: warpModal.warpData.private_key,
|
||||||
address: Object.values(config.interface.addresses),
|
address: Object.values(config.interface.addresses),
|
||||||
domainStrategy: 'ForceIP',
|
domainStrategy: 'ForceIPv6v4',
|
||||||
peers: [{
|
peers: [{
|
||||||
publicKey: peer.public_key,
|
publicKey: peer.public_key,
|
||||||
endpoint: peer.endpoint.host,
|
endpoint: peer.endpoint.host,
|
||||||
|
|||||||
@@ -147,40 +147,6 @@
|
|||||||
</template>
|
</template>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
<a-row style="padding: 20px">
|
|
||||||
<a-col :lg="24" :xl="12">
|
|
||||||
<a-list-item-meta
|
|
||||||
title='{{ i18n "pages.xray.logLevel" }}'
|
|
||||||
description='{{ i18n "pages.xray.logLevelDesc" }}'/>
|
|
||||||
</a-col>
|
|
||||||
<a-col :lg="24" :xl="12">
|
|
||||||
<template>
|
|
||||||
<a-select
|
|
||||||
v-model="setLogLevel"
|
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme"
|
|
||||||
style="width: 100%">
|
|
||||||
<a-select-option v-for="s in logLevel" :value="s">[[ s ]]</a-select-option>
|
|
||||||
</a-select>
|
|
||||||
</template>
|
|
||||||
</a-col>
|
|
||||||
</a-row>
|
|
||||||
<a-row style="padding: 20px">
|
|
||||||
<a-col :lg="24" :xl="12">
|
|
||||||
<a-list-item-meta
|
|
||||||
title='{{ i18n "pages.xray.accessLog" }}'
|
|
||||||
description='{{ i18n "pages.xray.accessLogDesc" }}'/>
|
|
||||||
</a-col>
|
|
||||||
<a-col :lg="24" :xl="12">
|
|
||||||
<template>
|
|
||||||
<a-select
|
|
||||||
v-model="setAccessLog"
|
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme"
|
|
||||||
style="width: 100%">
|
|
||||||
<a-select-option v-for="s in access" :value="s">[[ s ]]</a-select-option>
|
|
||||||
</a-select>
|
|
||||||
</template>
|
|
||||||
</a-col>
|
|
||||||
</a-row>
|
|
||||||
</a-list-item>
|
</a-list-item>
|
||||||
</a-collapse-panel>
|
</a-collapse-panel>
|
||||||
<a-collapse-panel header='{{ i18n "pages.xray.blockConfigs"}}'>
|
<a-collapse-panel header='{{ i18n "pages.xray.blockConfigs"}}'>
|
||||||
@@ -375,15 +341,8 @@
|
|||||||
</a-table>
|
</a-table>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
<a-tab-pane key="tpl-3" tab='{{ i18n "pages.xray.Outbounds"}}' style="padding-top: 20px;" force-render="true">
|
<a-tab-pane key="tpl-3" tab='{{ i18n "pages.xray.Outbounds"}}' style="padding-top: 20px;" force-render="true">
|
||||||
<a-row>
|
<a-button type="primary" icon="plus" @click="addOutbound()" style="margin-bottom: 10px;">{{ i18n "pages.xray.outbound.addOutbound" }}</a-button>
|
||||||
<a-col :xs="12" :sm="12" :lg="12">
|
<a-button type="primary" @click="showWarp()" style="margin-bottom: 10px;">WARP</a-button>
|
||||||
<a-button type="primary" icon="plus" @click="addOutbound()" style="margin-bottom: 10px;">{{ i18n "pages.xray.outbound.addOutbound" }}</a-button>
|
|
||||||
<a-button type="primary" @click="showWarp()" style="margin-bottom: 10px;">WARP</a-button>
|
|
||||||
</a-col>
|
|
||||||
<a-col :xs="12" :sm="12" :lg="12" style="text-align: right;">
|
|
||||||
<a-icon type="sync" :spin="refreshing" @click="refreshOutboundTraffic()" style="margin: 0 5px;"/>
|
|
||||||
</a-col>
|
|
||||||
</a-row>
|
|
||||||
<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"
|
||||||
@@ -419,9 +378,6 @@
|
|||||||
<a-tag style="margin:0;" v-if="outbound.streamSettings.security=='reality'" color="green">reality</a-tag>
|
<a-tag style="margin:0;" v-if="outbound.streamSettings.security=='reality'" color="green">reality</a-tag>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
<template slot="traffic" slot-scope="text, outbound, index">
|
|
||||||
<a-tag color="green">[[ findOutboundTraffic(outbound) ]]</a-tag>
|
|
||||||
</template>
|
|
||||||
</a-table>
|
</a-table>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
<a-tab-pane key="tpl-4" tab='{{ i18n "pages.xray.outbound.reverse"}}' style="padding-top: 20px;" force-render="true">
|
<a-tab-pane key="tpl-4" tab='{{ i18n "pages.xray.outbound.reverse"}}' style="padding-top: 20px;" force-render="true">
|
||||||
@@ -507,7 +463,6 @@
|
|||||||
{ title: '{{ i18n "pages.xray.outbound.tag"}}', dataIndex: 'tag', align: 'center', width: 50 },
|
{ title: '{{ i18n "pages.xray.outbound.tag"}}', dataIndex: 'tag', align: 'center', width: 50 },
|
||||||
{ title: '{{ i18n "protocol"}}', align: 'center', width: 50, scopedSlots: { customRender: 'protocol' } },
|
{ title: '{{ i18n "protocol"}}', align: 'center', width: 50, scopedSlots: { customRender: 'protocol' } },
|
||||||
{ title: '{{ i18n "pages.xray.outbound.address"}}', align: 'center', width: 50, scopedSlots: { customRender: 'address' } },
|
{ title: '{{ i18n "pages.xray.outbound.address"}}', align: 'center', width: 50, scopedSlots: { customRender: 'address' } },
|
||||||
{ title: '{{ i18n "pages.inbounds.traffic" }}', align: 'center', width: 50, scopedSlots: { customRender: 'traffic' } },
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const reverseColumns = [
|
const reverseColumns = [
|
||||||
@@ -528,9 +483,7 @@
|
|||||||
oldXraySetting: '',
|
oldXraySetting: '',
|
||||||
xraySetting: '',
|
xraySetting: '',
|
||||||
inboundTags: [],
|
inboundTags: [],
|
||||||
outboundsTraffic: [],
|
|
||||||
saveBtnDisable: true,
|
saveBtnDisable: true,
|
||||||
refreshing: false,
|
|
||||||
restartResult: '',
|
restartResult: '',
|
||||||
isMobile: window.innerWidth <= 768,
|
isMobile: window.innerWidth <= 768,
|
||||||
advSettings: 'xraySetting',
|
advSettings: 'xraySetting',
|
||||||
@@ -568,8 +521,6 @@
|
|||||||
protocol: "freedom"
|
protocol: "freedom"
|
||||||
},
|
},
|
||||||
routingDomainStrategies: ["AsIs", "IPIfNonMatch", "IPOnDemand"],
|
routingDomainStrategies: ["AsIs", "IPIfNonMatch", "IPOnDemand"],
|
||||||
logLevel: ["none" , "debug" , "info" , "warning", "error"],
|
|
||||||
access: ["none" , "./access.log" ],
|
|
||||||
settingsData: {
|
settingsData: {
|
||||||
protocols: {
|
protocols: {
|
||||||
bittorrent: ["bittorrent"],
|
bittorrent: ["bittorrent"],
|
||||||
@@ -630,12 +581,6 @@
|
|||||||
loading(spinning = true) {
|
loading(spinning = true) {
|
||||||
this.spinning = spinning;
|
this.spinning = spinning;
|
||||||
},
|
},
|
||||||
async getOutboundsTraffic() {
|
|
||||||
const msg = await HttpUtil.get("/panel/xray/getOutboundsTraffic");
|
|
||||||
if (msg.success) {
|
|
||||||
this.outboundsTraffic = msg.obj;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async getXraySetting() {
|
async getXraySetting() {
|
||||||
this.loading(true);
|
this.loading(true);
|
||||||
const msg = await HttpUtil.post("/panel/xray/");
|
const msg = await HttpUtil.post("/panel/xray/");
|
||||||
@@ -814,14 +759,6 @@
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
findOutboundTraffic(o) {
|
|
||||||
for (const otraffic of this.outboundsTraffic) {
|
|
||||||
if (otraffic.tag == o.tag) {
|
|
||||||
return sizeFormat(otraffic.up) + ' / ' + sizeFormat(otraffic.down);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return sizeFormat(0) + ' / ' + sizeFormat(0);
|
|
||||||
},
|
|
||||||
findOutboundAddress(o) {
|
findOutboundAddress(o) {
|
||||||
serverObj = null;
|
serverObj = null;
|
||||||
switch(o.protocol){
|
switch(o.protocol){
|
||||||
@@ -879,22 +816,6 @@
|
|||||||
outbounds.splice(index,1);
|
outbounds.splice(index,1);
|
||||||
this.outboundSettings = JSON.stringify(outbounds);
|
this.outboundSettings = JSON.stringify(outbounds);
|
||||||
},
|
},
|
||||||
async refreshOutboundTraffic() {
|
|
||||||
if (!this.refreshing) {
|
|
||||||
this.refreshing = true;
|
|
||||||
await this.getOutboundsTraffic();
|
|
||||||
|
|
||||||
data = []
|
|
||||||
if (this.templateSettings != null) {
|
|
||||||
this.templateSettings.outbounds.forEach((o, index) => {
|
|
||||||
data.push({'key': index, ...o});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.outboundData = data;
|
|
||||||
this.refreshing = false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
addReverse(){
|
addReverse(){
|
||||||
reverseModal.show({
|
reverseModal.show({
|
||||||
title: '{{ i18n "pages.xray.outbound.addReverse"}}',
|
title: '{{ i18n "pages.xray.outbound.addReverse"}}',
|
||||||
@@ -1028,7 +949,6 @@
|
|||||||
async mounted() {
|
async mounted() {
|
||||||
await this.getXraySetting();
|
await this.getXraySetting();
|
||||||
await this.getXrayResult();
|
await this.getXrayResult();
|
||||||
await this.getOutboundsTraffic();
|
|
||||||
while (true) {
|
while (true) {
|
||||||
await PromiseUtil.sleep(800);
|
await PromiseUtil.sleep(800);
|
||||||
this.saveBtnDisable = this.oldXraySetting === this.xraySetting;
|
this.saveBtnDisable = this.oldXraySetting === this.xraySetting;
|
||||||
@@ -1145,28 +1065,6 @@
|
|||||||
this.templateSettings = newTemplateSettings;
|
this.templateSettings = newTemplateSettings;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
setLogLevel: {
|
|
||||||
get: function () {
|
|
||||||
if (!this.templateSettings || !this.templateSettings.log || !this.templateSettings.log.loglevel) return "warning";
|
|
||||||
return this.templateSettings.log.loglevel;
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
newTemplateSettings = this.templateSettings;
|
|
||||||
newTemplateSettings.log.loglevel = newValue;
|
|
||||||
this.templateSettings = newTemplateSettings;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
setAccessLog: {
|
|
||||||
get: function () {
|
|
||||||
if (!this.templateSettings || !this.templateSettings.log || !this.templateSettings.log.access) return "none";
|
|
||||||
return this.templateSettings.log.access;
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
newTemplateSettings = this.templateSettings;
|
|
||||||
newTemplateSettings.log.access = newValue;
|
|
||||||
this.templateSettings = newTemplateSettings;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
blockedIPs: {
|
blockedIPs: {
|
||||||
get: function () {
|
get: function () {
|
||||||
return this.templateRuleGetter({ outboundTag: "blocked", property: "ip" });
|
return this.templateRuleGetter({ outboundTag: "blocked", property: "ip" });
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
package job
|
package job
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io"
|
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
@@ -94,34 +92,24 @@ func (j *CheckClientIpJob) checkFail2BanInstalled() {
|
|||||||
|
|
||||||
func (j *CheckClientIpJob) processLogFile() {
|
func (j *CheckClientIpJob) processLogFile() {
|
||||||
accessLogPath := xray.GetAccessLogPath()
|
accessLogPath := xray.GetAccessLogPath()
|
||||||
|
|
||||||
if accessLogPath == "none" {
|
|
||||||
logger.Warning("Access log is set to 'none' check your Xray Configs")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if accessLogPath == "" {
|
if accessLogPath == "" {
|
||||||
logger.Warning("Access log doesn't exist in your Xray Configs")
|
logger.Warning("access.log doesn't exist in your config.json")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
file, err := os.Open(accessLogPath)
|
data, err := os.ReadFile(accessLogPath)
|
||||||
j.checkError(err)
|
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
InboundClientIps := make(map[string][]string)
|
InboundClientIps := make(map[string][]string)
|
||||||
|
j.checkError(err)
|
||||||
|
|
||||||
scanner := bufio.NewScanner(file)
|
lines := strings.Split(string(data), "\n")
|
||||||
for scanner.Scan() {
|
for _, line := range lines {
|
||||||
line := scanner.Text()
|
ipRegx, _ := regexp.Compile(`[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+`)
|
||||||
|
|
||||||
ipRegx, _ := regexp.Compile(`(\d+\.\d+\.\d+\.\d+).* accepted`)
|
|
||||||
emailRegx, _ := regexp.Compile(`email:.+`)
|
emailRegx, _ := regexp.Compile(`email:.+`)
|
||||||
|
|
||||||
matches := ipRegx.FindStringSubmatch(line)
|
matchesIp := ipRegx.FindString(line)
|
||||||
if len(matches) > 1 {
|
if len(matchesIp) > 0 {
|
||||||
ip := matches[1]
|
ip := string(matchesIp)
|
||||||
if ip == "127.0.0.1" || ip == "[::1]" {
|
if ip == "127.0.0.1" || ip == "1.1.1.1" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,14 +124,13 @@ func (j *CheckClientIpJob) processLogFile() {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
InboundClientIps[matchesEmail] = append(InboundClientIps[matchesEmail], ip)
|
InboundClientIps[matchesEmail] = append(InboundClientIps[matchesEmail], ip)
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
InboundClientIps[matchesEmail] = append(InboundClientIps[matchesEmail], ip)
|
InboundClientIps[matchesEmail] = append(InboundClientIps[matchesEmail], ip)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
j.checkError(scanner.Err())
|
|
||||||
|
|
||||||
shouldCleanLog := false
|
shouldCleanLog := false
|
||||||
|
|
||||||
for clientEmail, ips := range InboundClientIps {
|
for clientEmail, ips := range InboundClientIps {
|
||||||
@@ -154,6 +141,7 @@ func (j *CheckClientIpJob) processLogFile() {
|
|||||||
} else {
|
} else {
|
||||||
shouldCleanLog = j.updateInboundClientIps(inboundClientIps, clientEmail, ips)
|
shouldCleanLog = j.updateInboundClientIps(inboundClientIps, clientEmail, ips)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// added delay before cleaning logs to reduce chance of logging IP that already has been banned
|
// added delay before cleaning logs to reduce chance of logging IP that already has been banned
|
||||||
@@ -163,17 +151,13 @@ func (j *CheckClientIpJob) processLogFile() {
|
|||||||
// copy access log to persistent file
|
// copy access log to persistent file
|
||||||
logAccessP, err := os.OpenFile(xray.GetAccessPersistentLogPath(), os.O_CREATE|os.O_APPEND|os.O_RDWR, 0644)
|
logAccessP, err := os.OpenFile(xray.GetAccessPersistentLogPath(), os.O_CREATE|os.O_APPEND|os.O_RDWR, 0644)
|
||||||
j.checkError(err)
|
j.checkError(err)
|
||||||
|
input, err := os.ReadFile(accessLogPath)
|
||||||
|
j.checkError(err)
|
||||||
|
if _, err := logAccessP.Write(input); err != nil {
|
||||||
|
j.checkError(err)
|
||||||
|
}
|
||||||
defer logAccessP.Close()
|
defer logAccessP.Close()
|
||||||
|
|
||||||
// reopen the access log file for reading
|
|
||||||
file, err := os.Open(accessLogPath)
|
|
||||||
j.checkError(err)
|
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
// copy access log content to persistent file
|
|
||||||
_, err = io.Copy(logAccessP, file)
|
|
||||||
j.checkError(err)
|
|
||||||
|
|
||||||
// clean access log
|
// clean access log
|
||||||
if err := os.Truncate(xray.GetAccessLogPath(), 0); err != nil {
|
if err := os.Truncate(xray.GetAccessLogPath(), 0); err != nil {
|
||||||
j.checkError(err)
|
j.checkError(err)
|
||||||
|
|||||||
@@ -6,9 +6,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type XrayTrafficJob struct {
|
type XrayTrafficJob struct {
|
||||||
xrayService service.XrayService
|
xrayService service.XrayService
|
||||||
inboundService service.InboundService
|
inboundService service.InboundService
|
||||||
outboundService service.OutboundService
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewXrayTrafficJob() *XrayTrafficJob {
|
func NewXrayTrafficJob() *XrayTrafficJob {
|
||||||
@@ -25,15 +24,11 @@ func (j *XrayTrafficJob) Run() {
|
|||||||
logger.Warning("get xray traffic failed:", err)
|
logger.Warning("get xray traffic failed:", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err, needRestart0 := j.inboundService.AddTraffic(traffics, clientTraffics)
|
err, needRestart := j.inboundService.AddTraffic(traffics, clientTraffics)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warning("add inbound traffic failed:", err)
|
logger.Warning("add traffic failed:", err)
|
||||||
}
|
}
|
||||||
err, needRestart1 := j.outboundService.AddTraffic(traffics, clientTraffics)
|
if needRestart {
|
||||||
if err != nil {
|
|
||||||
logger.Warning("add outbound traffic failed:", err)
|
|
||||||
}
|
|
||||||
if needRestart0 || needRestart1 {
|
|
||||||
j.xrayService.SetToNeedRestart()
|
j.xrayService.SetToNeedRestart()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
{
|
{
|
||||||
"log": {
|
"log": {
|
||||||
"access": "none",
|
"loglevel": "warning",
|
||||||
"dnsLog": false,
|
"error": "./error.log"
|
||||||
"loglevel": "warning"
|
|
||||||
},
|
},
|
||||||
"api": {
|
"api": {
|
||||||
"tag": "api",
|
"tag": "api",
|
||||||
@@ -44,9 +43,7 @@
|
|||||||
},
|
},
|
||||||
"system": {
|
"system": {
|
||||||
"statsInboundDownlink": true,
|
"statsInboundDownlink": true,
|
||||||
"statsInboundUplink": true,
|
"statsInboundUplink": true
|
||||||
"statsOutboundDownlink": true,
|
|
||||||
"statsOutboundUplink": true
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"routing": {
|
"routing": {
|
||||||
|
|||||||
@@ -682,7 +682,7 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId strin
|
|||||||
return needRestart, tx.Save(oldInbound).Error
|
return needRestart, tx.Save(oldInbound).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InboundService) AddTraffic(traffics []*xray.Traffic, clientTraffics []*xray.ClientTraffic) (error, bool) {
|
func (s *InboundService) AddTraffic(inboundTraffics []*xray.Traffic, clientTraffics []*xray.ClientTraffic) (error, bool) {
|
||||||
var err error
|
var err error
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
tx := db.Begin()
|
tx := db.Begin()
|
||||||
@@ -694,7 +694,7 @@ func (s *InboundService) AddTraffic(traffics []*xray.Traffic, clientTraffics []*
|
|||||||
tx.Commit()
|
tx.Commit()
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
err = s.addInboundTraffic(tx, traffics)
|
err = s.addInboundTraffic(tx, inboundTraffics)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err, false
|
return err, false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,80 +0,0 @@
|
|||||||
package service
|
|
||||||
|
|
||||||
import (
|
|
||||||
"x-ui/database"
|
|
||||||
"x-ui/database/model"
|
|
||||||
"x-ui/logger"
|
|
||||||
"x-ui/xray"
|
|
||||||
|
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
|
||||||
|
|
||||||
type OutboundService struct {
|
|
||||||
xrayApi xray.XrayAPI
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *OutboundService) AddTraffic(traffics []*xray.Traffic, clientTraffics []*xray.ClientTraffic) (error, bool) {
|
|
||||||
var err error
|
|
||||||
db := database.GetDB()
|
|
||||||
tx := db.Begin()
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
if err != nil {
|
|
||||||
tx.Rollback()
|
|
||||||
} else {
|
|
||||||
tx.Commit()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
err = s.addOutboundTraffic(tx, traffics)
|
|
||||||
if err != nil {
|
|
||||||
return err, false
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *OutboundService) addOutboundTraffic(tx *gorm.DB, traffics []*xray.Traffic) error {
|
|
||||||
if len(traffics) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var err error
|
|
||||||
|
|
||||||
for _, traffic := range traffics {
|
|
||||||
if traffic.IsOutbound {
|
|
||||||
|
|
||||||
var outbound model.OutboundTraffics
|
|
||||||
|
|
||||||
err = tx.Model(&model.OutboundTraffics{}).Where("tag = ?", traffic.Tag).
|
|
||||||
FirstOrCreate(&outbound).Error
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
outbound.Tag = traffic.Tag
|
|
||||||
outbound.Up = outbound.Up + traffic.Up
|
|
||||||
outbound.Down = outbound.Down + traffic.Down
|
|
||||||
outbound.Total = outbound.Up + outbound.Down
|
|
||||||
|
|
||||||
err = tx.Save(&outbound).Error
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *OutboundService) GetOutboundsTraffic() ([]*model.OutboundTraffics, error) {
|
|
||||||
db := database.GetDB()
|
|
||||||
var traffics []*model.OutboundTraffics
|
|
||||||
|
|
||||||
err := db.Model(model.OutboundTraffics{}).Find(&traffics).Error
|
|
||||||
if err != nil {
|
|
||||||
logger.Warning(err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return traffics, nil
|
|
||||||
}
|
|
||||||
@@ -6,9 +6,9 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"slices"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"slices"
|
||||||
"time"
|
"time"
|
||||||
"x-ui/config"
|
"x-ui/config"
|
||||||
"x-ui/database"
|
"x-ui/database"
|
||||||
@@ -115,19 +115,14 @@ func (t *Tgbot) Start(i18nFS embed.FS) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Tgbot) NewBot(token string, proxyUrl string) (*telego.Bot, error) {
|
func (t *Tgbot) NewBot(token string, proxyUrl string) (*telego.Bot, error) {
|
||||||
if proxyUrl == "" {
|
if proxyUrl == "" || !strings.HasPrefix(proxyUrl, "socks5://") {
|
||||||
// No proxy URL provided, use default instance
|
logger.Warning("invalid socks5 url, start with default")
|
||||||
return telego.NewBot(token)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !strings.HasPrefix(proxyUrl, "socks5://") {
|
|
||||||
logger.Warning("Invalid socks5 URL, starting with default")
|
|
||||||
return telego.NewBot(token)
|
return telego.NewBot(token)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := url.Parse(proxyUrl)
|
_, err := url.Parse(proxyUrl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warning("Can't parse proxy URL, using default instance for tgbot:", err)
|
logger.Warning("cant parse proxy url, use default instance for tgbot:", err)
|
||||||
return telego.NewBot(token)
|
return telego.NewBot(token)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -265,7 +260,7 @@ func (t *Tgbot) answerCommand(message *telego.Message, chatId int64, isAdmin boo
|
|||||||
msg += t.I18nBot("tgbot.commands.unknown")
|
msg += t.I18nBot("tgbot.commands.unknown")
|
||||||
}
|
}
|
||||||
|
|
||||||
if msg != "" {
|
if msg != ""{
|
||||||
if onlyMessage {
|
if onlyMessage {
|
||||||
t.SendMsgToTgbot(chatId, msg)
|
t.SendMsgToTgbot(chatId, msg)
|
||||||
return
|
return
|
||||||
@@ -351,7 +346,7 @@ func (t *Tgbot) asnwerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
|
|||||||
tu.InlineKeyboardButton("40 GB").WithCallbackData(t.encodeQuery("limit_traffic_c "+email+" 40")),
|
tu.InlineKeyboardButton("40 GB").WithCallbackData(t.encodeQuery("limit_traffic_c "+email+" 40")),
|
||||||
),
|
),
|
||||||
tu.InlineKeyboardRow(
|
tu.InlineKeyboardRow(
|
||||||
tu.InlineKeyboardButton("50 GB").WithCallbackData(t.encodeQuery("limit_traffic_c "+email+" 50")),
|
tu.InlineKeyboardButton("50 GB").WithCallbackData(t.encodeQuery("limit_traffic_c "+email+" 60")),
|
||||||
tu.InlineKeyboardButton("60 GB").WithCallbackData(t.encodeQuery("limit_traffic_c "+email+" 60")),
|
tu.InlineKeyboardButton("60 GB").WithCallbackData(t.encodeQuery("limit_traffic_c "+email+" 60")),
|
||||||
tu.InlineKeyboardButton("80 GB").WithCallbackData(t.encodeQuery("limit_traffic_c "+email+" 80")),
|
tu.InlineKeyboardButton("80 GB").WithCallbackData(t.encodeQuery("limit_traffic_c "+email+" 80")),
|
||||||
),
|
),
|
||||||
@@ -1027,7 +1022,7 @@ func (t *Tgbot) getInboundUsages() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Tgbot) clientInfoMsg(traffic *xray.ClientTraffic, printEnabled bool, printOnline bool, printActive bool,
|
func (t *Tgbot) clientInfoMsg(traffic *xray.ClientTraffic, printEnabled bool, printOnline bool, printActive bool,
|
||||||
printDate bool, printTraffic bool, printRefreshed bool) string {
|
printDate bool, printTraffic bool, printRefreshed bool) string {
|
||||||
|
|
||||||
now := time.Now().Unix()
|
now := time.Now().Unix()
|
||||||
expiryTime := ""
|
expiryTime := ""
|
||||||
@@ -1386,6 +1381,7 @@ func (t *Tgbot) getExhausted(chatId int64) {
|
|||||||
output += t.I18nBot("tgbot.messages.disabled", "Disabled=="+strconv.Itoa(len(disabledClients)))
|
output += t.I18nBot("tgbot.messages.disabled", "Disabled=="+strconv.Itoa(len(disabledClients)))
|
||||||
output += t.I18nBot("tgbot.messages.depleteSoon", "Deplete=="+strconv.Itoa(exhaustedCC))
|
output += t.I18nBot("tgbot.messages.depleteSoon", "Deplete=="+strconv.Itoa(exhaustedCC))
|
||||||
|
|
||||||
|
|
||||||
if exhaustedCC > 0 {
|
if exhaustedCC > 0 {
|
||||||
output += t.I18nBot("tgbot.messages.depleteSoon", "Deplete=="+t.I18nBot("tgbot.clients"))
|
output += t.I18nBot("tgbot.messages.depleteSoon", "Deplete=="+t.I18nBot("tgbot.clients"))
|
||||||
var buttons []telego.InlineKeyboardButton
|
var buttons []telego.InlineKeyboardButton
|
||||||
@@ -1495,6 +1491,7 @@ func (t *Tgbot) onlineClients(chatId int64, messageID ...int) {
|
|||||||
keyboard := tu.InlineKeyboard(tu.InlineKeyboardRow(
|
keyboard := tu.InlineKeyboard(tu.InlineKeyboardRow(
|
||||||
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.refresh")).WithCallbackData(t.encodeQuery("onlines_refresh"))))
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.refresh")).WithCallbackData(t.encodeQuery("onlines_refresh"))))
|
||||||
|
|
||||||
|
|
||||||
if onlinesCount > 0 {
|
if onlinesCount > 0 {
|
||||||
var buttons []telego.InlineKeyboardButton
|
var buttons []telego.InlineKeyboardButton
|
||||||
for _, online := range onlines {
|
for _, online := range onlines {
|
||||||
@@ -1568,44 +1565,30 @@ func (t *Tgbot) sendBanLogs(chatId int64, dt bool) {
|
|||||||
|
|
||||||
file, err := os.Open(xray.GetIPLimitBannedPrevLogPath())
|
file, err := os.Open(xray.GetIPLimitBannedPrevLogPath())
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// Check if the file is non-empty before attempting to upload
|
document := tu.Document(
|
||||||
fileInfo, _ := file.Stat()
|
tu.ID(chatId),
|
||||||
if fileInfo.Size() > 0 {
|
tu.File(file),
|
||||||
document := tu.Document(
|
)
|
||||||
tu.ID(chatId),
|
_, err = bot.SendDocument(document)
|
||||||
tu.File(file),
|
if err != nil {
|
||||||
)
|
logger.Error("Error in uploading backup: ", err)
|
||||||
_, err = bot.SendDocument(document)
|
|
||||||
if err != nil {
|
|
||||||
logger.Error("Error in uploading IPLimitBannedPrevLog: ", err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
logger.Warning("IPLimitBannedPrevLog file is empty, not uploading.")
|
|
||||||
}
|
}
|
||||||
file.Close()
|
|
||||||
} else {
|
} else {
|
||||||
logger.Error("Error in opening IPLimitBannedPrevLog file for backup: ", err)
|
logger.Error("Error in opening db file for backup: ", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
file, err = os.Open(xray.GetIPLimitBannedLogPath())
|
file, err = os.Open(xray.GetIPLimitBannedLogPath())
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// Check if the file is non-empty before attempting to upload
|
document := tu.Document(
|
||||||
fileInfo, _ := file.Stat()
|
tu.ID(chatId),
|
||||||
if fileInfo.Size() > 0 {
|
tu.File(file),
|
||||||
document := tu.Document(
|
)
|
||||||
tu.ID(chatId),
|
_, err = bot.SendDocument(document)
|
||||||
tu.File(file),
|
if err != nil {
|
||||||
)
|
logger.Error("Error in uploading config.json: ", err)
|
||||||
_, err = bot.SendDocument(document)
|
|
||||||
if err != nil {
|
|
||||||
logger.Error("Error in uploading IPLimitBannedLog: ", err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
logger.Warning("IPLimitBannedLog file is empty, not uploading.")
|
|
||||||
}
|
}
|
||||||
file.Close()
|
|
||||||
} else {
|
} else {
|
||||||
logger.Error("Error in opening IPLimitBannedLog file for backup: ", err)
|
logger.Error("Error in opening config.json file for backup: ", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ func (s *XrayService) GetXrayConfig() (*xray.Config, error) {
|
|||||||
if !clientTraffic.Enable {
|
if !clientTraffic.Enable {
|
||||||
clients = RemoveIndex(clients, index-indexDecrease)
|
clients = RemoveIndex(clients, index-indexDecrease)
|
||||||
indexDecrease++
|
indexDecrease++
|
||||||
logger.Info("Remove Inbound User ", c["email"], " due the expire or traffic limit")
|
logger.Info("Remove Inbound User", c["email"], "due the expire or traffic limit")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -309,8 +309,8 @@
|
|||||||
"restart" = "Restart Xray"
|
"restart" = "Restart Xray"
|
||||||
"basicTemplate" = "Basics"
|
"basicTemplate" = "Basics"
|
||||||
"advancedTemplate" = "Advanced"
|
"advancedTemplate" = "Advanced"
|
||||||
"generalConfigs" = "General"
|
"generalConfigs" = "General Strategy"
|
||||||
"generalConfigsDesc" = "These options will determine general adjustments."
|
"generalConfigsDesc" = "These options will determine general strategy adjustments."
|
||||||
"blockConfigs" = "Protection Shield"
|
"blockConfigs" = "Protection Shield"
|
||||||
"blockConfigsDesc" = "These options will block traffic based on specific requested protocols and websites."
|
"blockConfigsDesc" = "These options will block traffic based on specific requested protocols and websites."
|
||||||
"blockCountryConfigs" = "Block Country"
|
"blockCountryConfigs" = "Block Country"
|
||||||
@@ -392,10 +392,6 @@
|
|||||||
"Routings" = "Routing Rules"
|
"Routings" = "Routing Rules"
|
||||||
"RoutingsDesc" = "The priority of each rule is important!"
|
"RoutingsDesc" = "The priority of each rule is important!"
|
||||||
"completeTemplate" = "All"
|
"completeTemplate" = "All"
|
||||||
"logLevel" = "Log Level"
|
|
||||||
"logLevelDesc" = "The log level for error logs, indicating the information that needs to be recorded."
|
|
||||||
"accessLog" = "Access Log"
|
|
||||||
"accessLogDesc" = "The file path for the access log. The special value 'none' disabled access logs"
|
|
||||||
|
|
||||||
[pages.xray.rules]
|
[pages.xray.rules]
|
||||||
"first" = "First"
|
"first" = "First"
|
||||||
@@ -456,7 +452,7 @@
|
|||||||
"wentWrong" = "❌ Something went wrong!"
|
"wentWrong" = "❌ Something went wrong!"
|
||||||
"noIpRecord" = "❗ No IP Record!"
|
"noIpRecord" = "❗ No IP Record!"
|
||||||
"noInbounds" = "❗ No inbound found!"
|
"noInbounds" = "❗ No inbound found!"
|
||||||
"unlimited" = "♾ Unlimited(Reset)"
|
"unlimited" = "♾ Unlimited"
|
||||||
"add" = "Add"
|
"add" = "Add"
|
||||||
"month" = "Month"
|
"month" = "Month"
|
||||||
"months" = "Months"
|
"months" = "Months"
|
||||||
|
|||||||
@@ -57,7 +57,7 @@
|
|||||||
"dashboard" = "Estado del Sistema"
|
"dashboard" = "Estado del Sistema"
|
||||||
"inbounds" = "Entradas"
|
"inbounds" = "Entradas"
|
||||||
"settings" = "Configuraciones"
|
"settings" = "Configuraciones"
|
||||||
"xray" = "Ajustes Xray"
|
"xray" = "Configuración Xray"
|
||||||
"logout" = "Cerrar Sesión"
|
"logout" = "Cerrar Sesión"
|
||||||
"link" = "Gestionar"
|
"link" = "Gestionar"
|
||||||
|
|
||||||
@@ -392,10 +392,6 @@
|
|||||||
"Routings" = "Reglas de enrutamiento"
|
"Routings" = "Reglas de enrutamiento"
|
||||||
"RoutingsDesc" = "¡La prioridad de cada regla es importante!"
|
"RoutingsDesc" = "¡La prioridad de cada regla es importante!"
|
||||||
"completeTemplate" = "Todos"
|
"completeTemplate" = "Todos"
|
||||||
"logLevel" = "Nivel de registro"
|
|
||||||
"logLevelDesc" = "El nivel de registro para registros de errores, que indica la información que debe registrarse."
|
|
||||||
"accessLog" = "Registro de acceso"
|
|
||||||
"accessLogDesc" = "La ruta del archivo para el registro de acceso. El valor especial 'ninguno' deshabilita los registros de acceso"
|
|
||||||
|
|
||||||
[pages.xray.rules]
|
[pages.xray.rules]
|
||||||
"first" = "Primero"
|
"first" = "Primero"
|
||||||
|
|||||||
@@ -392,10 +392,6 @@
|
|||||||
"Routings" = "قوانین مسیریابی"
|
"Routings" = "قوانین مسیریابی"
|
||||||
"RoutingsDesc" = "اولویت هر قانون مهم است"
|
"RoutingsDesc" = "اولویت هر قانون مهم است"
|
||||||
"completeTemplate" = "کامل"
|
"completeTemplate" = "کامل"
|
||||||
"logLevel" = "سطح گزارش"
|
|
||||||
"logLevelDesc" = "سطح گزارش برای گزارش های خطا، نشان دهنده اطلاعاتی است که باید ثبت شوند."
|
|
||||||
"accessLog" = "مسیر گزارش"
|
|
||||||
"accessLogDesc" = "مسیر فایل برای گزارش دسترسی. مقدار ویژه «هیچ» گزارشهای دسترسی را غیرفعال میکند."
|
|
||||||
|
|
||||||
[pages.xray.rules]
|
[pages.xray.rules]
|
||||||
"first" = "اولین"
|
"first" = "اولین"
|
||||||
@@ -456,7 +452,7 @@
|
|||||||
"wentWrong" = "❌ مشکلی رخ داده است!"
|
"wentWrong" = "❌ مشکلی رخ داده است!"
|
||||||
"noIpRecord" = "❗ رکورد IP یافت نشد!"
|
"noIpRecord" = "❗ رکورد IP یافت نشد!"
|
||||||
"noInbounds" = "❗ هیچ ورودی یافت نشد!"
|
"noInbounds" = "❗ هیچ ورودی یافت نشد!"
|
||||||
"unlimited" = "♾ - نامحدود(ریست)"
|
"unlimited" = "♾ نامحدود"
|
||||||
"add" = "اضافه کردن"
|
"add" = "اضافه کردن"
|
||||||
"month" = "ماه"
|
"month" = "ماه"
|
||||||
"months" = "ماهها"
|
"months" = "ماهها"
|
||||||
|
|||||||
@@ -392,10 +392,6 @@
|
|||||||
"Routings" = "Правила маршрутизации"
|
"Routings" = "Правила маршрутизации"
|
||||||
"RoutingsDesc" = "Важен приоритет каждого правила!"
|
"RoutingsDesc" = "Важен приоритет каждого правила!"
|
||||||
"completeTemplate" = "Все"
|
"completeTemplate" = "Все"
|
||||||
"logLevel" = "Уровень журнала"
|
|
||||||
"logLevelDesc" = "Уровень журнала для журналов ошибок, указывающий информацию, которую необходимо записать."
|
|
||||||
"accessLog" = "Журнал доступа"
|
|
||||||
"accessLogDesc" = "Путь к файлу журнала доступа. Специальное значение «none» отключило журналы доступа."
|
|
||||||
|
|
||||||
[pages.xray.rules]
|
[pages.xray.rules]
|
||||||
"first" = "Первый"
|
"first" = "Первый"
|
||||||
|
|||||||
@@ -392,10 +392,6 @@
|
|||||||
"Routings" = "Quy tắc định tuyến"
|
"Routings" = "Quy tắc định tuyến"
|
||||||
"RoutingsDesc" = "Mức độ ưu tiên của mỗi quy tắc đều quan trọng!"
|
"RoutingsDesc" = "Mức độ ưu tiên của mỗi quy tắc đều quan trọng!"
|
||||||
"completeTemplate" = "All"
|
"completeTemplate" = "All"
|
||||||
"logLevel" = "Mức đăng nhập"
|
|
||||||
"logLevelDesc" = "Cấp độ nhật ký cho nhật ký lỗi, cho biết thông tin cần được ghi lại."
|
|
||||||
"accessLog" = "Nhật ký truy cập"
|
|
||||||
"accessLogDesc" = "Đường dẫn tệp cho nhật ký truy cập. Nhật ký truy cập bị vô hiệu hóa có giá trị đặc biệt 'không'"
|
|
||||||
|
|
||||||
[pages.xray.rules]
|
[pages.xray.rules]
|
||||||
"first" = "Đầu tiên"
|
"first" = "Đầu tiên"
|
||||||
|
|||||||
@@ -392,10 +392,6 @@
|
|||||||
"Routings" = "路由规则"
|
"Routings" = "路由规则"
|
||||||
"RoutingsDesc" = "每条规则的优先级都很重要"
|
"RoutingsDesc" = "每条规则的优先级都很重要"
|
||||||
"completeTemplate" = "全部"
|
"completeTemplate" = "全部"
|
||||||
"logLevel" = "日志级别"
|
|
||||||
"logLevelDesc" = "错误日志的日志级别,表示需要记录的信息。"
|
|
||||||
"accessLog" = "访问日志"
|
|
||||||
"accessLogDesc" = "访问日志的文件路径。 特殊值“none”禁用访问日志"
|
|
||||||
|
|
||||||
[pages.xray.rules]
|
[pages.xray.rules]
|
||||||
"first" = "第一个"
|
"first" = "第一个"
|
||||||
|
|||||||
@@ -213,7 +213,6 @@ func (x *XrayAPI) GetTraffic(reset bool) ([]*Traffic, []*ClientTraffic, error) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
isInbound := matchs[1] == "inbound"
|
isInbound := matchs[1] == "inbound"
|
||||||
isOutbound := matchs[1] == "outbound"
|
|
||||||
tag := matchs[2]
|
tag := matchs[2]
|
||||||
isDown := matchs[3] == "downlink"
|
isDown := matchs[3] == "downlink"
|
||||||
if tag == "api" {
|
if tag == "api" {
|
||||||
@@ -222,9 +221,8 @@ func (x *XrayAPI) GetTraffic(reset bool) ([]*Traffic, []*ClientTraffic, error) {
|
|||||||
traffic, ok := tagTrafficMap[tag]
|
traffic, ok := tagTrafficMap[tag]
|
||||||
if !ok {
|
if !ok {
|
||||||
traffic = &Traffic{
|
traffic = &Traffic{
|
||||||
IsInbound: isInbound,
|
IsInbound: isInbound,
|
||||||
IsOutbound: isOutbound,
|
Tag: tag,
|
||||||
Tag: tag,
|
|
||||||
}
|
}
|
||||||
tagTrafficMap[tag] = traffic
|
tagTrafficMap[tag] = traffic
|
||||||
traffics = append(traffics, traffic)
|
traffics = append(traffics, traffic)
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ func (lw *LogWriter) Write(m []byte) (n int, err error) {
|
|||||||
// Find level in []
|
// Find level in []
|
||||||
startIndex := strings.Index(messageBody, "[")
|
startIndex := strings.Index(messageBody, "[")
|
||||||
endIndex := strings.Index(messageBody, "]")
|
endIndex := strings.Index(messageBody, "]")
|
||||||
if startIndex != -1 && endIndex != -1 && startIndex < endIndex {
|
if startIndex != -1 && endIndex != -1 {
|
||||||
level := strings.TrimSpace(messageBody[startIndex+1 : endIndex])
|
level := strings.TrimSpace(messageBody[startIndex+1 : endIndex])
|
||||||
msgBody := "XRAY: " + strings.TrimSpace(messageBody[endIndex+1:])
|
msgBody := "XRAY: " + strings.TrimSpace(messageBody[endIndex+1:])
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
package xray
|
package xray
|
||||||
|
|
||||||
type Traffic struct {
|
type Traffic struct {
|
||||||
IsInbound bool
|
IsInbound bool
|
||||||
IsOutbound bool
|
Tag string
|
||||||
Tag string
|
Up int64
|
||||||
Up int64
|
Down int64
|
||||||
Down int64
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user