@@ -6,6 +6,8 @@ var zlib = require('zlib');
6
6
var async = require ( 'async' ) ;
7
7
8
8
var apiInterfaces = require ( './apiInterfaces.js' ) ( config . daemon , config . wallet ) ;
9
+ var charts = require ( './charts.js' ) ;
10
+ var authSid = Math . round ( Math . random ( ) * 10000000000 ) + '' + Math . round ( Math . random ( ) * 10000000000 ) ;
9
11
10
12
var logSystem = 'api' ;
11
13
require ( './exceptionWriter.js' ) ( logSystem ) ;
@@ -28,6 +30,7 @@ var currentStats = "";
28
30
var currentStatsCompressed = "" ;
29
31
30
32
var minerStats = { } ;
33
+ var minersHashrate = { } ;
31
34
32
35
var liveConnections = { } ;
33
36
var addressConnections = { } ;
@@ -67,18 +70,20 @@ function collectStats(){
67
70
var hashrates = replies [ 1 ] ;
68
71
69
72
minerStats = { } ;
73
+ minersHashrate = { } ;
70
74
71
75
for ( var i = 0 ; i < hashrates . length ; i ++ ) {
72
76
var hashParts = hashrates [ i ] . split ( ':' ) ;
73
- minerStats [ hashParts [ 1 ] ] = ( minerStats [ hashParts [ 1 ] ] || 0 ) + parseInt ( hashParts [ 0 ] ) ;
77
+ minersHashrate [ hashParts [ 1 ] ] = ( minerStats [ hashParts [ 1 ] ] || 0 ) + parseInt ( hashParts [ 0 ] ) ;
74
78
}
75
79
76
80
var totalShares = 0 ;
77
81
78
- for ( var miner in minerStats ) {
79
- var shares = minerStats [ miner ] ;
82
+ for ( var miner in minersHashrate ) {
83
+ var shares = minersHashrate [ miner ] ;
80
84
totalShares += shares ;
81
- minerStats [ miner ] = getReadableHashRateString ( shares / config . api . hashrateWindow ) ;
85
+ minersHashrate [ miner ] = Math . round ( shares / config . api . hashrateWindow ) ;
86
+ minerStats [ miner ] = getReadableHashRateString ( minersHashrate [ miner ] ) ;
82
87
}
83
88
84
89
data . miners = Object . keys ( minerStats ) . length ;
@@ -133,7 +138,8 @@ function collectStats(){
133
138
minPaymentThreshold : config . payments . minPayment ,
134
139
denominationUnit : config . payments . denomination
135
140
} ) ;
136
- }
141
+ } ,
142
+ charts : charts . getPoolChartsData
137
143
} , function ( error , results ) {
138
144
139
145
log ( 'info' , logSystem , 'Stat collection finished: %d ms redis, %d ms daemon' , [ redisFinished - startTime , daemonFinished - startTime ] ) ;
@@ -237,7 +243,13 @@ function handleMinerStats(urlParts, response){
237
243
}
238
244
var stats = replies [ 0 ] ;
239
245
stats . hashrate = minerStats [ address ] ;
240
- response . end ( JSON . stringify ( { stats : stats , payments : replies [ 1 ] } ) ) ;
246
+ charts . getUserChartsData ( address , replies [ 1 ] , function ( error , chartsData ) {
247
+ response . end ( JSON . stringify ( {
248
+ stats : stats ,
249
+ payments : replies [ 1 ] ,
250
+ charts : chartsData
251
+ } ) ) ;
252
+ } ) ;
241
253
} ) ;
242
254
}
243
255
}
@@ -307,25 +319,59 @@ function handleGetBlocks(urlParts, response){
307
319
} ) ;
308
320
}
309
321
322
+ function handleGetMinersHashrate ( response ) {
323
+ var reply = JSON . stringify ( {
324
+ minersHashrate : minersHashrate
325
+ } ) ;
326
+ response . writeHead ( "200" , {
327
+ 'Access-Control-Allow-Origin' : '*' ,
328
+ 'Cache-Control' : 'no-cache' ,
329
+ 'Content-Type' : 'application/json' ,
330
+ 'Content-Length' : reply . length
331
+ } ) ;
332
+ response . end ( reply ) ;
333
+ }
310
334
311
- collectStats ( ) ;
335
+ function parseCookies ( request ) {
336
+ var list = { } ,
337
+ rc = request . headers . cookie ;
338
+ rc && rc . split ( ';' ) . forEach ( function ( cookie ) {
339
+ var parts = cookie . split ( '=' ) ;
340
+ list [ parts . shift ( ) . trim ( ) ] = unescape ( parts . join ( '=' ) ) ;
341
+ } ) ;
342
+ return list ;
343
+ }
312
344
313
345
function authorize ( request , response ) {
346
+ if ( request . connection . remoteAddress == '127.0.0.1' ) {
347
+ return true ;
348
+ }
314
349
315
350
response . setHeader ( 'Access-Control-Allow-Origin' , '*' ) ;
316
351
352
+ var cookies = parseCookies ( request ) ;
353
+ if ( cookies . sid && cookies . sid == authSid ) {
354
+ return true ;
355
+ }
356
+
317
357
var sentPass = url . parse ( request . url , true ) . query . password ;
318
358
359
+
319
360
if ( sentPass !== config . api . password ) {
320
361
response . statusCode = 401 ;
321
362
response . end ( 'invalid password' ) ;
322
363
return ;
323
364
}
324
365
366
+ log ( 'warn' , logSystem , 'Admin authorized' ) ;
325
367
response . statusCode = 200 ;
368
+
369
+ var cookieExpire = new Date ( new Date ( ) . getTime ( ) + 60 * 60 * 24 * 1000 ) ;
370
+ response . setHeader ( 'Set-Cookie' , 'sid=' + authSid + '; path=/; expires=' + cookieExpire . toUTCString ( ) ) ;
326
371
response . setHeader ( 'Cache-Control' , 'no-cache' ) ;
327
372
response . setHeader ( 'Content-Type' , 'application/json' ) ;
328
373
374
+
329
375
return true ;
330
376
}
331
377
@@ -406,6 +452,149 @@ function handleAdminStats(response){
406
452
407
453
}
408
454
455
+
456
+ function handleAdminUsers ( response ) {
457
+ async . waterfall ( [
458
+ // get workers Redis keys
459
+ function ( callback ) {
460
+ redisClient . keys ( config . coin + ':workers:*' , callback ) ;
461
+ } ,
462
+ // get workers data
463
+ function ( workerKeys , callback ) {
464
+ var redisCommands = workerKeys . map ( function ( k ) {
465
+ return [ 'hmget' , k , 'balance' , 'paid' , 'lastShare' , 'hashes' ] ;
466
+ } ) ;
467
+ redisClient . multi ( redisCommands ) . exec ( function ( error , redisData ) {
468
+ var workersData = { } ;
469
+ var addressLength = config . poolServer . poolAddress . length ;
470
+ for ( var i in redisData ) {
471
+ var address = workerKeys [ i ] . substr ( - addressLength ) ;
472
+ var data = redisData [ i ] ;
473
+ workersData [ address ] = {
474
+ pending : data [ 0 ] / config . coinUnits ,
475
+ paid : data [ 1 ] / config . coinUnits ,
476
+ lastShare : data [ 2 ] ,
477
+ hashes : data [ 3 ] ,
478
+ hashrate : minersHashrate [ address ] ? minersHashrate [ address ] : 0
479
+ } ;
480
+ }
481
+ callback ( null , workersData ) ;
482
+ } ) ;
483
+ }
484
+ ] , function ( error , workersData ) {
485
+ if ( error ) {
486
+ response . end ( JSON . stringify ( { error : 'error collecting users stats' } ) ) ;
487
+ return ;
488
+ }
489
+ response . end ( JSON . stringify ( workersData ) ) ;
490
+ }
491
+ ) ;
492
+ }
493
+
494
+
495
+ function handleAdminMonitoring ( response ) {
496
+ response . writeHead ( "200" , {
497
+ 'Access-Control-Allow-Origin' : '*' ,
498
+ 'Cache-Control' : 'no-cache' ,
499
+ 'Content-Type' : 'application/json'
500
+ } ) ;
501
+ async . parallel ( {
502
+ monitoring : getMonitoringData ,
503
+ logs : getLogFiles
504
+ } , function ( error , result ) {
505
+ response . end ( JSON . stringify ( result ) ) ;
506
+ } ) ;
507
+ }
508
+
509
+ function handleAdminLog ( urlParts , response ) {
510
+ var file = urlParts . query . file ;
511
+ var filePath = config . logging . files . directory + '/' + file ;
512
+ if ( ! file . match ( / ^ \w + \. l o g $ / ) ) {
513
+ response . end ( 'wrong log file' ) ;
514
+ }
515
+ response . writeHead ( 200 , {
516
+ 'Content-Type' : 'text/plain' ,
517
+ 'Cache-Control' : 'no-cache' ,
518
+ 'Content-Length' : fs . statSync ( filePath ) . size
519
+ } ) ;
520
+ fs . createReadStream ( filePath ) . pipe ( response )
521
+ }
522
+
523
+
524
+ function startRpcMonitoring ( rpc , module , method , interval ) {
525
+ setInterval ( function ( ) {
526
+ rpc ( method , { } , function ( error , response ) {
527
+ var stat = {
528
+ lastCheck : new Date ( ) / 1000 | 0 ,
529
+ lastStatus : error ? 'fail' : 'ok' ,
530
+ lastResponse : JSON . stringify ( error ? error : response )
531
+ } ;
532
+ if ( error ) {
533
+ stat . lastFail = stat . lastCheck ;
534
+ stat . lastFailResponse = stat . lastResponse ;
535
+ }
536
+ var key = getMonitoringDataKey ( module ) ;
537
+ var redisCommands = [ ] ;
538
+ for ( var property in stat ) {
539
+ redisCommands . push ( [ 'hset' , key , property , stat [ property ] ] ) ;
540
+ }
541
+ redisClient . multi ( redisCommands ) . exec ( ) ;
542
+ } ) ;
543
+ } , interval * 1000 ) ;
544
+ }
545
+
546
+ function getMonitoringDataKey ( module ) {
547
+ return config . coin + ':status:' + module ;
548
+ }
549
+
550
+ function initMonitoring ( ) {
551
+ var modulesRpc = {
552
+ daemon : apiInterfaces . rpcDaemon ,
553
+ wallet : apiInterfaces . rpcWallet
554
+ } ;
555
+ for ( var module in config . monitoring ) {
556
+ var settings = config . monitoring [ module ] ;
557
+ if ( settings . checkInterval ) {
558
+ startRpcMonitoring ( modulesRpc [ module ] , module , settings . rpcMethod , settings . checkInterval ) ;
559
+ }
560
+ }
561
+ }
562
+
563
+
564
+
565
+ function getMonitoringData ( callback ) {
566
+ var modules = Object . keys ( config . monitoring ) ;
567
+ var redisCommands = [ ] ;
568
+ for ( var i in modules ) {
569
+ redisCommands . push ( [ 'hgetall' , getMonitoringDataKey ( modules [ i ] ) ] )
570
+ }
571
+ redisClient . multi ( redisCommands ) . exec ( function ( error , results ) {
572
+ var stats = { } ;
573
+ for ( var i in modules ) {
574
+ if ( results [ i ] ) {
575
+ stats [ modules [ i ] ] = results [ i ] ;
576
+ }
577
+ }
578
+ callback ( error , stats ) ;
579
+ } ) ;
580
+ }
581
+
582
+ function getLogFiles ( callback ) {
583
+ var dir = config . logging . files . directory ;
584
+ fs . readdir ( dir , function ( error , files ) {
585
+ var logs = { } ;
586
+ for ( var i in files ) {
587
+ var file = files [ i ] ;
588
+ var stats = fs . statSync ( dir + '/' + file ) ;
589
+ logs [ file ] = {
590
+ size : stats . size ,
591
+ changed : Date . parse ( stats . mtime ) / 1000 | 0
592
+ }
593
+ }
594
+ callback ( error , logs ) ;
595
+ } ) ;
596
+ }
597
+
409
598
var server = http . createServer ( function ( request , response ) {
410
599
411
600
if ( request . method . toUpperCase ( ) === "OPTIONS" ) {
@@ -426,12 +615,13 @@ var server = http.createServer(function(request, response){
426
615
427
616
switch ( urlParts . pathname ) {
428
617
case '/stats' :
429
- var reply = currentStatsCompressed ;
618
+ var deflate = request . headers [ 'accept-encoding' ] && request . headers [ 'accept-encoding' ] . indexOf ( 'deflate' ) != - 1 ;
619
+ var reply = deflate ? currentStatsCompressed : currentStats ;
430
620
response . writeHead ( "200" , {
431
621
'Access-Control-Allow-Origin' : '*' ,
432
622
'Cache-Control' : 'no-cache' ,
433
623
'Content-Type' : 'application/json' ,
434
- 'Content-Encoding' : 'deflate' ,
624
+ 'Content-Encoding' : deflate ? 'deflate' : ' ',
435
625
'Content-Length' : reply . length
436
626
} ) ;
437
627
response . end ( reply ) ;
@@ -462,21 +652,45 @@ var server = http.createServer(function(request, response){
462
652
case '/admin_stats' :
463
653
if ( ! authorize ( request , response ) )
464
654
return ;
465
- log ( 'warn' , logSystem , 'Admin authorized' ) ;
466
655
handleAdminStats ( response ) ;
467
656
break ;
657
+ case '/admin_monitoring' :
658
+ if ( ! authorize ( request , response ) ) {
659
+ return ;
660
+ }
661
+ handleAdminMonitoring ( response ) ;
662
+ break ;
663
+ case '/admin_log' :
664
+ if ( ! authorize ( request , response ) ) {
665
+ return ;
666
+ }
667
+ handleAdminLog ( urlParts , response ) ;
668
+ break ;
669
+ case '/admin_users' :
670
+ if ( ! authorize ( request , response ) ) {
671
+ return ;
672
+ }
673
+ handleAdminUsers ( response ) ;
674
+ break ;
675
+
676
+ case '/miners_hashrate' :
677
+ if ( ! authorize ( request , response ) )
678
+ return ;
679
+ handleGetMinersHashrate ( response ) ;
680
+ break ;
681
+
468
682
default :
469
683
response . writeHead ( 404 , {
470
684
'Access-Control-Allow-Origin' : '*'
471
685
} ) ;
472
686
response . end ( 'Invalid API call' ) ;
473
687
break ;
474
688
}
475
-
476
-
477
689
} ) ;
478
690
691
+ collectStats ( ) ;
692
+ initMonitoring ( ) ;
479
693
480
694
server . listen ( config . api . port , function ( ) {
481
695
log ( 'info' , logSystem , 'API started & listening on port %d' , [ config . api . port ] ) ;
482
- } ) ;
696
+ } ) ;
0 commit comments