```
This lets any stack with a slug (including protected ones with `.felhom.yml`) be clickable. Stacks without `.felhom.yml` (no slug) won't have the click handler — which is correct.
In the protected actions section, add "Részletek" link:
```html
{{if .Protected}}
{{end}}
{{else if not .Deployed}}
```
**dashboard.html**: Same `data-href` fix for the compact card:
Change from:
```html
```
To:
```html
```
### Fix — FileBrowser .felhom.yml (resources)
The manually created `.felhom.yml` on the demo node is missing `resources`. Update it to include:
```yaml
resources:
mem_request: "128M"
mem_limit: "256M"
pi_compatible: true
needs_hdd: true
```
Also add this to the `.felhom.yml` created by `install_filebrowser()` in `scripts/docker-setup.sh`.
**Manual fix for demo node** (run after deploy):
```bash
cat >> /opt/docker/stacks/filebrowser/.felhom.yml << 'EOF'
resources:
mem_request: "128M"
mem_limit: "256M"
pi_compatible: true
needs_hdd: true
EOF
```
### Verification
- Clicking FileBrowser card on Alkalmazások page opens `/apps/filebrowser` detail page
- "Részletek" button appears next to "Újraindítás" on FileBrowser
- Detail page shows memory badges (~128M, HDD szükséges, Pi kompatibilis)
- App info section shows use cases, first steps, prerequisites
- Other protected stacks without `.felhom.yml` (traefik, cloudflared) don't show "Részletek" (no slug)
---
## Task 2: Backup page — cache expensive data
### Problem
`GetFullStatus()` is called synchronously on every page load of `/backups` and runs three blocking subprocess calls:
1. `m.restic.Stats()` — executes `restic stats --json` + `restic snapshots --json` (~2-3 seconds)
2. `ListDumpFiles()` — directory listing (fast, ~1ms)
3. `DiscoverDatabases()` — `docker ps` + `docker inspect` per container (~0.5s)
Total: 3-4 seconds per page load. This is unacceptable for a dashboard page.
### Solution: Background cache with periodic refresh
Add a cached `FullBackupStatus` to the Manager that refreshes periodically via the scheduler, instead of computing on every page load.
#### New fields in Manager:
```go
type Manager struct {
// ... existing fields ...
mu sync.Mutex
lastDBDump *DBDumpStatus
lastBackup *BackupStatus
running bool
snapshotHistory []SnapshotRecord
// Cached status for page rendering
cachedStatus *FullBackupStatus
cacheTime time.Time
}
```
#### New method: `RefreshCache()`
```go
// RefreshCache updates the cached full status. Called by scheduler every 5 minutes
// and after each backup run.
func (m *Manager) RefreshCache(nextDBDump, nextBackup time.Time) {
// Same logic as current GetFullStatus() — run restic stats, list dump files, discover DBs
status := &FullBackupStatus{ ... }
// ... all the expensive calls ...
m.mu.Lock()
m.cachedStatus = status
m.cacheTime = time.Now()
m.mu.Unlock()
m.logger.Printf("[INFO] Backup status cache refreshed")
}
```
#### Modified `GetFullStatus()`: read from cache
```go
// GetFullStatus returns the cached backup status for page rendering.
// Returns instantly — no subprocess calls.
func (m *Manager) GetFullStatus(nextDBDump, nextBackup time.Time) *FullBackupStatus {
m.mu.Lock()
defer m.mu.Unlock()
if m.cachedStatus != nil {
// Update dynamic fields that don't need subprocess calls
m.cachedStatus.Running = m.running
m.cachedStatus.NextDBDump = nextDBDump
m.cachedStatus.NextBackup = nextBackup
m.cachedStatus.LastDBDump = m.lastDBDump
m.cachedStatus.LastBackup = m.lastBackup
// Update snapshot history
m.cachedStatus.SnapshotHistory = make([]SnapshotRecord, len(m.snapshotHistory))
copy(m.cachedStatus.SnapshotHistory, m.snapshotHistory)
return m.cachedStatus
}
// No cache yet — return a minimal status (first page load before cache is populated)
return &FullBackupStatus{
Enabled: m.cfg.Backup.Enabled,
Running: m.running,
LastDBDump: m.lastDBDump,
LastBackup: m.lastBackup,
DBDumpSchedule: m.cfg.Backup.DBDumpSchedule,
ResticSchedule: m.cfg.Backup.ResticSchedule,
PruneSchedule: m.cfg.Backup.PruneSchedule,
NextDBDump: nextDBDump,
NextBackup: nextBackup,
Retention: m.cfg.Backup.Retention,
RepoPath: m.cfg.Backup.ResticRepo,
LastCheckTime: m.lastCheckTime,
LastCheckOK: m.lastCheckOK,
BackupPaths: []string{
m.cfg.Paths.StacksDir,
m.cfg.Paths.DBDumpDir,
"/opt/docker/felhom-controller/controller.yaml",
},
}
}
```
#### Scheduler integration (in `main.go`):
Register a periodic job that refreshes the backup cache:
```go
if cfg.Backup.Enabled && backupMgr != nil {
// ... existing daily jobs ...
// Cache refresh: every 5 minutes
sched.Every("backup-cache", 5*time.Minute, func(ctx context.Context) error {
nextDBDump := scheduler.NextDailyRun(cfg.Backup.DBDumpSchedule)
nextBackup := scheduler.NextDailyRun(cfg.Backup.ResticSchedule)
backupMgr.RefreshCache(nextDBDump, nextBackup)
return nil
})
}
```
#### Refresh after backup completion:
At the end of `RunFullBackup()` and `RunBackup()`, call `RefreshCache()` so the page shows updated data immediately after a backup:
```go
func (m *Manager) RunFullBackup(ctx context.Context) error {
// ... existing logic ...
// Refresh cache after backup completes
m.RefreshCache(
scheduler.NextDailyRun(m.cfg.Backup.DBDumpSchedule),
scheduler.NextDailyRun(m.cfg.Backup.ResticSchedule),
)
return nil // or the backup error
}
```
**Note**: `RefreshCache()` needs to import `scheduler.NextDailyRun`. To avoid a circular import (backup → scheduler → backup), either:
- Pass the next run times as parameters (already the pattern used)
- Make `NextDailyRun` a standalone utility function that both packages can import
- Or just call `RefreshCache` from `main.go` via a callback
Simplest approach: `RefreshCache` takes `nextDBDump, nextBackup time.Time` params (same as `GetFullStatus`). The scheduler job and the post-backup refresh both compute the times before calling.
#### Initial cache population on startup:
In `main.go`, after scheduler starts, trigger initial cache refresh in a goroutine (don't block startup):
```go
if cfg.Backup.Enabled && backupMgr != nil {
go func() {
nextDBDump := scheduler.NextDailyRun(cfg.Backup.DBDumpSchedule)
nextBackup := scheduler.NextDailyRun(cfg.Backup.ResticSchedule)
backupMgr.RefreshCache(nextDBDump, nextBackup)
}()
}
```
### Result
- Page load: instant (reads cached struct)
- Cache refresh: every 5 minutes in background (user never waits)
- After manual backup: cache refreshes immediately
- First page load after startup: may show minimal data for a few seconds until goroutine completes
### Cache staleness indicator (optional)
Add `CacheTime time.Time` to `FullBackupStatus`. The template can optionally show "Utolsó frissítés: X perccel ezelőtt" at the bottom of the page in a muted font. Not critical, but helpful for debugging.
---
## Implementation order
### Step 1: Protected stack detail pages
1. Fix `data-href` gating in `stacks.html` and `dashboard.html` — use `{{if .Meta.Slug}}` instead of `{{if not .Protected}}`
2. Add "Részletek" button to protected stack section in `stacks.html`
3. Update `install_filebrowser()` in `docker-setup.sh` — add `resources` to `.felhom.yml`
### Step 2: Backup page caching
1. Add `cachedStatus`, `cacheTime` fields to `Manager`
2. Create `RefreshCache()` method
3. Modify `GetFullStatus()` to read from cache
4. Register `backup-cache` scheduler job in `main.go`
5. Call `RefreshCache()` at end of `RunFullBackup()` and `RunBackup()`
6. Add initial cache goroutine in `main.go`
### Step 3: Build, deploy, verify
1. Build v0.4.7
2. Deploy to demo node
3. Update `/opt/docker/stacks/filebrowser/.felhom.yml` on demo node (add resources)
4. Verify FileBrowser card is clickable → detail page with memory badges
5. Verify backup page loads instantly
6. Trigger manual backup → verify page updates after completion
### Step 4: Documentation
1. Update CONTEXT.md, README
2. Bump version
---
## Files to modify
```
internal/backup/backup.go — add cachedStatus, RefreshCache(), modify GetFullStatus()
internal/web/templates/stacks.html — fix data-href gating, add Részletek button
internal/web/templates/dashboard.html — fix data-href gating
scripts/docker-setup.sh — add resources to filebrowser .felhom.yml
cmd/controller/main.go — register backup-cache job, initial goroutine
```
---
## Verification checklist
- [ ] FileBrowser card on Alkalmazások page is clickable → opens `/apps/filebrowser`
- [ ] FileBrowser has "Részletek" button next to "Újraindítás"
- [ ] FileBrowser detail page shows ~128M / HDD szükséges / Pi kompatibilis badges
- [ ] FileBrowser detail page shows use cases, first steps, prerequisites
- [ ] Traefik/cloudflared cards do NOT show "Részletek" (no .felhom.yml/slug)
- [ ] Felhom-controller card does NOT show "Részletek" (no .felhom.yml)
- [ ] Backup page loads in <500ms (instant, cached)
- [ ] Backup page shows correct data after initial cache population
- [ ] Manual backup → page shows updated data after completion
- [ ] Cache refreshes every 5 minutes (check logs for "[INFO] Backup status cache refreshed")
- [ ] No regressions on dashboard, app detail pages, deploy flow