272 lines
7.3 KiB
Go
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
|
|
}
|