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.
+
+ - Route Name: An internal identifier for this route (e.g., "public_api", "admin_area"). Used for reference and exports.
+ - Path Prefix: The URL path that triggers this route (e.g.,
/api/ or /admin/). Use / to match all remaining paths.
+ - Policy: The Access Policy that will be enforced when a user accesses this path.
+ - Order: Determines the priority of this route when evaluating the request. Lower numbers are evaluated first. If multiple routes match a path, the one with the lowest order wins.
+
+ ''')
+ }
+
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' %}
-
-
- {% trans 'Routes' %}
-
-
{% if active_tab == 'applications' or active_tab == 'hosts' %}
-
-
+
+
+ {% if active_tab == 'applications' %}
+
+ {% endif %}
{% if active_tab == 'applications' %}
-
-
{% if applications %}
| {% trans 'Name' %} |
- {% trans 'Display Name' %} |
{% trans 'Upstream' %} |
+ {% trans 'Hosts' %} |
+ {% trans 'Routes' %} |
{% trans 'Default Policy' %} |
- {% trans 'Actions' %} |
{% for app in applications %}
- | {{ 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 %}
|
-
-
-
-
-
-
-
- |
{% endfor %}
@@ -107,11 +86,6 @@
{% endif %}
{% elif active_tab == 'hosts' %}
-
{% if hosts %}
@@ -120,22 +94,15 @@
| {% trans 'Hostname' %} |
{% trans 'Application' %} |
- {% trans 'Actions' %} |
{% for host in hosts %}
| {{ host.hostname }} |
- {{ host.application.display_name }} |
-
-
-
-
-
-
+ |
+
+ {{ host.application }}
|
@@ -197,56 +164,7 @@
{% endif %}
- {% elif active_tab == 'routes' %}
-
-
- {% if routes %}
-
-
-
-
- | {% trans 'Application' %} |
- {% trans 'Route Name' %} |
- {% trans 'Path Prefix' %} |
- {% trans 'Policy' %} |
- {% trans 'Order' %} |
- {% trans 'Actions' %} |
-
-
-
- {% for route in routes %}
-
- | {{ route.application.display_name }} |
- {{ route.name }} |
- {{ route.path_prefix }} |
- {{ route.policy.name }} |
- {{ route.order }} |
-
-
-
-
-
-
-
- |
-
- {% endfor %}
-
-
-
- {% 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 %}
+
+
+
+
+ | {% trans 'Hostname' %} |
+ {% trans 'Actions' %} |
+
+
+
+ {% for host in hosts %}
+
+ | {{ host.hostname }} |
+
+
+
+
+
+
+
+ |
+
+ {% endfor %}
+
+
+
+ {% else %}
+
+ {% trans 'No Hosts configured for this application.' %}
+
+ {% endif %}
+
+
+
+
{% trans 'Application Routes' %}
+
+
+ {% if routes %}
+
+
+
+
+ | {% trans 'Route Name' %} |
+ {% trans 'Path Prefix' %} |
+ {% trans 'Policy' %} |
+ {% trans 'Order' %} |
+ {% trans 'Actions' %} |
+
+
+
+ {% for route in routes %}
+
+ | {{ route.name }} |
+ {{ route.path_prefix }} |
+ {{ route.policy.name }} |
+ {{ route.order }} |
+
+
+
+
+
+
+
+ |
+
+ {% endfor %}
+
+
+
+ {% else %}
+
+ {% trans 'No Routes configured for this application.' %}
+
+ {% endif %}
+
+
+
+{% endblock %}
\ No newline at end of file