- Refactored Dashboard to send one command to multiple miners and "beautified" dashboard

- Miner now publishs own config to XMRigCCServer on startup
- Added command to trigger miner to upload config to XMRigCCServer
- Added threads to miner info tooltip on client id
This commit is contained in:
BenDroid 2017-12-16 22:38:51 +01:00
parent b42e779bf6
commit e349dccb91
11 changed files with 475 additions and 239 deletions

View file

@ -1,3 +1,8 @@
# v1.2.1
- Miner now publishs own config to XMRigCCServer on startup
- Added command to trigger miner to upload config to XMRigCCServer
- Refactored Dashboard to send one command to multiple miners and "beautified" dashboard
- Added threads to miner info tooltip on client id
# v1.2.0 # v1.2.0
- Added configurability for thread based doublehash mode which helps you to use more of your l3 cache - Added configurability for thread based doublehash mode which helps you to use more of your l3 cache
- Memory optimizations / speed improvements - Memory optimizations / speed improvements

View file

@ -4,200 +4,299 @@
<meta charset=\"utf-8\"> <meta charset=\"utf-8\">
<title>XMRigCC Dashboard</title> <title>XMRigCC Dashboard</title>
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/1.10.16/css/jquery.dataTables.min.css">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/1.10.16/css/dataTables.bootstrap.min.css">
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/buttons/1.5.0/css/buttons.bootstrap.min.css">
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/select/1.2.4/css/select.dataTables.min.css">
<style> <style>
.left{text-align:left;} .left{text-align:left;}
.right{text-align:right;} .center{text-align:center; padding-bottom: 50pt}
.center{text-align:center;} .toolbar{text-align: right;}
.toolbar { float: right; padding-left: 10pt;} .center-tab{text-align: center; vertical-align: middle;}
.online { color: green} .online { color: green}
.offline { color: red} .offline { color: red}
</style> </style>
<script type="text/javascript" language="javascript" src="https://code.jquery.com/jquery-1.12.4.js"></script> <script type="text/javascript" language="javascript" src="https://code.jquery.com/jquery-1.12.4.js"></script>
<script type="text/javascript" language="javascript" src="https://cdn.datatables.net/1.10.16/js/jquery.dataTables.min.js"></script> <script type="text/javascript" language="javascript" src="https://cdn.datatables.net/1.10.16/js/jquery.dataTables.min.js"></script>
<script type="text/javascript" language="javascript" src="https://cdn.datatables.net/1.10.16/js/dataTables.bootstrap.min.js"></script>
<script type="text/javascript" language="javascript" src="https://cdn.datatables.net/buttons/1.5.0/js/dataTables.buttons.min.js"></script>
<script type="text/javascript" language="javascript" src="https://cdn.datatables.net/buttons/1.5.0/js/buttons.bootstrap.min.js"></script>
<script type="text/javascript" language="javascript" src="https://cdn.datatables.net/select/1.2.4/js/dataTables.select.min.js"></script>
<script type="text/javascript" language="javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery-timeago/1.6.1/jquery.timeago.min.js"></script> <script type="text/javascript" language="javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery-timeago/1.6.1/jquery.timeago.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script> <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
<script src="https://use.fontawesome.com/6b3cdfc597.js"></script> <script src="https://use.fontawesome.com/6b3cdfc597.js"></script>
<script type="text/javascript"> <script type="text/javascript">
$.fn.dataTable.ext.search.push( $.fn.dataTable.ext.search.push(
function( settings, data, dataIndex ) { function( settings, data, dataIndex ) {
var hide = document.getElementById("hideOffline").checked; var hide = document.getElementById("hideOffline").checked;
return (isOnline(settings.aoData[dataIndex]._aData.client_status.last_status_update * 1000) || !hide);
}
);
return (isOnline(settings.aoData[dataIndex]._aData.client_status.last_status_update * 1000) || !hide); $(document).ready(function() {
}
);
$(document).ready(function() { var latestRelease = "";
var latestRelease = ""; var table = $('#clientStatusList').DataTable({
dom: "<'toolbar'><'row'<'col-sm-9'B><'col-sm-3'f>><'row'<'col-sm-12't>><i>",
bPaginate : false,
ajax : {
url : "/admin/getClientStatusList",
dataSrc : 'client_status_list'
},
deferRender: true,
columns: [
{
data: null,
defaultContent: '',
className: 'select-checkbox',
orderable: false
},
{ data: "client_status.client_id", render: clientInfo},
{ data: "client_status.current_pool"},
{ data: "client_status.current_status"},
{ data: "client_status.current_algo_name"},
{ 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.last_status_update", render: laststatus},
{
data: null,
defaultContent:
"<td class='center-tab'><button type='button' id='EDIT' class='btn btn-xs btn-primary' data-toggle='tooltip' title='Edit Client Config'><i class='fa fa-cog'></i></button></td>",
orderable: false,
className: "center-tab"
}
],
rowId: 'client_status.client_id',
select: {
style : "multi"
},
order: [ 1, 'asc' ],
lengthChange: false,
buttons: [
{
text: '<i class="fa fa-download"> Pull client config</i>',
className: 'btn-default',
enabled: false,
action: function () {
table.rows({ selected: true }).eq(0).each(function (index) {
var row = table.row(index);
var data = row.data();
var table = $('#clientStatusList').DataTable({ sendAction("PUBLISH_CONFIG", data.client_status.client_id);
dom: '<"toolbar">frtip', });
processing : true, }
bPaginate : false, },
ajax : { {
url : "/admin/getClientStatusList", text: '<i class="fa fa-upload"> Push client config</i>',
dataSrc : 'client_status_list' className: 'btn-primary',
}, enabled: false,
columns: [ action: function () {
{ data: "client_status.client_id", render: clientInfo}, table.rows({ selected: true }).eq(0).each(function (index) {
{ data: "client_status.current_pool"}, var row = table.row(index);
{ data: "client_status.current_status"}, var data = row.data();
{ data: "client_status.current_algo_name"},
{ 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.last_status_update", render: laststatus},
{
targets: -1,
data: null,
defaultContent:
"<td><button type='button' id='START' class='btn btn-xs btn-success' data-toggle='tooltip' title='Start'><i class='fa fa-play'></i></button></td>" +
"<td><button type='button' id='STOP' class='btn btn-xs btn-warning' data-toggle='tooltip' title='Stop'><i class='fa fa-pause'></i></button></td>" +
"<td><button type='button' id='RESTART' class='btn btn-xs' data-toggle='tooltip' title='Restart'><i class='fa fa-repeat'></i></button></td>" +
"<td><button type='button' id='UPDATE_CONFIG' class='btn btn-xs btn-info' data-toggle='tooltip' title='Update Config'><i class='fa fa-refresh'></i></button></td>" +
"<td><button type='button' id='EDIT' class='btn btn-xs btn-primary' data-toggle='tooltip' title='Edit Client Config'><i class='fa fa-cog'></i></button></td>" +
"<td><button type='button' id='SHUTDOWN' class='btn btn-xs btn-danger' data-toggle='tooltip' title='Stutdown Client'><i class='fa fa-power-off'></i></button></td>",
className: "center"
}
],
order: [ 0, 'asc' ],
"footerCallback": function ( row, data, start, end, display ) { sendAction("UPDATE_CONFIG", data.client_status.client_id);
var api = this.api(); });
}
},
{
text: '<i class="fa fa-play"> Start miner</i>',
className: 'btn-success',
enabled: false,
action: function () {
table.rows({ selected: true }).eq(0).each(function (index) {
var row = table.row(index);
var data = row.data();
var sumHashrateShort = 0; sendAction("START", data.client_status.client_id);
var sumHashrateMedium = 0; });
var sumHashrateLong = 0; }
var sumHashrateHighest = 0; },
var sumHashesTotal = 0; {
var avgTimeTotal = 0; text: '<i class="fa fa-pause"> Pause miner</i>',
var sumSharesGood = 0; className: 'btn-warning',
var sumSharedTotal = 0; enabled: false,
action: function () {
table.rows({ selected: true }).eq(0).each(function (index) {
var row = table.row(index);
var data = row.data();
sumHashrateShort = api sendAction("STOP", data.client_status.client_id);
.column(4, {page: 'current'}) });
.data() }
.reduce( function (a, b) { },
return a+b; {
}, 0 ); text: '<i class="fa fa-repeat"> Restart miner</i>',
className: 'btn-info',
enabled: false,
action: function () {
table.rows({ selected: true }).eq(0).each(function (index) {
var row = table.row(index);
var data = row.data();
sumHashrateMedium = api sendAction("RESTART", data.client_status.client_id);
.column(5, {page: 'current'}) });
.data() }
.reduce( function (a, b) { },
return a+b; {
}, 0 ); text: '<i class="fa fa-power-off"> Shutdown miner</i>',
className: 'btn-danger',
enabled: false,
action: function () {
table.rows({ selected: true }).eq(0).each(function (index) {
var row = table.row(index);
var data = row.data();
sumHashrateLong = api sendAction("SHUTDOWN", data.client_status.client_id);
.column(6, {page: 'current'}) });
.data() }
.reduce( function (a, b) { }
return a+b; ],
}, 0 );
sumHashrateHighest = api "footerCallback": function ( row, data, start, end, display ) {
.column(7, {page: 'current'}) var api = this.api();
.data()
.reduce( function (a, b) {
return a+b;
}, 0 );
sumHashesTotal = api var sumHashrateShort = 0;
.column(8, {page: 'current'}) var sumHashrateMedium = 0;
.data() var sumHashrateLong = 0;
.reduce( function (a, b) { var sumHashrateHighest = 0;
return a+b; var sumHashesTotal = 0;
}, 0 ); var avgTimeTotal = 0;
var sumSharesGood = 0;
var sumSharedTotal = 0;
avgTimeTotal = api sumHashrateShort = api
.column(9, {page: 'current'}) .column(5, {page: 'current'})
.data() .data()
.reduce( function (a, b) { .reduce( function (a, b) {
return (a+b) / 2; return a+b;
}, 0 ); }, 0 );
sumSharesGood = api sumHashrateMedium = api
.column(10, {page: 'current'}) .column(6, {page: 'current'})
.data() .data()
.reduce( function (a, b) { .reduce( function (a, b) {
return a+b; return a+b;
}, 0 ); }, 0 );
sumSharedTotal = api sumHashrateLong = api
.column(11, {page: 'current'}) .column(7, {page: 'current'})
.data() .data()
.reduce( function (a, b) { .reduce( function (a, b) {
return a+b; return a+b;
}, 0 ); }, 0 );
sumHashrateShort = round(sumHashrateShort); sumHashrateHighest = api
sumHashrateMedium = round(sumHashrateMedium); .column(8, {page: 'current'})
sumHashrateLong = round(sumHashrateLong); .data()
sumHashrateHighest = round(sumHashrateHighest); .reduce( function (a, b) {
avgTimeTotal = round(avgTimeTotal); return a+b;
}, 0 );
sumHashesTotal = api
.column(9, {page: 'current'})
.data()
.reduce( function (a, b) {
return a+b;
}, 0 );
avgTimeTotal = api
.column(10, {page: 'current'})
.data()
.reduce( function (a, b) {
return (a+b) / 2;
}, 0 );
sumSharesGood = api
.column(11, {page: 'current'})
.data()
.reduce( function (a, b) {
return a+b;
}, 0 );
sumSharedTotal = api
.column(12, {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 // update footer
$(api.column(4).footer()).html(sumHashrateShort); $(api.column(5).footer()).html(sumHashrateShort);
$(api.column(5).footer()).html(sumHashrateMedium); $(api.column(6).footer()).html(sumHashrateMedium);
$(api.column(6).footer()).html(sumHashrateLong); $(api.column(7).footer()).html(sumHashrateLong);
$(api.column(7).footer()).html(sumHashrateHighest); $(api.column(8).footer()).html(sumHashrateHighest);
$(api.column(8).footer()).html(sumHashesTotal); $(api.column(9).footer()).html(sumHashesTotal);
$(api.column(9).footer()).html(avgTimeTotal); $(api.column(10).footer()).html(avgTimeTotal);
$(api.column(10).footer()).html(sumSharesGood); $(api.column(11).footer()).html(sumSharesGood);
$(api.column(11).footer()).html(sumSharedTotal); $(api.column(12).footer()).html(sumSharedTotal);
// check version // check version
if (latestRelease === "" && $(api).context[0].json !== undefined) { if (latestRelease === "" && $(api).context[0].json !== undefined) {
$.ajax({ $.ajax({
url: "https://api.github.com/repos/Bendr0id/xmrigCC/releases/latest", url: "https://api.github.com/repos/Bendr0id/xmrigCC/releases/latest",
type: 'GET', type: 'GET',
dataType: "json", dataType: "json",
success: function (release) { success: function (release) {
latestRelease = release.tag_name; latestRelease = release.tag_name;
if (latestRelease !== $(api).context[0].json.current_version) { if (latestRelease !== $(api).context[0].json.current_version) {
$("#notificationBar").html('<div class="alert alert-info alert-dismissable fade in">' + $("#notificationBar").html('<div class="alert alert-info alert-dismissable fade in">' +
'<a href="#" class="close" data-dismiss="alert" aria-label="close">&times;</a>' + '<a href="#" class="close" data-dismiss="alert" aria-label="close">&times;</a>' +
'<a href="https://github.com/Bendr0id/xmrigCC/releases/latest"><strong>Update!</strong> XMRigCC v' + latestRelease + ' is available for download\n</a>' + '<a href="https://github.com/Bendr0id/xmrigCC/releases/latest"><strong>Update!</strong> XMRigCC v' + latestRelease + ' is available for download\n</a>' +
'</div>'); '</div>');
}
} }
} });
});
}
}
});
$("div.toolbar").html('<input type="checkbox" id="hideOffline" value="hide" checked style="margin-right: 5pt"><label>Hide offline miners</label>');
$('#clientStatusList tbody').on( 'click', 'button', function () {
var data = table.row( $(this).parents('tr') ).data();
var clientId = data['client_status']['client_id'];
var action = this.id;
if (action != "EDIT"){
$.ajax({
type: "POST",
url: "/admin/setClientCommand?clientId=" + clientId,
dataType:"text",
data: '{"control_command":{"command": "' + action + '"}}',
success: function(data){
alert("Successfully send: " + action + " to: " + clientId);
},
error: function (data) {
alert("Failed to send: " + action +" to: " + clientId + "\nError: " + JSON.stringify(data,undefined, 2));
} }
}); }
} });
else {
table.on('select', function () {
var selectedRows = table.rows( { selected: true } ).count();
table.button(0).enable(selectedRows > 0);
table.button(1).enable(selectedRows > 0);
table.button(2).enable(selectedRows > 0);
table.button(3).enable(selectedRows > 0);
table.button(4).enable(selectedRows > 0);
table.button(5).enable(selectedRows > 0);
});
table.on('deselect', function () {
var selectedRows = table.rows( { selected: true } ).count();
table.button(0).enable(selectedRows > 0);
table.button(1).enable(selectedRows > 0);
table.button(2).enable(selectedRows > 0);
table.button(3).enable(selectedRows > 0);
table.button(4).enable(selectedRows > 0);
table.button(5).enable(selectedRows > 0);
});
table.buttons().container().appendTo( '#clientStatusList_wrapper .col-sm-6:eq(0)' );
$("div.toolbar").html('<input type="checkbox" id="hideOffline" value="hide" checked style="margin-right: 5pt"><label>Hide offline miners</label>');
$('#clientStatusList tbody').on( 'click', 'button', function () {
var data = table.row( $(this).parents('tr') ).data();
var clientId = data['client_status']['client_id'];
$.ajax({ $.ajax({
type: "GET", type: "GET",
url: "/admin/getClientConfig?clientId=" + clientId, url: "/admin/getClientConfig?clientId=" + clientId,
@ -208,59 +307,112 @@ $(document).ready(function() {
"<textarea class='form-control' rows='20' id='config'>" + "<textarea class='form-control' rows='20' id='config'>" +
JSON.stringify(jsonClientConfig,undefined, 2) + JSON.stringify(jsonClientConfig,undefined, 2) +
"</textarea>" + "</textarea>" +
"</div>"; "</div>";
$('#editConfig').find('.modal-body').html(htmlContent); $('#editConfig').find('.modal-body').html(htmlContent);
$('#editConfig').modal('show'); $('#editConfig').modal('show');
}, },
error: function (data) { error: function (data) {
alert("Unable to fetch " + clientId + "_config.json or default_config.json, please check your Server configuration and the the config files are located on the Server!"); $("#statusBar").html('<div class="alert alert-danger" role="alert">' +
'<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>' +
'<strong>Unable to fetch ' + clientId + '_config.json or default_config.json, please check your Server configuration and the the config files are located on the Server!</strong></div>');
window.setTimeout(function() {
$(".alert-danger").fadeTo(500, 0).slideUp(500, function(){
$(".alert-danger").alert('close');
});
}, 10000);
} }
}); });
} });
$('button.btn.btn-success').click(function(event)
{
var clientId = $('#editConfig').find('.form-group')["0"].dataset.value;
event.preventDefault();
$.ajax({
url: "/admin/setClientConfig?clientId=" + clientId,
type: 'POST',
dataType: "text",
data: $('#config').val(),
success: function(data){
$("#statusBar").html('<div class="alert alert-success" role="alert">' +
'<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>' +
'<strong>Successfully saved config for: ' + clientId + '</strong></div>');
window.setTimeout(function() {
$(".alert-success").fadeTo(500, 0).slideUp(500, function(){
$(".alert-success").alert('close');
});
}, 2500);
},
error: function (data) {
$("#statusBar").html('<div class="alert alert-danger" role="alert">' +
'<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>' +
'<strong>Failed to store config for: ' + clientId + '\nError: ' + JSON.stringify(data,undefined, 2) + '</strong></div>');
window.setTimeout(function() {
$(".alert-danger").fadeTo(500, 0).slideUp(500, function(){
$(".alert-danger").alert('close');
});
}, 10000);
}
});
});
$('#hideOffline').click( function() {
table.draw();
} );
setInterval(function () {
table.ajax.reload();
}, 10000);
}); });
$('button.btn.btn-success').click(function(event) function sendAction(action, clientId) {
{
var clientId = $('#editConfig').find('.form-group')["0"].dataset.value;
event.preventDefault();
$.ajax({ $.ajax({
url: "/admin/setClientConfig?clientId=" + clientId, type: "POST",
type: 'POST', url: "/admin/setClientCommand?clientId=" + clientId,
dataType: "text", dataType:"text",
data: $('#config').val(), data: '{"control_command":{"command": "' + action + '"}}',
success: function(data){ success: function(data){
alert("Successfully saved config for: " + clientId); $("#statusBar").html('<div class="alert alert-success" role="alert">' +
'<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>' +
'<strong>Successfully send ' + action + '</strong></div>');
window.setTimeout(function() {
$(".alert-success").fadeTo(500, 0).slideUp(500, function(){
$(".alert-success").alert('close');
});
}, 2500);
}, },
error: function (data) { error: function (data) {
alert("Failed to store config for: " + clientId + "\nError: " + JSON.stringify(data,undefined, 2)); $("#statusBar").html('<div class="alert alert-danger" role="alert">' +
'<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>' +
'<strong>Failed to send ' + action + '</strong></div>');
window.setTimeout(function() {
$(".alert-danger").fadeTo(500, 0).slideUp(500, function(){
$(".alert-danger").alert('close');
});
}, 10000);
} }
}); });
});
$('#hideOffline').click( function() {
table.draw();
} );
setInterval(function () {
table.ajax.reload();
}, 10000);
});
function laststatus( data, type, row ) {
if (type !== 'sort') {
var date = new Date(data*1000);
return '<span data-toggle="tooltip" title="' + date + '">' + jQuery.timeago(date) + '</span>';
} }
return data; function laststatus( data, type, row ) {
} if (type !== 'sort') {
var date = new Date(data*1000);
return '<span data-toggle="tooltip" title="' + date + '">' + jQuery.timeago(date) + '</span>';
}
function clientInfo( data, type, row ) { return data;
if (type !== 'sort') { }
var tooltip = "CPU: " + row.client_status.cpu_brand + " [" + row.client_status.cpu_cores + " cores]";
function clientInfo( data, type, row ) {
if (type !== 'sort') {
var tooltip = "CPU: " + row.client_status.cpu_brand + " [" + row.client_status.cpu_cores + " cores / " + row.client_status.cpu_threads + " threads]";
tooltip += '\n'; tooltip += '\n';
tooltip += "CPU Flags: " + (row.client_status.cpu_has_aes ? "AES-NI " : ""); 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_x64 ? "x64" : "");
@ -279,67 +431,71 @@ function clientInfo( data, type, row ) {
tooltip += '\n'; tooltip += '\n';
tooltip += "Status: "; tooltip += "Status: ";
var lastStatus = row.client_status.last_status_update * 1000; var lastStatus = row.client_status.last_status_update * 1000;
if (isOnline(lastStatus)) { if (isOnline(lastStatus)) {
tooltip += "Online"; tooltip += "Online";
return '<span data-toggle="tooltip" title="'+ tooltip + '"><div class="online">' + data + '</div></span>'; return '<span data-toggle="tooltip" title="'+ tooltip + '"><div class="online">' + data + '</div></span>';
} }
else { else {
tooltip += "Offline"; tooltip += "Offline";
return '<span data-toggle="tooltip" title="'+ tooltip + '"><div class="offline">' + data + '</div></span>'; return '<span data-toggle="tooltip" title="'+ tooltip + '"><div class="offline">' + data + '</div></span>';
}
} }
return data;
} }
return data; function round( data, type, row ) {
} return Math.round(data * 100) / 100;
function round( data, type, row ) {
return Math.round(data * 100) / 100;
}
function isOnline(lastStatus) {
var threshold = new Date().getTime() - 60 * 1000;
if (lastStatus > threshold) {
return true;
} else {
return false;
} }
}
function isOnline(lastStatus) {
var threshold = new Date().getTime() - 60 * 1000;
if (lastStatus > threshold) {
return true;
} else {
return false;
}
}
</script> </script>
</head> </head>
<body> <body>
<br/> <br/>
<div style="width: 90%; margin:0 auto;"> <div style="width: 95%; margin:0 auto;">
<div id="notificationBar"></div> <div id="notificationBar"></div>
<div id="statusBar"></div>
<div class="center"> <div class="center">
<h1>XMRigCC Dashboard</h1> <h1>XMRigCC Dashboard</h1>
</div> </div>
<table id="clientStatusList" class="display" cellspacing="0" width="100%">
<table id="clientStatusList" class="table table-striped table-bordered" cellspacing="0" width="100%">
<thead> <thead>
<tr> <tr>
<th>Client Id</th> <th width="2%"></th>
<th>Pool</th> <th>Client Id</th>
<th>Status</th> <th>Pool</th>
<th>Algo</th> <th>Status</th>
<th>Algo</th>
<th>Hashrate</th> <th>Hashrate</th>
<th>Hashrate 1m</th> <th>Hashrate 1m</th>
<th>Hashrate 15m</th> <th>Hashrate 15m</th>
<th>Hashrate Highest</th> <th>Hashrate Highest</th>
<th>Hashes Total</th> <th>Hashes Total</th>
<th>Avg. Time</th> <th>Avg. Time</th>
<th>Shares Good</th> <th>Shares Good</th>
<th>Shares Total</th> <th>Shares Total</th>
<th>Last Update</th> <th>Last Update</th>
<th colspan="6" class="center">Action</th> <th>Edit</th>
</tr> </tr>
</thead> </thead>
<tfoot> <tfoot>
<tr> <tr>
<th></th>
<th class="left">Total:</th> <th class="left">Total:</th>
<th></th> <th></th>
<th></th> <th></th>
@ -358,6 +514,8 @@ function isOnline(lastStatus) {
</tfoot> </tfoot>
</table> </table>
<br/>
<div class="modal fade" id="editConfig" role="dialog"> <div class="modal fade" id="editConfig" role="dialog">
<div class="modal-dialog modal-lg"> <div class="modal-dialog modal-lg">
<div class="modal-content"> <div class="modal-content">
@ -378,3 +536,4 @@ function isOnline(lastStatus) {
</div> </div>
</body> </body>
</html> </html>

View file

@ -262,5 +262,7 @@ void App::onCommandReceived(uv_async_t* async)
case ControlCommand::SHUTDOWN: case ControlCommand::SHUTDOWN:
App::shutdown(); App::shutdown();
break; break;
case ControlCommand::PUBLISH_CONFIG:;
break;
} }
} }

View file

@ -22,6 +22,8 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include <cstring>
#include <sstream>
#include <fstream> #include <fstream>
#include <3rdparty/rapidjson/stringbuffer.h> #include <3rdparty/rapidjson/stringbuffer.h>
#include <3rdparty/rapidjson/prettywriter.h> #include <3rdparty/rapidjson/prettywriter.h>
@ -83,6 +85,7 @@ CCClient::CCClient(Options* options, uv_async_t* async)
m_clientStatus.setCpuBrand(Cpu::brand()); m_clientStatus.setCpuBrand(Cpu::brand());
m_clientStatus.setCpuAES(Cpu::hasAES()); m_clientStatus.setCpuAES(Cpu::hasAES());
m_clientStatus.setCpuCores(Cpu::cores()); m_clientStatus.setCpuCores(Cpu::cores());
m_clientStatus.setCpuThreads(Cpu::threads());
m_clientStatus.setCpuX64(Cpu::isX64()); m_clientStatus.setCpuX64(Cpu::isX64());
m_clientStatus.setCpuL2(Cpu::l2()); m_clientStatus.setCpuL2(Cpu::l2());
@ -159,7 +162,10 @@ void CCClient::publishClientStatusReport()
} else if (controlCommand.getCommand() == ControlCommand::UPDATE_CONFIG) { } else if (controlCommand.getCommand() == ControlCommand::UPDATE_CONFIG) {
LOG_WARN("[CC-Client] Command: UPDATE_CONFIG received -> update config"); LOG_WARN("[CC-Client] Command: UPDATE_CONFIG received -> update config");
updateConfig(); updateConfig();
} else if (controlCommand.getCommand() == ControlCommand::RESTART) { } else if (controlCommand.getCommand() == ControlCommand::PUBLISH_CONFIG) {
LOG_WARN("[CC-Client] Command: PUBLISH_CONFIG received -> publish config");
publishConfig();
}else if (controlCommand.getCommand() == ControlCommand::RESTART) {
LOG_WARN("[CC-Client] Command: RESTART received -> restart"); LOG_WARN("[CC-Client] Command: RESTART received -> restart");
} else if (controlCommand.getCommand() == ControlCommand::SHUTDOWN) { } else if (controlCommand.getCommand() == ControlCommand::SHUTDOWN) {
LOG_WARN("[CC-Client] Command: SHUTDOWN received -> shutdown"); LOG_WARN("[CC-Client] Command: SHUTDOWN received -> shutdown");
@ -208,6 +214,44 @@ void CCClient::updateConfig()
} }
} }
void CCClient::publishConfig()
{
std::string requestUrl = "/client/setClientConfig?clientId=" + m_self->m_clientStatus.getClientId();
std::stringstream data;
std::ifstream clientConfig(m_self->m_options->configFile());
if (clientConfig) {
data << clientConfig.rdbuf();
clientConfig.close();
}
if (data.tellp() > 0) {
rapidjson::Document document;
document.Parse(data.str().c_str());
if (!document.HasParseError()) {
rapidjson::StringBuffer buffer(0, 65536);
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
writer.SetMaxDecimalPlaces(10);
document.Accept(writer);
auto res = performRequest(requestUrl, buffer.GetString(), "POST");
if (!res) {
LOG_ERR("[CC-Client] error: unable to performRequest POST -> http://%s:%d%s",
m_self->m_options->ccHost(), m_self->m_options->ccPort(), requestUrl.c_str());
} else if (res->status != 200) {
LOG_ERR("[CC-Client] error: \"%d\" -> http://%s:%d%s", res->status, m_self->m_options->ccHost(),
m_self->m_options->ccPort(), requestUrl.c_str());
}
} else {
LOG_ERR("Not able to send config. Client config %s is broken!", m_self->m_options->configFile());
}
} else {
LOG_ERR("Not able to load client config %s. Please make sure it exists!", m_self->m_options->configFile());
}
}
std::shared_ptr<httplib::Response> CCClient::performRequest(const std::string& requestUrl, std::shared_ptr<httplib::Response> CCClient::performRequest(const std::string& requestUrl,
const std::string& requestBuffer, const std::string& requestBuffer,
const std::string& operation) const std::string& operation)
@ -245,6 +289,8 @@ void CCClient::onThreadStarted(void* handle)
static_cast<uint64_t>(m_self->m_options->ccUpdateInterval() * 1000), static_cast<uint64_t>(m_self->m_options->ccUpdateInterval() * 1000),
static_cast<uint64_t>(m_self->m_options->ccUpdateInterval() * 1000)); static_cast<uint64_t>(m_self->m_options->ccUpdateInterval() * 1000));
publishConfig();
uv_run(&m_self->m_client_loop, UV_RUN_DEFAULT); uv_run(&m_self->m_client_loop, UV_RUN_DEFAULT);
} }

View file

@ -48,6 +48,7 @@ private:
static void publishClientStatusReport(); static void publishClientStatusReport();
static void updateConfig(); static void updateConfig();
static void publishConfig();
static std::shared_ptr<httplib::Response> performRequest(const std::string& requestUrl, static std::shared_ptr<httplib::Response> performRequest(const std::string& requestUrl,
const std::string& requestBuffer, const std::string& requestBuffer,
const std::string& operation); const std::string& operation);

View file

@ -44,6 +44,7 @@ ClientStatus::ClientStatus()
m_hashrateHighest(0), m_hashrateHighest(0),
m_currentThreads(0), m_currentThreads(0),
m_cpuCores(0), m_cpuCores(0),
m_cpuThreads(0),
m_cpuL2(0), m_cpuL2(0),
m_cpuL3(0), m_cpuL3(0),
m_sharesGood(0), m_sharesGood(0),
@ -235,6 +236,16 @@ void ClientStatus::setCpuCores(int cpuCores)
m_cpuCores = cpuCores; m_cpuCores = cpuCores;
} }
int ClientStatus::getCpuThreads() const
{
return m_cpuThreads;
}
void ClientStatus::setCpuThreads(int cpuThreads)
{
m_cpuThreads = cpuThreads;
}
int ClientStatus::getCpuL2() const int ClientStatus::getCpuL2() const
{ {
return m_cpuL2; return m_cpuL2;
@ -379,6 +390,10 @@ bool ClientStatus::parseFromJson(const rapidjson::Document& document)
m_cpuCores = clientStatus["cpu_cores"].GetInt(); m_cpuCores = clientStatus["cpu_cores"].GetInt();
} }
if (clientStatus.HasMember("cpu_threads")) {
m_cpuThreads = clientStatus["cpu_threads"].GetInt();
}
if (clientStatus.HasMember("cpu_l2")) { if (clientStatus.HasMember("cpu_l2")) {
m_cpuL2 = clientStatus["cpu_l2"].GetInt(); m_cpuL2 = clientStatus["cpu_l2"].GetInt();
} }
@ -440,6 +455,7 @@ rapidjson::Value ClientStatus::toJson(rapidjson::MemoryPoolAllocator<rapidjson::
clientStatus.AddMember("current_threads", m_currentThreads, allocator); clientStatus.AddMember("current_threads", m_currentThreads, allocator);
clientStatus.AddMember("cpu_cores", m_cpuCores, allocator); clientStatus.AddMember("cpu_cores", m_cpuCores, allocator);
clientStatus.AddMember("cpu_threads", m_cpuThreads, allocator);
clientStatus.AddMember("cpu_l2", m_cpuL2, allocator); clientStatus.AddMember("cpu_l2", m_cpuL2, allocator);
clientStatus.AddMember("cpu_l3", m_cpuL3, allocator); clientStatus.AddMember("cpu_l3", m_cpuL3, allocator);

View file

@ -112,6 +112,9 @@ public:
int getCpuCores() const; int getCpuCores() const;
void setCpuCores(int cpuCores); void setCpuCores(int cpuCores);
int getCpuThreads() const;
void setCpuThreads(int cpuThreads);
int getCpuL2() const; int getCpuL2() const;
void setCpuL2(int cpuL2); void setCpuL2(int cpuL2);
@ -166,6 +169,7 @@ private:
int m_currentThreads; int m_currentThreads;
int m_cpuCores; int m_cpuCores;
int m_cpuThreads;
int m_cpuL2; int m_cpuL2;
int m_cpuL3; int m_cpuL3;

View file

@ -94,6 +94,7 @@ ControlCommand::Command ControlCommand::getCommand() const
bool ControlCommand::isOneTimeCommand() const { bool ControlCommand::isOneTimeCommand() const {
return m_command == ControlCommand::UPDATE_CONFIG || return m_command == ControlCommand::UPDATE_CONFIG ||
m_command == ControlCommand::PUBLISH_CONFIG ||
m_command == ControlCommand::RESTART || m_command == ControlCommand::RESTART ||
m_command == ControlCommand::SHUTDOWN; m_command == ControlCommand::SHUTDOWN;
} }

View file

@ -28,10 +28,11 @@
#include <string> #include <string>
#include "rapidjson/document.h" #include "rapidjson/document.h"
static const char* command_str[5] = { static const char* command_str[6] = {
"START", "START",
"STOP", "STOP",
"UPDATE_CONFIG", "UPDATE_CONFIG",
"PUBLISH_CONFIG",
"RESTART", "RESTART",
"SHUTDOWN" "SHUTDOWN"
}; };
@ -43,6 +44,7 @@ public:
START, START,
STOP, STOP,
UPDATE_CONFIG, UPDATE_CONFIG,
PUBLISH_CONFIG,
RESTART, RESTART,
SHUTDOWN SHUTDOWN
}; };

View file

@ -99,7 +99,7 @@ unsigned Service::handlePOST(const Options* options, const std::string& url, con
if (url.rfind("/client/setClientStatus", 0) == 0) { if (url.rfind("/client/setClientStatus", 0) == 0) {
resultCode = setClientStatus(clientIp, clientId, data, resp); resultCode = setClientStatus(clientIp, clientId, data, resp);
} else if (url.rfind("/admin/setClientConfig", 0) == 0) { } else if (url.rfind("/admin/setClientConfig", 0) == 0 || url.rfind("/client/setClientConfig", 0) == 0) {
resultCode = setClientConfig(options, clientId, data, resp); resultCode = setClientConfig(options, clientId, data, resp);
} else if (url.rfind("/admin/setClientCommand", 0) == 0) { } else if (url.rfind("/admin/setClientCommand", 0) == 0) {
resultCode = setClientCommand(clientId, data, resp); resultCode = setClientCommand(clientId, data, resp);

View file

@ -36,14 +36,14 @@
#define APP_DESC "XMRigCC CPU miner" #define APP_DESC "XMRigCC CPU miner"
#define APP_COPYRIGHT "Copyright (C) 2017- BenDr0id" #define APP_COPYRIGHT "Copyright (C) 2017- BenDr0id"
#endif #endif
#define APP_VERSION "1.2.0 (based on XMRig 2.4.3)" #define APP_VERSION "1.2.1 (based on XMRig 2.4.3)"
#define APP_DOMAIN "" #define APP_DOMAIN ""
#define APP_SITE "https://github.com/Bendr0id/xmrigCC" #define APP_SITE "https://github.com/Bendr0id/xmrigCC"
#define APP_KIND "cpu" #define APP_KIND "cpu"
#define APP_VER_MAJOR 1 #define APP_VER_MAJOR 1
#define APP_VER_MINOR 2 #define APP_VER_MINOR 2
#define APP_VER_BUILD 0 #define APP_VER_BUILD 1
#define APP_VER_REV 0 #define APP_VER_REV 0
#ifdef _MSC_VER #ifdef _MSC_VER