mirror of
https://github.com/alireza0/x-ui.git
synced 2026-03-13 21:13:09 +00:00
add fully function Multi User With ExpireDate & Traffic
This commit is contained in:
@@ -8,6 +8,7 @@ import (
|
||||
"os"
|
||||
"path"
|
||||
"x-ui/config"
|
||||
"x-ui/xray"
|
||||
"x-ui/database/model"
|
||||
)
|
||||
|
||||
@@ -43,6 +44,9 @@ func initSetting() error {
|
||||
func initInboundClientIps() error {
|
||||
return db.AutoMigrate(&model.InboundClientIps{})
|
||||
}
|
||||
func initClientTraffic() error {
|
||||
return db.AutoMigrate(&xray.ClientTraffic{})
|
||||
}
|
||||
|
||||
func InitDB(dbPath string) error {
|
||||
dir := path.Dir(dbPath)
|
||||
@@ -83,7 +87,11 @@ func InitDB(dbPath string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = initClientTraffic()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ type Inbound struct {
|
||||
Remark string `json:"remark" form:"remark"`
|
||||
Enable bool `json:"enable" form:"enable"`
|
||||
ExpiryTime int64 `json:"expiryTime" form:"expiryTime"`
|
||||
ClientStats string `json:"clientStats" form:"clientStats"`
|
||||
ClientStats []xray.ClientTraffic `gorm:"foreignKey:InboundId;references:Id" json:"clientStats" form:"clientStats"`
|
||||
|
||||
// config part
|
||||
Listen string `json:"listen" form:"listen"`
|
||||
@@ -76,6 +76,6 @@ type Client struct {
|
||||
Email string `json:"email"`
|
||||
LimitIP int `json:"limitIp"`
|
||||
Security string `json:"security"`
|
||||
Total int64 `json:"total" form:"total"`
|
||||
TotalGB int64 `json:"totalGB" form:"totalGB"`
|
||||
ExpiryTime int64 `json:"expiryTime" form:"expiryTime"`
|
||||
}
|
||||
@@ -125,10 +125,7 @@ class DBInbound {
|
||||
if (!ObjectUtil.isEmpty(this.sniffing)) {
|
||||
sniffing = JSON.parse(this.sniffing);
|
||||
}
|
||||
let clientStats = {};
|
||||
if (!ObjectUtil.isEmpty(this.clientStats)) {
|
||||
clientStats = JSON.parse(this.clientStats);
|
||||
}
|
||||
|
||||
const config = {
|
||||
port: this.port,
|
||||
listen: this.listen,
|
||||
@@ -137,7 +134,7 @@ class DBInbound {
|
||||
streamSettings: streamSettings,
|
||||
tag: this.tag,
|
||||
sniffing: sniffing,
|
||||
clientStats: clientStats,
|
||||
clientStats: this.clientStats,
|
||||
};
|
||||
return Inbound.fromJson(config);
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
<a-form layout="inline">
|
||||
<a-collapse activeKey="0" v-for="(vmess, index) in inbound.settings.vmesses"
|
||||
:key="`vmess-${index}`">
|
||||
<a-collapse-panel :header="vmess.email == '' ? 'Add User' : vmess.email">
|
||||
<a-tag v-if="isExpiry(index)" color="red" style="margin-bottom: 10px;display: block;text-align: center;">Account is Expired And Disabled</a-tag>
|
||||
<a-collapse-panel :header="getHeaderText(vmess.email)">
|
||||
<a-tag v-if="isExpiry(index) || (getUpStats(vmess.email) + getDownStats(vmess.email)) > vmess.totalGB" color="red" style="margin-bottom: 10px;display: block;text-align: center;">Account is (Expired|Traffic Ended) And Disabled</a-tag>
|
||||
|
||||
<a-form layout="inline">
|
||||
<a-form-item label="Email">
|
||||
@@ -56,7 +56,7 @@
|
||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||
</a-tooltip>
|
||||
</span>
|
||||
<a-input-number v-model="vmess.totalGB" :min="0"></a-input-number>
|
||||
<a-input-number v-model="vmess._totalGB" :min="0"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<span slot="label">
|
||||
@@ -73,7 +73,7 @@
|
||||
</a-form-item>
|
||||
<a-form layout="inline">
|
||||
<a-tag color="blue">[[ sizeFormat(getUpStats(vmess.email)) ]] / [[ sizeFormat(getDownStats(vmess.email)) ]]</a-tag>
|
||||
<a-form v-if="vmess.totalGB > 0">
|
||||
<a-form v-if="vmess._totalGB > 0">
|
||||
<a-tag color="red">used : [[ sizeFormat(getUpStats(vmess.email) + getDownStats(vmess.email)) ]]</a-tag>
|
||||
</a-form>
|
||||
</a-form>
|
||||
|
||||
@@ -116,14 +116,51 @@
|
||||
return this.inbound.isExpiry(index)
|
||||
},
|
||||
getUpStats(email) {
|
||||
console.log(email,this.inbound.clientStats[email])
|
||||
if(this.inbound.clientStats[email])
|
||||
return this.inbound.clientStats[email]["Up"]
|
||||
clientStats = this.inbound.clientStats
|
||||
if(clientStats.length > 0)
|
||||
{
|
||||
for (const key in clientStats) {
|
||||
if (Object.hasOwnProperty.call(clientStats, key)) {
|
||||
if(clientStats[key]['email'] == email)
|
||||
return clientStats[key]['up']
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
getDownStats(email) {
|
||||
if(this.inbound.clientStats[email])
|
||||
return this.inbound.clientStats[email]["Down"]
|
||||
clientStats = this.inbound.clientStats
|
||||
if(clientStats.length > 0)
|
||||
{
|
||||
for (const key in clientStats) {
|
||||
if (Object.hasOwnProperty.call(clientStats, key)) {
|
||||
if(clientStats[key]['email'] == email)
|
||||
return clientStats[key]['down']
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
isClientEnable(email) {
|
||||
clientStats = this.inbound.clientStats
|
||||
if(clientStats.length > 0)
|
||||
{
|
||||
for (const key in clientStats) {
|
||||
if (Object.hasOwnProperty.call(clientStats, key)) {
|
||||
if(clientStats[key]['email'] == email)
|
||||
return clientStats[key]['enable']
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
getHeaderText(email) {
|
||||
if(email == "")
|
||||
return "Add Client"
|
||||
|
||||
return email + (this.isClientEnable(email) == true ? ' Active' : ' Deactive')
|
||||
},
|
||||
|
||||
|
||||
|
||||
@@ -15,7 +15,15 @@ func NewCheckInboundJob() *CheckInboundJob {
|
||||
}
|
||||
|
||||
func (j *CheckInboundJob) Run() {
|
||||
count, err := j.inboundService.DisableInvalidInbounds()
|
||||
count, err := j.inboundService.DisableInvalidClients()
|
||||
if err != nil {
|
||||
logger.Warning("disable invalid Client err:", err)
|
||||
} else if count > 0 {
|
||||
logger.Debugf("disabled %v Client", count)
|
||||
j.xrayService.SetToNeedRestart()
|
||||
}
|
||||
|
||||
count, err = j.inboundService.DisableInvalidInbounds()
|
||||
if err != nil {
|
||||
logger.Warning("disable invalid inbounds err:", err)
|
||||
} else if count > 0 {
|
||||
|
||||
@@ -18,16 +18,6 @@ func (j *XrayTrafficJob) Run() {
|
||||
if !j.xrayService.IsXrayRunning() {
|
||||
return
|
||||
}
|
||||
traffics, err := j.xrayService.GetXrayTraffic()
|
||||
if err != nil {
|
||||
logger.Warning("get xray traffic failed:", err)
|
||||
return
|
||||
}
|
||||
err = j.inboundService.AddTraffic(traffics)
|
||||
if err != nil {
|
||||
logger.Warning("add traffic failed:", err)
|
||||
}
|
||||
|
||||
|
||||
// get Client Traffic
|
||||
|
||||
@@ -40,5 +30,17 @@ func (j *XrayTrafficJob) Run() {
|
||||
if err != nil {
|
||||
logger.Warning("add client traffic failed:", err)
|
||||
}
|
||||
|
||||
traffics, err := j.xrayService.GetXrayTraffic()
|
||||
if err != nil {
|
||||
logger.Warning("get xray traffic failed:", err)
|
||||
return
|
||||
}
|
||||
err = j.inboundService.AddTraffic(traffics)
|
||||
if err != nil {
|
||||
logger.Warning("add traffic failed:", err)
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@ package service
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
"encoding/json"
|
||||
"x-ui/database"
|
||||
"encoding/json"
|
||||
"x-ui/database/model"
|
||||
"x-ui/util/common"
|
||||
"x-ui/xray"
|
||||
@@ -18,7 +18,7 @@ type InboundService struct {
|
||||
func (s *InboundService) GetInbounds(userId int) ([]*model.Inbound, error) {
|
||||
db := database.GetDB()
|
||||
var inbounds []*model.Inbound
|
||||
err := db.Model(model.Inbound{}).Where("user_id = ?", userId).Find(&inbounds).Error
|
||||
err := db.Model(model.Inbound{}).Preload("ClientStats").Where("user_id = ?", userId).Find(&inbounds).Error
|
||||
if err != nil && err != gorm.ErrRecordNotFound {
|
||||
return nil, err
|
||||
}
|
||||
@@ -28,7 +28,7 @@ func (s *InboundService) GetInbounds(userId int) ([]*model.Inbound, error) {
|
||||
func (s *InboundService) GetAllInbounds() ([]*model.Inbound, error) {
|
||||
db := database.GetDB()
|
||||
var inbounds []*model.Inbound
|
||||
err := db.Model(model.Inbound{}).Find(&inbounds).Error
|
||||
err := db.Model(model.Inbound{}).Preload("ClientStats").Find(&inbounds).Error
|
||||
if err != nil && err != gorm.ErrRecordNotFound {
|
||||
return nil, err
|
||||
}
|
||||
@@ -172,7 +172,7 @@ func (s *InboundService) AddClientTraffic(traffics []*xray.ClientTraffic) (err e
|
||||
return nil
|
||||
}
|
||||
db := database.GetDB()
|
||||
db = db.Model(model.Inbound{})
|
||||
db = db.Model(xray.ClientTraffic{})
|
||||
tx := db.Begin()
|
||||
defer func() {
|
||||
if err != nil {
|
||||
@@ -184,24 +184,30 @@ func (s *InboundService) AddClientTraffic(traffics []*xray.ClientTraffic) (err e
|
||||
for _, traffic := range traffics {
|
||||
inbound := &model.Inbound{}
|
||||
|
||||
err := tx.Where("settings like ?", "%" + traffic.Email + "%").First(inbound).Error
|
||||
clientStats := map[string]*xray.ClientTraffic{}
|
||||
json.Unmarshal([]byte(inbound.ClientStats), &clientStats)
|
||||
|
||||
if _, ok := clientStats[traffic.Email]; ok {
|
||||
clientStats[traffic.Email].Up = clientStats[traffic.Email].Up + traffic.Up
|
||||
clientStats[traffic.Email].Down = clientStats[traffic.Email].Down + traffic.Down
|
||||
}else{
|
||||
clientStats[traffic.Email] = traffic
|
||||
err := db.Model(model.Inbound{}).Where("settings like ?", "%" + traffic.Email + "%").First(inbound).Error
|
||||
traffic.InboundId = inbound.Id
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
jsonClientStats, err := json.Marshal(clientStats)
|
||||
|
||||
// if clientStats[traffic.Email]
|
||||
err = tx.Where("settings like ?", "%" + traffic.Email + "%").
|
||||
Update("client_stats", jsonClientStats).
|
||||
Error
|
||||
|
||||
|
||||
// get settings clients
|
||||
settings := map[string][]model.Client{}
|
||||
json.Unmarshal([]byte(inbound.Settings), &settings)
|
||||
clients := settings["clients"]
|
||||
for _, client := range clients {
|
||||
if traffic.Email == client.Email {
|
||||
traffic.ExpiryTime = client.ExpiryTime
|
||||
traffic.Total = client.TotalGB
|
||||
}
|
||||
}
|
||||
if tx.Where("inbound_id = ?", inbound.Id).Where("email = ?", traffic.Email).
|
||||
UpdateColumn("enable", true).
|
||||
UpdateColumn("expiry_time", traffic.ExpiryTime).
|
||||
UpdateColumn("total",traffic.Total).
|
||||
UpdateColumn("up", gorm.Expr("up + ?", traffic.Up)).
|
||||
UpdateColumn("down", gorm.Expr("down + ?", traffic.Down)).RowsAffected == 0 {
|
||||
err = tx.Create(traffic).Error
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -220,6 +226,16 @@ func (s *InboundService) DisableInvalidInbounds() (int64, error) {
|
||||
count := result.RowsAffected
|
||||
return count, err
|
||||
}
|
||||
func (s *InboundService) DisableInvalidClients() (int64, error) {
|
||||
db := database.GetDB()
|
||||
now := time.Now().Unix() * 1000
|
||||
result := db.Model(xray.ClientTraffic{}).
|
||||
Where("((total > 0 and up + down >= total) or (expiry_time > 0 and expiry_time <= ?)) and enable = ?", now, true).
|
||||
Update("enable", false)
|
||||
err := result.Error
|
||||
count := result.RowsAffected
|
||||
return count, err
|
||||
}
|
||||
|
||||
func (s *InboundService) GetInboundClientIps(clientEmail string) (string, error) {
|
||||
db := database.GetDB()
|
||||
|
||||
@@ -4,9 +4,11 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"x-ui/logger"
|
||||
"x-ui/xray"
|
||||
|
||||
"x-ui/database/model"
|
||||
"go.uber.org/atomic"
|
||||
)
|
||||
|
||||
@@ -51,6 +53,9 @@ func (s *XrayService) GetXrayVersion() string {
|
||||
}
|
||||
return p.GetVersion()
|
||||
}
|
||||
func RemoveIndex(s []model.Client, index int) []model.Client {
|
||||
return append(s[:index], s[index+1:]...)
|
||||
}
|
||||
|
||||
func (s *XrayService) GetXrayConfig() (*xray.Config, error) {
|
||||
templateConfig, err := s.settingService.GetXrayConfigTemplate()
|
||||
@@ -72,6 +77,41 @@ func (s *XrayService) GetXrayConfig() (*xray.Config, error) {
|
||||
if !inbound.Enable {
|
||||
continue
|
||||
}
|
||||
// get settings clients
|
||||
settings := map[string][]model.Client{}
|
||||
json.Unmarshal([]byte(inbound.Settings), &settings)
|
||||
clients := settings["clients"]
|
||||
|
||||
|
||||
|
||||
// check users active or not
|
||||
now := time.Now().Unix() * 1000
|
||||
|
||||
clientStats := inbound.ClientStats
|
||||
for _, clientTraffic := range clientStats {
|
||||
|
||||
for index, client := range clients {
|
||||
if client.Email == clientTraffic.Email {
|
||||
totalUsage := clientTraffic.Up + clientTraffic.Down
|
||||
if totalUsage > client.TotalGB || (client.ExpiryTime > 0 && client.ExpiryTime <= now){
|
||||
clients = RemoveIndex(clients,index)
|
||||
logger.Debug("Remove Inbound User",client.Email)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
settings["clients"] = clients
|
||||
modifiedSettings, err := json.Marshal(settings)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
inbound.Settings = string(modifiedSettings)
|
||||
|
||||
inboundConfig := inbound.GenXrayInboundConfig()
|
||||
xrayConfig.InboundConfigs = append(xrayConfig.InboundConfigs, *inboundConfig)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
package xray
|
||||
|
||||
type ClientTraffic struct {
|
||||
Email string
|
||||
Up int64
|
||||
Down int64
|
||||
Id int `json:"id" form:"id" gorm:"primaryKey;autoIncrement"`
|
||||
InboundId int `json:"inboundId" form:"inboundId"`
|
||||
Enable bool `json:"enable" form:"enable"`
|
||||
Email string `json:"email" form:"email" gorm:"unique"`
|
||||
Up int64 `json:"up" form:"up"`
|
||||
Down int64 `json:"down" form:"down"`
|
||||
ExpiryTime int64 `json:"expiryTime" form:"expiryTime"`
|
||||
Total int64 `json:"total" form:"total"`
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user