From 09baaa5786e3d809f849682eee15e4c03bef1e91 Mon Sep 17 00:00:00 2001 From: Tobias Kunze Date: Fri, 5 Sep 2025 14:24:46 +0200 Subject: [PATCH 1/2] Show additional ControlPlane information to users --- src/servala/core/admin.py | 9 ++ src/servala/core/forms.py | 13 ++ .../0006_add_controlplane_user_info.py | 23 ++++ src/servala/core/models/service.py | 8 ++ .../service_instance_detail.html | 29 +++- .../service_offering_detail.html | 125 +++++++++++------- src/servala/frontend/views/service.py | 2 +- 7 files changed, 159 insertions(+), 50 deletions(-) create mode 100644 src/servala/core/migrations/0006_add_controlplane_user_info.py 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() -- 2.49.1 From 7acadea4477bba26df6d086570fa4b9bb6856f41 Mon Sep 17 00:00:00 2001 From: Tobias Kunze Date: Fri, 5 Sep 2025 15:50:53 +0200 Subject: [PATCH 2/2] Extract user info into separate field --- src/servala/core/forms.py | 2 +- .../0006_add_controlplane_user_info.py | 23 ---------- .../0007_controlplane_user_info_and_more.py | 43 +++++++++++++++++++ .../service_instance_detail.html | 22 +--------- .../service_offering_detail.html | 22 +--------- .../includes/control_plane_user_info.html | 26 +++++++++++ 6 files changed, 73 insertions(+), 65 deletions(-) delete mode 100644 src/servala/core/migrations/0006_add_controlplane_user_info.py create mode 100644 src/servala/core/migrations/0007_controlplane_user_info_and_more.py create mode 100644 src/servala/frontend/templates/includes/control_plane_user_info.html diff --git a/src/servala/core/forms.py b/src/servala/core/forms.py index d44ce35..baa85fb 100644 --- a/src/servala/core/forms.py +++ b/src/servala/core/forms.py @@ -7,7 +7,7 @@ from servala.core.models import ControlPlane, ServiceDefinition CONTROL_PLANE_USER_INFO_SCHEMA = { "type": "object", "properties": { - "cname": { + "CNAME Record": { "title": "CNAME Record", "type": "string", }, diff --git a/src/servala/core/migrations/0006_add_controlplane_user_info.py b/src/servala/core/migrations/0006_add_controlplane_user_info.py deleted file mode 100644 index 0d4936c..0000000 --- a/src/servala/core/migrations/0006_add_controlplane_user_info.py +++ /dev/null @@ -1,23 +0,0 @@ -# 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/migrations/0007_controlplane_user_info_and_more.py b/src/servala/core/migrations/0007_controlplane_user_info_and_more.py new file mode 100644 index 0000000..5dda2ed --- /dev/null +++ b/src/servala/core/migrations/0007_controlplane_user_info_and_more.py @@ -0,0 +1,43 @@ +# Generated by Django 5.2.6 on 2025-09-08 07:14 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("core", "0006_remove_service_instance_soft_delete"), + ] + + 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", + ), + ), + migrations.AlterField( + model_name="cloudprovider", + name="external_links", + field=models.JSONField( + blank=True, + help_text='JSON array of link objects: {"url": "…", "title": "…"}. ', + null=True, + verbose_name="External links", + ), + ), + migrations.AlterField( + model_name="service", + name="external_links", + field=models.JSONField( + blank=True, + help_text='JSON array of link objects: {"url": "…", "title": "…", "featured": false}. Featured links will be shown on the service list page, all other links will only show on the service and offering detail pages.', + null=True, + verbose_name="External links", + ), + ), + ] 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 4027ec5..337893a 100644 --- a/src/servala/frontend/templates/frontend/organizations/service_instance_detail.html +++ b/src/servala/frontend/templates/frontend/organizations/service_instance_detail.html @@ -102,27 +102,7 @@
{% 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 %} + {% include "includes/control_plane_user_info.html" with control_plane=instance.context.control_plane %}
{% 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 dd35cce..7f3863e 100644 --- a/src/servala/frontend/templates/frontend/organizations/service_offering_detail.html +++ b/src/servala/frontend/templates/frontend/organizations/service_offering_detail.html @@ -8,26 +8,8 @@ {% 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 }}
-
-
-
+{% if selected_plane %} + {% include "includes/control_plane_user_info.html" with control_plane=selected_plane %} {% endif %} {% endpartialdef %} {% partialdef service-form %} diff --git a/src/servala/frontend/templates/includes/control_plane_user_info.html b/src/servala/frontend/templates/includes/control_plane_user_info.html new file mode 100644 index 0000000..b9ffe99 --- /dev/null +++ b/src/servala/frontend/templates/includes/control_plane_user_info.html @@ -0,0 +1,26 @@ +{% load i18n %} +{% comment %} +Reusable snippet for displaying ControlPlane user_info +Usage: {% include "includes/control_plane_user_info.html" with control_plane=control_plane_object %} +{% endcomment %} +{% if control_plane.user_info %} +
+
+

{% translate "Service Provider Zone Information" %}

+
+
+
+ + + {% for key, value in control_plane.user_info.items %} + + + + + {% endfor %} + +
{{ key }}{{ value }}
+
+
+
+{% endif %} -- 2.49.1