Backend • controller/uptime_kuma.go - Added Group field to Monitor struct to carry publicGroupList.name. - Extended status page parsing to capture group Name and inject it into each monitor. - Re-worked fetchGroupData loop: aggregate all sub-groups, drop unnecessary pre-allocation/breaks. Frontend • web/src/pages/Detail/index.js - renderMonitorList now buckets monitors by the new group field and renders a lightweight header per subgroup. - Fallback gracefully when group is empty to preserve previous single-list behaviour. Other • Expanded anonymous struct definition for statusData.PublicGroupList to include ID/Name, enabling JSON unmarshalling of group names. Result Custom CategoryName continues to work while each uptime group’s internal sub-groups are now clearly displayed in the UI, providing finer-grained visibility without impacting performance or existing validation logic.
154 lines
3.5 KiB
Go
154 lines
3.5 KiB
Go
package controller
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"net/http"
|
|
"one-api/setting/console_setting"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"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})
|
|
} |