diff --git a/app.py b/app.py index 148e27d..6a6b160 100644 --- a/app.py +++ b/app.py @@ -46,17 +46,19 @@ class User(UserMixin, db.Model): class Device(db.Model): id = db.Column(db.Integer, primary_key=True) - name = db.Column(db.String(120)) # Nazwa urządzenia (opcjonalnie) + name = db.Column(db.String(120)) 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 + branch = db.Column(db.String(20), default="stable") + update_required = db.Column(db.Boolean, default=False) 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 + current_version = db.Column(db.String(50)) + current_firmware = db.Column(db.String(50)) + use_ssl = db.Column(db.Boolean, default=False) # Czy używać SSL? + ssl_insecure = db.Column(db.Boolean, default=False) # Jeśli True – nie weryfikować certyfikatu SSL user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) class Settings(db.Model): @@ -129,38 +131,50 @@ def check_device_update(device): current_version = None current_firmware = None try: + app.logger.debug(f"Connecting to device {device.ip}:{device.port} using SSL: {device.use_ssl}, ssl_verify: {not device.ssl_insecure}") api = librouteros.connect( host=device.ip, username=device.device_username, password=device.device_password, port=device.port, - timeout=15 + timeout=15, + ssl=device.use_ssl, + ssl_verify=not device.ssl_insecure ) - # Pobranie podstawowych informacji + app.logger.debug(f"Connection established to {device.ip}") + + # Pobranie informacji o tożsamości urządzenia identity_resp = list(api('/system/identity/print')) + app.logger.debug(f"Identity response: {identity_resp}") 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')) + app.logger.debug(f"Resource response: {resource_resp}") 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 + + # Pobranie informacji o routerboard (firmware) board_resp = list(api('/system/routerboard/print')) + app.logger.debug(f"Routerboard response: {board_resp}") if board_resp: board_info = board_resp[0] + # Próba odczytania firmware z kilku możliwych kluczy 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')) + app.logger.debug(f"Update status response: {status_resp}") if status_resp: status = status_resp[0].get('status', '').lower() if 'checking' not in status: @@ -168,6 +182,7 @@ def check_device_update(device): break update_resp = list(api('/system/package/update/print')) + app.logger.debug(f"Update response: {update_resp}") if update_resp: for res in update_resp: installed = res.get('installed-version', '') @@ -179,9 +194,11 @@ def check_device_update(device): log_entries.append("No updates available.") return "\n".join(log_entries), update_available, current_version, current_firmware except Exception as e: + app.logger.error(f"Error connecting to device {device.ip}: {e}", exc_info=True) return f"Error: {str(e)}", False, None, None + def check_all_devices(): with app.app_context(): devices = Device.query.all() @@ -229,7 +246,26 @@ def clean_old_logs(): # 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 + +# Inicjalizacja bazy i scheduler’a +with app.app_context(): + db.create_all() # lub już wcześniej utworzona baza + # Pobranie globalnych ustawień – zakładamy, że Settings.query.first() zwróci ustawienia globalne + global_settings = Settings.query.first() + if global_settings and global_settings.check_interval: + interval = global_settings.check_interval + else: + interval = 60 + + scheduler.add_job( + func=check_all_devices, + trigger="interval", + seconds=interval, + id="check_all_devices", + max_instances=1 + ) + app.logger.debug(f"Scheduler initialized with interval: {interval} seconds") + scheduler.start() # ROUTY APLIKACJI @@ -301,14 +337,25 @@ def add_device(): 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) + use_ssl = bool(request.form.get('use_ssl')) + ssl_insecure = bool(request.form.get('ssl_insecure')) + new_device = Device( + name=name, + ip=ip, + port=port, + device_username=device_username, + device_password=device_password, + use_ssl=use_ssl, + ssl_insecure=ssl_insecure, + 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 @@ -371,12 +418,22 @@ def settings(): # 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() + + # Aktualizacja interwału zadania scheduler'a + try: + scheduler.reschedule_job("check_all_devices", trigger="interval", seconds=user_settings.check_interval) + app.logger.debug(f"Scheduler rescheduled with new interval: {user_settings.check_interval} seconds") + except Exception as e: + app.logger.error(f"Error rescheduling job: {e}") + 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): @@ -385,18 +442,20 @@ def edit_device(device_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') + device.use_ssl = bool(request.form.get('use_ssl')) + device.ssl_insecure = bool(request.form.get('ssl_insecure')) 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): @@ -414,7 +473,6 @@ def force_check(device_id): 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): @@ -423,14 +481,17 @@ def update_device(device_id): flash("Brak dostępu.") return redirect(url_for('devices')) try: + app.logger.debug(f"Initiating system update for device {device.ip}") api = librouteros.connect( host=device.ip, username=device.device_username, password=device.device_password, port=device.port, - timeout=15 + timeout=15, + ssl=device.use_ssl, + ssl_verify=not device.ssl_insecure ) - # Przykładowo: wybór komendy w zależności od wybranego branch + app.logger.debug("Connection established, starting update command") if device.branch == 'stable': list(api('/system/package/update/install')) elif device.branch == 'dev': @@ -440,11 +501,14 @@ def update_device(device_id): else: list(api('/system/package/update/install')) flash("Aktualizacja systemu została rozpoczęta.") + app.logger.debug("System update command executed successfully") except Exception as e: + app.logger.error(f"Błąd podczas aktualizacji urządzenia {device.ip}: {e}", exc_info=True) 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): diff --git a/templates/add_device.html b/templates/add_device.html index 7445406..6fbf668 100644 --- a/templates/add_device.html +++ b/templates/add_device.html @@ -5,10 +5,7 @@ <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> + <!-- Pozostałe pola: name, ip, port, username, password --> <div class="mb-3"> <label for="ip" class="form-label">Adres IP</label> <input type="text" class="form-control" name="ip" id="ip" required> @@ -25,6 +22,16 @@ <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> + <!-- Opcja SSL --> + <div class="mb-3 form-check"> + <input type="checkbox" class="form-check-input" name="use_ssl" id="use_ssl"> + <label class="form-check-label" for="use_ssl">Używaj SSL</label> + </div> + <!-- Opcja nie weryfikowania certyfikatu SSL --> + <div class="mb-3 form-check"> + <input type="checkbox" class="form-check-input" name="ssl_insecure" id="ssl_insecure"> + <label class="form-check-label" for="ssl_insecure">Nie weryfikuj certyfikatu SSL</label> + </div> <button type="submit" class="btn btn-primary">Dodaj urządzenie</button> </form> </div> diff --git a/templates/device_detail.html b/templates/device_detail.html index 942a87c..e064e06 100644 --- a/templates/device_detail.html +++ b/templates/device_detail.html @@ -2,18 +2,36 @@ {% 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"> + <div class="my-4"> + <h2 class="mb-3">Szczegóły urządzenia</h2> + <nav aria-label="breadcrumb"> + <ol class="breadcrumb"> + <li class="breadcrumb-item"><a href="{{ url_for('devices') }}">Urządzenia</a></li> + <li class="breadcrumb-item active" aria-current="page">{{ device.ip }}</li> + </ol> + </nav> + </div> + + <!-- Zakładki dla danych urządzenia i informacji o systemie --> + <ul class="nav nav-tabs mb-3" id="deviceTab" role="tablist"> + <li class="nav-item" role="presentation"> + <button class="nav-link active" id="device-data-tab" data-bs-toggle="tab" data-bs-target="#device-data" type="button" role="tab" aria-controls="device-data" aria-selected="true">Dane urządzenia</button> + </li> + <li class="nav-item" role="presentation"> + <button class="nav-link" id="system-info-tab" data-bs-toggle="tab" data-bs-target="#system-info" type="button" role="tab" aria-controls="system-info" aria-selected="false">Informacje o systemie</button> + </li> + </ul> + <div class="tab-content" id="deviceTabContent"> <!-- Dane urządzenia --> - <div class="col-md-6 mb-3"> - <div class="card h-100"> + <div class="tab-pane fade show active" id="device-data" role="tabpanel" aria-labelledby="device-data-tab"> + <div class="card mb-3"> <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> + <p><strong>Ostatnie sprawdzenie:</strong> {% if device.last_check %}{{ device.last_check.strftime('%Y-%m-%d %H:%M:%S') }}{% else %}Brak{% endif %} </p> <p> @@ -24,22 +42,22 @@ <strong>Branch aktualizacji:</strong> {{ device.branch|capitalize }} </p> <!-- Formularz zmiany branch --> - <form method="POST" action="{{ url_for('edit_device', device_id=device.id) }}"> + <form method="POST" action="{{ url_for('edit_device', device_id=device.id) }}" class="mt-3"> <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> + <button type="submit" class="btn btn-primary ms-2">Zmień branch</button> </div> </form> </div> </div> </div> <!-- Informacje o systemie --> - <div class="col-md-6 mb-3"> - <div class="card h-100"> + <div class="tab-pane fade" id="system-info" role="tabpanel" aria-labelledby="system-info-tab"> + <div class="card mb-3"> <div class="card-header bg-info text-white"> Informacje o systemie </div> @@ -53,7 +71,7 @@ <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> + <strong>Pamięć:</strong> {% if resource['free-memory'] and resource['total-memory'] %} {{ resource['free-memory'] }} wolnej / {{ resource['total-memory'] }} całkowita {% else %} @@ -67,28 +85,36 @@ </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> + <!-- Logi urządzenia w formie accordion --> + <div class="accordion mb-3" id="logsAccordion"> + <div class="accordion-item"> + <h2 class="accordion-header" id="headingLogs"> + <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseLogs" aria-expanded="false" aria-controls="collapseLogs"> + Logi urządzenia + </button> + </h2> + <div id="collapseLogs" class="accordion-collapse collapse" aria-labelledby="headingLogs" data-bs-parent="#logsAccordion"> + <div class="accordion-body"> + <pre class="bg-light p-3" style="white-space: pre-wrap;">{{ device.last_log or 'Brak logów' }}</pre> + </div> + </div> </div> </div> - <!-- Akcje --> + <!-- Akcje urządzenia --> <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> + <div class="d-flex flex-wrap gap-2"> + <form method="POST" action="{{ url_for('update_device', device_id=device.id) }}"> + <button type="submit" class="btn btn-warning">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 method="POST" action="{{ url_for('update_firmware', device_id=device.id) }}"> + <button type="submit" class="btn btn-danger">Aktualizuj firmware</button> </form> - <a href="{{ url_for('force_check', device_id=device.id) }}" class="btn btn-secondary me-2">Wymuś sprawdzenie</a> + <a href="{{ url_for('force_check', device_id=device.id) }}" class="btn btn-secondary">Wymuś sprawdzenie</a> + </div> + <div class="mt-3"> + <a href="{{ url_for('devices') }}" class="btn btn-outline-secondary">Powrót do listy urządzeń</a> </div> - <a href="{{ url_for('devices') }}" class="btn btn-outline-secondary ms-3">Powrót do listy urządzeń</a> </div> </div> {% endblock %} diff --git a/templates/devices.html b/templates/devices.html index 1510602..725c8ad 100644 --- a/templates/devices.html +++ b/templates/devices.html @@ -16,7 +16,14 @@ <tbody> {% for device in devices %} <tr> - <td>{{ device.name or device.ip }}</td> + <td> + {% if device.name %} + {{ device.name }} | + <code><small class="text-muted">{{ device.ip }}</small></code> + {% else %} + {{ device.ip }} + {% endif %} + </td> <td>{{ device.last_check.strftime('%Y-%m-%d %H:%M:%S') if device.last_check else 'Brak' }}</td> <td> {% if device.update_required %} diff --git a/templates/edit_device.html b/templates/edit_device.html index ddf08d4..0cd7291 100644 --- a/templates/edit_device.html +++ b/templates/edit_device.html @@ -5,10 +5,7 @@ <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> + <!-- Pola edycji: name, ip, port, username, password, branch --> <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> @@ -25,6 +22,16 @@ <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> + <!-- Opcja SSL --> + <div class="mb-3 form-check"> + <input type="checkbox" class="form-check-input" name="use_ssl" id="use_ssl" {% if device.use_ssl %}checked{% endif %}> + <label class="form-check-label" for="use_ssl">Używaj SSL</label> + </div> + <!-- Opcja nie weryfikowania certyfikatu SSL --> + <div class="mb-3 form-check"> + <input type="checkbox" class="form-check-input" name="ssl_insecure" id="ssl_insecure" {% if device.ssl_insecure %}checked{% endif %}> + <label class="form-check-label" for="ssl_insecure">Nie weryfikuj certyfikatu SSL</label> + </div> <div class="mb-3"> <label for="branch" class="form-label">Wybierz branch aktualizacji</label> <select class="form-select" name="branch" id="branch">