From 649b527a8f9faeb3e50463770057975ad523b73a Mon Sep 17 00:00:00 2001 From: Sebastian Grewe Date: Wed, 15 May 2013 01:07:59 +0200 Subject: [PATCH 01/15] further moved stats from user into statistics class, added more caching --- public/include/classes/statistics.class.php | 25 +++++++++++++++++ public/include/classes/user.class.php | 16 +---------- public/include/smarty_globals.inc.php | 31 +++++++++++++++------ public/templates/mmcFE/global/sidebar.tpl | 8 +++--- 4 files changed, 52 insertions(+), 28 deletions(-) diff --git a/public/include/classes/statistics.class.php b/public/include/classes/statistics.class.php index d409c131..aa08f014 100644 --- a/public/include/classes/statistics.class.php +++ b/public/include/classes/statistics.class.php @@ -87,6 +87,31 @@ class Statistics { return false; } + public function getUserShares($account_id) { + $stmt = $this->mysqli->prepare(" + SELECT + ( + SELECT COUNT(s.id) + FROM " . $this->share->getTableName() . " AS s, " . $this->user->getTableName() . " AS u + WHERE u.username = SUBSTRING_INDEX( s.username, '.', 1 ) + AND UNIX_TIMESTAMP(s.time) >IFNULL((SELECT MAX(b.time) FROM blocks AS b),0) + AND our_result = 'Y' + AND u.id = ? + ) AS valid, + ( + SELECT COUNT(s.id) + FROM " . $this->share->getTableName() . " AS s, " . $this->user->getTableName() . " AS u + WHERE u.username = SUBSTRING_INDEX( s.username, '.', 1 ) + AND UNIX_TIMESTAMP(s.time) >IFNULL((SELECT MAX(b.time) FROM blocks AS b),0) + AND our_result = 'N' + AND u.id = ? + ) AS invalid"); + if ($stmt && $stmt->bind_param("ii", $account_id, $account_id) && $stmt->execute() && $result = $stmt->get_result()) return $result->fetch_assoc(); + // Catchall + $this->debug->append("Unable to fetch user round shares: " . $this->mysqli->error); + return false; + } + public function getUserHashrate($account_id) { $stmt = $this->mysqli->prepare(" SELECT ROUND(COUNT(s.id) * POW(2,21)/600/1000) AS hashrate diff --git a/public/include/classes/user.class.php b/public/include/classes/user.class.php index fe358314..11f12403 100644 --- a/public/include/classes/user.class.php +++ b/public/include/classes/user.class.php @@ -171,21 +171,7 @@ class User { $stmt = $this->mysqli->prepare(" SELECT id, username, pin, pass, admin, - IFNULL(donate_percent, '0') as donate_percent, coin_address, ap_threshold, - ( - SELECT COUNT(id) - FROM shares - WHERE $this->table.username = SUBSTRING_INDEX( `username` , '.', 1 ) - AND UNIX_TIMESTAMP(time) >IFNULL((SELECT MAX(time) FROM blocks),0) - AND our_result = 'Y' - ) AS valid, - ( - SELECT COUNT(id) - FROM shares - WHERE $this->table.username = SUBSTRING_INDEX( `username` , '.', 1 ) - AND UNIX_TIMESTAMP(time) >IFNULL((SELECT MAX(time) FROM blocks),0) - AND our_result = 'N' - ) AS invalid + IFNULL(donate_percent, '0') as donate_percent, coin_address, ap_threshold FROM $this->table WHERE id = ? LIMIT 0,1"); echo $this->mysqli->error; diff --git a/public/include/smarty_globals.inc.php b/public/include/smarty_globals.inc.php index 218e6af4..29db7683 100644 --- a/public/include/smarty_globals.inc.php +++ b/public/include/smarty_globals.inc.php @@ -9,27 +9,31 @@ $debug->append('Global smarty variables', 3); // Store some stuff in memcache prior to assigning it to Smarty if (!$aRoundShares = $memcache->get('aRoundShares')) { - $debug->append('Fetching aRoundShares from database'); + $debug->append('STA Fetching aRoundShares from database'); $aRoundShares = $statistics->getRoundShares(); - $memcache->set('aRoundShares', $aRoundShares, 60); + $debug->append('END Fetching aRoundShares from database'); + $memcache->set('aRoundShares', $aRoundShares, 90); } if (!$iCurrentActiveWorkers = $memcache->get('iCurrentActiveWorkers')) { - $debug->append('Fetching iCurrentActiveWorkers from database'); + $debug->append('STA Fetching iCurrentActiveWorkers from database'); $iCurrentActiveWorkers = $worker->getCountAllActiveWorkers(); - $memcache->set('iCurrentActiveWorkers', $iCurrentActiveWorkers, 60); + $debug->append('END Fetching iCurrentActiveWorkers from database'); + $memcache->set('iCurrentActiveWorkers', $iCurrentActiveWorkers, 80); } if (!$iCurrentPoolHashrate = $memcache->get('iCurrentPoolHashrate')) { - $debug->append('Fetching iCurrentPoolHashrate from database'); + $debug->append('STA Fetching iCurrentPoolHashrate from database'); $iCurrentPoolHashrate = $statistics->getCurrentHashrate(); - $memcache->set('iCurrentPoolHashrate', $iCurrentPoolHashrate, 60); + $debug->append('END Fetching iCurrentPoolHashrate from database'); + $memcache->set('iCurrentPoolHashrate', $iCurrentPoolHashrate, 90); } if (!$iCurrentPoolShareRate = $memcache->get('iCurrentPoolShareRate')) { - $debug->append('Fetching iCurrentPoolShareRate from database'); + $debug->append('STA Fetching iCurrentPoolShareRate from database'); $iCurrentPoolShareRate = $statistics->getCurrentShareRate(); - $memcache->set('iCurrentPoolShareRate', $iCurrentPoolShareRate, 60); + $debug->append('END Fetching iCurrentPoolShareRate from database'); + $memcache->set('iCurrentPoolShareRate', $iCurrentPoolShareRate, 90); } $aGlobal = array( @@ -53,9 +57,18 @@ $aGlobal['userdata'] = $_SESSION['USERDATA']['id'] ? $user->getUserData($_SESSIO $aGlobal['userdata']['balance'] = $transaction->getBalance($_SESSION['USERDATA']['id']); // Other userdata that we can cache savely +if (!$aGlobal['userdata']['shares'] = $memcache->get('global_' . $_SESSION['USERDATA']['id'] . '_shares')) { + $debug->append('STA Loading user shares from database'); + $aGlobal['userdata']['shares'] = $statistics->getUserShares($_SESSION['USERDATA']['id']); + $debug->append('END Loading user shares from database'); + $memcache->set('global_' . $_SESSION['USERDATA']['id'] . '_shares', $aGlobal['userdata']['shares'], 80); +} + if (!$aGlobal['userdata']['hashrate'] = $memcache->get('global_' . $_SESSION['USERDATA']['id'] . '_hashrate') ) { + $debug->append('STA Loading user hashrate from database'); $aGlobal['userdata']['hashrate'] = $statistics->getUserHashrate($_SESSION['USERDATA']['id']); - $memcache->set('global_' . $_SESSION['USERDATA']['id'] . '_hashrate', $aGlobal['userdata']['hashrate'], 60); + $debug->append('END Loading user hashrate from database'); + $memcache->set('global_' . $_SESSION['USERDATA']['id'] . '_hashrate', $aGlobal['userdata']['hashrate'], 70); } // Make it available in Smarty diff --git a/public/templates/mmcFE/global/sidebar.tpl b/public/templates/mmcFE/global/sidebar.tpl index 269f7dbc..fbe95818 100644 --- a/public/templates/mmcFE/global/sidebar.tpl +++ b/public/templates/mmcFE/global/sidebar.tpl @@ -9,15 +9,15 @@ Your Current Hashrate
{$GLOBAL.userdata.hashrate} KH/s

Unpaid Shares
- Your Valid: {$GLOBAL.userdata.valid}
+ Your Valid: {$GLOBAL.userdata.shares.valid}
Pool Valid: {$GLOBAL.roundshares.valid}

Round Shares
Pool Valid: {$GLOBAL.roundshares.valid}
Pool Inalid: {$GLOBAL.roundshares.invalid}
- Your Invalid: {$GLOBAL.userdata.invalid}

+ Your Invalid: {$GLOBAL.userdata.shares.invalid}

Round Estimate
- {math equation="round(( x / y ) * z, 8)" x=$GLOBAL.userdata.valid y=$GLOBAL.roundshares.valid z=$GLOBAL.reward} LTC

- Account Balance
{$GLOBAL.userdata.balance} LTC

+ {math equation="round(( x / y ) * z, 8)" x=$GLOBAL.userdata.shares.valid y=$GLOBAL.roundshares.valid z=$GLOBAL.reward} LTC

+ Account Balance
{$GLOBAL.userdata.balance|default:"0"} LTC


From 884a2028427bd80d88a8661682eea132767748b8 Mon Sep 17 00:00:00 2001 From: Sebastian Grewe Date: Wed, 15 May 2013 13:26:53 +0200 Subject: [PATCH 02/15] replaced fixed targetdiff with setting from configuration in user hash rate calculation --- public/include/classes/statistics.class.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/include/classes/statistics.class.php b/public/include/classes/statistics.class.php index aa08f014..0161c139 100644 --- a/public/include/classes/statistics.class.php +++ b/public/include/classes/statistics.class.php @@ -114,7 +114,7 @@ class Statistics { public function getUserHashrate($account_id) { $stmt = $this->mysqli->prepare(" - SELECT ROUND(COUNT(s.id) * POW(2,21)/600/1000) AS hashrate + SELECT ROUND(COUNT(s.id) * POW(2, " . $this->config['difficulty'] . ")/600/1000) AS hashrate FROM " . $this->share->getTableName() . " AS s, " . $this->user->getTableName() . " AS u WHERE u.username = SUBSTRING_INDEX( s.username, '.', 1 ) From 1a2c357b5916de0e7f2b05b4a032d360543e504a Mon Sep 17 00:00:00 2001 From: Sebastian Grewe Date: Wed, 15 May 2013 16:52:03 +0200 Subject: [PATCH 03/15] removed admin link for now --- public/templates/mmcFE/global/navigation.tpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/templates/mmcFE/global/navigation.tpl b/public/templates/mmcFE/global/navigation.tpl index 3267f0db..83400cae 100644 --- a/public/templates/mmcFE/global/navigation.tpl +++ b/public/templates/mmcFE/global/navigation.tpl @@ -10,7 +10,7 @@ {/if} - {if $smarty.session.AUTHENTICATED == 1 && $GLOBAL.userdata.admin == 1}
  • Admin Panel
  • {/if} + {if $smarty.session.AUTHENTICATED == 1 && $GLOBAL.userdata.admin == 1}
  • Admin Panel
  • {/if}
  • Statistics
    • Pool Stats
    • From ec3d6d7cbd898602704109ee9337d9e054c79c29 Mon Sep 17 00:00:00 2001 From: Sebastian Grewe Date: Wed, 15 May 2013 16:55:29 +0200 Subject: [PATCH 04/15] merger contributer and hashrate tables into a sortable table, added personal hashrate graphs, added JS files, updated statistics class and page controllers --- public/include/classes/statistics.class.php | 27 +- public/include/pages/statistics/pool.inc.php | 25 +- public/include/pages/statistics/user.inc.php | 37 + .../mmcFE/js/jquery.tablesorter.pager.js | 184 ++++ .../site_assets/mmcFE/js/jquery.visualize.js | 806 ++++++++++++++++++ public/templates/mmcFE/master.tpl | 2 + .../mmcFE/statistics/pool/authenticated.tpl | 31 +- .../mmcFE/statistics/user/default.tpl | 23 + 8 files changed, 1095 insertions(+), 40 deletions(-) create mode 100644 public/include/pages/statistics/user.inc.php create mode 100644 public/site_assets/mmcFE/js/jquery.tablesorter.pager.js create mode 100644 public/site_assets/mmcFE/js/jquery.visualize.js create mode 100644 public/templates/mmcFE/statistics/user/default.tpl diff --git a/public/include/classes/statistics.class.php b/public/include/classes/statistics.class.php index 0161c139..5eae4cff 100644 --- a/public/include/classes/statistics.class.php +++ b/public/include/classes/statistics.class.php @@ -139,6 +139,31 @@ class Statistics { $this->debug->append("Failed to fetch hashrate: " . $this->mysqli->error); return false; } -} + public function getHourlyHashrateByAccount($account_id) { + $stmt = $this->mysqli->prepare(" + SELECT + ROUND(COUNT(s.id) * POW(2, 12)/600/1000) AS hashrate, + HOUR(s.time) AS hour + FROM " . $this->share->getTableName() . " AS s, accounts AS a + WHERE time < NOW() - INTERVAL 1 HOUR AND time > NOW() - INTERVAL 25 HOUR + AND a.username = SUBSTRING_INDEX( s.username, '.', 1 ) + AND a.id = ? + GROUP BY HOUR(time) + UNION ALL + SELECT + ROUND(COUNT(s.id) * POW(2, 12)/600/1000) AS hashrate, + HOUR(s.time) AS hour + FROM " . $this->share->getArchiveTableName() . " AS s, accounts AS a + WHERE time < NOW() - INTERVAL 1 HOUR AND time > NOW() - INTERVAL 25 HOUR + AND a.username = SUBSTRING_INDEX( s.username, '.', 1 ) + AND a.id = ? + GROUP BY HOUR(time)"); + if ($this->checkStmt($stmt) && $stmt->bind_param("ii", $account_id, $account_id) && $stmt->execute() && $hourlyhashrates = $stmt->get_result()) + return $hourlyhashrates->fetch_all(MYSQLI_ASSOC); + // Catchall + $this->debug->append("Failed to fetch hourly hashrate: " . $this->mysqli->error); + return false; + } +} $statistics = new Statistics($debug, $mysqli, $config, $share, $user); diff --git a/public/include/pages/statistics/pool.inc.php b/public/include/pages/statistics/pool.inc.php index bc12c3af..822eb3c0 100644 --- a/public/include/pages/statistics/pool.inc.php +++ b/public/include/pages/statistics/pool.inc.php @@ -21,30 +21,23 @@ if ($bitcoin->can_connect() === true){ } if (!$aHashData = $memcache->get('aHashData')) { - $debug->append('Hashrates expired in memcache'); + $debug->append('STA Fetching Hashrates from database'); // Top 15 hashrate list - $stmt = $mysqli->prepare("SELECT + $stmt = $mysqli->prepare(" + SELECT + COUNT(id) AS shares, ROUND(COUNT(id) * POW(2," . $config['difficulty'] . ")/600/1000,2) AS hashrate, SUBSTRING_INDEX( `username` , '.', 1 ) AS account - FROM shares - WHERE time > DATE_SUB(now(), INTERVAL 10 MINUTE) - GROUP BY account - ORDER BY hashrate DESC LIMIT 15"); + FROM shares + WHERE time > DATE_SUB(now(), INTERVAL 10 MINUTE) + GROUP BY account + ORDER BY hashrate DESC LIMIT 15"); $stmt->execute(); $hashrates= $stmt->get_result(); $aHashData = $hashrates->fetch_all(MYSQLI_ASSOC); $stmt->close(); $memcache->set('aHashData', $aHashData, 60); -} - -if (! $aContributerData = $memcache->get('aContributerData') ) { - // Top 15 Contributers - $stmt = $mysqli->prepare("SELECT count(id) AS shares, SUBSTRING_INDEX( `username` , '.', 1 ) AS account FROM shares GROUP BY account ORDER BY shares DESC LIMIT 15"); - $stmt->execute(); - $contributers = $stmt->get_result(); - $aContributerData = $contributers->fetch_all(MYSQLI_ASSOC); - $stmt->close(); - $memcache->set('aContributerData', $aContributerData, 60); + $debug->append('END Fetching Hashrates from database'); } // Grab the last block found diff --git a/public/include/pages/statistics/user.inc.php b/public/include/pages/statistics/user.inc.php new file mode 100644 index 00000000..e690fc80 --- /dev/null +++ b/public/include/pages/statistics/user.inc.php @@ -0,0 +1,37 @@ +can_connect() === true){ + if (!$dDifficulty = $memcache->get('dDifficulty')) { + $dDifficulty = $bitcoin->query('getdifficulty'); + $memcache->set('dDifficulty', $dDifficulty, 60); + } + if (!$iBlock = $memcache->get('iBlock')) { + $iBlock = $bitcoin->query('getblockcount'); + $memcache->set('iBlock', $iBlock, 60); + } +} else { + $iDifficulty = 1; + $iBlock = 0; + $_SESSION['POPUP'][] = array('CONTENT' => 'Unable to connect to pushpool service: ' . $bitcoin->can_connect(), 'TYPE' => 'errormsg'); +} + +if (!$aHourlyHashRates = $memcache->get('mmcfe_' . $_SESSION['USERDATA']['id'] . '_hourlyhashrate')) { + $debug->append('STA Fetching hourly hashrates from database'); + $aHourlyHashRates = $statistics->getHourlyHashrateByAccount($_SESSION['USERDATA']['id']); + $memcache->set('mmcfe_' . $_SESSION['USERDATA']['id'] . '_hourlyhashrate', $aHourlyHashRates, 600); + $debug->append('END Fetching hourly hashrates from database'); +} + +// Propagate content our template +$smarty->assign("YOURHASHRATES", $aHourlyHashRates); +$smarty->assign("DIFFICULTY", $dDifficulty); + +if ($_SESSION['AUTHENTICATED']) { + $smarty->assign("CONTENT", "default.tpl"); +} +?> diff --git a/public/site_assets/mmcFE/js/jquery.tablesorter.pager.js b/public/site_assets/mmcFE/js/jquery.tablesorter.pager.js new file mode 100644 index 00000000..cce6ea26 --- /dev/null +++ b/public/site_assets/mmcFE/js/jquery.tablesorter.pager.js @@ -0,0 +1,184 @@ +(function($) { + $.extend({ + tablesorterPager: new function() { + + function updatePageDisplay(c) { + var s = $(c.cssPageDisplay,c.container).val((c.page+1) + c.seperator + c.totalPages); + } + + function setPageSize(table,size) { + var c = table.config; + c.size = size; + c.totalPages = Math.ceil(c.totalRows / c.size); + c.pagerPositionSet = false; + moveToPage(table); + fixPosition(table); + } + + function fixPosition(table) { + var c = table.config; + if(!c.pagerPositionSet && c.positionFixed) { + var c = table.config, o = $(table); + if(o.offset) { + c.container.css({ + top: o.offset().top + o.height() + 'px', + position: 'absolute' + }); + } + c.pagerPositionSet = true; + } + } + + function moveToFirstPage(table) { + var c = table.config; + c.page = 0; + moveToPage(table); + } + + function moveToLastPage(table) { + var c = table.config; + c.page = (c.totalPages-1); + moveToPage(table); + } + + function moveToNextPage(table) { + var c = table.config; + c.page++; + if(c.page >= (c.totalPages-1)) { + c.page = (c.totalPages-1); + } + moveToPage(table); + } + + function moveToPrevPage(table) { + var c = table.config; + c.page--; + if(c.page <= 0) { + c.page = 0; + } + moveToPage(table); + } + + + function moveToPage(table) { + var c = table.config; + if(c.page < 0 || c.page > (c.totalPages-1)) { + c.page = 0; + } + + renderTable(table,c.rowsCopy); + } + + function renderTable(table,rows) { + + var c = table.config; + var l = rows.length; + var s = (c.page * c.size); + var e = (s + c.size); + if(e > rows.length ) { + e = rows.length; + } + + + var tableBody = $(table.tBodies[0]); + + // clear the table body + + $.tablesorter.clearTableBody(table); + + for(var i = s; i < e; i++) { + + //tableBody.append(rows[i]); + + var o = rows[i]; + var l = o.length; + for(var j=0; j < l; j++) { + + tableBody[0].appendChild(o[j]); + + } + } + + fixPosition(table,tableBody); + + $(table).trigger("applyWidgets"); + + if( c.page >= c.totalPages ) { + moveToLastPage(table); + } + + updatePageDisplay(c); + } + + this.appender = function(table,rows) { + + var c = table.config; + + c.rowsCopy = rows; + c.totalRows = rows.length; + c.totalPages = Math.ceil(c.totalRows / c.size); + + renderTable(table,rows); + }; + + this.defaults = { + size: 10, + offset: 0, + page: 0, + totalRows: 0, + totalPages: 0, + container: null, + cssNext: '.next', + cssPrev: '.prev', + cssFirst: '.first', + cssLast: '.last', + cssPageDisplay: '.pagedisplay', + cssPageSize: '.pagesize', + seperator: "/", + positionFixed: true, + appender: this.appender + }; + + this.construct = function(settings) { + + return this.each(function() { + + config = $.extend(this.config, $.tablesorterPager.defaults, settings); + + var table = this, pager = config.container; + + $(this).trigger("appendCache"); + + config.size = parseInt($(".pagesize",pager).val()); + + $(config.cssFirst,pager).click(function() { + moveToFirstPage(table); + return false; + }); + $(config.cssNext,pager).click(function() { + moveToNextPage(table); + return false; + }); + $(config.cssPrev,pager).click(function() { + moveToPrevPage(table); + return false; + }); + $(config.cssLast,pager).click(function() { + moveToLastPage(table); + return false; + }); + $(config.cssPageSize,pager).change(function() { + setPageSize(table,parseInt($(this).val())); + return false; + }); + }); + }; + + } + }); + // extend plugin scope + $.fn.extend({ + tablesorterPager: $.tablesorterPager.construct + }); + +})(jQuery); \ No newline at end of file diff --git a/public/site_assets/mmcFE/js/jquery.visualize.js b/public/site_assets/mmcFE/js/jquery.visualize.js new file mode 100644 index 00000000..3daf800a --- /dev/null +++ b/public/site_assets/mmcFE/js/jquery.visualize.js @@ -0,0 +1,806 @@ +/** + * -------------------------------------------------------------------- + * jQuery-Plugin "visualize" + * by Scott Jehl, scott@filamentgroup.com + * http://www.filamentgroup.com + * Copyright (c) 2009 Filament Group + * Dual licensed under the MIT (filamentgroup.com/examples/mit-license.txt) and GPL (filamentgroup.com/examples/gpl-license.txt) licenses. + * + * -------------------------------------------------------------------- + */ +(function ($) { + $.fn.visualize = function (options, container) { + return $(this).each(function () { + //configuration + var o = $.extend({ + type: 'bar', + //also available: area, pie, line + width: $(this).width(), + //height of canvas - defaults to table height + height: $(this).height(), + //height of canvas - defaults to table height + appendTitle: true, + //table caption text is added to chart + title: null, + //grabs from table caption if null + appendKey: true, + //color key is added to chart + colors: ['#be1e2d', '#666699', '#92d5ea', '#ee8310', '#8d10ee', '#5a3b16', '#26a4ed', '#f45a90', '#e9e744'], + textColors: [], + //corresponds with colors array. null/undefined items will fall back to CSS + parseDirection: 'x', + //which direction to parse the table data + pieMargin: 10, + //pie charts only - spacing around pie + pieLabelsAsPercent: true, + pieLabelPos: 'inside', + lineWeight: 4, + //for line and area - stroke weight + lineDots: false, + //also available: 'single', 'double' + dotInnerColor: "#ffffff", + // only used for lineDots:'double' + lineMargin: (options.lineDots ? 15 : 0), + //for line and area - spacing around lines + barGroupMargin: 10, + chartId: '', + xLabelParser: null, + // function to parse labels as values + valueParser: null, + // function to parse values. must return a Number + chartId: '', + chartClass: '', + barMargin: 1, + //space around bars in bar chart (added to both sides of bar) + yLabelInterval: 30, + //distance between y labels + interaction: false // only used for lineDots != false -- triggers mouseover and mouseout on original table + }, options); + + //reset width, height to numbers + o.width = parseFloat(o.width); + o.height = parseFloat(o.height); + + // reset padding if graph is not lines + if (o.type != 'line' && o.type != 'area') { + o.lineMargin = 0; + } + + var self = $(this); + + // scrape data from html table + var tableData = {}; + var colors = o.colors; + var textColors = o.textColors; + + + var parseLabels = function (direction) { + var labels = []; + if (direction == 'x') { + self.find('thead tr').each(function (i) { + $(this).find('th').each(function (j) { + if (!labels[j]) { + labels[j] = []; + } + labels[j][i] = $(this).text() + }) + }); + } else { + self.find('tbody tr').each(function (i) { + $(this).find('th').each(function (j) { + if (!labels[i]) { + labels[i] = []; + } + labels[i][j] = $(this).text() + }); + }); + } + return labels; + }; + + var fnParse = o.valueParser || parseFloat; + var dataGroups = tableData.dataGroups = []; + if (o.parseDirection == 'x') { + self.find('tbody tr').each(function (i, tr) { + dataGroups[i] = {}; + dataGroups[i].points = []; + dataGroups[i].color = colors[i]; + if (textColors[i]) { + dataGroups[i].textColor = textColors[i]; + } + $(tr).find('td').each(function (j, td) { + dataGroups[i].points.push({ + value: fnParse($(td).text()), + elem: td, + tableCords: [i, j] + }); + }); + }); + } else { + var cols = self.find('tbody tr:eq(0) td').size(); + for (var i = 0; i < cols; i++) { + dataGroups[i] = {}; + dataGroups[i].points = []; + dataGroups[i].color = colors[i]; + if (textColors[i]) { + dataGroups[i].textColor = textColors[i]; + } + self.find('tbody tr').each(function (j) { + dataGroups[i].points.push({ + value: $(this).find('td').eq(i).text() * 1, + elem: this, + tableCords: [i, j] + }); + }); + }; + } + + + var allItems = tableData.allItems = []; + $(dataGroups).each(function (i, row) { + var count = 0; + $.each(row.points, function (j, point) { + allItems.push(point); + count += point.value; + }); + row.groupTotal = count; + }); + + tableData.dataSum = 0; + tableData.topValue = 0; + tableData.bottomValue = Infinity; + $.each(allItems, function (i, item) { + tableData.dataSum += fnParse(item.value); + if (fnParse(item.value, 10) > tableData.topValue) { + tableData.topValue = fnParse(item.value, 10); + } + if (item.value < tableData.bottomValue) { + tableData.bottomValue = fnParse(item.value); + } + }); + var dataSum = tableData.dataSum; + var topValue = tableData.topValue; + var bottomValue = tableData.bottomValue; + + var xAllLabels = tableData.xAllLabels = parseLabels(o.parseDirection); + var yAllLabels = tableData.yAllLabels = parseLabels(o.parseDirection === 'x' ? 'y' : 'x'); + + var xLabels = tableData.xLabels = []; + $.each(tableData.xAllLabels, function (i, labels) { + tableData.xLabels.push(labels[0]); + }); + + var totalYRange = tableData.totalYRange = tableData.topValue - tableData.bottomValue; + + var zeroLocX = tableData.zeroLocX = 0; + + if ($.isFunction(o.xLabelParser)) { + + var xTopValue = null; + var xBottomValue = null; + + $.each(xLabels, function (i, label) { + label = xLabels[i] = o.xLabelParser(label); + if (i === 0) { + xTopValue = label; + xBottomValue = label; + } + if (label > xTopValue) { + xTopValue = label; + } + if (label < xBottomValue) { + xBottomValue = label; + } + }); + + var totalXRange = tableData.totalXRange = xTopValue - xBottomValue; + + + var xScale = tableData.xScale = (o.width - 2 * o.lineMargin) / totalXRange; + var marginDiffX = 0; + if (o.lineMargin) { + var marginDiffX = -2 * xScale - o.lineMargin; + } + zeroLocX = tableData.zeroLocX = xBottomValue + o.lineMargin; + + tableData.xBottomValue = xBottomValue; + tableData.xTopValue = xTopValue; + tableData.totalXRange = totalXRange; + } + + var yScale = tableData.yScale = (o.height - 2 * o.lineMargin) / totalYRange; + var zeroLocY = tableData.zeroLocY = (o.height - 2 * o.lineMargin) * (tableData.topValue / tableData.totalYRange) + o.lineMargin; + + var yLabels = tableData.yLabels = []; + + var numLabels = Math.floor((o.height - 2 * o.lineMargin) / 30); + + var loopInterval = tableData.totalYRange / numLabels; //fix provided from lab + loopInterval = Math.round(parseFloat(loopInterval) / 5) * 5; + loopInterval = Math.max(loopInterval, 1); + + // var start = + for (var j = Math.round(parseInt(tableData.bottomValue) / 5) * 5; j <= tableData.topValue - loopInterval; j += loopInterval) { + yLabels.push(j); + } + if (yLabels[yLabels.length - 1] > tableData.topValue + loopInterval) { + yLabels.pop(); + } else if (yLabels[yLabels.length - 1] <= tableData.topValue - 10) { + yLabels.push(tableData.topValue); + } + + // populate some data + $.each(dataGroups, function (i, row) { + row.yLabels = tableData.yAllLabels[i]; + $.each(row.points, function (j, point) { + point.zeroLocY = tableData.zeroLocY; + point.zeroLocX = tableData.zeroLocX; + point.xLabels = tableData.xAllLabels[j]; + point.yLabels = tableData.yAllLabels[i]; + point.color = row.color; + }); + }); + + try { + console.log(tableData); + } catch (e) {} + + var charts = {}; + + charts.pie = { + interactionPoints: dataGroups, + + setup: function () { + charts.pie.draw(true); + }, + draw: function (drawHtml) { + + var centerx = Math.round(canvas.width() / 2); + var centery = Math.round(canvas.height() / 2); + var radius = centery - o.pieMargin; + var counter = 0.0; + + if (drawHtml) { + canvasContain.addClass('visualize-pie'); + + if (o.pieLabelPos == 'outside') { + canvasContain.addClass('visualize-pie-outside'); + } + + var toRad = function (integer) { + return (Math.PI / 180) * integer; + }; + var labels = $('
        ').insertAfter(canvas); + } + + + //draw the pie pieces + $.each(dataGroups, function (i, row) { + var fraction = row.groupTotal / dataSum; + if (fraction <= 0 || isNaN(fraction)) return; + ctx.beginPath(); + ctx.moveTo(centerx, centery); + ctx.arc(centerx, centery, radius, counter * Math.PI * 2 - Math.PI * 0.5, (counter + fraction) * Math.PI * 2 - Math.PI * 0.5, false); + ctx.lineTo(centerx, centery); + ctx.closePath(); + ctx.fillStyle = dataGroups[i].color; + ctx.fill(); + // draw labels + if (drawHtml) { + var sliceMiddle = (counter + fraction / 2); + var distance = o.pieLabelPos == 'inside' ? radius / 1.5 : radius + radius / 5; + var labelx = Math.round(centerx + Math.sin(sliceMiddle * Math.PI * 2) * (distance)); + var labely = Math.round(centery - Math.cos(sliceMiddle * Math.PI * 2) * (distance)); + var leftRight = (labelx > centerx) ? 'right' : 'left'; + var topBottom = (labely > centery) ? 'bottom' : 'top'; + var percentage = parseFloat((fraction * 100).toFixed(2)); + + // interaction variables + row.canvasCords = [labelx, labely]; + row.zeroLocY = tableData.zeroLocY = 0; // related to zeroLocY and plugin API + row.zeroLocX = tableData.zeroLocX = 0; // related to zeroLocX and plugin API + row.value = row.groupTotal; + + + if (percentage) { + var labelval = (o.pieLabelsAsPercent) ? percentage + '%' : row.groupTotal; + var labeltext = $('' + labelval + '').css(leftRight, 0).css(topBottom, 0); + if (labeltext) var label = $('
      • ').appendTo(labels).css({ + left: labelx, + top: labely + }).append(labeltext); + labeltext.css('font-size', radius / 8).css('margin-' + leftRight, -labeltext.width() / 2).css('margin-' + topBottom, -labeltext.outerHeight() / 2); + + if (dataGroups[i].textColor) { + labeltext.css('color', dataGroups[i].textColor); + } + + } + } + counter += fraction; + }); + } + }; + + (function () { + + var xInterval; + + var drawPoint = function (ctx, x, y, color, size) { + ctx.moveTo(x, y); + ctx.beginPath(); + ctx.arc(x, y, size / 2, 0, 2 * Math.PI, false); + ctx.closePath(); + ctx.fillStyle = color; + ctx.fill(); + }; + + charts.line = { + + interactionPoints: allItems, + + setup: function (area) { + + if (area) { + canvasContain.addClass('visualize-area'); + } else { + canvasContain.addClass('visualize-line'); + } + + //write X labels + var xlabelsUL = $('
          ').width(canvas.width()).height(canvas.height()).insertBefore(canvas); + + if (!o.customXLabels) { + xInterval = (canvas.width() - 2 * o.lineMargin) / (xLabels.length - 1); + $.each(xLabels, function (i) { + var thisLi = $('
        • ' + this + '
        • ').prepend('').css('left', o.lineMargin + xInterval * i).appendTo(xlabelsUL); + var label = thisLi.find('span:not(.line)'); + var leftOffset = label.width() / -2; + if (i == 0) { + leftOffset = -20; + } else if (i == xLabels.length - 1) { + leftOffset = -label.width() + 20; + } + label.css('margin-left', leftOffset).addClass('label'); + }); + } else { + o.customXLabels(tableData, xlabelsUL); + } + + //write Y labels + var liBottom = (canvas.height() - 2 * o.lineMargin) / (yLabels.length - 1); + var ylabelsUL = $('
            ').width(canvas.width()).height(canvas.height()) + // .css('margin-top',-o.lineMargin) + .insertBefore(scroller); + + $.each(yLabels, function (i) { + var value = Math.floor(this); + var posB = (value - bottomValue) * yScale + o.lineMargin; + if (posB >= o.height - 1 || posB < 0) { + return; + } + var thisLi = $('
          • ' + value + '
          • ').css('bottom', posB); + if (Math.abs(posB) < o.height - 1) { + thisLi.prepend(''); + } + thisLi.prependTo(ylabelsUL); + + var label = thisLi.find('span:not(.line)'); + var topOffset = label.height() / -2; + if (!o.lineMargin) { + if (i == 0) { + topOffset = -label.height(); + } else if (i == yLabels.length - 1) { + topOffset = 0; + } + } + label.css('margin-top', topOffset).addClass('label'); + }); + + //start from the bottom left + ctx.translate(zeroLocX, zeroLocY); + + charts.line.draw(area); + + }, + + draw: function (area) { + // prevent drawing on top of previous draw + ctx.clearRect(-zeroLocX, -zeroLocY, o.width, o.height); + // Calculate each point properties before hand + var integer; + $.each(dataGroups, function (i, row) { + integer = o.lineMargin; // the current offset + $.each(row.points, function (j, point) { + if (o.xLabelParser) { + point.canvasCords = [(xLabels[j] - zeroLocX) * xScale - xBottomValue, -(point.value * yScale)]; + } else { + point.canvasCords = [integer, -(point.value * yScale)]; + } + + if (o.lineDots) { + point.dotSize = o.dotSize || o.lineWeight * Math.PI; + point.dotInnerSize = o.dotInnerSize || o.lineWeight * Math.PI / 2; + if (o.lineDots == 'double') { + point.innerColor = o.dotInnerColor; + } + } + integer += xInterval; + }); + }); + // fire custom event so we can enable rich interaction + self.trigger('vizualizeBeforeDraw', { + options: o, + table: self, + canvasContain: canvasContain, + tableData: tableData + }); + // draw lines and areas + $.each(dataGroups, function (h) { + // Draw lines + ctx.beginPath(); + ctx.lineWidth = o.lineWeight; + ctx.lineJoin = 'round'; + $.each(this.points, function (g) { + var loc = this.canvasCords; + if (g == 0) { + ctx.moveTo(loc[0], loc[1]); + } + ctx.lineTo(loc[0], loc[1]); + }); + ctx.strokeStyle = this.color; + ctx.stroke(); + // Draw fills + if (area) { + var integer = this.points[this.points.length - 1].canvasCords[0]; + if (isFinite(integer)) ctx.lineTo(integer, 0); + ctx.lineTo(o.lineMargin, 0); + ctx.closePath(); + ctx.fillStyle = this.color; + ctx.globalAlpha = .3; + ctx.fill(); + ctx.globalAlpha = 1.0; + } else { + ctx.closePath(); + } + }); + // draw points + if (o.lineDots) { + $.each(dataGroups, function (h) { + $.each(this.points, function (g) { + drawPoint(ctx, this.canvasCords[0], this.canvasCords[1], this.color, this.dotSize); + if (o.lineDots === 'double') { + drawPoint(ctx, this.canvasCords[0], this.canvasCords[1], this.innerColor, this.dotInnerSize); + } + }); + }); + } + + } + }; + + })(); + + charts.area = { + setup: function () { + charts.line.setup(true); + }, + draw: charts.line.draw + }; + + (function () { + + var horizontal, bottomLabels; + + charts.bar = { + setup: function () { + /** + * We can draw horizontal or vertical bars depending on the + * value of the 'barDirection' option (which may be 'vertical' or + * 'horizontal'). + */ + + horizontal = (o.barDirection == 'horizontal'); + + canvasContain.addClass('visualize-bar'); + + /** + * Write labels along the bottom of the chart. If we're drawing + * horizontal bars, these will be the yLabels, otherwise they + * will be the xLabels. The positioning also varies slightly: + * yLabels are values, hence they will span the whole width of + * the canvas, whereas xLabels are supposed to line up with the + * bars. + */ + bottomLabels = horizontal ? yLabels : xLabels; + + var xInterval = canvas.width() / (bottomLabels.length - (horizontal ? 1 : 0)); + + var xlabelsUL = $('
              ').width(canvas.width()).height(canvas.height()).insertBefore(canvas); + + $.each(bottomLabels, function (i) { + var thisLi = $('
            • ' + this + '
            • ').prepend('').css('left', xInterval * i).width(xInterval).appendTo(xlabelsUL); + + if (horizontal) { + var label = thisLi.find('span.label'); + label.css("margin-left", -label.width() / 2); + } + }); + + /** + * Write labels along the left of the chart. Follows the same idea + * as the bottom labels. + */ + var leftLabels = horizontal ? xLabels : yLabels; + var liBottom = canvas.height() / (leftLabels.length - (horizontal ? 0 : 1)); + + var ylabelsUL = $('
                ').width(canvas.width()).height(canvas.height()).insertBefore(canvas); + + $.each(leftLabels, function (i) { + var thisLi = $('
              • ' + this + '
              • ').prependTo(ylabelsUL); + + var label = thisLi.find('span:not(.line)').addClass('label'); + + if (horizontal) { + /** + * For left labels, we want to vertically align the text + * to the middle of its container, but we don't know how + * many lines of text we will have, since the labels could + * be very long. + * + * So we set a min-height of liBottom, and a max-height + * of liBottom + 1, so we can then check the label's actual + * height to determine if it spans one line or more lines. + */ + label.css({ + 'min-height': liBottom, + 'max-height': liBottom + 1, + 'vertical-align': 'middle' + }); + thisLi.css({ + 'top': liBottom * i, + 'min-height': liBottom + }); + + var r = label[0].getClientRects()[0]; + if (r.bottom - r.top == liBottom) { +/* This means we have only one line of text; hence + * we can centre the text vertically by setting the line-height, + * as described at: + * http://www.ampsoft.net/webdesign-l/vertical-aligned-nav-list.html + * + * (Although firefox has .height on the rectangle, IE doesn't, + * so we use r.bottom - r.top rather than r.height.) + */ + label.css('line-height', parseInt(liBottom) + 'px'); + } else { +/* + * If there is more than one line of text, then we shouldn't + * touch the line height, but we should make sure the text + * doesn't overflow the container. + */ + label.css("overflow", "hidden"); + } + } else { + thisLi.css('bottom', liBottom * i).prepend(''); + label.css('margin-top', -label.height() / 2) + } + }); + + charts.bar.draw(); + + }, + + draw: function () { + // Draw bars + if (horizontal) { + // for horizontal, keep the same code, but rotate everything 90 degrees + // clockwise. + ctx.rotate(Math.PI / 2); + } else { + // for vertical, translate to the top left corner. + ctx.translate(0, zeroLocY); + } + + // Don't attempt to draw anything if all the values are zero, + // otherwise we will get weird exceptions from the canvas methods. + if (totalYRange <= 0) return; + + var yScale = (horizontal ? canvas.width() : canvas.height()) / totalYRange; + var barWidth = horizontal ? (canvas.height() / xLabels.length) : (canvas.width() / (bottomLabels.length)); + var linewidth = (barWidth - o.barGroupMargin * 2) / dataGroups.length; + + for (var h = 0; h < dataGroups.length; h++) { + ctx.beginPath(); + + var strokeWidth = linewidth - (o.barMargin * 2); + ctx.lineWidth = strokeWidth; + var points = dataGroups[h].points; + var integer = 0; + for (var i = 0; i < points.length; i++) { + // If the last value is zero, IE will go nuts and not draw anything, + // so don't try to draw zero values at all. + if (points[i].value != 0) { + var xVal = (integer - o.barGroupMargin) + (h * linewidth) + linewidth / 2; + xVal += o.barGroupMargin * 2; + ctx.moveTo(xVal, 0); + ctx.lineTo(xVal, Math.round(-points[i].value * yScale)); + } + integer += barWidth; + } + ctx.strokeStyle = dataGroups[h].color; + ctx.stroke(); + ctx.closePath(); + } + + } + }; + + })(); + + //create new canvas, set w&h attrs (not inline styles) + var canvasNode = document.createElement("canvas"); + var canvas = $(canvasNode).attr({ + 'height': o.height, + 'width': o.width + }); + + //get title for chart + var title = o.title || self.find('caption').text(); + + //create canvas wrapper div, set inline w&h, append + var canvasContain = (container || $('
                ')).height(o.height).width(o.width); + + var scroller = $('
                ').appendTo(canvasContain).append(canvas); + + //title/key container + if (o.appendTitle || o.appendKey) { + var infoContain = $('
                ').appendTo(canvasContain); + } + + //append title + if (o.appendTitle) { + $('
                ' + title + '
                ').appendTo(infoContain); + } + + + //append key + if (o.appendKey) { + var newKey = $('
                  '); + $.each(yAllLabels, function (i, label) { + $('
                • ' + label + '
                • ').appendTo(newKey); + }); + newKey.appendTo(infoContain); + }; + + // init interaction + if (o.interaction) { + // sets the canvas to track interaction + // IE needs one div on top of the canvas since the VML shapes prevent mousemove from triggering correctly. + // Pie charts needs tracker because labels goes on top of the canvas and also messes up with mousemove + var tracker = $('
                  ').css({ + 'height': o.height + 'px', + 'width': o.width + 'px', + 'position': 'relative', + 'z-index': 200 + }).insertAfter(canvas); + + var triggerInteraction = function (overOut, data) { + var data = $.extend({ + canvasContain: canvasContain, + tableData: tableData + }, data); + self.trigger('vizualize' + overOut, data); + }; + + var over = false, + last = false, + started = false; + tracker.mousemove(function (e) { + var x, y, x1, y1, data, dist, i, current, selector, zLabel, elem, color, minDist, found, ev = e.originalEvent; + + // get mouse position relative to the tracker/canvas + x = ev.layerX || ev.offsetX || 0; + y = ev.layerY || ev.offsetY || 0; + + found = false; + minDist = started ? 30000 : (o.type == 'pie' ? (Math.round(canvas.height() / 2) - o.pieMargin) / 3 : o.lineWeight * 4); + // iterate datagroups to find points with matching + $.each(charts[o.type].interactionPoints, function (i, current) { + x1 = current.canvasCords[0] + zeroLocX; + y1 = current.canvasCords[1] + (o.type == "pie" ? 0 : zeroLocY); + dist = Math.sqrt((x1 - x) * (x1 - x) + (y1 - y) * (y1 - y)); + if (dist < minDist) { + found = current; + minDist = dist; + } + }); + + if (o.multiHover && found) { + x = found.canvasCords[0] + zeroLocX; + y = found.canvasCords[1] + (o.type == "pie" ? 0 : zeroLocY); + found = [found]; + $.each(charts[o.type].interactionPoints, function (i, current) { + if (current == found[0]) { + return; + } + x1 = current.canvasCords[0] + zeroLocX; + y1 = current.canvasCords[1] + zeroLocY; + dist = Math.sqrt((x1 - x) * (x1 - x) + (y1 - y) * (y1 - y)); + if (dist <= o.multiHover) { + found.push(current); + } + }); + } + // trigger over and out only when state changes, instead of on every mousemove + over = found; + if (over != last) { + if (over) { + if (last) { + triggerInteraction('Out', { + point: last + }); + } + triggerInteraction('Over', { + point: over + }); + last = over; + } + if (last && !over) { + triggerInteraction('Out', { + point: last + }); + last = false; + } + started = true; + } + }); + tracker.mouseleave(function () { + triggerInteraction('Out', { + point: last, + mouseOutGraph: true + }); + over = (last = false); + }); + } + + //append new canvas to page + if (!container) { + canvasContain.insertAfter(this); + } + if (typeof (G_vmlCanvasManager) != 'undefined') { + G_vmlCanvasManager.init(); + G_vmlCanvasManager.initElement(canvas[0]); + } + + //set up the drawing board + var ctx = canvas[0].getContext('2d'); + + // Scroll graphs + scroller.scrollLeft(o.width - scroller.width()); + + // init plugins + $.each($.visualizePlugins, function (i, plugin) { + plugin.call(self, o, tableData); + }); + + //create chart + charts[o.type].setup(); + + if (!container) { + //add event for updating + self.bind('visualizeRefresh', function () { + self.visualize(o, $(this).empty()); + }); + //add event for redraw + self.bind('visualizeRedraw', function () { + charts[o.type].draw(); + }); + } + }).next(); //returns canvas(es) + }; + // create array for plugins. if you wish to make a plugin, + // just push your init funcion into this array + $.visualizePlugins = []; + +})(jQuery); diff --git a/public/templates/mmcFE/master.tpl b/public/templates/mmcFE/master.tpl index 3df30667..6348c119 100644 --- a/public/templates/mmcFE/master.tpl +++ b/public/templates/mmcFE/master.tpl @@ -9,6 +9,8 @@ + + diff --git a/public/templates/mmcFE/statistics/pool/authenticated.tpl b/public/templates/mmcFE/statistics/pool/authenticated.tpl index f278fb04..a92afd67 100644 --- a/public/templates/mmcFE/statistics/pool/authenticated.tpl +++ b/public/templates/mmcFE/statistics/pool/authenticated.tpl @@ -1,12 +1,14 @@ {include file="global/block_header.tpl" BLOCK_HEADER="Pool Statistics" BLOCK_STYLE="clear:none;"} -{include file="global/block_header.tpl" BLOCK_HEADER="Top 15 Hashrates" ALIGN="left" BUTTONS=array(More,Less)} +{include file="global/block_header.tpl" BLOCK_HEADER="Top Contributers"}
                  - +
                  + + @@ -17,35 +19,18 @@ + + {/section}
                  Rank User Name KH/sSharesShares/s Ł/Day (est)
                  {$rank++} {$TOPHASHRATES[hashrate].account} {$TOPHASHRATES[hashrate].hashrate|number_format}{$TOPHASHRATES[hashrate].shares|number_format}{math equation="round(shares / 600,3)" shares=$TOPHASHRATES[hashrate].shares} {math equation="round(reward / ( diff * pow(2,32) / ( hashrate * 1000 ) / 3600 / 24),3)" diff=$DIFFICULTY reward=$REWARD hashrate=$TOPHASHRATES[hashrate].hashrate}
                  +
                  {include file="global/block_footer.tpl"} -{include file="global/block_header.tpl" BLOCK_HEADER="Top 15 Contributers" ALIGN="right" BUTTONS=array(More,Less)} -
                  - - - - - -{assign var=rank value=1} -{section contributor $CONTRIBUTORS} - - - - - -{/section} - -
                  RankUser NameShares
                  {$rank++}{$CONTRIBUTORS[contributor].account}{$CONTRIBUTORS[contributor].shares|number_format}
                  -
                  -{include file="global/block_footer.tpl"} - -{include file="global/block_header.tpl" BLOCK_HEADER="Server Stats" BLOCK_STYLE="clear:all;" STYLE="padding-left:5px;padding-right:5px;" BUTTONS=array(More)} +{include file="global/block_header.tpl" BLOCK_HEADER="Server Stats" BLOCK_STYLE="clear:all;" STYLE="padding-left:5px;padding-right:5px;"} diff --git a/public/templates/mmcFE/statistics/user/default.tpl b/public/templates/mmcFE/statistics/user/default.tpl new file mode 100644 index 00000000..ffc5e8c8 --- /dev/null +++ b/public/templates/mmcFE/statistics/user/default.tpl @@ -0,0 +1,23 @@ +{include file="global/block_header.tpl" BLOCK_HEADER="Your Average Hourly Hash Rate" BUTTONS=array(mine,pool,both)} +
                  +
                  + + + + +{section hashrate $YOURHASHRATES} + +{/section} + + + + + +{section hashrate $YOURHASHRATES} + +{/section} + + +
                  Your Hashrate 
                  {$YOURHASHRATES[hashrate].hour}
                  {$GLOBAL.USERDATA.username}{$YOURHASHRATES[hashrate].hashrate|number_format}
                  +
                  +{include file="global/block_footer.tpl"} From aadeac9f866a6968720207f005e8c646ca46465b Mon Sep 17 00:00:00 2001 From: Sebastian Grewe Date: Wed, 15 May 2013 18:11:06 +0200 Subject: [PATCH 05/15] properly search using getSingle, missed search field type --- public/include/classes/user.class.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/public/include/classes/user.class.php b/public/include/classes/user.class.php index 11f12403..f9b8e5e3 100644 --- a/public/include/classes/user.class.php +++ b/public/include/classes/user.class.php @@ -27,11 +27,11 @@ class User { } public function getUserName($id) { - return $this->getSingle($id, 'username'); + return $this->getSingle($id, 'username', 'id'); } public function getUserId($username) { - return $this->getSingle($username, 'id', 'username'); + return $this->getSingle($username, 'id', 'username', 's'); } public function checkLogin($username, $password) { @@ -55,10 +55,10 @@ class User { return $pin_hash === $row_pin; } - private function getSingle($value, $search='id', $field='id') { + private function getSingle($value, $search='id', $field='id', $type="i") { $stmt = $this->mysqli->prepare("SELECT $search FROM $this->table WHERE $field = ? LIMIT 1"); if ($this->checkStmt($stmt)) { - $stmt->bind_param('i', $value); + $stmt->bind_param($type, $value); $stmt->execute(); $stmt->bind_result($retval); $stmt->fetch(); @@ -69,7 +69,7 @@ class User { } public function getCoinAddress($userID) { - return $this->getSingle($userID, 'coin_address'); + return $this->getSingle($userID, 'coin_address', 'id', 's'); } private function updateSingle($userID, $field, $table) { From e791d27671dbcc287bcfa3c1be0e516f08bdc34d Mon Sep 17 00:00:00 2001 From: Sebastian Grewe Date: Thu, 16 May 2013 14:47:51 +0200 Subject: [PATCH 06/15] added getTableName method --- public/include/classes/block.class.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/public/include/classes/block.class.php b/public/include/classes/block.class.php index 9ecc8655..e4c3f7e2 100644 --- a/public/include/classes/block.class.php +++ b/public/include/classes/block.class.php @@ -23,6 +23,9 @@ class Block { public function getError() { return $this->sError; } + public function getTableName() { + return $this->table; + } public function getLast() { $stmt = $this->mysqli->prepare("SELECT * FROM $this->table ORDER BY height DESC LIMIT 1"); @@ -108,7 +111,7 @@ class Block { $stmt->close(); return true; } - return false; + return false; } public function setFinder($block_id, $account_id=NULL) { From 7344926ebfd3392ef28a9f29b04d78d2a9e2f51f Mon Sep 17 00:00:00 2001 From: Sebastian Grewe Date: Thu, 16 May 2013 14:54:43 +0200 Subject: [PATCH 07/15] added shebang for cronjobs --- cronjobs/blockupdate.php | 1 + cronjobs/findblock.php | 1 + cronjobs/pps_payout.php | 1 + 3 files changed, 3 insertions(+) mode change 100644 => 100755 cronjobs/blockupdate.php mode change 100644 => 100755 cronjobs/findblock.php mode change 100644 => 100755 cronjobs/pps_payout.php diff --git a/cronjobs/blockupdate.php b/cronjobs/blockupdate.php old mode 100644 new mode 100755 index 10f074d7..877471a8 --- a/cronjobs/blockupdate.php +++ b/cronjobs/blockupdate.php @@ -1,3 +1,4 @@ +#!/usr/bin/php Date: Thu, 16 May 2013 14:56:08 +0200 Subject: [PATCH 08/15] moved more stats to stats class --- public/include/classes/statistics.class.php | 74 +++++++++++++++----- public/include/pages/statistics/pool.inc.php | 30 +------- 2 files changed, 61 insertions(+), 43 deletions(-) diff --git a/public/include/classes/statistics.class.php b/public/include/classes/statistics.class.php index 5eae4cff..41803133 100644 --- a/public/include/classes/statistics.class.php +++ b/public/include/classes/statistics.class.php @@ -8,12 +8,13 @@ class Statistics { private $sError = ''; private $table = 'statistics_shares'; - public function __construct($debug, $mysqli, $config, $share, $user) { + public function __construct($debug, $mysqli, $config, $share, $user, $block) { $this->debug = $debug; $this->mysqli = $mysqli; $this->share = $share; $this->config = $config; $this->user = $user; + $this->block = $block; $this->debug->append("Instantiated Share class", 2); } @@ -34,6 +35,19 @@ class Statistics { return true; } + public function getBlocksFound($limit=10) { + $stmt = $this->mysqli->prepare(" + SELECT b.*, a.username as finder + FROM " . $this->block->getTableName() . " AS b + LEFT JOIN accounts AS a + ON b.account_id = a.id + ORDER BY height DESC LIMIT ?"); + if ($this->checkStmt($stmt) && $stmt->bind_param("i", $limit) && $stmt->execute() && $result = $stmt->get_result()) + return $result->fetch_all(MYSQLI_ASSOC); + // Catchall + $this->debug->append("Failed to find blocks:" . $this->mysqli->error); + return false; + } public function updateShareStatistics($aStats, $iBlockId) { $stmt = $this->mysqli->prepare("INSERT INTO $this->table (account_id, valid, invalid, block_id) VALUES (?, ?, ?, ?)"); if ($this->checkStmt($stmt) && $stmt->bind_param('iiii', $aStats['id'], $aStats['valid'], $aStats['invalid'], $iBlockId) && $stmt->execute()) return true; @@ -76,11 +90,11 @@ class Statistics { ( SELECT IFNULL(count(id), 0) FROM " . $this->share->getTableName() . " WHERE UNIX_TIMESTAMP(time) >IFNULL((SELECT MAX(time) FROM blocks),0) - AND our_result = 'Y' ) as valid, + AND our_result = 'Y' ) as valid, ( SELECT IFNULL(count(id), 0) FROM " . $this->share->getTableName() . " WHERE UNIX_TIMESTAMP(time) >IFNULL((SELECT MAX(time) FROM blocks),0) - AND our_result = 'N' ) as invalid"); + AND our_result = 'N' ) as invalid"); if ( $this->checkStmt($stmt) && $stmt->execute() && $result = $stmt->get_result() ) return $result->fetch_assoc(); // Catchall $this->debug->append("Failed to fetch round shares: " . $this->mysqli->error); @@ -92,19 +106,21 @@ class Statistics { SELECT ( SELECT COUNT(s.id) - FROM " . $this->share->getTableName() . " AS s, " . $this->user->getTableName() . " AS u + FROM " . $this->share->getTableName() . " AS s, + " . $this->user->getTableName() . " AS u WHERE u.username = SUBSTRING_INDEX( s.username, '.', 1 ) - AND UNIX_TIMESTAMP(s.time) >IFNULL((SELECT MAX(b.time) FROM blocks AS b),0) - AND our_result = 'Y' - AND u.id = ? + AND UNIX_TIMESTAMP(s.time) >IFNULL((SELECT MAX(b.time) FROM blocks AS b),0) + AND our_result = 'Y' + AND u.id = ? ) AS valid, ( SELECT COUNT(s.id) - FROM " . $this->share->getTableName() . " AS s, " . $this->user->getTableName() . " AS u + FROM " . $this->share->getTableName() . " AS s, + " . $this->user->getTableName() . " AS u WHERE u.username = SUBSTRING_INDEX( s.username, '.', 1 ) - AND UNIX_TIMESTAMP(s.time) >IFNULL((SELECT MAX(b.time) FROM blocks AS b),0) - AND our_result = 'N' - AND u.id = ? + AND UNIX_TIMESTAMP(s.time) >IFNULL((SELECT MAX(b.time) FROM blocks AS b),0) + AND our_result = 'N' + AND u.id = ? ) AS invalid"); if ($stmt && $stmt->bind_param("ii", $account_id, $account_id) && $stmt->execute() && $result = $stmt->get_result()) return $result->fetch_assoc(); // Catchall @@ -116,8 +132,8 @@ class Statistics { $stmt = $this->mysqli->prepare(" SELECT ROUND(COUNT(s.id) * POW(2, " . $this->config['difficulty'] . ")/600/1000) AS hashrate FROM " . $this->share->getTableName() . " AS s, - " . $this->user->getTableName() . " AS u - WHERE u.username = SUBSTRING_INDEX( s.username, '.', 1 ) + " . $this->user->getTableName() . " AS u + WHERE u.username = SUBSTRING_INDEX( s.username, '.', 1 ) AND s.time > DATE_SUB(now(), INTERVAL 10 MINUTE) AND u.id = ?"); if ($this->checkStmt($stmt) && $stmt->bind_param("i", $account_id) && $stmt->execute() && $result = $stmt->get_result() ) return $result->fetch_object()->hashrate; @@ -130,8 +146,8 @@ class Statistics { $stmt = $this->mysqli->prepare(" SELECT ROUND(COUNT(s.id) * POW(2,21)/600/1000) AS hashrate FROM " . $this->share->getTableName() . " AS s, - " . $this->user->getTableName() . " AS u - WHERE u.username = SUBSTRING_INDEX( s.username, '.', 1 ) + " . $this->user->getTableName() . " AS u + WHERE u.username = SUBSTRING_INDEX( s.username, '.', 1 ) AND s.time > DATE_SUB(now(), INTERVAL 10 MINUTE) AND u.id = ?"); if ($this->checkStmt($stmt) && $stmt->bind_param("i", $account_id) && $stmt->execute() && $result = $stmt->get_result() ) return $result->fetch_object()->hashrate; @@ -140,6 +156,32 @@ class Statistics { return false; } + public function getTopContributors($limit=15) { + $stmt = $this->mysqli->prepare(" + SELECT + ROUND(COUNT(id) * POW(2," . $this->config['difficulty'] . ")/600/1000,2) AS hashrate, + SUBSTRING_INDEX( username, '.', 1 ) AS account + FROM " . $this->share->getTableName() . " + WHERE time > DATE_SUB(now(), INTERVAL 10 MINUTE) + GROUP BY account + ORDER BY hashrate DESC LIMIT ?"); + if ($this->checkStmt($stmt) && $stmt->bind_param("i", $limit) && $stmt->execute() && $hashrates= $stmt->get_result()) { + $aHashData = $hashrates->fetch_all(MYSQLI_ASSOC); + $stmt->close(); + } else { + return false; + } + foreach ($aHashData as $key => $aData) { + $stmt = $this->mysqli->prepare("SELECT COUNT(id) FROM " . $this->share->getTableName() . " WHERE SUBSTRING_INDEX( username , '.', 1 ) = ?"); + if ($stmt->bind_param("s", $aData['username']) && $stmt->execute() && $result = $stmt->get_result()) { + $aHashData[$key]['shares'] = $this->getUserShares($this->user->getUserId($aData['account']))['valid']; + } else { + continue; + } + } + return $aHashData; + } + public function getHourlyHashrateByAccount($account_id) { $stmt = $this->mysqli->prepare(" SELECT @@ -166,4 +208,4 @@ class Statistics { return false; } } -$statistics = new Statistics($debug, $mysqli, $config, $share, $user); +$statistics = new Statistics($debug, $mysqli, $config, $share, $user, $block); diff --git a/public/include/pages/statistics/pool.inc.php b/public/include/pages/statistics/pool.inc.php index 822eb3c0..005e167d 100644 --- a/public/include/pages/statistics/pool.inc.php +++ b/public/include/pages/statistics/pool.inc.php @@ -22,37 +22,14 @@ if ($bitcoin->can_connect() === true){ if (!$aHashData = $memcache->get('aHashData')) { $debug->append('STA Fetching Hashrates from database'); - // Top 15 hashrate list - $stmt = $mysqli->prepare(" - SELECT - COUNT(id) AS shares, - ROUND(COUNT(id) * POW(2," . $config['difficulty'] . ")/600/1000,2) AS hashrate, - SUBSTRING_INDEX( `username` , '.', 1 ) AS account - FROM shares - WHERE time > DATE_SUB(now(), INTERVAL 10 MINUTE) - GROUP BY account - ORDER BY hashrate DESC LIMIT 15"); - $stmt->execute(); - $hashrates= $stmt->get_result(); - $aHashData = $hashrates->fetch_all(MYSQLI_ASSOC); - $stmt->close(); + $aHashData = $statistics->getTopContributors(); $memcache->set('aHashData', $aHashData, 60); $debug->append('END Fetching Hashrates from database'); } -// Grab the last block found -$stmt = $mysqli->prepare("SELECT * FROM blocks ORDER BY height DESC LIMIT 1"); -$stmt->execute(); -$blocks = $stmt->get_result(); -$aBlockData = $blocks->fetch_array(); -$stmt->close(); - // Grab the last 10 blocks found -$stmt = $mysqli->prepare("SELECT b.*, a.username as finder FROM blocks AS b LEFT JOIN accounts AS a ON b.account_id = a.id ORDER BY height DESC LIMIT 10"); -$stmt->execute(); -$blocksfound = $stmt->get_result(); -$aBlocksFoundData = $blocksfound->fetch_all(MYSQLI_ASSOC); -$stmt->close(); +$aBlocksFoundData = $statistics->getBlocksFound(10); +$aBlockData = $aBlocksFoundData[0]; // Estimated time to find the next block if (!$iCurrentPoolHashrate = $memcache->get('iCurrentPoolHashrate')) { @@ -74,7 +51,6 @@ if (!empty($aBlockData)) { // Propagate content our template $smarty->assign("ESTTIME", $iEstTime); $smarty->assign("TIMESINCELAST", $dTimeSinceLast); -$smarty->assign("CONTRIBUTORS", $aContributerData); $smarty->assign("BLOCKSFOUND", $aBlocksFoundData); $smarty->assign("TOPHASHRATES", $aHashData); $smarty->assign("CURRENTBLOCK", $iBlock); From 72156c543d27857355cfc503dc24dc7084ac2dcb Mon Sep 17 00:00:00 2001 From: Sebastian Grewe Date: Thu, 16 May 2013 15:03:36 +0200 Subject: [PATCH 09/15] updated footer --- public/templates/mmcFE/global/footer.tpl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public/templates/mmcFE/global/footer.tpl b/public/templates/mmcFE/global/footer.tpl index 68e7c3e0..bf425d19 100644 --- a/public/templates/mmcFE/global/footer.tpl +++ b/public/templates/mmcFE/global/footer.tpl @@ -1,5 +1,5 @@
                  - Litecoin Pool using on litecoind, pushpoold
                  - Website based on mmcfe, overhauled by TheSerapher, available on GitHub
                  + Litecoin Pool using litecoind, pushpoold
                  + mmcfe-ng Website based on mmcfe, overhauled by TheSerapher, available on GitHub
                  LTC: Lge95QR2frp9y1wJufjUPCycVsg5gLJPW8


                  From 16f9cc4390a3ec6911e4bc1fe7969ab69eacb29c Mon Sep 17 00:00:00 2001 From: Sebastian Grewe Date: Thu, 16 May 2013 21:12:59 +0200 Subject: [PATCH 10/15] adding proper shares per second --- public/include/classes/statistics.class.php | 1 + 1 file changed, 1 insertion(+) diff --git a/public/include/classes/statistics.class.php b/public/include/classes/statistics.class.php index 41803133..0681fcce 100644 --- a/public/include/classes/statistics.class.php +++ b/public/include/classes/statistics.class.php @@ -159,6 +159,7 @@ class Statistics { public function getTopContributors($limit=15) { $stmt = $this->mysqli->prepare(" SELECT + ROUND(COUNT(id) / 60 / 10, 2) AS sharesps, ROUND(COUNT(id) * POW(2," . $this->config['difficulty'] . ")/600/1000,2) AS hashrate, SUBSTRING_INDEX( username, '.', 1 ) AS account FROM " . $this->share->getTableName() . " From ed313c403d48db3eb77fa27369cac5a7214c2942 Mon Sep 17 00:00:00 2001 From: Sebastian Grewe Date: Thu, 16 May 2013 21:13:21 +0200 Subject: [PATCH 11/15] re-defined some defaults --- public/include/config/global.inc.dist.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/public/include/config/global.inc.dist.php b/public/include/config/global.inc.dist.php index 92d21d8f..fead5b6c 100644 --- a/public/include/config/global.inc.dist.php +++ b/public/include/config/global.inc.dist.php @@ -16,11 +16,15 @@ define('CLASS_DIR', INCLUDE_DIR . '/classes'); define('PAGES_DIR', INCLUDE_DIR . '/pages'); // Set debugging level for our debug class -define('DEBUG', 5); +define('DEBUG', 0); -define('SALT', 'LJKEHFuhgu7%&¤Hg783tr7gf¤%¤fyegfredfoGHYFGYe(%/(&%6'); +define('SALT', 'PLEASEMAKEMESOMETHINGRANDOM'); $config = array( + 'website' => array( + 'name' => 'The Pool', + 'slogan' => 'Resistance is futile', + ), 'difficulty' => '31', // Target difficulty for this pool 'reward' => '50', // Reward for finding blocks 'confirmations' => '120', // Confirmations per block found to credit transactions From f46655501aaa6f66d9c657083209f5439485cc8c Mon Sep 17 00:00:00 2001 From: Sebastian Grewe Date: Thu, 16 May 2013 21:13:37 +0200 Subject: [PATCH 12/15] removed unused smarty globals for now --- public/include/smarty_globals.inc.php | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/public/include/smarty_globals.inc.php b/public/include/smarty_globals.inc.php index 29db7683..060aba83 100644 --- a/public/include/smarty_globals.inc.php +++ b/public/include/smarty_globals.inc.php @@ -37,15 +37,12 @@ if (!$iCurrentPoolShareRate = $memcache->get('iCurrentPoolShareRate')) { } $aGlobal = array( - 'slogan' => $settings->getValue('slogan'), - 'websitename' => $settings->getValue('websitename'), - 'ltc_usd' => $settings->getValue('btcesell'), + 'slogan' => $config['website']['slogan'], + 'websitename' => $config['website']['name'], 'hashrate' => $iCurrentPoolHashrate, 'sharerate' => $iCurrentPoolShareRate, 'workers' => $iCurrentActiveWorkers, 'roundshares' => $aRoundShares, - 'statstime' => $settings->getValue('statstime'), - 'motd' => $settings->getValue('motd'), 'confirmations' => $config['confirmations'], 'reward' => $config['reward'] ); From f67b5855a958cb4c273d59ba8b3ba44d3c55ad81 Mon Sep 17 00:00:00 2001 From: Sebastian Grewe Date: Thu, 16 May 2013 21:14:03 +0200 Subject: [PATCH 13/15] removed unused ministats field --- public/templates/mmcFE/global/header.tpl | 1 - 1 file changed, 1 deletion(-) diff --git a/public/templates/mmcFE/global/header.tpl b/public/templates/mmcFE/global/header.tpl index 4a7bc449..1f08a366 100644 --- a/public/templates/mmcFE/global/header.tpl +++ b/public/templates/mmcFE/global/header.tpl @@ -4,7 +4,6 @@
                  - From 88ff57964746e562122401647674ef730c95d7a2 Mon Sep 17 00:00:00 2001 From: Sebastian Grewe Date: Thu, 16 May 2013 21:14:29 +0200 Subject: [PATCH 14/15] removed last stats update since values are cached and not generated regularly --- public/templates/mmcFE/global/sidebar.tpl | 4 ---- 1 file changed, 4 deletions(-) diff --git a/public/templates/mmcFE/global/sidebar.tpl b/public/templates/mmcFE/global/sidebar.tpl index fbe95818..52e21a94 100644 --- a/public/templates/mmcFE/global/sidebar.tpl +++ b/public/templates/mmcFE/global/sidebar.tpl @@ -19,10 +19,6 @@ {math equation="round(( x / y ) * z, 8)" x=$GLOBAL.userdata.shares.valid y=$GLOBAL.roundshares.valid z=$GLOBAL.reward} LTC

                  Account Balance
                  {$GLOBAL.userdata.balance|default:"0"} LTC

                  -

                  -
                  -

                  Stats last updated:
                  {$GLOBAL.statstime|date_format:"%T"} GMT+1
                  (updated every 60 secs)

                  -
                  From aed9d58c6b7c2ce2ad85b75e9f164961016bb678 Mon Sep 17 00:00:00 2001 From: Sebastian Grewe Date: Thu, 16 May 2013 21:14:46 +0200 Subject: [PATCH 15/15] added proper shares per second to contributor list --- public/templates/mmcFE/statistics/pool/authenticated.tpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/templates/mmcFE/statistics/pool/authenticated.tpl b/public/templates/mmcFE/statistics/pool/authenticated.tpl index a92afd67..094493a1 100644 --- a/public/templates/mmcFE/statistics/pool/authenticated.tpl +++ b/public/templates/mmcFE/statistics/pool/authenticated.tpl @@ -20,7 +20,7 @@
                  - + {/section}
                • Ł/usd: {$GLOBAL.ltc_usd}    
                • Pool Hashrate: {$GLOBAL.hashrate / 1000} MH/s    
                • Pool Sharerate: {$GLOBAL.sharerate} Shares/s    
                • Pool Workers: {$GLOBAL.workers}    
                • {$TOPHASHRATES[hashrate].account} {$TOPHASHRATES[hashrate].hashrate|number_format} {$TOPHASHRATES[hashrate].shares|number_format}{math equation="round(shares / 600,3)" shares=$TOPHASHRATES[hashrate].shares}{$TOPHASHRATES[hashrate].sharesps} {math equation="round(reward / ( diff * pow(2,32) / ( hashrate * 1000 ) / 3600 / 24),3)" diff=$DIFFICULTY reward=$REWARD hashrate=$TOPHASHRATES[hashrate].hashrate}