From: Ruben Beltran del Rio Date: Tue, 8 Jun 2021 19:37:55 +0000 (+0200) Subject: Add the monitor X-Git-Url: https://git.r.bdr.sh/rbdr/monitorcito/commitdiff_plain/2d72aab0a88baa906127489e1c25fd11881bdc35 Add the monitor --- 2d72aab0a88baa906127489e1c25fd11881bdc35 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6ed48a9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.env +node_modules diff --git a/README.md b/README.md new file mode 100644 index 0000000..807c1f7 --- /dev/null +++ b/README.md @@ -0,0 +1,48 @@ +# Monitorcito + +A tiny monitor that gives you the status of a handful of systemd services. + +## Configuration + +This project uses environment variables to change configuration. + +* `MONITORCITO_SERVICES`: A comma separated list of services. Required. +* `MONITORCITO_PORT`: A comma separated list of services. Defaults to 1991. + + +Set `NODE_DEBUG=monitorcito` if you want to log any output. Otherwise it will +be silent. + +## How to run + +Set the appropriate environment variables and run `bin/monitorcito`. + +## The Frontend + +Monitorcito includes a public directory, this is a frontend that queries +the service and refreshes every 5 seconds. The service assumes the API +is running in `/api`. + +Here's an example of how to set it up with nginx. + +``` +upstream monitorcito { + server localhost:1991; + keepalive 64; +} + +server { + listen 80; + + root /home/deploy/src/monitorcito/public; + index index.html; + + server_name monitor.unlimited.pizza; + + location /api { + proxy_redirect off; + + proxy_pass http://monitorcito; + } +} +``` diff --git a/bin/monitorcito b/bin/monitorcito new file mode 100755 index 0000000..2d41152 --- /dev/null +++ b/bin/monitorcito @@ -0,0 +1,55 @@ +#!/usr/bin/env node + +const Monitorcito = require('..'); +const Http = require('http'); +const { debuglog } = require('util'); + +const internals = { + + kUnsetServicesError: 'Please specify comma separated services in MONITORCITO_SERVICES env variable', + + arguments: null, + log: debuglog('monitorcito'), + + prepareArguments() { + + internals.log('Validating arguments'); + if (!process.env.MONITORCITO_SERVICES) { + throw new Error(internals.kUnsetServicesError); + } + + internals.arguments = process.env.MONITORCITO_SERVICES.split(',') + internals.log(`Arguments are ${internals.arguments}`); + }, + + startServer() { + + internals.log('Setting up the server'); + const server = Http.createServer(async (request, response) => { + + internals.log('Incoming request'); + const responseBody = JSON.stringify(await Monitorcito(internals.arguments)); + internals.log(`Responding with ${responseBody}`); + + response.writeHead(200, { 'Content-Type': 'application/json' }); + response.write(responseBody); + response.end(); + }); + const port = Number(process.env.MONITORCITO_PORT) || 1991; + server.listen(port); + internals.log(`Listening on port ${port}`); + }, + + async run() { + + internals.prepareArguments(); + internals.startServer(); + } +}; + +internals.run() + .catch((error) => { + + console.error(error); + process.exit(1); + }); diff --git a/env.dist b/env.dist new file mode 100644 index 0000000..6a1d0f6 --- /dev/null +++ b/env.dist @@ -0,0 +1 @@ +MONITORCITO_SERVICES=comma,separated,list diff --git a/lib/index.js b/lib/index.js new file mode 100644 index 0000000..d5d9668 --- /dev/null +++ b/lib/index.js @@ -0,0 +1,44 @@ +const { promisify } = require('util'); +const { exec } = require('child_process'); + +const internals = { + kActiveIndicator: 'active', + kFieldSeparator: '\n', + kKeyValueSeparator: '=', + kBlockSeparator: '\n\n', + statusCommand: 'systemctl show --no-page -p Id -p ActiveState', + + formatStatusOutput(systemctlText) { + + const blocks = systemctlText.trim().split(internals.kBlockSeparator); + return blocks + .map((block) => { + + const fields = block.split(internals.kFieldSeparator); + return fields.reduce((fieldObject, field) => { + + const [key, value] = field.split(internals.kKeyValueSeparator); + return { + ...fieldObject, + [key]: value + } + }, {}) + + }) + .reduce((statusObject, service) => { + + return { + ...statusObject, + [service.Id]: service.ActiveState === internals.kActiveIndicator + } + }, {}); + }, + + exec: promisify(exec) +}; + +module.exports = async (services) => { + + const { stdout } = await internals.exec(`${internals.statusCommand} ${services.join(' ')}`); + return internals.formatStatusOutput(stdout); +}; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..04670a8 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,5 @@ +{ + "name": "monitorcito", + "version": "1.0.0", + "lockfileVersion": 1 +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..68baafa --- /dev/null +++ b/package.json @@ -0,0 +1,19 @@ +{ + "name": "monitorcito", + "version": "1.0.0", + "description": "A tiny web monitor for systems", + "main": "lib/index.js", + "directories": { + "lib": "lib" + }, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [ + "systemd", + "monitor" + ], + "author": "Rubén Beltrán del Río ", + "license": "Apache-2.0", + "dependencies": {} +} diff --git a/public/css/application.css b/public/css/application.css new file mode 100644 index 0000000..094313c --- /dev/null +++ b/public/css/application.css @@ -0,0 +1,41 @@ +h1 canvas { + width: 64px; + height: 64px; + display: inline-block; + background-color: gainsboro; +} + +table { + border-collapse: collapse; +} + +th, td { + padding: 8px; + border-style: inset; +} + +.true { + color: green; +} +.false { + color: red; +} +.critical { + font-weight: bold; + color: red; +} + +@media (prefers-color-scheme: dark) { + body { + color: white; + background-color: black; + } + + a { + color: #5dc1fd; + } + + a:visited { + color: #ed6eff; + } +} diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..6e673be --- /dev/null +++ b/public/index.html @@ -0,0 +1,36 @@ + + + + + + + + + + monitor @ unlimited 🍕 + + + + + + + + +
+

+ + Monitorcito +

+
+
+

Status of services running in unlimited dot pizza

+ +
+
+ + diff --git a/public/js/monitorcito.js b/public/js/monitorcito.js new file mode 100644 index 0000000..ef428a5 --- /dev/null +++ b/public/js/monitorcito.js @@ -0,0 +1,40 @@ +(function () { + + const table = document.querySelector('#monitorcito'); + + function loadData () { + fetch('/api') + .then((response) => response.json()) + .then((serviceStatus) => { + + while (table.firstChild) { + table.removeChild(table.firstChild) + } + + for (const [service, isActive] of Object.entries(serviceStatus)) { + const row = document.createElement('tr'); + const serviceCell = document.createElement('td'); + serviceCell.appendChild(document.createTextNode(service)); + const isActiveCell = document.createElement('td'); + isActiveCell.appendChild(document.createTextNode(isActive ? 'OK' : 'FAIL')); + isActiveCell.classList.add(isActive); + row.appendChild(serviceCell); + row.appendChild(isActiveCell); + table.appendChild(row); + } + + setTimeout(loadData, 5000); + }) + .catch(() => { + const container = document.querySelector('main'); + container.removeChild(table); + const paragraph = document.createElement('p'); + paragraph.appendChild(document.createTextNode('Everything is broken. If your internet works, please check the server\'s not on fire. When in doubt, just refresh.')); + paragraph.classList.add('critical'); + container.appendChild(paragraph); + }) + } + + loadData(); +} +)();