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:
@@ -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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user