Admin portal - Tenant management¶
- Status: Draft
- Prefix:
TEN - Module: admin
- Feature: tenant-management
- Related: Admin portal, Operator access, Tenancy lifecycle, Provisioning monitor, Backup, restore, and reset
Purpose¶
Operators can find any tenant in seconds, understand its state at a glance, and change its lifecycle state safely: suspend it, bring it back, or delete it.
Users and permissions¶
All roles can view. Owner and Operator can edit metadata, suspend, and reactivate. Only Owner can schedule or cancel deletion. See the permission matrix.
Tenant states¶
| State | Meaning | Tenant users can sign in |
|---|---|---|
Provisioning |
Sign-up done, provisioning run in progress | No |
Provisioning failed |
The last provisioning run failed and needs an operator | No |
Active |
Normal use | Yes |
Maintenance |
An operator action (restore, reset) is running | No, users see a maintenance page |
Suspended |
Access blocked by an operator; data kept | No, users see a suspended page with a support contact |
Pending deletion |
Deletion scheduled; grace period running; data kept | No |
Deleted |
Database, files, and backups removed; registry record kept for history | No |
Transitions:
| From | Event | To |
|---|---|---|
Provisioning |
Run succeeds | Active |
Provisioning |
Run fails | Provisioning failed |
Provisioning failed |
Retry succeeds | Active |
Active |
Suspend | Suspended |
Suspended |
Reactivate | Active |
Active or Suspended |
Restore or reset starts | Maintenance |
Maintenance |
Restore or reset ends (success or failure) | State before maintenance |
Active, Suspended, or Provisioning failed |
Schedule deletion | Pending deletion |
Pending deletion |
Cancel deletion | State before deletion was scheduled |
Pending deletion |
Grace period ends | Deleted |
Requirements¶
| ID | Priority | Requirement |
|---|---|---|
| TEN-01 | Must | Tenant list columns, in order: checkbox, Tenant Workspace (initials avatar, semibold display name, muted monospace {subdomain}.bizos.cloud with copy and link-out), Plan (colored pill or neutral "No plan"), Storage, Users, State (badge with colored dot), Health (dot + Healthy / Near Quota / Inactive; omit redundant Inactive when state already implies it), Created (relative time with absolute date in a tooltip), Action. Action is a ⋯ menu: View details, Edit, Suspend/Resume (shell until wired), Open workspace, Delete (red, subdomain confirmation). Deleted rows are dimmed with actions disabled. |
| TEN-02 | Must | Search by display name, subdomain, or owner email (partial, case-insensitive), debounced ~300ms. Toolbar filters for state, plan, and health apply immediately as chips with Clear all. Four summary cards (Total, Active, Suspended/Maintenance, Pending deletion) filter the table when clicked. Sort by column headers. Page sizes 10 / 25 / 50 with "Showing X–Y of N". Row click opens a side sheet with tenant summary; full detail remains at /tenants/:id. Tenants in Provisioning or Provisioning failed are not listed. |
| TEN-03 | Must | Tenant detail Overview shows the TEN-01 fields plus tenant ID, legal name, owner name, owner phone, database name, state reason, state changed at, internal notes, and updated at. Legal name, owner phone, state reason, and internal notes are shown empty when they have no value. It does not show schema version or last backup time and result (TEN-14). |
| TEN-04 | Must | Owner and Operator can edit display name, owner name, owner email, owner phone, and internal notes on tenant detail. Tenant ID and database name are read-only. Subdomain, plan, and state are changed from the list quick edit (TEN-15). |
| TEN-05 | Must | Suspend an Active tenant with a required reason. Within 1 minute, all tenant sessions end, sign-in shows the suspended page, and API keys and inbound webhooks are rejected. Data is untouched. |
| TEN-06 | Must | Reactivate a Suspended tenant with a required reason. Users can sign in again; API keys work again. |
| TEN-07 | Must | Tenant state is enforced by the platform on every tenant request (web, API, webhooks, scheduled jobs), not only at sign-in. Scheduled jobs for non-Active tenants are skipped and logged. |
| TEN-08 | Should | Owner can schedule deletion with a reason and by typing the tenant subdomain to confirm. The tenant moves to Pending deletion for a grace period (default 30 days), and the tenant owner is emailed. |
| TEN-09 | Should | Owner can cancel a pending deletion before the grace period ends; the tenant returns to its previous state. |
| TEN-10 | Should | When the grace period ends, the platform deletes the tenant database, files, and backups, sets the state to Deleted, keeps the registry row and audit history, and releases the subdomain only after a further hold period (default 90 days). |
| TEN-11 | Should | Export the tenant list for the current filter to CSV (audited). |
| TEN-12 | Could | Tenant detail shows usage trends: active users, records, storage, and API calls per day for the last 30 days. |
| TEN-13 | Must | The Storage column shows a slim progress bar for usage percent (capped at 100%), plus used / total in GB (one decimal), with a tooltip for exact values. Values come from tenants.storage_used_bytes and tenants.storage_max_bytes. Percent is used ÷ max when max > 0. Bar colors: green below 60%, amber from 60% up to and including 85%, red above 85%. When used or max is missing, show "—" with an empty bar. Plan name comes from plans through tenants.plan_id; unset plans show a neutral "No plan" badge. User count shows the stored number (or 0) with a small user icon. |
| TEN-14 | Must | When schema and backup data exist, Overview also shows schema version and last backup time and result. Schema version is defined in System health (HLT-09). Last backup time and result come from Backup, restore, and reset. |
| TEN-15 | Must | The list Edit button opens a quick edit for display name, owner email, subdomain, plan, state, and storage maximum (5, 10, 20, 35, or 50 GB). Saving updates that tenant. A storage maximum below the bytes already used is rejected. Reserved subdomains admin, api, app, www, status, and mail are rejected. |
| TEN-16 | Must | A development server inserts 50 fixed sample tenants into the control-plane tenants table. The rows cover every state, every plan plus at least one tenant with no plan, storage that is blank and storage under 50%, from 50% to under 80%, and 80% or more, a user count from 1 to 40, and created dates on more than 20 different days. Owner emails use @example.com. Rows already stored under those ids are left unchanged, except a blank user count is filled from the sample. Any other tenant with a blank user count gets a fixed sample count from 1 to 40, no higher than its user maximum; its other fields are left unchanged. Production does not insert or fill anything. |
| TEN-17 | Must | The list delete button asks for the tenant subdomain. When it matches, the tenant row, its backup schedule and backup runs, and the users bound to that tenant together with their sessions are deleted, and the row leaves the list. A different subdomain does not delete anything. Users with no tenant id are left in place. The operator audit entry for the deletion is kept. |
| TEN-18 | Must | Tenant detail lists the users whose tenant_id is that tenant: name, email, role, and status. A tenant with no users shows an empty table. Users with no tenant id are not listed. |
| TEN-19 | Must | Tenant detail has an Add user button that opens a form for full name, email, and password. Saving creates an active user with role user on that tenant and increases the user count by 1. When the tenant user count is already at its user maximum, saving is rejected. A tenant with no user maximum is not blocked. |
Screens¶
Tenancy (/tenancy). Header: title "Tenancy", subtitle, primary "+ New tenant" (shell — tenants are created by sign-up; button shows "Not available yet"), and secondary Export (client CSV of the filtered list). Four summary cards sit above the table, each tinted by meaning (card, icon, label, count, and hint): sky for Total, emerald for Active, amber for Suspended/Maintenance, purple for Pending deletion. Toolbar: debounced search, State / Plan / Health filters as chips with Clear all, and a comfortable/compact density toggle. Table columns and actions follow TEN-01; storage follows TEN-13. Bulk selection shows a bar whose Suspend / Resume / Delete actions are shells until wired. Loading uses skeletons; empty state offers Clear filters; error state offers Retry. /tenants and /crm-oversight open this page. Tenant detail stays at /tenants/:id. Row click opens a side sheet; "Full details" navigates to the detail page. Delete still confirms with the subdomain (TEN-17).
Tenant detail. Header: display name, subdomain (link that opens the tenant sign-in page in a new tab), state badge, and actions allowed for the current state and role. Tabs:
- The tenant name opens that tenant's detail page at
/tenants/:id. Overview is a horizontal row: Tenant Information, Owner, Status, then a storage donut, followed by a row that is about 70% a user table (name, email, role, status, and edit or delete) and 30% Internal notes (TEN-03, TEN-18). The user table lists accounts whose tenant id is that tenant. An empty list shows "No users." Tenant ID is only in the header. The Status column does not show Created or Updated at. Legal name, owner phone, state reason, and internal notes are blank when unset. - Provisioning: runs for this tenant (see PRV).
- Backups: backups, restore, reset (see BKP).
- Activity: activity log filtered to this tenant (see ACC).
Action dialogs. Suspend, reactivate, schedule deletion, cancel deletion. Each states the consequence, asks for a reason, and for deletion asks the operator to type the subdomain.
Business rules¶
- Allowed actions by state:
| State | Suspend | Reactivate | Schedule deletion | Cancel deletion | Restore / reset |
|---|---|---|---|---|---|
| Provisioning | |||||
| Provisioning failed | ✓ | ||||
| Active | ✓ | ✓ | ✓ | ||
| Maintenance | |||||
| Suspended | ✓ | ✓ | ✓ | ||
| Pending deletion | ✓ | ||||
| Deleted |
- Suspension for non-payment will be triggered by the Billing part, using the same state and the same audit rules.
- Development inserts any of the 50 sample tenants whose ids are not already stored. It does not change other tenants. Production does not insert them.
Acceptance criteria¶
- TEN-01: The tenant list shows checkbox, Tenant Workspace (avatar + name + subdomain), Plan (or No plan), Storage, Users, State, Health, Created, and a ⋯ Action menu with View details, Edit, and Delete.
- TEN-02: Debounced search matches name, subdomain, or owner email. State / Plan / Health chips and summary cards filter immediately. Provisioning rows are hidden. Row click opens a side sheet.
- TEN-13: Storage bar uses green below 60%, amber through 85%, red above; missing storage shows "—"; unset plans show "No plan".
- TEN-03: Overview shows tenant ID, legal name, owner name, owner phone, database name, state reason, state changed at, and updated at, in addition to the list fields. An empty legal name, owner phone, or state reason is shown as "—". Overview does not show schema version or last backup time and result.
- TEN-04: Tenant ID and database name cannot be changed through the UI or the API.
- TEN-15: Edit opens name, email, subdomain, plan, state, and a storage maximum of 5, 10, 20, 35, or 50 GB. A maximum below the storage already used is rejected.
- TEN-16: Development exposes 50 sample tenants covering every state, every plan, a tenant with no plan, each storage band, and a user count from 1 to 40. Production does not insert them. Sample ids that are already stored are not inserted again. A stored sample with a blank user count is filled. Any other tenant with a blank user count gets a stable count from 1 to 40, capped at its user maximum.
- TEN-17: Given the delete button, when the typed subdomain does not match, then the tenant stays. Given the matching subdomain, when they confirm, then the tenant, its backups, and the users bound to it are gone and the row leaves the list.
- TEN-18: Given a tenant detail page, when it opens, then the users stored for that tenant id are listed with name, email, role, and status. Given a tenant with no users, then the table is empty.
- TEN-19: Given Add user, when the form is saved, then an active user with role
useris stored for that tenant and the user count increases by 1. Given the tenant is already at its user maximum, then saving is rejected. - TEN-05: After suspension, a signed-in tenant user is signed out at their next request, a new sign-in shows the suspended page, and an API call with a valid key is rejected.
- TEN-05: Tenant data is identical before and after a suspend/reactivate cycle.
- TEN-06: After reactivation, the same user and API key work again without any other change.
- TEN-07: A scheduled job for a suspended tenant does not run and leaves a log entry.
- TEN-08: Scheduling deletion without typing the exact subdomain is not possible.
- TEN-09: Cancelling a pending deletion of a previously suspended tenant returns it to
Suspended, notActive. - TEN-10: After the grace period, the tenant database and files no longer exist, the tenant row shows
Deleted, and the subdomain cannot be taken by a new sign-up during the hold period. - Actions not allowed for the current state or role are hidden in the UI and rejected by the server.
Not yet implemented¶
- TEN-14
- List Suspend/Resume and bulk actions (UI shells only until dedicated APIs land)
- Operator-created tenants from "+ New tenant" (shell; creation remains sign-up/provisioning)
Out of scope¶
- Changing a tenant's subdomain.
- Merging or splitting tenants.
- Viewing or editing tenant business data.
- Creating a tenant by hand; tenants are created by sign-up and provisioning. Recovery is through retry (see PRV).
Open questions¶
- Grace period before deletion (30 days proposed) and subdomain hold period (90 days proposed): confirm with the product owner and the terms of service.
- Should a customer be able to request deletion from inside the tenant, which then appears here for approval?
- Do we need a "read-only" state (sign-in allowed, no writes) for overdue accounts, in addition to
Suspended? - Support access: do operators ever need to see tenant data to help a customer? If yes, it needs a separate spec with tenant consent, time limits, and audit.