6b7ca566df
After an interrupted attach wizard, the raw mount stays behind, causing the device to appear as "mounted" in scan results. Now the scan button calls cancel first, which unmounts any stale raw mounts that have no bind mount in fstab. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
571 lines
23 KiB
HTML
571 lines
23 KiB
HTML
{{define "storage_attach"}}
|
||
{{template "layout_start" .}}
|
||
|
||
<div class="page-header">
|
||
<div style="display:flex;align-items:center;gap:.5rem">
|
||
<a href="/settings" class="btn btn-sm btn-outline">← Vissza</a>
|
||
<h2>Meglévő meghajtó csatolása</h2>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Step 1: Scan -->
|
||
<div class="settings-card" id="wizard-scan">
|
||
<h3>1. Meghajtók keresése</h3>
|
||
<p class="settings-card-desc">Keresse meg a rendszerhez csatlakoztatott, meglévő fájlrendszerrel rendelkező meghajtókat.</p>
|
||
|
||
<button class="btn btn-primary" onclick="scanDisks()" id="scan-btn">🔍 Meghajtók keresése</button>
|
||
<div id="scan-error" class="alert alert-error" style="display:none;margin-top:1rem"></div>
|
||
|
||
<div id="scan-result" style="display:none;margin-top:1.5rem">
|
||
<div id="available-disks"></div>
|
||
<div id="system-disks-note" style="display:none;margin-top:1rem"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Step 2: Browse -->
|
||
<div class="settings-card" id="wizard-browse" style="display:none">
|
||
<h3>2. Mappa kiválasztása</h3>
|
||
<p class="settings-card-desc">Válasszon ki egy mappát a meghajtón, amelyet a controller használni fog. Új mappát is létrehozhat.</p>
|
||
|
||
<div id="browse-info" class="settings-grid" style="margin-bottom:1rem">
|
||
<div class="settings-row">
|
||
<span class="settings-label">Partíció</span>
|
||
<span class="settings-value mono" id="browse-device"></span>
|
||
</div>
|
||
<div class="settings-row">
|
||
<span class="settings-label">Fájlrendszer</span>
|
||
<span class="settings-value mono" id="browse-fstype"></span>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="browse-error" class="alert alert-error" style="display:none;margin-bottom:1rem"></div>
|
||
|
||
<div id="dir-browser" style="border:1px solid var(--border);border-radius:6px;padding:1rem;background:var(--card-bg);margin-bottom:1rem">
|
||
<div id="dir-breadcrumb" class="form-hint mono" style="margin-bottom:.75rem"></div>
|
||
<div id="dir-list" style="min-height:100px"></div>
|
||
</div>
|
||
|
||
<div style="display:flex;gap:.75rem;align-items:flex-end;flex-wrap:wrap;margin-bottom:1rem">
|
||
<div class="form-group" style="margin-bottom:0">
|
||
<label for="new-dir-name">Új mappa neve</label>
|
||
<input type="text" id="new-dir-name" class="form-control" placeholder="felhom_data"
|
||
pattern="[a-zA-Z0-9_]+" style="max-width:200px">
|
||
</div>
|
||
<button class="btn btn-outline" onclick="createDir()" id="mkdir-btn">📁 Mappa létrehozása</button>
|
||
</div>
|
||
|
||
<div id="selected-dir-info" class="alert alert-info" style="display:none;margin-bottom:1rem">
|
||
Kiválasztott mappa: <strong id="selected-dir-display" class="mono"></strong>
|
||
</div>
|
||
|
||
<div class="form-actions" style="gap:.75rem">
|
||
<button class="btn btn-primary" onclick="goToConfigure()" id="browse-next-btn" disabled>Tovább →</button>
|
||
<button class="btn btn-outline" onclick="cancelAttach()">Mégsem</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Step 3: Configure -->
|
||
<div class="settings-card" id="wizard-configure" style="display:none">
|
||
<h3>3. Konfiguráció</h3>
|
||
<p class="settings-card-desc">Adja meg a csatolás paramétereit.</p>
|
||
|
||
<form id="attach-form">
|
||
<div class="form-group">
|
||
<label>Kiválasztott partíció</label>
|
||
<span class="settings-value mono" id="config-device-display"></span>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label>Kiválasztott mappa</label>
|
||
<span class="settings-value mono" id="config-subpath-display"></span>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label for="mount-name">Csatlakoztatási név <span class="required">*</span></label>
|
||
<div class="form-inline">
|
||
<span class="mono" style="opacity:.6">/mnt/</span>
|
||
<input type="text" id="mount-name" class="form-control" placeholder="hdd_1"
|
||
pattern="[a-zA-Z0-9_]+" required style="max-width:160px">
|
||
</div>
|
||
<span class="form-hint">Pl. hdd_1 → a mappa a /mnt/hdd_1 útvonalra kerül</span>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label for="storage-label">Megnevezés</label>
|
||
<input type="text" id="storage-label" class="form-control" placeholder="Külső HDD 1TB" maxlength="50">
|
||
</div>
|
||
|
||
<label class="toggle" style="margin-bottom:1.5rem">
|
||
<input type="checkbox" id="set-default" checked>
|
||
<span class="toggle-label">Beállítás alapértelmezett adattárolóként új telepítéseknél</span>
|
||
</label>
|
||
|
||
<div class="alert alert-info" style="margin-bottom:1.5rem">
|
||
<strong>ℹ️ Megjegyzés:</strong> A meghajtón lévő adatok <strong>NEM</strong> törlődnek.
|
||
A controller csak a kiválasztott mappában dolgozik.<br>
|
||
<strong>⚠️ A csatlakozási pont (/mnt/<név>) a meghajtó lecsatolásáig nem módosítható.</strong>
|
||
</div>
|
||
|
||
<div class="form-actions" style="gap:.75rem">
|
||
<button type="submit" class="btn btn-primary" id="attach-btn">Csatolás</button>
|
||
<button type="button" class="btn btn-outline" onclick="backToBrowse()">← Vissza</button>
|
||
</div>
|
||
<div id="attach-error" class="alert alert-error" style="display:none;margin-top:1rem"></div>
|
||
</form>
|
||
</div>
|
||
|
||
<!-- Step 4: Progress -->
|
||
<div class="settings-card" id="wizard-progress" style="display:none">
|
||
<h3>4. Csatolás folyamatban...</h3>
|
||
|
||
<div class="disk-progress-steps" id="progress-steps">
|
||
<div class="disk-step" id="pstep-validating"><span class="disk-step-icon">○</span> Ellenőrzés</div>
|
||
<div class="disk-step" id="pstep-mounting"><span class="disk-step-icon">○</span> Csatlakoztatás</div>
|
||
<div class="disk-step" id="pstep-permissions"><span class="disk-step-icon">○</span> Mappák és jogosultságok</div>
|
||
<div class="disk-step" id="pstep-done"><span class="disk-step-icon">○</span> Regisztráció</div>
|
||
</div>
|
||
|
||
<div style="margin-top:1.5rem;display:flex;align-items:center;gap:1rem">
|
||
<div class="progress-bar-task" style="flex:1">
|
||
<div class="progress-fill" id="progress-fill" style="width:0%"></div>
|
||
</div>
|
||
<span id="progress-percent" style="font-size:0.9rem;color:var(--text-muted);font-family:'JetBrains Mono',monospace;white-space:nowrap">0%</span>
|
||
</div>
|
||
|
||
<div id="progress-msg" class="form-hint" style="margin-top:.75rem"></div>
|
||
<div id="progress-error" class="alert alert-error" style="display:none;margin-top:1rem"></div>
|
||
</div>
|
||
|
||
<!-- Step 5: Done -->
|
||
<div class="settings-card" id="wizard-done" style="display:none">
|
||
<h3>✅ Meghajtó sikeresen csatolva!</h3>
|
||
<div id="done-info" class="settings-grid" style="margin-top:1rem">
|
||
<div class="settings-row">
|
||
<span class="settings-label">Útvonal</span>
|
||
<span class="settings-value mono" id="done-path"></span>
|
||
</div>
|
||
</div>
|
||
<a href="/settings" class="btn btn-primary" style="margin-top:1.5rem">← Vissza a Beállításokhoz</a>
|
||
</div>
|
||
|
||
<script>
|
||
var selectedDevice = null;
|
||
var selectedPartition = null;
|
||
var currentBrowsePath = '';
|
||
var rawMountPath = '';
|
||
var selectedSubPath = '';
|
||
var pollTimer = null;
|
||
|
||
// --- Step 1: Scan ---
|
||
|
||
function scanDisks() {
|
||
var btn = document.getElementById('scan-btn');
|
||
var errEl = document.getElementById('scan-error');
|
||
var resultEl = document.getElementById('scan-result');
|
||
btn.textContent = 'Keresés...';
|
||
btn.disabled = true;
|
||
errEl.style.display = 'none';
|
||
resultEl.style.display = 'none';
|
||
|
||
// Clean up any stale raw mounts from interrupted previous sessions first,
|
||
// so the device appears as available in the scan results.
|
||
fetch('/api/storage/attach/cancel', {method:'POST'})
|
||
.catch(function(){}) // ignore cancel errors
|
||
.then(function() { return fetch('/api/storage/scan', {method:'POST'}); })
|
||
.then(function(r){ return r.json(); })
|
||
.then(function(data) {
|
||
btn.textContent = '🔍 Meghajtók keresése';
|
||
btn.disabled = false;
|
||
if (!data.ok) {
|
||
errEl.textContent = data.error || 'Ismeretlen hiba';
|
||
errEl.style.display = 'block';
|
||
return;
|
||
}
|
||
renderScanResult(data);
|
||
resultEl.style.display = 'block';
|
||
})
|
||
.catch(function(e) {
|
||
btn.textContent = '🔍 Meghajtók keresése';
|
||
btn.disabled = false;
|
||
errEl.textContent = 'Hálózati hiba: ' + e.message;
|
||
errEl.style.display = 'block';
|
||
});
|
||
}
|
||
|
||
function renderScanResult(data) {
|
||
var availEl = document.getElementById('available-disks');
|
||
var sysEl = document.getElementById('system-disks-note');
|
||
|
||
// Filter: only show disks that have at least one partition with a filesystem
|
||
var disksWithFS = [];
|
||
if (data.available) {
|
||
data.available.forEach(function(disk) {
|
||
if (disk.Partitions) {
|
||
var fsPartitions = disk.Partitions.filter(function(p) { return p.FSType && p.FSType !== ''; });
|
||
if (fsPartitions.length > 0) {
|
||
disksWithFS.push({disk: disk, partitions: fsPartitions});
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
if (disksWithFS.length === 0) {
|
||
availEl.innerHTML = '<div class="empty-state" style="padding:1rem">Nem található meglévő fájlrendszerrel rendelkező meghajtó.<br>' +
|
||
'<span class="form-hint">Ha üres meghajtót szeretne inicializálni, használja az <a href="/settings/storage/init">inicializálás varázslót</a>.</span></div>';
|
||
return;
|
||
}
|
||
|
||
var html = '<h4 style="margin-bottom:.75rem">Talált meghajtók csatolható partíciókkal:</h4>';
|
||
disksWithFS.forEach(function(item) {
|
||
var disk = item.disk;
|
||
html += '<div style="margin-bottom:1rem">';
|
||
html += '<div class="form-hint" style="margin-bottom:.5rem">' + disk.Path + ' — ' + (disk.Size || '?') +
|
||
(disk.Model ? ' — ' + disk.Model : '') + '</div>';
|
||
|
||
item.partitions.forEach(function(part) {
|
||
var info = part.FSType;
|
||
if (part.Label) info += ', címke: ' + part.Label;
|
||
if (part.UUID) info += ', UUID: ' + part.UUID.substring(0, 8) + '...';
|
||
|
||
html += '<div class="storage-path-item" style="cursor:pointer;border:2px solid transparent;margin-bottom:.5rem" ' +
|
||
'onclick="selectPartition(this, \'' + part.Path + '\', \'' + (part.FSType || '') + '\', \'' + (part.Label || '') + '\')" ' +
|
||
'data-path="' + part.Path + '">' +
|
||
'<div class="storage-path-header"><div class="storage-path-info">' +
|
||
'<span class="storage-path-label">○ ' + part.Path + ' — ' + (part.Size || '?') + '</span>' +
|
||
'<span class="form-hint">' + info + '</span>' +
|
||
'</div></div></div>';
|
||
});
|
||
html += '</div>';
|
||
});
|
||
|
||
availEl.innerHTML = html;
|
||
|
||
if (data.system && data.system.length > 0) {
|
||
var sysNames = data.system.map(function(d){ return d.Path + ' (' + (d.Size||'?') + ')'; }).join(', ');
|
||
sysEl.innerHTML = '<span class="form-hint">A rendszermeghajtó(k) nem választhatók: ' + sysNames + '</span>';
|
||
sysEl.style.display = 'block';
|
||
}
|
||
}
|
||
|
||
function selectPartition(el, path, fsType, label) {
|
||
// Deselect all
|
||
document.querySelectorAll('[data-path]').forEach(function(d) {
|
||
d.style.border = '2px solid transparent';
|
||
var lbl = d.querySelector('.storage-path-label');
|
||
if (lbl) lbl.textContent = lbl.textContent.replace('● ', '○ ');
|
||
});
|
||
// Select this
|
||
el.style.border = '2px solid var(--accent-blue)';
|
||
var lbl = el.querySelector('.storage-path-label');
|
||
if (lbl) lbl.textContent = lbl.textContent.replace('○ ', '● ');
|
||
|
||
selectedDevice = path;
|
||
selectedPartition = {path: path, fsType: fsType, label: label};
|
||
|
||
// Mount raw and go to browse
|
||
mountRawAndBrowse(path, fsType);
|
||
}
|
||
|
||
// --- Step 2: Browse ---
|
||
|
||
function mountRawAndBrowse(devicePath, fsType) {
|
||
var errEl = document.getElementById('scan-error');
|
||
errEl.style.display = 'none';
|
||
|
||
fetch('/api/storage/attach/mount-raw', {
|
||
method: 'POST',
|
||
headers: {'Content-Type': 'application/json'},
|
||
body: JSON.stringify({device_path: devicePath})
|
||
}).then(function(r){ return r.json(); })
|
||
.then(function(data) {
|
||
if (!data.ok) {
|
||
errEl.textContent = data.error || 'Raw mount sikertelen';
|
||
errEl.style.display = 'block';
|
||
return;
|
||
}
|
||
rawMountPath = data.raw_path;
|
||
|
||
// Show browse step
|
||
document.getElementById('browse-device').textContent = devicePath;
|
||
document.getElementById('browse-fstype').textContent = fsType;
|
||
document.getElementById('wizard-scan').style.display = 'none';
|
||
document.getElementById('wizard-browse').style.display = 'block';
|
||
document.getElementById('wizard-browse').scrollIntoView({behavior:'smooth'});
|
||
|
||
// Browse root
|
||
browseDirectory(rawMountPath);
|
||
})
|
||
.catch(function(e) {
|
||
errEl.textContent = 'Hálózati hiba: ' + e.message;
|
||
errEl.style.display = 'block';
|
||
});
|
||
}
|
||
|
||
function browseDirectory(path) {
|
||
currentBrowsePath = path;
|
||
var errEl = document.getElementById('browse-error');
|
||
errEl.style.display = 'none';
|
||
|
||
// Update breadcrumb
|
||
var rel = path.replace(rawMountPath, '') || '/';
|
||
document.getElementById('dir-breadcrumb').textContent = 'Aktuális mappa: ' + rel;
|
||
|
||
fetch('/api/storage/attach/browse?path=' + encodeURIComponent(path))
|
||
.then(function(r){ return r.json(); })
|
||
.then(function(data) {
|
||
if (!data.ok) {
|
||
errEl.textContent = data.error || 'Hiba a mappák listázásakor';
|
||
errEl.style.display = 'block';
|
||
return;
|
||
}
|
||
renderDirList(data.dirs || [], path);
|
||
})
|
||
.catch(function(e) {
|
||
errEl.textContent = 'Hálózati hiba: ' + e.message;
|
||
errEl.style.display = 'block';
|
||
});
|
||
}
|
||
|
||
function renderDirList(dirs, basePath) {
|
||
var listEl = document.getElementById('dir-list');
|
||
var html = '';
|
||
|
||
// "Use this directory" option (select the current directory itself)
|
||
html += '<div class="storage-path-item" style="cursor:pointer;border:2px solid transparent;margin-bottom:.5rem;padding:.5rem .75rem" ' +
|
||
'onclick="selectDir(this, \'' + escapeJS(basePath) + '\')" data-dirpath="' + escapeAttr(basePath) + '">' +
|
||
'<span class="storage-path-label">📂 . (ez a mappa)</span></div>';
|
||
|
||
// Parent directory (if not at root)
|
||
if (basePath !== rawMountPath) {
|
||
var parentPath = basePath.substring(0, basePath.lastIndexOf('/'));
|
||
if (parentPath.length < rawMountPath.length) parentPath = rawMountPath;
|
||
html += '<div style="padding:.3rem .75rem;cursor:pointer;opacity:.7" onclick="browseDirectory(\'' + escapeJS(parentPath) + '\')">' +
|
||
'📁 .. (szülő mappa)</div>';
|
||
}
|
||
|
||
if (dirs.length === 0) {
|
||
html += '<div class="form-hint" style="padding:.5rem .75rem">Üres mappa</div>';
|
||
} else {
|
||
dirs.forEach(function(dir) {
|
||
html += '<div style="display:flex;align-items:center;gap:.5rem;margin-bottom:.25rem">';
|
||
// Clickable to navigate into
|
||
if (dir.has_children) {
|
||
html += '<div style="padding:.3rem .75rem;cursor:pointer;flex:1" onclick="browseDirectory(\'' + escapeJS(dir.path) + '\')">' +
|
||
'📁 ' + dir.name + ' →</div>';
|
||
} else {
|
||
html += '<div style="padding:.3rem .75rem;flex:1">📁 ' + dir.name + '</div>';
|
||
}
|
||
// Select button
|
||
html += '<button class="btn btn-xs btn-outline" onclick="selectDir(null, \'' + escapeJS(dir.path) + '\')">Kiválasztás</button>';
|
||
html += '</div>';
|
||
});
|
||
}
|
||
|
||
listEl.innerHTML = html;
|
||
}
|
||
|
||
function selectDir(el, path) {
|
||
selectedSubPath = path;
|
||
document.getElementById('selected-dir-display').textContent = path.replace(rawMountPath, '') || '/';
|
||
document.getElementById('selected-dir-info').style.display = 'block';
|
||
document.getElementById('browse-next-btn').disabled = false;
|
||
|
||
// Highlight selected
|
||
document.querySelectorAll('[data-dirpath]').forEach(function(d) {
|
||
d.style.border = '2px solid transparent';
|
||
});
|
||
if (el) {
|
||
el.style.border = '2px solid var(--accent-blue)';
|
||
}
|
||
|
||
// Pre-fill mount name from partition label if available
|
||
var mountInput = document.getElementById('mount-name');
|
||
if (!mountInput.value && selectedPartition && selectedPartition.label) {
|
||
mountInput.value = selectedPartition.label.replace(/[^a-zA-Z0-9_]/g, '_');
|
||
}
|
||
}
|
||
|
||
function createDir() {
|
||
var nameInput = document.getElementById('new-dir-name');
|
||
var name = nameInput.value.trim();
|
||
if (!name) return;
|
||
|
||
var errEl = document.getElementById('browse-error');
|
||
errEl.style.display = 'none';
|
||
|
||
fetch('/api/storage/attach/mkdir', {
|
||
method: 'POST',
|
||
headers: {'Content-Type': 'application/json'},
|
||
body: JSON.stringify({path: currentBrowsePath, name: name})
|
||
}).then(function(r){ return r.json(); })
|
||
.then(function(data) {
|
||
if (!data.ok) {
|
||
errEl.textContent = data.error || 'Mappa létrehozása sikertelen';
|
||
errEl.style.display = 'block';
|
||
return;
|
||
}
|
||
nameInput.value = '';
|
||
// Auto-select the created directory
|
||
selectedSubPath = data.created_path;
|
||
document.getElementById('selected-dir-display').textContent = data.created_path.replace(rawMountPath, '');
|
||
document.getElementById('selected-dir-info').style.display = 'block';
|
||
document.getElementById('browse-next-btn').disabled = false;
|
||
// Refresh directory listing
|
||
browseDirectory(currentBrowsePath);
|
||
})
|
||
.catch(function(e) {
|
||
errEl.textContent = 'Hálózati hiba: ' + e.message;
|
||
errEl.style.display = 'block';
|
||
});
|
||
}
|
||
|
||
function goToConfigure() {
|
||
if (!selectedSubPath) return;
|
||
document.getElementById('config-device-display').textContent = selectedDevice;
|
||
document.getElementById('config-subpath-display').textContent = selectedSubPath.replace(rawMountPath, '') || '/ (gyökérmappa)';
|
||
document.getElementById('wizard-browse').style.display = 'none';
|
||
document.getElementById('wizard-configure').style.display = 'block';
|
||
document.getElementById('wizard-configure').scrollIntoView({behavior:'smooth'});
|
||
}
|
||
|
||
function backToBrowse() {
|
||
document.getElementById('wizard-configure').style.display = 'none';
|
||
document.getElementById('wizard-browse').style.display = 'block';
|
||
}
|
||
|
||
function cancelAttach() {
|
||
// Cleanup raw mount
|
||
fetch('/api/storage/attach/cancel', {method:'POST'}).catch(function(){});
|
||
window.location.href = '/settings';
|
||
}
|
||
|
||
// --- Step 3: Submit ---
|
||
|
||
document.getElementById('attach-form').addEventListener('submit', function(e) {
|
||
e.preventDefault();
|
||
|
||
var mountName = document.getElementById('mount-name').value.trim();
|
||
var label = document.getElementById('storage-label').value.trim();
|
||
var setDefault = document.getElementById('set-default').checked;
|
||
var errEl = document.getElementById('attach-error');
|
||
|
||
if (!mountName) {
|
||
errEl.textContent = 'A csatlakoztatási nevet meg kell adni.';
|
||
errEl.style.display = 'block';
|
||
return;
|
||
}
|
||
|
||
errEl.style.display = 'none';
|
||
document.getElementById('wizard-configure').style.display = 'none';
|
||
document.getElementById('wizard-progress').style.display = 'block';
|
||
document.getElementById('wizard-progress').scrollIntoView({behavior:'smooth'});
|
||
|
||
var body = {
|
||
device_path: selectedDevice,
|
||
mount_name: mountName,
|
||
sub_path: selectedSubPath,
|
||
label: label,
|
||
set_default: setDefault
|
||
};
|
||
|
||
fetch('/api/storage/attach', {
|
||
method: 'POST',
|
||
headers: {'Content-Type': 'application/json'},
|
||
body: JSON.stringify(body)
|
||
}).then(function(r){ return r.json(); })
|
||
.then(function(data) {
|
||
if (!data.ok) {
|
||
showProgressError(data.error || 'Ismeretlen hiba');
|
||
return;
|
||
}
|
||
pollTimer = setInterval(pollProgress, 1500);
|
||
})
|
||
.catch(function(e) {
|
||
showProgressError('Hálózati hiba: ' + e.message);
|
||
});
|
||
});
|
||
|
||
// --- Step 4: Progress ---
|
||
|
||
var stepOrder = ['validating','mounting','permissions','done'];
|
||
|
||
function pollProgress() {
|
||
fetch('/api/storage/attach/status')
|
||
.then(function(r){ return r.json(); })
|
||
.then(function(data) {
|
||
if (!data.ok) return;
|
||
updateProgressUI(data);
|
||
if (data.done) {
|
||
clearInterval(pollTimer);
|
||
if (data.step === 'done') {
|
||
showDone('/mnt/' + document.getElementById('mount-name').value.trim());
|
||
}
|
||
}
|
||
})
|
||
.catch(function(){});
|
||
}
|
||
|
||
function updateProgressUI(data) {
|
||
var currentIdx = stepOrder.indexOf(data.step);
|
||
stepOrder.forEach(function(s, i) {
|
||
var el = document.getElementById('pstep-' + 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' ? '❌' : '⏳';
|
||
} else {
|
||
el.className = 'disk-step';
|
||
icon.textContent = '○';
|
||
}
|
||
});
|
||
|
||
var pct = data.pct || 0;
|
||
document.getElementById('progress-fill').style.width = pct + '%';
|
||
document.getElementById('progress-percent').textContent = pct + '%';
|
||
document.getElementById('progress-msg').textContent = data.msg || '';
|
||
|
||
if (data.step === 'error' || data.error) {
|
||
showProgressError(data.error || data.msg || 'Ismeretlen hiba');
|
||
}
|
||
}
|
||
|
||
function showProgressError(msg) {
|
||
clearInterval(pollTimer);
|
||
document.getElementById('progress-error').textContent = 'Hiba: ' + msg;
|
||
document.getElementById('progress-error').style.display = 'block';
|
||
document.getElementById('wizard-progress').querySelector('h3').textContent = 'Csatolás sikertelen';
|
||
}
|
||
|
||
function showDone(mountPath) {
|
||
document.getElementById('wizard-progress').style.display = 'none';
|
||
document.getElementById('wizard-done').style.display = 'block';
|
||
document.getElementById('done-path').textContent = mountPath;
|
||
document.getElementById('wizard-done').scrollIntoView({behavior:'smooth'});
|
||
}
|
||
|
||
// --- Helpers ---
|
||
|
||
function escapeJS(s) {
|
||
return s.replace(/\\/g, '\\\\').replace(/'/g, "\\'");
|
||
}
|
||
|
||
function escapeAttr(s) {
|
||
return s.replace(/&/g, '&').replace(/"/g, '"').replace(/'/g, ''').replace(/</g, '<').replace(/>/g, '>');
|
||
}
|
||
|
||
// Cleanup on page unload (best-effort)
|
||
window.addEventListener('beforeunload', function() {
|
||
if (rawMountPath && !document.getElementById('wizard-done').style.display !== 'none') {
|
||
// Best-effort cleanup via sendBeacon
|
||
navigator.sendBeacon('/api/storage/attach/cancel');
|
||
}
|
||
});
|
||
</script>
|
||
|
||
{{template "layout_end" .}}
|
||
{{end}}
|