initial commit

This commit is contained in:
Samuel Philipp 2020-02-26 23:56:42 +01:00
commit ea55fda9e5
5 changed files with 365 additions and 0 deletions

7
.gitignore vendored Normal file
View file

@ -0,0 +1,7 @@
.idea/
*.iml
node_modules/
cache.json
config.json

31
README.md Normal file
View file

@ -0,0 +1,31 @@
# cachet-monitor
Simple monitor to watch URLs (`HTTP`) or ports (`TCP`, `UDP`) and update Cachet status.
## Configuration
Example:
```json
{
"services": [
{
"id": 1,
"type": "HTTP",
"url": "https://sp-codes.de",
"timeout": 60
},
{
"id": 2,
"type": "TCP",
"host": "sp-codes.de",
"port": 443,
"timeout": 60
}
],
"cron": "0 * * * * *",
"offlineTimeUntilMajor": 300,
"api": "https://<cachet-url>/api/v1",
"token": "<user-token>"
}
```

132
index.js Normal file
View file

@ -0,0 +1,132 @@
const config = require('./config');
const fs = require('fs');
const cron = require('node-cron');
const fetch = require('node-fetch');
const abort = require('abort-controller');
const nmap = require('libnmap');
const cache = fs.existsSync("cache.json") ? JSON.parse(fs.readFileSync("cache.json", {encoding: "utf8"})) : {};
process.on('SIGINT', () => {
fs.writeFileSync("cache.json", JSON.stringify(cache), {encoding: "utf8"});
process.exit(0);
});
const cachetStatusMapping = {
"ONLINE": 1,
"SLOW": 2,
"OFFLINE": 3,
"INCIDENT": 4
};
const checkHttp = async (url, performanceTimeout, requestTimeout) => {
const controller = new abort.AbortController();
const timeout = setTimeout(() => controller.abort(), requestTimeout);
try {
const start = new Date().getTime();
const response = await fetch(url, {signal: controller.signal});
const stop = new Date().getTime();
if (response.ok) {
if (stop - start > performanceTimeout) {
return {status: "SLOW", message: response.statusText};
}
return {status: "ONLINE", message: response.statusText};
} else {
return {status: "OFFLINE", message: response.statusText};
}
} catch (e) {
return {status: "OFFLINE", message: e.message};
} finally {
clearTimeout(timeout);
}
};
const checkPort = async (host, port, type, performanceTimeout, requestTimeout) => {
return await new Promise(resolve => {
nmap.scan({
range: [host],
ports: port.toString(),
timeout: requestTimeout / 1000,
udp: type === 'udp'
}, (error, report) => {
if (error) {
resolve({status: "OFFLINE", message: error});
} else {
const result = report[host].host[0];
const time = parseInt(result.item.endtime) - parseInt(result.item.starttime);
const status = result.ports[0].port[0].state[0].item;
if (status.state.includes('open')) {
if (time > performanceTimeout) {
resolve({status: "SLOW", message: status.state});
} else {
resolve({status: "ONLINE", message: status.state});
}
} else {
resolve({status: "OFFLINE", message: status.state});
}
}
});
});
};
async function checkStatus(service) {
switch (service.type) {
case 'HTTP':
return await checkHttp(service.url, service.timeout * 1000, service.timeout * 2000);
case 'TCP':
return await checkPort(service.host, service.port, 'tcp', service.timeout * 1000, service.timeout * 2000);
case 'UDP':
return await checkPort(service.host, service.port, 'udp', service.timeout * 1000, service.timeout * 2000);
default:
throw new Error('unsupported type "' + type + '"')
}
}
const checkService = async (service, oldStatus) => {
const newStatus = await checkStatus(service);
newStatus.changed = new Date().getTime();
if (newStatus.status === "OFFLINE" && ["OFFLINE", "INCIDENT"].includes(oldStatus.status) &&
oldStatus.changed + config.offlineTimeUntilMajor * 1000 < newStatus.changed) {
newStatus.status = "INCIDENT";
}
return newStatus;
};
const pushStatusToCachet = async (id, status) => {
try {
let currentCachetStatus = cachetStatusMapping[status];
const oldState = await fetch(config.api + '/components/' + id).then(r => r.json());
if (oldState.data.status === currentCachetStatus) {
console.log('state already set');
return;
}
const update = await fetch(config.api + '/components/' + id, {
method: 'PUT',
body: JSON.stringify({status: currentCachetStatus}),
headers: {
'Content-Type': 'application/json',
'X-Cachet-Token': config.token
}
});
if (!update.ok) {
console.log('failed to update status: ' + update.statusText);
}
} catch (e) {
console.log('failed to update status', e);
}
};
const check = async () => {
for (const service of config.services) {
const oldStatus = cache[service.id];
const newStatus = await checkService(service, oldStatus);
if (oldStatus.status !== newStatus.status) {
console.log(service.id + ' status changed: ' + newStatus.status);
await pushStatusToCachet(service.id, newStatus.status);
cache[service.id] = newStatus;
}
}
};
cron.schedule(config.cron, async () => await check(), {});

180
package-lock.json generated Normal file
View file

@ -0,0 +1,180 @@
{
"name": "cachet-monitor",
"version": "1.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"abort-controller": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
"integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
"requires": {
"event-target-shim": "^5.0.0"
}
},
"async": {
"version": "2.6.3",
"resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz",
"integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==",
"requires": {
"lodash": "^4.17.14"
}
},
"bluebird": {
"version": "2.11.0",
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-2.11.0.tgz",
"integrity": "sha1-U0uQM8AiyVecVro7Plpcqvu2UOE="
},
"cidr-js": {
"version": "git+https://github.com/jas-/cidr-js.git#8bbf8227780da869d082096847fe3d9a74bf8ef4",
"from": "git+https://github.com/jas-/cidr-js.git#v2.3.2",
"requires": {
"bluebird": "^2.9.21",
"ip-subnet-calculator": "^1.0.2",
"line-by-line": "^0.1.3",
"locutus": "^2.0.9"
}
},
"deepmerge": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-2.2.1.tgz",
"integrity": "sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA=="
},
"es6-promise": {
"version": "4.2.8",
"resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz",
"integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w=="
},
"event-target-shim": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
"integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="
},
"hasbin": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/hasbin/-/hasbin-1.2.3.tgz",
"integrity": "sha1-eMWSaJPIAhXCtWiuH9P8q3omlrA=",
"requires": {
"async": "~1.5"
},
"dependencies": {
"async": {
"version": "1.5.2",
"resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz",
"integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo="
}
}
},
"ip-address": {
"version": "5.9.4",
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-5.9.4.tgz",
"integrity": "sha512-dHkI3/YNJq4b/qQaz+c8LuarD3pY24JqZWfjB8aZx1gtpc2MDILu9L9jpZe1sHpzo/yWFweQVn+U//FhazUxmw==",
"requires": {
"jsbn": "1.1.0",
"lodash": "^4.17.15",
"sprintf-js": "1.1.2"
}
},
"ip-subnet-calculator": {
"version": "1.1.8",
"resolved": "https://registry.npmjs.org/ip-subnet-calculator/-/ip-subnet-calculator-1.1.8.tgz",
"integrity": "sha1-m8AuIz1aQ++uet60X2ppS4K5cP0="
},
"jsbn": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz",
"integrity": "sha1-sBMHyym2GKHtJux56RH4A8TaAEA="
},
"libnmap": {
"version": "0.4.19",
"resolved": "https://registry.npmjs.org/libnmap/-/libnmap-0.4.19.tgz",
"integrity": "sha512-aMtOq3rsG5mH1UDqYRUf+DSojlSMh6wxV6B9qa8ZOeUHXguSaSCwABqQWMtjbU0o3d5y46nQPAvDZnPT9Thhvw==",
"requires": {
"async": "^2.6.3",
"cidr-js": "git+https://github.com/jas-/cidr-js.git#v2.3.2",
"deepmerge": "^2.2.1",
"hasbin": "^1.2.3",
"ip-address": "^5.9.4",
"netmask": "^1.0.6",
"stack-trace": "0.0.10",
"xml2js": "^0.4.22"
}
},
"line-by-line": {
"version": "0.1.6",
"resolved": "https://registry.npmjs.org/line-by-line/-/line-by-line-0.1.6.tgz",
"integrity": "sha512-MmwVPfOyp0lWnEZ3fBA8Ah4pMFvxO6WgWovqZNu7Y4J0TNnGcsV4S1LzECHbdgqk1hoHc2mFP1Axc37YUqwafg=="
},
"locutus": {
"version": "2.0.11",
"resolved": "https://registry.npmjs.org/locutus/-/locutus-2.0.11.tgz",
"integrity": "sha512-C0q1L38lK5q1t+wE0KY21/9szrBHxye6o2z5EJzU+5B79tubNOC+nLAEzTTn1vPUGoUuehKh8kYKqiVUTWRyaQ==",
"requires": {
"es6-promise": "^4.2.5"
}
},
"lodash": {
"version": "4.17.15",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A=="
},
"netmask": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/netmask/-/netmask-1.0.6.tgz",
"integrity": "sha1-ICl+idhvb2QA8lDZ9Pa0wZRfzTU="
},
"node-cron": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/node-cron/-/node-cron-2.0.3.tgz",
"integrity": "sha512-eJI+QitXlwcgiZwNNSRbqsjeZMp5shyajMR81RZCqeW0ZDEj4zU9tpd4nTh/1JsBiKbF8d08FCewiipDmVIYjg==",
"requires": {
"opencollective-postinstall": "^2.0.0",
"tz-offset": "0.0.1"
}
},
"node-fetch": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz",
"integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA=="
},
"opencollective-postinstall": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/opencollective-postinstall/-/opencollective-postinstall-2.0.2.tgz",
"integrity": "sha512-pVOEP16TrAO2/fjej1IdOyupJY8KDUM1CvsaScRbw6oddvpQoOfGk4ywha0HKKVAD6RkW4x6Q+tNBwhf3Bgpuw=="
},
"sax": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
"integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw=="
},
"sprintf-js": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz",
"integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug=="
},
"stack-trace": {
"version": "0.0.10",
"resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz",
"integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA="
},
"tz-offset": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/tz-offset/-/tz-offset-0.0.1.tgz",
"integrity": "sha512-kMBmblijHJXyOpKzgDhKx9INYU4u4E1RPMB0HqmKSgWG8vEcf3exEfLh4FFfzd3xdQOw9EuIy/cP0akY6rHopQ=="
},
"xml2js": {
"version": "0.4.23",
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz",
"integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==",
"requires": {
"sax": ">=0.6.0",
"xmlbuilder": "~11.0.0"
}
},
"xmlbuilder": {
"version": "11.0.1",
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
"integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA=="
}
}
}

15
package.json Normal file
View file

@ -0,0 +1,15 @@
{
"name": "cachet-monitor",
"version": "1.0.0",
"description": "",
"scripts": {
"start": "node index.js"
},
"author": "codes@samuel-philipp.de",
"dependencies": {
"abort-controller": "^3.0.0",
"libnmap": "^0.4.19",
"node-cron": "^2.0.3",
"node-fetch": "^2.6.0"
}
}