mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-08-18 01:00:59 +00:00
fix(groups): keep group traffic totals stable across client resets and deletes
ListGroups displays live_sum(client_traffics) minus the group's stored reset baseline, but only ResetGroupTraffic ever moved the baseline. Any client-level operation that zeroed or deleted traffic rows (single/bulk reset, client delete, removing a client's last inbound) shrank the live sum and silently subtracted that client's history from the group total. Shift the baseline down by the removed counters inside the same transaction, so group totals only change through group reset. Derived groups without a stored row get one with a negative baseline, which the existing clamp handles. Closes #5675
This commit is contained in:
@@ -8,6 +8,8 @@ import (
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/database"
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/util/common"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type GroupSummary struct {
|
||||
@@ -69,6 +71,57 @@ func (s *ClientService) ListGroups() ([]GroupSummary, error) {
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// adjustGroupBaselinesForRemovedTraffic shifts group baselines down by the clients'
|
||||
// current counters so ListGroups totals survive a traffic reset or client delete (#5675).
|
||||
func adjustGroupBaselinesForRemovedTraffic(tx *gorm.DB, emails []string) error {
|
||||
if len(emails) == 0 {
|
||||
return nil
|
||||
}
|
||||
type groupDelta struct {
|
||||
Name string
|
||||
Up int64
|
||||
Down int64
|
||||
}
|
||||
totals := make(map[string]*groupDelta)
|
||||
for _, batch := range chunkStrings(emails, sqlInChunk) {
|
||||
var part []groupDelta
|
||||
if err := tx.Table("clients AS c").
|
||||
Select("c.group_name AS name, COALESCE(SUM(ct.up), 0) AS up, COALESCE(SUM(ct.down), 0) AS down").
|
||||
Joins("JOIN client_traffics ct ON ct.email = c.email").
|
||||
Where("c.group_name <> '' AND c.email IN ?", batch).
|
||||
Group("c.group_name").
|
||||
Scan(&part).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
for i := range part {
|
||||
if agg, ok := totals[part[i].Name]; ok {
|
||||
agg.Up += part[i].Up
|
||||
agg.Down += part[i].Down
|
||||
} else {
|
||||
totals[part[i].Name] = &part[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
for name, d := range totals {
|
||||
if d.Up == 0 && d.Down == 0 {
|
||||
continue
|
||||
}
|
||||
res := tx.Model(&model.ClientGroup{}).Where("name = ?", name).Updates(map[string]any{
|
||||
"reset_up": gorm.Expr("reset_up - ?", d.Up),
|
||||
"reset_down": gorm.Expr("reset_down - ?", d.Down),
|
||||
})
|
||||
if res.Error != nil {
|
||||
return res.Error
|
||||
}
|
||||
if res.RowsAffected == 0 {
|
||||
if err := tx.Create(&model.ClientGroup{Name: name, ResetUp: -d.Up, ResetDown: -d.Down}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ClientService) EmailsByGroup(name string) ([]string, error) {
|
||||
name = strings.TrimSpace(name)
|
||||
if name == "" {
|
||||
|
||||
Reference in New Issue
Block a user