mirror of
https://github.com/eduardogsilva/routerfleet.git
synced 2025-08-02 17:24:29 +02:00
Backup download and delete.
This commit is contained in:
parent
60e1d557aa
commit
31b1c663f2
10 changed files with 110 additions and 11 deletions
|
@ -1,6 +1,6 @@
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.http import JsonResponse
|
from django.http import JsonResponse, HttpResponse, FileResponse
|
||||||
from django.shortcuts import render, get_object_or_404, redirect
|
from django.shortcuts import render, get_object_or_404, redirect, Http404
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
|
|
||||||
from routerlib.backup_functions import perform_backup
|
from routerlib.backup_functions import perform_backup
|
||||||
|
@ -10,6 +10,7 @@ from router_manager.models import Router
|
||||||
from backup_data.models import RouterBackup
|
from backup_data.models import RouterBackup
|
||||||
import difflib
|
import difflib
|
||||||
import unicodedata
|
import unicodedata
|
||||||
|
from routerlib.functions import gen_backup_name, get_router_backup_file_extension
|
||||||
|
|
||||||
|
|
||||||
@login_required()
|
@login_required()
|
||||||
|
@ -137,3 +138,36 @@ def view_debug_run_backups(request):
|
||||||
perform_backup(backup)
|
perform_backup(backup)
|
||||||
|
|
||||||
return JsonResponse(data)
|
return JsonResponse(data)
|
||||||
|
|
||||||
|
|
||||||
|
@login_required()
|
||||||
|
def view_backup_download(request):
|
||||||
|
backup = get_object_or_404(RouterBackup, uuid=request.GET.get('uuid'))
|
||||||
|
if request.GET.get('type') == 'text':
|
||||||
|
response = HttpResponse(backup.backup_text, content_type='text/plain')
|
||||||
|
if backup.backup_text_filename:
|
||||||
|
filename = backup.backup_text_filename
|
||||||
|
else:
|
||||||
|
filename = gen_backup_name(backup)
|
||||||
|
filename += f'.missing_backup_name.{get_router_backup_file_extension(backup.router.router_type)["text"]}'
|
||||||
|
print(filename)
|
||||||
|
response['Content-Disposition'] = f'attachment; filename={filename}'
|
||||||
|
return response
|
||||||
|
elif request.GET.get('type') == 'binary':
|
||||||
|
response = FileResponse(backup.backup_binary, as_attachment=True)
|
||||||
|
return response
|
||||||
|
else:
|
||||||
|
raise Http404
|
||||||
|
|
||||||
|
|
||||||
|
@login_required()
|
||||||
|
def view_backup_delete(request):
|
||||||
|
backup = get_object_or_404(RouterBackup, uuid=request.GET.get('uuid'))
|
||||||
|
redirect_url = f'/router/details/?uuid={backup.router.uuid}'
|
||||||
|
if request.GET.get('confirmation') == f'delete{backup.id}':
|
||||||
|
backup.delete()
|
||||||
|
messages.success(request, 'Backup deleted successfully')
|
||||||
|
return redirect(redirect_url)
|
||||||
|
else:
|
||||||
|
messages.warning(request, 'Backup not deleted|Invalid confirmation')
|
||||||
|
return redirect(f'/backup/backup_details/?uuid={backup.uuid}')
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 5.0.3 on 2024-04-01 12:31
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('backup_data', '0005_alter_routerbackup_backup_text_hash'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='routerbackup',
|
||||||
|
name='backup_text_filename',
|
||||||
|
field=models.CharField(blank=True, max_length=255, null=True),
|
||||||
|
),
|
||||||
|
]
|
|
@ -2,6 +2,7 @@ from django.db import models
|
||||||
from router_manager.models import Router
|
from router_manager.models import Router
|
||||||
import uuid
|
import uuid
|
||||||
import hashlib
|
import hashlib
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
class RouterBackup(models.Model):
|
class RouterBackup(models.Model):
|
||||||
|
@ -19,6 +20,7 @@ class RouterBackup(models.Model):
|
||||||
finish_time = models.DateTimeField(blank=True, null=True)
|
finish_time = models.DateTimeField(blank=True, null=True)
|
||||||
backup_text = models.TextField(blank=True, null=True)
|
backup_text = models.TextField(blank=True, null=True)
|
||||||
backup_text_hash = models.CharField(max_length=64, blank=True, db_index=True)
|
backup_text_hash = models.CharField(max_length=64, blank=True, db_index=True)
|
||||||
|
backup_text_filename = models.CharField(max_length=255, blank=True, null=True)
|
||||||
backup_binary = models.FileField(upload_to='backups/', blank=True, null=True)
|
backup_binary = models.FileField(upload_to='backups/', blank=True, null=True)
|
||||||
|
|
||||||
updated = models.DateTimeField(auto_now=True)
|
updated = models.DateTimeField(auto_now=True)
|
||||||
|
@ -32,4 +34,3 @@ class RouterBackup(models.Model):
|
||||||
self.backup_text_hash = ''
|
self.backup_text_hash = ''
|
||||||
super(RouterBackup, self).save(*args, **kwargs)
|
super(RouterBackup, self).save(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ crispy-bootstrap4==2024.1
|
||||||
crispy-bootstrap5==2024.2
|
crispy-bootstrap5==2024.2
|
||||||
cryptography==42.0.5
|
cryptography==42.0.5
|
||||||
Django==5.0.3
|
Django==5.0.3
|
||||||
|
django-cleanup==8.1.0
|
||||||
django-crispy-forms==2.1
|
django-crispy-forms==2.1
|
||||||
idna==3.6
|
idna==3.6
|
||||||
paramiko==3.4.0
|
paramiko==3.4.0
|
||||||
|
|
18
router_manager/migrations/0010_alter_router_router_type.py
Normal file
18
router_manager/migrations/0010_alter_router_router_type.py
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 5.0.3 on 2024-04-01 12:31
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('router_manager', '0009_router_backup_profile'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='router',
|
||||||
|
name='router_type',
|
||||||
|
field=models.CharField(choices=[('monitoring', 'Monitoring Only'), ('routeros', 'Mikrotik (RouterOS)'), ('openwrt', 'OpenWRT')], max_length=100),
|
||||||
|
),
|
||||||
|
]
|
|
@ -37,6 +37,7 @@ INSTALLED_APPS = [
|
||||||
'django.contrib.sessions',
|
'django.contrib.sessions',
|
||||||
'django.contrib.messages',
|
'django.contrib.messages',
|
||||||
'django.contrib.staticfiles',
|
'django.contrib.staticfiles',
|
||||||
|
'django_cleanup.apps.CleanupConfig',
|
||||||
'crispy_forms',
|
'crispy_forms',
|
||||||
'crispy_bootstrap4',
|
'crispy_bootstrap4',
|
||||||
'user_manager',
|
'user_manager',
|
||||||
|
|
|
@ -4,7 +4,7 @@ from dashboard.views import view_dashboard, view_status
|
||||||
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
|
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
|
||||||
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
|
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
|
from monitoring.views import view_export_router_list, view_update_router_status
|
||||||
|
|
||||||
|
|
||||||
|
@ -30,6 +30,8 @@ urlpatterns = [
|
||||||
path('backup/backup_list/', view_backup_list, name='backup_list'),
|
path('backup/backup_list/', view_backup_list, name='backup_list'),
|
||||||
path('backup/backup_details/', view_backup_details, name='backup_info'),
|
path('backup/backup_details/', view_backup_details, name='backup_info'),
|
||||||
path('backup/compare/', view_compare_backups, name='compare_backups'),
|
path('backup/compare/', view_compare_backups, name='compare_backups'),
|
||||||
|
path('backup/download/', view_backup_download, name='download_backup'),
|
||||||
|
path('backup/delete/', view_backup_delete, name='delete_backup'),
|
||||||
path('monitoring/export_router_list/', view_export_router_list, name='export_router_list'),
|
path('monitoring/export_router_list/', view_export_router_list, name='export_router_list'),
|
||||||
path('monitoring/update_router_status/', view_update_router_status, name='update_router_status'),
|
path('monitoring/update_router_status/', view_update_router_status, name='update_router_status'),
|
||||||
]
|
]
|
||||||
|
|
|
@ -5,6 +5,7 @@ import paramiko
|
||||||
import os
|
import os
|
||||||
from scp import SCPClient
|
from scp import SCPClient
|
||||||
from django.core.files.base import ContentFile
|
from django.core.files.base import ContentFile
|
||||||
|
from routerlib.functions import gen_backup_name
|
||||||
|
|
||||||
|
|
||||||
def perform_backup(router_backup: RouterBackup):
|
def perform_backup(router_backup: RouterBackup):
|
||||||
|
@ -70,7 +71,7 @@ def execute_backup(router_backup: RouterBackup):
|
||||||
router_backup.router.address, username=router_backup.router.username,
|
router_backup.router.address, username=router_backup.router.username,
|
||||||
password=router_backup.router.password, look_for_keys=False, allow_agent=False, timeout=10
|
password=router_backup.router.password, look_for_keys=False, allow_agent=False, timeout=10
|
||||||
)
|
)
|
||||||
backup_name = f"backup-routerfleet-{router_backup.schedule_type}-{router_backup.uuid}"
|
backup_name = gen_backup_name(router_backup)
|
||||||
ssh_client.exec_command(f'/system backup save name={backup_name}.backup')
|
ssh_client.exec_command(f'/system backup save name={backup_name}.backup')
|
||||||
ssh_client.exec_command(f'/export file={backup_name}.rsc')
|
ssh_client.exec_command(f'/export file={backup_name}.rsc')
|
||||||
return True, [f"{backup_name}.backup", f"{backup_name}.rsc"], error_message
|
return True, [f"{backup_name}.backup", f"{backup_name}.rsc"], error_message
|
||||||
|
@ -86,7 +87,7 @@ def execute_backup(router_backup: RouterBackup):
|
||||||
|
|
||||||
def retrieve_backup(router_backup: RouterBackup):
|
def retrieve_backup(router_backup: RouterBackup):
|
||||||
error_message = ""
|
error_message = ""
|
||||||
backup_name = f"backup-routerfleet-{router_backup.schedule_type}-{router_backup.uuid}"
|
backup_name = gen_backup_name(router_backup)
|
||||||
|
|
||||||
success = False
|
success = False
|
||||||
ssh_client = paramiko.SSHClient()
|
ssh_client = paramiko.SSHClient()
|
||||||
|
@ -110,6 +111,7 @@ def retrieve_backup(router_backup: RouterBackup):
|
||||||
rsc_content_cleaned = '\n'.join(
|
rsc_content_cleaned = '\n'.join(
|
||||||
line for line in rsc_content.split('\n') if not line.strip().startswith('#'))
|
line for line in rsc_content.split('\n') if not line.strip().startswith('#'))
|
||||||
router_backup.backup_text = rsc_content_cleaned
|
router_backup.backup_text = rsc_content_cleaned
|
||||||
|
router_backup.backup_text_filename = f"{backup_name}.rsc"
|
||||||
|
|
||||||
with open(backup_file_path, 'rb') as backup_file:
|
with open(backup_file_path, 'rb') as backup_file:
|
||||||
router_backup.backup_binary.save(f"{backup_name}.backup", ContentFile(backup_file.read()))
|
router_backup.backup_binary.save(f"{backup_name}.backup", ContentFile(backup_file.read()))
|
||||||
|
@ -141,7 +143,7 @@ def clean_up_backup_files(router_backup: RouterBackup):
|
||||||
router_backup.router.address, username=router_backup.router.username,
|
router_backup.router.address, username=router_backup.router.username,
|
||||||
password=router_backup.router.password, look_for_keys=False, timeout=10, allow_agent=False
|
password=router_backup.router.password, look_for_keys=False, timeout=10, allow_agent=False
|
||||||
)
|
)
|
||||||
ssh_client.exec_command('file remove [find where name~"backup-routerfleet-"]')
|
ssh_client.exec_command('file remove [find where name~"routerfleet-backup-"]')
|
||||||
else:
|
else:
|
||||||
print(f"Router type not supported: {router_backup.router.get_router_type_display()}")
|
print(f"Router type not supported: {router_backup.router.get_router_type_display()}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
|
@ -16,6 +16,10 @@ def get_router_backup_file_extension(router_type):
|
||||||
return {'text': 'txt', 'binary': 'bin'}
|
return {'text': 'txt', 'binary': 'bin'}
|
||||||
|
|
||||||
|
|
||||||
|
def gen_backup_name(router_backup):
|
||||||
|
return f'routerfleet-backup-{router_backup.id}-{router_backup.schedule_type}-{router_backup.router.address}-{router_backup.created.strftime("%Y-%m-%d_%H-%M")}'
|
||||||
|
|
||||||
|
|
||||||
def test_authentication(router_type, address, username, password, sshkey=None):
|
def test_authentication(router_type, address, username, password, sshkey=None):
|
||||||
router_features = get_router_features(router_type)
|
router_features = get_router_features(router_type)
|
||||||
if 'ssh' in router_features:
|
if 'ssh' in router_features:
|
||||||
|
|
|
@ -83,12 +83,19 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
<li class="list-group-item">
|
||||||
|
<b>Delete backup</b>
|
||||||
|
<span class="float-right">
|
||||||
|
|
||||||
|
<a href='javascript:void(0)' class='text-danger' data-command='delete' onclick='openCommandDialog(this)'>
|
||||||
|
<i class="far fa-trash-alt"></i>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -165,5 +172,16 @@
|
||||||
|
|
||||||
{% block custom_page_scripts %}
|
{% block custom_page_scripts %}
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function openCommandDialog(element) {
|
||||||
|
var command = element.getAttribute('data-command');
|
||||||
|
var confirmation = prompt("Please type 'delete{{ backup.id }}' to proceed.");
|
||||||
|
if (confirmation) {
|
||||||
|
var url = "/backup/delete/?uuid={{ backup.uuid }}&action=delete&confirmation=" + encodeURIComponent(confirmation);
|
||||||
|
window.location.href = url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
Loading…
Add table
Add a link
Reference in a new issue