Files
deploy-felhom-compose/controller/internal/web/templates/migrate.html
T
admin 2fb2c6e1ae v0.11.0 — Phase C: Storage Init Wizard, Data Migration & Startup Fix
- Startup ping: fire heartbeat + health + hub report immediately on boot
  (5s delay after scheduler start, instead of waiting 5-15 min for first tick)

- Storage init wizard: new internal/storage/ package with disk scanning
  (lsblk -J), format+mount pipeline (sfdisk → mkfs.ext4 → blkid → fstab →
  mount → chown), safety guards (system disk detection, confirmation "FORMÁZÁS"),
  progress channel, auto-register in settings.json

- Data migration: MigrateAppData() with rsync --info=progress2 progress parsing,
  stop/rsync/update-config/start flow, rollback on failure, old data preserved

- New pages: /settings/storage/init (wizard), /stacks/{name}/migrate (migration)
- New API routes: /api/storage/{scan,init,init/status,migrate,migrate/status}
- Deploy page: storage info section for deployed apps (path, size, free, migrate link)
- Settings page: "Mozgatás" button per app in storage path details
- Container: privileged: true, /dev:/dev, /etc/fstab:/host-fstab, /run/udev:/run/udev:ro
- Dockerfile: add util-linux, e2fsprogs, rsync, parted for disk ops

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 10:27:18 +01:00

191 lines
7.4 KiB
HTML

{{define "migrate"}}
{{template "layout_start" .}}
<div class="page-header">
<div style="display:flex;align-items:center;gap:.5rem">
<a href="/stacks/{{.Meta.Slug}}/deploy" class="btn btn-sm btn-outline">← Vissza</a>
<h2>{{.Meta.DisplayName}} — Adatáthelyezés</h2>
</div>
</div>
<div class="settings-card" id="migrate-form-card">
<h3>Adatok áthelyezése másik tárolóra</h3>
<div class="settings-grid" style="margin-bottom:1.5rem">
<div class="settings-row">
<span class="settings-label">Jelenlegi tárhely</span>
<span class="settings-value mono">{{.CurrentLabel}} ({{.CurrentHDDPath}})</span>
</div>
{{if .DataSizeHuman}}
<div class="settings-row">
<span class="settings-label">Adatméret</span>
<span class="settings-value mono">{{.DataSizeHuman}}</span>
</div>
{{end}}
</div>
<div class="form-group">
<label for="target-path">Cél tárhely <span class="required">*</span></label>
<select id="target-path" class="form-control">
{{range .OtherPaths}}
<option value="{{.Path}}">{{.Label}} ({{.Path}}) — {{.FreeHuman}} szabad</option>
{{end}}
</select>
</div>
<div class="alert alert-warning" style="margin-bottom:1.5rem">
<strong>Figyelmeztetések:</strong>
<ul style="margin:.5rem 0 0 1rem;padding:0">
<li>Az alkalmazás a mozgatás idejére leáll</li>
<li>Nagy adatmennyiségnél ez percekig tarthat</li>
<li>A régi adatok megmaradnak biztonsági másolatként</li>
</ul>
</div>
<div id="migrate-error" class="alert alert-error" style="display:none;margin-bottom:1rem"></div>
<div class="form-actions" style="gap:.75rem">
<button class="btn btn-primary" onclick="startMigrate()">📦 Mozgatás indítása</button>
<a href="/stacks/{{.Meta.Slug}}/deploy" class="btn btn-outline">Mégsem</a>
</div>
</div>
<div class="settings-card" id="migrate-progress-card" style="display:none">
<h3>Adatok áthelyezése...</h3>
<div class="disk-progress-steps" id="mig-steps">
<div class="disk-step" id="mstep-stopping"><span class="disk-step-icon"></span> Alkalmazás leállítása</div>
<div class="disk-step" id="mstep-copying"><span class="disk-step-icon"></span> Adatok másolása</div>
<div class="disk-step" id="mstep-updating"><span class="disk-step-icon"></span> Konfiguráció frissítése</div>
<div class="disk-step" id="mstep-starting"><span class="disk-step-icon"></span> Alkalmazás indítása</div>
<div class="disk-step" id="mstep-done"><span class="disk-step-icon"></span> Kész</div>
</div>
<div class="disk-progress-bar-wrap" style="margin-top:1.5rem">
<div class="system-bar" style="height:12px;border-radius:6px">
<div class="system-bar-fill system-bar-green" id="mig-progress-bar" style="width:0%;transition:width .4s ease;height:12px;border-radius:6px"></div>
</div>
<span class="mono form-hint" id="mig-progress-pct">0%</span>
</div>
<div id="mig-progress-msg" class="form-hint" style="margin-top:.75rem"></div>
<div id="mig-elapsed" class="form-hint mono" style="margin-top:.25rem"></div>
<div id="mig-progress-error" class="alert alert-error" style="display:none;margin-top:1rem"></div>
</div>
<div class="settings-card" id="migrate-done-card" style="display:none">
<h3>✅ Adatáthelyezés kész!</h3>
<p style="margin-top:.75rem;color:var(--text-secondary)">
Az alkalmazás az új tárolóról fut.<br>
A régi adatok a korábbi helyen megmaradtak biztonsági másolatként.
</p>
<div style="margin-top:1.5rem;display:flex;gap:.75rem">
<a href="/stacks" class="btn btn-primary">Alkalmazások megtekintése</a>
<a href="/settings" class="btn btn-outline">Beállítások</a>
</div>
</div>
<script>
var stackName = '{{.Stack.Name}}';
var migPollTimer = null;
function startMigrate() {
var targetPath = document.getElementById('target-path').value;
if (!targetPath) {
document.getElementById('migrate-error').textContent = 'Válasszon cél tárhelyet.';
document.getElementById('migrate-error').style.display = 'block';
return;
}
document.getElementById('migrate-form-card').style.display = 'none';
document.getElementById('migrate-progress-card').style.display = 'block';
fetch('/api/storage/migrate', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({stack_name: stackName, target_path: targetPath})
})
.then(function(r){ return r.json(); })
.then(function(data) {
if (!data.ok) {
showMigError(data.error || 'Ismeretlen hiba');
return;
}
migPollTimer = setInterval(pollMigProgress, 2000);
})
.catch(function(e) {
showMigError('Hálózati hiba: ' + e.message);
});
}
var migStepOrder = ['stopping','copying','updating','starting','done'];
function pollMigProgress() {
fetch('/api/storage/migrate/status')
.then(function(r){ return r.json(); })
.then(function(data) {
if (!data.ok) return;
updateMigUI(data);
if (data.done) {
clearInterval(migPollTimer);
if (data.step === 'done') {
showMigDone();
}
}
})
.catch(function(){});
}
function updateMigUI(data) {
var currentIdx = migStepOrder.indexOf(data.step);
if (currentIdx < 0 && data.step === 'rolling_back') {
currentIdx = 1; // show during copy step
}
migStepOrder.forEach(function(s, i) {
var el = document.getElementById('mstep-' + s);
if (!el) return;
var icon = el.querySelector('.disk-step-icon');
if (i < currentIdx) {
el.className = 'disk-step disk-step-done';
icon.textContent = '✅';
} else if (i === currentIdx) {
el.className = 'disk-step disk-step-active';
icon.textContent = (data.step === 'error' || data.step === 'rolling_back') ? '❌' : '⏳';
} else {
el.className = 'disk-step';
icon.textContent = '○';
}
});
var pct = data.pct || 0;
document.getElementById('mig-progress-bar').style.width = pct + '%';
document.getElementById('mig-progress-pct').textContent = pct + '%';
document.getElementById('mig-progress-msg').textContent = data.msg || '';
if (data.elapsed_sec) {
document.getElementById('mig-elapsed').textContent = data.elapsed_sec + ' másodperce fut';
}
if (data.step === 'error' || (data.error && data.error !== '')) {
showMigError(data.error || data.msg || 'Ismeretlen hiba');
}
}
function showMigError(msg) {
clearInterval(migPollTimer);
document.getElementById('mig-progress-error').textContent = 'Hiba: ' + msg;
document.getElementById('mig-progress-error').style.display = 'block';
document.getElementById('migrate-progress-card').querySelector('h3').textContent = 'Áthelyezés sikertelen';
}
function showMigDone() {
document.getElementById('migrate-progress-card').style.display = 'none';
document.getElementById('migrate-done-card').style.display = 'block';
document.getElementById('migrate-done-card').scrollIntoView({behavior:'smooth'});
}
</script>
{{template "layout_end" .}}
{{end}}