mirror of
https://github.com/acedanger/shell.git
synced 2025-12-05 21:40:12 -08:00
feat: Implement backup TUI with enhanced refresh functionality and consistent build system
This commit is contained in:
BIN
backup-tui
Executable file
BIN
backup-tui
Executable file
Binary file not shown.
173
docs/backup-tui-refresh-fix-summary.md
Normal file
173
docs/backup-tui-refresh-fix-summary.md
Normal 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
61
launch-backup-tui.sh
Executable 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
92
tui/Makefile
Normal 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)"
|
||||||
@@ -52,7 +52,7 @@ A modern, interactive Terminal User Interface (TUI) for browsing and viewing bac
|
|||||||
### 🖥️ User Interface
|
### 🖥️ User Interface
|
||||||
|
|
||||||
- **Dual-panel System**: Service list and detailed information viewer
|
- **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
|
- **Tab Navigation**: Switch between panels with Tab key
|
||||||
- **Smart Key Bindings**: Intuitive keyboard shortcuts for navigation
|
- **Smart Key Bindings**: Intuitive keyboard shortcuts for navigation
|
||||||
- **Color-coded Display**: Dynamic visual indicators for file types and status
|
- **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
|
### 📦 Backup Service Directories
|
||||||
|
|
||||||
- **🔵 Plex Backups**: Browse Plex Media Server backup files and archives
|
- **🔵 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
|
- **🎬 Media Services**: Browse Sonarr, Radarr, Prowlarr, and other media service backups
|
||||||
- **🔧 Environment Files**: View Docker environment and configuration file backups
|
- **🔧 Environment Files**: View Docker environment and configuration file backups
|
||||||
- **🐳 Docker Configuration**: Browse container and compose 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
|
### Installation
|
||||||
|
|
||||||
The TUI is already built and ready to use! Simply run:
|
The TUI uses a consistent build system with multiple options:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# From the shell directory
|
# Option 1: Use the launcher script (recommended)
|
||||||
./backup-tui
|
cd /home/acedanger/shell
|
||||||
|
./launch-backup-tui.sh
|
||||||
|
|
||||||
# Or directly from the tui directory
|
# Option 2: Use Makefile for development
|
||||||
cd tui && ./backup-manager
|
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
|
### First Time Setup
|
||||||
|
|
||||||
1. **Ensure Go 1.19+ is installed** (only needed for rebuilding)
|
1. **Ensure Go 1.19+ is installed** (only needed for rebuilding)
|
||||||
2. **Navigate to your shell directory** where backup scripts are located
|
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
|
4. **Use arrow keys or hjkl** to navigate the interface
|
||||||
5. **Press `?`** for comprehensive help and key bindings
|
5. **Press `?`** for comprehensive help and key bindings
|
||||||
|
|
||||||
### Launch the TUI
|
### Launch the TUI
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# From the shell directory
|
# From the shell directory (recommended - auto-builds if needed)
|
||||||
|
./launch-backup-tui.sh
|
||||||
|
|
||||||
|
# Or if binary exists
|
||||||
./backup-tui
|
./backup-tui
|
||||||
|
|
||||||
# Or directly from the tui directory
|
# Or from the tui directory
|
||||||
cd tui && ./backup-manager
|
cd tui && ./backup-tui
|
||||||
```
|
```
|
||||||
|
|
||||||
### Key Bindings
|
### Key Bindings
|
||||||
@@ -203,14 +218,31 @@ The TUI automatically discovers and organizes backup directories:
|
|||||||
|
|
||||||
### Building from Source
|
### Building from Source
|
||||||
|
|
||||||
|
The TUI includes multiple build options for consistent binary naming:
|
||||||
|
|
||||||
```bash
|
```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
|
cd tui
|
||||||
export GOROOT=/usr/lib/go-1.19
|
export GOROOT=/usr/lib/go-1.19
|
||||||
export PATH=$PATH:$GOROOT/bin
|
export PATH=$PATH:$GOROOT/bin
|
||||||
go mod tidy
|
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
|
### Dependencies
|
||||||
|
|
||||||
- [Bubble Tea](https://github.com/charmbracelet/bubbletea) - TUI framework
|
- [Bubble Tea](https://github.com/charmbracelet/bubbletea) - TUI framework
|
||||||
@@ -239,7 +271,7 @@ The TUI provides comprehensive backup file exploration:
|
|||||||
### Backup Analysis
|
### Backup Analysis
|
||||||
|
|
||||||
- **Size Tracking**: Monitor backup file sizes and storage usage
|
- **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
|
- **Service Organization**: Files organized by backup service for easy navigation
|
||||||
- **Compression Detection**: Identify compressed archives and their contents
|
- **Compression Detection**: Identify compressed archives and their contents
|
||||||
|
|
||||||
@@ -271,7 +303,7 @@ The TUI complements your existing backup infrastructure:
|
|||||||
## 📝 Notes
|
## 📝 Notes
|
||||||
|
|
||||||
- The TUI automatically detects backup directory structure and organization
|
- 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
|
- 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
|
- Log files can be viewed directly within the interface for troubleshooting and analysis
|
||||||
|
|
||||||
|
|||||||
BIN
tui/backup-tui
BIN
tui/backup-tui
Binary file not shown.
74
tui/build.sh
Executable file
74
tui/build.sh
Executable 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"
|
||||||
108
tui/main.go
108
tui/main.go
@@ -209,7 +209,8 @@ type Model struct {
|
|||||||
logs []LogEntry
|
logs []LogEntry
|
||||||
shellPath string
|
shellPath string
|
||||||
items []BackupItem // Store backup items for reference
|
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
|
// 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
|
// Helper function to convert list items to BackupItems
|
||||||
func convertToBackupItems(items []list.Item) []BackupItem {
|
func convertToBackupItems(items []list.Item) []BackupItem {
|
||||||
backupItems := make([]BackupItem, len(items))
|
backupItems := make([]BackupItem, len(items))
|
||||||
for i, item := range items {
|
for i, item := range items {
|
||||||
if bi, ok := item.(BackupItem); ok {
|
if bi, ok := item.(BackupItem); ok {
|
||||||
backupItems[i] = bi
|
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):
|
case key.Matches(msg, m.keys.Refresh):
|
||||||
// Refresh current view
|
// Refresh current view
|
||||||
if len(m.items) > 0 && m.currentView == "main" {
|
switch m.currentView {
|
||||||
m.showItemDescription()
|
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):
|
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):
|
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.viewport.SetContent("Output cleared.\n\nSelect a backup directory from the list and press Enter to browse.")
|
||||||
m.currentView = "main"
|
m.currentView = "main"
|
||||||
|
m.currentDirPath = "" // Clear directory path
|
||||||
// Show description for currently selected item
|
// Show description for currently selected item
|
||||||
m.showItemDescription()
|
m.showItemDescription()
|
||||||
|
|
||||||
case key.Matches(msg, m.keys.Escape) && m.currentView != "main":
|
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.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.currentView = "main"
|
||||||
|
m.currentDirPath = "" // Clear directory path
|
||||||
// Show description for currently selected item
|
// Show description for currently selected item
|
||||||
m.showItemDescription()
|
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 {
|
if i, ok := m.list.SelectedItem().(BackupItem); ok {
|
||||||
switch i.itemType {
|
switch i.itemType {
|
||||||
case "directory":
|
case "directory":
|
||||||
|
// Store the directory path for refresh functionality
|
||||||
|
m.currentDirPath = i.path
|
||||||
// Load backup directory details
|
// Load backup directory details
|
||||||
cmds = append(cmds, m.loadBackupDirectory(i.path))
|
cmds = append(cmds, m.loadBackupDirectory(i.path))
|
||||||
case "logs":
|
case "logs":
|
||||||
@@ -607,7 +623,7 @@ func (m *Model) showStatus() {
|
|||||||
content.WriteString("=== BACKUP DIRECTORY STATUS ===\n\n")
|
content.WriteString("=== BACKUP DIRECTORY STATUS ===\n\n")
|
||||||
|
|
||||||
content.WriteString("📂 Available Backup Directories:\n\n")
|
content.WriteString("📂 Available Backup Directories:\n\n")
|
||||||
|
|
||||||
for _, item := range m.items {
|
for _, item := range m.items {
|
||||||
if item.itemType == "directory" {
|
if item.itemType == "directory" {
|
||||||
content.WriteString(fmt.Sprintf("🔧 Service: %s\n", strings.ToUpper(item.service)))
|
content.WriteString(fmt.Sprintf("🔧 Service: %s\n", strings.ToUpper(item.service)))
|
||||||
@@ -684,13 +700,13 @@ func (m *Model) showItemDescription() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
item := m.items[selectedIndex]
|
item := m.items[selectedIndex]
|
||||||
|
|
||||||
var content strings.Builder
|
var content strings.Builder
|
||||||
content.WriteString(fmt.Sprintf("📋 %s\n", item.title))
|
content.WriteString(fmt.Sprintf("📋 %s\n", item.title))
|
||||||
content.WriteString(strings.Repeat("=", len(item.title)+3) + "\n\n")
|
content.WriteString(strings.Repeat("=", len(item.title)+3) + "\n\n")
|
||||||
|
|
||||||
content.WriteString(fmt.Sprintf("📝 Description:\n%s\n\n", item.description))
|
content.WriteString(fmt.Sprintf("📝 Description:\n%s\n\n", item.description))
|
||||||
|
|
||||||
// Add detailed information based on service type
|
// Add detailed information based on service type
|
||||||
switch item.service {
|
switch item.service {
|
||||||
case "plex":
|
case "plex":
|
||||||
@@ -699,82 +715,82 @@ func (m *Model) showItemDescription() {
|
|||||||
content.WriteString("• Configuration and preferences\n")
|
content.WriteString("• Configuration and preferences\n")
|
||||||
content.WriteString("• Plugin and custom data\n")
|
content.WriteString("• Plugin and custom data\n")
|
||||||
content.WriteString("• Compressed archive files (.tar.gz)\n\n")
|
content.WriteString("• Compressed archive files (.tar.gz)\n\n")
|
||||||
|
|
||||||
content.WriteString("📁 Typical Contents:\n")
|
content.WriteString("📁 Typical Contents:\n")
|
||||||
content.WriteString("• Library database files\n")
|
content.WriteString("• Library database files\n")
|
||||||
content.WriteString("• User preferences and settings\n")
|
content.WriteString("• User preferences and settings\n")
|
||||||
content.WriteString("• Custom artwork and thumbnails\n")
|
content.WriteString("• Custom artwork and thumbnails\n")
|
||||||
content.WriteString("• Plugin configurations\n\n")
|
content.WriteString("• Plugin configurations\n\n")
|
||||||
|
|
||||||
case "immich":
|
case "immich":
|
||||||
content.WriteString("📸 Immich Photo Management Backups:\n")
|
content.WriteString("📸 Immich Photo Management Backups:\n")
|
||||||
content.WriteString("• PostgreSQL database dumps\n")
|
content.WriteString("• PostgreSQL database dumps\n")
|
||||||
content.WriteString("• Uploaded photos and videos\n")
|
content.WriteString("• Uploaded photos and videos\n")
|
||||||
content.WriteString("• User settings and albums\n")
|
content.WriteString("• User settings and albums\n")
|
||||||
content.WriteString("• Machine learning models\n\n")
|
content.WriteString("• Machine learning models\n\n")
|
||||||
|
|
||||||
content.WriteString("📁 Typical Contents:\n")
|
content.WriteString("📁 Typical Contents:\n")
|
||||||
content.WriteString("• Database backup files (.sql)\n")
|
content.WriteString("• Database backup files (.sql)\n")
|
||||||
content.WriteString("• Media file archives\n")
|
content.WriteString("• Media file archives\n")
|
||||||
content.WriteString("• Configuration backups\n")
|
content.WriteString("• Configuration backups\n")
|
||||||
content.WriteString("• Thumbnail caches\n\n")
|
content.WriteString("• Thumbnail caches\n\n")
|
||||||
|
|
||||||
case "sonarr":
|
case "sonarr":
|
||||||
content.WriteString("📺 Sonarr TV Series Management:\n")
|
content.WriteString("📺 Sonarr TV Series Management:\n")
|
||||||
content.WriteString("• Series tracking database\n")
|
content.WriteString("• Series tracking database\n")
|
||||||
content.WriteString("• Download client configurations\n")
|
content.WriteString("• Download client configurations\n")
|
||||||
content.WriteString("• Quality profiles and settings\n")
|
content.WriteString("• Quality profiles and settings\n")
|
||||||
content.WriteString("• Custom scripts and metadata\n\n")
|
content.WriteString("• Custom scripts and metadata\n\n")
|
||||||
|
|
||||||
case "radarr":
|
case "radarr":
|
||||||
content.WriteString("🎬 Radarr Movie Management:\n")
|
content.WriteString("🎬 Radarr Movie Management:\n")
|
||||||
content.WriteString("• Movie collection database\n")
|
content.WriteString("• Movie collection database\n")
|
||||||
content.WriteString("• Indexer and download settings\n")
|
content.WriteString("• Indexer and download settings\n")
|
||||||
content.WriteString("• Quality and format preferences\n")
|
content.WriteString("• Quality and format preferences\n")
|
||||||
content.WriteString("• Custom filters and lists\n\n")
|
content.WriteString("• Custom filters and lists\n\n")
|
||||||
|
|
||||||
case "prowlarr":
|
case "prowlarr":
|
||||||
content.WriteString("🔍 Prowlarr Indexer Management:\n")
|
content.WriteString("🔍 Prowlarr Indexer Management:\n")
|
||||||
content.WriteString("• Indexer configurations\n")
|
content.WriteString("• Indexer configurations\n")
|
||||||
content.WriteString("• API keys and credentials\n")
|
content.WriteString("• API keys and credentials\n")
|
||||||
content.WriteString("• Sync profiles and settings\n")
|
content.WriteString("• Sync profiles and settings\n")
|
||||||
content.WriteString("• Application mappings\n\n")
|
content.WriteString("• Application mappings\n\n")
|
||||||
|
|
||||||
case "jellyseerr":
|
case "jellyseerr":
|
||||||
content.WriteString("🎯 Jellyseerr Request Management:\n")
|
content.WriteString("🎯 Jellyseerr Request Management:\n")
|
||||||
content.WriteString("• User request history\n")
|
content.WriteString("• User request history\n")
|
||||||
content.WriteString("• Notification configurations\n")
|
content.WriteString("• Notification configurations\n")
|
||||||
content.WriteString("• Integration settings\n")
|
content.WriteString("• Integration settings\n")
|
||||||
content.WriteString("• Custom user permissions\n\n")
|
content.WriteString("• Custom user permissions\n\n")
|
||||||
|
|
||||||
case "sabnzbd":
|
case "sabnzbd":
|
||||||
content.WriteString("📥 SABnzbd Download Client:\n")
|
content.WriteString("📥 SABnzbd Download Client:\n")
|
||||||
content.WriteString("• Server and category configs\n")
|
content.WriteString("• Server and category configs\n")
|
||||||
content.WriteString("• Download queue history\n")
|
content.WriteString("• Download queue history\n")
|
||||||
content.WriteString("• Post-processing scripts\n")
|
content.WriteString("• Post-processing scripts\n")
|
||||||
content.WriteString("• User settings and passwords\n\n")
|
content.WriteString("• User settings and passwords\n\n")
|
||||||
|
|
||||||
case "tautulli":
|
case "tautulli":
|
||||||
content.WriteString("📊 Tautulli Plex Statistics:\n")
|
content.WriteString("📊 Tautulli Plex Statistics:\n")
|
||||||
content.WriteString("• View history and statistics\n")
|
content.WriteString("• View history and statistics\n")
|
||||||
content.WriteString("• User activity tracking\n")
|
content.WriteString("• User activity tracking\n")
|
||||||
content.WriteString("• Notification configurations\n")
|
content.WriteString("• Notification configurations\n")
|
||||||
content.WriteString("• Custom dashboard settings\n\n")
|
content.WriteString("• Custom dashboard settings\n\n")
|
||||||
|
|
||||||
case "audiobookshelf":
|
case "audiobookshelf":
|
||||||
content.WriteString("📚 Audiobookshelf Server:\n")
|
content.WriteString("📚 Audiobookshelf Server:\n")
|
||||||
content.WriteString("• Library and user data\n")
|
content.WriteString("• Library and user data\n")
|
||||||
content.WriteString("• Listening progress tracking\n")
|
content.WriteString("• Listening progress tracking\n")
|
||||||
content.WriteString("• Playlist and collection info\n")
|
content.WriteString("• Playlist and collection info\n")
|
||||||
content.WriteString("• Server configuration\n\n")
|
content.WriteString("• Server configuration\n\n")
|
||||||
|
|
||||||
case "docker-data":
|
case "docker-data":
|
||||||
content.WriteString("🐳 Docker Container Data:\n")
|
content.WriteString("🐳 Docker Container Data:\n")
|
||||||
content.WriteString("• Volume and bind mount backups\n")
|
content.WriteString("• Volume and bind mount backups\n")
|
||||||
content.WriteString("• Container persistent data\n")
|
content.WriteString("• Container persistent data\n")
|
||||||
content.WriteString("• Application state files\n")
|
content.WriteString("• Application state files\n")
|
||||||
content.WriteString("• Configuration volumes\n\n")
|
content.WriteString("• Configuration volumes\n\n")
|
||||||
|
|
||||||
case "logs":
|
case "logs":
|
||||||
content.WriteString("📋 Backup Operation Logs:\n")
|
content.WriteString("📋 Backup Operation Logs:\n")
|
||||||
content.WriteString("• Daily 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("• Backup timing and performance\n")
|
||||||
content.WriteString("• System health monitoring\n\n")
|
content.WriteString("• System health monitoring\n\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add usage instructions based on item type
|
// Add usage instructions based on item type
|
||||||
content.WriteString("\n🚀 Actions:\n")
|
content.WriteString("\n🚀 Actions:\n")
|
||||||
switch item.itemType {
|
switch item.itemType {
|
||||||
@@ -797,7 +813,7 @@ func (m *Model) showItemDescription() {
|
|||||||
content.WriteString("• View backup operation details\n")
|
content.WriteString("• View backup operation details\n")
|
||||||
content.WriteString("• Check for errors or warnings\n")
|
content.WriteString("• Check for errors or warnings\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
content.WriteString("\n💡 Navigation:\n")
|
content.WriteString("\n💡 Navigation:\n")
|
||||||
content.WriteString("• Tab: Switch between panels\n")
|
content.WriteString("• Tab: Switch between panels\n")
|
||||||
content.WriteString("• r: Refresh current view\n")
|
content.WriteString("• r: Refresh current view\n")
|
||||||
@@ -815,19 +831,19 @@ func (m *Model) showBackupDetails(details BackupDetails) {
|
|||||||
var content strings.Builder
|
var content strings.Builder
|
||||||
content.WriteString(fmt.Sprintf("📁 %s Backup Directory\n", details.Directory.Name))
|
content.WriteString(fmt.Sprintf("📁 %s Backup Directory\n", details.Directory.Name))
|
||||||
content.WriteString(strings.Repeat("=", len(details.Directory.Name)+20) + "\n\n")
|
content.WriteString(strings.Repeat("=", len(details.Directory.Name)+20) + "\n\n")
|
||||||
|
|
||||||
// Directory summary
|
// Directory summary
|
||||||
content.WriteString("📊 Directory Summary:\n")
|
content.WriteString("📊 Directory Summary:\n")
|
||||||
content.WriteString(fmt.Sprintf(" 📁 Path: %s\n", details.Directory.Path))
|
content.WriteString(fmt.Sprintf(" 📁 Path: %s\n", details.Directory.Path))
|
||||||
|
|
||||||
// Show if this is using a scheduled subfolder
|
// Show if this is using a scheduled subfolder
|
||||||
if strings.HasSuffix(details.Directory.Path, "/scheduled") {
|
if strings.HasSuffix(details.Directory.Path, "/scheduled") {
|
||||||
content.WriteString(" 📅 Source: Scheduled backups (from 'scheduled' subfolder)\n")
|
content.WriteString(" 📅 Source: Scheduled backups (from 'scheduled' subfolder)\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
content.WriteString(fmt.Sprintf(" 📄 Files: %d\n", details.TotalFiles))
|
content.WriteString(fmt.Sprintf(" 📄 Files: %d\n", details.TotalFiles))
|
||||||
content.WriteString(fmt.Sprintf(" 💾 Total Size: %s\n", formatSize(details.TotalSize)))
|
content.WriteString(fmt.Sprintf(" 💾 Total Size: %s\n", formatSize(details.TotalSize)))
|
||||||
|
|
||||||
if !details.NewestBackup.IsZero() {
|
if !details.NewestBackup.IsZero() {
|
||||||
content.WriteString(fmt.Sprintf(" 🕒 Newest: %s\n", details.NewestBackup.Format("2006-01-02 15:04:05")))
|
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(fmt.Sprintf(" 📅 Oldest: %s\n", details.OldestBackup.Format("2006-01-02 15:04:05")))
|
||||||
}
|
}
|
||||||
content.WriteString("\n")
|
content.WriteString("\n")
|
||||||
|
|
||||||
// File listings
|
// File listings
|
||||||
if len(details.Files) > 0 {
|
if len(details.Files) > 0 {
|
||||||
content.WriteString("📄 Backup Files:\n")
|
content.WriteString("📄 Backup Files:\n")
|
||||||
content.WriteString(" Name Size Modified\n")
|
content.WriteString(" Name Size Modified\n")
|
||||||
content.WriteString(" " + strings.Repeat("-", 50) + "\n")
|
content.WriteString(" " + strings.Repeat("-", 50) + "\n")
|
||||||
|
|
||||||
for _, file := range details.Files {
|
for _, file := range details.Files {
|
||||||
// Truncate filename if too long
|
// Truncate filename if too long
|
||||||
displayName := file.Name
|
displayName := file.Name
|
||||||
if len(displayName) > 25 {
|
if len(displayName) > 25 {
|
||||||
displayName = displayName[:22] + "..."
|
displayName = displayName[:22] + "..."
|
||||||
}
|
}
|
||||||
|
|
||||||
compressionIndicator := ""
|
compressionIndicator := ""
|
||||||
if file.IsCompressed {
|
if file.IsCompressed {
|
||||||
compressionIndicator = "📦 "
|
compressionIndicator = "📦 "
|
||||||
}
|
}
|
||||||
|
|
||||||
content.WriteString(fmt.Sprintf(" %s%-25s %8s %s\n",
|
content.WriteString(fmt.Sprintf(" %s%-25s %8s %s\n",
|
||||||
compressionIndicator,
|
compressionIndicator,
|
||||||
displayName,
|
displayName,
|
||||||
formatSize(file.Size),
|
formatSize(file.Size),
|
||||||
file.ModifiedTime.Format("2006-01-02 15:04")))
|
file.ModifiedTime.Format("2006-01-02 15:04")))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
content.WriteString("📂 No backup files found in this directory.\n")
|
content.WriteString("📂 No backup files found in this directory.\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
content.WriteString("\n💡 Navigation:\n")
|
content.WriteString("\n💡 Navigation:\n")
|
||||||
content.WriteString(" • x: Return to main view\n")
|
content.WriteString(" • x: Return to main view\n")
|
||||||
content.WriteString(" • r: Refresh directory listing\n")
|
content.WriteString(" • r: Refresh directory listing\n")
|
||||||
@@ -877,7 +893,7 @@ func (m *Model) showLogFiles(logFiles []LogFile) {
|
|||||||
var content strings.Builder
|
var content strings.Builder
|
||||||
content.WriteString("📋 Backup Operation Logs\n")
|
content.WriteString("📋 Backup Operation Logs\n")
|
||||||
content.WriteString("=========================\n\n")
|
content.WriteString("=========================\n\n")
|
||||||
|
|
||||||
if len(logFiles) == 0 {
|
if len(logFiles) == 0 {
|
||||||
content.WriteString("📂 No log files found.\n")
|
content.WriteString("📂 No log files found.\n")
|
||||||
content.WriteString("Log files are typically stored in /mnt/share/media/backups/logs/\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("📄 Available Log Files:\n")
|
||||||
content.WriteString(" Filename Size Date\n")
|
content.WriteString(" Filename Size Date\n")
|
||||||
content.WriteString(" " + strings.Repeat("-", 45) + "\n")
|
content.WriteString(" " + strings.Repeat("-", 45) + "\n")
|
||||||
|
|
||||||
for _, logFile := range logFiles {
|
for _, logFile := range logFiles {
|
||||||
displayName := logFile.Name
|
displayName := logFile.Name
|
||||||
if len(displayName) > 25 {
|
if len(displayName) > 25 {
|
||||||
displayName = displayName[:22] + "..."
|
displayName = displayName[:22] + "..."
|
||||||
}
|
}
|
||||||
|
|
||||||
dateStr := "Unknown"
|
dateStr := "Unknown"
|
||||||
if !logFile.Date.IsZero() {
|
if !logFile.Date.IsZero() {
|
||||||
dateStr = logFile.Date.Format("2006-01-02 15:04")
|
dateStr = logFile.Date.Format("2006-01-02 15:04")
|
||||||
}
|
}
|
||||||
|
|
||||||
content.WriteString(fmt.Sprintf(" %-25s %8s %s\n",
|
content.WriteString(fmt.Sprintf(" %-25s %8s %s\n",
|
||||||
displayName,
|
displayName,
|
||||||
formatSize(logFile.Size),
|
formatSize(logFile.Size),
|
||||||
dateStr))
|
dateStr))
|
||||||
}
|
}
|
||||||
|
|
||||||
content.WriteString(fmt.Sprintf("\n📊 Total: %d log files\n", len(logFiles)))
|
content.WriteString(fmt.Sprintf("\n📊 Total: %d log files\n", len(logFiles)))
|
||||||
}
|
}
|
||||||
|
|
||||||
content.WriteString("\n💡 Navigation:\n")
|
content.WriteString("\n💡 Navigation:\n")
|
||||||
content.WriteString(" • Enter: View log content (when a specific log is selected)\n")
|
content.WriteString(" • Enter: View log content (when a specific log is selected)\n")
|
||||||
content.WriteString(" • x: Return to main view\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
|
// Check if there's a "scheduled" subfolder - if so, use that instead
|
||||||
scheduledPath := filepath.Join(dirPath, "scheduled")
|
scheduledPath := filepath.Join(dirPath, "scheduled")
|
||||||
actualPath := dirPath
|
actualPath := dirPath
|
||||||
|
|
||||||
if info, err := os.Stat(scheduledPath); err == nil && info.IsDir() {
|
if info, err := os.Stat(scheduledPath); err == nil && info.IsDir() {
|
||||||
actualPath = scheduledPath
|
actualPath = scheduledPath
|
||||||
}
|
}
|
||||||
|
|
||||||
files, err := os.ReadDir(actualPath)
|
files, err := os.ReadDir(actualPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errorMsg{error: fmt.Sprintf("Failed to read directory %s: %v", actualPath, err)}
|
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") {
|
if strings.HasPrefix(name, "backup_log_") && strings.HasSuffix(name, ".md") {
|
||||||
datePart := strings.TrimPrefix(name, "backup_log_")
|
datePart := strings.TrimPrefix(name, "backup_log_")
|
||||||
datePart = strings.TrimSuffix(datePart, ".md")
|
datePart = strings.TrimSuffix(datePart, ".md")
|
||||||
|
|
||||||
if len(datePart) >= 15 { // YYYYMMDD_HHMMSS
|
if len(datePart) >= 15 { // YYYYMMDD_HHMMSS
|
||||||
if parsedDate, err := time.Parse("20060102_150405", datePart); err == nil {
|
if parsedDate, err := time.Parse("20060102_150405", datePart); err == nil {
|
||||||
logDate = parsedDate
|
logDate = parsedDate
|
||||||
|
|||||||
Reference in New Issue
Block a user