v0.10.0: Phase B — Storage Management UI Polish & Health Severity Fix
- Health severity fix: mount-point check downgraded from issue (FAIL) to warning (WARN) - All storage health messages translated to Hungarian - Success flash messages for all storage operations - Edit storage path labels (inline edit UI + backend) - App details per storage path on settings page (expandable list with names + sizes) - Storage badge on stacks page showing which storage each app uses - Deploy dropdown with free space display and low-space warning (<20%) - Filesystem & disk info on settings page (ext4/btrfs, device, model via findmnt) - Backup page storage context with per-app storage label badges Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -254,9 +254,10 @@
|
||||
<span class="app-backup-name">{{.DisplayName}}</span>
|
||||
</div>
|
||||
{{end}}
|
||||
{{if .HasHDDData}}
|
||||
<span class="app-backup-size mono">{{.HDDSizeHuman}} (HDD)</span>
|
||||
{{end}}
|
||||
<div style="display:flex;align-items:center;gap:.5rem">
|
||||
{{if .StorageLabel}}<span class="meta-badge meta-badge-storage">{{.StorageLabel}}</span>{{end}}
|
||||
{{if .HasHDDData}}<span class="app-backup-size mono">{{.HDDSizeHuman}}</span>{{end}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="app-backup-details">
|
||||
{{range .HDDPaths}}
|
||||
|
||||
@@ -117,11 +117,18 @@
|
||||
{{else if eq .Type "path"}}
|
||||
{{if $.StoragePaths}}
|
||||
<select id="field-{{.EnvVar}}" name="{{.EnvVar}}" class="form-control"
|
||||
{{if $.AlreadyDeployed}}disabled{{end}}>
|
||||
{{if $.AlreadyDeployed}}disabled{{end}}
|
||||
onchange="checkStorageSpace(this)">
|
||||
{{range $.StoragePaths}}
|
||||
<option value="{{.Path}}">{{.Label}} ({{.Path}})</option>
|
||||
<option value="{{.Path}}" data-free-percent="{{printf "%.0f" .FreePercent}}"
|
||||
{{if .IsDefault}}selected{{end}}>
|
||||
{{.Label}} — {{.FreeHuman}} szabad{{if .IsDefault}} ★{{end}}
|
||||
</option>
|
||||
{{end}}
|
||||
</select>
|
||||
<div id="storage-space-warn" class="form-hint" style="color:var(--yellow);display:none">
|
||||
⚠️ A kiválasztott tárhely majdnem megtelt.
|
||||
</div>
|
||||
{{else}}
|
||||
<input type="text" id="field-{{.EnvVar}}" name="{{.EnvVar}}"
|
||||
class="form-control" value="{{.Default}}"
|
||||
@@ -177,6 +184,19 @@
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function checkStorageSpace(sel) {
|
||||
var opt = sel.options[sel.selectedIndex];
|
||||
var warn = document.getElementById('storage-space-warn');
|
||||
if (!warn) return;
|
||||
var freePct = parseFloat(opt.getAttribute('data-free-percent') || '100');
|
||||
warn.style.display = freePct < 20 ? 'block' : 'none';
|
||||
}
|
||||
// Check on page load
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var sel = document.querySelector('select[onchange="checkStorageSpace(this)"]');
|
||||
if (sel) checkStorageSpace(sel);
|
||||
});
|
||||
|
||||
function generatePassword(fieldId) {
|
||||
const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
||||
let pass = '';
|
||||
|
||||
@@ -69,6 +69,7 @@
|
||||
<p class="settings-card-desc">Külső meghajtók kezelése alkalmazásadatok tárolásához.</p>
|
||||
|
||||
{{if .StorageError}}<div class="alert alert-error">{{.StorageError}}</div>{{end}}
|
||||
{{if .StorageSuccess}}<div class="alert alert-info">{{.StorageSuccess}}</div>{{end}}
|
||||
|
||||
{{if .StoragePaths}}
|
||||
<div class="storage-paths-list">
|
||||
@@ -76,7 +77,10 @@
|
||||
<div class="storage-path-item">
|
||||
<div class="storage-path-header">
|
||||
<div class="storage-path-info">
|
||||
<span class="storage-path-label">{{.Label}}</span>
|
||||
<div class="storage-path-label-wrap" id="label-wrap-{{.Path}}">
|
||||
<span class="storage-path-label" id="label-display-{{.Path}}">{{.Label}}</span>
|
||||
<button class="btn btn-xs btn-ghost" onclick="editStorageLabel('{{.Path}}', '{{.Label}}')" title="Átnevezés">✏️</button>
|
||||
</div>
|
||||
<span class="storage-path-path mono">{{.Path}}</span>
|
||||
</div>
|
||||
<div class="storage-path-badges">
|
||||
@@ -97,8 +101,29 @@
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{if .FSInfo}}
|
||||
<div class="storage-path-fsinfo mono form-hint">
|
||||
{{.FSInfo.FSType}} · {{.FSInfo.Device}}{{if .FSInfo.Model}} · {{.FSInfo.Model}}{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
<div class="storage-path-meta">
|
||||
<span class="form-hint">{{.AppCount}} alkalmazás használja</span>
|
||||
{{if .AppDetails}}
|
||||
<details class="storage-app-details">
|
||||
<summary class="form-hint" style="cursor:pointer">
|
||||
{{.AppCount}} alkalmazás használja
|
||||
</summary>
|
||||
<div class="storage-app-list">
|
||||
{{range .AppDetails}}
|
||||
<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}}
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</details>
|
||||
{{else}}
|
||||
<span class="form-hint">Nincs alkalmazás ezen a tárolón</span>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="storage-path-actions">
|
||||
@@ -246,5 +271,24 @@
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function editStorageLabel(path, currentLabel) {
|
||||
var wrap = document.getElementById('label-wrap-' + path);
|
||||
if (!wrap) return;
|
||||
wrap.innerHTML = '<form method="POST" action="/settings/storage/label" style="display:inline-flex;gap:.5rem;align-items:center">' +
|
||||
'<input type="hidden" name="storage_path" value="' + path + '">' +
|
||||
'<input type="text" name="storage_label" class="form-control" value="' + currentLabel.replace(/"/g, '"') + '" style="width:200px;padding:.3rem .5rem;font-size:.9rem" maxlength="50">' +
|
||||
'<button type="submit" class="btn btn-xs btn-primary">OK</button>' +
|
||||
'<button type="button" class="btn btn-xs btn-outline" onclick="cancelEditLabel(\'' + path + '\', \'' + currentLabel.replace(/'/g, "\\'") + '\')">✕</button>' +
|
||||
'</form>';
|
||||
wrap.querySelector('input[name=storage_label]').focus();
|
||||
}
|
||||
function cancelEditLabel(path, label) {
|
||||
var wrap = document.getElementById('label-wrap-' + path);
|
||||
if (!wrap) return;
|
||||
wrap.innerHTML = '<span class="storage-path-label" id="label-display-' + path + '">' + label + '</span>' +
|
||||
' <button class="btn btn-xs btn-ghost" onclick="editStorageLabel(\'' + path + '\', \'' + label.replace(/'/g, "\\'") + '\')" title="Átnevezés">✏️</button>';
|
||||
}
|
||||
</script>
|
||||
{{template "layout_end" .}}
|
||||
{{end}}
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
{{if .Meta.Resources.MemRequest}}<span class="meta-badge">~{{.Meta.Resources.MemRequest}}</span>{{end}}
|
||||
{{if .Meta.Resources.PiCompatible}}<span class="meta-badge meta-badge-ok">Pi kompatibilis</span>{{end}}
|
||||
{{if .Meta.Resources.NeedsHDD}}<span class="meta-badge">HDD szükséges</span>{{end}}
|
||||
{{if and .Deployed (index $.StorageLabels .Name)}}<span class="meta-badge meta-badge-storage" title="Adattároló: {{index $.StorageLabels .Name}}">💾 {{index $.StorageLabels .Name}}</span>{{end}}
|
||||
</div>
|
||||
|
||||
{{if .Containers}}
|
||||
|
||||
@@ -1147,6 +1147,10 @@ a.stat-card:hover {
|
||||
}
|
||||
.config-save-ok { color: var(--green); }
|
||||
.config-save-err { color: var(--red); }
|
||||
.meta-badge-storage {
|
||||
background: rgba(0, 136, 204, 0.12);
|
||||
color: var(--accent-light);
|
||||
}
|
||||
.meta-badge-warn {
|
||||
background: rgba(255, 152, 0, 0.1) !important;
|
||||
color: var(--orange) !important;
|
||||
@@ -1563,6 +1567,11 @@ a.stat-card:hover {
|
||||
color: var(--orange);
|
||||
border-color: rgba(219, 109, 40, 0.3);
|
||||
}
|
||||
.monitoring-banner-warn {
|
||||
background: rgba(255, 193, 7, 0.15);
|
||||
border-left: 4px solid var(--yellow);
|
||||
color: var(--yellow);
|
||||
}
|
||||
.ping-status-ok { color: var(--green); }
|
||||
.ping-status-warn { color: var(--yellow); }
|
||||
.ping-schedule {
|
||||
@@ -2021,6 +2030,10 @@ a.stat-card:hover {
|
||||
.storage-path-disk {
|
||||
margin-bottom: .5rem;
|
||||
}
|
||||
.storage-path-fsinfo {
|
||||
font-size: .75rem;
|
||||
margin-bottom: .35rem;
|
||||
}
|
||||
.storage-path-meta {
|
||||
font-size: .8rem;
|
||||
color: var(--text-muted);
|
||||
@@ -2036,6 +2049,15 @@ a.stat-card:hover {
|
||||
font-size: .75rem;
|
||||
border-radius: 6px;
|
||||
}
|
||||
.btn-ghost {
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--text-muted);
|
||||
padding: .1rem .3rem;
|
||||
}
|
||||
.btn-ghost:hover {
|
||||
color: var(--accent-light);
|
||||
}
|
||||
.btn-danger-outline {
|
||||
background: transparent;
|
||||
border: 1px solid rgba(218, 54, 51, 0.5);
|
||||
@@ -2045,6 +2067,34 @@ a.stat-card:hover {
|
||||
background: var(--red-bg);
|
||||
border-color: var(--red);
|
||||
}
|
||||
.storage-app-details summary {
|
||||
font-size: .85rem;
|
||||
}
|
||||
.storage-app-list {
|
||||
margin-top: .5rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: .25rem;
|
||||
}
|
||||
.storage-app-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: .2rem .5rem;
|
||||
font-size: .85rem;
|
||||
}
|
||||
.storage-app-link {
|
||||
color: var(--accent-light);
|
||||
text-decoration: none;
|
||||
}
|
||||
.storage-app-link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.storage-path-label-wrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: .25rem;
|
||||
}
|
||||
.storage-add-details {
|
||||
margin-top: .5rem;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user