From 5f894c4e6e3c4e9e37c471845f4e6ce4c21a0f43 Mon Sep 17 00:00:00 2001 From: Eduardo Silva Date: Thu, 12 Mar 2026 18:41:21 -0300 Subject: [PATCH] add application details view and update related forms; improve layout --- app_gateway/forms.py | 24 ++- .../0005_alter_applicationroute_order.py | 18 +++ app_gateway/models.py | 2 +- app_gateway/urls.py | 1 + app_gateway/views.py | 74 +++++++-- ...alter_gatekeeperipaddress_prefix_length.py | 18 +++ templates/app_gateway/app_gateway_list.html | 128 +++------------- .../app_gateway/application_details.html | 142 ++++++++++++++++++ 8 files changed, 274 insertions(+), 133 deletions(-) create mode 100644 app_gateway/migrations/0005_alter_applicationroute_order.py create mode 100644 gatekeeper/migrations/0004_alter_gatekeeperipaddress_prefix_length.py create mode 100644 templates/app_gateway/application_details.html diff --git a/app_gateway/forms.py b/app_gateway/forms.py index 5b86428..79425e2 100644 --- a/app_gateway/forms.py +++ b/app_gateway/forms.py @@ -65,9 +65,8 @@ class ApplicationForm(forms.ModelForm): class ApplicationHostForm(forms.ModelForm): class Meta: model = ApplicationHost - fields = ['application', 'hostname'] + fields = ['hostname'] labels = { - 'application': _('Application'), 'hostname': _('Hostname'), } @@ -78,8 +77,7 @@ class ApplicationHostForm(forms.ModelForm): self.helper = FormHelper() self.helper.layout = Layout( Div( - Div('application', css_class='col-md-6'), - Div('hostname', css_class='col-md-6'), + Div('hostname', css_class='col-md-12'), css_class='row' ), Div( @@ -134,9 +132,8 @@ class AccessPolicyForm(forms.ModelForm): class ApplicationPolicyForm(forms.ModelForm): class Meta: model = ApplicationPolicy - fields = ['application', 'default_policy'] + fields = ['default_policy'] labels = { - 'application': _('Application'), 'default_policy': _('Default Policy'), } @@ -147,8 +144,7 @@ class ApplicationPolicyForm(forms.ModelForm): self.helper = FormHelper() self.helper.layout = Layout( Div( - Div('application', css_class='col-md-6'), - Div('default_policy', css_class='col-md-6'), + Div('default_policy', css_class='col-md-12'), css_class='row' ), Div( @@ -165,13 +161,12 @@ class ApplicationPolicyForm(forms.ModelForm): class ApplicationRouteForm(forms.ModelForm): class Meta: model = ApplicationRoute - fields = ['application', 'name', 'path_prefix', 'policy', 'order'] + fields = ['name', 'path_prefix', 'policy', 'order'] labels = { - 'application': _('Application'), 'name': _('Route Name'), 'path_prefix': _('Path Prefix'), 'policy': _('Policy'), - 'order': _('Priority Order'), + 'order': _('Order'), } def __init__(self, *args, **kwargs): @@ -181,13 +176,12 @@ class ApplicationRouteForm(forms.ModelForm): self.helper = FormHelper() self.helper.layout = Layout( Div( - Div('application', css_class='col-md-6'), - Div('name', css_class='col-md-6'), + Div('name', css_class='col-md-12'), css_class='row' ), Div( - Div('path_prefix', css_class='col-md-8'), - Div('order', css_class='col-md-4'), + Div('path_prefix', css_class='col-xl-7'), + Div('order', css_class='col-xl-5'), css_class='row' ), Div( diff --git a/app_gateway/migrations/0005_alter_applicationroute_order.py b/app_gateway/migrations/0005_alter_applicationroute_order.py new file mode 100644 index 0000000..6c85bc5 --- /dev/null +++ b/app_gateway/migrations/0005_alter_applicationroute_order.py @@ -0,0 +1,18 @@ +# Generated by Django 5.2.12 on 2026-03-12 21:39 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('app_gateway', '0004_alter_application_display_name'), + ] + + operations = [ + migrations.AlterField( + model_name='applicationroute', + name='order', + field=models.PositiveIntegerField(default=0), + ), + ] diff --git a/app_gateway/models.py b/app_gateway/models.py index 22bcff4..ab68b87 100644 --- a/app_gateway/models.py +++ b/app_gateway/models.py @@ -87,7 +87,7 @@ class ApplicationRoute(models.Model): name = models.SlugField(max_length=64, help_text=_("Route identifier, used in export (e.g.: public_area)")) path_prefix = models.CharField(max_length=255) policy = models.ForeignKey(AccessPolicy, on_delete=models.PROTECT, related_name='routes') - order = models.PositiveIntegerField(default=0, help_text=_("Evaluation order — lower value means higher priority")) + order = models.PositiveIntegerField(default=0) created = models.DateTimeField(auto_now_add=True) updated = models.DateTimeField(auto_now=True) diff --git a/app_gateway/urls.py b/app_gateway/urls.py index 14d0e56..fac4c35 100644 --- a/app_gateway/urls.py +++ b/app_gateway/urls.py @@ -9,6 +9,7 @@ urlpatterns = [ # Applications path('application/manage/', views.view_manage_application, name='manage_application'), path('application/delete/', views.view_delete_application, name='delete_application'), + path('application/view/', views.view_application_details, name='view_application'), # Application Hosts path('host/manage/', views.view_manage_application_host, name='manage_application_host'), diff --git a/app_gateway/views.py b/app_gateway/views.py index ae51793..6bd2725 100644 --- a/app_gateway/views.py +++ b/app_gateway/views.py @@ -23,7 +23,6 @@ def view_app_gateway_list(request): hosts = ApplicationHost.objects.all().order_by('hostname') access_policies = AccessPolicy.objects.all().order_by('name') app_policies = ApplicationPolicy.objects.all().order_by('application__name') - routes = ApplicationRoute.objects.all().order_by('application__name', 'order', 'path_prefix') tab = request.GET.get('tab', 'applications') @@ -32,12 +31,31 @@ def view_app_gateway_list(request): 'hosts': hosts, 'access_policies': access_policies, 'app_policies': app_policies, - 'routes': routes, 'active_tab': tab, } return render(request, 'app_gateway/app_gateway_list.html', context) +@login_required +def view_application_details(request): + if not UserAcl.objects.filter(user=request.user).filter(user_level__gte=20).exists(): + return render(request, 'access_denied.html', {'page_title': _('Access Denied')}) + + application_uuid = request.GET.get('uuid') + application = get_object_or_404(Application, uuid=application_uuid) + + hosts = application.hosts.all().order_by('hostname') + routes = application.routes.all().order_by('order', 'path_prefix') + + context = { + 'application': application, + 'hosts': hosts, + 'routes': routes, + 'page_title': _('Application Details'), + } + return render(request, 'app_gateway/application_details.html', context) + + @login_required def view_manage_application(request): if not UserAcl.objects.filter(user=request.user).filter(user_level__gte=50).exists(): @@ -97,19 +115,24 @@ def view_manage_application_host(request): return render(request, 'access_denied.html', {'page_title': _('Access Denied')}) application_host_uuid = request.GET.get('uuid') + application_uuid = request.GET.get('application_uuid') if application_host_uuid: application_host = get_object_or_404(ApplicationHost, uuid=application_host_uuid) + application = application_host.application title = _('Edit Application Host') else: application_host = None + application = get_object_or_404(Application, uuid=application_uuid) title = _('Add Application Host') - cancel_url = reverse('app_gateway_list') + '?tab=hosts' + cancel_url = reverse('view_application') + f'?uuid={application.uuid}#hosts' form = ApplicationHostForm(request.POST or None, instance=application_host, cancel_url=cancel_url) if form.is_valid(): - form.save() + host = form.save(commit=False) + host.application = application + host.save() messages.success(request, _('Application Host saved successfully.')) return redirect(cancel_url) @@ -127,8 +150,9 @@ def view_delete_application_host(request): return render(request, 'access_denied.html', {'page_title': _('Access Denied')}) application_host = get_object_or_404(ApplicationHost, uuid=request.GET.get('uuid')) + application = application_host.application - cancel_url = reverse('app_gateway_list') + '?tab=hosts' + cancel_url = reverse('view_application') + f'?uuid={application.uuid}#hosts' if request.method == 'POST': application_host.delete() @@ -203,20 +227,24 @@ def view_manage_application_policy(request): return render(request, 'access_denied.html', {'page_title': _('Access Denied')}) application_policy_uuid = request.GET.get('uuid') + application_uuid = request.GET.get('application_uuid') if application_policy_uuid: application_policy = get_object_or_404(ApplicationPolicy, uuid=application_policy_uuid) + application = application_policy.application title = _('Edit Application Default Policy') else: application_policy = None + application = get_object_or_404(Application, uuid=application_uuid) title = _('Set Application Default Policy') - cancel_url = reverse('app_gateway_list') + '?tab=applications' - + cancel_url = reverse('view_application') + f'?uuid={application.uuid}' form = ApplicationPolicyForm(request.POST or None, instance=application_policy, cancel_url=cancel_url) if form.is_valid(): - form.save() + policy_config = form.save(commit=False) + policy_config.application = application + policy_config.save() messages.success(request, _('Application Default Policy saved successfully.')) return redirect(cancel_url) @@ -234,8 +262,9 @@ def view_delete_application_policy(request): return render(request, 'access_denied.html', {'page_title': _('Access Denied')}) application_policy = get_object_or_404(ApplicationPolicy, uuid=request.GET.get('uuid')) + application = application_policy.application - cancel_url = reverse('app_gateway_list') + '?tab=applications' + cancel_url = reverse('view_application') + f'?uuid={application.uuid}' if request.method == 'POST': application_policy.delete() @@ -259,26 +288,46 @@ def view_manage_application_route(request): return render(request, 'access_denied.html', {'page_title': _('Access Denied')}) application_route_uuid = request.GET.get('uuid') + application_uuid = request.GET.get('application_uuid') if application_route_uuid: application_route = get_object_or_404(ApplicationRoute, uuid=application_route_uuid) + application = application_route.application title = _('Edit Application Route') else: application_route = None + application = get_object_or_404(Application, uuid=application_uuid) title = _('Add Application Route') - cancel_url = reverse('app_gateway_list') + '?tab=routes' + cancel_url = reverse('view_application') + f'?uuid={application.uuid}#routes' form = ApplicationRouteForm(request.POST or None, instance=application_route, cancel_url=cancel_url) if form.is_valid(): - form.save() + route = form.save(commit=False) + route.application = application + route.save() messages.success(request, _('Application Route saved successfully.')) return redirect(cancel_url) + form_description = { + 'size': 'col-lg-6', + 'content': _(''' +
Application Route
+

A Route defines a path prefix within this Application that requires a specific Access Policy.

+ + ''') + } + context = { 'form': form, 'title': title, 'page_title': title, + 'form_description': form_description, } return render(request, 'generic_form.html', context) @@ -289,8 +338,9 @@ def view_delete_application_route(request): return render(request, 'access_denied.html', {'page_title': _('Access Denied')}) application_route = get_object_or_404(ApplicationRoute, uuid=request.GET.get('uuid')) + application = application_route.application - cancel_url = reverse('app_gateway_list') + '?tab=routes' + cancel_url = reverse('view_application') + f'?uuid={application.uuid}#routes' if request.method == 'POST': application_route.delete() diff --git a/gatekeeper/migrations/0004_alter_gatekeeperipaddress_prefix_length.py b/gatekeeper/migrations/0004_alter_gatekeeperipaddress_prefix_length.py new file mode 100644 index 0000000..775f705 --- /dev/null +++ b/gatekeeper/migrations/0004_alter_gatekeeperipaddress_prefix_length.py @@ -0,0 +1,18 @@ +# Generated by Django 5.2.12 on 2026-03-12 21:39 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('gatekeeper', '0003_alter_authmethodalloweddomain_auth_method_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='gatekeeperipaddress', + name='prefix_length', + field=models.PositiveSmallIntegerField(blank=True, null=True), + ), + ] diff --git a/templates/app_gateway/app_gateway_list.html b/templates/app_gateway/app_gateway_list.html index d52058b..184b6d3 100644 --- a/templates/app_gateway/app_gateway_list.html +++ b/templates/app_gateway/app_gateway_list.html @@ -18,19 +18,13 @@ {% trans 'Access Policies' %} -
{% if active_tab == 'applications' or active_tab == 'hosts' %} -
-
+ {% if active_tab == 'applications' %} - - {% if applications %}
- + + - {% for app in applications %} - - + + + - {% endfor %} @@ -107,11 +86,6 @@ {% endif %} {% elif active_tab == 'hosts' %} - {% if hosts %}
@@ -120,22 +94,15 @@
- {% for host in hosts %} - - @@ -197,56 +164,7 @@ {% endif %} - {% elif active_tab == 'routes' %} - - - {% if routes %} -
-
{% trans 'Name' %}{% trans 'Display Name' %} {% trans 'Upstream' %}{% trans 'Hosts' %}{% trans 'Routes' %} {% trans 'Default Policy' %}{% trans 'Actions' %}
{{ app.name }}{{ app.display_name }} + + {{ app }} + + {{ app.upstream }}{{ app.hosts.count }}{{ app.routes.count }} {% if app.default_policy_config %} - {{ app.default_policy_config.default_policy.name }} - - - - - - + {{ app.default_policy_config.default_policy.name }} {% else %} - {% trans 'Not set' %} + {% trans 'Default (Deny)' %} {% endif %} - - - - - - -
{% trans 'Hostname' %} {% trans 'Application' %}{% trans 'Actions' %}
{{ host.hostname }}{{ host.application.display_name }} - - - - - + + + {{ host.application }}
- - - - - - - - - - - - {% for route in routes %} - - - - - - - - - {% endfor %} - -
{% trans 'Application' %}{% trans 'Route Name' %}{% trans 'Path Prefix' %}{% trans 'Policy' %}{% trans 'Order' %}{% trans 'Actions' %}
{{ route.application.display_name }}{{ route.name }}{{ route.path_prefix }}{{ route.policy.name }}{{ route.order }} - - - - - - -
-
- {% else %} -
- {% trans 'No Routes found.' %} -
{% endif %} - - {% endif %}
diff --git a/templates/app_gateway/application_details.html b/templates/app_gateway/application_details.html new file mode 100644 index 0000000..4099829 --- /dev/null +++ b/templates/app_gateway/application_details.html @@ -0,0 +1,142 @@ +{% extends "base.html" %} +{% load i18n %} + +{% block content %} +
+ +
+ +
+
+ {% trans 'Name' %}: {{ application.name }}
+ {% trans 'Display Name' %}: {{ application.display_name }}
+ {% trans 'Upstream' %}: {{ application.upstream }}
+
+
+ {% trans 'Default Policy' %}: + {% if application.default_policy_config %} + {{ application.default_policy_config.default_policy.name }} + + + {% else %} + {% trans 'Default (Deny)' %} + {% trans 'Set Policy' %} + {% endif %} +
+
+ +
+ +

{% trans 'Application Hosts' %}

+ + + {% if hosts %} +
+ + + + + + + + + {% for host in hosts %} + + + + + {% endfor %} + +
{% trans 'Hostname' %}{% trans 'Actions' %}
{{ host.hostname }} + + + + + + +
+
+ {% else %} +
+ {% trans 'No Hosts configured for this application.' %} +
+ {% endif %} + +
+ +

{% trans 'Application Routes' %}

+ + + {% if routes %} +
+ + + + + + + + + + + + {% for route in routes %} + + + + + + + + {% endfor %} + +
{% trans 'Route Name' %}{% trans 'Path Prefix' %}{% trans 'Policy' %}{% trans 'Order' %}{% trans 'Actions' %}
{{ route.name }}{{ route.path_prefix }}{{ route.policy.name }}{{ route.order }} + + + + + + +
+
+ {% else %} +
+ {% trans 'No Routes configured for this application.' %} +
+ {% endif %} + +
+
+{% endblock %} \ No newline at end of file