(function($) { var __ = window.wfi18n.__; var sprintf = window.wfi18n.sprintf; var LISTING_LIMIT = 50; LiveTrafficViewModel = function(listings, filters) { var self = this; var listingIDTable = {}; self.listings = ko.observableArray(listings); self.listings.subscribe(function(items) { listingIDTable = {}; for (var i = 0; i < items.length; i++) { listingIDTable[items[i].id()] = 1; } //console.log(items); }); self.hasListing = function(id) { return id in listingIDTable; }; self.filters = ko.observableArray(filters); var urlGroupBy = new GroupByModel('url', __('URL')); var groupBys = [ new GroupByModel('type', __('Type')), new GroupByModel('user_login', __('Username')), new GroupByModel('statusCode', __('HTTP Response Code')), new GroupByModel('action', __('Firewall Response'), 'enum', ['ok', 'throttled', 'lockedOut', 'blocked', 'blocked:waf']), new GroupByModel('ip', __('IP')), urlGroupBy ]; self.presetFiltersOptions = ko.observableArray([ new PresetFilterModel(__('All Hits'), "all", []), new PresetFilterModel(__('Humans'), "humans", [new ListingsFilterModel(self, 'type', 'human')]), new PresetFilterModel(__('Registered Users'), "users", [new ListingsFilterModel(self, 'userID', '0', '!=')]), new PresetFilterModel(__('Crawlers'), "crawlers", [new ListingsFilterModel(self, 'type', 'bot')]), new PresetFilterModel(__('Google Crawlers'), "google", [new ListingsFilterModel(self, 'isGoogle', '1')]), new PresetFilterModel(__('Pages Not Found'), "404s", [new ListingsFilterModel(self, 'statusCode', '404')]), new PresetFilterModel(__('Logins and Logouts'), "logins", [ new ListingsFilterModel(self, 'action', 'login', 'contains'), new ListingsFilterModel(self, 'action', 'logout', 'contains') ]), //new PresetFilterModel('Top Consumers', "top_consumers", [new ListingsFilterModel(self, 'statusCode', '200')], urlGroupBy), //new PresetFilterModel('Top 404s', "top_404s", [new ListingsFilterModel(self, 'statusCode', '404')], urlGroupBy), new PresetFilterModel(__('Locked Out'), "lockedOut", [new ListingsFilterModel(self, 'action', 'lockedOut')]), new PresetFilterModel(__('Blocked'), "blocked", [new ListingsFilterModel(self, 'action', 'blocked', 'contains')]), new PresetFilterModel(__('Blocked By Firewall'), "blocked:waf", [new ListingsFilterModel(self, 'action', 'blocked:waf')]) ]); self.showAdvancedFilters = ko.observable(false); self.showAdvancedFilters.subscribe(function(val) { if (val && self.filters().length == 0) { self.addFilter(); } }); self.presetFiltersOptionsText = function(item) { return item.text(); }; self.selectedPresetFilter = ko.observable(); self.selectedPresetFilter.subscribe(function(item) { var clonedFilters = ko.toJS(item.filters()); var newFilters = []; for (var i = 0; i < clonedFilters.length; i++) { newFilters.push(new ListingsFilterModel(self, clonedFilters[i].param, clonedFilters[i].value, clonedFilters[i].operator)); } self.filters(newFilters); self.groupBy(item.groupBy()); }); self.filters.subscribe(function() { self.checkQueryAndReloadListings(); }); self.addFilter = function() { self.filters.push(new ListingsFilterModel(self)); }; self.removeFilter = function(item) { self.filters.remove(item); }; var currentFilterQuery = ''; var getURLEncodedFilters = function() { var dataString = ''; ko.utils.arrayForEach(self.filters(), function(filter) { if (filter.getValue() !== false) { dataString += filter.urlEncoded() + '&'; } }); var groupBy = self.groupBy(); if (groupBy) { dataString += 'groupby=' + encodeURIComponent(groupBy.param()) + '&'; } var startDate = self.startDate(); if (startDate) { dataString += 'startDate=' + encodeURIComponent(startDate) + '&'; } var endDate = self.endDate(); if (endDate) { dataString += 'endDate=' + encodeURIComponent(endDate) + '&'; } if (dataString.length > 1) { return dataString.substring(0, dataString.length - 1); } return ''; }; self.filterGroupByOptions = ko.observableArray(groupBys); self.filterGroupByOptionsText = function(item) { return item.text() || item.param(); }; self.groupBy = ko.observable(); self.groupBy.subscribe(function() { self.checkQueryAndReloadListings(); }); self.startDate = ko.observable(); self.startDate.subscribe(function() { // console.log('start date change ' + self.startDate()); self.checkQueryAndReloadListings(); }); self.endDate = ko.observable(); self.endDate.subscribe(function() { // console.log('end date change ' + self.endDate()); self.checkQueryAndReloadListings(); }); /** * Pulls down fresh traffic data and resets the list. * * @param options */ self.checkQueryAndReloadListings = function(options) { if (currentFilterQuery !== getURLEncodedFilters()) { self.reloadListings(options); } }; self.reloadListings = function(options) { pullDownListings(options, function(listings) { var groupByKO = self.groupBy(); var groupBy = ''; if (groupByKO) { groupBy = groupByKO.param(); WFAD.mode = 'liveTraffic_paused'; } else { WFAD.mode = 'liveTraffic'; } var newListings = []; for (var i = 0; i < listings.length; i++) { newListings.push(new ListingModel(listings[i], groupBy)); } self.listings(newListings); }) }; /** * Used in the infinite scroll */ self.loadNextListings = function(callback) { var lastTimestamp = self.filters[0]; pullDownListings({ since: lastTimestamp, limit: LISTING_LIMIT, offset: self.listings().length }, function() { self.appendListings.apply(this, arguments); typeof callback === 'function' && callback.apply(this, arguments); }); }; self.getCurrentQueryString = function(options) { var queryOptions = { since: null, limit: LISTING_LIMIT, offset: 0 }; for (var prop in queryOptions) { if (queryOptions.hasOwnProperty(prop) && options && prop in options) { queryOptions[prop] = options[prop]; } } currentFilterQuery = getURLEncodedFilters(); var data = currentFilterQuery; for (prop in queryOptions) { if (queryOptions.hasOwnProperty(prop)) { var val = queryOptions[prop]; if (val === null || val === undefined) { val = ''; } data += '&' + encodeURIComponent(prop) + '=' + encodeURIComponent(val); } } return data; }; var pullDownListings = function(options, callback) { var data = self.getCurrentQueryString(options); WFAD.ajax('wordfence_loadLiveTraffic', data, function(response) { if (!response || !response.success) { return; } callback && callback(response.data, response); self.sql(response.sql); }); }; self.prependListings = function(listings, response) { for (var i = listings.length - 1; i >= 0; i--) { // Prevent duplicates if (self.hasListing(listings[i].id)) { continue; } var listing = new ListingModel(listings[i]); listing.highlighted(true); self.listings.unshift(listing); } //self.listings.sort(function(a, b) { // if (a.ctime() < b.ctime()) { // return 1; // } else if (a.ctime() > b.ctime()) { // return -1; // } // return 0; //}); }; self.appendListings = function(listings, response) { var highlight = 3; for (var i = 0; i < listings.length; i++) { // Prevent duplicates if (self.hasListing(listings[i].id)) { continue; } var listing = new ListingModel(listings[i]); listing.highlighted(highlight-- > 0); self.listings.push(listing); } //self.listings.sort(function(a, b) { // if (a.ctime() < b.ctime()) { // return 1; // } else if (a.ctime() > b.ctime()) { // return -1; // } // return 0; //}); }; self.whitelistWAFParamKey = function(path, paramKey, failedRules) { WFAD.ajax('wordfence_whitelistWAFParamKey', { path: path, paramKey: paramKey, failedRules: failedRules }, function(response) { }); }; self.trimIP = function(ip) { if (ip && ip.length > 16) { return ip.substring(0, 16) + "\u2026"; } return ip; }; $(window).on('wf-live-traffic-ip-blocked', function(e, ip) { ko.utils.arrayForEach(self.listings(), function(listing) { if (listing.IP() === ip) { listing.blocked(true); } }); }).on('wf-live-traffic-ip-unblocked', function(e, ip) { ko.utils.arrayForEach(self.listings(), function(listing) { if (listing.IP() === ip) { listing.blocked(false); } }); }); // For debuggering-a-ding self.sql = ko.observable(''); }; LiveTrafficViewModel.truncateText = function(text, maxLength) { maxLength = maxLength || 100; if (text && text.length > maxLength) { return text.substring(0, Math.round(maxLength)) + "\u2026"; // return text.substring(0, Math.round(maxLength / 2)) + " ... " + text.substring(text.length - Math.round(maxLength / 2)); } return text; }; var ListingModel = function(data, groupBy) { var self = this; self.id = ko.observable(0); self.ctime = ko.observable(0); self.IP = ko.observable(''); self.jsRun = ko.observable(0); self.statusCode = ko.observable(200); self.isGoogle = ko.observable(0); self.userID = ko.observable(0); self.URL = ko.observable(''); self.referer = ko.observable(''); self.UA = ko.observable(''); self.loc = ko.observable(); self.type = ko.observable(''); self.blocked = ko.observable(false); self.rangeBlocked = ko.observable(false); self.ipRangeID = ko.observable(-1); self.extReferer = ko.observable(); self.browser = ko.observable(); self.user = ko.observable(); self.hitCount = ko.observable(); self.username = ko.observable(''); // New fields/columns self.action = ko.observable(''); self.actionDescription = ko.observable(false); self.actionData = ko.observable(); self.highlighted = ko.observable(false); self.showDetails = ko.observable(false); self.toggleDetails = function() { self.showDetails(!self.showDetails()); }; //self.highlighted.subscribe(function(val) { // if (val) { // _classes += ' highlighted'; // self.cssClasses(_classes); // } else { // _classes.replace(/ highlighted(\s*|$)/, ' '); // self.cssClasses(_classes); // } //}); for (var prop in data) { if (data.hasOwnProperty(prop)) { if (prop === 'blocked' || prop === 'rangeBlocked') { data[prop] = !!data[prop]; } self[prop] !== undefined && self[prop](data[prop]); } } if (data['lastHit'] !== undefined) { self['ctime'](data['lastHit']); } self.timestamp = ko.pureComputed(function() { var date = new Date(self.ctime() * 1000); return date.toLocaleDateString() + ' ' + date.toLocaleTimeString(); }, self); // Use the same format as these update. self.timeAgo = ko.pureComputed(function() { var serverTime = WFAD.serverMicrotime; return $(WFAD.showTimestamp(this.ctime(), serverTime)).text(); }, self); self.displayURL = ko.pureComputed(function() { return LiveTrafficViewModel.truncateText(self.URL(), 105); }); self.displayURLShort = ko.pureComputed(function() { var a = document.createElement('a'); if (!self.URL()) { return ''; } a.href = self.URL(); if (a.host !== location.host) { return LiveTrafficViewModel.truncateText(self.URL(), 30); } var url = a.pathname + (typeof a.search === 'string' ? a.search : ''); return LiveTrafficViewModel.truncateText(url, 30); }); self.firewallAction = ko.pureComputed(function() { //Grouped by firewall action listing if (groupBy == 'action') { switch (self.action()) { case 'lockedOut': return __('Locked out from logging in'); case 'blocked:waf-always': return __('Blocked by the Wordfence Application Firewall and plugin settings'); case 'blocked:wordfence': return __('Blocked by Wordfence plugin settings'); case 'blocked:wfsnrepeat': case 'blocked:wfsn': return __('Blocked by the Wordfence Security Network'); case 'blocked:waf': return __('Blocked by the Wordfence Web Application Firewall'); case 'cbl:redirect': return __('Redirected by Country Blocking bypass URL'); default: return __('Blocked by Wordfence'); } } //Standard listing var desc = ''; switch (self.action()) { case 'lockedOut': return __('locked out from logging in'); case 'blocked:waf-always': case 'blocked:wordfence': case 'blocked:wfsnrepeat': desc = self.actionDescription(); if (desc && desc.toLowerCase().indexOf('block') === 0) { return 'b' + desc.substring(1); } return sprintf(__('blocked for %s'), desc); case 'blocked:wfsn': return __('blocked by the Wordfence Security Network'); case 'blocked:waf': var data = self.actionData(); if (typeof data === 'object') { var paramKey = WFAD.base64_decode(data.paramKey); var paramValue = WFAD.base64_decode(data.paramValue); // var category = data.category; var matches = paramKey.match(/([a-z0-9_]+\.[a-z0-9_]+)(?:\[(.+?)\](.*))?/i); desc = self.actionDescription(); if (matches) { switch (matches[1]) { case 'request.queryString': desc = sprintf(__('%s in query string: %s'), self.actionDescription(), matches[2] + '=' + LiveTrafficViewModel.truncateText(encodeURIComponent(paramValue))); break; case 'request.body': desc = sprintf(__('%s in POST body: %s'), self.actionDescription(), matches[2] + '=' + LiveTrafficViewModel.truncateText(encodeURIComponent(paramValue))); break; case 'request.cookie': desc = sprintf(__('%s in cookie: %s'), self.actionDescription(), matches[2] + '=' + LiveTrafficViewModel.truncateText(encodeURIComponent(paramValue))); break; case 'request.fileNames': desc = sprintf(__('%s in file: %s'), self.actionDescription(), matches[2] + '=' + LiveTrafficViewModel.truncateText(encodeURIComponent(paramValue))); break; } } if (desc) { return sprintf(__('blocked by firewall for %s'), desc); } if (data.failedRules == 'blocked') { return __('blocked by real-time IP blocklist'); } return __('blocked by firewall'); } return sprintf(__('blocked by firewall for %s'), self.actionDescription()); case 'cbl:redirect': desc = self.actionDescription(); return desc; } return desc; }); self.cssClasses = ko.pureComputed(function() { var classes = 'wf-live-traffic-hit-type'; if (self.statusCode() == 403 || self.statusCode() == 503) { classes += ' wfActionBlocked'; } if (self.statusCode() == 404) { classes += ' wf404'; } if (self.jsRun() == 1) { classes += ' wfHuman'; } if (self.actionData() && self.actionData().learningMode) { classes += ' wfWAFLearningMode'; } if (self.action() == 'loginFailValidUsername' || self.action() == 'loginFailInvalidUsername') { classes += ' wfFailedLogin'; } // if (self.highlighted()) { // classes += ' highlighted'; // } return classes; }); self.typeIconClass = ko.pureComputed(function() { var classes = 'wf-live-traffic-type-icon'; if (self.statusCode() == 403 || self.statusCode() == 503) { classes += ' wf-icon-blocked wf-ion-android-cancel'; } else if (self.statusCode() == 404 || self.action() == 'loginFailValidUsername' || self.action() == 'loginFailInvalidUsername') { classes += ' wf-icon-warning wf-ion-alert-circled'; } else if (self.jsRun() == 1) { classes += ' wf-icon-human wf-ion-ios-person'; } else { // classes += ' wf-ion-soup-can'; classes += ' wf-ion-bug'; } return classes; }); self.typeText = ko.pureComputed(function() { var type = ''; if (self.action() == 'loginFailValidUsername' || self.action() == 'loginFailInvalidUsername') { type = __('Failed Login'); } else if (self.statusCode() == 403 || self.statusCode() == 503) { type = __('Blocked'); } else if (self.statusCode() == 404) { type = __('404 Not Found'); } else if (self.statusCode() == 302) { type = __('Redirected'); } else if (self.jsRun() == 1) { type = __('Human'); } else { type = __('Bot'); } return sprintf(__('Type: %s'), type); }); function slideInDrawer() { overlayWrapper.fadeIn(400); overlay.css({ right: '-800px' }) .stop() .animate({ right: 0 }, 500); } self.showWhoisOverlay = function() { slideInDrawer(); overlayHeader.html($('#wfActEvent_' + self.id()).html()); overlayBody.html('').css('opacity', 0); WFAD.ajax('wordfence_whois', { val: self.IP() }, function(result) { var whoisHTML = WFAD.completeWhois(result, true); overlayBody.stop() .animate({ opacity: 1 }, 200) .html('

' + __('WHOIS LOOKUP') + '

' + whoisHTML); $(window).trigger('wf-live-traffic-overlay-bind', self); }); }; self.showRecentTraffic = function() { slideInDrawer(); overlayHeader.html($('#wfActEvent_' + self.id()).html()); overlayBody.html('').css('opacity', 0); WFAD.ajax('wordfence_recentTraffic', { ip: self.IP() }, function(result) { overlayBody.stop() .animate({ opacity: 1 }, 200) .html('

' + __('Recent Activity') + '

' + result.result); $(window).trigger('wf-live-traffic-overlay-bind', self); WFAD.avatarLookup(); }); }; /* Blocking functions */ self.unblockIP = function() { WFAD.unblockIP(self.IP(), function() { $(window).trigger('wf-live-traffic-ip-unblocked', self.IP()); }); }; self.unblockNetwork = function() { WFAD.unblockNetwork(self.ipRangeID()); }; self.blockIP = function() { WFAD.blockIP(self.IP(), __('Manual block by administrator'), function() { $(window).trigger('wf-live-traffic-ip-blocked', self.IP()); }); }; }; var ListingsFilterModel = function(viewModel, param, value, operator) { var self = this; self.viewModel = viewModel; self.param = ko.observable(''); self.value = ko.observable(''); self.operator = ko.observable(''); self.param(param); self.value(value); self.operator(operator || '='); var filterChanged = function() { self.viewModel && self.viewModel.checkQueryAndReloadListings && self.viewModel.checkQueryAndReloadListings(); }; self.param.subscribe(filterChanged); self.operator.subscribe(filterChanged); self.value.subscribe(function(value) { if (value instanceof FilterParamEnumOptionModel && value.operator()) { self.selectedFilterOperatorOptionValue(value.operator()); } filterChanged(); }); var equalsOperator = new FilterOperatorModel('='); var notEqualsOperator = new FilterOperatorModel('!=', '\u2260'); var containsOperator = new FilterOperatorModel('contains'); var matchOperator = new FilterOperatorModel('match'); self.filterOperatorOptions = ko.observableArray([ equalsOperator, notEqualsOperator, containsOperator, matchOperator ]); self.filterParamOptions = ko.observableArray([ new FilterParamModel('type', __('Type'), 'enum', [ new FilterParamEnumOptionModel('human', __('Human')), new FilterParamEnumOptionModel('bot', __('Bot')) ]), new FilterParamModel('user_login', __('Username')), new FilterParamModel('userID', __('User ID')), new FilterParamModel('isGoogle', __('Google Bot'), 'bool'), new FilterParamModel('ip', __('IP')), new FilterParamModel('ua', __('User Agent')), new FilterParamModel('referer', __('Referer')), new FilterParamModel('url', __('URL')), new FilterParamModel('statusCode', __('HTTP Response Code')), new FilterParamModel('action', __('Firewall Response'), 'enum', [ new FilterParamEnumOptionModel('', __('OK')), new FilterParamEnumOptionModel('throttled', __('Throttled')), new FilterParamEnumOptionModel('lockedOut', __('Locked Out')), new FilterParamEnumOptionModel('blocked', __('Blocked'), containsOperator), new FilterParamEnumOptionModel('blocked:waf', __('Blocked WAF')) ]), new FilterParamModel('action', __('Logins'), 'enum', [ new FilterParamEnumOptionModel('loginOK', __('Logged In')), new FilterParamEnumOptionModel('loginFail', __('Failed Login')), new FilterParamEnumOptionModel('loginFailInvalidUsername', __('Failed Login: Invalid Username')), new FilterParamEnumOptionModel('loginFailValidUsername', __('Failed Login: Valid Username')) ]), new FilterParamModel('action', __('Security Event')) ]); self.filterParamOptionsText = function(item) { return item.text() || item.param(); }; self.selectedFilterParamOptionValue = ko.observable(); self.selectedFilterParamOptionValue.subscribe(function(item) { self.param(item && item.param ? item.param() : ''); }); ko.utils.arrayForEach(self.filterParamOptions(), function(item) { if (self.param() == item.param()) { switch (item.type()) { case 'enum': // console.log(self.param(), item.param(), self.value(), values); switch (self.operator()) { case '=': ko.utils.arrayForEach(item.values(), function(enumOption) { if (enumOption.value() == self.value()) { self.selectedFilterParamOptionValue(item); } }); break; } break; default: self.selectedFilterParamOptionValue(item); break; } } }); self.filterOperatorOptionsText = function(item) { return item.text() || item.operator(); }; self.selectedFilterOperatorOptionValue = ko.observable(); self.selectedFilterOperatorOptionValue.subscribe(function(item) { self.operator(item.operator()); }); ko.utils.arrayForEach(self.filterOperatorOptions(), function(item) { if (self.operator() == item.operator()) { self.selectedFilterOperatorOptionValue(item); } }); self.getValue = function() { var value = self.value() instanceof FilterParamEnumOptionModel ? self.value().value() : self.value(); return (typeof value === 'string' || typeof value === 'number') ? value : false; }; self.urlEncoded = function() { var value = self.getValue(); return 'param[]=' + encodeURIComponent(self.param()) + '&value[]=' + encodeURIComponent(value) + '&operator[]=' + encodeURIComponent(self.operator()); }; }; var PresetFilterModel = function(text, value, filters, groupBy) { this.text = ko.observable(''); this.value = ko.observable(''); this.filters = ko.observableArray(filters); this.groupBy = ko.observable(groupBy); this.text(text); this.value(value); }; var FilterParamModel = function(param, text, type, values) { this.text = ko.observable(''); this.param = ko.observable(''); this.type = ko.observable(''); this.values = ko.observableArray(values); this.text(text); this.param(param); this.type(type || 'text'); this.optionsText = function(item) { if (item instanceof FilterParamEnumOptionModel) { return item.label() || item.value(); } return item; } }; var FilterParamEnumOptionModel = function(value, label, operator) { this.value = ko.observable(''); this.label = ko.observable(''); this.operator = ko.observable(''); this.value = ko.observable(value); this.label = ko.observable(label); this.operator = ko.observable(operator); this.toString = function() { return this.value(); } }; var FilterOperatorModel = function(operator, text) { this.text = ko.observable(''); this.operator = ko.observable(''); this.text(text); this.operator(operator); }; var GroupByModel = function(param, text) { this.text = ko.observable(''); this.param = ko.observable(''); this.text(text); this.param(param); }; ko.bindingHandlers.datetimepicker = { init: function(element, valueAccessor, allBindingsAccessor) { //initialize datepicker with some optional options var options = allBindingsAccessor().datepickerOptions || {}, $el = $(element); $el.datetimepicker(options); //handle the field changing by registering datepicker's changeDate event ko.utils.registerEventHandler(element, "changeDate", function() { var observable = valueAccessor(); observable($el.datetimepicker("getDate")); }); //handle disposal (if KO removes by the template binding) ko.utils.domNodeDisposal.addDisposeCallback(element, function() { $el.datetimepicker("destroy"); }); }, update: function(element, valueAccessor) { var value = ko.utils.unwrapObservable(valueAccessor()), $el = $(element); //handle date data coming via json from Microsoft if (String(value).indexOf('/Date(') == 0) { value = new Date(parseInt(value.replace(/\/Date\((.*?)\)\//gi, "$1"))); } var current = $el.datetimepicker("getDate"); if (value - current !== 0) { $el.datetimepicker("setDate", value); } } }; var overlayWrapper = null, overlay = null, overlayCloseButton = null, overlayHeader = null, overlayBody = null; $(function() { var liveTrafficWrapper = $('#wf-live-traffic'); $('#wf-lt-preset-filters').wfselect2({ templateSelection: function(value) { return $('' + __('Filter Traffic') + ': ' + value.text + ''); } }); overlayWrapper = $('#wf-live-traffic-util-overlay-wrapper').on('click', function(evt) { if (evt.target === this) { overlayCloseButton.trigger('click'); } }); overlay = overlayWrapper.find('.wf-live-traffic-util-overlay'); overlayCloseButton = overlayWrapper.find('.wf-live-traffic-util-overlay-close').on('click', function() { overlayWrapper.fadeOut(250); overlay .stop() .animate({ right: '-800px' }, 250); overlayHeader.html(''); overlayBody.html('').css('opacity', 0); $(window).trigger('wf-live-traffic-overlay-unbind'); }); overlayHeader = overlayWrapper.find('.wf-live-traffic-util-overlay-header'); overlayBody = overlayWrapper.find('.wf-live-traffic-util-overlay-body'); $([overlayHeader, overlayBody]).on('click', function() { return false; }); // liveTrafficWrapper.find('#wf-lt-advanced-filters select').wfselect2({ // // }); WFAD.wfLiveTraffic = new LiveTrafficViewModel(); ko.applyBindings(WFAD.wfLiveTraffic, liveTrafficWrapper.get(0)); liveTrafficWrapper.find('form').submit(); WFAD.mode = 'liveTraffic'; var legendWrapper = $('#wf-live-traffic-legend-wrapper'); var placeholder = $('#wf-live-traffic-legend-placeholder'); var legend = $('#wf-live-traffic-legend'); var adminBar = $('#wpadminbar'); var liveTrafficListings = $('#wf-lt-listings'); var groupedListings = $('div#wf-live-traffic-group-by'); var hasScrolled = false; var loadingListings = false; $(window).on('scroll', function() { var win = $(this); var needsSticky = (WFAD.isSmallScreen ? (legendWrapper.offset().top < win.scrollTop() + 10) : (legendWrapper.offset().top < win.scrollTop() + adminBar.outerHeight() + 10)); if (needsSticky) { var legendWidth = legend.width(); var legendHeight = legend.height(); legend.addClass('sticky'); legend.css('width', legendWidth); legend.css('height', legendHeight); placeholder.addClass('sticky'); placeholder.css('width', legendWidth); placeholder.css('height', legendHeight); } else { legend.removeClass('sticky'); legend.css('width', 'auto'); legend.css('height', 'auto'); placeholder.removeClass('sticky'); } var firstLTRow = liveTrafficListings.children().filter(':visible').first(); if ((firstLTRow.length > 0 && firstLTRow.offset().top + firstLTRow.height() < win.scrollTop() + adminBar.outerHeight() + 20) || (groupedListings.filter(':visible').length > 0)) { if (WFAD.mode != 'liveTraffic_paused') { WFAD.mode = 'liveTraffic_paused'; } } else { if (WFAD.mode != 'liveTraffic') { WFAD.mode = 'liveTraffic'; } } // console.log(win.scrollTop() + window.innerHeight, liveTrafficWrapper.outerHeight() + liveTrafficWrapper.offset().top); var currentScrollBottom = win.scrollTop() + window.innerHeight; var scrollThreshold = liveTrafficWrapper.outerHeight() + liveTrafficWrapper.offset().top; if (hasScrolled && !loadingListings && currentScrollBottom >= scrollThreshold) { // console.log('infinite scroll'); loadingListings = true; hasScrolled = false; WFAD.wfLiveTraffic.loadNextListings(function() { loadingListings = false; WFAD.reverseLookupIPs(); WFAD.avatarLookup(); }); } else if (currentScrollBottom < scrollThreshold) { hasScrolled = true; // console.log('no infinite scroll'); } }) .on('wf-live-traffic-overlay-bind', function(e, item) { ko.applyBindings(item, overlayHeader.get(0)); }) .on('wf-live-traffic-overlay-unbind', function(e, item) { ko.cleanNode(overlayHeader.get(0)); }); $([liveTrafficWrapper.find('.wf-filtered-traffic'), overlayWrapper]).tooltip({ tooltipClass: "wf-tooltip", track: true }); }); }) (jQuery);