cluster_handlers.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430
  1. package handlers
  2. import (
  3. "net/http"
  4. "strconv"
  5. "github.com/gin-gonic/gin"
  6. "github.com/seaweedfs/seaweedfs/weed/admin/dash"
  7. "github.com/seaweedfs/seaweedfs/weed/admin/view/app"
  8. "github.com/seaweedfs/seaweedfs/weed/admin/view/layout"
  9. )
  10. // ClusterHandlers contains all the HTTP handlers for cluster management
  11. type ClusterHandlers struct {
  12. adminServer *dash.AdminServer
  13. }
  14. // NewClusterHandlers creates a new instance of ClusterHandlers
  15. func NewClusterHandlers(adminServer *dash.AdminServer) *ClusterHandlers {
  16. return &ClusterHandlers{
  17. adminServer: adminServer,
  18. }
  19. }
  20. // ShowClusterVolumeServers renders the cluster volume servers page
  21. func (h *ClusterHandlers) ShowClusterVolumeServers(c *gin.Context) {
  22. // Get cluster volume servers data
  23. volumeServersData, err := h.adminServer.GetClusterVolumeServers()
  24. if err != nil {
  25. c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get cluster volume servers: " + err.Error()})
  26. return
  27. }
  28. // Set username
  29. username := c.GetString("username")
  30. if username == "" {
  31. username = "admin"
  32. }
  33. volumeServersData.Username = username
  34. // Render HTML template
  35. c.Header("Content-Type", "text/html")
  36. volumeServersComponent := app.ClusterVolumeServers(*volumeServersData)
  37. layoutComponent := layout.Layout(c, volumeServersComponent)
  38. err = layoutComponent.Render(c.Request.Context(), c.Writer)
  39. if err != nil {
  40. c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to render template: " + err.Error()})
  41. return
  42. }
  43. }
  44. // ShowClusterVolumes renders the cluster volumes page
  45. func (h *ClusterHandlers) ShowClusterVolumes(c *gin.Context) {
  46. // Get pagination and sorting parameters from query string
  47. page := 1
  48. if p := c.Query("page"); p != "" {
  49. if parsed, err := strconv.Atoi(p); err == nil && parsed > 0 {
  50. page = parsed
  51. }
  52. }
  53. pageSize := 100
  54. if ps := c.Query("pageSize"); ps != "" {
  55. if parsed, err := strconv.Atoi(ps); err == nil && parsed > 0 && parsed <= 1000 {
  56. pageSize = parsed
  57. }
  58. }
  59. sortBy := c.DefaultQuery("sortBy", "id")
  60. sortOrder := c.DefaultQuery("sortOrder", "asc")
  61. collection := c.Query("collection") // Optional collection filter
  62. // Get cluster volumes data
  63. volumesData, err := h.adminServer.GetClusterVolumes(page, pageSize, sortBy, sortOrder, collection)
  64. if err != nil {
  65. c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get cluster volumes: " + err.Error()})
  66. return
  67. }
  68. // Set username
  69. username := c.GetString("username")
  70. if username == "" {
  71. username = "admin"
  72. }
  73. volumesData.Username = username
  74. // Render HTML template
  75. c.Header("Content-Type", "text/html")
  76. volumesComponent := app.ClusterVolumes(*volumesData)
  77. layoutComponent := layout.Layout(c, volumesComponent)
  78. err = layoutComponent.Render(c.Request.Context(), c.Writer)
  79. if err != nil {
  80. c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to render template: " + err.Error()})
  81. return
  82. }
  83. }
  84. // ShowVolumeDetails renders the volume details page
  85. func (h *ClusterHandlers) ShowVolumeDetails(c *gin.Context) {
  86. volumeIDStr := c.Param("id")
  87. server := c.Param("server")
  88. if volumeIDStr == "" {
  89. c.JSON(http.StatusBadRequest, gin.H{"error": "Volume ID is required"})
  90. return
  91. }
  92. if server == "" {
  93. c.JSON(http.StatusBadRequest, gin.H{"error": "Server is required"})
  94. return
  95. }
  96. volumeID, err := strconv.Atoi(volumeIDStr)
  97. if err != nil {
  98. c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid volume ID"})
  99. return
  100. }
  101. // Get volume details
  102. volumeDetails, err := h.adminServer.GetVolumeDetails(volumeID, server)
  103. if err != nil {
  104. c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get volume details: " + err.Error()})
  105. return
  106. }
  107. // Render HTML template
  108. c.Header("Content-Type", "text/html")
  109. volumeDetailsComponent := app.VolumeDetails(*volumeDetails)
  110. layoutComponent := layout.Layout(c, volumeDetailsComponent)
  111. err = layoutComponent.Render(c.Request.Context(), c.Writer)
  112. if err != nil {
  113. c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to render template: " + err.Error()})
  114. return
  115. }
  116. }
  117. // ShowClusterCollections renders the cluster collections page
  118. func (h *ClusterHandlers) ShowClusterCollections(c *gin.Context) {
  119. // Get cluster collections data
  120. collectionsData, err := h.adminServer.GetClusterCollections()
  121. if err != nil {
  122. c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get cluster collections: " + err.Error()})
  123. return
  124. }
  125. // Set username
  126. username := c.GetString("username")
  127. if username == "" {
  128. username = "admin"
  129. }
  130. collectionsData.Username = username
  131. // Render HTML template
  132. c.Header("Content-Type", "text/html")
  133. collectionsComponent := app.ClusterCollections(*collectionsData)
  134. layoutComponent := layout.Layout(c, collectionsComponent)
  135. err = layoutComponent.Render(c.Request.Context(), c.Writer)
  136. if err != nil {
  137. c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to render template: " + err.Error()})
  138. return
  139. }
  140. }
  141. // ShowCollectionDetails renders the collection detail page
  142. func (h *ClusterHandlers) ShowCollectionDetails(c *gin.Context) {
  143. collectionName := c.Param("name")
  144. if collectionName == "" {
  145. c.JSON(http.StatusBadRequest, gin.H{"error": "Collection name is required"})
  146. return
  147. }
  148. // Map "default" collection to empty string for backend filtering
  149. actualCollectionName := collectionName
  150. if collectionName == "default" {
  151. actualCollectionName = ""
  152. }
  153. // Parse query parameters
  154. page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
  155. pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "25"))
  156. sortBy := c.DefaultQuery("sort_by", "volume_id")
  157. sortOrder := c.DefaultQuery("sort_order", "asc")
  158. // Get collection details data (volumes and EC volumes)
  159. collectionDetailsData, err := h.adminServer.GetCollectionDetails(actualCollectionName, page, pageSize, sortBy, sortOrder)
  160. if err != nil {
  161. c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get collection details: " + err.Error()})
  162. return
  163. }
  164. // Set username
  165. username := c.GetString("username")
  166. if username == "" {
  167. username = "admin"
  168. }
  169. collectionDetailsData.Username = username
  170. // Render HTML template
  171. c.Header("Content-Type", "text/html")
  172. collectionDetailsComponent := app.CollectionDetails(*collectionDetailsData)
  173. layoutComponent := layout.Layout(c, collectionDetailsComponent)
  174. err = layoutComponent.Render(c.Request.Context(), c.Writer)
  175. if err != nil {
  176. c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to render template: " + err.Error()})
  177. return
  178. }
  179. }
  180. // ShowClusterEcShards handles the cluster EC shards page (individual shards view)
  181. func (h *ClusterHandlers) ShowClusterEcShards(c *gin.Context) {
  182. // Parse query parameters
  183. page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
  184. pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "100"))
  185. sortBy := c.DefaultQuery("sort_by", "volume_id")
  186. sortOrder := c.DefaultQuery("sort_order", "asc")
  187. collection := c.DefaultQuery("collection", "")
  188. // Get data from admin server
  189. data, err := h.adminServer.GetClusterEcVolumes(page, pageSize, sortBy, sortOrder, collection)
  190. if err != nil {
  191. c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
  192. return
  193. }
  194. // Set username
  195. username := c.GetString("username")
  196. if username == "" {
  197. username = "admin"
  198. }
  199. data.Username = username
  200. // Render template
  201. c.Header("Content-Type", "text/html")
  202. ecVolumesComponent := app.ClusterEcVolumes(*data)
  203. layoutComponent := layout.Layout(c, ecVolumesComponent)
  204. err = layoutComponent.Render(c.Request.Context(), c.Writer)
  205. if err != nil {
  206. c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
  207. return
  208. }
  209. }
  210. // ShowEcVolumeDetails renders the EC volume details page
  211. func (h *ClusterHandlers) ShowEcVolumeDetails(c *gin.Context) {
  212. volumeIDStr := c.Param("id")
  213. if volumeIDStr == "" {
  214. c.JSON(http.StatusBadRequest, gin.H{"error": "Volume ID is required"})
  215. return
  216. }
  217. volumeID, err := strconv.Atoi(volumeIDStr)
  218. if err != nil {
  219. c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid volume ID"})
  220. return
  221. }
  222. // Check that volumeID is within uint32 range
  223. if volumeID < 0 {
  224. c.JSON(http.StatusBadRequest, gin.H{"error": "Volume ID out of range"})
  225. return
  226. }
  227. // Parse sorting parameters
  228. sortBy := c.DefaultQuery("sort_by", "shard_id")
  229. sortOrder := c.DefaultQuery("sort_order", "asc")
  230. // Get EC volume details
  231. ecVolumeDetails, err := h.adminServer.GetEcVolumeDetails(uint32(volumeID), sortBy, sortOrder)
  232. if err != nil {
  233. c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get EC volume details: " + err.Error()})
  234. return
  235. }
  236. // Set username
  237. username := c.GetString("username")
  238. if username == "" {
  239. username = "admin"
  240. }
  241. ecVolumeDetails.Username = username
  242. // Render HTML template
  243. c.Header("Content-Type", "text/html")
  244. ecVolumeDetailsComponent := app.EcVolumeDetails(*ecVolumeDetails)
  245. layoutComponent := layout.Layout(c, ecVolumeDetailsComponent)
  246. err = layoutComponent.Render(c.Request.Context(), c.Writer)
  247. if err != nil {
  248. c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to render template: " + err.Error()})
  249. return
  250. }
  251. }
  252. // ShowClusterMasters renders the cluster masters page
  253. func (h *ClusterHandlers) ShowClusterMasters(c *gin.Context) {
  254. // Get cluster masters data
  255. mastersData, err := h.adminServer.GetClusterMasters()
  256. if err != nil {
  257. c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get cluster masters: " + err.Error()})
  258. return
  259. }
  260. // Set username
  261. username := c.GetString("username")
  262. if username == "" {
  263. username = "admin"
  264. }
  265. mastersData.Username = username
  266. // Render HTML template
  267. c.Header("Content-Type", "text/html")
  268. mastersComponent := app.ClusterMasters(*mastersData)
  269. layoutComponent := layout.Layout(c, mastersComponent)
  270. err = layoutComponent.Render(c.Request.Context(), c.Writer)
  271. if err != nil {
  272. c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to render template: " + err.Error()})
  273. return
  274. }
  275. }
  276. // ShowClusterFilers renders the cluster filers page
  277. func (h *ClusterHandlers) ShowClusterFilers(c *gin.Context) {
  278. // Get cluster filers data
  279. filersData, err := h.adminServer.GetClusterFilers()
  280. if err != nil {
  281. c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get cluster filers: " + err.Error()})
  282. return
  283. }
  284. // Set username
  285. username := c.GetString("username")
  286. if username == "" {
  287. username = "admin"
  288. }
  289. filersData.Username = username
  290. // Render HTML template
  291. c.Header("Content-Type", "text/html")
  292. filersComponent := app.ClusterFilers(*filersData)
  293. layoutComponent := layout.Layout(c, filersComponent)
  294. err = layoutComponent.Render(c.Request.Context(), c.Writer)
  295. if err != nil {
  296. c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to render template: " + err.Error()})
  297. return
  298. }
  299. }
  300. // ShowClusterBrokers renders the cluster message brokers page
  301. func (h *ClusterHandlers) ShowClusterBrokers(c *gin.Context) {
  302. // Get cluster brokers data
  303. brokersData, err := h.adminServer.GetClusterBrokers()
  304. if err != nil {
  305. c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get cluster brokers: " + err.Error()})
  306. return
  307. }
  308. // Set username
  309. username := c.GetString("username")
  310. if username == "" {
  311. username = "admin"
  312. }
  313. brokersData.Username = username
  314. // Render HTML template
  315. c.Header("Content-Type", "text/html")
  316. brokersComponent := app.ClusterBrokers(*brokersData)
  317. layoutComponent := layout.Layout(c, brokersComponent)
  318. err = layoutComponent.Render(c.Request.Context(), c.Writer)
  319. if err != nil {
  320. c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to render template: " + err.Error()})
  321. return
  322. }
  323. }
  324. // GetClusterTopology returns the cluster topology as JSON
  325. func (h *ClusterHandlers) GetClusterTopology(c *gin.Context) {
  326. topology, err := h.adminServer.GetClusterTopology()
  327. if err != nil {
  328. c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
  329. return
  330. }
  331. c.JSON(http.StatusOK, topology)
  332. }
  333. // GetMasters returns master node information
  334. func (h *ClusterHandlers) GetMasters(c *gin.Context) {
  335. // Simple master info
  336. c.JSON(http.StatusOK, gin.H{"masters": []gin.H{{"address": "localhost:9333"}}})
  337. }
  338. // GetVolumeServers returns volume server information
  339. func (h *ClusterHandlers) GetVolumeServers(c *gin.Context) {
  340. topology, err := h.adminServer.GetClusterTopology()
  341. if err != nil {
  342. c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
  343. return
  344. }
  345. c.JSON(http.StatusOK, gin.H{"volume_servers": topology.VolumeServers})
  346. }
  347. // VacuumVolume handles volume vacuum requests via API
  348. func (h *ClusterHandlers) VacuumVolume(c *gin.Context) {
  349. volumeIDStr := c.Param("id")
  350. server := c.Param("server")
  351. if volumeIDStr == "" {
  352. c.JSON(http.StatusBadRequest, gin.H{"error": "Volume ID is required"})
  353. return
  354. }
  355. volumeID, err := strconv.Atoi(volumeIDStr)
  356. if err != nil {
  357. c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid volume ID"})
  358. return
  359. }
  360. // Perform vacuum operation
  361. err = h.adminServer.VacuumVolume(volumeID, server)
  362. if err != nil {
  363. c.JSON(http.StatusInternalServerError, gin.H{
  364. "error": "Failed to vacuum volume: " + err.Error(),
  365. })
  366. return
  367. }
  368. c.JSON(http.StatusOK, gin.H{
  369. "message": "Volume vacuum started successfully",
  370. "volume_id": volumeID,
  371. "server": server,
  372. })
  373. }