📚 Reorganized documentation files and renamed for clarity
This commit is contained in:
Binary file not shown.
Binary file not shown.
@@ -705,4 +705,211 @@ def fix_admin_permissions():
|
||||
'success': False,
|
||||
'error': 'Fehler beim Korrigieren der Berechtigungen',
|
||||
'message': str(e)
|
||||
}), 500
|
||||
|
||||
@api_blueprint.route('/stats/charts/job-status', methods=['GET'])
|
||||
@login_required
|
||||
def get_job_status_chart():
|
||||
"""
|
||||
API-Endpunkt für Job-Status-Chart-Daten.
|
||||
|
||||
Liefert Daten für ein Doughnut/Pie-Chart der Job-Status-Verteilung.
|
||||
"""
|
||||
try:
|
||||
db_session = get_db_session()
|
||||
|
||||
# Job-Status-Verteilung ermitteln
|
||||
completed_jobs = db_session.query(Job).filter(Job.status == 'completed').count()
|
||||
running_jobs = db_session.query(Job).filter(Job.status == 'running').count()
|
||||
pending_jobs = db_session.query(Job).filter(Job.status == 'pending').count()
|
||||
failed_jobs = db_session.query(Job).filter(Job.status == 'failed').count()
|
||||
queued_jobs = db_session.query(Job).filter(Job.status == 'queued').count()
|
||||
|
||||
db_session.close()
|
||||
|
||||
# Chart.js-Format für Doughnut-Chart
|
||||
chart_data = {
|
||||
'labels': ['Abgeschlossen', 'Läuft', 'Wartend', 'Fehlgeschlagen', 'Warteschlange'],
|
||||
'datasets': [{
|
||||
'data': [completed_jobs, running_jobs, pending_jobs, failed_jobs, queued_jobs],
|
||||
'backgroundColor': [
|
||||
'#10b981', # Grün für abgeschlossen
|
||||
'#3b82f6', # Blau für läuft
|
||||
'#f59e0b', # Gelb für wartend
|
||||
'#ef4444', # Rot für fehlgeschlagen
|
||||
'#8b5cf6' # Lila für Warteschlange
|
||||
],
|
||||
'borderColor': [
|
||||
'#059669',
|
||||
'#2563eb',
|
||||
'#d97706',
|
||||
'#dc2626',
|
||||
'#7c3aed'
|
||||
],
|
||||
'borderWidth': 2
|
||||
}]
|
||||
}
|
||||
|
||||
api_logger.info(f"Job-Status-Chart-Daten abgerufen von Benutzer {current_user.username}")
|
||||
return jsonify(chart_data)
|
||||
|
||||
except Exception as e:
|
||||
api_logger.error(f"Fehler beim Abrufen der Job-Status-Chart-Daten: {str(e)}")
|
||||
return jsonify({
|
||||
'error': 'Fehler beim Laden der Job-Status-Chart-Daten',
|
||||
'message': str(e)
|
||||
}), 500
|
||||
|
||||
@api_blueprint.route('/stats/charts/printer-usage', methods=['GET'])
|
||||
@login_required
|
||||
def get_printer_usage_chart():
|
||||
"""
|
||||
API-Endpunkt für Drucker-Nutzung-Chart-Daten.
|
||||
|
||||
Liefert Daten für ein Bar-Chart der Drucker-Nutzung.
|
||||
"""
|
||||
try:
|
||||
db_session = get_db_session()
|
||||
|
||||
# Drucker mit Job-Anzahl abfragen
|
||||
from sqlalchemy import func
|
||||
printer_usage = db_session.query(
|
||||
Printer.name,
|
||||
func.count(Job.id).label('job_count')
|
||||
).outerjoin(Job).group_by(Printer.id, Printer.name).all()
|
||||
|
||||
db_session.close()
|
||||
|
||||
# Sortieren nach Job-Anzahl (höchste zuerst)
|
||||
printer_usage = sorted(printer_usage, key=lambda x: x.job_count, reverse=True)
|
||||
|
||||
# Daten für Chart.js formatieren
|
||||
chart_data = {
|
||||
'labels': [printer.name for printer in printer_usage],
|
||||
'datasets': [{
|
||||
'label': 'Anzahl Jobs',
|
||||
'data': [printer.job_count for printer in printer_usage],
|
||||
'backgroundColor': 'rgba(59, 130, 246, 0.8)',
|
||||
'borderColor': 'rgba(59, 130, 246, 1)',
|
||||
'borderWidth': 1
|
||||
}]
|
||||
}
|
||||
|
||||
api_logger.info(f"Drucker-Nutzung-Chart-Daten abgerufen von Benutzer {current_user.username}")
|
||||
return jsonify(chart_data)
|
||||
|
||||
except Exception as e:
|
||||
api_logger.error(f"Fehler beim Abrufen der Drucker-Nutzung-Chart-Daten: {str(e)}")
|
||||
return jsonify({
|
||||
'error': 'Fehler beim Laden der Drucker-Nutzung-Chart-Daten',
|
||||
'message': str(e)
|
||||
}), 500
|
||||
|
||||
@api_blueprint.route('/stats/charts/jobs-timeline', methods=['GET'])
|
||||
@login_required
|
||||
def get_jobs_timeline_chart():
|
||||
"""
|
||||
API-Endpunkt für Jobs-Timeline-Chart-Daten.
|
||||
|
||||
Liefert Daten für ein Line-Chart der Jobs über Zeit (letzte 30 Tage).
|
||||
"""
|
||||
try:
|
||||
db_session = get_db_session()
|
||||
|
||||
# Letzte 30 Tage
|
||||
from datetime import date, timedelta
|
||||
end_date = date.today()
|
||||
start_date = end_date - timedelta(days=29) # 30 Tage inkl. heute
|
||||
|
||||
# Jobs pro Tag ermitteln
|
||||
from sqlalchemy import func, Date, cast
|
||||
daily_jobs = db_session.query(
|
||||
cast(Job.created_at, Date).label('date'),
|
||||
func.count(Job.id).label('job_count')
|
||||
).filter(
|
||||
cast(Job.created_at, Date) >= start_date,
|
||||
cast(Job.created_at, Date) <= end_date
|
||||
).group_by(cast(Job.created_at, Date)).all()
|
||||
|
||||
db_session.close()
|
||||
|
||||
# Dictionary für schnellen Zugriff erstellen
|
||||
jobs_by_date = {str(day.date): day.job_count for day in daily_jobs}
|
||||
|
||||
# Alle Tage füllen (auch die ohne Jobs)
|
||||
labels = []
|
||||
data = []
|
||||
current_date = start_date
|
||||
|
||||
while current_date <= end_date:
|
||||
labels.append(current_date.strftime('%d.%m'))
|
||||
data.append(jobs_by_date.get(str(current_date), 0))
|
||||
current_date += timedelta(days=1)
|
||||
|
||||
# Chart.js-Format für Line-Chart
|
||||
chart_data = {
|
||||
'labels': labels,
|
||||
'datasets': [{
|
||||
'label': 'Jobs pro Tag',
|
||||
'data': data,
|
||||
'borderColor': 'rgba(16, 185, 129, 1)',
|
||||
'backgroundColor': 'rgba(16, 185, 129, 0.1)',
|
||||
'borderWidth': 2,
|
||||
'fill': True,
|
||||
'tension': 0.4
|
||||
}]
|
||||
}
|
||||
|
||||
api_logger.info(f"Jobs-Timeline-Chart-Daten abgerufen von Benutzer {current_user.username}")
|
||||
return jsonify(chart_data)
|
||||
|
||||
except Exception as e:
|
||||
api_logger.error(f"Fehler beim Abrufen der Jobs-Timeline-Chart-Daten: {str(e)}")
|
||||
return jsonify({
|
||||
'error': 'Fehler beim Laden der Jobs-Timeline-Chart-Daten',
|
||||
'message': str(e)
|
||||
}), 500
|
||||
|
||||
@api_blueprint.route('/stats/charts/user-activity', methods=['GET'])
|
||||
@login_required
|
||||
def get_user_activity_chart():
|
||||
"""
|
||||
API-Endpunkt für Benutzer-Aktivität-Chart-Daten.
|
||||
|
||||
Liefert Daten für ein horizontales Bar-Chart der Benutzeraktivität.
|
||||
"""
|
||||
try:
|
||||
db_session = get_db_session()
|
||||
|
||||
# Top 10 aktivste Benutzer (nach Job-Anzahl)
|
||||
from sqlalchemy import func
|
||||
user_activity = db_session.query(
|
||||
User.username,
|
||||
func.count(Job.id).label('job_count')
|
||||
).outerjoin(Job).group_by(User.id, User.username).order_by(
|
||||
func.count(Job.id).desc()
|
||||
).limit(10).all()
|
||||
|
||||
db_session.close()
|
||||
|
||||
# Daten für Chart.js formatieren (horizontales Bar-Chart)
|
||||
chart_data = {
|
||||
'labels': [user.username for user in user_activity],
|
||||
'datasets': [{
|
||||
'label': 'Anzahl Jobs',
|
||||
'data': [user.job_count for user in user_activity],
|
||||
'backgroundColor': 'rgba(139, 92, 246, 0.8)',
|
||||
'borderColor': 'rgba(139, 92, 246, 1)',
|
||||
'borderWidth': 1
|
||||
}]
|
||||
}
|
||||
|
||||
api_logger.info(f"Benutzer-Aktivität-Chart-Daten abgerufen von Benutzer {current_user.username}")
|
||||
return jsonify(chart_data)
|
||||
|
||||
except Exception as e:
|
||||
api_logger.error(f"Fehler beim Abrufen der Benutzer-Aktivität-Chart-Daten: {str(e)}")
|
||||
return jsonify({
|
||||
'error': 'Fehler beim Laden der Benutzer-Aktivität-Chart-Daten',
|
||||
'message': str(e)
|
||||
}), 500
|
@@ -916,44 +916,76 @@ def api_approve_request(request_id):
|
||||
@guest_blueprint.route('/api/requests/<int:request_id>/deny', methods=['POST'])
|
||||
@approver_required
|
||||
def api_deny_request(request_id):
|
||||
"""Gastanfrage ablehnen."""
|
||||
"""Gastanfrage ablehnen mit robuster Fehlerbehandlung."""
|
||||
try:
|
||||
data = request.get_json() or {}
|
||||
rejection_reason = data.get('reason', '')
|
||||
# Content-Type prüfen
|
||||
if not request.is_json and request.content_type != 'application/json':
|
||||
return jsonify({"error": "Content-Type muss application/json sein"}), 400
|
||||
|
||||
if not rejection_reason.strip():
|
||||
return jsonify({"error": "Ablehnungsgrund ist erforderlich"}), 400
|
||||
data = request.get_json()
|
||||
if not data:
|
||||
return jsonify({"error": "Keine JSON-Daten erhalten"}), 400
|
||||
|
||||
# Reason validation verbessert
|
||||
rejection_reason = data.get('reason', '').strip()
|
||||
if not rejection_reason or len(rejection_reason) < 3:
|
||||
return jsonify({
|
||||
"error": "Ablehnungsgrund ist erforderlich (mindestens 3 Zeichen)",
|
||||
"field": "reason"
|
||||
}), 400
|
||||
|
||||
with get_cached_session() as db_session:
|
||||
guest_request = db_session.query(GuestRequest).filter_by(id=request_id).first()
|
||||
if not guest_request:
|
||||
return jsonify({"error": "Anfrage nicht gefunden"}), 404
|
||||
|
||||
if guest_request.status != "pending":
|
||||
return jsonify({"error": "Anfrage wurde bereits bearbeitet"}), 400
|
||||
|
||||
# Anfrage ablehnen
|
||||
guest_request.status = "denied"
|
||||
guest_request.processed_by = current_user.id
|
||||
guest_request.processed_at = datetime.now()
|
||||
guest_request.rejection_reason = rejection_reason
|
||||
|
||||
db_session.commit()
|
||||
|
||||
logger.info(f"Gastanfrage {request_id} abgelehnt von Admin {current_user.id} ({current_user.username}): {rejection_reason}")
|
||||
|
||||
return jsonify({
|
||||
"success": True,
|
||||
"status": "denied",
|
||||
"rejected_by": current_user.username,
|
||||
"rejected_at": guest_request.processed_at.isoformat(),
|
||||
"reason": rejection_reason,
|
||||
"message": "Anfrage wurde abgelehnt"
|
||||
})
|
||||
|
||||
try:
|
||||
guest_request = db_session.query(GuestRequest).filter_by(id=request_id).first()
|
||||
if not guest_request:
|
||||
return jsonify({
|
||||
"error": "Anfrage nicht gefunden",
|
||||
"request_id": request_id
|
||||
}), 404
|
||||
|
||||
# Status-Validation verbessert
|
||||
if guest_request.status not in ["pending"]:
|
||||
return jsonify({
|
||||
"error": f"Anfrage kann im Status '{guest_request.status}' nicht abgelehnt werden",
|
||||
"current_status": guest_request.status,
|
||||
"allowed_statuses": ["pending"]
|
||||
}), 400
|
||||
|
||||
# Transaktion mit besserer Fehlerbehandlung
|
||||
guest_request.status = "denied"
|
||||
guest_request.processed_by = current_user.id
|
||||
guest_request.processed_at = datetime.now()
|
||||
guest_request.rejection_reason = rejection_reason
|
||||
|
||||
# Commit mit expliziter Fehlerbehandlung
|
||||
db_session.commit()
|
||||
|
||||
logger.info(f"Gastanfrage {request_id} abgelehnt von Admin {current_user.id} ({current_user.username}): {rejection_reason}")
|
||||
|
||||
return jsonify({
|
||||
"success": True,
|
||||
"status": "denied",
|
||||
"request_id": request_id,
|
||||
"rejected_by": current_user.username,
|
||||
"rejected_at": guest_request.processed_at.isoformat(),
|
||||
"reason": rejection_reason,
|
||||
"message": "Anfrage wurde erfolgreich abgelehnt"
|
||||
})
|
||||
|
||||
except Exception as db_error:
|
||||
db_session.rollback()
|
||||
logger.error(f"Datenbankfehler beim Ablehnen der Anfrage {request_id}: {str(db_error)}")
|
||||
return jsonify({
|
||||
"error": "Datenbankfehler beim Verarbeiten der Anfrage",
|
||||
"details": str(db_error) if current_app.debug else None
|
||||
}), 500
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Ablehnen der Gastanfrage: {str(e)}")
|
||||
return jsonify({"error": "Fehler beim Verarbeiten der Anfrage"}), 500
|
||||
logger.error(f"Unerwarteter Fehler beim Ablehnen der Gastanfrage {request_id}: {str(e)}")
|
||||
return jsonify({
|
||||
"error": "Unerwarteter Serverfehler",
|
||||
"details": str(e) if current_app.debug else None
|
||||
}), 500
|
||||
|
||||
@guest_blueprint.route('/api/admin/requests/<int:request_id>/otp', methods=['GET'])
|
||||
@approver_required
|
||||
|
Reference in New Issue
Block a user