v0.13.0: UI polish fixes (8 improvements)

- Fix 1: Dashboard backup card border (verified already correct)
- Fix 2: Show auto-generated env values on deploy page with copy/reveal
- Fix 3: Temperature value pill for better visibility on dashboard
- Fix 4: Rework dashboard backup section (remove manual trigger, add Tier 2 summary)
- Fix 5: Scope HDD warning banner to dashboard and monitoring pages only
- Fix 6: Move Tárhely section up in monitoring page
- Fix 7: Snapshot table clarity (HOZZÁADOTT header, n/a instead of -)
- Fix 8: Restructure Tároló section into tiered storage view

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-18 13:30:21 +01:00
parent 6bfdd88d10
commit 90826dec7a
10 changed files with 328 additions and 126 deletions
+21
View File
@@ -1,5 +1,26 @@
## Changelog ## Changelog
### What was just completed (2026-02-18 session 47)
- **v0.13.0 — UI Polish Fixes (8 independent fixes):**
**Fix 1:** backup-status-card border already correct (verified same styling as system-info-card).
**Fix 2:** Deploy page auto-generated fields now show actual values for deployed apps (`deploy.html`, `handlers.go`). Secrets show as password fields with show/hide toggle; domain/plain values show as readonly text with copy button. JS helpers `toggleAutoField()` / `copyAutoField()` added.
**Fix 3:** Temperature display made more prominent (`dashboard.html`, `style.css`). Dot enlarged to 11px; value wrapped in colored pill badge (`.temp-value-pill` / `.temp-pill-{green|yellow|red}`).
**Fix 4:** Dashboard backup card reworked (`dashboard.html`, `handlers.go`). Removed "Mentés most" button and `triggerBackup()` JS. Removed "Tároló méret" line. Added Tier 2 status line (configured/total apps) + warning row for failed cross-drive backups. Handler now computes `CrossDriveTotal`, `CrossDriveConfigured`, `CrossDriveFailed`.
**Fix 5:** HDD warning banner scoped to dashboard + monitoring pages only (`alerts.go`, `layout.html`, `funcmap.go`). Added `PageOnly []string` field to `Alert` struct. Disk-related warnings (keywords "meghajtón", "adattároló") get stable ID `"disk-not-separate"` + `PageOnly: ["dashboard", "monitoring"]`. `pageMatch()` template function added. Layout renders alerts conditionally.
**Fix 6:** Tárhely section moved up in Rendszermonitor — now appears right after "Rendszer áttekintés", before "Távoli monitoring" (`monitoring.html`).
**Fix 7:** Snapshot table improvements (`backups.html`, `style.css`). "MÉRET" renamed to "HOZZÁADOTT (új adat)". `` for unavailable data replaced with `n/a` (with tooltip explaining restic limitations). New `.col-subtitle` and `.col-na` CSS classes.
**Fix 8:** Tároló section restructured into tiers (`backups.html`, `handlers.go`, `style.css`). Tier 1 (restic local), Tier 2 (cross-drive, only shown if configured), DB dump directory + total size. Removed "Távoli másolat: Nincs beállítva" placeholder. Handler passes `DBDumpDir`, `DBDumpTotalBytes`, `Tier2Dests` (deduplicated). New `.repo-tier` / `.repo-tier-title` CSS.
**Files modified (9):** `alerts.go`, `funcmap.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 46) ### What was just completed (2026-02-18 session 46)
- **v0.12.9 — Tier 2 for All Apps + Status Dot Update:** - **v0.12.9 — Tier 2 for All Apps + Status Dot Update:**
+10 -2
View File
@@ -3,6 +3,7 @@ package web
import ( import (
"fmt" "fmt"
"log" "log"
"strings"
"sync" "sync"
"gitea.dooplex.hu/admin/felhom-controller/internal/backup" "gitea.dooplex.hu/admin/felhom-controller/internal/backup"
@@ -17,6 +18,7 @@ type Alert struct {
Message string // Hungarian display text Message string // Hungarian display text
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"])
} }
// AlertManager generates and stores dashboard alerts from health check results. // AlertManager generates and stores dashboard alerts from health check results.
@@ -53,13 +55,19 @@ func (am *AlertManager) Refresh(report *monitor.HealthReport, cfg *config.Config
// From health check warnings // From health check warnings
for _, w := range report.Warnings { for _, w := range report.Warnings {
alerts = append(alerts, Alert{ alert := Alert{
ID: "health-" + simpleHash(w), ID: "health-" + simpleHash(w),
Level: "warning", Level: "warning",
Message: w, Message: w,
Link: "/monitoring", Link: "/monitoring",
LinkText: "Rendszermonitor", LinkText: "Rendszermonitor",
}) }
// Disk-related warnings only relevant on dashboard and monitoring pages
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"}
}
alerts = append(alerts, alert)
} }
// Missing ping UUIDs // Missing ping UUIDs
+10
View File
@@ -305,5 +305,15 @@ func (s *Server) templateFuncMap() template.FuncMap {
} }
return id return id
}, },
// pageMatch returns true if currentPage is in the pages slice.
// Used to filter page-specific alerts in layout.html.
"pageMatch": func(pages []string, currentPage string) bool {
for _, p := range pages {
if p == currentPage {
return true
}
}
return false
},
} }
} }
+61
View File
@@ -100,6 +100,28 @@ func (s *Server) dashboardHandler(w http.ResponseWriter, _ *http.Request) {
data["BackupStatus"] = fullStatus.LastBackup data["BackupStatus"] = fullStatus.LastBackup
data["BackupRunning"] = fullStatus.Running data["BackupRunning"] = fullStatus.Running
data["BackupMaxAgeHours"] = s.cfg.Monitoring.Thresholds.BackupMaxAgeHours data["BackupMaxAgeHours"] = s.cfg.Monitoring.Thresholds.BackupMaxAgeHours
// Cross-drive summary for dashboard Tier 2 status line
crossConfigs := s.settings.GetAllCrossDriveConfigs()
crossDriveTotal := 0
crossDriveConfigured := 0
crossDriveFailed := 0
for _, st := range deployedStacks {
if st.Protected {
continue
}
crossDriveTotal++
cfg, hasCfg := crossConfigs[st.Name]
if hasCfg && cfg != nil && cfg.Enabled {
crossDriveConfigured++
if cfg.LastStatus == "error" {
crossDriveFailed++
}
}
}
data["CrossDriveTotal"] = crossDriveTotal
data["CrossDriveConfigured"] = crossDriveConfigured
data["CrossDriveFailed"] = crossDriveFailed
} }
s.render(w, "dashboard", data) s.render(w, "dashboard", data)
@@ -181,6 +203,16 @@ func (s *Server) deployHandler(w http.ResponseWriter, r *http.Request, name stri
data["AppPageURL"] = s.cfg.AppPageURL(meta.Slug) data["AppPageURL"] = s.cfg.AppPageURL(meta.Slug)
data["UserFields"] = meta.UserFacingFields() data["UserFields"] = meta.UserFacingFields()
data["AutoFields"] = meta.AutoGeneratedFields() data["AutoFields"] = meta.AutoGeneratedFields()
// Auto-generated field values for already-deployed apps
autoFieldValues := make(map[string]string)
if alreadyDeployed && appCfg != nil {
for _, f := range meta.AutoGeneratedFields() {
if val, ok := appCfg.Env[f.EnvVar]; ok {
autoFieldValues[f.EnvVar] = val
}
}
}
data["AutoFieldValues"] = autoFieldValues
// Storage paths with free space info for deploy dropdown // Storage paths with free space info for deploy dropdown
var deployPaths []DeployStoragePath var deployPaths []DeployStoragePath
for _, sp := range s.settings.GetSchedulableStoragePaths() { for _, sp := range s.settings.GetSchedulableStoragePaths() {
@@ -487,6 +519,35 @@ func (s *Server) backupsHandler(w http.ResponseWriter, r *http.Request) {
if pw, err := s.backupMgr.GetResticPassword(); err == nil { if pw, err := s.backupMgr.GetResticPassword(); err == nil {
data["ResticPassword"] = pw data["ResticPassword"] = pw
} }
// Tároló section: DB dump directory and total size
data["DBDumpDir"] = s.cfg.Paths.DBDumpDir
var dbDumpTotalBytes int64
for _, f := range fullStatus.DumpFiles {
dbDumpTotalBytes += f.Size
}
data["DBDumpTotalBytes"] = dbDumpTotalBytes
// Tároló section: deduplicated Tier 2 destination list
tier2DestMap := make(map[string]map[string]string)
for _, item := range fullStatus.CrossDriveSummary {
if item.DestPath == "" {
continue
}
if _, exists := tier2DestMap[item.DestPath]; !exists {
tier2DestMap[item.DestPath] = map[string]string{
"Path": item.DestPath,
"Label": item.DestLabel,
"Method": item.MethodLabel,
"SizeHuman": item.SizeHuman,
}
}
}
var tier2DestList []map[string]string
for _, d := range tier2DestMap {
tier2DestList = append(tier2DestList, d)
}
data["Tier2Dests"] = tier2DestList
} else { } else {
data["Backup"] = nil data["Backup"] = nil
} }
+49 -11
View File
@@ -346,7 +346,7 @@
<tr> <tr>
<th>Azonosító</th> <th>Azonosító</th>
<th>Időpont</th> <th>Időpont</th>
<th>Méret</th> <th>Hozzáadott <span class="col-subtitle">(új adat)</span></th>
<th>Új fájl</th> <th>Új fájl</th>
<th>Változott</th> <th>Változott</th>
</tr> </tr>
@@ -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}}{{end}}</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}}{{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}}{{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>
</tr> </tr>
{{end}} {{end}}
</tbody> </tbody>
@@ -376,10 +376,14 @@
<!-- Section 6: Repository --> <!-- Section 6: Repository -->
<div class="repo-card"> <div class="repo-card">
<h3>Tároló</h3> <h3>Tároló</h3>
<!-- Tier 1: Local restic backup -->
<div class="repo-tier">
<h4 class="repo-tier-title">1. mentés — Helyi mentés (restic)</h4>
<div class="repo-info-rows"> <div class="repo-info-rows">
<div class="repo-info-row"> <div class="repo-info-row">
<span class="repo-label">Helyszín:</span> <span class="repo-label">Helyszín:</span>
<span class="repo-value mono">{{.Backup.RepoPath}} (helyi)</span> <span class="repo-value mono">{{.Backup.RepoPath}} <span class="relative-time">(helyi SSD)</span></span>
</div> </div>
{{if .Backup.RepoStats}} {{if .Backup.RepoStats}}
<div class="repo-info-row"> <div class="repo-info-row">
@@ -421,18 +425,52 @@
{{end}} {{end}}
<div class="repo-paths"> <div class="repo-paths">
<span class="repo-label">Mentett útvonalak:</span> <span class="repo-label">Mentett útvonalak (forrás):</span>
<ul class="repo-path-list"> <ul class="repo-path-list">
{{range .Backup.BackupPaths}} {{range .Backup.BackupPaths}}
<li class="mono">{{.}}</li> <li class="mono">{{.}}</li>
{{end}} {{end}}
</ul> </ul>
</div> </div>
<div class="repo-remote"> </div>
<span class="repo-label">Távoli másolat:</span>
<div class="repo-remote-status"> <!-- Tier 2: Cross-drive backup destinations -->
<span class="relative-time">Nincs beállítva</span> {{if .Tier2Dests}}
<span class="relative-time">(B2/S3/SFTP támogatás hamarosan)</span> <div class="repo-tier">
<h4 class="repo-tier-title">2. mentés — Másodlagos másolat</h4>
{{range .Tier2Dests}}
<div class="repo-info-rows">
<div class="repo-info-row">
<span class="repo-label">Cél:</span>
<span class="repo-value mono">{{index . "Path"}}{{if index . "Label"}} <span class="relative-time">({{index . "Label"}})</span>{{end}}</span>
</div>
<div class="repo-info-row">
<span class="repo-label">Módszer:</span>
<span class="repo-value">{{index . "Method"}}</span>
</div>
{{if index . "SizeHuman"}}
<div class="repo-info-row">
<span class="repo-label">Méret:</span>
<span class="repo-value">{{index . "SizeHuman"}}</span>
</div>
{{end}}
</div>
{{end}}
</div>
{{end}}
<!-- DB dump storage -->
<div class="repo-tier">
<h4 class="repo-tier-title">Adatbázis mentések</h4>
<div class="repo-info-rows">
<div class="repo-info-row">
<span class="repo-label">Mappa:</span>
<span class="repo-value mono">{{.DBDumpDir}}</span>
</div>
<div class="repo-info-row">
<span class="repo-label">Fájlok:</span>
<span class="repo-value">{{if .Backup.DumpFiles}}{{len .Backup.DumpFiles}} dump fájl{{if gt .DBDumpTotalBytes 0}} — {{fmtBytes .DBDumpTotalBytes}}{{end}}{{else}}Nincs dump fájl{{end}}</span>
</div>
</div> </div>
</div> </div>
</div> </div>
@@ -49,7 +49,7 @@
<span class="system-info-label">Hőmérséklet</span> <span class="system-info-label">Hőmérséklet</span>
<span class="system-info-value"> <span class="system-info-value">
<span class="temp-dot temp-dot-{{tempColor .SystemInfo.TemperatureCelsius}}"></span> <span class="temp-dot temp-dot-{{tempColor .SystemInfo.TemperatureCelsius}}"></span>
{{fmtTemp .SystemInfo.TemperatureCelsius}} <span class="temp-value-pill temp-pill-{{tempColor .SystemInfo.TemperatureCelsius}}">{{fmtTemp .SystemInfo.TemperatureCelsius}}</span>
</span> </span>
</div> </div>
</div> </div>
@@ -106,17 +106,24 @@
<span class="backup-value">{{len .DBDumpStatus.Results}} mentve</span> <span class="backup-value">{{len .DBDumpStatus.Results}} mentve</span>
</div> </div>
{{end}} {{end}}
{{if .BackupStatus}}{{if .BackupStatus.RepoStats}} {{if gt .CrossDriveTotal 0}}
<div class="backup-info-row"> <div class="backup-info-row">
<span class="backup-label">Tároló méret:</span> <span class="backup-label">2. mentés:</span>
<span class="backup-value">{{.BackupStatus.RepoStats.TotalSize}} ({{.BackupStatus.RepoStats.SnapshotCount}} pillanatkép)</span> <span class="backup-value">
{{if eq .CrossDriveConfigured 0}}
<span style="color:var(--yellow)">⚠ Nincs beállítva</span>
{{else}}
<span class="{{if gt .CrossDriveFailed 0}}backup-status-fail{{else}}backup-status-ok{{end}}">{{.CrossDriveConfigured}} / {{.CrossDriveTotal}} alk.</span>
{{end}}
</span>
</div> </div>
{{end}}{{end}} {{end}}
<div class="backup-actions" style="margin-top: .75rem;"> {{if gt .CrossDriveFailed 0}}
<button class="btn btn-sm btn-primary" onclick="triggerBackup()" id="backup-btn"> <div class="backup-info-row">
{{if .BackupRunning}}Mentés folyamatban...{{else}}Mentés most{{end}} <span class="backup-label" style="color:var(--red)">Figyelmeztetés:</span>
</button> <span class="backup-value backup-status-fail">⚠ {{.CrossDriveFailed}} 2. mentés sikertelen</span>
</div> </div>
{{end}}
</div> </div>
{{end}} {{end}}
@@ -163,28 +170,6 @@
{{end}} {{end}}
</div> </div>
<script>
function triggerBackup() {
const btn = document.getElementById('backup-btn');
btn.disabled = true;
btn.textContent = 'Mentés indítása...';
fetch('/api/backup/run', { method: 'POST' })
.then(r => r.json())
.then(data => {
if (data.ok) {
btn.textContent = 'Mentés folyamatban...';
btn.classList.add('loading');
} else {
btn.textContent = data.error || 'Hiba';
btn.disabled = false;
}
})
.catch(() => {
btn.textContent = 'Hiba';
btn.disabled = false;
});
}
</script>
{{template "layout_end" .}} {{template "layout_end" .}}
{{end}} {{end}}
@@ -255,9 +255,26 @@
<div class="form-section"> <div class="form-section">
<h4>Automatikusan generált értékek</h4> <h4>Automatikusan generált értékek</h4>
<p class="form-section-desc">Ezek az értékek automatikusan létrejönnek a telepítéskor.</p> <p class="form-section-desc">Ezek az értékek automatikusan létrejönnek a telepítéskor.</p>
{{$autoValues := .AutoFieldValues}}
{{$isDeployed := .AlreadyDeployed}}
{{range .AutoFields}} {{range .AutoFields}}
{{$val := index $autoValues .EnvVar}}
<div class="form-group form-group-auto"> <div class="form-group form-group-auto">
<label>{{.Label}}</label> <label>{{.Label}}</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>
{{end}}
{{end}}
<span class="auto-generated-badge">✓ Automatikusan generálva</span> <span class="auto-generated-badge">✓ Automatikusan generálva</span>
</div> </div>
{{end}} {{end}}
@@ -438,6 +455,22 @@ document.addEventListener('DOMContentLoaded', function() {
if (sel) checkStorageSpace(sel); if (sel) checkStorageSpace(sel);
}); });
function toggleAutoField(fieldId, btn) {
var el = document.getElementById(fieldId);
if (!el) return;
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) { function generatePassword(fieldId) {
const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
let pass = ''; let pass = '';
@@ -31,12 +31,14 @@
{{if .Alerts}} {{if .Alerts}}
<div class="alerts-container"> <div class="alerts-container">
{{range .Alerts}} {{range .Alerts}}
{{if 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>
{{if .Link}}<a href="{{.Link}}" class="alert-link">{{.LinkText}} →</a>{{end}} {{if .Link}}<a href="{{.Link}}" class="alert-link">{{.LinkText}} →</a>{{end}}
</div> </div>
{{end}} {{end}}
{{end}}
</div> </div>
{{end}} {{end}}
{{end}} {{end}}
@@ -36,7 +36,36 @@
</div> </div>
</div> </div>
<!-- Section 1.5: Remote Monitoring Status --> <!-- Section 1.5: Storage (moved here for visibility) -->
<div class="monitor-card">
<h3>Tárhely</h3>
<div class="storage-bars">
{{with .SystemInfo}}
<div class="storage-item">
<div class="storage-header">
<span class="storage-label">SSD (/)</span>
<span class="storage-value">{{fmtGB .DiskUsedGB}} / {{fmtGB .DiskTotalGB}} ({{printf "%.0f" .DiskPercent}}%)</span>
</div>
<div class="system-bar">
<div class="system-bar-fill {{usageColor .DiskPercent | printf "system-bar-%s"}}" style="width:{{printf "%.1f" .DiskPercent}}%"></div>
</div>
</div>
{{if .HDDConfigured}}
<div class="storage-item">
<div class="storage-header">
<span class="storage-label">Külső HDD</span>
<span class="storage-value">{{fmtGB .HDDUsedGB}} / {{fmtGB .HDDTotalGB}} ({{printf "%.0f" .HDDPercent}}%)</span>
</div>
<div class="system-bar">
<div class="system-bar-fill {{usageColor .HDDPercent | printf "system-bar-%s"}}" style="width:{{printf "%.1f" .HDDPercent}}%"></div>
</div>
</div>
{{end}}
{{end}}
</div>
</div>
<!-- Section 2: Remote Monitoring Status -->
<div class="monitor-card"> <div class="monitor-card">
<h3>Távoli monitoring</h3> <h3>Távoli monitoring</h3>
{{if not .MonitoringEnabled}} {{if not .MonitoringEnabled}}
@@ -67,7 +96,7 @@
{{end}} {{end}}
</div> </div>
<!-- Section 2: System Metrics Charts --> <!-- Section 3: System Metrics Charts -->
<div class="monitor-card"> <div class="monitor-card">
<div class="monitor-card-header"> <div class="monitor-card-header">
<h3>Rendszer metrikák</h3> <h3>Rendszer metrikák</h3>
@@ -102,7 +131,7 @@
</div> </div>
</div> </div>
<!-- Section 3: Container Resources --> <!-- Section 4: Container Resources -->
<div class="monitor-card"> <div class="monitor-card">
<h3>Alkalmazás erőforrások</h3> <h3>Alkalmazás erőforrások</h3>
<div class="container-charts-row" id="container-charts"> <div class="container-charts-row" id="container-charts">
@@ -120,7 +149,7 @@
</div> </div>
</div> </div>
<!-- Section 4: Per-container detail (expandable) --> <!-- Section 5: Per-container detail (expandable) -->
<div class="monitor-card" id="container-detail-panel" style="display:none"> <div class="monitor-card" id="container-detail-panel" style="display:none">
<div class="monitor-card-header"> <div class="monitor-card-header">
<h3 id="container-detail-title"></h3> <h3 id="container-detail-title"></h3>
@@ -144,35 +173,6 @@
</div> </div>
</div> </div>
<!-- Section 5: Storage -->
<div class="monitor-card">
<h3>Tárhely</h3>
<div class="storage-bars">
{{with .SystemInfo}}
<div class="storage-item">
<div class="storage-header">
<span class="storage-label">SSD (/)</span>
<span class="storage-value">{{fmtGB .DiskUsedGB}} / {{fmtGB .DiskTotalGB}} ({{printf "%.0f" .DiskPercent}}%)</span>
</div>
<div class="system-bar">
<div class="system-bar-fill {{usageColor .DiskPercent | printf "system-bar-%s"}}" style="width:{{printf "%.1f" .DiskPercent}}%"></div>
</div>
</div>
{{if .HDDConfigured}}
<div class="storage-item">
<div class="storage-header">
<span class="storage-label">Külső HDD</span>
<span class="storage-value">{{fmtGB .HDDUsedGB}} / {{fmtGB .HDDTotalGB}} ({{printf "%.0f" .HDDPercent}}%)</span>
</div>
<div class="system-bar">
<div class="system-bar-fill {{usageColor .HDDPercent | printf "system-bar-%s"}}" style="width:{{printf "%.1f" .HDDPercent}}%"></div>
</div>
</div>
{{end}}
{{end}}
</div>
</div>
<script src="/static/chart.min.js"></script> <script src="/static/chart.min.js"></script>
<script> <script>
(function() { (function() {
+49 -5
View File
@@ -1240,15 +1240,27 @@ a.stat-card:hover {
/* Temperature dot */ /* Temperature dot */
.temp-dot { .temp-dot {
display: inline-block; display: inline-block;
width: 8px; width: 11px;
height: 8px; height: 11px;
border-radius: 50%; border-radius: 50%;
margin-right: .25rem; margin-right: .25rem;
vertical-align: middle; vertical-align: middle;
} }
.temp-dot-green { background: var(--green); box-shadow: 0 0 4px rgba(35, 134, 54, 0.5); } .temp-dot-green { background: var(--green); box-shadow: 0 0 5px rgba(35, 134, 54, 0.6); }
.temp-dot-yellow { background: var(--yellow); box-shadow: 0 0 4px rgba(210, 153, 34, 0.5); } .temp-dot-yellow { background: var(--yellow); box-shadow: 0 0 5px rgba(210, 153, 34, 0.6); }
.temp-dot-red { background: var(--red); box-shadow: 0 0 4px rgba(218, 54, 51, 0.5); } .temp-dot-red { background: var(--red); box-shadow: 0 0 5px rgba(218, 54, 51, 0.6); }
/* Temperature value pill */
.temp-value-pill {
display: inline-block;
padding: 1px 7px;
border-radius: 4px;
font-weight: 600;
font-size: .85rem;
}
.temp-pill-green { background: rgba(35,134,54,0.18); color: var(--green); }
.temp-pill-yellow { background: rgba(210,153,34,0.18); color: var(--yellow); }
.temp-pill-red { background: rgba(218,54,51,0.18); color: var(--red); }
.system-info-item-compact { .system-info-item-compact {
flex: 0 1 auto; flex: 0 1 auto;
@@ -1461,6 +1473,20 @@ a.stat-card:hover {
color: var(--text-muted); color: var(--text-muted);
} }
.col-subtitle {
font-size: .7rem;
font-weight: 400;
color: var(--text-muted);
text-transform: none;
letter-spacing: 0;
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;
font-size: .8rem; font-size: .8rem;
@@ -1524,6 +1550,24 @@ a.stat-card:hover {
border-top: 1px solid var(--border-color); border-top: 1px solid var(--border-color);
padding-top: .75rem; padding-top: .75rem;
} }
.repo-tier {
border-top: 1px solid var(--border-color);
padding-top: .75rem;
margin-top: .75rem;
}
.repo-tier:first-child {
border-top: none;
padding-top: 0;
margin-top: 0;
}
.repo-tier-title {
font-size: .85rem;
font-weight: 600;
color: var(--text-secondary);
margin-bottom: .5rem;
text-transform: uppercase;
letter-spacing: .3px;
}
.repo-remote-status { .repo-remote-status {
margin-top: .25rem; margin-top: .25rem;
display: flex; display: flex;