Initial commit

This commit is contained in:
2026-03-23 20:59:26 +01:00
commit 16c95f747b
56 changed files with 21177 additions and 0 deletions

880
app/templates/admin.html Normal file
View File

@@ -0,0 +1,880 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Lycostorrent - Administration</title>
<!-- Chargement du thème (en premier pour éviter le flash) -->
<script src="/static/js/theme-loader.js"></script>
<!-- PWA Meta Tags -->
<meta name="theme-color" content="#e63946">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="apple-mobile-web-app-title" content="Lycostorrent">
<!-- PWA Manifest -->
<link rel="manifest" href="/static/manifest.json">
<!-- Icons -->
<link rel="icon" type="image/png" sizes="192x192" href="/static/icons/icon-192x192.png">
<link rel="apple-touch-icon" href="/static/icons/icon-192x192.png">
<link rel="stylesheet" href="/static/css/style.css">
<link rel="stylesheet" href="/static/css/themes.css">
<link rel="stylesheet" href="/static/css/admin.css">
</head>
<body>
<div class="container">
<!-- Header -->
<header class="header">
<h1>⚙️ Administration</h1>
<p class="subtitle">Configuration de Lycostorrent</p>
<nav class="main-nav" id="mainNav">
<!-- Navigation générée dynamiquement -->
</nav>
</header>
<!-- Onglets -->
<div class="admin-tabs">
<button class="tab-btn active" data-tab="modules">
<span class="tab-icon">🧩</span>
<span class="tab-label">Modules</span>
</button>
<button class="tab-btn" data-tab="categories">
<span class="tab-icon">📂</span>
<span class="tab-label">Catégories</span>
</button>
<button class="tab-btn" data-tab="tags">
<span class="tab-icon">🏷️</span>
<span class="tab-label">Tags</span>
</button>
<button class="tab-btn" data-tab="filters">
<span class="tab-icon">🎛️</span>
<span class="tab-label">Filtres</span>
</button>
<button class="tab-btn" data-tab="rss">
<span class="tab-icon">📡</span>
<span class="tab-label">Flux RSS</span>
</button>
<button class="tab-btn" data-tab="cache">
<span class="tab-icon">🔄</span>
<span class="tab-label">Cache</span>
</button>
<button class="tab-btn" data-tab="torrent-client">
<span class="tab-icon">⬇️</span>
<span class="tab-label">Client Torrent</span>
</button>
<button class="tab-btn" data-tab="appearance">
<span class="tab-icon">🎨</span>
<span class="tab-label">Apparence</span>
</button>
</div>
<!-- Contenu des onglets -->
<div class="admin-content">
<!-- ═══════════════════════════════════════════════════════════
ONGLET MODULES
═══════════════════════════════════════════════════════════ -->
<div id="tab-modules" class="tab-content active">
<div class="tab-header">
<h2>🧩 Modules</h2>
<p>Activez ou désactivez les fonctionnalités de Lycostorrent.</p>
</div>
<div class="admin-card">
<h3>📱 Fonctionnalités disponibles</h3>
<p class="help-text">Cochez les modules que vous souhaitez activer. La navigation s'adaptera automatiquement.</p>
<div class="modules-list">
<div class="module-item">
<div class="module-toggle">
<input type="checkbox" id="module-search" checked>
<label for="module-search"></label>
</div>
<div class="module-info">
<div class="module-header">
<span class="module-icon">🔍</span>
<span class="module-name">Recherche</span>
</div>
<p class="module-description">Recherche de torrents sur vos trackers configurés (Jackett/Prowlarr).</p>
</div>
</div>
<div class="module-item">
<div class="module-toggle">
<input type="checkbox" id="module-latest" checked>
<label for="module-latest"></label>
</div>
<div class="module-info">
<div class="module-header">
<span class="module-icon">🎬</span>
<span class="module-name">Nouveautés</span>
</div>
<p class="module-description">Dernières sorties depuis vos trackers (Films, Séries, Anime, Musique).</p>
</div>
</div>
<div class="module-item">
<div class="module-toggle">
<input type="checkbox" id="module-discover">
<label for="module-discover"></label>
</div>
<div class="module-info">
<div class="module-header">
<span class="module-icon">🌟</span>
<span class="module-name">Découvrir</span>
<span class="module-badge">Nouveau</span>
</div>
<p class="module-description">Explorez les nouveautés cinéma et TV depuis TMDb, puis trouvez les torrents disponibles.</p>
</div>
</div>
</div>
<div class="action-bar">
<button id="saveModulesBtn" class="btn btn-primary">💾 Sauvegarder</button>
</div>
</div>
<!-- Configuration des trackers pour Discover -->
<div class="admin-card" id="discoverTrackersCard">
<h3>🌟 Trackers pour Découvrir</h3>
<p class="help-text">Sélectionnez les trackers à utiliser pour la recherche de torrents dans la page Découvrir.</p>
<div class="discover-trackers-list" id="discoverTrackersList">
<p class="loading">Chargement des trackers...</p>
</div>
<div class="action-bar">
<button id="selectAllDiscoverTrackers" class="btn btn-secondary">✅ Tout sélectionner</button>
<button id="selectNoneDiscoverTrackers" class="btn btn-secondary">❌ Tout désélectionner</button>
<button id="saveDiscoverTrackers" class="btn btn-primary">💾 Sauvegarder</button>
</div>
</div>
<div class="admin-card">
<h3> À propos des modules</h3>
<div class="module-help">
<p><strong>🔍 Recherche</strong> : Page d'accueil par défaut. Permet de rechercher des torrents par mots-clés sur tous vos trackers.</p>
<p><strong>🎬 Nouveautés</strong> : Affiche les derniers torrents publiés sur vos trackers, enrichis avec les métadonnées TMDb/Last.fm.</p>
<p><strong>🌟 Découvrir</strong> : Inverse la logique - part des nouveautés TMDb (films/séries récents) et recherche les torrents disponibles.</p>
</div>
</div>
</div>
<!-- ═══════════════════════════════════════════════════════════
ONGLET CATÉGORIES
═══════════════════════════════════════════════════════════ -->
<div id="tab-categories" class="tab-content">
<div class="tab-header">
<h2>📂 Configuration des Catégories</h2>
<p>Associez les catégories Jackett/Prowlarr à chaque type de contenu pour chaque tracker.</p>
</div>
<!-- Sélection du tracker -->
<div class="admin-card">
<h3>1. Sélectionner un tracker</h3>
<div id="trackerSelector" class="tracker-grid">
<p class="loading">Chargement des trackers...</p>
</div>
</div>
<!-- Catégories disponibles -->
<div id="categoriesSection" class="admin-card hidden">
<h3>2. Catégories disponibles sur <span id="selectedTrackerName" class="highlight"></span></h3>
<div id="availableCategories" class="categories-cloud">
<p class="loading">Chargement...</p>
</div>
</div>
<!-- Configuration -->
<div id="configSection" class="admin-card hidden">
<h3>3. Associer les catégories</h3>
<p class="help-text">Cliquez sur une catégorie ci-dessus pour l'ajouter, ou entrez les IDs manuellement.</p>
<div class="config-grid">
<div class="config-item">
<label>🎥 Films</label>
<input type="text" id="config-movies" placeholder="Ex: 2000,2010">
<div class="quick-add" data-target="movies"></div>
</div>
<div class="config-item">
<label>📺 Séries</label>
<input type="text" id="config-tv" placeholder="Ex: 5000,5010">
<div class="quick-add" data-target="tv"></div>
</div>
<div class="config-item">
<label>🎌 Anime</label>
<input type="text" id="config-anime" placeholder="Ex: 5070">
<div class="quick-add" data-target="anime"></div>
</div>
<div class="config-item">
<label>🎵 Musique</label>
<input type="text" id="config-music" placeholder="Ex: 3000">
<div class="quick-add" data-target="music"></div>
</div>
</div>
<div class="action-bar">
<button id="saveConfigBtn" class="btn btn-primary">💾 Sauvegarder</button>
<button id="resetConfigBtn" class="btn btn-secondary">🔄 Réinitialiser</button>
</div>
</div>
<!-- Résumé -->
<div class="admin-card">
<h3>📋 Configuration actuelle</h3>
<div id="configSummary" class="config-summary">
<p class="loading">Chargement...</p>
</div>
</div>
</div>
<!-- ═══════════════════════════════════════════════════════════
ONGLET TAGS
═══════════════════════════════════════════════════════════ -->
<div id="tab-tags" class="tab-content">
<div class="tab-header">
<h2>🏷️ Tags de Parsing</h2>
<p>Ces tags sont utilisés pour nettoyer les titres avant la recherche TMDb/Last.fm.</p>
</div>
<!-- Tags actuels -->
<div class="admin-card">
<h3>Tags de coupure</h3>
<p class="help-text">Le titre sera coupé au premier tag rencontré. Ex: <code>Avatar.2009.MULTi.1080p</code><code>Avatar 2009</code></p>
<div id="tagsList" class="tags-cloud editable">
<p class="loading">Chargement...</p>
</div>
<div class="add-form">
<input type="text" id="newTagInput" placeholder="Nouveau tag..." maxlength="30">
<button id="addTagBtn" class="btn btn-primary"> Ajouter</button>
</div>
<div class="action-bar">
<button id="saveTagsBtn" class="btn btn-primary">💾 Sauvegarder</button>
<button id="resetTagsBtn" class="btn btn-secondary">🔄 Réinitialiser</button>
</div>
</div>
<!-- Présets -->
<div class="admin-card">
<h3>📦 Ajouter des présets</h3>
<div class="presets-grid">
<button class="preset-btn" data-preset="langues">
🗣️ Langues
<small>VFF, VFQ, VOSTFR...</small>
</button>
<button class="preset-btn" data-preset="resolutions">
📺 Résolutions
<small>1080p, 720p, 4K...</small>
</button>
<button class="preset-btn" data-preset="sources">
📀 Sources
<small>BluRay, WEB, HDTV...</small>
</button>
<button class="preset-btn" data-preset="codecs">
🎬 Codecs
<small>x264, x265, HEVC...</small>
</button>
<button class="preset-btn" data-preset="audio">
🔊 Audio
<small>DTS, AC3, FLAC...</small>
</button>
</div>
</div>
<!-- Test -->
<div class="admin-card">
<h3>🧪 Tester le parsing</h3>
<div class="test-form">
<input type="text" id="testTitleInput" placeholder="Ex: Avatar.2009.MULTi.1080p.BluRay.x264">
<button id="testParsingBtn" class="btn btn-primary">Tester</button>
</div>
<div id="testResult" class="test-result hidden">
<div class="result-row">
<span class="label">Original:</span>
<span id="testOriginal" class="value"></span>
</div>
<div class="result-row success">
<span class="label">Nettoyé:</span>
<span id="testCleaned" class="value"></span>
</div>
</div>
</div>
</div>
<!-- ═══════════════════════════════════════════════════════════
ONGLET FILTRES
═══════════════════════════════════════════════════════════ -->
<div id="tab-filters" class="tab-content">
<div class="tab-header">
<h2>🎛️ Filtres de Recherche</h2>
<p>Configurez les mots-clés détectés dans les titres de torrents pour créer les filtres.</p>
</div>
<!-- Liste des filtres -->
<div class="admin-card">
<h3>Filtres configurés</h3>
<p class="help-text">Cliquez sur un filtre pour modifier ses valeurs. Les valeurs sont détectées dans les titres de torrents.</p>
<div id="filtersList" class="filters-editor">
<p class="loading">Chargement...</p>
</div>
<div class="action-bar">
<button id="saveFiltersBtn" class="btn btn-primary">💾 Sauvegarder</button>
<button id="resetFiltersBtn" class="btn btn-secondary">🔄 Réinitialiser</button>
<button id="addFilterBtn" class="btn btn-secondary"> Nouveau filtre</button>
</div>
</div>
<!-- Test -->
<div class="admin-card">
<h3>🧪 Tester la détection</h3>
<p class="help-text">Entrez un titre de torrent pour voir les filtres détectés.</p>
<div class="test-form">
<input type="text" id="testFilterInput" placeholder="Ex: Gojira.-.Fortitude.2021.FLAC.24bit.WEB.Album">
<button id="testFilterBtn" class="btn btn-primary">Tester</button>
</div>
<div id="filterTestResult" class="test-result hidden"></div>
</div>
<!-- Aide -->
<div class="admin-card collapsible collapsed">
<h3 class="collapsible-header">
❓ Aide - Comment ça marche
<span class="collapse-icon"></span>
</h3>
<div class="collapsible-content">
<div class="help-section">
<h4>Principe</h4>
<p>Le parser analyse les titres de torrents et cherche les mots-clés configurés. Chaque mot trouvé crée une option de filtre.</p>
<h4>Exemple</h4>
<p><code>Gojira.-.Fortitude.2021.FLAC.24bit.WEB.Album</code></p>
<p>→ Format Audio: <strong>FLAC, 24bit</strong> | Type: <strong>Album</strong> | Source: <strong>WEB</strong></p>
<h4>Conseils</h4>
<ul>
<li>Les mots-clés sont insensibles à la casse (FLAC = flac = Flac)</li>
<li>Évitez les mots trop courts ou trop communs</li>
<li>Testez vos modifications avant de sauvegarder</li>
</ul>
</div>
</div>
</div>
</div>
<!-- ═══════════════════════════════════════════════════════════
ONGLET RSS
═══════════════════════════════════════════════════════════ -->
<div id="tab-rss" class="tab-content">
<div class="tab-header">
<h2>📡 Flux RSS</h2>
<p>Ajoutez des flux RSS pour les trackers non disponibles dans Jackett/Prowlarr.</p>
</div>
<!-- Formulaire d'ajout -->
<div class="admin-card">
<h3> Ajouter un flux</h3>
<form id="add-feed-form" class="rss-form">
<div class="form-row">
<div class="form-group">
<label for="feed-name">Nom *</label>
<input type="text" id="feed-name" placeholder="Ex: YGG Films" required>
</div>
<div class="form-group">
<label for="feed-category">Catégorie *</label>
<select id="feed-category" required>
<option value="">-- Choisir --</option>
<option value="movies">🎬 Films</option>
<option value="tv">📺 Séries</option>
<option value="anime">🎌 Anime</option>
<option value="music">🎵 Musique</option>
<option value="all">📦 Toutes</option>
</select>
</div>
</div>
<div class="form-group">
<label for="feed-url">URL du flux RSS *</label>
<input type="url" id="feed-url" placeholder="https://tracker.xxx/rss?passkey={passkey}" required>
<small>Utilisez <code>{passkey}</code> comme placeholder</small>
</div>
<div class="form-group">
<label for="feed-passkey">Passkey</label>
<input type="text" id="feed-passkey" placeholder="Votre passkey privé">
</div>
<div class="form-row">
<div class="form-group checkbox-inline">
<label>
<input type="checkbox" id="feed-flaresolverr">
<span>🛡️ Flaresolverr</span>
</label>
<small>Anti-Cloudflare</small>
</div>
</div>
<div class="form-group">
<label for="feed-cookies">Cookies de session</label>
<textarea id="feed-cookies" rows="2" placeholder="ygg_=abc123; cf_clearance=xyz789"></textarea>
<small>Récupérez-les depuis DevTools (F12) → Application → Cookies</small>
</div>
<div class="action-bar">
<button type="button" id="test-feed-btn" class="btn btn-secondary">🧪 Tester</button>
<button type="submit" class="btn btn-primary"> Ajouter</button>
</div>
</form>
<div id="test-result" class="test-result hidden"></div>
</div>
<!-- Liste des flux -->
<div class="admin-card">
<h3>📋 Flux configurés</h3>
<div id="feeds-list" class="feeds-list">
<p class="loading">Chargement...</p>
</div>
</div>
<!-- Aide -->
<div class="admin-card collapsible">
<h3 class="collapsible-header">
❓ Aide - Comment configurer un flux RSS
<span class="collapse-icon"></span>
</h3>
<div class="collapsible-content">
<div class="help-section">
<h4>YGGTorrent</h4>
<ol>
<li>Connectez-vous à YGG</li>
<li>Profil → "Mon RSS"</li>
<li>Copiez l'URL avec votre passkey</li>
</ol>
<h4>Catégories YGG</h4>
<div class="help-table">
<span class="help-row"><code>id=2145</code> Films</span>
<span class="help-row"><code>id=2184</code> Séries</span>
<span class="help-row"><code>id=2179</code> Anime</span>
<span class="help-row"><code>id=2139</code> Musique</span>
</div>
</div>
</div>
</div>
</div>
<!-- ═══════════════════════════════════════════════════════════
ONGLET CACHE
═══════════════════════════════════════════════════════════ -->
<div id="tab-cache" class="tab-content">
<div class="tab-header">
<h2>🔄 Cache des données</h2>
<p>Pré-chargez les données Latest et Discover pour un affichage instantané.</p>
</div>
<!-- Statut du cache -->
<div class="admin-card">
<h3>📊 Statut du cache</h3>
<div id="cacheStatus" class="cache-status">
<div class="status-row">
<span class="status-label">État :</span>
<span id="cacheStatusBadge" class="status-badge">Chargement...</span>
</div>
<div class="status-row">
<span class="status-label">Dernier refresh :</span>
<span id="cacheLastRefresh">-</span>
</div>
<div class="status-row">
<span class="status-label">Prochain refresh :</span>
<span id="cacheNextRefresh">-</span>
</div>
<div class="status-row">
<span class="status-label">Taille du cache :</span>
<span id="cacheSizeDisplay">-</span>
</div>
</div>
<div class="action-bar" style="margin-top: 15px;">
<button id="refreshCacheBtn" class="btn btn-primary">🔄 Forcer le refresh maintenant</button>
<button id="clearCacheBtn" class="btn btn-secondary">🗑️ Vider le cache</button>
</div>
</div>
<!-- Configuration -->
<div class="admin-card">
<h3>⚙️ Configuration</h3>
<div class="form-row">
<div class="form-group">
<label for="cacheEnabled">
<input type="checkbox" id="cacheEnabled">
Activer le cache automatique
</label>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label for="cacheInterval">Intervalle de refresh</label>
<select id="cacheInterval">
<option value="15">15 minutes</option>
<option value="30">30 minutes</option>
<option value="60" selected>1 heure</option>
<option value="120">2 heures</option>
<option value="240">4 heures</option>
<option value="360">6 heures</option>
</select>
</div>
</div>
</div>
<!-- Configuration Latest -->
<div class="admin-card">
<h3>📥 Cache Latest (Nouveautés)</h3>
<div class="form-row">
<div class="form-group">
<label for="cacheLatestEnabled">
<input type="checkbox" id="cacheLatestEnabled" checked>
Activer le cache Latest
</label>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label>Catégories à cacher :</label>
<div class="checkbox-group">
<label><input type="checkbox" id="cacheLatestMovies" checked> 🎥 Films</label>
<label><input type="checkbox" id="cacheLatestTv" checked> 📺 Séries</label>
<label><input type="checkbox" id="cacheLatestAnime"> 🎌 Anime</label>
<label><input type="checkbox" id="cacheLatestMusic"> 🎵 Musique</label>
</div>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label for="cacheLatestLimit">Nombre de résultats par catégorie</label>
<select id="cacheLatestLimit">
<option value="30">30</option>
<option value="50" selected>50</option>
<option value="100">100</option>
</select>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label>Trackers à utiliser :</label>
<p class="help-text">Laissez vide pour utiliser tous les trackers actifs</p>
<div id="cacheTrackersList" class="checkbox-group trackers-checkboxes">
<!-- Rempli dynamiquement -->
</div>
</div>
</div>
</div>
<!-- Configuration Discover -->
<div class="admin-card">
<h3>🎬 Cache Discover</h3>
<div class="form-row">
<div class="form-group">
<label for="cacheDiscoverEnabled">
<input type="checkbox" id="cacheDiscoverEnabled" checked>
Activer le cache Discover
</label>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label for="cacheDiscoverLimit">Nombre de films/séries à cacher</label>
<select id="cacheDiscoverLimit">
<option value="20">20</option>
<option value="30" selected>30</option>
<option value="50">50</option>
</select>
</div>
</div>
</div>
<!-- Sauvegarder -->
<div class="admin-card">
<div class="action-bar">
<button id="saveCacheConfigBtn" class="btn btn-primary">💾 Sauvegarder la configuration</button>
</div>
</div>
</div>
<!-- ═══════════════════════════════════════════════════════════
ONGLET CLIENT TORRENT
═══════════════════════════════════════════════════════════ -->
<div id="tab-torrent-client" class="tab-content">
<div class="tab-header">
<h2>⬇️ Client Torrent</h2>
<p>Configurez votre client torrent pour envoyer directement les téléchargements.</p>
</div>
<!-- Statut -->
<div class="admin-card">
<h3>📊 Statut</h3>
<div id="torrentClientStatus" class="client-status">
<span class="status-loading">Chargement...</span>
</div>
</div>
<!-- Configuration -->
<div class="admin-card">
<h3>⚙️ Configuration</h3>
<div class="form-row">
<div class="form-group">
<label for="tcEnabled">
<input type="checkbox" id="tcEnabled">
Activer le client torrent
</label>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label for="tcPlugin">Client</label>
<select id="tcPlugin">
<option value="">-- Sélectionner --</option>
</select>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label for="tcHost">Hôte</label>
<input type="text" id="tcHost" placeholder="geco.useed.me ou 192.168.1.x">
<small class="form-help">Domaine ou IP (sans http://)</small>
</div>
<div class="form-group">
<label for="tcPort">Port</label>
<input type="number" id="tcPort" placeholder="8080 ou vide">
<small class="form-help">Laisser vide si port par défaut</small>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label for="tcPath">Chemin (optionnel)</label>
<input type="text" id="tcPath" placeholder="/qbittorrent">
<small class="form-help">Si derrière un reverse proxy</small>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label for="tcUsername">Utilisateur</label>
<input type="text" id="tcUsername" placeholder="admin">
</div>
<div class="form-group">
<label for="tcPassword">Mot de passe</label>
<input type="password" id="tcPassword" placeholder="••••••••">
</div>
</div>
<div class="form-row">
<div class="form-group">
<label for="tcSSL">
<input type="checkbox" id="tcSSL">
Utiliser SSL (HTTPS)
</label>
</div>
</div>
<div class="action-bar">
<button id="testTorrentClientBtn" class="btn btn-secondary">🔌 Tester la connexion</button>
<button id="saveTorrentClientBtn" class="btn btn-primary">💾 Sauvegarder</button>
</div>
<div id="tcTestResult" class="test-result hidden"></div>
</div>
<!-- Catégories personnalisées -->
<div class="admin-card">
<h3>📁 Catégories & Dossiers</h3>
<p class="help-text">Définissez vos catégories avec leur dossier de destination par défaut.</p>
<div id="customCategoriesList" class="custom-categories-list">
<!-- Rempli dynamiquement -->
</div>
<div class="add-category-form">
<div class="form-row">
<div class="form-group">
<label for="newCategoryName">Nom de la catégorie</label>
<input type="text" id="newCategoryName" placeholder="Films, Séries, Musique...">
</div>
<div class="form-group">
<label for="newCategoryPath">Chemin de destination</label>
<input type="text" id="newCategoryPath" placeholder="/downloads/films">
</div>
<div class="form-group form-group-btn">
<button id="addCategoryBtn" class="btn btn-success"> Ajouter</button>
</div>
</div>
</div>
<div class="action-bar">
<button id="syncCategoriesBtn" class="btn btn-secondary">🔄 Synchroniser avec le client</button>
<button id="saveCategoriesBtn" class="btn btn-primary">💾 Sauvegarder</button>
</div>
</div>
<!-- Plugins disponibles -->
<div class="admin-card collapsible collapsed">
<h3 class="collapsible-header">
🔌 Plugins disponibles
<span class="collapse-icon"></span>
</h3>
<div class="collapsible-content">
<div id="pluginsList" class="plugins-list">
<p class="loading">Chargement...</p>
</div>
<p class="help-text">
Pour ajouter un nouveau client, créez un plugin dans <code>plugins/torrent_clients/</code>
</p>
</div>
</div>
</div>
<!-- ═══════════════════════════════════════════════════════════
ONGLET APPARENCE
═══════════════════════════════════════════════════════════ -->
<div id="tab-appearance" class="tab-content">
<div class="tab-header">
<h2>🎨 Apparence</h2>
<p>Personnalisez l'apparence de Lycostorrent.</p>
</div>
<!-- Sélection du thème -->
<div class="admin-card">
<h3>🎭 Thème</h3>
<p class="help-text">Choisissez un thème pour l'interface.</p>
<div class="theme-grid">
<div class="theme-card" data-theme="dark">
<div class="theme-preview theme-preview-dark">
<div class="preview-header"></div>
<div class="preview-content">
<div class="preview-card"></div>
<div class="preview-card"></div>
</div>
</div>
<span class="theme-name">🌙 Sombre</span>
</div>
<div class="theme-card" data-theme="light">
<div class="theme-preview theme-preview-light">
<div class="preview-header"></div>
<div class="preview-content">
<div class="preview-card"></div>
<div class="preview-card"></div>
</div>
</div>
<span class="theme-name">☀️ Clair</span>
</div>
<div class="theme-card" data-theme="ocean">
<div class="theme-preview theme-preview-ocean">
<div class="preview-header"></div>
<div class="preview-content">
<div class="preview-card"></div>
<div class="preview-card"></div>
</div>
</div>
<span class="theme-name">🌊 Océan</span>
</div>
<div class="theme-card" data-theme="purple">
<div class="theme-preview theme-preview-purple">
<div class="preview-header"></div>
<div class="preview-content">
<div class="preview-card"></div>
<div class="preview-card"></div>
</div>
</div>
<span class="theme-name">💜 Violet</span>
</div>
<div class="theme-card" data-theme="nature">
<div class="theme-preview theme-preview-nature">
<div class="preview-header"></div>
<div class="preview-content">
<div class="preview-card"></div>
<div class="preview-card"></div>
</div>
</div>
<span class="theme-name">🌿 Nature</span>
</div>
<div class="theme-card" data-theme="sunset">
<div class="theme-preview theme-preview-sunset">
<div class="preview-header"></div>
<div class="preview-content">
<div class="preview-card"></div>
<div class="preview-card"></div>
</div>
</div>
<span class="theme-name">🌅 Sunset</span>
</div>
<div class="theme-card" data-theme="cyberpunk">
<div class="theme-preview theme-preview-cyberpunk">
<div class="preview-header"></div>
<div class="preview-content">
<div class="preview-card"></div>
<div class="preview-card"></div>
</div>
</div>
<span class="theme-name">🤖 Cyberpunk</span>
</div>
<div class="theme-card" data-theme="nord">
<div class="theme-preview theme-preview-nord">
<div class="preview-header"></div>
<div class="preview-content">
<div class="preview-card"></div>
<div class="preview-card"></div>
</div>
</div>
<span class="theme-name">❄️ Nord</span>
</div>
</div>
</div>
</div>
</div>
<!-- Message toast -->
<div id="toast" class="toast hidden"></div>
<!-- Footer -->
<footer class="app-footer">
<span>Lycostorrent v<span id="app-version">1.0.0</span></span>
</footer>
</div>
<script src="/static/js/admin.js"></script>
<script src="/static/js/nav.js"></script>
<script>
fetch('/api/version').then(r => r.json()).then(data => {
if (data.version) document.getElementById('app-version').textContent = data.version;
}).catch(() => {});
</script>
</body>
</html>

View File

@@ -0,0 +1,105 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Lycostorrent - Admin Nouveautés</title>
<link rel="stylesheet" href="/static/css/style.css">
<link rel="stylesheet" href="/static/css/admin.css">
</head>
<body>
<div class="container">
<!-- Header -->
<header class="header">
<h1>⚙️ Administration</h1>
<p class="subtitle">Configuration des catégories pour les Nouveautés</p>
<nav class="main-nav">
<a href="/">🔍 Recherche</a>
<a href="/latest">🎬 Nouveautés</a>
<a href="/admin/latest" class="active">⚙️ Catégories</a>
<a href="/admin/parsing">🏷️ Tags</a>
</nav>
</header>
<!-- Instructions -->
<div class="admin-info">
<h3> Comment ça marche</h3>
<p>Configurez les catégories Jackett à utiliser pour chaque type de contenu et chaque tracker.</p>
<p>Les catégories sont les IDs numériques de Jackett (ex: 2000 = Films, 5000 = Séries).</p>
</div>
<!-- Sélection du tracker -->
<div class="admin-section">
<h2>1. Sélectionner un tracker</h2>
<div id="trackerSelector" class="tracker-selector">
<p class="loading">Chargement des trackers...</p>
</div>
</div>
<!-- Catégories du tracker sélectionné -->
<div id="categoriesSection" class="admin-section hidden">
<h2>2. Catégories disponibles sur <span id="selectedTrackerName"></span></h2>
<div id="availableCategories" class="available-categories">
<p class="loading">Chargement des catégories...</p>
</div>
</div>
<!-- Configuration des catégories -->
<div id="configSection" class="admin-section hidden">
<h2>3. Configuration pour <span id="configTrackerName"></span></h2>
<div class="config-grid">
<!-- Films -->
<div class="config-card">
<h3>🎥 Films</h3>
<p class="config-description">Catégories pour les films</p>
<input type="text" id="config-movies" placeholder="Ex: 2000,2010,2020">
<div class="quick-add" data-target="movies"></div>
</div>
<!-- Séries -->
<div class="config-card">
<h3>📺 Séries</h3>
<p class="config-description">Catégories pour les séries TV</p>
<input type="text" id="config-tv" placeholder="Ex: 5000,5010,5020">
<div class="quick-add" data-target="tv"></div>
</div>
<!-- Anime -->
<div class="config-card">
<h3>🎌 Anime</h3>
<p class="config-description">Catégories pour les animes</p>
<input type="text" id="config-anime" placeholder="Ex: 5070,5080">
<div class="quick-add" data-target="anime"></div>
</div>
<!-- Musique -->
<div class="config-card">
<h3>🎵 Musique</h3>
<p class="config-description">Catégories pour la musique</p>
<input type="text" id="config-music" placeholder="Ex: 3000,3010">
<div class="quick-add" data-target="music"></div>
</div>
</div>
<div class="config-actions">
<button id="saveConfigBtn" class="btn-primary">💾 Sauvegarder</button>
<button id="resetConfigBtn" class="btn-secondary">🔄 Réinitialiser</button>
</div>
</div>
<!-- Résumé de la configuration -->
<div id="summarySection" class="admin-section">
<h2>📋 Résumé de la configuration</h2>
<div id="configSummary" class="config-summary">
<p class="loading">Chargement...</p>
</div>
</div>
<!-- Messages -->
<div id="messageBox" class="message-box hidden"></div>
</div>
<script src="/static/js/admin_latest.js"></script>
</body>
</html>

View File

@@ -0,0 +1,108 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Lycostorrent - Tags de Parsing</title>
<link rel="stylesheet" href="/static/css/style.css">
<link rel="stylesheet" href="/static/css/admin.css">
</head>
<body>
<div class="container">
<!-- Header -->
<header class="header">
<h1>🏷️ Tags de Parsing</h1>
<p class="subtitle">Mots-clés utilisés pour couper les titres de torrents</p>
<nav class="main-nav">
<a href="/">🔍 Recherche</a>
<a href="/latest">🎬 Nouveautés</a>
<a href="/admin/latest">⚙️ Catégories</a>
<a href="/admin/parsing" class="active">🏷️ Tags</a>
</nav>
</header>
<!-- Instructions -->
<div class="admin-info">
<h3> Comment ça marche</h3>
<p>Ces tags sont utilisés pour <strong>couper</strong> les titres de torrents avant de chercher sur TMDb/Last.fm.</p>
<p>Exemple: <code>Avatar.2009.MULTi.1080p.BluRay</code> → si "MULTi" est dans la liste, le titre sera coupé à <code>Avatar</code></p>
<p><strong>⚠️ Attention:</strong> N'ajoutez pas de mots qui pourraient être des vrais titres (ex: "Intégrale", "Complete", "Extended").</p>
</div>
<!-- Tags actuels -->
<div class="admin-section">
<h2>Tags de coupure actuels</h2>
<div class="tags-editor">
<div id="tagsList" class="tags-list-editor">
<p class="loading">Chargement...</p>
</div>
<div class="add-tag-form">
<input type="text" id="newTagInput" placeholder="Nouveau tag (ex: VOSTFR)" maxlength="30">
<button id="addTagBtn" class="btn-primary"> Ajouter</button>
</div>
</div>
<div class="config-actions">
<button id="saveTagsBtn" class="btn-primary">💾 Sauvegarder</button>
<button id="resetTagsBtn" class="btn-secondary">🔄 Réinitialiser (défaut)</button>
</div>
</div>
<!-- Présets -->
<div class="admin-section">
<h2>📦 Ajouter des présets</h2>
<p class="config-description">Cliquez pour ajouter un groupe de tags courants</p>
<div class="presets-grid">
<button class="preset-btn" data-preset="langues">
🗣️ Langues<br>
<small>VFF, VFQ, VOSTFR, FRENCH...</small>
</button>
<button class="preset-btn" data-preset="resolutions">
📺 Résolutions<br>
<small>1080p, 720p, 4K, UHD...</small>
</button>
<button class="preset-btn" data-preset="sources">
📀 Sources<br>
<small>BluRay, WEB, HDTV, REMUX...</small>
</button>
<button class="preset-btn" data-preset="codecs">
🎬 Codecs<br>
<small>x264, x265, HEVC, AV1...</small>
</button>
<button class="preset-btn" data-preset="audio">
🔊 Audio<br>
<small>DTS, AC3, FLAC, Atmos...</small>
</button>
</div>
</div>
<!-- Test -->
<div class="admin-section">
<h2>🧪 Tester le parsing</h2>
<p class="config-description">Entrez un titre de torrent pour voir le résultat du nettoyage</p>
<div class="test-form">
<input type="text" id="testTitleInput" placeholder="Ex: Avatar.2009.MULTi.1080p.BluRay.x264">
<button id="testParsingBtn" class="btn-primary">🔍 Tester</button>
</div>
<div id="testResult" class="test-result hidden">
<div class="test-original">
<strong>Original:</strong> <span id="testOriginal"></span>
</div>
<div class="test-cleaned">
<strong>Nettoyé:</strong> <span id="testCleaned"></span>
</div>
</div>
</div>
<!-- Messages -->
<div id="messageBox" class="message-box hidden"></div>
</div>
<script src="/static/js/admin_parsing.js"></script>
</body>
</html>

View File

@@ -0,0 +1,126 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Lycostorrent - Flux RSS</title>
<link rel="stylesheet" href="/static/css/style.css">
<link rel="stylesheet" href="/static/css/admin.css">
</head>
<body>
<div class="container">
<header class="header">
<h1>🔗 Gestion des Flux RSS</h1>
<p>Ajoutez des flux RSS pour récupérer les nouveautés de trackers non supportés par Jackett/Prowlarr</p>
</header>
<nav class="admin-nav">
<a href="/">🔍 Recherche</a>
<a href="/latest">🎬 Nouveautés</a>
<a href="/admin/latest">⚙️ Catégories</a>
<a href="/admin/parsing">🏷️ Tags</a>
<a href="/admin/rss" class="active">🔗 RSS</a>
</nav>
<!-- Formulaire d'ajout -->
<section class="admin-section">
<h2> Ajouter un flux RSS</h2>
<form id="add-feed-form" class="feed-form">
<div class="form-row">
<div class="form-group">
<label for="feed-name">Nom du flux *</label>
<input type="text" id="feed-name" placeholder="Ex: YGG Films" required>
</div>
<div class="form-group">
<label for="feed-category">Catégorie *</label>
<select id="feed-category" required>
<option value="">-- Sélectionner --</option>
<option value="movies">🎬 Films</option>
<option value="tv">📺 Séries</option>
<option value="anime">🎌 Anime</option>
<option value="music">🎵 Musique</option>
<option value="all">📦 Toutes</option>
</select>
</div>
</div>
<div class="form-group">
<label for="feed-url">URL du flux RSS *</label>
<input type="url" id="feed-url" placeholder="https://tracker.xxx/rss?cat=films&passkey={passkey}" required>
<small>Utilisez <code>{passkey}</code> comme placeholder pour le passkey</small>
</div>
<div class="form-group">
<label for="feed-passkey">Passkey (optionnel)</label>
<input type="text" id="feed-passkey" placeholder="Votre passkey privé">
<small>Sera injecté à la place de <code>{passkey}</code> dans l'URL</small>
</div>
<div class="form-group checkbox-group">
<label class="checkbox-label">
<input type="checkbox" id="feed-flaresolverr">
<span>🛡️ Utiliser Flaresolverr (anti-Cloudflare)</span>
</label>
<small>Activer si le site est protégé par Cloudflare (erreur 403)</small>
</div>
<div class="form-group">
<label for="feed-cookies">Cookies de session (optionnel)</label>
<textarea id="feed-cookies" rows="2" placeholder="ygg_=abc123; cf_clearance=xyz789"></textarea>
<small>Format: <code>nom1=valeur1; nom2=valeur2</code> - Récupérez-les depuis les DevTools (F12) → Application → Cookies</small>
</div>
<div class="form-actions">
<button type="button" id="test-feed-btn" class="btn btn-secondary">🧪 Tester</button>
<button type="submit" class="btn btn-primary"> Ajouter</button>
</div>
</form>
<!-- Résultat du test -->
<div id="test-result" class="test-result hidden"></div>
</section>
<!-- Liste des flux configurés -->
<section class="admin-section">
<h2>📋 Flux RSS configurés</h2>
<div id="feeds-list" class="feeds-list">
<p class="loading">Chargement...</p>
</div>
</section>
<!-- Aide -->
<section class="admin-section help-section">
<h2>❓ Comment trouver l'URL RSS ?</h2>
<div class="help-content">
<h4>YGGTorrent</h4>
<ol>
<li>Connectez-vous à YGG</li>
<li>Allez dans votre profil → "Mon RSS"</li>
<li>Copiez l'URL avec votre passkey</li>
<li>Format: <code>https://www3.yggtorrent.xxx/rss?cat=XXX&passkey=VOTRE_PASSKEY</code></li>
</ol>
<h4>Autres trackers privés</h4>
<ol>
<li>Cherchez "RSS" dans les paramètres du tracker</li>
<li>Générez un flux personnalisé avec les catégories souhaitées</li>
<li>Copiez l'URL (contient généralement un passkey ou token)</li>
</ol>
<h4>Catégories YGG courantes</h4>
<table class="help-table">
<tr><td>Films</td><td><code>cat=2145</code></td></tr>
<tr><td>Séries</td><td><code>cat=2184</code></td></tr>
<tr><td>Anime</td><td><code>cat=2179</code></td></tr>
<tr><td>Musique</td><td><code>cat=2139</code></td></tr>
</table>
</div>
</section>
</div>
<script src="/static/js/admin_rss.js"></script>
</body>
</html>

129
app/templates/discover.html Normal file
View File

@@ -0,0 +1,129 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Lycostorrent - Découvrir</title>
<!-- Chargement du thème (en premier pour éviter le flash) -->
<script src="/static/js/theme-loader.js"></script>
<!-- PWA Meta Tags -->
<meta name="theme-color" content="#e63946">
<meta name="description" content="Découvrez les nouveautés cinéma et TV">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="apple-mobile-web-app-title" content="Lycostorrent">
<!-- PWA Manifest -->
<link rel="manifest" href="/static/manifest.json">
<!-- Icons -->
<link rel="icon" type="image/png" sizes="192x192" href="/static/icons/icon-192x192.png">
<link rel="apple-touch-icon" href="/static/icons/icon-192x192.png">
<link rel="stylesheet" href="/static/css/style.css">
<link rel="stylesheet" href="/static/css/themes.css">
<link rel="stylesheet" href="/static/css/discover.css">
<link rel="stylesheet" href="/static/css/cache-info.css">
</head>
<body>
<div class="container">
<!-- Header -->
<header class="header">
<h1>🌟 Lycostorrent</h1>
<p class="subtitle">Découvrez les nouveautés cinéma & TV</p>
<nav class="main-nav" id="mainNav">
<!-- Navigation générée dynamiquement -->
</nav>
</header>
<!-- Catégories simplifiées : Films / Séries -->
<div class="discover-tabs">
<button class="discover-tab active" data-category="movies">🎬 Films récents</button>
<button class="discover-tab" data-category="tv">📺 Séries en cours</button>
</div>
<!-- Info cache -->
<div id="cacheInfo" class="cache-info hidden">
<span class="cache-badge">📦 Cache</span>
<span id="cacheTimestamp" class="cache-timestamp"></span>
<button id="refreshLiveBtn" class="btn-refresh" onclick="refreshLive()" title="Actualiser en direct">🔄</button>
</div>
<!-- Grille de résultats -->
<div class="discover-grid" id="discoverGrid">
<!-- Rempli dynamiquement -->
</div>
<!-- Loader -->
<div class="discover-loader hidden" id="discoverLoader">
<div class="spinner"></div>
<span>Chargement...</span>
</div>
<!-- Message d'état -->
<div class="discover-empty hidden" id="discoverEmpty">
<span class="empty-icon">🎬</span>
<p>Sélectionnez une catégorie pour découvrir les nouveautés</p>
</div>
<!-- Modal détails -->
<div class="modal-overlay hidden" id="detailModal">
<div class="modal-content detail-modal">
<button class="modal-close" onclick="closeDetailModal()"></button>
<div class="detail-header">
<img src="" alt="" class="detail-poster" id="detailPoster">
<div class="detail-info">
<h2 id="detailTitle"></h2>
<div class="detail-meta">
<span class="detail-year" id="detailYear"></span>
<span class="detail-rating" id="detailRating">⭐ --</span>
</div>
<p class="detail-overview" id="detailOverview"></p>
<div class="detail-genres" id="detailGenres"></div>
</div>
</div>
<!-- Bande-annonce YouTube -->
<div class="detail-trailer hidden" id="detailTrailer">
<h3>🎬 Bande-annonce</h3>
<div class="trailer-container">
<iframe id="trailerFrame" src="" frameborder="0" allowfullscreen
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture">
</iframe>
</div>
</div>
<div class="detail-torrents">
<h3>🔍 Torrents disponibles</h3>
<div class="torrents-loading hidden" id="torrentsLoading">
<div class="spinner-small"></div>
<span>Recherche sur vos trackers...</span>
</div>
<div class="torrents-list" id="torrentsList">
<!-- Rempli dynamiquement -->
</div>
<div class="torrents-empty hidden" id="torrentsEmpty">
<p>Aucun torrent trouvé pour ce titre</p>
</div>
</div>
</div>
</div>
<!-- Footer -->
<footer class="app-footer">
<span>Lycostorrent v<span id="app-version">1.0.0</span></span>
<span class="tmdb-credit">Données fournies par <a href="https://www.themoviedb.org" target="_blank">TMDb</a></span>
</footer>
</div>
<script src="/static/js/discover.js"></script>
<script src="/static/js/nav.js"></script>
<script>
fetch('/api/version').then(r => r.json()).then(data => {
if (data.version) document.getElementById('app-version').textContent = data.version;
}).catch(() => {});
</script>
</body>
</html>

133
app/templates/index.html Normal file
View File

@@ -0,0 +1,133 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Lycostorrent - Recherche</title>
<!-- Chargement du thème (en premier pour éviter le flash) -->
<script src="/static/js/theme-loader.js"></script>
<!-- PWA Meta Tags -->
<meta name="theme-color" content="#e63946">
<meta name="description" content="Recherche de torrents avec enrichissement TMDb">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="apple-mobile-web-app-title" content="Lycostorrent">
<!-- PWA Manifest -->
<link rel="manifest" href="/static/manifest.json">
<!-- Icons -->
<link rel="icon" type="image/png" sizes="192x192" href="/static/icons/icon-192x192.png">
<link rel="apple-touch-icon" href="/static/icons/icon-192x192.png">
<link rel="stylesheet" href="/static/css/style.css">
<link rel="stylesheet" href="/static/css/themes.css">
</head>
<body>
<div class="container">
<!-- Header -->
<header class="header">
<h1>🔍 Lycostorrent</h1>
<p class="subtitle">Recherche de torrents</p>
<nav class="main-nav" id="mainNav">
<!-- Navigation générée dynamiquement -->
</nav>
</header>
<!-- Barre de recherche -->
<section class="search-section">
<div class="search-bar">
<input
type="text"
id="search-input"
placeholder="Rechercher un film, série, musique..."
autocomplete="off"
>
<button id="search-btn" class="btn-primary">Rechercher</button>
</div>
<!-- Catégorie -->
<div class="search-options">
<div class="option-group">
<label>Catégorie</label>
<select id="category-select">
<option value="all">Tout</option>
<option value="movies">Films</option>
<option value="tv">Séries TV</option>
<option value="audio">Audio</option>
<option value="pc">PC</option>
<option value="console">Console</option>
<option value="books">Livres</option>
<option value="other">Autre</option>
</select>
</div>
</div>
<!-- Sélection des trackers (style Nouveautés) -->
<div class="trackers-selector">
<button id="toggleTrackers" class="toggle-btn">🔧 Sélectionner les trackers</button>
<div id="trackersPanel" class="trackers-panel hidden">
<div class="trackers-actions">
<button id="selectAllTrackers">Tout sélectionner</button>
<button id="deselectAllTrackers">Tout désélectionner</button>
</div>
<div id="trackers-list" class="trackers-list">
<p class="loading">Chargement des trackers...</p>
</div>
</div>
</div>
</section>
<!-- Filtres dynamiques (masquables) -->
<section id="filters-section" class="filters-section hidden">
<div class="filters-header">
<h3>🎛️ Filtres <span id="results-count" class="results-count"></span></h3>
<button id="toggle-filters" class="btn-toggle" title="Masquer/Afficher les filtres"></button>
</div>
<div id="filters-content" class="filters-content">
<div id="filters-container" class="filters-container">
<!-- Filtres générés dynamiquement -->
</div>
<button id="clear-filters" class="btn-secondary">Effacer les filtres</button>
</div>
</section>
<!-- Résultats -->
<section id="results-section" class="results-section">
<div id="results-container">
<p class="placeholder-text">Effectuez une recherche pour voir les résultats</p>
</div>
</section>
<!-- Loading overlay -->
<div id="loading-overlay" class="loading-overlay hidden">
<div class="spinner"></div>
<p>Recherche en cours...</p>
</div>
<!-- Footer -->
<footer class="app-footer">
<span>Lycostorrent v<span id="app-version">1.1.0</span></span>
</footer>
</div>
<script src="/static/js/search.js"></script>
<script>
fetch('/api/version').then(r => r.json()).then(data => {
if (data.version) document.getElementById('app-version').textContent = data.version;
}).catch(() => {});
// Enregistrement du Service Worker pour PWA
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/static/sw.js')
.then(reg => console.log('✅ Service Worker enregistré'))
.catch(err => console.log('❌ Service Worker erreur:', err));
});
}
</script>
<script src="/static/js/nav.js"></script>
</body>
</html>

145
app/templates/latest.html Normal file
View File

@@ -0,0 +1,145 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Lycostorrent - Nouveautés</title>
<!-- Chargement du thème (en premier pour éviter le flash) -->
<script src="/static/js/theme-loader.js"></script>
<!-- PWA Meta Tags -->
<meta name="theme-color" content="#e63946">
<meta name="description" content="Dernières sorties Films, Séries et Musique">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="apple-mobile-web-app-title" content="Lycostorrent">
<!-- PWA Manifest -->
<link rel="manifest" href="/static/manifest.json">
<!-- Icons -->
<link rel="icon" type="image/png" sizes="192x192" href="/static/icons/icon-192x192.png">
<link rel="apple-touch-icon" href="/static/icons/icon-192x192.png">
<link rel="stylesheet" href="/static/css/style.css">
<link rel="stylesheet" href="/static/css/themes.css">
<link rel="stylesheet" href="/static/css/latest.css">
</head>
<body>
<div class="container">
<!-- Header -->
<header class="header">
<h1>🎬 Lycostorrent</h1>
<p class="subtitle">Dernières sorties Films, Séries & Musique</p>
<nav class="main-nav" id="mainNav">
<!-- Navigation générée dynamiquement -->
</nav>
</header>
<!-- Paramètres -->
<div class="latest-settings">
<div class="categories">
<button class="category-btn active" data-category="movies">🎥 Films</button>
<button class="category-btn" data-category="tv">📺 Séries</button>
<button class="category-btn" data-category="anime">🎌 Animes</button>
<button class="category-btn" data-category="music">🎵 Musique</button>
</div>
<div class="trackers-selector">
<button id="toggleTrackers" class="toggle-btn">🔧 Sélectionner les trackers</button>
<div id="trackersPanel" class="trackers-panel hidden">
<div class="trackers-actions">
<button id="selectAllTrackers">Tout sélectionner</button>
<button id="deselectAllTrackers">Tout désélectionner</button>
</div>
<div id="trackersList" class="trackers-list">
<p class="loading">Chargement des trackers...</p>
</div>
</div>
</div>
<div class="limit-selector">
<label>Résultats:</label>
<select id="limitSelect">
<option value="10">10</option>
<option value="20" selected>20</option>
<option value="30">30</option>
<option value="50">50</option>
<option value="100">100</option>
</select>
<button id="loadLatestBtn" class="btn-primary">📥 Charger</button>
</div>
</div>
<!-- Filtres par année (pastilles) -->
<div id="yearFilters" class="year-filters hidden">
<span class="filter-label">Année :</span>
<div class="year-pills">
<button class="year-pill" data-year="2026">2026</button>
<button class="year-pill" data-year="2025">2025</button>
<button class="year-pill" data-year="2024">2024</button>
<button class="year-pill" data-year="2023">2023</button>
<button class="year-pill" data-year="old">≤2022</button>
<button class="year-pill active" data-year="all">Tous</button>
</div>
<span id="filterCount" class="filter-count"></span>
</div>
<!-- Résultats -->
<div id="latestResults" class="latest-results hidden">
<div class="results-header">
<h2 id="resultsTitle">Dernières sorties</h2>
<div class="results-meta">
<span id="resultsCount">0 résultats</span>
<div id="cacheInfo" class="cache-info hidden">
<span class="cache-badge">📦 Cache</span>
<span id="cacheTimestamp" class="cache-timestamp"></span>
<button id="refreshLiveBtn" class="btn-refresh" title="Actualiser en direct">🔄</button>
</div>
</div>
</div>
<div id="resultsGrid" class="results-grid"></div>
</div>
<!-- Messages -->
<div id="messageBox" class="message-box hidden"></div>
<!-- Loader -->
<div id="loader" class="loader hidden">
<div class="spinner"></div>
<p>Chargement des nouveautés...</p>
</div>
<!-- Modal pour les détails -->
<div id="detailsModal" class="modal hidden">
<div class="modal-content">
<span class="modal-close">&times;</span>
<div id="modalBody"></div>
</div>
</div>
<!-- Footer -->
<footer class="app-footer">
<span>Lycostorrent v<span id="app-version">1.0.0</span></span>
</footer>
</div>
<script src="/static/js/latest.js"></script>
<script>
fetch('/api/version').then(r => r.json()).then(data => {
if (data.version) document.getElementById('app-version').textContent = data.version;
}).catch(() => {});
// Enregistrement du Service Worker pour PWA
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/static/sw.js')
.then(reg => console.log('✅ Service Worker enregistré'))
.catch(err => console.log('❌ Service Worker erreur:', err));
});
}
</script>
<script src="/static/js/nav.js"></script>
</body>
</html>

232
app/templates/login.html Normal file
View File

@@ -0,0 +1,232 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Lycostorrent - Connexion</title>
<!-- Chargement du thème (en premier pour éviter le flash) -->
<script src="/static/js/theme-loader.js"></script>
<!-- PWA Meta Tags -->
<meta name="theme-color" content="#e63946">
<meta name="description" content="Connexion à Lycostorrent">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="apple-mobile-web-app-title" content="Lycostorrent">
<!-- PWA Manifest -->
<link rel="manifest" href="/static/manifest.json">
<!-- Icons -->
<link rel="icon" type="image/png" sizes="192x192" href="/static/icons/icon-192x192.png">
<link rel="apple-touch-icon" href="/static/icons/icon-192x192.png">
<link rel="stylesheet" href="/static/css/style.css">
<link rel="stylesheet" href="/static/css/themes.css">
</head>
<body>
<div class="container">
<!-- Header -->
<header class="header">
<h1>🔐 Lycostorrent</h1>
<p class="subtitle">Connexion requise</p>
</header>
<!-- Formulaire de connexion -->
<div class="login-wrapper">
{% if locked_message %}
<div class="alert alert-warning">
<span class="alert-icon">🔒</span>
<span>{{ locked_message }}</span>
</div>
{% endif %}
{% if error %}
<div class="alert alert-error">
<span class="alert-icon">⚠️</span>
<span>{{ error }}</span>
</div>
{% endif %}
<form method="POST" action="{{ url_for('login') }}{% if request.args.get('next') %}?next={{ request.args.get('next') }}{% endif %}" class="login-form" autocomplete="on">
<!-- Token CSRF -->
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<div class="form-group">
<label for="username">Nom d'utilisateur</label>
<input type="text"
id="username"
name="username"
class="form-input"
placeholder="admin"
autocomplete="username"
required
autofocus
{% if locked_message %}disabled{% endif %}>
</div>
<div class="form-group">
<label for="password">Mot de passe</label>
<input type="password"
id="password"
name="password"
class="form-input"
placeholder="••••••••"
autocomplete="current-password"
required
{% if locked_message %}disabled{% endif %}>
</div>
<button type="submit" class="btn-primary btn-login" {% if locked_message %}disabled{% endif %}>
{% if locked_message %}
🔒 Verrouillé
{% else %}
🔐 Se connecter
{% endif %}
</button>
</form>
<div class="login-footer">
<span class="security-badge">🛡️ Connexion sécurisée</span>
</div>
</div>
<!-- Footer -->
<footer class="app-footer">
<span>Lycostorrent</span>
</footer>
</div>
{% if locked_message %}
<script>
// Rafraîchir la page après le délai de verrouillage
setTimeout(() => {
window.location.reload();
}, 10000);
</script>
{% endif %}
<style>
/* Styles spécifiques à la page login - utilise les variables CSS du thème */
.login-wrapper {
max-width: 400px;
margin: 2rem auto;
padding: 2rem;
background: var(--card-bg);
border-radius: 12px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
}
.login-form {
display: flex;
flex-direction: column;
gap: 1.25rem;
}
.form-group {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.form-group label {
color: var(--text-color);
font-weight: 500;
font-size: 0.95rem;
}
.form-input {
width: 100%;
padding: 0.875rem 1rem;
background: var(--bg-color);
border: 2px solid var(--border-color);
border-radius: 8px;
color: var(--text-color);
font-size: 1rem;
transition: border-color 0.2s, box-shadow 0.2s;
box-sizing: border-box;
}
.form-input:focus {
outline: none;
border-color: var(--accent-color);
box-shadow: 0 0 0 3px rgba(230, 57, 70, 0.2);
}
.form-input::placeholder {
color: var(--text-muted);
}
.form-input:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.btn-login {
width: 100%;
padding: 1rem;
font-size: 1rem;
font-weight: 600;
margin-top: 0.5rem;
}
.btn-login:disabled {
background: var(--text-muted);
cursor: not-allowed;
}
.alert {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 1rem;
border-radius: 8px;
margin-bottom: 1.5rem;
font-size: 0.95rem;
}
.alert-icon {
font-size: 1.25rem;
}
.alert-error {
background: rgba(239, 68, 68, 0.15);
border: 1px solid rgba(239, 68, 68, 0.3);
color: #ef4444;
}
.alert-warning {
background: rgba(245, 158, 11, 0.15);
border: 1px solid rgba(245, 158, 11, 0.3);
color: #f59e0b;
}
.login-footer {
margin-top: 1.5rem;
padding-top: 1.5rem;
border-top: 1px solid var(--border-color);
text-align: center;
}
.security-badge {
display: inline-flex;
align-items: center;
gap: 0.5rem;
background: rgba(34, 197, 94, 0.15);
color: #22c55e;
padding: 0.5rem 1rem;
border-radius: 20px;
font-size: 0.85rem;
font-weight: 500;
}
@media (max-width: 480px) {
.login-wrapper {
margin: 1rem;
padding: 1.5rem;
}
}
</style>
</body>
</html>