git add templates/! REFACOR INTERFEJSU git add templates/

This commit is contained in:
Mateusz Gruszczyński 2025-02-28 13:12:29 +01:00
parent 4e965195f5
commit d0f1d25063
16 changed files with 1335 additions and 771 deletions

2
app.py
View File

@ -285,7 +285,7 @@ def get_email_template(subject, message):
<p>{message}</p>
</div>
<div class="footer">
<p>Wiadomość wygenerowana automatycznie przez system RouterOS Backup.</p>
<p>Wiadomość wygenerowana automatycznie przez system RouterOS Update</p>
</div>
</div>
</body>

View File

@ -1,43 +1,90 @@
{% extends "base.html" %}
{% block title %}Dodaj urządzenie - RouterOS Update{% endblock %}
{% block extra_head %}
<style>
/* Styl karty w trybie ciemnym */
body.dark-mode .card {
background-color: #1e1e1e;
color: #ccc;
border-color: #444;
}
body.dark-mode .card .card-header.bg-light {
background-color: #333 !important;
border-bottom: 1px solid #444;
}
</style>
{% endblock %}
{% block content %}
<div class="row justify-content-center">
<div class="col-md-6">
<h2>Dodaj nowe urządzenie</h2>
<form method="POST">
<!-- Pole nazwy urządzenia -->
<div class="mb-3">
<label for="name" class="form-label">Nazwa urządzenia</label>
<input type="text" class="form-control" name="name" id="name" required>
<div class="container">
<div class="row justify-content-center">
<div class="col-md-6">
<!-- Karta z nagłówkiem i formularzem -->
<div class="card border-0 shadow">
<div class="card-header bg-light">
<h4 class="mb-0">Dodaj nowe urządzenie</h4>
</div>
<div class="card-body">
<form method="POST">
<!-- Pole nazwy urządzenia -->
<div class="mb-3">
<label for="name" class="form-label">Nazwa urządzenia</label>
<input type="text" class="form-control" name="name" id="name" required
placeholder="np. Mikrotik w biurze">
<small class="text-muted">Wpisz przyjazną nazwę, np. „Mikrotik #1” lub „Router na u Kocura.</small>
</div>
<!-- Adres IP -->
<div class="mb-3">
<label for="ip" class="form-label">Adres IP</label>
<input type="text" class="form-control" name="ip" id="ip" required
placeholder="np. 192.168.88.1">
<small class="text-muted">Adres IP lub domena urządzenia (np. mikrotik.moja-domena.pl).</small>
</div>
<!-- Port -->
<div class="mb-3">
<label for="port" class="form-label">Port</label>
<input type="number" class="form-control" name="port" id="port" value="8728" required>
<small class="text-muted">Domyślnie 8728 (lub 8729 w przypadku SSL).</small>
</div>
<!-- Nazwa użytkownika -->
<div class="mb-3">
<label for="device_username" class="form-label">Nazwa użytkownika urządzenia</label>
<input type="text" class="form-control" name="device_username" id="device_username" required
placeholder="np. admin">
</div>
<!-- Hasło -->
<div class="mb-3">
<label for="device_password" class="form-label">Hasło urządzenia</label>
<input type="password" class="form-control" name="device_password" id="device_password" required>
</div>
<!-- Opcja SSL -->
<div class="mb-3 form-check">
<input type="checkbox" class="form-check-input" name="use_ssl" id="use_ssl">
<label class="form-check-label" for="use_ssl">Używaj SSL</label>
</div>
<!-- Opcja braku weryfikacji certyfikatu SSL -->
<div class="mb-3 form-check">
<input type="checkbox" class="form-check-input" name="ssl_insecure" id="ssl_insecure">
<label class="form-check-label" for="ssl_insecure">
Nie weryfikuj certyfikatu SSL
</label>
</div>
<!-- Przycisk dodania urządzenia -->
<button type="submit" class="btn btn-primary">Dodaj urządzenie</button>
</form>
</div>
</div>
<div class="mb-3">
<label for="ip" class="form-label">Adres IP</label>
<input type="text" class="form-control" name="ip" id="ip" required>
</div>
<div class="mb-3">
<label for="port" class="form-label">Port</label>
<input type="number" class="form-control" name="port" id="port" value="8728" required>
</div>
<div class="mb-3">
<label for="device_username" class="form-label">Nazwa użytkownika urządzenia</label>
<input type="text" class="form-control" name="device_username" id="device_username" required>
</div>
<div class="mb-3">
<label for="device_password" class="form-label">Hasło urządzenia</label>
<input type="password" class="form-control" name="device_password" id="device_password" required>
</div>
<!-- Opcja SSL -->
<div class="mb-3 form-check">
<input type="checkbox" class="form-check-input" name="use_ssl" id="use_ssl">
<label class="form-check-label" for="use_ssl">Używaj SSL</label>
</div>
<!-- Opcja nie weryfikowania certyfikatu SSL -->
<div class="mb-3 form-check">
<input type="checkbox" class="form-check-input" name="ssl_insecure" id="ssl_insecure">
<label class="form-check-label" for="ssl_insecure">Nie weryfikuj certyfikatu SSL</label>
</div>
<button type="submit" class="btn btn-primary">Dodaj urządzenie</button>
</form>
</div>
</div>
</div>
{% endblock %}

View File

@ -1,27 +1,80 @@
{% extends "base.html" %}
{% block title %}Wykryte anomalie{% endblock %}
{% block extra_head %}
<style>
/* Karta w trybie ciemnym */
body.dark-mode .card {
background-color: #1e1e1e;
color: #ccc;
border-color: #444;
}
body.dark-mode .card .card-header.bg-light {
background-color: #333 !important;
border-bottom: 1px solid #444;
}
/* Tabela w trybie ciemnym */
body.dark-mode .table thead {
background-color: #2a2a2a;
color: #ccc;
}
body.dark-mode .table-bordered > :not(caption) > * > * {
border-color: #444 !important;
}
</style>
{% endblock %}
{% block content %}
<div class="container">
<h2 class="mb-4">Wykryte anomalie</h2>
<table class="table table-bordered">
<thead>
<tr>
<th>Data</th>
<th>Urządzenie</th>
<th>Opis</th>
<th>Status</th>
</tr>
</thead>
<tbody>
{% for anomaly in anomalies %}
<tr>
<td>{{ anomaly.timestamp.strftime('%Y-%m-%d %H:%M:%S') }}</td>
<td>{{ anomaly.device.name or anomaly.device.ip if anomaly.device }}</td>
<td>{{ anomaly.description }}</td>
<td>{{ 'Rozwiązana' if anomaly.resolved else 'Otwarta' }}</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="card border-0 shadow">
<div class="card-header bg-light">
<h4 class="mb-0">Wykryte anomalie</h4>
</div>
<div class="card-body p-0">
{% if anomalies and anomalies|length > 0 %}
<div class="table-responsive">
<table class="table table-bordered table-hover table-sm align-middle mb-0">
<thead>
<tr>
<th style="white-space: nowrap;">Data</th>
<th style="white-space: nowrap;">Urządzenie</th>
<th>Opis</th>
<th>Status</th>
</tr>
</thead>
<tbody>
{% for anomaly in anomalies %}
<tr>
<td style="white-space: nowrap;">
{{ anomaly.timestamp.strftime('%Y-%m-%d %H:%M:%S') }}
</td>
<td>
{% if anomaly.device %}
{{ anomaly.device.name or anomaly.device.ip }}
{% else %}
<span class="text-muted">Brak</span>
{% endif %}
</td>
<td>{{ anomaly.description }}</td>
<td>
{% if anomaly.resolved %}
<span class="badge bg-success">Rozwiązana</span>
{% else %}
<span class="badge bg-danger">Otwarta</span>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<div class="p-3">
<p class="mb-0">Brak aktualnych anomalii.</p>
</div>
{% endif %}
</div>
</div>
</div>
{% endblock %}

View File

@ -16,11 +16,6 @@
body.dark-mode .navbar {
background-color: #333 !important;
color: #ffffff;
/* ---
border-bottom: 1px solid #e0e0e0;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
padding: 0.5rem 1rem;
--- */
}
body.dark-mode .navbar .navbar-brand,
body.dark-mode .navbar .nav-link {
@ -57,11 +52,11 @@
background-color: #f8f9fa;
color: #212529;
}
/* Alerty pozostają bez zmian */
.diff-add { color: green; }
.diff-rem { color: red; }
/* Alerty pozostają bez zmian */
.diff-add { color: green; }
.diff-rem { color: red; }
body.dark-mode table {
background-color: #1a1a1a;
color: #cccccc;
@ -132,29 +127,19 @@
}
/* ========== Tryb jasny (light-mode) ========== */
/* --- Nawigacja (Navbar) --- */
body.light-mode .navbar {
background-color: #dcdcdc !important;
color: #333333;
/* ---
border-bottom: 1px solid #e0e0e0;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
padding: 0.5rem 1rem;
--- */
}
/* --- Przyciski (dla niezalogowanych) --- */
body.light-mode .navbar .btn-outline-primary:hover {
background-color: #007bff;
color: #ffffff;
}
/* --- Stopka --- */
footer {
background-color: #f8f9fa;
color: #212529;
}
/* --- Responsywność --- */
@media (max-width: 768px) {
body.light-mode .navbar .nav-link {
padding: 0.5rem;
@ -230,6 +215,25 @@
</div>
</div>
</nav>
<!-- Kontener do wyświetlania komunikatów flash -->
<div class="container mt-3">
{% with messages = get_flashed_messages(with_categories=True) %}
{% if messages %}
{% for category, message in messages %}
<!-- Jeśli brak kategorii, używamy 'info' -->
{% if category == 'message' %}
{% set category = 'info' %}
{% endif %}
<div class="alert alert-{{ category }} alert-dismissible fade show" role="alert">
{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{% endfor %}
{% endif %}
{% endwith %}
</div>
<main class="container my-4 flex-fill">
{% block content %}{% endblock %}
</main>

View File

@ -1,24 +1,71 @@
{% extends "base.html" %}
{% block title %}Reset hasła - RouterOS Update{% endblock %}
{% block title %}Zmiana hasła - RouterOS Update{% endblock %}
{% block extra_head %}
<style>
/* Karta w trybie ciemnym */
body.dark-mode .card {
background-color: #1e1e1e;
color: #ccc;
border-color: #444;
}
body.dark-mode .card .card-header.bg-light {
background-color: #333 !important;
border-bottom: 1px solid #444;
}
</style>
{% endblock %}
{% block content %}
<div class="row justify-content-center">
<div class="col-md-6">
<h2 class="mb-4">Zmiana hasła</h2>
<form method="POST">
<div class="mb-3">
<label for="old_password" class="form-label">Stare hasło</label>
<input type="password" class="form-control" name="old_password" id="old_password" required>
<div class="container">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card border-0 shadow">
<div class="card-header bg-light">
<h4 class="mb-0">Zmiana hasła</h4>
</div>
<div class="card-body">
<form method="POST">
<div class="mb-3">
<label for="old_password" class="form-label">Stare hasło</label>
<input
type="password"
class="form-control"
name="old_password"
id="old_password"
required
placeholder="••••••••"
>
</div>
<div class="mb-3">
<label for="new_password" class="form-label">Nowe hasło</label>
<input
type="password"
class="form-control"
name="new_password"
id="new_password"
required
placeholder="••••••••"
>
</div>
<div class="mb-3">
<label for="confirm_password" class="form-label">Potwierdź nowe hasło</label>
<input
type="password"
class="form-control"
name="confirm_password"
id="confirm_password"
required
placeholder="••••••••"
>
</div>
<button type="submit" class="btn btn-primary">Zresetuj hasło</button>
</form>
</div>
</div>
<div class="mb-3">
<label for="new_password" class="form-label">Nowe hasło</label>
<input type="password" class="form-control" name="new_password" id="new_password" required>
</div>
<div class="mb-3">
<label for="confirm_password" class="form-label">Potwierdź nowe hasło</label>
<input type="password" class="form-control" name="confirm_password" id="confirm_password" required>
</div>
<button type="submit" class="btn btn-primary">Zresetuj hasło</button>
</form>
</div>
</div>
</div>
{% endblock %}

View File

@ -76,24 +76,32 @@
</div>
</div>
<!-- Sekcja Ostatnie zdarzenia -->
<div class="card mb-3 border-0">
<div class="card-header bg-light py-2">
<h6 class="mb-0">Ostatnie zdarzenia</h6>
</div>
{% if recent_logs %}
<!-- Sekcja Ostatnie zdarzenia -->
<div class="card mb-3 border-0">
<div class="card-header bg-light py-2">
<h6 class="mb-0">Ostatnie zdarzenia</h6>
</div>
<div class="card-body p-0">
{% if recent_logs %}
<ul class="list-group list-group-flush">
{% for log in recent_logs %}
<li class="list-group-item p-1 border-top-0 border-bottom">
<small class="text-muted">{{ log.timestamp.strftime('%Y-%m-%d %H:%M:%S') }}</small>
{{ log.message|truncate(100) }}
</li>
<li class="list-group-item">
<!-- Data w nieco mniejszej czcionce, np. "small text-muted" -->
<div class="small text-muted">{{ log.timestamp.strftime('%Y-%m-%d %H:%M:%S') }}</div>
<!-- Treść zdarzenia; "truncate(100)" żeby skrócić tekst -->
<div class="mt-1">
{{ log.message|truncate(100) }}
</div>
</li>
{% endfor %}
</ul>
{% else %}
<p class="mb-0">Brak ostatnich zdarzeń.</p>
{% endif %}
{% else %}
<div class="p-3">
Brak ostatnich zdarzeń.
</div>
{% endif %}
</div>
</div>
</div>
</div>

View File

@ -1,89 +1,117 @@
{% extends "base.html" %}
{% block title %}Szczegóły urządzenia - RouterOS Update{% endblock %}
{% block extra_head %}
<style>
/* Stylizacja kart w trybie ciemnym */
body.dark-mode .card {
background-color: #1e1e1e;
color: #e0e0e0;
border-color: #444;
}
/* Stylizacja bloku logów przewijalny, z odpowiednim tłem i kolorem tekstu */
.log-block {
background-color: inherit;
color: inherit;
padding: 1rem;
border: 1px solid #ccc;
border-radius: 0.25rem;
max-height: 300px;
overflow-y: auto;
white-space: pre-wrap;
}
/* Stylizacja overlay dla system update */
#system-update-overlay, #reboot-progress-overlay {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.7);
z-index: 1000;
color: white;
text-align: center;
padding-top: 200px;
}
#system-update-overlay h3, #reboot-progress-overlay h3 {
margin-bottom: 20px;
}
.progress-container {
width: 50%;
margin: 20px auto;
background: #444;
border-radius: 5px;
}
.progress-bar {
width: 0%;
height: 30px;
background: #4caf50;
border-radius: 5px;
}
</style>
{% endblock %}
{% block content %}
<div class="container">
<div class="my-4">
<h2 class="mb-3">Szczegóły urządzenia</h2>
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{{ url_for('devices') }}">Urządzenia</a></li>
<li class="breadcrumb-item active" aria-current="page">{{ device.ip }}</li>
</ol>
</nav>
</div>
<!-- Dwukolumnowy układ: dane urządzenia i informacje o systemie -->
{% block extra_head %}
<style>
/* ========================
Tryb ciemny i styl kart
======================== */
body.dark-mode .card {
background-color: #1e1e1e;
color: #e0e0e0;
border-color: #444;
}
body.dark-mode .card .card-header.bg-primary,
body.dark-mode .card .card-header.bg-secondary {
background-color: #333 !important;
border-bottom: 1px solid #444;
}
/* Blok logów przewijalny, zachowanie białych znaków,
jasne/dark tło zgodnie z kartą */
.log-block {
background-color: inherit;
color: inherit;
padding: 1rem;
border: 1px solid #ccc;
border-radius: 0.25rem;
max-height: 300px;
overflow-y: auto;
white-space: pre-wrap;
}
/* Overlays (system-update / reboot) */
#system-update-overlay,
#reboot-progress-overlay {
display: none;
position: fixed;
top: 0; left: 0;
width: 100%; height: 100%;
background: rgba(0,0,0,0.7);
z-index: 1000;
color: white;
text-align: center;
padding-top: 200px;
}
#system-update-overlay h3,
#reboot-progress-overlay h3 {
margin-bottom: 20px;
}
.progress-container {
width: 50%;
margin: 20px auto;
background: #444;
border-radius: 5px;
overflow: hidden;
}
.progress-bar {
width: 0%;
height: 30px;
background: #4caf50;
border-radius: 5px;
transition: width 0.5s linear;
}
/* Div do firmware restart (informacja i przycisk) */
#firmware-restart-div {
display: none;
margin-top: 20px;
}
</style>
{% endblock %}
{% block content %}
<div class="container my-4">
<!-- Nagłówek strony z nawigacją breadcrumb -->
<h2 class="mb-3">Szczegóły urządzenia</h2>
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{{ url_for('devices') }}">Urządzenia</a></li>
<li class="breadcrumb-item active" aria-current="page">{{ device.ip }}</li>
</ol>
</nav>
<!-- Dwukolumnowy układ: kolumna z danymi urządzenia, kolumna z info systemu -->
<div class="row">
<div class="col-md-6">
<div class="card mb-3">
<div class="card border-0 shadow mb-3">
<div class="card-header bg-primary text-white">
Dane urządzenia
</div>
<div class="card-body">
<p><strong>Adres IP:</strong> {{ device.ip }}</p>
<p><strong>Port:</strong> {{ device.port }}</p>
<p><strong>Ostatnie sprawdzenie:</strong>
{% if device.last_check %}{{ device.last_check.strftime('%Y-%m-%d %H:%M:%S') }}{% else %}Brak{% endif %}
<p>
<strong>Ostatnie sprawdzenie:</strong>
{% if device.last_check %}
{{ device.last_check.strftime('%Y-%m-%d %H:%M:%S') }}
{% else %}
Brak
{% endif %}
</p>
<p>
<strong>System:</strong> {{ device.current_version or 'Brak' }}<br>
<strong>Firmware:</strong> {{ device.current_firmware or 'N/A' }}<br>
<strong>Upgrade Firmware:</strong> <b>{{ device.upgrade_firmware or 'N/A' }}</b>
<strong>Upgrade Firmware:</strong> {{ device.upgrade_firmware or 'N/A' }}
</p>
<p>
<strong>Branch aktualizacji:</strong> {{ device.branch|capitalize }}
</p>
<!-- Formularz zmiany branch -->
<!-- Formularz szybkiej zmiany branch -->
<form method="POST" action="{{ url_for('edit_device', device_id=device.id) }}" class="mt-3">
<div class="input-group">
<select class="form-select" name="branch">
@ -91,14 +119,15 @@
<option value="dev" {% if device.branch == 'dev' %}selected{% endif %}>Dev</option>
<option value="beta" {% if device.branch == 'beta' %}selected{% endif %}>Beta</option>
</select>
<button type="submit" class="btn btn-primary ms-2">Zmień branch</button>
<button type="submit" class="btn btn-primary">Zmień branch</button>
</div>
</form>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card mb-3">
<div class="card border-0 shadow mb-3">
<div class="card-header bg-primary text-white">
Informacje o systemie
</div>
@ -109,8 +138,14 @@
</div>
{% else %}
<p><strong>Wersja systemu:</strong> {{ resource.version or 'Brak danych' }}</p>
<p><strong>Czas pracy:</strong> {{ resource.uptime or 'Brak danych' }}</p>
<p><strong>Obciążenie CPU:</strong> {{ resource['cpu-load'] or 'Brak' }}%</p>
<p><strong>Czas pracy (uptime):</strong> {{ resource.uptime or 'Brak danych' }}</p>
<p><strong>Obciążenie CPU:</strong>
{% if resource['cpu-load'] is defined %}
{{ resource['cpu-load'] }}%
{% else %}
Brak danych
{% endif %}
</p>
<p>
<strong>Pamięć:</strong>
{% if resource['free-memory'] and resource['total-memory'] %}
@ -119,18 +154,23 @@
Brak danych
{% endif %}
</p>
<p><strong>Wolne miejsce na dysku:</strong> {{ resource['free-hdd-space'] or 'Brak danych' }}</p>
<p>
<strong>Wolne miejsce na dysku:</strong>
{% if resource['free-hdd-space'] %}
{{ resource['free-hdd-space'] }}
{% else %}
Brak danych
{% endif %}
</p>
{% endif %}
</div>
</div>
</div>
</div>
<!-- Logi urządzenia jako pojedynczy blok tekstu -->
<div class="card mb-3">
<div class="card-header bg-secondary text-white">
Logi urządzenia
</div>
<!-- Logi urządzenia -->
<div class="card border-0 shadow mb-3">
<div class="card-header bg-secondary text-white">Logi urządzenia</div>
<div class="card-body">
{% if device.last_log %}
<div class="log-block">
@ -142,32 +182,40 @@
</div>
</div>
<!-- Akcje urządzenia -->
<div class="mb-4">
<div class="d-flex flex-wrap gap-2">
<form id="system-update-form" method="POST" action="{{ url_for('update_device', device_id=device.id) }}">
<button type="submit" class="btn btn-warning">Aktualizuj system</button>
</form>
<form id="firmware-update-form" method="POST" action="{{ url_for('update_firmware', device_id=device.id) }}">
<button type="submit" class="btn btn-danger">Aktualizuj firmware</button>
</form>
<a href="{{ url_for('force_check', device_id=device.id) }}" class="btn btn-secondary">Wymuś sprawdzenie</a>
</div>
<div class="mt-3">
<a href="{{ url_for('devices') }}" class="btn btn-outline-secondary">Powrót do listy urządzeń</a>
</div>
<!-- Akcje urządzenia: aktualizacje, wymuszenie sprawdzenia -->
<div class="mb-4 d-flex flex-wrap gap-2">
<!-- Aktualizacja systemu -->
<form id="system-update-form" method="POST" action="{{ url_for('update_device', device_id=device.id) }}">
<button type="submit" class="btn btn-warning">Aktualizuj system</button>
</form>
<!-- Aktualizacja firmware -->
<form id="firmware-update-form" method="POST" action="{{ url_for('update_firmware', device_id=device.id) }}">
<button type="submit" class="btn btn-danger">Aktualizuj firmware</button>
</form>
<!-- Wymuszenie sprawdzenia -->
<a href="{{ url_for('force_check', device_id=device.id) }}" class="btn btn-secondary">Wymuś sprawdzenie</a>
<!-- Link powrotny do listy -->
<a href="{{ url_for('devices') }}" class="btn btn-outline-secondary ms-auto">Powrót</a>
</div>
<!-- Overlay dla system update (5 minut) -->
<!-- Miejsce na komunikat o konieczności rebootu po firmware update -->
<div id="firmware-restart-div">
<div class="alert alert-warning mb-3">
Urządzenie wymaga restartu po aktualizacji firmware.
</div>
<button id="restart-device-btn" class="btn btn-danger">Restart urządzenia</button>
</div>
<!-- Overlay: system update (4 min. można dać 5 min) -->
<div id="system-update-overlay">
<h3>Aktualizacja systemu rozpoczęta...</h3>
<div class="progress-container">
<div id="system-update-progress" class="progress-bar"></div>
</div>
<p id="system-update-timer">300 sekund</p>
<p id="system-update-timer">240 sekund</p>
</div>
<!-- Overlay dla reboot progress (2 minuty) -->
<!-- Overlay: reboot po restarcie (2 min) -->
<div id="reboot-progress-overlay">
<h3>Restart urządzenia...</h3>
<div class="progress-container">
@ -175,92 +223,85 @@
</div>
<p id="reboot-timer">120 sekund</p>
</div>
<!-- Div dla firmware restart (komunikat z przyciskiem restartu) -->
<div id="firmware-restart-div" style="display:none; margin-top:20px;">
<div class="alert alert-warning">
Router wymaga ręcznego wykonania polecenia reboot.
</div>
<button id="restart-device-btn" class="btn btn-danger">Restart urządzenia</button>
</div>
</div>
<script>
// System update: Po kliknięciu w formularz system update
document.getElementById('system-update-form').addEventListener('submit', function(e) {
e.preventDefault(); // Zatrzymaj standardowe wysłanie formularza
var overlay = document.getElementById('system-update-overlay');
overlay.style.display = 'block';
var timeLeft = 240;
var progressBar = document.getElementById('system-update-progress');
var timerDisplay = document.getElementById('system-update-timer');
var interval = setInterval(function(){
timeLeft--;
var percent = ((240 - timeLeft) / 240) * 100;
progressBar.style.width = percent + '%';
timerDisplay.textContent = timeLeft + ' sekund';
if(timeLeft <= 0){
clearInterval(interval);
// Po zakończeniu 5 minut wykonaj polecenie "Wymuś sprawdzenie"
fetch('{{ url_for("force_check", device_id=device.id) }}', { method: 'GET' })
.then(function(response){
location.reload();
})
.catch(function(error){ console.error('Błąd force check:', error); });
}
}, 1000);
// Opcjonalnie wysyłamy formularz AJAX, aby rozpocząć aktualizację systemu
fetch(this.action, { method: 'POST' })
.then(function(response){ /* opcjonalna obsługa odpowiedzi */ })
.catch(function(error){ console.error('Błąd aktualizacji systemu:', error); });
});
// ========================
// Obsługa aktualizacji systemu
// ========================
document.getElementById('system-update-form').addEventListener('submit', function(e) {
e.preventDefault(); // powstrzymujemy normalne przeładowanie
// Pokaż overlay postępu
var overlay = document.getElementById('system-update-overlay');
overlay.style.display = 'block';
var timeLeft = 240; // 4 min
var progressBar = document.getElementById('system-update-progress');
var timerDisplay = document.getElementById('system-update-timer');
var interval = setInterval(function(){
timeLeft--;
var percent = ((240 - timeLeft) / 240) * 100;
progressBar.style.width = percent + '%';
timerDisplay.textContent = timeLeft + ' sekund';
if(timeLeft <= 0){
clearInterval(interval);
// Po zakończeniu: fetch do force_check, potem reload
fetch('{{ url_for("force_check", device_id=device.id) }}', { method: 'GET' })
.then(()=> location.reload())
.catch((err)=> console.error('Błąd force check:', err));
}
}, 1000);
// Wyślij POST do update
fetch(this.action, { method: 'POST' })
.catch(err => console.error('Błąd aktualizacji systemu:', err));
});
// Firmware update: Po kliknięciu w formularz firmware update
document.getElementById('firmware-update-form').addEventListener('submit', function(e) {
e.preventDefault(); // Zatrzymaj standardowe wysłanie formularza
fetch(this.action, { method: 'POST' })
.then(function(response){ /* opcjonalna obsługa odpowiedzi */ })
.catch(function(error){ console.error('Błąd aktualizacji firmware:', error); });
// Pokaż div z komunikatem o reboot i przyciskiem restartu
document.getElementById('firmware-restart-div').style.display = 'block';
});
// ========================
// Obsługa aktualizacji firmware
// ========================
document.getElementById('firmware-update-form').addEventListener('submit', function(e) {
e.preventDefault();
fetch(this.action, { method: 'POST' })
.catch(err => console.error('Błąd aktualizacji firmware:', err));
// Pokazujemy div z przyciskiem do restartu
document.getElementById('firmware-restart-div').style.display = 'block';
});
// Restart urządzenia: Po kliknięciu przycisku restart wysyłamy komendę reboot,
// a następnie pokazujemy overlay z 2-minutowym paskiem postępu
document.getElementById('restart-device-btn').addEventListener('click', function() {
fetch('{{ url_for("restart_device", device_id=device.id) }}', {
method: 'POST'
}).then(function(response){
if(response.ok){
// ========================
// Obsługa reboot
// ========================
document.getElementById('restart-device-btn').addEventListener('click', function(){
fetch('{{ url_for("restart_device", device_id=device.id) }}', { method: 'POST' })
.then(response => {
if (response.ok) {
alert('Komenda reboot wysłana.');
// Pokaż overlay dla reboot progress
var rebootOverlay = document.getElementById('reboot-progress-overlay');
rebootOverlay.style.display = 'block';
var timeLeft = 90;
// Pokaż overlay z 2 min postępu
var overlay = document.getElementById('reboot-progress-overlay');
overlay.style.display = 'block';
var timeLeft = 120; // 2 min
var progressBar = document.getElementById('reboot-progress-bar');
var timerDisplay = document.getElementById('reboot-timer');
var interval = setInterval(function(){
timeLeft--;
var percent = ((90 - timeLeft) / 90) * 100;
var percent = ((120 - timeLeft) / 120) * 100;
progressBar.style.width = percent + '%';
timerDisplay.textContent = timeLeft + ' sekund';
if(timeLeft <= 0){
clearInterval(interval);
// Po 2 minutach wykonaj polecenie "Wymuś sprawdzenie"
// Po zakończeniu: fetch do force_check
fetch('{{ url_for("force_check", device_id=device.id) }}', { method: 'GET' })
.then(function(response){
location.reload();
})
.catch(function(error){ console.error('Błąd force check:', error); });
.then(()=> location.reload())
.catch(err => console.error('Błąd force check:', err));
}
}, 1000);
} else {
alert('Błąd podczas wysyłania komendy reboot.');
}
}).catch(function(error){
alert('Błąd: ' + error);
});
});
})
.catch(err => alert('Błąd: ' + err));
});
</script>
{% endblock %}

View File

@ -2,116 +2,178 @@
{% block title %}Moje urządzenia - RouterOS Update{% endblock %}
{% block extra_head %}
<style>
.progress-container {
width: 50%;
margin: 20px auto;
background: #444;
border-radius: 5px;
overflow: hidden;
}
.progress-bar {
width: 0%;
height: 30px;
background: #4caf50;
border-radius: 5px;
transition: width 0.5s linear;
}
<style>
/* Karta w trybie ciemnym */
body.dark-mode .card {
background-color: #1e1e1e;
color: #ccc;
border-color: #444;
}
#mass-system-update-overlay, #mass-firmware-update-overlay {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.7);
z-index: 1000;
color: white;
text-align: center;
padding-top: 200px;
}
</style>
/* Nagłówek karty w trybie ciemnym */
body.dark-mode .card .card-header.bg-light {
background-color: #333 !important;
border-bottom: 1px solid #444;
}
/* Tabela w trybie ciemnym */
body.dark-mode .table thead.table-dark {
background-color: #2a2a2a !important;
color: #ccc !important;
border-color: #444 !important;
}
body.dark-mode .table-hover tbody tr:hover {
background-color: #2f2f2f;
}
body.dark-mode .table-bordered > :not(caption) > * > * {
border-color: #444 !important;
}
/* Styl paska postępu w overlay */
.progress-container {
width: 50%;
margin: 20px auto;
background: #444;
border-radius: 5px;
overflow: hidden;
}
.progress-bar {
width: 0%;
height: 30px;
background: #4caf50;
border-radius: 5px;
transition: width 0.5s linear;
}
#mass-system-update-overlay,
#mass-firmware-update-overlay {
display: none;
position: fixed;
top: 0; left: 0;
width: 100%; height: 100%;
background: rgba(0,0,0,0.7);
z-index: 1000;
color: white;
text-align: center;
padding-top: 200px;
}
/* Prompt do firmware w overlay */
#mass-firmware-reboot-prompt {
display:none;
position:fixed;
top:0; left:0;
width:100%; height:100%;
background:rgba(0,0,0,0.7);
z-index:1100;
color:white;
text-align:center;
padding-top:200px;
}
</style>
{% endblock %}
{% block content %}
<h2>Moje urządzenia</h2>
<div class="row mb-3">
<div class="col text-end">
<button type="button" class="btn btn-success" onclick="window.location.href='{{ url_for('add_device') }}'">
Dodaj nowe urządzenie
</button>
<div class="container">
<h2 class="mb-4">Moje urządzenia</h2>
<!-- Karta z tabelą urządzeń i przyciskami masowych akcji -->
<div class="card border-0 shadow">
<div class="card-header bg-light d-flex align-items-center justify-content-between">
<h5 class="mb-0">Lista urządzeń</h5>
<button type="button" class="btn btn-success" onclick="window.location.href='{{ url_for('add_device') }}'">
Dodaj nowe urządzenie
</button>
</div>
<div class="card-body">
<!-- Formularz z przyciskami masowych operacji -->
<form id="mass-update-form">
<div class="mb-3">
<button type="button" id="mass-system-update-btn" class="btn btn-warning me-2">Aktualizuj system (masowo)</button>
<button type="button" id="mass-firmware-update-btn" class="btn btn-danger me-2">Aktualizuj firmware (masowo)</button>
<button type="submit" class="btn btn-primary">Odśwież wybrane</button>
</div>
<div class="table-responsive">
<table class="table table-striped table-hover table-sm align-middle table-bordered">
<thead class="table-dark">
<tr>
<th><input type="checkbox" id="select-all"></th>
<th>Nazwa / Adres IP</th>
<th>Ostatnie sprawdzenie</th>
<th>Status</th>
<th>System / Firmware</th>
<th>Akcje</th>
</tr>
</thead>
<tbody>
{% for device in devices %}
<tr>
<td>
<input type="checkbox" name="selected_devices" value="{{ device.id }}">
</td>
<td>
{% if device.name %}
{{ device.name }}
<br><code class="text-muted">{{ device.ip }}</code>
{% else %}
{{ device.ip }}
{% endif %}
</td>
<td>
{{ device.last_check.strftime('%Y-%m-%d %H:%M:%S') if device.last_check else 'Brak' }}
</td>
<td>
{% if device.update_required or (device.current_firmware and device.upgrade_firmware and device.current_firmware != device.upgrade_firmware) %}
{% if device.update_required and (device.current_firmware and device.upgrade_firmware and device.current_firmware != device.upgrade_firmware) %}
<span class="badge bg-danger">Wymaga aktualizacji <small>(system i firmware)</small></span>
{% elif device.update_required %}
<span class="badge bg-danger">Wymaga aktualizacji <small>(system)</small></span>
{% else %}
<span class="badge bg-danger">Wymaga aktualizacji <small>(firmware)</small></span>
{% endif %}
{% else %}
<span class="badge bg-success">Aktualny</span>
{% endif %}
</td>
<td style="white-space: nowrap;">
<small>
<strong>System:</strong> {{ device.current_version or 'Brak' }}<br>
<strong>Firmware:</strong> {{ device.current_firmware or 'Brak' }}<br>
<strong>Upgrade FW:</strong> {{ device.upgrade_firmware or 'N/A' }}
</small>
</td>
<td>
<button type="button" class="btn btn-info btn-sm"
onclick="window.location.href='{{ url_for('device_detail', device_id=device.id) }}'">
Szczegóły
</button>
<button type="button" class="btn btn-secondary btn-sm"
onclick="window.location.href='{{ url_for('force_check', device_id=device.id) }}'">
Sprawdź
</button>
<button type="button" class="btn btn-warning btn-sm"
onclick="window.location.href='{{ url_for('edit_device', device_id=device.id) }}'">
Edytuj
</button>
<!-- Dodane przykładowe przyciski (potrzebna obsługa w backendzie) -->
<button type="button" class="btn btn-outline-danger btn-sm"
onclick="fetch('/device/{{ device.id }}/restart', {method: 'POST'}) .then(()=>location.reload());">
Restart
</button>
</td>
</tr>
{% else %}
<tr>
<td colspan="6" class="text-center">Brak dodanych urządzeń.</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</form>
</div>
</div>
</div>
<!-- Formularz z przyciskami masowych operacji -->
<form id="mass-update-form">
<div class="mb-3">
<button type="button" id="mass-system-update-btn" class="btn btn-warning">Aktualizuj system (masowo)</button>
<button type="button" id="mass-firmware-update-btn" class="btn btn-danger">Aktualizuj firmware (masowo)</button>
<button type="submit" class="btn btn-primary">Odśwież wybrane</button>
</div>
<table class="table table-bordered table-hover">
<thead class="table-dark">
<tr>
<th><input type="checkbox" id="select-all"></th>
<th>Nazwa / Adres IP</th>
<th>Ostatnie sprawdzenie</th>
<th>Status</th>
<th>System / Firmware</th>
<th>Akcje</th>
</tr>
</thead>
<tbody>
{% for device in devices %}
<tr>
<td>
<input type="checkbox" name="selected_devices" value="{{ device.id }}">
</td>
<td>
{% if device.name %}
{{ device.name }} |
<code><small class="text-muted">{{ device.ip }}</small></code>
{% else %}
{{ device.ip }}
{% endif %}
</td>
<td>{{ device.last_check.strftime('%Y-%m-%d %H:%M:%S') if device.last_check else 'Brak' }}</td>
<td>
{% if device.update_required or (device.current_firmware and device.upgrade_firmware and device.current_firmware != device.upgrade_firmware) %}
{% if device.update_required and (device.current_firmware and device.upgrade_firmware and device.current_firmware != device.upgrade_firmware) %}
<span class="badge bg-danger">Wymaga aktualizacji <small>(system i firmware)</small></span>
{% elif device.update_required %}
<span class="badge bg-danger">Wymaga aktualizacji <small>(system)</small></span>
{% else %}
<span class="badge bg-danger">Wymaga aktualizacji <small>(firmware)</small></span>
{% endif %}
{% else %}
<span class="badge bg-success">Aktualny</span>
{% endif %}
</td>
<td>
<small>
<strong>System:</strong> {{ device.current_version or 'Brak' }}<br>
<strong>Firmware:</strong> {{ device.current_firmware or 'Brak' }}<br>
<strong>Upgrade Firmware:</strong> {{ device.upgrade_firmware or 'N/A' }}
</small>
</td>
<td>
<button type="button" class="btn btn-info btn-sm" onclick="window.location.href='{{ url_for('device_detail', device_id=device.id) }}'">Szczegóły</button>
<button type="button" class="btn btn-secondary btn-sm" onclick="window.location.href='{{ url_for('force_check', device_id=device.id) }}'">Wymuś sprawdzenie</button>
<button type="button" class="btn btn-warning btn-sm" onclick="window.location.href='{{ url_for('edit_device', device_id=device.id) }}'">Edytuj</button>
</td>
</tr>
{% else %}
<tr>
<td colspan="6" class="text-center">Brak dodanych urządzeń.</td>
</tr>
{% endfor %}
</tbody>
</table>
</form>
<!-- Overlay dla masowej aktualizacji systemu -->
<div id="mass-system-update-overlay">
@ -132,7 +194,7 @@
</div>
<!-- Dynamiczny prompt dla masowego restartu firmware -->
<div id="mass-firmware-reboot-prompt" style="display:none; position:fixed; top:0; left:0; width:100%; height:100%; background:rgba(0,0,0,0.7); z-index:1100; color:white; text-align:center; padding-top:200px;">
<div id="mass-firmware-reboot-prompt">
<h3>Firmware update zakończony.</h3>
<p>Czy chcesz zrestartować wszystkie wybrane urządzenia?</p>
<button id="mass-firmware-reboot-btn" class="btn btn-danger">Restart urządzeń</button>
@ -140,7 +202,7 @@
</div>
<script>
// Dodajemy event listenery raz, aby uniknąć wielokrotnego przypisywania
// Ten sam skrypt masowych akcji, przeniesiony bez zmian w logice
document.addEventListener('DOMContentLoaded', function() {
document.getElementById('select-all').addEventListener('change', function() {
var checkboxes = document.querySelectorAll('input[name="selected_devices"]');
@ -156,7 +218,6 @@
});
});
// Pobierz zaznaczone urządzenia
function getSelectedDeviceIds() {
var selected = [];
var checkboxes = document.querySelectorAll('input[name="selected_devices"]:checked');
@ -173,7 +234,6 @@
alert("Wybierz przynajmniej jedno urządzenie.");
return;
}
// Wysyłamy update systemu dla każdego urządzenia asynchronicznie
selectedDevices.forEach(function(id) {
fetch(`/device/${id}/update`, { method: 'POST' })
.catch(function(error){ console.error('Błąd aktualizacji systemu dla urządzenia ' + id, error); });
@ -191,7 +251,7 @@
timerDisplay.textContent = timeLeft + ' sekund';
if(timeLeft <= 0){
clearInterval(interval);
// Po zakończeniu odliczania, dla każdego urządzenia wykonaj force_check
// Po zakończeniu odliczania, force_check
selectedDevices.forEach(function(id) {
fetch(`/device/${id}/force_check`, { method: 'GET' })
.catch(function(error){ console.error('Błąd force check dla urządzenia ' + id, error); });
@ -208,7 +268,6 @@
alert("Wybierz przynajmniej jedno urządzenie.");
return;
}
// Wysyłamy update firmware dla każdego urządzenia asynchronicznie
selectedDevices.forEach(function(id) {
fetch(`/device/${id}/update_firmware`, { method: 'POST' })
.catch(function(error){ console.error('Błąd aktualizacji firmware dla urządzenia ' + id, error); });
@ -217,7 +276,7 @@
document.getElementById('mass-firmware-reboot-prompt').style.display = 'block';
});
// Funkcja sekwencyjnego wysyłania reboot (z opóźnieniem)
// Sekwencyjny reboot
function sendRebootSequentially(devices, index = 0) {
if (index >= devices.length) return Promise.resolve();
return fetch(`/device/${devices[index]}/restart`, { method: 'POST' })
@ -225,7 +284,6 @@
console.error('Błąd wysyłania reboot dla urządzenia ' + devices[index], error);
})
.then(function() {
// Opóźnienie 500 ms przed kolejnym rebootem
return new Promise(resolve => setTimeout(resolve, 500));
})
.then(function() {
@ -233,14 +291,11 @@
});
}
// Obsługa przycisku restartu w prompt dla masowego firmware update
function onMassFirmwareReboot() {
document.getElementById('mass-firmware-reboot-prompt').style.display = 'none';
var selectedDevices = getSelectedDeviceIds();
// Wysyłamy rebooty sekwencyjnie
sendRebootSequentially(selectedDevices)
.then(function() {
// Po wysłaniu reboot dla wszystkich urządzeń, pokazujemy overlay z 2-minutowym paskiem postępu
var overlay = document.getElementById('mass-firmware-update-overlay');
overlay.style.display = 'block';
var timeLeft = 120; // 120 sekund
@ -253,7 +308,7 @@
timerDisplay.textContent = timeLeft + ' sekund';
if(timeLeft <= 0){
clearInterval(interval);
// Po zakończeniu odliczania, dla każdego urządzenia wykonaj force_check
// Po zakończeniu odliczania, force_check
selectedDevices.forEach(function(id) {
fetch(`/device/${id}/force_check`, { method: 'GET' })
.catch(function(error){ console.error('Błąd force check dla urządzenia ' + id, error); });
@ -263,8 +318,8 @@
}, 1000);
});
}
// Obsługa standardowego przycisku "Odśwież wybrane"
// Obsługa "Odśwież wybrane"
document.getElementById('mass-update-form').addEventListener('submit', function(e) {
e.preventDefault();
var selectedDevices = getSelectedDeviceIds();
@ -279,4 +334,4 @@
setTimeout(function(){ location.reload(); }, 2000);
});
</script>
{% endblock %}
{% endblock %}

View File

@ -1,52 +1,102 @@
{% extends "base.html" %}
{% block title %}Edytuj urządzenie - RouterOS Update{% endblock %}
{% block extra_head %}
<style>
/* Karta w trybie ciemnym */
body.dark-mode .card {
background-color: #1e1e1e;
color: #ccc;
border-color: #444;
}
body.dark-mode .card .card-header.bg-light {
background-color: #333 !important;
border-bottom: 1px solid #444;
}
</style>
{% endblock %}
{% block content %}
<div class="row justify-content-center">
<div class="col-md-6">
<h2>Edytuj urządzenie</h2>
<form method="POST">
<!-- Pole nazwy urządzenia -->
<div class="mb-3">
<label for="name" class="form-label">Nazwa urządzenia</label>
<input type="text" class="form-control" name="name" id="name" value="{{ device.name }}" required>
<div class="container">
<div class="row justify-content-center">
<div class="col-md-6">
<!-- Karta: Edycja urządzenia -->
<div class="card border-0 shadow">
<div class="card-header bg-light">
<h4 class="mb-0">Edytuj urządzenie</h4>
</div>
<div class="card-body">
<form method="POST">
<!-- Pole nazwy urządzenia -->
<div class="mb-3">
<label for="name" class="form-label">Nazwa urządzenia</label>
<input type="text" class="form-control" name="name" id="name" required
value="{{ device.name }}"
placeholder="np. Mikrotik w biurze">
<small class="text-muted">Przyjazna nazwa, np. „Router w piwnicy” lub „Mikrotik #2”.</small>
</div>
<!-- Adres IP -->
<div class="mb-3">
<label for="ip" class="form-label">Adres IP</label>
<input type="text" class="form-control" name="ip" id="ip" required
value="{{ device.ip }}"
placeholder="np. 192.168.88.1">
</div>
<!-- Port -->
<div class="mb-3">
<label for="port" class="form-label">Port</label>
<input type="number" class="form-control" name="port" id="port" required
value="{{ device.port }}">
<small class="text-muted">Domyślnie 8728 (lub 8729 w przypadku SSL).</small>
</div>
<!-- Nazwa użytkownika -->
<div class="mb-3">
<label for="device_username" class="form-label">Nazwa użytkownika urządzenia</label>
<input type="text" class="form-control" name="device_username" id="device_username" required
value="{{ device.device_username }}"
placeholder="np. admin">
</div>
<!-- Hasło urządzenia -->
<div class="mb-3">
<label for="device_password" class="form-label">Hasło urządzenia</label>
<input type="password" class="form-control" name="device_password" id="device_password" required
value="{{ device.device_password }}">
</div>
<!-- SSL i weryfikacja certyfikatów -->
<div class="mb-3 form-check">
<input type="checkbox" class="form-check-input" name="use_ssl" id="use_ssl"
{% if device.use_ssl %}checked{% endif %}>
<label class="form-check-label" for="use_ssl">Używaj SSL</label>
</div>
<div class="mb-3 form-check">
<input type="checkbox" class="form-check-input" name="ssl_insecure" id="ssl_insecure"
{% if device.ssl_insecure %}checked{% endif %}>
<label class="form-check-label" for="ssl_insecure">Nie weryfikuj certyfikatu SSL</label>
</div>
<!-- Branch aktualizacji (stable/dev/beta) -->
<div class="mb-3">
<label for="branch" class="form-label">Wybierz branch aktualizacji</label>
<select class="form-select" name="branch" id="branch">
<option value="stable" {% if device.branch == 'stable' %}selected{% endif %}>Stable</option>
<option value="dev" {% if device.branch == 'dev' %}selected{% endif %}>Dev</option>
<option value="beta" {% if device.branch == 'beta' %}selected{% endif %}>Beta</option>
</select>
</div>
<!-- Przycisk zapisujący zmiany -->
<button type="submit" class="btn btn-primary">Zapisz zmiany</button>
</form>
</div>
</div>
<!-- Pozostałe pola edycji: ip, port, username, password, branch -->
<div class="mb-3">
<label for="ip" class="form-label">Adres IP</label>
<input type="text" class="form-control" name="ip" id="ip" value="{{ device.ip }}" required>
</div>
<div class="mb-3">
<label for="port" class="form-label">Port</label>
<input type="number" class="form-control" name="port" id="port" value="{{ device.port }}" required>
</div>
<div class="mb-3">
<label for="device_username" class="form-label">Nazwa użytkownika urządzenia</label>
<input type="text" class="form-control" name="device_username" id="device_username" value="{{ device.device_username }}" required>
</div>
<div class="mb-3">
<label for="device_password" class="form-label">Hasło urządzenia</label>
<input type="password" class="form-control" name="device_password" id="device_password" value="{{ device.device_password }}" required>
</div>
<!-- Opcja SSL -->
<div class="mb-3 form-check">
<input type="checkbox" class="form-check-input" name="use_ssl" id="use_ssl" {% if device.use_ssl %}checked{% endif %}>
<label class="form-check-label" for="use_ssl">Używaj SSL</label>
</div>
<!-- Opcja nie weryfikowania certyfikatu SSL -->
<div class="mb-3 form-check">
<input type="checkbox" class="form-check-input" name="ssl_insecure" id="ssl_insecure" {% if device.ssl_insecure %}checked{% endif %}>
<label class="form-check-label" for="ssl_insecure">Nie weryfikuj certyfikatu SSL</label>
</div>
<div class="mb-3">
<label for="branch" class="form-label">Wybierz branch aktualizacji</label>
<select class="form-select" name="branch" id="branch">
<option value="stable" {% if device.branch == 'stable' %}selected{% endif %}>Stable</option>
<option value="dev" {% if device.branch == 'dev' %}selected{% endif %}>Dev</option>
<option value="beta" {% if device.branch == 'beta' %}selected{% endif %}>Beta</option>
</select>
</div>
<button type="submit" class="btn btn-primary">Zapisz zmiany</button>
</form>
</div>
</div>
</div>
{% endblock %}

View File

@ -1,20 +1,50 @@
{% extends "base.html" %}
{% block title %}Logowanie - RouterOS Update{% endblock %}
{% block extra_head %}
<style>
/* Karta w trybie ciemnym */
body.dark-mode .card {
background-color: #1e1e1e;
color: #ccc;
border-color: #444;
}
body.dark-mode .card .card-header.bg-light {
background-color: #333 !important;
border-bottom: 1px solid #444;
}
</style>
{% endblock %}
{% block content %}
<div class="row justify-content-center">
<div class="col-md-6">
<h2>Logowanie</h2>
<form method="POST">
<div class="mb-3">
<label for="username" class="form-label">Nazwa użytkownika</label>
<input type="text" class="form-control" name="username" id="username" required>
<div class="container">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card border-0 shadow">
<div class="card-header bg-light">
<h4 class="mb-0">Logowanie</h4>
</div>
<div class="card-body">
<form method="POST">
<div class="mb-3">
<label for="username" class="form-label">Nazwa użytkownika</label>
<input type="text" class="form-control" name="username" id="username" required
placeholder="np. admin">
</div>
<div class="mb-3">
<label for="password" class="form-label">Hasło</label>
<input type="password" class="form-control" name="password" id="password" required
placeholder="••••••••">
</div>
<button type="submit" class="btn btn-primary">Zaloguj</button>
</form>
<hr>
<p class="mb-0">Nie masz konta?
<a href="{{ url_for('register') }}">Zarejestruj się</a>
</p>
</div>
</div>
<div class="mb-3">
<label for="password" class="form-label">Hasło</label>
<input type="password" class="form-control" name="password" id="password" required>
</div>
<button type="submit" class="btn btn-primary">Zaloguj</button>
</form>
</div>
</div>
</div>
{% endblock %}

View File

@ -1,65 +1,134 @@
{% extends "base.html" %}
{% block title %}Logi - Aplikacja Updatera{% endblock %}
{% block extra_head %}
<!-- Dołącz styl CSS biblioteki VanillaDataTables -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/vanilla-datatables@latest/dist/vanilla-dataTables.min.css">
<!-- Styl VanillaDataTables -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/vanilla-datatables@latest/dist/vanilla-dataTables.min.css">
<style>
/* =======================================
Dostosowanie do trybu ciemnego
(jeśli w base.html używasz body.dark-mode)
======================================= */
/* Karta w trybie ciemnym */
body.dark-mode .card {
background-color: #1e1e1e;
color: #cccccc;
border-color: #444;
}
/* Nagłówek karty w trybie ciemnym */
body.dark-mode .card .card-header.bg-light {
background-color: #333 !important;
border-bottom: 1px solid #444;
}
/* Tabela w trybie ciemnym
- wyjaśnienie: thead.table-dark w trybie jasnym ma swoje domyślne kolory,
więc w ciemnym je nadpisujemy, żeby był czytelny. */
body.dark-mode .table thead.table-dark {
background-color: #2a2a2a !important;
color: #ccc !important;
border-color: #444 !important;
}
body.dark-mode .table-hover tbody tr:hover {
background-color: #2f2f2f;
}
/* Obramowania w trybie ciemnym */
body.dark-mode .table.table-bordered > :not(caption) > * > * {
border-color: #444 !important;
}
/* Tabela DataTables drobne poprawki w trybie ciemnym */
body.dark-mode .dataTable-wrapper .dataTable-info,
body.dark-mode .dataTable-wrapper .dataTable-pagination,
body.dark-mode .dataTable-wrapper .dataTable-dropdown label,
body.dark-mode .dataTable-wrapper .dataTable-input {
color: #ccc;
}
</style>
{% endblock %}
{% block content %}
<h2>Logi</h2>
<!-- Formularz kasowania logów starszych niż podana liczba dni -->
<div class="mt-4">
<h4>Usuń logi starsze niż podana liczba dni</h4>
<form method="POST" action="{{ url_for('clean_logs') }}">
<div class="mb-3">
<label for="days" class="form-label">Liczba dni</label>
<input type="number" class="form-control" name="days" id="days" placeholder="Podaj liczbę dni">
<div class="container">
<!-- Nagłówek strony -->
<h2 class="mb-4">Logi</h2>
<!-- Karta: Formularz kasowania logów -->
<div class="card border-0 shadow mb-4">
<div class="card-header bg-light">
<h5 class="mb-0">Usuń logi starsze niż podana liczba dni</h5>
</div>
<button type="submit" class="btn btn-danger">Usuń logi</button>
</form>
<div class="card-body">
<form method="POST" action="{{ url_for('clean_logs') }}">
<div class="mb-3">
<label for="days" class="form-label">Liczba dni</label>
<input type="number" class="form-control" name="days" id="days" placeholder="Podaj liczbę dni">
</div>
<button type="submit" class="btn btn-danger">Usuń logi</button>
</form>
</div>
</div>
<!-- Karta: Tabela logów -->
<div class="card border-0 shadow">
<div class="card-header bg-light">
<h5 class="mb-0">Wszystkie logi systemu</h5>
</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-striped table-hover table-sm align-middle mb-0" id="logsTable">
<thead class="table-dark">
<tr>
<th style="white-space: nowrap;">Data i czas</th>
<th>Urządzenie</th>
<th>Wiadomość</th>
</tr>
</thead>
<tbody>
{% for log in logs %}
<tr>
<td style="white-space: nowrap;">{{ log.timestamp.strftime('%Y-%m-%d %H:%M:%S') }}</td>
<td>
{% if log.device_id %}
<a href="{{ url_for('device_detail', device_id=log.device.id) }}">
{{ log.device.name if log.device.name else "Urządzenie #" ~ log.device.id }}
</a>
{% else %}
<span class="text-muted">Ogólne</span>
{% endif %}
</td>
<!-- Treść logu w <pre> z white-space: pre-wrap, żeby łamać długie linie -->
<td><pre style="white-space: pre-wrap; margin:0;">{{ log.message }}</pre></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
<hr>
<table class="table table-striped" id="logsTable">
<thead class="table-dark">
<tr>
<th>Data i czas</th>
<th>Urządzenie</th>
<th>Wiadomość</th>
</tr>
</thead>
<tbody>
{% for log in logs %}
<tr>
<td>{{ log.timestamp.strftime('%Y-%m-%d %H:%M:%S') }}</td>
<td>
{% if log.device_id %}
<a href="{{ url_for('device_detail', device_id=log.device.id) }}">
{{ log.device.name if log.device.name else "Urządzenie #" ~ log.device.id }}
</a>
{% else %}
Ogólne
{% endif %}
</td>
<td><pre style="white-space: pre-wrap;">{{ log.message }}</pre></td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}
{% block extra_scripts %}
<!-- Dołącz skrypt biblioteki VanillaDataTables -->
<script src="https://cdn.jsdelivr.net/npm/vanilla-datatables@latest/dist/vanilla-dataTables.min.js"></script>
<script>
// Inicjalizacja VanillaDataTables dla tabeli logów
const dataTable = new DataTable("#logsTable", {
searchable: true,
sortable: true,
perPage: 10,
labels: {
placeholder: "Szukaj...", // placeholder dla pola wyszukiwania
perPage: "{select} wpisów na stronę", // etykieta przy wyborze liczby wierszy
noRows: "Brak logów.", // komunikat, gdy tabela jest pusta
info: "Wyświetlono {start} - {end} z {rows} logów" // tekst z informacją o paginacji
}
});
</script>
<!-- VanillaDataTables -->
<script src="https://cdn.jsdelivr.net/npm/vanilla-datatables@latest/dist/vanilla-dataTables.min.js"></script>
<script>
// Inicjalizacja VanillaDataTables dla tabeli logów
const dataTable = new DataTable("#logsTable", {
searchable: true,
sortable: true,
perPage: 10,
labels: {
placeholder: "Szukaj...", // placeholder dla pola wyszukiwania
perPage: "{select} wpisów na stronę", // tekst przy select
noRows: "Brak logów.", // gdy tabela jest pusta
info: "Wyświetlono {start} - {end} z {rows} logów" // info paginacja
}
});
</script>
{% endblock %}

View File

@ -1,24 +1,55 @@
{% extends "base.html" %}
{% block title %}Rejestracja - RouterOS Update{% endblock %}
{% block extra_head %}
<style>
/* Karta w trybie ciemnym */
body.dark-mode .card {
background-color: #1e1e1e;
color: #ccc;
border-color: #444;
}
body.dark-mode .card .card-header.bg-light {
background-color: #333 !important;
border-bottom: 1px solid #444;
}
</style>
{% endblock %}
{% block content %}
<div class="row justify-content-center">
<div class="col-md-6">
<h2>Rejestracja</h2>
<form method="POST">
<div class="mb-3">
<label for="username" class="form-label">Nazwa użytkownika</label>
<input type="text" class="form-control" name="username" id="username" required>
<div class="container">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card border-0 shadow">
<div class="card-header bg-light">
<h4 class="mb-0">Rejestracja</h4>
</div>
<div class="card-body">
<form method="POST">
<div class="mb-3">
<label for="username" class="form-label">Nazwa użytkownika</label>
<input type="text" class="form-control" name="username" id="username" required
placeholder="np. admin2">
</div>
<div class="mb-3">
<label for="email" class="form-label">Adres e-mail</label>
<input type="email" class="form-control" name="email" id="email" required
placeholder="np. user@example.com">
</div>
<div class="mb-3">
<label for="password" class="form-label">Hasło</label>
<input type="password" class="form-control" name="password" id="password" required
placeholder="••••••••">
</div>
<button type="submit" class="btn btn-primary">Zarejestruj się</button>
</form>
<hr>
<p class="mb-0">Masz już konto?
<a href="{{ url_for('login') }}">Zaloguj się</a>
</p>
</div>
</div>
<div class="mb-3">
<label for="email" class="form-label">Email</label>
<input type="email" class="form-control" name="email" id="email" required>
</div>
<div class="mb-3">
<label for="password" class="form-label">Hasło</label>
<input type="password" class="form-control" name="password" id="password" required>
</div>
<button type="submit" class="btn btn-primary">Zarejestruj się</button>
</form>
</div>
</div>
</div>
{% endblock %}

View File

@ -1,69 +0,0 @@
{% extends "base.html" %}
{% block title %}Changelog RouterOS Kanały publikacji{% endblock %}
{% block content %}
<div class="container py-4">
<h2 class="mb-4">Changelog RouterOS według kanałów publikacji</h2>
<div class="row">
<!-- Sekcja Stable -->
<div class="col-md-4">
<h3 class="text-success">Stable</h3>
{% for entry in entries_stable %}
<div class="card mb-3">
<div class="card-header bg-success text-white">
<h5 class="mb-0">
{{ entry.version }}
<small>({{ entry.timestamp.strftime('%Y-%b-%d') }})</small>
</h5>
</div>
<div class="card-body">
<pre style="white-space: pre-wrap;">{{ entry.details }}</pre>
</div>
</div>
{% else %}
<p>Brak wpisów.</p>
{% endfor %}
</div>
<!-- Sekcja RC -->
<div class="col-md-4">
<h3 class="text-warning">RC</h3>
{% for entry in entries_rc %}
<div class="card mb-3">
<div class="card-header bg-warning text-white">
<h5 class="mb-0">
{{ entry.version }}
<small>({{ entry.timestamp.strftime('%Y-%b-%d') }})</small>
</h5>
</div>
<div class="card-body">
<pre style="white-space: pre-wrap;">{{ entry.details }}</pre>
</div>
</div>
{% else %}
<p>Brak wpisów.</p>
{% endfor %}
</div>
<!-- Sekcja Beta -->
<div class="col-md-4">
<h3 class="text-info">Beta</h3>
{% for entry in entries_beta %}
<div class="card mb-3">
<div class="card-header bg-info text-white">
<h5 class="mb-0">
{{ entry.version }}
<small>({{ entry.timestamp.strftime('%Y-%b-%d') }})</small>
</h5>
</div>
<div class="card-body">
<pre style="white-space: pre-wrap;">{{ entry.details }}</pre>
</div>
</div>
{% else %}
<p>Brak wpisów.</p>
{% endfor %}
</div>
</div>
<div class="mt-4">
<a href="{{ url_for('dashboard') }}" class="btn btn-secondary">Powrót do dashboardu</a>
</div>
</div>
{% endblock %}

View File

@ -1,91 +1,154 @@
{% extends "base.html" %}
{% block title %}Changelog RouterOS{% endblock %}
{% block extra_head %}
<!-- Dodajemy Prism.js globalny arkusz stylów będzie zmieniany przez base.html -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-okaidia.min.css" id="prism-style">
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js"></script>
<style>
pre {
background-color: #282c34;
color: #abb2bf;
font-size: 1.1rem;
line-height: 1.5;
padding: 1rem;
border-radius: 0 0 5px 5px;
overflow-x: auto;
margin: 0;
{% block extra_head %}
<!-- PRISM.JS (temat okaidia lub dowolny inny) -->
<link rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-okaidia.min.css"
id="prism-style">
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js"></script>
<style>
/*
1) Mniejszy rozmiar fontu w bloczku <code>
2) Łamanie linii, max-width, itp.
3) Tło w jasnym/ciemnym trybie
*/
/* Mniejszy rozmiar i ograniczenie linii */
pre code[class*="language-"] {
font-size: 0.85rem !important; /* dopasuj do gustu, np. 0.9rem lub 0.8rem */
line-height: 1.3 !important;
}
</style>
/* Łamanie linii i scroll w razie potrzeby */
pre {
white-space: pre-wrap;
word-break: break-word;
overflow-wrap: break-word;
max-width: 100%;
overflow-x: auto;
margin: 0;
padding: 1rem;
}
/* Tło w trybie jasnym */
body.light-mode pre {
background-color: #ffffff !important;
color: #212529 !important;
}
/* Tło w trybie ciemnym */
body.dark-mode pre {
background-color: #2b2b2b !important;
color: #e0e0e0 !important;
}
body.dark-mode .card {
background-color: #1e1e1e !important;
color: #ccc !important;
border-color: #444 !important;
}
body.dark-mode .card-header {
background-color: #333 !important;
color: #fff !important;
}
</style>
{% endblock %}
{% block content %}
<div class="container py-4">
<!-- Nagłówek z tytułem i przyciskami akcji (przyciski aktualizacji są tutaj, globalny dark mode toggle już w navbarze) -->
<div class="d-flex justify-content-between align-items-center mb-4">
<h2>Changelog RouterOS</h2>
<a href="{{ url_for('force_fetch_changelogs') }}"
class="btn btn-danger"
onclick="return confirm('Czy na pewno chcesz ręcznie pobrać wszystkie changelogi? Operacja usunie wszystkie stare wpisy.');">
Aktualizuj changelogi
</a>
</div>
<!-- Nawigacja po kanałach -->
<ul class="nav nav-tabs mb-3">
<li class="nav-item">
<a class="nav-link {% if channel=='stable' %}active{% endif %}" href="{{ url_for('routeros_changelog', channel='stable', series=series) }}">Stable</a>
</li>
<li class="nav-item">
<a class="nav-link {% if channel=='rc' %}active{% endif %}" href="{{ url_for('routeros_changelog', channel='rc', series=series) }}">RC</a>
</li>
<li class="nav-item">
<a class="nav-link {% if channel=='beta' %}active{% endif %}" href="{{ url_for('routeros_changelog', channel='beta', series=series) }}">Beta</a>
</li>
</ul>
<!-- Nawigacja po seriach wersji -->
<ul class="nav nav-pills mb-3">
<li class="nav-item">
<a class="nav-link {% if series=='7.x' %}active{% endif %}" href="{{ url_for('routeros_changelog', channel=channel, series='7.x') }}">7.x</a>
</li>
<li class="nav-item">
<a class="nav-link {% if series=='6.x' %}active{% endif %}" href="{{ url_for('routeros_changelog', channel=channel, series='6.x') }}">6.x</a>
</li>
</ul>
<!-- Prezentacja wybranego wpisu changeloga -->
{% if selected_entry %}
<div class="mb-3">
<h4 class="changelog-header">
{{ selected_entry.version | format_version }}
<small>({{ selected_entry.timestamp.strftime('%Y-%b-%d') }})</small>
</h4>
<pre><code class="language-plaintext">{{ selected_entry.details }}</code></pre>
<!-- Karta z cieniowaniem, nagłówek i ciało -->
<div class="card border-0 shadow">
<div class="card-header d-flex justify-content-between align-items-center">
<h4 class="mb-0">Changelog RouterOS</h4>
<a href="{{ url_for('force_fetch_changelogs') }}"
class="btn btn-danger btn-sm"
onclick="return confirm('Czy na pewno chcesz ręcznie pobrać wszystkie changelogi? Operacja usunie wszystkie stare wpisy.');">
Aktualizuj changelogi
</a>
</div>
{% else %}
<p>Brak wpisów dla wybranych ustawień.</p>
{% endif %}
<!-- Formularz wyboru innego wpisu -->
{% if entries|length > 1 %}
<form method="GET" action="{{ url_for('routeros_changelog') }}">
<input type="hidden" name="channel" value="{{ channel }}">
<input type="hidden" name="series" value="{{ series }}">
<div class="input-group mb-3">
<select name="version" class="form-select">
{% for entry in entries %}
<option value="{{ entry.version }}" {% if selected_entry and entry.version == selected_entry.version %}selected{% endif %}>
{{ entry.version | format_version }} ({{ entry.timestamp.strftime('%Y-%b-%d') }})
</option>
{% endfor %}
</select>
<button class="btn btn-primary" type="submit">Pokaż changelog</button>
<div class="card-body">
<!-- Nawigacja kanałów (stable / rc / beta) -->
<ul class="nav nav-tabs mb-3">
<li class="nav-item">
<a class="nav-link {% if channel=='stable' %}active{% endif %}"
href="{{ url_for('routeros_changelog', channel='stable', series=series) }}">
Stable
</a>
</li>
<li class="nav-item">
<a class="nav-link {% if channel=='rc' %}active{% endif %}"
href="{{ url_for('routeros_changelog', channel='rc', series=series) }}">
RC
</a>
</li>
<li class="nav-item">
<a class="nav-link {% if channel=='beta' %}active{% endif %}"
href="{{ url_for('routeros_changelog', channel='beta', series=series) }}">
Beta
</a>
</li>
</ul>
<!-- Nawigacja serii (7.x / 6.x) -->
<ul class="nav nav-pills mb-3">
<li class="nav-item">
<a class="nav-link {% if series=='7.x' %}active{% endif %}"
href="{{ url_for('routeros_changelog', channel=channel, series='7.x') }}">
7.x
</a>
</li>
<li class="nav-item">
<a class="nav-link {% if series=='6.x' %}active{% endif %}"
href="{{ url_for('routeros_changelog', channel=channel, series='6.x') }}">
6.x
</a>
</li>
</ul>
<!-- Prezentacja wybranego changeloga -->
{% if selected_entry %}
<div class="mb-3">
<h5 class="fw-bold">
{{ selected_entry.version | format_version }}
<small class="text-muted">({{ selected_entry.timestamp.strftime('%Y-%b-%d') }})</small>
</h5>
<pre><code class="language-plaintext">{{ selected_entry.details }}</code></pre>
</div>
{% else %}
<div class="alert alert-warning mb-3">
Brak wpisów dla wybranych ustawień (kanał: {{ channel }}, seria: {{ series }}).
</div>
{% endif %}
<!-- Wybór innej wersji, jeśli mamy >1 wpis -->
{% if entries|length > 1 %}
<form method="GET" action="{{ url_for('routeros_changelog') }}">
<input type="hidden" name="channel" value="{{ channel }}">
<input type="hidden" name="series" value="{{ series }}">
<div class="input-group mb-3">
<select name="version" class="form-select">
{% for entry in entries %}
<option value="{{ entry.version }}"
{% if selected_entry and entry.version == selected_entry.version %}selected{% endif %}>
{{ entry.version | format_version }} ({{ entry.timestamp.strftime('%Y-%b-%d') }})
</option>
{% endfor %}
</select>
<button class="btn btn-primary" type="submit">Pokaż</button>
</div>
</form>
{% endif %}
<div class="mt-4">
<a href="{{ url_for('dashboard') }}" class="btn btn-secondary">Powrót do dashboardu</a>
</div>
</form>
{% endif %}
<div class="mt-4">
<a href="{{ url_for('dashboard') }}" class="btn btn-secondary">Powrót do dashboardu</a>
</div>
</div> <!-- /card-body -->
</div> <!-- /card -->
</div>
{% endblock %}

View File

@ -1,89 +1,159 @@
{% extends "base.html" %}
{% block title %}Ustawienia - RouterOS Update{% endblock %}
{% block content %}
<div class="row justify-content-center">
<div class="col-md-8">
<h2 class="mb-4">Ustawienia powiadomień i systemu</h2>
<!-- Główny formularz ustawień -->
<form method="POST">
<fieldset class="border p-3 mb-3">
<legend class="w-auto">Pushover</legend>
<div class="form-check mb-2">
<input type="checkbox" class="form-check-input" name="pushover_enabled" id="pushover_enabled" {% if settings.pushover_enabled %}checked{% endif %}>
<label class="form-check-label" for="pushover_enabled">Włącz powiadomienia Pushover</label>
</div>
<div class="mb-3">
<label for="pushover_user_key" class="form-label">Pushover User Key</label>
<input type="text" class="form-control" name="pushover_user_key" id="pushover_user_key" value="{{ settings.pushover_user_key or '' }}">
</div>
<div class="mb-3">
<label for="pushover_token" class="form-label">Pushover Token</label>
<input type="text" class="form-control" name="pushover_token" id="pushover_token" value="{{ settings.pushover_token or '' }}">
</div>
</fieldset>
<fieldset class="border p-3 mb-3">
<legend class="w-auto">SMTP (E-mail)</legend>
<div class="form-check mb-2">
<input type="checkbox" class="form-check-input" name="email_notifications_enabled" id="email_notifications_enabled" {% if settings.email_notifications_enabled %}checked{% endif %}>
<label class="form-check-label" for="email_notifications_enabled">Włącz powiadomienia e-mail</label>
</div>
<div class="mb-3">
<label for="smtp_server" class="form-label">SMTP Server</label>
<input type="text" class="form-control" name="smtp_server" id="smtp_server" value="{{ settings.smtp_server or '' }}">
</div>
<div class="mb-3">
<label for="smtp_port" class="form-label">SMTP Port</label>
<input type="number" class="form-control" name="smtp_port" id="smtp_port" value="{{ settings.smtp_port or '' }}">
</div>
<div class="mb-3">
<label for="smtp_username" class="form-label">SMTP Username</label>
<input type="text" class="form-control" name="smtp_username" id="smtp_username" value="{{ settings.smtp_username or '' }}">
</div>
<div class="mb-3">
<label for="smtp_password" class="form-label">SMTP Password</label>
<input type="password" class="form-control" name="smtp_password" id="smtp_password" value="{{ settings.smtp_password or '' }}">
</div>
</fieldset>
<fieldset class="border p-3 mb-3">
<legend class="w-auto">E-mail odbiorcy powiadomień</legend>
<div class="mb-3">
<label for="recipient_email" class="form-label">Adres e-mail do otrzymywania powiadomień</label>
<input type="email" class="form-control" name="recipient_email" id="recipient_email" value="{{ settings.recipient_email or '' }}">
</div>
</fieldset>
<fieldset class="border p-3 mb-3">
<legend class="w-auto">Interwał sprawdzania</legend>
<div class="mb-3">
<label for="check_interval" class="form-label">Interwał (sekundy) <code>21600 = 6 godzin</code></label>
<input type="number" class="form-control" name="check_interval" id="check_interval" value="{{ settings.check_interval or 21600 }}">
</div>
<div class="mb-3">
<label for="time_input" class="form-label">Czas (HH:MM:SS)</label>
<input type="text" class="form-control" id="time_input" placeholder="np. 01:30:00">
<button type="button" class="btn btn-secondary mt-2" onclick="convertTime()">Konwertuj na sekundy</button>
</div>
</fieldset>
<fieldset class="border p-3 mb-3">
<legend class="w-auto">Retencja logów</legend>
<div class="mb-3">
<label for="log_retention_days" class="form-label">Przechowywać logi przez (dni)</label>
<input type="number" class="form-control" name="log_retention_days" id="log_retention_days" value="{{ settings.log_retention_days or 30 }}">
</div>
</fieldset>
<button type="submit" class="btn btn-primary">Zapisz ustawienia</button>
</form>
<!-- Przyciski testowe w osobnym bloku, ułożone w jednej linii po prawej stronie -->
<div class="mt-4">
{% block extra_head %}
<style>
/* Karta w trybie ciemnym */
body.dark-mode .card {
background-color: #1e1e1e;
color: #ccc;
border-color: #444;
}
/* Nagłówek karty w trybie ciemnym */
body.dark-mode .card .card-header.bg-light {
background-color: #333 !important;
border-bottom: 1px solid #444;
}
</style>
{% endblock %}
{% block content %}
<div class="container">
<!-- Karta z głównym formularzem ustawień -->
<div class="card border-0 shadow mb-4">
<div class="card-header bg-light">
<h4 class="mb-0">Ustawienia powiadomień i systemu</h4>
</div>
<div class="card-body">
<form method="POST">
<!-- Sekcja Pushover -->
<fieldset class="border p-3 mb-3">
<legend class="w-auto">Pushover</legend>
<div class="form-check mb-2">
<input type="checkbox" class="form-check-input" name="pushover_enabled" id="pushover_enabled"
{% if settings.pushover_enabled %}checked{% endif %}>
<label class="form-check-label" for="pushover_enabled">
Włącz powiadomienia Pushover
</label>
</div>
<div class="mb-3">
<label for="pushover_user_key" class="form-label">Pushover User Key</label>
<input type="text" class="form-control" name="pushover_user_key" id="pushover_user_key"
value="{{ settings.pushover_user_key or '' }}"
placeholder="np. ujHGkDYop837...">
</div>
<div class="mb-3">
<label for="pushover_token" class="form-label">Pushover Token</label>
<input type="text" class="form-control" name="pushover_token" id="pushover_token"
value="{{ settings.pushover_token or '' }}"
placeholder="np. a9WsK09mnj3R8aGj...">
</div>
</fieldset>
<!-- Sekcja SMTP (E-mail) -->
<fieldset class="border p-3 mb-3">
<legend class="w-auto">SMTP (E-mail)</legend>
<div class="form-check mb-2">
<input type="checkbox" class="form-check-input" name="email_notifications_enabled"
id="email_notifications_enabled"
{% if settings.email_notifications_enabled %}checked{% endif %}>
<label class="form-check-label" for="email_notifications_enabled">
Włącz powiadomienia e-mail
</label>
</div>
<div class="mb-3">
<label for="smtp_server" class="form-label">SMTP Server</label>
<input type="text" class="form-control" name="smtp_server" id="smtp_server"
value="{{ settings.smtp_server or '' }}"
placeholder="np. smtp.gmail.com">
</div>
<div class="mb-3">
<label for="smtp_port" class="form-label">SMTP Port</label>
<input type="number" class="form-control" name="smtp_port" id="smtp_port"
value="{{ settings.smtp_port or '' }}"
placeholder="np. 587">
</div>
<div class="mb-3">
<label for="smtp_username" class="form-label">SMTP Username</label>
<input type="text" class="form-control" name="smtp_username" id="smtp_username"
value="{{ settings.smtp_username or '' }}">
</div>
<div class="mb-3">
<label for="smtp_password" class="form-label">SMTP Password</label>
<input type="password" class="form-control" name="smtp_password" id="smtp_password"
value="{{ settings.smtp_password or '' }}">
</div>
</fieldset>
<!-- Sekcja E-mail odbiorcy -->
<fieldset class="border p-3 mb-3">
<legend class="w-auto">Odbiorca powiadomień</legend>
<div class="mb-3">
<label for="recipient_email" class="form-label">Adres e-mail do otrzymywania powiadomień</label>
<input type="email" class="form-control" name="recipient_email" id="recipient_email"
value="{{ settings.recipient_email or '' }}"
placeholder="np. admin@example.com">
</div>
</fieldset>
<!-- Sekcja interwału -->
<fieldset class="border p-3 mb-3">
<legend class="w-auto">Interwał sprawdzania</legend>
<div class="mb-3">
<label for="check_interval" class="form-label">
Interwał (sekundy)
<code>21600 = 6 godzin</code>
</label>
<input type="number" class="form-control" name="check_interval" id="check_interval"
value="{{ settings.check_interval or 21600 }}">
<small class="text-muted">Co ile sekund system będzie sprawdzał aktualizacje.</small>
</div>
<div class="mb-3">
<label for="time_input" class="form-label">Czas (HH:MM:SS)</label>
<input type="text" class="form-control" id="time_input" placeholder="np. 01:30:00">
<button type="button" class="btn btn-secondary mt-2" onclick="convertTime()">
Konwertuj na sekundy
</button>
<small class="text-muted d-block mt-1">
Wpisz czas w formacie Godziny:Minuty:Sekundy, a następnie kliknij „Konwertuj”.
</small>
</div>
</fieldset>
<!-- Sekcja retencji logów -->
<fieldset class="border p-3 mb-3">
<legend class="w-auto">Retencja logów</legend>
<div class="mb-3">
<label for="log_retention_days" class="form-label">
Przechowywać logi przez (dni)
</label>
<input type="number" class="form-control" name="log_retention_days" id="log_retention_days"
value="{{ settings.log_retention_days or 30 }}">
<small class="text-muted">Starsze logi będą automatycznie usuwane po tym czasie.</small>
</div>
</fieldset>
<!-- Przycisk zapisywania -->
<button type="submit" class="btn btn-primary">Zapisz ustawienia</button>
</form>
</div>
</div>
<!-- Karta z przyciskami testowymi -->
<div class="card border-0 shadow">
<div class="card-header bg-light">
<h5 class="mb-0">Test powiadomień</h5>
</div>
<div class="card-body">
<p class="text-muted">Skorzystaj z przycisków, aby wysłać testowe powiadomienie Pushover / E-mail.</p>
<div class="d-flex justify-content-end">
<form method="POST" action="{{ url_for('test_pushover') }}" class="d-inline me-2">
<form method="POST" action="{{ url_for('test_pushover') }}" class="me-2">
<button type="submit" class="btn btn-secondary">Test Pushover</button>
</form>
<form method="POST" action="{{ url_for('test_email') }}" class="d-inline">
<form method="POST" action="{{ url_for('test_email') }}">
<button type="submit" class="btn btn-secondary">Test E-mail</button>
</form>
</div>

View File

@ -1,27 +1,92 @@
{% extends "base.html" %}
{% block title %}Historia aktualizacji{% endblock %}
{% block extra_head %}
<style>
/* === Styl dopasowany do trybu ciemnego i jasnego === */
/* Tło karty w trybie ciemnym */
body.dark-mode .card {
background-color: #1e1e1e;
color: #ccc;
}
/* Nagłówek karty w trybie ciemnym */
body.dark-mode .card .card-header.bg-light {
background-color: #333 !important;
border-bottom: 1px solid #444;
}
/* Tabela w trybie ciemnym: jaśniejsze wyróżnienie nagłówka */
body.dark-mode .card table thead {
background-color: #2a2a2a;
color: #ccc;
}
/* Tło wierszy tabeli w trybie ciemnym */
body.dark-mode .card table tbody tr {
background-color: #1e1e1e;
color: #ccc;
}
/* Obramowanie w trybie ciemnym */
body.dark-mode .table-bordered > :not(caption) > * > * {
border-color: #444 !important;
}
</style>
{% endblock %}
{% block content %}
<div class="container">
<h2 class="mb-4">Historia aktualizacji</h2>
<table class="table table-bordered">
<thead>
<tr>
<th>Data</th>
<th>Urządzenie</th>
<th>Typ aktualizacji</th>
<th>Szczegóły</th>
</tr>
</thead>
<tbody>
{% for history in histories %}
<tr>
<td>{{ history.timestamp.strftime('%Y-%m-%d %H:%M:%S') }}</td>
<td>{{ history.device.name or history.device.ip }}</td>
<td>{{ history.update_type }}</td>
<td>{{ history.details }}</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="card border-0 shadow mb-4">
<!-- Nagłówek karty -->
<div class="card-header bg-light">
<h4 class="mb-0">Historia aktualizacji</h4>
</div>
<!-- Zawartość karty -->
<div class="card-body p-0">
<!-- Jeśli mamy cokolwiek w histories -->
{% if histories %}
<div class="table-responsive">
<table class="table table-bordered table-hover table-sm align-middle mb-0">
<thead class="table-light">
<tr>
<th class="text-nowrap">Data</th>
<th class="text-nowrap">Urządzenie</th>
<th class="text-nowrap">Typ aktualizacji</th>
<th class="text-nowrap">Szczegóły</th>
</tr>
</thead>
<tbody>
{% for history in histories %}
<tr>
<td>{{ history.timestamp.strftime('%Y-%m-%d %H:%M:%S') }}</td>
<td>
{{ history.device.name or history.device.ip }}
</td>
<td>
{# Prosty przykład wyróżnienia typów aktualizacji #}
{% if history.update_type == "system" %}
<span class="badge bg-primary">System</span>
{% elif history.update_type == "firmware" %}
<span class="badge bg-success">Firmware</span>
{% else %}
<span class="badge bg-secondary">{{ history.update_type }}</span>
{% endif %}
</td>
<td>{{ history.details }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<div class="p-3">
<p class="mb-0">Brak historii aktualizacji.</p>
</div>
{% endif %}
</div>
</div>
</div>
{% endblock %}