- Neue Routen: /login, /stats, /logout - Session-basierte Authentifizierung - Umfassende Statistiken: Seitenaufrufe, Suchvorgänge, Quellen - Environment-Variablen: ADMIN_PASSWORD, FLASK_SECRET_KEY - Docker-Integration mit docker-compose.yml - Responsive UI mit Charts und Aktivitätsprotokoll
245 lines
9.6 KiB
HTML
245 lines
9.6 KiB
HTML
<!doctype html>
|
||
<html lang="de">
|
||
<head>
|
||
<meta charset="utf-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||
<title>Statistiken - Wordle‑Cheater</title>
|
||
<meta name="robots" content="noindex,nofollow" />
|
||
<style>
|
||
:root { --bg:#ffffff; --text:#111827; --muted:#6b7280; --border:#e5e7eb; --button-bg:#111827; --button-text:#ffffff; --accent:#3b82f6; --success:#10b981; --warning:#f59e0b; }
|
||
[data-theme="dark"] { --bg:#0b1220; --text:#e5e7eb; --muted:#9ca3af; --border:#334155; --button-bg:#e5e7eb; --button-text:#111827; --accent:#60a5fa; --success:#34d399; --warning:#fbbf24; }
|
||
|
||
html, body { background: var(--bg); color: var(--text); margin: 0; padding: 0; }
|
||
body { font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; line-height: 1.6; }
|
||
|
||
.container { max-width: 1200px; margin: 0 auto; padding: 2rem; }
|
||
|
||
.header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 2rem;
|
||
padding-bottom: 1rem;
|
||
border-bottom: 1px solid var(--border);
|
||
}
|
||
|
||
.header h1 { margin: 0; color: var(--text); }
|
||
.header .actions { display: flex; gap: 1rem; }
|
||
|
||
.btn {
|
||
padding: 0.5rem 1rem;
|
||
background: var(--button-bg);
|
||
color: var(--button-text);
|
||
border: none;
|
||
border-radius: 0.375rem;
|
||
text-decoration: none;
|
||
font-size: 0.9rem;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.btn:hover { opacity: 0.9; }
|
||
.btn.secondary { background: var(--muted); }
|
||
|
||
.stats-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||
gap: 1.5rem;
|
||
margin-bottom: 2rem;
|
||
}
|
||
|
||
.stat-card {
|
||
background: var(--bg);
|
||
border: 1px solid var(--border);
|
||
border-radius: 0.5rem;
|
||
padding: 1.5rem;
|
||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||
}
|
||
|
||
.stat-card h3 { margin: 0 0 1rem 0; color: var(--text); font-size: 1.1rem; }
|
||
.stat-number { font-size: 2.5rem; font-weight: bold; color: var(--accent); margin-bottom: 0.5rem; }
|
||
.stat-description { color: var(--muted); font-size: 0.9rem; }
|
||
|
||
.chart-container {
|
||
background: var(--bg);
|
||
border: 1px solid var(--border);
|
||
border-radius: 0.5rem;
|
||
padding: 1.5rem;
|
||
margin-bottom: 1.5rem;
|
||
}
|
||
|
||
.chart-container h3 { margin: 0 0 1rem 0; color: var(--text); }
|
||
|
||
.bar-chart { display: flex; align-items: end; gap: 0.5rem; height: 200px; }
|
||
.bar {
|
||
background: var(--accent);
|
||
min-width: 40px;
|
||
border-radius: 0.25rem 0.25rem 0 0;
|
||
position: relative;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
}
|
||
|
||
.bar-label {
|
||
position: absolute;
|
||
bottom: -25px;
|
||
font-size: 0.8rem;
|
||
color: var(--muted);
|
||
transform: rotate(-45deg);
|
||
transform-origin: top left;
|
||
}
|
||
|
||
.bar-value {
|
||
position: absolute;
|
||
top: -25px;
|
||
font-size: 0.8rem;
|
||
color: var(--text);
|
||
font-weight: 600;
|
||
}
|
||
|
||
.recent-activity {
|
||
background: var(--bg);
|
||
border: 1px solid var(--border);
|
||
border-radius: 0.5rem;
|
||
padding: 1.5rem;
|
||
}
|
||
|
||
.recent-activity h3 { margin: 0 0 1rem 0; color: var(--text); }
|
||
.activity-item {
|
||
padding: 0.5rem 0;
|
||
border-bottom: 1px solid var(--border);
|
||
font-family: monospace;
|
||
font-size: 0.85rem;
|
||
}
|
||
.activity-item:last-child { border-bottom: none; }
|
||
|
||
.activity-timestamp { color: var(--accent); font-weight: 600; }
|
||
|
||
.search-patterns {
|
||
background: var(--bg);
|
||
border: 1px solid var(--border);
|
||
border-radius: 0.5rem;
|
||
padding: 1.5rem;
|
||
}
|
||
|
||
.search-patterns h3 { margin: 0 0 1rem 0; color: var(--text); }
|
||
.pattern-item {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
padding: 0.5rem 0;
|
||
border-bottom: 1px solid var(--border);
|
||
}
|
||
.pattern-item:last-child { border-bottom: none; }
|
||
.pattern-text { font-family: monospace; color: var(--text); }
|
||
.pattern-count { color: var(--accent); font-weight: 600; }
|
||
|
||
.no-data {
|
||
text-align: center;
|
||
color: var(--muted);
|
||
padding: 2rem;
|
||
font-style: italic;
|
||
}
|
||
|
||
@media (max-width: 768px) {
|
||
.container { padding: 1rem; }
|
||
.header { flex-direction: column; gap: 1rem; align-items: stretch; }
|
||
.stats-grid { grid-template-columns: 1fr; }
|
||
.bar-chart { height: 150px; }
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="container">
|
||
<div class="header">
|
||
<h1>📊 Statistik-Dashboard</h1>
|
||
<div class="actions">
|
||
<a href="{{ url_for('index') }}" class="btn secondary">← Hauptseite</a>
|
||
<a href="{{ url_for('logout') }}" class="btn">Abmelden</a>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="stats-grid">
|
||
<div class="stat-card">
|
||
<h3>📈 Gesamte Seitenaufrufe</h3>
|
||
<div class="stat-number">{{ stats.total_page_views }}</div>
|
||
<div class="stat-description">Alle protokollierten Seitenaufrufe</div>
|
||
</div>
|
||
|
||
<div class="stat-card">
|
||
<h3>🔍 Gesamte Suchvorgänge</h3>
|
||
<div class="stat-number">{{ stats.total_searches }}</div>
|
||
<div class="stat-description">Alle durchgeführten Wortsuche</div>
|
||
</div>
|
||
|
||
<div class="stat-card">
|
||
<h3>📱 Seitenaufrufe pro Seite</h3>
|
||
{% if stats.page_views_by_page %}
|
||
{% for page, count in stats.page_views_by_page.items() %}
|
||
<div style="margin-bottom: 0.5rem;">
|
||
<strong>{{ page }}:</strong> {{ count }}
|
||
</div>
|
||
{% endfor %}
|
||
{% else %}
|
||
<div class="no-data">Keine Daten verfügbar</div>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
|
||
<div class="chart-container">
|
||
<h3>🔍 Suchvorgänge nach Quellen</h3>
|
||
{% if stats.searches_by_source and (stats.searches_by_source.OT > 0 or stats.searches_by_source.WF > 0 or stats.searches_by_source.Both > 0) %}
|
||
<div class="bar-chart">
|
||
{% set max_count = [stats.searches_by_source.OT, stats.searches_by_source.WF, stats.searches_by_source.Both] | max %}
|
||
{% if stats.searches_by_source.OT > 0 %}
|
||
<div class="bar" style="height: {{ (stats.searches_by_source.OT / max_count * 150) + 50 }}px;">
|
||
<div class="bar-value">{{ stats.searches_by_source.OT }}</div>
|
||
<div class="bar-label">OpenThesaurus</div>
|
||
</div>
|
||
{% endif %}
|
||
{% if stats.searches_by_source.WF > 0 %}
|
||
<div class="bar" style="height: {{ (stats.searches_by_source.WF / max_count * 150) + 50 }}px;">
|
||
<div class="bar-value">{{ stats.searches_by_source.WF }}</div>
|
||
<div class="bar-label">Wordfreq</div>
|
||
</div>
|
||
{% endif %}
|
||
{% if stats.searches_by_source.Both > 0 %}
|
||
<div class="bar" style="height: {{ (stats.searches_by_source.Both / max_count * 150) + 50 }}px;">
|
||
<div class="bar-value">{{ stats.searches_by_source.Both }}</div>
|
||
<div class="bar-label">Beide</div>
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
{% else %}
|
||
<div class="no-data">Keine Suchdaten verfügbar</div>
|
||
{% endif %}
|
||
</div>
|
||
|
||
{% if stats.top_search_patterns %}
|
||
<div class="search-patterns">
|
||
<h3>🎯 Häufigste Suchmuster (Positionen)</h3>
|
||
{% for pattern, count in stats.top_search_patterns.items() | sort(attribute='1', reverse=true) %}
|
||
<div class="pattern-item">
|
||
<span class="pattern-text">{{ pattern or '(leer)' }}</span>
|
||
<span class="pattern-count">{{ count }}</span>
|
||
</div>
|
||
{% endfor %}
|
||
</div>
|
||
{% endif %}
|
||
|
||
<div class="recent-activity">
|
||
<h3>⏰ Letzte Aktivitäten</h3>
|
||
{% if stats.recent_activity %}
|
||
{% for activity in stats.recent_activity %}
|
||
<div class="activity-item">
|
||
<span class="activity-timestamp">{{ activity.timestamp }}</span>
|
||
<span>{{ activity.line.split(' - ', 2)[2] if ' - ' in activity.line else activity.line }}</span>
|
||
</div>
|
||
{% endfor %}
|
||
{% else %}
|
||
<div class="no-data">Keine Aktivitäten verfügbar</div>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
</body>
|
||
</html>
|