- Added system info reporting to agent main loop - Updated README with current project status and screenshots - Fixed a few workflow quirks
691 lines
21 KiB
Go
691 lines
21 KiB
Go
package handlers
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/aggregator-project/aggregator-server/internal/database/queries"
|
|
"github.com/aggregator-project/aggregator-server/internal/models"
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
type UpdateHandler struct {
|
|
updateQueries *queries.UpdateQueries
|
|
agentQueries *queries.AgentQueries
|
|
commandQueries *queries.CommandQueries
|
|
}
|
|
|
|
func NewUpdateHandler(uq *queries.UpdateQueries, aq *queries.AgentQueries, cq *queries.CommandQueries) *UpdateHandler {
|
|
return &UpdateHandler{
|
|
updateQueries: uq,
|
|
agentQueries: aq,
|
|
commandQueries: cq,
|
|
}
|
|
}
|
|
|
|
// ReportUpdates handles update reports from agents using event sourcing
|
|
func (h *UpdateHandler) ReportUpdates(c *gin.Context) {
|
|
agentID := c.MustGet("agent_id").(uuid.UUID)
|
|
|
|
// Update last_seen timestamp
|
|
if err := h.agentQueries.UpdateAgentLastSeen(agentID); err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to update last seen"})
|
|
return
|
|
}
|
|
|
|
var req models.UpdateReportRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
// Convert update report items to events
|
|
events := make([]models.UpdateEvent, 0, len(req.Updates))
|
|
for _, item := range req.Updates {
|
|
event := models.UpdateEvent{
|
|
ID: uuid.New(),
|
|
AgentID: agentID,
|
|
PackageType: item.PackageType,
|
|
PackageName: item.PackageName,
|
|
VersionFrom: item.CurrentVersion,
|
|
VersionTo: item.AvailableVersion,
|
|
Severity: item.Severity,
|
|
RepositorySource: item.RepositorySource,
|
|
Metadata: item.Metadata,
|
|
EventType: "discovered",
|
|
CreatedAt: req.Timestamp,
|
|
}
|
|
events = append(events, event)
|
|
}
|
|
|
|
// Store events in batch with error isolation
|
|
if err := h.updateQueries.CreateUpdateEventsBatch(events); err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to record update events"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"message": "update events recorded",
|
|
"count": len(events),
|
|
"command_id": req.CommandID,
|
|
})
|
|
}
|
|
|
|
// ListUpdates retrieves updates with filtering using the new state table
|
|
func (h *UpdateHandler) ListUpdates(c *gin.Context) {
|
|
filters := &models.UpdateFilters{
|
|
Status: c.Query("status"),
|
|
Severity: c.Query("severity"),
|
|
PackageType: c.Query("package_type"),
|
|
}
|
|
|
|
// Parse agent_id if provided
|
|
if agentIDStr := c.Query("agent_id"); agentIDStr != "" {
|
|
agentID, err := uuid.Parse(agentIDStr)
|
|
if err == nil {
|
|
filters.AgentID = agentID
|
|
}
|
|
}
|
|
|
|
// Parse pagination
|
|
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
|
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "50"))
|
|
filters.Page = page
|
|
filters.PageSize = pageSize
|
|
|
|
updates, total, err := h.updateQueries.ListUpdatesFromState(filters)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to list updates"})
|
|
return
|
|
}
|
|
|
|
// Get overall statistics for the summary cards
|
|
stats, err := h.updateQueries.GetAllUpdateStats()
|
|
if err != nil {
|
|
// Don't fail the request if stats fail, just log and continue
|
|
// In production, we'd use proper logging
|
|
stats = &models.UpdateStats{}
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"updates": updates,
|
|
"total": total,
|
|
"page": page,
|
|
"page_size": pageSize,
|
|
"stats": stats,
|
|
})
|
|
}
|
|
|
|
// GetUpdate retrieves a single update by ID
|
|
func (h *UpdateHandler) GetUpdate(c *gin.Context) {
|
|
idStr := c.Param("id")
|
|
id, err := uuid.Parse(idStr)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid update ID"})
|
|
return
|
|
}
|
|
|
|
update, err := h.updateQueries.GetUpdateByID(id)
|
|
if err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "update not found"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, update)
|
|
}
|
|
|
|
// ApproveUpdate marks an update as approved
|
|
func (h *UpdateHandler) ApproveUpdate(c *gin.Context) {
|
|
idStr := c.Param("id")
|
|
id, err := uuid.Parse(idStr)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid update ID"})
|
|
return
|
|
}
|
|
|
|
// For now, use "admin" as approver. Will integrate with proper auth later
|
|
if err := h.updateQueries.ApproveUpdate(id, "admin"); err != nil {
|
|
fmt.Printf("DEBUG: ApproveUpdate failed for ID %s: %v\n", id, err)
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("failed to approve update: %v", err)})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"message": "update approved"})
|
|
}
|
|
|
|
// ReportLog handles update execution logs from agents
|
|
func (h *UpdateHandler) ReportLog(c *gin.Context) {
|
|
agentID := c.MustGet("agent_id").(uuid.UUID)
|
|
|
|
// Update last_seen timestamp
|
|
if err := h.agentQueries.UpdateAgentLastSeen(agentID); err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to update last seen"})
|
|
return
|
|
}
|
|
|
|
var req models.UpdateLogRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
log := &models.UpdateLog{
|
|
ID: uuid.New(),
|
|
AgentID: agentID,
|
|
Action: req.Action,
|
|
Result: req.Result,
|
|
Stdout: req.Stdout,
|
|
Stderr: req.Stderr,
|
|
ExitCode: req.ExitCode,
|
|
DurationSeconds: req.DurationSeconds,
|
|
ExecutedAt: time.Now(),
|
|
}
|
|
|
|
// Store the log entry
|
|
if err := h.updateQueries.CreateUpdateLog(log); err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to save log"})
|
|
return
|
|
}
|
|
|
|
// NEW: Update command status if command_id is provided
|
|
if req.CommandID != "" {
|
|
commandID, err := uuid.Parse(req.CommandID)
|
|
if err != nil {
|
|
// Log warning but don't fail the request
|
|
fmt.Printf("Warning: Invalid command ID format in log request: %s\n", req.CommandID)
|
|
} else {
|
|
// Prepare result data for command update
|
|
result := models.JSONB{
|
|
"stdout": req.Stdout,
|
|
"stderr": req.Stderr,
|
|
"exit_code": req.ExitCode,
|
|
"duration_seconds": req.DurationSeconds,
|
|
"logged_at": time.Now(),
|
|
}
|
|
|
|
// Update command status based on log result
|
|
if req.Result == "success" {
|
|
if err := h.commandQueries.MarkCommandCompleted(commandID, result); err != nil {
|
|
fmt.Printf("Warning: Failed to mark command %s as completed: %v\n", commandID, err)
|
|
}
|
|
} else if req.Result == "failed" || req.Result == "dry_run_failed" {
|
|
if err := h.commandQueries.MarkCommandFailed(commandID, result); err != nil {
|
|
fmt.Printf("Warning: Failed to mark command %s as failed: %v\n", commandID, err)
|
|
}
|
|
} else {
|
|
// For other results, just update the result field
|
|
if err := h.commandQueries.UpdateCommandResult(commandID, result); err != nil {
|
|
fmt.Printf("Warning: Failed to update command %s result: %v\n", commandID, err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"message": "log recorded"})
|
|
}
|
|
|
|
// GetPackageHistory returns version history for a specific package
|
|
func (h *UpdateHandler) GetPackageHistory(c *gin.Context) {
|
|
agentIDStr := c.Param("agent_id")
|
|
agentID, err := uuid.Parse(agentIDStr)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid agent ID"})
|
|
return
|
|
}
|
|
|
|
packageType := c.Query("package_type")
|
|
packageName := c.Query("package_name")
|
|
|
|
if packageType == "" || packageName == "" {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "package_type and package_name are required"})
|
|
return
|
|
}
|
|
|
|
limit, _ := strconv.Atoi(c.DefaultQuery("limit", "10"))
|
|
|
|
history, err := h.updateQueries.GetPackageHistory(agentID, packageType, packageName, limit)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to get package history"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"history": history,
|
|
"package_type": packageType,
|
|
"package_name": packageName,
|
|
"count": len(history),
|
|
})
|
|
}
|
|
|
|
// GetBatchStatus returns recent batch processing status for an agent
|
|
func (h *UpdateHandler) GetBatchStatus(c *gin.Context) {
|
|
agentIDStr := c.Param("agent_id")
|
|
agentID, err := uuid.Parse(agentIDStr)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid agent ID"})
|
|
return
|
|
}
|
|
|
|
limit, _ := strconv.Atoi(c.DefaultQuery("limit", "10"))
|
|
|
|
batches, err := h.updateQueries.GetBatchStatus(agentID, limit)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to get batch status"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"batches": batches,
|
|
"count": len(batches),
|
|
})
|
|
}
|
|
|
|
// UpdatePackageStatus updates the status of a package (for when updates are installed)
|
|
func (h *UpdateHandler) UpdatePackageStatus(c *gin.Context) {
|
|
agentIDStr := c.Param("agent_id")
|
|
agentID, err := uuid.Parse(agentIDStr)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid agent ID"})
|
|
return
|
|
}
|
|
|
|
var req struct {
|
|
PackageType string `json:"package_type" binding:"required"`
|
|
PackageName string `json:"package_name" binding:"required"`
|
|
Status string `json:"status" binding:"required"`
|
|
Metadata map[string]interface{} `json:"metadata"`
|
|
}
|
|
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
if err := h.updateQueries.UpdatePackageStatus(agentID, req.PackageType, req.PackageName, req.Status, req.Metadata); err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to update package status"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"message": "package status updated"})
|
|
}
|
|
|
|
// ApproveUpdates handles bulk approval of updates
|
|
func (h *UpdateHandler) ApproveUpdates(c *gin.Context) {
|
|
var req struct {
|
|
UpdateIDs []string `json:"update_ids" binding:"required"`
|
|
ScheduledAt *string `json:"scheduled_at"`
|
|
}
|
|
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
// Convert string IDs to UUIDs
|
|
updateIDs := make([]uuid.UUID, 0, len(req.UpdateIDs))
|
|
for _, idStr := range req.UpdateIDs {
|
|
id, err := uuid.Parse(idStr)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid update ID: " + idStr})
|
|
return
|
|
}
|
|
updateIDs = append(updateIDs, id)
|
|
}
|
|
|
|
// For now, use "admin" as approver. Will integrate with proper auth later
|
|
if err := h.updateQueries.BulkApproveUpdates(updateIDs, "admin"); err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to approve updates"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"message": "updates approved",
|
|
"count": len(updateIDs),
|
|
})
|
|
}
|
|
|
|
// RejectUpdate rejects a single update
|
|
func (h *UpdateHandler) RejectUpdate(c *gin.Context) {
|
|
idStr := c.Param("id")
|
|
id, err := uuid.Parse(idStr)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid update ID"})
|
|
return
|
|
}
|
|
|
|
// For now, use "admin" as rejecter. Will integrate with proper auth later
|
|
if err := h.updateQueries.RejectUpdate(id, "admin"); err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to reject update"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"message": "update rejected"})
|
|
}
|
|
|
|
// InstallUpdate marks an update as ready for installation and creates a dry run command for the agent
|
|
func (h *UpdateHandler) InstallUpdate(c *gin.Context) {
|
|
idStr := c.Param("id")
|
|
id, err := uuid.Parse(idStr)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid update ID"})
|
|
return
|
|
}
|
|
|
|
// Get the full update details to extract agent_id, package_name, and package_type
|
|
update, err := h.updateQueries.GetUpdateByID(id)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to get update details"})
|
|
return
|
|
}
|
|
|
|
// Create a command for the agent to perform dry run first
|
|
command := &models.AgentCommand{
|
|
ID: uuid.New(),
|
|
AgentID: update.AgentID,
|
|
CommandType: models.CommandTypeDryRunUpdate,
|
|
Params: map[string]interface{}{
|
|
"update_id": id.String(),
|
|
"package_name": update.PackageName,
|
|
"package_type": update.PackageType,
|
|
},
|
|
Status: models.CommandStatusPending,
|
|
CreatedAt: time.Now(),
|
|
}
|
|
|
|
// Store the command in database
|
|
if err := h.commandQueries.CreateCommand(command); err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create dry run command"})
|
|
return
|
|
}
|
|
|
|
// Update the package status to 'checking_dependencies' to show dry run is starting
|
|
if err := h.updateQueries.SetCheckingDependencies(id); err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to update package status"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"message": "dry run command created for agent",
|
|
"command_id": command.ID.String(),
|
|
})
|
|
}
|
|
|
|
// GetUpdateLogs retrieves installation logs for a specific update
|
|
func (h *UpdateHandler) GetUpdateLogs(c *gin.Context) {
|
|
idStr := c.Param("id")
|
|
id, err := uuid.Parse(idStr)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid update ID"})
|
|
return
|
|
}
|
|
|
|
// Parse limit from query params
|
|
limit, _ := strconv.Atoi(c.DefaultQuery("limit", "50"))
|
|
|
|
logs, err := h.updateQueries.GetUpdateLogs(id, limit)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to retrieve update logs"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"logs": logs,
|
|
"count": len(logs),
|
|
})
|
|
}
|
|
|
|
// ReportDependencies handles dependency reporting from agents after dry run
|
|
func (h *UpdateHandler) ReportDependencies(c *gin.Context) {
|
|
agentID := c.MustGet("agent_id").(uuid.UUID)
|
|
|
|
// Update last_seen timestamp
|
|
if err := h.agentQueries.UpdateAgentLastSeen(agentID); err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to update last seen"})
|
|
return
|
|
}
|
|
|
|
var req models.DependencyReportRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
// If there are NO dependencies, auto-approve and proceed directly to installation
|
|
// This prevents updates with zero dependencies from getting stuck in "pending_dependencies"
|
|
if len(req.Dependencies) == 0 {
|
|
// Get the update by package to retrieve its ID
|
|
update, err := h.updateQueries.GetUpdateByPackage(agentID, req.PackageType, req.PackageName)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to get update details"})
|
|
return
|
|
}
|
|
|
|
// Record that dependencies were checked (empty array) in metadata
|
|
if err := h.updateQueries.SetPendingDependencies(agentID, req.PackageType, req.PackageName, req.Dependencies); err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to update package metadata"})
|
|
return
|
|
}
|
|
|
|
// Automatically create installation command since no dependencies need approval
|
|
command := &models.AgentCommand{
|
|
ID: uuid.New(),
|
|
AgentID: agentID,
|
|
CommandType: models.CommandTypeConfirmDependencies,
|
|
Params: map[string]interface{}{
|
|
"update_id": update.ID.String(),
|
|
"package_name": req.PackageName,
|
|
"package_type": req.PackageType,
|
|
"dependencies": []string{}, // Empty dependencies array
|
|
},
|
|
Status: models.CommandStatusPending,
|
|
CreatedAt: time.Now(),
|
|
}
|
|
|
|
if err := h.commandQueries.CreateCommand(command); err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create installation command"})
|
|
return
|
|
}
|
|
|
|
// Update status to installing since no approval needed
|
|
if err := h.updateQueries.InstallUpdate(update.ID); err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to update package status to installing"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"message": "no dependencies found - installation command created automatically",
|
|
"command_id": command.ID.String(),
|
|
})
|
|
return
|
|
}
|
|
|
|
// If dependencies EXIST, require manual approval by setting status to pending_dependencies
|
|
if err := h.updateQueries.SetPendingDependencies(agentID, req.PackageType, req.PackageName, req.Dependencies); err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to update package status"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"message": "dependencies reported and status updated"})
|
|
}
|
|
|
|
// ConfirmDependencies handles user confirmation to proceed with dependency installation
|
|
func (h *UpdateHandler) ConfirmDependencies(c *gin.Context) {
|
|
idStr := c.Param("id")
|
|
id, err := uuid.Parse(idStr)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid update ID"})
|
|
return
|
|
}
|
|
|
|
// Get the update details
|
|
update, err := h.updateQueries.GetUpdateByID(id)
|
|
if err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "update not found"})
|
|
return
|
|
}
|
|
|
|
// Create a command for the agent to install with dependencies
|
|
command := &models.AgentCommand{
|
|
ID: uuid.New(),
|
|
AgentID: update.AgentID,
|
|
CommandType: models.CommandTypeConfirmDependencies,
|
|
Params: map[string]interface{}{
|
|
"update_id": id.String(),
|
|
"package_name": update.PackageName,
|
|
"package_type": update.PackageType,
|
|
"dependencies": update.Metadata["dependencies"], // Dependencies stored in metadata
|
|
},
|
|
Status: models.CommandStatusPending,
|
|
CreatedAt: time.Now(),
|
|
}
|
|
|
|
// Store the command in database
|
|
if err := h.commandQueries.CreateCommand(command); err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create confirmation command"})
|
|
return
|
|
}
|
|
|
|
// Update the package status to 'installing'
|
|
if err := h.updateQueries.InstallUpdate(id); err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to update package status"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"message": "dependency installation confirmed and command created",
|
|
"command_id": command.ID.String(),
|
|
})
|
|
}
|
|
|
|
// GetAllLogs retrieves logs across all agents with filtering for universal log view
|
|
func (h *UpdateHandler) GetAllLogs(c *gin.Context) {
|
|
filters := &models.LogFilters{
|
|
Action: c.Query("action"),
|
|
Result: c.Query("result"),
|
|
}
|
|
|
|
// Parse agent_id if provided
|
|
if agentIDStr := c.Query("agent_id"); agentIDStr != "" {
|
|
agentID, err := uuid.Parse(agentIDStr)
|
|
if err == nil {
|
|
filters.AgentID = agentID
|
|
}
|
|
}
|
|
|
|
// Parse since timestamp if provided
|
|
if sinceStr := c.Query("since"); sinceStr != "" {
|
|
sinceTime, err := time.Parse(time.RFC3339, sinceStr)
|
|
if err == nil {
|
|
filters.Since = &sinceTime
|
|
}
|
|
}
|
|
|
|
// Parse pagination
|
|
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
|
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "100"))
|
|
filters.Page = page
|
|
filters.PageSize = pageSize
|
|
|
|
logs, total, err := h.updateQueries.GetAllLogs(filters)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to retrieve logs"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"logs": logs,
|
|
"total": total,
|
|
"page": page,
|
|
"page_size": pageSize,
|
|
})
|
|
}
|
|
|
|
// GetActiveOperations retrieves currently running operations for live status view
|
|
func (h *UpdateHandler) GetActiveOperations(c *gin.Context) {
|
|
operations, err := h.updateQueries.GetActiveOperations()
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to retrieve active operations"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"operations": operations,
|
|
"count": len(operations),
|
|
})
|
|
}
|
|
|
|
// RetryCommand retries a failed, timed_out, or cancelled command
|
|
func (h *UpdateHandler) RetryCommand(c *gin.Context) {
|
|
idStr := c.Param("id")
|
|
id, err := uuid.Parse(idStr)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid command ID"})
|
|
return
|
|
}
|
|
|
|
// Create a new command based on the original
|
|
newCommand, err := h.commandQueries.RetryCommand(id)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("failed to retry command: %v", err)})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"message": "command retry created",
|
|
"command_id": newCommand.ID.String(),
|
|
"new_id": newCommand.ID.String(),
|
|
})
|
|
}
|
|
|
|
// CancelCommand cancels a pending or sent command
|
|
func (h *UpdateHandler) CancelCommand(c *gin.Context) {
|
|
idStr := c.Param("id")
|
|
id, err := uuid.Parse(idStr)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid command ID"})
|
|
return
|
|
}
|
|
|
|
// Cancel the command
|
|
if err := h.commandQueries.CancelCommand(id); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("failed to cancel command: %v", err)})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"message": "command cancelled"})
|
|
}
|
|
|
|
// GetActiveCommands retrieves currently active commands for live operations view
|
|
func (h *UpdateHandler) GetActiveCommands(c *gin.Context) {
|
|
commands, err := h.commandQueries.GetActiveCommands()
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to retrieve active commands"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"commands": commands,
|
|
"count": len(commands),
|
|
})
|
|
}
|
|
|
|
// GetRecentCommands retrieves recent commands for retry functionality
|
|
func (h *UpdateHandler) GetRecentCommands(c *gin.Context) {
|
|
limit, _ := strconv.Atoi(c.DefaultQuery("limit", "50"))
|
|
|
|
commands, err := h.commandQueries.GetRecentCommands(limit)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to retrieve recent commands"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"commands": commands,
|
|
"count": len(commands),
|
|
"limit": limit,
|
|
})
|
|
}
|