heroagent/pkg/servers/ui/controllers/job_controller.go
2025-05-24 09:24:19 +04:00

272 lines
7.3 KiB
Go

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
}