chore: update build and tui assets

This commit is contained in:
2025-10-24 15:43:27 +00:00
parent 9b3c3f2b1b
commit 4e281cff01
31 changed files with 2456 additions and 571 deletions

210
internal/tui/backup_exec.go Normal file
View File

@ -0,0 +1,210 @@
package tui
import (
"context"
"fmt"
"strings"
"time"
tea "github.com/charmbracelet/bubbletea"
"dbbackup/internal/backup"
"dbbackup/internal/config"
"dbbackup/internal/database"
"dbbackup/internal/logger"
"dbbackup/internal/progress"
)
// BackupExecutionModel handles backup execution with progress
type BackupExecutionModel struct {
config *config.Config
logger logger.Logger
parent tea.Model
backupType string
databaseName string
ratio int
status string
progress int
done bool
err error
result string
startTime time.Time
details []string
}
func NewBackupExecution(cfg *config.Config, log logger.Logger, parent tea.Model, backupType, dbName string, ratio int) BackupExecutionModel {
return BackupExecutionModel{
config: cfg,
logger: log,
parent: parent,
backupType: backupType,
databaseName: dbName,
ratio: ratio,
status: "Initializing...",
startTime: time.Now(),
details: []string{},
}
}
func (m BackupExecutionModel) Init() tea.Cmd {
reporter := NewTUIProgressReporter()
reporter.AddCallback(func(ops []progress.OperationStatus) {
if len(ops) == 0 {
return
}
latest := ops[len(ops)-1]
tea.Println(backupProgressMsg{
status: latest.Message,
progress: latest.Progress,
detail: latest.Status,
})
})
return executeBackupWithTUIProgress(m.config, m.logger, m.backupType, m.databaseName, m.ratio, reporter)
}
type backupProgressMsg struct {
status string
progress int
detail string
}
type backupCompleteMsg struct {
result string
err error
}
func executeBackupWithTUIProgress(cfg *config.Config, log logger.Logger, backupType, dbName string, ratio int, reporter *TUIProgressReporter) tea.Cmd {
return func() tea.Msg {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Minute)
defer cancel()
start := time.Now()
dbClient, err := database.New(cfg, log)
if err != nil {
return backupCompleteMsg{
result: "",
err: fmt.Errorf("failed to create database client: %w", err),
}
}
defer dbClient.Close()
if err := dbClient.Connect(ctx); err != nil {
return backupCompleteMsg{
result: "",
err: fmt.Errorf("database connection failed: %w", err),
}
}
engine := backup.NewSilent(cfg, log, dbClient, reporter)
var backupErr error
switch backupType {
case "single":
backupErr = engine.BackupSingle(ctx, dbName)
case "sample":
cfg.SampleStrategy = "ratio"
cfg.SampleValue = ratio
backupErr = engine.BackupSample(ctx, dbName)
case "cluster":
backupErr = engine.BackupCluster(ctx)
default:
return backupCompleteMsg{err: fmt.Errorf("unknown backup type: %s", backupType)}
}
if backupErr != nil {
return backupCompleteMsg{
result: "",
err: fmt.Errorf("backup failed: %w", backupErr),
}
}
elapsed := time.Since(start).Round(time.Second)
var result string
switch backupType {
case "single":
result = fmt.Sprintf("✓ Single database backup of '%s' completed successfully in %v", dbName, elapsed)
case "sample":
result = fmt.Sprintf("✓ Sample backup of '%s' (ratio: %d) completed successfully in %v", dbName, ratio, elapsed)
case "cluster":
result = fmt.Sprintf("✓ Cluster backup completed successfully in %v", elapsed)
}
return backupCompleteMsg{
result: result,
err: nil,
}
}
}
func (m BackupExecutionModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case backupProgressMsg:
m.status = msg.status
m.progress = msg.progress
return m, nil
case backupCompleteMsg:
m.done = true
m.err = msg.err
m.result = msg.result
if m.err == nil {
m.status = "✅ Backup completed successfully!"
} else {
m.status = fmt.Sprintf("❌ Backup failed: %v", m.err)
}
return m, nil
case tea.KeyMsg:
if m.done {
switch msg.String() {
case "enter", "esc", "q":
return m.parent, nil
}
}
}
return m, nil
}
func (m BackupExecutionModel) View() string {
var s strings.Builder
header := titleStyle.Render("🔄 Backup Execution")
s.WriteString(fmt.Sprintf("\n%s\n\n", header))
s.WriteString(fmt.Sprintf("Type: %s\n", m.backupType))
if m.databaseName != "" {
s.WriteString(fmt.Sprintf("Database: %s\n", m.databaseName))
}
if m.ratio > 0 {
s.WriteString(fmt.Sprintf("Sample Ratio: %d\n", m.ratio))
}
s.WriteString(fmt.Sprintf("Duration: %s\n\n", time.Since(m.startTime).Round(time.Second)))
s.WriteString(fmt.Sprintf("Status: %s\n", m.status))
if !m.done {
spinner := []string{"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"}
frame := int(time.Since(m.startTime).Milliseconds()/100) % len(spinner)
s.WriteString(fmt.Sprintf("\n%s Processing...\n", spinner[frame]))
} else {
s.WriteString("\n")
if m.err != nil {
s.WriteString(fmt.Sprintf("Error: %v\n\n", m.err))
}
lines := strings.Split(m.result, "\n")
for _, line := range lines {
if strings.Contains(line, "✅") || strings.Contains(line, "completed") ||
strings.Contains(line, "Size:") || strings.Contains(line, "backup_") {
s.WriteString(line + "\n")
}
}
s.WriteString("\n⌨ Press Enter or ESC to return to menu\n")
}
return s.String()
}