refacto: réécriture des textes, messages et commentaires

This commit is contained in:
ElPouki committed 2026-09-15 15:30:20 +02:00
1 parent 6a3cb21adf
commit 586eccbbb3
118 files changed
+1621 -2159

No files matched your search

@@ -9,8 +9,8 @@
<div class="flex flex-col gap-[7px]">
<label for="jellyfin-token" class="field-label">Clé API administrateur</label>
<input id="jellyfin-token" v-model="jellyfinAdminToken" type="password" class="field" autocomplete="off"
:placeholder="hasToken ? '•••••••••••••••••••• (configurée)' : 'Tableau de bord → Clés API'" />
<p class="text-xs text-muted">Dans Jellyfin : Tableau de bord → Clés API → Créer une clé.</p>
:placeholder="hasToken ? '•••••••••••••••••••• (configurée)' : 'Collez la clé API'" />
<p class="text-xs text-muted">Dans Jellyfin : Tableau de bord › Clés API › Créer une clé.</p>
</div>
</div>
@@ -27,6 +27,7 @@
{{ testResult.ok ? `Connecté · Jellyfin ${testResult.version}` : testResult.error }}
</span>
<span v-if="saved" class="text-meta text-ok flex items-center gap-1.5"><Check :size="14" /> Enregistré</span>
<span v-if="saveError" class="text-meta text-err">{{ saveError }}</span>
</div>
</div>
</template>
@@ -46,6 +47,7 @@ const jellyfinAdminToken = ref('')
const hasToken = ref(false)
const saving = ref(false)
const saved = ref(false)
const saveError = ref<string | null>(null)
const testing = ref(false)
const testResult = ref<JellyfinTestResult | null>(null)
@@ -64,15 +66,19 @@ onMounted(async () => {
})
async function save() {
saving.value = true
saved.value = false
saving.value = true
saved.value = false
saveError.value = null
const body: Record<string, string> = { jellyfinUrl: jellyfinUrl.value }
if (jellyfinAdminToken.value) body.jellyfinAdminToken = jellyfinAdminToken.value
const res = await fetch('/api/jellyfin/settings', {
method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include',
body: JSON.stringify(body),
})
if (res.ok) {
}).catch(() => null)
if (!res?.ok) {
const d = await res?.json().catch(() => ({}))
saveError.value = d?.error ?? 'Impossible d\'enregistrer les réglages Jellyfin.'
} else {
const d = await res.json()
hasToken.value = d.hasToken
jellyfinAdminToken.value = ''
+26 -28
View File
@@ -3,7 +3,6 @@
<div class="modal-backdrop" @click.self="emit('close')">
<div class="bg-card border border-border rounded-card w-full max-w-lg flex flex-col max-h-[85vh] overflow-y-auto shadow-[0_24px_60px_rgb(0_0_0/0.55)]" role="dialog" aria-modal="true" aria-labelledby="plex-title">
<!-- Header -->
<div class="flex items-center justify-between gap-4 px-6 py-4 border-b border-hover shrink-0">
<div>
<h3 id="plex-title" class="font-display text-xl font-bold text-primary">Configuration Plex</h3>
@@ -12,13 +11,13 @@
plexStep === 'server' ? 'Choisissez votre serveur Plex' :
plexStep === 'library' ? 'Créez la bibliothèque Fankai' :
plexStep === 'done' ? 'Configuration terminée' :
'Erreur' }}
'' }}
</p>
</div>
<button @click="emit('close')" class="btn-icon btn-sm border-transparent" aria-label="Fermer"><X :size="16" /></button>
</div>
<!-- Étape 1 : Auth -->
<!-- Étape 1 : connexion -->
<div v-if="plexStep === 'auth'" class="flex flex-col gap-4 px-6 py-5">
<div v-if="!plexAuthMethod" class="flex flex-col gap-3">
<button
@@ -30,7 +29,7 @@
</div>
<div>
<p class="text-sm font-medium text-primary">{{ plexLoading ? 'Ouverture…' : 'Connexion via Plex' }}</p>
<p class="text-meta text-muted">Google, Apple, ou compte Plex</p>
<p class="text-meta text-muted">Google, Apple ou compte Plex</p>
</div>
</button>
@@ -43,22 +42,20 @@
</div>
<div>
<p class="text-sm font-medium text-primary">E-mail et mot de passe</p>
<p class="text-meta text-muted">Connexion directe avec vos identifiants</p>
<p class="text-meta text-muted">Sans passer par la page de connexion Plex</p>
</div>
</button>
</div>
<!-- OAuth en attente -->
<div v-else-if="plexAuthMethod === 'oauth'" class="flex flex-col items-center gap-4 py-4 text-center">
<div class="w-10 h-10 border-2 border-border border-t-accent rounded-full animate-spin" />
<div>
<p class="text-sm text-primary font-medium">En attente de la connexion Plex…</p>
<p class="text-meta text-muted mt-1">Connectez-vous sur la page Plex qui s'est ouverte dans votre navigateur</p>
<p class="text-meta text-muted mt-1">Connectez-vous dans l'onglet Plex qui vient de s'ouvrir. Rien ne s'est ouvert ? Autorisez les fenêtres pop-up.</p>
</div>
<button @click="cancelOAuth" class="btn-ghost btn-sm">Annuler</button>
</div>
<!-- Formulaire credentials -->
<template v-else-if="plexAuthMethod === 'credentials'">
<div>
<label class="field-label mb-1 block">Adresse e-mail Plex</label>
@@ -69,7 +66,7 @@
<input v-model="plexForm.password" type="password" class="field w-full" placeholder="••••••••" @keyup.enter="plexConnect" />
</div>
<div v-if="plexNeeds2FA">
<label class="field-label mb-1 block">Code 2FA</label>
<label class="field-label mb-1 block">Code de validation (6 chiffres)</label>
<input v-model="plexForm.code" type="text" class="field w-full" placeholder="123456" maxlength="6" @keyup.enter="plexConnect" />
</div>
<p v-if="plexError" class="text-meta text-err">{{ plexError }}</p>
@@ -85,9 +82,9 @@
<button v-if="!plexAuthMethod" @click="emit('close')" class="btn-secondary self-start">Annuler</button>
</div>
<!-- Étape 2 : Choix serveur -->
<!-- Étape 2 : choix du serveur -->
<div v-else-if="plexStep === 'server'" class="flex flex-col gap-3 px-6 py-5">
<p class="text-meta text-muted">{{ plexServers.length }} serveur{{ plexServers.length > 1 ? 's' : '' }} trouvé{{ plexServers.length > 1 ? 's' : '' }}</p>
<p class="text-meta text-muted">{{ plural(plexServers.length, 'serveur trouvé', 'serveurs trouvés') }}</p>
<div
v-for="(server, i) in plexServers" :key="i"
@click="selectServer(server)"
@@ -96,7 +93,7 @@
>
<div>
<p class="text-sm text-primary font-medium">{{ server.name }}</p>
<p class="text-meta text-muted">{{ server.owned ? 'Propriétaire' : 'Partagé' }} · {{ server.connections.length }} connexion{{ server.connections.length > 1 ? 's' : '' }}</p>
<p class="text-meta text-muted">{{ server.owned ? 'Votre serveur' : 'Partagé avec vous' }}</p>
</div>
<Check v-if="plexSelectedServer === server" :size="15" :stroke-width="2.5" class="text-accent" />
</div>
@@ -117,7 +114,7 @@
</div>
<div>
<label class="field-label mb-1 block">Chemin de la médiathèque</label>
<p class="text-xs text-muted mb-1.5">Chemin tel que vu par votre serveur Plex</p>
<p class="text-xs text-muted mb-1.5">Le dossier de la médiathèque tel que Plex le voit (il peut différer du chemin FanKarr).</p>
<input v-model="plexForm.libraryPath" type="text" class="field w-full" placeholder="/data/media/fankai" />
</div>
<p v-if="plexError" class="text-meta text-err">{{ plexError }}</p>
@@ -147,9 +144,9 @@
<ol class="text-xs text-muted space-y-1 list-decimal list-inside">
<li>Rendez-vous sur la page <span class="text-primary">Metadata Agent</span> de Plex</li>
<li>Dans <span class="text-primary">Metadata Provider</span>, ajoutez : <code class="bg-hover px-1 rounded">https://metadata.fankai.fr/plex</code></li>
<li>Dans <span class="text-primary">Metadata Agent</span>, créez un agent avec ce provider</li>
<li>Dans votre bibliothèque → ⋯ → <span class="text-primary">Manage Library → Edit → Advanced → Agent</span>, sélectionnez l'agent Fankai</li>
<li>Rafraîchissez les métadonnées</li>
<li>Dans <span class="text-primary">Metadata Agent</span>, créez un agent qui utilise ce fournisseur</li>
<li>Dans le menu ⋯ de votre bibliothèque, <span class="text-primary">Manage Library › Edit › Advanced › Agent</span> : sélectionnez l'agent Fankai</li>
<li>Actualisez les métadonnées</li>
</ol>
</div>
@@ -164,6 +161,7 @@
<script setup lang="ts">
import { ref, onUnmounted } from 'vue'
import { Check, Clock3, User, X } from 'lucide-vue-next'
import { plural } from '@/utils/format'
const props = defineProps<{ mediaPath: string }>()
const emit = defineEmits<{ close: [] }>()
@@ -190,21 +188,21 @@ const plexForm = ref({
libraryPath: props.mediaPath,
})
// ── OAuth ─────────────────────────────────────────────────────
// ── Connexion via Plex ──
async function startOAuth() {
plexError.value = ''
plexLoading.value = true
try {
const res = await fetch('/api/plex/oauth/start', { method: 'POST', credentials: 'include' })
const data = await res.json()
if (!res.ok) { plexError.value = data.error ?? 'Erreur OAuth'; return }
if (!res.ok) { plexError.value = data.error ?? "Impossible d'ouvrir la connexion Plex."; return }
plexOAuthPinId.value = String(data.pinId)
plexAuthMethod.value = 'oauth'
window.open(data.authUrl, '_blank')
let attempts = 0
plexOAuthTimer = setInterval(async () => {
attempts++
if (attempts > 150) { cancelOAuth(); plexError.value = 'Délai expiré — réessayez'; return }
if (attempts > 150) { cancelOAuth(); plexError.value = 'Délai dépassé. Réessayez.'; return }
try {
const r = await fetch(`/api/plex/oauth/poll/${plexOAuthPinId.value}`, { credentials: 'include' })
const d = await r.json()
@@ -218,7 +216,7 @@ async function startOAuth() {
} catch {}
}, 2000)
} catch {
plexError.value = "Impossible de démarrer l'authentification"
plexError.value = "Impossible d'ouvrir la connexion Plex."
} finally {
plexLoading.value = false
}
@@ -230,7 +228,7 @@ function cancelOAuth() {
if (plexAuthMethod.value === 'oauth') plexAuthMethod.value = null
}
// ── Credentials ───────────────────────────────────────────────
// ── Identifiants ──
async function plexConnect() {
plexError.value = ''
plexLoading.value = true
@@ -241,8 +239,8 @@ async function plexConnect() {
})
const data = await res.json()
if (!res.ok) {
if (data.requires2FA) { plexNeeds2FA.value = true; plexError.value = 'Entrez votre code 2FA' }
else plexError.value = data.error ?? 'Erreur de connexion'
if (data.requires2FA) { plexNeeds2FA.value = true; plexError.value = 'Entrez le code de validation en deux étapes.' }
else plexError.value = data.error ?? 'Impossible de se connecter à Plex. Vérifiez vos identifiants.'
return
}
plexToken.value = data.token
@@ -250,7 +248,7 @@ async function plexConnect() {
if (data.servers.length === 1) { plexSelectedServer.value = data.servers[0]; await plexConnectServer() }
else plexStep.value = 'server'
} catch {
plexError.value = 'Impossible de contacter le serveur'
plexError.value = 'Impossible de contacter le serveur.'
} finally {
plexLoading.value = false
}
@@ -266,11 +264,11 @@ async function plexConnectServer() {
const conns = plexSelectedServer.value.connections ?? []
const conn = conns.find((c: any) => c.local && !c.relay) ?? conns.find((c: any) => !c.local && !c.relay) ?? conns.find((c: any) => c.relay)
plexServerUrl.value = conn?.uri ?? ''
if (!plexServerUrl.value) throw new Error('Aucune URL de connexion disponible')
if (!plexServerUrl.value) throw new Error('Ce serveur Plex ne propose aucune adresse de connexion.')
plexForm.value.libraryPath = props.mediaPath
plexStep.value = 'library'
} catch (err) {
plexError.value = err instanceof Error ? err.message : 'Erreur'
plexError.value = err instanceof Error ? err.message : 'Impossible de se connecter à ce serveur.'
} finally {
plexLoading.value = false
}
@@ -288,9 +286,9 @@ async function plexSetup() {
plexSteps.value = data.steps ?? []
plexManualSetup.value = data.manualSetup ?? false
plexStep.value = 'done'
if (!data.ok) plexError.value = data.error ?? 'Erreur lors du setup'
if (!data.ok) plexError.value = data.error ?? 'Impossible de configurer Plex.'
} catch {
plexError.value = 'Impossible de contacter le serveur'
plexError.value = 'Impossible de contacter le serveur.'
} finally {
plexLoading.value = false
}
+1 -1
View File
@@ -1,5 +1,5 @@
<template>
<div class="grid grid-cols-3 gap-3.5" role="radiogroup" aria-label="Accent">
<div class="grid grid-cols-3 gap-3.5" role="radiogroup" aria-label="Couleur de l'interface">
<button
v-for="theme in themes"
:key="theme.id"
+8 -10
View File
@@ -15,7 +15,6 @@
</div>
</div>
<!-- Champs principaux -->
<template v-if="currentDefinition">
<div v-for="field in basicFields" :key="field.key">
<SettingsToggle
@@ -39,7 +38,6 @@
</div>
</div>
<!-- Paramètres avancés -->
<div v-if="advancedFields.length > 0">
<button
type="button"
@@ -92,7 +90,7 @@
<button @click="test" :disabled="testing || !type" class="btn-secondary">
<Loader v-if="testing" :size="14" class="animate-spin" />
<Check v-else-if="tested" :size="14" class="text-ok" />
{{ testing ? 'Test…' : tested ? 'Connexion testée' : 'Tester' }}
{{ testing ? 'Test…' : tested ? 'Connexion testée' : 'Tester la connexion' }}
</button>
<button v-if="showCancel" @click="emit('cancel')" class="btn-ghost ml-auto">Annuler</button>
</div>
@@ -122,10 +120,10 @@ const { add: toast } = useToast()
const ADVANCED_KEYS = ['savePath', 'remotePath', 'localPath', 'ignoreCertificateErrors']
const FIELD_TOOLTIPS: Record<string, string> = {
savePath : 'Optionnel. Permet de placer les téléchargements dans un sous-dossier spécifique à l\'intérieur du dossier déjà configuré dans la gestion des médias.',
remotePath : 'Si le client tourne sur une autre machine, indiquez ici le chemin qu\'il utilise (ex: /downloads). À associer avec le chemin local ci-dessous.',
localPath : 'Chemin équivalent au chemin distant, mais vu par FanKarr sur sa machine (ex: /mnt/nas/downloads). Les deux champs fonctionnent en binôme pour faire la correspondance.',
ignoreCertificateErrors: 'Accepte les certificats auto-signés ou invalides en HTTPS. À n\'utiliser que sur un réseau de confiance.',
savePath : 'Facultatif. Dossier où le client enregistre les torrents envoyés par FanKarr (ex. /downloads/fankai). Vide, le client utilise son dossier par défaut.',
remotePath : "Si le client tourne sur une autre machine ou dans un autre conteneur : son dossier de téléchargement, tel qu'il le voit (ex. /downloads).",
localPath : 'Le même dossier, tel que FanKarr le voit (ex. /mnt/nas/downloads).',
ignoreCertificateErrors: "Accepte les certificats auto-signés ou invalides en HTTPS. À n'utiliser que sur un réseau de confiance.",
}
const name = ref(props.client?.name ?? '')
@@ -177,7 +175,7 @@ async function test() {
})
const { ok, message } = await res.json()
tested.value = ok
toast(ok ? 'Connexion réussie ✓' : (message ?? 'Connexion échouée'), ok ? 'success' : 'error')
toast(ok ? 'Connexion réussie' : (message ?? 'Impossible de se connecter au client torrent'), ok ? 'success' : 'error')
} catch {
toast('Impossible de contacter le serveur', 'error')
} finally {
@@ -199,11 +197,11 @@ async function save() {
}),
})
if (res.ok) {
toast(isEdit ? 'Client modifié ✓' : 'Client enregistré ✓', 'success')
toast(isEdit ? 'Client modifié' : 'Client enregistré', 'success')
emit('saved', await res.json(), isEdit)
} else {
const { error } = await res.json()
toast(error ?? "Erreur lors de l'enregistrement", 'error')
toast(error ?? "Impossible d'enregistrer le client", 'error')
}
} finally {
saving.value = false