...
This commit is contained in:
271
pkg/servers/ui/controllers/job_controller.go
Normal file
271
pkg/servers/ui/controllers/job_controller.go
Normal file
@@ -0,0 +1,271 @@
|
||||
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
|
||||
}
|
30
pkg/servers/ui/controllers/job_page_data.go
Normal file
30
pkg/servers/ui/controllers/job_page_data.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/servers/ui/models"
|
||||
)
|
||||
|
||||
// JobPageData represents the data needed for the jobs page
|
||||
type JobPageData struct {
|
||||
Title string
|
||||
Jobs []*models.JobInfo
|
||||
JobTree map[string]*CircleNode
|
||||
CircleOptions []OptionData
|
||||
TopicOptions []OptionData
|
||||
StatusOptions []OptionData
|
||||
FilterCircle string
|
||||
FilterTopic string
|
||||
FilterStatus string
|
||||
TotalJobs int
|
||||
ActiveJobs int
|
||||
CompletedJobs int
|
||||
ErrorJobs int
|
||||
NewJobs int
|
||||
}
|
||||
|
||||
// OptionData represents a select option
|
||||
type OptionData struct {
|
||||
Value string
|
||||
Text string
|
||||
Selected bool
|
||||
}
|
Reference in New Issue
Block a user