21 Aug 2025
08:56 AM
- last edited on
29 Aug 2025
02:00 PM
by
Michal_Gebacki
It's quite common to have dashboards display many tiles for individual components - for example multiple services. Luckily Monaco supports Go Templating.
Let's consider a Gen2 and a Gen3 dashboard where we want to easily generate a grid of tiles and just specify a set of parameters for each tile. It's just required to do some basic arithmetic to calculate the position of each tile and then wrap the tiles when we reach a defined width of the grid. Unfortunately, not even basic arithmetic is possible in the gotemplate engine used by monaco, so we need to do it creatively by using printf and len functions 😎
I believe the following sample is more than enough. Sample project is attached.
This is a YAML file for monaco specifying a list of tags for services with the tile title.
configs:
- id: sample-dashboard-with-auto-layout
config:
name: "Sample Dashboard with Auto Layout"
parameters:
services:
type: value
value:
- tag: mytag1
title: My Service with My Tag 1
- tag: mytag2
title: My Service with My Tag 2
- tag: mytag3
title: My Service with My Tag 3
- tag: mytag4
title: My Service with My Tag 4
- tag: mytag5
title: My Service with My Tag 5
- tag: mytag6
title: My Service with My Tag 6
- tag: mytag7
title: My Service with My Tag 7
- tag: mytag8
title: My Service with My Tag 8
template: dashboard-autolayout.json
skip: false
type:
api: dashboard
- id: gen3-sample-dashboard-with-auto-layout
config:
name: "Sample Dashboard with Auto Layout"
parameters:
services:
type: value
value:
- tag: mytag1
title: My Service with My Tag 1
- tag: mytag2
title: My Service with My Tag 2
- tag: mytag3
title: My Service with My Tag 3
- tag: mytag4
title: My Service with My Tag 4
- tag: mytag5
title: My Service with My Tag 5
- tag: mytag6
title: My Service with My Tag 6
- tag: mytag7
title: My Service with My Tag 7
- tag: mytag8
title: My Service with My Tag 8
template: gen3-dashboard-autolayout.json
type:
document:
kind: dashboard
The interesting part is the dashboard JSON (dashboard-autolayout.json) with gotemplate. We specify tile tileWidth, tileHeight for each tile and maxWidth = grid width.
Gen3 dashboard - gen3-dashboard-autolayout.json
{
"version": 19,
"variables": [],
"tiles": {
{{- range $i, $e := .services}}
{{- if $i}},{{- end}}
"{{$i}}": {
"type": "data",
"title": "{{ $e.title }}",
"query": "timeseries { `Response time` = avg(dt.service.request.response_time), value.A = avg(dt.service.request.response_time, scalar: true), Requests = sum(dt.service.request.count), value.B = avg(dt.service.request.count, scalar: true) }, union: TRUE, filter: { matchesValue(entityAttr(dt.entity.service, \"tags\"), \"{{ $e.tag }}\") }",
"queryConfig": {
"version": "15.4.0",
"subQueries": [
{
"id": "A",
"isEnabled": true,
"datatype": "metrics",
"metric": {
"key": "dt.service.request.response_time",
"alias": "Response time",
"aggregation": "avg"
},
"filter": "dt.entity.service.tags = {{ $e.tag}} "
},
{
"id": "B",
"isEnabled": true,
"datatype": "metrics",
"metric": {
"key": "dt.service.request.count",
"alias": "Requests",
"aggregation": "sum"
},
"filter": "dt.entity.service.tags = {{ $e.tag}} "
}
]
},
"subType": "dql-builder-metrics",
"visualization": "lineChart",
"davis": {
"enabled": false,
"davisVisualization": {
"isAvailable": true
}
},
"visualizationSettings": {
"autoSelectVisualization": false,
"thresholds": [],
"table": {
"columnTypeOverrides": [
{
"fields": [
"Response time",
"Requests"
],
"value": "sparkline",
"id": 1755890244552
}
]
},
"chartSettings": {
"legend": {
"hidden": false,
"position": "bottom"
},
"seriesOverrides": [
{
"seriesId": [
"Requests"
],
"override": {
"geometry": "bar"
}
},
{
"seriesId": [
"Response time"
],
"override": {
"color": {
"Default": "var(--dt-colors-charts-categorical-color-01-default, #134fc9)"
}
}
}
],
"hiddenLegendFields": [
"value.A",
"value.B",
"value.C"
]
}
},
"querySettings": {
"maxResultRecords": 1000,
"defaultScanLimitGbytes": 500,
"maxResultMegaBytes": 1,
"defaultSamplingRatio": 10,
"enableSampling": false
}
}
{{- end}}
},
"layouts": {
{{- $tileWidth := 6 }}
{{- $tileHeight := 4 }}
{{- $maxWidth := 18 }}
{{- $left := 0 }}{{ $top := 0 }}
{{- range $i, $e := .services}}
{{- if $i}},{{- end}}
"{{$i}}": {
"x": {{ $left }},
"y": {{ $top }},
"w": {{ $tileWidth}},
"h": {{ $tileHeight }}
}
{{- $left = len (printf "%s%s" (printf "%*s" $left "") (printf "%*s" $tileWidth "")) }}
{{- if gt $left $maxWidth}}
{{- $left = 0 }}
{{- $top = len (printf "%s%s" (printf "%*s" $top "") (printf "%*s" $tileHeight ""))}}
{{- end}}
{{- end}}
},
"importedWithCode": false,
"settings": {}
}
Gen2 dashboard - dashboard-autolayout.json:
{
"dashboardMetadata": {
"dashboardFilter": null,
"hasConsistentColors": false,
"name": "{{.name}}",
"owner": "Monaco",
"preset": true,
"shared": true,
"tilesNameSize": null
},
"tiles": [
{{- $tileWidth := 418 }}
{{- $tileHeight := 266 }}
{{- $maxWidth := 1300 }}
{{- $left := 0 }}{{ $top := 0 }}
{{- range $i, $e := .services}}
{{- if $i}},{{- end}}
{
"name": "{{ $e.title }}",
"tileType": "DATA_EXPLORER",
"configured": true,
"bounds": {
"top": {{ $top }},
"left": {{ $left }},
"width": {{ $tileWidth}},
"height": {{ $tileHeight }}
},
"tileFilter": {},
"isAutoRefreshDisabled": false,
"customName": "{{ $e.title}}",
"queries": [
{
"id": "A",
"metric": "builtin:service.response.server",
"spaceAggregation": "AUTO",
"timeAggregation": "DEFAULT",
"splitBy": [],
"sortBy": "DESC",
"sortByDimension": "",
"filterBy": {
"filterOperator": "AND",
"nestedFilters": [
{
"filter": "dt.entity.service",
"filterType": "TAG",
"filterOperator": "OR",
"nestedFilters": [],
"criteria": [
{
"value": "{{ $e.tag }}",
"evaluator": "IN"
}
]
}
],
"criteria": []
},
"limit": 20,
"rate": "NONE",
"enabled": true
},
{
"id": "B",
"metric": "builtin:service.requestCount.total",
"spaceAggregation": "AUTO",
"timeAggregation": "DEFAULT",
"splitBy": [],
"sortBy": "DESC",
"sortByDimension": "",
"filterBy": {
"filterOperator": "AND",
"nestedFilters": [
{
"filter": "dt.entity.service",
"filterType": "TAG",
"filterOperator": "OR",
"nestedFilters": [],
"criteria": [
{
"value": "{{ $e.tag }}",
"evaluator": "IN"
}
]
}
],
"criteria": []
},
"limit": 20,
"rate": "NONE",
"enabled": true
},
{
"id": "C",
"metric": "builtin:service.response.client",
"spaceAggregation": "AUTO",
"timeAggregation": "DEFAULT",
"splitBy": [],
"sortBy": "DESC",
"sortByDimension": "",
"filterBy": {
"filterOperator": "AND",
"nestedFilters": [
{
"filter": "dt.entity.service",
"filterType": "TAG",
"filterOperator": "OR",
"nestedFilters": [],
"criteria": [
{
"value": "{{ $e.tag }}",
"evaluator": "IN"
}
]
}
],
"criteria": []
},
"limit": 20,
"rate": "NONE",
"enabled": true
}
],
"visualConfig": {
"type": "GRAPH_CHART",
"global": {},
"rules": [
{
"matcher": "A:",
"unitTransform": "auto",
"valueFormat": "auto",
"properties": {
"color": "RED",
"seriesType": "LINE"
},
"seriesOverrides": []
},
{
"matcher": "B:",
"unitTransform": "auto",
"valueFormat": "auto",
"properties": {
"color": "ROYALBLUE",
"seriesType": "COLUMN"
},
"seriesOverrides": []
},
{
"matcher": "C:",
"properties": {
"color": "DEFAULT"
},
"seriesOverrides": []
}
],
"axes": {
"xAxis": {
"displayName": "",
"visible": true
},
"yAxes": [
{
"displayName": "",
"visible": true,
"min": "AUTO",
"max": "AUTO",
"position": "LEFT",
"queryIds": [
"A",
"C"
],
"defaultAxis": true
},
{
"displayName": "",
"visible": true,
"min": "AUTO",
"max": "AUTO",
"position": "RIGHT",
"queryIds": [
"B"
],
"defaultAxis": true
}
]
},
"heatmapSettings": {
"yAxis": "VALUE",
"showLabels": false
},
"singleValueSettings": {
"showTrend": true,
"showSparkLine": true,
"linkTileColorToThreshold": true
},
"thresholds": [
{
"axisTarget": "LEFT",
"rules": [
{
"color": "#7dc540"
},
{
"color": "#f5d30f"
},
{
"color": "#dc172a"
}
],
"visible": true
}
],
"tableSettings": {
"hiddenColumns": []
},
"graphChartSettings": {
"connectNulls": false
},
"honeycombSettings": {
"showHive": true,
"showLegend": true,
"showLabels": false
}
},
"queriesSettings": {
"resolution": ""
}
}
{{- $left = len (printf "%s%s" (printf "%*s" $left "") (printf "%*s" $tileWidth "")) }}
{{- if gt $left $maxWidth}}
{{- $left = 0 }}
{{- $top = len (printf "%s%s" (printf "%*s" $top "") (printf "%*s" $tileHeight ""))}}
{{- end}}
{{- end}}
]
}
When deployed, we will get a nice grid of tiles on a dashboard, and we can add new services just in the YAML without touching the JSON:
22 Aug 2025 01:37 PM
For completeness, it’s even better to extract the metrics into YAML and then send them into JSON. This way, each tile can display different data. Such an approach would also allow using the tiles within a CI/CD pipeline, where, for example, during an application deployment a dashboard could be created based on the specific requirements of the application owner.
22 Aug 2025 02:21 PM
Sure, this example above is the bare minimum for showcasing the possibilities. It's also possible to use different tile types of course, like service or host health.
What I was unable to do was provide a reference to another Monaco configuration in the list. @nico_riedmann any suggestions? For example, to have the tag a reference to an auto-tag deployed by Monaco in the same project. Monaco does not seem to resolve dependencies in this parameter type.
22 Aug 2025 02:24 PM
This is really cool. I'd like to see something similar for modern dashboards too. Now that RUM on Grail is available, we're starting to move more and more of our dashboards to the new framework, but I find the JSON not as friendly to read as gen 2 dashboards.
22 Aug 2025 02:30 PM
@36Krazyfists certainly possible with Gen3 dashboards. I agree, the new dashboard JSONs are less friendly for editing 🙃
22 Aug 2025 08:46 PM
@36Krazyfists I updated this post with Gen3 dashboard example - same approach, just you have to loop twice through the list.