diff --git a/server/server.go b/server/server.go
index 365adfe..3f2130d 100644
--- a/server/server.go
+++ b/server/server.go
@@ -154,5 +154,6 @@ func (s *Server) dashboardHandler(w http.ResponseWriter, r *http.Request) {
tmpl.ExecuteTemplate(w, "dashboard", map[string]interface{}{
"version": Version,
"hostname": hostname,
+ "port": s.port,
})
}
diff --git a/server/static/app.css b/server/static/app.css
index 6e2020b..37fc7a5 100644
--- a/server/static/app.css
+++ b/server/static/app.css
@@ -4,7 +4,7 @@
font-weight: 300;
}
-.heading, table {
+.heading, #dashboard {
position: absolute;
left: 50%;
width: 650px;
@@ -15,8 +15,11 @@
font-size: 1.8em;
text-align: center;
}
-table {
+#dashboard {
top: 100px;
+}
+table {
+ width: 100%;
border-collapse: collapse;
border-spacing: 0;
}
@@ -31,9 +34,6 @@ th {
thead tr {
border-bottom: #666 1px solid;
}
-/*tbody tr:nth-child(even) {
- background-color: #f5f5f5;
-}*/
.title {
position: relative;
width: 350px;
@@ -69,15 +69,7 @@ thead tr {
font-weight: 600;
color: #f20;
}
-#loading {
- display: none;
- position: absolute;
- bottom: 10px;
- right: 10px;
- font-size: 0.5em;
- width: auto;
-}
-#placeholder td {
+.placeholder {
text-align: center;
}
diff --git a/server/static/app.js b/server/static/app.js
deleted file mode 100644
index 193bb76..0000000
--- a/server/static/app.js
+++ /dev/null
@@ -1,191 +0,0 @@
-/*
- * Dashboard
- */
-
-function loadStatus(callback) {
- var xhr = new XMLHttpRequest(),
- loading = document.getElementById('loading');
-
- loading.setAttribute('style', 'display: block;');
- xhr.open('GET', '/status?rates=please', true);
- xhr.onreadystatechange = function() {
- if (xhr.readyState === 4) {
- if (xhr.status === 200) {
- var queues = JSON.parse(xhr.responseText);
- loading.setAttribute('style', 'display: none;');
- callback(queues);
- }
- }
- };
- xhr.send(null);
-}
-
-function updateDashboard(queues) {
- var queuesList = document.getElementById('queues'),
- placeholder = document.getElementById('placeholder'),
- fatThreshold = 100,
- hotThreshold = 1000;
-
- if (Object.keys(queues).length === 0) {
- var td = placeholder.getElementsByTagName('td')[0];
- td.innerHTML = 'Empty';
- } else if (placeholder) {
- queuesList.removeChild(placeholder);
- }
-
- for (queue in queues) {
- var meta = queues[queue],
- id = 'queue_' + queue,
- tr = document.getElementById(id);
-
- if (!tr) {
- tr = document.createElement('tr');
- tr.setAttribute('id', id);
-
- 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 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();
-
- messagesDiv.innerHTML = messages;
- subscriptionsDiv.innerHTML = subscriptions;
-
- if (meta.messages > hotThreshold) {
- nameDiv.setAttribute('class', 'name hot');
- messagesDiv.setAttribute('class', 'num messages hot');
- } else if (meta.messages > fatThreshold) {
- nameDiv.setAttribute('class', 'name fat');
- messagesDiv.setAttribute('class', 'num messages fat');
- } else if (meta.messages === 0) {
- messagesDiv.setAttribute('class', 'num messages zero');
- } else {
- nameDiv.setAttribute('class', 'name');
- messagesDiv.setAttribute('class', 'num messages');
- }
-
- if (meta.subscriptions === 0) {
- subscriptionsDiv.setAttribute('class', 'num subscriptions zero');
- } else {
- 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(){
- loop(timeout, func);
- }, timeout);
-}
-
-loop(1000, function(){
- loadStatus(updateDashboard);
-});
diff --git a/server/static/app.jsx b/server/static/app.jsx
new file mode 100644
index 0000000..b685e25
--- /dev/null
+++ b/server/static/app.jsx
@@ -0,0 +1,193 @@
+var Chart = React.createClass({
+ getInitialState: function() {
+ return {pointsIn: [], pointsOut: []};
+ },
+
+ componentDidMount: function() {
+ this.buildPoints(this.props);
+ },
+
+ componentWillReceiveProps: function(nextProps) {
+ this.buildPoints(nextProps);
+ },
+
+ buildPoints: function(props) {
+ var maxDouble = this.calcMaxDouble(props.valuesIn, props.valuesOut) || 1,
+ pointsIn = [],
+ pointsOut = [];
+
+ for (var i = 0; i < props.valuesIn.length; i++) {
+ var normIn = Math.ceil(props.valuesIn[i] / maxDouble * props.height),
+ normOut = Math.ceil(props.valuesOut[i] / maxDouble * props.height),
+ pointIn = props.height/2 - normIn,
+ pointOut = props.height/2 + normOut;
+
+ pointsIn.push(pointIn);
+ pointsOut.push(pointOut);
+ }
+
+ this.setState({pointsIn: pointsIn, pointsOut: pointsOut});
+ },
+
+ calcMaxDouble: function(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;
+ },
+
+ buildPathD: function(points) {
+ var d = ['M0,'+ this.props.height/2],
+ missing = this.props.width - points.length;
+
+ for (var i = 0; i < missing; i++) {
+ d.push('L'+ i +','+ this.props.height/2);
+ }
+ for (var i = 0; i < points.length; i++) {
+ d.push('L'+ missing+i +','+ points[i]);
+ }
+ d.push('L'+ this.props.width +','+ this.props.height/2, 'Z');
+
+ return d.join(' ');
+ },
+
+ render: function() {
+ var viewBox = [0, 0, this.props.width, this.props.height].join(' ');
+ return (
+
+ );
+ }
+});
+
+var QueuesList = React.createClass({
+ render: function(){
+ if (!this.props.isDataRecieved) {
+ return (
+
+
+ Loading... |
+
+
+ )
+ }
+
+ if (Object.keys(this.props.queues) === 0) {
+ return (
+
+
+ This server has no queues |
+
+
+ )
+ }
+
+ var queues = this.props.queues;
+ var createQueue = function(name) {
+ var meta = queues[name],
+ titleClasses = ['title'],
+ messagesClasses = ['messages'],
+ subscriptionsClasses = ['subscriptions'];
+
+ if (meta.messages > 1000) {
+ titleClasses.push('hot');
+ messagesClasses.push('hot');
+ } else if (meta.messages > 100) {
+ titleClasses.push('fat');
+ messagesClasses.push('fat');
+ } else if (meta.messages === 0) {
+ messagesClasses.push('zero');
+ }
+ if (meta.subscriptions === 0) {
+ subscriptionsClasses.push('zero');
+ }
+ return (
+
+
+ {name}
+
+ |
+
+ {meta.messages}
+ |
+
+ {meta.subscriptions}
+ |
+
+ );
+ };
+
+ return (
+
+ {Object.keys(queues).map(createQueue)}
+
+ );
+ }
+});
+
+var Dashboard = React.createClass({
+ getInitialState: function() {
+ return {queues: {}, isDataRecieved: false};
+ },
+
+ componentDidMount: function() {
+ this.loop(this.props.interval, this.refresh);
+ },
+
+ componentWillUnmount: function() {
+ clearTimeout(this.timeout);
+ },
+
+ loop: function (timeout, func) {
+ var loop = this.loop;
+ func();
+ this.timeout = setTimeout(function(){
+ loop(timeout, func);
+ }, timeout);
+ },
+
+ refresh: function () {
+ var xhr = new XMLHttpRequest()
+ self = this;
+ xhr.open('GET', '/status?rates=please', true);
+ xhr.onreadystatechange = function() {
+ if (xhr.readyState === 4) {
+ if (xhr.status === 200) {
+ self.setState({
+ queues: JSON.parse(xhr.responseText),
+ isDataRecieved: true
+ });
+ }
+ }
+ };
+ xhr.send(null);
+ },
+
+ render: function() {
+ return (
+
+
+
+ Queue |
+ Messages |
+ Subscriptions |
+
+
+
+
+ );
+ }
+});
diff --git a/server/static/dashboard.tmpl b/server/static/dashboard.tmpl
index cf01668..d9ed1d0 100644
--- a/server/static/dashboard.tmpl
+++ b/server/static/dashboard.tmpl
@@ -4,26 +4,20 @@
Queues @ {{.hostname}}
-
+
+
+
Burlesque v{{.version}} at {{.hostname}}
-
-
-
- Queue |
- Messages |
- Subscriptions |
-
-
-
-
- Loading queues... |
-
-
- Loading...
-
-
+
+
+