mirror of
https://github.com/eduardogsilva/wireguard_webadmin.git
synced 2026-01-31 11:36:18 +00:00
convert wireguard manage server to crispy forms
This commit is contained in:
@@ -1,163 +1,68 @@
|
||||
{% extends "base.html" %}
|
||||
{% load i18n %}
|
||||
{% extends 'base.html' %}
|
||||
{% load crispy_forms_tags %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="card card-primary card-outline">
|
||||
|
||||
<div class="card-body">
|
||||
<div class="tab-content" id="custom-content-below-tabContent">
|
||||
<div class="tab-pane fade show active" id="custom-content-below-home" role="tabpanel" aria-labelledby="custom-content-below-home-tab">
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
<div class="card-body row">
|
||||
<div class="col-lg-6">
|
||||
<!-- Line 1: Name and peer_list_refresh_interval -->
|
||||
<div class="form-row">
|
||||
<div class="form-group col-md-6">
|
||||
<label for="{{ form.name.id_for_label }}">{{ form.name.label }}</label>
|
||||
<input type="text" class="form-control" id="{{ form.name.id_for_label }}" name="{{ form.name.html_name }}" placeholder="Enter Name" value="{{ form.name.value|default_if_none:'' }}">
|
||||
</div>
|
||||
|
||||
<div class="form-group col-md-6">
|
||||
<label for="{{ form.peer_list_refresh_interval.id_for_label }}">{{ form.peer_list_refresh_interval.label }}</label>
|
||||
<input type="number" class="form-control" id="{{ form.peer_list_refresh_interval.id_for_label }}" name="{{ form.peer_list_refresh_interval.html_name }}" placeholder="Persistent Keepalive" value="{% if force_cache_refresh > 0 %}{{ force_cache_refresh }}{% else %}{{ form.peer_list_refresh_interval.value|default_if_none:'' }}{% endif %}" required {% if force_cache_refresh > 0 %}readonly{% endif %}>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<!-- Line 2: Hostname, Listen Port and instance id -->
|
||||
<div class="form-row">
|
||||
<div class="form-group col-md-6">
|
||||
<label for="{{ form.hostname.id_for_label }}">{{ form.hostname.label }}</label>
|
||||
<input type="text" class="form-control" id="{{ form.hostname.id_for_label }}" name="{{ form.hostname.html_name }}" placeholder="Hostname" value="{{ form.hostname.value|default_if_none:'' }}" required>
|
||||
</div>
|
||||
<div class="form-group col-md-3">
|
||||
<label for="{{ form.listen_port.id_for_label }}">{{ form.listen_port.label }}</label>
|
||||
<input type="number" class="form-control" id="{{ form.listen_port.id_for_label }}" name="{{ form.listen_port.html_name }}" placeholder="Listen Port" value="{{ form.listen_port.value|default_if_none:'' }}" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group col-md-3">
|
||||
<label for="{{ form.instance_id.id_for_label }}">{{ form.instance_id.label }}</label>
|
||||
<input type="number" class="form-control" id="{{ form.instance_id.id_for_label }}" name="{{ form.instance_id.html_name }}" placeholder="Instance ID" value="{{ form.instance_id.value|default_if_none:'' }}" required>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<!-- Line 3: Private Key and Persistent Keepalive -->
|
||||
<div class="form-row">
|
||||
{% comment %}
|
||||
<div class="form-group col-md-6">
|
||||
<label for="{{ form.private_key.id_for_label }}">{{ form.private_key.label }}</label>
|
||||
<input type="text" class="form-control" id="{{ form.private_key.id_for_label }}" name="{{ form.private_key.html_name }}" placeholder="Private Key" value="{{ form.private_key.value|default_if_none:'' }}" required>
|
||||
</div>
|
||||
{% endcomment %}
|
||||
<div class="form-group col-md-6">
|
||||
<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="Private Key" value="{{ form.private_key.value|default_if_none:'' }}" required>
|
||||
<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 class="form-group col-md-6">
|
||||
<label for="{{ form.public_key.id_for_label }}">{{ form.public_key.label }}</label>
|
||||
<input type="text" class="form-control" id="{{ form.public_key.id_for_label }}" name="{{ form.public_key.html_name }}" placeholder="public Key" value="{{ form.public_key.value|default_if_none:'' }}" required>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<!-- Line 4: Address and Netmask -->
|
||||
<div class="form-row">
|
||||
<div class="form-group col-md-6">
|
||||
<label for="{{ form.address.id_for_label }}">{{ form.address.label }}</label>
|
||||
<input type="text" class="form-control" id="{{ form.address.id_for_label }}" name="{{ form.address.html_name }}" placeholder="Address" value="{{ form.address.value|default_if_none:'' }}" required>
|
||||
</div>
|
||||
<div class="form-group col-md-6">
|
||||
<label for="{{ form.netmask.id_for_label }}">{{ form.netmask.label }}</label>
|
||||
<select class="form-control" id="{{ form.netmask.id_for_label }}" name="{{ form.netmask.html_name }}">
|
||||
{% for value, display in form.netmask.field.choices %}
|
||||
<option value="{{ value }}" {% if form.netmask.value == value %}selected{% endif %}>{{ display }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Line 5: Primary and secondary DNS -->
|
||||
<div class="form-row">
|
||||
<div class="form-group col-md-6">
|
||||
<label for="{{ form.dns_primary.id_for_label }}">{{ form.dns_primary.label }}</label>
|
||||
<input type="text" class="form-control" id="{{ form.dns_primary.id_for_label }}" name="{{ form.dns_primary.html_name }}" value="{{ form.dns_primary.value|default_if_none:'' }}">
|
||||
</div>
|
||||
<div class="form-group col-md-6">
|
||||
<label for="{{ form.dns_secondary.id_for_label }}">{{ form.dns_secondary.label }}</label>
|
||||
<input type="text" class="form-control" id="{{ form.dns_secondary.id_for_label }}" name="{{ form.dns_secondary.html_name }}" value="{{ form.dns_secondary.value|default_if_none:'' }}">
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
<div class="col-lg-6">
|
||||
<!-- Line 1: Post Up -->
|
||||
<div class="form-group">
|
||||
<label for="{{ form.post_up.id_for_label }}">{{ form.post_up.label }}</label>
|
||||
<textarea class="form-control" id="{{ form.post_up.id_for_label }}" name="{{ form.post_up.html_name }}" placeholder="Post Up" style="height: 150px;" readonly>{{ form.post_up.value|default_if_none:'' }}</textarea>
|
||||
</div>
|
||||
<!-- Line 2: Post Down -->
|
||||
<div class="form-group">
|
||||
<label for="{{ form.post_down.id_for_label }}">{{ form.post_down.label }}</label>
|
||||
<textarea class="form-control" id="{{ form.post_down.id_for_label }}" name="{{ form.post_down.html_name }}" placeholder="Post Down" style="height: 150px;" readonly>{{ form.post_down.value|default_if_none:'' }}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="{% if form_size %}{{ form_size }}{% else %}col-lg-12{% endif %}">
|
||||
<div class="card card-primary card-outline">
|
||||
{% if page_title %}
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">{{ page_title }}</h3>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<button type="submit" class="btn btn-primary">{% trans 'Save' %}</button>
|
||||
{% if current_instance.uuid %}
|
||||
<a href="{% url 'wireguard_server_list' %}" class='btn btn-outline-secondary' >{% trans 'Back' %}</a>
|
||||
<a href='javascript:void(0)' class='btn btn-outline-danger' data-command='delete' onclick='openCommandDialog(this)'>{% trans 'Delete Configuration' %}</a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
<div class="card-body row">
|
||||
<div class="col-lg-12">
|
||||
{% crispy form %}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block custom_page_scripts %}
|
||||
<script>
|
||||
function openCommandDialog(element) {
|
||||
var command = element.getAttribute('data-command');
|
||||
var confirmation = prompt("Please type 'delete wg{{ current_instance.instance_id }}' to remove the configuration.");
|
||||
if (confirmation) {
|
||||
var url = "?uuid={{current_instance.uuid}}&action=delete&confirmation=" + encodeURIComponent(confirmation);
|
||||
window.location.href = url;
|
||||
<script>
|
||||
function openCommandDialog(element) {
|
||||
var command = element.getAttribute('data-command');
|
||||
var confirmation = prompt("{{ delete_confirmation_message }}");
|
||||
if (confirmation) {
|
||||
var url = "?uuid={{ instance.uuid }}&action=delete&confirmation=" + encodeURIComponent(confirmation);
|
||||
window.location.href = url;
|
||||
}
|
||||
}
|
||||
}
|
||||
</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>';
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
let passwordInput = document.getElementById('id_private_key');
|
||||
|
||||
if (passwordInput) {
|
||||
let groupDiv = passwordInput.parentNode;
|
||||
if (!groupDiv.classList.contains('input-group')) {
|
||||
let newGroup = document.createElement('div');
|
||||
newGroup.className = 'input-group';
|
||||
|
||||
passwordInput.parentNode.insertBefore(newGroup, passwordInput);
|
||||
newGroup.appendChild(passwordInput);
|
||||
|
||||
let appendDiv = document.createElement('div');
|
||||
appendDiv.className = 'input-group-append';
|
||||
|
||||
let btn = document.createElement('button');
|
||||
btn.className = 'btn btn-outline-secondary toggle-password-btn';
|
||||
btn.type = 'button';
|
||||
btn.innerHTML = '<i class="fas fa-eye"></i>';
|
||||
|
||||
appendDiv.appendChild(btn);
|
||||
newGroup.appendChild(appendDiv);
|
||||
|
||||
btn.addEventListener('click', function () {
|
||||
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>
|
||||
{% endblock %}
|
||||
@@ -1,6 +1,10 @@
|
||||
import ipaddress
|
||||
|
||||
from crispy_forms.helper import FormHelper
|
||||
from crispy_forms.layout import Column, HTML, Layout, Row, Submit
|
||||
from django import forms
|
||||
from django.conf import settings
|
||||
from django.urls import reverse_lazy
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from wgwadmlibrary.tools import is_valid_ip_or_hostname
|
||||
@@ -16,8 +20,6 @@ class WireGuardInstanceForm(forms.ModelForm):
|
||||
listen_port = forms.IntegerField(label=_('Listen Port'))
|
||||
address = forms.GenericIPAddressField(label=_('Internal IP Address'))
|
||||
netmask = forms.ChoiceField(choices=NETMASK_CHOICES, label=_('Netmask'))
|
||||
post_up = forms.CharField(label=_('Post Up'), required=False)
|
||||
post_down = forms.CharField(label=_('Post Down'), required=False)
|
||||
peer_list_refresh_interval = forms.IntegerField(label=_('Web Refresh Interval'), initial=10)
|
||||
dns_primary = forms.GenericIPAddressField(label=_('Primary DNS'), initial='1.1.1.1', required=False)
|
||||
dns_secondary = forms.GenericIPAddressField(label=_('Secondary DNS'), initial='', required=False)
|
||||
@@ -26,40 +28,101 @@ class WireGuardInstanceForm(forms.ModelForm):
|
||||
model = WireGuardInstance
|
||||
fields = [
|
||||
'name', 'instance_id', 'private_key', 'public_key','hostname', 'listen_port', 'address',
|
||||
'netmask', 'post_up', 'post_down', 'peer_list_refresh_interval', 'dns_primary', 'dns_secondary'
|
||||
'netmask', 'peer_list_refresh_interval', 'dns_primary', 'dns_secondary'
|
||||
]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
force_cache_refresh = 0
|
||||
if hasattr(settings, 'WIREGUARD_STATUS_CACHE_ENABLED') and settings.WIREGUARD_STATUS_CACHE_ENABLED:
|
||||
if hasattr(settings, 'WIREGUARD_STATUS_CACHE_REFRESH_INTERVAL'):
|
||||
force_cache_refresh = settings.WIREGUARD_STATUS_CACHE_REFRESH_INTERVAL
|
||||
|
||||
if force_cache_refresh > 0:
|
||||
self.fields['peer_list_refresh_interval'].initial = force_cache_refresh
|
||||
self.fields['peer_list_refresh_interval'].widget.attrs['readonly'] = True
|
||||
self.fields['private_key'].widget = forms.PasswordInput(attrs={'class': 'form-control'}, render_value=True)
|
||||
|
||||
self.helper = FormHelper()
|
||||
self.helper.form_method = 'post'
|
||||
|
||||
back_label = _("Back")
|
||||
delete_label = _("Delete")
|
||||
|
||||
if self.instance.pk and not self.instance._state.adding:
|
||||
delete_html = f"<a href='javascript:void(0)' class='btn btn-outline-danger' data-command='delete' onclick='openCommandDialog(this)'>{delete_label}</a>"
|
||||
else:
|
||||
delete_html = ''
|
||||
|
||||
|
||||
self.helper.layout = Layout(
|
||||
|
||||
Row(
|
||||
Column(
|
||||
Row(
|
||||
Column('name', css_class='form-group col-md-6 mb-0'),
|
||||
Column('peer_list_refresh_interval', css_class='form-group col-md-6 mb-0'),
|
||||
css_class='form-row'
|
||||
),
|
||||
Row(
|
||||
Column('hostname', css_class='form-group col-md-6 mb-0'),
|
||||
Column('listen_port', css_class='form-group col-md-3 mb-0'),
|
||||
Column('instance_id', css_class='form-group col-md-3 mb-0'),
|
||||
css_class='form-row'
|
||||
),
|
||||
Row(
|
||||
Column('private_key', css_class='form-group col-md-6 mb-0'),
|
||||
Column('public_key', css_class='form-group col-md-6 mb-0'),
|
||||
css_class='form-row'
|
||||
),
|
||||
Row(
|
||||
Column('address', css_class='form-group col-md-6 mb-0'),
|
||||
Column('netmask', css_class='form-group col-md-6 mb-0'),
|
||||
css_class='form-row'
|
||||
),
|
||||
Row(
|
||||
Column('dns_primary', css_class='form-group col-md-6 mb-0'),
|
||||
Column('dns_secondary', css_class='form-group col-md-6 mb-0'),
|
||||
css_class='form-row'
|
||||
),
|
||||
css_class='col-lg-12'
|
||||
),
|
||||
css_class='row'
|
||||
),
|
||||
Row(
|
||||
Column(
|
||||
Submit('submit', _('Save'), css_class='btn btn-success'),
|
||||
HTML(f' <a class="btn btn-secondary" href="{reverse_lazy('wireguard_server_list')}">{back_label}</a> '),
|
||||
HTML(delete_html),
|
||||
css_class='col-12'
|
||||
),
|
||||
css_class='row'
|
||||
),
|
||||
)
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super().clean()
|
||||
hostname = cleaned_data.get('hostname')
|
||||
address = cleaned_data.get('address')
|
||||
netmask = cleaned_data.get('netmask')
|
||||
post_up = cleaned_data.get('post_up')
|
||||
post_down = cleaned_data.get('post_down')
|
||||
|
||||
peer_list_refresh_interval = cleaned_data.get('peer_list_refresh_interval')
|
||||
if peer_list_refresh_interval < 5:
|
||||
if peer_list_refresh_interval and peer_list_refresh_interval < 5:
|
||||
raise forms.ValidationError(_('Peer List Refresh Interval must be at least 5 seconds'))
|
||||
|
||||
if not is_valid_ip_or_hostname(hostname):
|
||||
if hostname and not is_valid_ip_or_hostname(hostname):
|
||||
raise forms.ValidationError(_('Invalid hostname or IP Address'))
|
||||
|
||||
current_network = ipaddress.ip_network(f"{address}/{netmask}", strict=False)
|
||||
all_other_instances = WireGuardInstance.objects.all()
|
||||
if self.instance:
|
||||
all_other_instances = all_other_instances.exclude(uuid=self.instance.uuid)
|
||||
for instance in all_other_instances:
|
||||
other_network = ipaddress.ip_network(f"{instance.address}/{instance.netmask}", strict=False)
|
||||
if current_network.overlaps(other_network):
|
||||
raise forms.ValidationError(_('The selected network range overlaps with another instance.'))
|
||||
|
||||
#if self.instance:
|
||||
# if post_up or post_down:
|
||||
# if self.instance.post_up != post_up or self.instance.post_down != post_down:
|
||||
# raise forms.ValidationError('Post Up and Post Down cannot be changed, please go to Firewall page to make changes to the firewall.')
|
||||
#else:
|
||||
# if post_up or post_down:
|
||||
# raise forms.ValidationError('Post Up and Post Down cannot be set, please go to Firewall page to make changes to the firewall.')
|
||||
if address and netmask:
|
||||
current_network = ipaddress.ip_network(f"{address}/{netmask}", strict=False)
|
||||
all_other_instances = WireGuardInstance.objects.all()
|
||||
if self.instance and self.instance.pk:
|
||||
all_other_instances = all_other_instances.exclude(uuid=self.instance.uuid)
|
||||
for instance in all_other_instances:
|
||||
other_network = ipaddress.ip_network(f"{instance.address}/{instance.netmask}", strict=False)
|
||||
if current_network.overlaps(other_network):
|
||||
raise forms.ValidationError(_('The selected network range overlaps with another instance.'))
|
||||
|
||||
return cleaned_data
|
||||
|
||||
|
||||
@@ -139,7 +139,7 @@ def view_wireguard_manage_instance(request):
|
||||
return redirect('/server/manage/?uuid=' + str(current_instance.uuid))
|
||||
current_instance.delete()
|
||||
messages.success(request, message_title + _('|WireGuard instance deleted: wg') + str(current_instance.instance_id))
|
||||
return redirect('/server/manage/')
|
||||
return redirect('/server/list/')
|
||||
else:
|
||||
messages.warning(request, _('Invalid confirmation|Please confirm deletion of WireGuard instance: wg') + str(current_instance.instance_id))
|
||||
return redirect('/server/manage/?uuid=' + str(current_instance.uuid))
|
||||
@@ -165,7 +165,17 @@ def view_wireguard_manage_instance(request):
|
||||
force_cache_refresh = settings.WIREGUARD_STATUS_CACHE_REFRESH_INTERVAL
|
||||
else:
|
||||
force_cache_refresh = 0
|
||||
context = {'page_title': page_title, 'wireguard_instances': wireguard_instances, 'current_instance': current_instance, 'form': form, 'force_cache_refresh': force_cache_refresh}
|
||||
|
||||
# Passing 'instance' and 'delete_confirmation_message' for generic_form compatibility
|
||||
context = {
|
||||
'page_title': page_title,
|
||||
'wireguard_instances': wireguard_instances,
|
||||
'current_instance': current_instance,
|
||||
'instance': current_instance,
|
||||
'form': form,
|
||||
'force_cache_refresh': force_cache_refresh,
|
||||
'delete_confirmation_message': 'Please type delete wg' + str(current_instance.instance_id) + ' to remove the configuration.' if current_instance else None
|
||||
}
|
||||
return render(request, 'wireguard/wireguard_manage_server.html', context)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user