fix(admin): XSS stocké dans l'activité, timeout des requêtes, déblocage manuel
activitySummary() injectait crawl_run.error en innerHTML sans échappement. Ce message provient d'exceptions du crawler, qui contiennent du contenu scrapé sur Metal Archives — donc influençable depuis l'extérieur. api() n'avait aucun timeout : une API muette (pool DB saturé, upstream nginx silencieux) laissait la vue bloquée sur « Chargement… » indéfiniment. reset-errors vise désormais aussi les lignes coincées en 'processing', en forçage manuel du mécanisme d'auto-réparation du worker. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
cc6be5e341
commit
da24d2a8a1
2 changed files with 21 additions and 6 deletions
|
|
@ -33,11 +33,21 @@ const state = {
|
|||
// API
|
||||
// ------------------------------------------------------------------
|
||||
async function api(path, opts = {}) {
|
||||
const res = await fetch(path, {
|
||||
// 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 ?? ""}`;
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
Loading…
Reference in a new issue