naprawa bledow i poprawki ux

This commit is contained in:
Mateusz Gruszczyński
2025-02-23 00:24:42 +01:00
parent ad6771c290
commit 31c898ba0c
11 changed files with 331 additions and 144 deletions

110
app.py
View File

@@ -5,6 +5,7 @@ import atexit
import io
import zipfile
import requests
import re
import smtplib
import shutil
import socket
@@ -16,24 +17,27 @@ from email.mime.text import MIMEText
from email import encoders
from flask import jsonify
from flask import Flask
#import difflib
import shutil
from datetime import datetime
from difflib import HtmlDiff
import difflib
#from flask_wtf.csrf import CSRFProtect
from datetime import datetime, timedelta
from sqlalchemy import text
from flask import (
Flask, render_template, request, redirect,
url_for, session, flash, send_file
)
from flask_sqlalchemy import SQLAlchemy
from passlib.hash import bcrypt
#from flask_wtf.csrf import CSRFProtect
from apscheduler.schedulers.background import BackgroundScheduler
from apscheduler.triggers.cron import CronTrigger
# REGEX dla nazwy urzadzenia
ALLOWED_NAME_REGEX = re.compile(r'^[A-Za-z0-9_-]+$')
###############################################################################
# Konfiguracja Flask
###############################################################################
@@ -63,7 +67,6 @@ class User(db.Model):
def check_password(self, password):
return bcrypt.verify(password, self.password_hash)
class Router(db.Model):
__tablename__ = 'routers'
id = db.Column(db.Integer, primary_key=True)
@@ -84,15 +87,12 @@ 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)
class OperationLog(db.Model):
__tablename__ = 'operation_logs'
__table_args__ = {'extend_existing': True} # Zapobiega redefinicji tabeli
id = db.Column(db.Integer, primary_key=True)
message = db.Column(db.Text, nullable=False)
timestamp = db.Column(db.DateTime, default=datetime.utcnow)
class GlobalSettings(db.Model):
__tablename__ = 'global_settings'
id = db.Column(db.Integer, primary_key=True)
@@ -111,6 +111,7 @@ class GlobalSettings(db.Model):
smtp_port = db.Column(db.Integer, default=587)
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)
###############################################################################
# Inicjalizacja bazy
@@ -371,6 +372,9 @@ def send_pushover(token, userkey, message, title="RouterOS Backup"):
return False
def send_mail_with_attachment(smtp_host, smtp_port, smtp_user, smtp_pass, to_address, subject, plain_body, attachment_path="", html_body=None):
if not (smtp_host and smtp_host.strip() and smtp_user and smtp_user.strip() and smtp_pass and smtp_pass.strip()):
print("SMTP not properly configured, skipping email sending.")
return False
try:
# Utwórz wiadomość typu alternative (obsługuje plain text i HTML)
msg = MIMEMultipart("alternative")
@@ -403,10 +407,13 @@ def send_mail_with_attachment(smtp_host, smtp_port, smtp_user, smtp_pass, to_add
server.send_message(msg)
return True
except Exception as e:
print("Mail error:", e)
print("Mail error_send_mail_with_attachment:", e)
return False
def send_mail(smtp_host, smtp_port, smtp_user, smtp_pass, to_address, subject, body):
if not (smtp_host and smtp_user and smtp_pass):
print("SMTP not configured, skipping email")
return False
try:
msg = MIMEMultipart()
msg["From"] = smtp_user
@@ -420,7 +427,7 @@ def send_mail(smtp_host, smtp_port, smtp_user, smtp_pass, to_address, subject, b
server.send_message(msg)
return True
except Exception as e:
print("Mail error:", e)
print("Mail error_send_mail:", e)
return False
def notify(settings: GlobalSettings, message: str, success: bool):
@@ -428,16 +435,25 @@ def notify(settings: GlobalSettings, message: str, success: bool):
return
if settings.pushover_token and settings.pushover_userkey:
send_pushover(settings.pushover_token, settings.pushover_userkey, message)
if settings.smtp_host and settings.smtp_login and settings.smtp_password:
send_mail_with_attachment(
smtp_host=settings.smtp_host,
smtp_port=settings.smtp_port,
smtp_user=settings.smtp_login,
smtp_pass=settings.smtp_password,
to_address=settings.smtp_login,
subject="RouterOS Backup Notification",
body=message
)
# Wysyłka maila tylko jeśli SMTP Notifications są włączone
if settings.smtp_notifications_enabled:
if (settings.smtp_host and settings.smtp_host.strip() and
settings.smtp_login and settings.smtp_login.strip() and
settings.smtp_password and settings.smtp_password.strip()):
try:
send_mail_with_attachment(
smtp_host=settings.smtp_host.strip(),
smtp_port=settings.smtp_port,
smtp_user=settings.smtp_login.strip(),
smtp_pass=settings.smtp_password.strip(),
to_address=settings.smtp_login.strip(),
subject="RouterOS Backup Notification",
plain_body=message
)
except Exception as e:
print("SMTP send error:", e)
else:
print("SMTP configuration is incomplete. Skipping email notification.")
###############################################################################
# Zadania cykliczne
@@ -642,8 +658,6 @@ def index():
return redirect(url_for('dashboard'))
return render_template('index.html')
import shutil
from datetime import datetime
# Globalna zmienna z czasem uruchomienia aplikacji
app_start_time = datetime.now()
@@ -765,17 +779,23 @@ def routers_list():
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)
@app.route('/routers/add', methods=['GET','POST'])
@login_required
def add_router():
if request.method=='POST':
user = get_current_user()
name = request.form['name']
host = request.form['host']
port = request.form.get('port','22')
ssh_user = request.form['ssh_user']
ssh_key = request.form['ssh_key']
ssh_password = request.form['ssh_password']
name = request.form['name'].strip()
# Walidacja nazwy: tylko litery, cyfry, - i _
if not ALLOWED_NAME_REGEX.match(name):
flash("Nazwa urządzenia może zawierać wyłącznie litery, cyfry, myślniki (-) oraz podkreślenia (_).")
return redirect(url_for('add_router'))
host = request.form['host'].strip()
port = request.form.get('port','22').strip()
ssh_user = request.form['ssh_user'].strip()
ssh_key = request.form['ssh_key'].strip()
ssh_password = request.form['ssh_password'].strip()
r = Router(owner_id=user.id, name=name, host=host, port=int(port),
ssh_user=ssh_user, ssh_key=ssh_key, ssh_password=ssh_password)
db.session.add(r)
@@ -795,7 +815,12 @@ def router_details(router_id):
all_b = Backup.query.filter_by(router_id=router.id).order_by(Backup.created_at.desc()).all()
export_b = [x for x in all_b if x.backup_type=='export']
bin_b = [x for x in all_b if x.backup_type=='binary']
return render_template('router_details.html', router=router, export_backups=export_b, binary_backups=bin_b)
#return render_template('router_details_v2.html', router=router, export_backups=export_b, binary_backups=bin_b)
view = request.args.get('view', 'v2')
if view == 'v2':
return render_template('router_details_v2.html', router=router, export_backups=export_b, binary_backups=bin_b, current_view='v2')
else:
return render_template('router_details.html', router=router, export_backups=export_b, binary_backups=bin_b, current_view='v1')
@app.route('/router/<int:router_id>/export', methods=['POST'])
@login_required
@@ -1063,6 +1088,7 @@ def settings_view():
s.pushover_token = request.form.get('pushover_token', '')
s.pushover_userkey = request.form.get('pushover_userkey', '')
s.notify_failures_only = bool(request.form.get('notify_failures_only', False))
s.smtp_notifications_enabled = True if request.form.get('smtp_notifications_enabled') == 'on' else False
s.smtp_host = request.form.get('smtp_host', '')
s.smtp_port = int(request.form.get('smtp_port', '587'))
s.smtp_login = request.form.get('smtp_login', '')
@@ -1073,7 +1099,6 @@ def settings_view():
return redirect(url_for('settings_view'))
return render_template('settings.html', settings=s)
# Nowa zakładka: edycja routera
@app.route('/router/<int:router_id>/edit', methods=['GET','POST'])
@login_required
def edit_router(router_id):
@@ -1083,15 +1108,19 @@ def edit_router(router_id):
flash("Brak routera.")
return redirect(url_for('routers_list'))
if request.method == 'POST':
router.name = request.form['name']
router.host = request.form['host']
router.port = int(request.form.get('port', '22'))
router.ssh_user = request.form['ssh_user']
router.ssh_key = request.form['ssh_key']
router.ssh_password = request.form['ssh_password']
# Przytnij wejścia i waliduj nazwę
new_name = request.form['name'].strip()
if not ALLOWED_NAME_REGEX.match(new_name):
flash("Nazwa urządzenia może zawierać wyłącznie litery, cyfry, myślniki (-) oraz podkreślenia (_).")
return redirect(url_for('edit_router', router_id=router_id))
router.name = new_name
router.host = request.form['host'].strip()
router.port = int(request.form.get('port', '22').strip())
router.ssh_user = request.form['ssh_user'].strip()
router.ssh_key = request.form['ssh_key'].strip()
router.ssh_password = request.form['ssh_password'].strip()
db.session.commit()
flash("Zapisano zmiany w routerze.")
#return redirect(url_for('router_details', router_id=router.id))
return redirect(url_for('routers_list'))
return render_template('edit_router.html', router=router)
@@ -1247,7 +1276,7 @@ def mass_actions():
@app.route('/health', methods=['GET'])
def healthcheck():
try:
db.session.execute('SELECT 1')
db.session.execute(text('SELECT 1'))
status = 'ok'
except Exception as e:
status = 'error'
@@ -1296,7 +1325,6 @@ 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)
if __name__ == '__main__':
with app.app_context():
scheduler = BackgroundScheduler()
@@ -1305,4 +1333,4 @@ if __name__ == '__main__':
schedule_auto_binary_backup_job()
scheduler.start()
atexit.register(lambda: scheduler.shutdown())
app.run(host='0.0.0.0', port=81, use_reloader=False, debug=True)
app.run(host='0.0.0.0', port=5581, use_reloader=False, debug=True)