From 5b0af9e3920035990d61f697ab5b89f1d23a68cc Mon Sep 17 00:00:00 2001
From: Frederico Sequeira <frederico.sequeira@ext.ec.europa.eu>
Date: Fri, 15 Nov 2024 11:19:27 +0000
Subject: [PATCH 1/4] =?UTF-8?q?=E2=9C=A8=20Add=20Provider=20Credentials?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 ...maintenance_maintenance_window_and_more.py | 45 +++++++++++++
 netbox_sys_plugin/models/__init__.py          |  4 ++
 .../{models.py => models/maintenance.py}      |  0
 netbox_sys_plugin/models/provider.py          | 67 +++++++++++++++++++
 netbox_sys_plugin/navigation.py               | 30 ++++++---
 netbox_sys_plugin/tables.py                   | 37 +++++++++-
 netbox_sys_plugin/urls.py                     |  1 +
 netbox_sys_plugin/views.py                    |  8 +++
 8 files changed, 181 insertions(+), 11 deletions(-)
 create mode 100644 netbox_sys_plugin/migrations/0002_alter_virtualmachinemaintenance_maintenance_window_and_more.py
 create mode 100644 netbox_sys_plugin/models/__init__.py
 rename netbox_sys_plugin/{models.py => models/maintenance.py} (100%)
 create mode 100644 netbox_sys_plugin/models/provider.py

diff --git a/netbox_sys_plugin/migrations/0002_alter_virtualmachinemaintenance_maintenance_window_and_more.py b/netbox_sys_plugin/migrations/0002_alter_virtualmachinemaintenance_maintenance_window_and_more.py
new file mode 100644
index 0000000..9c0f99e
--- /dev/null
+++ b/netbox_sys_plugin/migrations/0002_alter_virtualmachinemaintenance_maintenance_window_and_more.py
@@ -0,0 +1,45 @@
+# Generated by Django 4.2.16 on 2024-11-15 09:53
+
+import django.core.validators
+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', '0001_initial'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='virtualmachinemaintenance',
+            name='maintenance_window',
+            field=models.CharField(blank=True, default=None, max_length=200, null=True, validators=[django.core.validators.RegexValidator(message='Incorrect maintenance window format', regex='(^([0-6])-[0-1]?[0-9]|2[0-3]):[0-5][0-9]$')]),
+        ),
+        migrations.CreateModel(
+            name='ProviderCredentials',
+            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_url', models.CharField(blank=True, default=None, max_length=200, null=True)),
+                ('provider_username', models.CharField(blank=True, default=None, max_length=200, null=True)),
+                ('provider_password', models.CharField(blank=True, default=None, max_length=200, 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', 'Cluster'))), 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 Information',
+                'verbose_name_plural': 'Provider Informations',
+                'ordering': ['assigned_object_id'],
+                'unique_together': {('assigned_object_id',)},
+            },
+        ),
+    ]
diff --git a/netbox_sys_plugin/models/__init__.py b/netbox_sys_plugin/models/__init__.py
new file mode 100644
index 0000000..cef638b
--- /dev/null
+++ b/netbox_sys_plugin/models/__init__.py
@@ -0,0 +1,4 @@
+"""Models definitions"""
+
+from .maintenance import VirtualMachineMaintenance
+from .provider import ProviderCredentials
\ No newline at end of file
diff --git a/netbox_sys_plugin/models.py b/netbox_sys_plugin/models/maintenance.py
similarity index 100%
rename from netbox_sys_plugin/models.py
rename to netbox_sys_plugin/models/maintenance.py
diff --git a/netbox_sys_plugin/models/provider.py b/netbox_sys_plugin/models/provider.py
new file mode 100644
index 0000000..327afbc
--- /dev/null
+++ b/netbox_sys_plugin/models/provider.py
@@ -0,0 +1,67 @@
+"""Models definitions"""
+
+from django.core.validators import (
+    RegexValidator,
+)
+from django.urls import reverse
+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
+from netbox.models import NetBoxModel
+
+CLUSTER_ASSIGNMENT_MODELS = models.Q(models.Q(app_label="virtualization", model="Cluster"))
+
+
+
+class ProviderCredentials(NetBoxModel):
+    """Cluster ProviderInfo definition class"""
+
+    provider_url = models.CharField(
+        default=None, blank=True, null=True,
+        max_length=200)
+    
+    provider_username = models.CharField(
+        default=None, blank=True, null=True,
+        max_length=200)
+
+    provider_password = models.CharField(
+        default=None, blank=True, null=True,
+        max_length=200)
+
+    assigned_object_type = models.ForeignKey(
+        to=ContentType,
+        limit_choices_to=CLUSTER_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 Information"
+        verbose_name_plural = "Provider Informations"
+
+
+    def __str__(self):
+        return f"{self.provider}"
+
+    def get_absolute_url(self):
+        """override"""
+        return reverse("plugins:netbox_sys_plugin:providercredentials", args=[self.pk])
+
+GenericRelation(
+    to=ProviderCredentials,
+    content_type_field="assigned_object_type",
+    object_id_field="assigned_object_id",
+    related_query_name="Cluster",
+).contribute_to_class(Cluster, "ProviderCredentials")
+
diff --git a/netbox_sys_plugin/navigation.py b/netbox_sys_plugin/navigation.py
index a7dd5e8..6988410 100644
--- a/netbox_sys_plugin/navigation.py
+++ b/netbox_sys_plugin/navigation.py
@@ -10,16 +10,16 @@ vm_maintenance_buttons = [
         icon_class="mdi mdi-plus-thick",
         color=ButtonColorChoices.GREEN,
     ),
-    #PluginMenuButton(
-    #    link="plugins:netbox_sys_plugin:virtualmachinemaintenance_add",
-    #    title="Import",
-    #    icon_class="mdi mdi-upload",
-    #    color=ButtonColorChoices.CYAN,
-    #    #permissions=["netbox_sys_plugin.add_macaddress"],
-    #),
 ]
 
-
+cluster_provider_credentials_buttons = [
+    PluginMenuButton(
+        link="plugins:netbox_sys_plugin:virtualmachinemaintenance_add",
+        title="Add",
+        icon_class="mdi mdi-plus-thick",
+        color=ButtonColorChoices.GREEN,
+    ),
+]
 
 vmMaintenanceItem = [
     PluginMenuItem(
@@ -29,9 +29,19 @@ vmMaintenanceItem = [
     ),
 ]
 
+clusterProviderCredentialsItem = [
+    PluginMenuItem(
+        link="plugins:netbox_sys_plugin:providercredentials_list",
+        link_text="Provider Credentials",
+        buttons=cluster_provider_credentials_buttons,
+    ),
+]
+
 menu = PluginMenu(
     label="Sys",
-    groups=(("Maintenance", vmMaintenanceItem),)
-    ,
+    groups=(
+        ("Maintenance", vmMaintenanceItem),
+        ("Provider", clusterProviderCredentialsItem),
+            ),
     icon_class="mdi mdi-all-inclusive-box-outline",
 )
diff --git a/netbox_sys_plugin/tables.py b/netbox_sys_plugin/tables.py
index f3cb63b..118a627 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
+from .models import VirtualMachineMaintenance, ProviderCredentials
 
 
 class VmMaintenanceTable(NetBoxTable):
@@ -34,3 +34,38 @@ class VmMaintenanceTable(NetBoxTable):
         )
 
         default_columns = ('id','maintenance_window','assigned_object')
+
+
+class ProviderCredentialsTable(NetBoxTable):
+    """VM Maintenance Table definition class"""
+
+    pk = columns.ToggleColumn()
+    id = tables.Column(
+        linkify=False,
+    )
+    
+    assigned_object = tables.Column(
+    linkify=True,
+    orderable=False,
+    verbose_name="Cluster",
+    )
+
+    provider_url = tables.Column(linkify=True)
+    provider_username = tables.Column(linkify=True)
+    provider_password = tables.Column(linkify=True)
+
+    class Meta(NetBoxTable.Meta):
+        """Meta class"""  
+        model = ProviderCredentials
+        fields = (
+            "pk",
+            "id",
+            "provider_url",
+            "provider_username", 
+            "provider_password",
+            "assigned_object",
+            "created",
+            "last_updated",
+        )
+
+        default_columns = ('id','provider_url','provider_username','provider_password','assigned_object')
diff --git a/netbox_sys_plugin/urls.py b/netbox_sys_plugin/urls.py
index 8d9e94d..896246d 100644
--- a/netbox_sys_plugin/urls.py
+++ b/netbox_sys_plugin/urls.py
@@ -17,5 +17,6 @@ urlpatterns = (
     path('vm-maintenance/<int:pk>/journal/', ObjectJournalView.as_view(), name='virtualmachinemaintenance_journal', kwargs={
         'model': models.VirtualMachineMaintenance
     }),
+    path('provider-credentials/', views.ProviderCredentialsView.as_view(), name='providercredentials_list'),
 
 )
diff --git a/netbox_sys_plugin/views.py b/netbox_sys_plugin/views.py
index e4103f0..3b4baaa 100644
--- a/netbox_sys_plugin/views.py
+++ b/netbox_sys_plugin/views.py
@@ -29,3 +29,11 @@ class VmMaintenanceDeleteView(generic.ObjectDeleteView):
     """Vm maintenance delete view definition"""
 
     queryset = models.VirtualMachineMaintenance.objects.all()
+
+class ProviderCredentialsView(generic.ObjectListView):
+    """Cluster Provider Credentials list view definition"""
+
+    queryset = models.ProviderCredentials.objects.all()
+    table = tables.ProviderCredentialsTable
+    #filterset = filtersets.VmMaintenanceFilterSet
+    #filterset_form = forms.VmMaintenanceFilterForm
\ No newline at end of file
-- 
GitLab


From 89b3c765aba29d94c21b67caa24262198294afce Mon Sep 17 00:00:00 2001
From: Frederico Sequeira <frederico.sequeira@ext.ec.europa.eu>
Date: Fri, 15 Nov 2024 14:29:40 +0000
Subject: [PATCH 2/4] =?UTF-8?q?=F0=9F=8D=B1=20Updates=20Provider=20Credent?=
 =?UTF-8?q?ials=20objects?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 netbox_sys_plugin/api/serializers.py          | 20 ++++++-
 netbox_sys_plugin/api/urls.py                 |  3 +-
 netbox_sys_plugin/api/views.py                |  8 ++-
 netbox_sys_plugin/forms.py                    | 60 ++++++++++++++++++-
 netbox_sys_plugin/models/provider.py          |  2 +-
 netbox_sys_plugin/navigation.py               |  2 +-
 netbox_sys_plugin/template_content.py         | 10 +++-
 .../cluster_providercredentials.html          | 47 +++++++++++++++
 .../providercredentials.html                  | 52 ++++++++++++++++
 netbox_sys_plugin/urls.py                     | 13 +++-
 netbox_sys_plugin/views.py                    | 24 +++++++-
 11 files changed, 228 insertions(+), 13 deletions(-)
 create mode 100644 netbox_sys_plugin/templates/netbox_sys_plugin/cluster_providercredentials.html
 create mode 100644 netbox_sys_plugin/templates/netbox_sys_plugin/providercredentials.html

diff --git a/netbox_sys_plugin/api/serializers.py b/netbox_sys_plugin/api/serializers.py
index 3ad0b68..4e7d712 100644
--- a/netbox_sys_plugin/api/serializers.py
+++ b/netbox_sys_plugin/api/serializers.py
@@ -1,6 +1,6 @@
 from rest_framework import serializers
-from virtualization.models import VirtualMachine
-from .. models import VirtualMachineMaintenance
+from virtualization.models import VirtualMachine, Cluster
+from .. models import VirtualMachineMaintenance, ProviderCredentials
 from django.contrib.contenttypes.models import ContentType
 
 class VirtualMachineSerializer(serializers.ModelSerializer):
@@ -8,6 +8,11 @@ class VirtualMachineSerializer(serializers.ModelSerializer):
         model = VirtualMachine
         fields = ['id', 'name']
 
+class ClusterSerializer(serializers.ModelSerializer):
+    class Meta:
+        model = Cluster
+        fields = ['id', 'name']
+
 class VirtualMachineMaintenanceSerializer(serializers.ModelSerializer):
     id = serializers.IntegerField(read_only=True)
     virtual_machine = VirtualMachineSerializer(source='assigned_object', read_only=True)
@@ -18,3 +23,14 @@ class VirtualMachineMaintenanceSerializer(serializers.ModelSerializer):
         model = VirtualMachineMaintenance
         #fields = ['id','maintenance_window','assigned_object_type','assigned_object_id','assigned_object_type','virtual_machine']
         fields = '__all__'
+
+class ProviderCredentialsSerializer(serializers.ModelSerializer):
+    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)
+
+    class Meta:
+        model = ProviderCredentials
+        #fields = ['id','maintenance_window','assigned_object_type','assigned_object_id','assigned_object_type','virtual_machine']
+        fields = '__all__'
\ No newline at end of file
diff --git a/netbox_sys_plugin/api/urls.py b/netbox_sys_plugin/api/urls.py
index bdba566..6f3fb34 100644
--- a/netbox_sys_plugin/api/urls.py
+++ b/netbox_sys_plugin/api/urls.py
@@ -1,8 +1,9 @@
 from rest_framework.routers import DefaultRouter
-from .views import VirtualMachineMaintenanceViewSet
+from .views import VirtualMachineMaintenanceViewSet, ProviderCredentialsViewSet
 
 router = DefaultRouter()
 router.register(r'MaintenanceWindow', VirtualMachineMaintenanceViewSet)
+router.register(r'ProviderCredentials', ProviderCredentialsViewSet)
 
 app_name = "netbox_sys_plugin"
 
diff --git a/netbox_sys_plugin/api/views.py b/netbox_sys_plugin/api/views.py
index 473e143..b375333 100644
--- a/netbox_sys_plugin/api/views.py
+++ b/netbox_sys_plugin/api/views.py
@@ -1,8 +1,8 @@
 from rest_framework import viewsets, status
 from rest_framework.decorators import action
 from rest_framework.response import Response
-from .. models import VirtualMachineMaintenance
-from . serializers import VirtualMachineMaintenanceSerializer
+from .. models import VirtualMachineMaintenance, ProviderCredentials
+from . serializers import VirtualMachineMaintenanceSerializer, ProviderCredentialsSerializer
 
 class VirtualMachineMaintenanceViewSet(viewsets.ModelViewSet):
     queryset = VirtualMachineMaintenance.objects.all()
@@ -10,3 +10,7 @@ class VirtualMachineMaintenanceViewSet(viewsets.ModelViewSet):
     http_method_names = ["get", "post", "patch", "delete", "options"]
 
 
+class ProviderCredentialsViewSet(viewsets.ModelViewSet):
+    queryset = ProviderCredentials.objects.all()
+    serializer_class = ProviderCredentialsSerializer
+    http_method_names = ["get", "post", "patch", "delete", "options"]
\ No newline at end of file
diff --git a/netbox_sys_plugin/forms.py b/netbox_sys_plugin/forms.py
index d8dd189..0ac2861 100644
--- a/netbox_sys_plugin/forms.py
+++ b/netbox_sys_plugin/forms.py
@@ -1,6 +1,6 @@
 """Forms definitions"""
 
-from virtualization.models import VirtualMachine
+from virtualization.models import VirtualMachine, Cluster
 from django.contrib.contenttypes.models import ContentType
 from django.utils.translation import gettext_lazy as _
 from django.core.exceptions import ValidationError
@@ -11,7 +11,7 @@ from netbox.forms import (
     NetBoxModelFilterSetForm,
 )
 from utilities.forms.fields import DynamicModelChoiceField
-from .models import  VirtualMachineMaintenance
+from .models import  VirtualMachineMaintenance, ProviderCredentials
 
 class VmMaitenanceForm(NetBoxModelForm):
     """
@@ -102,3 +102,59 @@ class VmMaintenanceFilterForm(NetBoxModelFilterSetForm):
     """MacAddress filter form definition class"""
 
     model = VirtualMachineMaintenance
+
+
+class ProviderCredentialsForm(NetBoxModelForm):
+    """
+    GUI form to add or edit a Provider Credentials.
+    """
+
+    cluster = DynamicModelChoiceField(
+        queryset=Cluster.objects.all(), required=False, label="Cluster"
+    )
+    provider_url = forms.CharField(
+        max_length=500, min_length=1, required=False, label="Provider URL"
+    )
+    provider_username = forms.CharField(
+        max_length=500, min_length=1, required=False, label="Provider Username"
+    )
+    provider_password = forms.CharField(
+        max_length=500, min_length=1, required=False, label="Provider Password"
+    )
+
+    def __init__(self, *args, **kwargs):
+        # Initialize helper selectors
+        instance = kwargs.get("instance")
+        initial = kwargs.get("initial", {}).copy()
+        if instance:
+            if isinstance(instance.assigned_object, Cluster):
+                initial["cluster"] = instance.assigned_object
+
+        kwargs["initial"] = initial
+        super().__init__(*args, **kwargs)
+
+    class Meta:
+        """Meta class"""
+        model = ProviderCredentials
+        fields = ('provider_url', 'provider_username', 'provider_password', 'cluster')
+
+    def clean(self):
+        """
+        Validates form inputs before submitting:
+        """
+        super().clean()
+
+        if self.errors.get("mac_address"):
+            return
+
+    def save(self, *args, **kwargs):
+        # Set assigned object
+        self.instance.assigned_object = (
+            self.cleaned_data.get("cluster")
+        )
+        return super().save(*args, **kwargs)
+
+class ProviderCredentialsFilterForm(NetBoxModelFilterSetForm):
+    """Provider Credentials filter form definition class"""
+
+    model = ProviderCredentials
\ No newline at end of file
diff --git a/netbox_sys_plugin/models/provider.py b/netbox_sys_plugin/models/provider.py
index 327afbc..2cfe425 100644
--- a/netbox_sys_plugin/models/provider.py
+++ b/netbox_sys_plugin/models/provider.py
@@ -52,7 +52,7 @@ class ProviderCredentials(NetBoxModel):
 
 
     def __str__(self):
-        return f"{self.provider}"
+        return f"{self.provider_url}"
 
     def get_absolute_url(self):
         """override"""
diff --git a/netbox_sys_plugin/navigation.py b/netbox_sys_plugin/navigation.py
index 6988410..2a39a83 100644
--- a/netbox_sys_plugin/navigation.py
+++ b/netbox_sys_plugin/navigation.py
@@ -14,7 +14,7 @@ vm_maintenance_buttons = [
 
 cluster_provider_credentials_buttons = [
     PluginMenuButton(
-        link="plugins:netbox_sys_plugin:virtualmachinemaintenance_add",
+        link="plugins:netbox_sys_plugin:providercredentials_add",
         title="Add",
         icon_class="mdi mdi-plus-thick",
         color=ButtonColorChoices.GREEN,
diff --git a/netbox_sys_plugin/template_content.py b/netbox_sys_plugin/template_content.py
index 7ff699e..d23d99b 100644
--- a/netbox_sys_plugin/template_content.py
+++ b/netbox_sys_plugin/template_content.py
@@ -11,6 +11,14 @@ class VmMaintenanceTable(PluginTemplateExtension):
 
     def left_page(self):
         return self.render('netbox_sys_plugin/vm_vmmaintenance_table.html', extra_context={'vmmaintenanceinfo': models.VirtualMachineMaintenance.objects.filter(VirtualMachine=self.context['object'])})
+    
+class ProviderCredentialsTable(PluginTemplateExtension):
+    """Provider Credentials object template"""
 
+    model = 'virtualization.cluster'
 
-template_extensions = [VmMaintenanceTable]
+    def right_page(self):
+        return self.render('netbox_sys_plugin/cluster_providercredentials.html', extra_context={'providercredentialsinfo': models.ProviderCredentials.objects.filter(Cluster=self.context['object'])})
+
+
+template_extensions = [VmMaintenanceTable,ProviderCredentialsTable]
diff --git a/netbox_sys_plugin/templates/netbox_sys_plugin/cluster_providercredentials.html b/netbox_sys_plugin/templates/netbox_sys_plugin/cluster_providercredentials.html
new file mode 100644
index 0000000..28b10f1
--- /dev/null
+++ b/netbox_sys_plugin/templates/netbox_sys_plugin/cluster_providercredentials.html
@@ -0,0 +1,47 @@
+{% block content %}
+  <div class="row mb-3">
+    <div class="col col-md-12">
+      <div class="card">
+        <h5 class="card-header">Provider Credentials</h5>
+        <div class="card-body">
+          <table class="table table-responsive">
+              <th scope="row">Provider URL</th>
+              <th scope="row">Provider Username</th>
+              <th scope="row">Provider Password</th>
+              {% for providercredentials in providercredentialsinfo %}
+        <tr>
+            <td>
+              {% if providercredentials.provider_url %}
+                <a href="{{ providercredentials.get_absolute_url }}">{{ providercredentials.provider_url }}</a>
+              {% else %}
+                {{ ''|placeholder }}
+              {% endif %}
+            </td>
+            <td>
+              {% if providercredentials.provider_username %}
+                {{ providercredentials.provider_username }}
+              {% else %}
+                {{ ''|placeholder }}
+              {% endif %}
+            </td>
+            <td>
+              {% if providercredentials.provider_password %}
+                {{ providercredentials.provider_password }}
+              {% else %}
+                {{ ''|placeholder }}
+              {% endif %}
+            </td>
+        </tr>
+        {% empty %}
+        <tr>
+            <td colspan="3">No maintenance window 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/providercredentials.html b/netbox_sys_plugin/templates/netbox_sys_plugin/providercredentials.html
new file mode 100644
index 0000000..72c879c
--- /dev/null
+++ b/netbox_sys_plugin/templates/netbox_sys_plugin/providercredentials.html
@@ -0,0 +1,52 @@
+{% extends 'generic/object.html' %}
+
+{% block title%}
+Provider Credentials 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">Provider Credentials</h5>
+      <div class="card-body">
+        <table class="table table-hover attr-table">
+          <tr>
+            <th scope="row">Provider URL</th>
+            <td>{{ object.provider_url }}</td>
+          </tr>
+          <tr>
+            <th scope="row">Provider Username</th>
+            <td>{{ object.provider_username }}</td>
+          </tr>
+          <tr>
+            <th scope="row">Provider Password</th>
+            <td>{{ object.provider_password }}</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">Details</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 896246d..4d3e076 100644
--- a/netbox_sys_plugin/urls.py
+++ b/netbox_sys_plugin/urls.py
@@ -17,6 +17,17 @@ urlpatterns = (
     path('vm-maintenance/<int:pk>/journal/', ObjectJournalView.as_view(), name='virtualmachinemaintenance_journal', kwargs={
         'model': models.VirtualMachineMaintenance
     }),
-    path('provider-credentials/', views.ProviderCredentialsView.as_view(), name='providercredentials_list'),
+    path('provider-credentials/', views.ProviderCredentialsListView.as_view(), name='providercredentials_list'),
+    path('provider-credentials/add/', views.ProviderCredentialsEditView.as_view(), name='providercredentials_add'),
+    path('provider-credentials/<int:pk>/', views.ProviderCredentialsView.as_view(), name='providercredentials'),
+    path('provider-credentials/<int:pk>/edit/', views.ProviderCredentialsEditView.as_view(), name='providercredentials_edit'),
+    path('provider-credentials/<int:pk>/delete/', views.ProviderCredentialsDeleteView.as_view(), name='providercredentials_delete'),
+    path('provider-credentials/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='providercredentials_changelog', kwargs={
+        'model': models.VirtualMachineMaintenance
+    }),
+    path('provider-credentials/<int:pk>/journal/', ObjectJournalView.as_view(), name='providercredentials_journal', kwargs={
+        'model': models.VirtualMachineMaintenance
+    }),
+
 
 )
diff --git a/netbox_sys_plugin/views.py b/netbox_sys_plugin/views.py
index 3b4baaa..24f7a51 100644
--- a/netbox_sys_plugin/views.py
+++ b/netbox_sys_plugin/views.py
@@ -30,10 +30,30 @@ class VmMaintenanceDeleteView(generic.ObjectDeleteView):
 
     queryset = models.VirtualMachineMaintenance.objects.all()
 
-class ProviderCredentialsView(generic.ObjectListView):
+
+class  ProviderCredentialsView(generic.ObjectView):
+    """ Provider Credentials view definition"""
+
+    queryset = (
+        models.ProviderCredentials.objects.all()
+    )
+
+class ProviderCredentialsListView(generic.ObjectListView):
     """Cluster Provider Credentials list view definition"""
 
     queryset = models.ProviderCredentials.objects.all()
     table = tables.ProviderCredentialsTable
     #filterset = filtersets.VmMaintenanceFilterSet
-    #filterset_form = forms.VmMaintenanceFilterForm
\ No newline at end of file
+    #filterset_form = forms.VmMaintenanceFilterForm
+
+class ProviderCredentialsDeleteView(generic.ObjectDeleteView):
+    """Vm maintenance delete view definition"""
+
+    queryset = models.ProviderCredentials.objects.all()
+
+class ProviderCredentialsEditView(generic.ObjectEditView):
+    """
+    Defines the edit view for the Vm maintenance django model.
+    """
+    queryset = models.ProviderCredentials.objects.all()
+    form = forms.ProviderCredentialsForm
\ No newline at end of file
-- 
GitLab


From 32dc7867b70d4741d952929b51a3ada3a9ccc7ce Mon Sep 17 00:00:00 2001
From: Frederico Sequeira <frederico.sequeira@ext.ec.europa.eu>
Date: Fri, 15 Nov 2024 17:09:14 +0000
Subject: [PATCH 3/4] =?UTF-8?q?=F0=9F=8E=A8=20Improve=20structure=20of=20c?=
 =?UTF-8?q?ode=20and=20add=20validations?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 netbox_sys_plugin/forms/__init__.py           |  4 +
 .../{forms.py => forms/maintenance.py}        | 73 ++------------
 netbox_sys_plugin/forms/provider.py           | 98 +++++++++++++++++++
 netbox_sys_plugin/models/maintenance.py       |  2 +-
 netbox_sys_plugin/models/provider.py          | 13 ++-
 netbox_sys_plugin/tables.py                   |  4 +-
 .../cluster_providercredentials.html          |  8 --
 .../providercredentials.html                  |  8 +-
 8 files changed, 124 insertions(+), 86 deletions(-)
 create mode 100644 netbox_sys_plugin/forms/__init__.py
 rename netbox_sys_plugin/{forms.py => forms/maintenance.py} (58%)
 create mode 100644 netbox_sys_plugin/forms/provider.py

diff --git a/netbox_sys_plugin/forms/__init__.py b/netbox_sys_plugin/forms/__init__.py
new file mode 100644
index 0000000..0f3ba6e
--- /dev/null
+++ b/netbox_sys_plugin/forms/__init__.py
@@ -0,0 +1,4 @@
+"""Forms definitions"""
+
+from .maintenance import *
+from .provider import *
\ No newline at end of file
diff --git a/netbox_sys_plugin/forms.py b/netbox_sys_plugin/forms/maintenance.py
similarity index 58%
rename from netbox_sys_plugin/forms.py
rename to netbox_sys_plugin/forms/maintenance.py
index 0ac2861..46618c6 100644
--- a/netbox_sys_plugin/forms.py
+++ b/netbox_sys_plugin/forms/maintenance.py
@@ -1,6 +1,6 @@
 """Forms definitions"""
 
-from virtualization.models import VirtualMachine, Cluster
+from virtualization.models import VirtualMachine
 from django.contrib.contenttypes.models import ContentType
 from django.utils.translation import gettext_lazy as _
 from django.core.exceptions import ValidationError
@@ -11,7 +11,7 @@ from netbox.forms import (
     NetBoxModelFilterSetForm,
 )
 from utilities.forms.fields import DynamicModelChoiceField
-from .models import  VirtualMachineMaintenance, ProviderCredentials
+from ..models import  VirtualMachineMaintenance
 
 class VmMaitenanceForm(NetBoxModelForm):
     """
@@ -41,19 +41,16 @@ class VmMaitenanceForm(NetBoxModelForm):
             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)
-        #).values_list('assigned_object_id', flat=True)
         ).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')
+        fields = ( 'virtual_machine','maintenance_window','tags')
 
     def clean_virtual_machine(self):
 
@@ -65,9 +62,7 @@ class VmMaitenanceForm(NetBoxModelForm):
             assigned_object_type=virtual_machine_ct,
             assigned_object_id=virtual_machine.id
         ).exclude(pk=self.instance.pk).exists():
-            raise ValidationError(
-                "The virtual Machine already has a maintenance windows associates"
-            )
+            raise ValidationError()
         return virtual_machine
     
 
@@ -86,7 +81,7 @@ class VmMaitenanceForm(NetBoxModelForm):
         #Check if VM is assigned corretly
         if (not virtual_machine):
             raise ValidationError(
-                {"__all__": "Virtual Machine already with maintenance window"},
+                {"__all__": "A Virtual Machine needs to be associated with the maintenance window"},
             )
 
 
@@ -101,60 +96,4 @@ class VmMaitenanceForm(NetBoxModelForm):
 class VmMaintenanceFilterForm(NetBoxModelFilterSetForm):
     """MacAddress filter form definition class"""
 
-    model = VirtualMachineMaintenance
-
-
-class ProviderCredentialsForm(NetBoxModelForm):
-    """
-    GUI form to add or edit a Provider Credentials.
-    """
-
-    cluster = DynamicModelChoiceField(
-        queryset=Cluster.objects.all(), required=False, label="Cluster"
-    )
-    provider_url = forms.CharField(
-        max_length=500, min_length=1, required=False, label="Provider URL"
-    )
-    provider_username = forms.CharField(
-        max_length=500, min_length=1, required=False, label="Provider Username"
-    )
-    provider_password = forms.CharField(
-        max_length=500, min_length=1, required=False, label="Provider Password"
-    )
-
-    def __init__(self, *args, **kwargs):
-        # Initialize helper selectors
-        instance = kwargs.get("instance")
-        initial = kwargs.get("initial", {}).copy()
-        if instance:
-            if isinstance(instance.assigned_object, Cluster):
-                initial["cluster"] = instance.assigned_object
-
-        kwargs["initial"] = initial
-        super().__init__(*args, **kwargs)
-
-    class Meta:
-        """Meta class"""
-        model = ProviderCredentials
-        fields = ('provider_url', 'provider_username', 'provider_password', 'cluster')
-
-    def clean(self):
-        """
-        Validates form inputs before submitting:
-        """
-        super().clean()
-
-        if self.errors.get("mac_address"):
-            return
-
-    def save(self, *args, **kwargs):
-        # Set assigned object
-        self.instance.assigned_object = (
-            self.cleaned_data.get("cluster")
-        )
-        return super().save(*args, **kwargs)
-
-class ProviderCredentialsFilterForm(NetBoxModelFilterSetForm):
-    """Provider Credentials filter form definition class"""
-
-    model = ProviderCredentials
\ No newline at end of file
+    model = VirtualMachineMaintenance
\ No newline at end of file
diff --git a/netbox_sys_plugin/forms/provider.py b/netbox_sys_plugin/forms/provider.py
new file mode 100644
index 0000000..0463217
--- /dev/null
+++ b/netbox_sys_plugin/forms/provider.py
@@ -0,0 +1,98 @@
+"""Forms definitions"""
+
+from virtualization.models import Cluster
+from django.contrib.contenttypes.models import ContentType
+from django.utils.translation import gettext_lazy as _
+from django.core.exceptions import ValidationError
+from django import forms
+from netbox.forms import (
+    NetBoxModelForm,
+    NetBoxModelFilterSetForm,
+)
+from utilities.forms.fields import DynamicModelChoiceField
+from ..models import ProviderCredentials
+
+class ProviderCredentialsForm(NetBoxModelForm):
+    """
+    GUI form to add or edit a Provider Credentials.
+    """
+
+    cluster = DynamicModelChoiceField(
+        queryset=Cluster.objects.all(), required=True, label="Cluster"
+    )
+    provider_url = forms.CharField(
+        max_length=200, min_length=1, required=True, label="URL"
+    )
+    provider_username = forms.CharField(
+        max_length=50, min_length=1, required=True, label="Username"
+    )
+    provider_password = forms.CharField(
+        max_length=20, min_length=1, required=True, label="Password"
+    )
+
+    def __init__(self, *args, **kwargs):
+        # Initialize helper selectors
+        instance = kwargs.get("instance")
+        initial = kwargs.get("initial", {}).copy()
+        if instance:
+            if isinstance(instance.assigned_object, Cluster):
+                initial["cluster"] = instance.assigned_object
+        kwargs["initial"] = initial
+        super().__init__(*args, **kwargs)
+
+        if instance:
+            current_cluster_id = instance.assigned_object.id if instance.assigned_object else None
+        else:
+            current_cluster_id = None
+
+        assigned_clusters = ProviderCredentials.objects.filter(
+            assigned_object_type=ContentType.objects.get_for_model(Cluster)
+        ).exclude(assigned_object_id=current_cluster_id).values_list('assigned_object_id', flat=True)
+        self.fields['cluster'].queryset = Cluster.objects.exclude(id__in=assigned_clusters)
+
+    class Meta:
+        """Meta class"""
+        model = ProviderCredentials
+        fields = ('cluster','provider_url', 'provider_username', 'provider_password','tags')
+
+    def clean_cluster(self):
+
+        cluster = self.cleaned_data.get("cluster")
+
+        cluster_ct = ContentType.objects.get_for_model(Cluster)
+
+        if ProviderCredentials.objects.filter(
+            assigned_object_type=cluster_ct,
+            assigned_object_id=cluster.id
+        ).exclude(pk=self.instance.pk).exists():
+            raise ValidationError()
+        return cluster
+
+    def clean(self):
+        """
+        Validates form inputs before submitting:
+        """
+        super().clean()
+
+        if self.errors.get(f"provider_url"):
+            return
+        
+        cluster = self.cleaned_data.get("cluster")
+
+        #Check if cluster is assigned corretly
+        if (not cluster):
+            raise ValidationError(
+                {"__all__": "A Virtual Machine needs to be associated with the maintenance window"},
+            )
+
+    def save(self, *args, **kwargs):
+        # Set assigned object
+        self.instance.assigned_object = (
+            self.cleaned_data.get("cluster")
+        )
+        return super().save(*args, **kwargs)
+
+class ProviderCredentialsFilterForm(NetBoxModelFilterSetForm):
+    """Provider Credentials filter form definition class"""
+
+    model = ProviderCredentials
\ No newline at end of file
diff --git a/netbox_sys_plugin/models/maintenance.py b/netbox_sys_plugin/models/maintenance.py
index e3c1bf8..53e1a81 100644
--- a/netbox_sys_plugin/models/maintenance.py
+++ b/netbox_sys_plugin/models/maintenance.py
@@ -51,7 +51,7 @@ class VirtualMachineMaintenance(NetBoxModel):
 
 
     def __str__(self):
-        return f"{self.maintenance_window}"
+        return f"For Virtual machine ID {self.assigned_object_id}"
 
     def get_absolute_url(self):
         """override"""
diff --git a/netbox_sys_plugin/models/provider.py b/netbox_sys_plugin/models/provider.py
index 2cfe425..5d52a32 100644
--- a/netbox_sys_plugin/models/provider.py
+++ b/netbox_sys_plugin/models/provider.py
@@ -4,6 +4,9 @@ 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
@@ -20,7 +23,9 @@ class ProviderCredentials(NetBoxModel):
 
     provider_url = models.CharField(
         default=None, blank=True, null=True,
-        max_length=200)
+        max_length=200,
+        validators=[URLValidator(schemes=["http", "https"])],
+        )
     
     provider_username = models.CharField(
         default=None, blank=True, null=True,
@@ -47,12 +52,12 @@ class ProviderCredentials(NetBoxModel):
         """Meta class"""
         unique_together = ["assigned_object_id"]
         ordering = ["assigned_object_id"]
-        verbose_name = "Provider Information"
-        verbose_name_plural = "Provider Informations"
+        verbose_name = "Provider Credential"
+        verbose_name_plural = "Provider Credentials"
 
 
     def __str__(self):
-        return f"{self.provider_url}"
+        return f"for Cluster ID {self.assigned_object_id}"
 
     def get_absolute_url(self):
         """override"""
diff --git a/netbox_sys_plugin/tables.py b/netbox_sys_plugin/tables.py
index 118a627..8c9eda6 100644
--- a/netbox_sys_plugin/tables.py
+++ b/netbox_sys_plugin/tables.py
@@ -51,8 +51,8 @@ class ProviderCredentialsTable(NetBoxTable):
     )
 
     provider_url = tables.Column(linkify=True)
-    provider_username = tables.Column(linkify=True)
-    provider_password = tables.Column(linkify=True)
+    provider_username = tables.Column(linkify=False)
+    provider_password = tables.Column(linkify=False)
 
     class Meta(NetBoxTable.Meta):
         """Meta class"""  
diff --git a/netbox_sys_plugin/templates/netbox_sys_plugin/cluster_providercredentials.html b/netbox_sys_plugin/templates/netbox_sys_plugin/cluster_providercredentials.html
index 28b10f1..ef300cc 100644
--- a/netbox_sys_plugin/templates/netbox_sys_plugin/cluster_providercredentials.html
+++ b/netbox_sys_plugin/templates/netbox_sys_plugin/cluster_providercredentials.html
@@ -7,7 +7,6 @@
           <table class="table table-responsive">
               <th scope="row">Provider URL</th>
               <th scope="row">Provider Username</th>
-              <th scope="row">Provider Password</th>
               {% for providercredentials in providercredentialsinfo %}
         <tr>
             <td>
@@ -24,13 +23,6 @@
                 {{ ''|placeholder }}
               {% endif %}
             </td>
-            <td>
-              {% if providercredentials.provider_password %}
-                {{ providercredentials.provider_password }}
-              {% else %}
-                {{ ''|placeholder }}
-              {% endif %}
-            </td>
         </tr>
         {% empty %}
         <tr>
diff --git a/netbox_sys_plugin/templates/netbox_sys_plugin/providercredentials.html b/netbox_sys_plugin/templates/netbox_sys_plugin/providercredentials.html
index 72c879c..b86b1ec 100644
--- a/netbox_sys_plugin/templates/netbox_sys_plugin/providercredentials.html
+++ b/netbox_sys_plugin/templates/netbox_sys_plugin/providercredentials.html
@@ -12,15 +12,15 @@ Provider Credentials for {{ object.assigned_object }}
       <div class="card-body">
         <table class="table table-hover attr-table">
           <tr>
-            <th scope="row">Provider URL</th>
+            <th scope="row">URL</th>
             <td>{{ object.provider_url }}</td>
           </tr>
           <tr>
-            <th scope="row">Provider Username</th>
+            <th scope="row">Username</th>
             <td>{{ object.provider_username }}</td>
           </tr>
           <tr>
-            <th scope="row">Provider Password</th>
+            <th scope="row">Password</th>
             <td>{{ object.provider_password }}</td>
           </tr>
         </table>
@@ -31,7 +31,7 @@ Provider Credentials for {{ object.assigned_object }}
   </div>
   <div class="col col-md-6">
     <div class="card">
-      <h5 class="card-header">Details</h5>
+      <h5 class="card-header">Assigned Object</h5>
       <div class="card-body">
         <table class="table table-hover attr-table">
           <tr>
-- 
GitLab


From ef5c56ad1f0428baeb74e32fd7ea70404b3eba5c Mon Sep 17 00:00:00 2001
From: Frederico Sequeira <frederico.sequeira@ext.ec.europa.eu>
Date: Mon, 18 Nov 2024 15:21:46 +0000
Subject: [PATCH 4/4] =?UTF-8?q?=F0=9F=8E=A8=20Add=20validations=20and=20va?=
 =?UTF-8?q?ult=20path=20url?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 docs/model/netbox_sys_provider_credentials.md |  2 +-
 netbox_sys_plugin/filtersets.py               | 31 +++++++++++++++++--
 netbox_sys_plugin/forms/provider.py           |  6 ++--
 ...maintenance_maintenance_window_and_more.py | 12 +++----
 netbox_sys_plugin/models/provider.py          | 10 +++---
 netbox_sys_plugin/tables.py                   | 26 +++++++++++-----
 .../cluster_providercredentials.html          | 12 +++++--
 .../providercredentials.html                  |  4 +--
 netbox_sys_plugin/views.py                    |  4 +--
 9 files changed, 76 insertions(+), 31 deletions(-)

diff --git a/docs/model/netbox_sys_provider_credentials.md b/docs/model/netbox_sys_provider_credentials.md
index 11f23c6..0876ed0 100644
--- a/docs/model/netbox_sys_provider_credentials.md
+++ b/docs/model/netbox_sys_provider_credentials.md
@@ -9,4 +9,4 @@
 | 	assigned_object_id	        | 	Big (8 byte) integer	        | 	cluster type unique ID	                                                |
 | 	provider_url                | 	String              	        | 	Virtual Machine type name                                               |
 | 	provider_name               | 	String              	        | 	Virtual Machine type description	                                    |
-| 	provider_password           | 	String              	        | 	Virtual Machine type description	                                    |
\ No newline at end of file
+| 	provider_password_vault_url | 	String              	        | 	Virtual Machine type description	                                    |
\ No newline at end of file
diff --git a/netbox_sys_plugin/filtersets.py b/netbox_sys_plugin/filtersets.py
index 5748c46..75a77e0 100644
--- a/netbox_sys_plugin/filtersets.py
+++ b/netbox_sys_plugin/filtersets.py
@@ -2,8 +2,9 @@
 
 import django_filters
 from netbox.filtersets import NetBoxModelFilterSet
-from virtualization.models import VirtualMachine
-from .models import VirtualMachineMaintenance
+from virtualization.models import VirtualMachine, Cluster
+from django.db.models import Q
+from .models import VirtualMachineMaintenance, ProviderCredentials
 
 
 class VmMaintenanceFilterSet(NetBoxModelFilterSet):
@@ -24,4 +25,28 @@ class VmMaintenanceFilterSet(NetBoxModelFilterSet):
     # pylint: disable=W0613
     def search(self, queryset, name, value):
         """override"""
-        queryset.filter(virtual_machine__icontains=value)
+        if not value.strip():
+            return queryset
+        return queryset.filter(Q(maintenance_window__icontains=value))
+
+class ProviderCredentialsFilterSet(NetBoxModelFilterSet):
+    """ProviderCredentials filterset definition class"""
+
+    cluster = django_filters.ModelMultipleChoiceFilter(
+        field_name="Cluster__name",
+        queryset=Cluster.objects.all(),
+        to_field_name="name",
+        label="Virtual Machine (name)",
+    )
+
+    class Meta:
+        """Meta class"""     
+        model = ProviderCredentials
+        fields = ('provider_url','provider_username','provider_password_vault_url','cluster')
+
+    # pylint: disable=W0613
+    def search(self, queryset, name, value):
+        """override"""
+        if not value.strip():
+            return queryset
+        return queryset.filter(Q(provider_username__icontains=value)|Q(provider_url__icontains=value)|Q(provider_password_vault_url__icontains=value))
\ No newline at end of file
diff --git a/netbox_sys_plugin/forms/provider.py b/netbox_sys_plugin/forms/provider.py
index 0463217..1909d38 100644
--- a/netbox_sys_plugin/forms/provider.py
+++ b/netbox_sys_plugin/forms/provider.py
@@ -26,8 +26,8 @@ class ProviderCredentialsForm(NetBoxModelForm):
     provider_username = forms.CharField(
         max_length=50, min_length=1, required=True, label="Username"
     )
-    provider_password = forms.CharField(
-        max_length=20, min_length=1, required=True, label="Password"
+    provider_password_vault_url = forms.CharField(
+        max_length=200, min_length=1, required=True, label="Password Vault URL"
     )
 
     def __init__(self, *args, **kwargs):
@@ -53,7 +53,7 @@ class ProviderCredentialsForm(NetBoxModelForm):
     class Meta:
         """Meta class"""
         model = ProviderCredentials
-        fields = ('cluster','provider_url', 'provider_username', 'provider_password','tags')
+        fields = ('cluster','provider_url', 'provider_username', 'provider_password_vault_url','tags')
 
     def clean_cluster(self):
 
diff --git a/netbox_sys_plugin/migrations/0002_alter_virtualmachinemaintenance_maintenance_window_and_more.py b/netbox_sys_plugin/migrations/0002_alter_virtualmachinemaintenance_maintenance_window_and_more.py
index 9c0f99e..64b5bde 100644
--- a/netbox_sys_plugin/migrations/0002_alter_virtualmachinemaintenance_maintenance_window_and_more.py
+++ b/netbox_sys_plugin/migrations/0002_alter_virtualmachinemaintenance_maintenance_window_and_more.py
@@ -1,4 +1,4 @@
-# Generated by Django 4.2.16 on 2024-11-15 09:53
+# Generated by Django 4.2.16 on 2024-11-18 15:20
 
 import django.core.validators
 from django.db import migrations, models
@@ -28,16 +28,16 @@ class Migration(migrations.Migration):
                 ('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_url', models.CharField(blank=True, default=None, max_length=200, null=True)),
-                ('provider_username', models.CharField(blank=True, default=None, max_length=200, null=True)),
-                ('provider_password', models.CharField(blank=True, default=None, max_length=200, null=True)),
+                ('provider_url', models.CharField(blank=True, default=None, max_length=100, null=True, validators=[django.core.validators.URLValidator(schemes=['http', 'https'])])),
+                ('provider_username', models.CharField(blank=True, default=None, max_length=50, null=True)),
+                ('provider_password_vault_url', models.CharField(blank=True, default=None, max_length=200, null=True, validators=[django.core.validators.URLValidator(schemes=['http', 'https'])])),
                 ('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', 'Cluster'))), 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 Information',
-                'verbose_name_plural': 'Provider Informations',
+                'verbose_name': 'Provider Credential',
+                'verbose_name_plural': 'Provider Credentials',
                 'ordering': ['assigned_object_id'],
                 'unique_together': {('assigned_object_id',)},
             },
diff --git a/netbox_sys_plugin/models/provider.py b/netbox_sys_plugin/models/provider.py
index 5d52a32..eb1cc24 100644
--- a/netbox_sys_plugin/models/provider.py
+++ b/netbox_sys_plugin/models/provider.py
@@ -23,17 +23,19 @@ class ProviderCredentials(NetBoxModel):
 
     provider_url = models.CharField(
         default=None, blank=True, null=True,
-        max_length=200,
+        max_length=100,
         validators=[URLValidator(schemes=["http", "https"])],
         )
     
     provider_username = models.CharField(
         default=None, blank=True, null=True,
-        max_length=200)
+        max_length=50)
 
-    provider_password = models.CharField(
+    provider_password_vault_url = models.CharField(
         default=None, blank=True, null=True,
-        max_length=200)
+        max_length=200,
+        validators=[URLValidator(schemes=["http", "https"])],
+        )
 
     assigned_object_type = models.ForeignKey(
         to=ContentType,
diff --git a/netbox_sys_plugin/tables.py b/netbox_sys_plugin/tables.py
index 8c9eda6..bea34c6 100644
--- a/netbox_sys_plugin/tables.py
+++ b/netbox_sys_plugin/tables.py
@@ -45,14 +45,24 @@ class ProviderCredentialsTable(NetBoxTable):
     )
     
     assigned_object = tables.Column(
-    linkify=True,
-    orderable=False,
-    verbose_name="Cluster",
+        linkify=True,
+        orderable=False,
+        verbose_name="Cluster",
+    )
+
+    provider_url = tables.Column(
+        linkify=True,
+        verbose_name="URL",
     )
+    provider_username = tables.Column(
+        linkify=False,
+        verbose_name="Username",
 
-    provider_url = tables.Column(linkify=True)
-    provider_username = tables.Column(linkify=False)
-    provider_password = tables.Column(linkify=False)
+    )
+    provider_password_vault_url = tables.Column(
+        linkify=False,
+        verbose_name="Password Vault URL",
+    )
 
     class Meta(NetBoxTable.Meta):
         """Meta class"""  
@@ -62,10 +72,10 @@ class ProviderCredentialsTable(NetBoxTable):
             "id",
             "provider_url",
             "provider_username", 
-            "provider_password",
+            "provider_password_vault_url",
             "assigned_object",
             "created",
             "last_updated",
         )
 
-        default_columns = ('id','provider_url','provider_username','provider_password','assigned_object')
+        default_columns = ('id','provider_url','provider_username','provider_password_vault_url','assigned_object')
diff --git a/netbox_sys_plugin/templates/netbox_sys_plugin/cluster_providercredentials.html b/netbox_sys_plugin/templates/netbox_sys_plugin/cluster_providercredentials.html
index ef300cc..82082e0 100644
--- a/netbox_sys_plugin/templates/netbox_sys_plugin/cluster_providercredentials.html
+++ b/netbox_sys_plugin/templates/netbox_sys_plugin/cluster_providercredentials.html
@@ -5,8 +5,9 @@
         <h5 class="card-header">Provider Credentials</h5>
         <div class="card-body">
           <table class="table table-responsive">
-              <th scope="row">Provider URL</th>
-              <th scope="row">Provider Username</th>
+              <th scope="row">URL</th>
+              <th scope="row">Username</th>
+              <th scope="row">Passworl Vault URL</th>
               {% for providercredentials in providercredentialsinfo %}
         <tr>
             <td>
@@ -23,6 +24,13 @@
                 {{ ''|placeholder }}
               {% endif %}
             </td>
+            <td>
+              {% if providercredentials.provider_password_vault_url %}
+                {{ providercredentials.provider_password_vault_url }}
+              {% else %}
+                {{ ''|placeholder }}
+              {% endif %}
+            </td>
         </tr>
         {% empty %}
         <tr>
diff --git a/netbox_sys_plugin/templates/netbox_sys_plugin/providercredentials.html b/netbox_sys_plugin/templates/netbox_sys_plugin/providercredentials.html
index b86b1ec..d18f35b 100644
--- a/netbox_sys_plugin/templates/netbox_sys_plugin/providercredentials.html
+++ b/netbox_sys_plugin/templates/netbox_sys_plugin/providercredentials.html
@@ -20,8 +20,8 @@ Provider Credentials for {{ object.assigned_object }}
             <td>{{ object.provider_username }}</td>
           </tr>
           <tr>
-            <th scope="row">Password</th>
-            <td>{{ object.provider_password }}</td>
+            <th scope="row">Password vault URL</th>
+            <td>{{ object.provider_password_vault_url }}</td>
           </tr>
         </table>
       </div>
diff --git a/netbox_sys_plugin/views.py b/netbox_sys_plugin/views.py
index 24f7a51..f80387d 100644
--- a/netbox_sys_plugin/views.py
+++ b/netbox_sys_plugin/views.py
@@ -43,8 +43,8 @@ class ProviderCredentialsListView(generic.ObjectListView):
 
     queryset = models.ProviderCredentials.objects.all()
     table = tables.ProviderCredentialsTable
-    #filterset = filtersets.VmMaintenanceFilterSet
-    #filterset_form = forms.VmMaintenanceFilterForm
+    filterset = filtersets.ProviderCredentialsFilterSet
+    filterset_form = forms.ProviderCredentialsFilterForm
 
 class ProviderCredentialsDeleteView(generic.ObjectDeleteView):
     """Vm maintenance delete view definition"""
-- 
GitLab