mirror of
https://github.com/warmbly/warmbly.git
synced 2026-08-25 16:01:57 +00:00
cf5ca0e649
Replace separate backend settings pages with a single infrastructure view, remove the egresses route, and update admin navigation/readme copy.
132 lines
5.6 KiB
Markdown
132 lines
5.6 KiB
Markdown
# 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
|
|
|
|
```sh
|
|
pnpm install
|
|
pnpm dev # boots on http://localhost:5174
|
|
pnpm build # production bundle into ./dist
|
|
pnpm typecheck # tsc -b
|
|
pnpm lint
|
|
```
|
|
|
|
From the repo root you can also use `make admin`, which is a shortcut for
|
|
`cd admin && pnpm dev`. `make app` does **not** start this app — admin lives
|
|
outside the docker compose stack so it can ship on its own cadence.
|
|
|
|
The dev server defaults to port `5174` so it coexists with the dashboard's `5173`.
|
|
|
|
## First admin (local dev)
|
|
|
|
Admin access is gated by `users.admin_permissions` (bitmask) on the backend.
|
|
Nothing in the codebase seeds the first admin — sign up through the dashboard
|
|
as normal, then promote yourself from the repo root:
|
|
|
|
```sh
|
|
make grant-admin EMAIL=you@example.com # super-admin
|
|
make grant-admin EMAIL=you@example.com ROLE=support # or ops, analyst
|
|
make revoke-admin EMAIL=you@example.com # drop back to 0
|
|
```
|
|
|
|
Role bitmasks mirror `AdminRolePermissions` in
|
|
`internal/models/admin_permission.go`. For one-off permission combinations,
|
|
pass a raw `BITMASK=N` instead of `ROLE`.
|
|
|
|
Once a super-admin exists they can grant the rest through the in-app user
|
|
management screen, which goes through the audited `GrantAdminPermissions`
|
|
path instead of raw SQL.
|
|
|
|
Set up `.env.local` from `.env.example`:
|
|
|
|
```sh
|
|
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`)
|
|
- 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, 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/
|
|
```
|