Compare commits
43 Commits
94385e7bda
...
master
Author | SHA1 | Date | |
---|---|---|---|
![]() |
911aaab005 | ||
![]() |
d40511823f | ||
![]() |
26538f57f8 | ||
![]() |
1d41d303f3 | ||
e4b33cb0d3 | |||
![]() |
6167cd7983 | ||
![]() |
77074d2789 | ||
![]() |
967e1bee27 | ||
![]() |
b7d3b0a7e1 | ||
![]() |
6ac006e355 | ||
![]() |
7e9ce9894f | ||
![]() |
68c93631ff | ||
![]() |
d4ff3ff635 | ||
![]() |
16c9a75253 | ||
![]() |
d66797312d | ||
![]() |
4bac59a118 | ||
![]() |
ab88b752f5 | ||
![]() |
a8d00cc807 | ||
![]() |
ac2c9415c0 | ||
![]() |
5705884e5c | ||
![]() |
a9f954b7b3 | ||
![]() |
d684c331bd | ||
![]() |
e5b787213d | ||
![]() |
9189fe83bb | ||
![]() |
8dd96a68f2 | ||
![]() |
972e7c61cd | ||
![]() |
0689e887ea | ||
![]() |
05abc1c795 | ||
![]() |
9b2d2ac7d9 | ||
![]() |
0a9b0f22b8 | ||
![]() |
c9a5bfaaf8 | ||
![]() |
011590612e | ||
![]() |
3456bd21ae | ||
![]() |
27f969cc4b | ||
![]() |
1e910e347c | ||
![]() |
5ddd522b6f | ||
![]() |
c80dbdaee1 | ||
![]() |
b22134f15b | ||
![]() |
2b9a63a2b6 | ||
![]() |
7c8d3a0c86 | ||
![]() |
378fd7aade | ||
![]() |
92c8749bd8 | ||
![]() |
55d417708f |
11
Dockerfile
Normal file
11
Dockerfile
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
FROM python:3.13-slim
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY requirements.txt .
|
||||||
|
RUN pip install --upgrade pip
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
CMD ["python", "run_waitress.py"]
|
106
README.md
106
README.md
@@ -1,21 +1,101 @@
|
|||||||
|
# RouterOS Backup Manager
|
||||||
|
|
||||||
# RouterOS backup system
|
RouterOS Backup Manager to aplikacja Flask umożliwiająca zarządzanie kopiami zapasowymi urządzeń Mikrotik RouterOS. Aplikacja pozwala na eksport konfiguracji, tworzenie backupów binarnych, ich przechowywanie, porównywanie oraz przywracanie.
|
||||||
|
|
||||||
# Instalation:
|
## 🔧 Instalacja
|
||||||
- clone (to ex. /opt/routeros_backup)
|
|
||||||
- create venv
|
|
||||||
- install requirements via pip
|
|
||||||
- copy systemd service (routeros_backup.service)
|
|
||||||
|
|
||||||
# Start
|
### 1. Klonowanie repozytorium
|
||||||
- systemctl start routeros_backup.service
|
```sh
|
||||||
- go to http://IPADDRESS:5581
|
git clone https://gitea.linuxiarz.pl/gru/routeros_backup.git
|
||||||
|
cd routeros_backup
|
||||||
|
```
|
||||||
|
|
||||||
# Register, Login
|
### 2. Tworzenie i aktywacja środowiska wirtualnego (opcjonalnie)
|
||||||
|
```sh
|
||||||
|
python3 -m venv venv
|
||||||
|
source venv/bin/activate # Linux/macOS
|
||||||
|
venv\Scripts\activate # Windows
|
||||||
|
```
|
||||||
|
|
||||||
# Configure devices, keys, backups, crons
|
### 3. Instalacja zależności
|
||||||
|
```sh
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Uruchomienie aplikacji lokalnie
|
||||||
|
```sh
|
||||||
|
python run_waitress.py
|
||||||
|
```
|
||||||
|
Aplikacja będzie dostępna pod adresem: `http://127.0.0.1:5581/`
|
||||||
|
|
||||||
## Authors
|
---
|
||||||
|
|
||||||
- [@linuxiarz.pl]
|
## 📦 Uruchamianie w Dockerze
|
||||||
|
|
||||||
|
1. **Zbudowanie obrazu Docker**
|
||||||
|
```sh
|
||||||
|
docker-compose build
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Uruchomienie kontenera**
|
||||||
|
```sh
|
||||||
|
docker-compose up -d
|
||||||
|
```
|
||||||
|
Aplikacja uruchomi się na porcie `5581`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📚 Funkcjonalności
|
||||||
|
|
||||||
|
- 🔐 System użytkowników (rejestracja, logowanie, zmiana hasła)
|
||||||
|
- 📡 Połączenie SSH do routerów MikroTik
|
||||||
|
- 🛠 Eksport konfiguracji i tworzenie backupów binarnych
|
||||||
|
- 🕵️♂️ Porównywanie backupów (`diff`)
|
||||||
|
- 📩 Powiadomienia e-mail oraz Pushover
|
||||||
|
- 📅 Harmonogram automatycznych backupów (APScheduler)
|
||||||
|
- 🧹 Automatyczne czyszczenie starych backupów i logów
|
||||||
|
- 🚀 Obsługa przez interfejs webowy
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚙️ Konfiguracja
|
||||||
|
|
||||||
|
### Zmiana ustawień
|
||||||
|
Plik `app.py` zawiera konfigurację bazy danych oraz inne ustawienia aplikacji:
|
||||||
|
|
||||||
|
```python
|
||||||
|
app.config['SECRET_KEY'] = 'super-secret-key'
|
||||||
|
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///backup_routeros.db'
|
||||||
|
```
|
||||||
|
|
||||||
|
### 📬 Konfiguracja SMTP (E-mail)
|
||||||
|
Aby skonfigurować powiadomienia e-mail, wprowadź dane w sekcji ustawień:
|
||||||
|
|
||||||
|
- Serwer SMTP
|
||||||
|
- Login/hasło SMTP
|
||||||
|
- Port (587 dla TLS, 465 dla SSL)
|
||||||
|
|
||||||
|
### 📲 Powiadomienia Pushover
|
||||||
|
Aby włączyć powiadomienia Pushover, uzupełnij `pushover_token` oraz `pushover_userkey` w ustawieniach.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔍 API & Health Check
|
||||||
|
Aplikacja zawiera endpoint `/health`, który zwraca status bazy danych:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
curl http://127.0.0.1:5581/health
|
||||||
|
```
|
||||||
|
|
||||||
|
Przykładowa odpowiedź:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "ok",
|
||||||
|
"timestamp": "2024-02-26T12:34:56Z"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 Autor i licencja
|
||||||
|
Projekt stworzony przez Mateusz Grusczyński @linuxiarz.pl - dostępny na licencji MIT.
|
147
app.py
147
app.py
@@ -27,7 +27,7 @@ from flask import (
|
|||||||
url_for, session, flash, send_file
|
url_for, session, flash, send_file
|
||||||
)
|
)
|
||||||
from flask_sqlalchemy import SQLAlchemy
|
from flask_sqlalchemy import SQLAlchemy
|
||||||
from passlib.hash import bcrypt
|
from passlib.context import CryptContext
|
||||||
#from flask_wtf.csrf import CSRFProtect
|
#from flask_wtf.csrf import CSRFProtect
|
||||||
|
|
||||||
from apscheduler.schedulers.background import BackgroundScheduler
|
from apscheduler.schedulers.background import BackgroundScheduler
|
||||||
@@ -56,14 +56,18 @@ os.makedirs(DATA_DIR, exist_ok=True)
|
|||||||
###############################################################################
|
###############################################################################
|
||||||
# Modele bazy danych
|
# Modele bazy danych
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
||||||
class User(db.Model):
|
class User(db.Model):
|
||||||
__tablename__ = 'users'
|
__tablename__ = 'users'
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
username = db.Column(db.String(120), unique=True, nullable=False)
|
username = db.Column(db.String(120), unique=True, nullable=False)
|
||||||
password_hash = db.Column(db.String(255), nullable=False)
|
password_hash = db.Column(db.String(255), nullable=False)
|
||||||
|
|
||||||
|
def set_password(self, password):
|
||||||
|
self.password_hash = pwd_context.hash(password)
|
||||||
|
|
||||||
def check_password(self, password):
|
def check_password(self, password):
|
||||||
return bcrypt.verify(password, self.password_hash)
|
return pwd_context.verify(password, self.password_hash)
|
||||||
|
|
||||||
class Router(db.Model):
|
class Router(db.Model):
|
||||||
__tablename__ = 'routers'
|
__tablename__ = 'routers'
|
||||||
@@ -113,6 +117,7 @@ class GlobalSettings(db.Model):
|
|||||||
smtp_password = db.Column(db.String(255), nullable=True)
|
smtp_password = db.Column(db.String(255), nullable=True)
|
||||||
smtp_notifications_enabled = db.Column(db.Boolean, default=False)
|
smtp_notifications_enabled = db.Column(db.Boolean, default=False)
|
||||||
log_retention_days = db.Column(db.Integer, default=7)
|
log_retention_days = db.Column(db.Integer, default=7)
|
||||||
|
recipient_email = db.Column(db.String(255), nullable=True)
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
# Inicjalizacja bazy
|
# Inicjalizacja bazy
|
||||||
@@ -392,23 +397,19 @@ def send_mail_with_attachment(smtp_host, smtp_port, smtp_user, smtp_pass, to_add
|
|||||||
print("SMTP not properly configured, skipping email sending.")
|
print("SMTP not properly configured, skipping email sending.")
|
||||||
return False
|
return False
|
||||||
try:
|
try:
|
||||||
# Utwórz wiadomość typu alternative (obsługuje plain text i HTML)
|
|
||||||
msg = MIMEMultipart("alternative")
|
msg = MIMEMultipart("alternative")
|
||||||
msg["From"] = smtp_user
|
msg["From"] = smtp_user
|
||||||
msg["To"] = to_address
|
msg["To"] = to_address
|
||||||
msg["Subject"] = subject
|
msg["Subject"] = subject
|
||||||
|
|
||||||
# Dodaj część tekstową
|
|
||||||
part1 = MIMEText(plain_body, "plain")
|
part1 = MIMEText(plain_body, "plain")
|
||||||
msg.attach(part1)
|
msg.attach(part1)
|
||||||
|
|
||||||
# Jeśli nie podano wersji HTML, wygeneruj domyślny szablon
|
|
||||||
if html_body is None:
|
if html_body is None:
|
||||||
html_body = get_email_template(subject, plain_body)
|
html_body = get_email_template(subject, plain_body)
|
||||||
part2 = MIMEText(html_body, "html")
|
part2 = MIMEText(html_body, "html")
|
||||||
msg.attach(part2)
|
msg.attach(part2)
|
||||||
|
|
||||||
# Dodaj załącznik, jeśli podany i istnieje
|
|
||||||
if attachment_path and os.path.isfile(attachment_path):
|
if attachment_path and os.path.isfile(attachment_path):
|
||||||
with open(attachment_path, "rb") as attachment:
|
with open(attachment_path, "rb") as attachment:
|
||||||
part = MIMEBase("application", "octet-stream")
|
part = MIMEBase("application", "octet-stream")
|
||||||
@@ -417,10 +418,19 @@ def send_mail_with_attachment(smtp_host, smtp_port, smtp_user, smtp_pass, to_add
|
|||||||
part.add_header("Content-Disposition", f"attachment; filename={os.path.basename(attachment_path)}")
|
part.add_header("Content-Disposition", f"attachment; filename={os.path.basename(attachment_path)}")
|
||||||
msg.attach(part)
|
msg.attach(part)
|
||||||
|
|
||||||
with smtplib.SMTP(smtp_host, smtp_port) as server:
|
if smtp_port == 465:
|
||||||
server.starttls()
|
server = smtplib.SMTP_SSL(smtp_host, smtp_port)
|
||||||
server.login(smtp_user, smtp_pass)
|
server.login(smtp_user, smtp_pass)
|
||||||
server.send_message(msg)
|
server.send_message(msg)
|
||||||
|
server.quit()
|
||||||
|
else:
|
||||||
|
server = smtplib.SMTP(smtp_host, smtp_port)
|
||||||
|
server.ehlo()
|
||||||
|
if smtp_port == 587:
|
||||||
|
server.starttls()
|
||||||
|
server.login(smtp_user, smtp_pass)
|
||||||
|
server.send_message(msg)
|
||||||
|
server.quit()
|
||||||
return True
|
return True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print("Mail error_send_mail_with_attachment:", e)
|
print("Mail error_send_mail_with_attachment:", e)
|
||||||
@@ -457,12 +467,13 @@ def notify(settings: GlobalSettings, message: str, success: bool):
|
|||||||
settings.smtp_login and settings.smtp_login.strip() and
|
settings.smtp_login and settings.smtp_login.strip() and
|
||||||
settings.smtp_password and settings.smtp_password.strip()):
|
settings.smtp_password and settings.smtp_password.strip()):
|
||||||
try:
|
try:
|
||||||
|
to_address = settings.recipient_email.strip() if settings.recipient_email and settings.recipient_email.strip() else settings.smtp_login.strip()
|
||||||
send_mail_with_attachment(
|
send_mail_with_attachment(
|
||||||
smtp_host=settings.smtp_host.strip(),
|
smtp_host=settings.smtp_host.strip(),
|
||||||
smtp_port=settings.smtp_port,
|
smtp_port=settings.smtp_port,
|
||||||
smtp_user=settings.smtp_login.strip(),
|
smtp_user=settings.smtp_login.strip(),
|
||||||
smtp_pass=settings.smtp_password.strip(),
|
smtp_pass=settings.smtp_password.strip(),
|
||||||
to_address=settings.smtp_login.strip(),
|
to_address=to_address,
|
||||||
subject="RouterOS Backup Notification",
|
subject="RouterOS Backup Notification",
|
||||||
plain_body=message
|
plain_body=message
|
||||||
)
|
)
|
||||||
@@ -507,10 +518,10 @@ def scheduled_auto_backup():
|
|||||||
db.session.add(b)
|
db.session.add(b)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
notify(s, f"Auto-export dla routera {r.name} OK", True)
|
notify(s, f"Auto-export dla routera {r.name} OK", True)
|
||||||
log_operation(f"Automatyczny eksport dla routera {r.name} wykonany pomyślnie at {datetime.utcnow()}.")
|
log_operation(f"Automatyczny export dla routera {r.name} wykonany pomyślnie at {datetime.utcnow()}.")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
notify(s, f"Auto-export dla routera {r.name} FAILED: {e}", False)
|
notify(s, f"Auto-export dla routera {r.name} FAILED: {e}", False)
|
||||||
log_operation(f"Automatyczny eksport dla routera {r.name} FAILED at {datetime.utcnow()}: {e}")
|
log_operation(f"Automatyczny export dla routera {r.name} FAILED at {datetime.utcnow()}: {e}")
|
||||||
|
|
||||||
def scheduled_auto_binary_backup():
|
def scheduled_auto_binary_backup():
|
||||||
with app.app_context():
|
with app.app_context():
|
||||||
@@ -585,6 +596,19 @@ def schedule_auto_export_job():
|
|||||||
cron_used = s.export_cron if s.export_cron else "0 */12 * * *"
|
cron_used = s.export_cron if s.export_cron else "0 */12 * * *"
|
||||||
print(f"[DEBUG] schedule_auto_export_job -> cron_schedule={cron_used}")
|
print(f"[DEBUG] schedule_auto_export_job -> cron_schedule={cron_used}")
|
||||||
|
|
||||||
|
def cleanup_old_logs():
|
||||||
|
with app.app_context():
|
||||||
|
s = get_settings()
|
||||||
|
cutoff_date = datetime.utcnow() - timedelta(days=s.log_retention_days)
|
||||||
|
old_logs = OperationLog.query.filter(OperationLog.timestamp < cutoff_date).all()
|
||||||
|
deleted_count = len(old_logs)
|
||||||
|
for log in old_logs:
|
||||||
|
db.session.delete(log)
|
||||||
|
db.session.commit()
|
||||||
|
log_operation(f"Automatyczna retencja logów: usunięto {deleted_count} logów starszych niż {s.log_retention_days} dni.")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
# Konfiguracja APScheduler - harmonogram zadań
|
# Konfiguracja APScheduler - harmonogram zadań
|
||||||
###############################################################################
|
###############################################################################
|
||||||
@@ -597,8 +621,8 @@ scheduler = BackgroundScheduler()
|
|||||||
# Dodajemy z unikalnymi ID, co ułatwia re-schedulowanie
|
# Dodajemy z unikalnymi ID, co ułatwia re-schedulowanie
|
||||||
scheduler.add_job(func=cleanup_old_backups, trigger='interval', days=1, id="cleanup_job")
|
scheduler.add_job(func=cleanup_old_backups, trigger='interval', days=1, id="cleanup_job")
|
||||||
scheduler.add_job(func=scheduled_auto_backup, trigger='interval', days=1, id="auto_backup_job")
|
scheduler.add_job(func=scheduled_auto_backup, trigger='interval', days=1, id="auto_backup_job")
|
||||||
|
|
||||||
scheduler.start()
|
scheduler.start()
|
||||||
|
|
||||||
# Sprzątanie przy zamykaniu
|
# Sprzątanie przy zamykaniu
|
||||||
atexit.register(lambda: scheduler.shutdown())
|
atexit.register(lambda: scheduler.shutdown())
|
||||||
|
|
||||||
@@ -619,6 +643,7 @@ def reschedule_jobs():
|
|||||||
schedule_auto_export_job()
|
schedule_auto_export_job()
|
||||||
schedule_retention_job()
|
schedule_retention_job()
|
||||||
schedule_auto_binary_backup_job()
|
schedule_auto_binary_backup_job()
|
||||||
|
scheduler.add_job(func=cleanup_old_logs, trigger='interval', days=1, id="cleanup_logs_job", replace_existing=True)
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
# Filtr Jinja2 - basename
|
# Filtr Jinja2 - basename
|
||||||
@@ -666,6 +691,19 @@ def get_data_folder_size():
|
|||||||
pass
|
pass
|
||||||
return total_size
|
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
|
# ROUTES
|
||||||
###############################################################################
|
###############################################################################
|
||||||
@@ -740,16 +778,17 @@ def advanced_schedule():
|
|||||||
s.binary_cron = request.form.get('binary_cron', '').strip()
|
s.binary_cron = request.form.get('binary_cron', '').strip()
|
||||||
s.export_cron = request.form.get('export_cron', '').strip()
|
s.export_cron = request.form.get('export_cron', '').strip()
|
||||||
s.backup_retention_days = int(request.form.get('backup_retention_days', s.backup_retention_days))
|
s.backup_retention_days = int(request.form.get('backup_retention_days', s.backup_retention_days))
|
||||||
|
s.log_retention_days = int(request.form.get('log_retention_days', s.log_retention_days))
|
||||||
s.enable_auto_export = True if request.form.get('enable_auto_export') == 'on' else False
|
s.enable_auto_export = True if request.form.get('enable_auto_export') == 'on' else False
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
reschedule_jobs() # Aktualizacja harmonogramu zadań
|
reschedule_jobs() # Aktualizacja harmonogramu zadań
|
||||||
flash("Zaawansowane ustawienia harmonogramu zostały zapisane.")
|
flash("Ustawienia harmonogramu zostały zapisane.")
|
||||||
return redirect(url_for('advanced_schedule'))
|
return redirect(url_for('advanced_schedule'))
|
||||||
return render_template('advanced_schedule.html', settings=s)
|
return render_template('advanced_schedule.html', settings=s)
|
||||||
|
|
||||||
@app.route('/toggle_dark_mode')
|
@app.route('/toggle_dark_mode')
|
||||||
def toggle_dark_mode():
|
def toggle_dark_mode():
|
||||||
current_mode = session.get('dark_mode', False)
|
current_mode = session.get('dark_mode', True)
|
||||||
session['dark_mode'] = not current_mode
|
session['dark_mode'] = not current_mode
|
||||||
return redirect(request.referrer or url_for('index'))
|
return redirect(request.referrer or url_for('index'))
|
||||||
|
|
||||||
@@ -796,8 +835,6 @@ def routers_list():
|
|||||||
routers = Router.query.filter_by(owner_id=user.id).order_by(Router.created_at.desc()).all()
|
routers = Router.query.filter_by(owner_id=user.id).order_by(Router.created_at.desc()).all()
|
||||||
return render_template('routers.html', user=user, routers=routers)
|
return render_template('routers.html', user=user, routers=routers)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@app.route('/routers/add', methods=['GET','POST'])
|
@app.route('/routers/add', methods=['GET','POST'])
|
||||||
@login_required
|
@login_required
|
||||||
def add_router():
|
def add_router():
|
||||||
@@ -862,8 +899,8 @@ def router_export(router_id):
|
|||||||
notify(get_settings(), f"Export {router.name} OK", True)
|
notify(get_settings(), f"Export {router.name} OK", True)
|
||||||
log_operation(f"Export wykonany dla routera {router.name} at {datetime.utcnow()}")
|
log_operation(f"Export wykonany dla routera {router.name} at {datetime.utcnow()}")
|
||||||
if request.headers.get("X-Requested-With") == "XMLHttpRequest":
|
if request.headers.get("X-Requested-With") == "XMLHttpRequest":
|
||||||
return {"status": "success", "message": "Eksport zakończony."}
|
return {"status": "success", "message": "export zakończony."}
|
||||||
flash("Eksport zakończony.")
|
flash("Export zakończony.")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
notify(get_settings(), f"Export {router.name} FAIL: {e}", False)
|
notify(get_settings(), f"Export {router.name} FAIL: {e}", False)
|
||||||
log_operation(f"Export dla routera {router.name} FAILED: {e}")
|
log_operation(f"Export dla routera {router.name} FAILED: {e}")
|
||||||
@@ -907,13 +944,16 @@ def upload_backup(router_id, backup_id):
|
|||||||
b = Backup.query.filter_by(id=backup_id, router_id=router.id, backup_type='binary').first()
|
b = Backup.query.filter_by(id=backup_id, router_id=router.id, backup_type='binary').first()
|
||||||
if not b:
|
if not b:
|
||||||
flash("Nie znaleziono backupu binarnego.")
|
flash("Nie znaleziono backupu binarnego.")
|
||||||
return redirect(url_for('router_details', router_id=router.id))
|
#return redirect(url_for('router_details', router_id=router.id))
|
||||||
|
next_url = request.form.get('next') or request.referrer or url_for('dashboard')
|
||||||
|
return redirect(next_url)
|
||||||
|
|
||||||
# Sprawdź sumę kontrolną pliku przed wgraniem
|
|
||||||
local_checksum = compute_checksum(b.file_path)
|
local_checksum = compute_checksum(b.file_path)
|
||||||
if b.checksum != local_checksum:
|
if b.checksum != local_checksum:
|
||||||
flash("Błąd: suma kontrolna backupu nie zgadza się – plik może być uszkodzony.")
|
flash("Błąd: suma kontrolna backupu nie zgadza się – plik może być uszkodzony.")
|
||||||
return redirect(url_for('router_details', router_id=router.id))
|
#return redirect(url_for('router_details', router_id=router.id))
|
||||||
|
next_url = request.form.get('next') or request.referrer or url_for('dashboard')
|
||||||
|
return redirect(next_url)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
ssh_upload_backup(router, b.file_path, expected_checksum=b.checksum)
|
ssh_upload_backup(router, b.file_path, expected_checksum=b.checksum)
|
||||||
@@ -988,7 +1028,6 @@ def export_all_routers():
|
|||||||
flash(" | ".join(messages))
|
flash(" | ".join(messages))
|
||||||
return redirect(url_for('dashboard'))
|
return redirect(url_for('dashboard'))
|
||||||
|
|
||||||
# Nowa podstrona: diff selector
|
|
||||||
@app.route('/diff_selector', methods=['GET', 'POST'])
|
@app.route('/diff_selector', methods=['GET', 'POST'])
|
||||||
@login_required
|
@login_required
|
||||||
def diff_selector():
|
def diff_selector():
|
||||||
@@ -1008,13 +1047,11 @@ def diff_selector():
|
|||||||
def all_files():
|
def all_files():
|
||||||
user = get_current_user()
|
user = get_current_user()
|
||||||
query = Backup.query.join(Router).filter(Router.owner_id == user.id)
|
query = Backup.query.join(Router).filter(Router.owner_id == user.id)
|
||||||
|
|
||||||
# Filtrowanie – wyszukiwanie po nazwie pliku (zastosowanie filtru "basename")
|
# Filtrowanie – wyszukiwanie po nazwie pliku (zastosowanie filtru "basename")
|
||||||
search = request.args.get('search', '')
|
search = request.args.get('search', '')
|
||||||
if search:
|
if search:
|
||||||
query = query.filter(Backup.file_path.ilike(f"%{search}%"))
|
query = query.filter(Backup.file_path.ilike(f"%{search}%"))
|
||||||
|
|
||||||
# Sortowanie – sort_by i order
|
|
||||||
sort_by = request.args.get('sort_by', 'created_at')
|
sort_by = request.args.get('sort_by', 'created_at')
|
||||||
order = request.args.get('order', 'desc')
|
order = request.args.get('order', 'desc')
|
||||||
if sort_by not in ['created_at', 'file_path']:
|
if sort_by not in ['created_at', 'file_path']:
|
||||||
@@ -1044,7 +1081,7 @@ def view_export(backup_id):
|
|||||||
flash("Brak dostępu do backupu.")
|
flash("Brak dostępu do backupu.")
|
||||||
return redirect(url_for('all_files'))
|
return redirect(url_for('all_files'))
|
||||||
if b.backup_type != 'export':
|
if b.backup_type != 'export':
|
||||||
flash("Wybrany backup nie jest plikiem eksportu.")
|
flash("Wybrany backup nie jest plikiem exportu.")
|
||||||
return redirect(url_for('all_files'))
|
return redirect(url_for('all_files'))
|
||||||
try:
|
try:
|
||||||
with open(b.file_path, 'r', encoding='utf-8') as f:
|
with open(b.file_path, 'r', encoding='utf-8') as f:
|
||||||
@@ -1068,10 +1105,10 @@ def send_export_email(backup_id):
|
|||||||
flash("Nie skonfigurowano ustawień SMTP w panelu.")
|
flash("Nie skonfigurowano ustawień SMTP w panelu.")
|
||||||
return redirect(url_for('settings_view'))
|
return redirect(url_for('settings_view'))
|
||||||
subject = f"RouterOS Export: {os.path.basename(b.file_path)}"
|
subject = f"RouterOS Export: {os.path.basename(b.file_path)}"
|
||||||
body = f"Przesyłam eksport {os.path.basename(b.file_path)} z routera {b.router.name}."
|
body = f"Przesyłam export {os.path.basename(b.file_path)} z routera {b.router.name}."
|
||||||
if send_mail_with_attachment(s.smtp_host, s.smtp_port, s.smtp_login, s.smtp_password,
|
if send_mail_with_attachment(s.smtp_host, s.smtp_port, s.smtp_login, s.smtp_password,
|
||||||
s.smtp_login, subject, body, b.file_path):
|
s.smtp_login, subject, body, b.file_path):
|
||||||
flash("Wysłano eksport mailem.")
|
flash("Wysłano export mailem.")
|
||||||
else:
|
else:
|
||||||
flash("Błąd wysyłki mailowej.")
|
flash("Błąd wysyłki mailowej.")
|
||||||
#return redirect(url_for('router_details', router_id=b.router_id))
|
#return redirect(url_for('router_details', router_id=b.router_id))
|
||||||
@@ -1117,8 +1154,9 @@ def settings_view():
|
|||||||
s.smtp_port = int(request.form.get('smtp_port', '587'))
|
s.smtp_port = int(request.form.get('smtp_port', '587'))
|
||||||
s.smtp_login = request.form.get('smtp_login', '')
|
s.smtp_login = request.form.get('smtp_login', '')
|
||||||
s.smtp_password = request.form.get('smtp_password', '')
|
s.smtp_password = request.form.get('smtp_password', '')
|
||||||
|
s.recipient_email = request.form.get('recipient_email', '')
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
reschedule_jobs() # Aktualizacja zadań – zadania dotyczące backupu/CRON zostaną teraz sterowane z /advanced_schedule
|
#reschedule_jobs() # Aktualizacja zadań – zadania dotyczące backupu/CRON zostaną teraz sterowane z /advanced_schedule
|
||||||
flash("Zapisano ustawienia.")
|
flash("Zapisano ustawienia.")
|
||||||
return redirect(url_for('settings_view'))
|
return redirect(url_for('settings_view'))
|
||||||
return render_template('settings.html', settings=s)
|
return render_template('settings.html', settings=s)
|
||||||
@@ -1232,7 +1270,6 @@ def diff_view(backup_id1, backup_id2):
|
|||||||
lineterm=''
|
lineterm=''
|
||||||
))
|
))
|
||||||
diff_text = "\n".join(diff_lines)
|
diff_text = "\n".join(diff_lines)
|
||||||
|
|
||||||
return render_template('diff.html', diff_text=diff_text, backup1=b1, backup2=b2)
|
return render_template('diff.html', diff_text=diff_text, backup1=b1, backup2=b2)
|
||||||
|
|
||||||
@app.route('/routers/all_backup', methods=['POST'])
|
@app.route('/routers/all_backup', methods=['POST'])
|
||||||
@@ -1325,7 +1362,7 @@ def change_password():
|
|||||||
flash("Nowe hasło i potwierdzenie nie są zgodne.")
|
flash("Nowe hasło i potwierdzenie nie są zgodne.")
|
||||||
return redirect(url_for('change_password'))
|
return redirect(url_for('change_password'))
|
||||||
|
|
||||||
user.password_hash = bcrypt.hash(new_password)
|
user.password_hash = pwd_context.hash(new_password)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
flash("Hasło zostało zmienione pomyślnie.")
|
flash("Hasło zostało zmienione pomyślnie.")
|
||||||
return redirect(url_for('dashboard'))
|
return redirect(url_for('dashboard'))
|
||||||
@@ -1344,7 +1381,6 @@ def test_connection(router_id):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
flash(f"Błąd testu połączenia: {e}")
|
flash(f"Błąd testu połączenia: {e}")
|
||||||
return redirect(url_for('routers_list'))
|
return redirect(url_for('routers_list'))
|
||||||
# Jeśli wywołanie zawiera parametr modal=1, zwracamy widok dla modalu
|
|
||||||
if request.args.get("modal") == "1":
|
if request.args.get("modal") == "1":
|
||||||
return render_template("test_connection_modal.html", router=router, result=result)
|
return render_template("test_connection_modal.html", router=router, result=result)
|
||||||
return render_template("test_connection.html", router=router, result=result)
|
return render_template("test_connection.html", router=router, result=result)
|
||||||
@@ -1378,12 +1414,51 @@ def delete_old_logs():
|
|||||||
flash(f"Usunięto {deleted_count} logów starszych niż {delete_days} dni.")
|
flash(f"Usunięto {deleted_count} logów starszych niż {delete_days} dni.")
|
||||||
return redirect(url_for('logs_page'))
|
return redirect(url_for('logs_page'))
|
||||||
|
|
||||||
|
@app.route('/test_email', methods=['POST'])
|
||||||
|
@login_required
|
||||||
|
def test_email():
|
||||||
|
s = get_settings()
|
||||||
|
# Sprawdzamy, czy ustawienia SMTP są skonfigurowane
|
||||||
|
if not (s.smtp_host and s.smtp_login and s.smtp_password):
|
||||||
|
flash("Brak skonfigurowanych ustawień SMTP.")
|
||||||
|
return redirect(url_for('settings_view'))
|
||||||
|
subject = "Testowy e-mail z RouterOS Backup"
|
||||||
|
body = "To jest testowa wiadomość e-mail wysłana z systemu RouterOS Backup."
|
||||||
|
# Używamy recipient_email, jeśli jest ustawiony, w przeciwnym razie smtp_login
|
||||||
|
to_address = s.recipient_email.strip() if s.recipient_email and s.recipient_email.strip() else s.smtp_login.strip()
|
||||||
|
success = send_mail_with_attachment(
|
||||||
|
smtp_host=s.smtp_host,
|
||||||
|
smtp_port=s.smtp_port,
|
||||||
|
smtp_user=s.smtp_login,
|
||||||
|
smtp_pass=s.smtp_password,
|
||||||
|
to_address=to_address,
|
||||||
|
subject=subject,
|
||||||
|
plain_body=body
|
||||||
|
)
|
||||||
|
if success:
|
||||||
|
flash("Testowy e-mail został wysłany.")
|
||||||
|
else:
|
||||||
|
flash("Wysyłka testowego e-maila nie powiodła się.")
|
||||||
|
return redirect(url_for('settings_view'))
|
||||||
|
|
||||||
|
@app.route('/test_pushover', methods=['POST'])
|
||||||
|
@login_required
|
||||||
|
def test_pushover():
|
||||||
|
s = get_settings()
|
||||||
|
# Sprawdzamy, czy ustawienia Pushover są skonfigurowane
|
||||||
|
if not (s.pushover_token and s.pushover_userkey):
|
||||||
|
flash("Brak skonfigurowanych ustawień Pushover.")
|
||||||
|
return redirect(url_for('settings_view'))
|
||||||
|
message = "Testowe powiadomienie Pushover z systemu RouterOS Backup."
|
||||||
|
success = send_pushover(s.pushover_token, s.pushover_userkey, message)
|
||||||
|
if success:
|
||||||
|
flash("Testowe powiadomienie Pushover zostało wysłane.")
|
||||||
|
else:
|
||||||
|
flash("Wysyłka testowego powiadomienia Pushover nie powiodła się.")
|
||||||
|
return redirect(url_for('settings_view'))
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
with app.app_context():
|
with app.app_context():
|
||||||
scheduler = BackgroundScheduler()
|
reschedule_jobs()
|
||||||
schedule_retention_job()
|
|
||||||
schedule_auto_export_job()
|
|
||||||
schedule_auto_binary_backup_job()
|
|
||||||
scheduler.start()
|
|
||||||
atexit.register(lambda: scheduler.shutdown())
|
atexit.register(lambda: scheduler.shutdown())
|
||||||
app.run(host='0.0.0.0', port=5581, use_reloader=False, debug=True)
|
app.run(host='0.0.0.0', port=5581, use_reloader=False, debug=True)
|
||||||
|
15
docker-compose.yml
Normal file
15
docker-compose.yml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
app:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
container_name: routeros_backup
|
||||||
|
ports:
|
||||||
|
- "5581:5581"
|
||||||
|
environment:
|
||||||
|
- FLASK_ENV=production
|
||||||
|
restart: unless-stopped
|
||||||
|
volumes:
|
||||||
|
- ./data:/data
|
@@ -1,7 +0,0 @@
|
|||||||
bind = "0.0.0.0:81"
|
|
||||||
workers = 4
|
|
||||||
timeout = 120
|
|
||||||
server_header = False
|
|
||||||
def on_starting(server):
|
|
||||||
server.cfg.server_header = False
|
|
||||||
server.log.info("Server header disabled")
|
|
@@ -4,7 +4,8 @@ passlib
|
|||||||
paramiko
|
paramiko
|
||||||
APScheduler
|
APScheduler
|
||||||
requests
|
requests
|
||||||
gunicorn
|
#gunicorn
|
||||||
flask_wtf
|
flask_wtf
|
||||||
gevent
|
gevent
|
||||||
#croniter
|
#croniter
|
||||||
|
waitress
|
@@ -1,18 +1,14 @@
|
|||||||
[Unit]
|
[Unit]
|
||||||
Description=RouterOS Backup Application
|
Description=RouterOS Backup Waitress Service
|
||||||
After=network.target
|
After=network.target
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
#User=www-data # Zmień na odpowiedniego użytkownika
|
#User=routeros
|
||||||
#Group=www-data
|
#Group=routeros
|
||||||
WorkingDirectory=/opt/routeros_backup
|
WorkingDirectory=/opt/routeros_backup
|
||||||
Environment="PATH=/opt/hosts_app/venv/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
|
ExecStart=/opt/routeros_backup/venv/bin/python3 /opt/routeros_backup/run_waitress.py
|
||||||
Environment="FLASK_APP=app.py"
|
|
||||||
Environment="FLASK_ENV=production"
|
|
||||||
ExecStart=/opt/routeros_backup/venv/bin/gunicorn -c /opt/routeros_backup/gunicorn_config.py --worker-class gevent --keep-alive 10 app:app
|
|
||||||
|
|
||||||
Restart=always
|
Restart=always
|
||||||
RestartSec=5
|
Environment=PYTHONUNBUFFERED=1
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
|
9
run_waitress.py
Normal file
9
run_waitress.py
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
from waitress import serve
|
||||||
|
from app import app, reschedule_jobs, scheduler
|
||||||
|
import atexit
|
||||||
|
|
||||||
|
with app.app_context():
|
||||||
|
reschedule_jobs()
|
||||||
|
atexit.register(lambda: scheduler.shutdown())
|
||||||
|
|
||||||
|
serve(app, host='0.0.0.0', port=5581, ident='', threads=4)
|
21
start.sh
Executable file
21
start.sh
Executable file
@@ -0,0 +1,21 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Sprawdzenie, czy podman jest dostępny
|
||||||
|
if command -v podman &> /dev/null; then
|
||||||
|
COMPOSE_CMD="podman-compose"
|
||||||
|
echo "🟢 Wykryto Podman, używam podman-compose..."
|
||||||
|
else
|
||||||
|
COMPOSE_CMD="docker-compose"
|
||||||
|
echo "🔵 Podman nie znaleziony, używam docker-compose..."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Zatrzymanie i usunięcie kontenerów
|
||||||
|
$COMPOSE_CMD down
|
||||||
|
|
||||||
|
# Odbudowa obrazu
|
||||||
|
$COMPOSE_CMD build
|
||||||
|
|
||||||
|
# Uruchomienie w tle
|
||||||
|
$COMPOSE_CMD up -d
|
||||||
|
|
||||||
|
echo "✅ Aplikacja uruchomiona!"
|
@@ -1,9 +1,9 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container mt-5">
|
<div class="container my-4">
|
||||||
<div class="card shadow-sm">
|
<div class="card border-0 shadow-sm">
|
||||||
<div class="card-header">
|
<div class="card-header bg-light">
|
||||||
<h2 class="mb-0">Dodaj nowe urządzenie</h2>
|
<h4 class="mb-0">Dodaj nowe urządzenie</h4>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<form method="POST">
|
<form method="POST">
|
||||||
@@ -24,18 +24,16 @@
|
|||||||
<input type="text" class="form-control" id="ssh_user" name="ssh_user" required>
|
<input type="text" class="form-control" id="ssh_user" name="ssh_user" required>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="ssh_key" class="form-label">
|
<label for="ssh_key" class="form-label"><b>Klucz prywatny</b></label><br>
|
||||||
<b>Klucz prywatny</b> | Wklej wraz z <code>-----BEGIN RSA PRIVATE KEY-----</code> i <code>-----END RSA PRIVATE KEY-----</code><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>
|
||||||
Pozostaw puste jeśli ten RouterOS będzie używał <a href="{{ url_for('settings_view') }}">klucza globalnego</a>
|
<textarea class="form-control mt-2" id="ssh_key" name="ssh_key" rows="4"></textarea>
|
||||||
</label>
|
|
||||||
<textarea class="form-control" id="ssh_key" name="ssh_key" rows="4"></textarea>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="ssh_password" class="form-label"><b>Hasło SSH</b></label><br>
|
<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.
|
<small>Jeśli jest klucz SSH lub klucz globalny, hasło może być ignorowane.</small>
|
||||||
<input type="password" class="form-control" id="ssh_password" name="ssh_password">
|
<input type="password" class="form-control mt-2" id="ssh_password" name="ssh_password">
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" class="btn btn-primary">Dodaj</button>
|
<button type="submit" class="btn btn-primary">Dodaj urządzenie</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -1,52 +1,61 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container mt-5">
|
<div class="container my-4">
|
||||||
<div class="card shadow-sm">
|
<div class="card border-0 shadow-sm">
|
||||||
<div class="card-header">
|
<div class="card-header bg-light">
|
||||||
<h2 class="mb-0">Zaawansowane ustawienia harmonogramu</h2>
|
<h4 class="mb-0">Zaawansowane ustawienia harmonogramu</h4>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<form action="{{ url_for('advanced_schedule') }}" method="POST">
|
<form action="{{ url_for('advanced_schedule') }}" method="POST">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<div class="mb-3">
|
<label for="backup_retention_days" class="form-label">Próg retencji backupów (dni)</label>
|
||||||
<label for="backup_retention_days" class="form-label">Próg retencji backupów (dni)</label>
|
<small class="text-muted d-block mb-2">Usuwanie danych starszych niż ustawione w progu.</small>
|
||||||
<small>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 }}">
|
||||||
<input type="number" class="form-control" id="backup_retention_days" name="backup_retention_days" value="{{ settings.backup_retention_days }}">
|
</div>
|
||||||
</div>
|
<div class="mb-3">
|
||||||
<div class="mb-3">
|
<label for="log_retention_days" class="form-label">Próg retencji logów (dni)</label>
|
||||||
<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 }}">
|
||||||
<input type="number" class="form-control" id="log_retention_days" name="log_retention_days" value="{{ settings.log_retention_days }}">
|
</div>
|
||||||
</div>
|
|
||||||
<label for="retention_cron" class="form-label">Harmonogram retencji (cron)</label>
|
<div class="mb-3">
|
||||||
|
<label for="retention_cron" class="form-label">Harmonogram retencji <code>cron</code></label>
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input type="text" class="form-control" id="retention_cron" name="retention_cron" value="{{ settings.retention_cron }}">
|
<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>
|
<button type="button" class="btn btn-outline-secondary" onclick="openCronModal('retention_cron')">Generuj cron</button>
|
||||||
</div>
|
</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>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="binary_cron" class="form-label">Harmonogram kopii zapasowych binarnych (cron)</label>
|
<label for="binary_cron" class="form-label">Harmonogram kopii zapasowych binarnych <code>cron</code></label>
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input type="text" class="form-control" id="binary_cron" name="binary_cron" value="{{ settings.binary_cron|default('') }}">
|
<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>
|
<button type="button" class="btn btn-outline-secondary" onclick="openCronModal('binary_cron')">Generuj cron</button>
|
||||||
</div>
|
</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>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="export_cron" class="form-label">Harmonogram eksportów (cron)</label>
|
<label for="export_cron" class="form-label">Harmonogram exportów <code>cron</code></label>
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input type="text" class="form-control" id="export_cron" name="export_cron" value="{{ settings.export_cron }}">
|
<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>
|
<button type="button" class="btn btn-outline-secondary" onclick="openCronModal('export_cron')">Generuj cron</button>
|
||||||
</div>
|
</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>
|
||||||
|
|
||||||
<div class="mb-3 form-check">
|
<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 %}>
|
<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 eksport</label>
|
<label class="form-check-label" for="enable_auto_export">Włącz automatyczny export</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button type="submit" class="btn btn-primary">Zapisz ustawienia</button>
|
<button type="submit" class="btn btn-primary">Zapisz ustawienia</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="card-footer text-center">
|
||||||
|
<small class="text-muted">Ustawienia zostaną zapisane i użyte przez cron.</small>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@@ -3,8 +3,8 @@
|
|||||||
<div class="container my-4">
|
<div class="container my-4">
|
||||||
<h2 class="text-center mb-4">Lista wszystkich backupów</h2>
|
<h2 class="text-center mb-4">Lista wszystkich backupów</h2>
|
||||||
|
|
||||||
<!-- Formularz filtrowania -->
|
<!-- Karta filtra -->
|
||||||
<div class="card mb-4 shadow-sm">
|
<div class="card mb-4 shadow-sm border-0">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<form method="GET" action="{{ url_for('all_files') }}" class="row g-2">
|
<form method="GET" action="{{ url_for('all_files') }}" class="row g-2">
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
@@ -29,11 +29,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Tabela z backupami -->
|
<!-- Karta tabeli backupów -->
|
||||||
<div class="card shadow-sm mb-4">
|
<div class="card shadow-sm border-0 mb-4">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="table-responsive">
|
<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">
|
<thead class="table-dark">
|
||||||
<tr>
|
<tr>
|
||||||
<th style="width: 2%;"><input type="checkbox" id="select_all"></th>
|
<th style="width: 2%;"><input type="checkbox" id="select_all"></th>
|
||||||
@@ -63,18 +63,23 @@
|
|||||||
<span class="badge bg-secondary">{{ file.backup_type }}</span>
|
<span class="badge bg-secondary">{{ file.backup_type }}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td>{{ file.file_path|basename }}</td>
|
<td>
|
||||||
|
{% if file.backup_type == 'binary' %}
|
||||||
|
<span data-bs-toggle="tooltip" title="Checksum: {{ file.checksum }}">{{ file.file_path|basename }}</span>
|
||||||
|
{% else %}
|
||||||
|
{{ file.file_path|basename }}
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
<td>{{ file.created_at.strftime("%Y-%m-%d %H:%M:%S") }}</td>
|
<td>{{ file.created_at.strftime("%Y-%m-%d %H:%M:%S") }}</td>
|
||||||
<td>{{ file.file_path|filesize }}</td>
|
<td>{{ file.file_path|filesize }}</td>
|
||||||
<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>
|
<i class="bi bi-download"></i>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<form action="{{ url_for('send_by_email', backup_id=file.id) }}" method="POST" class="d-inline">
|
<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-sm btn-warning">
|
||||||
<button type="submit" class="btn btn-lg btn-warning">
|
|
||||||
<i class="bi bi-envelope"></i>
|
<i class="bi bi-envelope"></i>
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
@@ -82,8 +87,7 @@
|
|||||||
<td>
|
<td>
|
||||||
{% if file.backup_type == 'binary' %}
|
{% 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">
|
<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-sm btn-secondary">
|
||||||
<button type="submit" class="btn btn-lg btn-secondary">
|
|
||||||
<i class="bi bi-upload"></i>
|
<i class="bi bi-upload"></i>
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
@@ -93,7 +97,7 @@
|
|||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{% if file.backup_type == 'export' %}
|
{% 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>
|
<i class="bi bi-eye"></i>
|
||||||
</a>
|
</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
@@ -102,8 +106,7 @@
|
|||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<form action="{{ url_for('delete_backup', backup_id=file.id) }}" method="POST" class="d-inline" onsubmit="return confirm('Na pewno usunąć backup?');">
|
<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-sm btn-danger">
|
||||||
<button type="submit" class="btn btn-lg btn-danger">
|
|
||||||
<i class="bi bi-trash"></i>
|
<i class="bi bi-trash"></i>
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
@@ -117,22 +120,21 @@
|
|||||||
</div>
|
</div>
|
||||||
</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">
|
<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">
|
<button type="submit" name="action" value="download" class="btn btn-success me-2">
|
||||||
<i class="bi bi-file-earmark-zip"></i> Pobierz zip zaznaczonych
|
<i class="bi bi-file-earmark-zip"></i> Pobierz zip
|
||||||
</button>
|
</button>
|
||||||
<button type="submit" name="action" value="delete" class="btn btn-lg btn-danger" onclick="return confirm('Na pewno usunąć zaznaczone backupy?');">
|
<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 backupy
|
<i class="bi bi-trash"></i> Usuń zaznaczone
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
document.getElementById('select_all').addEventListener('change', function(e) {
|
document.getElementById('select_all').addEventListener('change', function(e) {
|
||||||
var checkboxes = document.querySelectorAll('input[name="backup_id"]');
|
var checkboxes = document.querySelectorAll('input[name="backup_id"]');
|
||||||
for (var i = 0; i < checkboxes.length; i++) {
|
checkboxes.forEach(cb => cb.checked = e.target.checked);
|
||||||
checkboxes[i].checked = e.target.checked;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@@ -1,124 +1,392 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="pl" class="{% if session.dark_mode %}dark-mode{% endif %}">
|
<html lang="pl" class="{% if session.get('dark_mode', True) %}dark-mode{% endif %}">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<title>Backup RouterOS App</title>
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<title>Backup RouterOS</title>
|
||||||
<!-- Bootstrap CSS -->
|
<!-- Bootstrap CSS -->
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css">
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
/* 1) Poprawa kontrastu dla form-text w trybie ciemnym */
|
||||||
|
.dark-mode .form-text {
|
||||||
|
color: #ccc !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 2) Ogólne style trybu ciemnego */
|
||||||
.dark-mode body {
|
.dark-mode body {
|
||||||
background-color: #222;
|
background-color: #121212;
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
}
|
}
|
||||||
.dark-mode a, .dark-mode a:hover {
|
.dark-mode a,
|
||||||
|
.dark-mode a:hover {
|
||||||
color: #ddd;
|
color: #ddd;
|
||||||
}
|
}
|
||||||
.dark-mode .navbar, .dark-mode .table {
|
|
||||||
|
/* 3) Nawigacja i menu w trybie ciemnym */
|
||||||
|
.dark-mode .navbar,
|
||||||
|
.dark-mode .navbar-nav,
|
||||||
|
.dark-mode .dropdown-menu {
|
||||||
background-color: #333 !important;
|
background-color: #333 !important;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
.dark-mode .navbar-nav .nav-link {
|
||||||
|
color: #ddd !important;
|
||||||
|
}
|
||||||
|
.dark-mode .navbar-nav .nav-link:hover {
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
/* Dropdown :hover, :focus, active */
|
||||||
|
.dark-mode .dropdown-item:hover,
|
||||||
|
.dark-mode .dropdown-item:focus,
|
||||||
|
.dark-mode .dropdown-item.active {
|
||||||
|
background-color: #444 !important;
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 4) Tabele w trybie ciemnym */
|
||||||
|
.dark-mode .table {
|
||||||
|
background-color: #333 !important;
|
||||||
|
border-color: #444;
|
||||||
|
}
|
||||||
|
.dark-mode .table thead th {
|
||||||
|
background-color: #444;
|
||||||
|
color: #fff;
|
||||||
|
border: 1px solid #555;
|
||||||
|
}
|
||||||
|
.dark-mode .table tbody td {
|
||||||
|
color: #ddd;
|
||||||
|
border: 1px solid #555;
|
||||||
|
}
|
||||||
|
html.dark-mode table.table thead th {
|
||||||
|
background-color: #444 !important;
|
||||||
|
color: #fff !important;
|
||||||
|
border: 1px solid #555 !important;
|
||||||
|
}
|
||||||
|
html.dark-mode table.table tbody td {
|
||||||
|
background-color: #333 !important;
|
||||||
|
color: #ddd !important;
|
||||||
|
border: 1px solid #555 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 5) Pola formularzy (input, select, textarea) w trybie ciemnym */
|
||||||
|
.dark-mode input,
|
||||||
|
.dark-mode textarea,
|
||||||
|
.dark-mode select {
|
||||||
|
background-color: #333;
|
||||||
|
color: #fff;
|
||||||
|
border: 1px solid #555;
|
||||||
|
}
|
||||||
|
.dark-mode ::placeholder {
|
||||||
|
color: #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 7) Karty i bloki w trybie ciemnym */
|
||||||
|
.dark-mode .block,
|
||||||
|
.dark-mode .card {
|
||||||
|
background-color: #171717;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 8) Stopka */
|
||||||
|
.dark-mode footer {
|
||||||
|
background-color: #1e1e1e !important;
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
footer {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
color: #212529;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 9) Nadpisanie .card-header.bg-light w trybie ciemnym */
|
||||||
|
.dark-mode .card-header.bg-light {
|
||||||
|
background-color: #333 !important;
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 10) Style diff2html w trybie ciemnym */
|
||||||
|
.dark-mode .d2h-wrapper,
|
||||||
|
.dark-mode .d2h-file-header,
|
||||||
|
.dark-mode .d2h-file-info,
|
||||||
|
.dark-mode .d2h-file-diff,
|
||||||
|
.dark-mode .d2h-diff-table,
|
||||||
|
.dark-mode .d2h-code-line,
|
||||||
|
.dark-mode .d2h-code-line-ctn,
|
||||||
|
.dark-mode .d2h-code-side-linenumber {
|
||||||
|
background-color: #333 !important;
|
||||||
|
color: #ddd !important;
|
||||||
|
border-color: #444 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 11) Modal w trybie ciemnym */
|
||||||
|
.dark-mode .modal-content {
|
||||||
|
background-color: #333;
|
||||||
|
color: #ddd;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
.dark-mode .modal-header,
|
||||||
|
.dark-mode .modal-footer {
|
||||||
|
border-color: #444;
|
||||||
|
}
|
||||||
|
.dark-mode .modal-title {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.dark-mode .btn-close {
|
||||||
|
filter: invert(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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;
|
||||||
|
border: 1px solid #555 !important;
|
||||||
|
}
|
||||||
|
.dark-mode .dataTables_wrapper .dataTables_paginate .paginate_button.current,
|
||||||
|
.dark-mode .dataTables_wrapper .dataTables_paginate .paginate_button:hover {
|
||||||
|
background-color: #444 !important;
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
html.dark-mode .dataTables_wrapper .pagination .page-link {
|
||||||
|
background-color: #333 !important;
|
||||||
|
color: #fff !important;
|
||||||
|
border: 1px solid #555 !important;
|
||||||
|
}
|
||||||
|
html.dark-mode .dataTables_wrapper .pagination .page-item.active .page-link,
|
||||||
|
html.dark-mode .dataTables_wrapper .pagination .page-link:hover {
|
||||||
|
background-color: #444 !important;
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
html.dark-mode .dataTables_wrapper .dataTables_info,
|
||||||
|
html.dark-mode .dataTables_wrapper .dataTables_length,
|
||||||
|
html.dark-mode .dataTables_wrapper .dataTables_filter {
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
.dark-mode input:focus,
|
||||||
|
.dark-mode textarea:focus,
|
||||||
|
.dark-mode select:focus {
|
||||||
|
background-color: #333 !important;
|
||||||
|
color: #fff !important;
|
||||||
|
border-color: #555 !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 14) Drobne poprawki przycisków wylogowania */
|
||||||
|
.btn-logout {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 15) Główne ustawienia, flex layout */
|
||||||
|
html, body {
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
main.container {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 16) Alerty (diff-add, diff-rem) – pozostawione bez zmian */
|
||||||
.diff-add { color: green; }
|
.diff-add { color: green; }
|
||||||
.diff-rem { color: red; }
|
.diff-rem { color: red; }
|
||||||
|
|
||||||
|
.dark-mode .text-muted {
|
||||||
|
color: #aaa !important; /* zamiast #aaa możesz wybrać #bbb, #ccc itp. */
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<!-- Blok head umożliwiający dołączenie dodatkowych stylów -->
|
{% block head %}{% endblock %}
|
||||||
{% block head %}{% endblock %}
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body class="d-flex flex-column">
|
||||||
<nav class="navbar navbar-expand navbar-dark bg-dark mb-4">
|
<!-- 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">
|
<div class="container-fluid">
|
||||||
<a href="{{ url_for('index') }}" class="navbar-brand">Backup RouterOS</a>
|
<a href="/" class="navbar-brand">Backup RouterOS</a>
|
||||||
<div>
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNavDropdown"
|
||||||
{% if session.user_id %}
|
aria-controls="navbarNavDropdown" aria-expanded="false" aria-label="Toggle navigation">
|
||||||
<a href="{{ url_for('dashboard') }}" class="btn btn-secondary me-2">Dashboard</a>
|
<span class="navbar-toggler-icon"></span>
|
||||||
<a href="{{ url_for('routers_list') }}" class="btn btn-secondary me-2">Urządzenia</a>
|
</button>
|
||||||
<a href="{{ url_for('diff_selector') }}" class="btn btn-secondary me-2">Diff selector</a>
|
<div class="collapse navbar-collapse" id="navbarNavDropdown">
|
||||||
<a href="{{ url_for('all_files') }}" class="btn btn-secondary me-2">Wszystkie pliki</a>
|
{% if session.get('user_id') %}
|
||||||
<a href="{{ url_for('logs_page') }}" class="btn btn-secondary me-2">Logi</a>
|
<ul class="navbar-nav me-auto">
|
||||||
|
<!-- Dashboard -->
|
||||||
<a href="{{ url_for('settings_view') }}" class="btn btn-secondary me-2">Ustawienia</a>
|
<li class="nav-item">
|
||||||
<a href="{{ url_for('advanced_schedule') }}" class="btn btn-secondary me-2">Harmonogram</a>
|
<a class="nav-link" href="{{ url_for('dashboard') }}">Dashboard</a>
|
||||||
|
</li>
|
||||||
<a href="{{ url_for('change_password') }}" class="btn btn-secondary me-2">Zmiana hasła</a>
|
<!-- Urządzenia dropdown -->
|
||||||
<a href="{{ url_for('logout') }}" class="btn btn-secondary me-2">Wyloguj</a>
|
<li class="nav-item dropdown">
|
||||||
{% else %}
|
<a class="nav-link dropdown-toggle" href="#" id="devicesDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
<a href="{{ url_for('login') }}" class="btn btn-secondary me-2">Zaloguj</a>
|
Urządzenia
|
||||||
<a href="{{ url_for('register') }}" class="btn btn-secondary me-2">Utwórz konto</a>
|
</a>
|
||||||
|
<ul class="dropdown-menu" aria-labelledby="devicesDropdown">
|
||||||
|
<li><a class="dropdown-item" href="/routers">Lista</a></li>
|
||||||
|
<li><a class="dropdown-item" href="/routers/add">Dodaj nowe</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<!-- Diff -->
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="/diff_selector">Diff</a>
|
||||||
|
</li>
|
||||||
|
<!-- Wszystkie pliki -->
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="/all_files">Wszystkie pliki</a>
|
||||||
|
</li>
|
||||||
|
<!-- Logi -->
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="/logs">Logi</a>
|
||||||
|
</li>
|
||||||
|
<!-- Ustawienia dropdown -->
|
||||||
|
<li class="nav-item dropdown">
|
||||||
|
<a class="nav-link dropdown-toggle" href="#" id="settingsDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
|
Ustawienia
|
||||||
|
</a>
|
||||||
|
<ul class="dropdown-menu" aria-labelledby="settingsDropdown">
|
||||||
|
<li><a class="dropdown-item" href="/settings">Główne</a></li>
|
||||||
|
<li><a class="dropdown-item" href="/advanced_schedule">Harmonogram</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<!--<a href="{{ url_for('toggle_dark_mode') }}" class="btn btn-warning">Toggle Dark Mode</a>-->
|
|
||||||
|
<!-- 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 ms-1" for="darkModeSwitch" style="user-select:none;">Ciemny</label>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
{% if session.get('user_id') %}
|
||||||
|
<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>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link btn btn-danger ms-2 btn-logout" href="{{ url_for('logout') }}">Wyloguj</a>
|
||||||
|
</li>
|
||||||
|
{% else %}
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link btn btn-success ms-2" href="{{ url_for('login') }}">Zaloguj się</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link btn btn-primary ms-2" href="{{ url_for('register') }}">Zarejestruj się</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</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 %}
|
{% if messages %}
|
||||||
<div class="alert alert-info">
|
{% for category, msg in messages %}
|
||||||
{% for msg in messages %}
|
{% set bs_cat = bootstrap_alert_category(category) %}
|
||||||
<div>{{ msg }}</div>
|
<div class="alert alert-{{ bs_cat }} alert-dismissible fade show" role="alert">
|
||||||
{% endfor %}
|
{{ msg }}
|
||||||
</div>
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
|
|
||||||
{% block content %}{% endblock %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
{% block content %}{% endblock %}
|
||||||
|
</main>
|
||||||
|
|
||||||
<!-- Modal Test Połączenia -->
|
<!-- Modal Test Połączenia -->
|
||||||
<div class="modal fade" id="testConnectionModal" tabindex="-1" aria-labelledby="testConnectionModalLabel" aria-hidden="true">
|
<div class="modal fade" id="testConnectionModal" tabindex="-1" aria-labelledby="testConnectionModalLabel" aria-hidden="true">
|
||||||
<div class="modal-dialog modal-lg">
|
<div class="modal-dialog modal-lg">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h5 class="modal-title" id="testConnectionModalLabel">Test Połączenia</h5>
|
<h5 class="modal-title" id="testConnectionModalLabel">Test Połączenia</h5>
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Zamknij"></button>
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Zamknij"></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body" id="testConnectionModalBody">
|
<div class="modal-body" id="testConnectionModalBody">
|
||||||
<!-- Zawartość zostanie załadowana przez AJAX -->
|
<!-- Zawartość ładowana przez AJAX -->
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
|
<!-- Stopka -->
|
||||||
|
<footer class="footer py-3 mt-auto">
|
||||||
|
<div class="container text-center">
|
||||||
|
<span>© 2025 Mateusz Gruszczyński, linuxiarz.pl</span>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js"></script>
|
<!-- Bootstrap Bundle JS -->
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
|
||||||
|
<!-- Dodatkowe skrypty -->
|
||||||
<script>
|
<script>
|
||||||
function ajaxExport(router_id) {
|
// Funkcja do wczytywania modalu testu połączenia
|
||||||
fetch("/router/" + router_id + "/export", {
|
function openTestConnectionModal(routerId) {
|
||||||
method: "POST",
|
fetch('/router/' + routerId + '/test_connection?modal=1')
|
||||||
headers: {"X-Requested-With": "XMLHttpRequest"}
|
.then(response => response.text())
|
||||||
})
|
.then(html => {
|
||||||
.then(response => response.json())
|
document.getElementById('testConnectionModalBody').innerHTML = html;
|
||||||
.then(data => {
|
var myModal = new bootstrap.Modal(document.getElementById('testConnectionModal'));
|
||||||
if(data.status === "success"){
|
myModal.show();
|
||||||
alert("Eksport wykonany: " + data.message);
|
})
|
||||||
// Możesz też zaktualizować część strony dynamicznie
|
.catch(error => {
|
||||||
} else {
|
console.error("Błąd ładowania modalu: ", error);
|
||||||
alert("Błąd eksportu: " + data.message);
|
alert("Wystąpił błąd podczas ładowania danych.");
|
||||||
}
|
});
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.error("Błąd AJAX:", error);
|
|
||||||
alert("Wystąpił błąd.");
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<script>
|
|
||||||
function openTestConnectionModal(routerId) {
|
|
||||||
fetch('/router/' + routerId + '/test_connection?modal=1')
|
|
||||||
.then(response => response.text())
|
|
||||||
.then(html => {
|
|
||||||
document.getElementById('testConnectionModalBody').innerHTML = html;
|
|
||||||
var myModal = new bootstrap.Modal(document.getElementById('testConnectionModal'));
|
|
||||||
myModal.show();
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.error("Błąd ładowania modalu: ", error);
|
|
||||||
alert("Wystąpił błąd podczas ładowania danych.");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<!-- Blok scripts umożliwiający dołączenie dodatkowych skryptów -->
|
|
||||||
{% block scripts %}{% endblock %}
|
{% block scripts %}{% endblock %}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@@ -1,11 +1,11 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container">
|
<div class="container my-5">
|
||||||
<div class="row justify-content-center align-items-center" style="min-height: 100vh;">
|
<div class="row justify-content-center">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="card shadow">
|
<div class="card border-0 shadow-sm">
|
||||||
<div class="card-header text-center">
|
<div class="card-header bg-light text-center">
|
||||||
<h2>Zmień hasło</h2>
|
<h4 class="mb-0">Zmień hasło</h4>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<form method="POST">
|
<form method="POST">
|
||||||
|
@@ -1,11 +1,11 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container my-4">
|
<div class="container my-4">
|
||||||
<h2 class="text-center mb-4">Panel administracyjny</h2>
|
<h2 class="text-center mb-4">Dashboard</h2>
|
||||||
|
|
||||||
<!-- Wiersz akcji ogólnych -->
|
<!-- Wiersz akcji ogólnych -->
|
||||||
<div class="row mb-4">
|
<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">
|
<a href="{{ url_for('routers_list') }}" class="btn btn-lg btn-outline-primary">
|
||||||
<i class="bi bi-hdd-network"></i> Zobacz routery
|
<i class="bi bi-hdd-network"></i> Zobacz routery
|
||||||
</a>
|
</a>
|
||||||
@@ -53,14 +53,14 @@
|
|||||||
<div class="col-md-6 d-flex justify-content-center">
|
<div class="col-md-6 d-flex justify-content-center">
|
||||||
<form action="{{ url_for('export_all_routers') }}" method="POST">
|
<form action="{{ url_for('export_all_routers') }}" method="POST">
|
||||||
<button type="submit" class="btn btn-lg btn-outline-success">
|
<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>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6 d-flex justify-content-center">
|
<div class="col-md-6 d-flex justify-content-center">
|
||||||
<form action="{{ url_for('backup_all_routers') }}" method="POST">
|
<form action="{{ url_for('backup_all_routers') }}" method="POST">
|
||||||
<button type="submit" class="btn btn-lg btn-outline-secondary">
|
<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>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@@ -73,11 +73,11 @@
|
|||||||
{% else %}
|
{% else %}
|
||||||
{% set success_percent = 0 %}
|
{% set success_percent = 0 %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="card mb-4 shadow-sm">
|
<div class="card mb-4 shadow-sm border-0">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h5 class="card-title">Statystyki operacji</h5>
|
<h5 class="card-title">Statystyki operacji</h5>
|
||||||
<p>Udane operacje: {{ success_ops }}, Nieudane operacje: {{ failure_ops }}</p>
|
<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">
|
<div class="progress-bar bg-success" role="progressbar" style="width: {{ success_percent }}%;" aria-valuenow="{{ success_percent }}" aria-valuemin="0" aria-valuemax="100">
|
||||||
{{ success_percent }}%
|
{{ success_percent }}%
|
||||||
</div>
|
</div>
|
||||||
@@ -89,7 +89,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Log operacji -->
|
<!-- Log operacji -->
|
||||||
<div class="card shadow-sm mb-4">
|
<div class="card shadow-sm border-0 mb-4">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h5 class="card-title">
|
<h5 class="card-title">
|
||||||
Log operacji
|
Log operacji
|
||||||
@@ -114,8 +114,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Dodatkowe statystyki przeniesione na sam dół, pod logami -->
|
<!-- Dodatkowe statystyki -->
|
||||||
<div class="card shadow-sm">
|
<div class="card shadow-sm border-0">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h5 class="card-title">Dodatkowe statystyki</h5>
|
<h5 class="card-title">Dodatkowe statystyki</h5>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
@@ -131,6 +131,5 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@@ -1,28 +1,47 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container my-4">
|
<div class="container my-4">
|
||||||
<h2>Porównanie: {{ backup1.file_path|basename }} vs {{ backup2.file_path|basename }}</h2>
|
<div class="card border-0 shadow-sm">
|
||||||
<hr>
|
<div class="card-header bg-light">
|
||||||
<div id="diffContainer"></div>
|
<h4 class="mb-0">
|
||||||
<a href="{{ url_for('router_details', router_id=backup1.router_id) }}" class="btn btn-secondary mt-3">Powrót</a>
|
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>
|
</div>
|
||||||
|
|
||||||
<!-- Dodajemy diff2html -->
|
<!-- diff2html resources -->
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/diff2html/bundles/css/diff2html.min.css" />
|
<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/bundles/js/diff2html.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/diff2html@3.4.4/bundles/js/diff2html.min.js"></script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener("DOMContentLoaded", function() {
|
document.addEventListener("DOMContentLoaded", function() {
|
||||||
// Upewnij się, że diff_text jest poprawnie escapowany
|
var diffText = `{{ diff_text|e }}`;
|
||||||
var diffText = `{{ diff_text|e }}`;
|
var targetElement = document.getElementById("diffContainer");
|
||||||
var targetElement = document.getElementById("diffContainer");
|
var configuration = {
|
||||||
var configuration = {
|
drawFileList: true,
|
||||||
drawFileList: true,
|
matching: 'lines',
|
||||||
matching: 'lines',
|
outputFormat: 'line-by-line'
|
||||||
outputFormat: 'line-by-line'
|
};
|
||||||
};
|
var diffHtml = Diff2Html.html(diffText, configuration);
|
||||||
var diffHtml = Diff2Html.html(diffText, configuration);
|
targetElement.innerHTML = diffHtml;
|
||||||
targetElement.innerHTML = diffHtml;
|
|
||||||
|
// 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; }
|
||||||
|
.d2h-file-header { background-color: #2e2e2e; color: #fff; }
|
||||||
|
.d2h-diff-table { background-color: #1e1e1e; color: #fff; }
|
||||||
|
.d2h-code-line { background-color: #1e1e1e; color: #fff; }
|
||||||
|
.d2h-code-line-ctn { color: #fff; }
|
||||||
|
`;
|
||||||
|
document.head.appendChild(darkStyle);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@@ -1,8 +1,10 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container my-4">
|
<div class="container my-4">
|
||||||
<h2 class="text-center mb-4">Porównanie backupów (Diff)</h2>
|
<div class="card shadow-sm border-0">
|
||||||
<div class="card shadow-sm">
|
<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">
|
<div class="card-body">
|
||||||
<form action="{{ url_for('diff_selector') }}" method="POST" id="diffForm">
|
<form action="{{ url_for('diff_selector') }}" method="POST" id="diffForm">
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
@@ -29,8 +31,10 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-center">
|
<div class="text-center mt-4">
|
||||||
<button type="submit" class="btn btn-primary btn-lg">Porównaj backupy</button>
|
<button type="submit" class="btn btn-primary btn-lg">
|
||||||
|
Porównaj backupy
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@@ -39,12 +43,12 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
document.getElementById("diffForm").addEventListener("submit", function(event) {
|
document.getElementById("diffForm").addEventListener("submit", function(event) {
|
||||||
var backup1 = document.getElementById("backup1").value;
|
var backup1 = document.getElementById("backup1").value;
|
||||||
var backup2 = document.getElementById("backup2").value;
|
var backup2 = document.getElementById("backup2").value;
|
||||||
if(backup1 === backup2) {
|
if (backup1 === backup2) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
alert("Wybierz dwa różne backupy do porównania.");
|
alert("Wybierz dwa różne backupy do porównania.");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container mt-5">
|
<div class="container my-4">
|
||||||
<div class="card shadow-sm">
|
<div class="card border-0 shadow-sm">
|
||||||
<div class="card-header">
|
<div class="card-header bg-light">
|
||||||
<h2 class="mb-0">Edycja urządzenia</h2>
|
<h4 class="mb-0">Edycja urządzenia</h4>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<form method="POST">
|
<form method="POST">
|
||||||
@@ -25,15 +25,15 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="ssh_key" class="form-label">
|
<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>
|
<b>Klucz prywatny</b>
|
||||||
Pozostaw puste jeśli ten RouterOS będzie używał <a href="{{ url_for('settings_view') }}">klucza globalnego</a>
|
</label><br>
|
||||||
</label>
|
<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" id="ssh_key" name="ssh_key" rows="4">{{ router.ssh_key }}</textarea>
|
<textarea class="form-control mt-2" id="ssh_key" name="ssh_key" rows="4">{{ router.ssh_key }}</textarea>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="ssh_password" class="form-label"><b>Hasło SSH</b></label><br>
|
<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.
|
<small>Jeśli jest klucz SSH lub klucz globalny, hasło może być ignorowane.</small>
|
||||||
<input type="password" class="form-control" id="ssh_password" name="ssh_password" value="{{ router.ssh_password }}">
|
<input type="password" class="form-control mt-2" id="ssh_password" name="ssh_password" value="{{ router.ssh_password }}">
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" class="btn btn-success">Zapisz zmiany</button>
|
<button type="submit" class="btn btn-success">Zapisz zmiany</button>
|
||||||
</form>
|
</form>
|
||||||
|
@@ -2,7 +2,11 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="d-flex flex-column align-items-center justify-content-center" style="min-height: 80vh;">
|
<div class="d-flex flex-column align-items-center justify-content-center" style="min-height: 80vh;">
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
|
{% if session.get('dark_mode', True) %}
|
||||||
|
<img src="https://mikrotik.com/logo/assets/logo-colors-white-E8duxH7y.svg" alt="Mikrotik Logo" class="img-fluid" style="max-width: 200px;">
|
||||||
|
{% else %}
|
||||||
<img src="https://mikrotik.com/logo/assets/logo-colors-dark-ToiqSI6u.svg" alt="Mikrotik Logo" class="img-fluid" style="max-width: 200px;">
|
<img src="https://mikrotik.com/logo/assets/logo-colors-dark-ToiqSI6u.svg" alt="Mikrotik Logo" class="img-fluid" style="max-width: 200px;">
|
||||||
|
{% endif %}
|
||||||
<h1 class="mt-3">Witamy w aplikacji Backup RouterOS</h1>
|
<h1 class="mt-3">Witamy w aplikacji Backup RouterOS</h1>
|
||||||
<p class="lead">Zarządzaj backupami swoich urządzeń RouterOS w prosty sposób.</p>
|
<p class="lead">Zarządzaj backupami swoich urządzeń RouterOS w prosty sposób.</p>
|
||||||
<div class="mt-4">
|
<div class="mt-4">
|
||||||
@@ -12,3 +16,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
@@ -1,21 +1,23 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
{% block head %}{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container">
|
<div class="container my-5">
|
||||||
<div class="row justify-content-center align-items-center" style="min-height: 100vh;">
|
<div class="row justify-content-center align-items-center">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="card shadow">
|
<div class="card shadow border-0">
|
||||||
<div class="card-header text-center">
|
<div class="card-header bg-light text-center">
|
||||||
<h2>Zaloguj się</h2>
|
<h4 class="mb-0">Zaloguj się</h4>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<form action="{{ url_for('login') }}" method="POST">
|
<form action="{{ url_for('login') }}" method="POST">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="username" class="form-label">Nazwa użytkownika</label>
|
<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>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="password" class="form-label">Hasło</label>
|
<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>
|
||||||
<div class="d-grid">
|
<div class="d-grid">
|
||||||
<button type="submit" class="btn btn-primary">Zaloguj się</button>
|
<button type="submit" class="btn btn-primary">Zaloguj się</button>
|
||||||
@@ -23,7 +25,10 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-footer text-center">
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -10,12 +10,12 @@
|
|||||||
<div class="container my-4">
|
<div class="container my-4">
|
||||||
<h2 class="text-center mb-4">Historia logów operacji</h2>
|
<h2 class="text-center mb-4">Historia logów operacji</h2>
|
||||||
|
|
||||||
<!-- Formularz usuwania logów starszych od podanej liczby dni -->
|
<!-- Karta z usuwaniem logów -->
|
||||||
<div class="card mb-4 shadow-sm">
|
<div class="card mb-4 shadow-sm border-0">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<form action="{{ url_for('delete_old_logs') }}" method="POST" class="row g-2 align-items-center">
|
<form action="{{ url_for('delete_old_logs') }}" method="POST" class="row g-2 align-items-center">
|
||||||
<div class="col-auto">
|
<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>
|
||||||
<div class="col-auto">
|
<div class="col-auto">
|
||||||
<input type="number" class="form-control" id="delete_days" name="delete_days" min="1" placeholder="Liczba dni" required>
|
<input type="number" class="form-control" id="delete_days" name="delete_days" min="1" placeholder="Liczba dni" required>
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Tabela logów -->
|
<!-- Tabela logów -->
|
||||||
<div class="card shadow-sm mb-4">
|
<div class="card shadow-sm border-0 mb-4">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<table id="logsTable" class="table table-striped table-bordered">
|
<table id="logsTable" class="table table-striped table-bordered">
|
||||||
<thead class="table-dark">
|
<thead class="table-dark">
|
||||||
@@ -50,14 +50,14 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="text-center mt-3">
|
<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>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block scripts %}
|
{% block scripts %}
|
||||||
{{ super() }}
|
{{ 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>
|
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||||
<!-- DataTables JS -->
|
<!-- DataTables JS -->
|
||||||
<script src="https://cdn.datatables.net/1.13.4/js/jquery.dataTables.min.js"></script>
|
<script src="https://cdn.datatables.net/1.13.4/js/jquery.dataTables.min.js"></script>
|
||||||
@@ -66,7 +66,10 @@
|
|||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
$('#logsTable').DataTable({
|
$('#logsTable').DataTable({
|
||||||
responsive: true,
|
responsive: true,
|
||||||
order: [[0, 'desc']]
|
order: [[0, 'desc']],
|
||||||
|
language: {
|
||||||
|
url: '//cdn.datatables.net/plug-ins/1.13.4/i18n/pl.json'
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
@@ -1,21 +1,23 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
{% block head %}{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container">
|
<div class="container my-5">
|
||||||
<div class="row justify-content-center align-items-center" style="min-height: 100vh;">
|
<div class="row justify-content-center align-items-center">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="card shadow">
|
<div class="card shadow border-0">
|
||||||
<div class="card-header text-center">
|
<div class="card-header bg-light text-center">
|
||||||
<h2>Rejestracja</h2>
|
<h4 class="mb-0">Rejestracja</h4>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<form action="{{ url_for('register') }}" method="POST">
|
<form action="{{ url_for('register') }}" method="POST">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="username" class="form-label">Nazwa użytkownika</label>
|
<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>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="password" class="form-label">Hasło</label>
|
<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>
|
||||||
<div class="d-grid">
|
<div class="d-grid">
|
||||||
<button type="submit" class="btn btn-primary">Zarejestruj się</button>
|
<button type="submit" class="btn btn-primary">Zarejestruj się</button>
|
||||||
@@ -23,7 +25,10 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-footer text-center">
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -28,6 +28,7 @@
|
|||||||
<h3>Pliki z /export</h3>
|
<h3>Pliki z /export</h3>
|
||||||
{% if export_backups %}
|
{% if export_backups %}
|
||||||
<!-- Tabela z indywidualnymi akcjami -->
|
<!-- Tabela z indywidualnymi akcjami -->
|
||||||
|
<div class="table-responsive">
|
||||||
<table class="table table-bordered">
|
<table class="table table-bordered">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -54,7 +55,6 @@
|
|||||||
<td>
|
<td>
|
||||||
<a href="{{ url_for('download_file', filename=b.file_path|basename) }}" class="btn btn-sm btn-info">Pobierz</a>
|
<a href="{{ url_for('download_file', filename=b.file_path|basename) }}" class="btn btn-sm btn-info">Pobierz</a>
|
||||||
<a href="{{ url_for('view_export', backup_id=b.id) }}" class="btn btn-sm btn-outline-primary">Podgląd</a>
|
<a href="{{ url_for('view_export', backup_id=b.id) }}" class="btn btn-sm btn-outline-primary">Podgląd</a>
|
||||||
|
|
||||||
<form action="{{ url_for('send_by_email', backup_id=b.id) }}" method="POST" style="display: inline;">
|
<form action="{{ url_for('send_by_email', backup_id=b.id) }}" method="POST" style="display: inline;">
|
||||||
<button type="submit" class="btn btn-sm btn-primary">Wyślij mailem</button>
|
<button type="submit" class="btn btn-sm btn-primary">Wyślij mailem</button>
|
||||||
</form>
|
</form>
|
||||||
@@ -67,8 +67,11 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Formularz do pobierania ZIP zaznaczonych eksportów -->
|
<!-- Formularz do pobierania ZIP zaznaczonych eksportów -->
|
||||||
<h4>Pobierz wybrane pliki z /export jako zip</h4>
|
<h4>Pobierz wybrane pliki z /export jako zip</h4>
|
||||||
|
<div class="table-responsive">
|
||||||
<form action="{{ url_for('download_zip') }}" method="POST">
|
<form action="{{ url_for('download_zip') }}" method="POST">
|
||||||
<table class="table table-bordered">
|
<table class="table table-bordered">
|
||||||
<thead>
|
<thead>
|
||||||
@@ -86,12 +89,13 @@
|
|||||||
<td>{{ b.file_path|basename }}</td>
|
<td>{{ b.file_path|basename }}</td>
|
||||||
<td>{{ b.file_path|filesize }}</td>
|
<td>{{ b.file_path|filesize }}</td>
|
||||||
<td>{{ b.created_at }}</td>
|
<td>{{ b.created_at }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<button type="submit" class="btn btn-success">Pobierz zaznaczone (.zip)</button>
|
<button type="submit" class="btn btn-success">Pobierz zaznaczone (.zip)</button>
|
||||||
</form>
|
</form>
|
||||||
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<p class="text-muted">Pusto</p>
|
<p class="text-muted">Pusto</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -101,6 +105,7 @@
|
|||||||
<!-- Sekcja backupów binarnych -->
|
<!-- Sekcja backupów binarnych -->
|
||||||
<h3>Pliki binarne (.backup)</h3>
|
<h3>Pliki binarne (.backup)</h3>
|
||||||
{% if binary_backups %}
|
{% if binary_backups %}
|
||||||
|
<div class="table-responsive">
|
||||||
<table class="table table-bordered">
|
<table class="table table-bordered">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -118,7 +123,6 @@
|
|||||||
<td>{{ b.created_at }}</td>
|
<td>{{ b.created_at }}</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="{{ url_for('download_file', filename=b.file_path|basename) }}" class="btn btn-sm btn-info">Pobierz</a>
|
<a href="{{ url_for('download_file', filename=b.file_path|basename) }}" class="btn btn-sm btn-info">Pobierz</a>
|
||||||
|
|
||||||
<form action="{{ url_for('upload_backup', router_id=router.id, backup_id=b.id) }}" method="POST" class="d-inline">
|
<form action="{{ url_for('upload_backup', router_id=router.id, backup_id=b.id) }}" method="POST" class="d-inline">
|
||||||
<button type="submit" class="btn btn-sm btn-secondary">Wgraj do routera</button>
|
<button type="submit" class="btn btn-sm btn-secondary">Wgraj do routera</button>
|
||||||
</form>
|
</form>
|
||||||
@@ -134,9 +138,11 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Formularz do pobierania ZIP zaznaczonych backupów binarnych -->
|
<!-- Formularz do pobierania ZIP zaznaczonych backupów binarnych -->
|
||||||
<h4>Pobierz wybrane backupy binarne jako zip</h4>
|
<h4>Pobierz wybrane backupy binarne jako zip</h4>
|
||||||
|
<div class="table-responsive">
|
||||||
<form action="{{ url_for('download_zip') }}" method="POST">
|
<form action="{{ url_for('download_zip') }}" method="POST">
|
||||||
<table class="table table-bordered">
|
<table class="table table-bordered">
|
||||||
<thead>
|
<thead>
|
||||||
@@ -160,6 +166,7 @@
|
|||||||
</table>
|
</table>
|
||||||
<button type="submit" class="btn btn-success">Pobierz zaznaczone (.zip)</button>
|
<button type="submit" class="btn btn-success">Pobierz zaznaczone (.zip)</button>
|
||||||
</form>
|
</form>
|
||||||
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<p class="text-muted">Pusto</p>
|
<p class="text-muted">Pusto</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@@ -1,17 +1,17 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="text-end mb-3">
|
|
||||||
{% 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">
|
<div class="container my-4">
|
||||||
<!-- Informacje o routerze -->
|
<div class="mb-3 text-end">
|
||||||
<div class="card shadow-sm mb-4">
|
{% if current_view == 'v1' %}
|
||||||
<div class="card-header">
|
<a href="{{ url_for('router_details', router_id=router.id, view='v2') }}" class="btn btn-outline-secondary">Przełącz na widok v2</a>
|
||||||
<h2 class="mb-0">Router: {{ router.name }}</h2>
|
{% 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="card border-0 shadow-sm mb-4">
|
||||||
|
<div class="card-header bg-light">
|
||||||
|
<h4 class="mb-0">Router: {{ router.name }}</h4>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<p>
|
<p>
|
||||||
@@ -20,7 +20,6 @@
|
|||||||
<strong>SSH User:</strong> {{ router.ssh_user }}
|
<strong>SSH User:</strong> {{ router.ssh_user }}
|
||||||
</p>
|
</p>
|
||||||
<div class="d-flex flex-wrap gap-2">
|
<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">
|
<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>
|
<button type="submit" class="btn btn-primary btn-sm">Wykonaj /export</button>
|
||||||
</form>
|
</form>
|
||||||
@@ -35,16 +34,21 @@
|
|||||||
<!-- Zakładki z backupami -->
|
<!-- Zakładki z backupami -->
|
||||||
<ul class="nav nav-tabs" id="routerTab" role="tablist">
|
<ul class="nav nav-tabs" id="routerTab" role="tablist">
|
||||||
<li class="nav-item" role="presentation">
|
<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>
|
||||||
<li class="nav-item" role="presentation">
|
<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>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<div class="tab-content" id="routerTabContent">
|
<div class="tab-content" id="routerTabContent">
|
||||||
<!-- Zakładka /export -->
|
<!-- Zakładka /export -->
|
||||||
<div class="tab-pane fade show active" id="export" role="tabpanel" aria-labelledby="export-tab">
|
<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">
|
<div class="card-body">
|
||||||
{% if export_backups %}
|
{% if export_backups %}
|
||||||
<!-- Formularz masowych akcji dla eksportów -->
|
<!-- Formularz masowych akcji dla eksportów -->
|
||||||
@@ -55,76 +59,79 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<!-- Tabela z eksportami -->
|
|
||||||
<table class="table table-bordered table-striped">
|
<div class="table-responsive">
|
||||||
<thead class="table-dark">
|
<table class="table table-bordered table-striped align-middle">
|
||||||
<tr>
|
<thead class="table-dark">
|
||||||
<th style="width: 3%;"><input type="checkbox" id="select_all_export"></th>
|
<tr>
|
||||||
<th>Nazwa pliku</th>
|
<th style="width: 3%;"><input type="checkbox" id="select_all_export"></th>
|
||||||
<th>Rozmiar</th>
|
<th>Nazwa pliku</th>
|
||||||
<th>Data</th>
|
<th>Rozmiar</th>
|
||||||
<th>Diff</th>
|
<th>Data</th>
|
||||||
<th>Pobierz</th>
|
<th>Diff</th>
|
||||||
<th>Podgląd</th>
|
<th>Pobierz</th>
|
||||||
<th>Wyślij mailem</th>
|
<th>Podgląd</th>
|
||||||
<th>Usuń</th>
|
<th>Wyślij mailem</th>
|
||||||
</tr>
|
<th>Usuń</th>
|
||||||
</thead>
|
</tr>
|
||||||
<tbody>
|
</thead>
|
||||||
{% for b in export_backups %}
|
<tbody>
|
||||||
<tr>
|
{% for b in export_backups %}
|
||||||
<td>
|
<tr>
|
||||||
<input type="checkbox" name="backup_id" value="{{ b.id }}" form="export_mass_actions_form">
|
<td>
|
||||||
</td>
|
<input type="checkbox" name="backup_id" value="{{ b.id }}" form="export_mass_actions_form">
|
||||||
<td>{{ b.file_path|basename }}</td>
|
</td>
|
||||||
<td>{{ b.file_path|filesize }}</td>
|
<td>{{ b.file_path|basename }}</td>
|
||||||
<td>{{ b.created_at.strftime("%Y-%m-%d %H:%M:%S") }}</td>
|
<td>{{ b.file_path|filesize }}</td>
|
||||||
<td>
|
<td>{{ b.created_at.strftime("%Y-%m-%d %H:%M:%S") }}</td>
|
||||||
{% if loop.index0 > 0 %}
|
<td>
|
||||||
<a href="{{ url_for('diff_view', backup_id1=b.id, backup_id2=export_backups[0].id) }}" class="btn btn-sm btn-info">Diff</a>
|
{% if loop.index0 > 0 %}
|
||||||
{% else %}
|
<a href="{{ url_for('diff_view', backup_id1=b.id, backup_id2=export_backups[0].id) }}" class="btn btn-sm btn-info">Diff</a>
|
||||||
<small>Brak nowszego</small>
|
{% else %}
|
||||||
{% endif %}
|
<small>Brak nowszego</small>
|
||||||
</td>
|
{% endif %}
|
||||||
<td>
|
</td>
|
||||||
<a href="{{ url_for('download_file', filename=b.file_path|basename) }}" class="btn btn-info btn-sm" title="Pobierz">
|
<td>
|
||||||
<i class="bi bi-download"></i>
|
<a href="{{ url_for('download_file', filename=b.file_path|basename) }}" class="btn btn-info btn-sm" title="Pobierz">
|
||||||
</a>
|
<i class="bi bi-download"></i>
|
||||||
</td>
|
</a>
|
||||||
<td>
|
</td>
|
||||||
<a href="{{ url_for('view_export', backup_id=b.id) }}" class="btn btn-outline-primary btn-sm" title="Podgląd">
|
<td>
|
||||||
<i class="bi bi-eye"></i>
|
<a href="{{ url_for('view_export', backup_id=b.id) }}" class="btn btn-outline-primary btn-sm" title="Podgląd">
|
||||||
</a>
|
<i class="bi bi-eye"></i>
|
||||||
</td>
|
</a>
|
||||||
<td>
|
</td>
|
||||||
<form action="{{ url_for('send_by_email', backup_id=b.id) }}" method="POST" class="d-inline">
|
<td>
|
||||||
<input type="hidden" name="next" value="{{ url_for('router_details', router_id=router.id) }}">
|
<form action="{{ url_for('send_by_email', backup_id=b.id) }}" method="POST" class="d-inline">
|
||||||
<button type="submit" class="btn btn-primary btn-sm" title="Wyślij mailem">
|
<input type="hidden" name="next" value="{{ url_for('router_details', router_id=router.id) }}">
|
||||||
<i class="bi bi-envelope"></i>
|
<button type="submit" class="btn btn-primary btn-sm" title="Wyślij mailem">
|
||||||
</button>
|
<i class="bi bi-envelope"></i>
|
||||||
</form>
|
</button>
|
||||||
</td>
|
</form>
|
||||||
<td>
|
</td>
|
||||||
<form action="{{ url_for('delete_backup', backup_id=b.id) }}" method="POST" class="d-inline" onsubmit="return confirm('Na pewno usunąć backup?');">
|
<td>
|
||||||
<input type="hidden" name="next" value="{{ url_for('router_details', router_id=router.id) }}">
|
<form action="{{ url_for('delete_backup', backup_id=b.id) }}" method="POST" class="d-inline" onsubmit="return confirm('Na pewno usunąć backup?');">
|
||||||
<button type="submit" class="btn btn-danger btn-sm" title="Usuń">
|
<input type="hidden" name="next" value="{{ url_for('router_details', router_id=router.id) }}">
|
||||||
<i class="bi bi-trash"></i>
|
<button type="submit" class="btn btn-danger btn-sm" title="Usuń">
|
||||||
</button>
|
<i class="bi bi-trash"></i>
|
||||||
</form>
|
</button>
|
||||||
</td>
|
</form>
|
||||||
</tr>
|
</td>
|
||||||
{% endfor %}
|
</tr>
|
||||||
</tbody>
|
{% endfor %}
|
||||||
</table>
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<p class="text-muted">Brak plików /export.</p>
|
<p class="text-muted">Brak plików /export.</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Zakładka backupów binarnych -->
|
<!-- Zakładka backupów binarnych -->
|
||||||
<div class="tab-pane fade" id="binary" role="tabpanel" aria-labelledby="binary-tab">
|
<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">
|
<div class="card-body">
|
||||||
{% if binary_backups %}
|
{% if binary_backups %}
|
||||||
<!-- Formularz masowych akcji dla backupów binarnych -->
|
<!-- Formularz masowych akcji dla backupów binarnych -->
|
||||||
@@ -135,63 +142,64 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<!-- Tabela z backupami binarnymi -->
|
|
||||||
<table class="table table-bordered table-striped">
|
<div class="table-responsive">
|
||||||
<thead class="table-dark">
|
<table class="table table-bordered table-striped align-middle">
|
||||||
<tr>
|
<thead class="table-dark">
|
||||||
<th style="width: 3%;"><input type="checkbox" id="select_all_binary"></th>
|
<tr>
|
||||||
<th>Nazwa pliku</th>
|
<th style="width: 3%;"><input type="checkbox" id="select_all_binary"></th>
|
||||||
<th>Rozmiar</th>
|
<th>Nazwa pliku</th>
|
||||||
<th>Data</th>
|
<th>Rozmiar</th>
|
||||||
<th>Pobierz</th>
|
<th>Data</th>
|
||||||
<th>Wgraj do routera</th>
|
<th>Pobierz</th>
|
||||||
<th>Wyślij mailem</th>
|
<th>Wgraj do routera</th>
|
||||||
<th>Usuń</th>
|
<th>Wyślij mailem</th>
|
||||||
</tr>
|
<th>Usuń</th>
|
||||||
</thead>
|
</tr>
|
||||||
<tbody>
|
</thead>
|
||||||
{% for b in binary_backups %}
|
<tbody>
|
||||||
<tr>
|
{% for b in binary_backups %}
|
||||||
<td>
|
<tr>
|
||||||
<input type="checkbox" name="backup_id" value="{{ b.id }}" form="binary_mass_actions_form">
|
<td>
|
||||||
</td>
|
<input type="checkbox" name="backup_id" value="{{ b.id }}" form="binary_mass_actions_form">
|
||||||
<td>
|
</td>
|
||||||
<!-- Dodaj tooltip z sumą kontrolną -->
|
<td>
|
||||||
<span data-bs-toggle="tooltip" title="Checksum: {{ b.checksum }}">{{ b.file_path|basename }}</span>
|
<span data-bs-toggle="tooltip" title="Checksum: {{ b.checksum }}">{{ b.file_path|basename }}</span>
|
||||||
</td>
|
</td>
|
||||||
<td>{{ b.file_path|filesize }}</td>
|
<td>{{ b.file_path|filesize }}</td>
|
||||||
<td>{{ b.created_at.strftime("%Y-%m-%d %H:%M:%S") }}</td>
|
<td>{{ b.created_at.strftime("%Y-%m-%d %H:%M:%S") }}</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="{{ url_for('download_file', filename=b.file_path|basename) }}" class="btn btn-info btn-sm" title="Pobierz">
|
<a href="{{ url_for('download_file', filename=b.file_path|basename) }}" class="btn btn-info btn-sm" title="Pobierz">
|
||||||
<i class="bi bi-download"></i>
|
<i class="bi bi-download"></i>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<form action="{{ url_for('upload_backup', router_id=router.id, backup_id=b.id) }}" method="POST" class="d-inline">
|
<form action="{{ url_for('upload_backup', router_id=router.id, backup_id=b.id) }}" method="POST" class="d-inline">
|
||||||
<button type="submit" class="btn btn-secondary btn-sm" title="Wgraj do routera">
|
<button type="submit" class="btn btn-secondary btn-sm" title="Wgraj do routera">
|
||||||
<i class="bi bi-upload"></i>
|
<i class="bi bi-upload"></i>
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<form action="{{ url_for('send_by_email', backup_id=b.id) }}" method="POST" class="d-inline">
|
<form action="{{ url_for('send_by_email', backup_id=b.id) }}" method="POST" class="d-inline">
|
||||||
<button type="submit" class="btn btn-primary btn-sm" title="Wyślij mailem">
|
<button type="submit" class="btn btn-primary btn-sm" title="Wyślij mailem">
|
||||||
<i class="bi bi-envelope"></i>
|
<i class="bi bi-envelope"></i>
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<form action="{{ url_for('delete_backup', backup_id=b.id) }}" method="POST" class="d-inline" onsubmit="return confirm('Na pewno usunąć backup?');">
|
<form action="{{ url_for('delete_backup', backup_id=b.id) }}" method="POST" class="d-inline" onsubmit="return confirm('Na pewno usunąć backup?');">
|
||||||
<input type="hidden" name="next" value="{{ url_for('router_details', router_id=router.id) }}">
|
<input type="hidden" name="next" value="{{ url_for('router_details', router_id=router.id) }}">
|
||||||
<button type="submit" class="btn btn-danger btn-sm" title="Usuń">
|
<button type="submit" class="btn btn-danger btn-sm" title="Usuń">
|
||||||
<i class="bi bi-trash"></i>
|
<i class="bi bi-trash"></i>
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<p class="text-muted">Brak plików binarnych.</p>
|
<p class="text-muted">Brak plików binarnych.</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -212,7 +220,7 @@ document.getElementById('select_all_binary').addEventListener('change', function
|
|||||||
checkboxes.forEach(cb => cb.checked = e.target.checked);
|
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'));
|
var triggerTabList = [].slice.call(document.querySelectorAll('#routerTab button'));
|
||||||
triggerTabList.forEach(function (triggerEl) {
|
triggerTabList.forEach(function (triggerEl) {
|
||||||
var tabTrigger = new bootstrap.Tab(triggerEl);
|
var tabTrigger = new bootstrap.Tab(triggerEl);
|
||||||
@@ -222,7 +230,7 @@ triggerTabList.forEach(function (triggerEl) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Inicjalizacja tooltipów Bootstrap
|
// Inicjalizacja tooltipów
|
||||||
var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
|
var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
|
||||||
tooltipTriggerList.forEach(function (tooltipTriggerEl) {
|
tooltipTriggerList.forEach(function (tooltipTriggerEl) {
|
||||||
new bootstrap.Tooltip(tooltipTriggerEl);
|
new bootstrap.Tooltip(tooltipTriggerEl);
|
||||||
|
@@ -1,63 +1,67 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container my-4">
|
<div class="container my-4">
|
||||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
<div class="card border-0 shadow-sm">
|
||||||
<h2>Moje Routery</h2>
|
<div class="card-header bg-light d-flex justify-content-between align-items-center">
|
||||||
<a href="{{ url_for('add_router') }}" class="btn btn-success">
|
<h4 class="mb-0">Lista urządzeń</h4>
|
||||||
<i class="bi bi-plus-lg"></i> Dodaj nowe urządzenie
|
<a href="{{ url_for('add_router') }}" class="btn btn-success">
|
||||||
</a>
|
<i class="bi bi-plus-lg"></i> Dodaj nowe urządzenie
|
||||||
</div>
|
</a>
|
||||||
<div class="table-responsive">
|
</div>
|
||||||
<table class="table table-striped table-hover">
|
<div class="card-body">
|
||||||
<thead class="table-primary">
|
<div class="table-responsive-sm">
|
||||||
<tr>
|
<table class="table table-striped table-hover align-middle">
|
||||||
<th>Nazwa</th>
|
<thead class="table-primary">
|
||||||
<th>Host</th>
|
<tr>
|
||||||
<th>Port</th>
|
<th>Nazwa</th>
|
||||||
<th>Exporty</th>
|
<th>Host</th>
|
||||||
<th>Backupy binarne</th>
|
<th>Port</th>
|
||||||
<th>Test Połączenia</th>
|
<th>Exporty</th>
|
||||||
<th>Akcje</th>
|
<th>Backupy binarne</th>
|
||||||
</tr>
|
<th>Test Połączenia</th>
|
||||||
</thead>
|
<th>Akcje</th>
|
||||||
<tbody>
|
</tr>
|
||||||
{% for router in routers %}
|
</thead>
|
||||||
<tr>
|
<tbody>
|
||||||
<td>{{ router.name }}</td>
|
{% for router in routers %}
|
||||||
<td>{{ router.host }}</td>
|
<tr>
|
||||||
<td>{{ router.port }}</td>
|
<td>{{ router.name }}</td>
|
||||||
<td>
|
<td>{{ router.host }}</td>
|
||||||
<span class="badge bg-success">
|
<td>{{ router.port }}</td>
|
||||||
{{ router.backups|selectattr("backup_type", "equalto", "export")|list|length }}
|
<td>
|
||||||
</span>
|
<span class="badge bg-success">
|
||||||
</td>
|
{{ router.backups|selectattr("backup_type", "equalto", "export")|list|length }}
|
||||||
<td>
|
</span>
|
||||||
<span class="badge bg-info">
|
</td>
|
||||||
{{ router.backups|selectattr("backup_type", "equalto", "binary")|list|length }}
|
<td>
|
||||||
</span>
|
<span class="badge bg-info">
|
||||||
</td>
|
{{ router.backups|selectattr("backup_type", "equalto", "binary")|list|length }}
|
||||||
<td>
|
</span>
|
||||||
<button type="button" class="btn btn-sm btn-info" onclick="openTestConnectionModal({{ router.id }})">
|
</td>
|
||||||
<i class="bi bi-wifi"></i> Test
|
<td>
|
||||||
</button>
|
<button type="button" class="btn btn-sm btn-info" onclick="openTestConnectionModal({{ router.id }})">
|
||||||
</td>
|
<i class="bi bi-wifi"></i> Test
|
||||||
<td>
|
</button>
|
||||||
<a href="{{ url_for('router_details', router_id=router.id) }}" class="btn btn-sm btn-primary">
|
</td>
|
||||||
<i class="bi bi-eye"></i> Szczegóły
|
<td>
|
||||||
</a>
|
<a href="{{ url_for('router_details', router_id=router.id) }}" class="btn btn-sm btn-primary">
|
||||||
<a href="{{ url_for('edit_router', router_id=router.id) }}" class="btn btn-sm btn-warning">
|
<i class="bi bi-eye"></i> Szczegóły
|
||||||
<i class="bi bi-pencil"></i> Edytuj
|
</a>
|
||||||
</a>
|
<a href="{{ url_for('edit_router', router_id=router.id) }}" class="btn btn-sm btn-warning">
|
||||||
<form action="{{ url_for('delete_router', router_id=router.id) }}" method="POST" class="d-inline">
|
<i class="bi bi-pencil"></i> Edytuj
|
||||||
<button type="submit" class="btn btn-sm btn-danger" onclick="return confirm('Na pewno usunąć urządzenie?');">
|
</a>
|
||||||
<i class="bi bi-trash"></i> Usuń
|
<form action="{{ url_for('delete_router', router_id=router.id) }}" method="POST" class="d-inline" onsubmit="return confirm('Na pewno usunąć urządzenie?');">
|
||||||
</button>
|
<button type="submit" class="btn btn-sm btn-danger">
|
||||||
</form>
|
<i class="bi bi-trash"></i> Usuń
|
||||||
</td>
|
</button>
|
||||||
</tr>
|
</form>
|
||||||
{% endfor %}
|
</td>
|
||||||
</tbody>
|
</tr>
|
||||||
</table>
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@@ -1,15 +1,16 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container my-5">
|
<div class="container my-4">
|
||||||
<div class="card shadow-sm">
|
<div class="card border-0 shadow-sm">
|
||||||
<div class="card-header">
|
<div class="card-header bg-light">
|
||||||
<h2 class="mb-0">Ustawienia globalne</h2>
|
<h4 class="mb-0">Ustawienia globalne</h4>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<form method="POST">
|
<form method="POST">
|
||||||
|
|
||||||
<!-- Sekcja Pushover -->
|
<!-- Sekcja Pushover -->
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<h4 class="mb-3">Powiadomienia - Pushover</h4>
|
<h5 class="mb-3">Powiadomienia - Pushover</h5>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="pushover_token" class="form-label">Pushover Token</label>
|
<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 }}">
|
<input type="text" class="form-control" id="pushover_token" name="pushover_token" value="{{ settings.pushover_token }}">
|
||||||
@@ -24,10 +25,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
<!-- Sekcja SMTP -->
|
<!-- Sekcja SMTP -->
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<h4 class="mb-3">Powiadomienia - SMTP (e-mail)</h4>
|
<h5 class="mb-3">Powiadomienia - SMTP (e-mail)</h5>
|
||||||
<div class="mb-3 form-check">
|
<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 %}>
|
<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>
|
<label class="form-check-label" for="smtp_notifications_enabled">Włącz powiadomienia SMTP</label>
|
||||||
</div>
|
</div>
|
||||||
@@ -47,25 +49,43 @@
|
|||||||
<label for="smtp_password" class="form-label">SMTP Hasło</label>
|
<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 }}">
|
<input type="password" class="form-control" id="smtp_password" name="smtp_password" value="{{ settings.smtp_password }}">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<hr>
|
|
||||||
<!-- Sekcja globalnego klucza SSH -->
|
|
||||||
<div class="mb-4">
|
|
||||||
<h4 class="mb-3">Globalny klucz SSH</h4>
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="global_ssh_key" class="form-label">
|
<label for="recipient_email" class="form-label">Adres e-mail docelowy</label>
|
||||||
Wklej wraz z <code>-----BEGIN RSA PRIVATE KEY-----</code> i <code>-----END RSA PRIVATE KEY-----</code>
|
<input type="email" class="form-control" id="recipient_email" name="recipient_email" value="{{ settings.recipient_email }}">
|
||||||
</label>
|
|
||||||
<textarea class="form-control" id="global_ssh_key" name="global_ssh_key" rows="4">{{ settings.global_ssh_key }}</textarea>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<!-- Sekcja globalnego klucza SSH -->
|
||||||
|
<div class="mb-4">
|
||||||
|
<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 class="d-grid">
|
<div class="d-grid">
|
||||||
<button type="submit" class="btn btn-primary btn-lg">Zapisz ustawienia</button>
|
<button type="submit" class="btn btn-primary btn-lg">Zapisz ustawienia</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
<!-- Przycisk do testowania powiadomień -->
|
||||||
|
<div class="mt-4 text-center">
|
||||||
|
<form method="POST" action="{{ url_for('test_email') }}" class="d-inline">
|
||||||
|
<button type="submit" class="btn btn-info">Testuj wysyłkę e-mail</button>
|
||||||
|
</form>
|
||||||
|
<form method="POST" action="{{ url_for('test_pushover') }}" class="d-inline ms-2">
|
||||||
|
<button type="submit" class="btn btn-warning">Testuj powiadomienie Pushover</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card-footer text-center">
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -1,30 +1,42 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container my-4">
|
<div class="container my-4">
|
||||||
<h2>Podgląd eksportu: {{ backup.file_path|basename }}</h2>
|
<div class="card border-0 shadow">
|
||||||
<hr>
|
<div class="card-header bg-light">
|
||||||
<textarea id="exportEditor" readonly>{{ content|e }}</textarea>
|
<h4 class="mb-0">
|
||||||
<a href="{{ next_url }}" class="btn btn-secondary">Powrót</a>
|
Podgląd eksportu: {{ backup.file_path|basename }}
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<textarea id="exportEditor" readonly>{{ content|e }}</textarea>
|
||||||
|
<div class="mt-3">
|
||||||
|
<a href="{{ next_url }}" class="btn btn-secondary">Powrót</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- CodeMirror CSS -->
|
<!-- CodeMirror CSS -->
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.5/codemirror.min.css">
|
{% if session.get('dark_mode', True) %}
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.5/theme/darcula.min.css">
|
||||||
|
{% else %}
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.5/theme/neo.min.css">
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.5/theme/neo.min.css">
|
||||||
|
{% endif %}
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.5/codemirror.min.css">
|
||||||
|
|
||||||
<!-- CodeMirror JS -->
|
<!-- CodeMirror JS -->
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.5/codemirror.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.5/codemirror.min.js"></script>
|
||||||
<!-- Dodajemy tryb dla plików shell, który dobrze radzi sobie z konfiguracjami RouterOS -->
|
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.5/mode/shell/shell.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.5/mode/shell/shell.min.js"></script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener("DOMContentLoaded", function() {
|
document.addEventListener("DOMContentLoaded", function() {
|
||||||
var editor = CodeMirror.fromTextArea(document.getElementById("exportEditor"), {
|
var editor = CodeMirror.fromTextArea(document.getElementById("exportEditor"), {
|
||||||
mode: "text/x-sh",
|
mode: "text/x-sh",
|
||||||
theme: "neo",
|
theme: "{{ 'darcula' if session.get('dark_mode', True) else 'neo' }}",
|
||||||
lineNumbers: true,
|
lineNumbers: true,
|
||||||
readOnly: true
|
readOnly: true
|
||||||
});
|
});
|
||||||
// Dopasowanie rozmiaru edytora do zawartości
|
editor.setSize("100%", "600px");
|
||||||
editor.setSize("100%", "800px");
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
Reference in New Issue
Block a user