feat!: refonte complète du frontend et amélioration des logs serveur
BREAKING CHANGE: nouvelle architecture frontend avec layout sidebar, système de thèmes et routes restructurées ### Frontend - nouveau layout global `AppLayout.vue` avec sidebar de navigation inspirée Radarr/Sonarr - sidebar configurable via un objet `NavItem[]` avec support parents/enfants cliquables - 3 thèmes CSS (Indigo Électrique, Crimson Night, Teal Froid) persistés via localStorage - switcher de thème en bas de sidebar avec pastilles colorées - logo FanKarr cliquable, bouton déconnexion discret dans le header sidebar - badge animé sur "Activité" affichant le nombre de téléchargements en cours - migration complète vers Tailwind v4 avec variables CSS `@theme` - classes utilitaires globales : `btn-primary`, `btn-secondary`, `settings-card`, `settings-input` ### Vues - `SeriesView` : barre colorée en bas des affiches (vert/bleu/orange), dropdown filtres, boutons taille S/M/L/XL persistés - `SerieView` : bouton état unique par épisode (↓/⟳/✓/✗), badges simplifiés, toast via `useToast` global - `ActivityView` : onglets pill, stats, toggle "masquer importés", polling silencieux - `DashboardView` : stats catalogue/importées/en cours/erreurs, torrents actifs, derniers imports - `SettingsView` découpée en 3 vues indépendantes : `DownloadClientView`, `MediaManagementView`, `CatalogueView` - `LogsView` déplacée dans `src/views/settings/` - `AuthView` : migré au système de thème ### Composants - `SidebarNavItem` : support badge, chevron masqué si badge présent - `SettingsToggle` : composant réutilisable `v-model` - `FolderPicker` : migré au thème, `Teleport` intégré - `ToastContainer` : déplacé en bas droite, groupement des toasts similaires avec badge count ### Router - `AppLayout` comme layout racine pour toutes les routes protégées - `/auth` reste hors layout - redirections `/` → `/series`, `/downloads` → `/activity` - `SettingsLayout` avec titre dynamique par route - scroll géré sur `<main id="main-scroll">` dans `AppLayout` ### Stores & composables - nouveau store `downloads` partagé pour le badge sidebar - `usePosterSize` : taille des affiches persistée en localStorage - `useToast` : groupement des toasts du même type, timer reset au lieu d'empilement ### Serveur - logs structurés via `logger` dans `auth.ts`, `organize.ts`, `organize-worker.ts`, `torrent-clients/index.ts`, `qbittorrent.ts`, `index.ts` - suppression des `console.log/error/warn` en faveur de `logger.info/warn/error/debug` - messages worker routés vers `logger` via `organize.ts` - logs `debug` pour les événements verbeux (healthcheck, skip, worker terminé)
This commit is contained in:
1 parent
e5e8baf1fd
commit
9780aee40d
36 files changed
+2815
-2366
No files matched your search
@@ -1,74 +1,75 @@
|
||||
<template>
|
||||
<!-- Overlay -->
|
||||
<div class="fixed inset-0 bg-black/70 flex items-center justify-center z-50 px-4" @click.self="$emit('cancel')">
|
||||
<div class="bg-[#0d1219] border border-[#1e2d3d] w-full max-w-lg flex flex-col" style="max-height: 70vh">
|
||||
<Teleport to="body">
|
||||
<div class="fixed inset-0 bg-black/60 flex items-center justify-center z-50 px-4" @click.self="$emit('cancel')">
|
||||
<div class="bg-card border border-border rounded-xl w-full max-w-lg flex flex-col" style="max-height: 70vh">
|
||||
|
||||
<!-- Header -->
|
||||
<div class="flex items-center justify-between px-5 py-4 border-b border-[#1e2d3d] shrink-0">
|
||||
<div>
|
||||
<div class="text-[10px] text-[#e8513a] tracking-widest mb-0.5">CHOISIR UN DOSSIER</div>
|
||||
<div class="text-xs text-[#5a7a94] font-mono truncate max-w-xs">{{ current }}</div>
|
||||
<!-- Header -->
|
||||
<div class="flex items-center justify-between px-5 py-4 border-b border-border shrink-0">
|
||||
<div>
|
||||
<p class="text-xs text-accent font-medium mb-0.5">Choisir un dossier</p>
|
||||
<p class="text-xs text-muted font-mono truncate max-w-xs">{{ current }}</p>
|
||||
</div>
|
||||
<button @click="$emit('cancel')" class="text-muted hover:text-primary transition-colors text-lg leading-none">✕</button>
|
||||
</div>
|
||||
<button @click="$emit('cancel')" class="text-[#5a7a94] hover:text-white transition-colors cursor-pointer text-lg leading-none">✕</button>
|
||||
</div>
|
||||
|
||||
<!-- Chemin éditable -->
|
||||
<div class="px-5 py-3 border-b border-[#1e2d3d] shrink-0 flex gap-2">
|
||||
<input
|
||||
v-model="inputPath"
|
||||
@keydown.enter="navigateTo(inputPath)"
|
||||
class="flex-1 bg-[#121920] border border-[#1e2d3d] text-xs text-white px-3 py-2 font-mono outline-none focus:border-[#e8513a] transition-colors"
|
||||
placeholder="/"
|
||||
/>
|
||||
<button @click="navigateTo(inputPath)"
|
||||
class="border border-[#1e2d3d] text-[#5a7a94] px-3 py-2 text-xs tracking-widest hover:border-[#e8513a] hover:text-[#e8513a] transition-colors cursor-pointer">
|
||||
OK
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Navigation retour -->
|
||||
<div v-if="parent !== null" class="px-5 shrink-0">
|
||||
<button @click="navigateTo(parent)"
|
||||
class="flex items-center gap-2 w-full py-2.5 text-xs text-[#5a7a94] hover:text-white transition-colors cursor-pointer border-b border-[#1e2d3d]">
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path d="M19 12H5M12 5l-7 7 7 7"/>
|
||||
</svg>
|
||||
..
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Liste dossiers -->
|
||||
<div class="overflow-y-auto flex-1">
|
||||
<div v-if="loading" class="flex items-center justify-center py-10 text-[#5a7a94] text-xs">
|
||||
<div class="w-4 h-4 border border-white/10 border-t-[#e8513a] rounded-full animate-spin mr-2"/>
|
||||
Chargement...
|
||||
<!-- Chemin éditable -->
|
||||
<div class="px-5 py-3 border-b border-border shrink-0 flex gap-2">
|
||||
<input
|
||||
v-model="inputPath"
|
||||
@keydown.enter="navigateTo(inputPath)"
|
||||
class="settings-input flex-1 font-mono text-xs"
|
||||
placeholder="/"
|
||||
/>
|
||||
<button @click="navigateTo(inputPath)" class="btn-secondary px-3 text-xs">
|
||||
OK
|
||||
</button>
|
||||
</div>
|
||||
<div v-else-if="error" class="px-5 py-4 text-xs text-red-400">{{ error }}</div>
|
||||
<div v-else-if="dirs.length === 0" class="px-5 py-4 text-xs text-[#5a7a94]">Aucun sous-dossier</div>
|
||||
<button
|
||||
v-for="dir in dirs"
|
||||
:key="dir"
|
||||
@click="navigateTo(joinPath(current, dir))"
|
||||
class="flex items-center gap-3 w-full px-5 py-2.5 text-xs text-[#c8d8e8] hover:bg-white/5 transition-colors cursor-pointer border-b border-[#1e2d3d]/50 text-left"
|
||||
>
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
|
||||
<path d="M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V7z"/>
|
||||
</svg>
|
||||
{{ dir }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="flex items-center justify-between px-5 py-4 border-t border-[#1e2d3d] shrink-0">
|
||||
<span class="text-[10px] text-[#5a7a94] font-mono truncate max-w-xs">{{ current }}</span>
|
||||
<button @click="$emit('select', current)"
|
||||
class="bg-[#e8513a] text-white px-5 py-2 text-xs tracking-widest hover:opacity-85 transition-opacity cursor-pointer">
|
||||
CHOISIR
|
||||
</button>
|
||||
</div>
|
||||
<!-- Retour parent -->
|
||||
<div v-if="parent !== null" class="px-5 shrink-0">
|
||||
<button
|
||||
@click="navigateTo(parent)"
|
||||
class="flex items-center gap-2 w-full py-2.5 text-xs text-muted hover:text-primary transition-colors border-b border-border"
|
||||
>
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" fill="none">
|
||||
<path d="M19 12H5M12 5l-7 7 7 7"/>
|
||||
</svg>
|
||||
..
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Liste dossiers -->
|
||||
<div class="overflow-y-auto flex-1">
|
||||
<div v-if="loading" class="flex items-center justify-center py-10 text-muted text-xs gap-2">
|
||||
<div class="w-4 h-4 border border-border border-t-accent rounded-full animate-spin" />
|
||||
Chargement...
|
||||
</div>
|
||||
<div v-else-if="error" class="px-5 py-4 text-xs text-red-400">{{ error }}</div>
|
||||
<div v-else-if="dirs.length === 0" class="px-5 py-4 text-xs text-muted">Aucun sous-dossier</div>
|
||||
<button
|
||||
v-for="dir in dirs"
|
||||
:key="dir"
|
||||
@click="navigateTo(joinPath(current, dir))"
|
||||
class="flex items-center gap-3 w-full px-5 py-2.5 text-xs text-primary hover:bg-hover transition-colors border-b border-border/50 text-left"
|
||||
>
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5" fill="none">
|
||||
<path d="M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V7z"/>
|
||||
</svg>
|
||||
{{ dir }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="flex items-center justify-between px-5 py-4 border-t border-border shrink-0">
|
||||
<span class="text-xs text-muted font-mono truncate max-w-xs">{{ current }}</span>
|
||||
<button @click="$emit('select', current)" class="btn-primary">
|
||||
Choisir
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@@ -95,11 +96,10 @@ async function navigateTo(p: string) {
|
||||
try {
|
||||
const res = await fetch(`/api/browse?path=${encodeURIComponent(p)}`, { credentials: 'include' })
|
||||
if (!res.ok) {
|
||||
const data = await res.json()
|
||||
error.value = data.error ?? 'Erreur'
|
||||
error.value = (await res.json()).error ?? 'Erreur'
|
||||
return
|
||||
}
|
||||
const data = await res.json()
|
||||
const data = await res.json()
|
||||
current.value = data.path
|
||||
inputPath.value = data.path
|
||||
parent.value = data.parent
|
||||
@@ -111,7 +111,5 @@ async function navigateTo(p: string) {
|
||||
}
|
||||
}
|
||||
|
||||
watch(() => props.initialPath, v => {
|
||||
if (v) navigateTo(v)
|
||||
}, { immediate: true })
|
||||
watch(() => props.initialPath, v => { if (v) navigateTo(v) }, { immediate: true })
|
||||
</script>
|
||||
Reference in new issue
Block a user