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
120 lines
3.4 KiB
Go
120 lines
3.4 KiB
Go
package cli
|
|
|
|
import (
|
|
"fmt"
|
|
"strconv"
|
|
|
|
"github.com/cnachtigall/heatwave-autopilot/internal/store"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
var (
|
|
roomSqm float64
|
|
roomCeiling float64
|
|
roomFloor int
|
|
roomOrient string
|
|
roomShading string
|
|
roomShadeFact float64
|
|
roomVent string
|
|
roomVentACH float64
|
|
roomWinFrac float64
|
|
roomSHGC float64
|
|
roomInsulation string
|
|
)
|
|
|
|
func init() {
|
|
roomCmd := &cobra.Command{
|
|
Use: "room",
|
|
Short: "Manage rooms",
|
|
}
|
|
|
|
addCmd := &cobra.Command{
|
|
Use: "add <name>",
|
|
Short: "Add a room",
|
|
Args: cobra.ExactArgs(1),
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
p, err := getActiveProfile()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
params := store.RoomParams{
|
|
CeilingHeightM: roomCeiling,
|
|
VentilationACH: roomVentACH,
|
|
WindowFraction: roomWinFrac,
|
|
SHGC: roomSHGC,
|
|
}
|
|
r, err := db.CreateRoom(p.ID, args[0], roomSqm, roomFloor, roomOrient, roomShading, roomShadeFact, roomVent, roomInsulation, params)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fmt.Printf("Room added: %s (ID: %d, %.0f m², %s-facing)\n", r.Name, r.ID, r.AreaSqm, r.Orientation)
|
|
return nil
|
|
},
|
|
}
|
|
addCmd.Flags().Float64Var(&roomSqm, "sqm", 0, "area in square meters")
|
|
addCmd.Flags().Float64Var(&roomCeiling, "ceiling", 2.5, "ceiling height in meters")
|
|
addCmd.Flags().IntVar(&roomFloor, "floor", 0, "floor number")
|
|
addCmd.Flags().StringVar(&roomOrient, "orientation", "N", "orientation (N/S/E/W/NE/NW/SE/SW)")
|
|
addCmd.Flags().StringVar(&roomShading, "shading", "none", "shading type")
|
|
addCmd.Flags().Float64Var(&roomShadeFact, "shading-factor", 1.0, "shading factor (0.0-1.0)")
|
|
addCmd.Flags().StringVar(&roomVent, "ventilation", "natural", "ventilation type")
|
|
addCmd.Flags().Float64Var(&roomVentACH, "ach", 0.5, "air changes per hour")
|
|
addCmd.Flags().Float64Var(&roomWinFrac, "window-fraction", 0.15, "window area as fraction of floor area")
|
|
addCmd.Flags().Float64Var(&roomSHGC, "shgc", 0.6, "solar heat gain coefficient of glazing")
|
|
addCmd.Flags().StringVar(&roomInsulation, "insulation", "average", "insulation level")
|
|
addCmd.MarkFlagRequired("sqm")
|
|
|
|
listCmd := &cobra.Command{
|
|
Use: "list",
|
|
Short: "List rooms",
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
p, err := getActiveProfile()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
rooms, err := db.ListRooms(p.ID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(rooms) == 0 {
|
|
fmt.Println("No rooms found")
|
|
return nil
|
|
}
|
|
for _, r := range rooms {
|
|
fmt.Printf(" [%d] %s — %.0f m², floor %d, %s-facing, shading: %s (%.1f)\n",
|
|
r.ID, r.Name, r.AreaSqm, r.Floor, r.Orientation, r.ShadingType, r.ShadingFactor)
|
|
}
|
|
return nil
|
|
},
|
|
}
|
|
|
|
editCmd := &cobra.Command{
|
|
Use: "edit <id> <field> <value>",
|
|
Short: "Edit a room 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 room ID: %s", args[0])
|
|
}
|
|
return db.UpdateRoom(id, args[1], args[2])
|
|
},
|
|
}
|
|
|
|
removeCmd := &cobra.Command{
|
|
Use: "remove <id>",
|
|
Short: "Remove a room",
|
|
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 room ID: %s", args[0])
|
|
}
|
|
return db.DeleteRoom(id)
|
|
},
|
|
}
|
|
|
|
roomCmd.AddCommand(addCmd, listCmd, editCmd, removeCmd)
|
|
rootCmd.AddCommand(roomCmd)
|
|
}
|