Merge pull request #61 from petrunetworking/main

update status.html page update view.py dashboard and urls.py
This commit is contained in:
Eduardo Silva 2025-02-04 10:59:12 -03:00 committed by GitHub
commit 3fd2d9fadf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 564 additions and 147 deletions

View file

@ -1,9 +1,10 @@
from django.http import HttpResponseBadRequest, JsonResponse
from django.shortcuts import render from django.shortcuts import render
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.views.decorators.csrf import csrf_protect
from backup.models import BackupProfile from backup.models import BackupProfile
from backup_data.models import RouterBackup from backup_data.models import RouterBackup
from router_manager.models import Router, RouterGroup, RouterStatus, BackupSchedule, SSHKey from router_manager.models import Router, RouterGroup, RouterStatus, BackupSchedule, SSHKey
from integration_manager.models import ExternalIntegration
from user_manager.models import User from user_manager.models import User
from django.conf import settings from django.conf import settings
from django.utils import timezone from django.utils import timezone
@ -11,23 +12,55 @@ from datetime import timedelta
import os import os
import shutil 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): def get_directory_statistics(directory_path):
# Get total disk usage
total, used, free = shutil.disk_usage(directory_path) total, used, free = shutil.disk_usage(directory_path)
# Convert to GB
total = total // (2 ** 30) # Calculate the size of the directory
used = used // (2 ** 30) directory_used = sum(os.path.getsize(os.path.join(root, file)) for root, dirs, files in os.walk(directory_path) for file in files)
free = free // (2 ** 30)
usage_percentage = (used / total) * 100 # 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 { return {
"total_gb": total, "total": total_human,
"used_gb": used, "used": used_human,
"free_gb": free, "free": free_human,
"usage_percentage": round(usage_percentage, 2) "usage_percentage": round(usage_percentage, 2) if total > 0 else 0,
"storage_warning": storage_warning
} }
@login_required @login_required
def view_dashboard(request): def view_dashboard(request):
context = {'page_title': 'Welcome to routerfleet'} context = {'page_title': 'Welcome to routerfleet'}
@ -43,7 +76,9 @@ def view_status(request):
'queue': RouterBackup.objects.filter(success=False, error=False).count(), '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(), '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(), '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_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_offline_count': RouterStatus.objects.filter(status_online=False, router__monitoring=True).count(),
'router_not_monitored_count': Router.objects.filter(enabled=True, monitoring=False).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) 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,5 +1,6 @@
from django.contrib import messages from django.contrib import messages
from django.db.models import Sum from django.db.models import Sum
from django.http import JsonResponse
from django.utils import timezone from django.utils import timezone
from django.shortcuts import render, get_object_or_404, redirect from django.shortcuts import render, get_object_or_404, redirect
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
@ -227,3 +228,44 @@ def view_create_instant_backup_task(request):
messages.success(request, 'Backup task created successfully') messages.success(request, 'Backup task created successfully')
return redirect(router_details_url) return redirect(router_details_url)
@login_required
def view_create_instant_backup_multiple_routers(request):
if request.method == 'POST':
if not UserAcl.objects.filter(user=request.user, user_level__gte=20).exists():
return JsonResponse({'error': 'Permission denied.'}, status=403)
uuids = request.POST.getlist('routers[]') # Ajustat pentru a primi lista corect
if not uuids:
return JsonResponse({'error': 'No routers selected.'}, status=400)
results = []
for uuid in uuids:
router = get_object_or_404(Router, uuid=uuid)
if RouterBackup.objects.filter(router=router, success=False, error=False).exists():
results.append({'router': router.name, 'status': 'active backup task exists'})
continue
if router.routerstatus.backup_lock:
results.append({'router': router.name, 'status': 'backup locked'})
continue
if not router.backup_profile:
results.append({'router': router.name, 'status': 'no backup profile'})
continue
router_backup = RouterBackup.objects.create(
router=router,
schedule_time=timezone.now(),
schedule_type='instant'
)
router.routerstatus.backup_lock = router_backup.schedule_time
router.routerstatus.save()
results.append({'router': router.name, 'status': 'backup started'})
return JsonResponse({'results': results})
return JsonResponse({'error': 'Invalid request method.'}, status=405)

View file

@ -1,10 +1,12 @@
from django.contrib import admin from django.contrib import admin
from django.urls import path from django.urls import path
from dashboard.views import view_dashboard, view_status 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 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 user_manager.views import view_manage_user, view_user_list
from accounts.views import view_login, view_logout, view_create_first_user from accounts.views import view_login, view_logout, view_create_first_user
from router_manager.views import view_router_list, view_manage_router, view_router_group_list, view_ssh_key_list, view_manage_router_group, view_manage_sshkey, view_router_details, view_create_instant_backup_task, view_router_availability from router_manager.views import view_create_instant_backup_multiple_routers, view_router_list, view_manage_router, view_router_group_list, view_ssh_key_list, view_manage_router_group, view_manage_sshkey, view_router_details, view_create_instant_backup_task, view_router_availability
from backup.views import view_backup_profile_list, view_manage_backup_profile, view_backup_list, view_backup_details, view_debug_run_backups, view_compare_backups, view_backup_download, view_backup_delete from backup.views import view_backup_profile_list, view_manage_backup_profile, view_backup_list, view_backup_details, view_debug_run_backups, view_compare_backups, view_backup_download, view_backup_delete
from monitoring.views import view_export_router_list, view_update_router_status, view_router_config_timestamp, view_router_last_status_change from monitoring.views import view_export_router_list, view_update_router_status, view_router_config_timestamp, view_router_last_status_change
from backup_data.views import view_generate_backup_schedule, view_create_backup_tasks, view_perform_backup_tasks, view_housekeeping from backup_data.views import view_generate_backup_schedule, view_create_backup_tasks, view_perform_backup_tasks, view_housekeeping
@ -19,6 +21,8 @@ urlpatterns = [
path('debug/test_messages/', view_debug_test_messages, name='debug_test_messages'), path('debug/test_messages/', view_debug_test_messages, name='debug_test_messages'),
path('', view_dashboard, name='dashboard'), path('', view_dashboard, name='dashboard'),
path('status/', view_status, name='status'), 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/list/', view_user_list, name='user_list'),
path('user/manage/', view_manage_user, name='manage_user'), path('user/manage/', view_manage_user, name='manage_user'),
path('accounts/create_first_user/', view_create_first_user, name='create_first_user'), path('accounts/create_first_user/', view_create_first_user, name='create_first_user'),
@ -33,6 +37,7 @@ urlpatterns = [
path('router/manage_group/', view_manage_router_group, name='manage_router_group'), path('router/manage_group/', view_manage_router_group, name='manage_router_group'),
path('router/manage_sshkey/', view_manage_sshkey, name='manage_sshkey'), path('router/manage_sshkey/', view_manage_sshkey, name='manage_sshkey'),
path('router/create_instant_backup/', view_create_instant_backup_task, name='create_instant_backup_task'), path('router/create_instant_backup/', view_create_instant_backup_task, name='create_instant_backup_task'),
path('router/create_instant_backup/multiple/', view_create_instant_backup_multiple_routers, name='create_instant_backup_multiple'),
path('router/import_tool/', view_import_tool_list, name='import_tool_list'), path('router/import_tool/', view_import_tool_list, name='import_tool_list'),
path('router/import_tool/csv/', view_import_csv_file, name='import_csv_file'), path('router/import_tool/csv/', view_import_csv_file, name='import_csv_file'),
path('router/import_tool/details/', view_import_details, name='import_details'), path('router/import_tool/details/', view_import_details, name='import_details'),

View file

@ -1,123 +1,321 @@
{% extends 'base.html' %} {% extends 'base.html' %}
{% block content %} {% block content %}
<div class='row'> <style>
<div class='{% if form_size %}{{ form_size }}{% else %}col-lg-6{% endif %}'> .info-box {
<div class="card card-primary card-outline"> transition: transform 0.2s;
{% if page_title %} }
<div class="card-header">
<h3 class="card-title">{{ page_title }}</h3> .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> </div>
{% endif %}
<div class="card-body row"> <!-- Backup Queue -->
<div class="col-lg-12"> <div class="col-md-3">
<table class="table"> <div class="info-box bg-warning">
<thead> <span class="info-box-icon"><i class="fas fa-tasks"></i></span>
<tr> <div class="info-box-content">
<th>Item</th> <span class="info-box-text">Backup Queue</span>
<th>Value</th> <span class="info-box-number">{{ queue }}</span>
</tr> </div>
</thead> </div>
<tbody> </div>
<tr>
<td> <!-- Successful Backups -->
<strong> <div class="col-md-3">
Used storage (binary backup) <div class="info-box bg-success">
</strong> <span class="info-box-icon"><i class="fas fa-check-circle"></i></span>
</td> <div class="info-box-content">
<td> <span class="info-box-text">Successful Backups (24h)</span>
{{ media_root_stats.usage_percentage }}% <span class="info-box-number">{{ success_backup_last_24h }}</span>
</td> </div>
</tr> </div>
<tr> </div>
<td>
<strong> <!-- Failed Backups -->
Backup Queue <div class="col-md-3">
</strong> <div class="info-box bg-danger">
</td> <span class="info-box-icon"><i class="fas fa-times-circle"></i></span>
<td> <div class="info-box-content">
{{ queue }} <span class="info-box-text">Failed Backups (24h)</span>
</td> <span class="info-box-number">{{ error_backup_last_24h }}</span>
</tr> </div>
<tr> </div>
<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>
</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> </div>
<p class="small">This status page is just a placeholder, it will be improved in next versions</p>
</div> </div>
</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 %}

View file

@ -17,6 +17,7 @@
<table class="table table-hover datatables-no-export"> <table class="table table-hover datatables-no-export">
<thead> <thead>
<tr> <tr>
<th>Select</th>
<th>Name</th> <th>Name</th>
<th>Type</th> <th>Type</th>
<th>Address</th> <th>Address</th>
@ -26,17 +27,14 @@
<th>Auth</th> <th>Auth</th>
<th></th> <th></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for router in router_list %} {% for router in router_list %}
<tr {% if not router.enabled %}style="text-decoration: line-through;"{% endif %}> <tr {% if not router.enabled %}style="text-decoration: line-through;"{% endif %}>
<td> <td><input type="checkbox" class="router-checkbox" data-uuid="{{ router.uuid }}"></td>
<a href="/router/details/?uuid={{ router.uuid }}">{{ router.name }}</a> <td><a href="/router/details/?uuid={{ router.uuid }}">{{ router.name }}</a></td>
</td>
<td>{{ router.get_router_type_display }}</td> <td>{{ router.get_router_type_display }}</td>
<td>{{ router.address }}</td> <td>{{ router.address }}</td>
<td id="status-{{ router.uuid }}"> <td id="status-{{ router.uuid }}">
{% if router.monitoring %} {% if router.monitoring %}
{% if router.routerstatus.status_online %} {% if router.routerstatus.status_online %}
@ -50,16 +48,14 @@
</td> </td>
<td> <td>
{% if router.router_type != 'monitoring' %} {% if router.router_type != 'monitoring' %}
{% if router.backup_profile %} {% if router.backup_profile %}
{{ router.backup_profile }} {% if router.routerstatus.last_backup_failed %}<i class="fas fa-exclamation-triangle text-danger" title="Last backup failed to complete"></i>{% endif %} {{ router.backup_profile }} {% if router.routerstatus.last_backup_failed %}<i class="fas fa-exclamation-triangle text-danger" title="Last backup failed to complete"></i>{% endif %}
{% else %} {% else %}
<i class="fas fa-exclamation-triangle text-warning" title="No backup profile selected"></i> <i class="fas fa-exclamation-triangle text-warning" title="No backup profile selected"></i>
{% endif %} {% endif %}
{% endif %} {% endif %}
</td> </td>
<td> <td>{{ router.routergroup_set.count }}</td>
{{ router.routergroup_set.count }}
</td>
<td class="min-width"> <td class="min-width">
{% if router.router_type != 'monitoring' %} {% if router.router_type != 'monitoring' %}
{% if router.ssh_key %} {% if router.ssh_key %}
@ -78,17 +74,20 @@
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
</div> </div>
<div class="row mb-3">
</div> <div class="col-lg-12 text-right">
<div class="row"> <div class="btn-group" role="group">
<div class="col-lg-12"> <a href="/router/manage/" class="btn btn-primary">Add Router</a>
<a href="/router/manage/" class="btn btn-primary">Add Router</a> <a href="/router/import_tool/" class="btn btn-warning">Import Tool</a>
<a href="/router/import_tool/" class="btn btn-outline-primary">Import tool</a> <button id="select-all" class="btn btn-outline-success"><i class="fas fa-check-square"></i> Select All</button>
<button id="select-none" class="btn btn-outline-danger"><i class="fas fa-square"></i> Select None</button>
<button id="create-backup" class="btn btn-warning" style="display: none;">Create Backup Task</button>
</div>
</div> </div>
</div> </div>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -160,4 +159,58 @@
checkStatusChange(); checkStatusChange();
}); });
</script> </script>
<script>
$(document).ready(function() {
$('.router-checkbox').prop('checked', false);
$('#select-all').click(function() {
$('.router-checkbox').prop('checked', true).trigger('change');
});
$('#select-none').click(function() {
$('.router-checkbox').prop('checked', false).trigger('change');
});
$('.router-checkbox').change(function() {
var checkedCount = $('.router-checkbox:checked').length;
$('#create-backup').toggle(checkedCount >= 1);
});
$('#create-backup').click(function() {
var selectedRouters = $('.router-checkbox:checked').map(function() {
return $(this).data('uuid');
}).get();
$.ajax({
url: '/router/create_instant_backup/multiple/',
method: 'POST',
data: {
routers: selectedRouters,
csrfmiddlewaretoken: '{{ csrf_token }}'
},
success: function(response) {
let messages = response.results.map(item => `${item.router}: ${item.status}`).join('\n');
// Create success toast
$(document).Toasts('create', {
class: 'bg-success',
title: 'Backup Status',
body: 'The following routers were backed up: ' + messages,
delay: 10000,
autohide: true
});
},
error: function(xhr) {
// Create error toast
$(document).Toasts('create', {
class: 'bg-danger',
title: 'Error',
body: 'Error: ' + xhr.responseJSON.error,
delay: 10000,
autohide: true
});
}
});
});
});
</script>
{% endblock %} {% endblock %}