feat: panel de Actualizaciones en Configuracion (solo informativo)
Compara la version instalada contra el CHANGELOG.md de main en Gitea (lectura anonima por HTTPS, sin token) y muestra cada version pendiente con su criticidad -- derivada del digito que cambio (1ro=Alta, 2do=Media, 3ro=Baja, sin etiquetar nada a mano, regla ya documentada en VERSIONADO.md), una descripcion breve, y si trae migraciones de base de datos nuevas. Banner en rojo avisando que la actualizacion requiere reiniciar el sistema. No instala nada automaticamente -- solo informa. Aplicar la actualizacion (git pull, build, migrar, reiniciar servicios) sigue siendo manual, a criterio del administrador de cada instalacion, tal cual DEPLOY_WEB.md ya documenta. Nuevo GET /api/updater/status (solo superadmin). Requiere que el repo en Gitea sea publico con vista anonima habilitada -- documentado en DEPLOY_WEB.md. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -70,6 +70,18 @@ export const messages = {
|
||||
smtp_test_send: 'Enviar prueba',
|
||||
smtp_test_sending: 'Enviando...',
|
||||
smtp_test_success: 'Correo de prueba enviado correctamente a',
|
||||
|
||||
config_updater: 'Actualizaciones',
|
||||
updater_check: 'Buscar actualizaciones',
|
||||
updater_current_version: 'Versión instalada:',
|
||||
updater_up_to_date: 'Estás en la última versión disponible.',
|
||||
updater_available: 'Actualización disponible',
|
||||
updater_restart_required: 'Esta actualización requiere reiniciar el sistema — coordina con tu administrador antes de aplicarla.',
|
||||
updater_has_migrations: 'Incluye cambios en la base de datos (migraciones nuevas).',
|
||||
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.',
|
||||
password_show: 'Mostrar clave',
|
||||
password_hide: 'Ocultar clave',
|
||||
common_new: 'Nuevo',
|
||||
@@ -318,6 +330,18 @@ export const messages = {
|
||||
smtp_test_send: 'Send test',
|
||||
smtp_test_sending: 'Sending...',
|
||||
smtp_test_success: 'Test email sent successfully to',
|
||||
|
||||
config_updater: 'Updates',
|
||||
updater_check: 'Check for updates',
|
||||
updater_current_version: 'Installed version:',
|
||||
updater_up_to_date: 'You are on the latest available version.',
|
||||
updater_available: 'Update available',
|
||||
updater_restart_required: 'This update requires restarting the system — coordinate with your administrator before applying it.',
|
||||
updater_has_migrations: 'Includes database changes (new migrations).',
|
||||
updater_criticality_alta: 'High',
|
||||
updater_criticality_media: 'Medium',
|
||||
updater_criticality_baja: 'Low',
|
||||
updater_rollback_hint: 'Back up the database before updating. If something goes wrong, restore that backup and roll back to the commit before this update in the Gitea history.',
|
||||
password_show: 'Show password',
|
||||
password_hide: 'Hide password',
|
||||
common_new: 'New',
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Navigate } from 'react-router-dom';
|
||||
import { Palette, ListChecks, Flag, FolderKanban, Layers, Mail, Building2 } from 'lucide-react';
|
||||
import { Palette, ListChecks, Flag, FolderKanban, Layers, Mail, Building2, RefreshCw } from 'lucide-react';
|
||||
import { useUi } from '../../context/ui-context.jsx';
|
||||
import { useAuth } from '../../context/auth-context.jsx';
|
||||
import { Expander } from '../../components/Expander.jsx';
|
||||
@@ -8,6 +8,7 @@ import { ComboBox } from '../../components/ComboBox.jsx';
|
||||
import { CatalogEditor } from './CatalogEditor.jsx';
|
||||
import SmtpSettingsPanel from './SmtpSettingsPanel.jsx';
|
||||
import CompanySettingsPanel from './CompanySettingsPanel.jsx';
|
||||
import UpdaterPanel from './UpdaterPanel.jsx';
|
||||
|
||||
export default function ConfiguracionPage() {
|
||||
const { t, theme, setTheme, lang, setLang, buttonMode, setButtonMode } = useUi();
|
||||
@@ -101,6 +102,10 @@ export default function ConfiguracionPage() {
|
||||
<Expander icon={Mail} title={t('config_smtp')}>
|
||||
<SmtpSettingsPanel />
|
||||
</Expander>
|
||||
|
||||
<Expander icon={RefreshCw} title={t('config_updater')}>
|
||||
<UpdaterPanel />
|
||||
</Expander>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
import { RefreshCw } from 'lucide-react';
|
||||
import { useUi } from '../../context/ui-context.jsx';
|
||||
import { useAsync } from '../../hooks/useAsync.js';
|
||||
import { getUpdateStatus } from '../../services/updater.js';
|
||||
import { Button } from '../../components/Button.jsx';
|
||||
import { Badge } from '../../components/Badge.jsx';
|
||||
import { InfoBar } from '../../components/InfoBar.jsx';
|
||||
|
||||
const CRITICALITY_COLOR = { alta: 'danger', media: 'warning', baja: 'success' };
|
||||
|
||||
export default function UpdaterPanel() {
|
||||
const { t } = useUi();
|
||||
const { data: status, loading, error, reload } = useAsync(getUpdateStatus, []);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 12 }}>
|
||||
<Button variant="standard" icon={RefreshCw} onClick={reload} disabled={loading}>
|
||||
{t('updater_check')}
|
||||
</Button>
|
||||
{status && <span className="text-muted" style={{ fontSize: '.78rem' }}>{t('updater_current_version')} v{status.currentVersion}</span>}
|
||||
</div>
|
||||
|
||||
{loading && <div className="text-muted">{t('common_loading')}</div>}
|
||||
{error && <InfoBar tone="error" dismissible={false}>{error}</InfoBar>}
|
||||
|
||||
{status && status.upToDate && (
|
||||
<InfoBar tone="success" dismissible={false}>{t('updater_up_to_date')}</InfoBar>
|
||||
)}
|
||||
|
||||
{status && !status.upToDate && (
|
||||
<div>
|
||||
<InfoBar tone="error" dismissible={false}>{t('updater_restart_required')}</InfoBar>
|
||||
{status.hasMigrations && (
|
||||
<InfoBar tone="warning" dismissible={false}>{t('updater_has_migrations')}</InfoBar>
|
||||
)}
|
||||
|
||||
<div className="panel-card">
|
||||
<h4 style={{ marginTop: 0 }}>
|
||||
{t('updater_available')} — v{status.currentVersion} → v{status.latestVersion}
|
||||
{' '}
|
||||
<Badge color={CRITICALITY_COLOR[status.overallCriticality]}>{t(`updater_criticality_${status.overallCriticality}`)}</Badge>
|
||||
</h4>
|
||||
{status.pending.map((entry) => (
|
||||
<div key={entry.version} style={{ padding: '10px 0', borderTop: '1px solid rgb(var(--line))' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 4 }}>
|
||||
<strong>v{entry.version}</strong>
|
||||
<span className="text-muted" style={{ fontSize: '.72rem' }}>{entry.date}</span>
|
||||
<Badge color={CRITICALITY_COLOR[entry.criticality]}>{t(`updater_criticality_${entry.criticality}`)}</Badge>
|
||||
</div>
|
||||
<div style={{ fontSize: '.8rem', color: 'rgb(var(--muted))' }}>{entry.summary}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="text-muted" style={{ fontSize: '.76rem' }}>{t('updater_rollback_hint')}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
import { api } from './api.js';
|
||||
|
||||
export const getUpdateStatus = () => api.get('/updater/status');
|
||||
Reference in New Issue
Block a user