The RpiDNS frontend is a single-page application built with Vue 3 and Bootstrap Vue Next. It provides a tab-based interface for monitoring DNS queries, managing RPZ feeds, configuring block/allow lists, and administering the system. The application communicates with the PHP backend API (see backend-api.md) via Axios HTTP requests.
Key technologies:
- Vue 3 (Composition API)
- Bootstrap Vue Next (
bootstrap-vue-next) - Vite (build tooling)
- Axios (HTTP client)
- ApexCharts via
vue3-apexcharts(dashboard charts) - FontAwesome (icons)
Source location: rpidns-frontend/
App.vue (root)
├── LoginPage — Authentication gate
├── Dashboard — DNS stats overview
│ ├── CustomPeriodPicker — Date/time range selector
│ └── ResearchLinks — External threat intel links
├── QueryLog — DNS query log viewer
│ ├── CustomPeriodPicker
│ ├── ResearchLinks
│ └── ContextMenu — Right-click research & block actions
├── RpzHits (RPZ Log) — Blocked query log viewer
│ ├── CustomPeriodPicker
│ ├── ResearchLinks
│ └── ContextMenu — Right-click research & allow actions
├── AdminTabs — Admin panel container
│ ├── Assets — Network device management
│ ├── RpzFeeds — RPZ feed configuration
│ │ ├── AddIoc2rpzFeed — Add ioc2rpz.net feed modal
│ │ ├── AddLocalFeed — Add local feed modal
│ │ ├── AddThirdPartyFeed — Add third-party feed modal
│ │ └── EditFeed — Edit feed modal
│ ├── BlockList — Custom block list
│ ├── AllowList — Custom allow list
│ ├── Settings — App settings & retention
│ │ └── PasswordChange — Password change modal
│ ├── Tools — Downloads & DB import
│ └── UserManager (admin only) — User administration
│ └── AddUser — Create user modal
├── DonateContent — Donation/support info
├── HelpContent — Built-in help documentation
└── Modals (rendered at root level)
├── PasswordChange — User password change
├── AddAsset — Add/edit network asset
├── AddIOC — Add/edit block/allow entry
└── ImportDB — Database import with progress
App.vue manages authentication state, tab navigation (via BTabs with URL hash routing #i2r/{tab}), and provides currentUser, isAdmin, and isAuthenticated to child components via Vue's provide/inject. The navigation sidebar is collapsible and persists its state in localStorage.
The main landing page after login. Displays eight statistical widget cards in two rows and a Queries per Minute area chart.
| Props | Type | Description |
|---|---|---|
isActive |
Boolean |
Whether this tab is currently visible |
customStart |
Number |
Custom period start (Unix timestamp) |
customEnd |
Number |
Custom period end (Unix timestamp) |
| Events | Payload | Description |
|---|---|---|
navigate |
{ tab, filter, period, type, customStart?, customEnd? } |
Navigate to Query Log or RPZ Log with filter |
add-ioc |
{ ioc, type } |
Open Add IOC modal to block/allow a domain |
custom-period-change |
{ start_dt, end_dt } |
Propagate custom period to parent |
Widgets (top row — allowed traffic): TopX Allowed Requests, TopX Allowed Clients, TopX Allowed Request Types, RpiDNS Stats (server metrics).
Widgets (bottom row — blocked traffic): TopX Blocked Requests, TopX Blocked Clients, TopX Feeds, TopX Servers.
Period options: 30m, 1h, 1d, 1w, 30d, custom. Auto-refresh via useAutoRefresh composable (60s interval, stored in localStorage key rpidns_autorefresh_dashboard).
API endpoints used: dash_topX_req, dash_topX_client, dash_topX_req_type, server_stats, dash_topX_breq, dash_topX_bclient, dash_topX_feeds, dash_topX_server, qps_chart.
Paginated, filterable table of DNS query records. Supports Logs (individual records with timestamps) and Stats (aggregated by selected fields) view modes.
| Props | Type | Description |
|---|---|---|
filter |
String |
Pre-applied filter string (e.g., fqdn=example.com) |
period |
String |
Time period (30m, 1h, 1d, 1w, 30d, custom) |
logs_height |
Number |
Table container height in pixels |
isActive |
Boolean |
Whether this tab is currently visible |
customStart |
Number |
Custom period start (Unix timestamp) |
customEnd |
Number |
Custom period end (Unix timestamp) |
| Events | Payload | Description |
|---|---|---|
add-ioc |
{ ioc, type } |
Block or allow a domain |
custom-period-change |
{ start_dt, end_dt } |
Propagate custom period |
Table columns: Local Time (logs mode), Client, Server, Request (FQDN), Type, Class, Options, Count, Action. In Stats mode, column checkboxes control grouping fields.
API endpoint: queries_raw with parameters for period, pagination (cp, pp), filter, log type (ltype), selected fields, and sort.
Paginated, filterable table of RPZ-blocked DNS queries, displayed as "RPZ Log" in the UI. Structurally similar to QueryLog with Logs/Stats toggle.
| Props | Type | Description |
|---|---|---|
filter |
String |
Pre-applied filter string |
period |
String |
Time period |
logs_height |
Number |
Table container height |
isActive |
Boolean |
Whether this tab is currently visible |
customStart |
Number |
Custom period start (Unix timestamp) |
customEnd |
Number |
Custom period end (Unix timestamp) |
| Events | Payload | Description |
|---|---|---|
add-ioc |
{ ioc, type } |
Allow a blocked domain |
custom-period-change |
{ start_dt, end_dt } |
Propagate custom period |
Table columns: Local Time, Client, Request, Action, Rule, Type, Count.
API endpoint: hits_raw.
Session-based authentication form. Submits credentials to /rpi_admin/auth.php?action=login via fetch. Handles rate limiting (HTTP 429) and displays appropriate error messages.
| Events | Payload | Description |
|---|---|---|
login-success |
user object |
Emitted on successful authentication |
Built-in help documentation with a collapsible sidebar navigation. Covers Getting Started, Dashboard, Query Log, RPZ Log, Admin Panel, and Common Actions sections. Content is static HTML rendered within a Vue component.
Static page with donation options for the ioc2rpz project: GitHub Sponsors, PayPal, Zelle, and cryptocurrency addresses (BTC, ETH, USDT, USDC). Includes links to the project website and GitHub repository.
Modal dialog for selecting a custom date/time range. Used by Dashboard, QueryLog, and RpzHits (RPZ Log).
| Props | Type | Description |
|---|---|---|
show |
Boolean |
Controls modal visibility (v-model) |
initialStart |
Date |
Pre-populated start date |
initialEnd |
Date |
Pre-populated end date |
| Events | Payload | Description |
|---|---|---|
update:show |
Boolean |
Two-way binding for visibility |
apply |
{ start_dt, end_dt } |
Unix timestamps for selected range |
cancel |
— | User cancelled selection |
Validates that start is before end and both fields are filled. Defaults to the last hour if no initial values are provided.
Renders external threat intelligence lookup links for a given domain. Used in Dashboard widget popovers. Consumes link definitions from the shared useResearchLinks.js composable.
| Props | Type | Description |
|---|---|---|
domain |
String (required) |
Domain name to research |
Integrated tools:
- DuckDuckGo search
- Google search
- VirusTotal domain lookup
- DomainTools Whois
- Robtex DNS lookup
- ThreatMiner domain analysis
A reusable, positioned right-click context menu that renders research links (from useResearchLinks.js) and action buttons. Used by QueryLog and RpzHits to provide domain research tools and smart block/allow actions.
| Props | Type | Description |
|---|---|---|
visible |
Boolean |
Controls visibility (v-model) |
domain |
String |
Domain name to display and act on |
x |
Number |
Cursor X coordinate |
y |
Number |
Cursor Y coordinate |
actions |
Array<{ label, icon, handler }> |
Action buttons to render |
| Events | Payload | Description |
|---|---|---|
update:visible |
Boolean |
Two-way binding for visibility |
action |
{ actionName, domain } |
Emitted when an action button is clicked |
Features:
- Displays the domain name as a header
- "Research" section with external threat intelligence links (DuckDuckGo, Google, VirusTotal, DomainTools Whois, Robtex, ThreatMiner) opening in new tabs
- Action buttons section (e.g., "Block" on QueryLog, "Allow" on RPZ Log) separated by a visual divider
- Viewport clamping: adjusts position in
nextTickif the menu would overflow the right or bottom viewport edges - Dismissal: closes on click-outside (
mousedownlistener) or Escape key (keydownlistener) - Listeners are added/removed on open/close to avoid leaks
All admin components are located in src/components/Admin/ and rendered within the AdminTabs container.
Tab container for all admin sub-components. Uses BTabs with card layout. The Users tab is conditionally rendered based on isAdmin (injected from App.vue). Automatically refreshes the active sub-component's data when switching tabs.
| Props | Type | Description |
|---|---|---|
logs_height |
Number |
Table container height passed to child components |
| Events | Payload | Description |
|---|---|---|
navigate |
navigation data | Forwarded from Assets |
add-asset |
asset data | Open AddAsset modal |
delete-asset |
asset data | Confirm asset deletion |
add-ioc |
IOC data | Open AddIOC modal |
delete-ioc |
IOC data | Confirm IOC deletion |
show-info |
{ msg, time } |
Display info toast |
open-import-modal |
{ db_import_type } |
Open ImportDB modal |
Manages network devices (assets) tracked by MAC or IP address. Provides a filterable table with add, edit, and delete operations. Uses the useApi composable for API calls.
API endpoint: assets (GET, POST, PUT, DELETE). Listens for refresh-table custom DOM events to sync after external changes.
Table columns: Address, Name, Vendor, Added date, Comment.
Manages RPZ feed configuration. Supports three feed source types: ioc2rpz.net, local, and third-party. Features drag-and-drop row reordering to control feed priority (first match wins in BIND's response-policy).
Key features:
- Add feeds via three separate modals (ioc2rpz.net, local, third-party)
- Edit feed settings (policy action, CNAME target, primary server, TSIG)
- Enable/disable feeds
- Delete feeds (predefined local RPZ zones cannot be deleted)
- Retransfer zone (available for non-local feeds only)
- Drag-and-drop reordering with backend persistence
Predefined feeds (non-deletable): allow.ioc2rpz.rpidns, block.ioc2rpz.rpidns, allow-ip.ioc2rpz.rpidns, block-ip.ioc2rpz.rpidns.
API endpoints: rpz_feeds (GET), rpz_feed (POST, PUT, DELETE), rpz_feeds_order (PUT), rpz_feed_status (PUT), retransfer_feed (PUT).
Manages custom domain/IP block rules. Each entry has an IOC (domain or IP), active toggle, subdomain wildcard toggle (*.), and optional comment. Inline toggles for active/subdomain status update immediately via PUT.
API endpoint: blacklist (GET, POST, PUT, DELETE).
Manages custom domain/IP allow rules. Structurally identical to BlockList. Allow list entries override RPZ feed blocks.
API endpoint: whitelist (GET, POST, PUT, DELETE).
Displays data retention statistics per table (size, row count, date range) and allows configuring retention periods (in days). Also provides miscellaneous settings:
- Auto-create assets toggle
- Track assets by MAC or IP
- Dashboard TopX count
- Password change (via embedded PasswordChange modal)
API endpoint: RPIsettings (GET, PUT).
Provides download links and database import functionality:
- CA Root Certificate — Download the SSL CA certificate for browser installation
- Database — Download SQLite DB backup or import a DB file
- ISC BIND Logs — Download
bind.log,bind_queries.log,bind_rpz.log
API endpoint: download (GET with file parameter). Import triggers the ImportDB modal with all table types pre-selected.
Admin-only user management. Lists all users with username, admin status, and creation date. Supports adding users, resetting passwords (generates random password shown once), and deleting users (last admin cannot be deleted).
API endpoints (via auth.php): users (GET), create_user (POST), reset_password (POST), delete_user (POST).
All modals are in src/components/modals/ and expose show() / hide() methods via Vue's expose.
Add or edit a network asset. Fields: address (MAC or IP depending on assetsBy setting), name, vendor, comment. Creates via POST or updates via PUT based on rowid prop.
Add or edit a block/allow list entry. Fields: IOC (domain/IP), include subdomains toggle, comment, active toggle. The iocType prop (bl or wl) determines which list is targeted.
Fetches available feeds from ioc2rpz.net (via ioc2rpz_available API endpoint), displays them in a selectable table with feed name, type (community/premium), rule count, and description. Supports bulk selection with a policy action selector. Requires a TSIG key configured in BIND.
Policy actions: given, nxdomain, nodata, passthru, drop, cname.
Creates a local RPZ zone. Fields: feed name (DNS name validated), policy action, CNAME target (if applicable), description. Validates DNS naming conventions (alphanumeric, hyphens, dots; max 253 chars; labels max 63 chars).
Adds a third-party RPZ feed via zone transfer. Fields: feed name, primary server IP, optional TSIG authentication (key name, algorithm, secret), policy action, CNAME target, description.
TSIG algorithms: HMAC-SHA256 (recommended), HMAC-SHA512, HMAC-SHA384, HMAC-SHA224, HMAC-SHA1 (legacy), HMAC-MD5 (deprecated).
Creates a new user account. Fields: username, password, confirm password, admin privileges checkbox. Password validation: 8+ characters with uppercase, lowercase, number, and symbol — or 18+ character passphrase.
Edits an existing RPZ feed's settings. Feed name is read-only. Available fields depend on source type:
- ioc2rpz.net: Policy action and CNAME target only
- Local: Policy action, CNAME target, description
- Third-party: Primary server, TSIG settings, policy action, CNAME target, description
Predefined allow feeds are restricted to passthru action. Predefined block feeds cannot use passthru or given.
Uploads a SQLite database file (.sqlite, .gzip, .zip) with selectable import targets: assets, block list, allow list, and query/hits logs at all aggregation tiers (raw, 5m, 1h, 1d). Shows upload progress bar and validation spinner.
Changes the current user's password. Fields: current password, new password, confirm new password. Same password complexity rules as AddUser. Submits to /rpi_admin/auth.php?action=change_password.
Located in src/composables/.
Centralized API client wrapping Axios for all backend communication.
Parameters: None (uses global API_BASE = '/rpi_admin/rpidata.php').
Return values:
| Method | Signature | Description |
|---|---|---|
get |
(params: Object) → Promise<any> |
GET request with query parameters |
post |
(params: Object, data: Object) → Promise<any> |
POST with query params and body |
put |
(params: Object, data: Object) → Promise<any> |
PUT with query params and body |
del |
(params: Object) → Promise<any> |
DELETE with query parameters |
getTableData |
(ctx: Object) → Promise<{items, records}> |
Table data provider with sort support |
uploadFile |
(params, formData, onProgress, cancelToken) → Promise<any> |
File upload with progress tracking |
createCancelToken |
() → CancelTokenSource |
Create Axios cancel token |
isCancel |
(error) → boolean |
Check if error is a cancellation |
Includes a global Axios response interceptor that dispatches a session-expired custom event on HTTP 401 responses, triggering automatic logout in App.vue.
Manages periodic data refresh with localStorage persistence.
Parameters:
| Parameter | Type | Description |
|---|---|---|
storageKey |
String |
localStorage key for persisting enabled state |
refreshFn |
Function |
Callback invoked on each refresh tick |
isActiveFn |
Function |
Returns true if the owning tab is active (default: () => true) |
Return values:
| Property | Type | Description |
|---|---|---|
autoRefreshEnabled |
Ref<Boolean> |
Reactive toggle for auto-refresh |
Refresh interval is 60 seconds. Toggling autoRefreshEnabled on triggers an immediate refresh. The interval runs continuously but only calls refreshFn when both enabled and active.
Shared research link definitions for external threat intelligence services. Consumed by both ResearchLinks.vue (Dashboard popovers) and ContextMenu.vue (right-click menus on QueryLog and RPZ Log).
Exports:
| Export | Type | Description |
|---|---|---|
RESEARCH_LINKS |
Array<{ name, urlTemplate, icon }> |
Constant array of research link definitions with {domain} placeholder in URL templates |
getResearchUrls(domain) |
Function → Array<{ name, url, icon }> |
Generates resolved research URLs for a given domain by replacing the {domain} placeholder |
Included services: DuckDuckGo (quoted search), Google (quoted search), VirusTotal, DomainTools Whois, Robtex, ThreatMiner.
Encapsulates the smart block/allow decision logic for context menu actions. Uses useApi internally for backend API calls. Determines the correct operation (remove from a local feed or open the AddIOC modal) based on the domain's current feed membership.
Return values (from useSmartActions()):
| Method | Signature | Description |
|---|---|---|
smartBlock |
(domain: string) → Promise<{ action, list?, type?, error? }> |
If domain is in the allow list, removes it and returns { action: 'removed', list: 'whitelist' }; otherwise returns { action: 'add-ioc', type: 'bl' } |
smartAllow |
(domain: string, feedName: string) → Promise<{ action, list?, type?, error? }> |
If feed is a local block feed and domain is in the block list, removes it and returns { action: 'removed', list: 'blacklist' }; otherwise returns { action: 'add-ioc', type: 'wl' } |
isLocalBlockFeed |
(feedName: string) → boolean |
Returns true when feedName === 'block.ioc2rpz.rpidns' |
isLocalFeed |
(feedName: string) → boolean |
Returns true for any of the four predefined local RPZ zones |
Local feed detection: The four predefined local RPZ zones are allow.ioc2rpz.rpidns, block.ioc2rpz.rpidns, allow-ip.ioc2rpz.rpidns, block-ip.ioc2rpz.rpidns.
Tracks window dimensions and calculates responsive layout values. Note: this composable uses the Vue 2 Vue.observable API and is a legacy holdover — the active responsive logic in App.vue uses inline window.addEventListener('resize', ...) instead.
Return values:
| Property | Type | Description |
|---|---|---|
state.windowInnerWidth |
Number |
Current window width |
state.logs_height |
Number |
Calculated log table height (innerHeight > 400 ? innerHeight - 240 : 150) |
state.logs_pp |
Number |
Logs per page based on viewport |
initWindowSize() |
Function |
Start tracking (call in mounted) |
destroyWindowSize() |
Function |
Stop tracking (call in beforeUnmount) |
Also exports useSharedWindowSize() for singleton access across components.
base: '/rpi_admin/dist/' // Deployed under the PHP admin pathManual chunks for optimized loading:
vendor-vue— Vue corevendor-bootstrap— Bootstrap Vue Next + Bootstrap CSSvendor-charts— ApexCharts + vue3-apexchartsvendor-utils— Axios
Path alias: @ → src/
Dev server proxy: /rpi_admin/rpidata.php → http://localhost:8080 for local development against a running backend.
test: {
include: ['src/__tests__/**/*.test.js'],
environment: 'node',
globals: true
}Tests run in Node environment with global test functions (describe, it, expect). Property-based tests use fast-check (v4.x).
| Script | Command | Description |
|---|---|---|
dev |
vite |
Start Vite dev server with HMR |
build |
vite build |
Production build to dist/ |
build:dev |
vite build --mode development --sourcemap |
Development build with source maps |
preview |
vite preview |
Preview production build locally |
copy:dist |
rm -rf ../www/rpi_admin/dist && cp -r dist ../www/rpi_admin/dist |
Copy build output to PHP serving directory |
build:deploy |
npm run build && npm run copy:dist |
Build and deploy in one step |
build:deploy:dev |
npm run build:dev && npm run copy:dist |
Dev build and deploy |
test |
vitest |
Run tests in watch mode |
The build:deploy workflow is the standard deployment path: Vite builds the frontend into rpidns-frontend/dist/, then copy:dist moves it to www/rpi_admin/dist/ where Nginx serves it.
Creates the Vue 3 app instance and configures:
- Bootstrap Vue Next plugin and all component/directive registrations (globally registered)
- vue3-apexcharts plugin for chart components
- Global color palette (
$gColors) with 50 colors for chart series - Global properties:
$rpiver,$assetsBy,$addressType(fromwindow.RPIDNS_CONFIG) - Mounts to
#app
- Architecture Overview — System-level component interactions and data flow
- Backend API Reference — PHP API endpoints consumed by the frontend
- Database Schema — SQLite tables and aggregation tiers queried by the frontend
- BIND Configuration — RPZ feeds and policy actions managed through the Admin UI
- Configuration Files —
rpisettings.phpsettings displayed in the Settings component - Docker Deployment — Container setup for serving the frontend via OpenResty
- README — Project overview and getting started