The admin UI

A local web dashboard covering every orbit command that operates on a project — migrations, mail, the code generators, sessions and the template cache — started by orbit ui, wired into nothing else.

Shell
$ orbit ui
Starting the admin UI — migrations, mail, routes, sessions, storage.
phporbit listening on http://127.0.0.1:8081 (production mode) — Ctrl-C to stop

A second, self-contained web application for setting up and maintaining a project: how many migrations are pending, what mail failed to send and needs resending, what routes actually compiled, how much is sitting in the session store and the template cache — buttons to act on each, and forms for every orbit make:* generator besides. It reads and writes the same database, the same mail configuration and the same filesystem orbit itself uses; nothing here is a preview.

Why a second application

It would be simpler to add these pages to app/routes.php. That is exactly why they are not there. A page that can run migrations, resend mail and wipe the template cache, merged into the real route table, ships with every deployment unless a developer remembers to strip it back out before going to production — and "remembers to remove it" is not a security boundary.

Instead, Admin\AdminApplication::boot() builds an independent Kernel\Application — its own routes, its own middleware, its own TemplateEngine pointed at templates that ship with the framework in src/Admin/templates rather than living in app/templates. It exists only for the lifetime of the process orbit ui starts. Nothing about running it changes what app/routes.php serves, and nothing about deploying normally exposes it.

There is no login

What stands in for one: orbit ui binds to 127.0.0.1 by default — the same as orbit serve — and warns loudly if told to bind anywhere else. Treat it the way you would a database console left open on your own machine: fine on localhost or over an SSH tunnel, never behind a public host or a port forward. Adding real authentication was deliberately left out rather than done halfway; see "Not built" below.

What it can do

PageShowsActions
OverviewOne tile per area below—
MigrationsEvery migration file, applied or pending, with its batchRun pending, roll back the last batch
Mailmail_log, filterable by statusResend one message, resend every failed message
RoutesThe project's real, compiled route table—
SessionsSession files on diskRemove the expired ones
StorageCompiled templates on disk, and their sizeClear the template cache
GenerateFive forms: class, controller, form, middleware, migrationWrite the file(s); show what the CLI would have printed
ToolsThe configured mail driverPrint a new APP_KEY; send one real test message

Every one of these is also a CLI command — orbit migrate, orbit mail:list / mail:resend, orbit routes, orbit sessions:gc, orbit storage:clear, orbit make:*, orbit key:generate, orbit mail:test — documented on The orbit CLI. The admin UI does not reimplement any of them: the same Migrator, the same PersistingMailer, the same Console\*Maker classes. A resend triggered from a button behaves identically to one triggered from a terminal, because it is the same call.

The routes page is worth being precise about: it reads app/routes.php directly — compiling a throwaway RouteCollection just to list what is in it — rather than serving those routes itself. The admin app's own router never touches them.

Generate

Five pages under /generate, one per orbit make:* command, each a plain form for the same options the CLI flags accept — a name, a lifetime, which fields a form should have. Submitting one calls the exact same ClassMaker, ControllerMaker, FormMaker, MiddlewareMaker or MigrationMaker the CLI uses, on the real project, and shows what it wrote — file paths, and the one or two lines still left to paste into app/routes.php or app/bootstrap.php by hand. Neither the CLI nor this UI edits those files for you; see The orbit CLI for why.

A submission that fails — a reserved word, a name that collides with an existing file, a field name that clashes with a form's own honeypot — redisplays the same form with the message and what was typed, rather than a redirect that would lose both. A successful one clears the name field but keeps the other choices, so generating several related classes with the same lifetime is a few clicks, not a re-selection each time.

Tools

orbit key:generate and orbit mail:test, as two small forms on one page. Generating a key prints it — the same as the CLI — and writes nothing; copying it into .env is still a deliberate, separate step. Sending a test message goes through the same PersistingMailer every controller uses, so it is one more row on the Mail page and, if it fails, resendable from there once whatever was wrong is fixed.

State-changing actions are still forms

Every action — running a migration, resending mail, clearing a cache — is an ordinary <form method="post"> carrying a CSRF token, checked by the same CsrfMiddleware every other phporbit application uses. There is no JavaScript anywhere in this interface, matching the rule the framework's own demo holds itself to: no <script>, no inline style=, no onclick. A destructive-looking action like "roll back last batch" is not gated behind a confirmation dialog, because that would need one — the button's label is the only warning it gets, the same way notes.orbit.php's delete button works in the demo application.

Session cookie

Named orbit_admin_session, not the project's own SESSION_COOKIE. Cookies are scoped by host, not port — orbit serve and orbit ui both default to 127.0.0.1, just on different ports — so sharing a cookie name would let the two silently overwrite each other's session while running side by side, which is a completely ordinary way to run both during development.

Before the first migration

The Migrations page always works — Migrator creates its own bookkeeping table on first use, on any database, before a single project migration has run. The Mail page depends on mail_log existing and says so plainly rather than raising a raw database error:

Shell
$ orbit ui
# visit /mail before running migrations

  The mail_log table does not exist yet.
  Run pending migrations to enable this page.

Not built

  • Authentication. Binding to 127.0.0.1 is the only protection. A real login would need password storage, session handling for a second identity system and a decision about who is allowed to administer what — all reasonable, none of it built here. Put it behind an SSH tunnel or a VPN if it needs to be reached from anywhere but the machine it runs on.
  • A confirmation step before destructive actions. No JavaScript means no dialog; the button label is the whole warning.
  • Editing data. Generate writes code — classes, controllers, migrations — the same files orbit make:* writes. It does not edit rows. Changing a note's title or a user's email is still a job for the database directly or a purpose-built admin resource in the application itself.
  • Concurrency beyond what OrbitServer already has. Connections are served sequentially, the same as orbit serve — fine for one developer administering one project, not a target for real traffic.