v3.42.11: Replace all Unicode emojis with ASCII text
All checks were successful
CI/CD / Test (push) Successful in 1m13s
CI/CD / Lint (push) Successful in 1m20s
CI/CD / Build & Release (push) Successful in 3m10s

- Replace all emoji characters with ASCII equivalents throughout codebase
- Replace Unicode box-drawing characters (═║╔╗╚╝━─) with ASCII (+|-=)
- Replace checkmarks (✓✗) with [OK]/[FAIL] markers
- 59 files updated, 741 lines changed
- Improves terminal compatibility and reduces visual noise
This commit is contained in:
2026-01-08 09:42:01 +01:00
parent 5fb88b14ba
commit 3e41d88445
61 changed files with 1040 additions and 739 deletions

View File

@@ -204,13 +204,13 @@ func CheckAuthenticationMismatch(cfg *config.Config) (bool, string) {
func buildAuthMismatchMessage(osUser, dbUser string, method AuthMethod) string {
var msg strings.Builder
msg.WriteString("\n⚠️ Authentication Mismatch Detected\n")
msg.WriteString("\n[WARN] Authentication Mismatch Detected\n")
msg.WriteString(strings.Repeat("=", 60) + "\n\n")
msg.WriteString(fmt.Sprintf(" PostgreSQL is using '%s' authentication\n", method))
msg.WriteString(fmt.Sprintf(" OS user '%s' cannot authenticate as DB user '%s'\n\n", osUser, dbUser))
msg.WriteString("💡 Solutions (choose one):\n\n")
msg.WriteString("[TIP] Solutions (choose one):\n\n")
msg.WriteString(fmt.Sprintf(" 1. Run as matching user:\n"))
msg.WriteString(fmt.Sprintf(" sudo -u %s %s\n\n", dbUser, getCommandLine()))
@@ -226,7 +226,7 @@ func buildAuthMismatchMessage(osUser, dbUser string, method AuthMethod) string {
msg.WriteString(" 4. Provide password via flag:\n")
msg.WriteString(fmt.Sprintf(" %s --password your_password\n\n", getCommandLine()))
msg.WriteString("📝 Note: For production use, ~/.pgpass or PGPASSWORD are recommended\n")
msg.WriteString("[NOTE] Note: For production use, ~/.pgpass or PGPASSWORD are recommended\n")
msg.WriteString(" to avoid exposing passwords in command history.\n\n")
msg.WriteString(strings.Repeat("=", 60) + "\n")

View File

@@ -473,7 +473,7 @@ func (e *Engine) BackupCluster(ctx context.Context) error {
mu.Lock()
e.printf(" Database size: %s\n", sizeStr)
if size > 10*1024*1024*1024 { // > 10GB
e.printf(" ⚠️ Large database detected - this may take a while\n")
e.printf(" [WARN] Large database detected - this may take a while\n")
}
mu.Unlock()
}
@@ -518,16 +518,16 @@ func (e *Engine) BackupCluster(ctx context.Context) error {
if err != nil {
e.log.Warn("Failed to backup database", "database", name, "error", err)
mu.Lock()
e.printf(" ⚠️ WARNING: Failed to backup %s: %v\n", name, err)
e.printf(" [WARN] WARNING: Failed to backup %s: %v\n", name, err)
mu.Unlock()
atomic.AddInt32(&failCount, 1)
} else {
compressedCandidate := strings.TrimSuffix(dumpFile, ".dump") + ".sql.gz"
mu.Lock()
if info, err := os.Stat(compressedCandidate); err == nil {
e.printf(" Completed %s (%s)\n", name, formatBytes(info.Size()))
e.printf(" [OK] Completed %s (%s)\n", name, formatBytes(info.Size()))
} else if info, err := os.Stat(dumpFile); err == nil {
e.printf(" Completed %s (%s)\n", name, formatBytes(info.Size()))
e.printf(" [OK] Completed %s (%s)\n", name, formatBytes(info.Size()))
}
mu.Unlock()
atomic.AddInt32(&successCount, 1)

View File

@@ -242,7 +242,7 @@ func TestIncrementalBackupRestore(t *testing.T) {
t.Errorf("Unchanged file base/12345/1235 not found in restore: %v", err)
}
t.Log(" Incremental backup and restore test completed successfully")
t.Log("[OK] Incremental backup and restore test completed successfully")
}
// TestIncrementalBackupErrors tests error handling

View File

@@ -75,16 +75,16 @@ func FormatDiskSpaceMessage(check *DiskSpaceCheck) string {
if check.Critical {
status = "CRITICAL"
icon = ""
icon = "[X]"
} else if check.Warning {
status = "WARNING"
icon = "⚠️ "
icon = "[!]"
} else {
status = "OK"
icon = ""
icon = "[+]"
}
msg := fmt.Sprintf(`📊 Disk Space Check (%s):
msg := fmt.Sprintf(`[DISK] Disk Space Check (%s):
Path: %s
Total: %s
Available: %s (%.1f%% used)
@@ -98,13 +98,13 @@ func FormatDiskSpaceMessage(check *DiskSpaceCheck) string {
status)
if check.Critical {
msg += "\n \n ⚠️ CRITICAL: Insufficient disk space!"
msg += "\n \n [!!] CRITICAL: Insufficient disk space!"
msg += "\n Operation blocked. Free up space before continuing."
} else if check.Warning {
msg += "\n \n ⚠️ WARNING: Low disk space!"
msg += "\n \n [!] WARNING: Low disk space!"
msg += "\n Backup may fail if database is larger than estimated."
} else {
msg += "\n \n Sufficient space available"
msg += "\n \n [+] Sufficient space available"
}
return msg

View File

@@ -75,16 +75,16 @@ func FormatDiskSpaceMessage(check *DiskSpaceCheck) string {
if check.Critical {
status = "CRITICAL"
icon = ""
icon = "[X]"
} else if check.Warning {
status = "WARNING"
icon = "⚠️ "
icon = "[!]"
} else {
status = "OK"
icon = ""
icon = "[+]"
}
msg := fmt.Sprintf(`📊 Disk Space Check (%s):
msg := fmt.Sprintf(`[DISK] Disk Space Check (%s):
Path: %s
Total: %s
Available: %s (%.1f%% used)
@@ -98,13 +98,13 @@ func FormatDiskSpaceMessage(check *DiskSpaceCheck) string {
status)
if check.Critical {
msg += "\n \n ⚠️ CRITICAL: Insufficient disk space!"
msg += "\n \n [!!] CRITICAL: Insufficient disk space!"
msg += "\n Operation blocked. Free up space before continuing."
} else if check.Warning {
msg += "\n \n ⚠️ WARNING: Low disk space!"
msg += "\n \n [!] WARNING: Low disk space!"
msg += "\n Backup may fail if database is larger than estimated."
} else {
msg += "\n \n Sufficient space available"
msg += "\n \n [+] Sufficient space available"
}
return msg

View File

@@ -58,16 +58,16 @@ func FormatDiskSpaceMessage(check *DiskSpaceCheck) string {
if check.Critical {
status = "CRITICAL"
icon = ""
icon = "[X]"
} else if check.Warning {
status = "WARNING"
icon = "⚠️ "
icon = "[!]"
} else {
status = "OK"
icon = ""
icon = "[+]"
}
msg := fmt.Sprintf(`📊 Disk Space Check (%s):
msg := fmt.Sprintf(`[DISK] Disk Space Check (%s):
Path: %s
Total: %s
Available: %s (%.1f%% used)
@@ -81,13 +81,13 @@ func FormatDiskSpaceMessage(check *DiskSpaceCheck) string {
status)
if check.Critical {
msg += "\n \n ⚠️ CRITICAL: Insufficient disk space!"
msg += "\n \n [!!] CRITICAL: Insufficient disk space!"
msg += "\n Operation blocked. Free up space before continuing."
} else if check.Warning {
msg += "\n \n ⚠️ WARNING: Low disk space!"
msg += "\n \n [!] WARNING: Low disk space!"
msg += "\n Backup may fail if database is larger than estimated."
} else {
msg += "\n \n Sufficient space available"
msg += "\n \n [+] Sufficient space available"
}
return msg

View File

@@ -94,16 +94,16 @@ func FormatDiskSpaceMessage(check *DiskSpaceCheck) string {
if check.Critical {
status = "CRITICAL"
icon = ""
icon = "[X]"
} else if check.Warning {
status = "WARNING"
icon = "⚠️ "
icon = "[!]"
} else {
status = "OK"
icon = ""
icon = "[+]"
}
msg := fmt.Sprintf(`📊 Disk Space Check (%s):
msg := fmt.Sprintf(`[DISK] Disk Space Check (%s):
Path: %s
Total: %s
Available: %s (%.1f%% used)
@@ -117,13 +117,13 @@ func FormatDiskSpaceMessage(check *DiskSpaceCheck) string {
status)
if check.Critical {
msg += "\n \n ⚠️ CRITICAL: Insufficient disk space!"
msg += "\n \n [!!] CRITICAL: Insufficient disk space!"
msg += "\n Operation blocked. Free up space before continuing."
} else if check.Warning {
msg += "\n \n ⚠️ WARNING: Low disk space!"
msg += "\n \n [!] WARNING: Low disk space!"
msg += "\n Backup may fail if database is larger than estimated."
} else {
msg += "\n \n Sufficient space available"
msg += "\n \n [+] Sufficient space available"
}
return msg

View File

@@ -234,22 +234,22 @@ func FormatErrorWithHint(errorMsg string) string {
var icon string
switch classification.Type {
case "ignorable":
icon = " "
icon = "[i]"
case "warning":
icon = "⚠️ "
icon = "[!]"
case "critical":
icon = ""
icon = "[X]"
case "fatal":
icon = "🛑"
icon = "[!!]"
default:
icon = "⚠️ "
icon = "[!]"
}
output := fmt.Sprintf("%s %s Error\n\n", icon, strings.ToUpper(classification.Type))
output += fmt.Sprintf("Category: %s\n", classification.Category)
output += fmt.Sprintf("Message: %s\n\n", classification.Message)
output += fmt.Sprintf("💡 Hint: %s\n\n", classification.Hint)
output += fmt.Sprintf("🔧 Action: %s\n", classification.Action)
output += fmt.Sprintf("[HINT] Hint: %s\n\n", classification.Hint)
output += fmt.Sprintf("[ACTION] Action: %s\n", classification.Action)
return output
}
@@ -257,7 +257,7 @@ func FormatErrorWithHint(errorMsg string) string {
// FormatMultipleErrors formats multiple errors with classification
func FormatMultipleErrors(errors []string) string {
if len(errors) == 0 {
return " No errors"
return "[+] No errors"
}
ignorable := 0
@@ -285,22 +285,22 @@ func FormatMultipleErrors(errors []string) string {
}
}
output := "📊 Error Summary:\n\n"
output := "[SUMMARY] Error Summary:\n\n"
if ignorable > 0 {
output += fmt.Sprintf(" %d ignorable (objects already exist)\n", ignorable)
output += fmt.Sprintf(" [i] %d ignorable (objects already exist)\n", ignorable)
}
if warnings > 0 {
output += fmt.Sprintf(" ⚠️ %d warnings\n", warnings)
output += fmt.Sprintf(" [!] %d warnings\n", warnings)
}
if critical > 0 {
output += fmt.Sprintf(" %d critical errors\n", critical)
output += fmt.Sprintf(" [X] %d critical errors\n", critical)
}
if fatal > 0 {
output += fmt.Sprintf(" 🛑 %d fatal errors\n", fatal)
output += fmt.Sprintf(" [!!] %d fatal errors\n", fatal)
}
if len(criticalErrors) > 0 {
output += "\n📝 Critical Issues:\n\n"
output += "\n[CRITICAL] Critical Issues:\n\n"
for i, err := range criticalErrors {
class := ClassifyError(err)
output += fmt.Sprintf("%d. %s\n", i+1, class.Hint)

View File

@@ -49,15 +49,15 @@ func (s CheckStatus) String() string {
func (s CheckStatus) Icon() string {
switch s {
case StatusPassed:
return ""
return "[+]"
case StatusWarning:
return ""
return "[!]"
case StatusFailed:
return ""
return "[-]"
case StatusSkipped:
return ""
return "[ ]"
default:
return "?"
return "[?]"
}
}

View File

@@ -11,9 +11,9 @@ func FormatPreflightReport(result *PreflightResult, dbName string, verbose bool)
var sb strings.Builder
sb.WriteString("\n")
sb.WriteString("╔══════════════════════════════════════════════════════════════╗\n")
sb.WriteString(" [DRY RUN] Preflight Check Results \n")
sb.WriteString("╚══════════════════════════════════════════════════════════════╝\n")
sb.WriteString("+==============================================================+\n")
sb.WriteString("| [DRY RUN] Preflight Check Results |\n")
sb.WriteString("+==============================================================+\n")
sb.WriteString("\n")
// Database info
@@ -29,7 +29,7 @@ func FormatPreflightReport(result *PreflightResult, dbName string, verbose bool)
// Check results
sb.WriteString(" Checks:\n")
sb.WriteString(" ─────────────────────────────────────────────────────────────\n")
sb.WriteString(" --------------------------------------------------------------\n")
for _, check := range result.Checks {
icon := check.Status.Icon()
@@ -40,26 +40,26 @@ func FormatPreflightReport(result *PreflightResult, dbName string, verbose bool)
color, icon, reset, check.Name+":", check.Message))
if verbose && check.Details != "" {
sb.WriteString(fmt.Sprintf(" └─ %s\n", check.Details))
sb.WriteString(fmt.Sprintf(" +- %s\n", check.Details))
}
}
sb.WriteString(" ─────────────────────────────────────────────────────────────\n")
sb.WriteString(" --------------------------------------------------------------\n")
sb.WriteString("\n")
// Summary
if result.AllPassed {
if result.HasWarnings {
sb.WriteString(" ⚠️ All checks passed with warnings\n")
sb.WriteString(" [!] All checks passed with warnings\n")
sb.WriteString("\n")
sb.WriteString(" Ready to backup. Remove --dry-run to execute.\n")
} else {
sb.WriteString(" All checks passed\n")
sb.WriteString(" [OK] All checks passed\n")
sb.WriteString("\n")
sb.WriteString(" Ready to backup. Remove --dry-run to execute.\n")
}
} else {
sb.WriteString(fmt.Sprintf(" %d check(s) failed\n", result.FailureCount))
sb.WriteString(fmt.Sprintf(" [FAIL] %d check(s) failed\n", result.FailureCount))
sb.WriteString("\n")
sb.WriteString(" Fix the issues above before running backup.\n")
}
@@ -96,7 +96,7 @@ func FormatPreflightReportPlain(result *PreflightResult, dbName string) string {
status := fmt.Sprintf("[%s]", check.Status.String())
sb.WriteString(fmt.Sprintf(" %-10s %-25s %s\n", status, check.Name+":", check.Message))
if check.Details != "" {
sb.WriteString(fmt.Sprintf(" └─ %s\n", check.Details))
sb.WriteString(fmt.Sprintf(" +- %s\n", check.Details))
}
}

View File

@@ -223,11 +223,11 @@ func (r *DrillResult) IsSuccess() bool {
// Summary returns a human-readable summary of the drill
func (r *DrillResult) Summary() string {
status := " PASSED"
status := "[OK] PASSED"
if !r.Success {
status = " FAILED"
status = "[FAIL] FAILED"
} else if r.Status == StatusPartial {
status = "⚠️ PARTIAL"
status = "[WARN] PARTIAL"
}
return fmt.Sprintf("%s - %s (%.2fs) - %d tables, %d rows",

View File

@@ -41,20 +41,20 @@ func (e *Engine) Run(ctx context.Context, config *DrillConfig) (*DrillResult, er
TargetRTO: float64(config.MaxRestoreSeconds),
}
e.log.Info("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
e.log.Info(" 🧪 DR Drill: " + result.DrillID)
e.log.Info("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
e.log.Info("=====================================================")
e.log.Info(" [TEST] DR Drill: " + result.DrillID)
e.log.Info("=====================================================")
e.log.Info("")
// Cleanup function for error cases
var containerID string
cleanup := func() {
if containerID != "" && config.CleanupOnExit && (result.Success || !config.KeepOnFailure) {
e.log.Info("🗑️ Cleaning up container...")
e.log.Info("[DEL] Cleaning up container...")
e.docker.RemoveContainer(context.Background(), containerID)
} else if containerID != "" {
result.ContainerKept = true
e.log.Info("📦 Container kept for debugging: " + containerID)
e.log.Info("[PKG] Container kept for debugging: " + containerID)
}
}
defer cleanup()
@@ -88,7 +88,7 @@ func (e *Engine) Run(ctx context.Context, config *DrillConfig) (*DrillResult, er
}
containerID = container.ID
result.ContainerID = containerID
e.log.Info("📦 Container started: " + containerID[:12])
e.log.Info("[PKG] Container started: " + containerID[:12])
// Wait for container to be healthy
if err := e.docker.WaitForHealth(ctx, containerID, config.DatabaseType, config.ContainerTimeout); err != nil {
@@ -118,7 +118,7 @@ func (e *Engine) Run(ctx context.Context, config *DrillConfig) (*DrillResult, er
result.RestoreTime = time.Since(restoreStart).Seconds()
e.completePhase(&phase, fmt.Sprintf("Restored in %.2fs", result.RestoreTime))
result.Phases = append(result.Phases, phase)
e.log.Info(fmt.Sprintf(" Backup restored in %.2fs", result.RestoreTime))
e.log.Info(fmt.Sprintf("[OK] Backup restored in %.2fs", result.RestoreTime))
// Phase 4: Validate
phase = e.startPhase("Validate Database")
@@ -182,24 +182,24 @@ func (e *Engine) preflightChecks(ctx context.Context, config *DrillConfig) error
if err := e.docker.CheckDockerAvailable(ctx); err != nil {
return fmt.Errorf("docker not available: %w", err)
}
e.log.Info(" Docker is available")
e.log.Info("[OK] Docker is available")
// Check backup file exists
if _, err := os.Stat(config.BackupPath); err != nil {
return fmt.Errorf("backup file not found: %s", config.BackupPath)
}
e.log.Info(" Backup file exists: " + filepath.Base(config.BackupPath))
e.log.Info("[OK] Backup file exists: " + filepath.Base(config.BackupPath))
// Pull Docker image
image := config.ContainerImage
if image == "" {
image = GetDefaultImage(config.DatabaseType, "")
}
e.log.Info("⬇️ Pulling image: " + image)
e.log.Info("[DOWN] Pulling image: " + image)
if err := e.docker.PullImage(ctx, image); err != nil {
return fmt.Errorf("failed to pull image: %w", err)
}
e.log.Info(" Image ready: " + image)
e.log.Info("[OK] Image ready: " + image)
return nil
}
@@ -243,7 +243,7 @@ func (e *Engine) restoreBackup(ctx context.Context, config *DrillConfig, contain
backupName := filepath.Base(config.BackupPath)
containerBackupPath := "/tmp/" + backupName
e.log.Info("📁 Copying backup to container...")
e.log.Info("[DIR] Copying backup to container...")
if err := e.docker.CopyToContainer(ctx, containerID, config.BackupPath, containerBackupPath); err != nil {
return fmt.Errorf("failed to copy backup: %w", err)
}
@@ -256,7 +256,7 @@ func (e *Engine) restoreBackup(ctx context.Context, config *DrillConfig, contain
}
// Restore based on database type and format
e.log.Info("🔄 Restoring backup...")
e.log.Info("[EXEC] Restoring backup...")
return e.executeRestore(ctx, config, containerID, containerBackupPath, containerConfig)
}
@@ -366,13 +366,13 @@ func (e *Engine) validateDatabase(ctx context.Context, config *DrillConfig, resu
tables, err := validator.GetTableList(ctx)
if err == nil {
result.TableCount = len(tables)
e.log.Info(fmt.Sprintf("📊 Tables found: %d", result.TableCount))
e.log.Info(fmt.Sprintf("[STATS] Tables found: %d", result.TableCount))
}
totalRows, err := validator.GetTotalRowCount(ctx)
if err == nil {
result.TotalRows = totalRows
e.log.Info(fmt.Sprintf("📊 Total rows: %d", result.TotalRows))
e.log.Info(fmt.Sprintf("[STATS] Total rows: %d", result.TotalRows))
}
dbSize, err := validator.GetDatabaseSize(ctx, config.DatabaseName)
@@ -387,9 +387,9 @@ func (e *Engine) validateDatabase(ctx context.Context, config *DrillConfig, resu
result.CheckResults = append(result.CheckResults, tr)
if !tr.Success {
errorCount++
e.log.Warn(" " + tr.Message)
e.log.Warn("[FAIL] " + tr.Message)
} else {
e.log.Info(" " + tr.Message)
e.log.Info("[OK] " + tr.Message)
}
}
}
@@ -404,9 +404,9 @@ func (e *Engine) validateDatabase(ctx context.Context, config *DrillConfig, resu
totalQueryTime += qr.Duration
if !qr.Success {
errorCount++
e.log.Warn(fmt.Sprintf(" %s: %s", qr.Name, qr.Error))
e.log.Warn(fmt.Sprintf("[FAIL] %s: %s", qr.Name, qr.Error))
} else {
e.log.Info(fmt.Sprintf(" %s: %s (%.0fms)", qr.Name, qr.Result, qr.Duration))
e.log.Info(fmt.Sprintf("[OK] %s: %s (%.0fms)", qr.Name, qr.Result, qr.Duration))
}
}
if len(queryResults) > 0 {
@@ -421,9 +421,9 @@ func (e *Engine) validateDatabase(ctx context.Context, config *DrillConfig, resu
result.CheckResults = append(result.CheckResults, cr)
if !cr.Success {
errorCount++
e.log.Warn(" " + cr.Message)
e.log.Warn("[FAIL] " + cr.Message)
} else {
e.log.Info(" " + cr.Message)
e.log.Info("[OK] " + cr.Message)
}
}
}
@@ -433,7 +433,7 @@ func (e *Engine) validateDatabase(ctx context.Context, config *DrillConfig, resu
errorCount++
msg := fmt.Sprintf("Total rows (%d) below minimum (%d)", result.TotalRows, config.MinRowCount)
result.Warnings = append(result.Warnings, msg)
e.log.Warn("⚠️ " + msg)
e.log.Warn("[WARN] " + msg)
}
return errorCount
@@ -441,7 +441,7 @@ func (e *Engine) validateDatabase(ctx context.Context, config *DrillConfig, resu
// startPhase starts a new drill phase
func (e *Engine) startPhase(name string) DrillPhase {
e.log.Info("▶️ " + name)
e.log.Info("[RUN] " + name)
return DrillPhase{
Name: name,
Status: "running",
@@ -463,7 +463,7 @@ func (e *Engine) failPhase(phase *DrillPhase, message string) {
phase.Duration = phase.EndTime.Sub(phase.StartTime).Seconds()
phase.Status = "failed"
phase.Message = message
e.log.Error(" Phase failed: " + message)
e.log.Error("[FAIL] Phase failed: " + message)
}
// finalize completes the drill result
@@ -472,9 +472,9 @@ func (e *Engine) finalize(result *DrillResult) {
result.Duration = result.EndTime.Sub(result.StartTime).Seconds()
e.log.Info("")
e.log.Info("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
e.log.Info("=====================================================")
e.log.Info(" " + result.Summary())
e.log.Info("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
e.log.Info("=====================================================")
if result.Success {
e.log.Info(fmt.Sprintf(" RTO: %.2fs (target: %.0fs) %s",
@@ -484,9 +484,9 @@ func (e *Engine) finalize(result *DrillResult) {
func boolIcon(b bool) string {
if b {
return ""
return "[OK]"
}
return ""
return "[FAIL]"
}
// Cleanup removes drill resources
@@ -498,7 +498,7 @@ func (e *Engine) Cleanup(ctx context.Context, drillID string) error {
for _, c := range containers {
if strings.Contains(c.Name, drillID) || (drillID == "" && strings.HasPrefix(c.Name, "drill_")) {
e.log.Info("🗑️ Removing container: " + c.Name)
e.log.Info("[DEL] Removing container: " + c.Name)
if err := e.docker.RemoveContainer(ctx, c.ID); err != nil {
e.log.Warn("Failed to remove container", "id", c.ID, "error", err)
}

View File

@@ -8,7 +8,7 @@ import (
func TestEncryptDecrypt(t *testing.T) {
// Test data
original := []byte("This is a secret database backup that needs encryption! 🔒")
original := []byte("This is a secret database backup that needs encryption! [LOCK]")
// Test with passphrase
t.Run("Passphrase", func(t *testing.T) {
@@ -57,7 +57,7 @@ func TestEncryptDecrypt(t *testing.T) {
string(original), string(decrypted))
}
t.Log(" Encryption/decryption successful")
t.Log("[OK] Encryption/decryption successful")
})
// Test with direct key
@@ -102,7 +102,7 @@ func TestEncryptDecrypt(t *testing.T) {
t.Errorf("Decrypted data doesn't match original")
}
t.Log(" Direct key encryption/decryption successful")
t.Log("[OK] Direct key encryption/decryption successful")
})
// Test wrong password
@@ -133,7 +133,7 @@ func TestEncryptDecrypt(t *testing.T) {
t.Error("Expected decryption to fail with wrong password, but it succeeded")
}
t.Logf(" Wrong password correctly rejected: %v", err)
t.Logf("[OK] Wrong password correctly rejected: %v", err)
})
}
@@ -183,7 +183,7 @@ func TestLargeData(t *testing.T) {
t.Errorf("Large data decryption failed")
}
t.Log(" Large data encryption/decryption successful")
t.Log("[OK] Large data encryption/decryption successful")
}
func TestKeyGeneration(t *testing.T) {
@@ -207,7 +207,7 @@ func TestKeyGeneration(t *testing.T) {
t.Error("Generated keys are identical - randomness broken!")
}
t.Log(" Key generation successful")
t.Log("[OK] Key generation successful")
}
func TestKeyDerivation(t *testing.T) {
@@ -230,5 +230,5 @@ func TestKeyDerivation(t *testing.T) {
t.Error("Different salts produced same key")
}
t.Log(" Key derivation successful")
t.Log("[OK] Key derivation successful")
}

View File

@@ -658,9 +658,9 @@ func (i *Installer) printNextSteps(opts InstallOptions) {
serviceName := strings.Replace(timerName, ".timer", ".service", 1)
fmt.Println()
fmt.Println(" Installation successful!")
fmt.Println("[OK] Installation successful!")
fmt.Println()
fmt.Println("📋 Next steps:")
fmt.Println("[NEXT] Next steps:")
fmt.Println()
fmt.Printf(" 1. Edit configuration: sudo nano %s\n", opts.ConfigPath)
fmt.Printf(" 2. Set credentials: sudo nano /etc/dbbackup/env.d/%s.conf\n", opts.Instance)
@@ -668,12 +668,12 @@ func (i *Installer) printNextSteps(opts InstallOptions) {
fmt.Printf(" 4. Verify timer status: sudo systemctl status %s\n", timerName)
fmt.Printf(" 5. Run backup manually: sudo systemctl start %s\n", serviceName)
fmt.Println()
fmt.Println("📊 View backup logs:")
fmt.Println("[LOGS] View backup logs:")
fmt.Printf(" journalctl -u %s -f\n", serviceName)
fmt.Println()
if opts.WithMetrics {
fmt.Println("📈 Prometheus metrics:")
fmt.Println("[METRICS] Prometheus metrics:")
fmt.Printf(" curl http://localhost:%d/metrics\n", opts.MetricsPort)
fmt.Println()
}

View File

@@ -202,9 +202,9 @@ func (b *Batcher) formatSummaryDigest(events []*Event, success, failure, dbCount
func (b *Batcher) formatCompactDigest(events []*Event, success, failure int) string {
if failure > 0 {
return fmt.Sprintf("⚠️ %d/%d operations failed", failure, len(events))
return fmt.Sprintf("[WARN] %d/%d operations failed", failure, len(events))
}
return fmt.Sprintf(" All %d operations successful", success)
return fmt.Sprintf("[OK] All %d operations successful", success)
}
func (b *Batcher) formatDetailedDigest(events []*Event) string {
@@ -215,9 +215,9 @@ func (b *Batcher) formatDetailedDigest(events []*Event) string {
icon := "•"
switch e.Severity {
case SeverityError, SeverityCritical:
icon = ""
icon = "[FAIL]"
case SeverityWarning:
icon = "⚠️"
icon = "[WARN]"
}
msg += fmt.Sprintf("%s [%s] %s: %s\n",

View File

@@ -183,43 +183,43 @@ func DefaultConfig() Config {
// FormatEventSubject generates a subject line for notifications
func FormatEventSubject(event *Event) string {
icon := ""
icon := "[INFO]"
switch event.Severity {
case SeverityWarning:
icon = "⚠️"
icon = "[WARN]"
case SeverityError, SeverityCritical:
icon = ""
icon = "[FAIL]"
}
verb := "Event"
switch event.Type {
case EventBackupStarted:
verb = "Backup Started"
icon = "🔄"
icon = "[EXEC]"
case EventBackupCompleted:
verb = "Backup Completed"
icon = ""
icon = "[OK]"
case EventBackupFailed:
verb = "Backup Failed"
icon = ""
icon = "[FAIL]"
case EventRestoreStarted:
verb = "Restore Started"
icon = "🔄"
icon = "[EXEC]"
case EventRestoreCompleted:
verb = "Restore Completed"
icon = ""
icon = "[OK]"
case EventRestoreFailed:
verb = "Restore Failed"
icon = ""
icon = "[FAIL]"
case EventCleanupCompleted:
verb = "Cleanup Completed"
icon = "🗑️"
icon = "[DEL]"
case EventVerifyCompleted:
verb = "Verification Passed"
icon = ""
icon = "[OK]"
case EventVerifyFailed:
verb = "Verification Failed"
icon = ""
icon = "[FAIL]"
case EventPITRRecovery:
verb = "PITR Recovery"
icon = "⏪"

View File

@@ -30,52 +30,52 @@ type Templates struct {
func DefaultTemplates() map[EventType]Templates {
return map[EventType]Templates{
EventBackupStarted: {
Subject: "🔄 Backup Started: {{.Database}} on {{.Hostname}}",
Subject: "[EXEC] Backup Started: {{.Database}} on {{.Hostname}}",
TextBody: backupStartedText,
HTMLBody: backupStartedHTML,
},
EventBackupCompleted: {
Subject: " Backup Completed: {{.Database}} on {{.Hostname}}",
Subject: "[OK] Backup Completed: {{.Database}} on {{.Hostname}}",
TextBody: backupCompletedText,
HTMLBody: backupCompletedHTML,
},
EventBackupFailed: {
Subject: " Backup FAILED: {{.Database}} on {{.Hostname}}",
Subject: "[FAIL] Backup FAILED: {{.Database}} on {{.Hostname}}",
TextBody: backupFailedText,
HTMLBody: backupFailedHTML,
},
EventRestoreStarted: {
Subject: "🔄 Restore Started: {{.Database}} on {{.Hostname}}",
Subject: "[EXEC] Restore Started: {{.Database}} on {{.Hostname}}",
TextBody: restoreStartedText,
HTMLBody: restoreStartedHTML,
},
EventRestoreCompleted: {
Subject: " Restore Completed: {{.Database}} on {{.Hostname}}",
Subject: "[OK] Restore Completed: {{.Database}} on {{.Hostname}}",
TextBody: restoreCompletedText,
HTMLBody: restoreCompletedHTML,
},
EventRestoreFailed: {
Subject: " Restore FAILED: {{.Database}} on {{.Hostname}}",
Subject: "[FAIL] Restore FAILED: {{.Database}} on {{.Hostname}}",
TextBody: restoreFailedText,
HTMLBody: restoreFailedHTML,
},
EventVerificationPassed: {
Subject: " Verification Passed: {{.Database}}",
Subject: "[OK] Verification Passed: {{.Database}}",
TextBody: verificationPassedText,
HTMLBody: verificationPassedHTML,
},
EventVerificationFailed: {
Subject: " Verification FAILED: {{.Database}}",
Subject: "[FAIL] Verification FAILED: {{.Database}}",
TextBody: verificationFailedText,
HTMLBody: verificationFailedHTML,
},
EventDRDrillPassed: {
Subject: " DR Drill Passed: {{.Database}}",
Subject: "[OK] DR Drill Passed: {{.Database}}",
TextBody: drDrillPassedText,
HTMLBody: drDrillPassedHTML,
},
EventDRDrillFailed: {
Subject: " DR Drill FAILED: {{.Database}}",
Subject: "[FAIL] DR Drill FAILED: {{.Database}}",
TextBody: drDrillFailedText,
HTMLBody: drDrillFailedHTML,
},
@@ -95,7 +95,7 @@ Started At: {{formatTime .Timestamp}}
const backupStartedHTML = `
<div style="font-family: Arial, sans-serif; padding: 20px;">
<h2 style="color: #3498db;">🔄 Backup Started</h2>
<h2 style="color: #3498db;">[EXEC] Backup Started</h2>
<table style="border-collapse: collapse; width: 100%; max-width: 600px;">
<tr><td style="padding: 8px; font-weight: bold;">Database:</td><td style="padding: 8px;">{{.Database}}</td></tr>
<tr><td style="padding: 8px; font-weight: bold;">Hostname:</td><td style="padding: 8px;">{{.Hostname}}</td></tr>
@@ -121,7 +121,7 @@ Completed: {{formatTime .Timestamp}}
const backupCompletedHTML = `
<div style="font-family: Arial, sans-serif; padding: 20px;">
<h2 style="color: #27ae60;"> Backup Completed</h2>
<h2 style="color: #27ae60;">[OK] Backup Completed</h2>
<table style="border-collapse: collapse; width: 100%; max-width: 600px;">
<tr><td style="padding: 8px; font-weight: bold;">Database:</td><td style="padding: 8px;">{{.Database}}</td></tr>
<tr><td style="padding: 8px; font-weight: bold;">Hostname:</td><td style="padding: 8px;">{{.Hostname}}</td></tr>
@@ -137,7 +137,7 @@ const backupCompletedHTML = `
`
const backupFailedText = `
⚠️ BACKUP FAILED ⚠️
[WARN] BACKUP FAILED [WARN]
Database: {{.Database}}
Hostname: {{.Hostname}}
@@ -152,7 +152,7 @@ Please investigate immediately.
const backupFailedHTML = `
<div style="font-family: Arial, sans-serif; padding: 20px;">
<h2 style="color: #e74c3c;"> Backup FAILED</h2>
<h2 style="color: #e74c3c;">[FAIL] Backup FAILED</h2>
<table style="border-collapse: collapse; width: 100%; max-width: 600px;">
<tr><td style="padding: 8px; font-weight: bold;">Database:</td><td style="padding: 8px;">{{.Database}}</td></tr>
<tr><td style="padding: 8px; font-weight: bold;">Hostname:</td><td style="padding: 8px;">{{.Hostname}}</td></tr>
@@ -176,7 +176,7 @@ Started At: {{formatTime .Timestamp}}
const restoreStartedHTML = `
<div style="font-family: Arial, sans-serif; padding: 20px;">
<h2 style="color: #3498db;">🔄 Restore Started</h2>
<h2 style="color: #3498db;">[EXEC] Restore Started</h2>
<table style="border-collapse: collapse; width: 100%; max-width: 600px;">
<tr><td style="padding: 8px; font-weight: bold;">Database:</td><td style="padding: 8px;">{{.Database}}</td></tr>
<tr><td style="padding: 8px; font-weight: bold;">Hostname:</td><td style="padding: 8px;">{{.Hostname}}</td></tr>
@@ -200,7 +200,7 @@ Completed: {{formatTime .Timestamp}}
const restoreCompletedHTML = `
<div style="font-family: Arial, sans-serif; padding: 20px;">
<h2 style="color: #27ae60;"> Restore Completed</h2>
<h2 style="color: #27ae60;">[OK] Restore Completed</h2>
<table style="border-collapse: collapse; width: 100%; max-width: 600px;">
<tr><td style="padding: 8px; font-weight: bold;">Database:</td><td style="padding: 8px;">{{.Database}}</td></tr>
<tr><td style="padding: 8px; font-weight: bold;">Hostname:</td><td style="padding: 8px;">{{.Hostname}}</td></tr>
@@ -214,7 +214,7 @@ const restoreCompletedHTML = `
`
const restoreFailedText = `
⚠️ RESTORE FAILED ⚠️
[WARN] RESTORE FAILED [WARN]
Database: {{.Database}}
Hostname: {{.Hostname}}
@@ -229,7 +229,7 @@ Please investigate immediately.
const restoreFailedHTML = `
<div style="font-family: Arial, sans-serif; padding: 20px;">
<h2 style="color: #e74c3c;"> Restore FAILED</h2>
<h2 style="color: #e74c3c;">[FAIL] Restore FAILED</h2>
<table style="border-collapse: collapse; width: 100%; max-width: 600px;">
<tr><td style="padding: 8px; font-weight: bold;">Database:</td><td style="padding: 8px;">{{.Database}}</td></tr>
<tr><td style="padding: 8px; font-weight: bold;">Hostname:</td><td style="padding: 8px;">{{.Hostname}}</td></tr>
@@ -255,7 +255,7 @@ Verified: {{formatTime .Timestamp}}
const verificationPassedHTML = `
<div style="font-family: Arial, sans-serif; padding: 20px;">
<h2 style="color: #27ae60;"> Verification Passed</h2>
<h2 style="color: #27ae60;">[OK] Verification Passed</h2>
<table style="border-collapse: collapse; width: 100%; max-width: 600px;">
<tr><td style="padding: 8px; font-weight: bold;">Database:</td><td style="padding: 8px;">{{.Database}}</td></tr>
<tr><td style="padding: 8px; font-weight: bold;">Hostname:</td><td style="padding: 8px;">{{.Hostname}}</td></tr>
@@ -269,7 +269,7 @@ const verificationPassedHTML = `
`
const verificationFailedText = `
⚠️ VERIFICATION FAILED ⚠️
[WARN] VERIFICATION FAILED [WARN]
Database: {{.Database}}
Hostname: {{.Hostname}}
@@ -284,7 +284,7 @@ Backup integrity may be compromised. Please investigate.
const verificationFailedHTML = `
<div style="font-family: Arial, sans-serif; padding: 20px;">
<h2 style="color: #e74c3c;"> Verification FAILED</h2>
<h2 style="color: #e74c3c;">[FAIL] Verification FAILED</h2>
<table style="border-collapse: collapse; width: 100%; max-width: 600px;">
<tr><td style="padding: 8px; font-weight: bold;">Database:</td><td style="padding: 8px;">{{.Database}}</td></tr>
<tr><td style="padding: 8px; font-weight: bold;">Hostname:</td><td style="padding: 8px;">{{.Hostname}}</td></tr>
@@ -314,7 +314,7 @@ Backup restore capability verified.
const drDrillPassedHTML = `
<div style="font-family: Arial, sans-serif; padding: 20px;">
<h2 style="color: #27ae60;"> DR Drill Passed</h2>
<h2 style="color: #27ae60;">[OK] DR Drill Passed</h2>
<table style="border-collapse: collapse; width: 100%; max-width: 600px;">
<tr><td style="padding: 8px; font-weight: bold;">Database:</td><td style="padding: 8px;">{{.Database}}</td></tr>
<tr><td style="padding: 8px; font-weight: bold;">Hostname:</td><td style="padding: 8px;">{{.Hostname}}</td></tr>
@@ -326,12 +326,12 @@ const drDrillPassedHTML = `
{{end}}
</table>
{{if .Message}}<p style="margin-top: 20px; color: #27ae60;">{{.Message}}</p>{{end}}
<p style="margin-top: 20px; color: #27ae60;"> Backup restore capability verified</p>
<p style="margin-top: 20px; color: #27ae60;">[OK] Backup restore capability verified</p>
</div>
`
const drDrillFailedText = `
⚠️ DR DRILL FAILED ⚠️
[WARN] DR DRILL FAILED [WARN]
Database: {{.Database}}
Hostname: {{.Hostname}}
@@ -346,7 +346,7 @@ Backup may not be restorable. Please investigate immediately.
const drDrillFailedHTML = `
<div style="font-family: Arial, sans-serif; padding: 20px;">
<h2 style="color: #e74c3c;"> DR Drill FAILED</h2>
<h2 style="color: #e74c3c;">[FAIL] DR Drill FAILED</h2>
<table style="border-collapse: collapse; width: 100%; max-width: 600px;">
<tr><td style="padding: 8px; font-weight: bold;">Database:</td><td style="padding: 8px;">{{.Database}}</td></tr>
<tr><td style="padding: 8px; font-weight: bold;">Hostname:</td><td style="padding: 8px;">{{.Hostname}}</td></tr>

View File

@@ -43,9 +43,9 @@ type RestoreOptions struct {
// RestorePointInTime performs a Point-in-Time Recovery
func (ro *RestoreOrchestrator) RestorePointInTime(ctx context.Context, opts *RestoreOptions) error {
ro.log.Info("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
ro.log.Info("=====================================================")
ro.log.Info(" Point-in-Time Recovery (PITR)")
ro.log.Info("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
ro.log.Info("=====================================================")
ro.log.Info("")
ro.log.Info("Target:", "summary", opts.Target.Summary())
ro.log.Info("Base Backup:", "path", opts.BaseBackupPath)
@@ -91,11 +91,11 @@ func (ro *RestoreOrchestrator) RestorePointInTime(ctx context.Context, opts *Res
return fmt.Errorf("failed to generate recovery configuration: %w", err)
}
ro.log.Info(" Recovery configuration generated successfully")
ro.log.Info("[OK] Recovery configuration generated successfully")
ro.log.Info("")
ro.log.Info("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
ro.log.Info("=====================================================")
ro.log.Info(" Next Steps:")
ro.log.Info("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
ro.log.Info("=====================================================")
ro.log.Info("")
ro.log.Info("1. Start PostgreSQL to begin recovery:")
ro.log.Info(fmt.Sprintf(" pg_ctl -D %s start", opts.TargetDataDir))
@@ -192,7 +192,7 @@ func (ro *RestoreOrchestrator) validateInputs(opts *RestoreOptions) error {
}
}
ro.log.Info(" Validation passed")
ro.log.Info("[OK] Validation passed")
return nil
}
@@ -238,7 +238,7 @@ func (ro *RestoreOrchestrator) extractTarGzBackup(ctx context.Context, source, d
return fmt.Errorf("tar extraction failed: %w", err)
}
ro.log.Info(" Base backup extracted successfully")
ro.log.Info("[OK] Base backup extracted successfully")
return nil
}
@@ -254,7 +254,7 @@ func (ro *RestoreOrchestrator) extractTarBackup(ctx context.Context, source, des
return fmt.Errorf("tar extraction failed: %w", err)
}
ro.log.Info(" Base backup extracted successfully")
ro.log.Info("[OK] Base backup extracted successfully")
return nil
}
@@ -270,7 +270,7 @@ func (ro *RestoreOrchestrator) copyDirectoryBackup(ctx context.Context, source,
return fmt.Errorf("directory copy failed: %w", err)
}
ro.log.Info(" Base backup copied successfully")
ro.log.Info("[OK] Base backup copied successfully")
return nil
}
@@ -291,7 +291,7 @@ func (ro *RestoreOrchestrator) startPostgreSQL(ctx context.Context, opts *Restor
return fmt.Errorf("pg_ctl start failed: %w", err)
}
ro.log.Info(" PostgreSQL started successfully")
ro.log.Info("[OK] PostgreSQL started successfully")
ro.log.Info("PostgreSQL is now performing recovery...")
return nil
}
@@ -320,7 +320,7 @@ func (ro *RestoreOrchestrator) monitorRecovery(ctx context.Context, opts *Restor
// Check if recovery is complete by looking for postmaster.pid
pidFile := filepath.Join(opts.TargetDataDir, "postmaster.pid")
if _, err := os.Stat(pidFile); err == nil {
ro.log.Info(" PostgreSQL is running")
ro.log.Info("[OK] PostgreSQL is running")
// Check if recovery files still exist
recoverySignal := filepath.Join(opts.TargetDataDir, "recovery.signal")
@@ -328,7 +328,7 @@ func (ro *RestoreOrchestrator) monitorRecovery(ctx context.Context, opts *Restor
if _, err := os.Stat(recoverySignal); os.IsNotExist(err) {
if _, err := os.Stat(recoveryConf); os.IsNotExist(err) {
ro.log.Info(" Recovery completed - PostgreSQL promoted to primary")
ro.log.Info("[OK] Recovery completed - PostgreSQL promoted to primary")
return nil
}
}

View File

@@ -256,7 +256,7 @@ func (ot *OperationTracker) Complete(message string) {
// Complete visual indicator
if ot.reporter.indicator != nil {
ot.reporter.indicator.Complete(fmt.Sprintf(" %s", message))
ot.reporter.indicator.Complete(fmt.Sprintf("[OK] %s", message))
}
// Log completion with duration
@@ -286,7 +286,7 @@ func (ot *OperationTracker) Fail(err error) {
// Fail visual indicator
if ot.reporter.indicator != nil {
ot.reporter.indicator.Fail(fmt.Sprintf(" %s", err.Error()))
ot.reporter.indicator.Fail(fmt.Sprintf("[FAIL] %s", err.Error()))
}
// Log failure
@@ -427,7 +427,7 @@ type OperationSummary struct {
// FormatSummary returns a formatted string representation of the summary
func (os *OperationSummary) FormatSummary() string {
return fmt.Sprintf(
"📊 Operations Summary:\n"+
"[STATS] Operations Summary:\n"+
" Total: %d | Completed: %d | Failed: %d | Running: %d\n"+
" Total Duration: %s",
os.TotalOperations,

View File

@@ -92,13 +92,13 @@ func (s *Spinner) Update(message string) {
// Complete stops the spinner with a success message
func (s *Spinner) Complete(message string) {
s.Stop()
fmt.Fprintf(s.writer, "\n %s\n", message)
fmt.Fprintf(s.writer, "\n[OK] %s\n", message)
}
// Fail stops the spinner with a failure message
func (s *Spinner) Fail(message string) {
s.Stop()
fmt.Fprintf(s.writer, "\n %s\n", message)
fmt.Fprintf(s.writer, "\n[FAIL] %s\n", message)
}
// Stop stops the spinner
@@ -167,13 +167,13 @@ func (d *Dots) Update(message string) {
// Complete stops the dots with a success message
func (d *Dots) Complete(message string) {
d.Stop()
fmt.Fprintf(d.writer, " %s\n", message)
fmt.Fprintf(d.writer, " [OK] %s\n", message)
}
// Fail stops the dots with a failure message
func (d *Dots) Fail(message string) {
d.Stop()
fmt.Fprintf(d.writer, " %s\n", message)
fmt.Fprintf(d.writer, " [FAIL] %s\n", message)
}
// Stop stops the dots indicator
@@ -239,14 +239,14 @@ func (p *ProgressBar) Complete(message string) {
p.current = p.total
p.message = message
p.render()
fmt.Fprintf(p.writer, " %s\n", message)
fmt.Fprintf(p.writer, " [OK] %s\n", message)
p.Stop()
}
// Fail stops the progress bar with failure
func (p *ProgressBar) Fail(message string) {
p.render()
fmt.Fprintf(p.writer, " %s\n", message)
fmt.Fprintf(p.writer, " [FAIL] %s\n", message)
p.Stop()
}
@@ -298,12 +298,12 @@ func (s *Static) Update(message string) {
// Complete shows completion message
func (s *Static) Complete(message string) {
fmt.Fprintf(s.writer, " %s\n", message)
fmt.Fprintf(s.writer, " [OK] %s\n", message)
}
// Fail shows failure message
func (s *Static) Fail(message string) {
fmt.Fprintf(s.writer, " %s\n", message)
fmt.Fprintf(s.writer, " [FAIL] %s\n", message)
}
// Stop does nothing for static indicator
@@ -359,7 +359,7 @@ func (l *LineByLine) Start(message string) {
if l.estimator != nil {
displayMsg = l.estimator.GetFullStatus(message)
}
fmt.Fprintf(l.writer, "\n🔄 %s\n", displayMsg)
fmt.Fprintf(l.writer, "\n[SYNC] %s\n", displayMsg)
}
// Update shows an update message
@@ -380,12 +380,12 @@ func (l *LineByLine) SetEstimator(estimator *ETAEstimator) {
// Complete shows completion message
func (l *LineByLine) Complete(message string) {
fmt.Fprintf(l.writer, " %s\n\n", message)
fmt.Fprintf(l.writer, "[OK] %s\n\n", message)
}
// Fail shows failure message
func (l *LineByLine) Fail(message string) {
fmt.Fprintf(l.writer, " %s\n\n", message)
fmt.Fprintf(l.writer, "[FAIL] %s\n\n", message)
}
// Stop does nothing for line-by-line (no cleanup needed)
@@ -396,7 +396,7 @@ func (l *LineByLine) Stop() {
// Light indicator methods - minimal output
func (l *Light) Start(message string) {
if !l.silent {
fmt.Fprintf(l.writer, " %s\n", message)
fmt.Fprintf(l.writer, "> %s\n", message)
}
}
@@ -408,13 +408,13 @@ func (l *Light) Update(message string) {
func (l *Light) Complete(message string) {
if !l.silent {
fmt.Fprintf(l.writer, " %s\n", message)
fmt.Fprintf(l.writer, "[OK] %s\n", message)
}
}
func (l *Light) Fail(message string) {
if !l.silent {
fmt.Fprintf(l.writer, " %s\n", message)
fmt.Fprintf(l.writer, "[FAIL] %s\n", message)
}
}

View File

@@ -296,11 +296,11 @@ func generateID() string {
func StatusIcon(s ComplianceStatus) string {
switch s {
case StatusCompliant:
return ""
return "[OK]"
case StatusNonCompliant:
return ""
return "[FAIL]"
case StatusPartial:
return "⚠️"
return "[WARN]"
case StatusNotApplicable:
return ""
default:

View File

@@ -714,7 +714,7 @@ func (d *Diagnoser) DiagnoseClusterDumps(archivePath, tempDir string) ([]*Diagno
// PrintDiagnosis outputs a human-readable diagnosis report
func (d *Diagnoser) PrintDiagnosis(result *DiagnoseResult) {
fmt.Println("\n" + strings.Repeat("=", 70))
fmt.Printf("📋 DIAGNOSIS: %s\n", result.FileName)
fmt.Printf("[DIAG] DIAGNOSIS: %s\n", result.FileName)
fmt.Println(strings.Repeat("=", 70))
// Basic info
@@ -724,69 +724,69 @@ func (d *Diagnoser) PrintDiagnosis(result *DiagnoseResult) {
// Status
if result.IsValid {
fmt.Println("\n STATUS: VALID")
fmt.Println("\n[OK] STATUS: VALID")
} else {
fmt.Println("\n STATUS: INVALID")
fmt.Println("\n[FAIL] STATUS: INVALID")
}
if result.IsTruncated {
fmt.Println("⚠️ TRUNCATED: Yes - file appears incomplete")
fmt.Println("[WARN] TRUNCATED: Yes - file appears incomplete")
}
if result.IsCorrupted {
fmt.Println("⚠️ CORRUPTED: Yes - file structure is damaged")
fmt.Println("[WARN] CORRUPTED: Yes - file structure is damaged")
}
// Details
if result.Details != nil {
fmt.Println("\n📊 DETAILS:")
fmt.Println("\n[DETAILS]:")
if result.Details.HasPGDMPSignature {
fmt.Println(" Has PGDMP signature (PostgreSQL custom format)")
fmt.Println(" [+] Has PGDMP signature (PostgreSQL custom format)")
}
if result.Details.HasSQLHeader {
fmt.Println(" Has PostgreSQL SQL header")
fmt.Println(" [+] Has PostgreSQL SQL header")
}
if result.Details.GzipValid {
fmt.Println(" Gzip compression valid")
fmt.Println(" [+] Gzip compression valid")
}
if result.Details.PgRestoreListable {
fmt.Printf(" pg_restore can list contents (%d tables)\n", result.Details.TableCount)
fmt.Printf(" [+] pg_restore can list contents (%d tables)\n", result.Details.TableCount)
}
if result.Details.CopyBlockCount > 0 {
fmt.Printf(" Contains %d COPY blocks\n", result.Details.CopyBlockCount)
fmt.Printf(" [-] Contains %d COPY blocks\n", result.Details.CopyBlockCount)
}
if result.Details.UnterminatedCopy {
fmt.Printf(" Unterminated COPY block: %s (line %d)\n",
fmt.Printf(" [-] Unterminated COPY block: %s (line %d)\n",
result.Details.LastCopyTable, result.Details.LastCopyLineNumber)
}
if result.Details.ProperlyTerminated {
fmt.Println(" All COPY blocks properly terminated")
fmt.Println(" [+] All COPY blocks properly terminated")
}
if result.Details.ExpandedSize > 0 {
fmt.Printf(" Expanded size: %s (ratio: %.1fx)\n",
fmt.Printf(" [-] Expanded size: %s (ratio: %.1fx)\n",
formatBytes(result.Details.ExpandedSize), result.Details.CompressionRatio)
}
}
// Errors
if len(result.Errors) > 0 {
fmt.Println("\nERRORS:")
fmt.Println("\n[ERRORS]:")
for _, e := range result.Errors {
fmt.Printf(" %s\n", e)
fmt.Printf(" - %s\n", e)
}
}
// Warnings
if len(result.Warnings) > 0 {
fmt.Println("\n⚠️ WARNINGS:")
fmt.Println("\n[WARNINGS]:")
for _, w := range result.Warnings {
fmt.Printf(" %s\n", w)
fmt.Printf(" - %s\n", w)
}
}
// Recommendations
if !result.IsValid {
fmt.Println("\n💡 RECOMMENDATIONS:")
fmt.Println("\n[HINT] RECOMMENDATIONS:")
if result.IsTruncated {
fmt.Println(" 1. Re-run the backup process for this database")
fmt.Println(" 2. Check disk space on backup server during backup")

View File

@@ -127,7 +127,7 @@ func (e *Engine) RestoreSingle(ctx context.Context, archivePath, targetDB string
e.log.Warn("Checksum verification failed", "error", checksumErr)
e.log.Warn("Continuing restore without checksum verification (use with caution)")
} else {
e.log.Info(" Archive checksum verified successfully")
e.log.Info("[OK] Archive checksum verified successfully")
}
// Detect archive format
@@ -461,7 +461,7 @@ func (e *Engine) executeRestoreCommandWithContext(ctx context.Context, cmdArgs [
e.log.Warn("Failed to save debug log", "error", saveErr)
} else {
e.log.Info("Debug log saved", "path", e.debugLogPath)
fmt.Printf("\n📋 Detailed error report saved to: %s\n", e.debugLogPath)
fmt.Printf("\n[LOG] Detailed error report saved to: %s\n", e.debugLogPath)
}
}
}
@@ -612,7 +612,7 @@ func (e *Engine) previewRestore(archivePath, targetDB string, format ArchiveForm
fmt.Printf(" 1. Execute: mysql %s < %s\n", targetDB, archivePath)
}
fmt.Println("\n⚠️ WARNING: This will restore data to the target database.")
fmt.Println("\n[WARN] WARNING: This will restore data to the target database.")
fmt.Println(" Existing data may be overwritten or merged.")
fmt.Println("\nTo execute this restore, add the --confirm flag.")
fmt.Println(strings.Repeat("=", 60) + "\n")
@@ -643,7 +643,7 @@ func (e *Engine) RestoreCluster(ctx context.Context, archivePath string) error {
e.log.Warn("Checksum verification failed", "error", checksumErr)
e.log.Warn("Continuing restore without checksum verification (use with caution)")
} else {
e.log.Info(" Cluster archive checksum verified successfully")
e.log.Info("[OK] Cluster archive checksum verified successfully")
}
format := DetectArchiveFormat(archivePath)
@@ -703,7 +703,7 @@ func (e *Engine) RestoreCluster(ctx context.Context, archivePath string) error {
if !isSuperuser {
e.log.Warn("Current user is not a superuser - database ownership may not be fully restored")
e.progress.Update("⚠️ Warning: Non-superuser - ownership restoration limited")
e.progress.Update("[WARN] Warning: Non-superuser - ownership restoration limited")
time.Sleep(2 * time.Second) // Give user time to see warning
} else {
e.log.Info("Superuser privileges confirmed - full ownership restoration enabled")
@@ -835,7 +835,7 @@ func (e *Engine) RestoreCluster(ctx context.Context, archivePath string) error {
e.log.Warn("Large objects detected in dump files - reducing parallelism to avoid lock contention",
"original_parallelism", parallelism,
"adjusted_parallelism", 1)
e.progress.Update("⚠️ Large objects detected - using sequential restore to avoid lock conflicts")
e.progress.Update("[WARN] Large objects detected - using sequential restore to avoid lock conflicts")
time.Sleep(2 * time.Second) // Give user time to see warning
parallelism = 1
}
@@ -1339,7 +1339,7 @@ func (e *Engine) previewClusterRestore(archivePath string) error {
fmt.Println(" 3. Restore all databases found in archive")
fmt.Println(" 4. Cleanup temporary files")
fmt.Println("\n⚠️ WARNING: This will restore multiple databases.")
fmt.Println("\n[WARN] WARNING: This will restore multiple databases.")
fmt.Println(" Existing databases may be overwritten or merged.")
fmt.Println("\nTo execute this restore, add the --confirm flag.")
fmt.Println(strings.Repeat("=", 60) + "\n")

View File

@@ -397,20 +397,20 @@ func (ec *ErrorCollector) SaveReport(report *RestoreErrorReport, outputPath stri
// PrintReport prints a human-readable summary of the error report
func (ec *ErrorCollector) PrintReport(report *RestoreErrorReport) {
fmt.Println()
fmt.Println(strings.Repeat("", 70))
fmt.Println(" 🔴 RESTORE ERROR REPORT")
fmt.Println(strings.Repeat("", 70))
fmt.Println(strings.Repeat("=", 70))
fmt.Println(" [ERROR] RESTORE ERROR REPORT")
fmt.Println(strings.Repeat("=", 70))
fmt.Printf("\n📅 Timestamp: %s\n", report.Timestamp.Format("2006-01-02 15:04:05"))
fmt.Printf("📦 Archive: %s\n", filepath.Base(report.ArchivePath))
fmt.Printf("📊 Format: %s\n", report.ArchiveFormat)
fmt.Printf("🎯 Target DB: %s\n", report.TargetDB)
fmt.Printf("⚠️ Exit Code: %d\n", report.ExitCode)
fmt.Printf(" Total Errors: %d\n", report.TotalErrors)
fmt.Printf("\n[TIME] Timestamp: %s\n", report.Timestamp.Format("2006-01-02 15:04:05"))
fmt.Printf("[FILE] Archive: %s\n", filepath.Base(report.ArchivePath))
fmt.Printf("[FMT] Format: %s\n", report.ArchiveFormat)
fmt.Printf("[TGT] Target DB: %s\n", report.TargetDB)
fmt.Printf("[CODE] Exit Code: %d\n", report.ExitCode)
fmt.Printf("[ERR] Total Errors: %d\n", report.TotalErrors)
fmt.Println("\n" + strings.Repeat("", 70))
fmt.Println("\n" + strings.Repeat("-", 70))
fmt.Println("ERROR DETAILS:")
fmt.Println(strings.Repeat("", 70))
fmt.Println(strings.Repeat("-", 70))
fmt.Printf("\nType: %s\n", report.ErrorType)
fmt.Printf("Message: %s\n", report.ErrorMessage)
@@ -420,9 +420,9 @@ func (ec *ErrorCollector) PrintReport(report *RestoreErrorReport) {
// Show failure context
if report.FailureContext != nil && report.FailureContext.FailedLine > 0 {
fmt.Println("\n" + strings.Repeat("", 70))
fmt.Println("\n" + strings.Repeat("-", 70))
fmt.Println("FAILURE CONTEXT:")
fmt.Println(strings.Repeat("", 70))
fmt.Println(strings.Repeat("-", 70))
fmt.Printf("\nFailed at line: %d\n", report.FailureContext.FailedLine)
if report.FailureContext.InCopyBlock {
@@ -439,9 +439,9 @@ func (ec *ErrorCollector) PrintReport(report *RestoreErrorReport) {
// Show first few errors
if len(report.FirstErrors) > 0 {
fmt.Println("\n" + strings.Repeat("", 70))
fmt.Println("\n" + strings.Repeat("-", 70))
fmt.Println("FIRST ERRORS:")
fmt.Println(strings.Repeat("", 70))
fmt.Println(strings.Repeat("-", 70))
for i, err := range report.FirstErrors {
if i >= 5 {
@@ -454,15 +454,15 @@ func (ec *ErrorCollector) PrintReport(report *RestoreErrorReport) {
// Show diagnosis summary
if report.DiagnosisResult != nil && !report.DiagnosisResult.IsValid {
fmt.Println("\n" + strings.Repeat("", 70))
fmt.Println("\n" + strings.Repeat("-", 70))
fmt.Println("DIAGNOSIS:")
fmt.Println(strings.Repeat("", 70))
fmt.Println(strings.Repeat("-", 70))
if report.DiagnosisResult.IsTruncated {
fmt.Println(" File is TRUNCATED")
fmt.Println(" [FAIL] File is TRUNCATED")
}
if report.DiagnosisResult.IsCorrupted {
fmt.Println(" File is CORRUPTED")
fmt.Println(" [FAIL] File is CORRUPTED")
}
for i, err := range report.DiagnosisResult.Errors {
if i >= 3 {
@@ -473,18 +473,18 @@ func (ec *ErrorCollector) PrintReport(report *RestoreErrorReport) {
}
// Show recommendations
fmt.Println("\n" + strings.Repeat("", 70))
fmt.Println("💡 RECOMMENDATIONS:")
fmt.Println(strings.Repeat("", 70))
fmt.Println("\n" + strings.Repeat("-", 70))
fmt.Println("[HINT] RECOMMENDATIONS:")
fmt.Println(strings.Repeat("-", 70))
for _, rec := range report.Recommendations {
fmt.Printf(" %s\n", rec)
fmt.Printf(" - %s\n", rec)
}
// Show tool versions
fmt.Println("\n" + strings.Repeat("", 70))
fmt.Println("\n" + strings.Repeat("-", 70))
fmt.Println("ENVIRONMENT:")
fmt.Println(strings.Repeat("", 70))
fmt.Println(strings.Repeat("-", 70))
fmt.Printf(" OS: %s/%s\n", report.OS, report.Arch)
fmt.Printf(" Go: %s\n", report.GoVersion)
@@ -495,7 +495,7 @@ func (ec *ErrorCollector) PrintReport(report *RestoreErrorReport) {
fmt.Printf(" psql: %s\n", report.PsqlVersion)
}
fmt.Println(strings.Repeat("", 70))
fmt.Println(strings.Repeat("=", 70))
}
// Helper functions

View File

@@ -25,7 +25,7 @@ func (pc *PrivilegeChecker) CheckAndWarn(allowRoot bool) error {
isRoot, user := pc.isRunningAsRoot()
if isRoot {
pc.log.Warn("⚠️ Running with elevated privileges (root/Administrator)")
pc.log.Warn("[WARN] Running with elevated privileges (root/Administrator)")
pc.log.Warn("Security recommendation: Create a dedicated backup user with minimal privileges")
if !allowRoot {

View File

@@ -64,7 +64,7 @@ func (rc *ResourceChecker) ValidateResourcesForBackup(estimatedSize int64) error
if len(warnings) > 0 {
for _, warning := range warnings {
rc.log.Warn("⚠️ Resource constraint: " + warning)
rc.log.Warn("[WARN] Resource constraint: " + warning)
}
rc.log.Info("Continuing backup operation (warnings are informational)")
}

View File

@@ -22,7 +22,7 @@ func (rc *ResourceChecker) checkPlatformLimits() (*ResourceLimits, error) {
rc.log.Debug("Resource limit: max open files", "limit", rLimit.Cur, "max", rLimit.Max)
if rLimit.Cur < 1024 {
rc.log.Warn("⚠️ Low file descriptor limit detected",
rc.log.Warn("[WARN] Low file descriptor limit detected",
"current", rLimit.Cur,
"recommended", 4096,
"hint", "Increase with: ulimit -n 4096")

View File

@@ -209,12 +209,12 @@ func (m ArchiveBrowserModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
// Validate selection based on mode
if m.mode == "restore-cluster" && !selected.Format.IsClusterBackup() {
m.message = errorStyle.Render(" Please select a cluster backup (.tar.gz)")
m.message = errorStyle.Render("[FAIL] Please select a cluster backup (.tar.gz)")
return m, nil
}
if m.mode == "restore-single" && selected.Format.IsClusterBackup() {
m.message = errorStyle.Render(" Please select a single database backup")
m.message = errorStyle.Render("[FAIL] Please select a single database backup")
return m, nil
}
@@ -227,7 +227,7 @@ func (m ArchiveBrowserModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
// Show detailed info
if len(m.archives) > 0 && m.cursor < len(m.archives) {
selected := m.archives[m.cursor]
m.message = fmt.Sprintf("📦 %s | Format: %s | Size: %s | Modified: %s",
m.message = fmt.Sprintf("[PKG] %s | Format: %s | Size: %s | Modified: %s",
selected.Name,
selected.Format.String(),
formatSize(selected.Size),
@@ -251,13 +251,13 @@ func (m ArchiveBrowserModel) View() string {
var s strings.Builder
// Header
title := "📦 Backup Archives"
title := "[PKG] Backup Archives"
if m.mode == "restore-single" {
title = "📦 Select Archive to Restore (Single Database)"
title = "[PKG] Select Archive to Restore (Single Database)"
} else if m.mode == "restore-cluster" {
title = "📦 Select Archive to Restore (Cluster)"
title = "[PKG] Select Archive to Restore (Cluster)"
} else if m.mode == "diagnose" {
title = "🔍 Select Archive to Diagnose"
title = "[SEARCH] Select Archive to Diagnose"
}
s.WriteString(titleStyle.Render(title))
@@ -269,7 +269,7 @@ func (m ArchiveBrowserModel) View() string {
}
if m.err != nil {
s.WriteString(errorStyle.Render(fmt.Sprintf(" Error: %v", m.err)))
s.WriteString(errorStyle.Render(fmt.Sprintf("[FAIL] Error: %v", m.err)))
s.WriteString("\n\n")
s.WriteString(infoStyle.Render("Press Esc to go back"))
return s.String()
@@ -293,7 +293,7 @@ func (m ArchiveBrowserModel) View() string {
s.WriteString(archiveHeaderStyle.Render(fmt.Sprintf("%-40s %-25s %-12s %-20s",
"FILENAME", "FORMAT", "SIZE", "MODIFIED")))
s.WriteString("\n")
s.WriteString(strings.Repeat("", 100))
s.WriteString(strings.Repeat("-", 100))
s.WriteString("\n")
// Show archives (limit to visible area)
@@ -317,13 +317,13 @@ func (m ArchiveBrowserModel) View() string {
}
// Color code based on validity and age
statusIcon := ""
statusIcon := "[+]"
if !archive.Valid {
statusIcon = ""
statusIcon = "[-]"
style = archiveInvalidStyle
} else if time.Since(archive.Modified) > 30*24*time.Hour {
style = archiveOldStyle
statusIcon = ""
statusIcon = "[WARN]"
}
filename := truncate(archive.Name, 38)
@@ -351,7 +351,7 @@ func (m ArchiveBrowserModel) View() string {
s.WriteString(infoStyle.Render(fmt.Sprintf("Total: %d archive(s) | Selected: %d/%d",
len(m.archives), m.cursor+1, len(m.archives))))
s.WriteString("\n")
s.WriteString(infoStyle.Render("⌨️ ↑/↓: Navigate | Enter: Select | d: Diagnose | f: Filter | i: Info | Esc: Back"))
s.WriteString(infoStyle.Render("[KEY] ↑/↓: Navigate | Enter: Select | d: Diagnose | f: Filter | i: Info | Esc: Back"))
return s.String()
}

View File

@@ -136,11 +136,11 @@ func executeBackupWithTUIProgress(parentCtx context.Context, cfg *config.Config,
var result string
switch backupType {
case "single":
result = fmt.Sprintf(" Single database backup of '%s' completed successfully in %v", dbName, elapsed)
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)
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)
result = fmt.Sprintf("[+] Cluster backup completed successfully in %v", elapsed)
}
return backupCompleteMsg{
@@ -200,9 +200,9 @@ func (m BackupExecutionModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.err = msg.err
m.result = msg.result
if m.err == nil {
m.status = " Backup completed successfully!"
m.status = "[OK] Backup completed successfully!"
} else {
m.status = fmt.Sprintf(" Backup failed: %v", m.err)
m.status = fmt.Sprintf("[FAIL] Backup failed: %v", m.err)
}
// Auto-forward in debug/auto-confirm mode
if m.config.TUIAutoConfirm {
@@ -216,7 +216,7 @@ func (m BackupExecutionModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
if !m.done && !m.cancelling {
// User requested cancellation - cancel the context
m.cancelling = true
m.status = "⏹️ Cancelling backup... (please wait)"
m.status = "[STOP] Cancelling backup... (please wait)"
if m.cancel != nil {
m.cancel()
}
@@ -240,7 +240,7 @@ func (m BackupExecutionModel) View() string {
// Clear screen with newlines and render header
s.WriteString("\n\n")
header := titleStyle.Render("🔄 Backup Execution")
header := titleStyle.Render("[EXEC] Backup Execution")
s.WriteString(header)
s.WriteString("\n\n")
@@ -261,13 +261,13 @@ func (m BackupExecutionModel) View() string {
s.WriteString(fmt.Sprintf(" %s %s\n", spinnerFrames[m.spinnerFrame], m.status))
} else {
s.WriteString(fmt.Sprintf(" %s %s\n", spinnerFrames[m.spinnerFrame], m.status))
s.WriteString("\n ⌨️ Press Ctrl+C or ESC to cancel\n")
s.WriteString("\n [KEY] Press Ctrl+C or ESC to cancel\n")
}
} else {
s.WriteString(fmt.Sprintf(" %s\n\n", m.status))
if m.err != nil {
s.WriteString(fmt.Sprintf(" Error: %v\n", m.err))
s.WriteString(fmt.Sprintf(" [FAIL] Error: %v\n", m.err))
} else if m.result != "" {
// Parse and display result cleanly
lines := strings.Split(m.result, "\n")
@@ -278,7 +278,7 @@ func (m BackupExecutionModel) View() string {
}
}
}
s.WriteString("\n ⌨️ Press Enter or ESC to return to menu\n")
s.WriteString("\n [KEY] Press Enter or ESC to return to menu\n")
}
return s.String()

View File

@@ -86,7 +86,7 @@ func (m BackupManagerModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
// Verify archive
if len(m.archives) > 0 && m.cursor < len(m.archives) {
selected := m.archives[m.cursor]
m.message = fmt.Sprintf("🔍 Verifying %s...", selected.Name)
m.message = fmt.Sprintf("[SEARCH] Verifying %s...", selected.Name)
// In real implementation, would run verification
}
@@ -96,16 +96,16 @@ func (m BackupManagerModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
selected := m.archives[m.cursor]
archivePath := selected.Path
confirm := NewConfirmationModelWithAction(m.config, m.logger, m,
"🗑️ Delete Archive",
"[DELETE] Delete Archive",
fmt.Sprintf("Delete archive '%s'? This cannot be undone.", selected.Name),
func() (tea.Model, tea.Cmd) {
// Delete the archive
err := deleteArchive(archivePath)
if err != nil {
m.err = fmt.Errorf("failed to delete archive: %v", err)
m.message = fmt.Sprintf(" Failed to delete: %v", err)
m.message = fmt.Sprintf("[FAIL] Failed to delete: %v", err)
} else {
m.message = fmt.Sprintf(" Deleted: %s", selected.Name)
m.message = fmt.Sprintf("[OK] Deleted: %s", selected.Name)
}
// Refresh the archive list
m.loading = true
@@ -118,7 +118,7 @@ func (m BackupManagerModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
// Show info
if len(m.archives) > 0 && m.cursor < len(m.archives) {
selected := m.archives[m.cursor]
m.message = fmt.Sprintf("📦 %s | %s | %s | Modified: %s",
m.message = fmt.Sprintf("[PKG] %s | %s | %s | Modified: %s",
selected.Name,
selected.Format.String(),
formatSize(selected.Size),
@@ -152,7 +152,7 @@ func (m BackupManagerModel) View() string {
var s strings.Builder
// Title
s.WriteString(titleStyle.Render("🗄️ Backup Archive Manager"))
s.WriteString(titleStyle.Render("[DB] Backup Archive Manager"))
s.WriteString("\n\n")
if m.loading {
@@ -161,7 +161,7 @@ func (m BackupManagerModel) View() string {
}
if m.err != nil {
s.WriteString(errorStyle.Render(fmt.Sprintf(" Error: %v", m.err)))
s.WriteString(errorStyle.Render(fmt.Sprintf("[FAIL] Error: %v", m.err)))
s.WriteString("\n\n")
s.WriteString(infoStyle.Render("Press Esc to go back"))
return s.String()
@@ -184,7 +184,7 @@ func (m BackupManagerModel) View() string {
s.WriteString(archiveHeaderStyle.Render(fmt.Sprintf("%-35s %-25s %-12s %-20s",
"FILENAME", "FORMAT", "SIZE", "MODIFIED")))
s.WriteString("\n")
s.WriteString(strings.Repeat("", 95))
s.WriteString(strings.Repeat("-", 95))
s.WriteString("\n")
// Show archives (limit to visible area)
@@ -208,12 +208,12 @@ func (m BackupManagerModel) View() string {
}
// Status icon
statusIcon := ""
statusIcon := "[+]"
if !archive.Valid {
statusIcon = ""
statusIcon = "[-]"
style = archiveInvalidStyle
} else if time.Since(archive.Modified) > 30*24*time.Hour {
statusIcon = ""
statusIcon = "[WARN]"
}
filename := truncate(archive.Name, 33)
@@ -240,7 +240,7 @@ func (m BackupManagerModel) View() string {
s.WriteString(infoStyle.Render(fmt.Sprintf("Selected: %d/%d", m.cursor+1, len(m.archives))))
s.WriteString("\n")
s.WriteString(infoStyle.Render("⌨️ ↑/↓: Navigate | r: Restore | v: Verify | d: Delete | i: Info | R: Refresh | Esc: Back"))
s.WriteString(infoStyle.Render("[KEY] ↑/↓: Navigate | r: Restore | v: Verify | d: Delete | i: Info | R: Refresh | Esc: Back"))
return s.String()
}

View File

@@ -129,7 +129,7 @@ func (m ConfirmationModel) View() string {
s.WriteString(" ")
}
s.WriteString("\n\n⌨️ ←/→: Select Enter/y: Confirm n/ESC: Cancel\n")
s.WriteString("\n\n[KEYS] <-/->: Select | Enter/y: Confirm | n/ESC: Cancel\n")
return s.String()
}

View File

@@ -109,7 +109,7 @@ func (m DatabaseSelectorModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return executor, executor.Init()
}
inputModel := NewInputModel(m.config, m.logger, m,
"📊 Sample Ratio",
"[STATS] Sample Ratio",
"Enter sample ratio (1-100):",
"10",
ValidateInt(1, 100))
@@ -152,7 +152,7 @@ func (m DatabaseSelectorModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
// If sample backup, ask for ratio first
if m.backupType == "sample" {
inputModel := NewInputModel(m.config, m.logger, m,
"📊 Sample Ratio",
"[STATS] Sample Ratio",
"Enter sample ratio (1-100):",
"10",
ValidateInt(1, 100))
@@ -176,12 +176,12 @@ func (m DatabaseSelectorModel) View() string {
s.WriteString(fmt.Sprintf("\n%s\n\n", header))
if m.loading {
s.WriteString(" Loading databases...\n")
s.WriteString("[WAIT] Loading databases...\n")
return s.String()
}
if m.err != nil {
s.WriteString(fmt.Sprintf(" Error: %v\n", m.err))
s.WriteString(fmt.Sprintf("[FAIL] Error: %v\n", m.err))
s.WriteString("\nPress ESC to go back\n")
return s.String()
}
@@ -203,7 +203,7 @@ func (m DatabaseSelectorModel) View() string {
s.WriteString(fmt.Sprintf("\n%s\n", m.message))
}
s.WriteString("\n⌨️ ↑/↓: Navigate Enter: Select ESC: Back q: Quit\n")
s.WriteString("\n[KEYS] Up/Down: Navigate | Enter: Select | ESC: Back | q: Quit\n")
return s.String()
}

View File

@@ -160,7 +160,7 @@ func (m DiagnoseViewModel) View() string {
var s strings.Builder
// Header
s.WriteString(titleStyle.Render("🔍 Backup Diagnosis"))
s.WriteString(titleStyle.Render("[SEARCH] Backup Diagnosis"))
s.WriteString("\n\n")
// Archive info
@@ -175,14 +175,14 @@ func (m DiagnoseViewModel) View() string {
s.WriteString("\n\n")
if m.running {
s.WriteString(infoStyle.Render(" " + m.progress))
s.WriteString(infoStyle.Render("[WAIT] " + m.progress))
s.WriteString("\n\n")
s.WriteString(diagnoseInfoStyle.Render("This may take a while for large archives..."))
return s.String()
}
if m.err != nil {
s.WriteString(errorStyle.Render(fmt.Sprintf(" Diagnosis failed: %v", m.err)))
s.WriteString(errorStyle.Render(fmt.Sprintf("[FAIL] Diagnosis failed: %v", m.err)))
s.WriteString("\n\n")
s.WriteString(infoStyle.Render("Press Enter or Esc to go back"))
return s.String()
@@ -205,72 +205,72 @@ func (m DiagnoseViewModel) renderSingleResult(result *restore.DiagnoseResult) st
var s strings.Builder
// Status
s.WriteString(strings.Repeat("", 60))
s.WriteString(strings.Repeat("-", 60))
s.WriteString("\n")
if result.IsValid {
s.WriteString(diagnosePassStyle.Render(" STATUS: VALID"))
s.WriteString(diagnosePassStyle.Render("[OK] STATUS: VALID"))
} else {
s.WriteString(diagnoseFailStyle.Render(" STATUS: INVALID"))
s.WriteString(diagnoseFailStyle.Render("[FAIL] STATUS: INVALID"))
}
s.WriteString("\n")
if result.IsTruncated {
s.WriteString(diagnoseFailStyle.Render("⚠️ TRUNCATED: File appears incomplete"))
s.WriteString(diagnoseFailStyle.Render("[WARN] TRUNCATED: File appears incomplete"))
s.WriteString("\n")
}
if result.IsCorrupted {
s.WriteString(diagnoseFailStyle.Render("⚠️ CORRUPTED: File structure is damaged"))
s.WriteString(diagnoseFailStyle.Render("[WARN] CORRUPTED: File structure is damaged"))
s.WriteString("\n")
}
s.WriteString(strings.Repeat("", 60))
s.WriteString(strings.Repeat("-", 60))
s.WriteString("\n\n")
// Details
if result.Details != nil {
s.WriteString(diagnoseHeaderStyle.Render("📊 DETAILS:"))
s.WriteString(diagnoseHeaderStyle.Render("[STATS] DETAILS:"))
s.WriteString("\n")
if result.Details.HasPGDMPSignature {
s.WriteString(diagnosePassStyle.Render(" "))
s.WriteString(diagnosePassStyle.Render(" [+] "))
s.WriteString("Has PGDMP signature (custom format)\n")
}
if result.Details.HasSQLHeader {
s.WriteString(diagnosePassStyle.Render(" "))
s.WriteString(diagnosePassStyle.Render(" [+] "))
s.WriteString("Has PostgreSQL SQL header\n")
}
if result.Details.GzipValid {
s.WriteString(diagnosePassStyle.Render(" "))
s.WriteString(diagnosePassStyle.Render(" [+] "))
s.WriteString("Gzip compression valid\n")
}
if result.Details.PgRestoreListable {
s.WriteString(diagnosePassStyle.Render(" "))
s.WriteString(diagnosePassStyle.Render(" [+] "))
s.WriteString(fmt.Sprintf("pg_restore can list contents (%d tables)\n", result.Details.TableCount))
}
if result.Details.CopyBlockCount > 0 {
s.WriteString(diagnoseInfoStyle.Render(" "))
s.WriteString(diagnoseInfoStyle.Render(" - "))
s.WriteString(fmt.Sprintf("Contains %d COPY blocks\n", result.Details.CopyBlockCount))
}
if result.Details.UnterminatedCopy {
s.WriteString(diagnoseFailStyle.Render(" "))
s.WriteString(diagnoseFailStyle.Render(" [-] "))
s.WriteString(fmt.Sprintf("Unterminated COPY block: %s (line %d)\n",
result.Details.LastCopyTable, result.Details.LastCopyLineNumber))
}
if result.Details.ProperlyTerminated {
s.WriteString(diagnosePassStyle.Render(" "))
s.WriteString(diagnosePassStyle.Render(" [+] "))
s.WriteString("All COPY blocks properly terminated\n")
}
if result.Details.ExpandedSize > 0 {
s.WriteString(diagnoseInfoStyle.Render(" "))
s.WriteString(diagnoseInfoStyle.Render(" - "))
s.WriteString(fmt.Sprintf("Expanded size: %s (ratio: %.1fx)\n",
formatSize(result.Details.ExpandedSize), result.Details.CompressionRatio))
}
@@ -279,14 +279,14 @@ func (m DiagnoseViewModel) renderSingleResult(result *restore.DiagnoseResult) st
// Errors
if len(result.Errors) > 0 {
s.WriteString("\n")
s.WriteString(diagnoseFailStyle.Render(" ERRORS:"))
s.WriteString(diagnoseFailStyle.Render("[FAIL] ERRORS:"))
s.WriteString("\n")
for i, e := range result.Errors {
if i >= 5 {
s.WriteString(diagnoseInfoStyle.Render(fmt.Sprintf(" ... and %d more\n", len(result.Errors)-5)))
break
}
s.WriteString(diagnoseFailStyle.Render(" "))
s.WriteString(diagnoseFailStyle.Render(" - "))
s.WriteString(truncate(e, 70))
s.WriteString("\n")
}
@@ -295,14 +295,14 @@ func (m DiagnoseViewModel) renderSingleResult(result *restore.DiagnoseResult) st
// Warnings
if len(result.Warnings) > 0 {
s.WriteString("\n")
s.WriteString(diagnoseWarnStyle.Render("⚠️ WARNINGS:"))
s.WriteString(diagnoseWarnStyle.Render("[WARN] WARNINGS:"))
s.WriteString("\n")
for i, w := range result.Warnings {
if i >= 3 {
s.WriteString(diagnoseInfoStyle.Render(fmt.Sprintf(" ... and %d more\n", len(result.Warnings)-3)))
break
}
s.WriteString(diagnoseWarnStyle.Render(" "))
s.WriteString(diagnoseWarnStyle.Render(" - "))
s.WriteString(truncate(w, 70))
s.WriteString("\n")
}
@@ -311,7 +311,7 @@ func (m DiagnoseViewModel) renderSingleResult(result *restore.DiagnoseResult) st
// Recommendations
if !result.IsValid {
s.WriteString("\n")
s.WriteString(diagnoseHeaderStyle.Render("💡 RECOMMENDATIONS:"))
s.WriteString(diagnoseHeaderStyle.Render("[HINT] RECOMMENDATIONS:"))
s.WriteString("\n")
if result.IsTruncated {
s.WriteString(" 1. Re-run the backup process for this database\n")
@@ -341,17 +341,17 @@ func (m DiagnoseViewModel) renderClusterResults() string {
}
}
s.WriteString(strings.Repeat("", 60))
s.WriteString(strings.Repeat("-", 60))
s.WriteString("\n")
s.WriteString(diagnoseHeaderStyle.Render(fmt.Sprintf("📊 CLUSTER SUMMARY: %d databases\n", len(m.results))))
s.WriteString(strings.Repeat("", 60))
s.WriteString(diagnoseHeaderStyle.Render(fmt.Sprintf("[STATS] CLUSTER SUMMARY: %d databases\n", len(m.results))))
s.WriteString(strings.Repeat("-", 60))
s.WriteString("\n\n")
if invalidCount == 0 {
s.WriteString(diagnosePassStyle.Render(" All dumps are valid"))
s.WriteString(diagnosePassStyle.Render("[OK] All dumps are valid"))
s.WriteString("\n\n")
} else {
s.WriteString(diagnoseFailStyle.Render(fmt.Sprintf(" %d/%d dumps have issues", invalidCount, len(m.results))))
s.WriteString(diagnoseFailStyle.Render(fmt.Sprintf("[FAIL] %d/%d dumps have issues", invalidCount, len(m.results))))
s.WriteString("\n\n")
}
@@ -378,13 +378,13 @@ func (m DiagnoseViewModel) renderClusterResults() string {
var status string
if r.IsValid {
status = diagnosePassStyle.Render("")
status = diagnosePassStyle.Render("[+]")
} else if r.IsTruncated {
status = diagnoseFailStyle.Render(" TRUNCATED")
status = diagnoseFailStyle.Render("[-] TRUNCATED")
} else if r.IsCorrupted {
status = diagnoseFailStyle.Render(" CORRUPTED")
status = diagnoseFailStyle.Render("[-] CORRUPTED")
} else {
status = diagnoseFailStyle.Render(" INVALID")
status = diagnoseFailStyle.Render("[-] INVALID")
}
line := fmt.Sprintf("%s %s %-35s %s",
@@ -405,7 +405,7 @@ func (m DiagnoseViewModel) renderClusterResults() string {
if m.cursor < len(m.results) {
selected := m.results[m.cursor]
s.WriteString("\n")
s.WriteString(strings.Repeat("", 60))
s.WriteString(strings.Repeat("-", 60))
s.WriteString("\n")
s.WriteString(diagnoseHeaderStyle.Render("Selected: " + selected.FileName))
s.WriteString("\n\n")
@@ -413,7 +413,7 @@ func (m DiagnoseViewModel) renderClusterResults() string {
// Show condensed details for selected
if selected.Details != nil {
if selected.Details.UnterminatedCopy {
s.WriteString(diagnoseFailStyle.Render(" Unterminated COPY: "))
s.WriteString(diagnoseFailStyle.Render(" [-] Unterminated COPY: "))
s.WriteString(selected.Details.LastCopyTable)
s.WriteString(fmt.Sprintf(" (line %d)\n", selected.Details.LastCopyLineNumber))
}
@@ -429,7 +429,7 @@ func (m DiagnoseViewModel) renderClusterResults() string {
if i >= 2 {
break
}
s.WriteString(diagnoseFailStyle.Render(" "))
s.WriteString(diagnoseFailStyle.Render(" - "))
s.WriteString(truncate(e, 55))
s.WriteString("\n")
}

View File

@@ -208,7 +208,7 @@ func (dp *DirectoryPicker) View() string {
if dp.allowFiles {
pickerType = "File/Directory"
}
header := fmt.Sprintf("📁 %s Picker - %s", pickerType, dp.currentPath)
header := fmt.Sprintf("[DIR] %s Picker - %s", pickerType, dp.currentPath)
content.WriteString(dp.styles.Header.Render(header))
content.WriteString("\n\n")
@@ -216,13 +216,13 @@ func (dp *DirectoryPicker) View() string {
for i, item := range dp.items {
var prefix string
if item.Name == ".." {
prefix = "⬆️ "
prefix = "[UP] "
} else if item.Name == "Error reading directory" {
prefix = " "
prefix = "[X] "
} else if item.IsDir {
prefix = "📁 "
prefix = "[DIR] "
} else {
prefix = "📄 "
prefix = "[FILE] "
}
line := prefix + item.Name
@@ -235,9 +235,9 @@ func (dp *DirectoryPicker) View() string {
}
// Help text
help := "\n↑/↓: Navigate Enter: Open/Select File s: Select Directory q/Esc: Cancel"
help := "\nUp/Down: Navigate | Enter: Open/Select File | s: Select Directory | q/Esc: Cancel"
if !dp.allowFiles {
help = "\n↑/↓: Navigate Enter: Open s: Select Directory q/Esc: Cancel"
help = "\nUp/Down: Navigate | Enter: Open | s: Select Directory | q/Esc: Cancel"
}
content.WriteString(dp.styles.Help.Render(help))

View File

@@ -104,7 +104,7 @@ func loadHistory(cfg *config.Config) []HistoryEntry {
Type: backupType,
Database: database,
Timestamp: info.ModTime(),
Status: " Completed",
Status: "[OK] Completed",
Filename: name,
})
}
@@ -191,11 +191,11 @@ func (m HistoryViewModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
func (m HistoryViewModel) View() string {
var s strings.Builder
header := titleStyle.Render("📜 Operation History")
header := titleStyle.Render("[HISTORY] Operation History")
s.WriteString(fmt.Sprintf("\n%s\n\n", header))
if len(m.history) == 0 {
s.WriteString("📭 No backup history found\n\n")
s.WriteString("[EMPTY] No backup history found\n\n")
} else {
maxVisible := 15 // Show max 15 items at once
@@ -211,7 +211,7 @@ func (m HistoryViewModel) View() string {
// Show scroll indicators
if start > 0 {
s.WriteString(" More entries above...\n")
s.WriteString(" [^] More entries above...\n")
}
// Display only visible entries
@@ -233,13 +233,13 @@ func (m HistoryViewModel) View() string {
// Show scroll indicator if more entries below
if end < len(m.history) {
s.WriteString(fmt.Sprintf(" %d more entries below...\n", len(m.history)-end))
s.WriteString(fmt.Sprintf(" [v] %d more entries below...\n", len(m.history)-end))
}
s.WriteString("\n")
}
s.WriteString("⌨️ ↑/↓: Navigate PgUp/PgDn: Jump Home/End: First/Last ESC: Back q: Quit\n")
s.WriteString("[KEYS] Up/Down: Navigate - PgUp/PgDn: Jump - Home/End: First/Last - ESC: Back - q: Quit\n")
return s.String()
}

View File

@@ -137,10 +137,10 @@ func (m InputModel) View() string {
s.WriteString("\n\n")
if m.err != nil {
s.WriteString(errorStyle.Render(fmt.Sprintf(" Error: %v\n\n", m.err)))
s.WriteString(errorStyle.Render(fmt.Sprintf("[FAIL] Error: %v\n\n", m.err)))
}
s.WriteString("⌨️ Type value Enter: Confirm ESC: Cancel\n")
s.WriteString("[KEYS] Type value | Enter: Confirm | ESC: Cancel\n")
return s.String()
}

View File

@@ -89,12 +89,12 @@ func NewMenuModel(cfg *config.Config, log logger.Logger) *MenuModel {
"Single Database Backup",
"Sample Database Backup (with ratio)",
"Cluster Backup (all databases)",
"────────────────────────────────",
"--------------------------------",
"Restore Single Database",
"Restore Cluster Backup",
"Diagnose Backup File",
"List & Manage Backups",
"────────────────────────────────",
"--------------------------------",
"View Active Operations",
"Show Operation History",
"Database Status & Health Check",
@@ -177,7 +177,7 @@ func (m *MenuModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case 12: // Settings
return m.handleSettings()
case 13: // Clear History
m.message = "🗑️ History cleared"
m.message = "[DEL] History cleared"
case 14: // Quit
if m.cancel != nil {
m.cancel()
@@ -262,7 +262,7 @@ func (m *MenuModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case 12: // Settings
return m.handleSettings()
case 13: // Clear History
m.message = "🗑️ History cleared"
m.message = "[DEL] History cleared"
case 14: // Quit
if m.cancel != nil {
m.cancel()
@@ -285,7 +285,7 @@ func (m *MenuModel) View() string {
var s string
// Header
header := titleStyle.Render("🗄️ Database Backup Tool - Interactive Menu")
header := titleStyle.Render("[DB] Database Backup Tool - Interactive Menu")
s += fmt.Sprintf("\n%s\n\n", header)
if len(m.dbTypes) > 0 {
@@ -299,7 +299,7 @@ func (m *MenuModel) View() string {
}
selector := fmt.Sprintf("Target Engine: %s", strings.Join(options, menuStyle.Render(" | ")))
s += dbSelectorLabelStyle.Render(selector) + "\n"
hint := infoStyle.Render("Switch with ←/→ or t Cluster backup requires PostgreSQL")
hint := infoStyle.Render("Switch with <-/-> or t | Cluster backup requires PostgreSQL")
s += hint + "\n"
}
@@ -326,7 +326,7 @@ func (m *MenuModel) View() string {
}
// Footer
footer := infoStyle.Render("\n⌨️ Press ↑/↓ to navigate Enter to select q to quit")
footer := infoStyle.Render("\n[KEYS] Press Up/Down to navigate | Enter to select | q to quit")
s += footer
return s
@@ -334,20 +334,20 @@ func (m *MenuModel) View() string {
// handleSingleBackup opens database selector for single backup
func (m *MenuModel) handleSingleBackup() (tea.Model, tea.Cmd) {
selector := NewDatabaseSelector(m.config, m.logger, m, m.ctx, "🗄️ Single Database Backup", "single")
selector := NewDatabaseSelector(m.config, m.logger, m, m.ctx, "[DB] Single Database Backup", "single")
return selector, selector.Init()
}
// handleSampleBackup opens database selector for sample backup
func (m *MenuModel) handleSampleBackup() (tea.Model, tea.Cmd) {
selector := NewDatabaseSelector(m.config, m.logger, m, m.ctx, "📊 Sample Database Backup", "sample")
selector := NewDatabaseSelector(m.config, m.logger, m, m.ctx, "[STATS] Sample Database Backup", "sample")
return selector, selector.Init()
}
// handleClusterBackup shows confirmation and executes cluster backup
func (m *MenuModel) handleClusterBackup() (tea.Model, tea.Cmd) {
if !m.config.IsPostgreSQL() {
m.message = errorStyle.Render(" Cluster backup is available only for PostgreSQL targets")
m.message = errorStyle.Render("[FAIL] Cluster backup is available only for PostgreSQL targets")
return m, nil
}
// Skip confirmation in auto-confirm mode
@@ -356,7 +356,7 @@ func (m *MenuModel) handleClusterBackup() (tea.Model, tea.Cmd) {
return executor, executor.Init()
}
confirm := NewConfirmationModelWithAction(m.config, m.logger, m,
"🗄️ Cluster Backup",
"[DB] Cluster Backup",
"This will backup ALL databases in the cluster. Continue?",
func() (tea.Model, tea.Cmd) {
executor := NewBackupExecution(m.config, m.logger, m, m.ctx, "cluster", "", 0)
@@ -399,7 +399,7 @@ func (m *MenuModel) handleRestoreSingle() (tea.Model, tea.Cmd) {
// handleRestoreCluster opens archive browser for cluster restore
func (m *MenuModel) handleRestoreCluster() (tea.Model, tea.Cmd) {
if !m.config.IsPostgreSQL() {
m.message = errorStyle.Render(" Cluster restore is available only for PostgreSQL")
m.message = errorStyle.Render("[FAIL] Cluster restore is available only for PostgreSQL")
return m, nil
}
browser := NewArchiveBrowser(m.config, m.logger, m, m.ctx, "restore-cluster")
@@ -428,7 +428,7 @@ func (m *MenuModel) applyDatabaseSelection() {
selection := m.dbTypes[m.dbTypeCursor]
if err := m.config.SetDatabaseType(selection.value); err != nil {
m.message = errorStyle.Render(fmt.Sprintf(" %v", err))
m.message = errorStyle.Render(fmt.Sprintf("[FAIL] %v", err))
return
}
@@ -437,7 +437,7 @@ func (m *MenuModel) applyDatabaseSelection() {
m.config.Port = m.config.GetDefaultPort()
}
m.message = successStyle.Render(fmt.Sprintf("🔀 Target database set to %s", m.config.DisplayDatabaseType()))
m.message = successStyle.Render(fmt.Sprintf("[SWITCH] Target database set to %s", m.config.DisplayDatabaseType()))
if m.logger != nil {
m.logger.Info("updated target database type", "type", m.config.DatabaseType, "port", m.config.Port)
}

View File

@@ -49,14 +49,14 @@ func (m OperationsViewModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
func (m OperationsViewModel) View() string {
var s strings.Builder
header := titleStyle.Render("📊 Active Operations")
header := titleStyle.Render("[STATS] Active Operations")
s.WriteString(fmt.Sprintf("\n%s\n\n", header))
s.WriteString("Currently running operations:\n\n")
s.WriteString(infoStyle.Render("📭 No active operations"))
s.WriteString(infoStyle.Render("[NONE] No active operations"))
s.WriteString("\n\n")
s.WriteString("⌨️ Press any key to return to menu\n")
s.WriteString("[KEYS] Press any key to return to menu\n")
return s.String()
}

View File

@@ -285,7 +285,7 @@ func (m RestoreExecutionModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
if !m.done && !m.cancelling {
// User requested cancellation - cancel the context
m.cancelling = true
m.status = "⏹️ Cancelling restore... (please wait)"
m.status = "[STOP] Cancelling restore... (please wait)"
m.phase = "Cancelling"
if m.cancel != nil {
m.cancel()
@@ -297,7 +297,7 @@ func (m RestoreExecutionModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case "q":
if !m.done && !m.cancelling {
m.cancelling = true
m.status = "⏹️ Cancelling restore... (please wait)"
m.status = "[STOP] Cancelling restore... (please wait)"
m.phase = "Cancelling"
if m.cancel != nil {
m.cancel()
@@ -321,9 +321,9 @@ func (m RestoreExecutionModel) View() string {
s.Grow(512) // Pre-allocate estimated capacity for better performance
// Title
title := "💾 Restoring Database"
title := "[RESTORE] Restoring Database"
if m.restoreType == "restore-cluster" {
title = "💾 Restoring Cluster"
title = "[RESTORE] Restoring Cluster"
}
s.WriteString(titleStyle.Render(title))
s.WriteString("\n\n")
@@ -338,12 +338,12 @@ func (m RestoreExecutionModel) View() string {
if m.done {
// Show result
if m.err != nil {
s.WriteString(errorStyle.Render(" Restore Failed"))
s.WriteString(errorStyle.Render("[FAIL] Restore Failed"))
s.WriteString("\n\n")
s.WriteString(errorStyle.Render(fmt.Sprintf("Error: %v", m.err)))
s.WriteString("\n")
} else {
s.WriteString(successStyle.Render(" Restore Completed Successfully"))
s.WriteString(successStyle.Render("[OK] Restore Completed Successfully"))
s.WriteString("\n\n")
s.WriteString(successStyle.Render(m.result))
s.WriteString("\n")
@@ -351,7 +351,7 @@ func (m RestoreExecutionModel) View() string {
s.WriteString(fmt.Sprintf("\nElapsed Time: %s\n", formatDuration(m.elapsed)))
s.WriteString("\n")
s.WriteString(infoStyle.Render("⌨️ Press Enter to continue"))
s.WriteString(infoStyle.Render("[KEYS] Press Enter to continue"))
} else {
// Show progress
s.WriteString(fmt.Sprintf("Phase: %s\n", m.phase))
@@ -373,7 +373,7 @@ func (m RestoreExecutionModel) View() string {
// Elapsed time
s.WriteString(fmt.Sprintf("Elapsed: %s\n", formatDuration(m.elapsed)))
s.WriteString("\n")
s.WriteString(infoStyle.Render("⌨️ Press Ctrl+C to cancel"))
s.WriteString(infoStyle.Render("[KEYS] Press Ctrl+C to cancel"))
}
return s.String()

View File

@@ -264,7 +264,7 @@ func (m RestorePreviewModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
// Toggle cluster cleanup
m.cleanClusterFirst = !m.cleanClusterFirst
if m.cleanClusterFirst {
m.message = checkWarningStyle.Render(fmt.Sprintf("⚠️ Will drop %d existing database(s) before restore", m.existingDBCount))
m.message = checkWarningStyle.Render(fmt.Sprintf("[WARN] Will drop %d existing database(s) before restore", m.existingDBCount))
} else {
m.message = fmt.Sprintf("Clean cluster first: disabled")
}
@@ -278,7 +278,7 @@ func (m RestorePreviewModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
// Toggle debug log saving
m.saveDebugLog = !m.saveDebugLog
if m.saveDebugLog {
m.message = infoStyle.Render("📋 Debug log: enabled (will save detailed report on failure)")
m.message = infoStyle.Render("[DEBUG] Debug log: enabled (will save detailed report on failure)")
} else {
m.message = "Debug log: disabled"
}
@@ -288,7 +288,7 @@ func (m RestorePreviewModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
if m.workDir == "" {
// Set to backup directory as default alternative
m.workDir = m.config.BackupDir
m.message = infoStyle.Render(fmt.Sprintf("📁 Work directory set to: %s", m.workDir))
m.message = infoStyle.Render(fmt.Sprintf("[DIR] Work directory set to: %s", m.workDir))
} else {
// Clear work directory (use system temp)
m.workDir = ""
@@ -302,7 +302,7 @@ func (m RestorePreviewModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}
if !m.canProceed {
m.message = errorStyle.Render(" Cannot proceed - critical safety checks failed")
m.message = errorStyle.Render("[FAIL] Cannot proceed - critical safety checks failed")
return m, nil
}
@@ -319,15 +319,15 @@ func (m RestorePreviewModel) View() string {
var s strings.Builder
// Title
title := "🔍 Restore Preview"
title := "Restore Preview"
if m.mode == "restore-cluster" {
title = "🔍 Cluster Restore Preview"
title = "Cluster Restore Preview"
}
s.WriteString(titleStyle.Render(title))
s.WriteString("\n\n")
// Archive Information
s.WriteString(archiveHeaderStyle.Render("📦 Archive Information"))
s.WriteString(archiveHeaderStyle.Render("[ARCHIVE] Information"))
s.WriteString("\n")
s.WriteString(fmt.Sprintf(" File: %s\n", m.archive.Name))
s.WriteString(fmt.Sprintf(" Format: %s\n", m.archive.Format.String()))
@@ -340,25 +340,25 @@ func (m RestorePreviewModel) View() string {
// Target Information
if m.mode == "restore-single" {
s.WriteString(archiveHeaderStyle.Render("🎯 Target Information"))
s.WriteString(archiveHeaderStyle.Render("[TARGET] Information"))
s.WriteString("\n")
s.WriteString(fmt.Sprintf(" Database: %s\n", m.targetDB))
s.WriteString(fmt.Sprintf(" Host: %s:%d\n", m.config.Host, m.config.Port))
cleanIcon := ""
cleanIcon := "[N]"
if m.cleanFirst {
cleanIcon = ""
cleanIcon = "[Y]"
}
s.WriteString(fmt.Sprintf(" Clean First: %s %v\n", cleanIcon, m.cleanFirst))
createIcon := ""
createIcon := "[N]"
if m.createIfMissing {
createIcon = ""
createIcon = "[Y]"
}
s.WriteString(fmt.Sprintf(" Create If Missing: %s %v\n", createIcon, m.createIfMissing))
s.WriteString("\n")
} else if m.mode == "restore-cluster" {
s.WriteString(archiveHeaderStyle.Render("🎯 Cluster Restore Options"))
s.WriteString(archiveHeaderStyle.Render("[CLUSTER] Restore Options"))
s.WriteString("\n")
s.WriteString(fmt.Sprintf(" Host: %s:%d\n", m.config.Host, m.config.Port))
@@ -376,10 +376,10 @@ func (m RestorePreviewModel) View() string {
s.WriteString(fmt.Sprintf(" - %s\n", db))
}
cleanIcon := ""
cleanIcon := "[N]"
cleanStyle := infoStyle
if m.cleanClusterFirst {
cleanIcon = ""
cleanIcon = "[Y]"
cleanStyle = checkWarningStyle
}
s.WriteString(cleanStyle.Render(fmt.Sprintf(" Clean All First: %s %v (press 'c' to toggle)\n", cleanIcon, m.cleanClusterFirst)))
@@ -390,7 +390,7 @@ func (m RestorePreviewModel) View() string {
}
// Safety Checks
s.WriteString(archiveHeaderStyle.Render("🛡️ Safety Checks"))
s.WriteString(archiveHeaderStyle.Render("[SAFETY] Checks"))
s.WriteString("\n")
if m.checking {
@@ -398,21 +398,21 @@ func (m RestorePreviewModel) View() string {
s.WriteString("\n")
} else {
for _, check := range m.safetyChecks {
icon := ""
icon := "[ ]"
style := checkPendingStyle
switch check.Status {
case "passed":
icon = ""
icon = "[+]"
style = checkPassedStyle
case "failed":
icon = ""
icon = "[-]"
style = checkFailedStyle
case "warning":
icon = ""
icon = "[!]"
style = checkWarningStyle
case "checking":
icon = ""
icon = "[~]"
style = checkPendingStyle
}
@@ -428,13 +428,13 @@ func (m RestorePreviewModel) View() string {
// Warnings
if m.cleanFirst {
s.WriteString(checkWarningStyle.Render("⚠️ Warning: Clean-first enabled"))
s.WriteString(checkWarningStyle.Render("[WARN] Warning: Clean-first enabled"))
s.WriteString("\n")
s.WriteString(infoStyle.Render(" All existing data in target database will be dropped!"))
s.WriteString("\n\n")
}
if m.cleanClusterFirst && m.existingDBCount > 0 {
s.WriteString(checkWarningStyle.Render("🔥 WARNING: Cluster cleanup enabled"))
s.WriteString(checkWarningStyle.Render("[DANGER] WARNING: Cluster cleanup enabled"))
s.WriteString("\n")
s.WriteString(checkWarningStyle.Render(fmt.Sprintf(" %d existing database(s) will be DROPPED before restore!", m.existingDBCount)))
s.WriteString("\n")
@@ -443,30 +443,30 @@ func (m RestorePreviewModel) View() string {
}
// Advanced Options
s.WriteString(archiveHeaderStyle.Render("⚙️ Advanced Options"))
s.WriteString(archiveHeaderStyle.Render("[OPTIONS] Advanced"))
s.WriteString("\n")
// Work directory option
workDirIcon := ""
workDirIcon := "[-]"
workDirStyle := infoStyle
workDirValue := "(system temp)"
if m.workDir != "" {
workDirIcon = ""
workDirIcon = "[+]"
workDirStyle = checkPassedStyle
workDirValue = m.workDir
}
s.WriteString(workDirStyle.Render(fmt.Sprintf(" %s Work Dir: %s (press 'w' to toggle)", workDirIcon, workDirValue)))
s.WriteString("\n")
if m.workDir == "" {
s.WriteString(infoStyle.Render(" ⚠️ Large archives need more space than /tmp may have"))
s.WriteString(infoStyle.Render(" [WARN] Large archives need more space than /tmp may have"))
s.WriteString("\n")
}
// Debug log option
debugIcon := ""
debugIcon := "[-]"
debugStyle := infoStyle
if m.saveDebugLog {
debugIcon = ""
debugIcon = "[+]"
debugStyle = checkPassedStyle
}
s.WriteString(debugStyle.Render(fmt.Sprintf(" %s Debug Log: %v (press 'd' to toggle)", debugIcon, m.saveDebugLog)))
@@ -485,25 +485,25 @@ func (m RestorePreviewModel) View() string {
// Footer
if m.checking {
s.WriteString(infoStyle.Render("⌨️ Please wait..."))
s.WriteString(infoStyle.Render("Please wait..."))
} else if m.canProceed {
s.WriteString(successStyle.Render(" Ready to restore"))
s.WriteString(successStyle.Render("[OK] Ready to restore"))
s.WriteString("\n")
if m.mode == "restore-single" {
s.WriteString(infoStyle.Render("⌨️ t: Clean-first | c: Create | w: WorkDir | d: Debug | Enter: Proceed | Esc: Cancel"))
s.WriteString(infoStyle.Render("t: Clean-first | c: Create | w: WorkDir | d: Debug | Enter: Proceed | Esc: Cancel"))
} else if m.mode == "restore-cluster" {
if m.existingDBCount > 0 {
s.WriteString(infoStyle.Render("⌨️ c: Cleanup | w: WorkDir | d: Debug | Enter: Proceed | Esc: Cancel"))
s.WriteString(infoStyle.Render("c: Cleanup | w: WorkDir | d: Debug | Enter: Proceed | Esc: Cancel"))
} else {
s.WriteString(infoStyle.Render("⌨️ w: WorkDir | d: Debug | Enter: Proceed | Esc: Cancel"))
s.WriteString(infoStyle.Render("w: WorkDir | d: Debug | Enter: Proceed | Esc: Cancel"))
}
} else {
s.WriteString(infoStyle.Render("⌨️ w: WorkDir | d: Debug | Enter: Proceed | Esc: Cancel"))
s.WriteString(infoStyle.Render("w: WorkDir | d: Debug | Enter: Proceed | Esc: Cancel"))
}
} else {
s.WriteString(errorStyle.Render(" Cannot proceed - please fix errors above"))
s.WriteString(errorStyle.Render("[FAIL] Cannot proceed - please fix errors above"))
s.WriteString("\n")
s.WriteString(infoStyle.Render("⌨️ Esc: Go back"))
s.WriteString(infoStyle.Render("Esc: Go back"))
}
return s.String()

View File

@@ -459,9 +459,9 @@ func (m SettingsModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
if m.cursor < len(m.settings) {
setting := m.settings[m.cursor]
if err := setting.Update(m.config, selectedPath); err != nil {
m.message = " Error: " + err.Error()
m.message = "[FAIL] Error: " + err.Error()
} else {
m.message = " Directory updated: " + selectedPath
m.message = "[OK] Directory updated: " + selectedPath
}
}
m.browsingDir = false
@@ -500,9 +500,9 @@ func (m SettingsModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
currentSetting := m.settings[m.cursor]
if currentSetting.Type == "selector" {
if err := currentSetting.Update(m.config, ""); err != nil {
m.message = errorStyle.Render(fmt.Sprintf(" %s", err.Error()))
m.message = errorStyle.Render(fmt.Sprintf("[FAIL] %s", err.Error()))
} else {
m.message = successStyle.Render(fmt.Sprintf(" Updated %s", currentSetting.DisplayName))
m.message = successStyle.Render(fmt.Sprintf("[OK] Updated %s", currentSetting.DisplayName))
}
return m, nil
}
@@ -515,11 +515,11 @@ func (m SettingsModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
if m.settings[m.cursor].Type == "path" {
return m.openDirectoryBrowser()
} else {
m.message = " Tab key only works on directory path fields"
m.message = "[FAIL] Tab key only works on directory path fields"
return m, nil
}
} else {
m.message = " Invalid selection"
m.message = "[FAIL] Invalid selection"
return m, nil
}
@@ -597,18 +597,18 @@ func (m SettingsModel) saveEditedValue() (tea.Model, tea.Cmd) {
}
if setting == nil {
m.message = errorStyle.Render(" Setting not found")
m.message = errorStyle.Render("[FAIL] Setting not found")
m.editing = false
return m, nil
}
// Update the configuration
if err := setting.Update(m.config, m.editingValue); err != nil {
m.message = errorStyle.Render(fmt.Sprintf(" %s", err.Error()))
m.message = errorStyle.Render(fmt.Sprintf("[FAIL] %s", err.Error()))
return m, nil
}
m.message = successStyle.Render(fmt.Sprintf(" Updated %s", setting.DisplayName))
m.message = successStyle.Render(fmt.Sprintf("[OK] Updated %s", setting.DisplayName))
m.editing = false
m.editingField = ""
m.editingValue = ""
@@ -628,7 +628,7 @@ func (m SettingsModel) resetToDefaults() (tea.Model, tea.Cmd) {
newConfig.DatabaseType = m.config.DatabaseType
*m.config = *newConfig
m.message = successStyle.Render(" Settings reset to defaults")
m.message = successStyle.Render("[OK] Settings reset to defaults")
return m, nil
}
@@ -636,19 +636,19 @@ func (m SettingsModel) resetToDefaults() (tea.Model, tea.Cmd) {
// saveSettings validates and saves current settings
func (m SettingsModel) saveSettings() (tea.Model, tea.Cmd) {
if err := m.config.Validate(); err != nil {
m.message = errorStyle.Render(fmt.Sprintf(" Validation failed: %s", err.Error()))
m.message = errorStyle.Render(fmt.Sprintf("[FAIL] Validation failed: %s", err.Error()))
return m, nil
}
// Optimize CPU settings if auto-detect is enabled
if m.config.AutoDetectCores {
if err := m.config.OptimizeForCPU(); err != nil {
m.message = errorStyle.Render(fmt.Sprintf(" CPU optimization failed: %s", err.Error()))
m.message = errorStyle.Render(fmt.Sprintf("[FAIL] CPU optimization failed: %s", err.Error()))
return m, nil
}
}
m.message = successStyle.Render(" Settings validated and saved")
m.message = successStyle.Render("[OK] Settings validated and saved")
return m, nil
}
@@ -671,11 +671,11 @@ func (m SettingsModel) cycleDatabaseType() (tea.Model, tea.Cmd) {
// Update config
if err := m.config.SetDatabaseType(newType); err != nil {
m.message = errorStyle.Render(fmt.Sprintf(" Failed to set database type: %s", err.Error()))
m.message = errorStyle.Render(fmt.Sprintf("[FAIL] Failed to set database type: %s", err.Error()))
return m, nil
}
m.message = successStyle.Render(fmt.Sprintf(" Database type set to %s", m.config.DisplayDatabaseType()))
m.message = successStyle.Render(fmt.Sprintf("[OK] Database type set to %s", m.config.DisplayDatabaseType()))
return m, nil
}
@@ -688,7 +688,7 @@ func (m SettingsModel) View() string {
var b strings.Builder
// Header
header := titleStyle.Render("⚙️ Configuration Settings")
header := titleStyle.Render("[CFG] Configuration Settings")
b.WriteString(fmt.Sprintf("\n%s\n\n", header))
// Settings list
@@ -710,7 +710,7 @@ func (m SettingsModel) View() string {
}
line := fmt.Sprintf("%s %s: %s", cursor, setting.DisplayName, editValue)
b.WriteString(selectedStyle.Render(line))
b.WriteString(" ✏️")
b.WriteString(" [EDIT]")
} else {
line := fmt.Sprintf("%s %s: %s", cursor, setting.DisplayName, displayValue)
b.WriteString(selectedStyle.Render(line))
@@ -747,7 +747,7 @@ func (m SettingsModel) View() string {
// Current configuration summary
if !m.editing {
b.WriteString("\n")
b.WriteString(infoStyle.Render("📋 Current Configuration:"))
b.WriteString(infoStyle.Render("[LOG] Current Configuration:"))
b.WriteString("\n")
summary := []string{
@@ -775,16 +775,16 @@ func (m SettingsModel) View() string {
// Footer with instructions
var footer string
if m.editing {
footer = infoStyle.Render("\n⌨️ Type new value Enter to save Esc to cancel")
footer = infoStyle.Render("\n[KEYS] Type new value | Enter to save | Esc to cancel")
} else {
if m.browsingDir {
footer = infoStyle.Render("\n⌨️ ↑/↓ navigate directories Enter open Space select Tab/Esc back to settings")
footer = infoStyle.Render("\n[KEYS] Up/Down navigate directories | Enter open | Space select | Tab/Esc back to settings")
} else {
// Show different help based on current selection
if m.cursor >= 0 && m.cursor < len(m.settings) && m.settings[m.cursor].Type == "path" {
footer = infoStyle.Render("\n⌨️ ↑/↓ navigate Enter edit Tab browse directories 's' save 'r' reset 'q' menu")
footer = infoStyle.Render("\n[KEYS] Up/Down navigate | Enter edit | Tab browse directories | 's' save | 'r' reset | 'q' menu")
} else {
footer = infoStyle.Render("\n⌨️ ↑/↓ navigate Enter edit 's' save 'r' reset 'q' menu Tab=dirs on path fields only")
footer = infoStyle.Render("\n[KEYS] Up/Down navigate | Enter edit | 's' save | 'r' reset | 'q' menu | Tab=dirs on path fields only")
}
}
}

View File

@@ -160,25 +160,25 @@ func (m StatusViewModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
func (m StatusViewModel) View() string {
var s strings.Builder
header := titleStyle.Render("📊 Database Status & Health Check")
header := titleStyle.Render("[STATS] Database Status & Health Check")
s.WriteString(fmt.Sprintf("\n%s\n\n", header))
if m.loading {
spinner := []string{"⠋", "⠙", "⠹", "", "", "⠴", "⠦", "⠧", "⠇", "⠏"}
spinner := []string{"-", "\\", "|", "/"}
frame := int(time.Now().UnixMilli()/100) % len(spinner)
s.WriteString(fmt.Sprintf("%s Loading status information...\n", spinner[frame]))
return s.String()
}
if m.err != nil {
s.WriteString(errorStyle.Render(fmt.Sprintf(" Error: %v\n", m.err)))
s.WriteString(errorStyle.Render(fmt.Sprintf("[FAIL] Error: %v\n", m.err)))
s.WriteString("\n")
} else {
s.WriteString("Connection Status:\n")
if m.connected {
s.WriteString(successStyle.Render(" Connected\n"))
s.WriteString(successStyle.Render(" [+] Connected\n"))
} else {
s.WriteString(errorStyle.Render(" Disconnected\n"))
s.WriteString(errorStyle.Render(" [-] Disconnected\n"))
}
s.WriteString("\n")
@@ -193,9 +193,9 @@ func (m StatusViewModel) View() string {
}
s.WriteString("\n")
s.WriteString(successStyle.Render(" All systems operational\n"))
s.WriteString(successStyle.Render("[+] All systems operational\n"))
}
s.WriteString("\n⌨️ Press any key to return to menu\n")
s.WriteString("\n[KEYS] Press any key to return to menu\n")
return s.String()
}

View File

@@ -99,8 +99,8 @@ func (pm *PITRManager) EnablePITR(ctx context.Context, archiveDir string) error
return fmt.Errorf("failed to update postgresql.conf: %w", err)
}
pm.log.Info(" PITR configuration updated successfully")
pm.log.Warn("⚠️ PostgreSQL restart required for changes to take effect")
pm.log.Info("[OK] PITR configuration updated successfully")
pm.log.Warn("[WARN] PostgreSQL restart required for changes to take effect")
pm.log.Info("To restart PostgreSQL:")
pm.log.Info(" sudo systemctl restart postgresql")
pm.log.Info(" OR: sudo pg_ctlcluster <version> <cluster> restart")
@@ -132,8 +132,8 @@ func (pm *PITRManager) DisablePITR(ctx context.Context) error {
return fmt.Errorf("failed to update postgresql.conf: %w", err)
}
pm.log.Info(" PITR disabled successfully")
pm.log.Warn("⚠️ PostgreSQL restart required")
pm.log.Info("[OK] PITR disabled successfully")
pm.log.Warn("[WARN] PostgreSQL restart required")
return nil
}

View File

@@ -361,7 +361,7 @@ func (tm *TimelineManager) FormatTimelineTree(history *TimelineHistory) string {
var sb strings.Builder
sb.WriteString("Timeline Branching Structure:\n")
sb.WriteString("═════════════════════════════\n\n")
sb.WriteString("=============================\n\n")
// Build tree recursively
tm.formatTimelineNode(&sb, history, 1, 0, "")
@@ -378,9 +378,9 @@ func (tm *TimelineManager) formatTimelineNode(sb *strings.Builder, history *Time
// Format current node
indent := strings.Repeat(" ", depth)
marker := "├─"
marker := "+-"
if depth == 0 {
marker = ""
marker = "*"
}
sb.WriteString(fmt.Sprintf("%s%s Timeline %d", indent, marker, tl.TimelineID))