fix: version 2.8.2 -- overlay de progreso al actualizar + fix mountedRef en StrictMode
Agrega un overlay de pantalla completa (circulo girando + porcentaje estimado + "Actualizando...") que bloquea todo clic mientras corre la auto-actualizacion, y recarga la pagina sola al terminar. De paso corrige un bug real: el useEffect de mountedRef no reactivaba el flag en su setup, asi que con React.StrictMode (dev) el polling de estado nunca llegaba a correr -- no afectaba produccion (StrictMode no duplica efectos ahi) pero ocultaba el problema al probar en dev. Tambien se corrigen un par de voseos que quedaron en los textos de Actualizaciones. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "project-linkedcorp-backend",
|
||||
"version": "2.8.1",
|
||||
"version": "2.8.2",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "src/server.js",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "project-linkedcorp-frontend",
|
||||
"private": true,
|
||||
"version": "2.8.1",
|
||||
"version": "2.8.2",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -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:',
|
||||
|
||||
@@ -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 (
|
||||
<div>
|
||||
{applying && (
|
||||
<div className="update-overlay" role="alert" aria-live="assertive">
|
||||
<div className="update-spinner-wrap">
|
||||
<div className="update-spinner-ring" />
|
||||
<div className="update-spinner-center">
|
||||
<div className="update-spinner-label">{t('updater_applying')}</div>
|
||||
<div className="update-spinner-pct">{progressPct}%</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 12, flexWrap: 'wrap' }}>
|
||||
<Button variant="standard" icon={RefreshCw} onClick={reload} disabled={loading || applying}>
|
||||
{t('updater_check')}
|
||||
|
||||
@@ -390,6 +390,28 @@ table.list-table { width: 100%; border-collapse: collapse; font-size: .8rem; }
|
||||
.vault-icon-option.brand, .vault-card-icon.brand { background: #fff; }
|
||||
.vault-icon-option.brand.selected { border-color: rgb(var(--accent)); }
|
||||
|
||||
/* --- Overlay de actualizacion (ver UpdaterPanel.jsx) ---
|
||||
Bloquea toda la pantalla (no solo el panel) mientras corre scripts/apply-update.sh, porque el
|
||||
backend y el frontend se reinician a mitad de proceso -- cualquier clic en ese rato no tiene
|
||||
adonde ir. z-index por encima de dialog-overlay/detail-popup-overlay a proposito. */
|
||||
.update-overlay {
|
||||
position: fixed; inset: 0; background: rgb(0 0 0 / .7); z-index: 300;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
}
|
||||
.update-spinner-wrap { position: relative; width: 150px; height: 150px; flex: none; }
|
||||
.update-spinner-ring {
|
||||
position: absolute; inset: 0; border-radius: 999px;
|
||||
border: 6px solid rgb(255 255 255 / .2); border-top-color: rgb(var(--accent));
|
||||
animation: update-spin 0.9s linear infinite;
|
||||
}
|
||||
.update-spinner-center {
|
||||
position: absolute; inset: 0; display: flex; flex-direction: column; gap: 2px;
|
||||
align-items: center; justify-content: center; text-align: center; color: #fff;
|
||||
}
|
||||
.update-spinner-label { font-size: .78rem; font-weight: 600; opacity: .85; }
|
||||
.update-spinner-pct { font-size: 1.7rem; font-weight: 700; }
|
||||
@keyframes update-spin { to { transform: rotate(360deg); } }
|
||||
|
||||
/* --- DetailPopup (ver DetailPopup.jsx) ---
|
||||
Al elegir un item de un listado, la lista queda inactiva detras del overlay y el popup
|
||||
presenta la pantalla de info/edicion completa. Regla dura: NUNCA se cierra con clic afuera
|
||||
|
||||
Reference in New Issue
Block a user