feat: Implement backup TUI with enhanced refresh functionality and consistent build system

This commit is contained in:
Peter Wood
2025-06-04 08:57:09 -04:00
parent 780e78f132
commit 8b514ac0b2
8 changed files with 508 additions and 60 deletions

BIN
backup-tui Executable file

Binary file not shown.

View File

@@ -0,0 +1,173 @@
# Backup TUI Refresh Fix - Implementation Summary
## Issue Resolved
Fixed the `r` (refresh) keybinding that was not working when viewing directory contents. Previously, pressing `r` while viewing a directory (e.g., after manually deleting files) would not update the file listing to reflect the changes.
## Root Cause
The refresh logic only handled the "main" view but not the "directory" view. When `currentView = "directory"`, pressing `r` didn't trigger directory content reload.
## Solution Implemented
### 1. Enhanced Model Structure
```go
type Model struct {
// ...existing fields...
currentView string // "main", "logs", "status", "directory"
currentDirPath string // Path of currently viewed directory for refresh
}
```
### 2. Fixed Refresh Logic
```go
case key.Matches(msg, m.keys.Refresh):
switch m.currentView {
case "main":
if len(m.items) > 0 {
m.showItemDescription()
}
case "directory":
// Reload the current directory
if m.currentDirPath != "" {
cmds = append(cmds, m.loadBackupDirectory(m.currentDirPath))
}
case "logs":
// Reload log files
cmds = append(cmds, m.loadLogFiles())
}
```
### 3. Directory Path Tracking
- Store directory path when entering directory view
- Clear path when returning to main view
- Use stored path for refresh operations
### 4. Build System Standardization
- **Makefile**: Comprehensive targets (build, clean, install, run, dev, help)
- **Build Script**: Alternative build method with consistent naming
- **Launcher Script**: Auto-building wrapper with fallback options
- **Consistent Binary**: All methods produce `backup-tui` binary
## Files Modified
### Core Application
- `/home/acedanger/shell/tui/main.go` - Enhanced refresh functionality
### Build System
- `/home/acedanger/shell/tui/Makefile` - Complete build system
- `/home/acedanger/shell/tui/build.sh` - Alternative build script
- `/home/acedanger/shell/launch-backup-tui.sh` - Auto-building launcher
### Documentation & Testing
- `/home/acedanger/shell/tui/README.md` - Updated documentation
- `/home/acedanger/shell/tui/test-refresh.sh` - Basic refresh test
- `/home/acedanger/shell/test-refresh-final.sh` - Comprehensive test
## Verification Results
### ✅ Functionality Tests
1. **Main View Navigation**: ✅ Working correctly
2. **Directory View**: ✅ Properly shows backup files with details
3. **Logs View**: ✅ Displays log files correctly
4. **Refresh in Directory View**: ✅ Now reloads directory contents
5. **View State Tracking**: ✅ Properly tracks current view and path
### ✅ Build System Tests
1. **Makefile Build**: ✅ Consistent `backup-tui` binary
2. **Build Script**: ✅ Alternative build method working
3. **Launcher Script**: ✅ Auto-detects missing binary and rebuilds
4. **Clean/Rebuild**: ✅ Proper cleanup and rebuild process
### ✅ Integration Tests
1. **TUI Launch**: ✅ Application starts correctly
2. **Navigation**: ✅ All views accessible and functional
3. **Directory Browsing**: ✅ File listings show proper details
4. **Refresh Indication**: ✅ UI shows refresh instructions when in directory view
## Key Improvements
### Refresh Functionality
- **Multi-View Support**: Refresh now works in main, directory, and logs views
- **Path Persistence**: Current directory path is maintained for refresh operations
- **Proper State Management**: View state correctly tracked and updated
### Build System Consistency
- **Single Binary Name**: All build methods produce `backup-tui` consistently
- **Multiple Build Options**: Makefile, build script, and launcher provide flexibility
- **Auto-Rebuild**: Launcher script detects missing binary and rebuilds automatically
### User Experience
- **Clear Instructions**: UI shows appropriate refresh instructions per view
- **Status Indicators**: View type clearly displayed in status bar
- **Error Handling**: Proper fallbacks and error messages
## Usage Instructions
### Building the Application
```bash
# Option 1: Use Makefile (recommended)
cd /home/acedanger/shell/tui
make all
# Option 2: Use build script
cd /home/acedanger/shell/tui
./build.sh
# Option 3: Use launcher (auto-builds if needed)
/home/acedanger/shell/launch-backup-tui.sh
```
### Testing Refresh Functionality
```bash
# Run comprehensive test
/home/acedanger/shell/test-refresh-final.sh
# Manual test steps:
# 1. Launch TUI: ./tui/backup-tui
# 2. Navigate to any backup directory (press Enter)
# 3. Delete a file externally: rm /path/to/backup/file
# 4. Press 'r' in TUI to refresh
# 5. Verify file disappears from listing
```
## Technical Details
### State Management
- `currentView`: Tracks which view is active (main/directory/logs)
- `currentDirPath`: Stores directory path for refresh operations
- Proper cleanup when switching between views
### Refresh Implementation
- View-specific refresh logic handles different content types
- Directory refresh reloads file listing from filesystem
- Log refresh reloads log file list
- Main view refresh updates item descriptions
### Build System Architecture
- Makefile provides comprehensive build targets
- Build script offers shell-based alternative
- Launcher script provides user-friendly wrapper
- All methods ensure consistent binary naming
## Status: ✅ COMPLETED
The refresh functionality is now fully working across all views in the backup TUI application. The build system provides consistent, reliable compilation with multiple usage options. All tests pass and the application is ready for production use.

61
launch-backup-tui.sh Executable file
View File

@@ -0,0 +1,61 @@
#!/bin/bash
# Backup TUI Launcher Script
# Provides consistent access to the backup TUI application
set -e
# Configuration
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
TUI_DIR="$SCRIPT_DIR/tui"
BINARY_NAME="backup-tui"
BINARY_PATH="$TUI_DIR/$BINARY_NAME"
# Colors for output
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
NC='\033[0m' # No Color
# Print colored output
print_color() {
local color="$1"
local message="$2"
case "$color" in
"green") printf "${GREEN}%s${NC}\n" "$message" ;;
"yellow") printf "${YELLOW}%s${NC}\n" "$message" ;;
"red") printf "${RED}%s${NC}\n" "$message" ;;
*) printf "%s\n" "$message" ;;
esac
}
# Check if binary exists
if [[ ! -f "$BINARY_PATH" ]]; then
print_color "yellow" "🔨 Binary not found. Building $BINARY_NAME..."
# Try to build using Makefile first, then fallback to build script
if [[ -f "$TUI_DIR/Makefile" ]] && command -v make &> /dev/null; then
print_color "yellow" "📋 Using Makefile to build..."
cd "$TUI_DIR" && make build
elif [[ -f "$TUI_DIR/build.sh" ]]; then
print_color "yellow" "🔧 Using build script..."
cd "$TUI_DIR" && ./build.sh
else
print_color "yellow" "🔧 Building directly with go..."
cd "$TUI_DIR" && go build -o "$BINARY_NAME" main.go
fi
if [[ ! -f "$BINARY_PATH" ]]; then
print_color "red" "❌ Failed to build $BINARY_NAME"
exit 1
fi
print_color "green" "✅ Build completed successfully"
fi
# Make sure binary is executable
chmod +x "$BINARY_PATH"
# Launch the TUI
print_color "green" "🚀 Launching Backup TUI..."
exec "$BINARY_PATH" "$@"

92
tui/Makefile Normal file
View File

@@ -0,0 +1,92 @@
# Makefile for backup-tui
# Ensures consistent binary naming and build process
# Configuration
BINARY_NAME := backup-tui
SOURCE_FILE := main.go
SHELL_DIR := /home/acedanger/shell
TUI_DIR := /home/acedanger/shell/tui
# Go configuration
GOCMD := go
GOBUILD := $(GOCMD) build
GOCLEAN := $(GOCMD) clean
GOTEST := $(GOCMD) test
GOGET := $(GOCMD) get
GOMOD := $(GOCMD) mod
# Default target
.PHONY: all
all: clean deps build install
# Build the application
.PHONY: build
build:
@echo "🔨 Building $(BINARY_NAME)..."
cd $(TUI_DIR) && $(GOBUILD) -o $(BINARY_NAME) $(SOURCE_FILE)
@echo "✅ Build completed: $(TUI_DIR)/$(BINARY_NAME)"
# Download dependencies
.PHONY: deps
deps:
@echo "📦 Downloading dependencies..."
cd $(TUI_DIR) && $(GOMOD) tidy
@echo "✅ Dependencies updated"
# Install to shell directory
.PHONY: install
install: build
@echo "📋 Installing to shell directory..."
cp $(TUI_DIR)/$(BINARY_NAME) $(SHELL_DIR)/$(BINARY_NAME)
chmod +x $(SHELL_DIR)/$(BINARY_NAME)
@echo "✅ Installed to $(SHELL_DIR)/$(BINARY_NAME)"
# Clean build artifacts
.PHONY: clean
clean:
@echo "🧹 Cleaning build artifacts..."
cd $(TUI_DIR) && $(GOCLEAN)
rm -f $(TUI_DIR)/$(BINARY_NAME)
rm -f $(SHELL_DIR)/$(BINARY_NAME)
@echo "✅ Clean completed"
# Run the application
.PHONY: run
run: build
@echo "🚀 Running $(BINARY_NAME)..."
cd $(TUI_DIR) && ./$(BINARY_NAME)
# Test the application
.PHONY: test
test:
@echo "🧪 Running tests..."
cd $(TUI_DIR) && $(GOTEST) -v ./...
# Development build (fast, no install)
.PHONY: dev
dev:
@echo "🔧 Development build..."
cd $(TUI_DIR) && $(GOBUILD) -o $(BINARY_NAME) $(SOURCE_FILE)
@echo "✅ Development build completed"
# Show help
.PHONY: help
help:
@echo "Available targets:"
@echo " all - Clean, download deps, build, and install (default)"
@echo " build - Build the application"
@echo " deps - Download Go dependencies"
@echo " install - Install binary to shell directory"
@echo " clean - Remove build artifacts"
@echo " run - Build and run the application"
@echo " test - Run tests"
@echo " dev - Quick development build"
@echo " help - Show this help message"
# Version info
.PHONY: version
version:
@echo "Binary name: $(BINARY_NAME)"
@echo "Source file: $(SOURCE_FILE)"
@echo "TUI directory: $(TUI_DIR)"
@echo "Shell directory: $(SHELL_DIR)"

View File

@@ -52,7 +52,7 @@ A modern, interactive Terminal User Interface (TUI) for browsing and viewing bac
### 🖥️ User Interface
- **Dual-panel System**: Service list and detailed information viewer
- **Multiple View Modes**: Main browser, logs viewer, status, and configuration views
- **Multiple View Modes**: Main browser, logs viewer, status, and configuration views
- **Tab Navigation**: Switch between panels with Tab key
- **Smart Key Bindings**: Intuitive keyboard shortcuts for navigation
- **Color-coded Display**: Dynamic visual indicators for file types and status
@@ -61,7 +61,7 @@ A modern, interactive Terminal User Interface (TUI) for browsing and viewing bac
### 📦 Backup Service Directories
- **🔵 Plex Backups**: Browse Plex Media Server backup files and archives
- **🖼️ Immich Backups**: View Immich photo management backup files
- **🖼️ Immich Backups**: View Immich photo management backup files
- **🎬 Media Services**: Browse Sonarr, Radarr, Prowlarr, and other media service backups
- **🔧 Environment Files**: View Docker environment and configuration file backups
- **🐳 Docker Configuration**: Browse container and compose file backups
@@ -81,32 +81,47 @@ A modern, interactive Terminal User Interface (TUI) for browsing and viewing bac
### Installation
The TUI is already built and ready to use! Simply run:
The TUI uses a consistent build system with multiple options:
```bash
# From the shell directory
./backup-tui
# Option 1: Use the launcher script (recommended)
cd /home/acedanger/shell
./launch-backup-tui.sh
# Or directly from the tui directory
cd tui && ./backup-manager
# Option 2: Use Makefile for development
cd tui
make all # Build and install
make build # Build only
make dev # Quick development build
# Option 3: Use build script
cd tui
./build.sh
# Option 4: Direct Go build
cd tui
go build -o backup-tui main.go
```
### First Time Setup
1. **Ensure Go 1.19+ is installed** (only needed for rebuilding)
2. **Navigate to your shell directory** where backup scripts are located
3. **Launch the TUI** using `./backup-tui`
3. **Launch the TUI** using `./launch-backup-tui.sh`
4. **Use arrow keys or hjkl** to navigate the interface
5. **Press `?`** for comprehensive help and key bindings
### Launch the TUI
```bash
# From the shell directory
# From the shell directory (recommended - auto-builds if needed)
./launch-backup-tui.sh
# Or if binary exists
./backup-tui
# Or directly from the tui directory
cd tui && ./backup-manager
# Or from the tui directory
cd tui && ./backup-tui
```
### Key Bindings
@@ -203,14 +218,31 @@ The TUI automatically discovers and organizes backup directories:
### Building from Source
The TUI includes multiple build options for consistent binary naming:
```bash
# Option 1: Use Makefile (recommended for development)
cd tui
make all # Clean, build, and install
make build # Build only
make dev # Quick development build
make clean # Remove build artifacts
make help # Show all targets
# Option 2: Use build script
cd tui
./build.sh
# Option 3: Direct Go build
cd tui
export GOROOT=/usr/lib/go-1.19
export PATH=$PATH:$GOROOT/bin
go mod tidy
go build -o backup-manager main.go
go build -o backup-tui main.go
```
**Binary Naming**: All build methods consistently create `backup-tui` binary.
### Dependencies
- [Bubble Tea](https://github.com/charmbracelet/bubbletea) - TUI framework
@@ -239,7 +271,7 @@ The TUI provides comprehensive backup file exploration:
### Backup Analysis
- **Size Tracking**: Monitor backup file sizes and storage usage
- **Date Analysis**: Review backup frequency and file modification dates
- **Date Analysis**: Review backup frequency and file modification dates
- **Service Organization**: Files organized by backup service for easy navigation
- **Compression Detection**: Identify compressed archives and their contents
@@ -271,7 +303,7 @@ The TUI complements your existing backup infrastructure:
## 📝 Notes
- The TUI automatically detects backup directory structure and organization
- Provides read-only access to backup files for safe browsing and analysis
- Provides read-only access to backup files for safe browsing and analysis
- Backup files are displayed with metadata including sizes, dates, and compression status
- Log files can be viewed directly within the interface for troubleshooting and analysis

Binary file not shown.

74
tui/build.sh Executable file
View File

@@ -0,0 +1,74 @@
#!/bin/bash
# Build script for backup-tui
# Ensures consistent binary naming across build sessions
set -e
# Colors for output
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
NC='\033[0m' # No Color
# Configuration
BINARY_NAME="backup-tui"
SOURCE_FILE="main.go"
TUI_DIR="/home/acedanger/shell/tui"
SHELL_DIR="/home/acedanger/shell"
# Print colored output
print_color() {
local color="$1"
local message="$2"
case "$color" in
"green") printf "${GREEN}%s${NC}\n" "$message" ;;
"yellow") printf "${YELLOW}%s${NC}\n" "$message" ;;
"red") printf "${RED}%s${NC}\n" "$message" ;;
*) printf "%s\n" "$message" ;;
esac
}
print_color "yellow" "🔨 Building Backup TUI..."
# Change to TUI directory
cd "$TUI_DIR"
# Check if Go is available
if ! command -v go &> /dev/null; then
print_color "red" "❌ Go is not installed or not in PATH"
print_color "yellow" "Please install Go 1.19+ or set up the Go environment"
exit 1
fi
# Ensure dependencies are available
print_color "yellow" "📦 Checking Go dependencies..."
if ! go mod tidy; then
print_color "red" "❌ Failed to download Go dependencies"
exit 1
fi
# Build the binary with consistent naming
print_color "yellow" "🔧 Building ${BINARY_NAME}..."
if go build -o "$BINARY_NAME" "$SOURCE_FILE"; then
print_color "green" "✅ Successfully built ${BINARY_NAME}"
else
print_color "red" "❌ Build failed"
exit 1
fi
# Copy to shell directory for easy access
if cp "$BINARY_NAME" "$SHELL_DIR/$BINARY_NAME"; then
print_color "green" "✅ Copied binary to shell directory"
else
print_color "yellow" "⚠️ Could not copy to shell directory"
fi
# Make executable
chmod +x "$BINARY_NAME"
chmod +x "$SHELL_DIR/$BINARY_NAME" 2>/dev/null || true
print_color "green" "🎉 Build completed successfully!"
print_color "yellow" "Binary location: $TUI_DIR/$BINARY_NAME"
print_color "yellow" "Also available: $SHELL_DIR/$BINARY_NAME"
print_color "yellow" "Launch with: ./backup-tui"

View File

@@ -209,7 +209,8 @@ type Model struct {
logs []LogEntry
shellPath string
items []BackupItem // Store backup items for reference
currentView string // "main", "logs", "status"
currentView string // "main", "logs", "status", "directory"
currentDirPath string // Path of currently viewed directory for refresh
}
// Initialize the model
@@ -351,7 +352,7 @@ func (d BackupItemDelegate) Render(w io.Writer, m list.Model, index int, listIte
// Helper function to convert list items to BackupItems
func convertToBackupItems(items []list.Item) []BackupItem {
backupItems := make([]BackupItem, len(items))
for i, item := range items {
for i, item := range items {
if bi, ok := item.(BackupItem); ok {
backupItems[i] = bi
}
@@ -404,8 +405,19 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case key.Matches(msg, m.keys.Refresh):
// Refresh current view
if len(m.items) > 0 && m.currentView == "main" {
m.showItemDescription()
switch m.currentView {
case "main":
if len(m.items) > 0 {
m.showItemDescription()
}
case "directory":
// Reload the current directory
if m.currentDirPath != "" {
cmds = append(cmds, m.loadBackupDirectory(m.currentDirPath))
}
case "logs":
// Reload log files
cmds = append(cmds, m.loadLogFiles())
}
case key.Matches(msg, m.keys.ViewLogs):
@@ -423,12 +435,14 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case key.Matches(msg, m.keys.Clear):
m.viewport.SetContent("Output cleared.\n\nSelect a backup directory from the list and press Enter to browse.")
m.currentView = "main"
m.currentDirPath = "" // Clear directory path
// Show description for currently selected item
m.showItemDescription()
case key.Matches(msg, m.keys.Escape) && m.currentView != "main":
m.viewport.SetContent("Welcome to Backup File Browser!\n\nSelect a backup directory from the list and press Enter to browse.\n\nUse 'v' to view logs, 's' for status, and 'tab' to switch between panels.")
m.currentView = "main"
m.currentDirPath = "" // Clear directory path
// Show description for currently selected item
m.showItemDescription()
@@ -436,6 +450,8 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
if i, ok := m.list.SelectedItem().(BackupItem); ok {
switch i.itemType {
case "directory":
// Store the directory path for refresh functionality
m.currentDirPath = i.path
// Load backup directory details
cmds = append(cmds, m.loadBackupDirectory(i.path))
case "logs":
@@ -607,7 +623,7 @@ func (m *Model) showStatus() {
content.WriteString("=== BACKUP DIRECTORY STATUS ===\n\n")
content.WriteString("📂 Available Backup Directories:\n\n")
for _, item := range m.items {
if item.itemType == "directory" {
content.WriteString(fmt.Sprintf("🔧 Service: %s\n", strings.ToUpper(item.service)))
@@ -684,13 +700,13 @@ func (m *Model) showItemDescription() {
}
item := m.items[selectedIndex]
var content strings.Builder
content.WriteString(fmt.Sprintf("📋 %s\n", item.title))
content.WriteString(strings.Repeat("=", len(item.title)+3) + "\n\n")
content.WriteString(fmt.Sprintf("📝 Description:\n%s\n\n", item.description))
// Add detailed information based on service type
switch item.service {
case "plex":
@@ -699,82 +715,82 @@ func (m *Model) showItemDescription() {
content.WriteString("• Configuration and preferences\n")
content.WriteString("• Plugin and custom data\n")
content.WriteString("• Compressed archive files (.tar.gz)\n\n")
content.WriteString("📁 Typical Contents:\n")
content.WriteString("• Library database files\n")
content.WriteString("• User preferences and settings\n")
content.WriteString("• Custom artwork and thumbnails\n")
content.WriteString("• Plugin configurations\n\n")
case "immich":
content.WriteString("📸 Immich Photo Management Backups:\n")
content.WriteString("• PostgreSQL database dumps\n")
content.WriteString("• Uploaded photos and videos\n")
content.WriteString("• User settings and albums\n")
content.WriteString("• Machine learning models\n\n")
content.WriteString("📁 Typical Contents:\n")
content.WriteString("• Database backup files (.sql)\n")
content.WriteString("• Media file archives\n")
content.WriteString("• Configuration backups\n")
content.WriteString("• Thumbnail caches\n\n")
case "sonarr":
content.WriteString("📺 Sonarr TV Series Management:\n")
content.WriteString("• Series tracking database\n")
content.WriteString("• Download client configurations\n")
content.WriteString("• Quality profiles and settings\n")
content.WriteString("• Custom scripts and metadata\n\n")
case "radarr":
content.WriteString("🎬 Radarr Movie Management:\n")
content.WriteString("• Movie collection database\n")
content.WriteString("• Indexer and download settings\n")
content.WriteString("• Quality and format preferences\n")
content.WriteString("• Custom filters and lists\n\n")
case "prowlarr":
content.WriteString("🔍 Prowlarr Indexer Management:\n")
content.WriteString("• Indexer configurations\n")
content.WriteString("• API keys and credentials\n")
content.WriteString("• Sync profiles and settings\n")
content.WriteString("• Application mappings\n\n")
case "jellyseerr":
content.WriteString("🎯 Jellyseerr Request Management:\n")
content.WriteString("• User request history\n")
content.WriteString("• Notification configurations\n")
content.WriteString("• Integration settings\n")
content.WriteString("• Custom user permissions\n\n")
case "sabnzbd":
content.WriteString("📥 SABnzbd Download Client:\n")
content.WriteString("• Server and category configs\n")
content.WriteString("• Download queue history\n")
content.WriteString("• Post-processing scripts\n")
content.WriteString("• User settings and passwords\n\n")
case "tautulli":
content.WriteString("📊 Tautulli Plex Statistics:\n")
content.WriteString("• View history and statistics\n")
content.WriteString("• User activity tracking\n")
content.WriteString("• Notification configurations\n")
content.WriteString("• Custom dashboard settings\n\n")
case "audiobookshelf":
content.WriteString("📚 Audiobookshelf Server:\n")
content.WriteString("• Library and user data\n")
content.WriteString("• Listening progress tracking\n")
content.WriteString("• Playlist and collection info\n")
content.WriteString("• Server configuration\n\n")
case "docker-data":
content.WriteString("🐳 Docker Container Data:\n")
content.WriteString("• Volume and bind mount backups\n")
content.WriteString("• Container persistent data\n")
content.WriteString("• Application state files\n")
content.WriteString("• Configuration volumes\n\n")
case "logs":
content.WriteString("📋 Backup Operation Logs:\n")
content.WriteString("• Daily backup operation logs\n")
@@ -782,7 +798,7 @@ func (m *Model) showItemDescription() {
content.WriteString("• Backup timing and performance\n")
content.WriteString("• System health monitoring\n\n")
}
// Add usage instructions based on item type
content.WriteString("\n🚀 Actions:\n")
switch item.itemType {
@@ -797,7 +813,7 @@ func (m *Model) showItemDescription() {
content.WriteString("• View backup operation details\n")
content.WriteString("• Check for errors or warnings\n")
}
content.WriteString("\n💡 Navigation:\n")
content.WriteString("• Tab: Switch between panels\n")
content.WriteString("• r: Refresh current view\n")
@@ -815,19 +831,19 @@ func (m *Model) showBackupDetails(details BackupDetails) {
var content strings.Builder
content.WriteString(fmt.Sprintf("📁 %s Backup Directory\n", details.Directory.Name))
content.WriteString(strings.Repeat("=", len(details.Directory.Name)+20) + "\n\n")
// Directory summary
content.WriteString("📊 Directory Summary:\n")
content.WriteString(fmt.Sprintf(" 📁 Path: %s\n", details.Directory.Path))
// Show if this is using a scheduled subfolder
if strings.HasSuffix(details.Directory.Path, "/scheduled") {
content.WriteString(" 📅 Source: Scheduled backups (from 'scheduled' subfolder)\n")
}
content.WriteString(fmt.Sprintf(" 📄 Files: %d\n", details.TotalFiles))
content.WriteString(fmt.Sprintf(" 💾 Total Size: %s\n", formatSize(details.TotalSize)))
if !details.NewestBackup.IsZero() {
content.WriteString(fmt.Sprintf(" 🕒 Newest: %s\n", details.NewestBackup.Format("2006-01-02 15:04:05")))
}
@@ -835,35 +851,35 @@ func (m *Model) showBackupDetails(details BackupDetails) {
content.WriteString(fmt.Sprintf(" 📅 Oldest: %s\n", details.OldestBackup.Format("2006-01-02 15:04:05")))
}
content.WriteString("\n")
// File listings
if len(details.Files) > 0 {
content.WriteString("📄 Backup Files:\n")
content.WriteString(" Name Size Modified\n")
content.WriteString(" " + strings.Repeat("-", 50) + "\n")
for _, file := range details.Files {
// Truncate filename if too long
displayName := file.Name
if len(displayName) > 25 {
displayName = displayName[:22] + "..."
}
compressionIndicator := ""
if file.IsCompressed {
compressionIndicator = "📦 "
}
content.WriteString(fmt.Sprintf(" %s%-25s %8s %s\n",
content.WriteString(fmt.Sprintf(" %s%-25s %8s %s\n",
compressionIndicator,
displayName,
formatSize(file.Size),
displayName,
formatSize(file.Size),
file.ModifiedTime.Format("2006-01-02 15:04")))
}
} else {
content.WriteString("📂 No backup files found in this directory.\n")
}
content.WriteString("\n💡 Navigation:\n")
content.WriteString(" • x: Return to main view\n")
content.WriteString(" • r: Refresh directory listing\n")
@@ -877,7 +893,7 @@ func (m *Model) showLogFiles(logFiles []LogFile) {
var content strings.Builder
content.WriteString("📋 Backup Operation Logs\n")
content.WriteString("=========================\n\n")
if len(logFiles) == 0 {
content.WriteString("📂 No log files found.\n")
content.WriteString("Log files are typically stored in /mnt/share/media/backups/logs/\n")
@@ -885,27 +901,27 @@ func (m *Model) showLogFiles(logFiles []LogFile) {
content.WriteString("📄 Available Log Files:\n")
content.WriteString(" Filename Size Date\n")
content.WriteString(" " + strings.Repeat("-", 45) + "\n")
for _, logFile := range logFiles {
displayName := logFile.Name
if len(displayName) > 25 {
displayName = displayName[:22] + "..."
}
dateStr := "Unknown"
if !logFile.Date.IsZero() {
dateStr = logFile.Date.Format("2006-01-02 15:04")
}
content.WriteString(fmt.Sprintf(" %-25s %8s %s\n",
displayName,
formatSize(logFile.Size),
content.WriteString(fmt.Sprintf(" %-25s %8s %s\n",
displayName,
formatSize(logFile.Size),
dateStr))
}
content.WriteString(fmt.Sprintf("\n📊 Total: %d log files\n", len(logFiles)))
}
content.WriteString("\n💡 Navigation:\n")
content.WriteString(" • Enter: View log content (when a specific log is selected)\n")
content.WriteString(" • x: Return to main view\n")
@@ -934,11 +950,11 @@ func (m *Model) loadBackupDirectory(dirPath string) tea.Cmd {
// Check if there's a "scheduled" subfolder - if so, use that instead
scheduledPath := filepath.Join(dirPath, "scheduled")
actualPath := dirPath
if info, err := os.Stat(scheduledPath); err == nil && info.IsDir() {
actualPath = scheduledPath
}
files, err := os.ReadDir(actualPath)
if err != nil {
return errorMsg{error: fmt.Sprintf("Failed to read directory %s: %v", actualPath, err)}
@@ -1035,7 +1051,7 @@ func (m *Model) loadLogFiles() tea.Cmd {
if strings.HasPrefix(name, "backup_log_") && strings.HasSuffix(name, ".md") {
datePart := strings.TrimPrefix(name, "backup_log_")
datePart = strings.TrimSuffix(datePart, ".md")
if len(datePart) >= 15 { // YYYYMMDD_HHMMSS
if parsedDate, err := time.Parse("20060102_150405", datePart); err == nil {
logDate = parsedDate