diff --git a/src/servala/core/admin.py b/src/servala/core/admin.py index 149635c..0b1b980 100644 --- a/src/servala/core/admin.py +++ b/src/servala/core/admin.py @@ -191,6 +191,15 @@ class ControlPlaneAdmin(admin.ModelAdmin): ), }, ), + ( + _("User Information"), + { + "fields": ("user_info",), + "description": _( + "Key-value information displayed to users when selecting this control plane." + ), + }, + ), ) def get_exclude(self, request, obj=None): diff --git a/src/servala/core/forms.py b/src/servala/core/forms.py index 1fece41..d44ce35 100644 --- a/src/servala/core/forms.py +++ b/src/servala/core/forms.py @@ -1,8 +1,20 @@ from django import forms from django.utils.translation import gettext_lazy as _ +from django_jsonform.widgets import JSONFormWidget from servala.core.models import ControlPlane, ServiceDefinition +CONTROL_PLANE_USER_INFO_SCHEMA = { + "type": "object", + "properties": { + "cname": { + "title": "CNAME Record", + "type": "string", + }, + }, + "additionalProperties": {"type": "string"}, +} + class ControlPlaneAdminForm(forms.ModelForm): certificate_authority_data = forms.CharField( @@ -23,6 +35,7 @@ class ControlPlaneAdminForm(forms.ModelForm): class Meta: model = ControlPlane fields = "__all__" + widgets = {"user_info": JSONFormWidget(schema=CONTROL_PLANE_USER_INFO_SCHEMA)} def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) diff --git a/src/servala/core/migrations/0006_add_controlplane_user_info.py b/src/servala/core/migrations/0006_add_controlplane_user_info.py new file mode 100644 index 0000000..0d4936c --- /dev/null +++ b/src/servala/core/migrations/0006_add_controlplane_user_info.py @@ -0,0 +1,23 @@ +# Generated by Django 5.2.4 on 2025-09-04 11:29 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("core", "0005_organization_sale_order_fields"), + ] + + operations = [ + migrations.AddField( + model_name="controlplane", + name="user_info", + field=models.JSONField( + blank=True, + help_text="Key-value information displayed to users when selecting this control plane", + null=True, + verbose_name="User Information", + ), + ), + ] diff --git a/src/servala/core/models/service.py b/src/servala/core/models/service.py index 6944750..3677cca 100644 --- a/src/servala/core/models/service.py +++ b/src/servala/core/models/service.py @@ -144,6 +144,14 @@ class ControlPlane(ServalaModelMixin, models.Model): related_name="control_planes", verbose_name=_("Cloud provider"), ) + user_info = models.JSONField( + null=True, + blank=True, + verbose_name=_("User Information"), + help_text=_( + "Key-value information displayed to users when selecting this control plane" + ), + ) class Meta: verbose_name = _("Control plane") diff --git a/src/servala/frontend/templates/frontend/organizations/service_instance_detail.html b/src/servala/frontend/templates/frontend/organizations/service_instance_detail.html index a12efcf..4027ec5 100644 --- a/src/servala/frontend/templates/frontend/organizations/service_instance_detail.html +++ b/src/servala/frontend/templates/frontend/organizations/service_instance_detail.html @@ -58,8 +58,8 @@ - {% if instance.status_conditions %} -
+
+ {% if instance.status_conditions %}

{% translate "Status" %}

@@ -101,8 +101,29 @@
-
- {% endif %} + {% endif %} + {% if instance.context.control_plane.user_info %} +
+
+

{% translate "Service Provider Information" %}

+
+
+
+ + + {% for key, value in instance.context.control_plane.user_info.items %} + + + + + {% endfor %} + +
{{ key }}{{ value }}
+
+
+
+ {% endif %} + {% if instance.spec and spec_fieldsets %}
diff --git a/src/servala/frontend/templates/frontend/organizations/service_offering_detail.html b/src/servala/frontend/templates/frontend/organizations/service_offering_detail.html index 56dc7e2..dd35cce 100644 --- a/src/servala/frontend/templates/frontend/organizations/service_offering_detail.html +++ b/src/servala/frontend/templates/frontend/organizations/service_offering_detail.html @@ -7,6 +7,29 @@ {{ offering }} {% endblock page_title %} {% endblock html_title %} +{% partialdef control-plane-info %} +{% if selected_plane and selected_plane.user_info %} +
+
+

{% translate "Service Provider Information" %}

+
+
+
+ + + {% for key, value in selected_plane.user_info.items %} + + + + + {% endfor %} + +
{{ key }}{{ value }}
+
+
+
+{% endif %} +{% endpartialdef %} {% partialdef service-form %} {% if service_form %}
@@ -25,57 +48,69 @@ {% endpartialdef %} {% block content %}
-
-
- {% if service.logo %} - {{ service.name }} - {% endif %} -
-

{{ offering }}

- {{ offering.service.category }} -
-
-
- {% if offering.description %} -
-
-

{{ offering.description|urlize }}

+
+
+
+
+ {% if service.logo %} + {{ service.name }} + {% endif %} +
+

{{ offering }}

+ {{ offering.service.category }}
- {% endif %} - {% if not has_control_planes %} -

{% translate "We currently cannot offer this service, sorry!" %}

- {% else %} -
- {{ select_form }} -
- {% endif %} - {% if service.external_links %} -
-
-
{% translate "External Links" %}
-
- {% for link in service.external_links %} - - {{ link.title }} - - - {% endfor %} +
+ {% if offering.description %} +
+
+

{{ offering.description|urlize }}

+
-
+ {% endif %} + {% if not has_control_planes %} +

{% translate "We currently cannot offer this service, sorry!" %}

+ {% else %} +
+ {{ select_form }} +
+ {% endif %} + {% if service.external_links %} +
+
+
{% translate "External Links" %}
+
+ {% for link in service.external_links %} + + {{ link.title }} + + + {% endfor %} +
+
+
+ {% endif %}
+
+
{% partial service-form %}
+
+
+ {% if has_control_planes %} +
{% partial control-plane-info %}
{% endif %}
-
{% partial service-form %}
{% endblock content %} diff --git a/src/servala/frontend/views/service.py b/src/servala/frontend/views/service.py index 201a510..7e3f162 100644 --- a/src/servala/frontend/views/service.py +++ b/src/servala/frontend/views/service.py @@ -73,7 +73,7 @@ class ServiceOfferingDetailView(OrganizationViewMixin, HtmxViewMixin, DetailView context_object_name = "offering" model = ServiceOffering permission_type = "view" - fragments = ("service-form",) + fragments = ("service-form", "control-plane-info") def has_permission(self): return self.has_organization_permission()