Merge pull request #2 from bitpay/insight-bitcore-api

Insight bitcore api -- This commit convert insight in an API (without front-end)
This commit is contained in:
Gustavo Maximiliano Cortez 2014-02-26 14:00:46 -02:00
commit 492abec636
77 changed files with 95 additions and 2913 deletions

View File

@ -1,3 +0,0 @@
{
"directory": "public/lib"
}

11
.ctags
View File

@ -1,11 +0,0 @@
--extra=+f
--exclude=*jquery*
--exclude=node_modules/a*
--exclude=node_modules/[c-z]*
--exclude=*grunt*
--exclude=*bower*
--exclude=.swp
--exclude=public
--links=yes
--totals=yes

View File

@ -1,21 +0,0 @@
# EditorConfig helps developers define and maintain consistent
# coding styles between different editors and IDEs
# editorconfig.org
root = true
[*]
# Change these settings to your own preference
indent_style = space
indent_size = 2
# We recommend you to keep these unchanged
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false

7
.gitignore vendored
View File

@ -27,7 +27,6 @@ npm-debug.log
.nodemonignore
.DS_Store
public/lib/*
db/txs/*
db/txs
db/testnet/txs/*
@ -37,10 +36,4 @@ db/blocks
db/testnet/blocks/*
db/testnet/blocks
public/js/angularjs-all.js
public/js/main.js
public/js/vendors.js
public/css/main.css
README.html

View File

@ -5,9 +5,6 @@ module.exports = function(grunt) {
//Load NPM tasks
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-contrib-concat');
grunt.loadNpmTasks('grunt-css');
grunt.loadNpmTasks('grunt-mocha-test');
grunt.loadNpmTasks('grunt-nodemon');
grunt.loadNpmTasks('grunt-concurrent');
@ -22,37 +19,12 @@ module.exports = function(grunt) {
files: ['README.md'],
tasks: ['markdown']
},
jade: {
files: ['app/views/**'],
options: {
livereload: true,
},
},
js: {
files: ['Gruntfile.js', 'insight.js', 'app/**/*.js', 'public/js/**'],
files: ['Gruntfile.js', 'insight.js', 'app/**/*.js'],
tasks: ['jshint'],
options: {
livereload: true,
},
},
assets: {
files: ['public/src/**/*.js', 'public/**/*.css'],
tasks: ['compile'],
options: {
livereload: true,
},
},
html: {
files: ['public/views/**'],
options: {
livereload: true,
},
},
css: {
files: ['public/css/**'],
options: {
livereload: true
}
},
// we monitor only app/models/* because we have test for models only now
// test: {
@ -62,64 +34,12 @@ module.exports = function(grunt) {
},
jshint: {
all: {
src: ['Gruntfile.js', 'insight.js', 'app/**/*.js', 'public/src/js/**/*.js','lib/*.js'],
src: ['Gruntfile.js', 'insight.js', 'app/**/*.js', 'lib/*.js', 'config/*.js'],
options: {
jshintrc: true
}
}
},
concat: {
options: {
process: function(src, filepath) {
if (filepath.substr(filepath.length - 2) === 'js') {
return '// Source: ' + filepath + '\n' +
src.replace(/(^|\n)[ \t]*('use strict'|"use strict");?\s*/g, '$1');
} else {
return src;
}
}
},
vendors: {
src: ['public/lib/momentjs/min/moment.min.js', 'public/lib/qrcode-generator/js/qrcode.js', 'public/lib/zeroclipboard/ZeroClipboard.min.js'],
dest: 'public/js/vendors.js'
},
angular: {
src: ['public/lib/angular/angular.min.js', 'public/lib/angular-resource/angular-resource.min.js', 'public/lib/angular-route/angular-route.min.js', 'public/lib/angular-qrcode/qrcode.js', 'public/lib/angular-animate/angular-animate.min.js', 'public/lib/angular-bootstrap/ui-bootstrap.min.js', 'public/lib/angular-bootstrap/ui-bootstrap-tpls.min.js', 'public/lib/angular-ui-utils/ui-utils.min.js', 'public/lib/ngprogress/build/ngProgress.min.js'],
dest: 'public/js/angularjs-all.js'
},
main: {
src: ['public/src/js/app.js', 'public/src/js/controllers/*.js', 'public/src/js/services/*.js', 'public/src/js/directives.js', 'public/src/js/filters.js', 'public/src/js/config.js', 'public/src/js/init.js'],
dest: 'public/js/main.js'
},
css: {
src: ['public/src/css/**/*.css'],
dest: 'public/css/main.css'
}
},
uglify: {
options: {
banner: '/*! <%= pkg.name %> <%= pkg.version %> */\n',
mangle: false
},
vendors: {
src: 'public/js/vendors.js',
dest: 'public/js/vendors.min.js'
},
angular: {
src: 'public/js/angularjs-all.js',
dest: 'public/js/angularjs-all.min.js'
},
main: {
src: 'public/js/main.js',
dest: 'public/js/main.min.js'
}
},
cssmin: {
css: {
src: 'public/css/main.css',
dest: 'public/css/main.min.css'
}
},
mochaTest: {
options: {
reporter: 'spec',
@ -131,7 +51,7 @@ module.exports = function(grunt) {
script: 'insight.js',
options: {
args: [],
ignore: ['public/**/*.html','public/**/*.css', 'public/**/*.js', 'test/**/*','util/**/*', ,'dev-util/**/*'],
ignore: ['test/**/*', 'util/**/*', 'dev-util/**/*'],
// nodeArgs: ['--debug'],
delayTime: 1,
env: {
@ -170,10 +90,7 @@ module.exports = function(grunt) {
grunt.option('force', true);
//Default task(s).
grunt.registerTask('default', ['jshint', 'compile', 'concurrent']);
//Compile task (concat + minify)
grunt.registerTask('compile', ['concat', 'uglify', 'cssmin']);
grunt.registerTask('default', ['jshint', 'concurrent']);
//Test task.
grunt.registerTask('test', ['env:test', 'mochaTest']);

View File

@ -1,8 +1,7 @@
# *insight*
# *insight API*
*insight* is an open-source bitcoin blockchain explorer with complete REST
and websocket APIs. Insight runs in NodeJS, uses AngularJS for the
front-end and LevelDB for storage.
and websocket APIs. Insight runs in NodeJS and use LevelDB for storage.
Check some screenshots and more details at [insight's project homepage](http://insight.bitcore.io).
@ -16,7 +15,7 @@ thru the RPC API, Peer-to-peer protocol and will even read its raw .dat files fo
Configure bitcoind to listen to RPC calls and set `txindex` to true.
The easiest way to do this is by copying `./etc/bitcoind/bitcoin.conf` to your
bitcoin data directory (usually `"~/.bitcoin"` on Linux, `"%appdata%\Bitcoin\"` on Windows,
bitcoin data directory (usually `"~/.bitcoin"` on Linux, `"%appdata%\Bitcoin\"` on Windows,
or `"~/Library/Application Support/Bitcoin"` on Mac OS X).
bitcoind must be running and must have finished downloading the blockchain **before** running *insight*.
@ -36,7 +35,7 @@ bitcoind must be running and must have finished downloading the blockchain **bef
Install dependencies:
$ npm install
Run the main application:
$ node insight.js
@ -48,7 +47,7 @@ bitcoind must be running and must have finished downloading the blockchain **bef
Please note that the app will need to sync its internal database
with the blockchain state, which may take some time. You can check
sync progress from within the web interface.
## Configuration
@ -74,11 +73,11 @@ In case the network is changed (testnet to livenet or vice versa) levelDB databa
The initial synchronization process scans the blockchain from the paired bitcoind server to update addresses and balances. *insight* needs one (and only one) trusted bitcoind node to run. This node must have finished downloading the blockchain befure running *insight*.
While *insight* is synchronizing the website can be accessed (the sync process is embedded in the webserver), but there may be missing data or incorrect balances for addresses. The 'sync' status is shown on the top-right of all pages.
While *insight* is synchronizing the website can be accessed (the sync process is embedded in the webserver), but there may be missing data or incorrect balances for addresses. The 'sync' status is shown on the top-right of all pages.
The blockchain can be read from bitcoind's raw `.dat` files or RPC interface. Reading the information from the `.dat` files is much faster so it's the recommended (and default) alternative. `.dat` files are scanned in the default location for each platform. In case a non-standard location is used, it needs to be defined (see the Configuration section). The synchronization type being used can be seen at the [Status page](http://localhost:3001/status). As of February 2014, using `.dat` files the sync process takes 7 hrs. for livenet and 20 mins. for testnet.
While synchronizing the blockchain, *insight* listens for new blocks and transactions relayed by the bitcoind node. Those are also stored on *insight*'s database. In case *insight* is shutdown for a period of time, restarting it will trigger a partial (historic) synchronization of the blockchain. Depending on the size of that synchronization task, a reverse RPC or forward `.dat` syncing strategy will be used.
While synchronizing the blockchain, *insight* listens for new blocks and transactions relayed by the bitcoind node. Those are also stored on *insight*'s database. In case *insight* is shutdown for a period of time, restarting it will trigger a partial (historic) synchronization of the blockchain. Depending on the size of that synchronization task, a reverse RPC or forward `.dat` syncing strategy will be used.
If bitcoind is shutdown, *insight* needs to be stopped and restarted once bitcoind is restarted.
@ -95,9 +94,9 @@ If bitcoind is shutdown, *insight* needs to be stopped and restarted once bitcoi
### DB storage requirement
To store the blockchain and address related information, *insight* uses LevelDB. Two DBs are created: txs and blocks. By default these are stored on
```<insight root>/db```
To store the blockchain and address related information, *insight* uses LevelDB. Two DBs are created: txs and blocks. By default these are stored on
```<insight root>/db```
this can be changed on config/config.js. As of February 2014, storing the livenet blockchain takes ~30GB of disk space (2GB for the testnet).
## Development
@ -106,10 +105,6 @@ To run insight locally for development with grunt:
```$ NODE_ENV=development grunt```
To compile and minify the web application's assets:
```$ grunt compile```
To run the tests
```$ grunt test```
@ -120,7 +115,9 @@ Contributions and suggestions are welcomed at [insight github repository](https:
## API
A REST API is provided at /api. The entry points are:
By default, insight provides a REST API at /api, but this prefix is configurable from the var `apiPrefix` in the `config.js` file.
The end-points are:
### Block

View File

@ -23,7 +23,7 @@ var getAddr = function(req, res, next) {
exports.show = function(req, res, next) {
var a = getAddr(req, res, next);
if (a)
if (a)
a.update(function(err) {
if (err) {
return common.handleErrors(err, res);
@ -39,7 +39,7 @@ exports.show = function(req, res, next) {
exports.utxo = function(req, res, next) {
var a = getAddr(req, res, next);
if (a)
if (a)
a.getUtxo(function(err, utxo) {
if (err)
return common.handleErrors(err, res);

View File

@ -1,11 +1,17 @@
'use strict';
var _getVersion = function() {
var pjson = require('../../package.json');
return pjson.version;
};
exports.render = function(req, res) {
res.render('index');
var version = _getVersion();
res.send('insight API v' + version);
};
exports.version = function(req, res) {
var pjson = require('../../package.json');
res.json({version: pjson.version});
var version = _getVersion();
res.json({ version: version });
};

View File

@ -22,7 +22,7 @@ module.exports.broadcastTx = function(tx) {
t = {
txid: tx
};
}
}
else {
t = {

View File

@ -47,7 +47,7 @@ var getTransaction = function(txid, cb) {
if (err) console.log(err);
if (!tx || !tx.info) {
console.log('[transactions.js.48]:: TXid %s not found in RPC. CHECK THIS.', txid);
console.log('[transactions.js.48]:: TXid %s not found in RPC. CHECK THIS.', txid);
return ({ txid: txid });
}

View File

@ -1,12 +0,0 @@
extends layouts/default
block main
h1 Oops something went wrong
br
span 404
block content
#error-message-box
#error-stack-trace
pre
code!= error

View File

@ -1,12 +0,0 @@
extends layouts/default
block main
h1 Oops something went wrong
br
span 500
block content
#error-message-box
#error-stack-trace
pre
code!= error

View File

@ -1,7 +0,0 @@
#footer(data-ng-include="'/views/includes/footer.html'", role='navigation')
script(type='text/javascript', src='/socket.io/socket.io.js')
script(type='text/javascript', src='/js/vendors.min.js')
script(type='text/javascript', src='/js/angularjs-all.min.js')
script(type='text/javascript', src='/js/main.min.js')

View File

@ -1,22 +0,0 @@
head
meta(charset='utf-8')
meta(http-equiv='X-UA-Compatible', content='IE=edge,chrome=1')
meta(name='viewport', content='width=device-width,initial-scale=1.0')
meta(name="fragment", content="!")
title(data-ng-bind="$root.title + $root.titleDetail + ' | #{appName}'")= appName
meta(http-equiv='Content-type', content='text/html;charset=UTF-8')
meta(name="keywords", content="bitcoins, transactions, blocks, address, block chain, best block, mining difficulty, hash serialized")
meta(name="description", content="Bitcoin Insight. View detailed information on all bitcoin transactions and block. {{ $root.title + $root.titleDetail }}")
link(rel='shortcut icon', href='/img/icons/favicon.ico', type='image/x-icon')
link(rel='stylesheet', href='//fonts.googleapis.com/css?family=Ubuntu:300,400,500,700,400italic')
link(rel='stylesheet', href='/lib/bootstrap/dist/css/bootstrap.min.css')
link(rel='stylesheet', href='/css/main.min.css')
if (config.keys.segmentio)
script(type='text/javascript').
window.analytics||(window.analytics=[]),window.analytics.methods=['identify','track','trackLink','trackForm','trackClick','trackSubmit','page','pageview','ab','alias','ready','group','on','once','off'],window.analytics.factory=function(t){return function(){var a=Array.prototype.slice.call(arguments);return a.unshift(t),window.analytics.push(a),window.analytics}};for(var i=0;i<window.analytics.methods.length;i++){var method=window.analytics.methods[i];window.analytics[method]=window.analytics.factory(method)}window.analytics.load=function(t){var a=document.createElement('script');a.type='text/javascript',a.async=!0,a.src=('https:'===document.location.protocol?'https://':'http://')+'d2dq2ahtl5zl1z.cloudfront.net/analytics.js/v1/'+t+'/analytics.min.js';var n=document.getElementsByTagName('script')[0];n.parentNode.insertBefore(a,n)},window.analytics.SNIPPET_VERSION='2.0.8',
window.analytics.load('#{config.keys.segmentio}');
window.analytics.page();

View File

@ -1 +0,0 @@
.navbar.navbar-default.navbar-fixed-top(data-ng-include="'/views/includes/header.html'", role='navigation')

View File

@ -1,4 +0,0 @@
extends layouts/default
block content
section.container(data-ng-view)

View File

@ -1,8 +0,0 @@
doctype
html(lang='en', data-ng-app='insight' data-ng-csp)
include ../includes/head
body
#wrap
include ../includes/navbar
block content
include ../includes/foot

View File

@ -1,18 +0,0 @@
{
"name": "Insight",
"version": "0.0.1",
"dependencies": {
"angular": "latest",
"angular-resource": "latest",
"angular-mocks": "latest",
"angular-route": "latest",
"bootstrap": "3.0.3",
"angular-bootstrap": "0.9.0",
"angular-ui-utils": "0.1.0",
"angular-qrcode": "latest",
"angular-animate": "latest",
"momentjs": "~2.5.0",
"zeroclipboard": "~1.3.0-beta.1",
"ngprogress": "~1.0.4"
}
}

View File

@ -17,9 +17,9 @@ if (process.env.INSIGHT_NETWORK === 'livenet') {
}
else {
env = 'testnet';
db = './db/testnet',
port = '3001',
b_port = '18332',
db = './db/testnet';
port = '3001';
b_port = '18332';
p2p_port = '18333';
}
@ -35,6 +35,8 @@ switch(process.env.NODE_ENV) {
break;
}
var network = process.env.INSIGHT_NETWORK || 'testnet';
var dataDir = process.env.BITCOIND_DATADIR;
var isWin = /^win/.test(process.platform);
var isMac = /^darwin/.test(process.platform);
@ -44,11 +46,12 @@ if (!dataDir) {
if (isMac) dataDir = process.env.HOME + '/Library/Application Support/Bitcoin/';
if (isLinux) dataDir = process.env.HOME + '/.bitcoin/';
}
dataDir += ((process.env.INSIGHT_NETWORK || 'testnet')==='testnet'?'testnet3':'');
dataDir += network === 'testnet' ? 'testnet3' : '';
module.exports = {
root: rootPath,
appName: 'Insight ' + env,
apiPrefix: '/api',
port: port,
leveldb: db,
bitcoind: {
@ -62,14 +65,14 @@ module.exports = {
// DO NOT CHANGE THIS!
disableAgent: true
},
network: process.env.INSIGHT_NETWORK || 'testnet',
network: network,
disableP2pSync: false,
disableHistoricSync: false,
poolMatchFile: './etc/minersPoolStrings.json',
// Time to refresh the currency rate. In minutes
currencyRefresh: 10
, keys: {
currencyRefresh: 10,
keys: {
segmentio: process.env.INSIGHT_SEGMENTIO_KEY
}
};

View File

@ -4,46 +4,45 @@
* Module dependencies.
*/
var express = require('express'),
helpers = require('view-helpers'),
config = require('./config');
config = require('./config'),
path = require('path');
module.exports = function(app, historicSync, peerSync) {
//custom middleware
function setHistoric(req, res, next) {
//custom middleware
var setHistoric = function(req, res, next) {
req.historicSync = historicSync;
next();
}
function setPeer(req, res, next) {
};
var setPeer = function(req, res, next) {
req.peerSync = peerSync;
next();
}
};
app.set('showStackError', true);
//Set views path, template engine and default layout
app.set('views', config.root + '/app/views');
app.set('view engine', 'jade');
// Compress JSON outputs
app.set('json spaces', 0);
//Enable jsonp
app.enable('jsonp callback');
app.use('/api/sync', setHistoric);
app.use('/api/peer', setPeer);
app.use(config.apiPrefix + '/sync', setHistoric);
app.use(config.apiPrefix + '/peer', setPeer);
app.use(express.logger('dev'));
app.use(express.json());
app.use(express.urlencoded());
app.use(express.methodOverride());
app.use(express.compress());
// IMPORTANT: for html5mode, this line must to be before app.router
app.use(express.static(config.root + '/public'));
if (process.env.INSIGHT_PUBLIC_PATH) {
var staticPath = path.normalize(config.rootPath + '/../../' + process.env.INSIGHT_PUBLIC_PATH);
//dynamic helpers
app.use(helpers(config.appName));
//IMPORTANT: for html5mode, this line must to be before app.router
app.use(express.static(staticPath));
}
// manual helpers
app.use(function(req, res, next) {
@ -63,14 +62,16 @@ module.exports = function(app, historicSync, peerSync) {
console.error(err.stack);
//Error page
res.status(500).render('500', {
res.status(500).jsonp({
status: 500,
error: err.stack
});
});
//Assume 404 since no middleware responded
app.use(function(req, res) {
res.status(404).render('404', {
res.status(404).jsonp({
status: 404,
url: req.originalUrl,
error: 'Not found'
});

View File

@ -1,42 +1,49 @@
'use strict';
/**
* Module dependencies.
*/
var config = require('./config');
module.exports = function(app) {
var apiPrefix = config.apiPrefix;
//Block routes
var blocks = require('../app/controllers/blocks');
app.get('/api/blocks', blocks.list);
app.get(apiPrefix + '/blocks', blocks.list);
app.get('/api/block/:blockHash', blocks.show);
app.get(apiPrefix + '/block/:blockHash', blocks.show);
app.param('blockHash', blocks.block);
app.get('/api/block-index/:height', blocks.blockindex);
app.get(apiPrefix + '/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);
app.get(apiPrefix + '/tx/:txid', transactions.show);
app.param('txid', transactions.transaction);
app.get('/api/txs', transactions.list);
app.get(apiPrefix + '/txs', transactions.list);
// Address routes
var addresses = require('../app/controllers/addresses');
app.get('/api/addr/:addr', addresses.show);
app.get('/api/addr/:addr/utxo', addresses.utxo);
app.get(apiPrefix + '/addr/:addr', addresses.show);
app.get(apiPrefix + '/addr/:addr/utxo', addresses.utxo);
// Status route
var st = require('../app/controllers/status');
app.get('/api/status', st.show);
app.get(apiPrefix + '/status', st.show);
app.get('/api/sync', st.sync);
app.get('/api/peer', st.peer);
app.get(apiPrefix + '/sync', st.sync);
app.get(apiPrefix + '/peer', st.peer);
// Currency
var currency = require('../app/controllers/currency');
app.get('/api/currency', currency.index);
app.get(apiPrefix + '/currency', currency.index);
//Home route
var index = require('../app/controllers/index');
app.get('/api/version', index.version);
app.get('*', index.render);
app.get(apiPrefix + '/version', index.version);
app.get('/', index.render);
};

View File

@ -85,7 +85,7 @@ require('./app/controllers/socket.js').init(expressApp, ios);
//Start the app by listening on <port>
server.listen(config.port, function(){
console.log('Express server listening on port %d in %s mode', server.address().port, process.env.NODE_ENV);
console.log('insight server listening on port %d in %s mode', server.address().port, process.env.NODE_ENV);
});
//expose app

View File

@ -25,7 +25,6 @@ function spec(b) {
var Rpc = b.rpc || require('./Rpc').class();
var PoolMatch = b.poolMatch || require('./PoolMatch').class(config);
var buffertools = require('buffertools');
var TransactionDb = require('./TransactionDb.js').class();
var BlockDb = function() {
@ -176,9 +175,9 @@ function spec(b) {
if (a.isCoinBase) {
var coinbaseHexBuffer = new Buffer(a.vin[0].coinbase, 'hex');
var a = self.poolMatch.match(coinbaseHexBuffer);
var aa = self.poolMatch.match(coinbaseHexBuffer);
return cb(a);
return cb(aa);
}
});
};

View File

@ -29,7 +29,7 @@ function spec(b) {
if (buffertools.indexOf(buffer, k) >= 0) {
return self.strings[k];
}
};
}
};
return PoolMatch;

View File

@ -19,21 +19,21 @@ function spec(b) {
var b = new Buffer(info.hex,'hex');
// remove fields we dont need, to speed and adapt the information
delete info['hex'];
delete info.hex;
// Inputs => add index + coinBase flag
var n =0;
info.vin.forEach(function(i) {
i.n = n++;
if (i.coinbase) info.isCoinBase = true;
if (i.scriptSig) delete i.scriptSig['hex'];
if (i.scriptSig) delete i.scriptSig.hex;
});
// Outputs => add total
var valueOutSat = 0;
info.vout.forEach( function(o) {
valueOutSat += o.value * bitcoreUtil.COIN;
delete o.scriptPubKey['hex'];
delete o.scriptPubKey.hex;
});
info.valueOut = parseInt(valueOutSat) / bitcoreUtil.COIN;
info.size = b.length;

View File

@ -1,12 +1,12 @@
{
"name": "insight-bitcore",
"name": "insight-bitcore-api",
"description": "An open-source bitcoin blockchain API. The Insight API provides you with a convenient, powerful and simple way to query and broadcast data on the bitcoin network and build your own services with it.",
"version": "0.1.2",
"author": {
"name": "Ryan X Charles",
"email": "ryan@bitpay.com"
},
"repository": "git://github.com/bitpay/insight.git",
"repository": "git://github.com/bitpay/insight-api.git",
"contributors": [
{
"name": "Matias Alejo Garcia",
@ -30,9 +30,9 @@
}
],
"bugs": {
"url": "https://github.com/bitpay/insight/issues"
"url": "https://github.com/bitpay/insight-api/issues"
},
"homepage": "https://github.com/bitpay/insight",
"homepage": "https://github.com/bitpay/insight-api",
"license": "MIT",
"keywords": [
"insight",
@ -41,7 +41,9 @@
"riddle",
"mystification",
"puzzle",
"conundrum"
"conundrum",
"api",
"bitcore"
],
"engines": {
"node": "*"
@ -59,11 +61,8 @@
"commander": "*",
"bignum": "*",
"express": "~3.4.7",
"jade": "~1.0.2",
"lodash": "~2.4.1",
"buffertools": "*",
"should": "~2.1.1",
"view-helpers": "latest",
"socket.io": "~0.9.16",
"moment": "~2.5.0",
"sinon": "~1.7.3",
@ -73,7 +72,6 @@
"xmlhttprequest": "~1.6.0"
},
"devDependencies": {
"bower": "~1.2.8",
"grunt": "~0.4.2",
"grunt-cli": "~0.1.11",
"grunt-env": "~0.4.1",
@ -82,10 +80,7 @@
"grunt-concurrent": "~0.4.2",
"grunt-nodemon": "~0.2.0",
"grunt-mocha-test": "~0.8.1",
"grunt-contrib-concat": "~0.3.0",
"grunt-contrib-uglify": "~0.3.2",
"should": "latest",
"grunt-css": "~0.5.4",
"should": "2.1.1",
"grunt-markdown": "~0.5.0"
}
}

File diff suppressed because one or more lines are too long

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 148 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@ -1,711 +0,0 @@
/* Sticky footer styles
-------------------------------------------------- */
@charset "UTF-8";
html,
body {
color: #373D42;
font-family: 'Ubuntu', sans-serif;
height: 100%;
/* The html and body elements cannot have any padding or margin. */
}
h1, h2, h3, h4, h5, h6, .h1, .h2, .h3, .h4, .h5, .h6 {
color: #373D42;
font-family: 'Ubuntu', sans-serif;
}
[ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak],
.ng-cloak, .x-ng-cloak,
.ng-hide {
display: none !important;
}
/* Styling for the ngProgress itself */
#ngProgress {
background-color: #6C9032 !important;
box-shadow: none !important;
color: #373D42 !important;
height: 3px !important;
margin: 0;
opacity: 0;
padding: 0;
z-index: 99998;
/* Add CSS3 styles for transition smoothing */
-webkit-transition: all 0.5s ease-in-out;
-moz-transition: all 0.5s ease-in-out;
-o-transition: all 0.5s ease-in-out;
transition: all 0.5s ease-in-out;
}
/* Styling for the ngProgress-container */
#ngProgress-container {
position: fixed;
margin: 0;
padding: 0;
top: 63px;
left: 0;
right: 0;
z-index: 99999;
}
/* Wrapper for page content to push down footer */
#wrap {
min-height: 100%;
height: auto;
/* Negative indent footer by its height */
margin: 0 auto -51px;
/* Pad bottom by footer height */
padding: 0 0 75px;
}
.m10h { margin: 0 10px; }
.m20h { margin: 0 20px; }
.m5v { margin: 5px 0; }
.m20v { margin: 20px 0; }
.m10v { margin: 10px 0; }
.m50v { margin: 50px 0; }
.m10b { margin-bottom: 10px; }
.m10l { margin-left: 10px; }
.vm { vertical-align: middle; }
.bgwhite {
background-color: white;
}
.btn-group .btn+.btn, .btn-group .btn+.btn-group, .btn-group .btn-group+.btn, .btn-group .btn-group+.btn-group {
margin-left: 0;
}
.table-hover>tbody>tr:hover>td, .table-hover>tbody>tr:hover>th {
background-color: #F0F7E2;
}
.navbar-default .navbar-toggle {
border-color: #fff;
margin-top: 15px;
}
.navbar-default .navbar-toggle .icon-bar { background-color: #fff; }
.navbar-default .navbar-toggle:hover, .navbar-default .navbar-toggle:focus {background-color: #373D42;}
.navbar-default {
background-color: #8DC429;
margin: 0;
border: 0;
}
.navbar-default .navbar-nav>li>a {
color: #F4FBE8;
font-family: 'Ubuntu', sans-serif;
padding-left: 25px;
padding-right: 25px;
}
.navbar-default .navbar-nav>.active>a, .navbar-default .navbar-nav>.active>a:focus {
background-color: #6C9032;
color: #fff;
}
.navbar-default .navbar-nav>li>a:hover, .navbar-default .navbar-nav>.active>a:hover {
background-color: #fff;
}
.navbar-form .form-group {
display: block;
}
.navbar-form {
width: 35%;
margin-top: 15px;
}
@media (max-width: 991px) {
.status {
margin: 0;
}
.navbar-form {
width: 15%;
}
}
@media (max-width: 767px) {
.navbar-form {
width: auto;
}
}
.nav-tabs.nav-justified>li>a:hover {
cursor: pointer;
}
.insight {
font-family: 'Ubuntu', sans-serif;
font-size: 34px;
font-style: italic;
font-weight: 700;
overflow: hidden;
}
.navbar-default .navbar-brand:hover, .navbar-default .navbar-brand:focus {
color: #fffffe;
}
.navbar-default .navbar-brand {
color: #FFFFFF;
padding: 22px 15px;
}
.navbar-form .form-control {
background-color: #7CAD23;
border-radius: 3px;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
border: 0;
-webkit-box-shadow: 1px 1px 0px 0px rgba(255,255,255,0.41), inset 1px 1px 3px 0px rgba(0,0,0,0.10);
-moz-box-shadow: 1px 1px 0px 0px rgba(255,255,255,0.41), inset 1px 1px 3px 0px rgba(0,0,0,0.10);
box-shadow: 1px 1px 0px 0px rgba(255,255,255,0.41), inset 1px 1px 3px 0px rgba(0,0,0,0.10);
}
.navbar-nav>li>a {
padding-top: 22px;
padding-bottom: 22px;
}
#search {
color: #fff;
font-family: 'Ubuntu', sans-serif;
}
#search.loading {
background-image: url('/img/loading.gif');
background-position: 5px center;
background-repeat: no-repeat;
padding-left: 25px;
}
#search::-webkit-input-placeholder {
color: #BCDF7E;
font-family: 'Ubuntu', sans-serif;
font-size: 14px;
font-style: italic;
font-weight: 100;
}
#search::-moz-placeholder {
color: #BCDF7E;
font-family: 'Ubuntu', sans-serif;
font-size: 14px;
font-weight: 100;
}
.status {
-moz-border-radius: 3px;
-webkit-border-radius: 3px;
background-color: #597338;
border-radius: 3px;
margin: 15px 0;
padding: 8px 10px;
font-size: 12px;
color: #eee;
text-align: center;
margin-right: 10px;
}
.status .tooltip {
margin: 0;
}
.col-gray {
-moz-border-radius: 5px;
-webkit-border-radius: 5px;
background-color: #F4F4F4;
border-radius: 5px;
padding: 14px;
border: 1px solid #eee;
}
.col-gray-responsive {
width: auto;
}
.col-gray-fixed {
margin-top: 15px;
position: fixed;
width: 250px;
border: 1px solid #eee;
z-index: 1;
}
@media (max-width: 768px) {
.col-gray-fixed {
width:100%;
}
}
@media (max-width: 995px) {
.col-gray-fixed {
position:static;
width: 100%;
}
}
@media (min-width: 1200px) {
.col-gray-fixed {
width: 280px;
}
}
.ellipsis {
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.line20 {
border: 1px solid #D4D4D4;
margin-bottom: 15px;
}
.line10 {
border: 1px solid #EAEAEA;
margin: 10px 0;
}
.block-id {
background-color: #373D42;
border: 3px solid #FFFFFF;
margin: 0 auto;
width: 165px;
color: #fff;
text-align: center;
}
.block-id span {
font-size: 40px;
margin: 30px 0;
}
.block-id h2 {
color: #FFFFFF;
font-weight: bold;
line-height: 30px;
font-size: 24px;
margin-top: 0;
margin-bottom: 10px;
}
.icon-block {
color: #FFFFFF;
font-size: 35px;
margin-top: 10px;
}
.icon-block h3 {
color: #fff;
}
.block-tx {
-moz-border-radius: 2px;
-webkit-border-radius: 2px;
background-color: #F4F4F4;
border-radius: 2px;
margin: 20px 0 10px;
overflow: hidden;
padding: 15px;
border: 1px solid #eee;
}
.btn {
border-radius: 2px;
}
.btn-primary {
background-color: #8DC429;
border: 2px solid #76AF0F;
}
.btn-primary:hover, .btn-primary:focus, .btn-primary:active,
.btn-primary.active, .open .dropdown-toggle.btn-primary,
.btn-success:hover, .btn-success:focus, .btn-success:active,
.btn-success.active, .open .dropdown-toggle.btn-success,
.btn-danger:hover, .btn-danger:focus, .btn-danger:active,
.btn-danger.active, .open .dropdown-toggle.btn-danger {
background-color: #fff;
border: 2px solid #ccc;
color: #373D42;
}
.btn-default:hover, .btn-default:focus, .btn-default:active, .btn-default.active, .open .dropdown-toggle.btn-default {
background-color: #fff;
}
.btn-default {
background-color: #E7E7E7;
}
.btn-success {
background-color: #2FA4D7;
border: 2px solid #237FA7;
}
.btn-danger {
background-color: #AC0015;
border: 2px solid #6C0000;
}
.txvalues {
display: inline-block;
padding: .7em 2em;
font-size: 13px;
text-transform: uppercase;
font-weight:100;
color: #fff;
text-align: center;
white-space: nowrap;
vertical-align: baseline;
border-radius: .25em;
}
@media (max-width: 768px) {
.txvalues {
display: block;
margin: 5px;
}
}
.txvalues-primary {
background-color: #8DC429;
}
.txvalues-default {
background-color: #EBEBEB;
color: #333;
}
.txvalues-success {
background-color: #2FA4D7;
}
.txvalues-danger {
background-color: #AC0015;
}
.txvalues-normal {
background-color: transparent;
text-transform: none;
color: #333;
font-size: 14px;
font-weight: normal;
}
.progress-bar-info { background-color: #8DC429; }
/* Set the fixed height of the footer here */
#footer {
background-color: #373D42;
color: #fff;
height: 51px;
overflow: hidden;
}
#footer a.insight {
font-size: 20px;
text-decoration: none;
color: #fff;
}
#footer a.insight:hover {
color: #fffffe;
}
#footer a.insight small { font-size: 11px; }
.line-footer { border-top: 2px dashed #ccc; }
.line-bot {
border-bottom: 2px solid #EAEAEA;
padding: 0 0 10px 0;
}
.line-mid { padding: 15px 0;}
.line-top {
border-top: 1px solid #EAEAEA;
padding: 15px 0 0 0;
}
/* Custom page CSS
-------------------------------------------------- */
/* Not required for template or sticky footer method. */
#wrap > .container { padding: 70px 15px 0; }
#footer > .container { padding: auto 15px; }
.code { font-size: 80%; }
.address { font-size: 11px; }
.no_matching {
-moz-border-radius-bottomleft: 2px;
-moz-border-radius-bottomright: 2px;
-webkit-border-bottom-left-radius: 2px;
-webkit-border-bottom-right-radius: 2px;
background-color: #FFFFFF;
border-bottom-left-radius: 2px;
border-bottom-right-radius: 2px;
border-top: none;
border: 1px solid #64920F;
padding: 10px 20px;
position: absolute;
text-align: center;
top: 45px;
width: 300px;
}
/*Animations*/
.fader.ng-enter {
opacity: 0;
-webkit-transition: opacity 1s;
-moz-transition: opacity 1s;
-o-transition: opacity 1s;
transition: opacity 1s;
}
.fader.ng-enter-active { opacity: 1; }
.tx-bg {
background-color: #F4F4F4;
left: 0;
min-height: 340px;
position: absolute;
top: 0;
width: 100%;
z-index: -9999;
}
.badge {
-moz-border-radius: 9px;
-webkit-border-radius: 9px;
background-color: #999999;
border-radius: 9px;
color: #ffffff;
font-size: 12.025px;
font-weight: bold;
padding: 1px 9px 2px;
white-space: nowrap;
}
.badge:hover {
color: #ffffff;
text-decoration: none;
cursor: pointer;
}
.badge-error { background-color: #b94a48; }
.badge-error:hover { background-color: #953b39; }
.badge-warning { background-color: #f89406; }
.badge-warning:hover { background-color: #c67605; }
.badge-success { background-color: #468847; }
.badge-success:hover { background-color: #356635; }
.badge-info { background-color: #3a87ad; }
.badge-info:hover { background-color: #2d6987; }
.badge-inverse { background-color: #333333; }
.badge-inverse:hover { background-color: #1a1a1a; }
.status .t {
color: white;
}
.status .text-danger { background: red; }
.status .text-warning {
background: yellow;
color: black;
}
.btn-copy {
color: #9b9b9b;
display: inline-block;
height: 16px;
width: 16px;
outline: none;
vertical-align: sub;
}
.btn-expand {
color: #9b9b9b;
vertical-align: middle;
}
.btn-copy:hover, .btn-expand:hover {
color: #000;
text-decoration: none;
}
.btn-copy {
background: transparent url('/img/icons/copy.png') center center no-repeat;
}
.btn-copy .tooltip {
display: block;
margin-left: 20px;
margin-top: -2px;
opacity: 0;
}
.btn-copy.zeroclipboard-is-hover { color: #2a6496; }
.btn-copy.zeroclipboard-is-active .tooltip { opacity: 1; }
@media (max-width: 991px) {
.btn-copy {
display: none;
}
}
.txid {
line-height: 26px;
}
.tx-id {
background-color: #373D42;
border: 3px solid #FFFFFF;
margin: 0 auto;
width: 165px;
color: #FFFFFF;
text-align: center;
}
.tx-id span {
font-size: 40px;
margin: 30px 0;
}
.page-header { margin-top: 0; }
/* Index */
#home .btn-more {
border-top: 1px solid #ddd;
margin: 30px auto 0;
text-align: center;
width: 90%;
}
#home .btn-more .btn-default {
margin-top: -23px;
}
#powered .powered-text {
border-top: 1px solid #ddd;
margin: 30px auto 0;
text-align: center;
width: 90%;
}
#powered .powered-text small {
background-color: #f4f4f4;
padding: 4px;
position: relative;
top: -12px;
}
#powered a {
background-repeat: no-repeat;
background-position: center center;
display: inline-block;
float: left;
height: 45px;
}
#powered a.bitcore {
background-image: url('http://bitcore.io/images/logo.svg');
background-size: 80px;
width: 30%;
}
#powered a.nodejs {
background-image: url('/img/nodejs.png');
background-size: 80px;
width: 30%;
}
#powered a.angularjs {
background-image: url('/img/angularjs.png');
background-size: 50px;
width: 20%;
}
#powered a.leveldb {
background-image: url('/img/leveldb.png');
background-size: 50px;
width: 20%;
}
@keyframes rotateThis {
from { transform: scale( 1 ) rotate( 0deg ); }
to { transform: scale( 1 ) rotate( 360deg ); }
}
@-webkit-keyframes rotateThis {
from { -webkit-transform: scale( 1 ) rotate( 0deg ); }
to { -webkit-transform: scale( 1 ) rotate( 360deg ); }
}
.icon-rotate {
animation-name: rotateThis;
animation-duration: 2s;
animation-iteration-count: infinite;
animation-timing-function: linear;
-webkit-animation-name: rotateThis;
-webkit-animation-duration: 2s;
-webkit-animation-iteration-count: infinite;
-webkit-animation-timing-function: linear;
}
.transaction-vin-vout {
line-height: 1.8em;
}
.v_highlight {
background-color: #8DC429;
overflow: hidden;
color: #fff;
}
a.v_highlight_more {
background-color: #8DC429;
color: #fff;
}
.secondary_navbar {
width: 100%;
background: #fff;
position: fixed;
top: 64px;
left: 0;
text-align: center;
z-index: 1000;
margin: 0 auto;
-moz-box-shadow: 0px 1px 4px 0px rgba(0,0,0,0.20);
-webkit-box-shadow: 0px 1px 4px 0px rgba(0,0,0,0.20);
box-shadow: 0px 1px 4px 0px rgba(0,0,0,0.20);
}
.secondary_navbar .container {
margin: 0 auto;
padding: 1.8em 0;
}
.secondary_navbar h3, .secondary_navbar p, .secondary_navbar .lead {
margin: 0;
}
.secondary_navbar p {
line-height: 1.9em;
}
.hide_snavbar {
border-bottom-right-radius: 0.3em;
border-bottom-left-radius: 0.3em;
position: absolute;
right: 25px;
padding: 5px 10px;
background: #fff;
-moz-box-shadow: 0px 2px 3px 0px rgba(0,0,0,0.20);
-webkit-box-shadow: 0px 2px 3px 0px rgba(0,0,0,0.20);
box-shadow: 0px 2px 3px 0px rgba(0,0,0,0.20);
}

View File

@ -1,30 +0,0 @@
'use strict';
angular.module('insight',[
'ngAnimate',
'ngResource',
'ngRoute',
'ngProgress',
'ui.bootstrap',
'ui.route',
'monospaced.qrcode',
'insight.system',
'insight.socket',
'insight.blocks',
'insight.transactions',
'insight.address',
'insight.search',
'insight.status',
'insight.connection',
'insight.currency'
]);
angular.module('insight.system', []);
angular.module('insight.socket', []);
angular.module('insight.blocks', []);
angular.module('insight.transactions', []);
angular.module('insight.address', []);
angular.module('insight.search', []);
angular.module('insight.status', []);
angular.module('insight.connection', []);
angular.module('insight.currency', []);

View File

@ -1,64 +0,0 @@
'use strict';
//Setting up route
angular.module('insight').config(function($routeProvider) {
$routeProvider.
when('/block/:blockHash', {
templateUrl: '/views/block.html',
title: 'Bitcoin Block '
}).
when('/block-index/:blockHeight', {
controller: 'BlocksController',
templateUrl: '/views/redirect.html'
}).
when('/tx/:txId/:v_type?/:v_index?', {
templateUrl: '/views/transaction.html',
title: 'Bitcoin Transaction '
}).
when('/', {
templateUrl: '/views/index.html',
title: 'Home'
}).
when('/blocks', {
templateUrl: '/views/block_list.html',
title: 'Bitcoin Blocks solved Today'
}).
when('/blocks-date/:blockDate', {
templateUrl: '/views/block_list.html',
title: 'Bitcoin Blocks solved '
}).
when('/address/:addrStr', {
templateUrl: '/views/address.html',
title: 'Bitcoin Address '
}).
when('/status', {
templateUrl: '/views/status.html',
title: 'Status'
})
.otherwise({
templateUrl: '/views/404.html',
title: 'Error'
});
});
//Setting HTML5 Location Mode
angular.module('insight')
.config(function($locationProvider) {
$locationProvider.html5Mode(true);
$locationProvider.hashPrefix('!');
})
.run(function($rootScope, $route, ngProgress) {
$rootScope.$on('$routeChangeStart', function() {
ngProgress.start();
});
$rootScope.$on('$routeChangeSuccess', function() {
ngProgress.complete();
//Change page title, based on Route information
$rootScope.titleDetail = '';
$rootScope.title = $route.current.title;
$rootScope.isCollapsed = true;
$rootScope.currentAddr = null;
});
});

View File

@ -1,41 +0,0 @@
'use strict';
angular.module('insight.address').controller('AddressController',
function($scope, $rootScope, $routeParams, $location, Global, Address, getSocket) {
$scope.global = Global;
$scope.findOne = function() {
$rootScope.currentAddr = $routeParams.addrStr;
Address.get({
addrStr: $routeParams.addrStr
},
function(address) {
$rootScope.titleDetail = address.addrStr.substring(0, 7) + '...';
$rootScope.flashMessage = null;
$scope.address = address;
},
function(e) {
if (e.status === 400) {
$rootScope.flashMessage = 'Invalid Address: ' + $routeParams.addrStr;
} else if (e.status === 503) {
$rootScope.flashMessage = 'Backend Error. ' + e.data;
} else {
$rootScope.flashMessage = 'Address Not Found';
}
$location.path('/');
});
};
var socket = getSocket($scope);
socket.on('connect', function() {
socket.emit('subscribe', $routeParams.addrStr);
socket.on($routeParams.addrStr, function(tx) {
console.log('AddressTx event received ' + tx);
$rootScope.$broadcast('tx', tx);
});
});
$scope.params = $routeParams;
});

View File

@ -1,90 +0,0 @@
'use strict';
angular.module('insight.blocks').controller('BlocksController',
function($scope, $rootScope, $routeParams, $location, Global, Block, Blocks, BlockByHeight) {
$scope.global = Global;
$scope.loading = false;
if ($routeParams.blockHeight) {
BlockByHeight.get({
blockHeight: $routeParams.blockHeight
}, function(hash) {
$location.path('/block/' + hash.blockHash);
}, function() {
$rootScope.flashMessage = 'Bad Request';
$location.path('/');
});
}
//Datepicker
var _formatTimestamp = function (date) {
var yyyy = date.getUTCFullYear().toString();
var mm = (date.getUTCMonth() + 1).toString(); // getMonth() is zero-based
var dd = date.getUTCDate().toString();
return yyyy + '-' + (mm[1] ? mm : '0' + mm[0]) + '-' + (dd[1] ? dd : '0' + dd[0]); //padding
};
$scope.$watch('dt', function(newValue, oldValue) {
if (newValue !== oldValue) {
$location.path('/blocks-date/' + _formatTimestamp(newValue));
}
});
$scope.openCalendar = function($event) {
$event.preventDefault();
$event.stopPropagation();
$scope.opened = true;
};
$scope.humanSince = function(time) {
var m = moment.unix(time).startOf('day');
var b = moment().startOf('day');
return m.max().from(b);
};
$scope.list = function() {
$scope.loading = true;
if ($routeParams.blockDate) {
$rootScope.titleDetail = 'on ' + $routeParams.blockDate;
}
Blocks.get({
blockDate: $routeParams.blockDate
}, function(res) {
$scope.loading = false;
$scope.blocks = res.blocks;
$scope.pagination = res.pagination;
});
};
$scope.findOne = function() {
$scope.loading = true;
Block.get({
blockHash: $routeParams.blockHash
}, function(block) {
$rootScope.titleDetail = block.height;
$rootScope.flashMessage = null;
$scope.loading = false;
$scope.block = block;
}, function(e) {
if (e.status === 400) {
$rootScope.flashMessage = 'Invalid Transaction ID: ' + $routeParams.txId;
}
else if (e.status === 503) {
$rootScope.flashMessage = 'Backend Error. ' + e.data;
}
else {
$rootScope.flashMessage = 'Block Not Found';
}
$location.path('/');
});
};
$scope.params = $routeParams;
});

View File

@ -1,53 +0,0 @@
'use strict';
angular.module('insight.connection').controller('ConnectionController',
function($scope, $window, Status, getSocket, PeerSync) {
// Set initial values
$scope.apiOnline = true;
$scope.serverOnline = true;
$scope.clienteOnline = true;
var socket = getSocket($scope);
// Check for the node server connection
socket.on('connect', function() {
$scope.serverOnline = true;
socket.on('disconnect', function() {
$scope.serverOnline = false;
});
});
// Check for the api connection
$scope.getConnStatus = function() {
PeerSync.get({},
function(peer) {
$scope.apiOnline = peer.connected;
$scope.host = peer.host;
$scope.port = peer.port;
},
function() {
$scope.apiOnline = false;
});
};
socket.emit('subscribe', 'sync');
socket.on('status', function(sync) {
$scope.sync = sync;
$scope.apiOnline = (sync.status !== 'aborted' && sync.status !== 'error');
});
// Check for the client conneciton
$window.addEventListener('offline', function() {
$scope.$apply(function() {
$scope.clienteOnline = false;
});
}, true);
$window.addEventListener('online', function() {
$scope.$apply(function() {
$scope.clienteOnline = true;
});
}, true);
});

View File

@ -1,51 +0,0 @@
'use strict';
angular.module('insight.currency').controller('CurrencyController',
function($scope, $rootScope, Currency) {
var _roundFloat = function(x, n) {
if(!parseInt(n, 10) || !parseFloat(x)) n = 0;
return Math.round(x * Math.pow(10, n)) / Math.pow(10, n);
};
$rootScope.currency.getConvertion = function(value) {
if (typeof value !== 'undefined' && value !== null) {
var response;
if (this.symbol === 'USD') {
response = _roundFloat((value * this.factor), 2);
} else if (this.symbol === 'mBTC') {
this.factor = 1000;
response = _roundFloat((value * this.factor), 5);
} else {
this.factor = 1;
response = value;
}
return response + ' ' + this.symbol;
}
return 'value error';
};
$scope.setCurrency = function(currency) {
$rootScope.currency.symbol = currency;
if (currency === 'USD') {
Currency.get({}, function(res) {
$rootScope.currency.factor = $rootScope.currency.bitstamp = res.data.bitstamp;
});
} else if (currency === 'mBTC') {
$rootScope.currency.factor = 1000;
} else {
$rootScope.currency.factor = 1;
}
};
// Get initial value
Currency.get({}, function(res) {
$rootScope.currency.bitstamp = res.data.bitstamp;
});
});

View File

@ -1,15 +0,0 @@
'use strict';
angular.module('insight.system').controller('FooterController',
function($scope, Version) {
var _getVersion = function() {
Version.get({},
function(res) {
$scope.version = res.version;
});
};
$scope.version = _getVersion();
});

View File

@ -1,40 +0,0 @@
'use strict';
angular.module('insight.system').controller('HeaderController',
function($scope, $rootScope, getSocket, Global, Block) {
$scope.global = Global;
$rootScope.currency = {
factor: 1,
bitstamp: 0,
symbol: 'BTC'
};
$scope.menu = [{
'title': 'Blocks',
'link': 'blocks'
}, {
'title': 'Status',
'link': 'status'
}];
var _getBlock = function(hash) {
Block.get({
blockHash: hash
}, function(res) {
$scope.totalBlocks = res.height;
});
};
var socket = getSocket($scope);
socket.on('connect', function() {
socket.emit('subscribe', 'inv');
socket.on('block', function(block) {
var blockHash = block.toString();
_getBlock(blockHash);
});
});
$rootScope.isCollapsed = true;
});

View File

@ -1,47 +0,0 @@
'use strict';
var TRANSACTION_DISPLAYED = 10;
var BLOCKS_DISPLAYED = 5;
angular.module('insight.system').controller('IndexController',
function($scope, Global, getSocket, Blocks) {
$scope.global = Global;
var _getBlocks = function() {
Blocks.get({
limit: BLOCKS_DISPLAYED
}, function(res) {
$scope.blocks = res.blocks;
$scope.blocksLength = res.lenght;
});
};
var socket = getSocket($scope);
socket.on('connect', function() {
socket.emit('subscribe', 'inv');
socket.on('tx', function(tx) {
$scope.txs.unshift(tx);
if (parseInt($scope.txs.length, 10) >= parseInt(TRANSACTION_DISPLAYED, 10)) {
$scope.txs = $scope.txs.splice(0, TRANSACTION_DISPLAYED);
}
});
socket.on('block', function() {
_getBlocks();
});
});
$scope.humanSince = function(time) {
var m = moment.unix(time);
return m.max().fromNow();
};
$scope.index = function() {
_getBlocks();
};
$scope.txs = [];
$scope.blocks = [];
});

View File

@ -1,63 +0,0 @@
'use strict';
angular.module('insight.search').controller('SearchController',
function($scope, $routeParams, $location, $timeout, Global, Block, Transaction, Address, BlockByHeight) {
$scope.global = Global;
$scope.loading = false;
var _badQuery = function() {
$scope.badQuery = true;
$timeout(function() {
$scope.badQuery = false;
}, 2000);
};
var _resetSearch = function() {
$scope.q = '';
$scope.loading = false;
};
$scope.search = function() {
var q = $scope.q;
$scope.badQuery = false;
$scope.loading = true;
Block.get({
blockHash: q
}, function() {
_resetSearch();
$location.path('block/' + q);
}, function() { //block not found, search on TX
Transaction.get({
txId: q
}, function() {
_resetSearch();
$location.path('tx/' + q);
}, function() { //tx not found, search on Address
Address.get({
addrStr: q
}, function() {
_resetSearch();
$location.path('address/' + q);
}, function() { // block by height not found
if (isFinite(q)) { // ensure that q is a finite number. A logical height value.
BlockByHeight.get({
blockHeight: q
}, function(hash) {
_resetSearch();
$location.path('/block/' + hash.blockHash);
}, function() { //not found, fail :(
_badQuery();
});
}
else {
$scope.loading = false;
_badQuery();
}
});
});
});
};
});

View File

@ -1,49 +0,0 @@
'use strict';
angular.module('insight.status').controller('StatusController',
function($scope, $routeParams, $location, Global, Status, Sync, getSocket) {
$scope.global = Global;
$scope.getStatus = function(q) {
Status.get({
q: 'get' + q
},
function(d) {
$scope.loaded = 1;
angular.extend($scope, d);
},
function(e) {
$scope.error = 'API ERROR: ' + e.data;
});
};
$scope.humanSince = function(time) {
var m = moment.unix(time / 1000);
return m.max().fromNow();
};
var _onSyncUpdate = function(sync) {
$scope.sync = sync;
};
$scope.getSync = function() {
Sync.get({},
function(sync) {
_onSyncUpdate(sync);
},
function(e) {
var err = 'Could not get sync information' + e.toString();
$scope.sync = {
error: err
};
});
};
var socket = getSocket($scope);
socket.on('connect', function() {
socket.emit('subscribe', 'sync');
socket.on('status', function(sync) {
_onSyncUpdate(sync);
});
});
});

View File

@ -1,172 +0,0 @@
'use strict';
angular.module('insight.transactions').controller('transactionsController',
function($scope, $rootScope, $routeParams, $location, Global, Transaction, TransactionsByBlock, TransactionsByAddress) {
$scope.global = Global;
$scope.loading = false;
$scope.loadedBy = null;
var pageNum = 0;
var pagesTotal = 1;
var COIN = 100000000;
var _aggregateItems = function(items) {
if (!items) return [];
var l = items.length;
var ret = [];
var tmp = {};
var u = 0;
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].isSpent = items[i].spentTxId;
tmp[addr].doubleSpentTxID = tmp[addr].doubleSpentTxID || items[i].doubleSpentTxID;
tmp[addr].doubleSpentIndex = tmp[addr].doubleSpentIndex || items[i].doubleSpentIndex;
tmp[addr].unconfirmedInput += items[i].unconfirmedInput;
tmp[addr].dbError = tmp[addr].dbError || items[i].dbError;
tmp[addr].valueSat += Math.round(items[i].value * COIN);
tmp[addr].items.push(items[i]);
tmp[addr].notAddr = notAddr;
tmp[addr].count++;
}
angular.forEach(tmp, function(v) {
v.value = v.value || parseInt(v.valueSat) / COIN;
ret.push(v);
});
return ret;
};
var _processTX = function(tx) {
tx.vinSimple = _aggregateItems(tx.vin);
tx.voutSimple = _aggregateItems(tx.vout);
};
var _paginate = function(data) {
$scope.loading = false;
pagesTotal = data.pagesTotal;
pageNum += 1;
data.txs.forEach(function(tx) {
_processTX(tx);
$scope.txs.push(tx);
});
};
var _byBlock = function() {
TransactionsByBlock.get({
block: $routeParams.blockHash,
pageNum: pageNum
}, function(data) {
_paginate(data);
});
};
var _byAddress = function () {
TransactionsByAddress.get({
address: $routeParams.addrStr,
pageNum: pageNum
}, function(data) {
_paginate(data);
});
};
var _findTx = function(txid) {
Transaction.get({
txId: txid
}, function(tx) {
$rootScope.titleDetail = tx.txid.substring(0,7) + '...';
$rootScope.flashMessage = null;
$scope.tx = tx;
_processTX(tx);
$scope.txs.unshift(tx);
}, function(e) {
if (e.status === 400) {
$rootScope.flashMessage = 'Invalid Transaction ID: ' + $routeParams.txId;
}
else if (e.status === 503) {
$rootScope.flashMessage = 'Backend Error. ' + e.data;
}
else {
$rootScope.flashMessage = 'Transaction Not Found';
}
$location.path('/');
});
};
$scope.findThis = function() {
_findTx($routeParams.txId);
};
//Initial load
$scope.load = function(from) {
$scope.loadedBy = from;
$scope.loadMore();
};
//Load more transactions for pagination
$scope.loadMore = function() {
if (pageNum < pagesTotal && !$scope.loading) {
$scope.loading = true;
if ($scope.loadedBy === 'address') {
_byAddress();
}
else {
_byBlock();
}
}
};
// Highlighted txout
if ($routeParams.v_type == '>' || $routeParams.v_type == '<') {
$scope.from_vin = $routeParams.v_type == '<' ? true : false;
$scope.from_vout = $routeParams.v_type == '>' ? true : false;
$scope.v_index = parseInt($routeParams.v_index);
$scope.itemsExpanded = true;
}
//Init without txs
$scope.txs = [];
$scope.$on('tx', function(event, txid) {
_findTx(txid);
});
});

View File

@ -1,75 +0,0 @@
'use strict';
var ZeroClipboard = window.ZeroClipboard;
angular.module('insight')
.directive("scroll", function ($window) {
return function(scope, element, attrs) {
angular.element($window).bind("scroll", function() {
if (this.pageYOffset >= 200) {
scope.secondaryNavbar = true;
} else {
scope.secondaryNavbar = false;
}
scope.$apply();
});
};
})
.directive('whenScrolled', function($window) {
return {
restric: 'A',
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);
});
}
};
})
.directive('clipCopy', function() {
ZeroClipboard.config({
moviePath: '/lib/zeroclipboard/ZeroClipboard.swf',
trustedDomains: ['*'],
allowScriptAccess: 'always',
forceHandCursor: true
});
return {
restric: 'A',
scope: { clipCopy: '=clipCopy' },
template: '<div class="tooltip fade right in"><div class="tooltip-arrow"></div><div class="tooltip-inner">Copied!</div></div>',
link: function(scope, elm) {
var clip = new ZeroClipboard(elm);
clip.on('load', function(client) {
var onMousedown = function(client) {
client.setText(scope.clipCopy);
};
client.on('mousedown', onMousedown);
scope.$on('$destroy', function() {
client.off('mousedown', onMousedown);
});
});
clip.on('noFlash wrongflash', function() {
return elm.remove();
});
}
};
});

View File

@ -1,9 +0,0 @@
'use strict';
angular.module('insight')
.filter('startFrom', function() {
return function(input, start) {
start = +start; //parse to int
return input.slice(start);
}
});

View File

@ -1,6 +0,0 @@
'use strict';
angular.element(document).ready(function() {
// Init the app
// angular.bootstrap(document, ['insight']);
});

View File

@ -1,23 +0,0 @@
'use strict';
angular.module('insight.address').factory('Address',
function($resource) {
return $resource('/api/addr/:addrStr', {
addrStr: '@addStr'
}, {
get: {
method: 'GET',
interceptor: {
response: function (res) {
return res.data;
},
responseError: function (res) {
if (res.status === 404) {
return res;
}
}
}
}
});
});

View File

@ -1,31 +0,0 @@
'use strict';
angular.module('insight.blocks')
.factory('Block',
function($resource) {
return $resource('/api/block/:blockHash', {
blockHash: '@blockHash'
}, {
get: {
method: 'GET',
interceptor: {
response: function (res) {
return res.data;
},
responseError: function (res) {
if (res.status === 404) {
return res;
}
}
}
}
});
})
.factory('Blocks',
function($resource) {
return $resource('/api/blocks');
})
.factory('BlockByHeight',
function($resource) {
return $resource('/api/block-index/:blockHeight');
});

View File

@ -1,6 +0,0 @@
'use strict';
angular.module('insight.currency').factory('Currency',
function($resource) {
return $resource('/api/currency');
});

View File

@ -1,12 +0,0 @@
'use strict';
//Global service for global variables
angular.module('insight.system')
.factory('Global',[
function() {
}
])
.factory('Version',
function($resource) {
return $resource('/api/version');
});

View File

@ -1,71 +0,0 @@
'use strict';
var ScopedSocket = function(socket, $rootScope) {
this.socket = socket;
this.$rootScope = $rootScope;
this.listeners = [];
};
ScopedSocket.prototype.removeAllListeners = function(opts) {
if (!opts) opts = {};
for (var i = 0; i < this.listeners.length; i++) {
var details = this.listeners[i];
if (opts.skipConnect && details.event === 'connect') {
continue;
}
this.socket.removeListener(details.event, details.fn);
}
this.listeners = [];
};
ScopedSocket.prototype.on = function(event, callback) {
var socket = this.socket;
var $rootScope = this.$rootScope;
var wrapped_callback = function() {
var args = arguments;
$rootScope.$apply(function() {
callback.apply(socket, args);
});
};
socket.on(event, wrapped_callback);
this.listeners.push({
event: event,
fn: wrapped_callback
});
};
ScopedSocket.prototype.emit = function(event, data, callback) {
var socket = this.socket;
var $rootScope = this.$rootScope;
socket.emit(event, data, function() {
var args = arguments;
$rootScope.$apply(function() {
if (callback) {
callback.apply(socket, args);
}
});
});
};
angular.module('insight.socket').factory('getSocket',
function($rootScope) {
var socket = io.connect(null, {
'reconnect': true,
'reconnection delay': 500,
});
return function(scope) {
var scopedSocket = new ScopedSocket(socket, $rootScope);
scope.$on('$destroy', function() {
scopedSocket.removeAllListeners();
});
socket.on('connect', function() {
scopedSocket.removeAllListeners({
skipConnect: true
});
});
return scopedSocket;
};
});

View File

@ -1,17 +0,0 @@
'use strict';
angular.module('insight.status')
.factory('Status',
function($resource) {
return $resource('/api/status', {
q: '@q'
});
})
.factory('Sync',
function($resource) {
return $resource('/api/sync');
})
.factory('PeerSync',
function($resource) {
return $resource('/api/peer');
});

View File

@ -1,39 +0,0 @@
'use strict';
angular.module('insight.transactions')
.factory('Transaction',
function($resource) {
return $resource('/api/tx/:txId', {
txId: '@txId'
}, {
get: {
method: 'GET',
interceptor: {
response: function (res) {
return res.data;
},
responseError: function (res) {
if (res.status === 404) {
return res;
}
}
}
}
});
})
.factory('TransactionsByBlock',
function($resource) {
return $resource('/api/txs', {
block: '@block'
});
})
.factory('TransactionsByAddress',
function($resource) {
return $resource('/api/txs', {
address: '@address'
});
})
.factory('Transactions',
function($resource) {
return $resource('/api/txs');
});

View File

@ -1,6 +0,0 @@
<div data-ng-include src="'/views/includes/connection.html'"></div>
<div class="jumbotron">
<h1>Ooops!</h1>
<h2 class="text-muted">404 Page not found :(</h2>
<p><a href="/" class="pull-right">Go to home</a></p>
</div>

View File

@ -1,80 +0,0 @@
<div data-ng-include src="'/views/includes/connection.html'"></div>
<section data-ng-controller="AddressController" data-ng-init="findOne()">
<div class="secondary_navbar hidden-xs hidden-sm" scroll data-ng-class="{'hidden': !secondaryNavbar}" data-ng-show="address.addrStr" data-ng-init="hideSNavbar=0">
<div class="container" data-ng-if="!hideSNavbar">
<div class="col-md-8 text-left">
<h3>Address</h3> {{address.addrStr}}
<span class="btn-copy" clip-copy="address.addrStr"></span>
</div>
<div class="col-md-4">
<span class="txvalues txvalues-primary"><strong>Final Balance</strong> {{$root.currency.getConvertion(address.balance) || address.balance + ' BTC' }}</span>
</div>
</div>
<div class="hide_snavbar">
<a href="#" data-ng-click="hideSNavbar=!hideSNavbar">
<span data-ng-show="hideSNavbar"><span class="text-muted glyphicon glyphicon-chevron-down"></span></span>
<span data-ng-show="!hideSNavbar"><span class="text-muted glyphicon glyphicon-chevron-up"></span></span>
</a>
</div>
</div>
<h1>Address <small data-ng-show="address.addrStr">{{$root.currency.getConvertion(address.balance) || address.balance + ' BTC'}}</small></h1>
<div class="text-muted" data-ng-if="!address.addrStr">
<span>Loading Address Information...</span>
</div>
<div data-ng-if="address.addrStr">
<div class="well well-sm ellipsis">
<strong>Address</strong>
<span class="text-muted">{{address.addrStr}}</span>
<span class="btn-copy" clip-copy="address.addrStr"></span>
</div>
<h2>Summary <small>confirmed</small></h2>
<div class="ng-cloak row" data-ng-hide="!address.addrStr" data-ng-cloak>
<div class="col-md-10">
<table class="table">
<tbody>
<tr>
<td><strong>Total Received</strong></td>
<td class="ellipsis text-right">{{$root.currency.getConvertion(address.totalReceived) || address.totalReceived + ' BTC'}}</td>
</tr>
<tr>
<td><strong>Total Sent</strong></td>
<td class="ellipsis text-right">{{$root.currency.getConvertion(address.totalSent) || address.totalSent + ' BTC'}}</td>
</tr>
<tr>
<td><strong>Final Balance</strong></td>
<td class="ellipsis text-right">{{$root.currency.getConvertion(address.balance) || address.balance + ' BTC'}}</td>
</tr>
<tr>
<td><strong>No. Transactions</strong></td>
<td class="ellipsis text-right">{{address.txApperances}}</td>
</tr>
</tbody>
</table>
</div>
<div class="col-md-2 text-center">
<qrcode size="160" data="{{address.addrStr}}"></qrcode>
</div>
</div>
<div data-ng-show="address.unconfirmedTxApperances">
<h3>Unconfirmed</h3>
<table class="table">
<tbody>
<tr>
<td class="small">Unconfirmed Txs Balance</td>
<td class="address ellipsis text-right">{{$root.currency.getConvertion(address.unconfirmedBalance)}}</td>
</tr>
<tr>
<td class="small">No. Transactions</td>
<td class="address ellipsis text-right">{{address.unconfirmedTxApperances}}</td>
</tr>
</tbody>
</table>
</div>
</div>
<h2>Transactions</h2>
<div data-ng-controller="transactionsController" data-ng-init="load('address')">
<div data-ng-include src="'/views/transaction/list.html'" when-scrolled="loadMore()"></div>
</div>
</section>

View File

@ -1,129 +0,0 @@
<div data-ng-include src="'/views/includes/connection.html'"></div>
<section data-ng-controller="BlocksController" data-ng-init="findOne()">
<div class="secondary_navbar hidden-xs hidden-sm" scroll data-ng-class="{'hidden': !secondaryNavbar}" data-ng-show="block.hash" data-ng-init="hideSNavbar=0">
<div class="container" data-ng-if="!hideSNavbar">
<div class="row">
<div class="col-md-1">
<a href="/block/{{block.previousblockhash}}"><span class="lead glyphicon glyphicon-chevron-left"></span></a>
</div>
<div class="col-md-10">
<div class="row">
<div class="col-md-5">
<h3 class="text-left">Block #{{block.height}}</h3>
</div>
<p class="col-md-6 ellipsis text-left">
<strong>Hash</strong> {{block.hash}}
</p>
<div class="col-md-1 text-left">
<span class="btn-copy" clip-copy="block.hash"></span>
</div>
</div>
</div>
<div class="col-md-1">
<a data-ng-show="block.nextblockhash" href="/block/{{block.nextblockhash}}"><span class="lead glyphicon glyphicon-chevron-right"></span></a>
</div>
</div>
</div> <!-- END OF CONTAINER -->
<div class="hide_snavbar">
<a href="#" data-ng-click="hideSNavbar=!hideSNavbar">
<span data-ng-show="hideSNavbar"><span class="text-muted glyphicon glyphicon-chevron-down"></span></span>
<span data-ng-show="!hideSNavbar"><span class="text-muted glyphicon glyphicon-chevron-up"></span></span>
</a>
</div>
</div>
<h1>Block #{{block.height}}</h1>
<div class="text-muted" data-ng-if="!block.hash">
<span>Loading Block Information...</span>
</div>
<div class="ng-cloak" data-ng-cloak data-ng-if="block.hash">
<div class="well well-sm ellipsis">
<strong>BlockHash</strong>
<span class="txid text-muted">{{block.hash}}</span>
<span class="btn-copy" clip-copy="block.hash"></span>
</div>
<h2>Summary</h2>
<div class="row">
<div class="col-md-6">
<table class="table" style="table-layout: fixed">
<tbody>
<tr>
<td><strong>Number Of Transactions</strong></td>
<td class="text-right text-muted">{{block.tx.length}}</td>
</tr>
<tr>
<td><strong>Height</strong></td>
<td class="text-right text-muted">{{block.height}}
<span data-ng-show="block.isMainChain" class="text-success">(Mainchain)</span>
<span data-ng-show="!block.isMainChain" class="text-danger"> <span class="glyphicon glyphicon-warning-sign"></span> (Orphaned)</span>
</td>
</tr>
<tr>
<td><strong>Block Reward</strong></td>
<td class="text-right text-muted">{{$root.currency.getConvertion(block.reward) || block.reward + ' BTC'}}</td>
</tr>
<tr>
<td><strong>Timestamp</strong></td>
<td class="text-right text-muted">{{block.time * 1000 | date:'medium'}}</td>
</tr>
<tr data-ng-show="block.poolInfo">
<td><strong>Mined by</strong></td>
<td class="text-right text-muted">
<a href="{{block.poolInfo.url}}" target="_blank" title="{{block.poolInfo.poolName}}">{{block.poolInfo.poolName}}</a>
</td>
</tr>
<tr>
<td><strong>Merkle Root</strong></td>
<td class="text-right text-muted">
<div class="ellipsis">
<span class="btn-copy" clip-copy="block.merkleroot"></span>
<span>{{block.merkleroot}}</span>
</div>
</td>
</tr>
<tr data-ng-show="block.previousblockhash">
<td><strong>Previous Block</strong></td>
<td class="text-right"><a href="/block/{{block.previousblockhash}}">{{block.height-1}}</a></td>
</tr>
</tbody>
</table>
</div>
<div class="col-md-6">
<table class="table">
<tbody>
<tr>
<td> <strong> Difficulty </strong></td>
<td class="text-right text-muted">{{block.difficulty}}</td>
</tr>
<tr>
<td> <strong> Bits </strong></td>
<td class="text-right text-muted">{{block.bits}}</td>
</tr>
<tr>
<td> <strong> Size (bytes) </strong></td>
<td class="text-right text-muted">{{block.size}}</td>
</tr>
<tr>
<td> <strong> Version </strong></td>
<td class="text-right text-muted">{{block.version}}</td>
</tr>
<tr>
<td> <strong> Nonce </strong></td>
<td class="text-right text-muted">{{block.nonce}}</td>
</tr>
<tr data-ng-show="block.nextblockhash">
<td><strong>Next Block</strong></td>
<td class="text-right"><a href="/block/{{block.nextblockhash}}">{{block.height+1}}</a></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div data-ng-controller="transactionsController" data-ng-init="load('block')">
<h3>Transactions</h3>
<div data-ng-include src="'/views/transaction/list.html'" when-scrolled="loadMore()"></div>
</div>
</section>

View File

@ -1,63 +0,0 @@
<div data-ng-include src="'/views/includes/connection.html'"></div>
<section data-ng-controller="BlocksController" data-ng-init="list()">
<div class="row">
<div class="col-xs-12 col-gray col-gray-fixed">
<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}} UTC
<a href="#" class="btn btn-primary btn-xs" datepicker-popup show-button-bar="false" data-ng-click="openCalendar($event)" data-ng-model="dt" is-open="opened" data-ng-required="true"><span class="glyphicon glyphicon-calendar"></span></a>
</p>
<div class="m20v text-center text-muted" data-ng-if="!pagination.current">
<span>Loading Selected Date...</span>
</div>
<div class="ng-cloak" data-ng-cloak data-ng-if="pagination.current">
<p class="lead text-center m20v" data-ng-show="loading">&nbsp;</p>
<p class="text-center m20v" data-ng-show="pagination.isToday && !loading">Today</p>
<p class="text-center m20v" data-ng-show="!pagination.isToday && !loading">{{humanSince(pagination.currentTs)}}
<p class="text-center m20v" data-ng-show="loading">&nbsp;</p>
<div class="m50v text-center">
<a class="btn btn-primary" href="/blocks-date/{{pagination.prev}}"><small>&larr; {{pagination.prev}}</small></a>
<a class="btn btn-primary" href="/blocks-date/{{pagination.next}}" data-ng-show="!pagination.isToday"><small>{{pagination.next}} &rarr;</small></a>
</div>
</div>
</div>
<div class="col-xs-12 col-md-9 col-md-offset-3">
<div class="page-header">
<h1>
Blocks
<small>by date</small>
</h1>
</div>
<table class="table table-hover table-striped">
<thead>
<tr>
<th>Height</th>
<th>Timestamp</th>
<th class="text-right">Transactions</th>
<th class="text-right">Mined by</th>
<th class="text-right">Size</th>
</tr>
</thead>
<tbody>
<tr data-ng-show="loading">
<td colspan="4">Waiting for blocks...</td>
</tr>
<tr class="fader" data-ng-repeat='b in blocks'>
<td><a href="/block/{{b.hash}}">{{b.height}}</a></td>
<td>{{b.time * 1000 | date:'medium'}}</td>
<td class="text-right">{{b.txlength}}</td>
<td class="text-right"><a href="{{b.poolInfo.url}}" title="{{b.poolInfo.poolName}}" target="_blank" data-ng-show="b.poolInfo">{{b.poolInfo.poolName}}</a></td>
<td class="text-right">{{b.size}}</td>
</tr>
</tbody>
</table>
</div>
</div>
<h2 class="text-center text-muted" data-ng-show="!blocks.length && !loading">No blocks yet.</h2>
</section>

View File

@ -1,23 +0,0 @@
<div class="connection-status container" data-ng-controller="ConnectionController">
<div class="alert alert-danger"
data-ng-show="!serverOnline || !clienteOnline || !apiOnline"
data-ng-init="getConnStatus()">
<strong>Error!</strong>
<p data-ng-show="!apiOnline">
Can't connect to bitcoind to get live updates from the p2p network.
(Tried connecting to bitcoind at {{host}}:{{port}} and failed.)
</p>
<p data-ng-show="!serverOnline">
Can't connect to insight server. Attempting to reconnect...
</p>
<p data-ng-show="!clienteOnline">
Can't connect to internet. Please, check your connection.
</p>
</div>
</div>

View File

@ -1,15 +0,0 @@
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
{{currency.symbol}} <span class="caret"></span>
</a>
<ul class="dropdown-menu">
<li>
<a href="#" data-ng-click="setCurrency('USD')" data-ng-class="{active: currency.symbol == 'USD'}">USD</a>
</li>
<li>
<a href="#" data-ng-click="setCurrency('BTC')" data-ng-class="{active: currency.symbol == 'BTC'}">BTC</a>
</li>
<li>
<a href="#" data-ng-click="setCurrency('mBTC')" data-ng-class="{active: currency.symbol == 'mBTC'}">mBTC</a>
</li>
</ul>

View File

@ -1,3 +0,0 @@
<div class="container" data-ng-controller="FooterController">
<a class="insight m10v pull-right" href="/">insight <small>API v{{version}}</small></a>
</div>

View File

@ -1,49 +0,0 @@
<div class="container">
<div data-ng-controller="HeaderController">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse" data-ng-click="$root.isCollapsed = !$root.isCollapsed">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="insight navbar-brand" href="/">insight</a>
</div>
<div class="navbar-collapse collapse" collapse="$root.isCollapsed">
<ul class="nav navbar-nav">
<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>
<form data-ng-controller="SearchController" class="navbar-form navbar-left hidden-xs" 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" data-ng-class="{'loading': loading}" placeholder="Search for block, transaction or address">
</div>
<div class="no_matching text-danger" data-ng-show="badQuery">No matching records found!</div>
</form>
<ul class="nav navbar-nav navbar-right">
<li>
<div class="status" data-ng-controller="StatusController">
<div data-ng-init="getSync()" class="pull-left">
<span class="t text-danger" data-ng-show="sync.error" tooltip="{{sync.error}}" tooltip-placement="bottom">
<span class="glyphicon glyphicon-warning-sign"></span>
ERROR
</span>
<span class="t" tooltip="{{sync.syncedBlocks}} / {{sync.blockChainHeight}} synced. {{sync.skippedBlocks}} skipped" tooltip-placement="bottom" data-ng-show="sync.status==='syncing'">
<span class="glyphicon glyphicon-refresh icon-rotate"></span>
{{sync.status}} {{sync.syncPercentage}}%
</span>
<span class="t text-default" tooltip="Historic sync finished" tooltip-placement="bottom" data-ng-show="sync.status==='finished'"> Synched </span>
</div>
&nbsp; &middot;
<span data-ng-init="getStatus('Info')">
<strong>Conn</strong> {{info.connections}}
</span> &middot;
<strong>Height</strong> {{totalBlocks || info.blocks}}
</div>
</li>
<li class="dropdown" data-ng-controller="CurrencyController" data-ng-include src="'/views/includes/currency.html'"></li>
</ul>
</div>
</div>
</div>

View File

@ -1,4 +0,0 @@
<span class="text-warning" data-ng-show="!loaded && !error">Loading...</span>
<span class="text-danger" data-ng-show="error">{{error}}</span>

View File

@ -1,6 +0,0 @@
<form data-ng-controller="SearchController" class="visible-xs" 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" data-ng-class="{'loading': loading}" placeholder="Search for block, transaction or address">
</div>
<div class="no_matching text-danger" data-ng-show="badQuery">No matching records found!</div>
</form>

View File

@ -1,83 +0,0 @@
<div class="alert alert-danger" data-ng-show="flashMessage">
{{$root.flashMessage}}
</div>
<div data-ng-include src="'/views/includes/connection.html'"></div>
<section data-ng-controller="IndexController" data-ng-init="index()">
<div class="container">
<div id="home" class="row">
<div class="col-xs-12 col-md-8">
<div data-ng-include src="'/views/includes/search.html'"></div>
<h1>Latest Blocks</h1>
<table class="table table-hover table-striped" style="table-layout: fixed">
<thead>
<tr>
<th>Height</th>
<th>Age</th>
<th class="text-right"><span class="ellipsis">Transactions</span></th>
<th class="text-right"><span class="ellipsis">Mined by</span></th>
<th class="text-right">Size</th>
</tr>
</thead>
<tbody>
<tr data-ng-show="!blocks.length"><td colspan="4">Waiting for blocks...</td></tr>
<tr class="fader" data-ng-repeat='b in blocks'>
<td>
<a href="/block/{{b.hash}}">{{b.height}}</a>
</td>
<td><span class="ellipsis">{{humanSince(b.time)}}</span></td>
<td class="text-right">{{b.txlength}}</td>
<td class="text-right"><a href="{{b.poolInfo.url}}" title="{{b.poolInfo.poolName}}" target="_blank" data-ng-show="b.poolInfo">{{b.poolInfo.poolName}}</a></td>
<td class="text-right">{{b.size}} bytes</td>
</tr>
</tbody>
</table>
<div class="btn-more">
<a href="/blocks" class="btn btn-default">See all blocks</a>
</div>
<h2>Latest Transactions</h2>
<table class="table table-hover table-striped" style="table-layout: fixed;">
<thead>
<tr>
<th>Hash</th>
<th class="text-right">Size</th>
<th class="text-right">Value Out</th>
</tr>
</thead>
<tbody>
<tr data-ng-show="!txs.length"><td colspan="3">Waiting for transactions...</td></tr>
<tr class="fader" data-ng-repeat='tx in txs'>
<td>
<a class="ellipsis" href="/tx/{{tx.txid}}">{{tx.txid}}</a>
</td>
<td class="text-right"><span class="ellipsis">{{tx.size}} bytes</span></td>
<td class="text-right"><span class="ellipsis">{{tx.valueOut}} BTC</span></td>
</tr>
</tbody>
</table>
</div>
<div class="col-xs-12 col-md-4 col-gray">
<h2> About </h2>
<p><strong>insight</strong> is an <a href="http://insight.bitcore.io/" target="_blank">open-source Bitcoin blockchain explorer</a> with complete REST
and websocket APIs that can be used for writing web wallets and other apps
that need more advanced blockchain queries than provided by bitcoind RPC.
Check out the <a href="http://github.com/bitpay/insight" target="_blank">source code</a>.</p>
<p><strong>insight</strong> is still in development, so be sure to report any bugs and provide feedback for improvement at our <a href="https://github.com/bitpay/insight/issues" target="_blank">github issue tracker</a>.</p>
<div id="powered" class="row">
<div class="powered-text">
<small class="text-muted">Powered by</small>
</div>
<a href="http://bitcore.io" target="_blank" class="bitcore" title="Bitcore"></a>
<a href="http://angularjs.org" target="_blank" class="angularjs" title="AngularJS"></a>
<a href="https://code.google.com/p/leveldb/" target="_blank" class="leveldb" title="LevelDB"></a>
<a href="http://nodejs.org" target="_blank" class="nodejs" title="NodeJs"></a>
</div>
</div> <!-- END OF COL-3 -->
</div>
</div>
</section>

View File

@ -1 +0,0 @@
<div class="text-center">Redirecting...</div>

View File

@ -1,165 +0,0 @@
<div data-ng-include src="'/views/includes/connection.html'"></div>
<section>
<div class="page-header">
<h1>
Application Status
</h1>
</div>
<div id="status" class="row">
<div class="col-xs-12 col-md-8">
<h2>Sync Status</h2>
<table class="table" data-ng-controller="StatusController" data-ng-init="getSync()">
<tbody>
<tr>
<td>Sync Progress</td>
<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 data-ng-show="sync.syncPercentage>0">{{sync.syncPercentage}}% Complete</span>
</div>
</div>
</td>
</tr>
<tr>
<td>Current Sync Status</td>
<td class="text-right">
<span data-ng-show="!sync.error">{{sync.status}}</span>
<span class="text-danger" data-ng-show="sync.error">
<span class="glyphicon glyphicon-warning-sign"></span>
{{sync.error}}
</span>
</td>
</tr>
<tr>
<td>Start Date</td>
<td class="text-right"><time title="{{sync.startTs | date:'medium'}}">{{humanSince(sync.startTs)}}</time></td>
</tr>
<tr data-ng-show="sync.endTs">
<td>Finish Date</td>
<td class="text-right"><time title="{{sync.startTs | date:'medium'}}" >{{humanSince(sync.endTs)}}</time></td>
</tr>
<tr>
<td>Initial Block Chain Height</td>
<td class="text-right">{{sync.blockChainHeight}}</td>
</tr>
<tr>
<td>Synced Blocks</td>
<td class="text-right">{{sync.syncedBlocks}}</td>
</tr>
<tr>
<td>Skipped Blocks (previously synced)</td>
<td class="text-right">{{sync.skippedBlocks}}</td>
</tr>
<tr>
<td>Sync Type</td>
<td class="text-right">{{sync.type}}</td>
</tr>
</tbody>
</table>
<h2>Last Block</h2>
<table class="table" style="table-layout: fixed" data-ng-controller="StatusController" data-ng-init="getStatus('LastBlockHash')">
<thead data-ng-include src="'/views/includes/infoStatus.html'"></thead>
<tbody>
<tr>
<td>Last Block Hash (Bitcoind)</td>
<td class="text-right ellipsis"><a href="/block/{{lastblockhash}}">{{lastblockhash}}</a></td>
</tr>
<tr>
<td>Current Blockchain Tip (insight)</td>
<td class="text-right ellipsis"><a href="/block/{{syncTipHash}}">{{syncTipHash}}</a></td>
</tr>
</tbody>
</table>
<h2>Transaction Output Set Information</h2>
<div data-ng-controller="StatusController">
<button data-ng-click="txoutLoading=1;getStatus('TxOutSetInfo')" class="btn btn-default" data-ng-show="!txoutsetinfo.height">
Show Transaction Output data
<span data-ng-show="txoutLoading" class="glyphicon glyphicon-refresh icon-rotate"></span>
</button >
<table class="table" data-ng-show="txoutsetinfo.height" style="table-layout: fixed" >
<thead data-ng-include src="'/views/includes/infoStatus.html'"></thead>
<tbody>
<tr>
<td>Height</td>
<td class="text-right"><a href="/block-index/{{txoutsetinfo.height}}">{{txoutsetinfo.height}}</a></td>
</tr>
<tr>
<td>Best Block</td>
<td class="text-right ellipsis"><a href="/block/{{txoutsetinfo.bestblock}}">{{txoutsetinfo.bestblock}}</a></td>
</tr>
<tr>
<td>Transactions</td>
<td class="text-right"> {{txoutsetinfo.transactions}}</td>
</tr>
<tr>
<td>Transaction Outputs</td>
<td class="text-right">{{txoutsetinfo.txouts}}</td>
</tr>
<tr>
<td>Bytes Serialized</td>
<td class="text-right">{{txoutsetinfo.bytes_serialized}}</td>
</tr>
<tr>
<td>Hash Serialized</td>
<td class="text-right ellipsis">{{txoutsetinfo.hash_serialized}}</td>
</tr>
<tr>
<td>Total Amount</td>
<td class="text-right">{{txoutsetinfo.total_amount}}</td>
</tr>
</tbody>
</table>
</div>
</div> <!-- END OF COL-8 -->
<div class="col-xs-12 col-md-4 col-gray">
<h2>Bitcoin node information</h2>
<table class="table" data-ng-controller="StatusController" data-ng-init="getStatus('Info')">
<thead data-ng-include src="'/views/includes/infoStatus.html'"></thead>
<tbody>
<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>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>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>Proxy setting</td>
<td class="text-right">{{info.proxy}}</td>
</tr>
<tr>
<td>Info Errors</td>
<td class="text-right">{{info.infoErrors}}</td>
</tr>
</tbody>
</table>
</div> <!-- END OF COL-GRAY -->
</div>
</section>

View File

@ -1,67 +0,0 @@
<div data-ng-include src="'/views/includes/connection.html'"></div>
<section data-ng-controller="transactionsController" data-ng-init="findThis()">
<div class="secondary_navbar hidden-xs hidden-sm" scroll data-ng-class="{'hidden': !secondaryNavbar}" data-ng-show="tx.txid" data-ng-init="hideSNavbar=0">
<div class="container" data-ng-if="!hideSNavbar">
<div class="col-md-8 text-left">
<h3>Transaction</h3> {{tx.txid}}
<span class="btn-copy" clip-copy="tx.txid"></span>
</div>
<div class="col-md-4">
<span data-ng-show="tx.confirmations" class="txvalues txvalues-success">{{tx.confirmations}} Confirmations</span>
<span data-ng-show="!tx.confirmations" class="txvalues txvalues-danger">Unconfirmed Transaction!</span>
<span class="txvalues txvalues-primary">{{$root.currency.getConvertion(tx.valueOut) || tx.valueOut + ' BTC' }}</span>
</div>
</div>
<div class="hide_snavbar">
<a href="#" data-ng-click="hideSNavbar=!hideSNavbar">
<span data-ng-show="hideSNavbar"><span class="text-muted glyphicon glyphicon-chevron-down"></span></span>
<span data-ng-show="!hideSNavbar"><span class="text-muted glyphicon glyphicon-chevron-up"></span></span>
</a>
</div>
</div>
<div class="ng-cloak" data-ng-cloak data-ng-if="tx.txid">
<h1>Transaction
<small data-ng-show="from_vin || from_vout">
<span data-ng-show="from_vin">Input</span>
<span data-ng-show="from_vout">Output</span>
<span>{{v_index}}</span>
</small>
</h1>
<div class="progress progress-striped active" data-ng-if="!tx.txid">
<div class="progress-bar progress-bar-info" style="width: 100%">
<span>Loading Transaction Details...</span>
</div>
</div>
<div data-ng-if="tx.txid">
<div class="well well-sm ellipsis">
<strong>Transaction</strong>
<span class="txid text-muted">{{tx.txid}}</span>
<span class="btn-copy" clip-copy="tx.txid"></span>
</div>
<h2>Summary</h2>
<table class="table" style="table-layout: fixed">
<tbody>
<tr>
<td><strong> Size </strong></td>
<td class="text-muted text-right">{{tx.size}} (bytes)</td>
</tr>
<tr>
<td><strong>Received Time </strong></td>
<td data-ng-show="tx.firstSeenTs" class="text-muted text-right">{{tx.firstSeenTs * 1000|date:'medium'}}</td>
<td data-ng-show="!tx.firstSeenTs" class="text-muted text-right">N/A</td>
</tr>
<tr>
<td><strong>Mined Time </strong></td>
<td data-ng-show="tx.time" class="text-muted text-right">{{tx.time * 1000|date:'medium'}}</td>
<td data-ng-show="!tx.time" class="text-muted text-right">N/A</td>
</tr>
</tbody>
</table>
</div>
<h2>Details</h2>
<div class="block-tx ng-cloak" data-ng-cloak data-ng-if="tx.txid">
<div data-ng-include src="'/views/transaction/tx.html'"></div>
</div>
</div>
</section>

View File

@ -1,9 +0,0 @@
<div class="alert alert-warning" data-ng-show="!txs[0].txid && !loading">There are not transactions involving this address.</div>
<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>
<div class="progress progress-striped active" data-ng-show="loading">
<div class="progress-bar progress-bar-info" style="width: 100%">
<span>Loading Transactions...</span>
</div>
</div>

View File

@ -1,184 +0,0 @@
<div class="line-bot row ng-cloak" data-ng-hide="!tx" data-ng-cloak>
<div class="col-xs-12 col-md-6">
<div class="ellipsis">
<a class="btn-expand" href="#" title="Show/Hide items details" data-ng-click="itemsExpanded = !itemsExpanded">
<span class="glyphicon glyphicon-plus-sign" data-ng-class="{'glyphicon-minus-sign': itemsExpanded}"></span>
</a>
<a class="txid" href="/tx/{{tx.txid}}">{{tx.txid}}</a>
<span class="btn-copy" clip-copy="tx.txid"></span>
</div>
</div>
<div class="col-xs-12 col-md-6 text-right">
<div data-ng-show="tx.firstSeenTs">
first seen at
<time>{{tx.firstSeenTs * 1000 | date:'medium'}}</time>
</div>
<div data-ng-show="tx.time && !tx.firstSeenTs">
mined at
<time>{{tx.time * 1000 | date:'medium'}}</time>
</div>
</div>
</div>
<div class="row line-mid">
<div class="col-md-5">
<div class="row" data-ng-if="tx.isCoinBase">
<div class="col-md-12 transaction-vin-vout" data-ng-repeat="vin in tx.vin">
<div class="ellipsis">
<span>No Inputs (Newly Generated Coins)</span>
</div>
</div>
</div>
<div class="row" data-ng-if="!tx.isCoinBase">
<!-- Simple view -->
<div data-ng-if="!itemsExpanded" data-ng-init="currentInNoExpanded=0; sizeInNoExpanded=5">
<div data-ng-repeat="vin in tx.vinSimple| startFrom:currentInNoExpanded*sizeInNoExpanded | limitTo:sizeInNoExpanded">
<div class="col-md-12 transaction-vin-vout">
<div class="pull-right btc-value" data-ng-class="{'text-danger': $root.currentAddr == vin.addr}">
<p>{{$root.currency.getConvertion(vin.value) || vin.value + ' BTC'}}</p>
</div>
<div class="ellipsis">
<span data-ng-show="vin.notAddr">{{vin.addr}}</span>
<span class="text-muted" title="Current Bitcoin Address" data-ng-show="vin.addr == $root.currentAddr">{{vin.addr}}</span>
<a href="/address/{{vin.addr}}" data-ng-show="!vin.notAddr && vin.addr != $root.currentAddr">{{vin.addr}}</a>
</div>
<div data-ng-show="vin.unconfirmedInput" class="text-danger"> <span class="glyphicon glyphicon-warning-sign"></span> (Input unconfirmed)</div>
<div data-ng-show="vin.dbError" class="text-danger"> <span class="glyphicon glyphicon-warning-sign"></span> Incoherence in levelDB detected, please resync</div>
<div data-ng-show="vin.doubleSpentTxID" class="text-danger"> <span class="glyphicon glyphicon-warning-sign"></span> Double spent attempt detected. From tx:
<a href="/tx/{{vin.doubleSpentTxID}}">{{vin.doubleSpentTxID}},{{vin.doubleSpentIndex}}</a>
</div>
</div>
</div>
<div class="showmore_collapse text-right" data-ng-show="tx.vinSimple.length > 5" data-ng-class="{ 'hidden': itemsExpanded}">
<a href="#" ng-hide="sizeInNoExpanded != tx.vinSimple.length" ng-click="currentInNoExpanded=0; sizeInNoExpanded=5"><small>...less</small></a>
<a href="#" ng-hide="currentInNoExpanded >= tx.vinSimple.length/sizeInNoExpanded - 1" ng-click="currentInNoExpanded=0; sizeInNoExpanded=tx.vinSimple.length"><small>more...</small></a>
</div>
</div>
<!-- Full view -->
<div data-ng-if="itemsExpanded" data-ng-init="currentInExpanded=0; sizeInExpanded=(from_vin) ? tx.vin.length : 5; fromVinCollapsed=(from_vin)">
<a href="#" data-ng-show="(from_vin) && tx.vin.length > 1" data-ng-class="{'text-muted': fromVinCollapsed}" data-ng-click="currentInExpanded=0; sizeInExpanded=tx.vin.length;fromVinCollapsed=1"><small>show input {{ v_index }}</small></a>
<a href="#" data-ng-show="(from_vin) && tx.vin.length > 1" data-ng-class="{'text-muted': !fromVinCollapsed}" data-ng-click="currentInExpanded=0; sizeInExpanded=tx.vin.length;fromVinCollapsed=0"><small>show all</small></a>
<div data-ng-repeat="vin in tx.vin| startFrom:currentInExpanded*sizeInExpanded | limitTo:sizeInExpanded" data-ng-if="fromVinCollapsed ? v_index == vin.n : 1">
<div class="col-md-12 transaction-vin-vout">
<div class="pull-right btc-value"><p>{{$root.currency.getConvertion(vin.value) || vin.value + ' BTC'}}</p></div>
<div class="ellipsis">
<a class="glyphicon glyphicon-chevron-right" href="/tx/{{vin.txid}}/>/{{vin.vout}}" title="Outpoint: {{vin.txid}},{{vin.vout}}"></a>&nbsp;&nbsp;
<span data-ng-show="vin.notAddr">{{vin.addr}}</span>
<a href="/address/{{vin.addr}}" data-ng-show="!vin.notAddr">{{vin.addr}}</a>
</div>
<div data-ng-show="vin.unconfirmedInput" class="text-danger"> <span class="glyphicon glyphicon-warning-sign"></span> (Input unconfirmed)</div>
<div data-ng-show="vin.dbError" class="text-danger"> <span class="glyphicon glyphicon-warning-sign"></span> Incoherence in levelDB detected, please resync</div>
<div data-ng-show="vin.doubleSpentTxID" class="text-danger"> <span class="glyphicon glyphicon-warning-sign"></span> Double spent attempt detected. From tx:
<a href="/tx/{{vin.doubleSpentTxID}}">{{vin.doubleSpentTxID}},{{vin.doubleSpentIndex}}</a>
</div>
</div>
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-body" style="word-wrap:break-word" data-ng-class="{true: 'v_highlight', false: ''}[from_vin == true && v_index == vin.n]">
<small>
<strong>scriptSig</strong>
{{vin.scriptSig.asm}}
</small>
</div>
</div>
</div>
</div>
<div class="showmore_collapse text-right" data-ng-show="tx.vin.length > 5 && !fromVinCollapsed" data-ng-class="{ 'hidden': !itemsExpanded}">
<a href="#" ng-hide="sizeInExpanded != tx.vin.length" ng-click="currentInExpanded=0; sizeInExpanded=5"><small>...less</small></a>
<a href="#" data-ng-class="{true: 'v_highlight_more', false: ''}[from_vin == true && v_index > 5]" ng-hide="currentInExpanded >= tx.vin.length/sizeInExpanded - 1" ng-click="currentInExpanded=0; sizeInExpanded=tx.vin.length"><small>more...</small></a>
</div>
</div>
</div>
</div>
<div class="col-md-1 col-xs-12">
<div class="hidden-xs hidden-sm text-center">
<span class="glyphicon glyphicon-chevron-right text-primary"></span>
</div>
<div class="hidden-md hidden-lg text-center">
<span class="glyphicon glyphicon-chevron-down text-primary"></span>
</div>
</div>
<div class="col-md-6">
<div class="row">
<!-- Simple view -->
<div data-ng-if="!itemsExpanded" data-ng-init="currentOutNoExpanded=0; sizeOutNoExpanded=5">
<div data-ng-repeat="vout in tx.voutSimple| startFrom:currentOutNoExpanded*sizeOutNoExpanded | limitTo:sizeOutNoExpanded">
<div class="col-md-12 transaction-vin-vout">
<div class="pull-right btc-value" data-ng-class="{'text-success': $root.currentAddr == vout.addr}">
{{$root.currency.getConvertion(vout.value) || vout.value + ' BTC' }}
<span class="text-danger" data-ng-show="vout.isSpent" tooltip="Output is spent" tooltip-placement="left">(S)</span>
<span class="text-success" data-ng-show="!vout.isSpent" tooltip="Output is unspent" tooltip-placement="left">(U)</span>
</div>
<div class="ellipsis">
<span data-ng-show="vout.notAddr">{{vout.addr}}</span>
<span class="text-muted" title="Current Bitcoin Address" data-ng-show="address == $root.currentAddr" data-ng-repeat="address in vout.addr.split(',')">{{vout.addr}}</span>
<a href="/address/{{address}}" data-ng-show="!vout.notAddr && address != $root.currentAddr" data-ng-repeat="address in vout.addr.split(',')">{{address}}</a>
</div>
</div>
</div>
<div class="showmore_collapse text-right" data-ng-show="tx.voutSimple.length > 5" data-ng-class="{ 'hidden': itemsExpanded}">
<a href="#" ng-hide="sizeOutNoExpanded != tx.voutSimple.length" ng-click="currentOutNoExpanded=0; sizeOutNoExpanded=5"><small>...less</small></a>
<a href="#" ng-hide="currentOutNoExpanded >= tx.voutSimple.length/sizeOutNoExpanded - 1" ng-click="currentOutNoExpanded=0; sizeOutNoExpanded=tx.voutSimple.length"><small>more...</small></a>
</div>
</div>
<!-- Full view -->
<div data-ng-if="itemsExpanded" data-ng-init="currentOutExpanded=0; sizeOutExpanded=(from_vout) ? tx.vout.length : 5; fromVoutCollapsed=(from_vout)">
<a href="#" data-ng-show="(from_vout) && tx.vout.length > 1" data-ng-class="{'text-muted': fromVoutCollapsed}" data-ng-click="currentOutExpanded=0; sizeOutExpanded=tx.vout.length;fromVoutCollapsed=1"><small>show output {{ v_index }}</small></a>
<a href="#" data-ng-show="(from_vout) && tx.vout.length > 1" data-ng-class="{'text-muted': !fromVoutCollapsed}" data-ng-click="currentOutExpanded=0; sizeOutExpanded=tx.vout.length;fromVoutCollapsed=0"><small>show all</small></a>
<div data-ng-repeat="vout in tx.vout| startFrom:currentOutExpanded*sizeOutExpanded | limitTo:sizeOutExpanded" data-ng-if="fromVoutCollapsed ? v_index == vout.n : 1">
<div class="col-md-12 transaction-vin-vout">
<div class="pull-right btc-value">
<p>{{$root.currency.getConvertion(vout.value) || vout.value + ' BTC'}}
<span class="text-success" data-ng-show="!vout.spentTxId" tooltip="Output is unspent" tooltip-placement="left">(U)</span>
<a class="glyphicon glyphicon-chevron-right" data-ng-show="vout.spentTxId" href="/tx/{{vout.spentTxId}}/</{{vout.spentIndex}}" title="Spent at: {{vout.spentTxId}},{{vout.spentIndex}}"></a>&nbsp;&nbsp;
</p>
</div>
<div class="ellipsis">
<a href="/address/{{address}}" data-ng-repeat="address in vout.scriptPubKey.addresses">{{address}}</a>
</div>
</div>
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-body" style="word-wrap:break-word" data-ng-class="{true: 'v_highlight', false: ''}[from_vout == true && v_index == vout.n]">
<small>
<p>
<strong>Type</strong>
{{vout.scriptPubKey.type}}
</p>
<p>
<strong>scriptPubKey</strong>
{{vout.scriptPubKey.asm}}
</p>
</small>
</div>
</div>
</div>
</div>
<div class="showmore_collapse text-right" data-ng-show="tx.vout.length > 5 && !fromVoutCollapsed" data-ng-class="{ 'hidden': !itemsExpanded}">
<a href="#" ng-hide="sizeOutExpanded != tx.vout.length" ng-click="currentOutExpanded=0; sizeOutExpanded=5"><small>...less</small></a>
<a href="#" data-ng-class="{true: 'v_highlight_more', false: ''}[from_vout == true && v_index > 5]" ng-hide="currentOutExpanded >= tx.vout.length/sizeOutExpanded - 1" ng-click="currentOutExpanded=0; sizeOutExpanded=tx.vout.length"><small>more...</small></a>
</div>
</div>
</div>
</div>
</div>
<div class="well well-sm bgwhite ellipsis" data-ng-if="itemsExpanded && !block.hash">
<strong>BlockHash</strong> <a class="text-muted" href="/block/{{tx.blockhash}}">{{tx.blockhash}}</a>
<span class="btn-copy" clip-copy="tx.blockhash"></span>
</div>
<div class="line-top row ng-cloak" data-ng-hide="!tx" data-ng-cloak>
<div class="col-xs-12 col-sm-4 col-md-4">
<span data-ng-show="!tx.isCoinBase && !isNaN(parseFloat(tx.fees))" class="txvalues txvalues-default">Fees: {{$root.currency.getConvertion(tx.fees) || tx.fees + 'BTC'}} </span>
</div>
<div class="col-xs-12 col-sm-8 col-md-8 text-right">
<span data-ng-show="tx.confirmations" class="txvalues txvalues-success">{{tx.confirmations}} Confirmations</span>
<span data-ng-show="!tx.confirmations" class="txvalues txvalues-danger">Unconfirmed Transaction!</span>
<span class="txvalues txvalues-primary">{{$root.currency.getConvertion(tx.valueOut) || tx.valueOut + ' BTC' }}</span>
</div>
</div>