diff --git a/frontend/LICENSE b/frontend/LICENSE new file mode 100644 index 0000000..8a56d54 --- /dev/null +++ b/frontend/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 Snipa22 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/frontend/app/admin.html b/frontend/app/admin.html new file mode 100644 index 0000000..96e9b89 --- /dev/null +++ b/frontend/app/admin.html @@ -0,0 +1,87 @@ + + + + + + + XMRPool.net - Mine XMR/Monero or BTC/Bitcoin + + + + + + + + + + + + + + + + + + +
+ + +
+

Pool Admin CP

+
+
+ Logout +
+
+
+ + + + Dashboard + + + Workers + + + Ports + + + Config + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/app/admin.js b/frontend/app/admin.js new file mode 100644 index 0000000..e20e81d --- /dev/null +++ b/frontend/app/admin.js @@ -0,0 +1,64 @@ +'use strict'; + +// Declare app level module which depends on views, and components +var app = angular.module('pooladmin', [ + 'pool.globals', + 'ngRoute', + 'ngMaterial', + 'md.data.table', + 'ngStorage', + 'angularMoment', + 'utils.xhr', + 'utils.strings' +]).config(['$locationProvider', '$routeProvider', '$mdThemingProvider', function($locationProvider, $routeProvider, $mdThemingProvider) { + $locationProvider.hashPrefix(''); + // $mdIconProvider.defaultIconSet("https://rawgit.com/angular/material-start/es5-tutorial/app/assets/svg/avatars.svg", 128) + + $mdThemingProvider.theme('default') + .primaryPalette('grey') + .accentPalette('light-blue'); + + $routeProvider + .when('/login', { + templateUrl: 'admin/adminlogin.html', + controller: 'AdminLoginCtrl' + }) + .when('/dashboard', { + templateUrl: 'admin/dashboard.html', + controller: 'AdminDashCtrl' + }) + .when('/workers', { + templateUrl: 'admin/workers.html', + controller: 'AdminWorkersCtrl' + }) + .when('/ports', { + templateUrl: 'admin/ports.html', + controller: 'AdminPortsCtrl' + }) + .when('/config', { + templateUrl: 'admin/config.html', + controller: 'AdminConfigCtrl' + }) + + $routeProvider.otherwise({redirectTo: '/login'}); + + }]); + +app.controller('AppCtrl', function($scope, $window, $route, $location, $interval, dataService, $localStorage, GLOBALS) { + $scope.GLOBALS = GLOBALS; + + var loginCheck = function (){ + if(!dataService.isLoggedIn()){ + $location.path('#login'); + } + } + + $scope.isLoggedIn = function () { + return dataService.isLoggedIn(); + } + + $scope.logout = function () { + dataService.logout(); + $location.path('#login'); + } +}); \ No newline at end of file diff --git a/frontend/app/admin/addport.js b/frontend/app/admin/addport.js new file mode 100644 index 0000000..55549a6 --- /dev/null +++ b/frontend/app/admin/addport.js @@ -0,0 +1,26 @@ +'use strict'; + + +app.controller('addPortCtrl', function ($scope, $mdDialog, dataService) { + 'use strict'; + + this.cancel = function (){ + //config.value=old_value; + $mdDialog.cancel(); + } + + this.edit = function () { + $scope.item.form.$setSubmitted(); + + if($scope.item.form.$valid) { + dataService.putData('/admin/ports', {id: config.id, value: config.value}, function(data) { + $mdDialog.hide(data); + }, function (e) { + // error + $scope.config = old_value; + $mdDialog.hide('error'); + }); + } + }; + +}); \ No newline at end of file diff --git a/frontend/app/admin/adminlogin.html b/frontend/app/admin/adminlogin.html new file mode 100644 index 0000000..7b47a34 --- /dev/null +++ b/frontend/app/admin/adminlogin.html @@ -0,0 +1,39 @@ + + +
+

Pool Admin Login

+ +
+
+ + + +
+
+ + + + person + + + + + + lock + + +
+ +
+ + + + Remember me + + +

{{status}}

+ + Login + +
+
\ No newline at end of file diff --git a/frontend/app/admin/adminlogin.js b/frontend/app/admin/adminlogin.js new file mode 100644 index 0000000..4357e6d --- /dev/null +++ b/frontend/app/admin/adminlogin.js @@ -0,0 +1,30 @@ +'use strict'; + +app.controller('AdminLoginCtrl', function($scope, $location, $route, dataService) { + $scope.admin = { + username:"", + password:"" + } + + $scope.login = function () { + dataService.postData("/authenticate", $scope.admin, function(data){ + if (data.success){ + data.remember = $scope.remember; + dataService.setAuthToken(data); + $location.path('#/dashboard'); + } else { + // $mdDialog.hide(false); + } + }, function(error){ + $scope.status = "Please check your login details"; + }); + } + + var isLoggedIn = function () { + if(dataService.isLoggedIn == false) ; + } + + if(dataService.isLoggedIn()) { + $location.path('/dashboard'); + }; +}); \ No newline at end of file diff --git a/frontend/app/admin/config.html b/frontend/app/admin/config.html new file mode 100644 index 0000000..97f9763 --- /dev/null +++ b/frontend/app/admin/config.html @@ -0,0 +1,48 @@ + + +
+ Pool Config + + + +
+
+ + +
+ {{selected.length}} {{selected.length > 1 ? 'items' : 'item'}} selected + + +
+
+ + + + + + + + + + + + + + + + + + + + + + +

ID

Item

Value

Type

Module

+ + mode_edit + + {{config.id}}{{config.item}}{{config.value}}{{config.type}}{{config.module}}
+
+
\ No newline at end of file diff --git a/frontend/app/admin/config.js b/frontend/app/admin/config.js new file mode 100644 index 0000000..91a88be --- /dev/null +++ b/frontend/app/admin/config.js @@ -0,0 +1,32 @@ +'use strict'; + +app.controller('AdminConfigCtrl', function($scope, $location, $route, $mdDialog, dataService) { + $scope.selected = []; + + var loadConfig = function () { + dataService.getData("/admin/config", function(data) { + $scope.pool_configs = data; + }); + } + + $scope.editConfig = function (ev, config) { + $mdDialog.show({ + locals: { + config: config + }, + clickOutsideToClose: true, + controller: 'editConfigCtrl', + controllerAs: 'ctrl', + focusOnOpen: false, + targetEvent: ev, + templateUrl: 'admin/editconfig.html', + }).then (function () { + loadConfig(); + }, function(){ + // error + }) + + }; + + loadConfig(); +}); \ No newline at end of file diff --git a/frontend/app/admin/dashboard.html b/frontend/app/admin/dashboard.html new file mode 100644 index 0000000..ce4189b --- /dev/null +++ b/frontend/app/admin/dashboard.html @@ -0,0 +1,131 @@ + +
+ + +
+

{{type}}

+
+
+ + + +

Owed

+

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

+
+ +

Paid

+

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

+
+ +

Mined

+

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

+
+ +

Shares

+

{{stats.shares | number }}

+
+ +

Target Shares

+

{{stats.targetShares | number}}

+
+
+
+
+ + + +
+

Wallet

+
+
+ +
+
+
+

Height

{{ pool_wallet.height | number }}

+
+
+

Unlocked

{{ pool_wallet.unlocked | toXMR }} XMR

+
+
+
+
+

Balance

{{ pool_wallet.balance | toXMR }} XMR

+
+
+

Timestamp

{{ pool_wallet.ts | date }}

+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + +

Height

Unlocked

Balanced

Timestamp

{{history.height | number}}

{{history.balance | toXMR }} XMR

{{history.unlocked | toXMR }} XMR

+

+ + ({{history.ts | date:'hh:mm:ss dd/MM/yy'}}) + +
+
+ +
+
+
+
+ + + + + \ No newline at end of file diff --git a/frontend/app/admin/dashboard.js b/frontend/app/admin/dashboard.js new file mode 100644 index 0000000..3601460 --- /dev/null +++ b/frontend/app/admin/dashboard.js @@ -0,0 +1,17 @@ +'use strict'; + +app.controller('AdminDashCtrl', function($scope, $location, $route, dataService) { + $scope.selected = []; + + dataService.getData("/admin/stats", function(data) { + $scope.pool_stats = data; + }); + + dataService.getData("/admin/wallet", function(data) { + $scope.pool_wallet = data; + }); + + $scope.promise = dataService.getData("/admin/wallet/history", function(data) { + $scope.pool_wallet_history = data; + }); +}); \ No newline at end of file diff --git a/frontend/app/admin/editconfig.html b/frontend/app/admin/editconfig.html new file mode 100644 index 0000000..a0f3f44 --- /dev/null +++ b/frontend/app/admin/editconfig.html @@ -0,0 +1,27 @@ + + + +

Edit Config

+ +

+ error_outline +   All fields are required. +

+ +
+
+ + + + +
+
+ +
+ + + Save + Cancel + + +
\ No newline at end of file diff --git a/frontend/app/admin/editconfig.js b/frontend/app/admin/editconfig.js new file mode 100644 index 0000000..8cd7f5a --- /dev/null +++ b/frontend/app/admin/editconfig.js @@ -0,0 +1,29 @@ +'use strict'; + + +app.controller('editConfigCtrl', function ($scope, $mdDialog, dataService, config) { + 'use strict'; + + $scope.config = config; + var old_value = config.value; + + this.cancel = function (){ + config.value=old_value; + $mdDialog.cancel(); + } + + this.edit = function () { + $scope.item.form.$setSubmitted(); + + if($scope.item.form.$valid) { + dataService.putData('/admin/config', {id: config.id, value: config.value}, function(data) { + $mdDialog.hide(data); + }, function (e) { + // error + $scope.config = old_value; + $mdDialog.hide('error'); + }); + } + }; + +}); \ No newline at end of file diff --git a/frontend/app/admin/editport.html b/frontend/app/admin/editport.html new file mode 100644 index 0000000..19e2725 --- /dev/null +++ b/frontend/app/admin/editport.html @@ -0,0 +1,52 @@ + + + +

Edit Port

+ +

+ error_outline +   All fields are required. +

+
+
+ + + + + + + + + + + + + + + + PPLNS + PPS + SOLO + + + + + Hidden + + + + + SSL + + +
+
+ +
+ + + Save + Cancel + + +
\ No newline at end of file diff --git a/frontend/app/admin/editport.js b/frontend/app/admin/editport.js new file mode 100644 index 0000000..3d0fe6f --- /dev/null +++ b/frontend/app/admin/editport.js @@ -0,0 +1,32 @@ +'use strict'; + + +app.controller('editPortCtrl', function ($scope, $mdDialog, dataService, port) { + 'use strict'; + var old_value = angular.copy(port); + + port.hidden = (port.hidden) ? 1 : 0; + port.ssl = (port.ssl) ? 1 : 0; + + $scope.port = port; + + this.cancel = function (){ + angular.copy(old_value, port); + $mdDialog.cancel(); + } + + this.edit = function () { + $scope.item.form.$setSubmitted(); + + if($scope.item.form.$valid) { + dataService.putData('/admin/ports', $scope.port, function(data) { + $mdDialog.hide(data); + }, function (e) { + // error + $scope.port = old_value; + $mdDialog.hide('error'); + }); + } + }; + +}); \ No newline at end of file diff --git a/frontend/app/admin/ports.html b/frontend/app/admin/ports.html new file mode 100644 index 0000000..8541817 --- /dev/null +++ b/frontend/app/admin/ports.html @@ -0,0 +1,70 @@ + + +
+ Ports + + + add + Add Port + + +
+
+ + +
+ {{selected.length}} {{selected.length > 1 ? 'items' : 'item'}} selected + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +

Port

Difficulty

Description

Type

Hidden

SSL

+ + mode_edit + + {{port.port}}{{port.diff}}{{port.desc}}{{port.portType}} + + done + + + clear + + + + done + + + clear + + + + delete + +
+
+
\ No newline at end of file diff --git a/frontend/app/admin/ports.js b/frontend/app/admin/ports.js new file mode 100644 index 0000000..f967535 --- /dev/null +++ b/frontend/app/admin/ports.js @@ -0,0 +1,67 @@ +'use strict'; + +app.controller('AdminPortsCtrl', function($scope, $location, $route, $mdDialog, dataService) { + $scope.selected = []; + + var loadPorts = function() { + dataService.getData("/admin/ports", function(data) { + $scope.pool_ports = data; + }); + } + + $scope.editPort = function (ev, port) { + $mdDialog.show({ + locals: { + port: port + }, + clickOutsideToClose: true, + controller: 'editPortCtrl', + controllerAs: 'ctrl', + focusOnOpen: true, + targetEvent: ev, + templateUrl: 'admin/editport.html', + }).then (function () { + loadPorts(); + }, function(msg){ + port=msg; + }) + }; + + $scope.addPort = function(ev){ + $mdDialog.show({ + clickOutsideToClose: true, + controller: 'addPortCtrl', + controllerAs: 'ctrl', + focusOnOpen: true, + targetEvent: ev, + templateUrl: 'admin/editport.html', + }).then (function () { + loadPorts(); + }, function(){ + // error + }) + } + + $scope.deletePort = function (ev, port) { + console.log(port); + var confirm = $mdDialog.confirm() + .title('Delete Port ' + port.port +' ?') + .textContent('Are you sure you want to get delete port ' + port.port + '?') + .ariaLabel('Delete Port') + .targetEvent(ev) + .ok("Delete") + .cancel("Cancel"); + + $mdDialog.show(confirm).then(function() { + // ;p + dataService.deleteData("/admin/ports", {port: port.port}, function(data){ + // successfully deleted + loadPorts(); + }); + }, function() { + // cancel do nothing + }); + }; + + loadPorts(); +}); \ No newline at end of file diff --git a/frontend/app/admin/workers.html b/frontend/app/admin/workers.html new file mode 100644 index 0000000..5e1a787 --- /dev/null +++ b/frontend/app/admin/workers.html @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

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}} +

+ + {{worker.lastHash | date:'hh:mm:ss dd/MM/yy'}} + +
+
+ +
\ No newline at end of file diff --git a/frontend/app/admin/workers.js b/frontend/app/admin/workers.js new file mode 100644 index 0000000..999abf5 --- /dev/null +++ b/frontend/app/admin/workers.js @@ -0,0 +1,9 @@ +'use strict'; + +app.controller('AdminWorkersCtrl', function($scope, $location, $route, dataService) { + $scope.selected = []; + + $scope.promise = dataService.getData("/admin/userList", function(data) { + $scope.pool_workers = data; + }); +}); \ No newline at end of file diff --git a/frontend/app/app.css b/frontend/app/app.css new file mode 100644 index 0000000..7fdf73d --- /dev/null +++ b/frontend/app/app.css @@ -0,0 +1,225 @@ +/*** Global Styles ***/ + +html, body { + font-family: 'Source Sans Pro', sans-serif; + font-size:14px; + margin: 0px; + padding: 0px; + min-width: 420px; +} + +a { + transition: color .4s; + color: #3287b3; +} + +a:link { text-decoration: none; }, +a:visited { color: #3b98c8; } +a:hover { color: #3b98c8; } +a:active { + transition: color .3s; + color: #007BE6; +} + +/*** Material Styles ***/ +#main { + background: #f3f5f7; +} + +.maintoolbar { + background-color: #fff; +} + + +.statsbar md-card-title-text{ + text-align: right; +} + +.statsbar md-card md-card-title { + padding: 12px ; +} + +.md-menu-toolbar { + background: #00796B; +} + +md-tooltip .md-content { + height: auto !important; +} + +.navbar .smallfont h3{ + font-size: 0.8em; +} + +.navbar .smallfont h3 b{ + display: inline-block; +} + +.sidenav { + background-color: #2b333e; +} + +.sidenav ul{ + list-style: none; +} + +.sidenav md-list-item { + padding: 0px; +} + +.sidenav a { + width: 100%; + text-align: left; + color: #fff; + margin: 0px; +} + +.sidebar { + display: flex; + flex-wrap: nowrap; + box-sizing: border-box; +} + +.sidebar a { + color: rgb(66,66,66); + text-decoration: none; + margin: 0; + font-size: 16px; + font-weight: 400; + line-height: 24px; + letter-spacing: 0; + opacity: 0.87; +} + +.sidebar md-icon, .sidebar ng-md-icon { + vertical-align: middle; + padding-left: 40px; +} + +#content { + padding: 40px; +} + +.logo { + background: #333d4b !important; + color: #fff !important; +} + +.logo i { + color: #00b0ff; + font-weight: 700; +} + +/* HELPERS */ +.text-left { + text-align: left; +} + +.text-right { + text-align: right; +} + +.text-center { + text-align: center; +} + +.truncate{ + width:30%; + white-space:nowrap; + overflow:hidden; + text-overflow:ellipsis +} + +.hide-error-msg .md-errors-spacer{ + display: none +} + +.menu-item { + position: relative; + width: 72px; + height: 72px; + display: inline-block; + overflow: hidden; + margin: 0px; + vertical-align: middle; + zoom:0.6; + transform: translateZ(0); + -webkit-transform: scale(0.60); + -moz-transform:scale(0.60); + font-size: 72px !important; + color: #aeb7c2; +} + +.valid { + color: green !important; +} + +.invalid { + color: red !important; +} + +.metric md-icon { + font-size: 32px; +} + +.navbar .login { + color: #fff !important; +} + +/* CHARTS */ + +.chart-legend { + display: none; +} + +.chartcontainer { + width: 100% !important; + height: 300px; +} + +.chartcontainer .ng-isolate-scope { + height:300px; +} + +/* LOGIN WINDOW */ +md-dialog .login{ + min-width: 800px !important; +} + +.console { + height: 300px; + min-width: 500px; +} + +.no-padding { + padding: 0px; +} + +.power { + width: 100%; + color: #fff; +} + +.power a { + color: #f2ff2f; +} + +.power a:hover { + color: #f2f; +} + +.power a.alt { + color: #f2f; +} + +.power a.alt:hover { + color: #f2ff2f; +} + +.fixed { + position: fixed; +} + +.theironfist { + cursor: pointer; +} \ No newline at end of file diff --git a/frontend/app/app.js b/frontend/app/app.js new file mode 100644 index 0000000..6e12c81 --- /dev/null +++ b/frontend/app/app.js @@ -0,0 +1,230 @@ +'use strict'; + +// Declare app level module which depends on views, and components +var app = angular.module('poolui', [ + 'pool.globals', + 'ngRoute', + 'ngMaterial', + 'md.data.table', + 'angularMoment', + 'ngStorage', + 'ngAudio', + 'utils.strings', + 'utils.services', + 'utils.xhr', + 'n3-line-chart', + 'angular-page-visibility' + ]).config(['$locationProvider', '$routeProvider', '$mdThemingProvider', function($locationProvider, $routeProvider, $mdThemingProvider) { + $locationProvider.hashPrefix('') + ; + $mdThemingProvider.theme('default') + .primaryPalette('grey') + .accentPalette('light-blue'); + + $routeProvider + .when('/home', { + templateUrl: 'user/home/home.html', + controller: 'HomeCtrl', + activetab: 'home' + }) + .when('/dashboard', { + templateUrl: 'user/dashboard/dashboard.html', + controller: 'DashboardCtrl', + activetab: 'dashboard' + }) + .when('/blocks', { + templateUrl: 'user/blocks/blocks.html', + controller: 'BlocksCtrl', + activetab: 'blocks' + }) + .when('/payments', { + templateUrl: 'user/payments/payments.html', + controller: 'PaymentsCtrl', + activetab: 'payments' + }) + .when('/network', { + templateUrl: 'user/network/network.html', + controller: 'NetworkCtrl', + activetab: 'network' + }) + .when('/ports', { + templateUrl: 'user/ports/ports.html', + controller: 'PortsCtrl', + activetab: 'ports' + }) + .when('/help/chat', { + templateUrl: 'user/help/chat.html', + controller: 'ChatCtrl', + activetab: 'support' + }) + .when('/help/getting_started', { + templateUrl: 'user/help/getting_started.html', + controller: 'GettingStartedCtrl', + activetab: 'help' + }) + .when('/help/faq', { + templateUrl: 'user/help/faq.html', + controller: 'FAQCtrl', + activetab: 'help' + }); + + $routeProvider.otherwise({redirectTo: '/home'}); + + }]); + + app.controller('AppCtrl', function($scope, $rootScope, $location, $route, $routeParams, $anchorScroll, $window, $interval, $mdDialog, dataService, timerService, addressService, $mdSidenav, $mdMedia, $localStorage, ngAudio, GLOBALS){ + $scope.GLOBALS = GLOBALS; + var appCache = window.applicationCache; + $scope.$storage = $localStorage; + + $scope.poolList = ["pplns", "pps", "solo"]; + $scope.poolStats = {}; // All Pool stats + $scope.addrStats = {}; // All tracked addresses + $scope.lastBlock = {}; + + // for miner tracking + $scope.yourTotalHashRate = 0; + + // Hashrate Alarm + $scope.globalSiren = false; + $scope.sirenAudio = ngAudio.load("assets/ding.wav"); + + // Update global hashrate and set off alarm if any of the tracked addresses fall below the threshold + var updateHashRate = function (addrStats){ + var totalHashRate = 0; + var siren = false; + + _.each(addrStats, function(addr,index) { + totalHashRate += addr.hash; + if (addr.alarm && addr.hash < addr.alarmLimit) { + siren=true; + } + }); + + $scope.globalSiren=siren; + $scope.yourTotalHashRate = totalHashRate; + } + + var playSiren = function (){ + ($scope.globalSiren) ? $scope.sirenAudio.play() : $scope.sirenAudio.stop(); + } + + // ------- UI HELPERS + + $scope.menuOpen = $mdMedia('gt-md'); + $scope.$watch(function() { return $mdMedia('gt-md'); }, function(big) { + $scope.menuOpen = $mdMedia('gt-md'); + }); + + $scope.toggleSidenav = function (){ + if (!$mdMedia('gt-md')) { + $mdSidenav('left').toggle(); + } else { + // toggle boolean + $scope.menuOpen = !$scope.menuOpen; + } + } + + // ------- Miner Login and auth + $scope.minerLogin = function (ev) { + $mdDialog.show({ + controller: "LoginCtrl", + templateUrl: 'user/home/login.html', + parent: angular.element(document.body), + targetEvent: ev, + clickOutsideToClose:true, + fullscreen: !$scope.menuOpen // Only for -xs, -sm breakpoints. + }) + .then(function(answer) { + // success callback + }, function(error) { + // error callback + }); + } + + $scope.minerConsole = function (ev) { + $mdDialog.show({ + locals: $scope.config, + controller: "ConsoleCtrl", + templateUrl: 'user/home/console.html', + parent: angular.element(document.body), + targetEvent: ev, + clickOutsideToClose:true, + fullscreen: !$scope.menuOpen // Only for -xs, -sm breakpoints. + }) + .then(function(answer){ + if(answer=='logout'){ + dataService.logout(); + } + }, function(reason){ + // console.log(reason); + }); + } + + $scope.isLoggedIn = function() { + return dataService.isLoggedIn(); + } + + // ------- App Update + var update = function() { + if (appCache.status == window.applicationCache.UPDATEREADY) { + appCache.swapCache(); + $window.location.reload(); + } + } + + appCache.addEventListener("updateready", function(event) { + update(); + }, false); + + var updateCache = function () { + appCache.update(); + } + + // API Requests + var loadData = function () { + dataService.getData("/pool/stats", function(data){ + $scope.poolList = data.pool_list; + $scope.poolStats.global = data.pool_statistics; + }); + + dataService.getData("/network/stats", function(data){ + $scope.network = data; + }); + } + + var loadOnce = function () { + dataService.getData("/config", function(data){ + $scope.config = data; + }); + } + + // For FAQ + $rootScope.$on('$routeChangeSuccess', function(newRoute, oldRoute) { + $location.hash($routeParams.scrollTo); + $anchorScroll(); + }); + + // Start doing things + loadOnce(); + loadData(); + update(); + + // Start the timer and register global requests + timerService.startTimer(GLOBALS.api_refresh_interval); + timerService.register(loadData, 'global'); + $interval(updateCache, GLOBALS.app_update_interval); // check for app updates every 5 mins + + // Start address tracking servuce after starting timer, only one callback supported at a time + addressService.start(function(addrStats) { + $scope.addrStats = addrStats; + updateHashRate(addrStats); + playSiren(); + } + ); + + + // Sponsor + $scope.sponsor_open = false + + }); \ No newline at end of file diff --git a/frontend/app/assets/cloudsigma.png b/frontend/app/assets/cloudsigma.png new file mode 100644 index 0000000..dd9e77c Binary files /dev/null and b/frontend/app/assets/cloudsigma.png differ diff --git a/frontend/app/assets/ding.wav b/frontend/app/assets/ding.wav new file mode 100644 index 0000000..e5e421b Binary files /dev/null and b/frontend/app/assets/ding.wav differ diff --git a/frontend/app/assets/sponsor-logo.png b/frontend/app/assets/sponsor-logo.png new file mode 100644 index 0000000..58bde4b Binary files /dev/null and b/frontend/app/assets/sponsor-logo.png differ diff --git a/frontend/app/globals.default.js b/frontend/app/globals.default.js new file mode 100644 index 0000000..e2743e0 --- /dev/null +++ b/frontend/app/globals.default.js @@ -0,0 +1,12 @@ +'use strict'; + +angular.module('pool.globals', []) + +.factory('GLOBALS', function() { + return { + pool_name: "XMRPool.net", + api_url : 'https://api.xmrpool.net', + api_refresh_interval: 5000, + app_update_interval: 30*60000 + }; +}); \ No newline at end of file diff --git a/frontend/app/globals.js b/frontend/app/globals.js new file mode 100644 index 0000000..3456a28 --- /dev/null +++ b/frontend/app/globals.js @@ -0,0 +1,12 @@ +'use strict'; + +angular.module('pool.globals', []) + +.factory('GLOBALS', function() { + return { + pool_name: "XMRPool.net", + api_url : 'https://api.xmrpool.net', + api_refresh_interval: 5000, + app_update_interval: 5*60000 + }; +}); \ No newline at end of file diff --git a/frontend/app/index.html b/frontend/app/index.html new file mode 100644 index 0000000..e1cdbee --- /dev/null +++ b/frontend/app/index.html @@ -0,0 +1,158 @@ + + + + + + + XMRPool.net - Mine XMR/Monero or BTC/Bitcoin + + + + + + + + + + + + + + + + + + +
+ + + + + + home + Home + + + + + dashboard + Dashboard + + + + + reorder + Blocks + + + + + payments + Payment + + + + + flight_land + Ports + + + + + language + Network + + + + + group + Support + + + + + launch + Getting Started + + + + + help_outline + FAQ + + + +
+
+
+ Powered by nodejs-pool & poolui +
+ MIT Licence +
+
+
+ + + + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/app/user/blocks/blocks.html b/frontend/app/user/blocks/blocks.html new file mode 100644 index 0000000..0af08d3 --- /dev/null +++ b/frontend/app/user/blocks/blocks.html @@ -0,0 +1,66 @@ + +
+
+ + +
+ Blocks Found +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Valid

Time Found

Height

Difficulty

Hash

Shares

Luck

Maturity

Pool

+ + {{::block.icon}} + + +

+ + {{::block.ts | date:"hh:mm:ss dd/MM/yy"}} + +

{{::block.height | number}}

{{::block.diff | number}}

{{::block.shares | number}}

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

+ +

+ lock_open + + {{-block.maturity}} blocks ago + +

+

+ {{block.maturity}} to go +

+

{{::block.pool_type}}

+
+ +
+
+
+
\ No newline at end of file diff --git a/frontend/app/user/blocks/blocks.js b/frontend/app/user/blocks/blocks.js new file mode 100644 index 0000000..abadc5b --- /dev/null +++ b/frontend/app/user/blocks/blocks.js @@ -0,0 +1,47 @@ +'use strict'; + +app.controller('BlocksCtrl', function($scope, $route, dataService, timerService) { + $scope.blocks = {}; + $scope.selected = []; + + $scope.options = { + page: 1, + limit: 15 + } + + $scope.loadBlocks = function () { + var params = angular.copy($scope.options); + params.page -= 1; + var urlParams = $.param(params) + $scope.promise = dataService.getData("/pool/blocks?"+urlParams, function(data){ + $scope.blocks.global = data; + updateMaturity(); + }); + }; + + var updateMaturity = function () { + var luck; + if($scope.poolStats.global !== undefined){ + _.each($scope.blocks.global, function(block, index){ + if($scope.network !== undefined) { + $scope.blocks.global[index].maturity = $scope.config.maturity_depth - ($scope.network.height - block.height); + } + + // calculate luck + luck = block.shares/block.diff*100; + $scope.blocks.global[index].luck = (luck <= 100) ? (100-luck) : (-luck+100) ; + $scope.blocks.global[index].icon = (block.valid) ? 'done' : 'clear'; + }); + } + } + + $scope.$watchGroup(["blocks.global", "poolStats.global"], updateMaturity); + + // Register call with timer + timerService.register($scope.loadBlocks, $route.current.controller); + $scope.loadBlocks(); + + $scope.$on("$routeChangeStart", function () { + timerService.remove($route.current.controller); + }); +}); \ No newline at end of file diff --git a/frontend/app/user/dashboard/dashboard.html b/frontend/app/user/dashboard/dashboard.html new file mode 100644 index 0000000..925035c --- /dev/null +++ b/frontend/app/user/dashboard/dashboard.html @@ -0,0 +1,240 @@ + +
+
+ + +
+ Network Stats +
+
+ + + +

Hash Rate

+

{{network.difficulty | difficultyToHashRate | toHashRate}}

+
+ +

Difficulty

+

{{network.difficulty | number }}

+
+ +

Hash

+

+
+ +

Height

+

{{network.height | number }}

+
+ +

Reward

+

{{network.value | toXMR}}

+
+ +

Time Found

+

+
+
+
+
+
+
+ + +
+ Pool Stats +
+
+ + + +
+
+ + +

Hash Rate

+

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

+
+ +

Height

+

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

+
+ +

Last Block

+

+
+ +

Block Reward

+

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

+
+ +

Time Found

+

+ Never + + {{poolStats[pooltype].pool_statistics.lastBlockFoundTime*1000 | date:'hh:mm:ss dd/MM/yy'}} + +

+
+
+
+
+ + +

Fees

+

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

+
+ +

Blocks Found

+

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

+
+ +

Miners

+

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

+
+ +

Miners Paid

+

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

+
+ +

Payments sent

+

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

+
+
+
+
+
+
+
+
+
+
+ +
+
+ + + + account_balance_wallet + + +
+ + add + Track Live Stats + +
+
+
+ +
+ + +
+ + + + account_balance + + + {{addr}} + + + + ( Last Hash : Never ) + + {{miner.lastHash*1000 | date:'hh:mm:ss dd/MM/yy'}} + + + + + + clear + + +
+
+ +
+
+
+

Hash Rate

{{miner.hash | toHashRate}}

+
+
+

Total Hashes

{{miner.totalHashes | number}}

+
+
+
+
+
Total Due

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

+
+
+
Total Paid

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

+
+
+
+
+ +
+ +
+
+ +
+ + + + + + + + + + + + + + + + + + +

Worker

#'s

Total #'s

Last Hash

{{id}}

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

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

+ + + {{minerStats[addr].dataset[id][0].ts | date: 'hh:mm:ss dd/MM/yy'}} + +
+
+
+
+ +
+
+
+ +
+
+

Valid Shares

{{miner.validShares | number}} check

+

Invalid Shares

{{miner.invalidShares | number}} clear

+
+
+
+
+ + +
+
+ + + + + + alarm + +
+
+ + View Payments +
+
+
\ No newline at end of file diff --git a/frontend/app/user/dashboard/dashboard.js b/frontend/app/user/dashboard/dashboard.js new file mode 100644 index 0000000..1dd295b --- /dev/null +++ b/frontend/app/user/dashboard/dashboard.js @@ -0,0 +1,110 @@ +'use strict'; + +app.controller('DashboardCtrl', function($scope , $route, $mdDialog, $pageVisibility, dataService, timerService, addressService, minerService) { + $scope.minerStats = {}; + + $scope.updateCharts = function (){ + minerService.updateStats($scope.addrStats, function(minerStats){ + $scope.minerStats = minerStats; + }); + } + + // Update miners everyime addrStats + $scope.$parent.$watch('addrStats', function(newValue, oldValue) { + $scope.updateCharts(); + }); + + $scope.addAddress = function (){ + if ($scope.paymentAddress){ + addressService.trackAddress($scope.paymentAddress); + $scope.paymentAddress = ""; + } + }; + + $scope.deleteAddress = function (key, ev){ + var confirm = $mdDialog.confirm() + .title('Hide live stats?') + .textContent('You can add it back by entering your wallet address again') + .ariaLabel('Stop tracking payment address') + .targetEvent(ev) + .ok("Remove") + .cancel("Cancel"); + + $mdDialog.show(confirm).then(function() { + addressService.deleteAddress(key); + }, function() { + // cancel do nothing + }); + } + + $scope.setAlarm = function(addr, bool){ + addressService.setAlarm(addr, bool); + }; + + $scope.viewPayments = function(ev, miner, addr){ + $mdDialog.show({ + locals: { + miner: miner, + addr: addr + }, + controller: "MinerPaymentsCtrl", + templateUrl: 'user/dashboard/minerpayments.html', + parent: angular.element(document.body), + targetEvent: ev, + clickOutsideToClose:true, + fullscreen: !$scope.menuOpen + }) + .then(function(answer) { + $scope.status = 'You said the information was "' + answer + '".'; + }, function() { + $scope.status = 'You cancelled the dialog.'; + }); + } + + // Recurring API calls and timer + var loadData = function () { + _.each($scope.poolList, function(pool_type) { + dataService.getData("/pool/stats/"+pool_type, function(data){ + $scope.poolStats[pool_type] = data; + }); + + dataService.getData("/pool/blocks/"+pool_type, function(data){ + if (data.length > 0){ + $scope.lastBlock[pool_type] = data[0]; + } else { + $scope.lastBlock[pool_type] = { + ts: 0, + hash: "", + diff: 0, + shares: 0, + height: 0, + valid: false, + unlocked: false, + pool_type: pool_type, + value: 0 + } + } + }); + }); + + // Call minerservice update + $scope.updateCharts(); + }; + + // No spawn xhr reqs in bg + $pageVisibility.$on('pageFocused', function(){ + minerService.runService(true); + }); + + $pageVisibility.$on('pageBlurred', function(){ + minerService.runService(false); + }); + + // Register call with timer + timerService.register(loadData, $route.current.controller); + loadData(); + + $scope.$on("$routeChangeStart", function () { + timerService.remove($route.current.controller); + }); +}); \ No newline at end of file diff --git a/frontend/app/user/dashboard/minerpayments.html b/frontend/app/user/dashboard/minerpayments.html new file mode 100644 index 0000000..5aaf08e --- /dev/null +++ b/frontend/app/user/dashboard/minerpayments.html @@ -0,0 +1,54 @@ + +
+ +
+

Payment History

+ + + clear + +
+
+ +

To : {{addr}}

+
+ + + + + + + + + + + + + + + + + + + + + + +

Time

Type

Amount

Txn Hash

Mixins

+

+ + {{payment.ts*1000 | date:'hh:mm:ss dd/MM/yy'}} + +

{{payment.pt}}

{{payment.amount | toXMR }} XMR

{{payment.mixin}}

+
+ +
+ + + + + Close + + +
+
\ No newline at end of file diff --git a/frontend/app/user/dashboard/minerpayments.js b/frontend/app/user/dashboard/minerpayments.js new file mode 100644 index 0000000..640774d --- /dev/null +++ b/frontend/app/user/dashboard/minerpayments.js @@ -0,0 +1,28 @@ +'use strict'; + +app.controller('MinerPaymentsCtrl', function($scope, $mdDialog, dataService, miner, addr) { + $scope.miner = miner; + $scope.addr = addr; + $scope.selected = []; + + $scope.options = { + page: 1, + limit: 15 + } + + $scope.loadPayments = function () { + var params = angular.copy($scope.options); + params.page -= 1; + var urlParams = $.param(params) + + dataService.getData("/miner/"+addr+"/payments?"+urlParams, function(data){ + $scope.payments = data; + }); + } + + $scope.loadPayments(); + + $scope.answer = function (answer) { + $mdDialog.hide('close') + } +}); \ No newline at end of file diff --git a/frontend/app/user/dashboard/poolstats.html b/frontend/app/user/dashboard/poolstats.html new file mode 100644 index 0000000..a98da15 --- /dev/null +++ b/frontend/app/user/dashboard/poolstats.html @@ -0,0 +1,72 @@ +
+
+
+ + + +
+ multiline_chart +
+
+ + {{poolStats.global.totalHashes | number}} + Hashes Accepted + +
+
+
+
+ + + +
+ widgets +
+
+ +
{{poolStats.global.totalBlocksFound}}
+
+ Blocks Found () + + {{poolStats.global.lastBlockFoundTime*1000 | date:'hh:mm:ss dd/MM/yy'}} + +
+
+
+
+
+
+
+
+ + + +
+ memory +
+
+ + {{poolStats.global.miners}} + Miners Connected + +
+
+
+
+ + + +
+ attach_money +
+
+ + {{poolStats.global.totalMinersPaid}} + Miners Paid ({{poolStats.global.totalPayments}} Payments) + + +
+
+
+
+
\ No newline at end of file diff --git a/frontend/app/user/help/chat.html b/frontend/app/user/help/chat.html new file mode 100644 index 0000000..70f5219 --- /dev/null +++ b/frontend/app/user/help/chat.html @@ -0,0 +1,16 @@ + +
+
+ + +
+ Support on #monero-pools (Ask for help or say hello!) +
+
+ + +
+ + +
+ \ No newline at end of file diff --git a/frontend/app/user/help/chat.js b/frontend/app/user/help/chat.js new file mode 100644 index 0000000..01a8b26 --- /dev/null +++ b/frontend/app/user/help/chat.js @@ -0,0 +1,4 @@ +'use strict'; + +app.controller('ChatCtrl', function() { +}); \ No newline at end of file diff --git a/frontend/app/user/help/faq.html b/frontend/app/user/help/faq.html new file mode 100644 index 0000000..d1c9232 --- /dev/null +++ b/frontend/app/user/help/faq.html @@ -0,0 +1,33 @@ +
+ + + Pool FAQ + + +
+

{{topic}}

+
+

{{question.title}}

+

+
+

{{question.media.title}}

+
+ +
+
+
+ +
+
+
+
+ + +
Contents
+
+

{{topic}}

+

{{q.title}}

+
+
+
+
\ No newline at end of file diff --git a/frontend/app/user/help/faq.js b/frontend/app/user/help/faq.js new file mode 100644 index 0000000..09778a4 --- /dev/null +++ b/frontend/app/user/help/faq.js @@ -0,0 +1,116 @@ +'use strict'; + +app.controller('FAQCtrl', function($scope, $location, $anchorScroll, $sce, dataService) { + // BooHoo:p + $scope.goto = function(topic, index) { + var newHash = topic + '_' + index; + if ($location.hash() !== newHash) { + // set the $location.hash to `newHash` and + // $anchorScroll will automatically scroll to it + $location.hash(newHash); + } else { + // call $anchorScroll() explicitly, + // since $location.hash hasn't changed + $anchorScroll(); + } + }; + + $scope.faq = { + "General" : [ + { + title: "What is Monero?", + 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.
"), + media: + // { + // "title": "Simple", + // "url": $sce.trustAsResourceUrl("https://www.youtube.com/embed/TZi9xx6aiuY?ecver=1") + // } + { + "title": "Monero essentials video", + "url": $sce.trustAsResourceUrl("https://www.youtube.com/embed/6DQb0cMvU7I?ecver=1") + } + }, + + { + title: "What is mining and why should I be interested?", + 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.") + }, + { + title: "How do I start mining?", + 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.") + }, + { + title: "What is pool mining?", + 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.") + }, + { + title: "What is PPLNS?", + 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.") + }, + { + title: "What does SOLO mining mean?", + 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.") + }, + { + title: "Is mining profitable?", + 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.") + }, + ], + "Pool Help": [ + { + title: "How payouts work?", + 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.") + }, + { + title: "Payout thresholds?", + 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.") + }, + { + title: "Why hasn't my \"Total Due\" amount increased?", + 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.") + }, + { + title: "Getting paid in BTC", + 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.") + }, + { + title: "Payments to exchanges/markets?", + 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.") + }, + { + title: "IP Banning?", + 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.") + }, + { + title: "How Fixed / Variable Difficulty works", + 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.") + }, + { + title: "Can i mine other coins?", + answer: $sce.trustAsHtml("Not yet, but we may add more soon. Follow https://github.com/Snipa22/nodejs-pool/issues/27.") + } + ], + "Mining":[ + { + title: "Hardware?", + 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.") + }, + { + title: "Software?", + answer: $sce.trustAsHtml("Read -- Getting Started.") + } + ], + "Support":[ + { + title: "Chat Support", + 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.") + }, + { + title: "Interesting links.", + answer: $sce.trustAsHtml("http://reddit.com/r/moneromining/
http://monero.stackexchange.com/") + } + ] + } + +// end +}); diff --git a/frontend/app/user/help/getting_started.html b/frontend/app/user/help/getting_started.html new file mode 100644 index 0000000..c62a37b --- /dev/null +++ b/frontend/app/user/help/getting_started.html @@ -0,0 +1,98 @@ +
+
+ + +
+ Getting Started with {{GLOBALS.pool_name}} +
+
+ +

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

+

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

+
+
+ + +
+ 1. Create a Wallet +
+
+ +

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

+

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

+

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

+
+
+ + +
+ 2. Download mining software +
+
+ +

AMD Miner

+

Nvidia Miner

+

CPU Miner (AESNI CPU's)

+

CPU Miner (Older CPU's})

+
+
+ + +
+ 3. Pick a server and port +
+
+ +

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

+

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

+

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.

+

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

+

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).

+

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

+
+
+ View Ports list +
+
+ +
+
+ + +
+ The moment +
+
+ +

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

+
+ +

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

+

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

+

Required fields are payment address and MinerIdentifier

+
+ Username format : address.paymentID+FixedDifficulty
+ Password Format : MinerIdentifier:Email
+
+ + Username / Password Samples + + +
+

## {{ sample.desc }}

+
+

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

+ (e.g. miner.exe -u {{(sample.type=="Username") ? sample.sample : 'paymentAddress'}} -p {{(sample.type=="Password") ? sample.sample : 'worker'}})
+
+
+ +
+
+
+
+

Happy Mining !

+
+
diff --git a/frontend/app/user/help/getting_started.js b/frontend/app/user/help/getting_started.js new file mode 100644 index 0000000..cfe8a02 --- /dev/null +++ b/frontend/app/user/help/getting_started.js @@ -0,0 +1,96 @@ +'use strict'; + +app.controller('GettingStartedCtrl', function($scope, $mdDialog, dataService) { + $scope.portsList = {}; + $scope.selected = []; + + $scope.promise = dataService.getData("/pool/ports", function(data){ + $scope.portsList = data; + }); + + $scope.viewPorts = function(ev){ + $mdDialog.show({ + controller: "PortsModalCtrl", + templateUrl: 'user/help/portsmodal.html', + parent: angular.element(document.body), + targetEvent: ev, + clickOutsideToClose:true, + fullscreen: $scope.menuOpen // Only for -xs, -sm breakpoints. + }) + .then(function(answer) { + $scope.status = 'You said the information was "' + answer + '".'; + }, function() { + $scope.status = 'You cancelled the dialog.'; + }); + } + + $scope.samples=[ + { + type: 'Username', + sample: '43To46Y9AxNFkY5rsMQaLwbRNaxLZVvc4LJZt7Cx9Dt23frL6aut2uC3PsMiwGY5C5fKLSn6sWyoxRQTK1dhdBpKAX8bsUW', + desc: 'Standard address for withdraws to a wallet (CLI/GUI/MyMonero)', + valid: true + }, + { + type: 'Username', + sample: '4DAU4uMdnDtFkY5rsMQaLwbRNaxLZVvc4LJZt7Cx9Dt23frL6aut2uC3PsMiwGY5C5fKLSn6sWyoxRQTK1dhdBpKF82nvn2H6jg9SUywAX', + desc: 'Integrated address withdraw for exchange (TuxExchange)', + valid: true + }, + { + type: 'Username', + sample: '43To46Y9AxNFkY5rsMQaLwbRNaxLZVvc4LJZt7Cx9Dt23frL6aut2uC3PsMiwGY5C5fKLSn6sWyoxRQTK1dhdBpKAX8bsUW.6FEBAC2C05EDABB16E451D824894CC48AE8B645A48BD4C4F21A1CC8624EB0E6F', + desc: 'Standard exchange withdrawl (Poloniex/etc.)', + valid: true + }, + { + type: 'Username', + sample: '1KEJ7EJvfD2bpL6vA9nJpTEgoS9P5jdyce', + desc: 'BTC Withdrawal (Will process through xmr.to or shapeshift.io automatically)', + valid: true + }, + { + type: 'Username', + sample: '4DAU4uMdnDtFkY5rsMQaLwbRNaxLZVvc4LJZt7Cx9Dt23frL6aut2uC3PsMiwGY5C5fKLSn6sWyoxRQTK1dhdBpKF82nvn2H6jg9SUywAX.6FEBAC2C05EDABB16E451D824894CC48AE8B645A48BD4C4F21A1CC8624EB0E6F', + desc: 'Integrated address withdraw for exchange w/ paymentID', + valid: false + }, + { + type: 'Username', + sample: '1KEJ7EJvfD2bpL6vA9nJpTEgoS9P5jdyce+100000', + desc: 'BTC Withdrawal w/ fixed diff (Good for NiceHash)', + valid: true + }, + { + type: 'Username', + sample: '43To46Y9AxNFkY5rsMQaLwbRNaxLZVvc4LJZt7Cx9Dt23frL6aut2uC3PsMiwGY5C5fKLSn6sWyoxRQTK1dhdBpKAX8bsUW+3500', + desc: 'Standard address for withdraws to a wallet w/ fixed diff (Good for NiceHash)', + valid: true + }, + { + type: 'Username', + sample: '43To46Y9AxNFkY5rsMQaLwbRNaxLZVvc4LJZt7Cx9Dt23frL6aut2uC3PsMiwGY5C5fKLSn6sWyoxRQTK1dhdBpKAX8bsUW.6FEBAC2C05EDABB16E451D824894CC48AE8B645A48BD4C4F21A1CC8624EB0E6F+23472', + desc: 'Standard exchange withdrawl w/ fixed diff (Good for NiceHash)', + valid: true + }, + { + type: 'Password', + sample: 'Steve', + desc: 'Miner identifier of Steve', + valid: true + }, + { + type: 'Password', + sample: 'x:test@e-mail.com', + desc: 'Miner identifier of x, registers address + paymentID if there is one, to the e-mail address for notification', + valid: true + }, + { + type: 'Password', + sample: 'test@e-mail.com', + desc: 'Will register the e-mail address as the worker ID', + valid: true + } + ] + +}); \ No newline at end of file diff --git a/frontend/app/user/help/portsmodal.html b/frontend/app/user/help/portsmodal.html new file mode 100644 index 0000000..2dca987 --- /dev/null +++ b/frontend/app/user/help/portsmodal.html @@ -0,0 +1,53 @@ + +
+ +
+

Ports List

+ + + clear + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

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}}

+
+
+ +
+ + + + Close + + +
+
\ No newline at end of file diff --git a/frontend/app/user/help/portsmodal.js b/frontend/app/user/help/portsmodal.js new file mode 100644 index 0000000..65d5d53 --- /dev/null +++ b/frontend/app/user/help/portsmodal.js @@ -0,0 +1,13 @@ +'use strict'; + +app.controller('PortsModalCtrl', function($scope, $mdDialog, dataService) { + $scope.selected = []; + + $scope.promise = dataService.getData("/pool/ports", function(data){ + $scope.portsList = data; + }); + + $scope.answer = function (answer) { + $mdDialog.hide('close') + } +}); \ No newline at end of file diff --git a/frontend/app/user/home/console.html b/frontend/app/user/home/console.html new file mode 100644 index 0000000..36b1559 --- /dev/null +++ b/frontend/app/user/home/console.html @@ -0,0 +1,73 @@ + +
+ +
+

Miner Console

+ + + clear + +
+
+ + + + +
+

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

+ + + + + +
+
+ This value cannot be lower than {{min_wallet_payout | toXMR}} XMR +
+
+

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

+ +
+
+ +
+
+ Receive miner hashrate alerts +
+
+
+ + +
+
+ + + + + + + +
+
+ Please confirm your new password +
+
+
+
+
+
+
+
+ + + + Logout + + +

{{status}}

+ + Update {{currentTab}} + +
+ +
\ No newline at end of file diff --git a/frontend/app/user/home/console.js b/frontend/app/user/home/console.js new file mode 100644 index 0000000..6694665 --- /dev/null +++ b/frontend/app/user/home/console.js @@ -0,0 +1,95 @@ +'use strict'; + +app.controller('ConsoleCtrl', function($scope, $route, $filter, $timeout, $mdDialog, min_wallet_payout, dataService, timerService) { + $scope.paymentThresh; + $scope.min_wallet_payout = min_wallet_payout; + $scope.currentTab = 'Threshold'; // default tab + $scope.status = ""; + $scope.statusClass = "valid"; + $scope.password = { + pwd: "", + cnf: "" + } + + var email_enabled; + + var getConfig = function () { + dataService.getData("/authed", function(data){ + $scope.paymentThresh = $filter('toXMR')(data.msg.payout_threshold); + email_enabled = data.msg.email_enabled; + $scope.email_toggle = data.msg.email_enabled; + }); + } + + var updateThreshold = function () { + dataService.postData("/authed/changePayoutThreshold", {threshold: $scope.paymentThresh},function(data){ + //$mdDialog.hide('updated'); + $scope.statusClass = "valid"; + $scope.status = "Threshold Saved"; + messageFlash(); + }); + } + + var updatePassword = function () { + if($scope.password.pwd == $scope.password.cnf && $scope.password.pwd !== "") { + dataService.postData("/authed/changePassword", {password: $scope.password.pwd},function(data){ + //$mdDialog.hide('updated'); + $scope.statusClass = "valid"; + $scope.status = "Password Saved"; + messageFlash(); + }); + } else { + $scope.statusClass = "invalid"; + $scope.status = "Check passwords"; + messageFlash(); + } + } + + var updateEmail = function () { + if($scope.email_toggle!=email_enabled){ + dataService.postData("/authed/toggleEmail", {}, function(data) { + $scope.status = data.msg; + email_enabled=$scope.email_toggle; + messageFlash(); + }); + } else { + $scope.statusClass = "invalid"; + $scope.status = "No Change..."; + messageFlash(); + } + } + + var messageFlash = function(){ + $timeout(function() { + $scope.status = ""; + $scope.statusClass = "valid"; + },2000); + } + + $scope.save = function () { + $scope.statusClass = "valid"; + $scope.status = "Saving...";// + $scope.currentTab; + switch ($scope.currentTab){ + case 'Threshold': + updateThreshold(); + break; + + case 'Password': + updatePassword(); + + case 'Email': + updateEmail(); + } + } + + $scope.logout = function () { + $mdDialog.hide('logout'); + } + + getConfig(); + + // Dialog methods + $scope.cancel = function () { + $mdDialog.cancel(); + } +}); \ No newline at end of file diff --git a/frontend/app/user/home/home.html b/frontend/app/user/home/home.html new file mode 100644 index 0000000..0b4adcb --- /dev/null +++ b/frontend/app/user/home/home.html @@ -0,0 +1,69 @@ + +
+ + +
+ + + Pool Config + + + + + +

PPLNS Fee

+ + {{config.pplns_fee}}% + + Core Devs Donation: {{config.dev_donation/100*config.pplns_fee | number:3 }}% | + Pool Devs Donation:{{config.pool_dev_donation/100*config.pplns_fee | number:3 }}% + + +
+ +

Solo Fee

+

+ {{config.solo_fee}}% + + Core Devs Donation: {{config.dev_donation/100*config.solo_fee | number:3}}% | + Pool Devs Donation:{{config.pool_dev_donation/100*config.solo_fee | number:3}}% + +

+
+ +

BTC Fee

+

{{config.btc_fee}}%

+
+ +

Minimum payout (Wallet)

+

{{config.min_wallet_payout | toXMR}} XMR

+
+ +

Minimum payout (BTC)

+

{{config.min_btc_payout | toXMR}} XMR

+
+ +

Minimum payout (Exchange)

+

{{config.min_exchange_payout | toXMR}} XMR

+
+ +

Block Maturity Depth

+

{{config.maturity_depth}}

+
+ +

Minimum Denomination

+

{{config.min_denom | toXMR}}

+
+
+
+
+
\ No newline at end of file diff --git a/frontend/app/user/home/home.js b/frontend/app/user/home/home.js new file mode 100644 index 0000000..6275a5a --- /dev/null +++ b/frontend/app/user/home/home.js @@ -0,0 +1,5 @@ +'use strict'; + +app.controller('HomeCtrl', function($scope, $route, dataService, timerService) { + +}); \ No newline at end of file diff --git a/frontend/app/user/home/login.html b/frontend/app/user/home/login.html new file mode 100644 index 0000000..78c2e19 --- /dev/null +++ b/frontend/app/user/home/login.html @@ -0,0 +1,43 @@ + + + \ No newline at end of file diff --git a/frontend/app/user/home/login.js b/frontend/app/user/home/login.js new file mode 100644 index 0000000..34f52f3 --- /dev/null +++ b/frontend/app/user/home/login.js @@ -0,0 +1,29 @@ +'use strict'; + +app.controller('LoginCtrl', function($scope, $route, $mdDialog, dataService, timerService) { + $scope.user = { + username: "", + password: "" + } + + $scope.remember = false; + $scope.status = ""; + + $scope.login = function () { + dataService.postData("/authenticate", $scope.user, function(data){ + if (data.success){ + data.remember = $scope.remember; + dataService.setAuthToken(data); + $mdDialog.hide(data); + } else { + $mdDialog.hide(false); + } + }, function(error){ + $scope.status = "Please check your login details"; + }); + } + + $scope.cancel = function () { + $mdDialog.cancel(); + } +}); \ No newline at end of file diff --git a/frontend/app/user/network/network.html b/frontend/app/user/network/network.html new file mode 100644 index 0000000..a8e5905 --- /dev/null +++ b/frontend/app/user/network/network.html @@ -0,0 +1,5 @@ +
+
+

Coming SoOn..

+
+
\ No newline at end of file diff --git a/frontend/app/user/network/network.js b/frontend/app/user/network/network.js new file mode 100644 index 0000000..e9307d1 --- /dev/null +++ b/frontend/app/user/network/network.js @@ -0,0 +1,22 @@ +'use strict'; + +app.controller('NetworkCtrl', function($scope, $route, dataService, timerService) { + + + var loadData = function () { + console.log("Getting Network Data"); + + dataService.getData("/network/chart/usdHash/60", function(data){ + $scope.config = data; + console.log(data); + }); + + }; + + loadData(); + // timerService.register(loadData, $route.current.controller); + + // $scope.$on("$routeChangeStart", function () { + // timerService.remove($route.current.controller); + // }); +}); \ No newline at end of file diff --git a/frontend/app/user/payments/payments.html b/frontend/app/user/payments/payments.html new file mode 100644 index 0000000..fc4c5e9 --- /dev/null +++ b/frontend/app/user/payments/payments.html @@ -0,0 +1,45 @@ + +
+
+ + +
+ Payments Made +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +

Time Sent

Transaction Hash

Amount

Fee

Mixin

Payees

+

+ + {{payment.ts | date:'hh:mm:ss dd/MM/yy'}} + +

{{payment.value | toXMR}} XMR

{{payment.fee | toXMR }} XMR

{{payment.mixins}}

{{payment.payees}}

+
+ +
+
+
+
\ No newline at end of file diff --git a/frontend/app/user/payments/payments.js b/frontend/app/user/payments/payments.js new file mode 100644 index 0000000..5a5a255 --- /dev/null +++ b/frontend/app/user/payments/payments.js @@ -0,0 +1,24 @@ +'use strict'; + +app.controller('PaymentsCtrl', function($scope, dataService) { + $scope.payments = {}; + $scope.selected = []; + + $scope.options = { + page: 1, + limit: 15 + } + + $scope.loadPayments = function () { + var params = angular.copy($scope.options); + params.page -= 1; + var urlParams = $.param(params) + + dataService.getData("/pool/payments?"+urlParams, function(data){ + $scope.payments.global = data; + }); + } + + $scope.loadPayments(); + +}); \ No newline at end of file diff --git a/frontend/app/user/ports/ports.html b/frontend/app/user/ports/ports.html new file mode 100644 index 0000000..5aa8ab5 --- /dev/null +++ b/frontend/app/user/ports/ports.html @@ -0,0 +1,42 @@ + + + +
+ Ports List +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

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}}

+
+
+
+
+
\ No newline at end of file diff --git a/frontend/app/user/ports/ports.js b/frontend/app/user/ports/ports.js new file mode 100644 index 0000000..22b8066 --- /dev/null +++ b/frontend/app/user/ports/ports.js @@ -0,0 +1,10 @@ +'use strict'; + +app.controller('PortsCtrl', function($scope, $route, dataService, timerService) { + $scope.portsList = {}; + $scope.selected = []; + + $scope.promise = dataService.getData("/pool/ports", function(data){ + $scope.portsList = data; + }); +}); \ No newline at end of file diff --git a/frontend/app/utils/dataservice.js b/frontend/app/utils/dataservice.js new file mode 100644 index 0000000..4411ab5 --- /dev/null +++ b/frontend/app/utils/dataservice.js @@ -0,0 +1,79 @@ +angular.module('utils.xhr', []) +.service('dataService', function($http, $localStorage, $sessionStorage, GLOBALS) { + var apiURL = GLOBALS.api_url; + var sessStorage = $sessionStorage; + var storage = $localStorage; + var sessionLock = false; + + this.getData = function(url, successFn, errorFn) { + this.xhr('GET', url, {}, successFn, errorFn); + } + + this.postData = function(url, params, successFn, errorFn) { + this.xhr('POST', url, params, successFn, errorFn); + } + + this.putData = function(url, params, successFn, errorFn) { + this.xhr('PUT', url, params, successFn, errorFn); + } + + this.deleteData = function(url, params, successFn, errorFn) { + this.xhr('DELETE', url, params, successFn, errorFn); + } + + this.xhr = function (type, url, params, successFn, errorFn) { + $http({ + method: type, + url: apiURL + url, + data: params, + headers: this.getRequestHeaders() + }).then(function successCallback(response) { + successFn(response.data); + }, function errorCallback(response) { + if (errorFn && response != undefined) errorFn(response); else console.log("Network Error", response); + }).$promise; + } + + this.setAuthToken = function(token) { + sessStorage.token = token.msg; + storage.authToken = (token.remember) ? token.msg : false; // remember me + this.validateSession(); + } + + this.getRequestHeaders = function() { + this.validateSession(); + return { 'x-access-token': (sessStorage.token) ? sessStorage.token : "" }; + } + + this.isLoggedIn = function() { + return sessStorage.token || storage.authToken; + } + + this.validateSession = function () { + if (storage.authToken !== undefined){ + sessionLock = true; + if (storage.authToken) { + $http.defaults.headers.common['x-access-token'] = storage.authToken; + sessStorage.token = storage.authToken; + } + } else if (sessionLock) { + // logout if, logout detected on another browser session + this.logout(); + sessionLock=false; + } + } + + this.logout = function() { + // invalidate existing token + $http.get(apiURL+"/authed/tokenRefresh") + .then(function (data) { + /* Do nothing */ + }, function (err) { + console.log("debug", err); + }); + delete storage.authToken; + delete sessStorage.authToken; + delete sessStorage.token; + // invalidate token on server todo + } + }) \ No newline at end of file diff --git a/frontend/app/utils/directives.js b/frontend/app/utils/directives.js new file mode 100644 index 0000000..a1c2f18 --- /dev/null +++ b/frontend/app/utils/directives.js @@ -0,0 +1,21 @@ +var compareTo = function() { + return { + require: "ngModel", + scope: { + otherModelValue: "=compareTo" + }, + link: function(scope, element, attributes, ngModel) { + + ngModel.$validators.compareTo = function(modelValue) { + return modelValue == scope.otherModelValue; + }; + + scope.$watch("otherModelValue", function() { + ngModel.$validate(); + }); + } + }; +}; + +angular.module('utils.directives', []) +.directive("compareTo", compareTo); \ No newline at end of file diff --git a/frontend/app/utils/services.js b/frontend/app/utils/services.js new file mode 100644 index 0000000..b3f5cb6 --- /dev/null +++ b/frontend/app/utils/services.js @@ -0,0 +1,172 @@ +'use strict'; + +angular.module('utils.services', []) + +.service('timerService', function($interval) { + var timer; + var listeners = {}; + + this.startTimer = function(ms) { + timer = $interval(function() { + _.each(listeners, function(listener) { + listener(); + }); + }, ms); + } + + this.stopTimer = function(){ + $interval.cancel(timer); + } + + this.register = function(callback, key){ + // console.log("Registering requests for", key); + return listeners[key] = callback; + } + + this.remove = function(key){ + // console.log("Destroying requests for", key); + delete listeners[key]; + } + }) + +.service('addressService', function(dataService, timerService, $localStorage, ngAudio) { + var addrStats = {}; + var callback; + var storage = $localStorage; + + this.trackAddress = function (addr) { + addrStats[addr] = {}; + track(); + } + + this.deleteAddress = function (key) { + delete addrStats[key]; + }; + + this.getData = function (){ + return addrStats; + } + + this.setAlarm = function(addr, bool){ + addrStats[addr].alarm = bool; + storage.addrStats[addr].alarm = bool; + } + + var track = function(){ + _.each(addrStats, function(addr, key) { + // Get Miner stats + dataService.getData("/miner/"+key+"/stats", function(data){ + addrStats[key] = Object.assign(addr, data); + + // check and inject alarm var + if (addr.alarm == undefined) { + addr.alarm = false; + } + + // Set default miner name address + if (addr.name === undefined) { + addr.name = key; + } + + // update + storage.addrStats = addrStats; + callback(addrStats); + }); + + // Get miner worker ids + dataService.getData("/miner/"+key+"/identifiers", function(minerIDs){ + addrStats[key].ids = minerIDs; + }); + + dataService.getData("/miner/"+key+"/stats/allWorkers", function(workerStats){ + addrStats[key].workerStats = workerStats; + }); + + }); + + } + + this.start = function (cb){ + timerService.register(track, 'minerStats'); + addrStats = storage.addrStats || {} ; + callback = cb; + track(); // also run immediately + } +}) + +.service('minerService', function($filter, dataService) { + var minerStats = {}; + var callback; + var status = true; // check pause + + this.runService = function (bool) { + status = bool; + } + + this.updateStats = function (addrs, callback) { + + // initalise addrs + if(!status) return 0; + + _.each(addrs, function (data, addr) { + + if (minerStats[addr] === undefined) minerStats[addr] = { + dataset : {}, + options : { + series: [], + allSeries: [], + axes: { + x: { + key: "ts", + type: "date" + } + } + }, + table_selected: [], + table_options: { + rowSelection: true, + multiSelect: true + } + }; + + dataService.getData("/miner/"+addr+"/chart/hashrate/allWorkers", function(allWorkersData){ + // Convert all dates to object + + _.each(allWorkersData, function (workerData, mid) { + for(var i = 0 ; i < workerData.length; i++){ + allWorkersData[mid][i].ts = new Date(allWorkersData[mid][i].ts); + } + + minerStats[addr].dataset[mid] = workerData; + + minerStats[addr].options.allSeries = _.unionBy(minerStats[addr].options.allSeries, [{ + axis: "y", + id: mid, + dataset: mid, + label: mid, + key: "hs", + color: (minerStats[addr].options.series[mid]===undefined) ? randomColor() : minerStats[addr].options.series[mid].color, + type: ['line', 'area'], + //interpolation: { mode: "basis"}, + defined: function (value){ + //console.log(value); + return (value !== undefined || value.x !== undefined || value.y !== undefined) ; + } + }], 'id'); + }); + + // only display selected miners + var selected = minerStats[addr].selected; + if(minerStats[addr].table_selected.length < 1) { + selected = _.union(minerStats[addr].table_selected, ['global']); + } + + minerStats[addr].options.series = _.intersectionWith(minerStats[addr].options.allSeries, selected, function(ser, sel) { return ( ser.id == sel ) }); + }); + + // report back + callback(minerStats); + + }); + }; +}); \ No newline at end of file diff --git a/frontend/app/utils/strings.js b/frontend/app/utils/strings.js new file mode 100644 index 0000000..608525c --- /dev/null +++ b/frontend/app/utils/strings.js @@ -0,0 +1,35 @@ +'use strict'; + +angular.module('utils.strings', []) + +.filter('toXMR', function() { + return function(amount) { + return amount / 1000000000000; + }; +}) + +.filter('toHashRate', function() { + return function(hashes) { + if (hashes > 1000000) { + return Math.floor(hashes / 1000000) + "." + (hashes % 1000000).toString().substring(0, 1) + " MH/s" + } + if (hashes > 1000) { + return Math.floor(hashes / 1000) + "." + (hashes % 1000).toString().substring(0, 1) + " KH/s" + } + return ( hashes || 0 ) + " H/s" + }; +}) + +.filter('hashToLink', function($sce) { + return function(hash, type) { + var str = (hash == undefined) ? 'none' : "" + hash + ""; + return $sce.trustAsHtml(str); + }; +}) + +.filter('difficultyToHashRate', function() { + return function(hashrate) { + return Math.floor(hashrate / 120) + }; +}); + diff --git a/frontend/app/welcome.html b/frontend/app/welcome.html new file mode 100644 index 0000000..6e6b282 --- /dev/null +++ b/frontend/app/welcome.html @@ -0,0 +1,5 @@ + +
Welcome to {{GLOBALS.pool_name}}
+

Update welcome content in app/welcome.html

+

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

+
\ No newline at end of file diff --git a/frontend/bower.json b/frontend/bower.json new file mode 100644 index 0000000..5967496 --- /dev/null +++ b/frontend/bower.json @@ -0,0 +1,23 @@ +{ + "name": "xmrpoolui", + "private": true, + "dependencies": { + "angular": "~1.6.1", + "angular-route": "~1.6.1", + "angular-loader": "~1.6.1", + "jquery": "~3.1.1", + "angular-material": "~1.1.3", + "angular-moment": "~1.0.1", + "moment": "momentjs#^2.17.1", + "ngstorage": "^0.3.11", + "angular-audio": "^1.7.3", + "angular-material-data-table": "^0.10.10", + "angular-chart.js": "^1.1.1", + "chart.js": "^2.4.0", + "lodash": "^4.17.4", + "angular_page_visibility": "angular-page-visibility#^0.0.4" + }, + "resolutions": { + "angular": "~1.6.1" + } +} diff --git a/frontend/gulpfile.js b/frontend/gulpfile.js new file mode 100644 index 0000000..1723f1c --- /dev/null +++ b/frontend/gulpfile.js @@ -0,0 +1,86 @@ +var gulp = require('gulp'); +var connect = require('gulp-connect'); +var manifest = require('gulp-manifest'); + +gulp.task('html', function(){ + return gulp.src(['app/**/*.html', '!app/vendor/**/*']) + .pipe(connect.reload()) + .pipe(gulp.dest('build/')) +}); + +gulp.task('css', function(){ + return gulp.src(['app/**/*.css', '!app/vendor/**/*']) + .pipe(connect.reload()) + .pipe(gulp.dest('build/')) +}); + +gulp.task('js', function(){ + return gulp.src(['app/**/*.js', '!app/vendor/**/*']) + .pipe(connect.reload()) + .pipe(gulp.dest('build/')) +}); + +gulp.task('assets', function(){ + return gulp.src('app/assets/*') + .pipe(connect.reload()) + .pipe(gulp.dest('build/assets')) +}); + +gulp.task('connect', function() { + connect.server({ + root: 'build', + livereload: true + }); +}); + +gulp.task('vendor', function() { + return gulp.src([ + 'app/vendor/**/dist/jquery.js', + 'app/vendor/**/angular.js', + 'app/vendor/**/angular-route.js', + 'app/vendor/**/angular-material.css', + 'app/vendor/**/angular-animate.js', + 'app/vendor/**/angular-aria.js', + 'app/vendor/**/angular-material.js', + 'app/vendor/**/angular-moment.js', + 'app/vendor/**/ngStorage.js', + 'app/vendor/**/app/angular.audio.js', + 'app/vendor/**/moment.js', + 'app/vendor/**/md-data-table.js', + 'app/vendor/**/md-data-table.css', + 'app/vendor/**/dist/chart.js', + 'app/vendor/**/dist/angular-chart.js', + 'node_modules/**/d3.js', + 'node_modules/**/LineChart.js', + 'node_modules/**/LineChart.css', + 'node_modules/**/randomColor.js', + 'app/vendor/**/lodash.js', + 'app/vendor/**/page_visibility.js' + ]) + .pipe(gulp.dest('build/vendor')) +}); + +gulp.task('watch', function () { + gulp.watch(['./app/**/*.html'], ['html', 'manifest']); + gulp.watch(['./app/**/*.css'], ['css', 'manifest']); + gulp.watch(['./app/**/*.js'], ['js', 'manifest']); + gulp.watch(['./assets/*.*'], ['assets', 'manifest']); +}); + +gulp.task('manifest', function(){ + gulp.src([ + 'build/**/*' + ], { base: './build' }) + .pipe(manifest({ + hash: true, + preferOnline: true, + network: ['*'], + filename: 'app.manifest', + exclude: 'app.manifest' + })) + .pipe(connect.reload()) + .pipe(gulp.dest('build')); +}); + +gulp.task('build', [ 'html', 'css', 'js', 'assets', 'vendor', 'manifest' ]); +gulp.task('default', [ 'build', 'connect', 'watch' ]); diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..aef37fe --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,23 @@ +{ + "name": "xmrpoolui", + "description": "UI for XMR Pool.net", + "repository": "", + "license": "?", + "devDependencies": { + "bower": "^1.7.7", + "gulp": "^3.9.1", + "gulp-connect": "^5.0.0", + "gulp-manifest": "^0.1.1" + }, + "scripts": { + "postinstall": "bower install && gulp build", + "update-deps": "npm update", + "postupdate-deps": "bower update", + "prestart": "npm install", + "start": "gulp" + }, + "dependencies": { + "n3-charts": "^2.0.28", + "randomcolor": "^0.4.4" + } +} diff --git a/frontend/readme.md b/frontend/readme.md new file mode 100644 index 0000000..5c2dac9 --- /dev/null +++ b/frontend/readme.md @@ -0,0 +1,42 @@ +# Monero Pool frontend + +### AngularJS based UI for [nodejs-pool](https://github.com/Snipa22/nodejs-pool) + +### Features +- See your hashrate on all pages +- Track multiple payment addresses. +- Hashrate siren when hashrate falls below a certain limit. +- Per miner charts & Payment History. +- Miner login and management for threhold and payment adjustment. +- Admin UI for simple Pool management. +- All the usual features + more. + +### Run it + +Home page html can be set in welcome.html +Set pool params in app/globals.js.default and copy to app/globals.js + +Requires NodeJS + +```sh +$ npm start # starts gulp + livereload, serves from ./build on 8080 +``` + +## Deploy +```sh +$ npm install # runs everything, serve from ./build +``` + +### Todo + +* Fix sort arrow styling +* Network stats page. +* Ship it deployment +* Websockets +* Miner graph colour picker + +### Support +* I'm usually on #monero-pools so drop me a line if you need help with something or have a feature request. + +#### Coffee :P ? +42yCGRP2p6bZzMjJxKpJtTFRz2x3X3eBYD97T17zdxC9NiGNWafCaU54MKWBZkHb9AVb4XBgcjkPGW8hjQyBM2vMMvVCzTj \ No newline at end of file