diff --git a/TASK.md b/TASK.md index c36bd63..3b70646 100644 --- a/TASK.md +++ b/TASK.md @@ -1,624 +1,392 @@ -# Per-App Cross-Drive Backup — Design & Task Document +# TASK — v0.11.9 UI Polish Fixes -## Overview +## Context -Extend the controller with per-app user data backup to a **secondary storage drive**. This is distinct from the existing nightly restic snapshot (which backs up to the same drive). The cross-drive backup provides the "second copy on different media" part of the 3-2-1 backup rule. +These are UI-only fixes for the deploy/settings page backup section introduced in v0.11.8. No backend changes needed. All changes are in `deploy.html` and `style.css`. -Two mechanisms available (user chooses per app): -- **rsync** — Simple file mirror. Easy to browse via FileBrowser. No versioning. -- **restic** — Versioned, encrypted, deduplicated snapshots on the secondary drive. +**Important design principle:** Use minimal emojis in the UI. The project prefers a clean, professional look. Replace emoji indicators with text or CSS-styled elements instead. -### Current State (what already exists) +## Files to modify -| Feature | Status | Location | -|---------|--------|----------| -| Per-app backup toggle | ✅ Exists | Backup page, `settings.json` `app_backup` map | -| `resolveAppBackupPaths()` | ✅ Exists | `backup.go` — includes enabled app HDD paths in nightly restic | -| `AppBackupInfo` discovery | ✅ Exists | `appdata.go` — discovers HDD mounts, Docker volumes per app | -| Storage paths registry | ✅ Exists | `settings.json` — multiple paths with labels, health, default | -| Mount health checks | ✅ Exists | `mounts_linux.go` — `IsMountPoint()`, `GetDiskUsage()` | -| Scheduler | ✅ Exists | `scheduler.go` — daily cron-style jobs | -| Stale data cleanup | ✅ v0.11.7 | Deploy page — delete old data from non-active paths | - -### What's New - -The existing backup toggle (`Enabled bool`) includes app data in the **same-drive** restic snapshot. The new feature adds a completely separate **cross-drive** backup job with its own method, destination, and schedule. +- `internal/web/templates/deploy.html` +- `internal/web/templates/style.css` +- `internal/web/templates/backups.html` (emoji cleanup in cross-drive summary section) --- -## Data Model +## Fix 1: Spacing between cards and "Automatikusan generált értékek" -### Extended `AppBackupPrefs` in `settings.json` +**Problem:** No clear visual separation between the last card above the deploy form (`deploy-cross-drive` or `deploy-stale-data`) and the "Automatikusan generált értékek" section inside `.deploy-form`. -```go -// AppBackupPrefs holds per-app backup configuration. -type AppBackupPrefs struct { - // Existing: includes app data in nightly restic (same drive) - Enabled bool `json:"enabled"` +**Root cause:** `.deploy-cross-drive` has `margin-bottom: 1rem` which doesn't provide enough separation before the next card. When stale data card exists without cross-drive, it's also tight. - // NEW: Cross-drive backup to secondary storage - CrossDrive *CrossDriveBackup `json:"cross_drive,omitempty"` -} +**Fix in `style.css`:** -// CrossDriveBackup configures per-app backup to a secondary drive. -type CrossDriveBackup struct { - Enabled bool `json:"enabled"` - Method string `json:"method"` // "rsync" or "restic" - DestinationPath string `json:"destination_path"` // e.g., "/mnt/hdd_1" - Schedule string `json:"schedule"` // "daily", "weekly", "manual" - - // Runtime state (updated by backup runner, persisted for display) - LastRun string `json:"last_run,omitempty"` // RFC3339 - LastStatus string `json:"last_status,omitempty"` // "ok", "error", "running" - LastError string `json:"last_error,omitempty"` - LastDuration string `json:"last_duration,omitempty"` // "2m34s" - LastSizeHuman string `json:"last_size_human,omitempty"` // "1.2 GB" +```css +/* Change margin-bottom from 1rem to 1.5rem */ +.deploy-cross-drive { + /* ... existing ... */ + margin-bottom: 1.5rem; /* was 1rem */ } ``` -Example `settings.json`: -```json -{ - "app_backup": { - "immich": { - "enabled": true, - "cross_drive": { - "enabled": true, - "method": "rsync", - "destination_path": "/mnt/hdd_1", - "schedule": "daily", - "last_run": "2026-02-17T03:15:00Z", - "last_status": "ok", - "last_duration": "45s", - "last_size_human": "48 MB" - } - }, - "paperless-ngx": { - "enabled": true, - "cross_drive": { - "enabled": true, - "method": "restic", - "destination_path": "/mnt/hdd_1", - "schedule": "weekly" - } - } - } -} -``` - -### Cross-drive backup directory layout - -On the destination drive: - -``` -/mnt/hdd_1/ -├── storage/ # App user data (active apps store data here) -│ ├── immich/ -│ └── paperless-ngx/ -├── media/ # User media files -├── Dokumentumok/ # User documents -└── backups/ # NEW: Cross-drive backups - ├── rsync/ # Mirror copies - │ ├── immich/ # rsync of /mnt/hdd_placeholder/storage/immich/ - │ └── ... - └── restic/ # Restic repository for versioned backups - ├── config - ├── data/ - ├── index/ - ├── keys/ - └── snapshots/ -``` - -Key decisions: -- All cross-drive backups go under `{destination}/backups/` to keep them separate from active app data -- rsync method: one directory per app under `backups/rsync/{stackname}/` -- restic method: single shared restic repo at `backups/restic/` (dedup benefits from shared repo) -- Restic repo on secondary drive uses a **separate password** stored in `settings.json` (not the same as the main backup repo) +No change needed for `.deploy-stale-data` — it already has `margin-bottom: 1.5rem`. But verify the margin is actually applied (check if another element is overriding it or if the stale data card is inside a container that collapses margins). --- -## Architecture +## Fix 2: Rename restic method + add info tooltip on "Módszer" -### New package: `internal/backup/crossdrive.go` +**Problem:** "Verziózott mentés (restic)" doesn't highlight the most important differentiator (encryption). Users should also understand the tradeoffs before picking. -```go -// CrossDriveRunner handles per-app backup to secondary storage. -type CrossDriveRunner struct { - settings *settings.Settings - stackProvider StackDataProvider - logger *log.Logger - mu sync.Mutex - running map[string]bool // per-app running state +**Fix in `deploy.html` — method dropdown (around line 16934-16942):** + +Replace: +```html +
+ Módszer + +
+``` + +With: +```html +
+ + Módszer + + i + + Egyszerű másolat (rsync): Tükörszerű másolat, a fájlok közvetlenül böngészhetők. + Nem titkosított, nem verziózott — mindig a legfrissebb állapotot tartalmazza. +

+ Titkosított mentés (restic): Titkosított, tömörített, verziózott mentés. + Korábbi állapotok visszaállíthatók. Nem böngészhető közvetlenül — + visszaállításhoz a vezérlőpult szükséges. +
+
+
+ +
+``` + +**Add to `style.css`:** + +```css +/* Info tooltip (i icon with hover popup) */ +.info-tooltip { + position: relative; + display: inline-flex; + align-items: center; + margin-left: .35rem; + cursor: help; } -// RunAppBackup runs cross-drive backup for a single app. -func (r *CrossDriveRunner) RunAppBackup(ctx context.Context, stackName string) error +.info-icon { + display: inline-flex; + align-items: center; + justify-content: center; + width: 16px; + height: 16px; + border-radius: 50%; + border: 1px solid var(--text-muted); + color: var(--text-muted); + font-size: .65rem; + font-weight: 700; + font-style: italic; + font-family: Georgia, serif; + line-height: 1; +} -// RunAllScheduled runs cross-drive backup for all apps matching the schedule. -func (r *CrossDriveRunner) RunAllScheduled(ctx context.Context, schedule string) error +.info-tooltip-text { + display: none; + position: absolute; + bottom: calc(100% + 8px); + left: 50%; + transform: translateX(-50%); + width: 320px; + padding: .75rem 1rem; + background: var(--bg-primary); + border: 1px solid var(--border-color); + border-radius: var(--radius); + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4); + font-size: .8rem; + font-weight: 400; + line-height: 1.5; + color: var(--text-secondary); + z-index: 100; + white-space: normal; +} -// GetAppStatus returns the current cross-drive backup status for an app. -func (r *CrossDriveRunner) GetAppStatus(stackName string) *CrossDriveStatus +/* Show on hover or focus (for keyboard) */ +.info-tooltip:hover .info-tooltip-text, +.info-tooltip:focus .info-tooltip-text, +.info-tooltip:focus-within .info-tooltip-text { + display: block; +} + +/* Arrow pointing down from tooltip */ +.info-tooltip-text::after { + content: ''; + position: absolute; + top: 100%; + left: 50%; + transform: translateX(-50%); + border: 6px solid transparent; + border-top-color: var(--border-color); +} ``` -### rsync backup flow - -``` -1. Validate: destination path mounted & writable -2. Resolve app HDD mounts (e.g., /mnt/hdd_placeholder/storage/immich/) -3. Create destination: {dest}/backups/rsync/{stackname}/ -4. For each HDD mount: - rsync -a --delete --info=progress2 \ - /mnt/hdd_placeholder/storage/immich/ \ - /mnt/hdd_1/backups/rsync/immich/storage/immich/ -5. Update settings: last_run, last_status, last_size_human -``` - -Note: `--delete` mirrors exactly — old files on destination get removed. This is a mirror, not versioned. - -### restic backup flow - -``` -1. Validate: destination path mounted & writable -2. Ensure shared restic repo initialized at {dest}/backups/restic/ -3. Resolve app HDD mounts -4. restic backup --repo {dest}/backups/restic/ \ - --password-file {settings-based} \ - --tag {stackname} \ - /mnt/hdd_placeholder/storage/immich/ -5. Update settings: last_run, last_status, last_size_human -``` - -Restic benefits: dedup across apps, versioned snapshots, can restore specific point-in-time. - --- -## UI Design +## Fix 3: Cursor on "Napi mentésbe foglalás" label -### 1. Deploy/Settings Page — Per-App Section +**Problem:** The `