node_tree.go 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. package service
  2. import (
  3. "context"
  4. "sync"
  5. "time"
  6. "github.com/mhsanaei/3x-ui/v3/database"
  7. "github.com/mhsanaei/3x-ui/v3/database/model"
  8. "github.com/mhsanaei/3x-ui/v3/web/runtime"
  9. )
  10. // LocalDescendants returns this panel's read-only summaries of the nodes it
  11. // directly manages, so a parent panel can surface them as transitive sub-nodes
  12. // (#4983). Only nodes with a known GUID are included — a stable identity is
  13. // required to attribute them one hop up. Not recursive: each panel reports its
  14. // own direct nodes, and a master walks one level via each direct node's
  15. // endpoint, which covers the Node1 -> Node2 -> Node3 case.
  16. func (s *NodeService) LocalDescendants() ([]model.NodeSummary, error) {
  17. selfGuid, _ := (&SettingService{}).GetPanelGuid()
  18. db := database.GetDB()
  19. var nodes []*model.Node
  20. if err := db.Model(model.Node{}).Order("id asc").Find(&nodes).Error; err != nil {
  21. return nil, err
  22. }
  23. out := make([]model.NodeSummary, 0, len(nodes))
  24. for _, n := range nodes {
  25. if n.Guid == "" {
  26. continue
  27. }
  28. out = append(out, model.NodeSummary{
  29. Guid: n.Guid,
  30. ParentGuid: selfGuid,
  31. Name: n.Name,
  32. Address: n.Address,
  33. Scheme: n.Scheme,
  34. Port: n.Port,
  35. Status: n.Status,
  36. LastHeartbeat: n.LastHeartbeat,
  37. LatencyMs: n.LatencyMs,
  38. PanelVersion: n.PanelVersion,
  39. XrayVersion: n.XrayVersion,
  40. XrayState: n.XrayState,
  41. XrayError: n.XrayError,
  42. })
  43. }
  44. return out, nil
  45. }
  46. var (
  47. nodeDescendantsMu sync.RWMutex
  48. nodeDescendantsCache = map[int][]model.NodeSummary{}
  49. )
  50. // RefreshDescendants pulls a direct node's published sub-node summaries and
  51. // caches them keyed by node id. Best-effort: a fetch error keeps the last good
  52. // set (the node may be briefly unreachable). Called from the heartbeat job.
  53. func (s *NodeService) RefreshDescendants(ctx context.Context, n *model.Node) {
  54. if n == nil {
  55. return
  56. }
  57. mgr := runtime.GetManager()
  58. if mgr == nil {
  59. return
  60. }
  61. rt, err := mgr.RemoteFor(n)
  62. if err != nil {
  63. return
  64. }
  65. summaries, err := rt.GetDescendants(ctx)
  66. if err != nil {
  67. return
  68. }
  69. nodeDescendantsMu.Lock()
  70. if len(summaries) == 0 {
  71. delete(nodeDescendantsCache, n.Id)
  72. } else {
  73. nodeDescendantsCache[n.Id] = summaries
  74. }
  75. nodeDescendantsMu.Unlock()
  76. }
  77. // ClearDescendants drops a node's cached sub-node summaries (its probe failed).
  78. func (s *NodeService) ClearDescendants(nodeID int) {
  79. nodeDescendantsMu.Lock()
  80. delete(nodeDescendantsCache, nodeID)
  81. nodeDescendantsMu.Unlock()
  82. }
  83. func cachedDescendants() []model.NodeSummary {
  84. nodeDescendantsMu.RLock()
  85. defer nodeDescendantsMu.RUnlock()
  86. out := make([]model.NodeSummary, 0)
  87. for _, list := range nodeDescendantsCache {
  88. out = append(out, list...)
  89. }
  90. return out
  91. }
  92. // GetNodeTree returns the direct nodes plus any transitive sub-nodes learned
  93. // from them, with per-GUID counts so each node shows only the inbounds/online
  94. // it physically hosts (#4983). Direct nodes carry the master's own GUID as
  95. // ParentGuid; a transitive node carries its parent node's GUID. Transitive
  96. // nodes are read-only projections (Id == 0). Used by the Nodes page and the
  97. // heartbeat broadcast — never for probing/syncing, which stay on GetAll.
  98. func (s *NodeService) GetNodeTree() ([]*model.Node, error) {
  99. nodes, err := s.GetAll()
  100. if err != nil {
  101. return nodes, err
  102. }
  103. selfGuid, _ := (&SettingService{}).GetPanelGuid()
  104. directGuids := make(map[string]struct{}, len(nodes))
  105. for _, n := range nodes {
  106. n.ParentGuid = selfGuid
  107. if n.Guid != "" {
  108. directGuids[n.Guid] = struct{}{}
  109. }
  110. }
  111. seen := make(map[string]struct{})
  112. var transitive []*model.Node
  113. for _, sum := range cachedDescendants() {
  114. if sum.Guid == "" {
  115. continue
  116. }
  117. if _, ok := directGuids[sum.Guid]; ok {
  118. continue // already shown as a direct node
  119. }
  120. if _, ok := seen[sum.Guid]; ok {
  121. continue
  122. }
  123. seen[sum.Guid] = struct{}{}
  124. transitive = append(transitive, &model.Node{
  125. Guid: sum.Guid,
  126. ParentGuid: sum.ParentGuid,
  127. Name: sum.Name,
  128. Address: sum.Address,
  129. Scheme: sum.Scheme,
  130. Port: sum.Port,
  131. Status: sum.Status,
  132. LastHeartbeat: sum.LastHeartbeat,
  133. LatencyMs: sum.LatencyMs,
  134. PanelVersion: sum.PanelVersion,
  135. XrayVersion: sum.XrayVersion,
  136. XrayState: sum.XrayState,
  137. XrayError: sum.XrayError,
  138. Transitive: true,
  139. })
  140. }
  141. if len(transitive) == 0 {
  142. return nodes, nil
  143. }
  144. all := make([]*model.Node, 0, len(nodes)+len(transitive))
  145. all = append(all, nodes...)
  146. all = append(all, transitive...)
  147. s.recountByGuid(all, selfGuid)
  148. return all, nil
  149. }
  150. // recountByGuid recomputes InboundCount/OnlineCount/DepletedCount for every node
  151. // in the tree, keyed by the GUID that physically hosts each inbound, so a direct
  152. // node shows only its own inbounds and each transitive node shows its own
  153. // (#4983). In a flat topology the per-GUID and per-node-id counts coincide, so
  154. // this only changes behaviour once a transitive node exists.
  155. func (s *NodeService) recountByGuid(nodes []*model.Node, selfGuid string) {
  156. db := database.GetDB()
  157. type ibRow struct {
  158. Id int
  159. NodeID *int `gorm:"column:node_id"`
  160. OriginNodeGuid string `gorm:"column:origin_node_guid"`
  161. }
  162. var ibRows []ibRow
  163. if err := db.Table("inbounds").Select("id, node_id, origin_node_guid").Scan(&ibRows).Error; err != nil {
  164. return
  165. }
  166. effByInbound := make(map[int]string, len(ibRows))
  167. inboundCountByGuid := make(map[string]int)
  168. ids := make([]int, 0, len(ibRows))
  169. for _, r := range ibRows {
  170. guid := r.OriginNodeGuid
  171. if guid == "" {
  172. if r.NodeID != nil {
  173. guid = synthNodeGuid(*r.NodeID)
  174. } else {
  175. guid = selfGuid
  176. }
  177. }
  178. effByInbound[r.Id] = guid
  179. inboundCountByGuid[guid]++
  180. ids = append(ids, r.Id)
  181. }
  182. now := time.Now().UnixMilli()
  183. depletedByGuid := make(map[string]int)
  184. if len(ids) > 0 {
  185. type tRow struct {
  186. InboundID int `gorm:"column:inbound_id"`
  187. Enable bool
  188. Total int64
  189. Up int64
  190. Down int64
  191. ExpiryTime int64 `gorm:"column:expiry_time"`
  192. }
  193. var tRows []tRow
  194. if err := db.Table("client_traffics").
  195. Select("inbound_id, enable, total, up, down, expiry_time").
  196. Where("inbound_id IN ?", ids).Scan(&tRows).Error; err == nil {
  197. for _, row := range tRows {
  198. guid, ok := effByInbound[row.InboundID]
  199. if !ok {
  200. continue
  201. }
  202. expired := row.ExpiryTime > 0 && row.ExpiryTime <= now
  203. exhausted := row.Total > 0 && row.Up+row.Down >= row.Total
  204. if expired || exhausted || !row.Enable {
  205. depletedByGuid[guid]++
  206. }
  207. }
  208. }
  209. }
  210. onlineByGuid := s.onlineEmailsByGuid()
  211. for _, n := range nodes {
  212. guid := effectiveNodeGuid(n)
  213. n.InboundCount = inboundCountByGuid[guid]
  214. n.OnlineCount = len(onlineByGuid[guid])
  215. n.DepletedCount = depletedByGuid[guid]
  216. }
  217. }