diff --git a/internal/tui/menu.go b/internal/tui/menu.go index c82a4b7..5e42728 100644 --- a/internal/tui/menu.go +++ b/internal/tui/menu.go @@ -48,16 +48,24 @@ type dbTypeOption struct { value string } +type workloadOption struct { + label string + value string + desc string +} + // MenuModel represents the simple menu state type MenuModel struct { - choices []string - cursor int - config *config.Config - logger logger.Logger - quitting bool - message string - dbTypes []dbTypeOption - dbTypeCursor int + choices []string + cursor int + config *config.Config + logger logger.Logger + quitting bool + message string + dbTypes []dbTypeOption + dbTypeCursor int + workloads []workloadOption + workloadCursor int // Background operations ctx context.Context @@ -73,6 +81,12 @@ func NewMenuModel(cfg *config.Config, log logger.Logger) MenuModel { {label: "MariaDB", value: "mariadb"}, } + workloads := []workloadOption{ + {label: "Balanced", value: "balanced", desc: "General purpose"}, + {label: "CPU-Intensive", value: "cpu-intensive", desc: "More parallelism"}, + {label: "I/O-Intensive", value: "io-intensive", desc: "Less parallelism"}, + } + dbCursor := 0 if cfg.DatabaseType == "mysql" { dbCursor = 1 @@ -80,6 +94,13 @@ func NewMenuModel(cfg *config.Config, log logger.Logger) MenuModel { dbCursor = 2 } + workloadCursor := 0 + if cfg.CPUWorkloadType == "cpu-intensive" { + workloadCursor = 1 + } else if cfg.CPUWorkloadType == "io-intensive" { + workloadCursor = 2 + } + model := MenuModel{ choices: []string{ "Single Database Backup", @@ -97,12 +118,14 @@ func NewMenuModel(cfg *config.Config, log logger.Logger) MenuModel { "Clear Operation History", "Quit", }, - config: cfg, - logger: log, - ctx: ctx, - cancel: cancel, - dbTypes: dbTypes, - dbTypeCursor: dbCursor, + config: cfg, + logger: log, + ctx: ctx, + cancel: cancel, + dbTypes: dbTypes, + dbTypeCursor: dbCursor, + workloads: workloads, + workloadCursor: workloadCursor, } return model @@ -143,6 +166,24 @@ func (m MenuModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.applyDatabaseSelection() } + case "shift+left", "H": + if m.workloadCursor > 0 { + m.workloadCursor-- + m.applyWorkloadSelection() + } + + case "shift+right", "L": + if m.workloadCursor < len(m.workloads)-1 { + m.workloadCursor++ + m.applyWorkloadSelection() + } + + case "w": + if len(m.workloads) > 0 { + m.workloadCursor = (m.workloadCursor + 1) % len(m.workloads) + m.applyWorkloadSelection() + } + case "up", "k": if m.cursor > 0 { m.cursor-- @@ -218,6 +259,21 @@ func (m MenuModel) View() string { selector := fmt.Sprintf("Target Engine: %s", strings.Join(options, menuStyle.Render(" | "))) s += dbSelectorLabelStyle.Render(selector) + "\n" hint := infoStyle.Render("Switch with ←/→ or t • Cluster backup requires PostgreSQL") + s += hint + "\n" + } + + if len(m.workloads) > 0 { + options := make([]string, len(m.workloads)) + for i, opt := range m.workloads { + if m.workloadCursor == i { + options[i] = menuSelectedStyle.Render(fmt.Sprintf("%s (%s)", opt.label, opt.desc)) + } else { + options[i] = menuStyle.Render(opt.label) + } + } + selector := fmt.Sprintf("CPU Workload: %s", strings.Join(options, menuStyle.Render(" | "))) + s += dbSelectorLabelStyle.Render(selector) + "\n" + hint := infoStyle.Render("Switch with Shift+←/→ or w • Affects parallelism and compression") s += hint + "\n\n" } @@ -346,6 +402,52 @@ func (m *MenuModel) applyDatabaseSelection() { } } +// applyWorkloadSelection updates the config with the selected workload type +func (m *MenuModel) applyWorkloadSelection() { + if m.config == nil { + return + } + if m.workloadCursor < 0 || m.workloadCursor >= len(m.workloads) { + return + } + + selection := m.workloads[m.workloadCursor] + m.config.CPUWorkloadType = selection.value + + // Recalculate optimal settings based on workload type + if m.config.CPUInfo != nil && m.config.AutoDetectCores { + // Recalculate Jobs and DumpJobs based on new workload type + switch selection.value { + case "cpu-intensive": + // More parallelism for CPU-bound tasks + m.config.Jobs = m.config.CPUInfo.PhysicalCores * 2 + m.config.DumpJobs = m.config.CPUInfo.PhysicalCores + case "io-intensive": + // Less parallelism to avoid I/O contention + m.config.Jobs = m.config.CPUInfo.PhysicalCores / 2 + if m.config.Jobs < 1 { + m.config.Jobs = 1 + } + m.config.DumpJobs = 2 + default: // balanced + m.config.Jobs = m.config.CPUInfo.PhysicalCores + m.config.DumpJobs = m.config.CPUInfo.PhysicalCores / 2 + if m.config.DumpJobs < 2 { + m.config.DumpJobs = 2 + } + } + } + + m.message = successStyle.Render(fmt.Sprintf("⚡ CPU workload set to %s (Jobs: %d, DumpJobs: %d)", + selection.label, m.config.Jobs, m.config.DumpJobs)) + if m.logger != nil { + m.logger.Info("updated CPU workload type", + "type", m.config.CPUWorkloadType, + "jobs", m.config.Jobs, + "dump_jobs", m.config.DumpJobs) + } +} + // RunInteractiveMenu starts the simple TUI func RunInteractiveMenu(cfg *config.Config, log logger.Logger) error { m := NewMenuModel(cfg, log)