- Removed `COMMON_ERRORS.md` file to streamline documentation. - Added `Flask-Limiter` for rate limiting and `redis` for session management in `requirements.txt`. - Expanded `ROADMAP.md` to include completed security features and planned enhancements for version 2.2. - Enhanced `setup_myp.sh` for ultra-secure kiosk installation, including system hardening and security configurations. - Updated `app.py` to integrate CSRF protection and improved logging setup. - Refactored user model to include username and active status for better user management. - Improved job scheduler with uptime tracking and task management features. - Updated various templates for a more cohesive user interface and experience.
452 lines
15 KiB
Python
452 lines
15 KiB
Python
from flask import Blueprint, jsonify, request, session, current_app
|
|
from datetime import datetime, timedelta
|
|
import logging
|
|
import uuid
|
|
import json
|
|
import os
|
|
|
|
from models import User, Job, Printer, SystemLog
|
|
from database.db_manager import DatabaseManager
|
|
from flask_login import login_required, current_user
|
|
|
|
api_bp = Blueprint('api', __name__, url_prefix='/api')
|
|
logger = logging.getLogger(__name__)
|
|
|
|
@api_bp.route('/jobs')
|
|
def get_jobs():
|
|
"""Get jobs with optional filtering"""
|
|
try:
|
|
db = DatabaseManager()
|
|
|
|
# Get query parameters
|
|
status = request.args.get('status')
|
|
limit = request.args.get('limit', type=int)
|
|
|
|
# Use a session for proper relationship handling
|
|
session = db.get_session()
|
|
|
|
try:
|
|
from sqlalchemy.orm import joinedload
|
|
|
|
# Get jobs based on filters with eager loading of relationships
|
|
if status == 'active':
|
|
jobs_query = session.query(Job).options(
|
|
joinedload(Job.user),
|
|
joinedload(Job.printer)
|
|
).filter(Job.status == 'running')
|
|
|
|
jobs = jobs_query.all() or []
|
|
|
|
# Also include pending jobs
|
|
pending_jobs_query = session.query(Job).options(
|
|
joinedload(Job.user),
|
|
joinedload(Job.printer)
|
|
).filter(Job.status == 'pending')
|
|
|
|
pending_jobs = pending_jobs_query.all() or []
|
|
jobs.extend(pending_jobs)
|
|
else:
|
|
jobs_query = session.query(Job).options(
|
|
joinedload(Job.user),
|
|
joinedload(Job.printer)
|
|
)
|
|
jobs = jobs_query.all() or []
|
|
|
|
# Apply limit
|
|
if limit:
|
|
jobs = jobs[:limit]
|
|
|
|
# Format jobs for API response - convert all data while session is open
|
|
formatted_jobs = []
|
|
for job in jobs:
|
|
if hasattr(job, '__dict__'):
|
|
# Access all relationship data here while the session is still open
|
|
printer_name = job.printer.name if job.printer else 'Kein Drucker'
|
|
user_name = job.user.name if job.user else 'Kein Benutzer'
|
|
|
|
job_data = {
|
|
'id': getattr(job, 'id', None),
|
|
'name': getattr(job, 'name', 'Unbenannter Auftrag'),
|
|
'status': getattr(job, 'status', 'unknown'),
|
|
'progress': getattr(job, 'progress', 0),
|
|
'printer_name': printer_name,
|
|
'user_name': user_name,
|
|
'created_at': getattr(job, 'created_at', datetime.now()).isoformat() if hasattr(job, 'created_at') else datetime.now().isoformat(),
|
|
'estimated_time': getattr(job, 'estimated_time', 0),
|
|
'print_time': getattr(job, 'print_time', 0)
|
|
}
|
|
formatted_jobs.append(job_data)
|
|
finally:
|
|
# Always close the session
|
|
session.close()
|
|
|
|
return jsonify(formatted_jobs)
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error getting jobs: {str(e)}")
|
|
return jsonify([])
|
|
|
|
@api_bp.route('/printers', methods=['GET'])
|
|
def get_printers():
|
|
"""Get all printers"""
|
|
try:
|
|
db = DatabaseManager()
|
|
session = db.get_session()
|
|
|
|
try:
|
|
printers = session.query(Printer).all() or []
|
|
|
|
# Format printers for API response
|
|
formatted_printers = []
|
|
for printer in printers:
|
|
printer_data = {
|
|
'id': printer.id,
|
|
'name': printer.name,
|
|
'model': printer.model,
|
|
'location': printer.location,
|
|
'mac_address': printer.mac_address,
|
|
'plug_ip': printer.plug_ip,
|
|
'status': printer.status,
|
|
'created_at': printer.created_at.isoformat() if printer.created_at else datetime.now().isoformat()
|
|
}
|
|
formatted_printers.append(printer_data)
|
|
|
|
return jsonify({'printers': formatted_printers})
|
|
finally:
|
|
session.close()
|
|
except Exception as e:
|
|
logger.error(f"Error getting printers: {str(e)}")
|
|
return jsonify({'printers': []})
|
|
|
|
@api_bp.route('/printers', methods=['POST'])
|
|
@login_required
|
|
def add_printer():
|
|
"""Add a new printer"""
|
|
# Überprüfe, ob der Benutzer Admin-Rechte hat
|
|
if not current_user.is_admin:
|
|
return jsonify({'error': 'Nur Administratoren können Drucker hinzufügen'}), 403
|
|
|
|
try:
|
|
# Hole die Daten aus dem Request
|
|
data = request.json
|
|
if not data:
|
|
return jsonify({'error': 'Keine Daten erhalten'}), 400
|
|
|
|
# Überprüfe, ob alle notwendigen Felder vorhanden sind
|
|
required_fields = ['name', 'model', 'location', 'mac_address', 'plug_ip']
|
|
for field in required_fields:
|
|
if field not in data:
|
|
return jsonify({'error': f'Feld {field} fehlt'}), 400
|
|
|
|
# Erstelle ein neues Printer-Objekt
|
|
new_printer = Printer(
|
|
name=data['name'],
|
|
model=data['model'],
|
|
location=data['location'],
|
|
mac_address=data['mac_address'],
|
|
plug_ip=data['plug_ip'],
|
|
plug_username='admin', # Default-Werte für Plug-Credentials
|
|
plug_password='admin', # In einer Produktionsumgebung sollten diese sicher gespeichert werden
|
|
status='available'
|
|
)
|
|
|
|
# Speichere den Drucker in der Datenbank
|
|
db = DatabaseManager()
|
|
session = db.get_session()
|
|
|
|
try:
|
|
session.add(new_printer)
|
|
session.commit()
|
|
|
|
# Gib die Drucker-Daten zurück
|
|
printer_data = {
|
|
'id': new_printer.id,
|
|
'name': new_printer.name,
|
|
'model': new_printer.model,
|
|
'location': new_printer.location,
|
|
'mac_address': new_printer.mac_address,
|
|
'plug_ip': new_printer.plug_ip,
|
|
'status': new_printer.status,
|
|
'created_at': new_printer.created_at.isoformat() if new_printer.created_at else datetime.now().isoformat()
|
|
}
|
|
|
|
return jsonify({'printer': printer_data, 'message': 'Drucker erfolgreich hinzugefügt'}), 201
|
|
except Exception as e:
|
|
session.rollback()
|
|
logger.error(f"Error adding printer: {str(e)}")
|
|
return jsonify({'error': f'Fehler beim Hinzufügen des Druckers: {str(e)}'}), 500
|
|
finally:
|
|
session.close()
|
|
except Exception as e:
|
|
logger.error(f"Error adding printer: {str(e)}")
|
|
return jsonify({'error': f'Fehler beim Hinzufügen des Druckers: {str(e)}'}), 500
|
|
|
|
@api_bp.route('/printers/<int:printer_id>', methods=['DELETE'])
|
|
@login_required
|
|
def delete_printer(printer_id):
|
|
"""Delete a printer by ID"""
|
|
# Überprüfe, ob der Benutzer Admin-Rechte hat
|
|
if not current_user.is_admin:
|
|
return jsonify({'error': 'Nur Administratoren können Drucker löschen'}), 403
|
|
|
|
try:
|
|
db = DatabaseManager()
|
|
session = db.get_session()
|
|
|
|
try:
|
|
# Finde den Drucker
|
|
printer = session.query(Printer).filter(Printer.id == printer_id).first()
|
|
if not printer:
|
|
return jsonify({'error': f'Drucker mit ID {printer_id} nicht gefunden'}), 404
|
|
|
|
# Lösche den Drucker
|
|
session.delete(printer)
|
|
session.commit()
|
|
|
|
return jsonify({'message': 'Drucker erfolgreich gelöscht'}), 200
|
|
except Exception as e:
|
|
session.rollback()
|
|
logger.error(f"Error deleting printer: {str(e)}")
|
|
return jsonify({'error': f'Fehler beim Löschen des Druckers: {str(e)}'}), 500
|
|
finally:
|
|
session.close()
|
|
except Exception as e:
|
|
logger.error(f"Error deleting printer: {str(e)}")
|
|
return jsonify({'error': f'Fehler beim Löschen des Druckers: {str(e)}'}), 500
|
|
|
|
# API-Route für Admin-Statistiken
|
|
@api_bp.route('/admin/stats', methods=['GET'])
|
|
@login_required
|
|
def get_admin_stats():
|
|
if not current_user.is_admin:
|
|
return jsonify({"error": "Unauthorized"}), 403
|
|
|
|
# Statistikdaten sammeln
|
|
total_users = User.query.count()
|
|
total_printers = Printer.query.count()
|
|
active_jobs = Job.query.filter_by(status='running').count()
|
|
|
|
# Erfolgsrate berechnen
|
|
total_completed_jobs = Job.query.filter_by(status='completed').count()
|
|
total_jobs = Job.query.filter(Job.status.in_(['completed', 'failed'])).count()
|
|
|
|
success_rate = "0%"
|
|
if total_jobs > 0:
|
|
success_rate = f"{int((total_completed_jobs / total_jobs) * 100)}%"
|
|
|
|
# Scheduler-Status (Beispiel)
|
|
scheduler_status = True # In einer echten Anwendung würde hier der tatsächliche Status geprüft
|
|
|
|
# Systeminformationen
|
|
system_stats = {
|
|
"cpu_usage": "25%",
|
|
"memory_usage": "40%",
|
|
"disk_usage": "30%",
|
|
"uptime": "3d 12h 45m"
|
|
}
|
|
|
|
# Drucker-Status
|
|
printer_status = {
|
|
"available": Printer.query.filter_by(status='available').count(),
|
|
"busy": Printer.query.filter_by(status='busy').count(),
|
|
"offline": Printer.query.filter_by(status='offline').count(),
|
|
"maintenance": Printer.query.filter_by(status='maintenance').count()
|
|
}
|
|
|
|
return jsonify({
|
|
"total_users": total_users,
|
|
"total_printers": total_printers,
|
|
"active_jobs": active_jobs,
|
|
"success_rate": success_rate,
|
|
"scheduler_status": scheduler_status,
|
|
"system_stats": system_stats,
|
|
"printer_status": printer_status,
|
|
"last_updated": datetime.now().isoformat()
|
|
})
|
|
|
|
# API-Route für Protokolle
|
|
@api_bp.route('/logs', methods=['GET'])
|
|
@login_required
|
|
def get_logs():
|
|
if not current_user.is_admin:
|
|
return jsonify({"error": "Unauthorized"}), 403
|
|
|
|
# Logs aus der Datenbank abrufen
|
|
logs = SystemLog.query.order_by(SystemLog.timestamp.desc()).limit(100).all()
|
|
|
|
log_data = []
|
|
for log in logs:
|
|
log_data.append({
|
|
"id": log.id,
|
|
"timestamp": log.timestamp.isoformat(),
|
|
"level": log.level,
|
|
"source": log.source,
|
|
"message": log.message
|
|
})
|
|
|
|
return jsonify({"logs": log_data})
|
|
|
|
# Scheduler-Status abrufen
|
|
@api_bp.route('/scheduler/status', methods=['GET'])
|
|
@login_required
|
|
def get_scheduler_status():
|
|
if not current_user.is_admin:
|
|
return jsonify({"error": "Unauthorized"}), 403
|
|
|
|
# In einer echten Anwendung würde hier der tatsächliche Status geprüft werden
|
|
scheduler_active = True
|
|
|
|
# Aktuelle Aufgaben (Beispiel)
|
|
tasks = [
|
|
{
|
|
"id": 1,
|
|
"name": "Druckaufträge verarbeiten",
|
|
"status": "running",
|
|
"last_run": datetime.now().isoformat(),
|
|
"next_run": datetime.now().isoformat()
|
|
},
|
|
{
|
|
"id": 2,
|
|
"name": "Status-Updates sammeln",
|
|
"status": "idle",
|
|
"last_run": datetime.now().isoformat(),
|
|
"next_run": datetime.now().isoformat()
|
|
}
|
|
]
|
|
|
|
return jsonify({
|
|
"active": scheduler_active,
|
|
"tasks": tasks,
|
|
"last_updated": datetime.now().isoformat()
|
|
})
|
|
|
|
# Scheduler starten
|
|
@api_bp.route('/scheduler/start', methods=['POST'])
|
|
@login_required
|
|
def start_scheduler():
|
|
if not current_user.is_admin:
|
|
return jsonify({"error": "Unauthorized"}), 403
|
|
|
|
# In einer echten Anwendung würde hier der Scheduler gestartet werden
|
|
|
|
return jsonify({
|
|
"success": True,
|
|
"message": "Scheduler started successfully",
|
|
"active": True
|
|
})
|
|
|
|
# Scheduler stoppen
|
|
@api_bp.route('/scheduler/stop', methods=['POST'])
|
|
@login_required
|
|
def stop_scheduler():
|
|
if not current_user.is_admin:
|
|
return jsonify({"error": "Unauthorized"}), 403
|
|
|
|
# In einer echten Anwendung würde hier der Scheduler gestoppt werden
|
|
|
|
return jsonify({
|
|
"success": True,
|
|
"message": "Scheduler stopped successfully",
|
|
"active": False
|
|
})
|
|
|
|
# Benutzer abrufen
|
|
@api_bp.route('/users', methods=['GET'])
|
|
@login_required
|
|
def get_users():
|
|
if not current_user.is_admin:
|
|
return jsonify({"error": "Unauthorized"}), 403
|
|
|
|
users = User.query.all()
|
|
|
|
result = []
|
|
for user in users:
|
|
result.append({
|
|
'id': user.id,
|
|
'name': user.name,
|
|
'email': user.email,
|
|
'is_admin': user.is_admin,
|
|
'is_active': user.is_active,
|
|
'created_at': user.created_at.isoformat()
|
|
})
|
|
|
|
return jsonify({"users": result})
|
|
|
|
# Neuen Benutzer hinzufügen
|
|
@api_bp.route('/users', methods=['POST'])
|
|
@login_required
|
|
def add_user():
|
|
if not current_user.is_admin:
|
|
return jsonify({"error": "Unauthorized"}), 403
|
|
|
|
data = request.json
|
|
|
|
if not data:
|
|
return jsonify({"error": "No data provided"}), 400
|
|
|
|
required_fields = ['name', 'email', 'password']
|
|
for field in required_fields:
|
|
if field not in data:
|
|
return jsonify({"error": f"Missing required field: {field}"}), 400
|
|
|
|
# Prüfen, ob E-Mail bereits existiert
|
|
existing_user = User.query.filter_by(email=data['email']).first()
|
|
if existing_user:
|
|
return jsonify({"error": "Email already exists"}), 400
|
|
|
|
# Neuen Benutzer erstellen
|
|
user = User(
|
|
name=data['name'],
|
|
email=data['email'],
|
|
is_admin=data.get('role') == 'admin',
|
|
is_active=data.get('status') == 'active'
|
|
)
|
|
user.set_password(data['password'])
|
|
|
|
try:
|
|
db = DatabaseManager()
|
|
session = db.get_session()
|
|
session.add(user)
|
|
session.commit()
|
|
|
|
return jsonify({
|
|
"success": True,
|
|
"message": "User added successfully",
|
|
"user": {
|
|
"id": user.id,
|
|
"name": user.name,
|
|
"email": user.email,
|
|
"is_admin": user.is_admin,
|
|
"is_active": user.is_active,
|
|
"created_at": user.created_at.isoformat()
|
|
}
|
|
}), 201
|
|
except Exception as e:
|
|
db.get_session().rollback()
|
|
return jsonify({"error": str(e)}), 500
|
|
|
|
# Benutzer löschen
|
|
@api_bp.route('/users/<int:user_id>', methods=['DELETE'])
|
|
@login_required
|
|
def delete_user(user_id):
|
|
if not current_user.is_admin:
|
|
return jsonify({"error": "Unauthorized"}), 403
|
|
|
|
# Benutzer kann sich nicht selbst löschen
|
|
if current_user.id == user_id:
|
|
return jsonify({"error": "Cannot delete yourself"}), 400
|
|
|
|
user = User.query.get(user_id)
|
|
|
|
if not user:
|
|
return jsonify({"error": "User not found"}), 404
|
|
|
|
try:
|
|
db = DatabaseManager()
|
|
session = db.get_session()
|
|
session.delete(user)
|
|
session.commit()
|
|
return jsonify({"success": True, "message": "User deleted successfully"})
|
|
except Exception as e:
|
|
session.rollback()
|
|
return jsonify({"error": str(e)}), 500 |