We can't find the internet
Attempting to reconnect
Something went wrong!
Hang in there while we get back on track
Jump to file
- ∟ mix.exs
- ∟ assets/package.json
- ∟ README.md
- ∟ assets/css/admin.css
- ∟ assets/js/admin.js
- ∟ config/config.exs
- ∟ config/dev.exs
- ∟ lib/my_app/accounts/scope.ex
- ∟ lib/my_app_admin/accounts.ex
- ∟ lib/my_app_admin/accounts/admin_user.ex
- ∟ lib/my_app_admin/accounts/admin_user_notifier.ex
- ∟ lib/my_app_admin/accounts/admin_user_token.ex
- ∟ lib/my_app_admin/accounts/scope.ex
- ∟ lib/my_app_admin/query.ex
- ∟ lib/my_app_admin/query/admin_users.ex
- ∟ lib/my_app_admin/query/users.ex
- ∟ lib/my_app_admin_web.ex
- ∟ lib/my_app_admin_web/admin_user_auth.ex
- ∟ lib/my_app_admin_web/components/core_components.ex
- ∟ lib/my_app_admin_web/components/layouts.ex
- ∟ lib/my_app_admin_web/components/layouts/app_sidebar.ex
- ∟ lib/my_app_admin_web/components/layouts/root.html.heex
- ∟ lib/my_app_admin_web/controllers/admin_user_session_controller.ex
- ∟ lib/my_app_admin_web/controllers/impersonation_controller.ex
- ∟ lib/my_app_admin_web/live/admin_user_live/confirmation.ex
- ∟ lib/my_app_admin_web/live/admin_user_live/index.ex
- ∟ lib/my_app_admin_web/live/admin_user_live/login.ex
- ∟ lib/my_app_admin_web/live/admin_user_live/show.ex
- ∟ lib/my_app_admin_web/live/dashboard_live.ex
- ∟ lib/my_app_admin_web/live/user_live/index.ex
- ∟ lib/my_app_admin_web/live/user_live/show.ex
- ∟ lib/my_app_web/components/layouts.ex
- ∟ lib/my_app_web/components/layouts/root.html.heex
- ∟ lib/my_app_web/router.ex
- ∟ lib/my_app_web/user_auth.ex
- ∟ priv/repo/migrations/20260115171925_create_admin_users_auth_tables.exs
Install Elixir deps
Flop provides backend pagination, sorting, and filtering for Ecto queries. Flop.Phoenix adds LiveView components for sortable table headers and pagination links. The
assets.build
and
assets.deploy
aliases are updated to also build the admin CSS and JS bundles.
|
|
|
|
|
|
|
|
| + |
|
| + |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| - |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
|
|
|
|
| + |
|
|
|
| + |
|
|
|
|
|
|
Install JS deps
The
@phx-hook/copy-to-clipboard
hook enables one-click copy buttons throughout the admin UI.
|
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
|
|
|
|
|
|
|
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
|
|
|
|
|
Admin CSS
A separate Tailwind CSS bundle for the admin panel with its own DaisyUI theme configuration. Includes the
copied
custom variant for copy-to-clipboard visual feedback. Sources only admin web files to keep the bundle independent from the main app.
|
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
Admin JS
A separate JavaScript bundle registering the
CopyToClipboard
hook alongside colocated hooks. The admin panel gets its own LiveSocket instance.
|
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
Flop configuration
Flop requires a repo configuration. The admin allowlist and session duration are also configured here, with an empty allowlist by default (no access in production unless explicitly configured).
|
|
|
|
|
|
|
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
|
|
|
|
|
Dev configuration
In development, the allowlist uses
~r/.+/
to allow any email. The admin asset watchers (esbuild and tailwind for the
my_app_admin
profile) are added alongside the existing app watchers. The live reload patterns include
my_app_admin_web
files.
|
|
|
|
|
|
| + |
|
| + |
|
| + |
|
| + |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| - |
|
| + |
|
| + |
|
| + |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| - |
|
| + |
|
| + |
|
|
|
|
|
|
User scope changes
The
Scope
struct gains an
impersonated_by
field to track which admin is impersonating.
Scope.impersonating?/1
checks whether the current session is impersonated.
|
|
|
|
|
|
|
|
| - |
|
| + |
|
|
|
|
|
|
|
|
|
|
|
|
|
| - |
|
| - |
|
| + |
|
| + |
|
| + |
|
| + |
|
|
|
|
|
| - |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
|
Admin Accounts context
The core admin authentication logic:
email_allowed?/1
checks the email against the configured allowlist, which supports both exact string matches and regex patterns.
get_or_create_admin_user_by_email!/1
auto-provisions admin users on first login if their email is allowed. This means you don't need to manually create admin accounts.
deliver_login_instructions/2
generates a magic link token and sends the login email. The login flow intentionally doesn't reveal whether an email is authorized, always showing the same success message.
|
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
AdminUser schema
A minimal schema with just an email. No password fields since admin auth is passwordless via magic links.
|
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
AdminUserNotifier
Sends magic link login emails to admin users. Uses the same Swoosh mailer as the main app.
|
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
AdminUserToken schema
Handles two token types: session tokens (random bytes stored directly) and magic link tokens (hashed for email delivery). Magic links expire after 15 minutes. Session duration is configurable via the
:session_duration_days
config.
|
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
Admin Scope
A separate scope struct for admin operations, parallel to
MyApp.Accounts.Scope
. Contains the
admin_user
field.
|
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
Query modules
Shared Flop options for admin list views: filterable by email, sortable by email and inserted_at, paginated with 20 items per page, defaulting to newest first.
Runs
Flop.validate_and_run/3
against the User schema with the shared options. Params from the URL (page, sort, filters) flow directly through Flop.
Same pattern for admin users.
|
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
|
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
|
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
Admin web module
A separate web module for the admin interface, parallel to
MyAppWeb
. Imports
MyAppAdminWeb.CoreComponents
and aliases
Flop.Filter
for use in templates. Uses the same endpoint and router as the main app, but with its own layout and component modules.
|
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
Admin auth plug and LiveView hooks
Handles admin session management with plugs and LiveView
on_mount
hooks:
|
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
Admin core components
Shared admin UI components built with DaisyUI:
copy_button/1
- Inline copy-to-clipboard button that shows the value, displays a clipboard icon on hover, and swaps to a checkmark on copy. Auto-generates the required LiveView hook ID.
data_table/1
- Wraps
Flop.Phoenix.table
with admin styling: bordered container, hover highlights,
whitespace-nowrap
cells, and sortable column headers. Views only define columns and actions.
pagination/1
- Custom DaisyUI pagination using
join
buttons. Builds page links with ellipsis, previous/next buttons, and only renders when there are multiple pages.
format_boolean/1
,
format_date/1
,
format_datetime/1
- Display helpers that handle nil values with a "-" fallback, keeping templates clean.
|
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
Admin layout components
Layout components including
admin_ui/1
, the main authenticated layout with the sidebar navigation, flash messages, and content area. Configures sidebar items for Dashboard, Users, and Admin Users pages.
|
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
Sidebar layout
A responsive sidebar with mobile drawer support. On desktop, it's a fixed 72-unit wide panel. On mobile, it slides in as an overlay with backdrop. Built with
Phoenix.LiveView.JS
commands for show/hide transitions.
|
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
Admin root layout
The admin HTML shell loads the admin-specific CSS and JS bundles. Includes the same theme persistence script as the main app.
|
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
Session controller
Handles the POST from the confirmation page (consuming the magic link token and creating a session) and DELETE for logout.
|
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
Impersonation controller
create/2
starts impersonation by storing the admin's session token in
impersonating_admin_token
and logging in as the target user via the main app's
UserAuth
.
delete/2
ends impersonation by clearing the user session and redirecting back to the admin panel.
|
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
Magic link confirmation
When a user clicks the magic link in their email, this page verifies the token and shows a "Log in" button. The form submits to the session controller to complete the login flow.
|
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
Admin users index
Same table pattern as the users index, listing admin users with sortable columns and search.
|
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
Login LiveView
The admin login page with a single email input. Shows a helpful notice when running with the local mail adapter. The
submit_magic
handler checks the allowlist, auto-provisions the admin user if needed, and sends the magic link.
|
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
Admin users show
Displays admin user details with copyable ID and email.
|
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
Dashboard
A minimal dashboard page that serves as the admin landing page at
/admin
.
|
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
Users index
Lists all app users with sortable columns, email search, and pagination. Uses
<.data_table>
and
<.pagination>
components. Email addresses have copy buttons. Rows are clickable to navigate to the show page.
The search handler builds a Flop filter with
:ilike_and
for partial email matching and pushes a URL patch, keeping search state in the URL.
|
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
Users show
Displays user details with copyable ID and email fields. Includes an "Impersonate" button that lets admins log in as any user.
|
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
App layouts
Aliases
Scope
for use in the impersonation banner template.
|
|
|
|
|
|
|
|
| + |
|
| + |
|
|
|
|
|
|
Impersonation banner
When a session is impersonated, a warning banner appears at the top of every page showing which user is being impersonated and which admin is doing it, with a "Stop impersonating" button.
|
|
|
|
|
|
|
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
|
|
|
|
|
Router
Admin routes are mounted under
/admin
with their own pipeline (
admin_browser
) that uses the admin root layout and auth plug. Two live sessions separate authenticated and unauthenticated admin pages. Impersonation routes use POST/DELETE for create/destroy.
|
|
|
|
|
|
|
|
| + |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
|
User auth changes
fetch_current_scope_for_user
and
mount_current_scope
now check for an
impersonating_admin_token
in the session and load the admin user into the scope's
impersonated_by
field.
renew_session
preserves both
admin_user_token
and
impersonating_admin_token
across session renewals, so impersonation survives session token rotation.
require_sudo_mode
is bypassed for impersonated sessions, since the admin has already authenticated.
|
|
|
|
|
|
|
|
| + |
|
| + |
|
|
|
| - |
|
| + |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| + |
|
| + |
|
|
|
|
|
|
|
|
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| + |
|
|
|
| - |
|
| + |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| - |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
|
Migration
Creates
admin_users
and
admin_users_tokens
tables. Admin users only have an email field since authentication is passwordless. The tokens table handles both session tokens and magic link tokens.
|
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
Install with Claude Code
Write instructions to implement this feature to your project directory in a LLM-friendly format, then have Claude take care of the rest! Requires Claude Code to be installed.
curl "https://elixir-saas.com/llms/p/admin.md?v=1.8.3&f=impl" > admin.md;claude "Implement the feature that is documented in: admin.md." --allowedTools "Write Edit Bash(mix:*) Bash(mkdir:*)";