Update peer management view and forms.

This commit is contained in:
Eduardo Silva
2026-01-26 13:57:31 -03:00
parent 666ea0402d
commit 5b21f24b7c
4 changed files with 152 additions and 126 deletions

View File

@@ -7,43 +7,65 @@
<div class="card-header"> <div class="card-header">
<h3 class="card-title">{% trans 'Peer Configuration' %}</h3> <h3 class="card-title">{% trans 'Peer Configuration' %}</h3>
</div> </div>
<form method="post"> <div class="card-body">
{% csrf_token %} <div class="row">
<div class="card-body row">
<div class="col-lg-6"> <div class="col-lg-6">
<!-- Name --> <!-- Name -->
<div class="form-group"> <div class="form-group border-bottom pb-3">
<label for="{{ form.name.id_for_label }}">{{ form.name.label }}</label> <label>{% trans 'Name' %}</label>
<input type="text" class="form-control" id="{{ form.name.id_for_label }}" name="{{ form.name.html_name }}" placeholder="{% trans 'Enter Name' %}" value="{{ form.name.value|default_if_none:'' }}"> <div class="d-flex justify-content-between align-items-center">
<span>{{ current_peer.name|default:"-" }}</span>
<a href="{% url 'wireguard_peer_edit_field' %}?peer={{ current_peer.uuid }}&group=name"
class="btn btn-tool">
<i class="fas fa-pen"></i>
</a>
</div>
</div> </div>
<!-- Persistent Keepalive --> <!-- Persistent Keepalive -->
<div class="form-group"> <div class="form-group border-bottom pb-3">
<label for="{{ form.persistent_keepalive.id_for_label }}">{{ form.persistent_keepalive.label }}</label> <label>{% trans 'Persistent Keepalive' %}</label>
<input type="number" class="form-control" id="{{ form.persistent_keepalive.id_for_label }}" name="{{ form.persistent_keepalive.html_name }}" placeholder="{% trans 'Persistent Keepalive' %}" value="{{ form.persistent_keepalive.value|default_if_none:'' }}" required> <div class="d-flex justify-content-between align-items-center">
</div> <span>{{ current_peer.persistent_keepalive }}</span>
<a href="{% url 'wireguard_peer_edit_field' %}?peer={{ current_peer.uuid }}&group=keepalive"
<!-- Public Key --> class="btn btn-tool">
<div class="form-group"> <i class="fas fa-pen"></i>
<label for="{{ form.public_key.id_for_label }}">{{ form.public_key.label }}</label> </a>
<input type="text" class="form-control" id="{{ form.public_key.id_for_label }}" name="{{ form.public_key.html_name }}" placeholder="{% trans 'Public Key' %}" value="{{ form.public_key.value|default_if_none:'' }}" required>
</div>
<!-- Private Key -->
<div class="form-group">
<label for="{{ form.private_key.id_for_label }}">{{ form.private_key.label }}</label>
<div class="input-group">
<input type="password" class="form-control" id="{{ form.private_key.id_for_label }}" name="{{ form.private_key.html_name }}" placeholder="{% trans 'Private Key' %}" value="{{ form.private_key.value|default_if_none:'' }}">
<div class="input-group-append">
<button class="btn btn-outline-secondary toggle-password" type="button"><i class="fas fa-eye"></i></button>
</div>
</div> </div>
</div> </div>
<!-- Pre-Shared Key --> <!-- Keys Section -->
<div class="form-group"> <div class="form-group">
<label for="{{ form.pre_shared_key.id_for_label }}">{{ form.pre_shared_key.label }}</label> <div class="d-flex justify-content-between align-items-center mb-2">
<input type="text" class="form-control" id="{{ form.pre_shared_key.id_for_label }}" name="{{ form.pre_shared_key.html_name }}" placeholder="{% trans 'Pre-Shared Key' %}" value="{{ form.pre_shared_key.value|default_if_none:'' }}" required> <label class="mb-0">{% trans 'Keys' %}</label>
<a href="{% url 'wireguard_peer_edit_field' %}?peer={{ current_peer.uuid }}&group=keys"
class="btn btn-tool">
<i class="fas fa-pen"></i>
</a>
</div>
<dl>
<dt class="small text-muted font-weight-normal">{% trans 'Public Key' %}</dt>
<dd class="text-break">{{ current_peer.public_key }}</dd>
<dt class="small text-muted font-weight-normal">{% trans 'Private Key' %}</dt>
<dd>
{% if current_peer.private_key %}
<code>********************************************</code>
{% else %}
<span class="text-muted">{% trans 'Not set' %}</span>
{% endif %}
</dd>
<dt class="small text-muted font-weight-normal">{% trans 'Pre-Shared Key' %}</dt>
<dd>
{% if current_peer.pre_shared_key %}
<code>********************************************</code>
{% else %}
<span class="text-muted">{% trans 'Not set' %}</span>
{% endif %}
</dd>
</dl>
</div> </div>
</div> </div>
@@ -195,47 +217,18 @@
</div>
</div> </div>
</div> </div>
<div class="card-footer"> <div class="card-footer">
<button type="submit" class="btn btn-primary">{% trans 'Save' %}</button>
<a href="/peer/manage_ip_address/?peer={{ current_peer.uuid }}" class="btn btn-outline-primary">{% trans 'Add IP Address' %}</a>
<a class="btn btn-outline-secondary" href="/peer/list/?uuid={{ current_peer.wireguard_instance.uuid }}#peer-{{ current_peer.public_key }}">{% trans 'Back' %}</a> <a class="btn btn-outline-secondary" href="/peer/list/?uuid={{ current_peer.wireguard_instance.uuid }}#peer-{{ current_peer.public_key }}">{% trans 'Back' %}</a>
<a href='javascript:void(0)' class='btn btn-outline-danger' data-command='delete' onclick='openCommandDialog(this)'>{% trans 'Delete Peer' %}</a> <a href='javascript:void(0)' class='btn btn-outline-danger' data-command='delete' onclick='openCommandDialog(this)'>{% trans 'Delete Peer' %}</a>
</div> </div>
</form>
</div> </div>
{% endblock %} {% endblock %}
{% block custom_page_scripts %} {% block custom_page_scripts %}
<script>
document.addEventListener('DOMContentLoaded', function () {
var alertShown = false;
var fieldsToWatch = ['id_public_key', 'id_pre_shared_key', 'id_private_key'];
function showAlert() {
if (!alertShown) {
$(document).Toasts('create', {
class: 'bg-warning',
title: '{% trans 'Action Required!' %}',
body: '{% trans 'When manually updating the "Public Key", "Pre-Shared Key", or "Private Key", please ensure the configuration is correct.' %}',
});
alertShown = true;
}
}
fieldsToWatch.forEach(function(fieldId) {
var field = document.getElementById(fieldId);
if (field) {
field.addEventListener('change', showAlert);
}
});
});
</script>
<script> <script>
function openCommandDialog(element) { function openCommandDialog(element) {
var command = element.getAttribute('data-command'); var command = element.getAttribute('data-command');
@@ -247,22 +240,6 @@
} }
</script> </script>
<script>
document.addEventListener('DOMContentLoaded', function () {
document.querySelector('.toggle-password').addEventListener('click', function () {
let passwordInput = document.getElementById('{{ form.private_key.id_for_label }}');
let passStatus = passwordInput.getAttribute('type') === 'password';
passwordInput.setAttribute('type', passStatus ? 'text' : 'password');
this.innerHTML = passStatus ? '<i class="fas fa-eye-slash"></i>' : '<i class="fas fa-eye"></i>';
});
document.getElementById('{{ form.private_key.id_for_label }}').addEventListener('keypress', function () {
this.setAttribute('type', 'text');
});
});
</script>
<script> <script>
document.addEventListener('DOMContentLoaded', function(){ document.addEventListener('DOMContentLoaded', function(){
var buttons = document.querySelectorAll('.btn-group a'); var buttons = document.querySelectorAll('.btn-group a');
@@ -280,23 +257,4 @@ document.addEventListener('DOMContentLoaded', function(){
}); });
</script> </script>
<script>
document.addEventListener('DOMContentLoaded', function() {
var form = document.querySelector('form[method="post"]');
if (form) {
form.addEventListener('submit', function(e) {
var privateKeyField = document.getElementById('{{ form.private_key.id_for_label }}');
if (privateKeyField && privateKeyField.value.trim() === '') {
var confirmed = confirm('{% trans "The private key is empty. The peers configuration file and QR code will be generated without the private key.\n It must be inserted manually when importing.\n\n Do you want to continue?" %}');
if (!confirmed) {
e.preventDefault();
return false;
}
}
});
}
});
</script>
{% endblock %} {% endblock %}

View File

@@ -1,21 +1,56 @@
import ipaddress import ipaddress
from crispy_forms.bootstrap import FormActions
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout, Submit, Button
from django import forms from django import forms
from django.core.validators import MinValueValidator, MaxValueValidator
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from wireguard.models import NETMASK_CHOICES, Peer, PeerAllowedIP from wireguard.models import NETMASK_CHOICES, Peer, PeerAllowedIP
class PeerForm(forms.ModelForm): class PeerModelForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_method = 'post'
self.helper.layout = Layout(
*self.Meta.fields,
FormActions(
Submit('save', _('Save'), css_class='btn-primary'),
Button('cancel', _('Back'), css_class='btn-outline-secondary', onclick='window.history.back()')
)
)
class PeerNameForm(PeerModelForm):
name = forms.CharField(label=_('Name'), required=False) name = forms.CharField(label=_('Name'), required=False)
public_key = forms.CharField(label=_('Public Key'), required=True)
private_key = forms.CharField(label=_('Private Key'), required=False)
pre_shared_key = forms.CharField(label=_('Pre-Shared Key'), required=True)
persistent_keepalive = forms.IntegerField(label=_('Persistent Keepalive'), required=True)
class Meta: class Meta:
model = Peer model = Peer
fields = ['name', 'public_key', 'private_key', 'pre_shared_key', 'persistent_keepalive'] fields = ['name']
class PeerKeepaliveForm(PeerModelForm):
persistent_keepalive = forms.IntegerField(
label=_('Persistent Keepalive'),
required=True,
validators=[MinValueValidator(1), MaxValueValidator(3600)],
)
class Meta:
model = Peer
fields = ['persistent_keepalive']
class PeerKeysForm(PeerModelForm):
public_key = forms.CharField(label=_('Public Key'), required=True)
private_key = forms.CharField(label=_('Private Key'), required=False)
pre_shared_key = forms.CharField(label=_('Pre-Shared Key'), required=True)
class Meta:
model = Peer
fields = ['public_key', 'private_key', 'pre_shared_key']
class PeerAllowedIPForm(forms.ModelForm): class PeerAllowedIPForm(forms.ModelForm):

View File

@@ -14,7 +14,7 @@ from user_manager.models import UserAcl
from wgwadmlibrary.tools import check_sort_order_conflict, deduplicate_sort_order, default_sort_peers, \ from wgwadmlibrary.tools import check_sort_order_conflict, deduplicate_sort_order, default_sort_peers, \
user_allowed_instances, user_allowed_peers, user_has_access_to_instance, user_has_access_to_peer user_allowed_instances, user_allowed_peers, user_has_access_to_instance, user_has_access_to_peer
from wireguard.models import Peer, PeerAllowedIP, WireGuardInstance from wireguard.models import Peer, PeerAllowedIP, WireGuardInstance
from wireguard_peer.forms import PeerAllowedIPForm, PeerForm from wireguard_peer.forms import PeerAllowedIPForm, PeerNameForm, PeerKeepaliveForm, PeerKeysForm
def generate_peer_default(wireguard_instance): def generate_peer_default(wireguard_instance):
@@ -184,10 +184,6 @@ def view_wireguard_peer_create(request):
@login_required @login_required
def view_wireguard_peer_manage(request): def view_wireguard_peer_manage(request):
if request.method == 'POST':
if not UserAcl.objects.filter(user=request.user).filter(user_level__gte=30).exists():
return render(request, 'access_denied.html', {'page_title': 'Access Denied'})
else:
if not UserAcl.objects.filter(user=request.user).filter(user_level__gte=20).exists(): if not UserAcl.objects.filter(user=request.user).filter(user_level__gte=20).exists():
return render(request, 'access_denied.html', {'page_title': 'Access Denied'}) return render(request, 'access_denied.html', {'page_title': 'Access Denied'})
user_acl = get_object_or_404(UserAcl, user=request.user) user_acl = get_object_or_404(UserAcl, user=request.user)
@@ -197,6 +193,8 @@ def view_wireguard_peer_manage(request):
raise Http404 raise Http404
current_instance = current_peer.wireguard_instance current_instance = current_peer.wireguard_instance
if request.GET.get('action') == 'delete': if request.GET.get('action') == 'delete':
if not UserAcl.objects.filter(user=request.user).filter(user_level__gte=30).exists():
return render(request, 'access_denied.html', {'page_title': 'Access Denied'})
if request.GET.get('confirmation') == 'delete': if request.GET.get('confirmation') == 'delete':
current_peer.wireguard_instance.pending_changes = True current_peer.wireguard_instance.pending_changes = True
current_peer.wireguard_instance.save() current_peer.wireguard_instance.save()
@@ -206,28 +204,62 @@ def view_wireguard_peer_manage(request):
else: else:
messages.warning(request, _('Error deleting peer|Invalid confirmation message. Type "delete" to confirm.')) messages.warning(request, _('Error deleting peer|Invalid confirmation message. Type "delete" to confirm.'))
return redirect('/peer/manage/?peer=' + str(current_peer.uuid)) return redirect('/peer/manage/?peer=' + str(current_peer.uuid))
page_title = _('Update Peer: ') + str(current_peer) page_title = _('Peer Configuration: ') + str(current_peer)
peer_ip_list = current_peer.peerallowedip_set.filter(config_file='server').order_by('priority') peer_ip_list = current_peer.peerallowedip_set.filter(config_file='server').order_by('priority')
peer_client_ip_list = current_peer.peerallowedip_set.filter(config_file='client').order_by('priority') peer_client_ip_list = current_peer.peerallowedip_set.filter(config_file='client').order_by('priority')
if request.method == 'POST':
form = PeerForm(request.POST, instance=current_peer)
if form.is_valid():
form.save()
messages.success(request, _('Peer updated|Peer updated successfully.'))
current_peer.wireguard_instance.pending_changes = True
current_peer.wireguard_instance.save()
return redirect('/peer/list/?uuid=' + str(current_peer.wireguard_instance.uuid))
else:
form = PeerForm(instance=current_peer)
context = { context = {
'page_title': page_title, 'current_instance': current_instance, 'current_peer': current_peer, 'form': form, 'page_title': page_title, 'current_instance': current_instance, 'current_peer': current_peer,
'peer_ip_list': peer_ip_list, 'peer_client_ip_list': peer_client_ip_list 'peer_ip_list': peer_ip_list, 'peer_client_ip_list': peer_client_ip_list
} }
return render(request, 'wireguard/wireguard_manage_peer.html', context) return render(request, 'wireguard/wireguard_manage_peer.html', context)
@login_required
def view_wireguard_peer_edit_field(request):
if not UserAcl.objects.filter(user=request.user).filter(user_level__gte=30).exists():
return render(request, 'access_denied.html', {'page_title': 'Access Denied'})
user_acl = get_object_or_404(UserAcl, user=request.user)
current_peer = get_object_or_404(Peer, uuid=request.GET.get('peer'))
if not user_has_access_to_peer(user_acl, current_peer):
raise Http404
group = request.GET.get('group')
form_classes = {
'name': PeerNameForm,
'keepalive': PeerKeepaliveForm,
'keys': PeerKeysForm
}
if group not in form_classes:
raise Http404
FormClass = form_classes[group]
form = FormClass(request.POST or None, instance=current_peer)
if form.is_valid():
form.save()
current_peer.wireguard_instance.pending_changes = True
current_peer.wireguard_instance.save()
messages.success(request, _('Peer updated|Peer updated successfully.'))
return redirect('/peer/manage/?peer=' + str(current_peer.uuid))
page_title = _('Edit Peer')
if group == 'name':
page_title = _('Edit Peer Name')
elif group == 'keepalive':
page_title = _('Edit Keepalive')
elif group == 'keys':
page_title = _('Edit Keys')
context = {
'page_title': page_title,
'form': form,
}
return render(request, 'generic_form.html', context)
def view_manage_ip_address(request): def view_manage_ip_address(request):
if not UserAcl.objects.filter(user=request.user).filter(user_level__gte=30).exists(): if not UserAcl.objects.filter(user=request.user).filter(user_level__gte=30).exists():
return render(request, 'access_denied.html', {'page_title': 'Access Denied'}) return render(request, 'access_denied.html', {'page_title': 'Access Denied'})

View File

@@ -39,7 +39,7 @@ from wgrrd.views import view_rrd_graph
from wireguard.views import view_apply_db_patches, view_wireguard_manage_instance, view_wireguard_status, \ from wireguard.views import view_apply_db_patches, view_wireguard_manage_instance, view_wireguard_status, \
view_server_list, view_server_detail view_server_list, view_server_detail
from wireguard_peer.views import view_manage_ip_address, view_wireguard_peer_list, view_wireguard_peer_manage, \ from wireguard_peer.views import view_manage_ip_address, view_wireguard_peer_list, view_wireguard_peer_manage, \
view_wireguard_peer_sort, view_apply_route_template, view_wireguard_peer_create view_wireguard_peer_sort, view_apply_route_template, view_wireguard_peer_create, view_wireguard_peer_edit_field
from wireguard_tools.views import download_config_or_qrcode, export_wireguard_configs, restart_wireguard_interfaces from wireguard_tools.views import download_config_or_qrcode, export_wireguard_configs, restart_wireguard_interfaces
urlpatterns = [ urlpatterns = [
@@ -57,6 +57,7 @@ urlpatterns = [
path('peer/sort/', view_wireguard_peer_sort, name='wireguard_peer_sort'), path('peer/sort/', view_wireguard_peer_sort, name='wireguard_peer_sort'),
path('peer/manage/', view_wireguard_peer_manage, name='wireguard_peer_manage'), path('peer/manage/', view_wireguard_peer_manage, name='wireguard_peer_manage'),
path('peer/create/', view_wireguard_peer_create, name='wireguard_peer_create'), path('peer/create/', view_wireguard_peer_create, name='wireguard_peer_create'),
path('peer/edit/', view_wireguard_peer_edit_field, name='wireguard_peer_edit_field'),
path('peer/apply_route_template/', view_apply_route_template, name='apply_route_template'), path('peer/apply_route_template/', view_apply_route_template, name='apply_route_template'),
path('peer/manage_ip_address/', view_manage_ip_address, name='manage_ip_address'), path('peer/manage_ip_address/', view_manage_ip_address, name='manage_ip_address'),
path('rrd/graph/', view_rrd_graph, name='rrd_graph'), path('rrd/graph/', view_rrd_graph, name='rrd_graph'),