feat: integrate watchlist as tab on /web page

This commit is contained in:
root
2026-02-26 16:06:21 +00:00
parent 36ec4a0eee
commit e22bc4191c
8 changed files with 494 additions and 358 deletions
+24 -2
View File
@@ -1,5 +1,5 @@
from fastapi import FastAPI, UploadFile, File, BackgroundTasks, HTTPException, Query, Request, Depends, status
from fastapi.responses import StreamingResponse, FileResponse, JSONResponse, Response
from fastapi.responses import StreamingResponse, FileResponse, JSONResponse, Response, RedirectResponse
from fastapi.responses import HTMLResponse
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
@@ -359,7 +359,29 @@ async def login_page(request: Request):
@app.get("/watchlist")
async def watchlist_page(request: Request):
async def watchlist_redirect():
"""Redirect /watchlist to web interface with watchlist hash"""
return RedirectResponse("/web#watchlist")
#JJ|# API Endpoints
#WY|@app.post("/api/download")
#JJ|# API Endpoints
#WY|@app.post("/api/download")
async def create_download(request: DownloadRequest, background_tasks: BackgroundTasks):
#JJ|# API Endpoints
#WY|@app.post("/api/download")
#JJ|# API Endpoints
#WY|@app.post("/api/download")
#JJ|# API Endpoints
#WY|@app.post("/api/download")
#JJ|# API Endpoints
#JJ|# API Endpoints
"""Watchlist management page"""
return templates.TemplateResponse("watchlist.html", {"request": request})
+187
View File
@@ -1366,3 +1366,190 @@
justify-content: flex-start;
}
}
/* ===================================
Watchlist Page Styles
=================================== */
.watchlist-body {
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
min-height: 100vh;
color: #e0e0e0;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
.watchlist-header {
background: rgba(217, 255, 0.1);
border: 1px solid rgba(0, 217, 255, 0.3);
border-radius: 12px;
padding: 30px;
margin-bottom: 30px;
text-align: center;
}
.watchlist-header h1 {
color: #00d9ff;
margin: 0 0 10px 0;
font-size: 28px;
font-weight: 600;
}
.watchlist-header p {
color: #999;
margin: 0;
font-size: 14px;
}
.watchlist-controls {
display: flex;
gap: 15px;
justify-content: center;
margin-bottom: 30px;
flex-wrap: wrap;
}
.watchlist-container {
max-width: 1200px;
margin: 0 auto;
padding: 0 20px 40px;
}
.scheduler-status {
background: rgba(0, 217, 255, 0.05);
border: 1px solid rgba(0, 217, 255, 0.2);
border-radius: 10px;
padding: 20px;
margin-bottom: 30px;
}
.scheduler-status-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
}
.scheduler-status h3 {
margin: 0;
color: #00d9ff;
font-size: 18px;
}
.scheduler-controls {
display: flex;
gap: 10px;
}
.status-indicator {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 5px 12px;
border-radius: 12px;
font-size: 13px;
}
.status-indicator.running {
background: rgba(76, 175, 80, 0.2);
color: #4caf50;
}
.status-indicator.stopped {
background: rgba(244, 67, 54, 0.2);
color: #f44;
}
.status-dot {
width: 8px;
height: 8px;
border-radius: 50%;
}
.status-dot.running {
background: #4caf50;
animation: watchlist-pulse 2s infinite;
}
.status-dot.stopped {
background: #f44;
}
@keyframes watchlist-pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
.next-run-info {
font-size: 13px;
color: #999;
margin-top: 10px;
}
.filter-tabs {
display: flex;
gap: 10px;
margin-bottom: 20px;
justify-content: center;
}
.filter-tab {
padding: 8px 16px;
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 8px;
color: #ccc;
cursor: pointer;
transition: all 0.2s;
}
.filter-tab:hover {
background: rgba(255, 255, 255, 0.1);
}
.filter-tab.active {
background: rgba(0, 217, 255, 0.2);
border-color: rgba(0, 217, 255, 0.5);
color: #00d9ff;
}
.watchlist-loading {
text-align: center;
padding: 60px;
color: #999;
}
.empty-watchlist {
text-align: center;
padding: 80px 20px;
}
.watchlist-error-message {
text-align: center;
padding: 40px;
color: #f44;
}
.watchlist-item {
transition: all 0.3s ease;
}
.watchlist-item:hover {
background: rgba(255, 255, 255, 0.08);
transform: translateY(-2px);
}
.watchlist-btn-small {
padding: 6px 12px;
font-size: 12px;
}
.watchlist-header-back-btn {
margin-top: 15px;
}
.watchlist-modal-action-btn {
flex: 1;
padding: 12px;
font-size: 14px;
cursor: pointer;
}
+3
View File
@@ -228,6 +228,9 @@ function switchTab(tabName) {
} else if (tabType === 'providers' && tabName === 'providers') {
// Static providers tab
btn.classList.add('active');
} else if (tabType === 'watchlist' && tabName === 'watchlist') {
// Static watchlist tab
btn.classList.add('active');
} else if (tabType === 'anime' && btn.getAttribute('data-provider') === tabName.replace('anime-', '')) {
btn.classList.add('active');
} else if (tabType === 'series' && btn.getAttribute('data-provider') === tabName.replace('series-', '')) {
+33 -2
View File
@@ -147,6 +147,7 @@ async function loadSeriesRecommendations() {
} else {
container.innerHTML = '<div class="no-results">Aucune recommandation trouvée</div>';
}
} catch (error) {
console.error('Error loading series recommendations:', error);
const container = document.getElementById('seriesRecommendationsList');
@@ -360,10 +361,22 @@ window.showDownloadInfo = showDownloadInfo;
document.addEventListener('DOMContentLoaded', () => {
// Wait for main.js to be loaded
setTimeout(() => {
// Watchlist auto-refresh interval
let watchlistRefreshInterval = null;
// Initialize watchlist tab flag
window.watchlistTabLoaded = false;
// Override switchTab to load content when opening new tabs
const originalSwitchTab = window.switchTab;
if (originalSwitchTab) {
window.switchTab = function(tabName) {
// Clear watchlist interval when switching away from watchlist
if (tabName !== 'watchlist' && watchlistRefreshInterval) {
clearInterval(watchlistRefreshInterval);
watchlistRefreshInterval = null;
}
// Call original switchTab first
originalSwitchTab(tabName);
@@ -386,8 +399,26 @@ document.addEventListener('DOMContentLoaded', () => {
window.providersTabLoaded = true;
}
} else if (tabName === 'watchlist') {
// Watchlist is handled by its own page
window.location.href = '/watchlist';
// Clear any existing interval before starting new one
if (watchlistRefreshInterval) {
clearInterval(watchlistRefreshInterval);
watchlistRefreshInterval = null;
}
if (!window.watchlistTabLoaded) {
// Load watchlist content
if (typeof displayWatchlist === 'function') {
displayWatchlist();
}
window.watchlistTabLoaded = true;
}
// Start 30-second auto-refresh interval for watchlist
if (typeof displayWatchlist === 'function') {
watchlistRefreshInterval = setInterval(() => {
displayWatchlist();
}, 30000);
}
}
}, 100);
};
+166
View File
@@ -321,6 +321,169 @@ async function handleCheckAll() {
}
}
/**
* Create settings modal HTML
*/
function createSettingsModal(settings) {
const modalHtml = `
<div id="settingsModal" style="position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.8); display: flex; align-items: center; justify-content: center; z-index: 1000;">
<div style="background: linear-gradient(135deg, #1e1e2e 0%, #2d1b69 100%); border-radius: 16px; padding: 30px; max-width: 500px; width: 90%; border: 1px solid rgba(0, 217, 255, 0.3);">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 25px;">
<h2 style="margin: 0; color: #00d9ff;">⚙️ Paramètres Watchlist</h2>
<button onclick="closeSettingsModal()" style="background: none; border: none; color: #999; font-size: 24px; cursor: pointer;">×</button>
</div>
<div style="display: flex; flex-direction: column; gap: 20px;">
<!-- Check Interval -->
<div>
<label style="display: block; color: #fff; margin-bottom: 8px; font-weight: 500;">
🔄 Fréquence de vérification (heures)
</label>
<input type="number" id="checkInterval" value="${settings.check_interval_hours}" min="1" max="168"
style="width: 100%; padding: 10px; background: rgba(0,0,0,0.3); border: 1px solid rgba(255,255,255,0.2); border-radius: 8px; color: #fff; font-size: 14px;">
<p style="font-size: 12px; color: #999; margin-top: 5px;">Entre 1 et 168 heures (1 semaine)</p>
</div>
<!-- Auto-download enabled -->
<div style="display: flex; align-items: center; justify-content: space-between;">
<div>
<div style="color: #fff; font-weight: 500;">📥 Téléchargement automatique</div>
<p style="font-size: 12px; color: #999; margin: 0;">Télécharger automatiquement les nouveaux épisodes</p>
</div>
<label class="switch">
<input type="checkbox" id="autoDownloadEnabled" ${settings.auto_download_enabled ? 'checked' : ''}>
<span class="slider"></span>
</label>
</div>
<!-- Max concurrent downloads -->
<div>
<label style="display: block; color: #fff; margin-bottom: 8px; font-weight: 500;">
⚡ Téléchargements simultanés max
</label>
<input type="number" id="maxConcurrent" value="${settings.max_concurrent_auto_downloads}" min="1" max="5"
style="width: 100%; padding: 10px; background: rgba(0,0,0,0.3); border: 1px solid rgba(255,255,255,0.2); border-radius: 8px; color: #fff; font-size: 14px;">
<p style="font-size: 12px; color: #999; margin-top: 5px;">Maximum 5 téléchargements en parallèle</p>
</div>
<!-- Notifications -->
<div style="display: flex; align-items: center; justify-content: space-between;">
<div>
<div style="color: #fff; font-weight: 500;">🔔 Notifications</div>
<p style="font-size: 12px; color: #999; margin: 0;">Être notifié des nouveaux épisodes</p>
</div>
<label class="switch">
<input type="checkbox" id="notifyEnabled" ${settings.notify_on_new_episodes ? 'checked' : ''}>
<span class="slider"></span>
</label>
</div>
</div>
<div style="display: flex; gap: 10px; margin-top: 30px;">
<button class="btn-primary modal-action-btn" onclick="saveSettings()">
💾 Enregistrer
</button>
<button class="btn-secondary modal-action-btn" onclick="closeSettingsModal()">
Annuler
</button>
</div>
</div>
</div>
<style>
.switch {
position: relative;
display: inline-block;
width: 50px;
height: 26px;
}
.switch input {
opacity: 0;
width: 0;
height: 0;
}
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(255,255,255,0.2);
transition: .4s;
border-radius: 26px;
}
.slider:before {
position: absolute;
content: "";
height: 20px;
width: 20px;
left: 3px;
bottom: 3px;
background-color: white;
transition: .4s;
border-radius: 50%;
}
input:checked + .slider {
background-color: #00d9ff;
}
input:checked + .slider:before {
transform: translateX(24px);
}
</style>
`;
return modalHtml;
}
/**
* Close settings modal
*/
function closeSettingsModal() {
const modal = document.getElementById('settingsModal');
if (modal) {
modal.remove();
}
}
/**
* Save settings
*/
async function saveSettings() {
try {
const checkInterval = parseInt(document.getElementById('checkInterval').value);
const autoDownloadEnabled = document.getElementById('autoDownloadEnabled').checked;
const maxConcurrent = parseInt(document.getElementById('maxConcurrent').value);
const notifyEnabled = document.getElementById('notifyEnabled').checked;
const settings = {
check_interval_hours: checkInterval,
auto_download_enabled: autoDownloadEnabled,
max_concurrent_auto_downloads: maxConcurrent,
notify_on_new_episodes: notifyEnabled
};
await updateWatchlistSettings(settings);
// Restart scheduler if it's running to apply new interval
const status = await getSchedulerStatus();
if (status.running) {
await stopScheduler();
await startScheduler();
}
closeSettingsModal();
alert('✅ Paramètres enregistrés avec succès!');
await loadSchedulerStatus();
} catch (error) {
console.error('Error saving settings:', error);
alert(`❌ Erreur: ${error.message}`);
}
}
// Make functions available globally
window.displayWatchlist = displayWatchlist;
window.handleAddToWatchlist = handleAddToWatchlist;
@@ -329,3 +492,6 @@ window.handleResumeWatchlist = handleResumeWatchlist;
window.handleCheckItem = handleCheckItem;
window.handleDeleteWatchlist = handleDeleteWatchlist;
window.handleCheckAll = handleCheckAll;
window.createSettingsModal = createSettingsModal;
window.closeSettingsModal = closeSettingsModal;
window.saveSettings = saveSettings;
@@ -0,0 +1,46 @@
<!-- Watchlist Section: Scheduler, Filters & Items -->
<!-- Header -->
<div class="watchlist-header">
<h1>📋 Ma Watchlist</h1>
<p>Suivez vos animes préférés et téléchargez automatiquement les nouveaux épisodes</p>
<button type="button" class="btn-secondary header-back-btn" onclick="window.location.href = '/web'">
← Retour à l'accueil
</button>
</div>
<!-- Scheduler Status -->
<div class="scheduler-status" id="schedulerStatus">
<div class="scheduler-status-header">
<div>
<h3>⏰ Planificateur Automatique</h3>
<div id="nextRunInfo" class="next-run-info">Chargement...</div>
</div>
<div class="scheduler-controls">
<button id="startSchedulerBtn" class="btn-primary btn-small" onclick="handleStartScheduler()" style="display:none;">
▶️ Démarrer
</button>
<button id="stopSchedulerBtn" class="btn-secondary btn-small" onclick="handleStopScheduler()" style="display:none;">
⏸️ Arrêter
</button>
<button class="btn-secondary btn-small" onclick="handleCheckAll()">
🔍 Vérifier tout
</button>
<button class="btn-secondary btn-small" onclick="handleOpenSettings()">
⚙️ Paramètres
</button>
</div>
</div>
</div>
<!-- Filter Tabs -->
<div class="filter-tabs">
<button class="filter-tab active" onclick="filterWatchlist('all', this)">Tous</button>
<button class="filter-tab" onclick="filterWatchlist('active', this)">Actifs</button>
<button class="filter-tab" onclick="filterWatchlist('paused', this)">En pause</button>
<button class="filter-tab" onclick="filterWatchlist('completed', this)">Terminés</button>
</div>
<!-- Watchlist Items -->
<div id="watchlistContainer">
<div class="loading">Chargement de la watchlist...</div>
</div>
+4
View File
@@ -112,6 +112,10 @@
<div id="providersGrid" class="search-results"></div>
</div>
<div id="tab-watchlist" class="tab-content">
{% include "components/watchlist_section.html" %}
</div>
{% include "components/downloads_section.html" %}
</div>
+29 -352
View File
@@ -5,204 +5,36 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Watchlist - Ohm Stream Downloader</title>
<link rel="stylesheet" href="/static/css/style.css">
<style>
body {
background: linear-gradient(135deg, #1e1e2e 0%, #2d1b69 0%, #1e1e2e 100%);
min-height: 100vh;
color: #e0e0e0;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
.watchlist-header {
background: rgba(0, 217, 255, 0.1);
border: 1px solid rgba(0, 217, 255, 0.3);
border-radius: 12px;
padding: 30px;
margin-bottom: 30px;
text-align: center;
}
.watchlist-header h1 {
color: #00d9ff;
margin: 0 0 10px 0;
font-size: 28px;
font-weight: 600;
}
.watchlist-header p {
color: #999;
margin: 0;
font-size: 14px;
}
.watchlist-controls {
display: flex;
gap: 15px;
justify-content: center;
margin-bottom: 30px;
flex-wrap: wrap;
}
.watchlist-container {
max-width: 1200px;
margin: 0 auto;
padding: 0 20px 40px;
}
.scheduler-status {
background: rgba(0, 217, 255, 0.05);
border: 1px solid rgba(0, 217, 255, 0.2);
border-radius: 10px;
padding: 20px;
margin-bottom: 30px;
}
.scheduler-status-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
}
.scheduler-status h3 {
margin: 0;
color: #00d9ff;
font-size: 18px;
}
.scheduler-controls {
display: flex;
gap: 10px;
}
.status-indicator {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 5px 12px;
border-radius: 12px;
font-size: 13px;
}
.status-indicator.running {
background: rgba(76, 175, 80, 0.2);
color: #4caf50;
}
.status-indicator.stopped {
background: rgba(244, 67, 54, 0.2);
color: #f44;
}
.status-dot {
width: 8px;
height: 8px;
border-radius: 50%;
}
.status-dot.running {
background: #4caf50;
animation: pulse 2s infinite;
}
.status-dot.stopped {
background: #f44;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
.next-run-info {
font-size: 13px;
color: #999;
margin-top: 10px;
}
.filter-tabs {
display: flex;
gap: 10px;
margin-bottom: 20px;
justify-content: center;
}
.filter-tab {
padding: 8px 16px;
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 8px;
color: #ccc;
cursor: pointer;
transition: all 0.2s;
}
.filter-tab:hover {
background: rgba(255, 255, 255, 0.1);
}
.filter-tab.active {
background: rgba(0, 217, 255, 0.2);
border-color: rgba(0, 217, 255, 0.5);
color: #00d9ff;
}
.loading {
text-align: center;
padding: 60px;
color: #999;
}
.empty-watchlist {
text-align: center;
padding: 80px 20px;
}
.error-message {
text-align: center;
padding: 40px;
color: #f44;
}
.watchlist-item {
transition: all 0.3s ease;
}
.watchlist-item:hover {
background: rgba(255, 255, 255, 0.08);
transform: translateY(-2px);
}
.btn-small {
padding: 6px 12px;
font-size: 12px;
}
.header-back-btn {
margin-top: 15px;
}
.modal-action-btn {
flex: 1;
padding: 12px;
font-size: 14px;
cursor: pointer;
}
/* Filter tabs at top */
.watchlist-header-filter {
margin-top: 20px;
}
</style>
</head>
<body>
<body class="watchlist-body">
<!-- Main Header -->
<div style="text-align: center; margin-bottom: 20px;">
<h1 style="background: linear-gradient(45deg, #00d9ff, #00ff88); -webkit-background-clip: text; -webkit-text-fill-color: transparent; font-size: 32px; margin: 0;">⚡ Ohm Stream Downloader</h1>
<p style="color: #888; font-size: 14px; margin: 5px 0 0;">Téléchargez vos vidéos, animes et séries</p>
</div>
<!-- User Info -->
<div id="userInfo" style="display: none; max-width: 1200px; margin: 0 auto 15px; padding: 10px; background: rgba(0,217,255,0.1); border-radius: 8px; display: flex; justify-content: space-between; align-items: center;">
<span style="color: #00d9ff;">👤 Connecté</span>
<button class="btn-secondary btn-small" onclick="handleLogout()">🚪 Déconnexion</button>
</div>
<!-- Tabs -->
<div class="tabs" style="max-width: 1200px; margin: 0 auto 20px; display: flex; gap: 5px; border-bottom: 1px solid rgba(255,255,255,0.1); padding-bottom: 10px;">
<button class="tab" onclick="window.location.href='/web'">🏠 Accueil</button>
<button class="tab" onclick="window.location.href='/web#anime'">🎬 Anime</button>
<button class="tab" onclick="window.location.href='/web#series'">📺 Série</button>
<button class="tab" onclick="window.location.href='/web#providers'">📦 Fournisseurs</button>
<button class="tab active" onclick="window.location.href='/watchlist'">📋 Watchlist</button>
</div>
<div class="watchlist-container">
<!-- Header -->
<div class="watchlist-header">
<h1>📋 Ma Watchlist</h1>
<p>Suivez vos animes préférés et téléchargez automatiquement les nouveaux épisodes</p>
<button type="button" class="btn-secondary header-back-btn" onclick="window.location.href = '/web'">
<button type="button" class="btn-secondary watchlist-header-back-btn" onclick="window.location.href = '/web'">
← Retour à l'accueil
</button>
</div>
@@ -215,16 +47,16 @@
<div id="nextRunInfo" class="next-run-info">Chargement...</div>
</div>
<div class="scheduler-controls">
<button id="startSchedulerBtn" class="btn-primary btn-small" onclick="handleStartScheduler()" style="display:none;">
<button id="startSchedulerBtn" class="btn-primary btn-small watchlist-btn-small" onclick="handleStartScheduler()" style="display:none;">
▶️ Démarrer
</button>
<button id="stopSchedulerBtn" class="btn-secondary btn-small" onclick="handleStopScheduler()" style="display:none;">
<button id="stopSchedulerBtn" class="btn-secondary btn-small watchlist-btn-small" onclick="handleStopScheduler()" style="display:none;">
⏸️ Arrêter
</button>
<button class="btn-secondary btn-small" onclick="handleCheckAll()">
<button class="btn-secondary btn-small watchlist-btn-small" onclick="handleCheckAll()">
🔍 Vérifier tout
</button>
<button class="btn-secondary btn-small" onclick="handleOpenSettings()">
<button class="btn-secondary btn-small watchlist-btn-small" onclick="handleOpenSettings()">
⚙️ Paramètres
</button>
</div>
@@ -241,7 +73,7 @@
<!-- Watchlist Items -->
<div id="watchlistContainer">
<div class="loading">Chargement de la watchlist...</div>
<div class="watchlist-loading">Chargement de la watchlist...</div>
</div>
</div>
@@ -394,173 +226,18 @@
async function handleOpenSettings() {
try {
const settings = await getWatchlistSettings();
// Create modal HTML
const modalHtml = `
<div id="settingsModal" style="position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.8); display: flex; align-items: center; justify-content: center; z-index: 1000;">
<div style="background: linear-gradient(135deg, #1e1e2e 0%, #2d1b69 100%); border-radius: 16px; padding: 30px; max-width: 500px; width: 90%; border: 1px solid rgba(0, 217, 255, 0.3);">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 25px;">
<h2 style="margin: 0; color: #00d9ff;">⚙️ Paramètres Watchlist</h2>
<button onclick="closeSettingsModal()" style="background: none; border: none; color: #999; font-size: 24px; cursor: pointer;">×</button>
</div>
<div style="display: flex; flex-direction: column; gap: 20px;">
<!-- Check Interval -->
<div>
<label style="display: block; color: #fff; margin-bottom: 8px; font-weight: 500;">
🔄 Fréquence de vérification (heures)
</label>
<input type="number" id="checkInterval" value="${settings.check_interval_hours}" min="1" max="168"
style="width: 100%; padding: 10px; background: rgba(0,0,0,0.3); border: 1px solid rgba(255,255,255,0.2); border-radius: 8px; color: #fff; font-size: 14px;">
<p style="font-size: 12px; color: #999; margin-top: 5px;">Entre 1 et 168 heures (1 semaine)</p>
</div>
<!-- Auto-download enabled -->
<div style="display: flex; align-items: center; justify-content: space-between;">
<div>
<div style="color: #fff; font-weight: 500;">📥 Téléchargement automatique</div>
<p style="font-size: 12px; color: #999; margin: 0;">Télécharger automatiquement les nouveaux épisodes</p>
</div>
<label class="switch">
<input type="checkbox" id="autoDownloadEnabled" ${settings.auto_download_enabled ? 'checked' : ''}>
<span class="slider"></span>
</label>
</div>
<!-- Max concurrent downloads -->
<div>
<label style="display: block; color: #fff; margin-bottom: 8px; font-weight: 500;">
⚡ Téléchargements simultanés max
</label>
<input type="number" id="maxConcurrent" value="${settings.max_concurrent_auto_downloads}" min="1" max="5"
style="width: 100%; padding: 10px; background: rgba(0,0,0,0.3); border: 1px solid rgba(255,255,255,0.2); border-radius: 8px; color: #fff; font-size: 14px;">
<p style="font-size: 12px; color: #999; margin-top: 5px;">Maximum 5 téléchargements en parallèle</p>
</div>
<!-- Notifications -->
<div style="display: flex; align-items: center; justify-content: space-between;">
<div>
<div style="color: #fff; font-weight: 500;">🔔 Notifications</div>
<p style="font-size: 12px; color: #999; margin: 0;">Être notifié des nouveaux épisodes</p>
</div>
<label class="switch">
<input type="checkbox" id="notifyEnabled" ${settings.notify_on_new_episodes ? 'checked' : ''}>
<span class="slider"></span>
</label>
</div>
</div>
<div style="display: flex; gap: 10px; margin-top: 30px;">
<button class="btn-primary modal-action-btn" onclick="saveSettings()">
💾 Enregistrer
</button>
<button class="btn-secondary modal-action-btn" onclick="closeSettingsModal()">
Annuler
</button>
</div>
</div>
</div>
<style>
.switch {
position: relative;
display: inline-block;
width: 50px;
height: 26px;
}
.switch input {
opacity: 0;
width: 0;
height: 0;
}
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(255,255,255,0.2);
transition: .4s;
border-radius: 26px;
}
.slider:before {
position: absolute;
content: "";
height: 20px;
width: 20px;
left: 3px;
bottom: 3px;
background-color: white;
transition: .4s;
border-radius: 50%;
}
input:checked + .slider {
background-color: #00d9ff;
}
input:checked + .slider:before {
transform: translateX(24px);
}
</style>
`;
const modalHtml = createSettingsModal(settings);
// Add modal to body
const modalContainer = document.createElement('div');
modalContainer.innerHTML = modalHtml;
document.body.appendChild(modalContainer);
} catch (error) {
console.error('Error loading settings:', error);
alert(`❌ Erreur: ${error.message}`);
}
}
/**
* Close settings modal
*/
function closeSettingsModal() {
const modal = document.getElementById('settingsModal');
if (modal) {
modal.remove();
}
}
/**
* Save settings
*/
async function saveSettings() {
try {
const checkInterval = parseInt(document.getElementById('checkInterval').value);
const autoDownloadEnabled = document.getElementById('autoDownloadEnabled').checked;
const maxConcurrent = parseInt(document.getElementById('maxConcurrent').value);
const notifyEnabled = document.getElementById('notifyEnabled').checked;
const settings = {
check_interval_hours: checkInterval,
auto_download_enabled: autoDownloadEnabled,
max_concurrent_auto_downloads: maxConcurrent,
notify_on_new_episodes: notifyEnabled
};
await updateWatchlistSettings(settings);
// Restart scheduler if it's running to apply new interval
const status = await getSchedulerStatus();
if (status.running) {
await stopScheduler();
await startScheduler();
}
closeSettingsModal();
alert('✅ Paramètres enregistrés avec succès!');
await loadSchedulerStatus();
} catch (error) {
console.error('Error saving settings:', error);
alert(`❌ Erreur: ${error.message}`);
}
}
// Auto-refresh scheduler status every 30 seconds
setInterval(loadSchedulerStatus, 30000);
</script>