From 0de8b0785f324b9bc24e0ba688f4f810aaa9d120 Mon Sep 17 00:00:00 2001 From: Frederico Sequeira <frederico.sequeira@ext.ec.europa.eu> Date: Mon, 23 Dec 2024 11:31:41 +0000 Subject: [PATCH 1/9] =?UTF-8?q?=F0=9F=93=9D=20Add=20extra=20configuration?= =?UTF-8?q?=20data=20model=20&=20general=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/model/netbox_sys_provider_credentials.md | 2 +- docs/model/netbox_sys_provider_type_extra_config.md | 10 ++++++++++ docs/model/netbox_sys_vm_assigned_extra_config.md | 10 ++++++++++ .../netbox_sys_vm_assigned_virtual_machine_type.md | 4 ++-- netbox_sys_plugin/tables.py | 6 +++--- 5 files changed, 26 insertions(+), 6 deletions(-) create mode 100644 docs/model/netbox_sys_provider_type_extra_config.md create mode 100644 docs/model/netbox_sys_vm_assigned_extra_config.md diff --git a/docs/model/netbox_sys_provider_credentials.md b/docs/model/netbox_sys_provider_credentials.md index 0876ed0..8c3b9ec 100644 --- a/docs/model/netbox_sys_provider_credentials.md +++ b/docs/model/netbox_sys_provider_credentials.md @@ -1,6 +1,6 @@ ## netbox_sys_provider_credentials -**Mapping Redirect definition class** +**Provider Credentials definition class** | FIELD | TYPE | DESCRIPTION | |-------------------------------|-----------------------------------|---------------------------------------------------------------------------| diff --git a/docs/model/netbox_sys_provider_type_extra_config.md b/docs/model/netbox_sys_provider_type_extra_config.md new file mode 100644 index 0000000..bcfdf9c --- /dev/null +++ b/docs/model/netbox_sys_provider_type_extra_config.md @@ -0,0 +1,10 @@ +## netbox_sys_provider_type_extra_config + +**Provider Type Extra Configurations definition class** + +| FIELD | TYPE | DESCRIPTION | +|-------------------------------|-----------------------------------|---------------------------------------------------------------------------| +| id | Big (8 byte) integer | Unique ID | +| assigned_object_type_id | Big (8 byte) integer | cluster type type ID | +| assigned_object_id | Big (8 byte) integer | cluster type unique ID | +| network_extra_config | JSON | Provider type network extra configurations | \ No newline at end of file diff --git a/docs/model/netbox_sys_vm_assigned_extra_config.md b/docs/model/netbox_sys_vm_assigned_extra_config.md new file mode 100644 index 0000000..a0d232a --- /dev/null +++ b/docs/model/netbox_sys_vm_assigned_extra_config.md @@ -0,0 +1,10 @@ +## netbox_sys_vm_assigned_provider_type_extra_config + +**VM Assigned Provider Type Extra Configuration definition class** + +| FIELD | TYPE | DESCRIPTION | +|------------------------------- |----------------------------------|---------------------------------------------------------------------------| +| id | Big (8 byte) integer | Unique ID | +| assigned_object_type_id | Big (8 byte) integer | Virtual Machine type type ID | +| assigned_object_id | Big (8 byte) integer | Virtual Machine unique ID | +| extra_config | JSON | Json with extra configuration with the structure from provider type | \ No newline at end of file diff --git a/docs/model/netbox_sys_vm_assigned_virtual_machine_type.md b/docs/model/netbox_sys_vm_assigned_virtual_machine_type.md index 791b6da..5bbd583 100644 --- a/docs/model/netbox_sys_vm_assigned_virtual_machine_type.md +++ b/docs/model/netbox_sys_vm_assigned_virtual_machine_type.md @@ -1,6 +1,6 @@ -## netbox_sys_virtual_machine_type +## netbox_sys_vm_assigned_virtual_machine_type -**Mapping Redirect definition class** +**VM assigned Virtual Machine Type definition class** | FIELD | TYPE | DESCRIPTION | |------------------------------- |-----------------------------------|---------------------------------------------------------------------------| diff --git a/netbox_sys_plugin/tables.py b/netbox_sys_plugin/tables.py index cac807e..6c1ca84 100644 --- a/netbox_sys_plugin/tables.py +++ b/netbox_sys_plugin/tables.py @@ -2,7 +2,7 @@ import django_tables2 as tables from netbox.tables import NetBoxTable, columns -from .models import VirtualMachineMaintenance, ProviderCredentials, VmAssignedVirtualMachineType, VirtualMachineType, WebhookSettings +from .models import VirtualMachineMaintenance, ProviderCredentials, VmAssignedVirtualMachineType, VirtualMachineType, WebhookSettings, DomainNames #VmMaintenance class VmMaintenanceTable(NetBoxTable): @@ -148,7 +148,7 @@ class VmTypeTable(NetBoxTable): #DomainNames class DomainNamesTable(NetBoxTable): - """VM Maintenance Table definition class""" + """Domain Names Table definition class""" pk = columns.ToggleColumn() id = tables.Column( @@ -168,7 +168,7 @@ class DomainNamesTable(NetBoxTable): class Meta(NetBoxTable.Meta): """Meta class""" - model = VirtualMachineType + model = DomainNames fields = ( "pk", "id", -- GitLab From b23c5d06648f09c705700c2dfb279ca5d6c52c7f Mon Sep 17 00:00:00 2001 From: Frederico Sequeira <frederico.sequeira@ext.ec.europa.eu> Date: Tue, 24 Dec 2024 11:45:06 +0000 Subject: [PATCH 2/9] =?UTF-8?q?=E2=9C=A8=20Add=20necessary=20objects=20to?= =?UTF-8?q?=20manage=20extra=20configs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../netbox_sys_provider_type_extra_config.md | 2 +- docs/model/netbox_sys_virtual_machine_type.md | 2 +- netbox_sys_plugin/api/serializers.py | 11 ++- netbox_sys_plugin/api/urls.py | 1 + netbox_sys_plugin/api/views.py | 10 ++- netbox_sys_plugin/filtersets.py | 28 ++++++- netbox_sys_plugin/forms/provider.py | 82 ++++++++++++++++++- .../0010_providertypeextraconfig.py | 37 +++++++++ ...ypeextraconfig_extra_config_description.py | 18 ++++ netbox_sys_plugin/models/provider.py | 51 +++++++++++- netbox_sys_plugin/navigation.py | 20 ++++- netbox_sys_plugin/tables.py | 32 +++++++- netbox_sys_plugin/template_content.py | 3 + .../clustertype_providertypeextraconfig.html | 39 +++++++++ .../providertypeextraconfig.html | 48 +++++++++++ netbox_sys_plugin/urls.py | 9 +- netbox_sys_plugin/views.py | 29 ++++++- 17 files changed, 404 insertions(+), 18 deletions(-) create mode 100644 netbox_sys_plugin/migrations/0010_providertypeextraconfig.py create mode 100644 netbox_sys_plugin/migrations/0011_providertypeextraconfig_extra_config_description.py create mode 100644 netbox_sys_plugin/templates/netbox_sys_plugin/clustertype_providertypeextraconfig.html create mode 100644 netbox_sys_plugin/templates/netbox_sys_plugin/providertypeextraconfig.html diff --git a/docs/model/netbox_sys_provider_type_extra_config.md b/docs/model/netbox_sys_provider_type_extra_config.md index bcfdf9c..7b7d3ce 100644 --- a/docs/model/netbox_sys_provider_type_extra_config.md +++ b/docs/model/netbox_sys_provider_type_extra_config.md @@ -7,4 +7,4 @@ | id | Big (8 byte) integer | Unique ID | | assigned_object_type_id | Big (8 byte) integer | cluster type type ID | | assigned_object_id | Big (8 byte) integer | cluster type unique ID | -| network_extra_config | JSON | Provider type network extra configurations | \ No newline at end of file +| extra_config | JSON | Provider type extra configurations | \ No newline at end of file diff --git a/docs/model/netbox_sys_virtual_machine_type.md b/docs/model/netbox_sys_virtual_machine_type.md index 3fe0881..4881755 100644 --- a/docs/model/netbox_sys_virtual_machine_type.md +++ b/docs/model/netbox_sys_virtual_machine_type.md @@ -1,6 +1,6 @@ ## netbox_sys_virtual_machine_type -**Mapping Redirect definition class** +**Virtual Machine Type definition class** | FIELD | TYPE | DESCRIPTION | |-------------------------------|-----------------------------------|---------------------------------------------------------------------------| diff --git a/netbox_sys_plugin/api/serializers.py b/netbox_sys_plugin/api/serializers.py index a09b18a..2e0a433 100644 --- a/netbox_sys_plugin/api/serializers.py +++ b/netbox_sys_plugin/api/serializers.py @@ -8,7 +8,7 @@ from dcim.api.nested_serializers import ( from ipam.api.nested_serializers import ( NestedIPAddressSerializer, NestedServiceSerializer ) -from .. models import VirtualMachineMaintenance, ProviderCredentials,VmAssignedVirtualMachineType, VirtualMachineType,DomainNames, WebhookSettings +from .. models import VirtualMachineMaintenance, ProviderCredentials,VmAssignedVirtualMachineType, VirtualMachineType,DomainNames, WebhookSettings,ProviderTypeExtraConfig from django.contrib.contenttypes.models import ContentType from .nested_serializers import * from virtualization.choices import * @@ -102,6 +102,15 @@ class WebhookSettingsSerializer(NetBoxModelSerializer): model = WebhookSettings fields = '__all__' +class ProviderTypeExtraConfigSerializer(NetBoxModelSerializer): + id = serializers.IntegerField(read_only=True) + extra_config = serializers.JSONField() + + class Meta: + model = ProviderTypeExtraConfig + fields = '__all__' + + class VirtualMachineSerializer(NetBoxModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='virtualization-api:virtualmachine-detail') diff --git a/netbox_sys_plugin/api/urls.py b/netbox_sys_plugin/api/urls.py index 3c43e5e..780e6ee 100644 --- a/netbox_sys_plugin/api/urls.py +++ b/netbox_sys_plugin/api/urls.py @@ -8,6 +8,7 @@ router.register(r'AssignedVmType', VmAssignedVirtualMachineTypeViewSet) router.register(r'VmType', VirtualMachineTypeViewSet) router.register(r'DomainNames', DomainNamesViewSet) router.register(r'WebhookSettings', WebhookSettingsViewSet) +router.register(r'ProviderTypeExtraConfig', ProviderTypeExtraConfigViewSet) router.register(r'VirtualMachine', VirtualMachineViewSet) app_name = "netbox_sys_plugin" diff --git a/netbox_sys_plugin/api/views.py b/netbox_sys_plugin/api/views.py index ed9e993..f7b6604 100644 --- a/netbox_sys_plugin/api/views.py +++ b/netbox_sys_plugin/api/views.py @@ -4,8 +4,8 @@ from rest_framework.response import Response from django.db.models import Prefetch from netbox.api.viewsets import NetBoxModelViewSet from virtualization import filtersets -from .. filtersets import VmTypeFilterSet -from .. models import VirtualMachineMaintenance, ProviderCredentials, VmAssignedVirtualMachineType, VirtualMachineType,DomainNames, WebhookSettings +from .. filtersets import VmTypeFilterSet, ProviderTypeExtraConfigFilterSet +from .. models import VirtualMachineMaintenance, ProviderCredentials, VmAssignedVirtualMachineType, VirtualMachineType,DomainNames, WebhookSettings, ProviderTypeExtraConfig from . serializers import * class VirtualMachineMaintenanceViewSet(viewsets.ModelViewSet): @@ -36,6 +36,12 @@ class VirtualMachineTypeViewSet(viewsets.ModelViewSet): filterset_class = VmTypeFilterSet http_method_names = ["get", "post", "patch", "delete", "options"] +class ProviderTypeExtraConfigViewSet(viewsets.ModelViewSet): + queryset = ProviderTypeExtraConfig.objects.all() + serializer_class = ProviderTypeExtraConfigSerializer + filterset_class = ProviderTypeExtraConfigFilterSet + http_method_names = ["get", "post", "patch", "delete", "options"] + class VirtualMachineViewSet(NetBoxModelViewSet): queryset = VirtualMachine.objects.prefetch_related( 'site', 'cluster', 'device', 'role', 'tenant', 'platform', 'primary_ip4', 'primary_ip6', 'tags', 'services' diff --git a/netbox_sys_plugin/filtersets.py b/netbox_sys_plugin/filtersets.py index 5b44013..d33d8af 100644 --- a/netbox_sys_plugin/filtersets.py +++ b/netbox_sys_plugin/filtersets.py @@ -4,7 +4,7 @@ import django_filters from netbox.filtersets import NetBoxModelFilterSet from virtualization.models import VirtualMachine, Cluster, ClusterType from django.db.models import Q -from .models import VirtualMachineMaintenance, ProviderCredentials, VmAssignedVirtualMachineType, VirtualMachineType +from .models import VirtualMachineMaintenance, ProviderCredentials, VmAssignedVirtualMachineType, VirtualMachineType, ProviderTypeExtraConfig class VmMaintenanceFilterSet(NetBoxModelFilterSet): @@ -100,4 +100,28 @@ class ProviderCredentialsFilterSet(NetBoxModelFilterSet): """override""" if not value.strip(): return queryset - return queryset.filter(Q(provider_username__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)) + +class ProviderTypeExtraConfigFilterSet(NetBoxModelFilterSet): + """Provider Type Extra Config filterset definition class""" + + cluster_type_id = django_filters.ModelMultipleChoiceFilter( + field_name="ClusterType__id", + queryset=ClusterType.objects.all(), + to_field_name="id", + label="Cluster Type (id)", + ) + + class Meta: + """Meta class""" + model = ProviderTypeExtraConfig + fields = ('extra_config_description','cluster_type_id') + + # pylint: disable=W0613 + def search(self, queryset, name, value): + """override""" + if not value.strip(): + return queryset + return queryset.filter(Q(extra_config_description__icontains=value)) + + diff --git a/netbox_sys_plugin/forms/provider.py b/netbox_sys_plugin/forms/provider.py index 1823cb1..fef9a91 100644 --- a/netbox_sys_plugin/forms/provider.py +++ b/netbox_sys_plugin/forms/provider.py @@ -1,6 +1,7 @@ """Forms definitions""" -from virtualization.models import Cluster +import json +from virtualization.models import Cluster,ClusterType from django.contrib.contenttypes.models import ContentType from django.utils.translation import gettext_lazy as _ from django.core.exceptions import ValidationError @@ -9,8 +10,8 @@ from netbox.forms import ( NetBoxModelForm, NetBoxModelFilterSetForm, ) -from utilities.forms.fields import DynamicModelChoiceField -from ..models import ProviderCredentials +from utilities.forms.fields import DynamicModelChoiceField, JSONField +from ..models import ProviderCredentials, ProviderTypeExtraConfig class ProviderCredentialsForm(NetBoxModelForm): """ @@ -82,4 +83,77 @@ class ProviderCredentialsForm(NetBoxModelForm): class ProviderCredentialsFilterForm(NetBoxModelFilterSetForm): """Provider Credentials filter form definition class""" - model = ProviderCredentials \ No newline at end of file + model = ProviderCredentials + +class ProviderTypeExtraConfigForm(NetBoxModelForm): + """ + GUI form to add or edit a Provider Type Extra Configs. + """ + + cluster_type = DynamicModelChoiceField( + queryset=ClusterType.objects.all(), required=True, label="Provider Type" + ) + + extra_config = JSONField( + label=_('Extra Config'), + required=True + ) + + + def __init__(self, *args, **kwargs): + # Initialize helper selectors + instance = kwargs.get("instance") + initial = kwargs.get("initial", {}).copy() + if instance: + if isinstance(instance.assigned_object, ClusterType): + initial["cluster_type"] = instance.assigned_object + kwargs["initial"] = initial + super().__init__(*args, **kwargs) + + if initial and 'extra_config' in initial: + if type(initial['extra_config']) is str: + initial['extra_config'] = json.loads(initial['extra_config']) + + if instance: + current_cluster_type_id = instance.assigned_object.id if instance.assigned_object else None + else: + current_cluster_type_id = None + + assigned_cluster_types = ProviderTypeExtraConfig.objects.filter( + assigned_object_type=ContentType.objects.get_for_model(ClusterType) + ).exclude(assigned_object_id=current_cluster_type_id).values_list('assigned_object_id', flat=True) + self.fields['cluster_type'].ClusterType = ClusterType.objects.exclude(id__in=assigned_cluster_types) + + class Meta: + """Meta class""" + model = ProviderTypeExtraConfig + fields = ('cluster_type', 'extra_config', 'extra_config_description','tags') + + + def clean(self): + """ + Validates form inputs before submitting: + """ + super().clean() + + cluster_type = self.cleaned_data.get("cluster_type") + + #Check if cluster is assigned corretly + if (not cluster_type): + raise ValidationError( + {"__all__": "Can't assign more than one Configuration to the same Provider Type"}, + ) + + if self.errors.get(f"cluster_type"): + return + def save(self, *args, **kwargs): + # Set assigned object + self.instance.assigned_object = ( + self.cleaned_data.get("cluster_type") + ) + return super().save(*args, **kwargs) + +class ProviderTypeExtraConfigFilterForm(NetBoxModelFilterSetForm): + """Provider Credentials filter form definition class""" + + model = ProviderTypeExtraConfig \ No newline at end of file diff --git a/netbox_sys_plugin/migrations/0010_providertypeextraconfig.py b/netbox_sys_plugin/migrations/0010_providertypeextraconfig.py new file mode 100644 index 0000000..c2b159c --- /dev/null +++ b/netbox_sys_plugin/migrations/0010_providertypeextraconfig.py @@ -0,0 +1,37 @@ +# Generated by Django 4.2.16 on 2024-12-23 14:46 + +from django.db import migrations, models +import django.db.models.deletion +import taggit.managers +import utilities.json + + +class Migration(migrations.Migration): + + dependencies = [ + ('extras', '0098_webhook_custom_field_data_webhook_tags'), + ('contenttypes', '0002_remove_content_type_name'), + ('netbox_sys_plugin', '0009_alter_domainnames_assigned_object_type_and_more'), + ] + + operations = [ + migrations.CreateModel( + name='ProviderTypeExtraConfig', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False)), + ('created', models.DateTimeField(auto_now_add=True, null=True)), + ('last_updated', models.DateTimeField(auto_now=True, null=True)), + ('custom_field_data', models.JSONField(blank=True, default=dict, encoder=utilities.json.CustomFieldJSONEncoder)), + ('extra_config', models.JSONField(blank=True, null=True)), + ('assigned_object_id', models.PositiveBigIntegerField(blank=True, null=True)), + ('assigned_object_type', models.ForeignKey(blank=True, limit_choices_to=models.Q(models.Q(('app_label', 'virtualization'), ('model', 'ClusterType'))), null=True, on_delete=django.db.models.deletion.PROTECT, to='contenttypes.contenttype')), + ('tags', taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag')), + ], + options={ + 'verbose_name': 'Provider Extra Configuration', + 'verbose_name_plural': 'Provider Extra Configurations', + 'ordering': ['assigned_object_id'], + 'unique_together': {('assigned_object_id',)}, + }, + ), + ] diff --git a/netbox_sys_plugin/migrations/0011_providertypeextraconfig_extra_config_description.py b/netbox_sys_plugin/migrations/0011_providertypeextraconfig_extra_config_description.py new file mode 100644 index 0000000..c5819cb --- /dev/null +++ b/netbox_sys_plugin/migrations/0011_providertypeextraconfig_extra_config_description.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.16 on 2024-12-23 15:25 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('netbox_sys_plugin', '0010_providertypeextraconfig'), + ] + + operations = [ + migrations.AddField( + model_name='providertypeextraconfig', + name='extra_config_description', + field=models.CharField(default=None, max_length=100), + ), + ] diff --git a/netbox_sys_plugin/models/provider.py b/netbox_sys_plugin/models/provider.py index 61a33cb..fd8b289 100644 --- a/netbox_sys_plugin/models/provider.py +++ b/netbox_sys_plugin/models/provider.py @@ -11,10 +11,11 @@ from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelatio from django.contrib.contenttypes.models import ContentType from django.db import models from django.db.models import Q -from virtualization.models import Cluster +from virtualization.models import Cluster, ClusterType from netbox.models import NetBoxModel CLUSTER_ASSIGNMENT_MODELS = models.Q(models.Q(app_label="virtualization", model="Cluster")) +CLUSTER_TYPE_ASSIGNMENT_MODELS = models.Q(models.Q(app_label="virtualization", model="ClusterType")) @@ -52,7 +53,7 @@ class ProviderCredentials(NetBoxModel): def __str__(self): - return f"for Cluster ID {self.assigned_object_id}" + return f"for Provider ID {self.assigned_object_id}" def get_absolute_url(self): """override""" @@ -65,3 +66,49 @@ GenericRelation( related_query_name="Cluster", ).contribute_to_class(Cluster, "ProviderCredentials") + +class ProviderTypeExtraConfig(NetBoxModel): + """Cluster Type Extra configurations definition class""" + + extra_config = models.JSONField( + blank=True, + null=True, + ) + extra_config_description = models.CharField( + default=None, blank=False, null=False, + max_length=100) + + assigned_object_type = models.ForeignKey( + to=ContentType, + limit_choices_to=CLUSTER_TYPE_ASSIGNMENT_MODELS, + on_delete=models.PROTECT, + null=True, + blank=True, + ) + assigned_object_id = models.PositiveBigIntegerField(null=True, blank=True) + assigned_object = GenericForeignKey( + ct_field="assigned_object_type", + fk_field="assigned_object_id", + ) + + class Meta: + """Meta class""" + unique_together = ["assigned_object_id"] + ordering = ["assigned_object_id"] + verbose_name = "Provider Extra Configuration" + verbose_name_plural = "Provider Extra Configurations" + + + def __str__(self): + return f"for Provider Type ID {self.assigned_object_id}" + + def get_absolute_url(self): + """override""" + return reverse("plugins:netbox_sys_plugin:providertypeextraconfig", args=[self.pk]) + +GenericRelation( + to=ProviderTypeExtraConfig, + content_type_field="assigned_object_type", + object_id_field="assigned_object_id", + related_query_name="ClusterType", +).contribute_to_class(ClusterType, "ProviderTypeExtraConfig") \ No newline at end of file diff --git a/netbox_sys_plugin/navigation.py b/netbox_sys_plugin/navigation.py index e92953a..73d299c 100644 --- a/netbox_sys_plugin/navigation.py +++ b/netbox_sys_plugin/navigation.py @@ -27,6 +27,16 @@ cluster_provider_credentials_buttons = [ ), ] +cluster_type_extra_config_buttons = [ + PluginMenuButton( + link="plugins:netbox_sys_plugin:providertypeextraconfig_add", + title="Add", + icon_class="mdi mdi-plus-thick", + color=ButtonColorChoices.GREEN, + permissions=["netbox_sys_plugin.add_providertypeextraconfig"], + ), +] + vm_assigned_machine_type_buttons = [ PluginMenuButton( link="plugins:netbox_sys_plugin:vmassignedvirtualmachinetype_add", @@ -61,13 +71,19 @@ create_domainnames_buttons = [ #Items -clusterProviderCredentialsItem = [ +clusterClusterTypeItem = [ PluginMenuItem( link="plugins:netbox_sys_plugin:providercredentials_list", link_text="Credentials", buttons=cluster_provider_credentials_buttons, permissions=["netbox_sys_plugin.list_providercredentials"], ), + PluginMenuItem( + link="plugins:netbox_sys_plugin:providertypeextraconfig_list", + link_text="Extra Config", + buttons=cluster_type_extra_config_buttons, + permissions=["netbox_sys_plugin.list_providertypeextraconfig"], + ), ] vmMachineItem = [ @@ -114,7 +130,7 @@ menu = PluginMenu( label="System Squad", groups=( - ("Provider", clusterProviderCredentialsItem), + ("Provider", clusterClusterTypeItem), ("Virtual Machine", (vmMachineItem)), ("Operation", (operItem)), ), diff --git a/netbox_sys_plugin/tables.py b/netbox_sys_plugin/tables.py index 6c1ca84..2554bcb 100644 --- a/netbox_sys_plugin/tables.py +++ b/netbox_sys_plugin/tables.py @@ -2,7 +2,7 @@ import django_tables2 as tables from netbox.tables import NetBoxTable, columns -from .models import VirtualMachineMaintenance, ProviderCredentials, VmAssignedVirtualMachineType, VirtualMachineType, WebhookSettings, DomainNames +from .models import VirtualMachineMaintenance, ProviderCredentials, VmAssignedVirtualMachineType, VirtualMachineType, WebhookSettings, DomainNames, ProviderTypeExtraConfig #VmMaintenance class VmMaintenanceTable(NetBoxTable): @@ -208,3 +208,33 @@ class WebhookSettingsTable(NetBoxTable): default_columns = ('id','payload_url','http_content_type') +#ExtraConfig +class ProviderTypeExtraConfigTable(NetBoxTable): + """Extra Configs Table definition class""" + + pk = columns.ToggleColumn() + id = tables.Column( + linkify=True, + ) + + assigned_object = tables.Column( + linkify=True, + orderable=False, + verbose_name="Provider Type", + ) + + extra_config = tables.Column( + linkify=True, + verbose_name="Extra Config", + ) + + class Meta(NetBoxTable.Meta): + """Meta class""" + model = ProviderTypeExtraConfig + fields = ( + "pk", + "id", + "extra_config", + ) + + default_columns = ('id','assigned_object','extra_config') diff --git a/netbox_sys_plugin/template_content.py b/netbox_sys_plugin/template_content.py index 99b8625..91588d3 100644 --- a/netbox_sys_plugin/template_content.py +++ b/netbox_sys_plugin/template_content.py @@ -31,6 +31,9 @@ class VmTypeTable(PluginTemplateExtension): def right_page(self): return self.render('netbox_sys_plugin/clustertype_virtualmachinetype.html', extra_context={'vmtypesinfo': models.VirtualMachineType.objects.filter(ClusterType=self.context['object'])}) + + def left_page(self): + return self.render('netbox_sys_plugin/clustertype_providertypeextraconfig.html', extra_context={'extraconfiginfo': models.ProviderTypeExtraConfig.objects.filter(ClusterType=self.context['object'])}) class ProviderCredentialsTable(PluginTemplateExtension): diff --git a/netbox_sys_plugin/templates/netbox_sys_plugin/clustertype_providertypeextraconfig.html b/netbox_sys_plugin/templates/netbox_sys_plugin/clustertype_providertypeextraconfig.html new file mode 100644 index 0000000..0bc024e --- /dev/null +++ b/netbox_sys_plugin/templates/netbox_sys_plugin/clustertype_providertypeextraconfig.html @@ -0,0 +1,39 @@ +{% block content %} + <div class="row mb-3"> + <div class="col col-md-12"> + <div class="card"> + <h5 class="card-header">Virtual Machine Types</h5> + <div class="card-body"> + <table class="table table-responsive"> + <th scope="row">Extra Config</th> + <th scope="row">Description</th> + {% for extraconfig in extraconfiginfo %} + <tr> + <td> + {% if extraconfig.extra_config %} + <a href="{{ extraconfig.get_absolute_url }}">{{ extraconfig.extra_config }}</a> + {% else %} + {{ ''|placeholder }} + {% endif %} + </td> + <td> + {% if extraconfig.extra_config_description %} + {{ extraconfig.extra_config_description }} + {% else %} + {{ ''|placeholder }} + {% endif %} + </td> + </tr> + {% empty %} + <tr> + <td colspan="3">No Virtual Machine Types found.</td> + </tr> + {% endfor %} + </tr> + </table> + </div> + </div> + {% include 'inc/panels/custom_fields.html' %} + </div> + </div> +{% endblock content %} \ No newline at end of file diff --git a/netbox_sys_plugin/templates/netbox_sys_plugin/providertypeextraconfig.html b/netbox_sys_plugin/templates/netbox_sys_plugin/providertypeextraconfig.html new file mode 100644 index 0000000..74ec628 --- /dev/null +++ b/netbox_sys_plugin/templates/netbox_sys_plugin/providertypeextraconfig.html @@ -0,0 +1,48 @@ +{% extends 'generic/object.html' %} + +{% block title%} +Extra Config for {{ object.assigned_object }} +{% endblock title%} + +{% block content %} +<div class="row mb-3"> + <div class="col col-md-6"> + <div class="card"> + <h5 class="card-header">Extra Configuration</h5> + <div class="card-body"> + <table class="table table-hover attr-table"> + <tr> + <th scope="row">Extra Config</th> + <td>{% include 'extras/inc/configcontext_data.html' with data=object.extra_config format=json %}</td> + </tr> + <tr> + <th scope="row">Description</th> + <td>{{ object.extra_config_desc }}</td> + </tr> + </table> + </div> + </div> + {% include 'inc/panels/custom_fields.html' %} + {% include 'inc/panels/tags.html' %} + </div> + <div class="col col-md-6"> + <div class="card"> + <h5 class="card-header">Provider Type</h5> + <div class="card-body"> + <table class="table table-hover attr-table"> + <tr> + <th scope="row">{{ object.assigned_object_type|cut:"virtualization | " }}</th> + <td> + {% if object.assigned_object %} + <a href="{{ object.assigned_object.get_absolute_url }}">{{ object.assigned_object }}</a> + {% else %} + {{ ''|placeholder }} + {% endif %} + </td> + </tr> + </table> + </div> + </div> + </div> +</div> +{% endblock content %} diff --git a/netbox_sys_plugin/urls.py b/netbox_sys_plugin/urls.py index 2d281af..711323d 100644 --- a/netbox_sys_plugin/urls.py +++ b/netbox_sys_plugin/urls.py @@ -56,5 +56,12 @@ urlpatterns = ( path('webhook-settings/<int:pk>/delete/', views.WebhookSettingsDeleteView.as_view(), name='webhooksettings_delete'), path('webhook-settings/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='webhooksettings_changelog', kwargs={'model': models.WebhookSettings}), path('webhook-settings/<int:pk>/journal/', ObjectJournalView.as_view(), name='webhooksettings_journal', kwargs={'model': models.WebhookSettings}), - + #Extra Config + path('extra-config/<int:pk>/', views.ProviderTypeExtraConfigView.as_view(), name='providertypeextraconfig'), + path('extra-config/', views.ProviderTypeExtraConfigListView.as_view(), name='providertypeextraconfig_list'), + path('extra-config/add/', views.ProviderTypeExtraConfigEditView.as_view(), name='providertypeextraconfig_add'), + path('extra-config/<int:pk>/edit/', views.ProviderTypeExtraConfigEditView.as_view(), name='providertypeextraconfig_edit'), + path('extra-config/<int:pk>/delete/', views.ProviderTypeExtraConfigDeleteView.as_view(), name='providertypeextraconfig_delete'), + path('extra-config/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='providertypeextraconfig_changelog', kwargs={'model': models.ProviderTypeExtraConfig}), + path('extra-config/<int:pk>/journal/', ObjectJournalView.as_view(), name='providertypeextraconfig_journal', kwargs={'model': models.ProviderTypeExtraConfig}), ) diff --git a/netbox_sys_plugin/views.py b/netbox_sys_plugin/views.py index 59ddfd7..759992f 100644 --- a/netbox_sys_plugin/views.py +++ b/netbox_sys_plugin/views.py @@ -197,4 +197,31 @@ class WebhookSettingsEditView(generic.ObjectEditView): class WebhookSettingsDeleteView(generic.ObjectDeleteView): - queryset = models.WebhookSettings.objects.all() \ No newline at end of file + queryset = models.WebhookSettings.objects.all() + +#ExtraConfig +class ProviderTypeExtraConfigView(generic.ObjectView): + """ Virtual Machine Type view definition""" + + queryset = ( + models.ProviderTypeExtraConfig.objects.all() + ) + +class ProviderTypeExtraConfigListView(generic.ObjectListView): + """Cluster Type Virtual Machine Type list view definition""" + + queryset = models.ProviderTypeExtraConfig.objects.all() + table = tables.ProviderTypeExtraConfigTable + filterset = filtersets.ProviderCredentialsFilterSet + filterset_form = forms.ProviderTypeExtraConfigFilterForm + +class ProviderTypeExtraConfigEditView(generic.ObjectEditView): + """ + Defines the edit view for the Extra Configs django model. + """ + queryset = models.ProviderTypeExtraConfig.objects.all() + form = forms.ProviderTypeExtraConfigForm + +class ProviderTypeExtraConfigDeleteView(generic.ObjectDeleteView): + + queryset = models.ProviderTypeExtraConfig.objects.all() \ No newline at end of file -- GitLab From 1d94d0f521e286e71a816450282cca8ce49cbe8a Mon Sep 17 00:00:00 2001 From: Frederico Sequeira <frederico.sequeira@ext.ec.europa.eu> Date: Tue, 24 Dec 2024 16:02:19 +0000 Subject: [PATCH 3/9] =?UTF-8?q?=F0=9F=90=9B?= 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, 2 insertions(+), 2 deletions(-) diff --git a/netbox_sys_plugin/forms/createvm.py b/netbox_sys_plugin/forms/createvm.py index 829acbc..8b78975 100644 --- a/netbox_sys_plugin/forms/createvm.py +++ b/netbox_sys_plugin/forms/createvm.py @@ -94,7 +94,7 @@ class IPAddressForm(NetBoxModelForm): super().__init__(*args, **kwargs) self.fields.pop('tags',None) -class DomainNamesForm(NetBoxModelForm): +class DomainNameForm(NetBoxModelForm): """Form for Domain Names.""" domain_names = JSONField( @@ -176,7 +176,7 @@ ClusterListFormSet = forms.modelformset_factory( ) DomainNamesFormSet = forms.modelformset_factory( - DomainNames, form=DomainNamesForm, formset=DomainNamesForm, extra=1, can_delete=False + DomainNames, form=DomainNameForm, formset=DomainNameForm, extra=1, can_delete=False ) VmAssignedVirtualMachineTypeFormSet = forms.modelformset_factory( -- GitLab From 3f3078ea84a6c7cdfb9a68f09c08db1a5c54e217 Mon Sep 17 00:00:00 2001 From: Frederico Sequeira <frederico.sequeira@ext.ec.europa.eu> Date: Tue, 24 Dec 2024 16:23:36 +0000 Subject: [PATCH 4/9] =?UTF-8?q?=F0=9F=A6=BA=20Add=20duplicated=20key=20val?= =?UTF-8?q?idation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- netbox_sys_plugin/forms/provider.py | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/netbox_sys_plugin/forms/provider.py b/netbox_sys_plugin/forms/provider.py index fef9a91..97b91ae 100644 --- a/netbox_sys_plugin/forms/provider.py +++ b/netbox_sys_plugin/forms/provider.py @@ -19,7 +19,7 @@ class ProviderCredentialsForm(NetBoxModelForm): """ cluster = DynamicModelChoiceField( - queryset=Cluster.objects.all(), required=True, label="Cluster" + queryset=Cluster.objects.all(), required=True, label="Provider" ) provider_username = forms.CharField( max_length=50, min_length=1, required=True, label="Username" @@ -67,7 +67,7 @@ class ProviderCredentialsForm(NetBoxModelForm): #Check if cluster is assigned corretly if (not cluster): raise ValidationError( - {"__all__": "Can't assign more than one Credential to the same Cluster"}, + {"__all__": "Can't assign more than one Credential to the same Provider"}, ) if self.errors.get(f"provider_username"): @@ -91,7 +91,9 @@ class ProviderTypeExtraConfigForm(NetBoxModelForm): """ cluster_type = DynamicModelChoiceField( - queryset=ClusterType.objects.all(), required=True, label="Provider Type" + queryset=ClusterType.objects.all(), + required=True, + label="Provider Type" ) extra_config = JSONField( @@ -110,10 +112,6 @@ class ProviderTypeExtraConfigForm(NetBoxModelForm): kwargs["initial"] = initial super().__init__(*args, **kwargs) - if initial and 'extra_config' in initial: - if type(initial['extra_config']) is str: - initial['extra_config'] = json.loads(initial['extra_config']) - if instance: current_cluster_type_id = instance.assigned_object.id if instance.assigned_object else None else: @@ -135,17 +133,18 @@ class ProviderTypeExtraConfigForm(NetBoxModelForm): Validates form inputs before submitting: """ super().clean() - + cluster_type = self.cleaned_data.get("cluster_type") + cluster_type_ct = ContentType.objects.get_for_model(ClusterType) - #Check if cluster is assigned corretly - if (not cluster_type): + if ProviderTypeExtraConfig.objects.filter( + assigned_object_type=cluster_type_ct, + assigned_object_id=cluster_type.id + ).exclude(pk=self.instance.pk).exists(): raise ValidationError( - {"__all__": "Can't assign more than one Configuration to the same Provider Type"}, + {"__all__":"A Provider Type can only have one extra configuration"} ) - if self.errors.get(f"cluster_type"): - return def save(self, *args, **kwargs): # Set assigned object self.instance.assigned_object = ( -- GitLab From 6702b05b1bf6653cf802d964afdfd033e3db8f33 Mon Sep 17 00:00:00 2001 From: Frederico Sequeira <frederico.sequeira@ext.ec.europa.eu> Date: Fri, 27 Dec 2024 10:56:14 +0000 Subject: [PATCH 5/9] =?UTF-8?q?=F0=9F=92=84=20Add=20pretty=20Json=20to=20U?= =?UTF-8?q?I?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../templates/netbox_sys_plugin/domainnames.html | 4 ++-- .../netbox_sys_plugin/providertypeextraconfig.html | 3 ++- netbox_sys_plugin/templatetags/extras.py | 10 ++++++++++ 3 files changed, 14 insertions(+), 3 deletions(-) create mode 100644 netbox_sys_plugin/templatetags/extras.py diff --git a/netbox_sys_plugin/templates/netbox_sys_plugin/domainnames.html b/netbox_sys_plugin/templates/netbox_sys_plugin/domainnames.html index a203221..f52b792 100644 --- a/netbox_sys_plugin/templates/netbox_sys_plugin/domainnames.html +++ b/netbox_sys_plugin/templates/netbox_sys_plugin/domainnames.html @@ -14,8 +14,8 @@ <table class="table table-hover attr-table"> <tr> <th scope="row">Domain Names</th> - <td>{% include 'extras/inc/configcontext_data.html' with data=object.domain_names format=json %}</td> - + {% load extras %} + <td><pre class="block">{{ object.domain_names | pretty_json }}</pre></td> </tr> </table> </div> diff --git a/netbox_sys_plugin/templates/netbox_sys_plugin/providertypeextraconfig.html b/netbox_sys_plugin/templates/netbox_sys_plugin/providertypeextraconfig.html index 74ec628..ff047e2 100644 --- a/netbox_sys_plugin/templates/netbox_sys_plugin/providertypeextraconfig.html +++ b/netbox_sys_plugin/templates/netbox_sys_plugin/providertypeextraconfig.html @@ -13,7 +13,8 @@ Extra Config for {{ object.assigned_object }} <table class="table table-hover attr-table"> <tr> <th scope="row">Extra Config</th> - <td>{% include 'extras/inc/configcontext_data.html' with data=object.extra_config format=json %}</td> + {% load extras %} + <td><pre class="block">{{ object.extra_config | pretty_json }}</pre></td> </tr> <tr> <th scope="row">Description</th> diff --git a/netbox_sys_plugin/templatetags/extras.py b/netbox_sys_plugin/templatetags/extras.py new file mode 100644 index 0000000..c40a78e --- /dev/null +++ b/netbox_sys_plugin/templatetags/extras.py @@ -0,0 +1,10 @@ +import json + +from django import template + +register = template.Library() + + +@register.filter +def pretty_json(value): + return json.dumps(value, indent=4) \ No newline at end of file -- GitLab From 5ad511ab5a9ad70538ddd3117ae86b14487e7d97 Mon Sep 17 00:00:00 2001 From: Frederico Sequeira <frederico.sequeira@ext.ec.europa.eu> Date: Fri, 27 Dec 2024 11:08:01 +0000 Subject: [PATCH 6/9] =?UTF-8?q?=F0=9F=90=9B=20Add=20missing=20value=20to?= =?UTF-8?q?=20template?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../templates/netbox_sys_plugin/providertypeextraconfig.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netbox_sys_plugin/templates/netbox_sys_plugin/providertypeextraconfig.html b/netbox_sys_plugin/templates/netbox_sys_plugin/providertypeextraconfig.html index ff047e2..05e7ebc 100644 --- a/netbox_sys_plugin/templates/netbox_sys_plugin/providertypeextraconfig.html +++ b/netbox_sys_plugin/templates/netbox_sys_plugin/providertypeextraconfig.html @@ -18,7 +18,7 @@ Extra Config for {{ object.assigned_object }} </tr> <tr> <th scope="row">Description</th> - <td>{{ object.extra_config_desc }}</td> + <td>{{ object.extra_config_description }}</td> </tr> </table> </div> -- GitLab From 7885006d6a80765f591167a4f878a06ec79f7bc6 Mon Sep 17 00:00:00 2001 From: Frederico Sequeira <frederico.sequeira@ext.ec.europa.eu> Date: Fri, 27 Dec 2024 17:11:18 +0000 Subject: [PATCH 7/9] =?UTF-8?q?=E2=9C=A8=20Add=20assignment=20management?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- netbox_sys_plugin/api/serializers.py | 13 +- netbox_sys_plugin/api/urls.py | 2 + netbox_sys_plugin/api/views.py | 13 +- netbox_sys_plugin/filtersets.py | 11 +- netbox_sys_plugin/forms/machine.py | 113 +++++++++--------- netbox_sys_plugin/forms/provider.py | 74 +++++++++++- .../migrations/0012_vmassignedextraconfig.py | 38 ++++++ ...aconfig_extra_config_structure_and_more.py | 23 ++++ netbox_sys_plugin/models/machine.py | 106 ++++++++-------- netbox_sys_plugin/models/provider.py | 68 +++++++++-- netbox_sys_plugin/navigation.py | 25 ++-- netbox_sys_plugin/tables.py | 50 +++++++- .../providertypeextraconfig.html | 6 +- .../vmassignedextraconfig.html | 55 +++++++++ netbox_sys_plugin/urls.py | 8 ++ netbox_sys_plugin/views.py | 32 ++++- 16 files changed, 493 insertions(+), 144 deletions(-) create mode 100644 netbox_sys_plugin/migrations/0012_vmassignedextraconfig.py create mode 100644 netbox_sys_plugin/migrations/0013_rename_extra_config_providertypeextraconfig_extra_config_structure_and_more.py create mode 100644 netbox_sys_plugin/templates/netbox_sys_plugin/vmassignedextraconfig.html diff --git a/netbox_sys_plugin/api/serializers.py b/netbox_sys_plugin/api/serializers.py index 2e0a433..4ca980e 100644 --- a/netbox_sys_plugin/api/serializers.py +++ b/netbox_sys_plugin/api/serializers.py @@ -8,7 +8,7 @@ from dcim.api.nested_serializers import ( from ipam.api.nested_serializers import ( NestedIPAddressSerializer, NestedServiceSerializer ) -from .. models import VirtualMachineMaintenance, ProviderCredentials,VmAssignedVirtualMachineType, VirtualMachineType,DomainNames, WebhookSettings,ProviderTypeExtraConfig +from .. models import VirtualMachineMaintenance, ProviderCredentials,VmAssignedVirtualMachineType, VirtualMachineType,DomainNames, WebhookSettings,ProviderTypeExtraConfig,VmAssignedExtraConfig from django.contrib.contenttypes.models import ContentType from .nested_serializers import * from virtualization.choices import * @@ -104,13 +104,22 @@ class WebhookSettingsSerializer(NetBoxModelSerializer): class ProviderTypeExtraConfigSerializer(NetBoxModelSerializer): id = serializers.IntegerField(read_only=True) - extra_config = serializers.JSONField() + extra_config_structure = serializers.JSONField() class Meta: model = ProviderTypeExtraConfig fields = '__all__' +class VmAssignedExtraConfigSerializer(NetBoxModelSerializer): + id = serializers.IntegerField(read_only=True) + virtual_machine = VirtualMachineSerializer(source='assigned_object', read_only=True) + assigned_object_id = serializers.IntegerField(write_only=True) + assigned_object_type = serializers.CharField(write_only=True) + display = serializers.CharField(source="__str__") + class Meta: + model = VmAssignedExtraConfig + fields = '__all__' class VirtualMachineSerializer(NetBoxModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='virtualization-api:virtualmachine-detail') diff --git a/netbox_sys_plugin/api/urls.py b/netbox_sys_plugin/api/urls.py index 780e6ee..cd2d3e1 100644 --- a/netbox_sys_plugin/api/urls.py +++ b/netbox_sys_plugin/api/urls.py @@ -10,6 +10,8 @@ router.register(r'DomainNames', DomainNamesViewSet) router.register(r'WebhookSettings', WebhookSettingsViewSet) router.register(r'ProviderTypeExtraConfig', ProviderTypeExtraConfigViewSet) router.register(r'VirtualMachine', VirtualMachineViewSet) +router.register(r'ExtraConfig', ProviderTypeExtraConfigViewSet) +router.register(r'AssignedExtraConfig', VmAssignedExtraConfigViewSet) app_name = "netbox_sys_plugin" diff --git a/netbox_sys_plugin/api/views.py b/netbox_sys_plugin/api/views.py index f7b6604..b3a1030 100644 --- a/netbox_sys_plugin/api/views.py +++ b/netbox_sys_plugin/api/views.py @@ -53,4 +53,15 @@ class VirtualMachineViewSet(NetBoxModelViewSet): class WebhookSettingsViewSet(viewsets.ModelViewSet): queryset = WebhookSettings.objects.all() serializer_class = WebhookSettingsSerializer - http_method_names = ["get", "post", "patch", "delete", "options"] \ No newline at end of file + http_method_names = ["get", "post", "patch", "delete", "options"] + +class ProviderTypeExtraConfigViewSet(viewsets.ModelViewSet): + queryset = ProviderTypeExtraConfig.objects.all() + serializer_class = ProviderTypeExtraConfigSerializer + http_method_names = ["get", "post", "patch", "delete", "options"] + +class VmAssignedExtraConfigViewSet(viewsets.ModelViewSet): + queryset = VmAssignedExtraConfig.objects.all() + serializer_class = VmAssignedExtraConfigSerializer + http_method_names = ["get", "post", "patch", "delete", "options"] + diff --git a/netbox_sys_plugin/filtersets.py b/netbox_sys_plugin/filtersets.py index d33d8af..45dcbae 100644 --- a/netbox_sys_plugin/filtersets.py +++ b/netbox_sys_plugin/filtersets.py @@ -4,7 +4,7 @@ import django_filters from netbox.filtersets import NetBoxModelFilterSet from virtualization.models import VirtualMachine, Cluster, ClusterType from django.db.models import Q -from .models import VirtualMachineMaintenance, ProviderCredentials, VmAssignedVirtualMachineType, VirtualMachineType, ProviderTypeExtraConfig +from .models import VirtualMachineMaintenance, ProviderCredentials, VmAssignedVirtualMachineType, VirtualMachineType, ProviderTypeExtraConfig, VmAssignedExtraConfig class VmMaintenanceFilterSet(NetBoxModelFilterSet): @@ -124,4 +124,13 @@ class ProviderTypeExtraConfigFilterSet(NetBoxModelFilterSet): return queryset return queryset.filter(Q(extra_config_description__icontains=value)) +class VmAssignedExtraConfigFilterSet(NetBoxModelFilterSet): + """Vm Assigned Extra Config filterset definition class""" + + + class Meta: + """Meta class""" + model = VmAssignedExtraConfig + fields = ('assigned_object_type','provider_type_extra_config_assignment_desc', 'provider_type_extra_config') + diff --git a/netbox_sys_plugin/forms/machine.py b/netbox_sys_plugin/forms/machine.py index a217ed7..d7d860c 100644 --- a/netbox_sys_plugin/forms/machine.py +++ b/netbox_sys_plugin/forms/machine.py @@ -11,7 +11,7 @@ from netbox.forms import ( NetBoxModelFilterSetForm, ) from utilities.forms.fields import DynamicModelChoiceField, JSONField -from ..models import VmAssignedVirtualMachineType, VirtualMachineMaintenance, VirtualMachineType, DomainNames +from ..models import VmAssignedVirtualMachineType, VirtualMachineMaintenance, DomainNames, VmAssignedExtraConfig class VmAssignedVmTypeForm(NetBoxModelForm): """ @@ -82,80 +82,88 @@ class VmAssignedVmTypeFilterForm(NetBoxModelFilterSetForm): model = VmAssignedVirtualMachineType -class VmTypeForm(NetBoxModelForm): +class VmMaitenanceForm(NetBoxModelForm): """ - GUI form to add or edit a Virtual Machine Type. + GUI form to add or edit a VM Maitenance. """ - - cluster_type = DynamicModelChoiceField( - queryset=ClusterType.objects.all(), required=True, label="Cluster Type" - ) - virtual_machine_type_name = forms.CharField( - max_length=200, min_length=1, required=True, label="Name" + virtual_machine = DynamicModelChoiceField( + queryset=VirtualMachine.objects.none(), required=True, label="Virtual Machine" ) - virtual_machine_type_desc = forms.CharField( - max_length=50, min_length=1, required=False, label="Description" + maintenance_window = forms.CharField( + max_length=7, min_length=1, required=True, label="Maintenance Window", + help_text=mark_safe( + "<b>*Note:</b> Day of the week and a time of the day, 0 = Sunday , 1 = monday. Format 0-00:00", + ), ) - def __init__(self, *args, **kwargs): # Initialize helper selectors instance = kwargs.get("instance") initial = kwargs.get("initial", {}).copy() if instance: - if isinstance(instance.assigned_object, ClusterType): - initial["cluster_type"] = instance.assigned_object + if isinstance(instance.assigned_object, VirtualMachine): + initial["virtual_machine"] = instance.assigned_object kwargs["initial"] = initial super().__init__(*args, **kwargs) + if instance: + current_vm_id = instance.assigned_object.id if instance.assigned_object else None + else: + current_vm_id = None + + assigned_vms = VirtualMachineMaintenance.objects.filter( + assigned_object_type=ContentType.objects.get_for_model(VirtualMachine) + ).exclude(assigned_object_id=current_vm_id).values_list('assigned_object_id', flat=True) + self.fields['virtual_machine'].queryset = VirtualMachine.objects.exclude(id__in=assigned_vms) class Meta: """Meta class""" - model = VirtualMachineType - fields = ('cluster_type','virtual_machine_type_name', 'virtual_machine_type_desc','tags') + model = VirtualMachineMaintenance + fields = ( 'virtual_machine','maintenance_window','tags') + def clean(self): """ Validates form inputs before submitting: """ super().clean() + + + vm = self.cleaned_data.get("virtual_machine") - virtual_machine_type_name = self.cleaned_data.get("virtual_machine_type_name") - cluster_type = self.cleaned_data.get("cluster_type") - cluster_type_ct = ContentType.objects.get_for_model(ClusterType) - - if VirtualMachineType.objects.filter( - virtual_machine_type_name =virtual_machine_type_name, - assigned_object_type=cluster_type_ct, - assigned_object_id=cluster_type.id - ).exclude(pk=self.instance.pk).exists(): + #Check if Virtual Machine is assigned corretly + if (not vm): raise ValidationError( - {"__all__":"A Type with this name and cluster type already exists"} + {"__all__": "Can't assign more than one Maintenance Window to the same Virtual Machine"}, ) + + if self.errors.get("virtual_machine"): + return + def save(self, *args, **kwargs): # Set assigned object - self.instance.assigned_object = self.cleaned_data.get("cluster_type") + self.instance.assigned_object = ( + self.cleaned_data.get("virtual_machine") + ) return super().save(*args, **kwargs) -class VmTypeFilterForm(NetBoxModelFilterSetForm): - """Virtual Machine Type filter form definition class""" +class VmMaintenanceFilterForm(NetBoxModelFilterSetForm): + """MacAddress filter form definition class""" - model = VirtualMachineType + model = VirtualMachineMaintenance -class VmMaitenanceForm(NetBoxModelForm): +class DomainNamesForm(NetBoxModelForm): """ GUI form to add or edit a VM Maitenance. """ virtual_machine = DynamicModelChoiceField( queryset=VirtualMachine.objects.none(), required=True, label="Virtual Machine" ) - maintenance_window = forms.CharField( - max_length=7, min_length=1, required=True, label="Maintenance Window", - help_text=mark_safe( - "<b>*Note:</b> Day of the week and a time of the day, 0 = Sunday , 1 = monday. Format 0-00:00", - ), + domain_names = JSONField( + label=_('Domain Names'), + required=False ) def __init__(self, *args, **kwargs): @@ -173,15 +181,15 @@ class VmMaitenanceForm(NetBoxModelForm): else: current_vm_id = None - assigned_vms = VirtualMachineMaintenance.objects.filter( + assigned_vms = DomainNames.objects.filter( assigned_object_type=ContentType.objects.get_for_model(VirtualMachine) ).exclude(assigned_object_id=current_vm_id).values_list('assigned_object_id', flat=True) self.fields['virtual_machine'].queryset = VirtualMachine.objects.exclude(id__in=assigned_vms) class Meta: """Meta class""" - model = VirtualMachineMaintenance - fields = ( 'virtual_machine','maintenance_window','tags') + model = DomainNames + fields = ( 'virtual_machine','domain_names','tags') def clean(self): @@ -211,22 +219,18 @@ class VmMaitenanceForm(NetBoxModelForm): ) return super().save(*args, **kwargs) -class VmMaintenanceFilterForm(NetBoxModelFilterSetForm): +class DomainNamesFilterForm(NetBoxModelFilterSetForm): """MacAddress filter form definition class""" model = VirtualMachineMaintenance -class DomainNamesForm(NetBoxModelForm): +class VmAssignedExtraConfigForm(NetBoxModelForm): """ - GUI form to add or edit a VM Maitenance. + GUI form to add or edit a Virtual Machine Type. """ virtual_machine = DynamicModelChoiceField( queryset=VirtualMachine.objects.none(), required=True, label="Virtual Machine" ) - domain_names = JSONField( - label=_('Domain Names'), - required=False - ) def __init__(self, *args, **kwargs): # Initialize helper selectors @@ -243,16 +247,15 @@ class DomainNamesForm(NetBoxModelForm): else: current_vm_id = None - assigned_vms = DomainNames.objects.filter( + assigned_vms = VmAssignedExtraConfig.objects.filter( assigned_object_type=ContentType.objects.get_for_model(VirtualMachine) ).exclude(assigned_object_id=current_vm_id).values_list('assigned_object_id', flat=True) self.fields['virtual_machine'].queryset = VirtualMachine.objects.exclude(id__in=assigned_vms) class Meta: """Meta class""" - model = DomainNames - fields = ( 'virtual_machine','domain_names','tags') - + model = VmAssignedExtraConfig + fields = ('virtual_machine','provider_type_extra_config', 'provider_type_extra_config_assignment_desc','tags') def clean(self): """ @@ -266,14 +269,12 @@ class DomainNamesForm(NetBoxModelForm): #Check if Virtual Machine is assigned corretly if (not vm): raise ValidationError( - {"__all__": "Can't assign more than one Maintenance Window to the same Virtual Machine"}, + {"__all__": "Can't assign more than one Extra Config to the same Virtual Machine"}, ) if self.errors.get("virtual_machine"): return - - def save(self, *args, **kwargs): # Set assigned object self.instance.assigned_object = ( @@ -281,7 +282,7 @@ class DomainNamesForm(NetBoxModelForm): ) return super().save(*args, **kwargs) -class DomainNamesFilterForm(NetBoxModelFilterSetForm): - """MacAddress filter form definition class""" +class VmAssignedExtraConfigFilterForm(NetBoxModelFilterSetForm): + """Virtual Machine Type filter form definition class""" - model = VirtualMachineMaintenance \ No newline at end of file + model = VmAssignedExtraConfig \ No newline at end of file diff --git a/netbox_sys_plugin/forms/provider.py b/netbox_sys_plugin/forms/provider.py index 97b91ae..e966127 100644 --- a/netbox_sys_plugin/forms/provider.py +++ b/netbox_sys_plugin/forms/provider.py @@ -11,7 +11,7 @@ from netbox.forms import ( NetBoxModelFilterSetForm, ) from utilities.forms.fields import DynamicModelChoiceField, JSONField -from ..models import ProviderCredentials, ProviderTypeExtraConfig +from ..models import ProviderCredentials, ProviderTypeExtraConfig, VirtualMachineType class ProviderCredentialsForm(NetBoxModelForm): """ @@ -85,6 +85,68 @@ class ProviderCredentialsFilterForm(NetBoxModelFilterSetForm): model = ProviderCredentials +class VmTypeForm(NetBoxModelForm): + """ + GUI form to add or edit a Virtual Machine Type. + """ + + cluster_type = DynamicModelChoiceField( + queryset=ClusterType.objects.all(), required=True, label="Cluster Type" + ) + virtual_machine_type_name = forms.CharField( + max_length=200, min_length=1, required=True, label="Name" + ) + virtual_machine_type_desc = forms.CharField( + max_length=50, min_length=1, required=False, label="Description" + ) + + + def __init__(self, *args, **kwargs): + # Initialize helper selectors + instance = kwargs.get("instance") + initial = kwargs.get("initial", {}).copy() + if instance: + if isinstance(instance.assigned_object, ClusterType): + initial["cluster_type"] = instance.assigned_object + kwargs["initial"] = initial + super().__init__(*args, **kwargs) + + + class Meta: + """Meta class""" + model = VirtualMachineType + fields = ('cluster_type','virtual_machine_type_name', 'virtual_machine_type_desc','tags') + + def clean(self): + """ + Validates form inputs before submitting: + """ + super().clean() + + virtual_machine_type_name = self.cleaned_data.get("virtual_machine_type_name") + cluster_type = self.cleaned_data.get("cluster_type") + cluster_type_ct = ContentType.objects.get_for_model(ClusterType) + + if VirtualMachineType.objects.filter( + virtual_machine_type_name =virtual_machine_type_name, + assigned_object_type=cluster_type_ct, + assigned_object_id=cluster_type.id + ).exclude(pk=self.instance.pk).exists(): + raise ValidationError( + {"__all__":"A Type with this name and cluster type already exists"} + ) + + + def save(self, *args, **kwargs): + # Set assigned object + self.instance.assigned_object = self.cleaned_data.get("cluster_type") + return super().save(*args, **kwargs) + +class VmTypeFilterForm(NetBoxModelFilterSetForm): + """Virtual Machine Type filter form definition class""" + + model = VirtualMachineType + class ProviderTypeExtraConfigForm(NetBoxModelForm): """ GUI form to add or edit a Provider Type Extra Configs. @@ -96,12 +158,18 @@ class ProviderTypeExtraConfigForm(NetBoxModelForm): label="Provider Type" ) - extra_config = JSONField( + extra_config_name = forms.CharField( + label=_('Extra Config Name'), + required=True + ) + + extra_config_structure = JSONField( label=_('Extra Config'), required=True ) + def __init__(self, *args, **kwargs): # Initialize helper selectors instance = kwargs.get("instance") @@ -125,7 +193,7 @@ class ProviderTypeExtraConfigForm(NetBoxModelForm): class Meta: """Meta class""" model = ProviderTypeExtraConfig - fields = ('cluster_type', 'extra_config', 'extra_config_description','tags') + fields = ('cluster_type', 'extra_config_name','extra_config_structure', 'extra_config_description','tags') def clean(self): diff --git a/netbox_sys_plugin/migrations/0012_vmassignedextraconfig.py b/netbox_sys_plugin/migrations/0012_vmassignedextraconfig.py new file mode 100644 index 0000000..8bb9b79 --- /dev/null +++ b/netbox_sys_plugin/migrations/0012_vmassignedextraconfig.py @@ -0,0 +1,38 @@ +# Generated by Django 4.2.16 on 2024-12-27 12:45 + +from django.db import migrations, models +import django.db.models.deletion +import taggit.managers +import utilities.json + + +class Migration(migrations.Migration): + + dependencies = [ + ('contenttypes', '0002_remove_content_type_name'), + ('extras', '0098_webhook_custom_field_data_webhook_tags'), + ('netbox_sys_plugin', '0011_providertypeextraconfig_extra_config_description'), + ] + + operations = [ + migrations.CreateModel( + name='VmAssignedExtraConfig', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False)), + ('created', models.DateTimeField(auto_now_add=True, null=True)), + ('last_updated', models.DateTimeField(auto_now=True, null=True)), + ('custom_field_data', models.JSONField(blank=True, default=dict, encoder=utilities.json.CustomFieldJSONEncoder)), + ('provider_type_extra_config_assignment_desc', models.CharField(blank=True, default=None, max_length=100, null=True)), + ('assigned_object_id', models.PositiveBigIntegerField(blank=True, null=True)), + ('assigned_object_type', models.ForeignKey(blank=True, limit_choices_to=models.Q(models.Q(('app_label', 'virtualization'), ('model', 'virtualmachine'))), null=True, on_delete=django.db.models.deletion.PROTECT, to='contenttypes.contenttype')), + ('provider_type_extra_config', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='assigned_vm_type', to='netbox_sys_plugin.providertypeextraconfig')), + ('tags', taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag')), + ], + options={ + 'verbose_name': 'Assigned Extra Config', + 'verbose_name_plural': 'Assigned Extra Configs', + 'ordering': ['assigned_object_id'], + 'unique_together': {('assigned_object_id',)}, + }, + ), + ] diff --git a/netbox_sys_plugin/migrations/0013_rename_extra_config_providertypeextraconfig_extra_config_structure_and_more.py b/netbox_sys_plugin/migrations/0013_rename_extra_config_providertypeextraconfig_extra_config_structure_and_more.py new file mode 100644 index 0000000..4de7a83 --- /dev/null +++ b/netbox_sys_plugin/migrations/0013_rename_extra_config_providertypeextraconfig_extra_config_structure_and_more.py @@ -0,0 +1,23 @@ +# Generated by Django 4.2.16 on 2024-12-27 16:33 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('netbox_sys_plugin', '0012_vmassignedextraconfig'), + ] + + operations = [ + migrations.RenameField( + model_name='providertypeextraconfig', + old_name='extra_config', + new_name='extra_config_structure', + ), + migrations.AddField( + model_name='providertypeextraconfig', + name='extra_config_name', + field=models.CharField(blank=True, default=None, max_length=50, null=True), + ), + ] diff --git a/netbox_sys_plugin/models/machine.py b/netbox_sys_plugin/models/machine.py index 8ccb800..42222a0 100644 --- a/netbox_sys_plugin/models/machine.py +++ b/netbox_sys_plugin/models/machine.py @@ -4,9 +4,6 @@ from django.core.validators import ( RegexValidator, ) from django.urls import reverse -from django.core.validators import ( - URLValidator, -) # pylint: disable=import-error from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation from django.contrib.contenttypes.models import ContentType from django.db import models @@ -14,55 +11,6 @@ from virtualization.models import VirtualMachine, ClusterType from netbox.models import NetBoxModel VM_ASSIGNMENT_MODELS = models.Q(models.Q(app_label="virtualization", model="virtualmachine")) -CLUSTERTYPE_ASSIGNMENT_MODELS = models.Q(models.Q(app_label="virtualization", model="ClusterType")) - -#VM Type -class VirtualMachineType(NetBoxModel): - """Cluster ProviderInfo definition class""" - - virtual_machine_type_name = models.CharField( - default=None, blank=False, null=False, - max_length=50, - ) - - virtual_machine_type_desc = models.CharField( - default=None, blank=False, null=False, - max_length=100) - - assigned_object_type = models.ForeignKey( - to=ContentType, - limit_choices_to=CLUSTERTYPE_ASSIGNMENT_MODELS, - on_delete=models.PROTECT, - null=True, - blank=True, - ) - assigned_object_id = models.PositiveBigIntegerField(null=True, blank=True) - assigned_object = GenericForeignKey( - ct_field="assigned_object_type", - fk_field="assigned_object_id", - ) - - class Meta: - """Meta class""" - unique_together = ["assigned_object_id","virtual_machine_type_name"] - ordering = ["assigned_object_id"] - verbose_name = "Virtual Machine Type" - verbose_name_plural = "Virtual Machine Types" - - - def __str__(self): - return self.virtual_machine_type_name - - def get_absolute_url(self): - """override""" - return reverse("plugins:netbox_sys_plugin:virtualmachinetype", args=[self.pk]) - -GenericRelation( - to=VirtualMachineType, - content_type_field="assigned_object_type", - object_id_field="assigned_object_id", - related_query_name="ClusterType", -).contribute_to_class(ClusterType, "VirtualMachineType") #VM Assigned Type class VmAssignedVirtualMachineType(NetBoxModel): @@ -208,4 +156,56 @@ GenericRelation( content_type_field="assigned_object_type", object_id_field="assigned_object_id", related_query_name="VirtualMachine", -).contribute_to_class(VirtualMachine, "DomainNames") \ No newline at end of file +).contribute_to_class(VirtualMachine, "DomainNames") + +#VM Assigned Extra Config +class VmAssignedExtraConfig(NetBoxModel): + """Virtual Machine Type Info definition class""" + + provider_type_extra_config = models.ForeignKey( + to="ProviderTypeExtraConfig", + on_delete=models.PROTECT, + null=False, + blank=False, + related_name="assigned_vm_type", + name='provider_type_extra_config' + ) + + provider_type_extra_config_assignment_desc = models.CharField( + default=None, blank=True, null=True, + max_length=100) + + assigned_object_type = models.ForeignKey( + to=ContentType, + limit_choices_to=VM_ASSIGNMENT_MODELS, + on_delete=models.PROTECT, + null=True, + blank=True, + ) + assigned_object_id = models.PositiveBigIntegerField(null=True, blank=True) + assigned_object = GenericForeignKey( + ct_field="assigned_object_type", + fk_field="assigned_object_id", + ) + + class Meta: + """Meta class""" + unique_together = ["assigned_object_id"] + ordering = ["assigned_object_id"] + verbose_name = "Assigned Extra Config" + verbose_name_plural = "Assigned Extra Configs" + + + def __str__(self): + return f"Virtual machine ID {self.assigned_object_id}" + + def get_absolute_url(self): + """override""" + return reverse("plugins:netbox_sys_plugin:vmassignedextraconfig", args=[self.pk]) + +GenericRelation( + to=VmAssignedExtraConfig, + content_type_field="assigned_object_type", + object_id_field="assigned_object_id", + related_query_name="VirtualMachine", +).contribute_to_class(VirtualMachine, "VmAssignedExtraConfig") diff --git a/netbox_sys_plugin/models/provider.py b/netbox_sys_plugin/models/provider.py index fd8b289..f116282 100644 --- a/netbox_sys_plugin/models/provider.py +++ b/netbox_sys_plugin/models/provider.py @@ -1,24 +1,16 @@ """Models definitions""" -from django.core.validators import ( - RegexValidator, -) from django.urls import reverse -from django.core.validators import ( - URLValidator, -) # pylint: disable=import-error from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation from django.contrib.contenttypes.models import ContentType from django.db import models -from django.db.models import Q from virtualization.models import Cluster, ClusterType from netbox.models import NetBoxModel CLUSTER_ASSIGNMENT_MODELS = models.Q(models.Q(app_label="virtualization", model="Cluster")) CLUSTER_TYPE_ASSIGNMENT_MODELS = models.Q(models.Q(app_label="virtualization", model="ClusterType")) - - +#Provider Credentials class ProviderCredentials(NetBoxModel): """Cluster ProviderInfo definition class""" @@ -66,11 +58,63 @@ GenericRelation( related_query_name="Cluster", ).contribute_to_class(Cluster, "ProviderCredentials") +#Virtual Machine Type +class VirtualMachineType(NetBoxModel): + """Cluster ProviderInfo definition class""" + + virtual_machine_type_name = models.CharField( + default=None, blank=False, null=False, + max_length=50, + ) + + virtual_machine_type_desc = models.CharField( + default=None, blank=False, null=False, + max_length=100) + assigned_object_type = models.ForeignKey( + to=ContentType, + limit_choices_to=CLUSTER_TYPE_ASSIGNMENT_MODELS, + on_delete=models.PROTECT, + null=True, + blank=True, + ) + assigned_object_id = models.PositiveBigIntegerField(null=True, blank=True) + assigned_object = GenericForeignKey( + ct_field="assigned_object_type", + fk_field="assigned_object_id", + ) + + class Meta: + """Meta class""" + unique_together = ["assigned_object_id","virtual_machine_type_name"] + ordering = ["assigned_object_id"] + verbose_name = "Virtual Machine Type" + verbose_name_plural = "Virtual Machine Types" + + + def __str__(self): + return self.virtual_machine_type_name + + def get_absolute_url(self): + """override""" + return reverse("plugins:netbox_sys_plugin:virtualmachinetype", args=[self.pk]) + +GenericRelation( + to=VirtualMachineType, + content_type_field="assigned_object_type", + object_id_field="assigned_object_id", + related_query_name="ClusterType", +).contribute_to_class(ClusterType, "VirtualMachineType") + +#Provider Extra Config class ProviderTypeExtraConfig(NetBoxModel): """Cluster Type Extra configurations definition class""" - extra_config = models.JSONField( + extra_config_name = models.CharField( + default=None, blank=True, null=True, + max_length=50) + + extra_config_structure = models.JSONField( blank=True, null=True, ) @@ -100,7 +144,7 @@ class ProviderTypeExtraConfig(NetBoxModel): def __str__(self): - return f"for Provider Type ID {self.assigned_object_id}" + return f"{ self.extra_config_name }" def get_absolute_url(self): """override""" @@ -111,4 +155,4 @@ GenericRelation( content_type_field="assigned_object_type", object_id_field="assigned_object_id", related_query_name="ClusterType", -).contribute_to_class(ClusterType, "ProviderTypeExtraConfig") \ No newline at end of file +).contribute_to_class(ClusterType, "ProviderTypeExtraConfig") diff --git a/netbox_sys_plugin/navigation.py b/netbox_sys_plugin/navigation.py index 73d299c..0b651bd 100644 --- a/netbox_sys_plugin/navigation.py +++ b/netbox_sys_plugin/navigation.py @@ -71,13 +71,21 @@ create_domainnames_buttons = [ #Items -clusterClusterTypeItem = [ +clusterItem = [ PluginMenuItem( link="plugins:netbox_sys_plugin:providercredentials_list", link_text="Credentials", buttons=cluster_provider_credentials_buttons, permissions=["netbox_sys_plugin.list_providercredentials"], ), +] +ClusterTypeItem = [ + PluginMenuItem( + link="plugins:netbox_sys_plugin:virtualmachinetype_list", + link_text="Virtual Machine Type", + buttons=vm_machine_type_buttons, + permissions=["netbox_sys_plugin.list_vmmachinetype"], + ), PluginMenuItem( link="plugins:netbox_sys_plugin:providertypeextraconfig_list", link_text="Extra Config", @@ -87,18 +95,18 @@ clusterClusterTypeItem = [ ] vmMachineItem = [ - PluginMenuItem( - link="plugins:netbox_sys_plugin:virtualmachinetype_list", - link_text="Type", - buttons=vm_machine_type_buttons, - permissions=["netbox_sys_plugin.list_vmmachinetype"], - ), PluginMenuItem( link="plugins:netbox_sys_plugin:vmassignedvirtualmachinetype_list", link_text="Type Assignment", buttons=vm_assigned_machine_type_buttons, permissions=["netbox_sys_plugin.list_vmassignedmachinetype"], ), + PluginMenuItem( + link="plugins:netbox_sys_plugin:vmassignedextraconfig_list", + link_text="Extra Config Assignment", + buttons=vm_assigned_machine_type_buttons, + permissions=["netbox_sys_plugin.list_vmassignedextraconfig"], + ), PluginMenuItem( link="plugins:netbox_sys_plugin:virtualmachinemaintenance_list", link_text="Maintenance", @@ -130,7 +138,8 @@ menu = PluginMenu( label="System Squad", groups=( - ("Provider", clusterClusterTypeItem), + ("Provider", clusterItem), + ("Provider Type", ClusterTypeItem), ("Virtual Machine", (vmMachineItem)), ("Operation", (operItem)), ), diff --git a/netbox_sys_plugin/tables.py b/netbox_sys_plugin/tables.py index 2554bcb..b19abd0 100644 --- a/netbox_sys_plugin/tables.py +++ b/netbox_sys_plugin/tables.py @@ -2,7 +2,7 @@ import django_tables2 as tables from netbox.tables import NetBoxTable, columns -from .models import VirtualMachineMaintenance, ProviderCredentials, VmAssignedVirtualMachineType, VirtualMachineType, WebhookSettings, DomainNames, ProviderTypeExtraConfig +from .models import VirtualMachineMaintenance, ProviderCredentials, VmAssignedVirtualMachineType, VirtualMachineType, WebhookSettings, DomainNames, ProviderTypeExtraConfig, VmAssignedExtraConfig #VmMaintenance class VmMaintenanceTable(NetBoxTable): @@ -222,10 +222,14 @@ class ProviderTypeExtraConfigTable(NetBoxTable): orderable=False, verbose_name="Provider Type", ) + extra_config_name = tables.Column( + linkify=True, + verbose_name="Extra Config Name", + ) - extra_config = tables.Column( + extra_config_structure = tables.Column( linkify=True, - verbose_name="Extra Config", + verbose_name="Extra Config Structure", ) class Meta(NetBoxTable.Meta): @@ -234,7 +238,43 @@ class ProviderTypeExtraConfigTable(NetBoxTable): fields = ( "pk", "id", - "extra_config", + "extra_config_name", + "extra_config_structure", + ) + + default_columns = ('id','assigned_object','extra_config_name','extra_config_structure') + +#VmAssignedType +class VmAssignedExtraConfig(NetBoxTable): + """VM Maintenance Table definition class""" + + pk = columns.ToggleColumn() + id = tables.Column( + linkify=True, + ) + + assigned_object = tables.Column( + linkify=True, + orderable=False, + verbose_name="Virtual Machine", + ) + provider_type_extra_config = tables.Column( + linkify=True, + verbose_name="Extra Configuration", + ) + provider_type_extra_config_assignment_desc = tables.Column( + linkify=False, + verbose_name="Description", + ) + + class Meta(NetBoxTable.Meta): + """Meta class""" + model = VmAssignedExtraConfig + fields = ( + "pk", + "id", + "provider_type_extra_config", + "provider_type_extra_config_assignment_desc", ) - default_columns = ('id','assigned_object','extra_config') + default_columns = ('id','assigned_object','provider_type_extra_config','provider_type_extra_config_assignment_desc') diff --git a/netbox_sys_plugin/templates/netbox_sys_plugin/providertypeextraconfig.html b/netbox_sys_plugin/templates/netbox_sys_plugin/providertypeextraconfig.html index 05e7ebc..b92dc21 100644 --- a/netbox_sys_plugin/templates/netbox_sys_plugin/providertypeextraconfig.html +++ b/netbox_sys_plugin/templates/netbox_sys_plugin/providertypeextraconfig.html @@ -11,10 +11,14 @@ Extra Config for {{ object.assigned_object }} <h5 class="card-header">Extra Configuration</h5> <div class="card-body"> <table class="table table-hover attr-table"> + <tr> + <th scope="row">Extra Config Name</th> + <td>{{ object.extra_config_name }}</td> + </tr> <tr> <th scope="row">Extra Config</th> {% load extras %} - <td><pre class="block">{{ object.extra_config | pretty_json }}</pre></td> + <td><pre class="block">{{ object.extra_config_structure | pretty_json }}</pre></td> </tr> <tr> <th scope="row">Description</th> diff --git a/netbox_sys_plugin/templates/netbox_sys_plugin/vmassignedextraconfig.html b/netbox_sys_plugin/templates/netbox_sys_plugin/vmassignedextraconfig.html new file mode 100644 index 0000000..8747cf0 --- /dev/null +++ b/netbox_sys_plugin/templates/netbox_sys_plugin/vmassignedextraconfig.html @@ -0,0 +1,55 @@ +{% extends 'generic/object.html' %} + +{% block title%} +Extra Config for {{ object.assigned_object }} +{% endblock title%} + +{% block content %} +<div class="row mb-3"> + <div class="col col-md-6"> + <div class="card"> + <h5 class="card-header">Virtual Machine Extra Config Assignement </h5> + <div class="card-body"> + <table class="table table-hover attr-table"> + <tr> + <th scope="row">Name</th> + <td> + {% if object.provider_type_extra_config %} + <a href="{{ object.provider_type_extra_config.get_absolute_url }}">{{ object.provider_type_extra_config }}</a> + {% else %} + {{ ''|placeholder }} + {% endif %} + </td> + </tr> + + <tr> + <th scope="row">Description</th> + <td>{{ object.provider_type_extra_config_assignment_desc }}</td> + </tr> + </table> + </div> + </div> + {% include 'inc/panels/custom_fields.html' %} + {% include 'inc/panels/tags.html' %} + </div> + <div class="col col-md-6"> + <div class="card"> + <h5 class="card-header">Virtual Machine</h5> + <div class="card-body"> + <table class="table table-hover attr-table"> + <tr> + <th scope="row">{{ object.assigned_object_type|cut:"virtualization | " }}</th> + <td> + {% if object.assigned_object %} + <a href="{{ object.assigned_object.get_absolute_url }}">{{ object.assigned_object }}</a> + {% else %} + {{ ''|placeholder }} + {% endif %} + </td> + </tr> + </table> + </div> + </div> + </div> +</div> +{% endblock content %} diff --git a/netbox_sys_plugin/urls.py b/netbox_sys_plugin/urls.py index 711323d..fc8bbab 100644 --- a/netbox_sys_plugin/urls.py +++ b/netbox_sys_plugin/urls.py @@ -64,4 +64,12 @@ urlpatterns = ( path('extra-config/<int:pk>/delete/', views.ProviderTypeExtraConfigDeleteView.as_view(), name='providertypeextraconfig_delete'), path('extra-config/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='providertypeextraconfig_changelog', kwargs={'model': models.ProviderTypeExtraConfig}), path('extra-config/<int:pk>/journal/', ObjectJournalView.as_view(), name='providertypeextraconfig_journal', kwargs={'model': models.ProviderTypeExtraConfig}), + #virtualMachineAssignedExtraConfig + path('vm-assigned-extra-config/<int:pk>/', views.VmAssignedExtraConfigView.as_view(), name='vmassignedextraconfig'), + path('vm-assigned-extra-config/', views.VmAssignedExtraConfigListView.as_view(), name='vmassignedextraconfig_list'), + path('vm-assigned-extra-config/add/', views.VmAssignedExtraConfigEditView.as_view(), name='vmassignedextraconfig_add'), + path('vm-assigned-extra-config/<int:pk>/edit/', views.VmAssignedExtraConfigEditView.as_view(), name='vmassignedextraconfig_edit'), + path('vm-assigned-extra-config/<int:pk>/delete/', views.VmAssignedExtraConfigDeleteView.as_view(), name='vmassignedextraconfig_delete'), + path('vm-assigned-extra-config/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='vmassignedextraconfig_changelog', kwargs={'model': models.VmAssignedExtraConfig}), + path('vm-assigned-extra-config/<int:pk>/journal/', ObjectJournalView.as_view(), name='vmassignedextraconfig_journal', kwargs={'model': models.VmAssignedExtraConfig}), ) diff --git a/netbox_sys_plugin/views.py b/netbox_sys_plugin/views.py index 759992f..0e2d120 100644 --- a/netbox_sys_plugin/views.py +++ b/netbox_sys_plugin/views.py @@ -65,7 +65,7 @@ class ProviderCredentialsEditView(generic.ObjectEditView): form = forms.ProviderCredentialsForm #VmAssignedType -class VmAssignedVmTypeView(generic.ObjectView): +class VmAssignedVmTypeView(generic.ObjectView): """ Virtual Machine Type view definition""" queryset = ( @@ -224,4 +224,32 @@ class ProviderTypeExtraConfigEditView(generic.ObjectEditView): class ProviderTypeExtraConfigDeleteView(generic.ObjectDeleteView): - queryset = models.ProviderTypeExtraConfig.objects.all() \ No newline at end of file + queryset = models.ProviderTypeExtraConfig.objects.all() + +#VmAssignedExtraConfig +class VmAssignedExtraConfigView(generic.ObjectView): + """ Virtual Machine Assigned Extra Config view definition""" + + queryset = ( + models.VmAssignedExtraConfig.objects.all() + ) + +class VmAssignedExtraConfigListView(generic.ObjectListView): + """Virtual Machine Assigned Extra Config list view definition""" + + queryset = models.VmAssignedExtraConfig.objects.all() + table = tables.VmAssignedExtraConfig + filterset = filtersets.VmAssignedExtraConfigFilterSet + filterset_form = forms.VmAssignedExtraConfigFilterForm + +class VmAssignedExtraConfigDeleteView(generic.ObjectDeleteView): + """Virtual Machine Assigned Extra Config delete view definition""" + + queryset = models.VmAssignedExtraConfig.objects.all() + +class VmAssignedExtraConfigEditView(generic.ObjectEditView): + """ + Defines the edit view for the Virtual Machine Assigned Extra Config django model. + """ + queryset = models.VmAssignedExtraConfig.objects.all() + form = forms.VmAssignedExtraConfigForm -- GitLab From 70b30f1b8e1ce00005a2fa9bd3bbef496ebc53e3 Mon Sep 17 00:00:00 2001 From: Frederico Sequeira <frederico.sequeira@ext.ec.europa.eu> Date: Tue, 31 Dec 2024 11:06:47 +0000 Subject: [PATCH 8/9] =?UTF-8?q?=E2=9C=A8=20Complete=20extra=20config=20man?= =?UTF-8?q?agement=20objects?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- netbox_sys_plugin/forms/machine.py | 13 +++++- ...assignedextraconfig_extra_config_values.py | 18 ++++++++ ...dextraconfig_provider_type_extra_config.py | 19 +++++++++ netbox_sys_plugin/models/machine.py | 7 +++- netbox_sys_plugin/navigation.py | 11 ++++- netbox_sys_plugin/tables.py | 8 ++-- netbox_sys_plugin/template_content.py | 3 ++ .../providertypeextraconfig.html | 4 +- .../vm_vmassignedextraconfig.html | 41 +++++++++++++++++++ 9 files changed, 114 insertions(+), 10 deletions(-) create mode 100644 netbox_sys_plugin/migrations/0014_vmassignedextraconfig_extra_config_values.py create mode 100644 netbox_sys_plugin/migrations/0015_alter_vmassignedextraconfig_provider_type_extra_config.py create mode 100644 netbox_sys_plugin/templates/netbox_sys_plugin/vm_vmassignedextraconfig.html diff --git a/netbox_sys_plugin/forms/machine.py b/netbox_sys_plugin/forms/machine.py index d7d860c..4d8c21b 100644 --- a/netbox_sys_plugin/forms/machine.py +++ b/netbox_sys_plugin/forms/machine.py @@ -11,7 +11,7 @@ from netbox.forms import ( NetBoxModelFilterSetForm, ) from utilities.forms.fields import DynamicModelChoiceField, JSONField -from ..models import VmAssignedVirtualMachineType, VirtualMachineMaintenance, DomainNames, VmAssignedExtraConfig +from ..models import VmAssignedVirtualMachineType, VirtualMachineMaintenance, DomainNames, VmAssignedExtraConfig, ProviderTypeExtraConfig class VmAssignedVmTypeForm(NetBoxModelForm): """ @@ -232,6 +232,15 @@ class VmAssignedExtraConfigForm(NetBoxModelForm): queryset=VirtualMachine.objects.none(), required=True, label="Virtual Machine" ) + provider_type_extra_config = DynamicModelChoiceField( + queryset=ProviderTypeExtraConfig.objects.all(), + required=True, + label="Provider Type Extra Config", + query_params={ + 'extra_config_structure__isnull': False # Example: Filter configs with non-null structures + } + ) + def __init__(self, *args, **kwargs): # Initialize helper selectors instance = kwargs.get("instance") @@ -255,7 +264,7 @@ class VmAssignedExtraConfigForm(NetBoxModelForm): class Meta: """Meta class""" model = VmAssignedExtraConfig - fields = ('virtual_machine','provider_type_extra_config', 'provider_type_extra_config_assignment_desc','tags') + fields = ('virtual_machine','provider_type_extra_config','extra_config_values','provider_type_extra_config_assignment_desc','tags') def clean(self): """ diff --git a/netbox_sys_plugin/migrations/0014_vmassignedextraconfig_extra_config_values.py b/netbox_sys_plugin/migrations/0014_vmassignedextraconfig_extra_config_values.py new file mode 100644 index 0000000..d55ed6a --- /dev/null +++ b/netbox_sys_plugin/migrations/0014_vmassignedextraconfig_extra_config_values.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.16 on 2024-12-30 10:10 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('netbox_sys_plugin', '0013_rename_extra_config_providertypeextraconfig_extra_config_structure_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='vmassignedextraconfig', + name='extra_config_values', + field=models.JSONField(null=True), + ), + ] diff --git a/netbox_sys_plugin/migrations/0015_alter_vmassignedextraconfig_provider_type_extra_config.py b/netbox_sys_plugin/migrations/0015_alter_vmassignedextraconfig_provider_type_extra_config.py new file mode 100644 index 0000000..81d305c --- /dev/null +++ b/netbox_sys_plugin/migrations/0015_alter_vmassignedextraconfig_provider_type_extra_config.py @@ -0,0 +1,19 @@ +# Generated by Django 4.2.16 on 2024-12-30 12:16 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('netbox_sys_plugin', '0014_vmassignedextraconfig_extra_config_values'), + ] + + operations = [ + migrations.AlterField( + model_name='vmassignedextraconfig', + name='provider_type_extra_config', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='provider_type_extra_config', to='netbox_sys_plugin.providertypeextraconfig'), + ), + ] diff --git a/netbox_sys_plugin/models/machine.py b/netbox_sys_plugin/models/machine.py index 42222a0..805fbab 100644 --- a/netbox_sys_plugin/models/machine.py +++ b/netbox_sys_plugin/models/machine.py @@ -167,9 +167,14 @@ class VmAssignedExtraConfig(NetBoxModel): on_delete=models.PROTECT, null=False, blank=False, - related_name="assigned_vm_type", + related_name="provider_type_extra_config", name='provider_type_extra_config' ) + + extra_config_values = models.JSONField( + blank=False, + null=True, + ) provider_type_extra_config_assignment_desc = models.CharField( default=None, blank=True, null=True, diff --git a/netbox_sys_plugin/navigation.py b/netbox_sys_plugin/navigation.py index 0b651bd..97b3b44 100644 --- a/netbox_sys_plugin/navigation.py +++ b/netbox_sys_plugin/navigation.py @@ -46,6 +46,15 @@ vm_assigned_machine_type_buttons = [ permissions=["netbox_sys_plugin.add_vmassignedmachinetype"], ), ] +vm_assigned_extra_config_buttons = [ + PluginMenuButton( + link="plugins:netbox_sys_plugin:vmassignedextraconfig_add", + title="Add", + icon_class="mdi mdi-plus-thick", + color=ButtonColorChoices.GREEN, + permissions=["netbox_sys_plugin.add_vmassignedextraconfig"], + ), +] vm_machine_type_buttons = [ PluginMenuButton( @@ -104,7 +113,7 @@ vmMachineItem = [ PluginMenuItem( link="plugins:netbox_sys_plugin:vmassignedextraconfig_list", link_text="Extra Config Assignment", - buttons=vm_assigned_machine_type_buttons, + buttons=vm_assigned_extra_config_buttons, permissions=["netbox_sys_plugin.list_vmassignedextraconfig"], ), PluginMenuItem( diff --git a/netbox_sys_plugin/tables.py b/netbox_sys_plugin/tables.py index b19abd0..02dcb4c 100644 --- a/netbox_sys_plugin/tables.py +++ b/netbox_sys_plugin/tables.py @@ -254,13 +254,13 @@ class VmAssignedExtraConfig(NetBoxTable): ) assigned_object = tables.Column( - linkify=True, - orderable=False, + linkify=False, + orderable=True, verbose_name="Virtual Machine", ) provider_type_extra_config = tables.Column( - linkify=True, - verbose_name="Extra Configuration", + linkify=False, + verbose_name="Extra Configuration Structure", ) provider_type_extra_config_assignment_desc = tables.Column( linkify=False, diff --git a/netbox_sys_plugin/template_content.py b/netbox_sys_plugin/template_content.py index 91588d3..43bc604 100644 --- a/netbox_sys_plugin/template_content.py +++ b/netbox_sys_plugin/template_content.py @@ -22,6 +22,9 @@ class VmTableDomainNames(PluginTemplateExtension): def left_page(self): return self.render('netbox_sys_plugin/vm_domainnames.html', extra_context={'domainnamesinfo': models.DomainNames.objects.filter(VirtualMachine=self.context['object'])}) + + def full_width_page(self): + return self.render('netbox_sys_plugin/vm_vmassignedextraconfig.html', extra_context={'vmassignextraconfiginfo': models.VmAssignedExtraConfig.objects.filter(VirtualMachine=self.context['object'])}) class VmTypeTable(PluginTemplateExtension): diff --git a/netbox_sys_plugin/templates/netbox_sys_plugin/providertypeextraconfig.html b/netbox_sys_plugin/templates/netbox_sys_plugin/providertypeextraconfig.html index b92dc21..6b57ccd 100644 --- a/netbox_sys_plugin/templates/netbox_sys_plugin/providertypeextraconfig.html +++ b/netbox_sys_plugin/templates/netbox_sys_plugin/providertypeextraconfig.html @@ -1,7 +1,7 @@ {% extends 'generic/object.html' %} {% block title%} -Extra Config for {{ object.assigned_object }} +Extra Config Structure for {{ object.assigned_object }} {% endblock title%} {% block content %} @@ -16,7 +16,7 @@ Extra Config for {{ object.assigned_object }} <td>{{ object.extra_config_name }}</td> </tr> <tr> - <th scope="row">Extra Config</th> + <th scope="row">Extra Config Structure</th> {% load extras %} <td><pre class="block">{{ object.extra_config_structure | pretty_json }}</pre></td> </tr> diff --git a/netbox_sys_plugin/templates/netbox_sys_plugin/vm_vmassignedextraconfig.html b/netbox_sys_plugin/templates/netbox_sys_plugin/vm_vmassignedextraconfig.html new file mode 100644 index 0000000..3c9a7f7 --- /dev/null +++ b/netbox_sys_plugin/templates/netbox_sys_plugin/vm_vmassignedextraconfig.html @@ -0,0 +1,41 @@ +{% block content %} + <div class="row mb-3"> + <div class="col col-md-12"> + <div class="card"> + <h5 class="card-header">Extra Config</h5> + <div class="card-body"> + <table class="table table-responsive"> + <th scope="row">ID</th> + <th scope="row">Extra Config</th> + <th scope="row">Description</th> + {% for vmassignextraconfig in vmassignextraconfiginfo %} + <tr> + <td>{{ vmassignextraconfig.id }}</td> + <td> + {% if vmassignextraconfig.extra_config_values %} + <a href="{{ vmassignextraconfig.get_absolute_url }}">{{ vmassignextraconfig.extra_config_values }}</a> + {% else %} + {{ ''|placeholder }} + {% endif %} + </td> + <td> + {% if vmassignextraconfig.provider_type_extra_config_assignment_desc %} + {{ vmassignextraconfig.provider_type_extra_config_assignment_desc }} + {% else %} + {{ ''|placeholder }} + {% endif %} + </td> + </tr> + {% empty %} + <tr> + <td colspan="3">No Virtual Machine Extra Configs Found</td> + </tr> + {% endfor %} + </tr> + </table> + </div> + </div> + {% include 'inc/panels/custom_fields.html' %} + </div> + </div> +{% endblock content %} \ No newline at end of file -- GitLab From ba8d4ce95fbce1b62fc0879783d340067f3e4c09 Mon Sep 17 00:00:00 2001 From: Frederico Sequeira <frederico.sequeira@ext.ec.europa.eu> Date: Tue, 31 Dec 2024 15:07:05 +0000 Subject: [PATCH 9/9] =?UTF-8?q?=F0=9F=A6=BA=20Add=20extra=20values=20vs=20?= =?UTF-8?q?extra=20structure=20validation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- netbox_sys_plugin/forms/machine.py | 31 +++++++++++++- netbox_sys_plugin/tables.py | 6 +-- .../clustertype_providertypeextraconfig.html | 8 ++-- .../vmassignedextraconfig.html | 8 +++- netbox_sys_plugin/utils.py | 42 ++++++++++++++++++- 5 files changed, 84 insertions(+), 11 deletions(-) diff --git a/netbox_sys_plugin/forms/machine.py b/netbox_sys_plugin/forms/machine.py index 4d8c21b..7f00170 100644 --- a/netbox_sys_plugin/forms/machine.py +++ b/netbox_sys_plugin/forms/machine.py @@ -11,6 +11,7 @@ from netbox.forms import ( NetBoxModelFilterSetForm, ) from utilities.forms.fields import DynamicModelChoiceField, JSONField +from . . utils import validate_extra_config_values from ..models import VmAssignedVirtualMachineType, VirtualMachineMaintenance, DomainNames, VmAssignedExtraConfig, ProviderTypeExtraConfig class VmAssignedVmTypeForm(NetBoxModelForm): @@ -226,7 +227,7 @@ class DomainNamesFilterForm(NetBoxModelFilterSetForm): class VmAssignedExtraConfigForm(NetBoxModelForm): """ - GUI form to add or edit a Virtual Machine Type. + GUI form to add or edit a Extra Config Assignment. """ virtual_machine = DynamicModelChoiceField( queryset=VirtualMachine.objects.none(), required=True, label="Virtual Machine" @@ -241,6 +242,11 @@ class VmAssignedExtraConfigForm(NetBoxModelForm): } ) + extra_config_values = JSONField( + label=_('Extra Config Values'), + required=False + ) + def __init__(self, *args, **kwargs): # Initialize helper selectors instance = kwargs.get("instance") @@ -266,6 +272,29 @@ class VmAssignedExtraConfigForm(NetBoxModelForm): model = VmAssignedExtraConfig fields = ('virtual_machine','provider_type_extra_config','extra_config_values','provider_type_extra_config_assignment_desc','tags') + def clean_extra_config_values(self): + """ + Validates `extra_config_values` against the selected `provider_type_extra_config`. + """ + # Fetch the selected provider configuration + provider_config = self.cleaned_data.get("provider_type_extra_config") + extra_config_values = self.cleaned_data.get("extra_config_values", {}) + + # Validate against the structure if a provider config is selected + if provider_config and provider_config.extra_config_structure: + structure_dict = provider_config.extra_config_structure + + first_structure_name = next(iter(structure_dict), None) + if first_structure_name: + structure = structure_dict[first_structure_name][0] + + if isinstance(structure, dict): # Ensure structure is in the expected format + is_valid, errors = validate_extra_config_values(structure, extra_config_values) + if not is_valid: + raise ValidationError(errors) + + return extra_config_values + def clean(self): """ Validates form inputs before submitting: diff --git a/netbox_sys_plugin/tables.py b/netbox_sys_plugin/tables.py index 02dcb4c..d0028ee 100644 --- a/netbox_sys_plugin/tables.py +++ b/netbox_sys_plugin/tables.py @@ -254,16 +254,16 @@ class VmAssignedExtraConfig(NetBoxTable): ) assigned_object = tables.Column( - linkify=False, + linkify=True, orderable=True, verbose_name="Virtual Machine", ) provider_type_extra_config = tables.Column( - linkify=False, + linkify=True, verbose_name="Extra Configuration Structure", ) provider_type_extra_config_assignment_desc = tables.Column( - linkify=False, + linkify=True, verbose_name="Description", ) diff --git a/netbox_sys_plugin/templates/netbox_sys_plugin/clustertype_providertypeextraconfig.html b/netbox_sys_plugin/templates/netbox_sys_plugin/clustertype_providertypeextraconfig.html index 0bc024e..64bbc40 100644 --- a/netbox_sys_plugin/templates/netbox_sys_plugin/clustertype_providertypeextraconfig.html +++ b/netbox_sys_plugin/templates/netbox_sys_plugin/clustertype_providertypeextraconfig.html @@ -2,16 +2,16 @@ <div class="row mb-3"> <div class="col col-md-12"> <div class="card"> - <h5 class="card-header">Virtual Machine Types</h5> + <h5 class="card-header">Extra Config Structures</h5> <div class="card-body"> <table class="table table-responsive"> - <th scope="row">Extra Config</th> + <th scope="row">Extra Config Struture</th> <th scope="row">Description</th> {% for extraconfig in extraconfiginfo %} <tr> <td> - {% if extraconfig.extra_config %} - <a href="{{ extraconfig.get_absolute_url }}">{{ extraconfig.extra_config }}</a> + {% if extraconfig.extra_config_structure %} + <a href="{{ extraconfig.get_absolute_url }}">{{ extraconfig.extra_config_structure }}</a> {% else %} {{ ''|placeholder }} {% endif %} diff --git a/netbox_sys_plugin/templates/netbox_sys_plugin/vmassignedextraconfig.html b/netbox_sys_plugin/templates/netbox_sys_plugin/vmassignedextraconfig.html index 8747cf0..cabd698 100644 --- a/netbox_sys_plugin/templates/netbox_sys_plugin/vmassignedextraconfig.html +++ b/netbox_sys_plugin/templates/netbox_sys_plugin/vmassignedextraconfig.html @@ -12,7 +12,7 @@ Extra Config for {{ object.assigned_object }} <div class="card-body"> <table class="table table-hover attr-table"> <tr> - <th scope="row">Name</th> + <th scope="row">Structure Name</th> <td> {% if object.provider_type_extra_config %} <a href="{{ object.provider_type_extra_config.get_absolute_url }}">{{ object.provider_type_extra_config }}</a> @@ -21,7 +21,11 @@ Extra Config for {{ object.assigned_object }} {% endif %} </td> </tr> - + <tr> + <th scope="row">Extra Config Values</th> + {% load extras %} + <td><pre class="block">{{ object.extra_config_values | pretty_json }}</pre></td> + </tr> <tr> <th scope="row">Description</th> <td>{{ object.provider_type_extra_config_assignment_desc }}</td> diff --git a/netbox_sys_plugin/utils.py b/netbox_sys_plugin/utils.py index 82c3136..e037a0a 100644 --- a/netbox_sys_plugin/utils.py +++ b/netbox_sys_plugin/utils.py @@ -28,4 +28,44 @@ def send_webhook(payload): response.raise_for_status() print(f"Webhook sent successfully: {response.status_code}") except requests.RequestException as e: - print(f"Error sending webhook: {e}") \ No newline at end of file + print(f"Error sending webhook: {e}") + + +def validate_extra_config_values(structure, values): + """Validates the user-provided `extra_config_values` against the `extra_config_structure`.""" + errors = [] + + # Loop through each field in the structure + for field_name, field_props in structure.items(): + field_type = field_props.get("type", "String").lower() + is_required = field_props.get("required", "false").lower() == "true" + + # Check for missing required fields + if is_required: + if field_name not in values or values[field_name] in [None, ""]: + errors.append(f"Missing or empty required field: '{field_name}' with type {field_type}") + continue + + # Validate type for present fields + if field_name in values: + value = values[field_name] + if not is_valid_type(value, field_type): + errors.append(f"Incorrect type for field '{field_name}': " + f"expected {field_type}, got {type(value).__name__}") + + return len(errors) == 0, errors + + +def is_valid_type(value, expected_type): + """Validates a value against the expected type.""" + if expected_type == "string": + return isinstance(value, str) + elif expected_type == "integer": + try: + int(value) # Allow string representation of numbers + return True + except ValueError: + return False + elif expected_type == "boolean": + return value in [True, False, "true", "false", "True", "False"] + return False # Default to invalid for unknown types \ No newline at end of file -- GitLab