Rewrite [uptimerobot] (#1891)

This commit is contained in:
Paul Melnikow
2018-08-12 21:47:30 -07:00
committed by GitHub
parent 66d444aa40
commit 0c703c11b5
6 changed files with 210 additions and 157 deletions

View File

@@ -1466,21 +1466,6 @@ const allBadgeExamples = [
keywords: ['website'],
documentation: websiteDoc,
},
{
title: 'Uptime Robot status',
previewUri:
'/uptimerobot/status/m778918918-3e92c097147760ee39d02d36.svg',
},
{
title: 'Uptime Robot ratio (30 days)',
previewUri:
'/uptimerobot/ratio/m778918918-3e92c097147760ee39d02d36.svg',
},
{
title: 'Uptime Robot ratio (7 days)',
previewUri:
'/uptimerobot/ratio/7/m778918918-3e92c097147760ee39d02d36.svg',
},
],
},
{

132
server.js
View File

@@ -6682,138 +6682,6 @@ cache(function(data, match, sendBadge, request) {
});
}));
// Uptime Robot status integration.
// API documentation : https://uptimerobot.com/api
camp.route(/^\/uptimerobot\/status\/(.*)\.(svg|png|gif|jpg|json)$/,
cache(function(data, match, sendBadge, request) {
var monitorApiKey = match[1]; // eg, m778918918-3e92c097147760ee39d02d36
var format = match[2];
var badgeData = getBadgeData('status', data);
var options = {
method: 'POST',
json: true,
body: {
"api_key": monitorApiKey,
"format": "json",
},
uri: 'https://api.uptimerobot.com/v2/getMonitors',
};
// A monitor API key must start with "m"
if (monitorApiKey.substring(0, "m".length) !== "m") {
badgeData.text[1] = 'must use a monitor key';
sendBadge(format, badgeData);
return;
}
request(options, function(err, res, json) {
if (err !== null || res.statusCode >= 500 || typeof json !== 'object') {
badgeData.text[1] = 'inaccessible';
sendBadge(format, badgeData);
return;
}
try {
if (json.stat === 'fail') {
badgeData.text[1] = 'vendor error';
if (json.error && typeof json.error.message === 'string') {
badgeData.text[1] = json.error.message;
}
badgeData.colorscheme = 'lightgrey';
sendBadge(format, badgeData);
return;
}
var status = json.monitors[0].status;
if (status === 0) {
badgeData.text[1] = 'paused';
badgeData.colorscheme = 'yellow';
} else if (status === 1) {
badgeData.text[1] = 'not checked yet';
badgeData.colorscheme = 'yellowgreen';
} else if (status === 2) {
badgeData.text[1] = 'up';
badgeData.colorscheme = 'brightgreen';
} else if (status === 8) {
badgeData.text[1] = 'seems down';
badgeData.colorscheme = 'orange';
} else if (status === 9) {
badgeData.text[1] = 'down';
badgeData.colorscheme = 'red';
} else {
badgeData.text[1] = 'invalid';
badgeData.colorscheme = 'lightgrey';
}
sendBadge(format, badgeData);
} catch(e) {
badgeData.text[1] = 'invalid';
sendBadge(format, badgeData);
}
});
}));
// Uptime Robot ratio integration.
// API documentation : https://uptimerobot.com/api
camp.route(/^\/uptimerobot\/ratio(\/[^/]+)?\/(.*)\.(svg|png|gif|jpg|json)$/,
cache(function(data, match, sendBadge, request) {
var numberOfDays = match[1]; // eg, 7, null if querying 30
var monitorApiKey = match[2]; // eg, m778918918-3e92c097147760ee39d02d36
var format = match[3];
var badgeData = getBadgeData('uptime', data);
if (numberOfDays) {
numberOfDays = numberOfDays.slice(1);
} else {
numberOfDays = '30';
}
var options = {
method: 'POST',
json: true,
body: {
"api_key": monitorApiKey,
"custom_uptime_ratios": numberOfDays,
"format": "json",
},
uri: 'https://api.uptimerobot.com/v2/getMonitors',
};
// A monitor API key must start with "m"
if (monitorApiKey.substring(0, "m".length) !== "m") {
badgeData.text[1] = 'must use a monitor key';
sendBadge(format, badgeData);
return;
}
request(options, function(err, res, json) {
if (err !== null || res.statusCode >= 500 || typeof json !== 'object') {
badgeData.text[1] = 'inaccessible';
sendBadge(format, badgeData);
return;
}
try {
if (json.stat === 'fail') {
badgeData.text[1] = 'vendor error';
if (json.error && typeof json.error.message === 'string') {
badgeData.text[1] = json.error.message;
}
badgeData.colorscheme = 'lightgrey';
sendBadge(format, badgeData);
return;
}
var percent = parseFloat(json.monitors[0].custom_uptime_ratio);
badgeData.text[1] = percent + '%';
if (percent <= 10) {
badgeData.colorscheme = 'red';
} else if (percent <= 30) {
badgeData.colorscheme = 'yellow';
} else if (percent <= 50) {
badgeData.colorscheme = 'yellowgreen';
} else if (percent <= 70) {
badgeData.colorscheme = 'green';
} else {
badgeData.colorscheme = 'brightgreen';
}
sendBadge(format, badgeData);
} catch (e) {
badgeData.text[1] = 'invalid';
sendBadge(format, badgeData);
}
});
}));
// Discord integration
camp.route(/^\/discord\/([^/]+)\.(svg|png|gif|jpg|json)$/,
cache((data, match, sendBadge, request) => {

View File

@@ -0,0 +1,97 @@
'use strict'
const Joi = require('joi')
const BaseJsonService = require('../base-json')
const { InvalidParameter, InvalidResponse } = require('../errors')
// https://uptimerobot.com/api
// POST getMonitors
const errorResponse = Joi.object({
stat: Joi.equal('fail').required(),
error: Joi.object({
message: Joi.string(),
}).default({}),
}).required()
const monitor = Joi.object({
status: Joi.equal(0, 1, 2, 8, 9).required(),
})
const monitorWithUptime = monitor.keys({
custom_uptime_ratio: Joi.string()
.regex(/^\d*\.\d{3}$/)
.required(),
})
const singleMonitorResponse = Joi.alternatives(
errorResponse,
Joi.object({
stat: Joi.equal('ok').required(),
monitors: Joi.array()
.length(1)
.items(monitor)
.required(),
}).required()
)
const singleMonitorResponseWithUptime = Joi.alternatives(
errorResponse,
Joi.object({
stat: Joi.equal('ok').required(),
monitors: Joi.array()
.length(1)
.items(monitorWithUptime)
.required(),
}).required()
)
module.exports = class UptimeRobotBase extends BaseJsonService {
static get category() {
return 'monitoring'
}
static ensureIsMonitorApiKey(value) {
// A monitor API key must start with "m".
if (!value.startsWith('m')) {
throw new InvalidParameter({
prettyMessage: 'must use a monitor-specific api key',
})
}
}
async fetch({ monitorApiKey, numberOfDays }) {
this.constructor.ensureIsMonitorApiKey(monitorApiKey)
let opts, schema
if (numberOfDays) {
opts = { custom_uptime_ratios: numberOfDays }
schema = singleMonitorResponseWithUptime
} else {
opts = {}
schema = singleMonitorResponse
}
const { stat, error, monitors } = await this._requestJson({
schema,
url: 'https://api.uptimerobot.com/v2/getMonitors',
options: {
method: 'POST',
headers: {
'cache-control': 'no-cache',
'content-type': 'application/x-www-form-urlencoded',
},
form: {
api_key: monitorApiKey,
format: 'json',
...opts,
},
},
})
if (stat === 'fail') {
const { message } = error
throw new InvalidResponse({ prettyMessage: message || 'service error' })
}
return { monitors }
}
}

View File

@@ -0,0 +1,48 @@
'use strict'
const { colorScale } = require('../../lib/color-formatters')
const UptimeRobotBase = require('./uptimerobot-base')
const ratioColor = colorScale([10, 30, 50, 70])
module.exports = class UptimeRobotRatio extends UptimeRobotBase {
static get defaultBadgeData() {
return {
label: 'uptime',
}
}
static get url() {
return {
base: 'uptimerobot/ratio',
format: '(?:([\\d+])/)?(.*)',
capture: ['numberOfDays', 'monitorApiKey'],
}
}
static get examples() {
return [
{
title: 'Uptime Robot ratio (30 days)',
previewUrl: 'm778918918-3e92c097147760ee39d02d36',
},
{
title: 'Uptime Robot ratio (7 days)',
previewUrl: '7/m778918918-3e92c097147760ee39d02d36',
},
]
}
static async render({ ratio }) {
return {
message: `${ratio}%`,
color: ratioColor(ratio),
}
}
async handle({ numberOfDays = 30, monitorApiKey }) {
const { monitors } = await this.fetch({ monitorApiKey, numberOfDays })
const ratio = Number.parseFloat(monitors[0].custom_uptime_ratio)
return this.constructor.render({ ratio })
}
}

View File

@@ -0,0 +1,51 @@
'use strict'
const UptimeRobotBase = require('./uptimerobot-base')
module.exports = class UptimeRobotStatus extends UptimeRobotBase {
static get defaultBadgeData() {
return {
label: 'status',
}
}
static get url() {
return {
base: 'uptimerobot/status',
format: '(.*)',
capture: ['monitorApiKey'],
}
}
static get examples() {
return [
{
title: 'Uptime Robot status',
previewUrl: 'm778918918-3e92c097147760ee39d02d36',
},
]
}
static async render({ status }) {
switch (status) {
case 0:
return { message: 'paused', color: 'yellow' }
case 1:
return { message: 'not checked yet', color: 'yellowgreen' }
case 2:
return { message: 'up', color: 'brightgreen' }
case 8:
return { message: 'seems down', color: 'orange' }
case 9:
return { message: 'down', color: 'red' }
default:
throw Error('Should not get here due to validation')
}
}
async handle({ monitorApiKey }) {
const { monitors } = await this.fetch({ monitorApiKey })
const { status } = monitors[0]
return this.constructor.render({ status })
}
}

View File

@@ -3,8 +3,12 @@
const Joi = require('joi')
const ServiceTester = require('../service-tester')
const isUptimeStatus = Joi.string().regex(
/^(paused|not checked yet|up|seems down|down)$/
const isUptimeStatus = Joi.string().valid(
'paused',
'not checked yet',
'up',
'seems down',
'down'
)
const { isPercentage } = require('../test-validators')
const { invalidJSON } = require('../response-fixtures')
@@ -27,7 +31,7 @@ t.create('Uptime Robot: Status (invalid, correct format)')
t.create('Uptime Robot: Status (invalid, incorrect format)')
.get('/status/not-a-service.json')
.expectJSON({ name: 'status', value: 'must use a monitor key' })
.expectJSON({ name: 'status', value: 'must use a monitor-specific api key' })
t.create('Uptime Robot: Status (unspecified error)')
.get('/status/m778918918-3e92c097147760ee39d02d36.json')
@@ -36,7 +40,7 @@ t.create('Uptime Robot: Status (unspecified error)')
.post('/v2/getMonitors')
.reply(200, '{"stat": "fail"}')
)
.expectJSON({ name: 'status', value: 'vendor error' })
.expectJSON({ name: 'status', value: 'service error' })
t.create('Uptime Robot: Status (connection error)')
.get('/status/m778918918-3e92c097147760ee39d02d36.json')
@@ -59,7 +63,7 @@ t.create('Uptime Robot: Status (unexpected response, valid json)')
.post('/v2/getMonitors')
.reply(200, '[]')
)
.expectJSON({ name: 'status', value: 'invalid' })
.expectJSON({ name: 'status', value: 'invalid json response' })
t.create('Uptime Robot: Status (unexpected response, invalid json)')
.get('/status/m778918918-3e92c097147760ee39d02d36.json')
@@ -68,7 +72,7 @@ t.create('Uptime Robot: Status (unexpected response, invalid json)')
.post('/v2/getMonitors')
.reply(invalidJSON)
)
.expectJSON({ name: 'status', value: 'inaccessible' })
.expectJSON({ name: 'status', value: 'unparseable json response' })
t.create('Uptime Robot: Percentage (valid)')
.get('/ratio/m778918918-3e92c097147760ee39d02d36.json')
@@ -94,7 +98,7 @@ t.create('Uptime Robot: Percentage (invalid, correct format)')
t.create('Uptime Robot: Percentage (invalid, incorrect format)')
.get('/ratio/not-a-service.json')
.expectJSON({ name: 'uptime', value: 'must use a monitor key' })
.expectJSON({ name: 'uptime', value: 'must use a monitor-specific api key' })
t.create('Uptime Robot: Percentage (unspecified error)')
.get('/ratio/m778918918-3e92c097147760ee39d02d36.json')
@@ -103,7 +107,7 @@ t.create('Uptime Robot: Percentage (unspecified error)')
.post('/v2/getMonitors')
.reply(200, '{"stat": "fail"}')
)
.expectJSON({ name: 'uptime', value: 'vendor error' })
.expectJSON({ name: 'uptime', value: 'service error' })
t.create('Uptime Robot: Percentage (connection error)')
.get('/ratio/m778918918-3e92c097147760ee39d02d36.json')
@@ -126,7 +130,7 @@ t.create('Uptime Robot: Percentage (unexpected response, valid json)')
.post('/v2/getMonitors')
.reply(200, '[]')
)
.expectJSON({ name: 'uptime', value: 'invalid' })
.expectJSON({ name: 'uptime', value: 'invalid json response' })
t.create('Uptime Robot: Percentage (unexpected response, invalid json)')
.get('/ratio/m778918918-3e92c097147760ee39d02d36.json')
@@ -135,4 +139,4 @@ t.create('Uptime Robot: Percentage (unexpected response, invalid json)')
.post('/v2/getMonitors')
.reply(invalidJSON)
)
.expectJSON({ name: 'uptime', value: 'inaccessible' })
.expectJSON({ name: 'uptime', value: 'unparseable json response' })