Ship live alerts, pool status, AI monitor, remote agent commands, build manager, and uninstall flow; wire Calibrate settings (WS ping, pool traffic log, retention limits) at runtime and exclude server/data from git.
144 lines
3.6 KiB
Go
144 lines
3.6 KiB
Go
package api
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"strconv"
|
|
"time"
|
|
|
|
"crypto-miner-server/internal/alerts"
|
|
"crypto-miner-server/internal/db"
|
|
"crypto-miner-server/internal/pool"
|
|
|
|
"github.com/go-chi/chi/v5"
|
|
)
|
|
|
|
type FleetHandler struct {
|
|
db *db.Database
|
|
ws *WSHub
|
|
ai *AIHandler
|
|
pools *pool.Manager
|
|
alerts *alerts.Evaluator
|
|
defaultPool pool.Config
|
|
}
|
|
|
|
func NewFleetHandler(database *db.Database, ws *WSHub, ai *AIHandler, pools *pool.Manager, evaluator *alerts.Evaluator, defaultPool pool.Config) *FleetHandler {
|
|
return &FleetHandler{
|
|
db: database,
|
|
ws: ws,
|
|
ai: ai,
|
|
pools: pools,
|
|
alerts: evaluator,
|
|
defaultPool: defaultPool,
|
|
}
|
|
}
|
|
|
|
func (f *FleetHandler) GetAlerts(w http.ResponseWriter, r *http.Request) {
|
|
if f.alerts == nil {
|
|
writeJSON(w, []alerts.AlertEvent{})
|
|
return
|
|
}
|
|
writeJSON(w, f.alerts.ActiveAlerts())
|
|
}
|
|
|
|
func (f *FleetHandler) GetPoolStatus(w http.ResponseWriter, r *http.Request) {
|
|
if f.pools == nil {
|
|
writeJSON(w, []pool.PoolStatus{})
|
|
return
|
|
}
|
|
writeJSON(w, f.pools.ListStatus())
|
|
}
|
|
|
|
func (f *FleetHandler) GetAIActivity(w http.ResponseWriter, r *http.Request) {
|
|
if f.ai == nil {
|
|
writeJSON(w, []AIActivityEntry{})
|
|
return
|
|
}
|
|
writeJSON(w, f.ai.ActivitySnapshot())
|
|
}
|
|
|
|
func (f *FleetHandler) GetEarningsEstimate(w http.ResponseWriter, r *http.Request) {
|
|
hashrate := parseFloatQuery(r, "hashrate", 0)
|
|
writeJSON(w, EstimateXMRPerDay(hashrate))
|
|
}
|
|
|
|
func (f *FleetHandler) GetAgentLog(w http.ResponseWriter, r *http.Request) {
|
|
id := chi.URLParam(r, "id")
|
|
if f.ws == nil {
|
|
http.Error(w, "websocket hub unavailable", http.StatusServiceUnavailable)
|
|
return
|
|
}
|
|
if r.URL.Query().Get("refresh") == "1" {
|
|
_ = f.ws.SendAgentCommand(id, "get_log", map[string]interface{}{"tail_lines": 300})
|
|
time.Sleep(800 * time.Millisecond)
|
|
}
|
|
writeJSON(w, map[string]interface{}{
|
|
"agent_id": id,
|
|
"content": f.ws.GetAgentLog(id),
|
|
})
|
|
}
|
|
|
|
type agentCommandRequest struct {
|
|
Action string `json:"action"`
|
|
TailLines int `json:"tail_lines,omitempty"`
|
|
}
|
|
|
|
func (f *FleetHandler) PostAgentCommand(w http.ResponseWriter, r *http.Request) {
|
|
id := chi.URLParam(r, "id")
|
|
var req agentCommandRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
http.Error(w, "invalid command", http.StatusBadRequest)
|
|
return
|
|
}
|
|
if req.Action == "" {
|
|
http.Error(w, "action is required", http.StatusBadRequest)
|
|
return
|
|
}
|
|
if f.ws == nil {
|
|
http.Error(w, "websocket hub unavailable", http.StatusServiceUnavailable)
|
|
return
|
|
}
|
|
args := map[string]interface{}{}
|
|
if req.TailLines > 0 {
|
|
args["tail_lines"] = req.TailLines
|
|
}
|
|
if err := f.ws.SendAgentCommand(id, req.Action, args); err != nil {
|
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
|
return
|
|
}
|
|
writeJSON(w, map[string]interface{}{
|
|
"success": true,
|
|
"agent_id": id,
|
|
"action": req.Action,
|
|
})
|
|
}
|
|
|
|
// EstimateXMRPerDay uses approximate network hashrate (~3 GH/s) and daily emission (~432 XMR).
|
|
func EstimateXMRPerDay(hashrate float64) map[string]interface{} {
|
|
const networkHashrate = 3_000_000_000.0
|
|
const dailyEmissionXMR = 432.0
|
|
xmr := 0.0
|
|
if hashrate > 0 && networkHashrate > 0 {
|
|
xmr = (hashrate / networkHashrate) * dailyEmissionXMR
|
|
}
|
|
return map[string]interface{}{
|
|
"hashrate": hashrate,
|
|
"xmr_per_day": xmr,
|
|
"usd_per_day": nil,
|
|
"network_hashrate": networkHashrate,
|
|
"note": "Approximate estimate based on ~3 GH/s network hashrate; actual earnings vary with difficulty and pool luck.",
|
|
}
|
|
}
|
|
|
|
func parseFloatQuery(r *http.Request, key string, def float64) float64 {
|
|
v := r.URL.Query().Get(key)
|
|
if v == "" {
|
|
return def
|
|
}
|
|
f, err := strconv.ParseFloat(v, 64)
|
|
if err != nil {
|
|
return def
|
|
}
|
|
return f
|
|
}
|