var wrt = { // variables for auto-update, interval is in seconds scheduleTimeout: undefined, updateTimeout: undefined, isScheduled: true, interval: 5, // option on whether to show per host sub-totals perHostTotals: false, // variables for sorting sortData: { column: 7, elId: 'thTotal', dir: 'desc', cache: {} } }; (function () { var oldDate, oldValues = []; // find base path var re = /(.*?admin\/nlbw\/[^/]+)/; var basePath = window.location.pathname.match(re)[1]; //---------------------- // HELPER FUNCTIONS //---------------------- /** * Human readable text for size * @param size * @returns {string} */ function getSize(size) { var prefix = [' ', 'k', 'M', 'G', 'T', 'P', 'E', 'Z']; var precision, base = 1000, pos = 0; while (size > base) { size /= base; pos++; } if (pos > 2) precision = 1000; else precision = 1; return (Math.round(size * precision) / precision) + ' ' + prefix[pos] + 'B'; } /** * Human readable text for date * @param date * @returns {string} */ function dateToString(date) { return date.toString().substring(0, 24); } /** * Gets the string representation of the date received from BE * @param value * @returns {*} */ function getDateString(value) { var tmp = value.split('_'), str = tmp[0].split('-').reverse().join('-') + 'T' + tmp[1]; return dateToString(new Date(str)); } /** * Create a `tr` element with content * @param content * @returns {string} */ function createTR(content) { var res = '' + data[2], {title: data[1]}), createTD(getSize(dlSpeed) + '/s', {right: true}), createTD(getSize(upSpeed) + '/s', {right: true}), createTD(getSize(data[3]), {right: true}), createTD(getSize(data[4]), {right: true}), createTD(getSize(data[5]), {right: true}), createTD(getDateString(data[6])), createTD(getDateString(data[7])) ]; // display row data var result = ''; for (var k = 0; k < displayData.length; k++) { result += displayData[k]; } result = createTR(result); return [result, rowData]; } /** * Creates the HTML output based on the `data` and `totals` inputs * @param data * @param totals * @returns {string} HTML output */ function getDisplayData(data, totals) { var result = createTH('客户端', {id: 'thClient'}) + createTH('下载带宽', {id: 'thDownload'}) + createTH('上传带宽', {id: 'thUpload'}) + createTH('总下载流量', {id: 'thTotalDown'}) + createTH('总上传流量', {id: 'thTotalUp'}) + createTH('流量合计', {id: 'thTotal'}) + createTH('首次上线时间', {id: 'thFirstSeen'}) + createTH('最后上线时间', {id: 'thLastSeen'}); result = createTR(result); for (var k = 0; k < data.length; k++) { result += data[k][0]; } var totalsRow = createTH('总计'); for (var m = 0; m < totals.length; m++) { var t = totals[m]; totalsRow += createTD(getSize(t) + (m < 2 ? '/s' : ''), {right: true}); } result += createTR(totalsRow); return result; } /** * Calculates per host sub-totals and adds them in the data input * @param data The data input */ function aggregateHostTotals(data) { if (!wrt.perHostTotals) return; var curHost = 0, insertAt = 1; while (curHost < data.length && insertAt < data.length) { // grab the current hostname/mac, and walk the data looking for rows with the same host/mac var hostName = data[curHost][1][0].toLowerCase(); for (var k = curHost + 1; k < data.length; k++) { if (data[k][1][0].toLowerCase() === hostName) { // this is another row for the same host, group it with any other rows for this host data.splice(insertAt, 0, data.splice(k, 1)[0]); insertAt++; } } // if we found more than one row for the host, add a subtotal row if (insertAt > curHost + 1) { var hostTotals = [data[curHost][1][0], '', '', 0, 0, 0, 0, 0]; for (var i = curHost; i < insertAt && i < data.length; i++) { for (var j = 3; j < hostTotals.length; j++) { hostTotals[j] += data[i][1][j]; } } var hostTotalRow = createTH(data[curHost][1][0] + '
(host total)', {title: data[curHost][1][1]}); for (var m = 3; m < hostTotals.length; m++) { var t = hostTotals[m]; hostTotalRow += createTD(getSize(t) + (m < 5 ? '/s' : ''), {right: true}); } hostTotalRow = createTR(hostTotalRow); data.splice(insertAt, 0, [hostTotalRow, hostTotals]); } curHost = insertAt; insertAt = curHost + 1; } } /** * Sorting function used to sort the `data`. Uses the global sort settings * @param x first item to compare * @param y second item to compare * @returns {number} 1 for desc, -1 for asc, 0 for equal */ function sortingFunction(x, y) { // get data from global variable var sortColumn = wrt.sortData.column, sortDirection = wrt.sortData.dir; var a = x[1][sortColumn]; var b = y[1][sortColumn]; if (a === b) { return 0; } else if (sortDirection === 'desc') { return a < b ? 1 : -1; } else { return a > b ? 1 : -1; } } /** * Sets the relevant global sort variables and re-renders the table to apply the new sorting * @param elId * @param column */ function setSortColumn(elId, column) { if (column === wrt.sortData.column) { // same column clicked, switch direction wrt.sortData.dir = wrt.sortData.dir === 'desc' ? 'asc' : 'desc'; } else { // change sort column wrt.sortData.column = column; // reset sort direction wrt.sortData.dir = 'desc'; } wrt.sortData.elId = elId; // render table data from cache renderTableData(wrt.sortData.cache.data, wrt.sortData.cache.totals); } /** * Registers the table events handlers for sorting when clicking the column headers */ function registerTableEventHandlers() { // note these ordinals are into the data array, not the table output document.getElementById('thClient').addEventListener('click', function () { setSortColumn(this.id, 0); // hostname }); document.getElementById('thDownload').addEventListener('click', function () { setSortColumn(this.id, 3); // dl speed }); document.getElementById('thUpload').addEventListener('click', function () { setSortColumn(this.id, 4); // ul speed }); document.getElementById('thTotalDown').addEventListener('click', function () { setSortColumn(this.id, 5); // total down }); document.getElementById('thTotalUp').addEventListener('click', function () { setSortColumn(this.id, 6); // total up }); document.getElementById('thTotal').addEventListener('click', function () { setSortColumn(this.id, 7); // total }); } /** * Fetches and handles the updated `values` from the BE * @param once If set to true, it re-schedules itself for execution based on selected interval */ function receiveData(once) { var ajax = new XMLHttpRequest(); ajax.onreadystatechange = function () { // noinspection EqualityComparisonWithCoercionJS if (this.readyState == 4 && this.status == 200) { var re = /(var values = new Array[^;]*;)/, match = ajax.responseText.match(re); if (!match) { handleError(); } else { // evaluate values eval(match[1]); //noinspection JSUnresolvedVariable var v = values; if (!v) { handleError(); } else { handleValues(v); // set old values oldValues = v; // set old date oldDate = new Date(); document.getElementById('updated').innerHTML = '数据更新时间 ' + dateToString(oldDate); } } var int = wrt.interval; if (!once && int > 0) reschedule(int); } }; ajax.open('GET', basePath + '/usage_data', true); ajax.send(); } /** * Registers DOM event listeners for user interaction */ function addEventListeners() { document.getElementById('intervalSelect').addEventListener('change', function () { var int = wrt.interval = this.value; if (int > 0) { // it is not scheduled, schedule it if (!wrt.isScheduled) { reschedule(int); } } else { // stop the scheduling stopSchedule(); } }); document.getElementById('resetDatabase').addEventListener('click', function () { if (confirm('This will delete the database file. Are you sure?')) { var ajax = new XMLHttpRequest(); ajax.onreadystatechange = function () { // noinspection EqualityComparisonWithCoercionJS if (this.readyState == 4 && this.status == 204) { location.reload(); } }; ajax.open('GET', basePath + '/usage_reset', true); ajax.send(); } }); document.getElementById('perHostTotals').addEventListener('change', function () { wrt.perHostTotals = !wrt.perHostTotals; }); } //---------------------- // AUTO-UPDATE //---------------------- /** * Stop auto-update schedule */ function stopSchedule() { window.clearTimeout(wrt.scheduleTimeout); window.clearTimeout(wrt.updateTimeout); setUpdateMessage(''); wrt.isScheduled = false; } /** * Start auto-update schedule * @param seconds */ function reschedule(seconds) { wrt.isScheduled = true; seconds = seconds || 60; updateSeconds(seconds); wrt.scheduleTimeout = window.setTimeout(receiveData, seconds * 1000); } /** * Sets the text of the `#updating` element * @param msg */ function setUpdateMessage(msg) { document.getElementById('updating').innerHTML = msg; } /** * Updates the 'Updating in X seconds' message * @param start */ function updateSeconds(start) { setUpdateMessage('倒数 ' + start + ' 秒后刷新.'); if (start > 0) { wrt.updateTimeout = window.setTimeout(function () { updateSeconds(start - 1); }, 1000); } } //---------------------- // END AUTO-UPDATE //---------------------- /** * Check for dependency, and if all is well, run callback * @param cb Callback function */ function checkForDependency(cb) { var ajax = new XMLHttpRequest(); ajax.onreadystatechange = function () { // noinspection EqualityComparisonWithCoercionJS if (this.readyState == 4 && this.status == 200) { // noinspection EqualityComparisonWithCoercionJS if (ajax.responseText == "1") { cb(); } else { alert("wrtbwmon is not installed!"); } } }; ajax.open('GET', basePath + '/check_dependency', true); ajax.send(); } checkForDependency(function () { // register events addEventListeners(); // Main entry point receiveData(); }); })();