Database indexing: Full Address history, fix balances, add db file locks
This commit is contained in:
@@ -184,7 +184,7 @@ app.use('/ext/getbasicstats', function(req,res){
|
|||||||
|
|
||||||
app.use('/ext/getaddresstxsajax', function(req,res){
|
app.use('/ext/getaddresstxsajax', function(req,res){
|
||||||
req.query.length = parseInt(req.query.length);
|
req.query.length = parseInt(req.query.length);
|
||||||
if(isNaN(req.query.length) || req.query.length > settings.index.last_txs){
|
if(isNaN(req.query.length) || req.query.length > settings.txcount){
|
||||||
req.query.length = settings.txcount;
|
req.query.length = settings.txcount;
|
||||||
}
|
}
|
||||||
db.get_address_txs_ajax(req.query.address, req.query.start, req.query.length,function(txs, count){
|
db.get_address_txs_ajax(req.query.address, req.query.start, req.query.length,function(txs, count){
|
||||||
@@ -196,13 +196,13 @@ app.use('/ext/getaddresstxsajax', function(req,res){
|
|||||||
|
|
||||||
txs[i].vout.forEach(function (r) {
|
txs[i].vout.forEach(function (r) {
|
||||||
if (r.addresses == req.query.address) {
|
if (r.addresses == req.query.address) {
|
||||||
out = r.amount;
|
out += r.amount;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
txs[i].vin.forEach(function (s) {
|
txs[i].vin.forEach(function (s) {
|
||||||
if (s.addresses == req.query.address) {
|
if (s.addresses == req.query.address) {
|
||||||
vin = s.amount
|
vin += s.amount
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -247,6 +247,7 @@ app.set('website', settings.website);
|
|||||||
app.set('genesis_block', settings.genesis_block);
|
app.set('genesis_block', settings.genesis_block);
|
||||||
app.set('index', settings.index);
|
app.set('index', settings.index);
|
||||||
app.set('heavy', settings.heavy);
|
app.set('heavy', settings.heavy);
|
||||||
|
app.set('lock_during_index', settings.lock_during_index);
|
||||||
app.set('txcount', settings.txcount);
|
app.set('txcount', settings.txcount);
|
||||||
app.set('nethash', settings.nethash);
|
app.set('nethash', settings.nethash);
|
||||||
app.set('nethash_units', settings.nethash_units);
|
app.set('nethash_units', settings.nethash_units);
|
||||||
@@ -304,4 +305,4 @@ app.use(function(err, req, res, next) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = app;
|
module.exports = app;
|
||||||
+198
-115
@@ -9,6 +9,7 @@ var mongoose = require('mongoose')
|
|||||||
, Heavy = require('../models/heavy')
|
, Heavy = require('../models/heavy')
|
||||||
, lib = require('./explorer')
|
, lib = require('./explorer')
|
||||||
, settings = require('./settings')
|
, settings = require('./settings')
|
||||||
|
, fs = require('fs')
|
||||||
, poloniex = require('./markets/poloniex')
|
, poloniex = require('./markets/poloniex')
|
||||||
, bittrex = require('./markets/bittrex')
|
, bittrex = require('./markets/bittrex')
|
||||||
, bleutrade = require('./markets/bleutrade')
|
, bleutrade = require('./markets/bleutrade')
|
||||||
@@ -39,6 +40,16 @@ function find_address(hash, caseSensitive, cb) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function find_address_tx(address, hash, cb) {
|
||||||
|
AddressTx.findOne({a_id: address, txid: hash}, function(err, address_tx) {
|
||||||
|
if(address_tx) {
|
||||||
|
return cb(address_tx);
|
||||||
|
} else {
|
||||||
|
return cb();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function find_richlist(coin, cb) {
|
function find_richlist(coin, cb) {
|
||||||
Richlist.findOne({coin: coin}, function(err, richlist) {
|
Richlist.findOne({coin: coin}, function(err, richlist) {
|
||||||
if(richlist) {
|
if(richlist) {
|
||||||
@@ -62,41 +73,38 @@ function update_address(hash, txid, amount, type, cb) {
|
|||||||
return cb();
|
return cb();
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
//Considering no duplicate
|
var received = address.received;
|
||||||
var tx_array = [];
|
var sent = address.sent;
|
||||||
var received = address.received;
|
if (type == 'vin') {
|
||||||
var sent = address.sent;
|
sent = sent + amount;
|
||||||
if (type == 'vin') {
|
} else {
|
||||||
sent = sent + amount;
|
received = received + amount;
|
||||||
} else {
|
}
|
||||||
received = received + amount;
|
Address.updateOne({a_id:hash}, {
|
||||||
|
received: received,
|
||||||
|
sent: sent,
|
||||||
|
balance: received - sent
|
||||||
|
}, function() {
|
||||||
|
// ensure tx doesnt already exist in address.txs
|
||||||
|
find_address_tx(hash, txid, function(address_tx) {
|
||||||
|
if (typeof address_tx == "undefined") {
|
||||||
|
var newAddressTx = new AddressTx({
|
||||||
|
a_id: hash,
|
||||||
|
txid: txid
|
||||||
|
});
|
||||||
|
newAddressTx.save(function(err) {
|
||||||
|
if (err) {
|
||||||
|
return cb(err);
|
||||||
|
} else {
|
||||||
|
return cb();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return cb(); //duplicate
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
tx_array.push({addresses: txid, type: type});
|
|
||||||
|
|
||||||
Address.updateOne({a_id:hash}, {
|
|
||||||
received: received,
|
|
||||||
sent: sent,
|
|
||||||
balance: received - sent
|
|
||||||
}, function() {
|
|
||||||
|
|
||||||
var newAddressTx = new AddressTx({
|
|
||||||
a_id: hash,
|
|
||||||
addresses: txid,
|
|
||||||
type: type
|
|
||||||
});
|
|
||||||
newAddressTx.save(function(err) {
|
|
||||||
if (err) {
|
|
||||||
return cb(err);
|
|
||||||
} else {
|
|
||||||
//console.log('address saved: %s', hash);
|
|
||||||
//console.log(newAddress);
|
|
||||||
return cb();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return cb();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
//new address
|
//new address
|
||||||
if (type == 'vin') {
|
if (type == 'vin') {
|
||||||
@@ -117,24 +125,17 @@ function update_address(hash, txid, amount, type, cb) {
|
|||||||
if (err) {
|
if (err) {
|
||||||
return cb(err);
|
return cb(err);
|
||||||
} else {
|
} else {
|
||||||
//console.log('address saved: %s', hash);
|
|
||||||
//console.log(newAddress);
|
|
||||||
|
|
||||||
var newAddressTx = new AddressTx({
|
var newAddressTx = new AddressTx({
|
||||||
a_id: hash,
|
a_id: hash,
|
||||||
addresses: txid,
|
txid: txid
|
||||||
type: type
|
|
||||||
});
|
});
|
||||||
newAddressTx.save(function(err) {
|
newAddressTx.save(function(err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
return cb(err);
|
return cb(err);
|
||||||
} else {
|
} else {
|
||||||
//console.log('address saved: %s', hash);
|
|
||||||
//console.log(newAddress);
|
|
||||||
return cb();
|
return cb();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return cb();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -255,6 +256,53 @@ function get_market_data(market, cb) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function create_lock(lockfile, cb) {
|
||||||
|
if (settings.lock_during_index == true) {
|
||||||
|
var fname = './tmp/' + lockfile + '.pid';
|
||||||
|
fs.appendFile(fname, process.pid, function (err) {
|
||||||
|
if (err) {
|
||||||
|
console.log("Error: unable to create %s", fname);
|
||||||
|
process.exit(1);
|
||||||
|
} else {
|
||||||
|
return cb();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return cb();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function remove_lock(lockfile, cb) {
|
||||||
|
if (settings.lock_during_index == true) {
|
||||||
|
var fname = './tmp/' + lockfile + '.pid';
|
||||||
|
fs.unlink(fname, function (err){
|
||||||
|
if(err) {
|
||||||
|
console.log("unable to remove lock: %s", fname);
|
||||||
|
process.exit(1);
|
||||||
|
} else {
|
||||||
|
return cb();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return cb();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function is_locked(lockfile, cb) {
|
||||||
|
if (settings.lock_during_index == true) {
|
||||||
|
var fname = './tmp/' + lockfile + '.pid';
|
||||||
|
fs.exists(fname, function (exists){
|
||||||
|
if(exists) {
|
||||||
|
return cb(true);
|
||||||
|
} else {
|
||||||
|
return cb(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return cb(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
// initialize DB
|
// initialize DB
|
||||||
connect: function(database, cb) {
|
connect: function(database, cb) {
|
||||||
@@ -270,6 +318,16 @@ module.exports = {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
is_locked: function(cb) {
|
||||||
|
is_locked("db_index", function (exists) {
|
||||||
|
if (exists) {
|
||||||
|
return cb(true);
|
||||||
|
} else {
|
||||||
|
return cb(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
check_stats: function(coin, cb) {
|
check_stats: function(coin, cb) {
|
||||||
Stats.findOne({coin: coin}, function(err, stats) {
|
Stats.findOne({coin: coin}, function(err, stats) {
|
||||||
if(stats) {
|
if(stats) {
|
||||||
@@ -380,29 +438,43 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
create_tx: function(txid, cb) {
|
create_tx: function(txid, cb) {
|
||||||
save_tx(txid, function(err){
|
is_locked("db_index", function (exists) {
|
||||||
if (err) {
|
if (exists) {
|
||||||
return cb(err);
|
console.log("db_index lock file exists...");
|
||||||
} else {
|
|
||||||
//console.log('tx stored: %s', txid);
|
|
||||||
return cb();
|
return cb();
|
||||||
|
} else {
|
||||||
|
save_tx(txid, function(err){
|
||||||
|
if (err) {
|
||||||
|
return cb(err);
|
||||||
|
} else {
|
||||||
|
//console.log('tx stored: %s', txid);
|
||||||
|
return cb();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
create_txs: function(block, cb) {
|
create_txs: function(block, cb) {
|
||||||
lib.syncLoop(block.tx.length, function (loop) {
|
is_locked("db_index", function (exists) {
|
||||||
var i = loop.iteration();
|
if (exists) {
|
||||||
save_tx(block.tx[i], function(err){
|
console.log("db_index lock file exists...");
|
||||||
if (err) {
|
return cb();
|
||||||
loop.next();
|
} else {
|
||||||
} else {
|
lib.syncLoop(block.tx.length, function (loop) {
|
||||||
//console.log('tx stored: %s', block.tx[i]);
|
var i = loop.iteration();
|
||||||
loop.next();
|
save_tx(block.tx[i], function(err){
|
||||||
}
|
if (err) {
|
||||||
});
|
loop.next();
|
||||||
}, function(){
|
} else {
|
||||||
return cb();
|
//console.log('tx stored: %s', block.tx[i]);
|
||||||
|
loop.next();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, function(){
|
||||||
|
return cb();
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -432,7 +504,6 @@ module.exports = {
|
|||||||
if (err) {
|
if (err) {
|
||||||
return cb(err);
|
return cb(err);
|
||||||
} else {
|
} else {
|
||||||
//console.log(address);
|
|
||||||
var txs = [];
|
var txs = [];
|
||||||
var count = address.length;
|
var count = address.length;
|
||||||
var hashes = address;
|
var hashes = address;
|
||||||
@@ -441,13 +512,15 @@ module.exports = {
|
|||||||
|
|
||||||
lib.syncLoop(count, function (loop) {
|
lib.syncLoop(count, function (loop) {
|
||||||
var i = loop.iteration();
|
var i = loop.iteration();
|
||||||
find_tx(hashes[i].addresses, function (tx) {
|
find_tx(hashes[i].txid, function (tx) {
|
||||||
if (tx) {
|
if (tx && !txs.includes(tx)) {
|
||||||
txs.push(tx);
|
txs.push(tx);
|
||||||
loop.next();
|
loop.next();
|
||||||
} else {
|
} else if (!txs.includes(tx)) {
|
||||||
txs.push("1. Not found");
|
txs.push("1. Not found");
|
||||||
loop.next();
|
loop.next();
|
||||||
|
} else {
|
||||||
|
loop.next();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}, function () {
|
}, function () {
|
||||||
@@ -738,65 +811,75 @@ module.exports = {
|
|||||||
|
|
||||||
// updates tx, address & richlist db's; called by sync.js
|
// updates tx, address & richlist db's; called by sync.js
|
||||||
update_tx_db: function(coin, start, end, timeout, cb) {
|
update_tx_db: function(coin, start, end, timeout, cb) {
|
||||||
var complete = false;
|
is_locked("db_index", function (exists) {
|
||||||
if (start < 1) start = 1; // fix for invalid block height (skip genesis block as it should not have valid txs)
|
if (exists) {
|
||||||
lib.syncLoop((end - start) + 1, function (loop) {
|
console.log("db_index lock file exists...");
|
||||||
var x = loop.iteration();
|
return cb();
|
||||||
if (x % 5000 === 0) {
|
} else {
|
||||||
Tx.find({}).where('blockindex').lt(start + x).sort({timestamp: 'desc'}).limit(settings.index.last_txs).exec(function(err, txs){
|
create_lock("db_index", function (){
|
||||||
Stats.updateOne({coin: coin}, {
|
var complete = false;
|
||||||
last: start + x - 1,
|
lib.syncLoop((end - start) + 1, function (loop) {
|
||||||
last_txs: '' //not used anymore left to clear out existing objects
|
var x = loop.iteration();
|
||||||
}, function() {});
|
if (x % 5000 === 0) {
|
||||||
});
|
Tx.find({}).where('blockindex').lt(start + x).sort({timestamp: 'desc'}).limit(settings.index.last_txs).exec(function(err, txs){
|
||||||
}
|
Stats.updateOne({coin: coin}, {
|
||||||
lib.get_blockhash(start + x, function(blockhash){
|
last: start + x - 1,
|
||||||
if (blockhash) {
|
last_txs: '' //not used anymore left to clear out existing objects
|
||||||
lib.get_block(blockhash, function(block) {
|
}, function() {});
|
||||||
if (block) {
|
});
|
||||||
lib.syncLoop(block.tx.length, function (subloop) {
|
}
|
||||||
var i = subloop.iteration();
|
lib.get_blockhash(start + x, function(blockhash){
|
||||||
Tx.findOne({txid: block.tx[i]}, function(err, tx) {
|
if (blockhash) {
|
||||||
if(tx) {
|
lib.get_block(blockhash, function(block) {
|
||||||
tx = null;
|
if (block) {
|
||||||
subloop.next();
|
lib.syncLoop(block.tx.length, function (subloop) {
|
||||||
} else {
|
var i = subloop.iteration();
|
||||||
save_tx(block.tx[i], function(err){
|
Tx.findOne({txid: block.tx[i]}, function(err, tx) {
|
||||||
if (err) {
|
if(tx) {
|
||||||
console.log(err);
|
tx = null;
|
||||||
} else {
|
subloop.next();
|
||||||
console.log('%s: %s', block.height, block.tx[i]);
|
} else {
|
||||||
}
|
save_tx(block.tx[i], function(err){
|
||||||
setTimeout( function(){
|
if (err) {
|
||||||
tx = null;
|
console.log(err);
|
||||||
subloop.next();
|
} else {
|
||||||
}, timeout);
|
console.log('%s: %s', block.height, block.tx[i]);
|
||||||
|
}
|
||||||
|
setTimeout( function(){
|
||||||
|
tx = null;
|
||||||
|
subloop.next();
|
||||||
|
}, timeout);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, function(){
|
||||||
|
blockhash = null;
|
||||||
|
block = null;
|
||||||
|
loop.next();
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
console.log('block not found: %s', blockhash);
|
||||||
|
loop.next();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, function(){
|
} else {
|
||||||
blockhash = null;
|
|
||||||
block = null;
|
|
||||||
loop.next();
|
loop.next();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, function(){
|
||||||
|
Tx.find({}).sort({timestamp: 'desc'}).limit(settings.index.last_txs).exec(function(err, txs){
|
||||||
|
Stats.update({coin: coin}, {
|
||||||
|
last: end,
|
||||||
|
last_txs: '' //not used anymore left to clear out existing objects
|
||||||
|
}, function() {
|
||||||
|
remove_lock("db_index", function(){
|
||||||
|
return cb();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
} else {
|
});
|
||||||
console.log('block not found: %s', blockhash);
|
|
||||||
loop.next();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
loop.next();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}, function(){
|
|
||||||
Tx.find({}).sort({timestamp: 'desc'}).limit(settings.index.last_txs).exec(function(err, txs){
|
|
||||||
Stats.updateOne({coin: coin}, {
|
|
||||||
last: end,
|
|
||||||
last_txs: '' //not used anymore left to clear out existing objects
|
|
||||||
}, function() {
|
|
||||||
return cb();
|
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -143,6 +143,7 @@ exports.genesis_tx = "dd1d332ad2d8d8f49195056d482ae3c96fd2d16e9d166413b27ca7f197
|
|||||||
exports.genesis_block = "0000860fcf946b44df0e7d85d6757d45f8de6f4c9aacc5c7b6abc13db1f68819";
|
exports.genesis_block = "0000860fcf946b44df0e7d85d6757d45f8de6f4c9aacc5c7b6abc13db1f68819";
|
||||||
|
|
||||||
exports.heavy = false;
|
exports.heavy = false;
|
||||||
|
exports.lock_during_index = false;
|
||||||
exports.txcount = 100;
|
exports.txcount = 100;
|
||||||
exports.show_sent_received = true;
|
exports.show_sent_received = true;
|
||||||
exports.supply = "TXOUTSET";
|
exports.supply = "TXOUTSET";
|
||||||
|
|||||||
+2
-3
@@ -3,8 +3,7 @@ var mongoose = require('mongoose')
|
|||||||
|
|
||||||
var AddressTXSchema = new Schema({
|
var AddressTXSchema = new Schema({
|
||||||
a_id: { type: String, index: true},
|
a_id: { type: String, index: true},
|
||||||
addresses: { type: String, lowercase: true},
|
txid: { type: String, lowercase: true, index: true}
|
||||||
type: { type: String }
|
|
||||||
}, {id: false});
|
}, {id: false});
|
||||||
|
|
||||||
module.exports = mongoose.model('AddressTx', AddressTXSchema);
|
module.exports = mongoose.model('AddressTx', AddressTXSchema);
|
||||||
+2
-1
@@ -9,8 +9,9 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"express": ">=4.17.1",
|
"express": ">=4.17.1",
|
||||||
|
"intl": "^1.2.5",
|
||||||
"serve-favicon": "^2.5.0",
|
"serve-favicon": "^2.5.0",
|
||||||
"morgan": ">=1.9.1",
|
"morgan": ">=1.9.1",
|
||||||
"cookie-parser": "~1.4.4",
|
"cookie-parser": "~1.4.4",
|
||||||
"body-parser": "~1.19.0",
|
"body-parser": "~1.19.0",
|
||||||
"debug": ">=4.1.1",
|
"debug": ">=4.1.1",
|
||||||
|
|||||||
+23
-17
@@ -89,7 +89,13 @@ function route_get_tx(res, txid) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function route_get_index(res, error) {
|
function route_get_index(res, error) {
|
||||||
res.render('index', { active: 'home', error: error, warning: null});
|
db.is_locked(function(locked) {
|
||||||
|
if (locked) {
|
||||||
|
res.render('index', { active: 'home', error: error, warning: locale.initial_index_alert});
|
||||||
|
} else {
|
||||||
|
res.render('index', { active: 'home', error: error, warning: null});
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function route_get_address(res, hash, count) {
|
function route_get_address(res, hash, count) {
|
||||||
@@ -174,22 +180,22 @@ router.get('/network', function(req, res) {
|
|||||||
|
|
||||||
router.get('/reward', function(req, res){
|
router.get('/reward', function(req, res){
|
||||||
//db.get_stats(settings.coin, function (stats) {
|
//db.get_stats(settings.coin, function (stats) {
|
||||||
console.log(stats);
|
console.log(stats);
|
||||||
db.get_heavy(settings.coin, function (heavy) {
|
db.get_heavy(settings.coin, function (heavy) {
|
||||||
//heavy = heavy;
|
//heavy = heavy;
|
||||||
var votes = heavy.votes;
|
var votes = heavy.votes;
|
||||||
votes.sort(function (a,b) {
|
votes.sort(function (a,b) {
|
||||||
if (a.count < b.count) {
|
if (a.count < b.count) {
|
||||||
return -1;
|
return -1;
|
||||||
} else if (a.count > b.count) {
|
} else if (a.count > b.count) {
|
||||||
return 1;
|
return 1;
|
||||||
} else {
|
} else {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
res.render('reward', { active: 'reward', stats: stats, heavy: heavy, votes: heavy.votes });
|
res.render('reward', { active: 'reward', stats: stats, heavy: heavy, votes: heavy.votes });
|
||||||
});
|
});
|
||||||
//});
|
//});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -299,4 +305,4 @@ router.get('/ext/summary', function(req, res) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
+9
-9
@@ -1,12 +1,12 @@
|
|||||||
var mongoose = require('mongoose')
|
var mongoose = require('mongoose')
|
||||||
, db = require('../lib/database')
|
, db = require('../lib/database')
|
||||||
, Tx = require('../models/tx')
|
, Tx = require('../models/tx')
|
||||||
, Address = require('../models/address')
|
, Address = require('../models/address')
|
||||||
, AddressTx = require('../models/addresstx')
|
, AddressTx = require('../models/addresstx')
|
||||||
, Richlist = require('../models/richlist')
|
, Richlist = require('../models/richlist')
|
||||||
, Stats = require('../models/stats')
|
, Stats = require('../models/stats')
|
||||||
, settings = require('../lib/settings')
|
, settings = require('../lib/settings')
|
||||||
, fs = require('fs');
|
, fs = require('fs');
|
||||||
|
|
||||||
var mode = 'update';
|
var mode = 'update';
|
||||||
var database = 'index';
|
var database = 'index';
|
||||||
@@ -162,7 +162,7 @@ is_locked(function (exists) {
|
|||||||
}, function() {
|
}, function() {
|
||||||
console.log('index cleared (reindex)');
|
console.log('index cleared (reindex)');
|
||||||
});
|
});
|
||||||
db.update_tx_db(settings.coin, 1, stats.count, settings.check_timeout, function(){
|
db.update_tx_db(settings.coin, 1, stats.count, settings.update_timeout, function(){
|
||||||
db.update_richlist('received', function(){
|
db.update_richlist('received', function(){
|
||||||
db.update_richlist('balance', function(){
|
db.update_richlist('balance', function(){
|
||||||
db.get_stats(settings.coin, function(nstats){
|
db.get_stats(settings.coin, function(nstats){
|
||||||
|
|||||||
@@ -153,6 +153,9 @@
|
|||||||
//heavy (enable/disable additional heavy features)
|
//heavy (enable/disable additional heavy features)
|
||||||
"heavy": false,
|
"heavy": false,
|
||||||
|
|
||||||
|
//disable saving blocks & TXs via API during indexing.
|
||||||
|
"lock_during_index": false,
|
||||||
|
|
||||||
//amount of txs to index per address (stores latest n txs)
|
//amount of txs to index per address (stores latest n txs)
|
||||||
"txcount": 100,
|
"txcount": 100,
|
||||||
|
|
||||||
|
|||||||
+6
-6
@@ -1,9 +1,9 @@
|
|||||||
extends layout
|
extends layout
|
||||||
|
|
||||||
block content
|
block content
|
||||||
- var balance = (address.received - address.sent) / 100000000;
|
- var balance = ((address.received - address.sent) / 100000000).toLocaleString('en',{'minimumFractionDigits':2,'maximumFractionDigits':8,'useGrouping':true});
|
||||||
- var sent = address.sent /100000000
|
- var sent = (address.sent /100000000).toLocaleString('en',{'minimumFractionDigits':2,'maximumFractionDigits':8,'useGrouping':true})
|
||||||
- var received = address.received / 100000000
|
- var received = (address.received / 100000000).toLocaleString('en',{'minimumFractionDigits':2,'maximumFractionDigits':8,'useGrouping':true})
|
||||||
img.qrcode.pull-right.hidden-xs(src='/qr/' + address.a_id)
|
img.qrcode.pull-right.hidden-xs(src='/qr/' + address.a_id)
|
||||||
.col-xs-12.col-md-10.col-md-offset-1
|
.col-xs-12.col-md-10.col-md-offset-1
|
||||||
.panel.panel-default.panel-address-summary
|
.panel.panel-default.panel-address-summary
|
||||||
@@ -34,11 +34,11 @@ block content
|
|||||||
tbody
|
tbody
|
||||||
tr
|
tr
|
||||||
if settings.show_sent_received == true
|
if settings.show_sent_received == true
|
||||||
td #{sent.toFixed(8)}
|
td #{sent}
|
||||||
if address.a_id !== 'coinbase'
|
if address.a_id !== 'coinbase'
|
||||||
if settings.show_sent_received == true
|
if settings.show_sent_received == true
|
||||||
td #{received.toFixed(8)}
|
td #{received}
|
||||||
td #{balance.toFixed(8)}
|
td #{balance}
|
||||||
.panel.panel-default
|
.panel.panel-default
|
||||||
.panel-heading
|
.panel-heading
|
||||||
strong #{settings.locale.ex_latest_transactions}
|
strong #{settings.locale.ex_latest_transactions}
|
||||||
|
|||||||
+4
-3
@@ -91,10 +91,11 @@ block content
|
|||||||
a(href='/tx/' + txn.txid) #{txn.txid}
|
a(href='/tx/' + txn.txid) #{txn.txid}
|
||||||
td #{txn.vout.length}
|
td #{txn.vout.length}
|
||||||
if txn.vout.length > 0
|
if txn.vout.length > 0
|
||||||
- var total = txn.total / 100000000
|
- var total = (txn.total / 100000000).toLocaleString('en',{'minimumFractionDigits':2,'maximumFractionDigits':8,'useGrouping':true});
|
||||||
td #{total.toFixed(8)}
|
td #{total}
|
||||||
else
|
else
|
||||||
td #{txn.total.toFixed(8)}
|
- var total = (txn.total).toLocaleString('en',{'minimumFractionDigits':2,'maximumFractionDigits':8,'useGrouping':true});
|
||||||
|
td #{total}
|
||||||
td.view_tx
|
td.view_tx
|
||||||
a(href='/tx/' + txn.txid)
|
a(href='/tx/' + txn.txid)
|
||||||
span.glyphicon.glyphicon-eye-open
|
span.glyphicon.glyphicon-eye-open
|
||||||
|
|||||||
@@ -29,20 +29,21 @@ script.
|
|||||||
if(out > 0 && vin > 0) {
|
if(out > 0 && vin > 0) {
|
||||||
amount = (out - vin) / 100000000
|
amount = (out - vin) / 100000000
|
||||||
if (amount < 0) {
|
if (amount < 0) {
|
||||||
amount = amount * -1
|
amount = (amount * -1).toLocaleString('en',{'minimumFractionDigits':2,'maximumFractionDigits':8,'useGrouping':true});
|
||||||
$("td:eq(2)", row).html("-" + amount.toFixed(8)).addClass("info");
|
$("td:eq(2)", row).html("-" + amount).addClass("info");
|
||||||
} else if (amount > 0) {
|
} else if (amount > 0) {
|
||||||
ammount = amount.toFixed(8);
|
amount = amount.toLocaleString('en',{'minimumFractionDigits':2,'maximumFractionDigits':8,'useGrouping':true});
|
||||||
$("td:eq(2)", row).html("+" + amount).addClass("info");
|
$("td:eq(2)", row).html("+" + amount).addClass("info");
|
||||||
} else {
|
} else {
|
||||||
$("td:eq(2)", row).html(amount.toFixed(8)).addClass("info");
|
amount = amount.toLocaleString('en',{'minimumFractionDigits':2,'maximumFractionDigits':8,'useGrouping':true});
|
||||||
|
$("td:eq(2)", row).html(amount).addClass("info");
|
||||||
}
|
}
|
||||||
}else if(out > 0) {
|
}else if(out > 0) {
|
||||||
amount = out / 100000000;
|
amount = (out / 100000000).toLocaleString('en',{'minimumFractionDigits':2,'maximumFractionDigits':8,'useGrouping':true});
|
||||||
$("td:eq(2)", row).html("+" + amount.toFixed(8)).addClass("success");
|
$("td:eq(2)", row).html("+" + amount).addClass("success");
|
||||||
}else{
|
}else{
|
||||||
amount = vin / 100000000;
|
amount = (vin / 100000000).toLocaleString('en',{'minimumFractionDigits':2,'maximumFractionDigits':8,'useGrouping':true});
|
||||||
$("td:eq(2)", row).html("-" + amount.toFixed(8)).addClass("danger");
|
$("td:eq(2)", row).html("-" + amount).addClass("danger");
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -10,8 +10,10 @@ table.table.table-hover.table-bordered(cellspacing="0")
|
|||||||
- var count = 0
|
- var count = 0
|
||||||
each item in balance
|
each item in balance
|
||||||
- count = count + 1
|
- count = count + 1
|
||||||
- var itemFixed = item.balance / 100000000
|
- var itemFixed = (parseInt(item.balance) / 100000000);
|
||||||
- var percentage = (itemFixed / stats.supply) * 100
|
- var itemFixedParts = itemFixed.toFixed(0).toString().split(".");
|
||||||
|
- var itemFixedStr = itemFixedParts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ",") + (itemFixedParts[1] ? "." + itemFixedParts[1] : "");
|
||||||
|
- var percentage = (itemFixed / stats.supply) * 100;
|
||||||
tr
|
tr
|
||||||
td(style='text-align:center;')
|
td(style='text-align:center;')
|
||||||
=count
|
=count
|
||||||
@@ -19,6 +21,6 @@ table.table.table-hover.table-bordered(cellspacing="0")
|
|||||||
a(href='/address/' + item.a_id) #{item.a_id}
|
a(href='/address/' + item.a_id) #{item.a_id}
|
||||||
include ./rl_labels.pug
|
include ./rl_labels.pug
|
||||||
td.hidden-xs
|
td.hidden-xs
|
||||||
=itemFixed.toFixed(8)
|
=itemFixedStr
|
||||||
td.hidden-xs(style='text-align:center;')
|
td.hidden-xs(style='text-align:center;')
|
||||||
=percentage.toFixed(2)
|
=percentage.toFixed(2)
|
||||||
@@ -8,8 +8,10 @@ table.table.table-hover.table-bordered(cellspacing="0")
|
|||||||
tbody
|
tbody
|
||||||
- var count = 0
|
- var count = 0
|
||||||
each item in received
|
each item in received
|
||||||
- count = count + 1
|
- count = count + 1;
|
||||||
- var itemFixed = item.received / 100000000
|
- var itemFixed = (parseInt(item.received) / 100000000);
|
||||||
|
- var itemFixedParts = itemFixed.toFixed(0).toString().split(".");
|
||||||
|
- var itemFixedStr = itemFixedParts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ",") + (itemFixedParts[1] ? "." + itemFixedParts[1] : "");
|
||||||
tr
|
tr
|
||||||
td(style='text-align:center;')
|
td(style='text-align:center;')
|
||||||
=count
|
=count
|
||||||
@@ -17,4 +19,4 @@ table.table.table-hover.table-bordered(cellspacing="0")
|
|||||||
a(href='/address/' + item.a_id) #{item.a_id}
|
a(href='/address/' + item.a_id) #{item.a_id}
|
||||||
include ./rl_labels.pug
|
include ./rl_labels.pug
|
||||||
td.hidden-xs
|
td.hidden-xs
|
||||||
=itemFixed.toFixed(8)
|
=itemFixedStr
|
||||||
+1
-1
@@ -44,7 +44,7 @@ block content
|
|||||||
json.data[i]['txid'] = "<a href='/tx/" + json.data[i]['txid'] + "'>" + json.data[i]['txid'] + "</a>";
|
json.data[i]['txid'] = "<a href='/tx/" + json.data[i]['txid'] + "'>" + json.data[i]['txid'] + "</a>";
|
||||||
json.data[i]['blockindex'] = "<a href='/block/" + json.data[i]['blockhash'] + "'>" + json.data[i]['blockindex'] + "</a>";
|
json.data[i]['blockindex'] = "<a href='/block/" + json.data[i]['blockhash'] + "'>" + json.data[i]['blockindex'] + "</a>";
|
||||||
var amount = json.data[i]['total'] / 100000000;
|
var amount = json.data[i]['total'] / 100000000;
|
||||||
json.data[i]['total'] = amount.toFixed(8);
|
json.data[i]['total'] = amount.toLocaleString('en',{'minimumFractionDigits':2,'maximumFractionDigits':8,'useGrouping':true});
|
||||||
json.data[i]['recipients'] = json.data[i]['vout'].length;
|
json.data[i]['recipients'] = json.data[i]['vout'].length;
|
||||||
}
|
}
|
||||||
return json.data;
|
return json.data;
|
||||||
|
|||||||
+3
-3
@@ -26,14 +26,14 @@ html
|
|||||||
$('##{active}').addClass('active');
|
$('##{active}').addClass('active');
|
||||||
function update_stats(){
|
function update_stats(){
|
||||||
$.ajax({url: '/ext/summary', success: function(json){
|
$.ajax({url: '/ext/summary', success: function(json){
|
||||||
$("#supply").text(json.data[0].supply);
|
$("#supply").text(parseInt(parseFloat(json.data[0].supply).toFixed(0)).toLocaleString('en'));
|
||||||
$("#masternodeCountOnline").text(json.data[0].masternodeCountOnline).prop("alt", json.data[0].masternodeCountOnline+" nodes online").prop("title", json.data[0].masternodeCountOnline+" nodes online");
|
$("#masternodeCountOnline").text(json.data[0].masternodeCountOnline).prop("alt", json.data[0].masternodeCountOnline+" nodes online").prop("title", json.data[0].masternodeCountOnline+" nodes online");
|
||||||
$("#masternodeCountOffline").text(json.data[0].masternodeCountOffline).prop("alt", json.data[0].masternodeCountOffline+" unreachable nodes").prop("title", json.data[0].masternodeCountOffline+" unreachable nodes");
|
$("#masternodeCountOffline").text(json.data[0].masternodeCountOffline).prop("alt", json.data[0].masternodeCountOffline+" unreachable nodes").prop("title", json.data[0].masternodeCountOffline+" unreachable nodes");
|
||||||
$("#spnMasternodeCountOnline").prop("alt", json.data[0].masternodeCountOnline+" nodes online").prop("title", json.data[0].masternodeCountOnline+" nodes online");
|
$("#spnMasternodeCountOnline").prop("alt", json.data[0].masternodeCountOnline+" nodes online").prop("title", json.data[0].masternodeCountOnline+" nodes online");
|
||||||
$("#spnMasternodeCountOffline").prop("alt", json.data[0].masternodeCountOffline+" unreachable nodes").prop("title", json.data[0].masternodeCountOffline+" unreachable nodes");
|
$("#spnMasternodeCountOffline").prop("alt", json.data[0].masternodeCountOffline+" unreachable nodes").prop("title", json.data[0].masternodeCountOffline+" unreachable nodes");
|
||||||
$("#difficulty").text(json.data[0].difficulty);
|
$("#difficulty").text(json.data[0].difficulty.toFixed(2));
|
||||||
$("#difficultyHybrid").text(json.data[0].difficultyHybrid);
|
$("#difficultyHybrid").text(json.data[0].difficultyHybrid);
|
||||||
$("#hashrate").text(json.data[0].hashrate);
|
$("#hashrate").text(parseFloat(json.data[0].hashrate).toLocaleString('en'));
|
||||||
$("#lastPrice").text(json.data[0].lastPrice.toFixed(8));
|
$("#lastPrice").text(json.data[0].lastPrice.toFixed(8));
|
||||||
$("#lblConnections").text(json.data[0].connections + ' connections');
|
$("#lblConnections").text(json.data[0].connections + ' connections');
|
||||||
$("#lblBlockcount").text(json.data[0].blockcount + ' blocks');
|
$("#lblBlockcount").text(json.data[0].blockcount + ' blocks');
|
||||||
|
|||||||
+3
-3
@@ -45,11 +45,11 @@ block content
|
|||||||
json.data[i]['txid'] = "<a href='/tx/" + json.data[i]['txid'] + "' target='_blank'>" + json.data[i]['txid'] + "</a>"
|
json.data[i]['txid'] = "<a href='/tx/" + json.data[i]['txid'] + "' target='_blank'>" + json.data[i]['txid'] + "</a>"
|
||||||
var amount = json.data[i]['total'] / 100000000;
|
var amount = json.data[i]['total'] / 100000000;
|
||||||
if (amount > '#{flagb}') {
|
if (amount > '#{flagb}') {
|
||||||
json .data[i]['total'] = "<label class='label label-danger'>" + amount + "</label>";
|
json .data[i]['total'] = "<label class='label label-danger'>" + amount.toLocaleString('en') + "</label>";
|
||||||
} else if (amount > '#{flaga}') {
|
} else if (amount > '#{flaga}') {
|
||||||
json .data[i]['total'] = "<label class='label label-warning'>" + amount + "</label>";
|
json .data[i]['total'] = "<label class='label label-warning'>" + amount.toLocaleString('en') + "</label>";
|
||||||
} else {
|
} else {
|
||||||
json .data[i]['total'] = "<label class='label label-success'>" + amount + "</label>";
|
json .data[i]['total'] = "<label class='label label-success'>" + amount.toLocaleString('en') + "</label>";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return json.data;
|
return json.data;
|
||||||
|
|||||||
+8
-4
@@ -64,25 +64,29 @@ block content
|
|||||||
th
|
th
|
||||||
div.pull-left(style='background-color:#d9534f;width:20px;height:20px;margin-right:6px;')
|
div.pull-left(style='background-color:#d9534f;width:20px;height:20px;margin-right:6px;')
|
||||||
span #{settings.locale.rl_top25}
|
span #{settings.locale.rl_top25}
|
||||||
td #{parseFloat(dista.total).toFixed(8)}
|
- var total = parseFloat(dista.total).toLocaleString('en',{'minimumFractionDigits':2,'maximumFractionDigits':8,'useGrouping':true})
|
||||||
|
td #{total}
|
||||||
td.text-center #{parseFloat(dista.percent).toFixed(2)}
|
td.text-center #{parseFloat(dista.percent).toFixed(2)}
|
||||||
tr
|
tr
|
||||||
th
|
th
|
||||||
div.pull-left(style='background-color:#5cb85c;width:20px;height:20px;margin-right:6px;')
|
div.pull-left(style='background-color:#5cb85c;width:20px;height:20px;margin-right:6px;')
|
||||||
span #{settings.locale.rl_top50}
|
span #{settings.locale.rl_top50}
|
||||||
td #{parseFloat(distb.total).toFixed(8)}
|
- var total = parseFloat(distb.total).toLocaleString('en',{'minimumFractionDigits':2,'maximumFractionDigits':8,'useGrouping':true})
|
||||||
|
td #{total}
|
||||||
td.text-center #{parseFloat(distb.percent).toFixed(2)}
|
td.text-center #{parseFloat(distb.percent).toFixed(2)}
|
||||||
tr
|
tr
|
||||||
th
|
th
|
||||||
div.pull-left(style='background-color:#428bca;width:20px;height:20px;margin-right:6px;')
|
div.pull-left(style='background-color:#428bca;width:20px;height:20px;margin-right:6px;')
|
||||||
span #{settings.locale.rl_top75}
|
span #{settings.locale.rl_top75}
|
||||||
td #{parseFloat(distc.total).toFixed(8)}
|
- var total = parseFloat(distc.total).toLocaleString('en',{'minimumFractionDigits':2,'maximumFractionDigits':8,'useGrouping':true})
|
||||||
|
td #{total}
|
||||||
td.text-center #{parseFloat(distc.percent).toFixed(2)}
|
td.text-center #{parseFloat(distc.percent).toFixed(2)}
|
||||||
tr
|
tr
|
||||||
th
|
th
|
||||||
div.pull-left(style='background-color:#222;width:20px;height:20px;margin-right:6px;')
|
div.pull-left(style='background-color:#222;width:20px;height:20px;margin-right:6px;')
|
||||||
span #{settings.locale.rl_top100}
|
span #{settings.locale.rl_top100}
|
||||||
td #{parseFloat(distd.total).toFixed(8)}
|
- var total = parseFloat(distd.total).toLocaleString('en',{'minimumFractionDigits':2,'maximumFractionDigits':8,'useGrouping':true})
|
||||||
|
td #{total}
|
||||||
td.text-center #{parseFloat(distd.percent).toFixed(2)}
|
td.text-center #{parseFloat(distd.percent).toFixed(2)}
|
||||||
tr
|
tr
|
||||||
th
|
th
|
||||||
|
|||||||
+18
-17
@@ -71,7 +71,7 @@ block content
|
|||||||
tr.info(style='text-align:center')
|
tr.info(style='text-align:center')
|
||||||
td #{settings.locale.new_coins}
|
td #{settings.locale.new_coins}
|
||||||
else
|
else
|
||||||
- var ramount = r.amount / 100000000
|
- var ramount = (r.amount / 100000000).toLocaleString('en',{'minimumFractionDigits':2,'maximumFractionDigits':8,'useGrouping':true})
|
||||||
tr
|
tr
|
||||||
td
|
td
|
||||||
if r.addresses != 'private_tx'
|
if r.addresses != 'private_tx'
|
||||||
@@ -79,9 +79,9 @@ block content
|
|||||||
=r.addresses
|
=r.addresses
|
||||||
else
|
else
|
||||||
=settings.locale.hidden_sender
|
=settings.locale.hidden_sender
|
||||||
td.danger.hidden-xs #{ramount.toFixed(8)}
|
td.danger.hidden-xs #{ramount}
|
||||||
tr.hidden-lg.hidden-md
|
tr.hidden-lg.hidden-md
|
||||||
td.danger #{ramount.toFixed(8)} #{settings.symbol}
|
td.danger #{ramount} #{settings.symbol}
|
||||||
else
|
else
|
||||||
tr.info(style='text-align:center')
|
tr.info(style='text-align:center')
|
||||||
td #{settings.locale.proof_of_stake}
|
td #{settings.locale.proof_of_stake}
|
||||||
@@ -89,36 +89,37 @@ block content
|
|||||||
.panel.panel-default
|
.panel.panel-default
|
||||||
.panel-heading
|
.panel-heading
|
||||||
strong #{settings.locale.tx_recipients}
|
strong #{settings.locale.tx_recipients}
|
||||||
table.table.table-bordered.table-striped.summary-table
|
table.table.table-bordered.table-striped.summary-table
|
||||||
thead
|
thead
|
||||||
tr
|
tr
|
||||||
th #{settings.locale.tx_address}
|
th #{settings.locale.tx_address}
|
||||||
th.hidden-xs #{settings.locale.mkt_amount} (#{settings.symbol})
|
th.hidden-xs #{settings.locale.mkt_amount} (#{settings.symbol})
|
||||||
tbody
|
tbody
|
||||||
each r in tx.vout
|
each r in tx.vout
|
||||||
if tx.vout.length > 0
|
if tx.vout.length > 0
|
||||||
- var ramount = r.amount / 100000000
|
- var ramount = (r.amount / 100000000).toLocaleString('en',{'minimumFractionDigits':2,'maximumFractionDigits':8,'useGrouping':true})
|
||||||
tr
|
tr
|
||||||
if r.addresses != 'private_tx'
|
if r.addresses != 'private_tx'
|
||||||
td
|
td
|
||||||
a.loading(href='/address/' + r.addresses)
|
a.loading(href='/address/' + r.addresses)
|
||||||
=r.addresses
|
=r.addresses
|
||||||
td.success.hidden-xs #{ramount.toFixed(8)}
|
td.success.hidden-xs #{ramount}
|
||||||
else if r.amount > 0
|
else if r.amount > 0
|
||||||
td
|
td
|
||||||
=settings.locale.hidden_recipient
|
=settings.locale.hidden_recipient
|
||||||
td.success.hidden-xs #{ramount.toFixed(8)}
|
td.success.hidden-xs #{ramount}
|
||||||
else
|
else
|
||||||
td(colspan='2')
|
td(colspan='2')
|
||||||
=settings.locale.hidden_recipient
|
=settings.locale.hidden_recipient
|
||||||
tr.hidden-lg.hidden-md
|
tr.hidden-lg.hidden-md
|
||||||
td.success #{ramount.toFixed(8)} #{settings.symbol}
|
td.success #{ramount} #{settings.symbol}
|
||||||
else
|
else
|
||||||
|
- var ramount = (r.amount / 100000000).toLocaleString('en',{'minimumFractionDigits':2,'maximumFractionDigits':8,'useGrouping':true})
|
||||||
tr
|
tr
|
||||||
td
|
td
|
||||||
a.loading(href='/address/' + r.addresses)
|
a.loading(href='/address/' + r.addresses)
|
||||||
=r.addresses
|
=r.addresses
|
||||||
td.success.hidden-xs #{ramount.toFixed(8)}
|
td.success.hidden-xs #{ramount}
|
||||||
tr.hidden-lg.hidden-md
|
tr.hidden-lg.hidden-md
|
||||||
td.success #{ramount.toFixed(8)} #{settings.symbol}
|
td.success #{ramount} #{settings.symbol}
|
||||||
.footer-padding
|
.footer-padding
|
||||||
Reference in New Issue
Block a user