Add rotating spinner to TUI status for visual progress feedback
This commit is contained in:
Binary file not shown.
@ -23,6 +23,7 @@ type Engine struct {
|
||||
progress progress.Indicator
|
||||
detailedReporter *progress.DetailedReporter
|
||||
dryRun bool
|
||||
progressCallback func(phase, status string, percent int) // Callback for TUI progress
|
||||
}
|
||||
|
||||
// New creates a new restore engine
|
||||
@ -52,6 +53,19 @@ func NewSilent(cfg *config.Config, log logger.Logger, db database.Database) *Eng
|
||||
progress: progressIndicator,
|
||||
detailedReporter: detailedReporter,
|
||||
dryRun: false,
|
||||
progressCallback: nil,
|
||||
}
|
||||
}
|
||||
|
||||
// SetProgressCallback sets a callback function for progress updates (used by TUI)
|
||||
func (e *Engine) SetProgressCallback(callback func(phase, status string, percent int)) {
|
||||
e.progressCallback = callback
|
||||
}
|
||||
|
||||
// reportProgress calls the progress callback if set
|
||||
func (e *Engine) reportProgress(phase, status string, percent int) {
|
||||
if e.progressCallback != nil {
|
||||
e.progressCallback(phase, status, percent)
|
||||
}
|
||||
}
|
||||
|
||||
@ -312,6 +326,7 @@ func (e *Engine) RestoreCluster(ctx context.Context, archivePath string) error {
|
||||
}
|
||||
|
||||
e.progress.Start(fmt.Sprintf("Restoring cluster from %s", filepath.Base(archivePath)))
|
||||
e.reportProgress("Extracting", "Extracting cluster archive...", 5)
|
||||
|
||||
// Create temporary extraction directory
|
||||
tempDir := filepath.Join(e.cfg.BackupDir, fmt.Sprintf(".restore_%d", time.Now().Unix()))
|
||||
@ -333,6 +348,7 @@ func (e *Engine) RestoreCluster(ctx context.Context, archivePath string) error {
|
||||
if _, err := os.Stat(globalsFile); err == nil {
|
||||
e.log.Info("Restoring global objects")
|
||||
e.progress.Update("Restoring global objects (roles, tablespaces)...")
|
||||
e.reportProgress("Globals", "Restoring global objects...", 15)
|
||||
if err := e.restoreGlobals(ctx, globalsFile); err != nil {
|
||||
e.log.Warn("Failed to restore global objects", "error", err)
|
||||
// Continue anyway - global objects might already exist
|
||||
@ -355,6 +371,14 @@ func (e *Engine) RestoreCluster(ctx context.Context, archivePath string) error {
|
||||
successCount := 0
|
||||
failCount := 0
|
||||
var failedDBs []string
|
||||
totalDBs := 0
|
||||
|
||||
// Count total databases
|
||||
for _, entry := range entries {
|
||||
if !entry.IsDir() {
|
||||
totalDBs++
|
||||
}
|
||||
}
|
||||
|
||||
for i, entry := range entries {
|
||||
if entry.IsDir() {
|
||||
@ -364,7 +388,12 @@ func (e *Engine) RestoreCluster(ctx context.Context, archivePath string) error {
|
||||
dumpFile := filepath.Join(dumpsDir, entry.Name())
|
||||
dbName := strings.TrimSuffix(entry.Name(), ".dump")
|
||||
|
||||
e.progress.Update(fmt.Sprintf("[%d/%d] Restoring database: %s", i+1, len(entries), dbName))
|
||||
// Calculate progress: 15% for extraction/globals, 85% for databases
|
||||
dbProgress := 15 + int(float64(i)/float64(totalDBs)*85.0)
|
||||
|
||||
statusMsg := fmt.Sprintf("⠋ [%d/%d] Restoring: %s", i+1, totalDBs, dbName)
|
||||
e.progress.Update(statusMsg)
|
||||
e.reportProgress("Restoring", statusMsg, dbProgress)
|
||||
e.log.Info("Restoring database", "name", dbName, "file", dumpFile)
|
||||
|
||||
// Create database first if it doesn't exist
|
||||
|
||||
@ -31,6 +31,8 @@ type RestoreExecutionModel struct {
|
||||
progress int
|
||||
details []string
|
||||
startTime time.Time
|
||||
spinnerFrame int
|
||||
spinnerFrames []string
|
||||
|
||||
// Results
|
||||
done bool
|
||||
@ -54,6 +56,8 @@ func NewRestoreExecution(cfg *config.Config, log logger.Logger, parent tea.Model
|
||||
phase: "Starting",
|
||||
startTime: time.Now(),
|
||||
details: []string{},
|
||||
spinnerFrames: []string{"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"},
|
||||
spinnerFrame: 0,
|
||||
}
|
||||
}
|
||||
|
||||
@ -139,7 +143,7 @@ func (m RestoreExecutionModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
switch msg := msg.(type) {
|
||||
case restoreTickMsg:
|
||||
if !m.done {
|
||||
m.progress = (m.progress + 2) % 100
|
||||
m.spinnerFrame = (m.spinnerFrame + 1) % len(m.spinnerFrames)
|
||||
m.elapsed = time.Since(m.startTime)
|
||||
return m, restoreTickCmd()
|
||||
}
|
||||
@ -228,7 +232,10 @@ func (m RestoreExecutionModel) View() string {
|
||||
} else {
|
||||
// Show progress
|
||||
s.WriteString(fmt.Sprintf("Phase: %s\n", m.phase))
|
||||
s.WriteString(fmt.Sprintf("Status: %s\n", m.status))
|
||||
|
||||
// Show status with rotating spinner
|
||||
spinner := m.spinnerFrames[m.spinnerFrame]
|
||||
s.WriteString(fmt.Sprintf("Status: %s %s\n", spinner, m.status))
|
||||
s.WriteString("\n")
|
||||
|
||||
// Progress bar
|
||||
|
||||
Reference in New Issue
Block a user