Rindle ships a mountable, host-authenticated admin console in the rindle
package — Rindle.Admin.Router.rindle_admin/2. It is the operator UI for the
media lifecycle: assets, upload sessions, variants/jobs, runtime health, and a
small set of destructive and repair actions. The console follows the
LiveDashboard / Oban Web pattern: you call one macro from an authenticated
router scope and it expands to direct LiveView routes inside a live_session.
Adopters who do not want the console pay nothing.
phoenix_live_viewis an optional dependency; without it the console modules compile away cleanly and Rindle keeps its zero-cost posture. The console is the only new public surface in this milestone — it reuses existing lifecycle facade verbs and adds no new lifecycle semantics.
This guide covers:
- What the console is, and when to mount it
- The optional
phoenix_live_viewdependency - Mounting with
Rindle.Admin.Router.rindle_admin/2 - Authentication and the production refusal rule
- The eight console pages
- Operator actions (destructive UX, typed confirmation, reused facade verbs)
- Self-contained assets and CSP / socket options
- Optional-dependency compile-away behavior
- Trying it locally via the Cohort demo at
/admin/rindle
1. What It Is, And When To Mount It
Rindle Admin is host-authenticated and library-owned. The host application
owns the browser pipeline, the auth pipeline, the LiveView :on_mount hooks,
the route scope, and the current-user / actor assigns. Rindle owns only the
console route expansion, the read query boundary, and the self-contained static
assets. There is no Rindle-managed login, no Rindle-owned session, and no
bypass of your existing authorization — the console is exactly as protected as
the router scope you mount it inside.
Mount it when you want an in-app operator view of Rindle's lifecycle state and a
guarded surface for owner erasure, variant regeneration, and lifecycle repair.
You do not need it for normal media flows; the public Rindle facade and the
mix rindle.* tasks remain the canonical programmatic and CLI surfaces.
2. Add The Optional Dependency
The console renders with Phoenix LiveView. Add phoenix_live_view to your host
application if it is not already present:
# mix.exs
defp deps do
[
# ... your existing deps ...
{:rindle, "~> 0.3"},
{:phoenix_live_view, "~> 1.0"}
]
endphoenix_live_view is optional from Rindle's perspective: the admin router and
LiveViews are guarded behind Code.ensure_loaded?/1 on
Phoenix.LiveView, Phoenix.Router, and Plug.Static. If those are not
available, the admin modules are not defined and Rindle still compiles — see
section 8.
3. Mount The Console
Call Rindle.Admin.Router.rindle_admin/2 from an authenticated scope in your
host router. The macro expands to direct LiveView routes (not a forward-only
plug), so it must be called inside scope, after a pipe_through that applies
your browser and auth pipelines.
# lib/my_app_web/router.ex
scope "/admin", MyAppWeb do
pipe_through [:browser, :require_admin]
rindle_admin "/rindle",
on_mount: [MyAppWeb.AdminLiveAuth],
as: :rindle_admin,
home_path: "/admin",
live_socket_path: "/live",
transport: "websocket",
csp_nonce_assign_key: %{
img: :img_csp_nonce,
style: :style_csp_nonce,
script: :script_csp_nonce
}
endThis mounts the console at /admin/rindle. The :require_admin plug and the
MyAppWeb.AdminLiveAuth :on_mount hook are yours — Rindle does not provide
them, and that is intentional.
4. Authentication And The Production Refusal Rule
The host owns auth. To make misconfiguration loud rather than silent, the macro refuses to mount an unguarded console in production. A production mount must supply one of:
- a non-empty
:on_mountlist (your LiveView session/auth verification), or - an explicit
auth_guarded?: trueacknowledgement (you assert the surrounding pipeline already enforces auth).
If neither is present in :prod, the macro raises at compile time with a clear
message rather than booting an unauthenticated privileged console.
There is a dev/test escape hatch, allow_unauthenticated?: true, for examples,
CI fixtures, and local previews. It is rejected in production — passing it
in :prod raises. Treat it as a convenience for the Cohort demo and your own
local exploration, never as a deployment posture.
In short: do not deploy the console under a bare pipe_through :browser scope
with no :on_mount guard and no auth_guarded?: true. Production will not let
you.
5. Pages
The mount expands to eight routes, relative to the mount path (/admin/rindle
in the example above):
| Route | LiveView | Purpose |
|---|---|---|
/ | HomeLive | Console home / lifecycle overview |
/assets | AssetsLive (index) | Browse media assets |
/assets/:id | AssetsLive (show) | Asset detail, variants, attachments |
/upload-sessions | UploadSessionsLive (index) | Upload sessions list |
/upload-sessions/:id | UploadSessionsLive (show) | Upload session detail |
/variants-jobs | VariantsJobsLive | Variant state and job/processing context |
/runtime-doctor | RuntimeDoctorLive | Runtime health / doctor surface |
/actions | ActionsLive | Operator actions hub (see below) |
Live updates reuse Rindle.PubSub and the existing :asset / :variant /
:upload_session topics, so lifecycle changes reflect in the console without a
console-specific realtime channel. The reads behind these pages are composed
internally and are not part of the public Rindle facade — they are an
implementation detail of the console, not an adopter entrypoint.
6. Operator Actions
The /actions hub exposes a small set of guarded operations. Destructive
actions require a typed confirmation (you type an explicit token before the
action executes), and every action reuses an existing public facade or ops verb
— the console adds no new lifecycle semantics:
- Owner erasure — preview and execute single-owner erasure via the v1.10
owner-erasure facade, gated behind a typed
ERASE type:idconfirmation. - Batch owner erasure — typed
ERASE N OWNERSconfirmation, with per-owner partial-failure receipts. - Lifecycle repair —
Rindle.reprobe/1andRindle.requeue_variants/2. - Variant regeneration — re-derive variants through existing facade verbs.
- Quarantine triage — read-only review of quarantined/problem rows.
The console never invents a new deletion or lifecycle path. It is a UI over the
same supported surfaces you would call from code or mix rindle.* tasks.
7. Assets, CSP, And Socket Options
The console is self-contained: Rindle serves its own CSS and JavaScript from
the :rindle OTP app's priv/static/rindle_admin/ directory through a small
static plug. There is no host Tailwind, esbuild, or asset-pipeline
requirement — you do not compile the library's styles. Only an allowlist of
files is served (rindle-admin.css, rindle-admin.js, logo.svg,
favicon.svg); tokens.json is explicitly denied.
For strict-CSP hosts, the mount keeps these options explicit:
| Option | Purpose |
|---|---|
:on_mount | Host LiveView session/auth verification. |
:as | Route-helper prefix (default :rindle_admin). |
:home_path | Destination for the logo and Home link (default "/"). |
:live_socket_path | Host LiveView socket path (default "/live"). |
:transport | Socket transport (default "websocket"). |
:csp_nonce_assign_key | Assign keys used to apply host CSP nonces to console assets. |
auth_guarded? | Acknowledge host auth without an :on_mount list. |
allow_unauthenticated? | Dev/test-only escape hatch; rejected in production. |
Rindle uses your CSP nonces via :csp_nonce_assign_key rather than inventing
its own nonce generator, so the console works inside a strict host CSP.
8. Optional-Dependency Behavior
Rindle.Admin.Router and the console LiveViews are wrapped in
Code.ensure_loaded?/1 guards on Phoenix.LiveView, Phoenix.Router, and
Plug.Static. The practical effect:
| Install shape | Behavior |
|---|---|
No phoenix_live_view | Admin modules are not defined; Rindle compiles normally. |
With phoenix_live_view | rindle_admin/2 and the console LiveViews are available. |
Non-console adopters keep the existing zero-cost posture — the console adds no required runtime dependency.
9. Try It Locally (Cohort Demo)
The bundled Cohort adoption demo mounts the console so you can click around before wiring your own auth. The demo router mounts it under an unauthenticated preview scope:
scope "/admin", CohortWeb do
rindle_admin "/rindle", allow_unauthenticated?: true
endThat puts the console at /admin/rindle (demo port 4102). The full
walkthrough — seeded assets, every lifecycle state, and the actions hub — lives
in examples/adoption_demo/README.md. Remember that allow_unauthenticated?: true is a demo-only convenience: production requires real host auth as
described in section 4.