The admin console is a standalone management surface for a MagicFrog control plane. It lives at /admin on the console web deployment and shares nothing with the workspace: it has its own login gate, its own session storage, and its own admission check. Signing in to the desktop or web workspace never grants access to the admin console.
Access model
Admission is based on the account's explicit role. Accounts carry a role field with a closed set of values: owner or member. Only owner accounts may use the admin console — there is no grant, invite, or escalation path that turns a member into an owner from inside the product, and the first account to register on a deployment does not become an owner implicitly. The owner account is created by the deployment itself at first boot (see Self-hosting).
Enforcement is server-side. Every /v1/admin/* endpoint resolves the caller's identity and requires the owner role at a single centralized guard; a non-owner request fails with 403 regardless of what the UI sends. The console's own checks are presentation only.
The login gate
The /admin entry presents one of four states, derived from the admin session alone:
| State | When | What you see |
|---|---|---|
login |
No admin session | The admin sign-in form |
change-password |
The account still uses its initial deployment password | A forced password-change form; the session is held in memory only until the change succeeds |
forbidden |
Signed in, but the role is not owner (or the session payload is malformed) |
An explicit no-access screen — never a silent downgrade |
ready |
Signed in as an owner with no pending password change |
The admin shell with the six resource faces |
The admin session is stored under its own browser storage key, separate from the account session used by the workspace. A tampered or stale session payload is discarded and treated as signed out.
While the forced password change is pending, the server rejects every business endpoint except login and password change with 403 password_change_required — this applies to any account in that state, not only owners.
Managed resources
The admin shell has six faces. /admin redirects to the first one.
| Face | What it shows | Actions |
|---|---|---|
| Computers | Name, type, source (provisioned / enrolled), status |
Create a provisioned computer on behalf of an account (account ID, OS linux/windows, name, CPU cores, memory, optional image); start and stop provisioned computers; destroy provisioned computers; revoke enrolled machines |
| Enrollments | Account, device, computer, name, state, egress authorization | Revoke an enrollment; grant or revoke the egress grant |
| Plugins | Kind, state, source | Install (source plus kind directory/url/registry), enable, disable, update, uninstall |
| Runs | Runtime owner, location, target computer, egress, terminal state | Cancel a run |
| Forms | Session, state, creation time | Expire a form |
| Audit log | Actor, action, resource, time, summary | Read-only, cursor-paginated (50 entries per page), filterable by resource type |
The egress grant on an enrollment is written as a unit: granting requires the protocol (socks5 or http), the local port, and the remote port together — an incomplete grant is rejected by the server. Revoking takes the authorization away.
Destroy and revoke are different operations
Destroying a computer and revoking one are two separate actions with separate confirmations, and they never share an endpoint or a copy:
- Destroy applies to provisioned computers only — machines created through a provider. It destroys the provider-managed machine.
- Revoke applies to enrolled machines only — real computers registered through the CLI. It withdraws the enrollment and the machine's access; the machine itself, its data, and its running state are untouched. Revoking an enrollment does not shut the machine down or wipe it.
The UI scopes each action to the rows it applies to, and the server rejects the mismatch independently: a destroy against an enrolled machine, or a revoke against a provisioned one, fails.
Audit log
Every admin change writes an audit record with the operator account, the target resource, and the time, committed in the same transaction as the change itself. There is no admin action that bypasses the audit trail. The audit face queries these records with cursor pagination and an optional filter by target resource type (computer, enrollment, plugin, run, or form).
HTTP API
All endpoints below require an authenticated owner session; any other caller gets 403. The admin face authenticates through the regular account endpoints (POST /v1/account/login, POST /v1/account/change-password, POST /v1/account/logout) and then calls the /v1/admin/* surface.
| Method | Path | Purpose |
|---|---|---|
| GET | /v1/admin/audit-log |
Query the audit log (cursor, limit, resourceType) |
| GET | /v1/admin/computers |
List computers |
| POST | /v1/admin/computers |
Create a provisioned computer for an account |
| POST | /v1/admin/computers/:computerId/start |
Start a provisioned computer |
| POST | /v1/admin/computers/:computerId/stop |
Stop a provisioned computer |
| POST | /v1/admin/computers/:computerId/destroy |
Destroy a provisioned computer |
| POST | /v1/admin/computers/:computerId/revoke |
Revoke an enrolled machine |
| GET | /v1/admin/enrollments |
List enrollments |
| POST | /v1/admin/enrollments/:enrollmentId/revoke |
Revoke an enrollment |
| PATCH | /v1/admin/enrollments/:enrollmentId/egress-grant |
Grant or revoke the egress grant |
| POST | /v1/admin/plugins |
Install a plugin |
| GET | /v1/admin/plugins |
List plugins |
| POST | /v1/admin/plugins/:pluginId/enable |
Enable a plugin |
| POST | /v1/admin/plugins/:pluginId/disable |
Disable a plugin |
| POST | /v1/admin/plugins/:pluginId/update |
Update a plugin |
| POST | /v1/admin/plugins/:pluginId/uninstall |
Uninstall a plugin |
| GET | /v1/admin/runs |
List runs |
| POST | /v1/admin/runs/:runId/cancel |
Cancel a run |
| GET | /v1/admin/forms |
List forms |
| POST | /v1/admin/forms/:formId/expire |
Expire a form |
Deployment
The admin console is part of the console web deployment. It authenticates against the control plane origin configured at deploy time via VITE_ADMIN_CONTROL_PLANE_URL; when that variable is unset, the login gate reports the missing configuration instead of guessing an origin. If the console is served from a different origin than the control plane, allow the console origin on the server with magicfrog serve --cors <origin>. See Self-hosting for the control plane deployment.