add application details view and update related forms; improve layout

This commit is contained in:
Eduardo Silva
2026-03-12 18:41:21 -03:00
parent 7e86bd7208
commit 5f894c4e6e
8 changed files with 274 additions and 133 deletions

View File

@@ -65,9 +65,8 @@ class ApplicationForm(forms.ModelForm):
class ApplicationHostForm(forms.ModelForm): class ApplicationHostForm(forms.ModelForm):
class Meta: class Meta:
model = ApplicationHost model = ApplicationHost
fields = ['application', 'hostname'] fields = ['hostname']
labels = { labels = {
'application': _('Application'),
'hostname': _('Hostname'), 'hostname': _('Hostname'),
} }
@@ -78,8 +77,7 @@ class ApplicationHostForm(forms.ModelForm):
self.helper = FormHelper() self.helper = FormHelper()
self.helper.layout = Layout( self.helper.layout = Layout(
Div( Div(
Div('application', css_class='col-md-6'), Div('hostname', css_class='col-md-12'),
Div('hostname', css_class='col-md-6'),
css_class='row' css_class='row'
), ),
Div( Div(
@@ -134,9 +132,8 @@ class AccessPolicyForm(forms.ModelForm):
class ApplicationPolicyForm(forms.ModelForm): class ApplicationPolicyForm(forms.ModelForm):
class Meta: class Meta:
model = ApplicationPolicy model = ApplicationPolicy
fields = ['application', 'default_policy'] fields = ['default_policy']
labels = { labels = {
'application': _('Application'),
'default_policy': _('Default Policy'), 'default_policy': _('Default Policy'),
} }
@@ -147,8 +144,7 @@ class ApplicationPolicyForm(forms.ModelForm):
self.helper = FormHelper() self.helper = FormHelper()
self.helper.layout = Layout( self.helper.layout = Layout(
Div( Div(
Div('application', css_class='col-md-6'), Div('default_policy', css_class='col-md-12'),
Div('default_policy', css_class='col-md-6'),
css_class='row' css_class='row'
), ),
Div( Div(
@@ -165,13 +161,12 @@ class ApplicationPolicyForm(forms.ModelForm):
class ApplicationRouteForm(forms.ModelForm): class ApplicationRouteForm(forms.ModelForm):
class Meta: class Meta:
model = ApplicationRoute model = ApplicationRoute
fields = ['application', 'name', 'path_prefix', 'policy', 'order'] fields = ['name', 'path_prefix', 'policy', 'order']
labels = { labels = {
'application': _('Application'),
'name': _('Route Name'), 'name': _('Route Name'),
'path_prefix': _('Path Prefix'), 'path_prefix': _('Path Prefix'),
'policy': _('Policy'), 'policy': _('Policy'),
'order': _('Priority Order'), 'order': _('Order'),
} }
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@@ -181,13 +176,12 @@ class ApplicationRouteForm(forms.ModelForm):
self.helper = FormHelper() self.helper = FormHelper()
self.helper.layout = Layout( self.helper.layout = Layout(
Div( Div(
Div('application', css_class='col-md-6'), Div('name', css_class='col-md-12'),
Div('name', css_class='col-md-6'),
css_class='row' css_class='row'
), ),
Div( Div(
Div('path_prefix', css_class='col-md-8'), Div('path_prefix', css_class='col-xl-7'),
Div('order', css_class='col-md-4'), Div('order', css_class='col-xl-5'),
css_class='row' css_class='row'
), ),
Div( Div(

View File

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

View File

@@ -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)")) name = models.SlugField(max_length=64, help_text=_("Route identifier, used in export (e.g.: public_area)"))
path_prefix = models.CharField(max_length=255) path_prefix = models.CharField(max_length=255)
policy = models.ForeignKey(AccessPolicy, on_delete=models.PROTECT, related_name='routes') 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) created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True) updated = models.DateTimeField(auto_now=True)

View File

@@ -9,6 +9,7 @@ urlpatterns = [
# Applications # Applications
path('application/manage/', views.view_manage_application, name='manage_application'), path('application/manage/', views.view_manage_application, name='manage_application'),
path('application/delete/', views.view_delete_application, name='delete_application'), path('application/delete/', views.view_delete_application, name='delete_application'),
path('application/view/', views.view_application_details, name='view_application'),
# Application Hosts # Application Hosts
path('host/manage/', views.view_manage_application_host, name='manage_application_host'), path('host/manage/', views.view_manage_application_host, name='manage_application_host'),

View File

@@ -23,7 +23,6 @@ def view_app_gateway_list(request):
hosts = ApplicationHost.objects.all().order_by('hostname') hosts = ApplicationHost.objects.all().order_by('hostname')
access_policies = AccessPolicy.objects.all().order_by('name') access_policies = AccessPolicy.objects.all().order_by('name')
app_policies = ApplicationPolicy.objects.all().order_by('application__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') tab = request.GET.get('tab', 'applications')
@@ -32,12 +31,31 @@ def view_app_gateway_list(request):
'hosts': hosts, 'hosts': hosts,
'access_policies': access_policies, 'access_policies': access_policies,
'app_policies': app_policies, 'app_policies': app_policies,
'routes': routes,
'active_tab': tab, 'active_tab': tab,
} }
return render(request, 'app_gateway/app_gateway_list.html', context) 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 @login_required
def view_manage_application(request): def view_manage_application(request):
if not UserAcl.objects.filter(user=request.user).filter(user_level__gte=50).exists(): 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')}) return render(request, 'access_denied.html', {'page_title': _('Access Denied')})
application_host_uuid = request.GET.get('uuid') application_host_uuid = request.GET.get('uuid')
application_uuid = request.GET.get('application_uuid')
if application_host_uuid: if application_host_uuid:
application_host = get_object_or_404(ApplicationHost, uuid=application_host_uuid) application_host = get_object_or_404(ApplicationHost, uuid=application_host_uuid)
application = application_host.application
title = _('Edit Application Host') title = _('Edit Application Host')
else: else:
application_host = None application_host = None
application = get_object_or_404(Application, uuid=application_uuid)
title = _('Add Application Host') 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) form = ApplicationHostForm(request.POST or None, instance=application_host, cancel_url=cancel_url)
if form.is_valid(): if form.is_valid():
form.save() host = form.save(commit=False)
host.application = application
host.save()
messages.success(request, _('Application Host saved successfully.')) messages.success(request, _('Application Host saved successfully.'))
return redirect(cancel_url) return redirect(cancel_url)
@@ -127,8 +150,9 @@ def view_delete_application_host(request):
return render(request, 'access_denied.html', {'page_title': _('Access Denied')}) return render(request, 'access_denied.html', {'page_title': _('Access Denied')})
application_host = get_object_or_404(ApplicationHost, uuid=request.GET.get('uuid')) 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': if request.method == 'POST':
application_host.delete() application_host.delete()
@@ -203,20 +227,24 @@ def view_manage_application_policy(request):
return render(request, 'access_denied.html', {'page_title': _('Access Denied')}) return render(request, 'access_denied.html', {'page_title': _('Access Denied')})
application_policy_uuid = request.GET.get('uuid') application_policy_uuid = request.GET.get('uuid')
application_uuid = request.GET.get('application_uuid')
if application_policy_uuid: if application_policy_uuid:
application_policy = get_object_or_404(ApplicationPolicy, uuid=application_policy_uuid) application_policy = get_object_or_404(ApplicationPolicy, uuid=application_policy_uuid)
application = application_policy.application
title = _('Edit Application Default Policy') title = _('Edit Application Default Policy')
else: else:
application_policy = None application_policy = None
application = get_object_or_404(Application, uuid=application_uuid)
title = _('Set Application Default Policy') 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) form = ApplicationPolicyForm(request.POST or None, instance=application_policy, cancel_url=cancel_url)
if form.is_valid(): 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.')) messages.success(request, _('Application Default Policy saved successfully.'))
return redirect(cancel_url) return redirect(cancel_url)
@@ -234,8 +262,9 @@ def view_delete_application_policy(request):
return render(request, 'access_denied.html', {'page_title': _('Access Denied')}) return render(request, 'access_denied.html', {'page_title': _('Access Denied')})
application_policy = get_object_or_404(ApplicationPolicy, uuid=request.GET.get('uuid')) 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': if request.method == 'POST':
application_policy.delete() application_policy.delete()
@@ -259,26 +288,46 @@ def view_manage_application_route(request):
return render(request, 'access_denied.html', {'page_title': _('Access Denied')}) return render(request, 'access_denied.html', {'page_title': _('Access Denied')})
application_route_uuid = request.GET.get('uuid') application_route_uuid = request.GET.get('uuid')
application_uuid = request.GET.get('application_uuid')
if application_route_uuid: if application_route_uuid:
application_route = get_object_or_404(ApplicationRoute, uuid=application_route_uuid) application_route = get_object_or_404(ApplicationRoute, uuid=application_route_uuid)
application = application_route.application
title = _('Edit Application Route') title = _('Edit Application Route')
else: else:
application_route = None application_route = None
application = get_object_or_404(Application, uuid=application_uuid)
title = _('Add Application Route') 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) form = ApplicationRouteForm(request.POST or None, instance=application_route, cancel_url=cancel_url)
if form.is_valid(): if form.is_valid():
form.save() route = form.save(commit=False)
route.application = application
route.save()
messages.success(request, _('Application Route saved successfully.')) messages.success(request, _('Application Route saved successfully.'))
return redirect(cancel_url) return redirect(cancel_url)
form_description = {
'size': 'col-lg-6',
'content': _('''
<h5>Application Route</h5>
<p>A Route defines a path prefix within this Application that requires a specific Access Policy.</p>
<ul>
<li><strong>Route Name</strong>: An internal identifier for this route (e.g., "public_api", "admin_area"). Used for reference and exports.</li>
<li><strong>Path Prefix</strong>: The URL path that triggers this route (e.g., <code>/api/</code> or <code>/admin/</code>). Use <code>/</code> to match all remaining paths.</li>
<li><strong>Policy</strong>: The Access Policy that will be enforced when a user accesses this path.</li>
<li><strong>Order</strong>: 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.</li>
</ul>
''')
}
context = { context = {
'form': form, 'form': form,
'title': title, 'title': title,
'page_title': title, 'page_title': title,
'form_description': form_description,
} }
return render(request, 'generic_form.html', context) 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')}) return render(request, 'access_denied.html', {'page_title': _('Access Denied')})
application_route = get_object_or_404(ApplicationRoute, uuid=request.GET.get('uuid')) 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': if request.method == 'POST':
application_route.delete() application_route.delete()

View File

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

View File

@@ -18,19 +18,13 @@
{% trans 'Access Policies' %} {% trans 'Access Policies' %}
</a> </a>
</li> </li>
<li class="nav-item">
<a class="nav-link {% if active_tab == 'routes' %}active{% endif %}"
href="{% url 'app_gateway_list' %}?tab=routes" role="tab">
{% trans 'Routes' %}
</a>
</li>
</ul> </ul>
<div class="tab-content mt-4"> <div class="tab-content mt-4">
{% if active_tab == 'applications' or active_tab == 'hosts' %} {% if active_tab == 'applications' or active_tab == 'hosts' %}
<div class="mb-3"> <div class="d-flex justify-content-between align-items-center mb-3">
<div class="btn-group mb-3"> <div class="btn-group">
<a href="{% url 'app_gateway_list' %}?tab=applications" <a href="{% url 'app_gateway_list' %}?tab=applications"
class="btn {% if active_tab == 'applications' %}btn-primary{% else %}btn-outline-primary{% endif %}"> class="btn {% if active_tab == 'applications' %}btn-primary{% else %}btn-outline-primary{% endif %}">
{% trans 'Applications' %} {% trans 'Applications' %}
@@ -40,61 +34,46 @@
{% trans 'Hosts' %} {% trans 'Hosts' %}
</a> </a>
</div> </div>
{% if active_tab == 'applications' %}
<div>
<a href="{% url 'manage_application' %}" class="btn btn-outline-primary">
<i class="fas fa-plus"></i> {% trans 'Add Application' %}
</a>
</div>
{% endif %}
</div> </div>
{% if active_tab == 'applications' %} {% if active_tab == 'applications' %}
<div class="mb-3">
<a href="{% url 'manage_application' %}" class="btn btn-outline-primary btn-sm">
<i class="fas fa-plus"></i> {% trans 'Add Application' %}
</a>
<a href="{% url 'manage_application_policy' %}" class="btn btn-outline-secondary btn-sm">
<i class="fas fa-shield-alt"></i> {% trans 'Set Default Policy' %}
</a>
</div>
{% if applications %} {% if applications %}
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-striped table-hover"> <table class="table table-striped table-hover">
<thead> <thead>
<tr> <tr>
<th>{% trans 'Name' %}</th> <th>{% trans 'Name' %}</th>
<th>{% trans 'Display Name' %}</th>
<th>{% trans 'Upstream' %}</th> <th>{% trans 'Upstream' %}</th>
<th>{% trans 'Hosts' %}</th>
<th>{% trans 'Routes' %}</th>
<th>{% trans 'Default Policy' %}</th> <th>{% trans 'Default Policy' %}</th>
<th>{% trans 'Actions' %}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for app in applications %} {% for app in applications %}
<tr> <tr>
<td>{{ app.name }}</td> <td>
<td>{{ app.display_name }}</td> <a href="{% url 'view_application' %}?uuid={{ app.uuid }}">
<strong>{{ app }}</strong>
</a>
</td>
<td><code>{{ app.upstream }}</code></td> <td><code>{{ app.upstream }}</code></td>
<td>{{ app.hosts.count }}</td>
<td>{{ app.routes.count }}</td>
<td> <td>
{% if app.default_policy_config %} {% if app.default_policy_config %}
<span class="badge badge-info">{{ app.default_policy_config.default_policy.name }}</span> {{ app.default_policy_config.default_policy.name }}
<a href="{% url 'manage_application_policy' %}?uuid={{ app.default_policy_config.uuid }}"
class="btn btn-sm btn-outline-secondary btn-xs" title="{% trans 'Edit Default Policy' %}">
<i class="fas fa-edit"></i>
</a>
<a href="{% url 'delete_application_policy' %}?uuid={{ app.default_policy_config.uuid }}"
class="btn btn-sm btn-outline-danger btn-xs" title="{% trans 'Remove Default Policy' %}">
<i class="fas fa-times"></i>
</a>
{% else %} {% else %}
<span class="text-muted">{% trans 'Not set' %}</span> {% trans 'Default (Deny)' %}
{% endif %} {% endif %}
</td> </td>
<td style="width: 15%">
<a href="{% url 'manage_application' %}?uuid={{ app.uuid }}"
class="btn btn-sm btn-info" title="{% trans 'Edit' %}">
<i class="fas fa-edit"></i>
</a>
<a href="{% url 'delete_application' %}?uuid={{ app.uuid }}"
class="btn btn-sm btn-danger" title="{% trans 'Delete' %}">
<i class="fas fa-trash"></i>
</a>
</td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
@@ -107,11 +86,6 @@
{% endif %} {% endif %}
{% elif active_tab == 'hosts' %} {% elif active_tab == 'hosts' %}
<div class="mb-3">
<a href="{% url 'manage_application_host' %}" class="btn btn-outline-primary btn-sm">
<i class="fas fa-plus"></i> {% trans 'Add Host' %}
</a>
</div>
{% if hosts %} {% if hosts %}
<div class="table-responsive"> <div class="table-responsive">
@@ -120,22 +94,15 @@
<tr> <tr>
<th>{% trans 'Hostname' %}</th> <th>{% trans 'Hostname' %}</th>
<th>{% trans 'Application' %}</th> <th>{% trans 'Application' %}</th>
<th>{% trans 'Actions' %}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for host in hosts %} {% for host in hosts %}
<tr> <tr>
<td>{{ host.hostname }}</td> <td>{{ host.hostname }}</td>
<td>{{ host.application.display_name }}</td> <td>
<td style="width: 15%"> <a href="{% url 'view_application' %}?uuid={{ host.application.uuid }}">
<a href="{% url 'manage_application_host' %}?uuid={{ host.uuid }}" {{ host.application }}
class="btn btn-sm btn-info" title="{% trans 'Edit' %}">
<i class="fas fa-edit"></i>
</a>
<a href="{% url 'delete_application_host' %}?uuid={{ host.uuid }}"
class="btn btn-sm btn-danger" title="{% trans 'Delete' %}">
<i class="fas fa-trash"></i>
</a> </a>
</td> </td>
</tr> </tr>
@@ -197,56 +164,7 @@
</div> </div>
{% endif %} {% endif %}
{% elif active_tab == 'routes' %}
<div class="mb-3">
<a href="{% url 'manage_application_route' %}" class="btn btn-outline-primary btn-sm">
<i class="fas fa-plus"></i> {% trans 'Add Route' %}
</a>
</div>
{% if routes %}
<div class="table-responsive">
<table class="table table-striped table-hover">
<thead>
<tr>
<th>{% trans 'Application' %}</th>
<th>{% trans 'Route Name' %}</th>
<th>{% trans 'Path Prefix' %}</th>
<th>{% trans 'Policy' %}</th>
<th>{% trans 'Order' %}</th>
<th>{% trans 'Actions' %}</th>
</tr>
</thead>
<tbody>
{% for route in routes %}
<tr>
<td>{{ route.application.display_name }}</td>
<td>{{ route.name }}</td>
<td><code>{{ route.path_prefix }}</code></td>
<td>{{ route.policy.name }}</td>
<td>{{ route.order }}</td>
<td style="width: 15%">
<a href="{% url 'manage_application_route' %}?uuid={{ route.uuid }}"
class="btn btn-sm btn-info" title="{% trans 'Edit' %}">
<i class="fas fa-edit"></i>
</a>
<a href="{% url 'delete_application_route' %}?uuid={{ route.uuid }}"
class="btn btn-sm btn-danger" title="{% trans 'Delete' %}">
<i class="fas fa-trash"></i>
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<div class="alert alert-info">
{% trans 'No Routes found.' %}
</div>
{% endif %} {% endif %}
{% endif %}
</div> </div>
</div> </div>

View File

@@ -0,0 +1,142 @@
{% extends "base.html" %}
{% load i18n %}
{% block content %}
<div class="card card-primary card-outline">
<div class="card-header">
<h3 class="card-title">
<i class="fas fa-server"></i> {{ application }}
</h3>
<div class="card-tools">
<a href="{% url 'app_gateway_list' %}" class="btn btn-sm btn-secondary">
<i class="fas fa-arrow-left"></i> {% trans 'Back to List' %}
</a>
<a href="{% url 'manage_application' %}?uuid={{ application.uuid }}" class="btn btn-sm btn-info">
<i class="fas fa-edit"></i> {% trans 'Edit Application' %}
</a>
<a href="{% url 'delete_application' %}?uuid={{ application.uuid }}" class="btn btn-sm btn-danger">
<i class="fas fa-trash"></i> {% trans 'Delete Application' %}
</a>
</div>
</div>
<div class="card-body">
<div class="row mb-4">
<div class="col-md-6">
<strong>{% trans 'Name' %}:</strong> {{ application.name }}<br>
<strong>{% trans 'Display Name' %}:</strong> {{ application.display_name }}<br>
<strong>{% trans 'Upstream' %}:</strong> <code>{{ application.upstream }}</code><br>
</div>
<div class="col-md-6">
<strong>{% trans 'Default Policy' %}:</strong>
{% if application.default_policy_config %}
<span>{{ application.default_policy_config.default_policy.name }}</span>
<a href="{% url 'manage_application_policy' %}?uuid={{ application.default_policy_config.uuid }}"
class="btn btn-sm btn-outline-secondary btn-xs"><i class="fas fa-edit"></i></a>
<a href="{% url 'delete_application_policy' %}?uuid={{ application.default_policy_config.uuid }}"
class="btn btn-sm btn-outline-danger btn-xs"><i class="fas fa-times"></i></a>
{% else %}
<span class="text-muted">{% trans 'Default (Deny)' %}</span>
<a href="{% url 'manage_application_policy' %}?application_uuid={{ application.uuid }}"
class="btn btn-sm btn-outline-primary btn-xs"><i class="fas fa-plus"></i> {% trans 'Set Policy' %}</a>
{% endif %}
</div>
</div>
<hr>
<h4 id="hosts">{% trans 'Application Hosts' %}</h4>
<div class="mb-3">
<a href="{% url 'manage_application_host' %}?application_uuid={{ application.uuid }}"
class="btn btn-outline-primary btn-sm">
<i class="fas fa-plus"></i> {% trans 'Add Host' %}
</a>
</div>
{% if hosts %}
<div class="table-responsive">
<table class="table table-striped table-hover">
<thead>
<tr>
<th>{% trans 'Hostname' %}</th>
<th>{% trans 'Actions' %}</th>
</tr>
</thead>
<tbody>
{% for host in hosts %}
<tr>
<td>{{ host.hostname }}</td>
<td style="width: 15%">
<a href="{% url 'manage_application_host' %}?uuid={{ host.uuid }}"
class="btn btn-sm btn-info" title="{% trans 'Edit' %}">
<i class="fas fa-edit"></i>
</a>
<a href="{% url 'delete_application_host' %}?uuid={{ host.uuid }}"
class="btn btn-sm btn-danger" title="{% trans 'Delete' %}">
<i class="fas fa-trash"></i>
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<div class="alert alert-info">
{% trans 'No Hosts configured for this application.' %}
</div>
{% endif %}
<hr class="mt-4 mb-4">
<h4 id="routes">{% trans 'Application Routes' %}</h4>
<div class="mb-3">
<a href="{% url 'manage_application_route' %}?application_uuid={{ application.uuid }}"
class="btn btn-outline-primary btn-sm">
<i class="fas fa-plus"></i> {% trans 'Add Route' %}
</a>
</div>
{% if routes %}
<div class="table-responsive">
<table class="table table-striped table-hover">
<thead>
<tr>
<th>{% trans 'Route Name' %}</th>
<th>{% trans 'Path Prefix' %}</th>
<th>{% trans 'Policy' %}</th>
<th>{% trans 'Order' %}</th>
<th>{% trans 'Actions' %}</th>
</tr>
</thead>
<tbody>
{% for route in routes %}
<tr>
<td>{{ route.name }}</td>
<td><code>{{ route.path_prefix }}</code></td>
<td>{{ route.policy.name }}</td>
<td>{{ route.order }}</td>
<td style="width: 15%">
<a href="{% url 'manage_application_route' %}?uuid={{ route.uuid }}"
class="btn btn-sm btn-info" title="{% trans 'Edit' %}">
<i class="fas fa-edit"></i>
</a>
<a href="{% url 'delete_application_route' %}?uuid={{ route.uuid }}"
class="btn btn-sm btn-danger" title="{% trans 'Delete' %}">
<i class="fas fa-trash"></i>
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<div class="alert alert-info">
{% trans 'No Routes configured for this application.' %}
</div>
{% endif %}
</div>
</div>
{% endblock %}