Spaces:
Running
on
CPU Upgrade
Running
on
CPU Upgrade
| {% 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"> | |
| <!-- Session Duration Statistics --> | |
| <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> | |
| <!-- Cache Hit Statistics --> | |
| <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> | |
| <!-- Top IP Addresses --> | |
| <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> | |
| <!-- Browser Statistics --> | |
| <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> | |
| <!-- Recent Votes with Analytics --> | |
| <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 %} |