|
{% 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%; |
|
} |
|
|
|
|
|
.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; |
|
scrollbar-width: none; |
|
} |
|
|
|
|
|
.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; |
|
} |
|
|
|
.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); |
|
} |
|
|
|
|
|
.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; |
|
} |
|
|
|
.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-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; |
|
} |
|
|
|
|
|
.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; |
|
} |
|
|
|
|
|
.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 { |
|
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); |
|
} |
|
|
|
|
|
@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; |
|
} |
|
} |
|
|
|
|
|
@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; |
|
} |
|
|
|
|
|
.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.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 %} |