diff --git a/brian_dashboard_manager/templating/helpers.py b/brian_dashboard_manager/templating/helpers.py
index 46e141c5eb2282444c7458f45bdfc231f051945c..9f9adf29cb3d2094dcaa2943b5e72bf729a2d5c7 100644
--- a/brian_dashboard_manager/templating/helpers.py
+++ b/brian_dashboard_manager/templating/helpers.py
@@ -1,8 +1,11 @@
 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
+from brian_dashboard_manager.templating.render import create_panel, \
+    create_panel_target
 
 PANEL_HEIGHT = 12
 PANEL_WIDTH = 24
@@ -35,6 +38,10 @@ 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)
 
@@ -51,6 +58,10 @@ 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)
 
@@ -82,6 +93,11 @@ def is_lhcone_customer(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))
 
@@ -96,6 +112,10 @@ def is_lag_backbone(interface):
     return is_infrastructure_backbone(interface) and is_lag
 
 
+def is_cae1(interface):
+    return interface.get('router', '').lower() == 'mx1.lon.uk.geant.net'
+
+
 def parse_backbone_name(description, *args, **kwargs):
     link = description.split('|')[1].strip()
     link = link.replace('( ', '(')
@@ -119,16 +139,24 @@ def num_generator(start=1):
         num += 1
 
 
-def gridPos_generator(id_generator, start=0):
+def gridPos_generator(id_generator, start=0, agg=False):
     num = start
     while True:
         yield {
             "height": PANEL_HEIGHT,
-            "width": PANEL_WIDTH,
+            "width": PANEL_WIDTH if not agg else PANEL_WIDTH // 2,
             "x": 0,
             "y": num * PANEL_HEIGHT,
             "id": next(id_generator)
         }
+        if agg:
+            yield {
+                "height": PANEL_HEIGHT,
+                "width": PANEL_WIDTH // 2,
+                "x": PANEL_WIDTH // 2,
+                "y": num * PANEL_HEIGHT,
+                "id": next(id_generator)
+            }
         num += 1
 
 
@@ -155,12 +183,14 @@ def letter_generator():
 # parse_func receives interface information and returns a peer name.
 def get_interface_data(interfaces, name_parse_func=None):
     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:
-        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()
 
         description = interface.get('description', '').strip()
         interface_name = interface.get('name')
@@ -182,6 +212,67 @@ def get_interface_data(interfaces, name_parse_func=None):
     return result
 
 
+def get_aggregate_interface_data(interfaces, agg_type):
+    result = []
+
+    def reduce_func(prev, curr):
+        remotes = prev.get(curr['remote'], [])
+        remotes.append(curr)
+        all_agg = prev.get('EVERYSINGLEPANEL', [])
+        all_agg.append(curr)
+        prev[curr['remote']] = remotes
+        prev['EVERYSINGLEPANEL'] = all_agg
+        return prev
+
+    for interface in interfaces:
+
+        description = interface.get('description', '').strip()
+        interface_name = interface.get('name')
+        host = interface.get('router', '')
+
+        remote = description.split(' ')[2].upper()
+
+        result.append({
+            'type': agg_type,
+            'interface': interface_name,
+            'hostname': host,
+            'remote': remote,
+            'alias': f"{host.split('.')[1].upper()} - {remote}"
+        })
+    return reduce(reduce_func, result, {})
+
+
+# Helper used for generating stacked aggregate panels
+# with multiple target fields (ingress/egress)
+def get_aggregate_targets(targets):
+    ingress = []
+    egress = []
+
+    # used to generate refIds
+    letters = letter_generator()
+
+    for target in targets:
+        ref_id = next(letters)
+        in_data = {
+            **target,
+            'alias': f"{target['alias']} - Ingress Traffic",
+            'refId': ref_id,
+            'select_field': 'ingress'
+        }
+        out_data = {
+            **target,
+            'alias': f"{target['alias']} - Egress Traffic",
+            'refId': ref_id,
+            'select_field': 'egress'
+        }
+        ingress_target = create_panel_target(in_data)
+        egress_target = create_panel_target(out_data)
+        ingress.append(ingress_target)
+        egress.append(egress_target)
+
+    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):
@@ -214,6 +305,7 @@ def get_panel_fields(panel, panel_type, datasource):
     return create_panel({
         **panel,
         'datasource': datasource,
+        'linewidth': 1,
         'title': panel['title'].format(panel_type),
         'panel_targets': [get_target_data(*target) for target in targets],
         'y_axis_type': 'errors' if is_error else 'bits',
@@ -244,3 +336,82 @@ def get_dashboard_data(data, datasource, tag, errors=False):
             'panels': get_panel_definitions(panels, datasource),
             'tag': tag
         }
+
+
+def create_aggregate_panel(title, gridpos, targets, datasource):
+
+    ingress_targets, egress_targets = get_aggregate_targets(targets)
+    result = []
+
+    ingress_pos = next(gridpos)
+    egress_pos = next(gridpos)
+
+    is_total = 'totals' in title.lower()
+
+    def reduce_alias(prev, curr):
+        d = json.loads(curr)
+        alias = d['alias']
+        if 'egress' in alias.lower():
+            prev[alias] = '#0000FF'
+        else:
+            prev[alias] = '#00FF00'
+        return prev
+
+    ingress_colors = reduce(reduce_alias, ingress_targets, {})
+    egress_colors = reduce(reduce_alias, egress_targets, {})
+
+    result.append(create_panel({
+        **ingress_pos,
+        'stack': True,
+        'linewidth': 0 if is_total else 1,
+        'datasource': datasource,
+        'title': title + ' - ingress',
+        'targets': ingress_targets,
+        'y_axis_type': 'bits',
+        'alias_colors': json.dumps(ingress_colors) if is_total else {}
+    }))
+
+    result.append(create_panel({
+        **egress_pos,
+        'stack': True,
+        'linewidth': 0 if is_total else 1,
+        'datasource': datasource,
+        'title': title + ' - egress',
+        'targets': egress_targets,
+        'y_axis_type': 'bits',
+        'alias_colors': json.dumps(egress_colors) if is_total else {}
+    }))
+
+    return result
+
+
+def get_aggregate_dashboard_data(title, targets, datasource, tag):
+    id_gen = num_generator()
+    gridPos = gridPos_generator(id_gen, agg=True)
+
+    panels = []
+    all_targets = targets.get('EVERYSINGLEPANEL', [])
+
+    ingress, egress = create_aggregate_panel(
+        title, gridPos, all_targets, datasource)
+    panels.extend([ingress, egress])
+
+    totals_title = title + ' - Totals'
+    t_in, t_eg = create_aggregate_panel(
+        totals_title, gridPos, all_targets, datasource)
+    panels.extend([t_in, t_eg])
+
+    if 'EVERYSINGLEPANEL' in targets:
+        del targets['EVERYSINGLEPANEL']
+
+    for target in targets:
+        _in, _out = create_aggregate_panel(
+            title + f' - {target}', gridPos, targets[target], datasource)
+        panels.extend([_in, _out])
+
+    return {
+        'title': title,
+        'datasource': datasource,
+        'panels': panels,
+        'tag': tag
+    }