v0.13.1: UI polish fixes round 2 (4 fixes)

- Fix 1: deploy-cross-drive card uses correct CSS vars (--bg-secondary, --border-color)
- Fix 2: Auto-generated env values — badge inline with label, remove copy buttons, muted readonly inputs
- Fix 3: Snapshot table shows 0 instead of n/a; remove unused .col-na CSS
- Fix 4: Disk warnings moved inline under storage bars (Inline alert field, GetInlineAlerts, inline-warning CSS)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-18 16:09:56 +01:00
parent e247fdf54c
commit 05f6095e6b
9 changed files with 110 additions and 34 deletions
+13
View File
@@ -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 `<span class="col-na" title="...">n/a</span>` 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):**
+27 -1
View File
@@ -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
+5
View File
@@ -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
@@ -356,9 +356,9 @@
<tr>
<td class="mono">{{shortID .SnapshotID}}</td>
<td class="mono">{{fmtTime .Time}}</td>
<td class="mono">{{if .HasStats}}+{{.DataAdded}}{{else}}<span class="col-na" title="A restic pillanatképek nem tartalmaznak méretadatot — csak az utolsó mentés adatai állnak rendelkezésre.">n/a</span>{{end}}</td>
<td class="mono">{{if .HasStats}}{{.FilesNew}}{{else}}<span class="col-na" title="A restic pillanatképek nem tartalmaznak fájlszámot — csak az utolsó mentés adatai állnak rendelkezésre.">n/a</span>{{end}}</td>
<td class="mono">{{if .HasStats}}{{.FilesChanged}}{{else}}<span class="col-na" title="A restic pillanatképek nem tartalmaznak fájlszámot — csak az utolsó mentés adatai állnak rendelkezésre.">n/a</span>{{end}}</td>
<td class="mono">{{if .HasStats}}+{{.DataAdded}}{{else}}0{{end}}</td>
<td class="mono">{{if .HasStats}}{{.FilesNew}}{{else}}0{{end}}</td>
<td class="mono">{{if .HasStats}}{{.FilesChanged}}{{else}}0{{end}}</td>
</tr>
{{end}}
</tbody>
@@ -77,6 +77,17 @@
</div>
{{end}}
</div>
{{if .DiskWarnings}}
<div class="inline-warnings">
{{range .DiskWarnings}}
<div class="inline-warning inline-warning-{{.Level}}">
<span class="inline-warning-dot"></span>
<span class="inline-warning-text">{{.Message}}</span>
{{if .Link}}<a href="{{.Link}}" class="inline-warning-link">{{.LinkText}} →</a>{{end}}
</div>
{{end}}
</div>
{{end}}
</div>
{{end}}
+2 -17
View File
@@ -260,22 +260,17 @@
{{range .AutoFields}}
{{$val := index $autoValues .EnvVar}}
<div class="form-group form-group-auto">
<label>{{.Label}}</label>
<label>{{.Label}} <span class="auto-generated-badge">✓ Automatikusan generálva</span></label>
{{if and $isDeployed $val}}
{{if eq .Type "secret"}}
<div class="input-with-button">
<input type="password" id="auto-field-{{.EnvVar}}" class="form-control" value="{{$val}}" readonly>
<button type="button" class="btn btn-sm btn-outline" onclick="toggleAutoField('auto-field-{{.EnvVar}}', this)">Megjelenítés</button>
<button type="button" class="btn btn-sm btn-outline" onclick="copyAutoField('auto-field-{{.EnvVar}}', this)">Másolás</button>
</div>
{{else}}
<div class="input-with-button">
<input type="text" id="auto-field-{{.EnvVar}}" class="form-control" value="{{$val}}" readonly>
<button type="button" class="btn btn-sm btn-outline" onclick="copyAutoField('auto-field-{{.EnvVar}}', this)">Másolás</button>
</div>
<input type="text" id="auto-field-{{.EnvVar}}" class="form-control" value="{{$val}}" readonly>
{{end}}
{{end}}
<span class="auto-generated-badge">✓ Automatikusan generálva</span>
</div>
{{end}}
</div>
@@ -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 = '';
@@ -31,7 +31,7 @@
{{if .Alerts}}
<div class="alerts-container">
{{range .Alerts}}
{{if or (not .PageOnly) (pageMatch .PageOnly $.Page)}}
{{if and (not .Inline) (or (not .PageOnly) (pageMatch .PageOnly $.Page))}}
<div class="alert-banner alert-banner-{{.Level}}">
<span class="alert-icon">{{if eq .Level "error"}}🔴{{else if eq .Level "warning"}}🟡{{else}}️{{end}}</span>
<span class="alert-message">{{.Message}}</span>
@@ -63,6 +63,17 @@
{{end}}
{{end}}
</div>
{{if .DiskWarnings}}
<div class="inline-warnings">
{{range .DiskWarnings}}
<div class="inline-warning inline-warning-{{.Level}}">
<span class="inline-warning-dot"></span>
<span class="inline-warning-text">{{.Message}}</span>
{{if .Link}}<a href="{{.Link}}" class="inline-warning-link">{{.LinkText}} →</a>{{end}}
</div>
{{end}}
</div>
{{end}}
</div>
<!-- Section 2: Remote Monitoring Status -->
+37 -12
View File
@@ -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;