diff --git a/src/servala/core/models/service.py b/src/servala/core/models/service.py
index 41cc896..b2d56e8 100644
--- a/src/servala/core/models/service.py
+++ b/src/servala/core/models/service.py
@@ -1,6 +1,7 @@
import json
import kubernetes
+import rules
import urlman
from django.conf import settings
from django.core.cache import cache
@@ -12,6 +13,7 @@ from encrypted_fields.fields import EncryptedJSONField
from kubernetes import client, config
from kubernetes.client.rest import ApiException
+from servala.core import rules as perms
from servala.core.models.mixins import ServalaModelMixin
from servala.core.validators import kubernetes_name_validator
@@ -486,6 +488,12 @@ class ServiceInstance(ServalaModelMixin, models.Model):
# Names are unique per de-facto namespace, which is defined by the
# Organization + ServiceDefinition (group, version) + the ControlPlane.
unique_together = [("name", "organization", "context")]
+ rules_permissions = {
+ "view": rules.is_staff | perms.is_organization_member,
+ "change": rules.is_staff | perms.is_organization_member,
+ "delete": rules.is_staff | perms.is_organization_admin,
+ "add": rules.is_authenticated,
+ }
class urls(urlman.Urls):
base = "{self.organization.urls.instances}{self.name}/"
diff --git a/src/servala/core/models/user.py b/src/servala/core/models/user.py
index 8fa3b88..0084593 100644
--- a/src/servala/core/models/user.py
+++ b/src/servala/core/models/user.py
@@ -69,9 +69,7 @@ class User(ServalaModelMixin, PermissionsMixin, AbstractBaseUser):
verbose_name_plural = _("Users")
def __str__(self):
- if self.first_name and self.last_name:
- return f"{self.first_name} {self.last_name}"
- return self.email
+ return f"{self.first_name} {self.last_name}".strip() or self.email
def normalize_username(self, username):
return super().normalize_username(username).strip().lower()
diff --git a/src/servala/frontend/forms/service.py b/src/servala/frontend/forms/service.py
index 3b7a547..428f1bc 100644
--- a/src/servala/frontend/forms/service.py
+++ b/src/servala/frontend/forms/service.py
@@ -1,7 +1,13 @@
from django import forms
from django.utils.translation import gettext_lazy as _
-from servala.core.models import CloudProvider, ControlPlane, ServiceCategory
+from servala.core.models import (
+ CloudProvider,
+ ControlPlane,
+ Service,
+ ServiceCategory,
+ ServiceOffering,
+)
class ServiceFilterForm(forms.Form):
@@ -33,3 +39,52 @@ class ControlPlaneSelectForm(forms.Form):
def __init__(self, *args, planes=None, **kwargs):
super().__init__(*args, **kwargs)
self.fields["control_plane"].queryset = planes
+
+
+class ServiceInstanceFilterForm(forms.Form):
+ name = forms.CharField(required=False, label=_("Name"))
+ service = forms.ModelChoiceField(
+ queryset=Service.objects.all(), required=False, label=_("Service")
+ )
+ provider = forms.ModelChoiceField(
+ queryset=ServiceOffering.objects.all()
+ .values_list("provider", flat=True)
+ .distinct(),
+ required=False,
+ label=_("Provider"),
+ )
+ control_plane = forms.ModelChoiceField(
+ queryset=ControlPlane.objects.all(),
+ required=False,
+ label=_("Service Provider Zone"),
+ )
+ status = forms.ChoiceField(
+ choices=(
+ ("active", _("Active")),
+ ("deleted", _("Deleted")),
+ ),
+ required=False,
+ label=_("Status"),
+ )
+
+ def filter_queryset(self, queryset):
+ if self.is_valid():
+ data = self.cleaned_data
+ if data["name"]:
+ queryset = queryset.filter(name__icontains=data["name"])
+ if data["service"]:
+ queryset = queryset.filter(
+ context__service_definition__service=data["service"]
+ )
+ if data["provider"]:
+ queryset = queryset.filter(
+ context__service_offering__provider=data["provider"]
+ )
+ if data["control_plane"]:
+ queryset = queryset.filter(context__control_plane=data["control_plane"])
+ if data["status"]:
+ if data["status"] == "active":
+ queryset = queryset.filter(is_deleted=False)
+ else:
+ queryset = queryset.filter(is_deleted=True)
+ return queryset
diff --git a/src/servala/frontend/templates/frontend/organizations/service_instance_detail.html b/src/servala/frontend/templates/frontend/organizations/service_instance_detail.html
new file mode 100644
index 0000000..7a949f1
--- /dev/null
+++ b/src/servala/frontend/templates/frontend/organizations/service_instance_detail.html
@@ -0,0 +1,54 @@
+{% extends "frontend/base.html" %}
+{% load i18n static %}
+{% block html_title %}
+ {% block page_title %}
+ {{ instance.name }}
+ {% endblock page_title %}
+{% endblock html_title %}
+{% block content %}
+ {% translate "Details" %}
+
+
+
+
+
+
+
+
+
+ {% for instance in instances %}
+ {% translate "Name" %}
+ {% translate "Service" %}
+ {% translate "Service Provider" %}
+ {% translate "Service Provider Zone" %}
+ {% translate "Created At" %}
+ {% translate "Status" %}
+
+
+ {% empty %}
+
+ {{ instance.name }}
+
+ {{ instance.context.service_definition.service.name }}
+ {{ instance.context.service_offering.provider.name }}
+ {{ instance.context.control_plane.name }}
+ {{ instance.created_at|date:"SHORT_DATETIME_FORMAT" }}
+
+ {% if instance.is_deleted %}
+ {% translate "Deleted" %}
+ {% else %}
+ {% translate "Active" %}
+ {% endif %}
+
+
+
+ {% endfor %}
+
+ {% translate "No service instances found." %}
+