Files
RollCalculator/With Authentification/ADMIN_INTEGRATION.md
T

12 KiB
Raw Blame History

📊 Admin Dashboard Integration – 4 Optionen

Übersicht

Hier sind die Best Practices für die Integration eines Admin-Panels in die Flask-App:


Option 1: Embedded Admin-Route (Empfohlen für Beta)

Am schnellsten & sichersten für Beta-Phase

Wie es funktioniert:

  • Admin-HTML wird direkt als Template serviert
  • Nur für Admin-Benutzer zugänglich (/admin)
  • Integriertes Logging-Dashboard
  • Keine separate App nötig

Implementierung:

In app.py (nach den Error Handlers hinzufügen):

@app.route("/admin", methods=["GET"])
@auth.login_required
def admin_dashboard():
    """Admin Dashboard - nur für Admins"""
    user = auth.current_user()
    if user not in ADMIN_USERS:
        log_access(user, "/admin", "GET", 403)
        return jsonify({"error": "Unauthorized"}), 403
    
    log_access(user, "/admin", "GET")
    return render_template("admin.html", username=user)

@app.route("/api/admin/logs", methods=["GET"])
@auth.login_required
def get_admin_logs():
    """Logs als JSON für Dashboard"""
    user = auth.current_user()
    if user not in ADMIN_USERS:
        return jsonify({"error": "Unauthorized"}), 403
    
    logs = []
    if os.path.exists(LOG_FILE):
        with open(LOG_FILE, "r") as f:
            logs = json.load(f)
    
    # Optional: Letzte 100 Logs
    return jsonify({"logs": logs[-100:]}), 200

@app.route("/api/admin/stats", methods=["GET"])
@auth.login_required
def get_admin_stats():
    """Statistiken für Dashboard"""
    user = auth.current_user()
    if user not in ADMIN_USERS:
        return jsonify({"error": "Unauthorized"}), 403
    
    logs = []
    if os.path.exists(LOG_FILE):
        with open(LOG_FILE, "r") as f:
            logs = json.load(f)
    
    # Statistiken berechnen
    total_requests = len(logs)
    unique_users = len(set(log["username"] for log in logs))
    failed_logins = len([l for l in logs if l["status"] == 401])
    
    return jsonify({
        "total_requests": total_requests,
        "unique_users": unique_users,
        "failed_logins": failed_logins,
        "last_update": datetime.now().isoformat()
    }), 200

Neue Template-Datei: templates/admin.html

<!DOCTYPE html>
<html lang="de">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Naue Admin Panel</title>
  <style>
    * { margin: 0; padding: 0; box-sizing: border-box; }
    body {
      font-family: 'Segoe UI', Arial, sans-serif;
      background: #f4f6f9;
      color: #333;
    }
    
    .admin-header {
      background: linear-gradient(135deg, #003366 0%, #0066cc 100%);
      color: white;
      padding: 20px 32px;
      box-shadow: 0 2px 8px rgba(0,0,0,0.2);
      display: flex;
      justify-content: space-between;
      align-items: center;
    }
    
    .admin-header h1 { font-size: 24px; }
    .admin-header .user-info {
      font-size: 12px;
      opacity: 0.9;
    }
    
    .admin-container {
      max-width: 1200px;
      margin: 30px auto;
      padding: 0 20px 60px;
    }
    
    .stats-grid {
      display: grid;
      grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
      gap: 20px;
      margin-bottom: 30px;
    }
    
    .stat-card {
      background: white;
      border-radius: 10px;
      padding: 20px;
      box-shadow: 0 2px 8px rgba(0,0,0,0.07);
      border-left: 4px solid #0066cc;
    }
    
    .stat-card .label {
      font-size: 12px;
      color: #666;
      text-transform: uppercase;
      margin-bottom: 8px;
    }
    
    .stat-card .value {
      font-size: 32px;
      font-weight: 700;
      color: #0066cc;
    }
    
    .logs-section {
      background: white;
      border-radius: 10px;
      padding: 20px;
      box-shadow: 0 2px 8px rgba(0,0,0,0.07);
    }
    
    .logs-section h2 {
      font-size: 18px;
      margin-bottom: 20px;
      color: #003366;
    }
    
    .logs-table {
      width: 100%;
      border-collapse: collapse;
      font-size: 13px;
    }
    
    .logs-table thead {
      background: #f0f4fa;
      border-bottom: 2px solid #0066cc;
    }
    
    .logs-table th {
      padding: 12px;
      text-align: left;
      font-weight: 600;
      color: #333;
    }
    
    .logs-table td {
      padding: 10px 12px;
      border-bottom: 1px solid #e8ecf0;
    }
    
    .logs-table tbody tr:hover {
      background: #f8f9fa;
    }
    
    .status-200 { color: #28a745; font-weight: 600; }
    .status-401 { color: #dc3545; font-weight: 600; }
    .status-403 { color: #f0b429; font-weight: 600; }
    
    .btn-refresh {
      background: #0066cc;
      color: white;
      border: none;
      padding: 10px 20px;
      border-radius: 5px;
      cursor: pointer;
      font-weight: 600;
      margin-bottom: 20px;
    }
    
    .btn-refresh:hover { background: #0052a3; }
    
    .loading {
      text-align: center;
      padding: 40px;
      color: #666;
    }
    
    .footer {
      text-align: center;
      font-size: 11px;
      color: #aaa;
      margin-top: 40px;
      padding-top: 20px;
      border-top: 1px solid #e8ecf0;
    }
  </style>
</head>
<body>

<div class="admin-header">
  <div>
    <h1>🔐 Admin Panel</h1>
    <div class="user-info">Roll Calculator – Naue</div>
  </div>
  <div class="user-info">
    Angemeldet als: <strong>{{ username }}</strong>
  </div>
</div>

<div class="admin-container">
  
  <!-- Statistics -->
  <div class="stats-grid" id="statsGrid">
    <div class="loading">Statistiken werden geladen...</div>
  </div>
  
  <!-- Logs Section -->
  <div class="logs-section">
    <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
      <h2>📋 Access Logs</h2>
      <button class="btn-refresh" onclick="refreshData()">🔄 Aktualisieren</button>
    </div>
    
    <table class="logs-table" id="logsTable">
      <thead>
        <tr>
          <th>Zeitstempel</th>
          <th>Benutzer</th>
          <th>Endpoint</th>
          <th>Methode</th>
          <th>Status</th>
        </tr>
      </thead>
      <tbody id="logsList">
        <tr>
          <td colspan="5" class="loading">Logs werden geladen...</td>
        </tr>
      </tbody>
    </table>
  </div>
  
  <div class="footer">
    Naue GmbH & Co. KG · Roll Calculator Admin Panel
  </div>
  
</div>

<script>
async function refreshData() {
  try {
    // Fetch Statistics
    const statsRes = await fetch('/api/admin/stats');
    const stats = await statsRes.json();
    
    // Fetch Logs
    const logsRes = await fetch('/api/admin/logs');
    const logsData = await logsRes.json();
    
    // Display Statistics
    const statsHtml = `
      <div class="stat-card">
        <div class="label">Gesamte Anfragen</div>
        <div class="value">${stats.total_requests}</div>
      </div>
      <div class="stat-card">
        <div class="label">Eindeutige Benutzer</div>
        <div class="value">${stats.unique_users}</div>
      </div>
      <div class="stat-card">
        <div class="label">Fehlgeschlagene Logins</div>
        <div class="value" style="color: #dc3545;">${stats.failed_logins}</div>
      </div>
    `;
    document.getElementById('statsGrid').innerHTML = statsHtml;
    
    // Display Logs
    const logsHtml = logsData.logs.reverse().map(log => {
      const time = new Date(log.timestamp).toLocaleString('de-DE');
      const statusClass = `status-${log.status}`;
      return `
        <tr>
          <td>${time}</td>
          <td>${log.username}</td>
          <td>${log.endpoint}</td>
          <td>${log.method}</td>
          <td class="${statusClass}">${log.status}</td>
        </tr>
      `;
    }).join('');
    
    document.getElementById('logsList').innerHTML = logsHtml || 
      '<tr><td colspan="5" style="text-align: center; padding: 20px;">Keine Logs verfügbar</td></tr>';
    
  } catch (error) {
    console.error('Fehler beim Laden der Daten:', error);
    document.getElementById('logsList').innerHTML = 
      '<tr><td colspan="5" style="color: #dc3545;">Fehler beim Laden der Daten</td></tr>';
  }
}

// Initial load
document.addEventListener('DOMContentLoaded', refreshData);

// Auto-refresh alle 30 Sekunden
setInterval(refreshData, 30000);
</script>

</body>
</html>

Zugriff:

  • URL: http://localhost:5000/admin
  • Nur für Admin-Benutzer (403 Unauthorized für andere)
  • Automatische Log-Anzeige

Option 2: Separate Admin-App (für später)

Wenn Admin-Features wachsen:

Erstelle eine separate admin_app.py:

from flask import Flask, render_template
from flask_httpauth import HTTPBasicAuth

admin = Flask(__name__, template_folder='admin_templates')
auth_admin = HTTPBasicAuth()

ADMIN_USERS = {"admin": "admin_secure_pwd_2026"}

@auth_admin.verify_password
def verify(username, password):
    if username in ADMIN_USERS and ADMIN_USERS[username] == password:
        return username
    return None

@admin.route("/", methods=["GET"])
@auth_admin.login_required
def dashboard():
    return render_template("admin.html")

if __name__ == "__main__":
    admin.run(port=5001, debug=False)  # Anderer Port

Dann starten mit: python admin_app.py


Option 3: Reverse Proxy Setup (Production)

Mit Nginx als Reverse Proxy:

server {
    listen 80;
    server_name rollcalculator.naue.de;

    # Main App (Port 5000)
    location / {
        proxy_pass http://localhost:5000;
        proxy_set_header Host $host;
        proxy_set_header Authorization $http_authorization;
    }

    # Admin (gleiche Auth, aber separates Template)
    location /admin {
        proxy_pass http://localhost:5000/admin;
        proxy_set_header Host $host;
        proxy_set_header Authorization $http_authorization;
    }
}

Option 4: Vue.js / React Admin UI (für erweiterte Features)

Falls du später komplexere Admin-Features brauchst:

// static/js/admin-dashboard.js
class AdminDashboard {
  constructor() {
    this.stats = null;
    this.logs = [];
    this.init();
  }
  
  async init() {
    await this.loadStats();
    await this.loadLogs();
    this.setupAutoRefresh();
  }
  
  async loadStats() {
    const res = await fetch('/api/admin/stats');
    this.stats = await res.json();
    this.renderStats();
  }
  
  async loadLogs() {
    const res = await fetch('/api/admin/logs');
    this.logs = (await res.json()).logs;
    this.renderLogs();
  }
  
  renderStats() {
    // Render logic...
  }
  
  renderLogs() {
    // Render logic...
  }
  
  setupAutoRefresh() {
    setInterval(() => this.loadStats(), 30000);
    setInterval(() => this.loadLogs(), 30000);
  }
}

// Starten
new AdminDashboard();

🎯 Empfehlungen

Phase Option Vorteile Nachteile
Beta Option 1 (Embedded) ✅ Schnell, ✅ Sicher, ✅ Integriert ⚠️ Einfaches UI
Production Beta Option 1 + Option 3 ✅ Mit HTTPS, ✅ Skalierbar ⚠️ Nginx-Setup
Später erweitern Option 4 (Vue/React) ✅ Modern, ✅ Interactive ⚠️ Komplexer
Separate Team Option 2 (Separate App) ✅ Unabhängig, ✅ Unterschiedliche Tech Stack ⚠️ 2 Apps

⚡ Quick Start: Option 1 implementieren

  1. Kopiere den Code oben in app.py (die neuen Routes)
  2. Erstelle templates/admin.html mit dem HTML oben
  3. Starte Flask neu: python app.py
  4. Öffne: http://localhost:5000/admin
  5. Login: admin / admin_secure_pwd_2026

Nächste Schritte

  • admin.html als Template in templates/ speichern
  • Routes in app.py hinzufügen
  • Tests mit Admin-User durchführen
  • Logs überprüfen
  • Optional: Charts/Graphs hinzufügen (Chart.js)