Initial commit: AetherForge Linux (forge-mesh) v0.1.0-dev
Some checks failed
Test / test (push) Has been cancelled
Some checks failed
Test / test (push) Has been cancelled
This commit is contained in:
181
internal/fleet/summary.go
Normal file
181
internal/fleet/summary.go
Normal file
@@ -0,0 +1,181 @@
|
||||
package fleet
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"forge-mesh/internal/api/types"
|
||||
"forge-mesh/internal/policy"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// FleetSummary aggregates fleet stats for the dashboard API.
|
||||
type FleetSummary struct {
|
||||
Hosts []FleetHostCard `json:"hosts"`
|
||||
TotalHashrate float64 `json:"totalHashrate"`
|
||||
OnlineCount int `json:"onlineCount"`
|
||||
}
|
||||
|
||||
// FleetHostCard is the dashboard-facing host shape (matches React types).
|
||||
type FleetHostCard struct {
|
||||
ID string `json:"id"`
|
||||
Hostname string `json:"hostname"`
|
||||
IP string `json:"ip"`
|
||||
Arch string `json:"arch"`
|
||||
Hashrate float64 `json:"hashrate"`
|
||||
Tier int `json:"tier"`
|
||||
TierName string `json:"tierName"`
|
||||
TierState string `json:"tierState"`
|
||||
Algo string `json:"algo"`
|
||||
UptimeSec int `json:"uptimeSec"`
|
||||
LastSeen string `json:"lastSeen"`
|
||||
Clearance int `json:"clearance"`
|
||||
Online bool `json:"online"`
|
||||
}
|
||||
|
||||
// ToFleetCard converts a DB host to a dashboard card.
|
||||
func ToFleetCard(h *types.Host) FleetHostCard {
|
||||
if h == nil {
|
||||
return FleetHostCard{}
|
||||
}
|
||||
|
||||
hps := h.HashrateHps
|
||||
if hps == 0 {
|
||||
hps = h.Hashrate
|
||||
}
|
||||
|
||||
online := h.Status == "online" || h.Status == "mining" || h.Status == "paused"
|
||||
tier := h.CurrentTier
|
||||
if tier == 0 {
|
||||
tier = 2
|
||||
}
|
||||
tierName := policy.TierDisplayName(h.TierType)
|
||||
if tierName == "" || tierName == h.TierType {
|
||||
tierName = "Bundled xmrig"
|
||||
}
|
||||
tierState := h.TierState
|
||||
if tierState == "" {
|
||||
switch h.Status {
|
||||
case "mining":
|
||||
tierState = "active"
|
||||
case "probing":
|
||||
tierState = "probing"
|
||||
case "paused":
|
||||
tierState = "paused"
|
||||
case "offline":
|
||||
tierState = "idle"
|
||||
online = false
|
||||
default:
|
||||
tierState = "idle"
|
||||
}
|
||||
}
|
||||
|
||||
arch := h.Phenotype
|
||||
if arch == "" {
|
||||
arch = "linux/amd64"
|
||||
}
|
||||
|
||||
lastSeen := ""
|
||||
if h.LastSeenAt != nil {
|
||||
lastSeen = h.LastSeenAt.UTC().Format(time.RFC3339)
|
||||
}
|
||||
|
||||
return FleetHostCard{
|
||||
ID: h.ID,
|
||||
Hostname: h.Hostname,
|
||||
IP: coalesceIP(h.Fingerprint),
|
||||
Arch: arch,
|
||||
Hashrate: hps,
|
||||
Tier: tier,
|
||||
TierName: tierName,
|
||||
TierState: tierState,
|
||||
Algo: "rx/0",
|
||||
UptimeSec: 0,
|
||||
LastSeen: lastSeen,
|
||||
Clearance: h.ClearanceLevel,
|
||||
Online: online,
|
||||
}
|
||||
}
|
||||
|
||||
func coalesceIP(fp string) string {
|
||||
if fp == "" {
|
||||
return "—"
|
||||
}
|
||||
return fp
|
||||
}
|
||||
|
||||
// BuildFleetSummary builds the GET /api/v1/fleet response.
|
||||
func (s *Store) BuildFleetSummary() (FleetSummary, error) {
|
||||
hosts, err := s.ListHosts()
|
||||
if err != nil {
|
||||
return FleetSummary{}, err
|
||||
}
|
||||
|
||||
summary := FleetSummary{Hosts: make([]FleetHostCard, 0, len(hosts))}
|
||||
for i := range hosts {
|
||||
card := ToFleetCard(&hosts[i])
|
||||
summary.Hosts = append(summary.Hosts, card)
|
||||
if card.Online {
|
||||
summary.OnlineCount++
|
||||
summary.TotalHashrate += card.Hashrate
|
||||
}
|
||||
}
|
||||
return summary, nil
|
||||
}
|
||||
|
||||
// SeedDemoHost inserts a demo host when fleet is empty.
|
||||
func (s *Store) SeedDemoHost() error {
|
||||
hosts, err := s.ListHosts()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(hosts) > 0 {
|
||||
return nil
|
||||
}
|
||||
id := uuid.NewString()
|
||||
now := time.Now().UTC().Format(time.RFC3339)
|
||||
_, err = s.db.Exec(`
|
||||
INSERT INTO hosts (id, hostname, fingerprint, phenotype, status, hashrate, hashrate_hps,
|
||||
current_tier, tier_type, tier_state, clearance_level, last_seen_at, created_at, updated_at)
|
||||
VALUES (?, 'forge-node-alpha', '10.0.1.12', 'linux/amd64', 'mining', 18200000, 18200000,
|
||||
2, 'xmrig', 'active', 2, ?, ?, ?)`,
|
||||
id, now, now, now)
|
||||
return err
|
||||
}
|
||||
|
||||
// TouchHost enrolls or refreshes a host from the register API.
|
||||
func (s *Store) TouchHost(hostname, fingerprint, arch string) (*types.Host, error) {
|
||||
phenotype := ""
|
||||
if arch != "" {
|
||||
phenotype = "linux/" + arch
|
||||
}
|
||||
hb := types.HeartbeatPayload{
|
||||
Hostname: hostname,
|
||||
Fingerprint: fingerprint,
|
||||
Arch: arch,
|
||||
TierState: "idle",
|
||||
}
|
||||
hb.SetHashrateFields(0)
|
||||
host, err := s.UpsertHeartbeat(hb)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if phenotype != "" {
|
||||
now := time.Now().UTC().Format(time.RFC3339)
|
||||
_, _ = s.db.Exec(`UPDATE hosts SET phenotype = ?, updated_at = ? WHERE id = ?`,
|
||||
phenotype, now, host.ID)
|
||||
return s.GetHost(host.ID)
|
||||
}
|
||||
return host, nil
|
||||
}
|
||||
|
||||
// SetHostStatus updates host status.
|
||||
func (s *Store) SetHostStatus(id, status string) error {
|
||||
now := time.Now().UTC().Format(time.RFC3339)
|
||||
_, err := s.db.Exec(`UPDATE hosts SET status = ?, updated_at = ? WHERE id = ?`, status, now, id)
|
||||
return err
|
||||
}
|
||||
|
||||
// ErrNotFound indicates a missing host.
|
||||
var ErrNotFound = fmt.Errorf("host not found")
|
||||
Reference in New Issue
Block a user