How to Monitor Kaffy with Vigilmon
Kaffy is a feature-rich admin interface generator for Phoenix that builds CRUD dashboards, custom actions, and search from your Ecto schemas. It's often the most sensitive part of a Phoenix app: it runs as the same process as your production app, touches every schema, and is used by staff who need it to work exactly when things are going wrong.
A broken admin panel often means a broken incident response. This tutorial adds external monitoring so you know before your team finds out:
- A health endpoint that validates the app and admin routes are responding
- HTTP uptime monitoring with Vigilmon
- Heartbeat monitoring for background admin tasks
- Slack alerts when admin access fails
Step 1: Install Kaffy
# mix.exs
defp deps do
[
{:kaffy, "~> 0.10"}
]
end
Configure it in your router:
# lib/my_app_web/router.ex
import Kaffy.Routes
scope "/admin", KaffyWeb do
pipe_through [:browser, :admin_auth]
kaffy_resources "/", MyApp, [
resources: [
users: [schema: MyApp.Accounts.User],
orders: [schema: MyApp.Orders.Order],
products: [schema: MyApp.Catalog.Product]
]
]
end
Configure Kaffy itself:
# config/config.exs
config :kaffy,
otp_app: :my_app,
ecto_repo: MyApp.Repo,
router: MyAppWeb.Router
Step 2: Build a health endpoint
Phoenix apps with Kaffy benefit from a health plug that validates the database connection and core routes are responding. Add it before the Kaffy router so monitoring traffic doesn't pass through admin authentication:
# lib/my_app_web/plugs/health_check.ex
defmodule MyAppWeb.Plugs.HealthCheck do
import Plug.Conn
def init(opts), do: opts
def call(%Plug.Conn{request_path: "/health"} = conn, _opts) do
checks = run_checks()
status = if all_ok?(checks), do: 200, else: 503
conn
|> put_resp_content_type("application/json")
|> send_resp(status, Jason.encode!(checks))
|> halt()
end
def call(conn, _opts), do: conn
defp run_checks do
db = db_check()
%{
status: if(db.status == "ok", do: "ok", else: "degraded"),
database: db,
uptime_seconds: uptime_seconds()
}
end
defp db_check do
case Ecto.Adapters.SQL.query(MyApp.Repo, "SELECT 1", []) do
{:ok, _} ->
%{status: "ok"}
{:error, reason} ->
%{status: "error", reason: inspect(reason)}
end
end
defp uptime_seconds do
{uptime, _} = :erlang.statistics(:wall_clock)
div(uptime, 1000)
end
defp all_ok?(checks), do: checks.status == "ok"
end
Add it to your endpoint before your router:
# lib/my_app_web/endpoint.ex
plug MyAppWeb.Plugs.HealthCheck
plug MyAppWeb.Router
Step 3: Add a dedicated admin probe endpoint
The standard health endpoint proves the app is up, but it doesn't verify the admin routes are reachable. Add a lightweight admin probe that uses Kaffy's database queries to validate the admin panel is functional:
# lib/my_app_web/controllers/admin_probe_controller.ex
defmodule MyAppWeb.AdminProbeController do
use MyAppWeb, :controller
@doc """
A non-authenticated probe that verifies the database is serving
admin-relevant queries. Add auth at the Kaffy layer, not here.
"""
def index(conn, _params) do
checks = %{
user_count: count_check(MyApp.Accounts.User),
order_count: count_check(MyApp.Orders.Order)
}
all_ok = Enum.all?(checks, fn {_, v} -> v.status == "ok" end)
conn
|> put_status(if(all_ok, do: :ok, else: :service_unavailable))
|> json(%{
status: if(all_ok, do: "ok", else: "degraded"),
checks: checks
})
end
defp count_check(schema) do
try do
count = MyApp.Repo.aggregate(schema, :count)
%{status: "ok", count: count}
rescue
e -> %{status: "error", reason: Exception.message(e)}
end
end
end
Add the route before the admin scope (no auth required — it returns only aggregate counts):
# lib/my_app_web/router.ex
scope "/", MyAppWeb do
pipe_through :api
get "/health", HealthController, :index
get "/admin-probe", AdminProbeController, :index
end
Step 4: Set up Vigilmon monitors
Primary uptime monitor:
- Sign up at vigilmon.online
- Click New Monitor → HTTP
- Enter
https://yourdomain.com/health - Set check interval: 1 minute
- Save
Admin route monitor:
- Click New Monitor → HTTP
- Enter
https://yourdomain.com/admin-probe - Set check interval: 5 minutes
- Add keyword check: body contains
"status":"ok" - Save
The two monitors catch different failure modes:
/healthcatches: app is down, database is unreachable/admin-probecatches: schema changes broke Kaffy queries, Ecto pool exhausted for admin operations
Step 5: Monitor your admin interface directly (with auth)
For a more thorough check, you can monitor the Kaffy admin login page directly to verify the UI is rendering:
- Click New Monitor → HTTP
- Enter
https://yourdomain.com/admin - Set keyword check: body must contain your admin panel title or a known UI element (e.g.,
Kaffyor your app name) - If your
/adminredirects to a login page, check for a login form keyword instead
This catches issues that the probe endpoint misses: view rendering failures, static asset 404s causing broken layouts, and Kaffy configuration errors that only surface when the LiveView renders.
Step 6: Heartbeat monitoring for admin background tasks
Many Phoenix admin panels run background tasks — data exports, report generation, or scheduled cleanup jobs. Add heartbeats to prove these are completing:
# lib/my_app/admin/export_worker.ex
defmodule MyApp.Admin.ExportWorker do
use GenServer
require Logger
@interval :timer.hours(1)
def start_link(_), do: GenServer.start_link(__MODULE__, %{}, name: __MODULE__)
def init(state) do
schedule()
{:ok, state}
end
def handle_info(:run, state) do
case run_export() do
:ok ->
ping_heartbeat()
Logger.info("Export completed successfully")
{:error, reason} ->
Logger.error("Export failed: #{inspect(reason)}")
# No heartbeat ping → Vigilmon alerts after window expires
end
schedule()
{:noreply, state}
end
defp run_export do
# Your export logic here
:ok
end
defp ping_heartbeat do
url = Application.get_env(:my_app, :vigilmon)[:export_heartbeat_url]
if url, do: Req.get(url, receive_timeout: 10_000)
end
defp schedule, do: Process.send_after(self(), :run, @interval)
end
In Vigilmon, create a Heartbeat Monitor:
- Click New Monitor → Heartbeat
- Set expected interval: 2 hours (2× your worker interval)
- Copy the ping URL and set it as
VIGILMON_EXPORT_HEARTBEAT_URL
Step 7: Slack alerts and status page
Slack alerts:
- In Vigilmon, go to Notifications → New Channel → Slack
- Paste your Slack webhook URL
- Enable it on your health monitor, admin probe monitor, and heartbeat monitors
When admin access fails:
🔴 DOWN: yourdomain.com/admin-probe
Status: 503 Service Unavailable
Detected: EU-West, US-East
2 minutes ago
Status page:
Create a status page with all your monitors and share it with your team. During incidents, it lets everyone check the same source of truth instead of asking you to reproduce the issue.
What you've built
| What | How | |------|-----| | App health endpoint | HealthCheck plug with DB validation | | Admin probe endpoint | Schema count queries via Ecto | | UI availability check | Keyword match on admin login page | | HTTP uptime monitoring | Vigilmon HTTP monitor (1-minute checks) | | Admin route monitoring | Vigilmon HTTP monitor (5-minute checks) | | Background task heartbeat | GenServer worker + heartbeat monitor | | Slack alerts | Vigilmon Slack notification channel | | Status page | Vigilmon public status page |
Kaffy gives your team admin power. Vigilmon makes sure that power is always available.
Next steps
- Add a Vigilmon monitor specifically for your admin export/download endpoints if those are latency-sensitive
- Use Vigilmon's response time history to spot slow Ecto queries in your admin list views — these degrade before they error
- If you restrict admin access by IP, use Vigilmon's allowlist feature to add monitoring probe IPs to your firewall rules
Get started free at vigilmon.online.