feat: Ajout du support pour l'édition/mise à jour des clients torrent
Nouveau helper updateClient et un endpoint PUT /api/torrent-clients/:uuid qui enregistre les modifications, journalise les mises à jour et renvoie le client. Mise à jour de l'UI des paramètres pour ajouter un bouton "Modifier" et réutiliser la modale d'ajout de client pour les éditions (afin d'assurer le suivi de l'uuid ), bascule méthode PUT pour les requêtes de mise à jour, et actualisation de la liste des clients ainsi que des notifications en cas de modification réussie.
This commit is contained in:
1 parent
f92b3c5fb9
commit
15fac6c472
3 files changed
+77
-9
No files matched your search
+22
-2
@@ -7,7 +7,7 @@ import { readSettings, writeSettings } from './settings.js'
|
||||
import {
|
||||
registerDriver, getDriver, getAvailableClients,
|
||||
listClients, addClient, removeClient, getClient,
|
||||
sanitizeClient, dispatchDownload, dispatchList
|
||||
sanitizeClient, dispatchDownload, dispatchList, updateClient
|
||||
} from './torrent-clients/index.js'
|
||||
import qbittorrentDriver from './torrent-clients/qbittorrent.js'
|
||||
import transmissionDriver from './torrent-clients/transmission.js'
|
||||
@@ -71,11 +71,31 @@ app.delete('/api/torrent-clients/:uuid', requireAuth, (req, res) => {
|
||||
if (!ok) { res.status(404).json({ error: 'Client introuvable' }); return }
|
||||
res.json({ ok: true })
|
||||
})
|
||||
app.put('/api/torrent-clients/:uuid', requireAuth, (req, res) => {
|
||||
const { name, type, config } = req.body
|
||||
if (!name || !type || !config) { res.status(400).json({ error: 'name, type et config requis' }); return }
|
||||
if (!getDriver(type)) { res.status(400).json({ error: `Type inconnu : ${type}` }); return }
|
||||
const updated = updateClient(String(req.params.uuid), name, type, config)
|
||||
if (!updated) { res.status(404).json({ error: 'Client introuvable' }); return }
|
||||
res.json(sanitizeClient(updated))
|
||||
})
|
||||
app.post('/api/torrent-clients/test-config', requireAuth, async (req, res) => {
|
||||
const { type, config } = req.body
|
||||
const { type, config, uuid } = req.body
|
||||
if (!type || !config) { res.status(400).json({ error: 'type et config requis' }); return }
|
||||
const driver = getDriver(type)
|
||||
if (!driver) { res.status(400).json({ error: `Type inconnu : ${type}` }); return }
|
||||
|
||||
if (uuid) {
|
||||
const oldClient = getClient(uuid)
|
||||
if (oldClient) {
|
||||
for (const field of driver.definition.fields) {
|
||||
if (field.type === 'password' && config[field.key] === '••••••••') {
|
||||
config[field.key] = oldClient.config[field.key]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
res.json(await driver.test(config))
|
||||
})
|
||||
app.post('/api/torrent-clients/:uuid/test', requireAuth, async (req, res) => {
|
||||
|
||||
@@ -103,6 +103,30 @@ export function addClient(name: string, type: string, config: Record<string, str
|
||||
return client
|
||||
}
|
||||
|
||||
export function updateClient(uuid: string, name: string, type: string, config: Record<string, string | number>): SavedClient | null {
|
||||
const clients = loadClients()
|
||||
const index = clients.findIndex(c => c.uuid === uuid)
|
||||
if (index === -1) return null
|
||||
|
||||
const oldClient = clients[index]
|
||||
const definition = getDriver(type)?.definition
|
||||
|
||||
// Conserver les mots de passe si "••••••••" est envoyé
|
||||
if (definition) {
|
||||
for (const field of definition.fields) {
|
||||
if (field.type === 'password' && config[field.key] === '••••••••') {
|
||||
config[field.key] = oldClient.config[field.key]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const client: SavedClient = { uuid, name, type, config }
|
||||
clients[index] = client
|
||||
saveClients(clients)
|
||||
logger.info('torrent-clients', `Client mis à jour : "${name}" (${type})`)
|
||||
return client
|
||||
}
|
||||
|
||||
export function removeClient(uuid: string): boolean {
|
||||
const clients = loadClients()
|
||||
const target = clients.find(c => c.uuid === uuid)
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
</div>
|
||||
<div class="flex items-center gap-1">
|
||||
<button @click="testClient(client.uuid)" class="btn-ghost text-xs">Tester</button>
|
||||
<button @click="editClient(client)" class="btn-ghost text-xs text-blue-400 hover:text-blue-300">Modifier</button>
|
||||
<button @click="deleteClient(client.uuid)" class="btn-ghost text-xs text-red-400 hover:text-red-300">Supprimer</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -49,7 +50,7 @@
|
||||
<div class="bg-card border border-border rounded-xl w-full max-w-md p-6 flex flex-col gap-4">
|
||||
|
||||
<div class="flex items-center justify-between">
|
||||
<h3 class="text-sm font-semibold text-primary">Ajouter un client</h3>
|
||||
<h3 class="text-sm font-semibold text-primary">{{ modal.uuid ? 'Modifier un client' : 'Ajouter un client' }}</h3>
|
||||
<button @click="closeModal" class="text-muted hover:text-primary transition-colors">✕</button>
|
||||
</div>
|
||||
|
||||
@@ -116,6 +117,7 @@ const healthStatus = ref<Record<string, boolean | null>>({})
|
||||
|
||||
const modal = ref({
|
||||
open : false,
|
||||
uuid : null as string | null,
|
||||
name : '',
|
||||
type : '',
|
||||
config : {} as Record<string, string>,
|
||||
@@ -171,7 +173,20 @@ async function deleteClient(uuid: string) {
|
||||
}
|
||||
|
||||
function openAddModal() {
|
||||
modal.value = { open: true, name: '', type: '', config: {}, testing: false, saving: false, tested: false }
|
||||
modal.value = { open: true, uuid: null, name: '', type: '', config: {}, testing: false, saving: false, tested: false }
|
||||
}
|
||||
|
||||
function editClient(client: any) {
|
||||
modal.value = {
|
||||
open : true,
|
||||
uuid : client.uuid,
|
||||
name : client.name,
|
||||
type : client.type,
|
||||
config : JSON.parse(JSON.stringify(client.config)),
|
||||
testing: false,
|
||||
saving : false,
|
||||
tested : true,
|
||||
}
|
||||
}
|
||||
|
||||
function closeModal() { modal.value.open = false }
|
||||
@@ -193,7 +208,7 @@ async function testNewClient() {
|
||||
try {
|
||||
const res = await fetch('/api/torrent-clients/test-config', {
|
||||
method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include',
|
||||
body: JSON.stringify({ type: modal.value.type, config: modal.value.config }),
|
||||
body: JSON.stringify({ type: modal.value.type, config: modal.value.config, uuid: modal.value.uuid }),
|
||||
})
|
||||
const { ok, message } = await res.json()
|
||||
modal.value.tested = ok
|
||||
@@ -209,8 +224,12 @@ async function saveClient() {
|
||||
if (!modal.value.tested) return
|
||||
modal.value.saving = true
|
||||
try {
|
||||
const res = await fetch('/api/torrent-clients', {
|
||||
method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include',
|
||||
const isEdit = !!modal.value.uuid
|
||||
const method = isEdit ? 'PUT' : 'POST'
|
||||
const url = isEdit ? `/api/torrent-clients/${modal.value.uuid}` : '/api/torrent-clients'
|
||||
|
||||
const res = await fetch(url, {
|
||||
method, headers: { 'Content-Type': 'application/json' }, credentials: 'include',
|
||||
body: JSON.stringify({
|
||||
name : modal.value.name || clientLabel(modal.value.type),
|
||||
type : modal.value.type,
|
||||
@@ -219,10 +238,15 @@ async function saveClient() {
|
||||
})
|
||||
if (res.ok) {
|
||||
const client = await res.json()
|
||||
clients.value.push(client)
|
||||
if (isEdit) {
|
||||
const index = clients.value.findIndex(c => c.uuid === client.uuid)
|
||||
if (index !== -1) clients.value[index] = client
|
||||
} else {
|
||||
clients.value.push(client)
|
||||
}
|
||||
refreshHealth(client.uuid)
|
||||
closeModal()
|
||||
toast('Client enregistré ✓', 'success')
|
||||
toast(isEdit ? 'Client modifié ✓' : 'Client enregistré ✓', 'success')
|
||||
} else {
|
||||
const { error } = await res.json()
|
||||
toast(error ?? "Erreur lors de l'enregistrement", 'error')
|
||||
|
||||
Reference in new issue
Block a user