Skip to content

Latest commit

ย 

History

201 Commits

Folders and files

NameName
Last commit message
Last commit date
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

Dash# logo

Dash#

A fast, self-hosted homelab dashboard with drag-and-drop tiles and live widgets โ€”
one small container, no database, nothing phoning home.

Version Image size Docker pulls Architectures


๐Ÿค– Built with Claude. This project was designed and implemented with heavy help from Claude (Anthropic's AI). I'm not a professional developer โ€” I wanted a dashboard for my own homelab and used Claude as a pair-programmer to actually get it built.

What is Dash#?

I run a small homelab and wanted one good-looking start page for it โ€” a place to jump to my services and see live status at a glance: system load, Docker containers, DNS filtering, media, network, storage. I tried a few of the dashboards out there, and while they're solid projects, none of them quite gave me the exact mix of integrations and workflow I was after. So I built Dash# to be the dashboard that fit my setup โ€” configurable entirely from the browser, one small container, nothing else to babysit.

Under the hood it's a plain Node.js + Express app that serves a static frontend and proxies every integration server-side: the backend talks to your services with your stored credentials and returns small, normalized JSON. So there's no CORS, your tokens never reach the browser, and there's no database and no build step.

  • ๐Ÿงฉ Design mode โ€” freely place & resize tiles on a grid, add/hide them from a catalog, rename section headings, and spread widgets across multiple pages; the layout is saved server-side
  • ๐ŸŽ›๏ธ Per-tile settings โ€” hover a tile, open โ‹ฏ โ†’ Einstellungen: rename the tile, toggle its building blocks (rings, charts, summaries, posters, columns โ€ฆ) and cap list lengths; stored with the dashboard layout
  • โšก Live widgets โ€” System (via Glances), Docker, AdGuard Home, JDownloader, Plex, UniFi Network & Protect, Nextcloud, Unraid, weather, news, game releases
  • ๐Ÿ“ฐ News tile โ€” aggregates RSS/Atom feeds into one time-sorted list: pick sources from a curated IT/tech/hardware/gaming catalogue (split into German and English) or add your own feed URLs, then filter per tile by language, topic, source or keyword. Readability is adjustable per tile: text and thumbnail size, how many lines a headline and teaser may use, the spacing between headlines and how they are separated (divider line, cards or alternating tint). Clicking a headline opens a window with the teaser, the lead image and a link to the original article โ€” images are fetched and served by Dash# itself, so the browser never talks to the news sites
  • โ–ถ๏ธ YouTube tile โ€” watch your favourite channels without a Google login: add channels by name (search needs the free YouTube Data API key) or by URL/@handle/ID, and Dash# polls each channel's public RSS feed for new uploads โ€” no API quota, nothing to sign into. New videos are flagged, counted in the tile badge and, if you opt in, raised as a browser notification; "mark as seen" sets the watermark per channel. On top of that, already-watched videos are tracked per video โ€” playing one in the window or opening it on YouTube marks it watched (you can also toggle the tick by hand), watched videos drop out of the tile's "new" count and are hidden from the list by default (a per-tile toggle brings them back, greyed out). A per-tile time filter (all / today / last 24 h / 7 days) keeps the feed to just the fresh uploads. An anti-bubble discovery section deliberately surfaces content from outside your subscriptions โ€” a curated pool of diverse, German-language topic channels (no key) plus a slow topic-rotation over your own search terms, biased to German results (key). Smart filters (hide Shorts, min/max duration, keyword allow/block, include or drop live & premieres) act on both the feed and the notifications; channels can be grouped into topic tabs; a dashboard-local watch-later queue lives in config/youtube.json (independent of YouTube). Clicking a video opens a window with a description, a watch-later button and SponsorBlock segment info; an opt-in privacy player (youtube-nocookie) plays it inside the dashboard, otherwise you jump out to YouTube. Thumbnails and avatars are proxied by Dash# itself, so the browser only talks to the dashboard (the embedded player is the sole, opt-in exception)
  • ๐ŸŽฎ Game releases tile โ€” shows the games released on a given day, sourced from IGDB: box art, platform chips, genres and critic score per card. Step through days with โ€น/โ€บ, jump to any date with the date picker, or open the magnifier to look up when a specific game comes out. Clicking a card opens a window with the German description (taken from the Steam store, falling back to German Wikipedia, and marked EN when only the English IGDB text exists), ratings incl. USK/PEGI, developer, publisher, the Steam price (with the discount spelled out; absent for titles without a Steam page), game modes, engine, screenshots and store links. Metadata is translated to German by lookup tables in the module. Games that are in the Xbox Game Pass catalogue right now carry a Game Pass chip next to their platform chips โ€” no credentials needed, the catalogue is public โ€” and the detail window spells out whether that is console, PC or via EA Play. A relevance filter keeps the daily shovelware out (โ‰ˆ 8 of โ‰ˆ 54 titles a day at the default setting); platform, "only with cover" and "only Game Pass" filters are per tile. Images are proxied by Dash# itself, so the browser never talks to IGDB or Steam
  • ๐Ÿ–ฅ๏ธ Unraid suite โ€” eight tiles on the official GraphQL API: VMs (incl. VNC console), Docker containers (start/stop/restart), array & parity (status, capacity, check control), per-disk health, shares, notifications (incl. archive), system info (live CPU/RAM, versions, reboot/shutdown via SSH) and UPS โ€” risky actions locked behind a server-side opt-in
  • ๐ŸŸข Service monitoring โ€” the Service Status tile checks your services and shows online/offline + latency; the check method is picked automatically from what you enter: a URL (http(s)://โ€ฆ) โ†’ HTTP, host:port โ†’ TCP connect, a bare hostname or IP โ†’ ICMP ping
  • โš™๏ธ Configure in the browser โ€” everything under /settings, no config files to hand-edit
  • ๐Ÿ”’ Private by design โ€” no telemetry, no tracking, and no third-party requests from the browser: fonts ship with the image and service icons are proxied and cached by Dash# itself, so the dashboard works fully offline; secrets stay in your mounted config volume
  • ๐Ÿณ One container โ€” node:20-alpine, multi-arch (amd64/arm64), healthcheck, ~48 MB

Note

Dash# is still pre-1.0. It works and is used daily, but expect rough edges and breaking changes before 1.0. Feedback and issues are very welcome.

Screenshot

Coming soon.

Quick start

docker run -d --name dashsharp \
  -p 8085:3000 \
  -v /path/to/appdata/dashsharp:/app/config \
  even512/dashsharp:latest

Open http://<host>:8085, then go to โš™๏ธ โ†’ Integrations to connect your services. On first start a default config is written into the mounted volume automatically.

Prefer Compose? See docker-compose.yml:

docker compose up -d

Install on Unraid

  1. Fetch the template on your Unraid box:
    wget -O /boot/config/plugins/dockerMan/templates-user/my-dashsharp.xml \
      https://raw.githubusercontent.com/even512/dashsharp/main/unraid/dashsharp.xml
  2. Docker โ†’ Add Container โ†’ pick Dash-Sharp from the Template dropdown.
  3. Check the WebUI port (8085) and appdata path (/mnt/user/appdata/dashsharp) โ†’ Apply.

Configuration

Everything is configured from the web UI under Settings โ†’ Integrations and stored in config/secrets.json inside your mounted volume โ€” never baked into the image, never committed.

Integration Needs
System / Docker Glances URL (http://host:61208)
AdGuard Home URL, user, password
JDownloader MyJDownloader email + password (+ optional device name)
Plex URL, X-Plex-Token
UniFi Cloud API key (api.ui.com)
Nextcloud URL, user, app password
Unraid Unraid URL + GraphQL API key
Weather City (Open-Meteo, no key)

Game releases (IGDB)

The tile reads release dates from IGDB, which authenticates through Twitch. Both values are free and take about five minutes to create:

  1. Open https://dev.twitch.tv/console/apps and click Register Your Application
  2. OAuth redirect URL http://localhost, category Application Integration
  3. Copy the Client ID and generate a Client Secret

Enter them under Settings โ†’ Modules โ†’ Game Releases, or as environment variables:

IGDB_CLIENT_ID=
IGDB_CLIENT_SECRET=

There is no monthly request cap; only the rate is limited (4 requests/second), which the module keeps to. Until both values are set, the tile stays in its "not configured" state and the server makes no outbound request at all.

Price

IGDB carries no price data at all, so the price in the detail window comes from the public Steam store API โ€” no extra credentials, and no extra request either: the German description already fetches that same store page via the Steam AppID IGDB hands out in external_games, and the price falls out of the same response. Amounts are in EUR (cc=de) and cached for three hours, while the IGDB detail keeps its 24 hours โ€” a discount that ended hours ago would be worse than no price at all.

The price is therefore Steam's. Console-only titles, releases not yet priced and anything without a Steam page simply show no price row, and a Steam outage costs that one row, never the window. Nothing is estimated or carried over from another store.

node scripts/igdb-check.mjs verifies the credentials and prints what IGDB returns for a given day โ€” useful when the tile looks emptier than expected:

node scripts/igdb-check.mjs                 # today
node scripts/igdb-check.mjs 2026-09-17      # a specific day
node scripts/igdb-check.mjs --search gothic # test the magnifier
node scripts/igdb-check.mjs --game 375232   # detail view incl. translation
node scripts/igdb-check.mjs --gamepass      # Game Pass catalogue (no credentials)

Game Pass chip

Microsoft has no official Game Pass API, but the catalogue behind catalog.gamepass.com and displaycatalog.mp.microsoft.com is public, so this part needs no setup and no credentials. The full catalogue (~900 titles) is fetched at most every six hours, kept in memory only, and served for up to a week if Microsoft is unreachable โ€” an outage costs you a chip, never the tile.

Two limits are deliberate:

  • There is no "coming to Game Pass on X". Every collection the Xbox site itself uses was checked; none of them lists announced titles, and even "day one releases" only contains games that have already shipped. The tile therefore only ever reflects the current catalogue.
  • Titles are matched exactly, after normalising trademarks, punctuation and platform suffixes (Xbox Series X|S, for PC, (Game Preview) โ€ฆ). Edition names are not stripped: the catalogue carries Halo Wars: Definitive Edition but not Halo Wars, and IGDB knows both as separate games โ€” folding them together would put the chip on a game that is not in Game Pass. So a game can be in Game Pass without showing a chip; it will never show one without being in it. --gamepass "Some Game" shows which key a title normalises to.

Unraid

Eight tiles cover the whole Unraid GraphQL API (Unraid 7.2+) โ€” add them from the tile catalog in design mode:

Tile Shows Controls
Unraid VMs live VM status start / stop / pause / resume / reboot / force-stop, VNC/RDP console
Unraid Docker containers, uptime, pending updates start / stop / restart / pause
Unraid Array array state, capacity, parity status & progress array start/stop, parity check start/pause/resume/cancel โš ๏ธ
Unraid Disks per-disk status, temperature, fill level, error counter โ€”
Unraid Shares per-share usage with fill bars โ€”
Unraid Meldungen unread notifications by severity archive one / all
Unraid System live CPU/RAM, uptime, versions, host info reboot / shutdown โš ๏ธ (needs SSH)
Unraid USV UPS charge, runtime, load/power โ€”

Tiles marked โš ๏ธ are locked by default: their actions must be enabled in Settings โ†’ Unraid โ†’ Gefahrenzone first. The lock is enforced server-side (the API endpoints reject gated actions with 403 while it's off), destructive actions additionally ask for confirmation in the browser, and reboot/shutdown also requires the SSH access below (the GraphQL API has no reboot/shutdown mutation). Only enable this if the dashboard is not publicly reachable.

Setup:

  1. Enable the API and create a key on your Unraid box:
    unraid-api apikey --create
    (or generate one from the Unraid web UI). A viewer-scoped key is enough for display; controlling VMs/containers/array needs the matching write scopes.
  2. In Settings โ†’ Unraid, enter your Unraid URL (e.g. http://tower.local) and the API key.

Fields that only newer unraid-api versions expose (live metrics, parity status, UPS power data, container update flags) degrade gracefully on older servers โ€” the tiles simply omit them.

Direct VNC console (no Unraid login)

By default the VNC button opens Unraid's own VM manager, which sits behind the Unraid web login. To jump straight into the console โ€” embedded in the dashboard, with no login โ€” add SSH access in the same settings tab (host defaults to the Unraid URL, user defaults to root; password or private key). Dash# then reads the VM's VNC port over SSH (virsh dumpxml), tunnels the connection, and renders it with a bundled noVNC โ€” talking directly to the VM's QEMU VNC port, bypassing Unraid's login entirely. The console modal also has open-in-new-tab and fullscreen buttons.

Warning

This gives Dash# SSH access to your Unraid box โ€” only enable it on a trusted LAN. If you serve Dash# over HTTPS, browsers block the plain ws:// VNC connection (mixed content); run Dash# over HTTP on your LAN for the embedded console. Per-VM CPU/RAM graphs are not exposed by the GraphQL API and are therefore not shown.

Making the console feel smooth

VNC is inherently choppy for interactive desktops (it re-encodes the screen on the host), so the same lag shows up in Unraid's own noVNC too. Options, from quick to best:

  • Tune noVNC โ€” the console toolbar has a quality selector (Fluid / Balanced / Sharp); Fluid sends less data and feels smoother. Dash# also requests a server-side resize to your window and disables Nagle on the socket to cut input latency.
  • Windows guests โ†’ RDP. Windows VMs get an extra RDP button that downloads a ready-made .rdp file for the native client โ€” dramatically smoother than VNC. The guest IP is found automatically over SSH (virsh domifaddr; install the VirtIO guest tools / QEMU guest agent), or set it per-VM under Settings โ†’ Unraid โ†’ VMs & RDP. Enable Remote Desktop inside Windows first. The Windows type is auto-detected from the Unraid VM template and can be overridden there.
  • Linux guests โ€” switch the VM's graphics from QXL to VirtIO (3D / VirGL) and install the guest drivers; give it more video RAM.
  • Near-native (gaming / GPU) โ€” run Sunshine in the VM and connect with Moonlight for hardware-encoded H.264/HEVC streaming, ideal with GPU passthrough.

Any value can also be set as an environment variable (see .env.example); env vars take precedence over the UI values.

Docker tile shows only running containers? That's controlled by Glances itself, not Dash#. Set all = True under [containers] (or the legacy [docker] section) in glances.conf on your Glances host and restart Glances to include stopped/exited containers too.

System panel shows a container ID instead of your hostname? This happens when the Glances container isn't run with --uts=host, so it reports its own Docker-assigned hostname. Fix it at the source (--uts=host), or set a fixed override under Settings โ†’ Glances โ†’ Hostname.

Data & persistence

All mutable state lives in the mounted /app/config volume:

config/
โ”œโ”€โ”€ services.yaml
โ”œโ”€โ”€ secrets.json
โ”œโ”€โ”€ dashboard-layout.json
โ”œโ”€โ”€ quicklinks.json
โ”œโ”€โ”€ disks.json
โ”œโ”€โ”€ news.json
โ””โ”€โ”€ status.json

services.yaml holds title, search and quicklinks and is auto-created on first run. secrets.json holds your API keys/tokens, set via the UI. The rest track dashboard state: tile order and visibility, quick-access tiles, custom disk names, the selected news feeds, and health-check targets (URL, host:port or a bare host/IP).

Back up that folder and you've backed up everything. Image updates never touch it.

Updating

  • Unraid: Docker tab โ†’ Check for Updates โ†’ apply. Your config is preserved.
  • Compose: docker compose pull && docker compose up -d.

How it works

Dash# is a single Node/Express process. It serves the static frontend from public/ and exposes a set of same-origin /api/* endpoints the browser calls. Each integration is a server-side proxy: the backend contacts Glances/AdGuard/Plex/etc. with your stored credentials and returns a trimmed JSON โ€” no CORS, no tokens in the browser. State is a handful of JSON/YAML files in the config volume; there is no database and no build step.

Security

The dashboard has no built-in authentication โ€” anyone who can reach the port can use it. Keep it on your LAN, or put it behind a VPN or an authenticated reverse proxy. Do not expose port 8085 directly to the internet.

Two things Dash# does do on its own:

  • Host allowlist. Requests are only accepted when the Host header looks like a LAN address (localhost, private IP ranges, .local/.lan/.home/.internal). This blocks DNS rebinding โ€” an attacker domain resolving to your LAN IP would otherwise be same-origin and could trigger the control actions. Behind a reverse proxy with its own hostname, add it to TRUSTED_HOSTS (comma-separated); TRUSTED_HOSTS=* turns the check off.
  • Non-root container. The entrypoint starts as root, adopts the ownership of your mounted /app/config (so it always matches your volume โ€” no permission surprises on existing installs) and drops privileges before starting the app. Override with PUID/PGID if you want a specific user; if the volume turns out not to be writable, it stays root and says so in the log rather than dying. Note that unprivileged ICMP needs sysctl -w net.ipv4.ping_group_range="0 2147483647" on the host; without it, the Service Status tile falls back to a TCP reachability check for bare hostnames (HTTP and host:port targets are unaffected).

Build from source

git clone https://github.com/even512/dashsharp.git
cd dashsharp
cp .env.example .env
npm install
npm run dev

npm run dev starts the app at http://localhost:3000. The .env copy is optional.

Run the checks (syntax, module manifests, core endpoints, host allowlist):

npm test

Build the image yourself: docker build -t dashsharp .

Regenerate the logo/icons after editing logo.svg:

npm i -D sharp && node scripts/render-icon.mjs

Adding a module

Dash# is built so a new integration is two files โ€” no changes to server.js, app.js or index.html, and no build step:

server/modules/<id>.js    fetch, normalize, cache, push
public/modules/<id>.js    render the tile

The backend file declares what it needs and how to get it:

module.exports = {
  id: 'uptimekuma',
  label: 'Uptime Kuma',
  ttl: 30000,
  secrets: [{ key: 'KUMA_URL', label: 'URL' },
            { key: 'KUMA_TOKEN', label: 'API token', masked: true }],
  configured: (get) => !!get('KUMA_URL'),
  async fetch(get, ctx) {
    const d = await ctx.httpJson(`${get('KUMA_URL')}/api/status`);
    return { ok: true, up: d.up, down: d.down };
  },
};

From that, the registry derives the cache slot, the TTL, GET /api/uptimekuma, the SSE push event, the entries in /api/secrets (incl. masking), in-flight de-duplication, the not_configured response and a _stale fallback to the last good data when the upstream is down.

The frontend file registers the tile โ€” size, options, live event and markup in one manifest โ€” and the tile catalog, per-tile settings, push routing and settings entry follow from it.

Both directories carry a README.md with the full contract and a commented _example.js template (files starting with _ are not loaded). npm test validates every manifest, so a typo fails the build instead of silently producing a blank tile.

Contributing

This started as a personal project, so it's still a bit rough around the edges โ€” but issues and PRs are very welcome. Small, focused improvements are the easiest for me to review and land.

License

No license chosen yet (default copyright applies). An OSI license such as MIT may be added later.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages