Files
gastown/internal/web/templates.go
Mike Lady fe72bd4ddc feat(dashboard): Add dynamic work status column for convoys
The status column now shows computed work status based on progress and activity:
- "complete" (green) - all tracked items are done
- "active" (green) - recent polecat activity (within 1 min)
- "stale" (yellow) - older activity (1-5 min)
- "stuck" (red) - stale activity (5+ min)
- "waiting" (gray) - no assignee/activity

Previously the status column always showed "open" since we only fetch
open convoys, making it static and uninformative.

Changes:
- templates.go: Add WorkStatus field to ConvoyRow, add workStatusClass func
- fetcher.go: Add calculateWorkStatus() to compute status from progress/activity
- convoy.html: Add work status badge styling, use WorkStatus in table

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-03 18:03:51 -08:00

139 lines
3.3 KiB
Go

// Package web provides HTTP server and templates for the Gas Town dashboard.
package web
import (
"embed"
"html/template"
"io/fs"
"github.com/steveyegge/gastown/internal/activity"
)
//go:embed templates/*.html
var templateFS embed.FS
// ConvoyData represents data passed to the convoy template.
type ConvoyData struct {
Convoys []ConvoyRow
MergeQueue []MergeQueueRow
Polecats []PolecatRow
}
// PolecatRow represents a polecat worker in the dashboard.
type PolecatRow struct {
Name string // e.g., "dag", "nux"
Rig string // e.g., "roxas", "gastown"
SessionID string // e.g., "gt-roxas-dag"
LastActivity activity.Info // Colored activity display
StatusHint string // Last line from pane (optional)
}
// MergeQueueRow represents a PR in the merge queue.
type MergeQueueRow struct {
Number int
Repo string // Short repo name (e.g., "roxas", "gastown")
Title string
URL string
CIStatus string // "pass", "fail", "pending"
Mergeable string // "ready", "conflict", "pending"
ColorClass string // "mq-green", "mq-yellow", "mq-red"
}
// ConvoyRow represents a single convoy in the dashboard.
type ConvoyRow struct {
ID string
Title string
Status string // "open" or "closed" (raw beads status)
WorkStatus string // Computed: "complete", "active", "stale", "stuck", "waiting"
Progress string // e.g., "2/5"
Completed int
Total int
LastActivity activity.Info
TrackedIssues []TrackedIssue
}
// TrackedIssue represents an issue tracked by a convoy.
type TrackedIssue struct {
ID string
Title string
Status string
Assignee string
}
// LoadTemplates loads and parses all HTML templates.
func LoadTemplates() (*template.Template, error) {
// Define template functions
funcMap := template.FuncMap{
"activityClass": activityClass,
"statusClass": statusClass,
"workStatusClass": workStatusClass,
"progressPercent": progressPercent,
}
// Get the templates subdirectory
subFS, err := fs.Sub(templateFS, "templates")
if err != nil {
return nil, err
}
// Parse all templates
tmpl, err := template.New("").Funcs(funcMap).ParseFS(subFS, "*.html")
if err != nil {
return nil, err
}
return tmpl, nil
}
// activityClass returns the CSS class for an activity color.
func activityClass(info activity.Info) string {
switch info.ColorClass {
case activity.ColorGreen:
return "activity-green"
case activity.ColorYellow:
return "activity-yellow"
case activity.ColorRed:
return "activity-red"
default:
return "activity-unknown"
}
}
// statusClass returns the CSS class for a convoy status.
func statusClass(status string) string {
switch status {
case "open":
return "status-open"
case "closed":
return "status-closed"
default:
return "status-unknown"
}
}
// workStatusClass returns the CSS class for a computed work status.
func workStatusClass(workStatus string) string {
switch workStatus {
case "complete":
return "work-complete"
case "active":
return "work-active"
case "stale":
return "work-stale"
case "stuck":
return "work-stuck"
case "waiting":
return "work-waiting"
default:
return "work-unknown"
}
}
// progressPercent calculates percentage as an integer for progress bars.
func progressPercent(completed, total int) int {
if total == 0 {
return 0
}
return (completed * 100) / total
}