Files
wireguard_webadmin/templates/scheduler/scheduleprofile_form.html
2026-02-04 17:21:08 -03:00

298 lines
14 KiB
HTML

{% extends "base.html" %}
{% load i18n %}
{% load crispy_forms_tags %}
{% block page_custom_head %}
<style>
.grid-cell {
transition: background-color 0.3s ease;
}
</style>
{% endblock %}
{% 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 Grid Visualization -->
{% if profile %}
<div class="row mt-4">
<div class="col-12">
<div class="card card-outline card-secondary">
<div class="card-header">
<h4 class="card-title">{% trans "Schedule Visualization" %}</h4>
</div>
<div class="card-body p-1">
<div class="schedule-grid-container mb-1">
<div class="schedule-grid-header d-flex text-center mb-0">
<div class="flex-grow-1">Mon
</div>
<div class="flex-grow-1">Tue
</div>
<div class="flex-grow-1">Wed
</div>
<div class="flex-grow-1">Thu
</div>
<div class="flex-grow-1">Fri
</div>
<div class="flex-grow-1">Sat
</div>
<div class="flex-grow-1">Sun
</div>
</div>
<div class="schedule-grid-body">
{% for hour in "012345678901234567890123"|make_list %}
<div class="d-flex align-items-center mb-0">
<div class="grid-row flex-grow-1 d-flex gap-0" style="height: 6px;">
{% for day in "0123456"|make_list %}
<div class="grid-cell flex-grow-1 border-bottom border-end"
id="cell-{{ day }}-{{ forloop.parentloop.counter0 }}"
title="{{ forloop.parentloop.counter0 }}:00"
style="background-color: #f8f9fa;">
</div>
{% endfor %}
</div>
</div>
{% endfor %}
</div>
</div>
<div class="d-flex mt-0 justify-content-center">
<span><i class="fas fa-circle" style="color: #28a745;"></i> {% trans "Active" %}</span> &nbsp;
<span><i class="fas fa-circle" style="color: #dc3545;"></i> {% trans "Inactive" %}</span>
</div>
<div class="row">
<div class="col-12">
<ul>
{% if profile.active %}
<li><b>{% trans 'Current time' %}:</b> {{ now }}</li>
<li><b>{% trans 'Becomes active at' %}:</b> {{ profile.next_dates.enable|default_if_none:'' }}</li>
<li><b>{% trans 'Becomes inactive at' %}:</b> {{ profile.next_dates.disable|default_if_none:'' }}</li>
{% else %}
<li><b>{% trans 'Status' %}:</b> {% trans 'Inactive' %}</li>
{% endif %}
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Existing Table -->
<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 %}
<!-- Peers List Debug Table -->
{% if show_peers %}
<div class="row mt-4">
<div class="col-12">
<div class="card card-outline card-info">
<div class="card-header">
<h4 class="card-title">{% trans "Linked Peers (Debug)" %}</h4>
</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-hover table-striped mb-0">
<thead>
<tr>
<th>{% trans "Peer" %}</th>
<th>{% trans "Disabled by Schedule" %}</th>
<th>{% trans "Next Enable" %}</th>
<th>{% trans "Next Disable" %}</th>
</tr>
</thead>
<tbody>
{% for item in peers_scheduling %}
<tr>
<td>
<a href="{% url 'wireguard_peer_manage' %}?peer={{ item.peer.uuid }}">
{{ item.peer }}
</a>
</td>
<td>
{% if item.peer.disabled_by_schedule %}
<span class="badge bg-danger">{% trans "Yes" %}</span>
{% else %}
<span class="badge bg-success">{% trans "No" %}</span>
{% endif %}
</td>
<td>{{ item.next_scheduled_enable_at|default:"-" }}</td>
<td>{{ item.next_scheduled_disable_at|default:"-" }}</td>
</tr>
{% empty %}
<tr>
<td colspan="4" class="text-center text-muted">{% trans "No peers linked to this profile." %}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</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> &nbsp;
<a href="{% url 'scheduler_profile_list' %}" class="btn btn-secondary">
<i class="fas fa-times"></i> {% trans "Cancel" %}
</a> &nbsp;
{% if profile %}
<a href="?uuid={{ profile.uuid }}&show_peers=true" class="btn btn-outline-info">
<i class="fas fa-bug"></i> {% trans "Show Peers" %}
</a> &nbsp;
{% endif %}
</div>
</div>
</div>
</form>
</div>
{% endblock %}
{% block custom_page_scripts %}
<script>
document.addEventListener('DOMContentLoaded', function () {
const slots = [
{% for slot in slots %}
{
start_day: {{ slot.start_weekday }},
end_day: {{ slot.end_weekday }},
start_time: "{{ slot.start_time|time:'H:i' }}",
end_time: "{{ slot.end_time|time:'H:i' }}"
},
{% endfor %}
];
function timeToMinutes(timeStr) {
const [hours, minutes] = timeStr.split(':').map(Number);
return hours * 60 + minutes;
}
function isHourCovered(day, hour) {
const hourStart = hour * 60;
const hourEnd = (hour + 1) * 60;
// Normalize day to 0-6 (Mon-Sun)
// Note: Our model uses 0=Monday, which matches the grid loop
for (const slot of slots) {
let currentDay = slot.start_day;
let currentMinutes = timeToMinutes(slot.start_time);
// Calculate total duration in minutes
let totalMinutes = 0;
let endMinutes = timeToMinutes(slot.end_time);
let tempDay = slot.start_day;
while (tempDay != slot.end_day) {
totalMinutes += 1440; // 24 * 60
tempDay = (tempDay + 1) % 7;
}
totalMinutes = totalMinutes - currentMinutes + endMinutes;
if (totalMinutes < 0) totalMinutes += 7 * 1440; // Wrap around week
// Check if the hour [day:hourStart, day:hourEnd] overlaps with [slotStart, slotEnd]
// Convert everything to week minutes
const slotStartWeekMinutes = slot.start_day * 1440 + currentMinutes;
const hourStartWeekMinutes = day * 1440 + hourStart;
const hourEndWeekMinutes = day * 1440 + hourEnd;
// Handle wrap around week
const weekTotal = 7 * 1440;
// A simple way is to check the range [slotStart, slotStart + totalMinutes]
// and see if the hour (or its week-repetitions) overlaps it.
// Try for current week and next week (to handle wrap)
for (let offset of [0, -weekTotal, weekTotal]) {
const s = slotStartWeekMinutes + offset;
const e = s + totalMinutes;
if (Math.max(s, hourStartWeekMinutes) < Math.min(e, hourEndWeekMinutes)) {
return true;
}
}
}
return false;
}
for (let day = 0; day < 7; day++) {
for (let hour = 0; hour < 24; hour++) {
const cell = document.getElementById(`cell-${day}-${hour}`);
if (cell) {
if (isHourCovered(day, hour)) {
cell.style.backgroundColor = '#28a745'; // green-ish
} else {
cell.style.backgroundColor = '#dc3545'; // red-ish
}
}
}
}
});
</script>
{% endblock %}