From ece2321224da288f400ce8768d7bb230e035feab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ben=20Gr=C3=A4f?= Date: Tue, 16 May 2023 22:02:41 +0200 Subject: [PATCH] # 3.3.3 (#401) * Dashboard changes * Changed sorting of column `workerId` to natural sorting * Fixed `group by algo` sorting * Fixed offline notification banner * Added button to remove selected miner from Dashboard #396 * Added confirm dialog to "Reset ClientStatusList" #397 * Rebase latest xmrig-6.19.2 changes --- CHANGELOG.md | 8 + index.html | 4465 +++++++++-------- src/backend/opencl/runners/OclCnGpuRunner.cpp | 1 + src/base/net/stratum/Pools.cpp | 35 +- src/base/net/stratum/Pools.h | 29 +- src/cc/Service.cpp | 11 + src/cc/Service.h | 1 + src/net/strategies/DonateStrategy.cpp | 136 +- src/net/strategies/DonateStrategy.h | 60 +- src/version.h | 4 +- 10 files changed, 2494 insertions(+), 2256 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f8ac2973e..07c5ca29fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +# 3.3.3 + * Dashboard changes + * Changed sorting of column `workerId` to natural sorting + * Fixed `group by algo` sorting + * Fixed offline notification banner + * Added button to remove selected miner from Dashboard #396 + * Added confirm dialog to "Reset ClientStatusList" #397 + * Rebase latest xmrig-6.19.2 changes # 3.3.2 * Integrated CC-Server failover #229 * Rebase latest xmrig-6.18.2-dev changes diff --git a/index.html b/index.html index 6fc0fc6c94..0f601b7302 100644 --- a/index.html +++ b/index.html @@ -14,900 +14,1009 @@ - - - - + + + +
+
+
+ + + +
@@ -985,7 +1094,8 @@
- +
@@ -1213,7 +1323,8 @@ + + - - - - - - - - - - + + + + + + + + + + + @@ -1323,1342 +1450,1372 @@ function setCookie(name, value, days) { let expires = ""; if (days) { - let date = new Date(); - date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000)); - expires = "; expires=" + date.toUTCString(); + let date = new Date(); + date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000)); + expires = "; expires=" + date.toUTCString(); } document.cookie = name + "=" + (value.toString() || "") + expires + "; path=/"; - } + } - function getCookie(name) { + function getCookie(name) { let nameEQ = name + "="; let ca = document.cookie.split(';'); for (let i = 0; i < ca.length; i++) { - let c = ca[i]; - while (c.charAt(0) == ' ') c = c.substring(1, c.length); - if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length); + let c = ca[i]; + while (c.charAt(0) == ' ') c = c.substring(1, c.length); + if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length); } return null; - } + } - function toggle() { + function toggle() { let hideOffline = getCookie('hideOffline'); let showOfflineNotification = getCookie('showOfflineNotification'); let groupByAlgo = getCookie('groupByAlgo'); - if(hideOffline){ - $( "#hideOffline" ).prop( "checked", JSON.parse(hideOffline)).trigger('change'); + if (hideOffline) { + $("#hideOffline").prop("checked", JSON.parse(hideOffline)).trigger('change'); } - if(showOfflineNotification){ - $( "#showOfflineNotification" ).prop( "checked", JSON.parse(showOfflineNotification)).trigger('change'); + if (showOfflineNotification) { + $("#showOfflineNotification").prop("checked", JSON.parse(showOfflineNotification)).trigger('change'); } - if(groupByAlgo){ - $( "#groupByAlgo" ).prop( "checked", JSON.parse(groupByAlgo)).trigger('change'); + if (groupByAlgo) { + $("#groupByAlgo").prop("checked", JSON.parse(groupByAlgo)).trigger('change'); } - } + } - (function($) { "use strict"; + (function ($) { + "use strict"; - $(function() { - let header = $(".start-style"); - $(window).scroll(function() { - let scroll = $(window).scrollTop(); - - if (Math.floor(scroll) >= 10) { - header.removeClass("scroll-on").addClass('start-style'); - } - else { - header.removeClass('start-style').addClass("scroll-on"); + $(function () { + let header = $(".start-style"); + $(window).scroll(function () { + let scroll = $(window).scrollTop(); + + if (Math.floor(scroll) >= 10) { + header.removeClass("scroll-on").addClass('start-style'); + } else { + header.removeClass('start-style').addClass("scroll-on"); + } + }); + + let mode = getCookie('mode'); + + toggle(); + + if (mode == "light") { + $("body").removeClass("dark"); + $("#switch").removeClass("switched"); + $("#light-mode").css("fill", "#02ABE4"); + $("#dark-mode").css("fill", "#000"); + $(".contact-item").css("color", "#171515"); + $(".navbar-brand").removeClass("dark-mode-logo"); + $(".select-checkbox").css("color", "#000"); + $(".modal-content").css("background-color", "#fff"); + $(".dialog-close").removeClass("dialog-close-scheme"); + } else { + $("body").addClass("dark"); + $("#switch").addClass("switched"); + $("#light-mode").css("fill", "#fff"); + $("#dark-mode").css("fill", "#02ABE4"); + $(".contact-item").css("color", "#fff"); + $(".navbar-brand").addClass("dark-mode-logo"); + $(".select-checkbox").css("color", "#fff"); + $(".modal-content").css("background-color", "#1f2029"); + $(".dialog-close").addClass("dialog-close-scheme"); } }); - let mode = getCookie('mode'); - - toggle(); - - if (mode=="light") { - $("body").removeClass("dark"); - $("#switch").removeClass("switched"); - $("#light-mode").css("fill", "#02ABE4"); - $("#dark-mode").css("fill", "#000"); - $(".contact-item").css("color", "#171515"); - $(".navbar-brand").removeClass("dark-mode-logo"); - $(".select-checkbox").css("color", "#000"); - $(".modal-content").css("background-color", "#fff"); - $(".dialog-close").removeClass("dialog-close-scheme"); - } - else { - $("body").addClass("dark"); - $("#switch").addClass("switched"); - $("#light-mode").css("fill", "#fff"); - $("#dark-mode").css("fill", "#02ABE4"); - $(".contact-item").css("color", "#fff"); - $(".navbar-brand").addClass("dark-mode-logo"); - $(".select-checkbox").css("color", "#fff"); - $(".modal-content").css("background-color", "#1f2029"); - $(".dialog-close").addClass("dialog-close-scheme"); - } - }); - - //Animation - - $(document).ready(function() { - $('body.xmrigCC').removeClass('xmrigCC'); - }); - - //Menu On Hover - - $('body').on('mouseenter mouseleave','.nav-item',function(e){ + //Animation + + $(document).ready(function () { + $('body.xmrigCC').removeClass('xmrigCC'); + }); + + //Menu On Hover + + $('body').on('mouseenter mouseleave', '.nav-item', function (e) { if ($(window).width() > 750) { - let _d=$(e.target).closest('.nav-item');_d.addClass('show'); - setTimeout(function(){ - _d[_d.is(':hover')?'addClass':'removeClass']('show'); - },1); + let _d = $(e.target).closest('.nav-item'); + _d.addClass('show'); + setTimeout(function () { + _d[_d.is(':hover') ? 'addClass' : 'removeClass']('show'); + }, 1); } - }); + }); - $('#hideOffline').change(function(){ - setCookie('hideOffline', $(this).prop('checked'), 90); - }); - + $('#hideOffline').change(function () { + setCookie('hideOffline', $(this).prop('checked'), 90); + }); - $('#showOfflineNotification').change(function(){ - setCookie('showOfflineNotification', $(this).prop('checked'), 90); - }); + $('#showOfflineNotification').change(function () { + setCookie('showOfflineNotification', $(this).prop('checked'), 90); + }); - $('#groupByAlgo').change(function(){ - setCookie('groupByAlgo', $(this).prop('checked'), 90); - }); + $('#groupByAlgo').change(function () { + setCookie('groupByAlgo', $(this).prop('checked'), 90); + }); + + //Switch light/dark + + $("#switch").on('click', function () { + if ($("body").hasClass("dark")) { + $("body").removeClass("dark"); + $("#switch").removeClass("switched"); + $("#light-mode").css("fill", "#02ABE4"); + $("#dark-mode").css("fill", "#000"); + $("#github").css("color", "#171515"); + $(".navbar-brand").removeClass("dark-mode-logo"); + $(".select-checkbox").css("color", "#000"); + $(".modal-content").css("background-color", "#fff"); + $(".dialog-close").removeClass("dialog-close-scheme"); + setCookie('mode', 'light', 90); + } else { + $("body").addClass("dark"); + $("#switch").addClass("switched"); + $("#light-mode").css("fill", "#fff"); + $("#dark-mode").css("fill", "#02ABE4"); + $("#github").css("color", "#fff"); + $(".navbar-brand").addClass("dark-mode-logo"); + $(".select-checkbox").css("color", "#fff"); + $(".modal-content").css("background-color", "#202020"); + $(".dialog-close").addClass("dialog-close-scheme"); + setCookie('mode', 'dark', 90) + } + }); - - //Switch light/dark - - $("#switch").on('click', function () { - if ($("body").hasClass("dark")) { - $("body").removeClass("dark"); - $("#switch").removeClass("switched"); - $("#light-mode").css("fill", "#02ABE4"); - $("#dark-mode").css("fill", "#000"); - $("#github").css("color", "#171515"); - $(".navbar-brand").removeClass("dark-mode-logo"); - $(".select-checkbox").css("color", "#000"); - $(".modal-content").css("background-color", "#fff"); - $(".dialog-close").removeClass("dialog-close-scheme"); - setCookie('mode', 'light', 90); - } - else { - $("body").addClass("dark"); - $("#switch").addClass("switched"); - $("#light-mode").css("fill", "#fff"); - $("#dark-mode").css("fill", "#02ABE4"); - $("#github").css("color", "#fff"); - $(".navbar-brand").addClass("dark-mode-logo"); - $(".select-checkbox").css("color", "#fff"); - $(".modal-content").css("background-color", "#202020"); - $(".dialog-close").addClass("dialog-close-scheme"); - setCookie('mode', 'dark', 90) - } - }); - })(jQuery); - + document.onreadystatechange = function () { - let state = document.readyState - if (state == 'interactive') { - document.getElementById('loader-parent').style.display="none"; - document.getElementById('loader-parent').style.visibility="hidden"; - } else if (state == 'complete') { - setTimeout(function(){ - document.getElementById('loader-parent').style.display="none"; - document.getElementById('loader-parent').style.height="0"; - document.getElementById('loader-parent').style.visibility="hidden"; - }, 3000); - - } -} - - - - $.fn.dataTable.ext.search.push( - function (settings, data, dataIndex) { - - let hideOffline = $('#hideOffline').prop('checked'); - let showNotification = $('#showOfflineNotification').prop('checked'); - - let clientId = settings.aoData[dataIndex]._aData.client_status.client_id; - let lastStatus = settings.aoData[dataIndex]._aData.client_status.last_status_update * 1000; - - let online = isOnline(lastStatus); - - if (!online) { - let threshold = currentServerTime - (THRESHOLD_IN_MS + RELOAD_INTERVAL_IN_MS); - if (lastStatus > threshold && showNotification) { - $("#notificationBar").after('
' + - '×' + - 'Miner ' + clientId + ' just went offline! Last update: ' + new Date(lastStatus) + - '
'); - } - } - - return (online || !hideOffline); + let state = document.readyState + if (state == 'interactive') { + document.getElementById('loader-parent').style.display = "none"; + document.getElementById('loader-parent').style.visibility = "hidden"; + } else if (state == 'complete') { + setTimeout(function () { + document.getElementById('loader-parent').style.display = "none"; + document.getElementById('loader-parent').style.height = "0"; + document.getElementById('loader-parent').style.visibility = "hidden"; + }, 3000); + + } + } + + + $.fn.dataTable.ext.search.push( + function (settings, searchData, index, rowData, counter) { + + let hideOffline = $('#hideOffline').prop('checked'); + let showNotification = $('#showOfflineNotification').prop('checked'); + + let clientId = rowData.client_status.client_id; + let lastStatus = rowData.client_status.last_status_update * 1000; + + let online = isOnline(lastStatus); + + if (!online) { + let threshold = currentServerTime - (THRESHOLD_IN_MS + RELOAD_INTERVAL_IN_MS); + if (lastStatus > threshold && showNotification) { + $("#notificationBar").after('
' + + '×' + + 'Miner ' + clientId + ' just went offline! Last update: ' + new Date(lastStatus) + + '
'); } - ); - - - - $(document).ready(function () { - let table = $('#clientStatusList').DataTable({ - dom: "<'row'<'col-sm-12'B>><'row rowPadded'<'col-sm-9'l><'col-sm-3'f>><'row'<'col-sm-12't>><'row'<'col-sm-4'i><'col-sm-8'p>><'col-sm-13'<'#serverTime'>>", - lengthMenu: [ [10, 25, 50, 100, -1], [10, 25, 50, 100, "All"] ], - pageLength: -1, - bPpaginate: true, - pagingType: "full_numbers", - stateSave: true, - ajax: { - url: "/admin/getClientStatusList", - dataSrc: 'client_status_list' - }, - orderFixed: [5, 'asc'], - rowGroup: { - dataSrc: "client_status.current_algo_name", - endRender: null, - startRender: function ( rows, group ) { - return $('') - .append( '' ) - }, - }, - columns: [ - { - data: null, - defaultContent: '', - className: 'select-checkbox', - // render: checkbox, - orderable: false - }, - {data: "client_status.client_id", render: clientInfo}, - {data: "client_status.version", render: version}, - {data: "client_status.current_pool"}, - {data: "client_status.current_pool_user", visible: false}, - {data: "client_status.current_pool_pass", visible: false}, - {data: "client_status.current_pool_rig_id", visible: false}, - {data: "client_status.current_status", render: clientStatus}, - {data: "client_status.current_algo_name", render: algoAndPowVariantName}, - - {data: "client_status.cpu_brand", visible: false}, - {data: "client_status.external_ip", visible: false}, - {data: "client_status.hugepages_available", visible: false}, - {data: "client_status.hugepages_enabled", visible: false}, - {data: "client_status.cpu_is_x64", visible: false}, - {data: "client_status.cpu_has_aes", visible: false}, - {data: "client_status.cpu_is_vm", visible: false}, - {data: "client_status.hash_factor", className: "right", visible: false}, - {data: "client_status.total_pages", className: "right", visible: false}, - {data: "client_status.total_hugepages", className: "right", visible: false}, - {data: null, render: clientHPpercent, className: "right", visible: false}, - {data: "client_status.free_memory", render: memory, className: "right", visible: false}, - {data: "client_status.total_memory", render: memory, className: "right", visible: false}, - {data: "client_status.current_threads", className: "right", visible: false}, - {data: "client_status.cpu_sockets", className: "right", visible: false}, - {data: "client_status.cpu_cores", className: "right", visible: false}, - {data: "client_status.cpu_threads", className: "right", visible: false}, - {data: "client_status.cpu_l2", render: cache, className: "right", visible: false}, - {data: "client_status.cpu_l3", render: cache, className: "right", visible: false}, - {data: "client_status.cpu_nodes", className: "right", visible: false}, - {data: "client_status.max_cpu_usage", render: percent, className: "right", visible: false}, - - {data: "client_status.hashrate_short", render: round, className: "right"}, - {data: "client_status.hashrate_medium", render: round, className: "right"}, - {data: "client_status.hashrate_long", render: round, className: "right"}, - {data: "client_status.hashrate_highest", render: round, className: "right"}, - {data: "client_status.hashes_total", className: "right"}, - {data: "client_status.avg_time", className: "right"}, - {data: "client_status.shares_good", className: "right"}, - {data: "client_status.shares_total", className: "right"}, - {data: "client_status.uptime", render: uptime, className: "right"}, - {data: "client_status.last_status_update", render: laststatus}, - { - data: null, - defaultContent: - "", - orderable: false, - className: "center-tab" - }, - { - data: null, - defaultContent: - "", - orderable: false, - className: "center-tab" - } - ], - rowId: 'client_status.client_id', - select: { - style: "multi+shift" - }, - order: [1, 'asc'], - buttons: [ - 'colvis', - { - text: ' Push miner config', - className: 'btn-info', - enabled: false, - action: function () { - table.rows({selected: true}).eq(0).each(function (index) { - let row = table.row(index); - let data = row.data(); - - sendAction("UPDATE_CONFIG", data.client_status.client_id); - }); - } - }, - { - text: ' Pull miner config', - className: 'btn-primary', - enabled: false, - action: function () { - table.rows({selected: true}).eq(0).each(function (index) { - let row = table.row(index); - let data = row.data(); - - sendAction("PUBLISH_CONFIG", data.client_status.client_id); - }); - } - }, - { - text: ' Update miner', - className: 'btn-danger', - enabled: false, - action: function () { - table.rows({selected: true}).eq(0).each(function (index) { - let row = table.row(index); - let data = row.data(); - - sendAction("UPDATE", data.client_status.client_id); - }); - } - }, - { - text: ' Assign template', - className: 'btn-info', - enabled: false, - action: function () { - $.ajax({ - type: "GET", - url: "/admin/getClientConfigTemplates", - dataType: "json", - success: function (data) { - let htmlContent = ""; - - let arrayLength = data["templates"].length; - for (let i = 0; i < arrayLength; i++) { - htmlContent += ""; - } - - if (arrayLength > 0) { - $('#assignTemplate').prop('disabled', false); - - $('#assignTemplateSelector').html(htmlContent); - $('#assignTemplateSelector').selectpicker('refresh'); - } else { - $('#assignTemplate').prop('disabled', true); - } - - $('#assignTemplateEditor').modal('show'); - }, - error: function (data) { - setError('Unable to fetch templates - Please make they exist!'); - } - }); - } - }, - { - text: ' Template Editor', - className: 'btn-primary', - enabled: true, - action: function () { - $.ajax({ - type: "GET", - url: "/admin/getClientConfigTemplates", - dataType: "json", - success: function (data) { - let htmlContent = ""; - - let arrayLength = data["templates"].length; - for (let i = 0; i < arrayLength; i++) { - htmlContent += ""; - } - - if (arrayLength > 0) { - $('#templateEditorSave').prop('disabled', false); - $('#templateEditorDeleteDialog').prop('disabled', false); - - $('#templateSelector').html(htmlContent); - $('#templateSelector').selectpicker('refresh'); - $('#templateSelector').trigger('change'); - } else { - $('#templateEditorSave').prop('disabled', true); - $('#templateEditorDeleteDialog').prop('disabled', true); - } - - $('#templateEditor').modal('show'); - }, - error: function (data) { - setError('Unable to fetch templates - Please make they exist!'); - } - }); - } - }, - { - text: ' Start', - className: 'btn-success', - enabled: false, - action: function () { - table.rows({selected: true}).eq(0).each(function (index) { - let row = table.row(index); - let data = row.data(); - - sendAction("START", data.client_status.client_id); - }); - } - }, - { - text: ' Pause', - className: 'btn-warning', - enabled: false, - action: function () { - table.rows({selected: true}).eq(0).each(function (index) { - let row = table.row(index); - let data = row.data(); - - sendAction("STOP", data.client_status.client_id); - }); - } - }, - { - text: ' Stop', - className: 'btn-danger', - enabled: false, - action: function () { - $('#commandDialogStop').modal('show'); - } - }, - { - text: ' Restart', - className: 'btn-info', - enabled: false, - action: function () { - table.rows({selected: true}).eq(0).each(function (index) { - let row = table.row(index); - let data = row.data(); - - sendAction("RESTART", data.client_status.client_id); - }); - } - }, - { - text: ' Reboot', - className: 'btn-danger', - enabled: false, - action: function () { - table.rows({selected: true}).eq(0).each(function (index) { - let row = table.row(index); - let data = row.data(); - - sendAction("REBOOT", data.client_status.client_id); - }); - } - }, - { - text: ' Execute', - className: 'btn-primary', - enabled: true, - action: function () { - $('#commandExecuteDialog').modal('show'); - } - } - ], - - "footerCallback": function (row, data, start, end, display) { - let api = this.api(); - - let sumHashrateShort = 0; - let sumHashrateMedium = 0; - let sumHashrateLong = 0; - let sumHashrateHighest = 0; - let sumHashesTotal = 0; - let avgTimeTotal = 0; - let sumSharesGood = 0; - let sumSharedTotal = 0; - - let colOffset = 30; - - sumHashrateShort = api - .column(colOffset, {page: 'current'}) - .data() - .reduce(function (a, b) { - return a + b; - }, 0); - - sumHashrateMedium = api - .column(colOffset+1, {page: 'current'}) - .data() - .reduce(function (a, b) { - return a + b; - }, 0); - - sumHashrateLong = api - .column(colOffset+2, {page: 'current'}) - .data() - .reduce(function (a, b) { - return a + b; - }, 0); - - sumHashrateHighest = api - .column(colOffset+3, {page: 'current'}) - .data() - .reduce(function (a, b) { - return a + b; - }, 0); - - sumHashesTotal = api - .column(colOffset+4, {page: 'current'}) - .data() - .reduce(function (a, b) { - return a + b; - }, 0); - - avgTimeTotal = api - .column(colOffset+5, {page: 'current'}) - .data() - .reduce(function (a, b) { - return (a + b); - }, 0) / api.column(26, {page: 'current'}).data().length; - - sumSharesGood = api - .column(colOffset+6, {page: 'current'}) - .data() - .reduce(function (a, b) { - return a + b; - }, 0); - - sumSharedTotal = api - .column(colOffset+7, {page: 'current'}) - .data() - .reduce(function (a, b) { - return a + b; - }, 0); - - sumHashrateShort = round(sumHashrateShort); - sumHashrateMedium = round(sumHashrateMedium); - sumHashrateLong = round(sumHashrateLong); - sumHashrateHighest = round(sumHashrateHighest); - avgTimeTotal = round(avgTimeTotal); - - // update footer - $(api.column(colOffset).footer()).html(sumHashrateShort); - $(api.column(colOffset+1).footer()).html(sumHashrateMedium); - $(api.column(colOffset+2).footer()).html(sumHashrateLong); - $(api.column(colOffset+3).footer()).html(sumHashrateHighest); - $(api.column(colOffset+4).footer()).html(sumHashesTotal); - $(api.column(colOffset+5).footer()).html(avgTimeTotal); - $(api.column(colOffset+6).footer()).html(sumSharesGood); - $(api.column(colOffset+7).footer()).html(sumSharedTotal); + } + + return (online || !hideOffline); + } + ); + + + $(document).ready(function () { + let table = $('#clientStatusList').DataTable({ + dom: "<'row'<'col-sm-12'B>><'row rowPadded'<'col-sm-9'l><'col-sm-3'f>><'row'<'col-sm-12't>><'row'<'col-sm-4'i><'col-sm-8'p>><'col-sm-13'<'#serverTime'>>", + lengthMenu: [[10, 25, 50, 100, -1], [10, 25, 50, 100, "All"]], + pageLength: -1, + bPpaginate: true, + pagingType: "full_numbers", + stateSave: true, + ajax: { + url: "/admin/getClientStatusList", + dataSrc: 'client_status_list' + }, + orderFixed: [8, 'asc'], //algo + columnDefs: [ + {type: 'natural', targets: 1} + ], + rowGroup: { + dataSrc: "client_status.current_algo_name", + endRender: null, + startRender: function (rows, group) { + return $('') + .append('') + }, + }, + columns: [ + { + data: null, + defaultContent: '', + className: 'select-checkbox', + // render: checkbox, + orderable: false + }, + {data: "client_status.client_id", render: clientInfo}, + {data: "client_status.version", render: version}, + {data: "client_status.current_pool"}, + {data: "client_status.current_pool_user", visible: false}, + {data: "client_status.current_pool_pass", visible: false}, + {data: "client_status.current_pool_rig_id", visible: false}, + {data: "client_status.current_status", render: clientStatus}, + {data: "client_status.current_algo_name", render: algoAndPowVariantName}, + + {data: "client_status.cpu_brand", visible: false}, + {data: "client_status.external_ip", visible: false}, + {data: "client_status.hugepages_available", visible: false}, + {data: "client_status.hugepages_enabled", visible: false}, + {data: "client_status.cpu_is_x64", visible: false}, + {data: "client_status.cpu_has_aes", visible: false}, + {data: "client_status.cpu_is_vm", visible: false}, + {data: "client_status.hash_factor", className: "right", visible: false}, + {data: "client_status.total_pages", className: "right", visible: false}, + {data: "client_status.total_hugepages", className: "right", visible: false}, + {data: null, render: clientHPpercent, className: "right", visible: false}, + {data: "client_status.free_memory", render: memory, className: "right", visible: false}, + {data: "client_status.total_memory", render: memory, className: "right", visible: false}, + {data: "client_status.current_threads", className: "right", visible: false}, + {data: "client_status.cpu_sockets", className: "right", visible: false}, + {data: "client_status.cpu_cores", className: "right", visible: false}, + {data: "client_status.cpu_threads", className: "right", visible: false}, + {data: "client_status.cpu_l2", render: cache, className: "right", visible: false}, + {data: "client_status.cpu_l3", render: cache, className: "right", visible: false}, + {data: "client_status.cpu_nodes", className: "right", visible: false}, + {data: "client_status.max_cpu_usage", render: percent, className: "right", visible: false}, + + {data: "client_status.hashrate_short", render: round, className: "right"}, + {data: "client_status.hashrate_medium", render: round, className: "right"}, + {data: "client_status.hashrate_long", render: round, className: "right"}, + {data: "client_status.hashrate_highest", render: round, className: "right"}, + {data: "client_status.hashes_total", className: "right"}, + {data: "client_status.avg_time", className: "right"}, + {data: "client_status.shares_good", className: "right"}, + {data: "client_status.shares_total", className: "right"}, + {data: "client_status.uptime", render: uptime, className: "right"}, + {data: "client_status.last_status_update", render: laststatus}, + { + data: null, + defaultContent: + "", + orderable: false, + className: "center-tab" + }, + { + data: null, + defaultContent: + "", + orderable: false, + className: "center-tab" + } + ], + rowId: 'client_status.client_id', + select: { + style: "multi+shift" + }, + order: [1, 'asc'], // workerid + buttons: [ + 'colvis', + { + text: ' Push miner config', + className: 'btn-info', + enabled: false, + action: function () { + table.rows({selected: true}).eq(0).each(function (index) { + let row = table.row(index); + let data = row.data(); + + sendAction("UPDATE_CONFIG", data.client_status.client_id); + }); } - }); - - table.on('xhr.dt', function (e, settings, json, xhr) { - // check version - if (latestRelease === "" && json !== undefined) { + }, + { + text: ' Pull miner config', + className: 'btn-primary', + enabled: false, + action: function () { + table.rows({selected: true}).eq(0).each(function (index) { + let row = table.row(index); + let data = row.data(); + + sendAction("PUBLISH_CONFIG", data.client_status.client_id); + }); + } + }, + { + text: ' Update miner', + className: 'btn-danger', + enabled: false, + action: function () { + table.rows({selected: true}).eq(0).each(function (index) { + let row = table.row(index); + let data = row.data(); + + sendAction("UPDATE", data.client_status.client_id); + }); + } + }, + { + text: ' Assign template', + className: 'btn-info', + enabled: false, + action: function () { $.ajax({ - url: "https://api.github.com/repos/Bendr0id/xmrigCC/releases/latest", - type: 'GET', + type: "GET", + url: "/admin/getClientConfigTemplates", dataType: "json", - success: function (release) { - latestRelease = release.tag_name - latestVersion = parseInt(release.tag_name.split('.').join("")); - currentVersion = parseInt(json.current_version.split('.').join("")); - - if (currentVersion < 1000) { - currentVersion = currentVersion * 10; + success: function (data) { + let htmlContent = ""; + + let arrayLength = data["templates"].length; + for (let i = 0; i < arrayLength; i++) { + htmlContent += ""; + } + + if (arrayLength > 0) { + $('#assignTemplate').prop('disabled', false); + + $('#assignTemplateSelector').html(htmlContent); + $('#assignTemplateSelector').selectpicker('refresh'); + } else { + $('#assignTemplate').prop('disabled', true); } - - if (latestVersion < 1000) { - latestVersion = latestVersion * 10; + + $('#assignTemplateEditor').modal('show'); + }, + error: function (data) { + setError('Unable to fetch templates - Please make they exist!'); + } + }); + } + }, + { + text: ' Template Editor', + className: 'btn-primary', + enabled: true, + action: function () { + $.ajax({ + type: "GET", + url: "/admin/getClientConfigTemplates", + dataType: "json", + success: function (data) { + let htmlContent = ""; + + let arrayLength = data["templates"].length; + for (let i = 0; i < arrayLength; i++) { + htmlContent += ""; } - - if (latestVersion > currentVersion) { - $("#updateNotificationBar").html(''); + + if (arrayLength > 0) { + $('#templateEditorSave').prop('disabled', false); + $('#templateEditorDeleteDialog').prop('disabled', false); + + $('#templateSelector').html(htmlContent); + $('#templateSelector').selectpicker('refresh'); + $('#templateSelector').trigger('change'); + } else { + $('#templateEditorSave').prop('disabled', true); + $('#templateEditorDeleteDialog').prop('disabled', true); } + + $('#templateEditor').modal('show'); + }, + error: function (data) { + setError('Unable to fetch templates - Please make they exist!'); } }); } - - currentServerTime = settings.json.current_server_time * 1000; - clockDrift = new Date().getTime() - currentServerTime; - - $('#serverTime').html("
" + new Date(currentServerTime) + "
"); + }, + { + text: ' Start', + className: 'btn-success', + enabled: false, + action: function () { + table.rows({selected: true}).eq(0).each(function (index) { + let row = table.row(index); + let data = row.data(); + + sendAction("START", data.client_status.client_id); + }); + } + }, + { + text: ' Pause', + className: 'btn-warning', + enabled: false, + action: function () { + table.rows({selected: true}).eq(0).each(function (index) { + let row = table.row(index); + let data = row.data(); + + sendAction("STOP", data.client_status.client_id); + }); + } + }, + { + text: ' Stop', + className: 'btn-danger', + enabled: false, + action: function () { + $('#commandDialogStop').modal('show'); + } + }, + { + text: ' Restart', + className: 'btn-info', + enabled: false, + action: function () { + table.rows({selected: true}).eq(0).each(function (index) { + let row = table.row(index); + let data = row.data(); + + sendAction("RESTART", data.client_status.client_id); + }); + } + }, + { + text: ' Reboot', + className: 'btn-danger', + enabled: false, + action: function () { + table.rows({selected: true}).eq(0).each(function (index) { + let row = table.row(index); + let data = row.data(); + + sendAction("REBOOT", data.client_status.client_id); + }); + } + }, + { + text: ' Remove', + className: 'btn-warning', + enabled: false, + action: function () { + table.rows({selected: true}).eq(0).each(function (index) { + let row = table.row(index); + let data = row.data(); + + removeClientStatus(data.client_status.client_id); + }); + } + }, + { + text: ' Execute', + className: 'btn-primary', + enabled: true, + action: function () { + $('#commandExecuteDialog').modal('show'); + } + } + ], + + "footerCallback": function (row, data, start, end, display) { + let api = this.api(); + + let sumHashrateShort = 0; + let sumHashrateMedium = 0; + let sumHashrateLong = 0; + let sumHashrateHighest = 0; + let sumHashesTotal = 0; + let avgTimeTotal = 0; + let sumSharesGood = 0; + let sumSharedTotal = 0; + + let colOffset = 30; + + sumHashrateShort = api + .column(colOffset, {page: 'current'}) + .data() + .reduce(function (a, b) { + return a + b; + }, 0); + + sumHashrateMedium = api + .column(colOffset + 1, {page: 'current'}) + .data() + .reduce(function (a, b) { + return a + b; + }, 0); + + sumHashrateLong = api + .column(colOffset + 2, {page: 'current'}) + .data() + .reduce(function (a, b) { + return a + b; + }, 0); + + sumHashrateHighest = api + .column(colOffset + 3, {page: 'current'}) + .data() + .reduce(function (a, b) { + return a + b; + }, 0); + + sumHashesTotal = api + .column(colOffset + 4, {page: 'current'}) + .data() + .reduce(function (a, b) { + return a + b; + }, 0); + + avgTimeTotal = api + .column(colOffset + 5, {page: 'current'}) + .data() + .reduce(function (a, b) { + return (a + b); + }, 0) / api.column(26, {page: 'current'}).data().length; + + sumSharesGood = api + .column(colOffset + 6, {page: 'current'}) + .data() + .reduce(function (a, b) { + return a + b; + }, 0); + + sumSharedTotal = api + .column(colOffset + 7, {page: 'current'}) + .data() + .reduce(function (a, b) { + return a + b; + }, 0); + + sumHashrateShort = round(sumHashrateShort); + sumHashrateMedium = round(sumHashrateMedium); + sumHashrateLong = round(sumHashrateLong); + sumHashrateHighest = round(sumHashrateHighest); + avgTimeTotal = round(avgTimeTotal); + + // update footer + $(api.column(colOffset).footer()).html(sumHashrateShort); + $(api.column(colOffset + 1).footer()).html(sumHashrateMedium); + $(api.column(colOffset + 2).footer()).html(sumHashrateLong); + $(api.column(colOffset + 3).footer()).html(sumHashrateHighest); + $(api.column(colOffset + 4).footer()).html(sumHashesTotal); + $(api.column(colOffset + 5).footer()).html(avgTimeTotal); + $(api.column(colOffset + 6).footer()).html(sumSharesGood); + $(api.column(colOffset + 7).footer()).html(sumSharedTotal); + } + }); + + table.on('xhr.dt', function (e, settings, json, xhr) { + // check version + if (latestRelease === "" && json !== undefined) { + $.ajax({ + url: "https://api.github.com/repos/Bendr0id/xmrigCC/releases/latest", + type: 'GET', + dataType: "json", + success: function (release) { + latestRelease = release.tag_name + latestVersion = parseInt(release.tag_name.split('.').join("")); + currentVersion = parseInt(json.current_version.split('.').join("")); + + if (currentVersion < 1000) { + currentVersion = currentVersion * 10; + } + + if (latestVersion < 1000) { + latestVersion = latestVersion * 10; + } + + if (latestVersion > currentVersion) { + $("#updateNotificationBar").html(''); + } + } }); + } + + currentServerTime = settings.json.current_server_time * 1000; + clockDrift = new Date().getTime() - currentServerTime; + + $('#serverTime').html("
" + new Date(currentServerTime) + "
"); + }); + + table.on('init', function () { + if ($('#groupByAlgo').prop('checked')) { + table.rowGroup().enable().draw(); + table.order.fixed({ + pre: [8, 'asc'] // algo + }).draw(); + } else { + table.rowGroup().disable().draw(); + table.order.fixed({ + pre: [] + }).draw(); + } + }); + + table.on('select', function () { + let selectedRows = table.rows({selected: true}).count(); + for (let i = 1; i < 12; i++) { + table.button(i).enable(selectedRows > 0 || i === 5); + } + }); + + table.on('deselect', function () { + let selectedRows = table.rows({selected: true}).count(); + + for (let i = 1; i < 12; i++) { + table.button(i).enable(selectedRows > 0 || i === 5); + } + }); + + table.buttons().container().appendTo('#clientStatusList_wrapper .col-sm-6:eq(0)'); + + $('#hideOffline').change(function () { + table.draw(); + }); + + $('#groupByAlgo').change(function () { + if ($('#groupByAlgo').prop('checked')) { + table.rowGroup().enable().draw(); + table.order.fixed({ + pre: [8, 'asc'] // algo + }).draw(); + } else { + table.rowGroup().disable().draw(); + table.order.fixed({ + pre: [] + }).draw(); + } + }); + + $('#resetClientStatus').click(function () { + $('#resetClientStatusListDialog').modal('show'); + }); + + $('#resetClientStatusList').click(function () { + resetClientStatusList(); + }); + + $('#clientStatusList tbody ').on('click', 'button#EDIT', function () { + let data = table.row($(this).parents('tr')).data(); + let clientId = data['client_status']['client_id']; + + $.ajax({ + type: "GET", + url: "/admin/getClientConfig?clientId=" + htmlDecode(clientId), + dataType: "json", + success: function (jsonClientConfig) { + let htmlContent = "
" + + "" + + "" + + "
"; + + $('#minerEditor').find('.modal-body').html(htmlContent); + $('#minerEditor').modal('show'); + }, + error: function (data) { + setError('Unable to fetch ' + clientId + '_config.json - Please make sure that you pulled the config before!'); + } + }); + }); + + $('#minerEditorSave').click(function (event) { + let clientId = htmlEncode($('#minerEditor').find('.form-group')["0"].dataset.value); + let clientConfig = $('#config').val(); + + setClientConfig(clientId, clientConfig); + }); + + $('#templateEditorSave').click(function (event) { + let templateId = htmlEncode($('#templateSelector').val()); + let template = $('#template').val(); + + setTemplateConfig(templateId, template); + }); + + $('#templateEditorSaveAsDialog').click(function (event) { + $('#templateDialogSaveAs').modal('show'); + }); + + $('#templateEditorDeleteDialog').click(function (event) { + $('#templateDialogDelete').modal('show'); + }); + + $('#templateEditorSaveAs').click(function (event) { + let templateId = htmlEncode($('#templateName').val()); + let template = $('#template').val(); + + setTemplateConfig(templateId, template); + + $('#templateEditor').modal('hide'); + }); + + $('#templateEditorDelete').click(function (event) { + let templateId = htmlEncode($('#templateSelector').val()); + + deleteTemplateConfig(templateId, template); + }); + + $('#commandStop').click(function (event) { + table.rows({selected: true}).eq(0).each(function (index) { + let row = table.row(index); + let data = row.data(); + + sendAction("SHUTDOWN", data.client_status.client_id); + }); + }); + + $('#commandExecuteDialogExecute').click(function (event) { + let command = $('#executeCommand').val(); + + table.rows({selected: true}).eq(0).each(function (index) { + let row = table.row(index); + let data = row.data(); + + sendAction("EXECUTE", data.client_status.client_id, command); + }); + }); + + $('#clientStatusList tbody').on('click', 'button#LOG', function () { + let data = table.row($(this).parents('tr')).data(); + let clientId = data['client_status']['client_id']; + let clientIp = data['client_status']['external_ip']; + + $.ajax({ + type: "GET", + url: "/admin/getClientLog?clientId=" + htmlDecode(clientId), + dataType: "json", + success: function (data) { + let htmlContent = "
" + + "" + + "" + + "
"; + + $('#minerLog').find('.modal-body').html(htmlContent); + $('#minerLog').modal('show'); + }, + error: function (data) { + setError('Unable to fetch ' + clientId + ' log. - Please make sure it is enabled on the miner!'); + } + }); + }); + + $('#minerLogRefresh').click(function (event) { + let clientId = htmlEncode($('#minerLog').find('.form-group')["0"].dataset.value); - table.on('init', function(){ - if ($('#groupByAlgo').prop('checked')) { - table.rowGroup().enable().draw(); - table.order.fixed({ - pre: [5, 'asc'] - }).draw(); - } else { - table.rowGroup().disable().draw(); - table.order.fixed({ - pre: [] - }).draw(); - } - }); - - table.on('select', function () { - let selectedRows = table.rows({selected: true}).count(); - for (let i=1; i < 12; i++) - { - table.button(i).enable(selectedRows > 0 || i === 5); - } - }); - - table.on('deselect', function () { - let selectedRows = table.rows({selected: true}).count(); + table.rows().eq(0).each(function (index) { + let row = table.row(index); + let data = row.data(); - for (let i=1; i < 12; i++) - { - table.button(i).enable(selectedRows > 0 || i === 5); - } - }); - - table.buttons().container().appendTo('#clientStatusList_wrapper .col-sm-6:eq(0)'); - - $('#hideOffline').change(function () { - table.draw(); - }); - - $('#groupByAlgo').change(function () { - if ($('#groupByAlgo').prop('checked')) { - table.rowGroup().enable().draw(); - table.order.fixed({ - pre: [5, 'asc'] - }).draw(); - } else { - table.rowGroup().disable().draw(); - table.order.fixed({ - pre: [] - }).draw(); + $.ajax({ + type: "GET", + url: "/admin/getClientLog?clientId=" + htmlDecode(clientId), + dataType: "json", + success: function (data) { + $('#log').val(data.client_log); + }, + error: function (data) { + setError('Unable to fetch ' + clientId + ' log. - Please make sure it is enabled on the miner!'); } }); - - $('#resetClientStatusList').click(function () { - resetClientStatusList(); - }); + }); + }); - - - $('#clientStatusList tbody ').on('click', 'button#EDIT', function () { - let data = table.row($(this).parents('tr')).data(); - let clientId = data['client_status']['client_id']; - - $.ajax({ - type: "GET", - url: "/admin/getClientConfig?clientId=" + htmlDecode(clientId), - dataType: "json", - success: function (jsonClientConfig) { - let htmlContent = "
" + - "" + - "" + - "
"; - - $('#minerEditor').find('.modal-body').html(htmlContent); - $('#minerEditor').modal('show'); - }, - error: function (data) { - setError('Unable to fetch ' + clientId + '_config.json - Please make sure that you pulled the config before!'); - } - }); - }); - - $('#minerEditorSave').click(function (event) { - let clientId = htmlEncode($('#minerEditor').find('.form-group')["0"].dataset.value); - let clientConfig = $('#config').val(); - - setClientConfig(clientId, clientConfig); - }); - - $('#templateEditorSave').click(function (event) { - let templateId = htmlEncode($('#templateSelector').val()); - let template = $('#template').val(); - - setTemplateConfig(templateId, template); - }); - - $('#templateEditorSaveAsDialog').click(function (event) { - $('#templateDialogSaveAs').modal('show'); - }); - - $('#templateEditorDeleteDialog').click(function (event) { - $('#templateDialogDelete').modal('show'); - }); - - $('#templateEditorSaveAs').click(function (event) { - let templateId = htmlEncode($('#templateName').val()); - let template = $('#template').val(); - - setTemplateConfig(templateId, template); - - $('#templateEditor').modal('hide'); - }); - - $('#templateEditorDelete').click(function (event) { - let templateId = htmlEncode($('#templateSelector').val()); - - deleteTemplateConfig(templateId, template); - }); - - $('#commandStop').click(function (event) { - table.rows({selected: true}).eq(0).each(function (index) { - let row = table.row(index); - let data = row.data(); - - sendAction("SHUTDOWN", data.client_status.client_id); - }); - }); - - $('#commandExecuteDialogExecute').click(function (event) { - let command = $('#executeCommand').val(); - + $('#templateSelector').on('changed.bs.select', function (e, clickedIndex, newValue, oldValue) { + let selected = $(e.currentTarget).val(); + $.ajax({ + type: "GET", + url: "/admin/getClientConfig?clientId=template_" + selected, + dataType: "json", + success: function (jsonClientConfig) { + $('#template').val(JSON.stringify(jsonClientConfig, undefined, 2)); + }, + error: function (data) { + setError('Unable to fetch template ' + selected + ' - Please make sure it readable!'); + } + }); + }); + + $('#assignTemplate').click(function (event) { + let template = $('#assignTemplateSelector').val(); + $.ajax({ + type: "GET", + url: "/admin/getClientConfig?clientId=template_" + template, + dataType: "json", + success: function (jsonTemplate) { table.rows({selected: true}).eq(0).each(function (index) { let row = table.row(index); let data = row.data(); - - sendAction("EXECUTE", data.client_status.client_id, command); - }); - }); - - $('#clientStatusList tbody').on('click', 'button#LOG', function () { - let data = table.row($(this).parents('tr')).data(); - let clientId = data['client_status']['client_id']; - let clientIp = data['client_status']['external_ip']; - - $.ajax({ - type: "GET", - url: "/admin/getClientLog?clientId=" + htmlDecode(clientId), - dataType: "json", - success: function (data) { - let htmlContent = "
" + - "" + - "" + - "
"; - - $('#minerLog').find('.modal-body').html(htmlContent); - $('#minerLog').modal('show'); - }, - error: function (data) { - setError('Unable to fetch ' + clientId + ' log. - Please make sure it is enabled on the miner!'); - } - }); - }); - - $('#minerLogRefresh').click(function (event) { - let clientId = htmlEncode($('#minerLog').find('.form-group')["0"].dataset.value); - - table.rows().eq(0).each(function (index) { - let row = table.row(index); - let data = row.data(); - - $.ajax({ - type: "GET", - url: "/admin/getClientLog?clientId=" + htmlDecode(clientId), - dataType: "json", - success: function (data) { - $('#log').val(data.client_log); - }, - error: function (data) { - setError('Unable to fetch ' + clientId + ' log. - Please make sure it is enabled on the miner!'); + let clientId = data['client_status']['client_id']; + + if ($('#mergeTemplate').prop('checked')) { + setMergedClientConfig(jsonTemplate, clientId); + } else { + jsonTemplate['cc-client']['worker-id'] = htmlDecode(clientId); + + clientConfig = JSON.stringify(jsonTemplate, undefined, 2); + + if ($('#replaceWorkerId').prop('checked')) { + clientConfig = clientConfig.replace(new RegExp("@WORKER-ID@", 'g'), htmlDecode(clientId)).trim(); } - }); - }); - }); - - $('#templateSelector').on('changed.bs.select', function (e, clickedIndex, newValue, oldValue) { - let selected = $(e.currentTarget).val(); - $.ajax({ - type: "GET", - url: "/admin/getClientConfig?clientId=template_" + selected, - dataType: "json", - success: function (jsonClientConfig) { - $('#template').val(JSON.stringify(jsonClientConfig, undefined, 2)); - }, - error: function (data) { - setError('Unable to fetch template ' + selected + ' - Please make sure it readable!'); + + setClientConfig(clientId, clientConfig); } }); - }); - - $('#assignTemplate').click(function (event) { - let template = $('#assignTemplateSelector').val(); - $.ajax({ - type: "GET", - url: "/admin/getClientConfig?clientId=template_" + template, - dataType: "json", - success: function (jsonTemplate) { - table.rows({selected: true}).eq(0).each(function (index) { - let row = table.row(index); - let data = row.data(); - let clientId = data['client_status']['client_id']; - - if ($('#mergeTemplate').prop('checked')) { - setMergedClientConfig(jsonTemplate, clientId); - } else { - jsonTemplate['cc-client']['worker-id'] = htmlDecode(clientId); - - clientConfig = JSON.stringify(jsonTemplate, undefined, 2); - - if ($('#replaceWorkerId').prop('checked')) { - clientConfig = clientConfig.replace(new RegExp("@WORKER-ID@", 'g'), htmlDecode(clientId)).trim(); - } - - setClientConfig(clientId, clientConfig); - } - }); + }, + error: function (data) { + setError('Unable to fetch template ' + template + ' - Please make sure it readable!'); + } + }); + }); + + $('#selectAllTop,#selectAllBottom').click(function () { + if ($("#selectAllTop").hasClass("fa fa-square-o")) { + $("#selectAllTop").removeClass("fa fa-square-o").addClass("fa fa-check-square-o"); + $("#selectAllBottom").removeClass("fa fa-square-o").addClass("fa fa-check-square-o"); + + table.rows().select(); + } else { + $("#selectAllTop").removeClass("fa fa-check-square-o").addClass("fa fa-square-o"); + $("#selectAllBottom").removeClass("fa fa-check-square-o").addClass("fa fa-square-o"); + + table.rows().deselect(); + } + }); + + + let hashrateChart = new Chart(document.getElementById("hashrateChart").getContext("2d"), { + type: "line", + data: { + datasets: [] + }, + options: { + title: { + display: true, + text: 'Hashrates' + }, + tooltips: { + mode: 'nearest', + intersect: false, + }, + legend: { + display: true, + position: 'top', + labels: { + generateLabels: function (chart) { + let data = chart.data; + return Chart.helpers.isArray(data.datasets) ? data.datasets.map(function (dataset, i) { + let total = 0; + dataset.data.forEach(item => { + total += item.y; + }); + + let avg = (dataset.data.length > 0) ? (total / dataset.data.length).toFixed(2) : 0; + return { + text: dataset.label + " (avg.: " + avg + " h/s)", + fillStyle: (!Chart.helpers.isArray(dataset.backgroundColor) ? dataset.backgroundColor : dataset.backgroundColor[0]), + hidden: !chart.isDatasetVisible(i), + lineCap: dataset.borderCapStyle, + lineDash: dataset.borderDash, + lineDashOffset: dataset.borderDashOffset, + lineJoin: dataset.borderJoinStyle, + lineWidth: dataset.borderWidth, + strokeStyle: dataset.borderColor, + pointStyle: dataset.pointStyle, + + // Below is extra data used for toggling the datasets + datasetIndex: i + }; + }, this) : []; }, - error: function (data) { - setError('Unable to fetch template ' + template + ' - Please make sure it readable!'); - } - }); - }); - - $('#selectAllTop,#selectAllBottom').click(function () { - if ($("#selectAllTop").hasClass("fa fa-square-o")) { - $("#selectAllTop").removeClass("fa fa-square-o").addClass("fa fa-check-square-o"); - $("#selectAllBottom").removeClass("fa fa-square-o").addClass("fa fa-check-square-o"); - - table.rows().select(); - } else { - $("#selectAllTop").removeClass("fa fa-check-square-o").addClass("fa fa-square-o"); - $("#selectAllBottom").removeClass("fa fa-check-square-o").addClass("fa fa-square-o"); - - table.rows().deselect(); - } - }); - - - let hashrateChart = new Chart(document.getElementById("hashrateChart").getContext("2d"), { - type: "line", - data: { - datasets: [] }, - options: { - title:{ - display:true, - text:'Hashrates' - }, - tooltips: { - mode: 'nearest', - intersect: false, - }, - legend: { - display: true, - position: 'top', - labels: { - generateLabels: function (chart) { - let data = chart.data; - return Chart.helpers.isArray(data.datasets) ? data.datasets.map(function (dataset, i) { - let total = 0; - dataset.data.forEach(item => { - total += item.y; - }); - - let avg = (dataset.data.length > 0) ? (total / dataset.data.length).toFixed(2) : 0; - return { - text: dataset.label + " (avg.: " + avg + " h/s)", - fillStyle: (!Chart.helpers.isArray(dataset.backgroundColor) ? dataset.backgroundColor : dataset.backgroundColor[0]), - hidden: !chart.isDatasetVisible(i), - lineCap: dataset.borderCapStyle, - lineDash: dataset.borderDash, - lineDashOffset: dataset.borderDashOffset, - lineJoin: dataset.borderJoinStyle, - lineWidth: dataset.borderWidth, - strokeStyle: dataset.borderColor, - pointStyle: dataset.pointStyle, - - // Below is extra data used for toggling the datasets - datasetIndex: i - }; - }, this) : []; - }, + }, + scales: { + yAxes: [ + { + id: 'hashrate', + position: 'left', + scalePositionLeft: true, + stacked: false, + ticks: { + beginAtZero: true }, - }, - scales: { - yAxes: [ - { - id: 'hashrate', - position: 'left', - scalePositionLeft: true, - stacked: false, - ticks: { - beginAtZero: true - }, - scaleLabel: { - display: true, - labelString: 'Hashrate', - fontSize: 16 - } - } - ], - xAxes: [{ - type: 'time', - distribution: 'series', - time: { - unit: 'minute', - displayFormats: { - minute: 'HH:mm' - } - }, - ticks: { - stepSize: 10, - autoSkip: true, - maxTicksLimit: 60 - } - }] - }, - elements: { - point:{ - radius: 0 + scaleLabel: { + display: true, + labelString: 'Hashrate', + fontSize: 16 + } + } + ], + xAxes: [{ + type: 'time', + distribution: 'series', + time: { + unit: 'minute', + displayFormats: { + minute: 'HH:mm' } }, - responsive: true, - maintainAspectRatio: false - }, - plugins: { - colorschemes: { - scheme: 'brewer.Paired12' + ticks: { + stepSize: 10, + autoSkip: true, + maxTicksLimit: 60 } + }] + }, + elements: { + point: { + radius: 0 } - }); - - let minerChart = new Chart(document.getElementById("minerChart").getContext("2d"), { - type: "line", - data: { - datasets: [] - }, - options: { - title:{ - display:true, - text:'Miners' - }, - tooltips: { - mode: 'nearest', - intersect: false, - }, - scales: { - yAxes: [ - { - id: 'miner', - position: 'left', - scalePositionLeft: true, - stacked: false, - ticks: { - beginAtZero: true, - callback: function(value) {if (value % 1 === 0) {return value;}} - }, - scaleLabel: { - display: true, - labelString: 'Miner', - fontSize: 16 - } - } - ], - xAxes: [{ - type: 'time', - distribution: 'series', - time: { - unit: 'minute', - displayFormats: { - minute: 'HH:mm' + }, + responsive: true, + maintainAspectRatio: false + }, + plugins: { + colorschemes: { + scheme: 'brewer.Paired12' + } + } + }); + + let minerChart = new Chart(document.getElementById("minerChart").getContext("2d"), { + type: "line", + data: { + datasets: [] + }, + options: { + title: { + display: true, + text: 'Miners' + }, + tooltips: { + mode: 'nearest', + intersect: false, + }, + scales: { + yAxes: [ + { + id: 'miner', + position: 'left', + scalePositionLeft: true, + stacked: false, + ticks: { + beginAtZero: true, + callback: function (value) { + if (value % 1 === 0) { + return value; } - }, - ticks: { - stepSize: 10, - autoSkip: true, - maxTicksLimit: 60 } - }] - }, - elements: { - point:{ - radius: 0 + }, + scaleLabel: { + display: true, + labelString: 'Miner', + fontSize: 16 } - }, - responsive: true, - maintainAspectRatio: false - }, - plugins: { - colorschemes: { - scheme: 'brewer.Paired12' } - } - }); - - Chart.plugins.register({ - afterDraw: function(chart) { - if (chart.data.datasets.length === 0) { - // No data is present - let ctx = chart.chart.ctx; - let width = chart.chart.width; - let height = chart.chart.height - - ctx.save(); - ctx.textAlign = 'center'; - ctx.textBaseline = 'middle'; - ctx.font = "16px normal 'Helvetica Nueue'"; - ctx.fillText('No data to display', width / 2, height / 2); - ctx.restore(); + ], + xAxes: [{ + type: 'time', + distribution: 'series', + time: { + unit: 'minute', + displayFormats: { + minute: 'HH:mm' + } + }, + ticks: { + stepSize: 10, + autoSkip: true, + maxTicksLimit: 60 } + }] + }, + elements: { + point: { + radius: 0 } - }); - - updateCharts(hashrateChart, minerChart); - - setInterval(function () { - table.ajax.reload(null, false); - }, RELOAD_INTERVAL_IN_MS); - - setInterval(function () { - updateCharts(hashrateChart, minerChart); - }, CHART_RELOAD_INTERVAL_IN_MS); - }); - - function clientHPpercent(data, type, row) { - if (!row.client_status.hugepages_enabled || row.client_status.total_pages == 0) return 0; - return Math.round(1000 * row.client_status.total_hugepages / row.client_status.total_pages) / 10; - } - - function percent(data, type, row) { - if (row.client_status.max_cpu_usage === -1) { - return 100; - } else { - return row.client_status.max_cpu_usage; + }, + responsive: true, + maintainAspectRatio: false + }, + plugins: { + colorschemes: { + scheme: 'brewer.Paired12' } } + }); - function sendAction(action, clientId, payload) { - let payloadContent = payload == null ? '' : ' [payload=\'' + payload +'\']'; - $.ajax({ - type: "POST", - url: "/admin/setClientCommand?clientId=" + htmlDecode(clientId), - dataType: "text", - data: '{"control_command":{"command": "' + action + '", "payload": "' + payload + '"}}', - success: function (data) { - setSuccess('Successfully send ' + action + ' to ' + clientId + payloadContent + ' - It can take up to 30s until the command is processed.'); - }, - error: function (data) { - setError('Failed to send ' + action + ' to ' + clientId + + payloadContent +' \nError: ' + JSON.stringify(data, undefined, 2)); - } - }); - } - - function resetClientStatusList() { - $.ajax({ - type: "POST", - url: "/admin/resetClientStatusList", - dataType: "text", - data: '{}', - success: function (data) { - setSuccess('Successfully send the reset client status list request to the Server. - Now just wait for the next refresh.'); - }, - error: function (data) { - setError('Failed to send the reset client status list request to the Server. \nError: ' + JSON.stringify(data, undefined, 2)); - } - }); - } - - function uptime(data, type, row) { - if (type !== 'sort') { - let lastStatus = row.client_status.last_status_update * 1000; - - if (isOnline(lastStatus)) { - return numeral(data / 1000).format('00:00:00:00'); - } else { - return ""; - } + Chart.plugins.register({ + afterDraw: function (chart) { + if (chart.data.datasets.length === 0) { + // No data is present + let ctx = chart.chart.ctx; + let width = chart.chart.width; + let height = chart.chart.height + + ctx.save(); + ctx.textAlign = 'center'; + ctx.textBaseline = 'middle'; + ctx.font = "16px normal 'Helvetica Nueue'"; + ctx.fillText('No data to display', width / 2, height / 2); + ctx.restore(); } - - return data; } - - function setMergedClientConfig(template, clientId) { - $.ajax({ - type: "GET", - url: "/admin/getClientConfig?clientId=" + htmlDecode(clientId), - dataType: "json", - success: function (clientConfig) { - $.extend(true, clientConfig, template); - - clientConfig = JSON.stringify(clientConfig, undefined, 2) - - if ($('#replaceWorkerId').prop('checked')) { - clientConfig = clientConfig.replace(new RegExp("@WORKER-ID@", 'g'), htmlDecode(clientId)).trim(); - } - - setClientConfig(clientId, clientConfig); - }, - error: function (data) { - setError('Unable to fetch client config ' + clientId + ' for template merge - Please make sure it readable!'); - } - }); + }); + + updateCharts(hashrateChart, minerChart); + + setInterval(function () { + table.ajax.reload(null, false); + }, RELOAD_INTERVAL_IN_MS); + + setInterval(function () { + updateCharts(hashrateChart, minerChart); + }, CHART_RELOAD_INTERVAL_IN_MS); + }); + + function clientHPpercent(data, type, row) { + if (!row.client_status.hugepages_enabled || row.client_status.total_pages == 0) return 0; + return Math.round(1000 * row.client_status.total_hugepages / row.client_status.total_pages) / 10; + } + + function percent(data, type, row) { + if (row.client_status.max_cpu_usage === -1) { + return 100; + } else { + return row.client_status.max_cpu_usage; + } + } + + function sendAction(action, clientId, payload) { + let payloadContent = payload == null ? '' : ' [payload=\'' + payload + '\']'; + $.ajax({ + type: "POST", + url: "/admin/setClientCommand?clientId=" + htmlDecode(clientId), + dataType: "text", + data: '{"control_command":{"command": "' + action + '", "payload": "' + payload + '"}}', + success: function (data) { + setSuccess('Successfully send ' + action + ' to ' + clientId + payloadContent + ' - It can take up to 30s until the command is processed.'); + }, + error: function (data) { + setError('Failed to send ' + action + ' to ' + clientId + payloadContent + ' \nError: ' + JSON.stringify(data, undefined, 2)); } - - function laststatus(data, type, row) { - if (type !== 'sort') { - let date = new Date(data * 1000 - clockDrift); - return '' + jQuery.timeago(date) + ''; - } - - return data; + }); + } + + function removeClientStatus(clientId) { + $.ajax({ + type: "POST", + url: "/admin/removeClientStatus?clientId=" + htmlDecode(clientId), + dataType: "text", + data: '', + success: function (data) { + setSuccess('Successfully send the removed client status request to the Server. - Now just wait for the next refresh.'); + }, + error: function (data) { + setError('Failed to send the removed client status request to the Server. \nError: ' + JSON.stringify(data, undefined, 2)); } - - function algoAndPowVariantName(data, type, row) { - let algo = row.client_status.current_algo_name; - let powVariant = row.client_status.current_pow_variant_name; - - if (powVariant !== "") { - return algo + " / " + powVariant - } else { - return algo; - } + }); + } + + function resetClientStatusList() { + $.ajax({ + type: "POST", + url: "/admin/resetClientStatusList", + dataType: "text", + data: '{}', + success: function (data) { + setSuccess('Successfully send the reset client status list request to the Server. - Now just wait for the next refresh.'); + }, + error: function (data) { + setError('Failed to send the reset client status list request to the Server. \nError: ' + JSON.stringify(data, undefined, 2)); } - - function version(data, type, row) { - let clientVersion = parseInt(row.client_status.version.split('.').join("")); - - if (clientVersion < 1000) { - clientVersion = clientVersion * 10; - } - - if (latestVersion > clientVersion) { - return '
' + data + '
'; - } else { - return data; - } + }); + } + + function uptime(data, type, row) { + if (type !== 'sort') { + let lastStatus = row.client_status.last_status_update * 1000; + + if (isOnline(lastStatus)) { + return numeral(data / 1000).format('00:00:00:00'); + } else { + return ""; } - - function clientStatus(data, type, row) { - let lastStatus = row.client_status.last_status_update * 1000; - - if (isOnline(lastStatus)) { - return data; - } else { - return "OFFLINE"; + } + + return data; + } + + function setMergedClientConfig(template, clientId) { + $.ajax({ + type: "GET", + url: "/admin/getClientConfig?clientId=" + htmlDecode(clientId), + dataType: "json", + success: function (clientConfig) { + $.extend(true, clientConfig, template); + + clientConfig = JSON.stringify(clientConfig, undefined, 2) + + if ($('#replaceWorkerId').prop('checked')) { + clientConfig = clientConfig.replace(new RegExp("@WORKER-ID@", 'g'), htmlDecode(clientId)).trim(); } - } - function checkbox(data, type, row){ - return '
' + setClientConfig(clientId, clientConfig); + }, + error: function (data) { + setError('Unable to fetch client config ' + clientId + ' for template merge - Please make sure it readable!'); } - - function clientInfo(data, type, row) { - if (type !== 'sort') { - let lastStatus = row.client_status.last_status_update * 1000; - let online = isOnline(lastStatus); - - let tooltip = "CPU: " + row.client_status.cpu_brand + " (" + row.client_status.cpu_sockets + ") [" + row.client_status.cpu_cores + " cores / " + row.client_status.cpu_threads + " threads]"; - tooltip += '\n'; - tooltip += "CPU Flags: " + (row.client_status.cpu_has_aes ? "AES-NI " : ""); - tooltip += (row.client_status.cpu_is_x64 ? "x64 " : ""); - tooltip += (row.client_status.cpu_is_vm ? "VM" : ""); - tooltip += '\n'; - tooltip += "CPU Cache L2/L3: " + cache(row.client_status.cpu_l2) + " MB/" + cache(row.client_status.cpu_l3) + " MB"; - tooltip += '\n'; - tooltip += "CPU Nodes: " + row.client_status.cpu_nodes; - tooltip += '\n'; - tooltip += "Max CPU usage: " + (row.client_status.max_cpu_usage > 0 ? row.client_status.max_cpu_usage : "100") + "%"; - tooltip += '\n'; - tooltip += "Huge Pages: " + (row.client_status.hugepages_available ? " available, " : " unavailable, "); - tooltip += (row.client_status.hugepages_enabled ? "enabled (" + row.client_status.total_hugepages + "/" + row.client_status.total_pages + ")" : "disabled"); - tooltip += '\n'; - tooltip += "Used Threads: " + row.client_status.current_threads; - tooltip += (row.client_status.hash_factor > 1 ? " [" + row.client_status.hash_factor + "x multi hash mode]" : ""); - tooltip += '\n'; - tooltip += "Memory Free/Total: " + memory(row.client_status.free_memory) + " GB/" + memory(row.client_status.total_memory) + " GB"; - tooltip += '\n'; - - if (row.client_status.gpu_info_list) { - for (let id in row.client_status.gpu_info_list) { - tooltip += "GPU #" + row.client_status.gpu_info_list[id].gpu_info.device_idx + ": "; - tooltip += row.client_status.gpu_info_list[id].gpu_info.name + ", " - tooltip += "intensity: " + row.client_status.gpu_info_list[id].gpu_info.raw_intensity + " "; - tooltip += "(" + row.client_status.gpu_info_list[id].gpu_info.work_size + "/" + row.client_status.gpu_info_list[id].gpu_info.max_work_size + "), "; - tooltip += "cu: " + row.client_status.gpu_info_list[id].gpu_info.compute_units; - tooltip += '\n'; - } - } - - tooltip += "Client IP: " + row.client_status.external_ip; - tooltip += '\n'; - tooltip += "Version: " + row.client_status.version; + }); + } + + function laststatus(data, type, row) { + if (type !== 'sort') { + let date = new Date(data * 1000 - clockDrift); + return '' + jQuery.timeago(date) + ''; + } + + return data; + } + + function algoAndPowVariantName(data, type, row) { + let algo = row.client_status.current_algo_name; + let powVariant = row.client_status.current_pow_variant_name; + + if (powVariant !== "") { + return algo + " / " + powVariant + } else { + return algo; + } + } + + function version(data, type, row) { + let clientVersion = parseInt(row.client_status.version.split('.').join("")); + + if (clientVersion < 1000) { + clientVersion = clientVersion * 10; + } + + if (latestVersion > clientVersion) { + return '
' + data + '
'; + } else { + return data; + } + } + + function clientStatus(data, type, row) { + let lastStatus = row.client_status.last_status_update * 1000; + + if (isOnline(lastStatus)) { + return data; + } else { + return "OFFLINE"; + } + } + + function checkbox(data, type, row) { + return '
' + } + + function clientInfo(data, type, row) { + if (type !== 'sort') { + let lastStatus = row.client_status.last_status_update * 1000; + let online = isOnline(lastStatus); + + let tooltip = "CPU: " + row.client_status.cpu_brand + " (" + row.client_status.cpu_sockets + ") [" + row.client_status.cpu_cores + " cores / " + row.client_status.cpu_threads + " threads]"; + tooltip += '\n'; + tooltip += "CPU Flags: " + (row.client_status.cpu_has_aes ? "AES-NI " : ""); + tooltip += (row.client_status.cpu_is_x64 ? "x64 " : ""); + tooltip += (row.client_status.cpu_is_vm ? "VM" : ""); + tooltip += '\n'; + tooltip += "CPU Cache L2/L3: " + cache(row.client_status.cpu_l2) + " MB/" + cache(row.client_status.cpu_l3) + " MB"; + tooltip += '\n'; + tooltip += "CPU Nodes: " + row.client_status.cpu_nodes; + tooltip += '\n'; + tooltip += "Max CPU usage: " + (row.client_status.max_cpu_usage > 0 ? row.client_status.max_cpu_usage : "100") + "%"; + tooltip += '\n'; + tooltip += "Huge Pages: " + (row.client_status.hugepages_available ? " available, " : " unavailable, "); + tooltip += (row.client_status.hugepages_enabled ? "enabled (" + row.client_status.total_hugepages + "/" + row.client_status.total_pages + ")" : "disabled"); + tooltip += '\n'; + tooltip += "Used Threads: " + row.client_status.current_threads; + tooltip += (row.client_status.hash_factor > 1 ? " [" + row.client_status.hash_factor + "x multi hash mode]" : ""); + tooltip += '\n'; + tooltip += "Memory Free/Total: " + memory(row.client_status.free_memory) + " GB/" + memory(row.client_status.total_memory) + " GB"; + tooltip += '\n'; + + if (row.client_status.gpu_info_list) { + for (let id in row.client_status.gpu_info_list) { + tooltip += "GPU #" + row.client_status.gpu_info_list[id].gpu_info.device_idx + ": "; + tooltip += row.client_status.gpu_info_list[id].gpu_info.name + ", " + tooltip += "intensity: " + row.client_status.gpu_info_list[id].gpu_info.raw_intensity + " "; + tooltip += "(" + row.client_status.gpu_info_list[id].gpu_info.work_size + "/" + row.client_status.gpu_info_list[id].gpu_info.max_work_size + "), "; + tooltip += "cu: " + row.client_status.gpu_info_list[id].gpu_info.compute_units; tooltip += '\n'; - tooltip += "Status: " + online ? "Online" : "Offline"; - - if (online) { - return '
' + data + '
'; - } else { - return '
' + data + '
'; - } - } - - return data; - } - - function round(data, type, row) { - return Math.round(data * 100) / 100; - } - - function memory(data, type, row) { - return Math.round(data / 1024 / 1024 /1024 * 10) / 10; - } - - function cache(data, type, row) { - return Math.round(data / 1024 * 100) / 100; - } - - function isOnline(lastStatus) { - let threshold = currentServerTime - THRESHOLD_IN_MS; - if (lastStatus > threshold) { - return true; - } else { - return false; } } - - function setSuccess(info) { - $("#statusBar").after(''); - - window.setTimeout(function () { - $(".alert-success").fadeTo(500, 0).slideUp(500, function () { - $(".alert-success").alert('close'); - }); - }, 5000); + + tooltip += "Client IP: " + row.client_status.external_ip; + tooltip += '\n'; + tooltip += "Version: " + row.client_status.version; + tooltip += '\n'; + tooltip += "Status: " + online ? "Online" : "Offline"; + + if (online) { + return '
' + data + '
'; + } else { + return '
' + data + '
'; } - - function setError(error) { - $("#statusBar").after(''); - - window.setTimeout(function () { - $(".alert-danger").fadeTo(500, 0).slideUp(500, function () { - $(".alert-danger").alert('close'); - }); - }, 10000); + } + + return data; + } + + function round(data, type, row) { + return Math.round(data * 100) / 100; + } + + function memory(data, type, row) { + return Math.round(data / 1024 / 1024 / 1024 * 10) / 10; + } + + function cache(data, type, row) { + return Math.round(data / 1024 * 100) / 100; + } + + function isOnline(lastStatus) { + let threshold = currentServerTime - THRESHOLD_IN_MS; + if (lastStatus > threshold) { + return true; + } else { + return false; + } + } + + function setSuccess(info) { + $("#statusBar").after(''); + + window.setTimeout(function () { + $(".alert-success").fadeTo(500, 0).slideUp(500, function () { + $(".alert-success").alert('close'); + }); + }, 5000); + } + + function setError(error) { + $("#statusBar").after(''); + + window.setTimeout(function () { + $(".alert-danger").fadeTo(500, 0).slideUp(500, function () { + $(".alert-danger").alert('close'); + }); + }, 10000); + } + + function setClientConfig(clientId, clientConfig) { + $.ajax({ + url: "/admin/setClientConfig?clientId=" + htmlDecode(clientId), + type: 'POST', + dataType: "text", + data: clientConfig, + success: function (data) { + setSuccess('Successfully updated config for: ' + clientId + ' - You need push the config to the miner to apply the config.'); + }, + error: function (data) { + setError('Failed to update config for: ' + clientId + ' \nError: ' + JSON.stringify(data, undefined, 2)); } - - function setClientConfig(clientId, clientConfig) { - $.ajax({ - url: "/admin/setClientConfig?clientId=" + htmlDecode(clientId), - type: 'POST', - dataType: "text", - data: clientConfig, - success: function (data) { - setSuccess('Successfully updated config for: ' + clientId + ' - You need push the config to the miner to apply the config.'); - }, - error: function (data) { - setError('Failed to update config for: ' + clientId + ' \nError: ' + JSON.stringify(data, undefined, 2)); - } - }); + }); + } + + function setTemplateConfig(templateId, templateConfig) { + $.ajax({ + url: "/admin/setClientConfig?clientId=template_" + htmlDecode(templateId), + type: 'POST', + dataType: "text", + data: templateConfig, + success: function (data) { + setSuccess('Successfully stored template: ' + templateId + ' - You need to assign it to miners to apply.'); + }, + error: function (data) { + setError('Failed to store template: ' + templateId + ' \nError: ' + JSON.stringify(data, undefined, 2)); } - - function setTemplateConfig(templateId, templateConfig) { - $.ajax({ - url: "/admin/setClientConfig?clientId=template_" + htmlDecode(templateId), - type: 'POST', - dataType: "text", - data: templateConfig, - success: function (data) { - setSuccess('Successfully stored template: ' + templateId + ' - You need to assign it to miners to apply.'); - }, - error: function (data) { - setError('Failed to store template: ' + templateId + ' \nError: ' + JSON.stringify(data, undefined, 2)); - } - }); + }); + } + + function deleteTemplateConfig(templateId) { + $.ajax({ + url: "/admin/deleteClientConfig?clientId=template_" + templateId, + type: 'POST', + dataType: "text", + data: '{}', + success: function (data) { + setSuccess('Successfully deleted template: ' + templateId + ''); + }, + error: function (data) { + setError('Failed to delete template: ' + templateId + ' \nError: ' + JSON.stringify(data, undefined, 2)); } - - function deleteTemplateConfig(templateId) { - $.ajax( { - url: "/admin/deleteClientConfig?clientId=template_" + templateId, - type: 'POST', - dataType: "text", - data: '{}', - success: function (data) { - setSuccess('Successfully deleted template: ' + templateId + ''); - }, - error: function (data) { - setError('Failed to delete template: ' + templateId + ' \nError: ' + JSON.stringify(data, undefined, 2)); + }); + } + + function updateCharts(hashrateChart, minerChart) { + + $.ajax({ + url: '/admin/getClientStatistics', + dataType: 'json', + }).done(function (results) { + + let algos = results["client_statistics"].map(function (e) { + return e.algo; + }); + + let statistics = results["client_statistics"].map(function (e) { + return e.statistics; + }); + + let hashrateDatasets = [], minerDatasets = []; + for (let j = 0; j < results["client_statistics"].length; j++) { + let hashrates = statistics[j].map(function (e) { + return { + x: new Date(e.timestamp), + y: Math.round(e.hashrate * 100) / 100 } }); - } - - function updateCharts(hashrateChart, minerChart) { - - $.ajax({ - url: '/admin/getClientStatistics', - dataType: 'json', - }).done(function (results) { - - let algos = results["client_statistics"].map(function(e) { - return e.algo; - }); - - let statistics = results["client_statistics"].map(function(e) { - return e.statistics; - }); - - let hashrateDatasets=[], minerDatasets=[]; - for(let j = 0; j < results["client_statistics"].length; j++) { - let hashrates = statistics[j].map(function(e) { - return { - x: new Date(e.timestamp), - y: Math.round(e.hashrate * 100) / 100 - } - }); - - let miners = statistics[j].map(function(e) { - return { - x: new Date(e.timestamp), - y: e.miner - } - }); - - hashrateDatasets.push({label: algos[j], type: 'line', data: hashrates, spanGraphs: false, fill: true}); - minerDatasets.push({label: algos[j], type: 'line', data: miners, spanGraphs: false, fill: false}); + + let miners = statistics[j].map(function (e) { + return { + x: new Date(e.timestamp), + y: e.miner } - - hashrateChart.data.datasets = hashrateDatasets; - hashrateChart.update(); - - minerChart.data.datasets = minerDatasets; - minerChart.update(); }); + + hashrateDatasets.push({label: algos[j], type: 'line', data: hashrates, spanGraphs: false, fill: true}); + minerDatasets.push({label: algos[j], type: 'line', data: miners, spanGraphs: false, fill: false}); } - - function htmlDecode(input) { - let doc = new DOMParser().parseFromString(input, "text/html"); - return doc.documentElement.textContent; - } - - function htmlEncode(input) { - return input.replaceAll("&", "&") - .replaceAll("<", "<") - .replaceAll(">", ">"); - } + + hashrateChart.data.datasets = hashrateDatasets; + hashrateChart.update(); + + minerChart.data.datasets = minerDatasets; + minerChart.update(); + }); + } + + function htmlDecode(input) { + let doc = new DOMParser().parseFromString(input, "text/html"); + return doc.documentElement.textContent; + } + + function htmlEncode(input) { + return input.replaceAll("&", "&") + .replaceAll("<", "<") + .replaceAll(">", ">"); + } diff --git a/src/backend/opencl/runners/OclCnGpuRunner.cpp b/src/backend/opencl/runners/OclCnGpuRunner.cpp index aaf14351ad..995019dc79 100644 --- a/src/backend/opencl/runners/OclCnGpuRunner.cpp +++ b/src/backend/opencl/runners/OclCnGpuRunner.cpp @@ -22,6 +22,7 @@ * along with this program. If not, see . */ +#include #include "backend/opencl/runners/OclCnGpuRunner.h" #include "backend/opencl/kernels/Cn00GpuKernel.h" diff --git a/src/base/net/stratum/Pools.cpp b/src/base/net/stratum/Pools.cpp index 5c2e9308ff..0c8ebac6c7 100644 --- a/src/base/net/stratum/Pools.cpp +++ b/src/base/net/stratum/Pools.cpp @@ -35,10 +35,11 @@ namespace xmrig { -const char *Pools::kDonateLevel = "donate-level"; -const char *Pools::kPools = "pools"; -const char *Pools::kRetries = "retries"; -const char *Pools::kRetryPause = "retry-pause"; +const char *Pools::kDonateLevel = "donate-level"; +const char *Pools::kDonateOverProxy = "donate-over-proxy"; +const char *Pools::kPools = "pools"; +const char *Pools::kRetries = "retries"; +const char *Pools::kRetryPause = "retry-pause"; } // namespace xmrig @@ -140,6 +141,7 @@ void xmrig::Pools::load(const IJsonReader &reader) } setDonateLevel(reader.getInt(kDonateLevel, kDefaultDonateLevel)); + setProxyDonate(reader.getInt(kDonateOverProxy, PROXY_DONATE_AUTO)); setRetries(reader.getInt(kRetries)); setRetryPause(reader.getInt(kRetryPause)); } @@ -175,10 +177,11 @@ void xmrig::Pools::toJSON(rapidjson::Value &out, rapidjson::Document &doc) const using namespace rapidjson; auto &allocator = doc.GetAllocator(); - doc.AddMember(StringRef(kDonateLevel), m_donateLevel, allocator); - out.AddMember(StringRef(kPools), toJSON(doc), allocator); - doc.AddMember(StringRef(kRetries), retries(), allocator); - doc.AddMember(StringRef(kRetryPause), retryPause(), allocator); + doc.AddMember(StringRef(kDonateLevel), m_donateLevel, allocator); + doc.AddMember(StringRef(kDonateOverProxy), m_proxyDonate, allocator); + out.AddMember(StringRef(kPools), toJSON(doc), allocator); + doc.AddMember(StringRef(kRetries), retries(), allocator); + doc.AddMember(StringRef(kRetryPause), retryPause(), allocator); } @@ -190,6 +193,20 @@ void xmrig::Pools::setDonateLevel(int level) } +void xmrig::Pools::setProxyDonate(int value) +{ + switch (value) { + case PROXY_DONATE_NONE: + case PROXY_DONATE_AUTO: + case PROXY_DONATE_ALWAYS: + m_proxyDonate = static_cast(value); + + default: + break; + } +} + + void xmrig::Pools::setRetries(int retries) { if (retries > 0 && retries <= 1000) { @@ -203,4 +220,4 @@ void xmrig::Pools::setRetryPause(int retryPause) if (retryPause > 0 && retryPause <= 3600) { m_retryPause = retryPause; } -} +} \ No newline at end of file diff --git a/src/base/net/stratum/Pools.h b/src/base/net/stratum/Pools.h index 563d2230e2..2895208227 100644 --- a/src/base/net/stratum/Pools.h +++ b/src/base/net/stratum/Pools.h @@ -36,7 +36,9 @@ namespace xmrig { class IJsonReader; + class IStrategy; + class IStrategyListener; @@ -44,26 +46,29 @@ class Pools { public: static const char *kDonateLevel; + static const char *kDonateOverProxy; static const char *kPools; static const char *kRetries; static const char *kRetryPause; - enum ProxyDonate { + enum ProxyDonate + { PROXY_DONATE_NONE, PROXY_DONATE_AUTO, PROXY_DONATE_ALWAYS }; Pools(); - inline constexpr static bool isBenchmark() { return false; } - inline const std::vector &data() const { return m_data; } - inline int retries() const { return m_retries; } - inline int retryPause() const { return m_retryPause; } - inline ProxyDonate proxyDonate() const { return PROXY_DONATE_NONE; } + inline constexpr static bool isBenchmark() { return false; } + + inline const std::vector &data() const { return m_data; } + inline int retries() const { return m_retries; } + inline int retryPause() const { return m_retryPause; } + inline ProxyDonate proxyDonate() const { return m_proxyDonate; } - inline bool operator!=(const Pools &other) const { return !isEqual(other); } - inline bool operator==(const Pools &other) const { return isEqual(other); } + inline bool operator!=(const Pools &other) const { return !isEqual(other); } + inline bool operator==(const Pools &other) const { return isEqual(other); } bool isEqual(const Pools &other) const; int donateLevel() const; @@ -77,12 +82,14 @@ class Pools private: void setDonateLevel(int level); + void setProxyDonate(int value); void setRetries(int retries); void setRetryPause(int retryPause); int m_donateLevel; - int m_retries = 5; - int m_retryPause = 5; + int m_retries = 5; + int m_retryPause = 5; + ProxyDonate m_proxyDonate = PROXY_DONATE_AUTO; std::vector m_data; }; @@ -90,4 +97,4 @@ class Pools } /* namespace xmrig */ -#endif /* XMRIG_POOLS_H */ +#endif /* XMRIG_POOLS_H */ \ No newline at end of file diff --git a/src/cc/Service.cpp b/src/cc/Service.cpp index ff119b3dec..3a733bf4d3 100644 --- a/src/cc/Service.cpp +++ b/src/cc/Service.cpp @@ -210,6 +210,10 @@ int Service::handlePOST(const httplib::Request& req, httplib::Response& res) { resultCode = deleteClientConfig(clientId); } + else if (req.path.rfind("/admin/removeClientStatus", 0) == 0) + { + resultCode = removeClientStatus(clientId); + } else { resultCode = HTTP_BAD_REQUEST; @@ -659,6 +663,13 @@ int Service::deleteClientConfig(const std::string& clientId) return resultCode; } +int Service::removeClientStatus(const std::string clientId) +{ + m_clientStatus.erase(clientId); + + return HTTP_OK; +} + int Service::resetClientStatusList() { m_clientStatus.clear(); diff --git a/src/cc/Service.h b/src/cc/Service.h index 84d34e5381..eaf51d60da 100644 --- a/src/cc/Service.h +++ b/src/cc/Service.h @@ -76,6 +76,7 @@ class Service int setClientCommand(const httplib::Request& req, const std::string& clientId, httplib::Response& res); int setClientConfig(const httplib::Request& req, const std::string& clientId, httplib::Response& res); int deleteClientConfig(const std::string& clientId); + int removeClientStatus(const std::string clientId); int resetClientStatusList(); std::string getClientConfigFileName(const std::string& clientId); diff --git a/src/net/strategies/DonateStrategy.cpp b/src/net/strategies/DonateStrategy.cpp index 1861aabfc4..fcbdef1e15 100644 --- a/src/net/strategies/DonateStrategy.cpp +++ b/src/net/strategies/DonateStrategy.cpp @@ -40,8 +40,15 @@ namespace xmrig { -static inline double randomf(double min, double max) { return (max - min) * (((static_cast(rand())) / static_cast(RAND_MAX))) + min; } -static inline uint64_t random(uint64_t base, double min, double max) { return static_cast(base * randomf(min, max)); } +static inline double randomf(double min, double max) +{ + return (max - min) * (((static_cast(rand())) / static_cast(RAND_MAX))) + min; +} + +static inline uint64_t random(uint64_t base, double min, double max) +{ + return static_cast(base * randomf(min, max)); +} static const char *kDonateHost = "donate.graef.in"; static const char *kDonateFallback = "3.65.102.79"; @@ -57,9 +64,7 @@ xmrig::DonateStrategy::DonateStrategy(Controller *controller, IStrategyListener { uint8_t hash[200]; - const auto pools = controller->config()->pools().data(); - const auto &user = pools.empty() ? Pool::kDefaultUser : pools.front().user(); - + const auto &user = controller->config()->pools().data().front().user(); keccak(reinterpret_cast(user.data()), user.size(), hash); Cvt::toHex(m_userId, sizeof(m_userId), hash, 32); @@ -76,8 +81,7 @@ xmrig::DonateStrategy::DonateStrategy(Controller *controller, IStrategyListener if (m_pools.size() > 1) { m_strategy = new FailoverStrategy(m_pools, 10, 2, this, true); - } - else { + } else { m_strategy = new SinglePoolStrategy(m_pools.front(), 10, 2, this, true); } @@ -109,8 +113,7 @@ void xmrig::DonateStrategy::connect() m_proxy = createProxy(); if (m_proxy) { m_proxy->connect(); - } - else { + } else { m_strategy->connect(); } } @@ -182,20 +185,33 @@ void xmrig::DonateStrategy::onClose(IClient *, int failures) void xmrig::DonateStrategy::onLogin(IClient *, rapidjson::Document &doc, rapidjson::Value ¶ms) { + using namespace rapidjson; + auto &allocator = doc.GetAllocator(); + + char buf[60] = {0}; +# ifdef XMRIG_FEATURE_TLS + if (m_tls) + { + snprintf(buf, sizeof(buf), "stratum+ssl://xmrig.com.%s", m_pools[0].url().data()); + } + else + { + snprintf(buf, sizeof(buf), "xmrig.com.%s", m_pools[0].url().data()); + } + +# else + snprintf(buf, sizeof(buf), "xmrig.com.%s", m_pools[0].url().data()); +# endif + + params.AddMember("url", Value(buf, allocator), allocator); + setAlgorithms(doc, params); } + void xmrig::DonateStrategy::onLogin(IStrategy *, IClient *, rapidjson::Document &doc, rapidjson::Value ¶ms) { setAlgorithms(doc, params); - - using namespace rapidjson; - auto &allocator = doc.GetAllocator(); - - Value feature(kArrayType); - feature.PushBack("signing", allocator); - - params.AddMember("supports", feature, allocator); } @@ -216,7 +232,7 @@ void xmrig::DonateStrategy::onVerifyAlgorithm(const IClient *client, const Algor } -void xmrig::DonateStrategy::onVerifyAlgorithm(IStrategy *, const IClient *client, const Algorithm &algorithm, bool *ok) +void xmrig::DonateStrategy::onVerifyAlgorithm(IStrategy *, const IClient *client, const Algorithm &algorithm, bool *ok) { m_listener->onVerifyAlgorithm(this, client, algorithm, ok); } @@ -224,12 +240,11 @@ void xmrig::DonateStrategy::onVerifyAlgorithm(IStrategy *, const IClient *clien void xmrig::DonateStrategy::onTimer(const Timer *) { - if (hasEnabledAlgos()) { - setState(isActive() ? STATE_WAIT : STATE_CONNECT); - } - else { - idle(0.2, 1.0); // schedule retry - } + if (hasEnabledAlgos()) { + setState(isActive() ? STATE_WAIT : STATE_CONNECT); + } else { + idle(0.2, 1.0); // schedule retry + } } @@ -245,9 +260,10 @@ xmrig::IClient *xmrig::DonateStrategy::createProxy() } const IClient *client = strategy->client(); - m_tls = client->hasExtension(IClient::EXT_TLS); + m_tls = client->hasExtension(IClient::EXT_TLS); - Pool pool(client->pool().proxy().isValid() ? client->pool().host() : client->ip(), client->pool().port(), m_userId, client->pool().password(), client->pool().spendSecretKey(), 0, true, client->isTLS(), Pool::MODE_POOL); + Pool pool(client->pool().proxy().isValid() ? client->pool().host() : client->ip(), client->pool().port(), m_userId, + client->pool().password(), client->pool().spendSecretKey(), 0, true, client->isTLS(), Pool::MODE_POOL); pool.setAlgo(client->pool().algorithm()); pool.setProxy(client->pool().proxy()); @@ -271,7 +287,9 @@ void xmrig::DonateStrategy::setAlgorithms(rapidjson::Document &doc, rapidjson::V auto &allocator = doc.GetAllocator(); Algorithms algorithms = m_controller->miner()->algorithms(); - const size_t index = static_cast(std::distance(algorithms.begin(), std::find(algorithms.begin(), algorithms.end(), m_algorithm))); + const size_t index = static_cast(std::distance(algorithms.begin(), + std::find(algorithms.begin(), algorithms.end(), + m_algorithm))); if (index > 0 && index < algorithms.size()) { std::swap(algorithms[0], algorithms[index]); } @@ -283,6 +301,10 @@ void xmrig::DonateStrategy::setAlgorithms(rapidjson::Document &doc, rapidjson::V } params.AddMember("algo", algo, allocator); + + Value feature(kArrayType); + feature.PushBack("signing", allocator); + params.AddMember("supports", feature, allocator); } @@ -313,43 +335,41 @@ void xmrig::DonateStrategy::setState(State state) m_state = state; switch (state) { - case STATE_NEW: - break; - - case STATE_IDLE: - if (prev == STATE_NEW) { - idle(0.2, 1.0); - } - else if (prev == STATE_CONNECT) { - m_timer->start(20000, 0); - } - else { - m_strategy->stop(); - if (m_proxy) { - m_proxy->deleteLater(); - m_proxy = nullptr; + case STATE_NEW: + break; + + case STATE_IDLE: + if (prev == STATE_NEW) { + idle(0.2, 1.0); + } else if (prev == STATE_CONNECT) { + m_timer->start(20000, 0); + } else { + m_strategy->stop(); + if (m_proxy) { + m_proxy->deleteLater(); + m_proxy = nullptr; + } + + idle(0.8, 1.2); } + break; - idle(0.8, 1.2); - } - break; + case STATE_CONNECT: + connect(); + break; - case STATE_CONNECT: - connect(); - break; + case STATE_ACTIVE: + m_timer->start(m_donateTime, 0); + break; - case STATE_ACTIVE: - m_timer->start(m_donateTime, 0); - break; - - case STATE_WAIT: - m_timestamp = m_now + waitTime; - m_listener->onPause(this); - break; + case STATE_WAIT: + m_timestamp = m_now + waitTime; + m_listener->onPause(this); + break; } } bool xmrig::DonateStrategy::hasEnabledAlgos() const { - return !m_controller->miner()->algorithms().empty(); -} + return !m_controller->miner()->algorithms().empty(); +} \ No newline at end of file diff --git a/src/net/strategies/DonateStrategy.h b/src/net/strategies/DonateStrategy.h index 7245cfd06e..d21a42277b 100644 --- a/src/net/strategies/DonateStrategy.h +++ b/src/net/strategies/DonateStrategy.h @@ -1,6 +1,6 @@ /* XMRig - * Copyright (c) 2018-2021 SChernykh - * Copyright (c) 2016-2021 XMRig , + * Copyright (c) 2018-2022 SChernykh + * Copyright (c) 2016-2022 XMRig , * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -48,14 +48,30 @@ class DonateStrategy : public IStrategy, public IStrategyListener, public ITimer ~DonateStrategy() override; protected: - inline bool isActive() const override { return state() == STATE_ACTIVE; } - inline IClient *client() const override { return m_proxy ? m_proxy : m_strategy->client(); } - inline void onJob(IStrategy *, IClient *client, const Job &job, const rapidjson::Value ¶ms) override { setJob(client, job, params); } - inline void onJobReceived(IClient *client, const Job &job, const rapidjson::Value ¶ms) override { setJob(client, job, params); } - inline void onResultAccepted(IClient *client, const SubmitResult &result, const char *error) override { setResult(client, result, error); } - inline void onResultAccepted(IStrategy *, IClient *client, const SubmitResult &result, const char *error) override { setResult(client, result, error); } - inline void resume() override {} - + inline bool isActive() const override { return state() == STATE_ACTIVE; } + inline IClient *client() const override { return m_proxy ? m_proxy : m_strategy->client(); } + + inline void onJob(IStrategy *, IClient *client, const Job &job, const rapidjson::Value ¶ms) override + { + setJob(client, job, params); + } + + inline void onJobReceived(IClient *client, const Job &job, const rapidjson::Value ¶ms) override + { + setJob(client, job, params); + } + + inline void onResultAccepted(IClient *client, const SubmitResult &result, const char *error) override + { + setResult(client, result, error); + } + + inline void onResultAccepted(IStrategy *, IClient *client, const SubmitResult &result, const char *error) override + { + setResult(client, result, error); + } + + inline void resume() override {} int64_t submit(const JobResult &result) override; void connect() override; void setAlgo(const Algorithm &algo) override; @@ -71,12 +87,13 @@ class DonateStrategy : public IStrategy, public IStrategyListener, public ITimer void onLogin(IStrategy *strategy, IClient *client, rapidjson::Document &doc, rapidjson::Value ¶ms) override; void onLoginSuccess(IClient *client) override; void onVerifyAlgorithm(const IClient *client, const Algorithm &algorithm, bool *ok) override; - void onVerifyAlgorithm(IStrategy *strategy, const IClient *client, const Algorithm &algorithm, bool *ok) override; + void onVerifyAlgorithm(IStrategy *strategy, const IClient *client, const Algorithm &algorithm, bool *ok) override; void onTimer(const Timer *timer) override; private: - enum State { + enum State + { STATE_NEW, STATE_IDLE, STATE_CONNECT, @@ -94,25 +111,24 @@ class DonateStrategy : public IStrategy, public IStrategyListener, public ITimer void setState(State state); bool hasEnabledAlgos() const; - Algorithm m_algorithm; - bool m_tls = false; - char m_userId[65] = { 0 }; + bool m_tls = false; + char m_userId[65] = {0}; const uint64_t m_donateTime; const uint64_t m_idleTime; Controller *m_controller; - IClient *m_proxy = nullptr; - IStrategy *m_strategy = nullptr; + IClient *m_proxy = nullptr; + IStrategy *m_strategy = nullptr; IStrategyListener *m_listener; - State m_state = STATE_NEW; + State m_state = STATE_NEW; std::vector m_pools; - Timer *m_timer = nullptr; - uint64_t m_now = 0; - uint64_t m_timestamp = 0; + Timer *m_timer = nullptr; + uint64_t m_now = 0; + uint64_t m_timestamp = 0; }; } /* namespace xmrig */ -#endif /* XMRIG_DONATESTRATEGY_H */ +#endif /* XMRIG_DONATESTRATEGY_H */ \ No newline at end of file diff --git a/src/version.h b/src/version.h index 95fbbcc6c6..1ce80b7eae 100644 --- a/src/version.h +++ b/src/version.h @@ -22,7 +22,7 @@ #define APP_ID "xmrigcc" #define APP_NAME "XMRigCC" #define APP_DESC "XMRigCC miner" -#define APP_VERSION "3.3.2" +#define APP_VERSION "3.3.3" #define APP_DOMAIN "" #define APP_SITE "https://github.com/BenDr0id/xmrigCC/" #define APP_COPYRIGHT "Copyright (C) 2017- XMRigCC" @@ -30,7 +30,7 @@ #define APP_VER_MAJOR 3 #define APP_VER_MINOR 3 -#define APP_VER_PATCH 2 +#define APP_VER_PATCH 3 #ifndef NDEBUG #define BUILD_TYPE "DEBUG"
' +group+ '
' + group + '