v3.42.16: TUI cleanup - remove STATUS box, add global styles
This commit is contained in:
@@ -4,8 +4,8 @@ This directory contains pre-compiled binaries for the DB Backup Tool across mult
|
|||||||
|
|
||||||
## Build Information
|
## Build Information
|
||||||
- **Version**: 3.42.10
|
- **Version**: 3.42.10
|
||||||
- **Build Time**: 2026-01-08_09:40:57_UTC
|
- **Build Time**: 2026-01-08_09:54:02_UTC
|
||||||
- **Git Commit**: 55d34be
|
- **Git Commit**: 83ad62b
|
||||||
|
|
||||||
## Recent Updates (v1.1.0)
|
## Recent Updates (v1.1.0)
|
||||||
- ✅ Fixed TUI progress display with line-by-line output
|
- ✅ Fixed TUI progress display with line-by-line output
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
tea "github.com/charmbracelet/bubbletea"
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
"github.com/mattn/go-runewidth"
|
|
||||||
|
|
||||||
"dbbackup/internal/config"
|
"dbbackup/internal/config"
|
||||||
"dbbackup/internal/logger"
|
"dbbackup/internal/logger"
|
||||||
@@ -229,72 +228,66 @@ func (m BackupManagerModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
|
|
||||||
func (m BackupManagerModel) View() string {
|
func (m BackupManagerModel) View() string {
|
||||||
var s strings.Builder
|
var s strings.Builder
|
||||||
const boxWidth = 60
|
|
||||||
|
|
||||||
// Helper to pad string to box width (handles UTF-8)
|
|
||||||
padToWidth := func(text string, width int) string {
|
|
||||||
textWidth := runewidth.StringWidth(text)
|
|
||||||
if textWidth >= width {
|
|
||||||
return runewidth.Truncate(text, width-3, "...")
|
|
||||||
}
|
|
||||||
return text + strings.Repeat(" ", width-textWidth)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Title
|
// Title
|
||||||
s.WriteString(titleStyle.Render("[DB] Backup Archive Manager"))
|
s.WriteString(TitleStyle.Render("[DB] Backup Archive Manager"))
|
||||||
s.WriteString("\n\n")
|
s.WriteString("\n\n")
|
||||||
|
|
||||||
// Operation Status Box (always visible)
|
// Status line (no box, bold+color accents)
|
||||||
s.WriteString("+--[ STATUS ]" + strings.Repeat("-", boxWidth-13) + "+\n")
|
|
||||||
switch m.opState {
|
switch m.opState {
|
||||||
case OpVerifying:
|
case OpVerifying:
|
||||||
spinner := spinnerFrames[m.spinnerFrame]
|
spinner := spinnerFrames[m.spinnerFrame]
|
||||||
statusText := fmt.Sprintf(" %s Verifying: %s", spinner, m.opTarget)
|
s.WriteString(StatusActiveStyle.Render(fmt.Sprintf("%s Verifying: %s", spinner, m.opTarget)))
|
||||||
s.WriteString("|" + padToWidth(statusText, boxWidth) + "|\n")
|
s.WriteString("\n\n")
|
||||||
case OpDeleting:
|
case OpDeleting:
|
||||||
spinner := spinnerFrames[m.spinnerFrame]
|
spinner := spinnerFrames[m.spinnerFrame]
|
||||||
statusText := fmt.Sprintf(" %s Deleting: %s", spinner, m.opTarget)
|
s.WriteString(StatusActiveStyle.Render(fmt.Sprintf("%s Deleting: %s", spinner, m.opTarget)))
|
||||||
s.WriteString("|" + padToWidth(statusText, boxWidth) + "|\n")
|
s.WriteString("\n\n")
|
||||||
default:
|
default:
|
||||||
if m.loading {
|
if m.loading {
|
||||||
spinner := spinnerFrames[m.spinnerFrame]
|
spinner := spinnerFrames[m.spinnerFrame]
|
||||||
statusText := fmt.Sprintf(" %s Loading archives...", spinner)
|
s.WriteString(StatusActiveStyle.Render(fmt.Sprintf("%s Loading archives...", spinner)))
|
||||||
s.WriteString("|" + padToWidth(statusText, boxWidth) + "|\n")
|
s.WriteString("\n\n")
|
||||||
} else if m.message != "" {
|
} else if m.message != "" {
|
||||||
msgText := " " + m.message
|
// Color based on message content
|
||||||
s.WriteString("|" + padToWidth(msgText, boxWidth) + "|\n")
|
if strings.HasPrefix(m.message, "[+]") || strings.HasPrefix(m.message, "Valid") {
|
||||||
|
s.WriteString(StatusSuccessStyle.Render(m.message))
|
||||||
|
} else if strings.HasPrefix(m.message, "[-]") || strings.HasPrefix(m.message, "Error") {
|
||||||
|
s.WriteString(StatusErrorStyle.Render(m.message))
|
||||||
} else {
|
} else {
|
||||||
s.WriteString("|" + padToWidth(" Ready", boxWidth) + "|\n")
|
s.WriteString(StatusActiveStyle.Render(m.message))
|
||||||
}
|
}
|
||||||
|
s.WriteString("\n\n")
|
||||||
|
}
|
||||||
|
// No "Ready" message when idle - cleaner UI
|
||||||
}
|
}
|
||||||
s.WriteString("+" + strings.Repeat("-", boxWidth) + "+\n\n")
|
|
||||||
|
|
||||||
if m.loading {
|
if m.loading {
|
||||||
return s.String()
|
return s.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
if m.err != nil {
|
if m.err != nil {
|
||||||
s.WriteString(errorStyle.Render(fmt.Sprintf("[FAIL] Error: %v", m.err)))
|
s.WriteString(StatusErrorStyle.Render(fmt.Sprintf("[FAIL] Error: %v", m.err)))
|
||||||
s.WriteString("\n\n")
|
s.WriteString("\n\n")
|
||||||
s.WriteString(infoStyle.Render("Press Esc to go back"))
|
s.WriteString(ShortcutStyle.Render("Press Esc to go back"))
|
||||||
return s.String()
|
return s.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Summary
|
// Summary
|
||||||
s.WriteString(infoStyle.Render(fmt.Sprintf("Total Archives: %d | Total Size: %s",
|
s.WriteString(LabelStyle.Render(fmt.Sprintf("Total Archives: %d | Total Size: %s",
|
||||||
len(m.archives), formatSize(m.totalSize))))
|
len(m.archives), formatSize(m.totalSize))))
|
||||||
s.WriteString("\n\n")
|
s.WriteString("\n\n")
|
||||||
|
|
||||||
// Archives list
|
// Archives list
|
||||||
if len(m.archives) == 0 {
|
if len(m.archives) == 0 {
|
||||||
s.WriteString(infoStyle.Render("No backup archives found"))
|
s.WriteString(StatusReadyStyle.Render("No backup archives found"))
|
||||||
s.WriteString("\n\n")
|
s.WriteString("\n\n")
|
||||||
s.WriteString(infoStyle.Render("Press Esc to go back"))
|
s.WriteString(ShortcutStyle.Render("Press Esc to go back"))
|
||||||
return s.String()
|
return s.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Column headers with better alignment
|
// Column headers with better alignment
|
||||||
s.WriteString(archiveHeaderStyle.Render(fmt.Sprintf(" %-32s %-22s %10s %-16s",
|
s.WriteString(ListHeaderStyle.Render(fmt.Sprintf(" %-32s %-22s %10s %-16s",
|
||||||
"FILENAME", "FORMAT", "SIZE", "MODIFIED")))
|
"FILENAME", "FORMAT", "SIZE", "MODIFIED")))
|
||||||
s.WriteString("\n")
|
s.WriteString("\n")
|
||||||
s.WriteString(strings.Repeat("-", 90))
|
s.WriteString(strings.Repeat("-", 90))
|
||||||
@@ -313,18 +306,18 @@ func (m BackupManagerModel) View() string {
|
|||||||
for i := start; i < end; i++ {
|
for i := start; i < end; i++ {
|
||||||
archive := m.archives[i]
|
archive := m.archives[i]
|
||||||
cursor := " "
|
cursor := " "
|
||||||
style := archiveNormalStyle
|
style := ListNormalStyle
|
||||||
|
|
||||||
if i == m.cursor {
|
if i == m.cursor {
|
||||||
cursor = "> "
|
cursor = "> "
|
||||||
style = archiveSelectedStyle
|
style = ListSelectedStyle
|
||||||
}
|
}
|
||||||
|
|
||||||
// Status icon - consistent 4-char width
|
// Status icon - consistent 4-char width
|
||||||
statusIcon := " [+]"
|
statusIcon := " [+]"
|
||||||
if !archive.Valid {
|
if !archive.Valid {
|
||||||
statusIcon = " [-]"
|
statusIcon = " [-]"
|
||||||
style = archiveInvalidStyle
|
style = ItemInvalidStyle
|
||||||
} else if time.Since(archive.Modified) > 30*24*time.Hour {
|
} else if time.Since(archive.Modified) > 30*24*time.Hour {
|
||||||
statusIcon = " [!]"
|
statusIcon = " [!]"
|
||||||
}
|
}
|
||||||
@@ -347,11 +340,11 @@ func (m BackupManagerModel) View() string {
|
|||||||
// Footer
|
// Footer
|
||||||
s.WriteString("\n")
|
s.WriteString("\n")
|
||||||
|
|
||||||
s.WriteString(infoStyle.Render(fmt.Sprintf("Selected: %d/%d", m.cursor+1, len(m.archives))))
|
s.WriteString(StatusReadyStyle.Render(fmt.Sprintf("Selected: %d/%d", m.cursor+1, len(m.archives))))
|
||||||
s.WriteString("\n\n")
|
s.WriteString("\n\n")
|
||||||
|
|
||||||
// Grouped keyboard shortcuts - simple aligned format
|
// Grouped keyboard shortcuts
|
||||||
s.WriteString("SHORTCUTS: Up/Down=Move | r=Restore | v=Verify | d=Delete | i=Info | R=Refresh | Esc=Back | q=Quit")
|
s.WriteString(ShortcutStyle.Render("SHORTCUTS: Up/Down=Move | r=Restore | v=Verify | d=Delete | i=Info | R=Refresh | Esc=Back | q=Quit"))
|
||||||
|
|
||||||
return s.String()
|
return s.String()
|
||||||
}
|
}
|
||||||
|
|||||||
133
internal/tui/styles.go
Normal file
133
internal/tui/styles.go
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
package tui
|
||||||
|
|
||||||
|
import "github.com/charmbracelet/lipgloss"
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// GLOBAL TUI STYLE DEFINITIONS
|
||||||
|
// =============================================================================
|
||||||
|
// Design Language:
|
||||||
|
// - Bold text for labels and headers
|
||||||
|
// - Colors for semantic meaning (green=success, red=error, yellow=warning)
|
||||||
|
// - No emoticons - use simple text prefixes like [OK], [FAIL], [!]
|
||||||
|
// - No boxes for inline status - use bold+color accents
|
||||||
|
// - Consistent color palette across all views
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
// Color Palette (ANSI 256 colors for terminal compatibility)
|
||||||
|
const (
|
||||||
|
ColorWhite = lipgloss.Color("15") // Bright white
|
||||||
|
ColorGray = lipgloss.Color("250") // Light gray
|
||||||
|
ColorDim = lipgloss.Color("244") // Dim gray
|
||||||
|
ColorDimmer = lipgloss.Color("240") // Darker gray
|
||||||
|
ColorSuccess = lipgloss.Color("2") // Green
|
||||||
|
ColorError = lipgloss.Color("1") // Red
|
||||||
|
ColorWarning = lipgloss.Color("3") // Yellow
|
||||||
|
ColorInfo = lipgloss.Color("6") // Cyan
|
||||||
|
ColorAccent = lipgloss.Color("4") // Blue
|
||||||
|
)
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// TITLE & HEADER STYLES
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
// TitleStyle - main view title (bold white on gray background)
|
||||||
|
var TitleStyle = lipgloss.NewStyle().
|
||||||
|
Bold(true).
|
||||||
|
Foreground(ColorWhite).
|
||||||
|
Background(ColorDimmer).
|
||||||
|
Padding(0, 1)
|
||||||
|
|
||||||
|
// HeaderStyle - section headers (bold gray)
|
||||||
|
var HeaderStyle = lipgloss.NewStyle().
|
||||||
|
Bold(true).
|
||||||
|
Foreground(ColorDim)
|
||||||
|
|
||||||
|
// LabelStyle - field labels (bold cyan)
|
||||||
|
var LabelStyle = lipgloss.NewStyle().
|
||||||
|
Bold(true).
|
||||||
|
Foreground(ColorInfo)
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// STATUS STYLES
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
// StatusReadyStyle - idle/ready state (dim)
|
||||||
|
var StatusReadyStyle = lipgloss.NewStyle().
|
||||||
|
Foreground(ColorDim)
|
||||||
|
|
||||||
|
// StatusActiveStyle - operation in progress (bold cyan)
|
||||||
|
var StatusActiveStyle = lipgloss.NewStyle().
|
||||||
|
Bold(true).
|
||||||
|
Foreground(ColorInfo)
|
||||||
|
|
||||||
|
// StatusSuccessStyle - success messages (bold green)
|
||||||
|
var StatusSuccessStyle = lipgloss.NewStyle().
|
||||||
|
Bold(true).
|
||||||
|
Foreground(ColorSuccess)
|
||||||
|
|
||||||
|
// StatusErrorStyle - error messages (bold red)
|
||||||
|
var StatusErrorStyle = lipgloss.NewStyle().
|
||||||
|
Bold(true).
|
||||||
|
Foreground(ColorError)
|
||||||
|
|
||||||
|
// StatusWarningStyle - warning messages (bold yellow)
|
||||||
|
var StatusWarningStyle = lipgloss.NewStyle().
|
||||||
|
Bold(true).
|
||||||
|
Foreground(ColorWarning)
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// LIST & TABLE STYLES
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
// ListNormalStyle - unselected list items
|
||||||
|
var ListNormalStyle = lipgloss.NewStyle().
|
||||||
|
Foreground(ColorGray)
|
||||||
|
|
||||||
|
// ListSelectedStyle - selected/cursor item (bold white)
|
||||||
|
var ListSelectedStyle = lipgloss.NewStyle().
|
||||||
|
Foreground(ColorWhite).
|
||||||
|
Bold(true)
|
||||||
|
|
||||||
|
// ListHeaderStyle - column headers (bold dim)
|
||||||
|
var ListHeaderStyle = lipgloss.NewStyle().
|
||||||
|
Bold(true).
|
||||||
|
Foreground(ColorDim)
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// ITEM STATUS STYLES
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
// ItemValidStyle - valid/OK items (green)
|
||||||
|
var ItemValidStyle = lipgloss.NewStyle().
|
||||||
|
Foreground(ColorSuccess)
|
||||||
|
|
||||||
|
// ItemInvalidStyle - invalid/failed items (red)
|
||||||
|
var ItemInvalidStyle = lipgloss.NewStyle().
|
||||||
|
Foreground(ColorError)
|
||||||
|
|
||||||
|
// ItemOldStyle - old/stale items (yellow)
|
||||||
|
var ItemOldStyle = lipgloss.NewStyle().
|
||||||
|
Foreground(ColorWarning)
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// SHORTCUT STYLE
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
// ShortcutStyle - keyboard shortcuts footer (dim)
|
||||||
|
var ShortcutStyle = lipgloss.NewStyle().
|
||||||
|
Foreground(ColorDim)
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// HELPER PREFIXES (no emoticons)
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
const (
|
||||||
|
PrefixOK = "[OK]"
|
||||||
|
PrefixFail = "[FAIL]"
|
||||||
|
PrefixWarn = "[!]"
|
||||||
|
PrefixInfo = "[i]"
|
||||||
|
PrefixPlus = "[+]"
|
||||||
|
PrefixMinus = "[-]"
|
||||||
|
PrefixArrow = ">"
|
||||||
|
PrefixSpinner = "" // Spinner character added dynamically
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user