All jobs

Build an Admin Page in a Phoenix App

Yes. An agent adds an admin-only LiveView of listener counts per profile, with tests and browser screenshots, while teammates follow the run.

Run this on RushIn a Project that has your repository.

What the agent does

The agent starts from the app's own auth, not a new login. In a Phoenix app shaped like fly-apps/live_beats, mix.exs sets elixir ~> 1.12 and phoenix ~> 1.7.1, with phoenix_live_view pulled from GitHub, ecto_sql ~> 3.11 and postgrex. The test alias creates the database, migrates, then runs the tests. LiveBeats.Accounts.admin?/1 is true when user.email is in the :admin_emails config. That is a different list from :files, :admin_usernames, which runtime.exs sets to chrismccord and mrkurt and which MediaLibrary uses to skip those usernames. LiveBeatsWeb.UserAuth already has on_mount :current_user and :ensure_authenticated, and a plug require_authenticated_admin that redirects other users to / with the flash You must be logged into access that page. The router never pipes a live route through that plug. Live sessions today are :default and :authenticated.

Listener counts already exist on a profile, which is the room this app has. LiveBeatsWeb.Presence.track_profile_user tracks the current user on proxy: joined to the topic active_profiles:{user_id}. list_profile_users reads that topic. ProfileLive.assign_presences keeps presences_count, but presence_ids stops growing at @max_presences 20, and listening_now renders Listening now with the capped count plus a more line. An admin page that summed one LiveView's presence_ids would undercount. The agent adds a LiveView that lists profiles and shows Presence.list_profile_users for each, using the same topic helper, and it mounts only for admins.

The page follows the router and the tests that already exist. The agent adds an on_mount, for example :ensure_admin, next to the two hooks in user_auth.ex, halting with a redirect when Accounts.admin?/1 is false, and a live_session in router.ex that uses it. Layout stays LiveBeatsWeb.Layouts and the Nav on_mount the other sessions use. Tests follow test/live_beats_web/controllers/user_auth_test.exs and test/live_beats_web/live/profile_live_test.exs: LiveBeatsWeb.ConnCase, user_fixture, log_in_user, and live/3. One test signs in an admin and sees the counts. One signs in a normal user and asserts the redirect. The test tree today is those two files plus settings_live_test, the controller tests, media_library_test, accounts_test and mp3_stat_test. config/dev.exs serves http on port 4000. The agent runs mix test, then opens that port in the sandbox browser and takes screenshots of the admin page and of the non-admin redirect.

Teammates watch the same session while this happens. The live stream is GET /o/{org}/sessions/{id}/events. When a turn finishes, status leaves running and the sandbox parks. A teammate who can call the API sends POST /o/{org}/sessions/{id}/messages with a follow-up, for example to use the existing table component or to keep the route under /admin. A message during the turn is 409 TURN_ACTIVE. The agent asks before adding a dependency, and the request says not to add one. Screenshots come back as artifacts on the session, each with a name, a mime type, and a url.

Start it with one API call

POST https://api.prix.dev/o/{org}/sessions with this body:

{
  "agent": "codex",
  "input": "Build an admin page that shows live listener counts per profile, using LiveBeatsWeb.Presence and the topic active_profiles, available only to admins as LiveBeats.Accounts.admin?/1 defines them (the :admin_emails config, not :admin_usernames). Add an on_mount hook beside the ones in lib/live_beats_web/controllers/user_auth.ex and a live route in lib/live_beats_web/router.ex. Follow the existing layout. Add tests in the ConnCase style of test/live_beats_web/live/profile_live_test.exs, including the non-admin redirect. Run mix test. Take screenshots in the browser at the dev server on port 4000. Open a pull request. Ask before adding any dependency.",
  "resources": [
    {
      "url": "https://github.com/your-org/your-app"
    }
  ]
}
  1. Get an API key. In the console, open Settings, then API keys, and create a key for your org. Send it in the Authorization header as a Bearer token.
  2. Send the request. POST the body in request.json to https://api.prix.dev/o/{org}/sessions, with your repository as the resources URL. The run starts in its own cloud sandbox.
  3. Read what came back. Follow the run on its live stream or wait for the run.completed webhook, then read the result and its trajectory.

What comes back

What comes back is a pull request. The branch adds the LiveView, the on_mount hook, the route, and the tests. The pull request description names the route, says admin means :admin_emails, and points at the screenshots. The session lists those screenshots as artifacts, each with a url that downloads without the key for an hour. GET /o/{org}/sessions/{id} also names the pull request. The transcript has mix test and the browser steps as tool calls with their output.

A reviewer looks at four things before merging. The hook uses Accounts.admin?/1 and does not treat the media-library username list as admins. The count comes from Presence.list_profile_users, not from the capped presence_ids on ProfileLive. The non-admin test expects a redirect, in the style of the existing flash on require_authenticated_admin. And mix.exs has no new dependency. The checks on the pull request are whatever the repository already runs. The session does not mark those checks passed on its own.

Run it on every admin page request

When someone files the request, POST this session, or start it inside the Project with POST /o/{org}/p/{project}/sessions so the run sits with the rest of that Project's sessions. resources is the repository URL. Send an Idempotency-Key if the form can submit twice. Teammates follow GET /o/{org}/sessions/{id}/events and send a follow-up when the turn is idle.

Point the API key's webhook at the channel where the team reads pull requests. run.completed carries session_url and, up to 16 KiB, the final answer with the pull request link. A second admin page is a second session, not a second message on this one, unless you want the same sandbox to keep going. max_concurrent defaults to 4.

The use case this job follows, with its automation and trigger: Steer a run as a team.

Why run it on Rush?

Building this on a laptop means a local Postgres, mix deps.get against the GitHub live_view pin, and a browser only you can see. Teammates hear about the page when the pull request opens. On Rush the same work runs in a sandbox with its own checkout and a browser, and the event stream is how a teammate reads the route choice while the tests run. A follow-up wakes the parked sandbox, which keeps the Mix build and the database the test alias created.

The proof is the pull request plus the command output and the screenshots. mix test is in the transcript, the non-admin test is a file a reviewer can run, and the artifact url is the picture of port 4000, not a description of it. Other admin pages can run in other sandboxes at the same time without sharing your local Phoenix port.

Frequently asked questions

Admins are emails, and the config also lists usernames. Which list does the page use?

Accounts.admin?/1, which checks :admin_emails. :admin_usernames is for the media library, set in config/runtime.exs and read from LiveBeats.config([:files, :admin_usernames]). The pull request should not use that list as the page's auth. The test signs in a user whose email is in the admin list, and a user whose email is not.

Will it add a dependency for the table or the charts?

The request says to ask first, and the page should use the components already in LiveBeatsWeb, including the table in core_components.ex. A new hex package waits for a follow-up that approves it. Until that message, mix.exs stays as the repository has it.

ProfileLive already shows Listening now. Why a new page?

That block is the capped presence list for one profile, with @max_presences of 20, and it is visible to the profile's viewers. The admin page lists every profile's count from Presence.list_profile_users and is behind the admin hook. It does not replace listening_now on the profile.

How does a teammate steer it while the tests run?

They read the live event stream. They send a follow-up only once status leaves running. A message mid-turn is 409 TURN_ACTIVE. The follow-up is POST /o/{org}/sessions/{id}/messages with an input string. The sandbox parks between turns and wakes with its disk, including deps and the test database.

The dev server is on port 4000. Do screenshots require me to open it?

No. The sandbox browser opens the server the agent starts. config/dev.exs binds port 4000, and config/test.exs uses port 4002 for the test endpoint. Screenshots are artifacts on the session. You download them from the artifact url. You do not port-forward the sandbox to your laptop.