This guide explains the operator-facing UI page by page, including the important empty states and behavior changes that depend on your settings.
For the full route inventory and visibility conditions, see page-map.md.
- Operator pages live under
/operator/*. - Authentication is a single operator password set during installation.
- Sessions last 30 days.
- Login is rate-limited to 5 attempts per IP per 15 minutes.
- The shared operator layout includes:
- Sidebar navigation:
Dashboard,Instances,Templates,Deployment,Account,System - Mobile top bar and slide-in sidebar
- Theme toggle
- Logout button
- Version badge in the sidebar footer
- Sidebar navigation:
The dashboard has two distinct states.
Visible when there are no instances yet.
What you see:
- A centered welcome screen
- A 3-step onboarding checklist:
Prepare a templateConfigure deploymentCreate your first instance
Behavior details:
- The template step is marked complete only when at least one prepared template version exists.
- Deployment completion is not auto-detected. The dashboard keeps showing deployment as the next recommended step even after you save Deployment settings.
- The dashboard does not show stats or recent activity in onboarding mode.
- If you already have a template and want to create the first instance, go to
/operator/instancesand useNew Instance.
Visible once at least one instance exists.
What you see:
- Summary cards:
Total InstancesActivePausedStorage Used
Recent Activitytable showing the last 10 provisioning log rowsNew Instancebutton
If there is no recent activity yet, the table is replaced with an empty state.
The New Instance modal appears from the active dashboard and the Instances page.
Important gating behavior:
- If no prepared template exists yet, the modal does not open.
- Instead, the UI shows a confirmation dialog that sends the operator to
Templates.
When Filesystem (Local) is selected in Deployment:
- Title:
New Instance - Subtitle: deploy to the filesystem
- Required field:
Folder Name - Optional fields:
NameEmail
- The modal shows the current instances root path as a prefix before the folder name input.
- Folder names are limited to lowercase letters, numbers, and hyphens.
- If
Emailis left empty, the operator email is used as the fallback value.
When Nginx, Forge, cPanel, or Plesk is selected:
- Required field:
Subdomain - The modal shows
.{base_domain}as the suffix - A live preview updates as you type
- Optional fields remain
NameandEmail - Subdomains are limited to lowercase letters, numbers, and hyphens.
- If
Emailis left empty, the operator email is used as the fallback value.
On successful submit:
- The instance is created immediately
- The browser redirects to
/operator/instances/{id}
This is the main instance management page.
What you see:
- Header with
New Instance - Search field for name or email
- Status filter dropdown
- Current result count
- Clickable table rows when instances exist
Table columns:
Identifier(shows custom domain as primary label when set, with slug below)NameEmailStatusTypeCreated
Empty states:
No instances yetwhen nothing existsNo matcheswhen filters/search exclude all rows
Implementation note:
- The controller supports a
typefilter in the query string, but the current UI does not expose a type filter control.
This page is the operator view for one instance.
- Instance name
- Status badge with colored dot
- Slug in monospace
- Live URL link when the instance is active
Pauseappears only when status isactiveResumeappears only when status ispausedDeleteis always available
Adapter caveat:
- Nginx pause/resume rewrites the server config into and out of a maintenance response
- The local adapter only logs pause/resume
- Forge, cPanel, and Plesk currently log warnings instead of fully toggling site availability
Rows shown in the card:
IdentifierURLEmailTypeCreatedProvisioned
Shown only when the instance is active or paused. Allows attaching a fully qualified domain (e.g., bakery-anna.com) to the instance.
States:
- No domain set: Input field with "Verify & Add" button. Help text shows the server IP and required A record. If no server IP is configured, a warning links to Deployment settings.
- DNS pending: Amber panel showing the required DNS record. "Re-check DNS" button. "Force add" bypass for Cloudflare-proxied domains.
- Domain active, SSL pending: Domain as clickable link with "VERIFIED" badge and amber "SSL PENDING" badge. "Re-check SSL" button. "Remove" button.
- Domain active, SSL active: Domain as clickable link with "VERIFIED" and green "SSL" badges. "Remove" button.
Behavior details:
- The controller rejects
setDomain()when a domain is already attached. The operator must remove the current domain first, which triggers adapter cleanup before a new one can be added. - Explicit domain removal requires confirmed adapter cleanup. If the adapter fails, the domain stays tracked and the operator sees an error with instructions to retry or resolve manually. This is distinct from instance deletion, which uses best-effort cleanup.
- SSL verification uses a real TLS handshake with
verify_peer=trueand checks that the certificate CN/SAN matches the domain. Self-signed or wrong certificates are not marked as active. - DNS verification checks A/AAAA records against the
server_ipsetting. Force-add bypasses DNS checks for domains behind proxies like Cloudflare.
- Private operator-only notes
- Saved in place with
Save Notes
Shows step-by-step deployment history for the instance.
Columns:
StepStatusDurationErrorTime
If no rows exist yet, the page shows a log empty state instead of the table.
This page has two cards: Prepared Versions and Available ZIPs.
This is the top card.
When no prepared template exists yet:
- The page shows a
No template yetempty state - It explains the 3 steps:
- upload a VoxelSite ZIP
- process it
- activate it
When prepared versions exist:
- Each version row shows:
- version number
- backing directory
- size
- active badge when applicable
- Non-active versions can be:
- activated
- deleted
- The active version cannot be deleted
This is the lower card.
When no ZIP files exist in template/voxelsite/:
- The page shows a
No ZIP files foundempty state
When ZIPs exist:
- Each row shows:
- filename
- size
- modified timestamp
- Available actions:
ProcessDelete
Processing a ZIP:
- extracts it
- reads the internal
VERSIONfile - prepares the template
- activates that version for future instances
This page controls how instances are provisioned, what visitors see, and how the system sends email.
It has three sections.
This section always shows:
Control PaneldropdownTest Connectionbutton
The currently exposed adapter choices are:
Filesystem (Local)Nginx (Direct Config)Laravel ForgecPanel / WHMPleskDirectAdmin
The fields below the dropdown change by adapter.
Shows:
Instances PathInstance Limit
No Base Domain field is shown in the current local-adapter Deployment UI.
All domain-based adapters also show a Server IP field below Instance Limit — the public IP address used for custom domain DNS verification. Comma-separate for IPv4 + IPv6.
Shows:
Conf DirectoryReload CommandSSL CertificateSSL KeyBase DomainInstance Limit
Shows:
API TokenServer IDBase DomainInstance Limit
Shows:
WHM HostnameWHM UsernameAPI TokenBase DomainInstance Limit
Shows:
HostnameAPI KeyBase DomainInstance Limit
Behavior details:
Test Connectiontests the values currently visible in the form, even if they are not saved yet.- Switching between local and domain adapters changes whether the page shows a shared
Base Domainblock or the simplified local instance-limit field.
This section controls the public-facing root URL.
Fields:
Show landing pageAccept signups
Behavior matrix:
| Show landing page | Accept signups | Result |
|---|---|---|
| Off | Off or On | / redirects to /operator/login. The signups toggle is visually disabled. |
| On | Off | / shows the landing page, but signup CTAs switch to a disabled/coming-soon state and /signup shows a Coming soon message. |
| On | On | / shows the landing page and /signup shows the public signup form. |
This section controls email behavior.
Fields:
Email DriverSend Test Email
When Email Driver is SMTP, extra fields appear:
SMTP HostPortUsernamePasswordFrom AddressFrom Name
When Email Driver is Log to file or Disabled, the SMTP fields stay hidden.
This page has two cards.
Shows:
Email
This address receives failure alerts and system notifications.
Shows:
Current PasswordNew Password
Behavior details:
- Leave both fields empty to keep the current password
- A new password must be at least 8 characters
- The current password is required only when changing the password
This page has four sections.
Shows:
VoxelSwarmPHPSQLiteDatabase
Shows:
- Current app version badge
- Git availability state
Pull Latestwhen the app is running inside a Git repository
If Git is available:
- Pull output is shown inline after the action runs
If Git is not available:
- The page shows a note that the installation is not a Git repository
When no .log files exist yet:
- The page shows a
No log files yetempty state
When logs exist:
- A table appears with:
FileSizeModified- row actions
- Row actions:
- download
- delete
Delete Allappears only when at least one log file exists
Two destructive actions are available:
Refresh Installation- Deletes instances, logs, ZIPs, and processed templates
- Keeps the account and settings
Reset Installation- Wipes the database and files
- Sends the app back to the install wizard
Both actions require password confirmation in a modal before execution.
These pages are not part of the operator layout. The landing page and signup flow are controlled from Deployment > Public Site.
Visible only when Show landing page is enabled.
Main content:
- Hero
- marketing sections
- CTA buttons
Operatorlink
The CTA behavior depends on Accept signups.
Visible only when the public site is enabled.
States:
Create your workspaceform when signups are enabledComing soonmessage when signups are disabled
Visible after a public signup creates an instance.
States:
- in-progress polling state
- success state with workspace CTA
- failure state
- not-found state
Log files are written to storage/logs/ with daily rotation by channel:
provision-YYYY-MM-DD.logadapter-YYYY-MM-DD.logswarm-YYYY-MM-DD.logmail-YYYY-MM-DD.log
See troubleshooting.md for channel-by-channel details.