Building a basic Metrics dashboard using CSS / JavaScript
posted on April 10, 2017, 12:00 am in
Using some basic JavaScript and CSS, we can create a small Metrics front end to display data from various API sources.
Based on a 4x4 Grid and designed to look good on 1080p TV's.
Demo Screenshot
Code
CSS Styles
* { margin: 0; padding: 0; }
img { border: 0; }
body { font-family: 'Roboto Condensed', sans-serif; overflow: hidden; }
.dash-wrapper {
width: 1920px;
height: 1080px;
position: relative;
background: #191919;
}
.dash-widget {
position: absolute;
border-radius: 4px;
overflow: hidden;
box-sizing: border-box;
border: 10px solid #191919;
padding: 15px;
text-align: center;
background: #31343B;
color: #fff;
}
.dash-widget .label { color: #a6a6a6; }
.dash-announcement {
position: absolute;
box-sizing: border-box;
text-align: center;
color: #fff;
background: #000;
left: 0;
width: 100%;
height: 10%;
top: 90%;
padding: 10px;
font-size: 57px;
}
.x-1 { left: 0; }
.x-2 { left: 25%; }
.x-3 { left: 50%; }
.x-4 { left: 75%;}
.y-1 { top: 0; }
.y-2 { top: 22.5%; }
.y-3 { top: 45%; }
.y-4 { top: 67.5%;}
.width-1 { width: 25%; }
.width-2 { width: 50%; }
.width-3 { width: 75%; }
.width-4 { width: 100%; }
.height-1 { height: 22.5%; }
.height-2 { height: 45%; }
.height-3 { height: 67.5%; }
.height-4 { height: 90%; }
.title, .value { margin-bottom: 12px; text-transform: uppercase; }
.title { font-size: 30px; font-weight: 400; }
.value { font-size: 80px; font-weight: 700;}
.label { font-size: 30px; text-transform: uppercase; font-weight: 400;}
.red { background: #d02127; }
.green { background: #81B72B; }
.orange { background: #F0A542; }
.red .label, .green .label, .orange .label { color: #fff !important; }
.list { margin: 0 20px 0 20px; }
.list .left, .list .right { float: left; width: 50%; font-size: 36px; }
.list .left { text-align: left; }
.list .right { text-align: right; }
.clear { clear: both; }
JavaScript Code
/*
* APIDash.js (Apr 10 2017)
* Copyright 2017, http://codeeverywhere.ca
* Licensed under the MIT license.
*/
(function(document, window) {
let counter = 0;
var APIDash = function(args) {
const animationInterval = 1000 * 10
const APICallInterval = 1000 * 30
const uuid = () => 'uid-' + counter++
this.init = function(className) {
document.querySelector(className).innerHTML = `
<div class="dash-wrapper">
<div class="dash-widgets"></div>
<div class="dash-announcement">... text ...</div>
</div>
`
}
this.message = function(str){
document.querySelector('.dash-announcement').innerHTML = str
}
this.textWidget = function(title, className, callAPI) {
const id = uuid()
const animate = (className) => { document.querySelector(`#${id} .data`).className = `data ${className}` }
let format = [{label:"no data", value: "--"}]
let index = 0
document.querySelector('.dash-widgets').innerHTML += `
<div class="dash-widget ${className} animated fadeInUp" id="${id}">
<div class="title">${title}</div>
<div class="data">
<div class="value">--</div>
<div class="label">--</div>
</div>
</div>
`
const slideOut = () => {
animate('animated slideOutLeft')
index = (index + 1) % format.length
setTimeout(slideIn, 500)
}
const slideIn = () => {
animate('animated slideInRight')
document.querySelector(`#${id} .value`).innerHTML = format[index].value
document.querySelector(`#${id} .label`).innerHTML = format[index].label
}
const runCallAPI = () => {
callAPI().then( (resp) => { format = resp })
}
runCallAPI()
setInterval(runCallAPI, APICallInterval)
// add some random time so the animations aren't in sync
setInterval( slideOut, animationInterval + Math.floor(Math.random()*10000) )
}
this.listWidget = (title, className, callAPI) => {
const id = uuid()
const animate = (className) => { document.querySelector(`#${id} .list`).className = `list ${className}` }
let format = [{label:"--", value: "--"}]
document.querySelector('.dash-widgets').innerHTML += `
<div class="dash-widget ${className} animated fadeInUp" id="${id}">
<div class="title">${title}</div>
<div class="list">--</div>
</div>
`
const slideOut = () => {
animate('animated slideOutLeft')
setTimeout(slideIn, 500)
}
const slideIn = () => {
animate('animated slideInRight')
document.querySelector(`#${id} .list`).innerHTML = format.reduce( ( acc, item ) => acc + `
<div class="left">${item.label}</div>
<div class="right">${item.value}</div>
<div class="clear"></div>
`, '')
}
const runCallAPI = () => { callAPI().then( (resp) => { format = resp }) }
runCallAPI()
setInterval(runCallAPI, APICallInterval)
setInterval( slideOut, animationInterval + Math.floor(Math.random()*10000) )
}
return this;
}
window.APIDash = APIDash;
})(document, window);
HTML Template
<!DOCTYPE html>
<html>
<head>
<title>API Dashboard</title>
<link href='https://fonts.googleapis.com/css?family=Roboto+Condensed:300,400,700' rel='stylesheet' type='text/css'>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/3.7.0/animate.min.css">
<link rel="stylesheet" href="APIDash.css">
<script type="text/javascript" src="APIDash.js"></script>
</head>
<body>
<div class="dash-app"></div>
<script type="text/javascript">
// Initialize APIDash ..
APIDash().init('.dash-app')
APIDash().message('hello!')
// Get weather data, get every 3 hrs
let weather = "-- ℃"
const getWeather = () => {
const url = 'https://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20weather.forecast%20where%20woeid%20in%20(select%20woeid%20from%20geo.places(1)%20where%20text%3D%22montreal%2C%20qc%22)&format=json&env=store%3A%2F%2Fdatatables.org%2Falltableswithkeys'
fetch(url).then((resp) => resp.json()).then(
data => {
const celsius = Math.floor((parseInt(data.query.results.channel.item.condition.temp) - 32) * .5556);
weather = celsius + "℃"
},
fail => { }
)
}
getWeather()
setInterval(getWeather, 1000 * 60 * 60 * 3)
// Sample Widget 1
APIDash().textWidget('time/weather ( widget 1 )', 'width-1 height-1 x-3 y-1', () => {
return Promise.resolve([
{
value: weather,
label: "weather"
},
{
value: new Date().toString().substr(16, 5),
label: new Date().toString().substr(0, 10)
}
])
})
// Sample Widget 2
let outsideVar = 0
APIDash().textWidget('active users ( widget 2 )', 'width-1 height-1 x-1 y-1 green', () => {
return Promise.resolve([{
value: (outsideVar++).toLocaleString(),
label: "today"
},
{
value: (outsideVar * 10).toLocaleString(),
label: "this week"
}])
})
// Sample Widget 3
APIDash().textWidget('error rate ( widget 3 )', 'width-1 height-1 x-4 y-1 red', () => {
return Promise.resolve([{
value: (999).toLocaleString(),
label: "past 24H"
}])
})
// Sample Widget 4
APIDash().textWidget('error rate ( widget 4 )', 'width-1 height-1 x-3 y-2', () => {
return Promise.resolve([{
value: (999).toLocaleString(),
label: "past 24H"
}])
})
// Sample Widget 5
APIDash().listWidget('list ( widget 5 )', 'width-1 height-3 x-2 y-2', () => {
return Promise.resolve([
{
label: "item1",
value: 1234
},
{
label: "item2",
value: 1234
},
{
label: "item3",
value: 1234
}
])
})
// Sample Widget 6
APIDash().textWidget('long text ( widget 6 )', 'width-2 height-1 x-3 y-3', () => {
return Promise.resolve([
{
value: (1234).toLocaleString(),
label: "complete"
},
{
value: (5678).toLocaleString(),
label: "failed"
}
])
})
</script>
</body>
</html>