Commit 9d6a10ff authored by Mantas's avatar Mantas
Browse files

Complete refactor: add beta of #2, fix #1

parents
The MIT License (MIT)
=====================
Copyright (C) 2017 Mantas Vilčinskas
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the “Software”), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
*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 OR COPYRIGHT
HOLDERS 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.*
# cState
[![Donate](https://img.shields.io/badge/support_us-donate-yellow.svg?style=flat-square)](https://www.polargalaxy.com/donate) [![Discord](https://img.shields.io/badge/discord-join%20chat-7289DA.svg?style=flat-square)](https://www.polargalaxy.com/discord) [![feross/standard compliant](https://img.shields.io/badge/code_style-standard-brightgreen.svg?style=flat-square)](https://github.com/feross/standard) [![standard-readme compliant](https://img.shields.io/badge/readme%20style-standard-brightgreen.svg?style=flat-square)](https://github.com/RichardLitt/standard-readme) [![Twitter](https://img.shields.io/twitter/follow/polargalaxymc.svg?style=social&label=Follow)](https://twitter.com/polargalaxymc)
> Blazing fast status page with excellent browser support. Built with Hugo. Work in progress, many features planned.
Is [statuspage.io](https://www.statuspage.io/) too expensive? Do you need an open source alternative for your project that is supported on archaic browsers like IE8 and never stops beating? cState is here to help.
[Live demo](https://status.polargalaxy.com)
[![Deploy to Netlify](https://www.netlify.com/img/deploy/button.svg)](https://app.netlify.com/start/deploy?repository=https://github.com/onlinemantas/cstatet)
## Usage
To set up cState, do the following:
1. Deploy the status page, preferably using [Netlify](https://app.netlify.com/start/deploy?repository=https://github.com/onlinemantas/cstatet) and/or [GitHub Pages](https://pages.github.com).
2. Upload your favicon and logo to `/static/` and edit `config.yml`.
Once that is done, you can create a new post like this:
```
hugo new incident/dns-killed-us.md
```
Then, go into `incident/dns-killed-us.md` and follow this format:
```md
---
Title: Catastrophic DNS failure
Description: After moving from one server to another, some DNS settings had unknowingly been tweaked which caused a complete outage for everything hosted on our domain.
Date: 2017-04-04T15:58:32
Section: post
---
##### Post-mortem
On Monday, Amazon gave up on us.
```
## Features
+ Built with [Hugo](https://gohugo.io), on a sturdy foundation
+ Works everywhere: all modern browsers, even IE8 and later
+ You can subscribe to web notifications for status updates
+ Simple, focused, and robust design
+ Easy to edit and deploy
## Resources
+ [Issue tracker](https://github.com/onlinemantas/cstate)
+ [Code repository](https://github.com/onlinemantas/cstate)
## License
MIT © Mantas Vilčinskas
# General
title: Polar Galaxy
languageCode: en-US
baseURL: /
permalinks:
incidents: /incidents/:year/:month/:day/:slug/
params:
# Extra customization
logo: /logo.png
description: We continuously monitor the status of our services and if there are any interruptions, a note will be posted here. Latency measurements work only on modern browsers and may not be 100% accurate. A ping below 1000ms is usually acceptable.
# Current state
announcement: Our Minecraft server is offline while we perform an overhaul of the entire server. Please keep an eye on our website or this status page for more updates.
---
Title: Catastrophic DNS failure
Description: After moving from one Minecraft server to another, some DNS settings had unknowingly been tweaked which caused a complete outage for everything hosted on our domain.
Date: 2017-04-04T15:58:32
Section: post
---
##### Post-mortem
On Monday, we switched our Minecraft game server’s DNS records to point from the old box in Seattle to the one which is currently in Bristol. According to our host, SpartanHost, “the domain for [our DNS] expired so working on getting it transferred”.
We did salvage our domain before our host made the realization that our nameservers went kaput. This should not happen in the forthcoming feature as we are now using [Netlify](https://www.netlify.com) (and supposedly Amazon) for our DNS and nameserver setup.
##### Another update (2017-04-06T14:48:40)
Most services back up. Some sites still may not work; we’re working on it.
##### Update (2017-04-04T17:16:48)
Switched to different nameserver and DNS provider. Services are recovering.
##### Initial report (2017-04-04T15:58:32)
A DNS change of some sort occurred that removed critical metadata after the server move. We switched name servers—which takes a while—but this should ensure smoother sailing forward. There is nothing else we can do at the moment—however—you can join the server with this IP for the time being: pgmc.mcs4.me
<div class="article">
<h3>
<a href="{{ .Permalink }}">{{ .Title }} {{ if .Draft }}[DRAFT]{{ end }}</a>
</h3>
<h4>Began on {{ .Date }} ({{ .ReadingTime }} min read)</h4>
{{ .Content }}
</div>
{{ partial "meta" . }}
<body>
<div class="contain">
<a href="{{ .Site.BaseURL }}">← Go back</a>
<br><br><hr>
{{ .Render "post"}}
</div>
{{ partial "footer" . }}
{{ partial "meta" . }}
<body>
<div class="header notice">
<div class="contain">
<a href="{{ .Site.BaseURL }}" class="logo">
<img src="{{ .Site.Params.logo }}" alt="{{ .Site.Title }}">
</a>
<button class="subscribe">Subscribe</button>
</div>
</div>
<!-- Main -->
<div class="contain">
<noscript>
<p class="error">Uh oh! It looks like you have disabled JavaScript or your browser is a piece of garbage. This means we cannot fetch the neccesary data to show you information about our services. Please <a href="//enable-javascript.com">enable scripting</a> and try again.</p>
</noscript>
<div class="subscriber-box">
<div class="subscriber-box--header">
<div class="contain">
<strong>Notifications</strong>
<span class="close">
<img alt="Close" src="https://cdn4.iconfinder.com/data/icons/geomicons/32/672366-x-128.png">
</span>
</div>
</div>
<div class="contain">
<p>Users of modern browsers such as Chrome and Firefox get access to web notifications, a feature that shows alerts for things such as Facebook mentions, new emails, or in our case, status updates. These updates can be seen even if the user is not actively looking at this status page, however, the tab has to stay open.</p>
<p class="error">Only some users may be able to use this feature. This feature does not work on mobile devices and is not fully tested.</p>
<div id="alert-init">
<input type="checkbox" id="alerts">
<label for="alerts">Ping me when the status changes</label>
</div>
<p class="alert-status faded">You have not enabled notifications.</p>
</div>
</div>
<!-- Main info -->
<div class="summary notice">
<div class="tldr notice">
<strong>Checking status…</strong>
<span class="status"></span>
</div>
<div class="details contain">
<p>{{ .Site.Params.announcement }}</p>
</div>
</div><br>
<!-- Individual info -->
<div class="components">
<div class="component" data-status="" data-id="forums">
Forums <small class="ping testing">Pinging…</small>
</div>
<div class="component" data-status="" data-id="website">
Website <small class="ping testing">Pinging…</small>
</div>
<div class="component" data-status="" data-id="gameserver">
Minecraft server
</div>
</div>
<br>
<small><a href="#disclaimer">Disclaimer</a></small>
<!-- End main -->
</div>
<div class="contain">
<h2>Incident history</h2><hr><br>
{{ range first 10 .Data.Pages }}
{{ .Render "post" }}
{{ end }}
<aside id="meta"> </aside>
</div>
<script async>
/**
* Dev toolset
*/
console.log('Welcome to cState! https://github.com/onlinemantas/cstate');
/**
* Notifications
*/
// Notification toggle
document.querySelector('#alerts').addEventListener('click', enableNotifications)
// Toggling logic
function enableNotifications() {
if(window.Notification && Notification.permission !== "denied") {
Notification.requestPermission(function(status) {
// status is "granted", if accepted by user
var n = new Notification('Great, you just enabled alerts!', {
body: 'You are now going to receive notifications (like this one) whenever the status changes so long as this tab is open. This feature is still being tested.',
icon: '/favicon.ico'
})
// Looks like we DO have permission now
// So let's mark that checkbox
document.querySelector('#alert-init').setAttribute('hidden', 'hidden')
document.querySelector('.alert-status').innerHTML = '<strong>Notifications are enabled. </strong>'
document.querySelector('.alert-status').className = 'alert-status'
})
}
}
if (Notification.permission === 'granted') {
// Looks like we DO have permission
// So let's mark that checkbox
document.querySelector('#alert-init').setAttribute('hidden', 'hidden')
document.querySelector('.alert-status').innerHTML = '<strong>Notifications are enabled. </strong>'
document.querySelector('.alert-status').className = 'alert-status'
}
/**
* Subscribe button
*/
function hasClass(element, cls) {
return (' ' + element.className + ' ').indexOf(' ' + cls + ' ') > -1;
}
document.querySelector('.subscribe').addEventListener('click', pressSubscribeButton);
document.querySelector('.close').addEventListener('click', pressSubscribeButton);
function pressSubscribeButton() {
if (document.querySelector('.subscriber-box').className === 'subscriber-box active') {
document.querySelector('.subscriber-box').className = 'subscriber-box';
} else {
document.querySelector('.subscriber-box').className = 'subscriber-box active';
}
}
/**
* Apply data
*/
document.querySelector('.component[data-id=forums]').setAttribute('data-status', 'ok');
document.querySelector('.component[data-id=website]').setAttribute('data-status', 'ok');
document.querySelector('.component[data-id=gameserver]').setAttribute('data-status', 'notice');
/**
* Get elements
*/
const header = document.querySelector('.header');
const summary = document.querySelector('.summary');
const summaryDetails = document.querySelector('.details');
const tldr = document.querySelector('.tldr');
var summaryText = document.querySelector('.summary strong');
var lastUpdated = document.querySelector('.summary span');
/**
* Prelimenary logic
*/
var online = navigator.onLine;
function updateStatus() {
var lastUpdate = new Date;
}
/**
* Check for internet
*/
var lastUpdate = new Date();
function timeSince(date) {
var seconds = Math.floor((new Date() - date) / 1000);
var interval = Math.floor(seconds / 31536000);
if (interval > 1) {
return interval + ' years';
}
interval = Math.floor(seconds / 2592000);
if (interval > 1) {
return interval + ' months';
}
interval = Math.floor(seconds / 86400);
if (interval > 1) {
return interval + 'd';
}
interval = Math.floor(seconds / 3600);
if (interval > 1) {
return interval + 'h';
}
interval = Math.floor(seconds / 60);
if (interval > 1) {
return interval + 'min';
}
return Math.floor(seconds) + 's';
}
var aDay = 24*60*60*1000;
// Show second by second updates
window.setInterval(function() {
lastUpdated.innerHTML = 'Last updated ' + timeSince(lastUpdate) + ' ago';
}, 1000);
/**
* Adaptive TLDR
*/
const status = document.querySelector('.component[data-id=forums]').getAttribute('data-status') === 'ok' &&
document.querySelector('.component[data-id=website]').getAttribute('data-status') === 'ok' &&
document.querySelector('.component[data-id=gameserver]').getAttribute('data-status') === 'ok'
if (status) {
// Change text
summaryText.innerHTML = 'All systems operational';
// Change design
summary.className = 'summary ok';
tldr.className = 'tldr ok';
summaryDetails.className = 'details contain ok';
header.className = 'header ok';
} else if (!status) {
// Change text
summaryText.innerHTML = 'Experiencing downtime';
// Change design
summary.className = 'summary down';
tldr.className = 'tldr down';
summaryDetails.className = 'details contain down';
header.className = 'header down';
new Notification (
'We are experiencing downtime!', {
body : 'Please view the status page for more information. This alert was automatically triggered to let you know of this change.',
icon : '/favicon.ico'
}
)
}
/**
* Check ping
*/
// Links
const forumPingURL = 'https://forums.polargalaxy.com';
const websiteURL = 'https://www.polargalaxy.com';
// Lib
!function(a,b){"function"==typeof define&&define.amd?define([],b):"object"==typeof module&&module.exports?module.exports=b():a.ping=b()}(this,function(){function a(a){return new Promise(function(b,c){var d=new Image;d.onload=function(){b(d)},d.onerror=function(){c(a)},d.src=a+"?random-no-cache="+Math.floor(65536*(1+Math.random())).toString(16)})}function b(b,c){return new Promise(function(d,e){var f=(new Date).getTime(),g=function(){var a=(new Date).getTime()-f;a*=c||1,d(a)};a(b).then(g).catch(g),setTimeout(function(){e(Error("Timeout"))},5e3)})}return b});
// Check ping for everything
ping(forumPingURL).then(function(delta) {
document.querySelector('.component[data-id="forums"] .ping').innerHTML = String(delta) + 'ms';
document.querySelector('.component[data-id="forums"] .ping').className = 'ping done';
}).catch(function(err) {
document.querySelector('.component[data-id="forums"] .ping').innerHTML = 'Can’t ping';
document.querySelector('.component[data-id="forums"] .ping').className = 'ping error';
})
ping(websiteURL).then(function(delta) {
document.querySelector('.component[data-id="website"] .ping').innerHTML = String(delta) + 'ms';
document.querySelector('.component[data-id="website"] .ping').className = 'ping done';
}).catch(function(err) {
document.querySelector('.component[data-id="website"] .ping').innerHTML = 'Can’t ping';
document.querySelector('.component[data-id="website"] .ping').className = 'ping error';
})
</script>
{{ partial "footer" . }}
<div class="footer">
<div class="contain">
<p id="disclaimer">{{ .Site.Params.description }}</p>
<small class="copyright">Powered by <a href="https://github.com/onlinemantas/cstate">cState</a></small>
</div>
</div>
</body>
</html>
<!DOCTYPE html>
<html lang="{{ .Site.LanguageCode }}" class="nojs">
<head>
<!-- Basics -->
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Data -->
<meta name="description" content="{{ if .Description }}{{ else }}{{ .Site.Params.description }}{{ end }}">
<title>{{ .Site.Title }}</title>
<link rel="canonical" href="{{ .Permalink }}">
<link href="{{ .RSSlink }}" rel="alternate" type="application/rss+xml" title="{{ .Site.Title }}">
<meta name="theme-color" content="#000000">
<!-- Sources -->
<style>
/**
* Color palette
*
* white: #fff;
* forestgreen: #228B22;
* crimson: #DC143C;
* darkorange: #FF8C00;
* slategray: #708090;
*
*/
html, body {
margin: 0;
background: #fff;
color: #444;
font: 16px sans-serif;
}
a {
text-decoration: none;
border-bottom: 0.2px dotted currentColor;
}
a:hover { border-bottom-style: solid; }
h1, h2, h4 {
font-weight: normal;
color: #000;
}
h3 {
margin-bottom: 0;
color: #000;
}
h4 {
margin-top: 0;
color: #999;
}
.faded {
color: #999;
}
.header {
padding: 16px;
color: #fff;
}
img {
height: 16px;
padding: 4px;
}
.contain {
max-width: 640px;
margin: 16px auto;
padding: 16px;
}
.summary {
border: 2px solid #fff;
}
.summary .tldr {
padding: 16px;
color: #fff;
}
.components {
border: 2px solid #ccc;
border-bottom: 0;
}
.component {
color: #000;
padding: 16px;
border-bottom: 2px solid #ccc;
}
.ping.testing {
color: #dcdcdc;
font-style: italic;
}
.ping.done {
color: #aaa;
}
.error {
color: #DC143C;
}
/* .component:before {
content: '[+]';
opacity: 0.4;
cursor: pointer;
} */
.close {
float: right;
}
.subscribe, .status {
display: block;
}
.subscribe {
margin: 8px 0;
}
button, .close {
cursor: pointer;
}
.subscriber-box.active {
display: block;
}
.subscriber-box {
display: none;
background: #fff;
position: fixed;
width: 100%;
height: 100%;
top: 0;
left: 0;
z-index: 1;
overflow-y: scroll;
}
.subscriber-box--header {
color: #000;
border-bottom: 1px solid #aaa;
}
.footer {
padding: 64px;
background: #F0F0F0;
text-align: center;
}
.copyright {
display: block;
font-variant: small-caps;
text-align: center;
}
.copyright a, a.logo {
border-bottom: 0;
}
/**
* Specific to the status
*/
.header.ok, .tldr.ok { background: #228B22; }
.header.disrupted, .tldr.disrupted { background: #FF8C00; }
.header.down, .tldr.down { background: #DC143C; }
.header.notice, .tldr.notice { background: #708090; }
.summary.ok { border-color: #228B22; }
.summary.disrupted { border-color: #FF8C00; }
.summary.down { border-color: #DC143C; }
.summary.notice { border-color: #708090; }
/**