Files
wordle-cheater/templates/stats.html
elpatron d730e6b266 Admin: Passwortgeschütztes Statistik-Dashboard implementiert
- 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
2025-08-20 09:11:53 +02:00

245 lines
9.6 KiB
HTML
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.

<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Statistiken - WordleCheater</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>