git status! RFACTOR TEMPLATE git status!

This commit is contained in:
Mateusz Gruszczyński 2025-02-28 16:26:27 +01:00
parent d40511823f
commit 911aaab005
17 changed files with 506 additions and 432 deletions

13
app.py
View File

@ -691,6 +691,19 @@ def get_data_folder_size():
pass
return total_size
@app.template_global()
def bootstrap_alert_category(cat):
mapping = {
"error": "danger",
"fail": "danger",
"warn": "warning",
"warning": "warning",
"ok": "success",
"success": "success",
"info": "info"
}
return mapping.get(cat.lower(), "info")
###############################################################################
# ROUTES
###############################################################################

View File

@ -1,9 +1,9 @@
{% extends "base.html" %}
{% block content %}
<div class="container mt-5">
<div class="card shadow-sm">
<div class="card-header">
<h2 class="mb-0">Dodaj nowe urządzenie</h2>
<div class="container my-4">
<div class="card border-0 shadow-sm">
<div class="card-header bg-light">
<h4 class="mb-0">Dodaj nowe urządzenie</h4>
</div>
<div class="card-body">
<form method="POST">
@ -24,18 +24,16 @@
<input type="text" class="form-control" id="ssh_user" name="ssh_user" required>
</div>
<div class="mb-3">
<label for="ssh_key" class="form-label">
<b>Klucz prywatny</b> | Wklej wraz z <code>-----BEGIN RSA PRIVATE KEY-----</code> i <code>-----END RSA PRIVATE KEY-----</code><br>
Pozostaw puste jeśli ten RouterOS będzie używał <a href="{{ url_for('settings_view') }}">klucza globalnego</a>
</label>
<textarea class="form-control" id="ssh_key" name="ssh_key" rows="4"></textarea>
<label for="ssh_key" class="form-label"><b>Klucz prywatny</b></label><br>
<small>Wklej wraz z <code>-----BEGIN RSA PRIVATE KEY-----</code> i <code>-----END RSA PRIVATE KEY-----</code>. Jeśli pusty użyje klucza globalnego.</small>
<textarea class="form-control mt-2" id="ssh_key" name="ssh_key" rows="4"></textarea>
</div>
<div class="mb-3">
<label for="ssh_password" class="form-label"><b>Hasło SSH</b></label><br>
Jeśli podajesz klucz SSH lub zdefiniowany jest <a href="{{ url_for('settings_view') }}">klucz globalny</a>, to logowanie hasłem jest nieaktywne.
<input type="password" class="form-control" id="ssh_password" name="ssh_password">
<small>Jeśli jest klucz SSH lub klucz globalny, hasło może być ignorowane.</small>
<input type="password" class="form-control mt-2" id="ssh_password" name="ssh_password">
</div>
<button type="submit" class="btn btn-primary">Dodaj</button>
<button type="submit" class="btn btn-primary">Dodaj urządzenie</button>
</form>
</div>
</div>

View File

@ -1,52 +1,61 @@
{% extends "base.html" %}
{% block content %}
<div class="container mt-5">
<div class="card shadow-sm">
<div class="card-header">
<h2 class="mb-0">Zaawansowane ustawienia harmonogramu</h2>
<div class="container my-4">
<div class="card border-0 shadow-sm">
<div class="card-header bg-light">
<h4 class="mb-0">Zaawansowane ustawienia harmonogramu</h4>
</div>
<div class="card-body">
<form action="{{ url_for('advanced_schedule') }}" method="POST">
<div class="mb-3">
<div class="mb-3">
<label for="backup_retention_days" class="form-label">Próg retencji backupów (dni)</label>
<small>Usuwanie danych starszych niż ustawione w progu</small>
<small class="text-muted d-block mb-2">Usuwanie danych starszych niż ustawione w progu.</small>
<input type="number" class="form-control" id="backup_retention_days" name="backup_retention_days" value="{{ settings.backup_retention_days }}">
</div>
<div class="mb-3">
<label for="log_retention_days" class="form-label">Próg retencji logów (dni)</label>
<input type="number" class="form-control" id="log_retention_days" name="log_retention_days" value="{{ settings.log_retention_days }}">
</div>
<label for="retention_cron" class="form-label">Harmonogram retencji</label> <code>cron</code>
<div class="mb-3">
<label for="retention_cron" class="form-label">Harmonogram retencji <code>cron</code></label>
<div class="input-group">
<input type="text" class="form-control" id="retention_cron" name="retention_cron" value="{{ settings.retention_cron }}">
<button type="button" class="btn btn-outline-secondary" onclick="openCronModal('retention_cron')">Generuj cron</button>
</div>
<div class="form-text">Np. <code>0 */12 * * *</code> co 12 godzin</div>
<small class="text-muted">Np. <code>0 */12 * * *</code> co 12 godzin</small>
</div>
<div class="mb-3">
<label for="binary_cron" class="form-label">Harmonogram kopii zapasowych binarnych</label> <code>cron</code>
<label for="binary_cron" class="form-label">Harmonogram kopii zapasowych binarnych <code>cron</code></label>
<div class="input-group">
<input type="text" class="form-control" id="binary_cron" name="binary_cron" value="{{ settings.binary_cron|default('') }}">
<button type="button" class="btn btn-outline-secondary" onclick="openCronModal('binary_cron')">Generuj cron</button>
</div>
<div class="form-text">Np. <code>15 2 * * *</code> codziennie o 2:15</div>
<small class="text-muted">Np. <code>15 2 * * *</code> codziennie o 2:15</small>
</div>
<div class="mb-3">
<label for="export_cron" class="form-label">Harmonogram exportów (poleceń /export) <code>cron</code></label>
<label for="export_cron" class="form-label">Harmonogram exportów <code>cron</code></label>
<div class="input-group">
<input type="text" class="form-control" id="export_cron" name="export_cron" value="{{ settings.export_cron }}">
<button type="button" class="btn btn-outline-secondary" onclick="openCronModal('export_cron')">Generuj cron</button>
</div>
<div class="form-text">Np. <code>0 */12 * * *</code> co 12 godzin</div>
<small class="text-muted">Np. <code>0 */12 * * *</code> co 12 godzin</small>
</div>
<div class="mb-3 form-check">
<input type="checkbox" class="form-check-input" id="enable_auto_export" name="enable_auto_export" {% if settings.enable_auto_export %}checked{% endif %}>
<label class="form-check-label" for="enable_auto_export">Włącz automatyczny export</label>
</div>
<button type="submit" class="btn btn-primary">Zapisz ustawienia</button>
</form>
</div>
<div class="card-footer text-center">
<small class="text-muted">Ustawienia zostaną zapisane i użyte przez cron.</small>
</div>
</div>
</div>

View File

@ -3,8 +3,8 @@
<div class="container my-4">
<h2 class="text-center mb-4">Lista wszystkich backupów</h2>
<!-- Formularz filtrowania -->
<div class="card mb-4 shadow-sm">
<!-- Karta filtra -->
<div class="card mb-4 shadow-sm border-0">
<div class="card-body">
<form method="GET" action="{{ url_for('all_files') }}" class="row g-2">
<div class="col-md-4">
@ -29,11 +29,11 @@
</div>
</div>
<!-- Tabela z backupami -->
<div class="card shadow-sm mb-4">
<!-- Karta tabeli backupów -->
<div class="card shadow-sm border-0 mb-4">
<div class="card-body">
<div class="table-responsive">
<table class="table table-striped table-hover mb-0">
<table class="table table-striped table-hover align-middle">
<thead class="table-dark">
<tr>
<th style="width: 2%;"><input type="checkbox" id="select_all"></th>
@ -73,14 +73,13 @@
<td>{{ file.created_at.strftime("%Y-%m-%d %H:%M:%S") }}</td>
<td>{{ file.file_path|filesize }}</td>
<td>
<a href="{{ url_for('download_file', filename=file.file_path|basename) }}" class="btn btn-lg btn-info">
<a href="{{ url_for('download_file', filename=file.file_path|basename) }}" class="btn btn-sm btn-info">
<i class="bi bi-download"></i>
</a>
</td>
<td>
<form action="{{ url_for('send_by_email', backup_id=file.id) }}" method="POST" class="d-inline">
<input type="hidden" name="next" value="{{ url_for('all_files') }}">
<button type="submit" class="btn btn-lg btn-warning">
<button type="submit" class="btn btn-sm btn-warning">
<i class="bi bi-envelope"></i>
</button>
</form>
@ -88,8 +87,7 @@
<td>
{% if file.backup_type == 'binary' %}
<form action="{{ url_for('upload_backup', router_id=file.router.id, backup_id=file.id) }}" method="POST" class="d-inline">
<input type="hidden" name="next" value="{{ url_for('all_files') }}">
<button type="submit" class="btn btn-lg btn-secondary">
<button type="submit" class="btn btn-sm btn-secondary">
<i class="bi bi-upload"></i>
</button>
</form>
@ -99,7 +97,7 @@
</td>
<td>
{% if file.backup_type == 'export' %}
<a href="{{ url_for('view_export', backup_id=file.id) }}" class="btn btn-lg btn-outline-primary">
<a href="{{ url_for('view_export', backup_id=file.id) }}" class="btn btn-sm btn-outline-primary">
<i class="bi bi-eye"></i>
</a>
{% else %}
@ -108,8 +106,7 @@
</td>
<td>
<form action="{{ url_for('delete_backup', backup_id=file.id) }}" method="POST" class="d-inline" onsubmit="return confirm('Na pewno usunąć backup?');">
<input type="hidden" name="next" value="{{ url_for('all_files') }}">
<button type="submit" class="btn btn-lg btn-danger">
<button type="submit" class="btn btn-sm btn-danger">
<i class="bi bi-trash"></i>
</button>
</form>
@ -123,16 +120,17 @@
</div>
</div>
<!-- Formularz dla masowych akcji (jeden formularz) -->
<!-- Formularz dla masowych akcji (zaznaczone pliki) -->
<form id="mass_actions_form" action="{{ url_for('mass_actions') }}" method="POST" class="d-flex justify-content-end mb-4">
<button type="submit" name="action" value="download" class="btn btn-lg btn-success me-2">
<i class="bi bi-file-earmark-zip"></i> Pobierz zip zaznaczonych
<button type="submit" name="action" value="download" class="btn btn-success me-2">
<i class="bi bi-file-earmark-zip"></i> Pobierz zip
</button>
<button type="submit" name="action" value="delete" class="btn btn-lg btn-danger" onclick="return confirm('Na pewno usunąć zaznaczone pliki?');">
<i class="bi bi-trash"></i> Usuń zaznaczone pliki
<button type="submit" name="action" value="delete" class="btn btn-danger" onclick="return confirm('Na pewno usunąć zaznaczone pliki?');">
<i class="bi bi-trash"></i> Usuń zaznaczone
</button>
</form>
</div>
<script>
document.getElementById('select_all').addEventListener('change', function(e) {
var checkboxes = document.querySelectorAll('input[name="backup_id"]');

View File

@ -8,11 +8,12 @@
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
/* Poprawa kontrastu dla form-text w trybie ciemnym */
/* 1) Poprawa kontrastu dla form-text w trybie ciemnym */
.dark-mode .form-text {
color: #ccc !important;
}
/* Ogólne style trybu ciemnego */
/* 2) Ogólne style trybu ciemnego */
.dark-mode body {
background-color: #121212;
color: #ffffff;
@ -22,7 +23,7 @@
color: #ddd;
}
/* Nawigacja i menu */
/* 3) Nawigacja i menu w trybie ciemnym */
.dark-mode .navbar,
.dark-mode .navbar-nav,
.dark-mode .dropdown-menu {
@ -35,7 +36,7 @@
.dark-mode .navbar-nav .nav-link:hover {
color: #fff !important;
}
/* Nowa reguła dla elementów dropdown po najechaniu */
/* Dropdown :hover, :focus, active */
.dark-mode .dropdown-item:hover,
.dark-mode .dropdown-item:focus,
.dark-mode .dropdown-item.active {
@ -43,7 +44,7 @@
color: #fff !important;
}
/* Tabele */
/* 4) Tabele w trybie ciemnym */
.dark-mode .table {
background-color: #333 !important;
border-color: #444;
@ -67,7 +68,8 @@
color: #ddd !important;
border: 1px solid #555 !important;
}
/* Pola formularzy */
/* 5) Pola formularzy (input, select, textarea) w trybie ciemnym */
.dark-mode input,
.dark-mode textarea,
.dark-mode select {
@ -79,25 +81,41 @@
color: #ccc;
}
/* Przyciski - poprawiony kontrast dla btn-warning */
/* 6) Przyciski w trybie ciemnym: .btn-warning, .btn-secondary, .btn-outline-dark */
.dark-mode .btn-warning {
background-color: #d39e00;
border-color: #b38600;
color: #fff;
}
.dark-mode .btn-warning:hover {
background-color: #e6aa00 !important;
border-color: #c98f00 !important;
color: #fff !important;
}
.dark-mode .btn-secondary {
background-color: #444;
border-color: #555;
color: #fff;
}
.dark-mode .btn-secondary:hover {
background-color: #555 !important;
border-color: #888888 !important;
color: #fff !important;
}
.dark-mode .btn-outline-dark:hover {
background-color: #444 !important;
border-color: #888888 !important;
color: #fff !important;
}
/* Bloki (np. zawartość kontenera, karty) */
.dark-mode .block, .dark-mode .card {
/* 7) Karty i bloki w trybie ciemnym */
.dark-mode .block,
.dark-mode .card {
background-color: #171717;
color: #fff;
}
/* Stopka */
/* 8) Stopka */
.dark-mode footer {
background-color: #1e1e1e !important;
color: #fff !important;
@ -107,11 +125,13 @@
color: #212529;
}
/* Alerty pozostają bez zmian */
.diff-add { color: green; }
.diff-rem { color: red; }
/* 9) Nadpisanie .card-header.bg-light w trybie ciemnym */
.dark-mode .card-header.bg-light {
background-color: #333 !important;
color: #fff !important;
}
/* Dodatkowe nadpisanie styli diff2html w trybie ciemnym */
/* 10) Style diff2html w trybie ciemnym */
.dark-mode .d2h-wrapper,
.dark-mode .d2h-file-header,
.dark-mode .d2h-file-info,
@ -125,7 +145,7 @@
border-color: #444 !important;
}
/* Style trybu ciemnego dla modala */
/* 11) Modal w trybie ciemnym */
.dark-mode .modal-content {
background-color: #333;
color: #ddd;
@ -142,11 +162,12 @@
filter: invert(1);
}
/* Niestandardowy styl dla trybu jasnego ciemniejsze, szare menu */
/* 12) Niestandardowy styl trybu jasnego navbar */
.navbar-light.bg-custom-light {
background-color: #dcdcdc !important;
}
/* 13) DataTables w trybie ciemnym */
.dark-mode .dataTables_wrapper .dataTables_paginate .paginate_button {
background-color: #333 !important;
color: #fff !important;
@ -180,47 +201,45 @@
border-color: #555 !important;
box-shadow: none !important;
}
.dark-mode .btn-secondary:hover {
background-color: #555 !important;
border-color: #888888 !important;
color: #fff !important;
}
.dark-mode .btn-warning:hover {
background-color: #e6aa00 !important;
border-color: #c98f00 !important;
color: #fff !important;
}
.dark-mode .btn-outline-dark:hover {
background-color: #444 !important;
border-color: #888888 !important;
color: #fff !important;
}
/* 14) Drobne poprawki przycisków wylogowania */
.btn-logout {
color: #fff;
}
/* Ustawienia globalne */
/* 15) Główne ustawienia, flex layout */
html, body {
height: 100%;
margin: 0;
padding: 0;
}
body {
display: flex;
flex-direction: column;
min-height: 100vh;
}
/* Wrapper głównej zawartości, który rośnie, aby wypełnić przestrzeń */
.wrapper {
main.container {
flex: 1;
}
/* 16) Alerty (diff-add, diff-rem) pozostawione bez zmian */
.diff-add { color: green; }
.diff-rem { color: red; }
.dark-mode .text-muted {
color: #aaa !important; /* zamiast #aaa możesz wybrać #bbb, #ccc itp. */
}
</style>
{% block head %}{% endblock %}
</head>
<body>
<nav class="navbar navbar-expand-lg {% if session.get('dark_mode', True) %}navbar-dark bg-dark{% else %}navbar-light bg-custom-light{% endif %} mb-4">
<body class="d-flex flex-column">
<!-- Navbar -->
<nav class="navbar navbar-expand-lg
{% if session.get('dark_mode', True) %}navbar-dark bg-dark{% else %}navbar-light bg-custom-light{% endif %}
mb-4">
<div class="container-fluid">
<!-- <a href="{{ url_for('dashboard') }}" class="navbar-brand">Backup RouterOS</a> -->
<a href="/" class="navbar-brand">Backup RouterOS</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNavDropdown"
aria-controls="navbarNavDropdown" aria-expanded="false" aria-label="Toggle navigation">
@ -267,18 +286,22 @@
</li>
</ul>
{% endif %}
<!-- Prawa strona navbaru -->
<ul class="navbar-nav ms-auto align-items-center">
<!-- Przełącznik trybu ciemnego -->
<li class="nav-item me-2">
<form action="{{ url_for('toggle_dark_mode') }}" method="GET" class="d-flex align-items-center">
<div class="form-check form-switch mb-0">
<input class="form-check-input" type="checkbox" id="darkModeSwitch" onchange="this.form.submit()" {% if session.get('dark_mode', True) %}checked{% endif %}>
<label class="form-check-label" for="darkModeSwitch">Tryb ciemny</label>
<input class="form-check-input" type="checkbox" id="darkModeSwitch"
onchange="this.form.submit()"
{% if session.get('dark_mode', True) %}checked{% endif %}>
<label class="form-check-label ms-1" for="darkModeSwitch" style="user-select:none;">Ciemny</label>
</div>
</form>
</li>
{% if session.get('user_id') %}
<!-- Opcje dla zalogowanego użytkownika -->
<li class="nav-item">
<a class="nav-link btn btn-alert ms-2 btn-logout" href="{{ url_for('change_password') }}">Zmień hasło</a>
</li>
@ -286,7 +309,6 @@
<a class="nav-link btn btn-danger ms-2 btn-logout" href="{{ url_for('logout') }}">Wyloguj</a>
</li>
{% else %}
<!-- Opcje logowania i rejestracji dla niezalogowanych -->
<li class="nav-item">
<a class="nav-link btn btn-success ms-2" href="{{ url_for('login') }}">Zaloguj się</a>
</li>
@ -299,19 +321,28 @@
</div>
</nav>
<div class="container">
{% with messages = get_flashed_messages() %}
<!-- Główna zawartość -->
<main class="container mb-5">
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
<div class="alert alert-info">
{% for msg in messages %}
<div>{{ msg }}</div>
{% endfor %}
{% for category, msg in messages %}
{% set bs_cat = bootstrap_alert_category(category) %}
<div class="alert alert-{{ bs_cat }} alert-dismissible fade show" role="alert">
{{ msg }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{% endfor %}
{% endif %}
{% endwith %}
{% block content %}{% endblock %}
</div>
</main>
<!-- Modal Test Połączenia -->
<div class="modal fade" id="testConnectionModal" tabindex="-1" aria-labelledby="testConnectionModalLabel" aria-hidden="true">
@ -322,40 +353,25 @@
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Zamknij"></button>
</div>
<div class="modal-body" id="testConnectionModalBody">
<!-- Zawartość zostanie załadowana przez AJAX -->
<!-- Zawartość ładowana przez AJAX -->
</div>
</div>
</div>
</div>
<!-- Stopka -->
<footer class="footer py-3 mt-auto">
<div class="container text-center">
<span>&copy; 2025 Mateusz Gruszczyński, linuxiarz.pl</span>
</div>
</footer>
<!-- Bootstrap Bundle JS -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<!-- Dodatkowe skrypty -->
<script>
function ajaxExport(router_id) {
fetch("/router/" + router_id + "/export", {
method: "POST",
headers: {"X-Requested-With": "XMLHttpRequest"}
})
.then(response => response.json())
.then(data => {
if(data.status === "success"){
alert("Eksport wykonany: " + data.message);
} else {
alert("Błąd eksportu: " + data.message);
}
})
.catch(error => {
console.error("Błąd AJAX:", error);
alert("Wystąpił błąd.");
});
}
</script>
<script>
// Funkcja do wczytywania modalu testu połączenia
function openTestConnectionModal(routerId) {
fetch('/router/' + routerId + '/test_connection?modal=1')
.then(response => response.text())
@ -370,6 +386,7 @@
});
}
</script>
{% block scripts %}{% endblock %}
</body>
</html>

View File

@ -1,11 +1,11 @@
{% extends "base.html" %}
{% block content %}
<div class="container">
<div class="row justify-content-center align-items-center" style="min-height: 100vh;">
<div class="container my-5">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card shadow">
<div class="card-header text-center">
<h2>Zmień hasło</h2>
<div class="card border-0 shadow-sm">
<div class="card-header bg-light text-center">
<h4 class="mb-0">Zmień hasło</h4>
</div>
<div class="card-body">
<form method="POST">

View File

@ -5,7 +5,7 @@
<!-- Wiersz akcji ogólnych -->
<div class="row mb-4">
<div class="col-md-12 text-center">
<div class="col text-center">
<a href="{{ url_for('routers_list') }}" class="btn btn-lg btn-outline-primary">
<i class="bi bi-hdd-network"></i> Zobacz routery
</a>
@ -53,14 +53,14 @@
<div class="col-md-6 d-flex justify-content-center">
<form action="{{ url_for('export_all_routers') }}" method="POST">
<button type="submit" class="btn btn-lg btn-outline-success">
<i class="bi bi-arrow-down-circle"></i> Eksport dla wszystkich routerów
<i class="bi bi-arrow-down-circle"></i> Eksport wszystkich routerów
</button>
</form>
</div>
<div class="col-md-6 d-flex justify-content-center">
<form action="{{ url_for('backup_all_routers') }}" method="POST">
<button type="submit" class="btn btn-lg btn-outline-secondary">
<i class="bi bi-cloud-download"></i> Backup binarny dla wszystkich routerów
<i class="bi bi-cloud-download"></i> Backup binarny wszystkich routerów
</button>
</form>
</div>
@ -73,11 +73,11 @@
{% else %}
{% set success_percent = 0 %}
{% endif %}
<div class="card mb-4 shadow-sm">
<div class="card mb-4 shadow-sm border-0">
<div class="card-body">
<h5 class="card-title">Statystyki operacji</h5>
<p>Udane operacje: {{ success_ops }}, Nieudane operacje: {{ failure_ops }}</p>
<div class="progress">
<div class="progress mb-2">
<div class="progress-bar bg-success" role="progressbar" style="width: {{ success_percent }}%;" aria-valuenow="{{ success_percent }}" aria-valuemin="0" aria-valuemax="100">
{{ success_percent }}%
</div>
@ -89,7 +89,7 @@
</div>
<!-- Log operacji -->
<div class="card shadow-sm mb-4">
<div class="card shadow-sm border-0 mb-4">
<div class="card-body">
<h5 class="card-title">
Log operacji
@ -114,8 +114,8 @@
</div>
</div>
<!-- Dodatkowe statystyki przeniesione na sam dół, pod logami -->
<div class="card shadow-sm">
<!-- Dodatkowe statystyki -->
<div class="card shadow-sm border-0">
<div class="card-body">
<h5 class="card-title">Dodatkowe statystyki</h5>
<div class="row">
@ -131,6 +131,5 @@
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -1,19 +1,25 @@
{% extends "base.html" %}
{% block content %}
<div class="container my-4">
<h2>Porównanie: {{ backup1.file_path|basename }} vs {{ backup2.file_path|basename }}</h2>
<hr>
<div class="card border-0 shadow-sm">
<div class="card-header bg-light">
<h4 class="mb-0">
Porównanie: {{ backup1.file_path|basename }} vs {{ backup2.file_path|basename }}
</h4>
</div>
<div class="card-body">
<div id="diffContainer"></div>
<a href="{{ url_for('router_details', router_id=backup1.router_id) }}" class="btn btn-secondary mt-3">Powrót</a>
</div>
</div>
</div>
<!-- Ładujemy nowszą wersję diff2html -->
<!-- diff2html resources -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/diff2html@3.4.4/bundles/css/diff2html.min.css" />
<script src="https://cdn.jsdelivr.net/npm/diff2html@3.4.4/bundles/js/diff2html.min.js"></script>
<script>
document.addEventListener("DOMContentLoaded", function() {
// Escapowany diff_text z serwera
var diffText = `{{ diff_text|e }}`;
var targetElement = document.getElementById("diffContainer");
var configuration = {
@ -24,8 +30,8 @@
var diffHtml = Diff2Html.html(diffText, configuration);
targetElement.innerHTML = diffHtml;
// Jeśli tryb ciemny jest aktywny, dołącz dodatkowe style dla diff2html
if(document.documentElement.classList.contains('dark-mode')) {
// Dark mode styl dla diff2html, jeśli potrzebne
if(document.body.classList.contains('dark-mode')) {
var darkStyle = document.createElement('style');
darkStyle.textContent = `
.d2h-wrapper { background-color: #1e1e1e; color: #fff; }

View File

@ -1,8 +1,10 @@
{% extends "base.html" %}
{% block content %}
<div class="container my-4">
<h2 class="text-center mb-4">Porównanie backupów (Diff)</h2>
<div class="card shadow-sm">
<div class="card shadow-sm border-0">
<div class="card-header bg-light d-flex align-items-center">
<h4 class="mb-0">Porównanie backupów (Diff)</h4>
</div>
<div class="card-body">
<form action="{{ url_for('diff_selector') }}" method="POST" id="diffForm">
<div class="row mb-3">
@ -29,8 +31,10 @@
</select>
</div>
</div>
<div class="text-center">
<button type="submit" class="btn btn-primary btn-lg">Porównaj backupy</button>
<div class="text-center mt-4">
<button type="submit" class="btn btn-primary btn-lg">
Porównaj backupy
</button>
</div>
</form>
</div>
@ -41,7 +45,7 @@
document.getElementById("diffForm").addEventListener("submit", function(event) {
var backup1 = document.getElementById("backup1").value;
var backup2 = document.getElementById("backup2").value;
if(backup1 === backup2) {
if (backup1 === backup2) {
event.preventDefault();
alert("Wybierz dwa różne backupy do porównania.");
}

View File

@ -1,9 +1,9 @@
{% extends "base.html" %}
{% block content %}
<div class="container mt-5">
<div class="card shadow-sm">
<div class="card-header">
<h2 class="mb-0">Edycja urządzenia</h2>
<div class="container my-4">
<div class="card border-0 shadow-sm">
<div class="card-header bg-light">
<h4 class="mb-0">Edycja urządzenia</h4>
</div>
<div class="card-body">
<form method="POST">
@ -25,15 +25,15 @@
</div>
<div class="mb-3">
<label for="ssh_key" class="form-label">
<label for="ssh_password" class="form-label"><b>Klucz prywatny</b></label> | Wklej wraz z <code>-----BEGIN RSA PRIVATE KEY-----</code> i <code>-----END RSA PRIVATE KEY-----</code><br>
Pozostaw puste jeśli ten RouterOS będzie używał <a href="{{ url_for('settings_view') }}">klucza globalnego</a>
</label>
<textarea class="form-control" id="ssh_key" name="ssh_key" rows="4">{{ router.ssh_key }}</textarea>
<b>Klucz prywatny</b>
</label><br>
<small>Wklej wraz z <code>-----BEGIN RSA PRIVATE KEY-----</code> i <code>-----END RSA PRIVATE KEY-----</code>. Jeśli pusty użyje klucza globalnego.</small>
<textarea class="form-control mt-2" id="ssh_key" name="ssh_key" rows="4">{{ router.ssh_key }}</textarea>
</div>
<div class="mb-3">
<label for="ssh_password" class="form-label"><b>Hasło SSH</b></label><br>
Jeśli podajesz klucz SSH lub zdefiniowany jest <a href="{{ url_for('settings_view') }}">klucz globalny</a>, to logowanie hasłem jest nieaktywne.
<input type="password" class="form-control" id="ssh_password" name="ssh_password" value="{{ router.ssh_password }}">
<small>Jeśli jest klucz SSH lub klucz globalny, hasło może być ignorowane.</small>
<input type="password" class="form-control mt-2" id="ssh_password" name="ssh_password" value="{{ router.ssh_password }}">
</div>
<button type="submit" class="btn btn-success">Zapisz zmiany</button>
</form>

View File

@ -1,23 +1,23 @@
{% extends "base.html" %}
{% block head %}
{% endblock %}
{% block head %}{% endblock %}
{% block content %}
<div class="container">
<div class="row full-height-row justify-content-center align-items-center">
<div class="container my-5">
<div class="row justify-content-center align-items-center">
<div class="col-md-6">
<div class="card shadow">
<div class="card-header text-center">
<h2>Zaloguj się</h2>
<div class="card shadow border-0">
<div class="card-header bg-light text-center">
<h4 class="mb-0">Zaloguj się</h4>
</div>
<div class="card-body">
<form action="{{ url_for('login') }}" method="POST">
<div class="mb-3">
<label for="username" class="form-label">Nazwa użytkownika</label>
<input type="text" class="form-control" id="username" name="username" placeholder="Wpisz nazwę użytkownika">
<input type="text" class="form-control" id="username" name="username" placeholder="Wpisz nazwę użytkownika" required>
</div>
<div class="mb-3">
<label for="password" class="form-label">Hasło</label>
<input type="password" class="form-control" id="password" name="password" placeholder="Wpisz hasło">
<input type="password" class="form-control" id="password" name="password" placeholder="Wpisz hasło" required>
</div>
<div class="d-grid">
<button type="submit" class="btn btn-primary">Zaloguj się</button>
@ -25,7 +25,10 @@
</form>
</div>
<div class="card-footer text-center">
<a href="{{ url_for('register') }}">Nie masz konta? Zarejestruj się</a>
<small>
Nie masz konta?
<a href="{{ url_for('register') }}">Zarejestruj się</a>
</small>
</div>
</div>
</div>

View File

@ -10,12 +10,12 @@
<div class="container my-4">
<h2 class="text-center mb-4">Historia logów operacji</h2>
<!-- Formularz usuwania logów starszych od podanej liczby dni -->
<div class="card mb-4 shadow-sm">
<!-- Karta z usuwaniem logów -->
<div class="card mb-4 shadow-sm border-0">
<div class="card-body">
<form action="{{ url_for('delete_old_logs') }}" method="POST" class="row g-2 align-items-center">
<div class="col-auto">
<label for="delete_days" class="col-form-label">Usuń logi starsze niż:</label>
<label for="delete_days" class="col-form-label">Usuń logi starsze niż (dni):</label>
</div>
<div class="col-auto">
<input type="number" class="form-control" id="delete_days" name="delete_days" min="1" placeholder="Liczba dni" required>
@ -28,7 +28,7 @@
</div>
<!-- Tabela logów -->
<div class="card shadow-sm mb-4">
<div class="card shadow-sm border-0 mb-4">
<div class="card-body">
<table id="logsTable" class="table table-striped table-bordered">
<thead class="table-dark">
@ -50,14 +50,14 @@
</div>
<div class="text-center mt-3">
<a href="{{ url_for('dashboard') }}" class="btn btn-outline-primary">Powrót do Dashboard</a>
<a href="{{ url_for('dashboard') }}" class="btn btn-outline-primary">Powrót do Dashboardu</a>
</div>
</div>
{% endblock %}
{% block scripts %}
{{ super() }}
<!-- jQuery (jeśli nie jest już dołączone w base.html) -->
<!-- jQuery -->
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<!-- DataTables JS -->
<script src="https://cdn.datatables.net/1.13.4/js/jquery.dataTables.min.js"></script>
@ -66,7 +66,10 @@
$(document).ready(function() {
$('#logsTable').DataTable({
responsive: true,
order: [[0, 'desc']]
order: [[0, 'desc']],
language: {
url: '//cdn.datatables.net/plug-ins/1.13.4/i18n/pl.json'
}
});
});
</script>

View File

@ -1,23 +1,23 @@
{% extends "base.html" %}
{% block head %}
{% endblock %}
{% block head %}{% endblock %}
{% block content %}
<div class="container">
<div class="row full-height-row justify-content-center align-items-center">
<div class="container my-5">
<div class="row justify-content-center align-items-center">
<div class="col-md-6">
<div class="card shadow">
<div class="card-header text-center">
<h2>Rejestracja</h2>
<div class="card shadow border-0">
<div class="card-header bg-light text-center">
<h4 class="mb-0">Rejestracja</h4>
</div>
<div class="card-body">
<form action="{{ url_for('register') }}" method="POST">
<div class="mb-3">
<label for="username" class="form-label">Nazwa użytkownika</label>
<input type="text" class="form-control" id="username" name="username" placeholder="Wpisz nazwę użytkownika">
<input type="text" class="form-control" id="username" name="username" placeholder="Wpisz nazwę użytkownika" required>
</div>
<div class="mb-3">
<label for="password" class="form-label">Hasło</label>
<input type="password" class="form-control" id="password" name="password" placeholder="Wpisz hasło">
<input type="password" class="form-control" id="password" name="password" placeholder="Wpisz hasło" required>
</div>
<div class="d-grid">
<button type="submit" class="btn btn-primary">Zarejestruj się</button>
@ -25,7 +25,10 @@
</form>
</div>
<div class="card-footer text-center">
<a href="{{ url_for('login') }}">Masz już konto? Zaloguj się</a>
<small>
Masz już konto?
<a href="{{ url_for('login') }}">Zaloguj się</a>
</small>
</div>
</div>
</div>

View File

@ -1,17 +1,17 @@
{% extends "base.html" %}
{% block content %}
<div class="text-end mb-3">
<div class="container my-4">
<div class="mb-3 text-end">
{% if current_view == 'v1' %}
<a href="{{ url_for('router_details', router_id=router.id, view='v2') }}" class="btn btn-outline-secondary">Przełącz na widok v2</a>
{% else %}
<a href="{{ url_for('router_details', router_id=router.id, view='v1') }}" class="btn btn-outline-secondary">Przełącz na widok v1</a>
{% endif %}
</div>
<div class="container my-4">
<!-- Informacje o routerze -->
<div class="card shadow-sm mb-4">
<div class="card-header">
<h2 class="mb-0">Router: {{ router.name }}</h2>
</div>
<div class="card border-0 shadow-sm mb-4">
<div class="card-header bg-light">
<h4 class="mb-0">Router: {{ router.name }}</h4>
</div>
<div class="card-body">
<p>
@ -20,7 +20,6 @@
<strong>SSH User:</strong> {{ router.ssh_user }}
</p>
<div class="d-flex flex-wrap gap-2">
<!-- Mniejsze przyciski górne -->
<form action="{{ url_for('router_export', router_id=router.id) }}" method="POST" class="d-inline">
<button type="submit" class="btn btn-primary btn-sm">Wykonaj /export</button>
</form>
@ -35,16 +34,21 @@
<!-- Zakładki z backupami -->
<ul class="nav nav-tabs" id="routerTab" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" id="export-tab" data-bs-toggle="tab" data-bs-target="#export" type="button" role="tab" aria-controls="export" aria-selected="true">Pliki /export</button>
<button class="nav-link active" id="export-tab" data-bs-toggle="tab" data-bs-target="#export" type="button" role="tab" aria-controls="export" aria-selected="true">
Pliki /export
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="binary-tab" data-bs-toggle="tab" data-bs-target="#binary" type="button" role="tab" aria-controls="binary" aria-selected="false">Pliki binarne</button>
<button class="nav-link" id="binary-tab" data-bs-toggle="tab" data-bs-target="#binary" type="button" role="tab" aria-controls="binary" aria-selected="false">
Pliki binarne
</button>
</li>
</ul>
<div class="tab-content" id="routerTabContent">
<!-- Zakładka /export -->
<div class="tab-pane fade show active" id="export" role="tabpanel" aria-labelledby="export-tab">
<div class="card mt-3 shadow-sm">
<div class="card mt-3 shadow-sm border-0">
<div class="card-body">
{% if export_backups %}
<!-- Formularz masowych akcji dla eksportów -->
@ -55,9 +59,9 @@
</button>
</div>
</form>
<!-- Tabela z eksportami -->
<div class="table-responsive">
<table class="table table-bordered table-striped">
<table class="table table-bordered table-striped align-middle">
<thead class="table-dark">
<tr>
<th style="width: 3%;"><input type="checkbox" id="select_all_export"></th>
@ -124,9 +128,10 @@
</div>
</div>
</div>
<!-- Zakładka backupów binarnych -->
<div class="tab-pane fade" id="binary" role="tabpanel" aria-labelledby="binary-tab">
<div class="card mt-3 shadow-sm">
<div class="card mt-3 shadow-sm border-0">
<div class="card-body">
{% if binary_backups %}
<!-- Formularz masowych akcji dla backupów binarnych -->
@ -137,9 +142,9 @@
</button>
</div>
</form>
<!-- Tabela z backupami binarnymi -->
<div class="table-responsive">
<table class="table table-bordered table-striped">
<table class="table table-bordered table-striped align-middle">
<thead class="table-dark">
<tr>
<th style="width: 3%;"><input type="checkbox" id="select_all_binary"></th>
@ -159,7 +164,6 @@
<input type="checkbox" name="backup_id" value="{{ b.id }}" form="binary_mass_actions_form">
</td>
<td>
<!-- Dodaj tooltip z sumą kontrolną -->
<span data-bs-toggle="tooltip" title="Checksum: {{ b.checksum }}">{{ b.file_path|basename }}</span>
</td>
<td>{{ b.file_path|filesize }}</td>
@ -216,7 +220,7 @@ document.getElementById('select_all_binary').addEventListener('change', function
checkboxes.forEach(cb => cb.checked = e.target.checked);
});
// Inicjalizacja zakładek Bootstrap (jeśli nie są już inicjowane globalnie)
// Inicjalizacja zakładek Bootstrap
var triggerTabList = [].slice.call(document.querySelectorAll('#routerTab button'));
triggerTabList.forEach(function (triggerEl) {
var tabTrigger = new bootstrap.Tab(triggerEl);
@ -226,7 +230,7 @@ triggerTabList.forEach(function (triggerEl) {
});
});
// Inicjalizacja tooltipów Bootstrap
// Inicjalizacja tooltipów
var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
tooltipTriggerList.forEach(function (tooltipTriggerEl) {
new bootstrap.Tooltip(tooltipTriggerEl);

View File

@ -1,14 +1,16 @@
{% extends "base.html" %}
{% block content %}
<div class="container my-4">
<div class="d-flex justify-content-between align-items-center mb-3">
<h2>Lista urządzeń</h2>
<div class="card border-0 shadow-sm">
<div class="card-header bg-light d-flex justify-content-between align-items-center">
<h4 class="mb-0">Lista urządzeń</h4>
<a href="{{ url_for('add_router') }}" class="btn btn-success">
<i class="bi bi-plus-lg"></i> Dodaj nowe urządzenie
</a>
</div>
<div class="card-body">
<div class="table-responsive-sm">
<table class="table table-striped table-hover">
<table class="table table-striped table-hover align-middle">
<thead class="table-primary">
<tr>
<th>Nazwa</th>
@ -48,8 +50,8 @@
<a href="{{ url_for('edit_router', router_id=router.id) }}" class="btn btn-sm btn-warning">
<i class="bi bi-pencil"></i> Edytuj
</a>
<form action="{{ url_for('delete_router', router_id=router.id) }}" method="POST" class="d-inline">
<button type="submit" class="btn btn-sm btn-danger" onclick="return confirm('Na pewno usunąć urządzenie?');">
<form action="{{ url_for('delete_router', router_id=router.id) }}" method="POST" class="d-inline" onsubmit="return confirm('Na pewno usunąć urządzenie?');">
<button type="submit" class="btn btn-sm btn-danger">
<i class="bi bi-trash"></i> Usuń
</button>
</form>
@ -59,5 +61,7 @@
</tbody>
</table>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -1,15 +1,16 @@
{% extends "base.html" %}
{% block content %}
<div class="container my-5">
<div class="card shadow-sm">
<div class="card-header">
<h2 class="mb-0">Ustawienia globalne</h2>
<div class="container my-4">
<div class="card border-0 shadow-sm">
<div class="card-header bg-light">
<h4 class="mb-0">Ustawienia globalne</h4>
</div>
<div class="card-body">
<form method="POST">
<!-- Sekcja Pushover -->
<div class="mb-4">
<h4 class="mb-3">Powiadomienia - Pushover</h4>
<h5 class="mb-3">Powiadomienia - Pushover</h5>
<div class="mb-3">
<label for="pushover_token" class="form-label">Pushover Token</label>
<input type="text" class="form-control" id="pushover_token" name="pushover_token" value="{{ settings.pushover_token }}">
@ -24,10 +25,11 @@
</div>
</div>
<hr>
<!-- Sekcja SMTP -->
<div class="mb-4">
<h4 class="mb-3">Powiadomienia - SMTP (e-mail)</h4>
<div class="mb-3 form-check">
<h5 class="mb-3">Powiadomienia - SMTP (e-mail)</h5>
<div class="form-check mb-3">
<input type="checkbox" class="form-check-input" id="smtp_notifications_enabled" name="smtp_notifications_enabled" {% if settings.smtp_notifications_enabled %}checked{% endif %}>
<label class="form-check-label" for="smtp_notifications_enabled">Włącz powiadomienia SMTP</label>
</div>
@ -47,27 +49,27 @@
<label for="smtp_password" class="form-label">SMTP Hasło</label>
<input type="password" class="form-control" id="smtp_password" name="smtp_password" value="{{ settings.smtp_password }}">
</div>
<!-- Nowe pole: docelowy adres e-mail -->
<div class="mb-3">
<label for="recipient_email" class="form-label">Adres e-mail docelowy</label>
<input type="email" class="form-control" id="recipient_email" name="recipient_email" value="{{ settings.recipient_email }}">
</div>
</div>
<hr>
<!-- Sekcja globalnego klucza SSH -->
<div class="mb-4">
<h4 class="mb-3">Globalny klucz SSH</h4>
<div class="mb-3">
<h5 class="mb-3">Globalny klucz SSH</h5>
<label for="global_ssh_key" class="form-label">
Wklej wraz z <code>-----BEGIN RSA PRIVATE KEY-----</code> i <code>-----END RSA PRIVATE KEY-----</code>
</label>
<textarea class="form-control" id="global_ssh_key" name="global_ssh_key" rows="4">{{ settings.global_ssh_key }}</textarea>
</div>
</div>
<div class="d-grid">
<button type="submit" class="btn btn-primary btn-lg">Zapisz ustawienia</button>
</div>
</form>
<!-- Przycisk do testowania powiadomień -->
<div class="mt-4 text-center">
<form method="POST" action="{{ url_for('test_email') }}" class="d-inline">
@ -78,8 +80,12 @@
</form>
</div>
</div>
<div class="card-footer text-center">
<p>Ustawienia dotyczące backupu oraz harmonogramu CRON znajdują się na <a href="{{ url_for('advanced_schedule') }}">zaawansowanych ustawieniach harmonogramu</a>.</p>
<p class="mb-0">
Ustawienia harmonogramu i retencji:
<a href="{{ url_for('advanced_schedule') }}">Zaawansowane ustawienia</a>
</p>
</div>
</div>
</div>

View File

@ -1,11 +1,19 @@
{% extends "base.html" %}
{% block content %}
<div class="container my-4">
<h2>Podgląd eksportu: {{ backup.file_path|basename }}</h2>
<hr>
<div class="card border-0 shadow">
<div class="card-header bg-light">
<h4 class="mb-0">
Podgląd eksportu: {{ backup.file_path|basename }}
</h4>
</div>
<div class="card-body">
<textarea id="exportEditor" readonly>{{ content|e }}</textarea>
<br>
<div class="mt-3">
<a href="{{ next_url }}" class="btn btn-secondary">Powrót</a>
</div>
</div>
</div>
</div>
<!-- CodeMirror CSS -->
@ -28,8 +36,7 @@
lineNumbers: true,
readOnly: true
});
// Dopasowanie rozmiaru edytora do zawartości
editor.setSize("100%", "588px");
editor.setSize("100%", "600px");
});
</script>
{% endblock %}