diff --git a/config.json b/config.json index 2baa2eb..40f2c9f 100644 --- a/config.json +++ b/config.json @@ -17,9 +17,9 @@ "psubscribeKey" : "newblocks:*" }, "website": { - "enabled": false, + "enabled": true, "port": 80, - "statUpdateInterval": 3 + "statUpdateInterval": 5 }, "proxy": { "enabled": false, diff --git a/libs/api.js b/libs/api.js index 7dc8bab..0a7e335 100644 --- a/libs/api.js +++ b/libs/api.js @@ -1,9 +1,13 @@ var redis = require('redis'); +var async = require('async'); + var os = require('os'); module.exports = function(logger, poolConfigs){ + var _this = this; + var redisClients = []; Object.keys(poolConfigs).forEach(function(coin){ @@ -41,11 +45,62 @@ module.exports = function(logger, poolConfigs){ clearExpiredHashrates(); - + this.stats = {}; this.getStats = function(callback){ + async.map(redisClients, function(client, callback){ + var tenMinutesAgo = (Date.now() / 1000 | 0) - (60 * 10); + var redisCommands = client.coins.map(function(coin){ + return ['zrangebyscore', coin + '_hashrate', tenMinutesAgo, '+inf']; + }); + client.client.multi(redisCommands).exec(function(err, replies){ + if (err){ + console.log('error with getting hashrate stats ' + JSON.stringify(err)); + callback(err); + } + else{ + var replyObj = {}; + for(var i = 0; i < replies.length; i++){ + replyObj[client.coins[i]] = replies[i]; + } + callback(null, replyObj); + } + }); + }, function(err, results){ + + var portalStats = { + global:{ + workers: 0, + shares: 0 + }, + pools:{ + + } + }; + + results.forEach(function(r){ + var coin = Object.keys(r)[0]; + var coinStats = {workers: {}, shares: 0}; + r[coin].forEach(function(ins){ + var parts = ins.split(':'); + var workerShares = parseInt(parts[0]);; + coinStats.shares += workerShares + var worker = parts[1]; + if (worker in coinStats.workers) + coinStats.workers[worker] += workerShares + else + coinStats.workers[worker] = workerShares + }); + portalStats.pools[coin] = coinStats; + portalStats.global.shares += coinStats.shares; + portalStats.global.workers += Object.keys(coinStats.workers).length; + }); + + _this.stats = portalStats; + callback(); + }); /* { global: { diff --git a/libs/paymentProcessor.js b/libs/paymentProcessor.js index d78588f..3e3432f 100644 --- a/libs/paymentProcessor.js +++ b/libs/paymentProcessor.js @@ -167,15 +167,13 @@ function SetupForPool(logger, poolOptions){ return; } - - for (var i = txDetails.length; i > 0; --i){ - var tx = txDetails[i]; + txDetails = txDetails.filter(function(tx){ if (tx.error || !tx.result){ console.log('error with requesting transaction from block daemon: ' + JSON.stringify(t)); - txDetails.splice(i, 1); + return false; } - } - + return true; + }); var orphanedRounds = []; var confirmedRounds = []; @@ -292,7 +290,7 @@ function SetupForPool(logger, poolOptions){ redisClient.hmget([coin + '_balances'].concat(workers), function(error, results){ if (error){ - callback('done - redis error with multi get rounds share') + callback('done - redis error with multi get balances') return; } diff --git a/libs/website.js b/libs/website.js index 17031ff..6292a8e 100644 --- a/libs/website.js +++ b/libs/website.js @@ -39,6 +39,7 @@ module.exports = function(logger){ var portalConfig = JSON.parse(process.env.portalConfig); var poolConfigs = JSON.parse(process.env.pools); + var websiteConfig = portalConfig.website; var portalApi = new api(logger, poolConfigs); @@ -56,92 +57,116 @@ module.exports = function(logger){ } }; - var pageResources = ''; - var pageTemplate; - var pageProcessed = ''; - var loadWebPage = function(callback){ - fs.readdir('website', function(err, files){ - async.map(files, function(fileName, callback){ - var filePath = 'website/' + fileName; - fs.readFile(filePath, 'utf8', function(err, data){ - callback(null, {name: fileName, data: data, ext: path.extname(filePath)}); - }); - }, function(err, fileObjects){ + var pageFiles = { + 'index.html': 'index', + 'home.html': '', + 'getting_started.html': 'getting_started', + 'stats.html': 'stats', + 'api.html': 'api' + }; - var indexPage = fileObjects.filter(function(f){ - return f.name === 'index.html'; - })[0].data; + var pageTemplates = {}; - var jsCode = ''; - cssCode += ''; - - var bodyIndex = indexPage.indexOf('
'); - pageTemplate = dot.template(indexPage.slice(bodyIndex)); + var pageProcessed = {}; - pageResources = indexPage.slice(0, bodyIndex); - var headIndex = pageResources.indexOf(''); - pageResources = pageResources.slice(0, headIndex) + - jsCode + '\n\n\n\n' + - cssCode + '\n\n\n\n' + - pageResources.slice(headIndex); + var processTemplates = function(){ + for (var pageName in pageTemplates){ + pageProcessed[pageName] = pageTemplates[pageName]({ + poolsConfigs: poolConfigs, + stats: portalApi.stats + }); + } + }; - applyTemplateInfo(); - callback || function(){}(); - }) + + var readPageFiles = function(){ + async.each(Object.keys(pageFiles), function(fileName, callback){ + fs.readFile('website/' + fileName, 'utf8', function(err, data){ + var pTemp = dot.template(data); + pageTemplates[pageFiles[fileName]] = pTemp + callback(); + }); + }, function(err){ + if (err){ + console.log('error reading files for creating dot templates: '+ JSON.stringify(err)); + return; + } + processTemplates(); }); }; - loadWebPage(); - - var applyTemplateInfo = function(){ - - portalApi.getStats(function(stats){ - - //need to give template info about pools and stats - - pageProcessed = pageTemplate({test: 'visitor', time: Date.now()}); - }); - }; - - setInterval(function(){ - applyTemplateInfo(); - }, 5000); - var reloadTimeout; - fs.watch('website', function(){ - clearTimeout(reloadTimeout); - reloadTimeout = setTimeout(function(){ - loadWebPage(); - }, 500); + + fs.watch('website', function(event, filename){ + if (event === 'change' && filename in pageFiles) + readPageFiles(); + }); + portalApi.getStats(function(){ + readPageFiles(Object.keys(pageFiles)); + }); + + var buildUpdatedWebsite = function(){ + portalApi.getStats(function(){ + processTemplates(); + }); + }; + + setInterval(buildUpdatedWebsite, websiteConfig.statUpdateInterval * 1000); + var app = express(); - //need to create a stats api endpoint for eventsource live stat updates which are triggered on the applytemplateinfo interval + var getPage = function(pageId){ + if (pageId in pageProcessed){ + var requestedPage = pageProcessed[pageId]; + return requestedPage; + } + }; + var route = function(req, res, next){ + var pageId = req.params.page || ''; + var requestedPage = getPage(pageId); + if (requestedPage){ + var data = pageTemplates.index({ + siteTitle: websiteConfig.siteTitle, + page: requestedPage, + selected: pageId, + stats: portalApi.stats, + poolConfigs: poolConfigs + }); + res.send(data); + } + else + next(); + }; + app.get('/:page', route); + app.get('/', route); - app.get('/', function(req, res){ - res.send(pageResources + pageProcessed); + app.get('/api/:method', function(req, res, next){ + + switch(req.params.method){ + case 'get_page': + var requestedPage = getPage(req.query.id); + if (requestedPage){ + res.send(requestedPage); + return; + } + default: + next(); + } + + res.send('you did api method ' + req.params.method); }); + app.use('/static', express.static('website')); + app.use(function(err, req, res, next){ console.error(err.stack); res.send(500, 'Something broke!'); diff --git a/website/Chart.min.js b/website/Chart.min.js deleted file mode 100644 index ab63588..0000000 --- a/website/Chart.min.js +++ /dev/null @@ -1,39 +0,0 @@ -var Chart=function(s){function v(a,c,b){a=A((a-c.graphMin)/(c.steps*c.stepValue),1,0);return b*c.steps*a}function x(a,c,b,e){function h(){g+=f;var k=a.animation?A(d(g),null,0):1;e.clearRect(0,0,q,u);a.scaleOverlay?(b(k),c()):(c(),b(k));if(1>=g)D(h);else if("function"==typeof a.onAnimationComplete)a.onAnimationComplete()}var f=a.animation?1/A(a.animationSteps,Number.MAX_VALUE,1):1,d=B[a.animationEasing],g=a.animation?0:1;"function"!==typeof c&&(c=function(){});D(h)}function C(a,c,b,e,h,f){var d;a= -Math.floor(Math.log(e-h)/Math.LN10);h=Math.floor(h/(1*Math.pow(10,a)))*Math.pow(10,a);e=Math.ceil(e/(1*Math.pow(10,a)))*Math.pow(10,a)-h;a=Math.pow(10,a);for(d=Math.round(e/a);dc;)a=dc?c:!isNaN(parseFloat(b))&& -isFinite(b)&&a)[^\t]*)'/g,"$1\r").replace(/\t=(.*?)%>/g,"',$1,'").split("\t").join("');").split("%>").join("p.push('").split("\r").join("\\'")+"');}return p.join('');");return c? -b(c):b}var r=this,B={linear:function(a){return a},easeInQuad:function(a){return a*a},easeOutQuad:function(a){return-1*a*(a-2)},easeInOutQuad:function(a){return 1>(a/=0.5)?0.5*a*a:-0.5*(--a*(a-2)-1)},easeInCubic:function(a){return a*a*a},easeOutCubic:function(a){return 1*((a=a/1-1)*a*a+1)},easeInOutCubic:function(a){return 1>(a/=0.5)?0.5*a*a*a:0.5*((a-=2)*a*a+2)},easeInQuart:function(a){return a*a*a*a},easeOutQuart:function(a){return-1*((a=a/1-1)*a*a*a-1)},easeInOutQuart:function(a){return 1>(a/=0.5)? -0.5*a*a*a*a:-0.5*((a-=2)*a*a*a-2)},easeInQuint:function(a){return 1*(a/=1)*a*a*a*a},easeOutQuint:function(a){return 1*((a=a/1-1)*a*a*a*a+1)},easeInOutQuint:function(a){return 1>(a/=0.5)?0.5*a*a*a*a*a:0.5*((a-=2)*a*a*a*a+2)},easeInSine:function(a){return-1*Math.cos(a/1*(Math.PI/2))+1},easeOutSine:function(a){return 1*Math.sin(a/1*(Math.PI/2))},easeInOutSine:function(a){return-0.5*(Math.cos(Math.PI*a/1)-1)},easeInExpo:function(a){return 0==a?1:1*Math.pow(2,10*(a/1-1))},easeOutExpo:function(a){return 1== -a?1:1*(-Math.pow(2,-10*a/1)+1)},easeInOutExpo:function(a){return 0==a?0:1==a?1:1>(a/=0.5)?0.5*Math.pow(2,10*(a-1)):0.5*(-Math.pow(2,-10*--a)+2)},easeInCirc:function(a){return 1<=a?a:-1*(Math.sqrt(1-(a/=1)*a)-1)},easeOutCirc:function(a){return 1*Math.sqrt(1-(a=a/1-1)*a)},easeInOutCirc:function(a){return 1>(a/=0.5)?-0.5*(Math.sqrt(1-a*a)-1):0.5*(Math.sqrt(1-(a-=2)*a)+1)},easeInElastic:function(a){var c=1.70158,b=0,e=1;if(0==a)return 0;if(1==(a/=1))return 1;b||(b=0.3);e