feat: version 2.2.0 -- boton real de actualizacion (auto-update)
POST /api/updater/apply dispara scripts/apply-update.sh (backup + git pull + migraciones + build + restart) en un proceso separado. Apagado por defecto: solo se activa si el servidor define UPDATE_APP_DIR/UPDATE_BACKEND_SERVICE/UPDATE_FRONTEND_SERVICE en su backend/.env. Sin esa config, el panel se comporta igual que antes. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
import { RefreshCw } from 'lucide-react';
|
||||
import { useState } from 'react';
|
||||
import { RefreshCw, Download } from 'lucide-react';
|
||||
import { useUi } from '../../context/ui-context.jsx';
|
||||
import { useAsync } from '../../hooks/useAsync.js';
|
||||
import { getUpdateStatus } from '../../services/updater.js';
|
||||
import { useAsync, useAction } from '../../hooks/useAsync.js';
|
||||
import { getUpdateStatus, applyUpdate } from '../../services/updater.js';
|
||||
import { Button } from '../../components/Button.jsx';
|
||||
import { Badge } from '../../components/Badge.jsx';
|
||||
import { InfoBar } from '../../components/InfoBar.jsx';
|
||||
@@ -9,18 +10,46 @@ import { InfoBar } from '../../components/InfoBar.jsx';
|
||||
const CRITICALITY_COLOR = { alta: 'danger', media: 'warning', baja: 'success' };
|
||||
|
||||
export default function UpdaterPanel() {
|
||||
const { t } = useUi();
|
||||
const { t, openDialog, pushToast } = useUi();
|
||||
const { data: status, loading, error, reload } = useAsync(getUpdateStatus, []);
|
||||
const runAction = useAction();
|
||||
const [applying, setApplying] = useState(false);
|
||||
|
||||
function confirmApply() {
|
||||
openDialog({
|
||||
tone: 'warning',
|
||||
title: t('updater_apply_confirm_title'),
|
||||
message: `${t('updater_apply_confirm_msg')} v${status.currentVersion} → v${status.latestVersion}`,
|
||||
actions: [
|
||||
{ label: t('common_cancel'), variant: 'standard' },
|
||||
{
|
||||
label: t('updater_apply_confirm_ok'), variant: 'danger',
|
||||
onClick: () => runAction(async () => {
|
||||
setApplying(true);
|
||||
await applyUpdate();
|
||||
pushToast({ tone: 'success', message: t('updater_apply_started') });
|
||||
}).finally(() => setApplying(false)),
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 12 }}>
|
||||
<Button variant="standard" icon={RefreshCw} onClick={reload} disabled={loading}>
|
||||
<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')}
|
||||
</Button>
|
||||
{status && !status.upToDate && status.applyAvailable && (
|
||||
<Button variant="accent" icon={Download} onClick={confirmApply} disabled={applying}>
|
||||
{applying ? t('updater_applying') : t('updater_apply')}
|
||||
</Button>
|
||||
)}
|
||||
{status && <span className="text-muted" style={{ fontSize: '.78rem' }}>{t('updater_current_version')} v{status.currentVersion}</span>}
|
||||
</div>
|
||||
|
||||
{applying && <InfoBar tone="warning" dismissible={false}>{t('updater_applying_hint')}</InfoBar>}
|
||||
|
||||
{loading && <div className="text-muted">{t('common_loading')}</div>}
|
||||
{error && <InfoBar tone="error" dismissible={false}>{error}</InfoBar>}
|
||||
|
||||
@@ -31,6 +60,9 @@ export default function UpdaterPanel() {
|
||||
{status && !status.upToDate && (
|
||||
<div>
|
||||
<InfoBar tone="error" dismissible={false}>{t('updater_restart_required')}</InfoBar>
|
||||
{!status.applyAvailable && (
|
||||
<InfoBar tone="warning" dismissible={false}>{t('updater_apply_not_configured')}</InfoBar>
|
||||
)}
|
||||
{status.hasMigrations && (
|
||||
<InfoBar tone="warning" dismissible={false}>{t('updater_has_migrations')}</InfoBar>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user