├── .bowerrc ├── .gitignore ├── LICENSE ├── app ├── admin.html ├── admin.js ├── admin │ ├── addport.js │ ├── adminlogin.html │ ├── adminlogin.js │ ├── config.html │ ├── config.js │ ├── dashboard.html │ ├── dashboard.js │ ├── editconfig.html │ ├── editconfig.js │ ├── editport.html │ ├── editport.js │ ├── ports.html │ ├── ports.js │ ├── workers.html │ └── workers.js ├── app.css ├── app.js ├── assets │ ├── cloudsigma.png │ ├── ding.wav │ └── sponsor-logo.png ├── globals.default.js ├── globals.js ├── index.html ├── user │ ├── blocks │ │ ├── blocks.html │ │ └── blocks.js │ ├── dashboard │ │ ├── dashboard.html │ │ ├── dashboard.js │ │ ├── minerpayments.html │ │ ├── minerpayments.js │ │ └── poolstats.html │ ├── help │ │ ├── chat.html │ │ ├── chat.js │ │ ├── faq.html │ │ ├── faq.js │ │ ├── getting_started.html │ │ ├── getting_started.js │ │ ├── portsmodal.html │ │ └── portsmodal.js │ ├── home │ │ ├── console.html │ │ ├── console.js │ │ ├── home.html │ │ ├── home.js │ │ ├── login.html │ │ └── login.js │ ├── network │ │ ├── network.html │ │ └── network.js │ ├── payments │ │ ├── payments.html │ │ └── payments.js │ └── ports │ │ ├── ports.html │ │ └── ports.js ├── utils │ ├── dataservice.js │ ├── directives.js │ ├── services.js │ └── strings.js └── welcome.html ├── bower.json ├── gulpfile.js ├── package.json └── readme.md /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "app/vendor" 3 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | logs/* 2 | */.log 3 | !.gitkeep 4 | node_modules/ 5 | vendor/ 6 | tmp 7 | .DS_Store 8 | .idea 9 | app/vendor/ 10 | build/ 11 | app/globals.js -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Snipa22 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /app/admin.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | XMRPool.net - Mine XMR/Monero or BTC/Bitcoin 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 27 | 28 |
29 | 30 | 31 |
32 |

Pool Admin CP

33 |
34 |
35 | Logout 36 |
37 |
38 |
39 | 40 | 41 | 42 | Dashboard 43 | 44 | 45 | Workers 46 | 47 | 48 | Ports 49 | 50 | 51 | Config 52 | 53 | 54 | 55 | 56 | 57 | 58 |
59 |
60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /app/admin.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Declare app level module which depends on views, and components 4 | var app = angular.module('pooladmin', [ 5 | 'pool.globals', 6 | 'ngRoute', 7 | 'ngMaterial', 8 | 'md.data.table', 9 | 'ngStorage', 10 | 'angularMoment', 11 | 'utils.xhr', 12 | 'utils.strings' 13 | ]).config(['$locationProvider', '$routeProvider', '$mdThemingProvider', function($locationProvider, $routeProvider, $mdThemingProvider) { 14 | $locationProvider.hashPrefix(''); 15 | // $mdIconProvider.defaultIconSet("https://rawgit.com/angular/material-start/es5-tutorial/app/assets/svg/avatars.svg", 128) 16 | 17 | $mdThemingProvider.theme('default') 18 | .primaryPalette('grey') 19 | .accentPalette('light-blue'); 20 | 21 | $routeProvider 22 | .when('/login', { 23 | templateUrl: 'admin/adminlogin.html', 24 | controller: 'AdminLoginCtrl' 25 | }) 26 | .when('/dashboard', { 27 | templateUrl: 'admin/dashboard.html', 28 | controller: 'AdminDashCtrl' 29 | }) 30 | .when('/workers', { 31 | templateUrl: 'admin/workers.html', 32 | controller: 'AdminWorkersCtrl' 33 | }) 34 | .when('/ports', { 35 | templateUrl: 'admin/ports.html', 36 | controller: 'AdminPortsCtrl' 37 | }) 38 | .when('/config', { 39 | templateUrl: 'admin/config.html', 40 | controller: 'AdminConfigCtrl' 41 | }) 42 | 43 | $routeProvider.otherwise({redirectTo: '/login'}); 44 | 45 | }]); 46 | 47 | app.controller('AppCtrl', function($scope, $window, $route, $location, $interval, dataService, $localStorage, GLOBALS) { 48 | $scope.GLOBALS = GLOBALS; 49 | 50 | var loginCheck = function (){ 51 | if(!dataService.isLoggedIn()){ 52 | $location.path('#login'); 53 | } 54 | } 55 | 56 | $scope.isLoggedIn = function () { 57 | return dataService.isLoggedIn(); 58 | } 59 | 60 | $scope.logout = function () { 61 | dataService.logout(); 62 | $location.path('#login'); 63 | } 64 | }); -------------------------------------------------------------------------------- /app/admin/addport.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | app.controller('addPortCtrl', function ($scope, $mdDialog, dataService) { 5 | 'use strict'; 6 | 7 | this.cancel = function (){ 8 | //config.value=old_value; 9 | $mdDialog.cancel(); 10 | } 11 | 12 | this.edit = function () { 13 | $scope.item.form.$setSubmitted(); 14 | 15 | if($scope.item.form.$valid) { 16 | dataService.putData('/admin/ports', {id: config.id, value: config.value}, function(data) { 17 | $mdDialog.hide(data); 18 | }, function (e) { 19 | // error 20 | $scope.config = old_value; 21 | $mdDialog.hide('error'); 22 | }); 23 | } 24 | }; 25 | 26 | }); -------------------------------------------------------------------------------- /app/admin/adminlogin.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |

Pool Admin Login

5 | 6 |
7 |
8 | 9 | 10 | 11 |
12 |
13 | 14 | 15 | 16 | person 17 | 18 | 19 | 20 | 21 | 22 | lock 23 | 24 | 25 |
26 | 27 |
28 | 29 | 30 | 31 | Remember me 32 | 33 | 34 |

{{status}}

35 | 36 | Login 37 | 38 |
39 |
-------------------------------------------------------------------------------- /app/admin/adminlogin.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | app.controller('AdminLoginCtrl', function($scope, $location, $route, dataService) { 4 | $scope.admin = { 5 | username:"", 6 | password:"" 7 | } 8 | 9 | $scope.login = function () { 10 | dataService.postData("/authenticate", $scope.admin, function(data){ 11 | if (data.success){ 12 | data.remember = $scope.remember; 13 | dataService.setAuthToken(data); 14 | $location.path('#/dashboard'); 15 | } else { 16 | // $mdDialog.hide(false); 17 | } 18 | }, function(error){ 19 | $scope.status = "Please check your login details"; 20 | }); 21 | } 22 | 23 | var isLoggedIn = function () { 24 | if(dataService.isLoggedIn == false) ; 25 | } 26 | 27 | if(dataService.isLoggedIn()) { 28 | $location.path('/dashboard'); 29 | }; 30 | }); -------------------------------------------------------------------------------- /app/admin/config.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | Pool Config 5 | 6 | 9 | 10 |
11 |
12 | 13 | 14 |
15 | {{selected.length}} {{selected.length > 1 ? 'items' : 'item'}} selected 16 | 17 | 18 |
19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 |

ID

Item

Value

Type

Module

35 | 36 | mode_edit 37 | 38 | {{config.id}}{{config.item}}{{config.value}}{{config.type}}{{config.module}}
47 |
48 |
-------------------------------------------------------------------------------- /app/admin/config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | app.controller('AdminConfigCtrl', function($scope, $location, $route, $mdDialog, dataService) { 4 | $scope.selected = []; 5 | 6 | var loadConfig = function () { 7 | dataService.getData("/admin/config", function(data) { 8 | $scope.pool_configs = data; 9 | }); 10 | } 11 | 12 | $scope.editConfig = function (ev, config) { 13 | $mdDialog.show({ 14 | locals: { 15 | config: config 16 | }, 17 | clickOutsideToClose: true, 18 | controller: 'editConfigCtrl', 19 | controllerAs: 'ctrl', 20 | focusOnOpen: false, 21 | targetEvent: ev, 22 | templateUrl: 'admin/editconfig.html', 23 | }).then (function () { 24 | loadConfig(); 25 | }, function(){ 26 | // error 27 | }) 28 | 29 | }; 30 | 31 | loadConfig(); 32 | }); -------------------------------------------------------------------------------- /app/admin/dashboard.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 |
6 |

{{type}}

7 |
8 |
9 | 10 | 11 | 12 |

Owed

13 |

{{stats.owed | toXMR | number }} XMR

14 |
15 | 16 |

Paid

17 |

{{stats.paid | toXMR | number }} XMR

18 |
19 | 20 |

Mined

21 |

{{stats.mined | toXMR | number}} XMR

22 |
23 | 24 |

Shares

25 |

{{stats.shares | number }}

26 |
27 | 28 |

Target Shares

29 |

{{stats.targetShares | number}}

30 |
31 |
32 |
33 |
34 | 35 | 36 | 37 |
38 |

Wallet

39 |
40 |
41 | 42 |
43 |
44 |
45 |

Height

{{ pool_wallet.height | number }}

46 |
47 |
48 |

Unlocked

{{ pool_wallet.unlocked | toXMR }} XMR

49 |
50 |
51 |
52 |
53 |

Balance

{{ pool_wallet.balance | toXMR }} XMR

54 |
55 |
56 |

Timestamp

{{ pool_wallet.ts | date }}

57 |
58 |
59 |
60 |
61 |
62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 87 | 88 | 89 |

Height

Unlocked

Balanced

Timestamp

{{history.height | number}}

{{history.balance | toXMR }} XMR

{{history.unlocked | toXMR }} XMR

82 |

83 | 84 | ({{history.ts | date:'hh:mm:ss dd/MM/yy'}}) 85 | 86 |
90 |
91 | 92 |
93 |
94 |
95 |
96 | 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /app/admin/dashboard.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | app.controller('AdminDashCtrl', function($scope, $location, $route, dataService) { 4 | $scope.selected = []; 5 | 6 | dataService.getData("/admin/stats", function(data) { 7 | $scope.pool_stats = data; 8 | }); 9 | 10 | dataService.getData("/admin/wallet", function(data) { 11 | $scope.pool_wallet = data; 12 | }); 13 | 14 | $scope.promise = dataService.getData("/admin/wallet/history", function(data) { 15 | $scope.pool_wallet_history = data; 16 | }); 17 | }); -------------------------------------------------------------------------------- /app/admin/editconfig.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |

Edit Config

5 | 6 |

7 | error_outline 8 |   All fields are required. 9 |

10 | 11 |
12 |
13 | 14 | 15 | 16 | 17 |
18 |
19 | 20 |
21 | 22 | 23 | Save 24 | Cancel 25 | 26 | 27 |
-------------------------------------------------------------------------------- /app/admin/editconfig.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | app.controller('editConfigCtrl', function ($scope, $mdDialog, dataService, config) { 5 | 'use strict'; 6 | 7 | $scope.config = config; 8 | var old_value = config.value; 9 | 10 | this.cancel = function (){ 11 | config.value=old_value; 12 | $mdDialog.cancel(); 13 | } 14 | 15 | this.edit = function () { 16 | $scope.item.form.$setSubmitted(); 17 | 18 | if($scope.item.form.$valid) { 19 | dataService.putData('/admin/config', {id: config.id, value: config.value}, function(data) { 20 | $mdDialog.hide(data); 21 | }, function (e) { 22 | // error 23 | $scope.config = old_value; 24 | $mdDialog.hide('error'); 25 | }); 26 | } 27 | }; 28 | 29 | }); -------------------------------------------------------------------------------- /app/admin/editport.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |

Edit Port

5 | 6 |

7 | error_outline 8 |   All fields are required. 9 |

10 |
11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | PPLNS 28 | PPS 29 | SOLO 30 | 31 | 32 | 33 | 34 | Hidden 35 | 36 | 37 | 38 | 39 | SSL 40 | 41 | 42 |
43 |
44 | 45 |
46 | 47 | 48 | Save 49 | Cancel 50 | 51 | 52 |
-------------------------------------------------------------------------------- /app/admin/editport.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | app.controller('editPortCtrl', function ($scope, $mdDialog, dataService, port) { 5 | 'use strict'; 6 | var old_value = angular.copy(port); 7 | 8 | port.hidden = (port.hidden) ? 1 : 0; 9 | port.ssl = (port.ssl) ? 1 : 0; 10 | 11 | $scope.port = port; 12 | 13 | this.cancel = function (){ 14 | angular.copy(old_value, port); 15 | $mdDialog.cancel(); 16 | } 17 | 18 | this.edit = function () { 19 | $scope.item.form.$setSubmitted(); 20 | 21 | if($scope.item.form.$valid) { 22 | dataService.putData('/admin/ports', $scope.port, function(data) { 23 | $mdDialog.hide(data); 24 | }, function (e) { 25 | // error 26 | $scope.port = old_value; 27 | $mdDialog.hide('error'); 28 | }); 29 | } 30 | }; 31 | 32 | }); -------------------------------------------------------------------------------- /app/admin/ports.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | Ports 5 | 6 | 7 | add 8 | Add Port 9 | 10 | 11 |
12 |
13 | 14 | 15 |
16 | {{selected.length}} {{selected.length > 1 ? 'items' : 'item'}} selected 17 | 18 |
19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 41 | 42 | 43 | 44 | 45 | 53 | 61 | 66 | 67 | 68 |

Port

Difficulty

Description

Type

Hidden

SSL

37 | 38 | mode_edit 39 | 40 | {{port.port}}{{port.diff}}{{port.desc}}{{port.portType}} 46 | 47 | done 48 | 49 | 50 | clear 51 | 52 | 54 | 55 | done 56 | 57 | 58 | clear 59 | 60 | 62 | 63 | delete 64 | 65 |
69 |
70 |
-------------------------------------------------------------------------------- /app/admin/ports.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | app.controller('AdminPortsCtrl', function($scope, $location, $route, $mdDialog, dataService) { 4 | $scope.selected = []; 5 | 6 | var loadPorts = function() { 7 | dataService.getData("/admin/ports", function(data) { 8 | $scope.pool_ports = data; 9 | }); 10 | } 11 | 12 | $scope.editPort = function (ev, port) { 13 | $mdDialog.show({ 14 | locals: { 15 | port: port 16 | }, 17 | clickOutsideToClose: true, 18 | controller: 'editPortCtrl', 19 | controllerAs: 'ctrl', 20 | focusOnOpen: true, 21 | targetEvent: ev, 22 | templateUrl: 'admin/editport.html', 23 | }).then (function () { 24 | loadPorts(); 25 | }, function(msg){ 26 | port=msg; 27 | }) 28 | }; 29 | 30 | $scope.addPort = function(ev){ 31 | $mdDialog.show({ 32 | clickOutsideToClose: true, 33 | controller: 'addPortCtrl', 34 | controllerAs: 'ctrl', 35 | focusOnOpen: true, 36 | targetEvent: ev, 37 | templateUrl: 'admin/editport.html', 38 | }).then (function () { 39 | loadPorts(); 40 | }, function(){ 41 | // error 42 | }) 43 | } 44 | 45 | $scope.deletePort = function (ev, port) { 46 | console.log(port); 47 | var confirm = $mdDialog.confirm() 48 | .title('Delete Port ' + port.port +' ?') 49 | .textContent('Are you sure you want to get delete port ' + port.port + '?') 50 | .ariaLabel('Delete Port') 51 | .targetEvent(ev) 52 | .ok("Delete") 53 | .cancel("Cancel"); 54 | 55 | $mdDialog.show(confirm).then(function() { 56 | // ;p 57 | dataService.deleteData("/admin/ports", {port: port.port}, function(data){ 58 | // successfully deleted 59 | loadPorts(); 60 | }); 61 | }, function() { 62 | // cancel do nothing 63 | }); 64 | }; 65 | 66 | loadPorts(); 67 | }); -------------------------------------------------------------------------------- /app/admin/workers.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 34 | 35 | 36 |

Hashrate

Address

Paid

Due

Total #s

Good #s

Bad #s

Workers

Last Hash

{{worker.hashRate | toHashRate}}{{worker.address}}{{worker.paid | toXMR}} XMR{{worker.due | toXMR }} XMR{{worker.totalHashes | number }}{{worker.goodShares | number }}{{worker.badShares | number }}{{worker.workers.length}} 29 |

30 | 31 | {{worker.lastHash | date:'hh:mm:ss dd/MM/yy'}} 32 | 33 |
37 |
38 | 39 |
-------------------------------------------------------------------------------- /app/admin/workers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | app.controller('AdminWorkersCtrl', function($scope, $location, $route, dataService) { 4 | $scope.selected = []; 5 | 6 | $scope.promise = dataService.getData("/admin/userList", function(data) { 7 | $scope.pool_workers = data; 8 | }); 9 | }); -------------------------------------------------------------------------------- /app/app.css: -------------------------------------------------------------------------------- 1 | /*** Global Styles ***/ 2 | 3 | html, body { 4 | font-family: 'Source Sans Pro', sans-serif; 5 | font-size:14px; 6 | margin: 0px; 7 | padding: 0px; 8 | min-width: 420px; 9 | } 10 | 11 | a { 12 | transition: color .4s; 13 | color: #3287b3; 14 | } 15 | 16 | a:link { text-decoration: none; }, 17 | a:visited { color: #3b98c8; } 18 | a:hover { color: #3b98c8; } 19 | a:active { 20 | transition: color .3s; 21 | color: #007BE6; 22 | } 23 | 24 | /*** Material Styles ***/ 25 | #main { 26 | background: #f3f5f7; 27 | } 28 | 29 | .maintoolbar { 30 | background-color: #fff; 31 | } 32 | 33 | 34 | .statsbar md-card-title-text{ 35 | text-align: right; 36 | } 37 | 38 | .statsbar md-card md-card-title { 39 | padding: 12px ; 40 | } 41 | 42 | .md-menu-toolbar { 43 | background: #00796B; 44 | } 45 | 46 | md-tooltip .md-content { 47 | height: auto !important; 48 | } 49 | 50 | .navbar .smallfont h3{ 51 | font-size: 0.8em; 52 | } 53 | 54 | .navbar .smallfont h3 b{ 55 | display: inline-block; 56 | } 57 | 58 | .sidenav { 59 | background-color: #2b333e; 60 | } 61 | 62 | .sidenav ul{ 63 | list-style: none; 64 | } 65 | 66 | .sidenav md-list-item { 67 | padding: 0px; 68 | } 69 | 70 | .sidenav a { 71 | width: 100%; 72 | text-align: left; 73 | color: #fff; 74 | margin: 0px; 75 | } 76 | 77 | .sidebar { 78 | display: flex; 79 | flex-wrap: nowrap; 80 | box-sizing: border-box; 81 | } 82 | 83 | .sidebar a { 84 | color: rgb(66,66,66); 85 | text-decoration: none; 86 | margin: 0; 87 | font-size: 16px; 88 | font-weight: 400; 89 | line-height: 24px; 90 | letter-spacing: 0; 91 | opacity: 0.87; 92 | } 93 | 94 | .sidebar md-icon, .sidebar ng-md-icon { 95 | vertical-align: middle; 96 | padding-left: 40px; 97 | } 98 | 99 | #content { 100 | padding: 40px; 101 | } 102 | 103 | .logo { 104 | background: #333d4b !important; 105 | color: #fff !important; 106 | } 107 | 108 | .logo i { 109 | color: #00b0ff; 110 | font-weight: 700; 111 | } 112 | 113 | /* HELPERS */ 114 | .text-left { 115 | text-align: left; 116 | } 117 | 118 | .text-right { 119 | text-align: right; 120 | } 121 | 122 | .text-center { 123 | text-align: center; 124 | } 125 | 126 | .truncate{ 127 | width:30%; 128 | white-space:nowrap; 129 | overflow:hidden; 130 | text-overflow:ellipsis 131 | } 132 | 133 | .hide-error-msg .md-errors-spacer{ 134 | display: none 135 | } 136 | 137 | .menu-item { 138 | position: relative; 139 | width: 72px; 140 | height: 72px; 141 | display: inline-block; 142 | overflow: hidden; 143 | margin: 0px; 144 | vertical-align: middle; 145 | zoom:0.6; 146 | transform: translateZ(0); 147 | -webkit-transform: scale(0.60); 148 | -moz-transform:scale(0.60); 149 | font-size: 72px !important; 150 | color: #aeb7c2; 151 | } 152 | 153 | .valid { 154 | color: green !important; 155 | } 156 | 157 | .invalid { 158 | color: red !important; 159 | } 160 | 161 | .metric md-icon { 162 | font-size: 32px; 163 | } 164 | 165 | .navbar .login { 166 | color: #fff !important; 167 | } 168 | 169 | /* CHARTS */ 170 | 171 | .chart-legend { 172 | display: none; 173 | } 174 | 175 | .chartcontainer { 176 | width: 100% !important; 177 | height: 300px; 178 | } 179 | 180 | .chartcontainer .ng-isolate-scope { 181 | height:300px; 182 | } 183 | 184 | /* LOGIN WINDOW */ 185 | md-dialog .login{ 186 | min-width: 800px !important; 187 | } 188 | 189 | .console { 190 | height: 300px; 191 | min-width: 500px; 192 | } 193 | 194 | .no-padding { 195 | padding: 0px; 196 | } 197 | 198 | .power { 199 | width: 100%; 200 | color: #fff; 201 | } 202 | 203 | .power a { 204 | color: #f2ff2f; 205 | } 206 | 207 | .power a:hover { 208 | color: #f2f; 209 | } 210 | 211 | .power a.alt { 212 | color: #f2f; 213 | } 214 | 215 | .power a.alt:hover { 216 | color: #f2ff2f; 217 | } 218 | 219 | .fixed { 220 | position: fixed; 221 | } 222 | 223 | .theironfist { 224 | cursor: pointer; 225 | } -------------------------------------------------------------------------------- /app/app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Declare app level module which depends on views, and components 4 | var app = angular.module('poolui', [ 5 | 'pool.globals', 6 | 'ngRoute', 7 | 'ngMaterial', 8 | 'md.data.table', 9 | 'angularMoment', 10 | 'ngStorage', 11 | 'ngAudio', 12 | 'utils.strings', 13 | 'utils.services', 14 | 'utils.xhr', 15 | 'n3-line-chart', 16 | 'angular-page-visibility' 17 | ]).config(['$locationProvider', '$routeProvider', '$mdThemingProvider', function($locationProvider, $routeProvider, $mdThemingProvider) { 18 | $locationProvider.hashPrefix('') 19 | ; 20 | $mdThemingProvider.theme('default') 21 | .primaryPalette('grey') 22 | .accentPalette('light-blue'); 23 | 24 | $routeProvider 25 | .when('/home', { 26 | templateUrl: 'user/home/home.html', 27 | controller: 'HomeCtrl', 28 | activetab: 'home' 29 | }) 30 | .when('/dashboard', { 31 | templateUrl: 'user/dashboard/dashboard.html', 32 | controller: 'DashboardCtrl', 33 | activetab: 'dashboard' 34 | }) 35 | .when('/blocks', { 36 | templateUrl: 'user/blocks/blocks.html', 37 | controller: 'BlocksCtrl', 38 | activetab: 'blocks' 39 | }) 40 | .when('/payments', { 41 | templateUrl: 'user/payments/payments.html', 42 | controller: 'PaymentsCtrl', 43 | activetab: 'payments' 44 | }) 45 | .when('/network', { 46 | templateUrl: 'user/network/network.html', 47 | controller: 'NetworkCtrl', 48 | activetab: 'network' 49 | }) 50 | .when('/ports', { 51 | templateUrl: 'user/ports/ports.html', 52 | controller: 'PortsCtrl', 53 | activetab: 'ports' 54 | }) 55 | .when('/help/chat', { 56 | templateUrl: 'user/help/chat.html', 57 | controller: 'ChatCtrl', 58 | activetab: 'support' 59 | }) 60 | .when('/help/getting_started', { 61 | templateUrl: 'user/help/getting_started.html', 62 | controller: 'GettingStartedCtrl', 63 | activetab: 'help' 64 | }) 65 | .when('/help/faq', { 66 | templateUrl: 'user/help/faq.html', 67 | controller: 'FAQCtrl', 68 | activetab: 'help' 69 | }); 70 | 71 | $routeProvider.otherwise({redirectTo: '/home'}); 72 | 73 | }]); 74 | 75 | app.controller('AppCtrl', function($scope, $rootScope, $location, $route, $routeParams, $anchorScroll, $window, $interval, $mdDialog, dataService, timerService, addressService, $mdSidenav, $mdMedia, $localStorage, ngAudio, GLOBALS){ 76 | $scope.GLOBALS = GLOBALS; 77 | var appCache = window.applicationCache; 78 | $scope.$storage = $localStorage; 79 | 80 | $scope.poolList = ["pplns", "pps", "solo"]; 81 | $scope.poolStats = {}; // All Pool stats 82 | $scope.addrStats = {}; // All tracked addresses 83 | $scope.lastBlock = {}; 84 | 85 | // for miner tracking 86 | $scope.yourTotalHashRate = 0; 87 | 88 | // Hashrate Alarm 89 | $scope.globalSiren = false; 90 | $scope.sirenAudio = ngAudio.load("assets/ding.wav"); 91 | 92 | // Update global hashrate and set off alarm if any of the tracked addresses fall below the threshold 93 | var updateHashRate = function (addrStats){ 94 | var totalHashRate = 0; 95 | var siren = false; 96 | 97 | _.each(addrStats, function(addr,index) { 98 | totalHashRate += addr.hash; 99 | if (addr.alarm && addr.hash < addr.alarmLimit) { 100 | siren=true; 101 | } 102 | }); 103 | 104 | $scope.globalSiren=siren; 105 | $scope.yourTotalHashRate = totalHashRate; 106 | } 107 | 108 | var playSiren = function (){ 109 | ($scope.globalSiren) ? $scope.sirenAudio.play() : $scope.sirenAudio.stop(); 110 | } 111 | 112 | // ------- UI HELPERS 113 | 114 | $scope.menuOpen = $mdMedia('gt-md'); 115 | $scope.$watch(function() { return $mdMedia('gt-md'); }, function(big) { 116 | $scope.menuOpen = $mdMedia('gt-md'); 117 | }); 118 | 119 | $scope.toggleSidenav = function (){ 120 | if (!$mdMedia('gt-md')) { 121 | $mdSidenav('left').toggle(); 122 | } else { 123 | // toggle boolean 124 | $scope.menuOpen = !$scope.menuOpen; 125 | } 126 | } 127 | 128 | // ------- Miner Login and auth 129 | $scope.minerLogin = function (ev) { 130 | $mdDialog.show({ 131 | controller: "LoginCtrl", 132 | templateUrl: 'user/home/login.html', 133 | parent: angular.element(document.body), 134 | targetEvent: ev, 135 | clickOutsideToClose:true, 136 | fullscreen: !$scope.menuOpen // Only for -xs, -sm breakpoints. 137 | }) 138 | .then(function(answer) { 139 | // success callback 140 | }, function(error) { 141 | // error callback 142 | }); 143 | } 144 | 145 | $scope.minerConsole = function (ev) { 146 | $mdDialog.show({ 147 | locals: $scope.config, 148 | controller: "ConsoleCtrl", 149 | templateUrl: 'user/home/console.html', 150 | parent: angular.element(document.body), 151 | targetEvent: ev, 152 | clickOutsideToClose:true, 153 | fullscreen: !$scope.menuOpen // Only for -xs, -sm breakpoints. 154 | }) 155 | .then(function(answer){ 156 | if(answer=='logout'){ 157 | dataService.logout(); 158 | } 159 | }, function(reason){ 160 | // console.log(reason); 161 | }); 162 | } 163 | 164 | $scope.isLoggedIn = function() { 165 | return dataService.isLoggedIn(); 166 | } 167 | 168 | // ------- App Update 169 | var update = function() { 170 | if (appCache.status == window.applicationCache.UPDATEREADY) { 171 | appCache.swapCache(); 172 | $window.location.reload(); 173 | } 174 | } 175 | 176 | appCache.addEventListener("updateready", function(event) { 177 | update(); 178 | }, false); 179 | 180 | var updateCache = function () { 181 | appCache.update(); 182 | } 183 | 184 | // API Requests 185 | var loadData = function () { 186 | dataService.getData("/pool/stats", function(data){ 187 | $scope.poolList = data.pool_list; 188 | $scope.poolStats.global = data.pool_statistics; 189 | }); 190 | 191 | dataService.getData("/network/stats", function(data){ 192 | $scope.network = data; 193 | }); 194 | } 195 | 196 | var loadOnce = function () { 197 | dataService.getData("/config", function(data){ 198 | $scope.config = data; 199 | }); 200 | } 201 | 202 | // For FAQ 203 | $rootScope.$on('$routeChangeSuccess', function(newRoute, oldRoute) { 204 | $location.hash($routeParams.scrollTo); 205 | $anchorScroll(); 206 | }); 207 | 208 | // Start doing things 209 | loadOnce(); 210 | loadData(); 211 | update(); 212 | 213 | // Start the timer and register global requests 214 | timerService.startTimer(GLOBALS.api_refresh_interval); 215 | timerService.register(loadData, 'global'); 216 | $interval(updateCache, GLOBALS.app_update_interval); // check for app updates every 5 mins 217 | 218 | // Start address tracking servuce after starting timer, only one callback supported at a time 219 | addressService.start(function(addrStats) { 220 | $scope.addrStats = addrStats; 221 | updateHashRate(addrStats); 222 | playSiren(); 223 | } 224 | ); 225 | 226 | 227 | // Sponsor 228 | $scope.sponsor_open = false 229 | 230 | }); -------------------------------------------------------------------------------- /app/assets/cloudsigma.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mesh0000/poolui/4e8c550cb2617d24b55f4049da1eb0a1479560bb/app/assets/cloudsigma.png -------------------------------------------------------------------------------- /app/assets/ding.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mesh0000/poolui/4e8c550cb2617d24b55f4049da1eb0a1479560bb/app/assets/ding.wav -------------------------------------------------------------------------------- /app/assets/sponsor-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mesh0000/poolui/4e8c550cb2617d24b55f4049da1eb0a1479560bb/app/assets/sponsor-logo.png -------------------------------------------------------------------------------- /app/globals.default.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('pool.globals', []) 4 | 5 | .factory('GLOBALS', function() { 6 | return { 7 | pool_name: "XMRPool.net", 8 | api_url : 'https://api.xmrpool.net', 9 | api_refresh_interval: 5000, 10 | app_update_interval: 30*60000 11 | }; 12 | }); -------------------------------------------------------------------------------- /app/globals.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('pool.globals', []) 4 | 5 | .factory('GLOBALS', function() { 6 | return { 7 | pool_name: "XMRPool.net", 8 | api_url : 'https://api.xmrpool.net', 9 | api_refresh_interval: 5000, 10 | app_update_interval: 5*60000 11 | }; 12 | }); -------------------------------------------------------------------------------- /app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | XMRPool.net - Mine XMR/Monero or BTC/Bitcoin 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 27 | 28 |
29 | 30 | 33 | 34 | 35 | 36 | home 37 | Home 38 | 39 | 40 | 41 | 42 | dashboard 43 | Dashboard 44 | 45 | 46 | 47 | 48 | reorder 49 | Blocks 50 | 51 | 52 | 53 | 54 | payments 55 | Payment 56 | 57 | 58 | 59 | 60 | flight_land 61 | Ports 62 | 63 | 64 | 65 | 66 | language 67 | Network 68 | 69 | 70 | 71 | 72 | group 73 | Support 74 | 75 | 76 | 77 | 78 | launch 79 | Getting Started 80 | 81 | 82 | 83 | 84 | help_outline 85 | FAQ 86 | 87 | 88 | 89 |
90 |
91 |
92 | Powered by nodejs-pool & poolui 93 |
94 | MIT Licence 95 |
96 |
97 |
98 | 99 | 100 | 101 |
102 | 103 | menu 104 | 105 |

Network : {{network.difficulty | difficultyToHashRate | toHashRate}}

106 |

Pool : {{poolStats.global.hashRate | toHashRate}}

107 |

You : {{yourTotalHashRate | toHashRate}}

108 |
109 | 110 | 111 | Login 112 | 113 | 114 | 115 | 116 |
117 |
118 |
119 |
120 |
121 |
122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | -------------------------------------------------------------------------------- /app/user/blocks/blocks.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 | 5 | 6 |
7 | Blocks Found 8 |
9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 34 | 40 | 41 | 42 | 43 | 44 | 45 | 57 | 58 | 59 | 60 |

Valid

Time Found

Height

Difficulty

Hash

Shares

Luck

Maturity

Pool

30 | 31 | {{::block.icon}} 32 | 33 | 35 |

36 | 37 | {{::block.ts | date:"hh:mm:ss dd/MM/yy"}} 38 | 39 |

{{::block.height | number}}

{{::block.diff | number}}

{{::block.shares | number}}

{{::block.luck | number:2}} %

46 | 47 |

48 | lock_open 49 | 50 | {{-block.maturity}} blocks ago 51 | 52 |

53 |

54 | {{block.maturity}} to go 55 |

56 |

{{::block.pool_type}}

61 |
62 | 63 |
64 |
65 |
66 |
-------------------------------------------------------------------------------- /app/user/blocks/blocks.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | app.controller('BlocksCtrl', function($scope, $route, dataService, timerService) { 4 | $scope.blocks = {}; 5 | $scope.selected = []; 6 | 7 | $scope.options = { 8 | page: 1, 9 | limit: 15 10 | } 11 | 12 | $scope.loadBlocks = function () { 13 | var params = angular.copy($scope.options); 14 | params.page -= 1; 15 | var urlParams = $.param(params) 16 | $scope.promise = dataService.getData("/pool/blocks?"+urlParams, function(data){ 17 | $scope.blocks.global = data; 18 | updateMaturity(); 19 | }); 20 | }; 21 | 22 | var updateMaturity = function () { 23 | var luck; 24 | if($scope.poolStats.global !== undefined){ 25 | _.each($scope.blocks.global, function(block, index){ 26 | if($scope.network !== undefined) { 27 | $scope.blocks.global[index].maturity = $scope.config.maturity_depth - ($scope.network.height - block.height); 28 | } 29 | 30 | // calculate luck 31 | luck = block.shares/block.diff*100; 32 | $scope.blocks.global[index].luck = (luck <= 100) ? (100-luck) : (-luck+100) ; 33 | $scope.blocks.global[index].icon = (block.valid) ? 'done' : 'clear'; 34 | }); 35 | } 36 | } 37 | 38 | $scope.$watchGroup(["blocks.global", "poolStats.global"], updateMaturity); 39 | 40 | // Register call with timer 41 | timerService.register($scope.loadBlocks, $route.current.controller); 42 | $scope.loadBlocks(); 43 | 44 | $scope.$on("$routeChangeStart", function () { 45 | timerService.remove($route.current.controller); 46 | }); 47 | }); -------------------------------------------------------------------------------- /app/user/dashboard/dashboard.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 | 5 | 6 |
7 | Network Stats 8 |
9 |
10 | 11 | 12 | 13 |

Hash Rate

14 |

{{network.difficulty | difficultyToHashRate | toHashRate}}

15 |
16 | 17 |

Difficulty

18 |

{{network.difficulty | number }}

19 |
20 | 21 |

Hash

22 |

23 |
24 | 25 |

Height

26 |

{{network.height | number }}

27 |
28 | 29 |

Reward

30 |

{{network.value | toXMR}}

31 |
32 | 33 |

Time Found

34 |

35 |
36 |
37 |
38 |
39 |
40 |
41 | 42 | 43 |
44 | Pool Stats 45 |
46 |
47 | 48 | 49 | 50 |
51 |
52 | 53 | 54 |

Hash Rate

55 |

{{ poolStats[pooltype].pool_statistics.hashRate | toHashRate }}

56 |
57 | 58 |

Height

59 |

{{poolStats[pooltype].pool_statistics.lastBlockFound | number}}

60 |
61 | 62 |

Last Block

63 |

64 |
65 | 66 |

Block Reward

67 |

{{lastBlock[pooltype].value | toXMR | number:10}} XMR

68 |
69 | 70 |

Time Found

71 |

72 | Never 73 | 74 | {{poolStats[pooltype].pool_statistics.lastBlockFoundTime*1000 | date:'hh:mm:ss dd/MM/yy'}} 75 | 76 |

77 |
78 |
79 |
80 |
81 | 82 | 83 |

Fees

84 |

{{ poolStats[pooltype].pool_statistics.fee }} %

85 |
86 | 87 |

Blocks Found

88 |

{{ poolStats[pooltype].pool_statistics.totalBlocksFound || '0' }}

89 |
90 | 91 |

Miners

92 |

{{ poolStats[pooltype].pool_statistics.miners || '0' }}

93 |
94 | 95 |

Miners Paid

96 |

{{ poolStats[pooltype].pool_statistics.totalMinersPaid || '0' }}

97 |
98 | 99 |

Payments sent

100 |

{{ poolStats[pooltype].pool_statistics.totalPayments || '0' }}

101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 | 112 |
113 |
114 | 115 | 116 | 117 | account_balance_wallet 118 | 119 | 120 |
121 | 122 | add 123 | Track Live Stats 124 | 125 |
126 |
127 |
128 | 129 |
130 | 131 | 132 |
133 | 134 | 135 | 136 | account_balance 137 | 138 | 139 | {{addr}} 140 | 141 | 142 | 143 | ( Last Hash : Never ) 144 | 145 | {{miner.lastHash*1000 | date:'hh:mm:ss dd/MM/yy'}} 146 | 147 | 148 | 149 | 150 | 151 | clear 152 | 153 | 154 |
155 |
156 | 157 |
158 |
159 |
160 |

Hash Rate

{{miner.hash | toHashRate}}

161 |
162 |
163 |

Total Hashes

{{miner.totalHashes | number}}

164 |
165 |
166 |
167 |
168 |
Total Due

{{miner.amtDue | toXMR | number:10}} XMR

169 |
170 |
171 |
Total Paid

{{miner.amtPaid | toXMR | number:10}} XMR

172 |
173 |
174 |
175 |
176 | 177 |
178 | 179 |
180 |
181 | 182 |
183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 204 | 205 | 206 |

Worker

#'s

Total #'s

Last Hash

{{id}}

{{minerStats[addr].dataset[id][0].hs | toHashRate}}

{{addrStats[addr].workerStats[id].totalHash | number}}

199 | 200 | 201 | {{minerStats[addr].dataset[id][0].ts | date: 'hh:mm:ss dd/MM/yy'}} 202 | 203 |
207 |
208 |
209 |
210 | 211 |
212 |
213 |
214 | 215 |
216 |
217 |

Valid Shares

{{miner.validShares | number}} check

218 |

Invalid Shares

{{miner.invalidShares | number}} clear

219 |
220 |
221 |
222 |
223 | 224 | 225 |
226 |
227 | 228 | 229 | 230 | 231 | 232 | alarm 233 | 234 |
235 |
236 | 237 | View Payments 238 |
239 |
240 |
-------------------------------------------------------------------------------- /app/user/dashboard/dashboard.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | app.controller('DashboardCtrl', function($scope , $route, $mdDialog, $pageVisibility, dataService, timerService, addressService, minerService) { 4 | $scope.minerStats = {}; 5 | 6 | $scope.updateCharts = function (){ 7 | minerService.updateStats($scope.addrStats, function(minerStats){ 8 | $scope.minerStats = minerStats; 9 | }); 10 | } 11 | 12 | // Update miners everyime addrStats 13 | $scope.$parent.$watch('addrStats', function(newValue, oldValue) { 14 | $scope.updateCharts(); 15 | }); 16 | 17 | $scope.addAddress = function (){ 18 | if ($scope.paymentAddress){ 19 | addressService.trackAddress($scope.paymentAddress); 20 | $scope.paymentAddress = ""; 21 | } 22 | }; 23 | 24 | $scope.deleteAddress = function (key, ev){ 25 | var confirm = $mdDialog.confirm() 26 | .title('Hide live stats?') 27 | .textContent('You can add it back by entering your wallet address again') 28 | .ariaLabel('Stop tracking payment address') 29 | .targetEvent(ev) 30 | .ok("Remove") 31 | .cancel("Cancel"); 32 | 33 | $mdDialog.show(confirm).then(function() { 34 | addressService.deleteAddress(key); 35 | }, function() { 36 | // cancel do nothing 37 | }); 38 | } 39 | 40 | $scope.setAlarm = function(addr, bool){ 41 | addressService.setAlarm(addr, bool); 42 | }; 43 | 44 | $scope.viewPayments = function(ev, miner, addr){ 45 | $mdDialog.show({ 46 | locals: { 47 | miner: miner, 48 | addr: addr 49 | }, 50 | controller: "MinerPaymentsCtrl", 51 | templateUrl: 'user/dashboard/minerpayments.html', 52 | parent: angular.element(document.body), 53 | targetEvent: ev, 54 | clickOutsideToClose:true, 55 | fullscreen: !$scope.menuOpen 56 | }) 57 | .then(function(answer) { 58 | $scope.status = 'You said the information was "' + answer + '".'; 59 | }, function() { 60 | $scope.status = 'You cancelled the dialog.'; 61 | }); 62 | } 63 | 64 | // Recurring API calls and timer 65 | var loadData = function () { 66 | _.each($scope.poolList, function(pool_type) { 67 | dataService.getData("/pool/stats/"+pool_type, function(data){ 68 | $scope.poolStats[pool_type] = data; 69 | }); 70 | 71 | dataService.getData("/pool/blocks/"+pool_type, function(data){ 72 | if (data.length > 0){ 73 | $scope.lastBlock[pool_type] = data[0]; 74 | } else { 75 | $scope.lastBlock[pool_type] = { 76 | ts: 0, 77 | hash: "", 78 | diff: 0, 79 | shares: 0, 80 | height: 0, 81 | valid: false, 82 | unlocked: false, 83 | pool_type: pool_type, 84 | value: 0 85 | } 86 | } 87 | }); 88 | }); 89 | 90 | // Call minerservice update 91 | $scope.updateCharts(); 92 | }; 93 | 94 | // No spawn xhr reqs in bg 95 | $pageVisibility.$on('pageFocused', function(){ 96 | minerService.runService(true); 97 | }); 98 | 99 | $pageVisibility.$on('pageBlurred', function(){ 100 | minerService.runService(false); 101 | }); 102 | 103 | // Register call with timer 104 | timerService.register(loadData, $route.current.controller); 105 | loadData(); 106 | 107 | $scope.$on("$routeChangeStart", function () { 108 | timerService.remove($route.current.controller); 109 | }); 110 | }); -------------------------------------------------------------------------------- /app/user/dashboard/minerpayments.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 |
5 |

Payment History

6 | 7 | 8 | clear 9 | 10 |
11 |
12 | 13 |

To : {{addr}}

14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 36 | 37 | 38 | 39 | 40 | 41 | 42 |

Time

Type

Amount

Txn Hash

Mixins

31 |

32 | 33 | {{payment.ts*1000 | date:'hh:mm:ss dd/MM/yy'}} 34 | 35 |

{{payment.pt}}

{{payment.amount | toXMR }} XMR

{{payment.mixin}}

43 |
44 | 45 |
46 | 47 | 48 | 49 | 50 | Close 51 | 52 | 53 |
54 |
-------------------------------------------------------------------------------- /app/user/dashboard/minerpayments.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | app.controller('MinerPaymentsCtrl', function($scope, $mdDialog, dataService, miner, addr) { 4 | $scope.miner = miner; 5 | $scope.addr = addr; 6 | $scope.selected = []; 7 | 8 | $scope.options = { 9 | page: 1, 10 | limit: 15 11 | } 12 | 13 | $scope.loadPayments = function () { 14 | var params = angular.copy($scope.options); 15 | params.page -= 1; 16 | var urlParams = $.param(params) 17 | 18 | dataService.getData("/miner/"+addr+"/payments?"+urlParams, function(data){ 19 | $scope.payments = data; 20 | }); 21 | } 22 | 23 | $scope.loadPayments(); 24 | 25 | $scope.answer = function (answer) { 26 | $mdDialog.hide('close') 27 | } 28 | }); -------------------------------------------------------------------------------- /app/user/dashboard/poolstats.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 | 6 | 7 |
8 | multiline_chart 9 |
10 |
11 | 12 | {{poolStats.global.totalHashes | number}} 13 | Hashes Accepted 14 | 15 |
16 |
17 |
18 |
19 | 20 | 21 | 22 |
23 | widgets 24 |
25 |
26 | 27 |
{{poolStats.global.totalBlocksFound}}
28 |
29 | Blocks Found () 30 | 31 | {{poolStats.global.lastBlockFoundTime*1000 | date:'hh:mm:ss dd/MM/yy'}} 32 | 33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | 42 | 43 | 44 |
45 | memory 46 |
47 |
48 | 49 | {{poolStats.global.miners}} 50 | Miners Connected 51 | 52 |
53 |
54 |
55 |
56 | 57 | 58 | 59 |
60 | attach_money 61 |
62 |
63 | 64 | {{poolStats.global.totalMinersPaid}} 65 | Miners Paid ({{poolStats.global.totalPayments}} Payments) 66 | 67 | 68 |
69 |
70 |
71 |
72 |
-------------------------------------------------------------------------------- /app/user/help/chat.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 | 5 | 6 |
7 | Support on #monero-pools (Ask for help or say hello!) 8 |
9 |
10 | 11 | 12 |
13 | 14 | 15 |
16 | -------------------------------------------------------------------------------- /app/user/help/chat.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | app.controller('ChatCtrl', function() { 4 | }); -------------------------------------------------------------------------------- /app/user/help/faq.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | Pool FAQ 5 | 6 | 7 |
8 |

{{topic}}

9 |
10 |

{{question.title}}

11 |

12 |
13 |

{{question.media.title}}

14 |
15 | 16 |
17 |
18 |
19 | 20 |
21 |
22 |
23 |
24 | 25 | 26 |
Contents
27 |
28 |

{{topic}}

29 |

{{q.title}}

30 |
31 |
32 |
33 |
-------------------------------------------------------------------------------- /app/user/help/faq.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | app.controller('FAQCtrl', function($scope, $location, $anchorScroll, $sce, dataService) { 4 | // BooHoo:p 5 | $scope.goto = function(topic, index) { 6 | var newHash = topic + '_' + index; 7 | if ($location.hash() !== newHash) { 8 | // set the $location.hash to `newHash` and 9 | // $anchorScroll will automatically scroll to it 10 | $location.hash(newHash); 11 | } else { 12 | // call $anchorScroll() explicitly, 13 | // since $location.hash hasn't changed 14 | $anchorScroll(); 15 | } 16 | }; 17 | 18 | $scope.faq = { 19 | "General" : [ 20 | { 21 | title: "What is Monero?", 22 | answer: $sce.trustAsHtml("Monero is a cryptocurrency that promises untraceability and privacy. It accomplishes this by obfuscating and encrypting transactions beyond recognition, while allowing you to discreetly view and manage your assets. You can also prove your transactions to a third party if necessary.
"), 23 | media: 24 | // { 25 | // "title": "Simple", 26 | // "url": $sce.trustAsResourceUrl("https://www.youtube.com/embed/TZi9xx6aiuY?ecver=1") 27 | // } 28 | { 29 | "title": "Monero essentials video", 30 | "url": $sce.trustAsResourceUrl("https://www.youtube.com/embed/6DQb0cMvU7I?ecver=1") 31 | } 32 | }, 33 | 34 | { 35 | title: "What is mining and why should I be interested?", 36 | answer: $sce.trustAsHtml("Cryptocurrencies achieve decentralisation via a process called mining. When new transactions are created, they need to be validated. Miners compete with each other to validate a group of transactions(a.k.a. block). The winning miner is paid a block reward and collects transaction fees for the work carried out. Block rewards are also how new coins are generated and help regulate the economy of the currency.") 37 | }, 38 | { 39 | title: "How do I start mining?", 40 | answer: $sce.trustAsHtml("You can start mining today if you have a computer that sits idle. Monero can be mined on CPUs, GPU's or even a raspberry PI. To start mining you need to find the right mining software for your hardware and get going.

Read
Getting Started for more details.") 41 | }, 42 | { 43 | title: "What is pool mining?", 44 | answer: $sce.trustAsHtml("If you are mining on a small scale, it becomes extremely hard and unpredictable to earn a stable profit on your mining income. Pool mining gives you the opportunity to join a group of miners and share earnings for a consistent payout.") 45 | }, 46 | { 47 | title: "What is PPLNS?", 48 | answer: $sce.trustAsHtml("PPLNS is short for pay per last N shares. It is a method to split miner earnings fairly based on rounds. PPLNS hence favours loyal pool memebers over pool hoppers.") 49 | }, 50 | { 51 | title: "What does SOLO mining mean?", 52 | answer: $sce.trustAsHtml("Solo mining is the opposite of pool mining. You essentially submit your shares directly to the blockchain, which is the most profitable method if you run your own farm.") 53 | }, 54 | { 55 | title: "Is mining profitable?", 56 | answer: $sce.trustAsHtml("Mining can be profitable depending on the conditions involved. Your primary cost is your electricity and the cost of your hardware.
It is not practical to calculate the exact amount you would earn as it depends on the total hash rate of the network, difficulty and your luck.

An accurate estimate of earnings of the pool can be calculated by observing average daily number of blocks found ... followed by some mathematics?

* An earnings estimator may be implemented in the future.") 57 | }, 58 | ], 59 | "Pool Help": [ 60 | { 61 | title: "How payouts work?", 62 | answer: $sce.trustAsHtml("You might often wonder why you haven't been paid yet. This is normal and is how the payment cycle is designed to work.

Every 2 hours a master payment check is executed which pays out all dues. If everything goes as planned all dues that exceed the set payment thresholds are paid out.

In case of a wallet lock up or failure, the pool automatically requeues futher checks at 15 minute intervals until all payments are successfully completed. Once everything is paid out the system returns to the 2 hourly master cycle.

If you have any questions please dont hesitate to contact your pool admin.") 63 | }, 64 | { 65 | title: "Payout thresholds?", 66 | answer: $sce.trustAsHtml("Payout threshold is the minimum amount that needs to be earned before the pool pays out to your wallet. Since transactions in Monero have a significant miner fees, it's cost effective to set a higher payout threshold for your pool. The minimum value for this is usually 0.3 XMR.

To change your payment threshold, click the wrench after you login via \"Login\" button on the top right.

You could also adjust your payout threshold to regulate your payout schedule etc daily/weekly etc depending on your hash rate.") 67 | }, 68 | { 69 | title: "Why hasn't my \"Total Due\" amount increased?", 70 | answer: $sce.trustAsHtml("Sometimes, the monero blockchain will take a couple days for a new block to be found. Although you are contributing shares, the pool cannot guarantee your earnings until they are static.") 71 | }, 72 | { 73 | title: "Getting paid in BTC", 74 | answer: $sce.trustAsHtml("nodejs-pool supports direct payments to btc. This is done by using the shapeshift API to convert your XMR and send them to a BTC wallet.

To configure BTC payments please have a look at Getting Started command line samples.") 75 | }, 76 | { 77 | title: "Payments to exchanges/markets?", 78 | answer: $sce.trustAsHtml("Direct payment to exchange / pool wallets are supported. The only primary difference when using this method is that the minimum payout threshold is higher and usually a defaults to 3XMR.") 79 | }, 80 | { 81 | title: "IP Banning?", 82 | answer: $sce.trustAsHtml("Your IP gets banned if you submit invalid shares to the pool server. This usually happens if your card is overclocked or unstable.

The ban is temporary and usually cleared in xx mins. You could also contact your pool admin and request an unban.") 83 | }, 84 | { 85 | title: "How Fixed / Variable Difficulty works", 86 | answer: $sce.trustAsHtml("When you select a pool port, the starting difficulty only represents your initial setting. As soon as your miner starts submitting shares the server will try to adjust your difficulty to reflect your hash rate.

This assures you not creating too many or too few requests to your server optimizing bandwidth consumption and server loads.

Optionally you could set a fixed difficulty via your miner command line options, though if you set a difficulty too high, you could exceed the 60 seconds job limit and loose earnings.") 87 | }, 88 | { 89 | title: "Can i mine other coins?", 90 | answer: $sce.trustAsHtml("Not yet, but we may add more soon. Follow https://github.com/Snipa22/nodejs-pool/issues/27.") 91 | } 92 | ], 93 | "Mining":[ 94 | { 95 | title: "Hardware?", 96 | answer: $sce.trustAsHtml("Monero is an AISC resistant cryptocurrency, that means it should be cost prohibitive to mine monero with an FGPA/AISC allowing desktop grade hardware to keep its share in the network hashrate and earnings.

http://monerobechmarks.byethost5.com/ is a list of community collected hashrate results ordered by hardware, but be careful as some entries may not be accurate.") 97 | }, 98 | { 99 | title: "Software?", 100 | answer: $sce.trustAsHtml("Read -- Getting Started.") 101 | } 102 | ], 103 | "Support":[ 104 | { 105 | title: "Chat Support", 106 | answer: $sce.trustAsHtml("Monero is an AISC resistant cryptocurrency, that means it should be cost prohibitive to mine monero with an FGPA/AISC allowing desktop grade hardware to keep its share in the network hashrate and earnings.

http://monerobechmarks.byethost5.com/ is a list of community collected hashrate results ordered by hardware, but be careful as some entries may not be accurate.") 107 | }, 108 | { 109 | title: "Interesting links.", 110 | answer: $sce.trustAsHtml("http://reddit.com/r/moneromining/
http://monero.stackexchange.com/") 111 | } 112 | ] 113 | } 114 | 115 | // end 116 | }); 117 | -------------------------------------------------------------------------------- /app/user/help/getting_started.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 |
6 | Getting Started with {{GLOBALS.pool_name}} 7 |
8 |
9 | 10 |

If you are new to mining, this is the place for you.

11 |

take a deep breath, its only confusing at first but pure joy once up and running.

12 |
13 |
14 | 15 | 16 |
17 | 1. Create a Wallet 18 |
19 |
20 | 21 |

Both online wallets and hardware wallets have their pros and cons.

22 |

MyMonero : MyMonero is an online wallet maintained by the core monero team, so while safe is still suceptable to hacks.

23 |

Monero GUI Wallet : The safest way to safely store and secure your XMR, although it would be your responsibility to kee your XMR Safe.

24 |
25 |
26 | 27 | 28 |
29 | 2. Download mining software 30 |
31 |
32 | 33 |

AMD Miner

34 |

Nvidia Miner

35 |

CPU Miner (AESNI CPU's)

36 |

CPU Miner (Older CPU's})

37 |
38 |
39 | 40 | 41 |
42 | 3. Pick a server and port 43 |
44 |
45 | 46 |

There are 2 important things here the URL and the port that you are going to connect to.

47 |

You need to pick your URL and Port from the Ports List. Here you'll notice theres Global, PPLNS, Solo (?)

48 |

Global is simply a collection of both PPLNS & Solo ports powerd by GEODNS.
GEODNS automagically finds the closest server to you and could also be used to shares you in case of a regional outage.

49 |

If you prefer to shave off a few of those ms and connect directly, you have the option to select your preferred endpoint too.

50 |

The Port is used in conjunction with the url and is used to specify the starting difficulty. You should select this depending on the total #ing power of your rig (see port descriptions).

51 |

If you are an advanced user or would like to set a fixed difficulty. examples below.

52 |
53 |
54 | View Ports list 55 |
56 |
57 | 61 |
62 |
63 | 64 | 65 |
66 | The moment 67 |
68 |
69 | 70 |

OK So by now you should have a wallet, mining software, url and port. Its time to put them together.

71 |
72 | 73 |

Here are some examples of the parameters that you need to pass on to your miner software, the format matters so be careful.

74 |

Depending on your miner you need to add the required parameters via command line or config.txt

75 |

Required fields are payment address and MinerIdentifier

76 |
77 | Username format : address.paymentID+FixedDifficulty
78 | Password Format : MinerIdentifier:Email
79 |
80 | 81 | Username / Password Samples 82 | 83 | 84 |
85 |

## {{ sample.desc }}

86 |
87 |

{{ sample.type }} : {{ sample.sample }}

88 | (e.g. miner.exe -u {{(sample.type=="Username") ? sample.sample : 'paymentAddress'}} -p {{(sample.type=="Password") ? sample.sample : 'worker'}})
89 |
90 |
91 | 92 |
93 |
94 |
95 |
96 |

Happy Mining !

97 |
98 |
99 | -------------------------------------------------------------------------------- /app/user/help/getting_started.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | app.controller('GettingStartedCtrl', function($scope, $mdDialog, dataService) { 4 | $scope.portsList = {}; 5 | $scope.selected = []; 6 | 7 | $scope.promise = dataService.getData("/pool/ports", function(data){ 8 | $scope.portsList = data; 9 | }); 10 | 11 | $scope.viewPorts = function(ev){ 12 | $mdDialog.show({ 13 | controller: "PortsModalCtrl", 14 | templateUrl: 'user/help/portsmodal.html', 15 | parent: angular.element(document.body), 16 | targetEvent: ev, 17 | clickOutsideToClose:true, 18 | fullscreen: $scope.menuOpen // Only for -xs, -sm breakpoints. 19 | }) 20 | .then(function(answer) { 21 | $scope.status = 'You said the information was "' + answer + '".'; 22 | }, function() { 23 | $scope.status = 'You cancelled the dialog.'; 24 | }); 25 | } 26 | 27 | $scope.samples=[ 28 | { 29 | type: 'Username', 30 | sample: '43To46Y9AxNFkY5rsMQaLwbRNaxLZVvc4LJZt7Cx9Dt23frL6aut2uC3PsMiwGY5C5fKLSn6sWyoxRQTK1dhdBpKAX8bsUW', 31 | desc: 'Standard address for withdraws to a wallet (CLI/GUI/MyMonero)', 32 | valid: true 33 | }, 34 | { 35 | type: 'Username', 36 | sample: '4DAU4uMdnDtFkY5rsMQaLwbRNaxLZVvc4LJZt7Cx9Dt23frL6aut2uC3PsMiwGY5C5fKLSn6sWyoxRQTK1dhdBpKF82nvn2H6jg9SUywAX', 37 | desc: 'Integrated address withdraw for exchange (TuxExchange)', 38 | valid: true 39 | }, 40 | { 41 | type: 'Username', 42 | sample: '43To46Y9AxNFkY5rsMQaLwbRNaxLZVvc4LJZt7Cx9Dt23frL6aut2uC3PsMiwGY5C5fKLSn6sWyoxRQTK1dhdBpKAX8bsUW.6FEBAC2C05EDABB16E451D824894CC48AE8B645A48BD4C4F21A1CC8624EB0E6F', 43 | desc: 'Standard exchange withdrawl (Poloniex/etc.)', 44 | valid: true 45 | }, 46 | { 47 | type: 'Username', 48 | sample: '1KEJ7EJvfD2bpL6vA9nJpTEgoS9P5jdyce', 49 | desc: 'BTC Withdrawal (Will process through xmr.to or shapeshift.io automatically)', 50 | valid: true 51 | }, 52 | { 53 | type: 'Username', 54 | sample: '4DAU4uMdnDtFkY5rsMQaLwbRNaxLZVvc4LJZt7Cx9Dt23frL6aut2uC3PsMiwGY5C5fKLSn6sWyoxRQTK1dhdBpKF82nvn2H6jg9SUywAX.6FEBAC2C05EDABB16E451D824894CC48AE8B645A48BD4C4F21A1CC8624EB0E6F', 55 | desc: 'Integrated address withdraw for exchange w/ paymentID', 56 | valid: false 57 | }, 58 | { 59 | type: 'Username', 60 | sample: '1KEJ7EJvfD2bpL6vA9nJpTEgoS9P5jdyce+100000', 61 | desc: 'BTC Withdrawal w/ fixed diff (Good for NiceHash)', 62 | valid: true 63 | }, 64 | { 65 | type: 'Username', 66 | sample: '43To46Y9AxNFkY5rsMQaLwbRNaxLZVvc4LJZt7Cx9Dt23frL6aut2uC3PsMiwGY5C5fKLSn6sWyoxRQTK1dhdBpKAX8bsUW+3500', 67 | desc: 'Standard address for withdraws to a wallet w/ fixed diff (Good for NiceHash)', 68 | valid: true 69 | }, 70 | { 71 | type: 'Username', 72 | sample: '43To46Y9AxNFkY5rsMQaLwbRNaxLZVvc4LJZt7Cx9Dt23frL6aut2uC3PsMiwGY5C5fKLSn6sWyoxRQTK1dhdBpKAX8bsUW.6FEBAC2C05EDABB16E451D824894CC48AE8B645A48BD4C4F21A1CC8624EB0E6F+23472', 73 | desc: 'Standard exchange withdrawl w/ fixed diff (Good for NiceHash)', 74 | valid: true 75 | }, 76 | { 77 | type: 'Password', 78 | sample: 'Steve', 79 | desc: 'Miner identifier of Steve', 80 | valid: true 81 | }, 82 | { 83 | type: 'Password', 84 | sample: 'x:test@e-mail.com', 85 | desc: 'Miner identifier of x, registers address + paymentID if there is one, to the e-mail address for notification', 86 | valid: true 87 | }, 88 | { 89 | type: 'Password', 90 | sample: 'test@e-mail.com', 91 | desc: 'Will register the e-mail address as the worker ID', 92 | valid: true 93 | } 94 | ] 95 | 96 | }); -------------------------------------------------------------------------------- /app/user/help/portsmodal.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 |
5 |

Ports List

6 | 7 | 8 | clear 9 | 10 |
11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 |

Server

Port

Port Type

Difficulty

Miners

Current Block ID

Block Time

Description

{{portinfo.host.hostname}}

{{portinfo.port}}

{{portinfo.pool_type}}

{{portinfo.difficulty}}

{{portinfo.miners}}

{{portinfo.host.blockID}}

{{portinfo.host.blockIDTime * 1000 | date: 'hh:mm:ss - dd/MM/yy'}}

{{portinfo.description}}

42 |
43 |
44 | 45 |
46 | 47 | 48 | 49 | Close 50 | 51 | 52 |
53 |
-------------------------------------------------------------------------------- /app/user/help/portsmodal.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | app.controller('PortsModalCtrl', function($scope, $mdDialog, dataService) { 4 | $scope.selected = []; 5 | 6 | $scope.promise = dataService.getData("/pool/ports", function(data){ 7 | $scope.portsList = data; 8 | }); 9 | 10 | $scope.answer = function (answer) { 11 | $mdDialog.hide('close') 12 | } 13 | }); -------------------------------------------------------------------------------- /app/user/home/console.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 |
5 |

Miner Console

6 | 7 | 8 | clear 9 | 10 |
11 |
12 | 13 | 14 | 15 | 16 |
17 |

You currently have no payment threshold set, defaults are in use.

18 | 19 | 20 | 21 | 22 | 23 |
24 |
25 | This value cannot be lower than {{min_wallet_payout | toXMR}} XMR 26 |
27 |
28 |

The pool will pay out to your wallet once your total due exceeds the payment threshold

29 | 30 |
31 |
32 | 33 |
34 |
35 | Receive miner hashrate alerts 36 |
37 |
38 |
39 | 40 | 41 |
42 |
43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 |
51 |
52 | Please confirm your new password 53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 | 62 | 63 | 64 | Logout 65 | 66 | 67 |

{{status}}

68 | 69 | Update {{currentTab}} 70 | 71 |
72 | 73 |
-------------------------------------------------------------------------------- /app/user/home/console.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | app.controller('ConsoleCtrl', function($scope, $route, $filter, $timeout, $mdDialog, min_wallet_payout, dataService, timerService) { 4 | $scope.paymentThresh; 5 | $scope.min_wallet_payout = min_wallet_payout; 6 | $scope.currentTab = 'Threshold'; // default tab 7 | $scope.status = ""; 8 | $scope.statusClass = "valid"; 9 | $scope.password = { 10 | pwd: "", 11 | cnf: "" 12 | } 13 | 14 | var email_enabled; 15 | 16 | var getConfig = function () { 17 | dataService.getData("/authed", function(data){ 18 | $scope.paymentThresh = $filter('toXMR')(data.msg.payout_threshold); 19 | email_enabled = data.msg.email_enabled; 20 | $scope.email_toggle = data.msg.email_enabled; 21 | }); 22 | } 23 | 24 | var updateThreshold = function () { 25 | dataService.postData("/authed/changePayoutThreshold", {threshold: $scope.paymentThresh},function(data){ 26 | //$mdDialog.hide('updated'); 27 | $scope.statusClass = "valid"; 28 | $scope.status = "Threshold Saved"; 29 | messageFlash(); 30 | }); 31 | } 32 | 33 | var updatePassword = function () { 34 | if($scope.password.pwd == $scope.password.cnf && $scope.password.pwd !== "") { 35 | dataService.postData("/authed/changePassword", {password: $scope.password.pwd},function(data){ 36 | //$mdDialog.hide('updated'); 37 | $scope.statusClass = "valid"; 38 | $scope.status = "Password Saved"; 39 | messageFlash(); 40 | }); 41 | } else { 42 | $scope.statusClass = "invalid"; 43 | $scope.status = "Check passwords"; 44 | messageFlash(); 45 | } 46 | } 47 | 48 | var updateEmail = function () { 49 | if($scope.email_toggle!=email_enabled){ 50 | dataService.postData("/authed/toggleEmail", {}, function(data) { 51 | $scope.status = data.msg; 52 | email_enabled=$scope.email_toggle; 53 | messageFlash(); 54 | }); 55 | } else { 56 | $scope.statusClass = "invalid"; 57 | $scope.status = "No Change..."; 58 | messageFlash(); 59 | } 60 | } 61 | 62 | var messageFlash = function(){ 63 | $timeout(function() { 64 | $scope.status = ""; 65 | $scope.statusClass = "valid"; 66 | },2000); 67 | } 68 | 69 | $scope.save = function () { 70 | $scope.statusClass = "valid"; 71 | $scope.status = "Saving...";// + $scope.currentTab; 72 | switch ($scope.currentTab){ 73 | case 'Threshold': 74 | updateThreshold(); 75 | break; 76 | 77 | case 'Password': 78 | updatePassword(); 79 | 80 | case 'Email': 81 | updateEmail(); 82 | } 83 | } 84 | 85 | $scope.logout = function () { 86 | $mdDialog.hide('logout'); 87 | } 88 | 89 | getConfig(); 90 | 91 | // Dialog methods 92 | $scope.cancel = function () { 93 | $mdDialog.cancel(); 94 | } 95 | }); -------------------------------------------------------------------------------- /app/user/home/home.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 |
6 | 7 | 8 | Pool Config 9 | 10 | 11 | 12 | 22 | 23 |

PPLNS Fee

24 | 25 | {{config.pplns_fee}}% 26 | 27 | Core Devs Donation: {{config.dev_donation/100*config.pplns_fee | number:3 }}% | 28 | Pool Devs Donation:{{config.pool_dev_donation/100*config.pplns_fee | number:3 }}% 29 | 30 | 31 |
32 | 33 |

Solo Fee

34 |

35 | {{config.solo_fee}}% 36 | 37 | Core Devs Donation: {{config.dev_donation/100*config.solo_fee | number:3}}% | 38 | Pool Devs Donation:{{config.pool_dev_donation/100*config.solo_fee | number:3}}% 39 | 40 |

41 |
42 | 43 |

BTC Fee

44 |

{{config.btc_fee}}%

45 |
46 | 47 |

Minimum payout (Wallet)

48 |

{{config.min_wallet_payout | toXMR}} XMR

49 |
50 | 51 |

Minimum payout (BTC)

52 |

{{config.min_btc_payout | toXMR}} XMR

53 |
54 | 55 |

Minimum payout (Exchange)

56 |

{{config.min_exchange_payout | toXMR}} XMR

57 |
58 | 59 |

Block Maturity Depth

60 |

{{config.maturity_depth}}

61 |
62 | 63 |

Minimum Denomination

64 |

{{config.min_denom | toXMR}}

65 |
66 |
67 |
68 |
69 |
-------------------------------------------------------------------------------- /app/user/home/home.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | app.controller('HomeCtrl', function($scope, $route, dataService, timerService) { 4 | 5 | }); -------------------------------------------------------------------------------- /app/user/home/login.html: -------------------------------------------------------------------------------- 1 | 2 | 43 | -------------------------------------------------------------------------------- /app/user/home/login.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | app.controller('LoginCtrl', function($scope, $route, $mdDialog, dataService, timerService) { 4 | $scope.user = { 5 | username: "", 6 | password: "" 7 | } 8 | 9 | $scope.remember = false; 10 | $scope.status = ""; 11 | 12 | $scope.login = function () { 13 | dataService.postData("/authenticate", $scope.user, function(data){ 14 | if (data.success){ 15 | data.remember = $scope.remember; 16 | dataService.setAuthToken(data); 17 | $mdDialog.hide(data); 18 | } else { 19 | $mdDialog.hide(false); 20 | } 21 | }, function(error){ 22 | $scope.status = "Please check your login details"; 23 | }); 24 | } 25 | 26 | $scope.cancel = function () { 27 | $mdDialog.cancel(); 28 | } 29 | }); -------------------------------------------------------------------------------- /app/user/network/network.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Coming SoOn..

4 |
5 |
-------------------------------------------------------------------------------- /app/user/network/network.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | app.controller('NetworkCtrl', function($scope, $route, dataService, timerService) { 4 | 5 | 6 | var loadData = function () { 7 | console.log("Getting Network Data"); 8 | 9 | dataService.getData("/network/chart/usdHash/60", function(data){ 10 | $scope.config = data; 11 | console.log(data); 12 | }); 13 | 14 | }; 15 | 16 | loadData(); 17 | // timerService.register(loadData, $route.current.controller); 18 | 19 | // $scope.$on("$routeChangeStart", function () { 20 | // timerService.remove($route.current.controller); 21 | // }); 22 | }); -------------------------------------------------------------------------------- /app/user/payments/payments.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 | 5 | 6 |
7 | Payments Made 8 |
9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 |

Time Sent

Transaction Hash

Amount

Fee

Mixin

Payees

27 |

28 | 29 | {{payment.ts | date:'hh:mm:ss dd/MM/yy'}} 30 | 31 |

{{payment.value | toXMR}} XMR

{{payment.fee | toXMR }} XMR

{{payment.mixins}}

{{payment.payees}}

40 |
41 | 42 |
43 |
44 |
45 |
-------------------------------------------------------------------------------- /app/user/payments/payments.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | app.controller('PaymentsCtrl', function($scope, dataService) { 4 | $scope.payments = {}; 5 | $scope.selected = []; 6 | 7 | $scope.options = { 8 | page: 1, 9 | limit: 15 10 | } 11 | 12 | $scope.loadPayments = function () { 13 | var params = angular.copy($scope.options); 14 | params.page -= 1; 15 | var urlParams = $.param(params) 16 | 17 | dataService.getData("/pool/payments?"+urlParams, function(data){ 18 | $scope.payments.global = data; 19 | }); 20 | } 21 | 22 | $scope.loadPayments(); 23 | 24 | }); -------------------------------------------------------------------------------- /app/user/ports/ports.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | Ports List 6 |
7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 |

Server

Port

Port Type

Difficulty

Miners

Current Block ID

Block Time

Description

{{portinfo.host.hostname}}

{{portinfo.port}}

{{portinfo.pool_type}}

{{portinfo.difficulty}}

{{portinfo.miners}}

{{portinfo.host.blockID}}

{{portinfo.host.blockIDTime * 1000 | date: 'hh:mm:ss - dd/MM/yy'}}

{{portinfo.description}}

38 |
39 |
40 |
41 |
42 |
-------------------------------------------------------------------------------- /app/user/ports/ports.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | app.controller('PortsCtrl', function($scope, $route, dataService, timerService) { 4 | $scope.portsList = {}; 5 | $scope.selected = []; 6 | 7 | $scope.promise = dataService.getData("/pool/ports", function(data){ 8 | $scope.portsList = data; 9 | }); 10 | }); -------------------------------------------------------------------------------- /app/utils/dataservice.js: -------------------------------------------------------------------------------- 1 | angular.module('utils.xhr', []) 2 | .service('dataService', function($http, $localStorage, $sessionStorage, GLOBALS) { 3 | var apiURL = GLOBALS.api_url; 4 | var sessStorage = $sessionStorage; 5 | var storage = $localStorage; 6 | var sessionLock = false; 7 | 8 | this.getData = function(url, successFn, errorFn) { 9 | this.xhr('GET', url, {}, successFn, errorFn); 10 | } 11 | 12 | this.postData = function(url, params, successFn, errorFn) { 13 | this.xhr('POST', url, params, successFn, errorFn); 14 | } 15 | 16 | this.putData = function(url, params, successFn, errorFn) { 17 | this.xhr('PUT', url, params, successFn, errorFn); 18 | } 19 | 20 | this.deleteData = function(url, params, successFn, errorFn) { 21 | this.xhr('DELETE', url, params, successFn, errorFn); 22 | } 23 | 24 | this.xhr = function (type, url, params, successFn, errorFn) { 25 | $http({ 26 | method: type, 27 | url: apiURL + url, 28 | data: params, 29 | headers: this.getRequestHeaders() 30 | }).then(function successCallback(response) { 31 | successFn(response.data); 32 | }, function errorCallback(response) { 33 | if (errorFn && response != undefined) errorFn(response); else console.log("Network Error", response); 34 | }).$promise; 35 | } 36 | 37 | this.setAuthToken = function(token) { 38 | sessStorage.token = token.msg; 39 | storage.authToken = (token.remember) ? token.msg : false; // remember me 40 | this.validateSession(); 41 | } 42 | 43 | this.getRequestHeaders = function() { 44 | this.validateSession(); 45 | return { 'x-access-token': (sessStorage.token) ? sessStorage.token : "" }; 46 | } 47 | 48 | this.isLoggedIn = function() { 49 | return sessStorage.token || storage.authToken; 50 | } 51 | 52 | this.validateSession = function () { 53 | if (storage.authToken !== undefined){ 54 | sessionLock = true; 55 | if (storage.authToken) { 56 | $http.defaults.headers.common['x-access-token'] = storage.authToken; 57 | sessStorage.token = storage.authToken; 58 | } 59 | } else if (sessionLock) { 60 | // logout if, logout detected on another browser session 61 | this.logout(); 62 | sessionLock=false; 63 | } 64 | } 65 | 66 | this.logout = function() { 67 | // invalidate existing token 68 | $http.get(apiURL+"/authed/tokenRefresh") 69 | .then(function (data) { 70 | /* Do nothing */ 71 | }, function (err) { 72 | console.log("debug", err); 73 | }); 74 | delete storage.authToken; 75 | delete sessStorage.authToken; 76 | delete sessStorage.token; 77 | // invalidate token on server todo 78 | } 79 | }) -------------------------------------------------------------------------------- /app/utils/directives.js: -------------------------------------------------------------------------------- 1 | var compareTo = function() { 2 | return { 3 | require: "ngModel", 4 | scope: { 5 | otherModelValue: "=compareTo" 6 | }, 7 | link: function(scope, element, attributes, ngModel) { 8 | 9 | ngModel.$validators.compareTo = function(modelValue) { 10 | return modelValue == scope.otherModelValue; 11 | }; 12 | 13 | scope.$watch("otherModelValue", function() { 14 | ngModel.$validate(); 15 | }); 16 | } 17 | }; 18 | }; 19 | 20 | angular.module('utils.directives', []) 21 | .directive("compareTo", compareTo); -------------------------------------------------------------------------------- /app/utils/services.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('utils.services', []) 4 | 5 | .service('timerService', function($interval) { 6 | var timer; 7 | var listeners = {}; 8 | 9 | this.startTimer = function(ms) { 10 | timer = $interval(function() { 11 | _.each(listeners, function(listener) { 12 | listener(); 13 | }); 14 | }, ms); 15 | } 16 | 17 | this.stopTimer = function(){ 18 | $interval.cancel(timer); 19 | } 20 | 21 | this.register = function(callback, key){ 22 | // console.log("Registering requests for", key); 23 | return listeners[key] = callback; 24 | } 25 | 26 | this.remove = function(key){ 27 | // console.log("Destroying requests for", key); 28 | delete listeners[key]; 29 | } 30 | }) 31 | 32 | .service('addressService', function(dataService, timerService, $localStorage, ngAudio) { 33 | var addrStats = {}; 34 | var callback; 35 | var storage = $localStorage; 36 | 37 | this.trackAddress = function (addr) { 38 | addrStats[addr] = {}; 39 | track(); 40 | } 41 | 42 | this.deleteAddress = function (key) { 43 | delete addrStats[key]; 44 | }; 45 | 46 | this.getData = function (){ 47 | return addrStats; 48 | } 49 | 50 | this.setAlarm = function(addr, bool){ 51 | addrStats[addr].alarm = bool; 52 | storage.addrStats[addr].alarm = bool; 53 | } 54 | 55 | var track = function(){ 56 | _.each(addrStats, function(addr, key) { 57 | // Get Miner stats 58 | dataService.getData("/miner/"+key+"/stats", function(data){ 59 | addrStats[key] = Object.assign(addr, data); 60 | 61 | // check and inject alarm var 62 | if (addr.alarm == undefined) { 63 | addr.alarm = false; 64 | } 65 | 66 | // Set default miner name address 67 | if (addr.name === undefined) { 68 | addr.name = key; 69 | } 70 | 71 | // update 72 | storage.addrStats = addrStats; 73 | callback(addrStats); 74 | }); 75 | 76 | // Get miner worker ids 77 | dataService.getData("/miner/"+key+"/identifiers", function(minerIDs){ 78 | addrStats[key].ids = minerIDs; 79 | }); 80 | 81 | dataService.getData("/miner/"+key+"/stats/allWorkers", function(workerStats){ 82 | addrStats[key].workerStats = workerStats; 83 | }); 84 | 85 | }); 86 | 87 | } 88 | 89 | this.start = function (cb){ 90 | timerService.register(track, 'minerStats'); 91 | addrStats = storage.addrStats || {} ; 92 | callback = cb; 93 | track(); // also run immediately 94 | } 95 | }) 96 | 97 | .service('minerService', function($filter, dataService) { 98 | var minerStats = {}; 99 | var callback; 100 | var status = true; // check pause 101 | 102 | this.runService = function (bool) { 103 | status = bool; 104 | } 105 | 106 | this.updateStats = function (addrs, callback) { 107 | 108 | // initalise addrs 109 | if(!status) return 0; 110 | 111 | _.each(addrs, function (data, addr) { 112 | 113 | if (minerStats[addr] === undefined) minerStats[addr] = { 114 | dataset : {}, 115 | options : { 116 | series: [], 117 | allSeries: [], 118 | axes: { 119 | x: { 120 | key: "ts", 121 | type: "date" 122 | } 123 | } 124 | }, 125 | table_selected: [], 126 | table_options: { 127 | rowSelection: true, 128 | multiSelect: true 129 | } 130 | }; 131 | 132 | dataService.getData("/miner/"+addr+"/chart/hashrate/allWorkers", function(allWorkersData){ 133 | // Convert all dates to object 134 | 135 | _.each(allWorkersData, function (workerData, mid) { 136 | for(var i = 0 ; i < workerData.length; i++){ 137 | allWorkersData[mid][i].ts = new Date(allWorkersData[mid][i].ts); 138 | } 139 | 140 | minerStats[addr].dataset[mid] = workerData; 141 | 142 | minerStats[addr].options.allSeries = _.unionBy(minerStats[addr].options.allSeries, [{ 143 | axis: "y", 144 | id: mid, 145 | dataset: mid, 146 | label: mid, 147 | key: "hs", 148 | color: (minerStats[addr].options.series[mid]===undefined) ? randomColor() : minerStats[addr].options.series[mid].color, 149 | type: ['line', 'area'], 150 | //interpolation: { mode: "basis"}, 151 | defined: function (value){ 152 | //console.log(value); 153 | return (value !== undefined || value.x !== undefined || value.y !== undefined) ; 154 | } 155 | }], 'id'); 156 | }); 157 | 158 | // only display selected miners 159 | var selected = minerStats[addr].selected; 160 | if(minerStats[addr].table_selected.length < 1) { 161 | selected = _.union(minerStats[addr].table_selected, ['global']); 162 | } 163 | 164 | minerStats[addr].options.series = _.intersectionWith(minerStats[addr].options.allSeries, selected, function(ser, sel) { return ( ser.id == sel ) }); 165 | }); 166 | 167 | // report back 168 | callback(minerStats); 169 | 170 | }); 171 | }; 172 | }); -------------------------------------------------------------------------------- /app/utils/strings.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('utils.strings', []) 4 | 5 | .filter('toXMR', function() { 6 | return function(amount) { 7 | return amount / 1000000000000; 8 | }; 9 | }) 10 | 11 | .filter('toHashRate', function() { 12 | return function(hashes) { 13 | if (hashes > 1000000) { 14 | return Math.floor(hashes / 1000000) + "." + (hashes % 1000000).toString().substring(0, 1) + " MH/s" 15 | } 16 | if (hashes > 1000) { 17 | return Math.floor(hashes / 1000) + "." + (hashes % 1000).toString().substring(0, 1) + " KH/s" 18 | } 19 | return ( hashes || 0 ) + " H/s" 20 | }; 21 | }) 22 | 23 | .filter('hashToLink', function($sce) { 24 | return function(hash, type) { 25 | var str = (hash == undefined) ? 'none' : "" + hash + ""; 26 | return $sce.trustAsHtml(str); 27 | }; 28 | }) 29 | 30 | .filter('difficultyToHashRate', function() { 31 | return function(hashrate) { 32 | return Math.floor(hashrate / 120) 33 | }; 34 | }); 35 | 36 | -------------------------------------------------------------------------------- /app/welcome.html: -------------------------------------------------------------------------------- 1 | 2 |
Welcome to {{GLOBALS.pool_name}}
3 |

Update welcome content in app/welcome.html

4 |

Please stay tuned for more features and changes! If you have a feature request, please poke Snipa on irc.freenode.net in #monero-pools

5 |
-------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "xmrpoolui", 3 | "private": true, 4 | "dependencies": { 5 | "angular": "~1.6.1", 6 | "angular-route": "~1.6.1", 7 | "angular-loader": "~1.6.1", 8 | "jquery": "~3.1.1", 9 | "angular-material": "~1.1.3", 10 | "angular-moment": "~1.0.1", 11 | "moment": "momentjs#^2.17.1", 12 | "ngstorage": "^0.3.11", 13 | "angular-audio": "^1.7.3", 14 | "angular-material-data-table": "^0.10.10", 15 | "angular-chart.js": "^1.1.1", 16 | "chart.js": "^2.4.0", 17 | "lodash": "^4.17.4", 18 | "angular_page_visibility": "angular-page-visibility#^0.0.4" 19 | }, 20 | "resolutions": { 21 | "angular": "~1.6.1" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var connect = require('gulp-connect'); 3 | var manifest = require('gulp-manifest'); 4 | 5 | gulp.task('html', function(){ 6 | return gulp.src(['app/**/*.html', '!app/vendor/**/*']) 7 | .pipe(connect.reload()) 8 | .pipe(gulp.dest('build/')) 9 | }); 10 | 11 | gulp.task('css', function(){ 12 | return gulp.src(['app/**/*.css', '!app/vendor/**/*']) 13 | .pipe(connect.reload()) 14 | .pipe(gulp.dest('build/')) 15 | }); 16 | 17 | gulp.task('js', function(){ 18 | return gulp.src(['app/**/*.js', '!app/vendor/**/*']) 19 | .pipe(connect.reload()) 20 | .pipe(gulp.dest('build/')) 21 | }); 22 | 23 | gulp.task('assets', function(){ 24 | return gulp.src('app/assets/*') 25 | .pipe(connect.reload()) 26 | .pipe(gulp.dest('build/assets')) 27 | }); 28 | 29 | gulp.task('connect', function() { 30 | connect.server({ 31 | root: 'build', 32 | livereload: true 33 | }); 34 | }); 35 | 36 | gulp.task('vendor', function() { 37 | return gulp.src([ 38 | 'app/vendor/**/dist/jquery.js', 39 | 'app/vendor/**/angular.js', 40 | 'app/vendor/**/angular-route.js', 41 | 'app/vendor/**/angular-material.css', 42 | 'app/vendor/**/angular-animate.js', 43 | 'app/vendor/**/angular-aria.js', 44 | 'app/vendor/**/angular-material.js', 45 | 'app/vendor/**/angular-moment.js', 46 | 'app/vendor/**/ngStorage.js', 47 | 'app/vendor/**/app/angular.audio.js', 48 | 'app/vendor/**/moment.js', 49 | 'app/vendor/**/md-data-table.js', 50 | 'app/vendor/**/md-data-table.css', 51 | 'app/vendor/**/dist/chart.js', 52 | 'app/vendor/**/dist/angular-chart.js', 53 | 'node_modules/**/d3.js', 54 | 'node_modules/**/LineChart.js', 55 | 'node_modules/**/LineChart.css', 56 | 'node_modules/**/randomColor.js', 57 | 'app/vendor/**/lodash.js', 58 | 'app/vendor/**/page_visibility.js' 59 | ]) 60 | .pipe(gulp.dest('build/vendor')) 61 | }); 62 | 63 | gulp.task('watch', function () { 64 | gulp.watch(['./app/**/*.html'], ['html', 'manifest']); 65 | gulp.watch(['./app/**/*.css'], ['css', 'manifest']); 66 | gulp.watch(['./app/**/*.js'], ['js', 'manifest']); 67 | gulp.watch(['./assets/*.*'], ['assets', 'manifest']); 68 | }); 69 | 70 | gulp.task('manifest', function(){ 71 | gulp.src([ 72 | 'build/**/*' 73 | ], { base: './build' }) 74 | .pipe(manifest({ 75 | hash: true, 76 | preferOnline: true, 77 | network: ['*'], 78 | filename: 'app.manifest', 79 | exclude: 'app.manifest' 80 | })) 81 | .pipe(connect.reload()) 82 | .pipe(gulp.dest('build')); 83 | }); 84 | 85 | gulp.task('build', [ 'html', 'css', 'js', 'assets', 'vendor', 'manifest' ]); 86 | gulp.task('default', [ 'build', 'connect', 'watch' ]); 87 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "xmrpoolui", 3 | "description": "UI for XMR Pool.net", 4 | "repository": "", 5 | "license": "?", 6 | "devDependencies": { 7 | "bower": "^1.7.7", 8 | "gulp": "^3.9.1", 9 | "gulp-connect": "^5.0.0", 10 | "gulp-manifest": "^0.1.1" 11 | }, 12 | "scripts": { 13 | "postinstall": "bower install && gulp build", 14 | "update-deps": "npm update", 15 | "postupdate-deps": "bower update", 16 | "prestart": "npm install", 17 | "start": "gulp" 18 | }, 19 | "dependencies": { 20 | "n3-charts": "^2.0.28", 21 | "randomcolor": "^0.4.4" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Monero Pool frontend 2 | 3 | ### AngularJS based UI for [nodejs-pool](https://github.com/Snipa22/nodejs-pool) 4 | 5 | ### Features 6 | - See your hashrate on all pages 7 | - Track multiple payment addresses. 8 | - Hashrate siren when hashrate falls below a certain limit. 9 | - Per miner charts & Payment History. 10 | - Miner login and management for threhold and payment adjustment. 11 | - Admin UI for simple Pool management. 12 | - All the usual features + more. 13 | 14 | ### Run it 15 | 16 | Home page html can be set in welcome.html 17 | Set pool params in app/globals.js.default and copy to app/globals.js 18 | 19 | Requires NodeJS 20 | 21 | ```sh 22 | $ npm start # starts gulp + livereload, serves from ./build on 8080 23 | ``` 24 | 25 | ## Deploy 26 | ```sh 27 | $ npm install # runs everything, serve from ./build 28 | ``` 29 | 30 | ### Todo 31 | 32 | * Fix sort arrow styling 33 | * Network stats page. 34 | * Ship it deployment 35 | * Websockets 36 | * Miner graph colour picker 37 | 38 | ### Support 39 | * I'm usually on #monero-pools so drop me a line if you need help with something or have a feature request. 40 | 41 | #### Coffee :P ? 42 | 42yCGRP2p6bZzMjJxKpJtTFRz2x3X3eBYD97T17zdxC9NiGNWafCaU54MKWBZkHb9AVb4XBgcjkPGW8hjQyBM2vMMvVCzTj --------------------------------------------------------------------------------