Dashboards combine typed variables with stat, timeseries, table, and chart widgets. Application callbacks own metrics and queries; Incant handles controls, layout, formatting, and localized loading errors.
Define a dashboard
defmodule MyApp.Admin.Dashboards.LLMOperations do
use Incant.Dashboard
title "LLM Operations"
variables do
var :range, :date_range, default: {:last, 24, :hours}
var :provider, :multi_select, options: [:openai, :anthropic, :google]
var :model, :select, options: ["gpt-4.1", "claude-3-5-haiku-20241022"]
end
grid columns: 12, row_height: 8 do
stat :total_requests,
span: 3,
label: "Requests",
format: :compact_number,
query: &MyApp.Admin.Metrics.LLM.total_requests/2
stat :total_cost,
span: 3,
label: "Spend",
format: :money,
query: &MyApp.Admin.Metrics.LLM.total_cost/2
stat :avg_latency,
span: 3,
label: "Avg latency",
format: :duration_ms,
query: &MyApp.Admin.Metrics.LLM.avg_latency/2
timeseries :requests_over_time,
span: 8,
label: "Requests over time",
query: &MyApp.Admin.Metrics.LLM.requests_over_time/2
table :slow_requests,
span: 4,
label: "Slow & failed",
query: &MyApp.Admin.Metrics.LLM.slow_requests/2 do
column :provider, label: "Provider"
column :model, label: "Model"
column :latency_ms, label: "Latency", format: :duration_ms
column :cost_usd, label: "Cost", format: :money
column :status, label: "Status"
end
end
endRegister it on an admin root:
dashboard MyApp.Admin.Dashboards.LLMOperationsVariables
Variable controls persist in URL state. The LiveView casts submitted values before invoking widget callbacks.
Built-in dashboard controls include:
:date_range:select:multi_select- text-like controls supported by the semantic control layer
A two-argument query receives typed variables and context:
def total_requests(variables, context) do
range = variables[:range]
providers = variables[:provider]
actor = context[:actor]
MyApp.Analytics.count_requests(range, providers, actor)
endThe context also retains raw URL values:
def metric(variables, %{raw_variables: raw_variables}) do
# variables contains cast values; raw_variables contains URL strings/maps.
endUse typed values for application queries. Raw values are useful for diagnostics or custom controls, not as trusted query input.
Stat widgets
Stat callbacks return one value. Apply explicit formats for numeric or temporal values:
stat :tokens, format: :compact_number, query: &Metrics.tokens/2
stat :cost, format: :money, query: &Metrics.cost/2
stat :latency, format: :duration_ms, query: &Metrics.latency/2Timeseries widgets
The default LiveView adapter accepts labelled points:
def requests_over_time(_variables, _context) do
[
%{label: "3h", value: 42},
%{label: "2h", value: 58},
%{label: "1h", value: 51},
%{label: "now", value: 67}
]
endKeep bucket and aggregation logic in metrics/data-source modules so the dashboard remains presentation metadata.
Table widgets
Table queries return row maps. Declared columns control order, labels, numeric alignment, formatting, and full-value tooltips where supported.
def slow_requests(_variables, _context) do
[
%{
provider: :openai,
model: "gpt-4.1",
latency_ms: 2_940,
cost_usd: 0.018,
status: :error
}
]
endPortable dashboard values convert atoms to strings across remote service boundaries. Incant applies the admin naming vocabulary to known terms such as openai while leaving unknown identifiers and model names unchanged.
Dataset-backed charts
Chart widgets can bind to a registered dataset:
chart :cost_over_time, :line, span: 8 do
dataset MyApp.Admin.Datasets.LLMUsage
x :timestamp, bucket: :hour
y :cost_usd
series :provider
drilldown :provider
endThe chart declaration describes semantic binding. The dataset's data-source adapter owns query execution and aggregation.
Grid layout
grid columns: defines the desktop grid. Widget span values control width; the default adapter collapses the layout responsively on narrower screens.
Keep spans intentional:
- small stats usually use 3 or 4 columns in a 12-column grid;
- primary timeseries/charts usually use 8 or 12;
- companion tables usually use 4 or 6.
Errors
Widget queries execute independently. A failed widget is represented as a localized widget error rather than crashing the entire dashboard. Log and monitor the underlying callback failure; do not return exception terms as display data.
See Architecture for the dashboard loading path and Datasets for analytical data sources.