uptime_kuma.go 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. package controller
  2. import (
  3. "context"
  4. "encoding/json"
  5. "errors"
  6. "net/http"
  7. "one-api/setting/console_setting"
  8. "strconv"
  9. "strings"
  10. "time"
  11. "github.com/gin-gonic/gin"
  12. "golang.org/x/sync/errgroup"
  13. )
  14. const (
  15. requestTimeout = 30 * time.Second
  16. httpTimeout = 10 * time.Second
  17. uptimeKeySuffix = "_24"
  18. apiStatusPath = "/api/status-page/"
  19. apiHeartbeatPath = "/api/status-page/heartbeat/"
  20. )
  21. type Monitor struct {
  22. Name string `json:"name"`
  23. Uptime float64 `json:"uptime"`
  24. Status int `json:"status"`
  25. Group string `json:"group,omitempty"`
  26. }
  27. type UptimeGroupResult struct {
  28. CategoryName string `json:"categoryName"`
  29. Monitors []Monitor `json:"monitors"`
  30. }
  31. func getAndDecode(ctx context.Context, client *http.Client, url string, dest interface{}) error {
  32. req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
  33. if err != nil {
  34. return err
  35. }
  36. resp, err := client.Do(req)
  37. if err != nil {
  38. return err
  39. }
  40. defer resp.Body.Close()
  41. if resp.StatusCode != http.StatusOK {
  42. return errors.New("non-200 status")
  43. }
  44. return json.NewDecoder(resp.Body).Decode(dest)
  45. }
  46. func fetchGroupData(ctx context.Context, client *http.Client, groupConfig map[string]interface{}) UptimeGroupResult {
  47. url, _ := groupConfig["url"].(string)
  48. slug, _ := groupConfig["slug"].(string)
  49. categoryName, _ := groupConfig["categoryName"].(string)
  50. result := UptimeGroupResult{
  51. CategoryName: categoryName,
  52. Monitors: []Monitor{},
  53. }
  54. if url == "" || slug == "" {
  55. return result
  56. }
  57. baseURL := strings.TrimSuffix(url, "/")
  58. var statusData struct {
  59. PublicGroupList []struct {
  60. ID int `json:"id"`
  61. Name string `json:"name"`
  62. MonitorList []struct {
  63. ID int `json:"id"`
  64. Name string `json:"name"`
  65. } `json:"monitorList"`
  66. } `json:"publicGroupList"`
  67. }
  68. var heartbeatData struct {
  69. HeartbeatList map[string][]struct {
  70. Status int `json:"status"`
  71. } `json:"heartbeatList"`
  72. UptimeList map[string]float64 `json:"uptimeList"`
  73. }
  74. g, gCtx := errgroup.WithContext(ctx)
  75. g.Go(func() error {
  76. return getAndDecode(gCtx, client, baseURL+apiStatusPath+slug, &statusData)
  77. })
  78. g.Go(func() error {
  79. return getAndDecode(gCtx, client, baseURL+apiHeartbeatPath+slug, &heartbeatData)
  80. })
  81. if g.Wait() != nil {
  82. return result
  83. }
  84. for _, pg := range statusData.PublicGroupList {
  85. if len(pg.MonitorList) == 0 {
  86. continue
  87. }
  88. for _, m := range pg.MonitorList {
  89. monitor := Monitor{
  90. Name: m.Name,
  91. Group: pg.Name,
  92. }
  93. monitorID := strconv.Itoa(m.ID)
  94. if uptime, exists := heartbeatData.UptimeList[monitorID+uptimeKeySuffix]; exists {
  95. monitor.Uptime = uptime
  96. }
  97. if heartbeats, exists := heartbeatData.HeartbeatList[monitorID]; exists && len(heartbeats) > 0 {
  98. monitor.Status = heartbeats[0].Status
  99. }
  100. result.Monitors = append(result.Monitors, monitor)
  101. }
  102. }
  103. return result
  104. }
  105. func GetUptimeKumaStatus(c *gin.Context) {
  106. groups := console_setting.GetUptimeKumaGroups()
  107. if len(groups) == 0 {
  108. c.JSON(http.StatusOK, gin.H{"success": true, "message": "", "data": []UptimeGroupResult{}})
  109. return
  110. }
  111. ctx, cancel := context.WithTimeout(c.Request.Context(), requestTimeout)
  112. defer cancel()
  113. client := &http.Client{Timeout: httpTimeout}
  114. results := make([]UptimeGroupResult, len(groups))
  115. g, gCtx := errgroup.WithContext(ctx)
  116. for i, group := range groups {
  117. i, group := i, group
  118. g.Go(func() error {
  119. results[i] = fetchGroupData(gCtx, client, group)
  120. return nil
  121. })
  122. }
  123. g.Wait()
  124. c.JSON(http.StatusOK, gin.H{"success": true, "message": "", "data": results})
  125. }