---
title: "Glance Dashboard — Config Patterns and Homelab Setup"
aliases: [glance-dashboard, glance-yaml-config, glance-homarr-replacement]
tags: [glance, dashboard, homelab, monitoring, yaml, self-hosted]
sources:
  - "daily/2026-04-29.md"
  - "daily/2026-05-03.md"
created: 2026-04-29
updated: 2026-05-03
---

# Glance Dashboard — Config Patterns and Homelab Setup

Glance is a Go-binary self-hosted dashboard with rich widget support (RSS, Reddit, YouTube, monitors, weather). It was chosen as a replacement for Homarr due to lower RAM overhead, no Node.js runtime dependency, and richer feed-style widgets. Two non-obvious configuration constraints caught during setup: the `$include` directive cannot be used to split pages across files, and monitor widgets must use internal IPs to avoid Cloudflare-induced timeouts.

## Key Points

- **`$include` merges at the same YAML mapping level** — it cannot be used to separate `pages:` entries into individual files; all pages must be inlined directly in `glance.yml`
- **Monitor widgets must use internal IPs** (`192.168.x.x`), not public Cloudflare-proxied domains — Cloudflare proxy overhead causes timeouts that look like host-down false positives
- **Minimum 6-character password** — `admin` (5 chars) is rejected at first login; use `admin1` or longer
- **Homarr replacement rationale:** Go single binary, lower RAM overhead than Homarr's Node.js stack, native RSS/Reddit/YouTube/Hacker News widgets, no Docker socket required for basic use
- **Prometheus RAPL power metrics** can be embedded via iframe using `rate(node_rapl_package_joules_total[1m])` for CPU package power draw and `node_hwmon_temp_celsius` for temperature

## Details

### `$include` Directive Limitation

Glance supports a `$include: path/to/file.yml` directive to merge external YAML into the config. The merge happens **at the same mapping level** as the `$include` line — it does not wrap the included content in a sub-key.

This means:

```yaml
# THIS DOES NOT WORK — cannot split pages this way
pages:
  $include: pages/home.yml
  $include: pages/media.yml
```

The `$include` is not a list item; it replaces the content at that level. Attempting to use it to include separate page definitions results in only the last include being loaded, or a YAML parse error.

**Correct approach:** inline all pages directly in `glance.yml`:

```yaml
pages:
  - name: Home
    columns:
      - ...
  - name: Media
    columns:
      - ...
```

If the file becomes large, manage it with editor folding rather than file splitting.

### Monitor Widget — Use Internal IPs

Glance's `monitor` widget makes HTTP HEAD/GET requests from the server to check uptime. When services are behind Cloudflare proxy, the check goes: Glance server → public DNS → Cloudflare edge → origin. Cloudflare's proxy introduces 200–500 ms overhead and occasionally rate-limits or challenges automated requests.

**Symptom:** All monitors show timeout/down even when services are clearly reachable in a browser.

**Fix:** Use the internal IP directly:

```yaml
- type: monitor
  title: Prowlarr
  url: http://192.168.1.100:9696
```

This bypasses Cloudflare entirely. AdGuard DNS rewrites are not needed for monitor widgets since they use IPs, not hostnames.

### NPM Password Reset (SQLite bcrypt Injection)

If the Nginx Proxy Manager admin password is lost, reset it directly in the SQLite database:

```bash
# Backup first
cp /opt/npm/data/database.sqlite /opt/npm/data/database.sqlite.bak.$(date +%Y%m%d)

# Install bcrypt tool if needed
pip install bcrypt

# Generate hash
python3 -c "import bcrypt; print(bcrypt.hashpw(b'admin', bcrypt.gensalt()).decode())"

# Inject into NPM DB
sqlite3 /opt/npm/data/database.sqlite \
  "UPDATE user SET password = '<bcrypt_hash>' WHERE email = 'admin@ai-impress.com';"
```

### Prometheus RAPL Power Metrics

Glance can embed Grafana panels via iframe. For CPU power draw and thermal monitoring:

- **CPU package power:** `rate(node_rapl_package_joules_total[1m])` — returns watts directly (joules/second); do NOT multiply by 1000 (see [[wiki/concepts/prometheus-joules-watts-gotcha]])
- **CPU temperature:** `node_hwmon_temp_celsius{chip=~"coretemp.*", sensor="temp1"}`
- **RAM power** is not exposed by RAPL on most consumer motherboards

Power Cost dashboard was also proxied via NPM: `https://power.ai-impress.com → 192.168.1.225:8091`

### Hot-Reload — No Container Restart Needed

Glance picks up changes to `glance.yml` automatically without restarting the container. This makes iterative dashboard development fast: edit the file, save, and refresh the browser.

This is the default behavior for Glance as of v0.7.x — no polling interval config or signal is needed. The binary watches the config file with inotify.

### Jellyfin Sessions Widget Fix

The sessions widget uses Go templates to render `NowPlayingItem.Name`. If the session has no active item (user is paused or browsing), accessing `.NowPlayingItem.Name` panics the template. Fix:

```yaml
- type: custom-api
  url: http://192.168.1.111:8096/Sessions?apiKey={{ .Env.JELLYFIN_API_KEY }}
  template: |
    {{ range .Response }}
      {{ if .Exists "NowPlayingItem" }}
        {{ .NowPlayingItem.Name }} — {{ .UserName }}
      {{ end }}
    {{ end }}
```

The API key must be moved to `headers:` or `{{ .Env.VARIABLE }}` — embedding it directly in the URL path leaks it in Glance's request logs.

### Prometheus Path Mismatch

When a Prometheus query uses a mount path (e.g., for disk usage), the path must match the actual mount inside the container, not the host path. CT102 mounts the media drive at `/mnt/data`, not `/mnt/media` — using the wrong path returns 0 for the widget.

```yaml
# Wrong — /mnt/media doesn't exist inside CT102
expr: node_filesystem_free_bytes{mountpoint="/mnt/media"}

# Correct
expr: node_filesystem_free_bytes{mountpoint="/mnt/data"}
```

Debug with: `docker exec -it prometheus curl -s 'http://localhost:9090/api/v1/label/mountpoint/values'`

### RSS Feed URL Patterns

Common RSS feed gotchas found in practice:

| Source | Wrong | Correct |
|--------|-------|---------|
| Habr (hub) | `https://habr.com/ru/hub/llm/rss/` | `https://habr.com/ru/hubs/neural_networks/rss/` |
| Habr (hub) | `https://habr.com/ru/hub/llm_tools/rss/` | `https://habr.com/ru/hubs/llm_tools/rss/` |
| deeplearning.ai | any URL | No RSS feed — replaced with Simon Willison's blog (`https://simonwillison.net/atom/everything/`) |

Habr uses `hubs/` (plural) for topic feeds. The singular `hub/` URL returns 404. Verify RSS URLs in a browser before embedding in Glance config.

## Related Concepts

- [[wiki/concepts/prometheus-joules-watts-gotcha]] — RAPL `rate(joules_total)` is already watts; multiplying by 1000 is a display bug
- [[wiki/concepts/adguard-dns-rewrites-homelab]] — AdGuard wildcard DNS rewrites for internal services; not needed for Glance monitor IPs but relevant for browser access
- [[wiki/homelab/_index]] — homelab infrastructure overview; Glance is the dashboard layer

## Sources

- [[daily/2026-04-29.md]] — Glance install session: `$include` page split failure, monitor timeout root cause (Cloudflare IPs), NPM password reset via SQLite bcrypt, RAPL metrics integration, location set to Henley-on-Thames UK
- [[daily/2026-05-03.md]] — Glance dashboard redesign: hot-reload confirmed (no restart needed), Jellyfin sessions widget fix (`.Exists "NowPlayingItem"` + token in headers), Prometheus `/mnt/media` vs `/mnt/data` path mismatch, Habr RSS URL format (`hubs/` plural, not `hub/` singular), deeplearning.ai has no RSS
