"feat: Integrate printer functionality into blueprint system"
This commit is contained in:
parent
774bf645fc
commit
a642456d09
@ -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()
|
||||||
|
181
backend/app/blueprints/printers.py
Normal file
181
backend/app/blueprints/printers.py
Normal 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
|
@ -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",
|
||||||
|
@ -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();
|
||||||
|
@ -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
|
||||||
*/
|
*/
|
||||||
|
@ -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>
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user