refactor(webui): remove Migrate from the backup center (moved to Overview)
Migrate now lives at Overview › Migrate › Restore (standalone MigratePage). Strip it out of BackupPage: drop the migrate sidebar item/panel/modal from the fragment, the 'migrate' tab from the allowed set / titleFor / subtitleFor / iconFor, the renderMigrate() call, and the migrate-host/app/confirm click handlers; delete the now-orphaned backup-migrate.js. The backup center is now Dashboard/Backups/Locations/Configuration. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
509eea3188
commit
c449641b9c
@ -27,14 +27,6 @@
|
|||||||
</svg>
|
</svg>
|
||||||
Locations
|
Locations
|
||||||
</div>
|
</div>
|
||||||
<div class="category" data-backup-tab="migrate">
|
|
||||||
<svg class="category-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
||||||
<path d="M3 12h18"></path>
|
|
||||||
<polyline points="12 5 19 12 12 19"></polyline>
|
|
||||||
<path d="M3 5v14"></path>
|
|
||||||
</svg>
|
|
||||||
Migrate
|
|
||||||
</div>
|
|
||||||
<div class="category" data-backup-tab="configuration">
|
<div class="category" data-backup-tab="configuration">
|
||||||
<svg class="category-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
<svg class="category-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
<circle cx="12" cy="12" r="3"></circle>
|
<circle cx="12" cy="12" r="3"></circle>
|
||||||
@ -121,40 +113,6 @@
|
|||||||
<div class="backup-location-list" id="backup-location-list"></div>
|
<div class="backup-location-list" id="backup-location-list"></div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="backup-tabpanel" id="backup-panel-migrate">
|
|
||||||
<div class="backup-card backup-migrate-card">
|
|
||||||
<div class="backup-card-header">
|
|
||||||
<h2>Cross-host migrate <span class="tooltip" title="Pulls a backup taken on another host out of a shared backup location and lays it down here. The destination's existing copy of the app is backed up first (rollback safety), then replaced." style="font-size:.75em;opacity:.7;cursor:help">ℹ️</span></h2>
|
|
||||||
<span class="backup-card-hint">Restore an app from another LibrePortal</span>
|
|
||||||
</div>
|
|
||||||
<div class="backup-migrate-empty" id="backup-migrate-empty" hidden>
|
|
||||||
<!-- Bordered callout panel — matches the per-app task-card
|
|
||||||
visual so the empty state reads as a contained block
|
|
||||||
rather than floating centred text. -->
|
|
||||||
<div class="backup-empty-state" style="border: 1px solid var(--border-color, #2a2a2a); background: var(--surface-2, rgba(255,255,255,0.02)); border-radius: 10px; padding: 28px 24px; margin: 12px 0;">
|
|
||||||
<div style="margin-bottom: 6px; opacity: .7; display: flex; justify-content: center;">
|
|
||||||
<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
|
|
||||||
<path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"></path>
|
|
||||||
<circle cx="12" cy="10" r="3"></circle>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div style="margin-bottom: 14px;">
|
|
||||||
No backups from other hosts visible in any enabled location.<br>
|
|
||||||
Add a <strong>shared backup location</strong> on both hosts to enable cross-host migrate.
|
|
||||||
</div>
|
|
||||||
<button type="button" class="backup-primary-btn" data-action="go-to-locations">
|
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
||||||
<path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"></path>
|
|
||||||
<circle cx="12" cy="10" r="3"></circle>
|
|
||||||
</svg>
|
|
||||||
Open Locations
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="backup-migrate-body" id="backup-migrate-body"></div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="backup-tabpanel" id="backup-panel-configuration">
|
<section class="backup-tabpanel" id="backup-panel-configuration">
|
||||||
<div id="backup-configuration-body"></div>
|
<div id="backup-configuration-body"></div>
|
||||||
</section>
|
</section>
|
||||||
@ -220,20 +178,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="backup-modal" id="backup-migrate-modal">
|
|
||||||
<div class="backup-modal-inner">
|
|
||||||
<div class="backup-modal-header">
|
|
||||||
<h3>Migrate from another LibrePortal</h3>
|
|
||||||
<button class="backup-modal-close" data-close-modal>×</button>
|
|
||||||
</div>
|
|
||||||
<div class="backup-modal-body" id="backup-migrate-modal-body"></div>
|
|
||||||
<div class="backup-modal-footer">
|
|
||||||
<button class="backup-secondary-btn" data-close-modal>Cancel</button>
|
|
||||||
<button class="backup-primary-btn" id="backup-migrate-confirm">Start migrate</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="backup-modal" id="backup-engine-modal">
|
<div class="backup-modal" id="backup-engine-modal">
|
||||||
<div class="backup-modal-inner backup-modal-wide">
|
<div class="backup-modal-inner backup-modal-wide">
|
||||||
<div class="backup-modal-header">
|
<div class="backup-modal-header">
|
||||||
|
|||||||
@ -39,7 +39,7 @@ class BackupPage {
|
|||||||
either source resolve correctly. */
|
either source resolve correctly. */
|
||||||
parseTabFromUrl() {
|
parseTabFromUrl() {
|
||||||
if (this.embedded) return null; // embedded: always open on Dashboard; sub-tabs are in-page only
|
if (this.embedded) return null; // embedded: always open on Dashboard; sub-tabs are in-page only
|
||||||
const allowed = new Set(['dashboard', 'backups', 'locations', 'migrate', 'configuration']);
|
const allowed = new Set(['dashboard', 'backups', 'locations', 'configuration']);
|
||||||
// Path-based: /backup/<tab> (bare /backup → default tab).
|
// Path-based: /backup/<tab> (bare /backup → default tab).
|
||||||
const seg = window.location.pathname.replace(/^\/backup\/?/, '').split('/')[0];
|
const seg = window.location.pathname.replace(/^\/backup\/?/, '').split('/')[0];
|
||||||
if (seg && allowed.has(seg)) return seg;
|
if (seg && allowed.has(seg)) return seg;
|
||||||
@ -255,26 +255,6 @@ class BackupPage {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const migrateAppBtn = e.target.closest('[data-action="migrate-app"]');
|
|
||||||
if (migrateAppBtn) {
|
|
||||||
this.openMigrateModal({
|
|
||||||
mode: 'app',
|
|
||||||
locIdx: parseInt(migrateAppBtn.dataset.loc, 10),
|
|
||||||
host: migrateAppBtn.dataset.host,
|
|
||||||
app: migrateAppBtn.dataset.app
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const migrateHostBtn = e.target.closest('[data-action="migrate-host"]');
|
|
||||||
if (migrateHostBtn) {
|
|
||||||
this.openMigrateModal({
|
|
||||||
mode: 'host',
|
|
||||||
locIdx: parseInt(migrateHostBtn.dataset.loc, 10),
|
|
||||||
host: migrateHostBtn.dataset.host
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (e.target.closest('#backup-migrate-confirm')) { this.confirmMigrate(); return; }
|
|
||||||
if (e.target.closest('#backup-restore-confirm')) { this.confirmRestore(); return; }
|
if (e.target.closest('#backup-restore-confirm')) { this.confirmRestore(); return; }
|
||||||
if (e.target.closest('#backup-delete-confirm')) { this.confirmDelete(); return; }
|
if (e.target.closest('#backup-delete-confirm')) { this.confirmDelete(); return; }
|
||||||
if (e.target.closest('#backup-delete-location-confirm')) { this.confirmDeleteLocation(); return; }
|
if (e.target.closest('#backup-delete-location-confirm')) { this.confirmDeleteLocation(); return; }
|
||||||
@ -391,7 +371,6 @@ class BackupPage {
|
|||||||
dashboard: 'Dashboard',
|
dashboard: 'Dashboard',
|
||||||
backups: 'Backups',
|
backups: 'Backups',
|
||||||
locations: 'Locations',
|
locations: 'Locations',
|
||||||
migrate: 'Migrate',
|
|
||||||
configuration: 'Configuration'
|
configuration: 'Configuration'
|
||||||
}[tab] || 'Backups';
|
}[tab] || 'Backups';
|
||||||
}
|
}
|
||||||
@ -401,7 +380,6 @@ class BackupPage {
|
|||||||
dashboard: "Check what's protected — and when it last ran.",
|
dashboard: "Check what's protected — and when it last ran.",
|
||||||
backups: 'Every backup across every enabled location.',
|
backups: 'Every backup across every enabled location.',
|
||||||
locations: 'Where backups are stored. Add, edit, or remove destinations.',
|
locations: 'Where backups are stored. Add, edit, or remove destinations.',
|
||||||
migrate: 'Restore an app from another LibrePortal that shares one of your backup locations.',
|
|
||||||
configuration: 'Schedule, retention, and engine settings.'
|
configuration: 'Schedule, retention, and engine settings.'
|
||||||
}[tab] || '';
|
}[tab] || '';
|
||||||
}
|
}
|
||||||
@ -423,11 +401,6 @@ class BackupPage {
|
|||||||
'<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">' +
|
'<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">' +
|
||||||
'<path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"></path>' +
|
'<path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"></path>' +
|
||||||
'<circle cx="12" cy="10" r="3"></circle></svg>',
|
'<circle cx="12" cy="10" r="3"></circle></svg>',
|
||||||
migrate:
|
|
||||||
'<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">' +
|
|
||||||
'<path d="M3 12h18"></path>' +
|
|
||||||
'<polyline points="12 5 19 12 12 19"></polyline>' +
|
|
||||||
'<path d="M3 5v14"></path></svg>',
|
|
||||||
configuration:
|
configuration:
|
||||||
'<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">' +
|
'<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">' +
|
||||||
'<circle cx="12" cy="12" r="3"></circle>' +
|
'<circle cx="12" cy="12" r="3"></circle>' +
|
||||||
@ -510,7 +483,6 @@ class BackupPage {
|
|||||||
this.renderDashboard();
|
this.renderDashboard();
|
||||||
this.renderLocations();
|
this.renderLocations();
|
||||||
this.renderSnapshots();
|
this.renderSnapshots();
|
||||||
this.renderMigrate();
|
|
||||||
this.renderConfiguration();
|
this.renderConfiguration();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -27,7 +27,6 @@ LP.features.register({
|
|||||||
'/components/backup/locations/js/backup-location-fields.js',
|
'/components/backup/locations/js/backup-location-fields.js',
|
||||||
'/components/backup/locations/js/backup-location-modal.js',
|
'/components/backup/locations/js/backup-location-modal.js',
|
||||||
'/components/backup/locations/js/backup-ssh-key.js',
|
'/components/backup/locations/js/backup-ssh-key.js',
|
||||||
'/components/backup/migrate/js/backup-migrate.js',
|
|
||||||
'/components/backup/configuration/js/backup-configuration.js',
|
'/components/backup/configuration/js/backup-configuration.js',
|
||||||
'/components/backup/configuration/js/backup-retention-presets.js',
|
'/components/backup/configuration/js/backup-retention-presets.js',
|
||||||
'/components/backup/configuration/js/backup-engine-details.js',
|
'/components/backup/configuration/js/backup-engine-details.js',
|
||||||
|
|||||||
@ -1,182 +0,0 @@
|
|||||||
// Auto-extracted from backup-page.js (verbatim) — augments BackupPage.prototype. Loaded after the base.
|
|
||||||
Object.assign(BackupPage.prototype, {
|
|
||||||
renderMigrate() {
|
|
||||||
const body = document.getElementById('backup-migrate-body');
|
|
||||||
const empty = document.getElementById('backup-migrate-empty');
|
|
||||||
if (!body || !empty) return;
|
|
||||||
|
|
||||||
const data = this.migrate || {};
|
|
||||||
const locations = (data.locations || []).filter(l => (l.hosts || []).length > 0);
|
|
||||||
|
|
||||||
if (!locations.length) {
|
|
||||||
body.innerHTML = '';
|
|
||||||
empty.hidden = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
empty.hidden = true;
|
|
||||||
|
|
||||||
// Group: one card per source-host, with that host's apps listed underneath.
|
|
||||||
// We collapse across locations — if the same host appears in two locations,
|
|
||||||
// we still show it once with the union of apps (the per-app row carries
|
|
||||||
// which location it came from). Most setups have one shared location anyway.
|
|
||||||
const installed = new Set(data.destination?.installed_apps || []);
|
|
||||||
const html = locations.map(loc => `
|
|
||||||
<div class="backup-migrate-location">
|
|
||||||
<div class="backup-card-header" style="margin-bottom:8px">
|
|
||||||
<h3 style="margin:0">${this.escape(loc.name || 'Location')}</h3>
|
|
||||||
<span class="backup-card-hint">${(loc.hosts || []).length} other host${loc.hosts.length === 1 ? '' : 's'} backing up here</span>
|
|
||||||
</div>
|
|
||||||
${loc.hosts.map(host => {
|
|
||||||
const peerName = (this.hostnameToPeerName || {})[host.hostname];
|
|
||||||
const headerLabel = peerName
|
|
||||||
? `<strong style="font-size:1.05em">${this.escape(peerName)}</strong><span class="backup-card-hint" style="margin-left:6px; font-size:.85em">host: <code>${this.escape(host.hostname)}</code></span>`
|
|
||||||
: `<strong style="font-size:1.05em">${this.escape(host.hostname)}</strong>`;
|
|
||||||
return `
|
|
||||||
<div class="backup-migrate-host" style="border:1px solid var(--border-color, #2a2a2a); border-radius:8px; padding:14px; margin-bottom:12px">
|
|
||||||
<div style="display:flex; justify-content:space-between; align-items:center; gap:12px; margin-bottom:10px">
|
|
||||||
<div>
|
|
||||||
${headerLabel}
|
|
||||||
<span class="backup-card-hint" style="margin-left:10px">${(host.apps || []).length} app${host.apps.length === 1 ? '' : 's'} available</span>
|
|
||||||
</div>
|
|
||||||
<button class="backup-primary-btn" data-action="migrate-host"
|
|
||||||
data-loc="${loc.idx}" data-host="${this.escape(host.hostname)}">
|
|
||||||
Migrate every app from this host
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="backup-migrate-apps" style="display:grid; grid-template-columns:repeat(auto-fill, minmax(280px, 1fr)); gap:8px">
|
|
||||||
${(host.apps || []).map(app => {
|
|
||||||
const collide = installed.has(app.slug);
|
|
||||||
return `
|
|
||||||
<div class="backup-migrate-app" style="display:flex; justify-content:space-between; align-items:center; padding:8px 12px; background:var(--surface-2, #1a1a1a); border-radius:6px">
|
|
||||||
<div style="display:flex; flex-direction:column; min-width:0">
|
|
||||||
<span style="display:flex; align-items:center; gap:8px">
|
|
||||||
<strong>${this.escape(app.slug)}</strong>
|
|
||||||
${collide ? `<span class="backup-status-dot warn" title="Already installed here"></span>` : ''}
|
|
||||||
</span>
|
|
||||||
<span class="backup-card-hint" style="font-size:.82em">
|
|
||||||
${app.snapshots} snapshot${app.snapshots === 1 ? '' : 's'} · latest ${this.escape(this.formatRelativeTime(app.latest_date))}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<button class="backup-secondary-btn" data-action="migrate-app"
|
|
||||||
data-loc="${loc.idx}" data-host="${this.escape(host.hostname)}" data-app="${this.escape(app.slug)}">
|
|
||||||
Migrate
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}).join('')}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}).join('')}
|
|
||||||
</div>
|
|
||||||
`).join('');
|
|
||||||
|
|
||||||
body.innerHTML = html;
|
|
||||||
},
|
|
||||||
formatRelativeTime(iso) {
|
|
||||||
if (!iso) return 'never';
|
|
||||||
const t = Date.parse(iso);
|
|
||||||
if (!t) return iso;
|
|
||||||
const diff = Date.now() - t;
|
|
||||||
const minute = 60_000, hour = 60 * minute, day = 24 * hour;
|
|
||||||
if (diff < hour) return `${Math.max(1, Math.round(diff / minute))} min ago`;
|
|
||||||
if (diff < day) return `${Math.round(diff / hour)} h ago`;
|
|
||||||
if (diff < 7 * day) return `${Math.round(diff / day)} d ago`;
|
|
||||||
return new Date(t).toISOString().slice(0, 10);
|
|
||||||
},
|
|
||||||
openMigrateModal({ mode, locIdx, host, app }) {
|
|
||||||
const modal = document.getElementById('backup-migrate-modal');
|
|
||||||
const body = document.getElementById('backup-migrate-modal-body');
|
|
||||||
if (!modal || !body) return;
|
|
||||||
|
|
||||||
const dest = this.migrate?.destination || {};
|
|
||||||
const installed = new Set(dest.installed_apps || []);
|
|
||||||
const running = new Set(dest.running_apps || []);
|
|
||||||
const locName = this.locName(locIdx);
|
|
||||||
|
|
||||||
// App-mode: one specific app. Host-mode: every app from the host.
|
|
||||||
let targetApps = [];
|
|
||||||
if (mode === 'app') {
|
|
||||||
targetApps = [app];
|
|
||||||
} else {
|
|
||||||
const loc = (this.migrate?.locations || []).find(l => l.idx === locIdx);
|
|
||||||
const h = (loc?.hosts || []).find(x => x.hostname === host);
|
|
||||||
targetApps = (h?.apps || []).map(a => a.slug);
|
|
||||||
}
|
|
||||||
const collisions = targetApps.filter(a => installed.has(a));
|
|
||||||
const collisionsRunning = collisions.filter(a => running.has(a));
|
|
||||||
|
|
||||||
const intro = mode === 'app'
|
|
||||||
? `<p>Migrate <strong>${this.escape(app)}</strong> from <strong>${this.escape(host)}</strong> via <strong>${this.escape(locName)}</strong> onto this host.</p>`
|
|
||||||
: `<p>Migrate <strong>every app</strong> (${targetApps.length}) from <strong>${this.escape(host)}</strong> via <strong>${this.escape(locName)}</strong> onto this host.</p>`;
|
|
||||||
|
|
||||||
let collisionNote = '';
|
|
||||||
if (collisions.length) {
|
|
||||||
collisionNote = `
|
|
||||||
<p class="backup-card-hint" style="color:var(--warning, #d97706); margin-top:8px">
|
|
||||||
⚠ Already installed here: ${collisions.map(c => `<code>${this.escape(c)}</code>`).join(', ')}.
|
|
||||||
These will be <strong>replaced</strong>.
|
|
||||||
${collisionsRunning.length ? `Currently running: ${collisionsRunning.map(c => `<code>${this.escape(c)}</code>`).join(', ')} — will be stopped first.` : ''}
|
|
||||||
</p>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
body.innerHTML = `
|
|
||||||
${intro}
|
|
||||||
${collisionNote}
|
|
||||||
<div style="margin-top:14px; display:flex; flex-direction:column; gap:8px">
|
|
||||||
<label style="display:flex; align-items:flex-start; gap:8px; cursor:pointer">
|
|
||||||
<input type="checkbox" id="migrate-opt-pre-backup" ${collisions.length ? 'checked' : 'disabled'}>
|
|
||||||
<span>
|
|
||||||
Back up the destination's existing copy first
|
|
||||||
<span class="backup-card-hint" style="display:block; font-size:.85em">
|
|
||||||
Safety net: snapshot the current ${mode === 'app' ? this.escape(app) : 'app'} into your first
|
|
||||||
enabled backup location (tagged <code>pre-migrate</code>) before wipe.
|
|
||||||
${collisions.length ? '' : 'No collision — nothing to back up.'}
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
<label style="display:flex; align-items:flex-start; gap:8px; cursor:pointer">
|
|
||||||
<input type="checkbox" id="migrate-opt-rewrite-urls" checked>
|
|
||||||
<span>
|
|
||||||
Rewrite host-bound URLs to this host
|
|
||||||
<span class="backup-card-hint" style="display:block; font-size:.85em">
|
|
||||||
Replaces <code>CFG_*_URL</code>, <code>*_DOMAIN</code>, <code>*_HOSTNAME</code> with this
|
|
||||||
host's values. Uncheck only if you want the moved app to keep claiming the source's hostname.
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
modal.dataset.mode = mode;
|
|
||||||
modal.dataset.locIdx = String(locIdx);
|
|
||||||
modal.dataset.host = host;
|
|
||||||
modal.dataset.app = app || '';
|
|
||||||
modal.classList.add('open');
|
|
||||||
},
|
|
||||||
async confirmMigrate() {
|
|
||||||
const modal = document.getElementById('backup-migrate-modal');
|
|
||||||
if (!modal) return;
|
|
||||||
const { mode, locIdx, host, app } = modal.dataset;
|
|
||||||
const preBackup = document.getElementById('migrate-opt-pre-backup')?.checked;
|
|
||||||
const rewrite = document.getElementById('migrate-opt-rewrite-urls')?.checked;
|
|
||||||
|
|
||||||
// The bash CLI defaults are: pre-backup ON, URL rewrite ON. Opt-out flags
|
|
||||||
// only get appended when the user un-ticks; matches the kernel's defaults.
|
|
||||||
const opts = [];
|
|
||||||
if (preBackup === false) opts.push('--no-pre-backup');
|
|
||||||
if (rewrite === false) opts.push('--keep-urls');
|
|
||||||
const optStr = opts.length ? ' ' + opts.join(' ') : '';
|
|
||||||
|
|
||||||
this.closeAllModals();
|
|
||||||
if (mode === 'app') {
|
|
||||||
await this.runTask(
|
|
||||||
`libreportal restore migrate app ${app} ${host} ${locIdx}${optStr}`,
|
|
||||||
'restore', app);
|
|
||||||
} else {
|
|
||||||
await this.runTask(
|
|
||||||
`libreportal restore migrate system ${host} ${locIdx}${optStr}`,
|
|
||||||
'restore', null);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
Loading…
x
Reference in New Issue
Block a user