02650e3202
Controller: - internal/web/csrf.go (new): CsrfProtect middleware, csrfToken/csrfField helpers - auth.go: per-session CSRF token (csrfToken field, csrfTokenForSession method) - server.go: executeTemplate wrapper auto-injects CSRFField+CSRFToken - main.go: wire CsrfProtect on all routes; bump to v0.23.0 - handlers.go, storage_handlers.go, handler_restore.go: executeTemplate - All templates: CSRFField in forms, meta csrf-token, csrfHeaders() JS helper, fetch calls updated; sendBeacon→fetch+keepalive in storage_attach.html Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
583 lines
24 KiB
HTML
583 lines
24 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', headers: csrfHeaders()})
|
||
.catch(function(){}) // ignore cancel errors
|
||
.then(function() { return fetch('/api/storage/scan', {method:'POST', headers: csrfHeaders()}); })
|
||
.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: Object.assign({'Content-Type': 'application/json'}, csrfHeaders()),
|
||
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';
|
||
|
||
// Client-side validation
|
||
if (!/^[a-zA-Z0-9_]+$/.test(name)) {
|
||
errEl.textContent = 'A mappanéven csak betűk, számok és alávonás megengedett.';
|
||
errEl.style.display = 'block';
|
||
return;
|
||
}
|
||
if (name.length > 32) {
|
||
errEl.textContent = 'A mappanév legfeljebb 32 karakter lehet.';
|
||
errEl.style.display = 'block';
|
||
return;
|
||
}
|
||
|
||
fetch('/api/storage/attach/mkdir', {
|
||
method: 'POST',
|
||
headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders()),
|
||
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', headers: csrfHeaders()}).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: Object.assign({'Content-Type': 'application/json'}, csrfHeaders()),
|
||
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 fetch (sendBeacon can't send CSRF headers)
|
||
fetch('/api/storage/attach/cancel', {method:'POST', headers: csrfHeaders(), keepalive: true}).catch(function(){});
|
||
}
|
||
});
|
||
</script>
|
||
|
||
{{template "layout_end" .}}
|
||
{{end}}
|