feat: Implement comprehensive backup web application with Docker, systemd service, and Gunicorn support

This commit is contained in:
Peter Wood
2025-06-18 10:02:07 -04:00
parent 6d726cb015
commit 8cd33d4568
11 changed files with 799 additions and 42 deletions

View File

@@ -16,9 +16,8 @@ Author: Shell Repository
import os
import json
import logging
from datetime import datetime, timedelta
from pathlib import Path
from flask import Flask, render_template, jsonify, request, send_file, abort
from datetime import datetime
from flask import Flask, render_template, jsonify, request, abort
from werkzeug.utils import secure_filename
import subprocess
@@ -48,10 +47,10 @@ def load_json_file(filepath):
"""Safely load JSON file with error handling"""
try:
if os.path.exists(filepath):
with open(filepath, 'r') as f:
with open(filepath, 'r', encoding='utf-8') as f:
return json.load(f)
except Exception as e:
logger.error(f"Error loading JSON file {filepath}: {e}")
except (OSError, json.JSONDecodeError, UnicodeDecodeError) as e:
logger.error("Error loading JSON file %s: %s", filepath, e)
return None
@@ -206,7 +205,6 @@ def index():
"""Main dashboard"""
try:
# Get all services with their metrics
service_names = get_services()
services_data = []
# Status counters for summary
@@ -259,8 +257,8 @@ def index():
}
return render_template('dashboard.html', data=dashboard_data)
except Exception as e:
logger.error(f"Error in index route: {e}")
except (OSError, IOError, json.JSONDecodeError) as e:
logger.error("Error in index route: %s", e)
return f"Error: {e}", 500
@@ -285,8 +283,9 @@ def api_service_details(service_name):
'backup_files': backup_files,
'log_files': log_files
})
except Exception as e:
logger.error(f"Error getting service details for {service_name}: {e}")
except (OSError, IOError, json.JSONDecodeError) as e:
logger.error("Error getting service details for %s: %s",
service_name, e)
return jsonify({'error': str(e)}), 500
@@ -328,8 +327,8 @@ def service_detail(service_name):
service_data['latest_backup'] = latest_backup['path']
return render_template('service.html', service=service_data)
except Exception as e:
logger.error(f"Error in service detail for {service_name}: {e}")
except (OSError, IOError, json.JSONDecodeError) as e:
logger.error("Error in service detail for %s: %s", service_name, e)
return f"Error: {e}", 500
@@ -369,8 +368,8 @@ def logs_view():
})
return render_template('logs.html', logs=formatted_logs, filter_service=service_filter)
except Exception as e:
logger.error(f"Error in logs view: {e}")
except (OSError, IOError) as e:
logger.error("Error in logs view: %s", e)
return f"Error: {e}", 500
@@ -408,7 +407,7 @@ def view_log(filename):
# Read last N lines for large files
max_lines = int(request.args.get('lines', 1000))
with open(log_path, 'r') as f:
with open(log_path, 'r', encoding='utf-8') as f:
lines = f.readlines()
if len(lines) > max_lines:
lines = lines[-max_lines:]
@@ -427,8 +426,8 @@ def view_log(filename):
"%Y-%m-%d %H:%M:%S"),
total_lines=len(lines),
lines_shown=min(len(lines), max_lines))
except Exception as e:
logger.error(f"Error viewing log {filename}: {e}")
except (OSError, IOError, UnicodeDecodeError, ValueError) as e:
logger.error("Error viewing log %s: %s", filename, e)
return f"Error: {e}", 500
@@ -449,7 +448,8 @@ def api_refresh_metrics():
env=env,
capture_output=True,
text=True,
timeout=300 # 5 minute timeout
timeout=300, # 5 minute timeout
check=False
)
if result.returncode == 0:
@@ -460,7 +460,7 @@ def api_refresh_metrics():
'output': result.stdout
})
else:
logger.error(f"Metrics refresh failed: {result.stderr}")
logger.error("Metrics refresh failed: %s", result.stderr)
return jsonify({
'status': 'error',
'message': 'Metrics refresh failed',
@@ -477,8 +477,8 @@ def api_refresh_metrics():
'status': 'error',
'message': 'Metrics refresh timed out'
}), 408
except Exception as e:
logger.error(f"Error refreshing metrics: {e}")
except (OSError, subprocess.SubprocessError) as e:
logger.error("Error refreshing metrics: %s", e)
return jsonify({
'status': 'error',
'message': str(e)
@@ -498,14 +498,14 @@ def health_check():
@app.errorhandler(404)
def not_found(error):
def not_found(_error):
return render_template('error.html',
error_code=404,
error_message="Page not found"), 404
@app.errorhandler(500)
def internal_error(error):
def internal_error(_error):
return render_template('error.html',
error_code=500,
error_message="Internal server error"), 500