import tool first implementation

This commit is contained in:
Eduardo Silva 2024-07-10 10:18:29 -03:00
parent 931a047d09
commit f366e2cd69
14 changed files with 314 additions and 0 deletions

0
import_tool/__init__.py Normal file
View file

3
import_tool/admin.py Normal file
View file

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

6
import_tool/apps.py Normal file
View file

@ -0,0 +1,6 @@
from django.apps import AppConfig
class ImportToolConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'import_tool'

100
import_tool/forms.py Normal file
View file

@ -0,0 +1,100 @@
from router_manager.models import SUPPORTED_ROUTER_TYPES
from django import forms
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout, Submit, Row, Column, HTML
from .models import CsvData
import csv
import io
import re
from django.core.exceptions import ValidationError
SUPPORTED_ROUTER_TYPES = [rt[0] for rt in SUPPORTED_ROUTER_TYPES]
class CsvDataForm(forms.ModelForm):
class Meta:
model = CsvData
fields = ['raw_csv_data']
def __init__(self, *args, **kwargs):
super(CsvDataForm, self).__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_method = 'post'
self.helper.layout = Layout(
Row(
Column('raw_csv_data', css_class='form-group col-md-12 mb-0'),
css_class='form-row'
),
Row(
Column(
Submit('submit', 'Save', css_class='btn btn-success'),
HTML(' <a class="btn btn-secondary" href="/router/import_tool/">Back</a> '),
css_class='col-md-12'),
css_class='form-row'
)
)
def clean(self):
cleaned_data = super().clean()
raw_csv_data = cleaned_data.get('raw_csv_data')
expected_fields = [
'name', 'username', 'password', 'ssh_key', 'address', 'port', 'router_type',
'backup_profile', 'router_group', 'monitoring'
]
if raw_csv_data:
csv_file = io.StringIO(raw_csv_data)
reader = csv.DictReader(csv_file)
if reader.fieldnames is None:
raise ValidationError("The input is not a valid CSV file or it's empty.")
if reader.fieldnames != expected_fields:
raise ValidationError(f"The CSV header does not match the expected format. Expected fields: {expected_fields}")
required_fields = {'name', 'username', 'address', 'port', 'router_type', 'monitoring'}
optional_fields = {'backup_profile', 'router_group'}
valid_fields = required_fields | optional_fields | {'password', 'ssh_key'}
seen_names = set()
for line_number, row in enumerate(reader, start=2): # start=2 to account for the header
if not any(row.values()):
continue # Skip empty lines
missing_fields = required_fields - row.keys()
if missing_fields:
raise ValidationError(f"Missing required fields: {missing_fields} on line {line_number}")
extra_fields = set(row.keys()) - valid_fields
if extra_fields:
raise ValidationError(f"Unexpected fields: {extra_fields} on line {line_number}")
if not (row.get('password') or row.get('ssh_key')):
raise ValidationError(f"Either 'password' or 'ssh_key' must be provided on line {line_number}")
if row.get('password') and row.get('ssh_key'):
raise ValidationError(f"Both 'password' and 'ssh_key' cannot be provided together on line {line_number}")
if row['name'] in seen_names:
raise ValidationError(f"Duplicate name '{row['name']}' found on line {line_number}")
seen_names.add(row['name'])
if row['router_type'] not in SUPPORTED_ROUTER_TYPES:
raise ValidationError(f"Invalid router_type '{row['router_type']}' on line {line_number}")
if row['monitoring'].lower() not in {'true', 'false'}:
raise ValidationError(f"Invalid value for 'monitoring' on line {line_number}. Must be 'true' or 'false'.")
if not self._is_valid_ip_or_hostname(row['address']):
raise ValidationError(f"Invalid address '{row['address']}' on line {line_number}")
if not row['port'].isdigit() or not (1 <= int(row['port']) <= 65535):
raise ValidationError(f"Invalid port '{row['port']}' on line {line_number}")
return cleaned_data
def _is_valid_ip_or_hostname(self, value):
ip_pattern = re.compile(r"^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$")
hostname_pattern = re.compile(r"^(?=.{1,253}$)(?:(?!-)[A-Za-z0-9-]{1,63}(?<!-)\.)+[A-Za-z]{2,6}$")
return ip_pattern.match(value) or hostname_pattern.match(value)

View file

@ -0,0 +1,53 @@
# Generated by Django 5.0.7 on 2024-07-09 19:06
import django.db.models.deletion
import uuid
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
('backup', '0008_alter_backupprofile_backup_interval'),
('router_manager', '0016_router_port'),
]
operations = [
migrations.CreateModel(
name='CsvData',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('raw_csv_data', models.TextField()),
('import_data', models.JSONField(blank=True, null=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, unique=True)),
],
),
migrations.CreateModel(
name='ImportTask',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=100)),
('username', models.CharField(max_length=100)),
('password', models.CharField(blank=True, max_length=100, null=True)),
('address', models.CharField(max_length=100)),
('port', models.IntegerField(default=22)),
('router_type', models.CharField(choices=[('monitoring', 'Monitoring Only'), ('routeros', 'Mikrotik (RouterOS)'), ('openwrt', 'OpenWRT')], max_length=100)),
('monitoring', models.BooleanField(default=True)),
('import_success', models.BooleanField(default=False)),
('import_error', models.BooleanField(default=False)),
('import_error_message', models.TextField(blank=True, null=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, unique=True)),
('backup_profile', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='backup.backupprofile')),
('csv_data', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='import_tool.csvdata')),
('router', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='router_manager.router')),
('router_group', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='router_manager.routergroup')),
('ssh_key', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='router_manager.sshkey')),
],
),
]

View file

@ -0,0 +1,33 @@
# Generated by Django 5.0.7 on 2024-07-09 19:19
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('import_tool', '0001_initial'),
]
operations = [
migrations.RenameField(
model_name='csvdata',
old_name='created_at',
new_name='created',
),
migrations.RenameField(
model_name='csvdata',
old_name='updated_at',
new_name='updated',
),
migrations.RenameField(
model_name='importtask',
old_name='created_at',
new_name='created',
),
migrations.RenameField(
model_name='importtask',
old_name='updated_at',
new_name='updated',
),
]

View file

44
import_tool/models.py Normal file
View file

@ -0,0 +1,44 @@
from django.db import models
from backup.models import BackupProfile
from router_manager.models import Router, SSHKey, SUPPORTED_ROUTER_TYPES, RouterGroup
import uuid
class CsvData(models.Model):
raw_csv_data = models.TextField()
import_data = models.JSONField(blank=True, null=True)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
uuid = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
def __str__(self):
return self.name
class ImportTask(models.Model):
csv_data = models.ForeignKey(CsvData, on_delete=models.CASCADE)
router = models.ForeignKey(Router, on_delete=models.SET_NULL, blank=True, null=True)
name = models.CharField(max_length=100)
ssh_key = models.ForeignKey(SSHKey, on_delete=models.SET_NULL, blank=True, null=True)
username = models.CharField(max_length=100)
password = models.CharField(max_length=100, blank=True, null=True)
router_group = models.ForeignKey(RouterGroup, on_delete=models.SET_NULL, blank=True, null=True)
address = models.CharField(max_length=100)
port = models.IntegerField(default=22)
backup_profile = models.ForeignKey(BackupProfile, on_delete=models.SET_NULL, blank=True, null=True)
router_type = models.CharField(max_length=100, choices=SUPPORTED_ROUTER_TYPES)
monitoring = models.BooleanField(default=True)
import_success = models.BooleanField(default=False)
import_error = models.BooleanField(default=False)
import_error_message = models.TextField(blank=True, null=True)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
uuid = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
def __str__(self):
return self.name

3
import_tool/tests.py Normal file
View file

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

22
import_tool/views.py Normal file
View file

@ -0,0 +1,22 @@
from django.shortcuts import render
from django.contrib.auth.decorators import login_required
from .models import CsvData, ImportTask
from .forms import CsvDataForm
@login_required()
def view_import_tool_list(request):
import_list = CsvData.objects.all().order_by('-created')
data = {
'import_list': import_list,
'page_title': 'CSV import List',
}
return render(request, 'import_tool/import_tool_list.html', context=data)
@login_required()
def view_import_csv_file(request):
form = CsvDataForm(request.POST or None)
data = {'form': form, 'page_title': 'Import CSV File'}
return render(request, 'generic_form.html', context=data)

View file

@ -48,6 +48,7 @@ INSTALLED_APPS = [
'integration_manager',
'routerfleet_tools',
'message_center',
'import_tool'
]
MIDDLEWARE = [

View file

@ -10,6 +10,7 @@ from monitoring.views import view_export_router_list, view_update_router_status,
from backup_data.views import view_generate_backup_schedule, view_create_backup_tasks, view_perform_backup_tasks, view_housekeeping
from routerfleet_tools.views import cron_check_updates
from message_center.views import view_message_channel_list, view_manage_message_settings, view_manage_message_channel, view_debug_test_messages, view_cron_concatenate_notifications, view_cron_send_messages, view_cron_daily_reports, view_message_history
from import_tool.views import view_import_tool_list, view_import_csv_file
urlpatterns = [
@ -32,6 +33,8 @@ urlpatterns = [
path('router/manage_group/', view_manage_router_group, name='manage_router_group'),
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/import_tool/', view_import_tool_list, name='import_tool_list'),
path('router/import_tool/csv/', view_import_csv_file, name='import_csv_file'),
path('backup/profile_list/', view_backup_profile_list, name='backup_profile_list'),
path('backup/manage_profile/', view_manage_backup_profile, name='manage_backup_profile'),
path('backup/backup_list/', view_backup_list, name='backup_list'),

View file

@ -0,0 +1,45 @@
{% extends 'base.html' %}
{% block content %}
<div class='row'>
<div class='col-lg-12'>
<div class="card card-primary card-outline">
{% if page_title %}
<div class="card-header">
<h3 class="card-title">{{ page_title }}</h3>
</div>
{% endif %}
<div class="card-body">
<div class="row">
<div class="col-lg-12">
<table class="table table-hover datatables-no-export">
<thead>
<tr>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
<div class="row">
<div class="col-lg-12">
<a href="/router/import_tool/csv/" class="btn btn-primary">Import CSV</a>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block custom_page_scripts %}
{% endblock %}

View file

@ -84,6 +84,7 @@
<div class="row">
<div class="col-lg-12">
<a href="/router/manage/" class="btn btn-primary">Add Router</a>
<a href="/router/import_tool/" class="btn btn-outline-primary">Import tool</a>
</div>
</div>