🎉 Improved backend functionality & documentation, added OTP documentation & JavaScript file. 🎨
This commit is contained in:
496
backend/app.py
496
backend/app.py
@@ -41,6 +41,29 @@ from utils.queue_manager import start_queue_manager, stop_queue_manager, get_que
|
||||
from config.settings import SECRET_KEY, UPLOAD_FOLDER, ALLOWED_EXTENSIONS, ENVIRONMENT, SESSION_LIFETIME, SCHEDULER_ENABLED, SCHEDULER_INTERVAL, TAPO_USERNAME, TAPO_PASSWORD
|
||||
from utils.file_manager import file_manager, save_job_file, save_guest_file, save_avatar_file, save_asset_file, save_log_file, save_backup_file, save_temp_file, delete_file as delete_file_safe
|
||||
|
||||
# ===== OFFLINE-MODUS KONFIGURATION =====
|
||||
# System läuft im Offline-Modus ohne Internetverbindung
|
||||
OFFLINE_MODE = True # Produktionseinstellung für Offline-Betrieb
|
||||
|
||||
# ===== BEDINGTE IMPORTS FÜR OFFLINE-MODUS =====
|
||||
if not OFFLINE_MODE:
|
||||
# Nur laden wenn Online-Modus
|
||||
import requests
|
||||
else:
|
||||
# Offline-Mock für requests
|
||||
class OfflineRequestsMock:
|
||||
"""Mock-Klasse für requests im Offline-Modus"""
|
||||
|
||||
@staticmethod
|
||||
def get(*args, **kwargs):
|
||||
raise ConnectionError("System läuft im Offline-Modus - keine Internet-Verbindung verfügbar")
|
||||
|
||||
@staticmethod
|
||||
def post(*args, **kwargs):
|
||||
raise ConnectionError("System läuft im Offline-Modus - keine Internet-Verbindung verfügbar")
|
||||
|
||||
requests = OfflineRequestsMock()
|
||||
|
||||
# Datenbank-Engine für Kompatibilität mit init_simple_db.py
|
||||
from models import engine as db_engine
|
||||
|
||||
@@ -311,7 +334,7 @@ def format_datetime_filter(value, format='%d.%m.%Y %H:%M'):
|
||||
setup_logging()
|
||||
log_startup_info()
|
||||
|
||||
# Logger für verschiedene Komponenten
|
||||
# app_logger für verschiedene Komponenten
|
||||
app_logger = get_logger("app")
|
||||
auth_logger = get_logger("auth")
|
||||
jobs_logger = get_logger("jobs")
|
||||
@@ -1695,7 +1718,7 @@ def api_admin_system_health():
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim System-Gesundheitscheck: {str(e)}")
|
||||
app_logger.error(f"Fehler beim System-Gesundheitscheck: {str(e)}")
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": str(e)
|
||||
@@ -1731,7 +1754,7 @@ def api_admin_system_health():
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim System-Gesundheitscheck: {str(e)}")
|
||||
app_logger.error(f"Fehler beim System-Gesundheitscheck: {str(e)}")
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": str(e)
|
||||
@@ -3210,6 +3233,118 @@ def test_admin_guest_requests():
|
||||
'user_is_admin': current_user.is_admin if current_user.is_authenticated else False
|
||||
})
|
||||
|
||||
@app.route('/api/guest-status', methods=['POST'])
|
||||
def get_guest_request_status():
|
||||
"""
|
||||
Öffentliche Route für Gäste um ihren Auftragsstatus mit OTP-Code zu prüfen.
|
||||
Keine Authentifizierung erforderlich.
|
||||
"""
|
||||
try:
|
||||
data = request.get_json()
|
||||
if not data:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': 'Keine Daten empfangen'
|
||||
}), 400
|
||||
|
||||
otp_code = data.get('otp_code', '').strip()
|
||||
email = data.get('email', '').strip() # Optional für zusätzliche Verifikation
|
||||
|
||||
if not otp_code:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': 'OTP-Code ist erforderlich'
|
||||
}), 400
|
||||
|
||||
db_session = get_db_session()
|
||||
|
||||
# Alle Gastaufträge finden, die den OTP-Code haben könnten
|
||||
# Da OTP gehashed ist, müssen wir durch alle iterieren
|
||||
guest_requests = db_session.query(GuestRequest).filter(
|
||||
GuestRequest.otp_code.isnot(None)
|
||||
).all()
|
||||
|
||||
found_request = None
|
||||
for request_obj in guest_requests:
|
||||
if request_obj.verify_otp(otp_code):
|
||||
# Zusätzliche E-Mail-Verifikation falls angegeben
|
||||
if email and request_obj.email.lower() != email.lower():
|
||||
continue
|
||||
found_request = request_obj
|
||||
break
|
||||
|
||||
if not found_request:
|
||||
db_session.close()
|
||||
app_logger.warning(f"Ungültiger OTP-Code für Gast-Status-Abfrage: {otp_code[:4]}****")
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': 'Ungültiger Code oder E-Mail-Adresse'
|
||||
}), 404
|
||||
|
||||
# Status-Informationen für den Gast zusammenstellen
|
||||
status_info = {
|
||||
'id': found_request.id,
|
||||
'name': found_request.name,
|
||||
'file_name': found_request.file_name,
|
||||
'status': found_request.status,
|
||||
'created_at': found_request.created_at.isoformat() if found_request.created_at else None,
|
||||
'updated_at': found_request.updated_at.isoformat() if found_request.updated_at else None,
|
||||
'duration_minutes': found_request.duration_minutes,
|
||||
'copies': found_request.copies,
|
||||
'reason': found_request.reason
|
||||
}
|
||||
|
||||
# Status-spezifische Informationen hinzufügen
|
||||
if found_request.status == 'approved':
|
||||
status_info.update({
|
||||
'approved_at': found_request.approved_at.isoformat() if found_request.approved_at else None,
|
||||
'approval_notes': found_request.approval_notes,
|
||||
'message': 'Ihr Auftrag wurde genehmigt! Sie können mit dem Drucken beginnen.'
|
||||
})
|
||||
|
||||
elif found_request.status == 'rejected':
|
||||
status_info.update({
|
||||
'rejected_at': found_request.rejected_at.isoformat() if found_request.rejected_at else None,
|
||||
'rejection_reason': found_request.rejection_reason,
|
||||
'message': 'Ihr Auftrag wurde leider abgelehnt.'
|
||||
})
|
||||
|
||||
elif found_request.status == 'pending':
|
||||
# Berechne wie lange der Auftrag schon wartet
|
||||
if found_request.created_at:
|
||||
waiting_time = datetime.now() - found_request.created_at
|
||||
hours_waiting = int(waiting_time.total_seconds() / 3600)
|
||||
status_info.update({
|
||||
'hours_waiting': hours_waiting,
|
||||
'message': f'Ihr Auftrag wird bearbeitet. Wartezeit: {hours_waiting} Stunden.'
|
||||
})
|
||||
else:
|
||||
status_info['message'] = 'Ihr Auftrag wird bearbeitet.'
|
||||
|
||||
db_session.commit() # OTP als verwendet markieren
|
||||
db_session.close()
|
||||
|
||||
app_logger.info(f"Gast-Status-Abfrage erfolgreich für Request {found_request.id}")
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'request': status_info
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
app_logger.error(f"Fehler bei Gast-Status-Abfrage: {str(e)}")
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': 'Fehler beim Abrufen des Status'
|
||||
}), 500
|
||||
|
||||
@app.route('/guest-status')
|
||||
def guest_status_page():
|
||||
"""
|
||||
Öffentliche Seite für Gäste um ihren Auftragsstatus zu prüfen.
|
||||
"""
|
||||
return render_template('guest_status.html')
|
||||
|
||||
@app.route('/api/admin/guest-requests', methods=['GET'])
|
||||
@admin_required
|
||||
def get_admin_guest_requests():
|
||||
@@ -3374,32 +3509,37 @@ def approve_guest_request(request_id):
|
||||
if printer:
|
||||
guest_request.assigned_printer_id = printer_id
|
||||
|
||||
# OTP-Code generieren für den Gast
|
||||
import secrets
|
||||
otp_code = ''.join([str(secrets.randbelow(10)) for _ in range(6)])
|
||||
guest_request.otp_code = otp_code
|
||||
guest_request.otp_expires_at = datetime.now() + timedelta(hours=24)
|
||||
# OTP-Code generieren falls noch nicht vorhanden (nutze die Methode aus models.py)
|
||||
otp_code = None
|
||||
if not guest_request.otp_code:
|
||||
otp_code = guest_request.generate_otp()
|
||||
guest_request.otp_expires_at = datetime.now() + timedelta(hours=48) # 48h gültig
|
||||
|
||||
db_session.commit()
|
||||
|
||||
# Benachrichtigung an den Gast senden (falls E-Mail verfügbar)
|
||||
if guest_request.email:
|
||||
if guest_request.email and otp_code:
|
||||
try:
|
||||
# Hier würde normalerweise eine E-Mail gesendet werden
|
||||
app_logger.info(f"E-Mail-Benachrichtigung würde an {guest_request.email} gesendet (OTP: {otp_code})")
|
||||
app_logger.info(f"Genehmigungs-E-Mail würde an {guest_request.email} gesendet (OTP für Status-Abfrage verfügbar)")
|
||||
except Exception as e:
|
||||
app_logger.warning(f"Fehler beim Senden der E-Mail-Benachrichtigung: {str(e)}")
|
||||
|
||||
db_session.close()
|
||||
|
||||
app_logger.info(f"Gastauftrag {request_id} von Admin {current_user.id} genehmigt (OTP: {otp_code})")
|
||||
app_logger.info(f"Gastauftrag {request_id} von Admin {current_user.id} genehmigt")
|
||||
|
||||
return jsonify({
|
||||
response_data = {
|
||||
'success': True,
|
||||
'message': 'Gastauftrag erfolgreich genehmigt',
|
||||
'otp_code': otp_code,
|
||||
'expires_at': (datetime.now() + timedelta(hours=24)).isoformat()
|
||||
})
|
||||
'message': 'Gastauftrag erfolgreich genehmigt'
|
||||
}
|
||||
|
||||
# OTP-Code nur zurückgeben wenn er neu generiert wurde (für Admin-Info)
|
||||
if otp_code:
|
||||
response_data['otp_code_generated'] = True
|
||||
response_data['status_check_url'] = url_for('guest_status_page', _external=True)
|
||||
|
||||
return jsonify(response_data)
|
||||
|
||||
except Exception as e:
|
||||
app_logger.error(f"Fehler beim Genehmigen des Gastauftrags {request_id}: {str(e)}")
|
||||
@@ -4065,7 +4205,7 @@ def get_validation_js():
|
||||
response.headers['Cache-Control'] = 'public, max-age=3600' # 1 Stunde Cache
|
||||
return response
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Laden des Validierungs-JS: {str(e)}")
|
||||
app_logger.error(f"Fehler beim Laden des Validierungs-JS: {str(e)}")
|
||||
return "console.error('Validierungs-JavaScript konnte nicht geladen werden');", 500
|
||||
|
||||
@app.route('/api/validation/validate-form', methods=['POST'])
|
||||
@@ -4099,7 +4239,7 @@ def validate_form_api():
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler bei Formular-Validierung: {str(e)}")
|
||||
app_logger.error(f"Fehler bei Formular-Validierung: {str(e)}")
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
|
||||
# ===== REPORT GENERATOR API =====
|
||||
@@ -4171,7 +4311,7 @@ def generate_report():
|
||||
return jsonify({'error': 'Report-Generierung fehlgeschlagen'}), 500
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler bei Report-Generierung: {str(e)}")
|
||||
app_logger.error(f"Fehler bei Report-Generierung: {str(e)}")
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
# ===== REALTIME DASHBOARD API =====
|
||||
@@ -4183,7 +4323,7 @@ def get_dashboard_config():
|
||||
config = dashboard_manager.get_dashboard_config(current_user.id)
|
||||
return jsonify(config)
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Laden der Dashboard-Konfiguration: {str(e)}")
|
||||
app_logger.error(f"Fehler beim Laden der Dashboard-Konfiguration: {str(e)}")
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
@app.route('/api/dashboard/widgets/<widget_id>/data', methods=['GET'])
|
||||
@@ -4198,7 +4338,7 @@ def get_widget_data(widget_id):
|
||||
'timestamp': datetime.now().isoformat()
|
||||
})
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Laden der Widget-Daten für {widget_id}: {str(e)}")
|
||||
app_logger.error(f"Fehler beim Laden der Widget-Daten für {widget_id}: {str(e)}")
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
@app.route('/api/dashboard/emit-event', methods=['POST'])
|
||||
@@ -4223,7 +4363,7 @@ def emit_dashboard_event():
|
||||
return jsonify({'success': True})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Senden des Dashboard-Ereignisses: {str(e)}")
|
||||
app_logger.error(f"Fehler beim Senden des Dashboard-Ereignisses: {str(e)}")
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
@app.route('/api/dashboard/client-js', methods=['GET'])
|
||||
@@ -4236,7 +4376,7 @@ def get_dashboard_js():
|
||||
response.headers['Cache-Control'] = 'public, max-age=1800' # 30 Minuten Cache
|
||||
return response
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Laden des Dashboard-JS: {str(e)}")
|
||||
app_logger.error(f"Fehler beim Laden des Dashboard-JS: {str(e)}")
|
||||
return "console.error('Dashboard-JavaScript konnte nicht geladen werden');", 500
|
||||
|
||||
# ===== DRAG & DROP API =====
|
||||
@@ -4270,7 +4410,7 @@ def update_job_order():
|
||||
return jsonify({'error': 'Fehler beim Aktualisieren der Job-Reihenfolge'}), 500
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Aktualisieren der Job-Reihenfolge: {str(e)}")
|
||||
app_logger.error(f"Fehler beim Aktualisieren der Job-Reihenfolge: {str(e)}")
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
@app.route('/api/dragdrop/get-job-order/<int:printer_id>', methods=['GET'])
|
||||
@@ -4300,7 +4440,7 @@ def get_job_order_api(printer_id):
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Abrufen der Job-Reihenfolge: {str(e)}")
|
||||
app_logger.error(f"Fehler beim Abrufen der Job-Reihenfolge: {str(e)}")
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
@app.route('/api/dragdrop/upload-session', methods=['POST'])
|
||||
@@ -4318,7 +4458,7 @@ def create_upload_session():
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Erstellen der Upload-Session: {str(e)}")
|
||||
app_logger.error(f"Fehler beim Erstellen der Upload-Session: {str(e)}")
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
@app.route('/api/dragdrop/upload-progress/<session_id>', methods=['GET'])
|
||||
@@ -4329,7 +4469,7 @@ def get_upload_progress(session_id):
|
||||
progress = drag_drop_manager.get_session_progress(session_id)
|
||||
return jsonify(progress)
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Abrufen des Upload-Progress: {str(e)}")
|
||||
app_logger.error(f"Fehler beim Abrufen des Upload-Progress: {str(e)}")
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
@app.route('/api/dragdrop/client-js', methods=['GET'])
|
||||
@@ -4342,7 +4482,7 @@ def get_dragdrop_js():
|
||||
response.headers['Cache-Control'] = 'public, max-age=3600'
|
||||
return response
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Laden des Drag & Drop JS: {str(e)}")
|
||||
app_logger.error(f"Fehler beim Laden des Drag & Drop JS: {str(e)}")
|
||||
return "console.error('Drag & Drop JavaScript konnte nicht geladen werden');", 500
|
||||
|
||||
@app.route('/api/dragdrop/client-css', methods=['GET'])
|
||||
@@ -4355,7 +4495,7 @@ def get_dragdrop_css():
|
||||
response.headers['Cache-Control'] = 'public, max-age=3600'
|
||||
return response
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Laden des Drag & Drop CSS: {str(e)}")
|
||||
app_logger.error(f"Fehler beim Laden des Drag & Drop CSS: {str(e)}")
|
||||
return "/* Drag & Drop CSS konnte nicht geladen werden */", 500
|
||||
|
||||
# ===== ADVANCED TABLES API =====
|
||||
@@ -4422,7 +4562,7 @@ def query_advanced_table():
|
||||
return jsonify(result)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler bei erweiterte Tabellen-Abfrage: {str(e)}")
|
||||
app_logger.error(f"Fehler bei erweiterte Tabellen-Abfrage: {str(e)}")
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
@app.route('/api/tables/export', methods=['POST'])
|
||||
@@ -4435,31 +4575,137 @@ def export_table_data():
|
||||
export_format = data.get('format', 'csv')
|
||||
query_params = data.get('query', {})
|
||||
|
||||
# Hier würde die Export-Logik implementiert
|
||||
# Für jetzt einfache CSV-Export-Simulation
|
||||
# Vollständige Export-Logik implementierung
|
||||
app_logger.info(f"📊 Starte Tabellen-Export: {table_type} als {export_format}")
|
||||
|
||||
# Tabellen-Konfiguration basierend auf Typ erstellen
|
||||
if table_type == 'jobs':
|
||||
config = create_table_config(
|
||||
'jobs',
|
||||
['id', 'filename', 'status', 'printer_name', 'user_name', 'created_at', 'completed_at'],
|
||||
base_query='Job'
|
||||
)
|
||||
elif table_type == 'printers':
|
||||
config = create_table_config(
|
||||
'printers',
|
||||
['id', 'name', 'ip_address', 'status', 'location', 'model'],
|
||||
base_query='Printer'
|
||||
)
|
||||
elif table_type == 'users':
|
||||
config = create_table_config(
|
||||
'users',
|
||||
['id', 'name', 'email', 'role', 'active', 'last_login'],
|
||||
base_query='User'
|
||||
)
|
||||
else:
|
||||
return jsonify({'error': 'Unbekannter Tabellen-Typ für Export'}), 400
|
||||
|
||||
# Erweiterte Abfrage für Export-Daten erstellen
|
||||
query_builder = AdvancedTableQuery(config)
|
||||
|
||||
# Filter aus Query-Parametern anwenden
|
||||
if 'filters' in query_params:
|
||||
for filter_data in query_params['filters']:
|
||||
query_builder.add_filter(
|
||||
filter_data['column'],
|
||||
filter_data['operator'],
|
||||
filter_data['value']
|
||||
)
|
||||
|
||||
# Sortierung anwenden
|
||||
if 'sort' in query_params:
|
||||
query_builder.set_sorting(
|
||||
query_params['sort']['column'],
|
||||
query_params['sort']['direction']
|
||||
)
|
||||
|
||||
# Für Export: Alle Daten ohne Paginierung
|
||||
query_builder.set_pagination(1, 10000) # Maximale Anzahl für Export
|
||||
|
||||
# Daten abrufen
|
||||
result = query_builder.execute()
|
||||
export_data = result.get('data', [])
|
||||
|
||||
if export_format == 'csv':
|
||||
import csv
|
||||
import io
|
||||
|
||||
# CSV-Export implementierung
|
||||
output = io.StringIO()
|
||||
writer = csv.writer(output)
|
||||
writer = csv.writer(output, delimiter=';', quoting=csv.QUOTE_MINIMAL)
|
||||
|
||||
# Beispiel-Daten (würde durch echte Abfrage ersetzt)
|
||||
writer.writerow(['ID', 'Name', 'Status', 'Erstellt'])
|
||||
writer.writerow([1, 'Beispiel Job', 'Aktiv', '2025-01-07'])
|
||||
# Header-Zeile schreiben
|
||||
if export_data:
|
||||
headers = list(export_data[0].keys())
|
||||
writer.writerow(headers)
|
||||
|
||||
# Daten-Zeilen schreiben
|
||||
for row in export_data:
|
||||
# Werte für CSV formatieren
|
||||
formatted_row = []
|
||||
for value in row.values():
|
||||
if value is None:
|
||||
formatted_row.append('')
|
||||
elif isinstance(value, datetime):
|
||||
formatted_row.append(value.strftime('%d.%m.%Y %H:%M:%S'))
|
||||
else:
|
||||
formatted_row.append(str(value))
|
||||
writer.writerow(formatted_row)
|
||||
|
||||
response = make_response(output.getvalue())
|
||||
response.headers['Content-Type'] = 'text/csv'
|
||||
response.headers['Content-Disposition'] = f'attachment; filename="{table_type}_export.csv"'
|
||||
# Response erstellen
|
||||
csv_content = output.getvalue()
|
||||
output.close()
|
||||
|
||||
response = make_response(csv_content)
|
||||
response.headers['Content-Type'] = 'text/csv; charset=utf-8'
|
||||
response.headers['Content-Disposition'] = f'attachment; filename="{table_type}_export_{datetime.now().strftime("%Y%m%d_%H%M%S")}.csv"'
|
||||
|
||||
app_logger.info(f"✅ CSV-Export erfolgreich: {len(export_data)} Datensätze")
|
||||
return response
|
||||
|
||||
return jsonify({'error': 'Export-Format nicht unterstützt'}), 400
|
||||
|
||||
elif export_format == 'json':
|
||||
# JSON-Export implementierung
|
||||
json_content = json.dumps(export_data, indent=2, default=str, ensure_ascii=False)
|
||||
|
||||
response = make_response(json_content)
|
||||
response.headers['Content-Type'] = 'application/json; charset=utf-8'
|
||||
response.headers['Content-Disposition'] = f'attachment; filename="{table_type}_export_{datetime.now().strftime("%Y%m%d_%H%M%S")}.json"'
|
||||
|
||||
app_logger.info(f"✅ JSON-Export erfolgreich: {len(export_data)} Datensätze")
|
||||
return response
|
||||
|
||||
elif export_format == 'excel':
|
||||
# Excel-Export implementierung (falls openpyxl verfügbar)
|
||||
try:
|
||||
import openpyxl
|
||||
from openpyxl.utils.dataframe import dataframe_to_rows
|
||||
import pandas as pd
|
||||
|
||||
# DataFrame erstellen
|
||||
df = pd.DataFrame(export_data)
|
||||
|
||||
# Excel-Datei in Memory erstellen
|
||||
output = io.BytesIO()
|
||||
with pd.ExcelWriter(output, engine='openpyxl') as writer:
|
||||
df.to_excel(writer, sheet_name=table_type.capitalize(), index=False)
|
||||
|
||||
output.seek(0)
|
||||
|
||||
response = make_response(output.getvalue())
|
||||
response.headers['Content-Type'] = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
||||
response.headers['Content-Disposition'] = f'attachment; filename="{table_type}_export_{datetime.now().strftime("%Y%m%d_%H%M%S")}.xlsx"'
|
||||
|
||||
app_logger.info(f"✅ Excel-Export erfolgreich: {len(export_data)} Datensätze")
|
||||
return response
|
||||
|
||||
except ImportError:
|
||||
app_logger.warning("⚠️ Excel-Export nicht verfügbar - openpyxl/pandas fehlt")
|
||||
return jsonify({'error': 'Excel-Export nicht verfügbar - erforderliche Bibliotheken fehlen'}), 400
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Tabellen-Export: {str(e)}")
|
||||
app_logger.error(f"Fehler beim Tabellen-Export: {str(e)}")
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
|
||||
@app.route('/api/tables/client-js', methods=['GET'])
|
||||
def get_tables_js():
|
||||
"""Liefert Client-seitige Advanced Tables JavaScript"""
|
||||
@@ -4470,7 +4716,7 @@ def get_tables_js():
|
||||
response.headers['Cache-Control'] = 'public, max-age=3600'
|
||||
return response
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Laden des Tables-JS: {str(e)}")
|
||||
app_logger.error(f"Fehler beim Laden des Tables-JS: {str(e)}")
|
||||
return "console.error('Advanced Tables JavaScript konnte nicht geladen werden');", 500
|
||||
|
||||
@app.route('/api/tables/client-css', methods=['GET'])
|
||||
@@ -4483,7 +4729,7 @@ def get_tables_css():
|
||||
response.headers['Cache-Control'] = 'public, max-age=3600'
|
||||
return response
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Laden des Tables-CSS: {str(e)}")
|
||||
app_logger.error(f"Fehler beim Laden des Tables-CSS: {str(e)}")
|
||||
return "/* Advanced Tables CSS konnte nicht geladen werden */", 500
|
||||
|
||||
# ===== MAINTENANCE SYSTEM API =====
|
||||
@@ -4508,7 +4754,7 @@ def maintenance_tasks():
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Abrufen der Wartungsaufgaben: {str(e)}")
|
||||
app_logger.error(f"Fehler beim Abrufen der Wartungsaufgaben: {str(e)}")
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
elif request.method == 'POST':
|
||||
@@ -4542,7 +4788,7 @@ def maintenance_tasks():
|
||||
return jsonify({'error': 'Fehler beim Erstellen der Wartungsaufgabe'}), 500
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Erstellen der Wartungsaufgabe: {str(e)}")
|
||||
app_logger.error(f"Fehler beim Erstellen der Wartungsaufgabe: {str(e)}")
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
@app.route('/api/maintenance/tasks/<int:task_id>/status', methods=['PUT'])
|
||||
@@ -4570,7 +4816,7 @@ def update_maintenance_task_status(task_id):
|
||||
return jsonify({'error': 'Fehler beim Aktualisieren des Status'}), 500
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Aktualisieren des Wartungsaufgaben-Status: {str(e)}")
|
||||
app_logger.error(f"Fehler beim Aktualisieren des Wartungsaufgaben-Status: {str(e)}")
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
@app.route('/api/maintenance/overview', methods=['GET'])
|
||||
@@ -4581,7 +4827,7 @@ def get_maintenance_overview():
|
||||
overview = get_maintenance_overview()
|
||||
return jsonify(overview)
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Abrufen der Wartungs-Übersicht: {str(e)}")
|
||||
app_logger.error(f"Fehler beim Abrufen der Wartungs-Übersicht: {str(e)}")
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
@app.route('/api/maintenance/schedule', methods=['POST'])
|
||||
@@ -4609,7 +4855,7 @@ def schedule_maintenance_api():
|
||||
return jsonify({'error': 'Fehler beim Erstellen des Wartungsplans'}), 500
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Planen der Wartung: {str(e)}")
|
||||
app_logger.error(f"Fehler beim Planen der Wartung: {str(e)}")
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
# ===== MULTI-LOCATION SYSTEM API =====
|
||||
@@ -4631,7 +4877,7 @@ def locations():
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Abrufen der Standorte: {str(e)}")
|
||||
app_logger.error(f"Fehler beim Abrufen der Standorte: {str(e)}")
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
elif request.method == 'POST':
|
||||
@@ -4657,7 +4903,7 @@ def locations():
|
||||
return jsonify({'error': 'Fehler beim Erstellen des Standorts'}), 500
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Erstellen des Standorts: {str(e)}")
|
||||
app_logger.error(f"Fehler beim Erstellen des Standorts: {str(e)}")
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
@app.route('/api/locations/<int:location_id>/users', methods=['GET', 'POST'])
|
||||
@@ -4675,7 +4921,7 @@ def location_users(location_id):
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Abrufen der Standort-Benutzer: {str(e)}")
|
||||
app_logger.error(f"Fehler beim Abrufen der Standort-Benutzer: {str(e)}")
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
elif request.method == 'POST':
|
||||
@@ -4698,7 +4944,7 @@ def location_users(location_id):
|
||||
return jsonify({'error': 'Fehler bei der Benutzer-Zuweisung'}), 500
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler bei der Benutzer-Zuweisung: {str(e)}")
|
||||
app_logger.error(f"Fehler bei der Benutzer-Zuweisung: {str(e)}")
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
@app.route('/api/locations/user/<int:user_id>', methods=['GET'])
|
||||
@@ -4718,7 +4964,7 @@ def get_user_locations_api(user_id):
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Abrufen der Benutzer-Standorte: {str(e)}")
|
||||
app_logger.error(f"Fehler beim Abrufen der Benutzer-Standorte: {str(e)}")
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
@app.route('/api/locations/distance', methods=['POST'])
|
||||
@@ -4741,7 +4987,7 @@ def calculate_distance_api():
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler bei Entfernungsberechnung: {str(e)}")
|
||||
app_logger.error(f"Fehler bei Entfernungsberechnung: {str(e)}")
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
@app.route('/api/locations/nearest', methods=['POST'])
|
||||
@@ -4776,10 +5022,47 @@ def find_nearest_location_api():
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler bei der Suche nach nächstem Standort: {str(e)}")
|
||||
app_logger.error(f"Fehler bei der Suche nach nächstem Standort: {str(e)}")
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
# ===== GASTANTRÄGE API-ROUTEN =====
|
||||
|
||||
def setup_database_with_migrations():
|
||||
"""
|
||||
Datenbank initialisieren und alle erforderlichen Tabellen erstellen.
|
||||
Führt Migrationen für neue Tabellen wie JobOrder durch.
|
||||
"""
|
||||
try:
|
||||
app_logger.info("🔄 Starte Datenbank-Setup und Migrationen...")
|
||||
|
||||
# Standard-Datenbank-Initialisierung
|
||||
init_database()
|
||||
|
||||
# Explizite Migration für JobOrder-Tabelle
|
||||
engine = get_engine()
|
||||
|
||||
# Erstelle alle Tabellen (nur neue werden tatsächlich erstellt)
|
||||
Base.metadata.create_all(engine)
|
||||
|
||||
# Prüfe ob JobOrder-Tabelle existiert
|
||||
from sqlalchemy import inspect
|
||||
inspector = inspect(engine)
|
||||
existing_tables = inspector.get_table_names()
|
||||
|
||||
if 'job_orders' in existing_tables:
|
||||
app_logger.info("✅ JobOrder-Tabelle bereits vorhanden")
|
||||
else:
|
||||
# Tabelle manuell erstellen
|
||||
JobOrder.__table__.create(engine, checkfirst=True)
|
||||
app_logger.info("✅ JobOrder-Tabelle erfolgreich erstellt")
|
||||
|
||||
# Initial-Admin erstellen falls nicht vorhanden
|
||||
create_initial_admin()
|
||||
|
||||
app_logger.info("✅ Datenbank-Setup und Migrationen erfolgreich abgeschlossen")
|
||||
|
||||
except Exception as e:
|
||||
app_logger.error(f"❌ Fehler bei Datenbank-Setup: {str(e)}")
|
||||
raise e
|
||||
|
||||
# ===== STARTUP UND MAIN =====
|
||||
if __name__ == "__main__":
|
||||
@@ -5008,100 +5291,3 @@ if __name__ == "__main__":
|
||||
except:
|
||||
pass
|
||||
sys.exit(1)
|
||||
|
||||
def setup_database_with_migrations():
|
||||
"""
|
||||
Datenbank initialisieren und alle erforderlichen Tabellen erstellen.
|
||||
Führt Migrationen für neue Tabellen wie JobOrder durch.
|
||||
"""
|
||||
try:
|
||||
app_logger.info("🔄 Starte Datenbank-Setup und Migrationen...")
|
||||
|
||||
# Standard-Datenbank-Initialisierung
|
||||
init_database()
|
||||
|
||||
# Explizite Migration für JobOrder-Tabelle
|
||||
engine = get_engine()
|
||||
|
||||
# Erstelle alle Tabellen (nur neue werden tatsächlich erstellt)
|
||||
Base.metadata.create_all(engine)
|
||||
|
||||
# Prüfe ob JobOrder-Tabelle existiert
|
||||
from sqlalchemy import inspect
|
||||
inspector = inspect(engine)
|
||||
existing_tables = inspector.get_table_names()
|
||||
|
||||
if 'job_orders' in existing_tables:
|
||||
app_logger.info("✅ JobOrder-Tabelle bereits vorhanden")
|
||||
else:
|
||||
# Tabelle manuell erstellen
|
||||
JobOrder.__table__.create(engine, checkfirst=True)
|
||||
app_logger.info("✅ JobOrder-Tabelle erfolgreich erstellt")
|
||||
|
||||
# Initial-Admin erstellen falls nicht vorhanden
|
||||
create_initial_admin()
|
||||
|
||||
app_logger.info("✅ Datenbank-Setup und Migrationen erfolgreich abgeschlossen")
|
||||
|
||||
except Exception as e:
|
||||
app_logger.error(f"❌ Fehler bei Datenbank-Setup: {str(e)}")
|
||||
raise e
|
||||
|
||||
@app.route("/admin/printers/<int:printer_id>/settings")
|
||||
@login_required
|
||||
def admin_printer_settings_page(printer_id):
|
||||
"""Zeigt die Drucker-Einstellungsseite an."""
|
||||
if not current_user.is_admin:
|
||||
flash("Sie haben keine Berechtigung für den Admin-Bereich.", "error")
|
||||
return redirect(url_for("index"))
|
||||
|
||||
db_session = get_db_session()
|
||||
try:
|
||||
printer = db_session.get(Printer, printer_id)
|
||||
if not printer:
|
||||
flash("Drucker nicht gefunden.", "error")
|
||||
return redirect(url_for("admin_page"))
|
||||
|
||||
printer_data = {
|
||||
"id": printer.id,
|
||||
"name": printer.name,
|
||||
"model": printer.model or 'Unbekanntes Modell',
|
||||
"location": printer.location or 'Unbekannter Standort',
|
||||
"mac_address": printer.mac_address,
|
||||
"plug_ip": printer.plug_ip,
|
||||
"status": printer.status or "offline",
|
||||
"active": printer.active if hasattr(printer, 'active') else True,
|
||||
"created_at": printer.created_at.isoformat() if printer.created_at else datetime.now().isoformat()
|
||||
}
|
||||
|
||||
db_session.close()
|
||||
return render_template("admin_printer_settings.html", printer=printer_data)
|
||||
|
||||
except Exception as e:
|
||||
db_session.close()
|
||||
app_logger.error(f"Fehler beim Laden der Drucker-Einstellungen: {str(e)}")
|
||||
flash("Fehler beim Laden der Drucker-Daten.", "error")
|
||||
return redirect(url_for("admin_page"))
|
||||
# Erstelle alle Tabellen (nur neue werden tatsächlich erstellt)
|
||||
Base.metadata.create_all(engine)
|
||||
|
||||
# Prüfe ob JobOrder-Tabelle existiert
|
||||
from sqlalchemy import inspect
|
||||
inspector = inspect(engine)
|
||||
existing_tables = inspector.get_table_names()
|
||||
|
||||
if 'job_orders' in existing_tables:
|
||||
app_logger.info("✅ JobOrder-Tabelle bereits vorhanden")
|
||||
else:
|
||||
# Tabelle manuell erstellen
|
||||
JobOrder.__table__.create(engine, checkfirst=True)
|
||||
app_logger.info("✅ JobOrder-Tabelle erfolgreich erstellt")
|
||||
|
||||
# Initial-Admin erstellen falls nicht vorhanden
|
||||
create_initial_admin()
|
||||
|
||||
app_logger.info("✅ Datenbank-Setup und Migrationen erfolgreich abgeschlossen")
|
||||
|
||||
except Exception as e:
|
||||
app_logger.error(f"❌ Fehler bei Datenbank-Setup: {str(e)}")
|
||||
raise e
|
Reference in New Issue
Block a user