diff --git a/apps/admin/site/app.js b/apps/admin/site/app.js index 3cfcee8..f57bf88 100644 --- a/apps/admin/site/app.js +++ b/apps/admin/site/app.js @@ -33,11 +33,21 @@ const state = { // API // ------------------------------------------------------------------ async function api(path, opts = {}) { - const res = await fetch(path, { - credentials: "include", - headers: { "Content-Type": "application/json", ...(opts.headers || {}) }, - ...opts, - }); + // Timeout explicite : sans lui, une API qui ne répond jamais (pool DB saturé, + // upstream nginx muet) laisse la vue bloquée sur « Chargement… » indéfiniment. + const ctrl = new AbortController(); + const t = setTimeout(() => ctrl.abort(), opts.timeoutMs || 20000); + let res; + try { + res = await fetch(path, { + credentials: "include", + headers: { "Content-Type": "application/json", ...(opts.headers || {}) }, + signal: ctrl.signal, + ...opts, + }); + } finally { + clearTimeout(t); + } if (res.status === 401) { showLogin(); throw new Error("unauthorized"); @@ -1168,7 +1178,7 @@ function activitySummary(row) { if (sum.bands_new) parts.push(`${fmtNum(sum.bands_new)} nouveaux`); if (sum.bands_updated) parts.push(`${fmtNum(sum.bands_updated)} MAJ`); if (sum.bands_enriched) parts.push(`${fmtNum(sum.bands_enriched)} enrichis`); - if (sum.error) parts.push(`erreur : ${sum.error}`); + if (sum.error) parts.push(`erreur : ${esc(sum.error)}`); return parts.join(" · ") || "—"; } return `${esc(sum.target_table || "")} #${sum.target_id ?? ""}`; diff --git a/apps/api/src/adminRoutes.js b/apps/api/src/adminRoutes.js index c968cc0..05f96c8 100644 --- a/apps/api/src/adminRoutes.js +++ b/apps/api/src/adminRoutes.js @@ -872,6 +872,10 @@ export default async function adminRoutes(fastify, opts) { // ------------------------------------------------------------------ // Géocodage — actions sur band_locations (nouveau pipeline) // ------------------------------------------------------------------ + // Débloque les lignes coincées en 'processing' (worker tué en plein + // traitement) en plus de l'ancien statut 'error' (legacy). Le worker se répare + // aussi tout seul au bout de GEOCODE_STUCK_PROCESSING_MIN ; ce bouton est un + // forçage manuel pour ne pas attendre. fastify.post("/admin/api/locations/reset-errors", async (req, reply) => { try { const r = await pool.query(` @@ -879,6 +883,7 @@ export default async function adminRoutes(fastify, opts) { SET geocode_status='queued', geocode_tries_geo=0, geocode_error=NULL, geocode_next_at=now(), updated_at=now() WHERE geocode_status = 'error' + OR (geocode_status = 'processing' AND updated_at < now() - interval '5 minutes') `); const count = r.rowCount; await pool.query(