Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/lissy93/adguardian-term/llms.txt

Use this file to discover all available pages before exploring further.

AdGuardian Term is a single-binary Rust application built with Tokio for async I/O, ratatui for terminal rendering, and crossterm for keyboard and mouse events. The architecture cleanly separates data fetching from UI rendering via Tokio MPSC channels — the main loop fetches from the AdGuard Home API on a configurable interval and sends results down channels, while a dedicated UI task consumes those results and redraws the terminal on every tick.

Module Structure

The project source lives entirely under src/ and is divided into four top-level modules:
Reads environment variables (ADGUARD_IP, ADGUARD_PORT, ADGUARD_USERNAME, ADGUARD_PASSWORD, ADGUARD_PROTOCOL, ADGUARD_UPDATE_INTERVAL, ADGUARD_TIMEOUT, ADGUARD_QUERYLOG_LIMIT), constructs a reqwest::Client with a configurable per-request timeout, and wires everything together.The run() async function:
  • Creates three MPSC channels (queries, stats, status) and a shutdown watch channel.
  • Spawns draw_ui() as a Tokio task.
  • Runs the main fetch loop, calling fetch_all() on a tokio::time::interval (default 2 seconds, minimum 1 second).
  • Stops when the shutdown watch fires or a channel send fails (indicating the UI has exited).
fetch_all() is a helper that calls fetch_adguard_query_log, fetch_adguard_stats, and fetch_adguard_status together. If any one of the three fails, the function returns an error and the UI data is not partially updated — ensuring the dashboard never shows a mixed snapshot from different points in time.
Runs before the main fetch loop. It:
  1. Prints the AdGuardian ASCII art banner.
  2. Checks for updates by querying crates.io/api/v1/crates/adguardian and comparing the installed version against the latest published version using semver.
  3. Parses command-line flags (--adguard-ip, --adguard-port, --adguard-username, --adguard-password) and maps them to environment variables.
  4. Prompts interactively for any environment variables that are still unset, masking password input on interactive terminals.
  5. Verifies the connection to AdGuard Home via verify_connection(), retrying up to 3 times with a 5-second delay between attempts using the with_retries() helper.
  6. Checks the reported AdGuard Home version is at least v0.107.29 — exits with an error if the version is too old or unparseable.
The exported with_retries() function is also reused in main.rs when fetching the initial filter list at startup.
Four submodules, one per AdGuard Home API endpoint. Each module defines a typed response struct (deriving serde::Deserialize with #[serde(default)] so missing fields fall back to Default rather than erroring) and an async fetch function that builds a Basic Auth header and sends a reqwest GET request.
ModuleEndpointResponse type
fetch_stats.rsGET /control/statsStatsResponse
fetch_query_log.rsGET /control/querylog?limit=NQueryResponse containing Vec<Query>
fetch_status.rsGET /control/statusStatusResponse
fetch_filters.rsGET /control/filtering/statusAdGuardFilteringStatus
The DomainData struct (a name/count pair used by the stats top-lists) and the deserialize_domains helper that converts AdGuard’s Vec<HashMap<String, i32>> format into Vec<DomainData> are both defined in fetch_stats.rs alongside StatsResponse.
Exports draw_ui(), an async function that owns the terminal lifecycle. It uses TerminalGuard — a struct that enables raw mode and switches to the alternate screen buffer on construction, and restores them via Drop even if the function returns early due to an error.On each iteration of the render loop, draw_ui():
  • Waits for data from all three MPSC channels (queries_rx, stats_rx, status_rx).
  • Calls prepare_chart_data() to compute interpolated time-series data for the history chart.
  • Calls terminal.draw() to compose the full layout and render every widget in a single frame.
A separate Tokio task handles keyboard input via crossterm::event::EventStream. Quit keys (q, Q, or Ctrl+C) trigger the shutdown watch channel, which causes both the render loop and the main fetch loop in main.rs to exit cleanly.The layout is built from nested ratatui Layout splits:
  • Top row (30% height): left column (status paragraph + block-rate gauge) and right column (history chart, 70% width).
  • Middle row (remaining space): scrollable live query log table.
  • Bottom row (20% height, only shown if terminal height > 42 lines): four equal columns for filter list, top queried domains, top blocked domains, and top clients.
One file per widget type. Each exports a function that takes references to the latest data and returns a ratatui widget ready to be rendered.
FileWidgetDescription
chart.rsChart30-day time-series history of total queries and blocked queries, with 3-point interpolation to smooth the Braille-marker line
status.rsParagraphColoured status panel: running state, protection enabled, DHCP availability, average processing time, version, DNS/HTTP ports, and query counts by category
gauge.rsGaugeHorizontal bar showing the block rate as a percentage of total queries
table.rsTableScrollable live query log showing client, domain, type, reason, upstream, and elapsed time for each recent DNS query
list.rsListRanked list widget reused for top queried domains, top blocked domains, and top clients
filters.rsListActive AdGuard filter subscriptions with their names and rule counts

Data Flow

The following sequence describes how data moves from AdGuard Home to the terminal on every update tick:
1

Startup checks

welcome() runs synchronously (within the Tokio runtime) before the main application starts. It verifies credentials, confirms the AdGuard Home version, and fetches the filter list once (with retries). The filter list is passed directly to draw_ui() since it does not change during a session.
2

Channel creation

Three bounded MPSC channels (capacity 1) are created for queries, stats, and status. A tokio::sync::watch channel is created for the shutdown signal.
3

UI task spawned

draw_ui() is spawned as a Tokio task. It blocks waiting for data on the three receive ends of the MPSC channels.
4

Fetch loop starts

The main task enters a tokio::select! loop ticking at the configured interval (default 2 seconds, minimum 1 second). On each tick it calls fetch_all().
5

Atomic fetch and send

fetch_all() awaits all three API calls. If all succeed, the results are sent down the three channels. If any call fails, the tick is skipped entirely — the UI retains the last good data rather than showing a partial update. A send error (channel closed) means the UI has exited, so the loop breaks.
6

UI renders

On receiving a full set of data, draw_ui() calls prepare_chart_data() to compute the interpolated chart datasets, then calls terminal.draw() to paint all widgets to the screen in a single atomic frame.
7

Graceful shutdown

When the user presses q, Q, or Ctrl+C, the input task sends true on the shutdown watch channel. Both the render loop and the fetch loop observe the watch and exit. draw_ui_task.await in main.rs ensures the terminal is fully restored before the process exits.

AdGuard Home API Endpoints

All requests use HTTP Basic Authentication. The credentials are base64-encoded in an Authorization header on every request.
EndpointMethodReturns
/control/statsGETAggregated query counts, average processing time, 30-day per-day history arrays, and top queried domains, top blocked domains, and top clients
/control/querylog?limit=NGETThe most recent N DNS query log entries, each with client IP, domain, query type, reason, upstream resolver, elapsed time, and timestamp
/control/statusGETCurrent running state, AdGuard Home version string, DNS port, HTTP port, protection-enabled flag, and DHCP-available flag
/control/filtering/statusGETList of active filter subscriptions, each with a name, enabled state, and total rule count
AdGuardian Term requires AdGuard Home v0.107.29 or later. The version is checked at startup via the /control/status endpoint. Older versions will cause the app to exit with an error message.

Key Dependencies

The full dependency list is in Cargo.toml. The most significant crates are:
CrateVersionRole
ratatui (aliased as tui)0.20.1Terminal UI widgets, layout engine, and drawing backend
crossterm0.22.0Raw mode, alternate screen buffer, keyboard and mouse event stream
tokio1 (full)Async runtime, MPSC channels, watch channels, and interval timers
reqwest0.13Async HTTP client using rustls (no native TLS dependency)
serde / serde_json1.0Derive macros and JSON deserialization of API responses
chrono0.4Date and time parsing for query log entry timestamps
semver1.0Semantic version parsing for the startup version compatibility check
anyhow1.0Ergonomic error propagation throughout the codebase
base640.22Encoding credentials into the HTTP Basic Auth header value
colored3ANSI colour output in the startup and welcome sequence
futures0.3StreamExt extension trait used to poll the crossterm event stream

Build docs developers (and LLMs) love