Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/MHSanaei/3x-ui.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAli Golzar <57574919+aliglzr@users.noreply.github.com>2025-08-27 20:30:49 +0300
committerGitHub <noreply@github.com>2025-08-27 20:30:49 +0300
commit21983971971b14377b36c8db92c8603f723f955d (patch)
tree1bdc1838e95c52f6a87b96c1d0b0708dcde806f2 /web/service
parentd10c312e62e0abf6da64e21a55c51151e23d9929 (diff)
Created / Updated fields for clients (#3384)
* feat(backend): add created_at/updated_at to clients and maintain on create/update backfill existing clients and set updated_at on mutations * feat(frontend): carry created_at/updated_at in client models and round-trip via JSON * feat(frontend): display Created and Updated columns in client table with proper date formatting * i18n: add pages.inbounds.createdAt/updatedAt across all locales * Update inbound.go Remove duplicate code
Diffstat (limited to 'web/service')
-rw-r--r--web/service/inbound.go117
1 files changed, 117 insertions, 0 deletions
diff --git a/web/service/inbound.go b/web/service/inbound.go
index 6e10e798..4ef5fce3 100644
--- a/web/service/inbound.go
+++ b/web/service/inbound.go
@@ -175,6 +175,30 @@ func (s *InboundService) AddInbound(inbound *model.Inbound) (*model.Inbound, boo
return inbound, false, err
}
+ // Ensure created_at and updated_at on clients in settings
+ if len(clients) > 0 {
+ var settings map[string]any
+ if err2 := json.Unmarshal([]byte(inbound.Settings), &settings); err2 == nil && settings != nil {
+ now := time.Now().Unix() * 1000
+ updatedClients := make([]model.Client, 0, len(clients))
+ for _, c := range clients {
+ if c.CreatedAt == 0 {
+ c.CreatedAt = now
+ }
+ c.UpdatedAt = now
+ updatedClients = append(updatedClients, c)
+ }
+ settings["clients"] = updatedClients
+ if bs, err3 := json.MarshalIndent(settings, "", " "); err3 == nil {
+ inbound.Settings = string(bs)
+ } else {
+ logger.Debug("Unable to marshal inbound settings with timestamps:", err3)
+ }
+ } else if err2 != nil {
+ logger.Debug("Unable to parse inbound settings for timestamps:", err2)
+ }
+ }
+
// Secure client ID
for _, client := range clients {
switch inbound.Protocol {
@@ -320,6 +344,53 @@ func (s *InboundService) UpdateInbound(inbound *model.Inbound) (*model.Inbound,
return inbound, false, err
}
+ // Ensure created_at and updated_at exist in inbound.Settings clients
+ {
+ var oldSettings map[string]any
+ _ = json.Unmarshal([]byte(oldInbound.Settings), &oldSettings)
+ emailToCreated := map[string]int64{}
+ if oldSettings != nil {
+ if oc, ok := oldSettings["clients"].([]any); ok {
+ for _, it := range oc {
+ if m, ok2 := it.(map[string]any); ok2 {
+ if email, ok3 := m["email"].(string); ok3 {
+ switch v := m["created_at"].(type) {
+ case float64:
+ emailToCreated[email] = int64(v)
+ case int64:
+ emailToCreated[email] = v
+ }
+ }
+ }
+ }
+ }
+ }
+ var newSettings map[string]any
+ if err2 := json.Unmarshal([]byte(inbound.Settings), &newSettings); err2 == nil && newSettings != nil {
+ now := time.Now().Unix() * 1000
+ if nSlice, ok := newSettings["clients"].([]any); ok {
+ for i := range nSlice {
+ if m, ok2 := nSlice[i].(map[string]any); ok2 {
+ email, _ := m["email"].(string)
+ if _, ok3 := m["created_at"]; !ok3 {
+ if v, ok4 := emailToCreated[email]; ok4 && v > 0 {
+ m["created_at"] = v
+ } else {
+ m["created_at"] = now
+ }
+ }
+ m["updated_at"] = now
+ nSlice[i] = m
+ }
+ }
+ newSettings["clients"] = nSlice
+ if bs, err3 := json.MarshalIndent(newSettings, "", " "); err3 == nil {
+ inbound.Settings = string(bs)
+ }
+ }
+ }
+ }
+
oldInbound.Up = inbound.Up
oldInbound.Down = inbound.Down
oldInbound.Total = inbound.Total
@@ -422,6 +493,17 @@ func (s *InboundService) AddInboundClient(data *model.Inbound) (bool, error) {
}
interfaceClients := settings["clients"].([]any)
+ // Add timestamps for new clients being appended
+ nowTs := time.Now().Unix() * 1000
+ for i := range interfaceClients {
+ if cm, ok := interfaceClients[i].(map[string]any); ok {
+ if _, ok2 := cm["created_at"]; !ok2 {
+ cm["created_at"] = nowTs
+ }
+ cm["updated_at"] = nowTs
+ interfaceClients[i] = cm
+ }
+ }
existEmail, err := s.checkEmailsExistForClients(clients)
if err != nil {
return false, err
@@ -672,6 +754,25 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId strin
return false, err
}
settingsClients := oldSettings["clients"].([]any)
+ // Preserve created_at and set updated_at for the replacing client
+ var preservedCreated any
+ if clientIndex >= 0 && clientIndex < len(settingsClients) {
+ if oldMap, ok := settingsClients[clientIndex].(map[string]any); ok {
+ if v, ok2 := oldMap["created_at"]; ok2 {
+ preservedCreated = v
+ }
+ }
+ }
+ if len(interfaceClients) > 0 {
+ if newMap, ok := interfaceClients[0].(map[string]any); ok {
+ if preservedCreated == nil {
+ preservedCreated = time.Now().Unix() * 1000
+ }
+ newMap["created_at"] = preservedCreated
+ newMap["updated_at"] = time.Now().Unix() * 1000
+ interfaceClients[0] = newMap
+ }
+ }
settingsClients[clientIndex] = interfaceClients[0]
oldSettings["clients"] = settingsClients
@@ -909,10 +1010,16 @@ func (s *InboundService) adjustTraffics(tx *gorm.DB, dbClientTraffics []*xray.Cl
oldExpiryTime := c["expiryTime"].(float64)
newExpiryTime := (time.Now().Unix() * 1000) - int64(oldExpiryTime)
c["expiryTime"] = newExpiryTime
+ c["updated_at"] = time.Now().Unix() * 1000
dbClientTraffics[traffic_index].ExpiryTime = newExpiryTime
break
}
}
+ // Backfill created_at and updated_at
+ if _, ok := c["created_at"]; !ok {
+ c["created_at"] = time.Now().Unix() * 1000
+ }
+ c["updated_at"] = time.Now().Unix() * 1000
newClients = append(newClients, any(c))
}
settings["clients"] = newClients
@@ -1274,6 +1381,7 @@ func (s *InboundService) SetClientTelegramUserID(trafficId int, tgId int64) (boo
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))
}
}
@@ -1360,6 +1468,7 @@ func (s *InboundService) ToggleClientEnableByEmail(clientEmail string) (bool, bo
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))
}
}
@@ -1423,6 +1532,7 @@ func (s *InboundService) ResetClientIpLimitByEmail(clientEmail string, count int
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))
}
}
@@ -1481,6 +1591,7 @@ func (s *InboundService) ResetClientExpiryTimeByEmail(clientEmail string, expiry
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))
}
}
@@ -1542,6 +1653,7 @@ func (s *InboundService) ResetClientTrafficLimitByEmail(clientEmail string, tota
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))
}
}
@@ -1962,6 +2074,11 @@ func (s *InboundService) MigrationRequirements() {
c["flow"] = ""
}
}
+ // Backfill created_at and updated_at
+ if _, ok := c["created_at"]; !ok {
+ c["created_at"] = time.Now().Unix() * 1000
+ }
+ c["updated_at"] = time.Now().Unix() * 1000
newClients = append(newClients, any(c))
}
settings["clients"] = newClients