feat: fleet intelligence dashboard -- health score, XMR price, contribution map, analytics

This commit is contained in:
drjones
2026-05-30 15:10:50 -07:00
parent 2e36483158
commit 117801f882
9 changed files with 826 additions and 61 deletions

View File

@@ -17,6 +17,18 @@ import (
"github.com/go-chi/chi/v5"
)
// xmrPriceEntry caches the CoinGecko price response to avoid hammering the API.
type xmrPriceEntry struct {
USD float64
fetchedAt time.Time
}
var (
xmrPriceMu sync.Mutex
xmrPriceCache *xmrPriceEntry
xmrPriceTTL = 10 * time.Minute
)
type FleetHandler struct {
db *db.Database
ws *WSHub
@@ -72,6 +84,51 @@ func (f *FleetHandler) GetAIActivity(w http.ResponseWriter, r *http.Request) {
writeJSON(w, f.ai.ActivitySnapshot())
}
// GetXMRPrice returns the current XMR/USD price from CoinGecko, cached for 10 minutes.
// Falls back to a 503 when the upstream is unreachable so the frontend can degrade gracefully.
func (f *FleetHandler) GetXMRPrice(w http.ResponseWriter, r *http.Request) {
xmrPriceMu.Lock()
if xmrPriceCache != nil && time.Since(xmrPriceCache.fetchedAt) < xmrPriceTTL {
usd := xmrPriceCache.USD
at := xmrPriceCache.fetchedAt
xmrPriceMu.Unlock()
writeJSON(w, map[string]interface{}{
"usd": usd,
"fetched_at": at.UTC().Format(time.RFC3339),
"source": "coingecko",
})
return
}
xmrPriceMu.Unlock()
client := &http.Client{Timeout: 8 * time.Second}
resp, err := client.Get("https://api.coingecko.com/api/v3/simple/price?ids=monero&vs_currencies=usd") //nolint:gosec
if err != nil {
http.Error(w, "price fetch failed: "+err.Error(), http.StatusServiceUnavailable)
return
}
defer resp.Body.Close()
body, _ := io.ReadAll(io.LimitReader(resp.Body, 4096))
var raw map[string]map[string]float64
if err := json.Unmarshal(body, &raw); err != nil || raw["monero"] == nil {
http.Error(w, "price parse failed", http.StatusBadGateway)
return
}
usd := raw["monero"]["usd"]
entry := &xmrPriceEntry{USD: usd, fetchedAt: time.Now()}
xmrPriceMu.Lock()
xmrPriceCache = entry
xmrPriceMu.Unlock()
writeJSON(w, map[string]interface{}{
"usd": usd,
"fetched_at": entry.fetchedAt.UTC().Format(time.RFC3339),
"source": "coingecko",
})
}
// GetEarningsEstimate — kept for backwards compat; delegates to GetEarnings.
func (f *FleetHandler) GetEarningsEstimate(w http.ResponseWriter, r *http.Request) {
f.GetEarnings(w, r)

View File

@@ -322,6 +322,7 @@ func NewRouter(database *db.Database, wsHub *WSHub, configHandler *ConfigHandler
r.Get("/pools/status", fleetHandler.GetPoolStatus)
r.Get("/ai/activity", fleetHandler.GetAIActivity)
r.Get("/earnings/estimate", fleetHandler.GetEarningsEstimate)
r.Get("/market/xmr", fleetHandler.GetXMRPrice)
}
// Shares