From da55ce2464cf68a9d191a083bef004a43955b761 Mon Sep 17 00:00:00 2001 From: Frederico Sequeira <frederico.sequeira@ext.ec.europa.eu> Date: Fri, 22 Nov 2024 17:38:35 +0000 Subject: [PATCH 01/28] =?UTF-8?q?=E2=9C=A8=20Add=20new=20form=20to=20creat?= =?UTF-8?q?eVM?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- netbox_sys_plugin/forms/__init__.py | 3 +- netbox_sys_plugin/forms/createvm.py | 51 +++++++++++++++++++ netbox_sys_plugin/navigation.py | 11 +++- .../netbox_sys_plugin/cluster_management.html | 15 ++++++ netbox_sys_plugin/urls.py | 4 ++ netbox_sys_plugin/views.py | 22 ++++++++ 6 files changed, 104 insertions(+), 2 deletions(-) create mode 100644 netbox_sys_plugin/forms/createvm.py create mode 100644 netbox_sys_plugin/templates/netbox_sys_plugin/cluster_management.html diff --git a/netbox_sys_plugin/forms/__init__.py b/netbox_sys_plugin/forms/__init__.py index c3fb247..b26bccd 100644 --- a/netbox_sys_plugin/forms/__init__.py +++ b/netbox_sys_plugin/forms/__init__.py @@ -1,4 +1,5 @@ """Forms definitions""" from .provider import * -from .machine import * \ No newline at end of file +from .machine import * +from .createvm import * \ No newline at end of file diff --git a/netbox_sys_plugin/forms/createvm.py b/netbox_sys_plugin/forms/createvm.py new file mode 100644 index 0000000..78a38de --- /dev/null +++ b/netbox_sys_plugin/forms/createvm.py @@ -0,0 +1,51 @@ +from django import forms +from django.forms import inlineformset_factory +from netbox.forms import NetBoxModelForm +from virtualization.models import ClusterType, Cluster, VirtualMachine + +# Base forms +class ClusterTypeForm(NetBoxModelForm): + """Form for Cluster Types.""" + class Meta: + model = ClusterType + fields = ('name', 'description') + +class ClusterForm(NetBoxModelForm): + """Form for Clusters.""" + class Meta: + model = Cluster + fields = ('name', 'type', 'description') + +class VirtualMachineForm(NetBoxModelForm): + """Form for Virtual Machines.""" + class Meta: + model = VirtualMachine + fields = ('name', 'cluster', 'status', 'role', 'description') + +# Inline formsets for managing relationships +ClusterFormSet = inlineformset_factory( + ClusterType, Cluster, form=ClusterForm, extra=1, can_delete=True +) + +VirtualMachineFormSet = inlineformset_factory( + Cluster, VirtualMachine, form=VirtualMachineForm, extra=1, can_delete=True +) + +# Combined form +class ClusterManagementForm(ClusterTypeForm): + """Combined form for managing ClusterType, Cluster, and VirtualMachine.""" + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.cluster_formset = ClusterFormSet(instance=self.instance, prefix='clusters') + + def is_valid(self): + """Validate main form and inline formsets.""" + return super().is_valid() and self.cluster_formset.is_valid() + + def save(self, commit=True): + """Save main form and inline formsets.""" + instance = super().save(commit=commit) + self.cluster_formset.instance = instance + self.cluster_formset.save(commit=commit) + return instance \ No newline at end of file diff --git a/netbox_sys_plugin/navigation.py b/netbox_sys_plugin/navigation.py index c9e7582..9d52a7f 100644 --- a/netbox_sys_plugin/navigation.py +++ b/netbox_sys_plugin/navigation.py @@ -74,7 +74,15 @@ operItem =[ PluginMenuItem( link="plugins:netbox_sys_plugin:virtualmachinetype_list", link_text="Virtual Machine Types", - buttons=vm_machine_type_buttons, + buttons=vm_machine_type_buttons + ), +] + + +createVMItem =[ + PluginMenuItem( + link="plugins:netbox_sys_plugin:clustermanagement_list", + link_text="Create Virtual Machine ", ), ] @@ -85,6 +93,7 @@ menu = PluginMenu( ("Virtual Machine", (vmMachineItem)), ("Provider", clusterProviderCredentialsItem), ("Operation", (operItem)), + ("SYS", (createVMItem)), ), icon_class="mdi mdi-all-inclusive-box-outline", ) \ No newline at end of file diff --git a/netbox_sys_plugin/templates/netbox_sys_plugin/cluster_management.html b/netbox_sys_plugin/templates/netbox_sys_plugin/cluster_management.html new file mode 100644 index 0000000..8e17cf9 --- /dev/null +++ b/netbox_sys_plugin/templates/netbox_sys_plugin/cluster_management.html @@ -0,0 +1,15 @@ +{% extends 'base/layout.html' %} +{% load static %} + +{% block content %} +<h1>Create Virtual machine</h1> +<form method="post"> + {% csrf_token %} + {{ form.as_p }} + {{ form.cluster_formset.management_form }} + {% for cluster_form in form.cluster_formset %} + {{ cluster_form.as_p }} + {% endfor %} + <button type="submit" class="btn btn-primary">Save</button> +</form> +{% endblock %} \ No newline at end of file diff --git a/netbox_sys_plugin/urls.py b/netbox_sys_plugin/urls.py index 94b7d55..4421c8b 100644 --- a/netbox_sys_plugin/urls.py +++ b/netbox_sys_plugin/urls.py @@ -3,6 +3,7 @@ from django.urls import path from netbox.views.generic import ObjectChangeLogView, ObjectJournalView from netbox_sys_plugin import models, views +from .views import ClusterManagementView urlpatterns = ( #maintenance @@ -38,6 +39,9 @@ urlpatterns = ( path('vm-type/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='virtualmachinetype_changelog', kwargs={'model': models.VirtualMachineType}), path('vm-type/<int:pk>/journal/', ObjectJournalView.as_view(), name='virtualmachinetype_journal', kwargs={'model': models.VirtualMachineType}), + #CreateVM + + path('cluster-management/', ClusterManagementView.as_view(), name='clustermanagement_list'), ) diff --git a/netbox_sys_plugin/views.py b/netbox_sys_plugin/views.py index f93e69d..55b4680 100644 --- a/netbox_sys_plugin/views.py +++ b/netbox_sys_plugin/views.py @@ -1,7 +1,11 @@ """Model views definitions""" +from django.shortcuts import render, redirect from netbox.views import generic from . import forms, models, tables, filtersets +from virtualization.models import ClusterType + + #VmMaintenance class VmMaintenanceView(generic.ObjectView): @@ -115,3 +119,21 @@ class VmTypeEditView(generic.ObjectEditView): """ queryset = models.VirtualMachineType.objects.all() form = forms.VmTypeForm + +class ClusterManagementView(generic.ObjectEditView): + """View for managing ClusterType, Cluster, and VirtualMachine in one form.""" + queryset = ClusterType.objects.all() + form = forms.ClusterManagementForm + + def get(self, request, *args, **kwargs): + instance = self.get_object() + form = self.form(instance=instance) + return render(request, 'netbox_sys_plugin/cluster_management.html', {'form': form}) + + def post(self, request, *args, **kwargs): + instance = self.get_object() + form = self.form(data=request.POST, instance=instance) + if form.is_valid(): + form.save() + return redirect('plugins:netbox_sys_plugin:clustermanagement_list') + return render(request, 'netbox_sys_plugin/cluster_management.html', {'form': form}) \ No newline at end of file -- GitLab From 72bce8328bb1364ba41d4e4c912b6ca8fccffd54 Mon Sep 17 00:00:00 2001 From: Frederico Sequeira <frederico.sequeira@ext.ec.europa.eu> Date: Mon, 25 Nov 2024 10:51:05 +0000 Subject: [PATCH 02/28] =?UTF-8?q?=F0=9F=9A=A7=20Add=20new=20elements?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- netbox_sys_plugin/forms/createvm.py | 58 +++++++++++++++++++++++++++-- 1 file changed, 54 insertions(+), 4 deletions(-) diff --git a/netbox_sys_plugin/forms/createvm.py b/netbox_sys_plugin/forms/createvm.py index 78a38de..440654d 100644 --- a/netbox_sys_plugin/forms/createvm.py +++ b/netbox_sys_plugin/forms/createvm.py @@ -6,18 +6,45 @@ from virtualization.models import ClusterType, Cluster, VirtualMachine # Base forms class ClusterTypeForm(NetBoxModelForm): """Form for Cluster Types.""" + + name = forms.CharField( + max_length=200, min_length=1, required=True, label="Cluster Type Name" + ) + + description = forms.CharField( + max_length=200, min_length=1, required=True, label="Cluster Type Description" + ) + class Meta: model = ClusterType fields = ('name', 'description') class ClusterForm(NetBoxModelForm): """Form for Clusters.""" + + name = forms.CharField( + max_length=200, min_length=1, required=True, label="Cluster Name" + ) + + description = forms.CharField( + max_length=200, min_length=1, required=True, label="Cluster Description" + ) + class Meta: model = Cluster fields = ('name', 'type', 'description') class VirtualMachineForm(NetBoxModelForm): """Form for Virtual Machines.""" + + name = forms.CharField( + max_length=200, min_length=1, required=True, label="Virtual Machine Name" + ) + + description = forms.CharField( + max_length=200, min_length=1, required=True, label="Virtual Machine Description" + ) + class Meta: model = VirtualMachine fields = ('name', 'cluster', 'status', 'role', 'description') @@ -32,20 +59,43 @@ VirtualMachineFormSet = inlineformset_factory( ) # Combined form -class ClusterManagementForm(ClusterTypeForm): +class ClusterManagementForm(ClusterForm): """Combined form for managing ClusterType, Cluster, and VirtualMachine.""" def __init__(self, *args, **kwargs): + data = kwargs.pop('data',None) super().__init__(*args, **kwargs) - self.cluster_formset = ClusterFormSet(instance=self.instance, prefix='clusters') + + #Save temp + if not self.instance.pk: + self.instance.save() + + #init formsets + self.cluster_formsets = ClusterFormSet(instance=self.instance, data=data,prefix='clusters') + self.virtual_machine_formsets = [] + + for cluster in self.instance.clusters.all(): + vm_formset= VirtualMachineFormSet( + instance=cluster, data=kwargs.get('data'), prefix=f'vms_{cluster.pk}' + ) + self.virtual_machine_formsets.append((cluster.pk,vm_formset)) def is_valid(self): """Validate main form and inline formsets.""" - return super().is_valid() and self.cluster_formset.is_valid() + valid = super().is_valid() and self.cluster_formset.is_valid() + valid = valid and all(vm_formset.is_valid() for _, vm_formset in self.virtual_machine_formsets) + return valid + def save(self, commit=True): """Save main form and inline formsets.""" instance = super().save(commit=commit) + #save clusters self.cluster_formset.instance = instance - self.cluster_formset.save(commit=commit) + clusters = self.cluster_formset.save(commit=commit) + + #save virtual machine for eact cluster + for cluster, formset in zip(clusters, self.virtual_machine_formsets): + formset.instance = cluster + formset.save(commit=commit) return instance \ No newline at end of file -- GitLab From de0e2d977ea559c2dab3d5ee650e3b38b2c2da27 Mon Sep 17 00:00:00 2001 From: Frederico Sequeira <frederico.sequeira@ext.ec.europa.eu> Date: Mon, 25 Nov 2024 14:41:57 +0000 Subject: [PATCH 03/28] =?UTF-8?q?=F0=9F=94=8A=20Add=20logs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- netbox_sys_plugin/forms/createvm.py | 123 ++++++++++-------- .../netbox_sys_plugin/cluster_management.html | 29 ++++- netbox_sys_plugin/views.py | 1 + 3 files changed, 96 insertions(+), 57 deletions(-) diff --git a/netbox_sys_plugin/forms/createvm.py b/netbox_sys_plugin/forms/createvm.py index 440654d..74c91db 100644 --- a/netbox_sys_plugin/forms/createvm.py +++ b/netbox_sys_plugin/forms/createvm.py @@ -6,96 +6,113 @@ from virtualization.models import ClusterType, Cluster, VirtualMachine # Base forms class ClusterTypeForm(NetBoxModelForm): """Form for Cluster Types.""" - - name = forms.CharField( - max_length=200, min_length=1, required=True, label="Cluster Type Name" - ) - - description = forms.CharField( - max_length=200, min_length=1, required=True, label="Cluster Type Description" - ) - class Meta: model = ClusterType fields = ('name', 'description') class ClusterForm(NetBoxModelForm): """Form for Clusters.""" - - name = forms.CharField( - max_length=200, min_length=1, required=True, label="Cluster Name" - ) - - description = forms.CharField( - max_length=200, min_length=1, required=True, label="Cluster Description" - ) - class Meta: model = Cluster fields = ('name', 'type', 'description') class VirtualMachineForm(NetBoxModelForm): """Form for Virtual Machines.""" - - name = forms.CharField( - max_length=200, min_length=1, required=True, label="Virtual Machine Name" - ) - - description = forms.CharField( - max_length=200, min_length=1, required=True, label="Virtual Machine Description" - ) - class Meta: model = VirtualMachine fields = ('name', 'cluster', 'status', 'role', 'description') # Inline formsets for managing relationships ClusterFormSet = inlineformset_factory( - ClusterType, Cluster, form=ClusterForm, extra=1, can_delete=True + ClusterType, Cluster, form=ClusterForm, extra=1 #, can_delete=True ) VirtualMachineFormSet = inlineformset_factory( - Cluster, VirtualMachine, form=VirtualMachineForm, extra=1, can_delete=True + Cluster, VirtualMachine, form=VirtualMachineForm, extra=1 #, can_delete=True ) # Combined form -class ClusterManagementForm(ClusterForm): +class ClusterManagementForm(NetBoxModelForm): """Combined form for managing ClusterType, Cluster, and VirtualMachine.""" + class Meta: + model = ClusterType + fields = ('name', 'description') + def __init__(self, *args, **kwargs): - data = kwargs.pop('data',None) + data = kwargs.pop('data', None) super().__init__(*args, **kwargs) - #Save temp - if not self.instance.pk: - self.instance.save() + # Initialize Cluster formset + self.cluster_formset = ClusterFormSet(instance=self.instance, data=data, prefix='clusters') - #init formsets - self.cluster_formsets = ClusterFormSet(instance=self.instance, data=data,prefix='clusters') + # Populate the 'type' field for each cluster form + for cluster_form in self.cluster_formset: + cluster_form.initial['type'] = self.instance.pk + + # Handle clusters when the parent instance is not saved yet self.virtual_machine_formsets = [] - - for cluster in self.instance.clusters.all(): - vm_formset= VirtualMachineFormSet( - instance=cluster, data=kwargs.get('data'), prefix=f'vms_{cluster.pk}' + if self.instance.pk: + clusters = self.instance.clusters.all() # Get existing clusters for a saved ClusterType + else: + clusters = [] # For unsaved ClusterType, no clusters exist yet + + # Initialize Virtual Machine formsets for each cluster + for cluster in clusters: + vm_formset = VirtualMachineFormSet( + instance=cluster, data=data, prefix=f'vms_{cluster.pk}' ) - self.virtual_machine_formsets.append((cluster.pk,vm_formset)) + # Populate the 'cluster' field for each virtual machine form + for vm_form in vm_formset: + vm_form.initial['cluster'] = cluster.pk - def is_valid(self): - """Validate main form and inline formsets.""" - valid = super().is_valid() and self.cluster_formset.is_valid() - valid = valid and all(vm_formset.is_valid() for _, vm_formset in self.virtual_machine_formsets) - return valid + self.virtual_machine_formsets.append((cluster.pk, vm_formset)) + + # Add an empty virtual machine formset for new clusters + if not clusters and not data: + empty_vm_formset = VirtualMachineFormSet( + instance=Cluster(), data=data, prefix='vms_new' + ) + self.virtual_machine_formsets.append(('new', empty_vm_formset)) + def clean(self): + cleaned_data = super().clean() + if not cleaned_data.get('name'): + raise forms.ValidationError({'name':"This field is requiered."}) + return cleaned_data + + def is_valid(self): + """Validate the main form and all inline formsets.""" + valid = super().is_valid() + + # Validate Cluster formset + cluster_valid = self.cluster_formset.is_valid() + if not cluster_valid: + print("Cluster formset validation failed:", self.cluster_formset.errors) + # Validate Virtual Machine formsets + vm_valid = True + for _, vm_formset in self.virtual_machine_formsets: + if not vm_formset.is_valid(): + print("VM formset validation failed:", vm_formset.errors) + vm_valid = False + + return valid and cluster_valid and vm_valid + def save(self, commit=True): - """Save main form and inline formsets.""" + """Save the main form and all inline formsets.""" instance = super().save(commit=commit) - #save clusters + + # Save Cluster formset self.cluster_formset.instance = instance clusters = self.cluster_formset.save(commit=commit) - - #save virtual machine for eact cluster - for cluster, formset in zip(clusters, self.virtual_machine_formsets): - formset.instance = cluster - formset.save(commit=commit) + + # Save Virtual Machine formsets for each cluster + for cluster, vm_formset in zip(clusters, self.virtual_machine_formsets): + vm_formset.instance = cluster + for vm_form in vm_formset: + if not vm_form.cleaned_data.get('cluster'): + vm_form.cleaned_data['cluster'] = cluster + vm_formset.save(commit=commit) + return instance \ No newline at end of file diff --git a/netbox_sys_plugin/templates/netbox_sys_plugin/cluster_management.html b/netbox_sys_plugin/templates/netbox_sys_plugin/cluster_management.html index 8e17cf9..323c212 100644 --- a/netbox_sys_plugin/templates/netbox_sys_plugin/cluster_management.html +++ b/netbox_sys_plugin/templates/netbox_sys_plugin/cluster_management.html @@ -1,15 +1,36 @@ -{% extends 'base/layout.html' %} -{% load static %} +{% extends "base/layout.html" %} {% block content %} -<h1>Create Virtual machine</h1> +<h1>Manage Cluster Types, Clusters, and Virtual Machines</h1> <form method="post"> {% csrf_token %} {{ form.as_p }} + + <h2>Clusters</h2> {{ form.cluster_formset.management_form }} {% for cluster_form in form.cluster_formset %} - {{ cluster_form.as_p }} + <div class="form-group"> + {{ cluster_form.as_p }} + {% for error in cluster_form.errors %} + <p class="text-danger">{{ error }}</p> + {% endfor %} + </div> + {% endfor %} + + <h2>Virtual Machines</h2> + {% for cluster_id, vm_formset in form.virtual_machine_formsets %} + <h3>Cluster {{ cluster_id }}</h3> + {{ vm_formset.management_form }} + {% for vm_form in vm_formset %} + <div class="form-group"> + {{ vm_form.as_p }} + {% for error in vm_form.errors %} + <p class="text-danger">{{ error }}</p> + {% endfor %} + </div> + {% endfor %} {% endfor %} + <button type="submit" class="btn btn-primary">Save</button> </form> {% endblock %} \ No newline at end of file diff --git a/netbox_sys_plugin/views.py b/netbox_sys_plugin/views.py index 55b4680..90f82a2 100644 --- a/netbox_sys_plugin/views.py +++ b/netbox_sys_plugin/views.py @@ -131,6 +131,7 @@ class ClusterManagementView(generic.ObjectEditView): return render(request, 'netbox_sys_plugin/cluster_management.html', {'form': form}) def post(self, request, *args, **kwargs): + print('POST DATA: ',request.POST) instance = self.get_object() form = self.form(data=request.POST, instance=instance) if form.is_valid(): -- GitLab From c26c22ce77aa8ce85a128411983412d3b6999c1a Mon Sep 17 00:00:00 2001 From: Frederico Sequeira <frederico.sequeira@ext.ec.europa.eu> Date: Mon, 25 Nov 2024 15:26:34 +0000 Subject: [PATCH 04/28] =?UTF-8?q?=F0=9F=92=A9=20=F0=9F=92=84=20Add=20Ui=20?= =?UTF-8?q?elements=20and=20not=20working=20code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- netbox_sys_plugin/forms/createvm.py | 5 +- .../netbox_sys_plugin/cluster_management.html | 51 +++++++++++++++---- 2 files changed, 44 insertions(+), 12 deletions(-) diff --git a/netbox_sys_plugin/forms/createvm.py b/netbox_sys_plugin/forms/createvm.py index 74c91db..c198a54 100644 --- a/netbox_sys_plugin/forms/createvm.py +++ b/netbox_sys_plugin/forms/createvm.py @@ -111,8 +111,9 @@ class ClusterManagementForm(NetBoxModelForm): for cluster, vm_formset in zip(clusters, self.virtual_machine_formsets): vm_formset.instance = cluster for vm_form in vm_formset: - if not vm_form.cleaned_data.get('cluster'): - vm_form.cleaned_data['cluster'] = cluster + #if not vm_form.cleaned_data.get('cluster'): + # vm_form.cleaned_data['cluster'] = cluster + vm_form.instance.cluster = cluster vm_formset.save(commit=commit) return instance \ No newline at end of file diff --git a/netbox_sys_plugin/templates/netbox_sys_plugin/cluster_management.html b/netbox_sys_plugin/templates/netbox_sys_plugin/cluster_management.html index 323c212..115e891 100644 --- a/netbox_sys_plugin/templates/netbox_sys_plugin/cluster_management.html +++ b/netbox_sys_plugin/templates/netbox_sys_plugin/cluster_management.html @@ -1,15 +1,38 @@ -{% extends "base/layout.html" %} +{% extends 'base/layout.html' %} +{% load buttons %} +{% load custom_links %} +{% load helpers %} +{% load perms %} +{% load plugins %} +{% load tabs %} +{% load i18n %} + + +{% block title %} +SYS - Virtual Machine +{% endblock title %} +{% block tabs %} + <ul class="nav nav-tabs px-3"> + <li class="nav-item" role="presentation"> + <button class="nav-link active" id="edit-form-tab" data-bs-toggle="tab" data-bs-target="#edit-form" type="button" role="tab" aria-controls="edit-form" aria-selected="true"> + Create </button> + </li> + </ul> +{% endblock tabs %} +{% block content-wrapper %} +<div class="tab-content"> + <div class="tab-pane show active" id="edit-form" role="tabpanel" aria-labelledby="object-list-tab"> {% block content %} -<h1>Manage Cluster Types, Clusters, and Virtual Machines</h1> -<form method="post"> +<form method="post" enctype="multipart/form-data" class="form-object-edit mt-5"> + <h5>Provider Type</h5> {% csrf_token %} {{ form.as_p }} - <h2>Clusters</h2> + <h5>Provider Credentials</h5> {{ form.cluster_formset.management_form }} {% for cluster_form in form.cluster_formset %} - <div class="form-group"> + <div class="form-group" class="field-group mb-5"> {{ cluster_form.as_p }} {% for error in cluster_form.errors %} <p class="text-danger">{{ error }}</p> @@ -17,12 +40,11 @@ </div> {% endfor %} - <h2>Virtual Machines</h2> + <h5>Virtual Machine Info</h5> {% for cluster_id, vm_formset in form.virtual_machine_formsets %} - <h3>Cluster {{ cluster_id }}</h3> {{ vm_formset.management_form }} {% for vm_form in vm_formset %} - <div class="form-group"> + <div class="form-group" class="field-group mb-5"> {{ vm_form.as_p }} {% for error in vm_form.errors %} <p class="text-danger">{{ error }}</p> @@ -30,7 +52,16 @@ </div> {% endfor %} {% endfor %} - + <div class="text-end my-3"> + {% block buttons %} <button type="submit" class="btn btn-primary">Save</button> + {% endblock buttons %} +</div> </form> -{% endblock %} \ No newline at end of file +</div> +</div> +{% endblock %} +{% endblock content-wrapper %} + +{# User messages #} +{% include 'inc/messages.html' %} \ No newline at end of file -- GitLab From 7a85e2849b06835fc527d7856023164a194d815f Mon Sep 17 00:00:00 2001 From: Frederico Sequeira <frederico.sequeira@ext.ec.europa.eu> Date: Tue, 26 Nov 2024 14:22:52 +0000 Subject: [PATCH 05/28] =?UTF-8?q?=F0=9F=9A=A7=20Working=20form!?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- netbox_sys_plugin/forms/createvm.py | 100 +++++----------------------- netbox_sys_plugin/navigation.py | 11 +++ netbox_sys_plugin/views.py | 55 ++++++++++++--- 3 files changed, 75 insertions(+), 91 deletions(-) diff --git a/netbox_sys_plugin/forms/createvm.py b/netbox_sys_plugin/forms/createvm.py index c198a54..7c063c4 100644 --- a/netbox_sys_plugin/forms/createvm.py +++ b/netbox_sys_plugin/forms/createvm.py @@ -3,32 +3,25 @@ from django.forms import inlineformset_factory from netbox.forms import NetBoxModelForm from virtualization.models import ClusterType, Cluster, VirtualMachine -# Base forms -class ClusterTypeForm(NetBoxModelForm): - """Form for Cluster Types.""" - class Meta: - model = ClusterType - fields = ('name', 'description') - class ClusterForm(NetBoxModelForm): """Form for Clusters.""" class Meta: model = Cluster - fields = ('name', 'type', 'description') + fields = ('name', 'type', 'description','status','tags') class VirtualMachineForm(NetBoxModelForm): """Form for Virtual Machines.""" class Meta: model = VirtualMachine - fields = ('name', 'cluster', 'status', 'role', 'description') + fields = ('name', 'cluster', 'status', 'role', 'description','tags') # Inline formsets for managing relationships ClusterFormSet = inlineformset_factory( - ClusterType, Cluster, form=ClusterForm, extra=1 #, can_delete=True + ClusterType, Cluster, form=ClusterForm, extra=1, can_delete=False ) VirtualMachineFormSet = inlineformset_factory( - Cluster, VirtualMachine, form=VirtualMachineForm, extra=1 #, can_delete=True + Cluster, VirtualMachine, form=VirtualMachineForm, extra=1, can_delete=False ) # Combined form @@ -37,83 +30,24 @@ class ClusterManagementForm(NetBoxModelForm): class Meta: model = ClusterType - fields = ('name', 'description') + fields = ('name', 'slug','description','tags') def __init__(self, *args, **kwargs): data = kwargs.pop('data', None) super().__init__(*args, **kwargs) - # Initialize Cluster formset - self.cluster_formset = ClusterFormSet(instance=self.instance, data=data, prefix='clusters') - - # Populate the 'type' field for each cluster form - for cluster_form in self.cluster_formset: - cluster_form.initial['type'] = self.instance.pk - - # Handle clusters when the parent instance is not saved yet + # Initialize formsets + #Cluster + Cluster type + self.cluster_formset = ClusterFormSet(data=data, prefix='clusters') + #Virtual Machine self.virtual_machine_formsets = [] - if self.instance.pk: - clusters = self.instance.clusters.all() # Get existing clusters for a saved ClusterType - else: - clusters = [] # For unsaved ClusterType, no clusters exist yet - - # Initialize Virtual Machine formsets for each cluster - for cluster in clusters: - vm_formset = VirtualMachineFormSet( - instance=cluster, data=data, prefix=f'vms_{cluster.pk}' - ) - # Populate the 'cluster' field for each virtual machine form - for vm_form in vm_formset: - vm_form.initial['cluster'] = cluster.pk - - self.virtual_machine_formsets.append((cluster.pk, vm_formset)) - - # Add an empty virtual machine formset for new clusters - if not clusters and not data: - empty_vm_formset = VirtualMachineFormSet( - instance=Cluster(), data=data, prefix='vms_new' - ) - self.virtual_machine_formsets.append(('new', empty_vm_formset)) - - def clean(self): - cleaned_data = super().clean() - if not cleaned_data.get('name'): - raise forms.ValidationError({'name':"This field is requiered."}) - return cleaned_data + empty_vm_formset = VirtualMachineFormSet(data=data, prefix='vms_new') + self.virtual_machine_formsets.append(('new', empty_vm_formset)) def is_valid(self): - """Validate the main form and all inline formsets.""" - valid = super().is_valid() - - # Validate Cluster formset - cluster_valid = self.cluster_formset.is_valid() - if not cluster_valid: - print("Cluster formset validation failed:", self.cluster_formset.errors) - - # Validate Virtual Machine formsets - vm_valid = True - for _, vm_formset in self.virtual_machine_formsets: - if not vm_formset.is_valid(): - print("VM formset validation failed:", vm_formset.errors) - vm_valid = False - - return valid and cluster_valid and vm_valid - - def save(self, commit=True): - """Save the main form and all inline formsets.""" - instance = super().save(commit=commit) - - # Save Cluster formset - self.cluster_formset.instance = instance - clusters = self.cluster_formset.save(commit=commit) - - # Save Virtual Machine formsets for each cluster - for cluster, vm_formset in zip(clusters, self.virtual_machine_formsets): - vm_formset.instance = cluster - for vm_form in vm_formset: - #if not vm_form.cleaned_data.get('cluster'): - # vm_form.cleaned_data['cluster'] = cluster - vm_form.instance.cluster = cluster - vm_formset.save(commit=commit) - - return instance \ No newline at end of file + """Validate the main form only.""" + main_form_valid = super().is_valid() + print("Main form valid:", main_form_valid) + if not main_form_valid: + print("Main form errors:", self.errors) + return main_form_valid diff --git a/netbox_sys_plugin/navigation.py b/netbox_sys_plugin/navigation.py index 9d52a7f..69afd5a 100644 --- a/netbox_sys_plugin/navigation.py +++ b/netbox_sys_plugin/navigation.py @@ -47,6 +47,16 @@ vm_machine_type_buttons = [ ), ] +create_vm_buttons = [ + PluginMenuButton( + link="plugins:netbox_sys_plugin:clustermanagement_list", + title="Add", + icon_class="mdi mdi-plus-thick", + color=ButtonColorChoices.GREEN, + permissions=["netbox_sys_plugin.add_createvm"], + ), +] + #Items clusterProviderCredentialsItem = [ @@ -83,6 +93,7 @@ createVMItem =[ PluginMenuItem( link="plugins:netbox_sys_plugin:clustermanagement_list", link_text="Create Virtual Machine ", + buttons=create_vm_buttons ), ] diff --git a/netbox_sys_plugin/views.py b/netbox_sys_plugin/views.py index 90f82a2..fb28a5e 100644 --- a/netbox_sys_plugin/views.py +++ b/netbox_sys_plugin/views.py @@ -3,7 +3,8 @@ from django.shortcuts import render, redirect from netbox.views import generic from . import forms, models, tables, filtersets -from virtualization.models import ClusterType +from virtualization.models import ClusterType, Cluster, VirtualMachine +from django.http import JsonResponse @@ -131,10 +132,48 @@ class ClusterManagementView(generic.ObjectEditView): return render(request, 'netbox_sys_plugin/cluster_management.html', {'form': form}) def post(self, request, *args, **kwargs): - print('POST DATA: ',request.POST) - instance = self.get_object() - form = self.form(data=request.POST, instance=instance) - if form.is_valid(): - form.save() - return redirect('plugins:netbox_sys_plugin:clustermanagement_list') - return render(request, 'netbox_sys_plugin/cluster_management.html', {'form': form}) \ No newline at end of file + form = forms.ClusterManagementForm(data=request.POST) + if request.method == 'POST': + print("POST DATA:", request.POST) + + #cluster type + name = request.POST.get('name', '') + slug = request.POST.get('slug', '') + description = request.POST.get('description', '') + + #cluster + cluster_name = request.POST.get('clusters-0-name', '') + cluster_description = request.POST.get('clusters-0-description', '') + cluster_status = request.POST.get('clusters-0-status', '') + + #vm + vm_name = request.POST.get('vms_new-0-name', '') + vm_status = request.POST.get('vms_new-0-status', '') + vm_role = request.POST.get('vms_new-0-role', '') + vm_description = request.POST.get('vms_new-0-description', '') + + + try: + #cluster type + cluster_type = ClusterType(name=name, slug=slug, description=description) + cluster_type.full_clean() # Trigger validation + cluster_type.save() + print(f"ClusterType saved with ID: {cluster_type.pk}") + #cluster + cluster = Cluster(name=cluster_name,type=cluster_type,status=cluster_status,description=cluster_description) + cluster.full_clean() + cluster.save() + print(f"ClusterType saved with ID: {cluster_type.pk}") + print(f"Cluster saved with ID: {cluster.pk}") + #vm + vm = VirtualMachine(name=vm_name,status=vm_status, description=vm_description,cluster=cluster) + vm.full_clean() + vm.save() + print(f"ClusterType saved with ID: {cluster_type.pk}") + print(f"Cluster saved with ID: {cluster.pk}") + print(f"Virtual Machine saved with ID: {vm.pk}") + return render(request, 'netbox_sys_plugin/cluster_management.html', {'form': form}) + except Exception as e: + print(f"Error saving Something: {str(e)}") + return JsonResponse({"status": "error", "message": str(e)}) + -- GitLab From 523a551b2f0cce7f41a72aef53e13bfe035ed747 Mon Sep 17 00:00:00 2001 From: Frederico Sequeira <frederico.sequeira@ext.ec.europa.eu> Date: Wed, 27 Nov 2024 14:20:40 +0000 Subject: [PATCH 06/28] =?UTF-8?q?=F0=9F=91=94=20Add=20new=20fields=20-=20v?= =?UTF-8?q?m=20interface=20and=20services?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- netbox_sys_plugin/forms/createvm.py | 42 +++- netbox_sys_plugin/navigation.py | 4 +- ...cluster_management.html => create_vm.html} | 33 +-- netbox_sys_plugin/urls.py | 2 +- netbox_sys_plugin/views.py | 211 ++++++++++++++---- 5 files changed, 223 insertions(+), 69 deletions(-) rename netbox_sys_plugin/templates/netbox_sys_plugin/{cluster_management.html => create_vm.html} (68%) diff --git a/netbox_sys_plugin/forms/createvm.py b/netbox_sys_plugin/forms/createvm.py index 7c063c4..6d54cbd 100644 --- a/netbox_sys_plugin/forms/createvm.py +++ b/netbox_sys_plugin/forms/createvm.py @@ -1,7 +1,9 @@ from django import forms from django.forms import inlineformset_factory from netbox.forms import NetBoxModelForm -from virtualization.models import ClusterType, Cluster, VirtualMachine +from virtualization.models import ClusterType, Cluster, VirtualMachine, VMInterface +from dcim.models import Site, Platform +from ipam.models import IPAddress, Service class ClusterForm(NetBoxModelForm): """Form for Clusters.""" @@ -13,7 +15,20 @@ class VirtualMachineForm(NetBoxModelForm): """Form for Virtual Machines.""" class Meta: model = VirtualMachine - fields = ('name', 'cluster', 'status', 'role', 'description','tags') + fields = ('name', 'cluster', 'status', 'role','platform','site', 'description','tags') + +class VirtualMachineInterfaceForm(NetBoxModelForm): + """Form for Virtual Machine Interfaces.""" + class Meta: + model = VMInterface + fields = ('name','tags') + +class ServiceForm(NetBoxModelForm): + """Form for Virtual Machine Interfaces.""" + class Meta: + model = Service + fields = ('name','protocol','ports','tags') + # Inline formsets for managing relationships ClusterFormSet = inlineformset_factory( @@ -24,6 +39,13 @@ VirtualMachineFormSet = inlineformset_factory( Cluster, VirtualMachine, form=VirtualMachineForm, extra=1, can_delete=False ) +VirtualMachineInterfaceFormSet = inlineformset_factory( + VirtualMachine, VMInterface, form=VirtualMachineInterfaceForm, extra=1, can_delete=False +) + +ServiceFormSet = inlineformset_factory( + VirtualMachine, Service, form=ServiceForm, extra=3, can_delete=False +) # Combined form class ClusterManagementForm(NetBoxModelForm): """Combined form for managing ClusterType, Cluster, and VirtualMachine.""" @@ -43,11 +65,11 @@ class ClusterManagementForm(NetBoxModelForm): self.virtual_machine_formsets = [] empty_vm_formset = VirtualMachineFormSet(data=data, prefix='vms_new') self.virtual_machine_formsets.append(('new', empty_vm_formset)) - - def is_valid(self): - """Validate the main form only.""" - main_form_valid = super().is_valid() - print("Main form valid:", main_form_valid) - if not main_form_valid: - print("Main form errors:", self.errors) - return main_form_valid + #Virtual Machine Interfaces + self.virtual_machine_interface_formsets = [] + empty_vmi_formset = VirtualMachineInterfaceFormSet(data=data, prefix='vmis_new') + self.virtual_machine_interface_formsets.append(('new', empty_vmi_formset)) + #Service + self.service_formsets = [] + empty_service_formset = ServiceFormSet(data=data, prefix='service_new') + self.service_formsets.append(('new', empty_service_formset)) diff --git a/netbox_sys_plugin/navigation.py b/netbox_sys_plugin/navigation.py index 69afd5a..a418b4a 100644 --- a/netbox_sys_plugin/navigation.py +++ b/netbox_sys_plugin/navigation.py @@ -49,7 +49,7 @@ vm_machine_type_buttons = [ create_vm_buttons = [ PluginMenuButton( - link="plugins:netbox_sys_plugin:clustermanagement_list", + link="plugins:netbox_sys_plugin:creatvm_add", title="Add", icon_class="mdi mdi-plus-thick", color=ButtonColorChoices.GREEN, @@ -91,7 +91,7 @@ operItem =[ createVMItem =[ PluginMenuItem( - link="plugins:netbox_sys_plugin:clustermanagement_list", + link="plugins:netbox_sys_plugin:creatvm_add", link_text="Create Virtual Machine ", buttons=create_vm_buttons ), diff --git a/netbox_sys_plugin/templates/netbox_sys_plugin/cluster_management.html b/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html similarity index 68% rename from netbox_sys_plugin/templates/netbox_sys_plugin/cluster_management.html rename to netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html index 115e891..91a7e60 100644 --- a/netbox_sys_plugin/templates/netbox_sys_plugin/cluster_management.html +++ b/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html @@ -28,30 +28,39 @@ SYS - Virtual Machine <h5>Provider Type</h5> {% csrf_token %} {{ form.as_p }} - <h5>Provider Credentials</h5> - {{ form.cluster_formset.management_form }} {% for cluster_form in form.cluster_formset %} <div class="form-group" class="field-group mb-5"> {{ cluster_form.as_p }} - {% for error in cluster_form.errors %} - <p class="text-danger">{{ error }}</p> - {% endfor %} </div> {% endfor %} - <h5>Virtual Machine Info</h5> - {% for cluster_id, vm_formset in form.virtual_machine_formsets %} - {{ vm_formset.management_form }} + <h5>Virtual Machine</h5> + {% for vm_formset in form.virtual_machine_formsets %} {% for vm_form in vm_formset %} <div class="form-group" class="field-group mb-5"> {{ vm_form.as_p }} - {% for error in vm_form.errors %} - <p class="text-danger">{{ error }}</p> - {% endfor %} </div> {% endfor %} {% endfor %} + <h5>Network Name</h5> + {% for vmi_formset in form.virtual_machine_interface_formsets %} + {% for vmi_form in vmi_formset %} + <div class="form-group" class="field-group mb-5"> + {{ vmi_form.as_p }} + </div> + {% endfor %} + {% endfor %} + <h5>Dependencies</h5> + <div class="row"> + {% for service_formset in form.service_formsets %} + {% for service_form in service_formset %} + <div class="form-group" class="field-group mb-5"> + {{ service_form.as_p }} + </div> + {% endfor %} + {% endfor %} + </div> <div class="text-end my-3"> {% block buttons %} <button type="submit" class="btn btn-primary">Save</button> @@ -63,5 +72,3 @@ SYS - Virtual Machine {% endblock %} {% endblock content-wrapper %} -{# User messages #} -{% include 'inc/messages.html' %} \ No newline at end of file diff --git a/netbox_sys_plugin/urls.py b/netbox_sys_plugin/urls.py index 4421c8b..c3ece9c 100644 --- a/netbox_sys_plugin/urls.py +++ b/netbox_sys_plugin/urls.py @@ -41,7 +41,7 @@ urlpatterns = ( #CreateVM - path('cluster-management/', ClusterManagementView.as_view(), name='clustermanagement_list'), + path('create-vm/', ClusterManagementView.as_view(), name='creatvm_add'), ) diff --git a/netbox_sys_plugin/views.py b/netbox_sys_plugin/views.py index fb28a5e..0948049 100644 --- a/netbox_sys_plugin/views.py +++ b/netbox_sys_plugin/views.py @@ -3,8 +3,12 @@ from django.shortcuts import render, redirect from netbox.views import generic from . import forms, models, tables, filtersets -from virtualization.models import ClusterType, Cluster, VirtualMachine -from django.http import JsonResponse +from virtualization.models import ClusterType, Cluster, VirtualMachine,VMInterface +from dcim.models import DeviceRole,Site, Platform +from ipam.models import Service +from django.db import transaction +from django.contrib import messages +from taggit.models import Tag @@ -121,6 +125,128 @@ class VmTypeEditView(generic.ObjectEditView): queryset = models.VirtualMachineType.objects.all() form = forms.VmTypeForm + +#Create VM +# Helper functions for object creation +def create_cluster_type(data): + """Creates and saves a ClusterType object.""" + cluster_type = ClusterType( + name=data.get('name', ''), + slug=data.get('slug', ''), + description=data.get('description', '') + #tag=tag, + ) + cluster_type.full_clean() # Validate the data + cluster_type.save() # Save to the database + return cluster_type + + +def create_cluster(data, cluster_type): + """Creates and saves a Cluster object linked to a ClusterType.""" + site_id = data.get('vms_new-0-site', '') + try: + site=Site.objects.get(pk=site_id) + except Site.DoesNotExist: + raise ValueError(f"Invalid Site ID: {site_id}") + + cluster = Cluster( + name=data.get('clusters-0-name', ''), + type=cluster_type, # Link to the saved ClusterType + status=data.get('clusters-0-status', ''), + site=site, + description=data.get('clusters-0-description', '') + ) + cluster.full_clean() # Validate the data + cluster.save() # Save to the database + return cluster + + +def create_virtual_machine(data, cluster): + """Creates and saves a VirtualMachine object linked to a Cluster.""" + role_id = data.get('vms_new-0-role', '') + try: + role=DeviceRole.objects.get(pk=role_id) + except DeviceRole.DoesNotExist: + raise ValueError(f"Invalid Device Role ID: {role_id}") + + site_id = data.get('vms_new-0-site', '') + try: + site=Site.objects.get(pk=site_id) + except Site.DoesNotExist: + raise ValueError(f"Invalid Site ID: {site_id}") + + platform_id = data.get('vms_new-0-platform', '') + try: + platform=Platform.objects.get(pk=platform_id) + except Platform.DoesNotExist: + raise ValueError(f"Invalid Platform ID: {platform_id}") + + vm = VirtualMachine( + name=data.get('vms_new-0-name', ''), + status=data.get('vms_new-0-status', ''), + role=role, + site=site, + platform=platform, + description=data.get('vms_new-0-description', ''), + cluster=cluster # Link to the saved Cluster + ) + vm.full_clean() # Validate the data + vm.save() # Save to the database + return vm + +def create_vm_interface(data, vm): + """Creates and saves a Vm Interface object linked to a Virtual Machine.""" + vmi = VMInterface( + name=data.get('vmis_new-0-name', ''), + virtual_machine=vm # Link to the saved Virtual Machine + ) + vmi.full_clean() # Validate the data + vmi.save() # Save to the database + return vmi + +def create_ntp_services(data, vm): + """Creates and saves a Vm Interface object linked to a Virtual Machine.""" + ntp_s = Service( + name=data.get('service_new-0-name', ''), + protocol=data.get('service_new-0-protocol', ''), + ports=data.get('service_new-0-ports', ''), + virtual_machine=vm # Link to the saved Virtual Machine + ) + + ntp_s.full_clean() # Validate the data + ntp_s.save() # Save to the database + return ntp_s + +def create_dns_services(data, vm): + """Creates and saves a Vm Interface object linked to a Virtual Machine.""" + dns_s = Service( + name=data.get('service_new-1-name', ''), + protocol=data.get('service_new-1-protocol', ''), + ports=data.get('service_new-1-ports', ''), + virtual_machine=vm # Link to the saved Virtual Machine + ) + + dns_s.full_clean() # Validate the data + dns_s.save() # Save to the database + return dns_s + + +def create_syslog_services(data, vm): + """Creates and saves a Vm Interface object linked to a Virtual Machine.""" + syslog_s = Service( + name=data.get('service_new-2-name', ''), + protocol=data.get('service_new-2-protocol', ''), + ports=data.get('service_new-2-ports', ''), + virtual_machine=vm # Link to the saved Virtual Machine + ) + syslog_s.full_clean() # Validate the data + syslog_s.save() # Save to the database + return syslog_s + + + + +# Main view for handling ClusterManagement class ClusterManagementView(generic.ObjectEditView): """View for managing ClusterType, Cluster, and VirtualMachine in one form.""" queryset = ClusterType.objects.all() @@ -129,51 +255,50 @@ class ClusterManagementView(generic.ObjectEditView): def get(self, request, *args, **kwargs): instance = self.get_object() form = self.form(instance=instance) - return render(request, 'netbox_sys_plugin/cluster_management.html', {'form': form}) + return render(request, 'netbox_sys_plugin/create_vm.html', {'form': form}) def post(self, request, *args, **kwargs): - form = forms.ClusterManagementForm(data=request.POST) + """Handles POST requests for objects.""" + form = self.form(data=request.POST) + if request.method == 'POST': print("POST DATA:", request.POST) - #cluster type - name = request.POST.get('name', '') - slug = request.POST.get('slug', '') - description = request.POST.get('description', '') - - #cluster - cluster_name = request.POST.get('clusters-0-name', '') - cluster_description = request.POST.get('clusters-0-description', '') - cluster_status = request.POST.get('clusters-0-status', '') - - #vm - vm_name = request.POST.get('vms_new-0-name', '') - vm_status = request.POST.get('vms_new-0-status', '') - vm_role = request.POST.get('vms_new-0-role', '') - vm_description = request.POST.get('vms_new-0-description', '') - - try: - #cluster type - cluster_type = ClusterType(name=name, slug=slug, description=description) - cluster_type.full_clean() # Trigger validation - cluster_type.save() - print(f"ClusterType saved with ID: {cluster_type.pk}") - #cluster - cluster = Cluster(name=cluster_name,type=cluster_type,status=cluster_status,description=cluster_description) - cluster.full_clean() - cluster.save() - print(f"ClusterType saved with ID: {cluster_type.pk}") - print(f"Cluster saved with ID: {cluster.pk}") - #vm - vm = VirtualMachine(name=vm_name,status=vm_status, description=vm_description,cluster=cluster) - vm.full_clean() - vm.save() - print(f"ClusterType saved with ID: {cluster_type.pk}") - print(f"Cluster saved with ID: {cluster.pk}") - print(f"Virtual Machine saved with ID: {vm.pk}") - return render(request, 'netbox_sys_plugin/cluster_management.html', {'form': form}) + # Use a database transaction to ensure all-or-nothing behavior + with transaction.atomic(): + # Create the ClusterType + cluster_type = create_cluster_type(request.POST) + print(f"ClusterType saved with ID: {cluster_type.pk}") + + # Create the Cluster + cluster = create_cluster(request.POST, cluster_type) + print(f"Cluster saved with ID: {cluster.pk}") + + # Create the Virtual Machine + vm = create_virtual_machine(request.POST, cluster) + print(f"Virtual Machine saved with ID: {vm.pk}") + + #Create the Vm Interface + vmi = create_vm_interface(request.POST, vm) + print(f"Virtual Machine Interface saved with ID: {vmi.pk}") + + #Create the Services + ntps = create_ntp_services(request.POST, vm) + print(f"NTP Server service saved with ID: {ntps.pk}") + dnsss = create_dns_services(request.POST, vm) + print(f"DNS Server service saved with ID: {dnsss.pk}") + syslogs = create_syslog_services(request.POST, vm) + print(f"SYSLOG Server service saved with ID: {syslogs.pk}") + + # On successful save, provide feedback to the user + messages.success(request,'ClusterType, Cluster, and VirtualMachine created successfully!') + return render(request,'netbox_sys_plugin/create_vm.html',{'form': form,}) + except Exception as e: - print(f"Error saving Something: {str(e)}") - return JsonResponse({"status": "error", "message": str(e)}) - + # Log the error and display feedback + print(f"Error saving objects: {str(e)}") + messages.error(request, f"Error: {str(e)}. Please correct the form and try again.") + return render(request,'netbox_sys_plugin/create_vm.html',{'form': form,}) + + return render(request,'netbox_sys_plugin/create_vm.html',{'form': form}) -- GitLab From 916d20ea45ff4552369d86ab3ac6de4d318b735d Mon Sep 17 00:00:00 2001 From: Frederico Sequeira <frederico.sequeira@ext.ec.europa.eu> Date: Wed, 27 Nov 2024 15:24:32 +0000 Subject: [PATCH 07/28] =?UTF-8?q?=F0=9F=90=9B=20Fix=20issue=20in=20service?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- netbox_sys_plugin/forms/createvm.py | 4 +-- netbox_sys_plugin/views.py | 42 ++++++++++++++++++++++------- 2 files changed, 34 insertions(+), 12 deletions(-) diff --git a/netbox_sys_plugin/forms/createvm.py b/netbox_sys_plugin/forms/createvm.py index 6d54cbd..d75a85e 100644 --- a/netbox_sys_plugin/forms/createvm.py +++ b/netbox_sys_plugin/forms/createvm.py @@ -9,13 +9,13 @@ class ClusterForm(NetBoxModelForm): """Form for Clusters.""" class Meta: model = Cluster - fields = ('name', 'type', 'description','status','tags') + fields = ('name', 'type', 'description','status','site','tags') class VirtualMachineForm(NetBoxModelForm): """Form for Virtual Machines.""" class Meta: model = VirtualMachine - fields = ('name', 'cluster', 'status', 'role','platform','site', 'description','tags') + fields = ('name', 'cluster', 'status', 'role','platform', 'description','tags') class VirtualMachineInterfaceForm(NetBoxModelForm): """Form for Virtual Machine Interfaces.""" diff --git a/netbox_sys_plugin/views.py b/netbox_sys_plugin/views.py index 0948049..aac0fc6 100644 --- a/netbox_sys_plugin/views.py +++ b/netbox_sys_plugin/views.py @@ -127,7 +127,22 @@ class VmTypeEditView(generic.ObjectEditView): #Create VM +# Main view for handling ClusterManagement +class ClusterManagementView(generic.ObjectEditView): + """View for managing ClusterType, Cluster, and VirtualMachine in one form.""" + queryset = ClusterType.objects.all() + form = forms.ClusterManagementForm + # Helper functions for object creation +def parse_ports(ports_field): + """Parse the value for ports""" + try: + ports=[int(port.strip())for port in ports_field.split(',') if port.strip().isdigit()] + return ports + except Exception as e: + raise ValueError(f"Invalid ports value:{ports_field}. Error: {str(e)}") + + def create_cluster_type(data): """Creates and saves a ClusterType object.""" cluster_type = ClusterType( @@ -143,7 +158,7 @@ def create_cluster_type(data): def create_cluster(data, cluster_type): """Creates and saves a Cluster object linked to a ClusterType.""" - site_id = data.get('vms_new-0-site', '') + site_id = data.get('clusters-0-site', '') try: site=Site.objects.get(pk=site_id) except Site.DoesNotExist: @@ -169,7 +184,7 @@ def create_virtual_machine(data, cluster): except DeviceRole.DoesNotExist: raise ValueError(f"Invalid Device Role ID: {role_id}") - site_id = data.get('vms_new-0-site', '') + site_id = data.get('clusters-0-site', '') try: site=Site.objects.get(pk=site_id) except Site.DoesNotExist: @@ -206,11 +221,15 @@ def create_vm_interface(data, vm): def create_ntp_services(data, vm): """Creates and saves a Vm Interface object linked to a Virtual Machine.""" + ports_field=data.get('service_new-0-ports', '') + ports=parse_ports(ports_field) ntp_s = Service( name=data.get('service_new-0-name', ''), protocol=data.get('service_new-0-protocol', ''), - ports=data.get('service_new-0-ports', ''), + ports=ports, + description='NTP Server', virtual_machine=vm # Link to the saved Virtual Machine + ) ntp_s.full_clean() # Validate the data @@ -219,11 +238,15 @@ def create_ntp_services(data, vm): def create_dns_services(data, vm): """Creates and saves a Vm Interface object linked to a Virtual Machine.""" + ports_field=data.get('service_new-1-ports', '') + ports=parse_ports(ports_field) dns_s = Service( name=data.get('service_new-1-name', ''), protocol=data.get('service_new-1-protocol', ''), - ports=data.get('service_new-1-ports', ''), + ports=ports, + description='DNS Server', virtual_machine=vm # Link to the saved Virtual Machine + ) dns_s.full_clean() # Validate the data @@ -233,10 +256,13 @@ def create_dns_services(data, vm): def create_syslog_services(data, vm): """Creates and saves a Vm Interface object linked to a Virtual Machine.""" + ports_field=data.get('service_new-2-ports', '') + ports=parse_ports(ports_field) syslog_s = Service( name=data.get('service_new-2-name', ''), protocol=data.get('service_new-2-protocol', ''), - ports=data.get('service_new-2-ports', ''), + ports=ports, + description='SYSLOG Server', virtual_machine=vm # Link to the saved Virtual Machine ) syslog_s.full_clean() # Validate the data @@ -246,11 +272,7 @@ def create_syslog_services(data, vm): -# Main view for handling ClusterManagement -class ClusterManagementView(generic.ObjectEditView): - """View for managing ClusterType, Cluster, and VirtualMachine in one form.""" - queryset = ClusterType.objects.all() - form = forms.ClusterManagementForm + def get(self, request, *args, **kwargs): instance = self.get_object() -- GitLab From 349110c1de36a052aead629c33adda818bdb4008 Mon Sep 17 00:00:00 2001 From: Frederico Sequeira <frederico.sequeira@ext.ec.europa.eu> Date: Wed, 27 Nov 2024 16:15:47 +0000 Subject: [PATCH 08/28] =?UTF-8?q?=F0=9F=92=84=20Add=20UI=20seperation=20fo?= =?UTF-8?q?r=20services?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- netbox_sys_plugin/forms/createvm.py | 25 ++++++++++++--- .../netbox_sys_plugin/create_vm.html | 27 ++++++++++++++-- netbox_sys_plugin/views.py | 32 +++++++++---------- 3 files changed, 59 insertions(+), 25 deletions(-) diff --git a/netbox_sys_plugin/forms/createvm.py b/netbox_sys_plugin/forms/createvm.py index d75a85e..97aa082 100644 --- a/netbox_sys_plugin/forms/createvm.py +++ b/netbox_sys_plugin/forms/createvm.py @@ -43,8 +43,14 @@ VirtualMachineInterfaceFormSet = inlineformset_factory( VirtualMachine, VMInterface, form=VirtualMachineInterfaceForm, extra=1, can_delete=False ) -ServiceFormSet = inlineformset_factory( - VirtualMachine, Service, form=ServiceForm, extra=3, can_delete=False +ServiceNtpFormSet = inlineformset_factory( + VirtualMachine, Service, form=ServiceForm, extra=1, can_delete=False +) +ServiceDnsFormSet = inlineformset_factory( + VirtualMachine, Service, form=ServiceForm, extra=1, can_delete=False +) +ServiceSyslogFormSet = inlineformset_factory( + VirtualMachine, Service, form=ServiceForm, extra=1, can_delete=False ) # Combined form class ClusterManagementForm(NetBoxModelForm): @@ -70,6 +76,15 @@ class ClusterManagementForm(NetBoxModelForm): empty_vmi_formset = VirtualMachineInterfaceFormSet(data=data, prefix='vmis_new') self.virtual_machine_interface_formsets.append(('new', empty_vmi_formset)) #Service - self.service_formsets = [] - empty_service_formset = ServiceFormSet(data=data, prefix='service_new') - self.service_formsets.append(('new', empty_service_formset)) + self.service_ntp_formsets = [] + empty_ntp_service_formset = ServiceNtpFormSet(data=data, prefix='service_ntp') + self.service_ntp_formsets.append(('new', empty_ntp_service_formset)) + #Service + self.service_dns_formsets = [] + empty_dns_service_formset = ServiceDnsFormSet(data=data, prefix='service_dns') + self.service_dns_formsets.append(('new', empty_dns_service_formset)) + #Service + self.service_syslog_formsets = [] + empty_syslog_service_formset = ServiceSyslogFormSet(data=data, prefix='service_syslog') + self.service_syslog_formsets.append(('new', empty_syslog_service_formset)) + diff --git a/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html b/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html index 91a7e60..82c25e7 100644 --- a/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html +++ b/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html @@ -53,13 +53,34 @@ SYS - Virtual Machine {% endfor %} <h5>Dependencies</h5> <div class="row"> - {% for service_formset in form.service_formsets %} - {% for service_form in service_formset %} - <div class="form-group" class="field-group mb-5"> + <h6>NTP Server</h6> + {% for service_ntp_formset in form.service_ntp_formsets %} + <div class="form-group" class="field-group mb-5"> + {% for service_form in service_ntp_formset %} + <div class="form-group" class="field-group mb-5"> + {{ service_form.as_p }} + </div> + {% endfor %} + {% endfor %} + <h6>DNS Server</h6> + {% for service_dns_formset in form.service_dns_formsets %} + <div class="form-group" class="field-group mb-5"> + {% for service_form in service_dns_formset %} + <div class="form-group" class="field-group mb-5"> {{ service_form.as_p }} </div> {% endfor %} {% endfor %} + <h6>SYSLOG Server</h6> + {% for service_syslog_formset in form.service_syslog_formsets %} + <div class="form-group" class="field-group mb-5"> + {% for service_form in service_syslog_formset %} + <div class="form-group" class="field-group mb-5"> + {{ service_form.as_p }} + </div> + {% endfor %} + {% endfor %} + </div> <div class="text-end my-3"> {% block buttons %} diff --git a/netbox_sys_plugin/views.py b/netbox_sys_plugin/views.py index aac0fc6..9ca68fa 100644 --- a/netbox_sys_plugin/views.py +++ b/netbox_sys_plugin/views.py @@ -127,11 +127,6 @@ class VmTypeEditView(generic.ObjectEditView): #Create VM -# Main view for handling ClusterManagement -class ClusterManagementView(generic.ObjectEditView): - """View for managing ClusterType, Cluster, and VirtualMachine in one form.""" - queryset = ClusterType.objects.all() - form = forms.ClusterManagementForm # Helper functions for object creation def parse_ports(ports_field): @@ -221,11 +216,11 @@ def create_vm_interface(data, vm): def create_ntp_services(data, vm): """Creates and saves a Vm Interface object linked to a Virtual Machine.""" - ports_field=data.get('service_new-0-ports', '') + ports_field=data.get('service_syslog-0-ports', '') ports=parse_ports(ports_field) ntp_s = Service( - name=data.get('service_new-0-name', ''), - protocol=data.get('service_new-0-protocol', ''), + name=data.get('service_ntp-0-name', ''), + protocol=data.get('service_ntp-0-protocol', ''), ports=ports, description='NTP Server', virtual_machine=vm # Link to the saved Virtual Machine @@ -238,11 +233,11 @@ def create_ntp_services(data, vm): def create_dns_services(data, vm): """Creates and saves a Vm Interface object linked to a Virtual Machine.""" - ports_field=data.get('service_new-1-ports', '') + ports_field=data.get('service_syslog-0-ports', '') ports=parse_ports(ports_field) dns_s = Service( - name=data.get('service_new-1-name', ''), - protocol=data.get('service_new-1-protocol', ''), + name=data.get('service_dns-0-name', ''), + protocol=data.get('service_dns-0-protocol', ''), ports=ports, description='DNS Server', virtual_machine=vm # Link to the saved Virtual Machine @@ -256,11 +251,11 @@ def create_dns_services(data, vm): def create_syslog_services(data, vm): """Creates and saves a Vm Interface object linked to a Virtual Machine.""" - ports_field=data.get('service_new-2-ports', '') + ports_field=data.get('service_syslog-0-ports', '') ports=parse_ports(ports_field) syslog_s = Service( - name=data.get('service_new-2-name', ''), - protocol=data.get('service_new-2-protocol', ''), + name=data.get('service_syslog-0-name', ''), + protocol=data.get('service_syslog-0-protocol', ''), ports=ports, description='SYSLOG Server', virtual_machine=vm # Link to the saved Virtual Machine @@ -270,9 +265,12 @@ def create_syslog_services(data, vm): return syslog_s - - - +#Create VM +# Main view for handling ClusterManagement +class ClusterManagementView(generic.ObjectEditView): + """View for managing ClusterType, Cluster, and VirtualMachine in one form.""" + queryset = ClusterType.objects.all() + form = forms.ClusterManagementForm def get(self, request, *args, **kwargs): instance = self.get_object() -- GitLab From baa2231d564bc36a683b2c19302e3836126987d2 Mon Sep 17 00:00:00 2001 From: Frederico Sequeira <frederico.sequeira@ext.ec.europa.eu> Date: Wed, 27 Nov 2024 16:45:18 +0000 Subject: [PATCH 09/28] =?UTF-8?q?=F0=9F=9A=9A=20Rename=20main=20class?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- netbox_sys_plugin/forms/machine.py | 4 ++++ netbox_sys_plugin/urls.py | 4 ++-- netbox_sys_plugin/views.py | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/netbox_sys_plugin/forms/machine.py b/netbox_sys_plugin/forms/machine.py index 7dbb437..4396562 100644 --- a/netbox_sys_plugin/forms/machine.py +++ b/netbox_sys_plugin/forms/machine.py @@ -22,6 +22,10 @@ class VmAssignedVmTypeForm(NetBoxModelForm): queryset=VirtualMachine.objects.all(), required=True, label="Virtual Machine" ) + virtual_machine_type_assignment_desc = forms.CharField( + max_length=50, min_length=1, required=False, label="Assignment Description" + ) + def __init__(self, *args, **kwargs): # Initialize helper selectors diff --git a/netbox_sys_plugin/urls.py b/netbox_sys_plugin/urls.py index c3ece9c..299078a 100644 --- a/netbox_sys_plugin/urls.py +++ b/netbox_sys_plugin/urls.py @@ -3,7 +3,7 @@ from django.urls import path from netbox.views.generic import ObjectChangeLogView, ObjectJournalView from netbox_sys_plugin import models, views -from .views import ClusterManagementView +from .views import CreateVmView urlpatterns = ( #maintenance @@ -41,7 +41,7 @@ urlpatterns = ( #CreateVM - path('create-vm/', ClusterManagementView.as_view(), name='creatvm_add'), + path('create-vm/', CreateVmView.as_view(), name='creatvm_add'), ) diff --git a/netbox_sys_plugin/views.py b/netbox_sys_plugin/views.py index 9ca68fa..85ecf0d 100644 --- a/netbox_sys_plugin/views.py +++ b/netbox_sys_plugin/views.py @@ -267,7 +267,7 @@ def create_syslog_services(data, vm): #Create VM # Main view for handling ClusterManagement -class ClusterManagementView(generic.ObjectEditView): +class CreateVmView(generic.ObjectEditView): """View for managing ClusterType, Cluster, and VirtualMachine in one form.""" queryset = ClusterType.objects.all() form = forms.ClusterManagementForm -- GitLab From b729ebf10420eb20cbb9b3f36b6a1eb2cc5218e6 Mon Sep 17 00:00:00 2001 From: Frederico Sequeira <frederico.sequeira@ext.ec.europa.eu> Date: Wed, 27 Nov 2024 17:57:12 +0000 Subject: [PATCH 10/28] =?UTF-8?q?=F0=9F=9A=A7=20Add=20IP=20Addresses?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- netbox_sys_plugin/forms/createvm.py | 20 ++++++++++++++-- .../netbox_sys_plugin/create_vm.html | 10 ++++++++ netbox_sys_plugin/views.py | 23 +++++++++++++++++-- 3 files changed, 49 insertions(+), 4 deletions(-) diff --git a/netbox_sys_plugin/forms/createvm.py b/netbox_sys_plugin/forms/createvm.py index 97aa082..1b1f11b 100644 --- a/netbox_sys_plugin/forms/createvm.py +++ b/netbox_sys_plugin/forms/createvm.py @@ -1,5 +1,5 @@ from django import forms -from django.forms import inlineformset_factory +from django.forms import inlineformset_factory, BaseModelFormSet from netbox.forms import NetBoxModelForm from virtualization.models import ClusterType, Cluster, VirtualMachine, VMInterface from dcim.models import Site, Platform @@ -29,6 +29,12 @@ class ServiceForm(NetBoxModelForm): model = Service fields = ('name','protocol','ports','tags') +class IPAddressForm(NetBoxModelForm): + """Form for IP Addresses.""" + class Meta: + model = IPAddress + fields = ('address', 'status', 'role', 'tags') + # Inline formsets for managing relationships ClusterFormSet = inlineformset_factory( @@ -52,8 +58,14 @@ ServiceDnsFormSet = inlineformset_factory( ServiceSyslogFormSet = inlineformset_factory( VirtualMachine, Service, form=ServiceForm, extra=1, can_delete=False ) + +IPAddressFormSet = forms.modelformset_factory( + IPAddress, form=IPAddressForm, formset=IPAddressForm, extra=1, can_delete=False +) + + # Combined form -class ClusterManagementForm(NetBoxModelForm): +class CreateVmForm(NetBoxModelForm): """Combined form for managing ClusterType, Cluster, and VirtualMachine.""" class Meta: @@ -87,4 +99,8 @@ class ClusterManagementForm(NetBoxModelForm): self.service_syslog_formsets = [] empty_syslog_service_formset = ServiceSyslogFormSet(data=data, prefix='service_syslog') self.service_syslog_formsets.append(('new', empty_syslog_service_formset)) + # IP Address + self.ip_formsets = [] + empty_ip_formset = IPAddressFormSet(data=data, prefix='ip_new') + self.ip_formsets.append(('new', empty_ip_formset)) diff --git a/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html b/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html index 82c25e7..3ce0938 100644 --- a/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html +++ b/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html @@ -80,6 +80,16 @@ SYS - Virtual Machine </div> {% endfor %} {% endfor %} + + <h6>IP Address</h6> + {% for ip_formset in form.ip_formsets %} + <div class="form-group" class="field-group mb-5"> + {% for ip_form in ip_formset %} + <div class="form-group" class="field-group mb-5"> + {{ ip_form.as_p }} + </div> + {% endfor %} + {% endfor %} </div> <div class="text-end my-3"> diff --git a/netbox_sys_plugin/views.py b/netbox_sys_plugin/views.py index 85ecf0d..de10bb9 100644 --- a/netbox_sys_plugin/views.py +++ b/netbox_sys_plugin/views.py @@ -5,7 +5,7 @@ from netbox.views import generic from . import forms, models, tables, filtersets from virtualization.models import ClusterType, Cluster, VirtualMachine,VMInterface from dcim.models import DeviceRole,Site, Platform -from ipam.models import Service +from ipam.models import Service, IPAddress from django.db import transaction from django.contrib import messages from taggit.models import Tag @@ -265,12 +265,27 @@ def create_syslog_services(data, vm): return syslog_s +def create_ip_address(data, vm_interface): + """ + Creates and saves an IPAddress object linked to a specific VMInterface. + """ + ip_address = IPAddress( + address=data.get('ip_new-address', ''), + status=data.get('ip_new-status', ''), + role=data.get('ip_new-role', ''), + assigned_object=vm_interface # Link to the saved VMInterface + ) + ip_address.full_clean() # Validate the data + ip_address.save() # Save to the database + return ip_address + + #Create VM # Main view for handling ClusterManagement class CreateVmView(generic.ObjectEditView): """View for managing ClusterType, Cluster, and VirtualMachine in one form.""" queryset = ClusterType.objects.all() - form = forms.ClusterManagementForm + form = forms.CreateVmForm def get(self, request, *args, **kwargs): instance = self.get_object() @@ -311,6 +326,10 @@ class CreateVmView(generic.ObjectEditView): syslogs = create_syslog_services(request.POST, vm) print(f"SYSLOG Server service saved with ID: {syslogs.pk}") + # Create the IP Address + ip_address = create_ip_address(request.POST, vmi) + print(f"IP Address saved with ID: {ip_address.pk}") + # On successful save, provide feedback to the user messages.success(request,'ClusterType, Cluster, and VirtualMachine created successfully!') return render(request,'netbox_sys_plugin/create_vm.html',{'form': form,}) -- GitLab From 9334af1bd7785977bf4cd338c4822bbd7fe18000 Mon Sep 17 00:00:00 2001 From: Frederico Sequeira <frederico.sequeira@ext.ec.europa.eu> Date: Thu, 28 Nov 2024 12:23:44 +0000 Subject: [PATCH 11/28] =?UTF-8?q?=F0=9F=92=84=20Fix=20UI=20vanishing=20ele?= =?UTF-8?q?ments?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- netbox_sys_plugin/forms/createvm.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/netbox_sys_plugin/forms/createvm.py b/netbox_sys_plugin/forms/createvm.py index 1b1f11b..5f26f41 100644 --- a/netbox_sys_plugin/forms/createvm.py +++ b/netbox_sys_plugin/forms/createvm.py @@ -78,7 +78,9 @@ class CreateVmForm(NetBoxModelForm): # Initialize formsets #Cluster + Cluster type - self.cluster_formset = ClusterFormSet(data=data, prefix='clusters') + self.cluster_formsets = [] + empty_cluster_formset = ClusterFormSet(data=data, prefix='clusters') + self.cluster_formsets.append(('new', empty_cluster_formset)) #Virtual Machine self.virtual_machine_formsets = [] empty_vm_formset = VirtualMachineFormSet(data=data, prefix='vms_new') -- GitLab From 976e34b84e848416914cdecbc1154e34f3ed89bc Mon Sep 17 00:00:00 2001 From: Frederico Sequeira <frederico.sequeira@ext.ec.europa.eu> Date: Thu, 28 Nov 2024 14:28:47 +0000 Subject: [PATCH 12/28] =?UTF-8?q?=F0=9F=92=84=20Add=20Ui=20tab=20to=20mana?= =?UTF-8?q?ge=20providers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../netbox_sys_plugin/create_vm.html | 45 +++++++++++++++---- 1 file changed, 36 insertions(+), 9 deletions(-) diff --git a/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html b/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html index 3ce0938..88836d0 100644 --- a/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html +++ b/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html @@ -25,16 +25,43 @@ SYS - Virtual Machine <div class="tab-pane show active" id="edit-form" role="tabpanel" aria-labelledby="object-list-tab"> {% block content %} <form method="post" enctype="multipart/form-data" class="form-object-edit mt-5"> - <h5>Provider Type</h5> - {% csrf_token %} - {{ form.as_p }} - <h5>Provider Credentials</h5> - {% for cluster_form in form.cluster_formset %} - <div class="form-group" class="field-group mb-5"> - {{ cluster_form.as_p }} + <div class="field-group my-5"> + <div class="row mb-2"> </div> - {% endfor %} - + <div class="row mb-2"> + <ul class="nav nav-pills" role="tablist"> + <li role="presentation" class="nav-item"> + <button role="tab" type="button" id="device_tab" data-bs-toggle="tab" aria-controls="device" data-bs-target="#device" class="nav-link {% if not form.initial.vminterface and not form.initial.fhrpgroup %}active{% endif %}"> + New Provider + </button> + </li> + <li role="presentation" class="nav-item"> + <button role="tab" type="button" id="vm_tab" data-bs-toggle="tab" aria-controls="vm" data-bs-target="#vm" class="nav-link {% if form.initial.vminterface %}active{% endif %}"> + Existing Provider + </button> + </li> + </ul> + </div> + <div class="tab-content p-0 border-0"> + <div class="tab-pane" id="device" role="tabpanel" aria-labeled-by="device_tab"> + <h5>Provider Type</h5> + {% csrf_token %} + {{ form.as_p }} + <h5>Provider Credentials</h5> + {% for cluster_formset in form.cluster_formsets %} + {% for vl_form in cluster_formset %} + <div class="form-group" class="field-group mb-5"> + {{ vl_form.as_p }} + </div> + {% endfor %} + {% endfor %} + </div> + <div class="tab-pane" id="vm" role="tabpanel" aria-labeled-by="vm_tab"> + <p>Tab2</p> + </div> + </div> + </div> + <h5>Virtual Machine</h5> {% for vm_formset in form.virtual_machine_formsets %} {% for vm_form in vm_formset %} -- GitLab From f3bd19dcdd7a39f35bedfd7e6d26f1cd56bcb04a Mon Sep 17 00:00:00 2001 From: Frederico Sequeira <frederico.sequeira@ext.ec.europa.eu> Date: Thu, 28 Nov 2024 15:52:50 +0000 Subject: [PATCH 13/28] =?UTF-8?q?=F0=9F=92=84=20Add=20elements=20to=20the?= =?UTF-8?q?=20specific=20tabs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- netbox_sys_plugin/forms/createvm.py | 50 ++++++++++++++++--- .../netbox_sys_plugin/create_vm.html | 24 +++++++-- 2 files changed, 62 insertions(+), 12 deletions(-) diff --git a/netbox_sys_plugin/forms/createvm.py b/netbox_sys_plugin/forms/createvm.py index 5f26f41..68c2e0b 100644 --- a/netbox_sys_plugin/forms/createvm.py +++ b/netbox_sys_plugin/forms/createvm.py @@ -2,8 +2,8 @@ from django import forms from django.forms import inlineformset_factory, BaseModelFormSet from netbox.forms import NetBoxModelForm from virtualization.models import ClusterType, Cluster, VirtualMachine, VMInterface -from dcim.models import Site, Platform from ipam.models import IPAddress, Service +from utilities.forms.fields import DynamicModelChoiceField class ClusterForm(NetBoxModelForm): """Form for Clusters.""" @@ -11,6 +11,24 @@ class ClusterForm(NetBoxModelForm): model = Cluster fields = ('name', 'type', 'description','status','site','tags') +class ClusterFormList(NetBoxModelForm): + """Form for Cluster List.""" + provider = DynamicModelChoiceField( + queryset=Cluster.objects.all(), required=False, label="Provider" + ) + class Meta: + model = Cluster + fields = ('provider',) + +class ClusterTypeFormList(NetBoxModelForm): + """Form for Cluster Types List.""" + provider_type = DynamicModelChoiceField( + queryset=ClusterType.objects.all(), required=False, label="Provider Type" + ) + class Meta: + model = ClusterType + fields = ('provider_type',) + class VirtualMachineForm(NetBoxModelForm): """Form for Virtual Machines.""" class Meta: @@ -63,6 +81,14 @@ IPAddressFormSet = forms.modelformset_factory( IPAddress, form=IPAddressForm, formset=IPAddressForm, extra=1, can_delete=False ) +ClusterTypeListFormSet = forms.modelformset_factory( + ClusterType, form=ClusterTypeFormList, formset=ClusterTypeFormList, extra=1, can_delete=False +) + +ClusterListFormSet = forms.modelformset_factory( + Cluster, form=ClusterFormList, formset=ClusterFormList, extra=1, can_delete=False +) + # Combined form class CreateVmForm(NetBoxModelForm): @@ -77,27 +103,27 @@ class CreateVmForm(NetBoxModelForm): super().__init__(*args, **kwargs) # Initialize formsets - #Cluster + Cluster type + # Cluster + Cluster type self.cluster_formsets = [] empty_cluster_formset = ClusterFormSet(data=data, prefix='clusters') self.cluster_formsets.append(('new', empty_cluster_formset)) - #Virtual Machine + # Virtual Machine self.virtual_machine_formsets = [] empty_vm_formset = VirtualMachineFormSet(data=data, prefix='vms_new') self.virtual_machine_formsets.append(('new', empty_vm_formset)) - #Virtual Machine Interfaces + # Virtual Machine Interfaces self.virtual_machine_interface_formsets = [] empty_vmi_formset = VirtualMachineInterfaceFormSet(data=data, prefix='vmis_new') self.virtual_machine_interface_formsets.append(('new', empty_vmi_formset)) - #Service + # Service self.service_ntp_formsets = [] empty_ntp_service_formset = ServiceNtpFormSet(data=data, prefix='service_ntp') self.service_ntp_formsets.append(('new', empty_ntp_service_formset)) - #Service + # Service self.service_dns_formsets = [] empty_dns_service_formset = ServiceDnsFormSet(data=data, prefix='service_dns') self.service_dns_formsets.append(('new', empty_dns_service_formset)) - #Service + # Service self.service_syslog_formsets = [] empty_syslog_service_formset = ServiceSyslogFormSet(data=data, prefix='service_syslog') self.service_syslog_formsets.append(('new', empty_syslog_service_formset)) @@ -105,4 +131,14 @@ class CreateVmForm(NetBoxModelForm): self.ip_formsets = [] empty_ip_formset = IPAddressFormSet(data=data, prefix='ip_new') self.ip_formsets.append(('new', empty_ip_formset)) + # List Cluster Type + self.clt_list_formsets = [] + empty_clt_list_formset = ClusterTypeListFormSet(data=data, prefix='clt_list_new') + self.clt_list_formsets.append(('new', empty_clt_list_formset)) + # List Cluster + self.cl_list_formsets = [] + empty_cl_list_formset = ClusterListFormSet(data=data, prefix='cl_list_new') + self.cl_list_formsets.append(('new', empty_cl_list_formset)) + + diff --git a/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html b/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html index 88836d0..d482803 100644 --- a/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html +++ b/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html @@ -25,27 +25,27 @@ SYS - Virtual Machine <div class="tab-pane show active" id="edit-form" role="tabpanel" aria-labelledby="object-list-tab"> {% block content %} <form method="post" enctype="multipart/form-data" class="form-object-edit mt-5"> + {% csrf_token %} <div class="field-group my-5"> <div class="row mb-2"> </div> <div class="row mb-2"> <ul class="nav nav-pills" role="tablist"> <li role="presentation" class="nav-item"> - <button role="tab" type="button" id="device_tab" data-bs-toggle="tab" aria-controls="device" data-bs-target="#device" class="nav-link {% if not form.initial.vminterface and not form.initial.fhrpgroup %}active{% endif %}"> + <button role="tab" type="button" id="device_tab" data-bs-toggle="tab" aria-controls="device" data-bs-target="#device" class="nav-link active"> New Provider </button> </li> <li role="presentation" class="nav-item"> - <button role="tab" type="button" id="vm_tab" data-bs-toggle="tab" aria-controls="vm" data-bs-target="#vm" class="nav-link {% if form.initial.vminterface %}active{% endif %}"> + <button role="tab" type="button" id="vm_tab" data-bs-toggle="tab" aria-controls="vm" data-bs-target="#vm" class="nav-link"> Existing Provider </button> </li> </ul> </div> <div class="tab-content p-0 border-0"> - <div class="tab-pane" id="device" role="tabpanel" aria-labeled-by="device_tab"> + <div class="tab-pane active" id="device" role="tabpanel" aria-labeled-by="device_tab"> <h5>Provider Type</h5> - {% csrf_token %} {{ form.as_p }} <h5>Provider Credentials</h5> {% for cluster_formset in form.cluster_formsets %} @@ -57,7 +57,21 @@ SYS - Virtual Machine {% endfor %} </div> <div class="tab-pane" id="vm" role="tabpanel" aria-labeled-by="vm_tab"> - <p>Tab2</p> + <h5>Provider</h5> + {% for clt_list_formset in form.clt_list_formsets %} + {% for ctl_form in clt_list_formset %} + <div class="form-group" class="field-group mb-5"> + {{ ctl_form.as_p }} + </div> + {% endfor %} + {% endfor %} + {% for cl_list_formset in form.cl_list_formsets %} + {% for ct_form in cl_list_formset %} + <div class="form-group" class="field-group mb-5"> + {{ ct_form.as_p }} + </div> + {% endfor %} + {% endfor %} </div> </div> </div> -- GitLab From 412e87f31e156e2ccd0cca5c3a78cf26e7363522 Mon Sep 17 00:00:00 2001 From: Frederico Sequeira <frederico.sequeira@ext.ec.europa.eu> Date: Thu, 28 Nov 2024 16:05:56 +0000 Subject: [PATCH 14/28] =?UTF-8?q?=F0=9F=94=A5=20Remove=20unnecessary=20cod?= =?UTF-8?q?e?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- netbox_sys_plugin/forms/createvm.py | 18 ++---------------- .../templates/netbox_sys_plugin/create_vm.html | 7 ------- 2 files changed, 2 insertions(+), 23 deletions(-) diff --git a/netbox_sys_plugin/forms/createvm.py b/netbox_sys_plugin/forms/createvm.py index 68c2e0b..6fc14d6 100644 --- a/netbox_sys_plugin/forms/createvm.py +++ b/netbox_sys_plugin/forms/createvm.py @@ -16,18 +16,12 @@ class ClusterFormList(NetBoxModelForm): provider = DynamicModelChoiceField( queryset=Cluster.objects.all(), required=False, label="Provider" ) - class Meta: - model = Cluster - fields = ('provider',) - -class ClusterTypeFormList(NetBoxModelForm): - """Form for Cluster Types List.""" provider_type = DynamicModelChoiceField( queryset=ClusterType.objects.all(), required=False, label="Provider Type" ) class Meta: - model = ClusterType - fields = ('provider_type',) + model = Cluster + fields = ('provider_type','provider') class VirtualMachineForm(NetBoxModelForm): """Form for Virtual Machines.""" @@ -81,10 +75,6 @@ IPAddressFormSet = forms.modelformset_factory( IPAddress, form=IPAddressForm, formset=IPAddressForm, extra=1, can_delete=False ) -ClusterTypeListFormSet = forms.modelformset_factory( - ClusterType, form=ClusterTypeFormList, formset=ClusterTypeFormList, extra=1, can_delete=False -) - ClusterListFormSet = forms.modelformset_factory( Cluster, form=ClusterFormList, formset=ClusterFormList, extra=1, can_delete=False ) @@ -131,10 +121,6 @@ class CreateVmForm(NetBoxModelForm): self.ip_formsets = [] empty_ip_formset = IPAddressFormSet(data=data, prefix='ip_new') self.ip_formsets.append(('new', empty_ip_formset)) - # List Cluster Type - self.clt_list_formsets = [] - empty_clt_list_formset = ClusterTypeListFormSet(data=data, prefix='clt_list_new') - self.clt_list_formsets.append(('new', empty_clt_list_formset)) # List Cluster self.cl_list_formsets = [] empty_cl_list_formset = ClusterListFormSet(data=data, prefix='cl_list_new') diff --git a/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html b/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html index d482803..a990f45 100644 --- a/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html +++ b/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html @@ -58,13 +58,6 @@ SYS - Virtual Machine </div> <div class="tab-pane" id="vm" role="tabpanel" aria-labeled-by="vm_tab"> <h5>Provider</h5> - {% for clt_list_formset in form.clt_list_formsets %} - {% for ctl_form in clt_list_formset %} - <div class="form-group" class="field-group mb-5"> - {{ ctl_form.as_p }} - </div> - {% endfor %} - {% endfor %} {% for cl_list_formset in form.cl_list_formsets %} {% for ct_form in cl_list_formset %} <div class="form-group" class="field-group mb-5"> -- GitLab From b42fc4ade4c758cef5c73ac28ad22e36d92a7918 Mon Sep 17 00:00:00 2001 From: Frederico Sequeira <frederico.sequeira@ext.ec.europa.eu> Date: Thu, 28 Nov 2024 18:30:52 +0000 Subject: [PATCH 15/28] =?UTF-8?q?=F0=9F=9A=A7=20Validation=20between=20new?= =?UTF-8?q?=20and=20existing=20clusters?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- netbox_sys_plugin/forms/createvm.py | 45 +++++++++++++- .../netbox_sys_plugin/create_vm.html | 1 - netbox_sys_plugin/views.py | 62 ++++++++++++++++--- 3 files changed, 96 insertions(+), 12 deletions(-) diff --git a/netbox_sys_plugin/forms/createvm.py b/netbox_sys_plugin/forms/createvm.py index 6fc14d6..6756d88 100644 --- a/netbox_sys_plugin/forms/createvm.py +++ b/netbox_sys_plugin/forms/createvm.py @@ -7,46 +7,76 @@ from utilities.forms.fields import DynamicModelChoiceField class ClusterForm(NetBoxModelForm): """Form for Clusters.""" + + name = forms.CharField( + max_length=50, min_length=1, required=False, label="Name" + ) + class Meta: model = Cluster fields = ('name', 'type', 'description','status','site','tags') + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields.pop('tags',None) + class ClusterFormList(NetBoxModelForm): """Form for Cluster List.""" - provider = DynamicModelChoiceField( + cluster = DynamicModelChoiceField( queryset=Cluster.objects.all(), required=False, label="Provider" ) - provider_type = DynamicModelChoiceField( + cluster_type = DynamicModelChoiceField( queryset=ClusterType.objects.all(), required=False, label="Provider Type" ) class Meta: model = Cluster - fields = ('provider_type','provider') + fields = ('cluster_type','cluster') + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields.pop('tags',None) class VirtualMachineForm(NetBoxModelForm): """Form for Virtual Machines.""" + class Meta: model = VirtualMachine fields = ('name', 'cluster', 'status', 'role','platform', 'description','tags') + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields.pop('tags',None) + class VirtualMachineInterfaceForm(NetBoxModelForm): """Form for Virtual Machine Interfaces.""" class Meta: model = VMInterface fields = ('name','tags') + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields.pop('tags',None) + class ServiceForm(NetBoxModelForm): """Form for Virtual Machine Interfaces.""" class Meta: model = Service fields = ('name','protocol','ports','tags') + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields.pop('tags',None) + class IPAddressForm(NetBoxModelForm): """Form for IP Addresses.""" class Meta: model = IPAddress fields = ('address', 'status', 'role', 'tags') + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields.pop('tags',None) + # Inline formsets for managing relationships ClusterFormSet = inlineformset_factory( @@ -84,6 +114,14 @@ ClusterListFormSet = forms.modelformset_factory( class CreateVmForm(NetBoxModelForm): """Combined form for managing ClusterType, Cluster, and VirtualMachine.""" + name = forms.CharField( + max_length=50, min_length=1, required=False, label="Name" + ) + + slug = forms.CharField( + max_length=100, min_length=1, required=False, label="Slug" + ) + class Meta: model = ClusterType fields = ('name', 'slug','description','tags') @@ -91,6 +129,7 @@ class CreateVmForm(NetBoxModelForm): def __init__(self, *args, **kwargs): data = kwargs.pop('data', None) super().__init__(*args, **kwargs) + self.fields.pop('tags',None) # Initialize formsets # Cluster + Cluster type diff --git a/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html b/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html index a990f45..e39703f 100644 --- a/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html +++ b/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html @@ -1,7 +1,6 @@ {% extends 'base/layout.html' %} {% load buttons %} {% load custom_links %} -{% load helpers %} {% load perms %} {% load plugins %} {% load tabs %} diff --git a/netbox_sys_plugin/views.py b/netbox_sys_plugin/views.py index de10bb9..a0e3a14 100644 --- a/netbox_sys_plugin/views.py +++ b/netbox_sys_plugin/views.py @@ -129,6 +129,33 @@ class VmTypeEditView(generic.ObjectEditView): #Create VM # Helper functions for object creation +def check_cluster_existence(data): + + # Get Value from request + new_clustertype=data.get('name', ''), + new_cluster=data.get('clusters-0-name', ''), + existing_cluster_type=data.get('cl_list_new-cluster_type', ''), + existing_cluster=data.get('cl_list_new-cluster', ''), + + # Clean values + new_clustertype=new_clustertype[0].strip() + new_cluster=new_cluster[0].strip() + existing_cluster_type=existing_cluster_type[0].strip() + existing_cluster=existing_cluster[0].strip() + + # Check assignment + # Validation for both cases selected + if (new_clustertype and new_cluster and existing_cluster_type and existing_cluster): + raise ValueError(f"Cant create a provider and choose a existing one at the same time") + + # Choose outcome + if ((not new_clustertype and not new_clustertype) and (existing_cluster_type and existing_cluster)): + result=True + else: + result=False + + return result + def parse_ports(ports_field): """Parse the value for ports""" try: @@ -136,6 +163,16 @@ def parse_ports(ports_field): return ports except Exception as e: raise ValueError(f"Invalid ports value:{ports_field}. Error: {str(e)}") + +def get_cluster(data): + cluster_id = data.get('cl_list_new-cluster', '') + print("cluster id: ",cluster_id) + try: + cluster=Cluster.objects.get(pk=cluster_id) + print("cluster obj: ",cluster) + except Cluster.DoesNotExist: + raise ValueError(f"Invalid Platform ID: {cluster_id}") + return cluster def create_cluster_type(data): @@ -144,7 +181,6 @@ def create_cluster_type(data): name=data.get('name', ''), slug=data.get('slug', ''), description=data.get('description', '') - #tag=tag, ) cluster_type.full_clean() # Validate the data cluster_type.save() # Save to the database @@ -302,13 +338,23 @@ class CreateVmView(generic.ObjectEditView): try: # Use a database transaction to ensure all-or-nothing behavior with transaction.atomic(): - # Create the ClusterType - cluster_type = create_cluster_type(request.POST) - print(f"ClusterType saved with ID: {cluster_type.pk}") - - # Create the Cluster - cluster = create_cluster(request.POST, cluster_type) - print(f"Cluster saved with ID: {cluster.pk}") + + if check_cluster_existence(request.POST) == False: + print('HERE NEW CLUSTER TYPE') + # Create the ClusterType + cluster_type = create_cluster_type(request.POST) + print(f"ClusterType saved with ID: {cluster_type.pk}") + # Create the Cluster + print('HERE NEW CLUSTER') + cluster = create_cluster(request.POST, cluster_type) + print(f"Cluster saved with ID: {cluster.pk}") + print("cluster new: ",cluster) + else: + # Get existint Cluster + print('HERE EXISTING CLUSTER') + cluster = get_cluster(request.POST) + print("cluster existing: ",cluster) + # Create the Virtual Machine vm = create_virtual_machine(request.POST, cluster) -- GitLab From 7d882c8d6092edb05fa1feb7ad5f13f16973417c Mon Sep 17 00:00:00 2001 From: Frederico Sequeira <frederico.sequeira@ext.ec.europa.eu> Date: Thu, 28 Nov 2024 20:02:18 +0000 Subject: [PATCH 16/28] =?UTF-8?q?=F0=9F=9A=A7=20Fixed=20create=20vm=20for?= =?UTF-8?q?=20new=20and=20existing=20cluster?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- netbox_sys_plugin/forms/createvm.py | 11 ++++---- netbox_sys_plugin/navigation.py | 6 ---- netbox_sys_plugin/views.py | 43 ++++++++++++++++------------- 3 files changed, 30 insertions(+), 30 deletions(-) diff --git a/netbox_sys_plugin/forms/createvm.py b/netbox_sys_plugin/forms/createvm.py index 6756d88..da25cb4 100644 --- a/netbox_sys_plugin/forms/createvm.py +++ b/netbox_sys_plugin/forms/createvm.py @@ -14,7 +14,7 @@ class ClusterForm(NetBoxModelForm): class Meta: model = Cluster - fields = ('name', 'type', 'description','status','site','tags') + fields = ('name', 'type', 'description','status','tags') def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -22,12 +22,13 @@ class ClusterForm(NetBoxModelForm): class ClusterFormList(NetBoxModelForm): """Form for Cluster List.""" - cluster = DynamicModelChoiceField( - queryset=Cluster.objects.all(), required=False, label="Provider" - ) cluster_type = DynamicModelChoiceField( queryset=ClusterType.objects.all(), required=False, label="Provider Type" ) + cluster = DynamicModelChoiceField( + queryset=Cluster.objects.all(), required=False, label="Provider", + query_params={'type': '$cluster_type',}, + ) class Meta: model = Cluster fields = ('cluster_type','cluster') @@ -41,7 +42,7 @@ class VirtualMachineForm(NetBoxModelForm): class Meta: model = VirtualMachine - fields = ('name', 'cluster', 'status', 'role','platform', 'description','tags') + fields = ('name', 'cluster', 'status','site','role','platform', 'description','tags') def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) diff --git a/netbox_sys_plugin/navigation.py b/netbox_sys_plugin/navigation.py index a418b4a..2391977 100644 --- a/netbox_sys_plugin/navigation.py +++ b/netbox_sys_plugin/navigation.py @@ -86,16 +86,11 @@ operItem =[ link_text="Virtual Machine Types", buttons=vm_machine_type_buttons ), -] - - -createVMItem =[ PluginMenuItem( link="plugins:netbox_sys_plugin:creatvm_add", link_text="Create Virtual Machine ", buttons=create_vm_buttons ), - ] #Menu menu = PluginMenu( @@ -104,7 +99,6 @@ menu = PluginMenu( ("Virtual Machine", (vmMachineItem)), ("Provider", clusterProviderCredentialsItem), ("Operation", (operItem)), - ("SYS", (createVMItem)), ), icon_class="mdi mdi-all-inclusive-box-outline", ) \ No newline at end of file diff --git a/netbox_sys_plugin/views.py b/netbox_sys_plugin/views.py index a0e3a14..6cce239 100644 --- a/netbox_sys_plugin/views.py +++ b/netbox_sys_plugin/views.py @@ -129,7 +129,7 @@ class VmTypeEditView(generic.ObjectEditView): #Create VM # Helper functions for object creation -def check_cluster_existence(data): +def check_cluster_exist(data): # Get Value from request new_clustertype=data.get('name', ''), @@ -166,12 +166,10 @@ def parse_ports(ports_field): def get_cluster(data): cluster_id = data.get('cl_list_new-cluster', '') - print("cluster id: ",cluster_id) try: cluster=Cluster.objects.get(pk=cluster_id) - print("cluster obj: ",cluster) except Cluster.DoesNotExist: - raise ValueError(f"Invalid Platform ID: {cluster_id}") + raise ValueError(f"Invalid Cluster ID: {cluster_id}") return cluster @@ -189,7 +187,7 @@ def create_cluster_type(data): def create_cluster(data, cluster_type): """Creates and saves a Cluster object linked to a ClusterType.""" - site_id = data.get('clusters-0-site', '') + site_id = data.get('vms_new-0-role', '') try: site=Site.objects.get(pk=site_id) except Site.DoesNotExist: @@ -207,20 +205,30 @@ def create_cluster(data, cluster_type): return cluster -def create_virtual_machine(data, cluster): +def create_virtual_machine(data, cluster, cluster_existence): """Creates and saves a VirtualMachine object linked to a Cluster.""" role_id = data.get('vms_new-0-role', '') try: role=DeviceRole.objects.get(pk=role_id) except DeviceRole.DoesNotExist: raise ValueError(f"Invalid Device Role ID: {role_id}") - - site_id = data.get('clusters-0-site', '') - try: - site=Site.objects.get(pk=site_id) - except Site.DoesNotExist: - raise ValueError(f"Invalid Site ID: {site_id}") - + #Site logic for existing clusters + if cluster_existence == False: + site_id = data.get('vms_new-0-site', '') + try: + site=Site.objects.get(pk=site_id) + except Site.DoesNotExist: + raise ValueError(f"Invalid Site ID: {site_id}") + + else: + cluster_id = data.get('cl_list_new-cluster', '') + cluster=Cluster.objects.get(pk=cluster_id) + site_id = cluster.site_id + try: + site=Site.objects.get(pk=site_id) + except Site.DoesNotExist: + raise ValueError(f"Invalid Site ID: {site_id}") + platform_id = data.get('vms_new-0-platform', '') try: platform=Platform.objects.get(pk=platform_id) @@ -338,26 +346,23 @@ class CreateVmView(generic.ObjectEditView): try: # Use a database transaction to ensure all-or-nothing behavior with transaction.atomic(): + cl_exist = check_cluster_exist(request.POST) - if check_cluster_existence(request.POST) == False: + if cl_exist == False: print('HERE NEW CLUSTER TYPE') # Create the ClusterType cluster_type = create_cluster_type(request.POST) print(f"ClusterType saved with ID: {cluster_type.pk}") # Create the Cluster - print('HERE NEW CLUSTER') cluster = create_cluster(request.POST, cluster_type) print(f"Cluster saved with ID: {cluster.pk}") - print("cluster new: ",cluster) else: # Get existint Cluster - print('HERE EXISTING CLUSTER') cluster = get_cluster(request.POST) - print("cluster existing: ",cluster) # Create the Virtual Machine - vm = create_virtual_machine(request.POST, cluster) + vm = create_virtual_machine(request.POST, cluster,cl_exist) print(f"Virtual Machine saved with ID: {vm.pk}") #Create the Vm Interface -- GitLab From e319587ca5f181d1d1907ec93eea960d0ffdeb65 Mon Sep 17 00:00:00 2001 From: Frederico Sequeira <frederico.sequeira@ext.ec.europa.eu> Date: Fri, 29 Nov 2024 09:52:09 +0000 Subject: [PATCH 17/28] =?UTF-8?q?=F0=9F=8E=A8=20Improve=20template=20struc?= =?UTF-8?q?ture?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- netbox_sys_plugin/forms/createvm.py | 7 ++++ .../netbox_sys_plugin/create_vm.html | 41 +++++++++---------- 2 files changed, 26 insertions(+), 22 deletions(-) diff --git a/netbox_sys_plugin/forms/createvm.py b/netbox_sys_plugin/forms/createvm.py index da25cb4..4ab1da3 100644 --- a/netbox_sys_plugin/forms/createvm.py +++ b/netbox_sys_plugin/forms/createvm.py @@ -25,10 +25,12 @@ class ClusterFormList(NetBoxModelForm): cluster_type = DynamicModelChoiceField( queryset=ClusterType.objects.all(), required=False, label="Provider Type" ) + cluster = DynamicModelChoiceField( queryset=Cluster.objects.all(), required=False, label="Provider", query_params={'type': '$cluster_type',}, ) + class Meta: model = Cluster fields = ('cluster_type','cluster') @@ -50,6 +52,11 @@ class VirtualMachineForm(NetBoxModelForm): class VirtualMachineInterfaceForm(NetBoxModelForm): """Form for Virtual Machine Interfaces.""" + + name = forms.CharField( + max_length=50, min_length=1, required=True, label="Interface Name" + ) + class Meta: model = VMInterface fields = ('name','tags') diff --git a/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html b/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html index e39703f..120b947 100644 --- a/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html +++ b/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html @@ -26,8 +26,6 @@ SYS - Virtual Machine <form method="post" enctype="multipart/form-data" class="form-object-edit mt-5"> {% csrf_token %} <div class="field-group my-5"> - <div class="row mb-2"> - </div> <div class="row mb-2"> <ul class="nav nav-pills" role="tablist"> <li role="presentation" class="nav-item"> @@ -45,8 +43,10 @@ SYS - Virtual Machine <div class="tab-content p-0 border-0"> <div class="tab-pane active" id="device" role="tabpanel" aria-labeled-by="device_tab"> <h5>Provider Type</h5> + <div class="form-group" class="field-group mb-5"> {{ form.as_p }} - <h5>Provider Credentials</h5> + </div> + <h5>Provider</h5> {% for cluster_formset in form.cluster_formsets %} {% for vl_form in cluster_formset %} <div class="form-group" class="field-group mb-5"> @@ -67,15 +67,17 @@ SYS - Virtual Machine </div> </div> </div> - + <div class="field-group my-5"> <h5>Virtual Machine</h5> {% for vm_formset in form.virtual_machine_formsets %} {% for vm_form in vm_formset %} - <div class="form-group" class="field-group mb-5"> + <div class="form-group" class="field-group mb-5"> {{ vm_form.as_p }} - </div> + </div> {% endfor %} {% endfor %} + </div> + <div class="field-group my-5"> <h5>Network Name</h5> {% for vmi_formset in form.virtual_machine_interface_formsets %} {% for vmi_form in vmi_formset %} @@ -84,11 +86,19 @@ SYS - Virtual Machine </div> {% endfor %} {% endfor %} + <h5>IP Address</h5> + {% for ip_formset in form.ip_formsets %} + {% for ip_form in ip_formset %} + <div class="form-group" class="field-group mb-5"> + {{ ip_form.as_p }} + </div> + {% endfor %} + {% endfor %} + </div> + <div class="field-group my-5"> <h5>Dependencies</h5> - <div class="row"> <h6>NTP Server</h6> {% for service_ntp_formset in form.service_ntp_formsets %} - <div class="form-group" class="field-group mb-5"> {% for service_form in service_ntp_formset %} <div class="form-group" class="field-group mb-5"> {{ service_form.as_p }} @@ -97,7 +107,6 @@ SYS - Virtual Machine {% endfor %} <h6>DNS Server</h6> {% for service_dns_formset in form.service_dns_formsets %} - <div class="form-group" class="field-group mb-5"> {% for service_form in service_dns_formset %} <div class="form-group" class="field-group mb-5"> {{ service_form.as_p }} @@ -106,30 +115,18 @@ SYS - Virtual Machine {% endfor %} <h6>SYSLOG Server</h6> {% for service_syslog_formset in form.service_syslog_formsets %} - <div class="form-group" class="field-group mb-5"> {% for service_form in service_syslog_formset %} <div class="form-group" class="field-group mb-5"> {{ service_form.as_p }} </div> {% endfor %} {% endfor %} - - <h6>IP Address</h6> - {% for ip_formset in form.ip_formsets %} - <div class="form-group" class="field-group mb-5"> - {% for ip_form in ip_formset %} - <div class="form-group" class="field-group mb-5"> - {{ ip_form.as_p }} - </div> - {% endfor %} - {% endfor %} - </div> + </div> <div class="text-end my-3"> {% block buttons %} <button type="submit" class="btn btn-primary">Save</button> {% endblock buttons %} -</div> </form> </div> </div> -- GitLab From e96fde511e14bb7510ce164b042a1cd25c6d2ff0 Mon Sep 17 00:00:00 2001 From: Frederico Sequeira <frederico.sequeira@ext.ec.europa.eu> Date: Fri, 29 Nov 2024 16:54:11 +0000 Subject: [PATCH 18/28] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor=20views=20a?= =?UTF-8?q?nd=20forms=20code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- netbox_sys_plugin/forms/createvm.py | 170 ++++++++++- .../netbox_sys_plugin/create_vm.html | 196 ++++++------ netbox_sys_plugin/views.py | 286 ++---------------- 3 files changed, 275 insertions(+), 377 deletions(-) diff --git a/netbox_sys_plugin/forms/createvm.py b/netbox_sys_plugin/forms/createvm.py index 4ab1da3..e8c94bf 100644 --- a/netbox_sys_plugin/forms/createvm.py +++ b/netbox_sys_plugin/forms/createvm.py @@ -1,9 +1,14 @@ from django import forms -from django.forms import inlineformset_factory, BaseModelFormSet +from django.forms import inlineformset_factory from netbox.forms import NetBoxModelForm + from virtualization.models import ClusterType, Cluster, VirtualMachine, VMInterface from ipam.models import IPAddress, Service +from dcim.models import DeviceRole,Site, Platform from utilities.forms.fields import DynamicModelChoiceField +from django.db import transaction + + class ClusterForm(NetBoxModelForm): """Form for Clusters.""" @@ -122,22 +127,17 @@ ClusterListFormSet = forms.modelformset_factory( class CreateVmForm(NetBoxModelForm): """Combined form for managing ClusterType, Cluster, and VirtualMachine.""" - name = forms.CharField( - max_length=50, min_length=1, required=False, label="Name" - ) - - slug = forms.CharField( - max_length=100, min_length=1, required=False, label="Slug" - ) + name = forms.CharField(max_length=50, min_length=1, required=False, label="Name") + slug = forms.CharField(max_length=100, min_length=1, required=False, label="Slug") class Meta: model = ClusterType - fields = ('name', 'slug','description','tags') + fields = ('name', 'slug', 'description', 'tags') def __init__(self, *args, **kwargs): data = kwargs.pop('data', None) super().__init__(*args, **kwargs) - self.fields.pop('tags',None) + self.fields.pop('tags', None) # Initialize formsets # Cluster + Cluster type @@ -173,5 +173,151 @@ class CreateVmForm(NetBoxModelForm): empty_cl_list_formset = ClusterListFormSet(data=data, prefix='cl_list_new') self.cl_list_formsets.append(('new', empty_cl_list_formset)) - - + @staticmethod + def check_cluster_exist(data): + """Validate whether a cluster already exists based on the input.""" + new_clustertype = data.get('name', '').strip() + new_cluster = data.get('clusters-0-name', '').strip() + existing_cluster_type = data.get('cl_list_new-cluster_type', '').strip() + existing_cluster = data.get('cl_list_new-cluster', '').strip() + + if new_clustertype and new_cluster and existing_cluster_type and existing_cluster: + raise ValueError("Cannot create a provider and choose an existing one at the same time.") + + return bool(existing_cluster_type and existing_cluster) + + @staticmethod + def parse_ports(ports_field): + """Parse the port numbers.""" + try: + ports = [int(port.strip()) for port in ports_field.split(',') if port.strip().isdigit()] + return ports + except ValueError: + raise ValueError(f"Invalid ports value: {ports_field}") + + @staticmethod + def create_cluster_type(data): + """Create and save a ClusterType object.""" + cluster_type = ClusterType( + name=data.get('name', ''), + slug=data.get('slug', ''), + description=data.get('description', '') + ) + cluster_type.full_clean() + cluster_type.save() + return cluster_type + + @staticmethod + def create_cluster(data, cluster_type): + """Create and save a Cluster object.""" + site_id = data.get('vms_new-0-role', '') + try: + site = Site.objects.get(pk=site_id) + except Site.DoesNotExist: + raise ValueError(f"Invalid Site ID: {site_id}") + + cluster = Cluster( + name=data.get('clusters-0-name', ''), + type=cluster_type, + status=data.get('clusters-0-status', ''), + site=site, + description=data.get('clusters-0-description', '') + ) + cluster.full_clean() + cluster.save() + return cluster + + @staticmethod + def create_virtual_machine(data, cluster, cluster_existence): + """Create and save a VirtualMachine object.""" + role_id = data.get('vms_new-0-role', '') + try: + role = DeviceRole.objects.get(pk=role_id) + except DeviceRole.DoesNotExist: + raise ValueError(f"Invalid Device Role ID: {role_id}") + + platform_id = data.get('vms_new-0-platform', '') + try: + platform = Platform.objects.get(pk=platform_id) + except Platform.DoesNotExist: + raise ValueError(f"Invalid Platform ID: {platform_id}") + + vm = VirtualMachine( + name=data.get('vms_new-0-name', ''), + status=data.get('vms_new-0-status', ''), + role=role, + site=cluster.site, + platform=platform, + description=data.get('vms_new-0-description', ''), + cluster=cluster + ) + vm.full_clean() + vm.save() + return vm + + @staticmethod + def create_vm_interface(data, vm): + """Create and save a VMInterface object.""" + vmi = VMInterface( + name=data.get('vmis_new-0-name', ''), + virtual_machine=vm + ) + vmi.full_clean() + vmi.save() + return vmi + + @staticmethod + def create_service(data, vm, prefix, description): + """Create and save a Service object.""" + ports_field = data.get(f'{prefix}-0-ports', '') + ports = CreateVmForm.parse_ports(ports_field) + + service = Service( + name=data.get(f'{prefix}-0-name', ''), + protocol=data.get(f'{prefix}-0-protocol', ''), + ports=ports, + description=description, + virtual_machine=vm + ) + service.full_clean() + service.save() + return service + + def create_all_services(self, data, vm): + """Create NTP, DNS, and SYSLOG services.""" + self.create_service(data, vm, 'service_ntp', 'NTP Server') + self.create_service(data, vm, 'service_dns', 'DNS Server') + self.create_service(data, vm, 'service_syslog', 'SYSLOG Server') + + @staticmethod + def create_ip_address(data, vm_interface): + """Create and save an IPAddress object.""" + ip_address = IPAddress( + address=data.get('ip_new-address', ''), + status=data.get('ip_new-status', ''), + role=data.get('ip_new-role', ''), + assigned_object=vm_interface + ) + ip_address.full_clean() + ip_address.save() + return ip_address + + def process_creation(self, data): + """Process creation of ClusterType, Cluster, VM, and related objects.""" + try: + with transaction.atomic(): + cluster_exists = self.check_cluster_exist(data) + if not cluster_exists: + cluster_type = self.create_cluster_type(data) + cluster = self.create_cluster(data, cluster_type) + else: + cluster = Cluster.objects.get(pk=data.get('cl_list_new-cluster', '')) + + vm = self.create_virtual_machine(data, cluster, cluster_exists) + vmi = self.create_vm_interface(data, vm) + self.create_all_services(data, vm) + self.create_ip_address(data, vmi) + return vm + + except ValueError as e: + raise ValueError(f"Error during creation: {str(e)}") \ No newline at end of file diff --git a/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html b/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html index 120b947..d267c46 100644 --- a/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html +++ b/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html @@ -12,25 +12,25 @@ SYS - Virtual Machine {% endblock title %} {% block tabs %} - <ul class="nav nav-tabs px-3"> - <li class="nav-item" role="presentation"> - <button class="nav-link active" id="edit-form-tab" data-bs-toggle="tab" data-bs-target="#edit-form" type="button" role="tab" aria-controls="edit-form" aria-selected="true"> - Create </button> +<ul class="nav nav-tabs px-3"> + <li class="nav-item" role="presentation"> + <button class="nav-link active" id="edit-form-tab" data-bs-toggle="tab" data-bs-target="#edit-form" type="button" role="tab" aria-controls="edit-form" aria-selected="true"> + Create </button> </li> </ul> -{% endblock tabs %} -{% block content-wrapper %} -<div class="tab-content"> + {% endblock tabs %} + {% block content-wrapper %} + <div class="tab-content"> <div class="tab-pane show active" id="edit-form" role="tabpanel" aria-labelledby="object-list-tab"> -{% block content %} -<form method="post" enctype="multipart/form-data" class="form-object-edit mt-5"> - {% csrf_token %} - <div class="field-group my-5"> - <div class="row mb-2"> + {% block content %} + <form method="post" enctype="multipart/form-data" class="form-object-edit mt-5"> + {% csrf_token %} + <div class="field-group my-5"> + <div class="row mb-2"> <ul class="nav nav-pills" role="tablist"> <li role="presentation" class="nav-item"> <button role="tab" type="button" id="device_tab" data-bs-toggle="tab" aria-controls="device" data-bs-target="#device" class="nav-link active"> - New Provider + New Provider </button> </li> <li role="presentation" class="nav-item"> @@ -39,97 +39,99 @@ SYS - Virtual Machine </button> </li> </ul> - </div> - <div class="tab-content p-0 border-0"> - <div class="tab-pane active" id="device" role="tabpanel" aria-labeled-by="device_tab"> - <h5>Provider Type</h5> - <div class="form-group" class="field-group mb-5"> - {{ form.as_p }} </div> - <h5>Provider</h5> - {% for cluster_formset in form.cluster_formsets %} - {% for vl_form in cluster_formset %} - <div class="form-group" class="field-group mb-5"> + <div class="tab-content p-0 border-0"> + <div class="tab-pane active" id="device" role="tabpanel" aria-labeled-by="device_tab"> + <h5>Provider Type</h5> + <div class="form-group" class="field-group mb-5"> + {{ form.as_p }} + </div> + <h5>Provider</h5> + {% for cluster_formset in form.cluster_formsets %} + {% for vl_form in cluster_formset %} + <div class="form-group" class="field-group mb-5"> {{ vl_form.as_p }} + </div> + {% endfor %} + {% endfor %} </div> - {% endfor %} - {% endfor %} - </div> - <div class="tab-pane" id="vm" role="tabpanel" aria-labeled-by="vm_tab"> - <h5>Provider</h5> - {% for cl_list_formset in form.cl_list_formsets %} - {% for ct_form in cl_list_formset %} - <div class="form-group" class="field-group mb-5"> + <div class="tab-pane" id="vm" role="tabpanel" aria-labeled-by="vm_tab"> + <h5>Provider</h5> + {% for cl_list_formset in form.cl_list_formsets %} + {% for ct_form in cl_list_formset %} + <div class="form-group" class="field-group mb-5"> {{ ct_form.as_p }} + </div> + {% endfor %} + {% endfor %} </div> - {% endfor %} - {% endfor %} </div> </div> - </div> - <div class="field-group my-5"> - <h5>Virtual Machine</h5> - {% for vm_formset in form.virtual_machine_formsets %} - {% for vm_form in vm_formset %} - <div class="form-group" class="field-group mb-5"> - {{ vm_form.as_p }} - </div> - {% endfor %} - {% endfor %} - </div> - <div class="field-group my-5"> - <h5>Network Name</h5> - {% for vmi_formset in form.virtual_machine_interface_formsets %} - {% for vmi_form in vmi_formset %} - <div class="form-group" class="field-group mb-5"> - {{ vmi_form.as_p }} - </div> - {% endfor %} - {% endfor %} - <h5>IP Address</h5> - {% for ip_formset in form.ip_formsets %} - {% for ip_form in ip_formset %} - <div class="form-group" class="field-group mb-5"> - {{ ip_form.as_p }} - </div> - {% endfor %} - {% endfor %} + <div class="field-group my-5"> + <h5>Virtual Machine</h5> + {% for vm_formset in form.virtual_machine_formsets %} + {% for vm_form in vm_formset %} + <div class="form-group" class="field-group mb-5"> + {{ vm_form.as_p }} </div> - <div class="field-group my-5"> - <h5>Dependencies</h5> - <h6>NTP Server</h6> - {% for service_ntp_formset in form.service_ntp_formsets %} - {% for service_form in service_ntp_formset %} - <div class="form-group" class="field-group mb-5"> - {{ service_form.as_p }} - </div> - {% endfor %} - {% endfor %} - <h6>DNS Server</h6> - {% for service_dns_formset in form.service_dns_formsets %} - {% for service_form in service_dns_formset %} - <div class="form-group" class="field-group mb-5"> - {{ service_form.as_p }} - </div> - {% endfor %} - {% endfor %} - <h6>SYSLOG Server</h6> - {% for service_syslog_formset in form.service_syslog_formsets %} - {% for service_form in service_syslog_formset %} - <div class="form-group" class="field-group mb-5"> - {{ service_form.as_p }} - </div> - {% endfor %} - {% endfor %} + {% endfor %} + {% endfor %} + </div> + <div class="field-group my-5"> + <h5>Network Name</h5> + {% for vmi_formset in form.virtual_machine_interface_formsets %} + {% for vmi_form in vmi_formset %} + <div class="form-group" class="field-group mb-5"> + {{ vmi_form.as_p }} + </div> + {% endfor %} + {% endfor %} + <h5>IP Address</h5> + {% for ip_formset in form.ip_formsets %} + {% for ip_form in ip_formset %} + <div class="form-group" class="field-group mb-5"> + {{ ip_form.as_p }} + </div> + {% endfor %} + {% endfor %} + </div> + <div class="field-group my-5"> + <h5>Dependencies</h5> + <h6>NTP Server</h6> + {% for service_ntp_formset in form.service_ntp_formsets %} + {% for service_form in service_ntp_formset %} + <div class="form-group" class="field-group mb-5"> + {{ service_form.as_p }} + </div> + {% endfor %} + {% endfor %} + <h6>DNS Server</h6> + {% for service_dns_formset in form.service_dns_formsets %} + {% for service_form in service_dns_formset %} + <div class="form-group" class="field-group mb-5"> + {{ service_form.as_p }} + </div> + {% endfor %} + {% endfor %} + <h6>SYSLOG Server</h6> + {% for service_syslog_formset in form.service_syslog_formsets %} + {% for service_form in service_syslog_formset %} + <div class="form-group" class="field-group mb-5"> + {{ service_form.as_p }} + </div> + {% endfor %} + {% endfor %} + </div> + + <div class="text-end my-3"> + {% block buttons %} + <button type="submit" class="btn btn-primary">Save</button> + {% endblock buttons %} + </div> + </form> </div> </div> - <div class="text-end my-3"> - {% block buttons %} - <button type="submit" class="btn btn-primary">Save</button> - {% endblock buttons %} -</form> -</div> -</div> -{% endblock %} -{% endblock content-wrapper %} - + {% endblock %} + {% endblock content-wrapper %} + + \ No newline at end of file diff --git a/netbox_sys_plugin/views.py b/netbox_sys_plugin/views.py index 6cce239..fd8bbc9 100644 --- a/netbox_sys_plugin/views.py +++ b/netbox_sys_plugin/views.py @@ -3,12 +3,8 @@ from django.shortcuts import render, redirect from netbox.views import generic from . import forms, models, tables, filtersets -from virtualization.models import ClusterType, Cluster, VirtualMachine,VMInterface -from dcim.models import DeviceRole,Site, Platform -from ipam.models import Service, IPAddress -from django.db import transaction from django.contrib import messages -from taggit.models import Tag +from django.urls import reverse @@ -96,7 +92,6 @@ class VmAssignedVmTypeEditView(generic.ObjectEditView): queryset = models.VmAssignedVirtualMachineType.objects.all() form = forms.VmAssignedVmTypeForm - #VmType class VmTypeView(generic.ObjectView): """ Virtual Machine Type view definition""" @@ -125,270 +120,25 @@ class VmTypeEditView(generic.ObjectEditView): queryset = models.VirtualMachineType.objects.all() form = forms.VmTypeForm - -#Create VM - -# Helper functions for object creation -def check_cluster_exist(data): - - # Get Value from request - new_clustertype=data.get('name', ''), - new_cluster=data.get('clusters-0-name', ''), - existing_cluster_type=data.get('cl_list_new-cluster_type', ''), - existing_cluster=data.get('cl_list_new-cluster', ''), - - # Clean values - new_clustertype=new_clustertype[0].strip() - new_cluster=new_cluster[0].strip() - existing_cluster_type=existing_cluster_type[0].strip() - existing_cluster=existing_cluster[0].strip() - - # Check assignment - # Validation for both cases selected - if (new_clustertype and new_cluster and existing_cluster_type and existing_cluster): - raise ValueError(f"Cant create a provider and choose a existing one at the same time") - - # Choose outcome - if ((not new_clustertype and not new_clustertype) and (existing_cluster_type and existing_cluster)): - result=True - else: - result=False - - return result - -def parse_ports(ports_field): - """Parse the value for ports""" - try: - ports=[int(port.strip())for port in ports_field.split(',') if port.strip().isdigit()] - return ports - except Exception as e: - raise ValueError(f"Invalid ports value:{ports_field}. Error: {str(e)}") - -def get_cluster(data): - cluster_id = data.get('cl_list_new-cluster', '') - try: - cluster=Cluster.objects.get(pk=cluster_id) - except Cluster.DoesNotExist: - raise ValueError(f"Invalid Cluster ID: {cluster_id}") - return cluster - - -def create_cluster_type(data): - """Creates and saves a ClusterType object.""" - cluster_type = ClusterType( - name=data.get('name', ''), - slug=data.get('slug', ''), - description=data.get('description', '') - ) - cluster_type.full_clean() # Validate the data - cluster_type.save() # Save to the database - return cluster_type - - -def create_cluster(data, cluster_type): - """Creates and saves a Cluster object linked to a ClusterType.""" - site_id = data.get('vms_new-0-role', '') - try: - site=Site.objects.get(pk=site_id) - except Site.DoesNotExist: - raise ValueError(f"Invalid Site ID: {site_id}") - - cluster = Cluster( - name=data.get('clusters-0-name', ''), - type=cluster_type, # Link to the saved ClusterType - status=data.get('clusters-0-status', ''), - site=site, - description=data.get('clusters-0-description', '') - ) - cluster.full_clean() # Validate the data - cluster.save() # Save to the database - return cluster - - -def create_virtual_machine(data, cluster, cluster_existence): - """Creates and saves a VirtualMachine object linked to a Cluster.""" - role_id = data.get('vms_new-0-role', '') - try: - role=DeviceRole.objects.get(pk=role_id) - except DeviceRole.DoesNotExist: - raise ValueError(f"Invalid Device Role ID: {role_id}") - #Site logic for existing clusters - if cluster_existence == False: - site_id = data.get('vms_new-0-site', '') - try: - site=Site.objects.get(pk=site_id) - except Site.DoesNotExist: - raise ValueError(f"Invalid Site ID: {site_id}") - - else: - cluster_id = data.get('cl_list_new-cluster', '') - cluster=Cluster.objects.get(pk=cluster_id) - site_id = cluster.site_id - try: - site=Site.objects.get(pk=site_id) - except Site.DoesNotExist: - raise ValueError(f"Invalid Site ID: {site_id}") - - platform_id = data.get('vms_new-0-platform', '') - try: - platform=Platform.objects.get(pk=platform_id) - except Platform.DoesNotExist: - raise ValueError(f"Invalid Platform ID: {platform_id}") - - vm = VirtualMachine( - name=data.get('vms_new-0-name', ''), - status=data.get('vms_new-0-status', ''), - role=role, - site=site, - platform=platform, - description=data.get('vms_new-0-description', ''), - cluster=cluster # Link to the saved Cluster - ) - vm.full_clean() # Validate the data - vm.save() # Save to the database - return vm - -def create_vm_interface(data, vm): - """Creates and saves a Vm Interface object linked to a Virtual Machine.""" - vmi = VMInterface( - name=data.get('vmis_new-0-name', ''), - virtual_machine=vm # Link to the saved Virtual Machine - ) - vmi.full_clean() # Validate the data - vmi.save() # Save to the database - return vmi - -def create_ntp_services(data, vm): - """Creates and saves a Vm Interface object linked to a Virtual Machine.""" - ports_field=data.get('service_syslog-0-ports', '') - ports=parse_ports(ports_field) - ntp_s = Service( - name=data.get('service_ntp-0-name', ''), - protocol=data.get('service_ntp-0-protocol', ''), - ports=ports, - description='NTP Server', - virtual_machine=vm # Link to the saved Virtual Machine - - ) - - ntp_s.full_clean() # Validate the data - ntp_s.save() # Save to the database - return ntp_s - -def create_dns_services(data, vm): - """Creates and saves a Vm Interface object linked to a Virtual Machine.""" - ports_field=data.get('service_syslog-0-ports', '') - ports=parse_ports(ports_field) - dns_s = Service( - name=data.get('service_dns-0-name', ''), - protocol=data.get('service_dns-0-protocol', ''), - ports=ports, - description='DNS Server', - virtual_machine=vm # Link to the saved Virtual Machine - - ) - - dns_s.full_clean() # Validate the data - dns_s.save() # Save to the database - return dns_s - - -def create_syslog_services(data, vm): - """Creates and saves a Vm Interface object linked to a Virtual Machine.""" - ports_field=data.get('service_syslog-0-ports', '') - ports=parse_ports(ports_field) - syslog_s = Service( - name=data.get('service_syslog-0-name', ''), - protocol=data.get('service_syslog-0-protocol', ''), - ports=ports, - description='SYSLOG Server', - virtual_machine=vm # Link to the saved Virtual Machine - ) - syslog_s.full_clean() # Validate the data - syslog_s.save() # Save to the database - return syslog_s - - -def create_ip_address(data, vm_interface): - """ - Creates and saves an IPAddress object linked to a specific VMInterface. - """ - ip_address = IPAddress( - address=data.get('ip_new-address', ''), - status=data.get('ip_new-status', ''), - role=data.get('ip_new-role', ''), - assigned_object=vm_interface # Link to the saved VMInterface - ) - ip_address.full_clean() # Validate the data - ip_address.save() # Save to the database - return ip_address - - #Create VM -# Main view for handling ClusterManagement -class CreateVmView(generic.ObjectEditView): - """View for managing ClusterType, Cluster, and VirtualMachine in one form.""" - queryset = ClusterType.objects.all() - form = forms.CreateVmForm +class CreateVmView(generic.ObjectView): + """View for creating a ClusterType, Cluster, and VirtualMachine.""" + form_class = forms.CreateVmForm + template_name = 'netbox_sys_plugin/create_vm.html' + queryset = models.Cluster.objects.all() def get(self, request, *args, **kwargs): - instance = self.get_object() - form = self.form(instance=instance) - return render(request, 'netbox_sys_plugin/create_vm.html', {'form': form}) + form = self.form_class() + return render(request, self.template_name, {'form': form}) def post(self, request, *args, **kwargs): - """Handles POST requests for objects.""" - form = self.form(data=request.POST) - - if request.method == 'POST': - print("POST DATA:", request.POST) - - try: - # Use a database transaction to ensure all-or-nothing behavior - with transaction.atomic(): - cl_exist = check_cluster_exist(request.POST) - - if cl_exist == False: - print('HERE NEW CLUSTER TYPE') - # Create the ClusterType - cluster_type = create_cluster_type(request.POST) - print(f"ClusterType saved with ID: {cluster_type.pk}") - # Create the Cluster - cluster = create_cluster(request.POST, cluster_type) - print(f"Cluster saved with ID: {cluster.pk}") - else: - # Get existint Cluster - cluster = get_cluster(request.POST) - - - # Create the Virtual Machine - vm = create_virtual_machine(request.POST, cluster,cl_exist) - print(f"Virtual Machine saved with ID: {vm.pk}") - - #Create the Vm Interface - vmi = create_vm_interface(request.POST, vm) - print(f"Virtual Machine Interface saved with ID: {vmi.pk}") - - #Create the Services - ntps = create_ntp_services(request.POST, vm) - print(f"NTP Server service saved with ID: {ntps.pk}") - dnsss = create_dns_services(request.POST, vm) - print(f"DNS Server service saved with ID: {dnsss.pk}") - syslogs = create_syslog_services(request.POST, vm) - print(f"SYSLOG Server service saved with ID: {syslogs.pk}") - - # Create the IP Address - ip_address = create_ip_address(request.POST, vmi) - print(f"IP Address saved with ID: {ip_address.pk}") - - # On successful save, provide feedback to the user - messages.success(request,'ClusterType, Cluster, and VirtualMachine created successfully!') - return render(request,'netbox_sys_plugin/create_vm.html',{'form': form,}) - - except Exception as e: - # Log the error and display feedback - print(f"Error saving objects: {str(e)}") - messages.error(request, f"Error: {str(e)}. Please correct the form and try again.") - return render(request,'netbox_sys_plugin/create_vm.html',{'form': form,}) - - return render(request,'netbox_sys_plugin/create_vm.html',{'form': form}) + form = self.form_class(data=request.POST) + + try: + form.process_creation(request.POST) + messages.success(request, "ClusterType, Cluster, and VirtualMachine created successfully!") + #return render(request, self.template_name, {'form': form}) + return redirect(reverse('virtualization:virtualmachine_list')) + except ValueError as e: + messages.error(request, f"Error: {str(e)}") + return render(request, self.template_name, {'form': form}) \ No newline at end of file -- GitLab From 8bf2a46e778841a9dc5dd02cad70b0770b8211ba Mon Sep 17 00:00:00 2001 From: Frederico Sequeira <frederico.sequeira@ext.ec.europa.eu> Date: Mon, 2 Dec 2024 09:59:57 +0000 Subject: [PATCH 19/28] =?UTF-8?q?=F0=9F=94=A5=20Remove=20url=20column=20an?= =?UTF-8?q?d=20change=20validator?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- netbox_sys_plugin/filtersets.py | 4 ++-- netbox_sys_plugin/forms/provider.py | 11 ++++------ ...redentials_provider_password_vault_path.py | 18 +++++++++++++++ ...ovidercredentials_provider_url_and_more.py | 22 +++++++++++++++++++ netbox_sys_plugin/models/provider.py | 11 ++-------- netbox_sys_plugin/tables.py | 15 +++++-------- .../cluster_providercredentials.html | 11 ++-------- .../providercredentials.html | 6 +---- 8 files changed, 56 insertions(+), 42 deletions(-) create mode 100644 netbox_sys_plugin/migrations/0004_rename_provider_password_vault_url_providercredentials_provider_password_vault_path.py create mode 100644 netbox_sys_plugin/migrations/0005_remove_providercredentials_provider_url_and_more.py diff --git a/netbox_sys_plugin/filtersets.py b/netbox_sys_plugin/filtersets.py index 3c031b6..a934a31 100644 --- a/netbox_sys_plugin/filtersets.py +++ b/netbox_sys_plugin/filtersets.py @@ -87,11 +87,11 @@ class ProviderCredentialsFilterSet(NetBoxModelFilterSet): class Meta: """Meta class""" model = ProviderCredentials - fields = ('provider_url','provider_username','provider_password_vault_url','cluster') + fields = ('provider_username','provider_password_vault_path','cluster') # pylint: disable=W0613 def search(self, queryset, name, value): """override""" if not value.strip(): return queryset - return queryset.filter(Q(provider_username__icontains=value)|Q(provider_url__icontains=value)|Q(provider_password_vault_url__icontains=value)) \ No newline at end of file + return queryset.filter(Q(provider_username__icontains=value)|Q(provider_password_vault_url__icontains=value)) \ No newline at end of file diff --git a/netbox_sys_plugin/forms/provider.py b/netbox_sys_plugin/forms/provider.py index ffd747c..1823cb1 100644 --- a/netbox_sys_plugin/forms/provider.py +++ b/netbox_sys_plugin/forms/provider.py @@ -20,14 +20,11 @@ class ProviderCredentialsForm(NetBoxModelForm): cluster = DynamicModelChoiceField( queryset=Cluster.objects.all(), required=True, label="Cluster" ) - provider_url = forms.CharField( - max_length=200, min_length=1, required=True, label="URL" - ) provider_username = forms.CharField( max_length=50, min_length=1, required=True, label="Username" ) - provider_password_vault_url = forms.CharField( - max_length=200, min_length=1, required=True, label="Password Vault URL" + provider_password_vault_path = forms.CharField( + max_length=200, min_length=1, required=True, label="Password Vault Path" ) def __init__(self, *args, **kwargs): @@ -53,7 +50,7 @@ class ProviderCredentialsForm(NetBoxModelForm): class Meta: """Meta class""" model = ProviderCredentials - fields = ('cluster','provider_url', 'provider_username', 'provider_password_vault_url','tags') + fields = ('cluster', 'provider_username', 'provider_password_vault_path','tags') def clean(self): @@ -72,7 +69,7 @@ class ProviderCredentialsForm(NetBoxModelForm): {"__all__": "Can't assign more than one Credential to the same Cluster"}, ) - if self.errors.get(f"provider_url"): + if self.errors.get(f"provider_username"): return def save(self, *args, **kwargs): diff --git a/netbox_sys_plugin/migrations/0004_rename_provider_password_vault_url_providercredentials_provider_password_vault_path.py b/netbox_sys_plugin/migrations/0004_rename_provider_password_vault_url_providercredentials_provider_password_vault_path.py new file mode 100644 index 0000000..2f21dc6 --- /dev/null +++ b/netbox_sys_plugin/migrations/0004_rename_provider_password_vault_url_providercredentials_provider_password_vault_path.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.16 on 2024-12-02 09:44 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('netbox_sys_plugin', '0003_virtualmachinetype_vmassignedvirtualmachinetype'), + ] + + operations = [ + migrations.RenameField( + model_name='providercredentials', + old_name='provider_password_vault_url', + new_name='provider_password_vault_path', + ), + ] diff --git a/netbox_sys_plugin/migrations/0005_remove_providercredentials_provider_url_and_more.py b/netbox_sys_plugin/migrations/0005_remove_providercredentials_provider_url_and_more.py new file mode 100644 index 0000000..17ee435 --- /dev/null +++ b/netbox_sys_plugin/migrations/0005_remove_providercredentials_provider_url_and_more.py @@ -0,0 +1,22 @@ +# Generated by Django 4.2.16 on 2024-12-02 09:47 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('netbox_sys_plugin', '0004_rename_provider_password_vault_url_providercredentials_provider_password_vault_path'), + ] + + operations = [ + migrations.RemoveField( + model_name='providercredentials', + name='provider_url', + ), + migrations.AlterField( + model_name='providercredentials', + name='provider_password_vault_path', + field=models.CharField(blank=True, default=None, max_length=200, null=True), + ), + ] diff --git a/netbox_sys_plugin/models/provider.py b/netbox_sys_plugin/models/provider.py index eb1cc24..61a33cb 100644 --- a/netbox_sys_plugin/models/provider.py +++ b/netbox_sys_plugin/models/provider.py @@ -20,21 +20,14 @@ CLUSTER_ASSIGNMENT_MODELS = models.Q(models.Q(app_label="virtualization", model= class ProviderCredentials(NetBoxModel): """Cluster ProviderInfo definition class""" - - provider_url = models.CharField( - default=None, blank=True, null=True, - max_length=100, - validators=[URLValidator(schemes=["http", "https"])], - ) - + provider_username = models.CharField( default=None, blank=True, null=True, max_length=50) - provider_password_vault_url = models.CharField( + provider_password_vault_path = models.CharField( default=None, blank=True, null=True, max_length=200, - validators=[URLValidator(schemes=["http", "https"])], ) assigned_object_type = models.ForeignKey( diff --git a/netbox_sys_plugin/tables.py b/netbox_sys_plugin/tables.py index 001f223..8366169 100644 --- a/netbox_sys_plugin/tables.py +++ b/netbox_sys_plugin/tables.py @@ -47,19 +47,15 @@ class ProviderCredentialsTable(NetBoxTable): assigned_object = tables.Column( linkify=True, orderable=False, - verbose_name="Cluster", + verbose_name="Provider", ) - provider_url = tables.Column( - linkify=True, - verbose_name="URL", - ) provider_username = tables.Column( - linkify=False, + linkify=True, verbose_name="Username", ) - provider_password_vault_url = tables.Column( + provider_password_vault_path = tables.Column( linkify=False, verbose_name="Password Vault URL", ) @@ -70,15 +66,14 @@ class ProviderCredentialsTable(NetBoxTable): fields = ( "pk", "id", - "provider_url", "provider_username", - "provider_password_vault_url", + "provider_password_vault_path", "assigned_object", "created", "last_updated", ) - default_columns = ('id','provider_url','provider_username','provider_password_vault_url','assigned_object') + default_columns = ('id','assigned_object','provider_username','provider_password_vault_path',) #VmAssignedType class VmAssignedVmTypeTable(NetBoxTable): diff --git a/netbox_sys_plugin/templates/netbox_sys_plugin/cluster_providercredentials.html b/netbox_sys_plugin/templates/netbox_sys_plugin/cluster_providercredentials.html index 71616af..5bf84db 100644 --- a/netbox_sys_plugin/templates/netbox_sys_plugin/cluster_providercredentials.html +++ b/netbox_sys_plugin/templates/netbox_sys_plugin/cluster_providercredentials.html @@ -10,13 +10,6 @@ <th scope="row">Passworl Vault URL</th> {% for providercredentials in providercredentialsinfo %} <tr> - <td> - {% if providercredentials.provider_url %} - <a href="{{ providercredentials.get_absolute_url }}">{{ providercredentials.provider_url }}</a> - {% else %} - {{ ''|placeholder }} - {% endif %} - </td> <td> {% if providercredentials.provider_username %} {{ providercredentials.provider_username }} @@ -25,8 +18,8 @@ {% endif %} </td> <td> - {% if providercredentials.provider_password_vault_url %} - {{ providercredentials.provider_password_vault_url }} + {% if providercredentials.provider_password_vault_path %} + {{ providercredentials.provider_password_vault_path }} {% else %} {{ ''|placeholder }} {% endif %} diff --git a/netbox_sys_plugin/templates/netbox_sys_plugin/providercredentials.html b/netbox_sys_plugin/templates/netbox_sys_plugin/providercredentials.html index d18f35b..a8a289d 100644 --- a/netbox_sys_plugin/templates/netbox_sys_plugin/providercredentials.html +++ b/netbox_sys_plugin/templates/netbox_sys_plugin/providercredentials.html @@ -11,17 +11,13 @@ Provider Credentials for {{ object.assigned_object }} <h5 class="card-header">Provider Credentials</h5> <div class="card-body"> <table class="table table-hover attr-table"> - <tr> - <th scope="row">URL</th> - <td>{{ object.provider_url }}</td> - </tr> <tr> <th scope="row">Username</th> <td>{{ object.provider_username }}</td> </tr> <tr> <th scope="row">Password vault URL</th> - <td>{{ object.provider_password_vault_url }}</td> + <td>{{ object.provider_password_vault_path }}</td> </tr> </table> </div> -- GitLab From 68c5fa11801292d0284a7247aaacd95b0b0b4551 Mon Sep 17 00:00:00 2001 From: Frederico Sequeira <frederico.sequeira@ext.ec.europa.eu> Date: Mon, 2 Dec 2024 10:04:55 +0000 Subject: [PATCH 20/28] =?UTF-8?q?=F0=9F=92=84=20Fix=20UI=20elements?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cluster_providercredentials.html | 17 ++++++++--------- .../netbox_sys_plugin/providercredentials.html | 2 +- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/netbox_sys_plugin/templates/netbox_sys_plugin/cluster_providercredentials.html b/netbox_sys_plugin/templates/netbox_sys_plugin/cluster_providercredentials.html index 5bf84db..c40dba5 100644 --- a/netbox_sys_plugin/templates/netbox_sys_plugin/cluster_providercredentials.html +++ b/netbox_sys_plugin/templates/netbox_sys_plugin/cluster_providercredentials.html @@ -5,18 +5,17 @@ <h5 class="card-header">Provider Credentials</h5> <div class="card-body"> <table class="table table-responsive"> - <th scope="row">URL</th> <th scope="row">Username</th> - <th scope="row">Passworl Vault URL</th> + <th scope="row">Passworl Vault path</th> {% for providercredentials in providercredentialsinfo %} <tr> - <td> - {% if providercredentials.provider_username %} - {{ providercredentials.provider_username }} - {% else %} - {{ ''|placeholder }} - {% endif %} - </td> + <td> + {% if providercredentials.provider_username %} + <a href="{{ providercredentials.get_absolute_url }}">{{ providercredentials.provider_username }}</a> + {% else %} + {{ ''|placeholder }} + {% endif %} + </td> <td> {% if providercredentials.provider_password_vault_path %} {{ providercredentials.provider_password_vault_path }} diff --git a/netbox_sys_plugin/templates/netbox_sys_plugin/providercredentials.html b/netbox_sys_plugin/templates/netbox_sys_plugin/providercredentials.html index a8a289d..cb91198 100644 --- a/netbox_sys_plugin/templates/netbox_sys_plugin/providercredentials.html +++ b/netbox_sys_plugin/templates/netbox_sys_plugin/providercredentials.html @@ -27,7 +27,7 @@ Provider Credentials for {{ object.assigned_object }} </div> <div class="col col-md-6"> <div class="card"> - <h5 class="card-header">Assigned Object</h5> + <h5 class="card-header">Provider</h5> <div class="card-body"> <table class="table table-hover attr-table"> <tr> -- GitLab From ea3b9d68ce32553b647ee8e235fd91e696a6c450 Mon Sep 17 00:00:00 2001 From: Frederico Sequeira <frederico.sequeira@ext.ec.europa.eu> Date: Mon, 2 Dec 2024 10:10:26 +0000 Subject: [PATCH 21/28] =?UTF-8?q?=F0=9F=92=84=20More=20UI=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- netbox_sys_plugin/tables.py | 6 +++--- .../templates/netbox_sys_plugin/virtualmachinetype.html | 2 +- .../netbox_sys_plugin/vmassignedvirtualmachinetype.html | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/netbox_sys_plugin/tables.py b/netbox_sys_plugin/tables.py index 8366169..b7de3e4 100644 --- a/netbox_sys_plugin/tables.py +++ b/netbox_sys_plugin/tables.py @@ -10,7 +10,7 @@ class VmMaintenanceTable(NetBoxTable): pk = columns.ToggleColumn() id = tables.Column( - linkify=False, + linkify=True, ) assigned_object = tables.Column( @@ -41,7 +41,7 @@ class ProviderCredentialsTable(NetBoxTable): pk = columns.ToggleColumn() id = tables.Column( - linkify=False, + linkify=True, ) assigned_object = tables.Column( @@ -122,7 +122,7 @@ class VmTypeTable(NetBoxTable): assigned_object = tables.Column( linkify=True, orderable=False, - verbose_name="Cluster Type", + verbose_name="Provider Type", ) virtual_machine_type_name = tables.Column( diff --git a/netbox_sys_plugin/templates/netbox_sys_plugin/virtualmachinetype.html b/netbox_sys_plugin/templates/netbox_sys_plugin/virtualmachinetype.html index c33372a..1bdb7fc 100644 --- a/netbox_sys_plugin/templates/netbox_sys_plugin/virtualmachinetype.html +++ b/netbox_sys_plugin/templates/netbox_sys_plugin/virtualmachinetype.html @@ -27,7 +27,7 @@ Virtual Machine Type for {{ object.assigned_object }} </div> <div class="col col-md-6"> <div class="card"> - <h5 class="card-header">Assigned Object</h5> + <h5 class="card-header">Provider Type</h5> <div class="card-body"> <table class="table table-hover attr-table"> <tr> diff --git a/netbox_sys_plugin/templates/netbox_sys_plugin/vmassignedvirtualmachinetype.html b/netbox_sys_plugin/templates/netbox_sys_plugin/vmassignedvirtualmachinetype.html index 5fa41ae..a51c986 100644 --- a/netbox_sys_plugin/templates/netbox_sys_plugin/vmassignedvirtualmachinetype.html +++ b/netbox_sys_plugin/templates/netbox_sys_plugin/vmassignedvirtualmachinetype.html @@ -34,7 +34,7 @@ Virtual Machine Type for {{ object.assigned_object }} </div> <div class="col col-md-6"> <div class="card"> - <h5 class="card-header">Assigned Object</h5> + <h5 class="card-header">Virtual Machine</h5> <div class="card-body"> <table class="table table-hover attr-table"> <tr> -- GitLab From 9dbce602be17e5884f4b04d5695a14fbce1ac066 Mon Sep 17 00:00:00 2001 From: Frederico Sequeira <frederico.sequeira@ext.ec.europa.eu> Date: Mon, 2 Dec 2024 15:09:33 +0000 Subject: [PATCH 22/28] =?UTF-8?q?=E2=9C=A8=20Add=20tags=20to=20form?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- netbox_sys_plugin/forms/createvm.py | 57 +++++++++++++++++-- .../netbox_sys_plugin/create_vm.html | 10 +++- 2 files changed, 60 insertions(+), 7 deletions(-) diff --git a/netbox_sys_plugin/forms/createvm.py b/netbox_sys_plugin/forms/createvm.py index e8c94bf..740f5ce 100644 --- a/netbox_sys_plugin/forms/createvm.py +++ b/netbox_sys_plugin/forms/createvm.py @@ -5,6 +5,7 @@ from netbox.forms import NetBoxModelForm from virtualization.models import ClusterType, Cluster, VirtualMachine, VMInterface from ipam.models import IPAddress, Service from dcim.models import DeviceRole,Site, Platform +from extras.models import Tag from utilities.forms.fields import DynamicModelChoiceField from django.db import transaction @@ -38,7 +39,7 @@ class ClusterFormList(NetBoxModelForm): class Meta: model = Cluster - fields = ('cluster_type','cluster') + fields = ('cluster_type','cluster','tags') def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -90,6 +91,21 @@ class IPAddressForm(NetBoxModelForm): super().__init__(*args, **kwargs) self.fields.pop('tags',None) +class TagsForm(NetBoxModelForm): + """Form for IP Addresses.""" + tag = DynamicModelChoiceField( + queryset=Tag.objects.all(), required=False, label="Tag" + ) + class Meta: + model = Tag + fields = ('tag',) + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields.pop('tags',None) + + + # Inline formsets for managing relationships ClusterFormSet = inlineformset_factory( @@ -122,6 +138,10 @@ ClusterListFormSet = forms.modelformset_factory( Cluster, form=ClusterFormList, formset=ClusterFormList, extra=1, can_delete=False ) +TagFormSet = forms.modelformset_factory( + Tag, form=TagsForm, formset=TagsForm, extra=1, can_delete=False +) + # Combined form class CreateVmForm(NetBoxModelForm): @@ -172,6 +192,10 @@ class CreateVmForm(NetBoxModelForm): self.cl_list_formsets = [] empty_cl_list_formset = ClusterListFormSet(data=data, prefix='cl_list_new') self.cl_list_formsets.append(('new', empty_cl_list_formset)) + # Tags + self.tagformsets = [] + empty_tagformset = TagFormSet(data=data, prefix='tag_new') + self.tagformsets.append(('new', empty_tagformset)) @staticmethod def check_cluster_exist(data): @@ -194,14 +218,28 @@ class CreateVmForm(NetBoxModelForm): return ports except ValueError: raise ValueError(f"Invalid ports value: {ports_field}") + + @staticmethod + def get_parse_tags(data): + tag_id = data.get('tag_new-tag', '') + tags_field = Tag.objects.get(pk=tag_id) + #try: + # tags = [int(tag.strip()) for tag in tags_field.split(',') if tag.strip().isdigit()] + # return tags + #except ValueError: + # raise ValueError(f"Invalid ports value: {tags_field}") + + return tags_field @staticmethod def create_cluster_type(data): """Create and save a ClusterType object.""" + tags = CreateVmForm.get_parse_tags(data) cluster_type = ClusterType( name=data.get('name', ''), slug=data.get('slug', ''), - description=data.get('description', '') + description=data.get('description', ''), + tags=tags ) cluster_type.full_clean() cluster_type.save() @@ -215,13 +253,15 @@ class CreateVmForm(NetBoxModelForm): site = Site.objects.get(pk=site_id) except Site.DoesNotExist: raise ValueError(f"Invalid Site ID: {site_id}") + tags = CreateVmForm.get_parse_tags(data) cluster = Cluster( name=data.get('clusters-0-name', ''), type=cluster_type, status=data.get('clusters-0-status', ''), site=site, - description=data.get('clusters-0-description', '') + description=data.get('clusters-0-description', ''), + tags=tags ) cluster.full_clean() cluster.save() @@ -241,6 +281,7 @@ class CreateVmForm(NetBoxModelForm): platform = Platform.objects.get(pk=platform_id) except Platform.DoesNotExist: raise ValueError(f"Invalid Platform ID: {platform_id}") + tags = CreateVmForm.get_parse_tags(data) vm = VirtualMachine( name=data.get('vms_new-0-name', ''), @@ -249,7 +290,8 @@ class CreateVmForm(NetBoxModelForm): site=cluster.site, platform=platform, description=data.get('vms_new-0-description', ''), - cluster=cluster + cluster=cluster, + tags=tags ) vm.full_clean() vm.save() @@ -271,13 +313,15 @@ class CreateVmForm(NetBoxModelForm): """Create and save a Service object.""" ports_field = data.get(f'{prefix}-0-ports', '') ports = CreateVmForm.parse_ports(ports_field) + tags = CreateVmForm.get_parse_tags(data) service = Service( name=data.get(f'{prefix}-0-name', ''), protocol=data.get(f'{prefix}-0-protocol', ''), ports=ports, description=description, - virtual_machine=vm + virtual_machine=vm, + tags=tags ) service.full_clean() service.save() @@ -296,7 +340,8 @@ class CreateVmForm(NetBoxModelForm): address=data.get('ip_new-address', ''), status=data.get('ip_new-status', ''), role=data.get('ip_new-role', ''), - assigned_object=vm_interface + assigned_object=vm_interface, + tags=data.get('tag_new-tag', '') ) ip_address.full_clean() ip_address.save() diff --git a/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html b/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html index d267c46..ad1def9 100644 --- a/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html +++ b/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html @@ -122,7 +122,15 @@ SYS - Virtual Machine {% endfor %} {% endfor %} </div> - + <div class="field-group my-5"> + <h5>Tag</h5> + {% for tagformset in form.tagformsets %} + {% for tag_form in tagformset %} + <div class="form-group" class="field-group mb-5"> + {{ tag_form.as_p }} + </div> + {% endfor %} + {% endfor %} <div class="text-end my-3"> {% block buttons %} <button type="submit" class="btn btn-primary">Save</button> -- GitLab From 922197b939724ee459ef1c30cdb2c1293f0c132e Mon Sep 17 00:00:00 2001 From: Frederico Sequeira <frederico.sequeira@ext.ec.europa.eu> Date: Mon, 2 Dec 2024 15:58:49 +0000 Subject: [PATCH 23/28] =?UTF-8?q?=F0=9F=90=9B=20Fix=20not=20assigned=20tag?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- netbox_sys_plugin/forms/createvm.py | 52 ++++++++++++++++++----------- 1 file changed, 32 insertions(+), 20 deletions(-) diff --git a/netbox_sys_plugin/forms/createvm.py b/netbox_sys_plugin/forms/createvm.py index 740f5ce..f0483a8 100644 --- a/netbox_sys_plugin/forms/createvm.py +++ b/netbox_sys_plugin/forms/createvm.py @@ -1,13 +1,15 @@ from django import forms from django.forms import inlineformset_factory +from django.contrib.contenttypes.models import ContentType +from django.db import transaction +from utilities.forms.fields import DynamicModelChoiceField from netbox.forms import NetBoxModelForm - from virtualization.models import ClusterType, Cluster, VirtualMachine, VMInterface from ipam.models import IPAddress, Service from dcim.models import DeviceRole,Site, Platform -from extras.models import Tag -from utilities.forms.fields import DynamicModelChoiceField -from django.db import transaction +from extras.models import Tag, TaggedItem + + @@ -222,27 +224,39 @@ class CreateVmForm(NetBoxModelForm): @staticmethod def get_parse_tags(data): tag_id = data.get('tag_new-tag', '') - tags_field = Tag.objects.get(pk=tag_id) - #try: - # tags = [int(tag.strip()) for tag in tags_field.split(',') if tag.strip().isdigit()] - # return tags - #except ValueError: - # raise ValueError(f"Invalid ports value: {tags_field}") - return tags_field + if not tag_id: + return [] + try: + tags_field = Tag.objects.get(pk=tag_id) + return [tags_field] + except Tag.DoesNotExist: + raise ValueError (f"Invalid tag: {tags_field}") + + @staticmethod + def assign_tags(obj,data): + """Assign Tags to the created objects""" + + tags = CreateVmForm.get_parse_tags(data) + content_type = ContentType.objects.get_for_model(obj) + for tag in tags: + TaggedItem.objects.create( + tag=tag, + content_type=content_type, + object_id=obj.pk + ) @staticmethod def create_cluster_type(data): """Create and save a ClusterType object.""" - tags = CreateVmForm.get_parse_tags(data) cluster_type = ClusterType( name=data.get('name', ''), slug=data.get('slug', ''), description=data.get('description', ''), - tags=tags ) cluster_type.full_clean() cluster_type.save() + CreateVmForm.assign_tags(cluster_type,data) return cluster_type @staticmethod @@ -253,7 +267,6 @@ class CreateVmForm(NetBoxModelForm): site = Site.objects.get(pk=site_id) except Site.DoesNotExist: raise ValueError(f"Invalid Site ID: {site_id}") - tags = CreateVmForm.get_parse_tags(data) cluster = Cluster( name=data.get('clusters-0-name', ''), @@ -261,10 +274,10 @@ class CreateVmForm(NetBoxModelForm): status=data.get('clusters-0-status', ''), site=site, description=data.get('clusters-0-description', ''), - tags=tags ) cluster.full_clean() cluster.save() + CreateVmForm.assign_tags(cluster,data) return cluster @staticmethod @@ -281,7 +294,6 @@ class CreateVmForm(NetBoxModelForm): platform = Platform.objects.get(pk=platform_id) except Platform.DoesNotExist: raise ValueError(f"Invalid Platform ID: {platform_id}") - tags = CreateVmForm.get_parse_tags(data) vm = VirtualMachine( name=data.get('vms_new-0-name', ''), @@ -291,10 +303,10 @@ class CreateVmForm(NetBoxModelForm): platform=platform, description=data.get('vms_new-0-description', ''), cluster=cluster, - tags=tags ) vm.full_clean() vm.save() + CreateVmForm.assign_tags(vm,data) return vm @staticmethod @@ -306,6 +318,7 @@ class CreateVmForm(NetBoxModelForm): ) vmi.full_clean() vmi.save() + CreateVmForm.assign_tags(vmi,data) return vmi @staticmethod @@ -313,7 +326,6 @@ class CreateVmForm(NetBoxModelForm): """Create and save a Service object.""" ports_field = data.get(f'{prefix}-0-ports', '') ports = CreateVmForm.parse_ports(ports_field) - tags = CreateVmForm.get_parse_tags(data) service = Service( name=data.get(f'{prefix}-0-name', ''), @@ -321,10 +333,10 @@ class CreateVmForm(NetBoxModelForm): ports=ports, description=description, virtual_machine=vm, - tags=tags ) service.full_clean() service.save() + CreateVmForm.assign_tags(service,data) return service def create_all_services(self, data, vm): @@ -341,10 +353,10 @@ class CreateVmForm(NetBoxModelForm): status=data.get('ip_new-status', ''), role=data.get('ip_new-role', ''), assigned_object=vm_interface, - tags=data.get('tag_new-tag', '') ) ip_address.full_clean() ip_address.save() + CreateVmForm.assign_tags(ip_address,data) return ip_address def process_creation(self, data): -- GitLab From bd367838bf3881d6a15f393f185fabbcb22c73ce Mon Sep 17 00:00:00 2001 From: Frederico Sequeira <frederico.sequeira@ext.ec.europa.eu> Date: Tue, 3 Dec 2024 11:31:47 +0000 Subject: [PATCH 24/28] =?UTF-8?q?=F0=9F=90=9B=20Fix=20several=20issues?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- netbox_sys_plugin/forms/createvm.py | 6 ++--- netbox_sys_plugin/navigation.py | 27 ++++++++++--------- netbox_sys_plugin/tables.py | 2 +- .../providercredentials.html | 2 +- 4 files changed, 19 insertions(+), 18 deletions(-) diff --git a/netbox_sys_plugin/forms/createvm.py b/netbox_sys_plugin/forms/createvm.py index f0483a8..9bbfbb4 100644 --- a/netbox_sys_plugin/forms/createvm.py +++ b/netbox_sys_plugin/forms/createvm.py @@ -28,7 +28,7 @@ class ClusterForm(NetBoxModelForm): super().__init__(*args, **kwargs) self.fields.pop('tags',None) -class ClusterFormList(NetBoxModelForm): +class ClusterFormList(NetBoxModelForm, forms.Form): """Form for Cluster List.""" cluster_type = DynamicModelChoiceField( queryset=ClusterType.objects.all(), required=False, label="Provider Type" @@ -147,7 +147,7 @@ TagFormSet = forms.modelformset_factory( # Combined form class CreateVmForm(NetBoxModelForm): - """Combined form for managing ClusterType, Cluster, and VirtualMachine.""" + """Combined form for managing ClusterType, Cluster, and VirtualMachine, etc""" name = forms.CharField(max_length=50, min_length=1, required=False, label="Name") slug = forms.CharField(max_length=100, min_length=1, required=False, label="Slug") @@ -360,7 +360,7 @@ class CreateVmForm(NetBoxModelForm): return ip_address def process_creation(self, data): - """Process creation of ClusterType, Cluster, VM, and related objects.""" + """Object creation""" try: with transaction.atomic(): cluster_exists = self.check_cluster_exist(data) diff --git a/netbox_sys_plugin/navigation.py b/netbox_sys_plugin/navigation.py index 2391977..8a4fa9b 100644 --- a/netbox_sys_plugin/navigation.py +++ b/netbox_sys_plugin/navigation.py @@ -68,37 +68,38 @@ clusterProviderCredentialsItem = [ ] vmMachineItem = [ + PluginMenuItem( + link="plugins:netbox_sys_plugin:virtualmachinetype_list", + link_text="VM Type", + buttons=vm_machine_type_buttons + ), PluginMenuItem( link="plugins:netbox_sys_plugin:vmassignedvirtualmachinetype_list", - link_text="Type Assignment", + link_text="VM Type Assignment", buttons=vm_assigned_machine_type_buttons, ), PluginMenuItem( link="plugins:netbox_sys_plugin:virtualmachinemaintenance_list", - link_text="Maintenance", + link_text="VM Maintenance", buttons=vm_maintenance_buttons, ), -] - -operItem =[ - PluginMenuItem( - link="plugins:netbox_sys_plugin:virtualmachinetype_list", - link_text="Virtual Machine Types", - buttons=vm_machine_type_buttons - ), PluginMenuItem( link="plugins:netbox_sys_plugin:creatvm_add", - link_text="Create Virtual Machine ", + link_text="Create VM", buttons=create_vm_buttons ), ] + +operItem =[ + +] #Menu menu = PluginMenu( label="Sys", groups=( - ("Virtual Machine", (vmMachineItem)), + ("Provider", clusterProviderCredentialsItem), - ("Operation", (operItem)), + ("Virtual Machine", (vmMachineItem)), ), icon_class="mdi mdi-all-inclusive-box-outline", ) \ No newline at end of file diff --git a/netbox_sys_plugin/tables.py b/netbox_sys_plugin/tables.py index b7de3e4..374b9b2 100644 --- a/netbox_sys_plugin/tables.py +++ b/netbox_sys_plugin/tables.py @@ -57,7 +57,7 @@ class ProviderCredentialsTable(NetBoxTable): ) provider_password_vault_path = tables.Column( linkify=False, - verbose_name="Password Vault URL", + verbose_name="Password Vault Path", ) class Meta(NetBoxTable.Meta): diff --git a/netbox_sys_plugin/templates/netbox_sys_plugin/providercredentials.html b/netbox_sys_plugin/templates/netbox_sys_plugin/providercredentials.html index cb91198..f6c63b9 100644 --- a/netbox_sys_plugin/templates/netbox_sys_plugin/providercredentials.html +++ b/netbox_sys_plugin/templates/netbox_sys_plugin/providercredentials.html @@ -16,7 +16,7 @@ Provider Credentials for {{ object.assigned_object }} <td>{{ object.provider_username }}</td> </tr> <tr> - <th scope="row">Password vault URL</th> + <th scope="row">Password vault Path</th> <td>{{ object.provider_password_vault_path }}</td> </tr> </table> -- GitLab From 5b91279442d3ca48717d45a953bf8ebf437ce7a7 Mon Sep 17 00:00:00 2001 From: Frederico Sequeira <frederico.sequeira@ext.ec.europa.eu> Date: Tue, 3 Dec 2024 16:23:06 +0000 Subject: [PATCH 25/28] =?UTF-8?q?=F0=9F=A6=BA=20Add=20validation=20to=20IP?= =?UTF-8?q?=20address?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- netbox_sys_plugin/forms/createvm.py | 15 +++++++++++++-- .../templates/netbox_sys_plugin/create_vm.html | 16 +++++++++++++--- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/netbox_sys_plugin/forms/createvm.py b/netbox_sys_plugin/forms/createvm.py index 9bbfbb4..89293df 100644 --- a/netbox_sys_plugin/forms/createvm.py +++ b/netbox_sys_plugin/forms/createvm.py @@ -8,6 +8,7 @@ from virtualization.models import ClusterType, Cluster, VirtualMachine, VMInterf from ipam.models import IPAddress, Service from dcim.models import DeviceRole,Site, Platform from extras.models import Tag, TaggedItem +import netaddr @@ -57,7 +58,7 @@ class VirtualMachineForm(NetBoxModelForm): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.fields.pop('tags',None) - + class VirtualMachineInterfaceForm(NetBoxModelForm): """Form for Virtual Machine Interfaces.""" @@ -125,9 +126,11 @@ VirtualMachineInterfaceFormSet = inlineformset_factory( ServiceNtpFormSet = inlineformset_factory( VirtualMachine, Service, form=ServiceForm, extra=1, can_delete=False ) + ServiceDnsFormSet = inlineformset_factory( VirtualMachine, Service, form=ServiceForm, extra=1, can_delete=False ) + ServiceSyslogFormSet = inlineformset_factory( VirtualMachine, Service, form=ServiceForm, extra=1, can_delete=False ) @@ -348,8 +351,16 @@ class CreateVmForm(NetBoxModelForm): @staticmethod def create_ip_address(data, vm_interface): """Create and save an IPAddress object.""" + ipaddress_raw=data.get('ip_new-address', '').strip() + if not ipaddress_raw: + raise ValueError("IP Address cannot be empty") + try: + ipaddress_raw = netaddr.IPNetwork(ipaddress_raw) + except: + raise ValueError("Invalid IP Address") + ip_address = IPAddress( - address=data.get('ip_new-address', ''), + address=ipaddress_raw, status=data.get('ip_new-status', ''), role=data.get('ip_new-role', ''), assigned_object=vm_interface, diff --git a/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html b/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html index ad1def9..2c04cd5 100644 --- a/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html +++ b/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html @@ -1,25 +1,31 @@ {% extends 'base/layout.html' %} {% load buttons %} {% load custom_links %} +{% load form_helpers %} {% load perms %} {% load plugins %} {% load tabs %} +{% load static %} {% load i18n %} - - {% block title %} SYS - Virtual Machine {% endblock title %} + {% block tabs %} <ul class="nav nav-tabs px-3"> <li class="nav-item" role="presentation"> <button class="nav-link active" id="edit-form-tab" data-bs-toggle="tab" data-bs-target="#edit-form" type="button" role="tab" aria-controls="edit-form" aria-selected="true"> - Create </button> + Create</button> </li> </ul> {% endblock tabs %} {% block content-wrapper %} + <style> + .errorlist.nonfield { + display: none; + } + </style> <div class="tab-content"> <div class="tab-pane show active" id="edit-form" role="tabpanel" aria-labelledby="object-list-tab"> {% block content %} @@ -73,6 +79,10 @@ SYS - Virtual Machine {% for vm_form in vm_formset %} <div class="form-group" class="field-group mb-5"> {{ vm_form.as_p }} + {% if vm_form.non_field_errors %} + <h1> {{ vm_form.non_field_errors }}</h1> + {% endif %} + </div> {% endfor %} {% endfor %} -- GitLab From 8d031f65d1cb3f091021becdfe0de1adc9835a74 Mon Sep 17 00:00:00 2001 From: Frederico Sequeira <frederico.sequeira@ext.ec.europa.eu> Date: Wed, 4 Dec 2024 10:15:06 +0000 Subject: [PATCH 26/28] =?UTF-8?q?=F0=9F=90=9B=20Fix=20bug=20on=20filter=20?= =?UTF-8?q?for=20dropbox?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- netbox_sys_plugin/forms/createvm.py | 2 +- .../templates/netbox_sys_plugin/create_vm.html | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/netbox_sys_plugin/forms/createvm.py b/netbox_sys_plugin/forms/createvm.py index 89293df..557806e 100644 --- a/netbox_sys_plugin/forms/createvm.py +++ b/netbox_sys_plugin/forms/createvm.py @@ -37,7 +37,7 @@ class ClusterFormList(NetBoxModelForm, forms.Form): cluster = DynamicModelChoiceField( queryset=Cluster.objects.all(), required=False, label="Provider", - query_params={'type': '$cluster_type',}, + query_params={'type_id': '$cl_list_new-cluster_type',}, ) class Meta: diff --git a/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html b/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html index 2c04cd5..d236c57 100644 --- a/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html +++ b/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html @@ -26,6 +26,11 @@ SYS - Virtual Machine display: none; } </style> + <script + type="text/javascript" + src="{% static 'netbox.js' %}?v={{ settings.VERSION }}" + onerror="window.location='{% url 'media_failure' %}?filename=netbox.js'"> + </script> <div class="tab-content"> <div class="tab-pane show active" id="edit-form" role="tabpanel" aria-labelledby="object-list-tab"> {% block content %} @@ -151,5 +156,9 @@ SYS - Virtual Machine </div> {% endblock %} {% endblock content-wrapper %} +{% block modals %} + {% include 'inc/htmx_modal.html' %} +{% endblock modals %} +{% block javascript %}{% endblock %} \ No newline at end of file -- GitLab From 14828e9d5b521eb6a65750a57d989ba873f31422 Mon Sep 17 00:00:00 2001 From: Frederico Sequeira <frederico.sequeira@ext.ec.europa.eu> Date: Wed, 4 Dec 2024 17:29:56 +0000 Subject: [PATCH 27/28] =?UTF-8?q?=F0=9F=92=84=20removed=20site=20from=20VM?= =?UTF-8?q?,=20added=20gatewat=20IP?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 +-- netbox_sys_plugin/forms/createvm.py | 36 +++++++++++++------ .../netbox_sys_plugin/create_vm.html | 8 +++++ 3 files changed, 35 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 316aac8..9551765 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# SYS Address Plugin for Netbox +# SYS Plugin for Netbox -A Netbox plugin for Certificate management. +A Netbox plugin for Create Virtual Machine Process. ## Installation diff --git a/netbox_sys_plugin/forms/createvm.py b/netbox_sys_plugin/forms/createvm.py index 557806e..6f96ce9 100644 --- a/netbox_sys_plugin/forms/createvm.py +++ b/netbox_sys_plugin/forms/createvm.py @@ -23,7 +23,7 @@ class ClusterForm(NetBoxModelForm): class Meta: model = Cluster - fields = ('name', 'type', 'description','status','tags') + fields = ('name', 'type', 'description','site','status','tags') def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -53,7 +53,7 @@ class VirtualMachineForm(NetBoxModelForm): class Meta: model = VirtualMachine - fields = ('name', 'cluster', 'status','site','role','platform', 'description','tags') + fields = ('name', 'cluster', 'status','role','platform', 'description','tags') def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -88,7 +88,7 @@ class IPAddressForm(NetBoxModelForm): """Form for IP Addresses.""" class Meta: model = IPAddress - fields = ('address', 'status', 'role', 'tags') + fields = ('address', 'status', 'tags') def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -193,6 +193,10 @@ class CreateVmForm(NetBoxModelForm): self.ip_formsets = [] empty_ip_formset = IPAddressFormSet(data=data, prefix='ip_new') self.ip_formsets.append(('new', empty_ip_formset)) + # IP Address Gateway + self.gateway_formsets = [] + empty_gateway_formset = IPAddressFormSet(data=data, prefix='gateway_new') + self.gateway_formsets.append(('new', empty_gateway_formset)) # List Cluster self.cl_list_formsets = [] empty_cl_list_formset = ClusterListFormSet(data=data, prefix='cl_list_new') @@ -256,6 +260,7 @@ class CreateVmForm(NetBoxModelForm): name=data.get('name', ''), slug=data.get('slug', ''), description=data.get('description', ''), + tags = CreateVmForm.get_parse_tags(data) ) cluster_type.full_clean() cluster_type.save() @@ -265,7 +270,7 @@ class CreateVmForm(NetBoxModelForm): @staticmethod def create_cluster(data, cluster_type): """Create and save a Cluster object.""" - site_id = data.get('vms_new-0-role', '') + site_id = data.get('clusters-0-site', '') try: site = Site.objects.get(pk=site_id) except Site.DoesNotExist: @@ -277,6 +282,7 @@ class CreateVmForm(NetBoxModelForm): status=data.get('clusters-0-status', ''), site=site, description=data.get('clusters-0-description', ''), + tags = CreateVmForm.get_parse_tags(data) ) cluster.full_clean() cluster.save() @@ -302,10 +308,10 @@ class CreateVmForm(NetBoxModelForm): name=data.get('vms_new-0-name', ''), status=data.get('vms_new-0-status', ''), role=role, - site=cluster.site, platform=platform, description=data.get('vms_new-0-description', ''), cluster=cluster, + tags = CreateVmForm.get_parse_tags(data) ) vm.full_clean() vm.save() @@ -317,7 +323,8 @@ class CreateVmForm(NetBoxModelForm): """Create and save a VMInterface object.""" vmi = VMInterface( name=data.get('vmis_new-0-name', ''), - virtual_machine=vm + virtual_machine=vm, + tags = CreateVmForm.get_parse_tags(data) ) vmi.full_clean() vmi.save() @@ -336,6 +343,7 @@ class CreateVmForm(NetBoxModelForm): ports=ports, description=description, virtual_machine=vm, + tags = CreateVmForm.get_parse_tags(data) ) service.full_clean() service.save() @@ -349,9 +357,9 @@ class CreateVmForm(NetBoxModelForm): self.create_service(data, vm, 'service_syslog', 'SYSLOG Server') @staticmethod - def create_ip_address(data, vm_interface): + def create_ip_address(data, vm_interface, prefix, description): """Create and save an IPAddress object.""" - ipaddress_raw=data.get('ip_new-address', '').strip() + ipaddress_raw=data.get(f'{prefix}-address', '').strip() if not ipaddress_raw: raise ValueError("IP Address cannot be empty") try: @@ -361,14 +369,20 @@ class CreateVmForm(NetBoxModelForm): ip_address = IPAddress( address=ipaddress_raw, - status=data.get('ip_new-status', ''), - role=data.get('ip_new-role', ''), + status=data.get(f'{prefix}-status', ''), assigned_object=vm_interface, + tags = CreateVmForm.get_parse_tags(data), + description=description ) ip_address.full_clean() ip_address.save() CreateVmForm.assign_tags(ip_address,data) return ip_address + + + def create_all_ip_adresses(self, data, vm_interface,): + self.create_ip_address(data,vm_interface,'ip_new','IP Address') + self.create_ip_address(data,vm_interface,'gateway_new','Gateway') def process_creation(self, data): """Object creation""" @@ -384,7 +398,7 @@ class CreateVmForm(NetBoxModelForm): vm = self.create_virtual_machine(data, cluster, cluster_exists) vmi = self.create_vm_interface(data, vm) self.create_all_services(data, vm) - self.create_ip_address(data, vmi) + self.create_all_ip_adresses(data, vmi) return vm except ValueError as e: diff --git a/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html b/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html index d236c57..d1d14b2 100644 --- a/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html +++ b/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html @@ -109,6 +109,14 @@ SYS - Virtual Machine </div> {% endfor %} {% endfor %} + <h5>Gateway</h5> + {% for gateway_formset in form.gateway_formsets %} + {% for gateway_form in gateway_formset %} + <div class="form-group" class="field-group mb-5"> + {{ gateway_form.as_p }} + </div> + {% endfor %} + {% endfor %} </div> <div class="field-group my-5"> <h5>Dependencies</h5> -- GitLab From 3ce1fab46c893b2d396987a6218cd56abee47c56 Mon Sep 17 00:00:00 2001 From: Frederico Sequeira <frederico.sequeira@ext.ec.europa.eu> Date: Thu, 5 Dec 2024 09:17:08 +0000 Subject: [PATCH 28/28] =?UTF-8?q?=F0=9F=9B=82=20Fix=20permissions=20for=20?= =?UTF-8?q?menu=20items?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- netbox_sys_plugin/navigation.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/netbox_sys_plugin/navigation.py b/netbox_sys_plugin/navigation.py index 8a4fa9b..bd6fefc 100644 --- a/netbox_sys_plugin/navigation.py +++ b/netbox_sys_plugin/navigation.py @@ -64,6 +64,7 @@ clusterProviderCredentialsItem = [ link="plugins:netbox_sys_plugin:providercredentials_list", link_text="Credentials", buttons=cluster_provider_credentials_buttons, + permissions=["netbox_sys_plugin.list_providercredentials"], ), ] @@ -71,22 +72,26 @@ vmMachineItem = [ PluginMenuItem( link="plugins:netbox_sys_plugin:virtualmachinetype_list", link_text="VM Type", - buttons=vm_machine_type_buttons + buttons=vm_machine_type_buttons, + permissions=["netbox_sys_plugin.list_vmmachinetype"], ), PluginMenuItem( link="plugins:netbox_sys_plugin:vmassignedvirtualmachinetype_list", link_text="VM Type Assignment", buttons=vm_assigned_machine_type_buttons, + permissions=["netbox_sys_plugin.list_vmassignedmachinetype"], ), PluginMenuItem( link="plugins:netbox_sys_plugin:virtualmachinemaintenance_list", link_text="VM Maintenance", buttons=vm_maintenance_buttons, + permissions=["netbox_sys_plugin.list_maintenance"], ), PluginMenuItem( link="plugins:netbox_sys_plugin:creatvm_add", link_text="Create VM", - buttons=create_vm_buttons + buttons=create_vm_buttons, + permissions=["netbox_sys_plugin.add_createvm"], ), ] -- GitLab