diff --git a/cmd/server/main.go b/cmd/server/main.go index aee8a3a..5dab3c7 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -45,6 +45,7 @@ func main() { protectedRoutes.Use(authMgr.Middleware) protectedRoutes.Get("/", uiHandler.Dashboard) protectedRoutes.Get("/agents/{id}", uiHandler.AgentDetail) + protectedRoutes.Post("/agents/{id}/hostname", uiHandler.UpdateAgentHostname) protectedRoutes.Post("/logout", uiHandler.Logout) protectedRoutes.Post("/api/agents/remove-stale", uiHandler.RemoveStaleAgents) protectedRoutes.Post("/api/agents/{id}/delete", uiHandler.DeleteAgent) diff --git a/internal/api/api.go b/internal/api/api.go index fdeb8c1..3dfee0b 100644 --- a/internal/api/api.go +++ b/internal/api/api.go @@ -73,3 +73,32 @@ func (h *Handler) DeleteAgent(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]string{"status": "deleted"}) } + +// UpdateHostname updates the hostname for an agent. +func (h *Handler) UpdateHostname(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + http.Error(w, "method not allowed", http.StatusMethodNotAllowed) + return + } + + agentID := r.PathValue("id") + if agentID == "" { + http.Error(w, "missing agent id", http.StatusBadRequest) + return + } + + hostname := r.FormValue("hostname") + if hostname == "" { + http.Error(w, "missing hostname", http.StatusBadRequest) + return + } + + err := h.store.UpdateHostname(agentID, hostname) + if err != nil { + http.Error(w, "agent not found", http.StatusNotFound) + return + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(map[string]string{"status": "updated"}) +} diff --git a/internal/store/store.go b/internal/store/store.go index 52ffce3..74e6a30 100644 --- a/internal/store/store.go +++ b/internal/store/store.go @@ -1,12 +1,18 @@ package store import ( + "errors" "sync" "time" "nerd-monitor/internal/stats" ) +var ( + // ErrAgentNotFound is returned when an agent is not found. + ErrAgentNotFound = errors.New("agent not found") +) + // AgentStats represents the latest stats for an agent. type AgentStats struct { ID string @@ -76,3 +82,17 @@ func (s *Store) DeleteAgent(id string) { delete(s.agents, id) } + +// UpdateHostname updates the hostname for an agent. +func (s *Store) UpdateHostname(id string, hostname string) error { + s.mu.Lock() + defer s.mu.Unlock() + + agent, exists := s.agents[id] + if !exists { + return ErrAgentNotFound + } + + agent.Hostname = hostname + return nil +} diff --git a/internal/ui/handlers.go b/internal/ui/handlers.go index 8fdd734..edf463b 100644 --- a/internal/ui/handlers.go +++ b/internal/ui/handlers.go @@ -133,6 +133,30 @@ func (h *Handler) DeleteAgent(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, "/", http.StatusSeeOther) } +// UpdateAgentHostname handles hostname updates for agents. +func (h *Handler) UpdateAgentHostname(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + + agentID := r.PathValue("id") + hostname := r.FormValue("hostname") + + if hostname == "" { + http.Error(w, "Hostname cannot be empty", http.StatusBadRequest) + return + } + + err := h.store.UpdateHostname(agentID, hostname) + if err != nil { + http.NotFound(w, r) + return + } + + http.Redirect(w, r, "/agents/"+agentID, http.StatusSeeOther) +} + // getStaleAgents returns agents that haven't reported in 6 months. func (h *Handler) getStaleAgents() []*store.AgentStats { const staleThreshold = 6 * 30 * 24 * time.Hour diff --git a/views/agent_detail.templ b/views/agent_detail.templ index f7c0675..5c0c2d2 100644 --- a/views/agent_detail.templ +++ b/views/agent_detail.templ @@ -17,6 +17,7 @@ templ agentDetailContent(agent *store.AgentStats) { @AgentStatusBadge(agent.LastSeen) +