Files
dbbackup/internal/tui/tools.go
Alexander Renz 6be19323d2 TUI: Improve UX and input validation
## Fixed
- Menu navigation now skips separator lines (up/down arrows)
- Input validation for sample ratio (0-100 range check)
- Graceful handling of invalid input with error message

## Improved
- Tools menu 'coming soon' items now show clear TODO status
- Added version hints (planned for v6.1)
- CLI alternative shown for Catalog Sync

## Code Quality
- Added warnStyle for TODO messages in tools.go
- Consistent error handling in input.go
2026-02-03 15:11:07 +01:00

200 lines
5.1 KiB
Go

package tui
import (
"context"
"fmt"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"dbbackup/internal/config"
"dbbackup/internal/logger"
)
// warnStyle for TODO/coming soon messages
var warnStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("3")).Bold(true)
// ToolsMenu represents the tools submenu
type ToolsMenu struct {
choices []string
cursor int
config *config.Config
logger logger.Logger
parent tea.Model
ctx context.Context
message string
}
// NewToolsMenu creates a new tools submenu
func NewToolsMenu(cfg *config.Config, log logger.Logger, parent tea.Model, ctx context.Context) *ToolsMenu {
return &ToolsMenu{
choices: []string{
"Blob Statistics",
"Blob Extract (externalize LOBs)",
"Table Sizes",
"--------------------------------",
"Kill Connections",
"Drop Database",
"--------------------------------",
"System Health Check",
"Dedup Store Analyze",
"Verify Backup Integrity",
"Catalog Sync",
"--------------------------------",
"Back to Main Menu",
},
config: cfg,
logger: log,
parent: parent,
ctx: ctx,
}
}
// Init initializes the model
func (t *ToolsMenu) Init() tea.Cmd {
return nil
}
// Update handles messages
func (t *ToolsMenu) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
switch msg.String() {
case "ctrl+c", "q", "esc":
return t.parent, nil
case "up", "k":
if t.cursor > 0 {
t.cursor--
// Skip separators
if t.choices[t.cursor] == "--------------------------------" && t.cursor > 0 {
t.cursor--
}
}
case "down", "j":
if t.cursor < len(t.choices)-1 {
t.cursor++
// Skip separators
if t.choices[t.cursor] == "--------------------------------" && t.cursor < len(t.choices)-1 {
t.cursor++
}
}
case "enter", " ":
switch t.cursor {
case 0: // Blob Statistics
return t.handleBlobStats()
case 1: // Blob Extract
return t.handleBlobExtract()
case 2: // Table Sizes
return t.handleTableSizes()
case 4: // Kill Connections
return t.handleKillConnections()
case 5: // Drop Database
return t.handleDropDatabase()
case 7: // System Health Check
return t.handleSystemHealth()
case 8: // Dedup Store Analyze
return t.handleDedupAnalyze()
case 9: // Verify Backup Integrity
return t.handleVerifyIntegrity()
case 10: // Catalog Sync
return t.handleCatalogSync()
case 12: // Back to Main Menu
return t.parent, nil
}
}
}
return t, nil
}
// View renders the tools menu
func (t *ToolsMenu) View() string {
var s string
// Header
s += "\n" + titleStyle.Render("Tools") + "\n\n"
// Description
s += infoStyle.Render("Advanced utilities for database backup management") + "\n\n"
// Menu items
for i, choice := range t.choices {
cursor := " "
if t.cursor == i {
cursor = ">"
s += menuSelectedStyle.Render(fmt.Sprintf("%s %s", cursor, choice))
} else {
s += menuStyle.Render(fmt.Sprintf("%s %s", cursor, choice))
}
s += "\n"
}
// Message area
if t.message != "" {
s += "\n" + t.message + "\n"
}
// Footer
s += "\n" + infoStyle.Render("[KEYS] Up/Down to navigate | Enter to select | Esc to go back")
return s
}
// handleBlobStats opens the blob statistics view
func (t *ToolsMenu) handleBlobStats() (tea.Model, tea.Cmd) {
stats := NewBlobStatsView(t.config, t.logger, t, t.ctx)
return stats, stats.Init()
}
// handleBlobExtract opens the blob extraction wizard
func (t *ToolsMenu) handleBlobExtract() (tea.Model, tea.Cmd) {
t.message = warnStyle.Render("[TODO] Blob extraction - planned for v6.1")
return t, nil
}
// handleSystemHealth opens the system health check
func (t *ToolsMenu) handleSystemHealth() (tea.Model, tea.Cmd) {
view := NewHealthView(t.config, t.logger, t, t.ctx)
return view, view.Init()
}
// handleDedupAnalyze shows dedup store analysis
func (t *ToolsMenu) handleDedupAnalyze() (tea.Model, tea.Cmd) {
t.message = warnStyle.Render("[TODO] Dedup analyze - planned for v6.1")
return t, nil
}
// handleVerifyIntegrity opens backup verification
func (t *ToolsMenu) handleVerifyIntegrity() (tea.Model, tea.Cmd) {
// Use existing archive browser for verification
browser := NewArchiveBrowser(t.config, t.logger, t, t.ctx, "verify")
return browser, browser.Init()
}
// handleCatalogSync synchronizes backup catalog
func (t *ToolsMenu) handleCatalogSync() (tea.Model, tea.Cmd) {
t.message = warnStyle.Render("[TODO] Catalog sync TUI - use CLI: dbbackup catalog sync")
return t, nil
}
// handleTableSizes opens the table sizes view
func (t *ToolsMenu) handleTableSizes() (tea.Model, tea.Cmd) {
view := NewTableSizesView(t.config, t.logger, t, t.ctx)
return view, view.Init()
}
// handleKillConnections opens the kill connections view
func (t *ToolsMenu) handleKillConnections() (tea.Model, tea.Cmd) {
view := NewKillConnectionsView(t.config, t.logger, t, t.ctx)
return view, view.Init()
}
// handleDropDatabase opens the drop database confirmation
func (t *ToolsMenu) handleDropDatabase() (tea.Model, tea.Cmd) {
view := NewDropDatabaseView(t.config, t.logger, t, t.ctx)
return view, view.Init()
}