Files
warmbly/docs/content/docs/development/admin-panel.mdx
T
Matthew MeszarosandDevin AI 5cc3cb4e4c feat: preserve the operations documentation table around backfill and mailbox safety notes
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-10-11 14:41:47 +00:00

166 lines
16 KiB
Plaintext

---
title: Admin panel
description: The operator surface on :5174, page by page, with the platform admin bit each one needs.
---
The admin panel is the operator's view of an instance: every worker, user, organization, mailbox and background loop, across every workspace. It is a separate app from the customer dashboard, it holds nothing a customer needs, and every page in it is gated on a [platform admin bit](/development/accounts-and-access/#platform-admins) rather than a workspace role.
## Reaching it
The panel runs on `:5174` in the default stack (`http://localhost:5174` on a stock install, or the host you proxied to it). Sign in with the same account as the dashboard. The first owner holds every admin bit from the moment they claim the instance, and `warmblyctl user create --admin` creates a further account that already holds them. A page the account has no bit for says which bit it needs instead of rendering.
A completed admin sign-in discards previously cached operator data and permissions before storing the new session. An unsuccessful sign-in or a pending email-code/second-factor step does not replace the session.
<Callout type="warn" title="Two-factor authentication is required">
Every admin route refuses a session that did not present a second factor. Turn on 2FA or add a passkey under **Settings > Security** in the dashboard, then sign in again; the panel says so rather than failing silently.
The check is on the session, not on enrolment, so switching 2FA on does not upgrade the session you are already holding. Sign out and back in.
This is deliberate and not configurable. An administrative interface reachable from the internet is the one account where a stolen password should not be enough, and it is what the App Defense Alliance CASA assessment requires of any such interface.
</Callout>
The old paths still redirect, so a bookmark or a link in an older version of these docs lands on the right tab.
## Command palette
`Cmd K` (`Ctrl K` on Linux and Windows) opens the command palette from any page. It jumps to any page by name and searches users, organizations, mailboxes and workers, so a support request that names an email address is one keystroke from the record. The search icon at the top of the sidebar opens it too.
## Navigation
The sidebar lists sections. A section with more than one page shows its pages as tabs in the page header, and every page keeps its own address, so bookmarks and links from these docs still land on the right page. Press `[` (or the panel button at the top of the sidebar) to fold the sidebar down to icons and back.
| Section | Pages |
|---|---|
| **Fleet** | Workers, Placement (the Fleet page below) |
| **Mailboxes** | Mailboxes, Sync |
| **Warmup** | Pool (the Warmup page below), Appeals, Content, Seed panel |
| **Sending** | Campaigns, Sends |
| **Organizations** | Organizations, Limit requests |
| **Growth** | Promo codes, Outreach |
| **Admins** | Admins, Testers |
| **Activity** | Audit log, Live events, Jobs |
Overview, Users, Developer apps, Setup and health, Configuration and Transfers are single pages.
## Theme
The panel is dark by default. The account menu at the bottom of the sidebar (your name and email) has a **Theme** submenu to switch to light or to follow the system setting. The choice is kept per browser.
## Overview
The landing page. Instance-wide counters and their trends, acquisition by channel, and a strip of open instance problems at the top, which is the same list as the Findings tab of [Setup and health](/development/instance-health/).
**Operational monitoring** adds recorded failure evidence, unverified recovery, safety restrictions, missing telemetry and measured backlogs. Positive Kafka lag or JetStream pending/ack-pending/redelivered counts stay visible separately, even when their backend condition is unknown. These are observations, not proof of an outage or provider delivery. Counts are per metric and can overlap; the attention count is a count of signals, not customers or failed messages.
The strip displays coverage gaps, stale sources and unknown/no-recent-evidence sources, rather than hiding a failed request behind an empty panel. **All operational details** opens Setup and health's Operations tab. The snapshot and each investigation link require their own admin permission; hidden sections cannot be recovered by changing tabs. See [monitoring permissions and evidence](/development/instance-health/#operational-monitoring).
Relative ages use your browser's current clock when a snapshot arrives, with periodic updates between requests. A genuinely future-dated observation still has unknown age; it is not fresh evidence of sending or recovery.
## Operations
| Page | What it answers |
|---|---|
| **Workers** | Every worker, its heartbeat, region and the mailboxes placed on it, with a **Has node error** filter for any standing problem a node reports, such as its network blocking outbound port `465`. Detail pages carry stats, the assigned mailbox list, and reassignment |
| **Fleet** | Capacity per worker with utilization, the control loops' decision log (what the placement and rotation loops did and why), and isolated-egress reservations, with reserve and release |
| **Mailboxes** | Every connected mailbox across every workspace, with its state and the worker it sits on |
| **Sync** | The platform copy of each mailbox's backfill progress and fair-use throttle, with clear-throttle and restart-backfill. The budgets it enforces are the four `sync.*` values under [Configuration > Settings](/development/configuration/#settings-stored-in-the-database); the worker-side engine is the sync governor described under [anti-abuse layers](/development/architecture/#anti-abuse-layers) |
| **Warmup** | The pools, the blocked list, and the block and unblock history |
| **Warmup appeals** | Appeals from blocked mailboxes, to approve or reject |
| **Warmup content** | The generated and reviewed [diagnostic conversation bank](/development/warmup-generation/), effective generation controls, library and generation jobs. Changing effective controls requires `manage_settings`; existing library reads still require `view_warmup_pool` |
| **Seed panel** | The instance's placement seed inboxes by provider, adding and removing them, every workspace's recent [placement tests](/guides/placement-tests/) with their results, and an operator test that counts against no allowance |
| **Campaigns** | Every campaign on the instance with its state and owner |
| **Sends** | In-flight reservations no worker has answered, and the reclaimer that resolves them after 30 minutes; dead letters with replay limited to non-sending campaign wakeups; task failures; customer webhook delivery health, with reclaim |
The Sync page's **Stalled** filter, row indicator and summary count only active mailboxes with a running backfill unchanged for over an hour and no active fair-use hold. Inactive and revoked backfill states remain in the **Backfilling** history count, without a current stalled warning. Reactivating a mailbox does not reset its backfill state.
Mailbox diagnostic participation, sender/recipient flags and shared/rolling limits are organization-scoped mailbox fields, not new admin-panel switches. Use the existing authorized mailbox PATCH; no new UI is implied. An operator-reviewed `send_recovery_resolution` binds the current held task/reason and definite repair/provider evidence, never an unknown send. See [send safety](/development/send-safety/).
## Accounts
| Page | What it answers |
|---|---|
| **Users** | Every account, with ban and unban, ban history and rate-limit overrides |
| **Organizations** | Every workspace, with its members, plan, abuse posture, API keys, webhooks and transfer tabs |
| **Limit requests** | Customer requests for a higher allowance, to approve or decline |
| **Developer apps** | Every OAuth app on the instance, its community directory listing, and who may build apps. Suspend an app, revoke its tokens, remove its logo, feature or hide a listing, and block a workspace or a person from building apps |
| **Promo codes** | Every discount code on the instance, who has redeemed each one, and the builder that creates them |
| **Outreach** | Mail an operator sends from the instance to a user, a workspace or an address, and the delivery state of each message sent so far |
| **Admins** | Who holds platform admin bits, and grant and revoke by preset or per bit. This replaces the CLI-only path; `warmblyctl user grant-admin` still works |
### Promo codes
A promo code is created here, never in the Stripe dashboard. Warmbly stores the code itself, validates it when a customer types it on the billing page or at checkout, and mints a matching single-use Stripe coupon for that one redemption. A coupon or promotion code created in Stripe directly is not reachable: the checkout session carries the discount Warmbly resolved, so Stripe never shows its own promo field.
The builder asks six things:
- **Code**, the literal string a customer types. It is uppercased on save and cannot be changed afterwards, because redemptions point at it.
- **Discount**, one of a percentage off, a fixed amount in one currency, or extra free trial days. The kind is fixed once the code exists; its value can still change.
- **How long it lasts**, for the two money kinds: the first invoice only, a number of months, or forever. A months-long discount runs on a clock, not a count of invoices, so a monthly plan gets that many discounted invoices while an annual plan bills once inside the window and has its whole first year discounted. The builder says this back to you as you set it.
- **Eligible plans**, all of them or a chosen set.
- **Redemption limits**, which are two separate numbers. The total cap counts across every workspace on the instance and is off by default; the per-workspace limit is how many times one customer may use it and is 1 by default. Setting the total cap to 1 means one customer in total, which is almost never what a launch offer wants.
- **Schedule**, an optional start and expiry. A start in the future leaves the code scheduled, an expiry is the end of that day in your own timezone, and the Active switch refuses a code at checkout without deleting it.
Deleting a code stops new redemptions and removes it from the list. Workspaces that already redeemed it keep their discount, because the Stripe coupon behind each redemption was minted separately. Disable the code instead when you want the history to stay readable under its name.
Reading the list needs `view_organizations`; creating, editing and deleting need `manage_organizations`.
### Developer apps
Three tabs.
**Apps** lists every OAuth app workspaces have registered, suspended ones first, with its workspace, the person who registered it, its permissions, installs and directory state. The menu on each app:
- **Suspend** needs a reason the developer sees. A suspended app signs nobody in, its access and refresh tokens stop working, its webhook deliveries stop and it leaves the directory. Its owner cannot lift a suspension; **Unsuspend** does.
- **Revoke every token** ends every workspace's grant; each has to authorize the app again.
- **Remove logo** clears a logo that should not be shown, and deletes the image.
- **Block workspace** and **Block person** stop that workspace, or that person in any workspace, from registering and publishing apps. The block can also suspend the apps they already have.
**Directory** is the community directory. A workspace that publishes an [OAuth app](/api/oauth/#publish-to-the-community-directory) creates a link-only listing: it opens from its link and stays out of every workspace's directory. It is listed when you **Feature** it, or on its own once it is installed in 25 workspaces other than the publisher's, each at least two weeks old. Before featuring, check that the install URL, website and description belong to the same product, that the permissions fit what it claims to do, and that it does not borrow another product's name or logo. Any later edit to the listing, or to its app's name, logo, website or permissions, removes the feature. **Hide** takes a listing down everywhere, link included, and needs a note the developer sees; **Restore** returns it to link only.
**Blocked** lists every block with its reason and who set it. **Unblock** lets them build apps again; apps suspended with the block stay suspended until you unsuspend each one.
Reading the tabs needs `view_organizations`; every action needs `manage_organizations`.
## Activity
| Page | What it answers |
|---|---|
| **Live events** | The event bus as it happens |
| **Audit log** | Who did what, from where, across the instance |
| **Jobs** | Every background loop on the instance with its last run, next run and last error, and a **Run now** that the owning process picks up within about 15 seconds. The backend's and the consumer's loops are in the same list. The next run is stored, so a restart or deploy resumes the schedule instead of starting a fresh interval, a job that came due while the process was down runs shortly after boot, and when several processes host the same job each slot runs once |
## Instance
| Page | What it answers |
|---|---|
| **Setup and health** | Three tabs. **Findings** is the [instance health](/development/instance-health/) check list, and the sidebar badge counts its errors and warnings, not operational signals. **Services** holds scoped service reachability probes and the platform mail card. **Operations** holds read-only measurements, coverage, affected counts, evidence age, waits and permission-gated investigation links |
| **Configuration** | Four tabs. **Settings** is the database-backed settings document; **Notifications** is the [operator notification](/development/operator-notifications/) channels; **Environment** is the read-only [configuration reference](/development/configuration/) view of what the backend resolved; **Effective limits** lists the compiled pacing constants |
| **Transfers** | Workspace export and import for any organization, from the operator side, with the same archives as `warmblyctl org export` and `org import`. A whole-instance backup stays `warmblyctl backup` and `restore` |
## Permission per page
| Page | Bit |
|---|---|
| Overview, Live events | none beyond signing in as an admin |
| Workers, Fleet | `view_workers` |
| Mailboxes, Sync, Users, Testers | `view_users` |
| Warmup, Warmup content, Seed panel | `view_warmup_pool` (changing seeds or running a test needs `manage_warmup_bans`) |
| Warmup appeals | `review_appeals` |
| Campaigns, Sends | `view_campaigns` |
| Organizations, Limit requests, Developer apps, Promo codes, Outreach, Transfers | `view_organizations` |
| Admins | `grant_admin_access` |
| Audit log | `view_audit_logs` |
| Jobs, Setup and health | `view_analytics` |
| Configuration | `manage_settings` |
Detail pages need the same bit as the list they open from. Actions inside a page can need more than the page itself: reading an organization's abuse posture or the Outreach list needs `view_organizations`, while changing a posture or sending outreach needs `manage_organizations`, as [accounts and access](/development/accounts-and-access/#workspace-abuse-posture) lists.
On **Admins > Testers**, `manage_testers` allows creating or revoking reviewer accounts and adding [sample data](/development/accounts-and-access/#adding-sample-data) to active dedicated Test workspaces. The confirmation lists what will be added; a seeded row shows its timestamp. It never creates a mailbox or sends email, and it cannot populate a customer workspace that a tester joined.
## See also
- [Accounts and access](/development/accounts-and-access/) for the presets and the shell commands
- [Instance health](/development/instance-health/) for every finding the Setup and health page can show
- [Configuration reference](/development/configuration/) for what the Environment and Settings tabs hold
- [warmblyctl](/development/warmblyctl/) for the operator commands the panel mirrors