package controllers import ( "log" "strconv" "git.ourworld.tf/herocode/heroagent/pkg/herojobs" "git.ourworld.tf/herocode/heroagent/pkg/servers/ui/models" "github.com/gofiber/fiber/v2" ) // JobController handles requests related to job management type JobController struct { jobManager models.JobManager } // NewJobController creates a new instance of JobController func NewJobController(jobManager models.JobManager) *JobController { return &JobController{ jobManager: jobManager, } } // ShowJobsPage renders the jobs management page func (jc *JobController) ShowJobsPage(c *fiber.Ctx) error { // Get filter parameters circleID := c.Query("circle", "") topic := c.Query("topic", "") status := c.Query("status", "") var jobs []*models.JobInfo var err error // Apply filters if circleID != "" { jobs, err = jc.jobManager.GetJobsByCircle(circleID) } else if topic != "" { jobs, err = jc.jobManager.GetJobsByTopic(topic) } else if status != "" { // Convert status string to JobStatus var jobStatus herojobs.JobStatus switch status { case "new": jobStatus = herojobs.JobStatusNew case "active": jobStatus = herojobs.JobStatusActive case "error": jobStatus = herojobs.JobStatusError case "done": jobStatus = herojobs.JobStatusDone default: // Invalid status, get all jobs jobs, err = jc.jobManager.GetAllJobs() } if jobStatus != "" { jobs, err = jc.jobManager.GetJobsByStatus(jobStatus) } } else { // No filters, get all jobs jobs, err = jc.jobManager.GetAllJobs() } if err != nil { log.Printf("Error fetching jobs: %v", err) return c.Status(fiber.StatusInternalServerError).Render("pages/error", fiber.Map{ "Title": "Error", "Message": "Failed to fetch jobs: " + err.Error(), }) } // Group jobs by circle and topic for tree view jobTree := buildJobTree(jobs) // Get unique circles and topics for filter dropdowns circles := getUniqueCircles(jobs) topics := getUniqueTopics(jobs) // Create circle options with selected state circleOptions := make([]map[string]interface{}, 0, len(circles)) for _, circle := range circles { circleOptions = append(circleOptions, map[string]interface{}{ "Value": circle, "Text": circle, "Selected": circle == circleID, }) } // Create topic options with selected state topicOptions := make([]map[string]interface{}, 0, len(topics)) for _, topicName := range topics { topicOptions = append(topicOptions, map[string]interface{}{ "Value": topicName, "Text": topicName, "Selected": topicName == topic, }) } // Create status options with selected state statusOptions := []map[string]interface{}{ {"Value": "new", "Text": "New", "Selected": status == "new"}, {"Value": "active", "Text": "Active", "Selected": status == "active"}, {"Value": "done", "Text": "Done", "Selected": status == "done"}, {"Value": "error", "Text": "Error", "Selected": status == "error"}, } // Convert map options to OptionData structs circleOptionData := make([]OptionData, 0, len(circleOptions)) for _, option := range circleOptions { circleOptionData = append(circleOptionData, OptionData{ Value: option["Value"].(string), Text: option["Text"].(string), Selected: option["Selected"].(bool), }) } topicOptionData := make([]OptionData, 0, len(topicOptions)) for _, option := range topicOptions { topicOptionData = append(topicOptionData, OptionData{ Value: option["Value"].(string), Text: option["Text"].(string), Selected: option["Selected"].(bool), }) } statusOptionData := make([]OptionData, 0, len(statusOptions)) for _, option := range statusOptions { statusOptionData = append(statusOptionData, OptionData{ Value: option["Value"].(string), Text: option["Text"].(string), Selected: option["Selected"].(bool), }) } // Create JobPageData struct for the template pageData := JobPageData{ Title: "Job Management", Jobs: jobs, JobTree: jobTree, CircleOptions: circleOptionData, TopicOptions: topicOptionData, StatusOptions: statusOptionData, FilterCircle: circleID, FilterTopic: topic, FilterStatus: status, TotalJobs: len(jobs), ActiveJobs: countJobsByStatus(jobs, herojobs.JobStatusActive), CompletedJobs: countJobsByStatus(jobs, herojobs.JobStatusDone), ErrorJobs: countJobsByStatus(jobs, herojobs.JobStatusError), NewJobs: countJobsByStatus(jobs, herojobs.JobStatusNew), } // Render the template with the structured data return c.Render("pages/jobs", pageData) } // ShowJobDetails renders the job details page func (jc *JobController) ShowJobDetails(c *fiber.Ctx) error { // Get job ID from URL parameter jobIDStr := c.Params("id") jobID, err := strconv.ParseUint(jobIDStr, 10, 32) if err != nil { return c.Status(fiber.StatusBadRequest).Render("pages/error", fiber.Map{ "Title": "Error", "Message": "Invalid job ID: " + jobIDStr, }) } // Get job details job, err := jc.jobManager.GetJob(uint32(jobID)) if err != nil { return c.Status(fiber.StatusNotFound).Render("pages/error", fiber.Map{ "Title": "Error", "Message": "Job not found: " + jobIDStr, }) } return c.Render("pages/job_details", fiber.Map{ "Title": "Job Details", "Job": job, }) } // CircleNode represents a circle in the job tree type CircleNode struct { CircleID string `json:"id"` Name string `json:"name"` Topics map[string]*TopicNode `json:"topics"` } // TopicNode represents a topic in the job tree type TopicNode struct { Topic string `json:"topic"` Name string `json:"name"` Jobs []*models.JobInfo `json:"jobs"` } // buildJobTree groups jobs by circle and topic for the tree view func buildJobTree(jobs []*models.JobInfo) map[string]*CircleNode { tree := make(map[string]*CircleNode) for _, job := range jobs { // Get or create circle node circle, exists := tree[job.CircleID] if !exists { circle = &CircleNode{ CircleID: job.CircleID, Name: job.CircleID, // Use CircleID as name for now Topics: make(map[string]*TopicNode), } tree[job.CircleID] = circle } // Get or create topic node topic, exists := circle.Topics[job.Topic] if !exists { topic = &TopicNode{ Topic: job.Topic, Name: job.Topic, // Use Topic as name for now Jobs: make([]*models.JobInfo, 0), } circle.Topics[job.Topic] = topic } // Add job to topic topic.Jobs = append(topic.Jobs, job) } return tree } // getUniqueCircles returns a list of unique circle IDs from jobs func getUniqueCircles(jobs []*models.JobInfo) []string { circleMap := make(map[string]bool) for _, job := range jobs { circleMap[job.CircleID] = true } circles := make([]string, 0, len(circleMap)) for circle := range circleMap { circles = append(circles, circle) } return circles } // getUniqueTopics returns a list of unique topics from jobs func getUniqueTopics(jobs []*models.JobInfo) []string { topicMap := make(map[string]bool) for _, job := range jobs { topicMap[job.Topic] = true } topics := make([]string, 0, len(topicMap)) for topic := range topicMap { topics = append(topics, topic) } return topics } // countJobsByStatus counts jobs with a specific status func countJobsByStatus(jobs []*models.JobInfo, status herojobs.JobStatus) int { count := 0 for _, job := range jobs { if job.Status == status { count++ } } return count }