| {% extends "admin/base.html" %} |
|
|
| {% block extra_head %} |
| {{ super() }} |
| <style> |
| .admin-grid { |
| display: grid; |
| grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); |
| gap: 24px; |
| margin-bottom: 24px; |
| } |
| |
| .stats-grid { |
| display: grid; |
| grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); |
| gap: 16px; |
| } |
| |
| .stat-item { |
| text-align: center; |
| padding: 12px; |
| background-color: var(--secondary-color); |
| border-radius: var(--radius); |
| } |
| |
| .stat-value { |
| font-size: 24px; |
| font-weight: 600; |
| color: var(--primary-color); |
| margin-bottom: 4px; |
| } |
| |
| .stat-label { |
| font-size: 12px; |
| color: #666; |
| text-transform: uppercase; |
| letter-spacing: 0.5px; |
| } |
| |
| .badge-success { |
| background-color: #10b981; |
| color: white; |
| } |
| |
| .badge-warning { |
| background-color: #f59e0b; |
| color: white; |
| } |
| |
| @media (prefers-color-scheme: dark) { |
| .stat-item { |
| background-color: rgba(255, 255, 255, 0.05); |
| } |
| |
| .stat-label { |
| color: #999; |
| } |
| } |
| </style> |
| {% endblock %} |
|
|
| {% block admin_content %} |
| <div class="admin-header"> |
| <div class="admin-title">Analytics</div> |
| </div> |
|
|
| <div class="admin-grid"> |
| |
| <div class="admin-card"> |
| <div class="admin-card-header"> |
| <div class="admin-card-title">Session Duration</div> |
| </div> |
| <div class="admin-card-content"> |
| <div class="stats-grid"> |
| <div class="stat-item"> |
| <div class="stat-value">{{ analytics_stats.duration.avg }}s</div> |
| <div class="stat-label">Average Duration</div> |
| </div> |
| <div class="stat-item"> |
| <div class="stat-value">{{ analytics_stats.duration.min }}s</div> |
| <div class="stat-label">Minimum Duration</div> |
| </div> |
| <div class="stat-item"> |
| <div class="stat-value">{{ analytics_stats.duration.max }}s</div> |
| <div class="stat-label">Maximum Duration</div> |
| </div> |
| <div class="stat-item"> |
| <div class="stat-value">{{ analytics_stats.duration.total }}</div> |
| <div class="stat-label">Total Sessions</div> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="admin-card"> |
| <div class="admin-card-header"> |
| <div class="admin-card-title">Cache Performance</div> |
| </div> |
| <div class="admin-card-content"> |
| <div class="stats-grid"> |
| <div class="stat-item"> |
| <div class="stat-value">{{ analytics_stats.cache.hits }}</div> |
| <div class="stat-label">Cache Hits</div> |
| </div> |
| <div class="stat-item"> |
| <div class="stat-value">{{ analytics_stats.cache.misses }}</div> |
| <div class="stat-label">Cache Misses</div> |
| </div> |
| <div class="stat-item"> |
| <div class="stat-value"> |
| {% if analytics_stats.cache.total > 0 %} |
| {{ "%.1f"|format((analytics_stats.cache.hits / analytics_stats.cache.total) * 100) }}% |
| {% else %} |
| 0% |
| {% endif %} |
| </div> |
| <div class="stat-label">Hit Rate</div> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="admin-card"> |
| <div class="admin-card-header"> |
| <div class="admin-card-title">Top IP Address Regions (Anonymized)</div> |
| </div> |
| <div class="table-responsive"> |
| <table class="admin-table"> |
| <thead> |
| <tr> |
| <th>IP Range</th> |
| <th>Vote Count</th> |
| </tr> |
| </thead> |
| <tbody> |
| {% for ip_stat in analytics_stats.top_ips %} |
| <tr> |
| <td>{{ ip_stat.ip }}</td> |
| <td>{{ ip_stat.count }}</td> |
| </tr> |
| {% endfor %} |
| </tbody> |
| </table> |
| </div> |
| </div> |
|
|
| |
| <div class="admin-card"> |
| <div class="admin-card-header"> |
| <div class="admin-card-title">Browser/Device Statistics</div> |
| </div> |
| <div class="table-responsive"> |
| <table class="admin-table"> |
| <thead> |
| <tr> |
| <th>Browser/Device</th> |
| <th>Vote Count</th> |
| </tr> |
| </thead> |
| <tbody> |
| {% for browser_stat in analytics_stats.browsers %} |
| <tr> |
| <td>{{ browser_stat.browser }}</td> |
| <td>{{ browser_stat.count }}</td> |
| </tr> |
| {% endfor %} |
| </tbody> |
| </table> |
| </div> |
| </div> |
|
|
| |
| <div class="admin-card"> |
| <div class="admin-card-header"> |
| <div class="admin-card-title">Recent Votes with Analytics Data</div> |
| </div> |
| <div class="table-responsive"> |
| <table class="admin-table"> |
| <thead> |
| <tr> |
| <th>ID</th> |
| <th>Date</th> |
| <th>User</th> |
| <th>Type</th> |
| <th>Duration (s)</th> |
| <th>IP Range</th> |
| <th>Cache Hit</th> |
| <th>Chosen Model</th> |
| <th>Rejected Model</th> |
| </tr> |
| </thead> |
| <tbody> |
| {% for vote in analytics_stats.recent_votes %} |
| <tr> |
| <td>{{ vote.id }}</td> |
| <td>{{ vote.vote_date.strftime('%Y-%m-%d %H:%M') }}</td> |
| <td>{{ vote.username or 'Anonymous' }}</td> |
| <td>{{ vote.model_type }}</td> |
| <td>{{ vote.duration }}</td> |
| <td>{{ vote.ip }}</td> |
| <td> |
| {% if vote.cache_hit %} |
| <span class="badge badge-success">Yes</span> |
| {% else %} |
| <span class="badge badge-warning">No</span> |
| {% endif %} |
| </td> |
| <td>{{ vote.chosen_model }}</td> |
| <td>{{ vote.rejected_model }}</td> |
| </tr> |
| {% endfor %} |
| </tbody> |
| </table> |
| </div> |
| </div> |
|
|
| {% endblock %} |