Complete convoy dashboard feature with real-time status tracking: - Activity package: LastActivity calculation with color thresholds (green <2min, yellow 2-5min, red >5min) - Web package: Template, handler, fetcher for convoy list - CLI: `gt dashboard [--port=8080] [--open]` command - Browser E2E tests with rod (headless Chrome) Features: - Real-time convoy status with htmx auto-refresh (30s) - Progress tracking for each convoy - Last activity indicator with color coding - Empty state handling Supersedes: PRs #55, #57, #58, #65, #66 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
101 lines
2.5 KiB
Go
101 lines
2.5 KiB
Go
package cmd
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"os/exec"
|
|
"runtime"
|
|
"time"
|
|
|
|
"github.com/spf13/cobra"
|
|
"github.com/steveyegge/gastown/internal/web"
|
|
"github.com/steveyegge/gastown/internal/workspace"
|
|
)
|
|
|
|
var (
|
|
dashboardPort int
|
|
dashboardOpen bool
|
|
)
|
|
|
|
var dashboardCmd = &cobra.Command{
|
|
Use: "dashboard",
|
|
GroupID: GroupDiag,
|
|
Short: "Start the convoy tracking web dashboard",
|
|
Long: `Start a web server that displays the convoy tracking dashboard.
|
|
|
|
The dashboard shows real-time convoy status with:
|
|
- Convoy list with status indicators
|
|
- Progress tracking for each convoy
|
|
- Last activity indicator (green/yellow/red)
|
|
- Auto-refresh every 30 seconds via htmx
|
|
|
|
Example:
|
|
gt dashboard # Start on default port 8080
|
|
gt dashboard --port 3000 # Start on port 3000
|
|
gt dashboard --open # Start and open browser`,
|
|
RunE: runDashboard,
|
|
}
|
|
|
|
func init() {
|
|
dashboardCmd.Flags().IntVar(&dashboardPort, "port", 8080, "HTTP port to listen on")
|
|
dashboardCmd.Flags().BoolVar(&dashboardOpen, "open", false, "Open browser automatically")
|
|
rootCmd.AddCommand(dashboardCmd)
|
|
}
|
|
|
|
func runDashboard(cmd *cobra.Command, args []string) error {
|
|
// Verify we're in a workspace
|
|
if _, err := workspace.FindFromCwdOrError(); err != nil {
|
|
return fmt.Errorf("not in a Gas Town workspace: %w", err)
|
|
}
|
|
|
|
// Create the live convoy fetcher
|
|
fetcher, err := web.NewLiveConvoyFetcher()
|
|
if err != nil {
|
|
return fmt.Errorf("creating convoy fetcher: %w", err)
|
|
}
|
|
|
|
// Create the handler
|
|
handler, err := web.NewConvoyHandler(fetcher)
|
|
if err != nil {
|
|
return fmt.Errorf("creating convoy handler: %w", err)
|
|
}
|
|
|
|
// Build the URL
|
|
url := fmt.Sprintf("http://localhost:%d", dashboardPort)
|
|
|
|
// Open browser if requested
|
|
if dashboardOpen {
|
|
go openBrowser(url)
|
|
}
|
|
|
|
// Start the server with timeouts
|
|
fmt.Printf("🚚 Gas Town Dashboard starting at %s\n", url)
|
|
fmt.Printf(" Press Ctrl+C to stop\n")
|
|
|
|
server := &http.Server{
|
|
Addr: fmt.Sprintf(":%d", dashboardPort),
|
|
Handler: handler,
|
|
ReadHeaderTimeout: 10 * time.Second,
|
|
ReadTimeout: 30 * time.Second,
|
|
WriteTimeout: 60 * time.Second,
|
|
IdleTimeout: 120 * time.Second,
|
|
}
|
|
return server.ListenAndServe()
|
|
}
|
|
|
|
// openBrowser opens the specified URL in the default browser.
|
|
func openBrowser(url string) {
|
|
var cmd *exec.Cmd
|
|
switch runtime.GOOS {
|
|
case "darwin":
|
|
cmd = exec.Command("open", url)
|
|
case "linux":
|
|
cmd = exec.Command("xdg-open", url)
|
|
case "windows":
|
|
cmd = exec.Command("cmd", "/c", "start", url)
|
|
default:
|
|
return
|
|
}
|
|
_ = cmd.Start()
|
|
}
|