You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Nginx/goaccess++/resources/js/app.js

1794 lines
48 KiB

/*jshint sub:true*/
(function () {
'use strict';
// Syntactic sugar
function $(selector) {
return document.querySelector(selector);
}
// Syntactic sugar & execute callback
function $$(selector, callback) {
var elems = document.querySelectorAll(selector);
for (var i = 0; i < elems.length; ++i) {
if (callback && typeof callback == 'function')
callback.call(this, elems[i]);
}
}
var debounce = function (func, wait, now) {
var timeout;
return function debounced () {
var that = this, args = arguments;
function delayed() {
if (!now)
func.apply(that, args);
timeout = null;
}
if (timeout) {
clearTimeout(timeout);
} else if (now) {
func.apply(obj, args);
}
timeout = setTimeout(delayed, wait || 250);
};
};
// global namespace
window.GoAccess = window.GoAccess || {
initialize: function (options) {
this.opts = options;
this.AppState = {}; // current state app key-value store
this.AppTpls = {}; // precompiled templates
this.AppCharts = {}; // holds all rendered charts
this.AppUIData = (this.opts || {}).uiData || {}; // holds panel definitions
this.AppData = (this.opts || {}).panelData || {}; // hold raw data
this.AppWSConn = (this.opts || {}).wsConnection || {}; // WebSocket connection
this.i18n = (this.opts || {}).i18n || {}; // i18n report labels
this.AppPrefs = {
'autoHideTables': true,
'layout': 'horizontal',
'perPage': 7,
'theme': 'darkPurple',
};
this.AppPrefs = GoAccess.Util.merge(this.AppPrefs, this.opts.prefs);
if (GoAccess.Util.hasLocalStorage()) {
var ls = JSON.parse(localStorage.getItem('AppPrefs'));
this.AppPrefs = GoAccess.Util.merge(this.AppPrefs, ls);
}
if (Object.keys(this.AppWSConn).length)
this.setWebSocket(this.AppWSConn);
},
getPanelUI: function (panel) {
return panel ? this.AppUIData[panel] : this.AppUIData;
},
getPrefs: function (panel) {
return panel ? this.AppPrefs[panel] : this.AppPrefs;
},
setPrefs: function () {
if (GoAccess.Util.hasLocalStorage()) {
localStorage.setItem('AppPrefs', JSON.stringify(GoAccess.getPrefs()));
}
},
getPanelData: function (panel) {
return panel ? this.AppData[panel] : this.AppData;
},
setWebSocket: function (wsConn) {
var host = null;
host = wsConn.url ? wsConn.url : window.location.hostname ? window.location.hostname : "localhost";
var str = /^(wss?:\/\/)?[^\/]+:[0-9]{1,5}\//.test(host + "/") ? host : String(host + ':' + wsConn.port);
str = !/^wss?:\/\//i.test(str) ? (window.location.protocol === "https:" ? 'wss://' : 'ws://') + str : str;
var socket = new WebSocket(str);
socket.onopen = function (event) {
GoAccess.Nav.WSOpen();
}.bind(this);
socket.onmessage = function (event) {
this.AppState['updated'] = true;
this.AppData = JSON.parse(event.data);
this.App.renderData();
}.bind(this);
socket.onclose = function (event) {
GoAccess.Nav.WSClose();
}.bind(this);
},
};
// HELPERS
GoAccess.Util = {
months: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul","Aug", "Sep", "Oct", "Nov", "Dec"],
// Add all attributes of n to o
merge: function (o, n) {
var obj = {}, i = 0, il = arguments.length, key;
for (; i < il; i++) {
for (key in arguments[i]) {
if (arguments[i].hasOwnProperty(key)) {
obj[key] = arguments[i][key];
}
}
}
return obj;
},
// hash a string
hashCode: function (s) {
return (s.split('').reduce(function (a, b) {
a = ((a << 5) - a) + b.charCodeAt(0);
return a&a;
}, 0) >>> 0).toString(16);
},
// Format bytes to human readable
formatBytes: function (bytes, decimals, numOnly) {
if (bytes == 0)
return numOnly ? 0 : '0 Byte';
var k = 1024;
var dm = decimals + 1 || 2;
var sizes = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB'];
var i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + (numOnly ? '' : (' ' + sizes[i]));
},
// Validate number
isNumeric: function (n) {
return !isNaN(parseFloat(n)) && isFinite(n);
},
// Format microseconds to human readable
utime2str: function (usec) {
if (usec >= 864E8)
return ((usec) / 864E8).toFixed(2) + ' d';
else if (usec >= 36E8)
return ((usec) / 36E8).toFixed(2) + ' h';
else if (usec >= 6E7)
return ((usec) / 6E7).toFixed(2) + ' m';
else if (usec >= 1E6)
return ((usec) / 1E6).toFixed(2) + ' s';
else if (usec >= 1E3)
return ((usec) / 1E3).toFixed(2) + ' ms';
return (usec).toFixed(2) + ' us';
},
// Format date from 20120124 to 24/Jan/2012
formatDate: function (str) {
var y = str.substr(0,4), m = str.substr(4,2) - 1, d = str.substr(6,2),
h = str.substr(8,2) || 0, i = str.substr(10, 2) || 0, s = str.substr(12, 2) || 0;
var date = new Date(y,m,d,h,i,s);
var out = ('0' + date.getDate()).slice(-2) + '/' + this.months[date.getMonth()] + '/' + date.getFullYear();
10 <= str.length && (out += ":" + h);
12 <= str.length && (out += ":" + i);
14 <= str.length && (out += ":" + s);
return out;
},
// Format field value to human readable
fmtValue: function (value, dataType, decimals) {
var val = 0;
if (!dataType)
val = value;
switch (dataType) {
case 'utime':
val = this.utime2str(value);
break;
case 'date':
val = this.formatDate(value);
break;
case 'numeric':
if (this.isNumeric(value))
val = value.toLocaleString();
break;
case 'bytes':
val = this.formatBytes(value, decimals);
break;
case 'percent':
val = parseFloat(value.replace(',', '.')).toFixed(2) + '%';
break;
case 'time':
if (this.isNumeric(value))
val = value.toLocaleString();
break;
case 'secs':
val = value + ' secs';
break;
default:
val = value;
}
return value == 0 ? String(val) : val;
},
isPanelValid: function (panel) {
var data = GoAccess.getPanelData(), ui = GoAccess.getPanelUI();
return (!ui.hasOwnProperty(panel) || !data.hasOwnProperty(panel) || !ui[panel].id);
},
// Attempts to extract the count from either an object or a scalar.
// e.g., item = Object {count: 14351, percent: 5.79} OR item = 4824825140
getCount: function (item) {
if (this.isObject(item) && 'count' in item)
return item.count;
return item;
},
getPercent: function (item) {
if (this.isObject(item) && 'percent' in item)
return this.fmtValue(item.percent, 'percent');
return null;
},
isObject: function (o) {
return o === Object(o);
},
setProp: function (o, s, v) {
var schema = o;
var a = s.split('.');
for (var i = 0, n = a.length; i < n-1; ++i) {
var k = a[i];
if (!schema[k])
schema[k] = {};
schema = schema[k];
}
schema[a[n-1]] = v;
},
getProp: function (o, s) {
s = s.replace(/\[(\w+)\]/g, '.$1');
s = s.replace(/^\./, '');
var a = s.split('.');
for (var i = 0, n = a.length; i < n; ++i) {
var k = a[i];
if (this.isObject(o) && k in o) {
o = o[k];
} else {
return;
}
}
return o;
},
hasLocalStorage: function () {
try {
localStorage.setItem('test', 'test');
localStorage.removeItem('test');
return true;
} catch(e) {
return false;
}
},
isWithinViewPort: function (el) {
var elemTop = el.getBoundingClientRect().top;
var elemBottom = el.getBoundingClientRect().bottom;
return elemTop < window.innerHeight && elemBottom >= 0;
},
};
// OVERALL STATS
GoAccess.OverallStats = {
// Render each overall stats box
renderBox: function (data, ui, row, x, idx) {
var wrap = $('.wrap-general-items');
// create a new bootstrap row every 6 elements
if (idx % 6 == 0) {
row = document.createElement('div');
row.setAttribute('class', 'row');
wrap.appendChild(row);
}
var box = document.createElement('div');
box.innerHTML = GoAccess.AppTpls.General.items.render({
'id': x,
'className': ui.items[x].className,
'label': ui.items[x].label,
'value': GoAccess.Util.fmtValue(data[x], ui.items[x].dataType),
});
row.appendChild(box);
return row;
},
// Render overall stats
renderData: function (data, ui) {
var idx = 0, row = null;
$('.wrap-general').innerHTML = GoAccess.AppTpls.General.wrap.render(GoAccess.Util.merge(ui, {
'lastUpdated': data.date_time,
'from': data.start_date,
'to': data.end_date,
}));
// Iterate over general data object
for (var x in data) {
if (!data.hasOwnProperty(x) || !ui.items.hasOwnProperty(x))
continue;
row = this.renderBox(data, ui, row, x, idx);
idx++;
}
},
// Render general/overall analyzed requests.
initialize: function () {
var ui = GoAccess.getPanelUI('general');
var data = GoAccess.getPanelData('general'), i = 0;
this.renderData(data, ui);
}
};
// RENDER PANELS
GoAccess.Nav = {
events: function () {
$('.nav-bars').onclick = function (e) {
e.stopPropagation();
this.renderMenu(e);
}.bind(this);
$('.nav-gears').onclick = function (e) {
e.stopPropagation();
this.renderOpts(e);
}.bind(this);
$('.nav-minibars').onclick = function (e) {
e.stopPropagation();
this.renderOpts(e);
}.bind(this);
$('body').onclick = function (e) {
$('nav').classList.remove('active');
}.bind(this);
$$('.export-json', function (item) {
item.onclick = function (e) {
this.downloadJSON(e);
}.bind(this);
}.bind(this));
$$('.theme-bright', function (item) {
item.onclick = function (e) {
this.setTheme('bright');
}.bind(this);
}.bind(this));
$$('.theme-dark-blue', function (item) {
item.onclick = function (e) {
this.setTheme('darkBlue');
}.bind(this);
}.bind(this));
$$('.theme-dark-gray', function (item) {
item.onclick = function (e) {
this.setTheme('darkGray');
}.bind(this);
}.bind(this));
$$('.theme-dark-purple', function (item) {
item.onclick = function (e) {
this.setTheme('darkPurple');
}.bind(this);
}.bind(this));
$$('.layout-horizontal', function (item) {
item.onclick = function (e) {
this.setLayout('horizontal');
}.bind(this);
}.bind(this));
$$('.layout-vertical', function (item) {
item.onclick = function (e) {
this.setLayout('vertical');
}.bind(this);
}.bind(this));
$$('[data-perpage]', function (item) {
item.onclick = function (e) {
this.setPerPage(e);
}.bind(this);
}.bind(this));
$$('[data-show-tables]', function (item) {
item.onclick = function (e) {
this.toggleTables();
}.bind(this);
}.bind(this));
$$('[data-autohide-tables]', function (item) {
item.onclick = function (e) {
this.toggleAutoHideTables();
}.bind(this);
}.bind(this));
},
downloadJSON: function (e) {
var targ = e.currentTarget;
var data = "text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(GoAccess.getPanelData()));
targ.href = 'data:' + data;
targ.download = 'goaccess-' + (+new Date()) + '.json';
},
setLayout: function (layout) {
if ('horizontal' == layout) {
$('.container').classList.add('container-fluid');
$('.container').classList.remove('container');
} else if ('vertical' == layout) {
$('.container-fluid').classList.add('container');
$('.container').classList.remove('container-fluid');
}
GoAccess.AppPrefs['layout'] = layout;
GoAccess.setPrefs();
GoAccess.Panels.initialize();
GoAccess.Charts.initialize();
GoAccess.Tables.initialize();
},
toggleAutoHideTables: function (e) {
var autoHideTables = GoAccess.Tables.autoHideTables();
$$('.table-wrapper', function (item) {
if (autoHideTables) {
item.classList.remove('hidden-xs');
} else {
item.classList.add('hidden-xs');
}
}.bind(this));
GoAccess.AppPrefs['autoHideTables'] = !autoHideTables;
GoAccess.setPrefs();
},
toggleTables: function () {
var ui = GoAccess.getPanelUI();
var showTables = GoAccess.Tables.showTables();
Object.keys(ui).forEach(function (panel, idx) {
if (!GoAccess.Util.isPanelValid(panel))
ui[panel]['table'] = !showTables;
}.bind(this));
GoAccess.AppPrefs['showTables'] = !showTables;
GoAccess.setPrefs();
GoAccess.Panels.initialize();
GoAccess.Charts.initialize();
GoAccess.Tables.initialize();
},
setTheme: function (theme) {
if (!theme)
return;
$('html').className = '';
switch(theme) {
case 'darkGray':
$('html').classList.add('dark');
$('html').classList.add('gray');
break;
case 'darkBlue':
$('html').classList.add('dark');
$('html').classList.add('blue');
break;
case 'darkPurple':
$('html').classList.add('dark');
$('html').classList.add('purple');
break;
}
GoAccess.AppPrefs['theme'] = theme;
GoAccess.setPrefs();
},
getIcon: function (key) {
switch(key) {
case 'visitors' : return 'users';
case 'requests' : return 'file';
case 'static_requests' : return 'file-text';
case 'not_found' : return 'file-o';
case 'hosts' : return 'user';
case 'os' : return 'desktop';
case 'browsers' : return 'chrome';
case 'visit_time' : return 'clock-o';
case 'vhosts' : return 'th-list';
case 'referrers' : return 'external-link';
case 'referring_sites' : return 'external-link';
case 'keyphrases' : return 'google';
case 'status_codes' : return 'warning';
case 'remote_user' : return 'users';
case 'geolocation' : return 'map-marker';
default : return 'pie-chart';
}
},
getItems: function () {
var ui = GoAccess.getPanelUI(), menu = [];
for (var panel in ui) {
if (GoAccess.Util.isPanelValid(panel))
continue;
// Push valid panels to our navigation array
menu.push({
'current': window.location.hash.substr(1) == panel,
'head': ui[panel].head,
'key': panel,
'icon': this.getIcon(panel),
});
}
return menu;
},
setPerPage: function (e) {
GoAccess.AppPrefs['perPage'] = +e.currentTarget.getAttribute('data-perpage');
GoAccess.App.renderData();
GoAccess.setPrefs();
},
getTheme: function () {
return GoAccess.AppPrefs.theme || 'darkGray';
},
getLayout: function () {
return GoAccess.AppPrefs.layout || 'horizontal';
},
getPerPage: function () {
return GoAccess.AppPrefs.perPage || 7;
},
// Render left-hand side navigation options.
renderOpts: function () {
var o = {};
o[this.getLayout()] = true;
o[this.getTheme()] = true;
o['perPage' + this.getPerPage()] = true;
o['autoHideTables'] = GoAccess.Tables.autoHideTables();
o['showTables'] = GoAccess.Tables.showTables();
o['labels'] = GoAccess.i18n;
$('.nav-list').innerHTML = GoAccess.AppTpls.Nav.opts.render(o);
$('nav').classList.toggle('active');
this.events();
},
// Render left-hand side navigation given the available panels.
renderMenu: function (e) {
$('.nav-list').innerHTML = GoAccess.AppTpls.Nav.menu.render({
'nav': this.getItems(),
'overall': window.location.hash.substr(1) == '',
'labels': GoAccess.i18n,
});
$('nav').classList.toggle('active');
this.events();
},
WSStatus: function () {
if (Object.keys(GoAccess.AppWSConn).length)
$$('.nav-ws-status', function (item) { item.style.display = 'block'; });
},
WSClose: function () {
$$('.nav-ws-status', function (item) {
item.classList.remove('connected');
item.setAttribute('title', 'Disconnected');
});
},
WSOpen: function () {
$$('.nav-ws-status', function (item) {
item.classList.add('connected');
item.setAttribute('title', 'Connected to ' + GoAccess.AppWSConn.url);
});
},
// Render left-hand side navigation given the available panels.
renderWrap: function (nav) {
$('nav').innerHTML = GoAccess.AppTpls.Nav.wrap.render(GoAccess.i18n);
},
// Iterate over all available panels and render each.
initialize: function () {
this.setTheme(GoAccess.AppPrefs.theme);
this.renderWrap();
this.WSStatus();
this.events();
}
};
// RENDER PANELS
GoAccess.Panels = {
events: function () {
$$('[data-toggle=dropdown]', function (item) {
item.onclick = function (e) {
this.openOpts(e.currentTarget);
}.bind(this);
item.onblur = function (e) {
this.closeOpts(e);
}.bind(this);
}.bind(this));
$$('[data-plot]', function (item) {
item.onclick = function (e) {
GoAccess.Charts.redrawChart(e.currentTarget);
}.bind(this);
}.bind(this));
$$('[data-chart]', function (item) {
item.onclick = function (e) {
GoAccess.Charts.toggleChart(e.currentTarget);
}.bind(this);
}.bind(this));
$$('[data-chart-type]', function (item) {
item.onclick = function (e) {
GoAccess.Charts.setChartType(e.currentTarget);
}.bind(this);
}.bind(this));
$$('[data-metric]', function (item) {
item.onclick = function (e) {
GoAccess.Tables.toggleColumn(e.currentTarget);
}.bind(this);
}.bind(this));
},
openOpts: function (targ) {
var panel = targ.getAttribute('data-panel');
targ.parentElement.classList.toggle('open');
this.renderOpts(panel);
},
closeOpts: function (e) {
e.currentTarget.parentElement.classList.remove('open');
// Trigger the click event on the target if not opening another menu
if (e.relatedTarget && e.relatedTarget.getAttribute('data-toggle') !== 'dropdown')
e.relatedTarget.click();
},
setPlotSelection: function (ui, prefs) {
var chartType = ((prefs || {}).plot || {}).chartType || ui.plot[0].chartType;
var metric = ((prefs || {}).plot || {}).metric || ui.plot[0].className;
ui[chartType] = true;
for (var i = 0, len = ui.plot.length; i < len; ++i)
if (ui.plot[i].className == metric)
ui.plot[i]['selected'] = true;
},
setColSelection: function (items, prefs) {
var columns = (prefs || {}).columns || {};
for (var i = 0, len = items.length; i < len; ++i)
if ((items[i].key in columns) && columns[items[i].key]['hide'])
items[i]['hide'] = true;
},
setChartSelection: function (ui, prefs) {
ui['showChart'] = prefs && ('chart' in prefs) ? prefs.chart : true;
},
setOpts: function (panel) {
var ui = JSON.parse(JSON.stringify(GoAccess.getPanelUI(panel))), prefs = GoAccess.getPrefs(panel);
// set preferences selection upon opening panel options
this.setChartSelection(ui, prefs);
this.setPlotSelection(ui, prefs);
this.setColSelection(ui.items, prefs);
return GoAccess.Util.merge(ui, {'labels': GoAccess.i18n});
},
renderOpts: function (panel) {
$('.panel-opts-' + panel).innerHTML = GoAccess.AppTpls.Panels.opts.render(this.setOpts(panel));
this.events();
},
enablePrev: function (panel) {
var $pagination = $('#panel-' + panel + ' .pagination a.panel-prev');
if ($pagination)
$pagination.parentNode.classList.remove('disabled');
},
disablePrev: function (panel) {
var $pagination = $('#panel-' + panel + ' .pagination a.panel-prev');
if ($pagination)
$pagination.parentNode.classList.add('disabled');
},
enableNext: function (panel) {
var $pagination = $('#panel-' + panel + ' .pagination a.panel-next');
if ($pagination)
$pagination.parentNode.classList.remove('disabled');
},
disableNext: function (panel) {
var $pagination = $('#panel-' + panel + ' .pagination a.panel-next');
if ($pagination)
$pagination.parentNode.classList.add('disabled');
},
enableFirst: function (panel) {
var $pagination = $('#panel-' + panel + ' .pagination a.panel-first');
if ($pagination)
$pagination.parentNode.classList.remove('disabled');
},
disableFirst: function (panel) {
var $pagination = $('#panel-' + panel + ' .pagination a.panel-first');
if ($pagination)
$pagination.parentNode.classList.add('disabled');
},
enableLast: function (panel) {
var $pagination = $('#panel-' + panel + ' .pagination a.panel-last');
if ($pagination)
$pagination.parentNode.classList.remove('disabled');
},
disableLast: function (panel) {
var $pagination = $('#panel-' + panel + ' .pagination a.panel-last');
if ($pagination)
$pagination.parentNode.classList.add('disabled');
},
enablePagination: function (panel) {
this.enablePrev(panel);
this.enableNext(panel);
this.enableFirst(panel);
this.enableLast(panel);
},
disablePagination: function (panel) {
this.disablePrev(panel);
this.disableNext(panel);
this.disableFirst(panel);
this.disableLast(panel);
},
hasSubItems: function (ui, data) {
for (var i = 0, len = data.length; i < len; ++i) {
if (!data[i].items)
return (ui['hasSubItems'] = false);
if (data[i].items.length) {
return (ui['hasSubItems'] = true);
}
}
return false;
},
setComputedData: function (panel, ui, data) {
this.hasSubItems(ui, data.data);
GoAccess.Charts.hasChart(panel, ui);
GoAccess.Tables.hasTable(ui);
},
// Render the given panel given a user interface definition.
renderPanel: function (panel, ui, col) {
// set some computed values before rendering panel structure
var data = GoAccess.getPanelData(panel);
this.setComputedData(panel, ui, data);
// per panel wrapper
var box = document.createElement('div');
box.id = 'panel-' + panel;
box.innerHTML = GoAccess.AppTpls.Panels.wrap.render(GoAccess.Util.merge(ui, {
'labels': GoAccess.i18n
}));
col.appendChild(box);
// Remove pagination if not enough data for the given panel
if (data.data.length <= GoAccess.getPrefs().perPage)
this.disablePagination(panel);
GoAccess.Tables.renderThead(panel, ui);
return col;
},
createCol: function (row) {
var perRow = GoAccess.AppPrefs['layout'] == 'horizontal' ? 6 : 12;
// set the number of columns based on current layout
var col = document.createElement('div');
col.setAttribute('class', 'col-md-' + perRow + ' wrap-panel');
row.appendChild(col);
return col;
},
createRow: function (row, idx) {
var wrap = $('.wrap-panels');
var every = GoAccess.AppPrefs['layout'] == 'horizontal' ? 2 : 1;
// create a new bootstrap row every one or two elements depending on
// the layout
if (idx % every == 0) {
row = document.createElement('div');
row.setAttribute('class', 'row' + (every == 2 ? ' equal' : ''));
wrap.appendChild(row);
}
return row;
},
resetPanel: function (panel) {
var ui = GoAccess.getPanelUI(), idx = 0, row = null;
var ele = $('#panel-' + panel);
if (GoAccess.Util.isPanelValid(panel))
return false;
var col = ele.parentNode;
col.removeChild(ele);
// Render panel given a user interface definition
this.renderPanel(panel, ui[panel], col);
this.events();
},
// Iterate over all available panels and render each panel
// structure.
renderPanels: function () {
var ui = GoAccess.getPanelUI(), idx = 0, row = null, col = null;
$('.wrap-panels').innerHTML = '';
for (var panel in ui) {
if (GoAccess.Util.isPanelValid(panel))
continue;
row = this.createRow(row, idx++);
col = this.createCol(row);
// Render panel given a user interface definition
col = this.renderPanel(panel, ui[panel], col);
}
},
initialize: function () {
this.renderPanels();
this.events();
}
};
// RENDER CHARTS
GoAccess.Charts = {
iter: function (callback) {
Object.keys(GoAccess.AppCharts).forEach(function (panel) {
// redraw chart only if it's within the viewport
if (!GoAccess.Util.isWithinViewPort($('#panel-' + panel)))
return;
if (callback && typeof callback === 'function')
callback.call(this, GoAccess.AppCharts[panel], panel);
});
},
getMetricKeys: function (panel, key) {
return GoAccess.getPanelUI(panel)['items'].map(function (a) { return a[key]; });
},
getPanelData: function (panel, data) {
// Grab ui plot data for the selected panel
var plot = GoAccess.Util.getProp(GoAccess.AppState, panel + '.plot');
// Grab the data for the selected panel
data = data || this.processChartData(GoAccess.getPanelData(panel).data);
return plot.chartReverse ? data.reverse() : data;
},
drawPlot: function (panel, plotUI, data) {
var chart = this.getChart(panel, plotUI, data);
if (!chart)
return;
this.renderChart(panel, chart, data);
GoAccess.AppCharts[panel] = null;
GoAccess.AppCharts[panel] = chart;
},
setChartType: function (targ) {
var panel = targ.getAttribute('data-panel');
var type = targ.getAttribute('data-chart-type');
GoAccess.Util.setProp(GoAccess.AppPrefs, panel + '.plot.chartType', type);
GoAccess.setPrefs();
var plotUI = GoAccess.Util.getProp(GoAccess.AppState, panel + '.plot');
// Extract data for the selected panel and process it
this.drawPlot(panel, plotUI, this.getPanelData(panel));
},
toggleChart: function (targ) {
var panel = targ.getAttribute('data-panel');
var prefs = GoAccess.getPrefs(panel),
chart = prefs && ('chart' in prefs) ? prefs.chart : true;
GoAccess.Util.setProp(GoAccess.AppPrefs, panel + '.chart', !chart);
GoAccess.setPrefs();
GoAccess.Panels.resetPanel(panel);
GoAccess.Charts.resetChart(panel);
GoAccess.Tables.renderFullTable(panel);
},
hasChart: function (panel, ui) {
var prefs = GoAccess.getPrefs(panel),
chart = prefs && ('chart' in prefs) ? prefs.chart : true;
ui['chart'] = ui.plot.length && chart && chart;
},
// Redraw a chart upon selecting a metric.
redrawChart: function (targ) {
var plot = targ.getAttribute('data-plot');
var panel = targ.getAttribute('data-panel');
var ui = GoAccess.getPanelUI(panel);
var plotUI = ui.plot;
GoAccess.Util.setProp(GoAccess.AppPrefs, panel + '.plot.metric', plot);
GoAccess.setPrefs();
// Iterate over plot user interface definition
for (var x in plotUI) {
if (!plotUI.hasOwnProperty(x) || plotUI[x].className != plot)
continue;
GoAccess.Util.setProp(GoAccess.AppState, panel + '.plot', plotUI[x]);
// Extract data for the selected panel and process it
this.drawPlot(panel, plotUI[x], this.getPanelData(panel));
break;
}
},
// Iterate over the item properties and and extract the count value.
extractCount: function (item) {
var o = {};
for (var prop in item)
o[prop] = GoAccess.Util.getCount(item[prop]);
return o;
},
// Extract an array of objects that D3 can consume to process the chart.
// e.g., o = Object {hits: 37402, visitors: 6949, bytes:
// 505881789, avgts: 118609, cumts: 4436224010…}
processChartData: function (data) {
var out = [];
for (var i = 0; i < data.length; ++i)
out.push(this.extractCount(data[i]));
return out;
},
findUIItem: function (panel, key) {
var items = GoAccess.getPanelUI(panel).items, o = {};
for (var i = 0; i < items.length; ++i) {
if (items[i].key == key)
return items[i];
}
return null;
},
getXKey: function (datum, key) {
var arr = [];
if (typeof key === 'string')
return datum[key];
for (var prop in key)
arr.push(datum[key[prop]]);
return arr.join(' ');
},
getAreaSpline: function (panel, plotUI, data) {
var dualYaxis = plotUI['d3']['y1'];
var chart = AreaChart(dualYaxis)
.labels({
y0: plotUI['d3']['y0'].label,
y1: dualYaxis ? plotUI['d3']['y1'].label : ''
})
.x(function (d) {
if ((((plotUI || {}).d3 || {}).x || {}).key)
return this.getXKey(d, plotUI['d3']['x']['key']);
return d.data;
}.bind(this))
.y0(function (d) {
return +d[plotUI['d3']['y0']['key']];
})
.width($("#chart-" + panel).getBoundingClientRect().width)
.height(175)
.format({
x: (this.findUIItem(panel, 'data') || {}).dataType || null,
y0: ((plotUI.d3 || {}).y0 || {}).format,
y1: ((plotUI.d3 || {}).y1 || {}).format,
})
.opts(plotUI);
dualYaxis && chart.y1(function (d) {
return +d[plotUI['d3']['y1']['key']];
});
return chart;
},
getVBar: function (panel, plotUI, data) {
var dualYaxis = plotUI['d3']['y1'];
var chart = BarChart(dualYaxis)
.labels({
y0: plotUI['d3']['y0'].label,
y1: dualYaxis ? plotUI['d3']['y1'].label : ''
})
.x(function (d) {
if ((((plotUI || {}).d3 || {}).x || {}).key)
return this.getXKey(d, plotUI['d3']['x']['key']);
return d.data;
}.bind(this))
.y0(function (d) {
return +d[plotUI['d3']['y0']['key']];
})
.width($("#chart-" + panel).getBoundingClientRect().width)
.height(175)
.format({
x: (this.findUIItem(panel, 'data') || {}).dataType || null,
y0: ((plotUI.d3 || {}).y0 || {}).format,
y1: ((plotUI.d3 || {}).y1 || {}).format,
})
.opts(plotUI);
dualYaxis && chart.y1(function (d) {
return +d[plotUI['d3']['y1']['key']];
});
return chart;
},
getChartType: function (panel) {
var ui = GoAccess.getPanelUI(panel);
if (!ui.chart)
return '';
return GoAccess.Util.getProp(GoAccess.getPrefs(), panel + '.plot.chartType') || ui.plot[0].chartType;
},
getPlotUI: function (panel, ui) {
var metric = GoAccess.Util.getProp(GoAccess.getPrefs(), panel + '.plot.metric');
if (!metric)
return ui.plot[0];
return ui.plot.filter(function (v) {
return v.className == metric;
})[0];
},
getChart: function (panel, plotUI, data) {
var chart = null;
// Render given its type
switch (this.getChartType(panel)) {
case 'area-spline':
chart = this.getAreaSpline(panel, plotUI, data);
break;
case 'bar':
chart = this.getVBar(panel, plotUI, data);
break;
}
return chart;
},
renderChart: function (panel, chart, data) {
// remove popup
d3.select('#chart-' + panel + '>.chart-tooltip-wrap')
.remove();
// remove svg
d3.select('#chart-' + panel).select('svg')
.remove();
// add chart to the document
d3.select("#chart-" + panel)
.datum(data)
.call(chart)
.append("div").attr("class", "chart-tooltip-wrap");
},
addChart: function (panel, ui) {
var plotUI = null, chart = null;
// Ensure it has a plot definition
if (!ui.plot || !ui.plot.length)
return;
plotUI = this.getPlotUI(panel, ui);
// set ui plot data
GoAccess.Util.setProp(GoAccess.AppState, panel + '.plot', plotUI);
// Grab the data for the selected panel
var data = this.getPanelData(panel);
if (!(chart = this.getChart(panel, plotUI, data)))
return;
this.renderChart(panel, chart, data);
GoAccess.AppCharts[panel] = chart;
},
// Render all charts for the applicable panels.
renderCharts: function (ui) {
for (var panel in ui) {
if (!ui.hasOwnProperty(panel))
continue;
this.addChart(panel, ui[panel]);
}
},
resetChart: function (panel) {
var ui = {};
if (GoAccess.Util.isPanelValid(panel))
return false;
ui = GoAccess.getPanelUI(panel);
this.addChart(panel, ui);
},
// Reload (doesn't redraw) the given chart's data
reloadChart: function (chart, panel) {
var subItems = GoAccess.Tables.getSubItemsData(panel);
var data = (subItems.length ? subItems : GoAccess.getPanelData(panel).data).slice(0);
d3.select("#chart-" + panel)
.datum(this.processChartData(this.getPanelData(panel, data)))
.call(chart.width($("#chart-" + panel).offsetWidth));
},
// Reload (doesn't redraw) all chart's data
reloadCharts: function () {
this.iter(function (chart, panel) {
this.reloadChart(chart, panel);
}.bind(this));
GoAccess.AppState.updated = false;
},
// Only redraw charts with current data
redrawCharts: function () {
this.iter(function (chart, panel) {
d3.select("#chart-" + panel).call(chart.width($("#chart-" + panel).offsetWidth));
});
},
initialize: function () {
this.renderCharts(GoAccess.getPanelUI());
// reload on scroll & redraw on resize
d3.select(window).on('scroll.charts', debounce(function () {
this.reloadCharts();
}, 250, false).bind(this)).on('resize.charts', function () {
this.redrawCharts();
}.bind(this));
}
};
// RENDER TABLES
GoAccess.Tables = {
chartData: {}, // holds all panel sub items data that feeds the chart
events: function () {
$$('.panel-next', function (item) {
item.onclick = function (e) {
var panel = e.currentTarget.getAttribute('data-panel');
this.renderTable(panel, this.nextPage(panel));
}.bind(this);
}.bind(this));
$$('.panel-prev', function (item) {
item.onclick = function (e) {
var panel = e.currentTarget.getAttribute('data-panel');
this.renderTable(panel, this.prevPage(panel));
}.bind(this);
}.bind(this));
$$('.panel-first', function (item) {
item.onclick = function (e) {
var panel = e.currentTarget.getAttribute('data-panel');
this.renderTable(panel, "FIRST_PAGE");
}.bind(this);
}.bind(this));
$$('.panel-last', function (item) {
item.onclick = function (e) {
var panel = e.currentTarget.getAttribute('data-panel');
this.renderTable(panel, "LAST_PAGE");
}.bind(this);
}.bind(this));
$$('.expandable>td', function (item) {
item.onclick = function (e) {
if (!window.getSelection().toString())
this.toggleRow(e.currentTarget);
}.bind(this);
}.bind(this));
$$('.row-expandable.clickable', function (item) {
item.onclick = function (e) {
this.toggleRow(e.currentTarget);
}.bind(this);
}.bind(this));
$$('.sortable', function (item) {
item.onclick = function (e) {
this.sortColumn(e.currentTarget);
}.bind(this);
}.bind(this));
},
toggleColumn: function (targ) {
var panel = targ.getAttribute('data-panel');
var metric = targ.getAttribute('data-metric');
var columns = (GoAccess.getPrefs(panel) || {}).columns || {};
if (metric in columns) {
delete columns[metric];
} else {
GoAccess.Util.setProp(columns, metric + '.hide', true);
}
GoAccess.Util.setProp(GoAccess.AppPrefs, panel + '.columns', columns);
GoAccess.setPrefs();
GoAccess.Tables.renderThead(panel, GoAccess.getPanelUI(panel));
GoAccess.Tables.renderFullTable(panel);
},
sortColumn: function (ele) {
var field = ele.getAttribute('data-key');
var order = ele.getAttribute('data-order');
var panel = ele.parentElement.parentElement.parentElement.getAttribute('data-panel');
order = order ? 'asc' == order ? 'desc' : 'asc' : 'asc';
GoAccess.App.sortData(panel, field, order);
GoAccess.Util.setProp(GoAccess.AppState, panel + '.sort', {
'field': field,
'order': order,
});
this.renderThead(panel, GoAccess.getPanelUI(panel));
this.renderTable(panel, this.getCurPage(panel));
GoAccess.Charts.reloadChart(GoAccess.AppCharts[panel], panel);
},
getDataByKey: function (panel, key) {
var data = GoAccess.getPanelData(panel).data;
for (var i = 0, n = data.length; i < n; ++i) {
if (GoAccess.Util.hashCode(data[i].data) == key)
return data[i];
}
return null;
},
getSubItemsData: function (panel) {
var out = [], items = this.chartData[panel];
for (var x in items) {
if (!items.hasOwnProperty(x))
continue;
out = out.concat(items[x]);
}
return out;
},
addChartData: function (panel, key) {
var data = this.getDataByKey(panel, key);
var path = panel + '.' + key;
if (!data || !data.items)
return [];
GoAccess.Util.setProp(this.chartData, path, data.items);
return this.getSubItemsData(panel);
},
removeChartData: function (panel, key) {
if (GoAccess.Util.getProp(this.chartData, panel + '.' + key))
delete this.chartData[panel][key];
if (!this.chartData[panel] || Object.keys(this.chartData[panel]).length == 0)
return GoAccess.getPanelData(panel).data;
return this.getSubItemsData(panel);
},
isExpanded: function (panel, key) {
var path = panel + '.expanded.' + key;
return GoAccess.Util.getProp(GoAccess.AppState, path);
},
toggleExpanded: function (panel, key) {
var path = panel + '.expanded.' + key, ret = true;
if (this.isExpanded(panel, key)) {
delete GoAccess.AppState[panel]['expanded'][key];
} else {
GoAccess.Util.setProp(GoAccess.AppState, path, true), ret = false;
}
return ret;
},
// Toggle children rows
toggleRow: function (ele) {
var hide = false, data = [];
var row = ele.parentNode;
var panel = row.getAttribute('data-panel'), key = row.getAttribute('data-key');
var plotUI = GoAccess.AppCharts[panel].opts();
hide = this.toggleExpanded(panel, key);
this.renderTable(panel, this.getCurPage(panel));
if (!plotUI.redrawOnExpand)
return;
if (!hide) {
data = GoAccess.Charts.processChartData(this.addChartData(panel, key));
} else {
data = GoAccess.Charts.processChartData(this.removeChartData(panel, key));
}
GoAccess.Charts.drawPlot(panel, plotUI, data);
},
// Get current panel page
getCurPage: function (panel) {
return GoAccess.Util.getProp(GoAccess.AppState, panel + '.curPage') || 0;
},
// Page offset.
// e.g., Return Value: 11, curPage: 2
pageOffSet: function (panel) {
return ((this.getCurPage(panel) - 1) * GoAccess.getPrefs().perPage);
},
// Get total number of pages given the number of items on array
getTotalPages: function (dataItems) {
return Math.ceil(dataItems.length / GoAccess.getPrefs().perPage);
},
// Get a shallow copy of a portion of the given data array and the
// current page.
getPage: function (panel, dataItems, page) {
var totalPages = this.getTotalPages(dataItems);
if (page < 1)
page = 1;
if (page > totalPages)
page = totalPages;
GoAccess.Util.setProp(GoAccess.AppState, panel + '.curPage', page);
var start = this.pageOffSet(panel);
var end = start + GoAccess.getPrefs().perPage;
return dataItems.slice(start, end);
},
// Get previous page
prevPage: function (panel) {
return this.getCurPage(panel) - 1;
},
// Get next page
nextPage: function (panel) {
return this.getCurPage(panel) + 1;
},
getMetaValue: function (ui, value) {
if ('meta' in ui)
return value[ui.meta];
return null;
},
getMetaCell: function (ui, value) {
var val = this.getMetaValue(ui, value);
var max = (value || {}).max;
var min = (value || {}).min;
// use metaType if exist else fallback to dataType
var vtype = ui.metaType || ui.dataType;
var className = ui.className || '';
className += ui.dataType != 'string' ? 'text-right' : '';
return {
'className': className,
'max' : max != undefined ? GoAccess.Util.fmtValue(max, vtype) : null,
'min' : min != undefined ? GoAccess.Util.fmtValue(min, vtype) : null,
'value' : val != undefined ? GoAccess.Util.fmtValue(val, vtype) : null,
'title' : ui.meta,
'label' : ui.metaLabel || null,
};
},
hideColumn: function (panel, col) {
var columns = (GoAccess.getPrefs(panel) || {}).columns || {};
return ((col in columns) && columns[col]['hide']);
},
showTables: function () {
return ('showTables' in GoAccess.getPrefs()) ? GoAccess.getPrefs().showTables : true;
},
autoHideTables: function () {
return ('autoHideTables' in GoAccess.getPrefs()) ? GoAccess.getPrefs().autoHideTables : true;
},
hasTable: function (ui) {
ui['table'] = this.showTables();
ui['autoHideTables'] = this.autoHideTables();
},
renderMetaRow: function (panel, ui) {
// find the table to set
var table = $('.table-' + panel + ' tbody.tbody-meta');
if (!table)
return;
var cells = [], uiItems = ui.items;
var data = GoAccess.getPanelData(panel).metadata;
for (var i = 0; i < uiItems.length; ++i) {
var item = uiItems[i];
if (this.hideColumn(panel, item.key))
continue;
var value = data[item.key];
cells.push(this.getMetaCell(item, value));
}
table.innerHTML = GoAccess.AppTpls.Tables.meta.render({
row: [{
'hasSubItems': ui.hasSubItems,
'cells': cells
}]
});
},
// Iterate over user interface definition properties
iterUIItems: function (panel, uiItems, dataItems, callback) {
var out = [];
for (var i = 0; i < uiItems.length; ++i) {
var uiItem = uiItems[i];
if (this.hideColumn(panel, uiItem.key))
continue;
// Data for the current user interface property.
// e.g., dataItem = Object {count: 13949, percent: 5.63}
var dataItem = dataItems[uiItem.key];
// Apply the callback and push return data to output array
if (callback && typeof callback == 'function') {
var ret = callback.call(this, panel, uiItem, dataItem);
if (ret) out.push(ret);
}
}
return out;
},
// Return an object that can be consumed by the table template given a user
// interface definition and a cell value object.
// e.g., value = Object {count: 14351, percent: 5.79}
getObjectCell: function (panel, ui, value) {
var className = ui.className || '';
className += ui.dataType != 'string' ? 'text-right' : '';
return {
'className': className,
'percent': GoAccess.Util.getPercent(value),
'value': GoAccess.Util.fmtValue(GoAccess.Util.getCount(value), ui.dataType)
};
},
// Given a data item object, set all the row cells and return a
// table row that the template can consume.
renderRow: function (panel, callback, ui, dataItem, idx, subItem, parentId, expanded) {
var shadeParent = ((!subItem && idx % 2 != 0) ? 'shaded' : '');
var shadeChild = ((parentId % 2 != 0) ? 'shaded' : '');
return {
'panel' : panel,
'idx' : !subItem && (String((idx + 1) + this.pageOffSet(panel))),
'key' : !subItem ? GoAccess.Util.hashCode(dataItem.data) : '',
'expanded' : !subItem && expanded,
'parentId' : subItem ? String(parentId) : '',
'className' : subItem ? 'child ' + shadeChild : 'parent ' + shadeParent,
'hasSubItems' : ui.hasSubItems,
'items' : dataItem.items ? dataItem.items.length : 0,
'cells' : callback.call(this),
};
},
renderRows: function (rows, panel, ui, dataItems, subItem, parentId) {
subItem = subItem || false;
// no data rows
if (dataItems.length == 0 && ui.items.length) {
rows.push({
cells: [{
className: 'text-center',
colspan: ui.items.length + 1,
value: 'No data on this panel.'
}]
});
}
// Iterate over all data items for the given panel and
// generate a table row per date item.
var cellcb = null;
for (var i = 0; i < dataItems.length; ++i) {
var dataItem = dataItems[i], data = null, expanded = false;
switch(typeof dataItem) {
case 'string':
data = dataItem;
cellcb = function () {
return {
'colspan': ui.items.length,
'value': data
};
};
break;
default:
data = dataItem.data;
cellcb = this.iterUIItems.bind(this, panel, ui.items, dataItem, this.getObjectCell.bind(this));
}
expanded = this.isExpanded(panel, GoAccess.Util.hashCode(data));
rows.push(this.renderRow(panel, cellcb, ui, dataItem, i, subItem, parentId, expanded));
if (dataItem.items && dataItem.items.length && expanded) {
this.renderRows(rows, panel, ui, dataItem.items, true, i, expanded);
}
}
},
// Entry point to render all data rows into the table
renderDataRows: function (panel, ui, dataItems, page) {
// find the table to set
var table = $('.table-' + panel + ' tbody.tbody-data');
if (!table)
return;
dataItems = this.getPage(panel, dataItems, page);
var rows = [];
this.renderRows(rows, panel, ui, dataItems);
if (rows.length == 0)
return;
table.innerHTML = GoAccess.AppTpls.Tables.data.render({
rows: rows
});
},
togglePagination: function (panel, page, dataItems) {
GoAccess.Panels.enablePagination(panel);
// Disable pagination next button if last page is reached
if (page >= this.getTotalPages(dataItems)) {
GoAccess.Panels.disableNext(panel);
GoAccess.Panels.disableLast(panel);
}
if (page <= 1) {
GoAccess.Panels.disablePrev(panel);
GoAccess.Panels.disableFirst(panel);
}
},
renderTable: function (panel, page) {
var dataItems = GoAccess.getPanelData(panel).data;
var ui = GoAccess.getPanelUI(panel);
if (page === "LAST_PAGE") {
page = this.getTotalPages(dataItems);
} else if (page === "FIRST_PAGE") {
page = 1;
}
this.togglePagination(panel, page, dataItems);
// Render data rows
this.renderDataRows(panel, ui, dataItems, page);
this.events();
},
renderFullTable: function (panel) {
var ui = GoAccess.getPanelUI(panel), page = 0;
// panel's data
var data = GoAccess.getPanelData(panel);
// render meta data
if (data.hasOwnProperty('metadata'))
this.renderMetaRow(panel, ui);
// render actual data
if (data.hasOwnProperty('data')) {
page = this.getCurPage(panel);
this.togglePagination(panel, page, data.data);
this.renderDataRows(panel, ui, data.data, page);
}
},
// Iterate over all panels and determine which ones should contain
// a data table.
renderTables: function (force) {
var ui = GoAccess.getPanelUI();
for (var panel in ui) {
if (GoAccess.Util.isPanelValid(panel) || !this.showTables())
continue;
if (force || GoAccess.Util.isWithinViewPort($('#panel-' + panel)))
this.renderFullTable(panel);
}
},
// Given a UI panel definition, make a copy of it and assign the sort
// fields to the template object to render
sort2Tpl: function (panel, ui) {
var uiClone = JSON.parse(JSON.stringify(ui)), out = [];
var sort = GoAccess.Util.getProp(GoAccess.AppState, panel + '.sort');
for (var i = 0, len = uiClone.items.length; i < len; ++i) {
var item = uiClone.items[i];
if (this.hideColumn(panel, item.key))
continue;
item['sort'] = false;
if (item.key == sort.field && sort.order) {
item['sort'] = true;
item[sort.order.toLowerCase()] = true;
}
out.push(item);
}
uiClone.items = out;
return uiClone;
},
renderThead: function (panel, ui) {
var $thead = $('.table-' + panel + '>thead'), $colgroup = $('.table-' + panel + '>colgroup');
if ($thead && $colgroup && this.showTables()) {
ui = this.sort2Tpl(panel, ui);
$thead.innerHTML = GoAccess.AppTpls.Tables.head.render(ui);
$colgroup.innerHTML = GoAccess.AppTpls.Tables.colgroup.render(ui);
}
},
reloadTables: function () {
this.renderTables(false);
this.events();
},
initialize: function () {
this.renderTables(true);
this.events();
// redraw on scroll
d3.select(window).on('scroll.tables', debounce(function () {
this.reloadTables();
}, 250, false).bind(this));
},
};
// Main App
GoAccess.App = {
hasFocus: true,
tpl: function (tpl) {
return Hogan.compile(tpl);
},
setTpls: function () {
GoAccess.AppTpls = {
'Nav': {
'wrap': this.tpl($('#tpl-nav-wrap').innerHTML),
'menu': this.tpl($('#tpl-nav-menu').innerHTML),
'opts': this.tpl($('#tpl-nav-opts').innerHTML),
},
'Panels': {
'wrap': this.tpl($('#tpl-panel').innerHTML),
'opts': this.tpl($('#tpl-panel-opts').innerHTML),
},
'General': {
'wrap': this.tpl($('#tpl-general').innerHTML),
'items': this.tpl($('#tpl-general-items').innerHTML),
},
'Tables': {
'colgroup': this.tpl($('#tpl-table-colgroup').innerHTML),
'head': this.tpl($('#tpl-table-thead').innerHTML),
'meta': this.tpl($('#tpl-table-row-meta').innerHTML),
'data': this.tpl($('#tpl-table-row').innerHTML),
},
};
},
sortField: function (o, field) {
var f = o[field];
if (GoAccess.Util.isObject(f) && (f !== null))
f = o[field].count;
return f;
},
sortData: function (panel, field, order) {
// panel's data
var panelData = GoAccess.getPanelData(panel).data;
panelData.sort(function (a, b) {
a = this.sortField(a, field);
b = this.sortField(b, field);
if (typeof a === 'string' && typeof b === 'string')
return 'asc' == order ? a.localeCompare(b) : b.localeCompare(a);
return 'asc' == order ? a - b : b - a;
}.bind(this));
},
setInitSort: function () {
var ui = GoAccess.getPanelUI();
for (var panel in ui) {
if (GoAccess.Util.isPanelValid(panel))
continue;
GoAccess.Util.setProp(GoAccess.AppState, panel + '.sort', ui[panel].sort);
}
},
// Verify if we need to sort panels upon data re-entry
verifySort: function () {
var ui = GoAccess.getPanelUI();
for (var panel in ui) {
if (GoAccess.Util.isPanelValid(panel))
continue;
var sort = GoAccess.Util.getProp(GoAccess.AppState, panel + '.sort');
// do not sort panels if they still hold the same sort properties
if (JSON.stringify(sort) === JSON.stringify(ui[panel].sort))
continue;
this.sortData(panel, sort.field, sort.order);
}
},
initDom: function () {
$('nav').classList.remove('hide');
$('.container').classList.remove('hide');
$('.spinner').classList.add('hide');
if (GoAccess.AppPrefs['layout'] == 'horizontal') {
$('.container').classList.add('container-fluid');
$('.container-fluid').classList.remove('container');
}
},
renderData: function () {
// update data and charts if tab/document has focus
if (!this.hasFocus)
return;
this.verifySort();
GoAccess.OverallStats.initialize();
// do not rerender tables/charts if data hasn't changed
if (!GoAccess.AppState.updated)
return;
GoAccess.Charts.reloadCharts();
GoAccess.Tables.reloadTables();
},
initialize: function () {
this.setInitSort();
this.setTpls();
GoAccess.Nav.initialize();
this.initDom();
GoAccess.OverallStats.initialize();
GoAccess.Panels.initialize();
GoAccess.Charts.initialize();
GoAccess.Tables.initialize();
},
};
// Adds the visibilitychange EventListener
document.addEventListener('visibilitychange', function () {
// fires when user switches tabs, apps, etc.
if (document.visibilityState === 'hidden')
GoAccess.App.hasFocus = false;
// fires when app transitions from hidden or user returns to the app/tab.
if (document.visibilityState === 'visible') {
var hasFocus = GoAccess.App.hasFocus;
GoAccess.App.hasFocus = true;
hasFocus || GoAccess.App.renderData();
}
});
// Init app
window.onload = function () {
GoAccess.initialize({
'i18n': window.json_i18n,
'uiData': window.user_interface,
'panelData': window.json_data,
'wsConnection': window.connection || null,
'prefs': window.html_prefs || {},
});
GoAccess.App.initialize();
};
}());