| package controller |
|
|
| import ( |
| "context" |
| "encoding/json" |
| "errors" |
| "net/http" |
| "strconv" |
| "strings" |
| "time" |
|
|
| "github.com/QuantumNous/new-api/setting/console_setting" |
|
|
| "github.com/gin-gonic/gin" |
| "golang.org/x/sync/errgroup" |
| ) |
|
|
| const ( |
| requestTimeout = 30 * time.Second |
| httpTimeout = 10 * time.Second |
| uptimeKeySuffix = "_24" |
| apiStatusPath = "/api/status-page/" |
| apiHeartbeatPath = "/api/status-page/heartbeat/" |
| ) |
|
|
| type Monitor struct { |
| Name string `json:"name"` |
| Uptime float64 `json:"uptime"` |
| Status int `json:"status"` |
| Group string `json:"group,omitempty"` |
| } |
|
|
| type UptimeGroupResult struct { |
| CategoryName string `json:"categoryName"` |
| Monitors []Monitor `json:"monitors"` |
| } |
|
|
| func getAndDecode(ctx context.Context, client *http.Client, url string, dest interface{}) error { |
| req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) |
| if err != nil { |
| return err |
| } |
|
|
| resp, err := client.Do(req) |
| if err != nil { |
| return err |
| } |
| defer resp.Body.Close() |
|
|
| if resp.StatusCode != http.StatusOK { |
| return errors.New("non-200 status") |
| } |
|
|
| return json.NewDecoder(resp.Body).Decode(dest) |
| } |
|
|
| func fetchGroupData(ctx context.Context, client *http.Client, groupConfig map[string]interface{}) UptimeGroupResult { |
| url, _ := groupConfig["url"].(string) |
| slug, _ := groupConfig["slug"].(string) |
| categoryName, _ := groupConfig["categoryName"].(string) |
|
|
| result := UptimeGroupResult{ |
| CategoryName: categoryName, |
| Monitors: []Monitor{}, |
| } |
|
|
| if url == "" || slug == "" { |
| return result |
| } |
|
|
| baseURL := strings.TrimSuffix(url, "/") |
|
|
| var statusData struct { |
| PublicGroupList []struct { |
| ID int `json:"id"` |
| Name string `json:"name"` |
| MonitorList []struct { |
| ID int `json:"id"` |
| Name string `json:"name"` |
| } `json:"monitorList"` |
| } `json:"publicGroupList"` |
| } |
|
|
| var heartbeatData struct { |
| HeartbeatList map[string][]struct { |
| Status int `json:"status"` |
| } `json:"heartbeatList"` |
| UptimeList map[string]float64 `json:"uptimeList"` |
| } |
|
|
| g, gCtx := errgroup.WithContext(ctx) |
| g.Go(func() error { |
| return getAndDecode(gCtx, client, baseURL+apiStatusPath+slug, &statusData) |
| }) |
| g.Go(func() error { |
| return getAndDecode(gCtx, client, baseURL+apiHeartbeatPath+slug, &heartbeatData) |
| }) |
|
|
| if g.Wait() != nil { |
| return result |
| } |
|
|
| for _, pg := range statusData.PublicGroupList { |
| if len(pg.MonitorList) == 0 { |
| continue |
| } |
|
|
| for _, m := range pg.MonitorList { |
| monitor := Monitor{ |
| Name: m.Name, |
| Group: pg.Name, |
| } |
|
|
| monitorID := strconv.Itoa(m.ID) |
|
|
| if uptime, exists := heartbeatData.UptimeList[monitorID+uptimeKeySuffix]; exists { |
| monitor.Uptime = uptime |
| } |
|
|
| if heartbeats, exists := heartbeatData.HeartbeatList[monitorID]; exists && len(heartbeats) > 0 { |
| monitor.Status = heartbeats[0].Status |
| } |
|
|
| result.Monitors = append(result.Monitors, monitor) |
| } |
| } |
|
|
| return result |
| } |
|
|
| func GetUptimeKumaStatus(c *gin.Context) { |
| groups := console_setting.GetUptimeKumaGroups() |
| if len(groups) == 0 { |
| c.JSON(http.StatusOK, gin.H{"success": true, "message": "", "data": []UptimeGroupResult{}}) |
| return |
| } |
|
|
| ctx, cancel := context.WithTimeout(c.Request.Context(), requestTimeout) |
| defer cancel() |
|
|
| client := &http.Client{Timeout: httpTimeout} |
| results := make([]UptimeGroupResult, len(groups)) |
|
|
| g, gCtx := errgroup.WithContext(ctx) |
| for i, group := range groups { |
| i, group := i, group |
| g.Go(func() error { |
| results[i] = fetchGroupData(gCtx, client, group) |
| return nil |
| }) |
| } |
|
|
| g.Wait() |
| c.JSON(http.StatusOK, gin.H{"success": true, "message": "", "data": results}) |
| } |
|
|