desktop: embed Navidrome again (needs ND_HTTPHEADERS_FRAMEOPTIONS on the Navidrome host)

This commit is contained in:
Martin Dvořák 2026-09-25 08:36:08 +00:00
commit 19fa915e74
3 changed files with 21 additions and 14 deletions

View file

@ -36,9 +36,10 @@ heatmap**, a gym SBD panel and links to friends' sites.
- **Live service status** — each self-hosted service is health-checked through the site's
own API; Minecraft is checked through mcstatus.io.
- **Embedded live services** — opening a frameable self-hosted service (Llama.cpp/Open WebUI,
Jellyfin) from the desktop shows the real site inside the window (an iframe). Services that
send `X-Frame-Options` / `frame-ancestors` can't be framed, so they keep a link card or their
own app card instead (Navidrome sends `DENY`, Forgejo/GitDXD `SAMEORIGIN`, Discord `DENY`).
Jellyfin, Navidrome) from the desktop shows the real site inside the window (an iframe). A
service must not send `X-Frame-Options`: Navidrome sends `DENY` unless it runs with
`ND_HTTPHEADERS_FRAMEOPTIONS=ALLOWALL`. Forgejo/GitDXD (`SAMEORIGIN`) and Discord (`DENY`)
keep a link card / their own app card instead.
- **Minecraft-style SMP window** — the SMP shortcut opens a join menu modelled on
Minecraft's multiplayer screen, with live status, player count, MOTD and version pulled
from mcstatus.io through the site's own ping API.

View file

@ -270,13 +270,16 @@ list reordering, not free placement. Don't confuse the two.
- Service shortcuts can show the real site in an iframe via
`createWebsiteApp({ embed: true })` (the shortcut's `embed` flag), but the
browser refuses to frame a site that sends `X-Frame-Options` /
`frame-ancestors`, leaving a blank window. `embed` is only set on Llama.cpp
(Open WebUI) and Jellyfin, which allow framing. Navidrome (`DENY`) and
Forgejo/GitDXD (`SAMEORIGIN`) are deliberately **not** embedded: Navidrome
falls back to the link card and GitDXD opens its dedicated app card. To embed
them live you must strip those headers at the edge (Cloudflare "Modify
Response Header" or nginx) and accept the clickjacking risk. Always verify
headers before enabling `embed` on a new service.
`frame-ancestors`, leaving a blank window. `embed` is set on Llama.cpp (Open
WebUI), Jellyfin and Navidrome. Llama.cpp/Jellyfin allow framing; Navidrome
only does when its host runs with `ND_HTTPHEADERS_FRAMEOPTIONS=ALLOWALL`
(Navidrome maps `HTTPHeaders.FrameOptions` to that env var and it overrides
its built-in `FrameDeny`; any unrecognised value is ignored by the browser).
For a scoped, clickjacking-safe alternative, remove the header at the edge
(Cloudflare "Modify Response Header" / nginx) and add
`Content-Security-Policy: frame-ancestors`. Forgejo/GitDXD (`SAMEORIGIN`)
keeps its dedicated app card. Always verify headers before enabling `embed`
on a new service.
- Reduced motion is handled with a near-zero `animation-duration` (not
`animation: none`) so `animationend` still fires and windows unmount.

View file

@ -41,15 +41,18 @@ interface IconEntry {
// `embed: true` renders the real site in an iframe, which the browser refuses
// (blank window) when the server sends X-Frame-Options / frame-ancestors.
// Llama.cpp and Jellyfin allow framing; Navidrome sends `X-Frame-Options: DENY`
// and Forgejo (GitDXD) sends `SAMEORIGIN`, so those two use their link card or
// the dedicated GitDXD app instead.
// Llama.cpp and Jellyfin allow framing out of the box. Navidrome sends
// `X-Frame-Options: DENY` unless it is started with
// `ND_HTTPHEADERS_FRAMEOPTIONS=ALLOWALL`, which replaces that header with an
// unrecognised value the browser ignores (that env var is set on the Navidrome
// host — without it this window is blank again). Forgejo (GitDXD) sends
// `SAMEORIGIN`, so it keeps its dedicated app card.
const SERVICE_SHORTCUTS: Shortcut[] = [
{ id: "svc-proxmox", icon: "🖥️", label: "Proxmox", appId: "alacritty", color: "#1257a0", desc: "Server Management" },
{ id: "svc-ai", icon: "🦾", label: "Llama.cpp", url: "https://ai.dvorakmortonxd.dev/", color: "#157347", desc: "Local AI Server", embed: true },
{ id: "svc-jellyfin", icon: "🎬", label: "Jellyfin", url: "https://jelly.dvorakmortonxd.dev/", color: "#6a4fb3", desc: "Local Media Server", embed: true },
{ id: "svc-smp", icon: "🎮", label: "SMP", appId: "smp", color: "#c0392b", desc: "My Survival Minecraft Server" },
{ id: "svc-navidrome", icon: "🎸", label: "Navidrome", url: "https://navidrome.dvorakmortonxd.dev/", color: "#b3005f", desc: "Music Streaming" },
{ id: "svc-navidrome", icon: "🎸", label: "Navidrome", url: "https://navidrome.dvorakmortonxd.dev/", color: "#b3005f", desc: "Music Streaming", embed: true },
{ id: "svc-gitdxd", icon: "🐙", label: "GitDXD", appId: "gitdxd", color: "#6a4fb3", desc: "Best Git Hosting OAT" },
];