Files
gastown/internal/web/templates.go
Mike Lady 3488933cc2 Add 'gt dashboard' CLI command (hq-s1bg) (#65)
* Add LastActivity calculation for convoy dashboard (hq-x2xy)

Adds internal/activity package with color-coded activity tracking:
- Green: <2 minutes (active)
- Yellow: 2-5 minutes (stale)
- Red: >5 minutes (stuck)

Features:
- Calculate() function returns Info with formatted age and color class
- Helper methods: IsActive(), IsStale(), IsStuck()
- Handles edge cases: zero time, future time (clock skew)

Tests: 8 test functions with 25 sub-tests covering all thresholds.

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Add convoy dashboard HTML template with Last Activity (hq-fq1g)

Adds internal/web package with convoy dashboard template:
- convoy.html with Last Activity column and color coding
- Green (<2min), Yellow (2-5min), Red (>5min) activity indicators
- htmx auto-refresh every 30 seconds
- Progress bars for convoy completion
- Status indicators for open/closed convoys
- Empty state when no convoys

Also includes internal/activity package (dependency from hq-x2xy):
- Calculate() returns Info with formatted age and color class
- Helper methods: IsActive(), IsStale(), IsStuck()

Tests: 6 template tests + 8 activity tests, all passing.

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Add convoy list handler with activity data (hq-3edt)

Adds HTTP handler that wires convoy dashboard template to real data:
- ConvoyHandler: HTTP handler for GET / rendering convoy dashboard
- LiveConvoyFetcher: Fetches convoys from beads with activity data
- ConvoyFetcher interface: Enables mocking for tests

Features:
- Fetches open convoys from town beads
- Calculates progress (completed/total) from tracked issues
- Gets Last Activity from worker agent beads
- Color codes activity: Green (<2min), Yellow (2-5min), Red (>5min)

Includes dependencies (not yet merged):
- internal/activity: Activity calculation (hq-x2xy)
- internal/web/templates: HTML template (hq-fq1g)

Tests: 5 handler tests + 6 template tests + 8 activity tests = 19 total

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Add 'gt dashboard' CLI command (hq-s1bg)

Add dashboard command to start the convoy tracking web server.

Usage: gt dashboard [--port=8080] [--open]

Features:
- --port: Configurable HTTP port (default 8080)
- --open: Auto-open browser on start
- Cross-platform browser launch (darwin/linux/windows)
- Graceful workspace detection

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-03 11:49:42 -08:00

97 lines
2.1 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
}
// ConvoyRow represents a single convoy in the dashboard.
type ConvoyRow struct {
ID string
Title string
Status string // "open" or "closed"
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,
"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"
}
}
// progressPercent calculates percentage as an integer for progress bars.
func progressPercent(completed, total int) int {
if total == 0 {
return 0
}
return (completed * 100) / total
}