# TASK: Phase B — Storage Management UI Polish & Health Severity Fix
**Version target:** controller 0.10.0
**Repo:** `deploy-felhom-compose` (controller)
## Overview
Phase A (v0.9.0) delivered the storage paths foundation: registry in settings.json, auto-discovery, per-app HDD_PATH resolution, settings UI with CRUD, deploy dropdown, and health monitoring. All functional — but health check now FAILS on demo-felhom because `/mnt/hdd_placeholder` is (correctly) detected as not a real mount point.
**Immediate fix:** Health severity reclassification — non-mount-point is a **warning**, not an **issue** that causes FAIL. The FAIL status should be reserved for genuinely broken things (services down, disk critically full, backup failing), not informational findings.
Phase B then polishes the UI and fills gaps:
1. **Health severity fix** — mount-point check: warning not issue
2. **Success flash messages** — storage operations only show errors, never success
3. **Edit labels** — can't rename a storage path after adding it
4. **App names per storage path** — settings page shows count, not which apps
5. **Per-app storage info on stacks page** — no visibility into which storage each app uses
6. **Deploy dropdown enhancements** — show free space, disk usage warning
7. **Filesystem & disk info** — show ext4/btrfs, device, model on settings page
8. **Backup page: storage path context** — show which storage path each app is on
---
## 0. Health Severity Fix (URGENT — do first)
### 0.1 Problem
`checkStoragePaths()` in `healthcheck.go` currently classifies non-mount-point as an **issue**:
```go
// CURRENT (line ~6751):
if !system.IsMountPoint(sp.Path) {
issues = append(issues, fmt.Sprintf("Storage path %s is NOT a mount point — data writes to SSD!", sp.Path))
}
```
Issues → `status = "fail"` → Healthchecks shows FAIL → Healthchecks triggers alert → hub shows "STATUS: FAIL". This cascades into a false alarm for any setup where the storage path is intentionally on SSD (demo environments, test environments, customers who haven't connected an external drive yet).
### 0.2 Fix: Warning + Hungarian message
```go
// FIXED:
if !system.IsMountPoint(sp.Path) {
warnings = append(warnings, fmt.Sprintf(
"Storage path %s is not a separate mount point — data is stored on the system drive",
sp.Path))
}
```
Health status becomes `"warn"` instead of `"fail"`. The warning still appears on:
- Controller monitoring page (red banner → yellow banner)
- Hub customer detail page (Issues → Warnings section)
- Healthchecks ping body (status: WARN instead of FAIL)
### 0.3 When should non-mount-point be an ISSUE?
In the future (Phase C or later), consider an "acknowledged" flag per storage path:
- When adding a path that's not a mount point, show a confirmation dialog: "Ez az útvonal a rendszermeghajtón van. Biztosan folytatja?"
- If acknowledged, the health check uses warning level
- If a previously-mount-point path STOPS being a mount point (drive disconnected), that IS an issue — it means something changed unexpectedly
For now, the simple severity downgrade to warning is sufficient. The informational value is preserved, without false alarms.
### 0.4 Also: Hungarian messages in health check
Currently health messages are in English:
- "Storage path /mnt/hdd_placeholder is NOT a mount point — data writes to SSD!"
- "Storage path not accessible: ..."
- "Storage ... nearly full: ..."
These appear on the customer-facing monitoring page. Change to Hungarian:
```go
// Path not accessible
warnings = append(warnings, fmt.Sprintf("Adattároló nem elérhető: %s", sp.Path))
// Not a mount point
warnings = append(warnings, fmt.Sprintf(
"Az adattároló (%s) nem külön meghajtón van — az adatok a rendszermeghajtóra íródnak", sp.Path))
// Disk usage critical (≥95%) — this stays as issue
issues = append(issues, fmt.Sprintf("Adattároló majdnem megtelt: %s (%.0f%%)", sp.Path, di.UsedPercent))
// Disk usage high (≥90%) — warning
warnings = append(warnings, fmt.Sprintf("Adattároló használat magas: %s (%.0f%%)", sp.Path, di.UsedPercent))
```
Note: Hub and Healthchecks receive the raw text. Hub is operator-facing (English would also be fine there), but since the same messages show on the customer controller, Hungarian is better for consistency.
### 0.5 Monitoring page banner color
Currently the monitoring page shows issues as red banners. Warnings should be yellow/amber:
```html
{{range .Warnings}}
⚠️ {{.}}
{{end}}
```
Check if the monitoring template already differentiates issue vs warning banners. If not, add CSS class:
```css
.monitoring-banner-warn {
background: rgba(255, 193, 7, 0.15);
border-left: 4px solid var(--yellow);
color: var(--yellow);
}
```
---
## 1. Success Flash Messages for Storage Operations
### 1.1 Problem
All storage handlers (`/settings/storage/add`, `/remove`, `/default`, `/schedulable`) only set `StorageError` on failure. On success they redirect without feedback.
### 1.2 Fix: Query param flash (consistent with backup page)
Use the existing backup page pattern: redirect with query params `?storage_msg=success&storage_detail=...`
In settings handler, parse:
```go
if msg := r.URL.Query().Get("storage_msg"); msg == "success" {
data["StorageSuccess"] = r.URL.Query().Get("storage_detail")
}
```
Success messages:
- **Add:** "Adattároló sikeresen hozzáadva: /mnt/hdd_1"
- **Remove:** "Adattároló eltávolítva: /mnt/hdd_1"
- **Set default:** "Alapértelmezett adattároló beállítva: /mnt/hdd_1"
- **Toggle schedulable:** "Adattároló állapot módosítva: /mnt/hdd_1"
### 1.3 Template
Add to settings.html (after `StorageError`):
```html
{{if .StorageSuccess}}
{{.StorageSuccess}}
{{end}}
```
---
## 2. Edit Storage Path Labels
### 2.1 UI: Inline edit
Add edit button next to label. JS toggles between display and inline form:
```html
{{.Label}}
```
JS `editStorageLabel()` replaces content with:
```html
```
### 2.2 Route & handler
| Method | Path | Auth? |
|--------|------|-------|
| POST | `/settings/storage/label` | Yes |
Handler: parse `storage_path` + `storage_label`, validate (non-empty, max 50 chars), call `settings.SetStorageLabel()`, redirect with success flash.
### 2.3 Settings method
```go
func (s *Settings) SetStorageLabel(path, label string) error {
s.mu.Lock()
defer s.mu.Unlock()
for i := range s.StoragePaths {
if s.StoragePaths[i].Path == path {
s.StoragePaths[i].Label = label
return s.save()
}
}
return fmt.Errorf("storage path %q not found", path)
}
```
---
## 3. App Names Per Storage Path (Settings Page)
### 3.1 Current: "3 alkalmazás használja" — no names
### 3.2 Enhancement: Expandable list with names + sizes
Extend `StoragePathView`:
```go
type StorageAppDetail struct {
Name string // Display name (e.g., "Immich")
Stack string // Stack name (for link)
SizeHuman string // Data size on this path
}
type StoragePathView struct {
// ... existing fields ...
AppDetails []StorageAppDetail // NEW
}
```
Template:
```html
{{if .AppDetails}}
{{.AppCount}} alkalmazás használja