v0.43.0: rebuilt storage management (guided init/attach/eject on agent disk model)
Controller-only UI/orchestration over the agent's disk endpoints + StoragePath registry. New: storage overview (data_bearing badges), guided init (format -> resolve fs UUID -> assign -> register; data-bearing REFUSAL surfaces the felhom-opsign command, no force-format), guided attach, eject (+deregister, dependent-guest warning). agentapi: DiskInfo.DurableID/FSUUID + FormatResult. PendingOp (parsed from the 403). Honest buttons (migrate disabled, no 404s). Phase 3: removed dead CrossDrive blocks in deploy.html/backups.html. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -335,9 +335,6 @@
|
||||
<span class="tier-browsable" title="A mentés böngészhető fájlrendszerben">📁</span>
|
||||
<div class="layer-actions">
|
||||
<a href="/stacks/{{.StackName}}/deploy" class="btn btn-xs btn-outline">Beállítás</a>
|
||||
<button class="btn btn-xs btn-outline"
|
||||
onclick="triggerCrossDriveBackup('{{.StackName}}', this)">
|
||||
Futtatás most</button>
|
||||
</div>
|
||||
{{else}}
|
||||
<span class="layer-auto-ok">✓ 1. mentés auto</span>
|
||||
@@ -364,11 +361,6 @@
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{if .Backup.CrossDriveSummary}}
|
||||
<div class="cross-drive-actions" style="margin-top:1rem">
|
||||
<button class="btn btn-sm btn-outline" onclick="triggerAllCrossDrive(this)">Összes 2. mentés futtatása most</button>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
@@ -604,50 +596,6 @@ function toggleTier(header) {
|
||||
}
|
||||
}
|
||||
|
||||
function triggerCrossDriveBackup(stackName, btn) {
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Fut...';
|
||||
fetch('/api/stacks/' + stackName + '/cross-backup/run', {method: 'POST', headers: csrfHeaders()})
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(d) {
|
||||
if (!d.ok) {
|
||||
alert('Hiba: ' + (d.error || 'Ismeretlen hiba'));
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Futtatás most';
|
||||
return;
|
||||
}
|
||||
btn.textContent = 'Fut...';
|
||||
setTimeout(function() { location.reload(); }, 5000);
|
||||
})
|
||||
.catch(function(e) {
|
||||
alert('Hálózati hiba: ' + e.message);
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Futtatás most';
|
||||
});
|
||||
}
|
||||
|
||||
function triggerAllCrossDrive(btn) {
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Indítás...';
|
||||
fetch('/api/backup/cross-drive/run-all', {method: 'POST', headers: csrfHeaders()})
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(d) {
|
||||
if (!d.ok) {
|
||||
alert('Hiba: ' + (d.error || 'Ismeretlen hiba'));
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Összes futtatása most';
|
||||
return;
|
||||
}
|
||||
btn.textContent = 'Mentések futnak...';
|
||||
setTimeout(function() { location.reload(); }, 5000);
|
||||
})
|
||||
.catch(function(e) {
|
||||
alert('Hálózati hiba: ' + e.message);
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Összes futtatása most';
|
||||
});
|
||||
}
|
||||
|
||||
function triggerBackupFromPage() {
|
||||
const btn = document.getElementById('backup-page-btn');
|
||||
btn.disabled = true;
|
||||
|
||||
@@ -68,9 +68,9 @@
|
||||
{{end}}
|
||||
</div>
|
||||
{{if .OtherStoragePaths}}
|
||||
<a href="/stacks/{{.Meta.Slug}}/migrate" class="btn btn-sm btn-outline" style="margin-top:.75rem">
|
||||
<span class="btn btn-sm btn-outline" style="margin-top:.75rem;opacity:.45;cursor:not-allowed" title="Hamarosan">
|
||||
📦 Mozgatás másik tárolóra
|
||||
</a>
|
||||
</span>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
@@ -116,94 +116,6 @@
|
||||
<a href="/backups" style="color:var(--accent-blue)">Mentési állapot →</a>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<hr style="border-color:var(--border);margin:1rem 0">
|
||||
|
||||
<p style="font-weight:500;margin-bottom:1rem">2. mentés — másolat másik meghajtóra:</p>
|
||||
|
||||
{{if .BackupDestWarning}}
|
||||
<div class="alert {{if eq .BackupDestWarningSeverity "critical"}}alert-error{{else}}alert-warning{{end}}" style="margin-bottom:1rem">{{.BackupDestWarning}}</div>
|
||||
{{end}}
|
||||
|
||||
{{if not .BackupDestPaths}}
|
||||
<div class="alert alert-info">
|
||||
Másik adattároló szükséges a másolat készítéséhez.
|
||||
<a href="/settings" style="color:var(--accent-blue)">Csatlakoztass egy külső meghajtót a Beállítások oldalon.</a>
|
||||
</div>
|
||||
{{else}}
|
||||
<form method="post" action="/settings/cross-backup/{{.Meta.Slug}}">
|
||||
{{.CSRFField}}
|
||||
<div class="settings-grid" style="margin-bottom:1rem">
|
||||
<div class="settings-row">
|
||||
<span class="settings-label">Engedélyezve</span>
|
||||
<label class="toggle" style="margin:0">
|
||||
<input type="checkbox" name="cross_drive_enabled" id="cross-drive-enabled"
|
||||
{{if and .CrossDriveConfig .CrossDriveConfig.Enabled}}checked{{end}}
|
||||
onchange="toggleCrossDriveFields()">
|
||||
<span class="toggle-label">Igen</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="settings-row">
|
||||
<span class="settings-label">Cél tárhely</span>
|
||||
<select name="cross_drive_dest" id="cd-dest" class="form-control cross-drive-field" style="max-width:20rem"
|
||||
{{if not (and .CrossDriveConfig .CrossDriveConfig.Enabled)}}disabled{{end}}>
|
||||
{{range .BackupDestPaths}}
|
||||
<option value="{{.Path}}"
|
||||
{{if and $.CrossDriveConfig (eq $.CrossDriveConfig.DestinationPath .Path)}}selected{{end}}>
|
||||
{{.Label}} ({{.Path}}){{if .IsDefault}} ★{{end}}
|
||||
{{if .FreeHuman}} — {{.FreeHuman}} szabad{{end}}
|
||||
</option>
|
||||
{{end}}
|
||||
</select>
|
||||
</div>
|
||||
<div class="settings-row">
|
||||
<span class="settings-label">Ütemezés</span>
|
||||
<div>
|
||||
<select name="cross_drive_schedule" id="cd-schedule" class="form-control cross-drive-field" style="max-width:20rem"
|
||||
{{if not (and .CrossDriveConfig .CrossDriveConfig.Enabled)}}disabled{{end}}
|
||||
onchange="onScheduleChange()">
|
||||
<option value="daily" {{if and .CrossDriveConfig (eq .CrossDriveConfig.Schedule "daily")}}selected{{end}}>
|
||||
Naponta (az éjszakai mentés után)
|
||||
</option>
|
||||
<option value="weekly" {{if or (and .CrossDriveConfig (eq .CrossDriveConfig.Schedule "weekly")) (and .CrossDriveConfig (eq .CrossDriveConfig.Schedule "manual"))}}selected{{end}}>
|
||||
Hetente, vasárnap (az éjszakai mentés után)
|
||||
</option>
|
||||
</select>
|
||||
<div id="weekly-note" class="form-hint" style="margin-top:.5rem;display:{{if and .CrossDriveConfig (or (eq .CrossDriveConfig.Schedule "weekly") (eq .CrossDriveConfig.Schedule "manual"))}}block{{else}}none{{end}}">
|
||||
ℹ Heti mentés esetén visszaállításkor az adatbázis is a mentés napjára áll vissza
|
||||
a konzisztencia érdekében. A mentés napja és a visszaállítás között keletkezett
|
||||
adatbázis-változások elvesznek (max. 7 nap).
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{if .CrossDriveConfig}}
|
||||
{{if .CrossDriveConfig.LastRun}}
|
||||
<div class="form-hint" style="margin-bottom:.75rem">
|
||||
Utolsó futás: {{.CrossDriveConfig.LastRun}}
|
||||
{{if eq .CrossDriveConfig.LastStatus "ok"}}Sikeres{{else if eq .CrossDriveConfig.LastStatus "error"}}Hiba: {{.CrossDriveConfig.LastError}}{{else if eq .CrossDriveConfig.LastStatus "running"}}Fut...{{end}}
|
||||
{{if .CrossDriveConfig.LastDuration}} ({{.CrossDriveConfig.LastDuration}}){{end}}
|
||||
{{if .CrossDriveConfig.LastSizeHuman}} — {{.CrossDriveConfig.LastSizeHuman}}{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
<div style="display:flex;gap:.5rem;flex-wrap:wrap">
|
||||
<button type="submit" class="btn btn-sm btn-primary">Beállítások mentése</button>
|
||||
{{if and .CrossDriveConfig .CrossDriveConfig.Enabled}}
|
||||
<button type="button" class="btn btn-sm btn-outline"
|
||||
onclick="triggerCrossDriveBackup('{{.Meta.Slug}}', this)">
|
||||
Mentés most
|
||||
</button>
|
||||
{{end}}
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="form-hint" style="margin-top:.75rem;color:var(--text-muted)">
|
||||
A cél meghajtó legyen más fizikai eszköz a meghibásodás elleni védelem érdekében.
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
@@ -775,62 +687,6 @@ function buildPostDeployCard(stackName) {
|
||||
return html;
|
||||
}
|
||||
|
||||
function toggleCrossDriveFields() {
|
||||
var enabled = document.getElementById('cross-drive-enabled').checked;
|
||||
var fields = document.querySelectorAll('.cross-drive-field');
|
||||
for (var i = 0; i < fields.length; i++) {
|
||||
fields[i].disabled = !enabled;
|
||||
}
|
||||
}
|
||||
|
||||
function onScheduleChange() {
|
||||
var sel = document.getElementById('cd-schedule');
|
||||
var note = document.getElementById('weekly-note');
|
||||
if (sel && note) {
|
||||
note.style.display = sel.value === 'weekly' ? 'block' : 'none';
|
||||
}
|
||||
}
|
||||
|
||||
function triggerCrossDriveBackup(stackName, btn) {
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Mentés folyamatban...';
|
||||
fetch('/api/stacks/' + stackName + '/cross-backup/run', {method: 'POST', headers: csrfHeaders()})
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(d) {
|
||||
if (!d.ok) {
|
||||
showAlert('Hiba: ' + (d.error || 'Ismeretlen hiba'));
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Mentés most';
|
||||
return;
|
||||
}
|
||||
btn.textContent = 'Mentés folyamatban...';
|
||||
// Poll status
|
||||
var poll = setInterval(function() {
|
||||
fetch('/api/stacks/' + stackName + '/cross-backup/status')
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(s) {
|
||||
if (!s.ok || !s.data) return;
|
||||
if (!s.data.running) {
|
||||
clearInterval(poll);
|
||||
var status = s.data.last_status;
|
||||
if (status === 'ok') {
|
||||
btn.textContent = 'Mentés kész';
|
||||
} else {
|
||||
btn.textContent = 'Hiba';
|
||||
showAlert('Hiba: ' + (s.data.last_error || 'Ismeretlen hiba'));
|
||||
}
|
||||
setTimeout(function() { location.reload(); }, 2000);
|
||||
}
|
||||
}).catch(function(){});
|
||||
}, 3000);
|
||||
})
|
||||
.catch(function(e) {
|
||||
showAlert('Hálózati hiba: ' + e.message);
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Mentés most';
|
||||
});
|
||||
}
|
||||
|
||||
function checkStorageSpace(sel) {
|
||||
var opt = sel.options[sel.selectedIndex];
|
||||
var warn = document.getElementById('storage-space-warn');
|
||||
|
||||
@@ -289,7 +289,7 @@ function pollUntilBack() {
|
||||
<div class="storage-app-row">
|
||||
<a href="/apps/{{.Stack}}" class="storage-app-link">{{.Name}}</a>
|
||||
{{if .SizeHuman}}<span class="mono form-hint">{{.SizeHuman}}</span>{{end}}
|
||||
<a href="/stacks/{{.Stack}}/migrate" class="btn btn-xs btn-outline" title="Adatok áthelyezése másik tárolóra">📦 Mozgatás</a>
|
||||
<span class="btn btn-xs btn-outline" style="opacity:.45;cursor:not-allowed" title="Hamarosan">📦 Mozgatás</span>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
@@ -334,7 +334,7 @@ function pollUntilBack() {
|
||||
</form>
|
||||
{{end}}
|
||||
{{if and (gt .AppCount 0) .HasOtherPaths}}
|
||||
<a href="/settings/storage/migrate-drive?source={{.Path}}" class="btn btn-xs btn-outline">📦 Összes adat átköltöztetése</a>
|
||||
<span class="btn btn-xs btn-outline" style="opacity:.45;cursor:not-allowed" title="Hamarosan">📦 Összes adat átköltöztetése</span>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
@@ -352,6 +352,53 @@ function pollUntilBack() {
|
||||
<a href="/settings/storage/attach" class="btn btn-sm btn-outline">🔗 Meglévő meghajtó csatolása</a>
|
||||
</div>
|
||||
|
||||
<div style="margin-top:1.5rem">
|
||||
<h4 style="margin-bottom:.25rem">Meghajtók (ügynök nézet)</h4>
|
||||
<p class="form-hint" style="margin-bottom:.75rem">A host-ügynök által észlelt meghajtók élő nézete (a tárolás végrehajtása az ügynöké).</p>
|
||||
<div id="agent-disks">Betöltés…</div>
|
||||
</div>
|
||||
<script>
|
||||
window.__registeredPaths=[{{range .StoragePaths}}{{if .Path}}"{{.Path}}",{{end}}{{end}}];
|
||||
(function(){
|
||||
function badge(d){
|
||||
if(d.backing_device===""){ return ''; }
|
||||
return d.data_bearing
|
||||
? '<span class="badge badge-error" title="'+(d.data_reason||'')+'">Adatot tartalmaz</span>'
|
||||
: '<span class="badge badge-ok">Üres</span>';
|
||||
}
|
||||
function reg(d, registered){ return registered[d.mount_path] ? '<span class="badge badge-ok">Regisztrálva</span>' : (d.mount_path?'<span class="badge">Nem regisztrált</span>':''); }
|
||||
async function load(){
|
||||
var box=document.getElementById('agent-disks'); if(!box) return;
|
||||
try{
|
||||
var r=await fetch('/api/disks'); var j=await r.json();
|
||||
if(!j.ok){ box.innerHTML='<p class="form-hint">'+(j.error||'Nem elérhető')+'</p>'; return; }
|
||||
var disks=(j.data&&j.data.disks)||[];
|
||||
if(disks.length===0){ box.innerHTML='<p class="form-hint">Nincs észlelt meghajtó.</p>'; return; }
|
||||
var registered={}; (window.__registeredPaths||[]).forEach(function(p){registered[p]=true;});
|
||||
var html='<table class="data-table"><thead><tr><th>Tároló</th><th>Típus</th><th>Eszköz</th><th>Csatolás</th><th>Osztály</th><th>Adat</th><th>Reg.</th><th></th></tr></thead><tbody>';
|
||||
disks.forEach(function(d){
|
||||
var ej = (d.mount_path && d.mount_path.indexOf('/mnt/')===0) ? '<button class="btn btn-xs btn-danger-outline" onclick="ejectDisk(\''+d.mount_path+'\')">Leválasztás</button>' : '';
|
||||
html+='<tr><td>'+d.name+'</td><td>'+d.type+'</td><td class="mono">'+(d.backing_device||'—')+'</td><td class="mono">'+(d.mount_path||'—')+'</td><td>'+(d.class||'—')+'</td><td>'+badge(d)+'</td><td>'+reg(d,registered)+'</td><td>'+ej+'</td></tr>';
|
||||
});
|
||||
html+='</tbody></table>';
|
||||
box.innerHTML=html;
|
||||
}catch(e){ box.innerHTML='<p class="form-hint">Hiba: '+e.message+'</p>'; }
|
||||
}
|
||||
window.ejectDisk=async function(where){
|
||||
if(!confirm('Leválasztja a(z) '+where+' meghajtót? Az adatok megmaradnak, de az ott lévő alkalmazások elveszítik a tárhelyet.')) return;
|
||||
try{
|
||||
var r=await fetch('/api/storage/eject',{method:'POST',headers:Object.assign({'Content-Type':'application/json'},csrfHeaders()),body:JSON.stringify({where:where})});
|
||||
var j=await r.json();
|
||||
if(!j.ok){ alert('Hiba: '+(j.error||'')); return; }
|
||||
var dep=(j.data&&j.data.dependent_guests)||[];
|
||||
if(dep.length>0){ alert('Leválasztva. Figyelem: '+dep.length+' vendég (VMID: '+dep.join(', ')+') függött ettől a tárhelytől.'); }
|
||||
location.reload();
|
||||
}catch(e){ alert('Hiba: '+e.message); }
|
||||
};
|
||||
load();
|
||||
})();
|
||||
</script>
|
||||
|
||||
<details class="storage-add-details">
|
||||
<summary class="btn btn-sm btn-outline" style="margin-top:.75rem;cursor:pointer">Már csatlakoztatott tárhely hozzáadása kézzel</summary>
|
||||
<form method="POST" action="/settings/storage/add" class="storage-add-form">
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
{{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>
|
||||
|
||||
<div class="settings-card">
|
||||
<h3>1. Meghajtó kiválasztása</h3>
|
||||
<p class="settings-card-desc">Válassza ki a már fájlrendszerrel rendelkező meghajtót.
|
||||
<strong>A meghajtón lévő adatok nem törlődnek</strong> — a csatolás csak elérhetővé teszi azokat.</p>
|
||||
<div id="disk-error" class="alert alert-error" style="display:none;margin-bottom:1rem"></div>
|
||||
<div id="disk-list">Betöltés…</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-card" id="cfg-card" style="display:none">
|
||||
<h3>2. Konfiguráció</h3>
|
||||
<form id="attach-form" onsubmit="return submitAttach(event)">
|
||||
<div class="form-group">
|
||||
<label>Kiválasztott eszköz</label>
|
||||
<span class="settings-value mono" id="sel-device">—</span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="mount-name">Csatlakoztatási név <span class="required">*</span></label>
|
||||
<div style="display:flex;align-items:center;gap:.25rem">
|
||||
<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:180px">
|
||||
</div>
|
||||
<span class="form-hint">A meghajtó a /mnt/<név> ú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.25rem">
|
||||
<input type="checkbox" id="set-default">
|
||||
<span class="toggle-label">Beállítás alapértelmezett adattárolóként új telepítéseknél</span>
|
||||
</label>
|
||||
<div class="form-actions" style="gap:.75rem">
|
||||
<button type="submit" class="btn btn-primary" id="attach-btn">Csatolás</button>
|
||||
<a href="/settings" class="btn btn-outline">Mégsem</a>
|
||||
</div>
|
||||
<div id="attach-result" style="margin-top:1rem"></div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
var selDevice = "", selFSType = "";
|
||||
|
||||
async function loadDisks(){
|
||||
try{
|
||||
var r = await fetch('/api/disks'); var j = await r.json();
|
||||
if(!j.ok){ throw new Error(j.error||'Hiba'); }
|
||||
var disks = (j.data&&j.data.disks)||[];
|
||||
// Attachable: has a backing device, an fs-UUID identity (durable_id "uuid:…"), and isn't mounted yet.
|
||||
var attachable = disks.filter(function(d){ return d.backing_device!=="" && (d.durable_id||"").indexOf("uuid:")===0 && !d.mount_path; });
|
||||
if(attachable.length===0){ document.getElementById('disk-list').innerHTML='<p class="form-hint">Nincs csatolható (fájlrendszerrel rendelkező, még nem csatolt) meghajtó.</p>'; return; }
|
||||
var html='<table class="data-table"><thead><tr><th></th><th>Tároló</th><th>Típus</th><th>Eszköz</th><th>Osztály</th></tr></thead><tbody>';
|
||||
attachable.forEach(function(d){
|
||||
html+='<tr><td><input type="radio" name="disk" value="'+d.backing_device+'" onchange="pickDisk(this)"></td>'
|
||||
+'<td>'+d.name+'</td><td>'+d.type+'</td><td class="mono">'+d.backing_device+'</td><td>'+(d.class||'—')+'</td></tr>';
|
||||
});
|
||||
html+='</tbody></table>';
|
||||
document.getElementById('disk-list').innerHTML=html;
|
||||
}catch(e){ var el=document.getElementById('disk-error'); el.style.display='block'; el.textContent='Meghajtók betöltése sikertelen: '+e.message; }
|
||||
}
|
||||
|
||||
function pickDisk(radio){
|
||||
selDevice=radio.value;
|
||||
document.getElementById('sel-device').textContent=selDevice;
|
||||
document.getElementById('cfg-card').style.display='block';
|
||||
document.getElementById('cfg-card').scrollIntoView({behavior:'smooth'});
|
||||
}
|
||||
|
||||
async function submitAttach(ev){
|
||||
ev.preventDefault();
|
||||
var btn=document.getElementById('attach-btn'); var out=document.getElementById('attach-result');
|
||||
btn.disabled=true; out.innerHTML='<p class="form-hint">Csatlakoztatás folyamatban…</p>';
|
||||
try{
|
||||
var body={device:selDevice, fstype:"", mount_name:document.getElementById('mount-name').value,
|
||||
label:document.getElementById('storage-label').value, set_default:document.getElementById('set-default').checked};
|
||||
var r=await fetch('/api/storage/attach',{method:'POST',headers:Object.assign({'Content-Type':'application/json'},csrfHeaders()),body:JSON.stringify(body)});
|
||||
var j=await r.json();
|
||||
if(!j.ok){ throw new Error(j.error||'Hiba'); }
|
||||
out.innerHTML='<div class="alert alert-success">✅ A meghajtó sikeresen csatolva és regisztrálva: <strong class="mono">'+(j.data.where||'')+'</strong>. <a href="/settings">Vissza a Beállításokhoz →</a></div>';
|
||||
}catch(e){ out.innerHTML='<div class="alert alert-error">Hiba: '+e.message+'</div>'; btn.disabled=false; }
|
||||
return false;
|
||||
}
|
||||
loadDisks();
|
||||
</script>
|
||||
|
||||
{{template "layout_end" .}}
|
||||
{{end}}
|
||||
@@ -0,0 +1,125 @@
|
||||
{{define "storage_init"}}
|
||||
{{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>Új meghajtó inicializálása</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-card">
|
||||
<h3>1. Meghajtó kiválasztása</h3>
|
||||
<p class="settings-card-desc">Válassza ki a formázandó meghajtót. A formázás biztonságát a host-ügynök
|
||||
garantálja: <strong>adatot tartalmazó meghajtó nem formázható operátori aláírás nélkül</strong>.</p>
|
||||
<div id="disk-error" class="alert alert-error" style="display:none;margin-bottom:1rem"></div>
|
||||
<div id="disk-list">Betöltés…</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-card" id="cfg-card" style="display:none">
|
||||
<h3>2. Konfiguráció</h3>
|
||||
<form id="init-form" onsubmit="return submitInit(event)">
|
||||
<div class="form-group">
|
||||
<label>Kiválasztott eszköz</label>
|
||||
<span class="settings-value mono" id="sel-device">—</span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="mount-name">Csatlakoztatási név <span class="required">*</span></label>
|
||||
<div style="display:flex;align-items:center;gap:.25rem">
|
||||
<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:180px">
|
||||
</div>
|
||||
<span class="form-hint">A meghajtó a /mnt/<név> útvonalra kerül.</span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="fstype">Fájlrendszer</label>
|
||||
<select id="fstype" class="form-control" style="max-width:180px">
|
||||
<option value="ext4" selected>ext4</option>
|
||||
<option value="xfs">xfs</option>
|
||||
</select>
|
||||
</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.25rem">
|
||||
<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-warning" id="warn-databearing" style="display:none;margin-bottom:1rem">
|
||||
⚠️ A kiválasztott meghajtó <strong>adatot tartalmaz</strong>. A formázás védelmi okból csak
|
||||
operátori aláírással hajtható végre — a rendszer megmutatja a szükséges parancsot.
|
||||
</div>
|
||||
<div class="form-actions" style="gap:.75rem">
|
||||
<button type="submit" class="btn btn-danger-outline" id="init-btn">Inicializálás indítása</button>
|
||||
<a href="/settings" class="btn btn-outline">Mégsem</a>
|
||||
</div>
|
||||
<div id="init-result" style="margin-top:1rem"></div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
var selDevice = "", selDataBearing = false;
|
||||
|
||||
function badge(d){
|
||||
if(d.backing_device===""){ return '<span class="badge">—</span>'; }
|
||||
return d.data_bearing
|
||||
? '<span class="badge badge-error" title="'+(d.data_reason||'')+'">Adatot tartalmaz</span>'
|
||||
: '<span class="badge badge-ok">Üres — formázható</span>';
|
||||
}
|
||||
|
||||
async function loadDisks(){
|
||||
try{
|
||||
var r = await fetch('/api/disks'); var j = await r.json();
|
||||
if(!j.ok){ throw new Error(j.error||'Hiba'); }
|
||||
var disks = (j.data&&j.data.disks)||[];
|
||||
var formattable = disks.filter(function(d){return d.backing_device!=="";});
|
||||
if(formattable.length===0){ document.getElementById('disk-list').innerHTML='<p class="form-hint">Nincs formázható (blokkeszközzel rendelkező) meghajtó.</p>'; return; }
|
||||
var html='<table class="data-table"><thead><tr><th></th><th>Tároló</th><th>Típus</th><th>Eszköz</th><th>Állapot</th><th>Osztály</th><th>Adat</th></tr></thead><tbody>';
|
||||
formattable.forEach(function(d,i){
|
||||
html+='<tr><td><input type="radio" name="disk" value="'+d.backing_device+'" data-db="'+(d.data_bearing?'1':'0')+'" onchange="pickDisk(this)"></td>'
|
||||
+'<td>'+d.name+'</td><td>'+d.type+'</td><td class="mono">'+d.backing_device+'</td>'
|
||||
+'<td>'+(d.state==='attached'?'csatlakoztatva':d.state)+'</td><td>'+(d.class||'—')+'</td><td>'+badge(d)+'</td></tr>';
|
||||
});
|
||||
html+='</tbody></table>';
|
||||
document.getElementById('disk-list').innerHTML=html;
|
||||
}catch(e){ var el=document.getElementById('disk-error'); el.style.display='block'; el.textContent='Meghajtók betöltése sikertelen: '+e.message; }
|
||||
}
|
||||
|
||||
function pickDisk(radio){
|
||||
selDevice=radio.value; selDataBearing=radio.getAttribute('data-db')==='1';
|
||||
document.getElementById('sel-device').textContent=selDevice;
|
||||
document.getElementById('warn-databearing').style.display=selDataBearing?'block':'none';
|
||||
document.getElementById('cfg-card').style.display='block';
|
||||
document.getElementById('cfg-card').scrollIntoView({behavior:'smooth'});
|
||||
}
|
||||
|
||||
async function submitInit(ev){
|
||||
ev.preventDefault();
|
||||
var btn=document.getElementById('init-btn'); var out=document.getElementById('init-result');
|
||||
btn.disabled=true; out.innerHTML='<p class="form-hint">Formázás és csatlakoztatás folyamatban…</p>';
|
||||
try{
|
||||
var body={device:selDevice, fstype:document.getElementById('fstype').value,
|
||||
mount_name:document.getElementById('mount-name').value, label:document.getElementById('storage-label').value,
|
||||
set_default:document.getElementById('set-default').checked};
|
||||
var r=await fetch('/api/storage/init',{method:'POST',headers:Object.assign({'Content-Type':'application/json'},csrfHeaders()),body:JSON.stringify(body)});
|
||||
var j=await r.json();
|
||||
if(r.status===409 && j.data && j.data.refused){
|
||||
out.innerHTML='<div class="alert alert-warning"><strong>Operátori aláírás szükséges.</strong><br>'
|
||||
+'A meghajtó adatot tartalmaz, ezért a formázás védelmi okból nem hajtható végre automatikusan'
|
||||
+(j.data.reason?(' ('+j.data.reason+')'):'')+'.<br><br>Az engedélyezéshez futtassa offline az operátor gépén:'
|
||||
+'<pre class="mono" style="white-space:pre-wrap;background:var(--bg-primary);padding:.75rem;border-radius:6px;margin-top:.5rem">'+(j.data.opsign||'(nem elérhető)')+'</pre>'
|
||||
+'Az aláírás után a Hub végrehajtja a műveletet; ezután térjen vissza ide.</div>';
|
||||
btn.disabled=false; return false;
|
||||
}
|
||||
if(!j.ok){ throw new Error(j.error||'Hiba'); }
|
||||
out.innerHTML='<div class="alert alert-success">✅ A meghajtó sikeresen inicializálva és regisztrálva: <strong class="mono">'+(j.data.where||'')+'</strong>. <a href="/settings">Vissza a Beállításokhoz →</a></div>';
|
||||
}catch(e){ out.innerHTML='<div class="alert alert-error">Hiba: '+e.message+'</div>'; btn.disabled=false; }
|
||||
return false;
|
||||
}
|
||||
loadDisks();
|
||||
</script>
|
||||
|
||||
{{template "layout_end" .}}
|
||||
{{end}}
|
||||
Reference in New Issue
Block a user