diff --git a/.gitignore b/.gitignore
index 00941bb5af067040269d23ab91d781bfb6cd5bc7..cfd4dc461a50e0a01b60ca0f88152e9ca9a2d787 100644
--- a/.gitignore
+++ b/.gitignore
@@ -27,3 +27,4 @@ tangostationcontrol/docs/build
 **/pending_log_messages.db
 **/.eggs
 
+docker-compose/alerta-web/alerta-secrets.json
diff --git a/docker-compose/alerta-web/Dockerfile b/docker-compose/alerta-web/Dockerfile
index 090c14354a37260d75a506cabd53226d9f35865b..80431da39da9ddb7ff0c28997660163234eb6d57 100644
--- a/docker-compose/alerta-web/Dockerfile
+++ b/docker-compose/alerta-web/Dockerfile
@@ -1,8 +1,14 @@
 FROM alerta/alerta-web
 
+RUN bash -c 'source /venv/bin/activate; pip install git+https://github.com/alerta/alerta-contrib.git#subdirectory=plugins/slack'
+RUN bash -c 'source /venv/bin/activate; pip install git+https://github.com/alerta/alerta-contrib.git#subdirectory=plugins/jira'
+
+COPY grafana-plugin /tmp/grafana-plugin
+RUN bash -c 'source /venv/bin/activate; pip install /tmp/grafana-plugin'
+
+COPY lofar-plugin /tmp/lofar-plugin
+RUN bash -c 'source /venv/bin/activate; pip install /tmp/lofar-plugin'
+
 COPY alertad.conf /app/alertad.conf
 COPY alerta.conf /app/alerta.conf
 COPY config.json /web/config.json
-
-RUN pip install git+https://github.com/alerta/alerta-contrib.git#subdirectory=plugins/slack
-
diff --git a/docker-compose/alerta-web/README.md b/docker-compose/alerta-web/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..8900026226cb6e3ee9c987792f24b44d8beff374
--- /dev/null
+++ b/docker-compose/alerta-web/README.md
@@ -0,0 +1,23 @@
+You need:
+
+* Your own Slack App:
+        * Give it channel write rights
+        * Get the OAuth token
+        * Install it in your slack
+        * Invite the app into your channel
+        * Feed the OAuth token to the config
+        * Add it to alerta-secrets.json
+* Grafana:
+        * By default, grafana resends alarms every 4h, configure this in the notification settings to faster resend deleted alarms for testing
+        * Add alerts by hand
+                * add "Summary" as alert text
+                * add label "severity": "major"/"minor"/etc (see https://docs.alerta.io/webui/configuration.html#severity-colors)
+
+* Create alerta-secrets.json in this directory:
+
+Example alerta-secrets.json:
+
+{
+ "SLACK_TOKEN": "xoxb-...",
+ "SLACK_CHANNEL": "#lofar20-alerta"
+}
diff --git a/docker-compose/alerta-web/alerta-secrets.json b/docker-compose/alerta-web/alerta-secrets.json
new file mode 100644
index 0000000000000000000000000000000000000000..8fb44d7b830c3090408fb3bd576fa297e0e2dcc9
--- /dev/null
+++ b/docker-compose/alerta-web/alerta-secrets.json
@@ -0,0 +1,4 @@
+{
+ "SLACK_TOKEN": "xoxb-get-this-from-your-slack-app",
+ "SLACK_CHANNEL": "#your-channel"
+}
diff --git a/docker-compose/alerta-web/alertad.conf b/docker-compose/alerta-web/alertad.conf
index 96c25994054d7ffffc1ca720c46ca734abf9be63..a9e3e93c6ed20e12751714c5680aa8a7c469003e 100644
--- a/docker-compose/alerta-web/alertad.conf
+++ b/docker-compose/alerta-web/alertad.conf
@@ -1,4 +1,56 @@
 DEBUG = True
 SECRET = "T=&7xvF2S&x7w_JAcq$h1x5ocfA)8H2i"
 
-PLUGINS = ['reject', 'blackout', 'normalise', 'enhance', 'slack']
+# Allow non-admin views
+CUSTOMER_VIEWS = True
+
+# Never timeout alerts
+ALERT_TIMEOUT = 0
+# Auto unack after a day
+ACK_TIMEOUT = 24 * 3600
+# Auto unshelve after 2 hours
+SHELVE_TIMEOUT = 2 * 3600
+
+# Use custom date formats
+DATE_FORMAT_MEDIUM_DATE = "dd DD/MM HH:mm"
+DATE_FORMAT_LONG_DATE   = "yyyy-MM-DD HH:mm:ss.sss"
+
+# Default columns to list
+COLUMNS = ['severity', 'status', 'lastReceiveTime', 'environment', 'resource', 'lofarDevice', 'lofarAttribute', 'event', 'text']
+
+# ------------------------------------
+#    Plugin configuration
+# ------------------------------------
+
+PLUGINS = ['reject', 'blackout', 'acked_by', 'enhance', 'grafana', 'lofar', 'slack']
+
+# Slack plugin settings, see https://github.com/alerta/alerta-contrib/tree/master/plugins/slack
+import os, json
+
+with open("/run/secrets/alerta-secrets") as secrets_file:
+    secrets = json.load(secrets_file)
+
+SLACK_WEBHOOK_URL = 'https://slack.com/api/chat.postMessage'
+SLACK_TOKEN = secrets["SLACK_TOKEN"]
+SLACK_CHANNEL = secrets["SLACK_CHANNEL"]
+SLACK_ATTACHMENTS = True
+DASHBOARD_URL = os.environ.get("DASHBOARD_URL", "")
+
+# for the Slack message configuration syntax, see https://api.slack.com/methods/chat.postMessage
+# and https://app.slack.com/block-kit-builder
+SLACK_PAYLOAD = {
+  "channel": "{{ channel }}",
+  "emoji": ":fire:",
+  "text": "*{{ alert.severity|capitalize }}* :: _{{ alert.event }}_\n\n```{{ alert.text }}```",
+  "attachments": [{
+    "color": "{{ color }}",
+    "fields": [
+      {"title": "Device", "value": "{{ alert.attributes.lofarDevice }}", "short": True },
+      {"title": "Attribute", "value": "{{ alert.attributes.lofarAttribute }}", "short": True },
+      {"title": "Environment", "value": "{{ alert.environment }}", "short": True },
+      {"title": "Status", "value": "{{ status|capitalize }}", "short": True },
+      {"title": "Dashboards", "value": "<{{ config.DASHBOARD_URL }}/#/alert/{{ alert.id }}|Alerta>\nGrafana <{{ alert.attributes.grafanaDashboardUrl }}|Dashboard> <{{ alert.attributes.grafanaPanelUrl }}|Panel>", "short": True },
+      {"title": "Configure", "value": "Grafana <{{ alert.attributes.grafanaAlertUrl }}|Edit> <{{ alert.attributes.grafanaSilenceUrl }}|Silence>", "short": True },
+    ],
+ }]
+}
diff --git a/docker-compose/alerta-web/grafana-plugin/alerta_grafana.py b/docker-compose/alerta-web/grafana-plugin/alerta_grafana.py
new file mode 100644
index 0000000000000000000000000000000000000000..917cddae1e57191151e8405eaeb277c330859745
--- /dev/null
+++ b/docker-compose/alerta-web/grafana-plugin/alerta_grafana.py
@@ -0,0 +1,32 @@
+import os
+import json
+import logging
+
+from alerta.plugins import PluginBase
+
+LOG = logging.getLogger()
+
+
+class EnhanceGrafana(PluginBase):
+    """
+    Plugin for parsing alerts coming from Grafana
+    """
+
+    def pre_receive(self, alert, **kwargs):
+        # Parse Grafana-specific fields
+        alert.attributes['grafanaStatus']       = alert.raw_data.get('status', '')
+        alert.attributes['grafanaPanelUrl']     = alert.raw_data.get('panelURL', '')
+        alert.attributes['grafanaDashboardUrl'] = alert.raw_data.get('dashboardURL', '')
+        alert.attributes['grafanaAlertUrl']     = alert.raw_data.get('generatorURL', '')
+        alert.attributes['grafanaSilenceUrl']   = alert.raw_data.get('silenceURL', '')
+
+        return alert
+
+    def post_receive(self, alert, **kwargs):
+        return
+
+    def status_change(self, alert, status, text, **kwargs):
+        return
+
+    def take_action(self, alert, action, text, **kwargs):
+        raise NotImplementedError
diff --git a/docker-compose/alerta-web/grafana-plugin/setup.py b/docker-compose/alerta-web/grafana-plugin/setup.py
new file mode 100644
index 0000000000000000000000000000000000000000..cb06d95919fde788f299e8318bfc23ef01dbfb79
--- /dev/null
+++ b/docker-compose/alerta-web/grafana-plugin/setup.py
@@ -0,0 +1,24 @@
+
+from setuptools import setup, find_packages
+
+version = '1.0.0'
+
+setup(
+    name="alerta-grafana",
+    version=version,
+    description='Alerta plugin for enhancing Grafana alerts',
+    url='https://git.astron.nl/lofar2.0/tango',
+    license='Apache License 2.0',
+    author='Jan David Mol',
+    author_email='mol@astron.nl',
+    packages=find_packages(),
+    py_modules=['alerta_grafana'],
+    include_package_data=True,
+    zip_safe=True,
+    entry_points={
+        'alerta.plugins': [
+            'grafana = alerta_grafana:EnhanceGrafana'
+        ]
+    },
+    python_requires='>=3.5'
+)
diff --git a/docker-compose/alerta-web/lofar-plugin/alerta_lofar.py b/docker-compose/alerta-web/lofar-plugin/alerta_lofar.py
new file mode 100644
index 0000000000000000000000000000000000000000..eaaed8614d34be68c7fa217e813760b75fe9161b
--- /dev/null
+++ b/docker-compose/alerta-web/lofar-plugin/alerta_lofar.py
@@ -0,0 +1,38 @@
+import os
+import json
+import logging
+
+from alerta.plugins import PluginBase
+
+LOG = logging.getLogger()
+
+
+class EnhanceLOFAR(PluginBase):
+    """
+    Plugin for enhancing alerts with LOFAR-specific information
+    """
+
+    def pre_receive(self, alert, **kwargs):
+        # Parse LOFAR-specific fields
+        for tag in alert.tags:
+            try:
+                key, value = tag.split("=", 1)
+            except ValueError:
+                continue
+
+            if key == "device":
+                alert.attributes['lofarDevice'] = value
+
+            if key == "name":
+                alert.attributes['lofarAttribute'] = value
+
+        return alert
+
+    def post_receive(self, alert, **kwargs):
+        return
+
+    def status_change(self, alert, status, text, **kwargs):
+        return
+
+    def take_action(self, alert, action, text, **kwargs):
+        raise NotImplementedError
diff --git a/docker-compose/alerta-web/lofar-plugin/setup.py b/docker-compose/alerta-web/lofar-plugin/setup.py
new file mode 100644
index 0000000000000000000000000000000000000000..70ab552180a5ad10a978fb10f1deeb0d87319bb7
--- /dev/null
+++ b/docker-compose/alerta-web/lofar-plugin/setup.py
@@ -0,0 +1,24 @@
+
+from setuptools import setup, find_packages
+
+version = '1.0.0'
+
+setup(
+    name="alerta-lofar",
+    version=version,
+    description='Alerta plugin for enhancing LOFAR alerts',
+    url='https://git.astron.nl/lofar2.0/tango',
+    license='Apache License 2.0',
+    author='Jan David Mol',
+    author_email='mol@astron.nl',
+    packages=find_packages(),
+    py_modules=['alerta_lofar'],
+    include_package_data=True,
+    zip_safe=True,
+    entry_points={
+        'alerta.plugins': [
+            'lofar = alerta_lofar:EnhanceLOFAR'
+        ]
+    },
+    python_requires='>=3.5'
+)
diff --git a/docker-compose/alerta-web/rules.json b/docker-compose/alerta-web/rules.json
new file mode 100644
index 0000000000000000000000000000000000000000..ca8df8cf7b01a4bd014387e045a2492d35292300
--- /dev/null
+++ b/docker-compose/alerta-web/rules.json
@@ -0,0 +1 @@
+{"test":[{"name":"test2","interval":"10s","rules":[{"expr":"","for":"20s","labels":{"severity":"major"},"annotations":{"__dashboardUid__":"nC8N_kO7k","__panelId__":"9","summary":"My test alert"},"grafana_alert":{"id":3,"orgId":1,"title":"FPGA processing error 2","condition":"B","data":[{"refId":"A","queryType":"","relativeTimeRange":{"from":600,"to":0},"datasourceUid":"ZqArtG97z","model":{"exemplar":false,"expr":"device_attribute{device=\"stat/sdp/1\",name=\"FPGA_error_R\"}","format":"time_series","group":[],"hide":false,"interval":"","intervalMs":1000,"legendFormat":"","maxDataPoints":43200,"metricColumn":"name","rawQuery":true,"rawSql":"SELECT\n  data_time AS \"time\",\n  x::text,\n  device,\n  name,\n  case when value then 1 else 0 end AS value\nFROM lofar_array_boolean\nWHERE\n  $__timeFilter(data_time) AND\n  name = 'fpga_error_r'\nORDER BY 1,2","refId":"A","select":[[{"params":["x"],"type":"column"}],[{"params":["value"],"type":"column"}]],"table":"lofar_array_boolean","timeColumn":"data_time","timeColumnType":"timestamptz","where":[{"name":"$__timeFilter","params":[],"type":"macro"},{"datatype":"text","name":"","params":["name","=","'fpga_error_r'"],"type":"expression"}]}},{"refId":"B","queryType":"","relativeTimeRange":{"from":0,"to":0},"datasourceUid":"-100","model":{"conditions":[{"evaluator":{"params":[0,0],"type":"gt"},"operator":{"type":"and"},"query":{"params":[]},"reducer":{"params":[],"type":"avg"},"type":"query"}],"datasource":{"type":"__expr__","uid":"__expr__"},"expression":"A","hide":false,"intervalMs":1000,"maxDataPoints":43200,"reducer":"last","refId":"B","settings":{"mode":"dropNN"},"type":"reduce"}}],"updated":"2022-04-04T14:18:48Z","intervalSeconds":10,"version":1,"uid":"waXdSCynk","namespace_uid":"9DkbdYy7z","namespace_id":6,"rule_group":"test2","no_data_state":"OK","exec_err_state":"Error"}}]},{"name":"test","interval":"10s","rules":[{"expr":"","for":"20s","labels":{"severity":"major"},"annotations":{"__dashboardUid__":"nC8N_kO7k","__panelId__":"9","summary":"My test alert"},"grafana_alert":{"id":2,"orgId":1,"title":"FPGA processing error","condition":"B","data":[{"refId":"A","queryType":"","relativeTimeRange":{"from":600,"to":0},"datasourceUid":"ZqArtG97z","model":{"exemplar":false,"expr":"device_attribute{device=\"stat/sdp/1\",name=\"FPGA_error_R\"}","format":"time_series","group":[],"hide":false,"interval":"","intervalMs":1000,"legendFormat":"","maxDataPoints":43200,"metricColumn":"name","rawQuery":true,"rawSql":"SELECT\n  data_time AS \"time\",\n  x::text,\n  device,\n  name,\n  case when value then 1 else 0 end AS value\nFROM lofar_array_boolean\nWHERE\n  $__timeFilter(data_time) AND\n  name = 'fpga_error_r'\nORDER BY 1,2","refId":"A","select":[[{"params":["x"],"type":"column"}],[{"params":["value"],"type":"column"}]],"table":"lofar_array_boolean","timeColumn":"data_time","timeColumnType":"timestamptz","where":[{"name":"$__timeFilter","params":[],"type":"macro"},{"datatype":"text","name":"","params":["name","=","'fpga_error_r'"],"type":"expression"}]}},{"refId":"B","queryType":"","relativeTimeRange":{"from":0,"to":0},"datasourceUid":"-100","model":{"conditions":[{"evaluator":{"params":[0,0],"type":"gt"},"operator":{"type":"and"},"query":{"params":[]},"reducer":{"params":[],"type":"avg"},"type":"query"}],"datasource":{"type":"__expr__","uid":"__expr__"},"expression":"A","hide":false,"intervalMs":1000,"maxDataPoints":43200,"reducer":"last","refId":"B","settings":{"mode":"dropNN"},"type":"reduce"}}],"updated":"2022-04-04T14:16:22Z","intervalSeconds":10,"version":1,"uid":"MIt4Ijs7k","namespace_uid":"9DkbdYy7z","namespace_id":6,"rule_group":"test","no_data_state":"OK","exec_err_state":"Error"}}]}]}
\ No newline at end of file
diff --git a/docker-compose/alerta.yml b/docker-compose/alerta.yml
index 6c30bf9dfc018e445b58faa83d807d5788bc3888..696fcb24e77871d0cd94fb9cefb3fc49158138e1 100644
--- a/docker-compose/alerta.yml
+++ b/docker-compose/alerta.yml
@@ -3,6 +3,10 @@ version: '2.1'
 volumes:
   alerta-postgres-data: {}
 
+secrets:
+  alerta-secrets:
+    file: alerta-web/alerta-secrets.json
+
 services:
   alerta-web:
     build: alerta-web
@@ -13,9 +17,12 @@ services:
       - "8081:8080"
     depends_on:
       - alerta-db
+    secrets:
+      - alerta-secrets
     environment:
       - DEBUG=1  # remove this line to turn DEBUG off
       - DATABASE_URL=postgres://postgres:postgres@alerta-db:5432/monitoring
+      - DASHBOARD_URL=http://${HOSTNAME}:8081
       - AUTH_REQUIRED=True
       - ADMIN_USERS=admin #default password: alerta
       - ADMIN_KEY=demo-key
diff --git a/docker-compose/grafana/Dockerfile b/docker-compose/grafana/Dockerfile
index e51cce5eeaa0310c1ecd698d8d797e3163ce4457..7eceb9c154c654da53eb0a4b060df945013bf766 100644
--- a/docker-compose/grafana/Dockerfile
+++ b/docker-compose/grafana/Dockerfile
@@ -3,6 +3,7 @@ FROM grafana/grafana
 # Install some plugins
 RUN grafana-cli plugins install briangann-datatable-panel
 RUN grafana-cli plugins install ae3e-plotly-panel
+RUN grafana-cli plugins install yesoreyeram-infinity-datasource
 
 COPY grafana.ini /etc/grafana/
 
diff --git a/docker-compose/grafana/README.md b/docker-compose/grafana/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..754c00a75abde5600ee65088d057558eabe02352
--- /dev/null
+++ b/docker-compose/grafana/README.md
@@ -0,0 +1,21 @@
+# Post configuration
+
+To export all current alert rules, use:
+
+To import rules into a fresh Grafana instance:
+
+  * Obtain an 'editor' API key through the Grafan GUI (cogwheel -> API keys),
+  * Run:
+
+  curl http://localhost:3000/api/alertmanager/grafana/config/api/v1/alerts -H 'Authorization: Bearer (api key)' > alerting.json
+  curl localhost:3000/api/ruler/grafana/api/v1/rules > rules.json
+
+  * Delete the UIDs in alerting.json
+
+To import rules into a fresh Grafana instance:
+
+  * Obtain an 'editor' API key through the Grafan GUI (cogwheel -> API keys),
+  * Run (first without piping to bash):
+
+  python3 import-rules.py -c alerting.json -r rules.json -B key | bash
+
diff --git a/docker-compose/grafana/grafana.ini b/docker-compose/grafana/grafana.ini
index 6be79d62ce20e1b456caa09292c5ced027c5dccb..acfabe0f10190c2b07ae579d21bd1abfc1891ff3 100644
--- a/docker-compose/grafana/grafana.ini
+++ b/docker-compose/grafana/grafana.ini
@@ -867,7 +867,9 @@ enabled = true
 
 [panels]
 # If set to true Grafana will allow script tags in text panels. Not recommended as it enable XSS vulnerabilities.
-;disable_sanitize_html = false
+
+# enable this to allow us to create mash ups with other pages
+disable_sanitize_html = true
 
 [plugins]
 ;enable_alpha = false
diff --git a/docker-compose/grafana/import-rules.py b/docker-compose/grafana/import-rules.py
new file mode 100755
index 0000000000000000000000000000000000000000..340215ce1e53744aef3a2722f69c3ecdfd28ca82
--- /dev/null
+++ b/docker-compose/grafana/import-rules.py
@@ -0,0 +1,74 @@
+#!/usr/bin/python3
+import json
+import os
+import argparse
+
+parser = argparse.ArgumentParser(
+  formatter_class=argparse.RawDescriptionHelpFormatter,
+  description=
+"""
+Generate rule import files and script for Grafana.
+
+This script expands a given rules.json file into individual rules and
+prints the bash commands to import them in Grafana.
+
+To export rules from Grafana, use
+  curl <grafana>/api/ruler/grafana/api/v1/rules > rules.json
+""")
+parser.add_argument(
+    '-c', '--alert-config-file', type=str, required=False, help="Input alertmanager configuration JSON to parse, output of 'curl <grafana>/api/ruler/grafana/api/v1/rules' [%(default)s]")
+parser.add_argument(
+    '-r', '--rules-file', type=str, required=True, help="Input rules JSON to parse, output of 'curl <grafana>/api/ruler/grafana/api/v1/rules' [%(default)s]")
+parser.add_argument(
+    '-o', '--output-dir', type=str, default="rules", help="Directory to store the output [%(default)s]")
+parser.add_argument(
+    '-B', '--authorization-bearer', type=str, default="abcdefghijklmnopqrstuvwxyz", help="Authorization bearer from the Grafana 'editor' API key [%(default)s]")
+parser.add_argument(
+    '-g', '--grafana_url', type=str, default="http://localhost:3000", help="Base URL of Grafana [%(default)s]")
+parser.add_argument(
+    '-u', '--update', default=False, action='store_true', help="Update existing alerts, instead of creating new ones [%(default)s]")
+
+args = parser.parse_args()
+
+if args.alert_config_file:
+  print(f"echo Importing alert configuration file {args.alert_config_file}")
+  print(f"curl -X POST {args.grafana_url}/api/alertmanager/grafana/config/api/v1/alerts -H 'Content-Type: application/json' -H 'Accept: application/json' -H 'Authorization: Bearer {args.authorization_bearer}' -d '@{args.alert_config_file}'")
+  print(f"echo ''")
+
+with open(args.rules_file) as f:
+  data=json.load(f)
+
+  try:
+    os.mkdir(args.output_dir)
+  except FileExistsError as e:
+    pass
+
+  # the rules are of format {"folder": [{alert}, {alert}] }
+  for folder, rules in data.items():
+    try:
+      os.mkdir(f"{args.output_dir}/{folder}")
+    except FileExistsError as e:
+      pass
+
+    # print command to create folder
+    payload = json.dumps({"title": folder})
+    print(f"echo Creating folder {folder}")
+    print(f"curl -X POST {args.grafana_url}/api/folders -H 'Content-Type: application/json' -H 'Accept: application/json' -H 'Authorization: Bearer {args.authorization_bearer}' -d '{payload}'")
+    print(f"echo ''")
+
+    for rule in rules:
+      rule_filename = f"{args.output_dir}/{folder}/{rule['name']}.json"
+
+      if not args.update:
+        # strip rule UIDs
+        for subrule in rule["rules"]:
+          del subrule["grafana_alert"]["uid"]
+
+      # dump this rule
+      with open(rule_filename, "w") as rule_file:
+        json.dump(rule, rule_file)
+
+      # print import statement for this rule
+      print(f"echo Processing rule {folder}/{rule['name']}")
+      print(f"curl -X POST {args.grafana_url}/api/ruler/grafana/api/v1/rules/{folder} -H 'Content-Type: application/json' -H 'Accept: application/json' -H 'Authorization: Bearer {args.authorization_bearer}' -d '@{rule_filename}'")
+      print(f"echo ''")
diff --git a/docker-compose/grafana/rules.json b/docker-compose/grafana/rules.json
new file mode 100644
index 0000000000000000000000000000000000000000..1469233d304ab0ff26d744dc0ac8738bddf03566
--- /dev/null
+++ b/docker-compose/grafana/rules.json
@@ -0,0 +1 @@
+{"station":[{"name":"FPGA processing error","interval":"10s","rules":[{"expr":"","for":"20s","labels":{"severity":"major"},"annotations":{"__dashboardUid__":"nC8N_kO7k","__panelId__":"9","summary":"One or more FPGAs are unusable."},"grafana_alert":{"id":4,"orgId":1,"title":"FPGA processing error","condition":"B","data":[{"refId":"A","queryType":"","relativeTimeRange":{"from":600,"to":0},"datasourceUid":"ZqArtG97z","model":{"format":"time_series","group":[],"hide":false,"intervalMs":1000,"maxDataPoints":43200,"metricColumn":"none","rawQuery":true,"rawSql":"SELECT\n  data_time AS \"time\",\n  x::text,\n  device,\n  name,\n  case when value then 1 else 0 end AS value\nFROM lofar_array_boolean\nWHERE\n  $__timeFilter(data_time) AND\n  name = 'fpga_error_r'\nORDER BY 1,2","refId":"A","select":[[{"params":["value_r"],"type":"column"}]],"table":"att_scalar_devdouble","timeColumn":"data_time","timeColumnType":"timestamp","where":[{"name":"$__timeFilter","params":[],"type":"macro"}]}},{"refId":"B","queryType":"","relativeTimeRange":{"from":0,"to":0},"datasourceUid":"-100","model":{"conditions":[{"evaluator":{"params":[0],"type":"gt"},"operator":{"type":"and"},"query":{"params":["A"]},"reducer":{"params":[],"type":"last"},"type":"query"}],"datasource":{"type":"__expr__","uid":"-100"},"expression":"A","hide":false,"intervalMs":1000,"maxDataPoints":43200,"reducer":"last","refId":"B","settings":{"mode":"dropNN"},"type":"reduce"}}],"updated":"2022-04-04T14:29:45Z","intervalSeconds":10,"version":1,"uid":"tzj3Ijynk","namespace_uid":"9DkbdYy7z","namespace_id":6,"rule_group":"FPGA processing error","no_data_state":"NoData","exec_err_state":"Alerting"}}]}]}
diff --git a/tangostationcontrol/docs/source/alerting.rst b/tangostationcontrol/docs/source/alerting.rst
new file mode 100644
index 0000000000000000000000000000000000000000..ec613fe0ff449602e5694f2e7b17117e7b424480
--- /dev/null
+++ b/tangostationcontrol/docs/source/alerting.rst
@@ -0,0 +1,11 @@
+Alerting
+==================
+
+To setup alerting, you first need to post-configure Grafana to populate it with alerting rules, and a policy to forward rules to Grafana:
+
+- Go to Grafana (http://localhost:3000) and sign in with an administration account (default: admin/admin),
+- Go to ``(cogwheel) -> API keys`` and create an ``editor`` API key. Copy the resulting hash,
+- Go to the ``docker-compose/grafana/`` source directory, and run::
+
+  ./import-rules.py -c alerting.json -r rules.json -B <apikey> | bash
+
diff --git a/tangostationcontrol/docs/source/index.rst b/tangostationcontrol/docs/source/index.rst
index c0b64b2a83975abf0636157347354506c0532d9d..d89a0cda0bef89798497ae94263cd0204c4dfe3a 100644
--- a/tangostationcontrol/docs/source/index.rst
+++ b/tangostationcontrol/docs/source/index.rst
@@ -30,6 +30,7 @@ Even without having access to any LOFAR2.0 hardware, you can install the full st
    devices/sst-xst
    devices/configure
    configure_station
+   alerting
    signal_chain
    beam_tracking
    developer
diff --git a/tangostationcontrol/docs/source/installation.rst b/tangostationcontrol/docs/source/installation.rst
index fd01fe45e0b27de2170c23341ab04e1c6b97f900..09877ef26a8c5f2ea71822338910b884e9bd7a3b 100644
--- a/tangostationcontrol/docs/source/installation.rst
+++ b/tangostationcontrol/docs/source/installation.rst
@@ -78,13 +78,3 @@ Configuration
 
 These sections are optional, to configure specific functionality you may or may not want to use.
 
-Alerta
-````````
-
-If you want Grafana alerts to appear in Alerta, you need to manually configure Grafana to forward them. Import the alert settings manually:
-
-- Go to Grafana (http://localhost:3000) and sign in with an administration account (default: admin/admin),
-- Go to ``Alerting`` and select ``Admin`` in the left menu bar,
-- Copy/paste the following information, and press ``Save``:
-
-.. literalinclude:: ../../../docker-compose/grafana/alerting.json