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

200
DEPLOYMENT-GUIDE.md Normal file
View File

@@ -0,0 +1,200 @@
# Backup Web Application Deployment Guide
This guide covers multiple methods to keep the backup web application running perpetually on your server.
## Deployment Options
### 1. 🚀 Systemd Service (Recommended for Production)
**Best for:** Production environments, automatic startup on boot, proper logging, and system integration.
#### Setup Steps:
```bash
# Install the service
sudo ./manage-backup-web-service.sh install
# Start the service
sudo ./manage-backup-web-service.sh start
# Check status
./manage-backup-web-service.sh status
# View logs
./manage-backup-web-service.sh logs
```
#### Service Management:
```bash
# Start/Stop/Restart
sudo systemctl start backup-web-app
sudo systemctl stop backup-web-app
sudo systemctl restart backup-web-app
# Enable/Disable auto-start on boot
sudo systemctl enable backup-web-app
sudo systemctl disable backup-web-app
# Check logs
sudo journalctl -u backup-web-app -f
```
### 2. 🐳 Docker (Recommended for Isolation)
**Best for:** Containerized environments, easy deployment, consistent runtime.
#### Using Docker Compose:
```bash
# Build and start
docker-compose up -d
# View logs
docker-compose logs -f
# Stop
docker-compose down
# Rebuild and restart
docker-compose up -d --build
```
#### Using Docker directly:
```bash
# Build image
docker build -t backup-web-app .
# Run container
docker run -d \
--name backup-web-app \
-p 5000:5000 \
-v /mnt/share/media/backups:/data/backups:ro \
-e BACKUP_ROOT=/data/backups \
--restart unless-stopped \
backup-web-app
```
### 3. 📺 Screen Session (Quick & Simple)
**Best for:** Development, testing, quick deployments.
```bash
# Start the application
./run-backup-web-screen.sh start
# Check status
./run-backup-web-screen.sh status
# View logs (connect to session)
./run-backup-web-screen.sh logs
# Stop the application
./run-backup-web-screen.sh stop
```
### 4. ⚡ Production with Gunicorn
**Best for:** High-performance production deployments.
```bash
# Install gunicorn
pip install gunicorn
# Run with production settings
./run-production.sh
```
## Configuration
### Environment Variables
- `BACKUP_ROOT`: Path to backup directory (default: `/mnt/share/media/backups`)
- `PORT`: Application port (default: `5000`)
- `FLASK_ENV`: Environment mode (`development` or `production`)
- `FLASK_DEBUG`: Enable debug mode (`true` or `false`)
### Security Considerations
1. **Firewall**: Ensure port 5000 is properly secured
2. **Reverse Proxy**: Consider using nginx for SSL termination
3. **Authentication**: Add authentication for production use
4. **File Permissions**: Ensure proper read permissions for backup directories
## Monitoring & Maintenance
### Health Checks
The application provides a health endpoint:
```bash
curl http://localhost:5000/health
```
### Log Locations
- **Systemd**: `sudo journalctl -u backup-web-app`
- **Docker**: `docker-compose logs` or `docker logs backup-web-app`
- **Screen**: Connect to session with `screen -r backup-web-app`
- **Gunicorn**: `/tmp/backup-web-app-access.log` and `/tmp/backup-web-app-error.log`
### Automatic Restarts
- **Systemd**: Built-in restart on failure
- **Docker**: Use `--restart unless-stopped` or `restart: unless-stopped` in compose
- **Screen**: Manual restart required
## Troubleshooting
### Common Issues
1. **Port already in use**:
```bash
sudo lsof -i :5000
sudo netstat -tulpn | grep :5000
```
2. **Permission denied for backup directory**:
```bash
sudo chown -R acedanger:acedanger /mnt/share/media/backups
chmod -R 755 /mnt/share/media/backups
```
3. **Service won't start**:
```bash
sudo journalctl -u backup-web-app -n 50
```
### Performance Tuning
1. **Gunicorn Workers**: Adjust in `gunicorn.conf.py`
2. **Memory Limits**: Set in systemd service or docker-compose
3. **Log Rotation**: Configure logrotate for production
## Quick Start Commands
```bash
# For development/testing (Screen)
./run-backup-web-screen.sh start
# For production (Systemd)
sudo ./manage-backup-web-service.sh install
sudo ./manage-backup-web-service.sh start
# For containerized (Docker)
docker-compose up -d
# Check if running
curl http://localhost:5000/health
```
## Recommended Setup
For a production server, use this combination:
1. **Primary**: Systemd service for reliability
2. **Backup**: Docker setup for easy maintenance
3. **Monitoring**: Set up log monitoring and alerts
4. **Security**: Add reverse proxy with SSL
Choose the method that best fits your infrastructure and requirements!

37
Dockerfile Normal file
View File

@@ -0,0 +1,37 @@
# Dockerfile for Backup Web Application
FROM python:3.11-slim
# Set working directory
WORKDIR /app
# Install system dependencies
RUN apt-get update && apt-get install -y \
curl \
&& rm -rf /var/lib/apt/lists/*
# Copy requirements and install Python dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Copy application files
COPY backup-web-app.py .
COPY templates/ ./templates/
COPY static/ ./static/
# Create non-root user
RUN useradd -m -u 1000 appuser && chown -R appuser:appuser /app
USER appuser
# Expose port
EXPOSE 5000
# Environment variables
ENV FLASK_ENV=production
ENV BACKUP_ROOT=/data/backups
# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD curl -f http://localhost:5000/health || exit 1
# Run application
CMD ["python", "backup-web-app.py"]

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

24
backup-web-app.service Normal file
View File

@@ -0,0 +1,24 @@
[Unit]
Description=Backup Web Application
After=network.target
Wants=network.target
[Service]
Type=simple
User=acedanger
Group=acedanger
WorkingDirectory=/home/acedanger/shell
Environment=PATH=/usr/bin:/usr/local/bin
Environment=BACKUP_ROOT=/mnt/share/media/backups
Environment=FLASK_ENV=production
Environment=PORT=5000
ExecStart=/usr/bin/python3 /home/acedanger/shell/backup-web-app.py
ExecReload=/bin/kill -s HUP $MAINPID
KillMode=mixed
TimeoutStopSec=5
PrivateTmp=true
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target

27
docker-compose.yml Normal file
View File

@@ -0,0 +1,27 @@
version: '3.8'
services:
backup-web-app:
build: .
container_name: backup-web-app
ports:
- "5000:5000"
volumes:
- /mnt/share/media/backups:/data/backups:ro
- ./logs:/app/logs
environment:
- BACKUP_ROOT=/data/backups
- FLASK_ENV=production
- PORT=5000
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:5000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"

61
gunicorn.conf.py Normal file
View File

@@ -0,0 +1,61 @@
#!/usr/bin/env python3
# Gunicorn configuration for backup web application
import os
import multiprocessing
# Server socket
bind = f"0.0.0.0:{os.environ.get('PORT', '5000')}"
backlog = 2048
# Worker processes
workers = multiprocessing.cpu_count() * 2 + 1
worker_class = "sync"
worker_connections = 1000
timeout = 30
keepalive = 2
# Restart workers after this many requests, to help prevent memory leaks
max_requests = 1000
max_requests_jitter = 50
# Logging
accesslog = "/tmp/backup-web-app-access.log"
errorlog = "/tmp/backup-web-app-error.log"
loglevel = "info"
access_log_format = '%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s" %(D)s'
# Process naming
proc_name = "backup-web-app"
# Daemon mode
daemon = False
pidfile = "/tmp/backup-web-app.pid"
umask = 0
user = None
group = None
tmp_upload_dir = None
# SSL (if needed)
# keyfile = "/path/to/keyfile"
# certfile = "/path/to/certfile"
# Environment
raw_env = [
f"BACKUP_ROOT={os.environ.get('BACKUP_ROOT', '/mnt/share/media/backups')}",
]
# Preload app for better performance
preload_app = True
# Graceful timeout
graceful_timeout = 30
# Security
forwarded_allow_ips = "*"
secure_scheme_headers = {
'X-FORWARDED-PROTOCOL': 'ssl',
'X-FORWARDED-PROTO': 'https',
'X-FORWARDED-SSL': 'on'
}

197
manage-backup-web-service.sh Executable file
View File

@@ -0,0 +1,197 @@
#!/bin/bash
# Backup Web Application Service Manager
# Manages the backup web application as a systemd service
set -e
SERVICE_NAME="backup-web-app"
SERVICE_FILE="/home/acedanger/shell/${SERVICE_NAME}.service"
SYSTEMD_DIR="/etc/systemd/system"
APP_USER="acedanger"
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
print_status() {
echo -e "${BLUE}[INFO]${NC} $1"
}
print_success() {
echo -e "${GREEN}[SUCCESS]${NC} $1"
}
print_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
print_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
check_root() {
if [[ $EUID -ne 0 ]]; then
print_error "This script must be run as root (use sudo)"
exit 1
fi
}
install_service() {
print_status "Installing backup web application service..."
# Check if service file exists
if [[ ! -f "$SERVICE_FILE" ]]; then
print_error "Service file not found: $SERVICE_FILE"
exit 1
fi
# Copy service file to systemd directory
cp "$SERVICE_FILE" "$SYSTEMD_DIR/"
print_success "Service file copied to $SYSTEMD_DIR"
# Reload systemd daemon
systemctl daemon-reload
print_success "Systemd daemon reloaded"
# Enable service to start on boot
systemctl enable "$SERVICE_NAME"
print_success "Service enabled for auto-start on boot"
print_success "Service installation completed!"
print_status "Use 'sudo systemctl start $SERVICE_NAME' to start the service"
}
start_service() {
print_status "Starting backup web application service..."
systemctl start "$SERVICE_NAME"
sleep 2
if systemctl is-active --quiet "$SERVICE_NAME"; then
print_success "Service started successfully"
systemctl status "$SERVICE_NAME" --no-pager -l
else
print_error "Failed to start service"
print_status "Check logs with: sudo journalctl -u $SERVICE_NAME -f"
exit 1
fi
}
stop_service() {
print_status "Stopping backup web application service..."
systemctl stop "$SERVICE_NAME"
print_success "Service stopped"
}
restart_service() {
print_status "Restarting backup web application service..."
systemctl restart "$SERVICE_NAME"
sleep 2
if systemctl is-active --quiet "$SERVICE_NAME"; then
print_success "Service restarted successfully"
else
print_error "Failed to restart service"
exit 1
fi
}
status_service() {
print_status "Service status:"
systemctl status "$SERVICE_NAME" --no-pager -l
}
logs_service() {
print_status "Following service logs (Ctrl+C to exit):"
journalctl -u "$SERVICE_NAME" -f
}
uninstall_service() {
print_status "Uninstalling backup web application service..."
# Stop service if running
if systemctl is-active --quiet "$SERVICE_NAME"; then
systemctl stop "$SERVICE_NAME"
print_status "Service stopped"
fi
# Disable service
if systemctl is-enabled --quiet "$SERVICE_NAME"; then
systemctl disable "$SERVICE_NAME"
print_status "Service disabled"
fi
# Remove service file
if [[ -f "$SYSTEMD_DIR/${SERVICE_NAME}.service" ]]; then
rm "$SYSTEMD_DIR/${SERVICE_NAME}.service"
print_status "Service file removed"
fi
# Reload systemd daemon
systemctl daemon-reload
print_success "Service uninstalled successfully"
}
show_help() {
echo "Backup Web Application Service Manager"
echo
echo "Usage: $0 {install|start|stop|restart|status|logs|uninstall|help}"
echo
echo "Commands:"
echo " install - Install the service (requires root)"
echo " start - Start the service (requires root)"
echo " stop - Stop the service (requires root)"
echo " restart - Restart the service (requires root)"
echo " status - Show service status"
echo " logs - Follow service logs"
echo " uninstall - Remove the service (requires root)"
echo " help - Show this help message"
echo
echo "Examples:"
echo " sudo $0 install # Install and enable the service"
echo " sudo $0 start # Start the service"
echo " $0 status # Check service status"
echo " $0 logs # View live logs"
}
# Main script logic
case "${1:-}" in
install)
check_root
install_service
;;
start)
check_root
start_service
;;
stop)
check_root
stop_service
;;
restart)
check_root
restart_service
;;
status)
status_service
;;
logs)
logs_service
;;
uninstall)
check_root
uninstall_service
;;
help|--help|-h)
show_help
;;
*)
print_error "Invalid command: ${1:-}"
echo
show_help
exit 1
;;
esac

3
requirements.txt Normal file
View File

@@ -0,0 +1,3 @@
Flask==2.3.3
Werkzeug==2.3.7
gunicorn==21.2.0

150
run-backup-web-screen.sh Executable file
View File

@@ -0,0 +1,150 @@
#!/bin/bash
# Simple script to run backup web app in a persistent screen session
SESSION_NAME="backup-web-app"
APP_DIR="/home/acedanger/shell"
PYTHON_CMD="python3"
# Colors for output
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
NC='\033[0m'
print_status() {
echo -e "${GREEN}[INFO]${NC} $1"
}
print_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
print_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
check_screen() {
if ! command -v screen &> /dev/null; then
print_error "Screen is not installed. Install it with: sudo apt install screen"
exit 1
fi
}
start_app() {
check_screen
# Check if session already exists
if screen -list | grep -q "$SESSION_NAME"; then
print_warning "Session '$SESSION_NAME' already exists"
print_status "Use './run-backup-web-screen.sh status' to check or './run-backup-web-screen.sh stop' to stop"
exit 1
fi
print_status "Starting backup web app in screen session '$SESSION_NAME'..."
# Start new detached screen session
cd "$APP_DIR" || exit 1
screen -dmS "$SESSION_NAME" bash -c "
export BACKUP_ROOT=/mnt/share/media/backups
export FLASK_ENV=production
$PYTHON_CMD backup-web-app.py
"
sleep 2
if screen -list | grep -q "$SESSION_NAME"; then
print_status "✅ Backup web app started successfully!"
print_status "Session: $SESSION_NAME"
print_status "URL: http://localhost:5000"
print_status ""
print_status "Commands:"
print_status " View logs: ./run-backup-web-screen.sh logs"
print_status " Stop app: ./run-backup-web-screen.sh stop"
print_status " Status: ./run-backup-web-screen.sh status"
else
print_error "Failed to start the application"
exit 1
fi
}
stop_app() {
if screen -list | grep -q "$SESSION_NAME"; then
print_status "Stopping backup web app..."
screen -S "$SESSION_NAME" -X quit
print_status "✅ Application stopped"
else
print_warning "No session '$SESSION_NAME' found"
fi
}
status_app() {
if screen -list | grep -q "$SESSION_NAME"; then
print_status "✅ Backup web app is running"
print_status "Session details:"
screen -list | grep "$SESSION_NAME"
print_status ""
print_status "Access the session with: screen -r $SESSION_NAME"
print_status "Detach from session with: Ctrl+A, then D"
else
print_warning "❌ Backup web app is not running"
fi
}
show_logs() {
if screen -list | grep -q "$SESSION_NAME"; then
print_status "Connecting to session '$SESSION_NAME'..."
print_status "Press Ctrl+A, then D to detach from the session"
screen -r "$SESSION_NAME"
else
print_error "No session '$SESSION_NAME' found. App is not running."
fi
}
restart_app() {
print_status "Restarting backup web app..."
stop_app
sleep 2
start_app
}
show_help() {
echo "Backup Web App Screen Manager"
echo
echo "Usage: $0 {start|stop|restart|status|logs|help}"
echo
echo "Commands:"
echo " start - Start the app in a screen session"
echo " stop - Stop the app"
echo " restart - Restart the app"
echo " status - Check if app is running"
echo " logs - Connect to the screen session to view logs"
echo " help - Show this help message"
}
case "${1:-}" in
start)
start_app
;;
stop)
stop_app
;;
restart)
restart_app
;;
status)
status_app
;;
logs)
show_logs
;;
help|--help|-h)
show_help
;;
*)
print_error "Invalid command: ${1:-}"
echo
show_help
exit 1
;;
esac

59
run-production.sh Executable file
View File

@@ -0,0 +1,59 @@
#!/bin/bash
# Production runner for backup web application using Gunicorn
APP_DIR="/home/acedanger/shell"
APP_MODULE="backup-web-app:app"
CONFIG_FILE="gunicorn.conf.py"
VENV_PATH="/home/acedanger/shell/venv"
# Colors
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
NC='\033[0m'
print_status() {
echo -e "${GREEN}[INFO]${NC} $1"
}
print_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
print_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# Check if we're in the right directory
cd "$APP_DIR" || {
print_error "Cannot change to app directory: $APP_DIR"
exit 1
}
# Check for virtual environment
if [[ -d "$VENV_PATH" ]]; then
print_status "Activating virtual environment..."
source "$VENV_PATH/bin/activate"
fi
# Set environment variables
export BACKUP_ROOT="/mnt/share/media/backups"
export FLASK_ENV="production"
# Check if gunicorn is installed
if ! command -v gunicorn &> /dev/null; then
print_error "Gunicorn is not installed"
print_status "Install with: pip install gunicorn"
exit 1
fi
print_status "Starting backup web application with Gunicorn..."
print_status "Configuration: $CONFIG_FILE"
print_status "Module: $APP_MODULE"
print_status "Directory: $APP_DIR"
# Start Gunicorn
exec gunicorn \
--config "$CONFIG_FILE" \
"$APP_MODULE"

View File

@@ -2,7 +2,6 @@
import os
import json
import sys
# Set environment
os.environ['BACKUP_ROOT'] = '/home/acedanger/shell'
@@ -13,9 +12,9 @@ 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:
except (OSError, json.JSONDecodeError, UnicodeDecodeError) as e:
print(f"Error loading JSON file {filepath}: {e}")
return None
@@ -25,35 +24,35 @@ def get_service_metrics(service_name):
# Simple status file approach
status_file = os.path.join(METRICS_DIR, f'{service_name}_status.json')
status = load_json_file(status_file)
service_status = load_json_file(status_file)
return {
'status': status,
'last_run': status.get('end_time') if status else None,
'current_status': status.get('status', 'unknown') if status else 'never_run',
'files_processed': status.get('files_processed', 0) if status else 0,
'total_size': status.get('total_size_bytes', 0) if status else 0,
'duration': status.get('duration_seconds', 0) if status else 0
'status': service_status,
'last_run': service_status.get('end_time') if service_status else None,
'current_status': service_status.get('status', 'unknown') if service_status else 'never_run',
'files_processed': service_status.get('files_processed', 0) if service_status else 0,
'total_size': service_status.get('total_size_bytes', 0) if service_status else 0,
'duration': service_status.get('duration_seconds', 0) if service_status else 0
}
def get_consolidated_metrics():
"""Get consolidated metrics across all services"""
# With simplified approach, we consolidate by reading all status files
services = {}
all_services = {}
if os.path.exists(METRICS_DIR):
for filename in os.listdir(METRICS_DIR):
if filename.endswith('_status.json'):
service_name = filename.replace('_status.json', '')
status_file = os.path.join(METRICS_DIR, filename)
status = load_json_file(status_file)
if status:
services[service_name] = status
service_status = load_json_file(status_file)
if service_status:
all_services[service_name] = service_status
return {
'services': services,
'total_services': len(services),
'services': all_services,
'total_services': len(all_services),
'last_updated': '2025-06-18T05:15:00-04:00'
}
@@ -70,7 +69,7 @@ if __name__ == "__main__":
files = metrics['files_processed']
duration = metrics['duration']
print(f' {service}: {status} ({files} files, {duration}s)')
except Exception as e:
except (OSError, IOError, KeyError) as e:
print(f' {service}: Error - {e}')
# Test consolidated metrics
@@ -82,7 +81,7 @@ if __name__ == "__main__":
for name, status in services.items():
message = status.get('message', 'N/A')
print(f' {name}: {status["status"]} - {message}')
except Exception as e:
except (OSError, IOError, KeyError) as e:
print(f' Error: {e}')
print('\n✅ Web integration test completed successfully!')