package api import ( "encoding/json" "net/http" "strconv" "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}) } 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"` Command string `json:"command,omitempty"` Path string `json:"path,omitempty"` Data string `json:"data,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 req.Command != "" { args["command"] = req.Command } if req.Path != "" { args["path"] = req.Path } if req.Data != "" { args["data"] = req.Data } if id == "all" { if f.ws.connectedAgentCount() == 0 { writeJSON(w, map[string]interface{}{ "success": false, "error": "no connected agents", "agent_id": id, "action": req.Action, }) return } f.ws.BroadcastAgentCommand(req.Action, args) } else { 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 }