Compare commits
No commits in common. "a1ffdf565d33e08bbf69ba1929e4b0f92d73cb74" and "c4f7c8df69a4bc95315e13f95d7367be08a49c82" have entirely different histories.
a1ffdf565d
...
c4f7c8df69
6 changed files with 29 additions and 397 deletions
|
@ -44,7 +44,7 @@ known_first_party = "servala"
|
||||||
[tool.flake8]
|
[tool.flake8]
|
||||||
max-line-length = 160
|
max-line-length = 160
|
||||||
exclude = ".venv"
|
exclude = ".venv"
|
||||||
ignore = "E203,W503"
|
ignore = "E203"
|
||||||
|
|
||||||
[tool.djlint]
|
[tool.djlint]
|
||||||
extend_exclude = "src/servala/static/mazer"
|
extend_exclude = "src/servala/static/mazer"
|
||||||
|
|
|
@ -126,7 +126,6 @@ class ControlPlaneAdmin(admin.ModelAdmin):
|
||||||
search_fields = ("name", "description")
|
search_fields = ("name", "description")
|
||||||
autocomplete_fields = ("cloud_provider",)
|
autocomplete_fields = ("cloud_provider",)
|
||||||
actions = ["test_kubernetes_connection"]
|
actions = ["test_kubernetes_connection"]
|
||||||
ordering = ("name",)
|
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(
|
(
|
||||||
|
|
|
@ -355,27 +355,11 @@ class ControlPlaneCRD(ServalaModelMixin, models.Model):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.service_offering} on {self.control_plane} with {self.service_definition}"
|
return f"{self.service_offering} on {self.control_plane} with {self.service_definition}"
|
||||||
|
|
||||||
@cached_property
|
|
||||||
def group(self):
|
|
||||||
return self.service_definition.api_definition["group"]
|
|
||||||
|
|
||||||
@cached_property
|
|
||||||
def version(self):
|
|
||||||
return self.service_definition.api_definition["version"]
|
|
||||||
|
|
||||||
@cached_property
|
|
||||||
def kind(self):
|
|
||||||
return self.service_definition.api_definition["kind"]
|
|
||||||
|
|
||||||
@cached_property
|
|
||||||
def kind_plural(self):
|
|
||||||
plural = self.kind.lower()
|
|
||||||
if not plural.endswith("s"):
|
|
||||||
plural = f"{plural}s"
|
|
||||||
return plural
|
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def resource_definition(self):
|
def resource_definition(self):
|
||||||
|
kind = self.service_definition.api_definition["kind"]
|
||||||
|
group = self.service_definition.api_definition["group"]
|
||||||
|
version = self.service_definition.api_definition["version"]
|
||||||
client = self.control_plane.get_kubernetes_client()
|
client = self.control_plane.get_kubernetes_client()
|
||||||
|
|
||||||
extensions_api = kubernetes.client.ApiextensionsV1Api(client)
|
extensions_api = kubernetes.client.ApiextensionsV1Api(client)
|
||||||
|
@ -384,10 +368,10 @@ class ControlPlaneCRD(ServalaModelMixin, models.Model):
|
||||||
for crd in crds.items:
|
for crd in crds.items:
|
||||||
if matching_crd:
|
if matching_crd:
|
||||||
break
|
break
|
||||||
if crd.spec.group == self.group:
|
if crd.spec.group == group:
|
||||||
for served_version in crd.spec.versions:
|
for served_version in crd.spec.versions:
|
||||||
if served_version.name == self.version and served_version.served:
|
if served_version.name == version and served_version.served:
|
||||||
if crd.spec.names.kind == self.kind:
|
if crd.spec.names.kind == kind:
|
||||||
matching_crd = crd
|
matching_crd = crd
|
||||||
break
|
break
|
||||||
return matching_crd
|
return matching_crd
|
||||||
|
@ -398,8 +382,9 @@ class ControlPlaneCRD(ServalaModelMixin, models.Model):
|
||||||
if result := cache.get(cache_key):
|
if result := cache.get(cache_key):
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
version = self.service_definition.api_definition["version"]
|
||||||
for v in self.resource_definition.spec.versions:
|
for v in self.resource_definition.spec.versions:
|
||||||
if v.name == self.version:
|
if v.name == version:
|
||||||
result = v.schema.open_apiv3_schema.to_dict()
|
result = v.schema.open_apiv3_schema.to_dict()
|
||||||
timeout_seconds = 60 * 60 * 24
|
timeout_seconds = 60 * 60 * 24
|
||||||
cache.set(cache_key, result, timeout=timeout_seconds)
|
cache.set(cache_key, result, timeout=timeout_seconds)
|
||||||
|
@ -410,9 +395,9 @@ class ControlPlaneCRD(ServalaModelMixin, models.Model):
|
||||||
from servala.core.crd import generate_django_model
|
from servala.core.crd import generate_django_model
|
||||||
|
|
||||||
kwargs = {
|
kwargs = {
|
||||||
"group": self.group,
|
key: value
|
||||||
"version": self.version,
|
for key, value in self.service_definition.api_definition.items()
|
||||||
"kind": self.kind,
|
if key in ("group", "version", "kind")
|
||||||
}
|
}
|
||||||
return generate_django_model(self.resource_schema, **kwargs)
|
return generate_django_model(self.resource_schema, **kwargs)
|
||||||
|
|
||||||
|
@ -532,9 +517,12 @@ class ServiceInstance(ServalaModelMixin, models.Model):
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
group = context.service_definition.api_definition["group"]
|
||||||
|
version = context.service_definition.api_definition["version"]
|
||||||
|
kind = context.service_definition.api_definition["kind"]
|
||||||
create_data = {
|
create_data = {
|
||||||
"apiVersion": f"{context.group}/{context.version}",
|
"apiVersion": f"{group}/{version}",
|
||||||
"kind": context.kind,
|
"kind": kind,
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"name": name,
|
"name": name,
|
||||||
"namespace": organization.namespace,
|
"namespace": organization.namespace,
|
||||||
|
@ -546,11 +534,15 @@ class ServiceInstance(ServalaModelMixin, models.Model):
|
||||||
api_instance = client.CustomObjectsApi(
|
api_instance = client.CustomObjectsApi(
|
||||||
context.control_plane.get_kubernetes_client()
|
context.control_plane.get_kubernetes_client()
|
||||||
)
|
)
|
||||||
|
plural = kind.lower()
|
||||||
|
if not plural.endswith("s"):
|
||||||
|
plural = f"{plural}s"
|
||||||
|
|
||||||
api_instance.create_namespaced_custom_object(
|
api_instance.create_namespaced_custom_object(
|
||||||
group=context.group,
|
group=group,
|
||||||
version=context.version,
|
version=version,
|
||||||
namespace=organization.namespace,
|
namespace=organization.namespace,
|
||||||
plural=context.kind_plural,
|
plural=plural,
|
||||||
body=create_data,
|
body=create_data,
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
@ -564,95 +556,3 @@ class ServiceInstance(ServalaModelMixin, models.Model):
|
||||||
raise ValidationError(_("Kubernetes API error: {}").format(str(e)))
|
raise ValidationError(_("Kubernetes API error: {}").format(str(e)))
|
||||||
raise ValidationError(_("Error creating instance: {}").format(str(e)))
|
raise ValidationError(_("Error creating instance: {}").format(str(e)))
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
@cached_property
|
|
||||||
def kubernetes_object(self):
|
|
||||||
"""Fetch the Kubernetes custom resource object"""
|
|
||||||
try:
|
|
||||||
api_instance = client.CustomObjectsApi(
|
|
||||||
self.context.control_plane.get_kubernetes_client()
|
|
||||||
)
|
|
||||||
|
|
||||||
return api_instance.get_namespaced_custom_object(
|
|
||||||
group=self.context.group,
|
|
||||||
version=self.context.version,
|
|
||||||
namespace=self.organization.namespace,
|
|
||||||
plural=self.context.kind_plural,
|
|
||||||
name=self.name,
|
|
||||||
)
|
|
||||||
except ApiException as e:
|
|
||||||
if e.status == 404:
|
|
||||||
return None
|
|
||||||
raise
|
|
||||||
|
|
||||||
@cached_property
|
|
||||||
def spec(self):
|
|
||||||
if not self.kubernetes_object:
|
|
||||||
return {}
|
|
||||||
if not (spec := self.kubernetes_object.get("spec")):
|
|
||||||
return {}
|
|
||||||
|
|
||||||
# Remove fields that shouldn't be displayed
|
|
||||||
spec = spec.copy()
|
|
||||||
spec.pop("resourceRef", None)
|
|
||||||
spec.pop("writeConnectionSecretToRef", None)
|
|
||||||
return spec
|
|
||||||
|
|
||||||
@cached_property
|
|
||||||
def status_conditions(self):
|
|
||||||
if not self.kubernetes_object:
|
|
||||||
return []
|
|
||||||
if not (status := self.kubernetes_object.get("status")):
|
|
||||||
return []
|
|
||||||
return status.get("conditions") or []
|
|
||||||
|
|
||||||
@cached_property
|
|
||||||
def connection_credentials(self):
|
|
||||||
"""
|
|
||||||
Get connection credentials directly from the resource's writeConnectionSecretToRef
|
|
||||||
after checking that secret conditions are available.
|
|
||||||
"""
|
|
||||||
if not self.kubernetes_object:
|
|
||||||
return {}
|
|
||||||
|
|
||||||
# Check if secrets are available based on conditions
|
|
||||||
secrets_available = any(
|
|
||||||
[
|
|
||||||
condition.get("type") == "Ready" and condition.get("status") == "True"
|
|
||||||
for condition in self.status_conditions
|
|
||||||
]
|
|
||||||
)
|
|
||||||
if not secrets_available:
|
|
||||||
return {}
|
|
||||||
|
|
||||||
spec = self.kubernetes_object.get("spec")
|
|
||||||
if not (secret_ref := spec.get("writeConnectionSecretToRef")):
|
|
||||||
return {}
|
|
||||||
if not (secret_name := secret_ref.get("name")):
|
|
||||||
return {}
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Get the secret data
|
|
||||||
v1 = kubernetes.client.CoreV1Api(
|
|
||||||
self.context.control_plane.get_kubernetes_client()
|
|
||||||
)
|
|
||||||
secret = v1.read_namespaced_secret(
|
|
||||||
name=secret_name, namespace=self.organization.namespace
|
|
||||||
)
|
|
||||||
|
|
||||||
# Secret data is base64 encoded
|
|
||||||
credentials = {}
|
|
||||||
if hasattr(secret, "data") and secret.data:
|
|
||||||
import base64
|
|
||||||
|
|
||||||
for key, value in secret.data.items():
|
|
||||||
try:
|
|
||||||
credentials[key] = base64.b64decode(value).decode("utf-8")
|
|
||||||
except Exception:
|
|
||||||
credentials[key] = f"<binary data: {len(value)} bytes>"
|
|
||||||
|
|
||||||
return credentials
|
|
||||||
except ApiException as e:
|
|
||||||
return {"error": str(e)}
|
|
||||||
except Exception as e:
|
|
||||||
return {"error": str(e)}
|
|
||||||
|
|
|
@ -7,13 +7,11 @@
|
||||||
{% endblock html_title %}
|
{% endblock html_title %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<section class="section">
|
<section class="section">
|
||||||
<div class="row">
|
|
||||||
<div class="col-12 col-md-5">
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header">
|
|
||||||
<h4>{% translate "Details" %}</h4>
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<h5>{% translate "Details" %}</h5>
|
||||||
<dl class="row">
|
<dl class="row">
|
||||||
<dt class="col-sm-4">{% translate "Service" %}</dt>
|
<dt class="col-sm-4">{% translate "Service" %}</dt>
|
||||||
<dd class="col-sm-8">
|
<dd class="col-sm-8">
|
||||||
|
@ -51,148 +49,6 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% if instance.status_conditions %}
|
|
||||||
<div class="col-12 col-md-7">
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-header">
|
|
||||||
<h4>{% translate "Status" %}</h4>
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
<div class="row">
|
|
||||||
<div class="table-responsive">
|
|
||||||
<table class="table table-bordered">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>{% translate "Type" %}</th>
|
|
||||||
<th>{% translate "Status" %}</th>
|
|
||||||
<th>{% translate "Last Transition Time" %}</th>
|
|
||||||
<th>{% translate "Reason" %}</th>
|
|
||||||
<th>{% translate "Message" %}</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{% for condition in instance.status_conditions %}
|
|
||||||
<tr>
|
|
||||||
<td>{{ condition.type }}</td>
|
|
||||||
<td>
|
|
||||||
{% if condition.status == "True" %}
|
|
||||||
<span class="badge text-bg-success">True</span>
|
|
||||||
{% elif condition.status == "False" %}
|
|
||||||
<span class="badge text-bg-danger">False</span>
|
|
||||||
{% else %}
|
|
||||||
<span class="badge text-bg-secondary">{{ condition.status }}</span>
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
<td>{{ condition.lastTransitionTime }}</td>
|
|
||||||
<td>{{ condition.reason }}</td>
|
|
||||||
<td>{{ condition.message }}</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% if instance.spec %}
|
|
||||||
<div class="col-12">
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-header">
|
|
||||||
<h4>{% translate "Specification" %}</h4>
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
<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 %}
|
|
||||||
</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>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% if instance.connection_credentials %}
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-header">
|
|
||||||
<h4>{% translate "Connection Credentials" %}</h4>
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
<div class="table-responsive">
|
|
||||||
<table class="table table-bordered">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>{% translate "Name" %}</th>
|
|
||||||
<th>{% translate "Value" %}</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{% for key, value in instance.connection_credentials.items %}
|
|
||||||
<tr>
|
|
||||||
<td>{{ key }}</td>
|
|
||||||
<td>
|
|
||||||
{% if key == "error" %}
|
|
||||||
<span class="text-danger">{{ value }}</span>
|
|
||||||
{% else %}
|
|
||||||
<code>{{ value }}</code>
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
import json
|
|
||||||
|
|
||||||
from django import template
|
|
||||||
|
|
||||||
register = template.Library()
|
|
||||||
|
|
||||||
|
|
||||||
@register.filter
|
|
||||||
def pprint(value):
|
|
||||||
if isinstance(value, (dict, list)):
|
|
||||||
return json.dumps(value, indent=2)
|
|
||||||
return value
|
|
|
@ -4,7 +4,6 @@ from django.shortcuts import redirect
|
||||||
from django.utils.functional import cached_property
|
from django.utils.functional import cached_property
|
||||||
from django.views.generic import DetailView, ListView
|
from django.views.generic import DetailView, ListView
|
||||||
|
|
||||||
from servala.core.crd import deslugify
|
|
||||||
from servala.core.models import (
|
from servala.core.models import (
|
||||||
ControlPlaneCRD,
|
ControlPlaneCRD,
|
||||||
Service,
|
Service,
|
||||||
|
@ -169,116 +168,6 @@ class ServiceInstanceDetailView(OrganizationViewMixin, DetailView):
|
||||||
"context__service_definition__service",
|
"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):
|
class ServiceInstanceListView(OrganizationViewMixin, ListView):
|
||||||
template_name = "frontend/organizations/service_instances.html"
|
template_name = "frontend/organizations/service_instances.html"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue