added rich list and stats page
This commit is contained in:
parent
6c2e6727a8
commit
abf84407cc
6 changed files with 111 additions and 5 deletions
|
@ -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']);
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
@ -233,3 +235,5 @@ CREATE TABLE `PriceHistory`
|
||||||
) 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`);
|
|
@ -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);
|
||||||
|
|
|
@ -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> • <a href="/stats" class="last">Stats</a></div></div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
53
src/Template/Main/stats.ctp
Normal file
53
src/Template/Main/stats.ctp
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
<?php $this->assign('title', 'Stats & 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>
|
|
@ -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% }
|
||||||
|
|
Loading…
Reference in a new issue