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:
2025-10-30 11:43:06 -05:00
parent 0ce353ea9d
commit 91d52d2de5
1692 changed files with 202851 additions and 0 deletions

View File

@@ -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');
}
}

View File

@@ -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();
}
}

View File

@@ -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"]');
}
}

View File

@@ -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');
}
}

View File

@@ -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');
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}
}
}

View File

@@ -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');
}
}

View File

@@ -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');
}
}

View File

@@ -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();
}
}

View File

@@ -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');
}
}

View File

@@ -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');
}
}

View File

@@ -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();
});
}
};

View File

@@ -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');
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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));
}
}
}

View File

@@ -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');
}
}

View File

@@ -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');
}
}

View File

@@ -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];
}
}

View File

@@ -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;
}
}

View File

@@ -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());
}
}

View File

@@ -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'];
}
}

View File

@@ -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'),
];
}
}

View File

@@ -0,0 +1,10 @@
import {BaseLineChart} from './BaseLine.js?v=2';
export class BotsChart extends BaseLineChart {
getSeries() {
return [
this.getDaySeries(),
this.getSingleSeries('Bots', 'red'),
];
}
}

View File

@@ -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'),
];
}
}

View File

@@ -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'),
];
}
}

View File

@@ -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'),
];
}
}

View File

@@ -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'),
];
}
}

View File

@@ -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'),
];
}
}

View File

@@ -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'),
];
}
}

View File

@@ -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'),
];
}
}

View File

@@ -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'),
];
}
}

View File

@@ -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;
}
}

View File

@@ -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'
);
}
}

View File

@@ -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'
);
}
}

View File

@@ -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'
);
}
}

View File

@@ -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'
);
}
}

View File

@@ -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'
);
}
}

View File

@@ -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'
);
}
}

View File

@@ -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) {}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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: '&lt;',
next: '&gt;',
},
},
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;
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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');
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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,
};

View File

@@ -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};

View File

@@ -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,
};

View File

@@ -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};

View File

@@ -0,0 +1,8 @@
const fireEvent = (name, data) => {
const details = {detail: data};
const event = new CustomEvent(name, details);
dispatchEvent(event);
};
export {fireEvent};

View File

@@ -0,0 +1,10 @@
const debounce = callback => {
let timeout;
return (...args) => {
clearTimeout(timeout);
timeout = setTimeout(() => callback.apply(this, args), 500);
};
};
export {debounce};

View File

@@ -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,
};