A lightweight alert dashboard for Alertmanager, Grafana and Zabbix. Built with Rust (Axum) and a single-page HTML frontend — no database, no dependencies at runtime.
⚠️ Security: AlertView has no built-in authentication and binds to0.0.0.0. Its/api/alertsendpoint exposes alert data (hostnames, labels, messages). Never expose it directly to an untrusted network — always place it behind a reverse proxy with authentication and TLS (see Reverse proxy).
Features:
- Aggregates alerts from multiple Alertmanager, Grafana and/or Zabbix sources (Zabbix 6.0 → 7.x, detected at runtime)
- Severity-colored cards (critical / error / high / warning / info), ranking configurable via
display.severity_order - Search by label straight from the search box:
team=sre|dba, hostname~web(=,!=,~,|for several values), mixed with free text — Ctrl+F / Cmd+F or/jumps into it - The severity dot opens a link built from the alert's labels (
display.alert_link_template) and lights up on hover, independent from the ↗ "open in the source" button - Labels shown in front of the alert name (
display.prefix_labels) - Automatic light/dark theme following the OS, TV mode startable by default
- Filter by severity and source, and show or hide silenced alerts
- Multi-source filter chips
- Direct links to specific alerts (Zabbix builds a
problem.viewURL from the trigger id, Alertmanager/Grafana use the alert's generator URL) - TV mode for wall displays — dense rows aligned in columns, a permanent status bar (source dots, clock, last refresh, version) with the controls behind a
+, URL-persisted filters - Dark/light theme with custom CSS support
- Automatic config reload — changes to the config file are detected and applied without restart
- Sound notifications for new alerts (using Web Audio API)
- Timezone support (local, UTC, or any IANA timezone)
- Response caching with configurable TTL
- Retry logic with exponential backoff per source
- Gzip compression for API responses
- Health check endpoint (
/health), and a graceful shutdown on SIGTERM - Says when it is stale — a backend it cannot reach gets a banner and a dimmed list, never a frozen dashboard that reads as "all quiet"
- Keyboard-navigable — every filter chip and group header takes focus and answers Enter/Space
- Installable as a PWA — add AlertView to your home screen on Android, iOS or desktop (standalone, full-screen)
- Requirements
- Configuration
- Command Line
- Environment Variables
- Running Locally
- Docker
- Kubernetes Deployment
- Install as an App (PWA)
- API
- Tests
- Credits
- Rust 1.75+ (build only)
- Docker (optional, for containerized deployment)
Copy config.example to config.yaml and edit it. The config file is automatically reloaded when modified.
This example shows all available options:
port: 8080
refresh_interval: 30 # seconds between auto-refreshes
tls_insecure: false # set true to skip TLS verification (self-signed certs)
sources:
- name: "Alertmanager"
type: alertmanager
url: "http://alertmanager.monitoring.svc.cluster.local:9093"
dashboard_url: "https://grafana.example.com/alerting/list" # optional link on cards
severity_label: "severity" # label used to classify severity (case-insensitive, default: "severity")
- name: "Grafana"
type: grafana
url: "http://grafana.monitoring.svc.cluster.local:3000"
bearer_token: "glsa_xxxx" # Grafana service account token
# or: basic_auth: { username: admin, password: secret }
- name: "Zabbix"
type: zabbix
url: "https://zabbix.example.com/zabbix"
bearer_token: "YOUR_ZABBIX_TOKEN_HERE" # Zabbix API token
dashboard_url: "https://zabbix.example.com/zabbix/zabbix.php?action=problem.view"
display:
labels: # which labels to show on each alert card
- namespace
- job
- instance
- host
- hostgroupAlertmanager only:
sources:
- name: "Alertmanager"
type: alertmanager
url: "http://localhost:9093"Grafana only:
sources:
- name: "Grafana"
type: grafana
url: "http://localhost:3000"
bearer_token: "your_token_here"Zabbix only:
sources:
- name: "Zabbix"
type: zabbix
url: "http://zabbix-server/zabbix"
bearer_token: "your_zabbix_token"Note for Zabbix: alert links are built as
zabbix.php?action=problem.view&triggerids[]=<ID>, derived fromdashboard_urlwhen you set one.eventidandtriggeridare exposed as labels if you would rather write your ownlink_template.
Customize the display of alerts:
display:
# Labels to show on each alert card
labels:
- namespace
- job
- instance
- host
- hostgroup
# Theme: "dark", "light", or URL to custom CSS file
# theme: "dark"
# Timezone: "local", "UTC", or any IANA timezone (e.g., "Europe/Paris", "America/New_York")
# timezone: "local"
# Enable sound notifications for new alerts (uses Web Audio API)
# Different sounds for each severity level (critical, high, warning, info)
# play_sounds: falseEach source supports additional configuration:
sources:
- name: "Alertmanager"
type: alertmanager
url: "http://alertmanager.example.com:9093"
timeout: 30 # seconds (default: 15)
link_template: "https://grafana.com/alerts?query={{.Labels.alertname}}"
retry_policy:
max_retries: 5 # default: 3
initial_delay_ms: 2000 # default: 1000 (1 second)
max_delay_ms: 60000 # default: 30000 (30 seconds)Link Template Variables:
{{.Labels.<key>}}- Any label value (e.g.,{{.Labels.namespace}}){{.Annotations.<key>}}- Any annotation value (e.g.,{{.Annotations.summary}}){{.Fingerprint}}- Alert fingerprint{{.Source}},{{.SourceType}},{{.Status}},{{.Severity}},{{.Name}}{{.StartsAt}},{{.EndsAt}}- Timestamps
Enable response caching to reduce load on your alert sources:
# Global cache TTL in seconds (0 = disabled)
cache_ttl_seconds: 60One entry per source. While an entry is being refreshed, the browsers that arrive meanwhile wait for that single fetch instead of each firing their own.
config.yamlis gitignored — never commit credentials.
alertview # uses ./config.yaml
alertview /etc/alertview/config.yaml # positional path
alertview --config /etc/alertview/config.yaml
alertview --version # prints the running version
alertview --helpThe running version is also printed on the first log line at startup, shown in
the footer of the dashboard and in the TV mode status bar, and sent as the
User-Agent on every request to a source.
Configuration path precedence: --config <path>, then a positional argument,
then $ALERTVIEW_CONFIG, then ./config.yaml.
AlertView can be configured entirely through environment variables:
| Variable | Default | Description |
|---|---|---|
ALERTVIEW_PORT |
8080 | Port to listen on |
ALERTVIEW_REFRESH_INTERVAL |
30 | Seconds between auto-refreshes |
ALERTVIEW_CACHE_TTL |
0 | Cache TTL in seconds (0 = disabled) |
ALERTVIEW_LOG_FORMAT |
text | Log format: text or json |
Example:
# Run with environment variables
ALERTVIEW_PORT=9090 ALERTVIEW_LOG_FORMAT=json cargo run
# Or with Docker
docker run -e ALERTVIEW_PORT=9090 -e ALERTVIEW_LOG_FORMAT=json -p 9090:9090 alertviewcargo run -- config.yaml
# open http://localhost:8080Note: The config file is automatically reloaded when modified. No restart needed.
# Build the image
docker build -t alertview .
# Run with the config file mounted read-only
docker run -p 8080:8080 -v $(pwd)/config.yaml:/config/config.yaml:ro alertviewNote:
:rois enough, including for auto-reload — AlertView only ever reads the file, and a bind mount shows it the host's changes. Mount the directory rather than the file (-v $(pwd)/conf:/config:ro) if you edit with something that saves by renaming, like vim: a single-file bind mount stays attached to the old inode and the change would go unnoticed.
A pre-built image is published to GHCR for every released version (v* tag):
# Pull the latest image
docker pull ghcr.io/frakev/alertview:latest
# Run it
docker run -p 8080:8080 -v $(pwd)/config.yaml:/config/config.yaml:ro ghcr.io/frakev/alertview:latestExample docker-compose.yml:
version: '3.8'
services:
alertview:
image: ghcr.io/frakev/alertview:latest
ports:
- "8080:8080"
volumes:
- ./config.yaml:/config/config.yaml:ro
restart: unless-stoppedRun with: docker compose up -d
The Kubernetes manifests (prefixed with numbers: 01-namespace.yaml, 02-configmap.yaml, etc.) deploy AlertView into its own namespace using a ConfigMap for configuration.
- Kubernetes cluster
kubectlconfigured to access your cluster- (Optional) Ingress controller if using the ingress manifest
| File | Purpose | What to change |
|---|---|---|
01-namespace.yaml |
Creates the alertview namespace |
Usually no changes needed |
02-configmap.yaml |
Configuration (sources, tokens, URLs) | Alertmanager/Grafana/Zabbix URLs, dashboard links, tokens |
03-deployment.yaml |
Deployment configuration | Resource limits, replicas |
04-service.yaml |
Service (ClusterIP) | Port, service type |
05-ingress.yaml |
Ingress for external access | Your domain, TLS secret name, annotations |
Method 1: Apply manifests individually
kubectl apply -f 01-namespace.yaml
kubectl apply -f 02-configmap.yaml
kubectl apply -f 03-deployment.yaml
kubectl apply -f 04-service.yaml
kubectl apply -f 05-ingress.yamlMethod 2: Use the Makefile
# For standard kubectl
make deploy
# For microk8s
KUBECTL=microk8s kubectl make deploy
# For other custom kubectl
KUBECTL=/path/to/your/kubectl make deployFor local deployments (binary or Docker), the config file is automatically reloaded when modified.
For Kubernetes deployments, since ConfigMaps are mounted as read-only volumes, you need to restart the deployment after changing the ConfigMap:
# Apply the updated configmap
kubectl apply -f 02-configmap.yaml
# Restart the deployment to pick up changes
kubectl rollout restart deployment/alertview -n alertview
# Or use the Makefile
make restartNote: The automatic config reload feature does not work with Kubernetes ConfigMaps because they are mounted as read-only. Consider using a sidecar like
configmap-reloador mounting the config from an emptyDir volume with an initContainer that copies from the ConfigMap.
After deployment:
- Internal access:
http://alertview.alertview.svc.cluster.local:8080 - External access (if ingress configured):
https://your-domain.com
Check pods and service:
kubectl get all -n alertview.github/workflows/docker-publish.yml builds and pushes the image to GHCR on version tags only (v*) — pushing to main used to build a second, identical image per release. It uses GITHUB_TOKEN, so no extra secret is required.
.github/workflows/ci.yml runs clippy (-D warnings), the test suite and a syntax check of the frontend on every push to main and on every pull request. .github/workflows/release.yml builds the Linux binary and creates the GitHub release, also on tags.
push to main → ghcr.io/frakev/alertview:main
push v1.2.3 → ghcr.io/frakev/alertview:1.2.3 + :latest
The workflow targets a self-hosted runner labeled k8s-home. Change runs-on in the workflow file if your runner has a different label.
AlertView is a Progressive Web App, so it can be installed on a phone, tablet or desktop and launched like a native app — full-screen, with its own icon and no browser address bar.
- Open AlertView in Chrome over HTTPS (e.g.
https://alerts.example.com). - Open the ⋮ menu and tap Install app (or Add to Home screen). Chrome may also show an install banner automatically.
- AlertView opens standalone from your home screen.
Open AlertView, tap the Share button, then Add to Home Screen.
Click the install icon in the address bar, or use the browser menu → Install AlertView.
- A secure context is required: this means HTTPS, or
http://localhostfor local testing. Plain-HTTP access over a LAN IP (e.g.http://192.168.1.10:8080) will not offer installation. The provided Kubernetes ingress already terminates TLS via cert-manager / Let's Encrypt — just point it at your own domain.
The PWA assets are embedded in the binary and served from these routes:
| Route | Purpose |
|---|---|
/manifest.webmanifest |
App metadata (name, icons, standalone display) |
/sw.js |
Service worker (caches the static shell for fast/offline launch) |
/icons/*.png |
App icons (192px, 512px, maskable, apple-touch) |
Live data is never cached. The service worker only caches the static app shell (HTML/CSS/JS/icons). Requests to
/api/*,/events(SSE) and/healthalways go straight to the network, so alerts stay real-time.
AlertView provides a simple REST API for programmatic access to alerts.
| Method | Endpoint | Description |
|---|---|---|
GET |
/ |
Web UI dashboard |
GET |
/api/alerts |
JSON — all alerts aggregated from all configured sources |
GET |
/health |
Health check endpoint (returns "OK") |
GET |
/style.css |
Dashboard stylesheet |
GET |
/app.js |
Dashboard JavaScript |
Note: All endpoints except
/healthsupport gzip compression automatically.
{
"alerts": [
{
"fingerprint": "source1:abc123",
"source": "Alertmanager",
"source_type": "alertmanager",
"status": "firing",
"severity": "critical",
"name": "HighCPUUsage",
"labels": {
"namespace": "production",
"job": "node-exporter",
"instance": "server-1"
},
"annotations": {
"summary": "High CPU usage detected",
"description": "CPU usage is above 90% for 5 minutes"
},
"starts_at": "2024-01-15T10:30:00Z",
"ends_at": null,
"link_url": "https://grafana.example.com/alerting/list"
}
],
"sources": [
{
"name": "Alertmanager",
"status": "ok",
"alert_count": 5,
"error": null
}
],
"refresh_interval": 30,
"display_labels": ["namespace", "job", "instance"],
"timezone": "local",
"theme": null,
"play_sounds": false
}Additional response fields:
timezone: Current timezone setting (from config)theme: Current theme setting (from config, null if default)play_sounds: Whether sound notifications are enabled
Alert object:
fingerprint: Unique identifier (format:{source}:{internal_id})source: Name of the source as configuredsource_type: One ofalertmanager,grafana,zabbixstatus: One offiring,silenced,pendingseverity: One ofcritical,error,high,warning,info,none, or any level listed indisplay.severity_ordername: Alert namelabels: Object with alert labelsannotations: Object with alert annotationsstarts_at: RFC3339 timestamp when alert startedends_at: RFC3339 timestamp when alert ended (null if still active)link_url: Direct link to the alert in the source dashboard (if configured)
SourceStatus object:
name: Source namestatus:okorerroralert_count: Number of alerts from this sourceerror: Error message if status iserror, otherwise null
200 OK: Success500 Internal Server Error: Failed to fetch from one or more sources (partial results may still be returned)
AlertView includes unit tests for configuration parsing and link template rendering:
# Run all tests
cargo test
# Run with coverage (requires cargo-tarpaulin)
cargo tarpaulin --out HtmlThe tests verify:
- Configuration file loading and parsing
- Default values for all config options
- Source-specific configuration (timeout, retry policy)
- Link template rendering with various placeholders
- Display configuration (theme, timezone, labels)
AlertView is maintained by @frakev, who designed it, decided what it should do and runs it in production.
The code itself — the Rust backend, the frontend, the tests and this documentation — is written by Claude (Anthropic), through Claude Code, from their specifications and under their review.
AlertView is released under the MIT License.
