<h1>{{lang_dnbd3Management}}</h1>
<p><i>{{lang_dnbd3IntroText}}</i></p>
<style>
.shd { text-shadow: #fff 1px 1px 2px; border:1px solid #ddd; min-width:100px; }
.shd:empty { display: none; }
#speed-graph { width: 100%; height: 100px; margin: 3px; border-radius: 3px; }
</style>
<div class="panel panel-default">
<div class="panel-heading">
{{lang_dnbd3Status}}:
<b>
{{#enabled}}{{lang_enabled}}{{/enabled}}
{{^enabled}}{{lang_disabled}} (NFS/CIFS){{/enabled}}
</b>
– <a href="#" data-toggle="collapse" data-target="#toggle-div">{{lang_changeDnbd3Status}}</a>
</div>
<div class="panel-collapse {{^show_enable_warning}}collapse{{/show_enable_warning}}" id="toggle-div">
<div class="panel-body">
<form method="post" action="?do=dnbd3">
<input type="hidden" name="token" value="{{token}}">
<div class="checkbox">
<input id="enable-dnbd3" type="checkbox" name="enabled" {{enabled_checked_s}} {{perms.toggle-usage.disabled}}>
<label for="enable-dnbd3">{{lang_enableDnbd3}}</label>
</div>
{{#show_enable_warning}}
<div class="text-warning">
{{lang_enableDnbd3Hint}}
</div>
{{/show_enable_warning}}
<div class="checkbox">
<input id="allow-nfs" type="checkbox" name="with-nfs" {{nfs_checked_s}} {{perms.toggle-usage.disabled}}>
<label for="allow-nfs">{{lang_allowNfsFallback}}</label>
</div>
<div class="checkbox">
<input id="prefer-local" type="checkbox" name="prefer-local" {{local_checked_s}} {{perms.toggle-usage.disabled}}>
<label for="prefer-local">{{lang_preferSatDnbd3}}</label>
</div>
<button type="submit" name="action" value="toggle-usage" class="btn btn-success" {{perms.toggle-usage.disabled}}>
<span class="glyphicon glyphicon-floppy-disk"></span>
{{lang_save}}
</button>
</form>
</div>
</div>
</div>
<form method="post" onsubmit="$('#refbtn').prop('disabled', true).find('span').addClass('slx-rotation')" action="?do=dnbd3">
<input type="hidden" name="token" value="{{token}}">
<input type="hidden" name="action" value="refresh">
<h2>
{{lang_serverList}}
<button id="refbtn" type="submit" class="btn btn-default" {{perms.refresh.disabled}} title="{{lang_manualRefreshInfo}}">
<span class="glyphicon glyphicon-refresh"></span>
{{lang_manualRefresh}}
</button>
</h2>
</form>
<form method="post" action="?do=dnbd3">
<input type="hidden" name="token" value="{{token}}">
<input type="hidden" name="action" value="delserver">
<table class="table">
<thead>
<tr>
<th></th>
<th>{{lang_proxyServerTHead}}</th>
<th class="text-right">{{lang_storageSize}}</th>
<th class="text-right">{{lang_clientCount}}</th>
<th style="min-width:116px">{{lang_uploadSpeed}}</th>
<th class="text-right">{{lang_lastSeen}}</th>
<th class="text-right">{{lang_uptime}}</th>
<th class="text-right">{{lang_txTotal}}</th>
<th class="text-right">{{lang_rxTotal}}</th>
<th class="text-right">{{lang_locations}}</th>
<th></th>
</tr>
</thead>
<tbody>
{{#list}}
<tr>
<td class="text-right text-nowrap">
{{#slxOk}}
<span class="glyphicon glyphicon-ok text-success"></span>
{{/slxOk}}
{{#slxDown}}
<span class="glyphicon glyphicon-off"></span>
{{/slxDown}}
{{#uptime}}
<span class="glyphicon glyphicon-ok text-success"></span>
{{/uptime}}
{{^uptime}}
<span class="glyphicon glyphicon-remove text-danger"></span>
{{/uptime}}
</td>
<td class="{{#self}}slx-bold{{/self}}">
{{#machineuuid}}
<a class="pull-right btn btn-default btn-xs" href="?do=statistics&uuid={{machineuuid}}">
<span class="glyphicon glyphicon-eye-open"></span>
</a>
{{/machineuuid}}
{{^perms.view.details.disabled}}
<a href="?do=dnbd3&show=proxy&server={{serverid}}">
{{/perms.view.details.disabled}}
{{fixedip}}
<span class="small">{{clientip}}</span>
{{^perms.view.details.disabled}}
</a>
{{/perms.view.details.disabled}}
<div class="small">{{hostname}}</div>
</td>
<td data-sort="int" data-sort-default="desc" data-sort-value="{{disktotal}}">
<div style="background:linear-gradient(to right, #f85 {{diskUsePercent}}%, transparent {{diskUsePercent}}%)"
class="text-center text-nowrap shd"
title="{{lang_diskFree}}: {{diskfree_s}}">
{{disktotal_s}}
</div>
{{#errormsg}}
<div class="small text-nowrap text-danger" style="margin-right:-500px">
{{errormsg}}
</div>
{{/errormsg}}
</td>
<td data-sort="int" data-sort-default="desc" class="text-right text-nowrap" id="clientcount-{{serverid}}">
{{clientcount}}
</td>
<td data-sort="int" data-sort-default="desc" class="text-right text-nowrap">
<div id="upspeed-{{serverid}}" class="text-center text-nowrap shd"></div>
</td>
<td data-sort="int" data-sort-default="desc" data-sort-value="{{dnbd3lastseen}}" class="text-right text-nowrap">
{{dnbd3lastseen_s}}
</td>
<td data-sort="int" data-sort-default="desc" data-sort-value="{{uptime}}" class="text-right text-nowrap">
{{uptime_s}}
</td>
<td data-sort="int" data-sort-default="desc" data-sort-value="{{totalup}}" class="text-right text-nowrap">
{{totalup_s}}
</td>
<td data-sort="int" data-sort-default="desc" data-sort-value="{{totaldown}}" class="text-right text-nowrap">
{{totaldown_s}}
</td>
<td class="text-right text-nowrap">
{{^self}}
{{^locations}}
<i>{{lang_global}}</i>
{{/locations}}
{{#locations}}
{{locations}}
{{/locations}}
<a href="?do=dnbd3&show=locations&server={{serverid}}" class="btn btn-default btn-xs {{edit_disabled}}">
<span class="glyphicon glyphicon-map-marker"></span>
</a>
{{/self}}
</td>
<td class="text-right text-nowrap">
{{#machineuuid}}
{{#rebootcontrol}}
<button class="btn btn-warning btn-xs reboot-btn" type="button" data-id="{{serverid}}"
data-toggle="modal" data-target="#server-reboot-modal" title="{{lang_reboot}}"
{{edit_disabled}}>
<span class="glyphicon glyphicon-repeat"></span>
</button>
{{/rebootcontrol}}
<button class="btn btn-default btn-xs edit-btn" type="button" data-id="{{serverid}}"
data-toggle="modal" data-target="#server-edit-modal" title="{{lang_settings}}"
{{edit_disabled}}>
<span class="glyphicon glyphicon-cog"></span>
</button>
{{/machineuuid}}
{{^self}}
<button type="submit" class="btn btn-danger btn-xs" name="server" value="{{serverid}}" {{edit_disabled}}
data-confirm="#confirm-delete" data-title="{{fixedip}} {{clientip}}" title="{{lang_delete}}">
<span class="glyphicon glyphicon-trash"></span>
</button>
{{/self}}
</td>
</tr>
{{/list}}
</tbody>
</table>
</form>
<div class="hidden" id="confirm-delete">{{lang_wantToDelete}}</div>
<div class="btn-toolbar pull-right">
<div class="btn-group">
<button type="button" class="btn btn-success" data-toggle="modal" data-target="#add-modal" {{perms.configure.external.disabled}}>
<span class="glyphicon glyphicon-plus"></span>
{{lang_externalServerAdd}}
</button>
<button type="button" class="btn btn-default" data-toggle="modal" data-target="#help-external">
<span class="glyphicon glyphicon-question-sign"></span>
</button>
</div>
<div class="btn-group">
<a class="btn btn-success {{perms.configure.proxy.disabled}}" href="?do=runmode&module=dnbd3&modeid=proxy&redirect=?do=dnbd3">
<span class="glyphicon glyphicon-plus"></span>
{{lang_managedServerAdd}}
</a>
<button type="button" class="btn btn-default" data-toggle="modal" data-target="#help-automatic">
<span class="glyphicon glyphicon-question-sign"></span>
</button>
</div>
</div>
<div id="help-external" class="modal fade" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">×</button>
<h4 class="modal-title"><b>{{lang_externalServer}}</b></h4>
</div>
<div class="modal-body">
{{lang_externalServerHelp}}
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary pull-right" data-dismiss="modal">
{{lang_close}}
</button>
</div>
</div>
</div>
</div>
<div id="help-automatic" class="modal fade" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">×</button>
<h4 class="modal-title"><b>{{lang_managedServer}}</b></h4>
</div>
<div class="modal-body">
{{lang_managedServerHelp}}
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary pull-right" data-dismiss="modal">
{{lang_close}}
</button>
</div>
</div>
</div>
</div>
<div id="server-edit-modal" class="fade modal" role="dialog">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<form method="post" action="?do=dnbd3">
<input type="hidden" name="token" value="{{token}}">
<input type="hidden" name="action" value="editserver">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">×</button>
<h4 class="modal-title"><b>{{lang_editProxyHeading}}</b></h4>
</div>
<div class="modal-body" id="server-edit-body">
.
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary pull-right">
<span class="glyphicon glyphicon-floppy-disk"></span>
{{lang_save}}
</button>
</div>
</form>
</div>
</div>
</div>
<div id="server-reboot-modal" class="modal fade" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">×</button>
<h4 class="modal-title"><b>{{lang_rebootProxyHeading}}</b></h4>
</div>
<div class="modal-body">
<p>{{lang_rebootProxyText}}</p>
<p id="reboot-status"></p>
</div>
<div class="modal-footer text-right">
<button type="button" class="btn btn-default" data-dismiss="modal">{{lang_close}}</button>
<button id="btn-exec-reboot" type="button" class="btn btn-primary">
<span class="glyphicon glyphicon-repeat"></span>
{{lang_reboot}}
</button>
</div>
</div>
</div>
</div>
<div id="add-modal" class="modal fade" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">×</button>
<h4 class="modal-title"><b>{{lang_addServer}}</b></h4>
</div>
<form id="addform" method="post" action="?do=dnbd3">
<div class="modal-body">
<p>{{lang_enterIpOfServer}}</p>
<input type="hidden" name="token" value="{{token}}">
<input type="text" class="form-control" name="newip" id="newip">
<div id="addtest" class="text-danger"></div>
</div>
<div class="modal-footer">
<div class="btn-toolbar pull-right">
<button id="testbtn" type="submit" class="btn btn-warning" name="action" value="addserver">
<span class="glyphicon glyphicon-question-sign"></span>
{{lang_test}}
</button>
<button id="savebtn" type="submit" class="btn btn-primary" name="action" value="addserver" disabled>
<span class="glyphicon glyphicon-floppy-disk"></span>
{{lang_save}}
</button>
</div>
</div>
</form>
</div>
</div>
</div>
<div class="clearfix"></div>
<div class="slx-space"></div>
<canvas id="speed-graph"></canvas>
<script type="application/javascript"><!--
document.addEventListener('DOMContentLoaded', function () {
var slxWorking = false;
var testedIp;
const $form = $('#addform');
const $inputs = $form.find(':input');
const $result =$('#addtest');
const $ip = $('#newip');
const $save = $('#savebtn');
const changeFunc = function() {
$save.prop('disabled', $ip.val() !== testedIp);
};
$ip.change(changeFunc).keypress(function () { setTimeout(changeFunc, 1); });
$form.submit(function (event) {
console.log('pre-sub');
console.log($save.prop('disabled'));
if (!$save.prop('disabled')) return;
console.log('post-sub');
event.preventDefault();
runTest();
});
$('#testbtn').click(function (event) {
console.log('pre-focus');
if ($ip.is(':focus')) return;
console.log('post-focus');
event.preventDefault();
runTest();
});
function runTest() {
if (slxWorking === false) {
var ip = $ip.val();
var form = $('#addform');
slxWorking = Math.random();
var workCopy = slxWorking;
$inputs.prop('disabled', true);
$result.empty().removeClass('text-danger').text('...working...');
var resetFunc = function(ok) {
if (slxWorking === workCopy) {
slxWorking = false;
$inputs.prop('disabled', false);
if (!ok) $result.empty().addClass('text-danger').text('Timeout.');
}
};
setTimeout(resetFunc, 3000);
testedIp = ip;
$.post('?do=dnbd3', {action:'servertest', ip:ip, token:TOKEN}, function (data) {
if (workCopy !== slxWorking) return;
resetFunc(true);
if (!data || data.fatal) {
$save.prop('disabled', true);
}
if (data && data.error) {
$result.empty().addClass('text-danger').text(data.error);
} else {
$result.empty().removeClass('text-danger').text('OK, Uptime: ' + data.uptime + ', Clients: ' + data.clientCount);
}
}, 'json').fail(function(oh, what) {
resetFunc(true);
$result.empty().addClass('text-danger').text('Fail ' + what);
});
}
}
$('.edit-btn').click(function() {
var id = $(this).data('id');
$('#server-edit-body').text('loading').load('?do=dnbd3&action=editserver&server=' + id);
});
var rebootServerId = 0;
$('.reboot-btn').click(function() {
rebootServerId = $(this).data('id');
$('#btn-exec-reboot').prop('disabled', false);
$('#reboot-status').empty();
});
$('#btn-exec-reboot').click(function() {
$(this).prop('disabled', true);
var $t = $('#reboot-status');
if (rebootServerId === 0) {
$t.text('No ID!?');
return;
}
$t.html('<span class="glyphicon glyphicon-refresh slx-rotation"></span>');
var sid = rebootServerId;
var taskId = false;
var lastText;
var query = function() {
data = {"token": TOKEN, "action": "reboot", "server": sid};
if (taskId !== false) data['taskid'] = taskId;
$.ajax({
"data": data,
"method": "POST",
"dataType": "json",
"url": "?do=dnbd3"
}).done(function (data) {
if (!data || !data.taskId) return;
if (taskId === false) taskId = data.taskId;
if (data.error) data.rebootStatus += ' (' + data.error + ')';
if (data.rebootStatus !== lastText) {
$t.empty().text(data.rebootStatus);
}
if (data.taskStatus === 'TASK_PROCESSING' || data.taskStatus === 'TASK_WAITING') {
setTimeout(query, 5000);
if (data.rebootStatus !== lastText) {
$t.append($('<span class="glyphicon glyphicon-refresh slx-rotation"></span>'));
}
}
lastText = data.rebootStatus;
}).fail(function () {
$t.text('Failed');
});
};
query();
rebootServerId = 0;
});
// live speed
var hiddenProp;
if (typeof document.hidden !== "undefined") { // Opera 12.10 and Firefox 18 and later support
hiddenProp = "hidden";
} else if (typeof document.msHidden !== "undefined") {
hiddenProp = "msHidden";
} else if (typeof document.webkitHidden !== "undefined") {
hiddenProp = "webkitHidden";
} else {
hiddenProp = null;
}
var formatBytes = function(bytes) {
if (bytes < 1024) return bytes.toFixed(0) + '\u2009B';
if (bytes < 1048576) return (bytes / 1024).toFixed(0) + '\u2009KiB';
if (bytes < 1073741824) return (bytes / 1048576).toFixed(1) + '\u2009MiB';
if (bytes < 1099511627776) return (bytes / 1073741824).toFixed(2) + '\u2009GiB';
return (bytes / 1099511627776).toFixed(2) + '\u2009TiB';
};
var calcBackgroundStyle = function(speed) {
const colors = ['#eee', '#cfc', '#6f6', '#bc3', '#f00', '#f88'];
const limits = [1048576, 10485760, 104857600, 1073741824, 10737418240];
for (var i = 0; i < 4; ++i) {
if (speed < limits[i]) break;
}
const percent = Math.round(Math.max(0, Math.min(100, speed / limits[i] * 100)));
return { background: 'linear-gradient(90deg, ' + colors[i+1] + ' ' + percent + '%, ' + colors[i] + ' ' + percent + '%)' };
};
var lastSpeedList = {};
var history = [];
var inactiveCount = 0;
var updateSpeed = function() {
if (hiddenProp && document[hiddenProp]) {
if (++inactiveCount > 300)
return;
} else {
if (inactiveCount > 300) {
history.push(-1);
}
inactiveCount = 0;
}
$.ajax('?do=dnbd3&action=stats').done(function(elist) {
var speedSum = 0;
for (var k in elist) {
var e = elist[k];
if (lastSpeedList[k]) {
var lastSpeed = lastSpeedList[k];
if (lastSpeed['ts'] < e['ts']) {
var $speed = $('#upspeed-' + k);
var s = (e['bytesSent'] - lastSpeed['bytesSent']) / (e['ts'] - lastSpeed['ts']);
$speed.text(formatBytes(s) + "/s").css(calcBackgroundStyle(s));
speedSum += s;
}
}
var $clients = $('#clientcount-' + k);
$clients.text(e['clientCount'] + e['serverCount']);
}
history.push(speedSum);
while (history.length > 500) history.shift();
for (k in lastSpeedList) {
if (!elist[k]) {
$('#upspeed-' + k).text('???').css('background', '#aaa');
$('#clientcount-' + k).text('-');
}
}
lastSpeedList = elist;
updateGraph();
});
};
updateSpeed();
setInterval(updateSpeed, 2500);
var graph = document.getElementById('speed-graph');
var updateGraph = function() {
var i;
var gctx = graph.getContext('2d');
graph.width = Math.floor(graph.clientWidth / graph.clientHeight * 100);
graph.height = 100;
gctx.fillStyle = '#eee';
gctx.fillRect(0, 0, graph.width, graph.height);
var part = history.slice(-Math.floor(graph.width / 10));
var max = 1;
var peakIdx = -1;
var peakCount = 0;
var peakList = {};
for (i = 0; i < part.length; ++i) {
if (part[i] > max) max = part[i];
}
for (i = 0; i < part.length; ++i) {
if (peakIdx === -1 || part[i] > part[peakIdx]) {
peakIdx = i;
peakCount = 0;
} else if ((part[peakIdx] - part[i]) / max > 0.1) {
if (peakCount > 3) {
peakList[part.length - peakIdx - 1] = 1;
peakIdx = -1;
} else {
peakCount++;
}
} else {
peakIdx = i;
}
}
if (peakCount > 1) {
peakList[part.length - peakIdx - 1] = 1;
}
const BAR_COLOR = '#999';
part.reverse();
gctx.fillStyle = BAR_COLOR;
gctx.font = "9pt Arial";
gctx.textBaseline = 'top';
for (i = 0; i < part.length; ++i) {
var x = graph.width - i*10;
if (part[i] === -1) {
gctx.fillStyle = '#bbb';
gctx.fillRect(x - 5, 0, 1, 100);
gctx.fillStyle = BAR_COLOR;
} else {
var v = Math.round((1 - part[i] / max) * 100);
gctx.fillRect(x - 10, v, 9, 100);
if (peakList[i]) {
gctx.fillStyle = '#333';
gctx.fillText(formatBytes(part[i]) + "/s", x + 1, v);
gctx.fillStyle = BAR_COLOR;
}
}
}
gctx.stroke();
};
});
//--></script>