feat: Add base HTML template and implement dashboard, logs, and service views

- Created a base HTML template for consistent layout across pages.
- Developed a dashboard page to display backup service metrics and statuses.
- Implemented a log viewer for detailed log file inspection.
- Added error handling page for better user experience during failures.
- Introduced service detail page to show specific service metrics and actions.
- Enhanced log filtering and viewing capabilities.
- Integrated auto-refresh functionality for real-time updates on metrics.
- Created integration and unit test scripts for backup metrics functionality.
This commit is contained in:
Peter Wood
2025-06-18 08:06:08 -04:00
parent d066f32b10
commit 6d726cb015
34 changed files with 6006 additions and 26 deletions

197
templates/dashboard.html Normal file
View File

@@ -0,0 +1,197 @@
{% extends "base.html" %}
{% block title %}Dashboard - Backup Monitor{% endblock %}
{% block content %}
<div class="container mt-4">
<!-- Header -->
<div class="row mb-4">
<div class="col-12">
<h1 class="display-4">
<i class="fas fa-tachometer-alt text-primary me-3"></i>
Backup Dashboard
</h1>
<p class="lead text-muted">Monitor and manage your backup services</p>
</div>
</div>
<!-- Status Overview -->
<div class="row mb-4">
<div class="col-md-3">
<div class="card bg-success text-white">
<div class="card-body">
<div class="d-flex justify-content-between">
<div>
<h4>{{ data.summary.successful }}</h4>
<p class="mb-0">Successful</p>
</div>
<div class="align-self-center">
<i class="fas fa-check-circle fa-2x"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card bg-warning text-white">
<div class="card-body">
<div class="d-flex justify-content-between">
<div>
<h4>{{ data.summary.partial }}</h4>
<p class="mb-0">Partial</p>
</div>
<div class="align-self-center">
<i class="fas fa-exclamation-triangle fa-2x"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card bg-danger text-white">
<div class="card-body">
<div class="d-flex justify-content-between">
<div>
<h4>{{ data.summary.failed }}</h4>
<p class="mb-0">Failed</p>
</div>
<div class="align-self-center">
<i class="fas fa-times-circle fa-2x"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card bg-info text-white">
<div class="card-body">
<div class="d-flex justify-content-between">
<div>
<h4>{{ data.summary.total }}</h4>
<p class="mb-0">Total Services</p>
</div>
<div class="align-self-center">
<i class="fas fa-server fa-2x"></i>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Service Cards -->
<div class="row">
{% for service in data.services %}
<div class="col-lg-4 col-md-6 mb-4">
<div class="card h-100 service-card" data-service="{{ service.service }}">
<div class="card-header d-flex justify-content-between align-items-center">
<h5 class="mb-0">
<i class="fas fa-{{ service.icon | default('database') }} me-2"></i>
{{ service.service | title }}
</h5>
<span class="badge bg-{{ 'success' if service.status == 'success' else 'warning' if service.status == 'partial' else 'danger' if service.status == 'failed' else 'secondary' }}">
{{ service.status | title }}
</span>
</div>
<div class="card-body">
<p class="card-text text-muted">{{ service.description }}</p>
{% if service.start_time %}
<div class="mb-2">
<small class="text-muted">
<i class="fas fa-clock me-1"></i>
Last Run: {{ service.start_time | default('Never') }}
</small>
</div>
{% endif %}
{% if service.duration_seconds %}
<div class="mb-2">
<small class="text-muted">
<i class="fas fa-stopwatch me-1"></i>
Duration: {{ (service.duration_seconds / 60) | round(1) }} minutes
</small>
</div>
{% endif %}
{% if service.files_processed %}
<div class="mb-2">
<small class="text-muted">
<i class="fas fa-file me-1"></i>
Files: {{ service.files_processed }}
</small>
</div>
{% endif %}
{% if service.total_size_bytes %}
<div class="mb-2">
<small class="text-muted">
<i class="fas fa-hdd me-1"></i>
Size: {{ (service.total_size_bytes / 1024 / 1024 / 1024) | round(2) }}GB
</small>
</div>
{% endif %}
{% if service.current_operation %}
<div class="mb-2">
<small class="text-muted">
<i class="fas fa-info-circle me-1"></i>
{{ service.current_operation }}
</small>
</div>
{% endif %}
{% if service.message and service.status != 'success' %}
<div class="alert alert-{{ 'warning' if service.status == 'partial' else 'danger' }} py-1 px-2 mt-2">
<small>{{ service.message }}</small>
</div>
{% endif %}
</div>
<div class="card-footer">
<div class="d-flex justify-content-between">
<a href="{{ url_for('service_detail', service_name=service.service) }}" class="btn btn-outline-primary btn-sm">
<i class="fas fa-eye me-1"></i>Details
</a>
{% if service.backup_path %}
<small class="text-muted">
<i class="fas fa-folder me-1"></i>Backup Path: <code>{{ service.backup_path }}</code>
</small>
{% endif %}
</div>
</div>
</div>
</div>
{% endfor %}
</div>
<!-- Empty State -->
{% if not data.services %}
<div class="row">
<div class="col-12">
<div class="text-center py-5">
<i class="fas fa-database fa-4x text-muted mb-3"></i>
<h3 class="text-muted">No backup services found</h3>
<p class="text-muted">No backup metrics are available at this time.</p>
<button class="btn btn-primary" onclick="refreshMetrics()">
<i class="fas fa-sync-alt me-1"></i>Refresh
</button>
</div>
</div>
</div>
{% endif %}
</div>
<script>
function refreshMetrics() {
location.reload();
}
// Auto-refresh every 30 seconds
setInterval(refreshMetrics, 30000);
// Update last updated time
document.addEventListener('DOMContentLoaded', function() {
document.getElementById('last-updated').textContent = 'Last updated: ' + new Date().toLocaleTimeString();
});
</script>
{% endblock %}