Add server-side rendered setup UI accessible via `heatwave web`. The dashboard is now re-rendered per request and includes a nav bar linking to the new /setup page. Setup provides full CRUD for profiles, rooms, devices, occupants, AC units (with room assignment), scenario toggles, and forecast fetching — all via POST/redirect/GET forms. - Add ShowNav field to DashboardData for conditional nav bar - Extract fetchForecastForProfile() for reuse by web handler - Create setup.html.tmpl with Tailwind-styled entity sections - Create web_handlers.go with 15 route handlers and flash cookies - Switch web.go from pre-rendered to per-request dashboard rendering - Graceful dashboard fallback when no forecast data exists
111 lines
2.8 KiB
Go
111 lines
2.8 KiB
Go
package cli
|
|
|
|
import (
|
|
"fmt"
|
|
"strconv"
|
|
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
var (
|
|
acType string
|
|
acBTU float64
|
|
acRooms []int64
|
|
acDehumidify bool
|
|
acEER float64
|
|
)
|
|
|
|
func init() {
|
|
acCmd := &cobra.Command{
|
|
Use: "ac",
|
|
Short: "Manage AC units",
|
|
}
|
|
|
|
addCmd := &cobra.Command{
|
|
Use: "add <name>",
|
|
Short: "Add an AC unit",
|
|
Args: cobra.ExactArgs(1),
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
p, err := getActiveProfile()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
ac, err := db.CreateACUnit(p.ID, args[0], acType, acBTU, acDehumidify, acEER)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, roomID := range acRooms {
|
|
if err := db.AssignACToRoom(ac.ID, roomID); err != nil {
|
|
return fmt.Errorf("assign room %d: %w", roomID, err)
|
|
}
|
|
}
|
|
fmt.Printf("AC unit added: %s (ID: %d, %.0f BTU/h, type: %s)\n", ac.Name, ac.ID, ac.CapacityBTU, ac.ACType)
|
|
return nil
|
|
},
|
|
}
|
|
addCmd.Flags().StringVar(&acType, "type", "portable", "AC type (portable, split, window, central)")
|
|
addCmd.Flags().Float64Var(&acBTU, "btu", 0, "cooling capacity in BTU/h")
|
|
addCmd.Flags().Int64SliceVar(&acRooms, "rooms", nil, "room IDs to assign")
|
|
addCmd.Flags().BoolVar(&acDehumidify, "dehumidify", false, "has dehumidify mode")
|
|
addCmd.Flags().Float64Var(&acEER, "eer", 10.0, "energy efficiency ratio")
|
|
addCmd.MarkFlagRequired("btu")
|
|
|
|
listCmd := &cobra.Command{
|
|
Use: "list",
|
|
Short: "List AC units",
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
p, err := getActiveProfile()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
units, err := db.ListACUnits(p.ID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(units) == 0 {
|
|
fmt.Println("No AC units found")
|
|
return nil
|
|
}
|
|
for _, u := range units {
|
|
rooms, _ := db.GetACRoomAssignments(u.ID)
|
|
dehumid := ""
|
|
if u.HasDehumidify {
|
|
dehumid = " +dehumidify"
|
|
}
|
|
fmt.Printf(" [%d] %s — %.0f BTU/h, type: %s, EER: %.1f%s, rooms: %v\n",
|
|
u.ID, u.Name, u.CapacityBTU, u.ACType, u.EfficiencyEER, dehumid, rooms)
|
|
}
|
|
return nil
|
|
},
|
|
}
|
|
|
|
editCmd := &cobra.Command{
|
|
Use: "edit <id> <field> <value>",
|
|
Short: "Edit an AC unit field",
|
|
Args: cobra.ExactArgs(3),
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
id, err := strconv.ParseInt(args[0], 10, 64)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid AC unit ID: %s", args[0])
|
|
}
|
|
return db.UpdateACUnit(id, args[1], args[2])
|
|
},
|
|
}
|
|
|
|
removeCmd := &cobra.Command{
|
|
Use: "remove <id>",
|
|
Short: "Remove an AC unit",
|
|
Args: cobra.ExactArgs(1),
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
id, err := strconv.ParseInt(args[0], 10, 64)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid AC unit ID: %s", args[0])
|
|
}
|
|
return db.DeleteACUnit(id)
|
|
},
|
|
}
|
|
|
|
acCmd.AddCommand(addCmd, listCmd, editCmd, removeCmd)
|
|
rootCmd.AddCommand(acCmd)
|
|
}
|