205 lines
9.0 KiB
HTML
205 lines
9.0 KiB
HTML
{% extends "base.html" %}
|
|
{% block title %}Zarządzanie banami{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="container-fluid px-0 px-md-2">
|
|
<!-- Header z wyszukiwarką i przyciskiem Nowy ban -->
|
|
<div class="d-flex flex-wrap align-items-center justify-content-between gap-3 mb-4">
|
|
<div>
|
|
<h1 class="h3 text-white mb-1">Zarządzanie banami</h1>
|
|
<p class="text-secondary mb-0">Dodawaj, przeglądaj i usuwaj blokady IP. Szybkie wyszukiwanie, selekcja zbiorcza i
|
|
podgląd szczegółów.</p>
|
|
</div>
|
|
<div class="d-flex align-items-center gap-2">
|
|
<button class="btn btn-outline-light" data-bs-toggle="offcanvas" data-bs-target="#newBanOffcanvas"
|
|
aria-controls="newBanOffcanvas">
|
|
+ Dodaj
|
|
</button>
|
|
<div class="vr d-none d-md-block"></div>
|
|
<div class="input-group input-group-sm" style="min-width: 260px;">
|
|
<span class="input-group-text bg-dark border-secondary text-secondary">🔎</span>
|
|
<input id="search-input" type="search" class="form-control bg-dark border-secondary text-white"
|
|
placeholder="Szukaj po IP, hostname, powodzie…" aria-label="Szukaj">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Pasek narzędzi + jeden wspólny formularz dla selekcji zbiorczej -->
|
|
<div class="card bg-dark border-secondary mb-3">
|
|
<div class="card-body d-flex flex-wrap align-items-center gap-2">
|
|
<div class="me-auto d-flex align-items-center gap-3">
|
|
<span id="selection-counter" class="text-secondary small">0 zaznaczonych</span>
|
|
<span class="text-secondary small">Aktualne bany: <span class="text-white fw-semibold">{{ banned_ips|length
|
|
}}</span></span>
|
|
</div>
|
|
<div class="btn-group" role="group" aria-label="Akcje zbiorcze">
|
|
<!-- Przycisk zbiorczego usuwania zwiążemy z formularzem poniżej -->
|
|
<button type="submit" name="delete" id="delete-selected" class="btn btn-outline-danger" form="bulk-form" disabled>
|
|
Usuń zaznaczone
|
|
</button>
|
|
<button type="button" id="delete-all-btn" class="btn btn-outline-danger">Usuń wszystkie</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Jeden wspólny formularz obejmujący tabelę z checkboxami -->
|
|
<form id="bulk-form" method="post" class="mb-0">
|
|
<div class="card bg-dark border-secondary">
|
|
<div class="table-responsive">
|
|
<table class="table table-dark table-hover align-middle mb-0">
|
|
<thead class="position-sticky top-0" style="z-index: 1;">
|
|
<tr class="border-secondary">
|
|
<th style="width:42px">
|
|
<input type="checkbox" id="select-all" aria-label="Zaznacz/odznacz wszystkie">
|
|
</th>
|
|
<th class="text-uppercase text-secondary small">IP</th>
|
|
<th class="text-uppercase text-secondary small">Hostname</th>
|
|
<th class="text-uppercase text-secondary small">Powód</th>
|
|
<th class="text-uppercase text-secondary small">Wygasa</th>
|
|
<th class="text-uppercase text-secondary small text-end" style="width:80px">Akcje</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="bans-tbody">
|
|
{% if banned_ips|length == 0 %}
|
|
<tr>
|
|
<td colspan="6" class="text-center py-5 text-secondary">
|
|
Brak aktywnych banów. Użyj przycisku <span class="text-white">“Nowy ban”</span>, aby dodać pierwszy
|
|
wpis.
|
|
</td>
|
|
</tr>
|
|
{% else %}
|
|
{% for ban in banned_ips %}
|
|
<tr class="ban-row">
|
|
<td>
|
|
<input type="checkbox" class="row-check" name="selected_ips" value="{{ ban.ip }}"
|
|
aria-label="Zaznacz {{ ban.ip }}">
|
|
</td>
|
|
<td>
|
|
<button class="btn btn-link p-0 text-decoration-none ban-ip text-white" data-ip="{{ ban.ip }}"
|
|
type="button">
|
|
{{ ban.ip }}
|
|
</button>
|
|
</td>
|
|
<td>
|
|
{% if ban.hostname and ban.hostname != 'Manual ban' %}
|
|
<span class="badge bg-primary-subtle text-primary-emphasis border border-primary-subtle">{{ ban.hostname
|
|
}}</span>
|
|
{% else %}
|
|
<span class="text-primary">Brak</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
{% if ban.reason %}{{ ban.reason }}{% else %}<span class="text-secondary">Manual ban</span>{% endif %}
|
|
</td>
|
|
<td><span class="expires-text">{{ ban.expires }}</span></td>
|
|
<td class="text-end">
|
|
<!-- Pojedyncze usunięcie: oddzielny mini-form w wierszu -->
|
|
<form method="post" class="d-inline">
|
|
<input type="hidden" name="selected_ips" value="{{ ban.ip }}">
|
|
<button type="submit" name="delete" value="1" class="btn btn-sm btn-outline-danger"
|
|
title="Usuń ten ban">Usuń</button>
|
|
</form>
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
{% endif %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<!-- Offcanvas: Nowy ban -->
|
|
<div class="offcanvas offcanvas-end text-bg-dark" tabindex="-1" id="newBanOffcanvas" aria-labelledby="newBanLabel">
|
|
<div class="offcanvas-header border-bottom border-secondary">
|
|
<h5 class="offcanvas-title" id="newBanLabel">Dodaj nowy ban</h5>
|
|
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="offcanvas" aria-label="Zamknij"></button>
|
|
</div>
|
|
<div class="offcanvas-body">
|
|
<form method="post" id="add-ban-form" novalidate>
|
|
<input type="hidden" name="add_ban">
|
|
<div class="mb-3">
|
|
<label for="ip" class="form-label">Adres IP</label>
|
|
<input id="ip" name="ip" inputmode="numeric" autocomplete="off"
|
|
class="form-control bg-dark text-white border-secondary" required placeholder="np. 192.168.0.10"
|
|
pattern="\d{1,3}(\.\d{1,3}){3}">
|
|
<code>Format: xxx.xxx.xxx.xxx</code>
|
|
<div class="invalid-feedback">Podaj poprawny adres IPv4.</div>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="reason" class="form-label">Powód <span class="text-secondary">(opcjonalnie)</span></label>
|
|
<input id="reason" name="reason" class="form-control bg-dark text-white border-secondary"
|
|
placeholder="np. brute force / abuse">
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="duration" class="form-label">Czas trwania</label>
|
|
<select id="duration" name="duration" class="form-select bg-dark text-white border-secondary">
|
|
<option value="3600">1 godzina</option>
|
|
<option value="86400">1 dzień</option>
|
|
<option value="604800">1 tydzień</option>
|
|
<option value="2592000">1 miesiąc</option>
|
|
<option value="5184000">2 miesiące</option>
|
|
<option value="7776000">3 miesiące</option>
|
|
<option value="10368000">4 miesiące</option>
|
|
<option value="12960000">5 miesięcy</option>
|
|
<option value="15552000">6 miesięcy</option>
|
|
<option value="18144000">7 miesięcy</option>
|
|
<option value="20736000">8 miesięcy</option>
|
|
<option value="23328000">9 miesięcy</option>
|
|
<option value="25920000">10 miesięcy</option>
|
|
<option value="28512000">11 miesięcy</option>
|
|
<option value="31536000">1 rok</option>
|
|
<option value="63072000">2 lata</option>
|
|
</select>
|
|
</div>
|
|
<div class="d-grid gap-2">
|
|
<button type="submit" class="btn btn-outline-primary">Dodaj ban</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Modal: Szczegóły bana -->
|
|
<div class="modal fade" id="banModal" tabindex="-1" aria-labelledby="banModalLabel" aria-hidden="true">
|
|
<div class="modal-dialog modal-lg">
|
|
<div class="modal-content bg-dark text-white">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title" id="banModalLabel">Szczegóły bana</h5>
|
|
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Zamknij"></button>
|
|
</div>
|
|
<div class="modal-body" id="banModalBody">
|
|
<div class="placeholder-glow">
|
|
<span class="placeholder col-12"></span>
|
|
<span class="placeholder col-10"></span>
|
|
<span class="placeholder col-8"></span>
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Zamknij</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block scripts %}
|
|
{{ super() }}
|
|
<script src="{{ url_for('static', filename='js/bans.js') }}" defer></script>
|
|
<script src="{{ url_for('static', filename='js/delete_bans.js') }}" defer></script>
|
|
{% if message %}
|
|
<script>
|
|
window.addEventListener('DOMContentLoaded', () => {
|
|
window.showToast({ text: {{ message| tojson }}, variant: 'success' });
|
|
});
|
|
</script>
|
|
{% endif %}
|
|
{% if error %}
|
|
<script>
|
|
window.addEventListener('DOMContentLoaded', () => {
|
|
window.showToast({ text: {{ error| tojson }}, variant: 'danger' });
|
|
});
|
|
</script>
|
|
{% endif %}
|
|
|
|
{% endblock %} |