"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.calendar import calendar_blueprint
|
||||
from blueprints.users import users_blueprint
|
||||
from blueprints.printers import printers_blueprint
|
||||
|
||||
# Scheduler importieren falls verfügbar
|
||||
try:
|
||||
@ -129,6 +130,7 @@ def csrf_error(error):
|
||||
app.register_blueprint(guest_blueprint)
|
||||
app.register_blueprint(calendar_blueprint)
|
||||
app.register_blueprint(users_blueprint)
|
||||
app.register_blueprint(printers_blueprint)
|
||||
|
||||
# Login-Manager initialisieren
|
||||
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_PASSWORD = "744563017196"
|
||||
|
||||
# Automatische Steckdosen-Erkennung aktivieren
|
||||
TAPO_AUTO_DISCOVERY = True
|
||||
|
||||
# Standard-Steckdosen-IPs (diese können später in der Datenbank überschrieben werden)
|
||||
DEFAULT_TAPO_IPS = [
|
||||
"192.168.1.100",
|
||||
|
@ -104,13 +104,17 @@
|
||||
|
||||
// Error Handler für unbehandelte Fehler
|
||||
window.addEventListener('error', function(e) {
|
||||
console.error('🐛 JavaScript Error abgefangen:', {
|
||||
message: e.message,
|
||||
filename: e.filename,
|
||||
lineno: e.lineno,
|
||||
colno: e.colno,
|
||||
error: e.error
|
||||
});
|
||||
// Bessere Fehler-Serialisierung
|
||||
const errorInfo = {
|
||||
message: e.message || 'Unbekannter Fehler',
|
||||
filename: e.filename || 'Unbekannte Datei',
|
||||
lineno: e.lineno || 0,
|
||||
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
|
||||
if (e.message.includes('MVP.UI.DarkModeManager is not a constructor')) {
|
||||
@ -125,6 +129,19 @@
|
||||
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')) {
|
||||
console.log('🔧 Undefined Properties Fehler erkannt - ignoriert für Stabilität');
|
||||
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
|
||||
*/
|
||||
@ -210,6 +266,57 @@ function updateDashboardStats(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
|
||||
*/
|
||||
|
@ -152,6 +152,9 @@
|
||||
<!-- Chart.js CDN -->
|
||||
<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 -->
|
||||
<script src="{{ url_for('static', filename='js/charts.js') }}"></script>
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user