diff --git a/app_gateway/forms.py b/app_gateway/forms.py
new file mode 100644
index 0000000..fe875a4
--- /dev/null
+++ b/app_gateway/forms.py
@@ -0,0 +1,205 @@
+from urllib.parse import urlparse
+
+from crispy_forms.helper import FormHelper
+from crispy_forms.layout import Layout, Submit, HTML, Div
+from django import forms
+from django.utils.translation import gettext_lazy as _
+
+from app_gateway.models import (
+ Application, ApplicationHost, AccessPolicy, ApplicationPolicy, ApplicationRoute
+)
+
+
+class ApplicationForm(forms.ModelForm):
+ class Meta:
+ model = Application
+ fields = ['name', 'display_name', 'upstream']
+ labels = {
+ 'name': _('Name'),
+ 'display_name': _('Display Name'),
+ 'upstream': _('Upstream'),
+ }
+
+ def __init__(self, *args, **kwargs):
+ cancel_url = kwargs.pop('cancel_url', '#')
+ super().__init__(*args, **kwargs)
+
+ self.helper = FormHelper()
+ self.helper.layout = Layout(
+ Div(
+ Div('name', css_class='col-md-6'),
+ Div('display_name', css_class='col-md-6'),
+ css_class='row'
+ ),
+ Div(
+ Div('upstream', css_class='col-md-12'),
+ css_class='row'
+ ),
+ Div(
+ Div(
+ Submit('submit', _('Save'), css_class='btn btn-primary'),
+ HTML(f'{_("Cancel")}'),
+ css_class='col-12 d-flex justify-content-end gap-2 mt-3'
+ ),
+ css_class='row'
+ )
+ )
+
+ def clean(self):
+ cleaned_data = super().clean()
+ upstream = (cleaned_data.get("upstream") or "").strip()
+
+ if upstream:
+ if " " in upstream:
+ self.add_error("upstream", _("Upstream URL cannot contain spaces."))
+
+ parsed = urlparse(upstream)
+ is_valid = (parsed.scheme in {"http", "https"} and bool(parsed.netloc))
+
+ if not is_valid:
+ self.add_error("upstream", _("Enter a valid upstream URL starting with http:// or https://"))
+
+ return cleaned_data
+
+
+class ApplicationHostForm(forms.ModelForm):
+ class Meta:
+ model = ApplicationHost
+ fields = ['application', 'hostname']
+ labels = {
+ 'application': _('Application'),
+ 'hostname': _('Hostname'),
+ }
+
+ def __init__(self, *args, **kwargs):
+ cancel_url = kwargs.pop('cancel_url', '#')
+ super().__init__(*args, **kwargs)
+
+ self.helper = FormHelper()
+ self.helper.layout = Layout(
+ Div(
+ Div('application', css_class='col-md-6'),
+ Div('hostname', css_class='col-md-6'),
+ css_class='row'
+ ),
+ Div(
+ Div(
+ Submit('submit', _('Save'), css_class='btn btn-primary'),
+ HTML(f'{_("Cancel")}'),
+ css_class='col-12 d-flex justify-content-end gap-2 mt-3'
+ ),
+ css_class='row'
+ )
+ )
+
+
+class AccessPolicyForm(forms.ModelForm):
+ class Meta:
+ model = AccessPolicy
+ fields = ['name', 'policy_type', 'groups', 'methods']
+ labels = {
+ 'name': _('Name'),
+ 'policy_type': _('Policy Type'),
+ 'groups': _('Allowed Groups'),
+ 'methods': _('Authentication Methods'),
+ }
+
+ def __init__(self, *args, **kwargs):
+ cancel_url = kwargs.pop('cancel_url', '#')
+ super().__init__(*args, **kwargs)
+
+ self.helper = FormHelper()
+ self.helper.layout = Layout(
+ Div(
+ Div('name', css_class='col-md-6'),
+ Div('policy_type', css_class='col-md-6'),
+ css_class='row'
+ ),
+ Div(
+ Div('groups', css_class='col-md-6'),
+ Div('methods', css_class='col-md-6'),
+ css_class='row'
+ ),
+ Div(
+ Div(
+ Submit('submit', _('Save'), css_class='btn btn-primary'),
+ HTML(f'{_("Cancel")}'),
+ css_class='col-12 d-flex justify-content-end gap-2 mt-3'
+ ),
+ css_class='row'
+ )
+ )
+
+
+class ApplicationPolicyForm(forms.ModelForm):
+ class Meta:
+ model = ApplicationPolicy
+ fields = ['application', 'default_policy']
+ labels = {
+ 'application': _('Application'),
+ 'default_policy': _('Default Policy'),
+ }
+
+ def __init__(self, *args, **kwargs):
+ cancel_url = kwargs.pop('cancel_url', '#')
+ super().__init__(*args, **kwargs)
+
+ self.helper = FormHelper()
+ self.helper.layout = Layout(
+ Div(
+ Div('application', css_class='col-md-6'),
+ Div('default_policy', css_class='col-md-6'),
+ css_class='row'
+ ),
+ Div(
+ Div(
+ Submit('submit', _('Save'), css_class='btn btn-primary'),
+ HTML(f'{_("Cancel")}'),
+ css_class='col-12 d-flex justify-content-end gap-2 mt-3'
+ ),
+ css_class='row'
+ )
+ )
+
+
+class ApplicationRouteForm(forms.ModelForm):
+ class Meta:
+ model = ApplicationRoute
+ fields = ['application', 'name', 'path_prefix', 'policy', 'order']
+ labels = {
+ 'application': _('Application'),
+ 'name': _('Route Name'),
+ 'path_prefix': _('Path Prefix'),
+ 'policy': _('Policy'),
+ 'order': _('Priority Order'),
+ }
+
+ def __init__(self, *args, **kwargs):
+ cancel_url = kwargs.pop('cancel_url', '#')
+ super().__init__(*args, **kwargs)
+
+ self.helper = FormHelper()
+ self.helper.layout = Layout(
+ Div(
+ Div('application', css_class='col-md-6'),
+ Div('name', css_class='col-md-6'),
+ css_class='row'
+ ),
+ Div(
+ Div('path_prefix', css_class='col-md-8'),
+ Div('order', css_class='col-md-4'),
+ css_class='row'
+ ),
+ Div(
+ Div('policy', css_class='col-md-12'),
+ css_class='row'
+ ),
+ Div(
+ Div(
+ Submit('submit', _('Save'), css_class='btn btn-primary'),
+ HTML(f'{_("Cancel")}'),
+ css_class='col-12 d-flex justify-content-end gap-2 mt-3'
+ ),
+ css_class='row'
+ )
+ )
diff --git a/app_gateway/migrations/0002_alter_application_display_name.py b/app_gateway/migrations/0002_alter_application_display_name.py
new file mode 100644
index 0000000..da11eee
--- /dev/null
+++ b/app_gateway/migrations/0002_alter_application_display_name.py
@@ -0,0 +1,18 @@
+# Generated by Django 5.2.12 on 2026-03-12 14:38
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('app_gateway', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='application',
+ name='display_name',
+ field=models.CharField(blank=True, max_length=128),
+ ),
+ ]
diff --git a/app_gateway/migrations/0003_alter_application_display_name.py b/app_gateway/migrations/0003_alter_application_display_name.py
new file mode 100644
index 0000000..875fd68
--- /dev/null
+++ b/app_gateway/migrations/0003_alter_application_display_name.py
@@ -0,0 +1,18 @@
+# Generated by Django 5.2.12 on 2026-03-12 14:46
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('app_gateway', '0002_alter_application_display_name'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='application',
+ name='display_name',
+ field=models.CharField(max_length=128),
+ ),
+ ]
diff --git a/app_gateway/migrations/0004_alter_application_display_name.py b/app_gateway/migrations/0004_alter_application_display_name.py
new file mode 100644
index 0000000..b055dc2
--- /dev/null
+++ b/app_gateway/migrations/0004_alter_application_display_name.py
@@ -0,0 +1,18 @@
+# Generated by Django 5.2.12 on 2026-03-12 14:52
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('app_gateway', '0003_alter_application_display_name'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='application',
+ name='display_name',
+ field=models.CharField(blank=True, max_length=128),
+ ),
+ ]
diff --git a/app_gateway/models.py b/app_gateway/models.py
index 0ae00de..22bcff4 100644
--- a/app_gateway/models.py
+++ b/app_gateway/models.py
@@ -8,7 +8,7 @@ from gatekeeper.models import GatekeeperGroup, AuthMethod
class Application(models.Model):
name = models.SlugField(max_length=64, unique=True)
- display_name = models.CharField(max_length=128)
+ display_name = models.CharField(max_length=128, blank=True)
upstream = models.CharField(max_length=255, help_text=_("Upstream address, e.g.: http://10.188.18.27:3000"))
created = models.DateTimeField(auto_now_add=True)
@@ -16,7 +16,10 @@ class Application(models.Model):
uuid = models.UUIDField(unique=True, default=uuid.uuid4, editable=False)
def __str__(self):
- return self.display_name
+ if self.display_name:
+ return f"{self.display_name} ({self.name})"
+ else:
+ return self.name
class Meta:
ordering = ['name']
diff --git a/app_gateway/urls.py b/app_gateway/urls.py
new file mode 100644
index 0000000..14d0e56
--- /dev/null
+++ b/app_gateway/urls.py
@@ -0,0 +1,28 @@
+from django.urls import path
+
+from app_gateway import views
+
+urlpatterns = [
+ # Main Dashboard / List
+ path('', views.view_app_gateway_list, name='app_gateway_list'),
+
+ # Applications
+ path('application/manage/', views.view_manage_application, name='manage_application'),
+ path('application/delete/', views.view_delete_application, name='delete_application'),
+
+ # Application Hosts
+ path('host/manage/', views.view_manage_application_host, name='manage_application_host'),
+ path('host/delete/', views.view_delete_application_host, name='delete_application_host'),
+
+ # Access Policies
+ path('policy/manage/', views.view_manage_access_policy, name='manage_access_policy'),
+ path('policy/delete/', views.view_delete_access_policy, name='delete_access_policy'),
+
+ # Application Default Policies
+ path('app_policy/manage/', views.view_manage_application_policy, name='manage_application_policy'),
+ path('app_policy/delete/', views.view_delete_application_policy, name='delete_application_policy'),
+
+ # Application Routes
+ path('route/manage/', views.view_manage_application_route, name='manage_application_route'),
+ path('route/delete/', views.view_delete_application_route, name='delete_application_route'),
+]
diff --git a/app_gateway/views.py b/app_gateway/views.py
index 60f00ef..ae51793 100644
--- a/app_gateway/views.py
+++ b/app_gateway/views.py
@@ -1 +1,309 @@
-# Create your views here.
+from django.contrib import messages
+from django.contrib.auth.decorators import login_required
+from django.shortcuts import render, get_object_or_404, redirect
+from django.urls import reverse
+from django.utils.translation import gettext as _
+
+from app_gateway.forms import (
+ ApplicationForm, ApplicationHostForm, AccessPolicyForm,
+ ApplicationPolicyForm, ApplicationRouteForm
+)
+from app_gateway.models import (
+ Application, ApplicationHost, AccessPolicy, ApplicationPolicy, ApplicationRoute
+)
+from user_manager.models import UserAcl
+
+
+@login_required
+def view_app_gateway_list(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')})
+
+ applications = Application.objects.all().order_by('name')
+ 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')
+
+ context = {
+ 'applications': applications,
+ '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_manage_application(request):
+ if not UserAcl.objects.filter(user=request.user).filter(user_level__gte=50).exists():
+ return render(request, 'access_denied.html', {'page_title': _('Access Denied')})
+
+ application_uuid = request.GET.get('uuid')
+
+ if application_uuid:
+ application = get_object_or_404(Application, uuid=application_uuid)
+ title = _('Edit Application')
+ else:
+ application = None
+ title = _('Create Application')
+
+ cancel_url = reverse('app_gateway_list') + '?tab=applications'
+
+ form = ApplicationForm(request.POST or None, instance=application, cancel_url=cancel_url)
+ if form.is_valid():
+ form.save()
+ messages.success(request, _('Application saved successfully.'))
+ return redirect(cancel_url)
+
+ context = {
+ 'form': form,
+ 'title': title,
+ 'page_title': title,
+ }
+ return render(request, 'generic_form.html', context)
+
+
+@login_required
+def view_delete_application(request):
+ if not UserAcl.objects.filter(user=request.user).filter(user_level__gte=50).exists():
+ return render(request, 'access_denied.html', {'page_title': _('Access Denied')})
+
+ application = get_object_or_404(Application, uuid=request.GET.get('uuid'))
+
+ cancel_url = reverse('app_gateway_list') + '?tab=applications'
+
+ if request.method == 'POST':
+ application.delete()
+ messages.success(request, _('Application deleted successfully.'))
+ return redirect(cancel_url)
+
+ context = {
+ 'application': application,
+ 'title': _('Delete Application'),
+ 'cancel_url': cancel_url,
+ 'text': _('Are you sure you want to delete the application "%(name)s"?') % {'name': application.display_name}
+ }
+ return render(request, 'generic_delete_confirmation.html', context)
+
+
+@login_required
+def view_manage_application_host(request):
+ if not UserAcl.objects.filter(user=request.user).filter(user_level__gte=50).exists():
+ return render(request, 'access_denied.html', {'page_title': _('Access Denied')})
+
+ application_host_uuid = request.GET.get('uuid')
+
+ if application_host_uuid:
+ application_host = get_object_or_404(ApplicationHost, uuid=application_host_uuid)
+ title = _('Edit Application Host')
+ else:
+ application_host = None
+ title = _('Add Application Host')
+
+ cancel_url = reverse('app_gateway_list') + '?tab=hosts'
+
+ form = ApplicationHostForm(request.POST or None, instance=application_host, cancel_url=cancel_url)
+ if form.is_valid():
+ form.save()
+ messages.success(request, _('Application Host saved successfully.'))
+ return redirect(cancel_url)
+
+ context = {
+ 'form': form,
+ 'title': title,
+ 'page_title': title,
+ }
+ return render(request, 'generic_form.html', context)
+
+
+@login_required
+def view_delete_application_host(request):
+ if not UserAcl.objects.filter(user=request.user).filter(user_level__gte=50).exists():
+ return render(request, 'access_denied.html', {'page_title': _('Access Denied')})
+
+ application_host = get_object_or_404(ApplicationHost, uuid=request.GET.get('uuid'))
+
+ cancel_url = reverse('app_gateway_list') + '?tab=hosts'
+
+ if request.method == 'POST':
+ application_host.delete()
+ messages.success(request, _('Application Host deleted successfully.'))
+ return redirect(cancel_url)
+
+ context = {
+ 'application_host': application_host,
+ 'title': _('Delete Application Host'),
+ 'cancel_url': cancel_url,
+ 'text': _('Are you sure you want to delete the host "%(hostname)s"?') % {'hostname': application_host.hostname}
+ }
+ return render(request, 'generic_delete_confirmation.html', context)
+
+
+@login_required
+def view_manage_access_policy(request):
+ if not UserAcl.objects.filter(user=request.user).filter(user_level__gte=50).exists():
+ return render(request, 'access_denied.html', {'page_title': _('Access Denied')})
+
+ access_policy_uuid = request.GET.get('uuid')
+
+ if access_policy_uuid:
+ access_policy = get_object_or_404(AccessPolicy, uuid=access_policy_uuid)
+ title = _('Edit Access Policy')
+ else:
+ access_policy = None
+ title = _('Create Access Policy')
+
+ cancel_url = reverse('app_gateway_list') + '?tab=policies'
+
+ form = AccessPolicyForm(request.POST or None, instance=access_policy, cancel_url=cancel_url)
+ if form.is_valid():
+ form.save()
+ messages.success(request, _('Access Policy saved successfully.'))
+ return redirect(cancel_url)
+
+ context = {
+ 'form': form,
+ 'title': title,
+ 'page_title': title,
+ }
+ return render(request, 'generic_form.html', context)
+
+
+@login_required
+def view_delete_access_policy(request):
+ if not UserAcl.objects.filter(user=request.user).filter(user_level__gte=50).exists():
+ return render(request, 'access_denied.html', {'page_title': _('Access Denied')})
+
+ access_policy = get_object_or_404(AccessPolicy, uuid=request.GET.get('uuid'))
+
+ cancel_url = reverse('app_gateway_list') + '?tab=policies'
+
+ if request.method == 'POST':
+ access_policy.delete()
+ messages.success(request, _('Access Policy deleted successfully.'))
+ return redirect(cancel_url)
+
+ context = {
+ 'access_policy': access_policy,
+ 'title': _('Delete Access Policy'),
+ 'cancel_url': cancel_url,
+ 'text': _('Are you sure you want to delete the access policy "%(name)s"?') % {'name': access_policy.name}
+ }
+ return render(request, 'generic_delete_confirmation.html', context)
+
+
+@login_required
+def view_manage_application_policy(request):
+ if not UserAcl.objects.filter(user=request.user).filter(user_level__gte=50).exists():
+ return render(request, 'access_denied.html', {'page_title': _('Access Denied')})
+
+ application_policy_uuid = request.GET.get('uuid')
+
+ if application_policy_uuid:
+ application_policy = get_object_or_404(ApplicationPolicy, uuid=application_policy_uuid)
+ title = _('Edit Application Default Policy')
+ else:
+ application_policy = None
+ title = _('Set Application Default Policy')
+
+ cancel_url = reverse('app_gateway_list') + '?tab=applications'
+
+
+ form = ApplicationPolicyForm(request.POST or None, instance=application_policy, cancel_url=cancel_url)
+ if form.is_valid():
+ form.save()
+ messages.success(request, _('Application Default Policy saved successfully.'))
+ return redirect(cancel_url)
+
+ context = {
+ 'form': form,
+ 'title': title,
+ 'page_title': title,
+ }
+ return render(request, 'generic_form.html', context)
+
+
+@login_required
+def view_delete_application_policy(request):
+ if not UserAcl.objects.filter(user=request.user).filter(user_level__gte=50).exists():
+ return render(request, 'access_denied.html', {'page_title': _('Access Denied')})
+
+ application_policy = get_object_or_404(ApplicationPolicy, uuid=request.GET.get('uuid'))
+
+ cancel_url = reverse('app_gateway_list') + '?tab=applications'
+
+ if request.method == 'POST':
+ application_policy.delete()
+ messages.success(request, _('Application Default Policy deleted successfully.'))
+ return redirect(cancel_url)
+
+ context = {
+ 'application_policy': application_policy,
+ 'title': _('Delete Application Default Policy'),
+ 'cancel_url': cancel_url,
+ 'text': _('Are you sure you want to remove the default policy for "%(name)s"?') % {
+ 'name': application_policy.application.display_name
+ }
+ }
+ return render(request, 'generic_delete_confirmation.html', context)
+
+
+@login_required
+def view_manage_application_route(request):
+ if not UserAcl.objects.filter(user=request.user).filter(user_level__gte=50).exists():
+ return render(request, 'access_denied.html', {'page_title': _('Access Denied')})
+
+ application_route_uuid = request.GET.get('uuid')
+
+ if application_route_uuid:
+ application_route = get_object_or_404(ApplicationRoute, uuid=application_route_uuid)
+ title = _('Edit Application Route')
+ else:
+ application_route = None
+ title = _('Add Application Route')
+
+ cancel_url = reverse('app_gateway_list') + '?tab=routes'
+
+ form = ApplicationRouteForm(request.POST or None, instance=application_route, cancel_url=cancel_url)
+ if form.is_valid():
+ form.save()
+ messages.success(request, _('Application Route saved successfully.'))
+ return redirect(cancel_url)
+
+ context = {
+ 'form': form,
+ 'title': title,
+ 'page_title': title,
+ }
+ return render(request, 'generic_form.html', context)
+
+
+@login_required
+def view_delete_application_route(request):
+ if not UserAcl.objects.filter(user=request.user).filter(user_level__gte=50).exists():
+ return render(request, 'access_denied.html', {'page_title': _('Access Denied')})
+
+ application_route = get_object_or_404(ApplicationRoute, uuid=request.GET.get('uuid'))
+
+ cancel_url = reverse('app_gateway_list') + '?tab=routes'
+
+ if request.method == 'POST':
+ application_route.delete()
+ messages.success(request, _('Application Route deleted successfully.'))
+ return redirect(cancel_url)
+
+ context = {
+ 'application_route': application_route,
+ 'title': _('Delete Application Route'),
+ 'cancel_url': cancel_url,
+ 'text': _('Are you sure you want to delete the route "%(name)s" (%(path)s)?') % {
+ 'name': application_route.name,
+ 'path': application_route.path_prefix
+ }
+ }
+ return render(request, 'generic_delete_confirmation.html', context)
diff --git a/templates/app_gateway/app_gateway_list.html b/templates/app_gateway/app_gateway_list.html
new file mode 100644
index 0000000..d52058b
--- /dev/null
+++ b/templates/app_gateway/app_gateway_list.html
@@ -0,0 +1,254 @@
+{% extends "base.html" %}
+{% load i18n %}
+
+{% block content %}
+
+
+
+
+
+
+
+ {% if active_tab == 'applications' or active_tab == 'hosts' %}
+
+
+ {% if active_tab == 'applications' %}
+
+
+ {% if applications %}
+
+
+
+
+ | {% trans 'Name' %} |
+ {% trans 'Display Name' %} |
+ {% trans 'Upstream' %} |
+ {% trans 'Default Policy' %} |
+ {% trans 'Actions' %} |
+
+
+
+ {% for app in applications %}
+
+ | {{ app.name }} |
+ {{ app.display_name }} |
+ {{ app.upstream }} |
+
+ {% if app.default_policy_config %}
+ {{ app.default_policy_config.default_policy.name }}
+
+
+
+
+
+
+ {% else %}
+ {% trans 'Not set' %}
+ {% endif %}
+ |
+
+
+
+
+
+
+
+ |
+
+ {% endfor %}
+
+
+
+ {% else %}
+
+ {% trans 'No Applications found.' %}
+
+ {% endif %}
+
+ {% elif active_tab == 'hosts' %}
+
+
+ {% if hosts %}
+
+
+
+
+ | {% trans 'Hostname' %} |
+ {% trans 'Application' %} |
+ {% trans 'Actions' %} |
+
+
+
+ {% for host in hosts %}
+
+ | {{ host.hostname }} |
+ {{ host.application.display_name }} |
+
+
+
+
+
+
+
+ |
+
+ {% endfor %}
+
+
+
+ {% else %}
+
+ {% trans 'No Hosts found.' %}
+
+ {% endif %}
+ {% endif %}
+
+ {% elif active_tab == 'policies' %}
+
+
+ {% if access_policies %}
+
+
+
+
+ | {% trans 'Name' %} |
+ {% trans 'Policy Type' %} |
+ {% trans 'Groups' %} |
+ {% trans 'Auth Methods' %} |
+ {% trans 'Actions' %} |
+
+
+
+ {% for policy in access_policies %}
+
+ | {{ policy.name }} |
+ {{ policy.get_policy_type_display }} |
+ {{ policy.groups.count }} |
+ {{ policy.methods.count }} |
+
+
+
+
+
+
+
+ |
+
+ {% endfor %}
+
+
+
+ {% else %}
+
+ {% trans 'No Access Policies found.' %}
+
+ {% 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 %}
+
+
+
+
+{% endblock %}
diff --git a/wireguard_webadmin/urls.py b/wireguard_webadmin/urls.py
index 1e2647d..d27d6a1 100644
--- a/wireguard_webadmin/urls.py
+++ b/wireguard_webadmin/urls.py
@@ -23,6 +23,7 @@ urlpatterns = [
path('peer/', include('wireguard_peer.urls')),
path('routing-templates/', include('routing_templates.urls')),
path('gatekeeper/', include('gatekeeper.urls')),
+ path('app_gateway/', include('app_gateway.urls')),
path('scheduler/', include('scheduler.urls')),
path('server/', include('wireguard.urls')),
path('tools/', include('wireguard_tools.urls')),