I have a few services the I run internally, and most of them are using nice sub-domains for easy access. Who wants to remember which ip address you assigned to which service? Or even worse, which port. This is compounded when you use docker and have a ton of random ports. Aint nobody got time to be remembering that.

So what do you do? "Bookmarks work well enough" I hear you say. Well yes, but not if you're using multiple devices. "So why not sync your bookmarks across?" Yeah, well shush you. We're doing dashboards. Here's a nice image of my Heimdall dashboard when I was using that.

I like purple. Shut up.

Looks fine I guess. Kinda cluttered but whatever. Then I heard about Homer. Looked nice and all, but I was put off by the yaml. Man do I dislike yaml with its stupid "can't use tabs". Anyway, long story short, I set up Homer, and it's just lovely. Look at it.

Latin quotes make me sound smort

As much as I dislike yaml, it is quite nice having the config stored in my local Gitlab instance. I won't go into setting that up as the instructions are pretty self explanatory. What I did want to delve into a little deeper is this section.

Looking through the tips and tricks section on the Homer github page shows a nice little method of getting the news on there. While that's cool and all, I don't particularly want to be bombarded with how crazy the outside world is. This got me thinking though. Wouldn't it be nice to have a little internal service status here? Ya know. Show what is working and what is doing the slacking?

Well I did it.

Green for good tings and dat.

Killing some services causes this to happen.

Red for when the lab has the sads.

Yeah. We got dynamic updates up in here. Wanna know how I did it? Ayt, I'll share.

So this all started when I heard about Statping. It's a nice little service that will poke your internal services occasionally and let you know if they're working or not.

It can be installed pretty easily on docker and the configuration afterwards is pretty trivial. With that done, you have access to a very nice api endpoint that can be accessed at ipaddress/api/services/

Poking that endpoing returns a nice json object that has all the information regarding the services you set up in statping. So how do we convert that data to a format that Homer can use?

Node-Red.

Also super simple to set up and has a docker container. Small caveat though, is that I couldn't get node-red to probe the statping endpoint if they were both on the same docker host. Not a huge problem as I just dumped it on its own dedicated vm. There's probably a smarter fix here, but this seems like a very boring problem so I chose the easy way, so that I could work on the more interesting parts.

I'm not even going to attempt to explain how to use Node-Red as I literally only installed it this morning. Additionally, I have almost no experience with Javascript so much DuckDuckGo'ing was required. I pretty much just took the flow shown on the Homer github repo and messed about with it until I managed to pull the data I wanted from the statping endpoint. Basically beating rocks together until it worked.

Should be pretty easy to follow along with what this does. The top portion polls the statping endpoint once a minute, applies some logic and stores the results. The bottom portion listens for a request to noderedipaddress/dashboard-endpoint. When it recieves a request, it sets some values based on the top portion stored, formats the response to match what Homer requires and then returns it. Here's the "logic".

// Get the number of services returned
msg.total = msg.payload.length
// Stores the number of services that returned as online
msg.success = 0;
// Default assumption is things are broke so set the sad values
msg.colour = "is-danger";
msg.title = "Halp"
msg.hasFailures = true;

// Going to return the failed services as a list so create an array and dump in a little html
var failures = ['<ul>'];

// Iterate through the json values from the statping endpoint
for(var i = 0; i < msg.total; i++) {
    if(msg.payload[i].online == true)
    {
        // If the service is online, increment the success variable
        msg.success++;
    }
    else {
        // Otherwise add it to the naughty list with some html
        failures.push('<li>' + msg.payload[i].name + '</li>');
    }
}
// Close off the html for the list
failures.push('</ul>');

// If the number of successfull services is the same as the total number of services, set things to be happy.
if(msg.success == msg.total)
{
    msg.colour = "is-success";
    msg.title = "All good in the hood boss";
    msg.hasFailures = false;
    msg.failures= "";
}
// Assuming there are any failures, we want to convert the array to a string and strip out the commas. Regex because
// node doesn't like str.replaceAll()
else
{
    msg.failures = failures.toString().replace(/,/g,'');   
}

return msg;

To save you the hassle though, you can import the flow I created. "It Works on my machine".

[{"id":"6a019d7.2b7fe64","type":"tab","label":"Statping Homer Flow","disabled":false,"info":""},{"id":"fba2bf1a.1937a","type":"http in","z":"6a019d7.2b7fe64","name":"","url":"/dashboard-endpoint","method":"get","upload":false,"swaggerDoc":"","x":130,"y":200,"wires":[["afb26f3b.5c86e8"]]},{"id":"fc7b07b3.729688","type":"http response","z":"6a019d7.2b7fe64","name":"Return JSON Response","statusCode":"200","headers":{},"x":900,"y":200,"wires":[]},{"id":"6e0735de.550464","type":"template","z":"6a019d7.2b7fe64","name":"format the response","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"{\n  \"style\": \"{{style}}\",\n  \"title\": \"{{title}}\",\n  \"content\": \"<b>{{{flow.success}}} / {{flow.total}} Services showing as online. Check <a href=https://statping.ipaddresss target=_blank>here</a> for more info. </br> <p>{{#flow.hasFailures}}<b><br>Here are the numpties that are letting the side down:</br> {{{flow.failures}}}{{/flow.hasFailures}}\"\n}","output":"json","x":650,"y":200,"wires":[["fc7b07b3.729688"]]},{"id":"87ec2a70.11a93","type":"http request","z":"6a019d7.2b7fe64","name":"Get Statping Data","method":"GET","ret":"obj","paytoqs":"ignore","url":"http://10.10.10.70:8880/api/services","tls":"","persist":false,"proxy":"","authType":"","x":450,"y":100,"wires":[["35c937ef.10881"]]},{"id":"afb26f3b.5c86e8","type":"change","z":"6a019d7.2b7fe64","name":"Set style, Title and Payload","rules":[{"t":"set","p":"style","pt":"msg","to":"colour","tot":"flow"},{"t":"set","p":"title","pt":"msg","to":"title","tot":"flow"},{"t":"set","p":"payload","pt":"msg","to":"feed","tot":"flow"}],"action":"","property":"","from":"","to":"","reg":false,"x":390,"y":200,"wires":[["6e0735de.550464"]]},{"id":"176a09fc.3e9b36","type":"inject","z":"6a019d7.2b7fe64","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"60","crontab":"","once":true,"onceDelay":"1","topic":"Every 1 Minute","payload":"true","payloadType":"bool","x":170,"y":100,"wires":[["87ec2a70.11a93"]]},{"id":"35c937ef.10881","type":"function","z":"6a019d7.2b7fe64","name":"Logic","func":"// Get the number of services returned\nmsg.total = msg.payload.length\n// Stores the number of services that returned as online\nmsg.success = 0;\n// Default assumption is things are broke so set the sad values\nmsg.colour = \"is-danger\";\nmsg.title = \"Halp\"\nmsg.hasFailures = true;\n\n// Going to return the failed services as a list so create an array and dump in a little html\nvar failures = ['<ul>'];\n\n// Iterate through the json values from the statping endpoint\nfor(var i = 0; i < msg.total; i++) {\n    if(msg.payload[i].online == true)\n    {\n        // If the service is online, increment the success variable\n        msg.success++;\n    }\n    else {\n        // Otherwise add it to the naughty list with some html\n        failures.push('<li>' + msg.payload[i].name + '</li>');\n    }\n}\n// Close off the html for the list\nfailures.push('</ul>');\n\n// If the number of successfull services is the same as the total number of services, set things to be happy.\nif(msg.success == msg.total)\n{\n    msg.colour = \"is-success\";\n    msg.title = \"All good in the hood boss\";\n    msg.hasFailures = false;\n    msg.failures= \"\";\n}\n// Assuming there are any failures, we want to convert the array to a string and strip out the commas. Regex because\n// node doesn't like str.replaceAll()\nelse\n{\n    msg.failures = failures.toString().replace(/,/g,'');   \n}\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":650,"y":100,"wires":[["62f765cc.5dc0f4"]]},{"id":"62f765cc.5dc0f4","type":"change","z":"6a019d7.2b7fe64","name":"Store success and totals","rules":[{"t":"move","p":"total","pt":"msg","to":"total","tot":"flow"},{"t":"move","p":"success","pt":"msg","to":"success","tot":"flow"},{"t":"move","p":"colour","pt":"msg","to":"colour","tot":"flow"},{"t":"move","p":"title","pt":"msg","to":"title","tot":"flow"},{"t":"move","p":"failures","pt":"msg","to":"failures","tot":"flow"},{"t":"move","p":"hasFailures","pt":"msg","to":"hasFailures","tot":"flow"}],"action":"","property":"","from":"","to":"","reg":false,"x":890,"y":100,"wires":[[]]}]

To get this to work, you'll need to double click on the "Get Statping Data" node and replace the URL with the one from your Statping endpoint. Finally, change the following section to match in your Homer config.yml file.

message:
  url: "https://noderedipordomainorwhatever/dashboard-endpoint"
  style: "is-danger"
  title: "Node Red"
  content: "Couldn't get dynamic data from Node Red!"

You'll want to read up on Moustache templating if you want to faff about with the "format the response" node. It use the "msg.hasFailures" variable to determine whether or not to display the failures. At the minimum, you'll want to change the content section in that node to include your Statping url or delete that portion altogether if you don't need it.

That's it. Anytime you add services to statping, they'll be reflected in the item count on the Homer dashboard.

Toodles.