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_view is 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_view dependency
  • 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"}
  ]
end

phoenix_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
    }
end

This 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_mount list (your LiveView session/auth verification), or
  • an explicit auth_guarded?: true acknowledgement (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):

RouteLiveViewPurpose
/HomeLiveConsole home / lifecycle overview
/assetsAssetsLive (index)Browse media assets
/assets/:idAssetsLive (show)Asset detail, variants, attachments
/upload-sessionsUploadSessionsLive (index)Upload sessions list
/upload-sessions/:idUploadSessionsLive (show)Upload session detail
/variants-jobsVariantsJobsLiveVariant state and job/processing context
/runtime-doctorRuntimeDoctorLiveRuntime health / doctor surface
/actionsActionsLiveOperator 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:id confirmation.
  • Batch owner erasure — typed ERASE N OWNERS confirmation, with per-owner partial-failure receipts.
  • Lifecycle repairRindle.reprobe/1 and Rindle.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:

OptionPurpose
:on_mountHost LiveView session/auth verification.
:asRoute-helper prefix (default :rindle_admin).
:home_pathDestination for the logo and Home link (default "/").
:live_socket_pathHost LiveView socket path (default "/live").
:transportSocket transport (default "websocket").
:csp_nonce_assign_keyAssign 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 shapeBehavior
No phoenix_live_viewAdmin modules are not defined; Rindle compiles normally.
With phoenix_live_viewrindle_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
end

That 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.