Skip to content

Instantly share code, notes, and snippets.

@nhalstead
Forked from eeejay/hosting_good.html
Last active June 21, 2019 05:45
Show Gist options
  • Save nhalstead/6067e542f2a815e49c3d08eb217b361f to your computer and use it in GitHub Desktop.
Save nhalstead/6067e542f2a815e49c3d08eb217b361f to your computer and use it in GitHub Desktop.
Simple Status Page that has auto Reload and fully controlled by a Meta Tag.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Server Status Page</title>
<meta property="status" content='{"api":"//example.com/server_status.php?format=json","interval":60,"expand":true,"servers":[{"id":12,"short":"Strawberry","online":true,"error":true,"hostname":"nhalstead.me","statusPageLink":false,"serialNumber":"0000","location":{"building":"X","rack":13,"unit":40}}]}'>
<style>
a:hover, a:focus, a:active {
text-decoration: none;
color: inherit;
}
body {
margin: 50px;
}
h3 {
margin: 0;
}
dt {
float: left;
clear: left;
font-weight: bold;
margin-left: 1rem;
}
dt:after {
content: ':\00a0';
}
dd:before {
content: '\00a0';
}
.server {
position: relative;
box-sizing: border-box;
width: 100%;
margin: 0 0 5px 0;
border: 0;
padding: 20px;
background-color: green;
color: #fff;
font-family: helvetica;
font-size: 18px;
line-height: 1.7;
text-align: left;
}
.server_down .server{
background-color: red;
}
.server_error .server {
background-color: #FFEB3B;
color: black;
}
.server:after {
content: 'details \25B8';
speak: none;
position: absolute;
top: 2em;
right: 20px;
display: inline-block;
border-radius: 4px;
border: 0px none;
padding: 5px 11px;
box-shadow: 0px -2px rgba(0,0,0, 0.5) inset;
background-color: #fff;
color: #4D4E53;
font-weight: normal;
font-size: 14px;
line-height: 1;
text-transform: uppercase;
}
.server[aria-expanded="true"]:after {
content: 'details \25BE';
}
.server:hover, .server:focus {
cursor: pointer;
}
.server:hover:after, .server:focus:after {
padding: 8px 14px;
top: calc(2em - 3px);
cursor: pointer;
}
.server:before {
content: '\2713';
speak: none;
}
.server_down .server:before {
content: '\2715';
speak: none;
}
.server_error .server:before {
content: '\25EC';
speak: none;
}
.server_details {
margin-bottom: 5px;
margin-top: -8px;
padding: 0;
border: 3px solid green;
font-family: helvetica;
line-height:2;
list-style-type: none;
transition: height 0.5s ease-in-out, visibility 0.5s;
height: 8rem;
overflow: hidden;
}
.server_down + .server_details {
border-color: red;
}
.server_error + .server_details {
border-color: #FFEB3B;
}
.server_details.hidden {
height: 0;
visibility: hidden;
}
.offscreen {
position: absolute;
height: 1px;
width: 1px;
overflow: hidden;
clip: rect(1px 1px 1px 1px);
clip: rect(1px, 1px, 1px, 1px);
}
</style>
</head>
<body>
<div>
<h3>
<button class="server" aria-controls="blueberry" aria-expanded="false" aria-label="Server up: Blueberry">
Blueberry
</button>
</h3>
<dl class="server_details hidden" id="blueberry">
<dt>Hostname</dt><dd>web02.onr.example.com</dd>
<dt>Service Tag</dt><dd>JCQFZK1</dd>
<dt>Datacenter</dt><dd>ONR</dd>
<dt>Rack Location</dt><dd>104.6, Unit #24</dd>
</dl>
</div>
<div>
<h3 class="server_down">
<button class="server" aria-controls="raspberry" aria-expanded="false" aria-label="Server down: Raspberry">
Raspberry
</button>
</h3>
<dl class="server_details hidden" id="raspberry">
<dt>Hostname</dt><dd>web02.onr.example.com</dd>
<dt>Service Tag</dt><dd>JCQFZK1</dd>
<dt>Datacenter</dt><dd>ONR</dd>
<dt>Rack Location</dt><dd>104.6, Unit #24</dd>
</dl>
</div>
<div>
<h3>
<button class="server" aria-controls="strawberry" aria-expanded="false" aria-label="Server up: Raspberry">
Strawberry
</button>
</h3>
<dl class="server_details hidden" id="12">
<dt>Hostname</dt><dd>web02.onr.example.com</dd>
<dt>Service Tag</dt><dd>JCQFZK1</dd>
<dt>Datacenter</dt><dd>ONR</dd>
<dt>Rack Location</dt><dd>104.6, Unit #24</dd>
</dl>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js" integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo=" crossorigin="anonymous"></script>
<script>
/**
* Adds some merge Capibility to objects like arrayMerge in php but for Objects
* This is the Same thing as Object.assign BUT it does not overrite the
* object you are calling it on
*
* @param {Object} Another Object to Add to the Current Object
* @return {Object} The self + the Object to merge.
*/
Object.prototype.merge = function (objectToMerge) {
return Object.assign({}, this, objectToMerge);
};
/**
* Adds some Capacity to run a foreach on an object
*
* @param {Function} Callback that will execute on every element
* @param {Mixed} Data to send to Every Call of the data
*/
Object.prototype.forEach = function(callback, extraData) {
Object.keys(this).forEach(function(key) {
var value = this[key];
callback(key, value, extraData);
});
}
/**
*
* @param {String} name Meta Tag to get value of.
* @param {String} defaultValue Default value to return if the Meta Tag DOES NOT Exist.
* @return {String} Value from the Meta Tag.
*/
function getHeadMeta(name, defaultValue = "") {
if(typeof defaultValue !== "string") defaultValue = "{}";
return (document.head.querySelector("meta[property~='"+name+"'][content]") || {content: defaultValue}).content;
}
/**
* url Function, Get URL Data.
*
* @param {string} URL to Parse and Process
* @return {Object} URL Data
*/
function url(urlIn = "/"){
if(typeof urlIn !== "string") urlIn = "";
let parser = document.createElement('a');
parser.href = urlIn;
return {
href: parser.href,
protocol: parser.protocol,
host: parser.host,
hostname: parser.hostname,
port: parser.port,
pathname: parser.pathname,
hash: parser.hash,
serach: parser.search
};
}
const dataContainer = {
api: "", // Update API URL
interval: 60, // Update Interval of the Data, 0 is disabled
expand: true, // Expand all of the Elements on Load.
first: false, // Tells the Init that it needs to pull data on load of the page.
servers: [] // Server List
};
const dataServer = {
id: "",
short: "",
online: true,
error: false,
hostname: "",
statusPageLink: false, // Is the Hostname Link a FQDN that has a Status Page, Should it be Clickable?
serialNumber: "",
location: {
building: "",
rack: "",
unit: ""
}
};
// Process all of the Init Config with the Server data.
function parse(data = "{}"){
try {
var x = {};
if(typeof data == "string"){
x = JSON.parse(data);
}
else if (typeof data == "object"){
x = data;
}
x = dataContainer.merge(x);
x.servers = parseServer(x.servers);
return x;
}
catch(e){
console.warn(e);
return {};
}
}
// Process Just Server Data
function parseServer(data = "{}"){
try {
var x = [];
if(typeof data == "string"){
x = JSON.parse(data);
}
else if (typeof data == "object"){
x = data;
}
x.forEach((e,i)=>{
e = dataServer.merge(e);
});
return x;
}
catch(e){
console.warn(e);
return [];
}
}
/**
* Build HTML for the UI from the Server Info being provided.
*
* @return {HtmlCollection} HTML Collection from JS
*/
function buildItem (serverInfo){
serverInfo = dataServer.merge(serverInfo);
return ((si, d, c, pH, pHb, pD, dt1, dt2, dt3, dt4, dd1, dd2, dd3, dd4)=>{
c = d.createElement("div");
pH = d.createElement("h3");
if(si.online !== true){
pH.classList.add("server_down");
}
else if(si.error == true){
pH.classList.add("server_error");
}
pHb = d.createElement("button");
pHb.classList.add("server");
pHb.onclick = function(){
$(this).toggleExpand();
};
pHb.setAttribute("aria-controls", si.id);
pHb.setAttribute("aria-expanded", "false");
pHb.setAttribute("aria-label", "Server Details for " + si.id);
pHb.innerText = " " + ((si.short)?si.short:si.id);
pH.appendChild(pHb);
pD = d.createElement("dl");
pD.id = si.id;
pD.classList.add("server_details");
pD.classList.add("hidden");
dt1 = d.createElement("dt");
dd1 = d.createElement("dd");
dt1.innerText = "Hostname";
dd1.innerText = si.hostname;
pD.appendChild(dt1);
pD.appendChild(dd1);
dt2 = d.createElement("dt");
dd2 = d.createElement("dd");
dt2.innerText = "Service Tag";
dd2.innerText = si.serialNumber;
pD.appendChild(dt2);
pD.appendChild(dd2);
dd3 = d.createElement("dd");
dt3 = d.createElement("dt");
dt3.innerText = "Datacenter";
dd3.innerText = si.location.building;
pD.appendChild(dt3);
pD.appendChild(dd3);
dt4 = d.createElement("dt");
dd4 = d.createElement("dd");
dt4.innerText = "Rack Location";
dd4.innerText = "Rack " + si.location.rack + ", Unit " + si.location.unit;
pD.appendChild(dt4);
pD.appendChild(dd4);
c.appendChild(pH);
c.appendChild(pD);
return c;
})(serverInfo, document);
}
/**
* Update Document Elements with the given data.
*
*/
function process(data = "[]"){
let list = [];
if(typeof data == "string"){
list = parseServer(data);
}
else {
list = data;
}
list.forEach((e) => {
// Process Each Entry of the Server List.
let h = buildItem(e);
if(document.getElementById(e.id) == undefined){
document.body.appendChild(h);
}
else {
let r = document.getElementById(e.id);
r.parentElement.replaceWith(h);
}
});
}
function updateUI(){
if(!window.RuntimeConfig) return;
if(!window.RuntimeConfig.api) return;
console.log("%cUpdating UI Component from API Address%s", "display:block;text-align:center;background-color:pink;padding:2px 4px;", url(window.RuntimeConfig.api).href);
var x = []; // Server List
x = '[{"id": "s2f-master"}]';
process(x);
}
/**
* This INIT function is good for a few things.
* When you call it with a string or object it will process it,
* also sets up the needed var storage.
*
* If you give it an Object and the env is setup then it will add to the
* current config and reset the init environment.
*
* @param {Object} Config Function
*/
function init(data){
let conf = {};
if(typeof window.RuntimeConfig !== "undefined" && typeof data == "object"){
conf = data;
}
else {
window.RuntimeConfig = window.RuntimeConfig || {};
conf = parse(data);
}
window.RuntimeConfig = Object.assign({}, window.RuntimeConfig, conf);
delete window.RuntimeConfig.servers;
console.log("Loaded Config.");
process(conf.servers); // Init Setup of the UI
if(conf.first == true){
updateUI();
}
if(conf.interval !== 0 && conf.api !== "") {
if(typeof window.RuntimeConfigInterval !== "undefined"){
clearInterval(window.RuntimeConfigInterval);
}
console.log("Data Reload set for %c%s %cseconds", "color:lightseagreen;font-weight:bold;", conf.interval, "");
conf.interval = conf.interval * 1000;
window.RuntimeConfigInterval = setInterval(() => {
updateUI();
}, conf.interval);
}
}
(function ($) {
$.fn.toggleExpand = function(){
// this == control element
var $controller = $(this);
// get controlled element
var controlledId = $controller.attr('aria-controls');
var $controlled = $('#' + controlledId);
// Toggle values on both
$controller.ariaToggle('aria-expanded');
$controlled.toggleClass('hidden');
// Make it so we can chain this function, like any good jQuery function
return this;
};
$.fn.ariaToggle = function(attr) {
var $this = $(this);
var currentValue = $this.attr(attr);
if(currentValue === 'true'){
$this.attr(attr, false);
} else {
$this.attr(attr, true);
}
// Make it so we can chain this function, like any good jQuery function
return this;
};
$('.server').on('click', function(){
$(this).toggleExpand();
});
}(jQuery));
</script>
<script>
// Start Setup of the page by loading the config from the Meta Tag.
init(getHeadMeta("status", "{}"));
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment