Grafana proved too annoying to use, so we host our own Flask+websocket server.
The visuals are generated by nnenov
The visualisation is in Grafana.
Set up a canvas in a Grafana Dashboard, and name it VISUALISATION. Optionally, add a few SVG elements.
Also, add a service account and put the API key into .env. (cp .env.example .env)
$ ./get_dashboard.sh 25t62w56-25ttg-252d-24gf-28thjf8wsgfws \
tee dashboard.json
{
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": {
"type": "grafana",
"uid": "-- Grafana --"
},
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"type": "dashboard"
}
]
},
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 0,
"links": [],
"panels": [
{
…
…
}This visualisation relies on SVG logos on a Grafana Canvas. If you've saved the Grafana dashboard as dashboard.json, see the elements like:
# see all
cat dashboard.json | jq -c '.panels[] | select(.title == "VISUALISATION") | .options.root.elements'
# see first (example)
cat dashboard.json | jq '.panels[] | select(.title == "VISUALISATION") | .options.root.elements[0]'example of an element (static). When it is a dynamic element, then .config.path.mode should be switched to field (not fixed) — it's easy to do this via the GUI.
{
"background": {
"color": {
"fixed": "transparent"
}
},
"border": {
"color": {
"fixed": "transparent"
}
},
"config": {
"fill": {
"fixed": "transparent"
},
"path": {
"field": "eng_bench",
"fixed": "https://server.alifeee.net/static/shhm/state/objects.engineRoom_bench.NA.svg",
"mode": "fixed"
}
},
"constraint": {
"horizontal": "left",
"vertical": "top"
},
"links": [],
"name": "eng_bench",
"placement": {
"height": 540,
"left": 0,
"rotation": 0,
"top": 0,
"width": 540
},
"type": "icon"
}In order to be dynamic, this visualisation uses overrides to change the value of an logo's SVG URL based on the content of a field, via overrides.
# see all
cat dashboard.json | jq -c '.panels[] | select(.title == "VISUALISATION") | .fieldConfig.overrides'
# see single override
cat dashboard.json | jq '.panels[] | select(.title == "VISUALISATION") | .fieldConfig.overrides[0]'An example of an override. This override changes the icons of anything which is linked to the field eng_bench. It uses a value match so is set to on if the value is ON, and a regex to match anything else.
{
"matcher": {
"id": "byName",
"options": "eng_bench"
},
"properties": [
{
"id": "mappings",
"value": [
{
"options": {
"ON": {
"icon": "https://server.alifeee.net/static/images/shhm/state/objects.engineRoom_bench.on.svg",
"index": 0
}
},
"type": "value"
},
{
"options": {
"pattern": ".*",
"result": {
"icon": "https://server.alifeee.net/static/images/shhm/state/objects.engineRoom_bench.off.svg",
"index": 1
}
},
"type": "regex"
}
]
}
]
}function make_element_json() {
URL_BASE="${1}"
id="${2}"
name="${3}"
config_element='.
| .background.color.fixed = "transparent"
| .border.color.fixed = "transparent"
| .config.fill.fixed = "transparent"
| .config.path.fixed = $img_url + ".NA.svg"
| .config.path.field = $name
| .config.path.mode = "fixed"
| .constraint.horizontal = "left"
| .constraint.vertical = "top"
| .links = []
| .name = $name
| .placement.height = 540
| .placement.width = 540
| .placement.left = 0
| .placement.rotation = 0
| .placement.top = 0
| .type = "icon"
'
jq -n \
--arg name "${name}" \
--arg img_url "${URL_BASE}${id}" \
"${config_element}"
}
el_json=$(
make_element_json "https://server.alifeee.net/static/shhm/state/objects." "Commons_TVDisplay" "com_tv"
)
echo "${el_json}" | jq
function make_override_json() {
URL_BASE="${1}"
id="${2}"
name="${3}"
on_state="${4}"
config_override='.
| .matcher.id = "byName"
| .matcher.options = $name
| .properties[0].id = "mappings"
| .properties[0].value[0].options |= (.[$on_state].icon = $img_url + ".on.svg")
| .properties[0].value[0].options |= (.[$on_state].index = 0)
| .properties[0].value[0].type = "value"
| .properties[0].value[1].options.pattern = ".*"
| .properties[0].value[1].options.result.icon = $img_url + ".off.svg"
| .properties[0].value[1].options.result.index = 1
| .properties[0].value[1].type = "regex"
'
jq -n \
--arg name "${name}" \
--arg img_url "${URL_BASE}${id}" \
--arg on_state "${on_state}" \
"${config_override}"
}
ovrd_json=$(
make_override_json "https://server.alifeee.net/static/shhm/state/objects." "engineRoom_bench" "eng_bench" "ON"
)
echo "${ovrd_json}" | jqThe above just generate elements/overrides, but to put them into the JSON, use something like:
dashboard_id="25t62w56-25ttg-252d-24gf-28thjf8wsgfws"
./get_dashboard.sh "${dashboard_id}" > dashboard.json
panel_title="VISUALISATION"
cat dashboard.json \
| jq \
--arg paneltitle "${panel_title}" \
--argjson newelement "${el_json}" \
'.panels |= map(
if .title == $paneltitle then
(.options.root.elements += [$newelement])
else
.
end
)' \
| sponge dashboard.json
./set_dashboard.sh "${dashboard_id}" dashboard.json
./get_dashboard.sh "${dashboard_id}" > dashboard.jsoncreate list of commands to generate elements (you have to manually set the names)
ls | grep objects | sed -E s'/objects\.//; /\.(off|on)/d; s/\.NA\.png//; s+^+el_json=$(make_element_json https://server.alifeee.net/static/shhm/state/objects. +; s/$/ NAME)/'