feat: add Settings tab with provider management and language preferences
CI / Test (Python 3.11) (push) Has been cancelled
CI / Test (Python 3.12) (push) Has been cancelled
CI / Lint (push) Has been cancelled
CI / Type Check (push) Has been cancelled
CI / Summary (push) Has been cancelled

- Implemented AppSettings model and table using SQLModel.
- Created Settings router with endpoints for preferences and provider toggling.
- Added Settings tab to the UI with real-time health status of providers.
- Integrated language and provider filtering into anime and series search logic.
- Updated templates to respect user-defined settings.
This commit is contained in:
root
2026-03-26 16:12:29 +00:00
parent 3b405f2a42
commit 0c03f4f4a6
15 changed files with 383 additions and 25 deletions
+7 -6
View File
@@ -1,4 +1,4 @@
{% macro anime_card(anime, in_watchlist=False) %}
{% macro anime_card(anime, in_watchlist=False, lang='vostfr') %}
<div class="anime-card" id="anime-{{ anime.url | hash }}">
<div class="anime-poster">
{% set poster = anime.cover_image or (anime.metadata.poster_image if anime.metadata else None) or 'https://placehold.co/400x600/161625/00d9ff?text=No+Image' %}
@@ -17,7 +17,7 @@
<div class="anime-overlay">
<div class="overlay-buttons">
<button class="btn btn-primary btn-circle"
hx-get="/api/anime/episodes?url={{ anime.url | urlencode }}"
hx-get="/api/anime/episodes?url={{ anime.url | urlencode }}&lang={{ lang }}"
hx-target="#player-container"
hx-swap="innerHTML"
title="Play">
@@ -32,20 +32,21 @@
<div class="anime-meta-tags">
<span class="badge">{{ anime.provider_id or 'Anime' }}</span>
<span class="badge" style="color: var(--primary)">{{ lang | upper }}</span>
{% if anime.metadata and anime.metadata.status %}
<span class="badge" style="color: var(--primary)">{{ anime.metadata.status }}</span>
<span class="badge" style="color: #aaa">{{ anime.metadata.status }}</span>
{% endif %}
</div>
<div class="anime-card-buttons">
<button class="btn btn-primary btn-small"
hx-get="/api/anime/episodes?url={{ anime.url | urlencode }}"
hx-get="/api/anime/episodes?url={{ anime.url | urlencode }}&lang={{ lang }}"
hx-target="#player-container"
hx-swap="innerHTML">
<i class="fas fa-eye"></i> <span>Regarder</span>
</button>
<button class="btn btn-secondary btn-small"
hx-get="/api/anime/episodes?url={{ anime.url | urlencode }}"
hx-get="/api/anime/episodes?url={{ anime.url | urlencode }}&lang={{ lang }}"
hx-target="#player-container"
hx-swap="innerHTML">
<i class="fas fa-download"></i> <span>Télécharger</span>
@@ -55,7 +56,7 @@
{% if not in_watchlist %}
<button class="btn btn-secondary btn-small btn-block"
hx-post="/api/watchlist"
hx-vals='{"anime_url": "{{ anime.url }}", "anime_title": "{{ anime.title }}", "provider_id": "{{ anime.provider_id }}"}'
hx-vals='{"anime_url": "{{ anime.url }}", "anime_title": "{{ anime.title }}", "provider_id": "{{ anime.provider_id }}", "lang": "{{ lang }}"}'
hx-swap="none"
hx-on::after-request="this.innerHTML='<i class=\'fas fa-check\'></i> Suivi'; this.disabled=true; this.classList.add('followed')">
<i class="fas fa-plus"></i> Watchlist
@@ -7,7 +7,7 @@
<h3 class="provider-title">{{ provider_id | upper }}</h3>
<div class="anime-grid">
{% for anime in items %}
{{ anime_card(anime) }}
{{ anime_card(anime, lang=settings.default_lang if settings else 'vostfr') }}
{% endfor %}
</div>
</div>
+9
View File
@@ -65,5 +65,14 @@
</svg>
Téléchargements
</button>
<button class="tab"
:class="{ 'active': activeTab === 'settings' }"
@click="activeTab = 'settings'; window.dispatchEvent(new CustomEvent('set-tab', { detail: { tab: 'settings' } }))">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"></path>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path>
</svg>
Paramètres
</button>
</nav>
</header>
+7 -6
View File
@@ -1,4 +1,4 @@
{% macro series_card(series, in_watchlist=False) %}
{% macro series_card(series, in_watchlist=False, lang='vf') %}
<div class="anime-card" id="series-{{ series.url | hash }}">
<div class="anime-poster">
<img src="{{ series.cover_image or 'https://placehold.co/400x600/161625/ff6b6b?text=No+Image' }}"
@@ -9,7 +9,7 @@
<div class="anime-overlay">
<div class="overlay-buttons">
<button class="btn btn-primary btn-circle"
hx-get="/api/anime/episodes?url={{ series.url | urlencode }}&lang=vf"
hx-get="/api/anime/episodes?url={{ series.url | urlencode }}&lang={{ lang }}"
hx-target="#player-container"
hx-swap="innerHTML"
title="Play">
@@ -21,18 +21,19 @@
<div class="anime-info">
<h3 class="anime-title" title="{{ series.title }}">{{ series.title }}</h3>
<div class="anime-meta-tags">
<span class="badge">FS7</span>
<span class="badge">{{ series.provider_id | upper if series.provider_id else 'FS7' }}</span>
<span class="badge" style="color: var(--primary)">{{ lang | upper }}</span>
</div>
<div class="anime-card-buttons">
<button class="btn btn-primary btn-small"
hx-get="/api/anime/episodes?url={{ series.url | urlencode }}&lang=vf"
hx-get="/api/anime/episodes?url={{ series.url | urlencode }}&lang={{ lang }}"
hx-target="#player-container"
hx-swap="innerHTML">
<i class="fas fa-eye"></i> <span>Regarder</span>
</button>
<button class="btn btn-secondary btn-small"
hx-get="/api/anime/episodes?url={{ series.url | urlencode }}&lang=vf"
hx-get="/api/anime/episodes?url={{ series.url | urlencode }}&lang={{ lang }}"
hx-target="#player-container"
hx-swap="innerHTML">
<i class="fas fa-download"></i> <span>Télécharger</span>
@@ -42,7 +43,7 @@
{% if not in_watchlist %}
<button class="btn btn-secondary btn-small btn-block"
hx-post="/api/watchlist"
hx-vals='{"anime_url": "{{ series.url }}", "anime_title": "{{ series.title }}", "provider_id": "fs7", "lang": "vf"}'
hx-vals='{"anime_url": "{{ series.url }}", "anime_title": "{{ series.title }}", "provider_id": "{{ series.provider_id or 'fs7' }}", "lang": "{{ lang }}"}'
hx-swap="none"
hx-on::after-request="this.innerHTML='<i class=\'fas fa-check\'></i> Suivi'; this.disabled=true; this.classList.add('followed')">
<i class="fas fa-plus"></i> Watchlist
@@ -7,7 +7,7 @@
<h3 class="provider-title">{{ provider_id | upper }}</h3>
<div class="anime-grid">
{% for series in items %}
{{ series_card(series) }}
{{ series_card(series, lang=settings.default_lang if settings else 'vf') }}
{% endfor %}
</div>
</div>
@@ -0,0 +1,84 @@
<div class="settings-container section-container">
<div class="section-header">
<h2>⚙️ Paramètres</h2>
</div>
<!-- General Preferences -->
<div class="settings-card card" style="margin-bottom: 30px; padding: 25px; background: var(--bg-card); border-radius: var(--card-radius); border: 1px solid rgba(255,255,255,0.05);">
<h3 style="margin-bottom: 20px; color: var(--primary);">Général</h3>
<form hx-patch="/api/settings" hx-swap="none" hx-ext="json-enc" class="settings-form">
<div class="form-group">
<label for="default_lang">Langue par défaut</label>
<select name="default_lang" id="default_lang" class="btn btn-secondary btn-block" style="text-align: left; padding: 12px 15px;">
<option value="vostfr" {% if settings.default_lang == 'vostfr' %}selected{% endif %}>VOSTFR (Version Originale Sous-Titrée Français)</option>
<option value="vf" {% if settings.default_lang == 'vf' %}selected{% endif %}>VF (Version Française)</option>
</select>
</div>
<div class="form-group" style="margin-top: 20px;">
<label for="theme">Thème</label>
<select name="theme" id="theme" class="btn btn-secondary btn-block" style="text-align: left; padding: 12px 15px;">
<option value="dark" {% if settings.theme == 'dark' %}selected{% endif %}>Sombre (Premium Dark)</option>
<option value="light" {% if settings.theme == 'light' %}selected{% endif %}>Clair (Soon)</option>
<option value="oled" {% if settings.theme == 'oled' %}selected{% endif %}>OLED (Noir absolu)</option>
</select>
</div>
<button type="submit" class="btn btn-primary" style="margin-top: 20px; width: 100%;">
<i class="fas fa-save"></i> Enregistrer les préférences
</button>
</form>
</div>
<!-- Providers Management -->
<div class="settings-card card" style="padding: 25px; background: var(--bg-card); border-radius: var(--card-radius); border: 1px solid rgba(255,255,255,0.05);">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
<h3 style="margin: 0; color: var(--primary);">Disponibilité des Fournisseurs</h3>
<button class="btn btn-secondary btn-small" hx-post="/api/providers/health/check" hx-swap="none">
<i class="fas fa-sync-alt"></i> Forcer vérification
</button>
</div>
<div class="providers-settings-list" style="display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 15px;">
{% for provider in providers %}
<div class="provider-status-card" style="padding: 15px; background: rgba(255,255,255,0.02); border-radius: 10px; border: 1px solid rgba(255,255,255,0.05); display: flex; align-items: center; justify-content: space-between;">
<div style="display: flex; align-items: center; gap: 12px;">
<span style="font-size: 1.5rem;">{{ provider.icon }}</span>
<div>
<div style="font-weight: 600;">{{ provider.name }}</div>
<div style="font-size: 0.75rem; display: flex; align-items: center; gap: 5px;">
<span class="status-dot" style="width: 8px; height: 8px; border-radius: 50%; background: {% if provider.status == 'up' %}var(--accent){% elif provider.status == 'down' %}var(--danger){% else %}#aaa{% endif %};"></span>
<span style="color: {% if provider.status == 'up' %}var(--accent){% elif provider.status == 'down' %}var(--danger){% else %}var(--text-dim){% endif %}; text-transform: uppercase; font-weight: 800;">
{{ provider.status | upper }}
</span>
</div>
</div>
</div>
<button class="btn {% if provider.enabled %}btn-secondary{% else %}btn-accent{% endif %} btn-sm"
hx-post="/api/settings/providers/{{ provider.id }}/toggle"
hx-target="closest .settings-container"
hx-get="/api/settings/ui"
hx-trigger="click delay:200ms"
style="min-width: 100px;">
{% if provider.enabled %}Désactiver{% else %}Activer{% endif %}
</button>
</div>
{% endfor %}
</div>
</div>
</div>
<style>
.settings-form label {
display: block;
margin-bottom: 8px;
font-weight: 600;
color: var(--text-dim);
}
.status-dot {
display: inline-block;
box-shadow: 0 0 5px currentColor;
}
</style>