naprawa bledow i poprawki ux
This commit is contained in:
110
app.py
110
app.py
@@ -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)
|
||||
|
Reference in New Issue
Block a user