mirror of
https://github.com/eduardogsilva/wireguard_webadmin.git
synced 2026-02-19 19:26:17 +00:00
Add scheduling functionality with profile and slot management
This commit is contained in:
67
scheduler/forms.py
Normal file
67
scheduler/forms.py
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
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 scheduler.models import ScheduleProfile, ScheduleSlot
|
||||||
|
|
||||||
|
|
||||||
|
class ScheduleProfileForm(forms.ModelForm):
|
||||||
|
class Meta:
|
||||||
|
model = ScheduleProfile
|
||||||
|
fields = ['name']
|
||||||
|
labels = {
|
||||||
|
'name': _('Profile Name'),
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.helper = FormHelper()
|
||||||
|
self.helper.form_tag = False
|
||||||
|
self.helper.layout = Layout(
|
||||||
|
Div(
|
||||||
|
'name',
|
||||||
|
css_class='col-12'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ScheduleSlotForm(forms.ModelForm):
|
||||||
|
class Meta:
|
||||||
|
model = ScheduleSlot
|
||||||
|
fields = ['start_weekday', 'start_time', 'end_weekday', 'end_time']
|
||||||
|
widgets = {
|
||||||
|
'start_time': forms.TimeInput(attrs={'type': 'time'}),
|
||||||
|
'end_time': forms.TimeInput(attrs={'type': 'time'}),
|
||||||
|
}
|
||||||
|
labels = {
|
||||||
|
'start_weekday': _('Start Day'),
|
||||||
|
'start_time': _('Start Time'),
|
||||||
|
'end_weekday': _('End Day'),
|
||||||
|
'end_time': _('End Time'),
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
cancel_url = kwargs.pop('cancel_url', '#')
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.helper = FormHelper()
|
||||||
|
self.helper.layout = Layout(
|
||||||
|
Div(
|
||||||
|
Div('start_weekday', css_class='col-md-6'),
|
||||||
|
Div('start_time', css_class='col-md-6'),
|
||||||
|
css_class='row'
|
||||||
|
),
|
||||||
|
Div(
|
||||||
|
Div('end_weekday', css_class='col-md-6'),
|
||||||
|
Div('end_time', 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'
|
||||||
|
)
|
||||||
|
)
|
||||||
@@ -24,7 +24,7 @@ class ScheduleProfile(models.Model):
|
|||||||
|
|
||||||
|
|
||||||
class ScheduleSlot(models.Model):
|
class ScheduleSlot(models.Model):
|
||||||
profile = models.ForeignKey(ScheduleProfile, on_delete=models.CASCADE, related_name="slots")
|
profile = models.ForeignKey(ScheduleProfile, on_delete=models.CASCADE, related_name="time_interval")
|
||||||
start_weekday = models.PositiveSmallIntegerField(choices=WEEK_DAYS)
|
start_weekday = models.PositiveSmallIntegerField(choices=WEEK_DAYS)
|
||||||
end_weekday = models.PositiveSmallIntegerField(choices=WEEK_DAYS)
|
end_weekday = models.PositiveSmallIntegerField(choices=WEEK_DAYS)
|
||||||
start_time = models.TimeField()
|
start_time = models.TimeField()
|
||||||
|
|||||||
@@ -1 +1,124 @@
|
|||||||
# 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 scheduler.forms import ScheduleProfileForm
|
||||||
|
from scheduler.forms import ScheduleSlotForm
|
||||||
|
from scheduler.models import ScheduleProfile, ScheduleSlot
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def view_scheduler_profile_list(request):
|
||||||
|
profiles = ScheduleProfile.objects.all().order_by('name')
|
||||||
|
context = {
|
||||||
|
'profiles': profiles,
|
||||||
|
}
|
||||||
|
return render(request, 'scheduler/scheduleprofile_list.html', context)
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def view_manage_scheduler_profile(request):
|
||||||
|
profile_uuid = request.GET.get('uuid')
|
||||||
|
if profile_uuid:
|
||||||
|
profile = get_object_or_404(ScheduleProfile, uuid=profile_uuid)
|
||||||
|
title = _('Edit Schedule Profile')
|
||||||
|
slots = profile.time_interval.all()
|
||||||
|
else:
|
||||||
|
profile = None
|
||||||
|
title = _('Create Schedule Profile')
|
||||||
|
slots = None
|
||||||
|
|
||||||
|
if request.method == 'POST':
|
||||||
|
form = ScheduleProfileForm(request.POST, instance=profile)
|
||||||
|
if form.is_valid():
|
||||||
|
form.save()
|
||||||
|
messages.success(request, _('Schedule Profile saved successfully.'))
|
||||||
|
return redirect('scheduler_profile_list')
|
||||||
|
else:
|
||||||
|
form = ScheduleProfileForm(instance=profile)
|
||||||
|
|
||||||
|
context = {
|
||||||
|
'form': form,
|
||||||
|
'title': title,
|
||||||
|
'profile': profile,
|
||||||
|
'slots': slots,
|
||||||
|
}
|
||||||
|
return render(request, 'scheduler/scheduleprofile_form.html', context)
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def view_delete_scheduler_profile(request):
|
||||||
|
profile_uuid = request.GET.get('uuid')
|
||||||
|
profile = get_object_or_404(ScheduleProfile, uuid=profile_uuid)
|
||||||
|
|
||||||
|
if request.method == 'POST':
|
||||||
|
profile.delete()
|
||||||
|
messages.success(request, _('Schedule Profile deleted successfully.'))
|
||||||
|
return redirect('scheduler_profile_list')
|
||||||
|
|
||||||
|
context = {
|
||||||
|
'object': profile,
|
||||||
|
'title': _('Delete Schedule Profile'),
|
||||||
|
'cancel_url': reverse('scheduler_profile_list'),
|
||||||
|
'text': _('Are you sure you want to delete the profile "%(name)s"?') % {'name': profile.name}
|
||||||
|
}
|
||||||
|
return render(request, 'generic_delete_confirmation.html', context)
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def view_manage_scheduler_slot(request):
|
||||||
|
slot_uuid = request.GET.get('uuid')
|
||||||
|
profile_uuid = request.GET.get('profile_uuid')
|
||||||
|
|
||||||
|
if slot_uuid:
|
||||||
|
slot = get_object_or_404(ScheduleSlot, uuid=slot_uuid)
|
||||||
|
profile = slot.profile
|
||||||
|
title = _('Edit Time Interval')
|
||||||
|
else:
|
||||||
|
profile = get_object_or_404(ScheduleProfile, uuid=profile_uuid)
|
||||||
|
slot = None
|
||||||
|
title = _('Add Time Interval')
|
||||||
|
|
||||||
|
cancel_url = f"{reverse('manage_scheduler_profile')}?uuid={profile.uuid}"
|
||||||
|
|
||||||
|
if request.method == 'POST':
|
||||||
|
form = ScheduleSlotForm(request.POST, instance=slot, cancel_url=cancel_url)
|
||||||
|
if form.is_valid():
|
||||||
|
new_slot = form.save(commit=False)
|
||||||
|
new_slot.profile = profile
|
||||||
|
new_slot.save()
|
||||||
|
messages.success(request, _('Time Interval saved successfully.'))
|
||||||
|
return redirect(cancel_url)
|
||||||
|
else:
|
||||||
|
form = ScheduleSlotForm(instance=slot, cancel_url=cancel_url)
|
||||||
|
|
||||||
|
context = {
|
||||||
|
'form': form,
|
||||||
|
'title': title,
|
||||||
|
'page_title': title,
|
||||||
|
'profile': profile,
|
||||||
|
}
|
||||||
|
return render(request, 'generic_form.html', context)
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def view_delete_scheduler_slot(request):
|
||||||
|
slot_uuid = request.GET.get('uuid')
|
||||||
|
slot = get_object_or_404(ScheduleSlot, uuid=slot_uuid)
|
||||||
|
profile = slot.profile
|
||||||
|
cancel_url = f"{reverse('manage_scheduler_profile')}?uuid={profile.uuid}"
|
||||||
|
|
||||||
|
if request.method == 'POST':
|
||||||
|
slot.delete()
|
||||||
|
messages.success(request, _('Time Interval deleted successfully.'))
|
||||||
|
return redirect(cancel_url)
|
||||||
|
|
||||||
|
context = {
|
||||||
|
'object': slot,
|
||||||
|
'title': _('Delete Time Interval'),
|
||||||
|
'cancel_url': cancel_url,
|
||||||
|
'text': _('Are you sure you want to delete this time interval?')
|
||||||
|
}
|
||||||
|
return render(request, 'scheduler/generic_delete_confirm.html', context)
|
||||||
|
|||||||
24
templates/generic_delete_confirmation.html
Normal file
24
templates/generic_delete_confirmation.html
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="card card-danger card-outline">
|
||||||
|
<div class="card-header">
|
||||||
|
<h3 class="card-title">{{ title }}</h3>
|
||||||
|
</div>
|
||||||
|
<form method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="card-body">
|
||||||
|
<p>{{ text }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="card-footer">
|
||||||
|
<button type="submit" class="btn btn-danger">
|
||||||
|
<i class="fas fa-trash"></i> {% trans 'Confirm Delete' %}
|
||||||
|
</button>
|
||||||
|
<a href="{{ cancel_url }}" class="btn btn-secondary">
|
||||||
|
{% trans 'Cancel' %}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
86
templates/scheduler/scheduleprofile_form.html
Normal file
86
templates/scheduler/scheduleprofile_form.html
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% load crispy_forms_tags %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="card card-primary card-outline">
|
||||||
|
<div class="card-header">
|
||||||
|
<h3 class="card-title">{{ title }}</h3>
|
||||||
|
</div>
|
||||||
|
<form method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12">
|
||||||
|
{{ form|crispy }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Schedule Slots -->
|
||||||
|
{% if profile %}
|
||||||
|
<div class="row mt-4">
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||||
|
<h4>{% trans "Time Intervals" %}</h4>
|
||||||
|
<a href="{% url 'manage_scheduler_slot' %}?profile_uuid={{ profile.uuid }}"
|
||||||
|
class="btn btn-sm btn-primary">
|
||||||
|
<i class="fas fa-plus"></i> {% trans "Add Interval" %}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>{% trans "Start Day" %}</th>
|
||||||
|
<th>{% trans "Start Time" %}</th>
|
||||||
|
<th>{% trans "End Day" %}</th>
|
||||||
|
<th>{% trans "End Time" %}</th>
|
||||||
|
<th class="text-end">{% trans "Actions" %}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for slot in slots %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ slot.get_start_weekday_display }}</td>
|
||||||
|
<td>{{ slot.start_time }}</td>
|
||||||
|
<td>{{ slot.get_end_weekday_display }}</td>
|
||||||
|
<td>{{ slot.end_time }}</td>
|
||||||
|
<td class="text-end">
|
||||||
|
<a href="{% url 'manage_scheduler_slot' %}?uuid={{ slot.uuid }}"
|
||||||
|
class="btn btn-sm btn-outline-primary" title="{% trans 'Edit' %}">
|
||||||
|
<i class="fas fa-edit"></i>
|
||||||
|
</a>
|
||||||
|
<a href="{% url 'delete_scheduler_slot' %}?uuid={{ slot.uuid }}"
|
||||||
|
class="btn btn-sm btn-outline-danger" title="{% trans 'Delete' %}">
|
||||||
|
<i class="fas fa-trash"></i>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% empty %}
|
||||||
|
<tr>
|
||||||
|
<td colspan="5" class="text-center text-muted">
|
||||||
|
{% trans "No time intervals found." %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="row mt-4">
|
||||||
|
<div class="col-12 d-flex justify-content-end gap-2">
|
||||||
|
<button type="submit" class="btn btn-primary">
|
||||||
|
<i class="fas fa-save"></i> {% trans "Save" %}
|
||||||
|
</button>
|
||||||
|
<a href="{% url 'scheduler_profile_list' %}" class="btn btn-secondary">
|
||||||
|
<i class="fas fa-times"></i> {% trans "Cancel" %}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
48
templates/scheduler/scheduleprofile_list.html
Normal file
48
templates/scheduler/scheduleprofile_list.html
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="card card-primary card-outline">
|
||||||
|
<div class="card-header">
|
||||||
|
<h3 class="card-title">{% trans 'Schedule Profiles' %}</h3>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<table class="table table-striped table-bordered">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>{% trans 'Name' %}</th>
|
||||||
|
<th>{% trans 'Peers' %}</th>
|
||||||
|
<th style="width: 100px;">{% trans 'Actions' %}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for profile in profiles %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ profile.name }}</td>
|
||||||
|
<td>{{ profile.peerscheduling_set.count }}</td>
|
||||||
|
<td class="text-nowrap">
|
||||||
|
<a href="{% url 'manage_scheduler_profile' %}?uuid={{ profile.uuid }}"
|
||||||
|
class="btn btn-sm btn-info" title="{% trans 'Edit' %}">
|
||||||
|
<i class="far fa-edit"></i>
|
||||||
|
</a>
|
||||||
|
<a href="{% url 'delete_scheduler_profile' %}?uuid={{ profile.uuid }}"
|
||||||
|
class="btn btn-sm btn-danger" title="{% trans 'Delete' %}">
|
||||||
|
<i class="fas fa-trash"></i>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% empty %}
|
||||||
|
<tr>
|
||||||
|
<td colspan="5" class="text-center">{% trans 'No schedule profiles found.' %}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="card-footer">
|
||||||
|
<a href="{% url 'manage_scheduler_profile' %}" class="btn btn-primary">
|
||||||
|
<i class="fas fa-plus"></i> {% trans 'Add Profile' %}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
@@ -99,6 +99,16 @@
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="{% url 'scheduler_profile_list' %}"
|
||||||
|
class="nav-link {% if '/scheduler/' in request.path %}active{% endif %}">
|
||||||
|
<i class="fas fa-calendar-alt nav-icon"></i>
|
||||||
|
<p>
|
||||||
|
{% trans 'Scheduler' %}
|
||||||
|
</p>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
|
|||||||
@@ -32,6 +32,8 @@ from firewall.views import manage_firewall_rule, manage_redirect_rule, view_fire
|
|||||||
view_reset_firewall
|
view_reset_firewall
|
||||||
from intl_tools.views import view_change_language
|
from intl_tools.views import view_change_language
|
||||||
from routing_templates.views import view_manage_routing_template, view_routing_template_list
|
from routing_templates.views import view_manage_routing_template, view_routing_template_list
|
||||||
|
from scheduler.views import view_scheduler_profile_list, view_manage_scheduler_profile, view_delete_scheduler_profile, \
|
||||||
|
view_manage_scheduler_slot, view_delete_scheduler_slot
|
||||||
from user_manager.views import view_manage_user, view_peer_group_list, view_peer_group_manage, view_user_list
|
from user_manager.views import view_manage_user, view_peer_group_list, view_peer_group_manage, view_user_list
|
||||||
from vpn_invite.views import view_email_settings, view_vpn_invite_list, view_vpn_invite_settings
|
from vpn_invite.views import view_email_settings, view_vpn_invite_list, view_vpn_invite_settings
|
||||||
from vpn_invite_public.views import view_public_vpn_invite
|
from vpn_invite_public.views import view_public_vpn_invite
|
||||||
@@ -108,5 +110,10 @@ urlpatterns = [
|
|||||||
path('cluster/settings/', cluster_settings, name='cluster_settings'),
|
path('cluster/settings/', cluster_settings, name='cluster_settings'),
|
||||||
path('routing-templates/list/', view_routing_template_list, name='routing_template_list'),
|
path('routing-templates/list/', view_routing_template_list, name='routing_template_list'),
|
||||||
path('routing-templates/manage/', view_manage_routing_template, name='manage_routing_template'),
|
path('routing-templates/manage/', view_manage_routing_template, name='manage_routing_template'),
|
||||||
|
path('scheduler/profile/list/', view_scheduler_profile_list, name='scheduler_profile_list'),
|
||||||
|
path('scheduler/profile/manage/', view_manage_scheduler_profile, name='manage_scheduler_profile'),
|
||||||
|
path('scheduler/profile/delete/', view_delete_scheduler_profile, name='delete_scheduler_profile'),
|
||||||
|
path('scheduler/slot/manage/', view_manage_scheduler_slot, name='manage_scheduler_slot'),
|
||||||
|
path('scheduler/slot/delete/', view_delete_scheduler_slot, name='delete_scheduler_slot'),
|
||||||
path('change_language/', view_change_language, name='change_language'),
|
path('change_language/', view_change_language, name='change_language'),
|
||||||
]
|
]
|
||||||
|
|||||||
Reference in New Issue
Block a user