diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1865324..433c641 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,18 @@
## Changelog
+### What was just completed (2026-02-18 session 48)
+- **v0.13.1 — UI Polish Fixes Round 2 (4 fixes):**
+
+ **Fix 1:** Deploy page "Biztonsági mentés" section now has proper card border. Root cause: `.deploy-cross-drive` used undefined CSS variables `--card-bg` and `--border` (only `--bg-secondary` and `--border-color` exist). Fixed by using correct vars (`style.css`).
+
+ **Fix 2:** Auto-generated env values section cleaned up (`deploy.html`, `style.css`). Badge moved inline with label. "Másolás" buttons removed (native select+copy sufficient). Secret fields keep show/hide toggle. Non-secret fields now plain readonly input without button wrapper. Removed `copyAutoField()` JS. CSS updated: `.form-group-auto` now block layout (was flex row), label uses `display: flex; gap: .5rem`, badge downsized to `0.75rem / normal weight`, readonly inputs get muted background.
+
+ **Fix 3:** Snapshot table n/a → 0 (`backups.html`). Replaced `n/a` with plain `0` in all three stats columns. Removed `.col-na` CSS class (no longer used).
+
+ **Fix 4:** Disk warnings moved from top banner to inline under storage bars (`alerts.go`, `layout.html`, `handlers.go`, `dashboard.html`, `monitoring.html`, `style.css`). Added `Inline bool` field to `Alert` struct. Disk-related warnings set `Inline: true`. Layout banner skips inline alerts. New `GetInlineAlerts(page)` method on `AlertManager`. Dashboard and monitoring handlers pass `DiskWarnings`. Inline warning block rendered below storage bars. New `.inline-warning*` CSS classes (compact, subtle, colored).
+
+ **Files modified (8):** `alerts.go`, `handlers.go`, `templates/style.css`, `templates/dashboard.html`, `templates/backups.html`, `templates/deploy.html`, `templates/monitoring.html`, `templates/layout.html`
+
### What was just completed (2026-02-18 session 47)
- **v0.13.0 — UI Polish Fixes (8 independent fixes):**
diff --git a/controller/internal/web/alerts.go b/controller/internal/web/alerts.go
index d28291e..48b632e 100644
--- a/controller/internal/web/alerts.go
+++ b/controller/internal/web/alerts.go
@@ -19,6 +19,7 @@ type Alert struct {
Link string // optional link to relevant page
LinkText string // link display text
PageOnly []string // if non-empty, only show on these pages (e.g., ["dashboard", "monitoring"])
+ Inline bool // if true, rendered by page template inline, not in layout banner
}
// AlertManager generates and stores dashboard alerts from health check results.
@@ -62,10 +63,11 @@ func (am *AlertManager) Refresh(report *monitor.HealthReport, cfg *config.Config
Link: "/monitoring",
LinkText: "Rendszermonitor",
}
- // Disk-related warnings only relevant on dashboard and monitoring pages
+ // Disk-related warnings rendered inline under storage bars, not in top banner
if strings.Contains(w, "meghajtón") || strings.Contains(w, "adattároló") || strings.Contains(w, "meghajtó") {
alert.ID = "disk-not-separate"
alert.PageOnly = []string{"dashboard", "monitoring"}
+ alert.Inline = true
}
alerts = append(alerts, alert)
}
@@ -143,6 +145,30 @@ func (am *AlertManager) GetAlerts(excludeIDs ...string) []Alert {
return result
}
+// GetInlineAlerts returns alerts marked as Inline for a specific page.
+func (am *AlertManager) GetInlineAlerts(page string) []Alert {
+ am.mu.RLock()
+ defer am.mu.RUnlock()
+
+ var result []Alert
+ for _, a := range am.alerts {
+ if !a.Inline {
+ continue
+ }
+ if len(a.PageOnly) == 0 {
+ result = append(result, a)
+ continue
+ }
+ for _, p := range a.PageOnly {
+ if p == page {
+ result = append(result, a)
+ break
+ }
+ }
+ }
+ return result
+}
+
// countMissingPings counts how many ping UUIDs are not configured.
func countMissingPings(cfg *config.Config) int {
count := 0
diff --git a/controller/internal/web/handlers.go b/controller/internal/web/handlers.go
index 58149ef..54a8707 100644
--- a/controller/internal/web/handlers.go
+++ b/controller/internal/web/handlers.go
@@ -124,6 +124,10 @@ func (s *Server) dashboardHandler(w http.ResponseWriter, _ *http.Request) {
data["CrossDriveFailed"] = crossDriveFailed
}
+ if s.alertManager != nil {
+ data["DiskWarnings"] = s.alertManager.GetInlineAlerts("dashboard")
+ }
+
s.render(w, "dashboard", data)
}
@@ -363,6 +367,7 @@ func (s *Server) monitoringHandler(w http.ResponseWriter, _ *http.Request) {
// On monitoring page, exclude the "pings-missing" alert since the detailed table is visible
if s.alertManager != nil {
data["Alerts"] = s.alertManager.GetAlerts("pings-missing")
+ data["DiskWarnings"] = s.alertManager.GetInlineAlerts("monitoring")
}
// Ping status section
diff --git a/controller/internal/web/templates/backups.html b/controller/internal/web/templates/backups.html
index 78a58c0..0bb346f 100644
--- a/controller/internal/web/templates/backups.html
+++ b/controller/internal/web/templates/backups.html
@@ -356,9 +356,9 @@
| {{shortID .SnapshotID}} |
{{fmtTime .Time}} |
- {{if .HasStats}}+{{.DataAdded}}{{else}}n/a{{end}} |
- {{if .HasStats}}{{.FilesNew}}{{else}}n/a{{end}} |
- {{if .HasStats}}{{.FilesChanged}}{{else}}n/a{{end}} |
+ {{if .HasStats}}+{{.DataAdded}}{{else}}0{{end}} |
+ {{if .HasStats}}{{.FilesNew}}{{else}}0{{end}} |
+ {{if .HasStats}}{{.FilesChanged}}{{else}}0{{end}} |
{{end}}
diff --git a/controller/internal/web/templates/dashboard.html b/controller/internal/web/templates/dashboard.html
index 9b40b10..08bc5e9 100644
--- a/controller/internal/web/templates/dashboard.html
+++ b/controller/internal/web/templates/dashboard.html
@@ -77,6 +77,17 @@
{{end}}
+ {{if .DiskWarnings}}
+
+ {{range .DiskWarnings}}
+
+ {{end}}
+
+ {{end}}
{{end}}
diff --git a/controller/internal/web/templates/deploy.html b/controller/internal/web/templates/deploy.html
index ab57262..468faf8 100644
--- a/controller/internal/web/templates/deploy.html
+++ b/controller/internal/web/templates/deploy.html
@@ -260,22 +260,17 @@
{{range .AutoFields}}
{{$val := index $autoValues .EnvVar}}
{{end}}
@@ -461,16 +456,6 @@ function toggleAutoField(fieldId, btn) {
el.type = el.type === 'password' ? 'text' : 'password';
btn.textContent = el.type === 'password' ? 'Megjelenítés' : 'Elrejtés';
}
-function copyAutoField(fieldId, btn) {
- var el = document.getElementById(fieldId);
- if (!el) return;
- navigator.clipboard.writeText(el.value).then(function() {
- var orig = btn.textContent;
- btn.textContent = 'Másolva!';
- setTimeout(function() { btn.textContent = orig; }, 2000);
- });
-}
-
function generatePassword(fieldId) {
const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
let pass = '';
diff --git a/controller/internal/web/templates/layout.html b/controller/internal/web/templates/layout.html
index 0af9623..06aadcc 100644
--- a/controller/internal/web/templates/layout.html
+++ b/controller/internal/web/templates/layout.html
@@ -31,7 +31,7 @@
{{if .Alerts}}
{{range .Alerts}}
- {{if or (not .PageOnly) (pageMatch .PageOnly $.Page)}}
+ {{if and (not .Inline) (or (not .PageOnly) (pageMatch .PageOnly $.Page))}}
{{if eq .Level "error"}}🔴{{else if eq .Level "warning"}}🟡{{else}}ℹ️{{end}}
{{.Message}}
diff --git a/controller/internal/web/templates/monitoring.html b/controller/internal/web/templates/monitoring.html
index 62c7421..13f9869 100644
--- a/controller/internal/web/templates/monitoring.html
+++ b/controller/internal/web/templates/monitoring.html
@@ -63,6 +63,17 @@
{{end}}
{{end}}
+ {{if .DiskWarnings}}
+
+ {{range .DiskWarnings}}
+
+ {{end}}
+
+ {{end}}
diff --git a/controller/internal/web/templates/style.css b/controller/internal/web/templates/style.css
index 1a8251a..16bc4f9 100644
--- a/controller/internal/web/templates/style.css
+++ b/controller/internal/web/templates/style.css
@@ -518,16 +518,14 @@ h3 {
.form-group { margin-bottom: 1rem; }
.form-group label { display: block; font-size: .85rem; font-weight: 500; margin-bottom: .4rem; color: var(--text-primary); }
.form-group-auto {
- display: flex;
- justify-content: space-between;
- align-items: center;
padding: .5rem .75rem;
background: var(--bg-secondary);
border: 1px solid var(--border-color);
border-radius: 8px;
}
-.form-group-auto label { margin: 0; }
-.auto-generated-badge { color: var(--green); font-size: .8rem; font-weight: 500; }
+.form-group-auto label { display: flex; align-items: center; gap: .5rem; margin-bottom: .4rem; }
+.form-group-auto .form-control[readonly] { background: var(--bg-primary); }
+.auto-generated-badge { color: var(--green); font-size: .75rem; font-weight: normal; }
.checkbox-group {
display: flex;
flex-direction: column;
@@ -695,6 +693,37 @@ select.form-control option { background: var(--bg-secondary); color: var(--text-
.alert-message { flex: 1; }
.alert-link { margin-left: auto; white-space: nowrap; }
+/* Inline storage warnings (under storage bars on dashboard and monitoring) */
+.inline-warnings { margin-top: 0.75rem; }
+.inline-warning {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ padding: 0.4rem 0.75rem;
+ margin-top: 0.25rem;
+ font-size: 0.8rem;
+ border-radius: 6px;
+}
+.inline-warning-warning {
+ color: var(--yellow);
+ background: rgba(250, 204, 21, 0.06);
+ border: 1px solid rgba(250, 204, 21, 0.15);
+}
+.inline-warning-error {
+ color: var(--red);
+ background: rgba(218, 54, 51, 0.06);
+ border: 1px solid rgba(218, 54, 51, 0.15);
+}
+.inline-warning-dot { font-size: 0.6rem; flex-shrink: 0; }
+.inline-warning-text { flex: 1; }
+.inline-warning-link {
+ color: inherit;
+ opacity: 0.8;
+ white-space: nowrap;
+ text-decoration: none;
+}
+.inline-warning-link:hover { opacity: 1; text-decoration: underline; }
+
/* Memory summary on deploy page */
.memory-summary {
background: var(--bg-card);
@@ -1481,11 +1510,7 @@ a.stat-card:hover {
letter-spacing: 0;
margin-left: .25rem;
}
-.col-na {
- color: var(--text-muted);
- font-style: italic;
- cursor: help;
-}
+
.snapshot-footer {
padding: .75rem .75rem 0;
@@ -2343,8 +2368,8 @@ a.stat-card:hover {
/* Cross-drive backup card on deploy page */
.deploy-cross-drive {
- background: var(--card-bg);
- border: 1px solid var(--border);
+ background: var(--bg-secondary);
+ border: 1px solid var(--border-color);
border-radius: var(--radius);
padding: 1.5rem;
margin-top: 1rem;