Display spec data tabbed like in form
All checks were successful
Tests / test (push) Successful in 26s

This commit is contained in:
Tobias Kunze 2025-04-17 10:47:08 +02:00
parent 6160f48d61
commit b2d9004359
3 changed files with 163 additions and 23 deletions

View file

@ -43,7 +43,7 @@ known_first_party = "servala"
[tool.flake8]
max-line-length = 160
exclude = ".venv"
ignore = "E203"
ignore = "E203,W503"
[tool.djlint]
extend_exclude = "src/servala/static/mazer"

View file

@ -103,29 +103,58 @@
<h4>{% translate "Specification" %}</h4>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-bordered">
<thead>
<tr>
<th>{% translate "Property" %}</th>
<th>{% translate "Value" %}</th>
</tr>
</thead>
<tbody>
{% for key, value in instance.spec.items %}
<tr>
<td>{{ key }}</td>
<td>
{% if value|default:""|stringformat:"s"|slice:":1" == "{" or value|default:""|stringformat:"s"|slice:":1" == "[" %}
<pre>{{ value|pprint }}</pre>
{% else %}
{{ value }}
{% endif %}
</td>
</tr>
<div class="row">
<div class="col-12">
<!-- Tabs -->
<ul class="nav nav-tabs" role="tablist">
{% for fieldset in spec_fieldsets %}
<li class="nav-item" role="presentation">
<a href="#spec-tab-{{ forloop.counter }}"
class="nav-link {% if forloop.first %}active{% endif %}"
data-bs-toggle="tab"
role="tab">{{ fieldset.title }}</a>
</li>
{% endfor %}
</tbody>
</table>
</ul>
<!-- Tab Content -->
<div class="tab-content pt-3">
{% for fieldset in spec_fieldsets %}
<div class="tab-pane fade {% if forloop.first %}show active{% endif %}"
id="spec-tab-{{ forloop.counter }}"
role="tabpanel">
<!-- Main Fields -->
<dl class="row">
{% for field in fieldset.fields %}
<dt class="col-sm-3">{{ field.label }}</dt>
<dd class="col-sm-9">
{% if field.value|default:""|stringformat:"s"|slice:":1" == "{" or field.value|default:""|stringformat:"s"|slice:":1" == "[" %}
<pre>{{ field.value|pprint }}</pre>
{% else %}
{{ field.value }}
{% endif %}
</dd>
{% endfor %}
</dl>
<!-- Nested Fieldsets -->
{% for sub_key, sub_fieldset in fieldset.fieldsets.items %}
<h5>{{ sub_fieldset.title }}</h5>
<dl class="row">
{% for field in sub_fieldset.fields %}
<dt class="col-sm-3">{{ field.label }}</dt>
<dd class="col-sm-9">
{% if field.value|default:""|stringformat:"s"|slice:":1" == "{" or field.value|default:""|stringformat:"s"|slice:":1" == "[" %}
<pre>{{ field.value|pprint }}</pre>
{% else %}
{{ field.value }}
{% endif %}
</dd>
{% endfor %}
</dl>
{% endfor %}
</div>
{% endfor %}
</div>
</div>
</div>
</div>
</div>

View file

@ -4,6 +4,7 @@ from django.shortcuts import redirect
from django.utils.functional import cached_property
from django.views.generic import DetailView, ListView
from servala.core.crd import deslugify
from servala.core.models import (
ControlPlaneCRD,
Service,
@ -168,6 +169,116 @@ class ServiceInstanceDetailView(OrganizationViewMixin, DetailView):
"context__service_definition__service",
)
def get_nested_spec(self):
"""
Organize spec data into fieldsets similar to how the form does it.
"""
spec = self.object.spec or {}
# Process spec fields
others = []
nested_fieldsets = {}
# First pass: organize fields into nested structures
for key, value in spec.items():
if isinstance(value, dict):
# This is a nested structure
if key not in nested_fieldsets:
nested_fieldsets[key] = {
"title": deslugify(key),
"fields": [],
"fieldsets": {},
}
# Process fields in the nested structure
for sub_key, sub_value in value.items():
if isinstance(sub_value, dict):
# Even deeper nesting
if sub_key not in nested_fieldsets[key]["fieldsets"]:
nested_fieldsets[key]["fieldsets"][sub_key] = {
"title": deslugify(sub_key),
"fields": [],
}
# Add fields from the deeper level
for leaf_key, leaf_value in sub_value.items():
nested_fieldsets[key]["fieldsets"][sub_key][
"fields"
].append(
{
"key": leaf_key,
"label": deslugify(leaf_key),
"value": leaf_value,
}
)
else:
# Add field to parent level
nested_fieldsets[key]["fields"].append(
{
"key": sub_key,
"label": deslugify(sub_key),
"value": sub_value,
}
)
else:
# This is a top-level field
others.append(
{
"key": key,
"label": deslugify(key),
"value": value,
}
)
# Second pass: Promote fields based on count
for group_key, group in list(nested_fieldsets.items()):
# Promote single sub-fieldsets to parent
for sub_key, sub_fieldset in list(group["fieldsets"].items()):
if len(sub_fieldset["fields"]) == 1:
field = sub_fieldset["fields"][0]
field["label"] = f"{sub_fieldset['title']}: {field['label']}"
group["fields"].append(field)
del group["fieldsets"][sub_key]
# Move single-field groups to others
total_fields = len(group["fields"])
for sub_fieldset in group["fieldsets"].values():
total_fields += len(sub_fieldset["fields"])
if (
total_fields == 1
and len(group["fields"]) == 1
and not group["fieldsets"]
):
field = group["fields"][0]
field["label"] = f"{group['title']}: {field['label']}"
others.append(field)
del nested_fieldsets[group_key]
elif total_fields == 0:
del nested_fieldsets[group_key]
fieldsets = []
if others:
fieldsets.append(
{
"title": "General",
"fields": others,
"fieldsets": {},
}
)
# Create fieldsets from the organized data
for group in nested_fieldsets.values():
fieldsets.append(group)
return fieldsets
def get_context_data(self, **kwargs):
"""Return service instance for the current organization."""
context = super().get_context_data(**kwargs)
if self.object.spec:
context["spec_fieldsets"] = self.get_nested_spec()
return context
class ServiceInstanceListView(OrganizationViewMixin, ListView):
template_name = "frontend/organizations/service_instances.html"