feat: sync deployed v1.0.0-rc.38-custom.42
Sync production source including routing, billing, Astra detection and public uptime. Update custom build instructions and license packaging.
This commit is contained in:
1 parent
aa6c3f50f4
commit
ceddfd3d6b
828 files changed
+122253
-13702
No files matched your search
+83
-146
@@ -2,9 +2,7 @@ package controller
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -15,29 +13,31 @@ import (
|
||||
"github.com/QuantumNous/new-api/service"
|
||||
|
||||
"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/"
|
||||
)
|
||||
const requestTimeout = 30 * time.Second
|
||||
|
||||
type Monitor struct {
|
||||
Metrics *perfmetrics.GroupResult `json:"metrics,omitempty"`
|
||||
Name string `json:"name"`
|
||||
Uptime float64 `json:"uptime"`
|
||||
Status int `json:"status"`
|
||||
Group string `json:"group,omitempty"`
|
||||
Ping int `json:"ping,omitempty"`
|
||||
LastChecked string `json:"last_checked,omitempty"`
|
||||
History []MonitorSample `json:"history,omitempty"`
|
||||
CacheStats *model.GroupCacheStats `json:"cache_stats,omitempty"`
|
||||
ProbeStatus int `json:"probe_status"`
|
||||
ChannelDisabled bool `json:"channel_disabled"`
|
||||
ProbeHistory []service.ProbeHistoryPoint `json:"probe_history"`
|
||||
ID string `json:"id"`
|
||||
Models []string `json:"models"`
|
||||
Alias string `json:"alias,omitempty"`
|
||||
Hidden bool `json:"hidden,omitempty"`
|
||||
Pelican *service.PelicanSummary `json:"pelican,omitempty"`
|
||||
StatusSource string `json:"status_source"`
|
||||
StatusCheckedAt int64 `json:"status_checked_at,omitempty"`
|
||||
Fingerprint *service.AstraSamplingSummary `json:"fingerprint,omitempty"`
|
||||
Metrics *perfmetrics.GroupResult `json:"metrics,omitempty"`
|
||||
Name string `json:"name"`
|
||||
Uptime float64 `json:"uptime"`
|
||||
Status int `json:"status"`
|
||||
Group string `json:"group,omitempty"`
|
||||
Ping int `json:"ping,omitempty"`
|
||||
LastChecked string `json:"last_checked,omitempty"`
|
||||
History []MonitorSample `json:"history,omitempty"`
|
||||
CacheStats *model.GroupCacheStats `json:"cache_stats,omitempty"`
|
||||
ProbeStatus int `json:"probe_status"`
|
||||
ChannelDisabled bool `json:"channel_disabled"`
|
||||
}
|
||||
|
||||
type MonitorSample struct {
|
||||
@@ -51,113 +51,6 @@ type UptimeGroupResult struct {
|
||||
Monitors []Monitor `json:"monitors"`
|
||||
}
|
||||
|
||||
func getAndDecode(ctx context.Context, client *http.Client, url string, dest any) 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 common.DecodeJson(resp.Body, dest)
|
||||
}
|
||||
|
||||
func fetchGroupData(ctx context.Context, client *http.Client, groupConfig map[string]any) 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"`
|
||||
Ping int `json:"ping"`
|
||||
Time string `json:"time"`
|
||||
} `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,
|
||||
Status: -1,
|
||||
}
|
||||
|
||||
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 {
|
||||
latest := heartbeats[len(heartbeats)-1]
|
||||
monitor.Status = latest.Status
|
||||
monitor.Ping = latest.Ping
|
||||
monitor.LastChecked = latest.Time
|
||||
monitor.History = make([]MonitorSample, 0, len(heartbeats))
|
||||
for _, heartbeat := range heartbeats {
|
||||
monitor.History = append(monitor.History, MonitorSample{
|
||||
Status: heartbeat.Status,
|
||||
Ping: heartbeat.Ping,
|
||||
Time: heartbeat.Time,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
result.Monitors = append(result.Monitors, monitor)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
var groupCacheSnapshot struct {
|
||||
sync.Mutex
|
||||
updated time.Time
|
||||
@@ -168,18 +61,35 @@ var groupCacheSnapshot struct {
|
||||
|
||||
const requestStatusFreshness = 2 * time.Hour
|
||||
|
||||
func currentGroupMonitors(ctx context.Context, results []UptimeGroupResult) ([]UptimeGroupResult, error) {
|
||||
func currentGroupMonitors(ctx context.Context, admin bool) ([]UptimeGroupResult, error) {
|
||||
channels, err := model.GetStatusChannels(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
settings, err := model.GetStatusPageGroups(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
models, err := model.GetStatusPageModels(ctx, channels)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
usable := service.GetUserUsableGroups("")
|
||||
now := time.Now()
|
||||
fingerprints, fingerprintErr := service.AstraSamplingSummaries(ctx, now, settings)
|
||||
if fingerprintErr != nil {
|
||||
return nil, fingerprintErr
|
||||
}
|
||||
detections, detectionErr := service.GroupDetectionSummaries(ctx, now, fingerprints, settings)
|
||||
if detectionErr != nil {
|
||||
return nil, detectionErr
|
||||
}
|
||||
active := make(map[string]bool)
|
||||
order := make([]string, 0)
|
||||
for _, channel := range channels {
|
||||
for _, group := range strings.Split(channel.Group, ",") {
|
||||
group = strings.TrimSpace(group)
|
||||
if _, visible := usable[group]; !visible {
|
||||
if _, visible := usable[group]; (!visible || settings[group].Hidden) && !admin {
|
||||
continue
|
||||
}
|
||||
if _, exists := active[group]; !exists {
|
||||
@@ -202,8 +112,8 @@ func currentGroupMonitors(ctx context.Context, results []UptimeGroupResult) ([]U
|
||||
}
|
||||
groupCacheSnapshot.stats = stats
|
||||
names := []string{}
|
||||
for _, item := range model.GetPricing() {
|
||||
names = append(names, item.ModelName)
|
||||
if err := model.DB.WithContext(ctx).Model(&model.Ability{}).Where("enabled = ?", true).Distinct().Pluck("model", &names).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
performance, metricsErr := perfmetrics.QueryGroups(names, 24)
|
||||
if metricsErr != nil {
|
||||
@@ -216,21 +126,26 @@ func currentGroupMonitors(ctx context.Context, results []UptimeGroupResult) ([]U
|
||||
groupCacheSnapshot.updated = now
|
||||
groupCacheSnapshot.channels = string(encoded)
|
||||
}
|
||||
byName := make(map[string]Monitor)
|
||||
for _, result := range results {
|
||||
for _, monitor := range result.Monitors {
|
||||
if previous, exists := byName[monitor.Name]; !exists || (monitor.Status == 1 && previous.Status != 1) {
|
||||
byName[monitor.Name] = monitor
|
||||
}
|
||||
}
|
||||
}
|
||||
successes := perfmetrics.LatestUserSuccess(ctx, order)
|
||||
monitors := make([]Monitor, 0, len(order))
|
||||
for _, group := range order {
|
||||
monitor, exists := byName[group]
|
||||
if !exists {
|
||||
monitor = Monitor{Name: group, Status: -1}
|
||||
monitor := Monitor{ID: model.DetectionSecretHash(group)[:16], Name: group, Status: -1, Models: models[group]}
|
||||
if monitor.Models == nil {
|
||||
monitor.Models = []string{}
|
||||
}
|
||||
if settings[group].Alias != "" {
|
||||
monitor.Name = settings[group].Alias
|
||||
}
|
||||
if admin {
|
||||
monitor.Alias, monitor.Hidden = settings[group].Alias, settings[group].Hidden
|
||||
}
|
||||
if admin {
|
||||
monitor.Group = group
|
||||
}
|
||||
monitor.Fingerprint = fingerprints[group]
|
||||
if monitor.Fingerprint == nil {
|
||||
monitor.Fingerprint = &service.AstraSamplingSummary{Enabled: service.AstraSamplingEnabled(), Available: fingerprintErr == nil, History: []service.AstraSamplingHistory{}}
|
||||
}
|
||||
monitor.Group = group
|
||||
monitor.ProbeStatus = monitor.Status
|
||||
monitor.ChannelDisabled = !active[group]
|
||||
if !active[group] {
|
||||
@@ -239,16 +154,30 @@ func currentGroupMonitors(ctx context.Context, results []UptimeGroupResult) ([]U
|
||||
stats := groupCacheSnapshot.stats[group]
|
||||
monitor.CacheStats = &stats
|
||||
applyRequestMetrics(&monitor, groupCacheSnapshot.metrics[group])
|
||||
if !admin && monitor.Metrics != nil {
|
||||
monitor.Metrics.Group = monitor.Name
|
||||
}
|
||||
monitor.Pelican = detections[group].Pelican
|
||||
monitor.StatusSource = "requests"
|
||||
if monitor.ChannelDisabled {
|
||||
monitor.StatusSource = "disabled"
|
||||
} else if monitor.Status < 0 {
|
||||
monitor.Status = detections[group].Status
|
||||
monitor.StatusSource = "detection"
|
||||
monitor.StatusCheckedAt = detections[group].At
|
||||
}
|
||||
monitor.ProbeHistory = detections[group].ProbeHistory
|
||||
applyUserSuccess(&monitor, successes[group], detections[group].At, now.Unix())
|
||||
monitors = append(monitors, monitor)
|
||||
}
|
||||
return []UptimeGroupResult{{CategoryName: "Groups", Monitors: monitors}}, nil
|
||||
}
|
||||
|
||||
func GetUptimeKumaStatus(c *gin.Context) {
|
||||
func GetGroupStatus(c *gin.Context) {
|
||||
ctx, cancel := context.WithTimeout(c.Request.Context(), requestTimeout)
|
||||
defer cancel()
|
||||
|
||||
results, err := currentGroupMonitors(ctx, nil)
|
||||
results, err := currentGroupMonitors(ctx, false)
|
||||
if err != nil {
|
||||
common.SysError("group status statistics: " + err.Error())
|
||||
c.JSON(http.StatusServiceUnavailable, gin.H{"success": false, "message": "Status data is temporarily unavailable"})
|
||||
@@ -267,6 +196,14 @@ func requestStatus(rate float64) int {
|
||||
return 0
|
||||
}
|
||||
|
||||
func applyUserSuccess(monitor *Monitor, success, probe, now int64) {
|
||||
if !monitor.ChannelDisabled && success > 0 && success >= now-900 && success >= probe {
|
||||
monitor.Status = 1
|
||||
monitor.StatusSource = "requests"
|
||||
monitor.StatusCheckedAt = success
|
||||
}
|
||||
}
|
||||
|
||||
func applyRequestMetrics(monitor *Monitor, metrics perfmetrics.GroupResult) {
|
||||
monitor.Status = -1
|
||||
monitor.Uptime = 0
|
||||
|
||||
Reference in new issue
Block a user