(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('