controller v0.92.0: NAS network storage Part A2 (registry + UI + per-share health)

Controller-side of NAS network storage, proxying to agent A1 /netstorage/*. Distinct
'network' storage kind (no drive lifecycle); add/list/remove + per-share health UI;
unreachable NAS is a recoverable warning, never the drive missing/stop cascade; SMB
creds pass through to the agent, never persisted.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HxLA1mZurFq9kt8hneFeCs
This commit is contained in:
2026-06-30 14:40:47 +02:00
parent 9391f4ea71
commit 364dc50794
13 changed files with 837 additions and 1 deletions
@@ -158,6 +158,7 @@
<span class="stack-state-label">{{stateLabel .State}}</span>
{{if .Orphaned}}<span class="badge badge-orphaned">Elavult</span>{{end}}
{{$ms := index $.MissingStorage .Name}}{{if $ms}}<span class="badge badge-missing-storage" title="Az alkalmazás adattárolója nem elérhető. Csatlakoztasd újra a meghajtót, vagy helyezd át az adatokat egy másik tárhelyre.">⚠ Hiányzó tárhely: {{$ms}}</span>{{end}}
{{$nw := index $.NetworkWarnings .Name}}{{if $nw}}<span class="badge badge-warn" title="A hálózati tárhely (NAS) jelenleg nem érhető el. Az alkalmazás fut; az adatok elérése a NAS visszatértével helyreáll.">⚠ Hálózati tárhely nem elérhető: {{$nw}}</span>{{end}}
{{if and .Deployed (routeUnpublished .State)}}<span class="badge badge-route-unpublished" title="A proxy (Traefik) csak egészséges konténerhez publikál nyilvános útvonalat. Amíg az alkalmazás nem egészséges, az URL 404-et ad, pedig a konténer fut.">⚠ URL nem elérhető</span>{{end}}
{{if .Protected}}
@@ -372,6 +372,105 @@ pollUntilBack();
{{end}}
</div>
<!-- NAS network storage (Part A2) — a distinct class from the physical drives above. No
leválasztás/leszerelés/áthelyezés/törlés; the only lifecycle action is Eltávolítás. -->
<div class="storage-section" style="margin-top:1.5rem">
<h3 style="margin-bottom:.25rem">Hálózati tárhely (NAS)</h3>
<p class="form-hint" style="margin-top:0">
Egy NAS-megosztás (NFS vagy SMB) csatlakoztatása nagy méretű médiatartalomhoz (film, fotó, zene).
A megosztás kiválasztható médiaalkalmazás adatkönyvtáraként. A hálózati tárhely nem fizikai
meghajtó — nincs leszerelés/áthelyezés, csak eltávolítás.
</p>
{{if .NetworkStoragePaths}}
<div class="storage-paths-list">
{{range .NetworkStoragePaths}}
<div class="storage-path-item{{if eq .Health "unreachable"}} storage-disconnected{{end}}">
<div class="storage-path-header">
<div class="storage-path-info">
<span class="storage-path-label">{{.Label}}</span>
<span class="storage-path-path mono">{{.Protocol}} · {{.Server}}:{{.Export}} → {{.Path}}</span>
</div>
<div class="storage-path-badges">
{{if eq .Health "ok"}}<span class="badge badge-ok" title="A megosztás elérhető és csatlakoztatva van">Elérhető</span>
{{else if eq .Health "idle"}}<span class="badge badge-neutral" title="Elérhető, jelenleg készenlétben (igény szerint csatlakozik)">Készenlét</span>
{{else if eq .Health "unreachable"}}<span class="badge badge-warn" title="A NAS jelenleg nem érhető el — az érintett alkalmazások átmenetileg nem olvasnak róla">Nem elérhető</span>
{{else}}<span class="badge badge-neutral" title="Az állapot jelenleg nem lekérdezhető">Ismeretlen</span>{{end}}
</div>
</div>
<div class="storage-path-actions">
<button class="btn btn-xs btn-danger-outline" onclick="netStorageRemove('{{.Name}}','{{.Label}}')">Eltávolítás</button>
</div>
</div>
{{end}}
</div>
{{else}}
<p class="form-hint">Nincs hálózati tárhely beállítva.</p>
{{end}}
<details style="margin-top:.75rem">
<summary class="btn btn-xs btn-primary" style="cursor:pointer;display:inline-block">Hálózati tárhely hozzáadása</summary>
<div style="margin-top:.75rem;padding:1rem;border:1px solid var(--border,#ddd);border-radius:6px;max-width:520px">
<div class="form-row"><label>Név (azonosító)</label>
<input id="ns-name" type="text" placeholder="pl. media" class="form-input"></div>
<div class="form-row"><label>Protokoll</label>
<select id="ns-protocol" class="form-input" onchange="nsToggleSmb()">
<option value="nfs">NFS (ajánlott)</option>
<option value="smb">SMB / CIFS</option>
</select></div>
<div class="form-row"><label>Szerver (IP vagy hosztnév)</label>
<input id="ns-server" type="text" placeholder="pl. 192.168.0.10" class="form-input"></div>
<div class="form-row"><label id="ns-export-label">Megosztás (NFS export útvonal)</label>
<input id="ns-export" type="text" placeholder="pl. /volume1/media" class="form-input"></div>
<div class="form-row"><label>Alkalmazás felhasználói azonosító (uid)</label>
<input id="ns-uid" type="number" value="1000" class="form-input">
<span class="form-hint">A legtöbb médiaalkalmazás 1000-es uid-del fut.</span></div>
<div id="ns-smb-creds" style="display:none">
<div class="form-row"><label>SMB felhasználónév</label>
<input id="ns-username" type="text" autocomplete="off" class="form-input"></div>
<div class="form-row"><label>SMB jelszó</label>
<input id="ns-password" type="password" autocomplete="new-password" class="form-input">
<span class="form-hint">A jelszót a gazda ügynök 0600-as fájlba írja; a vezérlő nem tárolja.</span></div>
</div>
<button class="btn btn-sm btn-primary" style="margin-top:.5rem" onclick="netStorageAdd()">Csatlakoztatás</button>
<div id="ns-add-msg" class="form-hint" style="margin-top:.5rem"></div>
</div>
</details>
</div>
<script>
function nsToggleSmb(){
var smb = document.getElementById('ns-protocol').value === 'smb';
document.getElementById('ns-smb-creds').style.display = smb ? 'block' : 'none';
document.getElementById('ns-export-label').textContent = smb ? 'Megosztás (SMB megosztásnév)' : 'Megosztás (NFS export útvonal)';
document.getElementById('ns-export').placeholder = smb ? 'pl. media' : 'pl. /volume1/media';
}
function netStorageAdd(){
var msg = document.getElementById('ns-add-msg');
var body = {
name: (document.getElementById('ns-name').value||'').trim(),
protocol: document.getElementById('ns-protocol').value,
server: (document.getElementById('ns-server').value||'').trim(),
export: (document.getElementById('ns-export').value||'').trim(),
mapped_uid: parseInt(document.getElementById('ns-uid').value||'1000',10),
mapped_gid: parseInt(document.getElementById('ns-uid').value||'1000',10),
username: (document.getElementById('ns-username').value||''),
password: (document.getElementById('ns-password').value||'')
};
msg.textContent = 'Csatlakoztatás folyamatban…';
fetch('/api/storage/netstorage/add',{method:'POST',headers:Object.assign({'Content-Type':'application/json'},csrfHeaders()),body:JSON.stringify(body)})
.then(function(r){return r.json();}).then(function(d){
if(d.ok){ msg.textContent='Sikeres ✓'; setTimeout(function(){location.reload();},900); }
else { msg.textContent='Hiba: '+(d.error||'ismeretlen'); }
}).catch(function(e){ msg.textContent='Hiba: '+e; });
}
function netStorageRemove(name,label){
if(!confirm('Biztosan eltávolítja a(z) '+label+' hálózati tárhelyet?\n\nA megosztás leválasztásra kerül; a NAS-on lévő adatok érintetlenek maradnak.')) return;
fetch('/api/storage/netstorage/remove',{method:'POST',headers:Object.assign({'Content-Type':'application/json'},csrfHeaders()),body:JSON.stringify({name:name})})
.then(function(r){return r.json();}).then(function(d){
if(d.ok){ location.reload(); } else { alert('Hiba: '+(d.error||'ismeretlen')); }
}).catch(function(e){ alert('Hiba: '+e); });
}
</script>
<div id="migrate-progress" style="display:none;margin-top:1rem;padding:1rem;border:1px solid var(--accent);border-radius:6px;background:rgba(0,136,204,0.06)">
<strong>Adatok áthelyezése</strong>
<div id="migrate-progress-body" style="margin-top:.5rem"></div>
@@ -39,6 +39,7 @@
<span class="stack-state-badge state-{{stateColor .State}}">{{stateLabel .State}}</span>
{{if .Orphaned}}<span class="badge badge-orphaned">Elavult</span>{{end}}
{{$ms := index $.MissingStorage .Name}}{{if $ms}}<span class="badge badge-missing-storage" title="Az alkalmazás adattárolója nem elérhető. Csatlakoztasd újra a meghajtót, vagy helyezd át az adatokat.">⚠ Hiányzó tárhely: {{$ms}}</span>{{end}}
{{$nw := index $.NetworkWarnings .Name}}{{if $nw}}<span class="badge badge-warn" title="A hálózati tárhely (NAS) jelenleg nem érhető el. Az alkalmazás fut; az adatok elérése a NAS visszatértével helyreáll.">⚠ Hálózati tárhely nem elérhető: {{$nw}}</span>{{end}}
</div>
{{if .Meta.Description}}
@@ -3146,6 +3146,7 @@ a.stat-card:hover {
/* badges missing from the global sheet */
.badge-ok { background: rgba(35, 134, 54, 0.18); color: #3fb950; }
.badge-neutral { background: rgba(139, 148, 158, 0.18); color: #8b949e; }
.badge-lock { background: rgba(210, 153, 34, 0.18); color: var(--yellow); }
.badge-muted { background: rgba(110, 118, 129, 0.18); color: var(--text-muted); }
.badge-info { background: rgba(0, 136, 204, 0.18); color: var(--accent-light); }