Merge branch 'master' of github.com:bitpay/insight into process-friendly

This commit is contained in:
Eric Martindale 2014-01-22 00:04:19 +00:00
commit 1b32e63cb2
42 changed files with 1035 additions and 789 deletions

View File

@ -57,36 +57,6 @@ $ npm install -g bower
If you get an error, please check the next section "Post-install"
### Post-install (post-dependecies)
Get bufferput package from Github repository:
$ git clone git@github.com:gasteve/node-bufferput.git
Create symbolic link of node-bufferput in your insight folder:
$ cd <your_path_to>/insight/node_modules
$ ln -s <path_to>/node-bufferput bufferput
Get bitcore from github repository:
Get bitcore from github repository:
$ git clone https://github.com/bitpay/bitcore.git
$ cd bitcore
$ npm install
Then create a symbolic link from this to your insight repository. We need to
use bitcore from github, not with npm for now:
$ cd insight/node_modules
$ rm -R bitcore
$ ln -s <path-to-your-clone-repositoy>/bitcore
## Syncing old blockchain data
Run sync from insight repository (to save old blocks and transactions in MongoDB):

View File

@ -32,6 +32,20 @@ exports.show = function(req, res) {
}
};
/**
* Show block by Height
*/
exports.blockindex = function(req, res, next, height) {
Block.fromHeight(height, function(err, hash) {
if (err) {
console.log(err);
res.status(400).send('Bad Request'); // TODO
}
else {
res.jsonp(hash);
}
});
};
/**
* List of blocks by date

View File

@ -26,6 +26,6 @@ module.exports.broadcast_address_tx = function(address, tx) {
ios.sockets.in(address).emit('atx', tx);
};
module.exports.broadcastSyncInfo = function(syncInfo) {
ios.sockets.emit('sync', syncInfo);
module.exports.broadcastSyncInfo = function(historicSync) {
ios.sockets.in('sync').emit('status', historicSync);
};

View File

@ -10,7 +10,7 @@ var Status = require('../models/Status'),
/**
* Status
*/
exports.show = function(req, res, next) {
exports.show = function(req, res) {
if (! req.query.q) {
res.status(400).send('Bad Request');
@ -50,6 +50,6 @@ exports.show = function(req, res, next) {
};
exports.sync = function(req, res) {
if (req.syncInfo)
res.jsonp(req.syncInfo);
if (req.historicSync)
res.jsonp(req.historicSync.info());
};

View File

@ -52,8 +52,12 @@ var getTransaction = function(txid, cb) {
*/
exports.list = function(req, res, next) {
var bId = req.query.block;
var aId = req.query.address;
var limit = req.query.limit || 1000;
var addrStr = req.query.address;
var page = req.query.pageNum;
var pageLength = 20;
var pagesTotal = 1;
var txLength;
var txs;
if (bId) {
Block.fromHashWithInfo(bId, function(err, block) {
@ -63,14 +67,28 @@ exports.list = function(req, res, next) {
return next();
}
async.mapSeries(block.info.tx, getTransaction,
txLength = block.info.tx.length;
if (page) {
var spliceInit = page * pageLength;
txs = block.info.tx.splice(spliceInit, pageLength);
pagesTotal = Math.ceil(txLength / pageLength);
}
else {
txs = block.info.tx;
}
async.mapSeries(txs, getTransaction,
function(err, results) {
res.jsonp(results);
res.jsonp({
pagesTotal: pagesTotal,
txs: results
});
});
});
}
else if (aId) {
var a = Address.new(aId);
else if (addrStr) {
var a = Address.new(addrStr);
a.update(function(err) {
if (err && !a.totalReceivedSat) {
@ -79,23 +97,25 @@ exports.list = function(req, res, next) {
return next();
}
async.mapSeries(a.transactions, getTransaction,
txLength = a.transactions.length;
if (page) {
var spliceInit = page * pageLength;
txs = a.transactions.splice(spliceInit, pageLength);
pagesTotal = Math.ceil(txLength / pageLength);
}
else {
txs = a.transactions;
}
async.mapSeries(txs, getTransaction,
function(err, results) {
res.jsonp(results);
res.jsonp({
pagesTotal: pagesTotal,
txs: results
});
});
});
}
else {
Transaction
.find()
.limit(limit)
.sort('-time')
.exec(function(err, txs) {
if (err) {
res.status(500).send(err);
} else {
res.jsonp(txs);
}
});
}
};

View File

@ -60,7 +60,7 @@ function spec() {
// TODO TXout!
//T
function (cb) {
TransactionItem.find({addr:that.addrStr}).sort({ts:1}).exec(function(err,txItems){
TransactionItem.find({addr:that.addrStr}).sort({ts:-1}).exec(function(err,txItems){
if (err) return cb(err);
txItems.forEach(function(txItem){

View File

@ -72,6 +72,15 @@ BlockSchema.statics.load = function(id, cb) {
}).exec(cb);
};
BlockSchema.statics.fromHeight = function(height, cb) {
var rpc = new RpcClient(config.bitcoind);
var hash = {};
rpc.getBlockHash(height, function(err, bh){
if (err) return cb(err);
hash.blockHash = bh.result;
cb(null, hash);
});
};
BlockSchema.statics.fromHash = function(hash, cb) {
this.findOne({

View File

@ -171,10 +171,13 @@ TransactionSchema.statics.explodeTransactionItems = function(txid, time, cb) {
TransactionItem.create({
txid : txid,
value_sat : o.valueSat,
addr : o.scriptPubKey.addresses[0],
addr : o.scriptPubKey.addresses[0], // TODO: only address 0?
index : o.n,
ts : time,
}, next_out);
if (addrs.indexOf(o.scriptPubKey.addresses[0]) === -1) {
addrs.push(o.scriptPubKey.addresses[0]);
}
}
else {
console.log ('WARN in TX: %s could not parse OUTPUT %d', txid, o.n);
@ -279,7 +282,7 @@ TransactionSchema.statics.queryInfo = function(txid, cb) {
else {
tx.ins.forEach(function(i) {
if (i.value) {
info.vin[c].value = util.formatValue(i.value);
info.vin[c].value = parseFloat(util.formatValue(i.value));
var n = util.valueToBigInt(i.value).toNumber();
info.vin[c].valueSat = n;
valueIn = valueIn.add( n );
@ -319,7 +322,7 @@ TransactionSchema.statics.queryInfo = function(txid, cb) {
if ( !tx.isCoinBase() ) {
info.valueIn = valueIn / util.COIN;
info.feeds = (valueIn - valueOut) / util.COIN;
info.fees = (valueIn - valueOut) / util.COIN;
}
else {
var reward = BitcoreBlock.getBlockValue(info.height) / util.COIN;
@ -343,8 +346,13 @@ TransactionSchema.methods.fillInfo = function(next) {
if (err) return next(err);
that.info = info;
that.info.time = that.time;
return next();
if (! that.info) {
return next();
}
else {
that.info.time = that.time;
return next();
}
});
};

View File

@ -36,7 +36,6 @@ script(type='text/javascript', src='/js/services/socket.js')
//Application Controllers
script(type='text/javascript', src='/js/controllers/index.js')
script(type='text/javascript', src='/js/controllers/header.js')
script(type='text/javascript', src='/js/controllers/footer.js')
script(type='text/javascript', src='/js/controllers/blocks.js')
script(type='text/javascript', src='/js/controllers/transactions.js')
script(type='text/javascript', src='/js/controllers/address.js')

View File

@ -31,7 +31,7 @@ module.exports = function(app, historicSync) {
//custom middleware
function setHistoric(req, res, next) {
req.syncInfo = historicSync.syncInfo;
req.historicSync = historicSync;
next();
}
app.use('/api/sync', setHistoric);

View File

@ -14,6 +14,9 @@ module.exports = function(app, historicSync) {
app.get('/api/block/:blockHash', blocks.show);
app.param('blockHash', blocks.block);
app.get('/api/block-index/:height', blocks.blockindex);
app.param('height', blocks.blockindex);
// Transaction routes
var transactions = require('../app/controllers/transactions');
app.get('/api/tx/:txid', transactions.show);

View File

@ -24,6 +24,16 @@ var express = require('express'),
var config = require('./config/config');
//Bootstrap db connection
// If mongod is running
mongoose.connection.on('open', function () {
console.log('Connected to mongo server.');
});
// If mongod is not running
mongoose.connection.on('error', function (err) {
console.log('Could not connect to mongo server!');
console.log(err);
});
mongoose.connect(config.db);
//Bootstrap models
@ -45,19 +55,29 @@ walk(models_path);
// historic_sync process
var historicSync = {};
if (!config.disableHistoricSync) {
historicSync = new HistoricSync();
historicSync.init({
skipDbConnection: true,
shouldBroadcast: true,
progressStep: 2,
networkName: config.network
}, function() {
historicSync.smart_import(function(err){
var txt= 'ended.';
if (err) txt = 'ABORTED with error: ' + err.message;
}, function(err) {
if (err) {
var txt = 'ABORTED with error: ' + err.message;
console.log('[historic_sync] ' + txt);
}
else {
historicSync.smartImport(function(err){
var txt= 'ended.';
if (err) txt = 'ABORTED with error: ' + err.message;
console.log('[historic_sync] ' + txt, historicSync.syncInfo);
});
console.log('[historic_sync] ' + txt, historicSync.info());
});
}
});
}

View File

@ -3,45 +3,76 @@
require('classtool');
function spec() {
var util = require('util');
var RpcClient = require('bitcore/RpcClient').class();
var networks = require('bitcore/networks');
var async = require('async');
var config = require('../config/config');
var Block = require('../app/models/Block');
var Sync = require('./Sync').class();
var sockets = require('../app/controllers/socket.js');
var util = require('util');
var RpcClient = require('bitcore/RpcClient').class();
var networks = require('bitcore/networks');
var async = require('async');
var config = require('../config/config');
var Block = require('../app/models/Block');
var Sync = require('./Sync').class();
var sockets = require('../app/controllers/socket.js');
var BAD_GEN_ERROR = 'Bad genesis block. Network mismatch between Insight and bitcoind? Insight is configured for:';
function HistoricSync(opts) {
this.network = config.network === 'testnet' ? networks.testnet: networks.livenet;
this.network = config.network === 'testnet' ? networks.testnet: networks.livenet;
var genesisHashReversed = new Buffer(32);
this.network.genesisBlock.hash.copy(genesisHashReversed);
this.genesis = genesisHashReversed.reverse().toString('hex');
this.sync = new Sync(opts);
this.sync = new Sync(opts);
//available status: new / syncing / finished / aborted
this.status = 'new';
this.syncInfo = {};
this.status = 'new';
this.error = null;
this.syncPercentage = 0;
this.syncedBlocks = 0;
this.skippedBlocks = 0;
}
function p() {
var args = [];
Array.prototype.push.apply( args, arguments );
Array.prototype.push.apply(args, arguments);
args.unshift('[historic_sync]');
/*jshint validthis:true */
console.log.apply(this, args);
}
HistoricSync.prototype.init = function(opts,cb) {
this.rpc = new RpcClient(config.bitcoind);
this.opts = opts;
this.sync.init(opts, cb);
HistoricSync.prototype.setError = function(err) {
var self = this;
self.error = err.toString();
self.status='error';
self.showProgress();
};
HistoricSync.prototype.init = function(opts, cb) {
var self = this;
self.rpc = new RpcClient(config.bitcoind);
self.opts = opts;
self.sync.init(opts, function(err) {
if (err) {
self.setError(err);
return cb(err);
}
else {
// check testnet?
self.rpc.getBlockHash(0, function(err, res){
if (!err && ( res && res.result !== self.genesis)) {
err = new Error(BAD_GEN_ERROR + config.network);
self.setError(err);
}
return cb(err);
});
}
});
};
HistoricSync.prototype.close = function() {
@ -49,75 +80,93 @@ function spec() {
};
HistoricSync.prototype.info = function() {
return {
status: this.status,
blockChainHeight: this.blockChainHeight,
syncPercentage: this.syncPercentage,
skippedBlocks: this.skippedBlocks,
syncedBlocks: this.syncedBlocks,
};
};
HistoricSync.prototype.showProgress = function() {
var self = this;
var i = self.syncInfo;
var per = parseInt(100 * i.syncedBlocks / i.blocksToSync);
p(util.format('status: %d/%d [%d%%]', i.syncedBlocks, i.blocksToSync, per));
if (self.opts.broadcast) {
sockets.broadcastSyncInfo(self.syncInfo);
if (self.error) {
p('ERROR:' + self.error);
}
else {
self.syncPercentage = parseFloat(100 * self.syncedBlocks / self.blockChainHeight).toFixed(3);
if (self.syncPercentage > 100) self.syncPercentage = 100;
p(util.format('status: [%d%%] skipped: %d', self.syncPercentage, self.skippedBlocks));
}
if (self.opts.shouldBroadcast) {
sockets.broadcastSyncInfo(self.info());
}
};
HistoricSync.prototype.getPrevNextBlock = function(blockHash, blockEnd, opts, cb) {
var self = this;
// recursion end.
if (!blockHash ) return cb();
if (!blockHash) return cb();
var existed = 0;
var existed = false;
var blockInfo;
var blockObj;
async.series([
// Already got it?
function(c) {
Block.findOne({hash:blockHash}, function(err,block){
if (err) { p(err); return c(err); }
if (block) {
existed =1;
blockObj =block;
}
return c();
});
// Already got it?
function(c) {
Block.findOne({
hash: blockHash
},
//show some (inacurate) status
function(c) {
var step = parseInt(self.syncInfo.blocksToSync / 100);
if (step < 10) step = 10;
if (self.syncInfo.syncedBlocks % step === 1) {
self.showProgress();
function(err, block) {
if (err) {
p(err);
return c(err);
}
if (block) {
existed = true;
blockObj = block;
}
return c();
},
//get Info from RPC
function(c) {
});
},
//show some (inacurate) status
function(c) {
if ( ( self.syncedBlocks + self.skippedBlocks) % self.step === 1) {
self.showProgress();
}
// TODO: if we store prev/next, no need to go to RPC
// if (blockObj && blockObj.nextBlockHash) return c();
return c();
},
//get Info from RPC
function(c) {
self.rpc.getBlock(blockHash, function(err, ret) {
if (err) return c(err);
// TODO: if we store prev/next, no need to go to RPC
// if (blockObj && blockObj.nextBlockHash) return c();
self.rpc.getBlock(blockHash, function(err, ret) {
if (err) return c(err);
blockInfo = ret;
return c();
});
},
//store it
function(c) {
if (existed) return c();
self.sync.storeBlock(blockInfo.result, function(err) {
blockInfo = ret;
return c();
});
},
//store it
function(c) {
if (existed) return c();
self.sync.storeBlock(blockInfo.result, function(err) {
existed = err && err.toString().match(/E11000/);
existed = err && err.toString().match(/E11000/);
if (err && ! existed) return c(err);
return c();
});
},
/* TODO: Should Start to sync backwards? (this is for partial syncs)
if (err && ! existed) return c(err);
return c();
});
},
/* TODO: Should Start to sync backwards? (this is for partial syncs)
function(c) {
if (blockInfo.result.prevblockhash != current.blockHash) {
@ -127,176 +176,159 @@ function spec() {
return c();
}
*/
],
function (err){
], function(err) {
if (err) {
self.err = util.format('ERROR: @%s: %s [count: syncedBlocks: %d]', blockHash, err, self.syncInfo.syncedBlocks);
self.status = 'aborted';
p(self.err);
}
if (err) {
self.err = util.format('ERROR: @%s: %s [count: syncedBlocks: %d]', blockHash, err, self.syncedBlocks);
self.status = 'aborted';
self.showProgress();
p(self.err);
}
else {
self.err = null;
self.status = 'syncing';
}
else {
self.err = null;
self.status = 'syncing';
}
if (opts.upToExisting && existed ) {
var diff = self.syncInfo.blocksToSync - self.syncInfo.syncedBlocks;
if (diff <= 0) {
self.status = 'finished';
p('DONE. Found existing block: ', blockHash);
return cb(err);
}
else {
self.syncInfo.skipped_blocks = self.syncInfo.skipped_blocks || 1;
if ((self.syncInfo.skipped_blocks++ % 1000) === 1 ) {
p('WARN found target block\n\tbut blockChain Height is still higher that ours. Previous light sync must be interrupted.\n\tWill keep syncing.', self.syncInfo.syncedBlocks, self.syncInfo.blocksToSync, self.syncInfo.skipped_blocks);
}
}
}
if (blockEnd && blockEnd === blockHash) {
if ( (opts.upToExisting && existed && self.syncedBlocks >= self.blockChainHeight) ||
(blockEnd && blockEnd === blockHash)) {
self.status = 'finished';
p('DONE. Found END block: ', blockHash);
p('DONE. Found existing block: ', blockHash);
self.showProgress();
return cb(err);
}
}
// Continue
if (blockInfo && blockInfo.result) {
// Continue
if (blockInfo && blockInfo.result) {
if (! existed) self.syncInfo.syncedBlocks++;
if (opts.prev && blockInfo.result.previousblockhash) {
return self.getPrevNextBlock(blockInfo.result.previousblockhash, blockEnd, opts, cb);
}
if (existed)
self.skippedBlocks++;
else
self.syncedBlocks++;
if (opts.next && blockInfo.result.nextblockhash)
return self.getPrevNextBlock(blockInfo.result.nextblockhash, blockEnd, opts, cb);
}
return cb(err);
// recursion
if (opts.prev && blockInfo.result.previousblockhash)
return self.getPrevNextBlock(blockInfo.result.previousblockhash, blockEnd, opts, cb);
if (opts.next && blockInfo.result.nextblockhash)
return self.getPrevNextBlock(blockInfo.result.nextblockhash, blockEnd, opts, cb);
}
return cb(err);
});
};
HistoricSync.prototype.import_history = function(opts, next) {
HistoricSync.prototype.importHistory = function(opts, next) {
var self = this;
var retry_secs = 2;
var retry_secs = 2;
var bestBlock;
var blockChainHeight;
async.series([
function(cb) {
if (opts.destroy) {
p('Deleting DB...');
return self.sync.destroy(cb);
}
function(cb) {
if (opts.destroy) {
p('Deleting DB...');
return self.sync.destroy(cb);
}
return cb();
},
// We are not using getBestBlockHash, because is not available in all clients
function(cb) {
if (!opts.reverse) return cb();
self.rpc.getBlockCount(function(err, res) {
if (err) return cb(err);
self.blockChainHeight = res.result;
return cb();
},
// We are not using getBestBlockHash, because is not available in all clients
function(cb) {
if (!opts.reverse) return cb();
});
},
function(cb) {
if (!opts.reverse) return cb();
self.rpc.getBlockCount(function(err, res) {
if (err) return cb(err);
blockChainHeight = res.result;
return cb();
});
},
function(cb) {
if (!opts.reverse) return cb();
self.rpc.getBlockHash(self.blockChainHeight, function(err, res) {
if (err) return cb(err);
self.rpc.getBlockHash(blockChainHeight, function(err, res) {
bestBlock = res.result;
return cb();
});
},
function(cb) {
if (opts.upToExisting) {
// should be isOrphan = true or null to be more accurate.
Block.count({
isOrphan: null
},
function(err, count) {
if (err) return cb(err);
bestBlock = res.result;
self.syncedBlocks = count || 0;
return cb();
});
},
function(cb) {
// This is only to inform progress.
if (!opts.upToExisting) {
self.rpc.getInfo(function(err, res) {
if (err) return cb(err);
self.syncInfo.blocksToSync = res.result.blocks;
return cb();
});
}
},
], function(err) {
var start, end;
function sync() {
if (opts.reverse) {
start = bestBlock;
end = self.genesis;
opts.prev = true;
}
else {
// should be isOrphan = true or null to be more accurate.
Block.count({ isOrphan: null}, function(err, count) {
if (err) return cb(err);
start = self.genesis;
end = null;
opts.next = true;
}
p('Starting from: ', start);
p(' to : ', end);
p(' opts: ', JSON.stringify(opts));
self.syncInfo.blocksToSync = blockChainHeight - count;
if (self.syncInfo.blocksToSync < 1) self.syncInfo.blocksToSync = 1;
return cb();
});
}
},
],
function(err) {
var start, end;
function sync() {
if (opts.reverse) {
start = bestBlock;
end = self.genesis;
opts.prev = true;
}
else {
start = self.genesis;
end = null;
opts.next = true;
self.getPrevNextBlock(start, end, opts, function(err) {
if (err && err.message.match(/ECONNREFUSED/)) {
setTimeout(function() {
p('Retrying in %d secs', retry_secs);
sync();
},
retry_secs * 1000);
}
else return next(err);
});
}
self.syncInfo = util._extend(self.syncInfo, {
start: start,
isStartGenesis: start === self.genesis,
end: end,
isEndGenesis: end === self.genesis,
scanningForward: opts.next,
scanningBackward: opts.prev,
upToExisting: opts.upToExisting,
syncedBlocks: 0,
});
p('Starting from: ', start);
p(' to : ', end);
p(' opts: ', JSON.stringify(opts));
if (!self.step) {
var step = parseInt( (self.blockChainHeight - self.syncedBlocks) / 1000);
self.getPrevNextBlock( start, end, opts , function(err) {
if (err && err.message.match(/ECONNREFUSED/)){
setTimeout(function() {
p('Retrying in %d secs', retry_secs);
sync();
}, retry_secs * 1000);
}
else
return next(err);
});
if (self.opts.progressStep) {
step = self.opts.progressStep;
}
if (err) {
self.syncInfo = util._extend(self.syncInfo, { error: err.message });
return next(err, 0);
}
else {
sync();
}
if (step < 10) step = 10;
self.step = step;
}
if (err) {
self.setError(err);
return next(err, 0);
}
else {
sync();
}
});
};
// upto if we have genesis block?
HistoricSync.prototype.smart_import = function(next) {
HistoricSync.prototype.smartImport = function(next) {
var self = this;
Block.findOne({hash:self.genesis}, function(err, b){
Block.findOne({
hash: self.genesis
},
function(err, b) {
if (err) return next(err);
if (!b) {
p('Could not find Genesis block. Running FULL SYNC');
}
@ -305,15 +337,14 @@ function spec() {
}
var opts = {
reverse: 1,
reverse: true,
upToExisting: b ? true: false,
};
return self.import_history(opts, next);
return self.importHistory(opts, next);
});
};
return HistoricSync;
}
module.defineClass(spec);

View File

@ -60,7 +60,6 @@
"mongoose": "~3.8.3",
"lodash": "~2.4.1",
"bower": "~1.2.8",
"bitcore": "*",
"buffertools": "*",
"grunt": "~0.4.2",
"grunt-cli": "~0.1.11",
@ -75,7 +74,9 @@
"socket.io": "~0.9.16",
"moment": "~2.5.0",
"sinon": "~1.7.3",
"chai": "~1.8.1"
"chai": "~1.8.1",
"bitcore": "git://github.com/bitpay/bitcore.git",
"bufferput": "git://github.com/bitpay/node-bufferput.git"
},
"devDependencies": {
"grunt-contrib-watch": "latest",

View File

@ -29,7 +29,7 @@ h1, h2, h3, h4, h5, h6, .h1, .h2, .h3, .h4, .h5, .h6 {
min-height: 100%;
height: auto;
/* Negative indent footer by its height */
margin: 0 auto -60px;
margin: 0 auto -51px;
/* Pad bottom by footer height */
padding: 0 0 60px;
}
@ -38,6 +38,7 @@ h1, h2, h3, h4, h5, h6, .h1, .h2, .h3, .h4, .h5, .h6 {
.m20h {margin: 0 20px;}
.m5v {margin: 5px 0;}
.m20v {margin: 20px 0;}
.m10v {margin: 10px 0;}
.m50v {margin: 50px 0;}
.m10b {margin-bottom: 10px;}
@ -87,22 +88,22 @@ h1, h2, h3, h4, h5, h6, .h1, .h2, .h3, .h4, .h5, .h6 {
font-weight: 100;
}
#search::-webkit-input-placeholder {
#search::-webkit-input-placeholder {
font-family: Ubuntu, sans-serif;
font-weight: 100;
font-style: italic;
font-size: 13px;
font-size: 15px;
color: #BCDF7E;
line-height: 18px;
line-height: 20px;
}
#search::-moz-placeholder {
#search::-moz-placeholder {
font-family: Ubuntu, sans-serif;
font-weight: 100;
font-size: 13px;
font-size: 15px;
color: #BCDF7E;
line-height: 18px;
line-height: 20px;
}
.status {
@ -119,11 +120,10 @@ h1, h2, h3, h4, h5, h6, .h1, .h2, .h3, .h4, .h5, .h6 {
}
.col-gray {
width: 267px;
background-color: #F4F4F4;
padding: 15px;
margin-top: 21px;
width: 265px;
height: 87%;
border-radius: 5px;
}
@ -159,9 +159,8 @@ h1, h2, h3, h4, h5, h6, .h1, .h2, .h3, .h4, .h5, .h6 {
border-radius: :;px;
}
.block-id h1 {
.block-id h3 {
font-weight: bold;
font-size: 24px;
color: #FFFFFF;
line-height: 30px;
text-align: center;
@ -221,10 +220,29 @@ h1, h2, h3, h4, h5, h6, .h1, .h2, .h3, .h4, .h5, .h6 {
border: 2px solid #6C0000;
}
#status .table {
margin-bottom: 45px;
}
.progress-bar-info {
background-color: #8DC429;
}
/* Set the fixed height of the footer here */
#footer {
height: 60px;
background-color: #f5f5f5;
height: 51px;
background-color: #373D42;
border-top: 4px solid #656E76;
color: #fff;
}
#footer .insight {
font-size: 20px;
text-decoration: none;
}
.line-footer {
border-top: 2px dashed #ccc;
}
.line-bot {
@ -246,9 +264,14 @@ h1, h2, h3, h4, h5, h6, .h1, .h2, .h3, .h4, .h5, .h6 {
#wrap > .container {
padding: 60px 15px 0;
}
/*.container .text-muted {
margin: 20px 0;
}*/
.container .text-muted {
margin: 10px 0;
}
.container .text-muted a {
color: #eee;
}
#footer > .container {
padding-left: 15px;
@ -265,7 +288,13 @@ h1, h2, h3, h4, h5, h6, .h1, .h2, .h3, .h4, .h5, .h6 {
#search { width: 400px; }
.no_matching {
background-color: #FFFFFF;
border: 2px solid #64920F;
padding: 10px 20px;
position: absolute;
top: 46px;
}
/*Animations*/
.fader.ng-enter {
@ -334,4 +363,30 @@ h1, h2, h3, h4, h5, h6, .h1, .h2, .h3, .h4, .h5, .h6 {
background-color: #1a1a1a;
}
.expanded-tx {
border-bottom: 1px dashed #444;
}
.expanded-tx small {
font-size: 80%;
}
.status .t {
color: white;
display: inline-block;
padding:0px 5px;
}
.status .text-danger {
background: red;
}
.status .text-warning {
background: yellow;
color: black;
}
.status .text-default {
}

View File

@ -7,6 +7,10 @@ angular.module('insight').config(['$routeProvider',
when('/block/:blockHash', {
templateUrl: 'views/block.html'
}).
when('/block-index/:blockHeight', {
controller: 'BlocksController',
template: 'Redirecting...'
}).
when('/tx/:txId', {
templateUrl: 'views/transaction.html'
}).

View File

@ -1,13 +1,6 @@
'use strict';
angular.module('insight.address').controller('AddressController',
['$scope',
'$rootScope',
'$routeParams',
'$location',
'Global',
'Address',
'get_socket',
function ($scope, $rootScope, $routeParams, $location, Global, Address, get_socket) {
$scope.global = Global;
@ -33,4 +26,4 @@ angular.module('insight.address').controller('AddressController',
socket.emit('subscribe', $routeParams.addrStr);
$scope.params = $routeParams;
}]);
});

View File

@ -1,8 +1,20 @@
'use strict';
angular.module('insight.blocks').controller('BlocksController', ['$scope', '$rootScope', '$routeParams', '$location', 'Global', 'Block', 'Blocks', function ($scope, $rootScope, $routeParams, $location, Global, Block, Blocks) {
angular.module('insight.blocks').controller('BlocksController',
function ($scope, $rootScope, $routeParams, $location, Global, Block, Blocks, BlockByHeight) {
$scope.global = Global;
if ($routeParams.blockHeight) {
BlockByHeight.get({
blockHeight: $routeParams.blockHeight
}, function(hash) {
$location.path('/block/' + hash.blockHash);
}, function() {
$rootScope.flashMessage = 'Bad Request';
$location.path('/');
});
}
$scope.list = function() {
Blocks.get({
blockDate: $routeParams.blockDate
@ -32,4 +44,4 @@ angular.module('insight.blocks').controller('BlocksController', ['$scope', '$roo
};
$scope.params = $routeParams;
}]);
});

View File

@ -1,19 +0,0 @@
'use strict';
angular.module('insight.system').controller('FooterController',
['$scope',
'Global',
'Status',
function ($scope, Global, Status) {
$scope.global = Global;
$scope.getFooter = function() {
Status.get({
q: 'getInfo'
}, function(d) {
$scope.info = d.info;
});
};
}]);

View File

@ -1,6 +1,7 @@
'use strict';
angular.module('insight.system').controller('HeaderController', ['$scope', 'Global', function ($scope, Global) {
angular.module('insight.system').controller('HeaderController',
function ($scope, Global) {
$scope.global = Global;
$scope.menu = [
@ -15,4 +16,4 @@ angular.module('insight.system').controller('HeaderController', ['$scope', 'Glob
];
$scope.isCollapsed = false;
}]);
});

View File

@ -3,13 +3,7 @@
var TRANSACTION_DISPLAYED = 5;
var BLOCKS_DISPLAYED = 5;
angular.module('insight.system').controller('IndexController',
['$scope',
'$rootScope',
'Global',
'get_socket',
'Blocks',
'Transactions',
function($scope, $rootScope, Global, get_socket, Blocks, Transactions) {
function($scope, $rootScope, Global, get_socket, Blocks, Transactions) {
$scope.global = Global;
var socket = get_socket($scope);
@ -55,4 +49,4 @@ angular.module('insight.system').controller('IndexController',
$scope.txs = [];
$scope.blocks = [];
}]);
});

View File

@ -1,6 +1,7 @@
'use strict';
angular.module('insight.search').controller('SearchController', ['$scope', '$routeParams', '$location', 'Global', 'Block', 'Transaction', 'Address', function ($scope, $routeParams, $location, Global, Block, Transaction, Address) {
angular.module('insight.search').controller('SearchController',
function ($scope, $routeParams, $location, $timeout, Global, Block, Transaction, Address, BlockByHeight) {
$scope.global = Global;
$scope.search = function() {
@ -9,26 +10,35 @@ angular.module('insight.search').controller('SearchController', ['$scope', '$rou
$scope.badQuery = false;
$scope.q = '';
Block.get({
blockHash: q
}, function() {
$location.path('block/' + q);
}, function () { //block not found, search on TX
Transaction.get({
txId: q
BlockByHeight.get({
blockHeight: q
}, function(hash) {
$location.path('/block/' + hash.blockHash);
}, function() { // block by height not found
Block.get({
blockHash: q
}, function() {
$location.path('tx/' + q);
}, function () { //tx not found, search on Address
Address.get({
addrStr: q
$location.path('block/' + q);
}, function () { //block not found, search on TX
Transaction.get({
txId: q
}, function() {
$location.path('address/' + q);
}, function () { //address not found, fail :(
$scope.badQuery = true;
$scope.q = q;
});
});
$location.path('tx/' + q);
}, function () { //tx not found, search on Address
Address.get({
addrStr: q
}, function() {
$location.path('address/' + q);
}, function () { //address not found, fail :(
$scope.badQuery = true;
$timeout(function() {
$scope.badQuery = false;
}, 2000);
$scope.q = q;
});
});
});
});
};
}]);
});

View File

@ -1,15 +1,18 @@
'use strict';
angular.module('insight.status').controller('StatusController', ['$scope', '$routeParams', '$location', '$rootScope', 'Global', 'Status', 'Sync', function ($scope, $routeParams, $location, $rootScope, Global, Status, Sync) {
angular.module('insight.status').controller('StatusController',
function($scope, $routeParams, $location, $rootScope, Global, Status, Sync, get_socket) {
$scope.global = Global;
$scope.getStatus = function(q) {
Status.get({
q: 'get' + q
}, function(d) {
q: 'get' + q
},
function(d) {
$rootScope.infoError = null;
angular.extend($scope, d);
}, function(e) {
},
function(e) {
if (e.status === 503) {
$rootScope.infoError = 'Backend Error. ' + e.data;
}
@ -19,13 +22,26 @@ angular.module('insight.status').controller('StatusController', ['$scope', '$rou
});
};
var on_sync_update = function(sync) {
$scope.sync = sync;
};
$scope.getSync = function() {
Sync.get({}, function(sync) {
$rootScope.syncError = null;
$scope.sync = sync;
}, function(e) {
$rootScope.syncError = 'Could not get sync information' + e;
Sync.get({},
function(sync) {
on_sync_update(sync);
},
function(e) {
$scope.sync = { error: 'Could not get sync information' + e };
});
};
}]);
var socket = get_socket($scope);
socket.emit('subscribe', 'sync');
socket.on('status', function(sync) {
console.log('[status.js.55::] sync status update received!');
on_sync_update(sync);
});
});

View File

@ -1,28 +1,87 @@
'use strict';
angular.module('insight.transactions').controller('transactionsController',
['$scope',
'$rootScope',
'$routeParams',
'$location',
'Global',
'Transaction',
'TransactionsByBlock',
'TransactionsByAddress',
'get_socket',
function ($scope, $rootScope, $routeParams, $location, Global, Transaction, TransactionsByBlock, TransactionsByAddress, get_socket) {
function ($scope, $rootScope, $routeParams, $location, Global, Transaction, TransactionsByBlock, TransactionsByAddress, get_socket) {
$scope.global = Global;
$scope.loading = false;
$scope.loadedBy = null;
var pageNum = 0;
var pagesTotal = 1;
$scope.findThis = function() {
$scope.findTx($routeParams.txId);
};
$scope.aggregateItems = function(items) {
if (!items) return [];
var l = items.length;
var ret = [];
var tmp = {};
var u=0;
// TODO multiple output address
//
for(var i=0; i < l; i++) {
var notAddr = false;
// non standard input
if (items[i].scriptSig && !items[i].addr) {
items[i].addr = 'Unparsed address [' + u++ + ']';
items[i].notAddr = true;
notAddr = true;
}
// non standard output
if (items[i].scriptPubKey && !items[i].scriptPubKey.addresses) {
items[i].scriptPubKey.addresses = ['Unparsed address [' + u++ + ']'];
items[i].notAddr = true;
notAddr = true;
}
// multiple addr at output
if (items[i].scriptPubKey && items[i].scriptPubKey.addresses.length > 1) {
items[i].addr = items[i].scriptPubKey.addresses.join(',');
ret.push(items[i]);
continue;
}
var addr = items[i].addr || (items[i].scriptPubKey && items[i].scriptPubKey.addresses[0] );
if (!tmp[addr]) {
tmp[addr] = {};
tmp[addr].valueSat = 0;
tmp[addr].count = 0;
tmp[addr].addr = addr;
tmp[addr].items = [];
}
tmp[addr].valueSat += items[i].valueSat;
tmp[addr].value = tmp[addr].valueSat / 100000000;
tmp[addr].items.push(items[i]);
tmp[addr].notAddr = notAddr;
tmp[addr].count++;
}
angular.forEach(tmp, function(v) {
ret.push(v);
});
return (ret);
};
$scope.processTX = function(tx) {
tx.vinSimple = $scope.aggregateItems(tx.vin);
tx.voutSimple = $scope.aggregateItems(tx.vout);
};
$scope.findTx = function(txid) {
Transaction.get({
txId: txid
}, function(tx) {
$scope.tx = tx;
$scope.txs.push(tx);
$scope.processTX(tx);
$scope.txs.unshift(tx);
}, function(e) {
if (e.status === 400) {
$rootScope.flashMessage = 'Invalid Transaction ID: ' + $routeParams.txId;
@ -37,27 +96,62 @@ angular.module('insight.transactions').controller('transactionsController',
});
};
$scope.byBlock = function(bId) {
TransactionsByBlock.query({
block: bId
}, function(txs) {
$scope.txs = txs;
$scope.byBlock = function() {
TransactionsByBlock.get({
block: $routeParams.blockHash,
pageNum: pageNum
}, function(data) {
$scope.paginate(data);
});
};
$scope.byAddress = function(aId) {
TransactionsByAddress.query({
address: aId
}, function(txs) {
$scope.txs = txs;
$scope.byAddress = function () {
TransactionsByAddress.get({
address: $routeParams.addrStr,
pageNum: pageNum
}, function(data) {
$scope.paginate(data);
});
};
$scope.paginate = function (data) {
$scope.loading = false;
pagesTotal = data.pagesTotal;
pageNum += 1;
data.txs.forEach(function(tx) {
$scope.processTX(tx);
$scope.txs.push(tx);
});
};
$scope.load = function(from) {
$scope.loadedBy = from;
$scope.loadMore();
};
$scope.loadMore = function() {
if (pageNum < pagesTotal && !$scope.loading) {
$scope.loading = true;
if ($scope.loadedBy === 'address') {
$scope.byAddress();
}
else {
$scope.byBlock();
}
}
};
var socket = get_socket($scope);
socket.on('atx', function(tx) {
console.log('Incoming transaction for address!', tx);
console.log('atx '+tx.txid);
var beep = new Audio('/sound/transaction.mp3');
beep.play();
$scope.findTx(tx.txid);
});
$scope.txs = [];
}]);
});

View File

@ -1 +1,25 @@
'use strict';
'use strict';
angular.module('insight.address').directive('whenScrolled', ['$window', function($window) {
return {
link: function(scope, elm, attr) {
var pageHeight, clientHeight, scrollPos;
$window = angular.element($window);
var handler = function() {
pageHeight = window.document.documentElement.scrollHeight;
clientHeight = window.document.documentElement.clientHeight;
scrollPos = window.pageYOffset;
if (pageHeight - (scrollPos + clientHeight) === 0) {
scope.$apply(attr.whenScrolled);
}
};
$window.on('scroll', handler);
scope.$on('$destroy', function() {
return $window.off('scroll', handler);
});
}
};
}]);

View File

@ -1,6 +1,7 @@
'use strict';
angular.module('insight.address').factory('Address', ['$resource', function($resource) {
angular.module('insight.address').factory('Address',
function($resource) {
return $resource('/api/addr/:addrStr', {
addrStr: '@addStr'
}, {
@ -18,5 +19,5 @@ angular.module('insight.address').factory('Address', ['$resource', function($res
}
}
});
}]);
});

View File

@ -1,6 +1,7 @@
'use strict';
angular.module('insight.blocks').factory('Block', ['$resource', function($resource) {
angular.module('insight.blocks').factory('Block',
function($resource) {
return $resource('/api/block/:blockHash', {
blockHash: '@blockHash'
}, {
@ -18,8 +19,15 @@ angular.module('insight.blocks').factory('Block', ['$resource', function($resour
}
}
});
}]);
});
angular.module('insight.blocks').factory('Blocks',
function($resource) {
return $resource('/api/blocks');
});
angular.module('insight.blocks').factory('BlockByHeight',
function($resource) {
return $resource('/api/block-index/:blockHeight');
});
angular.module('insight.blocks').factory('Blocks', ['$resource', function($resource) {
return $resource('/api/blocks');
}]);

View File

@ -1,5 +1,7 @@
'use strict';
//Global service for global variables
angular.module('insight.system').factory('Global', [function() {}]);
angular.module('insight.system').factory('Global',
function() {
});

View File

@ -46,7 +46,8 @@ ScopedSocket.prototype.emit = function(event, data, callback) {
});
};
angular.module('insight.socket').factory('get_socket', ['$rootScope', function($rootScope) {
angular.module('insight.socket').factory('get_socket',
function($rootScope) {
var socket = io.connect();
return function(scope) {
var scopedSocket = new ScopedSocket(socket, $rootScope);
@ -55,5 +56,5 @@ angular.module('insight.socket').factory('get_socket', ['$rootScope', function($
});
return scopedSocket;
};
}]);
});

View File

@ -1,12 +1,14 @@
'use strict';
angular.module('insight.status').factory('Status', ['$resource', function($resource) {
angular.module('insight.status').factory('Status',
function($resource) {
return $resource('/api/status', {
q: '@q'
});
}]);
});
angular.module('insight.status').factory('Sync', ['$resource', function($resource) {
angular.module('insight.status').factory('Sync',
function($resource) {
return $resource('/api/sync');
}]);
});

View File

@ -1,6 +1,7 @@
'use strict';
angular.module('insight.transactions').factory('Transaction', ['$resource', function($resource) {
angular.module('insight.transactions').factory('Transaction',
function($resource) {
return $resource('/api/tx/:txId', {
txId: '@txId'
}, {
@ -18,20 +19,23 @@ angular.module('insight.transactions').factory('Transaction', ['$resource', func
}
}
});
}]);
});
angular.module('insight.transactions').factory('TransactionsByBlock', ['$resource', function($resource) {
angular.module('insight.transactions').factory('TransactionsByBlock',
function($resource) {
return $resource('/api/txs', {
block: '@block'
});
}]);
});
angular.module('insight.transactions').factory('TransactionsByAddress', ['$resource', function($resource) {
angular.module('insight.transactions').factory('TransactionsByAddress',
function($resource) {
return $resource('/api/txs', {
address: '@address'
});
}]);
});
angular.module('insight.transactions').factory('Transactions', ['$resource', function($resource) {
angular.module('insight.transactions').factory('Transactions',
function($resource) {
return $resource('/api/txs');
}]);
});

Binary file not shown.

View File

@ -1,45 +1,44 @@
<section data-ng-controller="AddressController" data-ng-init="findOne()">
<div class="page-header">
<h1>
Address
<small>Addresses are identifiers which you use to send bitcoins to another person.</small>
</h1>
</div>
<div class="row">
<div class="col-lg-9">
<table class="table table-striped">
<tbody>
<tr>
<td>Address</td>
<td><a href="/#!/address/{{address.addrStr}}">{{address.addrStr}}</a></td>
</tr>
<tr>
<td>Total Received</td>
<td>{{address.totalReceived}} BTC</td>
</tr>
<tr>
<td>Total Sent</td>
<td>{{address.totalSent}} BTC</td>
</tr>
<tr>
<td>Final Balance</td>
<td>{{address.balance}} BTC</td>
</tr>
<tr>
<td>No. Transactions</td>
<td>{{address.txApperances}}</td>
</tr>
<div class="col-md-3">
<div class="bs-sidebar hidden-print affix col-gray">
<div class="text-center m10v">
<qrcode size="200" data="{{address.addrStr}}"></qrcode>
<h4><span class="glyphicon glyphicon-qrcode"></span> Address</h4>
<a class="ellipsis" href="/#!/address/{{address.addrStr}}">{{address.addrStr}}</a>
</div>
</tbody>
</table>
</div>
<div class="col-lg-3">
<qrcode size="200" data="{{address.addrStr}}"></qrcode>
</div>
</div>
<div class="m50v">
<h4>Summary</h4>
<table class="table">
<tbody>
<tr>
<td class="small">Total Received</td>
<td class="address ellipsis text-right">{{address.totalReceived}} BTC</td>
</tr>
<tr>
<td class="small">Total Sent</td>
<td class="address ellipsis text-right">{{address.totalSent}} BTC</td>
</tr>
<tr>
<td class="small">Final Balance</td>
<td class="address ellipsis text-right">{{address.balance}} BTC</td>
</tr>
<tr>
<td class="small">No. Transactions</td>
<td class="address ellipsis text-right">{{address.txApperances}}</td>
</tr>
</tbody>
</table>
</div> <!-- END OF TRANSACTIONS TABLE -->
</div>
</div> <!-- END OF COL-MD-3 -->
<div data-ng-controller="transactionsController" data-ng-init="byAddress(params.addrStr)">
<h2>Transactions <small>Transactions contained within this block</small></h2>
<div data-ng-include src="'/views/transaction/list.html'"></div>
</div>
<div class="col-md-9">
<div data-ng-controller="transactionsController" data-ng-init="load('address')">
<h2>Transactions <small>Transactions for this address</small></h2>
<div data-ng-include src="'/views/transaction/list.html'" when-scrolled="loadMore()"></div>
</div>
</div>
</div> <!-- END OF ROW -->
</section>

View File

@ -1,40 +1,39 @@
<section data-ng-controller="BlocksController" data-ng-init="findOne()">
<div class="row">
<div class="col-md-3">
<div class="bs-sidebar hidden-print affix col-gray">
<div class="bs-sidebar affix col-gray">
<div class="block-id">
<div class="icon-block text-center">
<span class="glyphicon glyphicon-list-alt"></span>
</div>
<h1 data-ng-if="block">Block #{{ block.height }}</h1>
<h3 data-ng-if="block">Block #{{ block.height }}</h3>
</div>
<div class="m50v" data-ng-show="!tx.isCoinBase">
<h4>Hashes</h4>
<table class="table">
<tbody>
<tr>
<td> <small>Hash </small></td>
<td class="small"> Hash </td>
<td><a class="address ellipsis" href="/#!/block/{{block.hash}}">{{block.hash}}</a></td>
</tr>
<tr>
<td><small> Previous Block</small></td>
<td class="small"> Previous Block</td>
<td><a class="address ellipsis" href="/#!/block/{{block.previousblockhash}}">{{block.previousblockhash}}</a></td>
</tr>
<tr>
<td><small> Next Block</small></td>
<td class="small"> Next Block</td>
<td><a class="address ellipsis" href="/#!/block/{{block.nextblockhash}}">{{block.nextblockhash}}</a></td>
</tr>
<tr>
<td><small> Merkle Root</small></td>
<td class="small">Merkle Root</td>
<td> <p class="address ellipsis"> {{block.merkleroot}} </p> </td>
</tr>
</tbody>
</table>
</div>
</div> <!-- END OF TRANSACTIONS TABLE -->
</div>
</div> <!-- END OF COL-GRAY -->
</div> <!-- END OF COL-GRAY -->
<div class="col-md-9">
<h3>Summary</h3>
@ -69,7 +68,7 @@
<td class="text-right text-muted">{{block.bits}}</td>
</tr>
<tr>
<td> <strong> Size </strong></td>
<td> <strong> Size (bytes) </strong></td>
<td class="text-right text-muted">{{block.size}}</td>
</tr>
<tr>
@ -85,9 +84,9 @@
</div>
</div><!-- END OF ROW -->
<div data-ng-controller="transactionsController" data-ng-init="byBlock(params.blockHash)">
<div data-ng-controller="transactionsController" data-ng-init="load('block')">
<h2>Transactions <small >Transactions contained within this block</small></h2>
<div data-ng-include src="'/views/transaction/list.html'"></div>
<div data-ng-include src="'/views/transaction/list.html'" when-scrolled="loadMore()"></div>
</div>
</div>
</div>

View File

@ -1,27 +1,44 @@
<section data-ng-controller="BlocksController" data-ng-init="list()">
<div class="page-header">
<h1>
<span class="glyphicon glyphicon-calendar"></span>
Blocks
<small>by date</small>
</h1>
<ul class="pagination">
<li><a href="#!/blocks-date/{{pagination.prev}}">&laquo; {{pagination.prev}}</a></li>
<li class="disabled"><a href="#">{{pagination.current}}</a></li>
<li><a href="#!/blocks-date/{{pagination.next}}">{{pagination.next}} &raquo;</a></li>
</ul>
<div class="row">
<div class="col-md-3">
<div class="bs-sidebar affix col-gray">
<div class="block-id">
<div class="icon-block text-center">
<span class="glyphicon glyphicon-list"></span>
<h3>Blocks <br> mined on:</h3>
</div>
</div>
<p class="lead text-center m20v">{{pagination.current}}</p>
<div class="m50v">
<a class="btn btn-default" href="#!/blocks-date/{{pagination.prev}}"><small>&larr; {{pagination.prev}}</small></a>
<a class="btn btn-primary" href="#!/blocks-date/{{pagination.next}}"><small>{{pagination.next}} &rarr;</small></a>
</div>
</div>
</div>
<div class="col-md-8">
<div class="page-header">
<h1>
Blocks
<small>by date</small>
</h1>
</div>
<table class="table" data-ng-show="blocks || blocks.length">
<thead>
<tr>
<th>Hash</th>
<th>Solved at</th>
</tr>
</thead>
<tbody>
<tr data-ng-repeat="block in blocks">
<td><a href="#!/block/{{block.hash}}">{{block.hash}}</a></td>
<td>{{block.time * 1000 | date:'medium'}}</td>
</tr>
</tbody>
</table>
</div>
</div>
<table class="table table-striped" ng-show="blocks || blocks.length">
<thead>
<th>Hash</th>
<th>Solved at</th>
</thead>
<tbody>
<tr data-ng-repeat="block in blocks">
<td><a href="#!/block/{{block.hash}}">{{block.hash}}</a></td>
<td>{{block.time * 1000 | date:'medium'}}</td>
</tr>
</tbody>
</table>
<h1 class="text-center text-muted" ng-hide="!blocks || blocks.length">No blocks yet.</h1>
<h2 class="text-center text-muted" data-ng-hide="!blocks || blocks.length">No blocks yet.</h2>
</section>

View File

@ -1,10 +1,3 @@
<div data-ng-controller="FooterController" data-ng-init="getFooter()">
<div class="container">
<p class="text-muted text-right" data-ng-show="info.blocks">
Blocks: {{info.blocks}} |
Connections: {{info.connections}} |
Difficulty: {{info.difficulty}}
</p>
</div>
<div class="container">
<a class="insight m10v pull-right" href="/"><small>Insight</small></a>
</div>

View File

@ -11,29 +11,34 @@
</div>
<div class="collapse navbar-collapse">
<ul class="nav navbar-nav">
<li data-ng-repeat="item in menu" ui-route="/{{item.link}}" ng-class="{active: $uiRoute}">
<li data-ng-repeat="item in menu" ui-route="/{{item.link}}" data-ng-class="{active: $uiRoute}">
<a href="#!/{{item.link}}">{{item.title}}</a>
</li>
</ul>
<div ng-controller="SearchController">
<form class="navbar-form navbar-left" role="search" ng-submit="search()">
<div class="form-group" ng-class="{'has-error': badQuery}">
<input id="search" type="text" class="form-control" ng-model="q" placeholder="Search for block, transaction or address">
<div data-ng-controller="SearchController">
<form class="navbar-form navbar-left" role="search" data-ng-submit="search()">
<div class="form-group" data-ng-class="{'has-error': badQuery}">
<input id="search" type="text" class="form-control" data-ng-model="q" placeholder="Search for block, transaction or address">
</div>
<span class="text-danger" ng-show="badQuery">No matching records found!</span>
<div class="no_matching text-danger" data-ng-show="badQuery">No matching records found!</div>
</form>
<div class="status" data-ng-controller="FooterController" data-ng-init="getFooter()">
<i class="small" data-ng-show="info.blocks">
<strong>Status:</strong> On Sync
</i>
<i class="small">
<strong> Connections: </strong> {{info.connections}}
</i>
<i class="small">
<strong>Height:</strong> {{info.blocks}}
</i>
</div>
<div class="status" data-ng-controller="StatusController">
<span data-ng-init="getSync()">
<div class="t text-danger" data-ng-show="sync.error" tooltip="{{sync.error}}" tooltip-placement="bottom"> ERROR </div>
<div class="t text-warning " tooltip="{{sync.syncedBlocks}} / {{sync.blockChainHeight}} synced. {{sync.skippedBlocks}} skipped" tooltip-placement="bottom" data-ng-show="sync.status==='syncing'"> {{sync.status}} {{sync.syncPercentage}}%</div>
<div class="t text-default" tooltip="Historic sync finished" tooltip-placement="bottom" data-ng-show="sync.status==='finished'"> On sync</div>
</span>
</span>
<span data-ng-init="getStatus('Info')">
<i class="small">
<strong> Conn: </strong> {{info.connections}}
</i>
<i class="small">
<strong>Height:</strong> {{info.blocks}}
</i>
</span>
</div>
</div>
</div>
</div>
</div>

View File

@ -1,130 +1,42 @@
<section data-ng-controller="StatusController">
<div class="page-header">
<h1>
Status
Application Status
</h1>
</div>
<div class="row">
<div class="col-lg-6">
<h3>getInfo</h3>
<table class="table table-striped" data-ng-init="getStatus('Info')">
<div id="status" class="row">
<div class="col-md-9">
<h4>Sync Status</h4>
<table class="table" data-ng-init="getSync()">
<tbody>
<tr data-ng-show="!info &amp;&amp; !infoError">
<td colspan="2" class="text-center">Loading...
<tr data-ng-show="infoError">
<td colspan="2" class="text-danger">{{infoError}}
<tr>
<td>Version</td>
<td>{{info.version}}</td>
</tr>
<tr>
<td>protocolversion</td>
<td>{{info.protocolversion}}</td>
</tr>
<tr>
<td>walletversion</td>
<td>{{info.walletversion}}</td>
</tr>
<tr>
<td>balance</td>
<td>{{info.balance}}</td>
</tr>
<tr>
<td>blocks</td>
<td>{{info.blocks}}</td>
</tr>
<tr>
<td>timeoffset</td>
<td>{{info.timeoffset}}</td>
</tr>
<tr>
<td>connections</td>
<td>{{info.connections}}</td>
</tr>
<tr>
<td>proxy</td>
<td>{{info.proxy}}</td>
</tr>
<tr>
<td>difficulty</td>
<td>{{info.difficulty}}</td>
</tr>
<tr>
<td>testnet</td>
<td>{{info.testnet}}</td>
</tr>
<tr>
<td>keypoololdest</td>
<td>{{info.keypoololdest}}</td>
</tr>
<tr>
<td>keypoolsize</td>
<td>{{info.keypoolsize}}</td>
</tr>
<tr>
<td>paytxfee</td>
<td>{{info.paytxfee}}</td>
</tr>
<tr>
<td>infoErrors</td>
<td>{{info.infoErrors}}</td>
</tr>
</tbody>
</table>
</div>
<div class="col-lg-6">
<h3>sync status</h3>
<table class="table table-striped" data-ng-init="getSync()">
<tbody>
<tr data-ng-show="syncError">
<td colspan="2"> <span class="text-danger"> {{ syncError }} </span>
<tr data-ng-show="sync.error">
<td colspan="2"> <span class="text-danger"> {{ sync.err }} </span>
<td colspan="2"> <span class="text-danger"> {{sync.error}} </span>
</tr>
<tr>
<td>Sync Progress</td>
<td> {{(100 * sync.syncedBlocks/sync.blocksToSync)| number:2}}%
<td>
<div class="progress">
<div class="progress-bar progress-bar-info" role="progressbar" aria-valuenow="40" aria-valuemin="0" aria-valuemax="100" style="width: {{ sync.syncPercentage}}%">
<span>{{sync.syncPercentage}}% Complete</span>
</div>
</div>
</tr>
<tr>
<td>blocksToSync</td>
<td>{{sync.blocksToSync}}</td>
<td>Initial Block Chain Height</td>
<td class="text-right">{{sync.blockChainHeight}}</td>
</tr>
<tr>
<td>syncedBlocks</td>
<td>{{sync.syncedBlocks}}</td>
</tr>
<td>Synced Blocks</td>
<td class="text-right">{{sync.syncedBlocks}}</td>
<tr>
<td>start</td>
<td>{{sync.start}}
<span data-ng-show="sync.isStartGenesis"> (genesisBlock) </span>
</td>
</tr>
<tr>
<td>end</td>
<td>{{sync.end}}
<span data-ng-show="sync.isEndGenesis"> (genesisBlock) </span>
</td>
</tr>
<tr>
<td>Sync Type</td>
<td>
<ul>
<li data-ng-show="sync.upToExisting"> <span> Stops at existing block </span>
<li>
<span data-ng-show="sync.scanningBackward"> scanningBackward </span>
<span data-ng-hide="sync.scanningBackward"> scanningForward </span>
</ul>
</td>
</tr>
<td>Skipped Blocks (previously synced)</td>
<td class="text-right">{{sync.skippedBlocks}}</td>
</tbody>
</table>
<h3>getTxOutSetInfo</h3>
<table class="table table-striped" data-ng-init="getStatus('TxOutSetInfo')">
<h4>Transaction Output Set Information</h4>
<table class="table" data-ng-init="getStatus('TxOutSetInfo')">
<tbody>
<tr data-ng-show="!txoutsetinfo &amp;&amp; !infoError">
<td colspan="2" class="text-center">Loading...</td>
@ -134,59 +46,37 @@
</tr>
<tr>
<td>Height</td>
<td>{{txoutsetinfo.height}}</td>
<td class="text-right"><a href="/#!/block-index/{{txoutsetinfo.height}}">{{txoutsetinfo.height}}</a></td>
</tr>
<tr>
<td>bestblock</td>
<td>{{txoutsetinfo.bestblock}}</td>
<td>Best Block</td>
<td class="text-right"><a href="/#!/block/{{txoutsetinfo.bestblock}}">{{txoutsetinfo.bestblock}}</a></td>
</tr>
<tr>
<td>transactions</td>
<td>{{txoutsetinfo.transactions}}</td>
<td>Transactions</td>
<td class="text-right"> {{txoutsetinfo.transactions}}</td>
</tr>
<tr>
<td>txouts</td>
<td>{{txoutsetinfo.txouts}}</td>
<td>Transaction Outputs</td>
<td class="text-right">{{txoutsetinfo.txouts}}</td>
</tr>
<tr>
<td>bytes_serialized</td>
<td>{{txoutsetinfo.bytes_serialized}}</td>
<td>Bytes Serialized</td>
<td class="text-right">{{txoutsetinfo.bytes_serialized}}</td>
</tr>
<tr>
<td>hash_serialized</td>
<td>{{txoutsetinfo.hash_serialized}}</td>
<td>Hash Serialized</td>
<td class="text-right">{{txoutsetinfo.hash_serialized}}</td>
</tr>
<tr>
<td>total_amount</td>
<td>{{txoutsetinfo.total_amount}}</td>
<td>Total Amount</td>
<td class="text-right">{{txoutsetinfo.total_amount}}</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="row">
<div class="col-lg-6">
<h3>getDifficulty</h3>
<table class="table table-striped" data-ng-init="getStatus('Difficulty')">
<tbody>
<tr data-ng-show="!difficulty &amp;&amp; !infoError">
<td colspan="2" class="text-center">Loading...</td>
</tr>
<tr data-ng-show="infoError">
<td colspan="2" class="text-danger">{{infoError}}</td>
</tr>
<tr>
<td>Difficulty</td>
<td>{{difficulty}}</td>
</tr>
</tbody>
</table>
</div>
<div class="col-lg-6">
<h3>getLastBlockHash</h3>
<table class="table table-striped" data-ng-init="getStatus('LastBlockHash')">
<h4>Last Block</h4>
<table class="table" data-ng-init="getStatus('LastBlockHash')">
<tbody>
<tr data-ng-show="!lastblockhash &amp;&amp; !infoError">
<td colspan="2" class="text-center">Loading...</td>
@ -196,11 +86,98 @@
</tr>
<tr>
<td>Last block hash</td>
<td>{{lastblockhash}}</td>
<td class="text-right"><a href="/#!/block/{{lastblockhash}}">{{lastblockhash}}</a></td>
</tr>
</tbody>
</table>
</div>
</div> <!-- END OF COL-8 -->
<div class="col-md-3">
<div class="col-gray">
<h4>Bitcoin node information</h4>
<table class="table" data-ng-init="getStatus('Info')">
<tbody>
<tr data-ng-show="!info &amp;&amp; !infoError">
<td colspan="2" class="text-center">Loading...
<tr data-ng-show="infoError">
<td colspan="2" class="text-danger">{{infoError}}
<tr>
<td>Version</td>
<td class="text-right">{{info.version}}</td>
</tr>
<tr>
<td>Protocol version</td>
<td class="text-right">{{info.protocolversion}}</td>
</tr>
<tr>
<td>Wallet version</td>
<td class="text-right">{{info.walletversion}}</td>
</tr>
<tr>
<td>Balance (BTC)</td>
<td class="text-right">{{info.balance}}</td>
</tr>
<tr>
<td>Blocks</td>
<td class="text-right"><a href="/#!/block-index/{{info.blocks}}">{{info.blocks}}</a></td>
</tr>
<tr>
<td>Time Offset</td>
<td class="text-right">{{info.timeoffset}}</td>
</tr>
<tr>
<td>Connections to other nodes</td>
<td class="text-right">{{info.connections}}</td>
</tr>
<tr>
<td>Proxy setting</td>
<td class="text-right">{{info.proxy}}</td>
</tr>
<tr>
<td>Mining Difficulty</td>
<td class="text-right">{{info.difficulty}}</td>
</tr>
<tr>
<td>Testnet</td>
<td class="text-right">{{info.testnet}}</td>
</tr>
<tr>
<td>Keypool Oldest Date</td>
<td class="text-right">{{info.keypoololdest*1000 | date:'medium' }}</td>
</tr>
<tr>
<td>Keypool Size</td>
<td class="text-right">{{info.keypoolsize}}</td>
</tr>
<tr>
<td>Default Transaction Fee (BTC)</td>
<td class="text-right">{{info.paytxfee}}</td>
</tr>
<tr>
<td>Info Errors</td>
<td class="text-right">{{info.infoErrors}}</td>
</tr>
</tbody>
</table>
<h4>Difficulty</h4>
<table class="table" data-ng-init="getStatus('Difficulty')">
<tbody>
<tr data-ng-show="!difficulty &amp;&amp; !infoError">
<td colspan="2" class="text-center">Loading...</td>
</tr>
<tr data-ng-show="infoError">
<td colspan="2" class="text-danger">{{infoError}}</td>
</tr>
<tr>
<td>Mining Difficulty</td>
<td>{{difficulty}}</td>
</tr>
</tbody>
</table>
</div> <!-- END OF COL-GRAY -->
</div> <!-- END OF COL-3 -->
</div>
</section>

View File

@ -5,72 +5,13 @@
</h1>
<div class="block-tx">
<div class="line-bot">
<a href="/#!/tx/{{tx.txid}}">{{tx.txid}}</a>
</div>
<div class="row m10b">
<div class="col-md-5">
<ul class="list-unstyled" data-ng-repeat="vin in tx.vin" data-ng-show="!tx.isCoinBase">
<li>
<a class="m10h vm lead glyphicon glyphicon-circle-arrow-left" href="/#!/tx/{{vin.txid}}" alt="Outpoint: {{vin.txid}},{{vin.vout}}" data-toggle="tooltip" title="Outpoint: {{vin.txid}},{{vin.vout}}">
</a>
<span data-ng-show="!vin.addr">Address could not be parsed</span>
<a data-ng-show="vin.addr" href="/#!/address/{{vin.addr}}">{{vin.addr}}</a>
<span class="pull-right badge">{{vin.value}} BTC</span>
</li>
</ul>
<div data-ng-show="tx.isCoinBase">
<ul class="list-unstyled" data-ng-repeat="vinn in tx.vin">
<li>
No Inputs (Newly Generated isCoinBasens)
<span class="pull-right badge">{{vinn.reward}} BTC</span>
</li>
</ul>
</div>
</div>
<div class="col-md-2 text-center">
<span class="glyphicon glyphicon-chevron-right lead"></span>
</div>
<div class="col-md-5" data-ng-repeat="vout in tx.vout">
<div class="row m10b">
<div class="col-md-3">
<b>{{vout.scriptPubKey.type}}</b>
</div>
<div class="col-md-9">
<ul class="list-unstyled" data-ng-repeat="addr in vout.scriptPubKey.addresses">
<li>
<a class="ellipsis pull-left" style="width:200px;" href="/#!/address/{{addr}}">{{addr}}</a>
<span class="pull-right badge">{{vout.value}} BTC</span>
</li>
</ul>
</div>
</div>
</div>
</div>
<div class="line-top">
<small class="text-muted">Feeds: {{tx.feeds}}</small>
<div class="pull-right">
<button data-ng-show="tx.confirmations" type="button" class="btn btn-success">
{{tx.confirmations}} Confirmations
</button>
<button data-ng-show="!tx.confirmations" type="button" class="btn btn-danger">
Unconfirmed Transaction!
</button>
<button type="button" class="btn btn-primary">{{tx.valueOut}} BTC</button>
</div>
</div>
<div data-ng-include src="'/views/transaction/tx.html'"></div>
</div><!-- END OF BLOCK-TX -->
<div class="row m50v">
<div data-ng-class="{'col-md-6':!tx.isCoinBase}">
<h3>Summary</h3>
<table class="table">
<table class="table" style="table-layout: fixed">
<tbody>
<tr>
<td><strong> Size </strong></td>
@ -81,9 +22,10 @@
<td class="text-muted text-right">{{tx.time * 1000|date:'medium'}}</td>
</tr>
<tr>
<td><strong>Block </strong></td>
<td class="text-muted text-right"><a href="/#!/block/{{tx.blockhash}}">Block</a></td>
</tr>
<td><strong>Block </strong>
<td class="text-right">
<a href="/#!/block/{{tx.blockhash}}" class=" ellipsis">{{tx.blockhash}}</a>
</tbody>
</table>
</div>
@ -101,11 +43,12 @@
</tr>
<tr>
<td><strong>Fees</strong></td>
<td class="text-muted text-right">{{tx.feeds}} BTC</td>
<td class="text-muted text-right">{{tx.fees}} BTC</td>
</tr>
</tbody>
</table>
</div>
</div>
</section>

View File

@ -1,71 +1,9 @@
<div data-ng-show="!txs || txs.lenght">Loading...</div>
<div class="alert alert-warning" data-ng-show="txs && !txs[0].txid">There are not transactions</div>
<div class="block-tx" data-ng-show="txs && txs[0].txid" data-ng-repeat="tx in txs">
<div class="line-bot">
<a href="/#!/tx/{{tx.txid}}">{{tx.txid}}</a>
<span class="pull-right">{{tx.time * 1000 | date:'medium'}}</span>
</div>
<div class="row">
<div class="col-md-5">
<div class="ellipsis row" data-ng-show="tx.isCoinBase" data-ng-repeat="vin in tx.vin">
<div class="col-md-10">
<p class="ellipsis">No Inputs (Newly Generated isCoinBasens)</p>
</div>
<p class="text-muted pull-right"> <small>{{vin.reward}} BTC</small></p>
</div>
<ul class="list-unstyled" data-ng-repeat="vin in tx.vin" data-ng-show="!tx.isCoinBase">
<li class="row">
<a class="col-md-1 glyphicon glyphicon-circle-arrow-left" href="/#!/tx/{{vin.txid}}" alt="Outpoint: {{vin.txid}},{{vin.vout}}" data-toggle="tooltip" title="Outpoint: {{vin.txid}},{{vin.vout}}">
</a>
<div class="col-md-8">
<div class="ellipsis">
<span data-ng-show="!vin.addr">Address could not be parsed</span>
<a data-ng-show="vin.addr" href="/#!/address/{{vin.addr}}">{{vin.addr}}</a>
</div>
</div>
<p class="text-right text-muted"><small>{{vin.value}} BTC</small></p>
</li>
</ul>
</div>
<div class="col-md-1 text-center">
<span class="glyphicon glyphicon-arrow-right lead">&nbsp;</span>
</div>
<div class="col-md-6">
<div class="row">
<div class="col-md-3">
<div data-ng-repeat="vout in tx.vout">
<small data-ng-repeat="address in vout.scriptPubKey.addresses">{{vout.scriptPubKey.type}}</small>
</div>
</div>
<div data-ng-repeat="vout in tx.vout">
<div class="col-md-6">
<div class="ellipsis">
<a href="/#!/address/{{address}}" data-ng-repeat="address in vout.scriptPubKey.addresses" class="ellipsis">{{address}}</a>
</div>
</div>
<div class="col-md-3">
<p class="text-right text-muted"> <small>{{vout.value}} BTC</small></p>
</div>
</div>
</div>
</div>
</div>
<div class="line-top">
<div class="m5v">
<div class="pull-right">
<button data-ng-show="tx.confirmations" type="button" class="btn btn-success">
{{tx.confirmations}} Confirmations
</button>
<button data-ng-show="!tx.confirmations" type="button" class="btn btn-danger">
Unconfirmed Transaction!
</button>
<button type="button" class="btn btn-primary">{{tx.valueOut}} BTC</button>
</div>
<small data-ng-show="!tx.isCoinBase" class="text-muted">Feeds: {{tx.feeds}}</small>
</div>
<div class="alert alert-warning" data-ng-show="txs.length && !txs[0].txid">There are not transactions</div>
<div class="block-tx fader" data-ng-show="txs && txs[0].txid" data-ng-repeat="tx in txs">
<div data-ng-include src="'/views/transaction/tx.html'"></div>
</div>
<div class="progress progress-striped active" data-ng-show="!txs.length || loading">
<div class="progress-bar" style="width: 100%">
<span>Loading...</span>
</div>
</div>

View File

@ -0,0 +1,98 @@
<div class="line-bot">
<a href="/#!/tx/{{tx.txid}}">{{tx.txid}}</a>
<a class="pull-right" style="margin-left:10px" data-ng-click="itemsExpanded = !itemsExpanded">
<span class="glyphicon" data-ng-class="{'glyphicon-minus-sign': itemsExpanded, 'glyphicon-plus-sign': !itemsExpanded}" tooltip="Show/Hide items details" tooltip-placement="down"></span>
</a>
<span class="pull-right">{{tx.time * 1000 | date:'medium'}}</span>
</div>
<div class="row">
<div class="col-md-5">
<div class="ellipsis row" data-ng-show="tx.isCoinBase" data-ng-repeat="vin in tx.vin">
<div class="col-md-10">
<p class="ellipsis">No Inputs (Newly Generated Coins)</p>
</div>
<p class="text-muted pull-right"> <small>{{vin.reward}} BTC</small></p>
</div>
<div data-ng-show="!tx.isCoinBase">
<ul class="list-unstyled" data-ng-repeat="vin in tx.vinSimple" data-ng-show="!itemsExpanded">
<li class="row">
<div class="col-md-8">
<div class="ellipsis">
<span data-ng-show="vin.notAddr">{{vin.addr}}</span>
<a data-ng-show="!vin.notAddr" href="/#!/address/{{vin.addr}}">{{vin.addr}}</a>
</div>
</div>
<p class="text-right text-muted"><small>{{vin.value}} BTC</small></p>
</ul>
<ul class="list-unstyled" data-ng-repeat="vin in tx.vin" data-ng-show="itemsExpanded">
<li class="row expanded-tx">
<a class="col-md-1 glyphicon glyphicon-arrow-right" href="/#!/tx/{{vin.txid}}" alt="Outpoint: {{vin.txid}},{{vin.vout}}" tooltip="Outpoint: {{vin.txid}},{{vin.vout}}" tooltip-placement="right" >
</a>
<div class="col-md-8">
<div class="ellipsis">
<span data-ng-show="vin.notAddr">{{vin.addr}}</span>
<a data-ng-show="!vin.notAddr" href="/#!/address/{{vin.addr}}">{{vin.addr}}</a>
</div>
<div style="word-wrap:break-word">
<small><strong>scriptSig</strong> {{vin.scriptSig.asm}}</small>
</div>
</div>
<p class="text-right text-muted"><small>{{vin.value}} BTC</small></p>
</ul>
</div>
</div>
<div class="col-md-1 text-center">
<span class="glyphicon glyphicon-arrow-right lead">&nbsp;</span>
</div>
<div class="col-md-6">
<div class="row">
<div data-ng-repeat="vout in tx.voutSimple" data-ng-show="!itemsExpanded">
<div class="col-md-9">
<div class="ellipsis">
<span data-ng-show="vout.notAddr">{{vout.addr}}</span>
<a href="/#!/address/{{address}}" data-ng-show="!vout.notAddr" data-ng-repeat="address in vout.addr.split(',')">{{address}}</a>
</div>
</div>
<div class="col-md-3">
<p class="text-right text-muted"> <small>{{vout.value}} BTC</small></p>
</div>
</div>
<div data-ng-repeat="vout in tx.vout" data-ng-show="itemsExpanded">
<div class="col-md-9 expanded-tx">
<div class="ellipsis">
<a href="/#!/address/{{address}}" data-ng-repeat="address in vout.scriptPubKey.addresses">{{address}}</a>
</div>
<small><strong>type</strong> {{vout.scriptPubKey.type}}</small>
<div>
<small><strong>scriptPubKey</strong> {{vout.scriptPubKey.asm}}</small>
</div>
</div>
<div class="col-md-3">
<p class="text-right text-muted"> <small>{{vout.value}} BTC</small></p>
</div>
</div>
</div>
</div>
</div>
<div class="line-top">
<div class="m5v">
<div class="pull-right">
<button data-ng-show="tx.confirmations" type="button" class="btn btn-success">
{{tx.confirmations}} Confirmations
</button>
<button data-ng-show="!tx.confirmations" type="button" class="btn btn-danger">
Unconfirmed Transaction!
</button>
<button type="button" class="btn btn-primary">{{tx.valueOut}} BTC</button>
</div>
<small data-ng-show="!tx.isCoinBase" class="text-muted">Fees: {{tx.fees}}</small>
</div>
</div>

View File

@ -33,10 +33,10 @@ async.series([
},
function(cb) {
if (program.smart) {
historicSync.smart_import(cb);
historicSync.smartImport(cb);
}
else {
historicSync.import_history({
historicSync.importHistory({
destroy: program.destroy,
reverse: program.reverse,
upToExisting: program.uptoexisting,
@ -50,7 +50,7 @@ async.series([
console.log('CRITICAL ERROR: ', err);
}
else {
console.log('Finished.\n Status:\n', historicSync.syncInfo);
console.log('Finished.\n Status:\n', historicSync.info());
}
});