diff --git a/README.md b/README.md index a85025c..201e309 100644 --- a/README.md +++ b/README.md @@ -115,6 +115,7 @@ sync.sh (located in scripts/) is used for updating the local databases. This scr reindex-txcount Rescan and flatten the tx count value for faster access market Updates market summaries, orderbooks, trade history + charts peers Updates peer info based on local wallet connections + masternodes Updates the list of active masternodes on the network Notes: - 'current block' is the latest created block when script is executed. @@ -126,11 +127,12 @@ sync.sh (located in scripts/) is used for updating the local databases. This scr **crontab** -*Example crontab; update index every minute, market data every 2 minutes and peers every 5 minutes* +*Example crontab; update index every minute, market data every 2 minutes, peers and masternodes every 5 minutes* - */1 * * * * /path/to/explorer/scripts/sync.sh /path/to/nodejs update > /dev/null 2>&1 - */2 * * * * /path/to/explorer/scripts/sync.sh /path/to/nodejs market > /dev/null 2>&1 - */5 * * * * /path/to/explorer/scripts/sync.sh /path/to/nodejs peers > /dev/null 2>&1 + */1 * * * * /path/to/explorer/scripts/sync.sh /path/to/nodejs update > /dev/null 2>&1 + */2 * * * * /path/to/explorer/scripts/sync.sh /path/to/nodejs market > /dev/null 2>&1 + */5 * * * * /path/to/explorer/scripts/sync.sh /path/to/nodejs peers > /dev/null 2>&1 + */5 * * * * /path/to/explorer/scripts/sync.sh /path/to/nodejs masternodes > /dev/null 2>&1 ### Wallet diff --git a/app.js b/app.js index baf5b3f..5e0d09b 100644 --- a/app.js +++ b/app.js @@ -346,6 +346,24 @@ app.use('/ext/connections', function(req,res){ }); }); +// get the list of masternodes from local collection +app.use('/ext/getmasternodelist', function(req, res) { + // check if the getmasternodelist api is enabled or else check the headers to see if it matches an internal ajax request from the explorer itself (TODO: come up with a more secure method of whitelisting ajax calls from the explorer) + if (settings.public_api.ext['getmasternodelist'] || (req.headers['x-requested-with'] != null && req.headers['x-requested-with'].toLowerCase() == 'xmlhttprequest' && req.headers.referer != null && req.headers.accept.indexOf('text/javascript') > -1 && req.headers.accept.indexOf('application/json') > -1)) { + // get the masternode list from local collection + db.get_masternodes(function(masternodes) { + // loop through masternode list and remove the mongo _id and __v keys + for (i = 0; i < masternodes.length; i++) { + delete masternodes[i]['_doc']['_id']; + delete masternodes[i]['_doc']['__v']; + } + // return masternode list + res.send(masternodes); + }); + } else + res.end('This method is disabled'); +}); + // locals app.set('title', settings.title); app.set('explorer_version', package_metadata.version); @@ -390,9 +408,12 @@ app.set('labels', settings.labels); app.set('homelink', settings.homelink); app.set('logoheight', settings.logoheight); app.set('burned_coins', settings.burned_coins); -app.set('public_api', settings.public_api); app.set('api_cmds', settings.api_cmds); +// Always disable the rpc masternode list cmd from public apis +settings.public_api['rpc']['getmasternodelist'] = false; +app.set('public_api', settings.public_api); + app.set('sticky_header', settings.sticky_header); app.set('sticky_footer', settings.sticky_footer); diff --git a/lib/database.js b/lib/database.js index 84207be..4f4cc88 100644 --- a/lib/database.js +++ b/lib/database.js @@ -1,6 +1,7 @@ var mongoose = require('mongoose') , Stats = require('../models/stats') , Markets = require('../models/markets') + , Masternode = require('../models/masternode') , Address = require('../models/address') , AddressTx = require('../models/addresstx') , Tx = require('../models/tx') @@ -250,49 +251,18 @@ module.exports = { return fs.existsSync('./tmp/show_sync_message.tmp'); }, - update_label: function(hash, message, cb) { + update_label: function(hash, claim_name, cb) { find_address(hash, false, function(address) { if (address) { Address.updateOne({a_id: hash}, { - name: message + name: claim_name }, function() { - // ensure that if this address exists in the richlist that it displays the new alias - module.exports.get_richlist(settings.coin, function(richlist) { - var updated = false; - // loop through received addresses - for (r = 0; r < richlist.received.length; r++) { - // check if this is the correct address - if (richlist.received[r].a_id == hash) { - // update the claim name - richlist.received[r]['name'] = message; - // mark as updated - updated = true; - } - } - // loop through balance addresses - for (b = 0; b < richlist.balance.length; b++) { - // check if this is the correct address - if (richlist.balance[b].a_id == hash) { - // update the claim name - richlist.balance[b]['name'] = message; - // mark as updated - updated = true; - } - } - // check if the address was updated in the richlist - if (updated) { - // save the richlist back to collection - Richlist.updateOne({coin: settings.coin}, { - received: richlist.received, - balance: richlist.balance - }, function() { - // finished updating the claim label - return cb(''); - }); - } else { - // finished updating the claim label + // update claim name in richlist + module.exports.update_richlist_claim_name(hash, claim_name, function() { + // update claim name in masternode list + module.exports.update_masternode_claim_name(hash, claim_name, function() { return cb(''); - } + }); }); }); } else { @@ -302,6 +272,89 @@ module.exports = { }); }, + update_richlist_claim_name: function(hash, claim_name, cb) { + // check if the richlist is enabled + if (settings.display.richlist) { + // ensure that if this address exists in the richlist that it displays the new alias + module.exports.get_richlist(settings.coin, function(richlist) { + var updated = false; + // loop through received addresses + for (r = 0; r < richlist.received.length; r++) { + // check if this is the correct address + if (richlist.received[r].a_id == hash) { + // update the claim name + richlist.received[r]['name'] = claim_name; + // mark as updated + updated = true; + } + } + // loop through balance addresses + for (b = 0; b < richlist.balance.length; b++) { + // check if this is the correct address + if (richlist.balance[b].a_id == hash) { + // update the claim name + richlist.balance[b]['name'] = claim_name; + // mark as updated + updated = true; + } + } + // check if the address was updated in the richlist + if (updated) { + // save the richlist back to collection + Richlist.updateOne({coin: settings.coin}, { + received: richlist.received, + balance: richlist.balance + }, function() { + // finished updating the claim label + return cb(''); + }); + } else { + // finished updating the claim label + return cb(''); + } + }); + } else { + // richlist is not enabled so nothing to update + return cb(''); + } + }, + + update_masternode_claim_name: function(hash, claim_name, cb) { + // check if the masternode list is enabled + if (settings.display.masternodes) { + // ensure that if this address exists in the masternode that it displays the new alias + module.exports.get_masternodes(function(masternodes) { + var updated = false; + // loop through masternode addresses + for (m = 0; m < masternodes.length; m++) { + // check if this is the correct address + if (masternodes[m].addr == hash) { + // update the claim name + masternodes[m]['claim_name'] = claim_name; + // mark as updated + updated = true; + } + } + // check if the address was updated in the masternode list + if (updated) { + // save the updated masternode back to collection + Masternode.updateOne({addr: hash}, { + claim_name: claim_name + }, function() { + // finished updating the claim label + return cb(''); + }); + } else { + // finished updating the claim label + return cb(''); + } + }); + } else { + // masternode list is not enabled so nothing to update + return cb(''); + } + }, + check_stats: function(coin, cb) { Stats.findOne({coin: coin}, function(err, stats) { if(stats) { @@ -993,6 +1046,129 @@ module.exports = { }); }, + // determine if masternode exists and save masternode to collection + save_masternode: function (raw_masternode, cb) { + // lookup masternode in local collection + module.exports.find_masternode(raw_masternode.txhash, raw_masternode.outidx, function (masternode) { + // determine if the claim address feature is enabled + if (settings.display.claim_address) { + // claim address is enabled so lookup the address claim name + find_address(raw_masternode.addr, false, function(address) { + if (address) { + // save claim name to masternode obejct + raw_masternode.claim_name = address.name; + } else { + // save blank claim name to masternode obejct + raw_masternode.claim_name = ''; + } + // add/update the masternode + module.exports.add_update_masternode(raw_masternode, (masternode == null), function(success) { + return cb(success); + }); + }); + } else { + // claim address is disabled so add/update the masternode + module.exports.add_update_masternode(raw_masternode, (masternode == null), function(success) { + return cb(success); + }); + } + }); + }, + + // add or update a single masternode + add_update_masternode(masternode, add, cb) { + if (!masternode.txhash == null || !masternode.outidx == null) { + console.log('Masternode Update - TX or Outidx is missing'); + console.log(masternode.txhash); + console.log(masternode.outidx); + return cb(false); + } else { + var mn = new Masternode({ + rank: masternode.rank, + network: masternode.network, + txhash: masternode.txhash, + outidx: masternode.outidx, + status: masternode.status, + addr: masternode.addr, + version: masternode.version, + lastseen: masternode.lastseen, + activetime: masternode.activetime, + lastpaid: masternode.lastpaid, + claim_name: (masternode.claim_name == null ? '' : masternode.claim_name) + }); + + if (add) { + // add new masternode to collection + mn.save(function (err) { + if (err) { + console.log(err); + return cb(false); + } else + return cb(true); + }); + } else { + // update existing masternode in local collection + Masternode.updateOne({ txhash: masternode.txhash, outidx: masternode.outidx }, masternode, function (err) { + if (err) { + console.log(err); + return cb(false); + } else + return cb(true); + }); + } + } + }, + + // find masternode by txid and offset + find_masternode: function (txhash, outidx, cb) { + Masternode.findOne({ txhash: txhash, outidx: outidx }, function (err, masternode) { + if (err) + return cb(null); + else { + if (masternode) + return cb(masternode); + else + return cb(null); + } + }); + }, + + // remove masternodes older than 24 hours + remove_old_masternodes: function (cb) { + Masternode.deleteMany({ lastseen: { $lte: (Math.floor(Date.now() / 1000) - 86400) } }, function (err) { + if (err) { + console.log(err); + return cb(); + } else + return cb(); + }); + }, + + // get the list of masternodes from local collection + get_masternodes: function (cb) { + Masternode.find({}, function (err, masternodes) { + if (err) + return cb([]); + else + return cb(masternodes); + }); + }, + + // updates last_updated stats; called by sync.js + update_last_updated_stats: function (coin, param, cb) { + if (param.masternodes_last_updated) { + // update masternode last updated date + Stats.updateOne({ coin: coin }, { + masternodes_last_updated: param.masternodes_last_updated + }, function () { + return cb(true); + }); + } else { + // invalid option + return cb(false); + } + }, + populate_claim_address_names: function(tx, cb) { var addresses = []; diff --git a/lib/explorer.js b/lib/explorer.js index 9063864..3b59978 100644 --- a/lib/explorer.js +++ b/lib/explorer.js @@ -248,6 +248,34 @@ module.exports = { } }, + get_masternodelist: function(cb) { + var cmd = prepareRpcCommand(settings.api_cmds.getmasternodelist); + + if (!(cmd.method == '' && cmd.parameters.length == 0)) { + if (settings.use_rpc) { + rpcCommand([{method:cmd.method, parameters: cmd.parameters}], function(response) { + // check if an error msg was received from the rpc server + if (response == 'There was an error. Check your console.') + return cb(null); + else + return cb(response); + }); + } else { + var uri = base_url + 'getmasternodelist'; + request({uri: uri, json: true, headers: {'User-Agent': 'eiquidus'}}, function (error, response, body) { + // check if an error msg was received from the web api server + if (body == 'There was an error. Check your console.') + return cb(null); + else + return cb(body); + }); + } + } else { + // cmd not in use. return null. + return cb(null); + } + }, + get_masternodecount: function(cb) { var cmd = prepareRpcCommand(settings.api_cmds.getmasternodecount); diff --git a/lib/settings.js b/lib/settings.js index 5adf598..9bcd85e 100644 --- a/lib/settings.js +++ b/lib/settings.js @@ -92,6 +92,7 @@ exports.display = { "richlist": true, "movement": true, "network": true, + "masternodes": true, "claim_address": true, "claim_address_header_menu": true, "page_header_bgcolor": "", @@ -201,7 +202,6 @@ exports.public_api = { "getnetworkhashps": true, "getvotelist": true, "getmasternodecount": true, - "getmasternodelist": true, "getmaxmoney": true, "getmaxvote": true, "getvote": true, @@ -219,7 +219,8 @@ exports.public_api = { "getbalance": true, "getlasttxs": true, "getcurrentprice": true, - "getbasicstats": true + "getbasicstats": true, + "getmasternodelist": true } }; diff --git a/models/masternode.js b/models/masternode.js new file mode 100644 index 0000000..c18e3a4 --- /dev/null +++ b/models/masternode.js @@ -0,0 +1,18 @@ +var mongoose = require('mongoose') + , Schema = mongoose.Schema; + +var MasternodeSchema = new Schema({ + rank: { type: Number, default: 0 }, + network: { type: String, default: "" }, + txhash: { type: String, default: "" }, + outidx : { type: Number, default: 0}, + status : { type: String, default: "" }, + addr: { type: String, unique: true, index: true }, + version : { type: Number, default: 0}, + lastseen: { type: Number, default: 0 }, + activetime: { type: Number, default: 0 }, + lastpaid: { type: Number, default: 0 }, + claim_name: { type: String, default: '', index: true } +}, {id: false}); + +module.exports = mongoose.model('Masternode', MasternodeSchema); \ No newline at end of file diff --git a/models/stats.js b/models/stats.js index 31cb77e..0511ed9 100644 --- a/models/stats.js +++ b/models/stats.js @@ -5,13 +5,12 @@ var StatsSchema = new Schema({ coin: { type: String }, count: { type: Number, default: 1 }, last: { type: Number, default: 1 }, - //difficulty: { type: Object, default: {} }, - //hashrate: { type: String, default: 'N/A' }, supply: { type: Number, default: 0 }, txes: { type: Number, default: 0 }, connections: { type: Number, default: 0 }, last_price: { type: Number, default: 0 }, last_usd_price: { type: Number, default: 0 }, + masternodes_last_updated: { type: Number, default: 0 } }); module.exports = mongoose.model('coinstats', StatsSchema); \ No newline at end of file diff --git a/public/css/style.scss b/public/css/style.scss index 3c0bc07..65039bb 100644 --- a/public/css/style.scss +++ b/public/css/style.scss @@ -465,7 +465,7 @@ table { padding-top: 15px !important; } -.dataTables_length { +.dataTables_length, .dataTables_filter { padding-top: 10px; } diff --git a/routes/index.js b/routes/index.js index 4e8908f..b8dd07e 100644 --- a/routes/index.js +++ b/routes/index.js @@ -255,6 +255,20 @@ router.get('/network', function(req, res) { res.render('network', {active: 'network', showSync: db.check_show_sync_message()}); }); +// masternode list page +router.get('/masternodes', function(req, res) { + // ensure masternode page is enabled + if (settings.display.masternodes == true) { + // lookup last updated date + db.get_stats(settings.coin, function (stats) { + res.render('masternodes', {active: 'masternodes', last_updated: stats.masternodes_last_updated, showSync: db.check_show_sync_message()}); + }); + } else { + // masternode page is not enabled so default to the index page + route_get_index(res, null); + } +}); + router.get('/reward', function(req, res) { if (settings.heavy) { db.get_stats(settings.coin, function (stats) { diff --git a/scripts/delete_database.sh b/scripts/delete_database.sh index e15294a..98f67c9 100755 --- a/scripts/delete_database.sh +++ b/scripts/delete_database.sh @@ -29,6 +29,8 @@ db.heavies.remove({}) db.heavies.drop() db.markets.remove({}) db.markets.drop() +db.masternodes.remove({}) +db.masternodes.drop() db.peers.remove({}) db.peers.drop() db.richlists.remove({}) diff --git a/scripts/restore_backup.sh b/scripts/restore_backup.sh index 7baf21e..01f12f0 100755 --- a/scripts/restore_backup.sh +++ b/scripts/restore_backup.sh @@ -42,6 +42,8 @@ db.heavies.remove({}) db.heavies.drop() db.markets.remove({}) db.markets.drop() +db.masternodes.remove({}) +db.masternodes.drop() db.peers.remove({}) db.peers.drop() db.richlists.remove({}) diff --git a/scripts/sync.js b/scripts/sync.js index 672dacc..470e64e 100644 --- a/scripts/sync.js +++ b/scripts/sync.js @@ -23,6 +23,7 @@ function usage() { console.log('reindex-txcount Rescan and flatten the tx count value for faster access'); console.log('market Updates market summaries, orderbooks, trade history + charts'); console.log('peers Updates peer info based on local wallet connections'); + console.log('masternodes Updates the list of active masternodes on the network'); console.log(''); console.log('Notes:'); console.log('- \'current block\' is the latest created block when script is executed.'); @@ -63,6 +64,8 @@ if (process.argv[2] == 'index') { database = 'market'; } else if (process.argv[2] == 'peers') { database = 'peers'; +} else if (process.argv[2] == 'masternodes') { + database = 'masternodes'; } else { usage(); } @@ -236,6 +239,42 @@ if (database == 'peers') { }); } }); +} else if (database == 'masternodes') { + console.log('syncing masternodes.. please wait..'); + // syncing masternodes does not require a lock + mongoose.connect(dbString, { useNewUrlParser: true, useCreateIndex: true, useUnifiedTopology: true, useFindAndModify: false }, function(err) { + if (err) { + console.log('Unable to connect to database: %s', dbString); + console.log('Aborting'); + exit(); + } else { + lib.get_masternodelist(function (body) { + if (body != null) { + lib.syncLoop(body.length, function (loop) { + var i = loop.iteration(); + db.save_masternode(body[i], function (success) { + if (success) + loop.next(); + else { + console.log('error: cannot save masternode %s.', (body[i].addr ? body[i].addr : 'UNKNOWN')); + exit(); + } + }); + }, function () { + db.remove_old_masternodes(function (cb) { + db.update_last_updated_stats(settings.coin, { masternodes_last_updated: Math.floor(new Date() / 1000) }, function (cb) { + console.log('masternode sync complete'); + exit(); + }); + }); + }); + } else { + console.log('no masternodes found'); + exit(); + } + }); + } + }); } else { // index and market sync requires locking is_locked(function (exists) { diff --git a/scripts/sync.sh b/scripts/sync.sh index 0c42062..72487a8 100755 --- a/scripts/sync.sh +++ b/scripts/sync.sh @@ -34,6 +34,10 @@ if [ -n "${1}" ]; then # Peers update MODE="peers" ;; + "masternodes") + # Masternodes update + MODE="masternodes" + ;; *) # Check if this is a file that exists on the filesystem if [ -f ${1} ]; then @@ -75,7 +79,11 @@ if [ -n "${1}" ]; then "peers") # Peers update MODE="peers" - ;; + ;; + "masternodes") + # Masternodes update + MODE="masternodes" + ;; esac elif [ -n "${NODE_PATH}" ]; then # Node path was specified but no mode, so default to 'index update' mode @@ -90,7 +98,7 @@ fi if [ -n "${MODE}" ]; then # Mode is set # Check if the desired mode requires a lock - if [ "${MODE}" != "peers" ]; then + if [ "${MODE}" != "peers" ] && [ "${MODE}" != "masternodes" ]; then # A lock is required # Check if the script is already running (tmp/index.pid file already exists) if [ -f "${EXPLORER_PATH}/tmp/index.pid" ]; then diff --git a/settings.json.template b/settings.json.template index d260d89..5043fa4 100644 --- a/settings.json.template +++ b/settings.json.template @@ -115,6 +115,8 @@ "search": true, "movement": true, "network": true, + // Show/hide the "Masternodes" header menu item + "masternodes": true, // Enable/disable the ability for users to claim a wallet address. NOTE: Disabling this feature after addresses have already been claimed will effectively hide the claimed values and restore the original wallet addresses again "claim_address": true, // Show/hide the "Claim Address" header menu item. NOTE: "claim_address" setting must also be enabled or else the header item will automatically be hidden as well @@ -285,7 +287,6 @@ "getnetworkhashps": true, "getvotelist": true, "getmasternodecount": true, - "getmasternodelist": true, "getmaxmoney": true, "getmaxvote": true, "getvote": true, @@ -303,7 +304,8 @@ "getbalance": true, "getlasttxs": true, "getcurrentprice": true, - "getbasicstats": true + "getbasicstats": true, + "getmasternodelist": true } }, diff --git a/views/info.pug b/views/info.pug index 26c5411..bc19a75 100644 --- a/views/info.pug +++ b/views/info.pug @@ -10,8 +10,8 @@ block content p em #{settings.locale.api_message} hr - - var hide_rpc_api_section = !(settings.public_api.rpc['getdifficulty'] == true && settings.api_cmds['getdifficulty'] != null && settings.api_cmds['getdifficulty'] != '') && !(settings.public_api.rpc['getconnectioncount'] == true && settings.api_cmds['getconnectioncount'] != null && settings.api_cmds['getconnectioncount'] != '') && !(settings.public_api.rpc['getblockcount'] == true && settings.api_cmds['getblockcount'] != null && settings.api_cmds['getblockcount'] != '') && !(settings.public_api.rpc['getblockhash'] == true && settings.api_cmds['getblockhash'] != null && settings.api_cmds['getblockhash'] != '') && !(settings.public_api.rpc['getblock'] == true && settings.api_cmds['getblock'] != null && settings.api_cmds['getblock'] != '') && !(settings.public_api.rpc['getrawtransaction'] == true && settings.api_cmds['getrawtransaction'] != null && settings.api_cmds['getrawtransaction'] != '') && !(settings.public_api.rpc['getnetworkhashps'] == true && settings.index.show_hashrate == true && settings.api_cmds['getnetworkhashps'] != null && settings.api_cmds['getnetworkhashps'] != '') && !(settings.public_api.rpc['getvotelist'] == true && settings.api_cmds['getvotelist'] != null && settings.api_cmds['getvotelist'] != '') && !(settings.public_api.rpc['getmasternodecount'] == true && settings.api_cmds['getmasternodecount'] != null && settings.api_cmds['getmasternodecount'] != '') && !(settings.public_api.rpc['getmasternodelist'] == true && settings.api_cmds['getmasternodelist'] != null && settings.api_cmds['getmasternodelist'] != '') && (!settings.heavy || (!(settings.public_api.rpc['getmaxmoney'] == true && settings.api_cmds.heavies['getmaxmoney'] != null && settings.api_cmds.heavies['getmaxmoney'] != '') && !(settings.public_api.rpc['getmaxvote'] == true && settings.api_cmds.heavies['getmaxvote'] != null && settings.api_cmds.heavies['getmaxvote'] != '') && !(settings.public_api.rpc['getvote'] == true && settings.api_cmds.heavies['getvote'] != null && settings.api_cmds.heavies['getvote'] != '') && !(settings.public_api.rpc['getphase'] == true && settings.api_cmds.heavies['getphase'] != null && settings.api_cmds.heavies['getphase'] != '') && !(settings.public_api.rpc['getreward'] == true && settings.api_cmds.heavies['getreward'] != null && settings.api_cmds.heavies['getreward'] != '') && !(settings.public_api.rpc['getsupply'] == true && settings.api_cmds.heavies['getsupply'] != null && settings.api_cmds.heavies['getsupply'] != '') && !(settings.public_api.rpc['getnextrewardestimate'] == true && settings.api_cmds.heavies['getnextrewardestimate'] != null && settings.api_cmds.heavies['getnextrewardestimate'] != '') && !(settings.public_api.rpc['getnextrewardwhenstr'] == true && settings.api_cmds.heavies['getnextrewardwhenstr'] != null && settings.api_cmds.heavies['getnextrewardwhenstr'] != ''))); - - var hide_ext_api_section = !settings.public_api.ext['getmoneysupply'] && !settings.public_api.ext['getdistribution'] && !settings.public_api.ext['getaddress'] && !settings.public_api.ext['gettx'] && !settings.public_api.ext['getbalance'] && !settings.public_api.ext['getlasttxs'] && !settings.public_api.ext['getcurrentprice'] && !settings.public_api.ext['getbasicstats']; + - var hide_rpc_api_section = !(settings.public_api.rpc['getdifficulty'] == true && settings.api_cmds['getdifficulty'] != null && settings.api_cmds['getdifficulty'] != '') && !(settings.public_api.rpc['getconnectioncount'] == true && settings.api_cmds['getconnectioncount'] != null && settings.api_cmds['getconnectioncount'] != '') && !(settings.public_api.rpc['getblockcount'] == true && settings.api_cmds['getblockcount'] != null && settings.api_cmds['getblockcount'] != '') && !(settings.public_api.rpc['getblockhash'] == true && settings.api_cmds['getblockhash'] != null && settings.api_cmds['getblockhash'] != '') && !(settings.public_api.rpc['getblock'] == true && settings.api_cmds['getblock'] != null && settings.api_cmds['getblock'] != '') && !(settings.public_api.rpc['getrawtransaction'] == true && settings.api_cmds['getrawtransaction'] != null && settings.api_cmds['getrawtransaction'] != '') && !(settings.public_api.rpc['getnetworkhashps'] == true && settings.index.show_hashrate == true && settings.api_cmds['getnetworkhashps'] != null && settings.api_cmds['getnetworkhashps'] != '') && !(settings.public_api.rpc['getvotelist'] == true && settings.api_cmds['getvotelist'] != null && settings.api_cmds['getvotelist'] != '') && !(settings.public_api.rpc['getmasternodecount'] == true && settings.api_cmds['getmasternodecount'] != null && settings.api_cmds['getmasternodecount'] != '') && (!settings.heavy || (!(settings.public_api.rpc['getmaxmoney'] == true && settings.api_cmds.heavies['getmaxmoney'] != null && settings.api_cmds.heavies['getmaxmoney'] != '') && !(settings.public_api.rpc['getmaxvote'] == true && settings.api_cmds.heavies['getmaxvote'] != null && settings.api_cmds.heavies['getmaxvote'] != '') && !(settings.public_api.rpc['getvote'] == true && settings.api_cmds.heavies['getvote'] != null && settings.api_cmds.heavies['getvote'] != '') && !(settings.public_api.rpc['getphase'] == true && settings.api_cmds.heavies['getphase'] != null && settings.api_cmds.heavies['getphase'] != '') && !(settings.public_api.rpc['getreward'] == true && settings.api_cmds.heavies['getreward'] != null && settings.api_cmds.heavies['getreward'] != '') && !(settings.public_api.rpc['getsupply'] == true && settings.api_cmds.heavies['getsupply'] != null && settings.api_cmds.heavies['getsupply'] != '') && !(settings.public_api.rpc['getnextrewardestimate'] == true && settings.api_cmds.heavies['getnextrewardestimate'] != null && settings.api_cmds.heavies['getnextrewardestimate'] != '') && !(settings.public_api.rpc['getnextrewardwhenstr'] == true && settings.api_cmds.heavies['getnextrewardwhenstr'] != null && settings.api_cmds.heavies['getnextrewardwhenstr'] != ''))); + - var hide_ext_api_section = !settings.public_api.ext['getmoneysupply'] && !settings.public_api.ext['getdistribution'] && !settings.public_api.ext['getaddress'] && !settings.public_api.ext['gettx'] && !settings.public_api.ext['getbalance'] && !settings.public_api.ext['getlasttxs'] && !settings.public_api.ext['getcurrentprice'] && !settings.public_api.ext['getbasicstats'] && !(settings.public_api.ext['getmasternodelist'] == true && settings.api_cmds['getmasternodelist'] != null && settings.api_cmds['getmasternodelist'] != ''); if !hide_rpc_api_section h3 #{settings.locale.api_calls} p @@ -83,13 +83,6 @@ block content div em #{settings.locale.api_getmasternodecount} a(href='/api/getmasternodecount') #{address}/api/getmasternodecount - if settings.public_api.rpc['getmasternodelist'] == true && settings.api_cmds['getmasternodelist'] != null && settings.api_cmds['getmasternodelist'] != '' - li - p - div.font-weight-bold getmasternodelist - div - em #{settings.locale.api_getmasternodelist} - a(href='/api/getmasternodelist') #{address}/api/getmasternodelist if settings.heavy == true if settings.public_api.rpc['getmaxmoney'] == true && settings.api_cmds.heavies['getmaxmoney'] != null && settings.api_cmds.heavies['getmaxmoney'] != '' li @@ -212,6 +205,13 @@ block content div em Returns basic statistics about the coin including: block count, circulating supply, USD price, BTC price and # of masternodes a(href='/ext/getbasicstats') #{address}/ext/getbasicstats + if settings.public_api.ext['getmasternodelist'] == true && settings.api_cmds['getmasternodelist'] != null && settings.api_cmds['getmasternodelist'] != '' + li + p + div.font-weight-bold getmasternodelist + div + em #{settings.locale.api_getmasternodelist} + a(href='/ext/getmasternodelist') #{address}/ext/getmasternodelist hr h3 Linking (GET) p diff --git a/views/layout.pug b/views/layout.pug index 4291677..cbaf958 100644 --- a/views/layout.pug +++ b/views/layout.pug @@ -324,6 +324,11 @@ html(lang='en') a.nav-link(href='/reward') span.fa.fa-star span.margin-left-5 #{settings.locale.menu_reward} + if settings.display.masternodes == true + li#masternodes + a.nav-link(href='/masternodes') + span.fa.fa-share-alt + span.margin-left-5 Masternodes if settings.display.movement == true li#movement a.nav-link.loading(href='/movement') @@ -332,7 +337,7 @@ html(lang='en') if settings.display.network == true li#network a.nav-link(href='/network') - span.fa.fa-share-alt + span.fas.fa-network-wired span.margin-left-5 #{settings.locale.menu_network} if settings.display.richlist == true li#richlist diff --git a/views/masternodes.pug b/views/masternodes.pug new file mode 100644 index 0000000..be8be9c --- /dev/null +++ b/views/masternodes.pug @@ -0,0 +1,115 @@ +extends layout + +block content + include ./includes/common.pug + script. + function secondsToHms(d) { + d = Number(d); + var h = Math.floor(d / 3600); + var m = Math.floor(d % 3600 / 60); + var s = Math.floor(d % 3600 % 60); + var dy = Math.floor(h / 24); + var h = h % 24; + return ('0' + dy).slice(-2) + " day" + (('0' + dy).slice(-2) == 1 ? "" : "s") + " " + ('0' + h).slice(-2) + "h " + ('0' + m).slice(-2) + "m " + ('0' + s).slice(-2) + "s"; + } + $(document).ready(function() { + var labels = !{JSON.stringify(settings.labels)}; + var ctable = $('#masternodes-table').dataTable({ + autoWidth: true, + searching: true, + ordering: true, + order: [[ 5, 'desc' ]], + responsive: true, + lengthChange: true, + processing: true, + scrollX: true, + language: { + paginate: { + previous: '<', + next: '>' + } + }, + ajax: { + url: '/ext/getmasternodelist', + dataSrc: function(json) { + for (i = 0; i < json.length; i++) { + var addr = json[i]['addr']; + json[i]['address'] = "" + json[i]['address'] + ""; + json[i]['lastseen'] = new Date((json[i]['lastseen']) * 1000).toLocaleString(); + if (typeof json[i]['lastpaid'] != 'undefined') + json[i]['lastpaid'] = new Date((json[i]['lastpaid']) * 1000).toLocaleString(); + else + json[i]['lastpaid'] = 'N/A'; + if (json[i]['activetime']) + json[i]['activetime'] = secondsToHms(json[i]['activetime']); + else + json[i]['activetime'] = 'N/A'; + json[i]['addr'] = "" + json[i]['addr'] + (json[i]['claim_name'] != null && json[i]['claim_name'] != '' ? ' (' + json[i]['claim_name'] + ')' : '') + ""; + + if (labels[addr] != null) { + if (labels[addr].type) + json[i]['addr'] = '
' + json[i]['addr']; + else + json[i]['addr'] = '' + json[i]['addr']; + } + } + return json; + } + }, + columns: [ + { data: 'rank' }, + { data: 'network' }, + { data: 'addr' }, + { data: 'version' }, + { data: 'lastseen' }, + { data: 'lastpaid' }, + { data: 'activetime' }, + { data: 'status' } + ], + rowCallback: function(row, data, index) { + $("td:eq(2)", row).addClass("breakWord"); + + switch (data['status'].toUpperCase()) { + case 'ENABLED': + $("td:eq(7)", row).addClass('bg-success'); + break; + case 'REMOVE': + $("td:eq(7)", row).addClass('bg-danger'); + break; + case 'EXPIRED': + $("td:eq(7)", row).addClass('bg-warning'); + break; + default: + $("td:eq(7)", row).addClass('bg-info'); + break; + } + }, + fnDrawCallback: function(settings) { + fixDataTableColumns(); + fixFooterHeightAndPosition(); + } + }); + }); + .col-md-12.cardSpacer + .text-center(style='margin-bottom:15px;') + i The current listing of all masternodes known to be active on the network. + div.font-weight-bold(style='margin-bottom:15px;') Last updated: + span.font-weight-normal=(last_updated == null ? ' N/A' : ' ' + format_unixtime(last_updated)) + .card.card-default + .card-header + strong Masternodes + table#masternodes-table.table.table-bordered.table-striped.table-hover + - var theadClasses = []; + if settings.display.table_header_bgcolor != null && settings.display.table_header_bgcolor != '' + - theadClasses.push('thead-' + settings.display.table_header_bgcolor); + thead(class=theadClasses) + tr + th.text-center Pay rank + th.text-center Protocol + th.text-center Address + th.text-center Protocol + th.text-center Last seen + th.text-center Last paid + th.text-center Active since + th.text-center Status + tbody.text-center \ No newline at end of file