Commit 3208df63 authored by Mantas's avatar Mantas
Browse files

cState goes stable 1.0; fix #4 & fix #5

parent 4bc81871
# cState Updates
## v1.0 (November 25, 2017)
**cState is now ready for use in production! With a switch to the Hugo theming system, you can now independently manage incidents, content, settings, while seamlessly keeping cState up to date.**
+ Whole new header design that lets you choose between a logo and just text
+ Updated incident history section and post design
+ Updated config file; added variables for changing the look and feel of your status page
+ Ensured proper legacy browser support by making JavaScript optional
+ Update incident view to include better mini header, clear issue type, and whether it is resolved
+ Updated footer design, added copyright notice
- Removed pinging, which was never properly implemented
- Removed notifications, as they never worked in the first place
- Fixed bug, where the main announcement box would clip, creating a small white 1px border
- Generally removed a lot of leftover cruft and ensured cState is in pristine condition for a stable release
# Code of Conduct
> TL;DR is to be a good, sensible person. It does not matter who you are.
This community as a whole, much like others, is made up of a mixture of professionals and volunteers from all over the world, working on every aspect of the project at hand. Diversity can be a strength, but it can also lead to communication issues and unhappiness. To that end, we have a few ground rules that we ask people to adhere to. This code applies to all spaces managed by the project creators equally.
This isn’t an exhaustive list of things that you can’t do. Rather, take it in the spirit in which it’s intended — a guide to make it easier to enrich all of us and the technical communities in which we participate.
+ **Be friendly and patient.** Use common sense, treat others like you would want to be treated. Do not insult or put down other participants. Harassment and other exclusionary behavior aren't acceptable.
* Violent threats or language directed against another person, especially personally.
* Discriminatory jokes and language.
* Posting violent material, such as sexually explicit content.
* Posting (or threatening to post) other people’s personally identifying information (“doxing”).
* Unwelcome sexual attention.
* Advocating for, or encouraging, any of the above behavior.
* Repeated harassment of others. In general, if someone asks you to stop, then stop.
+ **Be welcoming and considerate.** Your work will be used by other people, and you in turn will depend on the work of others. Any decision you take will affect users, so you should take those consequences into account when making decisions. Remember that the internet stretches to almost all corners of the world and you might not be communicating in someone else’s primary language.
+ **Be respectful.** Not all of us will agree all the time, but disagreement is no excuse for poor behavior and poor manners. We might all experience some frustration now and then, but we cannot allow that frustration to turn into a personal attack. It’s important to remember that a community where people feel uncomfortable or threatened is not a productive one. Members of this community should be respectful when dealing with other members as well as with people outside the Django community.
+ **When there is disagreement, try to understand why.** Disagreements, both social and technical, can happen all the time and our community is no exception. It is important that we resolve disagreements and differing views constructively. Remember that we’re different. The strength of Django comes from its varied community, people from a wide range of backgrounds. Different people have different perspectives on issues. Being unable to understand why someone holds a viewpoint doesn’t mean that they’re wrong. Don’t forget that it is human to err and blaming each other doesn’t get us anywhere. Instead, focus on helping to resolve issues and learning from mistakes.
*This Code of Conduct is based upon the [Django Code of Conduct]( If you want to use this Code of Conduct for your project, please feel free to do so!*
# Contributing Guide
Thanks for taking the time to contribute. Folks like you are more than welcome!
## I want to suggest something, report a bug, etc.
Cool, [just create an issue](!
## I want to help contribute a new feature.
Great. Here are some things to keep in mind:
+ Keep it as simple as possible.
+ Keep it as light as possible.
+ Try to make it readable.
## Wait, my question has not been answered.
At the time of writing, cState is such a small project that anyone contributing would be a downright miracle. However, if you are a miracle worker, please do redirect any concerns and they will addressed as soon as humanely possible. [Just create an issue](!
# cState
[![Discord](]( [![standard-readme compliant](]( [![Twitter](](
[![GitHub last commit](](
[![GitHub repo size in bytes](](
[![Discord](]( [![Twitter](](
> Blazing fast status page with excellent browser support. Built with Hugo. Work in progress, may have bugs and incomplete features.
> The fastest and most efficient status page on the market, beating even paid solutions. cState has outstanding browser support (IE8+) and can easily be managed with GitHub Pages or Netlify. Ready for production.
Is []( 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.
[**See real-world example**](
[Live demo](
[![Deploy to Netlify](](
## Table of Contents
## Contents
+ [Features](#features)
+ [Installation](#installation)
+ On Netlify
+ GitHub Pages
+ [Install](#install)
+ [FAQ](#faq)
+ [Contribute](#contribute)
+ [License](#license)
## Features
+ Built with [Hugo](, 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
+ Built with [Hugo](, a hyperfast Golang generator
+ Works not just on mobile browsers, but also on archaic browsers like Internet Explorer 8
+ Comes with a simple, focused, and extremely light design
+ Edit your status page just from the config file
+ Comes pre-equipped with Netlify CMS for quick updates
+ Easy to edit and deploy on Netlify or GitHub Pages
## Installation
## Install
### On Netlify
We encourage you to use [Netlify]( for cState. The build command is `hugo` and your site is compiled to `public`.
1. [Deploy to Netlify](
2. Upload your favicon and logo to `/static/` and edit `config.yml`.
For this tutorial, it is assumed that you have Hugo and Git installed (check with `hugo version` & `git --version`).
### Everywhere Else
**This does not seem to work in one go on PowerShell, so enter each command individually.**
# !. First off, we initialize the Git repository
git init;
# 2. Then this creates all the necessary directories
mkdir -p content/issues themes static;
## Creating Incident
# 3. We get the config file
curl -o config.yml;
Once in the project root directory, you can create a new post from the command line like this:
# 4. Download cState
cd themes; git submodule add;
# 5. Last off, start the server locally
hugo serve
hugo new incident/
And that is it; you have set up cState locally.
Now is a good time to make cState look the way you want it to, so upload a favicon (and logo) to `/static/`. Edit `config.yml` to fit your needs. And so on, and so forth.
**Do not change any files in the `themes` directory or its subdirectories. Everything is handled automatically by Git.**
To make the status page public, you will need to connect to a remote GitHub repository much like this:
# Create a remote origin like this (if you have not already)
git remote add origin
# Add all the files
git add -A
# Then a message based on your changes
git commit -m "Testing out cState"
# All done
git push -u origin master
Essentially, just go into `content/incident` and add a new file. This will be an incident. The file name will indicate the URL of the incident. So, for example, if you create ``, the URL will lead to ``.
For an example of a working status page, see [rabbitnode/status](
## FAQ
Then, go into `incident/` and follow this format:
### Where do issues go? What is the frontmatter, how do I define metadata for issues?
Create a file in `content/issues`. The name of the file will be the slug (what shows up in the URL bar). For example, this is what `` should look like:
Title: Catastrophic DNS failure
Description: After moving from one server to another, DNS just kinda gave us the middle finger. Ugh.
Date: 2017-04-04T15:58:32
Title: Give your issue a good title
Description: This description is here merely for metadata purposes and may show up in search results. It may be used as a summary.
Date: 2017-02-30 14:30
Resolved: true
Severity: down
- Client Area
Section: post
##### Post-mortem
On Monday, Amazon gave up on us.
Content goes here.
Time to break that down.
`Title`: This is the one of the most important parts of an incident. *(required)*
`Description`: This description is here merely for metadata purposes and may show up in search results. It may be used as a summary.
`Date`: An ISO-8601 formatted date. Does not include time zone. *(required)*
`Resolved`: Whether issue should affect overall status. Either `true` or `false`. *(boolean, required)*
`Severity`: If an issue is not resolved, it will have an applied severity. There are 3 levels of severity: `notice`, `disrupted`, and `down`. If there are multiple issues, the status page will take the appearance of the more drastic issue (such as `disrupted` instead of `notice`). *(required)*
`Affected`. Add the items that were present in the config file which should alter the status of each individual system (component). *(array, required)*
`Section`. This must be `issue`, so that Hugo treats it as one. *(required)*
### Is there an admin panel or some easy way to change the state of each issue?
If you use [Netlify](, you can expect to see Netlify CMS integration very soon. Otherwise, you could fall back to []( or something similiar.
### How do I make this work on GitHub Pages?
Compile locally, commit changes, and push them out. We do recommend using [Netlify](, however.
### My question was not answered!
This part of the documentation still needs to finished. [Questions]( are more than welcome and you should get a pretty fast response as well.
## Contribute
Feel free to open an issue or make a pull request, those should get answered pretty quickly on GitHub.
+ Glance over the [Code of Conduct](/
+ Before submitting a pull request, create an issue to [discuss the implications of your proposal](
+ Write consistent, simple, and readable code. You can [join the Chorale Discord]( to discuss in `#cstate`.
## License
Section: issue
# Welcome to the Hugo configuration file.
# Hugo is used for building the status page,
# so this file can be used to change how
# your status page should behave or look.
# What is your status page called?
# Shows up in the browser bar and meta tags
title: Example Inc. Status
# What language is this page in?
# Only alters the html[lang] attribute
languageCode: en-US
# What is the hostname or path to the root?
# Where is the site hosted?
# Example:
# Should posts, which have a publish date
# from the future, be built? Useful for
# sharing upcoming maintenance, etc.
# We recommend to keep this at `true`.
# BOOLEAN; `true`, `false`
buildFuture: true
# These are your systems. Change them to
# change the amount of components.
- Core
- EU
- US
# Should we show the logo or the title
# of the status page?
useLogo: false
# Where is the logo located, if one is
# present at all?
logo: /logo.png
# This is the description that is shown
# in the footer and meta tags.
description: We continuously monitor the status of our services and if there are any interruptions, a note will be posted here.
# Cplors throughout cState
# Defaults:
# ok: 228B22
# warning: DC143C
# down: FF8C00
# notice: 708090
# border: dfdfdf
# faded: ccc
ok: 228B22
disrupted: FF8C00
down: DC143C
notice: 708090
border: dfdfdf
faded: ccc
# These options affect the core of cState.
# Please do not change them if you do not
# know what you are doing.
theme: cstate
# General
title: Chorale Status
languageCode: en-US
baseURL: /
incidents: /incidents/:year/:month/:day/:slug/
# 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. For the best experience, use a modern browser like Chrome.
# Current state
announcement: Everything looks fine, but if you see something wrong, give us a shout by using the chat box in the bottom right corner.
Title: HTTPS on status page not working
Description: Cannot access without getting a certificate error
Date: 2017-08-13T17:58:32
Section: post
**Update as of 2017-08-14T19:15:59:** The issue has been resolved.
Cannot access without getting a certificate error.
Contacted Netlify for support.
{{ partial "meta" . }}
<div class="contain">
<a href="{{ .Site.BaseURL }}">← Go back</a>
{{ .Render "post"}}
{{ .Render "post" }}
{{ partial "footer" . }}
<div class="article">
<a href="{{ .Permalink }}">{{ .Title }} {{ if .Draft }}[DRAFT]{{ end }}</a>
<h4>Began on {{ .Date }} ({{ .ReadingTime }} min read)</h4>
{{ .Content }}
{{ partial "meta" . }}
<div class="header notice">
<div class="contain">
<a href="{{ .Site.BaseURL }}" class="logo">
<img src="{{ .Site.Params.logo }}" alt="{{ .Site.Title }}">
{{ $incidents := where .Site.RegularPages "Params.section" "issue" }}
{{ $active := where $incidents "Params.resolved" "=" false }}
{{ $isNotice := where $active "Params.severity" "=" "notice" }}
{{ $isDisrupted := where $active "Params.severity" "=" "disrupted" }}
{{ $isDown := where $active "Params.severity" "=" "down" }}
<body class="status-{{ if $isDown }}down{{ else }}{{ if $isDisrupted}}disrupted{{ else }}{{ if $isNotice }}notice{{ else }}ok{{ end }}{{ end }}{{ end }}">
<div class="header">
<div class="contain contain--more center">
<a href="/" class="logo">
{{ if .Site.Params.useLogo }}
<h1><img src="{{ .Site.Params.logo }}" alt="{{ .Site.Title }}"></h1>
{{ else }}
<h1>{{ .Site.Title }}</h1>
{{ end }}
<button class="subscribe">Subscribe</button>
<!-- Main -->
<div class="contain">
<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 scripting</a> and try again.</p>
<p class="error">Uh oh! It looks like you have disabled JavaScript. Please <a href="//">enable scripting</a> to enhance your experience on this website.</p>
<div class="padding"></div>
<div class="subscriber-box">
<div class="subscriber-box--header">
<div class="contain">
<span class="close">
<img alt="Close" src="">
<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>
<p class="alert-status faded">You have not enabled notifications.</p>
<!-- Main info -->
<div class="summary">
{{ if $isDown }}
Experiencing major issues
{{ else }}
{{ if $isDisrupted}}
Experiencing disruptions
{{ else }}
{{ if $isNotice }}
Please read announcement
{{ else }}
All systems operational
{{ end }}{{ end }}{{ end }}
<span class="status summary__date" onclick="location.reload()"></span>
<!-- Main info -->
<div class="summary notice">
<div class="tldr notice">
<strong>Checking status…</strong>
{{ range $active }}
<div class="padding"></div>
<small class="date">{{ .Date.Format "January 02, 2006 at 3:04 PM" }}</small><br>
<strong class="faded">{{ .Title }}</strong>
{{ .Content }}
<div class="padding"></div>
{{ else }}{{ end }}
<span class="status"></span>
<div class="details contain">
<p>{{ .Site.Params.announcement }}</p>
<div class="padding"></div>
<!-- Individual info -->
<div class="components">
<div class="component" data-status="" data-id="forums">
API <small class="ping testing">Pinging…</small>
<div class="component" data-status="" data-id="website">
Website <small class="ping testing">Pinging…</small>
{{ $systems := }}
{{ range $index, $systems }}
<div class="component" data-status="ok">
{{ . }}
<span class="component-status">Operational</span>
{{ end }}
<small><a href="#disclaimer">Disclaimer</a></small>
<!-- End main -->
</div><div class="padding"></div><hr>
<div class="contain">
<h2>Incident history</h2><hr><br>
<h2 class="center">Incident history</h2>
{{ range first 10 .Data.Pages }}
{{ .Render "post" }}
{{ .Render "issue" }}
{{ end }}
<aside id="meta"> </aside>
<script async>
* Dev toolset
console.log('Welcome to 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');
* 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'
if (status) {
// Change text
summaryText.innerHTML = 'All systems operational';
// Change design
summary.className = 'summary ok';
tldr.className = 'tldr ok';
summaryDetails.className = 'details contain ok';