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:
@@ -1,5 +1,18 @@
|
|||||||
## Changelog
|
## 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)
|
### What was just completed (2026-02-18 session 47)
|
||||||
- **v0.13.0 — UI Polish Fixes (8 independent fixes):**
|
- **v0.13.0 — UI Polish Fixes (8 independent fixes):**
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ type Alert struct {
|
|||||||
Link string // optional link to relevant page
|
Link string // optional link to relevant page
|
||||||
LinkText string // link display text
|
LinkText string // link display text
|
||||||
PageOnly []string // if non-empty, only show on these pages (e.g., ["dashboard", "monitoring"])
|
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.
|
// 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",
|
Link: "/monitoring",
|
||||||
LinkText: "Rendszermonitor",
|
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ó") {
|
if strings.Contains(w, "meghajtón") || strings.Contains(w, "adattároló") || strings.Contains(w, "meghajtó") {
|
||||||
alert.ID = "disk-not-separate"
|
alert.ID = "disk-not-separate"
|
||||||
alert.PageOnly = []string{"dashboard", "monitoring"}
|
alert.PageOnly = []string{"dashboard", "monitoring"}
|
||||||
|
alert.Inline = true
|
||||||
}
|
}
|
||||||
alerts = append(alerts, alert)
|
alerts = append(alerts, alert)
|
||||||
}
|
}
|
||||||
@@ -143,6 +145,30 @@ func (am *AlertManager) GetAlerts(excludeIDs ...string) []Alert {
|
|||||||
return result
|
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.
|
// countMissingPings counts how many ping UUIDs are not configured.
|
||||||
func countMissingPings(cfg *config.Config) int {
|
func countMissingPings(cfg *config.Config) int {
|
||||||
count := 0
|
count := 0
|
||||||
|
|||||||
@@ -124,6 +124,10 @@ func (s *Server) dashboardHandler(w http.ResponseWriter, _ *http.Request) {
|
|||||||
data["CrossDriveFailed"] = crossDriveFailed
|
data["CrossDriveFailed"] = crossDriveFailed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if s.alertManager != nil {
|
||||||
|
data["DiskWarnings"] = s.alertManager.GetInlineAlerts("dashboard")
|
||||||
|
}
|
||||||
|
|
||||||
s.render(w, "dashboard", data)
|
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
|
// On monitoring page, exclude the "pings-missing" alert since the detailed table is visible
|
||||||
if s.alertManager != nil {
|
if s.alertManager != nil {
|
||||||
data["Alerts"] = s.alertManager.GetAlerts("pings-missing")
|
data["Alerts"] = s.alertManager.GetAlerts("pings-missing")
|
||||||
|
data["DiskWarnings"] = s.alertManager.GetInlineAlerts("monitoring")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ping status section
|
// Ping status section
|
||||||
|
|||||||
@@ -356,9 +356,9 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td class="mono">{{shortID .SnapshotID}}</td>
|
<td class="mono">{{shortID .SnapshotID}}</td>
|
||||||
<td class="mono">{{fmtTime .Time}}</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}}+{{.DataAdded}}{{else}}0{{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}}{{.FilesNew}}{{else}}0{{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}}{{.FilesChanged}}{{else}}0{{end}}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{{end}}
|
{{end}}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
@@ -77,6 +77,17 @@
|
|||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</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>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
|
|||||||
@@ -260,22 +260,17 @@
|
|||||||
{{range .AutoFields}}
|
{{range .AutoFields}}
|
||||||
{{$val := index $autoValues .EnvVar}}
|
{{$val := index $autoValues .EnvVar}}
|
||||||
<div class="form-group form-group-auto">
|
<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 and $isDeployed $val}}
|
||||||
{{if eq .Type "secret"}}
|
{{if eq .Type "secret"}}
|
||||||
<div class="input-with-button">
|
<div class="input-with-button">
|
||||||
<input type="password" id="auto-field-{{.EnvVar}}" class="form-control" value="{{$val}}" readonly>
|
<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="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>
|
</div>
|
||||||
{{else}}
|
{{else}}
|
||||||
<div class="input-with-button">
|
<input type="text" id="auto-field-{{.EnvVar}}" class="form-control" value="{{$val}}" readonly>
|
||||||
<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>
|
|
||||||
{{end}}
|
{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
<span class="auto-generated-badge">✓ Automatikusan generálva</span>
|
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
@@ -461,16 +456,6 @@ function toggleAutoField(fieldId, btn) {
|
|||||||
el.type = el.type === 'password' ? 'text' : 'password';
|
el.type = el.type === 'password' ? 'text' : 'password';
|
||||||
btn.textContent = el.type === 'password' ? 'Megjelenítés' : 'Elrejtés';
|
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) {
|
function generatePassword(fieldId) {
|
||||||
const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
||||||
let pass = '';
|
let pass = '';
|
||||||
|
|||||||
@@ -31,7 +31,7 @@
|
|||||||
{{if .Alerts}}
|
{{if .Alerts}}
|
||||||
<div class="alerts-container">
|
<div class="alerts-container">
|
||||||
{{range .Alerts}}
|
{{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}}">
|
<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-icon">{{if eq .Level "error"}}🔴{{else if eq .Level "warning"}}🟡{{else}}ℹ️{{end}}</span>
|
||||||
<span class="alert-message">{{.Message}}</span>
|
<span class="alert-message">{{.Message}}</span>
|
||||||
|
|||||||
@@ -63,6 +63,17 @@
|
|||||||
{{end}}
|
{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</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>
|
</div>
|
||||||
|
|
||||||
<!-- Section 2: Remote Monitoring Status -->
|
<!-- Section 2: Remote Monitoring Status -->
|
||||||
|
|||||||
@@ -518,16 +518,14 @@ h3 {
|
|||||||
.form-group { margin-bottom: 1rem; }
|
.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 label { display: block; font-size: .85rem; font-weight: 500; margin-bottom: .4rem; color: var(--text-primary); }
|
||||||
.form-group-auto {
|
.form-group-auto {
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
padding: .5rem .75rem;
|
padding: .5rem .75rem;
|
||||||
background: var(--bg-secondary);
|
background: var(--bg-secondary);
|
||||||
border: 1px solid var(--border-color);
|
border: 1px solid var(--border-color);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
}
|
}
|
||||||
.form-group-auto label { margin: 0; }
|
.form-group-auto label { display: flex; align-items: center; gap: .5rem; margin-bottom: .4rem; }
|
||||||
.auto-generated-badge { color: var(--green); font-size: .8rem; font-weight: 500; }
|
.form-group-auto .form-control[readonly] { background: var(--bg-primary); }
|
||||||
|
.auto-generated-badge { color: var(--green); font-size: .75rem; font-weight: normal; }
|
||||||
.checkbox-group {
|
.checkbox-group {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -695,6 +693,37 @@ select.form-control option { background: var(--bg-secondary); color: var(--text-
|
|||||||
.alert-message { flex: 1; }
|
.alert-message { flex: 1; }
|
||||||
.alert-link { margin-left: auto; white-space: nowrap; }
|
.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 on deploy page */
|
||||||
.memory-summary {
|
.memory-summary {
|
||||||
background: var(--bg-card);
|
background: var(--bg-card);
|
||||||
@@ -1481,11 +1510,7 @@ a.stat-card:hover {
|
|||||||
letter-spacing: 0;
|
letter-spacing: 0;
|
||||||
margin-left: .25rem;
|
margin-left: .25rem;
|
||||||
}
|
}
|
||||||
.col-na {
|
|
||||||
color: var(--text-muted);
|
|
||||||
font-style: italic;
|
|
||||||
cursor: help;
|
|
||||||
}
|
|
||||||
|
|
||||||
.snapshot-footer {
|
.snapshot-footer {
|
||||||
padding: .75rem .75rem 0;
|
padding: .75rem .75rem 0;
|
||||||
@@ -2343,8 +2368,8 @@ a.stat-card:hover {
|
|||||||
|
|
||||||
/* Cross-drive backup card on deploy page */
|
/* Cross-drive backup card on deploy page */
|
||||||
.deploy-cross-drive {
|
.deploy-cross-drive {
|
||||||
background: var(--card-bg);
|
background: var(--bg-secondary);
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border-color);
|
||||||
border-radius: var(--radius);
|
border-radius: var(--radius);
|
||||||
padding: 1.5rem;
|
padding: 1.5rem;
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
|
|||||||
Reference in New Issue
Block a user