diff --git a/src/servala/core/admin.py b/src/servala/core/admin.py index 2ecdb69..715c861 100644 --- a/src/servala/core/admin.py +++ b/src/servala/core/admin.py @@ -1,6 +1,7 @@ from django.contrib import admin from django.utils.translation import gettext_lazy as _ +from servala.core.forms import ControlPlaneAdminForm from servala.core.models import ( BillingEntity, CloudProvider, @@ -110,11 +111,32 @@ class CloudProviderAdmin(admin.ModelAdmin): @admin.register(ControlPlane) class ControlPlaneAdmin(admin.ModelAdmin): + form = ControlPlaneAdminForm list_display = ("name", "cloud_provider", "k8s_api_endpoint") list_filter = ("cloud_provider",) search_fields = ("name", "description", "k8s_api_endpoint") autocomplete_fields = ("cloud_provider",) + fieldsets = ( + ( + None, + {"fields": ("name", "description", "k8s_api_endpoint", "cloud_provider")}, + ), + ( + _("API Credentials"), + { + "fields": ("certificate_authority_data", "server", "token"), + "description": _( + "All three credential fields must be provided together or left empty." + ), + }, + ), + ) + + def get_exclude(self, request, obj=None): + # Exclude the original api_credentials field as we're using our custom fields + return ["api_credentials"] + @admin.register(Plan) class PlanAdmin(admin.ModelAdmin): diff --git a/src/servala/core/forms.py b/src/servala/core/forms.py new file mode 100644 index 0000000..6c8082a --- /dev/null +++ b/src/servala/core/forms.py @@ -0,0 +1,77 @@ +from django import forms +from django.core.exceptions import ValidationError +from django.utils.translation import gettext_lazy as _ + +from servala.core.models import ControlPlane + + +class ControlPlaneAdminForm(forms.ModelForm): + certificate_authority_data = forms.CharField( + widget=forms.Textarea, + required=False, + help_text=_("Certificate authority data for the Kubernetes API"), + ) + server = forms.URLField( + required=False, + help_text=_("Server URL for the Kubernetes API"), + ) + token = forms.CharField( + widget=forms.Textarea, + required=False, + help_text=_("Authentication token for the Kubernetes API"), + ) + + class Meta: + model = ControlPlane + fields = "__all__" + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + # If we have existing api_credentials, populate the individual fields + if self.instance.pk and self.instance.api_credentials: + creds = self.instance.api_credentials + self.fields["certificate_authority_data"].initial = creds.get( + "certificate-authority-data", "" + ) + self.fields["server"].initial = creds.get("server", "") + self.fields["token"].initial = creds.get("token", "") + + def clean(self): + cleaned_data = super().clean() + + # Get the three credential fields + ca_data = cleaned_data.get("certificate_authority_data") + server = cleaned_data.get("server") + token = cleaned_data.get("token") + + # Check if any of the fields are filled + has_ca = bool(ca_data) + has_server = bool(server) + has_token = bool(token) + + # If some but not all fields are filled, raise validation error + if (has_ca or has_server or has_token) and not ( + has_ca and has_server and has_token + ): + raise ValidationError( + _( + "All credential fields (certificate authority data, server, and token) must be provided together or left empty." + ) + ) + + # If all fields are filled, create the api_credentials JSON + if has_ca and has_server and has_token: + cleaned_data["api_credentials"] = { + "certificate-authority-data": ca_data, + "server": server, + "token": token, + } + else: + cleaned_data["api_credentials"] = {} + + return cleaned_data + + def save(self, *args, **kwargs): + self.instance.api_credentials = self.cleaned_data["api_credentials"] + return super().save(*args, **kwargs) diff --git a/src/servala/core/models/service.py b/src/servala/core/models/service.py index 83cc696..470fcec 100644 --- a/src/servala/core/models/service.py +++ b/src/servala/core/models/service.py @@ -68,7 +68,10 @@ class ControlPlane(models.Model): description = models.TextField(blank=True, verbose_name=_("Description")) k8s_api_endpoint = models.URLField(verbose_name=_("Kubernetes API endpoint")) # TODO: schema - api_credentials = EncryptedJSONField(verbose_name=_("API credentials")) + api_credentials = EncryptedJSONField( + verbose_name=_("API credentials"), + help_text="Required fields: certificate-authority-data, server (URL), token", + ) cloud_provider = models.ForeignKey( to="CloudProvider",