From bca79be02a28f0a0c6bcb024013051bd6599bba8 Mon Sep 17 00:00:00 2001 From: Tobias Kunze Date: Fri, 24 Oct 2025 11:55:56 +0200 Subject: [PATCH 1/4] Inline user info in service offering page ref #243 --- .../service_instance_detail.html | 11 ++++++- .../service_offering_detail.html | 27 ++++++++------- .../includes/control_plane_user_info.html | 33 +++++++------------ 3 files changed, 37 insertions(+), 34 deletions(-) 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 d375344..4f0f00e 100644 --- a/src/servala/frontend/templates/frontend/organizations/service_instance_detail.html +++ b/src/servala/frontend/templates/frontend/organizations/service_instance_detail.html @@ -102,7 +102,16 @@ {% endif %} - {% include "includes/control_plane_user_info.html" with control_plane=instance.context.control_plane %} + {% if control_plane.user_info %} +
+
+

{% translate "Service Provider Zone Information" %}

+
+
+ {% include "includes/control_plane_user_info.html" with control_plane=instance.context.control_plane %} +
+
+ {% 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 842e610..df57811 100644 --- a/src/servala/frontend/templates/frontend/organizations/service_offering_detail.html +++ b/src/servala/frontend/templates/frontend/organizations/service_offering_detail.html @@ -7,9 +7,14 @@ {{ offering }} {% endblock page_title %} {% endblock html_title %} -{% partialdef control-plane-info %} -{% if selected_plane %} - {% include "includes/control_plane_user_info.html" with control_plane=selected_plane %} +{% partialdef control-plane-info inline=True %} +{% if selected_plane and selected_plane.user_info %} +
+
+
{% translate "Service Provider Zone Information" %}
+ {% include "includes/control_plane_user_info.html" with control_plane=selected_plane %} +
+
{% endif %} {% endpartialdef %} {% partialdef service-form %} @@ -31,7 +36,7 @@ {% block content %}
-
+
{% if service.logo %} @@ -64,6 +69,13 @@ {{ select_form }} {% endif %} + {% if has_control_planes %} +
+ {% partial control-plane-info %} +
+ {% endif %} {% if service.external_links or offering.external_links %}
@@ -83,13 +95,6 @@
{% partial service-form %}
-
- {% if has_control_planes %} -
{% partial control-plane-info %}
- {% endif %} -
{% endblock content %} diff --git a/src/servala/frontend/templates/includes/control_plane_user_info.html b/src/servala/frontend/templates/includes/control_plane_user_info.html index b9ffe99..e8fcee8 100644 --- a/src/servala/frontend/templates/includes/control_plane_user_info.html +++ b/src/servala/frontend/templates/includes/control_plane_user_info.html @@ -1,26 +1,15 @@ {% 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 }}
-
-
+
+ + + {% for key, value in control_plane.user_info.items %} + + + + + {% endfor %} + +
{{ key }}{{ value }}
{% endif %} -- 2.49.1 From 75fe0799e0f3dc6fe961d43fb4d725d0594aa815 Mon Sep 17 00:00:00 2001 From: Tobias Kunze Date: Fri, 24 Oct 2025 12:16:38 +0200 Subject: [PATCH 2/4] Implement user info help text with popovers --- src/servala/core/forms.py | 23 ++++++++++++++----- src/servala/core/models/service.py | 3 ++- .../service_instance_detail.html | 9 ++++++++ .../service_offering_detail.html | 15 ++++++++++++ .../includes/control_plane_user_info.html | 16 ++++++++++--- 5 files changed, 56 insertions(+), 10 deletions(-) diff --git a/src/servala/core/forms.py b/src/servala/core/forms.py index baa85fb..034d1c9 100644 --- a/src/servala/core/forms.py +++ b/src/servala/core/forms.py @@ -5,14 +5,25 @@ from django_jsonform.widgets import JSONFormWidget from servala.core.models import ControlPlane, ServiceDefinition CONTROL_PLANE_USER_INFO_SCHEMA = { - "type": "object", - "properties": { - "CNAME Record": { - "title": "CNAME Record", - "type": "string", + "type": "array", + "items": { + "type": "object", + "properties": { + "title": { + "type": "string", + "title": "Title", + }, + "content": { + "type": "string", + "title": "Content", + }, + "help_text": { + "type": "string", + "title": "Help Text (optional)", + }, }, + "required": ["title", "content"], }, - "additionalProperties": {"type": "string"}, } diff --git a/src/servala/core/models/service.py b/src/servala/core/models/service.py index 42fc500..3af8c89 100644 --- a/src/servala/core/models/service.py +++ b/src/servala/core/models/service.py @@ -156,7 +156,8 @@ class ControlPlane(ServalaModelMixin, models.Model): blank=True, verbose_name=_("User Information"), help_text=_( - "Key-value information displayed to users when selecting this control plane" + 'Array of info objects: [{"title": "…", "content": "…", "help_text": "…"}]. ' + "The help_text field is optional and will be shown as a hover popover on an info icon." ), ) wildcard_dns = models.CharField( 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 4f0f00e..4aaef14 100644 --- a/src/servala/frontend/templates/frontend/organizations/service_instance_detail.html +++ b/src/servala/frontend/templates/frontend/organizations/service_instance_detail.html @@ -248,3 +248,12 @@
{% endblock content %} +{% block extra_js %} + +{% endblock extra_js %} 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 df57811..6fdc61a 100644 --- a/src/servala/frontend/templates/frontend/organizations/service_offering_detail.html +++ b/src/servala/frontend/templates/frontend/organizations/service_offering_detail.html @@ -108,4 +108,19 @@ {% endif %} + {% endblock extra_js %} diff --git a/src/servala/frontend/templates/includes/control_plane_user_info.html b/src/servala/frontend/templates/includes/control_plane_user_info.html index e8fcee8..21d5cdb 100644 --- a/src/servala/frontend/templates/includes/control_plane_user_info.html +++ b/src/servala/frontend/templates/includes/control_plane_user_info.html @@ -3,10 +3,20 @@
- {% for key, value in control_plane.user_info.items %} + {% for info in control_plane.user_info %} - - + + {% endfor %} -- 2.49.1 From b8f3621b47038884f8d09e6e99aaa88d1d5e9c2a Mon Sep 17 00:00:00 2001 From: Tobias Kunze Date: Fri, 24 Oct 2025 12:19:04 +0200 Subject: [PATCH 3/4] Migrate user info data --- .../0012_convert_user_info_to_array.py | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 src/servala/core/migrations/0012_convert_user_info_to_array.py diff --git a/src/servala/core/migrations/0012_convert_user_info_to_array.py b/src/servala/core/migrations/0012_convert_user_info_to_array.py new file mode 100644 index 0000000..892949e --- /dev/null +++ b/src/servala/core/migrations/0012_convert_user_info_to_array.py @@ -0,0 +1,65 @@ +# Generated by Django 5.2.7 on 2025-10-24 10:04 + +from django.db import migrations + + +def convert_user_info_to_array(apps, schema_editor): + """ + Convert user_info from object format {"key": "value"} to array format + [{"title": "key", "content": "value"}]. + """ + ControlPlane = apps.get_model("core", "ControlPlane") + + for control_plane in ControlPlane.objects.all(): + if not control_plane.user_info: + continue + + # If it's already an array (migration already run or new format), skip + if isinstance(control_plane.user_info, list): + continue + + # Convert from dict to array + if isinstance(control_plane.user_info, dict): + new_user_info = [] + for key, value in control_plane.user_info.items(): + new_user_info.append({"title": key, "content": value}) + + control_plane.user_info = new_user_info + control_plane.save(update_fields=["user_info"]) + + +def reverse_user_info_to_object(apps, schema_editor): + """ + Reverse the migration by converting array format back to object format. + Note: help_text will be lost during reversal. + """ + ControlPlane = apps.get_model("core", "ControlPlane") + + for control_plane in ControlPlane.objects.all(): + if not control_plane.user_info: + continue + + # If it's already an object, skip + if isinstance(control_plane.user_info, dict): + continue + + # Convert from array to dict + if isinstance(control_plane.user_info, list): + new_user_info = {} + for item in control_plane.user_info: + if isinstance(item, dict) and "title" in item and "content" in item: + new_user_info[item["title"]] = item["content"] + + control_plane.user_info = new_user_info + control_plane.save(update_fields=["user_info"]) + + +class Migration(migrations.Migration): + + dependencies = [ + ("core", "0011_alter_organizationorigin_billing_entity"), + ] + + operations = [ + migrations.RunPython(convert_user_info_to_array, reverse_user_info_to_object), + ] -- 2.49.1 From 6a677f265368be8a74c8da53c68d8537bc3c86c0 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Mon, 27 Oct 2025 11:13:48 +0100 Subject: [PATCH 4/4] restyle service information cards --- .../service_offering_detail.html | 126 +++++++++++------- .../includes/control_plane_user_info.html | 43 +++--- 2 files changed, 101 insertions(+), 68 deletions(-) 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 6fdc61a..3305328 100644 --- a/src/servala/frontend/templates/frontend/organizations/service_offering_detail.html +++ b/src/servala/frontend/templates/frontend/organizations/service_offering_detail.html @@ -9,9 +9,8 @@ {% endblock html_title %} {% partialdef control-plane-info inline=True %} {% if selected_plane and selected_plane.user_info %} -
-
-
{% translate "Service Provider Zone Information" %}
+
+
{% include "includes/control_plane_user_info.html" with control_plane=selected_plane %}
@@ -35,64 +34,97 @@ {% endpartialdef %} {% block content %}
-
-
-
-
- {% if service.logo %} - {{ service.name }} - {% endif %} -
-

{{ offering }}

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

{{ offering.description|urlize }}

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

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

- {% else %} +
+
+ {% else %} + +
+ +
+
+
+
{% translate "Service Provider Zone" %}
+
+
+ hx-swap="outerHTML" + class="control-plane-select-form"> {{ select_form }} - {% endif %} - {% if has_control_planes %} +
{% partial control-plane-info %}
- {% endif %} - {% if service.external_links or offering.external_links %} -
-
-
{% translate "External Links" %}
-
- {% for link in service.external_links %} - {% include "includes/external_link.html" %} - {% endfor %} - {% for link in offering.external_links %} - {% include "includes/external_link.html" %} - {% endfor %} -
-
-
- {% endif %} +
+ +
+
+
+
{% translate "Service Information" %}
+
+
+ {% if offering.service.logo or offering.description %} +
+ {% if offering.service.logo %} +
+ {{ offering.service.name }} +
+ {% endif %} + {% if offering.description %} +
+

{{ offering.description|urlize }}

+
+ {% endif %} +
+ {% endif %} + {% if offering.service.external_links or offering.external_links %} + {% if offering.service.logo or offering.description %}
{% endif %} +
{% translate "External Links" %}
+
+ {% for link in offering.service.external_links %} + {% include "includes/external_link.html" %} + {% endfor %} + {% for link in offering.external_links %} + {% include "includes/external_link.html" %} + {% endfor %} +
+ {% else %} + {% if not offering.service.logo and not offering.description %} +

{% translate "No additional information available." %}

+ {% endif %} + {% endif %} +
+
+
+
+ {% endif %} + +
+
{% partial 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 index 21d5cdb..a3a27f5 100644 --- a/src/servala/frontend/templates/includes/control_plane_user_info.html +++ b/src/servala/frontend/templates/includes/control_plane_user_info.html @@ -1,25 +1,26 @@ {% load i18n %} {% if control_plane.user_info %} -
-
{{ key }}{{ value }} + {{ info.title }} + {% if info.help_text %} + + {% endif %} + {{ info.content }}
- - {% for info in control_plane.user_info %} - - - - - {% endfor %} - -
- {{ info.title }} - {% if info.help_text %} - - {% endif %} - {{ info.content }}
+
+ {% for info in control_plane.user_info %} +
+
+ + {{ info.title }} + + {% if info.help_text %} + + {% endif %} +
+
+ {{ info.content }} +
+
+ {% endfor %}
{% endif %} -- 2.49.1