feat: implement task queue statistics and add UI templates for job management and visualization.

This commit is contained in:
2026-08-15 00:00:41 -07:00
parent 5510e3ac82
commit 286278c1ec
12 changed files with 1144 additions and 644 deletions
+63 -10
View File
@@ -1,9 +1,9 @@
package ui
import (
"encoding/json"
"net/http"
"strconv"
"strings"
"github.com/hireme/hireme/internal/config"
"github.com/hireme/hireme/internal/db"
@@ -33,12 +33,15 @@ func (s *Server) RegisterRoutes(mux *http.ServeMux) {
mux.HandleFunc("GET /jobs", s.handleGetJobs)
mux.HandleFunc("GET /jobs/{id}", s.handleGetJobDetail)
mux.HandleFunc("POST /jobs/{id}/analyze", s.handleAnalyzeJob)
mux.HandleFunc("POST /jobs/{id}/opt-out", s.handleOptOut)
mux.HandleFunc("GET /applications", s.handleGetApplications)
mux.HandleFunc("POST /applications", s.handleCreateApplication)
mux.HandleFunc("PATCH /applications/{id}/status", s.handleUpdateAppStatus)
mux.HandleFunc("POST /api/tasks/discover", s.handleDiscoverTasks)
mux.HandleFunc("POST /api/tasks/clean", s.handleCleanTasks)
mux.HandleFunc("POST /api/tasks/reprocess", s.handleReprocessJobs)
mux.HandleFunc("POST /api/tasks/toggle", s.handleToggleTasks)
mux.HandleFunc("GET /api/status", s.handleGetStatus)
}
func (s *Server) handleIndex(w http.ResponseWriter, r *http.Request) {
@@ -189,6 +192,39 @@ func (s *Server) handleAnalyzeJob(w http.ResponseWriter, r *http.Request) {
_, _ = w.Write([]byte(`<span class="text-xs px-2 py-1 bg-yellow-100 text-yellow-800 rounded">Analysis Queued</span>`))
}
func (s *Server) handleOptOut(w http.ResponseWriter, r *http.Request) {
idStr := r.PathValue("id")
id, err := strconv.ParseInt(idStr, 10, 64)
if err != nil {
http.Error(w, "invalid job id", http.StatusBadRequest)
return
}
_, err = s.db.CreateOrUpdateApplication(id, "OPTED_OUT", "")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
jobs, err := s.db.GetJobsWithAnalysis()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
_ = RenderJobList(w, jobs)
}
func (s *Server) handleGetApplications(w http.ResponseWriter, r *http.Request) {
apps, err := s.db.GetApplications()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
_ = RenderApplyTable(w, apps)
}
func (s *Server) handleCreateApplication(w http.ResponseWriter, r *http.Request) {
if err := r.ParseForm(); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
@@ -213,13 +249,7 @@ func (s *Server) handleCreateApplication(w http.ResponseWriter, r *http.Request)
return
}
if strings.Contains(r.Header.Get("HX-Target"), "kanban") {
apps, _ := s.db.GetApplications()
w.Header().Set("Content-Type", "text/html; charset=utf-8")
_ = RenderKanbanBoard(w, apps)
return
}
// Always return the updated job list so the action column refreshes
s.handleGetJobs(w, r)
}
@@ -270,7 +300,7 @@ func (s *Server) handleUpdateAppStatus(w http.ResponseWriter, r *http.Request) {
updatedApps, _ := s.db.GetApplications()
w.Header().Set("Content-Type", "text/html; charset=utf-8")
_ = RenderKanbanBoard(w, updatedApps)
_ = RenderApplyTable(w, updatedApps)
}
func (s *Server) handleDiscoverTasks(w http.ResponseWriter, r *http.Request) {
@@ -295,7 +325,7 @@ func (s *Server) handleDiscoverTasks(w http.ResponseWriter, r *http.Request) {
count++
}
}
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"status": "success", "message": "Queued ` + strconv.Itoa(count) + ` discovery tasks"}`))
}
@@ -332,3 +362,26 @@ func (s *Server) handleToggleTasks(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"status": "` + status + `"}`))
}
// handleGetStatus returns a JSON snapshot of worker pool state and task queue counts.
// Used by the bottom bar to auto-refresh every few seconds without a full page reload.
func (s *Server) handleGetStatus(w http.ResponseWriter, r *http.Request) {
workerStatus := "paused"
if s.pool.IsRunning() {
workerStatus = "running"
}
stats, err := s.db.GetQueueStats()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
resp := map[string]interface{}{
"worker_status": workerStatus,
"queue": stats,
}
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(resp)
}