diff --git a/CHANGELOG.md b/CHANGELOG.md index 7749939..a02c8b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog - project.linkedcorp.net +## [2.8.2] - 2026-10-01 +### Added +- Al aplicar una actualización desde Configuración → Actualizaciones, ahora se bloquea toda la + pantalla con un círculo girando y un porcentaje estimado en el centro ("Actualizando…") hasta + que el sistema termina de reiniciarse; al confirmar que ya está al día, la página se recarga + sola para traer los cambios. +### Fixed +- El seguimiento de la actualización (`pollUntilUpToDate`) nunca corría en el entorno de + desarrollo por un efecto de React mal planteado (`mountedRef` quedaba en `false` para siempre + por el doble-render de `React.StrictMode`) — no afectaba producción, pero sí ocultaba el bug al + probar en dev. + ## [2.8.1] - 2026-10-01 ### Added - Catálogo de íconos de "Contraseñas" ampliado con ~70 logos de marcas conocidas (Google, GitHub, diff --git a/VERSION b/VERSION index dbe5900..1817afe 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.8.1 +2.8.2 diff --git a/backend/package.json b/backend/package.json index 86aea9a..058827e 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,6 +1,6 @@ { "name": "project-linkedcorp-backend", - "version": "2.8.1", + "version": "2.8.2", "private": true, "type": "module", "main": "src/server.js", diff --git a/frontend/package.json b/frontend/package.json index 82a92b1..36c4127 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,7 +1,7 @@ { "name": "project-linkedcorp-frontend", "private": true, - "version": "2.8.1", + "version": "2.8.2", "type": "module", "scripts": { "dev": "vite", diff --git a/frontend/src/i18n/messages.js b/frontend/src/i18n/messages.js index e065b7e..f40c757 100644 --- a/frontend/src/i18n/messages.js +++ b/frontend/src/i18n/messages.js @@ -81,15 +81,16 @@ export const messages = { updater_criticality_alta: 'Alta', updater_criticality_media: 'Media', updater_criticality_baja: 'Baja', - updater_rollback_hint: 'Antes de actualizar, respalda la base de datos. Si algo sale mal, restaura ese respaldo y volvé al commit anterior a esta actualización en el historial de Gitea.', + updater_rollback_hint: 'Antes de actualizar, respalda la base de datos. Si algo sale mal, restaura ese respaldo y vuelve al commit anterior a esta actualización en el historial de Gitea.', updater_apply: 'Actualizar', updater_applying: 'Actualizando…', updater_apply_confirm_title: 'Aplicar actualización', updater_apply_confirm_msg: 'Se va a respaldar la base de datos, descargar el código nuevo, aplicar migraciones si las hay, y reiniciar el sistema. Puede tardar unos minutos y la app quedará momentáneamente fuera de servicio. Vas de', updater_apply_confirm_ok: 'Sí, actualizar', - updater_apply_started: 'Actualización iniciada. El sistema se reiniciará en unos minutos — refrescá la página luego.', + updater_apply_started: 'Actualización iniciada. El sistema se reiniciará en unos minutos.', updater_applying_hint: 'Actualización en curso — no cierres esta pestaña. El sistema se reiniciará solo al terminar.', - updater_apply_not_configured: 'La auto-actualización no está habilitada en este servidor. Pedile a tu administrador que aplique esta actualización por SSH.', + updater_apply_timeout: 'La actualización está tardando más de lo esperado. Revisa el estado en unos minutos o verifica el servidor.', + updater_apply_not_configured: 'La auto-actualización no está habilitada en este servidor. Pide a tu administrador que aplique esta actualización por SSH.', updater_login_popup_title: 'Hay una actualización pendiente', updater_login_popup_msg: 'El sistema tiene una versión nueva disponible:', updater_login_popup_go: 'Ver actualizaciones', @@ -417,8 +418,9 @@ export const messages = { updater_apply_confirm_title: 'Apply update', updater_apply_confirm_msg: 'This will back up the database, pull the new code, apply migrations if any, and restart the system. It may take a few minutes and the app will be briefly unavailable. Going from', updater_apply_confirm_ok: 'Yes, update', - updater_apply_started: 'Update started. The system will restart in a few minutes — refresh the page afterward.', + updater_apply_started: 'Update started. The system will restart in a few minutes.', updater_applying_hint: 'Update in progress — do not close this tab. The system will restart on its own when done.', + updater_apply_timeout: 'The update is taking longer than expected. Check back in a few minutes or verify the server.', updater_apply_not_configured: 'Auto-update is not enabled on this server. Ask your administrator to apply this update via SSH.', updater_login_popup_title: 'There is a pending update', updater_login_popup_msg: 'A new version of the system is available:', diff --git a/frontend/src/pages/Configuracion/UpdaterPanel.jsx b/frontend/src/pages/Configuracion/UpdaterPanel.jsx index 3f25a12..2cd7391 100644 --- a/frontend/src/pages/Configuracion/UpdaterPanel.jsx +++ b/frontend/src/pages/Configuracion/UpdaterPanel.jsx @@ -16,27 +16,44 @@ export default function UpdaterPanel() { const { data: status, loading, error, reload, setData } = useAsync(getUpdateStatus, []); const runAction = useAction(); const [applying, setApplying] = useState(false); + const [progressPct, setProgressPct] = useState(0); const mountedRef = useRef(true); - useEffect(() => () => { mountedRef.current = false; }, []); + // Setup explicito (no solo el cleanup): con React.StrictMode en dev el efecto se dispara + // mount->cleanup->mount de una, y si el setup no reactiva el flag queda en false para siempre + // aunque el componente SI este montado -- pollUntilUpToDate nunca correria. + useEffect(() => { + mountedRef.current = true; + return () => { mountedRef.current = false; }; + }, []); // El servidor se reinicia a mitad de la actualizacion (backend y frontend), asi que durante - // ese rato las llamadas van a fallar por un momento -- se ignoran y se sigue insistiendo. - // Cuando confirma que ya esta al dia, el boton "Actualizar" desaparece solo (esta gateado por - // !status.upToDate mas abajo) sin depender de que la pestaña se recargue. + // ese rato las llamadas van a fallar por un momento -- se ignoran y se sigue insistiendo. El + // porcentaje es una estimacion sobre el tope de espera (no hay progreso real del script), pero + // alcanza para que la persona sepa que sigue avanzando y no quede mirando una pantalla muerta. + // Al confirmar que ya esta al dia, se recarga la pestaña sola para traer el build nuevo. async function pollUntilUpToDate() { for (let i = 0; i < POLL_MAX_ATTEMPTS && mountedRef.current; i++) { await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS)); if (!mountedRef.current) return; + setProgressPct(Math.min(95, Math.round(((i + 1) / POLL_MAX_ATTEMPTS) * 100))); try { const result = await getUpdateStatus(); if (!mountedRef.current) return; setData(result); - if (result.upToDate) { setApplying(false); return; } + if (result.upToDate) { + setProgressPct(100); + await new Promise((resolve) => setTimeout(resolve, 600)); + window.location.reload(); + return; + } } catch { // backend reiniciandose -- se ignora y se reintenta en el proximo ciclo } } - if (mountedRef.current) setApplying(false); + if (mountedRef.current) { + setApplying(false); + pushToast({ tone: 'warning', message: t('updater_apply_timeout') }); + } } function confirmApply() { @@ -49,6 +66,7 @@ export default function UpdaterPanel() { { label: t('updater_apply_confirm_ok'), variant: 'danger', onClick: () => runAction(async () => { + setProgressPct(0); setApplying(true); await applyUpdate(); pushToast({ tone: 'success', message: t('updater_apply_started') }); @@ -60,6 +78,18 @@ export default function UpdaterPanel() { return (
+ {applying && ( +
+
+
+
+
{t('updater_applying')}
+
{progressPct}%
+
+
+
+ )} +