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/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), + ] 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 d375344..4aaef14 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 "We currently cannot offer this service. Please check back later or contact support for more information." %}
{{ offering.description|urlize }}
-{% translate "We currently cannot offer this service, sorry!" %}
- {% else %} +{{ offering.description|urlize }}
+{% translate "No additional information available." %}
+ {% endif %} + {% endif %} +| {{ key }} | -{{ value }} | -
|---|
{{ info.content }}
+