diff --git a/brian_dashboard_manager/grafana/provision.py b/brian_dashboard_manager/grafana/provision.py
index f04d851b4dbb9016cf3fae1e4c9db1793725f385..8a70538d6c125e2601169b6583cb2dde996faea6 100644
--- a/brian_dashboard_manager/grafana/provision.py
+++ b/brian_dashboard_manager/grafana/provision.py
@@ -11,9 +11,9 @@ from functools import reduce
 from concurrent.futures import Future
 from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor
 from brian_dashboard_manager.config import DEFAULT_ORGANIZATIONS, STATE_PATH
-from brian_dashboard_manager.grafana.utils.request import \
-    AdminRequest, \
+from brian_dashboard_manager.grafana.utils.request import AdminRequest, \
     TokenRequest
+
 from brian_dashboard_manager.grafana.organization import \
     get_organizations, create_organization, create_api_token, \
     delete_api_token, delete_expired_api_tokens, set_home_dashboard
@@ -25,15 +25,11 @@ from brian_dashboard_manager.grafana.folder import find_folder, \
     delete_folder
 from brian_dashboard_manager.inventory_provider.interfaces import \
     get_gws_direct, get_gws_indirect, get_interfaces
-from brian_dashboard_manager.templating.nren_access import generate_nrens
 
-from brian_dashboard_manager.templating.helpers import is_re_customer, \
-    is_cls_peer, is_cls, is_ias_customer, is_ias_private, is_ias_public, \
-    is_ias_upstream, is_ias_peer, is_lag_backbone, is_nren, is_phy_upstream, \
-    is_re_peer, is_gcs, is_cae1, is_geantopen, is_l2circuit, is_lhcone_peer, \
-    is_lhcone_customer, is_lhcone, is_mdvpn, get_aggregate_dashboard_data, \
-    get_interface_data, parse_backbone_name, parse_phy_upstream_name, \
-    get_dashboard_data, get_aggregate_interface_data
+from brian_dashboard_manager.templating.helpers import \
+    get_aggregate_dashboard_data, get_interface_data, \
+    get_nren_interface_data, get_dashboard_data, \
+    get_nren_dashboard_data, get_aggregate_interface_data
 
 from brian_dashboard_manager.templating.gws import generate_gws, \
     generate_indirect
@@ -43,46 +39,37 @@ from brian_dashboard_manager.templating.render import render_dashboard
 logger = logging.getLogger(__name__)
 
 
-def generate_all_nrens(token_request, nrens, folder_id, datasource_name):
-    provisioned = []
-    with ThreadPoolExecutor(max_workers=8) as executor:
-        for dashboard in generate_nrens(nrens, datasource_name):
-            res = executor.submit(create_dashboard, token_request,
-                                  dashboard, folder_id)
-            provisioned.append(res)
-    return [r.result() for r in provisioned]
-
-
-def provision_folder(token_request, folder_name,
-                     dash, excluded_interfaces, datasource_name,
-                     excluded_dashboards):
+def provision_folder(token_request, folder_name, dash,
+                     ds_name, excluded_dashboards):
+    """
+    Function to provision dashboards within a folder.
+    """
 
     folder = find_folder(token_request, folder_name)
-
-    predicate = dash['predicate']
     tag = dash['tag']
+    interfaces = dash['interfaces']
 
-    # dashboard will include error panel
+    # dashboard should include error panels
     errors = dash.get('errors', False)
 
-    # custom parsing function for description to dashboard name
-    parse_func = dash.get('parse_func')
-
-    relevant_interfaces = filter(predicate, excluded_interfaces)
-    data = get_interface_data(relevant_interfaces, parse_func)
-    dash_data = get_dashboard_data(data, datasource_name, tag, errors)
+    is_nren = folder_name == 'NREN Access'
+    if is_nren:
+        data = get_nren_interface_data(interfaces)
+        dash_data = get_nren_dashboard_data(data, ds_name, tag)
+    else:
+        data = get_interface_data(interfaces)
+        dash_data = get_dashboard_data(data, ds_name, tag, errors)
 
     if not isinstance(excluded_dashboards, list):
         excluded_dashboards = []
     else:
-        excluded_dashboards = list(
-            map(lambda s: s.lower(), excluded_dashboards))
+        excluded_dashboards = [s.lower() for s in excluded_dashboards]
 
     provisioned = []
 
     with ThreadPoolExecutor(max_workers=4) as executor:
         for dashboard in dash_data:
-            rendered = render_dashboard(dashboard)
+            rendered = render_dashboard(dashboard, nren=is_nren)
             if rendered.get('title').lower() in excluded_dashboards:
                 executor.submit(delete_dashboard, token_request,
                                 rendered, folder['id'])
@@ -92,19 +79,19 @@ def provision_folder(token_request, folder_name,
     return [r.result() for r in provisioned]
 
 
-def provision_aggregate(token_request, agg_type, aggregate_folder,
-                        dash, excluded_interfaces, datasource_name):
-    predicate = dash['predicate']
-    tag = dash['tag']
+def provision_aggregate(token_request, folder,
+                        dash, ds_name):
 
-    relevant_interfaces = filter(predicate, excluded_interfaces)
-    data = get_aggregate_interface_data(relevant_interfaces, agg_type)
+    name = dash['dashboard_name']
+    tag = dash['tag']
+    interfaces = dash['interfaces']
+    data = get_aggregate_interface_data(interfaces, name)
 
     dashboard = get_aggregate_dashboard_data(
-        f'Aggregate - {agg_type}', data, datasource_name, tag)
+        f'Aggregate - {name}', data, ds_name, tag)
 
     rendered = render_dashboard(dashboard)
-    return create_dashboard(token_request, rendered, aggregate_folder['id'])
+    return create_dashboard(token_request, rendered, folder['id'])
 
 
 def provision_maybe(config):
@@ -182,86 +169,130 @@ def provision(config):
                 logger.info(
                     f'Provisioned datasource: {datasource["name"]}')
 
-        excluded_nrens = org_config.get('excluded_nrens', [])
-        excluded_nrens = list(map(lambda f: f.lower(), excluded_nrens))
+        excluded_nrens = org_config['excluded_nrens']
 
         def excluded(interface):
-            desc = interface.get('description', '').lower()
-            lab = 'lab.office' in interface.get('router', '').lower()
-            excluded_desc = any(
-                nren.lower() in desc for nren in excluded_nrens)
-            return not (excluded_desc or lab)
+            desc = interface['description'].lower()
+            lab = 'lab.office' in interface['router'].lower()
+            to_exclude = any(nren.lower() in desc for nren in excluded_nrens)
+            return not (to_exclude or lab)
 
-        excluded_interfaces = list(filter(excluded, interfaces))
+        relevant_interfaces = list(filter(excluded, interfaces))
 
         dashboards = {
+            'NREN': {
+                'tag': ['customers'],
+                'folder_name': 'NREN Access',
+                'interfaces': []
+            },
             'CLS': {
-                'predicate': is_cls,
-                'tag': 'CLS'
+                'tag': 'CLS',
+                'folder_name': 'CLS',
+                'interfaces': []
             },
-            'RE PEER': {
-                'predicate': is_re_peer,
-                'tag': 'RE_PEER'
+            'RE_PEER': {
+                'tag': 'RE_PEER',
+                'folder_name': 'RE Peer',
+                'interfaces': []
             },
-            'RE CUST': {
-                'predicate': is_re_customer,
-                'tag': 'RE_CUST'
+            'RE_CUST': {
+                'tag': 'RE_CUST',
+                'folder_name': 'RE Customer',
+                'interfaces': []
             },
             'GEANTOPEN': {
-                'predicate': is_geantopen,
-                'tag': 'GEANTOPEN'
+                'tag': 'GEANTOPEN',
+                'folder_name': 'GEANTOPEN',
+                'interfaces': []
             },
             'GCS': {
-                'predicate': is_gcs,
-                'tag': 'AUTOMATED_L2_CIRCUITS'
+                'tag': 'AUTOMATED_L2_CIRCUITS',
+                'folder_name': 'GCS',
+                'interfaces': []
             },
-            'L2 CIRCUIT': {
-                'predicate': is_l2circuit,
-                'tag': 'L2_CIRCUITS'
+            'L2_CIRCUIT': {
+                'tag': 'L2_CIRCUITS',
+                'folder_name': 'L2 Circuit',
+                'interfaces': []
             },
-            'LHCONE PEER': {
-                'predicate': is_lhcone_peer,
-                'tag': 'LHCONE_PEER'
+            'LHCONE_PEER': {
+                'tag': 'LHCONE_PEER',
+                'folder_name': 'LHCONE Peer',
+                'interfaces': []
             },
-            'LHCONE CUST': {
-                'predicate': is_lhcone_customer,
-                'tag': 'LHCONE_CUST'
+            'LHCONE_CUST': {
+                'tag': 'LHCONE_CUST',
+                'folder_name': 'LHCONE Customer',
+                'interfaces': []
             },
-            'MDVPN Customers': {
-                'predicate': is_mdvpn,
-                'tag': 'MDVPN'
+            'MDVPN_CUSTOMERS': {
+                'tag': 'MDVPN',
+                'folder_name': 'MDVPN Customers',
+                'interfaces': []
             },
-            'Infrastructure Backbone': {
-                'predicate': is_lag_backbone,
+            'INFRASTRUCTURE_BACKBONE': {
                 'tag': 'BACKBONE',
                 'errors': True,
-                'parse_func': parse_backbone_name
+                'folder_name': 'Infrastructure Backbone',
+                'interfaces': []
             },
-            'IAS PRIVATE': {
-                'predicate': is_ias_private,
-                'tag': 'IAS_PRIVATE'
+            'IAS_PRIVATE': {
+                'tag': 'IAS_PRIVATE',
+                'folder_name': 'IAS Private',
+                'interfaces': []
             },
-            'IAS PUBLIC': {
-                'predicate': is_ias_public,
-                'tag': 'IAS_PUBLIC'
+            'IAS_PUBLIC': {
+                'tag': 'IAS_PUBLIC',
+                'folder_name': 'IAS Public',
+                'interfaces': []
             },
-            'IAS CUSTOMER': {
-                'predicate': is_ias_customer,
-                'tag': 'IAS_CUSTOMER'
+            'IAS_CUSTOMER': {
+                'tag': 'IAS_CUSTOMER',
+                'folder_name': 'IAS Customer',
+                'interfaces': []
             },
-            'IAS UPSTREAM': {
-                'predicate': is_ias_upstream,
-                'tag': ['IAS_UPSTREAM', 'UPSTREAM']
+            'IAS_UPSTREAM': {
+                'tag': ['IAS_UPSTREAM', 'UPSTREAM'],
+                'folder_name': 'IAS Upstream',
+                'interfaces': []
             },
-            'GWS PHY Upstream': {
-                'predicate': is_phy_upstream,
+            'GWS_PHY_UPSTREAM': {
                 'tag': ['GWS_UPSTREAM', 'UPSTREAM'],
                 'errors': True,
-                'parse_func': parse_phy_upstream_name
+                'folder_name': 'GWS PHY Upstream',
+                'interfaces': []
+            }
+        }
+
+        agg_dashboards = {
+            'CLS_PEERS': {
+                'tag': 'cls_peers',
+                'dashboard_name': 'CLS Peers',
+                'interfaces': []
+            },
+            'IAS_PEERS': {
+                'tag': 'ias_peers',
+                'dashboard_name': 'IAS Peers',
+                'interfaces': []
+            },
+            'IAS_UPSTREAM': {
+                'tag': 'gws_upstreams',
+                'dashboard_name': 'GWS Upstreams',
+                'interfaces': []
+            },
+            'LHCONE': {
+                'tag': 'lhcone',
+                'dashboard_name': 'LHCONE',
+                'interfaces': []
+            },
+            'CAE1': {
+                'tag': 'cae',
+                'dashboard_name': 'CAE1',
+                'interfaces': []
             }
         }
         # Provision dashboards, overwriting existing ones.
-        datasource_name = datasource.get('name', 'PollerInfluxDB')
+        ds_name = datasource.get('name', 'PollerInfluxDB')
         excluded_folders = org_config.get('excluded_folders', {})
 
         def get_uid(prev, curr):
@@ -270,8 +301,8 @@ def provision(config):
 
         # Map of dashboard UID -> whether it has been updated.
         # This is used to remove stale dashboards at the end.
-        dash_list = find_dashboard(token_request) or []
-        dash_list = reduce(get_uid, dash_list, {})
+        updated = find_dashboard(token_request) or []
+        updated = reduce(get_uid, updated, {})
 
         def update_dash_list(dashboards):
             for dashboard in dashboards:
@@ -279,16 +310,35 @@ def provision(config):
                     dashboard = dashboard.result()
                 if dashboard is None:
                     continue
-                dash_list[dashboard.get('uid')] = True
+                updated[dashboard.get('uid')] = True
 
+        # loop over interfaces and add them to the dashboard_name
+        # -> folder mapping structure `dashboards` above, for convenience.
+        for iface in relevant_interfaces:
+            for dash_name in iface['dashboards']:
+
+                # add interface to matched dashboard
+                if dash_name in dashboards:
+                    ifaces = dashboards[dash_name]['interfaces']
+                    ifaces.append(iface)
+
+                # add to matched aggregate dashboard
+                if dash_name in agg_dashboards:
+                    ifaces = agg_dashboards[dash_name]['interfaces']
+                    ifaces.append(iface)
+
+        # provision dashboards and their folders
         with ProcessPoolExecutor(max_workers=4) as executor:
             provisioned = []
-            for folder_name, dash in dashboards.items():
+            for folder in dashboards.values():
+                folder_name = folder['folder_name']
                 exclude = excluded_folders.get(folder_name)
+
+                # boolean True means entire folder excluded
+                # if list, it is specific dashboard names not to provision
+                # so is handled at provision time.
                 if exclude:
                     if isinstance(exclude, bool):
-                        # boolean True -> entire folder excluded
-                        # list -> dashboard names not to provision
                         executor.submit(
                             delete_folder, token_request, folder_name)
                         continue
@@ -296,8 +346,7 @@ def provision(config):
                 logger.info(
                     f'Provisioning {org["name"]}/{folder_name} dashboards')
                 res = executor.submit(provision_folder, token_request,
-                                      folder_name, dash,
-                                      excluded_interfaces, datasource_name,
+                                      folder_name, folder, ds_name,
                                       exclude)
                 provisioned.append(res)
 
@@ -316,11 +365,11 @@ def provision(config):
             delete_folder(token_request, folder_name)
         else:
             folder = find_folder(token_request, folder_name)
-            with ProcessPoolExecutor(max_workers=4) as executor:
+            with ThreadPoolExecutor(max_workers=4) as executor:
                 gws_indirect_data = get_gws_indirect(
                     config['inventory_provider'])
                 provisioned = []
-                dashes = generate_indirect(gws_indirect_data, datasource_name)
+                dashes = generate_indirect(gws_indirect_data, ds_name)
                 for dashboard in dashes:
                     rendered = render_dashboard(dashboard)
                     provisioned.append(executor.submit(create_dashboard,
@@ -338,11 +387,11 @@ def provision(config):
             delete_folder(token_request, folder_name)
         else:
             folder = find_folder(token_request, folder_name)
-            with ProcessPoolExecutor(max_workers=4) as executor:
+            with ThreadPoolExecutor(max_workers=4) as executor:
                 gws_data = get_gws_direct(config['inventory_provider'])
                 provisioned = []
 
-                for dashboard in generate_gws(gws_data, datasource_name):
+                for dashboard in generate_gws(gws_data, ds_name):
                     rendered = render_dashboard(dashboard)
                     provisioned.append(executor.submit(create_dashboard,
                                                        token_request,
@@ -350,29 +399,6 @@ def provision(config):
 
                 update_dash_list(provisioned)
 
-        aggregate_dashboards = {
-            'CLS PEERS': {
-                'predicate': is_cls_peer,
-                'tag': 'cls_peers',
-            },
-            'IAS PEERS': {
-                'predicate': is_ias_peer,
-                'tag': 'ias_peers',
-            },
-            'GWS UPSTREAMS': {
-                'predicate': is_ias_upstream,
-                'tag': 'gws_upstreams',
-            },
-            'LHCONE': {
-                'predicate': is_lhcone,
-                'tag': 'lhcone',
-            },
-            'CAE1': {
-                'predicate': is_cae1,
-                'tag': 'cae',
-            }
-        }
-
         exclude_agg = excluded_folders.get('Aggregates', [])
 
         if isinstance(exclude_agg, bool) and exclude_agg:
@@ -382,45 +408,31 @@ def provision(config):
             with ProcessPoolExecutor(max_workers=4) as executor:
                 provisioned = []
                 agg_folder = find_folder(token_request, 'Aggregates')
-                for agg_type, dash in aggregate_dashboards.items():
-                    if agg_type in exclude_agg:
-                        dash_name = {'title': f'Aggregate - {agg_type}'}
+                for dash in agg_dashboards.values():
+                    if dash['dashboard_name'] in exclude_agg:
+                        dash_name = {
+                            'title': f'Aggregate - {dash["dashboard_name"]}'}
                         executor.submit(delete_dashboard,
                                         token_request, dash_name,
                                         agg_folder['id'])
                         continue
                     logger.info(f'Provisioning {org["name"]}' +
-                                f'/Aggregate {agg_type} dashboards')
-                    res = executor.submit(provision_aggregate, token_request,
-                                          agg_type, agg_folder, dash,
-                                          excluded_interfaces, datasource_name)
+                                f'/Aggregate {dash["dashboard_name"]} dashboards')  # noqa: E501
+                    res = executor.submit(
+                        provision_aggregate, token_request,
+                        agg_folder, dash, ds_name)
                     provisioned.append(res)
 
                 update_dash_list(provisioned)
 
-        # NREN Access dashboards
-        # uses a different template than the above.
-        logger.info('Provisioning NREN Access dashboards')
-
-        folder = find_folder(token_request, 'NREN Access')
-
-        nrens = filter(is_nren, excluded_interfaces)
-        provisioned = generate_all_nrens(
-            token_request, nrens, folder['id'], datasource_name)
-
-        for dashboard in provisioned:
-            if dashboard is None:
-                continue
-            dash_list[dashboard.get('uid')] = True
-
-        # Non-generated dashboards
+        # Statically defined dashboards from json files
         excluded_dashboards = org_config.get('excluded_dashboards', [])
         logger.info('Provisioning static dashboards')
         for dashboard in get_dashboard_definitions():
             if dashboard['title'] not in excluded_dashboards:
                 res = create_dashboard(token_request, dashboard)
                 if res:
-                    dash_list[res.get('uid')] = True
+                    updated[res.get('uid')] = True
             else:
                 delete_dashboard(token_request, dashboard)
 
@@ -430,9 +442,9 @@ def provision(config):
         is_staff = org['name'] == 'GÉANT Staff'
         set_home_dashboard(token_request, is_staff)
         # just hardcode that we updated home dashboard
-        dash_list['home'] = True
+        updated['home'] = True
 
-        for dash, provisioned in dash_list.items():
+        for dash, provisioned in updated.items():
             if not provisioned:
                 logger.info(f'Deleting stale dashboard with UID {dash}')
                 delete_dashboard(token_request, {'uid': dash})
diff --git a/brian_dashboard_manager/templating/helpers.py b/brian_dashboard_manager/templating/helpers.py
index 5ce022c39c01206662da9017ef85c27f4cb02a77..b7c9e0e87bc89ae28be0f7a7e71a1f30bd40b204 100644
--- a/brian_dashboard_manager/templating/helpers.py
+++ b/brian_dashboard_manager/templating/helpers.py
@@ -1,16 +1,14 @@
 """
-Predicates
-and helper functions used to group interfaces together and generate the
-necessary data for the dashboard templates.
+Helper functions used to group interfaces together and generate the
+necessary data to generate the dashboards from templates.
 """
-import re
 import logging
 import json
 from itertools import product
 from functools import reduce
 from string import ascii_uppercase
 from brian_dashboard_manager.templating.render import create_panel, \
-    create_panel_target
+    create_panel_target, create_dropdown_panel
 
 PANEL_HEIGHT = 12
 PANEL_WIDTH = 24
@@ -18,128 +16,6 @@ PANEL_WIDTH = 24
 logger = logging.getLogger(__file__)
 
 
-def get_description(interface):
-    return interface.get('description', '').strip()
-
-
-def is_physical_interface(interface):
-    return re.match('^PHY', get_description(interface))
-
-
-def is_aggregate_interface(interface):
-    return re.match('^LAG', get_description(interface))
-
-
-def is_logical_interface(interface):
-    return re.match('^SRV_', get_description(interface))
-
-
-def is_nren(interface):
-    regex = '(PHY|LAG|(SRV_(GLOBAL|LHCONE|MDVPN|IAS|CLS|L3VPN))) CUSTOMER'
-    return re.match(regex, get_description(interface))
-
-
-def is_cls(interface):
-    return 'SRV_CLS' in get_description(interface)
-
-
-def is_cls_peer(interface):
-    return 'SRV_CLS PRIVATE' in get_description(interface)
-
-
-def is_ias_public(interface):
-    return 'SRV_IAS PUBLIC' in get_description(interface)
-
-
-def is_ias_private(interface):
-    return 'SRV_IAS PRIVATE' in get_description(interface)
-
-
-def is_ias_customer(interface):
-    return 'SRV_IAS CUSTOMER' in get_description(interface)
-
-
-def is_ias_upstream(interface):
-    return 'SRV_IAS UPSTREAM' in get_description(interface)
-
-
-def is_ias_peer(interface):
-    return is_ias_public(interface) or is_ias_private(interface)
-
-
-def is_re_peer(interface):
-    return 'SRV_GLOBAL RE_INTERCONNECT' in get_description(interface)
-
-
-def is_re_customer(interface):
-    regex = '(PHY|LAG|SRV_GLOBAL) CUSTOMER'
-    return re.match(regex, get_description(interface))
-
-
-def is_gcs(interface):
-    return re.match('^SRV_GCS', get_description(interface))
-
-
-def is_geantopen(interface):
-    return 'GEANTOPEN' in get_description(interface)
-
-
-def is_l2circuit(interface):
-    return 'SRV_L2CIRCUIT' in get_description(interface)
-
-
-def is_lhcone_peer(interface):
-    description = get_description(interface)
-    return 'LHCONE' in description and 'SRV_L3VPN RE' in description
-
-
-def is_lhcone_customer(interface):
-    description = get_description(interface)
-    return 'LHCONE' in description and 'SRV_L3VPN CUSTOMER' in description
-
-
-def is_lhcone(interface):
-    regex = 'SRV_L3VPN (CUSTOMER|RE_INTERCONNECT)'
-    return re.match(regex, get_description(interface))
-
-
-def is_mdvpn(interface):
-    return re.match('^SRV_MDVPN CUSTOMER', get_description(interface))
-
-
-def is_infrastructure_backbone(interface):
-    regex = '(SRV_GLOBAL|LAG|PHY) INFRASTRUCTURE BACKBONE'
-    return re.match(regex, get_description(interface))
-
-
-def is_lag_backbone(interface):
-    is_lag = 'LAG' in get_description(interface)
-    return is_infrastructure_backbone(interface) and is_lag
-
-
-def is_cae1(interface):
-    iface_regex = r'^ae12(\.\d+|$)$'
-    is_router = interface.get('router', '').lower() == 'mx1.lon.uk.geant.net'
-    is_iface = re.match(iface_regex, interface.get('name'))
-    return is_router and is_iface
-
-
-def parse_backbone_name(description, *args, **kwargs):
-    link = description.split('|')[1].strip()
-    link = link.replace('( ', '(')
-    return link
-
-
-def is_phy_upstream(interface):
-    return re.match('^PHY UPSTREAM', get_description(interface))
-
-
-def parse_phy_upstream_name(description, host):
-    name = description.split(' ')[2].strip().upper()
-    location = host.split('.')[1].upper()
-    return f'{name} - {location}'
-
-
 def num_generator(start=1):
     num = start
     while True:
@@ -187,47 +63,114 @@ def letter_generator():
         yield result
 
 
-# peer_predicate is a function that is used to filter the interfaces
-# parse_func receives interface information and returns a peer name.
-def get_interface_data(interfaces, name_parse_func=None):
+def get_nren_interface_data(interfaces):
+    """
+    Helper for grouping interfaces into groups of NRENs
+    Extracts information from interfaces to be used in panels.
+    NREN dashboards have aggregate panels at the top and
+    dropdowns for services / physical interfaces.
+    """
     result = {}
 
-    if not name_parse_func:
-        # Most (but not all) descriptions use a format
-        # which has the peer name as the third element.
-        def name_parse_func(desc, *args, **kwargs):
-            return desc.split(' ')[2].upper()
-
     for interface in interfaces:
 
-        description = interface.get('description', '').strip()
-        interface_name = interface.get('name')
-        host = interface.get('router', '')
+        description = interface['description'].strip()
+        interface_name = interface['name']
+        host = interface['router']
+
+        router = host.replace('.geant.net', '')
+        panel_title = f"{router} - {{}} - {interface_name} - {description}"
+
+        info = interface['dashboard_info']
+
+        dashboard_name = info['name']
 
-        dashboard_name = name_parse_func(description, host)
+        dashboard = result.get(dashboard_name, {
+            'AGGREGATES': [],
+            'SERVICES': [],
+            'PHYSICAL': []
+        })
+
+        location = host.split('.')[1].upper()
 
-        peer = result.get(dashboard_name, [])
+        if info['interface_type'] == 'AGGREGATE':
+            dashboard['AGGREGATES'].append({
+                'interface': interface_name,
+                'hostname': host,
+                'alias': f"{location} - {dashboard_name} ({interface_name})"
+            })
+
+            # link aggregates are also shown
+            # under the physical dropdown
+            dashboard['PHYSICAL'].append({
+                'title': panel_title,
+                'hostname': host,
+                'interface': interface_name
+            })
+
+        elif info['interface_type'] == 'LOGICAL':
+            dashboard['SERVICES'].append({
+                'title': panel_title,
+                'hostname': host,
+                'interface': interface_name
+            })
+        elif info['interface_type'] == 'PHYSICAL':
+            dashboard['PHYSICAL'].append({
+                'title': panel_title,
+                'hostname': host,
+                'interface': interface_name
+            })
+
+        result[dashboard_name] = dashboard
+    return result
+
+
+def get_interface_data(interfaces):
+    """
+    Helper for grouping interfaces into dashboards.
+    Extracts information from interfaces to be used in panels.
+    """
+    result = {}
+
+    for interface in interfaces:
+
+        description = interface['description'].strip()
+        interface_name = interface['name']
+        host = interface['router']
 
         router = host.replace('.geant.net', '')
         panel_title = f"{router} - {{}} - {interface_name} - {description}"
 
-        peer.append({
+        info = interface['dashboard_info']
+
+        dashboard_name = info['name']
+
+        dashboard = result.get(dashboard_name, [])
+        dashboard.append({
             'title': panel_title,
             'interface': interface_name,
             'hostname': host,
             'has_v6': len(interface.get('ipv6', [])) > 0
         })
-        result[dashboard_name] = peer
+
+        result[dashboard_name] = dashboard
     return result
 
 
 def get_aggregate_interface_data(interfaces, agg_type):
+    """
+    Helper for grouping interfaces into groups of remotes
+    (ISP/NREN/...) used for aggregate dashboards
+    Extracts information from interfaces to be used in panels.
+
+    Aggregate dashboards have aggregates at the top for all remotes
+    as well as aggregate panels for specific remotes.
+    This builds a dict with interfaces for each remote
+    and one with all interfaces.
+    """
+
     result = []
 
-    # Aggregate dashboards have aggregates at the top for all remotes
-    # as well as aggregate panels for specific remotes.
-    # This builds a dict with interfaces for each remote
-    # and one with all interfaces.
     def reduce_func(prev, curr):
         remotes = prev.get(curr['remote'], [])
         remotes.append(curr)
@@ -239,11 +182,10 @@ def get_aggregate_interface_data(interfaces, agg_type):
 
     for interface in interfaces:
 
-        description = interface.get('description', '').strip()
         interface_name = interface.get('name')
         host = interface.get('router', '')
 
-        remote = description.split(' ')[2].upper()
+        remote = interface['dashboard_info']['name']
 
         location = host.split('.')[1].upper()
         result.append({
@@ -256,9 +198,11 @@ def get_aggregate_interface_data(interfaces, agg_type):
     return reduce(reduce_func, result, {})
 
 
-# Helper used for generating stacked aggregate panels
-# with multiple target fields (ingress/egress)
 def get_aggregate_targets(targets):
+    """
+    Helper used for generating panel fields for aggregate panels
+    with multiple target fields (ingress/egress)
+    """
     ingress = []
     egress = []
 
@@ -287,9 +231,11 @@ def get_aggregate_targets(targets):
     return ingress, egress
 
 
-# Helper used for generating all traffic/error panels
-# with a single target field (ingress/egress or err/s)
 def get_panel_fields(panel, panel_type, datasource):
+    """
+    Helper for generating a single panel,
+    with ingress/egress and percentile targets
+    """
     letters = letter_generator()
 
     def get_target_data(alias, field):
@@ -329,28 +275,102 @@ def get_panel_fields(panel, panel_type, datasource):
     })
 
 
-def get_dashboard_data(data, datasource, tag, errors=False):
-    id_gen = num_generator()
-    gridPos = gridPos_generator(id_gen)
+def panel_generator(gridPos):
+    """
+    Shared wrapper for shorter calls without
+    gridPos to generate panels.
 
-    def get_panel_definitions(panels, datasource):
+    Generates panels used in a normal dashboard
+    for all traffic + (conditionally) IPv6 + Errors
+    """
+    def get_panel_definitions(panels, datasource, errors=False):
         result = []
+
         for panel in panels:
-            result.append(get_panel_fields(
-                {**panel, **next(gridPos)}, 'traffic', datasource))
+            result.append(get_panel_fields({
+                **panel,
+                **next(gridPos)
+            }, 'traffic', datasource))
             if panel.get('has_v6', False):
-                result.append(get_panel_fields(
-                    {**panel, **next(gridPos)}, 'IPv6', datasource))
+                result.append(get_panel_fields({
+                    **panel,
+                    **next(gridPos)
+                }, 'IPv6', datasource))
             if errors:
-                result.append(get_panel_fields(
-                    {**panel, **next(gridPos)}, 'errors', datasource))
+                result.append(get_panel_fields({
+                    **panel,
+                    **next(gridPos)
+                }, 'errors', datasource))
+
         return result
 
+    return get_panel_definitions
+
+
+def get_nren_dashboard_data(data, datasource, tag):
+    """
+    Generates all panels used in a NREN dashboard,
+    including dropdowns and aggregate panels.
+    """
+
+    for nren, dash in data.items():
+        id_gen = num_generator()
+
+        gridPos = gridPos_generator(id_gen, start=1)
+
+        panel_gen = panel_generator(gridPos)
+
+        if len(dash['AGGREGATES']) > 0:
+            agg_panels = create_aggregate_panel(
+                f'Aggregate - {nren}',
+                gridPos_generator(id_gen, agg=True),
+                dash['AGGREGATES'], datasource)
+        else:
+            # if there's no aggregate panel(s), start other stuff at y=0.
+            gridPos = gridPos_generator(id_gen, start=0)
+            agg_panels = []
+
+        services_dropdown = create_dropdown_panel('Services', **next(gridPos))
+        service_panels = panel_gen(dash['SERVICES'], datasource)
+        iface_dropdown = create_dropdown_panel('Interfaces', **next(gridPos))
+        phys_panels = panel_gen(dash['PHYSICAL'], datasource, True)
+
+        result = {
+            'nren_name': nren,
+            'datasource': datasource,
+            'aggregate_panels': agg_panels,
+            'dropdown_groups': [
+                {
+                    'dropdown': services_dropdown,
+                    'panels': service_panels,
+                },
+                {
+                    'dropdown': iface_dropdown,
+                    'panels': phys_panels,
+                }
+            ]
+        }
+        if isinstance(tag, list):
+            result['tags'] = tag
+        else:
+            result['tag'] = tag
+
+        yield result
+
+
+def get_dashboard_data(data, datasource, tag, errors=False):
+    """
+    Generates all panels used in a normal dashboard without aggregate panels
+    """
+    id_gen = num_generator()
+    gridPos = gridPos_generator(id_gen)
+    panel_gen = panel_generator(gridPos)
+
     for dashboard_name, panels in data.items():
         result = {
             'title': dashboard_name,
             'datasource': datasource,
-            'panels': get_panel_definitions(panels, datasource),
+            'panels': list(panel_gen(panels, datasource, errors)),
         }
 
         if isinstance(tag, list):
@@ -362,6 +382,10 @@ def get_dashboard_data(data, datasource, tag, errors=False):
 
 
 def create_aggregate_panel(title, gridpos, targets, datasource):
+    """
+    Generates a single panel with multiple targets.
+    Each target is one interface / line on the graph
+    """
 
     ingress_targets, egress_targets = get_aggregate_targets(targets)
     result = []
@@ -409,6 +433,19 @@ def create_aggregate_panel(title, gridpos, targets, datasource):
 
 
 def get_aggregate_dashboard_data(title, targets, datasource, tag):
+    """
+    Creates three types of aggregate panels:
+    Aggregate Ingress/Egress that contain
+    every target (interface) given as parameter
+
+    Totals Ingress/Egress which is the same as above,
+    but with a different line color.
+
+    Aggregates for each remote
+    (all interfaces for each remote (ISP/NREN/...)
+    on separate graphs
+    """
+
     id_gen = num_generator()
     gridPos = gridPos_generator(id_gen, agg=True)
 
diff --git a/brian_dashboard_manager/templating/nren_access.py b/brian_dashboard_manager/templating/nren_access.py
deleted file mode 100644
index 67ba24cdacf17ef72b715225dc50bd35913dec74..0000000000000000000000000000000000000000
--- a/brian_dashboard_manager/templating/nren_access.py
+++ /dev/null
@@ -1,137 +0,0 @@
-import json
-import os
-import jinja2
-from concurrent.futures import ProcessPoolExecutor
-from brian_dashboard_manager.templating.render import create_dropdown_panel
-from brian_dashboard_manager.templating.helpers import \
-    is_aggregate_interface, is_logical_interface, is_physical_interface, \
-    num_generator, gridPos_generator, get_panel_fields, create_aggregate_panel
-
-
-def get_nrens(interfaces):
-    result = {}
-    for interface in interfaces:
-
-        description = interface.get('description', '').strip()
-
-        nren_name = description.split(' ')[2].upper()
-
-        nren = result.get(
-            nren_name, {'AGGREGATES': [], 'SERVICES': [], 'PHYSICAL': []})
-
-        interface_name = interface.get('name')
-        host = interface.get('router', '')
-        router = host.replace('.geant.net', '')
-        panel_title = f"{router} - {{}} - {interface_name} - {description}"
-
-        location = host.split('.')[1].upper()
-        if is_aggregate_interface(interface):
-            nren['AGGREGATES'].append({
-                'interface': interface_name,
-                'hostname': host,
-                'alias': f"{location} - {nren_name} ({interface_name})"
-            })
-
-            # link aggregates are also shown
-            # under the physical dropdown
-            nren['PHYSICAL'].append({
-                'title': panel_title,
-                'hostname': host,
-                'interface': interface_name
-            })
-
-        elif is_logical_interface(interface):
-            nren['SERVICES'].append({
-                'title': panel_title,
-                'hostname': host,
-                'interface': interface_name
-            })
-        elif is_physical_interface(interface):
-            nren['PHYSICAL'].append({
-                'title': panel_title,
-                'hostname': host,
-                'interface': interface_name
-            })
-
-        result[nren_name] = nren
-    return result
-
-
-id_gen = num_generator(start=1)
-
-# aggregate panels have y=0, start generating at 1*height
-gridPos = gridPos_generator(id_gen, start=1)
-
-
-def get_panel_definitions(panels, datasource, errors=False):
-    result = []
-    for panel in panels:
-        result.append(get_panel_fields(
-            {**panel, **next(gridPos)}, 'traffic', datasource))
-        if panel.get('has_v6', False):
-            result.append(get_panel_fields(
-                {**panel, **next(gridPos)}, 'IPv6', datasource))
-        if errors:
-            result.append(get_panel_fields(
-                {**panel, **next(gridPos)}, 'errors', datasource))
-    return result
-
-
-def build_data(nren, data, datasource):
-    global gridPos
-    if len(data['AGGREGATES']) > 0:
-        agg_panels = create_aggregate_panel(
-            f'Aggregate - {nren}',
-            gridPos_generator(id_gen, agg=True),
-            data['AGGREGATES'], datasource)
-    else:
-        # if there's no aggregate panel(s), start other stuff at y=0.
-        gridPos = gridPos_generator(id_gen, start=0)
-        agg_panels = []
-    services_dropdown = create_dropdown_panel('Services', **next(gridPos))
-    service_panels = get_panel_definitions(data['SERVICES'], datasource)
-    iface_dropdown = create_dropdown_panel('Interfaces', **next(gridPos))
-    phys_panels = get_panel_definitions(data['PHYSICAL'], datasource, True)
-
-    return {
-        'nren_name': nren,
-        'datasource': datasource,
-        'aggregate_panels': agg_panels,
-        'dropdown_groups': [
-            {
-                'dropdown': services_dropdown,
-                'panels': service_panels,
-            },
-            {
-                'dropdown': iface_dropdown,
-                'panels': phys_panels,
-            }
-        ]
-    }
-
-
-def get_dashboard_data(interfaces, datasource):
-    nren_data = get_nrens(interfaces)
-    result = []
-    with ProcessPoolExecutor(max_workers=4) as executor:
-        for nren, data in nren_data.items():
-            result.append(executor.submit(build_data, nren, data, datasource))
-    return [r.result() for r in result]
-
-
-def generate_nrens(interfaces, datasource):
-    file = os.path.abspath(os.path.join(
-        os.path.dirname(__file__),
-        'templates',
-        'nren_access',
-        'nren-dashboard.json.j2'))
-
-    with open(file) as f:
-        template = jinja2.Template(f.read())
-
-    for dashboard in get_dashboard_data(interfaces, datasource):
-        rendered = template.render(dashboard)
-        rendered = json.loads(rendered)
-        rendered['uid'] = None
-        rendered['id'] = None
-        yield rendered
diff --git a/test/test_aggregrate.py b/test/test_aggregrate.py
index ac0df399fbcda6c2997770980f2df137db06261a..a992dc52600b5486f2ea42c60d01e4976ab7836e 100644
--- a/test/test_aggregrate.py
+++ b/test/test_aggregrate.py
@@ -1,7 +1,6 @@
 from brian_dashboard_manager.grafana.utils.request import TokenRequest
 import responses
-from brian_dashboard_manager.grafana.provision import provision_aggregate, \
-    is_cls_peer
+from brian_dashboard_manager.grafana.provision import provision_aggregate
 
 DEFAULT_REQUEST_HEADERS = {
     "Content-type": "application/json",
@@ -9,125 +8,265 @@ DEFAULT_REQUEST_HEADERS = {
 }
 
 
-TEST_INTERFACES = [
-    {
-        "router": "mx1.ath2.gr.geant.net",
-        "name": "xe-1/0/1",
-        "bundle": [],
-        "bundle-parents": [],
-        "snmp-index": 569,
-        "description": "PHY RESERVED | New OTEGLOBE ATH2-VIE 10Gb LS",
-        "circuits": []
-    },
-    {
-        "router": "mx1.ath2.gr.geant.net",
-        "name": "ge-1/3/7",
-        "bundle": [],
-        "bundle-parents": [],
-        "snmp-index": 543,
-        "description": "PHY SPARE",
-        "circuits": []
-    },
-    {
-        "router": "mx1.ham.de.geant.net",
-        "name": "xe-2/2/0.13",
-        "bundle": [],
-        "bundle-parents": [],
-        "snmp-index": 721,
-        "description": "SRV_L2CIRCUIT CUSTOMER WP6T3 WP6T3 #ham_lon2-WP6-GTS_20063 |",  # noqa: E501
-        "circuits": [
-            {
-                "id": 52382,
-                "name": "ham_lon2-WP6-GTS_20063_L2c",
-                "type": "",
-                "status": "operational"
-            }
-        ]
-    },
-    {
-        "router": "mx1.fra.de.geant.net",
-        "name": "ae27",
-        "bundle": [],
-        "bundle-parents": [
-            "xe-10/0/2",
-            "xe-10/3/2",
-            "xe-10/3/3"
-        ],
-        "snmp-index": 760,
-        "description": "LAG CUSTOMER ULAKBIM SRF9940983 |",
-        "circuits": [
-            {
-                "id": 40983,
-                "name": "ULAKBIM AP2 LAG",
-                "type": "",
-                "status": "operational"
-            }
-        ]
-    },
-    {
-        "router": "mx2.zag.hr.geant.net",
-        "name": "xe-2/1/0",
-        "bundle": [],
-        "bundle-parents": [],
-        "snmp-index": 739,
-        "description": "PHY SPARE",
-        "circuits": []
-    },
-    {
-        "router": "rt1.rig.lv.geant.net",
-        "name": "xe-0/1/5",
-        "bundle": [],
-        "bundle-parents": [],
-        "snmp-index": 539,
-        "description": "PHY SPARE",
-        "circuits": []
-    },
-    {
-        "router": "srx1.ch.office.geant.net",
-        "name": "ge-0/0/0",
-        "bundle": [],
-        "bundle-parents": [],
-        "snmp-index": 513,
-        "description": "Reserved for GEANT OC to test Virgin Media link",
-        "circuits": []
-    },
-    {
-        "router": "mx1.par.fr.geant.net",
-        "name": "xe-4/1/4.1",
-        "bundle": [],
-        "bundle-parents": [],
-        "snmp-index": 1516,
-        "description": "SRV_L2CIRCUIT INFRASTRUCTURE JRA1 JRA1 | #SDX-L2_PILOT-Br52 OF-P3_par   ",  # noqa: E501
-        "circuits": []
-    },
-    {
-        "router": "mx1.lon.uk.geant.net",
-        "name": "lt-1/3/0.61",
-        "bundle": [],
-        "bundle-parents": [],
-        "snmp-index": 1229,
-        "description": "SRV_IAS INFRASTRUCTURE ACCESS GLOBAL #LON-IAS-RE-Peering | BGP Peering - IAS Side",  # noqa: E501
-        "circuits": []
-    },
-    {
-        "router": "mx1.sof.bg.geant.net",
-        "name": "xe-2/0/5",
-        "bundle": [],
-        "bundle-parents": [],
-        "snmp-index": 694,
-        "description": "PHY RESERVED | Prime Telecom Sofia-Bucharest 3_4",
-        "circuits": []
-    },
-    {
-        "router": "mx1.sof.bg.geant.net",
-        "name": "xe-2/0/5",
-        "bundle": [],
-        "bundle-parents": [],
-        "snmp-index": 694,
-        "description": "SRV_GLOBAL CUSTOMER HEANET TESTDESCRIPTION |",
-        "circuits": []
-    }
-]
+TEST_DASHBOARD = {
+    "tag": "TEST_AGGREGATE",
+    "dashboard_name": "TEST CLS Peers",
+    "interfaces": [
+        {
+            "router": "mx1.gen.ch.geant.net",
+            "name": "ae23.667",
+            "bundle": [],
+            "bundle-parents": [
+                "et-1/0/5",
+                "et-5/0/2"
+            ],
+            "description": "SRV_CLS PRIVATE EXOSCALE #CH-EXOSCALE-CLS|ASN61098 | ",  # noqa: E501
+            "circuits": [
+                {
+                    "id": 663057,
+                    "name": "CH-EXOSCALE-CLS",
+                    "type": "GEANT CLOUD PEERING",
+                    "status": "operational"
+                }
+            ],
+            "snmp-index": 1276,
+            "dashboards": [
+                "CLS",
+                "CLS_PEERS"
+            ],
+            "dashboard_info": {
+                "name": "EXOSCALE",
+                "interface_type": "LOGICAL"
+            },
+            "ipv4": [
+                "62.40.100.26/31"
+            ],
+            "ipv6": [
+                "2001:798::29/126"
+            ]
+        },
+        {
+            "router": "mx1.fra.de.geant.net",
+            "name": "ae24.0",
+            "bundle": [],
+            "bundle-parents": [
+                "xe-10/1/5"
+            ],
+            "description": "SRV_CLS PRIVATE EXOSCALE #DE-EXOSCALE-CLS|ASN61098 | ",  # noqa: E501
+            "circuits": [
+                {
+                    "id": 708254,
+                    "name": "DE-EXOSCALE_CLS",
+                    "type": "GEANT CLOUD PEERING",
+                    "status": "operational"
+                }
+            ],
+            "snmp-index": 1251,
+            "dashboards": [
+                "CLS",
+                "CLS_PEERS"
+            ],
+            "dashboard_info": {
+                "name": "EXOSCALE",
+                "interface_type": "LOGICAL"
+            },
+            "ipv4": [
+                "62.40.100.20/31"
+            ],
+            "ipv6": [
+                "2001:798::51/126"
+            ]
+        },
+        {
+            "router": "mx1.fra.de.geant.net",
+            "name": "ae25.0",
+            "bundle": [],
+            "bundle-parents": [
+                "xe-10/2/2"
+            ],
+            "description": "SRV_CLS PRIVATE T-SYSTEMS #DE-T-SYSTEMS-CLS|ASN6878 | ",  # noqa: E501
+            "circuits": [
+                {
+                    "id": 708258,
+                    "name": "DE-T-SYSTEMS-CLS",
+                    "type": "GEANT CLOUD PEERING",
+                    "status": "operational"
+                }
+            ],
+            "snmp-index": 1079,
+            "dashboards": [
+                "CLS",
+                "CLS_PEERS"
+            ],
+            "dashboard_info": {
+                "name": "T-SYSTEMS",
+                "interface_type": "LOGICAL"
+            },
+            "ipv4": [
+                "94.100.252.88/31"
+            ],
+            "ipv6": []
+        },
+        {
+            "router": "mx1.fra.de.geant.net",
+            "name": "ae29.0",
+            "bundle": [],
+            "bundle-parents": [
+                "xe-10/1/2"
+            ],
+            "description": "SRV_CLS PRIVATE AWS #DE-AWS-CLS |ASN16509 | ",
+            "circuits": [
+                {
+                    "id": 708273,
+                    "name": "DE-AWS-CLS",
+                    "type": "GEANT CLOUD PEERING",
+                    "status": "operational"
+                }
+            ],
+            "snmp-index": 1208,
+            "dashboards": [
+                "CLS",
+                "CLS_PEERS"
+            ],
+            "dashboard_info": {
+                "name": "AWS",
+                "interface_type": "LOGICAL"
+            },
+            "ipv4": [
+                "52.95.219.129/31"
+            ],
+            "ipv6": [
+                "2620:107:4008:27b::2/64"
+            ]
+        },
+        {
+            "router": "mx1.fra.de.geant.net",
+            "name": "ae35.0",
+            "bundle": [],
+            "bundle-parents": [
+                "xe-11/2/3"
+            ],
+            "description": "SRV_CLS PRIVATE CLOUDFERRO #DE-CLOUDFERRO-CLS|ASN200999 | ",  # noqa: E501
+            "circuits": [
+                {
+                    "id": 708235,
+                    "name": "DE-CLOUDFERRO-CLS",
+                    "type": "GEANT CLOUD PEERING",
+                    "status": "operational"
+                }
+            ],
+            "snmp-index": 1218,
+            "dashboards": [
+                "CLS",
+                "CLS_PEERS"
+            ],
+            "dashboard_info": {
+                "name": "CLOUDFERRO",
+                "interface_type": "LOGICAL"
+            },
+            "ipv4": [
+                "45.92.241.127/31"
+            ],
+            "ipv6": []
+        },
+        {
+            "router": "mx1.fra.de.geant.net",
+            "name": "ae37.0",
+            "bundle": [],
+            "bundle-parents": [
+                "xe-3/3/0"
+            ],
+            "description": "SRV_CLS PRIVATE ORACLE #DE-ORACLE-CLS|ASN31898 ",
+            "circuits": [
+                {
+                    "id": 708312,
+                    "name": "DE-ORACLE-CLS",
+                    "type": "GEANT CLOUD PEERING",
+                    "status": "operational"
+                }
+            ],
+            "snmp-index": 1281,
+            "dashboards": [
+                "CLS",
+                "CLS_PEERS"
+            ],
+            "dashboard_info": {
+                "name": "ORACLE",
+                "interface_type": "LOGICAL"
+            },
+            "ipv4": [
+                "130.61.6.73/31"
+            ],
+            "ipv6": [
+                "2603:c000:280::5/127"
+            ]
+        },
+        {
+            "router": "mx1.vie.at.geant.net",
+            "name": "ae25.0",
+            "bundle": [],
+            "bundle-parents": [
+                "xe-4/0/4"
+            ],
+            "description": "SRV_CLS PRIVATE AWS #AT-AWS-CLS|ASN16509 | ",
+            "circuits": [
+                {
+                    "id": 708166,
+                    "name": "AT-AWS-CLS",
+                    "type": "GEANT CLOUD PEERING",
+                    "status": "operational"
+                }
+            ],
+            "snmp-index": 942,
+            "dashboards": [
+                "CLS",
+                "CLS_PEERS"
+            ],
+            "dashboard_info": {
+                "name": "AWS",
+                "interface_type": "LOGICAL"
+            },
+            "ipv4": [
+                "52.95.219.37/31"
+            ],
+            "ipv6": [
+                "2620:107:4008:251::2/64"
+            ]
+        },
+        {
+            "router": "mx1.lon.uk.geant.net",
+            "name": "ae24.0",
+            "bundle": [],
+            "bundle-parents": [
+                "xe-3/1/7"
+            ],
+            "description": "SRV_CLS PRIVATE ORACLE #UK-ORACLE-CLS |ASN31898",
+            "circuits": [
+                {
+                    "id": 708307,
+                    "name": "UK-ORACLE-CLS",
+                    "type": "GEANT CLOUD PEERING",
+                    "status": "operational"
+                }
+            ],
+            "snmp-index": 976,
+            "dashboards": [
+                "CLS",
+                "CLS_PEERS"
+            ],
+            "dashboard_info": {
+                "name": "ORACLE",
+                "interface_type": "LOGICAL"
+            },
+            "ipv4": [
+                "132.145.7.81/31"
+            ],
+            "ipv6": [
+                "2603:c000:380::9/127"
+            ]
+        }
+    ]
+}
 
 
 def generate_folder(data):
@@ -151,27 +290,19 @@ def generate_folder(data):
 @responses.activate
 def test_provision_aggregate(data_config, mocker, client):
 
-    TEST_DATASOURCE = [{
-        "name": "brian-influx-datasource",
-        "type": "influxdb",
-        "access": "proxy",
-        "url": "http://test-brian-datasource.geant.org:8086",
-        "database": "test-db",
-        "basicAuth": False,
-        "isDefault": True,
-        "readOnly": False
-    }]
+    def create_dashboard(_, dash, folder=None):
+        return dash
 
-    _mocked_create_dashboard = mocker.patch(
-        'brian_dashboard_manager.grafana.provision.create_dashboard')
-    # we dont care about this, tested separately
-    _mocked_create_dashboard.return_value = None
+    mocker.patch(
+        'brian_dashboard_manager.grafana.provision.create_dashboard',
+        create_dashboard)
 
     request = TokenRequest(**data_config, token='test')
     fake_folder = generate_folder({'uid': 'aggtest', 'title': 'aggtest'})
-    dash = {
-        'predicate': is_cls_peer,
-        'tag': 'cls_peers',
-    }
-    provision_aggregate(request, 'MY FAKE PEERS', fake_folder,
-                        dash, TEST_INTERFACES, TEST_DATASOURCE[0]['name'])
+    result = provision_aggregate(request, fake_folder, TEST_DASHBOARD,
+                                 'test_datasource')
+    panels = result['panels']
+    expected_title = f'Aggregate - {TEST_DASHBOARD["dashboard_name"]}'
+    assert result['title'] == expected_title
+    assert len(panels) == 14
+    assert len(panels[0]['targets']) == len(TEST_DASHBOARD['interfaces'])
diff --git a/test/test_update.py b/test/test_update.py
index 55050de6be23493d7b201cdecb1dfaa0f36ad556..16735679523bb7a3d7a69e8348bc9f18beb23e11 100644
--- a/test/test_update.py
+++ b/test/test_update.py
@@ -1,15 +1,10 @@
 import responses
 import json
 import re
-from brian_dashboard_manager.grafana.utils.request import TokenRequest
-from brian_dashboard_manager.templating.nren_access import get_nrens
+
 from brian_dashboard_manager.grafana.provision import provision_folder, \
-    generate_all_nrens, provision
-from brian_dashboard_manager.grafana.provision import is_re_customer, \
-    is_cls, is_ias_customer, is_ias_private, is_ias_public, is_ias_upstream, \
-    is_lag_backbone, is_phy_upstream, is_re_peer, is_gcs, \
-    is_geantopen, is_l2circuit, is_lhcone_peer, is_lhcone_customer, is_mdvpn,\
-    parse_backbone_name, parse_phy_upstream_name
+    provision
+
 
 DEFAULT_REQUEST_HEADERS = {
     "Content-type": "application/json",
@@ -19,121 +14,458 @@ DEFAULT_REQUEST_HEADERS = {
 
 TEST_INTERFACES = [
     {
-        "router": "mx1.ath2.gr.geant.net",
-        "name": "xe-1/0/1",
+        "router": "srx2.ch.office.geant.net",
+        "name": "ge-0/0/8",
         "bundle": [],
         "bundle-parents": [],
-        "snmp-index": 569,
-        "description": "PHY RESERVED | New OTEGLOBE ATH2-VIE 10Gb LS",
-        "circuits": []
+        "description": "PHY CUSTOMER GEANT CORPORATE SRF000001 | GEANT Corporate to MX1.LON - Via Vodafone",  # noqa: E501
+        "circuits": [
+            {
+                "id": 679232,
+                "name": "GEANT CORPORATE TO MX1.LON - VIA VODAFONE",
+                "type": "GEANT IP",
+                "status": "non-monitored"
+            }
+        ],
+        "snmp-index": 523,
+        "dashboards": [
+            "NREN",
+            "RE_CUST"
+        ],
+        "dashboard_info": {
+            "name": "GEANT",
+            "interface_type": "PHYSICAL"
+        },
+        "ipv4": [],
+        "ipv6": []
     },
     {
-        "router": "mx1.ath2.gr.geant.net",
-        "name": "ge-1/3/7",
+        "router": "srx2.ch.office.geant.net",
+        "name": "ge-0/0/8.10",
         "bundle": [],
         "bundle-parents": [],
+        "description": "SRV_GLOBAL CUSTOMER GEANT #GEANT_CORPORATE-ViaVodafone | GEANT Corporate to mx1.lon - Via Vodafone ",  # noqa: E501
+        "circuits": [
+            {
+                "id": 679360,
+                "name": "GEANT_CORPORATE-VIAVODAFONE",
+                "type": "GEANT IP",
+                "status": "non-monitored"
+            }
+        ],
         "snmp-index": 543,
-        "description": "PHY SPARE",
-        "circuits": []
+        "dashboards": [
+            "NREN",
+            "RE_CUST"
+        ],
+        "dashboard_info": {
+            "name": "GEANT",
+            "interface_type": "LOGICAL"
+        },
+        "ipv4": [
+            "62.40.127.131/31"
+        ],
+        "ipv6": [
+            "2001:798:99:1::ca/126"
+        ]
     },
     {
-        "router": "mx1.ham.de.geant.net",
-        "name": "xe-2/2/0.13",
+        "router": "srx2.ch.office.geant.net",
+        "name": "ge-0/0/8.11",
         "bundle": [],
         "bundle-parents": [],
-        "snmp-index": 721,
-        "description": "SRV_L2CIRCUIT CUSTOMER WP6T3 WP6T3 #ham_lon2-WP6-GTS_20063 |",  # noqa: E501
+        "description": "SRV_GLOBAL CUSTOMER GEANT #GEANT_CORPORATE_ViaVodafone-VRF | GEANT Corporate to mx1.lon - Via Vodafone - for VRF",  # noqa: E501
         "circuits": [
             {
-                "id": 52382,
-                "name": "ham_lon2-WP6-GTS_20063_L2c",
-                "type": "",
-                "status": "operational"
+                "id": 712144,
+                "name": "GEANT_CORPORATE_VIAVODAFONE-VRF",
+                "type": "GEANT IP",
+                "status": "non-monitored"
             }
-        ]
+        ],
+        "snmp-index": 545,
+        "dashboards": [
+            "NREN",
+            "RE_CUST"
+        ],
+        "dashboard_info": {
+            "name": "GEANT",
+            "interface_type": "LOGICAL"
+        },
+        "ipv4": [
+            "10.101.0.6/30"
+        ],
+        "ipv6": []
     },
     {
-        "router": "mx1.fra.de.geant.net",
-        "name": "ae27",
+        "router": "srx2.ch.office.geant.net",
+        "name": "ge-0/0/8.12",
         "bundle": [],
-        "bundle-parents": [
-            "xe-10/0/2",
-            "xe-10/3/2",
-            "xe-10/3/3"
-        ],
-        "snmp-index": 760,
-        "description": "LAG CUSTOMER ULAKBIM SRF9940983 |",
+        "bundle-parents": [],
+        "description": "SRV_GLOBAL CUSTOMER GEANT #GEANT_CORPORATE_ViaVodafone-VRF-TEST | GEANT Corporate to mx1.lon - Via Vodafone - DASHBOARD BGP TEST VLAN",  # noqa: E501
         "circuits": [
             {
-                "id": 40983,
-                "name": "ULAKBIM AP2 LAG",
-                "type": "",
-                "status": "operational"
+                "id": 678920,
+                "name": "GEANT_CORPORATE_VIAVODAFONE-VRF-TEST (DO NOT OPEN A TICKET)",  # noqa: E501
+                "type": "GEANT IP",
+                "status": "non-monitored"
             }
-        ]
+        ],
+        "snmp-index": 522,
+        "dashboards": [
+            "NREN",
+            "RE_CUST"
+        ],
+        "dashboard_info": {
+            "name": "GEANT",
+            "interface_type": "LOGICAL"
+        },
+        "ipv4": [
+            "62.40.127.141/31"
+        ],
+        "ipv6": []
     },
     {
-        "router": "mx2.zag.hr.geant.net",
-        "name": "xe-2/1/0",
+        "router": "srx2.ch.office.geant.net",
+        "name": "ge-0/0/8.996",
         "bundle": [],
         "bundle-parents": [],
-        "snmp-index": 739,
-        "description": "PHY SPARE",
-        "circuits": []
+        "description": "SRV_GLOBAL CUSTOMER GEANT #GEANT_OPERATIONS_LabConnectivity | GEANT MX1.LON Infinera VRF to Operations Lab",  # noqa: E501
+        "circuits": [
+            {
+                "id": 678999,
+                "name": "GEANT_OPERATIONS_LABCONNECTIVITY",
+                "type": "GEANT IP",
+                "status": "non-monitored"
+            }
+        ],
+        "snmp-index": 554,
+        "dashboards": [
+            "NREN",
+            "RE_CUST"
+        ],
+        "dashboard_info": {
+            "name": "GEANT",
+            "interface_type": "LOGICAL"
+        },
+        "ipv4": [
+            "10.1.1.1/30"
+        ],
+        "ipv6": []
     },
     {
-        "router": "rt1.rig.lv.geant.net",
-        "name": "xe-0/1/5",
-        "bundle": [],
+        "router": "mx1.ams.nl.geant.net",
+        "name": "xe-0/0/0",
+        "bundle": [
+            "ae16"
+        ],
         "bundle-parents": [],
-        "snmp-index": 539,
-        "description": "PHY SPARE",
-        "circuits": []
+        "description": "PHY CUSTOMER KIAE P_AE16 SRF9915671 |",
+        "circuits": [],
+        "snmp-index": 588,
+        "dashboards": [
+            "NREN",
+            "RE_CUST"
+        ],
+        "dashboard_info": {
+            "name": "KIAE",
+            "interface_type": "PHYSICAL"
+        },
+        "ipv4": [],
+        "ipv6": []
     },
     {
-        "router": "srx1.ch.office.geant.net",
-        "name": "ge-0/0/0",
-        "bundle": [],
+        "router": "mx1.ams.nl.geant.net",
+        "name": "xe-0/0/1",
+        "bundle": [
+            "ae14"
+        ],
         "bundle-parents": [],
-        "snmp-index": 513,
-        "description": "Reserved for GEANT OC to test Virgin Media link",
-        "circuits": []
+        "description": "PHY CUSTOMER SWITCH P_AE14 SRF18008 |",
+        "circuits": [],
+        "snmp-index": 589,
+        "dashboards": [
+            "NREN",
+            "RE_CUST"
+        ],
+        "dashboard_info": {
+            "name": "SWITCH",
+            "interface_type": "PHYSICAL"
+        },
+        "ipv4": [],
+        "ipv6": []
     },
     {
-        "router": "mx1.par.fr.geant.net",
-        "name": "xe-4/1/4.1",
+        "router": "mx1.ams.nl.geant.net",
+        "name": "ge-0/2/1.0",
         "bundle": [],
         "bundle-parents": [],
-        "snmp-index": 1516,
-        "description": "SRV_L2CIRCUIT INFRASTRUCTURE JRA1 JRA1 | #SDX-L2_PILOT-Br52 OF-P3_par   ",  # noqa: E501
-        "circuits": []
+        "description": "SRV_L2CIRCUIT INFRASTRUCTURE GEANT GEANT #AMS-ZAG OPENFLOW |",  # noqa: E501
+        "circuits": [],
+        "snmp-index": 687,
+        "dashboards": [
+            "L2_CIRCUIT"
+        ],
+        "dashboard_info": {
+            "name": "GEANT",
+            "interface_type": "LOGICAL"
+        },
+        "ipv4": [],
+        "ipv6": []
     },
     {
-        "router": "mx1.lon.uk.geant.net",
-        "name": "lt-1/3/0.61",
+        "router": "mx1.ams.nl.geant.net",
+        "name": "ge-0/2/2.0",
         "bundle": [],
         "bundle-parents": [],
-        "snmp-index": 1229,
-        "description": "SRV_IAS INFRASTRUCTURE ACCESS GLOBAL #LON-IAS-RE-Peering | BGP Peering - IAS Side",  # noqa: E501
-        "circuits": []
+        "description": "SRV_L2CIRCUIT INFRASTRUCTURE GEANT GEANT #AMS-VIE OPENFLOW |",  # noqa: E501
+        "circuits": [],
+        "snmp-index": 711,
+        "dashboards": [
+            "L2_CIRCUIT"
+        ],
+        "dashboard_info": {
+            "name": "GEANT",
+            "interface_type": "LOGICAL"
+        },
+        "ipv4": [],
+        "ipv6": []
     },
     {
-        "router": "mx1.sof.bg.geant.net",
-        "name": "xe-2/0/5",
+        "router": "mx1.ams.nl.geant.net",
+        "name": "ge-0/2/4.0",
         "bundle": [],
         "bundle-parents": [],
-        "snmp-index": 694,
-        "description": "PHY RESERVED | Prime Telecom Sofia-Bucharest 3_4",
-        "circuits": []
+        "description": "SRV_L2CIRCUIT INFRASTRUCTURE GEANT GEANT #AMS-FRA OPENFLOW |",  # noqa: E501
+        "circuits": [],
+        "snmp-index": 718,
+        "dashboards": [
+            "L2_CIRCUIT"
+        ],
+        "dashboard_info": {
+            "name": "GEANT",
+            "interface_type": "LOGICAL"
+        },
+        "ipv4": [],
+        "ipv6": []
+    }
+]
+
+NREN_INTERFACES = [
+    {
+        "router": "mx1.dub2.ie.geant.net",
+        "name": "xe-0/0/0",
+        "bundle": [
+                "ae10"
+        ],
+        "bundle-parents": [],
+        "description": "PHY CUSTOMER HEANET P_AE10 SRF9948758 | HEANET-AP2-LL3",  # noqa: E501
+        "circuits": [],
+        "snmp-index": 554,
+        "dashboards": [
+            "NREN",
+            "RE_CUST"
+        ],
+        "dashboard_info": {
+            "name": "HEANET",
+            "interface_type": "PHYSICAL"
+        },
+        "ipv4": [],
+        "ipv6": []
     },
     {
-        "router": "mx1.sof.bg.geant.net",
-        "name": "xe-2/0/5",
-        "bundle": [],
+        "router": "mx1.dub2.ie.geant.net",
+        "name": "xe-1/0/1",
+        "bundle": [
+                "ae10"
+        ],
+        "bundle-parents": [],
+        "description": "PHY CUSTOMER HEANET P_AE10 SRF0000001 | HEANET-AP2-LL2",  # noqa: E501
+        "circuits": [],
+        "snmp-index": 527,
+        "dashboards": [
+            "NREN",
+            "RE_CUST"
+        ],
+        "dashboard_info": {
+            "name": "HEANET",
+            "interface_type": "PHYSICAL"
+        },
+        "ipv4": [],
+        "ipv6": []
+    },
+    {
+        "router": "mx1.dub2.ie.geant.net",
+        "name": "xe-1/1/0",
+        "bundle": [
+                "ae10"
+        ],
         "bundle-parents": [],
-        "snmp-index": 694,
-        "description": "SRV_GLOBAL CUSTOMER HEANET TESTDESCRIPTION |",
-        "circuits": []
+        "description": "PHY CUSTOMER HEANET P_AE10 SRF9925903 | HEANET-AP2-LL1",  # noqa: E501
+        "circuits": [],
+        "snmp-index": 528,
+        "dashboards": [
+            "NREN",
+            "RE_CUST"
+        ],
+        "dashboard_info": {
+            "name": "HEANET",
+            "interface_type": "PHYSICAL"
+        },
+        "ipv4": [],
+        "ipv6": []
+    },
+    {
+        "router": "mx1.dub2.ie.geant.net",
+        "name": "ae10",
+        "bundle": [],
+        "bundle-parents": [
+                "xe-0/0/0",
+                "xe-1/0/1",
+                "xe-1/1/0"
+        ],
+        "description": "LAG CUSTOMER HEANET SRF9925909 | HEANET-AP2-LAG",
+        "circuits": [],
+        "snmp-index": 596,
+        "dashboards": [
+            "NREN",
+            "RE_CUST"
+        ],
+        "dashboard_info": {
+            "name": "HEANET",
+            "interface_type": "AGGREGATE"
+        },
+        "ipv4": [],
+        "ipv6": []
+    },
+    {
+        "router": "mx1.dub2.ie.geant.net",
+        "name": "ae10.12",
+        "bundle": [],
+        "bundle-parents": [
+                "xe-0/0/0",
+                "xe-1/0/1",
+                "xe-1/1/0"
+        ],
+        "description": "SRV_GLOBAL CUSTOMER HEANET #HEANET-AP2 | ASN1213 | ",
+        "circuits": [
+            {
+                "id": 662976,
+                "name": "HEANET-AP2",
+                "type": "GEANT IP",
+                "status": "operational"
+            }
+        ],
+        "snmp-index": 713,
+        "dashboards": [
+            "NREN",
+            "RE_CUST"
+        ],
+        "dashboard_info": {
+            "name": "HEANET",
+            "interface_type": "LOGICAL"
+        },
+        "ipv4": [
+            "62.40.125.13/30"
+        ],
+        "ipv6": [
+            "2001:0798:0018:10aa::15/126"
+        ]
+    },
+    {
+        "router": "mx1.dub2.ie.geant.net",
+        "name": "ae10.30",
+        "bundle": [],
+        "bundle-parents": [
+                "xe-0/0/0",
+                "xe-1/0/1",
+                "xe-1/1/0"
+        ],
+        "description": "SRV_MDVPN CUSTOMER HEANET AP2 #HEANET-BGP-LU-CoC-1 |",  # noqa: E501
+        "circuits": [
+            {
+                "id": 663160,
+                "name": "HEANET-BGP-LU-COC-1",
+                "type": "MD-VPN (NATIVE)",
+                "status": "operational"
+            }
+        ],
+        "snmp-index": 712,
+        "dashboards": [
+            "MDVPN_CUSTOMERS",
+            "NREN"
+        ],
+        "dashboard_info": {
+            "name": "HEANET",
+            "interface_type": "LOGICAL"
+        },
+        "ipv4": [
+            "62.40.102.36/31"
+        ],
+        "ipv6": []
+    },
+    {
+        "router": "mx1.dub2.ie.geant.net",
+        "name": "ae10.333",
+        "bundle": [],
+        "bundle-parents": [
+                "xe-0/0/0",
+                "xe-1/0/1",
+                "xe-1/1/0"
+        ],
+        "description": "SRV_IAS CUSTOMER HEANET #HEANET-AP2-IAS IASPS | ASN1213 ",  # noqa: E501
+        "circuits": [
+            {
+                "id": 663214,
+                "name": "HEANET-AP2-IAS",
+                "type": "GEANT PEERING",
+                "status": "operational"
+            }
+        ],
+        "snmp-index": 658,
+        "dashboards": [
+            "IAS_CUSTOMER",
+            "NREN"
+        ],
+        "dashboard_info": {
+            "name": "HEANET",
+            "interface_type": "LOGICAL"
+        },
+        "ipv4": [
+            "83.97.88.77/30"
+        ],
+        "ipv6": [
+            "2001:798:1::61/126"
+        ]
+    },
+    {
+        "router": "mx1.dub2.ie.geant.net",
+        "name": "ae10.1214",
+        "bundle": [],
+        "bundle-parents": [
+                "xe-0/0/0",
+                "xe-1/0/1",
+                "xe-1/1/0"
+        ],
+        "description": "SRV_L2CIRCUIT CUSTOMER HEANET GEANT #ams-dub2-HEANET-RARE-21061 |",  # noqa: E501
+        "circuits": [
+            {
+                "id": 713335,
+                "name": "AMS-DUB2-HEANET-RARE-21061",
+                "type": "GEANT PLUS",
+                "status": "non-monitored"
+            }
+        ],
+        "snmp-index": 790,
+        "dashboards": [
+            "L2_CIRCUIT"
+        ],
+        "dashboard_info": {
+            "name": "HEANET",
+            "interface_type": "LOGICAL"
+        },
+        "ipv4": [],
+        "ipv6": []
     }
 ]
 
@@ -159,70 +491,17 @@ def generate_folder(data):
 @responses.activate
 def test_provision_folder(data_config, mocker):
     dashboards = {
-        'CLS TESTDASHBOARD': {
-            'predicate': is_cls,
-            'tag': 'CLS'
-        },
-        'RE PEER TESTDASHBOARD': {
-            'predicate': is_re_peer,
-            'tag': 'RE_PEER'
-        },
-        'RE CUST TESTDASHBOARD': {
-            'predicate': is_re_customer,
-            'tag': 'RE_CUST'
-        },
-        'GEANTOPEN TESTDASHBOARD': {
-            'predicate': is_geantopen,
-            'tag': 'GEANTOPEN'
-        },
-        'GCS TESTDASHBOARD': {
-            'predicate': is_gcs,
-            'tag': 'AUTOMATED_L2_CIRCUITS'
-        },
-        'L2 CIRCUIT TESTDASHBOARD': {
-            'predicate': is_l2circuit,
-            'tag': 'L2_CIRCUITS'
-        },
-        'LHCONE PEER TESTDASHBOARD': {
-            'predicate': is_lhcone_peer,
-            'tag': 'LHCONE_PEER'
-        },
-        'LHCONE CUST TESTDASHBOARD': {
-            'predicate': is_lhcone_customer,
-            'tag': 'LHCONE_CUST'
-        },
-        'MDVPN Customers TESTDASHBOARD': {
-            'predicate': is_mdvpn,
-            'tag': 'MDVPN'
-        },
-        'Infrastructure Backbone TESTDASHBOARD': {
-            'predicate': is_lag_backbone,
-            'tag': 'BACKBONE',
-            'errors': True,
-            'parse_func': parse_backbone_name
+        'NREN': {
+            'tag': ['customers'],
+            'folder_name': 'NREN Access',
+            'interfaces': [iface for iface in TEST_INTERFACES if 'NREN' in iface['dashboards']]  # noqa: E501
         },
-        'IAS PRIVATE TESTDASHBOARD': {
-            'predicate': is_ias_private,
-            'tag': 'IAS_PRIVATE'
+        'RE_CUST': {
+            'tag': 'RE_CUST',
+            'folder_name': 'RE Customer',
+            'interfaces': [iface for iface in TEST_INTERFACES if 'RE_CUST' in iface['dashboards']]  # noqa: E501
         },
-        'IAS PUBLIC TESTDASHBOARD': {
-            'predicate': is_ias_public,
-            'tag': 'IAS_PUBLIC'
-        },
-        'IAS CUSTOMER TESTDASHBOARD': {
-            'predicate': is_ias_customer,
-            'tag': 'IAS_CUSTOMER'
-        },
-        'IAS UPSTREAM TESTDASHBOARD': {
-            'predicate': is_ias_upstream,
-            'tag': 'IAS_UPSTREAM'
-        },
-        'GWS PHY Upstream TESTDASHBOARD': {
-            'predicate': is_phy_upstream,
-            'tag': 'GWS_UPSTREAM',
-            'errors': True,
-            'parse_func': parse_phy_upstream_name
-        }
+
     }
 
     # just return a generated folder
@@ -231,111 +510,42 @@ def test_provision_folder(data_config, mocker):
     _mocked_find_folder.return_value = generate_folder(
         {'uid': 'testfolderuid', 'title': 'testfolder'})
 
-    # we don't care about testing create_dashboard
-    _mocked_find_folder = mocker.patch(
-        'brian_dashboard_manager.grafana.provision.create_dashboard')
-    _mocked_find_folder.return_value = None
-
-    for dashboard in dashboards:
-        provision_folder(None, 'testfolder', dashboards[dashboard],
-                         TEST_INTERFACES,
-                         'testdatasource', ['CLS TESTDASHBOARD'])
-
-
-@responses.activate
-def test_provision_nrens(data_config, mocker):
-    NREN_INTERFACES = [
-        # physical
-        {
-            "router": "mx1.dub2.ie.geant.net",
-            "name": "xe-0/0/0",
-            "bundle": ["ae10"],
-            "bundle-parents": [],
-            "snmp-index": 554,
-            "description": "PHY CUSTOMER HEANET P_AE10 SRF9948758 | HEANET AP2-3 LL",  # noqa: E501
-            "circuits": []
-        },
-        # aggregate
-        {
-            "router": "mx1.dub2.ie.geant.net",
-            "name": "ae10",
-            "bundle": [],
-            "bundle-parents": ["xe-0/0/0", "xe-1/0/1", "xe-1/1/0"],
-            "snmp-index": 596,
-            "description": "LAG CUSTOMER HEANET SRF9925909 |",
-            "circuits": [
-                {
-                    "id": 25909,
-                    "name": "HEANET AP2 LAG",
-                    "type": "",
-                    "status": "operational"
-                }
-            ]
-        },
-        # logical
-        {
-            "router": "mx1.dub2.ie.geant.net",
-            "name": "ae10.12",
-            "bundle": [],
-            "bundle-parents": [
-                "xe-0/0/0",
-                "xe-1/0/1",
-                "xe-1/1/0"
-            ],
-            "snmp-index": 713,
-            "description": "SRV_GLOBAL CUSTOMER HEANET #HEANET_AP2 | ASN1213 | ",  # noqa: E501
-            "circuits": [
-                {
-                    "id": 48776,
-                    "name": "HEANET AP2",
-                    "type": "ip access",
-                    "status": "operational"
-                },
-                {
-                    "id": 31347,
-                    "name": "HEANET AP2 L2c",
-                    "type": "",
-                    "status": "operational"
-                }
-            ]
-        }
-    ]
+    def create_dashboard(request, dashboard, folder_id=None):
+        return dashboard
 
-    UID = '1337'
+    mocker.patch(
+        'brian_dashboard_manager.grafana.provision.create_dashboard',
+        create_dashboard)
 
-    def get_callback(request):
-        query = request.params.get('query')
-        return 200, {}, json.dumps({'uid': UID, 'title': query})
+    excluded_dashboards = []
+    nren_result = provision_folder(None, 'testfolder', dashboards['NREN'],
+                                   'testdatasource', excluded_dashboards)
 
-    responses.add_callback(
-        method=responses.GET,
-        url=re.compile(f"http://{data_config['hostname']}/api/search"),
-        callback=get_callback)
+    assert len(nren_result) == 3
+    assert nren_result[0]['title'] == 'GEANT'
+    assert nren_result[1]['title'] == 'KIAE'
+    assert nren_result[2]['title'] == 'SWITCH'
 
-    def post_callback(request):
-        dashboard = json.loads(request.body).get('dashboard')
-        title = dashboard.get('title')
-        return 200, {}, json.dumps({'uid': UID, 'title': title})
+    excluded_dashboards = ['SWITCH']
+    nren_excluded = provision_folder(None, 'testfolder', dashboards['NREN'],
+                                     'testdatasource', excluded_dashboards)
 
-    responses.add_callback(
-        method=responses.POST,
-        url=re.compile(f"http://{data_config['hostname']}/api/dashboards/db"),
-        callback=post_callback)
+    assert len(nren_excluded) == 2
+    assert nren_excluded[0]['title'] == 'GEANT'
+    assert nren_excluded[1]['title'] == 'KIAE'
 
-    nrens = get_nrens(NREN_INTERFACES)
-    assert len(nrens) == 1 and nrens.get('HEANET') is not None
-    assert len(nrens.get('HEANET').get('AGGREGATES')) == 1
-    assert len(nrens.get('HEANET').get('SERVICES')) == 1
-    assert len(nrens.get('HEANET').get('PHYSICAL')) == 2
-    token_request = TokenRequest(token='testtoken', **data_config)
-    generate_all_nrens(token_request, NREN_INTERFACES, 1, 'testdatasource')
+    cust_result = provision_folder(None, 'testfolder', dashboards['RE_CUST'],
+                                   'testdatasource', ['GEANT'])
+    assert len(cust_result) == 2
+    assert cust_result[0]['title'] == 'KIAE'
+    assert cust_result[1]['title'] == 'SWITCH'
 
 
 @responses.activate
 def test_provision(data_config, mocker, client):
 
     def get_callback(request):
-        return 200, {}, json.dumps(TEST_INTERFACES)
+        return 200, {}, json.dumps(NREN_INTERFACES)
 
     responses.add_callback(
         method=responses.GET,