Files
digiserver-v2/app/templates/base.html
T
ske087 05fc1a14f6 Move the Ajutor nav link after Logout
Header nav order was Dashboard, Players, Playlists, Ajutor, Admin, Logout,
toggle. Ajutor now sits after Logout and before the theme toggle, so the
navigation links stay grouped and the two "settings"-style controls (logout,
help, theme) are together at the end.
2026-10-15 00:01:07 +03:00

634 lines
25 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}DigiServer v2{% endblock %}</title>
<style>
/* Ensure emoji font support */
@supports (font-family: "Apple Color Emoji") {
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji";
}
}
:root {
/* Light Mode Colors */
--primary-gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
--primary-color: #667eea;
--primary-dark: #5568d3;
--secondary-color: #764ba2;
/* Colour for text links. Kept separate from --primary-color so the
primary colour can stay dark enough for button backgrounds while
links remain legible on the matching background. */
--link-color: #667eea;
--bg-color: #f5f7fa;
--card-bg: #ffffff;
--text-color: #2d3748;
--text-secondary: #718096;
--border-color: #e2e8f0;
--header-bg: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
--shadow: 0 4px 6px rgba(0, 0, 0, 0.07);
--shadow-lg: 0 10px 25px rgba(0, 0, 0, 0.1);
}
body.dark-mode {
/* Dark Mode Colors */
--primary-gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
--primary-color: #7c3aed;
--primary-dark: #6d28d9;
--secondary-color: #8b5cf6;
/* Dark background needs a lighter link colour (#7c3aed is too dim
on a dark surface). */
--link-color: #a78bfa;
--bg-color: #1a202c;
--card-bg: #2d3748;
--text-color: #e2e8f0;
--text-secondary: #a0aec0;
--border-color: #4a5568;
--header-bg: linear-gradient(135deg, #7c3aed 0%, #8b5cf6 100%);
--shadow: 0 4px 6px rgba(0, 0, 0, 0.3);
--shadow-lg: 0 10px 25px rgba(0, 0, 0, 0.4);
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
line-height: 1.6;
color: var(--text-color);
background: var(--bg-color);
transition: background 0.3s, color 0.3s;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
header {
background: var(--header-bg);
color: white;
padding: 1rem 0;
margin-bottom: 2rem;
box-shadow: var(--shadow);
}
header .container {
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 1rem;
}
header h1 {
font-size: 1.5rem;
display: flex;
align-items: center;
gap: 0.5rem;
}
nav {
display: flex;
align-items: center;
gap: 0.5rem;
flex-wrap: wrap;
}
nav a {
color: white;
text-decoration: none;
padding: 0.5rem 1rem;
border-radius: 6px;
transition: all 0.3s;
font-weight: 500;
display: flex;
align-items: center;
gap: 0.5rem;
}
nav a:hover {
background: rgba(255,255,255,0.2);
transform: translateY(-2px);
}
nav a img {
width: 18px;
height: 18px;
filter: brightness(0) invert(1);
}
/* Mobile Responsive */
@media (max-width: 768px) {
header .container {
flex-direction: column;
align-items: stretch;
padding: 1rem;
}
header h1 {
font-size: 1.25rem;
justify-content: center;
text-align: center;
}
nav {
flex-direction: column;
gap: 0.5rem;
width: 100%;
}
nav a {
justify-content: center;
width: 100%;
padding: 0.75rem 1rem;
}
.dark-mode-toggle {
margin-left: 0;
width: 100%;
padding: 0.75rem;
}
.container {
padding: 15px;
}
.card {
padding: 1rem;
margin-bottom: 1rem;
}
.card-header {
padding: 15px;
margin: -1rem -1rem 1rem -1rem;
}
}
@media (max-width: 480px) {
header h1 {
font-size: 1.1rem;
}
nav a {
font-size: 0.9rem;
padding: 0.6rem 0.8rem;
}
nav a img {
width: 16px;
height: 16px;
}
}
.dark-mode-toggle {
background: rgba(255,255,255,0.2);
border: 2px solid rgba(255,255,255,0.3);
color: white;
cursor: pointer;
padding: 0.5rem;
border-radius: 6px;
transition: all 0.3s;
margin-left: 0.5rem;
display: flex;
align-items: center;
justify-content: center;
}
.dark-mode-toggle:hover {
background: rgba(255,255,255,0.3);
transform: scale(1.1);
}
.dark-mode-toggle img {
width: 20px;
height: 20px;
filter: brightness(0) invert(1);
}
/* Emoji fallback for systems without emoji fonts */
.emoji-fallback::before {
content: attr(data-emoji);
font-family: 'Apple Color Emoji', 'Segoe UI Emoji', 'Noto Color Emoji', sans-serif;
}
.alert {
padding: 1rem;
margin-bottom: 1rem;
border-radius: 8px;
border-left: 4px solid;
transition: all 0.3s;
}
.alert-success {
background: #d4edda;
border-color: #28a745;
color: #155724;
}
body.dark-mode .alert-success {
background: rgba(40, 167, 69, 0.2);
border-color: #28a745;
color: #7ce3a3;
}
.alert-danger {
background: #f8d7da;
border-color: #dc3545;
color: #721c24;
}
body.dark-mode .alert-danger {
background: rgba(220, 53, 69, 0.2);
border-color: #dc3545;
color: #f88f9a;
}
.alert-warning {
background: #fff3cd;
border-color: #ffc107;
color: #856404;
}
body.dark-mode .alert-warning {
background: rgba(255, 193, 7, 0.2);
border-color: #ffc107;
color: #ffd454;
}
.alert-info {
background: #d1ecf1;
border-color: #17a2b8;
color: #0c5460;
}
body.dark-mode .alert-info {
background: rgba(23, 162, 184, 0.2);
border-color: #17a2b8;
color: #7dd3e0;
}
.card {
background: var(--card-bg);
border-radius: 12px;
padding: 1.5rem;
margin-bottom: 1.5rem;
box-shadow: var(--shadow);
transition: all 0.3s;
border: 1px solid var(--border-color);
}
.card:hover {
box-shadow: var(--shadow-lg);
transform: translateY(-2px);
}
.card h2 {
margin-bottom: 1rem;
color: var(--text-color);
font-weight: 600;
}
.card h3 {
color: var(--text-color);
}
/* Card with gradient header */
.card-header {
background: var(--primary-gradient);
color: white;
padding: 20px;
border-radius: 12px 12px 0 0;
margin: -1.5rem -1.5rem 1.5rem -1.5rem;
}
.card-header h2 {
margin: 0;
color: white;
}
.btn {
display: inline-block;
padding: 0.5rem 1rem;
background: var(--primary-color);
color: white;
text-decoration: none;
border-radius: 6px;
border: none;
cursor: pointer;
transition: all 0.3s;
font-weight: 500;
}
.btn:hover {
background: var(--primary-dark);
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
}
.btn-primary {
background: var(--primary-gradient);
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4);
}
.btn-danger { background: #e74c3c; }
.btn-danger:hover {
background: #c0392b;
box-shadow: 0 4px 12px rgba(231, 76, 60, 0.4);
}
.btn-success { background: #27ae60; }
.btn-success:hover {
background: #229954;
box-shadow: 0 4px 12px rgba(39, 174, 96, 0.4);
}
.btn-info {
background: #3498db;
}
.btn-info:hover {
background: #2980b9;
box-shadow: 0 4px 12px rgba(52, 152, 219, 0.4);
}
.btn-sm {
padding: 0.375rem 0.75rem;
font-size: 0.875rem;
}
/* Form Inputs */
input, textarea, select {
background: var(--card-bg);
color: var(--text-color);
border: 1px solid var(--border-color);
transition: all 0.3s;
}
input:focus, textarea:focus, select:focus {
border-color: var(--primary-color);
outline: none;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}
body.dark-mode input:focus,
body.dark-mode textarea:focus,
body.dark-mode select:focus {
box-shadow: 0 0 0 3px rgba(124, 58, 237, 0.3);
}
/* Tables */
table {
background: var(--card-bg);
color: var(--text-color);
}
th {
background: var(--bg-color);
color: var(--text-color);
}
tr {
border-bottom: 1px solid var(--border-color);
}
/* Code blocks */
code, pre {
background: var(--bg-color);
color: var(--text-color);
border: 1px solid var(--border-color);
}
footer {
margin-top: 2rem;
padding: 1rem 0;
text-align: center;
color: var(--text-secondary);
font-size: 0.9rem;
}
/* ================================================================
Dark-mode fixes for INLINE colours
----------------------------------------------------------------
A recurring pattern across the templates is to set colours with
inline style="..." attributes (a few JS blocks build nodes the
same way). An inline declaration outranks every normal rule --
including the body.dark-mode rules above -- so those literals keep
their LIGHT value in dark mode. The text, which inherits the (now
light) --text-color, then ends up light-on-light: unreadable.
Inline styles can only be beaten with !important, so the light
literals are mapped to the theme variables here, centrally, with
the property scoped so a colour used for two things (e.g. #1a202c
is both a dark surface and dark text) is only changed where it is
wrong. This fixes every template at once -- including any future
page that repeats the pattern -- instead of patching each file.
Everything is scoped to body.dark-mode, so the LIGHT theme is
completely untouched.
Ordering matters: selectors have equal specificity, so later rules
win. The bare "#fff" rule is therefore placed BEFORE the "#fff3cd"
/"#fff8e1" ones, because "#fff" is a string prefix of them.
================================================================ */
/* --- White / near-white surfaces -> dark card --------------------- */
body.dark-mode [style*="background: white"], body.dark-mode [style*="background-color: white"],
body.dark-mode [style*="background: #fff"], body.dark-mode [style*="background-color: #fff"],
body.dark-mode [style*="background: #ffffff"], body.dark-mode [style*="background-color: #ffffff"],
body.dark-mode [style*="background: #fefefe"], body.dark-mode [style*="background-color: #fefefe"],
body.dark-mode [style*="background: #f5f7fa"], body.dark-mode [style*="background-color: #f5f7fa"],
body.dark-mode [style*="background: #e9ecef"], body.dark-mode [style*="background-color: #e9ecef"],
body.dark-mode [style*="background: #e0e0e0"], body.dark-mode [style*="background-color: #e0e0e0"],
body.dark-mode [style*="background: #f0f2ff"], body.dark-mode [style*="background-color: #f0f2ff"],
body.dark-mode [style*="background: #e8ebff"], body.dark-mode [style*="background-color: #e8ebff"],
body.dark-mode [style*="background: #e7e9ff"], body.dark-mode [style*="background-color: #e7e9ff"],
body.dark-mode [style*="background: #ede9fe"], body.dark-mode [style*="background-color: #ede9fe"] {
background: var(--card-bg) !important;
}
/* --- Light grey panels -> dark page background -------------------- */
body.dark-mode [style*="background: #f8f9fa"], body.dark-mode [style*="background-color: #f8f9fa"],
body.dark-mode [style*="background: #f0f0f0"], body.dark-mode [style*="background-color: #f0f0f0"],
body.dark-mode [style*="background: #f5f5f5"], body.dark-mode [style*="background-color: #f5f5f5"],
body.dark-mode [style*="background: #f4f4f4"], body.dark-mode [style*="background-color: #f4f4f4"],
body.dark-mode [style*="background: #f8fafc"], body.dark-mode [style*="background-color: #f8fafc"] {
background: var(--bg-color) !important;
}
/* --- Semantic pastel backgrounds -> dark equivalents -------------- */
/* info / blue */
body.dark-mode [style*="background: #d1ecf1"], body.dark-mode [style*="background-color: #d1ecf1"],
body.dark-mode [style*="background: #cce5ff"], body.dark-mode [style*="background-color: #cce5ff"],
body.dark-mode [style*="background: #e7f3ff"], body.dark-mode [style*="background-color: #e7f3ff"],
body.dark-mode [style*="background: #f0f7ff"], body.dark-mode [style*="background-color: #f0f7ff"] {
background: #1e3a5f !important;
}
/* success / green */
body.dark-mode [style*="background: #d4edda"], body.dark-mode [style*="background-color: #d4edda"],
body.dark-mode [style*="background: #d1f0e0"], body.dark-mode [style*="background-color: #d1f0e0"],
body.dark-mode [style*="background: #c6f6d5"], body.dark-mode [style*="background-color: #c6f6d5"] {
background: #1a4d2e !important;
}
/* warning / amber (must stay AFTER the "#fff" rule above) */
body.dark-mode [style*="background: #fff3cd"], body.dark-mode [style*="background-color: #fff3cd"],
body.dark-mode [style*="background: #fff8e1"], body.dark-mode [style*="background-color: #fff8e1"],
body.dark-mode [style*="background: #ffeaa7"], body.dark-mode [style*="background-color: #ffeaa7"],
body.dark-mode [style*="background: #fef3c7"], body.dark-mode [style*="background-color: #fef3c7"],
body.dark-mode [style*="background: #feebc8"], body.dark-mode [style*="background-color: #feebc8"] {
background: #4a3800 !important;
}
/* danger / red */
body.dark-mode [style*="background: #f8d7da"], body.dark-mode [style*="background-color: #f8d7da"],
body.dark-mode [style*="background: #fed7d7"], body.dark-mode [style*="background-color: #fed7d7"] {
background: #4a1a1a !important;
}
/* mid-grey chips / knobs */
body.dark-mode [style*="background: #ccc"], body.dark-mode [style*="background-color: #ccc"],
body.dark-mode [style*="background: #6c757d"], body.dark-mode [style*="background-color: #6c757d"] {
background: #4a5568 !important;
}
/* --- Dark text -> light text -------------------------------------- */
body.dark-mode [style*="color: black"] {
color: var(--text-color) !important;
}
body.dark-mode [style*="color: #6c757d"], body.dark-mode [style*="color:#6c757d"],
body.dark-mode [style*="color: #666"], body.dark-mode [style*="color: #888"],
body.dark-mode [style*="color: #999"], body.dark-mode [style*="color: #aaa"],
body.dark-mode [style*="color: #495057"], body.dark-mode [style*="color: #718096"],
body.dark-mode [style*="color: #7f8c8d"], body.dark-mode [style*="color: #5a6a78"],
body.dark-mode [style*="color: #4a5568"] {
color: var(--text-secondary) !important;
}
body.dark-mode [style*="color: #555"], body.dark-mode [style*="color: #475569"],
body.dark-mode [style*="color: #64748b"] {
color: #cbd5e1 !important;
}
body.dark-mode [style*="color: #333"], body.dark-mode [style*="color: #2c3e50"],
body.dark-mode [style*="color: #000"], body.dark-mode [style*="color: #1a202c"],
body.dark-mode [style*="color: #2d3748"] {
color: var(--text-color) !important;
}
/* links / info blue */
body.dark-mode [style*="color: #0066cc"], body.dark-mode [style*="color: #007bff"],
body.dark-mode [style*="color: #0056b3"], body.dark-mode [style*="color: #0c5460"],
body.dark-mode [style*="color: #004085"], body.dark-mode [style*="color: #3498db"] {
color: #90cdf4 !important;
}
/* success text */
body.dark-mode [style*="color: #155724"], body.dark-mode [style*="color: #156b2e"],
body.dark-mode [style*="color: #28a745"], body.dark-mode [style*="color: #27ae60"],
body.dark-mode [style*="color: #2f855a"] {
color: #86efac !important;
}
/* warning text */
body.dark-mode [style*="color: #856404"], body.dark-mode [style*="color: #92400e"],
body.dark-mode [style*="color: #c05621"], body.dark-mode [style*="color: #e67e22"] {
color: #fbbf24 !important;
}
/* danger text */
body.dark-mode [style*="color: #721c24"], body.dark-mode [style*="color: #dc3545"],
body.dark-mode [style*="color: #e74c3c"], body.dark-mode [style*="color: #e53e3e"],
body.dark-mode [style*="color:#e53e3e"], body.dark-mode [style*="color: #c53030"],
body.dark-mode [style*="color: #b91c1c"] {
color: #fca5a5 !important;
}
/* purple accents */
body.dark-mode [style*="color: #5b21b6"] {
color: #a78bfa !important;
}
/* --- Light borders -> dark borders ------------------------------- */
body.dark-mode [style*="border-top-color: white"] {
border-top-color: var(--border-color) !important;
}
body.dark-mode [style*="#e2e8f0"], body.dark-mode [style*="#dee2e6"],
body.dark-mode [style*="#ddd"], body.dark-mode [style*="#ccc"],
body.dark-mode [style*="#ced4da"], body.dark-mode [style*="#e0e0e0"],
body.dark-mode [style*="#eee"], body.dark-mode [style*="#e9ecef"],
body.dark-mode [style*="#f0f0f0"], body.dark-mode [style*="#f0f2ff"],
body.dark-mode [style*="#888"], body.dark-mode [style*="#999"] {
border-color: #4a5568 !important;
}
body.dark-mode [style*="#c3e6cb"], body.dark-mode [style*="#d1f0e0"] {
border-color: #2f855a !important;
}
body.dark-mode [style*="#f5c6cb"] {
border-color: #c53030 !important;
}
body.dark-mode [style*="#bee5eb"], body.dark-mode [style*="#cce5ff"] {
border-color: #2c5282 !important;
}
/* --- "Back" links (class used across the admin pages) ------------- */
body.dark-mode .back-link {
color: #90cdf4 !important;
}
</style>
{% block extra_css %}{% endblock %}
</head>
<body>
<header>
<div class="container">
<h1>
<img src="{{ url_for('static', filename='uploads/header_logo.png') }}?v={{ logo_version }}" alt="DigiServer" style="height: 32px; width: auto; margin-right: 8px;" onerror="this.style.display='none';" onload="this.style.display='inline';">
DigiServer
</h1>
<nav>
{% if current_user.is_authenticated %}
<a href="{{ url_for('main.dashboard') }}">
<img src="{{ url_for('static', filename='icons/home.svg') }}" alt="">
Dashboard
</a>
<a href="{{ url_for('players.list') }}">
<img src="{{ url_for('static', filename='icons/monitor.svg') }}" alt="">
Players
</a>
<a href="{{ url_for('content.content_list') }}">
<img src="{{ url_for('static', filename='icons/playlist.svg') }}" alt="">
Playlists
</a>
<a href="{{ url_for('admin.admin_panel') }}">Admin</a>
<a href="{{ url_for('auth.logout') }}">Logout ({{ current_user.username }})</a>
<a href="{{ url_for('main.help_page') }}">
<img src="{{ url_for('static', filename='icons/help.svg') }}" alt="" onerror="this.style.display='none';">
Ajutor
</a>
<button class="dark-mode-toggle" onclick="toggleDarkMode()" title="Toggle Dark Mode">
<img id="theme-icon" src="{{ url_for('static', filename='icons/moon.svg') }}" alt="Toggle theme">
</button>
{% else %}
<a href="{{ url_for('auth.login') }}">Login</a>
<a href="{{ url_for('auth.register') }}">Register</a>
{% endif %}
</nav>
</div>
</header>
<div class="container">
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="alert alert-{{ category }}">
{{ message }}
</div>
{% endfor %}
{% endif %}
{% endwith %}
{% block content %}{% endblock %}
</div>
<footer>
<div class="container">
DigiServer v2.0.0-alpha | Blueprint Architecture | {{ server_version if server_version else 'Development' }}
</div>
</footer>
<script>
// Dark Mode Toggle
function toggleDarkMode() {
const body = document.body;
const themeIcon = document.getElementById('theme-icon');
body.classList.toggle('dark-mode');
// Update icon
if (body.classList.contains('dark-mode')) {
themeIcon.src = "{{ url_for('static', filename='icons/sun.svg') }}";
localStorage.setItem('darkMode', 'enabled');
} else {
themeIcon.src = "{{ url_for('static', filename='icons/moon.svg') }}";
localStorage.setItem('darkMode', 'disabled');
}
}
// Load saved theme preference
document.addEventListener('DOMContentLoaded', function() {
const darkMode = localStorage.getItem('darkMode');
const themeIcon = document.getElementById('theme-icon');
if (darkMode === 'enabled') {
document.body.classList.add('dark-mode');
if (themeIcon) {
themeIcon.src = "{{ url_for('static', filename='icons/sun.svg') }}";
}
}
});
</script>
{% block extra_js %}{% endblock %}
</body>
</html>