initial commit

This commit is contained in:
Athena
2018-06-30 14:24:33 +00:00
committed by itismadness
commit 622dde5bca
22 changed files with 4801 additions and 0 deletions

205
.eslintrc Normal file
View File

@@ -0,0 +1,205 @@
{
"parserOptions": {
"ecmaVersion": 9,
"ecmaFeatures": {
"jsx": true
},
"sourceType": "module"
},
"env": {
"es6": true,
"node": true
},
"plugins": [
"import",
"node",
"promise",
"standard"
],
"globals": {
"document": false,
"navigator": false,
"window": false
},
"rules": {
"accessor-pairs": "error",
"arrow-spacing": ["error", { "before": true, "after": true }],
"block-spacing": ["error", "always"],
"brace-style": ["error", "stroustrup", { "allowSingleLine": false }],
"camelcase": ["error", { "properties": "never" }],
"comma-dangle": ["error", {
"arrays": "never",
"objects": "never",
"imports": "never",
"exports": "never",
"functions": "never"
}],
"comma-spacing": ["error", { "before": false, "after": true }],
"comma-style": ["error", "last"],
"constructor-super": "error",
"curly": ["error", "multi-line"],
"dot-location": ["error", "property"],
"eol-last": "error",
"eqeqeq": ["error", "always", { "null": "ignore" }],
"func-call-spacing": ["error", "never"],
"generator-star-spacing": ["error", { "before": true, "after": true }],
"handle-callback-err": ["error", "^(err|error)$" ],
"indent": ["error", 2, {
"SwitchCase": 1,
"VariableDeclarator": 1,
"outerIIFEBody": 1,
"MemberExpression": 1,
"FunctionDeclaration": { "parameters": 1, "body": 1 },
"FunctionExpression": { "parameters": 1, "body": 1 },
"CallExpression": { "arguments": 1 },
"ArrayExpression": 1,
"ObjectExpression": 1,
"ImportDeclaration": 1,
"flatTernaryExpressions": false,
"ignoreComments": false
}],
"key-spacing": ["error", { "beforeColon": false, "afterColon": true }],
"keyword-spacing": ["error", { "before": true, "after": true }],
"new-cap": ["error", { "newIsCap": true, "capIsNew": false }],
"new-parens": "error",
"no-array-constructor": "error",
"no-caller": "error",
"no-class-assign": "error",
"no-compare-neg-zero": "error",
"no-cond-assign": "error",
"no-const-assign": "error",
"no-constant-condition": ["error", { "checkLoops": false }],
"no-control-regex": "error",
"no-debugger": "error",
"no-delete-var": "error",
"no-dupe-args": "error",
"no-dupe-class-members": "error",
"no-dupe-keys": "error",
"no-duplicate-case": "error",
"no-empty-character-class": "error",
"no-empty-pattern": "error",
"no-eval": "error",
"no-ex-assign": "error",
"no-extend-native": "error",
"no-extra-bind": "error",
"no-extra-boolean-cast": "error",
"no-extra-parens": ["error", "functions"],
"no-fallthrough": "error",
"no-floating-decimal": "error",
"no-func-assign": "error",
"no-global-assign": "error",
"no-implied-eval": "error",
"no-inner-declarations": ["error", "functions"],
"no-invalid-regexp": "error",
"no-irregular-whitespace": "error",
"no-iterator": "error",
"no-label-var": "error",
"no-labels": ["error", { "allowLoop": false, "allowSwitch": false }],
"no-lone-blocks": "error",
"no-mixed-operators": ["error", {
"groups": [
["==", "!=", "===", "!==", ">", ">=", "<", "<="],
["&&", "||"],
["in", "instanceof"]
],
"allowSamePrecedence": true
}],
"no-mixed-spaces-and-tabs": "error",
"no-multi-spaces": "error",
"no-multi-str": "error",
"no-multiple-empty-lines": ["error", { "max": 1, "maxEOF": 0 }],
"no-negated-in-lhs": "error",
"no-new": "error",
"no-new-func": "error",
"no-new-object": "error",
"no-new-require": "error",
"no-new-symbol": "error",
"no-new-wrappers": "error",
"no-obj-calls": "error",
"no-octal": "error",
"no-octal-escape": "error",
"no-path-concat": "error",
"no-proto": "error",
"no-redeclare": "error",
"no-regex-spaces": "error",
"no-return-assign": ["error", "except-parens"],
"no-return-await": "error",
"no-self-assign": "error",
"no-self-compare": "error",
"no-sequences": "error",
"no-shadow-restricted-names": "error",
"no-sparse-arrays": "error",
"no-tabs": "error",
"no-template-curly-in-string": "error",
"no-this-before-super": "error",
"no-throw-literal": "error",
"no-trailing-spaces": "error",
"no-undef": "error",
"no-undef-init": "error",
"no-unexpected-multiline": "error",
"no-unmodified-loop-condition": "error",
"no-unneeded-ternary": ["error", { "defaultAssignment": false }],
"no-unreachable": "error",
"no-unsafe-finally": "error",
"no-unsafe-negation": "error",
"no-unused-expressions": ["error", { "allowShortCircuit": true, "allowTernary": true, "allowTaggedTemplates": true }],
"no-unused-vars": ["error", { "vars": "all", "args": "none", "ignoreRestSiblings": true }],
"no-use-before-define": ["error", { "functions": false, "classes": false, "variables": false }],
"no-useless-call": "error",
"no-useless-computed-key": "error",
"no-useless-constructor": "error",
"no-useless-escape": "error",
"no-useless-rename": "error",
"no-useless-return": "error",
"no-whitespace-before-property": "error",
"no-with": "error",
"no-var": "error",
"object-property-newline": ["error", { "allowMultiplePropertiesPerLine": true }],
"one-var": ["error", { "initialized": "never" }],
"operator-linebreak": ["error", "after", { "overrides": { "?": "before", ":": "before" } }],
"padded-blocks": ["error", { "blocks": "never", "switches": "never", "classes": "never" }],
"prefer-promise-reject-errors": "error",
"quotes": ["error", "single", { "avoidEscape": true, "allowTemplateLiterals": true }],
"rest-spread-spacing": ["error", "never"],
"semi": ["error", "always"],
"semi-spacing": ["error", { "before": false, "after": true }],
"space-before-blocks": ["error", "always"],
"space-before-function-paren": ["error", "always"],
"space-in-parens": ["error", "never"],
"space-infix-ops": "error",
"space-unary-ops": ["error", { "words": true, "nonwords": false }],
"spaced-comment": ["error", "always", {
"line": { "markers": ["*package", "!", "/", ",", "="] },
"block": { "balanced": true, "markers": ["*package", "!", ",", ":", "::", "flow-include"], "exceptions": ["*"] }
}],
"symbol-description": "error",
"template-curly-spacing": ["error", "never"],
"template-tag-spacing": ["error", "never"],
"unicode-bom": ["error", "never"],
"use-isnan": "error",
"valid-typeof": ["error", { "requireStringLiterals": true }],
"wrap-iife": ["error", "any", { "functionPrototypeMethods": true }],
"yield-star-spacing": ["error", "both"],
"yoda": ["error", "never"],
"import/export": "error",
"import/first": "error",
"import/no-duplicates": "error",
"import/no-webpack-loader-syntax": "error",
"node/no-deprecated-api": "error",
"node/process-exit-as-throw": "error",
"promise/param-names": "error",
"standard/array-bracket-even-spacing": ["error", "either"],
"standard/computed-property-even-spacing": ["error", "even"],
"standard/no-callback-literal": "error",
"standard/object-curly-even-spacing": ["error", "either"]
}
}

6
.gitignore vendored Normal file
View File

@@ -0,0 +1,6 @@
.DS_Store
/.idea/
/node_modules/
*.sublime-workspace
*.sublime-project
config.json

6
Dockerfile Normal file
View File

@@ -0,0 +1,6 @@
FROM node:8-alpine
ADD . /srv
WORKDIR /srv
EXPOSE 3000
RUN npm install && npm install nodemon
CMD ["node_modules/.bin/nodemon", "apollostatus.js"]

24
LICENSE.md Normal file
View File

@@ -0,0 +1,24 @@
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <http://unlicense.org/>

87
README.md Normal file
View File

@@ -0,0 +1,87 @@
ApolloStatus
============
Check the health of a site, tracker, and IRC.
ApolloStatus is a simple status page for torrent sites.
It's powered by [Express](http://expressjs.com/) and [Redis](https://redis.io).
It's based on [WhatStatus](https://github.com/dewey/WhatStatus) as well as inspiration
of certain features from [TrackerStatus](https://trackerstatus.info).
# Configure ApolloStatus
To make ApolloStatus more easily extendable, you can need to copy `config.json.template`
to `config.json` and edit it to match the paths to your site, IRC, and tracker.
# Running ApolloStatus
There are two methods for running and using ApolloStatus. The two covered methods
are through Docker or Locally. The first step is to clone this repository:
```
git clone https://github.com/ApolloRIP/ApolloStatus
```
When running ApolloStatus under either mode, it recognizes the following enviroment
variables:
Variable | Description | Default
-----------|-------------------------------------------------------------------|--------
LOG_LEVEL | Level of messages to log. Can set to debug, info, warn, or error. | warn
REDIS_HOST | Host of Redis that ApolloStatus should attempt to connect to. | 127.0.0.1
REDIS_PORT | Port of Redis that ApolloStatus should attempt to connect to. | 6379
PORT | Port that ApolloStatus should use. | 3000
## Docker
The easiest way is to use [Docker Compose](https://docs.docker.com/compose/) and utilize
the included `docker-compose.yml` file that will handle spinning up two containers (one
for apollostatus and one for Redis) and setup the network bridge between the two applications.
To use it, you'll just need to do `docker-compose up` which will handle building the local
`Dockerfile` (which is for ApolloStatus), downloading the necessary Redis container, setting
up a persistent volume layer for Redis, export port 3000 of the ApolloStatus container, and
ensures that the containers restart automatically if they go stop for whatever reason
(unless explicitly stopped via `docker-compose down`). You can do all of this yourself as
well if desired, utilizing just the builtin `Dockerfile`, doing
`docker build . -t apollo/apollostatus:latest` and then running it via `docker run`.
### Prerequisites
The first (and easiest) is
to use the included [Docker Compose](https://docs.docker.com/compose/) setup. This requires
that you have Docker and Docker Compose installed. Usage is then as simple as:
```docker-compose up```
This will spin up two containers, one for the redis server and one for the actual service.
The site container exposes port 3000 for TCP by default so you can access it by going to
`http://localhost:3000`.
## Locally
- Install [Node.JS](http://nodejs.org/) and [NPM](https://www.npmjs.com/)
- Install [Redis](http://redis.io/)
- Navigate to the directory and run (as non-root user):
```
npm install
```
which will install all the dependencies listed in `package.json`
To then run the site, you just need to do `node apollostatus.js`. To setup the site to
run permanently without you needing to start/stop it, it's recommended that you use
[pm2](http://pm2.keymetrics.io/) (an alternative is [forever](https://www.npmjs.com/package/forever)).
The app is now running on port 3000. To serve it on your regular port 443 or 80 you'll have to setup nginx like this:
# Nginx Configuration
To setup nginx to make your application accessible outside of localhost, you will want
to setup your location block to be:
```
{
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_set_header X-NginX-Proxy true;
proxy_pass http://127.0.0.1:3000/;
proxy_redirect off;
}
```
This will setup nginx that whenever a user visits the location you specify (whether it's
`/` or `/status` or whatever) to proxy that connection to our NodeJS application which is
listening to port 3000. This should be done when running the application locally
or via Docker.

285
apollostatus.js Normal file
View File

@@ -0,0 +1,285 @@
const express = require('express'),
bodyParser = require('body-parser'),
errorHandler = require('errorhandler'),
favicon = require('serve-favicon'),
morgan = require('morgan'),
net = require('net'),
path = require('path'),
redis = require('redis'),
CronJob = require('cron').CronJob,
request = require('request'),
winston = require('winston');
const logger = winston.createLogger({
level: process.env.LOG_LEVEL || 'warn',
format: winston.format.json(),
transports: [new winston.transports.Console({format: winston.format.simple()})]
});
const config = require('./config.json');
const app = express();
const db = redis.createClient(process.env.REDIS_PORT || 6379, process.env.REDIS_HOST || '127.0.0.1');
const page_title = `Status :: ${config['site_name']}`;
// Catch connection errors if redis-server isn't running
db.on('error', function (err) {
logger.error(err.toString());
logger.error(' Make sure redis-server is started and listening for connections.');
});
app.set('port', process.env.PORT || 3000);
app.set('views', __dirname + '/views');
app.set('view engine', 'pug');
app.use(favicon(path.join(__dirname, 'public', 'images', 'favicon.ico')));
app.use(bodyParser.json());
app.use(bodyParser.text());
app.use(express.static(path.join(__dirname, 'public')));
if (app.get('env') === 'development') {
app.use(errorHandler());
app.locals.pretty = true;
app.use(morgan('dev'));
}
//////////////////////////////////
// HTML ENDPOINTS
//////////////////////////////////
// Render the index page
app.get('/', (req, res) => {
res.render('index', {
title: page_title,
logo_url: 'images/logos/logo.png'
});
});
// Render the Stats page
app.get('/stats', (req, res) => {
res.render('stats', {
title: page_title
});
});
app.get('/api', (req, res) => {
res.render('api', {
title: page_title
});
});
// Render the About page
app.get('/about', (req, res) => {
res.render('about', {
title: page_title
});
});
// Render the FAQ page
app.get('/faq', (req, res) => {
res.render('faq', {
title: page_title
});
});
//////////////////////////////////
// API ENDPOINTS
//////////////////////////////////
// JSON Response for current component status
app.get('/api/status', (req, res) => {
db.mget(['status:site', 'status:irc', 'status:tracker', 'status:httptracker'], (err, results) => {
res.json({
site: parseInt(results[0]),
irc: parseInt(results[1]),
tracker: parseInt(results[2]),
httptracker: parseInt(results[3])
});
});
});
app.get('/api/latency', (req, res) => {
db.mget(['latency:site', 'latency: tracker', 'latency: httptracker'], (err, results) => {
res.json({
site: parseInt(results[0]),
tracker: parseInt(results[1]),
httptracker: parseInt(results[2])
});
});
});
// JSON Response for uptime values
app.get('/api/uptime', (req, res) => {
db.mget(['uptime:site', 'uptime:irc', 'uptime:tracker', 'uptime:httptracker'], (err, results) => {
res.json({
site: parseInt(results[0]),
irc: parseInt(results[1]),
tracker: parseInt(results[2]),
httptracker: parseInt(results[3])
});
});
});
// JSON Response for uptime records
app.get('/api/records', (req, res) => {
db.mget(['uptimerecord:site', 'uptimerecord:irc', 'uptimerecord:tracker', 'uptimerecord:httptracker'], (err, results) => {
res.json({
site: parseInt(results[0]),
irc: parseInt(results[1]),
tracker: parseInt(results[2]),
httptracker: parseInt(results[3])
});
});
});
app.get('/api/all', (req, res) => {
db.mget(['status:site', 'status:irc', 'status:tracker', 'status:httptracker', 'latency:site',
'latency: irc', 'latency: tracker', 'latency: httptracker', 'uptime:site', 'uptime:irc',
'uptime:tracker', 'uptime:httptracker', 'uptimerecord:site', 'uptimerecord:irc',
'uptimerecord:tracker', 'uptimerecord:httptracker'], (err, results) => {
let resp = {};
let keys = ['site', 'tracker', 'httptracker', 'irc'];
for (let i = 0; i < keys.length; i++) {
resp[keys[i]] = {
status: parseInt(results[i]),
latency: parseInt(results[i+4]),
uptime: parseInt(results[i+8]),
uptimerecord: parseInt(results[i+12])
};
}
res.json(resp);
});
});
//////////////////////////////////
// CRON JOBS
//////////////////////////////////
// Check all components every minute
let counter = {
site: [],
tracker: [],
httptracker: [],
irc: []
};
// Initialize Redis Keys to prevent "null" values
function initializeRedis(component) {
db.exists(component, (err, reply) => {
if (reply !== 1) {
db.set(component, '0');
}
});
}
for (let key of ['site', 'tracker', 'httptracker', 'irc']) {
initializeRedis(`status:${key}`);
initializeRedis(`latency:${key}`);
initializeRedis(`uptimerecord:${key}`);
initializeRedis(`uptime:${key}`);
}
function checkStatus(key, uri) {
request({uri: uri, method: 'GET', time: true}, (err, resp) => {
counter[key].pop();
if (!err && response.statusCode === 200) {
db.set(`latency:${key}`, Math.trunc(response.timings.connect).toString());
db.set(`status:${key}`, '1');
counter[key].unshift(0);
}
else {
counter[key].unshift(1);
const upper_key = key.replace(/^\w/, c => c.toUpperCase());
logger.info(`[Check-${upper_key}] ${upper_key} down`);
let sum = counter[key].reduce((a, b) => a + b, 0);
// if in the last three minutes, we've had at least one up,
// then we're unstable, else we mark it as down
if (sum < 3) {
db.set(`status:${key}`, '2');
logger.info(`[Check-${upper_key}] ${upper_key} unstable`);
}
else {
db.set(`status:${key}`, '0');
resetUptime(key);
logger.info(`[Check-${upper_key}] ${upper_key} down`);
}
}
updateRecord(key);
});
}
function updateRecord(key) {
const upper_key = key.replace(/^\w/, c => c.toUpperCase());
db.get(`status:${key}`, (err, status) => {
if (status !== '0') {
db.incr(`uptime:${key}`);
}
db.mget([`uptime:${key}`, `uptimerecord:${key}`], (err, results) => {
if (parseInt(results[0]) > parseInt(results[1])) {
logger.info(`[Stats-${upper_key}] ${upper_key} Records updated [${results[1]} to ${results[0]}]`);
db.set(`uptimerecord:${key}`, results[0]);
}
});
});
}
// If there's an outtage reset uptime record counter.
function resetUptime (component) {
db.set('uptime:' + component, '0');
}
const uris = {
site: `https://${config.site}`,
tracker: `https://${config.tracker.https}`,
httptracker: `http://${config.tracker.http}`
};
// Check Site Components (Cronjob running every minute)
new CronJob('*/1 * * * *', () => {
logger.info('Running minute cron job');
for (let key in uris) {
checkStatus(key, uris[key]);
}
// Get IRC Status
const time = process.hrtime();
let client = net.connect(config.irc.port, config.irc.fqdn, () => {
const diff = process.hrtime(time);
db.set('status:irc', '1');
db.set('latency:irc', Math.trunc(diff[0] * 1000000 + diff[1] / 1000).toString());
counter['irc'].pop();
counter['irc'].unshift(0);
updateRecord('irc');
});
// Socket connection closed
client.on('end', null);
// Error on connecting to target host
client.on('error', () => {
counter['irc'].pop();
counter['irc'].unshift(1);
let sum = counter['irc'].reduce((a, b) => a + b, 0);
logger.info(`[Check-IRC] Status counter: ${sum}`);
if (sum < 3) {
db.set(`status:irc`, '2');
logger.info(`[Check-IRC] IRC unstable`);
}
else {
db.set(`status:irc`, '0');
resetUptime('irc');
logger.info(`[Check-IRC] IRC down`);
}
updateRecord('irc');
client.end();
});
client.on('timeout', () => {
logger.info('[Check-IRC] Timeout');
client.end();
});
}, null, true);
app.listen(app.get('port'), () => {
logger.info('ApolloStatus server listening on port: ' + app.get('port'));
});

12
config.json.template Normal file
View File

@@ -0,0 +1,12 @@
{
"site_name": "EXAMPLE",
"site": "example.com",
"tracker": {
"https": "tracker.example.com",
"http": "example.com:2095"
},
"irc": {
"fqdn": "irc.example.com",
"port": 6667,
}
}

24
docker-compose.yml Normal file
View File

@@ -0,0 +1,24 @@
version: '3'
services:
status:
build: .
ports:
- "3000:3000"
depends_on:
- redis
environment:
- REDIS_PORT=6379
- REDIS_HOST=redis
- LOG_LEVEL=debug
volumes:
- .:/srv
restart: always
redis:
image: redis:4.0-alpine
volumes:
- redis-data:/data
restart: always
volumes:
redis-data:

3690
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

24
package.json Normal file
View File

@@ -0,0 +1,24 @@
{
"name": "apollostatus",
"description": "Status site to monitor the up status of various components of a torrent site",
"author": "ApolloRIP",
"version": "1.0.0",
"scripts": {
"start": "node app"
},
"repository": "github:apollo/status",
"license": "Unlicense",
"dependencies": {
"body-parser": "^1.18.3",
"cron": "^1.3",
"errorhandler": "^1.5.0",
"express": "^4.16",
"morgan": "^1.9",
"nodemon": "^1.17.5",
"pug": "^2.0",
"redis": "^2.8",
"request": "^2.87",
"serve-favicon": "^2.5",
"winston": "^3.0.0"
}
}

BIN
public/images/down.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
public/images/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

BIN
public/images/up.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
public/images/updating.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@@ -0,0 +1,169 @@
#content {
margin: 0 auto;
width: 600px;
}
.service-table {
margin-top: 50px;
width: 100%;
}
.stats-list {
list-style-type: none;
padding-left: 0;
}
ul span {
float: right;
}
.up {
color: #089a00;
font-weight: bold;
}
.down {
font-weight: bold;
color: #ff1600;
}
.maintenance {
font-weight: bold;
color: #ff1600;
}
footer {
text-align: center;
font-size: 10px;
margin-top: 50px;
width: 600px;
margin-right: auto;
margin-left: auto;
margin-bottom: 25px;
}
footer ul li {
list-style:none;
display: inline;
}
footer ul {
padding: 0;
}
footer ul li:after {
content:"·";
color:#666;
}
footer ul li:last-child:after {
content:"";
}
footer ul li a {
margin:0 8px;
color:blue;
}
body {
background: #212328;
color: #c6c9c9;
text-shadow: #111 0px 1px 0px;
font-family: "Lucida Grande", Verdana, Arial, sans-serif;
}
a:link, a:visited {
color: #999;
text-decoration: none;
}
a:hover, a:visited:hover {
color: #aaa;
}
.disclaimer {
margin-top: 20px;
text-align: center;
font-size: 10px;
}
th {
text-align: left;
}
.stats-table td {
width: 500px;
}
h3 {
padding-top: 0px;
border-bottom-style: solid;
border-bottom-width: 1px;
padding-bottom: 3px;
margin-bottom: 10px;
margin-top: 20px;
}
.follow-us {
position: absolute;
top: 0;
left: 0;
border: 0;
}
.twitter{
width: 550px;
margin: 0 auto
}
.status-dashboard {
width: 550px;
margin: 0 auto;
}
.logo {
height: 140px;
margin: 50px auto 0 auto;
width: 550px;
text-align: center;
}
.logo img {
height: 100%;
}
div #chart-tracker-uptime {
text-shadow: none;
}
p.api-description {
font-size: 12px;
padding-left: 15px;
}
.about-logos {
list-style: none;
padding: 0;
}
.about-logos li {
display: inline;
}
.about-logos li img {
height: 50px;
margin:0 8px;
}
h4 {
margin-top: 5px;
margin-bottom: 5px;
}
.faq-list {
margin-top: 0px;
}
:target {
color: yellow;
}
:target a:visited {
color: yellow;
}

12
views/about.pug Normal file
View File

@@ -0,0 +1,12 @@
extends layout
block body-content
div#content
h3 About
p This is the official status site of apollo.rip
p The services are checked every minute, if a service is down for more than 3 minutes it's reflected on the site.
p
| We make all service checks once a minute, and then average it over three minutes. If it's up for the
| full three minutes, then the service is considered up and healthy. If it's down for all three minutes, then
| the service is considered unhealthy and down. If it's not quite up or down, then it's considered unstable.
p
|

30
views/api.pug Normal file
View File

@@ -0,0 +1,30 @@
extends layout
block body-content
div#content
h3 About the APIs
h3 API Endpoints
p The following API endpoints are available:
p.api
a(href="/api/status") /api/status
p.api-description Get the status of all monitored services
p.api-description 0 = offline, no response over last 3 minutes
p.api-description 1 = online, service responded perfectly over last 3 minutes
p.api-description 2 = unstable, intermittent responses over the past 3 minutes
p.api
a(href="/api/latency") /api/latency
p.api-description Get the latency of all monitored services as applicable
p.api
a(href="/api/uptime") /api/uptime
p.api-description Get the current uptime of all services, the numbers are displayed in minutes.
p.api
a(href="/api/records") /api/records
p.api-description Get the best recorded continuous uptime of all services, the numbers are displayed in minutes.
p.api
a(href="/api/all") /api/all
p.api-description This combines all other APIs into one convenient endpoint.

53
views/faq.pug Normal file
View File

@@ -0,0 +1,53 @@
extends layout
block body-content
div#content
h3 My account is disabled, is there anything I can do?
p
| If you are having any problems with your account you'll have to join #disabled on the IRC network.
| This also includes other problems with your account, not just disabled accounts.
h3 How do I connect to the IRC network?
p
| You'll have to use an IRC client and connect to irc.apollo.rip. The port is 6667 or +7000
| if you are using SSL.
h3 My registration/activation/password reset mail isn't arriving
p
| Currently, our email servers are down, ask your inviter for a direct link. In case of a password reset,
| please join #disabled on our IRC network.
h3 How can I get invited to the site?
p
| Head over to #[a(href="https://interview.apollo.rip") interview.apollo.rip] and read the instructions.
h3 How do I join IRC? The Java Applet / Mibbit isn't working.
p
| Use a real IRC client:
h4 Mac
ul.faq-list
li Textual
li Colloquy
li irssi
li weechat
h4 Linux
ul.faq-list
li Hexchat
li irssi
li weechat
h4 Windows
ul.faq-list
li Hexchat
li irssi
li mIRC
h3 Credits
p
| This site is based on
| #[a(href="https://github.com/dewey/WhatStatus") https://github.com/dewey/WhatStatus]

61
views/index.pug Normal file
View File

@@ -0,0 +1,61 @@
extends layout
block body-content
script(type='text/javascript').
ajaxGet('/api/status', function(components) {
for (var component in components) {
if (components.hasOwnProperty(component)) {
var elem = document.getElementById('status-' + component);
if (components[component] === 1) {
elem.innerText = 'Up';
elem.style.color = '#089a00';
document.getElementById('status-' + component + '-icon').setAttribute('src', 'images/up.png');
}
else if (components[component] === 0) {
elem.innerText = 'Down';
elem.style.color = '#ff1600';
document.getElementById('status-' + component + '-icon').setAttribute('src', 'images/down.png');
}
else {
elem.innerText = 'Updating...';
document.getElementById('status-' + component + '-icon').setAttribute('src', 'images/updating.png');
}
}
}
});
ajaxGet('/api/latency', function(components) {
for (var component in components) {
if (components.hasOwnProperty(component)) {
var elem = document.getElementById('status-' + component);
elem.innerText = components[component] + 'ms';
}
}
});
div#content
div.status-dashboard
table.service-table
tr
td.icon
img(src, id='status-site-icon')
td.service Site - apollo.rip
td#status-site
tr
td.icon
img(src, id='status-tracker-icon')
td.service HTTPS Tracker - mars.apollo.rip
td#status-tracker
tr
td.icon
img(src, id='status-httptracker-icon')
td.service HTTP Tracker - apollo.rip:2095
td#status-httptracker
tr
td.icon
img(src, id='status-irc-icon')
td.service IRC - irc.apollo.rip
td#status-irc

71
views/layout.pug Normal file
View File

@@ -0,0 +1,71 @@
doctype html
html
head
title= title
link(rel="stylesheet", href="/stylesheets/style.css")
link(rel="shortcut icon", href="images/favicon.ico")
script(type='text/javascript').
function ajaxGet(url, callback) {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState === XMLHttpRequest.DONE) {
if (xhr.status === 200) {
callback(JSON.parse(xhr.responseText));
}
else {
console.log("Error: " + xhr.status + ", Text: " + xhr.statusText);
}
}
}
xhr.open('GET', url, true);
xhr.send();
}
function minutesToHumanString(minutes) {
var minutesYear = 525600;
var minutesMonth = 43200; // We assume 30 days in a month
var minutesDay = 1440;
var minutesHour = 60;
var string = "";
var years = Math.floor(minutes / minutesYear);
if (years > 0) {
string += years + "Y ";
minutes -= years * minutesYear;
}
var months = Math.floor(minutes / minutesMonth);
if (months > 0) {
string += months + "M ";
minutes -= months * minutesMonth;
}
var days = Math.floor(minutes / minutesDay);
if (days > 0) {
string += days + "D ";
minutes -= days * minutesDay;
}
var hours = Math.floor(minutes / minutesHour);
if (hours > 0) {
string += hours + "h ";
minutes -= hours * minutesHour;
}
if (minutes > 0) {
string += minutes + "m";
}
return string.trim();
}
body
div.logo
img(src='images/logos/logo.png')
block body-content
footer
p All information is cached and updated every minute.
ul
li
a(href='/') Home
li
a(href='stats') Stats
li
a(href='api') API
li
a(href='faq') FAQ
li
a(href='about') About

42
views/stats.pug Normal file
View File

@@ -0,0 +1,42 @@
extends layout
block body-content
script(type='text/javascript').
document.addEventListener('DOMContentLoaded', function() {
ajaxGet('/api/uptime', function (components) {
for (var component in components) {
if (components.hasOwnProperty(component)) {
document.getElementById('stats-uptime-' + component).innerHTML = minutesToHumanString(components[component]);
}
}
});
ajaxGet('/api/records', function (components) {
for (var component in components) {
if (components.hasOwnProperty(component)) {
document.getElementById('stats-uptimerecord-' + component).innerHTML = minutesToHumanString(components[component]);
}
}
});
});
div#content
h3 Uptime
ul.stats-list
li Site - apollo.rip
span#stats-uptime-site
li HTTPS Tracker - mars.apollo.rip
span#stats-uptime-tracker
li HTTP Tracker - apollo.rip:2095
span#stats-uptime-httptracker
li IRC - irc.apollo.rip
span#stats-uptime-irc
h3 Uptime Records
ul.stats-list
li Site - apollo.rip
span#stats-uptimerecord-site
li HTTPS Tracker - mars.apollo.rip
span#stats-uptimerecord-tracker
li HTTP Tracker - apollo.rip:2095
span#stats-uptimerecord-httptracker
li IRC - irc.apollo.rip
span#stats-uptimerecord-irc