diff --git a/src/servala/core/models/organization.py b/src/servala/core/models/organization.py index da8c11b..f1ddf26 100644 --- a/src/servala/core/models/organization.py +++ b/src/servala/core/models/organization.py @@ -79,7 +79,9 @@ class Organization(ServalaModelMixin, models.Model): support_message = _( "Need help? We're happy to help via the support form." ).format(support_url=self.urls.support) - return mark_safe(f"{message} {support_message}") + return mark_safe( + f'{message} {support_message}' + ) @classmethod @transaction.atomic diff --git a/src/servala/core/models/service.py b/src/servala/core/models/service.py index 365fcce..b4d27b3 100644 --- a/src/servala/core/models/service.py +++ b/src/servala/core/models/service.py @@ -1,5 +1,6 @@ import copy import json +import re import kubernetes import rules @@ -604,6 +605,32 @@ class ServiceInstance(ServalaModelMixin, models.Model): spec_data = prune_empty_data(spec_data) return spec_data + @classmethod + def _format_kubernetes_error(cls, error_message): + """ + Format Kubernetes API error messages for better user experience. + Converts validation error arrays into unordered lists. + """ + # Pattern to match validation errors in brackets + pattern = r"\[([^\]]+)\]" + match = re.search(pattern, error_message) + + if not match: + return error_message + + errors_text = match.group(1) + # Split by comma and clean up each error + errors = [error.strip() for error in errors_text.split(",")] + + if len(errors) > 1: + # Format as HTML unordered list + error_list = "".join(f"
  • {error}
  • " for error in errors) + # Replace the bracketed section with the formatted list + formatted_message = re.sub(pattern, f"", error_message) + return mark_safe(formatted_message) + + return error_message + @classmethod def create_instance(cls, name, organization, context, created_by, spec_data): # Ensure the namespace exists @@ -616,7 +643,9 @@ class ServiceInstance(ServalaModelMixin, models.Model): context=context, ) except IntegrityError: - message = "An instance with this name already exists in this organization. Please choose a different name." + message = _( + "An instance with this name already exists in this organization. Please choose a different name." + ) raise ValidationError(organization.add_support_message(message)) try: @@ -655,12 +684,21 @@ class ServiceInstance(ServalaModelMixin, models.Model): try: error_body = json.loads(e.body) reason = error_body.get("message", str(e)) - message = f"Kubernetes API error: {reason}." + formatted_reason = cls._format_kubernetes_error(reason) + message = _("Error reported by control plane: {reason}").format( + reason=formatted_reason + ) raise ValidationError(organization.add_support_message(message)) except (ValueError, TypeError): - message = f"Kubernetes API error: {str(e)}." + formatted_error = cls._format_kubernetes_error(str(e)) + message = _("Error reported by control plane: {error}").format( + error=formatted_error + ) raise ValidationError(organization.add_support_message(message)) - message = f"Error creating instance: {str(e)}." + formatted_error = cls._format_kubernetes_error(str(e)) + message = _("Error creating instance: {error}").format( + error=formatted_error + ) raise ValidationError(organization.add_support_message(message)) return instance @@ -682,18 +720,29 @@ class ServiceInstance(ServalaModelMixin, models.Model): self.save() # Updates updated_at timestamp except ApiException as e: if e.status == 404: - message = "Service instance not found in Kubernetes. It may have been deleted externally." + message = _( + "Service instance not found in control plane. It may have been deleted externally." + ) raise ValidationError(self.organization.add_support_message(message)) try: error_body = json.loads(e.body) reason = error_body.get("message", str(e)) - message = f"Kubernetes API error updating instance: {reason}." + formatted_reason = self._format_kubernetes_error(reason) + message = _( + "Error reported by control plane while updating instance: {reason}" + ).format(reason=formatted_reason) raise ValidationError(self.organization.add_support_message(message)) except (ValueError, TypeError): - message = f"Kubernetes API error updating instance: {str(e)}." + formatted_error = self._format_kubernetes_error(str(e)) + message = _( + "Error reported by control plane while updating instance: {error}" + ).format(error=formatted_error) raise ValidationError(self.organization.add_support_message(message)) except Exception as e: - message = f"Error updating instance: {str(e)}." + formatted_error = self._format_kubernetes_error(str(e)) + message = _("Error updating instance: {error}").format( + error=formatted_error + ) raise ValidationError(self.organization.add_support_message(message)) @transaction.atomic diff --git a/src/servala/frontend/views/service.py b/src/servala/frontend/views/service.py index be1b80c..f9ce50d 100644 --- a/src/servala/frontend/views/service.py +++ b/src/servala/frontend/views/service.py @@ -146,7 +146,9 @@ class ServiceOfferingDetailView(OrganizationViewMixin, HtmxViewMixin, DetailView if not form: # Should not happen if context_object is valid, but as a safeguard messages.error( self.request, - self.organization.add_support_message("Could not initialize service form."), + self.organization.add_support_message( + _("Could not initialize service form.") + ), ) return self.render_to_response(context) @@ -166,7 +168,7 @@ class ServiceOfferingDetailView(OrganizationViewMixin, HtmxViewMixin, DetailView messages.error( self.request, self.organization.add_support_message( - f"Error creating instance: {str(e)}." + _(f"Error creating instance: {str(e)}.") ), ) @@ -374,7 +376,7 @@ class ServiceInstanceUpdateView( messages.error( self.request, self.organization.add_support_message( - f"Error updating instance: {str(e)}." + _(f"Error updating instance: {str(e)}.") ), ) return self.form_invalid(form) @@ -445,7 +447,9 @@ class ServiceInstanceDeleteView( messages.error( self.request, self.organization.add_support_message( - f"An error occurred while trying to delete instance '{self.object.name}': {str(e)}." + _( + f"An error occurred while trying to delete instance '{self.object.name}': {str(e)}." + ) ), ) response = HttpResponse()