glance/internal/glance/widget-server-stats.go
2025-02-09 17:52:22 +00:00

117 lines
3.1 KiB
Go

package glance
import (
"context"
"html/template"
"log/slog"
"net/http"
"strconv"
"strings"
"sync"
"time"
"github.com/glanceapp/glance/pkg/sysinfo"
)
var serverStatsWidgetTemplate = mustParseTemplate("server-stats.html", "widget-base.html")
type serverStatsWidget struct {
widgetBase `yaml:",inline"`
Servers []serverStatsRequest `yaml:"servers"`
}
func (widget *serverStatsWidget) initialize() error {
widget.withTitle("Server Stats").withCacheDuration(15 * time.Second)
widget.widgetBase.WIP = true
if len(widget.Servers) == 0 {
widget.Servers = []serverStatsRequest{{Type: "local"}}
}
for i := range widget.Servers {
widget.Servers[i].URL = strings.TrimRight(widget.Servers[i].URL, "/")
if widget.Servers[i].Timeout == 0 {
widget.Servers[i].Timeout = durationField(3 * time.Second)
}
}
return nil
}
func (widget *serverStatsWidget) update(context.Context) {
// Refactor later, most of it may change depending on feedback
var wg sync.WaitGroup
for i := range widget.Servers {
serv := &widget.Servers[i]
if serv.Type == "local" {
info, errs := sysinfo.Collect(serv.SystemInfoRequest)
if len(errs) > 0 {
for i := range errs {
slog.Warn("Getting system info: " + errs[i].Error())
}
}
serv.IsReachable = true
serv.Info = info
} else {
wg.Add(1)
go func() {
defer wg.Done()
info, err := fetchRemoteServerInfo(serv)
if err != nil {
slog.Warn("Getting remote system info: " + err.Error())
serv.IsReachable = false
serv.Info = &sysinfo.SystemInfo{
Hostname: "Unnamed server #" + strconv.Itoa(i+1),
}
} else {
serv.IsReachable = true
serv.Info = info
}
}()
}
}
wg.Wait()
widget.withError(nil).scheduleNextUpdate()
}
func (widget *serverStatsWidget) Render() template.HTML {
return widget.renderTemplate(widget, serverStatsWidgetTemplate)
}
type serverStatsRequest struct {
*sysinfo.SystemInfoRequest `yaml:",inline"`
Info *sysinfo.SystemInfo `yaml:"-"`
IsReachable bool `yaml:"-"`
StatusText string `yaml:"-"`
Name string `yaml:"name"`
HideSwap bool `yaml:"hide-swap"`
Type string `yaml:"type"`
URL string `yaml:"url"`
Token string `yaml:"token"`
Timeout durationField `yaml:"timeout"`
// Support for other agents
// Provider string `yaml:"provider"`
}
func fetchRemoteServerInfo(infoReq *serverStatsRequest) (*sysinfo.SystemInfo, error) {
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(infoReq.Timeout))
defer cancel()
request, _ := http.NewRequestWithContext(ctx, "GET", infoReq.URL+"/api/sysinfo/all", nil)
if infoReq.Token != "" {
request.Header.Set("Authorization", "Bearer "+infoReq.Token)
}
info, err := decodeJsonFromRequest[*sysinfo.SystemInfo](defaultHTTPClient, request)
if err != nil {
return nil, err
}
return info, nil
}