Introduce client-side localization helper and en/fr locale bundles. Route admin control, picks, movies, stats, winners and settings UIs through i18n keys instead of hardcoded text. Remove French text from the English UI and standardize shared messages (saving, network errors, confirmations). Update .gitignore to ignore editor metadata and localization helper artifacts.
1453 lines
75 KiB
HTML
1453 lines
75 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title data-i18n="admin.title">Oscar Pool - Admin</title>
|
||
<script src="./js/app_theme.js"></script>
|
||
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
|
||
<link href="./css/app.css" rel="stylesheet">
|
||
<link rel="preload" as="image" href="img/logo_w.png">
|
||
<style>
|
||
/* Hide translatable content until translations are ready - prevents flash during page navigation */
|
||
body:not(.i18n-ready) [data-i18n] {
|
||
opacity: 0;
|
||
transition: opacity 0.1s ease-in;
|
||
}
|
||
body.i18n-ready [data-i18n] {
|
||
opacity: 1;
|
||
}
|
||
/* Fallback: show content after 2 seconds even if JS fails */
|
||
body:not(.i18n-ready) [data-i18n] {
|
||
animation: show-after-delay 0.1s 2s forwards;
|
||
}
|
||
@keyframes show-after-delay {
|
||
to { opacity: 1; }
|
||
}
|
||
</style>
|
||
<script src="./js/localization.js" defer></script>
|
||
<script src="./js/app_header_boot.js" defer></script>
|
||
<style>
|
||
.navbar-custom {
|
||
background-color: black;
|
||
color: white;
|
||
}
|
||
.navbar-custom .navbar-brand,
|
||
.navbar-custom .nav-link {
|
||
color: white;
|
||
}
|
||
.navbar-custom .nav-link:hover {
|
||
color: #ddd;
|
||
}
|
||
.navbar-toggler {
|
||
border-color: white;
|
||
}
|
||
.navbar-toggler-icon {
|
||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3E%3Cpath stroke='white' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E");
|
||
}
|
||
|
||
.admin-tabs-content {
|
||
font-size: 0.95rem;
|
||
}
|
||
|
||
.admin-tabs-content .info-line {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.5rem;
|
||
line-height: 1;
|
||
align-self: center;
|
||
transform: translateY(-2px);
|
||
}
|
||
|
||
.admin-tabs-content .info-line .badge {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
line-height: 1;
|
||
width: 1.2em;
|
||
height: 1.2em;
|
||
padding: 0;
|
||
}
|
||
|
||
.admin-tabs-content .info-line .info-text {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
line-height: 1;
|
||
}
|
||
|
||
.admin-tabs-content .btn-compact {
|
||
padding: 0.2rem 0.5rem;
|
||
font-size: 0.85rem;
|
||
line-height: 1.2;
|
||
}
|
||
|
||
.admin-tabs-content .btn-check-compact {
|
||
padding-left: 0.35rem;
|
||
padding-right: 0.35rem;
|
||
}
|
||
|
||
.admin-tabs-content .app-version-create {
|
||
font-size: 0.95rem;
|
||
}
|
||
|
||
.admin-tabs-content .app-version-create .fw-semibold {
|
||
font-size: 0.95rem;
|
||
}
|
||
|
||
.admin-tabs-content .backup-restore-panel {
|
||
font-size: 0.95rem;
|
||
}
|
||
|
||
.admin-tabs-content .backup-restore-panel .form-label,
|
||
.admin-tabs-content .backup-restore-panel .form-check-label {
|
||
font-size: 0.95rem;
|
||
}
|
||
|
||
.admin-tabs-content .backup-restore-panel .alert {
|
||
font-size: 0.9rem;
|
||
padding: 0.5rem 0.75rem;
|
||
}
|
||
|
||
.admin-tabs-content .btn-check-small {
|
||
height: 1.9rem;
|
||
}
|
||
|
||
.admin-tabs-content .oscar-date-year-select {
|
||
max-width: 220px;
|
||
width: 220px;
|
||
margin-right: 0;
|
||
}
|
||
|
||
.admin-tabs-content .manage-year-select {
|
||
max-width: 110px;
|
||
width: 110px;
|
||
}
|
||
|
||
.admin-tabs-content .oscar-date-input {
|
||
max-width: 220px;
|
||
width: 220px;
|
||
}
|
||
|
||
.admin-tabs-content .points-input-half {
|
||
max-width: 110px;
|
||
width: 110px;
|
||
}
|
||
|
||
.admin-tabs-content .oscar-date-layout {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
gap: 0.75rem;
|
||
}
|
||
|
||
.admin-tabs-content .oscar-date-fields {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 0.5rem;
|
||
}
|
||
|
||
.admin-tabs-content .oscar-date-fields .form-text {
|
||
margin-top: 0.2rem;
|
||
}
|
||
|
||
.admin-tabs-content .oscar-date-current {
|
||
display: inline-block;
|
||
width: fit-content;
|
||
max-width: 100%;
|
||
padding: 0.4rem 0.65rem;
|
||
min-width: 220px;
|
||
}
|
||
|
||
.admin-tabs-content .btn-check-wide {
|
||
padding-left: 0.55rem;
|
||
padding-right: 0.55rem;
|
||
}
|
||
|
||
.admin-tabs-content h4,
|
||
.admin-tabs-content h5 {
|
||
font-size: 1rem;
|
||
font-weight: 600;
|
||
letter-spacing: 0.01em;
|
||
}
|
||
|
||
.admin-subtabs .nav-link {
|
||
padding: 0.3rem 0.7rem;
|
||
font-size: 0.9rem;
|
||
line-height: 1.2;
|
||
}
|
||
|
||
|
||
html[data-theme="dark"] #adminAddMovieAccordion .accordion-item {
|
||
background-color: var(--app-surface-alt);
|
||
border-color: var(--app-border);
|
||
}
|
||
|
||
html[data-theme="dark"] #adminAddMovieAccordion .accordion-button {
|
||
background-color: var(--app-surface-alt);
|
||
color: var(--app-text);
|
||
box-shadow: none;
|
||
}
|
||
|
||
html[data-theme="dark"] #adminAddMovieAccordion .accordion-button::after {
|
||
filter: invert(1);
|
||
}
|
||
|
||
html[data-theme="dark"] #adminAddMovieAccordion .accordion-button:not(.collapsed) {
|
||
background-color: var(--app-surface);
|
||
color: var(--app-text);
|
||
box-shadow: none;
|
||
}
|
||
|
||
html[data-theme="dark"] #adminAddMovieAccordion .accordion-body {
|
||
background-color: var(--app-surface);
|
||
color: var(--app-text);
|
||
}
|
||
|
||
.admin-movies-add {
|
||
margin-top: 0.5rem;
|
||
}
|
||
|
||
.admin-movies-separator {
|
||
border-top: 1px solid var(--app-border);
|
||
margin: 0.75rem 0;
|
||
}
|
||
|
||
.admin-movies-meta {
|
||
margin: 0 0 0.5rem;
|
||
}
|
||
|
||
.admin-subtabs-tight {
|
||
margin-top: 0;
|
||
}
|
||
|
||
.admin-tabs-content .table,
|
||
.admin-tabs-content .table th,
|
||
.admin-tabs-content .table td {
|
||
font-size: 0.95rem;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body class="bg-light">
|
||
|
||
<nav class="navbar navbar-expand-lg navbar-custom">
|
||
<div class="container">
|
||
<a class="navbar-brand" href="movies.html">
|
||
<img src="img/logo_w.png" width="220" height="70" loading="eager" fetchpriority="high" decoding="async" style="max-width: 220px; height: 70px; object-fit: contain;">
|
||
</a>
|
||
<span class="navbar-text ms-auto me-2 d-lg-none navbar-user-name-mobile" data-user-name></span>
|
||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||
<span class="navbar-toggler-icon"></span>
|
||
</button>
|
||
<div class="collapse navbar-collapse" id="navbarNav">
|
||
<div class="navbar-nav">
|
||
<a href="movies.html" class="nav-link btn btn-outline-light me-2" data-i18n="nav.films">Films</a>
|
||
<a href="checklist.html" class="nav-link btn btn-outline-light me-2" data-i18n="nav.checklist">Checklist</a>
|
||
<a href="user-stat.html" class="nav-link btn btn-outline-light me-2" data-i18n="nav.stats">Stats</a>
|
||
<a href="picks.html" class="nav-link btn btn-outline-light me-2" id="nav-link-picks" style="display: none;" data-i18n="nav.myPicks">My picks</a>
|
||
<a href="control.html" class="nav-link btn btn-outline-light me-2 active" data-i18n="nav.admin">Admin</a>
|
||
</div>
|
||
<div class="d-flex ms-auto mt-2 mt-lg-0 align-items-center gap-2 navbar-user">
|
||
<span id="user-name" class="navbar-text" data-user-name></span>
|
||
<button class="theme-toggle" type="button" data-theme-toggle role="switch" aria-checked="false" data-i18n-aria-label="common.toggleDarkMode" aria-label="Toggle dark mode">
|
||
<span class="theme-toggle-icon theme-toggle-icon-sun" aria-hidden="true">
|
||
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
|
||
<circle cx="12" cy="12" r="4"></circle>
|
||
<path d="M12 2v2"></path>
|
||
<path d="M12 20v2"></path>
|
||
<path d="M4.93 4.93l1.41 1.41"></path>
|
||
<path d="M17.66 17.66l1.41 1.41"></path>
|
||
<path d="M2 12h2"></path>
|
||
<path d="M20 12h2"></path>
|
||
<path d="M4.93 19.07l1.41-1.41"></path>
|
||
<path d="M17.66 6.34l1.41-1.41"></path>
|
||
</svg>
|
||
</span>
|
||
<span class="theme-toggle-thumb" aria-hidden="true"></span>
|
||
<span class="theme-toggle-icon theme-toggle-icon-moon" aria-hidden="true">
|
||
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
|
||
<path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z"></path>
|
||
</svg>
|
||
</span>
|
||
</button>
|
||
<button id="log-off" class="btn btn-danger" data-i18n="nav.logout">Log out</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</nav>
|
||
|
||
<div class="container mt-4 pb-5">
|
||
<div id="response" class="alert d-none" role="alert"></div>
|
||
|
||
<ul class="nav nav-tabs" id="adminTabs" role="tablist">
|
||
<li class="nav-item" role="presentation">
|
||
<button class="nav-link active" id="admin-settings-tab" data-bs-toggle="tab" data-bs-target="#admin-settings" type="button" role="tab" aria-controls="admin-settings" aria-selected="true" data-i18n="admin.tabs.settings">
|
||
Settings
|
||
</button>
|
||
</li>
|
||
<li class="nav-item" role="presentation">
|
||
<button class="nav-link" id="admin-movies-tab" data-bs-toggle="tab" data-bs-target="#admin-movies" type="button" role="tab" aria-controls="admin-movies" aria-selected="false" data-i18n="nav.films">
|
||
Films
|
||
</button>
|
||
</li>
|
||
<li class="nav-item" role="presentation">
|
||
<button class="nav-link" id="admin-users-tab" data-bs-toggle="tab" data-bs-target="#admin-users" type="button" role="tab" aria-controls="admin-users" aria-selected="false" data-i18n="admin.tabs.users">
|
||
Users
|
||
</button>
|
||
</li>
|
||
<li class="nav-item" role="presentation">
|
||
<button class="nav-link" id="admin-modal-100-tab" data-bs-toggle="tab" data-bs-target="#admin-modal-100" type="button" role="tab" aria-controls="admin-modal-100" aria-selected="false" data-i18n="admin.tabs.modal100">
|
||
Modal 100%
|
||
</button>
|
||
</li>
|
||
<li class="nav-item" role="presentation">
|
||
<button class="nav-link" id="admin-winners-tab" data-bs-toggle="tab" data-bs-target="#admin-winners" type="button" role="tab" aria-controls="admin-winners" aria-selected="false" data-i18n="admin.tabs.winners">
|
||
Winners
|
||
</button>
|
||
</li>
|
||
<li class="nav-item" role="presentation">
|
||
<button class="nav-link" id="admin-categories-tab" data-bs-toggle="tab" data-bs-target="#admin-categories" type="button" role="tab" aria-controls="admin-categories" aria-selected="false" data-i18n="admin.tabs.oscarCategories">
|
||
Oscar Categories
|
||
</button>
|
||
</li>
|
||
<li class="nav-item" role="presentation">
|
||
<button class="nav-link" id="admin-backup-tab" data-bs-toggle="tab" data-bs-target="#admin-backup" type="button" role="tab" aria-controls="admin-backup" aria-selected="false" data-i18n="admin.tabs.backupRestore">
|
||
Backup / Restore
|
||
</button>
|
||
</li>
|
||
<li class="nav-item" role="presentation">
|
||
<button class="nav-link" id="admin-version-tab" data-bs-toggle="tab" data-bs-target="#admin-version" type="button" role="tab" aria-controls="admin-version" aria-selected="false" data-i18n="admin.tabs.version" style="display: none;">
|
||
Version
|
||
</button>
|
||
</li>
|
||
</ul>
|
||
|
||
<div class="tab-content pt-3 admin-tabs-content">
|
||
<div class="tab-pane fade show active" id="admin-settings" role="tabpanel" aria-labelledby="admin-settings-tab">
|
||
<div class="card p-3">
|
||
<div class="d-flex flex-wrap align-items-end justify-content-between gap-2">
|
||
<div class="info-line small text-muted">
|
||
<span class="badge bg-light text-dark border" aria-hidden="true">i</span>
|
||
<span class="info-text" data-i18n="admin.settings.info">Global site settings (year, dates, player options).</span>
|
||
</div>
|
||
</div>
|
||
|
||
<ul class="nav nav-pills admin-subtabs mt-3" id="adminSettingsSubTabs" role="tablist">
|
||
<li class="nav-item" role="presentation">
|
||
<button class="nav-link active" id="admin-settings-subtab-oscar-date" data-bs-toggle="tab" data-bs-target="#admin-settings-pane-oscar-date" type="button" role="tab" aria-controls="admin-settings-pane-oscar-date" aria-selected="true" data-i18n="admin.settings.oscarDate">
|
||
Oscar Date
|
||
</button>
|
||
</li>
|
||
<li class="nav-item" role="presentation">
|
||
<button class="nav-link" id="admin-settings-subtab-visibility" data-bs-toggle="tab" data-bs-target="#admin-settings-pane-visibility" type="button" role="tab" aria-controls="admin-settings-pane-visibility" aria-selected="false" data-i18n="admin.settings.visibility">
|
||
Visibility
|
||
</button>
|
||
</li>
|
||
<li class="nav-item" role="presentation">
|
||
<button class="nav-link" id="admin-settings-subtab-language" data-bs-toggle="tab" data-bs-target="#admin-settings-pane-language" type="button" role="tab" aria-controls="admin-settings-pane-language" aria-selected="false" data-i18n="admin.settings.language">
|
||
Language
|
||
</button>
|
||
</li>
|
||
</ul>
|
||
|
||
<div class="tab-content pt-3">
|
||
<!-- Oscar date + points -->
|
||
<div class="tab-pane fade show active" id="admin-settings-pane-oscar-date" role="tabpanel" aria-labelledby="admin-settings-subtab-oscar-date" tabindex="0">
|
||
<div class="row g-3">
|
||
<div class="col-12 col-xxl-6">
|
||
<div class="border rounded p-3 bg-light h-100">
|
||
<div class="text-muted small mb-3" data-i18n="admin.settings.oscarDateDesc">
|
||
Configure the ceremony date for the countdown (Films & Checklist). Default: March 15.
|
||
</div>
|
||
|
||
<div class="oscar-date-layout">
|
||
<div class="oscar-date-fields">
|
||
<div>
|
||
<label for="oscar_date_year" class="form-label visually-hidden" data-i18n="admin.settings.year">Year</label>
|
||
<div class="input-group align-items-center">
|
||
<select class="form-select oscar-date-year-select" id="oscar_date_year"></select>
|
||
<button type="button" id="save-active-year" class="btn btn-warning btn-compact btn-check-compact btn-check-small btn-check-wide rounded-3 ms-1" data-i18n-aria-label="admin.settings.applyActiveYear" aria-label="Apply active year">✓</button>
|
||
</div>
|
||
<div class="form-text" data-i18n="admin.settings.yearBasedOnMovies">Based on years present in movies.</div>
|
||
</div>
|
||
<div>
|
||
<label for="oscar_date_value" class="form-label visually-hidden" data-i18n="admin.settings.date">Date</label>
|
||
<div class="input-group align-items-center">
|
||
<input type="date" class="form-control oscar-date-input" id="oscar_date_value">
|
||
<button type="button" id="save-oscar-date" class="btn btn-warning btn-compact btn-check-compact btn-check-small btn-check-wide rounded-3 ms-1" data-i18n-aria-label="common.save" aria-label="Save">✓</button>
|
||
</div>
|
||
<div class="form-text" data-i18n="admin.settings.dateFormat">Format: YYYY-MM-DD.</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="mt-3">
|
||
<div class="text-muted small mb-1" data-i18n="admin.settings.configuredDates">Configured dates</div>
|
||
<div id="oscar-date-current" class="border rounded bg-white oscar-date-current">—</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="col-12 col-xxl-6">
|
||
<div class="border rounded p-3 bg-light h-100">
|
||
<div class="text-muted small mb-3" data-i18n="admin.settings.pointsDesc">
|
||
Configure points awarded for watched movies and correct Oscar picks.
|
||
</div>
|
||
|
||
<div id="points-config-alert" class="alert d-none" role="alert"></div>
|
||
|
||
<div class="row g-3">
|
||
<div class="col-12">
|
||
<label for="points-per-movie" class="form-label" data-i18n="admin.settings.pointsPerMovie">Points per movie watched</label>
|
||
<input type="number" class="form-control points-input-half" id="points-per-movie" min="0" step="1" value="1">
|
||
<div class="form-text" data-i18n="admin.settings.pointsPerMovieDesc">Points awarded for each completed movie.</div>
|
||
</div>
|
||
<div class="col-12">
|
||
<label for="points-per-correct-pick" class="form-label" data-i18n="admin.settings.pointsPerCorrectPick">Points per correct pick</label>
|
||
<input type="number" class="form-control points-input-half" id="points-per-correct-pick" min="0" step="1" value="1">
|
||
<div class="form-text" data-i18n="admin.settings.pointsPerCorrectPickDesc">Points awarded for each correct Oscar pick.</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="mt-3">
|
||
<div class="d-flex gap-2">
|
||
<button type="button" id="points-config-reload" class="btn btn-outline-secondary btn-compact" data-i18n="admin.settings.reload">Reload</button>
|
||
<button type="button" id="points-config-save" class="btn btn-warning btn-compact" data-i18n="common.save">Save</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="mt-3">
|
||
<div class="text-muted small mb-1" data-i18n="admin.settings.pointsCalculation">Points calculation</div>
|
||
<div class="border rounded p-2 bg-white">
|
||
<div class="small">
|
||
<strong data-i18n="admin.settings.total">Total</strong> = (<span data-i18n="admin.settings.moviesWatched">Movies watched</span> × <span id="points-per-movie-preview">1</span>) + (<span data-i18n="admin.settings.correctPicks">Correct picks</span> × <span id="points-per-correct-pick-preview">1</span>)
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Visibility settings -->
|
||
<div class="tab-pane fade" id="admin-settings-pane-visibility" role="tabpanel" aria-labelledby="admin-settings-subtab-visibility" tabindex="0">
|
||
<div class="border rounded p-3 bg-light">
|
||
<div id="visibility-config-alert" class="alert d-none mb-3" role="alert"></div>
|
||
|
||
<div class="form-check mb-4">
|
||
<input class="form-check-input" type="checkbox" id="visibility-show-picks-features">
|
||
<label class="form-check-label" for="visibility-show-picks-features" data-i18n="admin.visibility.showPicksFeatures">
|
||
Show "Picks" features
|
||
</label>
|
||
<div class="form-text small text-muted" data-i18n="admin.visibility.showPicksFeaturesDesc">Controls display of "My picks" button in navigation and "Correct picks" column in statistics.</div>
|
||
</div>
|
||
|
||
<div class="form-check mb-4">
|
||
<input class="form-check-input" type="checkbox" id="player_ui_show_status">
|
||
<label class="form-check-label" for="player_ui_show_status" data-i18n="admin.visibility.showSourceProgress">
|
||
Show "Source" + "Progress" (admins)
|
||
</label>
|
||
<div class="form-text small text-muted" data-i18n="admin.visibility.showSourceProgressDesc">Controls display of debug block in player.html (admins only).</div>
|
||
</div>
|
||
|
||
<div class="form-check mb-4">
|
||
<input class="form-check-input" type="checkbox" id="visibility-show-version-tab">
|
||
<label class="form-check-label" for="visibility-show-version-tab" data-i18n="admin.visibility.showVersionTab">
|
||
Show "Version" tab
|
||
</label>
|
||
<div class="form-text small text-muted" data-i18n="admin.visibility.showVersionTabDesc">Controls display of the "Version" tab in the admin panel.</div>
|
||
</div>
|
||
|
||
<div class="d-flex gap-2">
|
||
<button type="button" id="visibility-config-save" class="btn btn-warning btn-compact" data-i18n="common.save">Save</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Language settings -->
|
||
<div class="tab-pane fade" id="admin-settings-pane-language" role="tabpanel" aria-labelledby="admin-settings-subtab-language" tabindex="0">
|
||
<div class="border rounded p-3 bg-light">
|
||
<div id="language-config-alert" class="alert d-none mb-3" role="alert"></div>
|
||
|
||
<div class="mb-3">
|
||
<label for="site_language" class="form-label" data-i18n="admin.language.siteLanguage">Site language</label>
|
||
<select id="site_language" class="form-select" style="max-width: 200px;">
|
||
<option value="en">English</option>
|
||
<option value="fr">Français</option>
|
||
</select>
|
||
<div class="form-text small text-muted" data-i18n="admin.language.siteLanguageDesc">Changes the language for all users. Page refresh required.</div>
|
||
</div>
|
||
|
||
<div class="d-flex gap-2">
|
||
<button type="button" id="language-config-save" class="btn btn-warning btn-compact" data-i18n="common.save">Save</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="tab-pane fade" id="admin-version" role="tabpanel" aria-labelledby="admin-version-tab" style="display: none;">
|
||
<div class="card p-3">
|
||
<div class="d-flex flex-wrap align-items-end justify-content-between gap-2">
|
||
<div class="info-line small text-muted">
|
||
<span class="badge bg-light text-dark border" aria-hidden="true">i</span>
|
||
<span class="info-text" data-i18n="admin.version.info">Controls the version displayed in the footer (stored in DB). Click ✓ to activate a version, then check the preview.</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="app_version_alert" class="alert d-none mt-3 mb-0" role="alert"></div>
|
||
|
||
<div class="pt-3">
|
||
<div class="row g-3">
|
||
<div class="col-12 col-xxl-7">
|
||
<div id="app_version_list" class="border rounded bg-white app-version-list">—</div>
|
||
</div>
|
||
|
||
<div class="col-12 col-xxl-5">
|
||
<div class="border rounded p-3 bg-light app-version-create">
|
||
<div class="fw-semibold mb-1" data-i18n="admin.version.createNew">Create a new version</div>
|
||
<div class="text-muted small mb-3" data-i18n="admin.version.createNewDesc">
|
||
Adds an entry to the history (you can activate it immediately).
|
||
</div>
|
||
|
||
<div class="row g-3">
|
||
<div class="col-12 col-md-5">
|
||
<label for="app_version_new_version" class="form-label" data-i18n="admin.version.version">Version</label>
|
||
<input id="app_version_new_version" type="text" class="form-control" data-i18n-placeholder="admin.version.versionPlaceholder" placeholder="e.g.: 1.0.1" maxlength="80">
|
||
<div class="form-text" data-i18n="admin.version.versionExample">E.g.: 1.2.3</div>
|
||
</div>
|
||
<div class="col-12 col-md-7">
|
||
<label for="app_version_new_message" class="form-label" data-i18n="admin.version.messageOptional">Message (optional)</label>
|
||
<input id="app_version_new_message" type="text" class="form-control" data-i18n-placeholder="admin.version.messagePlaceholder" placeholder="e.g.: Hotfix login" maxlength="500">
|
||
<div class="form-text" data-i18n="admin.version.messageDesc">Short & readable (shown in footer).</div>
|
||
</div>
|
||
<div class="col-12 d-flex flex-wrap justify-content-end gap-2">
|
||
<button type="button" id="app_version_create" class="btn btn-primary btn-compact" data-i18n="admin.version.create">Create</button>
|
||
<button type="button" id="app_version_create_and_activate" class="btn btn-warning btn-compact" data-i18n="admin.version.createAndActivate">Create & activate</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="tab-pane fade" id="admin-modal-100" role="tabpanel" aria-labelledby="admin-modal-100-tab">
|
||
<div class="card p-3">
|
||
<div class="d-flex flex-wrap align-items-end justify-content-between gap-2">
|
||
<div class="info-line small text-muted">
|
||
<span class="badge bg-light text-dark border" aria-hidden="true">i</span>
|
||
<span class="info-text" data-i18n="admin.modal100.info">Content displayed when a user has watched 100% of films.</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="pt-3">
|
||
<!-- Edit + Live preview side-by-side -->
|
||
<div>
|
||
<div class="row g-3">
|
||
<div class="col-12 col-xxl-7">
|
||
<div class="border rounded p-3 bg-light h-100">
|
||
<div class="fw-semibold mb-1" data-i18n="admin.modal100.content">Content</div>
|
||
<div class="text-muted small mb-3" data-i18n="admin.modal100.contentDesc">
|
||
Edit the content then save. Preview is live (not saved until you save).
|
||
</div>
|
||
|
||
<div class="mb-3">
|
||
<label for="completion_modal_title" class="form-label" data-i18n="admin.modal100.titleLabel">Title</label>
|
||
<input type="text" class="form-control" id="completion_modal_title" maxlength="200" data-i18n-placeholder="admin.modal100.titlePlaceholder" placeholder="Congratulations! ...">
|
||
</div>
|
||
|
||
<div class="mb-3">
|
||
<label for="completion_modal_body_text" class="form-label" data-i18n="admin.modal100.textOptional">Text (optional)</label>
|
||
<textarea class="form-control" id="completion_modal_body_text" rows="4" data-i18n-placeholder="admin.modal100.textPlaceholder" placeholder="Text displayed in the modal (line breaks preserved)"></textarea>
|
||
<div class="form-text" data-i18n="admin.modal100.textDesc">Displayed as plain text (safe). Leave empty if not needed.</div>
|
||
</div>
|
||
|
||
<div class="mb-3">
|
||
<label for="completion_modal_body_html" class="form-label" data-i18n="admin.modal100.htmlOptional">HTML (optional)</label>
|
||
<textarea class="form-control font-monospace" id="completion_modal_body_html" rows="6" placeholder="<p>…</p>"></textarea>
|
||
<div class="form-text" data-i18n="admin.modal100.htmlDesc">
|
||
Displayed as HTML (server removes script tags and on* handlers).
|
||
</div>
|
||
</div>
|
||
|
||
<div class="mb-3">
|
||
<label for="completion_modal_video_src" class="form-label" data-i18n="admin.modal100.videoSrc">Video src (optional)</label>
|
||
<input type="text" class="form-control" id="completion_modal_video_src" maxlength="2048" placeholder="video/reward.mp4">
|
||
<div class="form-text" data-i18n="admin.modal100.videoSrcDesc">Relative path (e.g.: video/reward.mp4) or full URL. Empty = no video.</div>
|
||
</div>
|
||
|
||
<div class="d-flex flex-wrap justify-content-end gap-2">
|
||
<button type="button" id="completion_modal_save" class="btn btn-warning btn-compact" data-i18n="common.save">Save</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="col-12 col-xxl-5">
|
||
<div class="border rounded p-3 bg-light">
|
||
<div class="d-flex align-items-center justify-content-between gap-2 mb-2">
|
||
<div class="fw-semibold" id="completion_modal_preview_title">—</div>
|
||
</div>
|
||
<div id="completion_modal_preview_custom" class="text-start d-none"></div>
|
||
<div id="completion_modal_preview_text" class="text-start" style="white-space: pre-wrap;"></div>
|
||
|
||
<div class="mt-3">
|
||
<video id="completion_modal_preview_video" width="55%" controls class="d-none">
|
||
<source id="completion_modal_preview_video_source" src="" type="video/mp4">
|
||
<span data-i18n="admin.modal100.browserNoVideo">Your browser does not support video.</span>
|
||
</video>
|
||
</div>
|
||
|
||
<div class="border rounded p-3 bg-white mt-3">
|
||
<ul class="mb-0 text-muted">
|
||
<li><strong data-i18n="admin.modal100.textLabel">Text</strong>: <span data-i18n="admin.modal100.textInfoShort">displayed as-is (safe), line breaks preserved.</span></li>
|
||
<li><strong>HTML</strong>: <span data-i18n="admin.modal100.htmlInfoShort">server removes script tags and on* attributes.</span></li>
|
||
<li><strong data-i18n="admin.modal100.videoLabel">Video</strong>: <span data-i18n="admin.modal100.videoInfoShort">accepts relative path (e.g.: video/reward.mp4) or full URL.</span></li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="tab-pane fade" id="admin-users" role="tabpanel" aria-labelledby="admin-users-tab">
|
||
<div class="row g-3">
|
||
<div class="col-12">
|
||
<div class="card p-3">
|
||
<div class="d-flex flex-wrap align-items-end justify-content-between gap-2 mb-2">
|
||
<div class="info-line small text-muted">
|
||
<span class="badge bg-light text-dark border" aria-hidden="true">i</span>
|
||
<span class="info-text"><span id="admin-users-count" class="me-1">—</span><span data-i18n="admin.users.usersCount">user(s)</span></span>
|
||
</div>
|
||
<div class="d-flex gap-2">
|
||
<button type="button" id="open-add-user-modal" class="btn btn-warning btn-sm btn-compact" data-i18n="admin.users.addUser">Add user</button>
|
||
<button type="button" id="refresh-users" class="btn btn-outline-primary btn-sm btn-compact" data-i18n="common.refresh">Refresh</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="table-responsive">
|
||
<table class="table table-sm align-middle mb-0">
|
||
<thead>
|
||
<tr>
|
||
<th data-i18n="admin.users.name">Name</th>
|
||
<th data-i18n="auth.email">Email</th>
|
||
<th style="width: 1%;" data-i18n="admin.users.status">Status</th>
|
||
<th style="width: 1%;" data-i18n="admin.users.actions">Actions</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="admin-users-body">
|
||
<tr><td colspan="4" class="text-muted" data-i18n="admin.users.loading">Loading list…</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="tab-pane fade" id="admin-movies" role="tabpanel" aria-labelledby="admin-movies-tab">
|
||
<div class="card p-3">
|
||
<div class="admin-movies-add">
|
||
<div class="info-line small text-muted mb-3">
|
||
<span class="badge bg-light text-dark border" aria-hidden="true">i</span>
|
||
<span class="info-text" data-i18n="admin.movies.addInfo">Add movies: this section auto-fetches movie info via imdb_id (OMDb).</span>
|
||
</div>
|
||
|
||
<form id="add-movie-form">
|
||
<div class="row g-3">
|
||
<div class="col-12 col-md-4">
|
||
<label for="oscar_year" class="form-label" data-i18n="admin.movies.oscarYear">Oscar year</label>
|
||
<input type="number" class="form-control" id="oscar_year" placeholder="2026" min="1900" max="3000" required>
|
||
<div class="form-text" data-i18n="admin.movies.oscarYearDesc">Separates movies & stats by year.</div>
|
||
</div>
|
||
<div class="col-12 col-md-4">
|
||
<label for="imdb_id" class="form-label">IMDB ID</label>
|
||
<input type="text" class="form-control" id="imdb_id" placeholder="tt1234567" required>
|
||
</div>
|
||
<div class="col-12 col-md-4">
|
||
<label for="category" class="form-label" data-i18n="admin.movies.category">Category</label>
|
||
<input type="text" class="form-control" id="category" placeholder="Best Picture, etc." required>
|
||
</div>
|
||
<div class="col-12 d-flex flex-wrap justify-content-end gap-2">
|
||
<button type="submit" class="btn btn-primary btn-compact" data-i18n="common.add">Add</button>
|
||
</div>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
|
||
<div class="admin-movies-separator"></div>
|
||
|
||
<div class="d-flex flex-wrap align-items-end justify-content-between gap-2 admin-movies-meta">
|
||
<div class="text-muted small">
|
||
<span id="admin-movies-table-count"></span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="row g-2 align-items-end">
|
||
<div class="col-12 col-md-4 col-xl-3">
|
||
<label for="manage_year" class="form-label visually-hidden" data-i18n="admin.movies.oscarYear">Oscar year</label>
|
||
<select id="manage_year" class="form-select manage-year-select">
|
||
<option value="" data-i18n="admin.movies.allYears">All</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div class="col-12 col-md-8 col-xl-9 d-flex gap-2 justify-content-end">
|
||
<button type="button" id="refresh-movies" class="btn btn-outline-primary btn-compact" data-i18n="common.refresh">Refresh</button>
|
||
<button type="button" id="update-omdb-selected" class="btn btn-outline-warning btn-compact" disabled data-i18n="admin.movies.refreshOmdb">Refresh OMDb</button>
|
||
<button type="button" id="delete-selected" class="btn btn-danger btn-compact" disabled data-i18n="common.delete">Delete</button>
|
||
</div>
|
||
|
||
<div class="col-12">
|
||
<div class="d-flex justify-content-between align-items-center">
|
||
<div class="form-check">
|
||
<input class="form-check-input" type="checkbox" id="select-all-movies">
|
||
<label class="form-check-label" for="select-all-movies" data-i18n="admin.movies.selectAll">Select all</label>
|
||
</div>
|
||
<small class="text-muted"><span id="selected-count">0</span> <span data-i18n="admin.movies.selected">selected</span></small>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="table-responsive mt-2">
|
||
<table class="table table-sm align-middle mb-0 admin-movies-table">
|
||
<thead>
|
||
<tr>
|
||
<th style="width: 1%;" data-i18n="admin.movies.sel">Sel.</th>
|
||
<th style="width: 1%;">ID</th>
|
||
<th data-i18n="admin.movies.titleCol">Title</th>
|
||
<th style="width: 12%;" data-i18n="admin.movies.player">Player</th>
|
||
<th style="width: 20%;" data-i18n="admin.movies.category">Category</th>
|
||
<th style="width: 12%;" data-i18n="admin.movies.year">Year</th>
|
||
<th style="width: 1%;" data-i18n="admin.users.actions">Actions</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="admin-movies-body">
|
||
<tr><td colspan="7" class="text-muted" data-i18n="admin.users.loading">Loading list…</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="tab-pane fade" id="admin-backup" role="tabpanel" aria-labelledby="admin-backup-tab">
|
||
<div class="card p-3">
|
||
<div class="d-flex flex-wrap align-items-end justify-content-between gap-2">
|
||
<div class="info-line small text-muted">
|
||
<span class="badge bg-light text-dark border" aria-hidden="true">i</span>
|
||
<span class="info-text" data-i18n="admin.backup.info">MongoDB database backup and restore.</span>
|
||
</div>
|
||
</div>
|
||
|
||
<ul class="nav nav-pills admin-subtabs mt-3" id="adminBackupSubTabs" role="tablist">
|
||
<li class="nav-item" role="presentation">
|
||
<button class="nav-link active" id="admin-backup-subtab-backup" data-bs-toggle="tab" data-bs-target="#admin-backup-pane-backup" type="button" role="tab" aria-controls="admin-backup-pane-backup" aria-selected="true" data-i18n="admin.backup.backup">
|
||
Backup
|
||
</button>
|
||
</li>
|
||
<li class="nav-item" role="presentation">
|
||
<button class="nav-link" id="admin-backup-subtab-restore" data-bs-toggle="tab" data-bs-target="#admin-backup-pane-restore" type="button" role="tab" aria-controls="admin-backup-pane-restore" aria-selected="false" data-i18n="admin.backup.restore">
|
||
Restore
|
||
</button>
|
||
</li>
|
||
<li class="nav-item" role="presentation">
|
||
<button class="nav-link" id="admin-backup-subtab-help" data-bs-toggle="tab" data-bs-target="#admin-backup-pane-help" type="button" role="tab" aria-controls="admin-backup-pane-help" aria-selected="false" data-i18n="admin.backup.help">
|
||
Help
|
||
</button>
|
||
</li>
|
||
</ul>
|
||
|
||
<div class="tab-content pt-3">
|
||
<!-- Backup -->
|
||
<div class="tab-pane fade show active" id="admin-backup-pane-backup" role="tabpanel" aria-labelledby="admin-backup-subtab-backup" tabindex="0">
|
||
<div class="d-flex flex-wrap align-items-end justify-content-between gap-2 mb-2">
|
||
<div>
|
||
<div class="fw-semibold" data-i18n="admin.backup.backups">Backups</div>
|
||
<div class="text-muted small" data-i18n="admin.backup.backupsDesc">Format .ndjson.gz, stored on server.</div>
|
||
</div>
|
||
<div class="d-flex flex-wrap gap-2">
|
||
<button type="button" id="db-backup-create" class="btn btn-warning btn-compact" data-i18n="admin.backup.createBackup">Create backup</button>
|
||
<button type="button" id="db-backup-refresh" class="btn btn-outline-secondary btn-compact" data-i18n="common.refresh">Refresh</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="table-responsive">
|
||
<table class="table table-sm align-middle mb-0">
|
||
<thead>
|
||
<tr>
|
||
<th data-i18n="admin.backup.file">File</th>
|
||
<th class="text-nowrap" style="width: 1%;" data-i18n="admin.backup.size">Size</th>
|
||
<th class="text-nowrap" style="width: 1%;" data-i18n="admin.backup.modified">Modified</th>
|
||
<th class="text-nowrap text-end" style="width: 1%;" data-i18n="admin.users.actions">Actions</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="db-backup-list">
|
||
<tr><td colspan="4" class="text-muted" data-i18n="admin.backup.openTabToLoad">Open tab to load list…</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Restore -->
|
||
<div class="tab-pane fade" id="admin-backup-pane-restore" role="tabpanel" aria-labelledby="admin-backup-subtab-restore" tabindex="0">
|
||
<div class="row g-3">
|
||
<div class="col-12 col-xxl-7">
|
||
<div class="border rounded p-3 bg-light">
|
||
<div class="fw-semibold text-danger mb-1" data-i18n="admin.backup.restoreBackup">Restore a backup</div>
|
||
<div class="text-muted small mb-3" data-i18n="admin.backup.restoreDesc">
|
||
Restore a .ndjson.gz file. The drop option is recommended.
|
||
</div>
|
||
|
||
<div class="alert alert-warning mb-3" data-i18n="admin.backup.restoreWarning">
|
||
Restore can overwrite data. Make a backup first if needed.
|
||
</div>
|
||
|
||
<div class="mb-3">
|
||
<label for="db-restore-file" class="form-label" data-i18n="admin.backup.backupFile">Backup file</label>
|
||
<input class="form-control" type="file" id="db-restore-file" accept=".gz,.ndjson.gz,application/gzip">
|
||
</div>
|
||
|
||
<div class="form-check mb-3">
|
||
<input class="form-check-input" type="checkbox" id="db-restore-drop" checked>
|
||
<label class="form-check-label" for="db-restore-drop" data-i18n="admin.backup.dropBeforeRestore">
|
||
Drop collections before restore (recommended)
|
||
</label>
|
||
</div>
|
||
|
||
<div class="d-flex flex-wrap justify-content-end gap-2">
|
||
<button type="button" id="db-restore-run" class="btn btn-danger btn-compact" data-i18n="admin.backup.restore">Restore</button>
|
||
</div>
|
||
|
||
<div id="db-restore-result" class="small text-muted mt-3"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="col-12 col-xxl-5">
|
||
<div class="border rounded p-3 bg-white">
|
||
<div class="fw-semibold mb-2" data-i18n="admin.backup.tip">Tip</div>
|
||
<div class="text-muted" data-i18n="admin.backup.tipDesc">
|
||
Use drop unless you know exactly what you're doing (otherwise risk of duplicates/errors).
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Help -->
|
||
<div class="tab-pane fade" id="admin-backup-pane-help" role="tabpanel" aria-labelledby="admin-backup-subtab-help" tabindex="0">
|
||
<div class="row g-3">
|
||
<div class="col-12 col-xxl-8">
|
||
<div class="border rounded p-3 bg-white">
|
||
<div class="fw-semibold mb-2" data-i18n="admin.backup.howItWorks">How it works</div>
|
||
<ul class="mb-0 text-muted">
|
||
<li><strong data-i18n="admin.backup.backup">Backup</strong>: <span data-i18n="admin.backup.backupHowDesc">creates a .ndjson.gz file (MongoDB export) and keeps it on the server.</span></li>
|
||
<li><strong data-i18n="admin.backup.download">Download</strong>: <span data-i18n="admin.backup.downloadDesc">retrieves the file locally (useful before a risky operation).</span></li>
|
||
<li><strong data-i18n="admin.backup.restore">Restore</strong>: <span data-i18n="admin.backup.restoreHowDesc">imports the file. With drop, restored collections are deleted first.</span></li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
<div class="col-12 col-xxl-4">
|
||
<div class="border rounded p-3 bg-light">
|
||
<div class="fw-semibold mb-2" data-i18n="admin.backup.quickCheck">Quick check</div>
|
||
<div class="text-muted" data-i18n="admin.backup.quickCheckDesc">
|
||
After restore, reload the page and check "Films", "Checklist" and "Stats".
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="tab-pane fade" id="admin-categories" role="tabpanel" aria-labelledby="admin-categories-tab">
|
||
<div class="card p-3">
|
||
<div class="d-flex flex-wrap align-items-end justify-content-between gap-2">
|
||
<div class="info-line small text-muted">
|
||
<span class="badge bg-light text-dark border" aria-hidden="true">i</span>
|
||
<span class="info-text" data-i18n="admin.categories.info">Create and manage categories manually.</span>
|
||
</div>
|
||
</div>
|
||
|
||
<ul class="nav nav-pills admin-subtabs mt-3" id="adminCategoriesSubTabs" role="tablist">
|
||
<li class="nav-item" role="presentation">
|
||
<button class="nav-link active" id="admin-categories-subtab-manual" data-bs-toggle="tab" data-bs-target="#admin-categories-pane-manual" type="button" role="tab" aria-controls="admin-categories-pane-manual" aria-selected="true" data-i18n="admin.categories.manualCreation">
|
||
Manual creation
|
||
</button>
|
||
</li>
|
||
<li class="nav-item" role="presentation">
|
||
<button class="nav-link" id="admin-categories-subtab-list" data-bs-toggle="tab" data-bs-target="#admin-categories-pane-list" type="button" role="tab" aria-controls="admin-categories-pane-list" aria-selected="false" data-i18n="admin.categories.list">
|
||
List
|
||
</button>
|
||
</li>
|
||
</ul>
|
||
|
||
<div class="tab-content pt-3">
|
||
<!-- Manual creation -->
|
||
<div class="tab-pane fade show active" id="admin-categories-pane-manual" role="tabpanel" aria-labelledby="admin-categories-subtab-manual" tabindex="0">
|
||
<div class="row g-3">
|
||
<div class="col-12 col-xxl-7">
|
||
<div class="border rounded p-3 bg-light">
|
||
<div class="fw-semibold mb-1" data-i18n="admin.categories.createManually">Create a category manually</div>
|
||
<div class="text-muted small mb-3" data-i18n="admin.categories.createManuallyDesc">
|
||
Add a category and its nominees one by one.
|
||
</div>
|
||
|
||
<div id="categories-manual-alert" class="alert d-none" role="alert"></div>
|
||
|
||
<form id="create-category-form">
|
||
<div class="mb-3">
|
||
<label for="manual-year" class="form-label" data-i18n="admin.settings.year">Year</label>
|
||
<select class="form-select" id="manual-year" required>
|
||
<option value="" data-i18n="common.loading">Loading...</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div class="mb-3">
|
||
<label for="category-number" class="form-label" data-i18n="admin.categories.categoryNumber">Category number</label>
|
||
<input type="number" class="form-control" id="category-number" placeholder="1" min="1" max="50" required>
|
||
</div>
|
||
|
||
<div class="mb-3">
|
||
<label for="category-name" class="form-label" data-i18n="admin.categories.categoryName">Category name</label>
|
||
<input type="text" class="form-control" id="category-name" placeholder="Best Picture" required>
|
||
</div>
|
||
|
||
<div class="mb-3">
|
||
<label for="nominees-list" class="form-label" data-i18n="admin.categories.nomineesOnePerLine">Nominees (one per line)</label>
|
||
<textarea class="form-control" id="nominees-list" rows="10" placeholder="Anora The Brutalist Dune: Part Two" required></textarea>
|
||
<div class="form-text" data-i18n="admin.categories.nomineesDesc">Enter one nominee per line.</div>
|
||
</div>
|
||
|
||
<div class="d-flex flex-wrap justify-content-end gap-2">
|
||
<button type="submit" class="btn btn-primary" data-i18n="admin.version.create">Create</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="col-12 col-xxl-5">
|
||
<div class="border rounded p-3 bg-white">
|
||
<div class="fw-semibold mb-2" data-i18n="admin.categories.note">Note</div>
|
||
<div class="text-muted" data-i18n="admin.categories.noteDesc">
|
||
You can create multiple categories for the same year.
|
||
The category number must be unique per year.
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- List -->
|
||
<div class="tab-pane fade" id="admin-categories-pane-list" role="tabpanel" aria-labelledby="admin-categories-subtab-list" tabindex="0">
|
||
<div class="d-flex flex-wrap align-items-end justify-content-between gap-2 mb-2">
|
||
<div>
|
||
<div class="fw-semibold" data-i18n="admin.categories.importedCategories">Imported categories</div>
|
||
<div class="text-muted small" data-i18n="admin.categories.manageExisting">Manage existing categories</div>
|
||
</div>
|
||
<div class="d-flex flex-column gap-2">
|
||
<div class="d-flex gap-2">
|
||
<select class="form-select" id="list-year-filter" style="width: 150px;">
|
||
<option value="" data-i18n="admin.categories.allYears">All years</option>
|
||
</select>
|
||
<button type="button" id="refresh-categories-list" class="btn btn-outline-secondary" data-i18n="common.refresh">Refresh</button>
|
||
<button type="button" id="bulk-delete-categories-btn" class="btn btn-sm btn-danger" disabled data-i18n="common.delete">Delete</button>
|
||
</div>
|
||
<div id="categories-selection-text" class="text-muted small" style="display: none;">
|
||
<span id="bulk-delete-count">0</span> <span data-i18n="admin.categories.categoriesSelected">category(ies) selected</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="categories-list-container" class="border rounded bg-white p-3">
|
||
<div class="text-muted" data-i18n="admin.users.loading">Loading list…</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="tab-pane fade" id="admin-winners" role="tabpanel" aria-labelledby="admin-winners-tab">
|
||
<div class="card p-3">
|
||
<div class="d-flex flex-wrap align-items-end justify-content-between gap-2">
|
||
<div class="info-line small text-muted">
|
||
<span class="badge bg-light text-dark border" aria-hidden="true">i</span>
|
||
<span class="info-text" data-i18n="admin.winners.info">Define one or more winners per year (tie possible).</span>
|
||
</div>
|
||
</div>
|
||
|
||
<ul class="nav nav-pills admin-subtabs mt-3" id="adminWinnersSubTabs" role="tablist">
|
||
<li class="nav-item" role="presentation">
|
||
<button class="nav-link active" id="admin-winners-subtab-set" data-bs-toggle="tab" data-bs-target="#admin-winners-pane-set" type="button" role="tab" aria-controls="admin-winners-pane-set" aria-selected="true" data-i18n="admin.winners.manage">
|
||
Manage
|
||
</button>
|
||
</li>
|
||
<li class="nav-item" role="presentation">
|
||
<button class="nav-link" id="admin-winners-subtab-help" data-bs-toggle="tab" data-bs-target="#admin-winners-pane-help" type="button" role="tab" aria-controls="admin-winners-pane-help" aria-selected="false" data-i18n="admin.backup.help">
|
||
Help
|
||
</button>
|
||
</li>
|
||
</ul>
|
||
|
||
<div class="tab-content pt-3">
|
||
<!-- Set -->
|
||
<div class="tab-pane fade show active" id="admin-winners-pane-set" role="tabpanel" aria-labelledby="admin-winners-subtab-set" tabindex="0">
|
||
<div class="row g-3">
|
||
<div class="col-12 col-xxl-7">
|
||
<div class="border rounded p-3 bg-light">
|
||
<div class="fw-semibold mb-1" data-i18n="admin.winners.addWinner">Add a winner</div>
|
||
<div class="text-muted small mb-3" data-i18n="admin.winners.addWinnerDesc">
|
||
You can add multiple winners for the same year (tie).
|
||
</div>
|
||
|
||
<div class="row g-3">
|
||
<div class="col-12 col-md-4">
|
||
<label for="winner_year" class="form-label" data-i18n="admin.settings.year">Year</label>
|
||
<select id="winner_year" class="form-select"></select>
|
||
<div class="form-text" data-i18n="admin.settings.yearBasedOnMovies">Based on years present in movies.</div>
|
||
</div>
|
||
<div class="col-12 col-md-5">
|
||
<label for="winner_user" class="form-label" data-i18n="admin.winners.user">User</label>
|
||
<select id="winner_user" class="form-select">
|
||
<option value="">—</option>
|
||
</select>
|
||
<div class="form-text" data-i18n="admin.winners.removeNote">To remove: use the list below.</div>
|
||
</div>
|
||
<div class="col-12 col-md-3">
|
||
<label for="winner_points" class="form-label" data-i18n="admin.winners.pointsOptional">Points (optional)</label>
|
||
<input id="winner_points" type="number" class="form-control" data-i18n-placeholder="admin.winners.pointsPlaceholder" placeholder="e.g.: 42" min="0" step="1">
|
||
<div class="form-text" data-i18n="admin.winners.pointsDesc">Leave empty if not applicable.</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="d-flex flex-wrap justify-content-end gap-2 mt-3">
|
||
<button type="button" id="winner_reload" class="btn btn-outline-secondary btn-compact" data-i18n="admin.settings.reload">Reload</button>
|
||
<button type="button" id="winner_clear" class="btn btn-outline-danger btn-compact" data-i18n="admin.winners.deleteAll">Delete all</button>
|
||
<button type="button" id="winner_save" class="btn btn-warning btn-compact" data-i18n="common.add">Add</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="col-12 col-xxl-5">
|
||
<div class="border rounded p-3 bg-white h-100">
|
||
<div class="d-flex flex-wrap align-items-end justify-content-between gap-2 mb-2">
|
||
<div>
|
||
<div class="fw-semibold" data-i18n="admin.winners.currentWinners">Current winners</div>
|
||
<div class="text-muted small" data-i18n="admin.winners.globalView">Global view (all years).</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="winner_current" class="border rounded p-2 bg-light">—</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Help -->
|
||
<div class="tab-pane fade" id="admin-winners-pane-help" role="tabpanel" aria-labelledby="admin-winners-subtab-help" tabindex="0">
|
||
<div class="row g-3">
|
||
<div class="col-12 col-xxl-8">
|
||
<div class="border rounded p-3 bg-white">
|
||
<div class="fw-semibold mb-2" data-i18n="admin.winners.howItWorks">How it works</div>
|
||
<ul class="mb-0 text-muted">
|
||
<li data-i18n="admin.winners.multipleWinners">A year can have multiple winners (tie).</li>
|
||
<li data-i18n="admin.winners.pointsOptionalInfo">Points are optional (display only).</li>
|
||
<li data-i18n="admin.winners.removeInfo">To remove a winner, use the "Remove" button in the "Current winners" list.</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
<div class="col-12 col-xxl-4">
|
||
<div class="border rounded p-3 bg-light">
|
||
<div class="fw-semibold mb-2" data-i18n="admin.backup.tip">Tip</div>
|
||
<div class="text-muted" data-i18n="admin.winners.tipDesc">
|
||
If you change the site's active year, also check the winners for that year.
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Reset user password modal -->
|
||
<div class="modal fade" id="resetUserPasswordModal" tabindex="-1" aria-hidden="true">
|
||
<div class="modal-dialog modal-dialog-scrollable">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h5 class="modal-title" data-i18n="admin.users.resetPassword">Reset password</h5>
|
||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<div class="text-muted mb-2" data-i18n="admin.users.resetPasswordDesc">
|
||
Generates a temporary password. The user will need to change it on next login.
|
||
</div>
|
||
|
||
<div class="mb-3">
|
||
<div class="fw-semibold" id="reset_user_label">—</div>
|
||
<div class="text-muted small" id="reset_user_email_label">—</div>
|
||
</div>
|
||
|
||
<div id="reset-user-modal-response" class="alert d-none" role="alert"></div>
|
||
|
||
<div id="reset-user-modal-result" class="border rounded p-2 bg-light d-none">
|
||
<div class="small text-muted mb-1" data-i18n="admin.users.tempPasswordOnce">Temporary password (shown once)</div>
|
||
<div class="d-flex gap-2 align-items-center">
|
||
<code class="flex-grow-1" id="reset-user-temp-password">—</code>
|
||
<button type="button" class="btn btn-outline-secondary btn-sm" id="copy-temp-password" data-i18n="common.copy">Copy</button>
|
||
</div>
|
||
<div class="small text-muted mt-2" id="reset-user-expires-at"></div>
|
||
</div>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn btn-outline-secondary btn-compact" data-bs-dismiss="modal" data-i18n="common.close">Close</button>
|
||
<button type="button" class="btn btn-danger btn-compact" id="confirm-reset-user-password" data-i18n="admin.users.generate">Generate</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Delete user confirmation modal -->
|
||
<div class="modal fade" id="deleteUserModal" tabindex="-1" aria-hidden="true">
|
||
<div class="modal-dialog">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h5 class="modal-title text-danger" data-i18n="admin.users.deleteUser">Delete user</h5>
|
||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<div class="mb-2">
|
||
<span data-i18n="admin.users.aboutToDelete">You are about to delete:</span>
|
||
<div class="fw-semibold" id="delete_user_label">—</div>
|
||
<div class="text-muted small" id="delete_user_email_label">—</div>
|
||
</div>
|
||
|
||
<div class="alert alert-warning" data-i18n="admin.users.actionIrreversible">
|
||
This action is irreversible.
|
||
</div>
|
||
|
||
<div class="mb-2" data-i18n="admin.users.typeEmailToConfirm">
|
||
To confirm, type the exact email:
|
||
</div>
|
||
<input type="text" class="form-control" id="delete_user_verify_input" data-i18n-placeholder="admin.users.emailPlaceholder" placeholder="[email protected]">
|
||
<div class="form-text" data-i18n="admin.users.deleteButtonActivates">
|
||
The "Delete" button will activate only if the email matches.
|
||
</div>
|
||
|
||
<div id="delete-user-modal-response" class="alert d-none mt-3" role="alert"></div>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn btn-outline-secondary btn-compact" data-bs-dismiss="modal" data-i18n="common.cancel">Cancel</button>
|
||
<button type="button" class="btn btn-danger btn-compact" id="confirm-delete-user" disabled data-i18n="common.delete">Delete</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Add user modal -->
|
||
<div class="modal fade" id="addUserModal" tabindex="-1" aria-hidden="true">
|
||
<div class="modal-dialog modal-dialog-scrollable">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h5 class="modal-title" data-i18n="admin.users.addUser">Add user</h5>
|
||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<div class="text-muted mb-2" data-i18n="admin.users.addUserDesc">
|
||
Create an account with a temporary password (shown once). The user will need to change it on next login.
|
||
</div>
|
||
|
||
<div id="add-user-response" class="alert d-none" role="alert"></div>
|
||
|
||
<form id="add-user-form">
|
||
<div class="mb-3">
|
||
<label for="add_user_name" class="form-label" data-i18n="auth.name">Name</label>
|
||
<input type="text" class="form-control" id="add_user_name" data-i18n-placeholder="admin.users.namePlaceholder" placeholder="First name / nickname" required>
|
||
</div>
|
||
<div class="mb-3">
|
||
<label for="add_user_email" class="form-label" data-i18n="auth.email">Email</label>
|
||
<input type="email" class="form-control" id="add_user_email" data-i18n-placeholder="admin.users.emailPlaceholder" placeholder="[email protected]" required>
|
||
</div>
|
||
<div class="form-check mb-3">
|
||
<input class="form-check-input" type="checkbox" id="add_user_admin">
|
||
<label class="form-check-label" for="add_user_admin" data-i18n="nav.admin">Admin</label>
|
||
</div>
|
||
|
||
<div id="add-user-result" class="border rounded p-2 bg-light d-none">
|
||
<div class="small text-muted mb-1" data-i18n="admin.users.tempPasswordOnce">Temporary password (shown once)</div>
|
||
<div class="d-flex gap-2 align-items-center">
|
||
<code class="flex-grow-1" id="add-user-temp-password">—</code>
|
||
<button type="button" class="btn btn-outline-secondary btn-sm" id="copy-add-user-temp-password" data-i18n="common.copy">Copy</button>
|
||
</div>
|
||
<div class="small text-muted mt-2" id="add-user-expires-at"></div>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal" data-i18n="common.close">Close</button>
|
||
<button type="button" id="add-user-reset" class="btn btn-outline-secondary" data-i18n="admin.users.reset">Reset</button>
|
||
<button type="submit" form="add-user-form" class="btn btn-warning" id="add-user-submit" data-i18n="admin.version.create">Create</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Edit category modal -->
|
||
<div class="modal fade" id="editCategoryModal" tabindex="-1" aria-hidden="true">
|
||
<div class="modal-dialog modal-dialog-scrollable">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h5 class="modal-title" data-i18n="admin.categories.editCategory">Edit category</h5>
|
||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<input type="hidden" id="edit_category_id">
|
||
|
||
<div class="mb-3">
|
||
<label for="edit_category_number" class="form-label" data-i18n="admin.categories.categoryNumber">Category number</label>
|
||
<input type="number" class="form-control" id="edit_category_number" min="1" max="50" required>
|
||
</div>
|
||
|
||
<div class="mb-3">
|
||
<label for="edit_category_name" class="form-label" data-i18n="admin.categories.categoryName">Category name</label>
|
||
<input type="text" class="form-control" id="edit_category_name" required>
|
||
</div>
|
||
|
||
<div class="mb-3">
|
||
<label for="edit_nominees_list" class="form-label" data-i18n="admin.categories.nomineesOnePerLine">Nominees (one per line)</label>
|
||
<textarea class="form-control" id="edit_nominees_list" rows="10" required></textarea>
|
||
<div class="form-text" data-i18n="admin.categories.nomineesDesc">Enter one nominee per line.</div>
|
||
</div>
|
||
|
||
<div id="edit-category-alert" class="alert d-none" role="alert"></div>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal" data-i18n="common.cancel">Cancel</button>
|
||
<button type="button" class="btn btn-primary" id="save-category-changes" data-i18n="common.save">Save</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Edit movie modal -->
|
||
<div class="modal fade" id="editMovieModal" tabindex="-1" aria-hidden="true">
|
||
<div class="modal-dialog modal-lg modal-dialog-scrollable">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h5 class="modal-title" id="editMovieModalTitle" data-i18n="admin.movies.editMovie">Edit movie</h5>
|
||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<input type="hidden" id="edit_movie_id">
|
||
|
||
<div class="mb-3">
|
||
<div class="text-muted small mb-1">Movie ID</div>
|
||
<div class="d-flex gap-2 align-items-center">
|
||
<code class="flex-grow-1" id="edit_movie_id_label">—</code>
|
||
<button type="button" class="btn btn-outline-secondary btn-sm" id="copy-movie-id" data-i18n="common.copy">Copy</button>
|
||
</div>
|
||
</div>
|
||
|
||
<ul class="nav nav-tabs" id="editMovieTabs" role="tablist">
|
||
<li class="nav-item" role="presentation">
|
||
<button
|
||
class="nav-link active"
|
||
id="edit-movie-tab-info"
|
||
data-bs-toggle="tab"
|
||
data-bs-target="#edit-movie-pane-info"
|
||
type="button"
|
||
role="tab"
|
||
aria-controls="edit-movie-pane-info"
|
||
aria-selected="true"
|
||
data-i18n="admin.movies.info"
|
||
>
|
||
Info
|
||
</button>
|
||
</li>
|
||
<li class="nav-item" role="presentation">
|
||
<button
|
||
class="nav-link"
|
||
id="edit-movie-tab-video"
|
||
data-bs-toggle="tab"
|
||
data-bs-target="#edit-movie-pane-video"
|
||
type="button"
|
||
role="tab"
|
||
aria-controls="edit-movie-pane-video"
|
||
aria-selected="false"
|
||
data-i18n="admin.movies.playerVideo"
|
||
>
|
||
Player / Video
|
||
</button>
|
||
</li>
|
||
</ul>
|
||
|
||
<div class="tab-content pt-3" id="editMovieTabsContent">
|
||
<!-- Info tab -->
|
||
<div
|
||
class="tab-pane fade show active"
|
||
id="edit-movie-pane-info"
|
||
role="tabpanel"
|
||
aria-labelledby="edit-movie-tab-info"
|
||
tabindex="0"
|
||
>
|
||
<div class="row g-3">
|
||
<div class="col-md-6">
|
||
<label for="edit_imdb_id" class="form-label" data-i18n="admin.movies.imdbIdLabel">IMDB ID (e.g.: tt1234567)</label>
|
||
<input type="text" class="form-control" id="edit_imdb_id" placeholder="tt..." required>
|
||
</div>
|
||
<div class="col-md-6">
|
||
<label for="edit_year" class="form-label" data-i18n="admin.movies.oscarYear">Oscar year</label>
|
||
<input type="number" class="form-control" id="edit_year" placeholder="2026" min="1900" max="3000">
|
||
</div>
|
||
|
||
<div class="col-md-6">
|
||
<label for="edit_category" class="form-label" data-i18n="admin.movies.category">Category</label>
|
||
<input type="text" class="form-control" id="edit_category" placeholder="Best Picture, etc." required>
|
||
</div>
|
||
|
||
<div class="col-md-4">
|
||
<label for="edit_rating" class="form-label" data-i18n="admin.movies.rating">Rating (imdbRating)</label>
|
||
<input type="text" class="form-control" id="edit_rating" placeholder="7.9">
|
||
</div>
|
||
<div class="col-md-4">
|
||
<label for="edit_runtime" class="form-label" data-i18n="admin.movies.runtime">Runtime</label>
|
||
<input type="text" class="form-control" id="edit_runtime" placeholder="142 min">
|
||
<div class="form-text" data-i18n="admin.movies.runtimeExample">E.g.: 142 min</div>
|
||
</div>
|
||
<div class="col-md-4 d-flex align-items-end">
|
||
<div class="form-check">
|
||
<input class="form-check-input" type="checkbox" id="edit_cam_flag">
|
||
<label class="form-check-label" for="edit_cam_flag" data-i18n="admin.movies.camTag">CAM tag</label>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="col-12">
|
||
<div class="form-check">
|
||
<input class="form-check-input" type="checkbox" id="edit_refresh_omdb">
|
||
<label class="form-check-label" for="edit_refresh_omdb" data-i18n="admin.movies.refreshFromOmdb">
|
||
Refresh from OMDb (overwrites title / plot / rating / runtime / poster)
|
||
</label>
|
||
</div>
|
||
<div class="form-text" data-i18n="admin.movies.refreshFromOmdbDesc">If checked, the server makes a new OMDb call via IMDB ID when saving.</div>
|
||
</div>
|
||
|
||
<div class="col-12">
|
||
<label for="edit_title" class="form-label" data-i18n="admin.movies.titleCol">Title</label>
|
||
<input type="text" class="form-control" id="edit_title" placeholder="Title">
|
||
</div>
|
||
|
||
<div class="col-12">
|
||
<label for="edit_poster" class="form-label" data-i18n="admin.movies.posterUrl">Poster URL</label>
|
||
<input type="url" class="form-control" id="edit_poster" placeholder="https://...">
|
||
</div>
|
||
|
||
<div class="col-12">
|
||
<label for="edit_description" class="form-label" data-i18n="admin.movies.description">Description (Plot)</label>
|
||
<textarea class="form-control" id="edit_description" rows="4" placeholder="Plot..."></textarea>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Video tab -->
|
||
<div
|
||
class="tab-pane fade"
|
||
id="edit-movie-pane-video"
|
||
role="tabpanel"
|
||
aria-labelledby="edit-movie-tab-video"
|
||
tabindex="0"
|
||
>
|
||
<div class="row g-3">
|
||
<div class="col-12">
|
||
<label class="form-label" data-i18n="admin.movies.playerSource">Player (source)</label>
|
||
<div class="row g-2">
|
||
<div class="col-12 col-lg-4">
|
||
<label for="edit_player_mode" class="form-label" data-i18n="admin.movies.mode">Mode</label>
|
||
<select id="edit_player_mode" class="form-select">
|
||
<option value="auto">Auto</option>
|
||
<option value="video">Video</option>
|
||
<option value="embed">Embed</option>
|
||
</select>
|
||
<div class="form-text" data-i18n="admin.movies.modeDesc">Auto = video_src → embed_src → vod_link.</div>
|
||
</div>
|
||
<div class="col-12 col-lg-8">
|
||
<label for="edit_video_src" class="form-label" data-i18n="admin.movies.videoUrl">Video URL (mp4/webm/m3u8)</label>
|
||
<input type="text" class="form-control" id="edit_video_src" placeholder="https://.../movie.mp4 or .../playlist.m3u8">
|
||
<div class="form-text" data-i18n="admin.movies.videoUrlDesc">If you use a server file (below) and leave this empty, it will auto-use /api/video/<movieId>.</div>
|
||
</div>
|
||
<div class="col-12">
|
||
<label for="edit_video_file" class="form-label" data-i18n="admin.movies.serverVideoFile">Server video file (optional)</label>
|
||
<input type="text" class="form-control font-monospace" id="edit_video_file" placeholder="my_movie.mp4 or 2026/my_movie.mp4">
|
||
<div class="form-text" data-i18n="admin.movies.serverVideoFileDesc">Relative path under VIDEO_FILES_DIR (default: public/video).</div>
|
||
</div>
|
||
<div class="col-12">
|
||
<label for="edit_video_file_low" class="form-label" data-i18n="admin.movies.serverVideoFileLow">Server video file (low quality, optional)</label>
|
||
<input type="text" class="form-control font-monospace" id="edit_video_file_low" placeholder="LOW/my_movie.LOW.mp4">
|
||
<div class="form-text" data-i18n="admin.movies.serverVideoFileLowDesc">Relative path under VIDEO_FILES_DIR. Example: LOW/123.LOW.mp4.</div>
|
||
</div>
|
||
<div class="col-12">
|
||
<label for="edit_subtitle_file" class="form-label" data-i18n="admin.movies.subtitles">Subtitles (SRT, optional)</label>
|
||
<input type="file" class="form-control" id="edit_subtitle_file" accept=".srt,.vtt,text/plain,text/vtt">
|
||
<div class="form-text" data-i18n="admin.movies.existingSubtitles">Existing subtitles:</div>
|
||
<div id="edit_subtitle_list" class="d-flex flex-column gap-2 mt-1"></div>
|
||
<div class="form-text" data-i18n="admin.movies.subtitlesNote">Add one file per language; repeat upload for multiple subtitles.</div>
|
||
</div>
|
||
<div class="col-12">
|
||
<div class="form-check">
|
||
<input class="form-check-input" type="checkbox" id="edit_subtitle_remove">
|
||
<label class="form-check-label" for="edit_subtitle_remove" data-i18n="admin.movies.removeAllSubtitles">Remove all subtitles</label>
|
||
</div>
|
||
<div class="form-text" data-i18n="admin.movies.removeAllSubtitlesDesc">Deletes VTT/SRT files and resets language/label fields.</div>
|
||
</div>
|
||
<div class="col-12 col-lg-4">
|
||
<label for="edit_subtitle_lang" class="form-label" data-i18n="admin.movies.subtitleLang">Language (e.g.: fr, en)</label>
|
||
<input type="text" class="form-control" id="edit_subtitle_lang" placeholder="fr">
|
||
</div>
|
||
<div class="col-12 col-lg-4">
|
||
<label for="edit_subtitle_label" class="form-label" data-i18n="admin.movies.subtitleLabel">Label (displayed)</label>
|
||
<input type="text" class="form-control" id="edit_subtitle_label" placeholder="Français">
|
||
</div>
|
||
<div class="col-12 col-lg-4 d-flex align-items-end">
|
||
<div class="form-check">
|
||
<input class="form-check-input" type="checkbox" id="edit_subtitle_default">
|
||
<label class="form-check-label" for="edit_subtitle_default" data-i18n="admin.movies.subtitleDefault">Default</label>
|
||
</div>
|
||
</div>
|
||
<div class="col-12">
|
||
<label for="edit_embed_src" class="form-label" data-i18n="admin.movies.embedUrl">Embed URL or iframe code</label>
|
||
<textarea class="form-control font-monospace" id="edit_embed_src" rows="3" placeholder="https://... OR <iframe ...>"></textarea>
|
||
</div>
|
||
<div class="col-12">
|
||
<label for="edit_vod_link" class="form-label" data-i18n="admin.movies.legacyVodLink">Legacy VOD link (optional)</label>
|
||
<input type="text" class="form-control" id="edit_vod_link" placeholder="https://...">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal" data-i18n="common.cancel">Cancel</button>
|
||
<button type="button" class="btn btn-primary" id="save-movie-changes" data-i18n="common.save">Save</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Footer Section -->
|
||
<footer>
|
||
<p id="footer-copy"><span data-i18n="footer.oscarPool">Oscar Pool</span> <span aria-hidden="true">·</span> <a id="copy" href="https://github.com/quoije/oscars-pool">quoije</a></p>
|
||
<p id="footer-text"></p>
|
||
</footer>
|
||
|
||
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
|
||
<script src="./js/app_control.js"></script>
|
||
<script src="./js/app_version.js"></script>
|
||
|
||
</body>
|
||
</html>
|