Skip to content

Commit

Permalink
Bug 1295025: merge uptimerobot status into tools/status, make it bett…
Browse files Browse the repository at this point in the history
…er (#129)

* reorganize layout a little

* fetch results from uptimerobot

* change display to use fontawesome rather than images

* do a better job of running timers

* remove hard-coded 'up' status

* fix review comments

* put uptimerobot monitor keys in env vars

* nits
  • Loading branch information
djmitche authored and eliperelman committed Aug 17, 2016
1 parent fef1aaa commit 518970c
Show file tree
Hide file tree
Showing 5 changed files with 102 additions and 49 deletions.
9 changes: 8 additions & 1 deletion Gruntfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,14 @@ module.exports = function(grunt) {
status: {
options: {
env: {
CORS_PROXY: process.env.CORS_PROXY || 'https://cors-proxy.taskcluster.net/request'
CORS_PROXY: process.env.CORS_PROXY || 'https://cors-proxy.taskcluster.net/request',
UPTIMEROBOT_API_KEY_QUEUE: process.env.UPTIMEROBOT_API_KEY_QUEUE || 'm776323830-a170e7abc854f94cc2f4c078',
UPTIMEROBOT_API_KEY_AUTH: process.env.UPTIMEROBOT_API_KEY_AUTH || 'm776208480-28abc3b309cb0e526a5ebce8',
UPTIMEROBOT_API_KEY_AWS_PROVISIONER: process.env.UPTIMEROBOT_API_KEY_AWS_PROVISIONER || 'm776120201-37b5da206dfd8de4b00ae25b',
UPTIMEROBOT_API_KEY_EVENTS: process.env.UPTIMEROBOT_API_KEY_EVENTS || 'm776321033-e82bb32adfa08a0bba0002c6',
UPTIMEROBOT_API_KEY_INDEX: process.env.UPTIMEROBOT_API_KEY_INDEX || 'm776362434-85a6996de0f9c73cf21bbf89',
UPTIMEROBOT_API_KEY_SCHEDULER: process.env.UPTIMEROBOT_API_KEY_SCHEDULER || 'm776120202-44923d8660c2a1bd1a5de440',
UPTIMEROBOT_API_KEY_SECRETS: process.env.UPTIMEROBOT_API_KEY_SECRETS || 'm777577313-6d58b81186c4064cf7a8d1e1'
}
},
files: {
Expand Down
7 changes: 7 additions & 0 deletions status/config.js.in
Original file line number Diff line number Diff line change
@@ -1 +1,8 @@
export const CORS_PROXY = process.env.CORS_PROXY;
export const UPTIMEROBOT_API_KEY_QUEUE = process.env.UPTIMEROBOT_API_KEY_QUEUE;
export const UPTIMEROBOT_API_KEY_AUTH = process.env.UPTIMEROBOT_API_KEY_AUTH;
export const UPTIMEROBOT_API_KEY_AWS_PROVISIONER = process.env.UPTIMEROBOT_API_KEY_AWS_PROVISIONER;
export const UPTIMEROBOT_API_KEY_EVENTS = process.env.UPTIMEROBOT_API_KEY_EVENTS;
export const UPTIMEROBOT_API_KEY_INDEX = process.env.UPTIMEROBOT_API_KEY_INDEX;
export const UPTIMEROBOT_API_KEY_SCHEDULER = process.env.UPTIMEROBOT_API_KEY_SCHEDULER;
export const UPTIMEROBOT_API_KEY_SECRETS = process.env.UPTIMEROBOT_API_KEY_SECRETS;
Binary file removed status/green-check.png
Binary file not shown.
Binary file removed status/red-check.png
Binary file not shown.
135 changes: 87 additions & 48 deletions status/status.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const $ = require('jquery');
const WWW_Authenticate = require('www-authenticate').parsers.WWW_Authenticate;
const ReactTooltip = require("react-tooltip");
const config = require("../build/status/config")
const format = require('../lib/format');

// Make a request to the cors proxy service
function makeRequest(options, allowHeaders = []) {
Expand All @@ -23,10 +24,17 @@ function makeRequest(options, allowHeaders = []) {
});
}

function pollTaskclusterService(url, cb) {
$.getJSON(url)
.done(data => cb(data.alive))
.fail(err => cb(false));
function pollTaskclusterService(key, cb) {
return $.getJSON(`http://api.uptimerobot.com/getMonitors?apiKey=${key}&format=json&noJsonCallback=1`)
.done(res => {
const [ monitor ] = res.monitors.monitor;

cb(monitor.status === '2' ? 'up' : 'down'); // 2 is 'up'
}).fail(err => {
console.error('Error fetching data from uptimerobot');
console.error(err);
cb('err');
});
}

function dummyPoll(cb) {
Expand All @@ -36,27 +44,45 @@ function dummyPoll(cb) {
let taskclusterServices = [
{
name: "Queue",
poll: pollTaskclusterService.bind(null, "https://queue.taskcluster.net/v1/ping"),
poll: pollTaskclusterService.bind(null, config.UPTIMEROBOT_API_KEY_QUEUE),
link: "https://queue.taskcluster.net/v1/ping",
description: "queue.taskcluster.net"
},
{
name: "Auth",
poll: pollTaskclusterService.bind(null, config.UPTIMEROBOT_API_KEY_AUTH),
link: "https://auth.taskcluster.net/v1/ping",
description: "auth.taskcluster.net"
},
{
name: "AWS Provisioner",
poll: pollTaskclusterService.bind(null, "https://aws-provisioner.taskcluster.net/v1/ping"),
link: "https://aws-provider.taskcluster.net/v1/ping",
poll: pollTaskclusterService.bind(null, config.UPTIMEROBOT_API_KEY_AWS_PROVISIONER),
link: "https://aws-provisioner.taskcluster.net/v1/ping",
description: "aws-provisioner.taskcluster.net"
},
{
name: "Events",
poll: pollTaskclusterService.bind(null, config.UPTIMEROBOT_API_KEY_EVENTS),
link: "https://events.taskcluster.net/v1/ping",
description: "events.taskcluster.net"
},
{
name: "Index",
poll: pollTaskclusterService.bind(null, "https://index.taskcluster.net/v1/ping"),
poll: pollTaskclusterService.bind(null, config.UPTIMEROBOT_API_KEY_INDEX),
link: "https://index.taskcluster.net/v1/ping",
description: "index.taskcluster.net"
},
{
name: "Scheduler",
poll: pollTaskclusterService.bind(null, "https://scheduler.taskcluster.net/v1/ping"),
poll: pollTaskclusterService.bind(null, config.UPTIMEROBOT_API_KEY_SCHEDULER),
link: "https://scheduler.taskcluster.net/v1/ping",
description: "https://scheduler.taskcluster.net"
},
{
name: "Secrets",
poll: pollTaskclusterService.bind(null, config.UPTIMEROBOT_API_KEY_SECRETS),
link: "https://secrets.taskcluster.net/v1/ping",
description: "https://secrets.taskcluster.net"
}
];

Expand All @@ -73,15 +99,15 @@ let otherServices = [

let items = data.getElementsByTagName('item');
if (!items.length) {
cb(true);
cb('up');
return;
}

let title = items[0].getElementsByTagName('title');
cb(title[0].innerHTML.startsWith('Service is operating normally'));
cb(title[0].innerHTML.startsWith('Service is operating normally') ? 'up' : 'down');
} catch (err) {
console.log(err.stack || err);
cb(false);
cb('down');
}
}
},
Expand All @@ -105,7 +131,7 @@ let otherServices = [
await Promise.resolve(req);
} catch (err) {
if (err.status != 401) {
cb(false);
cb('down');
return;
}

Expand All @@ -125,12 +151,12 @@ let otherServices = [
}));
} catch (err) {
console.log(err.stack || err);
cb(false);
cb('err');
return;
}
}

cb(true);
cb('up');
}
},
{
Expand All @@ -141,23 +167,36 @@ let otherServices = [
Promise.resolve(makeRequest({
url: 'https://status.heroku.com/feed'
}))
.then(data => cb(!data.length || data[0].title.startsWith('Resolved')))
.then(data => cb((!data.length || data[0].title.startsWith('Resolved')) ? 'up' : 'down'))
.catch(err => {
console.log(err || err.stack);
cb(false);
cb('err');
});
}
}
];

const STATUS_DISPLAY = {
loading: { icon: 'spinner', spin: true, color: 'gray' },
up: { icon: 'thumbs-up', color: 'green' },
degraded: { icon: 'exclamation', color: 'orange' },
down: { icon: 'thumbs-down', color: 'red' },
err: { icon: 'frown-o', color: 'red' },
};

export let StatusChecker = React.createClass({
propTypes: {
up: React.PropTypes.bool.isRequired
status: React.PropTypes.string.isRequired
},
render: function() {
let image = this.props.up ? "green-check.png" : "red-check.png";
const { icon, spin, color } = STATUS_DISPLAY[this.props.status] || STATUS_DISPLAY['err'];
return (
<img className="img-check img-fluid center-block" src={image}/>
<format.Icon
name={icon}
size="lg"
spin={spin}
className="pull-left"
style={{ color }} />
);
}
});
Expand All @@ -169,22 +208,36 @@ export let Service = React.createClass({
link: React.PropTypes.string.isRequired,
poll: React.PropTypes.func.isRequired
},
getInitialState() {
let intervalId = setInterval(this.props.poll.bind(this, serviceUp => {
this.setState({up: serviceUp});
}), 5000);

componentWillMount() {
this.poll();
},

componentWillUnmount() {
if (this.timer) {
clearTimeout(this.timer);
}
},

poll() {
this.props.poll(status => {
this.setState({ status });
this.timer = setTimeout(this.poll, 5000);
});
},

getInitialState() {
return {
up: true,
intervalId: intervalId
status: 'loading',
};
},

render: function() {
return (
<div className="form-horizontal service-status-container">
<a target="_blank" href={this.props.link}>
<div data-tip data-for={this.props.name}>
<label className="service-status"><StatusChecker up={this.state.up}/></label>
<label className="service-status"><StatusChecker status={this.state.status} /></label>
<label className="service-status">{this.props.name}</label>
</div>
</a>
Expand All @@ -204,7 +257,7 @@ export let ServiceGroup = React.createClass({
},
render() {
return (
<div>
<bs.Col className="service-group" md={6} sm={12}>
<h2 data-tip data-for={this.props.name}>{this.props.name}</h2>
<bs.ButtonToolbar>
{this.props.services.map(service => {
Expand All @@ -220,21 +273,6 @@ export let ServiceGroup = React.createClass({
<ReactTooltip id={this.props.name} place="top" type="info" effect="float">
<span>{this.props.description}</span>
</ReactTooltip>
</div>
);
}
});

export let TaskclusterStatus = React.createClass({
render: function() {
return (
<bs.Col className="taskcluster-status" lg={12} md={12} sm={12} xs={12}>
<div className="center-block">
<h1>Taskcluster Status</h1>
</div>
<div className="circle center-block">
<StatusChecker up={true}/>
</div>
</bs.Col>
);
}
Expand All @@ -243,11 +281,12 @@ export let TaskclusterStatus = React.createClass({
export let TaskclusterDashboard = React.createClass({
render: function() {
return (
<bs.Row>
<TaskclusterStatus/>
<ServiceGroup name="Taskcluster" services={taskclusterServices} description="Taskcluster services"/>
<ServiceGroup name="Services" services={otherServices} description="External services Taskcluster depends on"/>
</bs.Row>
<div>
<bs.Row>
<ServiceGroup name="Taskcluster Services" services={taskclusterServices} description="Taskcluster services" />
<ServiceGroup name="External Services" services={otherServices} description="External services Taskcluster depends on" />
</bs.Row>
</div>
);
}
});

0 comments on commit 518970c

Please sign in to comment.