Files
dbbackup/internal/tui/dbselector.go
Alexander Renz 3e41d88445
All checks were successful
CI/CD / Test (push) Successful in 1m13s
CI/CD / Lint (push) Successful in 1m20s
CI/CD / Build & Release (push) Successful in 3m10s
v3.42.11: Replace all Unicode emojis with ASCII text
- Replace all emoji characters with ASCII equivalents throughout codebase
- Replace Unicode box-drawing characters (═║╔╗╚╝━─) with ASCII (+|-=)
- Replace checkmarks (✓✗) with [OK]/[FAIL] markers
- 59 files updated, 741 lines changed
- Improves terminal compatibility and reduces visual noise
2026-01-08 09:42:01 +01:00

214 lines
5.5 KiB
Go
Executable File

package tui
import (
"context"
"fmt"
"strings"
"time"
tea "github.com/charmbracelet/bubbletea"
"dbbackup/internal/config"
"dbbackup/internal/database"
"dbbackup/internal/logger"
)
// DatabaseSelectorModel for selecting a database
type DatabaseSelectorModel struct {
config *config.Config
logger logger.Logger
parent tea.Model
ctx context.Context
databases []string
cursor int
selected string
loading bool
err error
title string
message string
backupType string // "single" or "sample"
}
func NewDatabaseSelector(cfg *config.Config, log logger.Logger, parent tea.Model, ctx context.Context, title string, backupType string) DatabaseSelectorModel {
return DatabaseSelectorModel{
config: cfg,
logger: log,
parent: parent,
ctx: ctx,
databases: []string{"Loading databases..."},
title: title,
loading: true,
backupType: backupType,
}
}
func (m DatabaseSelectorModel) Init() tea.Cmd {
return fetchDatabases(m.config, m.logger)
}
type databaseListMsg struct {
databases []string
err error
}
func fetchDatabases(cfg *config.Config, log logger.Logger) tea.Cmd {
return func() tea.Msg {
// 60 seconds for database listing - busy servers may be slow
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()
dbClient, err := database.New(cfg, log)
if err != nil {
return databaseListMsg{databases: nil, err: fmt.Errorf("failed to create database client: %w", err)}
}
defer dbClient.Close()
if err := dbClient.Connect(ctx); err != nil {
return databaseListMsg{databases: nil, err: fmt.Errorf("connection failed: %w", err)}
}
databases, err := dbClient.ListDatabases(ctx)
if err != nil {
return databaseListMsg{databases: nil, err: fmt.Errorf("failed to list databases: %w", err)}
}
return databaseListMsg{databases: databases, err: nil}
}
}
func (m DatabaseSelectorModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case databaseListMsg:
m.loading = false
if msg.err != nil {
m.err = msg.err
m.databases = []string{"Error loading databases"}
} else {
m.databases = msg.databases
// Auto-select database if specified, or first database if auto-confirm is enabled
autoSelectDB := m.config.TUIAutoDatabase
if autoSelectDB == "" && m.config.TUIAutoConfirm && len(m.databases) > 0 {
// Auto-confirm mode: select first database automatically
autoSelectDB = m.databases[0]
m.logger.Info("Auto-confirm mode: selecting first database", "database", autoSelectDB)
}
if autoSelectDB != "" {
for i, db := range m.databases {
if db == autoSelectDB {
m.cursor = i
m.selected = db
m.logger.Info("Auto-selected database", "database", db)
// If sample backup, ask for ratio (or auto-use default)
if m.backupType == "sample" {
if m.config.TUIDryRun || m.config.TUIAutoConfirm {
// In dry-run or auto-confirm, use default ratio
executor := NewBackupExecution(m.config, m.logger, m.parent, m.ctx, m.backupType, m.selected, 10)
return executor, executor.Init()
}
inputModel := NewInputModel(m.config, m.logger, m,
"[STATS] Sample Ratio",
"Enter sample ratio (1-100):",
"10",
ValidateInt(1, 100))
return inputModel, nil
}
// For single backup, go directly to execution
executor := NewBackupExecution(m.config, m.logger, m.parent, m.ctx, m.backupType, m.selected, 0)
return executor, executor.Init()
}
}
m.logger.Warn("Auto-database not found in list", "requested", m.config.TUIAutoDatabase)
}
}
return m, nil
case tea.KeyMsg:
// Auto-forward ESC/quit in auto-confirm mode
if m.config.TUIAutoConfirm {
return m.parent, tea.Quit
}
switch msg.String() {
case "ctrl+c", "q", "esc":
return m.parent, nil
case "up", "k":
if m.cursor > 0 {
m.cursor--
}
case "down", "j":
if m.cursor < len(m.databases)-1 {
m.cursor++
}
case "enter":
if !m.loading && m.err == nil && len(m.databases) > 0 {
m.selected = m.databases[m.cursor]
// If sample backup, ask for ratio first
if m.backupType == "sample" {
inputModel := NewInputModel(m.config, m.logger, m,
"[STATS] Sample Ratio",
"Enter sample ratio (1-100):",
"10",
ValidateInt(1, 100))
return inputModel, nil
}
// For single backup, go directly to execution
executor := NewBackupExecution(m.config, m.logger, m.parent, m.ctx, m.backupType, m.selected, 0)
return executor, executor.Init()
}
}
}
return m, nil
}
func (m DatabaseSelectorModel) View() string {
var s strings.Builder
header := titleStyle.Render(m.title)
s.WriteString(fmt.Sprintf("\n%s\n\n", header))
if m.loading {
s.WriteString("[WAIT] Loading databases...\n")
return s.String()
}
if m.err != nil {
s.WriteString(fmt.Sprintf("[FAIL] Error: %v\n", m.err))
s.WriteString("\nPress ESC to go back\n")
return s.String()
}
s.WriteString("Select a database:\n\n")
for i, db := range m.databases {
cursor := " "
if m.cursor == i {
cursor = ">"
s.WriteString(selectedStyle.Render(fmt.Sprintf("%s %s", cursor, db)))
} else {
s.WriteString(fmt.Sprintf("%s %s", cursor, db))
}
s.WriteString("\n")
}
if m.message != "" {
s.WriteString(fmt.Sprintf("\n%s\n", m.message))
}
s.WriteString("\n[KEYS] Up/Down: Navigate | Enter: Select | ESC: Back | q: Quit\n")
return s.String()
}
func (m DatabaseSelectorModel) GetSelected() string {
return m.selected
}