node_contract.go 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. package service
  2. import (
  3. "strings"
  4. "github.com/mhsanaei/3x-ui/v3/internal/database/model"
  5. "github.com/mhsanaei/3x-ui/v3/internal/util/common"
  6. )
  7. // NodeView is the browser/API read contract for nodes. Credentials are
  8. // write-only: responses expose only whether a node has a token configured.
  9. type NodeView struct {
  10. Id int `json:"id" example:"1"`
  11. Name string `json:"name" example:"edge-1"`
  12. Remark string `json:"remark" example:"Primary edge"`
  13. Scheme string `json:"scheme" example:"https"`
  14. Address string `json:"address" example:"node.example.com"`
  15. Port int `json:"port" example:"2053"`
  16. BasePath string `json:"basePath" example:"/"`
  17. HasApiToken bool `json:"hasApiToken" example:"true"`
  18. Enable bool `json:"enable" example:"true"`
  19. AllowPrivateAddress bool `json:"allowPrivateAddress" example:"false"`
  20. TlsVerifyMode string `json:"tlsVerifyMode" example:"verify"`
  21. PinnedCertSha256 string `json:"pinnedCertSha256" example:""`
  22. InboundSyncMode string `json:"inboundSyncMode" example:"all"`
  23. InboundTags []string `json:"inboundTags" example:"[\"in-443-tcp\"]"`
  24. OutboundTag string `json:"outboundTag" example:"direct"`
  25. Guid string `json:"guid" example:"node-guid"`
  26. Status string `json:"status" example:"online"`
  27. LastHeartbeat int64 `json:"lastHeartbeat" example:"1700000000"`
  28. LatencyMs int `json:"latencyMs" example:"42"`
  29. XrayVersion string `json:"xrayVersion" example:"25.10.31"`
  30. PanelVersion string `json:"panelVersion" example:"v3.x.x"`
  31. CpuPct float64 `json:"cpuPct" example:"12.5"`
  32. MemPct float64 `json:"memPct" example:"45.2"`
  33. UptimeSecs uint64 `json:"uptimeSecs" example:"86400"`
  34. NetUp uint64 `json:"netUp" example:"2097152"`
  35. NetDown uint64 `json:"netDown" example:"1048576"`
  36. LastError string `json:"lastError" example:""`
  37. XrayState string `json:"xrayState" example:"running"`
  38. XrayError string `json:"xrayError" example:""`
  39. ConfigDirty bool `json:"configDirty" example:"false"`
  40. ConfigDirtyAt int64 `json:"configDirtyAt" example:"0"`
  41. InboundCount int `json:"inboundCount" example:"3"`
  42. ClientCount int `json:"clientCount" example:"25"`
  43. OnlineCount int `json:"onlineCount" example:"5"`
  44. ActiveCount int `json:"activeCount" example:"20"`
  45. DisabledCount int `json:"disabledCount" example:"2"`
  46. DepletedCount int `json:"depletedCount" example:"1"`
  47. ParentGuid string `json:"parentGuid,omitempty" example:""`
  48. Transitive bool `json:"transitive,omitempty" example:"false"`
  49. CreatedAt int64 `json:"createdAt" example:"1700000000"`
  50. UpdatedAt int64 `json:"updatedAt" example:"1700003600"`
  51. }
  52. func toNodeView(n *model.Node) *NodeView {
  53. if n == nil {
  54. return nil
  55. }
  56. return &NodeView{
  57. Id: n.Id,
  58. Name: n.Name,
  59. Remark: n.Remark,
  60. Scheme: n.Scheme,
  61. Address: n.Address,
  62. Port: n.Port,
  63. BasePath: n.BasePath,
  64. HasApiToken: n.ApiToken != "",
  65. Enable: n.Enable,
  66. AllowPrivateAddress: n.AllowPrivateAddress,
  67. TlsVerifyMode: n.TlsVerifyMode,
  68. PinnedCertSha256: n.PinnedCertSha256,
  69. InboundSyncMode: n.InboundSyncMode,
  70. InboundTags: n.InboundTags,
  71. OutboundTag: n.OutboundTag,
  72. Guid: n.Guid,
  73. Status: n.Status,
  74. LastHeartbeat: n.LastHeartbeat,
  75. LatencyMs: n.LatencyMs,
  76. XrayVersion: n.XrayVersion,
  77. PanelVersion: n.PanelVersion,
  78. CpuPct: n.CpuPct,
  79. MemPct: n.MemPct,
  80. UptimeSecs: n.UptimeSecs,
  81. NetUp: n.NetUp,
  82. NetDown: n.NetDown,
  83. LastError: n.LastError,
  84. XrayState: n.XrayState,
  85. XrayError: n.XrayError,
  86. ConfigDirty: n.ConfigDirty,
  87. ConfigDirtyAt: n.ConfigDirtyAt,
  88. InboundCount: n.InboundCount,
  89. ClientCount: n.ClientCount,
  90. OnlineCount: n.OnlineCount,
  91. ActiveCount: n.ActiveCount,
  92. DisabledCount: n.DisabledCount,
  93. DepletedCount: n.DepletedCount,
  94. ParentGuid: n.ParentGuid,
  95. Transitive: n.Transitive,
  96. CreatedAt: n.CreatedAt,
  97. UpdatedAt: n.UpdatedAt,
  98. }
  99. }
  100. func toNodeViews(nodes []*model.Node) []*NodeView {
  101. views := make([]*NodeView, 0, len(nodes))
  102. for _, node := range nodes {
  103. views = append(views, toNodeView(node))
  104. }
  105. return views
  106. }
  107. // NodeMutationRequest is the node write/probe contract. ApiToken is accepted
  108. // only as input. On update, nil means keep the stored token; replacement and
  109. // clearing are explicit and mutually exclusive.
  110. type NodeMutationRequest struct {
  111. Id int `json:"id" form:"id"`
  112. Name string `json:"name" form:"name" validate:"required"`
  113. Remark string `json:"remark" form:"remark"`
  114. Scheme string `json:"scheme" form:"scheme" validate:"omitempty,oneof=http https"`
  115. Address string `json:"address" form:"address" validate:"required"`
  116. Port int `json:"port" form:"port" validate:"gte=1,lte=65535"`
  117. BasePath string `json:"basePath" form:"basePath"`
  118. ApiToken *string `json:"apiToken,omitempty" form:"apiToken"`
  119. ClearApiToken bool `json:"clearApiToken,omitempty" form:"clearApiToken"`
  120. Enable bool `json:"enable" form:"enable"`
  121. AllowPrivateAddress bool `json:"allowPrivateAddress" form:"allowPrivateAddress"`
  122. TlsVerifyMode string `json:"tlsVerifyMode" form:"tlsVerifyMode" validate:"omitempty,oneof=verify skip pin mtls"`
  123. PinnedCertSha256 string `json:"pinnedCertSha256" form:"pinnedCertSha256"`
  124. InboundSyncMode string `json:"inboundSyncMode" form:"inboundSyncMode" validate:"omitempty,oneof=all selected"`
  125. InboundTags []string `json:"inboundTags" form:"inboundTags"`
  126. OutboundTag string `json:"outboundTag" form:"outboundTag"`
  127. }
  128. func (r *NodeMutationRequest) validateCredentials(create bool) error {
  129. if r == nil {
  130. return common.NewError("node request is required")
  131. }
  132. if r.ApiToken != nil && r.ClearApiToken {
  133. return common.NewError("apiToken and clearApiToken are mutually exclusive")
  134. }
  135. if r.ApiToken != nil {
  136. *r.ApiToken = strings.TrimSpace(*r.ApiToken)
  137. if *r.ApiToken == "" {
  138. if create {
  139. return common.NewError("apiToken is required unless mtls is enabled")
  140. }
  141. r.ApiToken = nil
  142. }
  143. }
  144. if create {
  145. if r.ClearApiToken {
  146. return common.NewError("credentials cannot be cleared while creating a node")
  147. }
  148. if r.ApiToken == nil && r.TlsVerifyMode != "mtls" {
  149. return common.NewError("apiToken is required unless mtls is enabled")
  150. }
  151. }
  152. if r.ClearApiToken && r.Enable && r.TlsVerifyMode != "mtls" {
  153. return common.NewError("disable the node or enable mtls before clearing its apiToken")
  154. }
  155. return nil
  156. }
  157. func (r *NodeMutationRequest) toNode() *model.Node {
  158. n := &model.Node{
  159. Id: r.Id,
  160. Name: r.Name,
  161. Remark: r.Remark,
  162. Scheme: r.Scheme,
  163. Address: r.Address,
  164. Port: r.Port,
  165. BasePath: r.BasePath,
  166. Enable: r.Enable,
  167. AllowPrivateAddress: r.AllowPrivateAddress,
  168. TlsVerifyMode: r.TlsVerifyMode,
  169. PinnedCertSha256: r.PinnedCertSha256,
  170. InboundSyncMode: r.InboundSyncMode,
  171. InboundTags: r.InboundTags,
  172. OutboundTag: r.OutboundTag,
  173. }
  174. if r.ApiToken != nil {
  175. n.ApiToken = *r.ApiToken
  176. }
  177. return n
  178. }