Compare commits

...

59 Commits

Author SHA1 Message Date
Mateusz Gruszczyński
528b49f78e fix w deployu na mikrotik 2025-03-11 09:21:33 +01:00
Mateusz Gruszczyński
46d9635d1e poprawka w menu 2025-03-11 09:18:54 +01:00
Mateusz Gruszczyński
a6d252e627 poprawka w przywracaniu backupu 2025-03-11 09:17:03 +01:00
Mateusz Gruszczyński
a754dcea53 zmiany w regex 2025-03-11 08:11:11 +01:00
Mateusz Gruszczyński
3d5189d9e3 fix edycji ajax 2025-03-10 23:57:23 +01:00
Mateusz Gruszczyński
1ed40e96dd duzo fixow i optymalizacji 2025-03-10 23:32:59 +01:00
Mateusz Gruszczyński
a8954020f6 duzo fixow i optymalizacji 2025-03-10 23:32:43 +01:00
Mateusz Gruszczyński
c4b5e703f3 remove dynamic hosts 2025-03-10 16:09:28 +01:00
Mateusz Gruszczyński
52694519d7 remove dynamic hosts 2025-03-10 16:04:57 +01:00
Mateusz Gruszczyński
7706a521b4 remove dynamic hosts 2025-03-10 16:04:04 +01:00
Mateusz Gruszczyński
125599e86a proba naprawy 2025-03-10 15:29:41 +01:00
Mateusz Gruszczyński
88583dba31 poprawka w format_host 2025-03-10 15:21:27 +01:00
Mateusz Gruszczyński
1a1e84ad11 poprawka w format_host 2025-03-10 15:15:00 +01:00
Mateusz Gruszczyński
36af672735 poprawka w format_host 2025-03-10 15:11:30 +01:00
Mateusz Gruszczyński
847e46bf02 poprawka w format_host 2025-03-10 14:53:42 +01:00
Mateusz Gruszczyński
e0f739e9a9 poprawka w format_host 2025-03-10 14:19:33 +01:00
Mateusz Gruszczyński
5f4973bfa4 poprawka w format_host 2025-03-10 12:22:16 +01:00
Mateusz Gruszczyński
6fd28d5765 poprawka w format_host 2025-03-10 12:15:31 +01:00
Mateusz Gruszczyński
6f4c1b56ad poprawka w deploy_user 2025-03-10 12:10:30 +01:00
Mateusz Gruszczyński
9cffcb0ca6 nowa funkcja / domyslne wpisy 2025-03-10 12:04:22 +01:00
Mateusz Gruszczyński
7fb796357f zmianu w css i poprawki w server-list 2025-03-09 23:42:29 +01:00
Mateusz Gruszczyński
146e0f5ab2 obsluga 500 2025-03-09 15:22:45 +01:00
Mateusz Gruszczyński
ef31984144 poprawka w formacie loga auto backupu 2025-03-09 15:09:10 +01:00
Mateusz Gruszczyński
ec364aac0c NEW MODAL FIX OSTATECZNY1 2025-03-09 14:57:26 +01:00
Mateusz Gruszczyński
b516ee9e52 NEW MODAL FIX OSTATECZNY1 2025-03-09 14:54:22 +01:00
Mateusz Gruszczyński
5189c60b1f NEW MODAL informacje o serwerze 2025-03-09 14:53:11 +01:00
Mateusz Gruszczyński
38e62df17d NEW MODAL informacje o serwerze 2025-03-09 14:52:35 +01:00
Mateusz Gruszczyński
75a9e2399b NEW MODAL informacje o serwerze 2025-03-09 14:51:52 +01:00
Mateusz Gruszczyński
d003683cd2 NEW MODAL informacje o serwerze 2025-03-09 14:50:11 +01:00
Mateusz Gruszczyński
362fe6338f NEW MODAL informacje o serwerze 2025-03-09 14:45:05 +01:00
Mateusz Gruszczyński
3d35f2bdee NEW MODAL informacje o serwerze 2025-03-09 14:44:20 +01:00
Mateusz Gruszczyński
7dc099a2b9 NEW MODAL informacje o serwerze 2025-03-09 14:43:42 +01:00
Mateusz Gruszczyński
36915ace4b NEW MODAL informacje o serwerze 2025-03-09 14:42:39 +01:00
Mateusz Gruszczyński
9c3cfd644a NEW MODAL informacje o serwerze 2025-03-09 14:40:48 +01:00
Mateusz Gruszczyński
389e98a4fc NEW MODAL informacje o serwerze 2025-03-09 14:39:48 +01:00
Mateusz Gruszczyński
48cf5841f2 NEW MODAL informacje o serwerze 2025-03-09 14:37:27 +01:00
Mateusz Gruszczyński
0a80e38564 NEW MODAL informacje o serwerze 2025-03-09 14:36:31 +01:00
Mateusz Gruszczyński
9714954828 NEW MODAL informacje o serwerze 2025-03-09 14:29:38 +01:00
Mateusz Gruszczyński
67c6e8a92e NEW MODAL informacje o serwerze 2025-03-09 14:25:22 +01:00
Mateusz Gruszczyński
7d4a856a03 NEW MODAL informacje o serwerze 2025-03-09 14:20:21 +01:00
Mateusz Gruszczyński
942ce73975 NEW MODAL informacje o serwerze 2025-03-09 14:17:17 +01:00
Mateusz Gruszczyński
985d3465c8 wyswietlanie hostow w /clear-servers 2025-03-09 11:24:01 +01:00
Mateusz Gruszczyński
3d2b05676b wyswietlanie hostow w /clear-servers 2025-03-09 11:20:28 +01:00
Mateusz Gruszczyński
1dc4300881 fixy i now funkcje (przebudowa rozwiazywania nazw) 2025-03-09 10:53:28 +01:00
Mateusz Gruszczyński
f8a9dd451b fixy z listy todo 2025-03-09 00:17:22 +01:00
Mateusz Gruszczyński
02088c1782 fixy z listy todo 2025-03-09 00:06:15 +01:00
Mateusz Gruszczyński
a5c59f8a64 fixy z listy todo 2025-03-09 00:03:29 +01:00
Mateusz Gruszczyński
2904209332 naprawa loga w /backup-all 2025-03-08 15:42:56 +01:00
Mateusz Gruszczyński
55f9cbfb9a import i eksport serwerów z nowymi danymi 2025-03-08 15:39:31 +01:00
Mateusz Gruszczyński
4ac60ee541 fixy/nowe funkcje 2025-03-08 15:23:56 +01:00
Mateusz Gruszczyński
4827f611b6 fixes 2025-03-07 14:41:17 +01:00
Mateusz Gruszczyński
f151a8a050 fix in deploy 2025-03-07 08:53:31 +01:00
Mateusz Gruszczyński
0b3bf4a654 fix in deploy 2025-03-07 08:51:06 +01:00
Mateusz Gruszczyński
aa0a0a0025 git add templatesgit add templates OBSLUGA DEMONAgit add templates 2025-03-06 23:52:44 +01:00
Mateusz Gruszczyński
cae5ed787d fixy i usprwnienia 2025-03-06 14:12:48 +01:00
Mateusz Gruszczyński
c4b753d4bd fixy i usprwnienia 2025-03-06 10:38:12 +01:00
Mateusz Gruszczyński
4979365617 poprawki ustawieniach 2025-03-04 22:28:59 +01:00
Mateusz Gruszczyński
8023794451 poprawki w zadaniach zaplanowanych 2025-03-04 14:00:40 +01:00
Mateusz Gruszczyński
982a5049d7 poprawki w zadaniach zaplanowanych 2025-03-04 14:00:31 +01:00
26 changed files with 2224 additions and 378 deletions

1
.gitignore vendored
View File

@@ -2,3 +2,4 @@ __pycache__
data/
instance/
venv/
todo.txt

34
alters.txt Normal file
View File

@@ -0,0 +1,34 @@
ALTER TABLE user_settings ADD COLUMN deploy_cron VARCHAR(100) DEFAULT '12 12 * * *';
ALTER TABLE user_settings ADD COLUMN backup_cron VARCHAR(100) DEFAULT '12 12 * * *';
ALTER TABLE host ADD COLUMN auto_deploy_enabled BOOLEAN DEFAULT 1;
ALTER TABLE host ADD COLUMN auto_backup_enabled BOOLEAN DEFAULT 1;
ALTER TABLE user_settings DROP COLUMN deploy_interval;
ALTER TABLE user_settings DROP COLUMN backup_interval;
ALTER TABLE host
ADD COLUMN use_daemon BOOLEAN NOT NULL DEFAULT 0;
ALTER TABLE host
ALTER COLUMN use_daemon DROP DEFAULT;
ALTER TABLE host
ADD COLUMN daemon_url VARCHAR(255);
ALTER TABLE host
ADD COLUMN daemon_token VARCHAR(255);
ALTER TABLE Host ADD COLUMN preferred_hostfile_id INTEGER;
ALTER TABLE Host
ADD COLUMN preferred_hostfile_id INTEGER
REFERENCES host_file(id);
ALTER TABLE user_settings ADD COLUMN global_ssh_key TEXT;
ALTER TABLE user_settings ADD COLUMN global_key_passphrase VARCHAR(200);
ALTER TABLE host ADD COLUMN disable_regex_deploy BOOLEAN NOT NULL DEFAULT 0;
ALTER TABLE host ADD COLUMN disable_local_default BOOLEAN DEFAULT FALSE;
ALTER TABLE user_dynamic_variables DROP TABLE;

1110
app.py

File diff suppressed because it is too large Load Diff

View File

@@ -4,4 +4,6 @@ Flask-SQLAlchemy
paramiko
apscheduler
gunicorn
waitress
waitress
croniter
requests

View File

@@ -5,8 +5,8 @@ from datetime import datetime
if __name__ == "__main__":
with app.app_context():
db.create_all()
for job in scheduler.get_jobs():
job.modify(next_run_time=datetime.now())
if not scheduler.running:
scheduler.start()
serve(app, listen="*:5580", threads=4, ident="")
serve(app, listen="*:5580", threads=4, ident="")

82
static/css/custom.css Normal file
View File

@@ -0,0 +1,82 @@
/* Style trybu ciemnego stosujemy je tylko, gdy body ma klasę dark-mode */
body.dark-mode {
background-color: #121212;
color: #e0e0e0;
}
body.dark-mode footer {
background-color: #1e1e1e !important;
}
/* Tabele style ciemnego motywu */
body.dark-mode .table {
color: #e0e0e0;
background-color: #1e1e1e;
border: 1px solid #444;
}
body.dark-mode .table th,
body.dark-mode .table td {
border: 1px solid #444;
}
body.dark-mode .table-striped tbody tr:nth-of-type(odd) {
background-color: #2e2e2e;
}
body.dark-mode .table-striped tbody tr:nth-of-type(even) {
background-color: #1e1e1e;
}
body.dark-mode .table thead {
background-color: #333;
color: #e0e0e0;
}
/* Karty */
body.dark-mode .card {
background-color: #1e1e1e;
color: #e0e0e0;
border-color: #333;
}
/* Formularze */
body.dark-mode .form-control,
body.dark-mode .form-select {
background-color: #2e2e2e;
color: #e0e0e0;
border: 1px solid #444;
}
body.dark-mode .form-control:focus,
body.dark-mode .form-select:focus {
background-color: #2e2e2e;
color: #e0e0e0;
border-color: #777;
box-shadow: none;
}
/* Przycisk Wyloguj solidny przycisk, by był czytelny */
.btn-logout {
color: #fff;
}
/* Zmniejszenie rozmiaru czcionki w navbarze */
.navbar {
font-size: 0.85rem; /* zmniejszony rozmiar czcionki */
}
/* Sprytne odwracanie kolorow dla svg */
html[data-bs-theme="dark"] .mikrotik-logo {
filter: invert(1);
}
html[data-bs-theme="dark"] .linux-logo {
filter: invert(1);
}
.progress-bar {
display: flex;
justify-content: center;
align-items: center;
}
/* Zmniejszenie tekstu w rozwijanym menu navbar */
.navbar .dropdown-menu {
font-size: 0.80rem; /* Dostosuj rozmiar czcionki */
}
/* Zmniejszenie paddingu dla elementów dropdown */
.navbar .dropdown-item {
font-size: 0.85rem; /* Dostosuj rozmiar czcionki */
padding: 0.2rem 0.8rem; /* Mniejszy padding */
}

1
static/img/linux.svg Normal file
View File

@@ -0,0 +1 @@
<svg enable-background="new 0 0 24 24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="m19.7 17.6c-.1-.2-.2-.4-.2-.6 0-.4-.2-.7-.5-1-.1-.1-.3-.2-.4-.2.6-1.8-.3-3.6-1.3-4.9-.8-1.2-2-2.1-1.9-3.7 0-1.9.2-5.4-3.3-5.1-3.6.2-2.6 3.9-2.7 5.2 0 1.1-.5 2.2-1.3 3.1-.2.2-.4.5-.5.7-1 1.2-1.5 2.8-1.5 4.3-.2.2-.4.4-.5.6-.1.1-.2.2-.2.3-.1.1-.3.2-.5.3-.4.1-.7.3-.9.7-.1.3-.2.7-.1 1.1.1.2.1.4 0 .7-.2.4-.2.9 0 1.4.3.4.8.5 1.5.6.5 0 1.1.2 1.6.4.5.3 1.1.5 1.7.5.3 0 .7-.1 1-.2.3-.2.5-.4.6-.7.4 0 1-.2 1.7-.2.6 0 1.2.2 2 .1 0 .1 0 .2.1.3.2.5.7.9 1.3 1h.2c.8-.1 1.6-.5 2.1-1.1.4-.4.9-.7 1.4-.9.6-.3 1-.5 1.1-1 .1-.7-.1-1.1-.5-1.7zm-6.9-12.8c.6.1 1.1.6 1 1.2 0 .3-.1.6-.3.9 0 0 0 0-.1 0-.2-.1-.3-.1-.4-.2.1-.1.1-.3.2-.5 0-.4-.2-.7-.4-.7-.3 0-.5.3-.5.7v.1c-.1-.1-.3-.1-.4-.2v-.1c-.1-.5.3-1.1.9-1.2zm-.3 2c.1.1.3.2.4.2s.3.1.4.2c.2.1.4.2.4.5s-.3.6-.9.8c-.2.1-.3.1-.4.2-.3.2-.6.3-1 .3-.3 0-.6-.2-.8-.4-.1-.1-.2-.2-.4-.3-.1-.1-.3-.3-.4-.6 0-.1.1-.2.2-.3.3-.2.4-.3.5-.4l.1-.1c.2-.3.6-.5 1-.5.3.1.6.2.9.4zm-2.1-1.8c.4 0 .7.4.8 1.1v.2c-.1 0-.3.1-.4.2 0 0 0-.1 0-.2 0-.3-.2-.6-.4-.5-.2 0-.3.3-.3.6 0 .2.1.3.2.4s-.1.1-.2.1c-.2-.2-.4-.5-.4-.8 0-.6.3-1.1.7-1.1zm-1 16.1c-.7.3-1.6.2-2.2-.2-.6-.3-1.1-.4-1.8-.4-.5-.1-1-.1-1.1-.3s-.1-.5.1-1c.1-.3.1-.6 0-.9s-.1-.5 0-.8.3-.4.6-.5.5-.2.7-.4c.1-.1.2-.2.3-.4.3-.4.5-.6.8-.6.6.1 1.1 1 1.5 1.9.2.3.4.7.7 1 .4.5.9 1.2.9 1.6 0 .5-.2.8-.5 1zm4.9-2.2c0 .1 0 .1-.1.2-1.2.9-2.8 1-4.1.3-.2-.3-.4-.6-.6-.9.9-.1.7-1.3-1.2-2.5-2-1.3-.6-3.7.1-4.8.1-.1.1 0-.3.8-.3.6-.9 2.1-.1 3.2 0-.8.2-1.6.5-2.4.7-1.3 1.2-2.8 1.5-4.3.1.1.1.1.2.1.1.1.2.2.3.2.2.3.6.4.9.4h.1c.4 0 .8-.1 1.1-.4.1-.1.2-.2.4-.2.3-.1.6-.3.9-.6.4 1.3.8 2.5 1.4 3.6.4.8.7 1.6.9 2.5.3 0 .7.1 1 .3.8.4 1.1.7 1 1.2-.1 0-.1 0-.2 0 0-.3-.2-.6-.9-.9s-1.3-.3-1.5.4c-.1 0-.2.1-.3.1-.8.4-.8 1.5-.9 2.6.1.4 0 .7-.1 1.1zm4.6.6c-.6.2-1.1.6-1.5 1.1-.4.6-1.1 1-1.9.9-.4 0-.8-.3-.9-.7-.1-.6-.1-1.2.2-1.8.1-.4.2-.7.3-1.1.1-1.2.1-1.9.6-2.2 0 .5.3.8.7 1 .5 0 1-.1 1.4-.5h.2c.3 0 .5 0 .7.2s.3.5.3.7c0 .3.2.6.3.9.5.5.5.8.5.9-.1.2-.5.4-.9.6zm-9-12c-.1 0-.1 0-.1.1 0 0 0 .1.1.1.1 0 .1.1.1.1.3.4.8.6 1.4.7.5-.1 1-.2 1.5-.6.2-.1.4-.2.6-.3.1 0 .1-.1.1-.1 0-.1 0-.1-.1-.1-.2.1-.5.2-.7.3-.4.3-.9.5-1.4.5s-.9-.3-1.2-.6c-.1 0-.2-.1-.3-.1z"/></svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

1
static/img/mikrotik.svg Normal file
View File

@@ -0,0 +1 @@
<svg height="610" viewBox="0 0 610 610" width="610" xmlns="http://www.w3.org/2000/svg"><path d="m586.8 193.4v222.5c0 13.8-1.7 25.6-5.5 34.3-.7 1.6-1.5 3.2-2.3 4.7-5.5 8.9-16.6 17.7-31.6 25.9l-203 111.2c-12.6 6.9-24.2 11.4-34 12.7q-2.8.4-5.4.4-2.7 0-5.5-.4c-9.8-1.3-21.4-5.8-34-12.7l-101.5-55.6-101.4-55.6c-15.1-8.2-26.2-17-31.6-25.9-5.5-9-7.9-22.5-7.9-39v-222.5c0-13.8 1.7-25.5 5.5-34.2.7-1.7 1.5-3.3 2.4-4.7q1.3-2.2 3-4.3c6.1-7.5 16-14.7 28.6-21.7l101.4-55.6 101.5-55.6c15-8.2 28.6-13 39.5-13q2.6 0 5.4.4c9.8 1.2 21.4 5.7 34 12.6l101.5 55.6 101.5 55.6c12.6 7 22.4 14.2 28.5 21.7q1.8 2.1 3.1 4.3c.8 1.4 1.6 3 2.3 4.7 3.8 8.7 5.5 20.4 5.5 34.2zm-102.5 33.2c0-9.8-5.3-18.8-13.8-23.4l-152.7-83.7c-8-4.4-17.7-4.4-25.7 0l-38.9 21.3c-4.6 2.6-4.6 9.2 0 11.7l116.4 63.8c4.6 2.6 4.6 9.2 0 11.7l-51.8 28.4c-8 4.4-17.7 4.4-25.7 0l-112-61.4c-8-4.4-17.7-4.4-25.7 0l-14.9 8.2c-8.6 4.7-13.9 13.6-13.9 23.4v7l135.5 74.3c8.6 4.6 13.9 13.6 13.9 23.3v141.4c0 4.8 2.6 9.3 6.9 11.7l10.2 5.6c8 4.4 17.7 4.4 25.7 0l10.3-5.6c4.2-2.4 6.9-6.9 6.9-11.7v-141.4c0-9.7 5.3-18.7 13.9-23.3l65.5-36c4.5-2.4 9.9.8 9.9 5.9v142.4c0 5.1 5.4 8.3 9.9 5.9l36.3-19.9c8.5-4.7 13.8-13.7 13.8-23.4zm-298.7 78.2c0-4.8-2.6-9.3-6.9-11.7l-43.2-23.7c-4.5-2.4-9.9.8-9.9 5.9v107.5c0 9.7 5.3 18.7 13.9 23.4l36.3 19.9c4.4 2.4 9.8-.8 9.8-5.9z" fill="#263037" fill-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

8
templates/404.html Normal file
View File

@@ -0,0 +1,8 @@
{% extends "base.html" %}
{% block title %}404 - Strona nie znaleziona{% endblock %}
{% block content %}
<div class="container text-center mt-5">
<h1 class="display-4">404</h1>
<p class="lead">Przepraszamy, ale strona, której szukasz, nie została odnaleziona.</p>
</div>
{% endblock %}

11
templates/500.html Normal file
View File

@@ -0,0 +1,11 @@
{% extends "base.html" %}
{% block title %}500 - Błąd serwera{% endblock %}
{% block content %}
<div class="container text-center mt-5">
<h1 class="display-4">500</h1>
<p class="lead">Wewnętrzny błąd serwera. Przepraszamy za niedogodności.</p>
{% if error %}
<pre>{{ error }}</pre>
{% endif %}
</div>
{% endblock %}

View File

@@ -1,14 +1,23 @@
{% extends "base.html" %}
{% block title %}Dodaj serwer - /etc/hosts Manager{% endblock %}
{% block extra_css %}
{{ super() }}
<style>
.tooltip-inner {
max-width: 300px;
text-align: left;
}
</style>
{{ super() }}
<style>
.tooltip-inner {
max-width: 300px;
text-align: left;
}
/* Kontenery domyślnie ukryte; pokażemy je przez JavaScript. */
#hostPortFields,
#userPassFields,
#sshKeyFields,
#daemonFields {
display: none;
}
</style>
{% endblock %}
{% block content %}
<div class="card mb-4">
<div class="card-header">
@@ -16,44 +25,93 @@
</div>
<div class="card-body">
<form method="POST" action="{{ url_for('add_server') }}">
<!-- 1) Wybór platformy (Linux / Mikrotik) -->
<div class="mb-3">
<label for="hostname" class="form-label">Nazwa hosta (IP lub domena)</label>
<input type="text" name="hostname" id="hostname" class="form-control" required>
</div>
<div class="mb-3">
<label for="username" class="form-label">Użytkownik SSH</label>
<input type="text" name="username" id="username" class="form-control" required>
</div>
<div class="mb-3">
<label for="password" class="form-label">Hasło SSH</label>
<input type="password" name="password" id="password" class="form-control">
</div>
<div class="mb-3">
<label for="port" class="form-label">Port SSH</label>
<input type="text" name="port" id="port" class="form-control" value="22">
</div>
<div class="mb-3">
<label for="host_type" class="form-label">Typ</label>
<select name="host_type" id="host_type" class="form-select" required>
<label for="host_type" class="form-label">Platforma (system)</label>
<select name="host_type" id="host_type" class="form-select" required onchange="toggleSystemOptions()">
<option value="linux">Linux</option>
<option value="mikrotik">Mikrotik</option>
</select>
</div>
<!-- 2) Wybór metody uwierzytelniania -->
<div class="mb-3">
<label for="auth_method" class="form-label">Metoda uwierzytelniania</label>
<select name="auth_method" id="auth_method" class="form-select">
<select name="auth_method" id="auth_method" class="form-select" onchange="toggleAuthFields()">
<option value="password">Hasło</option>
<option value="ssh_key">Klucz SSH</option>
<option value="global_key">Globalny klucz SSH</option>
<option value="daemon">Demon (Linux)</option>
</select>
</div>
<div class="mb-3">
<label for="private_key" class="form-label">Klucz prywatny (jeśli używasz klucza SSH)</label>
<textarea name="private_key" id="private_key" rows="4" class="form-control"></textarea>
<!-- Pola nazwy hosta i portu -->
<div id="hostPortFields">
<div class="mb-3">
<label for="hostname" class="form-label">Nazwa hosta (IP lub domena)</label>
<input type="text" name="hostname" id="hostname" class="form-control">
</div>
<div class="mb-3">
<label for="port" class="form-label">Port</label>
<input type="text" name="port" id="port" class="form-control" value="22">
</div>
</div>
<div class="mb-3">
<label for="key_passphrase" class="form-label">Hasło do klucza (jeśli klucz jest zaszyfrowany)</label>
<input type="password" name="key_passphrase" id="key_passphrase" class="form-control">
<!-- Pola user + hasło (dla password / ssh_key / global_key) -->
<div id="userPassFields">
<div class="mb-3">
<label for="username" class="form-label">Użytkownik</label>
<input type="text" name="username" id="username" class="form-control">
</div>
<div class="mb-3" id="passwordField">
<label for="password" class="form-label">Hasło</label>
<input type="password" name="password" id="password" class="form-control">
</div>
</div>
<!-- Pola klucza prywatnego (dla ssh_key) -->
<div id="sshKeyFields">
<div class="mb-3">
<label for="private_key" class="form-label">Klucz prywatny</label>
<textarea name="private_key" id="private_key" rows="4" class="form-control"></textarea>
</div>
<div class="mb-3">
<label for="key_passphrase" class="form-label">Hasło do klucza</label>
<input type="password" name="key_passphrase" id="key_passphrase" class="form-control">
</div>
</div>
<!-- Pola dla demona (tylko jeśli auth_method=daemon i platforma=linux) -->
<div id="daemonFields">
<div class="form-check mb-3">
<input class="form-check-input" type="checkbox" name="use_daemon" id="use_daemon" value="1">
<label class="form-check-label" for="use_daemon">
Korzystaj z demona (zamiast SSH) [Tylko Linux]
</label>
</div>
<div class="mb-3">
<label for="daemon_url" class="form-label">URL demona</label>
<input type="text" name="daemon_url" id="daemon_url" class="form-control">
</div>
<div class="mb-3">
<label for="daemon_token" class="form-label">Token autoryzacyjny demona</label>
<input type="text" name="daemon_token" id="daemon_token" class="form-control">
</div>
</div>
<!-- Nowe: wybór preferowanego pliku /etc/hosts -->
<div class="mb-3">
<label for="preferred_hostfile_id" class="form-label">Domyślny plik /etc/hosts</label>
<select name="preferred_hostfile_id" id="preferred_hostfile_id" class="form-select">
<!-- Opcja pusta => None => "Default" -->
<option value="">(Default - brak)</option>
{% for hf in user_hostfiles %}
<option value="{{ hf.id }}">{{ hf.title }}</option>
{% endfor %}
</select>
</div>
<button type="submit" class="btn btn-primary">Dodaj serwer</button>
</form>
</div>
@@ -61,6 +119,89 @@
<div class="mt-3 text-center">
<a href="{{ url_for('server_list') }}" class="btn btn-secondary">Lista serwerów</a>
<a href="{{ url_for('import_servers') }}" class="btn btn-secondary">Importuj serwery z CSV</a>
<a href="{{ url_for('import_servers') }}" class="btn btn-secondary">Importuj z CSV</a>
</div>
{% endblock %}
{% block extra_js %}
{{ super() }}
<script>
function toggleSystemOptions() {
const hostType = document.getElementById('host_type').value;
const authMethodSelect = document.getElementById('auth_method');
// Znajdź opcję 'daemon':
const daemonOption = Array.from(authMethodSelect.options).find(opt => opt.value === 'daemon');
// Jeśli to Mikrotik, ukrywamy opcję demona
if (hostType === 'mikrotik') {
if (daemonOption) {
daemonOption.style.display = 'none';
if (authMethodSelect.value === 'daemon') {
authMethodSelect.value = 'password';
}
}
} else {
// Linux -> z powrotem pokazujemy 'daemon'
if (daemonOption) {
daemonOption.style.display = 'block';
}
}
toggleAuthFields();
}
function toggleAuthFields() {
const hostType = document.getElementById('host_type').value;
const authMethod = document.getElementById('auth_method').value;
// Kontenery:
const hostPortFields = document.getElementById('hostPortFields');
const userPassFields = document.getElementById('userPassFields');
const passwordField = document.getElementById('passwordField');
const sshKeyFields = document.getElementById('sshKeyFields');
const daemonFields = document.getElementById('daemonFields');
const useDaemon = document.getElementById('use_daemon');
// Ukrywamy wszystkie na start
hostPortFields.style.display = 'none';
userPassFields.style.display = 'none';
passwordField.style.display = 'none';
sshKeyFields.style.display = 'none';
daemonFields.style.display = 'none';
if (useDaemon) {
useDaemon.checked = false;
}
// Jeżeli authMethod != 'daemon', to normalnie pokazujemy host/port
if (authMethod !== 'daemon') {
hostPortFields.style.display = 'block';
}
// W zależności od authMethod
if (authMethod === 'password') {
userPassFields.style.display = 'block';
passwordField.style.display = 'block';
} else if (authMethod === 'ssh_key') {
userPassFields.style.display = 'block';
sshKeyFields.style.display = 'block';
} else if (authMethod === 'global_key') {
userPassFields.style.display = 'block';
} else if (authMethod === 'daemon') {
// Tylko Linux
if (hostType === 'linux') {
daemonFields.style.display = 'block';
if (useDaemon) {
useDaemon.checked = true;
}
}
}
}
document.addEventListener('DOMContentLoaded', function() {
toggleSystemOptions();
});
</script>
{% endblock %}

View File

@@ -19,6 +19,7 @@
<table class="table table-striped">
<thead>
<tr>
<th><input type="checkbox" id="select-all"></th>
<th>Data utworzenia</th>
<th>Opis</th>
<th>Akcje</th>
@@ -27,11 +28,16 @@
<tbody>
{% for backup in backups %}
<tr>
<td>
<!-- Każdy checkbox jest przypisany do formularza bulkDeleteForm -->
<input type="checkbox" name="selected_backups" value="{{ backup.id }}" form="bulkDeleteForm">
</td>
<td>{{ backup.created_at.strftime("%Y-%m-%d %H:%M:%S") }}</td>
<td>{{ backup.description }}</td>
<td>
<a href="{{ url_for('view_backup', backup_id=backup.id) }}" class="btn btn-sm btn-info">Podgląd</a>
<a href="{{ url_for('restore_backup', backup_id=backup.id) }}" class="btn btn-sm btn-success">Przywróć</a>
<!-- Usuwanie pojedynczego backupu -->
<form action="{{ url_for('delete_backup', backup_id=backup.id) }}" method="post" style="display:inline;">
<button type="submit" class="btn btn-sm btn-danger" onclick="return confirm('Czy na pewno usunąć backup?');">Usuń</button>
</form>
@@ -40,6 +46,22 @@
{% endfor %}
</tbody>
</table>
<!-- Formularz do bulk usuwania checkboxy znajdują się poza tym formularzem, ale dzięki atrybutowi form są z nim powiązane -->
<form id="bulkDeleteForm" action="{{ url_for('delete_selected_backups') }}" method="post">
<button type="submit" class="btn btn-sm btn-danger" onclick="return confirm('Czy na pewno usunąć zaznaczone backupy?');">Usuń zaznaczone</button>
</form>
</div>
</div>
{% endblock %}
{% block extra_js %}
{{ super() }}
<script>
// Skrypt do zaznaczania/odznaczania wszystkich checkboxów
document.getElementById('select-all').addEventListener('change', function(){
var checkboxes = document.querySelectorAll('input[name="selected_backups"]');
checkboxes.forEach(function(checkbox) {
checkbox.checked = document.getElementById('select-all').checked;
});
});
</script>
{% endblock %}

View File

@@ -6,60 +6,10 @@
<title>{% block title %}/etc/hosts Manager{% endblock %}</title>
<!-- Bootstrap CSS (Bootstrap 5.3.0) -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="{{ url_for('static', filename='css/custom.css') }}">
<!-- Dodatkowe style -->
<style>
/* Style trybu ciemnego stosujemy je tylko, gdy body ma klasę dark-mode */
body.dark-mode {
background-color: #121212;
color: #e0e0e0;
}
body.dark-mode footer {
background-color: #1e1e1e !important;
}
/* Tabele style ciemnego motywu */
body.dark-mode .table {
color: #e0e0e0;
background-color: #1e1e1e;
border: 1px solid #444;
}
body.dark-mode .table th,
body.dark-mode .table td {
border: 1px solid #444;
}
body.dark-mode .table-striped tbody tr:nth-of-type(odd) {
background-color: #2e2e2e;
}
body.dark-mode .table-striped tbody tr:nth-of-type(even) {
background-color: #1e1e1e;
}
body.dark-mode .table thead {
background-color: #333;
color: #e0e0e0;
}
/* Karty */
body.dark-mode .card {
background-color: #1e1e1e;
color: #e0e0e0;
border-color: #333;
}
/* Formularze */
body.dark-mode .form-control,
body.dark-mode .form-select {
background-color: #2e2e2e;
color: #e0e0e0;
border: 1px solid #444;
}
body.dark-mode .form-control:focus,
body.dark-mode .form-select:focus {
background-color: #2e2e2e;
color: #e0e0e0;
border-color: #777;
box-shadow: none;
}
/* Przycisk Wyloguj solidny przycisk, by był czytelny */
.btn-logout {
color: #fff;
}
/* */
</style>
{% block extra_css %}{% endblock %}
</head>
@@ -90,13 +40,16 @@
<li><a class="dropdown-item" href="{{ url_for('import_servers') }}">Importuj serwery z CSV</a></li>
</ul>
</li>
<!-- WYczysc /etc/hosts -->
<li class="nav-item">
<a class="nav-link" href="{{ url_for('clear_server') }}">Wyczyść /etc/hosts</a>
</li>
<!-- Edytuj /etc/hosts -->
<li class="nav-item">
<a class="nav-link" href="{{ url_for('edit_local_hosts') }}">Edytuj /etc/hosts</a>
<!-- Edytuj /etc/hosts z podsekcjami -->
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="editHostsDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
Edytuj domyśny /etc/hosts
</a>
<ul class="dropdown-menu" aria-labelledby="editHostsDropdown">
<li><a class="dropdown-item" href="{{ url_for('edit_local_hosts') }}">Edytuj /etc/hosts</a></li>
<li><a class="dropdown-item" href="{{ url_for('default_hostfile_versions') }}">Historia wersji</a></li>
<!-- Opcjonalnie: dodaj inne akcje, np. diff wersji -->
</ul>
</li>
<!-- Sieci CIDR / Regex -->
<li class="nav-item dropdown">
@@ -111,13 +64,24 @@
<!-- Pliki /etc/hosts -->
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="filesDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
Pliki /etc/hosts (beta)
Pliki /etc/hosts
</a>
<ul class="dropdown-menu" aria-labelledby="filesDropdown">
<li><a class="dropdown-item" href="{{ url_for('list_hosts_files') }}">Lista plików</a></li>
<li><a class="dropdown-item" href="{{ url_for('new_hosts_file') }}">Utwórz nowy /etc/hosts</a></li>
</ul>
</li>
<!-- Dpmyślne wpisy -->
<li class="nav-item">
<a class="nav-link" href="{{ url_for('local_defaults') }}">Domyślne /etc/hosts</a>
</li>
<!-- Wyczysc /etc/hosts -->
<li class="nav-item">
<a class="nav-link" href="{{ url_for('clear_server') }}">Wyczyść /etc/hosts</a>
</li>
<!-- Kopie zapasowe -->
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="backupsDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
@@ -127,6 +91,7 @@
<li><a class="dropdown-item" href="{{ url_for('backups') }}">Lista kopii</a></li>
</ul>
</li>
<!-- Ustawienia -->
<li class="nav-item">
<a class="nav-link" href="{{ url_for('settings') }}">Ustawienia</a>

View File

@@ -48,7 +48,7 @@
<label for="host_id" class="form-label">Wybierz serwer:</label>
<select class="form-select" id="host_id" name="host_id">
{% for host in hosts %}
<option value="{{ host.id }}">{{ host.hostname }}</option>
<option value="{{ host.id }}">{{ format_host(host) }}</option>
{% endfor %}
</select>
</div>
@@ -61,17 +61,16 @@
{% block extra_js %}
{{ super() }}
<script>
// Ustaw dynamicznie action formularza dla czyszczenia pojedynczego serwera
document.getElementById('clear-single-form').addEventListener('submit', function(e) {
e.preventDefault();
var hostId = document.getElementById('host_id').value;
if(!hostId) {
if (!hostId) {
alert("Proszę wybrać serwer!");
return;
}
// Skonstruuj URL bez użycia url_for
this.action = "/clear-single-server/" + hostId;
this.submit();
});
</script>
{% endblock %}

View File

@@ -21,7 +21,14 @@
{% for host in hosts %}
<div class="form-check">
<input class="form-check-input" type="checkbox" name="hosts" value="{{ host.id }}" id="host{{ host.id }}">
<label class="form-check-label" for="host{{ host.id }}">{{ host.hostname }} ({{ host.type }})</label>
<label class="form-check-label" for="host{{ host.id }}">
{% if host.use_daemon and host.type == 'linux' and host.daemon_url %}
{{ host.daemon_ip }} - {{ host.resolved_daemon }}
{% else %}
{{ host.hostname }}
{% endif %}
({{ host.type }})
</label>
</div>
{% endfor %}
</div>

View File

@@ -0,0 +1,27 @@
{% extends "base.html" %}
{% block title %}Porównanie wersji{% endblock %}
{% block extra_css %}
{{ super() }}
<style>
/* Przykładowe style dla ciemnego motywu w diff */
table.diff {font-family: Courier; border: medium; }
.diff_header {background-color: #444; color: #fff; }
td.diff_header {text-align: center;}
.diff_next {background-color: #333; }
.diff_add {background-color: #008800; color: #fff; }
.diff_chg {background-color: #4444aa; color: #fff; }
.diff_sub {background-color: #aa0000; color: #fff; }
</style>
{% endblock %}
{% block content %}
<div class="card">
<div class="card-header">
<h2>Porównanie wersji</h2>
</div>
<div class="card-body">
{{ diff_html|safe }}
<hr>
<a href="{{ url_for('hostfile_versions', hostfile_id=hostfile_id) }}" class="btn btn-secondary">Powrót do historii</a>
</div>
</div>
{% endblock %}

View File

@@ -1,14 +1,5 @@
{% extends "base.html" %}
{% block title %}Edytuj lokalny Hosts - /etc/hosts Manager{% endblock %}
{% block extra_css %}
{{ super() }}
<style>
.tooltip-inner {
max-width: 300px;
text-align: left;
}
</style>
{% endblock %}
{% block content %}
<div class="card mb-4">
<div class="card-header">
@@ -24,7 +15,26 @@
</form>
</div>
</div>
{% if hostfile %}
<div class="card mb-4">
<div class="card-header">
<h3>Wersje /etc/hosts File</h3>
</div>
<div class="card-body">
<p>Przeglądaj historię zmian, porównuj aktualną zawartość z najnowszą wersją oraz usuwaj stare wersje.</p>
<a href="{{ url_for('hostfile_versions', hostfile_id=hostfile.id) }}" class="btn btn-info">Historia wersji</a>
<a href="{{ url_for('delete_old_versions', hostfile_id=hostfile.id, days=30) }}" class="btn btn-secondary" onclick="return confirm('Usuń wersje starsze niż 30 dni?');">Usuń wersje starsze niż 30 dni</a>
{% if hostfile.versions|length > 0 %}
<a href="{{ url_for('diff_current_hostfile', hostfile_id=hostfile.id) }}" class="btn btn-warning" onclick="return confirm('Porównaj aktualną zawartość z najnowszą wersją zapisanej historii?');">Diff aktualny vs. najnowsza</a>
{% else %}
<span class="text-muted">Brak zapisanych wersji do diff.</span>
{% endif %}
</div>
</div>
{% endif %}
<div class="mt-3 text-center">
<a href="{{ url_for('dashboard') }}" class="btn btn-secondary">Przejdź do pulpitu</a>
<a href="{{ url_for('dashboard') }}" class="btn btn-secondary">Przejdź do dashboardu</a>
</div>
{% endblock %}

View File

@@ -1,5 +1,5 @@
{% extends "base.html" %}
{% block title %}Edytuj server - /etc/hosts Manager{% endblock %}
{% block title %}Edytuj serwer - /etc/hosts Manager{% endblock %}
{% block extra_css %}
{{ super() }}
<style>
@@ -7,8 +7,17 @@
max-width: 300px;
text-align: left;
}
/* Domyślnie ukrywamy te kontenery. JS je włącza zależnie od opcji. */
#hostPortFields,
#userPassFields,
#sshKeyFields,
#daemonFields {
display: none;
}
</style>
{% endblock %}
{% block content %}
<div class="card mb-4">
<div class="card-header">
@@ -16,44 +25,100 @@
</div>
<div class="card-body">
<form method="POST" action="{{ url_for('edit_server', id=host.id) }}">
<!-- 1) Platforma (Linux / Mikrotik) -->
<div class="mb-3">
<label for="hostname" class="form-label">Nazwa hosta (IP lub domena)</label>
<input type="text" name="hostname" id="hostname" class="form-control" value="{{ host.hostname }}" required>
</div>
<div class="mb-3">
<label for="username" class="form-label">Użytkownik SSH</label>
<input type="text" name="username" id="username" class="form-control" value="{{ host.username }}" required>
</div>
<div class="mb-3">
<label for="password" class="form-label">Hasło (pozostaw puste, aby nie zmieniać)</label>
<input type="password" name="password" id="password" class="form-control" placeholder="Wprowadź nowe hasło">
</div>
<div class="mb-3">
<label for="port" class="form-label">Port SSH</label>
<input type="text" name="port" id="port" class="form-control" value="{{ host.port }}">
</div>
<div class="mb-3">
<label for="host_type" class="form-label">Typ</label>
<select name="host_type" id="host_type" class="form-select" required>
<label for="host_type" class="form-label">Platforma (system)</label>
<select name="host_type" id="host_type" class="form-select" required onchange="toggleSystemOptions()">
<option value="linux" {% if host.type == 'linux' %}selected{% endif %}>Linux</option>
<option value="mikrotik" {% if host.type == 'mikrotik' %}selected{% endif %}>Mikrotik</option>
</select>
</div>
<!-- 2) Metoda uwierzytelniania -->
<div class="mb-3">
<label for="auth_method" class="form-label">Metoda uwierzytelniania</label>
<select name="auth_method" id="auth_method" class="form-select">
<option value="password" {% if host.auth_method == 'password' %}selected{% endif %}>Hasło</option>
<option value="ssh_key" {% if host.auth_method == 'ssh_key' %}selected{% endif %}>Klucz SSH</option>
<select name="auth_method" id="auth_method" class="form-select" onchange="toggleAuthFields()">
<option value="password" {% if host.auth_method == 'password' %}selected{% endif %}>Hasło</option>
<option value="ssh_key" {% if host.auth_method == 'ssh_key' %}selected{% endif %}>Klucz SSH</option>
<option value="global_key" {% if host.auth_method == 'global_key' %}selected{% endif %}>Globalny klucz SSH</option>
<option value="daemon" {% if host.use_daemon and host.type == 'linux' %}selected{% endif %}>Demon (Linux)</option>
</select>
</div>
<div class="mb-3">
<label for="private_key" class="form-label">Klucz prywatny (jeśli używasz klucza SSH)</label>
<textarea name="private_key" id="private_key" rows="4" class="form-control">{{ host.private_key }}</textarea>
<!-- Kontener host + port -->
<div id="hostPortFields">
<div class="mb-3">
<label for="hostname" class="form-label">Nazwa hosta (IP lub domena)</label>
<input type="text" name="hostname" id="hostname" class="form-control" value="{{ host.hostname }}">
</div>
<div class="mb-3">
<label for="port" class="form-label">Port</label>
<input type="text" name="port" id="port" class="form-control" value="{{ host.port }}">
</div>
</div>
<div class="mb-3">
<label for="key_passphrase" class="form-label">Hasło do klucza (jeśli klucz jest zaszyfrowany)</label>
<input type="password" name="key_passphrase" id="key_passphrase" class="form-control" value="{{ host.key_passphrase }}">
<!-- Kontener user + hasło -->
<div id="userPassFields">
<div class="mb-3">
<label for="username" class="form-label">Użytkownik</label>
<input type="text" name="username" id="username" class="form-control" value="{{ host.username }}">
</div>
<div class="mb-3" id="passwordField">
<label for="password" class="form-label">Hasło (pozostaw puste, aby nie zmieniać)</label>
<input type="password" name="password" id="password" class="form-control" placeholder="Wprowadź nowe hasło">
</div>
</div>
<!-- Kontener klucza SSH (ssh_key) -->
<div id="sshKeyFields">
<div class="mb-3">
<label for="private_key" class="form-label">Klucz prywatny</label>
<textarea name="private_key" id="private_key" rows="4" class="form-control">{{ host.private_key }}</textarea>
</div>
<div class="mb-3">
<label for="key_passphrase" class="form-label">Hasło do klucza</label>
<input type="password" name="key_passphrase" id="key_passphrase" class="form-control" value="{{ host.key_passphrase }}">
</div>
</div>
<!-- Kontener demona -->
<div id="daemonFields">
<div class="form-check mb-3">
<input class="form-check-input" type="checkbox" name="use_daemon" id="use_daemon" value="1"
{% if host.use_daemon %}checked{% endif %}>
<label class="form-check-label" for="use_daemon">
Korzystaj z demona (zamiast SSH) [Tylko Linux]
</label>
</div>
<div class="mb-3">
<label for="daemon_url" class="form-label">URL demona</label>
<input type="text" name="daemon_url" id="daemon_url" class="form-control" value="{{ host.daemon_url }}">
</div>
<div class="mb-3">
<label for="daemon_token" class="form-label">Token autoryzacyjny demona</label>
<input type="text" name="daemon_token" id="daemon_token" class="form-control" value="{{ host.daemon_token }}">
</div>
</div>
<!-- Nowe: wybór pliku /etc/hosts (preferred_hostfile_id) -->
<div class="mb-3">
<label for="preferred_hostfile_id" class="form-label">Domyślny plik /etc/hosts dla tego serwera</label>
<select name="preferred_hostfile_id" id="preferred_hostfile_id" class="form-select">
<!-- pusta wartość => None => "Default" -->
<option value=""
{% if not host.preferred_hostfile_id %}selected{% endif %}>
(Default - brak)
</option>
{% for hf in user_hostfiles %}
<option value="{{ hf.id }}"
{% if host.preferred_hostfile_id == hf.id %}selected{% endif %}>
{{ hf.title }}
</option>
{% endfor %}
</select>
</div>
<button type="submit" class="btn btn-primary">Zapisz zmiany</button>
</form>
</div>
@@ -61,7 +126,84 @@
<div class="mt-3 text-center">
<a href="{{ url_for('server_list') }}" class="btn btn-secondary">Lista serwerów</a>
<a href="{{ url_for('import_servers') }}" class="btn btn-secondary">Importuj serwery z CSV</a>
<a href="{{ url_for('export_servers_to_csv') }}" class="btn btn-secondary">Eksportuj serwery do CSV</a>
<a href="{{ url_for('import_servers') }}" class="btn btn-secondary">Importuj z CSV</a>
<a href="{{ url_for('export_servers_to_csv') }}" class="btn btn-secondary">Eksportuj do CSV</a>
</div>
{% endblock %}
{% block extra_js %}
{{ super() }}
<script>
function toggleSystemOptions() {
const hostType = document.getElementById('host_type').value;
const authMethodSelect = document.getElementById('auth_method');
const daemonOption = Array.from(authMethodSelect.options).find(opt => opt.value === 'daemon');
// Mikrotik -> chowamy daemon
if (hostType === 'mikrotik') {
if (daemonOption) {
daemonOption.style.display = 'none';
if (authMethodSelect.value === 'daemon') {
authMethodSelect.value = 'password';
}
}
} else {
// Linux -> pokazujemy
if (daemonOption) {
daemonOption.style.display = 'block';
}
}
toggleAuthFields();
}
function toggleAuthFields() {
const hostType = document.getElementById('host_type').value;
const authMethod = document.getElementById('auth_method').value;
const hostPortFields = document.getElementById('hostPortFields');
const userPassFields = document.getElementById('userPassFields');
const passwordField = document.getElementById('passwordField');
const sshKeyFields = document.getElementById('sshKeyFields');
const daemonFields = document.getElementById('daemonFields');
const useDaemon = document.getElementById('use_daemon');
// Najpierw wszystko chowamy
hostPortFields.style.display = 'none';
userPassFields.style.display = 'none';
passwordField.style.display = 'none';
sshKeyFields.style.display = 'none';
daemonFields.style.display = 'none';
if (useDaemon) {
useDaemon.checked = false;
}
// Gdy authMethod != 'daemon', włącz hostPort
if (authMethod !== 'daemon') {
hostPortFields.style.display = 'block';
}
// Szczegółowe reguły
if (authMethod === 'password') {
userPassFields.style.display = 'block';
passwordField.style.display = 'block';
} else if (authMethod === 'ssh_key') {
userPassFields.style.display = 'block';
sshKeyFields.style.display = 'block';
} else if (authMethod === 'global_key') {
userPassFields.style.display = 'block';
} else if (authMethod === 'daemon') {
if (hostType === 'linux') {
daemonFields.style.display = 'block';
if (useDaemon) {
useDaemon.checked = true;
}
}
}
}
// Uruchamiamy przy ładowaniu
document.addEventListener('DOMContentLoaded', function() {
toggleSystemOptions();
});
</script>
{% endblock %}

View File

@@ -0,0 +1,52 @@
{% extends "base.html" %}
{% block title %}Historia wersji hosts{% endblock %}
{% block content %}
<div class="card">
<div class="card-header">
<h2>Historia wersji dla: {{ hostfile.title }}</h2>
</div>
<div class="card-body">
<form method="post" id="bulkDeleteForm">
<table class="table table-striped">
<thead>
<tr>
<th><input type="checkbox" id="select-all"></th>
<th>Data</th>
<th>Fragment treści</th>
<th>Akcje</th>
</tr>
</thead>
<tbody>
{% set latest_id = versions[0].id if versions|length > 0 else None %}
{% for version in versions %}
<tr>
<td><input type="checkbox" name="selected_versions" value="{{ version.id }}"></td>
<td>{{ version.timestamp.strftime("%Y-%m-%d %H:%M:%S") }}</td>
<td>{{ version.content[:50] }}{% if version.content|length > 50 %}...{% endif %}</td>
<td>
<a href="{{ url_for('view_hostfile_version', version_id=version.id) }}" class="btn btn-sm btn-info">Podgląd</a>
<a href="{{ url_for('restore_hostfile_version', version_id=version.id) }}" class="btn btn-sm btn-success" onclick="return confirm('Przywrócić tę wersję?');">Przywróć</a>
{% if latest_id and version.id != latest_id %}
<a href="{{ url_for('diff_hostfile_versions', version1_id=version.id, version2_id=latest_id) }}" class="btn btn-sm btn-warning">Diff z najnowszą</a>
{% else %}
<span class="text-muted">Brak diff</span>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
<button type="submit" class="btn btn-danger" onclick="return confirm('Czy na pewno usunąć zaznaczone wersje?');">Usuń zaznaczone</button>
<a href="{{ url_for('delete_old_versions', hostfile_id=hostfile.id, days=30) }}" class="btn btn-secondary" onclick="return confirm('Usuń wersje starsze niż 30 dni?');">Usuń wersje starsze niż 30 dni</a>
</form>
</div>
</div>
<script>
document.getElementById('select-all').addEventListener('change', function(){
var checkboxes = document.querySelectorAll('input[name="selected_versions"]');
checkboxes.forEach(function(checkbox) {
checkbox.checked = document.getElementById('select-all').checked;
});
});
</script>
{% endblock %}

View File

@@ -31,6 +31,7 @@
<a href="{{ url_for('edit_hosts_file', file_id=file.id) }}" class="btn btn-sm btn-warning">Edytuj</a>
<a href="{{ url_for('delete_hosts_file', file_id=file.id) }}" class="btn btn-sm btn-danger" onclick="return confirm('Czy na pewno usunąć plik?');">Usuń</a>
<a href="{{ url_for('deploy_hosts_file', file_id=file.id) }}" class="btn btn-sm btn-success">Deploy</a>
<a href="{{ url_for('hostfile_versions', hostfile_id=file.id) }}" class="btn btn-sm btn-info">Historia wersji</a>
</td>
</tr>
{% endfor %}

View File

@@ -0,0 +1,195 @@
{% extends "base.html" %}
{% block title %}Definiowanie wpisów z dynamicznymi zmiennymi{% endblock %}
{% block content %}
<div class="card">
<div class="card-header">
<h3>Ustaw domyślne wpisy /etc/hosts</h3>
</div>
<div class="card-body">
<form method="POST">
<div class="row align-items-end">
<div class="col-md-4">
<label class="form-label">Adres IP</label>
<input type="text" class="form-control" name="ip_address" placeholder="np. 127.0.0.1" required>
</div>
<div class="col-md-4">
<label class="form-label">Hostname</label>
<input type="text" class="form-control" name="hostname" placeholder="np. localhost" required>
</div>
<div class="col-md-4">
<button type="submit" class="btn btn-primary w-100">Dodaj wpis</button>
</div>
</div>
</form>
<hr>
<form id="bulkDeleteForm">
<div class="mb-2">
<button type="button" class="btn btn-danger btn-sm" id="deleteSelectedBtn" disabled>Usuń zaznaczone</button>
</div>
<table class="table table-striped mt-4">
<thead>
<tr>
<th><input type="checkbox" id="selectAll"></th>
<th>ID</th>
<th>Adres IP</th>
<th>Hostname</th>
<th>Akcje</th>
</tr>
</thead>
<tbody>
{% for e in entries %}
<tr>
<td><input type="checkbox" class="entry-checkbox" value="{{ e.id }}"></td>
<td>{{ e.id }}</td>
<td class="ip-address">{{ e.ip_address }}</td>
<td class="hostname">{{ e.hostname }}</td>
<td>
<button class="btn btn-warning btn-sm edit-btn" data-id="{{ e.id }}">Edytuj</button>
<button class="btn btn-success btn-sm save-btn d-none" data-id="{{ e.id }}">Zapisz</button>
<form method="POST" action="{{ url_for('delete_local_default', entry_id=e.id) }}"
onsubmit="return confirm('Usunąć wpis?');" style="display:inline;">
<button class="btn btn-danger btn-sm">Usuń</button>
</form>
</td>
</tr>
{% else %}
<tr><td colspan="5">Brak zdefiniowanych wpisów.</td></tr>
{% endfor %}
</tbody>
</table>
</form>
</div>
</div>
{% endblock %}
{% block extra_js %}
{{ super() }}
<script>
document.addEventListener("DOMContentLoaded", function() {
// Logika edycji w tabeli
document.querySelectorAll(".edit-btn").forEach(button => {
button.addEventListener("click", function(event) {
event.preventDefault(); // ✅ Zapobiega przeładowaniu strony
let row = this.closest("tr");
let entryId = this.getAttribute("data-id");
let ipCell = row.querySelector(".ip-address");
let hostnameCell = row.querySelector(".hostname");
let saveButton = row.querySelector(".save-btn");
// Jeśli pola edycji już istnieją, nie duplikuj ich
if (ipCell.querySelector("input") || hostnameCell.querySelector("input")) {
return;
}
let ipInput = document.createElement("input");
ipInput.type = "text";
ipInput.className = "form-control form-control-sm";
ipInput.value = ipCell.textContent.trim();
let hostnameInput = document.createElement("input");
hostnameInput.type = "text";
hostnameInput.className = "form-control form-control-sm";
hostnameInput.value = hostnameCell.textContent.trim();
ipCell.textContent = "";
hostnameCell.textContent = "";
ipCell.appendChild(ipInput);
hostnameCell.appendChild(hostnameInput);
this.classList.add("d-none");
saveButton.classList.remove("d-none");
saveButton.addEventListener("click", function(event) {
event.preventDefault(); // ✅ Zapobiega przeładowaniu strony
let newIp = ipInput.value.trim();
let newHostname = hostnameInput.value.trim();
fetch(`/local-defaults/update/${entryId}`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ ip_address: newIp, hostname: newHostname })
})
.then(response => response.json())
.then(data => {
if (data.status === "success") {
ipCell.textContent = newIp;
hostnameCell.textContent = newHostname;
button.classList.remove("d-none");
saveButton.classList.add("d-none");
} else {
alert("Błąd: " + data.message);
}
})
.catch(error => {
alert("Wystąpił błąd podczas zapisywania: " + error);
});
}, { once: true }); // ✅ Zapobiega dodaniu wielu eventów do przycisku "Zapisz"
});
});
// Logika zaznaczania i usuwania wpisów
let deleteButton = document.getElementById("deleteSelectedBtn");
let checkboxes = document.querySelectorAll(".entry-checkbox");
let selectAllCheckbox = document.getElementById("selectAll");
function updateDeleteButtonState() {
let anyChecked = Array.from(checkboxes).some(checkbox => checkbox.checked);
deleteButton.disabled = !anyChecked;
}
checkboxes.forEach(checkbox => {
checkbox.addEventListener("change", updateDeleteButtonState);
});
selectAllCheckbox.addEventListener("change", function() {
let isChecked = this.checked;
checkboxes.forEach(checkbox => checkbox.checked = isChecked);
updateDeleteButtonState();
});
deleteButton.addEventListener("click", function() {
let selectedIds = Array.from(checkboxes)
.filter(checkbox => checkbox.checked)
.map(checkbox => checkbox.value);
if (selectedIds.length === 0) {
alert("Nie zaznaczono żadnych wpisów do usunięcia.");
return;
}
if (!confirm("Czy na pewno chcesz usunąć zaznaczone wpisy?")) {
return;
}
fetch("/local-defaults/delete", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ entry_ids: selectedIds })
})
.then(response => response.json())
.then(data => {
if (data.status === "success") {
selectedIds.forEach(id => {
let row = document.querySelector(`input[value='${id}']`).closest("tr");
row.remove();
});
updateDeleteButtonState();
} else {
alert("Błąd: " + data.message);
}
})
.catch(error => {
alert("Wystąpił błąd podczas usuwania: " + error);
});
});
});
</script>
{% endblock %}

View File

@@ -28,8 +28,25 @@
</form>
</div>
</div>
{% if file %}
<!-- Sekcja zarządzania wersjami -->
<div class="card mb-4">
<div class="card-header">
<h3>Wersje pliku /etc/hosts</h3>
</div>
<div class="card-body">
<p>Przeglądaj historię zmian, porównaj aktualną zawartość z najnowszą wersją zapisanej historii lub usuń stare wersje.</p>
<a href="{{ url_for('hostfile_versions', hostfile_id=file.id) }}" class="btn btn-info">Historia wersji</a>
<a href="{{ url_for('delete_old_versions', hostfile_id=file.id, days=30) }}" class="btn btn-secondary" onclick="return confirm('Usuń wersje starsze niż 30 dni?');">Usuń wersje starsze niż 30 dni</a>
{% if file.versions|length > 0 %}
<a href="{{ url_for('diff_current_hostfile', hostfile_id=file.id) }}" class="btn btn-warning" onclick="return confirm('Porównaj aktualną zawartość z najnowszą wersją zapisanej historii?');">Diff aktualny vs. ostatnia kopia</a>
{% endif %}
</div>
</div>
{% endif %}
<div class="mt-3 text-center">
<a href="{{ url_for('list_hosts_files') }}" class="btn btn-secondary">Lista Hosts Files</a>
<a href="{{ url_for('dashboard') }}" class="btn btn-secondary">Przejdź do pulpitu</a>
</div>
{% endblock %}

View File

@@ -7,6 +7,25 @@
max-width: 300px;
text-align: left;
}
/* Tryb jasny */
.regex-container, .example-container {
background-color: var(--bg-light);
color: var(--text-dark);
}
/* Tryb ciemny */
@media (prefers-color-scheme: dark) {
.regex-container, .example-container {
background-color: var(--bg-dark);
color: var(--text-light);
border-color: var(--border-color);
}
}
.icon-green {
color: #28a745;
}
</style>
{% endblock %}
{% block content %}
@@ -45,12 +64,91 @@
<div class="mb-3">
<label for="comment" class="form-label">Komentarz</label>
<input type="text" class="form-control" id="comment" name="comment" value="{% if entry %}{{ entry.comment }}{% endif %}">
<hr>
<div class="mb-3">
<label class="form-label"><i class="fas fa-code"></i> Podgląd generowanego Regex</label>
<div class="regex-container p-3 border rounded">
<code id="regexPreview"></code>
</div>
<label class="form-label mt-2"><i class="fas fa-terminal"></i> Przykładowe wygenerowane wpisy</label>
<div class="example-container p-3 border rounded">
<ul id="exampleEntries" class="list-unstyled mb-0"></ul>
</div>
</div>
</div>
<button type="submit" class="btn btn-primary">{% if entry %}Zapisz zmiany{% else %}Utwórz{% endif %}</button>
</form>
</div>
</div>
<div class="mt-3 text-center">
<a href="{{ url_for('list_regex_hosts') }}" class="btn btn-secondary">Lista Regex Hosts</a>
</div>
{% endblock %}
{% block extra_js %}
{{ super() }}
<script>
document.addEventListener("DOMContentLoaded", function() {
function updateRegexPreview() {
let cidr = document.getElementById("cidr_range").value.trim();
let gatewayIp = document.getElementById("gateway_ip").value.trim();
let gatewayHostname = document.getElementById("gateway_hostname").value.trim();
let domain = document.getElementById("domain_suffix").value.trim();
let prefix = document.getElementById("host_prefix").value.trim();
let useGateway = document.getElementById("use_gateway_ip").checked;
let regexPreview = `<strong>Regex:</strong> <span class="icon-green">^${prefix}[0-9]+\\.${domain}$</span>`;
document.getElementById("regexPreview").innerHTML = regexPreview;
// Generowanie przykładowych wpisów
let exampleList = document.getElementById("exampleEntries");
exampleList.innerHTML = ""; // Wyczyść stare wpisy
let exampleIps = generateExampleIps(cidr, 3); // Pobierz 3 pierwsze IP
exampleIps.forEach((ip, index) => {
let listItem = document.createElement("li");
listItem.innerHTML = `<i class="fas fa-check-circle icon-green"></i> ${ip} ${prefix}${index + 1}.${domain}`;
exampleList.appendChild(listItem);
});
// Dodaj wpis dla bramy (jeśli zaznaczona)
if (useGateway && gatewayIp && gatewayHostname) {
let listItem = document.createElement("li");
listItem.innerHTML = `<i class="fas fa-network-wired icon-green"></i> ${gatewayIp} ${gatewayHostname}.${domain}`;
exampleList.prepend(listItem);
}
}
function generateExampleIps(cidr, count) {
let ipList = [];
if (!cidr.includes('/')) return ipList;
let [baseIp, subnet] = cidr.split('/');
let baseParts = baseIp.split('.').map(Number);
// Generujemy kolejne adresy IP w zakresie
for (let i = 1; i <= count; i++) {
let newIp = [...baseParts];
newIp[3] += i; // Zwiększamy ostatni oktet
ipList.push(newIp.join('.'));
}
return ipList;
}
// Nasłuchiwanie zmian w polach
document.getElementById("cidr_range").addEventListener("input", updateRegexPreview);
document.getElementById("gateway_ip").addEventListener("input", updateRegexPreview);
document.getElementById("gateway_hostname").addEventListener("input", updateRegexPreview);
document.getElementById("domain_suffix").addEventListener("input", updateRegexPreview);
document.getElementById("host_prefix").addEventListener("input", updateRegexPreview);
document.getElementById("use_gateway_ip").addEventListener("change", updateRegexPreview);
// Inicjalne wygenerowanie regexu
updateRegexPreview();
});
</script>
{% endblock %}

View File

@@ -1,29 +1,57 @@
{% extends "base.html" %}
{% block title %}Lista serwerów - /etc/hosts Manager{% endblock %}
{% block extra_css %}
{{ super() }}
<style>
.tooltip-inner {
max-width: 300px;
text-align: left;
}
</style>
{{ super() }}
<style>
.tooltip-inner {
max-width: 300px;
text-align: left;
}
/* Zmniejsz tekst w nagłówku tabeli i wyśrodkuj go */
thead th {
font-size: 0.75rem;
text-align: center;
}
/* Klasa zmniejszająca przyciski akcji */
.btn-action {
font-size: 0.65rem;
padding: 0.25rem 0.4rem;
line-height: 1;
}
form.inline-button button.btn {
background-color: #dc3545; /* btn-danger */
border-color: #dc3545;
font-size: 0.65rem;
padding: 0.25rem 0.4rem;
line-height: 1;
/* ewentualnie dodatkowe właściwości, aby upodobnić go do linków */
}
</style>
{% endblock %}
{% block content %}
<div class="card">
<div class="card-header">
<h2>Lista serwerów</h2>
</div>
<div class="card-body table-responsive">
<table class="table table-striped">
<table class="table table-striped align-middle">
<thead>
<tr>
<th>ID</th>
<th>Nazwa hosta</th>
<th>Użytkownik SSH</th>
<th>Serwer</th>
<th>Użytkownik</th>
<th>Port</th>
<th>Typ</th>
<th>Metoda uwierzytelniania</th>
<th>Uwierzytelnianie</th>
<th>Wybrany /etc/hosts</th>
<th>Auto Deploy</th>
<th>Auto Backup</th>
<th>Wyłącz <a href="{{ url_for('list_regex_hosts') }}">CIDR / regex</a></th>
<th>Wyłącz <a href="{{ url_for('local_defaults') }}">local-defaults</a></th>
<th>Akcje</th>
</tr>
</thead>
@@ -31,31 +59,222 @@
{% for h in hosts %}
<tr>
<td>{{ h.id }}</td>
<td data-bs-toggle="tooltip" data-bs-placement="top" title="{{ h.resolved_hostname }}">
{{ h.hostname }}
<td data-bs-toggle="tooltip" data-bs-placement="top" title="{{ h.raw_ip }}">
{% if h.use_daemon and h.type == 'linux' and h.daemon_url %}
{{ h.resolved_daemon }}
{% else %}
{{ h.resolved_hostname }}
{% endif %}
</td>
<td>{{ h.username }}</td>
<td>{{ h.port }}</td>
<td>{{ h.type }}</td>
<td>{{ h.auth_method }}</td>
<td>
<a href="{{ url_for('edit_server', id=h.id) }}" class="btn btn-primary btn-sm">Edytuj</a>
<a href="{{ url_for('test_server_connection', id=h.id) }}" class="btn btn-info btn-sm">Testuj</a>
<a href="{{ url_for('server_backup', host_id=h.id) }}" class="btn btn-success btn-sm">Backup</a>
<form method="GET" action="{{ url_for('delete_server', id=h.id) }}" style="display:inline;">
<button type="submit" class="btn btn-danger btn-sm">Usuń</button>
</form>
{% if h.use_daemon and h.type == 'linux' %}
<em></em>
{% else %}
{{ h.username }}
{% endif %}
</td>
<td>
{% if h.use_daemon and h.type == 'linux' %}
<em></em>
{% else %}
{{ h.port }}
{% endif %}
</td>
<td class="text-center">
{% if h.type == 'linux' %}
<img src="{{ url_for('static', filename='img/linux.svg') }}"
alt="Linux"
data-bs-toggle="tooltip"
data-bs-placement="top"
title="{% if h.use_daemon %}Linux - Używa Linux Demon{% else %}Linux{% endif %}"
class="linux-logo"
style="width: 40px; height: 40px;">
{% else %}
<img src="{{ url_for('static', filename='img/mikrotik.svg') }}"
alt="Mikrotik"
data-bs-toggle="tooltip"
data-bs-placement="top"
title="Mikrotik"
class="mikrotik-logo"
style="width: 40px; height: 40px;">
{% endif %}
</td>
<td>
{% if h.use_daemon and h.type == 'linux' %}
<span class="badge bg-secondary" style="font-size: 0.75rem;">- linux daemon -</span>
{% else %}
{% if h.auth_method == 'password' %}
<span class="badge bg-primary" style="font-size: 0.75rem;">Hasło</span>
{% elif h.auth_method == 'ssh_key' %}
<span class="badge bg-success" style="font-size: 0.75rem;">Klucz SSH</span>
{% elif h.auth_method == 'global_key' %}
<span class="badge bg-info" style="font-size: 0.75rem;">Globalny klucz</span>
{% else %}
<span class="badge bg-light text-dark" style="font-size: 0.75rem;">{{ h.auth_method }}</span>
{% endif %}
{% endif %}
</td>
<td>
{% if h.preferred_hostfile %}
{{ h.preferred_hostfile.title }}
{% else %}
(Default)
{% endif %}
</td>
<td>
<form method="POST" action="{{ url_for('update_host_automation', id=h.id) }}" class="d-flex justify-content-center">
<input type="hidden" name="setting" value="auto_deploy">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" id="autoDeployCheckbox{{ h.id }}" name="enabled" value="1" onchange="this.form.submit()" {% if h.auto_deploy_enabled %}checked{% endif %}>
<label class="form-check-label" for="autoDeployCheckbox{{ h.id }}"></label>
</div>
</form>
</td>
<td>
<form method="POST" action="{{ url_for('update_host_automation', id=h.id) }}" class="d-flex justify-content-center">
<input type="hidden" name="setting" value="auto_backup">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" id="autoBackupCheckbox{{ h.id }}" name="enabled" value="1" onchange="this.form.submit()" {% if h.auto_backup_enabled %}checked{% endif %}>
<label class="form-check-label" for="autoBackupCheckbox{{ h.id }}"></label>
</div>
</form>
</td>
<td>
<form method="POST" action="{{ url_for('update_host_automation', id=h.id) }}" class="d-flex justify-content-center">
<input type="hidden" name="setting" value="disable_regex">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" id="disableRegexCheckbox{{ h.id }}" name="enabled" value="1" onchange="this.form.submit()" {% if h.disable_regex_deploy %}checked{% endif %}>
<label class="form-check-label" for="disableRegexCheckbox{{ h.id }}"></label>
</div>
</form>
</td>
<td>
<form method="POST" action="{{ url_for('update_host_automation', id=h.id) }}" class="d-flex justify-content-center">
<input type="hidden" name="setting" value="disable_local_default">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" id="disableLocalDefaultCheckbox{{ h.id }}" name="enabled" value="1" onchange="this.form.submit()" {% if h.disable_local_default %}checked{% endif %}>
<label class="form-check-label" for="disableLocalDefaultCheckbox{{ h.id }}"></label>
</div>
</form>
</td>
<td>
<div class="d-flex flex-wrap gap-1">
<a href="{{ url_for('edit_server', id=h.id) }}" class="btn btn-primary btn-sm btn-action">Edytuj</a>
{% if h.use_daemon and h.type == 'linux' %}
<button class="btn btn-info btn-sm btn-action test-daemon-btn" data-host-id="{{ h.id }}">
Test
</button>
{% else %}
<a href="{{ url_for('test_server_connection', id=h.id) }}" class="btn btn-info btn-sm btn-action">Test</a>
{% endif %}
<a href="{{ url_for('server_backup', host_id=h.id) }}" class="btn btn-success btn-sm btn-action">Backup</a>
<form method="GET" action="{{ url_for('delete_server', id=h.id) }}" class="inline-button d-flex justify-content-center">
<button type="submit" class="btn btn-danger btn-sm btn-action" onclick="return confirm('Czy na pewno chcesz usunąć serwer?')">Usuń</button>
</form>
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<!-- Modal z informacjami -->
<div class="modal fade" id="serverInfoModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Informacje o serwerze</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<p><strong>Host:</strong> <span id="modal-hostname"></span> (<span id="modal-ip"></span>)</p>
<label><strong>CPU:</strong></label>
<div class="progress mb-3">
<div id="modal-cpu" class="progress-bar" role="progressbar"></div>
</div>
<label><strong>Pamięć:</strong></label>
<div class="progress mb-3">
<div id="modal-mem" class="progress-bar bg-warning" role="progressbar"></div>
</div>
<label><strong>Dysk:</strong></label>
<div class="progress mb-3">
<div id="modal-disk" class="progress-bar bg-success" role="progressbar"></div>
</div>
<p><strong>Czas działania:</strong> <span id="modal-uptime"></span></p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Zamknij</button>
</div>
</div>
</div>
</div>
<div class="mt-3 text-center">
<a href="{{ url_for('add_server') }}" class="btn btn-secondary">Dodaj nowy serwer</a>
<a href="{{ url_for('import_servers') }}" class="btn btn-secondary">Importuj serwery z CSV</a>
<a href="{{ url_for('export_servers_to_csv') }}" class="btn btn-secondary">Eksportuj serwery do CSV</a>
<a href="{{ url_for('import_servers') }}" class="btn btn-secondary">Importuj z CSV</a>
<a href="{{ url_for('export_servers_to_csv') }}" class="btn btn-secondary">Eksportuj do CSV</a>
</div>
{% endblock %}
{% block extra_js %}
{{ super() }}
<script>
function secondsToDhms(seconds) {
seconds = Number(seconds);
const d = Math.floor(seconds / (3600 * 24));
const h = Math.floor(seconds % (3600 * 24) / 3600);
const m = Math.floor(seconds % 3600 / 60);
const s = Math.floor(seconds % 60);
return `${d} dni, ${h} godz, ${m} min, ${s} sek`;
}
document.querySelectorAll('.test-daemon-btn').forEach(btn => {
btn.addEventListener('click', function() {
const hostId = this.dataset.hostId;
fetch(`/server-info/${hostId}`)
.then(res => res.json())
.then(data => {
if (data.error) {
alert(`Błąd: ${data.error}`);
return;
}
document.querySelector('#modal-hostname').textContent = data.hostname;
document.querySelector('#modal-ip').textContent = data.ip;
const cpu = document.querySelector('#modal-cpu');
cpu.style.width = `${data.cpu}%`;
cpu.textContent = `${data.cpu}%`;
const mem = document.querySelector('#modal-mem');
mem.style.width = `${data.mem}%`;
mem.textContent = `${data.mem}%`;
const disk = document.querySelector('#modal-disk');
disk.style.width = `${data.disk}%`;
disk.textContent = `${data.disk}%`;
document.querySelector('#modal-uptime').textContent = secondsToDhms(data.uptime_seconds);
new bootstrap.Modal(document.getElementById('serverInfoModal')).show();
})
.catch(() => alert('Błąd podczas pobierania danych.'));
});
});
</script>
{% endblock %}

View File

@@ -21,12 +21,24 @@
<label class="form-check-label" for="auto_deploy">Automatyczny deploy</label>
</div>
<div class="mb-3">
<label for="deploy_interval" class="form-label">Interwał deploy (minuty)</label>
<input type="number" class="form-control" id="deploy_interval" name="deploy_interval" value="{{ settings.deploy_interval }}">
<label for="deploy_cron" class="form-label">Harmonogram deploy (cron)</label>
<div class="input-group">
<input type="text" class="form-control" id="deploy_cron" name="deploy_cron" value="{{ settings.deploy_cron }}">
<button type="button" class="btn btn-outline-secondary" onclick="openCronModal('deploy_cron')">Generuj cron</button>
</div>
<small class="text-muted">Np. <code>0 0 * * *</code> codziennie o północy</small>
</div>
<div class="mb-3 form-check">
<input type="checkbox" class="form-check-input" id="auto_backup" name="auto_backup" {% if settings.auto_backup_enabled %}checked{% endif %}>
<label class="form-check-label" for="auto_backup">Automatyczne kopie zapasowe</label>
</div>
<div class="mb-3">
<label for="backup_interval" class="form-label">Interwał backupów (minuty)</label>
<input type="number" class="form-control" id="backup_interval" name="backup_interval" value="{{ settings.backup_interval }}">
<label for="backup_cron" class="form-label">Harmonogram backup (cron)</label>
<div class="input-group">
<input type="text" class="form-control" id="backup_cron" name="backup_cron" value="{{ settings.backup_cron }}">
<button type="button" class="btn btn-outline-secondary" onclick="openCronModal('backup_cron')">Generuj cron</button>
</div>
<small class="text-muted">Np. <code>0 */6 * * *</code> co 6 godzin</small>
</div>
<div class="mb-3 form-check">
<input type="checkbox" class="form-check-input" id="enable_regex_entries" name="enable_regex_entries" {% if settings.regex_deploy_enabled %}checked{% endif %}>
@@ -36,8 +48,95 @@
<label for="backup_retention_days" class="form-label">Ilość dni przechowywania backupów</label>
<input type="number" class="form-control" id="backup_retention_days" name="backup_retention_days" value="{{ settings.backup_retention_days }}">
</div>
<!-- Nowe pola dla globalnego klucza SSH -->
<div class="mb-3">
<label for="global_ssh_key" class="form-label">Globalny klucz SSH</label>
<textarea class="form-control" id="global_ssh_key" name="global_ssh_key" rows="4">{{ settings.global_ssh_key or '' }}</textarea>
<small class="text-muted">Wklej tutaj swój globalny klucz SSH, który będzie używany przez hosty z metodą "global_key".</small>
</div>
<div class="mb-3">
<label for="global_key_passphrase" class="form-label">Hasło globalnego klucza SSH</label>
<input type="password" class="form-control" id="global_key_passphrase" name="global_key_passphrase" value="{{ settings.global_key_passphrase or '' }}">
<small class="text-muted">Opcjonalnie: podaj hasło do globalnego klucza SSH, jeśli jest ustawione.</small>
</div>
<button type="submit" class="btn btn-primary">Zapisz ustawienia</button>
</form>
</div>
</div>
<!-- Modal do generowania wyrażenia CRON -->
<div class="modal fade" id="cronModal" tabindex="-1" aria-labelledby="cronModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="cronModalLabel">Generuj wyrażenie CRON</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Zamknij"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label for="cron_minute" class="form-label">Minuta</label>
<input type="text" class="form-control" id="cron_minute" placeholder="0-59">
</div>
<div class="mb-3">
<label for="cron_hour_modal" class="form-label">Godzina</label>
<input type="text" class="form-control" id="cron_hour_modal" placeholder="0-23">
</div>
<div class="mb-3">
<label for="cron_day" class="form-label">Dzień miesiąca</label>
<input type="text" class="form-control" id="cron_day" placeholder="1-31 lub *">
</div>
<div class="mb-3">
<label for="cron_month" class="form-label">Miesiąc</label>
<input type="text" class="form-control" id="cron_month" placeholder="1-12 lub *">
</div>
<div class="mb-3">
<label for="cron_dow" class="form-label">Dzień tygodnia</label>
<input type="text" class="form-control" id="cron_dow" placeholder="0-6 (0 = niedziela) lub *">
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Anuluj</button>
<button type="button" class="btn btn-primary" onclick="generateCronExpression()">Generuj</button>
</div>
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
{{ super() }}
<script>
// Globalna zmienna, która będzie przechowywać ID pola formularza do aktualizacji
var currentCronField = null;
function openCronModal(fieldId) {
currentCronField = fieldId;
// Resetuj wartości w modal
document.getElementById('cron_minute').value = "";
document.getElementById('cron_hour_modal').value = "";
document.getElementById('cron_day').value = "";
document.getElementById('cron_month').value = "";
document.getElementById('cron_dow').value = "";
// Wyświetl modal przy użyciu Bootstrap
var cronModal = new bootstrap.Modal(document.getElementById('cronModal'));
cronModal.show();
}
function generateCronExpression() {
var minute = document.getElementById('cron_minute').value || "*";
var hour = document.getElementById('cron_hour_modal').value || "*";
var day = document.getElementById('cron_day').value || "*";
var month = document.getElementById('cron_month').value || "*";
var dow = document.getElementById('cron_dow').value || "*";
var cronExpression = minute + " " + hour + " " + day + " " + month + " " + dow;
if (currentCronField) {
document.getElementById(currentCronField).value = cronExpression;
}
// Zamknij modal
var modalEl = document.getElementById('cronModal');
var modal = bootstrap.Modal.getInstance(modalEl);
modal.hide();
}
</script>
{% endblock %}

View File

@@ -0,0 +1,14 @@
{% extends "base.html" %}
{% block title %}Podgląd wersji{% endblock %}
{% block content %}
<div class="card">
<div class="card-header">
<h2>Podgląd wersji z: {{ version.timestamp.strftime("%Y-%m-%d %H:%M:%S") }}</h2>
</div>
<div class="card-body">
<pre>{{ version.content }}</pre>
<a href="{{ url_for('hostfile_versions', hostfile_id=version.hostfile_id) }}" class="btn btn-secondary">Powrót do historii</a>
<a href="{{ url_for('restore_hostfile_version', version_id=version.id) }}" class="btn btn-success" onclick="return confirm('Przywrócić tę wersję?');">Przywróć tę wersję</a>
</div>
</div>
{% endblock %}