Skip to content

Dashboard

The dashboard is a private web page that lists every link with its metadata and lets you act on it by clicking — no need to know a link’s #id. It’s the browser counterpart to the Telegram commands.

It’s a React + shadcn/ui single-page app (Vite + Tailwind v4) that lives in its own dashboard-app/ npm project, builds to /app/ in the Worker’s assets, and is served by the same Worker. The page calls a small JSON API the Worker exposes — GET /dashboard/api/links, GET /dashboard/api/meta, POST /dashboard/api/mutate, POST /dashboard/api/action — all behind the same Cloudflare Access gate. The public List is the same SPA in a read-only mode, served from a public GET /list/api/links.

It lives on a custom domain (links.<yourdomain>/dashboard) because Cloudflare Access can only path-protect a real zone, not a *.workers.dev route. The Telegram webhook and the public List stay on the workers.dev URL, untouched.

Each link is a card with inline controls: toggle ⭐ favorite, toggle 🌐 share (adds it to your public List), edit its category and collection in place, ✏️ edit its title and summary by hand (handy when a site blocks automatic enrichment — the edit also resyncs full-text search and the semantic index), refresh it (re-fetch and re-enrich), and 🗑 delete it behind a confirm dialog (the only action that removes data — it also cleans the search index).

Filter the whole library by category or collection, plus a free-text filter, to narrow down what’s shown. And run library-wide actions from buttons — Recategorize, Organize, and Find duplicates — the same jobs as the /recategorize, /organize, and /dupes commands.

The same app powers /list: it shows only the links you’ve marked 🌐 shareable, grouped by collection, with category badges. No Access gate — it’s your hand-picked public page.

  1. Cloudflare Access at the edge: only your email can load the page (you log in via Access).
  2. Worker JWT check: the Worker independently verifies the Access token, so even the *.workers.dev/dashboard back-door (which Access doesn’t front) is denied.

It fails closed: until the two secrets below are set, /dashboard returns 403 for everyone.

  1. In Cloudflare Zero Trust → Access → Applications, click Add an application → Self-hosted.

  2. Set the application domain to your dashboard host (e.g. links.yourdomain.com) with path /dashboard. Make sure an identity method is enabled — the built-in One-time PIN (email code) is enough.

  3. Add a policy: Action: Allow, Include → Emails → your email address. Save.

  4. Open the application’s settings and copy two values: the Application Audience (AUD) tag and your team domain (https://<team>.cloudflareaccess.com).

  5. Set them as Worker secrets, then they apply live (no redeploy):

    Terminal window
    npx wrangler secret put ACCESS_AUD # the AUD tag
    npx wrangler secret put ACCESS_TEAM_DOMAIN # https://<team>.cloudflareaccess.com

Visit https://links.yourdomain.com/dashboard → Access prompts you to log in → the dashboard loads. Confirm https://<your-worker>.workers.dev/dashboard returns 403 (no Access token), and that /webhook and /list still work.

There’s no Access in local dev, so .dev.vars sets DEV_NO_AUTH=1 to bypass the check and let you open localhost:8787/dashboard. Never set DEV_NO_AUTH in production — it would make the dashboard public.

The SPA ships as built assets, so it has to be built before the Worker serves it. npm run deploy builds the docs site then the dashboard-app (the build:ui script) before running wrangler deploy, so a normal deploy keeps /app/ current — no manual step.