diff --git a/docs/model/netbox_sys_webhook_settings.md b/docs/model/netbox_sys_webhook_settings.md new file mode 100644 index 0000000000000000000000000000000000000000..680b8007fd6d4ef85a4c3a0e13d45af63c060eef --- /dev/null +++ b/docs/model/netbox_sys_webhook_settings.md @@ -0,0 +1,11 @@ +## netbox_sys_webhook_settings + +**Webhook Settings definition class** + +| FIELD | TYPE | DESCRIPTION | +|----------------------------------------------------|----------------------------------|---------------------------------------------------------------------------| +| id | Big (8 byte) integer | Unique ID | +| payload_url | String (URL ) | URL will be called using the POST method when the webhook is called | +| http_content_type | String (100) | Http content type used in the webhook. Default value "application/json" | + + diff --git a/netbox_sys_plugin/api/serializers.py b/netbox_sys_plugin/api/serializers.py index afba76ed210d6a41ceca4d33a9de098cd29078f7..a09b18aadaaf5ad9d65fb15d0ac2085a10ad1801 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 +from .. models import VirtualMachineMaintenance, ProviderCredentials,VmAssignedVirtualMachineType, VirtualMachineType,DomainNames, WebhookSettings from django.contrib.contenttypes.models import ContentType from .nested_serializers import * from virtualization.choices import * @@ -40,29 +40,29 @@ class ClusterTypeSerializer(serializers.ModelSerializer): fields = ['id', 'name'] #Plugin Data Serializer -class VirtualMachineMaintenanceSerializer(serializers.ModelSerializer): +class VirtualMachineMaintenanceSerializer(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 = VirtualMachineMaintenance - #fields = ['id','maintenance_window','assigned_object_type','assigned_object_id','assigned_object_type','virtual_machine'] fields = '__all__' -class ProviderCredentialsSerializer(serializers.ModelSerializer): +class ProviderCredentialsSerializer(NetBoxModelSerializer): id = serializers.IntegerField(read_only=True) cluster = ClusterSerializer(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 = ProviderCredentials - #fields = ['id','maintenance_window','assigned_object_type','assigned_object_id','assigned_object_type','virtual_machine'] fields = '__all__' -class VirtualMachineTypeSerializer(serializers.ModelSerializer): +class VirtualMachineTypeSerializer(NetBoxModelSerializer): id = serializers.IntegerField(read_only=True) cluster_type = ClusterTypeSerializer(source='assigned_object', read_only=True) assigned_object_id = serializers.IntegerField(write_only=True) @@ -70,32 +70,39 @@ class VirtualMachineTypeSerializer(serializers.ModelSerializer): class Meta: model = VirtualMachineType - #fields = ['id','maintenance_window','assigned_object_type','assigned_object_id','assigned_object_type','virtual_machine'] fields = '__all__' -class VmAssignedVirtualMachineTypeSerializer(serializers.ModelSerializer): +class VmAssignedVirtualMachineTypeSerializer(NetBoxModelSerializer): id = serializers.IntegerField(read_only=True) virtual_machine = VirtualMachineSerializer(source='assigned_object', read_only=True) virtual_machine_type = VirtualMachineTypeSerializer() assigned_object_id = serializers.IntegerField(write_only=True) assigned_object_type = serializers.CharField(write_only=True) + display = serializers.CharField(source="__str__") class Meta: model = VmAssignedVirtualMachineType - #fields = ['id','maintenance_window','assigned_object_type','assigned_object_id','assigned_object_type','virtual_machine'] fields = '__all__' -class DomainNamesSerializer(serializers.ModelSerializer): +class DomainNamesSerializer(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 = DomainNames - #fields = ['id','maintenance_window','assigned_object_type','assigned_object_id','assigned_object_type','virtual_machine'] fields = '__all__' +class WebhookSettingsSerializer(NetBoxModelSerializer): + id = serializers.IntegerField(read_only=True) + + class Meta: + model = WebhookSettings + fields = '__all__' + + class VirtualMachineSerializer(NetBoxModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='virtualization-api:virtualmachine-detail') status = ChoiceField(choices=VirtualMachineStatusChoices, required=False) diff --git a/netbox_sys_plugin/api/urls.py b/netbox_sys_plugin/api/urls.py index 51e074e3a5341e1831abcd58be086c0bcac0e926..3c43e5e22686c9895b6cd8fc1e126efd710a2068 100644 --- a/netbox_sys_plugin/api/urls.py +++ b/netbox_sys_plugin/api/urls.py @@ -6,7 +6,8 @@ router.register(r'MaintenanceWindow', VirtualMachineMaintenanceViewSet) router.register(r'ProviderCredentials', ProviderCredentialsViewSet) router.register(r'AssignedVmType', VmAssignedVirtualMachineTypeViewSet) router.register(r'VmType', VirtualMachineTypeViewSet) -router.register(r'DomainNames', VirtualMachineTypeViewSet) +router.register(r'DomainNames', DomainNamesViewSet) +router.register(r'WebhookSettings', WebhookSettingsViewSet) 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 64e5e7f3b36a0ea895bd57cbffcbf159fddc9d6d..ed9e99365849ff771e629b801049f37a5557432c 100644 --- a/netbox_sys_plugin/api/views.py +++ b/netbox_sys_plugin/api/views.py @@ -4,7 +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 .. models import VirtualMachineMaintenance, ProviderCredentials, VmAssignedVirtualMachineType, VirtualMachineType,DomainNames +from .. filtersets import VmTypeFilterSet +from .. models import VirtualMachineMaintenance, ProviderCredentials, VmAssignedVirtualMachineType, VirtualMachineType,DomainNames, WebhookSettings from . serializers import * class VirtualMachineMaintenanceViewSet(viewsets.ModelViewSet): @@ -13,7 +14,7 @@ class VirtualMachineMaintenanceViewSet(viewsets.ModelViewSet): http_method_names = ["get", "post", "patch", "delete", "options"] class DomainNamesViewSet(viewsets.ModelViewSet): - queryset = VirtualMachineMaintenance.objects.all() + queryset = DomainNames.objects.all() serializer_class = DomainNamesSerializer http_method_names = ["get", "post", "patch", "delete", "options"] @@ -22,7 +23,6 @@ class ProviderCredentialsViewSet(viewsets.ModelViewSet): serializer_class = ProviderCredentialsSerializer http_method_names = ["get", "post", "patch", "delete", "options"] - class VmAssignedVirtualMachineTypeViewSet(viewsets.ModelViewSet): queryset = VmAssignedVirtualMachineType.objects.prefetch_related(Prefetch( 'virtual_machine_type', queryset=VirtualMachineType.objects.all(), @@ -33,6 +33,7 @@ class VmAssignedVirtualMachineTypeViewSet(viewsets.ModelViewSet): class VirtualMachineTypeViewSet(viewsets.ModelViewSet): queryset = VirtualMachineType.objects.all() serializer_class = VirtualMachineTypeSerializer + filterset_class = VmTypeFilterSet http_method_names = ["get", "post", "patch", "delete", "options"] class VirtualMachineViewSet(NetBoxModelViewSet): @@ -43,3 +44,7 @@ class VirtualMachineViewSet(NetBoxModelViewSet): serializer_class = VirtualMachineSerializer http_method_names = ["get"] +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 diff --git a/netbox_sys_plugin/filtersets.py b/netbox_sys_plugin/filtersets.py index a934a31032b86d49a92c58be1cd37e5726817fd4..5b44013788997a9c760e94de9bffe7dc894a1977 100644 --- a/netbox_sys_plugin/filtersets.py +++ b/netbox_sys_plugin/filtersets.py @@ -54,17 +54,24 @@ class VmAssignedVmTypeFilterSet(NetBoxModelFilterSet): class VmTypeFilterSet(NetBoxModelFilterSet): """VirtualMachineType filterset definition class""" - cluster_type = django_filters.ModelMultipleChoiceFilter( + cluster_type_name = django_filters.ModelMultipleChoiceFilter( field_name="ClusterType__name", queryset=ClusterType.objects.all(), to_field_name="name", label="Cluster Type (name)", ) + 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 = VirtualMachineType - fields = ('virtual_machine_type_name','virtual_machine_type_desc', 'cluster_type') + fields = ('virtual_machine_type_name','virtual_machine_type_desc', 'cluster_type_name','cluster_type_id') # pylint: disable=W0613 def search(self, queryset, name, value): @@ -73,7 +80,6 @@ class VmTypeFilterSet(NetBoxModelFilterSet): return queryset return queryset.filter(Q(virtual_machine_type_name__icontains=value)|Q(virtual_machine_type_desc__icontains=value)) - class ProviderCredentialsFilterSet(NetBoxModelFilterSet): """ProviderCredentials filterset definition class""" diff --git a/netbox_sys_plugin/forms/__init__.py b/netbox_sys_plugin/forms/__init__.py index b26bccdfec217ca571aff9bc75c1cd543337cff8..8b1460c4d2b84d1ddf42260fd56c2fbd40c7b39d 100644 --- a/netbox_sys_plugin/forms/__init__.py +++ b/netbox_sys_plugin/forms/__init__.py @@ -2,4 +2,5 @@ from .provider import * from .machine import * -from .createvm import * \ No newline at end of file +from .createvm import * +from .operation import * \ No newline at end of file diff --git a/netbox_sys_plugin/forms/createvm.py b/netbox_sys_plugin/forms/createvm.py index 6b71a49e35ff34c8453348e3b54338b6b51f2aca..829acbc043e8f6010e435429a13b2d86bfa224a9 100644 --- a/netbox_sys_plugin/forms/createvm.py +++ b/netbox_sys_plugin/forms/createvm.py @@ -1,19 +1,19 @@ from django import forms from django.forms import inlineformset_factory from django.contrib.contenttypes.models import ContentType +from django.utils.translation import gettext_lazy as _ from django.db import transaction -from utilities.forms.fields import DynamicModelChoiceField +from utilities.forms.fields import DynamicModelChoiceField, JSONField from netbox.forms import NetBoxModelForm from virtualization.models import ClusterType, Cluster, VirtualMachine, VMInterface from ipam.models import IPAddress, Service -from dcim.models import DeviceRole,Site, Platform +from dcim.models import DeviceRole, Platform from extras.models import Tag, TaggedItem +from .. models import DomainNames, VmAssignedVirtualMachineType, VirtualMachineType +from . . utils import send_webhook import netaddr - - - class ClusterForm(NetBoxModelForm): """Form for Clusters.""" @@ -94,6 +94,40 @@ class IPAddressForm(NetBoxModelForm): super().__init__(*args, **kwargs) self.fields.pop('tags',None) +class DomainNamesForm(NetBoxModelForm): + """Form for Domain Names.""" + + domain_names = JSONField( + label=_(''), + required=True, + initial=({'example_domain_name': 'example_domain1','example_domain_name2': 'example_domain2'}) + ) + + class Meta: + model = DomainNames + fields = ('domain_names','tags') + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields.pop('tags',None) + +class VmAssignedVirtualMachineTypeForm(NetBoxModelForm, forms.Form): + """Form for Type Assignment.""" + + virtual_machine_type = DynamicModelChoiceField( + queryset=VirtualMachineType.objects.all(), + required=True, + query_params={'cluster_type_id': '$cl_list_new-cluster_type',}, + ) + + class Meta: + model = VirtualMachineType + fields = ('virtual_machine_type','tags') + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields.pop('tags',None) + class TagsForm(NetBoxModelForm): """Form for IP Addresses.""" tag = DynamicModelChoiceField( @@ -108,8 +142,6 @@ class TagsForm(NetBoxModelForm): self.fields.pop('tags',None) - - # Inline formsets for managing relationships ClusterFormSet = inlineformset_factory( ClusterType, Cluster, form=ClusterForm, extra=1, can_delete=False @@ -143,6 +175,14 @@ ClusterListFormSet = forms.modelformset_factory( Cluster, form=ClusterFormList, formset=ClusterFormList, extra=1, can_delete=False ) +DomainNamesFormSet = forms.modelformset_factory( + DomainNames, form=DomainNamesForm, formset=DomainNamesForm, extra=1, can_delete=False +) + +VmAssignedVirtualMachineTypeFormSet = forms.modelformset_factory( + VirtualMachineType, form=VmAssignedVirtualMachineTypeForm, formset=VmAssignedVirtualMachineTypeForm, extra=1, can_delete=False +) + TagFormSet = forms.modelformset_factory( Tag, form=TagsForm, formset=TagsForm, extra=1, can_delete=False ) @@ -197,6 +237,14 @@ class CreateVmForm(NetBoxModelForm): self.cl_list_formsets = [] empty_cl_list_formset = ClusterListFormSet(data=data, prefix='cl_list_new') self.cl_list_formsets.append(('new', empty_cl_list_formset)) + # Domain Names + self.domainnames_formsets = [] + empty_domainnames_formset = DomainNamesFormSet(data=data, prefix='domainnames_new') + self.domainnames_formsets.append(('new', empty_domainnames_formset)) + # VM Assigned VM Type + self.vmassignedvmtype_formsets = [] + empty_vmassignedvmtype_formset = VmAssignedVirtualMachineTypeFormSet(data=data, prefix='vmassignedvmtype_new') + self.vmassignedvmtype_formsets.append(('new', empty_vmassignedvmtype_formset)) # Tags self.tagformsets = [] empty_tagformset = TagFormSet(data=data, prefix='tag_new') @@ -331,6 +379,43 @@ class CreateVmForm(NetBoxModelForm): self.create_ip_address(data,vm_interface,'ip_new','Network IP Address') self.create_ip_address(data,vm_interface,'gateway_new','Network Gateway') + @staticmethod + def create_vmtype_assignment(data, vm): + """Assign a Type to the virtual machine.""" + vmtype_id =data.get('vmassignedvmtype_new-virtual_machine_type', '') + try: + vmtype = VirtualMachineType.objects.get(pk=vmtype_id) + except VirtualMachineType.DoesNotExist: + raise ValueError(f"Invalid VM Type ID: {vmtype_id}") + + vm_assignment = VmAssignedVirtualMachineType( + virtual_machine_type=vmtype, + assigned_object=vm, + virtual_machine_type_assignment_desc=f"Type {vmtype.virtual_machine_type_name} {vmtype.virtual_machine_type_desc} assigned to {vm.name}" + ) + vm_assignment.full_clean() + vm_assignment.save() + CreateVmForm.assign_tags(vm_assignment,data) + return vm_assignment + + @staticmethod + def create_domain_names(data, vm): + """Create and save DomainNames objects for the virtual machine.""" + domain_names_data = data.get('domainnames_new-domain_names', None) + + if not domain_names_data: + return None + + domain_names_object = DomainNames( + domain_names=domain_names_data, + assigned_object_type=ContentType.objects.get_for_model(vm), + assigned_object_id=vm.id, + ) + domain_names_object.full_clean() + domain_names_object.save() + CreateVmForm.assign_tags(domain_names_object, data) + return domain_names_object + def process_creation(self, data): """Object creation""" try: @@ -338,8 +423,90 @@ class CreateVmForm(NetBoxModelForm): cluster = Cluster.objects.get(pk=data.get('cl_list_new-cluster', '')) vm = self.create_virtual_machine(data, cluster) vmi = self.create_vm_interface(data, vm) + self.create_vmtype_assignment(data,vm) + self.create_domain_names(data,vm) self.create_all_services(data, vm) self.create_all_ip_adresses(data, vmi) + + + # Gather related data for webhook payload + interfaces = VMInterface.objects.filter(virtual_machine=vm) + services = Service.objects.filter(virtual_machine=vm) + vm_interface_content_type = ContentType.objects.get_for_model(VMInterface) + vm_content_type = ContentType.objects.get_for_model(VirtualMachine) + ip_addresses = IPAddress.objects.filter( + assigned_object_id__in=interfaces.values_list('id', flat=True), + assigned_object_type=vm_interface_content_type + ) + types = VmAssignedVirtualMachineType.objects.filter( + assigned_object_id=vm.id, + assigned_object_type=vm_content_type + ).select_related('virtual_machine_type') + + domain_names = DomainNames.objects.filter( + assigned_object_id=vm.id, + assigned_object_type=ContentType.objects.get_for_model(VirtualMachine) + ) + + payload = { + "virtual_machine": { + "id": vm.id, + "name": vm.name, + "status": vm.status, + "role": vm.role.name if vm.role else None, + "platform": vm.platform.name if vm.platform else None, + "description": vm.description, + }, + "cluster": { + "id": cluster.id if cluster else None, + "name": cluster.name if cluster else None, + "type": { + "id": cluster.type.id if cluster.type else None, + "name": cluster.type.name if cluster.type else None, + }, + }, + "Virtual Machine Type": [ + { + "id": type.id, + "name": type.virtual_machine_type.virtual_machine_type_name, + "description": type.virtual_machine_type.virtual_machine_type_desc + + } + for type in types + ], + "interface": [ + { + "id": interface.id, + "name": interface.name} + for interface in interfaces + ], + "ip_addresses": [ + { + "id": ip.id, + "address": str(ip.address), + "status": ip.status, + } + for ip in ip_addresses + ], + "services": [ + { + "id": service.id, + "name": service.name, + "description": service.description, + } + for service in services + ], + "domain_names": [ + { + "id": domain.id, + "names": domain.domain_names, + } + for domain in domain_names + ], + } + + # Send webhook + send_webhook(payload) return vm except ValueError as e: diff --git a/netbox_sys_plugin/forms/operation.py b/netbox_sys_plugin/forms/operation.py new file mode 100644 index 0000000000000000000000000000000000000000..041d6f7ee18b9629f7f5f264cbad6dfed095a5fe --- /dev/null +++ b/netbox_sys_plugin/forms/operation.py @@ -0,0 +1,33 @@ +from django.core.exceptions import ValidationError +from netbox.forms import NetBoxModelForm, NetBoxModelFilterSetForm +from ..models import WebhookSettings + + + +class WebhookSettingsForm(NetBoxModelForm): + """Webhook Settings form definition class""" + + class Meta: + """Meta class""" + + model = WebhookSettings + fields = ('payload_url','http_content_type','tags') + labels = { + "payload_url": "Payload URL", + "http_content_type": "HTTP Content Type", + } + + def clean(self): + cleaned_data = super().clean() + + if self.instance.pk: + return cleaned_data + + if WebhookSettings.objects.exists(): + raise ValidationError("You can only have one webbook setting") + + +class WebhookSettingsFilterForm(NetBoxModelFilterSetForm): + """Webhook Settings filter form definition class""" + + model = WebhookSettings \ No newline at end of file diff --git a/netbox_sys_plugin/migrations/0008_webhooksettings.py b/netbox_sys_plugin/migrations/0008_webhooksettings.py new file mode 100644 index 0000000000000000000000000000000000000000..e00f7a366b0dd96fbc5cf99a1ed963750f90bbed --- /dev/null +++ b/netbox_sys_plugin/migrations/0008_webhooksettings.py @@ -0,0 +1,34 @@ +# Generated by Django 4.2.16 on 2024-12-17 12:29 + +from django.db import migrations, models +import taggit.managers +import utilities.json + + +class Migration(migrations.Migration): + + dependencies = [ + ('extras', '0098_webhook_custom_field_data_webhook_tags'), + ('netbox_sys_plugin', '0007_alter_domainnames_assigned_object_type'), + ] + + operations = [ + migrations.CreateModel( + name='WebhookSettings', + 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)), + ('payload_url', models.CharField(max_length=500)), + ('http_content_type', models.CharField(default='application/json', editable=False, max_length=100)), + ('tags', taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag')), + ], + options={ + 'verbose_name': 'Webhook Settings', + 'verbose_name_plural': 'Webhook Settings', + 'ordering': ['payload_url'], + 'unique_together': {('payload_url',)}, + }, + ), + ] diff --git a/netbox_sys_plugin/migrations/0009_alter_domainnames_assigned_object_type_and_more.py b/netbox_sys_plugin/migrations/0009_alter_domainnames_assigned_object_type_and_more.py new file mode 100644 index 0000000000000000000000000000000000000000..7dc9a6146abae491c7c6a26613e9c6560d16b075 --- /dev/null +++ b/netbox_sys_plugin/migrations/0009_alter_domainnames_assigned_object_type_and_more.py @@ -0,0 +1,40 @@ +# Generated by Django 4.2.16 on 2024-12-20 10:51 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('contenttypes', '0002_remove_content_type_name'), + ('netbox_sys_plugin', '0008_webhooksettings'), + ] + + operations = [ + migrations.AlterField( + model_name='domainnames', + name='assigned_object_type', + field=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'), + ), + migrations.AlterField( + model_name='virtualmachinemaintenance', + name='assigned_object_type', + field=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'), + ), + migrations.AlterField( + model_name='virtualmachinetype', + name='virtual_machine_type_desc', + field=models.CharField(default=None, max_length=100), + ), + migrations.AlterField( + model_name='vmassignedvirtualmachinetype', + name='assigned_object_type', + field=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'), + ), + migrations.AlterField( + model_name='webhooksettings', + name='http_content_type', + field=models.CharField(default='application/json', max_length=100), + ), + ] diff --git a/netbox_sys_plugin/models/__init__.py b/netbox_sys_plugin/models/__init__.py index bf52380ddb0d34078cc29b9010dcecc79e6caf9b..3cbc7b57e3a47a4955ca125cadc2d0a572c08426 100644 --- a/netbox_sys_plugin/models/__init__.py +++ b/netbox_sys_plugin/models/__init__.py @@ -1,4 +1,5 @@ """Models definitions""" from .provider import * -from .machine import * \ No newline at end of file +from .machine import * +from .operation import * \ No newline at end of file diff --git a/netbox_sys_plugin/models/machine.py b/netbox_sys_plugin/models/machine.py index f1e195be4d017cfbb493ada736972cb8bac6ab56..8ccb800dc7b6afcbe118a8e00942c48175c74ba2 100644 --- a/netbox_sys_plugin/models/machine.py +++ b/netbox_sys_plugin/models/machine.py @@ -13,7 +13,7 @@ from django.db import models from virtualization.models import VirtualMachine, ClusterType from netbox.models import NetBoxModel -VM_ASSIGNMENT_MODELS = models.Q(models.Q(app_label="virtualization", model="VirtualMachine")) +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 @@ -26,7 +26,7 @@ class VirtualMachineType(NetBoxModel): ) virtual_machine_type_desc = models.CharField( - default=None, blank=True, null=True, + default=None, blank=False, null=False, max_length=100) assigned_object_type = models.ForeignKey( @@ -73,7 +73,8 @@ class VmAssignedVirtualMachineType(NetBoxModel): on_delete=models.PROTECT, null=False, blank=False, - related_name="assigned_vm_type" + related_name="assigned_vm_type", + name='virtual_machine_type' ) virtual_machine_type_assignment_desc = models.CharField( diff --git a/netbox_sys_plugin/models/operation.py b/netbox_sys_plugin/models/operation.py new file mode 100644 index 0000000000000000000000000000000000000000..6b35db716701a63af127705c7d7f8c3bfff36021 --- /dev/null +++ b/netbox_sys_plugin/models/operation.py @@ -0,0 +1,38 @@ +from django.core.validators import ( + URLValidator, +) # pylint: disable=import-error +from django.db import models +from django.urls import reverse +from netbox.models import NetBoxModel +from django.db import models +from django.utils.translation import gettext_lazy as _ + + + +class WebhookSettings(NetBoxModel): + """Webhook settings config definition class""" + + payload_url = models.CharField( + max_length=500, + verbose_name=_('URL'), + help_text=_( + "This URL will be called using the HTTP POST when the webhook is called." + ) + ) + + http_content_type = models.CharField( + max_length=100, + default='application/json', + verbose_name=_('HTTP content type') + ) + + class Meta: + """Meta class""" + unique_together = ["payload_url"] + ordering = ["payload_url"] + verbose_name = "Webhook Settings" + verbose_name_plural = "Webhook Settings" + + def get_absolute_url(self): + """override""" + return reverse("plugins:netbox_sys_plugin:webhooksettings", args=[self.pk]) \ No newline at end of file diff --git a/netbox_sys_plugin/navigation.py b/netbox_sys_plugin/navigation.py index ca9245db9e5a0c79b702797650005b5e4615f1d2..e92953a49589b8bedf771f9484ce47f7a12f7ea2 100644 --- a/netbox_sys_plugin/navigation.py +++ b/netbox_sys_plugin/navigation.py @@ -47,15 +47,6 @@ vm_machine_type_buttons = [ ), ] -create_vm_buttons = [ - PluginMenuButton( - link="plugins:netbox_sys_plugin:creatvm_add", - title="Add", - icon_class="mdi mdi-plus-thick", - color=ButtonColorChoices.GREEN, - permissions=["netbox_sys_plugin.add_createvm"], - ), -] create_domainnames_buttons = [ PluginMenuButton( @@ -82,46 +73,50 @@ clusterProviderCredentialsItem = [ vmMachineItem = [ PluginMenuItem( link="plugins:netbox_sys_plugin:virtualmachinetype_list", - link_text="VM Type", + link_text="Type", buttons=vm_machine_type_buttons, permissions=["netbox_sys_plugin.list_vmmachinetype"], ), PluginMenuItem( link="plugins:netbox_sys_plugin:vmassignedvirtualmachinetype_list", - link_text="VM Type Assignment", + link_text="Type Assignment", buttons=vm_assigned_machine_type_buttons, permissions=["netbox_sys_plugin.list_vmassignedmachinetype"], ), PluginMenuItem( link="plugins:netbox_sys_plugin:virtualmachinemaintenance_list", - link_text="VM Maintenance", + link_text="Maintenance", buttons=vm_maintenance_buttons, permissions=["netbox_sys_plugin.list_maintenance"], ), PluginMenuItem( link="plugins:netbox_sys_plugin:domainnames_list", - link_text="VM Domain Names", + link_text="Domain Names", buttons=create_domainnames_buttons, permissions=["netbox_sys_plugin.add_domainnames"], ), PluginMenuItem( link="plugins:netbox_sys_plugin:creatvm_add", - link_text="Create VM", - buttons=create_vm_buttons, + link_text="Create Virtual Machine", permissions=["netbox_sys_plugin.add_createvm"], ), ] operItem =[ - + PluginMenuItem( + link="plugins:netbox_sys_plugin:webhooksettings_list", + link_text="Webhook", + permissions=["netbox_sys_plugin.list_webhooksettings"], + ), ] #Menu menu = PluginMenu( - label="Sys", + label="System Squad", groups=( ("Provider", clusterProviderCredentialsItem), ("Virtual Machine", (vmMachineItem)), + ("Operation", (operItem)), ), icon_class="mdi mdi-all-inclusive-box-outline", ) \ No newline at end of file diff --git a/netbox_sys_plugin/tables.py b/netbox_sys_plugin/tables.py index a92222722138363bb0f54f4e0d9e1ac84cc97d3c..cac807eb2af2c8777d71dca9a0730a044ea27e58 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 +from .models import VirtualMachineMaintenance, ProviderCredentials, VmAssignedVirtualMachineType, VirtualMachineType, WebhookSettings #VmMaintenance class VmMaintenanceTable(NetBoxTable): @@ -175,4 +175,36 @@ class DomainNamesTable(NetBoxTable): "domain_names", ) - default_columns = ('id','domain_names','assigned_object') \ No newline at end of file + default_columns = ('id','domain_names','assigned_object') + +#WebhookSettings +class WebhookSettingsTable(NetBoxTable): + """webhook Settings Table definition class""" + + pk = columns.ToggleColumn() + id = tables.Column( + linkify=True, + ) + + payload_url = tables.Column( + linkify=True, + verbose_name="Payload Url", + ) + + http_content_type = tables.Column( + linkify=True, + verbose_name="Http Content Type", + ) + + class Meta(NetBoxTable.Meta): + """Meta class""" + model = WebhookSettings + fields = ( + "pk", + "id", + "payload_url", + "http_content_type", + ) + + default_columns = ('id','payload_url','http_content_type') + diff --git a/netbox_sys_plugin/templates/netbox_sys_plugin/clustertype_virtualmachinetype.html b/netbox_sys_plugin/templates/netbox_sys_plugin/clustertype_virtualmachinetype.html index cd3a25c92ddca611cca2db4c41b300794c5ff816..fb72b298820403b310547bd3b009ab1d8c6e700d 100644 --- a/netbox_sys_plugin/templates/netbox_sys_plugin/clustertype_virtualmachinetype.html +++ b/netbox_sys_plugin/templates/netbox_sys_plugin/clustertype_virtualmachinetype.html @@ -2,7 +2,7 @@ <div class="row mb-3"> <div class="col col-md-12"> <div class="card"> - <h5 class="card-header">Provider Credentials</h5> + <h5 class="card-header">Virtual Machine Types</h5> <div class="card-body"> <table class="table table-responsive"> <th scope="row">Virtual Machine Type</th> diff --git a/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html b/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html index 3e562fdca0ece46896f68a4f87525bd58eff415f..fd8f6eccb6b4424b2270f65e2a3f36acf1198df7 100644 --- a/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html +++ b/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html @@ -55,13 +55,19 @@ SYS - Virtual Machine {% if vm_form.non_field_errors %} <h1> {{ vm_form.non_field_errors }}</h1> {% endif %} - + </div> + {% endfor %} + {% endfor %} + {% for vmassignedvmtype_formset in form.vmassignedvmtype_formsets %} + {% for vmassignedvmtype_form in vmassignedvmtype_formset %} + <div class="form-group" class="field-group mb-5"> + {{ vmassignedvmtype_form.as_p }} </div> {% endfor %} {% endfor %} </div> <div class="field-group my-5"> - <h5>Network Name</h5> + <h5>Network</h5> {% for vmi_formset in form.virtual_machine_interface_formsets %} {% for vmi_form in vmi_formset %} <div class="form-group" class="field-group mb-5"> @@ -85,6 +91,14 @@ SYS - Virtual Machine </div> {% endfor %} {% endfor %} + <h5>Domain Names</h5> + {% for domainnames_formset in form.domainnames_formsets %} + {% for domainnames_form in domainnames_formset %} + <div class="form-group" class="field-group mb-5"> + {{ domainnames_form.as_p }} + </div> + {% endfor %} + {% endfor %} </div> <div class="field-group my-5"> <h5>Dependencies</h5> diff --git a/netbox_sys_plugin/templates/netbox_sys_plugin/webhooksettings.html b/netbox_sys_plugin/templates/netbox_sys_plugin/webhooksettings.html new file mode 100644 index 0000000000000000000000000000000000000000..4d74ad1d20974edfe19b6f0a75f8fe37732e450a --- /dev/null +++ b/netbox_sys_plugin/templates/netbox_sys_plugin/webhooksettings.html @@ -0,0 +1,27 @@ +{% extends 'generic/object.html' %} +{% block title%} +Webhook for Create VM Form +{% endblock title%} +{% block content %} +<div class="row mb-3"> + <div class="col col-md-12"> + <div class="card"> + <h5 class="card-header">Webhook Information</h5> + <div class="card-body"> + <table class="table table-hover attr-table"> + <tr> + <th scope="row">Payload URL</th> + <td>{{ object.payload_url }}</td> + </tr> + <tr> + <th scope="row">HTTP Content Type</th> + <td>{{ object.http_content_type }}</td> + </tr> + </table> + </div> + </div> + {% include 'inc/panels/custom_fields.html' %} + {% include 'inc/panels/tags.html' %} + </div> +</div> +{% endblock content %} diff --git a/netbox_sys_plugin/urls.py b/netbox_sys_plugin/urls.py index c43d9b11fdfc1399a0d9dcd74c2ee7bea72864e6..2d281af64ec22cc68091bde0061094cbb6b00ba0 100644 --- a/netbox_sys_plugin/urls.py +++ b/netbox_sys_plugin/urls.py @@ -48,5 +48,13 @@ urlpatterns = ( path('domain-names/<int:pk>/journal/', ObjectJournalView.as_view(), name='domainnames_journal', kwargs={'model': models.DomainNames}), #CreateVM path('create-vm/', CreateVmView.as_view(), name='creatvm_add'), + #WebhookSettings + path('webhook-settings/<int:pk>/', views.WebhookSettingsView.as_view(), name='webhooksettings'), + path('webhook-settings/', views.WebhookSettingsListView.as_view(), name='webhooksettings_list'), + path('webhook-settings/add/', views.WebhookSettingsEditView.as_view(), name='webhooksettings_add'), + path('webhook-settings/<int:pk>/edit/', views.WebhookSettingsEditView.as_view(), name='webhooksettings_edit'), + 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}), ) diff --git a/netbox_sys_plugin/utils.py b/netbox_sys_plugin/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..82c31366fb360dc8a2833435dac631dc7d3319a2 --- /dev/null +++ b/netbox_sys_plugin/utils.py @@ -0,0 +1,31 @@ +import requests +from django.conf import settings +from .models import WebhookSettings + +def get_webhook_settings(): + try: + webhook_settings = WebhookSettings.objects.first() + if not webhook_settings: + raise ValueError("No webhook configured") + + return { + "url":webhook_settings.payload_url, + "content_type":webhook_settings.http_content_type + } + except Exception as e: + print(f"Error Fetching webhook settings: {e}") + return None + +def send_webhook(payload): + """Send a webhook to the specified endpoint.""" + settings = get_webhook_settings() + if not settings: + print("Cannot send webhook, missing configurations") + return + try: + headers = {'Content-Type': settings['content_type']} + response = requests.post(settings['url'], json=payload, headers=headers) + 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 diff --git a/netbox_sys_plugin/views.py b/netbox_sys_plugin/views.py index 0b1f59b63d4b46b106f3c036a53594d0c046f3ee..59ddfd7b7e453173942edc4546ae5814f6155d4f 100644 --- a/netbox_sys_plugin/views.py +++ b/netbox_sys_plugin/views.py @@ -170,4 +170,31 @@ class DomainNamesEditView(generic.ObjectEditView): class DomainNamesDeleteView(generic.ObjectDeleteView): """Domain Names delete view definition""" - queryset = models.DomainNames.objects.all() \ No newline at end of file + queryset = models.DomainNames.objects.all() + +#Webhook Settings +class WebhookSettingsView(generic.ObjectView): + + """ Webhook Settings view definition""" + + queryset = ( + models.WebhookSettings.objects.all() + ) + +class WebhookSettingsListView(generic.ObjectListView): + """Webhook Settings list view definition""" + + queryset = models.WebhookSettings.objects.all() + table = tables.WebhookSettingsTable + filterset_form = forms.WebhookSettingsFilterForm + +class WebhookSettingsEditView(generic.ObjectEditView): + """ + Defines the edit view for the Webhook Settings django model. + """ + queryset = models.WebhookSettings.objects.all() + form = forms.WebhookSettingsForm + +class WebhookSettingsDeleteView(generic.ObjectDeleteView): + + queryset = models.WebhookSettings.objects.all() \ No newline at end of file