From 3de9e7433164590790b5da7ff68738bb2cf10ac1 Mon Sep 17 00:00:00 2001 From: unfr Date: Sat, 27 Sep 2025 15:06:16 +0200 Subject: [PATCH] =?UTF-8?q?Ajout=20des=20fonctionnalit=C3=A9s=20de=20s?= =?UTF-8?q?=C3=A9lection=20multiple=20et=20unification=20de=20l'authentifi?= =?UTF-8?q?cation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Nouvelles fonctionnalités ### Sélection multiple et actions en lot - Ajout d'une colonne de checkboxes avec case "Tout sélectionner" - Panneau d'actions en lot (édition et suppression de plusieurs éléments) - Modals dédiées pour l'édition et suppression en lot - Gestion intelligente de la sélection (état indéterminé) - Routes serveur `/bulk-edit` et `/bulk-delete` avec validation sécurisée ### Amélioration des modals de confirmation - Modal de confirmation pour le renvoi (remplace le confirm() basique) - Interface cohérente avec les autres modals - Gestion clavier (Escape/Enter) pour toutes les modals ### Unification du système d'authentification - Fusion des deux systèmes de login (DB + config) en une seule route - Priorité à la base de données avec fallback sur le fichier config - Logs détaillés avec émojis pour faciliter le débogage - Robustesse améliorée (admin de secours si DB en panne) ## Améliorations configuration et posteur - Configuration API pour le renvoi vers le site principal (config.js) - Correction du calcul de taille pour les liens symboliques (posteur.sh) - Support amélioré du mode symlink avec option -L pour du - Ajout .gitignore pour exclure le dossier .specstory ## Améliorations techniques - Interface utilisateur moderne avec compteur de sélection - Mise à jour visuelle en temps réel - Validation côté serveur avec gestion d'erreurs - Conservation de toutes les fonctionnalités existantes --- .gitignore | 1 + autopost/config.js | 6 +- autopost/posteur.sh | 16 +- autopost/public/autopost.js | 298 ++++++++++++++++++++++++++++++++++- autopost/server.js | 248 ++++++++++++++++++++++++++++- autopost/views/autopost.html | 68 ++++++++ 6 files changed, 623 insertions(+), 14 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d56a18c --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.specstory \ No newline at end of file diff --git a/autopost/config.js b/autopost/config.js index eca4207..8d927d4 100644 --- a/autopost/config.js +++ b/autopost/config.js @@ -36,5 +36,9 @@ module.exports = { auth: { username: 'user', password: 'pass' - } + }, + + // Configuration pour le renvoi vers le site principal + apiUrl: 'A NOUS DEMANDER SUR DISCORD', + apiKey: 'A RETROUVER DANS VOTRE PROFIL' }; \ No newline at end of file diff --git a/autopost/posteur.sh b/autopost/posteur.sh index 73378ff..5c484cf 100644 --- a/autopost/posteur.sh +++ b/autopost/posteur.sh @@ -78,11 +78,25 @@ while true; do echo -e "${JAUNE}VERIF DU NZB${NORMAL}" nzbsizebit=$(bash ${ANALYZER} "${DOSSIER_NZB_ATTENTE}${FILESANSEXT}.nzb" | jq '.Taillebit') echo -e "NZB_SIZE : ${nzbsizebit}" + # Détermine si on est en mode "symlink" selon la conf + symlink_mode=0 + case "${MOVE_CMD:-}" in + "cp -rs"|"ln -s") symlink_mode=1 ;; + esac + if [[ "${name}" =~ \.(iso)$ ]]; then - jsonsizebit=$(du -b -c "${name}" | grep total | awk '{ print $1 }') + if (( symlink_mode )); then + # Suivre le lien symbolique pour obtenir la taille réelle + jsonsizebit=$(du -Lb -c -- "${name}" | awk '/total/ {print $1}') + else + # Comportement inchangé + jsonsizebit=$(du -b -c -- "${name}" | awk '/total/ {print $1}') + fi else + # Comportement inchangé jsonsizebit=$(jq -r '.media.track[] | select(."@type" == "General") | .FileSize' "${DOSSIER_NFO}${FILESANSEXT}.json") fi + echo -e "MEDIAINFO_SIZE : ${jsonsizebit}" if [[ ${nzbsizebit} -le ${jsonsizebit} ]] || [[ ${nzbsizebit} = "NAN" ]]; then diff --git a/autopost/public/autopost.js b/autopost/public/autopost.js index 7f55eef..76406c5 100644 --- a/autopost/public/autopost.js +++ b/autopost/public/autopost.js @@ -58,6 +58,10 @@ function updateTable(rows) { ? ' | DL' : ''; + var resendLink = (parseInt(row.status) === 1) + ? ' | Renvoyer' + : ''; + var tr = '' + '' + esc(row.nom) + '' + @@ -66,7 +70,7 @@ function updateTable(rows) { '' + 'Editer | ' + 'Supprimer' + - logLink + mediainfoLink + dlLink + + logLink + mediainfoLink + dlLink + resendLink + '' + ''; @@ -261,6 +265,25 @@ $(document).ready(function() { pendingDeleteId = null; } + // Fonctions pour la modal de renvoi + var pendingResendId = null; + var pendingResendFilename = null; + + function openConfirmResendModal(id, filename) { + pendingResendId = id; + pendingResendFilename = filename; + $('#confirmResendItemName').text(filename || ('ID ' + id)); + $('#confirmResendModal').removeClass('hidden').fadeIn(120); + } + + function closeConfirmResendModal() { + $('#confirmResendModal').fadeOut(120, function() { + $(this).addClass('hidden'); + }); + pendingResendId = null; + pendingResendFilename = null; + } + function showToast(msg) { $('#toastMsg').text(msg || 'Opération effectuée'); $('#toast').removeClass('hidden').fadeIn(120); @@ -282,17 +305,33 @@ $(document).ready(function() { closeConfirmModal(); }); - // Clic sur l’overlay pour fermer + // Clic sur l'overlay pour fermer $('#confirmDeleteModal').on('click', function(e) { if (e.target === this) { closeConfirmModal(); } }); + // Gestionnaires pour la modal de renvoi + $('#cancelResendBtn, #confirmResendClose').on('click', function() { + closeConfirmResendModal(); + }); + + // Clic sur l'overlay pour fermer la modal de renvoi + $('#confirmResendModal').on('click', function(e) { + if (e.target === this) { closeConfirmResendModal(); } + }); + // Accessibilité clavier: Esc = fermer, Enter = confirmer $(document).on('keydown', function(e) { - var modalVisible = !$('#confirmDeleteModal').hasClass('hidden'); - if (!modalVisible) return; - if (e.key === 'Escape') { closeConfirmModal(); } - if (e.key === 'Enter') { $('#confirmDeleteBtn').click(); } + var deleteModalVisible = !$('#confirmDeleteModal').hasClass('hidden'); + var resendModalVisible = !$('#confirmResendModal').hasClass('hidden'); + + if (deleteModalVisible) { + if (e.key === 'Escape') { closeConfirmModal(); } + if (e.key === 'Enter') { $('#confirmDeleteBtn').click(); } + } else if (resendModalVisible) { + if (e.key === 'Escape') { closeConfirmResendModal(); } + if (e.key === 'Enter') { $('#confirmResendBtn').click(); } + } }); // Confirmer et supprimer via AJAX @@ -318,6 +357,38 @@ $(document).ready(function() { }); }); + // Confirmer et renvoyer via AJAX + $('#confirmResendBtn').on('click', function() { + if (!pendingResendId) return; + var releaseId = pendingResendId; + var filename = pendingResendFilename; + + var $button = $(this); + var originalText = $button.text(); + $button.text('Envoi...'); + + $.ajax({ + url: '/autopost/resend/' + releaseId, + type: 'POST', + success: function(data) { + showToast(data.message || 'Renvoi effectué avec succès'); + console.log('Renvoi réussi pour', filename); + }, + error: function(xhr) { + var errorMsg = 'Erreur lors du renvoi'; + if (xhr.responseJSON && xhr.responseJSON.error) { + errorMsg = xhr.responseJSON.error; + } + alert(errorMsg); + console.error('Erreur renvoi:', xhr.responseJSON); + }, + complete: function() { + $button.text(originalText); + closeConfirmResendModal(); + } + }); + }); + // Affichage log $(document).on('click', '.log-link', function(e) { e.preventDefault(); @@ -419,4 +490,219 @@ $(document).ready(function() { alert('Erreur lors de la copie : ' + err); }); }); + + // Renvoi vers le site principal + $(document).on('click', '.resend-link', function(e) { + e.preventDefault(); + var releaseId = $(this).data('id'); + var filename = $(this).data('filename'); + openConfirmResendModal(releaseId, filename); + }); + + // ==================== GESTION SÉLECTION MULTIPLE ==================== + + function updateBulkActions() { + const selectedCheckboxes = $('.row-checkbox:checked'); + const count = selectedCheckboxes.length; + + if (count > 0) { + $('#bulkActions').removeClass('hidden'); + $('#selectedCount').text(count + ' élément' + (count > 1 ? 's' : '') + ' sélectionné' + (count > 1 ? 's' : '')); + } else { + $('#bulkActions').addClass('hidden'); + } + + // Mettre à jour la checkbox "Tout sélectionner" + const totalCheckboxes = $('.row-checkbox').length; + const allChecked = count === totalCheckboxes && totalCheckboxes > 0; + const someChecked = count > 0 && count < totalCheckboxes; + + $('#selectAll').prop('checked', allChecked); + $('#selectAll').prop('indeterminate', someChecked); + } + + // Checkbox "Tout sélectionner" + $(document).on('click', '#selectAll', function() { + const isChecked = $(this).is(':checked'); + $('.row-checkbox').prop('checked', isChecked); + updateBulkActions(); + }); + + // Checkboxes individuelles + $(document).on('click', '.row-checkbox', function() { + updateBulkActions(); + }); + + // Bouton "Éditer la sélection" + $(document).on('click', '#bulkEditBtn', function() { + const selectedCheckboxes = $('.row-checkbox:checked'); + const count = selectedCheckboxes.length; + + if (count === 0) return; + + $('#bulkEditCount').text(count); + $('#bulkEditModal').removeClass('hidden').fadeIn(120); + }); + + // Bouton "Supprimer la sélection" + $(document).on('click', '#bulkDeleteBtn', function() { + const selectedCheckboxes = $('.row-checkbox:checked'); + const count = selectedCheckboxes.length; + + if (count === 0) return; + + // Construire la liste des éléments à supprimer + let listHTML = ''; + selectedCheckboxes.each(function() { + const name = $(this).data('name'); + const id = $(this).data('id'); + listHTML += `
• ${name} (ID: ${id})
`; + }); + + $('#bulkDeleteCount').text(count); + $('#bulkDeleteList').html(listHTML); + $('#bulkDeleteModal').removeClass('hidden').fadeIn(120); + }); + + // Fermeture des modals en lot + $('#bulkEditClose, #bulkDeleteClose, #cancelBulkDeleteBtn').on('click', function() { + $('#bulkEditModal, #bulkDeleteModal').fadeOut(120, function() { + $(this).addClass('hidden'); + }); + }); + + // Clic sur l'overlay pour fermer les modals en lot + $('#bulkEditModal, #bulkDeleteModal').on('click', function(e) { + if (e.target === this) { + $(this).fadeOut(120, function() { + $(this).addClass('hidden'); + }); + } + }); + + // Soumission du formulaire d'édition en lot + $('#bulkEditForm').on('submit', function(e) { + e.preventDefault(); + + const selectedCheckboxes = $('.row-checkbox:checked'); + const newStatus = $('#bulkStatusSelect').val(); + const ids = []; + + selectedCheckboxes.each(function() { + ids.push($(this).data('id')); + }); + + if (ids.length === 0) return; + + const $submitBtn = $('#bulkEditForm button[type="submit"]'); + const originalText = $submitBtn.text(); + $submitBtn.text('Mise à jour...').prop('disabled', true); + + $.ajax({ + url: '/autopost/bulk-edit', + type: 'POST', + data: { + ids: ids, + status: newStatus, + _csrf: window.__BOOTSTRAP__.csrf + }, + success: function(data) { + // Mettre à jour l'interface pour chaque ligne modifiée + ids.forEach(function(id) { + updateRowStatus(id, newStatus); + }); + + showToast(`${ids.length} élément(s) mis à jour`); + + // Fermer la modal et désélectionner + $('#bulkEditModal').fadeOut(120, function() { + $(this).addClass('hidden'); + }); + $('.row-checkbox').prop('checked', false); + updateBulkActions(); + }, + error: function(xhr) { + let errorMsg = 'Erreur lors de la mise à jour en lot'; + if (xhr.responseJSON && xhr.responseJSON.error) { + errorMsg = xhr.responseJSON.error; + } + alert(errorMsg); + }, + complete: function() { + $submitBtn.text(originalText).prop('disabled', false); + } + }); + }); + + // Confirmation de suppression en lot + $('#confirmBulkDeleteBtn').on('click', function() { + const selectedCheckboxes = $('.row-checkbox:checked'); + const ids = []; + + selectedCheckboxes.each(function() { + ids.push($(this).data('id')); + }); + + if (ids.length === 0) return; + + const $button = $(this); + const originalText = $button.text(); + $button.text('Suppression...').prop('disabled', true); + + $.ajax({ + url: '/autopost/bulk-delete', + type: 'POST', + data: { + ids: ids, + _csrf: window.__BOOTSTRAP__.csrf + }, + success: function(data) { + // Supprimer visuellement les lignes + ids.forEach(function(id) { + $('#row-' + id) + .css('outline', '2px solid rgba(239,68,68,0.6)') + .fadeOut('300', function(){ $(this).remove(); }); + }); + + showToast(`${ids.length} élément(s) supprimé(s)`); + + // Fermer la modal + $('#bulkDeleteModal').fadeOut(120, function() { + $(this).addClass('hidden'); + }); + updateBulkActions(); + }, + error: function(xhr) { + let errorMsg = 'Erreur lors de la suppression en lot'; + if (xhr.responseJSON && xhr.responseJSON.error) { + errorMsg = xhr.responseJSON.error; + } + alert(errorMsg); + }, + complete: function() { + $button.text(originalText).prop('disabled', false); + } + }); + }); + + // Fonction utilitaire pour mettre à jour le statut d'une ligne + function updateRowStatus(id, newStatus) { + const $row = $('#row-' + id); + const $statusCell = $row.find('.status-text'); + + let statusText = ''; + let statusClass = ''; + switch (parseInt(newStatus)) { + case 0: statusText = 'EN ATTENTE'; statusClass = 'bg-cyan-500 text-black font-bold'; break; + case 1: statusText = 'ENVOI TERMINÉ'; statusClass = 'bg-green-300 text-black font-bold'; break; + case 2: statusText = 'ERREUR'; statusClass = 'bg-red-300 text-black font-bold'; break; + case 3: statusText = 'DEJA DISPONIBLE'; statusClass = 'bg-pink-300 text-black font-bold'; break; + case 4: statusText = 'EN COURS'; statusClass = 'bg-yellow-300 text-black font-bold'; break; + default: statusText = 'INCONNU'; + } + + $statusCell.removeClass().addClass('px-4 py-2 border border-gray-700 status-text whitespace-nowrap ' + statusClass); + $statusCell.text(statusText); + $row.attr('data-status', newStatus); + } }); diff --git a/autopost/server.js b/autopost/server.js index f85786f..99546a7 100644 --- a/autopost/server.js +++ b/autopost/server.js @@ -84,16 +84,22 @@ function renderRow(row) { const dlLink = (parseInt(row.status) === 1) ? ' | DL' : ''; + const resendLink = (parseInt(row.status) === 1) + ? ' | Renvoyer' + : ''; return ` + + + ${esc(row.nom)} ${statusText} ${row.id} Editer | Supprimer - ${logLink}${mediainfoLink}${dlLink} + ${logLink}${mediainfoLink}${dlLink}${resendLink} `; } @@ -207,14 +213,66 @@ autopostRouter.get('/login', (req, res) => { `); }); -autopostRouter.post('/login', (req, res) => { - const { username, password } = req.body; - if (username === config.auth.username && password === config.auth.password) { +autopostRouter.post('/login', async (req, res) => { + const { username, password } = req.body; + + console.log(`[LOGIN] Tentative de connexion pour "${username}"`); + console.log(`[LOGIN] Password reçu: longueur ${password ? password.length : 0} caractères`); + + // 1. Essayer d'abord l'authentification via la base de données + try { + console.log(`[LOGIN] Tentative d'authentification DB pour "${username}"`); + const [rows] = await db.query( + 'SELECT * FROM core_members WHERE name = ? AND member_group_id IN (1,2) LIMIT 1', + [username] + ); + + console.log(`[LOGIN] Nombre de résultats DB: ${rows.length}`); + + if (rows.length > 0) { + const member = rows[0]; + console.log(`[LOGIN] Utilisateur trouvé en DB:`, { + member_id: member.member_id, + name: member.name, + group: member.member_group_id + }); + + // Vérification du hash Argon2id + console.log(`[LOGIN] Vérification du mot de passe avec Argon2id`); + const valid = await argon2.verify(member.members_pass_hash, password); + console.log(`[LOGIN] Résultat vérification Argon2id: ${valid}`); + + if (valid) { + console.log(`[LOGIN] ✅ Authentification DB réussie pour "${member.name}"`); req.session.authenticated = true; - res.redirect('/autopost'); + req.session.user_id = member.member_id; + req.session.user_name = member.name; + return res.redirect('/autopost'); + } else { + console.warn(`[LOGIN] ❌ Mot de passe DB incorrect pour "${member.name}"`); + } } else { - res.send('Identifiants invalides. Réessayer'); + console.log(`[LOGIN] Aucun utilisateur trouvé en DB pour "${username}"`); } + } catch (err) { + console.error(`[LOGIN] Erreur DB:`, err); + // Continue vers le fallback config + } + + // 2. Fallback : authentification via fichier config + console.log(`[LOGIN] Tentative d'authentification config pour "${username}"`); + if (username === config.auth.username && password === config.auth.password) { + console.log(`[LOGIN] ✅ Authentification config réussie pour "${username}"`); + req.session.authenticated = true; + req.session.user_name = username; + return res.redirect('/autopost'); + } else { + console.log(`[LOGIN] ❌ Authentification config échouée pour "${username}"`); + } + + // 3. Échec des deux méthodes + console.warn(`[LOGIN] 🚫 Échec total d'authentification pour "${username}"`); + res.redirect('login?e=1'); }); autopostRouter.get('/logout', (req, res) => { @@ -607,6 +665,184 @@ autopostRouter.post('/delete/:id', async (req, res) => { } }); +// --------------------------- Opérations en lot ----------------------------- + +// Édition en lot +autopostRouter.post('/bulk-edit', async (req, res) => { + const { ids, status } = req.body; + + if (!Array.isArray(ids) || ids.length === 0) { + return res.status(400).json({ error: 'Liste d\'IDs invalide' }); + } + + const statusInt = parseInt(status, 10); + if (![0, 1, 2, 3, 4].includes(statusInt)) { + return res.status(400).json({ error: 'Statut invalide' }); + } + + // Valider que tous les IDs sont des entiers positifs + const validIds = ids.filter(id => parseInt(id, 10) > 0).map(id => parseInt(id, 10)); + if (validIds.length === 0) { + return res.status(400).json({ error: 'Aucun ID valide' }); + } + + try { + const placeholders = validIds.map(() => '?').join(','); + const query = `UPDATE \`${config.DB_TABLE}\` SET status = ? WHERE id IN (${placeholders})`; + const params = [statusInt, ...validIds]; + + const [result] = await db.query(query, params); + + res.json({ + message: `${result.affectedRows} élément(s) mis à jour`, + updated: result.affectedRows + }); + } catch (err) { + console.error(err.message); + res.status(500).json({ error: 'Erreur DB lors de la mise à jour en lot' }); + } +}); + +// Suppression en lot +autopostRouter.post('/bulk-delete', async (req, res) => { + const { ids } = req.body; + + if (!Array.isArray(ids) || ids.length === 0) { + return res.status(400).json({ error: 'Liste d\'IDs invalide' }); + } + + // Valider que tous les IDs sont des entiers positifs + const validIds = ids.filter(id => parseInt(id, 10) > 0).map(id => parseInt(id, 10)); + if (validIds.length === 0) { + return res.status(400).json({ error: 'Aucun ID valide' }); + } + + try { + const placeholders = validIds.map(() => '?').join(','); + const query = `DELETE FROM \`${config.DB_TABLE}\` WHERE id IN (${placeholders})`; + + const [result] = await db.query(query, validIds); + + res.json({ + message: `${result.affectedRows} élément(s) supprimé(s)`, + deleted: result.affectedRows + }); + } catch (err) { + console.error(err.message); + res.status(500).json({ error: 'Erreur DB lors de la suppression en lot' }); + } +}); + +// --------------------------- Renvoi ----------------------------- +autopostRouter.post('/resend/:id', async (req, res) => { + const id = req.params.id; + + try { + // Récupérer les informations de l'enregistrement + const [rows] = await db.query(`SELECT nom, status FROM \`${config.DB_TABLE}\` WHERE id = ?`, [id]); + + if (rows.length === 0) { + return res.status(404).json({ error: "Enregistrement non trouvé." }); + } + + const row = rows[0]; + const fileName = row.nom; + const status = parseInt(row.status); + + // Vérifier que le statut est "ENVOI TERMINÉ" (status = 1) + if (status !== 1) { + return res.status(400).json({ error: "Seuls les enregistrements avec statut 'ENVOI TERMINÉ' peuvent être renvoyés." }); + } + + const base = safeBaseName(fileName); + if (!base) { + return res.status(400).json({ error: "Nom de fichier invalide." }); + } + + // Chemins des fichiers nécessaires + const subfolder = base.charAt(0).toUpperCase(); + const nzbArchivePath = path.join(config.finishdirectory, subfolder, base + '.7z'); + const jsonPath = path.join(config.infodirectory, base + '.json'); + const bdinfoPath = path.join(config.infodirectory, base + '.bdinfo.txt'); + const quicksummaryPath = path.join(config.infodirectory, base + '.quicksummary.txt'); + + // Vérifier que l'archive NZB existe + if (!fs.existsSync(nzbArchivePath)) { + return res.status(404).json({ error: "Archive NZB non trouvée." }); + } + + // Extraire le NZB temporairement + const tmpDir = path.join(__dirname, 'tmp'); + fs.mkdirSync(tmpDir, { recursive: true }); + const tmpNzbPath = path.join(tmpDir, base + '.nzb'); + + const extractCommand = `7z e "${nzbArchivePath}" -o"${tmpDir}" -y`; + + exec(extractCommand, (extractError) => { + if (extractError) { + console.error(`Erreur lors de l'extraction: ${extractError.message}`); + return res.status(500).json({ error: "Erreur lors de l'extraction du NZB." }); + } + + // Vérifier si c'est un ISO (BDInfo) ou autre (JSON) + const isIso = fileName.toLowerCase().endsWith('.iso'); + let curlCommand; + + if (isIso) { + // Pour les ISO : bdinfo_full + bdinfo_mini + nzb + if (!fs.existsSync(bdinfoPath) || !fs.existsSync(quicksummaryPath)) { + fs.unlinkSync(tmpNzbPath); + return res.status(404).json({ error: "Fichiers BDInfo non trouvés." }); + } + + curlCommand = `curl -s -k -L -m 60 \\ + -F rlsname=${base} \\ + -F bdinfo_full=@${bdinfoPath} \\ + -F bdinfo_mini=@${quicksummaryPath} \\ + -F nzb=@${tmpNzbPath} \\ + -F upload=upload "${config.apiUrl}${config.apiKey}"`; + } else { + // Pour les autres : generated_nfo_json + nzb + if (!fs.existsSync(jsonPath)) { + fs.unlinkSync(tmpNzbPath); + return res.status(404).json({ error: "Fichier JSON non trouvé." }); + } + + curlCommand = `curl -s -k -L -m 60 \\ + -F rlsname=${base} \\ + -F generated_nfo_json=@${jsonPath} \\ + -F nzb=@${tmpNzbPath} \\ + -F upload=upload "${config.apiUrl}${config.apiKey}"`; + } + + // Exécuter la commande curl + exec(curlCommand, (curlError, stdout, stderr) => { + // Nettoyer le fichier temporaire + fs.unlinkSync(tmpNzbPath); + + // Log des sorties curl pour debug + console.log(`=== CURL OUTPUT pour ${fileName} ===`); + console.log(`Command: ${curlCommand}`); + if (stdout) console.log(`STDOUT: ${stdout}`); + if (stderr) console.log(`STDERR: ${stderr}`); + console.log(`================================`); + + if (curlError) { + console.error(`Erreur lors du renvoi: ${curlError.message}`); + return res.status(500).json({ error: "Erreur lors du renvoi vers le site principal." }); + } + + console.log(`Renvoi réussi pour ${fileName}`); + res.json({ success: true, message: "Renvoi effectué avec succès." }); + }); + }); + + } catch (err) { + console.error(err.message); + res.status(500).json({ error: "Erreur lors du renvoi." }); + } +}); + // --------------------------- Filtrage ----------------------------- autopostRouter.get('/filter', async (req, res) => { const status = Number.isFinite(parseInt(req.query.status, 10)) diff --git a/autopost/views/autopost.html b/autopost/views/autopost.html index edb7650..94562cb 100644 --- a/autopost/views/autopost.html +++ b/autopost/views/autopost.html @@ -114,10 +114,28 @@ {{PAGINATION_HTML}} + + +
+ @@ -187,6 +205,56 @@ + + + + + + + + +
+ + Name Status ID