Backend - Introduce `setting/console_setting` package that defines `ConsoleSetting` struct with JSON tags and validation rules. - Register the new module with `config.GlobalConfig` to enable automatic injection/export of configuration values. - Remove legacy `setting/console.go` and the manual `OptionMap` hooks; clean up `model/option.go`. - Add `controller/console_migrate.go` providing `/api/option/migrate_console_setting` endpoint for one-off data migration. - Update controllers (`misc`, `option`, `uptime_kuma`) and router to consume namespaced keys `console_setting.*`. Frontend - Refactor dashboard pages (`SettingsAPIInfo`, `SettingsAnnouncements`, `SettingsFAQ`, `SettingsUptimeKuma`) and detail page to read/write the new keys. - Simplify `DashboardSetting.js` state to only include namespaced options. BREAKING CHANGE: All console-related option keys are now stored under `console_setting.*`. Run the migration endpoint once after deployment to preserve existing data.
168 lines
3.9 KiB
Go
168 lines
3.9 KiB
Go
package controller
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"one-api/setting/console_setting"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"golang.org/x/sync/errgroup"
|
|
)
|
|
|
|
type UptimeKumaMonitor struct {
|
|
ID int `json:"id"`
|
|
Name string `json:"name"`
|
|
Type string `json:"type"`
|
|
}
|
|
|
|
type UptimeKumaGroup struct {
|
|
ID int `json:"id"`
|
|
Name string `json:"name"`
|
|
Weight int `json:"weight"`
|
|
MonitorList []UptimeKumaMonitor `json:"monitorList"`
|
|
}
|
|
|
|
type UptimeKumaHeartbeat struct {
|
|
Status int `json:"status"`
|
|
Time string `json:"time"`
|
|
Msg string `json:"msg"`
|
|
Ping *float64 `json:"ping"`
|
|
}
|
|
|
|
type UptimeKumaStatusResponse struct {
|
|
PublicGroupList []UptimeKumaGroup `json:"publicGroupList"`
|
|
}
|
|
|
|
type UptimeKumaHeartbeatResponse struct {
|
|
HeartbeatList map[string][]UptimeKumaHeartbeat `json:"heartbeatList"`
|
|
UptimeList map[string]float64 `json:"uptimeList"`
|
|
}
|
|
|
|
type MonitorStatus struct {
|
|
Name string `json:"name"`
|
|
Uptime float64 `json:"uptime"`
|
|
Status int `json:"status"`
|
|
}
|
|
|
|
var (
|
|
ErrUpstreamNon200 = errors.New("upstream non-200")
|
|
ErrTimeout = errors.New("context deadline exceeded")
|
|
)
|
|
|
|
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 {
|
|
if errors.Is(err, context.DeadlineExceeded) || errors.Is(err, context.Canceled) {
|
|
return ErrTimeout
|
|
}
|
|
return err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
return ErrUpstreamNon200
|
|
}
|
|
|
|
return json.NewDecoder(resp.Body).Decode(dest)
|
|
}
|
|
|
|
func GetUptimeKumaStatus(c *gin.Context) {
|
|
cs := console_setting.GetConsoleSetting()
|
|
uptimeKumaUrl := cs.UptimeKumaUrl
|
|
slug := cs.UptimeKumaSlug
|
|
|
|
if uptimeKumaUrl == "" || slug == "" {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": true,
|
|
"message": "",
|
|
"data": []MonitorStatus{},
|
|
})
|
|
return
|
|
}
|
|
|
|
uptimeKumaUrl = strings.TrimSuffix(uptimeKumaUrl, "/")
|
|
|
|
ctx, cancel := context.WithTimeout(c.Request.Context(), 10*time.Second)
|
|
defer cancel()
|
|
|
|
client := &http.Client{}
|
|
|
|
statusPageUrl := fmt.Sprintf("%s/api/status-page/%s", uptimeKumaUrl, slug)
|
|
heartbeatUrl := fmt.Sprintf("%s/api/status-page/heartbeat/%s", uptimeKumaUrl, slug)
|
|
|
|
var (
|
|
statusData UptimeKumaStatusResponse
|
|
heartbeatData UptimeKumaHeartbeatResponse
|
|
)
|
|
|
|
g, gCtx := errgroup.WithContext(ctx)
|
|
|
|
g.Go(func() error {
|
|
return getAndDecode(gCtx, client, statusPageUrl, &statusData)
|
|
})
|
|
|
|
g.Go(func() error {
|
|
return getAndDecode(gCtx, client, heartbeatUrl, &heartbeatData)
|
|
})
|
|
|
|
if err := g.Wait(); err != nil {
|
|
switch err {
|
|
case ErrUpstreamNon200:
|
|
c.JSON(http.StatusBadRequest, gin.H{
|
|
"success": false,
|
|
"message": "上游接口出现问题",
|
|
})
|
|
case ErrTimeout:
|
|
c.JSON(http.StatusRequestTimeout, gin.H{
|
|
"success": false,
|
|
"message": "请求上游接口超时",
|
|
})
|
|
default:
|
|
c.JSON(http.StatusBadRequest, gin.H{
|
|
"success": false,
|
|
"message": err.Error(),
|
|
})
|
|
}
|
|
return
|
|
}
|
|
|
|
var monitors []MonitorStatus
|
|
for _, group := range statusData.PublicGroupList {
|
|
for _, monitor := range group.MonitorList {
|
|
monitorStatus := MonitorStatus{
|
|
Name: monitor.Name,
|
|
Uptime: 0.0,
|
|
Status: 0,
|
|
}
|
|
|
|
uptimeKey := fmt.Sprintf("%d_24", monitor.ID)
|
|
if uptime, exists := heartbeatData.UptimeList[uptimeKey]; exists {
|
|
monitorStatus.Uptime = uptime
|
|
}
|
|
|
|
heartbeatKey := fmt.Sprintf("%d", monitor.ID)
|
|
if heartbeats, exists := heartbeatData.HeartbeatList[heartbeatKey]; exists && len(heartbeats) > 0 {
|
|
latestHeartbeat := heartbeats[0]
|
|
monitorStatus.Status = latestHeartbeat.Status
|
|
}
|
|
|
|
monitors = append(monitors, monitorStatus)
|
|
}
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": true,
|
|
"message": "",
|
|
"data": monitors,
|
|
})
|
|
} |