mirror of
https://github.com/smartyellow/status.git
synced 2025-06-28 04:35:11 +00:00
Merged some files
This commit is contained in:
@ -1,60 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const { rollup } = require('rollup');
|
||||
const commonjs = require('@rollup/plugin-commonjs');
|
||||
const css = require('rollup-plugin-css-only');
|
||||
const { default: resolve } = require('@rollup/plugin-node-resolve');
|
||||
const svelte = require('rollup-plugin-svelte');
|
||||
const { terser } = require('rollup-plugin-terser');
|
||||
|
||||
async function build() {
|
||||
let cssOutput = '';
|
||||
|
||||
try {
|
||||
const bundle = await rollup({
|
||||
input: __dirname + '/../../gui/dashboard/index.js',
|
||||
plugins: [
|
||||
// Svelte
|
||||
svelte({
|
||||
compilerOptions: {
|
||||
dev: false,
|
||||
generate: 'dom',
|
||||
},
|
||||
}),
|
||||
|
||||
// Extract CSS
|
||||
css({ output: style => cssOutput = style }),
|
||||
|
||||
// Resolve dependencies
|
||||
resolve({
|
||||
browser: true,
|
||||
dedupe: [ 'svelte' ],
|
||||
}),
|
||||
|
||||
// CommonJS functions
|
||||
commonjs(),
|
||||
|
||||
// Minify
|
||||
terser(),
|
||||
],
|
||||
});
|
||||
|
||||
const { output } = await bundle.generate({
|
||||
sourcemap: false,
|
||||
format: 'iife',
|
||||
name: 'app',
|
||||
file: 'public/build/bundle.js',
|
||||
});
|
||||
|
||||
return {
|
||||
map: output[0].map ? output[0].map.toUrl() : '',
|
||||
code: output[0].code,
|
||||
css: cssOutput,
|
||||
};
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Error while building status dashboard: ', error);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = build;
|
@ -1,108 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const { makeId } = require('core/makeid');
|
||||
|
||||
const decoder = new TextDecoder('utf-8');
|
||||
let downIdsBefore = [];
|
||||
let downIdsAfter = [];
|
||||
|
||||
const mapService = (s, beat) => ({
|
||||
id: s.id,
|
||||
name: s.name,
|
||||
cluster: s.cluster,
|
||||
lastBeat: beat,
|
||||
checked: s.lastChecked,
|
||||
});
|
||||
|
||||
async function createDashboardSocket(server) {
|
||||
server.ws({
|
||||
route: '/status/dashboard/socket',
|
||||
onOpen: async ws => {
|
||||
async function sendStatuses() {
|
||||
const services = await server.storage
|
||||
.store('smartyellow/webservice')
|
||||
.find({ public: true })
|
||||
.toArray();
|
||||
const heartbeats = await server.storage
|
||||
.store('smartyellow/webserviceheartbeat')
|
||||
.find({ webservice: { $in: services.map(s => s.id) } })
|
||||
.sort({ date: -1 })
|
||||
.toArray();
|
||||
|
||||
const servicesUp = [];
|
||||
const servicesDown = [];
|
||||
const servicesUnknown = [];
|
||||
downIdsAfter = [];
|
||||
|
||||
for (let service of services) {
|
||||
const beat = heartbeats.find(b => b.webservice === service.id);
|
||||
service = mapService(service, beat);
|
||||
|
||||
if (!beat) {
|
||||
servicesUnknown.push(service);
|
||||
}
|
||||
else if (beat.down) {
|
||||
servicesDown.push(service);
|
||||
downIdsAfter.push(service.id);
|
||||
}
|
||||
else {
|
||||
servicesUp.push(service);
|
||||
}
|
||||
}
|
||||
|
||||
const total = [
|
||||
...servicesUp,
|
||||
...servicesDown,
|
||||
...servicesUnknown,
|
||||
].length;
|
||||
|
||||
let newOutage = false;
|
||||
for (const id of downIdsAfter) {
|
||||
if (!downIdsBefore.includes(id)) {
|
||||
newOutage = true;
|
||||
}
|
||||
}
|
||||
downIdsBefore = JSON.parse(JSON.stringify(downIdsAfter));
|
||||
|
||||
try {
|
||||
if (newOutage) {
|
||||
ws.send(JSON.stringify({ cmd: 'bell' }));
|
||||
}
|
||||
|
||||
ws.send(JSON.stringify({
|
||||
cmd: 'data',
|
||||
servicesUp,
|
||||
servicesDown,
|
||||
servicesUnknown,
|
||||
total,
|
||||
}));
|
||||
}
|
||||
catch {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
sendStatuses();
|
||||
setInterval(sendStatuses, 5000);
|
||||
},
|
||||
onUpgrade: async () => ({ id: makeId(10) }),
|
||||
onMessage: async (ws, msg) => {
|
||||
msg = JSON.parse(decoder.decode(msg));
|
||||
|
||||
if (!msg || !msg.command) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (msg.command) {
|
||||
case 'data':
|
||||
ws.send('data');
|
||||
return;
|
||||
|
||||
default:
|
||||
return;
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = createDashboardSocket;
|
@ -1,141 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const { makeId } = require('core/makeid');
|
||||
|
||||
async function processOutage({ outage, server, settings, onDateUpdated }) {
|
||||
if (typeof onDateUpdated !== 'function') {
|
||||
onDateUpdated = () => null;
|
||||
}
|
||||
|
||||
for (const [ id, testResult ] of Object.entries(outage)) {
|
||||
// Update check date
|
||||
server.storage.store('smartyellow/webservice').update(
|
||||
{ id },
|
||||
{ $set: { lastChecked: new Date() } }
|
||||
).then(() => onDateUpdated(id));
|
||||
|
||||
// Get service entry
|
||||
const service = await server
|
||||
.storage
|
||||
.store('smartyellow/webservice')
|
||||
.findOne({ id });
|
||||
|
||||
// Get last heartbeat
|
||||
const heartbeat = await server
|
||||
.storage
|
||||
.store('smartyellow/webserviceheartbeat')
|
||||
.find({ webservice: id })
|
||||
.toArray();
|
||||
const lastBeat = heartbeat[heartbeat.length - 1];
|
||||
|
||||
// Encountered an error while checking status
|
||||
if (testResult.error) {
|
||||
server.error('Error while checking status of ' + id);
|
||||
server.error(testResult);
|
||||
}
|
||||
|
||||
// Service is down
|
||||
else if (!testResult.serviceUp) {
|
||||
// Don't perform automatic actions if already done
|
||||
if ((lastBeat && lastBeat.down == false) || !lastBeat) {
|
||||
// Insert heartbeat if last one is not valid anymore
|
||||
try {
|
||||
server.storage.store('smartyellow/webserviceheartbeat').insert({
|
||||
id: makeId(10),
|
||||
down: true,
|
||||
webservice: id,
|
||||
testResult,
|
||||
date: new Date(),
|
||||
});
|
||||
}
|
||||
catch (err) {
|
||||
server.error('could not save web service heartbeat');
|
||||
server.error(err);
|
||||
}
|
||||
|
||||
// Send e-mail notification
|
||||
if (server.sendEmail && settings.emailSender && settings.emailRecipient) {
|
||||
try {
|
||||
const date = new Date().toLocaleString('en-GB', {
|
||||
dateStyle: 'full',
|
||||
timeStyle: 'full',
|
||||
timeZone: 'Etc/UTC',
|
||||
});
|
||||
const a = await server.sendEmail({
|
||||
sender: settings.emailSender,
|
||||
to: settings.emailRecipient,
|
||||
subject: `[outage] ${service.name} is down`,
|
||||
body: `Dear recipient,
|
||||
|
||||
As of ${date} UTC time, the service "${service.name}" does not meet the requirements for being
|
||||
considered as working.
|
||||
|
||||
Technical information containing the reason for this alert:
|
||||
${JSON.stringify(testResult, null, 2)}
|
||||
|
||||
Please always check this before taking action. This is an automated message.`,
|
||||
});
|
||||
console.log(a);
|
||||
}
|
||||
catch (err) {
|
||||
server.error('could not send endpoint status notification e-mail');
|
||||
server.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
// Draft outage entry
|
||||
if (settings.draftOutageEntries) {
|
||||
try {
|
||||
server
|
||||
.storage
|
||||
.store('smartyellow/webserviceoutage')
|
||||
.insert({
|
||||
id: makeId(),
|
||||
name: {
|
||||
en: `[automatic] Outage for ${service.name.en}`,
|
||||
},
|
||||
state: 'concept',
|
||||
resolved: false,
|
||||
services: [ service.id ],
|
||||
tags: [ 'automatically created' ],
|
||||
notes: [ {
|
||||
date: new Date(),
|
||||
userId: 'system',
|
||||
text: `Automatically created outage. Reason: ${JSON.stringify(testResult, null, 2)}`,
|
||||
} ],
|
||||
});
|
||||
}
|
||||
catch (err) {
|
||||
server.error('could not automatically draft outage entry');
|
||||
server.error(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Service up
|
||||
else {
|
||||
// Don't perform automatic actions if already done
|
||||
if ((lastBeat && lastBeat.down == true) || !lastBeat) {
|
||||
// Insert heartbeat if last one is not valid anymore
|
||||
try {
|
||||
await server.storage.store('smartyellow/webserviceheartbeat').insert({
|
||||
id: makeId(10),
|
||||
down: false,
|
||||
webservice: id,
|
||||
testResult,
|
||||
date: new Date(),
|
||||
});
|
||||
}
|
||||
catch (err) {
|
||||
server.error('could not save web service heartbeat');
|
||||
server.error(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
module.exports = { processOutage };
|
@ -1,6 +1,84 @@
|
||||
'use strict';
|
||||
|
||||
const { testEndpoints } = require('./testendpoints');
|
||||
const fetch = require('node-fetch');
|
||||
const { operators } = require('./operators');
|
||||
const { realValues } = require('./realvalues');
|
||||
const http = require('http');
|
||||
const https = require('https');
|
||||
|
||||
// Force requests over IPv4
|
||||
const httpAgent = new http.Agent({ family: 4 });
|
||||
const httpsAgent = new https.Agent({ family: 4 });
|
||||
|
||||
async function testEndpoints(endpoints) {
|
||||
const output = {
|
||||
serviceUp: undefined,
|
||||
success: true,
|
||||
error: false,
|
||||
requirement: undefined,
|
||||
realValue: undefined,
|
||||
};
|
||||
|
||||
for (const endpoint of endpoints) {
|
||||
try {
|
||||
const headers = endpoint.headers.reduce((obj, item) => {
|
||||
obj[item.name] = item.value;
|
||||
return obj;
|
||||
}, {});
|
||||
|
||||
const res = await fetch(endpoint.uri, {
|
||||
headers,
|
||||
agent: url => {
|
||||
if (url.protocol === 'http:') {
|
||||
return httpAgent;
|
||||
}
|
||||
return httpsAgent;
|
||||
},
|
||||
});
|
||||
const body = await res.text();
|
||||
|
||||
endpoint.requirements.forEach(requirement => {
|
||||
if (output.success === false || output.serviceUp === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Object.keys(operators).includes(requirement.operator)) {
|
||||
output.success = false;
|
||||
output.error = 'unknown operator: ' + requirement.operator;
|
||||
}
|
||||
|
||||
if (!Object.keys(realValues).includes(requirement.type)) {
|
||||
output.success = false;
|
||||
output.error = 'unknown type: ' + requirement.type;
|
||||
}
|
||||
|
||||
const realValue = realValues[requirement.type]({ res, body });
|
||||
let result = operators[requirement.operator](realValue, requirement.string);
|
||||
|
||||
if (!requirement.truth) {
|
||||
result = !result;
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
output.serviceUp = false;
|
||||
output.requirement = requirement;
|
||||
output.realValue = realValue;
|
||||
}
|
||||
else {
|
||||
output.serviceUp = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (err) {
|
||||
output.success = false;
|
||||
output.serviceUp = false;
|
||||
output.error = err;
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
process.on('message', async message => {
|
||||
switch (message.command) {
|
||||
|
@ -1,83 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const fetch = require('node-fetch');
|
||||
const { operators } = require('./operators');
|
||||
const { realValues } = require('./realvalues');
|
||||
const http = require('http');
|
||||
const https = require('https');
|
||||
|
||||
// Force requests over IPv4
|
||||
const httpAgent = new http.Agent({ family: 4 });
|
||||
const httpsAgent = new https.Agent({ family: 4 });
|
||||
|
||||
async function testEndpoints(endpoints) {
|
||||
const output = {
|
||||
serviceUp: undefined,
|
||||
success: true,
|
||||
error: false,
|
||||
requirement: undefined,
|
||||
realValue: undefined,
|
||||
};
|
||||
|
||||
for (const endpoint of endpoints) {
|
||||
try {
|
||||
const headers = endpoint.headers.reduce((obj, item) => {
|
||||
obj[item.name] = item.value;
|
||||
return obj;
|
||||
}, {});
|
||||
|
||||
const res = await fetch(endpoint.uri, {
|
||||
headers,
|
||||
agent: url => {
|
||||
if (url.protocol === 'http:') {
|
||||
return httpAgent;
|
||||
}
|
||||
return httpsAgent;
|
||||
},
|
||||
});
|
||||
const body = await res.text();
|
||||
|
||||
endpoint.requirements.forEach(requirement => {
|
||||
if (output.success === false || output.serviceUp === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Object.keys(operators).includes(requirement.operator)) {
|
||||
output.success = false;
|
||||
output.error = 'unknown operator: ' + requirement.operator;
|
||||
}
|
||||
|
||||
if (!Object.keys(realValues).includes(requirement.type)) {
|
||||
output.success = false;
|
||||
output.error = 'unknown type: ' + requirement.type;
|
||||
}
|
||||
|
||||
const realValue = realValues[requirement.type]({ res, body });
|
||||
let result = operators[requirement.operator](realValue, requirement.string);
|
||||
|
||||
if (!requirement.truth) {
|
||||
result = !result;
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
output.serviceUp = false;
|
||||
output.requirement = requirement;
|
||||
output.realValue = realValue;
|
||||
}
|
||||
else {
|
||||
output.serviceUp = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (err) {
|
||||
output.success = false;
|
||||
output.serviceUp = false;
|
||||
output.error = err;
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
module.exports = { testEndpoints };
|
Reference in New Issue
Block a user