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" 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 ## Syncing old blockchain data
Run sync from insight repository (to save old blocks and transactions in MongoDB): 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 * 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); ios.sockets.in(address).emit('atx', tx);
}; };
module.exports.broadcastSyncInfo = function(syncInfo) { module.exports.broadcastSyncInfo = function(historicSync) {
ios.sockets.emit('sync', syncInfo); ios.sockets.in('sync').emit('status', historicSync);
}; };

View File

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

View File

@ -52,8 +52,12 @@ var getTransaction = function(txid, cb) {
*/ */
exports.list = function(req, res, next) { exports.list = function(req, res, next) {
var bId = req.query.block; var bId = req.query.block;
var aId = req.query.address; var addrStr = req.query.address;
var limit = req.query.limit || 1000; var page = req.query.pageNum;
var pageLength = 20;
var pagesTotal = 1;
var txLength;
var txs;
if (bId) { if (bId) {
Block.fromHashWithInfo(bId, function(err, block) { Block.fromHashWithInfo(bId, function(err, block) {
@ -63,14 +67,28 @@ exports.list = function(req, res, next) {
return 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) { function(err, results) {
res.jsonp(results); res.jsonp({
pagesTotal: pagesTotal,
txs: results
});
}); });
}); });
} }
else if (aId) { else if (addrStr) {
var a = Address.new(aId); var a = Address.new(addrStr);
a.update(function(err) { a.update(function(err) {
if (err && !a.totalReceivedSat) { if (err && !a.totalReceivedSat) {
@ -79,23 +97,25 @@ exports.list = function(req, res, next) {
return 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) { 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! // TODO TXout!
//T //T
function (cb) { 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); if (err) return cb(err);
txItems.forEach(function(txItem){ txItems.forEach(function(txItem){

View File

@ -72,6 +72,15 @@ BlockSchema.statics.load = function(id, cb) {
}).exec(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) { BlockSchema.statics.fromHash = function(hash, cb) {
this.findOne({ this.findOne({

View File

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

View File

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

View File

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

View File

@ -24,6 +24,16 @@ var express = require('express'),
var config = require('./config/config'); var config = require('./config/config');
//Bootstrap db connection //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); mongoose.connect(config.db);
//Bootstrap models //Bootstrap models
@ -45,19 +55,29 @@ walk(models_path);
// historic_sync process // historic_sync process
var historicSync = {}; var historicSync = {};
if (!config.disableHistoricSync) { if (!config.disableHistoricSync) {
historicSync = new HistoricSync(); historicSync = new HistoricSync();
historicSync.init({ historicSync.init({
skipDbConnection: true, skipDbConnection: true,
shouldBroadcast: true, shouldBroadcast: true,
progressStep: 2,
networkName: config.network networkName: config.network
}, function() { }, function(err) {
historicSync.smart_import(function(err){ if (err) {
var txt= 'ended.'; var txt = 'ABORTED with error: ' + err.message;
if (err) 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'); require('classtool');
function spec() { function spec() {
var util = require('util'); var util = require('util');
var RpcClient = require('bitcore/RpcClient').class(); var RpcClient = require('bitcore/RpcClient').class();
var networks = require('bitcore/networks'); var networks = require('bitcore/networks');
var async = require('async'); var async = require('async');
var config = require('../config/config'); var config = require('../config/config');
var Block = require('../app/models/Block'); var Block = require('../app/models/Block');
var Sync = require('./Sync').class(); var Sync = require('./Sync').class();
var sockets = require('../app/controllers/socket.js'); 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) { 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); var genesisHashReversed = new Buffer(32);
this.network.genesisBlock.hash.copy(genesisHashReversed); this.network.genesisBlock.hash.copy(genesisHashReversed);
this.genesis = genesisHashReversed.reverse().toString('hex'); this.genesis = genesisHashReversed.reverse().toString('hex');
this.sync = new Sync(opts); this.sync = new Sync(opts);
//available status: new / syncing / finished / aborted //available status: new / syncing / finished / aborted
this.status = 'new'; this.status = 'new';
this.syncInfo = {}; this.error = null;
this.syncPercentage = 0;
this.syncedBlocks = 0;
this.skippedBlocks = 0;
} }
function p() { function p() {
var args = []; var args = [];
Array.prototype.push.apply( args, arguments ); Array.prototype.push.apply(args, arguments);
args.unshift('[historic_sync]'); args.unshift('[historic_sync]');
/*jshint validthis:true */ /*jshint validthis:true */
console.log.apply(this, args); console.log.apply(this, args);
} }
HistoricSync.prototype.init = function(opts,cb) { HistoricSync.prototype.setError = function(err) {
this.rpc = new RpcClient(config.bitcoind); var self = this;
this.opts = opts; self.error = err.toString();
this.sync.init(opts, cb); 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() { 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() { HistoricSync.prototype.showProgress = function() {
var self = this; var self = this;
var i = self.syncInfo; if (self.error) {
var per = parseInt(100 * i.syncedBlocks / i.blocksToSync); p('ERROR:' + self.error);
p(util.format('status: %d/%d [%d%%]', i.syncedBlocks, i.blocksToSync, per)); }
if (self.opts.broadcast) { else {
sockets.broadcastSyncInfo(self.syncInfo); 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) { HistoricSync.prototype.getPrevNextBlock = function(blockHash, blockEnd, opts, cb) {
var self = this; var self = this;
// recursion end. // recursion end.
if (!blockHash ) return cb(); if (!blockHash) return cb();
var existed = 0; var existed = false;
var blockInfo; var blockInfo;
var blockObj; var blockObj;
async.series([ async.series([
// Already got it? // Already got it?
function(c) { function(c) {
Block.findOne({hash:blockHash}, function(err,block){ Block.findOne({
if (err) { p(err); return c(err); } hash: blockHash
if (block) {
existed =1;
blockObj =block;
}
return c();
});
}, },
//show some (inacurate) status function(err, block) {
function(c) { if (err) {
var step = parseInt(self.syncInfo.blocksToSync / 100); p(err);
if (step < 10) step = 10; return c(err);
}
if (self.syncInfo.syncedBlocks % step === 1) { if (block) {
self.showProgress(); existed = true;
blockObj = block;
} }
return c(); 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 return c();
// if (blockObj && blockObj.nextBlockHash) return c(); },
//get Info from RPC
function(c) {
self.rpc.getBlock(blockHash, function(err, ret) { // TODO: if we store prev/next, no need to go to RPC
if (err) return c(err); // if (blockObj && blockObj.nextBlockHash) return c();
self.rpc.getBlock(blockHash, function(err, ret) {
if (err) return c(err);
blockInfo = ret; blockInfo = ret;
return c(); return c();
}); });
}, },
//store it //store it
function(c) { function(c) {
if (existed) return c(); if (existed) return c();
self.sync.storeBlock(blockInfo.result, function(err) { 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); if (err && ! existed) return c(err);
return c(); return c();
}); });
}, },
/* TODO: Should Start to sync backwards? (this is for partial syncs) /* TODO: Should Start to sync backwards? (this is for partial syncs)
function(c) { function(c) {
if (blockInfo.result.prevblockhash != current.blockHash) { if (blockInfo.result.prevblockhash != current.blockHash) {
@ -127,176 +176,159 @@ function spec() {
return c(); return c();
} }
*/ */
], ], function(err) {
function (err){
if (err) { if (err) {
self.err = util.format('ERROR: @%s: %s [count: syncedBlocks: %d]', blockHash, err, self.syncInfo.syncedBlocks); self.err = util.format('ERROR: @%s: %s [count: syncedBlocks: %d]', blockHash, err, self.syncedBlocks);
self.status = 'aborted'; self.status = 'aborted';
p(self.err); self.showProgress();
} p(self.err);
}
else {
self.err = null;
self.status = 'syncing';
}
else { if ( (opts.upToExisting && existed && self.syncedBlocks >= self.blockChainHeight) ||
self.err = null; (blockEnd && blockEnd === blockHash)) {
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) {
self.status = 'finished'; self.status = 'finished';
p('DONE. Found END block: ', blockHash); p('DONE. Found existing block: ', blockHash);
self.showProgress();
return cb(err); return cb(err);
} }
// Continue
if (blockInfo && blockInfo.result) {
// Continue if (existed)
if (blockInfo && blockInfo.result) { self.skippedBlocks++;
if (! existed) self.syncInfo.syncedBlocks++; else
if (opts.prev && blockInfo.result.previousblockhash) { self.syncedBlocks++;
return self.getPrevNextBlock(blockInfo.result.previousblockhash, blockEnd, opts, cb);
}
if (opts.next && blockInfo.result.nextblockhash) // recursion
return self.getPrevNextBlock(blockInfo.result.nextblockhash, blockEnd, opts, cb); if (opts.prev && blockInfo.result.previousblockhash)
} return self.getPrevNextBlock(blockInfo.result.previousblockhash, blockEnd, opts, cb);
return cb(err);
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 self = this;
var retry_secs = 2; var retry_secs = 2;
var bestBlock; var bestBlock;
var blockChainHeight;
async.series([ async.series([
function(cb) { function(cb) {
if (opts.destroy) { if (opts.destroy) {
p('Deleting DB...'); p('Deleting DB...');
return self.sync.destroy(cb); 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(); return cb();
}, });
// We are not using getBestBlockHash, because is not available in all clients },
function(cb) { function(cb) {
if (!opts.reverse) return cb(); if (!opts.reverse) return cb();
self.rpc.getBlockCount(function(err, res) { self.rpc.getBlockHash(self.blockChainHeight, function(err, res) {
if (err) return cb(err); if (err) return cb(err);
blockChainHeight = res.result;
return cb();
});
},
function(cb) {
if (!opts.reverse) return cb();
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); if (err) return cb(err);
bestBlock = res.result; self.syncedBlocks = count || 0;
return cb(); return cb();
}); });
}, }
function(cb) { },
// This is only to inform progress. ], function(err) {
if (!opts.upToExisting) { var start, end;
self.rpc.getInfo(function(err, res) { function sync() {
if (err) return cb(err); if (opts.reverse) {
self.syncInfo.blocksToSync = res.result.blocks; start = bestBlock;
return cb(); end = self.genesis;
}); opts.prev = true;
} }
else { else {
// should be isOrphan = true or null to be more accurate. start = self.genesis;
Block.count({ isOrphan: null}, function(err, count) { end = null;
if (err) return cb(err); opts.next = true;
}
p('Starting from: ', start);
p(' to : ', end);
p(' opts: ', JSON.stringify(opts));
self.syncInfo.blocksToSync = blockChainHeight - count; self.getPrevNextBlock(start, end, opts, function(err) {
if (self.syncInfo.blocksToSync < 1) self.syncInfo.blocksToSync = 1; if (err && err.message.match(/ECONNREFUSED/)) {
return cb(); setTimeout(function() {
}); p('Retrying in %d secs', retry_secs);
} sync();
}, },
], retry_secs * 1000);
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;
} }
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); if (!self.step) {
p(' to : ', end); var step = parseInt( (self.blockChainHeight - self.syncedBlocks) / 1000);
p(' opts: ', JSON.stringify(opts));
self.getPrevNextBlock( start, end, opts , function(err) { if (self.opts.progressStep) {
if (err && err.message.match(/ECONNREFUSED/)){ step = self.opts.progressStep;
setTimeout(function() {
p('Retrying in %d secs', retry_secs);
sync();
}, retry_secs * 1000);
}
else
return next(err);
});
} }
if (err) { if (step < 10) step = 10;
self.syncInfo = util._extend(self.syncInfo, { error: err.message }); self.step = step;
return next(err, 0); }
}
else { if (err) {
sync(); self.setError(err);
} return next(err, 0);
}
else {
sync();
}
}); });
}; };
// upto if we have genesis block? // upto if we have genesis block?
HistoricSync.prototype.smart_import = function(next) { HistoricSync.prototype.smartImport = function(next) {
var self = this; 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 (err) return next(err);
if (!b) { if (!b) {
p('Could not find Genesis block. Running FULL SYNC'); p('Could not find Genesis block. Running FULL SYNC');
} }
@ -305,15 +337,14 @@ function spec() {
} }
var opts = { var opts = {
reverse: 1, reverse: true,
upToExisting: b ? true: false, upToExisting: b ? true: false,
}; };
return self.import_history(opts, next); return self.importHistory(opts, next);
}); });
}; };
return HistoricSync; return HistoricSync;
} }
module.defineClass(spec); module.defineClass(spec);

View File

@ -60,7 +60,6 @@
"mongoose": "~3.8.3", "mongoose": "~3.8.3",
"lodash": "~2.4.1", "lodash": "~2.4.1",
"bower": "~1.2.8", "bower": "~1.2.8",
"bitcore": "*",
"buffertools": "*", "buffertools": "*",
"grunt": "~0.4.2", "grunt": "~0.4.2",
"grunt-cli": "~0.1.11", "grunt-cli": "~0.1.11",
@ -75,7 +74,9 @@
"socket.io": "~0.9.16", "socket.io": "~0.9.16",
"moment": "~2.5.0", "moment": "~2.5.0",
"sinon": "~1.7.3", "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": { "devDependencies": {
"grunt-contrib-watch": "latest", "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%; min-height: 100%;
height: auto; height: auto;
/* Negative indent footer by its height */ /* Negative indent footer by its height */
margin: 0 auto -60px; margin: 0 auto -51px;
/* Pad bottom by footer height */ /* Pad bottom by footer height */
padding: 0 0 60px; padding: 0 0 60px;
} }
@ -38,6 +38,7 @@ h1, h2, h3, h4, h5, h6, .h1, .h2, .h3, .h4, .h5, .h6 {
.m20h {margin: 0 20px;} .m20h {margin: 0 20px;}
.m5v {margin: 5px 0;} .m5v {margin: 5px 0;}
.m20v {margin: 20px 0;} .m20v {margin: 20px 0;}
.m10v {margin: 10px 0;}
.m50v {margin: 50px 0;} .m50v {margin: 50px 0;}
.m10b {margin-bottom: 10px;} .m10b {margin-bottom: 10px;}
@ -87,22 +88,22 @@ h1, h2, h3, h4, h5, h6, .h1, .h2, .h3, .h4, .h5, .h6 {
font-weight: 100; font-weight: 100;
} }
#search::-webkit-input-placeholder { #search::-webkit-input-placeholder {
font-family: Ubuntu, sans-serif; font-family: Ubuntu, sans-serif;
font-weight: 100; font-weight: 100;
font-style: italic; font-style: italic;
font-size: 13px; font-size: 15px;
color: #BCDF7E; color: #BCDF7E;
line-height: 18px; line-height: 20px;
} }
#search::-moz-placeholder { #search::-moz-placeholder {
font-family: Ubuntu, sans-serif; font-family: Ubuntu, sans-serif;
font-weight: 100; font-weight: 100;
font-size: 13px; font-size: 15px;
color: #BCDF7E; color: #BCDF7E;
line-height: 18px; line-height: 20px;
} }
.status { .status {
@ -119,11 +120,10 @@ h1, h2, h3, h4, h5, h6, .h1, .h2, .h3, .h4, .h5, .h6 {
} }
.col-gray { .col-gray {
width: 267px;
background-color: #F4F4F4; background-color: #F4F4F4;
padding: 15px; padding: 15px;
margin-top: 21px; margin-top: 21px;
width: 265px;
height: 87%;
border-radius: 5px; border-radius: 5px;
} }
@ -159,9 +159,8 @@ h1, h2, h3, h4, h5, h6, .h1, .h2, .h3, .h4, .h5, .h6 {
border-radius: :;px; border-radius: :;px;
} }
.block-id h1 { .block-id h3 {
font-weight: bold; font-weight: bold;
font-size: 24px;
color: #FFFFFF; color: #FFFFFF;
line-height: 30px; line-height: 30px;
text-align: center; text-align: center;
@ -221,10 +220,29 @@ h1, h2, h3, h4, h5, h6, .h1, .h2, .h3, .h4, .h5, .h6 {
border: 2px solid #6C0000; border: 2px solid #6C0000;
} }
#status .table {
margin-bottom: 45px;
}
.progress-bar-info {
background-color: #8DC429;
}
/* Set the fixed height of the footer here */ /* Set the fixed height of the footer here */
#footer { #footer {
height: 60px; height: 51px;
background-color: #f5f5f5; 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 { .line-bot {
@ -246,9 +264,14 @@ h1, h2, h3, h4, h5, h6, .h1, .h2, .h3, .h4, .h5, .h6 {
#wrap > .container { #wrap > .container {
padding: 60px 15px 0; padding: 60px 15px 0;
} }
/*.container .text-muted {
margin: 20px 0; .container .text-muted {
}*/ margin: 10px 0;
}
.container .text-muted a {
color: #eee;
}
#footer > .container { #footer > .container {
padding-left: 15px; padding-left: 15px;
@ -265,7 +288,13 @@ h1, h2, h3, h4, h5, h6, .h1, .h2, .h3, .h4, .h5, .h6 {
#search { width: 400px; } #search { width: 400px; }
.no_matching {
background-color: #FFFFFF;
border: 2px solid #64920F;
padding: 10px 20px;
position: absolute;
top: 46px;
}
/*Animations*/ /*Animations*/
.fader.ng-enter { .fader.ng-enter {
@ -334,4 +363,30 @@ h1, h2, h3, h4, h5, h6, .h1, .h2, .h3, .h4, .h5, .h6 {
background-color: #1a1a1a; 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', { when('/block/:blockHash', {
templateUrl: 'views/block.html' templateUrl: 'views/block.html'
}). }).
when('/block-index/:blockHeight', {
controller: 'BlocksController',
template: 'Redirecting...'
}).
when('/tx/:txId', { when('/tx/:txId', {
templateUrl: 'views/transaction.html' templateUrl: 'views/transaction.html'
}). }).

View File

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

View File

@ -1,8 +1,20 @@
'use strict'; '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; $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() { $scope.list = function() {
Blocks.get({ Blocks.get({
blockDate: $routeParams.blockDate blockDate: $routeParams.blockDate
@ -32,4 +44,4 @@ angular.module('insight.blocks').controller('BlocksController', ['$scope', '$roo
}; };
$scope.params = $routeParams; $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'; '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.global = Global;
$scope.menu = [ $scope.menu = [
@ -15,4 +16,4 @@ angular.module('insight.system').controller('HeaderController', ['$scope', 'Glob
]; ];
$scope.isCollapsed = false; $scope.isCollapsed = false;
}]); });

View File

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

View File

@ -1,6 +1,7 @@
'use strict'; '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.global = Global;
$scope.search = function() { $scope.search = function() {
@ -9,26 +10,35 @@ angular.module('insight.search').controller('SearchController', ['$scope', '$rou
$scope.badQuery = false; $scope.badQuery = false;
$scope.q = ''; $scope.q = '';
Block.get({ BlockByHeight.get({
blockHash: q blockHeight: q
}, function() { }, function(hash) {
$location.path('block/' + q); $location.path('/block/' + hash.blockHash);
}, function () { //block not found, search on TX }, function() { // block by height not found
Transaction.get({ Block.get({
txId: q blockHash: q
}, function() { }, function() {
$location.path('tx/' + q); $location.path('block/' + q);
}, function () { //tx not found, search on Address }, function () { //block not found, search on TX
Address.get({ Transaction.get({
addrStr: q txId: q
}, function() { }, function() {
$location.path('address/' + q); $location.path('tx/' + q);
}, function () { //address not found, fail :( }, function () { //tx not found, search on Address
$scope.badQuery = true; Address.get({
$scope.q = q; 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'; '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.global = Global;
$scope.getStatus = function(q) { $scope.getStatus = function(q) {
Status.get({ Status.get({
q: 'get' + q q: 'get' + q
}, function(d) { },
function(d) {
$rootScope.infoError = null; $rootScope.infoError = null;
angular.extend($scope, d); angular.extend($scope, d);
}, function(e) { },
function(e) {
if (e.status === 503) { if (e.status === 503) {
$rootScope.infoError = 'Backend Error. ' + e.data; $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() { $scope.getSync = function() {
Sync.get({}, function(sync) { Sync.get({},
$rootScope.syncError = null; function(sync) {
$scope.sync = sync; on_sync_update(sync);
}, function(e) { },
$rootScope.syncError = 'Could not get sync information' + e; 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'; 'use strict';
angular.module('insight.transactions').controller('transactionsController', angular.module('insight.transactions').controller('transactionsController',
['$scope', function ($scope, $rootScope, $routeParams, $location, Global, Transaction, TransactionsByBlock, TransactionsByAddress, get_socket) {
'$rootScope',
'$routeParams',
'$location',
'Global',
'Transaction',
'TransactionsByBlock',
'TransactionsByAddress',
'get_socket',
function ($scope, $rootScope, $routeParams, $location, Global, Transaction, TransactionsByBlock, TransactionsByAddress, get_socket) {
$scope.global = Global; $scope.global = Global;
$scope.loading = false;
$scope.loadedBy = null;
var pageNum = 0;
var pagesTotal = 1;
$scope.findThis = function() { $scope.findThis = function() {
$scope.findTx($routeParams.txId); $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) { $scope.findTx = function(txid) {
Transaction.get({ Transaction.get({
txId: txid txId: txid
}, function(tx) { }, function(tx) {
$scope.tx = tx; $scope.tx = tx;
$scope.txs.push(tx); $scope.processTX(tx);
$scope.txs.unshift(tx);
}, function(e) { }, function(e) {
if (e.status === 400) { if (e.status === 400) {
$rootScope.flashMessage = 'Invalid Transaction ID: ' + $routeParams.txId; $rootScope.flashMessage = 'Invalid Transaction ID: ' + $routeParams.txId;
@ -37,27 +96,62 @@ angular.module('insight.transactions').controller('transactionsController',
}); });
}; };
$scope.byBlock = function(bId) { $scope.byBlock = function() {
TransactionsByBlock.query({ TransactionsByBlock.get({
block: bId block: $routeParams.blockHash,
}, function(txs) { pageNum: pageNum
$scope.txs = txs; }, function(data) {
$scope.paginate(data);
}); });
}; };
$scope.byAddress = function(aId) { $scope.byAddress = function () {
TransactionsByAddress.query({ TransactionsByAddress.get({
address: aId address: $routeParams.addrStr,
}, function(txs) { pageNum: pageNum
$scope.txs = txs; }, 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); var socket = get_socket($scope);
socket.on('atx', function(tx) { 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.findTx(tx.txid);
}); });
$scope.txs = []; $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'; 'use strict';
angular.module('insight.address').factory('Address', ['$resource', function($resource) { angular.module('insight.address').factory('Address',
function($resource) {
return $resource('/api/addr/:addrStr', { return $resource('/api/addr/:addrStr', {
addrStr: '@addStr' addrStr: '@addStr'
}, { }, {
@ -18,5 +19,5 @@ angular.module('insight.address').factory('Address', ['$resource', function($res
} }
} }
}); });
}]); });

View File

@ -1,6 +1,7 @@
'use strict'; 'use strict';
angular.module('insight.blocks').factory('Block', ['$resource', function($resource) { angular.module('insight.blocks').factory('Block',
function($resource) {
return $resource('/api/block/:blockHash', { return $resource('/api/block/:blockHash', {
blockHash: '@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'; 'use strict';
//Global service for global variables //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(); var socket = io.connect();
return function(scope) { return function(scope) {
var scopedSocket = new ScopedSocket(socket, $rootScope); var scopedSocket = new ScopedSocket(socket, $rootScope);
@ -55,5 +56,5 @@ angular.module('insight.socket').factory('get_socket', ['$rootScope', function($
}); });
return scopedSocket; return scopedSocket;
}; };
}]); });

View File

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

View File

@ -1,6 +1,7 @@
'use strict'; 'use strict';
angular.module('insight.transactions').factory('Transaction', ['$resource', function($resource) { angular.module('insight.transactions').factory('Transaction',
function($resource) {
return $resource('/api/tx/:txId', { return $resource('/api/tx/:txId', {
txId: '@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', { return $resource('/api/txs', {
block: '@block' block: '@block'
}); });
}]); });
angular.module('insight.transactions').factory('TransactionsByAddress', ['$resource', function($resource) { angular.module('insight.transactions').factory('TransactionsByAddress',
function($resource) {
return $resource('/api/txs', { return $resource('/api/txs', {
address: '@address' address: '@address'
}); });
}]); });
angular.module('insight.transactions').factory('Transactions', ['$resource', function($resource) { angular.module('insight.transactions').factory('Transactions',
function($resource) {
return $resource('/api/txs'); return $resource('/api/txs');
}]); });

Binary file not shown.

View File

@ -1,45 +1,44 @@
<section data-ng-controller="AddressController" data-ng-init="findOne()"> <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="row">
<div class="col-lg-9"> <div class="col-md-3">
<table class="table table-striped"> <div class="bs-sidebar hidden-print affix col-gray">
<tbody> <div class="text-center m10v">
<tr> <qrcode size="200" data="{{address.addrStr}}"></qrcode>
<td>Address</td> <h4><span class="glyphicon glyphicon-qrcode"></span> Address</h4>
<td><a href="/#!/address/{{address.addrStr}}">{{address.addrStr}}</a></td> <a class="ellipsis" href="/#!/address/{{address.addrStr}}">{{address.addrStr}}</a>
</tr> </div>
<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>
</tbody> <div class="m50v">
</table> <h4>Summary</h4>
</div> <table class="table">
<div class="col-lg-3"> <tbody>
<qrcode size="200" data="{{address.addrStr}}"></qrcode> <tr>
</div> <td class="small">Total Received</td>
</div> <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)"> <div class="col-md-9">
<h2>Transactions <small>Transactions contained within this block</small></h2> <div data-ng-controller="transactionsController" data-ng-init="load('address')">
<div data-ng-include src="'/views/transaction/list.html'"></div> <h2>Transactions <small>Transactions for this address</small></h2>
</div> <div data-ng-include src="'/views/transaction/list.html'" when-scrolled="loadMore()"></div>
</div>
</div>
</div> <!-- END OF ROW -->
</section> </section>

View File

@ -1,40 +1,39 @@
<section data-ng-controller="BlocksController" data-ng-init="findOne()"> <section data-ng-controller="BlocksController" data-ng-init="findOne()">
<div class="row"> <div class="row">
<div class="col-md-3"> <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="block-id">
<div class="icon-block text-center"> <div class="icon-block text-center">
<span class="glyphicon glyphicon-list-alt"></span> <span class="glyphicon glyphicon-list-alt"></span>
</div> </div>
<h1 data-ng-if="block">Block #{{ block.height }}</h1> <h3 data-ng-if="block">Block #{{ block.height }}</h3>
</div> </div>
<div class="m50v" data-ng-show="!tx.isCoinBase"> <div class="m50v" data-ng-show="!tx.isCoinBase">
<h4>Hashes</h4> <h4>Hashes</h4>
<table class="table"> <table class="table">
<tbody> <tbody>
<tr> <tr>
<td> <small>Hash </small></td> <td class="small"> Hash </td>
<td><a class="address ellipsis" href="/#!/block/{{block.hash}}">{{block.hash}}</a></td> <td><a class="address ellipsis" href="/#!/block/{{block.hash}}">{{block.hash}}</a></td>
</tr> </tr>
<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> <td><a class="address ellipsis" href="/#!/block/{{block.previousblockhash}}">{{block.previousblockhash}}</a></td>
</tr> </tr>
<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> <td><a class="address ellipsis" href="/#!/block/{{block.nextblockhash}}">{{block.nextblockhash}}</a></td>
</tr> </tr>
<tr> <tr>
<td><small> Merkle Root</small></td> <td class="small">Merkle Root</td>
<td> <p class="address ellipsis"> {{block.merkleroot}} </p> </td> <td> <p class="address ellipsis"> {{block.merkleroot}} </p> </td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
</div> </div> <!-- END OF TRANSACTIONS TABLE -->
</div> </div>
</div> <!-- END OF COL-GRAY --> </div> <!-- END OF COL-GRAY -->
<div class="col-md-9"> <div class="col-md-9">
<h3>Summary</h3> <h3>Summary</h3>
@ -69,7 +68,7 @@
<td class="text-right text-muted">{{block.bits}}</td> <td class="text-right text-muted">{{block.bits}}</td>
</tr> </tr>
<tr> <tr>
<td> <strong> Size </strong></td> <td> <strong> Size (bytes) </strong></td>
<td class="text-right text-muted">{{block.size}}</td> <td class="text-right text-muted">{{block.size}}</td>
</tr> </tr>
<tr> <tr>
@ -85,9 +84,9 @@
</div> </div>
</div><!-- END OF ROW --> </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> <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> </div>
</div> </div>

View File

@ -1,27 +1,44 @@
<section data-ng-controller="BlocksController" data-ng-init="list()"> <section data-ng-controller="BlocksController" data-ng-init="list()">
<div class="page-header"> <div class="row">
<h1> <div class="col-md-3">
<span class="glyphicon glyphicon-calendar"></span> <div class="bs-sidebar affix col-gray">
Blocks <div class="block-id">
<small>by date</small> <div class="icon-block text-center">
</h1> <span class="glyphicon glyphicon-list"></span>
<ul class="pagination"> <h3>Blocks <br> mined on:</h3>
<li><a href="#!/blocks-date/{{pagination.prev}}">&laquo; {{pagination.prev}}</a></li> </div>
<li class="disabled"><a href="#">{{pagination.current}}</a></li> </div>
<li><a href="#!/blocks-date/{{pagination.next}}">{{pagination.next}} &raquo;</a></li> <p class="lead text-center m20v">{{pagination.current}}</p>
</ul>
<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> </div>
<table class="table table-striped" ng-show="blocks || blocks.length"> <h2 class="text-center text-muted" data-ng-hide="!blocks || blocks.length">No blocks yet.</h2>
<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>
</section> </section>

View File

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

View File

@ -11,29 +11,34 @@
</div> </div>
<div class="collapse navbar-collapse"> <div class="collapse navbar-collapse">
<ul class="nav navbar-nav"> <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> <a href="#!/{{item.link}}">{{item.title}}</a>
</li> </li>
</ul> </ul>
<div ng-controller="SearchController"> <div data-ng-controller="SearchController">
<form class="navbar-form navbar-left" role="search" ng-submit="search()"> <form class="navbar-form navbar-left" role="search" data-ng-submit="search()">
<div class="form-group" ng-class="{'has-error': badQuery}"> <div class="form-group" data-ng-class="{'has-error': badQuery}">
<input id="search" type="text" class="form-control" ng-model="q" placeholder="Search for block, transaction or address"> <input id="search" type="text" class="form-control" data-ng-model="q" placeholder="Search for block, transaction or address">
</div> </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> </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>
<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> </div>
</div> </div>

View File

@ -1,130 +1,42 @@
<section data-ng-controller="StatusController"> <section data-ng-controller="StatusController">
<div class="page-header"> <div class="page-header">
<h1> <h1>
Status Application Status
</h1> </h1>
</div> </div>
<div class="row"> <div id="status" class="row">
<div class="col-lg-6"> <div class="col-md-9">
<h3>getInfo</h3>
<table class="table table-striped" data-ng-init="getStatus('Info')"> <h4>Sync Status</h4>
<table class="table" data-ng-init="getSync()">
<tbody> <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"> <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>
<tr> <tr>
<td>Sync Progress</td> <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>
<tr> <tr>
<td>blocksToSync</td> <td>Initial Block Chain Height</td>
<td>{{sync.blocksToSync}}</td> <td class="text-right">{{sync.blockChainHeight}}</td>
</tr> </tr>
<tr> <tr>
<td>syncedBlocks</td> <td>Synced Blocks</td>
<td>{{sync.syncedBlocks}}</td> <td class="text-right">{{sync.syncedBlocks}}</td>
</tr>
<tr> <tr>
<td>start</td> <td>Skipped Blocks (previously synced)</td>
<td>{{sync.start}} <td class="text-right">{{sync.skippedBlocks}}</td>
<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>
</tbody> </tbody>
</table> </table>
<h3>getTxOutSetInfo</h3> <h4>Transaction Output Set Information</h4>
<table class="table table-striped" data-ng-init="getStatus('TxOutSetInfo')"> <table class="table" data-ng-init="getStatus('TxOutSetInfo')">
<tbody> <tbody>
<tr data-ng-show="!txoutsetinfo &amp;&amp; !infoError"> <tr data-ng-show="!txoutsetinfo &amp;&amp; !infoError">
<td colspan="2" class="text-center">Loading...</td> <td colspan="2" class="text-center">Loading...</td>
@ -134,59 +46,37 @@
</tr> </tr>
<tr> <tr>
<td>Height</td> <td>Height</td>
<td>{{txoutsetinfo.height}}</td> <td class="text-right"><a href="/#!/block-index/{{txoutsetinfo.height}}">{{txoutsetinfo.height}}</a></td>
</tr> </tr>
<tr> <tr>
<td>bestblock</td> <td>Best Block</td>
<td>{{txoutsetinfo.bestblock}}</td> <td class="text-right"><a href="/#!/block/{{txoutsetinfo.bestblock}}">{{txoutsetinfo.bestblock}}</a></td>
</tr> </tr>
<tr> <tr>
<td>transactions</td> <td>Transactions</td>
<td>{{txoutsetinfo.transactions}}</td> <td class="text-right"> {{txoutsetinfo.transactions}}</td>
</tr> </tr>
<tr> <tr>
<td>txouts</td> <td>Transaction Outputs</td>
<td>{{txoutsetinfo.txouts}}</td> <td class="text-right">{{txoutsetinfo.txouts}}</td>
</tr> </tr>
<tr> <tr>
<td>bytes_serialized</td> <td>Bytes Serialized</td>
<td>{{txoutsetinfo.bytes_serialized}}</td> <td class="text-right">{{txoutsetinfo.bytes_serialized}}</td>
</tr> </tr>
<tr> <tr>
<td>hash_serialized</td> <td>Hash Serialized</td>
<td>{{txoutsetinfo.hash_serialized}}</td> <td class="text-right">{{txoutsetinfo.hash_serialized}}</td>
</tr> </tr>
<tr> <tr>
<td>total_amount</td> <td>Total Amount</td>
<td>{{txoutsetinfo.total_amount}}</td> <td class="text-right">{{txoutsetinfo.total_amount}}</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
</div>
</div> <h4>Last Block</h4>
<table class="table" data-ng-init="getStatus('LastBlockHash')">
<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')">
<tbody> <tbody>
<tr data-ng-show="!lastblockhash &amp;&amp; !infoError"> <tr data-ng-show="!lastblockhash &amp;&amp; !infoError">
<td colspan="2" class="text-center">Loading...</td> <td colspan="2" class="text-center">Loading...</td>
@ -196,11 +86,98 @@
</tr> </tr>
<tr> <tr>
<td>Last block hash</td> <td>Last block hash</td>
<td>{{lastblockhash}}</td> <td class="text-right"><a href="/#!/block/{{lastblockhash}}">{{lastblockhash}}</a></td>
</tr> </tr>
</tbody> </tbody>
</table> </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> </div>
</section> </section>

View File

@ -5,72 +5,13 @@
</h1> </h1>
<div class="block-tx"> <div class="block-tx">
<div class="line-bot"> <div data-ng-include src="'/views/transaction/tx.html'"></div>
<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><!-- END OF BLOCK-TX --> </div><!-- END OF BLOCK-TX -->
<div class="row m50v"> <div class="row m50v">
<div data-ng-class="{'col-md-6':!tx.isCoinBase}"> <div data-ng-class="{'col-md-6':!tx.isCoinBase}">
<h3>Summary</h3> <h3>Summary</h3>
<table class="table"> <table class="table" style="table-layout: fixed">
<tbody> <tbody>
<tr> <tr>
<td><strong> Size </strong></td> <td><strong> Size </strong></td>
@ -81,9 +22,10 @@
<td class="text-muted text-right">{{tx.time * 1000|date:'medium'}}</td> <td class="text-muted text-right">{{tx.time * 1000|date:'medium'}}</td>
</tr> </tr>
<tr> <tr>
<td><strong>Block </strong></td> <td><strong>Block </strong>
<td class="text-muted text-right"><a href="/#!/block/{{tx.blockhash}}">Block</a></td> <td class="text-right">
</tr>
<a href="/#!/block/{{tx.blockhash}}" class=" ellipsis">{{tx.blockhash}}</a>
</tbody> </tbody>
</table> </table>
</div> </div>
@ -101,11 +43,12 @@
</tr> </tr>
<tr> <tr>
<td><strong>Fees</strong></td> <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> </tr>
</tbody> </tbody>
</table> </table>
</div> </div>
</div> </div>
</section> </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.length && !txs[0].txid">There are not transactions</div>
<div class="alert alert-warning" data-ng-show="txs && !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 class="block-tx" data-ng-show="txs && txs[0].txid" data-ng-repeat="tx in txs"> <div data-ng-include src="'/views/transaction/tx.html'"></div>
<div class="line-bot"> </div>
<a href="/#!/tx/{{tx.txid}}">{{tx.txid}}</a> <div class="progress progress-striped active" data-ng-show="!txs.length || loading">
<span class="pull-right">{{tx.time * 1000 | date:'medium'}}</span> <div class="progress-bar" style="width: 100%">
</div> <span>Loading...</span>
<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> </div>
</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) { function(cb) {
if (program.smart) { if (program.smart) {
historicSync.smart_import(cb); historicSync.smartImport(cb);
} }
else { else {
historicSync.import_history({ historicSync.importHistory({
destroy: program.destroy, destroy: program.destroy,
reverse: program.reverse, reverse: program.reverse,
upToExisting: program.uptoexisting, upToExisting: program.uptoexisting,
@ -50,7 +50,7 @@ async.series([
console.log('CRITICAL ERROR: ', err); console.log('CRITICAL ERROR: ', err);
} }
else { else {
console.log('Finished.\n Status:\n', historicSync.syncInfo); console.log('Finished.\n Status:\n', historicSync.info());
} }
}); });