package tui import ( "fmt" "go-upkeep/internal/models" "strconv" "time" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/huh" "github.com/charmbracelet/lipgloss" ) var maintStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#bb9af7")) type maintFormData struct { Title string Description string Type string MonitorID string Duration string CustomHours string } func fmtMaintStatus(mw models.MaintenanceWindow) string { now := time.Now() if mw.StartTime.After(now) { return warnStyle.Render("SCHEDULED") } if !mw.EndTime.IsZero() && mw.EndTime.Before(now) { return subtleStyle.Render("ENDED") } return specialStyle.Render("ACTIVE") } func fmtMaintType(t string) string { if t == "incident" { return dangerStyle.Render("incident") } return maintStyle.Render("maintenance") } func fmtMaintMonitor(monitorID int, sites []models.Site) string { if monitorID == 0 { return "All" } for _, s := range sites { if s.ID == monitorID { return limitStr(s.Name, 18) } } return fmt.Sprintf("#%d", monitorID) } func fmtMaintTime(t time.Time) string { if t.IsZero() { return subtleStyle.Render("—") } now := time.Now() if t.Year() == now.Year() && t.YearDay() == now.YearDay() { return t.Format("15:04") } return t.Format("15:04 Jan 02") } func (m Model) isMonitorInMaintenance(monitorID int) bool { for _, mw := range m.maintenanceWindows { if mw.Type != "maintenance" { continue } now := time.Now() if mw.StartTime.After(now) { continue } if !mw.EndTime.IsZero() && mw.EndTime.Before(now) { continue } if mw.MonitorID == 0 || mw.MonitorID == monitorID { return true } for _, s := range m.sites { if s.ID == monitorID && s.ParentID > 0 && mw.MonitorID == s.ParentID { return true } } } return false } func (m Model) viewMaintTab() string { if len(m.maintenanceWindows) == 0 { return "\n No maintenance windows or incidents. Press [n] to create one." } return m.renderTable( []string{"#", "TITLE", "TYPE", "MONITORS", "STATUS", "STARTED", "ENDS"}, len(m.maintenanceWindows), func(start, end int) [][]string { var rows [][]string allSites := m.engine.GetAllSites() for i := start; i < end; i++ { mw := m.maintenanceWindows[i] rows = append(rows, []string{ strconv.Itoa(i + 1), m.zones.Mark(fmt.Sprintf("maint-%d", i), limitStr(mw.Title, 24)), fmtMaintType(mw.Type), fmtMaintMonitor(mw.MonitorID, allSites), fmtMaintStatus(mw), fmtMaintTime(mw.StartTime), fmtMaintTime(mw.EndTime), }) } return rows }, []int{6, 0, 14, 20, 12, 16, 16}, nil, ) } func (m *Model) initMaintHuhForm() tea.Cmd { m.maintFormData = &maintFormData{ Type: "maintenance", MonitorID: "0", Duration: "1h", CustomHours: "12", } monitorOpts := []huh.Option[string]{huh.NewOption("All Monitors", "0")} allSites := m.engine.GetAllSites() for _, s := range allSites { label := s.Name if s.Type == "group" { label = s.Name + " (group)" } monitorOpts = append(monitorOpts, huh.NewOption(label, strconv.Itoa(s.ID))) } m.huhForm = huh.NewForm( huh.NewGroup( huh.NewInput().Title("Title"). Placeholder("DB Migration"). Value(&m.maintFormData.Title). Validate(func(s string) error { if s == "" { return fmt.Errorf("title is required") } return nil }), huh.NewSelect[string]().Title("Type"). Options( huh.NewOption("Maintenance (suppress alerts)", "maintenance"), huh.NewOption("Incident (informational)", "incident"), ).Value(&m.maintFormData.Type), huh.NewSelect[string]().Title("Affected Monitors"). Options(monitorOpts...). Value(&m.maintFormData.MonitorID), huh.NewInput().Title("Description"). Placeholder("Optional notes"). Value(&m.maintFormData.Description), ).Title("Maintenance Window"), huh.NewGroup( huh.NewSelect[string]().Title("Duration"). Options( huh.NewOption("1 hour", "1h"), huh.NewOption("2 hours", "2h"), huh.NewOption("4 hours", "4h"), huh.NewOption("8 hours", "8h"), huh.NewOption("Indefinite (end manually)", "indefinite"), huh.NewOption("Custom", "custom"), ).Value(&m.maintFormData.Duration), huh.NewInput().Title("Custom Duration (hours)"). Placeholder("12"). Value(&m.maintFormData.CustomHours). Validate(func(s string) error { if m.maintFormData.Duration != "custom" { return nil } v, err := strconv.Atoi(s) if err != nil { return fmt.Errorf("must be a number") } if v < 1 { return fmt.Errorf("must be at least 1 hour") } return nil }), ).Title("Duration").WithHideFunc(func() bool { return m.maintFormData.Type == "incident" }), ).WithTheme(huh.ThemeDracula()) return m.huhForm.Init() } func (m *Model) submitMaintForm() { d := m.maintFormData monitorID, _ := strconv.Atoi(d.MonitorID) mw := models.MaintenanceWindow{ MonitorID: monitorID, Title: d.Title, Description: d.Description, Type: d.Type, StartTime: time.Now(), } if d.Type == "maintenance" { switch d.Duration { case "1h": mw.EndTime = mw.StartTime.Add(1 * time.Hour) case "2h": mw.EndTime = mw.StartTime.Add(2 * time.Hour) case "4h": mw.EndTime = mw.StartTime.Add(4 * time.Hour) case "8h": mw.EndTime = mw.StartTime.Add(8 * time.Hour) case "custom": hours, _ := strconv.Atoi(d.CustomHours) if hours < 1 { hours = 1 } mw.EndTime = mw.StartTime.Add(time.Duration(hours) * time.Hour) } } if err := m.store.AddMaintenanceWindow(mw); err != nil { m.engine.AddLog("Add maintenance window failed: " + err.Error()) } m.state = stateDashboard }