Platform admin
The Platform section in the sidebar is the operator-level surface — different from per-organisation admin pages.
Visible to users with is_staff = true (Platform staff) or
is_superuser = true (Platform owner). The first user on a fresh
deployment is auto-promoted to both.
The nine surfaces
| Page | What it does | Who can see |
|---|---|---|
| Overview | Cross-org counters (users, active, staff, orgs, projects, annotations) + an activity chart and top-orgs list. The platform owner's "is everything OK?" view. | Platform staff |
| Users | Manage every user across every org. Edit, activate / deactivate, force-verify email, grant / revoke staff, resend invites, delete. | Platform staff |
| Organizations | Create new organisations (with owner + tier), open one, freeze, delete, and the quota request queue with its approve / decline modal. The cross-tenant lever. | Platform staff |
| Roles | Edit role permissions matrix — who can create projects, who can see Data Manager, etc. | Platform staff |
| Audit log | Cross-org audit log — every staff action + sensitive user action. Filterable, CSV export. | Platform staff |
| Notifications | Email delivery backend, per-event email rules, email templates. | Platform staff |
| Localization | Translate the interface using the platform's own annotation + review flow; publish a language live without a rebuild. | Platform staff |
| Feedback | Review queue for what users sent from the in-app Send feedback box. See In-app feedback. | Platform staff |
| Settings | Branding (name, tagline, support email, logo suite), external links, typography, platform policies (incl. the community inbox), and the editable organisation tiers with the default for new organisations. | Platform staff |
What's "staff" vs "platform owner"
Two flags, slightly different scopes:
is_staff= true — sees the entire Platform section. Platform pages gate on either flag, so staff can also do the cross-tenant actions listed above (including deleting an org).is_superuser= true — everythingis_staffcan do, plus Django admin (/admin/) — which is where a few operator knobs still live (see Settings). The admin site lists what runs the platform: users, organisations, memberships, migrations and platform settings. An organisation's projects, tasks, annotations, predictions, model backends and provider connections are not on it, for anyone.
By convention the first user is both. Operators typically promote a
second person to is_staff only (a deputy who can troubleshoot
email + reset passwords but has no Django-admin access).
Promotions happen via Platform → Users → Edit → Grant staff.
Where each page is documented
The Platform section has its own pages spread across this docs section:
- Security → Security model
- Email + DNS → Email delivery
- Health → Health
- Notifications → Notifications & email events
- Feedback → In-app feedback
- Force-verify a user → Mark a user verified
- Operator FAQ → Operator FAQ
- Distribution → Distribution overview
- Extensions → Extending the platform: how a separately installed package adds apps, routes, policies, tabs and navigation.
- Overview, Users, Organizations, Roles, Audit, Localization, Settings → Below on this page.
The remaining surfaces below haven't yet got dedicated deep-dive pages — this overview is the source for now. Open https://github.com/warakacommunity/afriannotate/issues if you'd like one of them expanded.
Overview
Platform → Overview (the section's landing page).
- Counters: total users, active users, staff users, organisations, projects, annotations
- Activity: signups + annotations over a selectable range, with a "vs previous" comparison
- Organizations / projects summary card
- Top orgs by annotations in the selected range
For deep analytics, the per-org metrics pages (Organisation → Metrics) give the same data scoped to one org at a time. The Platform overview is the union.
Users
Platform → Users.
A searchable table of every user in every org. Columns:
| Column | Meaning |
|---|---|
| User | Name + email (+ staff badge). Click Edit to open the detail panel |
| Orgs | Number of orgs the user is a member of |
| Status | Active / Inactive |
| Joined | Signup date |
Row actions: Edit, Resend invite (for accounts still pending setup), Delete. The detail panel adds Activate / Deactivate, Verify email (see Mark a user verified), Grant / Revoke staff, and the user's org memberships.
Organizations
Platform → Organizations.
Create a new org, open one, or delete it. Visible to Platform staff
(is_staff or is_superuser).
- Create: modal asking for name, initial owner (an existing user), and optional quotas (empty = unlimited)
- View: switches you into the org
- Delete: irreversible — deletes the organisation and all its projects (members are notified). Ownership transfer happens from the org's own settings, not here.
Roles
Platform → Roles.
The platform's role matrix. Six roles (Owner / Admin / Manager / Reviewer / Annotator / Guest) with ~30 permissions each. Each cell is a tick / cross.
Editing role permissions affects every org. Operators rarely change the matrix; the defaults come from upstream Label Studio + AfriAnnotate's role audit.
See Roles for the per-role descriptions.
Audit log
Platform → Audit log.
Every staff action + sensitive user action lands here:
- User events: create, activate / deactivate, staff grant / revoke, password set, passkey added / removed / login, HF token set / cleared, consent events
- Org events: create, update, delete, ownership transfer; member invite / add / remove / role change / suspend / unsuspend
- Project events: create, delete; project member add / remove / role change
- Platform events: branding update,
email.send_failed
Each row: action type, target object, actor (email snapshotted so it
survives user deletion), organisation, timestamp, IP, and a
metadata JSON blob with action-specific detail.
Filters: free-text search, action type, organisation, date range. Export CSV downloads the current filter.
The audit log is append-only, enforced in the database. The app
only ever inserts rows (AuditEvent.log(...)), and on PostgreSQL a
row-level trigger (migration core.0028) refuses every UPDATE and
DELETE on the table — including from the ORM, a management shell, or
raw SQL run as the application role. The one change it allows is the
ON DELETE SET NULL cascade when a user or organisation is deleted;
the row stays, with the actor's email snapshotted. SQLite (the desktop
mirror and dev) installs no trigger, because the sync engine
legitimately re-writes mirrored rows there. A database superuser can
still drop the trigger, so for the strictest posture run the app under
a role that does not own the table. Details in
Security model → Audit log.
Retention: indefinite — there is no retention setting; prune via SQL if you must.
Notifications
Platform → Notifications.
Three tabs — Delivery (backend, From address, master email switch, Send test), Rules (per-event Email on/off), and Templates (the email template editor + per-event template assignment). Full reference, including every event and who receives it: Notifications & email events.
Settings
Platform → Settings.
Four tabs:
Branding
- Platform name — propagates to browser title, login page, email subjects, mobile splash
- Tagline — the one-line description below the name
- Support email — surfaced in error pages + email footers as "contact support"
- Logo URL / icon URL — or upload a logo suite (wordmark, square icon, favicon, social image, sign-in page logo); uploads win over URLs
The From address is not set here — it comes from the FROM_EMAIL
env var (see Email delivery).
External links
Docs / API docs / GitHub / Slack / Discord / website / privacy-notice
URLs shown in the sidebar footer. Empty hides the link — except Docs
and API docs, which fall back to the operator's PLATFORM_DOCS_URL /
PLATFORM_API_DOCS_URL env (the reference cloud sets these to
https://afriannotate.waraka.org/docs and https://afriannotate.waraka.org/docs/api/overview, so the
links are on by default); a value saved here overrides the env.
Privacy notice (privacy_url, env PLATFORM_PRIVACY_URL) is the
public notice linked from the sign-up consent line, the app footer and
this settings page. The reference cloud points it at
https://afriannotate.waraka.org/docs/privacy-notice.
Self-hosters: edit that page for your own deployment (operator, region,
processors) before linking it — see Privacy notice.
Typography
Font families, base size / scale, heading weight, and per-element size tiers. Applied at runtime — no rebuild.
Policies
- Allow anyone to create an organisation (off → staff only)
- Idle auto-logout default (minutes) — platform-wide inactivity sign-out (default 30, 0 = off); a project's own idle timeout in data-security settings overrides it
Rate limits (Django admin)
Per-endpoint backoff base + cap for the anonymous auth endpoints,
stored on the singleton PlatformSettings row and read at call
time — no deploy needed. There is no Platform-section tab for them
yet; edit at /admin/core/platformsettings/ (superuser). Four
endpoint groups, each with _base_secs / _cap_secs:
rate_limit_account_status_*(the email-first probe) — default 15 s base, 600 s cap, 8 free attempts, keyed per emailrate_limit_forgot_password_*— 60 s base, 1800 s caprate_limit_resend_verify_*— 60 s base, 1800 s caprate_limit_change_email_*— 60 s base, 1800 s cap
Backoff doubles per attempt within a 24-hour window. Tune up for high-traffic deployments; tune down for internal-only platforms.
Feature flags
Feature flags are not edited in the UI. They ship in
label_studio/feature_flags.json (upstream Label Studio flags) and
are overridden per deployment via env / settings at deploy time.
The SPA reads the resolved set from /api/feature-flags/public/;
the mobile app fetches the same endpoint at launch — see
Mobile architecture.
What's next
- Security model → — auth, transport, storage, isolation
- Email delivery → — SMTP provider + DNS records
- Mark a user verified → — manual override for the verify-email gate
- Operator FAQ → — troubleshooting for hosters
- Distribution overview → — mobile + desktop builds