Files
RollCalculator/With Authentification/ADMIN_INTEGRATION.md
T

480 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 📊 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):**
```python
@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`**
```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`:
```python
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:**
```nginx
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:
```javascript
// 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)