added rich list and stats page

This commit is contained in:
Akinwale Ariwodola 2017-07-03 21:47:30 +01:00
parent 6c2e6727a8
commit abf84407cc
6 changed files with 111 additions and 5 deletions

View file

@ -50,6 +50,7 @@ Router::scope('/', function (RouteBuilder $routes) {
$routes->connect('/claims/*', ['controller' => 'Main', 'action' => 'claims']); $routes->connect('/claims/*', ['controller' => 'Main', 'action' => 'claims']);
$routes->connect('/find', ['controller' => 'Main', 'action' => 'find']); $routes->connect('/find', ['controller' => 'Main', 'action' => 'find']);
$routes->connect('/realtime', ['controller' => 'Main', 'action' => 'realtime']); $routes->connect('/realtime', ['controller' => 'Main', 'action' => 'realtime']);
$routes->connect('/stats', ['controller' => 'Main', 'action' => 'stats']);
$routes->connect('/tx/*', ['controller' => 'Main', 'action' => 'tx']); $routes->connect('/tx/*', ['controller' => 'Main', 'action' => 'tx']);
$routes->connect('/qr/*', ['controller' => 'Main', 'action' => 'qr']); $routes->connect('/qr/*', ['controller' => 'Main', 'action' => 'qr']);

View file

@ -73,6 +73,7 @@ CREATE TABLE `Addresses`
`FirstSeen` DATETIME, `FirstSeen` DATETIME,
`TotalReceived` DECIMAL(18,8) DEFAULT 0 NOT NULL, `TotalReceived` DECIMAL(18,8) DEFAULT 0 NOT NULL,
`TotalSent` DECIMAL(18,8) DEFAULT 0 NOT NULL, `TotalSent` DECIMAL(18,8) DEFAULT 0 NOT NULL,
`Balance` DECIMAL(18,8) AS (`TotalReceived` - `TotalSent`) PERSISTENT,
`Tag` VARCHAR(30) NOT NULL, `Tag` VARCHAR(30) NOT NULL,
`TagUrl` VARCHAR(200), `TagUrl` VARCHAR(200),
`Created` DATETIME NOT NULL, `Created` DATETIME NOT NULL,
@ -82,6 +83,7 @@ CREATE TABLE `Addresses`
UNIQUE KEY `Idx_AddressTag` (`Tag`), UNIQUE KEY `Idx_AddressTag` (`Tag`),
INDEX `Idx_AddressTotalReceived` (`TotalReceived`), INDEX `Idx_AddressTotalReceived` (`TotalReceived`),
INDEX `Idx_AddressTotalSent` (`TotalSent`), INDEX `Idx_AddressTotalSent` (`TotalSent`),
INDEX `Idx_AddressBalance` (`Balance`),
INDEX `Idx_AddressCreated` (`Created`), INDEX `Idx_AddressCreated` (`Created`),
INDEX `Idx_AddressModified` (`Modified`) INDEX `Idx_AddressModified` (`Modified`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=4; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=4;
@ -232,4 +234,6 @@ CREATE TABLE `PriceHistory`
UNIQUE KEY `Idx_PriceHistoryCreated` (`Created`) UNIQUE KEY `Idx_PriceHistoryCreated` (`Created`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=4; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=4;
ALTER TABLE Claims ADD UNIQUE KEY `Idx_ClaimUnique` (`TransactionHash`, `Vout`, `ClaimId`); ALTER TABLE Claims ADD UNIQUE KEY `Idx_ClaimUnique` (`TransactionHash`, `Vout`, `ClaimId`);
ALTER TABLE Addresses ADD COLUMN `Balance` DECIMAL(18,8) AS (`TotalReceived` - `TotalSent`) PERSISTENT AFTER `TotalSent`;
ALTER TABLE Addresses ADD INDEX `Idx_AddressBalance` (`Balance`);

View file

@ -491,6 +491,37 @@ class MainController extends AppController {
$this->set('sourceAddress', $sourceAddress); $this->set('sourceAddress', $sourceAddress);
} }
public function stats() {
$this->loadModel('Addresses');
$richList = $this->Addresses->find()->order(['Balance' => 'DESC'])->limit(500)->toArray();
$priceRate = 0;
$priceInfo = json_decode($this->redis->get(self::lbcPriceKey));
if (isset($priceInfo->price)) {
$priceRate = $priceInfo->price;
}
// calculate percentages
$totalBalance = 0;
$maxBalance = 0;
$minBalance = 0;
foreach ($richList as $item) {
$totalBalance = bcadd($totalBalance, $item->Balance, 8);
$minBalance = $minBalance == 0 ? $item->Balance : min($minBalance, $item->Balance);
$maxBalance = max($maxBalance, $item->Balance);
}
for ($i = 0; $i < count($richList); $i++) {
$item = $richList[$i];
$percentage = bcdiv($item->Balance, $totalBalance, 8) * 100;
$richList[$i]->Top500Percent = $percentage;
$richList[$i]->MinMaxPercent = bcdiv($item->Balance, $maxBalance, 8) * 100;
}
$this->set('richList', $richList);
$this->set('rate', $priceRate);
}
public function address($addr = null) { public function address($addr = null) {
set_time_limit(0); set_time_limit(0);
@ -558,7 +589,7 @@ class MainController extends AppController {
$offset = ($page - 1) * $pageLimit; $offset = ($page - 1) * $pageLimit;
} }
$stmt = $conn->execute('SELECT A.TotalReceived, A.TotalSent FROM Addresses A WHERE A.Id = ?', [$address->Id]); $stmt = $conn->execute('SELECT A.TotalReceived, A.TotalSent, A.Balance FROM Addresses A WHERE A.Id = ?', [$address->Id]);
$totals = $stmt->fetch(\PDO::FETCH_OBJ); $totals = $stmt->fetch(\PDO::FETCH_OBJ);
$stmt = $conn->execute(sprintf('SELECT T.Id, T.Hash, T.InputCount, T.OutputCount, T.Value, ' . $stmt = $conn->execute(sprintf('SELECT T.Id, T.Hash, T.InputCount, T.OutputCount, T.Value, ' .
@ -572,7 +603,7 @@ class MainController extends AppController {
$totalRecvAmount = $totals->TotalReceived == 0 ? '0' : $totals->TotalReceived + 0; $totalRecvAmount = $totals->TotalReceived == 0 ? '0' : $totals->TotalReceived + 0;
$totalSentAmount = $totals->TotalSent == 0 ? '0' : $totals->TotalSent + 0; $totalSentAmount = $totals->TotalSent == 0 ? '0' : $totals->TotalSent + 0;
$balanceAmount = bcsub($totalRecvAmount, $totalSentAmount, 8) + 0; $balanceAmount = $totals->Balance == 0 ? '0' : $totals->Balance + 0;
} }
$this->set('offset', $offset); $this->set('offset', $offset);

View file

@ -116,7 +116,7 @@
<div class="title">LBRY Block Explorer</div> <div class="title">LBRY Block Explorer</div>
<form method="get" action="/find"> <form method="get" action="/find">
<input class="search-input" name="q" type="text" placeholder="Enter a block height or hash, claim id or name, transaction hash or address" /> <input class="search-input" name="q" type="text" placeholder="Enter a block height or hash, claim id or name, transaction hash or address" />
<div class="ctls"><button class="btn btn-search">Search</button> <a href="/realtime">Realtime</a></div> <div class="ctls"><button class="btn btn-search">Search</button> <div class="links"><a href="/realtime">Realtime</a> &bull; <a href="/stats" class="last">Stats</a></div></div>
</form> </form>
</div> </div>

View file

@ -0,0 +1,53 @@
<?php $this->assign('title', 'Stats &amp; Rich List') ?>
<?php $this->start('script'); ?>
<script type="text/javascript">
</script>
<?php $this->end(); ?>
<?php echo $this->element('header') ?>
<div class="stats-head">
<h3>LBRY Stats</h3>
</div>
<div class="stats-main">
<div class="richlist">
<h3>LBRY Rich List (Top 500)</h3>
<table class="table">
<thead>
<tr>
<th class="w50 right">Rank</th>
<th class="w300 left">Address</th>
<th class="w150 right">Balance (LBC)</th>
<th class="w150 right">Balance (USD)</th>
<th class="w200 left med-pad-left">First Seen</th>
<th class="w200 center">% Top 500</th>
</tr>
</thead>
<tbody>
<?php $rank = 0; foreach ($richList as $item): $rank++; ?>
<tr>
<td class="right topvalign"><?php echo $rank ?></td>
<td class="topvalign"><a href="/address/<?php echo $item->Address ?>" target="_blank"><?php echo $item->Address ?></a>
<?php if (isset($item->Tag) && strlen(trim($item->Tag)) > 0): ?>
<div class="tag">
<?php if (strlen(trim($item->TagUrl)) > 0): ?><a href="<?php echo $item->TagUrl ?>" target="_blank" rel="nofollow"><?php echo $tiem->Tag ?></a><?php else: echo $item->Tag; endif; ?>
</div>
<?php endif; ?></td>
<td class="right topvalign"><?php echo number_format($item->Balance, 8, '.', ',') ?></td>
<td class="right topvalign">$<?php echo number_format(bcmul($item->Balance, $rate, 8), 2, '.', ',') ?></td>
<td class="med-pad-left topvalign"><?php echo $item->FirstSeen->format('d M Y H:i:s') . ' UTC'; ?></td>
<td class="w150 center top500-percent-cell"><div class="top500-percent" style="width: <?php echo $item->MinMaxPercent ?>%"></div><div class="text"><?php echo number_format($item->Top500Percent, 2, '.', '') ?>%</div></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<div class="clear"></div>
</div>

View file

@ -21,7 +21,9 @@ border-radius: 0 6px 8px 0 }
.home-container-cell .ctls { width: 720px; text-align: center; margin: 24px auto; position: relative } .home-container-cell .ctls { width: 720px; text-align: center; margin: 24px auto; position: relative }
.home-container-cell .ctls .btn-search { font-size: 115%; display: inline-block; padding: 12px 48px; background: #1e88e5; color: #fff; border-radius: 8px; border: none; font-weight: 300; cursor: pointer } .home-container-cell .ctls .btn-search { font-size: 115%; display: inline-block; padding: 12px 48px; background: #1e88e5; color: #fff; border-radius: 8px; border: none; font-weight: 300; cursor: pointer }
.home-container-cell .ctls .btn-search:hover { background: #1976d2 } .home-container-cell .ctls .btn-search:hover { background: #1976d2 }
.home-container-cell .ctls a { font-size: 115%; display: inline-block; font-weight: 300; position: absolute; right: 0; padding-top: 12px } .home-container-cell .ctls .links { display: inline-block; font-weight: 300; position: absolute; right: 0; padding-top: 12px; }
.home-container-cell .ctls a { font-size: 115%; display: inline-block; margin-right: 12px }
.home-container-cell .ctls a.last { margin-left: 12px; margin-right: 0 }
.home-container-cell .ctls a:hover { text-decoration: none; color: #1976d2 } .home-container-cell .ctls a:hover { text-decoration: none; color: #1976d2 }
.home-container-cell .recent-blocks { width: 1000px; margin: 48px auto 0 auto; box-shadow: 0 2px 6px rgba(0,0,0,.175); border: 1px solid rgba(0,0,0,.15); padding: 24px 24px 36px 24px; cursor: default; position: relative } .home-container-cell .recent-blocks { width: 1000px; margin: 48px auto 0 auto; box-shadow: 0 2px 6px rgba(0,0,0,.175); border: 1px solid rgba(0,0,0,.15); padding: 24px 24px 36px 24px; cursor: default; position: relative }
@ -122,14 +124,17 @@ border-radius: 0 6px 8px 0 }
.table thead tr th { border-top: 1px solid #ddd; border-bottom: 1px solid #ddd; padding: 12px 4px } .table thead tr th { border-top: 1px solid #ddd; border-bottom: 1px solid #ddd; padding: 12px 4px }
.table tbody tr td { padding: 8px; border-bottom: 1px solid #eee; font-weight: 300; white-space: nowrap } .table tbody tr td { padding: 8px; border-bottom: 1px solid #eee; font-weight: 300; white-space: nowrap }
.table tbody tr td.nodata { text-align: center; font-style: italic; font-weight: 300 } .table tbody tr td.nodata { text-align: center; font-style: italic; font-weight: 300 }
.table .topvalign { vertical-align: top }
.table .last-cell { padding-left: 48px } .table .last-cell { padding-left: 48px }
.table .pad-right { padding-right: 12px } .table .pad-right { padding-right: 12px }
.table .pad-left { padding-left: 48px } .table .pad-left { padding-left: 48px }
.table .med-pad-left { padding-left: 24px }
.left { text-align: left } .left { text-align: left }
.center { text-align: center } .center { text-align: center }
.right { text-align: right } .right { text-align: right }
.w50 { width: 50px }
.w80 { width: 80px } .w80 { width: 80px }
.w100 { width: 100px } .w100 { width: 100px }
.w125 { width: 125px } .w125 { width: 125px }
@ -139,6 +144,7 @@ border-radius: 0 6px 8px 0 }
.w250, .w250 > div { width: 250px } .w250, .w250 > div { width: 250px }
.w275, .w275 > div { width: 275px } .w275, .w275 > div { width: 275px }
.w300, .w300 > div { width: 300px } .w300, .w300 > div { width: 300px }
.w350 { width: 350px }
.w200 > div, .w250 > div, .w275 > div, .w300 > div { overflow: hidden; text-overflow: ellipsis; white-space: nowrap } .w200 > div, .w250 > div, .w275 > div, .w300 > div { overflow: hidden; text-overflow: ellipsis; white-space: nowrap }
footer { padding-top: 64px; font-size: 80%; cursor: default } footer { padding-top: 64px; font-size: 80%; cursor: default }
@ -196,6 +202,17 @@ footer .content .page-time { position: absolute; right: 12px; bottom: 0px; paddi
.realtime-main .realtime-blocks { width: 350px; box-shadow: 0 2px 6px rgba(0,0,0,.175); border: 1px solid rgba(0,0,0,.15); padding: 24px; cursor: default; float: left } .realtime-main .realtime-blocks { width: 350px; box-shadow: 0 2px 6px rgba(0,0,0,.175); border: 1px solid rgba(0,0,0,.15); padding: 24px; cursor: default; float: left }
.realtime-main .realtime-tx { width: 800px; margin-left: 50px; box-shadow: 0 2px 6px rgba(0,0,0,.175); border: 1px solid rgba(0,0,0,.15); padding: 24px; cursor: default; float: left } .realtime-main .realtime-tx { width: 800px; margin-left: 50px; box-shadow: 0 2px 6px rgba(0,0,0,.175); border: 1px solid rgba(0,0,0,.15); padding: 24px; cursor: default; float: left }
.stats-head { width: 1200px; margin: 0 auto 36px auto; cursor: default }
.stats-head h3 { font-weight: 300; margin: 0; font-size: 200% }
.stats-main .richlist { width: 1200px; margin: 0 auto; box-shadow: 0 2px 6px rgba(0,0,0,.175); border: 1px solid rgba(0,0,0,.15); padding: 24px; cursor: default }
.stats-main .richlist .tag { font-weight: normal; margin-top: 1px; font-size: 80%; color: #333 }
.stats-main .richlist .tag a:link, .tx-details-layout .tag a:visited { color: #333 }
.stats-main .richlist .tag a:hover { text-decoration: none }
.stats-main .richlist .top500-percent-cell { padding: 0; position: relative }
.stats-main .richlist .top500-percent { background: #e3f2fd; height: 100%; position: absolute; top: 0 }
.stats-main .richlist .top500-percent-cell .text { z-index: 100; position: relative }
.stats-main h3 { font-weight: 300; margin: 0 0 12px 0 }
.stats { width: 1000px; margin: 0 auto 48px auto; box-shadow: 0 2px 6px rgba(0,0,0,.175); border: 1px solid rgba(0,0,0,.15); padding: 24px; cursor: default } .stats { width: 1000px; margin: 0 auto 48px auto; box-shadow: 0 2px 6px rgba(0,0,0,.175); border: 1px solid rgba(0,0,0,.15); padding: 24px; cursor: default }
.stats .box { padding: 24px 0; border-right: 1px solid #ccc; float: left; text-align: center; width: 25% } .stats .box { padding: 24px 0; border-right: 1px solid #ccc; float: left; text-align: center; width: 25% }
.stats .box .title { color: #1e88e5; font-size: 90% } .stats .box .title { color: #1e88e5; font-size: 90% }