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
|
progress progress.Indicator
|
||||||
detailedReporter *progress.DetailedReporter
|
detailedReporter *progress.DetailedReporter
|
||||||
dryRun bool
|
dryRun bool
|
||||||
|
progressCallback func(phase, status string, percent int) // Callback for TUI progress
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new restore engine
|
// New creates a new restore engine
|
||||||
@ -52,6 +53,19 @@ func NewSilent(cfg *config.Config, log logger.Logger, db database.Database) *Eng
|
|||||||
progress: progressIndicator,
|
progress: progressIndicator,
|
||||||
detailedReporter: detailedReporter,
|
detailedReporter: detailedReporter,
|
||||||
dryRun: false,
|
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.progress.Start(fmt.Sprintf("Restoring cluster from %s", filepath.Base(archivePath)))
|
||||||
|
e.reportProgress("Extracting", "Extracting cluster archive...", 5)
|
||||||
|
|
||||||
// Create temporary extraction directory
|
// Create temporary extraction directory
|
||||||
tempDir := filepath.Join(e.cfg.BackupDir, fmt.Sprintf(".restore_%d", time.Now().Unix()))
|
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 {
|
if _, err := os.Stat(globalsFile); err == nil {
|
||||||
e.log.Info("Restoring global objects")
|
e.log.Info("Restoring global objects")
|
||||||
e.progress.Update("Restoring global objects (roles, tablespaces)...")
|
e.progress.Update("Restoring global objects (roles, tablespaces)...")
|
||||||
|
e.reportProgress("Globals", "Restoring global objects...", 15)
|
||||||
if err := e.restoreGlobals(ctx, globalsFile); err != nil {
|
if err := e.restoreGlobals(ctx, globalsFile); err != nil {
|
||||||
e.log.Warn("Failed to restore global objects", "error", err)
|
e.log.Warn("Failed to restore global objects", "error", err)
|
||||||
// Continue anyway - global objects might already exist
|
// Continue anyway - global objects might already exist
|
||||||
@ -355,6 +371,14 @@ func (e *Engine) RestoreCluster(ctx context.Context, archivePath string) error {
|
|||||||
successCount := 0
|
successCount := 0
|
||||||
failCount := 0
|
failCount := 0
|
||||||
var failedDBs []string
|
var failedDBs []string
|
||||||
|
totalDBs := 0
|
||||||
|
|
||||||
|
// Count total databases
|
||||||
|
for _, entry := range entries {
|
||||||
|
if !entry.IsDir() {
|
||||||
|
totalDBs++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for i, entry := range entries {
|
for i, entry := range entries {
|
||||||
if entry.IsDir() {
|
if entry.IsDir() {
|
||||||
@ -364,7 +388,12 @@ func (e *Engine) RestoreCluster(ctx context.Context, archivePath string) error {
|
|||||||
dumpFile := filepath.Join(dumpsDir, entry.Name())
|
dumpFile := filepath.Join(dumpsDir, entry.Name())
|
||||||
dbName := strings.TrimSuffix(entry.Name(), ".dump")
|
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)
|
e.log.Info("Restoring database", "name", dbName, "file", dumpFile)
|
||||||
|
|
||||||
// Create database first if it doesn't exist
|
// Create database first if it doesn't exist
|
||||||
|
|||||||
@ -31,6 +31,8 @@ type RestoreExecutionModel struct {
|
|||||||
progress int
|
progress int
|
||||||
details []string
|
details []string
|
||||||
startTime time.Time
|
startTime time.Time
|
||||||
|
spinnerFrame int
|
||||||
|
spinnerFrames []string
|
||||||
|
|
||||||
// Results
|
// Results
|
||||||
done bool
|
done bool
|
||||||
@ -54,6 +56,8 @@ func NewRestoreExecution(cfg *config.Config, log logger.Logger, parent tea.Model
|
|||||||
phase: "Starting",
|
phase: "Starting",
|
||||||
startTime: time.Now(),
|
startTime: time.Now(),
|
||||||
details: []string{},
|
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) {
|
switch msg := msg.(type) {
|
||||||
case restoreTickMsg:
|
case restoreTickMsg:
|
||||||
if !m.done {
|
if !m.done {
|
||||||
m.progress = (m.progress + 2) % 100
|
m.spinnerFrame = (m.spinnerFrame + 1) % len(m.spinnerFrames)
|
||||||
m.elapsed = time.Since(m.startTime)
|
m.elapsed = time.Since(m.startTime)
|
||||||
return m, restoreTickCmd()
|
return m, restoreTickCmd()
|
||||||
}
|
}
|
||||||
@ -228,7 +232,10 @@ func (m RestoreExecutionModel) View() string {
|
|||||||
} else {
|
} else {
|
||||||
// Show progress
|
// Show progress
|
||||||
s.WriteString(fmt.Sprintf("Phase: %s\n", m.phase))
|
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")
|
s.WriteString("\n")
|
||||||
|
|
||||||
// Progress bar
|
// Progress bar
|
||||||
|
|||||||
Reference in New Issue
Block a user