From 214b217f12194b0778a70a5699518263b99d0e9a Mon Sep 17 00:00:00 2001
From: sprov <47310637+sprov065@users.noreply.github.com>
Date: Sat, 12 Jun 2021 11:26:35 +0800
Subject: [PATCH] 0.0.2
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 增加设置总流量功能,流量超出后自动禁用
- 优化部分 ui 细节
- 修复监听 ip 不为空导致无法启动 xray 的问题
- 修复二维码链接没有包含 address 的问题
---
README.md | 38 ++++++++++++++++++++++-
a.s | 0
config/config.go | 12 ++++++--
config/name | 1 +
config/version | 1 +
database/model/model.go | 12 ++++++--
main.go | 7 ++---
web/assets/js/model/models.js | 9 ++++++
web/assets/js/util/utils.js | 2 +-
web/global/global.go | 3 +-
web/html/xui/form/inbound.html | 12 ++++++++
web/html/xui/inbounds.html | 55 ++++++++++++++++++++++++----------
web/service/inbound.go | 13 ++++++++
web/web.go | 20 +++++++++----
14 files changed, 149 insertions(+), 36 deletions(-)
delete mode 100644 a.s
create mode 100644 config/name
create mode 100644 config/version
diff --git a/README.md b/README.md
index 2110bb2637..75b290d4cf 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,38 @@
# x-ui
-a web panel based on xray-core
+支持多协议多用户 xray 面板
+
+# 功能介绍
+- 系统状态监控
+- 支持多用户多协议,网页可视化操作
+- 支持的协议:vmess、vless、trojan、shadowsocks、dokodemo-door、socks、http
+- 支持配置更多传输配置
+- 账号流量统计
+- 可自定义 xray 配置模板
+- 支持 https 访问面板(自备域名 + ssl 证书)
+- 更多高级配置项,详见面板
+
+# 安装&升级
+## 测试版
+```
+bash <(curl -Ls https://raw.githubusercontent.com/sprov065/x-ui/master/install.sh) 0.0.1
+```
+
+## 建议系统
+- CentOS 7+
+- Ubuntu 16+
+- Debian 8+
+
+# 常见问题
+## 与 v2-ui 关系
+x-ui 相当于 v2-ui 的加强版,未来会加入更多功能,待 x-ui 功能稳定后,v2-ui 将不再提供更新
+
+x-ui 可与 v2-ui 并存,数据不互通,不影响对方的运行
+
+# Telegram
+群组:https://t.me/sprov_blog
+
+频道:https://t.me/sprov_channel
+
+## Stargazers over time
+
+[![Stargazers over time](https://starchart.cc/sprov065/x-ui.svg)](https://starchart.cc/sprov065/x-ui)
diff --git a/a.s b/a.s
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/config/config.go b/config/config.go
index 50f67ac84a..e1c7f911fb 100644
--- a/config/config.go
+++ b/config/config.go
@@ -1,10 +1,18 @@
package config
import (
+ _ "embed"
"fmt"
"os"
+ "strings"
)
+//go:embed version
+var version string
+
+//go:embed name
+var name string
+
type LogLevel string
const (
@@ -15,11 +23,11 @@ const (
)
func GetVersion() string {
- return "0.0.1"
+ return strings.TrimSpace(version)
}
func GetName() string {
- return "x-ui"
+ return strings.TrimSpace(name)
}
func GetLogLevel() LogLevel {
diff --git a/config/name b/config/name
new file mode 100644
index 0000000000..491114af6b
--- /dev/null
+++ b/config/name
@@ -0,0 +1 @@
+x-ui
\ No newline at end of file
diff --git a/config/version b/config/version
new file mode 100644
index 0000000000..7bcd0e3612
--- /dev/null
+++ b/config/version
@@ -0,0 +1 @@
+0.0.2
\ No newline at end of file
diff --git a/database/model/model.go b/database/model/model.go
index 1c325cfecf..bc19444598 100644
--- a/database/model/model.go
+++ b/database/model/model.go
@@ -1,6 +1,7 @@
package model
import (
+ "fmt"
"x-ui/util/json_util"
"x-ui/xray"
)
@@ -25,8 +26,9 @@ type User struct {
type Inbound struct {
Id int `json:"id" form:"id" gorm:"primaryKey;autoIncrement"`
UserId int `json:"-"`
- Up int64 `json:"up"`
- Down int64 `json:"down"`
+ Up int64 `json:"up" form:"up"`
+ Down int64 `json:"down" form:"down"`
+ Total int64 `json:"total" form:"total"`
Remark string `json:"remark" form:"remark"`
Enable bool `json:"enable" form:"enable"`
ExpiryTime int64 `json:"expiryTime" form:"expiryTime"`
@@ -42,8 +44,12 @@ type Inbound struct {
}
func (i *Inbound) GenXrayInboundConfig() *xray.InboundConfig {
+ listen := i.Listen
+ if listen != "" {
+ listen = fmt.Sprintf("\"%v\"", listen)
+ }
return &xray.InboundConfig{
- Listen: json_util.RawMessage(i.Listen),
+ Listen: json_util.RawMessage(listen),
Port: i.Port,
Protocol: string(i.Protocol),
Settings: json_util.RawMessage(i.Settings),
diff --git a/main.go b/main.go
index 4d1f771b8d..f9c2390ebd 100644
--- a/main.go
+++ b/main.go
@@ -18,9 +18,6 @@ import (
"x-ui/web/service"
)
-// this function call global.setWebServer
-func setWebServer(server global.WebServer)
-
func runWebServer() {
log.Printf("%v %v", config.GetName(), config.GetVersion())
@@ -45,7 +42,7 @@ func runWebServer() {
var server *web.Server
server = web.NewServer()
- setWebServer(server)
+ global.SetWebServer(server)
err = server.Start()
if err != nil {
log.Println(err)
@@ -60,7 +57,7 @@ func runWebServer() {
if sig == syscall.SIGHUP {
server.Stop()
server = web.NewServer()
- setWebServer(server)
+ global.SetWebServer(server)
err = server.Start()
if err != nil {
log.Println(err)
diff --git a/web/assets/js/model/models.js b/web/assets/js/model/models.js
index 650d247790..7fbed0cee0 100644
--- a/web/assets/js/model/models.js
+++ b/web/assets/js/model/models.js
@@ -26,6 +26,7 @@ class DBInbound {
userId = 0;
up = 0;
down = 0;
+ total = 0;
remark = "";
enable = true;
expiryTime = 0;
@@ -45,6 +46,14 @@ class DBInbound {
ObjectUtil.cloneProps(this, data);
}
+ get totalGB() {
+ return toFixed(this.total / ONE_GB, 2);
+ }
+
+ set totalGB(gb) {
+ this.total = toFixed(gb * ONE_GB, 0);
+ }
+
toInbound() {
let settings = {};
if (!ObjectUtil.isEmpty(this.settings)) {
diff --git a/web/assets/js/util/utils.js b/web/assets/js/util/utils.js
index 78beb20439..9c6d38e941 100644
--- a/web/assets/js/util/utils.js
+++ b/web/assets/js/util/utils.js
@@ -284,7 +284,7 @@ class ObjectUtil {
return false;
}
}
- return true
+ return true;
}
}
diff --git a/web/global/global.go b/web/global/global.go
index f8bfd02d53..09d0683a33 100644
--- a/web/global/global.go
+++ b/web/global/global.go
@@ -13,8 +13,7 @@ type WebServer interface {
GetCtx() context.Context
}
-//go:linkname setWebServer main.setWebServer
-func setWebServer(s WebServer) {
+func SetWebServer(s WebServer) {
webServer = s
}
diff --git a/web/html/xui/form/inbound.html b/web/html/xui/form/inbound.html
index dafe467d13..64da61e90b 100644
--- a/web/html/xui/form/inbound.html
+++ b/web/html/xui/form/inbound.html
@@ -27,6 +27,18 @@
+
+
+ 总流量(GB)
+
+
+ 0 表示不限制
+
+
+
+
+
+
diff --git a/web/html/xui/inbounds.html b/web/html/xui/inbounds.html
index a0e7aa9f81..3649d4a1d8 100644
--- a/web/html/xui/inbounds.html
+++ b/web/html/xui/inbounds.html
@@ -27,15 +27,15 @@
- upload / download:
+ 总上传 / 下载:
[[ sizeFormat(total.up) ]] / [[ sizeFormat(total.down) ]]
- total traffic:
+ 总用量:
[[ sizeFormat(total.up + total.down) ]]
- number of accounts:
+ 入站数量:
[[ dbInbounds.length ]]
@@ -59,6 +59,8 @@
[[ sizeFormat(dbInbound.up) ]]
[[ sizeFormat(dbInbound.down) ]]
+ [[ sizeFormat(dbInbound.total) ]]
+ 无限制
查看
@@ -76,6 +78,7 @@
+
@@ -94,17 +97,17 @@
dataIndex: "id",
width: 60,
}, {
- title: "protocol",
+ title: "协议",
align: 'center',
width: 60,
scopedSlots: { customRender: 'protocol' },
}, {
- title: "port",
+ title: "端口",
align: 'center',
dataIndex: "port",
width: 60,
}, {
- title: "traffic",
+ title: "流量↑|↓",
align: 'center',
width: 60,
scopedSlots: { customRender: 'traffic' },
@@ -119,7 +122,7 @@
// width: 60,
// scopedSlots: { customRender: 'streamSettings' },
}, {
- title: "enable",
+ title: "启用",
align: 'center',
width: 60,
scopedSlots: { customRender: 'enable' },
@@ -172,8 +175,8 @@
},
openAddInbound() {
inModal.show({
- title: 'add account',
- okText: 'add',
+ title: '添加入站',
+ okText: '添加',
confirm: async (inbound, dbInbound) => {
inModal.loading();
await this.addInbound(inbound, dbInbound);
@@ -184,8 +187,8 @@
openEditInbound(dbInbound) {
const inbound = dbInbound.toInbound();
inModal.show({
- title: 'update account',
- okText: 'update',
+ title: '修改入站',
+ okText: '修改',
inbound: inbound,
dbInbound: dbInbound,
confirm: async (inbound, dbInbound) => {
@@ -197,6 +200,9 @@
},
async addInbound(inbound, dbInbound) {
const data = {
+ up: dbInbound.up,
+ down: dbInbound.down,
+ total: dbInbound.total,
remark: dbInbound.remark,
enable: dbInbound.enable,
@@ -211,6 +217,9 @@
},
async updateInbound(inbound, dbInbound) {
const data = {
+ up: dbInbound.up,
+ down: dbInbound.down,
+ total: dbInbound.total,
remark: dbInbound.remark,
enable: dbInbound.enable,
@@ -223,18 +232,32 @@
};
await this.submit(`/xui/inbound/update/${dbInbound.id}`, data, inModal);
},
+ resetTraffic(dbInbound) {
+ this.$confirm({
+ title: '重置流量',
+ content: '确定要重置流量吗?',
+ okText: '重置',
+ cancelText: '取消',
+ onOk: () => {
+ const inbound = dbInbound.toInbound();
+ dbInbound.up = 0;
+ dbInbound.down = 0;
+ this.updateInbound(inbound, dbInbound);
+ },
+ });
+ },
delInbound(dbInbound) {
this.$confirm({
- title: 'delete account',
- content: 'Cannot be restored after deletion, confirm deletion?',
- okText: 'delete',
- cancelText: 'cancel',
+ title: '删除入站',
+ content: '确定要删除入站吗?',
+ okText: '删除',
+ cancelText: '取消',
onOk: () => this.submit('/xui/inbound/del/' + dbInbound.id),
});
},
showQrcode(dbInbound) {
let address = location.hostname;
- if (!ObjectUtil.isEmpty(dbInbound.listen) || dbInbound.listen !== "0.0.0.0") {
+ if (!ObjectUtil.isEmpty(dbInbound.listen) && dbInbound.listen !== "0.0.0.0") {
address = dbInbound.listen;
}
const link = dbInbound.genLink(address);
diff --git a/web/service/inbound.go b/web/service/inbound.go
index b3fda366fc..629e919d63 100644
--- a/web/service/inbound.go
+++ b/web/service/inbound.go
@@ -56,6 +56,9 @@ func (s *InboundService) UpdateInbound(inbound *model.Inbound) error {
if err != nil {
return err
}
+ oldInbound.Up = inbound.Up
+ oldInbound.Down = inbound.Down
+ oldInbound.Total = inbound.Total
oldInbound.Remark = inbound.Remark
oldInbound.Enable = inbound.Enable
oldInbound.ExpiryTime = inbound.ExpiryTime
@@ -98,3 +101,13 @@ func (s *InboundService) AddTraffic(traffics []*xray.Traffic) (err error) {
}
return
}
+
+func (s *InboundService) DisableInvalidInbounds() (bool, error) {
+ db := database.GetDB()
+ result := db.Model(model.Inbound{}).
+ Where("up + down >= total and total > 0 and enable = ?", true).
+ Update("enable", false)
+ err := result.Error
+ count := result.RowsAffected
+ return count > 0, err
+}
diff --git a/web/web.go b/web/web.go
index 469fdb992b..3b24f18eeb 100644
--- a/web/web.go
+++ b/web/web.go
@@ -21,7 +21,6 @@ import (
"time"
"x-ui/config"
"x-ui/logger"
- "x-ui/util"
"x-ui/util/common"
"x-ui/web/controller"
"x-ui/web/service"
@@ -244,6 +243,7 @@ func (s *Server) startTask() {
logger.Warning("start xray failed:", err)
}
var checkTime = 0
+ // 每 30 秒检查一次 xray 是否在运行
s.cron.AddFunc("@every 30s", func() {
if s.xrayService.IsXrayRunning() {
checkTime = 0
@@ -255,9 +255,10 @@ func (s *Server) startTask() {
}
s.xrayService.SetIsNeedRestart(true)
})
+
go func() {
time.Sleep(time.Second * 5)
- // 与重启 xray 的时间错开
+ // 每 10 秒统计一次流量,首次启动延迟 5 秒,与重启 xray 的时间错开
s.cron.AddFunc("@every 10s", func() {
if !s.xrayService.IsXrayRunning() {
return
@@ -273,6 +274,16 @@ func (s *Server) startTask() {
}
})
}()
+
+ // 每分钟检查一次 inbound 流量超出情况
+ s.cron.AddFunc("@every 1m", func() {
+ needRestart, err := s.inboundService.DisableInvalidInbounds()
+ if err != nil {
+ logger.Warning("disable invalid inbounds err:", err)
+ } else if needRestart {
+ s.xrayService.SetIsNeedRestart(true)
+ }
+ })
}
func (s *Server) Start() (err error) {
@@ -343,11 +354,8 @@ func (s *Server) Start() (err error) {
}
func (s *Server) Stop() error {
- if util.IsDone(s.ctx) {
- // 防止 gc 后调用第二次 Stop
- s.xrayService.StopXray()
- }
s.cancel()
+ s.xrayService.StopXray()
if s.cron != nil {
s.cron.Stop()
}