tutorial

How to Monitor Kaffy with Vigilmon

Add uptime monitoring, health checks, and alerts to your Kaffy-powered Phoenix admin panel — detect broken admin routes, failed DB queries, and access outages before they reach users.

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:

  1. Sign up at vigilmon.online
  2. Click New Monitor → HTTP
  3. Enter https://yourdomain.com/health
  4. Set check interval: 1 minute
  5. Save

Admin route monitor:

  1. Click New Monitor → HTTP
  2. Enter https://yourdomain.com/admin-probe
  3. Set check interval: 5 minutes
  4. Add keyword check: body contains "status":"ok"
  5. Save

The two monitors catch different failure modes:

  • /health catches: app is down, database is unreachable
  • /admin-probe catches: 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:

  1. Click New Monitor → HTTP
  2. Enter https://yourdomain.com/admin
  3. Set keyword check: body must contain your admin panel title or a known UI element (e.g., Kaffy or your app name)
  4. If your /admin redirects 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:

  1. Click New Monitor → Heartbeat
  2. Set expected interval: 2 hours (2× your worker interval)
  3. Copy the ping URL and set it as VIGILMON_EXPORT_HEARTBEAT_URL

Step 7: Slack alerts and status page

Slack alerts:

  1. In Vigilmon, go to Notifications → New Channel → Slack
  2. Paste your Slack webhook URL
  3. 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.

Monitor your app with Vigilmon

Free plan — 5 monitors, no credit card required. Up and running in 60 seconds.

Start free →