init
This commit is contained in:
parent
a53ff42934
commit
0267e4c2bb
478
app.py
Normal file
478
app.py
Normal file
@ -0,0 +1,478 @@
|
||||
#!/usr/bin/env python3
|
||||
# app.py
|
||||
|
||||
from flask import Flask, render_template, request, redirect, url_for, flash, session
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
from flask_login import LoginManager, UserMixin, login_user, logout_user, current_user, login_required
|
||||
from werkzeug.security import generate_password_hash, check_password_hash
|
||||
from datetime import datetime
|
||||
from apscheduler.schedulers.background import BackgroundScheduler
|
||||
import librouteros
|
||||
import threading
|
||||
import time
|
||||
import requests
|
||||
import smtplib
|
||||
import atexit
|
||||
from email.mime.text import MIMEText
|
||||
from datetime import timedelta
|
||||
|
||||
# Konfiguracja aplikacji
|
||||
app = Flask(__name__)
|
||||
app.config['SECRET_KEY'] = 'twoj-sekret-klucz'
|
||||
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///database.db'
|
||||
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
|
||||
|
||||
db = SQLAlchemy(app)
|
||||
|
||||
# Konfiguracja Flask-Login
|
||||
login_manager = LoginManager(app)
|
||||
login_manager.login_view = 'login'
|
||||
|
||||
# MODELE BAZY DANYCH
|
||||
|
||||
class User(UserMixin, db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
username = db.Column(db.String(80), unique=True, nullable=False)
|
||||
email = db.Column(db.String(120), unique=True, nullable=False)
|
||||
password_hash = db.Column(db.String(128), nullable=False)
|
||||
devices = db.relationship('Device', backref='owner', lazy=True)
|
||||
settings = db.relationship('Settings', uselist=False, backref='user')
|
||||
|
||||
def set_password(self, password):
|
||||
self.password_hash = generate_password_hash(password)
|
||||
|
||||
def check_password(self, password):
|
||||
return check_password_hash(self.password_hash, password)
|
||||
|
||||
class Device(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
name = db.Column(db.String(120)) # Nazwa urządzenia (opcjonalnie)
|
||||
ip = db.Column(db.String(120), nullable=False)
|
||||
port = db.Column(db.Integer, default=8728)
|
||||
device_username = db.Column(db.String(120), nullable=False)
|
||||
device_password = db.Column(db.String(120), nullable=False)
|
||||
branch = db.Column(db.String(20), default="stable") # Wybór gałęzi aktualizacji
|
||||
update_required = db.Column(db.Boolean, default=False) # True, gdy dostępna jest aktualizacja
|
||||
last_check = db.Column(db.DateTime)
|
||||
last_log = db.Column(db.Text)
|
||||
current_version = db.Column(db.String(50)) # Nowa kolumna – aktualna wersja systemu
|
||||
current_firmware = db.Column(db.String(50)) # Nowa kolumna – aktualna wersja firmware
|
||||
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
|
||||
|
||||
class Settings(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
pushover_user_key = db.Column(db.String(255))
|
||||
pushover_token = db.Column(db.String(255))
|
||||
pushover_enabled = db.Column(db.Boolean, default=False)
|
||||
smtp_server = db.Column(db.String(255))
|
||||
smtp_port = db.Column(db.Integer)
|
||||
smtp_username = db.Column(db.String(255))
|
||||
smtp_password = db.Column(db.String(255))
|
||||
email_notifications_enabled = db.Column(db.Boolean, default=False)
|
||||
check_interval = db.Column(db.Integer, default=60) # interwał sprawdzania w sekundach
|
||||
log_retention_days = db.Column(db.Integer, default=30) # nowe pole – retencja logów w dniach
|
||||
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False, unique=True)
|
||||
|
||||
class Log(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
timestamp = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
message = db.Column(db.Text)
|
||||
device_id = db.Column(db.Integer, db.ForeignKey('device.id'))
|
||||
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
|
||||
|
||||
# Inicjalizacja bazy (utworzyć bazę przy pierwszym uruchomieniu)
|
||||
with app.app_context():
|
||||
db.create_all()
|
||||
|
||||
@login_manager.user_loader
|
||||
def load_user(user_id):
|
||||
return User.query.get(int(user_id))
|
||||
|
||||
# FUNKCJE POWIADOMIEŃ
|
||||
|
||||
def send_pushover_notification(user, message):
|
||||
# Sprawdzamy, czy użytkownik posiada ustawienia oraz wymagane pola
|
||||
if not user.settings or not user.settings.pushover_enabled or not user.settings.pushover_user_key or not user.settings.pushover_token:
|
||||
return
|
||||
data = {
|
||||
"token": user.settings.pushover_token, # Używamy pushover_token z ustawień
|
||||
"user": user.settings.pushover_user_key,
|
||||
"message": message
|
||||
}
|
||||
try:
|
||||
r = requests.post("https://api.pushover.net/1/messages.json", data=data)
|
||||
# Możesz dodać logowanie odpowiedzi, jeśli potrzebne
|
||||
except Exception as e:
|
||||
print("Błąd przy wysyłaniu powiadomienia Pushover:", e)
|
||||
|
||||
def send_email_notification(user, subject, message):
|
||||
if not user.settings or not user.settings.email_notifications_enabled or not user.settings.smtp_server:
|
||||
return
|
||||
try:
|
||||
msg = MIMEText(message)
|
||||
msg["Subject"] = subject
|
||||
msg["From"] = user.settings.smtp_username
|
||||
msg["To"] = user.email
|
||||
|
||||
s = smtplib.SMTP(user.settings.smtp_server, user.settings.smtp_port)
|
||||
s.starttls()
|
||||
s.login(user.settings.smtp_username, user.settings.smtp_password)
|
||||
s.sendmail(user.settings.smtp_username, [user.email], msg.as_string())
|
||||
s.quit()
|
||||
except Exception as e:
|
||||
print("Błąd przy wysyłaniu powiadomienia e-mail:", e)
|
||||
|
||||
# FUNKCJA SPRAWDZAJĄCA AKTUALIZACJE URZĄDZENIA
|
||||
def check_device_update(device):
|
||||
log_entries = []
|
||||
update_available = False
|
||||
current_version = None
|
||||
current_firmware = None
|
||||
try:
|
||||
api = librouteros.connect(
|
||||
host=device.ip,
|
||||
username=device.device_username,
|
||||
password=device.device_password,
|
||||
port=device.port,
|
||||
timeout=15
|
||||
)
|
||||
# Pobranie podstawowych informacji
|
||||
identity_resp = list(api('/system/identity/print'))
|
||||
if identity_resp:
|
||||
identity = identity_resp[0].get('name', '')
|
||||
log_entries.append(f"Identity: {identity}")
|
||||
# Pobranie wersji systemu
|
||||
resource_resp = list(api('/system/resource/print'))
|
||||
if resource_resp:
|
||||
version = resource_resp[0].get('version', '')
|
||||
current_version = version
|
||||
log_entries.append(f"System Version: {version}")
|
||||
# Pobranie informacji o urządzeniu, w tym firmware
|
||||
board_resp = list(api('/system/routerboard/print'))
|
||||
if board_resp:
|
||||
board_info = board_resp[0]
|
||||
firmware = board_info.get('firmware', board_info.get('firmware-version', board_info.get('upgrade-firmware', 'N/A')))
|
||||
current_firmware = firmware
|
||||
log_entries.append(f"Firmware: {firmware}")
|
||||
# Sprawdzenie dostępnych aktualizacji
|
||||
log_entries.append("Checking for updates...")
|
||||
list(api('/system/package/update/check-for-updates'))
|
||||
# Czekamy aż operacja się zakończy
|
||||
for _ in range(10):
|
||||
time.sleep(1)
|
||||
status_resp = list(api('/system/package/update/print'))
|
||||
if status_resp:
|
||||
status = status_resp[0].get('status', '').lower()
|
||||
if 'checking' not in status:
|
||||
log_entries.append(f"Update check completed. Status: {status}")
|
||||
break
|
||||
|
||||
update_resp = list(api('/system/package/update/print'))
|
||||
if update_resp:
|
||||
for res in update_resp:
|
||||
installed = res.get('installed-version', '')
|
||||
latest = res.get('latest-version', '')
|
||||
if latest and latest != installed:
|
||||
log_entries.append(f"Updates available: {installed} -> {latest}")
|
||||
update_available = True
|
||||
else:
|
||||
log_entries.append("No updates available.")
|
||||
return "\n".join(log_entries), update_available, current_version, current_firmware
|
||||
except Exception as e:
|
||||
return f"Error: {str(e)}", False, None, None
|
||||
|
||||
|
||||
def check_all_devices():
|
||||
with app.app_context():
|
||||
devices = Device.query.all()
|
||||
for device in devices:
|
||||
result, update_available, current_version, current_firmware = check_device_update(device)
|
||||
device.last_log = result
|
||||
device.last_check = datetime.utcnow()
|
||||
device.update_required = update_available
|
||||
device.current_version = current_version
|
||||
device.current_firmware = current_firmware
|
||||
db.session.commit()
|
||||
# Zapis do tabeli logów
|
||||
log_entry = Log(message=result, device_id=device.id, user_id=device.user_id)
|
||||
db.session.add(log_entry)
|
||||
db.session.commit()
|
||||
# Powiadomienia, jeśli dostępna aktualizacja
|
||||
if update_available:
|
||||
user = device.owner
|
||||
message = f"Urządzenie {device.name or device.ip} ma dostępną aktualizację."
|
||||
send_pushover_notification(user, message)
|
||||
send_email_notification(user, "Aktualizacja dostępna", message)
|
||||
|
||||
|
||||
def bytes_to_human(n):
|
||||
try:
|
||||
n = int(n)
|
||||
except Exception:
|
||||
return n
|
||||
i = 0
|
||||
while n >= 1024 and i < 5:
|
||||
n /= 1024.0
|
||||
i += 1
|
||||
units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB']
|
||||
return f"{n:.2f} {units[i]}"
|
||||
|
||||
def clean_old_logs():
|
||||
with app.app_context():
|
||||
all_settings = Settings.query.all()
|
||||
for setting in all_settings:
|
||||
if setting.log_retention_days:
|
||||
cutoff = datetime.utcnow() - timedelta(days=setting.log_retention_days)
|
||||
# Usuwamy logi starsze niż cutoff dla danego użytkownika
|
||||
Log.query.filter(Log.user_id == setting.user_id, Log.timestamp < cutoff).delete()
|
||||
db.session.commit()
|
||||
|
||||
# Harmonogram sprawdzania aktualizacji – wykorzystujemy APScheduler
|
||||
scheduler = BackgroundScheduler()
|
||||
scheduler.add_job(func=check_all_devices, trigger="interval", seconds=60) # co 60 sekund; można zmienić na podstawie ustawień użytkownika
|
||||
scheduler.start()
|
||||
|
||||
# ROUTY APLIKACJI
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
return render_template('index.html')
|
||||
|
||||
# Rejestracja
|
||||
@app.route('/register', methods=['GET', 'POST'])
|
||||
def register():
|
||||
if request.method == 'POST':
|
||||
username = request.form['username']
|
||||
email = request.form['email']
|
||||
password = request.form['password']
|
||||
# Prosta walidacja – warto rozszerzyć
|
||||
if User.query.filter_by(username=username).first():
|
||||
flash("Użytkownik o tej nazwie już istnieje.")
|
||||
return redirect(url_for('register'))
|
||||
new_user = User(username=username, email=email)
|
||||
new_user.set_password(password)
|
||||
db.session.add(new_user)
|
||||
db.session.commit()
|
||||
# Utwórz domyślne ustawienia dla użytkownika
|
||||
default_settings = Settings(user_id=new_user.id, check_interval=60)
|
||||
db.session.add(default_settings)
|
||||
db.session.commit()
|
||||
flash("Rejestracja zakończona. Możesz się zalogować.")
|
||||
return redirect(url_for('login'))
|
||||
return render_template('register.html')
|
||||
|
||||
# Logowanie
|
||||
@app.route('/login', methods=['GET', 'POST'])
|
||||
def login():
|
||||
if request.method == 'POST':
|
||||
username = request.form['username']
|
||||
password = request.form['password']
|
||||
user = User.query.filter_by(username=username).first()
|
||||
if user and user.check_password(password):
|
||||
login_user(user)
|
||||
flash("Zalogowano pomyślnie.")
|
||||
return redirect(url_for('devices'))
|
||||
else:
|
||||
flash("Nieprawidłowa nazwa użytkownika lub hasło.")
|
||||
return render_template('login.html')
|
||||
|
||||
# Wylogowanie
|
||||
@app.route('/logout')
|
||||
@login_required
|
||||
def logout():
|
||||
logout_user()
|
||||
flash("Wylogowano.")
|
||||
return redirect(url_for('index'))
|
||||
|
||||
# Lista urządzeń użytkownika
|
||||
@app.route('/devices')
|
||||
@login_required
|
||||
def devices():
|
||||
user_devices = Device.query.filter_by(user_id=current_user.id).all()
|
||||
return render_template('devices.html', devices=user_devices)
|
||||
|
||||
# Dodawanie urządzenia
|
||||
@app.route('/device/add', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
def add_device():
|
||||
if request.method == 'POST':
|
||||
name = request.form.get('name')
|
||||
ip = request.form['ip']
|
||||
port = int(request.form.get('port', 8728))
|
||||
device_username = request.form['device_username']
|
||||
device_password = request.form['device_password']
|
||||
new_device = Device(name=name, ip=ip, port=port, device_username=device_username,
|
||||
device_password=device_password, user_id=current_user.id)
|
||||
db.session.add(new_device)
|
||||
db.session.commit()
|
||||
flash("Urządzenie dodane.")
|
||||
return redirect(url_for('devices'))
|
||||
return render_template('add_device.html')
|
||||
|
||||
# Szczegóły urządzenia
|
||||
@app.route('/device/<int:device_id>')
|
||||
@login_required
|
||||
def device_detail(device_id):
|
||||
device = Device.query.get_or_404(device_id)
|
||||
if device.user_id != current_user.id:
|
||||
flash("Brak dostępu.")
|
||||
return redirect(url_for('devices'))
|
||||
resource_data = {}
|
||||
try:
|
||||
api = librouteros.connect(
|
||||
host=device.ip,
|
||||
username=device.device_username,
|
||||
password=device.device_password,
|
||||
port=device.port,
|
||||
timeout=15
|
||||
)
|
||||
res_resp = list(api('/system/resource/print'))
|
||||
if res_resp:
|
||||
resource_data = res_resp[0]
|
||||
# Konwersja wartości pamięci i dysku na czytelny format
|
||||
if 'free-memory' in resource_data:
|
||||
resource_data['free-memory'] = bytes_to_human(resource_data['free-memory'])
|
||||
if 'total-memory' in resource_data:
|
||||
resource_data['total-memory'] = bytes_to_human(resource_data['total-memory'])
|
||||
if 'free-hdd-space' in resource_data:
|
||||
resource_data['free-hdd-space'] = bytes_to_human(resource_data['free-hdd-space'])
|
||||
except Exception as e:
|
||||
resource_data = {'error': str(e)}
|
||||
return render_template('device_detail.html', device=device, resource=resource_data)
|
||||
|
||||
|
||||
# Strona z logami
|
||||
@app.route('/logs')
|
||||
@login_required
|
||||
def logs():
|
||||
user_logs = Log.query.filter_by(user_id=current_user.id).order_by(Log.timestamp.desc()).all()
|
||||
return render_template('logs.html', logs=user_logs)
|
||||
|
||||
# Strona ustawień powiadomień
|
||||
@app.route('/settings', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
def settings():
|
||||
user_settings = current_user.settings
|
||||
if request.method == 'POST':
|
||||
# Aktualizacja ustawień Pushover
|
||||
user_settings.pushover_user_key = request.form.get('pushover_user_key')
|
||||
user_settings.pushover_token = request.form.get('pushover_token')
|
||||
user_settings.pushover_enabled = bool(request.form.get('pushover_enabled'))
|
||||
# Aktualizacja ustawień SMTP
|
||||
user_settings.smtp_server = request.form.get('smtp_server')
|
||||
smtp_port = request.form.get('smtp_port')
|
||||
user_settings.smtp_port = int(smtp_port) if smtp_port else None
|
||||
user_settings.smtp_username = request.form.get('smtp_username')
|
||||
user_settings.smtp_password = request.form.get('smtp_password')
|
||||
user_settings.email_notifications_enabled = bool(request.form.get('email_notifications_enabled'))
|
||||
# Aktualizacja interwału sprawdzania
|
||||
interval = request.form.get('check_interval')
|
||||
user_settings.check_interval = int(interval) if interval else 60
|
||||
# Aktualizacja retencji logów
|
||||
retention = request.form.get('log_retention_days')
|
||||
user_settings.log_retention_days = int(retention) if retention else 30
|
||||
db.session.commit()
|
||||
flash("Ustawienia zapisane.")
|
||||
return redirect(url_for('settings'))
|
||||
return render_template('settings.html', settings=user_settings)
|
||||
|
||||
|
||||
@app.route('/device/<int:device_id>/edit', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
def edit_device(device_id):
|
||||
device = Device.query.get_or_404(device_id)
|
||||
if device.user_id != current_user.id:
|
||||
flash("Brak dostępu.")
|
||||
return redirect(url_for('devices'))
|
||||
if request.method == 'POST':
|
||||
# Używamy get() z wartością domyślną, aby nie wymuszać przesłania wszystkich pól
|
||||
device.name = request.form.get('name', device.name)
|
||||
device.ip = request.form.get('ip', device.ip)
|
||||
device.port = int(request.form.get('port', device.port or 8728))
|
||||
device.device_username = request.form.get('device_username', device.device_username)
|
||||
device.device_password = request.form.get('device_password', device.device_password)
|
||||
device.branch = request.form.get('branch', device.branch or 'stable')
|
||||
db.session.commit()
|
||||
flash("Urządzenie zaktualizowane.")
|
||||
return redirect(url_for('devices'))
|
||||
return render_template('edit_device.html', device=device)
|
||||
|
||||
@app.route('/device/<int:device_id>/force_check')
|
||||
@login_required
|
||||
def force_check(device_id):
|
||||
device = Device.query.get_or_404(device_id)
|
||||
if device.user_id != current_user.id:
|
||||
flash("Brak dostępu.")
|
||||
return redirect(url_for('devices'))
|
||||
result, update_available, current_version, current_firmware = check_device_update(device)
|
||||
device.last_log = result
|
||||
device.last_check = datetime.utcnow()
|
||||
device.update_required = update_available
|
||||
device.current_version = current_version
|
||||
device.current_firmware = current_firmware
|
||||
db.session.commit()
|
||||
flash("Sprawdzenie urządzenia zakończone.")
|
||||
return redirect(url_for('devices'))
|
||||
|
||||
|
||||
@app.route('/device/<int:device_id>/update', methods=['POST'])
|
||||
@login_required
|
||||
def update_device(device_id):
|
||||
device = Device.query.get_or_404(device_id)
|
||||
if device.user_id != current_user.id:
|
||||
flash("Brak dostępu.")
|
||||
return redirect(url_for('devices'))
|
||||
try:
|
||||
api = librouteros.connect(
|
||||
host=device.ip,
|
||||
username=device.device_username,
|
||||
password=device.device_password,
|
||||
port=device.port,
|
||||
timeout=15
|
||||
)
|
||||
# Przykładowo: wybór komendy w zależności od wybranego branch
|
||||
if device.branch == 'stable':
|
||||
list(api('/system/package/update/install'))
|
||||
elif device.branch == 'dev':
|
||||
list(api('/system/package/update/install', branch='dev'))
|
||||
elif device.branch == 'beta':
|
||||
list(api('/system/package/update/install', branch='beta'))
|
||||
else:
|
||||
list(api('/system/package/update/install'))
|
||||
flash("Aktualizacja systemu została rozpoczęta.")
|
||||
except Exception as e:
|
||||
flash(f"Błąd podczas aktualizacji: {e}")
|
||||
return redirect(url_for('device_detail', device_id=device.id))
|
||||
|
||||
|
||||
@app.route('/device/<int:device_id>/update_firmware', methods=['POST'])
|
||||
@login_required
|
||||
def update_firmware(device_id):
|
||||
device = Device.query.get_or_404(device_id)
|
||||
if device.user_id != current_user.id:
|
||||
flash("Brak dostępu.")
|
||||
return redirect(url_for('devices'))
|
||||
try:
|
||||
api = librouteros.connect(
|
||||
host=device.ip,
|
||||
username=device.device_username,
|
||||
password=device.device_password,
|
||||
port=device.port,
|
||||
timeout=15
|
||||
)
|
||||
# Przykładowa komenda aktualizacji firmware
|
||||
list(api('/system/routerboard/upgrade'))
|
||||
flash("Aktualizacja firmware została rozpoczęta.")
|
||||
except Exception as e:
|
||||
flash(f"Błąd podczas aktualizacji firmware: {e}")
|
||||
return redirect(url_for('device_detail', device_id=device.id))
|
||||
|
||||
|
||||
|
||||
# Zamknięcie harmonogramu przy zatrzymaniu aplikacji
|
||||
|
||||
atexit.register(lambda: scheduler.shutdown())
|
||||
|
||||
if __name__ == '__main__':
|
||||
scheduler.add_job(func=clean_old_logs, trigger="interval", days=1)
|
||||
app.run(host='0.0.0.0', port=5581, use_reloader=False, debug=True)
|
8
requirements.txt
Normal file
8
requirements.txt
Normal file
@ -0,0 +1,8 @@
|
||||
Flask
|
||||
Flask-SQLAlchemy
|
||||
Flask-Login
|
||||
APScheduler
|
||||
librouteros
|
||||
requests
|
||||
gunicorn
|
||||
requests
|
4
templates/.gitignore
vendored
Normal file
4
templates/.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
__pycache__
|
||||
data/
|
||||
instance/
|
||||
venv/
|
32
templates/add_device.html
Normal file
32
templates/add_device.html
Normal file
@ -0,0 +1,32 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Dodaj urządzenie - RouterOS Update{% endblock %}
|
||||
{% block content %}
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<h2>Dodaj nowe urządzenie</h2>
|
||||
<form method="POST">
|
||||
<div class="mb-3">
|
||||
<label for="name" class="form-label">Nazwa urządzenia (opcjonalnie)</label>
|
||||
<input type="text" class="form-control" name="name" id="name">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="ip" class="form-label">Adres IP</label>
|
||||
<input type="text" class="form-control" name="ip" id="ip" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="port" class="form-label">Port</label>
|
||||
<input type="number" class="form-control" name="port" id="port" value="8728" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="device_username" class="form-label">Nazwa użytkownika urządzenia</label>
|
||||
<input type="text" class="form-control" name="device_username" id="device_username" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="device_password" class="form-label">Hasło urządzenia</label>
|
||||
<input type="password" class="form-control" name="device_password" id="device_password" required>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Dodaj urządzenie</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
61
templates/base.html
Normal file
61
templates/base.html
Normal file
@ -0,0 +1,61 @@
|
||||
<!doctype html>
|
||||
<html lang="pl">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>{% block title %}RouterOS Update{% endblock %}</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<!-- Bootstrap CSS -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
</head>
|
||||
<body class="d-flex flex-column min-vh-100">
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
||||
<div class="container-fluid">
|
||||
<span class="navbar-brand">RouterOS Update</span>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarButtons" aria-controls="navbarButtons" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarButtons">
|
||||
<div class="ms-auto">
|
||||
{% if current_user.is_authenticated %}
|
||||
<div class="btn-group me-2" role="group">
|
||||
<button type="button" class="btn btn-outline-light" onclick="window.location.href='{{ url_for('devices') }}'">Urządzenia</button>
|
||||
<button type="button" class="btn btn-outline-light" onclick="window.location.href='{{ url_for('logs') }}'">Logi</button>
|
||||
<button type="button" class="btn btn-outline-light" onclick="window.location.href='{{ url_for('settings') }}'">Ustawienia</button>
|
||||
</div>
|
||||
<div class="btn-group" role="group">
|
||||
<button type="button" class="btn btn-outline-light" onclick="window.location.href='{{ url_for('logout') }}'">Wyloguj</button>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="btn-group me-2" role="group">
|
||||
<button type="button" class="btn btn-outline-light" onclick="window.location.href='{{ url_for('login') }}'">Logowanie</button>
|
||||
<button type="button" class="btn btn-outline-light" onclick="window.location.href='{{ url_for('register') }}'">Rejestracja</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main class="container my-4 flex-fill">
|
||||
{% with messages = get_flashed_messages() %}
|
||||
{% if messages %}
|
||||
<div class="alert alert-info">
|
||||
{% for message in messages %}
|
||||
<div>{{ message }}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
{% block content %}{% endblock %}
|
||||
</main>
|
||||
|
||||
<footer class="footer bg-light py-3 mt-auto">
|
||||
<div class="container text-center">
|
||||
<span class="text-muted">© 2025 RouterOS Update</span>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<!-- Bootstrap JS -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
</body>
|
||||
</html>
|
94
templates/device_detail.html
Normal file
94
templates/device_detail.html
Normal file
@ -0,0 +1,94 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Szczegóły urządzenia - RouterOS Update{% endblock %}
|
||||
{% block content %}
|
||||
<div class="container">
|
||||
<h2 class="mb-4">Szczegóły urządzenia</h2>
|
||||
<div class="row">
|
||||
<!-- Dane urządzenia -->
|
||||
<div class="col-md-6 mb-3">
|
||||
<div class="card h-100">
|
||||
<div class="card-header bg-primary text-white">
|
||||
Dane urządzenia
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p><strong>Adres IP:</strong> {{ device.ip }}</p>
|
||||
<p><strong>Port:</strong> {{ device.port }}</p>
|
||||
<p><strong>Ostatnie sprawdzenie:</strong>
|
||||
{% if device.last_check %}{{ device.last_check.strftime('%Y-%m-%d %H:%M:%S') }}{% else %}Brak{% endif %}
|
||||
</p>
|
||||
<p>
|
||||
<strong>System:</strong> {{ device.current_version or 'Brak' }}<br>
|
||||
<strong>Firmware:</strong> {{ device.current_firmware or 'N/A' }}
|
||||
</p>
|
||||
<p>
|
||||
<strong>Branch aktualizacji:</strong> {{ device.branch|capitalize }}
|
||||
</p>
|
||||
<!-- Formularz zmiany branch -->
|
||||
<form method="POST" action="{{ url_for('edit_device', device_id=device.id) }}">
|
||||
<div class="input-group">
|
||||
<select class="form-select" name="branch">
|
||||
<option value="stable" {% if device.branch == 'stable' %}selected{% endif %}>Stable</option>
|
||||
<option value="dev" {% if device.branch == 'dev' %}selected{% endif %}>Dev</option>
|
||||
<option value="beta" {% if device.branch == 'beta' %}selected{% endif %}>Beta</option>
|
||||
</select>
|
||||
<button type="submit" class="btn btn-primary ms-2">Zmień</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Informacje o systemie -->
|
||||
<div class="col-md-6 mb-3">
|
||||
<div class="card h-100">
|
||||
<div class="card-header bg-info text-white">
|
||||
Informacje o systemie
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% if resource.error %}
|
||||
<div class="alert alert-danger" role="alert">
|
||||
Błąd pobierania danych: {{ resource.error }}
|
||||
</div>
|
||||
{% else %}
|
||||
<p><strong>Wersja systemu:</strong> {{ resource.version or 'Brak danych' }}</p>
|
||||
<p><strong>Czas pracy:</strong> {{ resource.uptime or 'Brak danych' }}</p>
|
||||
<p><strong>Obciążenie CPU:</strong> {{ resource['cpu-load'] or 'Brak' }}%</p>
|
||||
<p>
|
||||
<strong>Pamięć:</strong>
|
||||
{% if resource['free-memory'] and resource['total-memory'] %}
|
||||
{{ resource['free-memory'] }} wolnej / {{ resource['total-memory'] }} całkowita
|
||||
{% else %}
|
||||
Brak danych
|
||||
{% endif %}
|
||||
</p>
|
||||
<p><strong>Wolne miejsce na dysku:</strong> {{ resource['free-hdd-space'] or 'Brak danych' }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Logi urządzenia -->
|
||||
<div class="card mb-3">
|
||||
<div class="card-header bg-secondary text-white">
|
||||
Logi urządzenia
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<pre class="bg-light p-3" style="white-space: pre-wrap;">{{ device.last_log or 'Brak logów' }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Akcje -->
|
||||
<div class="mb-4">
|
||||
<div class="btn-group" role="group">
|
||||
<form method="POST" action="{{ url_for('update_device', device_id=device.id) }}" style="display: inline-block;">
|
||||
<button type="submit" class="btn btn-warning me-2">Aktualizuj system</button>
|
||||
</form>
|
||||
<form method="POST" action="{{ url_for('update_firmware', device_id=device.id) }}" style="display: inline-block;">
|
||||
<button type="submit" class="btn btn-danger me-2">Aktualizuj firmware</button>
|
||||
</form>
|
||||
<a href="{{ url_for('force_check', device_id=device.id) }}" class="btn btn-secondary me-2">Wymuś sprawdzenie</a>
|
||||
</div>
|
||||
<a href="{{ url_for('devices') }}" class="btn btn-outline-secondary ms-3">Powrót do listy urządzeń</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
47
templates/devices.html
Normal file
47
templates/devices.html
Normal file
@ -0,0 +1,47 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Moje urządzenia - RouterOS Update{% endblock %}
|
||||
{% block content %}
|
||||
<h2>Moje urządzenia</h2>
|
||||
<button type="button" class="btn btn-success mb-3" onclick="window.location.href='{{ url_for('add_device') }}'">Dodaj nowe urządzenie</button>
|
||||
<table class="table table-bordered table-hover">
|
||||
<thead class="table-dark">
|
||||
<tr>
|
||||
<th>Nazwa / Adres IP</th>
|
||||
<th>Ostatnie sprawdzenie</th>
|
||||
<th>Status</th>
|
||||
<th>System / Firmware</th>
|
||||
<th>Akcje</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for device in devices %}
|
||||
<tr>
|
||||
<td>{{ device.name or device.ip }}</td>
|
||||
<td>{{ device.last_check.strftime('%Y-%m-%d %H:%M:%S') if device.last_check else 'Brak' }}</td>
|
||||
<td>
|
||||
{% if device.update_required %}
|
||||
<span class="badge bg-danger">Wymaga aktualizacji</span>
|
||||
{% else %}
|
||||
<span class="badge bg-success">Aktualny</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<small>
|
||||
<strong>System:</strong> {{ device.current_version or 'Brak' }}<br>
|
||||
<strong>Firmware:</strong> {{ device.current_firmware or 'Brak' }}
|
||||
</small>
|
||||
</td>
|
||||
<td>
|
||||
<button type="button" class="btn btn-info btn-sm" onclick="window.location.href='{{ url_for('device_detail', device_id=device.id) }}'">Szczegóły</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="window.location.href='{{ url_for('force_check', device_id=device.id) }}'">Wymuś sprawdzenie</button>
|
||||
<button type="button" class="btn btn-warning btn-sm" onclick="window.location.href='{{ url_for('edit_device', device_id=device.id) }}'">Edytuj</button>
|
||||
</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr>
|
||||
<td colspan="5" class="text-center">Brak dodanych urządzeń.</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endblock %}
|
40
templates/edit_device.html
Normal file
40
templates/edit_device.html
Normal file
@ -0,0 +1,40 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Edytuj urządzenie - RouterOS Update{% endblock %}
|
||||
{% block content %}
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<h2>Edytuj urządzenie</h2>
|
||||
<form method="POST">
|
||||
<div class="mb-3">
|
||||
<label for="name" class="form-label">Nazwa urządzenia (opcjonalnie)</label>
|
||||
<input type="text" class="form-control" name="name" id="name" value="{{ device.name }}">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="ip" class="form-label">Adres IP</label>
|
||||
<input type="text" class="form-control" name="ip" id="ip" value="{{ device.ip }}" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="port" class="form-label">Port</label>
|
||||
<input type="number" class="form-control" name="port" id="port" value="{{ device.port }}" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="device_username" class="form-label">Nazwa użytkownika urządzenia</label>
|
||||
<input type="text" class="form-control" name="device_username" id="device_username" value="{{ device.device_username }}" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="device_password" class="form-label">Hasło urządzenia</label>
|
||||
<input type="password" class="form-control" name="device_password" id="device_password" value="{{ device.device_password }}" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="branch" class="form-label">Wybierz branch aktualizacji</label>
|
||||
<select class="form-select" name="branch" id="branch">
|
||||
<option value="stable" {% if device.branch == 'stable' %}selected{% endif %}>Stable</option>
|
||||
<option value="dev" {% if device.branch == 'dev' %}selected{% endif %}>Dev</option>
|
||||
<option value="beta" {% if device.branch == 'beta' %}selected{% endif %}>Beta</option>
|
||||
</select>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Zapisz zmiany</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
16
templates/index.html
Normal file
16
templates/index.html
Normal file
@ -0,0 +1,16 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Strona główna - RouterOS Update{% endblock %}
|
||||
{% block content %}
|
||||
<div class="p-5 mb-4 bg-light rounded-3">
|
||||
<div class="container-fluid py-5">
|
||||
<h1 class="display-5 fw-bold">Witamy w RouterOS Update</h1>
|
||||
<p class="col-md-8 fs-4">Monitoruj swoje urządzenia, sprawdzaj aktualizacje oraz zarządzaj powiadomieniami w jednym miejscu.</p>
|
||||
{% if not current_user.is_authenticated %}
|
||||
<button type="button" class="btn btn-primary btn-lg" onclick="window.location.href='{{ url_for('login') }}'">Logowanie</button>
|
||||
<button type="button" class="btn btn-secondary btn-lg" onclick="window.location.href='{{ url_for('register') }}'">Rejestracja</button>
|
||||
{% else %}
|
||||
<button type="button" class="btn btn-primary btn-lg" onclick="window.location.href='{{ url_for('devices') }}'">Moje urządzenia</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
20
templates/login.html
Normal file
20
templates/login.html
Normal file
@ -0,0 +1,20 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Logowanie - RouterOS Update{% endblock %}
|
||||
{% block content %}
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<h2>Logowanie</h2>
|
||||
<form method="POST">
|
||||
<div class="mb-3">
|
||||
<label for="username" class="form-label">Nazwa użytkownika</label>
|
||||
<input type="text" class="form-control" name="username" id="username" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="password" class="form-label">Hasło</label>
|
||||
<input type="password" class="form-control" name="password" id="password" required>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Zaloguj</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
33
templates/logs.html
Normal file
33
templates/logs.html
Normal file
@ -0,0 +1,33 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Logi - Aplikacja Updatera{% endblock %}
|
||||
{% block content %}
|
||||
<h2>Logi</h2>
|
||||
<table class="table table-striped">
|
||||
<thead class="table-dark">
|
||||
<tr>
|
||||
<th>Data i czas</th>
|
||||
<th>Urządzenie</th>
|
||||
<th>Wiadomość</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for log in logs %}
|
||||
<tr>
|
||||
<td>{{ log.timestamp.strftime('%Y-%m-%d %H:%M:%S') }}</td>
|
||||
<td>
|
||||
{% if log.device_id %}
|
||||
<a href="{{ url_for('device_detail', device_id=log.device_id) }}">Urządzenie #{{ log.device_id }}</a>
|
||||
{% else %}
|
||||
Ogólne
|
||||
{% endif %}
|
||||
</td>
|
||||
<td><pre style="white-space: pre-wrap;">{{ log.message }}</pre></td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr>
|
||||
<td colspan="3" class="text-center">Brak logów.</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endblock %}
|
24
templates/register.html
Normal file
24
templates/register.html
Normal file
@ -0,0 +1,24 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Rejestracja - RouterOS Update{% endblock %}
|
||||
{% block content %}
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<h2>Rejestracja</h2>
|
||||
<form method="POST">
|
||||
<div class="mb-3">
|
||||
<label for="username" class="form-label">Nazwa użytkownika</label>
|
||||
<input type="text" class="form-control" name="username" id="username" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="email" class="form-label">Email</label>
|
||||
<input type="email" class="form-control" name="email" id="email" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="password" class="form-label">Hasło</label>
|
||||
<input type="password" class="form-control" name="password" id="password" required>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Zarejestruj się</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
87
templates/settings.html
Normal file
87
templates/settings.html
Normal file
@ -0,0 +1,87 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Ustawienia - RouterOS Update{% endblock %}
|
||||
{% block content %}
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-8">
|
||||
<h2>Ustawienia powiadomień</h2>
|
||||
<form method="POST">
|
||||
<fieldset class="border p-3 mb-3">
|
||||
<legend class="w-auto">Pushover</legend>
|
||||
<div class="form-check mb-2">
|
||||
<input type="checkbox" class="form-check-input" name="pushover_enabled" id="pushover_enabled" {% if settings.pushover_enabled %}checked{% endif %}>
|
||||
<label class="form-check-label" for="pushover_enabled">Włącz powiadomienia Pushover</label>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="pushover_user_key" class="form-label">Pushover User Key</label>
|
||||
<input type="text" class="form-control" name="pushover_user_key" id="pushover_user_key" value="{{ settings.pushover_user_key or '' }}">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="pushover_token" class="form-label">Pushover Token</label>
|
||||
<input type="text" class="form-control" name="pushover_token" id="pushover_token" value="{{ settings.pushover_token or '' }}">
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<!-- Pozostała część formularza SMTP oraz interwału -->
|
||||
<fieldset class="border p-3 mb-3">
|
||||
<legend class="w-auto">SMTP (E-mail)</legend>
|
||||
<div class="form-check mb-2">
|
||||
<input type="checkbox" class="form-check-input" name="email_notifications_enabled" id="email_notifications_enabled" {% if settings.email_notifications_enabled %}checked{% endif %}>
|
||||
<label class="form-check-label" for="email_notifications_enabled">Włącz powiadomienia e-mail</label>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="smtp_server" class="form-label">SMTP Server</label>
|
||||
<input type="text" class="form-control" name="smtp_server" id="smtp_server" value="{{ settings.smtp_server or '' }}">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="smtp_port" class="form-label">SMTP Port</label>
|
||||
<input type="number" class="form-control" name="smtp_port" id="smtp_port" value="{{ settings.smtp_port or '' }}">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="smtp_username" class="form-label">SMTP Username</label>
|
||||
<input type="text" class="form-control" name="smtp_username" id="smtp_username" value="{{ settings.smtp_username or '' }}">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="smtp_password" class="form-label">SMTP Password</label>
|
||||
<input type="password" class="form-control" name="smtp_password" id="smtp_password" value="{{ settings.smtp_password or '' }}">
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset class="border p-3 mb-3">
|
||||
<legend class="w-auto">Interwał sprawdzania</legend>
|
||||
<div class="mb-3">
|
||||
<label for="check_interval" class="form-label">Interwał (sekundy)</label>
|
||||
<input type="number" class="form-control" name="check_interval" id="check_interval" value="{{ settings.check_interval or 60 }}">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="time_input" class="form-label">Czas (HH:MM:SS)</label>
|
||||
<input type="text" class="form-control" id="time_input" placeholder="np. 01:30:00">
|
||||
<button type="button" class="btn btn-secondary mt-2" onclick="convertTime()">Konwertuj na sekundy</button>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset class="border p-3 mb-3">
|
||||
<legend class="w-auto">Retencja logów</legend>
|
||||
<div class="mb-3">
|
||||
<label for="log_retention_days" class="form-label">Przechowywać logi przez (dni)</label>
|
||||
<input type="number" class="form-control" name="log_retention_days" id="log_retention_days" value="{{ settings.log_retention_days or 30 }}">
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<button type="submit" class="btn btn-primary">Zapisz ustawienia</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function convertTime() {
|
||||
var timeStr = document.getElementById("time_input").value;
|
||||
var parts = timeStr.split(':');
|
||||
if (parts.length !== 3) {
|
||||
alert("Podaj czas w formacie HH:MM:SS");
|
||||
return;
|
||||
}
|
||||
var seconds = parseInt(parts[0]) * 3600 + parseInt(parts[1]) * 60 + parseInt(parts[2]);
|
||||
document.getElementById("check_interval").value = seconds;
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
Loading…
x
Reference in New Issue
Block a user