Files
deploy-felhom-compose/TASK.md
T
2026-02-17 16:06:16 +01:00

14 KiB

TASK — v0.11.9 UI Polish Fixes

Context

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.

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.

Files to modify

  • internal/web/templates/deploy.html
  • internal/web/templates/style.css
  • internal/web/templates/backups.html (emoji cleanup in cross-drive summary section)

Fix 1: Spacing between cards and "Automatikusan generált értékek"

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.

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.

Fix in style.css:

/* Change margin-bottom from 1rem to 1.5rem */
.deploy-cross-drive {
    /* ... existing ... */
    margin-bottom: 1.5rem;  /* was 1rem */
}

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).


Fix 2: Rename restic method + add info tooltip on "Módszer"

Problem: "Verziózott mentés (restic)" doesn't highlight the most important differentiator (encryption). Users should also understand the tradeoffs before picking.

Fix in deploy.html — method dropdown (around line 16934-16942):

Replace:

<div class="settings-row">
    <span class="settings-label">Módszer</span>
    <select name="cross_drive_method" class="form-control" style="max-width:20rem">
        <option value="rsync" {{if and .CrossDriveConfig (eq .CrossDriveConfig.Method "rsync")}}selected{{end}}>
            Egyszerű másolat (rsync)
        </option>
        <option value="restic" {{if and .CrossDriveConfig (eq .CrossDriveConfig.Method "restic")}}selected{{end}}>
            Verziózott mentés (restic)
        </option>
    </select>
</div>

With:

<div class="settings-row">
    <span class="settings-label">
        Módszer
        <span class="info-tooltip" tabindex="0">
            <span class="info-icon">i</span>
            <span class="info-tooltip-text">
                <strong>Egyszerű másolat (rsync):</strong> 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.
                <br><br>
                <strong>Titkosított mentés (restic):</strong> 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.
            </span>
        </span>
    </span>
    <select name="cross_drive_method" class="form-control" style="max-width:20rem">
        <option value="rsync" {{if and .CrossDriveConfig (eq .CrossDriveConfig.Method "rsync")}}selected{{end}}>
            Egyszerű másolat (rsync)
        </option>
        <option value="restic" {{if and .CrossDriveConfig (eq .CrossDriveConfig.Method "restic")}}selected{{end}}>
            Titkosított mentés (restic)
        </option>
    </select>
</div>

Add to style.css:

/* Info tooltip (i icon with hover popup) */
.info-tooltip {
    position: relative;
    display: inline-flex;
    align-items: center;
    margin-left: .35rem;
    cursor: help;
}

.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;
}

.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;
}

/* 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);
}

Fix 3: Cursor on "Napi mentésbe foglalás" label

Problem: The <label class="toggle"> wrapping the disabled checkbox uses cursor: pointer from the .toggle class, making it look clickable when it's not.

Fix in deploy.html (around line 16886-16890):

Replace:

<div class="cross-drive-nightly">
    <label class="toggle">
        <input type="checkbox" id="app-backup-enabled" {{if .AppBackupEnabled}}checked{{end}} disabled>
        <span class="toggle-label">Napi mentésbe foglalás (restic, helyi)</span>
    </label>

With:

<div class="cross-drive-nightly">
    <div class="cross-drive-nightly-status">
        {{if .AppBackupEnabled}}
        <span class="nightly-status-indicator nightly-enabled"></span>
        {{else}}
        <span class="nightly-status-indicator nightly-disabled"></span>
        {{end}}
        <span class="toggle-label">Napi mentésbe foglalás (restic, helyi)</span>
    </div>

This replaces the disabled checkbox with a simple colored dot indicator — green if enabled, gray if not. Not clickable, not a checkbox, no confusing cursor.

Add to style.css:

/* Nightly backup status indicator (non-interactive) */
.cross-drive-nightly-status {
    display: flex;
    align-items: center;
    gap: .5rem;
}

.nightly-status-indicator {
    display: inline-block;
    width: 10px;
    height: 10px;
    border-radius: 50%;
    flex-shrink: 0;
}

.nightly-enabled {
    background: var(--green);
    box-shadow: 0 0 4px rgba(35, 134, 54, 0.4);
}

.nightly-disabled {
    background: var(--text-muted);
    opacity: 0.5;
}

Fix 4: Progressive disclosure — disable config fields until enabled

Problem: Users see the destination/method/schedule dropdowns and might think the backup is already configured/active, even though the "Engedélyezve" checkbox is unchecked.

Fix in deploy.html — add JS to toggle field states:

Add onchange handler to the enabled checkbox and disabled attribute to the select fields:

<div class="settings-row">
    <span class="settings-label">Engedélyezve</span>
    <label class="toggle" style="margin:0">
        <input type="checkbox" name="cross_drive_enabled" id="cross-drive-enabled"
            {{if and .CrossDriveConfig .CrossDriveConfig.Enabled}}checked{{end}}
            onchange="toggleCrossDriveFields()">
        <span class="toggle-label">Igen</span>
    </label>
</div>
<div class="settings-row">
    <span class="settings-label">Cél tárhely</span>
    <select name="cross_drive_dest" id="cd-dest" class="form-control cross-drive-field" style="max-width:20rem"
        {{if not (and .CrossDriveConfig .CrossDriveConfig.Enabled)}}disabled{{end}}>
        ...options...
    </select>
</div>
<div class="settings-row">
    <span class="settings-label">
        Módszer
        <span class="info-tooltip" tabindex="0">...</span>
    </span>
    <select name="cross_drive_method" id="cd-method" class="form-control cross-drive-field" style="max-width:20rem"
        {{if not (and .CrossDriveConfig .CrossDriveConfig.Enabled)}}disabled{{end}}>
        ...options...
    </select>
</div>
<div class="settings-row">
    <span class="settings-label">Ütemezés</span>
    <select name="cross_drive_schedule" id="cd-schedule" class="form-control cross-drive-field" style="max-width:20rem"
        {{if not (and .CrossDriveConfig .CrossDriveConfig.Enabled)}}disabled{{end}}>
        ...options...
    </select>
</div>

Add JS function (inside the existing <script> block at the bottom of deploy.html):

function toggleCrossDriveFields() {
    var enabled = document.getElementById('cross-drive-enabled').checked;
    var fields = document.querySelectorAll('.cross-drive-field');
    for (var i = 0; i < fields.length; i++) {
        fields[i].disabled = !enabled;
    }
}

Important: The disabled attribute prevents form submission of those fields. The backend handler for POST /settings/cross-backup/{name} must handle missing form values gracefully — if cross_drive_enabled is unchecked (not in form data), set Enabled: false and preserve existing dest/method/schedule values from settings (don't reset them to empty strings just because they weren't submitted).

Check internal/web/handlers.go (or internal/api/router.go) — the saveCrossBackupConfig handler. If it reads form values like:

cfg.DestinationPath = req.FormValue("cross_drive_dest")
cfg.Method = req.FormValue("cross_drive_method")

Then when disabled fields aren't submitted, these would be empty strings. Fix: only update those fields if enabled is true, otherwise preserve existing config:

enabled := req.FormValue("cross_drive_enabled") == "on"

// Load existing config to preserve values when disabled
existing := s.settings.GetCrossDriveConfig(name)

cfg := settings.CrossDriveBackup{
    Enabled: enabled,
}

if enabled {
    // Read from form
    cfg.DestinationPath = req.FormValue("cross_drive_dest")
    cfg.Method = req.FormValue("cross_drive_method")
    cfg.Schedule = req.FormValue("cross_drive_schedule")
} else if existing != nil {
    // Preserve existing settings when disabling
    cfg.DestinationPath = existing.DestinationPath
    cfg.Method = existing.Method
    cfg.Schedule = existing.Schedule
}

// Always preserve runtime state
if existing != nil {
    cfg.LastRun = existing.LastRun
    cfg.LastStatus = existing.LastStatus
    cfg.LastError = existing.LastError
    cfg.LastDuration = existing.LastDuration
    cfg.LastSizeHuman = existing.LastSizeHuman
}

Fix 5: Remove/reduce emojis

Problem: Too many emoji throughout the UI. Replace with text or CSS-styled elements.

deploy.html changes:

Location Current Replace with
Line ~16884 h4 🔒 Biztonsági mentés Biztonsági mentés
Line ~16902 warning ⚠️ {{.BackupDestWarning}} {{.BackupDestWarning}} (the .alert-warning class already visually marks it as warning)
Line ~16964 status ok ✅ Sikeres Sikeres
Line ~16964 status error ❌ Hiba: Hiba:
Line ~16964 status running ⏳ Fut... Fut...
Line ~16982 bottom hint ⚠️ A cél meghajtó legyen... A cél meghajtó legyen... (already inside form-hint, visually distinct)
Stale data delete button 🗑️ Korábbi adatok törlése Korábbi adatok törlése
Stale data h4 🗑️ Korábbi adatok (if emoji present) Korábbi adatok

backups.html cross-drive summary section changes:

Location Current Replace with
Status ok badge ✅ {{.LastRunShort}} {{.LastRunShort}} (use .meta-badge-ok class for green)
Status error badge ❌ Hiba Hiba (use .meta-badge-fail class for red)
Running badge ⏳ Fut... Fut...
Schedule badge ⏰ {{.ScheduleLabel}} {{.ScheduleLabel}}
Unconfigured apps warning ⚠️ {{len ...}} {{len ...}} (already in yellow-colored div)

Note: The .alert-warning class already provides visual differentiation (yellow/orange background/border), and .meta-badge-ok/.meta-badge-fail provide green/red colors. Emojis are redundant with these CSS classes.

Also check for any remaining emoji in:

  • stacks.html — if any status emojis were left
  • backups.html — the existing sections (db dumps, etc.)

Fix 6 (bonus): "Automatikusan generálva" badge also uses emoji

In the auto-generated values section (line ~17031):

<span class="auto-generated-badge">✓ Automatikusan generálva</span>

This is fine — the checkmark () is a Unicode character, not an emoji. Leave as-is.


Summary of visual outcome

Before (v0.11.8):

  • Disabled checkbox that looks clickable
  • Pointer cursor on non-interactive label
  • All form fields visible/enabled even when backup disabled
  • Emoji scattered throughout
  • No explanation of rsync vs restic
  • Tight spacing between sections

After (v0.11.9):

  • Clean green/gray dot indicator for nightly backup status
  • Default cursor on non-interactive elements
  • Dropdowns grayed out until "Engedélyezve" is checked → clear progressive disclosure
  • No emoji — clean professional look using CSS classes for visual feedback
  • Info tooltip on "Módszer" explaining both options clearly
  • Proper spacing between all sections
  • "Titkosított mentés" label better communicates restic's key advantage

Testing

  1. Visit deployed app page (e.g., /stacks/immich/deploy)
  2. Verify spacing between all cards is consistent
  3. Verify no emoji visible in the backup section
  4. Verify nightly backup shows green dot (if enabled) or gray dot (if not)
  5. Verify cursor doesn't change to pointer on the nightly status line
  6. Uncheck "Engedélyezve" → dropdowns should become disabled/grayed
  7. Check "Engedélyezve" → dropdowns should become active
  8. Save with "Engedélyezve" unchecked → verify existing config preserved (check settings.json)
  9. Hover/focus on the (i) icon next to "Módszer" → tooltip appears with explanation
  10. Check backup page → no emoji in cross-drive summary section