naprawa błędów i nowe funkcje
This commit is contained in:
81
app.py
81
app.py
@@ -9,18 +9,16 @@ import re
|
||||
import smtplib
|
||||
import shutil
|
||||
import socket
|
||||
import hashlib
|
||||
from datetime import datetime
|
||||
from ftplib import FTP
|
||||
from email.mime.base import MIMEBase
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
from email.mime.text import MIMEText
|
||||
from email import encoders
|
||||
from flask import jsonify
|
||||
from flask import Flask
|
||||
import shutil
|
||||
from datetime import datetime
|
||||
from difflib import HtmlDiff
|
||||
import difflib
|
||||
from datetime import datetime, timedelta
|
||||
from sqlalchemy import text
|
||||
|
||||
@@ -87,6 +85,8 @@ class Backup(db.Model):
|
||||
file_path = db.Column(db.String(255), nullable=False) # Ścieżka do pliku
|
||||
backup_type = db.Column(db.String(50), default='export') # 'export' lub 'binary'
|
||||
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
checksum = db.Column(db.String(64), nullable=True)
|
||||
|
||||
class OperationLog(db.Model):
|
||||
__tablename__ = 'operation_logs'
|
||||
__table_args__ = {'extend_existing': True} # Zapobiega redefinicji tabeli
|
||||
@@ -112,6 +112,7 @@ class GlobalSettings(db.Model):
|
||||
smtp_login = 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)
|
||||
log_retention_days = db.Column(db.Integer, default=7)
|
||||
|
||||
###############################################################################
|
||||
# Inicjalizacja bazy
|
||||
@@ -166,6 +167,13 @@ def load_pkey(ssh_key_str: str):
|
||||
except Exception as e:
|
||||
raise ValueError("Nie udało się załadować klucza SSH. Sprawdź, czy klucz jest poprawny i nie jest zaszyfrowany") from e
|
||||
|
||||
def compute_checksum(file_path):
|
||||
sha256 = hashlib.sha256()
|
||||
with open(file_path, 'rb') as f:
|
||||
for chunk in iter(lambda: f.read(4096), b""):
|
||||
sha256.update(chunk)
|
||||
return sha256.hexdigest()
|
||||
|
||||
###############################################################################
|
||||
# Funkcje SSH
|
||||
###############################################################################
|
||||
@@ -229,12 +237,17 @@ def ssh_backup(router: Router, backup_name: str) -> str:
|
||||
print(f"[DEBUG] ssh_backup -> local_path={local_path}")
|
||||
return local_path
|
||||
|
||||
def ssh_upload_backup(router: Router, local_backup_path: str):
|
||||
print(f"[DEBUG] ssh_upload_backup -> router id={router.id}, local_backup_path={local_backup_path}")
|
||||
def ssh_upload_backup(router: Router, local_backup_path: str, expected_checksum: str = None):
|
||||
# Weryfikacja sumy kontrolnej, jeśli podana
|
||||
if expected_checksum:
|
||||
local_checksum = compute_checksum(local_backup_path)
|
||||
if local_checksum != expected_checksum:
|
||||
raise ValueError("Suma kontrolna pliku nie zgadza się – plik może być uszkodzony.")
|
||||
|
||||
client = paramiko.SSHClient()
|
||||
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
|
||||
# Używamy indywidualnego klucza, a jeśli nie ma, to globalnego
|
||||
# Wybór klucza: indywidualny lub globalny
|
||||
key_source = router.ssh_key if router.ssh_key and router.ssh_key.strip() else get_settings().global_ssh_key
|
||||
if key_source and key_source.strip():
|
||||
try:
|
||||
@@ -245,8 +258,10 @@ def ssh_upload_backup(router: Router, local_backup_path: str):
|
||||
raise e
|
||||
else:
|
||||
client.connect(router.host, port=router.port, username=router.ssh_user,
|
||||
password=router.ssh_password, timeout=10, allow_agent=False, look_for_keys=False, banner_timeout=10)
|
||||
password=router.ssh_password, timeout=10,
|
||||
allow_agent=False, look_for_keys=False, banner_timeout=10)
|
||||
|
||||
# Otwieramy sesję SFTP, przesyłamy plik, a następnie zamykamy połączenie
|
||||
sftp = client.open_sftp()
|
||||
remote_file = os.path.basename(local_backup_path)
|
||||
sftp.put(local_backup_path, remote_file)
|
||||
@@ -254,6 +269,7 @@ def ssh_upload_backup(router: Router, local_backup_path: str):
|
||||
client.close()
|
||||
print(f"[DEBUG] ssh_upload_backup -> przesłano {local_backup_path} do routera")
|
||||
|
||||
|
||||
def ssh_test_connection(router: Router) -> dict:
|
||||
"""Testuje połączenie z routerem i zwraca informacje: model, uptime, hostname."""
|
||||
client = paramiko.SSHClient()
|
||||
@@ -504,7 +520,8 @@ def scheduled_auto_binary_backup():
|
||||
try:
|
||||
backup_name = f"{r.name}_{r.id}_{datetime.now():%Y%m%d_%H%M%S}"
|
||||
local_path = ssh_backup(r, backup_name)
|
||||
b = Backup(router_id=r.id, file_path=local_path, backup_type='binary')
|
||||
checksum = compute_checksum(local_path)
|
||||
b = Backup(router_id=r.id, file_path=local_path, backup_type='binary', checksum=checksum)
|
||||
db.session.add(b)
|
||||
db.session.commit()
|
||||
notify(s, f"Auto-binary backup dla routera {r.name} OK", True)
|
||||
@@ -720,12 +737,12 @@ def advanced_schedule():
|
||||
s = get_settings()
|
||||
if request.method == 'POST':
|
||||
s.retention_cron = request.form.get('retention_cron', '').strip()
|
||||
s.binary_cron = request.form.get('binary_cron', '').strip() # nowe pole
|
||||
s.binary_cron = request.form.get('binary_cron', '').strip()
|
||||
s.export_cron = request.form.get('export_cron', '').strip()
|
||||
# Checkbox: jeśli nie jest zaznaczony, nie pojawi się w formularzu, więc ustawiamy na False
|
||||
s.backup_retention_days = int(request.form.get('backup_retention_days', s.backup_retention_days))
|
||||
s.enable_auto_export = True if request.form.get('enable_auto_export') == 'on' else False
|
||||
db.session.commit()
|
||||
reschedule_jobs() # Aktualizuje harmonogram zadań
|
||||
reschedule_jobs() # Aktualizacja harmonogramu zadań
|
||||
flash("Zaawansowane ustawienia harmonogramu zostały zapisane.")
|
||||
return redirect(url_for('advanced_schedule'))
|
||||
return render_template('advanced_schedule.html', settings=s)
|
||||
@@ -866,7 +883,8 @@ def router_backup(router_id):
|
||||
try:
|
||||
backup_name = f"{router.name}_{router.id}_{datetime.now():%Y%m%d_%H%M%S}"
|
||||
local_path = ssh_backup(router, backup_name)
|
||||
b = Backup(router_id=router.id, file_path=local_path, backup_type='binary')
|
||||
checksum = compute_checksum(local_path)
|
||||
b = Backup(router_id=router.id, file_path=local_path, backup_type='binary', checksum=checksum)
|
||||
db.session.add(b)
|
||||
db.session.commit()
|
||||
notify(get_settings(), f"Backup {router.name} OK", True)
|
||||
@@ -890,13 +908,19 @@ def upload_backup(router_id, backup_id):
|
||||
if not b:
|
||||
flash("Nie znaleziono backupu binarnego.")
|
||||
return redirect(url_for('router_details', router_id=router.id))
|
||||
|
||||
# Sprawdź sumę kontrolną pliku przed wgraniem
|
||||
local_checksum = compute_checksum(b.file_path)
|
||||
if b.checksum != local_checksum:
|
||||
flash("Błąd: suma kontrolna backupu nie zgadza się – plik może być uszkodzony.")
|
||||
return redirect(url_for('router_details', router_id=router.id))
|
||||
|
||||
try:
|
||||
ssh_upload_backup(router, b.file_path)
|
||||
ssh_upload_backup(router, b.file_path, expected_checksum=b.checksum)
|
||||
log_operation(f"Backup {os.path.basename(b.file_path)} wgrany do routera {router.name} at {datetime.utcnow()}")
|
||||
flash("Plik backupu wgrany do routera.")
|
||||
except Exception as e:
|
||||
flash(f"Błąd wgrywania: {e}")
|
||||
#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)
|
||||
|
||||
@@ -1325,6 +1349,35 @@ def test_connection(router_id):
|
||||
return render_template("test_connection_modal.html", router=router, result=result)
|
||||
return render_template("test_connection.html", router=router, result=result)
|
||||
|
||||
@app.route('/logs')
|
||||
@login_required
|
||||
def logs_page():
|
||||
logs = OperationLog.query.order_by(OperationLog.timestamp.desc()).all()
|
||||
return render_template('logs.html', logs=logs)
|
||||
|
||||
@app.route('/logs/delete', methods=['POST'])
|
||||
@login_required
|
||||
def delete_old_logs():
|
||||
try:
|
||||
delete_days = int(request.form.get('delete_days', 0))
|
||||
except ValueError:
|
||||
flash("Podana wartość jest nieprawidłowa.")
|
||||
return redirect(url_for('logs_page'))
|
||||
|
||||
if delete_days < 1:
|
||||
flash("Podaj wartość większą lub równą 1.")
|
||||
return redirect(url_for('logs_page'))
|
||||
|
||||
cutoff_date = datetime.utcnow() - timedelta(days=delete_days)
|
||||
old_logs = OperationLog.query.filter(OperationLog.timestamp < cutoff_date).all()
|
||||
deleted_count = 0
|
||||
for log in old_logs:
|
||||
db.session.delete(log)
|
||||
deleted_count += 1
|
||||
db.session.commit()
|
||||
flash(f"Usunięto {deleted_count} logów starszych niż {delete_days} dni.")
|
||||
return redirect(url_for('logs_page'))
|
||||
|
||||
if __name__ == '__main__':
|
||||
with app.app_context():
|
||||
scheduler = BackgroundScheduler()
|
||||
|
Reference in New Issue
Block a user