mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-06-06 13:14:11 +00:00
refactor(clients): finish migrating to ClientService + tidy IP routes
Two related cleanups in the new /clients surface: 1. Move ResetAllClientTraffics (bulk-reset of xray_client_traffic + last_traffic_reset_time, with node-runtime propagation) from InboundService to ClientService. PeriodicTrafficResetJob now holds a clientService and calls j.clientService.ResetAllClientTraffics(&j.inboundService, id). The last client-mutation method on InboundService is gone. 2. Shorten redundantly-named routes/handlers under /panel/api/clients: - /clientIps/:email -> /ips/:email (handler getIps) - /clearClientIps/:email -> /clearIps/:email (handler clearIps) The "client" prefix was redundant inside the clients namespace. Frontend (InboundInfoModal) and api-docs updated to match. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
d4ddf702de
commit
a79cb9fe6d
6 changed files with 75 additions and 76 deletions
|
|
@ -473,7 +473,7 @@ export const sections = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
path: '/panel/api/clients/clientIps/:email',
|
path: '/panel/api/clients/ips/:email',
|
||||||
summary: 'List source IPs that have connected with the given client’s credentials. Returns an array of "ip (timestamp)" strings.',
|
summary: 'List source IPs that have connected with the given client’s credentials. Returns an array of "ip (timestamp)" strings.',
|
||||||
params: [
|
params: [
|
||||||
{ name: 'email', in: 'path', type: 'string', desc: 'Client email.' },
|
{ name: 'email', in: 'path', type: 'string', desc: 'Client email.' },
|
||||||
|
|
@ -481,7 +481,7 @@ export const sections = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
path: '/panel/api/clients/clearClientIps/:email',
|
path: '/panel/api/clients/clearIps/:email',
|
||||||
summary: 'Reset the recorded IP list for a client.',
|
summary: 'Reset the recorded IP list for a client.',
|
||||||
params: [
|
params: [
|
||||||
{ name: 'email', in: 'path', type: 'string', desc: 'Client email.' },
|
{ name: 'email', in: 'path', type: 'string', desc: 'Client email.' },
|
||||||
|
|
|
||||||
|
|
@ -137,7 +137,7 @@ async function loadClientIps() {
|
||||||
if (!clientStats.value?.email) return;
|
if (!clientStats.value?.email) return;
|
||||||
refreshing.value = true;
|
refreshing.value = true;
|
||||||
try {
|
try {
|
||||||
const msg = await HttpUtil.post(`/panel/api/clients/clientIps/${clientStats.value.email}`);
|
const msg = await HttpUtil.post(`/panel/api/clients/ips/${clientStats.value.email}`);
|
||||||
if (!msg?.success) {
|
if (!msg?.success) {
|
||||||
clientIpsText.value = msg?.obj || 'No IP record';
|
clientIpsText.value = msg?.obj || 'No IP record';
|
||||||
clientIpsArray.value = [];
|
clientIpsArray.value = [];
|
||||||
|
|
@ -164,7 +164,7 @@ async function loadClientIps() {
|
||||||
|
|
||||||
async function clearClientIps() {
|
async function clearClientIps() {
|
||||||
if (!clientStats.value?.email) return;
|
if (!clientStats.value?.email) return;
|
||||||
const msg = await HttpUtil.post(`/panel/api/clients/clearClientIps/${clientStats.value.email}`);
|
const msg = await HttpUtil.post(`/panel/api/clients/clearIps/${clientStats.value.email}`);
|
||||||
if (msg?.success) {
|
if (msg?.success) {
|
||||||
clientIpsArray.value = [];
|
clientIpsArray.value = [];
|
||||||
clientIpsText.value = t('tgbot.noIpRecord');
|
clientIpsText.value = t('tgbot.noIpRecord');
|
||||||
|
|
|
||||||
|
|
@ -36,8 +36,8 @@ func (a *ClientController) initRouter(g *gin.RouterGroup) {
|
||||||
g.POST("/delDepleted", a.delDepleted)
|
g.POST("/delDepleted", a.delDepleted)
|
||||||
g.POST("/resetTraffic/:email", a.resetTrafficByEmail)
|
g.POST("/resetTraffic/:email", a.resetTrafficByEmail)
|
||||||
g.POST("/updateTraffic/:email", a.updateTrafficByEmail)
|
g.POST("/updateTraffic/:email", a.updateTrafficByEmail)
|
||||||
g.POST("/clientIps/:email", a.getClientIps)
|
g.POST("/ips/:email", a.getIps)
|
||||||
g.POST("/clearClientIps/:email", a.clearClientIps)
|
g.POST("/clearIps/:email", a.clearIps)
|
||||||
g.POST("/onlines", a.onlines)
|
g.POST("/onlines", a.onlines)
|
||||||
g.POST("/lastOnline", a.lastOnline)
|
g.POST("/lastOnline", a.lastOnline)
|
||||||
g.GET("/traffic/:email", a.getTrafficByEmail)
|
g.GET("/traffic/:email", a.getTrafficByEmail)
|
||||||
|
|
@ -213,7 +213,7 @@ func (a *ClientController) updateTrafficByEmail(c *gin.Context) {
|
||||||
jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.inboundClientUpdateSuccess"), nil)
|
jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.inboundClientUpdateSuccess"), nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *ClientController) getClientIps(c *gin.Context) {
|
func (a *ClientController) getIps(c *gin.Context) {
|
||||||
email := c.Param("email")
|
email := c.Param("email")
|
||||||
ips, err := a.inboundService.GetInboundClientIps(email)
|
ips, err := a.inboundService.GetInboundClientIps(email)
|
||||||
if err != nil || ips == "" {
|
if err != nil || ips == "" {
|
||||||
|
|
@ -249,7 +249,7 @@ func (a *ClientController) getClientIps(c *gin.Context) {
|
||||||
jsonObj(c, ips, nil)
|
jsonObj(c, ips, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *ClientController) clearClientIps(c *gin.Context) {
|
func (a *ClientController) clearIps(c *gin.Context) {
|
||||||
email := c.Param("email")
|
email := c.Param("email")
|
||||||
if err := a.inboundService.ClearClientIps(email); err != nil {
|
if err := a.inboundService.ClearClientIps(email); err != nil {
|
||||||
jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.updateSuccess"), err)
|
jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.updateSuccess"), err)
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ type Period string
|
||||||
// PeriodicTrafficResetJob resets traffic statistics for inbounds based on their configured reset period.
|
// PeriodicTrafficResetJob resets traffic statistics for inbounds based on their configured reset period.
|
||||||
type PeriodicTrafficResetJob struct {
|
type PeriodicTrafficResetJob struct {
|
||||||
inboundService service.InboundService
|
inboundService service.InboundService
|
||||||
|
clientService service.ClientService
|
||||||
period Period
|
period Period
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -42,7 +43,7 @@ func (j *PeriodicTrafficResetJob) Run() {
|
||||||
logger.Warning("Failed to reset traffic for inbound", inbound.Id, ":", resetInboundErr)
|
logger.Warning("Failed to reset traffic for inbound", inbound.Id, ":", resetInboundErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
resetClientErr := j.inboundService.ResetAllClientTraffics(inbound.Id)
|
resetClientErr := j.clientService.ResetAllClientTraffics(&j.inboundService, inbound.Id)
|
||||||
if resetClientErr != nil {
|
if resetClientErr != nil {
|
||||||
logger.Warning("Failed to reset traffic for all users of inbound", inbound.Id, ":", resetClientErr)
|
logger.Warning("Failed to reset traffic for all users of inbound", inbound.Id, ":", resetClientErr)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -569,6 +569,71 @@ func (s *ClientService) DelDepleted(inboundSvc *InboundService) (int, bool, erro
|
||||||
return deleted, needRestart, nil
|
return deleted, needRestart, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *ClientService) ResetAllClientTraffics(inboundSvc *InboundService, id int) error {
|
||||||
|
return submitTrafficWrite(func() error {
|
||||||
|
return s.resetAllClientTrafficsLocked(inboundSvc, id)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ClientService) resetAllClientTrafficsLocked(inboundSvc *InboundService, id int) error {
|
||||||
|
db := database.GetDB()
|
||||||
|
now := time.Now().Unix() * 1000
|
||||||
|
|
||||||
|
if err := db.Transaction(func(tx *gorm.DB) error {
|
||||||
|
whereText := "inbound_id "
|
||||||
|
if id == -1 {
|
||||||
|
whereText += " > ?"
|
||||||
|
} else {
|
||||||
|
whereText += " = ?"
|
||||||
|
}
|
||||||
|
|
||||||
|
result := tx.Model(xray.ClientTraffic{}).
|
||||||
|
Where(whereText, id).
|
||||||
|
Updates(map[string]any{"enable": true, "up": 0, "down": 0})
|
||||||
|
|
||||||
|
if result.Error != nil {
|
||||||
|
return result.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
inboundWhereText := "id "
|
||||||
|
if id == -1 {
|
||||||
|
inboundWhereText += " > ?"
|
||||||
|
} else {
|
||||||
|
inboundWhereText += " = ?"
|
||||||
|
}
|
||||||
|
|
||||||
|
result = tx.Model(model.Inbound{}).
|
||||||
|
Where(inboundWhereText, id).
|
||||||
|
Update("last_traffic_reset_time", now)
|
||||||
|
|
||||||
|
return result.Error
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var inbounds []model.Inbound
|
||||||
|
q := db.Model(model.Inbound{}).Where("node_id IS NOT NULL")
|
||||||
|
if id != -1 {
|
||||||
|
q = q.Where("id = ?", id)
|
||||||
|
}
|
||||||
|
if err := q.Find(&inbounds).Error; err != nil {
|
||||||
|
logger.Warning("ResetAllClientTraffics: discover node inbounds failed:", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
for i := range inbounds {
|
||||||
|
ib := &inbounds[i]
|
||||||
|
rt, rterr := inboundSvc.runtimeFor(ib)
|
||||||
|
if rterr != nil {
|
||||||
|
logger.Warning("ResetAllClientTraffics: runtime lookup for inbound", ib.Id, "failed:", rterr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if e := rt.ResetInboundClientTraffics(context.Background(), ib); e != nil {
|
||||||
|
logger.Warning("ResetAllClientTraffics: remote propagation to", rt.Name(), "failed:", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *ClientService) ResetAllTraffics() (bool, error) {
|
func (s *ClientService) ResetAllTraffics() (bool, error) {
|
||||||
res := database.GetDB().Model(&xray.ClientTraffic{}).
|
res := database.GetDB().Model(&xray.ClientTraffic{}).
|
||||||
Where("1 = 1").
|
Where("1 = 1").
|
||||||
|
|
|
||||||
|
|
@ -2101,73 +2101,6 @@ func (s *InboundService) resetClientTrafficLocked(id int, clientEmail string) (b
|
||||||
return needRestart, nil
|
return needRestart, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InboundService) ResetAllClientTraffics(id int) error {
|
|
||||||
return submitTrafficWrite(func() error {
|
|
||||||
return s.resetAllClientTrafficsLocked(id)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *InboundService) resetAllClientTrafficsLocked(id int) error {
|
|
||||||
db := database.GetDB()
|
|
||||||
now := time.Now().Unix() * 1000
|
|
||||||
|
|
||||||
if err := db.Transaction(func(tx *gorm.DB) error {
|
|
||||||
whereText := "inbound_id "
|
|
||||||
if id == -1 {
|
|
||||||
whereText += " > ?"
|
|
||||||
} else {
|
|
||||||
whereText += " = ?"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset client traffics
|
|
||||||
result := tx.Model(xray.ClientTraffic{}).
|
|
||||||
Where(whereText, id).
|
|
||||||
Updates(map[string]any{"enable": true, "up": 0, "down": 0})
|
|
||||||
|
|
||||||
if result.Error != nil {
|
|
||||||
return result.Error
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update lastTrafficResetTime for the inbound(s)
|
|
||||||
inboundWhereText := "id "
|
|
||||||
if id == -1 {
|
|
||||||
inboundWhereText += " > ?"
|
|
||||||
} else {
|
|
||||||
inboundWhereText += " = ?"
|
|
||||||
}
|
|
||||||
|
|
||||||
result = tx.Model(model.Inbound{}).
|
|
||||||
Where(inboundWhereText, id).
|
|
||||||
Update("last_traffic_reset_time", now)
|
|
||||||
|
|
||||||
return result.Error
|
|
||||||
}); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var inbounds []model.Inbound
|
|
||||||
q := db.Model(model.Inbound{}).Where("node_id IS NOT NULL")
|
|
||||||
if id != -1 {
|
|
||||||
q = q.Where("id = ?", id)
|
|
||||||
}
|
|
||||||
if err := q.Find(&inbounds).Error; err != nil {
|
|
||||||
logger.Warning("ResetAllClientTraffics: discover node inbounds failed:", err)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
for i := range inbounds {
|
|
||||||
ib := &inbounds[i]
|
|
||||||
rt, rterr := s.runtimeFor(ib)
|
|
||||||
if rterr != nil {
|
|
||||||
logger.Warning("ResetAllClientTraffics: runtime lookup for inbound", ib.Id, "failed:", rterr)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if e := rt.ResetInboundClientTraffics(context.Background(), ib); e != nil {
|
|
||||||
logger.Warning("ResetAllClientTraffics: remote propagation to", rt.Name(), "failed:", e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *InboundService) ResetAllTraffics() error {
|
func (s *InboundService) ResetAllTraffics() error {
|
||||||
return submitTrafficWrite(func() error {
|
return submitTrafficWrite(func() error {
|
||||||
return s.resetAllTrafficsLocked()
|
return s.resetAllTrafficsLocked()
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue