Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Group Management of Subscription Clients #1543

Closed
wants to merge 13 commits into from
1 change: 1 addition & 0 deletions web/assets/js/model/setting.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ class AllSetting {
this.tgCpu = "";
this.tgLang = "";
this.subEnable = false;
this.subSyncEnable = true;
this.subListen = "";
this.subPort = "2096";
this.subPath = "/sub/";
Expand Down
35 changes: 35 additions & 0 deletions web/assets/js/util/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,41 @@ class HttpUtil {
}
return msg;
}

static async jsonPost(url, data) {
let msg;
try {
const requestOptions = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
};
const resp = await fetch(basePath + url.replace(/^\/+|\/+$/g, ''), requestOptions);
const response = await resp.json();

msg = this._respToMsg({data : response});
} catch (e) {
msg = new Msg(false, e.toString());
}
this._handleMsg(msg);
return msg;
}

static async postWithModalJson(url, data, modal) {
if (modal) {
modal.loading(true);
}
const msg = await this.jsonPost(url, data);
if (modal) {
modal.loading(false);
if (msg instanceof Msg && msg.success) {
modal.close();
}
}
return msg;
}
}

class PromiseUtil {
Expand Down
154 changes: 153 additions & 1 deletion web/controller/inbound.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package controller

import (
"errors"
"encoding/json"
"fmt"
"strconv"

"x-ui/database/model"
"x-ui/web/service"
"x-ui/web/session"
Expand All @@ -31,9 +31,13 @@ func (a *InboundController) initRouter(g *gin.RouterGroup) {
g.POST("/del/:id", a.delInbound)
g.POST("/update/:id", a.updateInbound)
g.POST("/addClient", a.addInboundClient)
g.POST("/addGroupClient", a.addGroupInboundClient)
g.POST("/:id/delClient/:clientId", a.delInboundClient)
g.POST("/delGroupClients", a.delGroupClients)
g.POST("/updateClient/:clientId", a.updateInboundClient)
g.POST("/updateClients", a.updateGroupInboundClient)
g.POST("/:id/resetClientTraffic/:email", a.resetClientTraffic)
g.POST("/resetGroupClientTraffic", a.resetGroupClientTraffic)
g.POST("/resetAllTraffics", a.resetAllTraffics)
g.POST("/resetAllClientTraffics/:id", a.resetAllClientTraffics)
g.POST("/delDepletedClients/:id", a.delDepletedClients)
Expand Down Expand Up @@ -164,6 +168,34 @@ func (a *InboundController) addInboundClient(c *gin.Context) {
}
}

func (a *InboundController) addGroupInboundClient(c *gin.Context) {
var requestData []model.Inbound

err := c.ShouldBindJSON(&requestData)

if err != nil {
jsonMsg(c, I18nWeb(c, "pages.inbounds.update"), err)
return
}

needRestart := true

for _, data := range requestData {

needRestart, err = a.inboundService.AddInboundClient(&data)
if err != nil {
jsonMsg(c, "Something went wrong!", err)
return
}
}

jsonMsg(c, "Client(s) added", nil)
if err == nil && needRestart {
a.xrayService.SetToNeedRestart()
}

}

func (a *InboundController) delInboundClient(c *gin.Context) {
id, err := strconv.Atoi(c.Param("id"))
if err != nil {
Expand All @@ -185,6 +217,38 @@ func (a *InboundController) delInboundClient(c *gin.Context) {
}
}

func (a *InboundController) delGroupClients(c *gin.Context) {
var requestData []struct {
InboundID int `json:"inboundId"`
ClientID string `json:"clientId"`
}

if err := c.ShouldBindJSON(&requestData); err != nil {
jsonMsg(c, "Invalid request data", err)
return
}

needRestart := false

for _, req := range requestData {
needRestartTmp, err := a.inboundService.DelInboundClient(req.InboundID, req.ClientID)
if err != nil {
jsonMsg(c, "Failed to delete client", err)
return
}

if needRestartTmp {
needRestart = true
}
}

jsonMsg(c, "Clients deleted successfully", nil)

if needRestart {
a.xrayService.SetToNeedRestart()
}
}

func (a *InboundController) updateInboundClient(c *gin.Context) {
clientId := c.Param("clientId")

Expand All @@ -208,6 +272,56 @@ func (a *InboundController) updateInboundClient(c *gin.Context) {
}
}

func (a *InboundController) updateGroupInboundClient(c *gin.Context) {
var requestData []map[string]interface{}

if err := c.ShouldBindJSON(&requestData); err != nil {
jsonMsg(c, I18nWeb(c, "pages.inbounds.update"), err)
return
}

needRestart := false

for _, item := range requestData {

inboundMap, ok := item["inbound"].(map[string]interface{})
if !ok {
jsonMsg(c, "Something went wrong!", errors.New("Failed to convert 'inbound' to map"))
return
}

clientId, ok := item["clientId"].(string)
if !ok {
jsonMsg(c, "Something went wrong!", errors.New("Failed to convert 'clientId' to string"))
return
}

inboundJSON, err := json.Marshal(inboundMap)
if err != nil {
jsonMsg(c, "Something went wrong!", err)
return
}

var inboundModel model.Inbound
if err := json.Unmarshal(inboundJSON, &inboundModel); err != nil {
jsonMsg(c, "Something went wrong!", err)
return
}

if restart, err := a.inboundService.UpdateInboundClient(&inboundModel, clientId); err != nil {
jsonMsg(c, "Something went wrong!", err)
return
} else {
needRestart = needRestart || restart
}
}

jsonMsg(c, "Client updated", nil)
if needRestart {
a.xrayService.SetToNeedRestart()
}
}

func (a *InboundController) resetClientTraffic(c *gin.Context) {
id, err := strconv.Atoi(c.Param("id"))
if err != nil {
Expand All @@ -229,6 +343,44 @@ func (a *InboundController) resetClientTraffic(c *gin.Context) {
}
}

func (a *InboundController) resetGroupClientTraffic(c *gin.Context) {
var requestData []struct {
InboundID int `json:"inboundId"` // Map JSON "inboundId" to struct field "InboundID"
Email string `json:"email"` // Map JSON "email" to struct field "Email"
}

// Parse JSON body directly using ShouldBindJSON
if err := c.ShouldBindJSON(&requestData); err != nil {
jsonMsg(c, "Invalid request data", err)
return
}

needRestart := false

// Process each request data
for _, req := range requestData {
needRestartTmp, err := a.inboundService.ResetClientTraffic(req.InboundID, req.Email)
if err != nil {
jsonMsg(c, "Failed to reset client traffic", err)
return
}

// If any request requires a restart, set needRestart to true
if needRestartTmp {
needRestart = true
}
}

// Send response back to the client
jsonMsg(c, "Traffic reset for all clients", nil)

// Restart the service if required
if needRestart {
a.xrayService.SetToNeedRestart()
}
}


func (a *InboundController) resetAllTraffics(c *gin.Context) {
err := a.inboundService.ResetAllTraffics()
if err != nil {
Expand Down
1 change: 1 addition & 0 deletions web/entity/entity.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ type AllSetting struct {
TgLang string `json:"tgLang" form:"tgLang"`
TimeLocation string `json:"timeLocation" form:"timeLocation"`
SubEnable bool `json:"subEnable" form:"subEnable"`
SubSyncEnable bool `json:"subSyncEnable" form:"subSyncEnable"`
SubListen string `json:"subListen" form:"subListen"`
SubPort int `json:"subPort" form:"subPort"`
SubPath string `json:"subPath" form:"subPath"`
Expand Down
22 changes: 15 additions & 7 deletions web/html/common/qrcode_modal.html
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,16 @@
style="width: 100%; height: 100%; display: flex; border-radius: 1rem;">
</canvas>
</template>
<a-divider>{{ i18n "pages.inbounds.client" }}</a-divider>
<template v-for="(row, index) in qrModal.qrcodes">
<a-tag color="blue" style="margin: 10px 0; display: block; text-align: center;">[[ row.remark ]]</a-tag>
<canvas @click="copyToClipboard('qrCode-'+index, row.link)"
:id="'qrCode-'+index"
style="width: 100%; height: 100%; display: flex; border-radius: 1rem;"></canvas>
<template v-if="!isJustSub">
<a-divider>{{ i18n "pages.inbounds.client" }}</a-divider>
<template v-for="(row, index) in qrModal.qrcodes">
<a-tag color="blue" style="margin: 10px 0; display: block; text-align: center;">[[ row.remark ]]</a-tag>
<canvas @click="copyToClipboard('qrCode-'+index, row.link)"
:id="'qrCode-'+index"
style="width: 100%; height: 100%; display: flex; border-radius: 1rem;"></canvas>
</template>
</template>
</tr-qr-modal>
</a-modal>

<script>
Expand All @@ -34,12 +37,14 @@
qrcodes: [],
clipboard: null,
visible: false,
isJustSub: false,
subId: '',
show: function (title = '', dbInbound, client) {
show: function (title = '', dbInbound, client, isJustSub = false) {
this.title = title;
this.dbInbound = dbInbound;
this.inbound = dbInbound.toInbound();
this.client = client;
this.isJustSub = isJustSub;
this.subId = '';
this.qrcodes = [];
if (this.inbound.protocol == Protocols.WIREGUARD){
Expand Down Expand Up @@ -69,6 +74,9 @@
el: '#qrcode-modal',
data: {
qrModal: qrModal,
get isJustSub(){
return qrModal.isJustSub
}
},
methods: {
copyToClipboard(elementId, content) {
Expand Down
Loading