"feat: Integrate printer functionality into blueprint system"

This commit is contained in:
Till Tomczak 2025-05-29 22:09:29 +02:00
parent 774bf645fc
commit a642456d09
6 changed files with 320 additions and 7 deletions

View File

@ -45,6 +45,7 @@ from utils.file_manager import file_manager, save_job_file, save_guest_file, sav
from blueprints.guest import guest_blueprint from blueprints.guest import guest_blueprint
from blueprints.calendar import calendar_blueprint from blueprints.calendar import calendar_blueprint
from blueprints.users import users_blueprint from blueprints.users import users_blueprint
from blueprints.printers import printers_blueprint
# Scheduler importieren falls verfügbar # Scheduler importieren falls verfügbar
try: try:
@ -129,6 +130,7 @@ def csrf_error(error):
app.register_blueprint(guest_blueprint) app.register_blueprint(guest_blueprint)
app.register_blueprint(calendar_blueprint) app.register_blueprint(calendar_blueprint)
app.register_blueprint(users_blueprint) app.register_blueprint(users_blueprint)
app.register_blueprint(printers_blueprint)
# Login-Manager initialisieren # Login-Manager initialisieren
login_manager = LoginManager() login_manager = LoginManager()

View File

@ -0,0 +1,181 @@
"""
Drucker-Blueprint für MYP Platform
Enthält alle Routen und Funktionen zur Druckerverwaltung, Statusüberwachung und Steuerung.
"""
import os
import json
import time
from datetime import datetime, timedelta
from flask import Blueprint, request, jsonify, current_app, abort, Response
from flask_login import login_required, current_user
from werkzeug.utils import secure_filename
from werkzeug.exceptions import NotFound, BadRequest
from sqlalchemy import func, desc, asc
from sqlalchemy.exc import SQLAlchemyError
from typing import Dict, List, Tuple, Any, Optional
from models import Printer, User, Job, get_db_session
from utils.logging_config import get_logger, measure_execution_time
from utils.permissions import require_permission, Permission, check_permission
from utils.printer_monitor import printer_monitor
# Logger initialisieren
printers_logger = get_logger("printers")
# Blueprint erstellen
printers_blueprint = Blueprint("printers", __name__, url_prefix="/api/printers")
@printers_blueprint.route("/monitor/live-status", methods=["GET"])
@login_required
@measure_execution_time(logger=printers_logger, task_name="API-Live-Drucker-Status-Abfrage")
def get_live_printer_status():
"""
Liefert den aktuellen Live-Status aller Drucker.
Query-Parameter:
- use_cache: ob Cache verwendet werden soll (default: true)
Returns:
JSON mit Live-Status aller Drucker
"""
printers_logger.info(f"🔄 Live-Status-Abfrage von Benutzer {current_user.name} (ID: {current_user.id})")
# Parameter auslesen
use_cache_param = request.args.get("use_cache", "true").lower()
use_cache = use_cache_param == "true"
try:
# Live-Status über den PrinterMonitor abrufen
status_data = printer_monitor.get_live_printer_status(use_session_cache=use_cache)
# Zusammenfassung der Druckerstatus erstellen
summary = printer_monitor.get_printer_summary()
# Antwort mit Status und Zusammenfassung
response = {
"success": True,
"status": status_data,
"summary": summary,
"timestamp": datetime.now().isoformat(),
"cache_used": use_cache
}
printers_logger.info(f"✅ Live-Status-Abfrage erfolgreich: {len(status_data)} Drucker")
return jsonify(response)
except Exception as e:
printers_logger.error(f"❌ Fehler bei Live-Status-Abfrage: {str(e)}")
return jsonify({
"success": False,
"error": "Fehler bei Abfrage des Druckerstatus",
"message": str(e)
}), 500
@printers_blueprint.route("/control/<int:printer_id>/power", methods=["POST"])
@login_required
@require_permission(Permission.MANAGE_PRINTERS)
@measure_execution_time(logger=printers_logger, task_name="API-Drucker-Stromversorgung-Steuerung")
def control_printer_power(printer_id):
"""
Steuert die Stromversorgung eines Druckers (ein-/ausschalten).
Args:
printer_id: ID des zu steuernden Druckers
JSON-Parameter:
- action: "on" oder "off"
Returns:
JSON mit Ergebnis der Steuerungsaktion
"""
printers_logger.info(f"🔌 Stromsteuerung für Drucker {printer_id} von Benutzer {current_user.name}")
# Parameter validieren
data = request.get_json()
if not data or "action" not in data:
return jsonify({
"success": False,
"error": "Parameter 'action' fehlt"
}), 400
action = data["action"]
if action not in ["on", "off"]:
return jsonify({
"success": False,
"error": "Ungültige Aktion. Erlaubt sind 'on' oder 'off'."
}), 400
try:
# Drucker aus Datenbank holen
db_session = get_db_session()
printer = db_session.query(Printer).filter(Printer.id == printer_id).first()
if not printer:
db_session.close()
return jsonify({
"success": False,
"error": f"Drucker mit ID {printer_id} nicht gefunden"
}), 404
# Prüfen, ob Drucker eine Steckdose konfiguriert hat
if not printer.plug_ip or not printer.plug_username or not printer.plug_password:
db_session.close()
return jsonify({
"success": False,
"error": f"Drucker {printer.name} hat keine Steckdose konfiguriert"
}), 400
# Steckdose steuern
from PyP100 import PyP110
try:
# TP-Link Tapo P110 Verbindung herstellen
p110 = PyP110.P110(printer.plug_ip, printer.plug_username, printer.plug_password)
p110.handshake() # Authentifizierung
p110.login() # Login
# Steckdose ein- oder ausschalten
if action == "on":
p110.turnOn()
success = True
message = "Steckdose erfolgreich eingeschaltet"
printer.status = "starting" # Status aktualisieren
else:
p110.turnOff()
success = True
message = "Steckdose erfolgreich ausgeschaltet"
printer.status = "offline" # Status aktualisieren
# Zeitpunkt der letzten Prüfung aktualisieren
printer.last_checked = datetime.now()
db_session.commit()
# Cache leeren, damit neue Status-Abfragen aktuell sind
printer_monitor.clear_all_caches()
printers_logger.info(f"{action.upper()}: Drucker {printer.name} erfolgreich {message}")
except Exception as e:
printers_logger.error(f"❌ Fehler bei Steckdosensteuerung für {printer.name}: {str(e)}")
db_session.close()
return jsonify({
"success": False,
"error": f"Fehler bei Steckdosensteuerung: {str(e)}"
}), 500
db_session.close()
return jsonify({
"success": True,
"message": message,
"printer_id": printer_id,
"printer_name": printer.name,
"action": action,
"timestamp": datetime.now().isoformat()
})
except Exception as e:
printers_logger.error(f"❌ Allgemeiner Fehler bei Stromsteuerung: {str(e)}")
return jsonify({
"success": False,
"error": f"Allgemeiner Fehler: {str(e)}"
}), 500

View File

@ -28,6 +28,9 @@ DATABASE_PATH = os.path.join(BASE_DIR, "database", "myp.db")
TAPO_USERNAME = "till.tomczak@mercedes-benz.com" TAPO_USERNAME = "till.tomczak@mercedes-benz.com"
TAPO_PASSWORD = "744563017196" TAPO_PASSWORD = "744563017196"
# Automatische Steckdosen-Erkennung aktivieren
TAPO_AUTO_DISCOVERY = True
# Standard-Steckdosen-IPs (diese können später in der Datenbank überschrieben werden) # Standard-Steckdosen-IPs (diese können später in der Datenbank überschrieben werden)
DEFAULT_TAPO_IPS = [ DEFAULT_TAPO_IPS = [
"192.168.1.100", "192.168.1.100",

View File

@ -104,13 +104,17 @@
// Error Handler für unbehandelte Fehler // Error Handler für unbehandelte Fehler
window.addEventListener('error', function(e) { window.addEventListener('error', function(e) {
console.error('🐛 JavaScript Error abgefangen:', { // Bessere Fehler-Serialisierung
message: e.message, const errorInfo = {
filename: e.filename, message: e.message || 'Unbekannter Fehler',
lineno: e.lineno, filename: e.filename || 'Unbekannte Datei',
colno: e.colno, lineno: e.lineno || 0,
error: e.error colno: e.colno || 0,
}); stack: e.error ? e.error.stack : 'Stack nicht verfügbar',
type: e.error ? e.error.constructor.name : 'Unbekannter Typ'
};
console.error('🐛 JavaScript Error abgefangen:', errorInfo);
// Spezifische Fehlerbehebungen // Spezifische Fehlerbehebungen
if (e.message.includes('MVP.UI.DarkModeManager is not a constructor')) { if (e.message.includes('MVP.UI.DarkModeManager is not a constructor')) {
@ -125,6 +129,19 @@
return false; return false;
} }
if (e.message.includes('refreshStats is not defined')) {
console.log('🔧 refreshStats Fehler erkannt - lade global-refresh-functions.js');
// Versuche, global-refresh-functions.js zu laden
const script = document.createElement('script');
script.src = '/static/js/global-refresh-functions.js';
script.onload = function() {
console.log('✅ global-refresh-functions.js nachgeladen');
};
document.head.appendChild(script);
e.preventDefault();
return false;
}
if (e.message.includes('Cannot read properties of undefined')) { if (e.message.includes('Cannot read properties of undefined')) {
console.log('🔧 Undefined Properties Fehler erkannt - ignoriert für Stabilität'); console.log('🔧 Undefined Properties Fehler erkannt - ignoriert für Stabilität');
e.preventDefault(); e.preventDefault();

View File

@ -58,6 +58,62 @@ window.refreshDashboard = async function() {
} }
}; };
/**
* Statistiken-Refresh-Funktion
*/
window.refreshStats = async function() {
const refreshButton = document.querySelector('button[onclick="refreshStats()"]');
if (refreshButton) {
refreshButton.disabled = true;
const icon = refreshButton.querySelector('svg');
if (icon) {
icon.classList.add('animate-spin');
}
}
try {
// Basis-Statistiken laden
if (typeof loadBasicStats === 'function') {
await loadBasicStats();
} else {
// Fallback: API-Aufruf für Statistiken
const response = await fetch('/api/stats');
const data = await response.json();
if (response.ok) {
// Statistiken im DOM aktualisieren
updateStatsCounter('total-jobs-count', data.total_jobs);
updateStatsCounter('completed-jobs-count', data.completed_jobs);
updateStatsCounter('online-printers-count', data.online_printers);
updateStatsCounter('success-rate-percent', data.success_rate + '%');
updateStatsCounter('active-jobs-count', data.active_jobs);
updateStatsCounter('failed-jobs-count', data.failed_jobs);
updateStatsCounter('total-users-count', data.total_users);
} else {
throw new Error(data.error || 'Fehler beim Laden der Statistiken');
}
}
// Charts aktualisieren
if (window.refreshAllCharts) {
window.refreshAllCharts();
}
showToast('✅ Statistiken erfolgreich aktualisiert', 'success');
} catch (error) {
console.error('Stats-Refresh Fehler:', error);
showToast('❌ Fehler beim Aktualisieren der Statistiken', 'error');
} finally {
if (refreshButton) {
refreshButton.disabled = false;
const icon = refreshButton.querySelector('svg');
if (icon) {
icon.classList.remove('animate-spin');
}
}
}
};
/** /**
* Jobs-Refresh-Funktion * Jobs-Refresh-Funktion
*/ */
@ -210,6 +266,57 @@ function updateDashboardStats(stats) {
console.log('📊 Dashboard-Statistiken aktualisiert:', stats); console.log('📊 Dashboard-Statistiken aktualisiert:', stats);
} }
/**
* Statistiken-Counter im DOM aktualisieren
*/
function updateStatsCounter(elementId, value, animate = true) {
const element = document.getElementById(elementId);
if (!element) return;
if (animate) {
// Animierte Zählung
const currentValue = parseInt(element.textContent.replace(/[^\d]/g, '')) || 0;
const targetValue = parseInt(value.toString().replace(/[^\d]/g, '')) || 0;
if (currentValue !== targetValue) {
animateCounter(element, currentValue, targetValue, value.toString());
}
} else {
element.textContent = value;
}
}
/**
* Animierte Counter-Funktion
*/
function animateCounter(element, start, end, finalText) {
const duration = 1000; // 1 Sekunde
const startTime = performance.now();
function updateCounter(currentTime) {
const elapsed = currentTime - startTime;
const progress = Math.min(elapsed / duration, 1);
// Easing-Funktion (ease-out)
const easeOut = 1 - Math.pow(1 - progress, 3);
const currentValue = Math.round(start + (end - start) * easeOut);
if (finalText.includes('%')) {
element.textContent = currentValue + '%';
} else {
element.textContent = currentValue;
}
if (progress < 1) {
requestAnimationFrame(updateCounter);
} else {
element.textContent = finalText;
}
}
requestAnimationFrame(updateCounter);
}
/** /**
* CSRF-Token abrufen * CSRF-Token abrufen
*/ */

View File

@ -152,6 +152,9 @@
<!-- Chart.js CDN --> <!-- Chart.js CDN -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.0/chart.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.0/chart.min.js"></script>
<!-- Global Refresh Functions -->
<script src="{{ url_for('static', filename='js/global-refresh-functions.js') }}"></script>
<!-- Charts JavaScript --> <!-- Charts JavaScript -->
<script src="{{ url_for('static', filename='js/charts.js') }}"></script> <script src="{{ url_for('static', filename='js/charts.js') }}"></script>