diff --git a/backend/blueprints/__pycache__/calendar.cpython-313.pyc b/backend/blueprints/__pycache__/calendar.cpython-313.pyc index 7cf8c8606..6349287bf 100644 Binary files a/backend/blueprints/__pycache__/calendar.cpython-313.pyc and b/backend/blueprints/__pycache__/calendar.cpython-313.pyc differ diff --git a/backend/blueprints/__pycache__/guest.cpython-313.pyc b/backend/blueprints/__pycache__/guest.cpython-313.pyc index 014dd364c..87461b85f 100644 Binary files a/backend/blueprints/__pycache__/guest.cpython-313.pyc and b/backend/blueprints/__pycache__/guest.cpython-313.pyc differ diff --git a/backend/blueprints/__pycache__/jobs.cpython-313.pyc b/backend/blueprints/__pycache__/jobs.cpython-313.pyc index 05ad20447..e1c6aa21f 100644 Binary files a/backend/blueprints/__pycache__/jobs.cpython-313.pyc and b/backend/blueprints/__pycache__/jobs.cpython-313.pyc differ diff --git a/backend/blueprints/calendar.py b/backend/blueprints/calendar.py index c73206e91..050abafb9 100644 --- a/backend/blueprints/calendar.py +++ b/backend/blueprints/calendar.py @@ -182,6 +182,7 @@ def api_get_calendar_events(): # Optional: Filter nach Druckern printer_id = request.args.get('printer_id') + show_plug_events = request.args.get('show_plug_events', 'false').lower() == 'true' with get_cached_session() as db_session: # Jobs im angegebenen Zeitraum abfragen @@ -234,12 +235,89 @@ def api_get_calendar_events(): "description": job.description, "userName": user_name, "printerId": job.printer_id, - "printerName": printer_name + "printerName": printer_name, + "eventType": "job" } } events.append(event) + # Steckdosen-Status-Events hinzufügen (falls gewünscht) + if show_plug_events: + from models import PlugStatusLog + + # PlugStatusLog-Events im Zeitraum abfragen + plug_query = db_session.query(PlugStatusLog).filter( + PlugStatusLog.timestamp >= start_date, + PlugStatusLog.timestamp <= end_date + ) + + if printer_id: + plug_query = plug_query.filter(PlugStatusLog.printer_id == printer_id) + + plug_logs = plug_query.order_by(PlugStatusLog.timestamp.desc()).all() + + # Steckdosen-Events gruppieren (nur Statuswechsel anzeigen) + for i, log in enumerate(plug_logs): + # Prüfen ob es eine Statusänderung ist (nicht einfach ein Status-Check) + if i < len(plug_logs) - 1: + prev_log = plug_logs[i + 1] + if prev_log.status == log.status and prev_log.printer_id == log.printer_id: + continue # Gleicher Status, überspringen + + # Drucker-Name für den Event + printer = db_session.query(Printer).filter_by(id=log.printer_id).first() + printer_name = printer.name if printer else f"Drucker {log.printer_id}" + + # Event-Farbe basierend auf Status + plug_color = "#FF6B35" # Orange für Steckdosen-Events + if log.status == "on": + plug_color = "#4ECDC4" # Türkis für eingeschaltet + elif log.status == "off": + plug_color = "#FFD23F" # Gelb für ausgeschaltet + elif log.status == "disconnected": + plug_color = "#EE6C4D" # Rot für nicht erreichbar + + # Status-Text übersetzen + status_text = { + "on": "Eingeschaltet", + "off": "Ausgeschaltet", + "connected": "Verbunden", + "disconnected": "Offline" + }.get(log.status, log.status) + + # Quelle anzeigen + source_text = { + "system": "System", + "manual": "Manuell", + "api": "API", + "scheduler": "Scheduler" + }.get(log.source, log.source) + + plug_event = { + "id": f"plug_{log.id}", + "title": f"🔌 {printer_name}: {status_text}", + "start": log.timestamp.isoformat(), + "end": log.timestamp.isoformat(), # Punktereignis + "color": plug_color, + "display": "list-item", # Als kleines Item anzeigen + "extendedProps": { + "eventType": "plug_status", + "status": log.status, + "printerId": log.printer_id, + "printerName": printer_name, + "source": source_text, + "powerConsumption": log.power_consumption, + "voltage": log.voltage, + "current": log.current, + "responseTime": log.response_time_ms, + "notes": log.notes, + "errorMessage": log.error_message + } + } + + events.append(plug_event) + logger.info(f"📅 Kalender-Events abgerufen: {len(events)} Einträge für Zeitraum {start_date} bis {end_date}") return jsonify(events) diff --git a/backend/blueprints/guest.py b/backend/blueprints/guest.py index f940b9a38..1e9404c67 100644 --- a/backend/blueprints/guest.py +++ b/backend/blueprints/guest.py @@ -446,11 +446,16 @@ def api_start_job_with_code(): # OTP als verwendet markieren matching_request.otp_used_at = now - # Drucker einschalten (falls implementiert) + # Drucker einschalten über Tapo-Steckdose if job.printer and job.printer.plug_ip: try: - from utils.job_scheduler import toggle_plug - toggle_plug(job.printer_id, True) + from utils.job_scheduler import BackgroundTaskScheduler + scheduler = BackgroundTaskScheduler() + plug_success = scheduler.toggle_printer_plug(job.printer_id, True) + if plug_success: + logger.info(f"🔌 Drucker für Gast-Job {job.id} eingeschaltet") + else: + logger.warning(f"⚠️ Steckdose für Gast-Job {job.id} konnte nicht eingeschaltet werden") except Exception as e: logger.warning(f"Fehler beim Einschalten des Druckers: {str(e)}") diff --git a/backend/blueprints/jobs.py b/backend/blueprints/jobs.py index ac9312b2e..14820b988 100644 --- a/backend/blueprints/jobs.py +++ b/backend/blueprints/jobs.py @@ -483,6 +483,14 @@ def start_job(job_id): db_session.close() return jsonify({"error": "Drucker ist nicht online"}), 400 + # Drucker einschalten über Tapo-Steckdose + from utils.job_scheduler import BackgroundTaskScheduler + scheduler = BackgroundTaskScheduler() + plug_success = scheduler.toggle_printer_plug(job.printer_id, True) + + if not plug_success: + jobs_logger.warning(f"⚠️ Steckdose für Job {job_id} konnte nicht eingeschaltet werden") + # Job als laufend markieren job.status = "running" job.start_at = datetime.now() @@ -490,6 +498,9 @@ def start_job(job_id): db_session.commit() + if plug_success: + jobs_logger.info(f"🔌 Drucker für Job {job_id} eingeschaltet") + # Job-Objekt für die Antwort serialisieren job_dict = job.to_dict() db_session.close() diff --git a/backend/instance/printer_manager.db-shm b/backend/instance/printer_manager.db-shm new file mode 100644 index 000000000..ee0d50ee5 Binary files /dev/null and b/backend/instance/printer_manager.db-shm differ diff --git a/backend/instance/printer_manager.db-wal b/backend/instance/printer_manager.db-wal new file mode 100644 index 000000000..02b72234e Binary files /dev/null and b/backend/instance/printer_manager.db-wal differ diff --git a/backend/templates/base.html b/backend/templates/base.html index f50507004..7e3e527ba 100644 --- a/backend/templates/base.html +++ b/backend/templates/base.html @@ -703,12 +703,18 @@ {% if current_user.is_authenticated and current_user.is_admin %} -
+
⚙️ Admin-Dashboard + + 🔌 + Status-Kalender +
{% endif %}
diff --git a/backend/templates/calendar.html b/backend/templates/calendar.html index 999238089..ca1ea7420 100644 --- a/backend/templates/calendar.html +++ b/backend/templates/calendar.html @@ -885,6 +885,32 @@ + +
+ +
+ +
+ + +
+
+
+
@@ -1178,7 +1204,32 @@ document.addEventListener('DOMContentLoaded', function() { startTime: '08:00', endTime: '18:00' }, - events: '/api/calendar/events', + events: function(info, successCallback, failureCallback) { + const params = new URLSearchParams({ + start: info.start.toISOString(), + end: info.end.toISOString() + }); + + // Drucker-Filter hinzufügen + const printerFilter = document.getElementById('printerFilter'); + if (printerFilter && printerFilter.value) { + params.append('printer_id', printerFilter.value); + } + + // Steckdosen-Events hinzufügen falls aktiviert + const showPlugEvents = document.getElementById('showPlugEvents'); + if (showPlugEvents && showPlugEvents.checked) { + params.append('show_plug_events', 'true'); + } + + fetch(`/api/calendar/events?${params.toString()}`) + .then(response => response.json()) + .then(data => successCallback(data)) + .catch(error => { + console.error('Fehler beim Laden der Events:', error); + failureCallback(error); + }); + }, editable: canEdit, selectable: canEdit, selectMirror: true, @@ -1223,6 +1274,30 @@ document.addEventListener('DOMContentLoaded', function() { calendar.refetchEvents(); }); + // Steckdosen-Events Toggle Handler + const showPlugEventsToggle = document.getElementById('showPlugEvents'); + if (showPlugEventsToggle) { + showPlugEventsToggle.addEventListener('change', function() { + // Toggle-Animation + const toggleElement = this.nextElementSibling; + const dot = toggleElement.querySelector('.dot'); + const toggleBg = toggleElement.querySelector('div'); + + if (this.checked) { + dot.classList.add('translate-x-5'); + toggleBg.classList.remove('bg-gray-200', 'dark:bg-gray-700'); + toggleBg.classList.add('bg-green-500', 'dark:bg-green-600'); + } else { + dot.classList.remove('translate-x-5'); + toggleBg.classList.remove('bg-green-500', 'dark:bg-green-600'); + toggleBg.classList.add('bg-gray-200', 'dark:bg-gray-700'); + } + + // Kalender neu laden + calendar.refetchEvents(); + }); + } + // Form Submit Handler document.getElementById('eventForm').addEventListener('submit', async function(e) { e.preventDefault();