Added live stats
This commit is contained in:
parent
4c7e41b71b
commit
ca60e5e7f4
@ -20,7 +20,8 @@
|
|||||||
"enabled": true,
|
"enabled": true,
|
||||||
"siteTitle": "Cryppit",
|
"siteTitle": "Cryppit",
|
||||||
"port": 80,
|
"port": 80,
|
||||||
"statUpdateInterval": 5
|
"statUpdateInterval": 5,
|
||||||
|
"hashrateWindow": 600
|
||||||
},
|
},
|
||||||
"proxy": {
|
"proxy": {
|
||||||
"enabled": false,
|
"enabled": false,
|
||||||
|
|||||||
4
init.js
4
init.js
@ -174,7 +174,7 @@ var startPaymentProcessor = function(poolConfigs){
|
|||||||
worker.on('exit', function(code, signal){
|
worker.on('exit', function(code, signal){
|
||||||
logError('paymentProcessor', 'system', 'Payment processor died, spawning replacement...');
|
logError('paymentProcessor', 'system', 'Payment processor died, spawning replacement...');
|
||||||
setTimeout(function(){
|
setTimeout(function(){
|
||||||
startPaymentProcessor.apply(null, arguments);
|
startPaymentProcessor(poolConfigs);
|
||||||
}, 2000);
|
}, 2000);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -192,7 +192,7 @@ var startWebsite = function(portalConfig, poolConfigs){
|
|||||||
worker.on('exit', function(code, signal){
|
worker.on('exit', function(code, signal){
|
||||||
logError('website', 'system', 'Website process died, spawning replacement...');
|
logError('website', 'system', 'Website process died, spawning replacement...');
|
||||||
setTimeout(function(){
|
setTimeout(function(){
|
||||||
startWebsite.apply(null, arguments);
|
startWebsite(portalConfig, poolConfigs);
|
||||||
}, 2000);
|
}, 2000);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
116
libs/api.js
116
libs/api.js
@ -1,116 +0,0 @@
|
|||||||
var redis = require('redis');
|
|
||||||
var async = require('async');
|
|
||||||
|
|
||||||
var os = require('os');
|
|
||||||
|
|
||||||
|
|
||||||
module.exports = function(logger, poolConfigs){
|
|
||||||
|
|
||||||
var _this = this;
|
|
||||||
|
|
||||||
var redisClients = [];
|
|
||||||
|
|
||||||
Object.keys(poolConfigs).forEach(function(coin){
|
|
||||||
var poolConfig = poolConfigs[coin];
|
|
||||||
var internalConfig = poolConfig.shareProcessing.internal;
|
|
||||||
var redisConfig = internalConfig.redis;
|
|
||||||
|
|
||||||
for (var i = 0; i < redisClients.length; i++){
|
|
||||||
var client = redisClients[i];
|
|
||||||
if (client.client.port === redisConfig.port && client.client.host === redisConfig.host){
|
|
||||||
client.coins.push(coin);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
redisClients.push({
|
|
||||||
coins: [coin],
|
|
||||||
client: redis.createClient(redisConfig.port, redisConfig.host)
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
//Every 10 minutes clear out old hashrate stats for each coin from redis
|
|
||||||
var clearExpiredHashrates = function(){
|
|
||||||
redisClients.forEach(function(client){
|
|
||||||
var tenMinutesAgo = (Date.now() / 1000 | 0) - (60 * 10);
|
|
||||||
var redisCommands = client.coins.map(function(coin){
|
|
||||||
return ['zremrangebyscore', coin + '_hashrate', '-inf', tenMinutesAgo];
|
|
||||||
});
|
|
||||||
client.client.multi(redisCommands).exec(function(err, replies){
|
|
||||||
if (err)
|
|
||||||
console.log('error with clearing old hashrates ' + JSON.stringify(err));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
setInterval(clearExpiredHashrates, 10 * 60 * 1000);
|
|
||||||
clearExpiredHashrates();
|
|
||||||
|
|
||||||
|
|
||||||
this.stats = {};
|
|
||||||
|
|
||||||
|
|
||||||
this.getStats = function(callback){
|
|
||||||
|
|
||||||
async.map(redisClients, function(client, callback){
|
|
||||||
var tenMinutesAgo = (Date.now() / 1000 | 0) - (60 * 10);
|
|
||||||
var redisCommands = client.coins.map(function(coin){
|
|
||||||
return ['zrangebyscore', coin + '_hashrate', tenMinutesAgo, '+inf'];
|
|
||||||
});
|
|
||||||
client.client.multi(redisCommands).exec(function(err, replies){
|
|
||||||
if (err){
|
|
||||||
console.log('error with getting hashrate stats ' + JSON.stringify(err));
|
|
||||||
callback(err);
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
var replyObj = {};
|
|
||||||
for(var i = 0; i < replies.length; i++){
|
|
||||||
replyObj[client.coins[i]] = replies[i];
|
|
||||||
}
|
|
||||||
callback(null, replyObj);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}, function(err, results){
|
|
||||||
|
|
||||||
var portalStats = {
|
|
||||||
global:{
|
|
||||||
workers: 0,
|
|
||||||
shares: 0
|
|
||||||
},
|
|
||||||
pools:{
|
|
||||||
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
results.forEach(function(r){
|
|
||||||
var coin = Object.keys(r)[0];
|
|
||||||
var coinStats = {workers: {}, shares: 0};
|
|
||||||
r[coin].forEach(function(ins){
|
|
||||||
var parts = ins.split(':');
|
|
||||||
var workerShares = parseInt(parts[0]);;
|
|
||||||
coinStats.shares += workerShares
|
|
||||||
var worker = parts[1];
|
|
||||||
if (worker in coinStats.workers)
|
|
||||||
coinStats.workers[worker] += workerShares
|
|
||||||
else
|
|
||||||
coinStats.workers[worker] = workerShares
|
|
||||||
});
|
|
||||||
portalStats.pools[coin] = coinStats;
|
|
||||||
portalStats.global.shares += coinStats.shares;
|
|
||||||
portalStats.global.workers += Object.keys(coinStats.workers).length;
|
|
||||||
});
|
|
||||||
|
|
||||||
_this.stats = portalStats;
|
|
||||||
callback();
|
|
||||||
});
|
|
||||||
|
|
||||||
/*
|
|
||||||
{ global: {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
//get stats like hashrate and in/valid shares/blocks and workers in current round
|
|
||||||
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
123
libs/stats.js
Normal file
123
libs/stats.js
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
var redis = require('redis');
|
||||||
|
var async = require('async');
|
||||||
|
|
||||||
|
var os = require('os');
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = function(logger, portalConfig, poolConfigs){
|
||||||
|
|
||||||
|
var _this = this;
|
||||||
|
|
||||||
|
var redisClients = [];
|
||||||
|
|
||||||
|
Object.keys(poolConfigs).forEach(function(coin){
|
||||||
|
var poolConfig = poolConfigs[coin];
|
||||||
|
var internalConfig = poolConfig.shareProcessing.internal;
|
||||||
|
var redisConfig = internalConfig.redis;
|
||||||
|
|
||||||
|
for (var i = 0; i < redisClients.length; i++){
|
||||||
|
var client = redisClients[i];
|
||||||
|
if (client.client.port === redisConfig.port && client.client.host === redisConfig.host){
|
||||||
|
client.coins.push(coin);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
redisClients.push({
|
||||||
|
coins: [coin],
|
||||||
|
client: redis.createClient(redisConfig.port, redisConfig.host)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
this.stats = {};
|
||||||
|
|
||||||
|
|
||||||
|
this.getStats = function(callback){
|
||||||
|
|
||||||
|
var allCoinStats = [];
|
||||||
|
|
||||||
|
async.each(redisClients, function(client, callback){
|
||||||
|
var windowTime = (Date.now() / 1000 | 0) - portalConfig.website.hashrateWindow;
|
||||||
|
var redisCommands = [];
|
||||||
|
var commandsPerCoin = 4;
|
||||||
|
|
||||||
|
//Clear out old hashrate stats for each coin from redis
|
||||||
|
client.coins.forEach(function(coin){
|
||||||
|
redisCommands.push(['zremrangebyscore', coin + '_hashrate', '-inf', windowTime]);
|
||||||
|
redisCommands.push(['zrangebyscore', coin + '_hashrate', windowTime, '+inf']);
|
||||||
|
redisCommands.push(['hgetall', coin + '_stats']);
|
||||||
|
redisCommands.push(['scard', coin + '_blocks']);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
client.client.multi(redisCommands).exec(function(err, replies){
|
||||||
|
if (err){
|
||||||
|
console.log('error with getting hashrate stats ' + JSON.stringify(err));
|
||||||
|
callback(err);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
for(var i = 0; i < replies.length; i += commandsPerCoin){
|
||||||
|
var coinStats = {
|
||||||
|
coinName: client.coins[i / commandsPerCoin | 0],
|
||||||
|
hashrates: replies[i + 1],
|
||||||
|
poolStats: replies[i + 2],
|
||||||
|
poolPendingBlocks: replies[i + 3]
|
||||||
|
};
|
||||||
|
allCoinStats.push(coinStats)
|
||||||
|
|
||||||
|
}
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, function(err){
|
||||||
|
if (err){
|
||||||
|
console.log('error getting all stats' + JSON.stringify(err));
|
||||||
|
callback();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var portalStats = {
|
||||||
|
global:{
|
||||||
|
workers: 0,
|
||||||
|
hashrate: 0
|
||||||
|
},
|
||||||
|
pools: allCoinStats
|
||||||
|
};
|
||||||
|
|
||||||
|
allCoinStats.forEach(function(coinStats){
|
||||||
|
coinStats.workers = {};
|
||||||
|
coinStats.shares = 0;
|
||||||
|
coinStats.hashrates.forEach(function(ins){
|
||||||
|
var parts = ins.split(':');
|
||||||
|
var workerShares = parseInt(parts[0]);
|
||||||
|
coinStats.shares += workerShares;
|
||||||
|
var worker = parts[1];
|
||||||
|
if (worker in coinStats.workers)
|
||||||
|
coinStats.workers[worker] += workerShares
|
||||||
|
else
|
||||||
|
coinStats.workers[worker] = workerShares
|
||||||
|
});
|
||||||
|
coinStats.hashrate = (coinStats.shares * 4294967296 / portalConfig.website.hashrateWindow) / 100000000 | 0;
|
||||||
|
delete coinStats.hashrates;
|
||||||
|
portalStats.global.hashrate += coinStats.hashrate;
|
||||||
|
portalStats.global.workers += Object.keys(coinStats.workers).length;
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(JSON.stringify(portalStats, null, 4));
|
||||||
|
|
||||||
|
_this.stats = portalStats;
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
{ global: {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
//get stats like hashrate and in/valid shares/blocks and workers in current round
|
||||||
|
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
@ -31,7 +31,7 @@ var async = require('async');
|
|||||||
var dot = require('dot');
|
var dot = require('dot');
|
||||||
var express = require('express');
|
var express = require('express');
|
||||||
|
|
||||||
var api = require('./api.js');
|
var stats = require('./stats.js');
|
||||||
|
|
||||||
|
|
||||||
module.exports = function(logger){
|
module.exports = function(logger){
|
||||||
@ -41,7 +41,7 @@ module.exports = function(logger){
|
|||||||
|
|
||||||
var websiteConfig = portalConfig.website;
|
var websiteConfig = portalConfig.website;
|
||||||
|
|
||||||
var portalApi = new api(logger, poolConfigs);
|
var portalStats = new stats(logger, portalConfig, poolConfigs);
|
||||||
|
|
||||||
var logIdentify = 'Website';
|
var logIdentify = 'Website';
|
||||||
|
|
||||||
@ -75,7 +75,7 @@ module.exports = function(logger){
|
|||||||
for (var pageName in pageTemplates){
|
for (var pageName in pageTemplates){
|
||||||
pageProcessed[pageName] = pageTemplates[pageName]({
|
pageProcessed[pageName] = pageTemplates[pageName]({
|
||||||
poolsConfigs: poolConfigs,
|
poolsConfigs: poolConfigs,
|
||||||
stats: portalApi.stats,
|
stats: portalStats.stats,
|
||||||
portalConfig: portalConfig
|
portalConfig: portalConfig
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -107,13 +107,20 @@ module.exports = function(logger){
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
portalApi.getStats(function(){
|
portalStats.getStats(function(){
|
||||||
readPageFiles(Object.keys(pageFiles));
|
readPageFiles(Object.keys(pageFiles));
|
||||||
});
|
});
|
||||||
|
|
||||||
var buildUpdatedWebsite = function(){
|
var buildUpdatedWebsite = function(){
|
||||||
portalApi.getStats(function(){
|
portalStats.getStats(function(){
|
||||||
processTemplates();
|
processTemplates();
|
||||||
|
|
||||||
|
var statData = 'data: ' + JSON.stringify(portalStats.stats) + '\n\n';
|
||||||
|
for (var uid in liveStatConnections){
|
||||||
|
var res = liveStatConnections[uid];
|
||||||
|
res.write(statData);
|
||||||
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -136,17 +143,21 @@ module.exports = function(logger){
|
|||||||
var data = pageTemplates.index({
|
var data = pageTemplates.index({
|
||||||
page: requestedPage,
|
page: requestedPage,
|
||||||
selected: pageId,
|
selected: pageId,
|
||||||
stats: portalApi.stats,
|
stats: portalStats.stats,
|
||||||
poolConfigs: poolConfigs,
|
poolConfigs: poolConfigs,
|
||||||
portalConfig: portalConfig
|
portalConfig: portalConfig
|
||||||
});
|
});
|
||||||
res.send(data);
|
res.end(data);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
next();
|
next();
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
var liveStatConnections = {};
|
||||||
|
|
||||||
|
|
||||||
app.get('/:page', route);
|
app.get('/:page', route);
|
||||||
app.get('/', route);
|
app.get('/', route);
|
||||||
|
|
||||||
@ -156,21 +167,35 @@ module.exports = function(logger){
|
|||||||
case 'get_page':
|
case 'get_page':
|
||||||
var requestedPage = getPage(req.query.id);
|
var requestedPage = getPage(req.query.id);
|
||||||
if (requestedPage){
|
if (requestedPage){
|
||||||
res.send(requestedPage);
|
res.end(requestedPage);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
case 'live_stats':
|
||||||
|
res.writeHead(200, {
|
||||||
|
'Content-Type': 'text/event-stream',
|
||||||
|
'Cache-Control': 'no-cache',
|
||||||
|
'Connection': 'keep-alive'
|
||||||
|
});
|
||||||
|
res.write('\n');
|
||||||
|
var uid = Math.random().toString();
|
||||||
|
liveStatConnections[uid] = res;
|
||||||
|
req.on("close", function() {
|
||||||
|
delete liveStatConnections[uid];
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
default:
|
default:
|
||||||
next();
|
next();
|
||||||
}
|
}
|
||||||
|
|
||||||
res.send('you did api method ' + req.params.method);
|
//res.send('you did api method ' + req.params.method);
|
||||||
});
|
});
|
||||||
|
|
||||||
app.use('/static', express.static('website'));
|
app.use('/static', express.static('website'));
|
||||||
|
|
||||||
app.use(function(err, req, res, next){
|
app.use(function(err, req, res, next){
|
||||||
console.error(err.stack);
|
console.error(err.stack);
|
||||||
res.send(500, 'Something broke!');
|
res.end(500, 'Something broke!');
|
||||||
});
|
});
|
||||||
|
|
||||||
app.listen(portalConfig.website.port, function(){
|
app.listen(portalConfig.website.port, function(){
|
||||||
|
|||||||
@ -1,33 +0,0 @@
|
|||||||
var stats = {
|
|
||||||
|
|
||||||
global:{
|
|
||||||
hashrate: 1000, //in KH/s
|
|
||||||
validShares: 1,
|
|
||||||
invalidShares: 1,
|
|
||||||
validBlocks: 1,
|
|
||||||
invalidBlocks: 1,
|
|
||||||
blocksPending: 1,
|
|
||||||
blocksConfirmed: 1,
|
|
||||||
blocksOrphaned: 1,
|
|
||||||
connectedMiners: 344
|
|
||||||
},
|
|
||||||
pools:[
|
|
||||||
{
|
|
||||||
coin: "Dogecoin",
|
|
||||||
sybmol: 'doge',
|
|
||||||
stats:{
|
|
||||||
hashrate: 1000, //in KH/s
|
|
||||||
validShares: 1,
|
|
||||||
invalidShares: 1,
|
|
||||||
validBlocks: 1,
|
|
||||||
invalidBlocks: 1,
|
|
||||||
blocksPending: 1,
|
|
||||||
blocksConfirmed: 1,
|
|
||||||
blocksOrphaned: 1,
|
|
||||||
connectedMiners: 34545,
|
|
||||||
totalPayedOut: 3343.789797
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -23,11 +23,36 @@
|
|||||||
<header class="header">
|
<header class="header">
|
||||||
<div class="pure-menu pure-menu-open pure-menu-horizontal">
|
<div class="pure-menu pure-menu-open pure-menu-horizontal">
|
||||||
<ul>
|
<ul>
|
||||||
<li id="home" class="{{? it.selected === '' }}selected{{?}}"><a class="hot-swapper" href="/"><i class="fa fa-home"></i> Home</a></li>
|
<li id="home" class="{{? it.selected === '' }}selected{{?}}">
|
||||||
<li class="{{? it.selected === 'getting_started' }}selected{{?}}"><a class="hot-swapper" href="/getting_started"><i class="fa fa-arrow-right"></i> Getting Started</a></li>
|
<a class="hot-swapper" href="/"><i class="fa fa-home"></i>
|
||||||
<li class="{{? it.selected === 'stats' }}selected{{?}}"><a class="hot-swapper" href="/stats"><i class="fa fa-bar-chart-o"></i> Stats</a></li>
|
Home
|
||||||
<li class="{{? it.selected === 'api' }}selected{{?}}"><a class="hot-swapper" href="/api"><i class="fa fa-code"></i> API</a></li>
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="{{? it.selected === 'getting_started' }}selected{{?}}">
|
||||||
|
<a class="hot-swapper" href="/getting_started">
|
||||||
|
<i class="fa fa-rocket"></i>
|
||||||
|
Getting Started
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="{{? it.selected === 'stats' }}selected{{?}}">
|
||||||
|
<a class="hot-swapper" href="/stats">
|
||||||
|
<i class="fa fa-bar-chart-o"></i>
|
||||||
|
Stats
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="{{? it.selected === 'api' }}selected{{?}}">
|
||||||
|
<a class="hot-swapper" href="/api">
|
||||||
|
<i class="fa fa-code"></i>
|
||||||
|
API
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
<div class="stats">
|
||||||
|
<div><i class="fa fa-users"></i> <span id="statsMiners">{{=it.stats.global.workers}}</span> Miners</div>
|
||||||
|
<div><i class="fa fa-tachometer"></i> <span id="statsHashrate">{{=it.stats.global.hashrate}}</span> KH/s</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
@ -40,10 +65,13 @@
|
|||||||
|
|
||||||
<footer>
|
<footer>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
This site is powered by the open source <a target="_blank" href="https://github.com/zone117x/node-open-mining-portal/">NOMP</a>
|
||||||
<div>This site is powered by the open source <a href="https://github.com/zone117x/node-open-mining-portal/">NOMP</a> project created by Matthew Little and licensed under the <a href="http://www.gnu.org/licenses/gpl-2.0.html">GPL</a></div>
|
project created by Matthew Little and licensed under the <a href="http://www.gnu.org/licenses/gpl-2.0.html">GPL</a>
|
||||||
<div><i class="fa fa-heart"></i> Support this project by donating <i class="fa fa-btc"></i> BTC: 1KRotMnQpxu3sePQnsVLRy3EraRFYfJQFR</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<i class="fa fa-heart"></i> Support this project by donating <i class="fa fa-btc"></i> BTC: 1KRotMnQpxu3sePQnsVLRy3EraRFYfJQFR
|
||||||
|
</div>
|
||||||
<div id="communityFooter">
|
<div id="communityFooter">
|
||||||
Community <i class="fa fa-comment"></i> : <a target="_blank" href="https://kiwiirc.com/client/irc.freenode.net/nomp">#nomp IRC</a>
|
Community <i class="fa fa-comment"></i> : <a target="_blank" href="https://kiwiirc.com/client/irc.freenode.net/nomp">#nomp IRC</a>
|
||||||
|
|
|
|
||||||
|
|||||||
@ -12,6 +12,7 @@ $(function(){
|
|||||||
};
|
};
|
||||||
|
|
||||||
$('.hot-swapper').click(function(event){
|
$('.hot-swapper').click(function(event){
|
||||||
|
if (event.which !== 1) return;
|
||||||
var pageId = $(this).attr('href').slice(1);
|
var pageId = $(this).attr('href').slice(1);
|
||||||
hotSwap(pageId, true);
|
hotSwap(pageId, true);
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
@ -26,4 +27,11 @@ $(function(){
|
|||||||
}, 0);
|
}, 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var statsSource = new EventSource("/api/live_stats");
|
||||||
|
statsSource.addEventListener('message', function(e){
|
||||||
|
var stats = JSON.parse(e.data);
|
||||||
|
$('#statsMiners').text(stats.global.workers);
|
||||||
|
$('#statsHashrate').text(stats.global.hashrate);
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
@ -22,9 +22,21 @@ header{
|
|||||||
background-color: #f3f2ef;
|
background-color: #f3f2ef;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
header > div{
|
||||||
|
display: -moz-box;
|
||||||
|
display: -webkit-flexbox;
|
||||||
|
display: -ms-flexbox;
|
||||||
|
display: -webkit-flex;
|
||||||
|
display: -moz-flex;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
header > div > ul{
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
header .selected, header a:active, header a:focus{
|
header .selected, header a:active, header a:focus{
|
||||||
background-color: #404040 !important;
|
background-color: #404040 !important;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.selected > a {
|
.selected > a {
|
||||||
@ -41,17 +53,42 @@ header a:hover {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.stats{
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats > div{
|
||||||
|
padding: 2px 15px;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: #f2f2f2;
|
||||||
|
font-size: 0.95em;
|
||||||
|
width: 120px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats > div:nth-child(1){
|
||||||
|
background-color: #71a380;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
.stats > div:nth-child(2){
|
||||||
|
background-color: #7196a3;
|
||||||
|
}
|
||||||
|
|
||||||
.pure-menu-heading{
|
.pure-menu-heading{
|
||||||
padding-left: 0 !important;
|
padding-left: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
header > div{
|
header > div{
|
||||||
background-color: transparent !important;
|
background-color: transparent !important;
|
||||||
max-width: 768px;
|
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
main > div, header > div{
|
||||||
|
max-width: 800px;
|
||||||
|
}
|
||||||
|
|
||||||
main {
|
main {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
@ -59,11 +96,11 @@ main {
|
|||||||
}
|
}
|
||||||
|
|
||||||
main > div{
|
main > div{
|
||||||
max-width: 768px;
|
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
footer{
|
footer{
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: #b3b3b3;
|
color: #b3b3b3;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user