Files
warmbly/admin
Matthew Meszaros c886b919ce ci(admin): regenerate pnpm lockfile to match workspace overrides
admin/pnpm-workspace.yaml carries an 'overrides: picomatch: ^4.0.4'
block (added to mirror the dashboard) that was never reflected in
pnpm-lock.yaml — the lockfile was generated before the overrides were
introduced. Admin CI's pnpm install --frozen-lockfile rejected the
mismatch:

  ERR_PNPM_LOCKFILE_CONFIG_MISMATCH  Cannot proceed with the frozen
  installation. The current 'overrides' configuration doesn't match
  the value found in the lockfile.

Ran pnpm install --no-frozen-lockfile to regenerate the lockfile with
the override applied; --frozen-lockfile now passes locally.
2026-05-27 16:34:09 +00:00
..
2026-05-27 16:17:47 +00:00
2026-05-27 16:17:47 +00:00
2026-05-27 16:17:47 +00:00
2026-05-27 16:17:47 +00:00
2026-05-27 16:17:47 +00:00
2026-05-27 16:17:47 +00:00
2026-05-27 16:17:47 +00:00
2026-05-27 16:17:47 +00:00
2026-05-27 16:17:47 +00:00
2026-05-27 16:17:47 +00:00
2026-05-27 16:17:47 +00:00
2026-05-27 16:17:47 +00:00
2026-05-27 16:17:47 +00:00
2026-05-27 16:17:47 +00:00

admin

Warmbly's internal admin control plane. Separate Vite + React app, parallel to web/, that drives the /admin/* endpoints on the same backend.

Why a separate app

The dashboard at web/ is the product surface for customers. The admin app is the surface for the Warmbly team running the platform. Splitting them gives us:

  • a smaller, faster admin bundle (no tiptap, no marketing chrome, no onboarding flow)
  • independent deployment cadence (admin can ship without touching customer code)
  • different origin in production, so a stolen dashboard session can't quietly use admin endpoints
  • a clear visual marker (the amber ADMIN badge + stripe + sidebar tint) so anyone with both tabs open knows which one is which

Both apps share the same backend, the same Bearer-token auth shape, and the same shadcn primitives.

Run it locally

pnpm install
pnpm dev          # boots on http://localhost:5174
pnpm build        # production bundle into ./dist
pnpm typecheck    # tsc -b
pnpm lint

The dev server defaults to port 5174 so it coexists with the dashboard's 5173.

Set up .env.local from .env.example:

cp .env.example .env.local
Variable Purpose
VITE_API_URL Same Warmbly backend the dashboard talks to. Reuses /admin/*.
VITE_ENV_LABEL Drives the Production / Staging / Development pill in the topbar.
VITE_DASHBOARD_URL Used by the "Open dashboard" link in the user menu.

Visual differentiation (do not strip)

This app is intentionally tinted differently from the dashboard. If you find yourself "cleaning up" the amber accent, stop and read this section first.

  • ADMIN badge in the sidebar header and on the login card. Amber pill, ShieldAlert icon. Always visible.
  • 3px stripe along the top of the app shell (admin-stripe utility). First thing the eye lands on.
  • Sidebar tint (--sidebar shifted warm + faint diagonal pattern via admin-sidebar-pattern) so the rail reads as a different surface than the dashboard's near-white sidebar.
  • Amber active-nav state instead of the dashboard's blue.
  • Env pill in the topbar — different colour per environment.
  • Title prefix: index.html ships <title>Admin · Warmbly</title> and the favicon is an amber-bordered shield (public/admin-icon.svg).

These signals are layered on purpose. A single one (e.g. just the badge) is easy to overlook in a tab strip. Stacked, they make it obvious that the user is in the privileged surface.

What's wired vs. stubbed

Real data:

  • Overview — /admin/analytics/overview plus /admin/workers/managed for the fleet card
  • Workers list — /admin/workers/managed
  • Worker detail — /admin/workers/:id/managed, /admin/workers/:id/live-status, /admin/workers/:id/logs, plus the SSH lifecycle mutations (test, install, restart, uninstall)
  • Egresses — wired to /admin/workers/managed re-framed as sending identities (TODO when /admin/egresses exists)
  • Audit Log — /admin/audit-logs
  • Settings (Encryption, Storage, Messaging, Cache, Transports) — /admin/settings/backends with kind filter; renders an "endpoint pending" placeholder when the registry isn't wired yet

Stubs (page exists, no backend wire-up yet):

  • Mailboxes
  • Users
  • Organizations
  • Plans & Billing
  • Warmup pools
  • Campaigns
  • Analytics (cross-platform charts; the Overview page already feeds from the same family of endpoints)

Layout

admin/
├── index.html
├── package.json
├── vite.config.ts
├── tsconfig*.json
├── eslint.config.js
├── components.json          # shadcn config, mirrors web/
├── public/
│   └── admin-icon.svg       # amber-stroked shield favicon
└── src/
    ├── main.tsx             # router + query client + providers
    ├── global.css           # design tokens (mirror of web/) + admin-only tokens
    ├── app/
    │   ├── auth/LoginPage.tsx
    │   ├── dashboard/       # Overview, Workers, Egresses, Audit, stubs
    │   └── settings/        # Encryption/Storage/Messaging/Cache/Transports
    ├── components/
    │   ├── layout/          # AppShell, Sidebar, Topbar, AdminBadge, EnvPill, …
    │   └── ui/              # shadcn primitives copied from web/src/components/ui
    ├── hooks/
    │   └── useMe.ts
    └── lib/
        ├── env.ts
        ├── utils.ts
        ├── auth/storage.ts  # Bearer token persistence
        └── api/
            ├── client.ts    # axios instance + Request<T>
            ├── client/
            │   ├── auth/    # login, getMe, logout
            │   └── admin/   # workers, audit, analytics, settings
            └── models/