desktop: put this computer health first
The Agent now owns one local view of machine health and top processes. RedFlag opens at dashboard scale, names itself plainly, and renders that evidence before updates.\n\nCI now compiles the Tauri bridge before release day.
This commit is contained in:
parent
320ad46e00
commit
ffa7afbb58
6 changed files with 302 additions and 20 deletions
|
|
@ -15,6 +15,7 @@ import (
|
|||
"github.com/Fimeg/RedFlag/agent/internal/cache"
|
||||
"github.com/Fimeg/RedFlag/agent/internal/client"
|
||||
"github.com/Fimeg/RedFlag/agent/internal/config"
|
||||
"github.com/Fimeg/RedFlag/agent/internal/system"
|
||||
"github.com/Fimeg/RedFlag/agent/internal/version"
|
||||
)
|
||||
|
||||
|
|
@ -49,6 +50,8 @@ type Options struct {
|
|||
RequestLog func(format string, args ...interface{})
|
||||
ListenerOverride net.Listener
|
||||
DesktopProvider DesktopStatusProvider
|
||||
SystemProvider func() (*system.SystemInfo, error)
|
||||
ProcessProvider func(limit int) ([]system.TopProcess, error)
|
||||
// TriggerScan enqueues a package-update scan through the agent's existing
|
||||
// scanner primitives (FEAT-002 write path). Authorization is the OS-local
|
||||
// group boundary on the socket/pipe — anyone who can connect may trigger.
|
||||
|
|
@ -135,6 +138,8 @@ type handler struct {
|
|||
triggerScan func(source string) error
|
||||
approveUpdate func(body []byte) (interface{}, error)
|
||||
onDesktopHealth func(version string, windowOpen bool)
|
||||
systemInfo func() (*system.SystemInfo, error)
|
||||
topProcesses func(limit int) ([]system.TopProcess, error)
|
||||
}
|
||||
|
||||
// DesktopStatusProvider allows the desktop manager to report its status.
|
||||
|
|
@ -194,6 +199,12 @@ type TokenResponse struct {
|
|||
Capabilities cache.CapabilityTokenState `json:"capabilities"`
|
||||
}
|
||||
|
||||
type SystemResponse struct {
|
||||
System *system.SystemInfo `json:"system"`
|
||||
TopProcesses []system.TopProcess `json:"top_processes"`
|
||||
CollectedAt time.Time `json:"collected_at"`
|
||||
}
|
||||
|
||||
func newHandler(opts Options) http.Handler {
|
||||
h := &handler{
|
||||
cfg: opts.Config,
|
||||
|
|
@ -202,6 +213,16 @@ func newHandler(opts Options) http.Handler {
|
|||
triggerScan: opts.TriggerScan,
|
||||
approveUpdate: opts.ApproveUpdate,
|
||||
onDesktopHealth: opts.OnDesktopHealth,
|
||||
systemInfo: opts.SystemProvider,
|
||||
topProcesses: opts.ProcessProvider,
|
||||
}
|
||||
if h.systemInfo == nil {
|
||||
h.systemInfo = func() (*system.SystemInfo, error) {
|
||||
return system.GetSystemInfo(version.Version)
|
||||
}
|
||||
}
|
||||
if h.topProcesses == nil {
|
||||
h.topProcesses = system.GetTopProcesses
|
||||
}
|
||||
if h.loadCache == nil {
|
||||
h.loadCache = cache.Load
|
||||
|
|
@ -211,6 +232,7 @@ func newHandler(opts Options) http.Handler {
|
|||
mux.HandleFunc("/v1/status", h.status)
|
||||
mux.HandleFunc("/v1/scans/latest", h.scansLatest)
|
||||
mux.HandleFunc("/v1/packages", h.scansLatest)
|
||||
mux.HandleFunc("/v1/system", h.system)
|
||||
mux.HandleFunc("/v1/tokens/active", h.tokensActive)
|
||||
mux.HandleFunc("/v1/desktop", h.desktopHealth)
|
||||
mux.HandleFunc("/v1/actions/trigger-scan", h.triggerScanAction)
|
||||
|
|
@ -218,6 +240,28 @@ func newHandler(opts Options) http.Handler {
|
|||
return mux
|
||||
}
|
||||
|
||||
func (h *handler) system(w http.ResponseWriter, r *http.Request) {
|
||||
if !requireGet(w, r) {
|
||||
return
|
||||
}
|
||||
info, err := h.systemInfo()
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] [agent] [localapi] system_info_failed error=%v", err)
|
||||
http.Error(w, "system health unavailable", http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
processes, err := h.topProcesses(8)
|
||||
if err != nil {
|
||||
log.Printf("[WARNING] [agent] [localapi] top_processes_failed error=%v", err)
|
||||
processes = nil
|
||||
}
|
||||
writeJSON(w, SystemResponse{
|
||||
System: info,
|
||||
TopProcesses: processes,
|
||||
CollectedAt: time.Now().UTC(),
|
||||
})
|
||||
}
|
||||
|
||||
func (h *handler) identity(w http.ResponseWriter, r *http.Request) {
|
||||
if !requireGet(w, r) {
|
||||
return
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import (
|
|||
"github.com/Fimeg/RedFlag/agent/internal/cache"
|
||||
"github.com/Fimeg/RedFlag/agent/internal/client"
|
||||
"github.com/Fimeg/RedFlag/agent/internal/config"
|
||||
"github.com/Fimeg/RedFlag/agent/internal/system"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
)
|
||||
|
||||
|
|
@ -115,6 +116,48 @@ func TestStatusAndPackagesReadLocalCache(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestSystemHealthUsesAgentCollectors(t *testing.T) {
|
||||
handler := newHandler(Options{
|
||||
Config: testConfig(t),
|
||||
LoadCache: func() (*cache.LocalCache, error) {
|
||||
return &cache.LocalCache{}, nil
|
||||
},
|
||||
SystemProvider: func() (*system.SystemInfo, error) {
|
||||
return &system.SystemInfo{
|
||||
Hostname: "workstation-01",
|
||||
OSVersion: "Arch Linux",
|
||||
OSArchitecture: "amd64",
|
||||
RunningProcesses: 173,
|
||||
Uptime: "4 days",
|
||||
MemoryInfo: system.MemoryInfo{
|
||||
Total: 32 << 30,
|
||||
Used: 12 << 30,
|
||||
Available: 20 << 30,
|
||||
UsedPercent: 37.5,
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
ProcessProvider: func(limit int) ([]system.TopProcess, error) {
|
||||
if limit != 8 {
|
||||
t.Fatalf("process limit = %d, want 8", limit)
|
||||
}
|
||||
return []system.TopProcess{{Name: "redflag-agent", PID: 42, CPU: 1.5, Mem: 0.8}}, nil
|
||||
},
|
||||
})
|
||||
|
||||
var response SystemResponse
|
||||
requestJSON(t, handler, http.MethodGet, "/v1/system", &response)
|
||||
if response.System.Hostname != "workstation-01" {
|
||||
t.Fatalf("hostname = %q, want workstation-01", response.System.Hostname)
|
||||
}
|
||||
if response.System.MemoryInfo.UsedPercent != 37.5 {
|
||||
t.Fatalf("memory used = %.1f, want 37.5", response.System.MemoryInfo.UsedPercent)
|
||||
}
|
||||
if len(response.TopProcesses) != 1 || response.TopProcesses[0].Name != "redflag-agent" {
|
||||
t.Fatalf("top processes = %#v", response.TopProcesses)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCacheLoadFailureReturnsServiceUnavailable(t *testing.T) {
|
||||
handler := newHandler(Options{Config: testConfig(t), LoadCache: func() (*cache.LocalCache, error) {
|
||||
return nil, errors.New("cannot read cache")
|
||||
|
|
|
|||
Loading…
Reference in a new issue