Spaces:
Running
on
CPU Upgrade
Running
on
CPU Upgrade
| {% extends "base.html" %} | |
| {% block title %}Admin Panel - TTS Arena{% endblock %} | |
| {% block extra_head %} | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.4/Chart.min.css"> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.4/Chart.bundle.min.js"></script> | |
| <style> | |
| .admin-container { | |
| width: 100%; | |
| } | |
| /* Horizontal navigation tabs */ | |
| .admin-nav { | |
| display: flex; | |
| overflow-x: auto; | |
| white-space: nowrap; | |
| margin-bottom: 24px; | |
| padding-bottom: 8px; | |
| border-bottom: 1px solid var(--border-color); | |
| -ms-overflow-style: none; /* Hide scrollbar IE and Edge */ | |
| scrollbar-width: none; /* Hide scrollbar Firefox */ | |
| } | |
| /* Hide scrollbar for Chrome, Safari and Opera */ | |
| .admin-nav::-webkit-scrollbar { | |
| display: none; | |
| } | |
| .admin-nav-item { | |
| display: flex; | |
| align-items: center; | |
| padding: 10px 16px; | |
| margin-right: 8px; | |
| border-radius: var(--radius); | |
| cursor: pointer; | |
| transition: all 0.2s; | |
| color: var(--text-color); | |
| text-decoration: none; | |
| font-size: 14px; | |
| position: relative; | |
| } | |
| .admin-nav-item.active { | |
| color: var(--primary-color); | |
| font-weight: 500; | |
| } | |
| .admin-nav-item.active::after { | |
| content: ''; | |
| position: absolute; | |
| bottom: -9px; | |
| left: 0; | |
| width: 100%; | |
| height: 3px; | |
| background-color: var(--primary-color); | |
| border-radius: 3px 3px 0 0; | |
| } | |
| .admin-nav-item:hover:not(.active) { | |
| background-color: rgba(0, 0, 0, 0.05); | |
| } | |
| .admin-nav-item svg { | |
| margin-right: 8px; | |
| width: 16px; | |
| height: 16px; | |
| } | |
| .admin-content { | |
| width: 100%; | |
| padding: 20px; | |
| } | |
| .admin-header { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| margin-bottom: 24px; | |
| } | |
| .admin-title { | |
| font-size: 24px; | |
| font-weight: 600; | |
| color: var(--primary-color); | |
| } | |
| .admin-card { | |
| background-color: white; | |
| border-radius: var(--radius); | |
| box-shadow: var(--shadow); | |
| padding: 20px; | |
| margin-bottom: 24px; | |
| overflow: auto; /* Add horizontal scrolling for content */ | |
| } | |
| .admin-card-header { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| margin-bottom: 16px; | |
| } | |
| .admin-card-title { | |
| font-size: 18px; | |
| font-weight: 600; | |
| color: var(--text-color); | |
| } | |
| .admin-stats { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); | |
| gap: 16px; | |
| margin-bottom: 24px; | |
| } | |
| .stat-card { | |
| background-color: white; | |
| border-radius: var(--radius); | |
| box-shadow: var(--shadow); | |
| padding: 16px; | |
| text-align: center; | |
| } | |
| .stat-title { | |
| font-size: 14px; | |
| color: #666; | |
| margin-bottom: 8px; | |
| } | |
| .stat-value { | |
| font-size: 24px; | |
| font-weight: 600; | |
| color: var(--primary-color); | |
| } | |
| /* Improved table styles with responsiveness */ | |
| .table-responsive { | |
| width: 100%; | |
| overflow-x: auto; | |
| -webkit-overflow-scrolling: touch; | |
| margin-bottom: 1rem; | |
| } | |
| .admin-table { | |
| width: 100%; | |
| border-collapse: separate; | |
| border-spacing: 0; | |
| border-radius: var(--radius); | |
| overflow: hidden; | |
| box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04); | |
| border: 1px solid var(--border-color); | |
| min-width: 600px; /* Ensures table doesn't get too squished */ | |
| } | |
| .admin-table th, .admin-table td { | |
| padding: 12px 16px; | |
| text-align: left; | |
| border-bottom: 1px solid var(--border-color); | |
| } | |
| .admin-table th { | |
| font-weight: 600; | |
| background-color: var(--secondary-color); | |
| position: sticky; | |
| top: 0; | |
| z-index: 1; | |
| font-size: 13px; | |
| } | |
| .admin-table tr:last-child td { | |
| border-bottom: none; | |
| } | |
| .admin-table tr:hover { | |
| background-color: rgba(0, 0, 0, 0.02); | |
| } | |
| /* Action buttons in tables */ | |
| .action-btn { | |
| display: inline-block; | |
| padding: 6px 12px; | |
| font-size: 13px; | |
| border-radius: var(--radius); | |
| text-decoration: none; | |
| color: var(--text-color); | |
| background-color: var(--secondary-color); | |
| border: 1px solid var(--border-color); | |
| transition: all 0.2s; | |
| } | |
| .action-btn:hover { | |
| background-color: #e0e0e0; | |
| } | |
| /* Enhanced form styles */ | |
| .admin-form { | |
| max-width: 700px; | |
| } | |
| .form-group { | |
| margin-bottom: 20px; | |
| } | |
| .form-group label { | |
| display: block; | |
| margin-bottom: 8px; | |
| font-weight: 500; | |
| color: var(--text-color); | |
| } | |
| .form-group small { | |
| display: block; | |
| margin-top: 4px; | |
| color: #666; | |
| font-size: 12px; | |
| } | |
| .form-control { | |
| width: 100%; | |
| padding: 12px; | |
| border: 1px solid var(--border-color); | |
| border-radius: var(--radius); | |
| font-family: 'Inter', sans-serif; | |
| background-color: white; | |
| transition: border-color 0.2s, box-shadow 0.2s; | |
| } | |
| .form-control:focus { | |
| border-color: var(--primary-color); | |
| box-shadow: 0 0 0 2px rgba(80, 70, 229, 0.25); | |
| outline: none; | |
| } | |
| /* Custom checkbox styles */ | |
| .form-check { | |
| display: flex; | |
| align-items: center; | |
| margin-bottom: 16px; | |
| position: relative; | |
| padding-left: 30px; | |
| cursor: pointer; | |
| } | |
| .form-check input { | |
| position: absolute; | |
| opacity: 0; | |
| cursor: pointer; | |
| height: 0; | |
| width: 0; | |
| } | |
| .form-check label { | |
| margin-bottom: 0; | |
| cursor: pointer; | |
| } | |
| .checkmark { | |
| position: absolute; | |
| top: 2px; | |
| left: 0; | |
| height: 18px; | |
| width: 18px; | |
| background-color: white; | |
| border: 1px solid var(--border-color); | |
| border-radius: 4px; | |
| } | |
| .form-check:hover input ~ .checkmark { | |
| background-color: #f5f5f5; | |
| } | |
| .form-check input:checked ~ .checkmark { | |
| background-color: var(--primary-color); | |
| border-color: var(--primary-color); | |
| } | |
| .checkmark:after { | |
| content: ""; | |
| position: absolute; | |
| display: none; | |
| } | |
| .form-check input:checked ~ .checkmark:after { | |
| display: block; | |
| } | |
| .form-check .checkmark:after { | |
| left: 6px; | |
| top: 2px; | |
| width: 4px; | |
| height: 9px; | |
| border: solid white; | |
| border-width: 0 2px 2px 0; | |
| transform: rotate(45deg); | |
| } | |
| .user-info { | |
| background-color: var(--light-gray); | |
| padding: 16px; | |
| border-radius: var(--radius); | |
| margin-bottom: 16px; | |
| border: 1px solid var(--border-color); | |
| } | |
| .user-info p { | |
| margin-bottom: 8px; | |
| } | |
| .btn-primary { | |
| background-color: var(--primary-color); | |
| color: white; | |
| border: none; | |
| padding: 12px 20px; | |
| border-radius: var(--radius); | |
| cursor: pointer; | |
| font-weight: 500; | |
| text-decoration: none; | |
| transition: background-color 0.2s; | |
| } | |
| .btn-primary:hover { | |
| background-color: #4038c7; | |
| } | |
| .btn-secondary { | |
| background-color: var(--secondary-color); | |
| color: var(--text-color); | |
| border: 1px solid var(--border-color); | |
| padding: 12px 20px; | |
| border-radius: var(--radius); | |
| cursor: pointer; | |
| font-weight: 500; | |
| text-decoration: none; | |
| transition: background-color 0.2s; | |
| } | |
| .btn-secondary:hover { | |
| background-color: #e0e0e0; | |
| } | |
| /* Badge styles */ | |
| .badge { | |
| display: inline-block; | |
| padding: 4px 8px; | |
| border-radius: 4px; | |
| font-size: 12px; | |
| font-weight: 500; | |
| } | |
| .badge-primary { | |
| background-color: var(--primary-color); | |
| color: white; | |
| } | |
| .badge-secondary { | |
| background-color: var(--secondary-color); | |
| color: var(--text-color); | |
| } | |
| .pagination { | |
| display: flex; | |
| justify-content: center; | |
| list-style: none; | |
| margin-top: 24px; | |
| } | |
| .pagination li { | |
| margin: 0 4px; | |
| } | |
| .pagination li a { | |
| display: block; | |
| padding: 8px 12px; | |
| border: 1px solid var(--border-color); | |
| border-radius: var(--radius); | |
| color: var(--text-color); | |
| text-decoration: none; | |
| } | |
| .pagination li.active a { | |
| background-color: var(--primary-color); | |
| color: white; | |
| border-color: var(--primary-color); | |
| } | |
| /* Responsive adjustments */ | |
| @media (max-width: 768px) { | |
| .admin-content { | |
| padding: 16px 12px; | |
| } | |
| .admin-stats { | |
| grid-template-columns: 1fr 1fr; | |
| } | |
| .admin-header { | |
| flex-direction: column; | |
| align-items: flex-start; | |
| gap: 12px; | |
| } | |
| .admin-card { | |
| padding: 15px 10px; | |
| } | |
| .admin-table { | |
| font-size: 13px; | |
| } | |
| .admin-table th, .admin-table td { | |
| padding: 8px 10px; | |
| } | |
| .action-btn { | |
| padding: 4px 8px; | |
| font-size: 12px; | |
| } | |
| .btn-primary, .btn-secondary { | |
| padding: 8px 16px; | |
| font-size: 14px; | |
| display: block; | |
| width: 100%; | |
| text-align: center; | |
| margin-bottom: 8px; | |
| } | |
| } | |
| /* Dark mode adjustments */ | |
| @media (prefers-color-scheme: dark) { | |
| .admin-card, .stat-card { | |
| background-color: var(--light-gray); | |
| } | |
| .admin-table th { | |
| background-color: rgba(80, 70, 229, 0.1); | |
| } | |
| .admin-table tr:hover { | |
| background-color: rgba(255, 255, 255, 0.05); | |
| } | |
| .form-control { | |
| background-color: var(--light-gray); | |
| color: var(--text-color); | |
| border-color: rgba(255, 255, 255, 0.1); | |
| } | |
| .checkmark { | |
| background-color: var(--light-gray); | |
| border-color: rgba(255, 255, 255, 0.2); | |
| } | |
| .form-check:hover input ~ .checkmark { | |
| background-color: rgba(255, 255, 255, 0.1); | |
| } | |
| .action-btn { | |
| background-color: rgba(255, 255, 255, 0.1); | |
| border-color: rgba(255, 255, 255, 0.15); | |
| } | |
| .action-btn:hover { | |
| background-color: rgba(255, 255, 255, 0.15); | |
| } | |
| .btn-secondary { | |
| background-color: rgba(255, 255, 255, 0.1); | |
| border-color: rgba(255, 255, 255, 0.15); | |
| } | |
| .btn-secondary:hover { | |
| background-color: rgba(255, 255, 255, 0.15); | |
| } | |
| .btn-primary:hover { | |
| background-color: #5d51ff; | |
| } | |
| } | |
| .user-detail-value { | |
| flex: 1; | |
| } | |
| /* Truncation utility class */ | |
| .text-truncate { | |
| max-width: 300px; | |
| white-space: nowrap; | |
| overflow: hidden; | |
| text-overflow: ellipsis; | |
| } | |
| @media (max-width: 576px) { | |
| .text-truncate { | |
| max-width: 150px; | |
| } | |
| .admin-stats { | |
| grid-template-columns: 1fr; | |
| } | |
| } | |
| </style> | |
| {% endblock %} | |
| {% block content %} | |
| <div class="admin-container"> | |
| <nav class="admin-nav"> | |
| <a href="{{ url_for('admin.index') }}" class="admin-nav-item {% if request.endpoint == 'admin.index' %}active{% endif %}"> | |
| <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="7" height="9" x="3" y="3" rx="1"/><rect width="7" height="5" x="14" y="3" rx="1"/><rect width="7" height="9" x="14" y="12" rx="1"/><rect width="7" height="5" x="3" y="16" rx="1"/></svg> | |
| Dashboard | |
| </a> | |
| <a href="{{ url_for('admin.models') }}" class="admin-nav-item {% if request.endpoint in ['admin.models', 'admin.edit_model', 'admin.add_model'] %}active{% endif %}"> | |
| <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 1v3M12 20v3M4.2 4.2l2.1 2.1M17.7 17.7l2.1 2.1M1 12h3M20 12h3M4.2 19.8l2.1-2.1M17.7 6.3l2.1-2.1"/></svg> | |
| Models | |
| </a> | |
| <a href="{{ url_for('admin.users') }}" class="admin-nav-item {% if request.endpoint == 'admin.users' or request.endpoint == 'admin.user_detail' %}active{% endif %}"> | |
| <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg> | |
| Users | |
| </a> | |
| <a href="{{ url_for('admin.votes') }}" class="admin-nav-item {% if request.endpoint == 'admin.votes' %}active{% endif %}"> | |
| <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg> | |
| Votes | |
| </a> | |
| <a href="{{ url_for('admin.statistics') }}" class="admin-nav-item {% if request.endpoint == 'admin.statistics' %}active{% endif %}"> | |
| <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 3v18h18"/><path d="M18 12V8"/><path d="M13 12v-2"/><path d="M8 12v-5"/></svg> | |
| Statistics | |
| </a> | |
| <a href="{{ url_for('admin.analytics') }}" class="admin-nav-item {% if request.endpoint == 'admin.analytics' %}active{% endif %}"> | |
| <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 20h9"/><path d="M16.5 3.5a2.12 2.12 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"/></svg> | |
| Analytics | |
| </a> | |
| <a href="{{ url_for('admin.security') }}" class="admin-nav-item {% if request.endpoint == 'admin.security' %}active{% endif %}"> | |
| <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg> | |
| Security | |
| </a> | |
| <a href="{{ url_for('admin.timeouts') }}" class="admin-nav-item {% if request.endpoint in ['admin.timeouts', 'admin.create_timeout', 'admin.cancel_timeout'] %}active{% endif %}"> | |
| <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><polyline points="12,6 12,12 16,14"/></svg> | |
| Timeouts | |
| </a> | |
| <a href="{{ url_for('admin.campaigns') }}" class="admin-nav-item {% if request.endpoint in ['admin.campaigns', 'admin.campaign_detail', 'admin.resolve_campaign_route'] %}active{% endif %}"> | |
| <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="m22 21-3-3m0 0a5 5 0 0 0-7-7 5 5 0 0 0 7 7Z"/></svg> | |
| Campaigns | |
| </a> | |
| <a href="{{ url_for('admin.activity') }}" class="admin-nav-item {% if request.endpoint == 'admin.activity' %}active{% endif %}"> | |
| <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12h-8v8h8v-8z"/><path d="M3 21V3h18v9"/><path d="M12 3v6H3"/></svg> | |
| Activity | |
| </a> | |
| </nav> | |
| <div class="admin-content"> | |
| {% block admin_content %}{% endblock %} | |
| </div> | |
| </div> | |
| {% endblock %} |