add app_gateway management views and templates

This commit is contained in:
Eduardo Silva
2026-03-12 14:15:14 -03:00
parent bd02f028b5
commit 289abdada3
9 changed files with 856 additions and 3 deletions

205
app_gateway/forms.py Normal file
View File

@@ -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'<a href="{cancel_url}" class="btn btn-secondary">{_("Cancel")}</a>'),
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'<a href="{cancel_url}" class="btn btn-secondary">{_("Cancel")}</a>'),
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'<a href="{cancel_url}" class="btn btn-secondary">{_("Cancel")}</a>'),
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'<a href="{cancel_url}" class="btn btn-secondary">{_("Cancel")}</a>'),
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'<a href="{cancel_url}" class="btn btn-secondary">{_("Cancel")}</a>'),
css_class='col-12 d-flex justify-content-end gap-2 mt-3'
),
css_class='row'
)
)

View File

@@ -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),
),
]

View File

@@ -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),
),
]

View File

@@ -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),
),
]

View File

@@ -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']

28
app_gateway/urls.py Normal file
View File

@@ -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'),
]

View File

@@ -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)