package webui import ( "encoding/json" "fmt" "html" "net/http" "os" "strings" "time" "bee/audit/internal/platform" ) func (h *handler) handleTaskPage(w http.ResponseWriter, r *http.Request) { id := r.PathValue("id") task, ok := globalQueue.findByID(id) if !ok { http.NotFound(w, r) return } snapshot := *task body := renderTaskDetailPage(h.opts, snapshot) w.Header().Set("Cache-Control", "no-store") w.Header().Set("Content-Type", "text/html; charset=utf-8") _, _ = w.Write([]byte(body)) } func (h *handler) handleAPITaskChartsIndex(w http.ResponseWriter, r *http.Request) { task, samples, _, _, ok := h.taskSamplesForRequest(r) if !ok { http.NotFound(w, r) return } type taskChartIndexEntry struct { Title string `json:"title"` File string `json:"file"` } entries := make([]taskChartIndexEntry, 0) for _, spec := range taskChartSpecsForSamples(samples) { title, _, ok := renderTaskChartSVG(spec.Path, samples, taskTimelineForTask(task)) if !ok { continue } entries = append(entries, taskChartIndexEntry{Title: title, File: spec.File}) } w.Header().Set("Cache-Control", "no-store") w.Header().Set("Content-Type", "application/json; charset=utf-8") _ = json.NewEncoder(w).Encode(entries) } func (h *handler) handleAPITaskChartSVG(w http.ResponseWriter, r *http.Request) { task, samples, _, _, ok := h.taskSamplesForRequest(r) if !ok { http.NotFound(w, r) return } file := strings.TrimPrefix(r.URL.Path, "/api/tasks/"+task.ID+"/chart/") path, ok := taskChartPathFromFile(file) if !ok { http.NotFound(w, r) return } title, buf, hasData := renderTaskChartSVG(path, samples, taskTimelineForTask(task)) if !hasData || len(buf) == 0 || strings.TrimSpace(title) == "" { http.Error(w, "metrics history unavailable", http.StatusServiceUnavailable) return } w.Header().Set("Content-Type", "image/svg+xml") w.Header().Set("Cache-Control", "no-store") _, _ = w.Write(buf) } func renderTaskDetailPage(opts HandlerOptions, task Task) string { title := task.Name if strings.TrimSpace(title) == "" { title = task.ID } var body strings.Builder body.WriteString(`
`) body.WriteString(`Back to Tasks`) if task.Status == TaskRunning || task.Status == TaskPending { body.WriteString(``) } body.WriteString(`Artifacts are saved in the task folder under ./tasks.`) body.WriteString(`
`) if report := loadTaskReportFragment(task); report != "" { body.WriteString(report) } else { body.WriteString(`
Task Summary
`) body.WriteString(`
` + html.EscapeString(title) + `
`) body.WriteString(`
` + renderTaskStatusBadge(task.Status) + `
`) if strings.TrimSpace(task.ErrMsg) != "" { body.WriteString(`
` + html.EscapeString(task.ErrMsg) + `
`) } body.WriteString(`
`) } if task.Status == TaskRunning || task.Status == TaskPending { body.WriteString(`
Live Charts
`) body.WriteString(`
Loading charts...
`) body.WriteString(`
`) body.WriteString(`
Live Logs
`) body.WriteString(`
Connecting...
`) body.WriteString(`
`) body.WriteString(``) } return layoutHead(opts.Title+" — "+title) + layoutNav("tasks", opts.BuildLabel) + `

` + html.EscapeString(title) + `

` + body.String() + `
` } func loadTaskReportFragment(task Task) string { if strings.TrimSpace(task.ReportHTMLPath) == "" { return "" } data, err := os.ReadFile(task.ReportHTMLPath) if err != nil || len(data) == 0 { return "" } return string(data) } func taskArtifactDownloadLink(task Task, absPath string) string { if strings.TrimSpace(absPath) == "" { return "" } return fmt.Sprintf(`/export/file?path=%s`, absPath) } func (h *handler) taskSamplesForRequest(r *http.Request) (Task, []platform.LiveMetricSample, time.Time, time.Time, bool) { id := r.PathValue("id") taskPtr, ok := globalQueue.findByID(id) if !ok { return Task{}, nil, time.Time{}, time.Time{}, false } task := *taskPtr start, end := taskTimeWindow(&task) samples, err := loadTaskMetricSamples(start, end) if err != nil { return task, nil, start, end, true } return task, samples, start, end, true } func taskTimelineForTask(task Task) []chartTimelineSegment { start, end := taskTimeWindow(&task) return []chartTimelineSegment{{Start: start, End: end, Active: true}} } func taskChartPathFromFile(file string) (string, bool) { file = strings.TrimSpace(file) for _, spec := range taskDashboardChartSpecs { if spec.File == file { return spec.Path, true } } if strings.HasPrefix(file, "gpu-") && strings.HasSuffix(file, "-overview.svg") { id := strings.TrimSuffix(strings.TrimPrefix(file, "gpu-"), "-overview.svg") return "gpu/" + id + "-overview", true } return "", false }