diff --git a/pyproject.toml b/pyproject.toml index a26e2ef..ef119ef 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,6 @@ dependencies = [ "django==5.2.4", "django-allauth>=65.10.0", "django-fernet-encrypted-fields>=0.3.0", - "django-jsonform>=2.22.0", "django-scopes>=2.0.0", "django-storages[s3]>=1.14.6", "django-template-partials>=24.4", diff --git a/src/servala/core/admin.py b/src/servala/core/admin.py index ed07574..740b7bf 100644 --- a/src/servala/core/admin.py +++ b/src/servala/core/admin.py @@ -1,6 +1,5 @@ from django.contrib import admin, messages from django.utils.translation import gettext_lazy as _ -from django_jsonform.widgets import JSONFormWidget from servala.core.forms import ControlPlaneAdminForm, ServiceDefinitionAdminForm from servala.core.models import ( @@ -112,60 +111,12 @@ class ServiceAdmin(admin.ModelAdmin): autocomplete_fields = ("category",) prepopulated_fields = {"slug": ["name"]} - def get_form(self, request, obj=None, **kwargs): - form = super().get_form(request, obj, **kwargs) - # JSON schema for external_links field - external_links_schema = { - "type": "array", - "title": "External Links", - "items": { - "type": "object", - "title": "Link", - "properties": { - "url": {"type": "string", "format": "uri", "title": "URL"}, - "title": {"type": "string", "title": "Title"}, - "featured": { - "type": "boolean", - "title": "Featured", - "default": False, - "description": "Featured links will be shown on the service list page", - }, - }, - "required": ["url", "title"], - }, - } - form.base_fields["external_links"].widget = JSONFormWidget( - schema=external_links_schema - ) - return form - @admin.register(CloudProvider) class CloudProviderAdmin(admin.ModelAdmin): list_display = ("name",) search_fields = ("name", "description") - def get_form(self, request, obj=None, **kwargs): - form = super().get_form(request, obj, **kwargs) - # JSON schema for external_links field - external_links_schema = { - "type": "array", - "title": "External Links", - "items": { - "type": "object", - "title": "Link", - "properties": { - "url": {"type": "string", "format": "uri", "title": "URL"}, - "title": {"type": "string", "title": "Title"}, - }, - "required": ["url", "title"], - }, - } - form.base_fields["external_links"].widget = JSONFormWidget( - schema=external_links_schema - ) - return form - @admin.register(ControlPlane) class ControlPlaneAdmin(admin.ModelAdmin): diff --git a/src/servala/core/models/organization.py b/src/servala/core/models/organization.py index 997b0a2..e517c83 100644 --- a/src/servala/core/models/organization.py +++ b/src/servala/core/models/organization.py @@ -3,8 +3,8 @@ import urlman from django.conf import settings from django.db import models, transaction from django.utils.functional import cached_property -from django.utils.safestring import mark_safe from django.utils.text import slugify +from django.utils.safestring import mark_safe from django.utils.translation import gettext_lazy as _ from django_scopes import ScopedManager, scopes_disabled diff --git a/src/servala/core/models/service.py b/src/servala/core/models/service.py index a06db4e..ba1871d 100644 --- a/src/servala/core/models/service.py +++ b/src/servala/core/models/service.py @@ -71,14 +71,9 @@ class Service(ServalaModelMixin, models.Model): logo = models.ImageField( upload_to="public/services", blank=True, null=True, verbose_name=_("Logo") ) + # TODO schema external_links = models.JSONField( - null=True, - blank=True, - verbose_name=_("External links"), - help_text=( - 'JSON array of link objects: {"url": "…", "title": "…", "featured": false}. ' - "Featured links will be shown on the service list page, all other links will only show on the service and offering detail pages." - ), + null=True, blank=True, verbose_name=_("External links") ) class Meta: @@ -88,13 +83,6 @@ class Service(ServalaModelMixin, models.Model): def __str__(self): return self.name - @property - def featured_links(self): - """Return external links marked as featured.""" - if not self.external_links: - return [] - return [link for link in self.external_links if link.get("featured")] - def validate_dict(data, required_fields=None, allow_empty=True): if not data: @@ -275,10 +263,7 @@ class CloudProvider(ServalaModelMixin, models.Model): verbose_name=_("Logo"), ) external_links = models.JSONField( - null=True, - blank=True, - verbose_name=_("External links"), - help_text=('JSON array of link objects: {"url": "…", "title": "…"}. '), + null=True, blank=True, verbose_name=_("External links") ) class Meta: diff --git a/src/servala/frontend/templates/account/login.html b/src/servala/frontend/templates/account/login.html index 58cd49b..233118b 100644 --- a/src/servala/frontend/templates/account/login.html +++ b/src/servala/frontend/templates/account/login.html @@ -1,22 +1,21 @@ {% extends "frontend/base.html" %} {% load static i18n %} {% load allauth account socialaccount %} + {% block html_title %} {% translate "Sign in" %} {% endblock html_title %} + {% block page_title %} {% translate "Welcome to Servala" %} {% endblock page_title %} + {% block card_header %} -
- Servala +
+ Servala
{% endblock card_header %} + {% block card_content %} {% if SOCIALACCOUNT_ENABLED %} @@ -25,10 +24,9 @@
{% translate "Ready to get started?" %}
-

- {% translate "Sign in to access your managed service instances and the Servala service catalog" %} -

+

{% translate "Sign in to access your managed service instances and the Servala service catalog" %}

+ {% for provider in socialaccount_providers %} {% provider_login_url provider process=process scope=scope auth_params=auth_params as href %}
@@ -37,9 +35,7 @@
@@ -47,6 +43,7 @@
{% endif %} {% endif %} +
@@ -75,8 +72,8 @@
- {% translate "Learn more" %} @@ -85,6 +82,7 @@
+
@@ -98,6 +96,7 @@
+
diff --git a/src/servala/frontend/templates/frontend/base.html b/src/servala/frontend/templates/frontend/base.html index 990dc17..c2d9285 100644 --- a/src/servala/frontend/templates/frontend/base.html +++ b/src/servala/frontend/templates/frontend/base.html @@ -5,18 +5,13 @@ - + - - {% block html_title %} - Dashboard - {% endblock html_title %} - – Servala + {% block html_title %}Dashboard{% endblock html_title %} – Servala diff --git a/src/servala/frontend/templates/frontend/organizations/service_detail.html b/src/servala/frontend/templates/frontend/organizations/service_detail.html index d9ca433..d03489e 100644 --- a/src/servala/frontend/templates/frontend/organizations/service_detail.html +++ b/src/servala/frontend/templates/frontend/organizations/service_detail.html @@ -26,24 +26,6 @@

{{ service.description|default:"No description available."|urlize }}

- {% if service.external_links %} -
-
-
{% translate "External Links" %}
-
- {% for link in service.external_links %} - - {{ link.title }} - - - {% endfor %} -
-
-
- {% endif %}
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 56dc7e2..184ff49 100644 --- a/src/servala/frontend/templates/frontend/organizations/service_offering_detail.html +++ b/src/servala/frontend/templates/frontend/organizations/service_offering_detail.html @@ -40,13 +40,6 @@
- {% if offering.description %} -
-
-

{{ offering.description|urlize }}

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

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

{% else %} @@ -56,24 +49,6 @@ {{ select_form }} {% endif %} - {% if service.external_links %} -
-
-
{% translate "External Links" %}
-
- {% for link in service.external_links %} - - {{ link.title }} - - - {% endfor %} -
-
-
- {% endif %}
{% partial service-form %}
diff --git a/src/servala/frontend/templates/frontend/organizations/services.html b/src/servala/frontend/templates/frontend/organizations/services.html index 2227e86..022b6e5 100644 --- a/src/servala/frontend/templates/frontend/organizations/services.html +++ b/src/servala/frontend/templates/frontend/organizations/services.html @@ -16,10 +16,10 @@ -
+
{% for service in services %}
-
+
{% if service.logo %} {{ service.category }}
-
+
{% if service.description %}

{{ service.description|urlize }}

{% endif %}
- diff --git a/src/servala/frontend/templates/frontend/profile.html b/src/servala/frontend/templates/frontend/profile.html index b9c1799..dc12100 100644 --- a/src/servala/frontend/templates/frontend/profile.html +++ b/src/servala/frontend/templates/frontend/profile.html @@ -116,8 +116,7 @@ {% endblocktranslate %}

- {% translate "VSHN Account Console" %} diff --git a/src/servala/frontend/templates/includes/k8s_error.html b/src/servala/frontend/templates/includes/k8s_error.html index 5707dcc..f97474d 100644 --- a/src/servala/frontend/templates/includes/k8s_error.html +++ b/src/servala/frontend/templates/includes/k8s_error.html @@ -1,12 +1,14 @@ {% if show_error %} -
- {% if has_list %} - {% if message %}{{ message }}{% endif %} -
    - {% for error in errors %}
  • {{ error }}
  • {% endfor %} -
- {% else %} - {{ message }} - {% endif %} -
+
+ {% if has_list %} + {% if message %}{{ message }}{% endif %} +
    + {% for error in errors %} +
  • {{ error }}
  • + {% endfor %} +
+ {% else %} + {{ message }} + {% endif %} +
{% endif %} diff --git a/src/servala/frontend/templates/includes/message.html b/src/servala/frontend/templates/includes/message.html index 278c009..d5debc9 100644 --- a/src/servala/frontend/templates/includes/message.html +++ b/src/servala/frontend/templates/includes/message.html @@ -1,29 +1,28 @@ -
+
{{ message }}
+ +document.addEventListener('DOMContentLoaded', function() { + const alert = document.getElementById('auto-dismiss-alert-{{ forloop.counter0|default:'0' }}'); + if (alert && !alert.classList.contains('alert-danger')) { + setTimeout(function() { + let opacity = 1; + const fadeOutInterval = setInterval(function() { + if (opacity > 0.05) { + opacity -= 0.05; + alert.style.opacity = opacity; + } else { + clearInterval(fadeOutInterval); + const bsAlert = new bootstrap.Alert(alert); + bsAlert.close(); + } + }, 25); + }, 5000); + } +}); + \ No newline at end of file diff --git a/src/servala/frontend/templatetags/error_filters.py b/src/servala/frontend/templatetags/error_filters.py index 410d49a..18c4c4d 100644 --- a/src/servala/frontend/templatetags/error_filters.py +++ b/src/servala/frontend/templatetags/error_filters.py @@ -3,7 +3,6 @@ Template filters for safe error formatting. """ import html - from django import template from django.utils.safestring import mark_safe diff --git a/src/servala/frontend/views/support.py b/src/servala/frontend/views/support.py index 6f4c4aa..a5de757 100644 --- a/src/servala/frontend/views/support.py +++ b/src/servala/frontend/views/support.py @@ -51,8 +51,7 @@ class SupportView(OrganizationViewMixin, FormView): self.request, mark_safe( _( - "There was an error submitting your support request. " - 'Please try again or contact us directly at servala-support@vshn.ch.' + 'There was an error submitting your support request. Please try again or contact us directly at servala-support@vshn.ch.' ) ), ) diff --git a/src/servala/settings.py b/src/servala/settings.py index 63f7895..ed28324 100644 --- a/src/servala/settings.py +++ b/src/servala/settings.py @@ -150,7 +150,6 @@ INSTALLED_APPS = [ # The frontend app is loaded early in order to supersede some allauth views/behaviour "servala.frontend", "django.forms", - "django_jsonform", "template_partials", "rules.apps.AutodiscoverRulesConfig", "allauth", diff --git a/src/servala/static/css/servala.css b/src/servala/static/css/servala.css index ee4c080..db9052a 100644 --- a/src/servala/static/css/servala.css +++ b/src/servala/static/css/servala.css @@ -174,21 +174,3 @@ a.btn-keycloak { margin-top: -16px; padding-right: 28px; } - -/* Service cards equal height styling */ -.service-cards-container .card { - height: 100%; - display: flex; - flex-direction: column; -} - -.service-cards-container .card-body { - flex-grow: 1; - display: flex; - flex-direction: column; - justify-content: flex-start; -} - -.service-cards-container .card-footer { - margin-top: auto; -} diff --git a/uv.lock b/uv.lock index df4202a..68b4f94 100644 --- a/uv.lock +++ b/uv.lock @@ -338,18 +338,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/75/8a/2c5d88cd540d83ceaa1cb3191ed35dfed0caacc6fe2ff5fe74c9ecc7776f/django_fernet_encrypted_fields-0.3.0-py3-none-any.whl", hash = "sha256:a17cca5bf3638ee44674e64f30792d5960b1d4d4b291ec478c27515fc4860612", size = 5400, upload-time = "2025-02-21T02:58:40.832Z" }, ] -[[package]] -name = "django-jsonform" -version = "2.23.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "django" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/9b/a8/83c57acbc153b86615be279cee5a194ce1163b578f29a9f6d658f267785e/django_jsonform-2.23.2.tar.gz", hash = "sha256:6fa2ba7c082be51d738e6c66e35075a3cb9ebc2f941e3a477c988900a7fe3269", size = 108182, upload-time = "2025-01-29T22:31:34.093Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3a/8e/8766f4bc535917ccbc6b3dcb57caf82210a6bacd613c3d9dbaec81018935/django_jsonform-2.23.2-py3-none-any.whl", hash = "sha256:1b7f94c5a2bd22c844e035a9940a9c8586f7b8fc3346ef2a6a13ba608e0059d7", size = 109148, upload-time = "2025-01-29T22:31:31.186Z" }, -] - [[package]] name = "django-scopes" version = "2.0.0" @@ -1062,7 +1050,6 @@ dependencies = [ { name = "django" }, { name = "django-allauth" }, { name = "django-fernet-encrypted-fields" }, - { name = "django-jsonform" }, { name = "django-scopes" }, { name = "django-storages", extra = ["s3"] }, { name = "django-template-partials" }, @@ -1099,7 +1086,6 @@ requires-dist = [ { name = "django", specifier = "==5.2.4" }, { name = "django-allauth", specifier = ">=65.10.0" }, { name = "django-fernet-encrypted-fields", specifier = ">=0.3.0" }, - { name = "django-jsonform", specifier = ">=2.22.0" }, { name = "django-scopes", specifier = ">=2.0.0" }, { name = "django-storages", extras = ["s3"], specifier = ">=1.14.6" }, { name = "django-template-partials", specifier = ">=24.4" },