feat(cloudron): add tirreno package artifacts
- Add CloudronStack/output/CloudronPackages-Artifacts/tirreno/ directory and its contents - Includes package manifest, Dockerfile, source code, documentation, and build artifacts - Add tirreno-1761840148.tar.gz as a build artifact - Add tirreno-cloudron-package-1761841304.tar.gz as the Cloudron package - Include all necessary files for the tirreno Cloudron package This adds the complete tirreno Cloudron package artifacts to the repository.
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
import {handleAjaxError} from './utils/ErrorHandler.js?v=2';
|
||||
|
||||
export class BlacklistGridActionButtons {
|
||||
|
||||
constructor(tableId) {
|
||||
this.tableId = tableId;
|
||||
const onTableLoaded = this.onTableLoaded.bind(this);
|
||||
window.addEventListener('tableLoaded', onTableLoaded, false);
|
||||
}
|
||||
|
||||
onTableLoaded(e) {
|
||||
const tableId = e.detail.tableId;
|
||||
const buttons = document.querySelectorAll(`#${tableId} button`);
|
||||
|
||||
const onButtonClick = this.onButtonClick.bind(this);
|
||||
buttons.forEach(button => button.addEventListener('click', onButtonClick, false));
|
||||
}
|
||||
|
||||
onButtonClick(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const me = this;
|
||||
const target = e.target;
|
||||
const url = '/admin/removeBlacklisted';
|
||||
|
||||
const data = {
|
||||
id: target.dataset.itemId,
|
||||
type: target.dataset.itemType,
|
||||
token: me.csrf
|
||||
};
|
||||
|
||||
target.classList.add('is-loading');
|
||||
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: url,
|
||||
data: data,
|
||||
scope: me,
|
||||
target: target,
|
||||
success: me.onSuccess,
|
||||
error: handleAjaxError,
|
||||
dataType: 'json'
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
onSuccess(dta, status) {
|
||||
if ('success' !== status) {
|
||||
return;
|
||||
}
|
||||
|
||||
const me = this.scope;
|
||||
|
||||
const target = this.target;
|
||||
const tableRow = target.closest('tr');
|
||||
|
||||
target.classList.remove('is-loading');
|
||||
target.setAttribute('disabled', '');
|
||||
target.textContent = 'Removed';
|
||||
|
||||
const card = target.closest('.card');
|
||||
const span = card.querySelector('.card-header-title span');
|
||||
|
||||
let total = parseInt(span.innerHTML, 10);
|
||||
|
||||
if (total > 0) {
|
||||
total -= 1;
|
||||
}
|
||||
|
||||
span.textContent = total;
|
||||
|
||||
if (tableRow) {
|
||||
const dataTable = $(`#${me.tableId}`).DataTable();
|
||||
dataTable.row(tableRow).remove().draw(false);
|
||||
}
|
||||
}
|
||||
|
||||
get csrf() {
|
||||
return document.querySelector('meta[name="csrf-token"]').getAttribute('content');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
import {Loader} from './Loader.js?v=2';
|
||||
import {handleAjaxError} from './utils/ErrorHandler.js?v=2';
|
||||
import {fireEvent} from './utils/Event.js?v=2';
|
||||
|
||||
export class DashboardTile {
|
||||
|
||||
constructor(tilesParams) {
|
||||
const me = this;
|
||||
this.config = tilesParams;
|
||||
|
||||
this.loader = new Loader();
|
||||
|
||||
if (!this.config.sequential) {
|
||||
const onDateFilterChanged = this.onDateFilterChanged.bind(this);
|
||||
window.addEventListener('dateFilterChanged', onDateFilterChanged, false);
|
||||
|
||||
this.initLoad();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
startLoader() {
|
||||
const el = document.querySelector(`.${this.config.mode} .title`);
|
||||
this.loader.start(el);
|
||||
}
|
||||
|
||||
loadData() {
|
||||
const me = this;
|
||||
const token = document.head.querySelector('[name=\'csrf-token\'][content]').content;
|
||||
|
||||
let params = this.config.getParams().dateRange;
|
||||
params.mode = this.config.mode;
|
||||
|
||||
if (!this.config.sequential) {
|
||||
this.startLoader();
|
||||
}
|
||||
|
||||
fireEvent('dateFilterChangedCaught');
|
||||
|
||||
$.ajax({
|
||||
url: `/admin/loadDashboardStat?token=${token}`,
|
||||
type: 'get',
|
||||
scope: me,
|
||||
data: params,
|
||||
success: me.onLoad,
|
||||
error: handleAjaxError,
|
||||
complete: function() {
|
||||
fireEvent('dateFilterChangedCompleted');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
onLoad(data, status) {
|
||||
if ('success' == status) {
|
||||
this.scope.loader.stop();
|
||||
|
||||
const frag = document.createDocumentFragment();
|
||||
|
||||
const period = document.createElement('p');
|
||||
if (this.scope.config.mode === 'totalUsersForReview') {
|
||||
period.className = 'periodTotalYellow';
|
||||
} else if (this.scope.config.mode === 'totalBlockedUsers') {
|
||||
period.className = 'periodTotalRed';
|
||||
} else {
|
||||
period.className = 'periodTotal';
|
||||
}
|
||||
period.textContent = data.total;
|
||||
|
||||
const total = document.createElement('p');
|
||||
total.className = 'allTimeTotal';
|
||||
total.textContent = data.allTimeTotal;
|
||||
|
||||
frag.appendChild(period);
|
||||
frag.appendChild(total);
|
||||
|
||||
const el = document.querySelector(`.${this.scope.config.mode} .title`);
|
||||
el.replaceChildren(frag);
|
||||
}
|
||||
}
|
||||
|
||||
onDateFilterChanged() {
|
||||
this.loadData();
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,261 @@
|
||||
import {fireEvent} from './utils/Event.js?v=2';
|
||||
import {
|
||||
formatStringTime,
|
||||
addDays,
|
||||
addHours,
|
||||
} from './utils/Date.js?v=2';
|
||||
import {debounce} from './utils/Functions.js?v=2';
|
||||
import {DAYS_IN_RANGE} from './utils/Constants.js?v=2';
|
||||
|
||||
export class DatesFilter {
|
||||
constructor(sequential=false) {
|
||||
this.setupXhrPool();
|
||||
this.offset = (this.offsetField) ? parseInt(this.offsetField.value, 10) : 0;
|
||||
this.ajaxCount = 0;
|
||||
this.sequential = sequential;
|
||||
if (this.isDateFilterUnavailable) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.dateToLocalField !== null && this.dateFromLocalField !== null) {
|
||||
// visible fields change should set invisible fields
|
||||
this.onTimestampFieldChange = this.onTimestampFieldChange.bind(this);
|
||||
const debouncedOnTimestampFieldChange = debounce(this.onTimestampFieldChange);
|
||||
|
||||
this.dateToLocalField.addEventListener('change', debouncedOnTimestampFieldChange, false);
|
||||
this.dateFromLocalField.addEventListener('change', debouncedOnTimestampFieldChange, false);
|
||||
|
||||
this.setDefaultLocalDates();
|
||||
this.setDefaultDates();
|
||||
} else if (!this.dateFromField.value && !this.dateToField.value) {
|
||||
this.setDefaultDates();
|
||||
}
|
||||
|
||||
const onDateFilterChange = this.onDateFilterChange.bind(this);
|
||||
|
||||
this.dateToField.addEventListener('change', onDateFilterChange, false);
|
||||
this.dateFromField.addEventListener('change', onDateFilterChange, false);
|
||||
|
||||
const onIntervalLinkClick = this.onIntervalLinkClick.bind(this);
|
||||
this.intervalLinks.forEach(item => item.addEventListener('click', onIntervalLinkClick, false));
|
||||
|
||||
const onDateFilterChangedCaught = this.onDateFilterChangedCaught.bind(this);
|
||||
window.addEventListener('dateFilterChangedCaught', onDateFilterChangedCaught, false);
|
||||
|
||||
const onDateFilterChangedCompleted = this.onDateFilterChangedCompleted.bind(this);
|
||||
window.addEventListener('dateFilterChangedCompleted', onDateFilterChangedCompleted, false);
|
||||
|
||||
const onSequentialLoadCompleted = this.onSequentialLoadCompleted.bind(this);
|
||||
window.addEventListener('sequentialLoadCompleted', onSequentialLoadCompleted, false);
|
||||
}
|
||||
|
||||
setupXhrPool() {
|
||||
// https://gist.github.com/msankhala/3fa2844c1fbad1f4c0185a8e3ef09aed
|
||||
// Stop all ajax request by http://tjrus.com/blog/stop-all-active-ajax-requests
|
||||
$.xhrPool = []; // array of uncompleted requests
|
||||
$.xhrPool.abortAll = function() { // our abort function
|
||||
$(this).each((idx, jqXHR) => {
|
||||
jqXHR.abort();
|
||||
});
|
||||
$.xhrPool.length = 0;
|
||||
};
|
||||
|
||||
$.ajaxSetup({
|
||||
beforeSend: function(jqXHR) { // before jQuery send the request we will push it to our array
|
||||
$.xhrPool.push(jqXHR);
|
||||
},
|
||||
complete: function(jqXHR) { // when some of the requests completed it will splice from the array
|
||||
const index = $.xhrPool.indexOf(jqXHR);
|
||||
if (index > -1) {
|
||||
$.xhrPool.splice(index, 1);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
setDefaultDates() {
|
||||
this.setDateRangeFromNow(DAYS_IN_RANGE * 24);
|
||||
}
|
||||
|
||||
setDefaultLocalDates() {
|
||||
let dateTo = new Date();
|
||||
dateTo = new Date(dateTo.getTime() + (dateTo.getTimezoneOffset() * 60 + this.offset) * 1000); // now time in op tz
|
||||
|
||||
const dateFrom = addDays(dateTo, -DAYS_IN_RANGE); // dateFrom in op tz
|
||||
dateFrom.setHours(24, 0, 0, 0);
|
||||
|
||||
this.dateToLocalField.value = formatStringTime(dateTo);
|
||||
this.dateFromLocalField.value = formatStringTime(dateFrom);
|
||||
}
|
||||
|
||||
onTimestampFieldChange(e) {
|
||||
// get value (with offset)
|
||||
// set normal input exluding offset
|
||||
e.preventDefault();
|
||||
|
||||
const input = e.target;
|
||||
|
||||
$.xhrPool.abortAll();
|
||||
|
||||
const value = input.value;
|
||||
const name = input.name;
|
||||
|
||||
let target = null;
|
||||
|
||||
if (name === 'date_from_local') {
|
||||
target = this.dateFromField;
|
||||
} else if (name === 'date_to_local') {
|
||||
target = this.dateToField;
|
||||
}
|
||||
|
||||
let dt = new Date(value);
|
||||
dt = new Date(dt.getTime() - this.offset * 1000); // shift fom op tz to utc
|
||||
|
||||
target.value = formatStringTime(dt);
|
||||
|
||||
this.onDateFilterChange(e);
|
||||
return false;
|
||||
}
|
||||
|
||||
onDateFilterChange() {
|
||||
$.xhrPool.abortAll();
|
||||
fireEvent('dateFilterChanged');
|
||||
}
|
||||
|
||||
getValue() {
|
||||
const data = {
|
||||
dateTo: null,
|
||||
dateFrom: null
|
||||
};
|
||||
|
||||
if (this.isDateFilterUnavailable) {
|
||||
return data;
|
||||
}
|
||||
|
||||
data['dateTo'] = this.dateToField.value;
|
||||
data['dateFrom'] = this.dateFromField.value;
|
||||
|
||||
/*const rangeWasChanged = (1 == this.dateFromField.dataset.changed) || (1 == this.dateToField.dataset.changed);
|
||||
if (rangeWasChanged) {
|
||||
data['keepDates'] = 1;
|
||||
}*/
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
onIntervalLinkClick(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const link = e.target;
|
||||
if (link.classList.contains('active') || link.classList.contains('blocked')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.updateDisabled(true);
|
||||
|
||||
$.xhrPool.abortAll();
|
||||
|
||||
const value = parseInt(link.dataset.value, 10) || 0;
|
||||
|
||||
if (value === 0) {
|
||||
this.clearDateRange();
|
||||
} else {
|
||||
this.setDateRangeFromNow(value);
|
||||
}
|
||||
|
||||
this.intervalLinks.forEach(item => item.classList.remove('active'));
|
||||
link.classList.add('active');
|
||||
|
||||
this.onDateFilterChange();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
updateDisabled(disabled) {
|
||||
let m = disabled ? 'true' : 'false';
|
||||
this.intervalLinks.forEach(item => {
|
||||
if (disabled) {
|
||||
if (!item.classList.contains('active')) {
|
||||
item.setAttribute('tabindex', '-1');
|
||||
item.classList.add('blocked');
|
||||
item.removeAttribute('href');
|
||||
}
|
||||
} else {
|
||||
item.setAttribute('tabindex', '0');
|
||||
item.classList.remove('blocked');
|
||||
item.setAttribute('href', 'javascript:void(0);');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// with op tz and utc shift for calculation request
|
||||
setDateRangeFromNow(hoursDiff) {
|
||||
let dateTo = new Date();
|
||||
dateTo = new Date(dateTo.getTime() + (dateTo.getTimezoneOffset() * 60 + this.offset) * 1000); // now time in op tz
|
||||
let dateFrom = addHours(dateTo, -hoursDiff); // dateFrom in op tz
|
||||
// floor to not miss data in group
|
||||
if (hoursDiff < 24 && hoursDiff > -24) {
|
||||
dateFrom.setMinutes(0, 0, 0);
|
||||
} else {
|
||||
dateFrom.setHours(24, 0, 0, 0);
|
||||
}
|
||||
|
||||
dateTo = new Date(dateTo.getTime() - (this.offset * 1000)); // dateTo at utc
|
||||
dateFrom = new Date(dateFrom.getTime() - (this.offset * 1000)); // dateFrom at utc
|
||||
|
||||
this.dateToField.value = formatStringTime(dateTo);
|
||||
this.dateFromField.value = formatStringTime(dateFrom);
|
||||
}
|
||||
|
||||
clearDateRange() {
|
||||
this.dateToField.value = null;
|
||||
this.dateFromField.value = null;
|
||||
}
|
||||
|
||||
onDateFilterChangedCaught() {
|
||||
this.ajaxCount++;
|
||||
}
|
||||
|
||||
onDateFilterChangedCompleted() {
|
||||
this.ajaxCount--;
|
||||
if (this.ajaxCount <= 0 && !this.sequential) {
|
||||
this.updateDisabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
onSequentialLoadCompleted() {
|
||||
this.updateDisabled(false);
|
||||
}
|
||||
|
||||
get isDateFilterUnavailable() {
|
||||
return this.dateFromField === null || this.dateToField === null;
|
||||
}
|
||||
|
||||
get intervalLinks() {
|
||||
return this.navbar.querySelectorAll('a');
|
||||
}
|
||||
|
||||
get navbar() {
|
||||
return document.querySelector('nav.filtersForm.daterange');
|
||||
}
|
||||
|
||||
get offsetField() {
|
||||
return document.querySelector('input[name="offset"]');
|
||||
}
|
||||
|
||||
get dateToField() {
|
||||
return document.querySelector('input[name="date_to"]');
|
||||
}
|
||||
|
||||
get dateFromField() {
|
||||
return document.querySelector('input[name="date_from"]');
|
||||
}
|
||||
|
||||
get dateToLocalField() {
|
||||
return document.querySelector('input[name="date_to_local"]');
|
||||
}
|
||||
|
||||
get dateFromLocalField() {
|
||||
return document.querySelector('input[name="date_from_local"]');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
import {fireEvent} from './utils/Event.js?v=2';
|
||||
|
||||
export class DeleteAccountPopUp {
|
||||
|
||||
constructor() {
|
||||
|
||||
const onDeleteAccountButtonClicked = this.onDeleteAccountButtonClicked.bind(this);
|
||||
this.closeAccountButton.addEventListener('click', onDeleteAccountButtonClicked, false);
|
||||
|
||||
const onConfirmDeleteAccountButton = this.onConfirmDeleteAccountButton.bind(this);
|
||||
this.confirmButton.addEventListener('click', onConfirmDeleteAccountButton, false);
|
||||
|
||||
const onKeydown = this.onKeydown.bind(this);
|
||||
window.addEventListener('keydown', onKeydown, false);
|
||||
|
||||
const onCloseButtonClick = this.onCloseButtonClick.bind(this);
|
||||
this.closePopUpButton.addEventListener('click', onCloseButtonClick, false);
|
||||
}
|
||||
|
||||
//https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key
|
||||
onKeydown(e) {
|
||||
if (e.defaultPrevented) {
|
||||
return; // Do nothing if the event was already processed
|
||||
}
|
||||
switch (e.key) {
|
||||
case 'Esc': // IE/Edge specific value
|
||||
case 'Escape':
|
||||
this.close();
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
// Cancel the default action to avoid it being handled twice
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
onConfirmDeleteAccountButton(e) {
|
||||
e.preventDefault();
|
||||
this.accountForm.submit();
|
||||
|
||||
this.card.classList.add('is-hidden');
|
||||
this.contentDiv.classList.add('is-hidden');
|
||||
|
||||
}
|
||||
|
||||
onDeleteAccountButtonClicked(e) {
|
||||
e.preventDefault();
|
||||
|
||||
// close other panels
|
||||
const card = document.querySelector('.details-card#enrich-all-popup');
|
||||
if (card && !card.classList.contains('is-hidden')) {
|
||||
fireEvent('enrichAllPopUpClosed');
|
||||
card.classList.add('is-hidden');
|
||||
}
|
||||
|
||||
this.card.classList.remove('is-hidden');
|
||||
this.contentDiv.classList.remove('is-hidden');
|
||||
}
|
||||
|
||||
onCloseButtonClick(e) {
|
||||
e.preventDefault();
|
||||
this.close();
|
||||
}
|
||||
|
||||
close() {
|
||||
fireEvent('closeAccountPopUpClosed');
|
||||
this.card.classList.add('is-hidden');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
get contentDiv() {
|
||||
return this.card.querySelector('div.content');
|
||||
}
|
||||
|
||||
get card() {
|
||||
return document.querySelector('.details-card#close-account-popup');
|
||||
}
|
||||
|
||||
get closePopUpButton() {
|
||||
return this.card.querySelector('.delete');
|
||||
}
|
||||
|
||||
get accountForm() {
|
||||
return document.getElementById('close-account-form');
|
||||
}
|
||||
|
||||
get confirmButton() {
|
||||
return document.getElementById('confirm-close-account-button');
|
||||
}
|
||||
|
||||
get closeAccountButton() {
|
||||
return document.getElementById('close-account-btn');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
import {Loader} from './Loader.js?v=2';
|
||||
import {fireEvent} from './utils/Event.js?v=2';
|
||||
import {handleAjaxError} from './utils/ErrorHandler.js?v=2';
|
||||
import {renderEnrichmentCalculation} from './DataRenderers.js?v=2';
|
||||
|
||||
export class EnrichAllPopUp {
|
||||
|
||||
constructor() {
|
||||
this.loader = new Loader();
|
||||
|
||||
const onEnrichAllButtonClicked = this.onEnrichAllButtonClicked.bind(this);
|
||||
this.enrichAllButton.addEventListener('click', onEnrichAllButtonClicked, false);
|
||||
|
||||
const onConfirmEnrichAllButton = this.onConfirmEnrichAllButton.bind(this);
|
||||
this.confirmButton.addEventListener('click', onConfirmEnrichAllButton, false);
|
||||
|
||||
const onKeydown = this.onKeydown.bind(this);
|
||||
window.addEventListener('keydown', onKeydown, false);
|
||||
|
||||
const onCloseButtonClick = this.onCloseButtonClick.bind(this);
|
||||
this.closePopUpButton.addEventListener('click', onCloseButtonClick, false);
|
||||
}
|
||||
|
||||
//https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key
|
||||
onKeydown(e) {
|
||||
if (e.defaultPrevented) {
|
||||
return; // Do nothing if the event was already processed
|
||||
}
|
||||
switch (e.key) {
|
||||
case 'Esc': // IE/Edge specific value
|
||||
case 'Escape':
|
||||
this.close();
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
// Cancel the default action to avoid it being handled twice
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
onConfirmEnrichAllButton(e) {
|
||||
e.preventDefault();
|
||||
this.accountForm.submit();
|
||||
|
||||
this.card.classList.add('is-hidden');
|
||||
this.contentDiv.classList.add('is-hidden');
|
||||
|
||||
}
|
||||
|
||||
onEnrichAllButtonClicked(e) {
|
||||
e.preventDefault();
|
||||
|
||||
// close other panels
|
||||
const card = document.querySelector('.details-card#close-account-popup');
|
||||
if (card && !card.classList.contains('is-hidden')) {
|
||||
fireEvent('closeAccountPopUpClosed');
|
||||
card.classList.add('is-hidden');
|
||||
}
|
||||
|
||||
// call ajax
|
||||
this.loadData();
|
||||
}
|
||||
|
||||
loadData(id) {
|
||||
this.contentDiv.classList.add('is-hidden');
|
||||
this.loaderDiv.classList.remove('is-hidden');
|
||||
this.card.classList.remove('is-hidden');
|
||||
|
||||
const el = this.loaderDiv;
|
||||
this.loader.start(el);
|
||||
|
||||
const onDetailsLoaded = this.onDetailsLoaded.bind(this);
|
||||
const token = document.head.querySelector('[name=\'csrf-token\'][content]').content;
|
||||
|
||||
$.ajax({
|
||||
url: '/admin/enrichmentDetails',
|
||||
type: 'get',
|
||||
data: {token: token},
|
||||
success: onDetailsLoaded,
|
||||
error: handleAjaxError,
|
||||
});
|
||||
}
|
||||
|
||||
onDetailsLoaded(data, status) {
|
||||
if ('success' !== status || 0 === data.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
data = this.proceedData(data);
|
||||
|
||||
this.loader.stop();
|
||||
this.contentDiv.classList.remove('is-hidden');
|
||||
this.loaderDiv.classList.add('is-hidden');
|
||||
|
||||
let span = null;
|
||||
//todo: foreach and arrow fn ?
|
||||
for (const key in data) {
|
||||
span = this.card.querySelector(`#details_${key}`);
|
||||
if (span) {
|
||||
if (data[key] instanceof Node) {
|
||||
span.replaceChildren(data[key]);
|
||||
} else {
|
||||
span.innerHTML = data[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
proceedData(data) {
|
||||
data.calculation = renderEnrichmentCalculation(data);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
onCloseButtonClick(e) {
|
||||
e.preventDefault();
|
||||
this.close();
|
||||
}
|
||||
|
||||
close() {
|
||||
fireEvent('enrichAllPopUpClosed');
|
||||
this.card.classList.add('is-hidden');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
get loaderDiv() {
|
||||
return this.card.querySelector('div.text-loader');
|
||||
}
|
||||
|
||||
get contentDiv() {
|
||||
return this.card.querySelector('div.content');
|
||||
}
|
||||
|
||||
get card() {
|
||||
return document.querySelector('.details-card#enrich-all-popup');
|
||||
}
|
||||
|
||||
get closePopUpButton() {
|
||||
return this.card.querySelector('.delete');
|
||||
}
|
||||
|
||||
get accountForm() {
|
||||
return document.getElementById('enrich-all-form');
|
||||
}
|
||||
|
||||
get confirmButton() {
|
||||
return document.getElementById('confirm-enrich-all-button');
|
||||
}
|
||||
|
||||
get enrichAllButton() {
|
||||
return document.getElementById('enrich-all-btn');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
export class Loader {
|
||||
|
||||
constructor() {
|
||||
this.symbols = [
|
||||
this.el('⣾'),
|
||||
this.el('⣷'),
|
||||
this.el('⣯'),
|
||||
this.el('⣟'),
|
||||
this.el('⡿'),
|
||||
this.el('⢿'),
|
||||
this.el('⣻'),
|
||||
this.el('⣽'),
|
||||
];
|
||||
}
|
||||
|
||||
start(loaderEl) {
|
||||
this.loaderEl = loaderEl;
|
||||
|
||||
let me = this;
|
||||
let counter = 0;
|
||||
|
||||
this.loaderEl.classList.add('loading');
|
||||
this.loaderEl.classList.remove('loaded');
|
||||
|
||||
let timerId = setInterval(() => {
|
||||
if (me.loaderEl.classList.contains('loaded')) {
|
||||
clearInterval(timerId);
|
||||
return;
|
||||
}
|
||||
|
||||
let symbol = me.symbols[counter % me.symbols.length];
|
||||
|
||||
me.loaderEl.replaceChildren(symbol);
|
||||
|
||||
counter++;
|
||||
}, 85);
|
||||
}
|
||||
|
||||
stop() {
|
||||
this.loaderEl.classList.add('loaded');
|
||||
this.loaderEl.classList.remove('loading');
|
||||
}
|
||||
|
||||
el(c) {
|
||||
const node = document.createElement('p');
|
||||
node.textContent = c;
|
||||
|
||||
return node;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,318 @@
|
||||
import {
|
||||
renderDefaultIfEmptyElement,
|
||||
renderBoolean,
|
||||
renderDate,
|
||||
renderCountryIso,
|
||||
renderHttpCode,
|
||||
renderPhoneType,
|
||||
renderPhoneCarrierName,
|
||||
renderAsn,
|
||||
} from '../parts/DataRenderers.js?v=2';
|
||||
|
||||
export class ManualCheckItems {
|
||||
|
||||
constructor() {
|
||||
const table = document.querySelector('.events-card.is-hidden');
|
||||
|
||||
if (!table) return;
|
||||
|
||||
table.classList.remove('is-hidden');
|
||||
|
||||
const itemType = table.dataset.itemType;
|
||||
|
||||
if ('ip' == itemType) {
|
||||
this.enrichIpDetails();
|
||||
}
|
||||
|
||||
if ('email' == itemType) {
|
||||
this.enrichEmailDetails();
|
||||
}
|
||||
|
||||
if ('domain' == itemType) {
|
||||
this.enrichDomainDetails();
|
||||
}
|
||||
|
||||
if ('phone' == itemType) {
|
||||
this.enrichPhoneDetails();
|
||||
}
|
||||
}
|
||||
|
||||
enrichPhoneDetails() {
|
||||
let item = null;
|
||||
|
||||
item = 'iso_country_code';
|
||||
this.renderCountryIso(item);
|
||||
|
||||
item = 'type';
|
||||
this.renderPhoneType(item);
|
||||
|
||||
item = 'invalid';
|
||||
this.renderBoolean(item);
|
||||
|
||||
item = 'profiles';
|
||||
this.renderProfiles(item);
|
||||
|
||||
item = 'carrier_name';
|
||||
this.renderPhoneCarrierName(item);
|
||||
}
|
||||
|
||||
enrichDomainDetails() {
|
||||
let item = null;
|
||||
|
||||
item = 'blockdomains';
|
||||
this.renderBoolean(item);
|
||||
|
||||
item = 'disposable_domains';
|
||||
this.renderBoolean(item);
|
||||
|
||||
item = 'free_email_provider';
|
||||
this.renderBoolean(item);
|
||||
|
||||
item = 'geo_ip';
|
||||
this.renderCountryIso(item);
|
||||
|
||||
item = 'geo_html';
|
||||
this.renderCountryIso(item);
|
||||
|
||||
item = 'web_server';
|
||||
this.renderDefaultIfEmptyElement(item);
|
||||
|
||||
item = 'hostname';
|
||||
this.renderDefaultIfEmptyElement(item);
|
||||
|
||||
item = 'emails';
|
||||
this.renderDefaultIfEmptyElement(item);
|
||||
|
||||
item = 'phone';
|
||||
this.renderDefaultIfEmptyElement(item);
|
||||
|
||||
item = 'discovery_date';
|
||||
this.renderDate(item);
|
||||
|
||||
item = 'creation_date';
|
||||
this.renderDate(item);
|
||||
|
||||
item = 'expiration_date';
|
||||
this.renderDate(item);
|
||||
|
||||
item = 'mx_record';
|
||||
this.renderBoolean(item);
|
||||
|
||||
item = 'return_code';
|
||||
this.renderHttpCode(item);
|
||||
|
||||
item = 'disabled';
|
||||
this.renderBoolean(item);
|
||||
|
||||
item = 'closest_snapshot';
|
||||
this.renderDate(item);
|
||||
}
|
||||
|
||||
enrichIpDetails() {
|
||||
let item = null;
|
||||
let value = null;
|
||||
|
||||
item = 'country';
|
||||
this.renderCountryIso(item);
|
||||
|
||||
item = 'asn';
|
||||
value = this.getItem(item);
|
||||
value = {asn: value};
|
||||
value = renderAsn(value);
|
||||
this.setItem(item, value);
|
||||
|
||||
item = 'hosting';
|
||||
this.renderBoolean(item);
|
||||
|
||||
item = 'vpn';
|
||||
this.renderBoolean(item);
|
||||
|
||||
item = 'tor';
|
||||
this.renderBoolean(item);
|
||||
|
||||
item = 'relay';
|
||||
this.renderBoolean(item);
|
||||
|
||||
item = 'starlink';
|
||||
this.renderBoolean(item);
|
||||
|
||||
item = 'description';
|
||||
this.renderDefaultIfEmptyElement(item);
|
||||
|
||||
item = 'blocklist';
|
||||
this.renderBoolean(item);
|
||||
|
||||
item = 'domains_count';
|
||||
value = this.getItem(item);
|
||||
if (value) {
|
||||
value = JSON.parse(value);
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
value = value.length;
|
||||
} else {
|
||||
value = parseInt(value, 10);
|
||||
}
|
||||
|
||||
if (isNaN(value)) {
|
||||
value = renderDefaultIfEmptyElement(value);
|
||||
} else {
|
||||
value = !!value;
|
||||
value = renderBoolean(value);
|
||||
}
|
||||
|
||||
} else {
|
||||
value = renderDefaultIfEmptyElement(value);
|
||||
}
|
||||
this.setItem(item, value);
|
||||
}
|
||||
|
||||
enrichEmailDetails() {
|
||||
let item = null;
|
||||
|
||||
item = 'blockemails';
|
||||
this.renderBoolean(item);
|
||||
|
||||
item = 'data_breach';
|
||||
this.renderDataBreach(item);
|
||||
|
||||
item = 'earliest_breach';
|
||||
this.renderDate(item);
|
||||
|
||||
item = 'domain_contact_email';
|
||||
this.renderBoolean(item);
|
||||
|
||||
item = 'profiles';
|
||||
this.renderProfiles(item);
|
||||
}
|
||||
|
||||
renderDataBreach(itemId) {
|
||||
let value;
|
||||
|
||||
value = this.getItem(itemId);
|
||||
|
||||
if (null === value) {
|
||||
value = renderDefaultIfEmptyElement(value);
|
||||
} else {
|
||||
//Revert databreach to "No databreach"
|
||||
value = !value;
|
||||
value = renderBoolean(value);
|
||||
}
|
||||
|
||||
this.setItem(itemId, value);
|
||||
}
|
||||
|
||||
renderProfiles(itemId) {
|
||||
let value;
|
||||
|
||||
value = this.getItem(itemId);
|
||||
value = parseInt(value, 10);
|
||||
|
||||
if (isNaN(value)) {
|
||||
value = renderDefaultIfEmptyElement(value);
|
||||
} else {
|
||||
//Convert to boolean
|
||||
value = !!value;
|
||||
|
||||
//Revert profiles to "No profiles"
|
||||
value = !value;
|
||||
|
||||
value = renderBoolean(value);
|
||||
}
|
||||
|
||||
this.setItem(itemId, value);
|
||||
}
|
||||
|
||||
renderDate(itemId) {
|
||||
let value;
|
||||
|
||||
value = this.getItem(itemId);
|
||||
value = renderDate(value);
|
||||
this.setItem(itemId, value);
|
||||
}
|
||||
|
||||
renderCountryIso(itemId) {
|
||||
let value;
|
||||
|
||||
value = this.getItem(itemId);
|
||||
value = {country_iso: value, full_country: value};
|
||||
value = renderCountryIso(value);
|
||||
this.setItem(itemId, value);
|
||||
}
|
||||
|
||||
renderHttpCode(itemId) {
|
||||
let value;
|
||||
|
||||
value = this.getItem(itemId);
|
||||
value = {http_code: value};
|
||||
value = renderHttpCode(value);
|
||||
this.setItem(itemId, value);
|
||||
}
|
||||
|
||||
renderPhoneType(itemId) {
|
||||
let value;
|
||||
|
||||
value = this.getItem(itemId);
|
||||
value = {type: value};
|
||||
value = renderPhoneType(value);
|
||||
this.setItem(itemId, value);
|
||||
}
|
||||
|
||||
renderPhoneCarrierName(itemId) {
|
||||
let value;
|
||||
|
||||
value = this.getItem(itemId);
|
||||
value = {carrier_name: value};
|
||||
value = renderPhoneCarrierName(value);
|
||||
this.setItem(itemId, value);
|
||||
|
||||
}
|
||||
|
||||
renderBoolean(itemId) {
|
||||
let value;
|
||||
|
||||
value = this.getItem(itemId);
|
||||
value = renderBoolean(value);
|
||||
this.setItem(itemId, value);
|
||||
}
|
||||
|
||||
renderDefaultIfEmptyElement(itemId) {
|
||||
let value;
|
||||
|
||||
value = this.getItem(itemId);
|
||||
value = renderDefaultIfEmptyElement(value);
|
||||
this.setItem(itemId, value);
|
||||
}
|
||||
|
||||
getItem(itemId, returnNode = false) {
|
||||
const td = document.querySelector(`td[data-item-id="${itemId}"]`);
|
||||
|
||||
if (!td) return null;
|
||||
|
||||
const tr = td.closest('tr');
|
||||
|
||||
const valueTd = tr.lastElementChild;
|
||||
if (returnNode) {
|
||||
return valueTd;
|
||||
} else {
|
||||
let text = valueTd.innerText;
|
||||
let value = text;
|
||||
|
||||
if ('false' === text) value = false;
|
||||
if ('true' === text) value = true;
|
||||
if ('null' === text) value = null;
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
setItem(itemId, value) {
|
||||
const item = this.getItem(itemId, true);
|
||||
if (item) {
|
||||
if (item instanceof Node) {
|
||||
item.replaceChildren(value);
|
||||
} else {
|
||||
item.innerHTML = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
import {TotalTile} from './TotalTile.js?v=2';
|
||||
import {getQueryParams} from './utils/DataSource.js?v=2';
|
||||
import {handleAjaxError} from './utils/ErrorHandler.js?v=2';
|
||||
import {fireEvent} from './utils/Event.js?v=2';
|
||||
|
||||
export class Map {
|
||||
|
||||
constructor(mapParams) {
|
||||
this.config = mapParams;
|
||||
|
||||
this.totalTile = new TotalTile();
|
||||
|
||||
this.regions = {};
|
||||
|
||||
const onRegionTipShow = this.onRegionTipShow.bind(this);
|
||||
const onRegionClick = this.onRegionClick.bind(this);
|
||||
|
||||
$('#world-map-markers').vectorMap({
|
||||
map: 'world_mill_en',
|
||||
|
||||
normalizeFunction: 'polynomial',
|
||||
hoverOpacity: 0.7,
|
||||
regionsSelectable: false,
|
||||
markersSelectable: false,
|
||||
zoomOnScroll: false,
|
||||
hoverColor: false,
|
||||
|
||||
series: {
|
||||
regions: [
|
||||
{
|
||||
values: {},
|
||||
scale: ['#4e6964', '#01EE99'],
|
||||
normalizeFunction: 'polynomial'
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
regionStyle: {
|
||||
initial: {
|
||||
fill: '#575678'
|
||||
},
|
||||
selected: {
|
||||
fill: '#01EE99'
|
||||
}
|
||||
},
|
||||
|
||||
onRegionTipShow: function(e, el, code) {
|
||||
onRegionTipShow(el, code);
|
||||
},
|
||||
onRegionClick: function(e, code) {
|
||||
onRegionClick(code);
|
||||
},
|
||||
|
||||
backgroundColor: '#131220'
|
||||
});
|
||||
|
||||
const onDateFilterChanged = this.onDateFilterChanged.bind(this);
|
||||
window.addEventListener('dateFilterChanged', onDateFilterChanged, false);
|
||||
|
||||
if (!this.config.sequential) {
|
||||
this.loadData();
|
||||
}
|
||||
}
|
||||
|
||||
startLoader() {
|
||||
}
|
||||
|
||||
onRegionTipShow(tipEl, value) {
|
||||
const regionValue = this.mapObject.series.regions[0].values[value];
|
||||
const phrase = this.getTooltipString(regionValue);
|
||||
|
||||
tipEl.html(`${tipEl.html()} - ${phrase}`);
|
||||
}
|
||||
|
||||
onRegionClick(value) {
|
||||
if (this.regions[value] !== undefined && this.regions[value][this.config.tooltipField] > 0) {
|
||||
const url = `/country/${this.regions[value].id}`;
|
||||
if (event.ctrlKey || event.metaKey) {
|
||||
window.open(url, '_blank');
|
||||
} else {
|
||||
window.location.href = url;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getCountriesRegionsFromResponse(records) {
|
||||
const me = this;
|
||||
const regions = {};
|
||||
|
||||
this.regions = {};
|
||||
|
||||
records.forEach(rec => {
|
||||
const country = rec.iso;
|
||||
if (!regions[country]) {
|
||||
regions[country] = 0;
|
||||
this.regions[country] = 0;
|
||||
}
|
||||
|
||||
const value = me.getRegionValue(rec);
|
||||
regions[country] = value;
|
||||
this.regions[country] = {
|
||||
[this.config.tooltipField]: value,
|
||||
id: rec.id,
|
||||
};
|
||||
});
|
||||
|
||||
return regions;
|
||||
}
|
||||
|
||||
getRegionValue(record) {
|
||||
const field = this.config.tooltipField;
|
||||
const value = record[field];
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
selectRegions(regions) {
|
||||
const map = this.mapObject;
|
||||
|
||||
//Remove countries which does not exist in the vectormap: MU, BH, etc...
|
||||
for (const [key, value] of Object.entries(regions)) {
|
||||
if (!map.regions.hasOwnProperty(key)) {
|
||||
delete regions[key];
|
||||
}
|
||||
}
|
||||
|
||||
//https://github.com/bjornd/jvectormap/issues/376
|
||||
map.series.regions[0].params.min = undefined;
|
||||
map.series.regions[0].params.max = undefined;
|
||||
|
||||
map.series.regions[0].clear();
|
||||
map.series.regions[0].setValues(regions);
|
||||
}
|
||||
|
||||
onDateFilterChanged() {
|
||||
this.loadData();
|
||||
}
|
||||
|
||||
loadData() {
|
||||
const me = this;
|
||||
const params = this.config.getParams();
|
||||
const token = document.head.querySelector('[name=\'csrf-token\'][content]').content;
|
||||
|
||||
const data = getQueryParams(params);
|
||||
|
||||
fireEvent('dateFilterChangedCaught');
|
||||
|
||||
$.ajax({
|
||||
type: 'get',
|
||||
url: `/admin/loadMap?token=${token}`,
|
||||
data: data,
|
||||
scope: me,
|
||||
success: me.onCountriesListLoaded,
|
||||
error: handleAjaxError,
|
||||
complete: function() {
|
||||
fireEvent('dateFilterChangedCompleted');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
onCountriesListLoaded(data, status) {
|
||||
if ('success' == status) {
|
||||
const me = this.scope;
|
||||
|
||||
const tableId = 'countries-table';
|
||||
|
||||
me.totalTile.update(tableId, me.config.tileId, data.length);
|
||||
|
||||
const regions = me.getCountriesRegionsFromResponse(data);
|
||||
|
||||
me.selectRegions(regions);
|
||||
}
|
||||
}
|
||||
|
||||
getTooltipString(value) {
|
||||
value = value ? value : 0;
|
||||
|
||||
let string = this.config.tooltipString;
|
||||
if (1 !== value) {
|
||||
string += 's';
|
||||
}
|
||||
|
||||
const tooltipPhrase = `${value} ${string}`;
|
||||
|
||||
return tooltipPhrase;
|
||||
}
|
||||
|
||||
get mapObject() {
|
||||
return $('#world-map-markers').vectorMap('get', 'mapObject');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// should be used for POST forms
|
||||
export class ReenrichmentButton {
|
||||
constructor() {
|
||||
if (this.reenrichmentButton) {
|
||||
const onButtonClick = this.onButtonClick.bind(this);
|
||||
this.reenrichmentButton.addEventListener('click', onButtonClick, false);
|
||||
}
|
||||
}
|
||||
|
||||
onButtonClick(e) {
|
||||
e.preventDefault();
|
||||
this.reenrichmentButton.setAttribute('disabled', '');
|
||||
this.form.submit();
|
||||
}
|
||||
|
||||
get reenrichmentButton() {
|
||||
return document.querySelector('#reenrichment-form .reenrichment-button');
|
||||
}
|
||||
|
||||
get form() {
|
||||
return document.querySelector('#reenrichment-form');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import {renderScoreDetails} from './DataRenderers.js?v=2';
|
||||
import {handleAjaxError} from './utils/ErrorHandler.js?v=2';
|
||||
import {Tooltip} from './Tooltip.js?v=2';
|
||||
|
||||
|
||||
export class ScoreDetails {
|
||||
|
||||
constructor(scoreParams) {
|
||||
this.config = scoreParams;
|
||||
this.loadData();
|
||||
}
|
||||
|
||||
loadData() {
|
||||
const onScoreDetailsLoaded = this.onScoreDetailsLoaded.bind(this);
|
||||
const token = document.head.querySelector('[name=\'csrf-token\'][content]').content;
|
||||
|
||||
$.ajax({
|
||||
url: '/admin/scoreDetails',
|
||||
type: 'get',
|
||||
data: {userId: this.config.userId, token: token},
|
||||
success: onScoreDetailsLoaded,
|
||||
error: handleAjaxError,
|
||||
});
|
||||
}
|
||||
|
||||
onScoreDetailsLoaded(data, status) {
|
||||
if ('success' !== status || 0 === data.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
let el = this.contentDiv;
|
||||
|
||||
if (el) {
|
||||
el.replaceChildren(renderScoreDetails(data));
|
||||
}
|
||||
|
||||
this.initTooltips();
|
||||
}
|
||||
|
||||
get card() {
|
||||
return document.querySelector('.score-details');
|
||||
}
|
||||
|
||||
get contentDiv() {
|
||||
return this.card.querySelector('div.score-details-content');
|
||||
}
|
||||
|
||||
initTooltips() {
|
||||
Tooltip.addTooltipsToScoreDetails();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import {fireEvent} from './utils/Event.js?v=2';
|
||||
import {debounce} from './utils/Functions.js?v=2';
|
||||
|
||||
export class SearchFilter {
|
||||
constructor() {
|
||||
const onSearchInputChange = this.onSearchInputChange.bind(this);
|
||||
const debouncedOnSearchInputChange = debounce(onSearchInputChange);
|
||||
this.searchField.addEventListener('input', debouncedOnSearchInputChange, false);
|
||||
|
||||
const onDateFilterChanged = this.onDateFilterChanged.bind(this);
|
||||
window.addEventListener('dateFilterChanged', onDateFilterChanged, false);
|
||||
}
|
||||
|
||||
onSearchInputChange({target}) {
|
||||
const value = target.value;
|
||||
fireEvent('searchFilterChanged', {query: value});
|
||||
}
|
||||
|
||||
onDateFilterChanged() {
|
||||
this.searchField.value = '';
|
||||
}
|
||||
|
||||
getValue() {
|
||||
return this.searchField.value;
|
||||
}
|
||||
|
||||
get searchField() {
|
||||
return document.getElementById('search');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
import {Loader} from './Loader.js?v=2';
|
||||
import {Tooltip} from './Tooltip.js?v=2';
|
||||
import {handleAjaxError} from './utils/ErrorHandler.js?v=2';
|
||||
import {padZero} from './utils/Date.js?v=2';
|
||||
|
||||
export class SearchLine {
|
||||
constructor() {
|
||||
this.loader = new Loader();
|
||||
|
||||
Tooltip.addTooltipsToClock();
|
||||
|
||||
const me = this;
|
||||
const token = document.head.querySelector('[name=\'csrf-token\'][content]').content;
|
||||
const url = `/admin/search?token=${token}`;
|
||||
|
||||
$('#auto-complete').autocomplete({
|
||||
serviceUrl: url,
|
||||
deferRequestBy: 300,
|
||||
minChars: 3,
|
||||
groupBy: 'category',
|
||||
showNoSuggestionNotice: true,
|
||||
noSuggestionNotice: 'Sorry, no matching results',
|
||||
|
||||
onSelect: function(suggestion) {
|
||||
window.open(`/${suggestion.entityId}/${suggestion.id}`, '_self');
|
||||
},
|
||||
|
||||
onSearchStart: function(params) {
|
||||
params.query = params.query.trim();
|
||||
me.loaderDiv.classList.remove('is-hidden');
|
||||
me.loader.start(me.loaderDiv);
|
||||
|
||||
},
|
||||
onSearchComplete: function(query, suggestions) {
|
||||
me.loader.stop();
|
||||
me.loaderDiv.classList.add('is-hidden');
|
||||
},
|
||||
|
||||
onSearchError: handleAjaxError,
|
||||
});
|
||||
|
||||
setInterval(this.updateTime.bind(this), 1000);
|
||||
}
|
||||
|
||||
updateTime() {
|
||||
let [time, tz] = this.timeInput.placeholder.split(' ');
|
||||
let [h, m, s] = time.split(':').map(x => parseInt(x, 10));
|
||||
let d = this.dayInput.placeholder;
|
||||
|
||||
s += 1;
|
||||
if (s >= 60) {
|
||||
s = 0;
|
||||
m += 1;
|
||||
}
|
||||
|
||||
if (m >= 60) {
|
||||
m = 0;
|
||||
h += 1;
|
||||
}
|
||||
|
||||
if (h >= 24) {
|
||||
h = 0;
|
||||
d = parseInt(d, 10) + 1;
|
||||
|
||||
if (d >= 366) {
|
||||
const now = new Date();
|
||||
const year = now.getFullYear() - (now.getMonth() === 0 ? 1 : 0);
|
||||
const isLeap = (year % 4 === 0 && year % 100 !== 0) || (year % 400 === 0);
|
||||
|
||||
if (d > 366 || (d === 366 && !isLeap)) {
|
||||
d = 0;
|
||||
}
|
||||
}
|
||||
|
||||
this.dayInput.placeholder = (d < 10 ? '00' : (d < 100 ? '0' : '')) + d.toString();
|
||||
}
|
||||
|
||||
h = padZero(h);
|
||||
m = padZero(m);
|
||||
s = padZero(s);
|
||||
|
||||
this.timeInput.placeholder = `${h}:${m}:${s} ${tz}`;
|
||||
}
|
||||
|
||||
onTypeLinkClick(e) {
|
||||
e.preventDefault();
|
||||
|
||||
this.queryTypeLinks.forEach(link => link.classList.remove('active'));
|
||||
e.target.classList.add('active');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
getActiveQueryTypeItem() {
|
||||
const activeLink = this.queryTypeControl.querySelector('a.active');
|
||||
const activeType = activeLink.dataset.value;
|
||||
|
||||
return activeType;
|
||||
}
|
||||
|
||||
get loaderDiv() {
|
||||
return document.querySelector('.searchline').querySelector('div.text-loader');
|
||||
}
|
||||
|
||||
get timeInput() {
|
||||
return document.getElementById('clock-time');
|
||||
}
|
||||
|
||||
get dayInput() {
|
||||
return document.getElementById('clock-day');
|
||||
}
|
||||
|
||||
get queryTypeLinks() {
|
||||
return this.queryTypeControl.querySelectorAll('A');
|
||||
}
|
||||
|
||||
get queryTypeControl() {
|
||||
return document.querySelector('nav.filtersForm.search');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
import {fireEvent} from './utils/Event.js?v=2';
|
||||
|
||||
export class SequentialLoad {
|
||||
constructor(data, eventName = 'dateFilterChangedCompleted') {
|
||||
this.objects = [];
|
||||
this.eventName = eventName;
|
||||
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
data[i][1].sequential = true;
|
||||
this.objects.push(new (data[i][0])(data[i][1]));
|
||||
}
|
||||
|
||||
const me = this;
|
||||
|
||||
$(document).ready(() => {
|
||||
me.startLoaders();
|
||||
|
||||
let i = 0;
|
||||
|
||||
const onReady = () => {
|
||||
if (i >= me.objects.length) {
|
||||
window.removeEventListener(eventName, onReady);
|
||||
fireEvent('sequentialLoadCompleted');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
me.objects[i].loadData();
|
||||
i++;
|
||||
};
|
||||
|
||||
window.addEventListener(eventName, onReady);
|
||||
|
||||
onReady();
|
||||
});
|
||||
|
||||
// catching filters change
|
||||
const onFilterChanged = this.onFilterChanged.bind(this);
|
||||
window.addEventListener('searchFilterChanged', onFilterChanged, false);
|
||||
window.addEventListener('dateFilterChanged', onFilterChanged, false);
|
||||
}
|
||||
|
||||
onFilterChanged() {
|
||||
this.startLoaders();
|
||||
|
||||
let i = 0;
|
||||
|
||||
const onLoad = () => {
|
||||
if (i >= this.objects.length) {
|
||||
fireEvent('sequentialLoadCompleted');
|
||||
window.removeEventListener(this.eventName, onLoad);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.objects[i].loadData();
|
||||
i++;
|
||||
};
|
||||
|
||||
window.addEventListener(this.eventName, onLoad);
|
||||
|
||||
onLoad();
|
||||
}
|
||||
|
||||
startLoaders() {
|
||||
this.objects.forEach(item => {
|
||||
item.startLoader();
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,142 @@
|
||||
import {
|
||||
renderUserActionButtons,
|
||||
renderUserReviewedStatus,
|
||||
} from './DataRenderers.js?v=2';
|
||||
import {handleAjaxError} from './utils/ErrorHandler.js?v=2';
|
||||
import {replaceAll} from './utils/String.js?v=2';
|
||||
|
||||
export class SingleReviewButton {
|
||||
|
||||
constructor(userId) {
|
||||
this.userId = userId;
|
||||
|
||||
const me = this;
|
||||
const onButtonClick = this.onButtonClick.bind(this);
|
||||
|
||||
if (me.legitFraudButtonsBlock) {
|
||||
//Get HTML w/ new fraud&legit buttons
|
||||
let fraud = null;
|
||||
if ('true' == me.legitFraudButtonsBlock.dataset.userFraud) fraud = true;
|
||||
if ('false' == me.legitFraudButtonsBlock.dataset.userFraud) fraud = false;
|
||||
|
||||
const record = {reviewed: true, accountid: me.userId, fraud: fraud};
|
||||
|
||||
me.legitFraudButtonsBlock.replaceChildren(renderUserActionButtons(record, false));
|
||||
}
|
||||
|
||||
if (me.reviewedButton) {
|
||||
this.reviewedButton.addEventListener('click', onButtonClick, false);
|
||||
}
|
||||
|
||||
if (me.legitButton) {
|
||||
this.legitButton.addEventListener('click', onButtonClick, false);
|
||||
}
|
||||
|
||||
if (me.fraudButton) {
|
||||
this.fraudButton.addEventListener('click', onButtonClick, false);
|
||||
}
|
||||
}
|
||||
|
||||
onButtonClick(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const me = this;
|
||||
const target = e.target;
|
||||
const url = '/admin/manageUser';
|
||||
const token = document.head.querySelector('[name=\'csrf-token\'][content]').content;
|
||||
const data = {userId: this.userId, type: target.dataset.type, token: token};
|
||||
|
||||
target.classList.add('is-loading');
|
||||
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: url,
|
||||
data: data,
|
||||
scope: me,
|
||||
target: target,
|
||||
success: me.onSuccess,
|
||||
error: handleAjaxError,
|
||||
dataType: 'json'
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
onSuccess() {
|
||||
const me = this.scope;
|
||||
|
||||
const target = this.target;
|
||||
const type = target.dataset.type;
|
||||
|
||||
target.classList.remove('is-loading');
|
||||
|
||||
if ('reviewed-button' === target.id) {
|
||||
//Get HTML w/ new fraud&legit buttons
|
||||
const record = {reviewed: true, accountid: me.userId};
|
||||
|
||||
const div = target.closest('div.head-button');
|
||||
div.replaceChildren(renderUserActionButtons(record, false));
|
||||
|
||||
const onButtonClick = me.onButtonClick.bind(me);
|
||||
|
||||
if (me.legitButton) {
|
||||
me.legitButton.addEventListener('click', onButtonClick, false);
|
||||
}
|
||||
|
||||
if (me.fraudButton) {
|
||||
me.fraudButton.addEventListener('click', onButtonClick, false);
|
||||
}
|
||||
}
|
||||
|
||||
const buttonType = target.dataset.buttonType;
|
||||
if ('fraudButton' === buttonType) {
|
||||
let reviewStatus = '';
|
||||
if ('fraud' === type) {
|
||||
reviewStatus = 'Blacklisted';
|
||||
me.fraudButton.classList.replace('is-neutral', 'is-highlighted');
|
||||
me.fraudButton.setAttribute('disabled', '');
|
||||
|
||||
me.legitButton.classList.replace('is-highlighted', 'is-neutral');
|
||||
me.legitButton.removeAttribute('disabled');
|
||||
} else {
|
||||
reviewStatus = 'Whitelisted';
|
||||
me.legitButton.classList.replace('is-neutral', 'is-highlighted');
|
||||
me.legitButton.setAttribute('disabled', '');
|
||||
|
||||
me.fraudButton.classList.replace('is-highlighted', 'is-neutral');
|
||||
me.fraudButton.removeAttribute('disabled');
|
||||
}
|
||||
const tile = document.querySelector('#user-id-tile');
|
||||
const title = tile.querySelector('#review-status span').title;
|
||||
|
||||
const record = {
|
||||
fraud: (reviewStatus === 'Blacklisted'),
|
||||
latest_decision: title,
|
||||
};
|
||||
|
||||
tile.querySelector('#review-status').replaceChildren(renderUserReviewedStatus(record));
|
||||
|
||||
const userTitleSpan = document.querySelector('h1 span');
|
||||
|
||||
userTitleSpan.textContent = (reviewStatus === 'Blacklisted') ? 'X' : 'OK';
|
||||
userTitleSpan.classList.remove('high', 'medium', 'low', 'empty');
|
||||
userTitleSpan.classList.add((reviewStatus === 'Blacklisted') ? 'low' : 'high');
|
||||
}
|
||||
}
|
||||
|
||||
get legitFraudButtonsBlock() {
|
||||
return document.getElementById('legit-fraud-buttons-block');
|
||||
}
|
||||
|
||||
get fraudButton() {
|
||||
return document.querySelector('[data-type="fraud"]');
|
||||
}
|
||||
|
||||
get legitButton() {
|
||||
return document.querySelector('[data-type="legit"]');
|
||||
}
|
||||
|
||||
get reviewedButton() {
|
||||
return document.getElementById('reviewed-button');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import {Loader} from './Loader.js?v=2';
|
||||
|
||||
export class StaticTiles {
|
||||
|
||||
constructor(tilesParams) {
|
||||
const me = this;
|
||||
this.config = tilesParams;
|
||||
this.loaders = {};
|
||||
|
||||
this.config.elems.forEach(elem => {
|
||||
me.loaders[elem] = new Loader();
|
||||
});
|
||||
|
||||
const onDateFilterChanged = this.onDateFilterChanged.bind(this);
|
||||
window.addEventListener('dateFilterChanged', onDateFilterChanged, false);
|
||||
|
||||
this.runLoaders();
|
||||
}
|
||||
|
||||
onDateFilterChanged() {
|
||||
this.runLoaders();
|
||||
}
|
||||
|
||||
runLoaders() {
|
||||
for (const property in this.loaders) {
|
||||
const el = document.querySelector(`.${property} .title`);
|
||||
this.loaders[property].start(el);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
export class ThresholdsForm {
|
||||
constructor() {
|
||||
const updateReviewQueueOptions = this.updateReviewQueueOptions.bind(this);
|
||||
this.blacklistOptions.forEach(radio => {
|
||||
radio.addEventListener('change', updateReviewQueueOptions, false);
|
||||
});
|
||||
|
||||
const updateBlacklistOptions = this.updateBlacklistOptions.bind(this);
|
||||
this.reviewQueueOptions.forEach(radio => {
|
||||
radio.addEventListener('change', updateBlacklistOptions, false);
|
||||
});
|
||||
}
|
||||
|
||||
updateReviewQueueOptions(e) {
|
||||
const blacklistValue = this.blacklistVal;
|
||||
|
||||
this.reviewQueueOptions.forEach(radio => {
|
||||
const value = parseInt(radio.value, 10);
|
||||
radio.disabled = value <= blacklistValue;
|
||||
});
|
||||
}
|
||||
|
||||
updateBlacklistOptions(e) {
|
||||
const reviewValue = this.reviewQueueVal;
|
||||
|
||||
this.blacklistOptions.forEach(radio => {
|
||||
const value = parseInt(radio.value, 10);
|
||||
radio.disabled = value >= reviewValue;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
get reviewQueueOptions() {
|
||||
return document.querySelectorAll('input[name="review-queue-threshold"]');
|
||||
}
|
||||
|
||||
get blacklistOptions() {
|
||||
return document.querySelectorAll('input[name="blacklist-threshold"]');
|
||||
}
|
||||
|
||||
get reviewQueueVal() {
|
||||
return parseInt(document.querySelector('input[name="review-queue-threshold"]:checked').value || 100, 10);
|
||||
}
|
||||
|
||||
get blacklistVal() {
|
||||
return parseInt(document.querySelector('input[name="blacklist-threshold"]:checked').value || -1, 10);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
export class Tooltip {
|
||||
|
||||
static init() {
|
||||
this.addTooltipToSpans();
|
||||
this.addTooltipToParagraphs();
|
||||
this.addTooltipToTableHeaders();
|
||||
}
|
||||
|
||||
static addTooltipsToEventDetailsPanel() {
|
||||
this.baseTooltips('.details-card .tooltip', true);
|
||||
}
|
||||
|
||||
static addTooltipsToScoreDetails() {
|
||||
this.baseTooltips('.score-details-content .tooltip', true);
|
||||
}
|
||||
|
||||
static addTooltipsToTiles() {
|
||||
this.baseTooltips('span.detailsTileValue .tooltip', false);
|
||||
}
|
||||
|
||||
static addTooltipsToGridRecords(tableId) {
|
||||
this.baseTooltips(`#${tableId} td .tooltip.tooltipster-word-break`, true, true);
|
||||
this.baseTooltips(`#${tableId} td .tooltip:not(.tooltipster-word-break)`, true);
|
||||
}
|
||||
|
||||
static addTooltipToSpans() {
|
||||
this.baseTooltips('span.tooltip', true);
|
||||
}
|
||||
|
||||
static addTooltipToTableHeaders() {
|
||||
this.baseTooltips('th.tooltip', true);
|
||||
}
|
||||
|
||||
static addTooltipToParagraphs() {
|
||||
this.baseTooltips('p.tooltip', true);
|
||||
}
|
||||
|
||||
static addTooltipsToRulesProportion() {
|
||||
this.baseTooltips('td .tooltip', true);
|
||||
}
|
||||
|
||||
static addTooltipsToClock() {
|
||||
this.baseTooltips('div .day-tile.tooltip, div .time-tile.tooltip', true);
|
||||
}
|
||||
|
||||
static baseTooltips(path, useMaxWidth = true, wordBreak = false) {
|
||||
$(document.querySelectorAll(path)).tooltipster(this.getConfig(useMaxWidth, wordBreak));
|
||||
}
|
||||
|
||||
static getConfig(useMaxWidth, wordBreak) {
|
||||
const config = {
|
||||
delay: 0,
|
||||
delayTouch: 0,
|
||||
debug: false,
|
||||
side: 'bottom',
|
||||
animationDuration: 0,
|
||||
theme: ['tooltipster-borderless'],
|
||||
};
|
||||
|
||||
if (wordBreak) {
|
||||
config.theme.push('tooltipster-word-break');
|
||||
}
|
||||
|
||||
if (useMaxWidth) {
|
||||
config['maxWidth'] = 250;
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
import {
|
||||
USER_IPS_CRITICAL_VALUE,
|
||||
USER_EVENTS_CRITICAL_VALUE,
|
||||
USER_DEVICES_CRITICAL_VALUE,
|
||||
USER_COUNTRIES_CRITICAL_VALUE,
|
||||
} from './utils/Constants.js?v=2';
|
||||
|
||||
export class TotalTile {
|
||||
|
||||
constructor() {
|
||||
this.criticalValues = {
|
||||
totalIps: USER_IPS_CRITICAL_VALUE,
|
||||
totalEvents: USER_EVENTS_CRITICAL_VALUE,
|
||||
totalDevices: USER_DEVICES_CRITICAL_VALUE,
|
||||
totalCountries: USER_COUNTRIES_CRITICAL_VALUE
|
||||
};
|
||||
}
|
||||
|
||||
update(tableId, tileId, value) {
|
||||
const tileCls = this.getTileClass(tableId, tileId, value);
|
||||
const path = `.${tileId} .title`;
|
||||
const el = document.querySelector(path);
|
||||
|
||||
if (el) {
|
||||
el.classList.add('loaded');
|
||||
el.classList.remove('loading');
|
||||
|
||||
//Remove previous class if exists
|
||||
el.classList.remove('low');
|
||||
el.classList.remove('medium');
|
||||
el.classList.remove('high');
|
||||
|
||||
//Add new color class
|
||||
el.classList.add(tileCls);
|
||||
|
||||
el.textContent = value;
|
||||
}
|
||||
}
|
||||
|
||||
getTileClass(tableId, tileId, value) {
|
||||
const litmus = this.criticalValues[tileId];
|
||||
|
||||
const USER_ID = parseInt(window.location.pathname.replace('/id/', ''), 10);
|
||||
const isUserPage = () => !isNaN(USER_ID);
|
||||
|
||||
if (!litmus || !isUserPage()) return;
|
||||
|
||||
let cls = null;
|
||||
|
||||
if (value >= litmus) {
|
||||
cls = 'medium';
|
||||
}
|
||||
|
||||
return cls;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
import {renderUserActionButtons} from './DataRenderers.js?v=2';
|
||||
import {handleAjaxError} from './utils/ErrorHandler.js?v=2';
|
||||
|
||||
export class UserGridActionButtons {
|
||||
|
||||
constructor(tableId) {
|
||||
this.tableId = tableId;
|
||||
const onTableLoaded = this.onTableLoaded.bind(this);
|
||||
window.addEventListener('tableLoaded', onTableLoaded, false);
|
||||
}
|
||||
|
||||
onTableLoaded(e) {
|
||||
const tableId = e.detail.tableId;
|
||||
const buttons = document.querySelectorAll(`#${tableId} button`);
|
||||
|
||||
const onButtonClick = this.onButtonClick.bind(this);
|
||||
buttons.forEach(button => button.addEventListener('click', onButtonClick, false));
|
||||
}
|
||||
|
||||
onButtonClick(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const me = this;
|
||||
const target = e.target;
|
||||
const url = '/admin/manageUser';
|
||||
const token = document.head.querySelector('[name=\'csrf-token\'][content]').content;
|
||||
const data = {userId: target.dataset.userId, type: target.dataset.type, token: token};
|
||||
|
||||
target.classList.add('is-loading');
|
||||
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: url,
|
||||
data: data,
|
||||
scope: me,
|
||||
target: target,
|
||||
success: me.onSuccess,
|
||||
error: handleAjaxError,
|
||||
dataType: 'json'
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
onSuccessCount(data) {
|
||||
const span = document.querySelector('span.reviewed-users-tile');
|
||||
span.textContent = data.total;
|
||||
}
|
||||
|
||||
setMenuCount() {
|
||||
const token = document.head.querySelector('[name=\'csrf-token\'][content]').content;
|
||||
$.ajax({
|
||||
type: 'GET',
|
||||
url: '/admin/loadReviewQueueCount',
|
||||
data: {token: token},
|
||||
success: this.onSuccessCount,
|
||||
error: handleAjaxError,
|
||||
});
|
||||
}
|
||||
|
||||
onSuccess() {
|
||||
const me = this.scope;
|
||||
|
||||
const target = this.target;
|
||||
const type = target.dataset.type;
|
||||
const buttonType = target.dataset.buttonType;
|
||||
const accountId = target.dataset.userId;
|
||||
const tableRow = target.closest('tr');
|
||||
|
||||
target.classList.remove('is-loading');
|
||||
|
||||
const twoButtonsContainer = target.closest('.legitfraud');
|
||||
if (twoButtonsContainer && !twoButtonsContainer.hasAttribute('counterUpdated')) {
|
||||
twoButtonsContainer.setAttribute('counterUpdated', 0);
|
||||
}
|
||||
|
||||
if ('fraudButton' === buttonType) {
|
||||
const td = target.closest('td');
|
||||
const fraudButton = td.querySelector('[data-type="fraud"]');
|
||||
const legitButton = td.querySelector('[data-type="legit"]');
|
||||
|
||||
if ('fraud' === type) {
|
||||
fraudButton.classList.replace('is-neutral', 'is-highlighted');
|
||||
fraudButton.setAttribute('disabled', '');
|
||||
|
||||
legitButton.classList.replace('is-highlighted', 'is-neutral');
|
||||
legitButton.removeAttribute('disabled');
|
||||
} else {
|
||||
legitButton.classList.replace('is-neutral', 'is-highlighted');
|
||||
legitButton.setAttribute('disabled', '');
|
||||
|
||||
fraudButton.classList.replace('is-highlighted', 'is-neutral');
|
||||
fraudButton.removeAttribute('disabled');
|
||||
}
|
||||
|
||||
const counterUpdated = twoButtonsContainer.getAttribute('counterUpdated');
|
||||
const wasCounterUpdated = parseInt(counterUpdated, 10);
|
||||
if (!wasCounterUpdated) {
|
||||
const card = target.closest('.card');
|
||||
const span = card.querySelector('.card-header-title span');
|
||||
let total = parseInt(span.innerHTML, 10);
|
||||
|
||||
if (total > 0) {
|
||||
total -= 1;
|
||||
}
|
||||
|
||||
span.textContent = total;
|
||||
|
||||
twoButtonsContainer.setAttribute('counterUpdated', 1);
|
||||
}
|
||||
|
||||
if (tableRow) {
|
||||
const dataTable = $(`#${me.tableId}`).DataTable();
|
||||
dataTable.row(tableRow).remove().draw(false);
|
||||
me.setMenuCount();
|
||||
}
|
||||
}
|
||||
|
||||
if ('reviewedButton' === buttonType) {
|
||||
//Get HTML w/ new fraud&legit buttons
|
||||
const record = {reviewed: true, accountid: accountId};
|
||||
const html = renderUserActionButtons(record);
|
||||
|
||||
const td = target.closest('td');
|
||||
td.replaceChildren(html);
|
||||
|
||||
//Add event listener to newly created buttons
|
||||
const buttons = td.querySelectorAll('button');
|
||||
const onButtonClick = me.onButtonClick.bind(me);
|
||||
buttons.forEach(button => button.addEventListener('click', onButtonClick, false));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
import {renderUserActionButtons} from './DataRenderers.js?v=2';
|
||||
import {handleAjaxError} from './utils/ErrorHandler.js?v=2';
|
||||
import {replaceAll} from './utils/String.js?v=2';
|
||||
|
||||
export class WatchlistBlock {
|
||||
|
||||
constructor(userId) {
|
||||
this.userId = userId;
|
||||
|
||||
const me = this;
|
||||
const onButtonClick = this.onButtonClick.bind(this);
|
||||
|
||||
if (me.legitFraudButtonsBlock) {
|
||||
//Get HTML w/ new fraud&legit buttons
|
||||
let fraud = null;
|
||||
if ('true' == me.legitFraudButtonsBlock.dataset.userFraud) fraud = true;
|
||||
if ('false' == me.legitFraudButtonsBlock.dataset.userFraud) fraud = false;
|
||||
|
||||
const record = {reviewed: true, accountid: me.userId, fraud: fraud};
|
||||
|
||||
let html = renderUserActionButtons(record);
|
||||
html = replaceAll(html, 'is-small', '');
|
||||
|
||||
me.legitFraudButtonsBlock.innerHTML = html;
|
||||
}
|
||||
|
||||
if (me.reviewedButton) {
|
||||
this.reviewedButton.addEventListener('click', onButtonClick, false);
|
||||
}
|
||||
|
||||
if (me.legitButton) {
|
||||
this.legitButton.addEventListener('click', onButtonClick, false);
|
||||
}
|
||||
|
||||
if (me.fraudButton) {
|
||||
this.fraudButton.addEventListener('click', onButtonClick, false);
|
||||
}
|
||||
|
||||
this.watchListButton.addEventListener('click', onButtonClick, false);
|
||||
}
|
||||
|
||||
onButtonClick(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const me = this;
|
||||
const target = e.target;
|
||||
const url = `/id/${this.userId}`;
|
||||
const data = {type: target.dataset.type};
|
||||
|
||||
target.classList.add('is-loading');
|
||||
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: url,
|
||||
data: data,
|
||||
scope: me,
|
||||
target: target,
|
||||
success: me.onSuccess,
|
||||
error: handleAjaxError,
|
||||
dataType: 'json'
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
onSuccess() {
|
||||
const me = this.scope;
|
||||
|
||||
const target = this.target;
|
||||
const type = target.dataset.type;
|
||||
|
||||
target.classList.remove('is-loading');
|
||||
|
||||
if ('watchlist-button' === target.id) {
|
||||
const oldClass = ('add' === type)? 'is-warning' : 'is-primary';
|
||||
const newClass = ('add' === type)? 'is-primary' : 'is-warning';
|
||||
|
||||
target.classList.replace(oldClass, newClass);
|
||||
|
||||
target.dataset.type = ('add' === type)? 'remove' : 'add';
|
||||
target.innerText = ('add' === type)? 'Remove from watchlist':'Add to watchlist';
|
||||
}
|
||||
|
||||
if ('reviewed-button' === target.id) {
|
||||
//Get HTML w/ new fraud&legit buttons
|
||||
const record = {reviewed: true, accountid: me.userId};
|
||||
|
||||
let html = renderUserActionButtons(record);
|
||||
html = replaceAll(html, 'is-small', '');
|
||||
|
||||
const div = target.closest('div.head-button');
|
||||
div.innerHTML = html;
|
||||
|
||||
const onButtonClick = me.onButtonClick.bind(me);
|
||||
|
||||
if (me.legitButton) {
|
||||
me.legitButton.addEventListener('click', onButtonClick, false);
|
||||
}
|
||||
|
||||
if (me.fraudButton) {
|
||||
me.fraudButton.addEventListener('click', onButtonClick, false);
|
||||
}
|
||||
}
|
||||
|
||||
const buttonType = target.dataset.buttonType;
|
||||
if ('fraudButton' === buttonType) {
|
||||
if ('fraud' === type) {
|
||||
me.fraudButton.classList.replace('is-grey', 'is-warning');
|
||||
me.fraudButton.setAttribute('disabled', '');
|
||||
|
||||
me.legitButton.classList.replace('is-warning', 'is-grey');
|
||||
me.legitButton.removeAttribute('disabled');
|
||||
} else {
|
||||
me.fraudButton.classList.replace('is-warning', 'is-grey');
|
||||
me.fraudButton.removeAttribute('disabled');
|
||||
|
||||
me.legitButton.classList.replace('is-grey', 'is-warning');
|
||||
me.legitButton.setAttribute('disabled', '');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get legitFraudButtonsBlock() {
|
||||
return document.getElementById('legit-fraud-buttons-block');
|
||||
}
|
||||
|
||||
get watchListButton() {
|
||||
return document.getElementById('watchlist-button');
|
||||
}
|
||||
|
||||
get fraudButton() {
|
||||
return document.querySelector('[data-type="fraud"]');
|
||||
}
|
||||
|
||||
get legitButton() {
|
||||
return document.querySelector('[data-type="legit"]');
|
||||
}
|
||||
|
||||
get reviewedButton() {
|
||||
return document.getElementById('reviewed-button');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import {fireEvent} from './utils/Event.js?v=2';
|
||||
import {handleAjaxError} from './utils/ErrorHandler.js?v=2';
|
||||
|
||||
export class WatchlistTags {
|
||||
|
||||
constructor() {
|
||||
this.onRemoveUserTagClick = this.onRemoveUserTagClick.bind(this);
|
||||
this.onUserTagRemoveCallback = this.onUserTagRemoveCallback.bind(this);
|
||||
|
||||
this.tags.forEach(tag => tag.addEventListener('click', this.onRemoveUserTagClick, false));
|
||||
}
|
||||
|
||||
onRemoveUserTagClick(e) {
|
||||
const wrapper = e.target.closest('.control');
|
||||
const id = wrapper.querySelector('[data-id]').dataset.id;
|
||||
const data = {
|
||||
'userId': id,
|
||||
'token': this.csrf,
|
||||
};
|
||||
const me = this;
|
||||
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: '/admin/removeWatchlisted',
|
||||
data: data,
|
||||
scope: me,
|
||||
success: me.onUserTagRemoveCallback,
|
||||
error: handleAjaxError,
|
||||
dataType: 'json'
|
||||
});
|
||||
}
|
||||
|
||||
onUserTagRemoveCallback(response) {
|
||||
if (response.success) {
|
||||
const userId = response.userId;
|
||||
const tag = document.querySelector(`[data-id="${userId}"]`);
|
||||
const wrapper = tag.closest('.control');
|
||||
|
||||
wrapper.remove();
|
||||
|
||||
//TODO: fireevent reloadData();
|
||||
fireEvent('watchlistTagRemoved', {});
|
||||
}
|
||||
}
|
||||
|
||||
get tags() {
|
||||
return document.querySelectorAll('#important-users a.is-delete');
|
||||
}
|
||||
|
||||
get csrf() {
|
||||
return document.querySelector('meta[name="csrf-token"]').getAttribute('content');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
import {BaseChart} from './BaseChart.js?v=2';
|
||||
import {COLOR_MAP} from '../utils/Constants.js?v=2';
|
||||
import {formatIntTimeUtc} from '../utils/Date.js?v=2';
|
||||
import {renderChartTooltipPart} from '../DataRenderers.js?v=2';
|
||||
|
||||
export class BaseBarChart extends BaseChart {
|
||||
getSeries() {
|
||||
return [
|
||||
this.getDaySeries(),
|
||||
{
|
||||
width: -1,
|
||||
paths: uPlot.paths.bars({size: [0.6, 100]}),
|
||||
points: {show: false},
|
||||
},
|
||||
this.getSingleSeries('Regular events', 'green'),
|
||||
this.getSingleSeries('Warning events', 'yellow'),
|
||||
this.getSingleSeries('Alert events', 'red'),
|
||||
];
|
||||
}
|
||||
|
||||
getSingleSeries(label, color) {
|
||||
return {
|
||||
label: label,
|
||||
width: -1,
|
||||
drawStyle: 1,
|
||||
fill: COLOR_MAP[color].main,
|
||||
stroke: COLOR_MAP[color].main,
|
||||
paths: uPlot.paths.bars({size: [0.6, 100]}),
|
||||
points: {show: false},
|
||||
};
|
||||
}
|
||||
|
||||
// dataset adaption for bands instead of regular bars
|
||||
getData(data) {
|
||||
let stacked = [data[0]];
|
||||
let sums = new Array(data[0].length).fill(0);
|
||||
|
||||
stacked.push(new Array(data[0].length).fill(0));
|
||||
|
||||
let maxLvl = data.length - 1;
|
||||
|
||||
for (let i = 1; i < data.length; i++) {
|
||||
let series = [];
|
||||
for (let j = 0; j < data[0].length; j++) {
|
||||
sums[j] += +data[i][j];
|
||||
series.push(sums[j]);
|
||||
}
|
||||
stacked.push(series);
|
||||
}
|
||||
|
||||
maxLvl = data.length;
|
||||
|
||||
for (let i = 0; i < data[0].length; i++) {
|
||||
let topMet = false;
|
||||
for (let j = maxLvl; j > 1; j--) {
|
||||
if (stacked[j][i] <= stacked[j-1][i] && !topMet) {
|
||||
stacked[j][i] = null;
|
||||
} else {
|
||||
topMet = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.data = stacked;
|
||||
|
||||
return stacked;
|
||||
}
|
||||
|
||||
stack(data, omit) {
|
||||
let data2 = [];
|
||||
let bands = [];
|
||||
let d0Len = data ? data[0].length : 0;
|
||||
let accum = Array(d0Len);
|
||||
|
||||
let i;
|
||||
|
||||
for (i = 0; i < d0Len; i++) {
|
||||
accum[i] = 0;
|
||||
}
|
||||
|
||||
let el;
|
||||
|
||||
for (i = 1; i < data.length; i++) {
|
||||
el = data[i];
|
||||
|
||||
if (!omit(i)) {
|
||||
el = el.map((v, j) => {
|
||||
let val = accum[j] + +v;
|
||||
accum[j] = val;
|
||||
|
||||
return val;
|
||||
});
|
||||
}
|
||||
|
||||
data2.push(el);
|
||||
}
|
||||
|
||||
for (i = 1; i < data.length; i++) {
|
||||
!omit(i) && bands.push({
|
||||
series: [
|
||||
data.findIndex((s, j) => j > i && !omit(j)),
|
||||
i,
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
bands = bands.filter(b => b.series[1] > -1);
|
||||
|
||||
return {
|
||||
data: [data[0]].concat(data2),
|
||||
bands,
|
||||
};
|
||||
}
|
||||
|
||||
getOptions(resolution = 'day', nullChar = '0') {
|
||||
const opts = super.getOptions(resolution, nullChar);
|
||||
|
||||
let stacked = this.stack(this.data, i => false);
|
||||
|
||||
opts.bands = stacked.bands;
|
||||
|
||||
opts.series.forEach((s, sIdx) => {
|
||||
if (s) {
|
||||
s.value = (u, v, si, i) => u.data[sIdx][i];
|
||||
|
||||
s.points = s.points || {};
|
||||
s.points.filter = (u, seriesIdx, show, gaps) => {
|
||||
if (show) {
|
||||
let pts = [];
|
||||
u.data[seriesIdx].forEach((v, i) => {
|
||||
v != null && pts.push(i);
|
||||
});
|
||||
return pts;
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
return opts;
|
||||
}
|
||||
|
||||
tooltipCursor(u, seriestt, opts, resolution, defaultVal) {
|
||||
const left = u.cursor.left;
|
||||
const idx = u.cursor.idx;
|
||||
const col = [];
|
||||
|
||||
if (opts && opts.cursorMemo) {
|
||||
opts.cursorMemo.set(left, top);
|
||||
}
|
||||
|
||||
seriestt.style.display = 'none';
|
||||
|
||||
if (left >= 0 && u.data) {
|
||||
let xVal = u.data[0][idx];
|
||||
|
||||
let maxLvl = u.data.length - 1;
|
||||
|
||||
for (let i = 0; i <= maxLvl; i++) {
|
||||
col.push(u.data[i][idx]);
|
||||
}
|
||||
|
||||
const vtp = (resolution === 'day') ? 'DAY' : ((resolution === 'hour') ? 'HOUR' : 'MINUTE');
|
||||
let ts = '';
|
||||
|
||||
if (Number.isInteger(xVal)) {
|
||||
const useTime = resolution === 'hour' || resolution === 'minute';
|
||||
ts = formatIntTimeUtc(xVal * 1000, useTime);
|
||||
}
|
||||
|
||||
let frag = document.createDocumentFragment();
|
||||
frag.appendChild(document.createTextNode(ts.replace(/\./g, '/')));
|
||||
|
||||
let prev = null;
|
||||
|
||||
let maxVal = 0;
|
||||
let maxIdx = 0;
|
||||
|
||||
for (let i = maxLvl; i >= 1; i--) {
|
||||
if (col[i] === null) {
|
||||
col[i] = 0;
|
||||
} else {
|
||||
if (maxVal < col[i]) {
|
||||
maxVal = col[i];
|
||||
maxIdx = i;
|
||||
}
|
||||
|
||||
if (prev !== null) {
|
||||
col[prev] -= col[i];
|
||||
}
|
||||
|
||||
prev = i;
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 2; i <= maxLvl; i++) {
|
||||
frag = this.extendTooltipFragment(i, null, col, defaultVal, u, frag);
|
||||
}
|
||||
|
||||
if (frag.children.length > 1) {
|
||||
seriestt.replaceChildren(frag);
|
||||
|
||||
seriestt.style.top = Math.round(u.valToPos(maxVal, u.series[maxIdx].scale)) + 'px';
|
||||
seriestt.style.left = Math.round(u.valToPos(xVal, vtp)) + 'px';
|
||||
seriestt.style.display = null;
|
||||
}
|
||||
}
|
||||
|
||||
return [seriestt, opts];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,380 @@
|
||||
import {Loader} from '../Loader.js?v=2';
|
||||
import {getQueryParams} from '../utils/DataSource.js?v=2';
|
||||
import {handleAjaxError} from '../utils/ErrorHandler.js?v=2';
|
||||
import {formatIntTimeUtc} from '../utils/Date.js?v=2';
|
||||
import {fireEvent} from '../utils/Event.js?v=2';
|
||||
import {renderChartTooltipPart} from '../DataRenderers.js?v=2';
|
||||
import {
|
||||
MAX_HOURS_CHART,
|
||||
MIN_HOURS_CHART,
|
||||
X_AXIS_SERIFS,
|
||||
} from '../utils/Constants.js?v=2';
|
||||
|
||||
export class BaseChart {
|
||||
constructor(chartParams) {
|
||||
this.config = chartParams;
|
||||
|
||||
this.cursLeft = -10;
|
||||
this.cursTop = -10;
|
||||
this.cursorMemo = {
|
||||
set: (left, top) => {
|
||||
this.cursLeft = left;
|
||||
this.cursTop = top;
|
||||
},
|
||||
get: () => ({
|
||||
left: this.cursLeft,
|
||||
top: this.cursTop,
|
||||
y: false,
|
||||
drag: {
|
||||
x: false,
|
||||
y: false
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
this.timeLabelColor = '#d7e6e1';
|
||||
|
||||
this.loader = new Loader();
|
||||
|
||||
const loaderDiv = document.createElement('div');
|
||||
loaderDiv.id = 'loader';
|
||||
this.chartBlock.appendChild(loaderDiv);
|
||||
|
||||
this.chart = null;
|
||||
if (!this.config.sequential) {
|
||||
this.loadData();
|
||||
}
|
||||
|
||||
const onDateFilterChanged = this.onDateFilterChanged.bind(this);
|
||||
window.addEventListener('dateFilterChanged', onDateFilterChanged, false);
|
||||
}
|
||||
|
||||
onDateFilterChanged() {
|
||||
this.loadData();
|
||||
}
|
||||
|
||||
stopAnimation() {
|
||||
this.loaderBlock.classList.add('is-hidden');
|
||||
this.loader.stop();
|
||||
}
|
||||
|
||||
startLoader() {
|
||||
const el = document.createElement('p');
|
||||
el.className = 'text-loader';
|
||||
|
||||
this.loaderBlock.classList.remove('is-hidden');
|
||||
this.loaderBlock.replaceChildren(el);
|
||||
|
||||
const p = this.loaderBlock.querySelector('p');
|
||||
|
||||
this.loader.start(p);
|
||||
}
|
||||
|
||||
loadData() {
|
||||
if (!this.config.sequential) {
|
||||
this.startLoader();
|
||||
}
|
||||
|
||||
const token = document.head.querySelector('[name=\'csrf-token\'][content]').content;
|
||||
const params = this.config.getParams();
|
||||
const data = getQueryParams(params);
|
||||
|
||||
data['mode'] = params.mode;
|
||||
data['token'] = token;
|
||||
data['resolution'] = 'day';
|
||||
if (data['dateFrom']) {
|
||||
const diff = new Date(data['dateTo']) - new Date(data['dateFrom']);
|
||||
const hours = diff/(60 * 60 * 1000);
|
||||
if (hours <= MAX_HOURS_CHART && hours > MIN_HOURS_CHART) {
|
||||
data['resolution'] = 'hour';
|
||||
} else if (hours <= MIN_HOURS_CHART) {
|
||||
data['resolution'] = 'minute';
|
||||
}
|
||||
}
|
||||
|
||||
fireEvent('dateFilterChangedCaught');
|
||||
|
||||
$.ajax({
|
||||
url: '/admin/loadChart',
|
||||
type: 'get',
|
||||
data: data,
|
||||
success: (responseData, status) => this.onChartLoaded(responseData, status, data['resolution']),
|
||||
error: handleAjaxError,
|
||||
complete: function() {
|
||||
fireEvent('dateFilterChangedCompleted');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
onChartLoaded(data, status, resolution) {
|
||||
if ('success' == status) {
|
||||
if (this.chart) {
|
||||
this.chart.destroy();
|
||||
}
|
||||
const prepData = this.getData(data);
|
||||
this.chart = new uPlot(this.getOptions(resolution), prepData, this.chartBlock);
|
||||
|
||||
this.stopAnimation();
|
||||
}
|
||||
}
|
||||
|
||||
seriesResolutionShift(series, resolution) {
|
||||
if (resolution === 'hour') {
|
||||
series[0].label = 'Hour';
|
||||
series[0].scale = 'HOUR';
|
||||
series[0].value = '{YYYY}-{MM}-{DD} {HH}:{mm}';
|
||||
} else if (resolution === 'minute') {
|
||||
series[0].label = 'Minute';
|
||||
series[0].scale = 'MINUTE';
|
||||
series[0].value = '{YYYY}-{MM}-{DD} {HH}:{mm}';
|
||||
}
|
||||
|
||||
return series;
|
||||
}
|
||||
|
||||
getDaySeries() {
|
||||
return {
|
||||
label: 'Day',
|
||||
scale: 'DAY',
|
||||
value: '{YYYY}-{MM}-{DD}'
|
||||
};
|
||||
}
|
||||
|
||||
getAxisConfig() {
|
||||
const xAxis = {
|
||||
scale: 'DAY',
|
||||
stroke: '#8180a0',
|
||||
grid: {
|
||||
width: 1 / devicePixelRatio,
|
||||
stroke: '#2b2a3d',
|
||||
},
|
||||
ticks: {
|
||||
width: 1 / devicePixelRatio,
|
||||
stroke: '#2b2a3d',
|
||||
},
|
||||
values: [
|
||||
//Copied from https://github.com/leeoniya/uPlot/tree/master/docs#axis--grid-opts
|
||||
// tick incr default year month day hour min sec mode
|
||||
[3600 * 24, '{DD}/{MM}', '\n{YYYY}', null, null, null, null, null, 1],
|
||||
],
|
||||
};
|
||||
const yAxis = {
|
||||
stroke: '#8180a0',
|
||||
values: (u, vals, space) => vals.map(v => this.formatKiloValue(u, v)),
|
||||
grid: {
|
||||
width: 1 / devicePixelRatio,
|
||||
stroke: '#2b2a3d',
|
||||
},
|
||||
ticks: {
|
||||
width: 1 / devicePixelRatio,
|
||||
stroke: '#2b2a3d',
|
||||
},
|
||||
};
|
||||
|
||||
return {
|
||||
x: xAxis,
|
||||
y: yAxis,
|
||||
};
|
||||
}
|
||||
|
||||
getData(data) {
|
||||
return data;
|
||||
}
|
||||
|
||||
getOptions(resolution = 'day', nullChar = '0') {
|
||||
const tooltipsPlugin = this.tooltipsPlugin({cursorMemo: this.cursorMemo}, resolution, nullChar);
|
||||
const axes = this.getAxisConfig();
|
||||
const series = this.seriesResolutionShift(this.getSeries(), resolution);
|
||||
const xAxis = this.xAxisResolutionShift(axes.x, resolution);
|
||||
const yAxis = axes.y;
|
||||
|
||||
const opts = {
|
||||
width: 995,
|
||||
height: 200,
|
||||
|
||||
tzDate: ts => uPlot.tzDate(new Date(ts * 1000), 'Etc/UTC'),
|
||||
series: series,
|
||||
|
||||
legend: {
|
||||
show: false
|
||||
},
|
||||
cursor: this.cursorMemo.get(),
|
||||
plugins: [tooltipsPlugin],
|
||||
scales: {
|
||||
x: {time: false},
|
||||
},
|
||||
axes: [
|
||||
xAxis,
|
||||
yAxis,
|
||||
]
|
||||
};
|
||||
|
||||
return opts;
|
||||
}
|
||||
|
||||
xAxisResolutionShift(xAxis, resolution) {
|
||||
if (resolution === 'hour') {
|
||||
xAxis.scale = 'HOUR';
|
||||
xAxis.values = [
|
||||
// tick incr default year month day hour min sec mode
|
||||
[3600, '{HH}:{mm}', '\n{DD}/{MM}/{YYYY}', null, '\n{DD}/{MM}', null, null, null, 1]
|
||||
];
|
||||
xAxis.space = function(self, axisIdx, scaleMin, scaleMax, plotDim) {
|
||||
let rangeHours = (scaleMax - scaleMin) / 3600;
|
||||
if (rangeHours > X_AXIS_SERIFS) rangeHours = X_AXIS_SERIFS;
|
||||
const pxPerHour = plotDim / rangeHours;
|
||||
|
||||
return pxPerHour;
|
||||
};
|
||||
} else if (resolution === 'minute') {
|
||||
xAxis.scale = 'MINUTE';
|
||||
xAxis.values = [
|
||||
// tick incr default year month day hour min sec mode
|
||||
[60, '{HH}:{mm}', '\n{DD}/{MM}/{YYYY}', null, '\n{DD}/{MM}', null, null, null, 1]
|
||||
];
|
||||
xAxis.space = function(self, axisIdx, scaleMin, scaleMax, plotDim) {
|
||||
let rangeMinutes = (scaleMax - scaleMin) / 60;
|
||||
if (rangeMinutes > X_AXIS_SERIFS) rangeMinutes = X_AXIS_SERIFS;
|
||||
const pxPerMinute = plotDim / rangeMinutes;
|
||||
|
||||
return pxPerMinute;
|
||||
};
|
||||
}
|
||||
|
||||
return xAxis;
|
||||
}
|
||||
|
||||
formatKiloValue(u, value) {
|
||||
if (value === 0) {
|
||||
return value;
|
||||
}
|
||||
if (value % 1000000 === 0) {
|
||||
return Math.round(value / 1000000) + 'M';
|
||||
}
|
||||
if (value % 1000 === 0) {
|
||||
return Math.round(value / 1000) + 'k';
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
get loaderBlock() {
|
||||
return document.getElementById('loader');
|
||||
}
|
||||
|
||||
get chartBlock() {
|
||||
return document.querySelector('.stat-chart:not(#session-stat)');
|
||||
}
|
||||
|
||||
tooltipsPlugin(opts, resolution = 'day', defaultVal = '0') {
|
||||
let self = this;
|
||||
let seriestt;
|
||||
|
||||
function init(u, options, data) {
|
||||
seriestt = self.tooltipInit(u, options, data);
|
||||
}
|
||||
|
||||
function setCursor(u) {
|
||||
[seriestt, opts] = self.tooltipCursor(u, seriestt, opts, resolution, defaultVal);
|
||||
}
|
||||
|
||||
return {
|
||||
hooks: {
|
||||
init,
|
||||
setCursor,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
tooltipCursor(u, seriestt, opts, resolution, defaultVal) {
|
||||
const left = u.cursor.left;
|
||||
const idx = u.cursor.idx;
|
||||
|
||||
if (opts && opts.cursorMemo) {
|
||||
opts.cursorMemo.set(left, top);
|
||||
}
|
||||
|
||||
seriestt.style.display = 'none';
|
||||
|
||||
if (left >= 0) {
|
||||
let xVal = u.data[0][idx];
|
||||
|
||||
const vtp = (resolution === 'day') ? 'DAY' : ((resolution === 'hour') ? 'HOUR' : 'MINUTE');
|
||||
let ts = '';
|
||||
|
||||
if (Number.isInteger(xVal)) {
|
||||
const useTime = resolution === 'hour' || resolution === 'minute';
|
||||
ts = formatIntTimeUtc(xVal * 1000, useTime);
|
||||
}
|
||||
|
||||
let frag = document.createDocumentFragment();
|
||||
frag.appendChild(renderChartTooltipPart(this.timeLabelColor, null, ts.replace(/\./g, '/')));
|
||||
|
||||
for (let i = 1; i <= 12; i++) {
|
||||
frag = this.extendTooltipFragment(i, idx, u.data, defaultVal, u, frag);
|
||||
}
|
||||
|
||||
if (frag.children.length > 1) {
|
||||
seriestt.replaceChildren(frag);
|
||||
|
||||
let val = null;
|
||||
let lvl = 1;
|
||||
|
||||
const lim = Math.min(u.data.length - 1, 12);
|
||||
|
||||
for (let i = 1; i <= lim; i++) {
|
||||
if (u.data[i][idx] > val) {
|
||||
val = u.data[i][idx];
|
||||
lvl = i;
|
||||
}
|
||||
}
|
||||
|
||||
val = (val !== null && val != undefined) ? val : defaultVal;
|
||||
|
||||
seriestt.style.top = Math.round(u.valToPos(val, u.series[lvl].scale)) + 'px';
|
||||
seriestt.style.left = Math.round(u.valToPos(xVal, vtp)) + 'px';
|
||||
seriestt.style.display = null;
|
||||
}
|
||||
}
|
||||
|
||||
return [seriestt, opts];
|
||||
}
|
||||
|
||||
tooltipInit(u, options, data) {
|
||||
let over = u.over;
|
||||
|
||||
let tt = document.createElement('div');
|
||||
tt.className = 'tooltipline';
|
||||
tt.textContent = '';
|
||||
tt.style.pointerEvents = 'none';
|
||||
tt.style.position = 'absolute';
|
||||
tt.style.background = 'rgba(0,0,0,1)';
|
||||
over.appendChild(tt);
|
||||
|
||||
over.addEventListener('mouseleave', () => {
|
||||
if (!u.cursor._lock) {
|
||||
tt.style.display = 'none';
|
||||
}
|
||||
});
|
||||
|
||||
over.addEventListener('mouseenter', () => {
|
||||
tt.style.display = u.data.length > 1 ? null : 'none';
|
||||
});
|
||||
|
||||
tt.style.display = (u.cursor.left < 0) ? 'none' : null;
|
||||
|
||||
return tt;
|
||||
}
|
||||
|
||||
extendTooltipFragment(lvl, idx, data, defaultVal, u, frag) {
|
||||
if (data.length > lvl) {
|
||||
let series = u.series[lvl];
|
||||
let val = (idx !== null) ? data[lvl][idx] : data[lvl];
|
||||
val = (val !== null && val != undefined) ? val : defaultVal;
|
||||
|
||||
frag.appendChild(document.createElement('br'));
|
||||
frag.appendChild(renderChartTooltipPart(series.stroke(), series.label, val));
|
||||
}
|
||||
|
||||
return frag;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
import {BaseChart} from './BaseChart.js?v=2';
|
||||
import {
|
||||
COLOR_MAP,
|
||||
X_AXIS_SERIFS,
|
||||
} from '../utils/Constants.js?v=2';
|
||||
|
||||
export class BaseLineChart extends BaseChart {
|
||||
getSeries() {
|
||||
return [
|
||||
this.getDaySeries(),
|
||||
this.getSingleSeries('Total events', 'green'),
|
||||
];
|
||||
}
|
||||
|
||||
getSingleSeries(label, color) {
|
||||
return {
|
||||
label: label,
|
||||
scale: 'EVENTS',
|
||||
value: (u, v) => Number(v.toFixed(0)).toLocaleString(),
|
||||
points: {
|
||||
space: 0,
|
||||
fill: COLOR_MAP[color].main,
|
||||
},
|
||||
stroke: COLOR_MAP[color].main,
|
||||
fill: COLOR_MAP[color].light,
|
||||
};
|
||||
}
|
||||
|
||||
getAxisConfig() {
|
||||
const axes = super.getAxisConfig();
|
||||
|
||||
axes.x.space = function(self, axisIdx, scaleMin, scaleMax, plotDim) {
|
||||
let rangeDays = (scaleMax - scaleMin) / 86400;
|
||||
if (rangeDays > X_AXIS_SERIFS) rangeDays = X_AXIS_SERIFS;
|
||||
const pxPerDay = plotDim / rangeDays;
|
||||
|
||||
return pxPerDay;
|
||||
};
|
||||
|
||||
axes.y.scale = 'EVENTS';
|
||||
axes.y.side = 3;
|
||||
axes.y.split = u => [
|
||||
u.series[1].min,
|
||||
u.series[1].max,
|
||||
];
|
||||
|
||||
return axes;
|
||||
}
|
||||
|
||||
getOptions(resolution = 'day') {
|
||||
return super.getOptions(resolution, '—');
|
||||
}
|
||||
|
||||
// invert lines order to keep originally first line on top layer
|
||||
seriesResolutionShift(series, resolution) {
|
||||
if (resolution === 'hour') {
|
||||
series[0].label = 'Hour';
|
||||
series[0].scale = 'HOUR';
|
||||
series[0].value = '{YYYY}-{MM}-{DD} {HH}:{mm}';
|
||||
} else if (resolution === 'minute') {
|
||||
series[0].label = 'Minute';
|
||||
series[0].scale = 'MINUTE';
|
||||
series[0].value = '{YYYY}-{MM}-{DD} {HH}:{mm}';
|
||||
}
|
||||
|
||||
const inverted = [series[0]].concat(series.slice(1).reverse());
|
||||
|
||||
return inverted;
|
||||
}
|
||||
|
||||
getData(data) {
|
||||
return [data[0]].concat(data.slice(1).reverse());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
import {Loader} from '../Loader.js?v=2';
|
||||
import {BaseChart} from './BaseChart.js?v=2';
|
||||
import {
|
||||
COLOR_LIGHT_GREEN,
|
||||
COLOR_GREEN,
|
||||
X_AXIS_SERIFS,
|
||||
} from '../utils/Constants.js?v=2';
|
||||
|
||||
export class BaseSparklineChart extends BaseChart {
|
||||
constructor(chartParams) {
|
||||
super(chartParams);
|
||||
|
||||
this.charts = null;
|
||||
|
||||
if (!this.loaders) {
|
||||
this.loaders = [];
|
||||
this.elems.forEach(el => {this.loaders[el] = new Loader();});
|
||||
}
|
||||
}
|
||||
|
||||
getOptions() {
|
||||
const tooltipsPlugin = this.tooltipsPlugin({cursorMemo: this.cursorMemo}, 'day', '0');
|
||||
return {
|
||||
width: 200,
|
||||
height: 30,
|
||||
pxAlign: false,
|
||||
cursor: {
|
||||
show: false
|
||||
},
|
||||
select: {
|
||||
show: false,
|
||||
},
|
||||
legend: {
|
||||
show: false,
|
||||
},
|
||||
scales: {
|
||||
x: {time: false},
|
||||
},
|
||||
axes: [
|
||||
{show: false},
|
||||
{show: false}
|
||||
],
|
||||
cursor: this.cursorMemo.get(),
|
||||
plugins: [tooltipsPlugin],
|
||||
series: [
|
||||
{
|
||||
label: 'Day',
|
||||
scale: 'DAY',
|
||||
value: '{YYYY}-{MM}-{DD}',
|
||||
stroke: '#8180a0',
|
||||
},
|
||||
{
|
||||
label: 'This week',
|
||||
stroke: COLOR_GREEN,
|
||||
fill: COLOR_LIGHT_GREEN,
|
||||
points: {show: false}
|
||||
},
|
||||
{
|
||||
label: 'Previous week',
|
||||
stroke: 'rgba(129,128,160,0.7)',
|
||||
fill: 'rgba(129,128,160,0.03)',
|
||||
points: {show: false}
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
onChartLoaded(data, status, resolution) {
|
||||
if ('success' == status) {
|
||||
data = this.getData(data);
|
||||
|
||||
this.stopLoader();
|
||||
|
||||
this.charts = [];
|
||||
|
||||
this.elems.forEach(el => {
|
||||
const lines = [data.time, data[el], data[el + 'Prev']];
|
||||
this.charts.push(new uPlot(this.getOptions(), lines, this.getChartBlock(el)));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
startLoader() {
|
||||
if (!this.loaders) {
|
||||
this.loaders = [];
|
||||
this.elems.forEach(el => this.loaders[el] = new Loader());
|
||||
}
|
||||
|
||||
this.elems.forEach(name => {
|
||||
const el = document.createElement('p');
|
||||
const block = this.getChartBlock(name);
|
||||
|
||||
block.classList.remove('is-hidden');
|
||||
block.replaceChildren(el);
|
||||
|
||||
const p = block.querySelector('p');
|
||||
|
||||
this.loaders[name].start(p);
|
||||
});
|
||||
}
|
||||
|
||||
stopLoader() {
|
||||
this.elems.forEach(el => {
|
||||
this.loaders[el].stop();
|
||||
this.getChartBlock(el).querySelector('p').classList.add('is-hidden');
|
||||
});
|
||||
}
|
||||
|
||||
getData(data) {
|
||||
return {
|
||||
'time': data[0],
|
||||
'totalDevices': data[1],
|
||||
'totalIps': data[2],
|
||||
'totalSessions': data[3],
|
||||
'totalEvents': data[4],
|
||||
'totalDevicesPrev': data[5],
|
||||
'totalIpsPrev': data[6],
|
||||
'totalSessionsPrev': data[7],
|
||||
'totalEventsPrev': data[8],
|
||||
};
|
||||
}
|
||||
|
||||
getChartBlock(cls) {
|
||||
return document.querySelector(`td.${cls} p.session-stat`);
|
||||
}
|
||||
|
||||
get chartBlocks() {
|
||||
const result = {};
|
||||
this.elems.forEach(el => {result[el] = this.getChartBlock(el);});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
get elems() {
|
||||
return ['totalDevices', 'totalIps', 'totalSessions', 'totalEvents'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import {BaseLineChart} from './BaseLine.js?v=2';
|
||||
|
||||
export class BlacklistChart extends BaseLineChart {
|
||||
getSeries() {
|
||||
return [
|
||||
this.getDaySeries(),
|
||||
this.getSingleSeries('Blacklisted identities', 'red'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import {BaseLineChart} from './BaseLine.js?v=2';
|
||||
|
||||
export class BotsChart extends BaseLineChart {
|
||||
getSeries() {
|
||||
return [
|
||||
this.getDaySeries(),
|
||||
this.getSingleSeries('Bots', 'red'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import {BaseLineChart} from './BaseLine.js?v=2';
|
||||
|
||||
export class DomainsChart extends BaseLineChart {
|
||||
getSeries() {
|
||||
return [
|
||||
this.getDaySeries(),
|
||||
this.getSingleSeries('Total domains', 'green'),
|
||||
this.getSingleSeries('New domains', 'yellow'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import {BaseLineChart} from './BaseLine.js?v=2';
|
||||
|
||||
export class EventsChart extends BaseLineChart {
|
||||
getSeries() {
|
||||
return [
|
||||
this.getDaySeries(),
|
||||
this.getSingleSeries('Regular events', 'green'),
|
||||
this.getSingleSeries('Warning events', 'yellow'),
|
||||
this.getSingleSeries('Alert events', 'red'),
|
||||
this.getSingleSeries('Unauthenticated events', 'purple'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import {BaseLineChart} from './BaseLine.js?v=2';
|
||||
|
||||
export class IpsChart extends BaseLineChart {
|
||||
getSeries() {
|
||||
return [
|
||||
this.getDaySeries(),
|
||||
this.getSingleSeries('Residential', 'green'),
|
||||
this.getSingleSeries('Privacy', 'yellow'),
|
||||
this.getSingleSeries('Suspicious', 'red'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import {BaseLineChart} from './BaseLine.js?v=2';
|
||||
|
||||
export class IspsChart extends BaseLineChart {
|
||||
getSeries() {
|
||||
return [
|
||||
this.getDaySeries(),
|
||||
this.getSingleSeries('Total ISPs', 'green'),
|
||||
this.getSingleSeries('New ISPs', 'yellow'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import {BaseLineChart} from './BaseLine.js?v=2';
|
||||
|
||||
export class LogbookChart extends BaseLineChart {
|
||||
getSeries() {
|
||||
return [
|
||||
this.getDaySeries(),
|
||||
this.getSingleSeries('Success', 'green'),
|
||||
this.getSingleSeries('Validation issues', 'yellow'),
|
||||
this.getSingleSeries('Failed', 'red'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import {BaseLineChart} from './BaseLine.js?v=2';
|
||||
|
||||
export class ResourcesChart extends BaseLineChart {
|
||||
getSeries() {
|
||||
return [
|
||||
this.getDaySeries(),
|
||||
this.getSingleSeries('200', 'green'),
|
||||
this.getSingleSeries('404', 'yellow'),
|
||||
this.getSingleSeries('403 & 500', 'red'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import {BaseLineChart} from './BaseLine.js?v=2';
|
||||
|
||||
export class ReviewQueueChart extends BaseLineChart {
|
||||
getSeries() {
|
||||
return [
|
||||
this.getDaySeries(),
|
||||
this.getSingleSeries('Whitelisted', 'green'),
|
||||
this.getSingleSeries('In review', 'yellow'),
|
||||
this.getSingleSeries('Blacklisted', 'red'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import {BaseLineChart} from './BaseLine.js?v=2';
|
||||
|
||||
export class UsersChart extends BaseLineChart {
|
||||
getSeries() {
|
||||
return [
|
||||
this.getDaySeries(),
|
||||
this.getSingleSeries('High trust', 'green'),
|
||||
this.getSingleSeries('Average trust', 'yellow'),
|
||||
this.getSingleSeries('In review', 'red'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import {fireEvent} from '../utils/Event.js?v=2';
|
||||
|
||||
export class BaseFilter {
|
||||
constructor(selectorId, renderItemFn, renderChoiceFn, eventType) {
|
||||
this.selectorId = selectorId;
|
||||
this.renderItemFn = renderItemFn;
|
||||
this.renderChoiceFn = renderChoiceFn;
|
||||
this.eventType = eventType;
|
||||
|
||||
const renderItem = renderItemFn;
|
||||
const renderChoice = renderChoiceFn;
|
||||
|
||||
const choices = new Choices(`${this.selectorId} select`, {
|
||||
removeItemButton: true,
|
||||
allowHTML: true,
|
||||
callbackOnCreateTemplates: function(strToEl) {
|
||||
const {classNames, itemSelectText} = this.config;
|
||||
return {
|
||||
item: function({classNames}, data) {
|
||||
return strToEl(renderItem(classNames, data));
|
||||
},
|
||||
choice: function({classNames}, data) {
|
||||
return strToEl(renderChoice(classNames, data, itemSelectText));
|
||||
},
|
||||
};
|
||||
}
|
||||
});
|
||||
choices.passedElement.element.addEventListener(
|
||||
'change',
|
||||
() => fireEvent(this.eventType)
|
||||
);
|
||||
}
|
||||
|
||||
getValues() {
|
||||
return Array.from(document.querySelector(`${this.selectorId} select`).options)
|
||||
.filter(option => option.selected)
|
||||
.map(option => option.value);
|
||||
}
|
||||
|
||||
getEventType() {
|
||||
return this.eventType;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import {BaseFilter} from './BaseFilter.js?v=2';
|
||||
import {
|
||||
renderDeviceTypeSelectorItem,
|
||||
renderDeviceTypeSelectorChoice,
|
||||
} from '../DataRenderers.js?v=2';
|
||||
|
||||
export class DeviceTypeFilter extends BaseFilter {
|
||||
constructor() {
|
||||
super(
|
||||
'#device-type-selectors',
|
||||
renderDeviceTypeSelectorItem,
|
||||
renderDeviceTypeSelectorChoice,
|
||||
'deviceTypeFilterChanged'
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import {BaseFilter} from './BaseFilter.js?v=2';
|
||||
import {
|
||||
renderEntityTypeSelectorItem,
|
||||
renderEntityTypeSelectorChoice,
|
||||
} from '../DataRenderers.js?v=2';
|
||||
|
||||
export class EntityTypeFilter extends BaseFilter {
|
||||
constructor() {
|
||||
super(
|
||||
'#entity-type-selectors',
|
||||
renderEntityTypeSelectorItem,
|
||||
renderEntityTypeSelectorChoice,
|
||||
'entityTypeFilterChanged'
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import {BaseFilter} from './BaseFilter.js?v=2';
|
||||
import {
|
||||
renderEventTypeSelectorItem,
|
||||
renderEventTypeSelectorChoice,
|
||||
} from '../DataRenderers.js?v=2';
|
||||
|
||||
export class EventTypeFilter extends BaseFilter {
|
||||
constructor() {
|
||||
super(
|
||||
'#event-type-selectors',
|
||||
renderEventTypeSelectorItem,
|
||||
renderEventTypeSelectorChoice,
|
||||
'eventTypeFilterChanged'
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import {BaseFilter} from './BaseFilter.js?v=2';
|
||||
import {
|
||||
renderIpTypeSelectorItem,
|
||||
renderIpTypeSelectorChoice,
|
||||
} from '../DataRenderers.js?v=2';
|
||||
|
||||
export class IpTypeFilter extends BaseFilter {
|
||||
constructor() {
|
||||
super(
|
||||
'#ip-type-selectors',
|
||||
renderIpTypeSelectorItem,
|
||||
renderIpTypeSelectorChoice,
|
||||
'ipTypeFilterChanged'
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import {BaseFilter} from './BaseFilter.js?v=2';
|
||||
import {
|
||||
renderRuleSelectorItem,
|
||||
renderRuleSelectorChoice,
|
||||
} from '../DataRenderers.js?v=2';
|
||||
|
||||
export class RulesFilter extends BaseFilter {
|
||||
constructor() {
|
||||
super(
|
||||
'#rule-selectors',
|
||||
renderRuleSelectorItem,
|
||||
renderRuleSelectorChoice,
|
||||
'rulesFilterChanged'
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import {BaseFilter} from './BaseFilter.js?v=2';
|
||||
import {
|
||||
renderScoresRangeSelectorItem,
|
||||
renderScoresRangeSelectorChoice,
|
||||
} from '../DataRenderers.js?v=2';
|
||||
|
||||
export class ScoresRangeFilter extends BaseFilter {
|
||||
constructor() {
|
||||
super(
|
||||
'#scores-range-selectors',
|
||||
renderScoresRangeSelectorItem,
|
||||
renderScoresRangeSelectorChoice,
|
||||
'scoresRangeFilterChanged'
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
import {Loader} from '../Loader.js?v=2';
|
||||
import {Tooltip} from '../Tooltip.js?v=2';
|
||||
import {handleAjaxError} from '../utils/ErrorHandler.js?v=2';
|
||||
import {fireEvent} from '../utils/Event.js?v=2';
|
||||
|
||||
export class BaseTiles {
|
||||
|
||||
constructor(tilesParams) {
|
||||
const me = this;
|
||||
this.config = tilesParams;
|
||||
this.loaders = {};
|
||||
|
||||
this.elems.forEach(elem => {
|
||||
me.loaders[elem] = new Loader();
|
||||
});
|
||||
|
||||
if (!this.config.sequential) {
|
||||
this.loadData();
|
||||
}
|
||||
}
|
||||
|
||||
loadData() {
|
||||
const me = this;
|
||||
const url = this.url;
|
||||
const params = this.config.getParams();
|
||||
const token = document.head.querySelector('[name=\'csrf-token\'][content]').content;
|
||||
|
||||
if (!this.config.sequential) {
|
||||
this.startLoaders();
|
||||
}
|
||||
|
||||
fireEvent('dateFilterChangedCaught');
|
||||
|
||||
$.ajax({
|
||||
url: `${url}?token=${token}`,
|
||||
type: 'get',
|
||||
scope: me,
|
||||
data: params,
|
||||
success: function(response) {
|
||||
me.onLoad(response, 'success');
|
||||
me.initTooltips();
|
||||
},
|
||||
error: handleAjaxError,
|
||||
});
|
||||
}
|
||||
|
||||
stopLoaders() {
|
||||
for (const property in this.loaders) {
|
||||
this.loaders[property].stop();
|
||||
}
|
||||
}
|
||||
|
||||
startLoaders() {
|
||||
for (const property in this.loaders) {
|
||||
const el = document.querySelector(`#${property}`);
|
||||
this.loaders[property].start(el);
|
||||
}
|
||||
}
|
||||
|
||||
startLoader() {
|
||||
this.startLoaders();
|
||||
}
|
||||
|
||||
onLoad(data, status) {
|
||||
if ('success' == status) {
|
||||
this.stopLoaders();
|
||||
this.updateTiles(data);
|
||||
fireEvent('dateFilterChangedCompleted');
|
||||
}
|
||||
}
|
||||
|
||||
initTooltips() {
|
||||
Tooltip.addTooltipsToTiles();
|
||||
}
|
||||
|
||||
updateTiles(data) {}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import {BaseTiles} from './BaseTiles.js?v=2';
|
||||
import {
|
||||
renderBoolean,
|
||||
renderDefaultIfEmptyElement,
|
||||
renderBrowser,
|
||||
renderOs,
|
||||
} from '../DataRenderers.js?v=2';
|
||||
|
||||
const URL = '/admin/loadBotDetails';
|
||||
const ELEMS = ['title', 'os', 'browser', 'modified'];
|
||||
|
||||
export class BotTiles extends BaseTiles {
|
||||
updateTiles(data) {
|
||||
|
||||
const os = [];
|
||||
if (data.os_name) os.push(data.os_name);
|
||||
if (data.os_version) os.push(data.os_version);
|
||||
|
||||
const browser = [];
|
||||
if (data.browser_name) browser.push(data.browser_name);
|
||||
if (data.browser_version) browser.push(data.browser_version);
|
||||
|
||||
const record = {
|
||||
os: os.join(' '),
|
||||
browser: browser.join(' ')
|
||||
};
|
||||
|
||||
document.getElementById('title').replaceChildren(renderDefaultIfEmptyElement(data.title));
|
||||
document.getElementById('os').replaceChildren(renderOs(record));
|
||||
document.getElementById('browser').replaceChildren(renderBrowser(record));
|
||||
document.getElementById('modified').replaceChildren(renderBoolean(data.modified));
|
||||
}
|
||||
|
||||
get elems() {
|
||||
return ELEMS;
|
||||
}
|
||||
|
||||
get url() {
|
||||
return URL;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import {BaseTiles} from './BaseTiles.js?v=2';
|
||||
import {
|
||||
renderBoolean,
|
||||
renderDefaultIfEmptyElement,
|
||||
renderDate,
|
||||
} from '../DataRenderers.js?v=2';
|
||||
|
||||
const URL = '/admin/loadDomainDetails';
|
||||
const ELEMS = [
|
||||
'free-email', 'tranco-rank', 'unavailable', 'disposable',
|
||||
'creation-date', 'expiration-date', 'total-account', 'fraud'];
|
||||
|
||||
export class DomainTiles extends BaseTiles {
|
||||
updateTiles(data) {
|
||||
document.getElementById('free-email').replaceChildren(renderBoolean(data.free_email_provider));
|
||||
document.getElementById('tranco-rank').replaceChildren(renderDefaultIfEmptyElement(data.tranco_rank));
|
||||
document.getElementById('unavailable').replaceChildren(renderBoolean(data.disabled));
|
||||
document.getElementById('disposable').replaceChildren(renderBoolean(data.disposable_domains));
|
||||
|
||||
document.getElementById('creation-date').replaceChildren(renderDate(data.creation_date));
|
||||
document.getElementById('expiration-date').replaceChildren(renderDate(data.expiration_date));
|
||||
document.getElementById('total-account').replaceChildren(data.total_account);
|
||||
document.getElementById('fraud').replaceChildren(data.fraud);
|
||||
}
|
||||
|
||||
get elems() {
|
||||
return ELEMS;
|
||||
}
|
||||
|
||||
get url() {
|
||||
return URL;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import {BaseTiles} from './BaseTiles.js?v=2';
|
||||
import {Tooltip} from '../Tooltip.js?v=2';
|
||||
import {
|
||||
renderBoolean,
|
||||
renderClickableCountryTruncated,
|
||||
renderClickableAsn,
|
||||
} from '../DataRenderers.js?v=2';
|
||||
|
||||
const URL = '/admin/loadIpDetails';
|
||||
const ELEMS = ['country', 'asn', 'blocklist', 'blacklist', 'dc', 'vpn', 'tor', 'ar'];
|
||||
|
||||
export class IpTiles extends BaseTiles {
|
||||
updateTiles(data) {
|
||||
const record = {
|
||||
full_country: data.full_country,
|
||||
country_id: data.country_id,
|
||||
country_iso: data.country_iso,
|
||||
asn: data.asn,
|
||||
ispid: data.ispid,
|
||||
};
|
||||
|
||||
document.getElementById('country').replaceChildren(renderClickableCountryTruncated(record));
|
||||
document.getElementById('asn').replaceChildren(renderClickableAsn(record));
|
||||
document.getElementById('blocklist').replaceChildren(renderBoolean(data.blocklist));
|
||||
document.getElementById('blacklist').replaceChildren(renderBoolean(data.fraud_detected));
|
||||
document.getElementById('dc').replaceChildren(renderBoolean(data.data_center));
|
||||
document.getElementById('vpn').replaceChildren(renderBoolean(data.vpn));
|
||||
document.getElementById('tor').replaceChildren(renderBoolean(data.tor));
|
||||
document.getElementById('ar').replaceChildren(renderBoolean(data.relay));
|
||||
}
|
||||
|
||||
initTooltips() {
|
||||
super.initTooltips();
|
||||
Tooltip.addTooltipToSpans();
|
||||
}
|
||||
|
||||
get elems() {
|
||||
return ELEMS;
|
||||
}
|
||||
|
||||
get url() {
|
||||
return URL;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import {BaseTiles} from './BaseTiles.js?v=2';
|
||||
import {renderAsn} from '../DataRenderers.js?v=2';
|
||||
|
||||
const URL = '/admin/loadIspDetails';
|
||||
const ELEMS = ['asn', 'total-ips', 'total-visits', 'total-accounts', 'total-fraud'];
|
||||
|
||||
export class IspTiles extends BaseTiles {
|
||||
updateTiles(data) {
|
||||
document.getElementById('asn').replaceChildren(renderAsn(data));
|
||||
document.getElementById('total-accounts').replaceChildren(data.total_account);
|
||||
document.getElementById('total-visits').replaceChildren(data.total_visit);
|
||||
document.getElementById('total-fraud').replaceChildren(data.total_fraud);
|
||||
document.getElementById('total-ips').replaceChildren(data.total_ip);
|
||||
}
|
||||
|
||||
get elems() {
|
||||
return ELEMS;
|
||||
}
|
||||
|
||||
get url() {
|
||||
return URL;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
import {BaseTiles} from './BaseTiles.js?v=2';
|
||||
import {
|
||||
renderDate,
|
||||
renderBoolean,
|
||||
renderUserCounter,
|
||||
renderReputation,
|
||||
renderUserId,
|
||||
renderUserFirstname,
|
||||
renderUserLastname,
|
||||
renderUserReviewedStatus,
|
||||
} from '../DataRenderers.js?v=2';
|
||||
|
||||
const URL = '/admin/loadUserDetails';
|
||||
|
||||
export class UserTiles extends BaseTiles {
|
||||
updateTiles(data) {
|
||||
this.updateIdDetails(data);
|
||||
this.updateIpDetails(data);
|
||||
this.updateDayDetails(data);
|
||||
this.updateWeekDetails(data);
|
||||
}
|
||||
|
||||
updateIdDetails(data) {
|
||||
const tile = document.querySelector('#user-id-tile');
|
||||
|
||||
if (!tile) {
|
||||
return;
|
||||
}
|
||||
|
||||
const record = data.userDetails;
|
||||
this.removeLoaderBackground(tile);
|
||||
|
||||
tile.querySelector('#signup-date').replaceChildren(renderDate(record.created));
|
||||
tile.querySelector('#lastseen').replaceChildren(renderDate(record.lastseen));
|
||||
tile.querySelector('#latest-decision').replaceChildren(renderDate(record.latest_decision));
|
||||
tile.querySelector('#review-status').replaceChildren(renderUserReviewedStatus(record));
|
||||
tile.querySelector('#firstname').replaceChildren(renderUserFirstname(record));
|
||||
tile.querySelector('#lastname').replaceChildren(renderUserLastname(record));
|
||||
tile.querySelector('#userid').replaceChildren(renderUserId(record.userid));
|
||||
}
|
||||
|
||||
updateIpDetails(data) {
|
||||
const tile = document.querySelector('#user-ip-tile');
|
||||
|
||||
if (!tile) {
|
||||
return;
|
||||
}
|
||||
|
||||
const record = data.ipDetails;
|
||||
this.removeLoaderBackground(tile);
|
||||
|
||||
tile.querySelector('#datacenter').replaceChildren(renderBoolean(record.withdc));
|
||||
tile.querySelector('#vpn').replaceChildren(renderBoolean(record.withvpn));
|
||||
tile.querySelector('#tor').replaceChildren(renderBoolean(record.withtor));
|
||||
tile.querySelector('#apple-relay').replaceChildren(renderBoolean(record.withar));
|
||||
tile.querySelector('#ip-shared').replaceChildren(renderBoolean(record.sharedips));
|
||||
tile.querySelector('#spam-list').replaceChildren(renderBoolean(record.spamlist));
|
||||
tile.querySelector('#blacklisted').replaceChildren(renderBoolean(record.fraud_detected));
|
||||
}
|
||||
|
||||
updateDayDetails(data) {
|
||||
const limits = {
|
||||
median_event_cnt: 20,
|
||||
login_cnt: 3,
|
||||
session_cnt: 5,
|
||||
};
|
||||
|
||||
this.updateDateRangeDetails(data.dayDetails, '#day-behaviour-tile', limits);
|
||||
}
|
||||
|
||||
updateWeekDetails(data) {
|
||||
const limits = {
|
||||
median_event_cnt: 20,
|
||||
login_cnt: 10,
|
||||
session_cnt: 25,
|
||||
};
|
||||
|
||||
this.updateDateRangeDetails(data.weekDetails, '#week-behaviour-tile', limits);
|
||||
}
|
||||
|
||||
updateDateRangeDetails(record, tileId, limits) {
|
||||
const tile = document.querySelector(tileId);
|
||||
|
||||
if (!tile) {
|
||||
return;
|
||||
}
|
||||
|
||||
const span = document.createElement('span');
|
||||
span.className = 'nolight';
|
||||
span.textContent = 'N/A';
|
||||
|
||||
this.removeLoaderBackground(tile);
|
||||
|
||||
if (record.session_cnt === 0) {
|
||||
tile.querySelector('#failed-login-count').replaceChildren(span.cloneNode(true));
|
||||
tile.querySelector('#password-reset-count').replaceChildren(span.cloneNode(true));
|
||||
tile.querySelector('#auth-error-count').replaceChildren(span.cloneNode(true));
|
||||
tile.querySelector('#off-hours-login-count').replaceChildren(span.cloneNode(true));
|
||||
tile.querySelector('#median-event-count').replaceChildren(span.cloneNode(true));
|
||||
tile.querySelector('#login-count').replaceChildren(span.cloneNode(true));
|
||||
tile.querySelector('#session-count').replaceChildren(span.cloneNode(true));
|
||||
} else {
|
||||
const map = [
|
||||
['#failed-login-count', 'failed_login_cnt'],
|
||||
['#password-reset-count', 'password_reset_cnt'],
|
||||
['#auth-error-count', 'auth_error_cnt'],
|
||||
['#off-hours-login-count', 'off_hours_login_cnt'],
|
||||
['#median-event-count', 'median_event_cnt'],
|
||||
['#login-count', 'login_cnt'],
|
||||
['#session-count', 'session_cnt'],
|
||||
];
|
||||
|
||||
for (const [id, el] of map) {
|
||||
tile.querySelector(id).replaceChildren(renderUserCounter(record[el], limits[el] || 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
removeLoaderBackground(tile) {
|
||||
const backgrounds = tile.querySelectorAll('.loading-background');
|
||||
for (let i = 0; i < backgrounds.length; i++) {
|
||||
backgrounds[i].classList.remove('loading-background');
|
||||
}
|
||||
}
|
||||
|
||||
get elems() {
|
||||
return [];
|
||||
}
|
||||
|
||||
get url() {
|
||||
return URL;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,466 @@
|
||||
import {Loader} from '../Loader.js?v=2';
|
||||
import {Tooltip} from '../Tooltip.js?v=2';
|
||||
import {fireEvent} from '../utils/Event.js?v=2';
|
||||
import {getQueryParams} from '../utils/DataSource.js?v=2';
|
||||
import {handleAjaxError} from '../utils/ErrorHandler.js?v=2';
|
||||
import {TotalTile} from '../TotalTile.js?v=2';
|
||||
import {renderTotalFrame} from '../DataRenderers.js?v=2';
|
||||
import {
|
||||
MIDLINE_HELLIP,
|
||||
} from '../utils/Constants.js?v=2';
|
||||
|
||||
export class BaseGrid {
|
||||
constructor(gridParams) {
|
||||
this.config = gridParams;
|
||||
|
||||
this.loader = new Loader();
|
||||
this.tooltip = new Tooltip();
|
||||
this.totalTile = new TotalTile();
|
||||
|
||||
this.firstload = true;
|
||||
|
||||
this.renderTotalsLoader = this.renderTotalsLoader.bind(this);
|
||||
|
||||
this.initLoad();
|
||||
|
||||
if (this.config.dateRangeGrid && !this.config.sequential) {
|
||||
const onDateFilterChanged = this.onDateFilterChanged.bind(this);
|
||||
window.addEventListener('dateFilterChanged', onDateFilterChanged, false);
|
||||
}
|
||||
|
||||
if (gridParams.choicesFilterEvents) {
|
||||
const onChoicesFilterChanged = this.onChoicesFilterChanged.bind(this);
|
||||
for (let i = 0; i < gridParams.choicesFilterEvents.length; i++) {
|
||||
window.addEventListener(gridParams.choicesFilterEvents[i], onChoicesFilterChanged, false);
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.config.sequential) {
|
||||
const onSearchFilterChanged = this.onSearchFilterChanged.bind(this);
|
||||
window.addEventListener('searchFilterChanged', onSearchFilterChanged, false);
|
||||
}
|
||||
}
|
||||
|
||||
initLoad() {
|
||||
const me = this;
|
||||
const tableId = this.config.tableId;
|
||||
|
||||
$(document).ready(() => {
|
||||
$.extend($.fn.dataTable.ext.classes, {
|
||||
sStripeEven: '', sStripeOdd: ''
|
||||
});
|
||||
|
||||
$.fn.dataTable.ext.pager.numbers_length = 9;
|
||||
|
||||
$.fn.dataTable.ext.errMode = function() {};
|
||||
$(`#${tableId}`).on('error.dt', me.onError);
|
||||
|
||||
const onBeforeLoad = me.onBeforeLoad.bind(me);
|
||||
$(`#${tableId}`).on('preXhr.dt', onBeforeLoad);
|
||||
|
||||
const onBeforePageChange = me.onBeforePageChange.bind(me);
|
||||
$(`#${tableId}`).on('page.dt', onBeforePageChange);
|
||||
|
||||
const config = me.getDataTableConfig();
|
||||
$(`#${tableId}`).DataTable(config);
|
||||
|
||||
const onTableRowClick = me.onTableRowClick.bind(me);
|
||||
$(`#${tableId} tbody`).on('click', 'tr', onTableRowClick);
|
||||
|
||||
const onDraw = me.onDraw.bind(me);
|
||||
$(`#${tableId}`).on('draw.dt', onDraw);
|
||||
|
||||
$(`#${tableId}`).closest('.dt-container').find('nav').empty();
|
||||
|
||||
document.getElementById(tableId).classList.add('hide-body');
|
||||
|
||||
if (!me.config.sequential) {
|
||||
me.loadData();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getDataTableConfig() {
|
||||
const me = this;
|
||||
const url = this.config.url;
|
||||
const columns = this.columns;
|
||||
const columnDefs = this.columnDefs;
|
||||
const isSortable = this.getConfigParam('isSortable');
|
||||
const order = this.orderConfig;
|
||||
|
||||
const config = {
|
||||
ajax: function(data, callback, settings) {
|
||||
$.ajax({
|
||||
url: url,
|
||||
method: 'GET',
|
||||
data: data,
|
||||
dataType: 'json',
|
||||
success: function(response, textStatus, jqXHR) {
|
||||
callback(response);
|
||||
me.performAdditional(response, me.config);
|
||||
me.stopAnimation();
|
||||
},
|
||||
error: handleAjaxError,
|
||||
});
|
||||
},
|
||||
processing: true,
|
||||
serverSide: true,
|
||||
deferRender: true,
|
||||
deferLoading: 0,
|
||||
pageLength: 25,
|
||||
autoWidth: false,
|
||||
lengthChange: false,
|
||||
searching: true,
|
||||
ordering: isSortable,
|
||||
info: false,
|
||||
pagingType: 'simple_numbers',
|
||||
language: {
|
||||
paginate: {
|
||||
previous: '<',
|
||||
next: '>',
|
||||
},
|
||||
},
|
||||
|
||||
layout: {
|
||||
topEnd: null,
|
||||
bottomEnd: {
|
||||
paging: {
|
||||
boundaryNumbers: false,
|
||||
type: 'simple_numbers',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
createdRow: function(row, data, dataIndex) {
|
||||
$(row).attr('data-item-id', data.id);
|
||||
},
|
||||
|
||||
drawCallback: function(settings) {
|
||||
me.drawCallback(settings);
|
||||
me.updateTableFooter(this);
|
||||
},
|
||||
|
||||
columnDefs: columnDefs,
|
||||
columns: columns,
|
||||
order: order
|
||||
};
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
performAdditional(response, config) {
|
||||
if (!config.calculateTotals) {
|
||||
fireEvent('dateFilterChangedCompleted');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const ids = response.data.map(item => item.id);
|
||||
const token = document.head.querySelector('[name=\'csrf-token\'][content]').content;
|
||||
const dateRange = response.dateRange;
|
||||
|
||||
if (dateRange && ids.length) {
|
||||
const requestData = {
|
||||
token: token,
|
||||
ids: ids,
|
||||
type: config.totals.type,
|
||||
startDate: dateRange.startDate,
|
||||
endDate: dateRange.endDate,
|
||||
};
|
||||
let preparedBase = {};
|
||||
response.data.forEach(rec => {
|
||||
preparedBase[rec.id] = rec;
|
||||
});
|
||||
$.ajax({
|
||||
type: 'GET',
|
||||
url: '/admin/timeFrameTotal',
|
||||
data: requestData,
|
||||
success: (data) => this.onTotalsSuccess(data, config, preparedBase),
|
||||
error: handleAjaxError,
|
||||
complete: function() {
|
||||
fireEvent('dateFilterChangedCompleted');
|
||||
},
|
||||
});
|
||||
} else {
|
||||
fireEvent('dateFilterChangedCompleted');
|
||||
}
|
||||
|
||||
if (!dateRange && ids.length) {
|
||||
// actualy making fake response from server
|
||||
let data = {totals: {}};
|
||||
let preparedBase = {};
|
||||
const cols = config.totals.columns;
|
||||
response.data.forEach(item => {
|
||||
data.totals[item.id] = {};
|
||||
preparedBase[item.id] = {};
|
||||
cols.forEach(col => {
|
||||
data.totals[item.id][col] = item[col];
|
||||
preparedBase[item.id][col] = item[col];
|
||||
});
|
||||
|
||||
});
|
||||
this.onTotalsSuccess(data, config, preparedBase);
|
||||
}
|
||||
}
|
||||
|
||||
onTotalsSuccess(data, config, base) {
|
||||
const table = $(`#${config.tableId}`).DataTable();
|
||||
const columns = config.totals.columns;
|
||||
|
||||
let idxs = {};
|
||||
|
||||
for (let i = 0; i < columns.length; i++) {
|
||||
idxs[columns[i]] = -1;
|
||||
}
|
||||
|
||||
table.settings().init().columns.forEach((col, index) => {
|
||||
columns.forEach(colName => {
|
||||
if (col.data === colName || col.name === colName) {
|
||||
idxs[colName] = index;
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
if (Object.values(idxs).includes(-1)) return;
|
||||
|
||||
let rowData;
|
||||
let id;
|
||||
|
||||
table.rows().every(function() {
|
||||
rowData = this.data();
|
||||
id = String(rowData.id);
|
||||
if (id in data.totals) {
|
||||
for (const col in idxs) {
|
||||
$(table.cell(this, idxs[col]).node()).html(renderTotalFrame(base[id][col], data.totals[id][col]));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
drawCallback(settings) {
|
||||
const me = this;
|
||||
|
||||
this.initTooltips();
|
||||
|
||||
//this.stopAnimation();
|
||||
|
||||
const params = {
|
||||
tableId: me.config.tableId
|
||||
};
|
||||
|
||||
if (settings && settings.iDraw > 1) {
|
||||
const total = settings.json.recordsTotal;
|
||||
const tileId = this.config.tileId;
|
||||
const tableId = this.config.tableId;
|
||||
|
||||
this.totalTile.update(tableId, tileId, total);
|
||||
this.updateTableTitle(total);
|
||||
|
||||
fireEvent('tableLoaded', params);
|
||||
}
|
||||
}
|
||||
|
||||
updateTableTitle(value) {
|
||||
const tableId = this.config.tableId;
|
||||
const wrapper = document.getElementById(tableId).closest('.card');
|
||||
|
||||
if (wrapper) {
|
||||
const span = wrapper.querySelector('header span');
|
||||
|
||||
span.textContent = value;
|
||||
}
|
||||
}
|
||||
|
||||
stopAnimation() {
|
||||
this.loader.stop();
|
||||
const el = document.getElementById(`${this.config.tableId}_loader`);
|
||||
if (el) el.remove();
|
||||
|
||||
const table = document.getElementById(this.config.tableId);
|
||||
table.classList.remove('dim-table');
|
||||
}
|
||||
|
||||
updateTableFooter(dataTable) {
|
||||
const tableId = this.config.tableId;
|
||||
const pagerSelector = `#${tableId}_wrapper .dt-paging`;
|
||||
|
||||
const api = dataTable.api();
|
||||
if (api.ajax && typeof api.ajax.json === 'function' && api.ajax.json() === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
$(`#${tableId}`).closest('.dt-container').find('nav').show();
|
||||
if (api.page.info().pages <= 1) {
|
||||
$(pagerSelector).hide();
|
||||
} else {
|
||||
$(pagerSelector).show();
|
||||
}
|
||||
}
|
||||
|
||||
initTooltips() {
|
||||
const tableId = this.config.tableId;
|
||||
Tooltip.addTooltipsToGridRecords(tableId);
|
||||
Tooltip.addTooltipToSpans();
|
||||
Tooltip.addTooltipToParagraphs();
|
||||
}
|
||||
|
||||
onBeforeLoad(e, settings, data) {
|
||||
if (!this.config.sequential) {
|
||||
this.startLoader();
|
||||
}
|
||||
this.updateTableTitle(MIDLINE_HELLIP);
|
||||
|
||||
fireEvent('dateFilterChangedCaught');
|
||||
|
||||
//TODO: move to events grid? Or not?
|
||||
const params = this.config.getParams();
|
||||
const queryParams = getQueryParams(params);
|
||||
|
||||
for (let key in queryParams) {
|
||||
data[key] = queryParams[key];
|
||||
}
|
||||
|
||||
const token = document.head.querySelector('[name=\'csrf-token\'][content]').content;
|
||||
data.token = token;
|
||||
}
|
||||
|
||||
onDraw(e, settings) {
|
||||
if (this.firstload) {
|
||||
document.getElementById(this.config.tableId).classList.remove('hide-body');
|
||||
this.firstload = false;
|
||||
}
|
||||
}
|
||||
|
||||
onBeforePageChange(e, settings) {
|
||||
const tableId = this.config.tableId;
|
||||
const pagesPath = `#${tableId}_paginate a`;
|
||||
|
||||
[...document.querySelectorAll(pagesPath)].forEach(a => {
|
||||
a.outerHTML =
|
||||
a.outerHTML
|
||||
.replace(/<a/g, '<span')
|
||||
.replace(/<\/a>/g, '</span>');
|
||||
});
|
||||
}
|
||||
|
||||
onTableRowClick(event) {
|
||||
const selection = window.getSelection();
|
||||
if ('Range' === selection.type) {
|
||||
return;
|
||||
}
|
||||
|
||||
const row = event.target.closest('tr');
|
||||
const link = row.querySelector('a');
|
||||
|
||||
if (link) {
|
||||
event.preventDefault();
|
||||
if (event.ctrlKey || event.metaKey) {
|
||||
window.open(link.href, '_blank');
|
||||
} else {
|
||||
window.location.href = link.href;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: leave blank and move to the events table
|
||||
addTableRowsEvents() {
|
||||
const tableId = this.config.tableId;
|
||||
const onRowClick = this.onRowClick.bind(this);
|
||||
|
||||
if ($(this.table).DataTable().data().any()) {
|
||||
const rows = document.querySelectorAll(`#${tableId} tbody tr`);
|
||||
rows.forEach(row => row.addEventListener('click', onRowClick, false));
|
||||
}
|
||||
}
|
||||
|
||||
startLoader() {
|
||||
const tableId = this.config.tableId;
|
||||
const loaderPath = `${tableId}_processing`;
|
||||
|
||||
const loaderWrapper = document.getElementById(loaderPath);
|
||||
const el = document.createElement('p');
|
||||
el.className = 'text-loader';
|
||||
loaderWrapper.replaceChildren(el);
|
||||
|
||||
this.loader.start(el);
|
||||
|
||||
loaderWrapper.style.display = null;
|
||||
|
||||
const table = document.getElementById(this.config.tableId);
|
||||
table.classList.add('dim-table');
|
||||
}
|
||||
|
||||
onRowClick(e) {
|
||||
const selection = window.getSelection();
|
||||
if ('Range' === selection.type) {
|
||||
return;
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
|
||||
const row = e.target.closest('tr');
|
||||
const itemId = row.dataset.itemId;
|
||||
const data = {itemId: itemId};
|
||||
|
||||
fireEvent('tableRowClicked', data);
|
||||
}
|
||||
|
||||
onError(e, settings, techNote, message) {
|
||||
if (settings.jqXHR !== undefined && 403 === settings.jqXHR.status) {
|
||||
window.location.href = escape('/');
|
||||
}
|
||||
|
||||
//console.warn('An error has been reported by DataTables: ', message);
|
||||
}
|
||||
|
||||
loadData() {
|
||||
//TODO: create getter for table el: $(me.table).DataTable().ajax.reload()
|
||||
const me = this;
|
||||
$(me.table).DataTable().ajax.reload();
|
||||
}
|
||||
|
||||
onDateFilterChanged() {
|
||||
this.loadData();
|
||||
}
|
||||
|
||||
onSearchFilterChanged() {
|
||||
this.loadData();
|
||||
}
|
||||
|
||||
onChoicesFilterChanged() {
|
||||
this.loadData();
|
||||
}
|
||||
|
||||
getConfigParam(key) {
|
||||
const cfg = this.config;
|
||||
const value = ('undefined' !== typeof cfg[key]) ? cfg[key] : true;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
get orderConfig() {
|
||||
return this.getConfigParam('isSortable') ? [[1, 'desc']] : [];
|
||||
}
|
||||
|
||||
get table() {
|
||||
const tableId = this.config.tableId;
|
||||
const tableEl = document.getElementById(tableId);
|
||||
|
||||
return tableEl;
|
||||
}
|
||||
|
||||
renderTotalsLoader(data, type, record, meta) {
|
||||
const span = document.createElement('span');
|
||||
|
||||
const col_name = meta.settings.aoColumns[meta.col].name;
|
||||
if (this.config.calculateTotals && this.config.totals.columns.includes(col_name)) {
|
||||
span.className = 'loading-table-total';
|
||||
span.textContent = MIDLINE_HELLIP;
|
||||
} else {
|
||||
span.textContent = data;
|
||||
}
|
||||
|
||||
return span;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
import {BaseGrid} from './Base.js?v=2';
|
||||
import {fireEvent} from '../utils/Event.js?v=2';
|
||||
|
||||
export class BaseGridWithPanel extends BaseGrid {
|
||||
|
||||
constructor(gridParams) {
|
||||
super(gridParams);
|
||||
|
||||
this.config = gridParams;
|
||||
this.markerClass = 'marker';
|
||||
|
||||
this.allPanels = {
|
||||
'event': {
|
||||
id: 'event-card',
|
||||
closedEvent: 'eventPanelClosed',
|
||||
close: 'closeEventPanel',
|
||||
rowClicked: 'eventTableRowClicked',
|
||||
},
|
||||
'logbook': {
|
||||
id: 'logbook-card',
|
||||
closedEvent: 'logbookPanelClosed',
|
||||
close: 'closeLogbookPanel',
|
||||
rowClicked: 'logbookTableRowClicked',
|
||||
},
|
||||
'email': {
|
||||
id: 'email-card',
|
||||
closedEvent: 'emailPanelClosed',
|
||||
close: 'closeEmailPanel',
|
||||
rowClicked: 'emailTableRowClicked',
|
||||
},
|
||||
'device': {
|
||||
id: 'device-card',
|
||||
closedEvent: 'devicePanelClosed',
|
||||
close: 'closeDevicePanel',
|
||||
rowClicked: 'deviceTableRowClicked',
|
||||
},
|
||||
'phone': {
|
||||
id: 'phone-card',
|
||||
closedEvent: 'phonePanelClosed',
|
||||
close: 'closePhonePanel',
|
||||
rowClicked: 'phoneTableRowClicked',
|
||||
},
|
||||
};
|
||||
|
||||
this.panelType = gridParams.panelType;
|
||||
|
||||
this.currentPanel = this.allPanels[this.panelType];
|
||||
|
||||
const onDetailsPanelClosed = this.onDetailsPanelClosed.bind(this);
|
||||
window.addEventListener(this.currentPanel.closedEvent, onDetailsPanelClosed, false);
|
||||
|
||||
const onTableRowClicked = this.onTableRowClicked.bind(this);
|
||||
window.addEventListener(this.currentPanel.rowClicked, onTableRowClicked, false);
|
||||
}
|
||||
|
||||
drawCallback(settings) {
|
||||
super.drawCallback(settings);
|
||||
|
||||
this.addTableRowsEvents();
|
||||
}
|
||||
|
||||
onDetailsPanelClosed() {
|
||||
const markerClass = this.markerClass;
|
||||
const tableId = this.config.tableId;
|
||||
|
||||
$(`#${tableId} tbody tr`).removeClass(markerClass);
|
||||
}
|
||||
|
||||
onRowClick(e) {
|
||||
const selection = window.getSelection();
|
||||
if ('Range' === selection.type) {
|
||||
return;
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
|
||||
const row = e.target.closest('tr');
|
||||
const itemId = row.dataset.itemId;
|
||||
const data = {itemId: itemId};
|
||||
|
||||
fireEvent(this.currentPanel.rowClicked, data);
|
||||
}
|
||||
|
||||
onTableRowClicked(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const itemId = e.detail.itemId;
|
||||
const targetRow = this.table.querySelector(`tr[data-item-id="${itemId}"]`);
|
||||
|
||||
const markerClass = this.markerClass;
|
||||
const isRowMarkered = targetRow.classList.contains(markerClass);
|
||||
|
||||
if (isRowMarkered) {
|
||||
fireEvent(this.currentPanel.close);
|
||||
targetRow.classList.remove(markerClass);
|
||||
} else {
|
||||
// close other panels
|
||||
for (const panel in this.allPanels) {
|
||||
if (panel !== this.panelType) {
|
||||
const card = document.querySelector(`.details-card#${this.allPanels[panel].id}`);
|
||||
if (card && !card.classList.contains('is-hidden')) {
|
||||
fireEvent(this.allPanels[panel].closedEvent);
|
||||
card.classList.add('is-hidden');
|
||||
}
|
||||
}
|
||||
}
|
||||
// unmark other rows in the same table
|
||||
const rows = this.table.querySelectorAll('tr[data-item-id]');
|
||||
rows.forEach(row => row.classList.remove(markerClass));
|
||||
|
||||
// mark current row
|
||||
targetRow.classList.add(markerClass);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
import {BaseGrid} from './Base.js?v=2';
|
||||
import {
|
||||
renderTime,
|
||||
renderBlacklistButtons,
|
||||
renderBlacklistItem,
|
||||
renderBlacklistType,
|
||||
renderClickableImportantUserWithScore,
|
||||
} from '../DataRenderers.js?v=2';
|
||||
|
||||
export class BlacklistGrid extends BaseGrid {
|
||||
get orderConfig() {
|
||||
return [[1, 'desc']];
|
||||
}
|
||||
|
||||
get columnDefs() {
|
||||
const columnDefs = [
|
||||
{
|
||||
className: 'blacklist-user-col',
|
||||
targets: 0
|
||||
},
|
||||
{
|
||||
className: 'blacklist-timestamp-col',
|
||||
targets: 1
|
||||
},
|
||||
{
|
||||
className: 'blacklist-type-col',
|
||||
targets: 2
|
||||
},
|
||||
{
|
||||
className: 'blacklist-value-col',
|
||||
targets: 3
|
||||
},
|
||||
{
|
||||
className: 'blacklist-button-col',
|
||||
targets: 4
|
||||
}
|
||||
];
|
||||
|
||||
return columnDefs;
|
||||
}
|
||||
|
||||
get columns() {
|
||||
const columns = [
|
||||
{
|
||||
data: 'score',
|
||||
render: (data, type, record) => {
|
||||
return renderClickableImportantUserWithScore(record, 'medium');
|
||||
}
|
||||
},
|
||||
{
|
||||
data: 'created',
|
||||
render: (data, type, record) => {
|
||||
return renderTime(data);
|
||||
}
|
||||
},
|
||||
{
|
||||
data: 'type',
|
||||
render: (data, type, record) => {
|
||||
return renderBlacklistType(record);
|
||||
}
|
||||
},
|
||||
{
|
||||
data: 'value',
|
||||
render: (data, _ype, record) => {
|
||||
return renderBlacklistItem(record);
|
||||
}
|
||||
},
|
||||
{
|
||||
data: 'entity_id',
|
||||
orderable: false,
|
||||
render: (data, type, record) => {
|
||||
return renderBlacklistButtons(record);
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
return columns;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import {BaseGrid} from './Base.js?v=2';
|
||||
import {
|
||||
renderClickableBotId,
|
||||
renderDevice,
|
||||
renderOs,
|
||||
renderBoolean,
|
||||
} from '../DataRenderers.js?v=2';
|
||||
|
||||
export class BotsGrid extends BaseGrid {
|
||||
get columnDefs() {
|
||||
const columnDefs = [
|
||||
{
|
||||
className: 'bot-id-col',
|
||||
targets: 0
|
||||
},
|
||||
{
|
||||
className: 'bot-type-col',
|
||||
targets: 1
|
||||
},
|
||||
{
|
||||
className: 'bot-os-col',
|
||||
targets: 2
|
||||
},
|
||||
{
|
||||
className: 'bot-modified-col',
|
||||
targets: 3
|
||||
},
|
||||
];
|
||||
|
||||
return columnDefs;
|
||||
}
|
||||
|
||||
get columns() {
|
||||
const columns = [
|
||||
{
|
||||
data: 'id',
|
||||
render: (data, type, record) => {
|
||||
return renderClickableBotId(record);
|
||||
}
|
||||
},
|
||||
{
|
||||
data: 'device',
|
||||
render: (data, type, record) => {
|
||||
return renderDevice(record);
|
||||
}
|
||||
},
|
||||
{
|
||||
data: 'os_name',
|
||||
render: (data, type, record) => {
|
||||
return renderOs(record);
|
||||
}
|
||||
},
|
||||
{
|
||||
data: 'modified',
|
||||
render: renderBoolean
|
||||
}
|
||||
];
|
||||
|
||||
return columns;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
import {BaseGrid} from './Base.js?v=2';
|
||||
import {fireEvent} from '../utils/Event.js?v=2';
|
||||
import {renderClickableCountry} from '../DataRenderers.js?v=2';
|
||||
|
||||
export class CountriesGrid extends BaseGrid {
|
||||
get orderConfig() {
|
||||
return [[0, 'asc']];
|
||||
}
|
||||
|
||||
drawCallback(settings) {
|
||||
super.drawCallback(settings);
|
||||
|
||||
if (settings && settings.iDraw > 1) {
|
||||
const data = settings.json.data;
|
||||
fireEvent('countriesGridLoaded', {data: data});
|
||||
}
|
||||
}
|
||||
|
||||
get columnDefs() {
|
||||
const columnDefs = [
|
||||
{
|
||||
className: 'country-country-col',
|
||||
targets: 0
|
||||
},
|
||||
{
|
||||
className: 'country-iso-col',
|
||||
targets: 1
|
||||
},
|
||||
{
|
||||
className: 'country-cnt-col',
|
||||
targets: 2
|
||||
},
|
||||
{
|
||||
className: 'country-cnt-col',
|
||||
targets: 3
|
||||
},
|
||||
{
|
||||
className: 'country-cnt-col',
|
||||
targets: 4
|
||||
},
|
||||
{
|
||||
visible: false,
|
||||
targets: 5
|
||||
}
|
||||
];
|
||||
|
||||
return columnDefs;
|
||||
}
|
||||
|
||||
get columns() {
|
||||
const columns = [
|
||||
{
|
||||
data: 'full_country',
|
||||
name: 'full_country',
|
||||
render: (data, type, record) => {
|
||||
return renderClickableCountry(record, false);
|
||||
}
|
||||
},
|
||||
{
|
||||
data: 'country_iso',
|
||||
name: 'country_iso'
|
||||
},
|
||||
{
|
||||
data: 'total_account',
|
||||
name: 'total_account',
|
||||
render: this.renderTotalsLoader,
|
||||
},
|
||||
{
|
||||
data: 'total_visit',
|
||||
name: 'total_visit',
|
||||
render: this.renderTotalsLoader,
|
||||
},
|
||||
{
|
||||
data: 'total_ip',
|
||||
name: 'total_ip',
|
||||
render: this.renderTotalsLoader,
|
||||
},
|
||||
{
|
||||
data: 'id',
|
||||
name: 'id',
|
||||
},
|
||||
];
|
||||
|
||||
return columns;
|
||||
}
|
||||
|
||||
updateTableFooter(dataTable) {
|
||||
const tableId = this.config.tableId;
|
||||
const pagerSelector = `#${tableId}_wrapper .dt-paging`;
|
||||
|
||||
const api = dataTable.api();
|
||||
if (api.ajax && typeof api.ajax.json === 'function' && api.ajax.json() === undefined) {
|
||||
$(`${pagerSelector} nav`).empty();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$(pagerSelector).hide();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
import {BaseGridWithPanel} from './BaseWithPanel.js?v=2';
|
||||
import {
|
||||
renderDate,
|
||||
renderBoolean,
|
||||
renderDevice,
|
||||
renderOs,
|
||||
renderBrowser,
|
||||
renderLanguage,
|
||||
} from '../DataRenderers.js?v=2';
|
||||
|
||||
export class DevicesGrid extends BaseGridWithPanel {
|
||||
get columnDefs() {
|
||||
const columnDefs = [
|
||||
{
|
||||
className: 'device-date-col',
|
||||
targets: 0
|
||||
},
|
||||
{
|
||||
className: 'device-type-col',
|
||||
targets: 1
|
||||
},
|
||||
{
|
||||
className: 'device-os-col',
|
||||
targets: 2
|
||||
},
|
||||
{
|
||||
className: 'device-browser-col',
|
||||
targets: 3
|
||||
},
|
||||
{
|
||||
className: 'device-language-col',
|
||||
targets: 4
|
||||
},
|
||||
{
|
||||
className: 'device-modified-col',
|
||||
targets: 5
|
||||
},
|
||||
{
|
||||
visible: false,
|
||||
targets: 6
|
||||
},
|
||||
];
|
||||
|
||||
return columnDefs;
|
||||
}
|
||||
|
||||
get columns() {
|
||||
const columns = [
|
||||
{
|
||||
data: 'created',
|
||||
render: (data, type, record) => {
|
||||
return renderDate(data);
|
||||
},
|
||||
},
|
||||
{
|
||||
data: 'device',
|
||||
render: (data, type, record) => {
|
||||
return renderDevice(record);
|
||||
}
|
||||
},
|
||||
{
|
||||
data: 'os_name',
|
||||
render: (data, type, record) => {
|
||||
return renderOs(record);
|
||||
}
|
||||
},
|
||||
{
|
||||
data: 'browser_name',
|
||||
render: (data, type, record) => {
|
||||
return renderBrowser(record);
|
||||
}
|
||||
},
|
||||
{
|
||||
data: 'lang',
|
||||
render: (data, type, record) => {
|
||||
return renderLanguage(record);
|
||||
},
|
||||
},
|
||||
{
|
||||
data: 'modified',
|
||||
render: renderBoolean
|
||||
},
|
||||
{
|
||||
data: 'id',
|
||||
name: 'id',
|
||||
},
|
||||
];
|
||||
|
||||
return columns;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
import {BaseGrid} from './Base.js?v=2';
|
||||
import {
|
||||
renderClickableDomain,
|
||||
renderBoolean,
|
||||
renderDate,
|
||||
renderDefaultIfEmptyElement,
|
||||
renderUserCounter,
|
||||
} from '../DataRenderers.js?v=2';
|
||||
|
||||
export class DomainsGrid extends BaseGrid {
|
||||
get orderConfig() {
|
||||
return [[6, 'desc']];
|
||||
}
|
||||
|
||||
get columnDefs() {
|
||||
const columnDefs = [
|
||||
{
|
||||
className: 'domain-domain-col',
|
||||
targets: 0
|
||||
},
|
||||
{
|
||||
className: 'domain-cnt-col',
|
||||
targets: 1
|
||||
},
|
||||
{
|
||||
className: 'domain-cnt-col',
|
||||
targets: 2
|
||||
},
|
||||
{
|
||||
className: 'domain-cnt-col',
|
||||
targets: 3
|
||||
},
|
||||
{
|
||||
className: 'domain-cnt-col',
|
||||
targets: 4
|
||||
},
|
||||
{
|
||||
className: 'domain-date-col',
|
||||
targets: 5
|
||||
},
|
||||
{
|
||||
className: 'domain-cnt-col',
|
||||
targets: 6
|
||||
},
|
||||
{
|
||||
className: 'domain-cnt-col',
|
||||
targets: 7
|
||||
},
|
||||
{
|
||||
visible: false,
|
||||
targets: 8
|
||||
}
|
||||
];
|
||||
|
||||
return columnDefs;
|
||||
}
|
||||
|
||||
get columns() {
|
||||
const columns = [
|
||||
{
|
||||
data: 'domain',
|
||||
render: (data, type, record) => {
|
||||
return renderClickableDomain(record);
|
||||
}
|
||||
},
|
||||
{
|
||||
data: 'free_email_provider',
|
||||
render: (data, type, record) => {
|
||||
const free_email_provider = record.free_email_provider;
|
||||
return renderBoolean(free_email_provider);
|
||||
}
|
||||
},
|
||||
{
|
||||
data: 'tranco_rank',
|
||||
name: 'tranco_rank',
|
||||
render: (data, type, record) => {
|
||||
let rank = renderDefaultIfEmptyElement(data);
|
||||
if (data) {
|
||||
rank = data;
|
||||
}
|
||||
|
||||
return rank;
|
||||
}
|
||||
},
|
||||
{
|
||||
data: 'disabled',
|
||||
render: (data, type, record) => {
|
||||
const unavailable = record.disabled;
|
||||
return renderBoolean(unavailable);
|
||||
}
|
||||
},
|
||||
{
|
||||
data: 'disposable_domains',
|
||||
render: (data, type, record) => {
|
||||
const disposable = record.disposable_domains;
|
||||
return renderBoolean(disposable);
|
||||
}
|
||||
},
|
||||
{
|
||||
data: 'creation_date',
|
||||
render: (data, type, record) => {
|
||||
const creation_date = record.creation_date;
|
||||
|
||||
if (creation_date) {
|
||||
return renderDate(creation_date);
|
||||
} else {
|
||||
return renderDefaultIfEmptyElement(creation_date);
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
data: 'total_account',
|
||||
name: 'total_account',
|
||||
render: this.renderTotalsLoader,
|
||||
},
|
||||
{
|
||||
data: 'fraud',
|
||||
name: 'fraud',
|
||||
render: (data, type, record) => {
|
||||
return renderUserCounter(data, 1);
|
||||
}
|
||||
},
|
||||
{
|
||||
data: 'id',
|
||||
name: 'id',
|
||||
},
|
||||
];
|
||||
|
||||
return columns;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
import {BaseGridWithPanel} from './BaseWithPanel.js?v=2';
|
||||
import {
|
||||
renderBoolean,
|
||||
renderReputation,
|
||||
renderEmail,
|
||||
renderDefaultIfEmptyElement,
|
||||
} from '../DataRenderers.js?v=2';
|
||||
|
||||
export class EmailsGrid extends BaseGridWithPanel {
|
||||
|
||||
get orderConfig() {
|
||||
return [];
|
||||
}
|
||||
|
||||
get columnDefs() {
|
||||
const columnDefs = [
|
||||
{
|
||||
className: 'email-email-col',
|
||||
targets: 0
|
||||
},
|
||||
{
|
||||
className: 'email-reputation-col',
|
||||
targets: 1
|
||||
},
|
||||
{
|
||||
className: 'email-free-provider-col',
|
||||
targets: 2
|
||||
},
|
||||
{
|
||||
className: 'email-no-breach-col',
|
||||
targets: 3
|
||||
},
|
||||
{
|
||||
className: 'email-total-breaches-col',
|
||||
targets: 4
|
||||
},
|
||||
{
|
||||
className: 'email-disposable-col',
|
||||
targets: 5
|
||||
},
|
||||
{
|
||||
className: 'email-spam-col',
|
||||
targets: 6
|
||||
},
|
||||
{
|
||||
className: 'email-blacklist-col',
|
||||
targets: 7
|
||||
},
|
||||
// TODO: return alert_list back in next release
|
||||
//{
|
||||
// className: 'medium-yes-no-col',
|
||||
// targets: 8
|
||||
//}
|
||||
];
|
||||
|
||||
return columnDefs;
|
||||
}
|
||||
|
||||
get columns() {
|
||||
const columns = [
|
||||
{
|
||||
data: 'email',
|
||||
render: (data, type, record) => {
|
||||
return renderEmail(record);
|
||||
}
|
||||
},
|
||||
{
|
||||
data: 'reputation',
|
||||
render: (data, type, record) => {
|
||||
return renderReputation(record);
|
||||
}
|
||||
},
|
||||
{
|
||||
data: 'free_email_provider',
|
||||
render: renderBoolean
|
||||
},
|
||||
//{
|
||||
// data: 'profiles',
|
||||
// orderable: false,
|
||||
// render: (data, type, record) => {
|
||||
// // revert profiles to `no profiles`
|
||||
// return renderBoolean(data === null ? null : !data);
|
||||
// }
|
||||
//},
|
||||
{
|
||||
data: 'data_breach',
|
||||
orderable: false,
|
||||
render: (data, type, record) => {
|
||||
// revert data breach to `no breach`
|
||||
return renderBoolean(data === null ? null : !data);
|
||||
}
|
||||
},
|
||||
{
|
||||
data: 'data_breaches',
|
||||
render: renderDefaultIfEmptyElement
|
||||
},
|
||||
{
|
||||
data: 'disposable_domains',
|
||||
render: renderBoolean
|
||||
},
|
||||
{
|
||||
data: 'blockemails',
|
||||
render: renderBoolean
|
||||
},
|
||||
{
|
||||
data: 'fraud_detected',
|
||||
render: renderBoolean
|
||||
},
|
||||
// TODO: return alert_list back in next release
|
||||
//{
|
||||
// data: 'alert_list',
|
||||
// render: renderBoolean
|
||||
//}
|
||||
];
|
||||
|
||||
return columns;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
import {BaseGridWithPanel} from './BaseWithPanel.js?v=2';
|
||||
import {
|
||||
renderResourceWithQueryAndEventType,
|
||||
renderDeviceWithOs,
|
||||
renderIpType,
|
||||
renderIpWithCountry,
|
||||
renderUserForEvent,
|
||||
renderTimestampForEvent,
|
||||
} from '../DataRenderers.js?v=2';
|
||||
|
||||
export class EventsGrid extends BaseGridWithPanel {
|
||||
// 7, 8 - invisible time and id columns to prevent sorting buttons appearence
|
||||
get orderConfig() {
|
||||
return this.config.sessionGroup && !this.config.singleUser
|
||||
? [[6, 'desc'], [7, 'desc'], [8, 'desc']]
|
||||
: [[7, 'desc'], [8, 'desc']];
|
||||
}
|
||||
|
||||
get columnDefs() {
|
||||
const columnDefs = [
|
||||
{
|
||||
className: 'event-user-col',
|
||||
targets: 0
|
||||
},
|
||||
{
|
||||
className: 'event-timestamp-col',
|
||||
targets: 1
|
||||
},
|
||||
{
|
||||
className: 'event-event-type-col',
|
||||
targets: 2
|
||||
},
|
||||
{
|
||||
className: 'event-ip-col',
|
||||
targets: 3
|
||||
},
|
||||
{
|
||||
className: 'event-ip-type-col',
|
||||
targets: 4
|
||||
},
|
||||
{
|
||||
className: 'event-device-col',
|
||||
targets: 5
|
||||
},
|
||||
{
|
||||
visible: false,
|
||||
targets: 6
|
||||
},
|
||||
{
|
||||
visible: false,
|
||||
targets: 7
|
||||
},
|
||||
{
|
||||
visible: false,
|
||||
targets: 8
|
||||
},
|
||||
];
|
||||
|
||||
return columnDefs;
|
||||
}
|
||||
|
||||
get columns() {
|
||||
const userIdRender = (record) => {
|
||||
return renderUserForEvent(record, 'medium', this.config.sessionGroup, this.config.singleUser);
|
||||
};
|
||||
const timestampRender = (record) => {
|
||||
return renderTimestampForEvent(record, this.config.sessionGroup, this.config.singleUser);
|
||||
};
|
||||
|
||||
const columns = [
|
||||
{
|
||||
data: 'userid',
|
||||
render: (data, type, record) => {
|
||||
return userIdRender(record);
|
||||
},
|
||||
orderable: false
|
||||
},
|
||||
{
|
||||
data: 'time',
|
||||
render: (data, type, record) => {
|
||||
return timestampRender(record);
|
||||
},
|
||||
orderable: false
|
||||
},
|
||||
{
|
||||
data: 'type',
|
||||
render: (data, type, record) => {
|
||||
return renderResourceWithQueryAndEventType(record);
|
||||
},
|
||||
orderable: false
|
||||
},
|
||||
{
|
||||
data: 'ip',
|
||||
render: (data, type, record) => {
|
||||
return renderIpWithCountry(record);
|
||||
},
|
||||
orderable: false
|
||||
},
|
||||
{
|
||||
data: 'ip_type',
|
||||
name: 'ip_type',
|
||||
render: (data, type, record) => {
|
||||
return renderIpType(record);
|
||||
},
|
||||
orderable: false
|
||||
},
|
||||
{
|
||||
data: 'device',
|
||||
render: (data, type, record) => {
|
||||
return renderDeviceWithOs(record);
|
||||
},
|
||||
orderable: false
|
||||
},
|
||||
{
|
||||
data: 'session_id',
|
||||
name: 'session_id',
|
||||
},
|
||||
{
|
||||
data: 'time',
|
||||
name: 'time',
|
||||
},
|
||||
{
|
||||
data: 'id',
|
||||
name: 'id',
|
||||
},
|
||||
];
|
||||
|
||||
return columns;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
import {BaseGrid} from './Base.js?v=2';
|
||||
import {
|
||||
renderIpType,
|
||||
renderUserCounter,
|
||||
renderNetName,
|
||||
renderFullCountry,
|
||||
renderAsn,
|
||||
renderClickableIpWithCountry,
|
||||
} from '../DataRenderers.js?v=2';
|
||||
|
||||
|
||||
export class IpsGrid extends BaseGrid {
|
||||
get orderConfig() {
|
||||
return [[this.config.orderByLastseen ? 7 : 6, 'desc']];
|
||||
}
|
||||
|
||||
get columnDefs() {
|
||||
const columnDefs = [
|
||||
{
|
||||
className: 'ip-ip-col',
|
||||
targets: 0
|
||||
},
|
||||
{
|
||||
className: 'ip-country-col',
|
||||
targets: 1
|
||||
},
|
||||
{
|
||||
className: 'ip-asn-col',
|
||||
targets: 2
|
||||
},
|
||||
{
|
||||
className: 'ip-newtwork-col',
|
||||
targets: 3
|
||||
},
|
||||
{
|
||||
className: 'ip-ip-type-col',
|
||||
targets: 4
|
||||
},
|
||||
{
|
||||
className: 'ip-cnt-col',
|
||||
targets: 5
|
||||
},
|
||||
{
|
||||
className: 'ip-cnt-col',
|
||||
targets: 6
|
||||
},
|
||||
{
|
||||
visible: false,
|
||||
targets: 7
|
||||
},
|
||||
{
|
||||
visible: false,
|
||||
targets: 8
|
||||
}
|
||||
// TODO: return alert_list back in next release
|
||||
//{
|
||||
// className: 'yes-no-col',
|
||||
// targets: 9
|
||||
//}
|
||||
];
|
||||
|
||||
return columnDefs;
|
||||
}
|
||||
|
||||
get columns() {
|
||||
const columns = [
|
||||
{
|
||||
data: 'ip',
|
||||
name: 'ip',
|
||||
render: (data, type, record) => {
|
||||
const rec = {
|
||||
ip: record.ip,
|
||||
ipid: record.id,
|
||||
country_iso: record.country_iso,
|
||||
full_country: record.full_country,
|
||||
isp_name: record.netname,
|
||||
};
|
||||
return renderClickableIpWithCountry(rec);
|
||||
}
|
||||
},
|
||||
{
|
||||
data: 'full_country',
|
||||
render: renderFullCountry,
|
||||
},
|
||||
{
|
||||
data: 'asn',
|
||||
name: 'asn',
|
||||
render: (data, type, record) => {
|
||||
return renderAsn(record);
|
||||
},
|
||||
},
|
||||
{
|
||||
data: 'netname',
|
||||
name: 'netname',
|
||||
render: (data, type, record) => {
|
||||
return renderNetName(record, 'short');
|
||||
}
|
||||
},
|
||||
{
|
||||
data: 'ip_type',
|
||||
name: 'ip_type',
|
||||
orderable: false,
|
||||
render: (data, type, record) => {
|
||||
return renderIpType(record);
|
||||
}
|
||||
},
|
||||
{
|
||||
data: 'total_visit',
|
||||
name: 'total_visit',
|
||||
render: this.renderTotalsLoader
|
||||
},
|
||||
{
|
||||
data: 'total_account',
|
||||
name: 'total_account',
|
||||
render: (data, type, record) => {
|
||||
return renderUserCounter(data, 2);
|
||||
}
|
||||
},
|
||||
{
|
||||
data: 'lastseen',
|
||||
name: 'lastseen',
|
||||
},
|
||||
{
|
||||
data: 'id',
|
||||
name: 'id',
|
||||
},
|
||||
// TODO: return alert_list back in next release
|
||||
//{
|
||||
// data: 'alert_list',
|
||||
// render: renderBoolean
|
||||
//}
|
||||
];
|
||||
|
||||
return columns;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
import {BaseGrid} from './Base.js?v=2';
|
||||
import {
|
||||
renderClickableAsn,
|
||||
renderNetName,
|
||||
renderUserCounter,
|
||||
} from '../DataRenderers.js?v=2';
|
||||
|
||||
export class IspsGrid extends BaseGrid {
|
||||
get orderConfig() {
|
||||
return [[3, 'desc']];
|
||||
}
|
||||
|
||||
get columnDefs() {
|
||||
const columnDefs = [
|
||||
{
|
||||
className: 'isp-asn-col',
|
||||
targets: 0
|
||||
},
|
||||
{
|
||||
className: 'isp-network-col',
|
||||
targets: 1
|
||||
},
|
||||
{
|
||||
className: 'isp-cnt-col',
|
||||
targets: 2
|
||||
},
|
||||
{
|
||||
className: 'isp-cnt-col',
|
||||
targets: 3
|
||||
},
|
||||
{
|
||||
className: 'isp-cnt-col',
|
||||
targets: 4
|
||||
},
|
||||
{
|
||||
className: 'isp-cnt-col',
|
||||
targets: 5
|
||||
},
|
||||
{
|
||||
visible: false,
|
||||
targets: 6
|
||||
}
|
||||
];
|
||||
|
||||
return columnDefs;
|
||||
}
|
||||
|
||||
get columns() {
|
||||
const columns = [
|
||||
{
|
||||
data: 'asn',
|
||||
name: 'asn',
|
||||
render: (data, type, record) => {
|
||||
record['ispid'] = record.id;
|
||||
return renderClickableAsn(record);
|
||||
}
|
||||
},
|
||||
{
|
||||
data: 'name',
|
||||
name: 'name',
|
||||
render: (data, type, record) => {
|
||||
record.netname = data;
|
||||
return renderNetName(record, 'long');
|
||||
}
|
||||
},
|
||||
{
|
||||
data: 'total_visit',
|
||||
name: 'total_visit',
|
||||
render: this.renderTotalsLoader
|
||||
},
|
||||
{
|
||||
data: 'total_ip',
|
||||
name: 'total_ip',
|
||||
render: this.renderTotalsLoader
|
||||
},
|
||||
{
|
||||
data: 'total_account',
|
||||
name: 'total_account',
|
||||
render: this.renderTotalsLoader
|
||||
},
|
||||
{
|
||||
data: 'fraud',
|
||||
name: 'fraud',
|
||||
render: (data, type, record) => {
|
||||
return renderUserCounter(data, 1);
|
||||
}
|
||||
},
|
||||
{
|
||||
data: 'id',
|
||||
name: 'id',
|
||||
},
|
||||
];
|
||||
|
||||
return columns;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import {BaseGridWithPanel} from './BaseWithPanel.js?v=2';
|
||||
import {
|
||||
renderIp,
|
||||
renderTimeMs,
|
||||
renderErrorType,
|
||||
renderSensorErrorColumn,
|
||||
} from '../DataRenderers.js?v=2';
|
||||
|
||||
export class LogbookGrid extends BaseGridWithPanel {
|
||||
|
||||
get orderConfig() {
|
||||
return [[2, 'desc'], [1, 'desc']];
|
||||
}
|
||||
|
||||
get columnDefs() {
|
||||
const columnDefs = [
|
||||
{
|
||||
className: 'logbook-ip-col',
|
||||
targets: 0
|
||||
},
|
||||
{
|
||||
className: 'logbook-timestamp-col',
|
||||
targets: 1
|
||||
},
|
||||
{
|
||||
className: 'logbook-status-col',
|
||||
targets: 2
|
||||
},
|
||||
{
|
||||
className: 'logbook-message-col',
|
||||
targets: 3
|
||||
},
|
||||
];
|
||||
|
||||
return columnDefs;
|
||||
}
|
||||
|
||||
get columns() {
|
||||
const columns = [
|
||||
{
|
||||
data: 'ip',
|
||||
render: (data, type, record) => {
|
||||
return renderIp(record);
|
||||
}
|
||||
},
|
||||
{
|
||||
data: 'started',
|
||||
render: renderTimeMs,
|
||||
},
|
||||
{
|
||||
data: 'error_type',
|
||||
render: (data, type, record) => {
|
||||
return renderErrorType(record);
|
||||
}
|
||||
},
|
||||
{
|
||||
data: 'error_text',
|
||||
render: (data, type, record) => {
|
||||
return renderSensorErrorColumn(record);
|
||||
}
|
||||
},
|
||||
];
|
||||
|
||||
return columns;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
import {BaseGridWithPanel} from './BaseWithPanel.js?v=2';
|
||||
import {
|
||||
renderClickableBotId,
|
||||
renderDevice,
|
||||
renderOs,
|
||||
renderBoolean,
|
||||
renderPhone,
|
||||
renderFullCountry,
|
||||
renderPhoneCarrierName,
|
||||
renderPhoneType,
|
||||
renderUserCounter,
|
||||
} from '../DataRenderers.js?v=2';
|
||||
|
||||
export class PhonesGrid extends BaseGridWithPanel {
|
||||
get orderConfig() {
|
||||
return [];
|
||||
}
|
||||
|
||||
get columnDefs() {
|
||||
const columnDefs = [
|
||||
{
|
||||
className: 'phone-phonenumber-col',
|
||||
targets: 0
|
||||
},
|
||||
{
|
||||
className: 'phone-invalid-col',
|
||||
targets: 1
|
||||
},
|
||||
{
|
||||
className: 'phone-country-col',
|
||||
targets: 2
|
||||
},
|
||||
{
|
||||
className: 'phone-carrier-col',
|
||||
targets: 3
|
||||
},
|
||||
{
|
||||
className: 'phone-type-col',
|
||||
targets: 4
|
||||
},
|
||||
{
|
||||
className: 'phone-users-col',
|
||||
targets: 5
|
||||
},
|
||||
{
|
||||
className: 'phone-blacklist-col',
|
||||
targets: 6
|
||||
},
|
||||
// TODO: return alert_list back in next release
|
||||
//{
|
||||
// className: 'yes-no-col',
|
||||
// targets: 6
|
||||
//}
|
||||
];
|
||||
|
||||
return columnDefs;
|
||||
}
|
||||
|
||||
get columns() {
|
||||
const columns = [
|
||||
{
|
||||
data: 'phonenumber',
|
||||
render: (data, type, record) => {
|
||||
return renderPhone(record);
|
||||
}
|
||||
},
|
||||
{
|
||||
data: 'invalid',
|
||||
render: renderBoolean
|
||||
},
|
||||
{
|
||||
data: 'full_country',
|
||||
render: renderFullCountry
|
||||
},
|
||||
{
|
||||
data: 'carrier_name',
|
||||
render: (data, type, record) => {
|
||||
return renderPhoneCarrierName(record);
|
||||
}
|
||||
},
|
||||
{
|
||||
data: 'type',
|
||||
render: (data, type, record) => {
|
||||
return renderPhoneType(record);
|
||||
}
|
||||
},
|
||||
{
|
||||
data: 'shared',
|
||||
name: 'shared',
|
||||
render: (data, type, record) => {
|
||||
return renderUserCounter(data, 2);
|
||||
}
|
||||
},
|
||||
{
|
||||
data: 'fraud_detected',
|
||||
render: renderBoolean
|
||||
},
|
||||
// TODO: return alert_list back in next release
|
||||
//{
|
||||
// data: 'alert_list',
|
||||
// render: renderBoolean
|
||||
//}
|
||||
];
|
||||
|
||||
return columns;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
import {BaseGrid} from './Base.js?v=2';
|
||||
import {
|
||||
renderClickableResourceWithoutQuery,
|
||||
renderHttpCode,
|
||||
renderBoolean,
|
||||
} from '../DataRenderers.js?v=2';
|
||||
|
||||
export class ResourcesGrid extends BaseGrid {
|
||||
get orderConfig() {
|
||||
return [[7, 'desc']];
|
||||
}
|
||||
|
||||
get columnDefs() {
|
||||
const columnDefs = [
|
||||
{
|
||||
className: 'resource-url-col',
|
||||
targets: 0
|
||||
},
|
||||
{
|
||||
className: 'resource-cnt-col',
|
||||
targets: 1
|
||||
},
|
||||
{
|
||||
className: 'resource-cnt-col',
|
||||
targets: 2
|
||||
},
|
||||
{
|
||||
className: 'resource-cnt-col',
|
||||
targets: 3
|
||||
},
|
||||
{
|
||||
className: 'resource-cnt-col',
|
||||
targets: 4
|
||||
},
|
||||
{
|
||||
className: 'resource-cnt-col',
|
||||
targets: 5
|
||||
},
|
||||
{
|
||||
className: 'resource-cnt-col',
|
||||
targets: 6
|
||||
},
|
||||
{
|
||||
visible: false,
|
||||
targets: 7
|
||||
}
|
||||
];
|
||||
|
||||
return columnDefs;
|
||||
}
|
||||
|
||||
get columns() {
|
||||
const columns = [
|
||||
{
|
||||
data: 'title',
|
||||
render: (data, type, record) => {
|
||||
return renderClickableResourceWithoutQuery(record);
|
||||
}
|
||||
},
|
||||
{
|
||||
data: 'http_code',
|
||||
render: (data, type, record) => {
|
||||
return renderHttpCode(record);
|
||||
}
|
||||
},
|
||||
{
|
||||
data: 'total_account',
|
||||
name: 'total_account',
|
||||
render: this.renderTotalsLoader
|
||||
},
|
||||
{
|
||||
data: 'total_country',
|
||||
name: 'total_country',
|
||||
render: this.renderTotalsLoader
|
||||
},
|
||||
{
|
||||
data: 'total_ip',
|
||||
name: 'total_ip',
|
||||
render: this.renderTotalsLoader
|
||||
},
|
||||
{
|
||||
data: 'total_visit',
|
||||
name: 'total_visit',
|
||||
render: this.renderTotalsLoader
|
||||
},
|
||||
{
|
||||
data: 'suspicious',
|
||||
render: renderBoolean,
|
||||
orderable: false
|
||||
},
|
||||
{
|
||||
data: 'id',
|
||||
name: 'id',
|
||||
}
|
||||
];
|
||||
|
||||
return columns;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
import {BaseGrid} from './Base.js?v=2';
|
||||
import {
|
||||
renderTime,
|
||||
renderDate,
|
||||
renderUserFirstname,
|
||||
renderUserLastname,
|
||||
renderUserActionButtons,
|
||||
renderClickableImportantUserWithScore,
|
||||
} from '../DataRenderers.js?v=2';
|
||||
|
||||
export class ReviewQueueGrid extends BaseGrid {
|
||||
get orderConfig() {
|
||||
return [[1, 'desc']];
|
||||
}
|
||||
|
||||
onTableRowClick(event) {}
|
||||
|
||||
get columnDefs() {
|
||||
const columnDefs = [
|
||||
{
|
||||
className: 'review-queue-user-col',
|
||||
targets: 0
|
||||
},
|
||||
{
|
||||
className: 'review-queue-timestamp-col',
|
||||
targets: 1
|
||||
},
|
||||
{
|
||||
className: 'review-queue-name-col',
|
||||
targets: 2
|
||||
},
|
||||
{
|
||||
className: 'review-queue-name-col',
|
||||
targets: 3
|
||||
},
|
||||
{
|
||||
className: 'review-queue-date-col',
|
||||
targets: 4
|
||||
},
|
||||
{
|
||||
className: 'review-queue-button-col',
|
||||
targets: 5
|
||||
}
|
||||
];
|
||||
|
||||
return columnDefs;
|
||||
}
|
||||
|
||||
get columns() {
|
||||
const columns = [
|
||||
{
|
||||
data: 'score',
|
||||
render: (data, type, record) => {
|
||||
return renderClickableImportantUserWithScore(record, 'medium');
|
||||
}
|
||||
},
|
||||
{
|
||||
data: 'added_to_review',
|
||||
render: renderTime
|
||||
},
|
||||
{
|
||||
data: 'firstname',
|
||||
render: (data, type, record) => {
|
||||
return renderUserFirstname(record);
|
||||
},
|
||||
},
|
||||
{
|
||||
data: 'lastname',
|
||||
render: (data, type, record) => {
|
||||
return renderUserLastname(record);
|
||||
},
|
||||
},
|
||||
{
|
||||
data: 'created',
|
||||
render: (data, type, record) => {
|
||||
return renderDate(data);
|
||||
},
|
||||
},
|
||||
{
|
||||
orderable: false,
|
||||
data: 'actions',
|
||||
render: (data, type, record) => {
|
||||
return renderUserActionButtons(record);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
return columns;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
import {BaseGrid} from './Base.js?v=2';
|
||||
import {fireEvent} from '../utils/Event.js?v=2';
|
||||
import {handleAjaxError} from '../utils/ErrorHandler.js?v=2';
|
||||
import {renderDefaultIfEmptyElement} from '../DataRenderers.js?v=2';
|
||||
|
||||
export class TopTenGrid extends BaseGrid {
|
||||
get columnDefs() {
|
||||
const columnDefs = [
|
||||
{
|
||||
className: 'top-ten-aggregating-col',
|
||||
targets: 0
|
||||
},
|
||||
{
|
||||
targets: 1
|
||||
},
|
||||
];
|
||||
|
||||
return columnDefs;
|
||||
}
|
||||
|
||||
get columns() {
|
||||
const columns = [
|
||||
{
|
||||
data: 'item',
|
||||
render: (data, type, record) => {
|
||||
return this.config.renderItemColumn(record);
|
||||
},
|
||||
},
|
||||
{
|
||||
data: 'value',
|
||||
render: renderDefaultIfEmptyElement,
|
||||
}
|
||||
];
|
||||
|
||||
return columns;
|
||||
}
|
||||
|
||||
getDataTableConfig() {
|
||||
const me = this;
|
||||
const columns = this.columns;
|
||||
const columnDefs = this.columnDefs;
|
||||
|
||||
const mode = this.config.mode;
|
||||
const token = document.head.querySelector('[name=\'csrf-token\'][content]').content;
|
||||
|
||||
const config = {
|
||||
ajax: function(data, callback, settings) {
|
||||
$.ajax({
|
||||
url: `/admin/loadTopTen?mode=${mode}&token=${token}`,
|
||||
method: 'GET',
|
||||
data: data,
|
||||
dataType: 'json',
|
||||
success: function(response, textStatus, jqXHR) {
|
||||
callback(response);
|
||||
me.stopAnimation();
|
||||
},
|
||||
error: handleAjaxError,
|
||||
complete: function() {
|
||||
fireEvent('dateFilterChangedCompleted');
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
processing: true,
|
||||
serverSide: true,
|
||||
searching: false,
|
||||
deferLoading: 0,
|
||||
pageLength: 10,
|
||||
paging: false,
|
||||
info: false,
|
||||
lengthChange: false,
|
||||
ordering: false,
|
||||
autoWidth: false,
|
||||
info: false,
|
||||
|
||||
createdRow: function(row, data, dataIndex) {
|
||||
$(row).attr('data-item-id', data.id);
|
||||
},
|
||||
|
||||
drawCallback: function(settings) {
|
||||
me.drawCallback(settings);
|
||||
me.updateTableFooter(this);
|
||||
},
|
||||
|
||||
columnDefs: columnDefs,
|
||||
columns: columns
|
||||
};
|
||||
|
||||
return config;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
import {BaseGrid} from './Base.js?v=2';
|
||||
import {
|
||||
currentPlanRender,
|
||||
currentStatusRender,
|
||||
currentUsageRender,
|
||||
currentBillingEndRender,
|
||||
updateCardButtonRender,
|
||||
} from '../DataRenderers.js?v=2';
|
||||
|
||||
|
||||
export class UsageStatsGrid extends BaseGrid {
|
||||
|
||||
// do not update counter tile
|
||||
updateTableTitle(value) {
|
||||
}
|
||||
|
||||
// do not use pagination
|
||||
updateTableFooter(dataTable) {
|
||||
const tableId = this.config.tableId;
|
||||
const pagerSelector = `#${tableId}_wrapper .dt-paging`;
|
||||
|
||||
$(pagerSelector).hide();
|
||||
}
|
||||
|
||||
get columnDefs() {
|
||||
const columnDefs = [
|
||||
{
|
||||
className: 'subscription-plan-col',
|
||||
targets: 0
|
||||
},
|
||||
{
|
||||
className: 'subscription-status-col',
|
||||
targets: 1
|
||||
},
|
||||
{
|
||||
className: 'subscription-usage-col',
|
||||
targets: 2
|
||||
},
|
||||
{
|
||||
className: 'billing-date-col',
|
||||
targets: 3
|
||||
},
|
||||
{
|
||||
className: 'action-button-col',
|
||||
targets: 4
|
||||
},
|
||||
];
|
||||
|
||||
return columnDefs;
|
||||
}
|
||||
|
||||
get columns() {
|
||||
const columns = [
|
||||
{
|
||||
data: 'sub_plan_api_calls',
|
||||
render: currentPlanRender,
|
||||
orderable: false,
|
||||
},
|
||||
{
|
||||
data: 'sub_status',
|
||||
render: currentStatusRender,
|
||||
orderable: false,
|
||||
},
|
||||
{
|
||||
data: 'sub_calls_used',
|
||||
render: currentUsageRender,
|
||||
orderable: false,
|
||||
},
|
||||
{
|
||||
data: 'sub_next_billed',
|
||||
render: currentBillingEndRender,
|
||||
orderable: false,
|
||||
},
|
||||
{
|
||||
data: 'sub_update_url',
|
||||
render: updateCardButtonRender,
|
||||
orderable: false,
|
||||
},
|
||||
];
|
||||
|
||||
return columns;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
import {BaseGrid} from './Base.js?v=2';
|
||||
import {
|
||||
renderClickableImportantUserWithScore,
|
||||
renderDate,
|
||||
renderUserFirstname,
|
||||
renderUserId,
|
||||
renderUserLastname,
|
||||
renderUserReviewedStatus,
|
||||
renderTime,
|
||||
} from '../DataRenderers.js?v=2';
|
||||
|
||||
|
||||
export class UsersGrid extends BaseGrid {
|
||||
|
||||
get orderConfig() {
|
||||
return [[4, 'desc']];
|
||||
}
|
||||
|
||||
get columnDefs() {
|
||||
const columnDefs = [
|
||||
{
|
||||
className: 'user-user-col',
|
||||
targets: 0
|
||||
},
|
||||
{
|
||||
className: 'user-userid-col',
|
||||
targets: 1
|
||||
},
|
||||
{
|
||||
className: 'user-name-col',
|
||||
targets: 2
|
||||
},
|
||||
{
|
||||
className: 'user-name-col',
|
||||
targets: 3
|
||||
},
|
||||
{
|
||||
className: 'user-date-col',
|
||||
targets: 4
|
||||
},
|
||||
{
|
||||
className: 'user-timestamp-col',
|
||||
targets: 5
|
||||
},
|
||||
{
|
||||
className: 'user-status-col',
|
||||
targets: 6
|
||||
},
|
||||
{
|
||||
visible: false,
|
||||
targets: 7
|
||||
}
|
||||
];
|
||||
|
||||
return columnDefs;
|
||||
}
|
||||
|
||||
get columns() {
|
||||
const columns = [
|
||||
{
|
||||
data: 'score',
|
||||
render: (data, type, record) => {
|
||||
return renderClickableImportantUserWithScore(record, 'medium');
|
||||
}
|
||||
},
|
||||
{
|
||||
data: 'accounttitle',
|
||||
render: renderUserId
|
||||
},
|
||||
{
|
||||
data: 'firstname',
|
||||
render: (data, type, record) => {
|
||||
return renderUserFirstname(record);
|
||||
},
|
||||
},
|
||||
{
|
||||
data: 'lastname',
|
||||
render: (data, type, record) => {
|
||||
return renderUserLastname(record);
|
||||
},
|
||||
},
|
||||
{
|
||||
data: 'created',
|
||||
render: renderDate,
|
||||
},
|
||||
{
|
||||
data: 'lastseen',
|
||||
render: renderTime,
|
||||
},
|
||||
{
|
||||
data: 'fraud',
|
||||
render: (data, type, record) => {
|
||||
return renderUserReviewedStatus(record);
|
||||
},
|
||||
},
|
||||
{
|
||||
data: 'id',
|
||||
name: 'id',
|
||||
},
|
||||
];
|
||||
|
||||
return columns;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
import {BaseGrid} from '../Base.js?v=2';
|
||||
import {
|
||||
renderDate,
|
||||
renderAuditField,
|
||||
renderAuditValue,
|
||||
renderAuditParent,
|
||||
} from '../../DataRenderers.js?v=2';
|
||||
|
||||
export class FieldAuditTrailGrid extends BaseGrid {
|
||||
get orderConfig() {
|
||||
return [];
|
||||
}
|
||||
|
||||
get columnDefs() {
|
||||
const columnDefs = [
|
||||
{
|
||||
className: 'field-audit-trail-date-col',
|
||||
targets: 0
|
||||
},
|
||||
{
|
||||
className: 'field-audit-trail-field-col',
|
||||
targets: 1
|
||||
},
|
||||
{
|
||||
className: 'field-audit-trail-value-col',
|
||||
targets: 2
|
||||
},
|
||||
{
|
||||
className: 'field-audit-trail-value-col',
|
||||
targets: 3
|
||||
},
|
||||
{
|
||||
className: 'field-audit-trail-parent-col',
|
||||
targets: 4
|
||||
},
|
||||
];
|
||||
|
||||
return columnDefs;
|
||||
}
|
||||
|
||||
get columns() {
|
||||
const columns = [
|
||||
{
|
||||
data: 'created',
|
||||
render: (data, type, record) => {
|
||||
return renderDate(data);
|
||||
},
|
||||
},
|
||||
{
|
||||
data: 'field_id',
|
||||
render: (data, type, record) => {
|
||||
return renderAuditField(record);
|
||||
},
|
||||
},
|
||||
{
|
||||
data: 'old_value',
|
||||
render: renderAuditValue,
|
||||
},
|
||||
{
|
||||
data: 'new_value',
|
||||
render: renderAuditValue,
|
||||
},
|
||||
{
|
||||
data: 'parent_id',
|
||||
render: (data, type, record) => {
|
||||
return renderAuditParent(record);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
return columns;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
import {Loader} from '../Loader.js?v=2';
|
||||
import {Tooltip} from '../Tooltip.js?v=2';
|
||||
import {fireEvent} from '../utils/Event.js?v=2';
|
||||
import {handleAjaxError} from '../utils/ErrorHandler.js?v=2';
|
||||
|
||||
export class BasePanel {
|
||||
|
||||
constructor(eventParams) {
|
||||
this.enrichment = eventParams.enrichment;
|
||||
this.type = eventParams.type;
|
||||
this.url = eventParams.url;
|
||||
this.cardId = eventParams.cardId;
|
||||
this.panelClosed = eventParams.panelClosed;
|
||||
this.closePanel = eventParams.closePanel;
|
||||
this.rowClicked = eventParams.rowClicked;
|
||||
|
||||
this.loader = new Loader();
|
||||
|
||||
this.itemId = null;
|
||||
|
||||
const onCloseDetailsPanel = this.onCloseDetailsPanel.bind(this);
|
||||
window.addEventListener(this.closePanel, onCloseDetailsPanel, false);
|
||||
|
||||
const onTableRowClicked = this.onTableRowClicked.bind(this);
|
||||
window.addEventListener(this.rowClicked, onTableRowClicked, false);
|
||||
|
||||
const onKeydown = this.onKeydown.bind(this);
|
||||
window.addEventListener('keydown', onKeydown, false);
|
||||
|
||||
const onCloseButtonClick = this.onCloseButtonClick.bind(this);
|
||||
this.closeButton.addEventListener('click', onCloseButtonClick, false);
|
||||
|
||||
if (this.enrichment) {
|
||||
const onEnrichmentButtonClick = this.onEnrichmentButtonClick.bind(this);
|
||||
this.reenrichmentButton.addEventListener('click', onEnrichmentButtonClick, false);
|
||||
}
|
||||
|
||||
this.allPanels = {
|
||||
'event': {id: 'event-card', closeEvent: 'eventPanelClosed'},
|
||||
'logbook': {id: 'logbook-card',closeEvent: 'logbookPanelClosed'},
|
||||
'email': {id: 'email-card', closeEvent: 'emailPanelClosed'},
|
||||
'device': {id: 'device-card', closeEvent: 'devicePanelClosed'},
|
||||
'phone': {id: 'phone-card', closeEvent: 'phonePanelClosed'},
|
||||
};
|
||||
}
|
||||
|
||||
//https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key
|
||||
onKeydown(e) {
|
||||
// procced keydown even if event was processed
|
||||
|
||||
switch (e.key) {
|
||||
case 'Esc': // IE/Edge specific value
|
||||
case 'Escape': {
|
||||
this.close();
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Cancel the default action to avoid it being handled twice
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
onEnrichmentButtonClick(e) {
|
||||
this.contentDiv.classList.add('is-hidden');
|
||||
this.loaderDiv.classList.remove('is-hidden');
|
||||
this.card.classList.remove('is-hidden');
|
||||
|
||||
const el = this.loaderDiv;
|
||||
this.loader.start(el);
|
||||
|
||||
this.reenrichmentButton.setAttribute('disabled', '');
|
||||
this.reenrichmentButton.classList.add('is-hidden');
|
||||
|
||||
const onEnrichmentLoaded = this.onEnrichmentLoaded.bind(this);
|
||||
const token = document.head.querySelector('[name=\'csrf-token\'][content]').content;
|
||||
|
||||
$.ajax({
|
||||
url: '/admin/reenrichment',
|
||||
type: 'post',
|
||||
data: {type: this.type, entityId: this.itemId, token: token},
|
||||
success: onEnrichmentLoaded,
|
||||
error: handleAjaxError,
|
||||
});
|
||||
}
|
||||
|
||||
onEnrichmentLoaded(data, status) {
|
||||
if ('success' !== status || 0 === data.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.loadData(this.itemId);
|
||||
}
|
||||
|
||||
onCloseButtonClick(e) {
|
||||
e.preventDefault();
|
||||
this.close();
|
||||
}
|
||||
|
||||
onCloseDetailsPanel() {
|
||||
this.close();
|
||||
}
|
||||
|
||||
loadData(id) {
|
||||
this.contentDiv.classList.add('is-hidden');
|
||||
this.loaderDiv.classList.remove('is-hidden');
|
||||
this.card.classList.remove('is-hidden');
|
||||
|
||||
const el = this.loaderDiv;
|
||||
this.loader.start(el);
|
||||
|
||||
const onDetailsLoaded = this.onDetailsLoaded.bind(this);
|
||||
const token = document.head.querySelector('[name=\'csrf-token\'][content]').content;
|
||||
|
||||
$.ajax({
|
||||
url: this.url,
|
||||
type: 'get',
|
||||
data: {id: id, token: token},
|
||||
success: onDetailsLoaded,
|
||||
error: handleAjaxError,
|
||||
});
|
||||
}
|
||||
|
||||
onTableRowClicked({detail}) {
|
||||
this.itemId = detail.itemId;
|
||||
this.loadData(this.itemId);
|
||||
}
|
||||
|
||||
onDetailsLoaded(data, status) {
|
||||
|
||||
if ('success' !== status || 0 === data.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
data = this.proceedData(data);
|
||||
|
||||
if (this.enrichment && data.hasOwnProperty('checked') && this.reenrichmentButton) {
|
||||
if (data.checked === false && data.enrichable) {
|
||||
this.reenrichmentButton.removeAttribute('disabled');
|
||||
this.reenrichmentButton.classList.remove('is-hidden');
|
||||
} else {
|
||||
this.reenrichmentButton.setAttribute('disabled', '');
|
||||
this.reenrichmentButton.classList.add('is-hidden');
|
||||
}
|
||||
}
|
||||
|
||||
this.loader.stop();
|
||||
this.contentDiv.classList.remove('is-hidden');
|
||||
this.loaderDiv.classList.add('is-hidden');
|
||||
|
||||
let span = null;
|
||||
//todo: foreach and arrow fn ?
|
||||
for (const key in data) {
|
||||
span = this.card.querySelector(`#details_${key}`);
|
||||
if (span) {
|
||||
if (data[key] instanceof Node) {
|
||||
span.replaceChildren(data[key]);
|
||||
} else {
|
||||
span.innerHTML = data[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.initTooltips();
|
||||
}
|
||||
|
||||
initTooltips() {
|
||||
Tooltip.addTooltipsToEventDetailsPanel();
|
||||
}
|
||||
|
||||
proceedData(data) {
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
close() {
|
||||
fireEvent(this.panelClosed);
|
||||
this.card.classList.add('is-hidden');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
get loaderDiv() {
|
||||
return this.card.querySelector('div.text-loader');
|
||||
}
|
||||
|
||||
get contentDiv() {
|
||||
return this.card.querySelector('div.content');
|
||||
}
|
||||
|
||||
get card() {
|
||||
return document.querySelector(`.details-card#${this.cardId}`);
|
||||
}
|
||||
|
||||
get closeButton() {
|
||||
return this.card.querySelector('.delete');
|
||||
}
|
||||
|
||||
get reenrichmentButton() {
|
||||
return this.card.querySelector('.reenrichment-button');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import {BasePanel} from './BasePanel.js?v=2';
|
||||
import {
|
||||
renderDeviceWithOs,
|
||||
renderBrowser,
|
||||
renderLanguage,
|
||||
renderDate,
|
||||
renderBoolean,
|
||||
renderUserAgent,
|
||||
} from '../DataRenderers.js?v=2';
|
||||
|
||||
export class DevicePanel extends BasePanel {
|
||||
|
||||
constructor() {
|
||||
let eventParams = {
|
||||
//enrichment: true,
|
||||
enrichemnt: false,
|
||||
type: 'device',
|
||||
url: '/admin/deviceDetails',
|
||||
cardId: 'device-card',
|
||||
panelClosed: 'devicePanelClosed',
|
||||
closePanel: 'closeDevicePanel',
|
||||
rowClicked: 'deviceTableRowClicked',
|
||||
};
|
||||
super(eventParams);
|
||||
}
|
||||
|
||||
proceedData(data) {
|
||||
let browser_name = data.browser_name;
|
||||
let browser_version = data.browser_version;
|
||||
browser_name = (browser_name !== null && browser_name !== undefined) ? browser_name : '';
|
||||
browser_version = (browser_version !== null && browser_version !== undefined) ? browser_version : '';
|
||||
|
||||
const device_record = {
|
||||
ua: data.ua,
|
||||
os_name: data.os_name,
|
||||
os_version: data.os_version,
|
||||
device_name: data.device,
|
||||
browser: `${browser_name} ${browser_version}`,
|
||||
lang: data.lang
|
||||
};
|
||||
data.device = renderDeviceWithOs(device_record);
|
||||
data.browser = renderBrowser(device_record);
|
||||
data.lang = renderLanguage(device_record);
|
||||
data.device_created = renderDate(data.created);
|
||||
|
||||
data.ua_modified = renderBoolean(data.modified);
|
||||
data.ua = renderUserAgent(data);
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import {BasePanel} from './BasePanel.js?v=2';
|
||||
import {
|
||||
renderEmail,
|
||||
renderReputation,
|
||||
renderBoolean,
|
||||
renderDefaultIfEmptyElement,
|
||||
renderDate,
|
||||
renderClickableDomain,
|
||||
renderHttpCode,
|
||||
} from '../DataRenderers.js?v=2';
|
||||
|
||||
export class EmailPanel extends BasePanel {
|
||||
|
||||
constructor() {
|
||||
let eventParams = {
|
||||
enrichment: true,
|
||||
type: 'email',
|
||||
url: '/admin/emailDetails',
|
||||
cardId: 'email-card',
|
||||
panelClosed: 'emailPanelClosed',
|
||||
closePanel: 'closeEmailPanel',
|
||||
rowClicked: 'emailTableRowClicked',
|
||||
};
|
||||
super(eventParams);
|
||||
}
|
||||
|
||||
proceedData(data) {
|
||||
data.email = renderEmail(data, 'long');
|
||||
data.reputation = renderReputation(data);
|
||||
|
||||
// to 'No breach'
|
||||
data.data_breach = renderBoolean(data.data_breach === null ? null : !data.data_breach);
|
||||
// to 'No Profiles'
|
||||
// data.profiles = renderBoolean(data.profiles === null ? null : data.profiles === 0);
|
||||
data.data_breaches = renderDefaultIfEmptyElement(data.data_breaches);
|
||||
|
||||
data.earliest_breach = renderDate(data.earliest_breach);
|
||||
data.fraud_detected = renderBoolean(data.fraud_detected);
|
||||
data.blockemails = renderBoolean(data.blockemails);
|
||||
// TODO: return alert_list back in next release
|
||||
//data.alert_list = renderBoolean(data.alert_list);
|
||||
data.domain_contact_email = renderBoolean(data.domain_contact_email);
|
||||
|
||||
data.free_email_provider = renderBoolean(data.free_email_provider);
|
||||
|
||||
const domain_record = {
|
||||
domain: data.domain,
|
||||
id: data.domain_id,
|
||||
};
|
||||
data.domain = renderClickableDomain(domain_record, 'long');
|
||||
data.blockdomains = renderBoolean(data.blockdomains);
|
||||
data.disabled = renderBoolean(data.disabled);
|
||||
data.mx_record = renderBoolean(data.mx_record === null ? null : !data.mx_record);
|
||||
data.disposable_domains = renderBoolean(data.disposable_domains);
|
||||
data.disabled = renderBoolean(data.disabled);
|
||||
data.tranco_rank = renderDefaultIfEmptyElement(data.tranco_rank);
|
||||
data.creation_date = renderDate(data.creation_date);
|
||||
data.expiration_date = renderDate(data.expiration_date);
|
||||
data.closest_snapshot = renderDate(data.closest_snapshot);
|
||||
data.return_code = renderHttpCode({http_code: data.return_code});
|
||||
|
||||
// also data.checked is used
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,213 @@
|
||||
import {BasePanel} from './BasePanel.js?v=2';
|
||||
import {
|
||||
renderTime,
|
||||
renderHttpCode,
|
||||
renderHttpMethod,
|
||||
renderClickableImportantUserWithScore,
|
||||
renderUserId,
|
||||
renderUserReviewedStatus,
|
||||
renderDate,
|
||||
renderScoreDetails,
|
||||
renderEmail,
|
||||
renderReputation,
|
||||
renderBoolean,
|
||||
renderDefaultIfEmptyElement,
|
||||
renderClickableDomain,
|
||||
renderPhone,
|
||||
renderFullCountry,
|
||||
renderPhoneCarrierName,
|
||||
renderPhoneType,
|
||||
renderUserCounter,
|
||||
renderClickableResourceWithoutQuery,
|
||||
renderDeviceWithOs,
|
||||
// renderClickableDeviceId,
|
||||
renderBrowser,
|
||||
renderLanguage,
|
||||
renderCidr,
|
||||
renderNetName,
|
||||
renderClickableIpWithCountry,
|
||||
renderClickableCountryName,
|
||||
renderReferer,
|
||||
renderUserAgent,
|
||||
renderQuery,
|
||||
renderClickableAsn,
|
||||
renderUserFirstname,
|
||||
renderUserLastname,
|
||||
renderIpType,
|
||||
renderJsonTextarea,
|
||||
} from '../DataRenderers.js?v=2';
|
||||
|
||||
export class EventPanel extends BasePanel {
|
||||
|
||||
constructor() {
|
||||
let eventParams = {
|
||||
enrichment: false,
|
||||
type: 'event',
|
||||
url: '/admin/eventDetails',
|
||||
cardId: 'event-card',
|
||||
panelClosed: 'eventPanelClosed',
|
||||
closePanel: 'closeEventPanel',
|
||||
rowClicked: 'eventTableRowClicked',
|
||||
};
|
||||
super(eventParams);
|
||||
}
|
||||
|
||||
proceedData(data) {
|
||||
const event_record = {
|
||||
time: data.event_time,
|
||||
http_code: data.event_http_code,
|
||||
http_method: data.event_http_method_name,
|
||||
};
|
||||
data.event_time = renderTime(event_record.time);
|
||||
data.event_http_code = renderHttpCode(event_record);
|
||||
data.event_http_method = renderHttpMethod(event_record);
|
||||
//data.event_type_name = data.event_type_name;
|
||||
|
||||
//Convert to boolean if number exists
|
||||
//if(Number.isInteger(data.profiles)) {
|
||||
// data.email_profiles = !!data.profiles;
|
||||
//
|
||||
// //Revert profiles to "No profiles"
|
||||
// data.email_profiles = !data.email_profiles;
|
||||
//}
|
||||
|
||||
//Convert to boolean if number exists
|
||||
//if(Number.isInteger(data.phone_profiles)) {
|
||||
// data.phone_profiles = !!data.phone_profiles;
|
||||
//
|
||||
// //Revert profiles to "No profiles"
|
||||
// data.phone_profiles = !data.phone_profiles;
|
||||
//}
|
||||
|
||||
if ('boolean' === typeof data.data_breach) {
|
||||
//Revert data_breach to "No breach"
|
||||
data.data_breach = !data.data_breach;
|
||||
}
|
||||
const current_email_record = {
|
||||
accountid: data.accountid,
|
||||
accounttitle: data.accounttitle,
|
||||
email: data.current_email,
|
||||
score_updated_at: data.score_updated_at,
|
||||
score: data.score,
|
||||
fraud: data.fraud,
|
||||
};
|
||||
data.user_id = renderClickableImportantUserWithScore(current_email_record, 'long');
|
||||
data.accounttitle = renderUserId(data.accounttitle);
|
||||
data.reviewed_status = renderUserReviewedStatus(data);
|
||||
data.latest_decision = renderDate(data.latest_decision);
|
||||
data.score_details = renderScoreDetails(data);
|
||||
|
||||
if (!!document.getElementById('details_email')) {
|
||||
data.email = renderEmail(data, 'long');
|
||||
data.reputation = renderReputation(data);
|
||||
//data.email_profiles = renderBoolean(data.email_profiles);
|
||||
data.free_provider = renderBoolean(data.free_email_provider);
|
||||
data.data_breach = renderBoolean(data.data_breach);
|
||||
data.data_breaches = renderDefaultIfEmptyElement(data.data_breaches);
|
||||
data.blockemails = renderBoolean(data.blockemails);
|
||||
data.email_fraud_detected = renderBoolean(data.email_fraud_detected);
|
||||
// TODO: return alert_list back in next release
|
||||
//data.email_alert_list = renderBoolean(data.email_alert_list);
|
||||
data.email_earliest_breach= renderDate(data.email_earliest_breach);
|
||||
|
||||
const domain_record = {
|
||||
domain: data.domain,
|
||||
id: data.domainid,
|
||||
http_code: data.domain_return_code,
|
||||
};
|
||||
data.domain = renderClickableDomain(domain_record, 'long');
|
||||
data.tranco_rank = renderDefaultIfEmptyElement(data.tranco_rank);
|
||||
data.blockdomains = renderBoolean(data.blockdomains);
|
||||
data.disposable_domains = renderBoolean(data.disposable_domains);
|
||||
data.domain_disabled = renderBoolean(data.domain_disabled);
|
||||
data.domain_creation_date = renderDate(data.domain_creation_date);
|
||||
data.domain_expiration_date = renderDate(data.domain_expiration_date);
|
||||
data.domain_return_code = renderHttpCode(domain_record);
|
||||
|
||||
const phone_record = {
|
||||
phonenumber: data.phonenumber,
|
||||
country_id: data.phone_country_id,
|
||||
country_iso: data.phone_country_iso,
|
||||
full_country: data.phone_full_country,
|
||||
carrier_name: data.carrier_name,
|
||||
type: data.phone_type
|
||||
};
|
||||
data.phonenumber = renderPhone(phone_record);
|
||||
data.phone_country = renderFullCountry(data.phone_full_country);
|
||||
data.carrier_name = renderPhoneCarrierName(phone_record);
|
||||
data.phone_type = renderPhoneType(phone_record);
|
||||
data.phone_users = renderUserCounter(data.phone_users, 2);
|
||||
data.phone_invalid = renderBoolean(data.phone_invalid);
|
||||
data.phone_fraud_detected = renderBoolean(data.phone_fraud_detected);
|
||||
//data.phone_profiles = renderBoolean(data.phone_profiles);
|
||||
// TODO: return alert_list back in next release
|
||||
//data.phone_alert_list = renderBoolean(data.phone_alert_list);
|
||||
}
|
||||
|
||||
data.url = renderClickableResourceWithoutQuery(data);
|
||||
|
||||
let browser_name = data.browser_name;
|
||||
let browser_version = data.browser_version;
|
||||
browser_name = (browser_name !== null && browser_name !== undefined) ? browser_name : '';
|
||||
browser_version = (browser_version !== null && browser_version !== undefined) ? browser_version : '';
|
||||
|
||||
const device_record = {
|
||||
id: data.deviceid,
|
||||
ua: data.ua,
|
||||
os_name: data.os_name,
|
||||
os_version: data.os_version,
|
||||
device_name: data.device_name,
|
||||
browser: `${browser_name} ${browser_version}`,
|
||||
lang: data.lang
|
||||
};
|
||||
data.device = renderDeviceWithOs(device_record);
|
||||
//data.device_id = renderClickableDeviceId(device_record);
|
||||
data.browser = renderBrowser(device_record);
|
||||
data.lang = renderLanguage(device_record);
|
||||
data.device_created = renderDate(data.device_created);
|
||||
|
||||
data.ua_modified = renderBoolean(data.ua_modified);
|
||||
const ip_country_record = {
|
||||
isp_name: data.netname,
|
||||
ipid: data.ipid,
|
||||
ip: data.ip,
|
||||
country_id: data.ip_country_id,
|
||||
country_iso: data.ip_country_iso,
|
||||
full_country: data.ip_full_country,
|
||||
};
|
||||
|
||||
data.cidr = renderCidr(data);
|
||||
data.netname = renderNetName(data);
|
||||
|
||||
data.ip = renderClickableIpWithCountry(ip_country_record);
|
||||
data.ip_country = renderClickableCountryName(ip_country_record);
|
||||
|
||||
data.referer = renderReferer(data);
|
||||
data.ua = renderUserAgent(data);
|
||||
data.query = renderQuery(data);
|
||||
|
||||
data.asn = renderClickableAsn(data);
|
||||
|
||||
data.firstname = renderUserFirstname(data);
|
||||
data.lastname = renderUserLastname(data);
|
||||
|
||||
data.ip_users = renderUserCounter(data.ip_users, 2, true);
|
||||
data.ip_events = renderDefaultIfEmptyElement(data.ip_events);
|
||||
//data.ip_events = data.ip_events;
|
||||
data.ip_spamlist = renderBoolean(data.spamlist);
|
||||
data.ip_type = renderIpType(data);
|
||||
// TODO: return alert_list back in next release
|
||||
//data.ip_alert_list = renderBoolean(data.ip_alert_list);
|
||||
|
||||
/***
|
||||
data.tor = renderBoolean(data.tor);
|
||||
data.vpn = renderBoolean(data.vpn);
|
||||
data.relay = renderBoolean(data.relay);
|
||||
data.data_center = renderBoolean(data.data_center);
|
||||
***/
|
||||
|
||||
data.payload = renderJsonTextarea(data.event_payload);
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import {BasePanel} from './BasePanel.js?v=2';
|
||||
import {
|
||||
renderIp,
|
||||
renderTimeMs,
|
||||
renderErrorType,
|
||||
renderSensorError,
|
||||
renderJsonTextarea,
|
||||
renderMailto,
|
||||
} from '../DataRenderers.js?v=2';
|
||||
|
||||
export class LogbookPanel extends BasePanel {
|
||||
|
||||
constructor() {
|
||||
let eventParams = {
|
||||
enrichment: false,
|
||||
type: 'logbook',
|
||||
url: '/admin/logbookDetails',
|
||||
cardId: 'logbook-card',
|
||||
panelClosed: 'logbookPanelClosed',
|
||||
closePanel: 'closeLogbookPanel',
|
||||
rowClicked: 'logbookTableRowClicked',
|
||||
};
|
||||
super(eventParams);
|
||||
}
|
||||
|
||||
proceedData(data) {
|
||||
data.ip = renderIp(data);
|
||||
data.started = renderTimeMs(data.started);
|
||||
data.error_type = renderErrorType(data);
|
||||
data.error_text = renderSensorError(data);
|
||||
data.request = renderJsonTextarea(data.raw);
|
||||
|
||||
data.mailto = renderMailto(data);
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
import {BasePanel} from './BasePanel.js?v=2';
|
||||
import {
|
||||
renderPhone,
|
||||
renderDefaultIfEmptyElement,
|
||||
renderFullCountry,
|
||||
renderPhoneCarrierName,
|
||||
renderPhoneType,
|
||||
renderUserCounter,
|
||||
renderBoolean,
|
||||
renderUsersList,
|
||||
} from '../DataRenderers.js?v=2';
|
||||
|
||||
export class PhonePanel extends BasePanel {
|
||||
|
||||
constructor() {
|
||||
let eventParams = {
|
||||
enrichment: true,
|
||||
type: 'phone',
|
||||
url: '/admin/phoneDetails',
|
||||
cardId: 'phone-card',
|
||||
panelClosed: 'phonePanelClosed',
|
||||
closePanel: 'closePhonePanel',
|
||||
rowClicked: 'phoneTableRowClicked',
|
||||
};
|
||||
super(eventParams);
|
||||
}
|
||||
|
||||
proceedData(data) {
|
||||
const phone_record = {
|
||||
phonenumber: data.phone_number,
|
||||
country_id: data.country_id,
|
||||
country_iso: data.country_iso,
|
||||
full_country: data.full_country,
|
||||
carrier_name: data.carrier_name,
|
||||
type: data.type
|
||||
};
|
||||
data.phone_number = renderPhone(phone_record);
|
||||
data.phone_national = renderDefaultIfEmptyElement(data.national_format);
|
||||
data.country = renderFullCountry(data.full_country);
|
||||
data.carrier_name = renderPhoneCarrierName(phone_record);
|
||||
data.type = renderPhoneType(phone_record);
|
||||
data.shared = renderUserCounter(data.shared, 2);
|
||||
|
||||
// to 'No Profiles'
|
||||
//data.profiles = renderBoolean(data.profiles === null ? null : data.profiles === 0);
|
||||
|
||||
data.fraud_detected = renderBoolean(data.fraud_detected);
|
||||
data.invalid = renderBoolean(data.invalid);
|
||||
// TODO: return alert_list back in next release
|
||||
//data.alert_list = renderBoolean(data.alert_list);
|
||||
|
||||
data.shared_users = renderUsersList(data.shared_users);
|
||||
|
||||
// also data.checked is used
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
const MAX_STRING_LONG_NETNAME_IN_TABLE = 48;
|
||||
const MAX_STRING_SHORT_NETNAME_IN_TABLE = 25;
|
||||
const MAX_STRING_LENGTH_IN_TABLE = 18;
|
||||
const MAX_STRING_USERID_LENGTH_IN_TABLE = 15;
|
||||
const MAX_STRING_USER_SHORT_LENGTH_IN_TABLE = 19;
|
||||
const MAX_STRING_USER_MEDIUM_LENGTH_IN_TABLE = 23;
|
||||
const MAX_STRING_USER_LONG_LENGTH_IN_TABLE = 32;
|
||||
const MAX_STRING_USER_LONG_LENGTH_IN_TILE = 26;
|
||||
const MAX_STRING_USER_NAME_IN_TABLE = 10;
|
||||
const MAX_STRING_LENGTH_IN_TABLE_ON_DASHBOARD = 24;
|
||||
const MAX_STRING_LENGTH_FOR_EMAIL = 14;
|
||||
const MAX_STRING_LENGTH_FOR_PHONE = 17;
|
||||
const MAX_STRING_LENGTH_FULL_COUNTRY = 23;
|
||||
const MAX_STRING_LENGTH_FOR_TILE = 15;
|
||||
const MAX_STRING_DEVICE_OS_LENGTH = 10;
|
||||
const MAX_STRING_LENGTH_URL = 32;
|
||||
const MAX_TOOLTIP_URL_LENGTH = 50;
|
||||
const MAX_TOOLTIP_LENGTH = 121;
|
||||
|
||||
const COLOR_RED = '#FB6E88';
|
||||
const COLOR_GREEN = '#01EE99';
|
||||
const COLOR_YELLOW = '#F5B944';
|
||||
const COLOR_PURPLE = '#BE95EB';
|
||||
|
||||
const COLOR_LIGHT_GREEN = 'rgba(64,220,97,0.03)';
|
||||
const COLOR_LIGHT_YELLOW = 'rgba(225,224,137,0.03)';
|
||||
const COLOR_LIGHT_RED = 'rgba(255,51,102,0.03)';
|
||||
const COLOR_LIGHT_PURPLE = 'rgba(190,149,235,0.03)';
|
||||
|
||||
const USER_LOW_TRUST_SCORE_INF = 0;
|
||||
const USER_LOW_TRUST_SCORE_SUP = 33;
|
||||
const USER_MEDIUM_TRUST_SCORE_INF = 33;
|
||||
const USER_MEDIUM_TRUST_SCORE_SUP = 67;
|
||||
const USER_HIGH_TRUST_SCORE_INF = 67;
|
||||
|
||||
const USER_IPS_CRITICAL_VALUE = 9;
|
||||
const USER_EVENTS_CRITICAL_VALUE = Infinity;
|
||||
const USER_DEVICES_CRITICAL_VALUE = 4;
|
||||
const USER_COUNTRIES_CRITICAL_VALUE = 3;
|
||||
|
||||
const MAX_HOURS_CHART = 96;
|
||||
const MIN_HOURS_CHART = 3;
|
||||
const DAYS_IN_RANGE = 1;
|
||||
const X_AXIS_SERIFS = 8;
|
||||
|
||||
const ASN_OVERRIDE = {
|
||||
'0': 'LAN',
|
||||
'64496': 'N/A',
|
||||
};
|
||||
|
||||
const COUNTRIES_EXCEPTIONS = [null, undefined, 'N/A', 'AN', 'CS', 'YU'];
|
||||
|
||||
const NORMAL_DEVICES = ['smartphone', 'desktop', 'bot', 'tablet'];
|
||||
|
||||
const PHONE_LANDLINE = [
|
||||
'landline',
|
||||
'FIXED_LINE',
|
||||
'FIXED_LINE_OR_MOBILE',
|
||||
'TOLL_FREE',
|
||||
'SHARED_COST',
|
||||
];
|
||||
|
||||
const NO_RULES_MSG = {
|
||||
value: 'No rule',
|
||||
tooltip: 'User currently doesn\'t correspond to selected rules.',
|
||||
};
|
||||
|
||||
const UNDEFINED_RULES_MSG = {
|
||||
value: 'In queue',
|
||||
tooltip: 'Waiting for a score to be calculated.',
|
||||
};
|
||||
|
||||
const MIDLINE_HELLIP = '\u22EF';
|
||||
const HELLIP = '\u2026';
|
||||
const HYPHEN = '\uFF0D';
|
||||
|
||||
const COLOR_MAP = {
|
||||
'red': {
|
||||
'main': COLOR_RED,
|
||||
'light': COLOR_LIGHT_RED,
|
||||
},
|
||||
'yellow': {
|
||||
'main': COLOR_YELLOW,
|
||||
'light': COLOR_LIGHT_YELLOW,
|
||||
},
|
||||
'green': {
|
||||
'main': COLOR_GREEN,
|
||||
'light': COLOR_LIGHT_GREEN,
|
||||
},
|
||||
'purple': {
|
||||
'main': COLOR_PURPLE,
|
||||
'light': COLOR_LIGHT_PURPLE,
|
||||
},
|
||||
};
|
||||
|
||||
export {
|
||||
MAX_STRING_LONG_NETNAME_IN_TABLE,
|
||||
MAX_STRING_SHORT_NETNAME_IN_TABLE,
|
||||
MAX_STRING_LENGTH_IN_TABLE,
|
||||
MAX_STRING_USERID_LENGTH_IN_TABLE,
|
||||
MAX_STRING_USER_SHORT_LENGTH_IN_TABLE,
|
||||
MAX_STRING_USER_MEDIUM_LENGTH_IN_TABLE,
|
||||
MAX_STRING_USER_LONG_LENGTH_IN_TABLE,
|
||||
MAX_STRING_USER_LONG_LENGTH_IN_TILE,
|
||||
MAX_STRING_USER_NAME_IN_TABLE,
|
||||
MAX_STRING_LENGTH_IN_TABLE_ON_DASHBOARD,
|
||||
MAX_STRING_LENGTH_FOR_EMAIL,
|
||||
MAX_STRING_LENGTH_FOR_PHONE,
|
||||
MAX_STRING_LENGTH_FULL_COUNTRY,
|
||||
MAX_STRING_LENGTH_FOR_TILE,
|
||||
MAX_STRING_DEVICE_OS_LENGTH,
|
||||
MAX_STRING_LENGTH_URL,
|
||||
MAX_TOOLTIP_URL_LENGTH,
|
||||
MAX_TOOLTIP_LENGTH,
|
||||
|
||||
COLOR_RED,
|
||||
COLOR_GREEN,
|
||||
COLOR_YELLOW,
|
||||
COLOR_PURPLE,
|
||||
|
||||
COLOR_LIGHT_GREEN,
|
||||
COLOR_LIGHT_YELLOW,
|
||||
COLOR_LIGHT_RED,
|
||||
COLOR_LIGHT_PURPLE,
|
||||
|
||||
USER_LOW_TRUST_SCORE_INF,
|
||||
USER_LOW_TRUST_SCORE_SUP,
|
||||
USER_MEDIUM_TRUST_SCORE_INF,
|
||||
USER_MEDIUM_TRUST_SCORE_SUP,
|
||||
USER_HIGH_TRUST_SCORE_INF,
|
||||
|
||||
USER_IPS_CRITICAL_VALUE,
|
||||
USER_EVENTS_CRITICAL_VALUE,
|
||||
USER_DEVICES_CRITICAL_VALUE,
|
||||
USER_COUNTRIES_CRITICAL_VALUE,
|
||||
|
||||
MAX_HOURS_CHART,
|
||||
MIN_HOURS_CHART,
|
||||
DAYS_IN_RANGE,
|
||||
X_AXIS_SERIFS,
|
||||
|
||||
ASN_OVERRIDE,
|
||||
|
||||
COUNTRIES_EXCEPTIONS,
|
||||
NORMAL_DEVICES,
|
||||
PHONE_LANDLINE,
|
||||
NO_RULES_MSG,
|
||||
UNDEFINED_RULES_MSG,
|
||||
|
||||
MIDLINE_HELLIP,
|
||||
HELLIP,
|
||||
HYPHEN,
|
||||
|
||||
COLOR_MAP,
|
||||
};
|
||||
@@ -0,0 +1,20 @@
|
||||
const getQueryParams = (params) => {
|
||||
const data = {};
|
||||
Object.assign(data, params);
|
||||
|
||||
if (params.dateRange) {
|
||||
data['dateTo'] = params.dateRange.dateTo;
|
||||
data['dateFrom'] = params.dateRange.dateFrom;
|
||||
data['keepDates'] = params.dateRange.keepDates;
|
||||
}
|
||||
|
||||
if (params.searchValue) {
|
||||
data['search'] = {
|
||||
value: params.searchValue
|
||||
};
|
||||
}
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
export {getQueryParams};
|
||||
@@ -0,0 +1,85 @@
|
||||
const addDays = (date, days) => {
|
||||
const dateCopy = new Date(date);
|
||||
dateCopy.setDate(date.getDate() + days);
|
||||
|
||||
return dateCopy;
|
||||
};
|
||||
|
||||
const addHours = (date, hours) => {
|
||||
const ms = hours * 60 * 60 * 1000;
|
||||
|
||||
const dateCopy = new Date(date);
|
||||
dateCopy.setTime(date.getTime() + ms);
|
||||
|
||||
return dateCopy;
|
||||
};
|
||||
|
||||
//https://stackoverflow.com/a/12550320
|
||||
const padZero = (n, s = 2) => {
|
||||
return (s > 0) ? ('000'+n).slice(-s) : (n+'000').slice(0, -s);
|
||||
};
|
||||
|
||||
const notificationTime = () => {
|
||||
const dt = new Date();
|
||||
const day = padZero(dt.getDate());
|
||||
const month = padZero(dt.getMonth() + 1);
|
||||
const year = padZero(dt.getFullYear(), 4);
|
||||
const hours = padZero(dt.getHours());
|
||||
const minutes = padZero(dt.getMinutes());
|
||||
const seconds = padZero(dt.getSeconds());
|
||||
|
||||
return `[${day}/${month}/${year} ${hours}:${minutes}:${seconds}]`;
|
||||
};
|
||||
|
||||
// offsetInSeconds is not inverted as .getTimezoneOffset() result
|
||||
const formatIntTimeUtc = (ts, useTime, offsetInSeconds = 0) => {
|
||||
const dt = new Date(ts + ((new Date()).getTimezoneOffset() * 60 + offsetInSeconds) * 1000);
|
||||
|
||||
let m = dt.getMonth() + 1;
|
||||
let d = dt.getDate();
|
||||
let y = dt.getFullYear();
|
||||
m = padZero(m);
|
||||
d = padZero(d);
|
||||
y = padZero(y, 4);
|
||||
|
||||
if (!useTime) {
|
||||
return `${d}/${m}/${y}`;
|
||||
}
|
||||
|
||||
let h = dt.getHours();
|
||||
let i = dt.getMinutes();
|
||||
let s = dt.getSeconds();
|
||||
h = padZero(h);
|
||||
i = padZero(i);
|
||||
s = padZero(s);
|
||||
|
||||
return `${d}/${m}/${y} ${h}:${i}:${s}`;
|
||||
};
|
||||
|
||||
const formatStringTime = (dt) => {
|
||||
|
||||
let m = dt.getMonth() + 1;
|
||||
let d = dt.getDate();
|
||||
let y = dt.getFullYear();
|
||||
m = padZero(m);
|
||||
d = padZero(d);
|
||||
y = padZero(y, 4);
|
||||
|
||||
let h = dt.getHours();
|
||||
let i = dt.getMinutes();
|
||||
let s = dt.getSeconds();
|
||||
h = padZero(h);
|
||||
i = padZero(i);
|
||||
s = padZero(s);
|
||||
|
||||
return `${y}-${m}-${d}T${h}:${i}:${s}`;
|
||||
};
|
||||
|
||||
export {
|
||||
formatIntTimeUtc,
|
||||
formatStringTime,
|
||||
notificationTime,
|
||||
padZero,
|
||||
addDays,
|
||||
addHours,
|
||||
};
|
||||
@@ -0,0 +1,43 @@
|
||||
import {notificationTime} from './Date.js?v=2';
|
||||
|
||||
const handleAjaxError = (xhr, status, error) => {
|
||||
// ignore abort, but not network issues
|
||||
if ((xhr.status === 0 && xhr.readyState === 0) || (status && status.status === 0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (xhr.status === 401 || xhr.status === 403) {
|
||||
window.location.href = escape('/login');
|
||||
return;
|
||||
}
|
||||
|
||||
const time = notificationTime();
|
||||
const msg = 'An error occurred while requesting resource. Please try again later.';
|
||||
const notificationEl = document.getElementById('client-error');
|
||||
|
||||
const frag = document.createDocumentFragment();
|
||||
|
||||
const span = document.createElement('span');
|
||||
span.className = 'faded';
|
||||
span.textContent = time;
|
||||
|
||||
const el = document.createTextNode('\u00A0\u00A0' + msg);
|
||||
|
||||
const button = document.createElement('button');
|
||||
button.className = 'delete';
|
||||
|
||||
frag.appendChild(span);
|
||||
frag.appendChild(el);
|
||||
frag.appendChild(button);
|
||||
|
||||
notificationEl.replaceChildren(frag);
|
||||
|
||||
const deleteButton = notificationEl.querySelector('.delete');
|
||||
deleteButton.addEventListener('click', () => {
|
||||
notificationEl.classList.add('is-hidden');
|
||||
});
|
||||
|
||||
notificationEl.classList.remove('is-hidden');
|
||||
};
|
||||
|
||||
export {handleAjaxError};
|
||||
@@ -0,0 +1,8 @@
|
||||
const fireEvent = (name, data) => {
|
||||
const details = {detail: data};
|
||||
const event = new CustomEvent(name, details);
|
||||
|
||||
dispatchEvent(event);
|
||||
};
|
||||
|
||||
export {fireEvent};
|
||||
@@ -0,0 +1,10 @@
|
||||
const debounce = callback => {
|
||||
let timeout;
|
||||
|
||||
return (...args) => {
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(() => callback.apply(this, args), 500);
|
||||
};
|
||||
};
|
||||
|
||||
export {debounce};
|
||||
@@ -0,0 +1,71 @@
|
||||
|
||||
const replaceAll = (str, search, replacement) => {
|
||||
return str.split(search).join(replacement);
|
||||
};
|
||||
|
||||
const getRuleClass = (value) => {
|
||||
switch (value) {
|
||||
case -20:
|
||||
return 'positive';
|
||||
case 10:
|
||||
return 'medium';
|
||||
case 20:
|
||||
return 'high';
|
||||
case 70:
|
||||
return 'extreme';
|
||||
default:
|
||||
return 'none';
|
||||
}
|
||||
};
|
||||
|
||||
const formatTime = (str) => {
|
||||
const dayPattern = /(\d+)\s+days?/;
|
||||
|
||||
let days = 0;
|
||||
const dayMatch = str.match(dayPattern);
|
||||
if (dayMatch) {
|
||||
days = parseInt(dayMatch[1], 10);
|
||||
str = str.replace(dayPattern, '').trim();
|
||||
}
|
||||
|
||||
// remove milliseconds part if exists
|
||||
str = str.split('.')[0];
|
||||
|
||||
const timePattern = /^\d{2}:\d{2}:\d{2}$/;
|
||||
if (!timePattern.test(str)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const parts = str.split(':');
|
||||
const hours = parseInt(parts[0], 10);
|
||||
let minutes = parseInt(parts[1], 10);
|
||||
const seconds = parseInt(parts[2], 10);
|
||||
|
||||
let humanTime = '';
|
||||
if (days > 0) {
|
||||
humanTime += `${days} d ${hours} h `;
|
||||
} else {
|
||||
minutes += 60 * hours;
|
||||
}
|
||||
if (minutes > 0) humanTime += `${minutes} min `;
|
||||
if (seconds > 0) humanTime += `${seconds} s`;
|
||||
|
||||
if (humanTime === '') humanTime = '1 s';
|
||||
|
||||
return humanTime.trim();
|
||||
};
|
||||
|
||||
const openJson = (str) => {
|
||||
try {
|
||||
return JSON.parse(str);
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export {
|
||||
replaceAll,
|
||||
getRuleClass,
|
||||
formatTime,
|
||||
openJson,
|
||||
};
|
||||
Reference in New Issue
Block a user