From 4db8e84cac9474454648bc367d4e327b9eb241f8 Mon Sep 17 00:00:00 2001 From: Gregory Eremin Date: Fri, 30 Jan 2015 14:08:22 +0700 Subject: [PATCH] Dashboard charts --- server/static/app.css | 50 +++++++++--- server/static/app.js | 145 ++++++++++++++++++++++++++++++----- server/static/dashboard.tmpl | 2 +- 3 files changed, 165 insertions(+), 32 deletions(-) diff --git a/server/static/app.css b/server/static/app.css index 0d0492f..6e2020b 100644 --- a/server/static/app.css +++ b/server/static/app.css @@ -1,26 +1,29 @@ * { box-sizing: border-box; } -.title, td, th { +.heading, td, th { font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; - font-size: 1.3em; - padding: 0.6em; font-weight: 300; } -.title, table { +.heading, table { position: absolute; left: 50%; width: 650px; margin-left: -325px; } -.title { +.heading { top: 10px; - text-align: center; font-size: 1.8em; + text-align: center; } table { top: 100px; - border-spacing: 0; border-collapse: collapse; + border-spacing: 0; +} +th, td { + font-size: 1.3em; + padding: 0.6em; + line-height: 1.5em; } th { font-weight: 400; @@ -28,13 +31,15 @@ th { thead tr { border-bottom: #666 1px solid; } -tbody tr:nth-child(even) { +/*tbody tr:nth-child(even) { background-color: #f5f5f5; +}*/ +.title { + position: relative; + width: 350px; } .name { - width: 350px; max-width: 350px; - text-align: left; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; @@ -44,6 +49,16 @@ tbody tr:nth-child(even) { max-width: 150px; text-align: right; } +.num { + display: inline-block; + width: auto; + font-family: 'Lucida Console', Monaco, 'Courier New', Courier, monospace; + font-size: 0.8em; + line-height: 0.8em; + padding: 5px; + background: rgba(255, 255, 255, .8); + border-radius: 3px; +} .zero { color: #aaa; } @@ -65,3 +80,18 @@ tbody tr:nth-child(even) { #placeholder td { text-align: center; } + +.chart { + position: absolute; + z-index: -1; + top: 0; + right: -300px; + width: 300px; + height: 100%; +} +.in { + fill: #7df; +} +.out { + fill: #7fd; +} diff --git a/server/static/app.js b/server/static/app.js index 59f259e..193bb76 100644 --- a/server/static/app.js +++ b/server/static/app.js @@ -1,9 +1,13 @@ +/* + * Dashboard + */ + function loadStatus(callback) { var xhr = new XMLHttpRequest(), loading = document.getElementById('loading'); loading.setAttribute('style', 'display: block;'); - xhr.open('GET', '/status', true); + xhr.open('GET', '/status?rates=please', true); xhr.onreadystatechange = function() { if (xhr.readyState === 4) { if (xhr.status === 200) { @@ -38,44 +42,143 @@ function updateDashboard(queues) { tr = document.createElement('tr'); tr.setAttribute('id', id); - var nameCol = document.createElement('td'); - nameCol.appendChild(document.createTextNode(queue)); - tr.appendChild(nameCol); - tr.appendChild(document.createElement('td')); - tr.appendChild(document.createElement('td')); + var titleCol = document.createElement('td'), + messagesCol = document.createElement('td'), + subscriptionsCol = document.createElement('td'), + nameDiv = document.createElement('div'), + svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'), + pathIn = document.createElementNS('http://www.w3.org/2000/svg', 'path'), + pathOut = document.createElementNS('http://www.w3.org/2000/svg', 'path'), + messagesDiv = document.createElement('div'), + subscriptionsDiv = document.createElement('div'); + pathIn.setAttribute('class', 'in'); + pathOut.setAttribute('class', 'out'); + svg.setAttributeNS(null, 'viewbox', '0 0 300 40'); + svg.appendChild(pathIn); + svg.appendChild(pathOut); + + nameDiv.setAttribute('class', 'name'); + svg.setAttribute('class', 'chart'); + titleCol.setAttribute('class', 'title'); + messagesCol.setAttribute('class', 'messages'); + subscriptionsCol.setAttribute('class', 'subscriptions'); + + nameDiv.appendChild(document.createTextNode(queue)); + titleCol.appendChild(svg); + titleCol.appendChild(nameDiv); + messagesCol.appendChild(messagesDiv); + subscriptionsCol.appendChild(subscriptionsDiv); + tr.appendChild(titleCol); + tr.appendChild(messagesCol); + tr.appendChild(subscriptionsCol); queuesList.appendChild(tr); } - var cols = tr.getElementsByTagName('td'), - nameCol = cols[0], - messagesCol = cols[1], - subscriptionsCol = cols[2]; + var titleCol = tr.getElementsByClassName('title')[0], + nameDiv = titleCol.getElementsByClassName('name')[0], + svg = titleCol.getElementsByClassName('chart')[0], + messagesCol = tr.getElementsByClassName('messages')[0], + subscriptionsCol = tr.getElementsByClassName('subscriptions')[0], + messagesDiv = messagesCol.getElementsByTagName('div')[0], + subscriptionsDiv = subscriptionsCol.getElementsByTagName('div')[0], + messages = Number(meta.messages).toLocaleString(), + subscriptions = Number(meta.subscriptions).toLocaleString(); - messagesCol.innerHTML = Number(meta.messages).toLocaleString(); - subscriptionsCol.innerHTML = Number(meta.subscriptions).toLocaleString(); + messagesDiv.innerHTML = messages; + subscriptionsDiv.innerHTML = subscriptions; if (meta.messages > hotThreshold) { - nameCol.setAttribute('class', 'name hot'); - messagesCol.setAttribute('class', 'messages hot'); + nameDiv.setAttribute('class', 'name hot'); + messagesDiv.setAttribute('class', 'num messages hot'); } else if (meta.messages > fatThreshold) { - nameCol.setAttribute('class', 'name fat'); - messagesCol.setAttribute('class', 'messages fat'); + nameDiv.setAttribute('class', 'name fat'); + messagesDiv.setAttribute('class', 'num messages fat'); } else if (meta.messages === 0) { - messagesCol.setAttribute('class', 'messages zero'); + messagesDiv.setAttribute('class', 'num messages zero'); } else { - nameCol.setAttribute('class', 'name'); - messagesCol.setAttribute('class', 'messages'); + nameDiv.setAttribute('class', 'name'); + messagesDiv.setAttribute('class', 'num messages'); } if (meta.subscriptions === 0) { - subscriptionsCol.setAttribute('class', 'subscriptions zero'); + subscriptionsDiv.setAttribute('class', 'num subscriptions zero'); } else { - subscriptionsCol.setAttribute('class', 'subscriptions'); + subscriptionsDiv.setAttribute('class', 'num subscriptions'); } + + svg.setAttributeNS(null, 'viewbox', '0 0 300 '+ titleCol.offsetTop); + drawChart(svg, titleCol.offsetTop, meta.in_rate_history, meta.out_rate_history); } } +/* + * Charts + */ + +function drawChart(svg, maxHeight, valuesIn, valuesOut) { + var pathIn = svg.getElementsByClassName('in')[0], + pathOut = svg.getElementsByClassName('out')[0], + // valuesIn = generateValues(300), + // valuesOut = generateValues(300), + maxDouble = calcMaxDouble(valuesIn, valuesOut), + pointsIn = [], + pointsOut = []; + + for (var i = 0; i < valuesIn.length; i++) { + var normIn = Math.ceil(valuesIn[i] / maxDouble * maxHeight), + normOut = Math.ceil(valuesOut[i] / maxDouble * maxHeight), + pointIn = maxHeight/2 - normIn, + pointOut = maxHeight/2 + normOut; + + pointsIn.push(pointIn); + pointsOut.push(pointOut); + } + + pathIn.setAttributeNS(null, 'd', buildPathD(pointsIn, maxHeight)); + pathIn.setAttributeNS(null, 'class', 'in'); + pathOut.setAttributeNS(null, 'd', buildPathD(pointsOut, maxHeight)); + pathOut.setAttributeNS(null, 'class', 'out'); +} + +function generateValues(num) { + var values = []; + for (var i = 0; i < num; i++) { + var value = Math.ceil(Math.random() * 60) + 30; + values.push(value); + } + + return values; +} + +function calcMaxDouble(a, b) { + var doubleValue = 0; + for (var i = 0; i < a.length; i++) { + if (a[i] * 2 > doubleValue) { + doubleValue = a[i] * 2; + } + if (b[i] * 2 > doubleValue) { + doubleValue = b[i] * 2; + } + } + + return doubleValue * 1.2; +} + +function buildPathD(points, maxHeight) { + var d = ['M0,'+ maxHeight/2]; + for (var i = 0; i < points.length; i++) { + d.push('L'+ i +','+ points[i]); + } + d.push('L300,'+ maxHeight/2, 'Z'); + + return d.join(' '); +} + + /* + * Starting up + */ + function loop(timeout, func) { func(); window.setTimeout(function(){ diff --git a/server/static/dashboard.tmpl b/server/static/dashboard.tmpl index d57c118..cf01668 100644 --- a/server/static/dashboard.tmpl +++ b/server/static/dashboard.tmpl @@ -7,7 +7,7 @@ -

Burlesque v{{.version}} at {{.hostname}}

+

Burlesque v{{.version}} at {{.hostname}}