update status.html page Enhance dashboard with backup and router status charts - Added info boxes displaying key system metrics (storage, backup queue, router counts)

This commit is contained in:
petrunetworking 2025-01-31 00:13:51 +02:00
parent 51bbf69038
commit 9770555ebf
3 changed files with 446 additions and 125 deletions

View file

@ -1,9 +1,10 @@
from django.http import HttpResponseBadRequest, JsonResponse
from django.shortcuts import render
from django.contrib.auth.decorators import login_required
from django.views.decorators.csrf import csrf_protect
from backup.models import BackupProfile
from backup_data.models import RouterBackup
from router_manager.models import Router, RouterGroup, RouterStatus, BackupSchedule, SSHKey
from integration_manager.models import ExternalIntegration
from user_manager.models import User
from django.conf import settings
from django.utils import timezone
@ -11,23 +12,55 @@ from datetime import timedelta
import os
import shutil
ALLOWED_DAYS = [3,5,7,10, 15, 30] # Define allowed values
import os
import shutil
from django.conf import settings
import os
import shutil
from django.conf import settings
def human_readable_size(size):
"""Convert bytes to a human-readable format (KB, MB, GB)."""
for unit in ['B', 'KB', 'MB', 'GB']:
if size < 1024.0:
return f"{size:.2f} {unit}"
size /= 1024.0
return f"{size:.2f} TB" # For sizes larger than GB
def get_directory_statistics(directory_path):
# Get total disk usage
total, used, free = shutil.disk_usage(directory_path)
# Convert to GB
total = total // (2 ** 30)
used = used // (2 ** 30)
free = free // (2 ** 30)
usage_percentage = (used / total) * 100
# Calculate the size of the directory
directory_used = sum(os.path.getsize(os.path.join(root, file)) for root, dirs, files in os.walk(directory_path) for file in files)
# Get sizes in a human-readable format
total_human = human_readable_size(total)
used_human = human_readable_size(directory_used)
free_human = human_readable_size(free)
# Calculate usage percentage
usage_percentage = (directory_used / total) * 100 if total > 0 else 0
# Determine storage warning
storage_warning = ""
if usage_percentage > 90:
storage_warning = "Warning: Storage usage is above 90%!"
elif usage_percentage > 75:
storage_warning = "Caution: Storage usage is above 75%."
return {
"total_gb": total,
"used_gb": used,
"free_gb": free,
"usage_percentage": round(usage_percentage, 2)
"total": total_human,
"used": used_human,
"free": free_human,
"usage_percentage": round(usage_percentage, 2) if total > 0 else 0,
"storage_warning": storage_warning
}
@login_required
def view_dashboard(request):
context = {'page_title': 'Welcome to routerfleet'}
@ -43,7 +76,9 @@ def view_status(request):
'queue': RouterBackup.objects.filter(success=False, error=False).count(),
'success_backup_last_24h': RouterBackup.objects.filter(success=True, created__gte=timezone.now() - timedelta(days=1)).count(),
'error_backup_last_24h': RouterBackup.objects.filter(error=True, created__gte=timezone.now() - timedelta(days=1)).count(),
'router_count': Router.objects.filter(enabled=True).count(),
'total_router_count':Router.objects.all().count(),
'router_enabled_count': Router.objects.filter(enabled=True).count(),
'router_disabled_count': Router.objects.filter(enabled=False).count(),
'router_online_count': RouterStatus.objects.filter(status_online=True, router__monitoring=True).count(),
'router_offline_count': RouterStatus.objects.filter(status_online=False, router__monitoring=True).count(),
'router_not_monitored_count': Router.objects.filter(enabled=True, monitoring=False).count(),
@ -51,3 +86,87 @@ def view_status(request):
}
return render(request, 'dashboard/status.html', context=context)
@login_required
def backup_statistics_data(request):
try:
days = int(request.GET.get('days', 7))
except ValueError:
return HttpResponseBadRequest("Invalid 'days' parameter")
if days not in ALLOWED_DAYS:
return HttpResponseBadRequest("Invalid 'days' parameter")
today = timezone.now()
start_date = today - timedelta(days=days)
dates = [start_date + timedelta(days=i) for i in range(days + 1)]
success_data = []
error_data = []
for i in range(days):
day_start = dates[i]
day_end = dates[i + 1]
success_count = RouterBackup.objects.filter(created__gte=day_start, created__lt=day_end, success=True).count()
error_count = RouterBackup.objects.filter(created__gte=day_start, created__lt=day_end, error=True).count()
success_data.append(success_count)
error_data.append(error_count)
data = {
'dates': [date.strftime('%Y-%m-%d') for date in dates[:-1]],
'success_data': success_data,
'error_data': error_data,
}
return JsonResponse(data)
@login_required
def router_status_data(request):
try:
days = int(request.GET.get('days', '7'))
except ValueError:
return HttpResponseBadRequest(f"Invalid 'days' {days} parameter")
if days not in ALLOWED_DAYS:
return HttpResponseBadRequest("Invalid 'days' parameter. Must be one of: " + ', '.join(map(str, ALLOWED_DAYS)))
today = timezone.now()
start_date = today - timedelta(days=days)
# Create a list of dates for the period
dates = [start_date + timedelta(days=i) for i in range(days + 1)]
router_statuses = RouterStatus.objects.filter(router__enabled=True)
online_data = []
offline_data = []
for i in range(days):
day_start = dates[i]
day_end = dates[i + 1]
# Get statuses that changed within the current day
daily_statuses = router_statuses.filter(last_status_change__gte=day_start, last_status_change__lt=day_end)
online_count = daily_statuses.filter(status_online=True).count()
offline_count = daily_statuses.filter(status_online=False).count()
# Get routers that have not changed status on the current day
unchanged_routers = router_statuses.exclude(last_status_change__gte=day_start, last_status_change__lt=day_end)
for router_status in unchanged_routers:
last_change = router_status.last_status_change
# Only perform the comparison if last_change is not None
if last_change and last_change < day_start:
if router_status.status_online:
online_count += 1
else:
offline_count += 1
online_data.append(online_count)
offline_data.append(offline_count)
data = {
'dates': [date.strftime('%Y-%m-%d') for date in dates[:-1]],
'online_data': online_data,
'offline_data': offline_data,
}
return JsonResponse(data)

View file

@ -1,6 +1,8 @@
from django.contrib import admin
from django.urls import path
from dashboard.views import view_dashboard, view_status
from dashboard.views import view_dashboard, view_status,backup_statistics_data,router_status_data
from integration_manager.views import view_wireguard_webadmin_launcher, view_manage_wireguard_integration, view_launch_wireguard_webadmin
from user_manager.views import view_manage_user, view_user_list
from accounts.views import view_login, view_logout, view_create_first_user
@ -19,6 +21,8 @@ urlpatterns = [
path('debug/test_messages/', view_debug_test_messages, name='debug_test_messages'),
path('', view_dashboard, name='dashboard'),
path('status/', view_status, name='status'),
path('router_status_data/', router_status_data, name='router_status_data'),
path('backup_statistics_data/', backup_statistics_data, name='backup_statistics_data'),
path('user/list/', view_user_list, name='user_list'),
path('user/manage/', view_manage_user, name='manage_user'),
path('accounts/create_first_user/', view_create_first_user, name='create_first_user'),

View file

@ -1,123 +1,321 @@
{% extends 'base.html' %}
{% block content %}
<div class='row'>
<div class='{% if form_size %}{{ form_size }}{% else %}col-lg-6{% endif %}'>
<div class="card card-primary card-outline">
{% if page_title %}
<div class="card-header">
<h3 class="card-title">{{ page_title }}</h3>
<style>
.info-box {
transition: transform 0.2s;
}
.info-box:hover {
transform: scale(1.05);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
}
</style>
<div class="row">
<!-- Left Column for General Information -->
<div class="col-lg-12">
<!-- System Stats Card -->
<div class="card card-primary card-outline">
{% if page_title %}
<div class="card-header">
<h3 class="card-title">
<i class="fas fa-tachometer-alt"></i> {{ page_title }}
</h3>
</div>
{% endif %}
<div class="card-body">
<div class="row mb-4">
<!-- Used Storage -->
<div class="col-md-3">
<div class="info-box bg-info">
<span class="info-box-icon"><i class="fas fa-hdd"></i></span>
<div class="info-box-content">
<span class="info-box-text">Used Storage (binary and rsc backups)</span>
<span class="info-box-number">{{ media_root_stats.used }} used</span>
<div class="progress">
<div class="progress-bar" style="width: {{ media_root_stats.usage_percentage }}%"></div>
</div>
<span class="progress-description">
{{ media_root_stats.usage_percentage }}% of {{ media_root_stats.total }}
{% if media_root_stats.storage_warning %}
<span class="text-danger">{{ media_root_stats.storage_warning }}</span>
{% endif %}
</span>
</div>
</div>
</div>
{% endif %}
<div class="card-body row">
<div class="col-lg-12">
<table class="table">
<thead>
<tr>
<th>Item</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<strong>
Used storage (binary backup)
</strong>
</td>
<td>
{{ media_root_stats.usage_percentage }}%
</td>
</tr>
<tr>
<td>
<strong>
Backup Queue
</strong>
</td>
<td>
{{ queue }}
</td>
</tr>
<tr>
<td>
<strong>
Successful backups (24h)
</strong>
</td>
<td>
{{ success_backup_last_24h }}
</td>
</tr>
<tr>
<td>
<strong>
Failed backups (24h)
</strong>
</td>
<td>
{{ error_backup_last_24h }}
</td>
</tr>
<tr>
<td>
<strong>
Routers
</strong>
</td>
<td>
{{ router_count }}
</td>
</tr>
<tr>
<td>
<strong>
Online Routers
</strong>
</td>
<td>
{{ router_online_count }}
</td>
</tr>
<tr>
<td>
<strong>
Offline Routers
</strong>
</td>
<td>
{{ router_offline_count }}
</td>
</tr>
<tr>
<td>
<strong>
Not monitored Routers
</strong>
</td>
<td>
{{ router_not_monitored_count }}
</td>
</tr>
<tr>
<td>
<strong>
Router Fleet Version
</strong>
</td>
<td>
{{ webadmin_version.current_version }}
</td>
</tr>
</tbody>
</table>
<!-- Backup Queue -->
<div class="col-md-3">
<div class="info-box bg-warning">
<span class="info-box-icon"><i class="fas fa-tasks"></i></span>
<div class="info-box-content">
<span class="info-box-text">Backup Queue</span>
<span class="info-box-number">{{ queue }}</span>
</div>
</div>
</div>
<!-- Successful Backups -->
<div class="col-md-3">
<div class="info-box bg-success">
<span class="info-box-icon"><i class="fas fa-check-circle"></i></span>
<div class="info-box-content">
<span class="info-box-text">Successful Backups (24h)</span>
<span class="info-box-number">{{ success_backup_last_24h }}</span>
</div>
</div>
</div>
<!-- Failed Backups -->
<div class="col-md-3">
<div class="info-box bg-danger">
<span class="info-box-icon"><i class="fas fa-times-circle"></i></span>
<div class="info-box-content">
<span class="info-box-text">Failed Backups (24h)</span>
<span class="info-box-number">{{ error_backup_last_24h }}</span>
</div>
</div>
</div>
</div>
<div class="row mb-4">
<!-- Total Routers -->
<div class="col-md-3">
<div class="info-box bg-primary">
<span class="info-box-icon"><i class="fas fa-network-wired"></i></span>
<div class="info-box-content">
<span class="info-box-text">Total Routers</span>
<span class="info-box-number">{{ total_router_count }}</span>
</div>
</div>
</div>
<!-- Online Routers -->
<div class="col-md-3">
<div class="info-box bg-success">
<span class="info-box-icon"><i class="fas fa-plug"></i></span>
<div class="info-box-content">
<span class="info-box-text">Online Routers</span>
<span class="info-box-number">{{ router_online_count }}</span>
</div>
</div>
</div>
<!-- Offline Routers -->
<div class="col-md-3">
<div class="info-box bg-danger">
<span class="info-box-icon"><i class="fas fa-plug"></i></span>
<div class="info-box-content">
<span class="info-box-text">Offline Routers</span>
<span class="info-box-number">{{ router_offline_count }}</span>
</div>
</div>
</div>
<!-- Disabled Routers -->
<div class="col-md-3">
<div class="info-box bg-danger">
<span class="info-box-icon"><i class="fas fa-times"></i></span>
<div class="info-box-content">
<span class="info-box-text">Disabled Routers</span>
<span class="info-box-number">{{ router_disabled_count }}</span>
</div>
</div>
</div>
</div>
<div class="row mb-4">
<!-- Not Monitored Routers -->
<div class="col-md-3">
<div class="info-box bg-secondary">
<span class="info-box-icon"><i class="fas fa-question-circle"></i></span>
<div class="info-box-content">
<span class="info-box-text">Not Monitored Routers</span>
<span class="info-box-number">{{ router_not_monitored_count }}</span>
</div>
</div>
</div>
</div>
</div>
</div> <!-- /.card-body -->
</div> <!-- /.card -->
</div> <!-- /.col-lg-12 -->
</div> <!-- /.row -->
<!-- Right Column for Charts -->
<div class="row">
<div class="col-lg-6">
<!-- Backup Status Chart -->
<div class="card card-primary card-outline">
<div class="card-header">
<h3 class="card-title"><i class="fas fa-chart-bar"></i> Backup Status Statistics</h3>
<div class="card-tools">
<select id="days-select-backup" class="form-control">
<option value="7">Last 7 days</option>
<option value="3">Last 3 days</option>
<option value="5">Last 5 days</option>
<option value="10">Last 10 days</option>
<option value="15">Last 15 days</option>
<option value="30">Last 30 days</option>
</select>
</div>
</div>
<div class="card-body">
<canvas id="backupChart"></canvas>
</div>
<p class="small">This status page is just a placeholder, it will be improved in next versions</p>
</div>
</div>
{% endblock %}
<div class="col-lg-6">
<!-- Router Status Chart -->
<div class="card card-primary card-outline">
<div class="card-header">
<h3 class="card-title"><i class="fas fa-signal"></i> Router Status Statistics</h3>
<div class="card-tools">
<select id="days-select-status" class="form-control">
<option value="7">Last 7 days</option>
<option value="3">Last 3 days</option>
<option value="5">Last 5 days</option>
<option value="10">Last 10 days</option>
<option value="15">Last 15 days</option>
<option value="30">Last 30 days</option>
</select>
</div>
</div>
<div class="card-body">
<canvas id="routerStatusChart"></canvas>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
var backupChart;
function createBackupChart(data) {
var ctx = document.getElementById('backupChart').getContext('2d');
if (backupChart) {
backupChart.destroy();
}
backupChart = new Chart(ctx, {
type: 'line',
data: {
labels: data.dates,
datasets: [{
label: 'Successful Backups',
data: data.success_data,
backgroundColor: 'rgba(54, 162, 235, 0.2)',
borderColor: 'rgba(54, 162, 235, 1)',
borderWidth: 2,
fill: true, // Fill the area under the line
},
{
label: 'Failed Backups',
data: data.error_data,
backgroundColor: 'rgba(255, 99, 132, 0.2)',
borderColor: 'rgba(255, 99, 132, 1)',
borderWidth: 2,
fill: true, // Fill the area under the line
}]
},
options: {
scales: {
y: {
beginAtZero: true
}
}
}
});
}
function updateBackupStatistics(days) {
fetch(`/backup_statistics_data?days=${days}`)
.then(response => {
if (!response.ok) {
throw new Error('Invalid days parameter');
}
return response.json();
})
.then(data => {
createBackupChart(data);
})
.catch(error => {
alert(error.message);
});
}
document.addEventListener("DOMContentLoaded", function() {
const daysSelectBackup = document.getElementById('days-select-backup');
daysSelectBackup.addEventListener('change', (event) => {
updateBackupStatistics(event.target.value);
});
updateBackupStatistics(7); // Initial load for the last 7 days
});
</script>
<script>
var routerStatusChart;
function createRouterStatusChart(data) {
var ctx2 = document.getElementById('routerStatusChart').getContext('2d');
if (routerStatusChart) {
routerStatusChart.destroy();
}
routerStatusChart = new Chart(ctx2, {
type: 'line',
data: {
labels: data.dates,
datasets: [{
label: 'Online Routers',
data: data.online_data,
backgroundColor: 'rgba(75, 192, 192, 0.2)',
borderColor: 'rgba(75, 192, 192, 1)',
borderWidth: 2,
fill: true, // Fill the area under the line
},
{
label: 'Offline Routers',
data: data.offline_data,
backgroundColor: 'rgba(153, 102, 255, 0.2)',
borderColor: 'rgba(153, 102, 255, 1)',
borderWidth: 2,
fill: true, // Fill the area under the line
}]
},
options: {
scales: {
y: {
beginAtZero: true
}
}
}
});
}
function updateRouterStatus(days) {
fetch(`/router_status_data?days=${days}`)
.then(response => {
if (!response.ok) {
throw new Error('Invalid days parameter'+ " " + days);
}
return response.json();
})
.then(data => {
createRouterStatusChart(data);
})
.catch(error => {
alert(error.message);
});
}
document.addEventListener("DOMContentLoaded", function() {
const daysSelectStatus = document.getElementById('days-select-status');
daysSelectStatus.addEventListener('change', (event) => {
updateRouterStatus(event.target.value);
});
updateRouterStatus(7); // Initial load for the last 7 days
});
</script>
{% endblock %}