mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-02-27 20:53:01 +00:00
refactor(inbound): extract client mutation helpers and simplify path handling
This commit is contained in:
parent
0de971fbef
commit
95336c6919
8 changed files with 273 additions and 348 deletions
|
|
@ -717,6 +717,23 @@ class URLBuilder {
|
|||
}
|
||||
}
|
||||
|
||||
class PathUtil {
|
||||
static normalizePath(path) {
|
||||
let normalized = path || "/";
|
||||
if (!normalized.startsWith("/")) {
|
||||
normalized = `/${normalized}`;
|
||||
}
|
||||
if (!normalized.endsWith("/")) {
|
||||
normalized = `${normalized}/`;
|
||||
}
|
||||
return normalized.replace(/\/+/g, "/");
|
||||
}
|
||||
|
||||
static stripLeadingSlash(path) {
|
||||
return (path || "").replace(/^\/+/, "");
|
||||
}
|
||||
}
|
||||
|
||||
class LanguageManager {
|
||||
static supportedLanguages = [
|
||||
{
|
||||
|
|
@ -916,4 +933,4 @@ class IntlUtil {
|
|||
|
||||
return formatter.format(diff, 'day');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -344,8 +344,7 @@
|
|||
const { webDomain, webPort, webBasePath, webCertFile, webKeyFile } = this.allSetting;
|
||||
const newProtocol = (webCertFile || webKeyFile) ? "https:" : "http:";
|
||||
|
||||
let base = webBasePath ? webBasePath.replace(/^\//, "") : "";
|
||||
if (base && !base.endsWith("/")) base += "/";
|
||||
const base = PathUtil.stripLeadingSlash(PathUtil.normalizePath(webBasePath));
|
||||
|
||||
if (!this.entryIsIP) {
|
||||
const url = new URL(window.location.href);
|
||||
|
|
@ -604,20 +603,20 @@
|
|||
confAlerts: {
|
||||
get: function () {
|
||||
if (!this.allSetting) return [];
|
||||
var alerts = []
|
||||
const alerts = [];
|
||||
if (window.location.protocol !== "https:") alerts.push('{{ i18n "secAlertSSL" }}');
|
||||
if (this.allSetting.webPort === 2053) alerts.push('{{ i18n "secAlertPanelPort" }}');
|
||||
panelPath = window.location.pathname.split('/').length < 4
|
||||
const panelPath = window.location.pathname.split('/').length < 4;
|
||||
if (panelPath && this.allSetting.webBasePath == '/') alerts.push('{{ i18n "secAlertPanelURI" }}');
|
||||
if (this.allSetting.subEnable) {
|
||||
subPath = this.allSetting.subURI.length > 0 ? new URL(this.allSetting.subURI).pathname : this.allSetting.subPath;
|
||||
const subPath = PathUtil.normalizePath(this.allSetting.subURI.length > 0 ? new URL(this.allSetting.subURI).pathname : this.allSetting.subPath);
|
||||
if (subPath == '/sub/') alerts.push('{{ i18n "secAlertSubURI" }}');
|
||||
}
|
||||
if (this.allSetting.subJsonEnable) {
|
||||
subJsonPath = this.allSetting.subJsonURI.length > 0 ? new URL(this.allSetting.subJsonURI).pathname : this.allSetting.subJsonPath;
|
||||
const subJsonPath = PathUtil.normalizePath(this.allSetting.subJsonURI.length > 0 ? new URL(this.allSetting.subJsonURI).pathname : this.allSetting.subJsonPath);
|
||||
if (subJsonPath == '/json/') alerts.push('{{ i18n "secAlertSubJsonURI" }}');
|
||||
}
|
||||
return alerts
|
||||
return alerts;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -635,4 +634,4 @@
|
|||
}
|
||||
});
|
||||
</script>
|
||||
{{ template "page/body_end" .}}
|
||||
{{ template "page/body_end" .}}
|
||||
|
|
|
|||
|
|
@ -46,7 +46,9 @@
|
|||
<template #title>{{ i18n "pages.settings.panelUrlPath"}}</template>
|
||||
<template #description>{{ i18n "pages.settings.panelUrlPathDesc"}}</template>
|
||||
<template #control>
|
||||
<a-input type="text" v-model="allSetting.webBasePath"></a-input>
|
||||
<a-input type="text" v-model="allSetting.webBasePath"
|
||||
@input="allSetting.webBasePath = ((typeof $event === 'string' ? $event : ($event && $event.target ? $event.target.value : '')) || '').replace(/[:*]/g, '')"
|
||||
@blur="allSetting.webBasePath = PathUtil.normalizePath(allSetting.webBasePath)"></a-input>
|
||||
</template>
|
||||
</a-setting-list-item>
|
||||
<a-setting-list-item paddings="small">
|
||||
|
|
@ -277,4 +279,4 @@
|
|||
</a-setting-list-item>
|
||||
</a-collapse-panel>
|
||||
</a-collapse>
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@
|
|||
<template #control>
|
||||
<a-input type="text" v-model="allSetting.subPath"
|
||||
@input="allSetting.subPath = ((typeof $event === 'string' ? $event : ($event && $event.target ? $event.target.value : '')) || '').replace(/[:*]/g, '')"
|
||||
@blur="allSetting.subPath = (p => { p = p || '/'; if (!p.startsWith('/')) p='/' + p; if (!p.endsWith('/')) p += '/'; return p.replace(/\/+/g,'/'); })(allSetting.subPath)"
|
||||
@blur="allSetting.subPath = PathUtil.normalizePath(allSetting.subPath)"
|
||||
placeholder="/sub/"></a-input>
|
||||
</template>
|
||||
</a-setting-list-item>
|
||||
|
|
@ -142,4 +142,4 @@
|
|||
</a-setting-list-item>
|
||||
</a-collapse-panel>
|
||||
</a-collapse>
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
<template #control>
|
||||
<a-input type="text" v-model="allSetting.subJsonPath"
|
||||
@input="allSetting.subJsonPath = ((typeof $event === 'string' ? $event : ($event && $event.target ? $event.target.value : '')) || '').replace(/[:*]/g, '')"
|
||||
@blur="allSetting.subJsonPath = (p => { p = p || '/'; if (!p.startsWith('/')) p='/' + p; if (!p.endsWith('/')) p += '/'; return p.replace(/\/+/g,'/'); })(allSetting.subJsonPath)"
|
||||
@blur="allSetting.subJsonPath = PathUtil.normalizePath(allSetting.subJsonPath)"
|
||||
placeholder="/json/"></a-input>
|
||||
</template>
|
||||
</a-setting-list-item>
|
||||
|
|
@ -199,4 +199,4 @@
|
|||
</a-list-item>
|
||||
</a-collapse-panel>
|
||||
</a-collapse>
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
|
|
|||
|
|
@ -1416,159 +1416,6 @@ func (s *InboundService) GetClientByEmail(clientEmail string) (*xray.ClientTraff
|
|||
return nil, nil, common.NewError("Client Not Found In Inbound For Email:", clientEmail)
|
||||
}
|
||||
|
||||
func (s *InboundService) SetClientTelegramUserID(trafficId int, tgId int64) (bool, error) {
|
||||
traffic, inbound, err := s.GetClientInboundByTrafficID(trafficId)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if inbound == nil {
|
||||
return false, common.NewError("Inbound Not Found For Traffic ID:", trafficId)
|
||||
}
|
||||
|
||||
clientEmail := traffic.Email
|
||||
|
||||
oldClients, err := s.GetClients(inbound)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
clientId := ""
|
||||
|
||||
for _, oldClient := range oldClients {
|
||||
if oldClient.Email == clientEmail {
|
||||
switch inbound.Protocol {
|
||||
case "trojan":
|
||||
clientId = oldClient.Password
|
||||
case "shadowsocks":
|
||||
clientId = oldClient.Email
|
||||
default:
|
||||
clientId = oldClient.ID
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if len(clientId) == 0 {
|
||||
return false, common.NewError("Client Not Found For Email:", clientEmail)
|
||||
}
|
||||
|
||||
var settings map[string]any
|
||||
err = json.Unmarshal([]byte(inbound.Settings), &settings)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
clients := settings["clients"].([]any)
|
||||
var newClients []any
|
||||
for client_index := range clients {
|
||||
c := clients[client_index].(map[string]any)
|
||||
if c["email"] == clientEmail {
|
||||
c["tgId"] = tgId
|
||||
c["updated_at"] = time.Now().Unix() * 1000
|
||||
newClients = append(newClients, any(c))
|
||||
}
|
||||
}
|
||||
settings["clients"] = newClients
|
||||
modifiedSettings, err := json.MarshalIndent(settings, "", " ")
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
inbound.Settings = string(modifiedSettings)
|
||||
needRestart, err := s.UpdateInboundClient(inbound, clientId)
|
||||
return needRestart, err
|
||||
}
|
||||
|
||||
func (s *InboundService) checkIsEnabledByEmail(clientEmail string) (bool, error) {
|
||||
_, inbound, err := s.GetClientInboundByEmail(clientEmail)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if inbound == nil {
|
||||
return false, common.NewError("Inbound Not Found For Email:", clientEmail)
|
||||
}
|
||||
|
||||
clients, err := s.GetClients(inbound)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
isEnable := false
|
||||
|
||||
for _, client := range clients {
|
||||
if client.Email == clientEmail {
|
||||
isEnable = client.Enable
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return isEnable, err
|
||||
}
|
||||
|
||||
func (s *InboundService) ToggleClientEnableByEmail(clientEmail string) (bool, bool, error) {
|
||||
_, inbound, err := s.GetClientInboundByEmail(clientEmail)
|
||||
if err != nil {
|
||||
return false, false, err
|
||||
}
|
||||
if inbound == nil {
|
||||
return false, false, common.NewError("Inbound Not Found For Email:", clientEmail)
|
||||
}
|
||||
|
||||
oldClients, err := s.GetClients(inbound)
|
||||
if err != nil {
|
||||
return false, false, err
|
||||
}
|
||||
|
||||
clientId := ""
|
||||
clientOldEnabled := false
|
||||
|
||||
for _, oldClient := range oldClients {
|
||||
if oldClient.Email == clientEmail {
|
||||
switch inbound.Protocol {
|
||||
case "trojan":
|
||||
clientId = oldClient.Password
|
||||
case "shadowsocks":
|
||||
clientId = oldClient.Email
|
||||
default:
|
||||
clientId = oldClient.ID
|
||||
}
|
||||
clientOldEnabled = oldClient.Enable
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if len(clientId) == 0 {
|
||||
return false, false, common.NewError("Client Not Found For Email:", clientEmail)
|
||||
}
|
||||
|
||||
var settings map[string]any
|
||||
err = json.Unmarshal([]byte(inbound.Settings), &settings)
|
||||
if err != nil {
|
||||
return false, false, err
|
||||
}
|
||||
clients := settings["clients"].([]any)
|
||||
var newClients []any
|
||||
for client_index := range clients {
|
||||
c := clients[client_index].(map[string]any)
|
||||
if c["email"] == clientEmail {
|
||||
c["enable"] = !clientOldEnabled
|
||||
c["updated_at"] = time.Now().Unix() * 1000
|
||||
newClients = append(newClients, any(c))
|
||||
}
|
||||
}
|
||||
settings["clients"] = newClients
|
||||
modifiedSettings, err := json.MarshalIndent(settings, "", " ")
|
||||
if err != nil {
|
||||
return false, false, err
|
||||
}
|
||||
inbound.Settings = string(modifiedSettings)
|
||||
|
||||
needRestart, err := s.UpdateInboundClient(inbound, clientId)
|
||||
if err != nil {
|
||||
return false, needRestart, err
|
||||
}
|
||||
|
||||
return !clientOldEnabled, needRestart, nil
|
||||
}
|
||||
|
||||
// SetClientEnableByEmail sets client enable state to desired value; returns (changed, needRestart, error)
|
||||
func (s *InboundService) SetClientEnableByEmail(clientEmail string, enable bool) (bool, bool, error) {
|
||||
current, err := s.checkIsEnabledByEmail(clientEmail)
|
||||
|
|
@ -1585,186 +1432,6 @@ func (s *InboundService) SetClientEnableByEmail(clientEmail string, enable bool)
|
|||
return newEnabled == enable, needRestart, nil
|
||||
}
|
||||
|
||||
func (s *InboundService) ResetClientIpLimitByEmail(clientEmail string, count int) (bool, error) {
|
||||
_, inbound, err := s.GetClientInboundByEmail(clientEmail)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if inbound == nil {
|
||||
return false, common.NewError("Inbound Not Found For Email:", clientEmail)
|
||||
}
|
||||
|
||||
oldClients, err := s.GetClients(inbound)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
clientId := ""
|
||||
|
||||
for _, oldClient := range oldClients {
|
||||
if oldClient.Email == clientEmail {
|
||||
switch inbound.Protocol {
|
||||
case "trojan":
|
||||
clientId = oldClient.Password
|
||||
case "shadowsocks":
|
||||
clientId = oldClient.Email
|
||||
default:
|
||||
clientId = oldClient.ID
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if len(clientId) == 0 {
|
||||
return false, common.NewError("Client Not Found For Email:", clientEmail)
|
||||
}
|
||||
|
||||
var settings map[string]any
|
||||
err = json.Unmarshal([]byte(inbound.Settings), &settings)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
clients := settings["clients"].([]any)
|
||||
var newClients []any
|
||||
for client_index := range clients {
|
||||
c := clients[client_index].(map[string]any)
|
||||
if c["email"] == clientEmail {
|
||||
c["limitIp"] = count
|
||||
c["updated_at"] = time.Now().Unix() * 1000
|
||||
newClients = append(newClients, any(c))
|
||||
}
|
||||
}
|
||||
settings["clients"] = newClients
|
||||
modifiedSettings, err := json.MarshalIndent(settings, "", " ")
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
inbound.Settings = string(modifiedSettings)
|
||||
needRestart, err := s.UpdateInboundClient(inbound, clientId)
|
||||
return needRestart, err
|
||||
}
|
||||
|
||||
func (s *InboundService) ResetClientExpiryTimeByEmail(clientEmail string, expiry_time int64) (bool, error) {
|
||||
_, inbound, err := s.GetClientInboundByEmail(clientEmail)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if inbound == nil {
|
||||
return false, common.NewError("Inbound Not Found For Email:", clientEmail)
|
||||
}
|
||||
|
||||
oldClients, err := s.GetClients(inbound)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
clientId := ""
|
||||
|
||||
for _, oldClient := range oldClients {
|
||||
if oldClient.Email == clientEmail {
|
||||
switch inbound.Protocol {
|
||||
case "trojan":
|
||||
clientId = oldClient.Password
|
||||
case "shadowsocks":
|
||||
clientId = oldClient.Email
|
||||
default:
|
||||
clientId = oldClient.ID
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if len(clientId) == 0 {
|
||||
return false, common.NewError("Client Not Found For Email:", clientEmail)
|
||||
}
|
||||
|
||||
var settings map[string]any
|
||||
err = json.Unmarshal([]byte(inbound.Settings), &settings)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
clients := settings["clients"].([]any)
|
||||
var newClients []any
|
||||
for client_index := range clients {
|
||||
c := clients[client_index].(map[string]any)
|
||||
if c["email"] == clientEmail {
|
||||
c["expiryTime"] = expiry_time
|
||||
c["updated_at"] = time.Now().Unix() * 1000
|
||||
newClients = append(newClients, any(c))
|
||||
}
|
||||
}
|
||||
settings["clients"] = newClients
|
||||
modifiedSettings, err := json.MarshalIndent(settings, "", " ")
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
inbound.Settings = string(modifiedSettings)
|
||||
needRestart, err := s.UpdateInboundClient(inbound, clientId)
|
||||
return needRestart, err
|
||||
}
|
||||
|
||||
func (s *InboundService) ResetClientTrafficLimitByEmail(clientEmail string, totalGB int) (bool, error) {
|
||||
if totalGB < 0 {
|
||||
return false, common.NewError("totalGB must be >= 0")
|
||||
}
|
||||
_, inbound, err := s.GetClientInboundByEmail(clientEmail)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if inbound == nil {
|
||||
return false, common.NewError("Inbound Not Found For Email:", clientEmail)
|
||||
}
|
||||
|
||||
oldClients, err := s.GetClients(inbound)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
clientId := ""
|
||||
|
||||
for _, oldClient := range oldClients {
|
||||
if oldClient.Email == clientEmail {
|
||||
switch inbound.Protocol {
|
||||
case "trojan":
|
||||
clientId = oldClient.Password
|
||||
case "shadowsocks":
|
||||
clientId = oldClient.Email
|
||||
default:
|
||||
clientId = oldClient.ID
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if len(clientId) == 0 {
|
||||
return false, common.NewError("Client Not Found For Email:", clientEmail)
|
||||
}
|
||||
|
||||
var settings map[string]any
|
||||
err = json.Unmarshal([]byte(inbound.Settings), &settings)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
clients := settings["clients"].([]any)
|
||||
var newClients []any
|
||||
for client_index := range clients {
|
||||
c := clients[client_index].(map[string]any)
|
||||
if c["email"] == clientEmail {
|
||||
c["totalGB"] = totalGB * 1024 * 1024 * 1024
|
||||
c["updated_at"] = time.Now().Unix() * 1000
|
||||
newClients = append(newClients, any(c))
|
||||
}
|
||||
}
|
||||
settings["clients"] = newClients
|
||||
modifiedSettings, err := json.MarshalIndent(settings, "", " ")
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
inbound.Settings = string(modifiedSettings)
|
||||
needRestart, err := s.UpdateInboundClient(inbound, clientId)
|
||||
return needRestart, err
|
||||
}
|
||||
|
||||
func (s *InboundService) ResetClientTrafficByEmail(clientEmail string) error {
|
||||
db := database.GetDB()
|
||||
|
||||
|
|
|
|||
191
web/service/inbound_client_mutation.go
Normal file
191
web/service/inbound_client_mutation.go
Normal file
|
|
@ -0,0 +1,191 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/mhsanaei/3x-ui/v2/database/model"
|
||||
"github.com/mhsanaei/3x-ui/v2/util/common"
|
||||
)
|
||||
|
||||
func (s *InboundService) resolveInboundAndClient(clientEmail string) (*model.Inbound, string, bool, error) {
|
||||
_, inbound, err := s.GetClientInboundByEmail(clientEmail)
|
||||
if err != nil {
|
||||
return nil, "", false, err
|
||||
}
|
||||
if inbound == nil {
|
||||
return nil, "", false, common.NewError("Inbound Not Found For Email:", clientEmail)
|
||||
}
|
||||
|
||||
clients, err := s.GetClients(inbound)
|
||||
if err != nil {
|
||||
return nil, "", false, err
|
||||
}
|
||||
|
||||
clientID := ""
|
||||
clientEnabled := false
|
||||
for _, oldClient := range clients {
|
||||
if oldClient.Email != clientEmail {
|
||||
continue
|
||||
}
|
||||
switch inbound.Protocol {
|
||||
case "trojan":
|
||||
clientID = oldClient.Password
|
||||
case "shadowsocks":
|
||||
clientID = oldClient.Email
|
||||
default:
|
||||
clientID = oldClient.ID
|
||||
}
|
||||
clientEnabled = oldClient.Enable
|
||||
break
|
||||
}
|
||||
|
||||
if clientID == "" {
|
||||
return nil, "", false, common.NewError("Client Not Found For Email:", clientEmail)
|
||||
}
|
||||
|
||||
return inbound, clientID, clientEnabled, nil
|
||||
}
|
||||
|
||||
func (s *InboundService) applySingleClientUpdate(inbound *model.Inbound, clientEmail string, mutate func(client map[string]any)) error {
|
||||
var settings map[string]any
|
||||
if err := json.Unmarshal([]byte(inbound.Settings), &settings); err != nil {
|
||||
return err
|
||||
}
|
||||
clients, ok := settings["clients"].([]any)
|
||||
if !ok {
|
||||
return common.NewError("invalid clients format in inbound settings")
|
||||
}
|
||||
|
||||
newClients := make([]any, 0, 1)
|
||||
for idx := range clients {
|
||||
c, ok := clients[idx].(map[string]any)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if c["email"] != clientEmail {
|
||||
continue
|
||||
}
|
||||
mutate(c)
|
||||
c["updated_at"] = time.Now().Unix() * 1000
|
||||
newClients = append(newClients, c)
|
||||
break
|
||||
}
|
||||
|
||||
if len(newClients) == 0 {
|
||||
return common.NewError("Client Not Found For Email:", clientEmail)
|
||||
}
|
||||
|
||||
settings["clients"] = newClients
|
||||
modifiedSettings, err := json.MarshalIndent(settings, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
inbound.Settings = string(modifiedSettings)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *InboundService) SetClientTelegramUserID(trafficId int, tgId int64) (bool, error) {
|
||||
traffic, inbound, err := s.GetClientInboundByTrafficID(trafficId)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if inbound == nil {
|
||||
return false, common.NewError("Inbound Not Found For Traffic ID:", trafficId)
|
||||
}
|
||||
clientEmail := traffic.Email
|
||||
|
||||
_, clientID, _, err := s.resolveInboundAndClient(clientEmail)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if err := s.applySingleClientUpdate(inbound, clientEmail, func(client map[string]any) {
|
||||
client["tgId"] = tgId
|
||||
}); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
needRestart, err := s.UpdateInboundClient(inbound, clientID)
|
||||
return needRestart, err
|
||||
}
|
||||
|
||||
func (s *InboundService) checkIsEnabledByEmail(clientEmail string) (bool, error) {
|
||||
_, _, enabled, err := s.resolveInboundAndClient(clientEmail)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return enabled, nil
|
||||
}
|
||||
|
||||
func (s *InboundService) ToggleClientEnableByEmail(clientEmail string) (bool, bool, error) {
|
||||
inbound, clientID, oldEnabled, err := s.resolveInboundAndClient(clientEmail)
|
||||
if err != nil {
|
||||
return false, false, err
|
||||
}
|
||||
|
||||
if err := s.applySingleClientUpdate(inbound, clientEmail, func(client map[string]any) {
|
||||
client["enable"] = !oldEnabled
|
||||
}); err != nil {
|
||||
return false, false, err
|
||||
}
|
||||
|
||||
needRestart, err := s.UpdateInboundClient(inbound, clientID)
|
||||
if err != nil {
|
||||
return false, needRestart, err
|
||||
}
|
||||
|
||||
return !oldEnabled, needRestart, nil
|
||||
}
|
||||
|
||||
func (s *InboundService) ResetClientIpLimitByEmail(clientEmail string, count int) (bool, error) {
|
||||
inbound, clientID, _, err := s.resolveInboundAndClient(clientEmail)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if err := s.applySingleClientUpdate(inbound, clientEmail, func(client map[string]any) {
|
||||
client["limitIp"] = count
|
||||
}); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
needRestart, err := s.UpdateInboundClient(inbound, clientID)
|
||||
return needRestart, err
|
||||
}
|
||||
|
||||
func (s *InboundService) ResetClientExpiryTimeByEmail(clientEmail string, expiryTime int64) (bool, error) {
|
||||
inbound, clientID, _, err := s.resolveInboundAndClient(clientEmail)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if err := s.applySingleClientUpdate(inbound, clientEmail, func(client map[string]any) {
|
||||
client["expiryTime"] = expiryTime
|
||||
}); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
needRestart, err := s.UpdateInboundClient(inbound, clientID)
|
||||
return needRestart, err
|
||||
}
|
||||
|
||||
func (s *InboundService) ResetClientTrafficLimitByEmail(clientEmail string, totalGB int) (bool, error) {
|
||||
if totalGB < 0 {
|
||||
return false, common.NewError("totalGB must be >= 0")
|
||||
}
|
||||
|
||||
inbound, clientID, _, err := s.resolveInboundAndClient(clientEmail)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if err := s.applySingleClientUpdate(inbound, clientEmail, func(client map[string]any) {
|
||||
client["totalGB"] = totalGB * 1024 * 1024 * 1024
|
||||
}); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
needRestart, err := s.UpdateInboundClient(inbound, clientID)
|
||||
return needRestart, err
|
||||
}
|
||||
49
web/service/inbound_client_mutation_test.go
Normal file
49
web/service/inbound_client_mutation_test.go
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/mhsanaei/3x-ui/v2/database/model"
|
||||
)
|
||||
|
||||
func TestApplySingleClientUpdate(t *testing.T) {
|
||||
svc := &InboundService{}
|
||||
inbound := &model.Inbound{Settings: `{"clients":[{"email":"a@example.com","limitIp":1},{"email":"b@example.com","limitIp":2}]}`}
|
||||
|
||||
err := svc.applySingleClientUpdate(inbound, "b@example.com", func(client map[string]any) {
|
||||
client["limitIp"] = 9
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
var settings map[string]any
|
||||
if err := json.Unmarshal([]byte(inbound.Settings), &settings); err != nil {
|
||||
t.Fatalf("unmarshal updated settings: %v", err)
|
||||
}
|
||||
clients := settings["clients"].([]any)
|
||||
if len(clients) != 1 {
|
||||
t.Fatalf("expected one updated client payload, got %d", len(clients))
|
||||
}
|
||||
client := clients[0].(map[string]any)
|
||||
if client["email"] != "b@example.com" {
|
||||
t.Fatalf("unexpected updated client email: %v", client["email"])
|
||||
}
|
||||
if int(client["limitIp"].(float64)) != 9 {
|
||||
t.Fatalf("expected limitIp=9, got %v", client["limitIp"])
|
||||
}
|
||||
if _, ok := client["updated_at"]; !ok {
|
||||
t.Fatalf("expected updated_at to be set")
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplySingleClientUpdateMissingClient(t *testing.T) {
|
||||
svc := &InboundService{}
|
||||
inbound := &model.Inbound{Settings: `{"clients":[{"email":"a@example.com"}]}`}
|
||||
|
||||
err := svc.applySingleClientUpdate(inbound, "x@example.com", func(client map[string]any) {})
|
||||
if err == nil {
|
||||
t.Fatalf("expected missing client error")
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue