# Bandera Flag Dashboard
Bandera ships an optional LiveView dashboard for browsing and managing flags. It
lives in the core library but is inert unless your app depends on
`phoenix_live_view`. It ships **no JavaScript** and needs **no asset-pipeline
changes**: it inherits your app's layout and runs on your existing LiveView
socket.
## Install in a Phoenix app
The dashboard is a LiveView that runs inside your app. It needs LiveView wired up
(a socket + `app.js`), Bandera configured with a storage backend, and a route
mounted behind your auth. Apps generated with `mix phx.new --live` already have
the LiveView pieces (step 2).
### 1. Add the dependencies
```elixir
# mix.exs
def deps do
[
{:bandera, "~> 0.4"},
{:phoenix_live_view, "~> 1.0"}
]
end
```
Run `mix deps.get`. The dashboard only compiles when `phoenix_live_view` is
present — without it, Bandera still works as a plain flag library.
### 2. Make sure LiveView is wired up
The dashboard reuses your app's LiveView socket and JavaScript; it ships none of
its own. If you generated your app with `--live` this is already done. Otherwise
confirm both:
```elixir
# lib/my_app_web/endpoint.ex
socket "/live", Phoenix.LiveView.Socket
```
```javascript
// assets/js/app.js
import {Socket} from "phoenix"
import {LiveSocket} from "phoenix_live_view"
const csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content")
const liveSocket = new LiveSocket("/live", Socket, {params: {_csrf_token: csrfToken}})
liveSocket.connect()
```
Mount the dashboard under a pipeline whose layout loads that `app.js` (the default
`:browser` pipeline does). Without the socket the page still renders, but toggles,
search, and gate editing won't work.
### 3. Configure a storage backend
The dashboard reads and writes real flags through Bandera, so configure a
persistence backend (defaults to in-memory, which is per-node and resets on
restart — fine for trying it out, not for production). For example, Ecto:
```elixir
# config/runtime.exs
config :bandera,
store: Bandera.Store.TwoLevel,
persistence: [adapter: Bandera.Store.Persistent.Ecto, repo: MyApp.Repo]
```
See the [README](README.md) for all backends (Ecto, Redis, in-memory) and
cross-node cache-bust notifications.
### 4. Mount the route behind your auth
```elixir
# lib/my_app_web/router.ex
import Bandera.Dashboard.Router
scope "/admin" do
pipe_through [:browser, :require_admin] # YOUR authentication/authorization
bandera_dashboard "/flags"
end
```
The dashboard can toggle production features, so **never** mount it on an
unauthenticated route. You can also hook authorization into the LiveView's own
mount lifecycle with `:on_mount`:
```elixir
bandera_dashboard "/flags", on_mount: {MyAppWeb.AdminAuth, :ensure_admin}
```
Mount it more than once (e.g. per environment) by passing a distinct
`:live_session_name`.
### 5. Visit it
Start your server and open `/admin/flags`. Flags are created in code/IEx (e.g.
`Bandera.enable(:my_flag)`); the dashboard manages their gates from there.
The dashboard sets no root layout — it inherits the one from your pipeline. By
default its CSS is self-contained and inlined, with `bandera-`-prefixed selectors,
so it needs **no asset-pipeline changes** and won't clash with your styles. See
[Theming](#theming) to retheme it or switch to daisyUI.
## Grouping
Flags are grouped by a name-prefix convention: the part of the flag's name before
the first separator is the group. The separator defaults to `"_"`:
```elixir
config :bandera, dashboard: [group_separator: "_"]
```
So `:billing_checkout_v2` and `:billing_invoices` appear under **billing**. Flags
with no separator (e.g. `:beta`) appear under **Ungrouped**. Set
`group_separator: nil` to disable grouping.
## Theming
The dashboard has two themes, chosen by config:
```elixir
config :bandera, dashboard: [theme: :standalone] # default
```
### `:standalone` (default)
A self-contained, inlined stylesheet with `bandera-`-prefixed selectors. No asset
pipeline, no daisyUI, no clashes. Colors, radii, and fonts are read as CSS custom
properties with built-in fallbacks, so you can retheme with a single rule — no
specificity battles, no `!important`:
```css
:root {
--bandera-primary: #0ea5e9; /* buttons, group headers, focus rings */
--bandera-radius: 6px; /* cards and inputs */
--bandera-surface: #ffffff; /* card background */
--bandera-border: #e2e8f0;
/* also: --bandera-fg, --bandera-muted, --bandera-surface-2,
--bandera-radius-sm, --bandera-shadow, --bandera-primary-fg,
--bandera-success, --bandera-off, --bandera-danger,
--bandera-danger-border, --bandera-danger-bg, --bandera-font */
}
```
The defaults are a fixed light palette. For dark mode, set the variables (e.g.
inside your own `@media (prefers-color-scheme: dark)`), or use the `:daisyui`
theme below.
### `:daisyui`
```elixir
config :bandera, dashboard: [theme: :daisyui]
```
In this mode the dashboard emits daisyUI component classes (`btn`, `input`,
`select`, `alert`, …) and **no** inlined `<style>`, so it inherits your app's daisyUI theme
(including dark mode and brand colors). This requires your app to build daisyUI
itself, and — because Bandera ships compiled templates that Tailwind doesn't scan
by default — you must add Bandera to your Tailwind sources so the classes are
generated:
```css
/* assets/css/app.css (Tailwind v4 / Phoenix 1.8) */
@source "../../deps/bandera/lib";
```
(Tailwind v3: add `"../deps/bandera/lib/**/*.ex"` to `content` in
`tailwind.config.js`.) Without this, the dashboard renders unstyled.
## Live updates across nodes
If you've configured Bandera's `PhoenixPubSub` cache-bust notifications, open
dashboards refresh automatically when another node or operator changes a flag.
## What you can manage
Per flag: the boolean on/off toggle, per-actor gates, per-group gates, and a
percentage rollout (of actors or of time), plus clearing the whole flag. Search
filters the list as you type. (Creating brand-new flags from the UI is not in this
version — flags are created in code/IEx via `Bandera.enable/2`.)