diff --git a/frontend/routes/index.js b/frontend/routes/index.js index 7992e8e..3e53789 100644 --- a/frontend/routes/index.js +++ b/frontend/routes/index.js @@ -8,7 +8,7 @@ module.exports = function (webinterface) { res.render('index', { title: app.title, - allViews: _.map(app.views, function (view) { return view.render(); }).join('') + allViews: _.map(app.views, function (view) { return view.getFullHtml(); }).join('') }); }); }; diff --git a/lib/View.js b/lib/View.js index 2aa18cb..cbf4305 100644 --- a/lib/View.js +++ b/lib/View.js @@ -1,45 +1,88 @@ 'use strict'; var _ = require('lodash'), - extendMixin = require('./extend'), + $ = require(typeof window !== 'undefined' ? 'jquery' : 'cheerio'), + Backbone = require('backbone'), Handlebars = require('./handlebars/handlebars'), View; -View = function (module) { - this.module = module; - if (this.template) { - this.template = this.getCompiledTemplate(this.template); - this.noDataTemplate = this.getCompiledTemplate('noData'); +Backbone.$ = $; + +View = Backbone.View.extend({ + tagName: 'section', + + constructor: function (module, options) { + this.module = module; + if (this.template) { + this.compiledTemplate = this.getCompiledTemplate(this.template); + this.noDataTemplate = this.getCompiledTemplate('noData'); + } + + Backbone.View.prototype.constructor.call(this, options); + }, + + className: function () { + var classNames = [ 'panel' ]; + + if (_.isEmpty(this.module.attributes)) { + classNames.push('panel-warning'); + classNames.push('noData'); + } else { + if (this.module.has('connected')) { + classNames.push(this.module.get('connected') ? 'panel-success' : 'panel-danger'); + } else { + classNames.push('panel-success'); + } + classNames.push(this.module.viewId); + } + + return classNames.join(' '); + }, + + attributes: function () { + return { + id: this.module.id + }; + }, + + getCompiledTemplate: function (templateName) { + return require('../build/compiledTemplates')(Handlebars)[templateName]; + }, + + render: function () { + this.updateHtml(); + }, + + updateHtml: function () { + var attrs = _.extend({}, _.result(this, 'attributes'), { 'class': _.result(this, 'className') }), + html = !_.isEmpty(this.module.attributes) ? this.renderViewWithData() : this.renderViewWithoutData(); + this.$el.attr(attrs).html(html); + }, + + getFullHtml: function () { + this.updateHtml(); + return $('
').append(this.$el).html(); + }, + + getViewData: function () { + return { json: JSON.stringify(this.module.attributes) }; + }, + + renderViewWithData: function () { + return this.compiledTemplate(_.extend({ + id: this.module.id, + title: this.module.title, + lastUpdated: new Date() + }, this.module.attributes, this.getViewData())); + }, + + renderViewWithoutData: function () { + return this.noDataTemplate({ + id: this.module.id, + title: this.module.title + }); } -}; -extendMixin(View); - -View.prototype.getCompiledTemplate = function (templateName) { - return require('../build/compiledTemplates')(Handlebars)[templateName]; -}; - -View.prototype.render = function () { - return !_.isEmpty(this.module.attributes) ? this.renderViewWithData() : this.renderViewWithoutData(); -}; - -View.prototype.getViewData = function () { - return { json: JSON.stringify(this.module.attributes) }; -}; - -View.prototype.renderViewWithData = function () { - return this.template(_.extend({ - id: this.module.id, - title: this.module.title, - lastUpdated: new Date() - }, this.module.attributes, this.getViewData())); -}; - -View.prototype.renderViewWithoutData = function () { - return this.noDataTemplate({ - id: this.module.id, - title: this.module.title - }); -}; +}); module.exports = View; diff --git a/package.json b/package.json index f6c5718..d69dc93 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,9 @@ }, "dependencies": { "async": "0.2.9", - "backbone": "1.1.0", + "backbone": "1.1.2", + "jquery": "2.1.0", + "cheerio": "0.13.1", "express": "3.4.0", "express3-handlebars": "0.5.0", "eventemitter2": "0.4.13", diff --git a/templates/json.hbs b/templates/json.hbs index b2d4abe..1e42af5 100644 --- a/templates/json.hbs +++ b/templates/json.hbs @@ -1,9 +1,7 @@ -
-
-

{{ title }}

-
-
- {{ json }} -
-
Updated: {{time lastUpdated}}
-
\ No newline at end of file +
+

{{ title }}

+
+
+ {{ json }} +
+ \ No newline at end of file diff --git a/templates/market.hbs b/templates/market.hbs index 7a11d79..337abf2 100644 --- a/templates/market.hbs +++ b/templates/market.hbs @@ -1,14 +1,12 @@ -
-
-

{{ title }}

-
-
-
Latest Trade:
-
{{number close}} {{currency}}
-
Best Bid:
-
{{number bid}} {{currency}}
-
Best Ask:
-
{{number ask}} {{currency}}
-
-
Updated: {{time lastUpdated}}
-
\ No newline at end of file +
+

{{ title }}

+
+
+
Latest Trade:
+
{{number close}} {{currency}}
+
Best Bid:
+
{{number bid}} {{currency}}
+
Best Ask:
+
{{number ask}} {{currency}}
+
+ \ No newline at end of file diff --git a/templates/miner.hbs b/templates/miner.hbs index 139ec86..5e22bed 100644 --- a/templates/miner.hbs +++ b/templates/miner.hbs @@ -1,90 +1,88 @@ -
-
+
+
+

{{ title }}

+
{{#if connected}}Connected{{else}}Disconnected{{/if}}
+
+
+{{#if connected}} +
+

Overview

-

{{ title }}

-
{{#if connected}}Connected{{else}}Disconnected{{/if}}
+
+
Hashrate:
+
{{hashrate avgHashrate}}
+
+
+
Hardware Errors:
+
{{number hardwareErrors}}
+
Hardware Error Rate:
+
{{number hardwareErrorRate precision="2"}}%
+
Uptime:
+
{{timespan elapsed}}
+
Software:
+
{{description}}
+
+
+
Accepted Shares:
+
{{number shares.accepted}}
+
Rejected Shares:
+
{{number shares.rejected}}
+
Stale Shares:
+
{{number shares.stale}}
+
Discarded Shares:
+
{{number shares.discarded}}
+
+
+
Difficulty Accepted:
+
{{number difficulty.accepted precision="2"}}
+
Difficulty Rejected:
+
{{number difficulty.rejected precision="2"}}
+
Difficulty Stale:
+
{{number difficulty.stale precision="2"}}
+
-
- {{#if connected}} -
-

Overview

-
-
-
Hashrate:
-
{{hashrate avgHashrate}}
-
-
-
Hardware Errors:
-
{{number hardwareErrors}}
-
Hardware Error Rate:
-
{{number hardwareErrorRate precision="2"}}%
-
Uptime:
-
{{timespan elapsed}}
-
Software:
-
{{description}}
-
-
-
Accepted Shares:
-
{{number shares.accepted}}
-
Rejected Shares:
-
{{number shares.rejected}}
-
Stale Shares:
-
{{number shares.stale}}
-
Discarded Shares:
-
{{number shares.discarded}}
-
-
-
Difficulty Accepted:
-
{{number difficulty.accepted precision="2"}}
-
Difficulty Rejected:
-
{{number difficulty.rejected precision="2"}}
-
Difficulty Stale:
-
{{number difficulty.stale precision="2"}}
-
+ {{#if pools}} +

Pools

+
+ + + + + + + + + + {{#each pools}} + + + + + + + + + {{/each}} +
PriorityUrlAliveActiveLast Share Submitted
Pool {{id}}{{priority}}{{url}}{{#if alive}}Yes{{else}}No{{/if}}{{#if active}}Yes{{else}}No{{/if}}{{#if lastShareTime}}{{time lastShareTime}}{{else}}Never{{/if}}
- {{#if pools}} -

Pools

-
- - - - - - - - - - {{#each pools}} - - - - - - - - - {{/each}} -
PriorityUrlAliveActiveLast Share Submitted
Pool {{id}}{{priority}}{{url}}{{#if alive}}Yes{{else}}No{{/if}}{{#if active}}Yes{{else}}No{{/if}}{{#if lastShareTime}}{{time lastShareTime}}{{else}}Never{{/if}}
-
- {{/if}} - {{#if devices}} -

Devices

-
- {{#each devices}} -
- - - - - - - -
Device {{id}}{{description}}{{hashrate avgHashrate}}{{number hardwareErrorRate precision="2"}}% Hardware Error Rate
-
- {{/each}} -
- {{/if}} -
-
Updated: {{time lastUpdated}}
- {{/if}} -
\ No newline at end of file + {{/if}} + {{#if devices}} +

Devices

+
+ {{#each devices}} +
+ + + + + + + +
Device {{id}}{{description}}{{hashrate avgHashrate}}{{number hardwareErrorRate precision="2"}}% Hardware Error Rate
+
+ {{/each}} +
+ {{/if}} +
+ +{{/if}} \ No newline at end of file diff --git a/templates/noData.hbs b/templates/noData.hbs index 308418b..6bd2663 100644 --- a/templates/noData.hbs +++ b/templates/noData.hbs @@ -1,8 +1,6 @@ -
-
-
-

{{ title }}

-
No Data Yet.
-
-
-
\ No newline at end of file +
+
+

{{ title }}

+
No Data Yet.
+
+
\ No newline at end of file diff --git a/templates/revenue.hbs b/templates/revenue.hbs index 6ace948..f17f477 100644 --- a/templates/revenue.hbs +++ b/templates/revenue.hbs @@ -1,12 +1,10 @@ -
-
-
-

{{ title }}

-
-
-
-
Revenue:
-
{{number value}} {{currency}}/{{interval}}
-
- -
\ No newline at end of file +
+
+

{{ title }}

+
+
+
+
Revenue:
+
{{number value}} {{currency}}/{{interval}}
+
+ \ No newline at end of file diff --git a/templates/technical.hbs b/templates/technical.hbs index e721bb8..f46e53f 100644 --- a/templates/technical.hbs +++ b/templates/technical.hbs @@ -1,28 +1,26 @@ -
-
-

{{ title }}

-
-
-
-
Total Hashrate:
-
{{hashrate hash_rate}}
-
Difficulty:
-
{{number difficulty precision="2"}}
-
-
-
Blocks Mined:
-
{{number n_blocks_mined}}
-
Time between Blocks:
-
{{number minutes_between_blocks precision="2"}} Minutes
-
-
-
Number of Transactions:
-
{{number n_tx}}
-
Total Output Volume:
-
{{number total_btc_sent}} BTC
-
Estimated Transaction Volume:
-
{{number estimated_btc_sent precision="2"}} BTC
-
-
- -
\ No newline at end of file +
+

{{ title }}

+
+
+
+
Total Hashrate:
+
{{hashrate hash_rate}}
+
Difficulty:
+
{{number difficulty precision="2"}}
+
+
+
Blocks Mined:
+
{{number n_blocks_mined}}
+
Time between Blocks:
+
{{number minutes_between_blocks precision="2"}} Minutes
+
+
+
Number of Transactions:
+
{{number n_tx}}
+
Total Output Volume:
+
{{number total_btc_sent}} BTC
+
Estimated Transaction Volume:
+
{{number estimated_btc_sent precision="2"}} BTC
+
+
+ \ No newline at end of file diff --git a/test/specs/lib/App.js b/test/specs/lib/App.js index ee937c3..b8d195c 100644 --- a/test/specs/lib/App.js +++ b/test/specs/lib/App.js @@ -42,6 +42,7 @@ describe('App', function () { }, app; + module.id = moduleConfig.id; constructorStub.prototype.viewId = 'json'; app = new App({ modules: [moduleConfig] diff --git a/test/specs/lib/View.js b/test/specs/lib/View.js index 0094f7f..bc5dd54 100644 --- a/test/specs/lib/View.js +++ b/test/specs/lib/View.js @@ -34,11 +34,50 @@ describe('View', function () { view = new View(module); expect(view.module).to.equal(module); - expect(view.template).to.equal(jsonTemplate); + expect(view.compiledTemplate).to.equal(jsonTemplate); expect(view.noDataTemplate).to.equal(noDataTemplate); }); }); + describe('className', function () { + var module, + view; + + beforeEach(function () { + module = { + attributes: {}, + has: sinon.stub(), + get: sinon.stub(), + viewId: 'someView' + }; + view = new View(module); + }); + + it('should add panel-warning and noData classes when no data has been fetched yet', function () { + expect(view.className()).to.equal('panel panel-warning noData'); + }); + + it('should add panel-success and viewId classes if there is no connection info for the module', function () { + module.attributes.foo = 'bar'; + module.has.withArgs('connected').returns(false); + expect(view.className()).to.equal('panel panel-success someView'); + }); + + it('should add panel-success and viewId classes if there is connection info and the connection is ok', function () { + module.attributes.foo = 'bar'; + module.has.withArgs('connected').returns(true); + module.get.withArgs('connected').returns(true); + expect(view.className()).to.equal('panel panel-success someView'); + }); + + it('should add panel-danger and viewId classes if there is connection info and the module is disconnected', function () { + module.attributes.foo = 'bar'; + module.has.withArgs('connected').returns(true); + module.get.withArgs('connected').returns(false); + expect(view.className()).to.equal('panel panel-danger someView'); + }); + }); + describe('renderViewWithoutData', function () { it('should render the default no-data view', function () { var module = { @@ -66,16 +105,17 @@ describe('View', function () { id: 'foo', template: 'json', title: 'bar', - attributes: { some: 'json', data: 'of', the: 'module' } + attributes: { some: 'json', data: 'of', the: 'module' }, + has: sinon.stub().returns(false) }, view = new View(module); - view.template = sinon.stub(); + view.compiledTemplate = sinon.stub(); view.renderViewWithData(); - expect(view.template).to.have.been.calledOnce; - expect(view.template).to.have.been.calledWithMatch({ + expect(view.compiledTemplate).to.have.been.calledOnce; + expect(view.compiledTemplate).to.have.been.calledWithMatch({ id: module.id, title: module.title, json: JSON.stringify(module.attributes),