feat(cloudron): add tirreno package artifacts
- Add CloudronStack/output/CloudronPackages-Artifacts/tirreno/ directory and its contents - Includes package manifest, Dockerfile, source code, documentation, and build artifacts - Add tirreno-1761840148.tar.gz as a build artifact - Add tirreno-cloudron-package-1761841304.tar.gz as the Cloudron package - Include all necessary files for the tirreno Cloudron package This adds the complete tirreno Cloudron package artifacts to the repository.
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
import {ApiPage} from '../pages/Api.js';
|
||||
|
||||
new ApiPage();
|
||||
@@ -0,0 +1,3 @@
|
||||
import {BlacklistPage} from '../pages/Blacklist.js';
|
||||
|
||||
new BlacklistPage();
|
||||
@@ -0,0 +1,3 @@
|
||||
import {BotPage} from '../pages/Bot.js';
|
||||
|
||||
new BotPage();
|
||||
@@ -0,0 +1,3 @@
|
||||
import {BotsPage} from '../pages/Bots.js';
|
||||
|
||||
new BotsPage();
|
||||
@@ -0,0 +1,3 @@
|
||||
import {CountriesPage} from '../pages/Countries.js';
|
||||
|
||||
new CountriesPage();
|
||||
@@ -0,0 +1,3 @@
|
||||
import {CountryPage} from '../pages/Country.js';
|
||||
|
||||
new CountryPage();
|
||||
@@ -0,0 +1,3 @@
|
||||
import {DashboardPage} from '../pages/Dashboard.js';
|
||||
|
||||
new DashboardPage();
|
||||
@@ -0,0 +1,3 @@
|
||||
import {DomainPage} from '../pages/Domain.js';
|
||||
|
||||
new DomainPage();
|
||||
@@ -0,0 +1,3 @@
|
||||
import {DomainsPage} from '../pages/Domains.js';
|
||||
|
||||
new DomainsPage();
|
||||
@@ -0,0 +1,3 @@
|
||||
import {EventsPage} from '../pages/Events.js';
|
||||
|
||||
new EventsPage();
|
||||
@@ -0,0 +1,3 @@
|
||||
import {IpPage} from '../pages/Ip.js';
|
||||
|
||||
new IpPage();
|
||||
@@ -0,0 +1,3 @@
|
||||
import {IpsPage} from '../pages/Ips.js';
|
||||
|
||||
new IpsPage();
|
||||
@@ -0,0 +1,3 @@
|
||||
import {IspPage} from '../pages/Isp.js';
|
||||
|
||||
new IspPage();
|
||||
@@ -0,0 +1,3 @@
|
||||
import {IspsPage} from '../pages/Isps.js';
|
||||
|
||||
new IspsPage();
|
||||
@@ -0,0 +1,3 @@
|
||||
import {LogbookPage} from '../pages/Logbook.js';
|
||||
|
||||
new LogbookPage();
|
||||
@@ -0,0 +1,3 @@
|
||||
import {ManualCheckPage} from '../pages/ManualCheck.js';
|
||||
|
||||
new ManualCheckPage();
|
||||
@@ -0,0 +1,3 @@
|
||||
import {ResourcePage} from '../pages/Resource.js';
|
||||
|
||||
new ResourcePage();
|
||||
@@ -0,0 +1,3 @@
|
||||
import {ResourcesPage} from '../pages/Resources.js';
|
||||
|
||||
new ResourcesPage();
|
||||
@@ -0,0 +1,3 @@
|
||||
import {ReviewQueuePage} from '../pages/ReviewQueue.js';
|
||||
|
||||
new ReviewQueuePage();
|
||||
@@ -0,0 +1,3 @@
|
||||
import {RulesPage} from '../pages/Rules.js';
|
||||
|
||||
new RulesPage();
|
||||
@@ -0,0 +1,3 @@
|
||||
import {SettingsPage} from '../pages/Settings.js';
|
||||
|
||||
new SettingsPage();
|
||||
@@ -0,0 +1,3 @@
|
||||
import {UserPage} from '../pages/User.js';
|
||||
|
||||
new UserPage();
|
||||
@@ -0,0 +1,3 @@
|
||||
import {UsersPage} from '../pages/Users.js';
|
||||
|
||||
new UsersPage();
|
||||
@@ -0,0 +1,3 @@
|
||||
import {WatchlistPage} from '../pages/Watchlist.js';
|
||||
|
||||
new WatchlistPage();
|
||||
@@ -0,0 +1,20 @@
|
||||
(function() {
|
||||
const emulateButtonClick = () => {
|
||||
let button = document.getElementById('submit-button');
|
||||
|
||||
button.classList.add('clicked');
|
||||
button.click();
|
||||
|
||||
setTimeout(() => {
|
||||
button.classList.remove('clicked');
|
||||
}, 200);
|
||||
};
|
||||
|
||||
const onEnterKeyDown = e => {
|
||||
if (e.keyCode === 13) {
|
||||
emulateButtonClick();
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('keydown', onEnterKeyDown, false);
|
||||
})();
|
||||
@@ -0,0 +1,65 @@
|
||||
import {BasePage} from './Base.js';
|
||||
import {UsageStatsGrid} from '../parts/grid/UsageStats.js?v=2';
|
||||
import {EnrichAllPopUp} from '../parts/EnrichAllPopUp.js?v=2';
|
||||
|
||||
export class ApiPage extends BasePage {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.initUi();
|
||||
}
|
||||
|
||||
initUi() {
|
||||
const onSelectChange = this.onSelectChange.bind(this);
|
||||
this.versionSelect.addEventListener('change', onSelectChange, false);
|
||||
|
||||
const onTextAreaClick = this.onTextAreaClick.bind(this);
|
||||
this.snippetTextareas.forEach(txt => txt.addEventListener('click', onTextAreaClick, false));
|
||||
|
||||
const gridParams = {
|
||||
url: '/admin/loadUsageStats',
|
||||
tableId: 'usage-stats-table',
|
||||
tileId: 'totalUsageStats',
|
||||
|
||||
isSortable: false,
|
||||
|
||||
getParams: function() {
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
new UsageStatsGrid(gridParams);
|
||||
new EnrichAllPopUp();
|
||||
}
|
||||
|
||||
onTextAreaClick(e) {
|
||||
const txt = e.target;
|
||||
const value = txt.value;
|
||||
|
||||
txt.setSelectionRange(0, value.length);
|
||||
}
|
||||
|
||||
onSelectChange(e) {
|
||||
const value = event.target.value;
|
||||
|
||||
this.snippetTextareas.forEach(txt => {
|
||||
const container = txt.closest('div');
|
||||
const isHidden = container.classList.contains('is-hidden');
|
||||
if (!isHidden) {
|
||||
container.classList.add('is-hidden');
|
||||
}
|
||||
});
|
||||
|
||||
const textarea = document.getElementById(value);
|
||||
textarea.closest('div').classList.remove('is-hidden');
|
||||
}
|
||||
|
||||
get versionSelect() {
|
||||
return document.querySelector('select[name=version]');
|
||||
}
|
||||
|
||||
get snippetTextareas() {
|
||||
return document.querySelectorAll('.code-snippet');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
import {SearchLine} from '../parts/SearchLine.js';
|
||||
import {Tooltip} from '../parts/Tooltip.js?v=2';
|
||||
|
||||
export class BasePage {
|
||||
constructor(name, single = false) {
|
||||
this.initCommonUi();
|
||||
|
||||
if (name) {
|
||||
this.name = name;
|
||||
if (single) {
|
||||
let path = (this.name !== 'user') ? this.name : 'id';
|
||||
this.id = parseInt(window.location.pathname.replace('/' + path + '/', ''), 10);
|
||||
const key = this.name + 'Id';
|
||||
this.getParams = () => {
|
||||
return {[key]: this.id};
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
initCommonUi() {
|
||||
new SearchLine();
|
||||
|
||||
document.addEventListener('keyup', e => {
|
||||
if (e.key !== '/' || e.ctrlKey || e.metaKey) return;
|
||||
if (/^(?:input|textarea|select|button)$/i.test(e.target.tagName)) return;
|
||||
|
||||
e.preventDefault();
|
||||
document.getElementById('auto-complete').focus();
|
||||
});
|
||||
|
||||
const initTooltip = this.initTooltip;
|
||||
if (initTooltip) {
|
||||
Tooltip.init();
|
||||
}
|
||||
|
||||
const closeNotificationButtons = this.closeNotificationButtons;
|
||||
if (closeNotificationButtons && closeNotificationButtons.length) {
|
||||
const onCloseNotificationButtonClick = this.onCloseNotificationButtonClick.bind(this);
|
||||
closeNotificationButtons.forEach(
|
||||
button => button.addEventListener('click', onCloseNotificationButtonClick, false)
|
||||
);
|
||||
}
|
||||
|
||||
this.procedureNotifications.forEach(notification => {
|
||||
const btn = notification.querySelector('.delete');
|
||||
if (!btn) return;
|
||||
btn.addEventListener('click', () => {notification.remove();}, false);
|
||||
});
|
||||
}
|
||||
|
||||
onCloseNotificationButtonClick() {
|
||||
const notification = event.target.closest('.notification.system');
|
||||
if (notification) {
|
||||
notification.remove();
|
||||
}
|
||||
}
|
||||
|
||||
getDevicesGridParams() {
|
||||
return {
|
||||
url: '/admin/loadDevices',
|
||||
tileId: 'totalDevices',
|
||||
tableId: 'devices-table',
|
||||
panelType: 'device',
|
||||
|
||||
isSortable: false,
|
||||
|
||||
getParams: this.getParams,
|
||||
};
|
||||
}
|
||||
|
||||
getIpsGridParams() {
|
||||
return {
|
||||
url: '/admin/loadIps',
|
||||
tileId: 'totalIps',
|
||||
tableId: 'ips-table',
|
||||
|
||||
isSortable: false,
|
||||
orderByLastseen: true,
|
||||
|
||||
getParams: this.getParams,
|
||||
};
|
||||
}
|
||||
|
||||
getEventsGridParams() {
|
||||
return {
|
||||
url: '/admin/loadEvents',
|
||||
tileId: 'totalEvents',
|
||||
tableId: 'user-events-table',
|
||||
panelType: 'event',
|
||||
|
||||
isSortable: false,
|
||||
|
||||
getParams: this.getParams,
|
||||
};
|
||||
}
|
||||
|
||||
getUsersGridParams() {
|
||||
return {
|
||||
url: '/admin/loadUsers',
|
||||
tileId: 'totalUsers',
|
||||
tableId: 'users-table',
|
||||
|
||||
isSortable: false,
|
||||
|
||||
getParams: this.getParams,
|
||||
};
|
||||
}
|
||||
|
||||
getIspsGridParams() {
|
||||
return {
|
||||
url: '/admin/loadIsps',
|
||||
tableId: 'isps-table',
|
||||
|
||||
isSortable: false,
|
||||
|
||||
getParams: this.getParams,
|
||||
};
|
||||
}
|
||||
|
||||
getMapParams() {
|
||||
return {
|
||||
getParams: this.getParams,
|
||||
tooltipString: 'event',
|
||||
tooltipField: 'total_visit',
|
||||
tileId: 'totalCountries',
|
||||
};
|
||||
}
|
||||
|
||||
getBarChartParams() {
|
||||
return {
|
||||
getParams: () => ({
|
||||
mode: this.name,
|
||||
id: this.id,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
getChartParams(datesFilter, searchFilter) {
|
||||
return {
|
||||
getParams: () => {
|
||||
const mode = this.name;
|
||||
const dateRange = datesFilter.getValue();
|
||||
const searchValue = searchFilter.getValue();
|
||||
|
||||
return {mode, dateRange, searchValue};
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
getSelfDetails() {
|
||||
return {
|
||||
getParams: this.getParams,
|
||||
};
|
||||
}
|
||||
|
||||
get initTooltip() {
|
||||
return true;
|
||||
}
|
||||
|
||||
get closeNotificationButtons() {
|
||||
return document.querySelectorAll('.notification.system:not(.is-hidden) .delete');
|
||||
}
|
||||
|
||||
get procedureNotifications() {
|
||||
return document.querySelectorAll('#success-procedure-notification, #error-procedure-notification');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
import {BasePage} from './Base.js';
|
||||
|
||||
import {DatesFilter} from '../parts/DatesFilter.js?v=2';
|
||||
import {SearchFilter} from '../parts/SearchFilter.js?v=2';
|
||||
import {EntityTypeFilter} from '../parts/choices/EntityTypeFilter.js?v=2';
|
||||
import {BlacklistGridActionButtons} from '../parts/BlacklistGridActionButtons.js?v=2';
|
||||
import {BlacklistChart} from '../parts/chart/Blacklist.js?v=2';
|
||||
import {BlacklistGrid} from '../parts/grid/Blacklist.js?v=2';
|
||||
|
||||
export class BlacklistPage extends BasePage {
|
||||
|
||||
constructor() {
|
||||
super('blacklist');
|
||||
this.tableId = 'blacklist-table';
|
||||
this.initUi();
|
||||
}
|
||||
|
||||
initUi() {
|
||||
const datesFilter = new DatesFilter();
|
||||
const searchFilter = new SearchFilter();
|
||||
|
||||
const gridParams = {
|
||||
url: '/admin/loadBlacklist',
|
||||
tileId: 'totalBlacklist',
|
||||
tableId: 'blacklist-table',
|
||||
|
||||
dateRangeGrid: true,
|
||||
|
||||
getParams: function() {
|
||||
const dateRange = datesFilter.getValue();
|
||||
const searchValue = searchFilter.getValue();
|
||||
|
||||
return {dateRange, searchValue};
|
||||
},
|
||||
};
|
||||
|
||||
if (document.getElementById('entity-type-selectors')) {
|
||||
const entityTypeFilter = new EntityTypeFilter();
|
||||
|
||||
gridParams.choicesFilterEvents = [entityTypeFilter.getEventType()];
|
||||
gridParams.getParams = function() {
|
||||
const dateRange = datesFilter.getValue();
|
||||
const searchValue = searchFilter.getValue();
|
||||
const entityTypeIds = entityTypeFilter.getValues();
|
||||
|
||||
return {dateRange, searchValue, entityTypeIds};
|
||||
};
|
||||
}
|
||||
|
||||
const chartParams = this.getChartParams(datesFilter, searchFilter);
|
||||
|
||||
new BlacklistChart(chartParams);
|
||||
new BlacklistGrid(gridParams);
|
||||
new BlacklistGridActionButtons(this.tableId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import {BasePage} from './Base.js';
|
||||
import {SequentialLoad} from '../parts/SequentialLoad.js?v=2';
|
||||
import {Map} from '../parts/Map.js?v=2';
|
||||
import {IpsGrid} from '../parts/grid/Ips.js?v=2';
|
||||
import {UsersGrid} from '../parts/grid/Users.js?v=2';
|
||||
import {EventsGrid} from '../parts/grid/Events.js?v=2';
|
||||
import {BaseBarChart} from '../parts/chart/BaseBar.js?v=2';
|
||||
import {EventPanel} from '../parts/panel/EventPanel.js?v=2';
|
||||
import {BotTiles} from '../parts/details/BotTiles.js?v=2';
|
||||
import {ReenrichmentButton} from '../parts/ReenrichmentButton.js?v=2';
|
||||
|
||||
export class BotPage extends BasePage {
|
||||
|
||||
constructor() {
|
||||
super('bot', true);
|
||||
|
||||
this.initUi();
|
||||
}
|
||||
|
||||
initUi() {
|
||||
const usersGridParams = this.getUsersGridParams();
|
||||
const eventsGridParams = this.getEventsGridParams();
|
||||
const ipsGridParams = this.getIpsGridParams();
|
||||
const mapParams = this.getMapParams();
|
||||
|
||||
const botDetailsTiles = this.getSelfDetails();
|
||||
|
||||
const chartParams = this.getBarChartParams();
|
||||
|
||||
new EventPanel();
|
||||
new ReenrichmentButton();
|
||||
|
||||
const elements = [
|
||||
[BotTiles, botDetailsTiles],
|
||||
[Map, mapParams],
|
||||
[IpsGrid, ipsGridParams],
|
||||
[UsersGrid, usersGridParams],
|
||||
[BaseBarChart, chartParams],
|
||||
[EventsGrid, eventsGridParams],
|
||||
];
|
||||
|
||||
new SequentialLoad(elements);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import {BasePage} from './Base.js';
|
||||
|
||||
import {DatesFilter} from '../parts/DatesFilter.js?v=2';
|
||||
import {SearchFilter} from '../parts/SearchFilter.js?v=2';
|
||||
import {BotsChart} from '../parts/chart/Bots.js?v=2';
|
||||
import {BotsGrid} from '../parts/grid/Bots.js?v=2';
|
||||
|
||||
export class BotsPage extends BasePage {
|
||||
|
||||
constructor() {
|
||||
super('bots');
|
||||
|
||||
this.initUi();
|
||||
}
|
||||
|
||||
initUi() {
|
||||
const datesFilter = new DatesFilter();
|
||||
const searchFilter = new SearchFilter();
|
||||
|
||||
const gridParams = {
|
||||
url: '/admin/loadBots',
|
||||
// tileId: 'totalDevices',
|
||||
tableId: 'bots-table',
|
||||
|
||||
dateRangeGrid: true,
|
||||
|
||||
getParams: function() {
|
||||
const dateRange = datesFilter.getValue();
|
||||
const searchValue = searchFilter.getValue();
|
||||
|
||||
return {dateRange, searchValue};
|
||||
},
|
||||
};
|
||||
|
||||
const chartParams = this.getChartParams(datesFilter, searchFilter);
|
||||
|
||||
new BotsChart(chartParams);
|
||||
new BotsGrid(gridParams);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import {BasePage} from './Base.js';
|
||||
|
||||
import {Map} from '../parts/Map.js?v=2';
|
||||
import {DatesFilter} from '../parts/DatesFilter.js?v=2';
|
||||
import {SearchFilter} from '../parts/SearchFilter.js?v=2';
|
||||
import {CountriesGrid} from '../parts/grid/Countries.js?v=2';
|
||||
|
||||
export class CountriesPage extends BasePage {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.initUi();
|
||||
}
|
||||
|
||||
initUi() {
|
||||
const datesFilter = new DatesFilter();
|
||||
const searchFilter = new SearchFilter();
|
||||
|
||||
const getMapParams = () => {
|
||||
const dateRange = datesFilter.getValue();
|
||||
return {dateRange};
|
||||
};
|
||||
|
||||
const gridParams = {
|
||||
url: '/admin/loadCountries',
|
||||
tileId: 'totalCountries',
|
||||
tableId: 'countries-table',
|
||||
|
||||
dateRangeGrid: true,
|
||||
calculateTotals: true,
|
||||
totals: {
|
||||
type: 'country',
|
||||
columns: ['total_visit', 'total_account', 'total_ip'],
|
||||
},
|
||||
|
||||
getParams: function() {
|
||||
const dateRange = datesFilter.getValue();
|
||||
const searchValue = searchFilter.getValue();
|
||||
|
||||
return {dateRange, searchValue};
|
||||
}
|
||||
};
|
||||
|
||||
const mapParams = {
|
||||
getParams: getMapParams,
|
||||
tooltipString: 'user',
|
||||
tooltipField: 'total_account'
|
||||
};
|
||||
|
||||
new Map(mapParams);
|
||||
new CountriesGrid(gridParams);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import {BasePage} from './Base.js';
|
||||
import {SequentialLoad} from '../parts/SequentialLoad.js?v=2';
|
||||
import {IpsGrid} from '../parts/grid/Ips.js?v=2';
|
||||
import {IspsGrid} from '../parts/grid/Isps.js?v=2';
|
||||
import {UsersGrid} from '../parts/grid/Users.js?v=2';
|
||||
import {EventsGrid} from '../parts/grid/Events.js?v=2';
|
||||
import {BaseBarChart} from '../parts/chart/BaseBar.js?v=2';
|
||||
import {StaticTiles} from '../parts/StaticTiles.js?v=2';
|
||||
import {EventPanel} from '../parts/panel/EventPanel.js?v=2';
|
||||
|
||||
export class CountryPage extends BasePage {
|
||||
|
||||
constructor() {
|
||||
super('country', true);
|
||||
|
||||
this.initUi();
|
||||
}
|
||||
|
||||
initUi() {
|
||||
const usersGridParams = this.getUsersGridParams();
|
||||
const eventsGridParams = this.getEventsGridParams();
|
||||
const ispsGridParams = this.getIspsGridParams();
|
||||
const ipsGridParams = this.getIpsGridParams();
|
||||
const chartParams = this.getBarChartParams();
|
||||
|
||||
const tilesParams = {
|
||||
elems: ['totalUsers', 'totalIps', 'totalEvents']
|
||||
};
|
||||
|
||||
new StaticTiles(tilesParams);
|
||||
new EventPanel();
|
||||
|
||||
const elements = [
|
||||
[UsersGrid, usersGridParams],
|
||||
[IpsGrid, ipsGridParams],
|
||||
[IspsGrid, ispsGridParams],
|
||||
[BaseBarChart, chartParams],
|
||||
[EventsGrid, eventsGridParams],
|
||||
];
|
||||
|
||||
new SequentialLoad(elements);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
import {BasePage} from './Base.js';
|
||||
import {SequentialLoad} from '../parts/SequentialLoad.js?v=2';
|
||||
import {DatesFilter} from '../parts/DatesFilter.js?v=2';
|
||||
import {DashboardTile} from '../parts/DashboardTile.js?v=2';
|
||||
import {TopTenGrid} from '../parts/grid/TopTen.js?v=2';
|
||||
import {
|
||||
renderClickableImportantUserWithScoreTile,
|
||||
renderClickableCountry,
|
||||
renderClickableResourceWithoutQuery,
|
||||
renderClickableIpWithCountry,
|
||||
} from '../parts/DataRenderers.js?v=2';
|
||||
|
||||
export class DashboardPage extends BasePage {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.initUi();
|
||||
}
|
||||
|
||||
initUi() {
|
||||
const datesFilter = new DatesFilter(true);
|
||||
|
||||
const getParams = () => {
|
||||
const dateRange = datesFilter.getValue();
|
||||
return {dateRange};
|
||||
};
|
||||
|
||||
const topTenUsersGridParams = {
|
||||
getParams: getParams,
|
||||
mode: 'mostActiveUsers',
|
||||
tableId: 'most-active-users-table',
|
||||
dateRangeGrid: true,
|
||||
renderItemColumn: renderClickableImportantUserWithScoreTile,
|
||||
};
|
||||
|
||||
const topTenCountriesGridParams = {
|
||||
getParams: getParams,
|
||||
mode: 'mostActiveCountries',
|
||||
tableId: 'most-active-countries-table',
|
||||
dateRangeGrid: true,
|
||||
renderItemColumn: renderClickableCountry,
|
||||
};
|
||||
|
||||
const topTenResourcesGridParams = {
|
||||
getParams: getParams,
|
||||
mode: 'mostActiveUrls',
|
||||
tableId: 'most-active-urls-table',
|
||||
dateRangeGrid: true,
|
||||
renderItemColumn: renderClickableResourceWithoutQuery,
|
||||
};
|
||||
|
||||
const topTenIpsWithMostUsersGridParams = {
|
||||
getParams: getParams,
|
||||
mode: 'ipsWithTheMostUsers',
|
||||
tableId: 'ips-with-the-most-users-table',
|
||||
dateRangeGrid: true,
|
||||
renderItemColumn: renderClickableIpWithCountry,
|
||||
};
|
||||
|
||||
const topTenUsersWithMostLoginFailGridParams = {
|
||||
getParams: getParams,
|
||||
mode: 'usersWithMostLoginFail',
|
||||
tableId: 'users-with-most-login-fail-table',
|
||||
dateRangeGrid: true,
|
||||
renderItemColumn: renderClickableImportantUserWithScoreTile,
|
||||
};
|
||||
|
||||
const topTenUsersWithMostIpsGridParams = {
|
||||
getParams: getParams,
|
||||
mode: 'usersWithMostIps',
|
||||
tableId: 'users-with-most-ips-table',
|
||||
dateRangeGrid: true,
|
||||
renderItemColumn: renderClickableImportantUserWithScoreTile,
|
||||
};
|
||||
|
||||
const elements = [
|
||||
//[DashboardTile, {getParams: getParams, mode: 'totalEvents'}],
|
||||
[DashboardTile, {getParams: getParams, mode: 'totalUsers'}],
|
||||
[DashboardTile, {getParams: getParams, mode: 'totalIps'}],
|
||||
[DashboardTile, {getParams: getParams, mode: 'totalCountries'}],
|
||||
[DashboardTile, {getParams: getParams, mode: 'totalUrls'}],
|
||||
[DashboardTile, {getParams: getParams, mode: 'totalUsersForReview'}],
|
||||
[DashboardTile, {getParams: getParams, mode: 'totalBlockedUsers'}],
|
||||
[TopTenGrid, topTenUsersGridParams],
|
||||
[TopTenGrid, topTenCountriesGridParams],
|
||||
[TopTenGrid, topTenResourcesGridParams],
|
||||
[TopTenGrid, topTenIpsWithMostUsersGridParams],
|
||||
[TopTenGrid, topTenUsersWithMostLoginFailGridParams],
|
||||
[TopTenGrid, topTenUsersWithMostIpsGridParams],
|
||||
];
|
||||
|
||||
new SequentialLoad(elements);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import {BasePage} from './Base.js';
|
||||
import {SequentialLoad} from '../parts/SequentialLoad.js?v=2';
|
||||
import {Map} from '../parts/Map.js?v=2';
|
||||
import {IpsGrid} from '../parts/grid/Ips.js?v=2';
|
||||
import {UsersGrid} from '../parts/grid/Users.js?v=2';
|
||||
import {IspsGrid} from '../parts/grid/Isps.js?v=2';
|
||||
import {EventsGrid} from '../parts/grid/Events.js?v=2';
|
||||
import {DomainsGrid} from '../parts/grid/Domains.js?v=2';
|
||||
import {BaseBarChart} from '../parts/chart/BaseBar.js?v=2';
|
||||
import {EventPanel} from '../parts/panel/EventPanel.js?v=2';
|
||||
import {DomainTiles} from '../parts/details/DomainTiles.js?v=2';
|
||||
import {ReenrichmentButton} from '../parts/ReenrichmentButton.js?v=2';
|
||||
|
||||
export class DomainPage extends BasePage {
|
||||
|
||||
constructor() {
|
||||
super('domain', true);
|
||||
|
||||
this.initUi();
|
||||
}
|
||||
|
||||
initUi() {
|
||||
const usersGridParams = this.getUsersGridParams();
|
||||
const eventsGridParams = this.getEventsGridParams();
|
||||
const ipsGridParams = this.getIpsGridParams();
|
||||
const ispsGridParams = this.getIspsGridParams();
|
||||
const mapParams = this.getMapParams();
|
||||
const domainDetailsTiles = this.getSelfDetails();
|
||||
const chartParams = this.getBarChartParams();
|
||||
|
||||
const domainsGridParams = {
|
||||
url: '/admin/loadDomains',
|
||||
tileId: 'totalDomains',
|
||||
tableId: 'domains-table',
|
||||
|
||||
isSortable: false,
|
||||
|
||||
getParams: this.getParams,
|
||||
};
|
||||
|
||||
new EventPanel();
|
||||
new ReenrichmentButton();
|
||||
|
||||
const elements = [
|
||||
[DomainTiles, domainDetailsTiles],
|
||||
[UsersGrid, usersGridParams],
|
||||
[DomainsGrid, domainsGridParams],
|
||||
[Map, mapParams],
|
||||
[IpsGrid, ipsGridParams],
|
||||
[IspsGrid, ispsGridParams],
|
||||
[BaseBarChart, chartParams],
|
||||
[EventsGrid, eventsGridParams],
|
||||
];
|
||||
|
||||
new SequentialLoad(elements);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import {BasePage} from './Base.js';
|
||||
|
||||
import {DatesFilter} from '../parts/DatesFilter.js?v=2';
|
||||
import {SearchFilter} from '../parts/SearchFilter.js?v=2';
|
||||
import {DomainsChart} from '../parts/chart/Domains.js?v=2';
|
||||
import {DomainsGrid} from '../parts/grid/Domains.js?v=2';
|
||||
|
||||
export class DomainsPage extends BasePage {
|
||||
|
||||
constructor() {
|
||||
super('domains');
|
||||
|
||||
this.initUi();
|
||||
}
|
||||
|
||||
initUi() {
|
||||
const datesFilter = new DatesFilter();
|
||||
const searchFilter = new SearchFilter();
|
||||
|
||||
const gridParams = {
|
||||
url: '/admin/loadDomains',
|
||||
tileId: 'totalDomains',
|
||||
tableId: 'domains-table',
|
||||
|
||||
dateRangeGrid: true,
|
||||
calculateTotals: true,
|
||||
totals: {
|
||||
type: 'domain',
|
||||
columns: ['total_account'],
|
||||
},
|
||||
|
||||
getParams: function() {
|
||||
const dateRange = datesFilter.getValue();
|
||||
const searchValue = searchFilter.getValue();
|
||||
|
||||
return {dateRange, searchValue};
|
||||
}
|
||||
};
|
||||
|
||||
const chartParams = this.getChartParams(datesFilter, searchFilter);
|
||||
|
||||
new DomainsChart(chartParams);
|
||||
new DomainsGrid(gridParams);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import {BasePage} from './Base.js';
|
||||
|
||||
import {EventsChart} from '../parts/chart/Events.js?v=2';
|
||||
import {DatesFilter} from '../parts/DatesFilter.js?v=2';
|
||||
import {SearchFilter} from '../parts/SearchFilter.js?v=2';
|
||||
import {EventTypeFilter} from '../parts/choices/EventTypeFilter.js?v=2';
|
||||
import {DeviceTypeFilter} from '../parts/choices/DeviceTypeFilter.js?v=2';
|
||||
import {RulesFilter} from '../parts/choices/RulesFilter.js?v=2';
|
||||
import {EventPanel} from '../parts/panel/EventPanel.js?v=2';
|
||||
import {EventsGrid} from '../parts/grid/Events.js?v=2';
|
||||
|
||||
export class EventsPage extends BasePage {
|
||||
|
||||
constructor() {
|
||||
super('events');
|
||||
|
||||
this.initUi();
|
||||
}
|
||||
|
||||
initUi() {
|
||||
const datesFilter = new DatesFilter();
|
||||
const searchFilter = new SearchFilter();
|
||||
const eventTypeFilter = new EventTypeFilter();
|
||||
const deviceTypeFilter = new DeviceTypeFilter();
|
||||
const rulesFilter = new RulesFilter();
|
||||
|
||||
const chartParams = this.getChartParams(datesFilter, searchFilter);
|
||||
|
||||
const gridParams = {
|
||||
url: '/admin/loadEvents',
|
||||
tileId: 'totalEvents',
|
||||
tableId: 'user-events-table',
|
||||
panelType: 'event',
|
||||
dateRangeGrid: true,
|
||||
|
||||
sessionGroup: true,
|
||||
singleUser: false,
|
||||
isSortable: true,
|
||||
|
||||
choicesFilterEvents: [
|
||||
eventTypeFilter.getEventType(),
|
||||
rulesFilter.getEventType(),
|
||||
deviceTypeFilter.getEventType(),
|
||||
],
|
||||
|
||||
getParams: function() {
|
||||
const dateRange = datesFilter.getValue();
|
||||
const searchValue = searchFilter.getValue();
|
||||
const eventTypeIds = eventTypeFilter.getValues();
|
||||
const ruleUids = rulesFilter.getValues();
|
||||
const deviceTypes = deviceTypeFilter.getValues();
|
||||
|
||||
return {dateRange, searchValue, eventTypeIds, ruleUids, deviceTypes};
|
||||
}
|
||||
};
|
||||
|
||||
new EventPanel();
|
||||
new EventsChart(chartParams);
|
||||
new EventsGrid(gridParams);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import {BasePage} from './Base.js';
|
||||
import {SequentialLoad} from '../parts/SequentialLoad.js?v=2';
|
||||
import {UsersGrid} from '../parts/grid/Users.js?v=2';
|
||||
import {EventsGrid} from '../parts/grid/Events.js?v=2';
|
||||
import {DevicesGrid} from '../parts/grid/Devices.js?v=2';
|
||||
import {BaseBarChart} from '../parts/chart/BaseBar.js?v=2';
|
||||
import {EventPanel} from '../parts/panel/EventPanel.js?v=2';
|
||||
import {DevicePanel} from '../parts/panel/DevicePanel.js?v=2';
|
||||
import {IpTiles} from '../parts/details/IpTiles.js?v=2';
|
||||
import {ReenrichmentButton} from '../parts/ReenrichmentButton.js?v=2';
|
||||
|
||||
export class IpPage extends BasePage {
|
||||
|
||||
constructor() {
|
||||
super('ip', true);
|
||||
|
||||
this.initUi();
|
||||
}
|
||||
|
||||
initUi() {
|
||||
const usersGridParams = this.getUsersGridParams();
|
||||
const devicesGridParams = this.getDevicesGridParams();
|
||||
const eventsGridParams = this.getEventsGridParams();
|
||||
const ipDetailsTiles = this.getSelfDetails();
|
||||
const chartParams = this.getBarChartParams();
|
||||
|
||||
new EventPanel();
|
||||
new DevicePanel();
|
||||
new ReenrichmentButton();
|
||||
|
||||
const elements = [
|
||||
[IpTiles, ipDetailsTiles],
|
||||
[UsersGrid, usersGridParams],
|
||||
[DevicesGrid, devicesGridParams],
|
||||
[BaseBarChart, chartParams],
|
||||
[EventsGrid, eventsGridParams],
|
||||
];
|
||||
|
||||
new SequentialLoad(elements);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import {BasePage} from './Base.js';
|
||||
|
||||
import {DatesFilter} from '../parts/DatesFilter.js?v=2';
|
||||
import {SearchFilter} from '../parts/SearchFilter.js?v=2';
|
||||
import {IpTypeFilter} from '../parts/choices/IpTypeFilter.js?v=2';
|
||||
import {IpsChart} from '../parts/chart/Ips.js?v=2';
|
||||
import {IpsGrid} from '../parts/grid/Ips.js?v=2';
|
||||
|
||||
export class IpsPage extends BasePage {
|
||||
|
||||
constructor() {
|
||||
super('ips');
|
||||
|
||||
this.initUi();
|
||||
}
|
||||
|
||||
initUi() {
|
||||
const datesFilter = new DatesFilter();
|
||||
const searchFilter = new SearchFilter();
|
||||
const ipTypeFilter = new IpTypeFilter();
|
||||
|
||||
const gridParams = {
|
||||
url: '/admin/loadIps',
|
||||
tileId: 'totalIps',
|
||||
tableId: 'ips-table',
|
||||
|
||||
dateRangeGrid: true,
|
||||
calculateTotals: true,
|
||||
totals: {
|
||||
type: 'ip',
|
||||
columns: ['total_visit'],
|
||||
},
|
||||
|
||||
isSortable: true,
|
||||
orderByLastseen: false,
|
||||
|
||||
choicesFilterEvents: [ipTypeFilter.getEventType()],
|
||||
|
||||
getParams: function() {
|
||||
const dateRange = datesFilter.getValue();
|
||||
const searchValue = searchFilter.getValue();
|
||||
const ipTypeIds = ipTypeFilter.getValues();
|
||||
|
||||
return {dateRange, searchValue, ipTypeIds};
|
||||
}
|
||||
};
|
||||
|
||||
const chartParams = this.getChartParams(datesFilter, searchFilter);
|
||||
|
||||
new IpsChart(chartParams);
|
||||
new IpsGrid(gridParams);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import {BasePage} from './Base.js';
|
||||
import {SequentialLoad} from '../parts/SequentialLoad.js?v=2';
|
||||
import {Map} from '../parts/Map.js?v=2';
|
||||
import {IpsGrid} from '../parts/grid/Ips.js?v=2';
|
||||
import {UsersGrid} from '../parts/grid/Users.js?v=2';
|
||||
import {EventsGrid} from '../parts/grid/Events.js?v=2';
|
||||
import {BaseBarChart} from '../parts/chart/BaseBar.js?v=2';
|
||||
import {EventPanel} from '../parts/panel/EventPanel.js?v=2';
|
||||
import {IspTiles} from '../parts/details/IspTiles.js?v=2';
|
||||
|
||||
export class IspPage extends BasePage {
|
||||
|
||||
constructor() {
|
||||
super('isp', true);
|
||||
|
||||
this.initUi();
|
||||
}
|
||||
|
||||
initUi() {
|
||||
const ispDetailsTiles = this.getSelfDetails();
|
||||
const usersGridParams = this.getUsersGridParams();
|
||||
const eventsGridParams = this.getEventsGridParams();
|
||||
const ipsGridParams = this.getIpsGridParams();
|
||||
const mapParams = this.getMapParams();
|
||||
const chartParams = this.getBarChartParams();
|
||||
|
||||
new EventPanel();
|
||||
|
||||
const elements = [
|
||||
[IspTiles, ispDetailsTiles],
|
||||
[UsersGrid, usersGridParams],
|
||||
[Map, mapParams],
|
||||
[IpsGrid, ipsGridParams],
|
||||
[BaseBarChart, chartParams],
|
||||
[EventsGrid, eventsGridParams],
|
||||
];
|
||||
|
||||
new SequentialLoad(elements);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import {BasePage} from './Base.js';
|
||||
|
||||
import {DatesFilter} from '../parts/DatesFilter.js?v=2';
|
||||
import {SearchFilter} from '../parts/SearchFilter.js?v=2';
|
||||
import {IspsChart} from '../parts/chart/Isps.js?v=2';
|
||||
import {IspsGrid} from '../parts/grid/Isps.js?v=2';
|
||||
|
||||
export class IspsPage extends BasePage {
|
||||
|
||||
constructor() {
|
||||
super('isps');
|
||||
|
||||
this.initUi();
|
||||
}
|
||||
|
||||
initUi() {
|
||||
const datesFilter = new DatesFilter();
|
||||
const searchFilter = new SearchFilter();
|
||||
|
||||
const gridParams = {
|
||||
url: '/admin/loadIsps',
|
||||
tileId: 'totalIsps',
|
||||
tableId: 'isps-table',
|
||||
|
||||
dateRangeGrid: true,
|
||||
calculateTotals: true,
|
||||
totals: {
|
||||
type: 'isp',
|
||||
columns: ['total_visit', 'total_account', 'total_ip'],
|
||||
},
|
||||
|
||||
getParams: function() {
|
||||
const dateRange = datesFilter.getValue();
|
||||
const searchValue = searchFilter.getValue();
|
||||
|
||||
return {dateRange, searchValue};
|
||||
}
|
||||
};
|
||||
|
||||
const chartParams = this.getChartParams(datesFilter, searchFilter);
|
||||
|
||||
new IspsChart(chartParams);
|
||||
new IspsGrid(gridParams);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import {BasePage} from './Base.js';
|
||||
|
||||
import {DatesFilter} from '../parts/DatesFilter.js?v=2';
|
||||
import {SearchFilter} from '../parts/SearchFilter.js?v=2';
|
||||
import {LogbookPanel} from '../parts/panel/LogbookPanel.js?v=2';
|
||||
import {LogbookGrid} from '../parts/grid/Logbook.js?v=2';
|
||||
import {LogbookChart} from '../parts/chart/Logbook.js?v=2';
|
||||
|
||||
export class LogbookPage extends BasePage {
|
||||
|
||||
constructor() {
|
||||
super('logbook');
|
||||
|
||||
this.initUi();
|
||||
}
|
||||
|
||||
initUi() {
|
||||
const datesFilter = new DatesFilter();
|
||||
const searchFilter = new SearchFilter();
|
||||
|
||||
const chartParams = this.getChartParams(datesFilter, searchFilter);
|
||||
|
||||
const gridParams = {
|
||||
url: '/admin/loadLogbook',
|
||||
tileId: 'totalLogbook',
|
||||
tableId: 'logbook-table',
|
||||
panelType: 'logbook',
|
||||
dateRangeGrid: true,
|
||||
|
||||
sessionGroup: false,
|
||||
singleUser: false,
|
||||
isSortable: true,
|
||||
|
||||
getParams: function() {
|
||||
const dateRange = datesFilter.getValue();
|
||||
const searchValue = searchFilter.getValue();
|
||||
|
||||
return {dateRange, searchValue};
|
||||
}
|
||||
};
|
||||
|
||||
new LogbookChart(chartParams);
|
||||
new LogbookPanel();
|
||||
new LogbookGrid(gridParams);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import {BasePage} from './Base.js';
|
||||
|
||||
import {ManualCheckItems} from '../parts/ManualCheckItems.js?v=2';
|
||||
|
||||
export class ManualCheckPage extends BasePage {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.initUi();
|
||||
}
|
||||
|
||||
initUi() {
|
||||
new ManualCheckItems();
|
||||
|
||||
const onTableLinkClick = e => {
|
||||
e.preventDefault();
|
||||
|
||||
const f = e.target.closest('form');
|
||||
f.submit();
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
const historyTableLinks = document.querySelectorAll('[data-item-id="manual-check-history-item"]');
|
||||
historyTableLinks.forEach(link => link.addEventListener('click', onTableLinkClick, false));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import {BasePage} from './Base.js';
|
||||
import {SequentialLoad} from '../parts/SequentialLoad.js?v=2';
|
||||
import {Map} from '../parts/Map.js?v=2';
|
||||
import {IpsGrid} from '../parts/grid/Ips.js';
|
||||
import {IspsGrid} from '../parts/grid/Isps.js?v=2';
|
||||
import {UsersGrid} from '../parts/grid/Users.js?v=2';
|
||||
import {EventsGrid} from '../parts/grid/Events.js?v=2';
|
||||
import {DevicesGrid} from '../parts/grid/Devices.js?v=2';
|
||||
import {BaseBarChart} from '../parts/chart/BaseBar.js?v=2';
|
||||
import {StaticTiles} from '../parts/StaticTiles.js?v=2';
|
||||
import {EventPanel} from '../parts/panel/EventPanel.js?v=2';
|
||||
import {DevicePanel} from '../parts/panel/DevicePanel.js?v=2';
|
||||
|
||||
export class ResourcePage extends BasePage {
|
||||
|
||||
constructor() {
|
||||
super('resource', true);
|
||||
|
||||
this.initUi();
|
||||
}
|
||||
|
||||
initUi() {
|
||||
const devicesGridParams = this.getDevicesGridParams();
|
||||
const eventsGridParams = this.getEventsGridParams();
|
||||
const ipsGridParams = this.getIpsGridParams();
|
||||
const usersGridParams = this.getUsersGridParams();
|
||||
const ispsGridParams = this.getIspsGridParams();
|
||||
const mapParams = this.getMapParams();
|
||||
const chartParams = this.getBarChartParams();
|
||||
|
||||
const tilesParams = {
|
||||
elems: ['totalUsers', 'totalCountries', 'totalIps', 'totalEvents']
|
||||
};
|
||||
|
||||
new StaticTiles(tilesParams);
|
||||
new EventPanel();
|
||||
new DevicePanel();
|
||||
|
||||
const elements = [
|
||||
[UsersGrid, usersGridParams],
|
||||
[Map, mapParams],
|
||||
[IpsGrid, ipsGridParams],
|
||||
[IspsGrid, ispsGridParams],
|
||||
[DevicesGrid, devicesGridParams],
|
||||
[BaseBarChart, chartParams],
|
||||
[EventsGrid, eventsGridParams],
|
||||
];
|
||||
|
||||
new SequentialLoad(elements);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import {BasePage} from './Base.js';
|
||||
|
||||
import {DatesFilter} from '../parts/DatesFilter.js?v=2';
|
||||
import {SearchFilter} from '../parts/SearchFilter.js?v=2';
|
||||
import {ResourcesChart} from '../parts/chart/Resources.js?v=2';
|
||||
import {ResourcesGrid} from '../parts/grid/Resources.js?v=2';
|
||||
|
||||
export class ResourcesPage extends BasePage {
|
||||
|
||||
constructor() {
|
||||
super('resources');
|
||||
|
||||
this.initUi();
|
||||
}
|
||||
|
||||
initUi() {
|
||||
const datesFilter = new DatesFilter();
|
||||
const searchFilter = new SearchFilter();
|
||||
|
||||
const gridParams = {
|
||||
url: '/admin/loadResources',
|
||||
tileId: 'totalResources',
|
||||
tableId: 'resources-table',
|
||||
|
||||
dateRangeGrid: true,
|
||||
calculateTotals: true,
|
||||
totals: {
|
||||
type: 'resource',
|
||||
columns: ['total_visit', 'total_account', 'total_ip', 'total_country'],
|
||||
},
|
||||
|
||||
getParams: function() {
|
||||
const dateRange = datesFilter.getValue();
|
||||
const searchValue = searchFilter.getValue();
|
||||
|
||||
return {dateRange, searchValue};
|
||||
}
|
||||
};
|
||||
|
||||
const chartParams = this.getChartParams(datesFilter, searchFilter);
|
||||
|
||||
new ResourcesChart(chartParams);
|
||||
new ResourcesGrid(gridParams);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import {BasePage} from './Base.js';
|
||||
|
||||
import {DatesFilter} from '../parts/DatesFilter.js?v=2';
|
||||
import {SearchFilter} from '../parts/SearchFilter.js?v=2';
|
||||
import {RulesFilter} from '../parts/choices/RulesFilter.js?v=2';
|
||||
import {UserGridActionButtons} from '../parts/UserGridActionButtons.js?v=2';
|
||||
import {ReviewQueueGrid} from '../parts/grid/ReviewQueue.js?v=2';
|
||||
import {ReviewQueueChart} from '../parts/chart/ReviewQueue.js?v=2';
|
||||
|
||||
export class ReviewQueuePage extends BasePage {
|
||||
|
||||
constructor() {
|
||||
super('review-queue');
|
||||
this.tableId = 'review-queue-table';
|
||||
this.initUi();
|
||||
}
|
||||
|
||||
initUi() {
|
||||
const datesFilter = new DatesFilter();
|
||||
const searchFilter = new SearchFilter();
|
||||
const rulesFilter = new RulesFilter();
|
||||
|
||||
const chartParams = this.getChartParams(datesFilter, searchFilter);
|
||||
|
||||
const gridParams = {
|
||||
url: '/admin/loadReviewQueue',
|
||||
tileId: 'totalUsers',
|
||||
tableId: 'review-queue-table',
|
||||
dateRangeGrid: true,
|
||||
|
||||
choicesFilterEvents: [rulesFilter.getEventType()],
|
||||
|
||||
getParams: function() {
|
||||
const dateRange = datesFilter.getValue();
|
||||
const searchValue = searchFilter.getValue();
|
||||
const ruleUids = rulesFilter.getValues();
|
||||
|
||||
return {dateRange, searchValue, ruleUids};
|
||||
}
|
||||
};
|
||||
|
||||
new ReviewQueueChart(chartParams);
|
||||
new ReviewQueueGrid(gridParams);
|
||||
new UserGridActionButtons(this.tableId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
import {BasePage} from './Base.js';
|
||||
import {Tooltip} from '../parts/Tooltip.js?v=2';
|
||||
import {handleAjaxError} from '../parts/utils/ErrorHandler.js?v=2';
|
||||
import {getRuleClass} from '../parts/utils/String.js?v=2';
|
||||
import {ThresholdsForm} from '../parts/ThresholdsForm.js?v=2';
|
||||
import {
|
||||
renderClickableUser,
|
||||
renderProportion,
|
||||
renderRulePlayResult,
|
||||
} from '../parts/DataRenderers.js?v=2';
|
||||
|
||||
export class RulesPage extends BasePage {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.initUi();
|
||||
}
|
||||
|
||||
initUi() {
|
||||
new ThresholdsForm();
|
||||
|
||||
const searchTable = this.searchTable.bind(this);
|
||||
this.searchInput.addEventListener('keyup', searchTable, false);
|
||||
|
||||
const onPlayButtonClick = this.onPlayButtonClick.bind(this);
|
||||
this.playButtons.forEach(button => button.addEventListener('click', onPlayButtonClick, false));
|
||||
|
||||
const onSaveButtonClick = this.onSaveButtonClick.bind(this);
|
||||
this.saveButtons.forEach(button => button.addEventListener('click', onSaveButtonClick, false));
|
||||
|
||||
const onSelectChange = this.onSelectChange.bind(this);
|
||||
this.selects.forEach(select => select.addEventListener('change', onSelectChange, false));
|
||||
}
|
||||
|
||||
onPlayButtonClick(e) {
|
||||
e.preventDefault();
|
||||
|
||||
this.updateDisabled(true);
|
||||
|
||||
const currentPlayButton = e.target.closest('button');
|
||||
currentPlayButton.classList.add('is-loading');
|
||||
|
||||
const ruleUid = currentPlayButton.dataset.ruleUid;
|
||||
const token = document.head.querySelector('[name=\'csrf-token\'][content]').content;
|
||||
const params = {ruleUid: currentPlayButton.dataset.ruleUid, token: token};
|
||||
|
||||
$.ajax({
|
||||
url: '/admin/checkRule',
|
||||
type: 'get',
|
||||
context: {currentPlayButton: currentPlayButton, ruleUid: ruleUid},
|
||||
data: params,
|
||||
success: this.onCheckRuleLoad, // without binding to keep simultaneous calls scopes separate
|
||||
error: handleAjaxError,
|
||||
complete: this.updateDisabled.bind(this, false)
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
onCheckRuleLoad(data, status) {
|
||||
if ('success' !== status || 0 === data.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.currentPlayButton.classList.remove('is-loading');
|
||||
|
||||
let row = document.querySelector(`tr[data-rule-uid="${this.ruleUid}"]`);
|
||||
let nextRow = row.nextElementSibling;
|
||||
if (!nextRow || nextRow.dataset.ruleUid) {
|
||||
const ex = document.createElement('tr');
|
||||
const td = document.createElement('td');
|
||||
td.colSpan = 6;
|
||||
|
||||
ex.replaceChildren(td);
|
||||
|
||||
nextRow = row.parentNode.insertBefore(ex, row.nextSibling);
|
||||
}
|
||||
|
||||
nextRow.querySelector('td').replaceChildren(renderRulePlayResult(data.users, data.count, this.ruleUid));
|
||||
|
||||
// 3 is index of proportion column
|
||||
row.children[3].replaceChildren(renderProportion(data.proportion, data.proportion_updated_at));
|
||||
|
||||
Tooltip.addTooltipsToRulesProportion();
|
||||
}
|
||||
|
||||
updateDisabled(disabled) {
|
||||
this.playButtons.forEach(button => button.disabled = disabled);
|
||||
}
|
||||
|
||||
onSelectChange(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const field = e.target;
|
||||
const parentRow = field.closest('tr');
|
||||
const saveButton = parentRow.querySelector('button[type="button"]');
|
||||
|
||||
const value = field.value;
|
||||
const cls = getRuleClass(parseInt(value, 10));
|
||||
|
||||
const newClassName = `ruleHighlight ${cls}`;
|
||||
parentRow.querySelector('h3').className = newClassName;
|
||||
|
||||
if (field.dataset.initialValue == value) {
|
||||
parentRow.classList.remove('input-field-changed');
|
||||
saveButton.classList.add('is-hidden');
|
||||
} else {
|
||||
parentRow.classList.add('input-field-changed');
|
||||
saveButton.classList.remove('is-hidden');
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
onSaveButtonClick(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const currentSaveButton = e.target.closest('button');
|
||||
currentSaveButton.classList.add('is-loading');
|
||||
|
||||
const select = currentSaveButton.closest('tr').querySelector('select');
|
||||
const token = document.head.querySelector('[name=\'csrf-token\'][content]').content;
|
||||
|
||||
const params = {
|
||||
rule: select.name,
|
||||
value: select.value,
|
||||
token: token,
|
||||
};
|
||||
|
||||
$.ajax({
|
||||
url: '/admin/saveRule',
|
||||
type: 'post',
|
||||
data: params,
|
||||
context: {currentSaveButton: currentSaveButton},
|
||||
error: handleAjaxError,
|
||||
success: this.onSaveLoaded, // without binding to keep simultaneous calls scopes separate
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
onSaveLoaded(data, status) {
|
||||
if ('success' !== status) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.currentSaveButton.classList.remove('is-loading');
|
||||
|
||||
const parentRow = this.currentSaveButton.closest('tr');
|
||||
const saveButton = parentRow.querySelector('button[type="button"]');
|
||||
|
||||
parentRow.classList.remove('input-field-changed');
|
||||
saveButton.classList.add('is-hidden');
|
||||
}
|
||||
|
||||
searchTable() {
|
||||
let td, i, txtValue;
|
||||
const input = document.getElementById('search');
|
||||
const filter = input.value.toLowerCase();
|
||||
const table = document.getElementById('rules-table');
|
||||
const tr = table.getElementsByTagName('tr');
|
||||
|
||||
// i = 1 because search must skip first line with column names
|
||||
for (i = 1; i < tr.length; i++) {
|
||||
td = tr[i].getElementsByTagName('td');
|
||||
let found = false;
|
||||
|
||||
for (let j = 0; j < Math.min(td.length, 3); j++) {
|
||||
if (td[j]) {
|
||||
txtValue = td[j].textContent || td[j].innerText;
|
||||
if (txtValue.toLowerCase().indexOf(filter) > -1) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
tr[i].style.display = found ? '' : 'none';
|
||||
}
|
||||
}
|
||||
|
||||
get selects() {
|
||||
return document.querySelectorAll('td select');
|
||||
}
|
||||
|
||||
get saveButtons() {
|
||||
return document.querySelectorAll('td button[type="button"]');
|
||||
}
|
||||
|
||||
get playButtons() {
|
||||
return document.querySelectorAll('td button[data-rule-uid]');
|
||||
}
|
||||
|
||||
get searchInput() {
|
||||
return document.getElementById('search');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import {BasePage} from './Base.js';
|
||||
import {DeleteAccountPopUp} from '../parts/DeleteAccountPopUp.js?v=2';
|
||||
|
||||
export class SettingsPage extends BasePage {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.initUi();
|
||||
}
|
||||
|
||||
initUi() {
|
||||
new DeleteAccountPopUp();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
import {BasePage} from './Base.js';
|
||||
import {SequentialLoad} from '../parts/SequentialLoad.js?v=2';
|
||||
import {Map} from '../parts/Map.js?v=2';
|
||||
import {EmailsGrid} from '../parts/grid/Emails.js?v=2';
|
||||
import {IpsGrid} from '../parts/grid/Ips.js?v=2';
|
||||
import {EventsGrid} from '../parts/grid/Events.js?v=2';
|
||||
import {DevicesGrid} from '../parts/grid/Devices.js?v=2';
|
||||
import {BaseBarChart} from '../parts/chart/BaseBar.js?v=2';
|
||||
import {BaseSparklineChart} from '../parts/chart/BaseSparkline.js?v=2';
|
||||
import {UserTiles} from '../parts/details/UserTiles.js?v=2';
|
||||
import {EventPanel} from '../parts/panel/EventPanel.js?v=2';
|
||||
import {SingleReviewButton} from '../parts/SingleReviewButton.js?v=2';
|
||||
import {ScoreDetails} from '../parts/ScoreDetails.js?v=2';
|
||||
import {PhonesGrid} from '../parts/grid/Phones.js?v=2';
|
||||
import {FieldAuditTrailGrid} from '../parts/grid/payloads/FieldAuditTrail.js?v=2';
|
||||
import {IspsGrid} from '../parts/grid/Isps.js?v=2';
|
||||
|
||||
import {EmailPanel} from '../parts/panel/EmailPanel.js?v=2';
|
||||
import {PhonePanel} from '../parts/panel/PhonePanel.js?v=2';
|
||||
import {DevicePanel} from '../parts/panel/DevicePanel.js?v=2';
|
||||
import {ReenrichmentButton} from '../parts/ReenrichmentButton.js?v=2';
|
||||
|
||||
export class UserPage extends BasePage {
|
||||
|
||||
constructor() {
|
||||
super('user', true);
|
||||
|
||||
this.initUi();
|
||||
}
|
||||
|
||||
initUi() {
|
||||
const userDetailsTiles = this.getSelfDetails();
|
||||
const devicesGridParams = this.getDevicesGridParams();
|
||||
const ipsGridParams = this.getIpsGridParams();
|
||||
const ispsGridParams = this.getIspsGridParams();
|
||||
const eventsGridParams = this.getEventsGridParams();
|
||||
eventsGridParams.sessionGroup = true;
|
||||
eventsGridParams.singleUser = true;
|
||||
|
||||
const mapParams = this.getMapParams();
|
||||
const chartParams = this.getBarChartParams();
|
||||
const statsChartParams = {
|
||||
getParams: () => ({
|
||||
mode: 'stats',
|
||||
id: this.id,
|
||||
}),
|
||||
};
|
||||
|
||||
ipsGridParams.tileId = null;
|
||||
eventsGridParams.tileId = null;
|
||||
devicesGridParams.tileId = null;
|
||||
mapParams.tileId = null;
|
||||
|
||||
const emailsGridParams = {
|
||||
url: '/admin/loadEmails',
|
||||
tableId: 'emails-table',
|
||||
panelType: 'email',
|
||||
|
||||
isSortable: false,
|
||||
|
||||
getParams: this.getParams,
|
||||
};
|
||||
|
||||
const phonesGridParams = {
|
||||
url: '/admin/loadPhones',
|
||||
tableId: 'phones-table',
|
||||
panelType: 'phone',
|
||||
|
||||
isSortable: false,
|
||||
|
||||
getParams: this.getParams,
|
||||
};
|
||||
|
||||
const fieldAuditTrailGridParams = {
|
||||
url: '/admin/loadFieldAuditTrail',
|
||||
tableId: 'field-audit-trail-table',
|
||||
|
||||
isSortable: false,
|
||||
|
||||
getParams: this.getParams,
|
||||
};
|
||||
|
||||
const userScoreDetails = {
|
||||
userId: this.id,
|
||||
};
|
||||
|
||||
new ScoreDetails(userScoreDetails);
|
||||
|
||||
new SingleReviewButton(this.id);
|
||||
new EventPanel();
|
||||
new DevicePanel();
|
||||
new ReenrichmentButton();
|
||||
|
||||
const isEmailPhone = !!document.getElementById('email-card');
|
||||
|
||||
if (isEmailPhone) {
|
||||
new EmailPanel();
|
||||
new PhonePanel();
|
||||
}
|
||||
|
||||
const elements = [
|
||||
[UserTiles, userDetailsTiles],
|
||||
[BaseSparklineChart, statsChartParams],
|
||||
[Map, mapParams],
|
||||
[IpsGrid, ipsGridParams],
|
||||
[IspsGrid, ispsGridParams],
|
||||
[DevicesGrid, devicesGridParams],
|
||||
];
|
||||
|
||||
if (isEmailPhone) {
|
||||
elements.push([EmailsGrid, emailsGridParams]);
|
||||
elements.push([PhonesGrid, phonesGridParams]);
|
||||
}
|
||||
|
||||
elements.push([FieldAuditTrailGrid, fieldAuditTrailGridParams]);
|
||||
elements.push([BaseBarChart, chartParams]);
|
||||
elements.push([EventsGrid, eventsGridParams]);
|
||||
|
||||
new SequentialLoad(elements);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import {BasePage} from './Base.js';
|
||||
|
||||
import {DatesFilter} from '../parts/DatesFilter.js?v=2';
|
||||
import {SearchFilter} from '../parts/SearchFilter.js?v=2';
|
||||
import {RulesFilter} from '../parts/choices/RulesFilter.js?v=2';
|
||||
import {ScoresRangeFilter} from '../parts/choices/ScoresRangeFilter.js?v=2';
|
||||
import {UsersGrid} from '../parts/grid/Users.js?v=2';
|
||||
import {UsersChart} from '../parts/chart/Users.js?v=2';
|
||||
|
||||
export class UsersPage extends BasePage {
|
||||
|
||||
constructor() {
|
||||
super('users');
|
||||
|
||||
this.initUi();
|
||||
}
|
||||
|
||||
initUi() {
|
||||
const datesFilter = new DatesFilter();
|
||||
const searchFilter = new SearchFilter();
|
||||
const rulesFilter = new RulesFilter();
|
||||
const scoresRangeFilter = new ScoresRangeFilter();
|
||||
|
||||
const chartParams = this.getChartParams(datesFilter, searchFilter);
|
||||
|
||||
const gridParams = {
|
||||
url: '/admin/loadUsers',
|
||||
tileId: 'totalUsers',
|
||||
tableId: 'users-table',
|
||||
|
||||
dateRangeGrid: true,
|
||||
|
||||
choicesFilterEvents: [rulesFilter.getEventType(), scoresRangeFilter.getEventType()],
|
||||
|
||||
getParams: function() {
|
||||
const dateRange = datesFilter.getValue();
|
||||
const searchValue = searchFilter.getValue();
|
||||
const ruleUids = rulesFilter.getValues();
|
||||
const scoresRange = scoresRangeFilter.getValues();
|
||||
|
||||
return {dateRange, searchValue, ruleUids, scoresRange};
|
||||
},
|
||||
};
|
||||
|
||||
new UsersGrid(gridParams);
|
||||
new UsersChart(chartParams);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import {BasePage} from './Base.js';
|
||||
|
||||
import {DatesFilter} from '../parts/DatesFilter.js?v=2';
|
||||
import {SearchFilter} from '../parts/SearchFilter.js?v=2';
|
||||
import {EventPanel} from '../parts/panel/EventPanel.js?v=2';
|
||||
import {WatchlistTags} from '../parts/WatchlistTags.js?v=2';
|
||||
import {EventsGrid} from '../parts/grid/Events.js?v=2';
|
||||
|
||||
export class WatchlistPage extends BasePage {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.initUi();
|
||||
}
|
||||
|
||||
initUi() {
|
||||
const datesFilter = new DatesFilter();
|
||||
const searchFilter = new SearchFilter();
|
||||
|
||||
const gridParams = {
|
||||
url: '/admin/loadEvents?watchlist=true',
|
||||
tileId: 'totalEvents',
|
||||
tableId: 'user-events-table',
|
||||
panelType: 'event',
|
||||
|
||||
dateRangeGrid: true,
|
||||
isSortable: false,
|
||||
|
||||
getParams: function() {
|
||||
const dateRange = datesFilter.getValue();
|
||||
const searchValue = searchFilter.getValue();
|
||||
|
||||
return {dateRange, searchValue};
|
||||
},
|
||||
};
|
||||
|
||||
new EventPanel();
|
||||
new WatchlistTags();
|
||||
new EventsGrid(gridParams);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
import {handleAjaxError} from './utils/ErrorHandler.js?v=2';
|
||||
|
||||
export class BlacklistGridActionButtons {
|
||||
|
||||
constructor(tableId) {
|
||||
this.tableId = tableId;
|
||||
const onTableLoaded = this.onTableLoaded.bind(this);
|
||||
window.addEventListener('tableLoaded', onTableLoaded, false);
|
||||
}
|
||||
|
||||
onTableLoaded(e) {
|
||||
const tableId = e.detail.tableId;
|
||||
const buttons = document.querySelectorAll(`#${tableId} button`);
|
||||
|
||||
const onButtonClick = this.onButtonClick.bind(this);
|
||||
buttons.forEach(button => button.addEventListener('click', onButtonClick, false));
|
||||
}
|
||||
|
||||
onButtonClick(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const me = this;
|
||||
const target = e.target;
|
||||
const url = '/admin/removeBlacklisted';
|
||||
|
||||
const data = {
|
||||
id: target.dataset.itemId,
|
||||
type: target.dataset.itemType,
|
||||
token: me.csrf
|
||||
};
|
||||
|
||||
target.classList.add('is-loading');
|
||||
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: url,
|
||||
data: data,
|
||||
scope: me,
|
||||
target: target,
|
||||
success: me.onSuccess,
|
||||
error: handleAjaxError,
|
||||
dataType: 'json'
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
onSuccess(dta, status) {
|
||||
if ('success' !== status) {
|
||||
return;
|
||||
}
|
||||
|
||||
const me = this.scope;
|
||||
|
||||
const target = this.target;
|
||||
const tableRow = target.closest('tr');
|
||||
|
||||
target.classList.remove('is-loading');
|
||||
target.setAttribute('disabled', '');
|
||||
target.textContent = 'Removed';
|
||||
|
||||
const card = target.closest('.card');
|
||||
const span = card.querySelector('.card-header-title span');
|
||||
|
||||
let total = parseInt(span.innerHTML, 10);
|
||||
|
||||
if (total > 0) {
|
||||
total -= 1;
|
||||
}
|
||||
|
||||
span.textContent = total;
|
||||
|
||||
if (tableRow) {
|
||||
const dataTable = $(`#${me.tableId}`).DataTable();
|
||||
dataTable.row(tableRow).remove().draw(false);
|
||||
}
|
||||
}
|
||||
|
||||
get csrf() {
|
||||
return document.querySelector('meta[name="csrf-token"]').getAttribute('content');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
import {Loader} from './Loader.js?v=2';
|
||||
import {handleAjaxError} from './utils/ErrorHandler.js?v=2';
|
||||
import {fireEvent} from './utils/Event.js?v=2';
|
||||
|
||||
export class DashboardTile {
|
||||
|
||||
constructor(tilesParams) {
|
||||
const me = this;
|
||||
this.config = tilesParams;
|
||||
|
||||
this.loader = new Loader();
|
||||
|
||||
if (!this.config.sequential) {
|
||||
const onDateFilterChanged = this.onDateFilterChanged.bind(this);
|
||||
window.addEventListener('dateFilterChanged', onDateFilterChanged, false);
|
||||
|
||||
this.initLoad();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
startLoader() {
|
||||
const el = document.querySelector(`.${this.config.mode} .title`);
|
||||
this.loader.start(el);
|
||||
}
|
||||
|
||||
loadData() {
|
||||
const me = this;
|
||||
const token = document.head.querySelector('[name=\'csrf-token\'][content]').content;
|
||||
|
||||
let params = this.config.getParams().dateRange;
|
||||
params.mode = this.config.mode;
|
||||
|
||||
if (!this.config.sequential) {
|
||||
this.startLoader();
|
||||
}
|
||||
|
||||
fireEvent('dateFilterChangedCaught');
|
||||
|
||||
$.ajax({
|
||||
url: `/admin/loadDashboardStat?token=${token}`,
|
||||
type: 'get',
|
||||
scope: me,
|
||||
data: params,
|
||||
success: me.onLoad,
|
||||
error: handleAjaxError,
|
||||
complete: function() {
|
||||
fireEvent('dateFilterChangedCompleted');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
onLoad(data, status) {
|
||||
if ('success' == status) {
|
||||
this.scope.loader.stop();
|
||||
|
||||
const frag = document.createDocumentFragment();
|
||||
|
||||
const period = document.createElement('p');
|
||||
if (this.scope.config.mode === 'totalUsersForReview') {
|
||||
period.className = 'periodTotalYellow';
|
||||
} else if (this.scope.config.mode === 'totalBlockedUsers') {
|
||||
period.className = 'periodTotalRed';
|
||||
} else {
|
||||
period.className = 'periodTotal';
|
||||
}
|
||||
period.textContent = data.total;
|
||||
|
||||
const total = document.createElement('p');
|
||||
total.className = 'allTimeTotal';
|
||||
total.textContent = data.allTimeTotal;
|
||||
|
||||
frag.appendChild(period);
|
||||
frag.appendChild(total);
|
||||
|
||||
const el = document.querySelector(`.${this.scope.config.mode} .title`);
|
||||
el.replaceChildren(frag);
|
||||
}
|
||||
}
|
||||
|
||||
onDateFilterChanged() {
|
||||
this.loadData();
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,261 @@
|
||||
import {fireEvent} from './utils/Event.js?v=2';
|
||||
import {
|
||||
formatStringTime,
|
||||
addDays,
|
||||
addHours,
|
||||
} from './utils/Date.js?v=2';
|
||||
import {debounce} from './utils/Functions.js?v=2';
|
||||
import {DAYS_IN_RANGE} from './utils/Constants.js?v=2';
|
||||
|
||||
export class DatesFilter {
|
||||
constructor(sequential=false) {
|
||||
this.setupXhrPool();
|
||||
this.offset = (this.offsetField) ? parseInt(this.offsetField.value, 10) : 0;
|
||||
this.ajaxCount = 0;
|
||||
this.sequential = sequential;
|
||||
if (this.isDateFilterUnavailable) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.dateToLocalField !== null && this.dateFromLocalField !== null) {
|
||||
// visible fields change should set invisible fields
|
||||
this.onTimestampFieldChange = this.onTimestampFieldChange.bind(this);
|
||||
const debouncedOnTimestampFieldChange = debounce(this.onTimestampFieldChange);
|
||||
|
||||
this.dateToLocalField.addEventListener('change', debouncedOnTimestampFieldChange, false);
|
||||
this.dateFromLocalField.addEventListener('change', debouncedOnTimestampFieldChange, false);
|
||||
|
||||
this.setDefaultLocalDates();
|
||||
this.setDefaultDates();
|
||||
} else if (!this.dateFromField.value && !this.dateToField.value) {
|
||||
this.setDefaultDates();
|
||||
}
|
||||
|
||||
const onDateFilterChange = this.onDateFilterChange.bind(this);
|
||||
|
||||
this.dateToField.addEventListener('change', onDateFilterChange, false);
|
||||
this.dateFromField.addEventListener('change', onDateFilterChange, false);
|
||||
|
||||
const onIntervalLinkClick = this.onIntervalLinkClick.bind(this);
|
||||
this.intervalLinks.forEach(item => item.addEventListener('click', onIntervalLinkClick, false));
|
||||
|
||||
const onDateFilterChangedCaught = this.onDateFilterChangedCaught.bind(this);
|
||||
window.addEventListener('dateFilterChangedCaught', onDateFilterChangedCaught, false);
|
||||
|
||||
const onDateFilterChangedCompleted = this.onDateFilterChangedCompleted.bind(this);
|
||||
window.addEventListener('dateFilterChangedCompleted', onDateFilterChangedCompleted, false);
|
||||
|
||||
const onSequentialLoadCompleted = this.onSequentialLoadCompleted.bind(this);
|
||||
window.addEventListener('sequentialLoadCompleted', onSequentialLoadCompleted, false);
|
||||
}
|
||||
|
||||
setupXhrPool() {
|
||||
// https://gist.github.com/msankhala/3fa2844c1fbad1f4c0185a8e3ef09aed
|
||||
// Stop all ajax request by http://tjrus.com/blog/stop-all-active-ajax-requests
|
||||
$.xhrPool = []; // array of uncompleted requests
|
||||
$.xhrPool.abortAll = function() { // our abort function
|
||||
$(this).each((idx, jqXHR) => {
|
||||
jqXHR.abort();
|
||||
});
|
||||
$.xhrPool.length = 0;
|
||||
};
|
||||
|
||||
$.ajaxSetup({
|
||||
beforeSend: function(jqXHR) { // before jQuery send the request we will push it to our array
|
||||
$.xhrPool.push(jqXHR);
|
||||
},
|
||||
complete: function(jqXHR) { // when some of the requests completed it will splice from the array
|
||||
const index = $.xhrPool.indexOf(jqXHR);
|
||||
if (index > -1) {
|
||||
$.xhrPool.splice(index, 1);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
setDefaultDates() {
|
||||
this.setDateRangeFromNow(DAYS_IN_RANGE * 24);
|
||||
}
|
||||
|
||||
setDefaultLocalDates() {
|
||||
let dateTo = new Date();
|
||||
dateTo = new Date(dateTo.getTime() + (dateTo.getTimezoneOffset() * 60 + this.offset) * 1000); // now time in op tz
|
||||
|
||||
const dateFrom = addDays(dateTo, -DAYS_IN_RANGE); // dateFrom in op tz
|
||||
dateFrom.setHours(24, 0, 0, 0);
|
||||
|
||||
this.dateToLocalField.value = formatStringTime(dateTo);
|
||||
this.dateFromLocalField.value = formatStringTime(dateFrom);
|
||||
}
|
||||
|
||||
onTimestampFieldChange(e) {
|
||||
// get value (with offset)
|
||||
// set normal input exluding offset
|
||||
e.preventDefault();
|
||||
|
||||
const input = e.target;
|
||||
|
||||
$.xhrPool.abortAll();
|
||||
|
||||
const value = input.value;
|
||||
const name = input.name;
|
||||
|
||||
let target = null;
|
||||
|
||||
if (name === 'date_from_local') {
|
||||
target = this.dateFromField;
|
||||
} else if (name === 'date_to_local') {
|
||||
target = this.dateToField;
|
||||
}
|
||||
|
||||
let dt = new Date(value);
|
||||
dt = new Date(dt.getTime() - this.offset * 1000); // shift fom op tz to utc
|
||||
|
||||
target.value = formatStringTime(dt);
|
||||
|
||||
this.onDateFilterChange(e);
|
||||
return false;
|
||||
}
|
||||
|
||||
onDateFilterChange() {
|
||||
$.xhrPool.abortAll();
|
||||
fireEvent('dateFilterChanged');
|
||||
}
|
||||
|
||||
getValue() {
|
||||
const data = {
|
||||
dateTo: null,
|
||||
dateFrom: null
|
||||
};
|
||||
|
||||
if (this.isDateFilterUnavailable) {
|
||||
return data;
|
||||
}
|
||||
|
||||
data['dateTo'] = this.dateToField.value;
|
||||
data['dateFrom'] = this.dateFromField.value;
|
||||
|
||||
/*const rangeWasChanged = (1 == this.dateFromField.dataset.changed) || (1 == this.dateToField.dataset.changed);
|
||||
if (rangeWasChanged) {
|
||||
data['keepDates'] = 1;
|
||||
}*/
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
onIntervalLinkClick(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const link = e.target;
|
||||
if (link.classList.contains('active') || link.classList.contains('blocked')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.updateDisabled(true);
|
||||
|
||||
$.xhrPool.abortAll();
|
||||
|
||||
const value = parseInt(link.dataset.value, 10) || 0;
|
||||
|
||||
if (value === 0) {
|
||||
this.clearDateRange();
|
||||
} else {
|
||||
this.setDateRangeFromNow(value);
|
||||
}
|
||||
|
||||
this.intervalLinks.forEach(item => item.classList.remove('active'));
|
||||
link.classList.add('active');
|
||||
|
||||
this.onDateFilterChange();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
updateDisabled(disabled) {
|
||||
let m = disabled ? 'true' : 'false';
|
||||
this.intervalLinks.forEach(item => {
|
||||
if (disabled) {
|
||||
if (!item.classList.contains('active')) {
|
||||
item.setAttribute('tabindex', '-1');
|
||||
item.classList.add('blocked');
|
||||
item.removeAttribute('href');
|
||||
}
|
||||
} else {
|
||||
item.setAttribute('tabindex', '0');
|
||||
item.classList.remove('blocked');
|
||||
item.setAttribute('href', 'javascript:void(0);');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// with op tz and utc shift for calculation request
|
||||
setDateRangeFromNow(hoursDiff) {
|
||||
let dateTo = new Date();
|
||||
dateTo = new Date(dateTo.getTime() + (dateTo.getTimezoneOffset() * 60 + this.offset) * 1000); // now time in op tz
|
||||
let dateFrom = addHours(dateTo, -hoursDiff); // dateFrom in op tz
|
||||
// floor to not miss data in group
|
||||
if (hoursDiff < 24 && hoursDiff > -24) {
|
||||
dateFrom.setMinutes(0, 0, 0);
|
||||
} else {
|
||||
dateFrom.setHours(24, 0, 0, 0);
|
||||
}
|
||||
|
||||
dateTo = new Date(dateTo.getTime() - (this.offset * 1000)); // dateTo at utc
|
||||
dateFrom = new Date(dateFrom.getTime() - (this.offset * 1000)); // dateFrom at utc
|
||||
|
||||
this.dateToField.value = formatStringTime(dateTo);
|
||||
this.dateFromField.value = formatStringTime(dateFrom);
|
||||
}
|
||||
|
||||
clearDateRange() {
|
||||
this.dateToField.value = null;
|
||||
this.dateFromField.value = null;
|
||||
}
|
||||
|
||||
onDateFilterChangedCaught() {
|
||||
this.ajaxCount++;
|
||||
}
|
||||
|
||||
onDateFilterChangedCompleted() {
|
||||
this.ajaxCount--;
|
||||
if (this.ajaxCount <= 0 && !this.sequential) {
|
||||
this.updateDisabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
onSequentialLoadCompleted() {
|
||||
this.updateDisabled(false);
|
||||
}
|
||||
|
||||
get isDateFilterUnavailable() {
|
||||
return this.dateFromField === null || this.dateToField === null;
|
||||
}
|
||||
|
||||
get intervalLinks() {
|
||||
return this.navbar.querySelectorAll('a');
|
||||
}
|
||||
|
||||
get navbar() {
|
||||
return document.querySelector('nav.filtersForm.daterange');
|
||||
}
|
||||
|
||||
get offsetField() {
|
||||
return document.querySelector('input[name="offset"]');
|
||||
}
|
||||
|
||||
get dateToField() {
|
||||
return document.querySelector('input[name="date_to"]');
|
||||
}
|
||||
|
||||
get dateFromField() {
|
||||
return document.querySelector('input[name="date_from"]');
|
||||
}
|
||||
|
||||
get dateToLocalField() {
|
||||
return document.querySelector('input[name="date_to_local"]');
|
||||
}
|
||||
|
||||
get dateFromLocalField() {
|
||||
return document.querySelector('input[name="date_from_local"]');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
import {fireEvent} from './utils/Event.js?v=2';
|
||||
|
||||
export class DeleteAccountPopUp {
|
||||
|
||||
constructor() {
|
||||
|
||||
const onDeleteAccountButtonClicked = this.onDeleteAccountButtonClicked.bind(this);
|
||||
this.closeAccountButton.addEventListener('click', onDeleteAccountButtonClicked, false);
|
||||
|
||||
const onConfirmDeleteAccountButton = this.onConfirmDeleteAccountButton.bind(this);
|
||||
this.confirmButton.addEventListener('click', onConfirmDeleteAccountButton, false);
|
||||
|
||||
const onKeydown = this.onKeydown.bind(this);
|
||||
window.addEventListener('keydown', onKeydown, false);
|
||||
|
||||
const onCloseButtonClick = this.onCloseButtonClick.bind(this);
|
||||
this.closePopUpButton.addEventListener('click', onCloseButtonClick, false);
|
||||
}
|
||||
|
||||
//https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key
|
||||
onKeydown(e) {
|
||||
if (e.defaultPrevented) {
|
||||
return; // Do nothing if the event was already processed
|
||||
}
|
||||
switch (e.key) {
|
||||
case 'Esc': // IE/Edge specific value
|
||||
case 'Escape':
|
||||
this.close();
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
// Cancel the default action to avoid it being handled twice
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
onConfirmDeleteAccountButton(e) {
|
||||
e.preventDefault();
|
||||
this.accountForm.submit();
|
||||
|
||||
this.card.classList.add('is-hidden');
|
||||
this.contentDiv.classList.add('is-hidden');
|
||||
|
||||
}
|
||||
|
||||
onDeleteAccountButtonClicked(e) {
|
||||
e.preventDefault();
|
||||
|
||||
// close other panels
|
||||
const card = document.querySelector('.details-card#enrich-all-popup');
|
||||
if (card && !card.classList.contains('is-hidden')) {
|
||||
fireEvent('enrichAllPopUpClosed');
|
||||
card.classList.add('is-hidden');
|
||||
}
|
||||
|
||||
this.card.classList.remove('is-hidden');
|
||||
this.contentDiv.classList.remove('is-hidden');
|
||||
}
|
||||
|
||||
onCloseButtonClick(e) {
|
||||
e.preventDefault();
|
||||
this.close();
|
||||
}
|
||||
|
||||
close() {
|
||||
fireEvent('closeAccountPopUpClosed');
|
||||
this.card.classList.add('is-hidden');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
get contentDiv() {
|
||||
return this.card.querySelector('div.content');
|
||||
}
|
||||
|
||||
get card() {
|
||||
return document.querySelector('.details-card#close-account-popup');
|
||||
}
|
||||
|
||||
get closePopUpButton() {
|
||||
return this.card.querySelector('.delete');
|
||||
}
|
||||
|
||||
get accountForm() {
|
||||
return document.getElementById('close-account-form');
|
||||
}
|
||||
|
||||
get confirmButton() {
|
||||
return document.getElementById('confirm-close-account-button');
|
||||
}
|
||||
|
||||
get closeAccountButton() {
|
||||
return document.getElementById('close-account-btn');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
import {Loader} from './Loader.js?v=2';
|
||||
import {fireEvent} from './utils/Event.js?v=2';
|
||||
import {handleAjaxError} from './utils/ErrorHandler.js?v=2';
|
||||
import {renderEnrichmentCalculation} from './DataRenderers.js?v=2';
|
||||
|
||||
export class EnrichAllPopUp {
|
||||
|
||||
constructor() {
|
||||
this.loader = new Loader();
|
||||
|
||||
const onEnrichAllButtonClicked = this.onEnrichAllButtonClicked.bind(this);
|
||||
this.enrichAllButton.addEventListener('click', onEnrichAllButtonClicked, false);
|
||||
|
||||
const onConfirmEnrichAllButton = this.onConfirmEnrichAllButton.bind(this);
|
||||
this.confirmButton.addEventListener('click', onConfirmEnrichAllButton, false);
|
||||
|
||||
const onKeydown = this.onKeydown.bind(this);
|
||||
window.addEventListener('keydown', onKeydown, false);
|
||||
|
||||
const onCloseButtonClick = this.onCloseButtonClick.bind(this);
|
||||
this.closePopUpButton.addEventListener('click', onCloseButtonClick, false);
|
||||
}
|
||||
|
||||
//https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key
|
||||
onKeydown(e) {
|
||||
if (e.defaultPrevented) {
|
||||
return; // Do nothing if the event was already processed
|
||||
}
|
||||
switch (e.key) {
|
||||
case 'Esc': // IE/Edge specific value
|
||||
case 'Escape':
|
||||
this.close();
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
// Cancel the default action to avoid it being handled twice
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
onConfirmEnrichAllButton(e) {
|
||||
e.preventDefault();
|
||||
this.accountForm.submit();
|
||||
|
||||
this.card.classList.add('is-hidden');
|
||||
this.contentDiv.classList.add('is-hidden');
|
||||
|
||||
}
|
||||
|
||||
onEnrichAllButtonClicked(e) {
|
||||
e.preventDefault();
|
||||
|
||||
// close other panels
|
||||
const card = document.querySelector('.details-card#close-account-popup');
|
||||
if (card && !card.classList.contains('is-hidden')) {
|
||||
fireEvent('closeAccountPopUpClosed');
|
||||
card.classList.add('is-hidden');
|
||||
}
|
||||
|
||||
// call ajax
|
||||
this.loadData();
|
||||
}
|
||||
|
||||
loadData(id) {
|
||||
this.contentDiv.classList.add('is-hidden');
|
||||
this.loaderDiv.classList.remove('is-hidden');
|
||||
this.card.classList.remove('is-hidden');
|
||||
|
||||
const el = this.loaderDiv;
|
||||
this.loader.start(el);
|
||||
|
||||
const onDetailsLoaded = this.onDetailsLoaded.bind(this);
|
||||
const token = document.head.querySelector('[name=\'csrf-token\'][content]').content;
|
||||
|
||||
$.ajax({
|
||||
url: '/admin/enrichmentDetails',
|
||||
type: 'get',
|
||||
data: {token: token},
|
||||
success: onDetailsLoaded,
|
||||
error: handleAjaxError,
|
||||
});
|
||||
}
|
||||
|
||||
onDetailsLoaded(data, status) {
|
||||
if ('success' !== status || 0 === data.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
data = this.proceedData(data);
|
||||
|
||||
this.loader.stop();
|
||||
this.contentDiv.classList.remove('is-hidden');
|
||||
this.loaderDiv.classList.add('is-hidden');
|
||||
|
||||
let span = null;
|
||||
//todo: foreach and arrow fn ?
|
||||
for (const key in data) {
|
||||
span = this.card.querySelector(`#details_${key}`);
|
||||
if (span) {
|
||||
if (data[key] instanceof Node) {
|
||||
span.replaceChildren(data[key]);
|
||||
} else {
|
||||
span.innerHTML = data[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
proceedData(data) {
|
||||
data.calculation = renderEnrichmentCalculation(data);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
onCloseButtonClick(e) {
|
||||
e.preventDefault();
|
||||
this.close();
|
||||
}
|
||||
|
||||
close() {
|
||||
fireEvent('enrichAllPopUpClosed');
|
||||
this.card.classList.add('is-hidden');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
get loaderDiv() {
|
||||
return this.card.querySelector('div.text-loader');
|
||||
}
|
||||
|
||||
get contentDiv() {
|
||||
return this.card.querySelector('div.content');
|
||||
}
|
||||
|
||||
get card() {
|
||||
return document.querySelector('.details-card#enrich-all-popup');
|
||||
}
|
||||
|
||||
get closePopUpButton() {
|
||||
return this.card.querySelector('.delete');
|
||||
}
|
||||
|
||||
get accountForm() {
|
||||
return document.getElementById('enrich-all-form');
|
||||
}
|
||||
|
||||
get confirmButton() {
|
||||
return document.getElementById('confirm-enrich-all-button');
|
||||
}
|
||||
|
||||
get enrichAllButton() {
|
||||
return document.getElementById('enrich-all-btn');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
export class Loader {
|
||||
|
||||
constructor() {
|
||||
this.symbols = [
|
||||
this.el('⣾'),
|
||||
this.el('⣷'),
|
||||
this.el('⣯'),
|
||||
this.el('⣟'),
|
||||
this.el('⡿'),
|
||||
this.el('⢿'),
|
||||
this.el('⣻'),
|
||||
this.el('⣽'),
|
||||
];
|
||||
}
|
||||
|
||||
start(loaderEl) {
|
||||
this.loaderEl = loaderEl;
|
||||
|
||||
let me = this;
|
||||
let counter = 0;
|
||||
|
||||
this.loaderEl.classList.add('loading');
|
||||
this.loaderEl.classList.remove('loaded');
|
||||
|
||||
let timerId = setInterval(() => {
|
||||
if (me.loaderEl.classList.contains('loaded')) {
|
||||
clearInterval(timerId);
|
||||
return;
|
||||
}
|
||||
|
||||
let symbol = me.symbols[counter % me.symbols.length];
|
||||
|
||||
me.loaderEl.replaceChildren(symbol);
|
||||
|
||||
counter++;
|
||||
}, 85);
|
||||
}
|
||||
|
||||
stop() {
|
||||
this.loaderEl.classList.add('loaded');
|
||||
this.loaderEl.classList.remove('loading');
|
||||
}
|
||||
|
||||
el(c) {
|
||||
const node = document.createElement('p');
|
||||
node.textContent = c;
|
||||
|
||||
return node;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,318 @@
|
||||
import {
|
||||
renderDefaultIfEmptyElement,
|
||||
renderBoolean,
|
||||
renderDate,
|
||||
renderCountryIso,
|
||||
renderHttpCode,
|
||||
renderPhoneType,
|
||||
renderPhoneCarrierName,
|
||||
renderAsn,
|
||||
} from '../parts/DataRenderers.js?v=2';
|
||||
|
||||
export class ManualCheckItems {
|
||||
|
||||
constructor() {
|
||||
const table = document.querySelector('.events-card.is-hidden');
|
||||
|
||||
if (!table) return;
|
||||
|
||||
table.classList.remove('is-hidden');
|
||||
|
||||
const itemType = table.dataset.itemType;
|
||||
|
||||
if ('ip' == itemType) {
|
||||
this.enrichIpDetails();
|
||||
}
|
||||
|
||||
if ('email' == itemType) {
|
||||
this.enrichEmailDetails();
|
||||
}
|
||||
|
||||
if ('domain' == itemType) {
|
||||
this.enrichDomainDetails();
|
||||
}
|
||||
|
||||
if ('phone' == itemType) {
|
||||
this.enrichPhoneDetails();
|
||||
}
|
||||
}
|
||||
|
||||
enrichPhoneDetails() {
|
||||
let item = null;
|
||||
|
||||
item = 'iso_country_code';
|
||||
this.renderCountryIso(item);
|
||||
|
||||
item = 'type';
|
||||
this.renderPhoneType(item);
|
||||
|
||||
item = 'invalid';
|
||||
this.renderBoolean(item);
|
||||
|
||||
item = 'profiles';
|
||||
this.renderProfiles(item);
|
||||
|
||||
item = 'carrier_name';
|
||||
this.renderPhoneCarrierName(item);
|
||||
}
|
||||
|
||||
enrichDomainDetails() {
|
||||
let item = null;
|
||||
|
||||
item = 'blockdomains';
|
||||
this.renderBoolean(item);
|
||||
|
||||
item = 'disposable_domains';
|
||||
this.renderBoolean(item);
|
||||
|
||||
item = 'free_email_provider';
|
||||
this.renderBoolean(item);
|
||||
|
||||
item = 'geo_ip';
|
||||
this.renderCountryIso(item);
|
||||
|
||||
item = 'geo_html';
|
||||
this.renderCountryIso(item);
|
||||
|
||||
item = 'web_server';
|
||||
this.renderDefaultIfEmptyElement(item);
|
||||
|
||||
item = 'hostname';
|
||||
this.renderDefaultIfEmptyElement(item);
|
||||
|
||||
item = 'emails';
|
||||
this.renderDefaultIfEmptyElement(item);
|
||||
|
||||
item = 'phone';
|
||||
this.renderDefaultIfEmptyElement(item);
|
||||
|
||||
item = 'discovery_date';
|
||||
this.renderDate(item);
|
||||
|
||||
item = 'creation_date';
|
||||
this.renderDate(item);
|
||||
|
||||
item = 'expiration_date';
|
||||
this.renderDate(item);
|
||||
|
||||
item = 'mx_record';
|
||||
this.renderBoolean(item);
|
||||
|
||||
item = 'return_code';
|
||||
this.renderHttpCode(item);
|
||||
|
||||
item = 'disabled';
|
||||
this.renderBoolean(item);
|
||||
|
||||
item = 'closest_snapshot';
|
||||
this.renderDate(item);
|
||||
}
|
||||
|
||||
enrichIpDetails() {
|
||||
let item = null;
|
||||
let value = null;
|
||||
|
||||
item = 'country';
|
||||
this.renderCountryIso(item);
|
||||
|
||||
item = 'asn';
|
||||
value = this.getItem(item);
|
||||
value = {asn: value};
|
||||
value = renderAsn(value);
|
||||
this.setItem(item, value);
|
||||
|
||||
item = 'hosting';
|
||||
this.renderBoolean(item);
|
||||
|
||||
item = 'vpn';
|
||||
this.renderBoolean(item);
|
||||
|
||||
item = 'tor';
|
||||
this.renderBoolean(item);
|
||||
|
||||
item = 'relay';
|
||||
this.renderBoolean(item);
|
||||
|
||||
item = 'starlink';
|
||||
this.renderBoolean(item);
|
||||
|
||||
item = 'description';
|
||||
this.renderDefaultIfEmptyElement(item);
|
||||
|
||||
item = 'blocklist';
|
||||
this.renderBoolean(item);
|
||||
|
||||
item = 'domains_count';
|
||||
value = this.getItem(item);
|
||||
if (value) {
|
||||
value = JSON.parse(value);
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
value = value.length;
|
||||
} else {
|
||||
value = parseInt(value, 10);
|
||||
}
|
||||
|
||||
if (isNaN(value)) {
|
||||
value = renderDefaultIfEmptyElement(value);
|
||||
} else {
|
||||
value = !!value;
|
||||
value = renderBoolean(value);
|
||||
}
|
||||
|
||||
} else {
|
||||
value = renderDefaultIfEmptyElement(value);
|
||||
}
|
||||
this.setItem(item, value);
|
||||
}
|
||||
|
||||
enrichEmailDetails() {
|
||||
let item = null;
|
||||
|
||||
item = 'blockemails';
|
||||
this.renderBoolean(item);
|
||||
|
||||
item = 'data_breach';
|
||||
this.renderDataBreach(item);
|
||||
|
||||
item = 'earliest_breach';
|
||||
this.renderDate(item);
|
||||
|
||||
item = 'domain_contact_email';
|
||||
this.renderBoolean(item);
|
||||
|
||||
item = 'profiles';
|
||||
this.renderProfiles(item);
|
||||
}
|
||||
|
||||
renderDataBreach(itemId) {
|
||||
let value;
|
||||
|
||||
value = this.getItem(itemId);
|
||||
|
||||
if (null === value) {
|
||||
value = renderDefaultIfEmptyElement(value);
|
||||
} else {
|
||||
//Revert databreach to "No databreach"
|
||||
value = !value;
|
||||
value = renderBoolean(value);
|
||||
}
|
||||
|
||||
this.setItem(itemId, value);
|
||||
}
|
||||
|
||||
renderProfiles(itemId) {
|
||||
let value;
|
||||
|
||||
value = this.getItem(itemId);
|
||||
value = parseInt(value, 10);
|
||||
|
||||
if (isNaN(value)) {
|
||||
value = renderDefaultIfEmptyElement(value);
|
||||
} else {
|
||||
//Convert to boolean
|
||||
value = !!value;
|
||||
|
||||
//Revert profiles to "No profiles"
|
||||
value = !value;
|
||||
|
||||
value = renderBoolean(value);
|
||||
}
|
||||
|
||||
this.setItem(itemId, value);
|
||||
}
|
||||
|
||||
renderDate(itemId) {
|
||||
let value;
|
||||
|
||||
value = this.getItem(itemId);
|
||||
value = renderDate(value);
|
||||
this.setItem(itemId, value);
|
||||
}
|
||||
|
||||
renderCountryIso(itemId) {
|
||||
let value;
|
||||
|
||||
value = this.getItem(itemId);
|
||||
value = {country_iso: value, full_country: value};
|
||||
value = renderCountryIso(value);
|
||||
this.setItem(itemId, value);
|
||||
}
|
||||
|
||||
renderHttpCode(itemId) {
|
||||
let value;
|
||||
|
||||
value = this.getItem(itemId);
|
||||
value = {http_code: value};
|
||||
value = renderHttpCode(value);
|
||||
this.setItem(itemId, value);
|
||||
}
|
||||
|
||||
renderPhoneType(itemId) {
|
||||
let value;
|
||||
|
||||
value = this.getItem(itemId);
|
||||
value = {type: value};
|
||||
value = renderPhoneType(value);
|
||||
this.setItem(itemId, value);
|
||||
}
|
||||
|
||||
renderPhoneCarrierName(itemId) {
|
||||
let value;
|
||||
|
||||
value = this.getItem(itemId);
|
||||
value = {carrier_name: value};
|
||||
value = renderPhoneCarrierName(value);
|
||||
this.setItem(itemId, value);
|
||||
|
||||
}
|
||||
|
||||
renderBoolean(itemId) {
|
||||
let value;
|
||||
|
||||
value = this.getItem(itemId);
|
||||
value = renderBoolean(value);
|
||||
this.setItem(itemId, value);
|
||||
}
|
||||
|
||||
renderDefaultIfEmptyElement(itemId) {
|
||||
let value;
|
||||
|
||||
value = this.getItem(itemId);
|
||||
value = renderDefaultIfEmptyElement(value);
|
||||
this.setItem(itemId, value);
|
||||
}
|
||||
|
||||
getItem(itemId, returnNode = false) {
|
||||
const td = document.querySelector(`td[data-item-id="${itemId}"]`);
|
||||
|
||||
if (!td) return null;
|
||||
|
||||
const tr = td.closest('tr');
|
||||
|
||||
const valueTd = tr.lastElementChild;
|
||||
if (returnNode) {
|
||||
return valueTd;
|
||||
} else {
|
||||
let text = valueTd.innerText;
|
||||
let value = text;
|
||||
|
||||
if ('false' === text) value = false;
|
||||
if ('true' === text) value = true;
|
||||
if ('null' === text) value = null;
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
setItem(itemId, value) {
|
||||
const item = this.getItem(itemId, true);
|
||||
if (item) {
|
||||
if (item instanceof Node) {
|
||||
item.replaceChildren(value);
|
||||
} else {
|
||||
item.innerHTML = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
import {TotalTile} from './TotalTile.js?v=2';
|
||||
import {getQueryParams} from './utils/DataSource.js?v=2';
|
||||
import {handleAjaxError} from './utils/ErrorHandler.js?v=2';
|
||||
import {fireEvent} from './utils/Event.js?v=2';
|
||||
|
||||
export class Map {
|
||||
|
||||
constructor(mapParams) {
|
||||
this.config = mapParams;
|
||||
|
||||
this.totalTile = new TotalTile();
|
||||
|
||||
this.regions = {};
|
||||
|
||||
const onRegionTipShow = this.onRegionTipShow.bind(this);
|
||||
const onRegionClick = this.onRegionClick.bind(this);
|
||||
|
||||
$('#world-map-markers').vectorMap({
|
||||
map: 'world_mill_en',
|
||||
|
||||
normalizeFunction: 'polynomial',
|
||||
hoverOpacity: 0.7,
|
||||
regionsSelectable: false,
|
||||
markersSelectable: false,
|
||||
zoomOnScroll: false,
|
||||
hoverColor: false,
|
||||
|
||||
series: {
|
||||
regions: [
|
||||
{
|
||||
values: {},
|
||||
scale: ['#4e6964', '#01EE99'],
|
||||
normalizeFunction: 'polynomial'
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
regionStyle: {
|
||||
initial: {
|
||||
fill: '#575678'
|
||||
},
|
||||
selected: {
|
||||
fill: '#01EE99'
|
||||
}
|
||||
},
|
||||
|
||||
onRegionTipShow: function(e, el, code) {
|
||||
onRegionTipShow(el, code);
|
||||
},
|
||||
onRegionClick: function(e, code) {
|
||||
onRegionClick(code);
|
||||
},
|
||||
|
||||
backgroundColor: '#131220'
|
||||
});
|
||||
|
||||
const onDateFilterChanged = this.onDateFilterChanged.bind(this);
|
||||
window.addEventListener('dateFilterChanged', onDateFilterChanged, false);
|
||||
|
||||
if (!this.config.sequential) {
|
||||
this.loadData();
|
||||
}
|
||||
}
|
||||
|
||||
startLoader() {
|
||||
}
|
||||
|
||||
onRegionTipShow(tipEl, value) {
|
||||
const regionValue = this.mapObject.series.regions[0].values[value];
|
||||
const phrase = this.getTooltipString(regionValue);
|
||||
|
||||
tipEl.html(`${tipEl.html()} - ${phrase}`);
|
||||
}
|
||||
|
||||
onRegionClick(value) {
|
||||
if (this.regions[value] !== undefined && this.regions[value][this.config.tooltipField] > 0) {
|
||||
const url = `/country/${this.regions[value].id}`;
|
||||
if (event.ctrlKey || event.metaKey) {
|
||||
window.open(url, '_blank');
|
||||
} else {
|
||||
window.location.href = url;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getCountriesRegionsFromResponse(records) {
|
||||
const me = this;
|
||||
const regions = {};
|
||||
|
||||
this.regions = {};
|
||||
|
||||
records.forEach(rec => {
|
||||
const country = rec.iso;
|
||||
if (!regions[country]) {
|
||||
regions[country] = 0;
|
||||
this.regions[country] = 0;
|
||||
}
|
||||
|
||||
const value = me.getRegionValue(rec);
|
||||
regions[country] = value;
|
||||
this.regions[country] = {
|
||||
[this.config.tooltipField]: value,
|
||||
id: rec.id,
|
||||
};
|
||||
});
|
||||
|
||||
return regions;
|
||||
}
|
||||
|
||||
getRegionValue(record) {
|
||||
const field = this.config.tooltipField;
|
||||
const value = record[field];
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
selectRegions(regions) {
|
||||
const map = this.mapObject;
|
||||
|
||||
//Remove countries which does not exist in the vectormap: MU, BH, etc...
|
||||
for (const [key, value] of Object.entries(regions)) {
|
||||
if (!map.regions.hasOwnProperty(key)) {
|
||||
delete regions[key];
|
||||
}
|
||||
}
|
||||
|
||||
//https://github.com/bjornd/jvectormap/issues/376
|
||||
map.series.regions[0].params.min = undefined;
|
||||
map.series.regions[0].params.max = undefined;
|
||||
|
||||
map.series.regions[0].clear();
|
||||
map.series.regions[0].setValues(regions);
|
||||
}
|
||||
|
||||
onDateFilterChanged() {
|
||||
this.loadData();
|
||||
}
|
||||
|
||||
loadData() {
|
||||
const me = this;
|
||||
const params = this.config.getParams();
|
||||
const token = document.head.querySelector('[name=\'csrf-token\'][content]').content;
|
||||
|
||||
const data = getQueryParams(params);
|
||||
|
||||
fireEvent('dateFilterChangedCaught');
|
||||
|
||||
$.ajax({
|
||||
type: 'get',
|
||||
url: `/admin/loadMap?token=${token}`,
|
||||
data: data,
|
||||
scope: me,
|
||||
success: me.onCountriesListLoaded,
|
||||
error: handleAjaxError,
|
||||
complete: function() {
|
||||
fireEvent('dateFilterChangedCompleted');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
onCountriesListLoaded(data, status) {
|
||||
if ('success' == status) {
|
||||
const me = this.scope;
|
||||
|
||||
const tableId = 'countries-table';
|
||||
|
||||
me.totalTile.update(tableId, me.config.tileId, data.length);
|
||||
|
||||
const regions = me.getCountriesRegionsFromResponse(data);
|
||||
|
||||
me.selectRegions(regions);
|
||||
}
|
||||
}
|
||||
|
||||
getTooltipString(value) {
|
||||
value = value ? value : 0;
|
||||
|
||||
let string = this.config.tooltipString;
|
||||
if (1 !== value) {
|
||||
string += 's';
|
||||
}
|
||||
|
||||
const tooltipPhrase = `${value} ${string}`;
|
||||
|
||||
return tooltipPhrase;
|
||||
}
|
||||
|
||||
get mapObject() {
|
||||
return $('#world-map-markers').vectorMap('get', 'mapObject');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// should be used for POST forms
|
||||
export class ReenrichmentButton {
|
||||
constructor() {
|
||||
if (this.reenrichmentButton) {
|
||||
const onButtonClick = this.onButtonClick.bind(this);
|
||||
this.reenrichmentButton.addEventListener('click', onButtonClick, false);
|
||||
}
|
||||
}
|
||||
|
||||
onButtonClick(e) {
|
||||
e.preventDefault();
|
||||
this.reenrichmentButton.setAttribute('disabled', '');
|
||||
this.form.submit();
|
||||
}
|
||||
|
||||
get reenrichmentButton() {
|
||||
return document.querySelector('#reenrichment-form .reenrichment-button');
|
||||
}
|
||||
|
||||
get form() {
|
||||
return document.querySelector('#reenrichment-form');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import {renderScoreDetails} from './DataRenderers.js?v=2';
|
||||
import {handleAjaxError} from './utils/ErrorHandler.js?v=2';
|
||||
import {Tooltip} from './Tooltip.js?v=2';
|
||||
|
||||
|
||||
export class ScoreDetails {
|
||||
|
||||
constructor(scoreParams) {
|
||||
this.config = scoreParams;
|
||||
this.loadData();
|
||||
}
|
||||
|
||||
loadData() {
|
||||
const onScoreDetailsLoaded = this.onScoreDetailsLoaded.bind(this);
|
||||
const token = document.head.querySelector('[name=\'csrf-token\'][content]').content;
|
||||
|
||||
$.ajax({
|
||||
url: '/admin/scoreDetails',
|
||||
type: 'get',
|
||||
data: {userId: this.config.userId, token: token},
|
||||
success: onScoreDetailsLoaded,
|
||||
error: handleAjaxError,
|
||||
});
|
||||
}
|
||||
|
||||
onScoreDetailsLoaded(data, status) {
|
||||
if ('success' !== status || 0 === data.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
let el = this.contentDiv;
|
||||
|
||||
if (el) {
|
||||
el.replaceChildren(renderScoreDetails(data));
|
||||
}
|
||||
|
||||
this.initTooltips();
|
||||
}
|
||||
|
||||
get card() {
|
||||
return document.querySelector('.score-details');
|
||||
}
|
||||
|
||||
get contentDiv() {
|
||||
return this.card.querySelector('div.score-details-content');
|
||||
}
|
||||
|
||||
initTooltips() {
|
||||
Tooltip.addTooltipsToScoreDetails();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import {fireEvent} from './utils/Event.js?v=2';
|
||||
import {debounce} from './utils/Functions.js?v=2';
|
||||
|
||||
export class SearchFilter {
|
||||
constructor() {
|
||||
const onSearchInputChange = this.onSearchInputChange.bind(this);
|
||||
const debouncedOnSearchInputChange = debounce(onSearchInputChange);
|
||||
this.searchField.addEventListener('input', debouncedOnSearchInputChange, false);
|
||||
|
||||
const onDateFilterChanged = this.onDateFilterChanged.bind(this);
|
||||
window.addEventListener('dateFilterChanged', onDateFilterChanged, false);
|
||||
}
|
||||
|
||||
onSearchInputChange({target}) {
|
||||
const value = target.value;
|
||||
fireEvent('searchFilterChanged', {query: value});
|
||||
}
|
||||
|
||||
onDateFilterChanged() {
|
||||
this.searchField.value = '';
|
||||
}
|
||||
|
||||
getValue() {
|
||||
return this.searchField.value;
|
||||
}
|
||||
|
||||
get searchField() {
|
||||
return document.getElementById('search');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
import {Loader} from './Loader.js?v=2';
|
||||
import {Tooltip} from './Tooltip.js?v=2';
|
||||
import {handleAjaxError} from './utils/ErrorHandler.js?v=2';
|
||||
import {padZero} from './utils/Date.js?v=2';
|
||||
|
||||
export class SearchLine {
|
||||
constructor() {
|
||||
this.loader = new Loader();
|
||||
|
||||
Tooltip.addTooltipsToClock();
|
||||
|
||||
const me = this;
|
||||
const token = document.head.querySelector('[name=\'csrf-token\'][content]').content;
|
||||
const url = `/admin/search?token=${token}`;
|
||||
|
||||
$('#auto-complete').autocomplete({
|
||||
serviceUrl: url,
|
||||
deferRequestBy: 300,
|
||||
minChars: 3,
|
||||
groupBy: 'category',
|
||||
showNoSuggestionNotice: true,
|
||||
noSuggestionNotice: 'Sorry, no matching results',
|
||||
|
||||
onSelect: function(suggestion) {
|
||||
window.open(`/${suggestion.entityId}/${suggestion.id}`, '_self');
|
||||
},
|
||||
|
||||
onSearchStart: function(params) {
|
||||
params.query = params.query.trim();
|
||||
me.loaderDiv.classList.remove('is-hidden');
|
||||
me.loader.start(me.loaderDiv);
|
||||
|
||||
},
|
||||
onSearchComplete: function(query, suggestions) {
|
||||
me.loader.stop();
|
||||
me.loaderDiv.classList.add('is-hidden');
|
||||
},
|
||||
|
||||
onSearchError: handleAjaxError,
|
||||
});
|
||||
|
||||
setInterval(this.updateTime.bind(this), 1000);
|
||||
}
|
||||
|
||||
updateTime() {
|
||||
let [time, tz] = this.timeInput.placeholder.split(' ');
|
||||
let [h, m, s] = time.split(':').map(x => parseInt(x, 10));
|
||||
let d = this.dayInput.placeholder;
|
||||
|
||||
s += 1;
|
||||
if (s >= 60) {
|
||||
s = 0;
|
||||
m += 1;
|
||||
}
|
||||
|
||||
if (m >= 60) {
|
||||
m = 0;
|
||||
h += 1;
|
||||
}
|
||||
|
||||
if (h >= 24) {
|
||||
h = 0;
|
||||
d = parseInt(d, 10) + 1;
|
||||
|
||||
if (d >= 366) {
|
||||
const now = new Date();
|
||||
const year = now.getFullYear() - (now.getMonth() === 0 ? 1 : 0);
|
||||
const isLeap = (year % 4 === 0 && year % 100 !== 0) || (year % 400 === 0);
|
||||
|
||||
if (d > 366 || (d === 366 && !isLeap)) {
|
||||
d = 0;
|
||||
}
|
||||
}
|
||||
|
||||
this.dayInput.placeholder = (d < 10 ? '00' : (d < 100 ? '0' : '')) + d.toString();
|
||||
}
|
||||
|
||||
h = padZero(h);
|
||||
m = padZero(m);
|
||||
s = padZero(s);
|
||||
|
||||
this.timeInput.placeholder = `${h}:${m}:${s} ${tz}`;
|
||||
}
|
||||
|
||||
onTypeLinkClick(e) {
|
||||
e.preventDefault();
|
||||
|
||||
this.queryTypeLinks.forEach(link => link.classList.remove('active'));
|
||||
e.target.classList.add('active');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
getActiveQueryTypeItem() {
|
||||
const activeLink = this.queryTypeControl.querySelector('a.active');
|
||||
const activeType = activeLink.dataset.value;
|
||||
|
||||
return activeType;
|
||||
}
|
||||
|
||||
get loaderDiv() {
|
||||
return document.querySelector('.searchline').querySelector('div.text-loader');
|
||||
}
|
||||
|
||||
get timeInput() {
|
||||
return document.getElementById('clock-time');
|
||||
}
|
||||
|
||||
get dayInput() {
|
||||
return document.getElementById('clock-day');
|
||||
}
|
||||
|
||||
get queryTypeLinks() {
|
||||
return this.queryTypeControl.querySelectorAll('A');
|
||||
}
|
||||
|
||||
get queryTypeControl() {
|
||||
return document.querySelector('nav.filtersForm.search');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
import {fireEvent} from './utils/Event.js?v=2';
|
||||
|
||||
export class SequentialLoad {
|
||||
constructor(data, eventName = 'dateFilterChangedCompleted') {
|
||||
this.objects = [];
|
||||
this.eventName = eventName;
|
||||
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
data[i][1].sequential = true;
|
||||
this.objects.push(new (data[i][0])(data[i][1]));
|
||||
}
|
||||
|
||||
const me = this;
|
||||
|
||||
$(document).ready(() => {
|
||||
me.startLoaders();
|
||||
|
||||
let i = 0;
|
||||
|
||||
const onReady = () => {
|
||||
if (i >= me.objects.length) {
|
||||
window.removeEventListener(eventName, onReady);
|
||||
fireEvent('sequentialLoadCompleted');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
me.objects[i].loadData();
|
||||
i++;
|
||||
};
|
||||
|
||||
window.addEventListener(eventName, onReady);
|
||||
|
||||
onReady();
|
||||
});
|
||||
|
||||
// catching filters change
|
||||
const onFilterChanged = this.onFilterChanged.bind(this);
|
||||
window.addEventListener('searchFilterChanged', onFilterChanged, false);
|
||||
window.addEventListener('dateFilterChanged', onFilterChanged, false);
|
||||
}
|
||||
|
||||
onFilterChanged() {
|
||||
this.startLoaders();
|
||||
|
||||
let i = 0;
|
||||
|
||||
const onLoad = () => {
|
||||
if (i >= this.objects.length) {
|
||||
fireEvent('sequentialLoadCompleted');
|
||||
window.removeEventListener(this.eventName, onLoad);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.objects[i].loadData();
|
||||
i++;
|
||||
};
|
||||
|
||||
window.addEventListener(this.eventName, onLoad);
|
||||
|
||||
onLoad();
|
||||
}
|
||||
|
||||
startLoaders() {
|
||||
this.objects.forEach(item => {
|
||||
item.startLoader();
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,142 @@
|
||||
import {
|
||||
renderUserActionButtons,
|
||||
renderUserReviewedStatus,
|
||||
} from './DataRenderers.js?v=2';
|
||||
import {handleAjaxError} from './utils/ErrorHandler.js?v=2';
|
||||
import {replaceAll} from './utils/String.js?v=2';
|
||||
|
||||
export class SingleReviewButton {
|
||||
|
||||
constructor(userId) {
|
||||
this.userId = userId;
|
||||
|
||||
const me = this;
|
||||
const onButtonClick = this.onButtonClick.bind(this);
|
||||
|
||||
if (me.legitFraudButtonsBlock) {
|
||||
//Get HTML w/ new fraud&legit buttons
|
||||
let fraud = null;
|
||||
if ('true' == me.legitFraudButtonsBlock.dataset.userFraud) fraud = true;
|
||||
if ('false' == me.legitFraudButtonsBlock.dataset.userFraud) fraud = false;
|
||||
|
||||
const record = {reviewed: true, accountid: me.userId, fraud: fraud};
|
||||
|
||||
me.legitFraudButtonsBlock.replaceChildren(renderUserActionButtons(record, false));
|
||||
}
|
||||
|
||||
if (me.reviewedButton) {
|
||||
this.reviewedButton.addEventListener('click', onButtonClick, false);
|
||||
}
|
||||
|
||||
if (me.legitButton) {
|
||||
this.legitButton.addEventListener('click', onButtonClick, false);
|
||||
}
|
||||
|
||||
if (me.fraudButton) {
|
||||
this.fraudButton.addEventListener('click', onButtonClick, false);
|
||||
}
|
||||
}
|
||||
|
||||
onButtonClick(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const me = this;
|
||||
const target = e.target;
|
||||
const url = '/admin/manageUser';
|
||||
const token = document.head.querySelector('[name=\'csrf-token\'][content]').content;
|
||||
const data = {userId: this.userId, type: target.dataset.type, token: token};
|
||||
|
||||
target.classList.add('is-loading');
|
||||
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: url,
|
||||
data: data,
|
||||
scope: me,
|
||||
target: target,
|
||||
success: me.onSuccess,
|
||||
error: handleAjaxError,
|
||||
dataType: 'json'
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
onSuccess() {
|
||||
const me = this.scope;
|
||||
|
||||
const target = this.target;
|
||||
const type = target.dataset.type;
|
||||
|
||||
target.classList.remove('is-loading');
|
||||
|
||||
if ('reviewed-button' === target.id) {
|
||||
//Get HTML w/ new fraud&legit buttons
|
||||
const record = {reviewed: true, accountid: me.userId};
|
||||
|
||||
const div = target.closest('div.head-button');
|
||||
div.replaceChildren(renderUserActionButtons(record, false));
|
||||
|
||||
const onButtonClick = me.onButtonClick.bind(me);
|
||||
|
||||
if (me.legitButton) {
|
||||
me.legitButton.addEventListener('click', onButtonClick, false);
|
||||
}
|
||||
|
||||
if (me.fraudButton) {
|
||||
me.fraudButton.addEventListener('click', onButtonClick, false);
|
||||
}
|
||||
}
|
||||
|
||||
const buttonType = target.dataset.buttonType;
|
||||
if ('fraudButton' === buttonType) {
|
||||
let reviewStatus = '';
|
||||
if ('fraud' === type) {
|
||||
reviewStatus = 'Blacklisted';
|
||||
me.fraudButton.classList.replace('is-neutral', 'is-highlighted');
|
||||
me.fraudButton.setAttribute('disabled', '');
|
||||
|
||||
me.legitButton.classList.replace('is-highlighted', 'is-neutral');
|
||||
me.legitButton.removeAttribute('disabled');
|
||||
} else {
|
||||
reviewStatus = 'Whitelisted';
|
||||
me.legitButton.classList.replace('is-neutral', 'is-highlighted');
|
||||
me.legitButton.setAttribute('disabled', '');
|
||||
|
||||
me.fraudButton.classList.replace('is-highlighted', 'is-neutral');
|
||||
me.fraudButton.removeAttribute('disabled');
|
||||
}
|
||||
const tile = document.querySelector('#user-id-tile');
|
||||
const title = tile.querySelector('#review-status span').title;
|
||||
|
||||
const record = {
|
||||
fraud: (reviewStatus === 'Blacklisted'),
|
||||
latest_decision: title,
|
||||
};
|
||||
|
||||
tile.querySelector('#review-status').replaceChildren(renderUserReviewedStatus(record));
|
||||
|
||||
const userTitleSpan = document.querySelector('h1 span');
|
||||
|
||||
userTitleSpan.textContent = (reviewStatus === 'Blacklisted') ? 'X' : 'OK';
|
||||
userTitleSpan.classList.remove('high', 'medium', 'low', 'empty');
|
||||
userTitleSpan.classList.add((reviewStatus === 'Blacklisted') ? 'low' : 'high');
|
||||
}
|
||||
}
|
||||
|
||||
get legitFraudButtonsBlock() {
|
||||
return document.getElementById('legit-fraud-buttons-block');
|
||||
}
|
||||
|
||||
get fraudButton() {
|
||||
return document.querySelector('[data-type="fraud"]');
|
||||
}
|
||||
|
||||
get legitButton() {
|
||||
return document.querySelector('[data-type="legit"]');
|
||||
}
|
||||
|
||||
get reviewedButton() {
|
||||
return document.getElementById('reviewed-button');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import {Loader} from './Loader.js?v=2';
|
||||
|
||||
export class StaticTiles {
|
||||
|
||||
constructor(tilesParams) {
|
||||
const me = this;
|
||||
this.config = tilesParams;
|
||||
this.loaders = {};
|
||||
|
||||
this.config.elems.forEach(elem => {
|
||||
me.loaders[elem] = new Loader();
|
||||
});
|
||||
|
||||
const onDateFilterChanged = this.onDateFilterChanged.bind(this);
|
||||
window.addEventListener('dateFilterChanged', onDateFilterChanged, false);
|
||||
|
||||
this.runLoaders();
|
||||
}
|
||||
|
||||
onDateFilterChanged() {
|
||||
this.runLoaders();
|
||||
}
|
||||
|
||||
runLoaders() {
|
||||
for (const property in this.loaders) {
|
||||
const el = document.querySelector(`.${property} .title`);
|
||||
this.loaders[property].start(el);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
export class ThresholdsForm {
|
||||
constructor() {
|
||||
const updateReviewQueueOptions = this.updateReviewQueueOptions.bind(this);
|
||||
this.blacklistOptions.forEach(radio => {
|
||||
radio.addEventListener('change', updateReviewQueueOptions, false);
|
||||
});
|
||||
|
||||
const updateBlacklistOptions = this.updateBlacklistOptions.bind(this);
|
||||
this.reviewQueueOptions.forEach(radio => {
|
||||
radio.addEventListener('change', updateBlacklistOptions, false);
|
||||
});
|
||||
}
|
||||
|
||||
updateReviewQueueOptions(e) {
|
||||
const blacklistValue = this.blacklistVal;
|
||||
|
||||
this.reviewQueueOptions.forEach(radio => {
|
||||
const value = parseInt(radio.value, 10);
|
||||
radio.disabled = value <= blacklistValue;
|
||||
});
|
||||
}
|
||||
|
||||
updateBlacklistOptions(e) {
|
||||
const reviewValue = this.reviewQueueVal;
|
||||
|
||||
this.blacklistOptions.forEach(radio => {
|
||||
const value = parseInt(radio.value, 10);
|
||||
radio.disabled = value >= reviewValue;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
get reviewQueueOptions() {
|
||||
return document.querySelectorAll('input[name="review-queue-threshold"]');
|
||||
}
|
||||
|
||||
get blacklistOptions() {
|
||||
return document.querySelectorAll('input[name="blacklist-threshold"]');
|
||||
}
|
||||
|
||||
get reviewQueueVal() {
|
||||
return parseInt(document.querySelector('input[name="review-queue-threshold"]:checked').value || 100, 10);
|
||||
}
|
||||
|
||||
get blacklistVal() {
|
||||
return parseInt(document.querySelector('input[name="blacklist-threshold"]:checked').value || -1, 10);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
export class Tooltip {
|
||||
|
||||
static init() {
|
||||
this.addTooltipToSpans();
|
||||
this.addTooltipToParagraphs();
|
||||
this.addTooltipToTableHeaders();
|
||||
}
|
||||
|
||||
static addTooltipsToEventDetailsPanel() {
|
||||
this.baseTooltips('.details-card .tooltip', true);
|
||||
}
|
||||
|
||||
static addTooltipsToScoreDetails() {
|
||||
this.baseTooltips('.score-details-content .tooltip', true);
|
||||
}
|
||||
|
||||
static addTooltipsToTiles() {
|
||||
this.baseTooltips('span.detailsTileValue .tooltip', false);
|
||||
}
|
||||
|
||||
static addTooltipsToGridRecords(tableId) {
|
||||
this.baseTooltips(`#${tableId} td .tooltip.tooltipster-word-break`, true, true);
|
||||
this.baseTooltips(`#${tableId} td .tooltip:not(.tooltipster-word-break)`, true);
|
||||
}
|
||||
|
||||
static addTooltipToSpans() {
|
||||
this.baseTooltips('span.tooltip', true);
|
||||
}
|
||||
|
||||
static addTooltipToTableHeaders() {
|
||||
this.baseTooltips('th.tooltip', true);
|
||||
}
|
||||
|
||||
static addTooltipToParagraphs() {
|
||||
this.baseTooltips('p.tooltip', true);
|
||||
}
|
||||
|
||||
static addTooltipsToRulesProportion() {
|
||||
this.baseTooltips('td .tooltip', true);
|
||||
}
|
||||
|
||||
static addTooltipsToClock() {
|
||||
this.baseTooltips('div .day-tile.tooltip, div .time-tile.tooltip', true);
|
||||
}
|
||||
|
||||
static baseTooltips(path, useMaxWidth = true, wordBreak = false) {
|
||||
$(document.querySelectorAll(path)).tooltipster(this.getConfig(useMaxWidth, wordBreak));
|
||||
}
|
||||
|
||||
static getConfig(useMaxWidth, wordBreak) {
|
||||
const config = {
|
||||
delay: 0,
|
||||
delayTouch: 0,
|
||||
debug: false,
|
||||
side: 'bottom',
|
||||
animationDuration: 0,
|
||||
theme: ['tooltipster-borderless'],
|
||||
};
|
||||
|
||||
if (wordBreak) {
|
||||
config.theme.push('tooltipster-word-break');
|
||||
}
|
||||
|
||||
if (useMaxWidth) {
|
||||
config['maxWidth'] = 250;
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
import {
|
||||
USER_IPS_CRITICAL_VALUE,
|
||||
USER_EVENTS_CRITICAL_VALUE,
|
||||
USER_DEVICES_CRITICAL_VALUE,
|
||||
USER_COUNTRIES_CRITICAL_VALUE,
|
||||
} from './utils/Constants.js?v=2';
|
||||
|
||||
export class TotalTile {
|
||||
|
||||
constructor() {
|
||||
this.criticalValues = {
|
||||
totalIps: USER_IPS_CRITICAL_VALUE,
|
||||
totalEvents: USER_EVENTS_CRITICAL_VALUE,
|
||||
totalDevices: USER_DEVICES_CRITICAL_VALUE,
|
||||
totalCountries: USER_COUNTRIES_CRITICAL_VALUE
|
||||
};
|
||||
}
|
||||
|
||||
update(tableId, tileId, value) {
|
||||
const tileCls = this.getTileClass(tableId, tileId, value);
|
||||
const path = `.${tileId} .title`;
|
||||
const el = document.querySelector(path);
|
||||
|
||||
if (el) {
|
||||
el.classList.add('loaded');
|
||||
el.classList.remove('loading');
|
||||
|
||||
//Remove previous class if exists
|
||||
el.classList.remove('low');
|
||||
el.classList.remove('medium');
|
||||
el.classList.remove('high');
|
||||
|
||||
//Add new color class
|
||||
el.classList.add(tileCls);
|
||||
|
||||
el.textContent = value;
|
||||
}
|
||||
}
|
||||
|
||||
getTileClass(tableId, tileId, value) {
|
||||
const litmus = this.criticalValues[tileId];
|
||||
|
||||
const USER_ID = parseInt(window.location.pathname.replace('/id/', ''), 10);
|
||||
const isUserPage = () => !isNaN(USER_ID);
|
||||
|
||||
if (!litmus || !isUserPage()) return;
|
||||
|
||||
let cls = null;
|
||||
|
||||
if (value >= litmus) {
|
||||
cls = 'medium';
|
||||
}
|
||||
|
||||
return cls;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
import {renderUserActionButtons} from './DataRenderers.js?v=2';
|
||||
import {handleAjaxError} from './utils/ErrorHandler.js?v=2';
|
||||
|
||||
export class UserGridActionButtons {
|
||||
|
||||
constructor(tableId) {
|
||||
this.tableId = tableId;
|
||||
const onTableLoaded = this.onTableLoaded.bind(this);
|
||||
window.addEventListener('tableLoaded', onTableLoaded, false);
|
||||
}
|
||||
|
||||
onTableLoaded(e) {
|
||||
const tableId = e.detail.tableId;
|
||||
const buttons = document.querySelectorAll(`#${tableId} button`);
|
||||
|
||||
const onButtonClick = this.onButtonClick.bind(this);
|
||||
buttons.forEach(button => button.addEventListener('click', onButtonClick, false));
|
||||
}
|
||||
|
||||
onButtonClick(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const me = this;
|
||||
const target = e.target;
|
||||
const url = '/admin/manageUser';
|
||||
const token = document.head.querySelector('[name=\'csrf-token\'][content]').content;
|
||||
const data = {userId: target.dataset.userId, type: target.dataset.type, token: token};
|
||||
|
||||
target.classList.add('is-loading');
|
||||
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: url,
|
||||
data: data,
|
||||
scope: me,
|
||||
target: target,
|
||||
success: me.onSuccess,
|
||||
error: handleAjaxError,
|
||||
dataType: 'json'
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
onSuccessCount(data) {
|
||||
const span = document.querySelector('span.reviewed-users-tile');
|
||||
span.textContent = data.total;
|
||||
}
|
||||
|
||||
setMenuCount() {
|
||||
const token = document.head.querySelector('[name=\'csrf-token\'][content]').content;
|
||||
$.ajax({
|
||||
type: 'GET',
|
||||
url: '/admin/loadReviewQueueCount',
|
||||
data: {token: token},
|
||||
success: this.onSuccessCount,
|
||||
error: handleAjaxError,
|
||||
});
|
||||
}
|
||||
|
||||
onSuccess() {
|
||||
const me = this.scope;
|
||||
|
||||
const target = this.target;
|
||||
const type = target.dataset.type;
|
||||
const buttonType = target.dataset.buttonType;
|
||||
const accountId = target.dataset.userId;
|
||||
const tableRow = target.closest('tr');
|
||||
|
||||
target.classList.remove('is-loading');
|
||||
|
||||
const twoButtonsContainer = target.closest('.legitfraud');
|
||||
if (twoButtonsContainer && !twoButtonsContainer.hasAttribute('counterUpdated')) {
|
||||
twoButtonsContainer.setAttribute('counterUpdated', 0);
|
||||
}
|
||||
|
||||
if ('fraudButton' === buttonType) {
|
||||
const td = target.closest('td');
|
||||
const fraudButton = td.querySelector('[data-type="fraud"]');
|
||||
const legitButton = td.querySelector('[data-type="legit"]');
|
||||
|
||||
if ('fraud' === type) {
|
||||
fraudButton.classList.replace('is-neutral', 'is-highlighted');
|
||||
fraudButton.setAttribute('disabled', '');
|
||||
|
||||
legitButton.classList.replace('is-highlighted', 'is-neutral');
|
||||
legitButton.removeAttribute('disabled');
|
||||
} else {
|
||||
legitButton.classList.replace('is-neutral', 'is-highlighted');
|
||||
legitButton.setAttribute('disabled', '');
|
||||
|
||||
fraudButton.classList.replace('is-highlighted', 'is-neutral');
|
||||
fraudButton.removeAttribute('disabled');
|
||||
}
|
||||
|
||||
const counterUpdated = twoButtonsContainer.getAttribute('counterUpdated');
|
||||
const wasCounterUpdated = parseInt(counterUpdated, 10);
|
||||
if (!wasCounterUpdated) {
|
||||
const card = target.closest('.card');
|
||||
const span = card.querySelector('.card-header-title span');
|
||||
let total = parseInt(span.innerHTML, 10);
|
||||
|
||||
if (total > 0) {
|
||||
total -= 1;
|
||||
}
|
||||
|
||||
span.textContent = total;
|
||||
|
||||
twoButtonsContainer.setAttribute('counterUpdated', 1);
|
||||
}
|
||||
|
||||
if (tableRow) {
|
||||
const dataTable = $(`#${me.tableId}`).DataTable();
|
||||
dataTable.row(tableRow).remove().draw(false);
|
||||
me.setMenuCount();
|
||||
}
|
||||
}
|
||||
|
||||
if ('reviewedButton' === buttonType) {
|
||||
//Get HTML w/ new fraud&legit buttons
|
||||
const record = {reviewed: true, accountid: accountId};
|
||||
const html = renderUserActionButtons(record);
|
||||
|
||||
const td = target.closest('td');
|
||||
td.replaceChildren(html);
|
||||
|
||||
//Add event listener to newly created buttons
|
||||
const buttons = td.querySelectorAll('button');
|
||||
const onButtonClick = me.onButtonClick.bind(me);
|
||||
buttons.forEach(button => button.addEventListener('click', onButtonClick, false));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
import {renderUserActionButtons} from './DataRenderers.js?v=2';
|
||||
import {handleAjaxError} from './utils/ErrorHandler.js?v=2';
|
||||
import {replaceAll} from './utils/String.js?v=2';
|
||||
|
||||
export class WatchlistBlock {
|
||||
|
||||
constructor(userId) {
|
||||
this.userId = userId;
|
||||
|
||||
const me = this;
|
||||
const onButtonClick = this.onButtonClick.bind(this);
|
||||
|
||||
if (me.legitFraudButtonsBlock) {
|
||||
//Get HTML w/ new fraud&legit buttons
|
||||
let fraud = null;
|
||||
if ('true' == me.legitFraudButtonsBlock.dataset.userFraud) fraud = true;
|
||||
if ('false' == me.legitFraudButtonsBlock.dataset.userFraud) fraud = false;
|
||||
|
||||
const record = {reviewed: true, accountid: me.userId, fraud: fraud};
|
||||
|
||||
let html = renderUserActionButtons(record);
|
||||
html = replaceAll(html, 'is-small', '');
|
||||
|
||||
me.legitFraudButtonsBlock.innerHTML = html;
|
||||
}
|
||||
|
||||
if (me.reviewedButton) {
|
||||
this.reviewedButton.addEventListener('click', onButtonClick, false);
|
||||
}
|
||||
|
||||
if (me.legitButton) {
|
||||
this.legitButton.addEventListener('click', onButtonClick, false);
|
||||
}
|
||||
|
||||
if (me.fraudButton) {
|
||||
this.fraudButton.addEventListener('click', onButtonClick, false);
|
||||
}
|
||||
|
||||
this.watchListButton.addEventListener('click', onButtonClick, false);
|
||||
}
|
||||
|
||||
onButtonClick(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const me = this;
|
||||
const target = e.target;
|
||||
const url = `/id/${this.userId}`;
|
||||
const data = {type: target.dataset.type};
|
||||
|
||||
target.classList.add('is-loading');
|
||||
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: url,
|
||||
data: data,
|
||||
scope: me,
|
||||
target: target,
|
||||
success: me.onSuccess,
|
||||
error: handleAjaxError,
|
||||
dataType: 'json'
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
onSuccess() {
|
||||
const me = this.scope;
|
||||
|
||||
const target = this.target;
|
||||
const type = target.dataset.type;
|
||||
|
||||
target.classList.remove('is-loading');
|
||||
|
||||
if ('watchlist-button' === target.id) {
|
||||
const oldClass = ('add' === type)? 'is-warning' : 'is-primary';
|
||||
const newClass = ('add' === type)? 'is-primary' : 'is-warning';
|
||||
|
||||
target.classList.replace(oldClass, newClass);
|
||||
|
||||
target.dataset.type = ('add' === type)? 'remove' : 'add';
|
||||
target.innerText = ('add' === type)? 'Remove from watchlist':'Add to watchlist';
|
||||
}
|
||||
|
||||
if ('reviewed-button' === target.id) {
|
||||
//Get HTML w/ new fraud&legit buttons
|
||||
const record = {reviewed: true, accountid: me.userId};
|
||||
|
||||
let html = renderUserActionButtons(record);
|
||||
html = replaceAll(html, 'is-small', '');
|
||||
|
||||
const div = target.closest('div.head-button');
|
||||
div.innerHTML = html;
|
||||
|
||||
const onButtonClick = me.onButtonClick.bind(me);
|
||||
|
||||
if (me.legitButton) {
|
||||
me.legitButton.addEventListener('click', onButtonClick, false);
|
||||
}
|
||||
|
||||
if (me.fraudButton) {
|
||||
me.fraudButton.addEventListener('click', onButtonClick, false);
|
||||
}
|
||||
}
|
||||
|
||||
const buttonType = target.dataset.buttonType;
|
||||
if ('fraudButton' === buttonType) {
|
||||
if ('fraud' === type) {
|
||||
me.fraudButton.classList.replace('is-grey', 'is-warning');
|
||||
me.fraudButton.setAttribute('disabled', '');
|
||||
|
||||
me.legitButton.classList.replace('is-warning', 'is-grey');
|
||||
me.legitButton.removeAttribute('disabled');
|
||||
} else {
|
||||
me.fraudButton.classList.replace('is-warning', 'is-grey');
|
||||
me.fraudButton.removeAttribute('disabled');
|
||||
|
||||
me.legitButton.classList.replace('is-grey', 'is-warning');
|
||||
me.legitButton.setAttribute('disabled', '');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get legitFraudButtonsBlock() {
|
||||
return document.getElementById('legit-fraud-buttons-block');
|
||||
}
|
||||
|
||||
get watchListButton() {
|
||||
return document.getElementById('watchlist-button');
|
||||
}
|
||||
|
||||
get fraudButton() {
|
||||
return document.querySelector('[data-type="fraud"]');
|
||||
}
|
||||
|
||||
get legitButton() {
|
||||
return document.querySelector('[data-type="legit"]');
|
||||
}
|
||||
|
||||
get reviewedButton() {
|
||||
return document.getElementById('reviewed-button');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import {fireEvent} from './utils/Event.js?v=2';
|
||||
import {handleAjaxError} from './utils/ErrorHandler.js?v=2';
|
||||
|
||||
export class WatchlistTags {
|
||||
|
||||
constructor() {
|
||||
this.onRemoveUserTagClick = this.onRemoveUserTagClick.bind(this);
|
||||
this.onUserTagRemoveCallback = this.onUserTagRemoveCallback.bind(this);
|
||||
|
||||
this.tags.forEach(tag => tag.addEventListener('click', this.onRemoveUserTagClick, false));
|
||||
}
|
||||
|
||||
onRemoveUserTagClick(e) {
|
||||
const wrapper = e.target.closest('.control');
|
||||
const id = wrapper.querySelector('[data-id]').dataset.id;
|
||||
const data = {
|
||||
'userId': id,
|
||||
'token': this.csrf,
|
||||
};
|
||||
const me = this;
|
||||
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: '/admin/removeWatchlisted',
|
||||
data: data,
|
||||
scope: me,
|
||||
success: me.onUserTagRemoveCallback,
|
||||
error: handleAjaxError,
|
||||
dataType: 'json'
|
||||
});
|
||||
}
|
||||
|
||||
onUserTagRemoveCallback(response) {
|
||||
if (response.success) {
|
||||
const userId = response.userId;
|
||||
const tag = document.querySelector(`[data-id="${userId}"]`);
|
||||
const wrapper = tag.closest('.control');
|
||||
|
||||
wrapper.remove();
|
||||
|
||||
//TODO: fireevent reloadData();
|
||||
fireEvent('watchlistTagRemoved', {});
|
||||
}
|
||||
}
|
||||
|
||||
get tags() {
|
||||
return document.querySelectorAll('#important-users a.is-delete');
|
||||
}
|
||||
|
||||
get csrf() {
|
||||
return document.querySelector('meta[name="csrf-token"]').getAttribute('content');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
import {BaseChart} from './BaseChart.js?v=2';
|
||||
import {COLOR_MAP} from '../utils/Constants.js?v=2';
|
||||
import {formatIntTimeUtc} from '../utils/Date.js?v=2';
|
||||
import {renderChartTooltipPart} from '../DataRenderers.js?v=2';
|
||||
|
||||
export class BaseBarChart extends BaseChart {
|
||||
getSeries() {
|
||||
return [
|
||||
this.getDaySeries(),
|
||||
{
|
||||
width: -1,
|
||||
paths: uPlot.paths.bars({size: [0.6, 100]}),
|
||||
points: {show: false},
|
||||
},
|
||||
this.getSingleSeries('Regular events', 'green'),
|
||||
this.getSingleSeries('Warning events', 'yellow'),
|
||||
this.getSingleSeries('Alert events', 'red'),
|
||||
];
|
||||
}
|
||||
|
||||
getSingleSeries(label, color) {
|
||||
return {
|
||||
label: label,
|
||||
width: -1,
|
||||
drawStyle: 1,
|
||||
fill: COLOR_MAP[color].main,
|
||||
stroke: COLOR_MAP[color].main,
|
||||
paths: uPlot.paths.bars({size: [0.6, 100]}),
|
||||
points: {show: false},
|
||||
};
|
||||
}
|
||||
|
||||
// dataset adaption for bands instead of regular bars
|
||||
getData(data) {
|
||||
let stacked = [data[0]];
|
||||
let sums = new Array(data[0].length).fill(0);
|
||||
|
||||
stacked.push(new Array(data[0].length).fill(0));
|
||||
|
||||
let maxLvl = data.length - 1;
|
||||
|
||||
for (let i = 1; i < data.length; i++) {
|
||||
let series = [];
|
||||
for (let j = 0; j < data[0].length; j++) {
|
||||
sums[j] += +data[i][j];
|
||||
series.push(sums[j]);
|
||||
}
|
||||
stacked.push(series);
|
||||
}
|
||||
|
||||
maxLvl = data.length;
|
||||
|
||||
for (let i = 0; i < data[0].length; i++) {
|
||||
let topMet = false;
|
||||
for (let j = maxLvl; j > 1; j--) {
|
||||
if (stacked[j][i] <= stacked[j-1][i] && !topMet) {
|
||||
stacked[j][i] = null;
|
||||
} else {
|
||||
topMet = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.data = stacked;
|
||||
|
||||
return stacked;
|
||||
}
|
||||
|
||||
stack(data, omit) {
|
||||
let data2 = [];
|
||||
let bands = [];
|
||||
let d0Len = data ? data[0].length : 0;
|
||||
let accum = Array(d0Len);
|
||||
|
||||
let i;
|
||||
|
||||
for (i = 0; i < d0Len; i++) {
|
||||
accum[i] = 0;
|
||||
}
|
||||
|
||||
let el;
|
||||
|
||||
for (i = 1; i < data.length; i++) {
|
||||
el = data[i];
|
||||
|
||||
if (!omit(i)) {
|
||||
el = el.map((v, j) => {
|
||||
let val = accum[j] + +v;
|
||||
accum[j] = val;
|
||||
|
||||
return val;
|
||||
});
|
||||
}
|
||||
|
||||
data2.push(el);
|
||||
}
|
||||
|
||||
for (i = 1; i < data.length; i++) {
|
||||
!omit(i) && bands.push({
|
||||
series: [
|
||||
data.findIndex((s, j) => j > i && !omit(j)),
|
||||
i,
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
bands = bands.filter(b => b.series[1] > -1);
|
||||
|
||||
return {
|
||||
data: [data[0]].concat(data2),
|
||||
bands,
|
||||
};
|
||||
}
|
||||
|
||||
getOptions(resolution = 'day', nullChar = '0') {
|
||||
const opts = super.getOptions(resolution, nullChar);
|
||||
|
||||
let stacked = this.stack(this.data, i => false);
|
||||
|
||||
opts.bands = stacked.bands;
|
||||
|
||||
opts.series.forEach((s, sIdx) => {
|
||||
if (s) {
|
||||
s.value = (u, v, si, i) => u.data[sIdx][i];
|
||||
|
||||
s.points = s.points || {};
|
||||
s.points.filter = (u, seriesIdx, show, gaps) => {
|
||||
if (show) {
|
||||
let pts = [];
|
||||
u.data[seriesIdx].forEach((v, i) => {
|
||||
v != null && pts.push(i);
|
||||
});
|
||||
return pts;
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
return opts;
|
||||
}
|
||||
|
||||
tooltipCursor(u, seriestt, opts, resolution, defaultVal) {
|
||||
const left = u.cursor.left;
|
||||
const idx = u.cursor.idx;
|
||||
const col = [];
|
||||
|
||||
if (opts && opts.cursorMemo) {
|
||||
opts.cursorMemo.set(left, top);
|
||||
}
|
||||
|
||||
seriestt.style.display = 'none';
|
||||
|
||||
if (left >= 0 && u.data) {
|
||||
let xVal = u.data[0][idx];
|
||||
|
||||
let maxLvl = u.data.length - 1;
|
||||
|
||||
for (let i = 0; i <= maxLvl; i++) {
|
||||
col.push(u.data[i][idx]);
|
||||
}
|
||||
|
||||
const vtp = (resolution === 'day') ? 'DAY' : ((resolution === 'hour') ? 'HOUR' : 'MINUTE');
|
||||
let ts = '';
|
||||
|
||||
if (Number.isInteger(xVal)) {
|
||||
const useTime = resolution === 'hour' || resolution === 'minute';
|
||||
ts = formatIntTimeUtc(xVal * 1000, useTime);
|
||||
}
|
||||
|
||||
let frag = document.createDocumentFragment();
|
||||
frag.appendChild(document.createTextNode(ts.replace(/\./g, '/')));
|
||||
|
||||
let prev = null;
|
||||
|
||||
let maxVal = 0;
|
||||
let maxIdx = 0;
|
||||
|
||||
for (let i = maxLvl; i >= 1; i--) {
|
||||
if (col[i] === null) {
|
||||
col[i] = 0;
|
||||
} else {
|
||||
if (maxVal < col[i]) {
|
||||
maxVal = col[i];
|
||||
maxIdx = i;
|
||||
}
|
||||
|
||||
if (prev !== null) {
|
||||
col[prev] -= col[i];
|
||||
}
|
||||
|
||||
prev = i;
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 2; i <= maxLvl; i++) {
|
||||
frag = this.extendTooltipFragment(i, null, col, defaultVal, u, frag);
|
||||
}
|
||||
|
||||
if (frag.children.length > 1) {
|
||||
seriestt.replaceChildren(frag);
|
||||
|
||||
seriestt.style.top = Math.round(u.valToPos(maxVal, u.series[maxIdx].scale)) + 'px';
|
||||
seriestt.style.left = Math.round(u.valToPos(xVal, vtp)) + 'px';
|
||||
seriestt.style.display = null;
|
||||
}
|
||||
}
|
||||
|
||||
return [seriestt, opts];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,380 @@
|
||||
import {Loader} from '../Loader.js?v=2';
|
||||
import {getQueryParams} from '../utils/DataSource.js?v=2';
|
||||
import {handleAjaxError} from '../utils/ErrorHandler.js?v=2';
|
||||
import {formatIntTimeUtc} from '../utils/Date.js?v=2';
|
||||
import {fireEvent} from '../utils/Event.js?v=2';
|
||||
import {renderChartTooltipPart} from '../DataRenderers.js?v=2';
|
||||
import {
|
||||
MAX_HOURS_CHART,
|
||||
MIN_HOURS_CHART,
|
||||
X_AXIS_SERIFS,
|
||||
} from '../utils/Constants.js?v=2';
|
||||
|
||||
export class BaseChart {
|
||||
constructor(chartParams) {
|
||||
this.config = chartParams;
|
||||
|
||||
this.cursLeft = -10;
|
||||
this.cursTop = -10;
|
||||
this.cursorMemo = {
|
||||
set: (left, top) => {
|
||||
this.cursLeft = left;
|
||||
this.cursTop = top;
|
||||
},
|
||||
get: () => ({
|
||||
left: this.cursLeft,
|
||||
top: this.cursTop,
|
||||
y: false,
|
||||
drag: {
|
||||
x: false,
|
||||
y: false
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
this.timeLabelColor = '#d7e6e1';
|
||||
|
||||
this.loader = new Loader();
|
||||
|
||||
const loaderDiv = document.createElement('div');
|
||||
loaderDiv.id = 'loader';
|
||||
this.chartBlock.appendChild(loaderDiv);
|
||||
|
||||
this.chart = null;
|
||||
if (!this.config.sequential) {
|
||||
this.loadData();
|
||||
}
|
||||
|
||||
const onDateFilterChanged = this.onDateFilterChanged.bind(this);
|
||||
window.addEventListener('dateFilterChanged', onDateFilterChanged, false);
|
||||
}
|
||||
|
||||
onDateFilterChanged() {
|
||||
this.loadData();
|
||||
}
|
||||
|
||||
stopAnimation() {
|
||||
this.loaderBlock.classList.add('is-hidden');
|
||||
this.loader.stop();
|
||||
}
|
||||
|
||||
startLoader() {
|
||||
const el = document.createElement('p');
|
||||
el.className = 'text-loader';
|
||||
|
||||
this.loaderBlock.classList.remove('is-hidden');
|
||||
this.loaderBlock.replaceChildren(el);
|
||||
|
||||
const p = this.loaderBlock.querySelector('p');
|
||||
|
||||
this.loader.start(p);
|
||||
}
|
||||
|
||||
loadData() {
|
||||
if (!this.config.sequential) {
|
||||
this.startLoader();
|
||||
}
|
||||
|
||||
const token = document.head.querySelector('[name=\'csrf-token\'][content]').content;
|
||||
const params = this.config.getParams();
|
||||
const data = getQueryParams(params);
|
||||
|
||||
data['mode'] = params.mode;
|
||||
data['token'] = token;
|
||||
data['resolution'] = 'day';
|
||||
if (data['dateFrom']) {
|
||||
const diff = new Date(data['dateTo']) - new Date(data['dateFrom']);
|
||||
const hours = diff/(60 * 60 * 1000);
|
||||
if (hours <= MAX_HOURS_CHART && hours > MIN_HOURS_CHART) {
|
||||
data['resolution'] = 'hour';
|
||||
} else if (hours <= MIN_HOURS_CHART) {
|
||||
data['resolution'] = 'minute';
|
||||
}
|
||||
}
|
||||
|
||||
fireEvent('dateFilterChangedCaught');
|
||||
|
||||
$.ajax({
|
||||
url: '/admin/loadChart',
|
||||
type: 'get',
|
||||
data: data,
|
||||
success: (responseData, status) => this.onChartLoaded(responseData, status, data['resolution']),
|
||||
error: handleAjaxError,
|
||||
complete: function() {
|
||||
fireEvent('dateFilterChangedCompleted');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
onChartLoaded(data, status, resolution) {
|
||||
if ('success' == status) {
|
||||
if (this.chart) {
|
||||
this.chart.destroy();
|
||||
}
|
||||
const prepData = this.getData(data);
|
||||
this.chart = new uPlot(this.getOptions(resolution), prepData, this.chartBlock);
|
||||
|
||||
this.stopAnimation();
|
||||
}
|
||||
}
|
||||
|
||||
seriesResolutionShift(series, resolution) {
|
||||
if (resolution === 'hour') {
|
||||
series[0].label = 'Hour';
|
||||
series[0].scale = 'HOUR';
|
||||
series[0].value = '{YYYY}-{MM}-{DD} {HH}:{mm}';
|
||||
} else if (resolution === 'minute') {
|
||||
series[0].label = 'Minute';
|
||||
series[0].scale = 'MINUTE';
|
||||
series[0].value = '{YYYY}-{MM}-{DD} {HH}:{mm}';
|
||||
}
|
||||
|
||||
return series;
|
||||
}
|
||||
|
||||
getDaySeries() {
|
||||
return {
|
||||
label: 'Day',
|
||||
scale: 'DAY',
|
||||
value: '{YYYY}-{MM}-{DD}'
|
||||
};
|
||||
}
|
||||
|
||||
getAxisConfig() {
|
||||
const xAxis = {
|
||||
scale: 'DAY',
|
||||
stroke: '#8180a0',
|
||||
grid: {
|
||||
width: 1 / devicePixelRatio,
|
||||
stroke: '#2b2a3d',
|
||||
},
|
||||
ticks: {
|
||||
width: 1 / devicePixelRatio,
|
||||
stroke: '#2b2a3d',
|
||||
},
|
||||
values: [
|
||||
//Copied from https://github.com/leeoniya/uPlot/tree/master/docs#axis--grid-opts
|
||||
// tick incr default year month day hour min sec mode
|
||||
[3600 * 24, '{DD}/{MM}', '\n{YYYY}', null, null, null, null, null, 1],
|
||||
],
|
||||
};
|
||||
const yAxis = {
|
||||
stroke: '#8180a0',
|
||||
values: (u, vals, space) => vals.map(v => this.formatKiloValue(u, v)),
|
||||
grid: {
|
||||
width: 1 / devicePixelRatio,
|
||||
stroke: '#2b2a3d',
|
||||
},
|
||||
ticks: {
|
||||
width: 1 / devicePixelRatio,
|
||||
stroke: '#2b2a3d',
|
||||
},
|
||||
};
|
||||
|
||||
return {
|
||||
x: xAxis,
|
||||
y: yAxis,
|
||||
};
|
||||
}
|
||||
|
||||
getData(data) {
|
||||
return data;
|
||||
}
|
||||
|
||||
getOptions(resolution = 'day', nullChar = '0') {
|
||||
const tooltipsPlugin = this.tooltipsPlugin({cursorMemo: this.cursorMemo}, resolution, nullChar);
|
||||
const axes = this.getAxisConfig();
|
||||
const series = this.seriesResolutionShift(this.getSeries(), resolution);
|
||||
const xAxis = this.xAxisResolutionShift(axes.x, resolution);
|
||||
const yAxis = axes.y;
|
||||
|
||||
const opts = {
|
||||
width: 995,
|
||||
height: 200,
|
||||
|
||||
tzDate: ts => uPlot.tzDate(new Date(ts * 1000), 'Etc/UTC'),
|
||||
series: series,
|
||||
|
||||
legend: {
|
||||
show: false
|
||||
},
|
||||
cursor: this.cursorMemo.get(),
|
||||
plugins: [tooltipsPlugin],
|
||||
scales: {
|
||||
x: {time: false},
|
||||
},
|
||||
axes: [
|
||||
xAxis,
|
||||
yAxis,
|
||||
]
|
||||
};
|
||||
|
||||
return opts;
|
||||
}
|
||||
|
||||
xAxisResolutionShift(xAxis, resolution) {
|
||||
if (resolution === 'hour') {
|
||||
xAxis.scale = 'HOUR';
|
||||
xAxis.values = [
|
||||
// tick incr default year month day hour min sec mode
|
||||
[3600, '{HH}:{mm}', '\n{DD}/{MM}/{YYYY}', null, '\n{DD}/{MM}', null, null, null, 1]
|
||||
];
|
||||
xAxis.space = function(self, axisIdx, scaleMin, scaleMax, plotDim) {
|
||||
let rangeHours = (scaleMax - scaleMin) / 3600;
|
||||
if (rangeHours > X_AXIS_SERIFS) rangeHours = X_AXIS_SERIFS;
|
||||
const pxPerHour = plotDim / rangeHours;
|
||||
|
||||
return pxPerHour;
|
||||
};
|
||||
} else if (resolution === 'minute') {
|
||||
xAxis.scale = 'MINUTE';
|
||||
xAxis.values = [
|
||||
// tick incr default year month day hour min sec mode
|
||||
[60, '{HH}:{mm}', '\n{DD}/{MM}/{YYYY}', null, '\n{DD}/{MM}', null, null, null, 1]
|
||||
];
|
||||
xAxis.space = function(self, axisIdx, scaleMin, scaleMax, plotDim) {
|
||||
let rangeMinutes = (scaleMax - scaleMin) / 60;
|
||||
if (rangeMinutes > X_AXIS_SERIFS) rangeMinutes = X_AXIS_SERIFS;
|
||||
const pxPerMinute = plotDim / rangeMinutes;
|
||||
|
||||
return pxPerMinute;
|
||||
};
|
||||
}
|
||||
|
||||
return xAxis;
|
||||
}
|
||||
|
||||
formatKiloValue(u, value) {
|
||||
if (value === 0) {
|
||||
return value;
|
||||
}
|
||||
if (value % 1000000 === 0) {
|
||||
return Math.round(value / 1000000) + 'M';
|
||||
}
|
||||
if (value % 1000 === 0) {
|
||||
return Math.round(value / 1000) + 'k';
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
get loaderBlock() {
|
||||
return document.getElementById('loader');
|
||||
}
|
||||
|
||||
get chartBlock() {
|
||||
return document.querySelector('.stat-chart:not(#session-stat)');
|
||||
}
|
||||
|
||||
tooltipsPlugin(opts, resolution = 'day', defaultVal = '0') {
|
||||
let self = this;
|
||||
let seriestt;
|
||||
|
||||
function init(u, options, data) {
|
||||
seriestt = self.tooltipInit(u, options, data);
|
||||
}
|
||||
|
||||
function setCursor(u) {
|
||||
[seriestt, opts] = self.tooltipCursor(u, seriestt, opts, resolution, defaultVal);
|
||||
}
|
||||
|
||||
return {
|
||||
hooks: {
|
||||
init,
|
||||
setCursor,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
tooltipCursor(u, seriestt, opts, resolution, defaultVal) {
|
||||
const left = u.cursor.left;
|
||||
const idx = u.cursor.idx;
|
||||
|
||||
if (opts && opts.cursorMemo) {
|
||||
opts.cursorMemo.set(left, top);
|
||||
}
|
||||
|
||||
seriestt.style.display = 'none';
|
||||
|
||||
if (left >= 0) {
|
||||
let xVal = u.data[0][idx];
|
||||
|
||||
const vtp = (resolution === 'day') ? 'DAY' : ((resolution === 'hour') ? 'HOUR' : 'MINUTE');
|
||||
let ts = '';
|
||||
|
||||
if (Number.isInteger(xVal)) {
|
||||
const useTime = resolution === 'hour' || resolution === 'minute';
|
||||
ts = formatIntTimeUtc(xVal * 1000, useTime);
|
||||
}
|
||||
|
||||
let frag = document.createDocumentFragment();
|
||||
frag.appendChild(renderChartTooltipPart(this.timeLabelColor, null, ts.replace(/\./g, '/')));
|
||||
|
||||
for (let i = 1; i <= 12; i++) {
|
||||
frag = this.extendTooltipFragment(i, idx, u.data, defaultVal, u, frag);
|
||||
}
|
||||
|
||||
if (frag.children.length > 1) {
|
||||
seriestt.replaceChildren(frag);
|
||||
|
||||
let val = null;
|
||||
let lvl = 1;
|
||||
|
||||
const lim = Math.min(u.data.length - 1, 12);
|
||||
|
||||
for (let i = 1; i <= lim; i++) {
|
||||
if (u.data[i][idx] > val) {
|
||||
val = u.data[i][idx];
|
||||
lvl = i;
|
||||
}
|
||||
}
|
||||
|
||||
val = (val !== null && val != undefined) ? val : defaultVal;
|
||||
|
||||
seriestt.style.top = Math.round(u.valToPos(val, u.series[lvl].scale)) + 'px';
|
||||
seriestt.style.left = Math.round(u.valToPos(xVal, vtp)) + 'px';
|
||||
seriestt.style.display = null;
|
||||
}
|
||||
}
|
||||
|
||||
return [seriestt, opts];
|
||||
}
|
||||
|
||||
tooltipInit(u, options, data) {
|
||||
let over = u.over;
|
||||
|
||||
let tt = document.createElement('div');
|
||||
tt.className = 'tooltipline';
|
||||
tt.textContent = '';
|
||||
tt.style.pointerEvents = 'none';
|
||||
tt.style.position = 'absolute';
|
||||
tt.style.background = 'rgba(0,0,0,1)';
|
||||
over.appendChild(tt);
|
||||
|
||||
over.addEventListener('mouseleave', () => {
|
||||
if (!u.cursor._lock) {
|
||||
tt.style.display = 'none';
|
||||
}
|
||||
});
|
||||
|
||||
over.addEventListener('mouseenter', () => {
|
||||
tt.style.display = u.data.length > 1 ? null : 'none';
|
||||
});
|
||||
|
||||
tt.style.display = (u.cursor.left < 0) ? 'none' : null;
|
||||
|
||||
return tt;
|
||||
}
|
||||
|
||||
extendTooltipFragment(lvl, idx, data, defaultVal, u, frag) {
|
||||
if (data.length > lvl) {
|
||||
let series = u.series[lvl];
|
||||
let val = (idx !== null) ? data[lvl][idx] : data[lvl];
|
||||
val = (val !== null && val != undefined) ? val : defaultVal;
|
||||
|
||||
frag.appendChild(document.createElement('br'));
|
||||
frag.appendChild(renderChartTooltipPart(series.stroke(), series.label, val));
|
||||
}
|
||||
|
||||
return frag;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
import {BaseChart} from './BaseChart.js?v=2';
|
||||
import {
|
||||
COLOR_MAP,
|
||||
X_AXIS_SERIFS,
|
||||
} from '../utils/Constants.js?v=2';
|
||||
|
||||
export class BaseLineChart extends BaseChart {
|
||||
getSeries() {
|
||||
return [
|
||||
this.getDaySeries(),
|
||||
this.getSingleSeries('Total events', 'green'),
|
||||
];
|
||||
}
|
||||
|
||||
getSingleSeries(label, color) {
|
||||
return {
|
||||
label: label,
|
||||
scale: 'EVENTS',
|
||||
value: (u, v) => Number(v.toFixed(0)).toLocaleString(),
|
||||
points: {
|
||||
space: 0,
|
||||
fill: COLOR_MAP[color].main,
|
||||
},
|
||||
stroke: COLOR_MAP[color].main,
|
||||
fill: COLOR_MAP[color].light,
|
||||
};
|
||||
}
|
||||
|
||||
getAxisConfig() {
|
||||
const axes = super.getAxisConfig();
|
||||
|
||||
axes.x.space = function(self, axisIdx, scaleMin, scaleMax, plotDim) {
|
||||
let rangeDays = (scaleMax - scaleMin) / 86400;
|
||||
if (rangeDays > X_AXIS_SERIFS) rangeDays = X_AXIS_SERIFS;
|
||||
const pxPerDay = plotDim / rangeDays;
|
||||
|
||||
return pxPerDay;
|
||||
};
|
||||
|
||||
axes.y.scale = 'EVENTS';
|
||||
axes.y.side = 3;
|
||||
axes.y.split = u => [
|
||||
u.series[1].min,
|
||||
u.series[1].max,
|
||||
];
|
||||
|
||||
return axes;
|
||||
}
|
||||
|
||||
getOptions(resolution = 'day') {
|
||||
return super.getOptions(resolution, '—');
|
||||
}
|
||||
|
||||
// invert lines order to keep originally first line on top layer
|
||||
seriesResolutionShift(series, resolution) {
|
||||
if (resolution === 'hour') {
|
||||
series[0].label = 'Hour';
|
||||
series[0].scale = 'HOUR';
|
||||
series[0].value = '{YYYY}-{MM}-{DD} {HH}:{mm}';
|
||||
} else if (resolution === 'minute') {
|
||||
series[0].label = 'Minute';
|
||||
series[0].scale = 'MINUTE';
|
||||
series[0].value = '{YYYY}-{MM}-{DD} {HH}:{mm}';
|
||||
}
|
||||
|
||||
const inverted = [series[0]].concat(series.slice(1).reverse());
|
||||
|
||||
return inverted;
|
||||
}
|
||||
|
||||
getData(data) {
|
||||
return [data[0]].concat(data.slice(1).reverse());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
import {Loader} from '../Loader.js?v=2';
|
||||
import {BaseChart} from './BaseChart.js?v=2';
|
||||
import {
|
||||
COLOR_LIGHT_GREEN,
|
||||
COLOR_GREEN,
|
||||
X_AXIS_SERIFS,
|
||||
} from '../utils/Constants.js?v=2';
|
||||
|
||||
export class BaseSparklineChart extends BaseChart {
|
||||
constructor(chartParams) {
|
||||
super(chartParams);
|
||||
|
||||
this.charts = null;
|
||||
|
||||
if (!this.loaders) {
|
||||
this.loaders = [];
|
||||
this.elems.forEach(el => {this.loaders[el] = new Loader();});
|
||||
}
|
||||
}
|
||||
|
||||
getOptions() {
|
||||
const tooltipsPlugin = this.tooltipsPlugin({cursorMemo: this.cursorMemo}, 'day', '0');
|
||||
return {
|
||||
width: 200,
|
||||
height: 30,
|
||||
pxAlign: false,
|
||||
cursor: {
|
||||
show: false
|
||||
},
|
||||
select: {
|
||||
show: false,
|
||||
},
|
||||
legend: {
|
||||
show: false,
|
||||
},
|
||||
scales: {
|
||||
x: {time: false},
|
||||
},
|
||||
axes: [
|
||||
{show: false},
|
||||
{show: false}
|
||||
],
|
||||
cursor: this.cursorMemo.get(),
|
||||
plugins: [tooltipsPlugin],
|
||||
series: [
|
||||
{
|
||||
label: 'Day',
|
||||
scale: 'DAY',
|
||||
value: '{YYYY}-{MM}-{DD}',
|
||||
stroke: '#8180a0',
|
||||
},
|
||||
{
|
||||
label: 'This week',
|
||||
stroke: COLOR_GREEN,
|
||||
fill: COLOR_LIGHT_GREEN,
|
||||
points: {show: false}
|
||||
},
|
||||
{
|
||||
label: 'Previous week',
|
||||
stroke: 'rgba(129,128,160,0.7)',
|
||||
fill: 'rgba(129,128,160,0.03)',
|
||||
points: {show: false}
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
onChartLoaded(data, status, resolution) {
|
||||
if ('success' == status) {
|
||||
data = this.getData(data);
|
||||
|
||||
this.stopLoader();
|
||||
|
||||
this.charts = [];
|
||||
|
||||
this.elems.forEach(el => {
|
||||
const lines = [data.time, data[el], data[el + 'Prev']];
|
||||
this.charts.push(new uPlot(this.getOptions(), lines, this.getChartBlock(el)));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
startLoader() {
|
||||
if (!this.loaders) {
|
||||
this.loaders = [];
|
||||
this.elems.forEach(el => this.loaders[el] = new Loader());
|
||||
}
|
||||
|
||||
this.elems.forEach(name => {
|
||||
const el = document.createElement('p');
|
||||
const block = this.getChartBlock(name);
|
||||
|
||||
block.classList.remove('is-hidden');
|
||||
block.replaceChildren(el);
|
||||
|
||||
const p = block.querySelector('p');
|
||||
|
||||
this.loaders[name].start(p);
|
||||
});
|
||||
}
|
||||
|
||||
stopLoader() {
|
||||
this.elems.forEach(el => {
|
||||
this.loaders[el].stop();
|
||||
this.getChartBlock(el).querySelector('p').classList.add('is-hidden');
|
||||
});
|
||||
}
|
||||
|
||||
getData(data) {
|
||||
return {
|
||||
'time': data[0],
|
||||
'totalDevices': data[1],
|
||||
'totalIps': data[2],
|
||||
'totalSessions': data[3],
|
||||
'totalEvents': data[4],
|
||||
'totalDevicesPrev': data[5],
|
||||
'totalIpsPrev': data[6],
|
||||
'totalSessionsPrev': data[7],
|
||||
'totalEventsPrev': data[8],
|
||||
};
|
||||
}
|
||||
|
||||
getChartBlock(cls) {
|
||||
return document.querySelector(`td.${cls} p.session-stat`);
|
||||
}
|
||||
|
||||
get chartBlocks() {
|
||||
const result = {};
|
||||
this.elems.forEach(el => {result[el] = this.getChartBlock(el);});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
get elems() {
|
||||
return ['totalDevices', 'totalIps', 'totalSessions', 'totalEvents'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import {BaseLineChart} from './BaseLine.js?v=2';
|
||||
|
||||
export class BlacklistChart extends BaseLineChart {
|
||||
getSeries() {
|
||||
return [
|
||||
this.getDaySeries(),
|
||||
this.getSingleSeries('Blacklisted identities', 'red'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import {BaseLineChart} from './BaseLine.js?v=2';
|
||||
|
||||
export class BotsChart extends BaseLineChart {
|
||||
getSeries() {
|
||||
return [
|
||||
this.getDaySeries(),
|
||||
this.getSingleSeries('Bots', 'red'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import {BaseLineChart} from './BaseLine.js?v=2';
|
||||
|
||||
export class DomainsChart extends BaseLineChart {
|
||||
getSeries() {
|
||||
return [
|
||||
this.getDaySeries(),
|
||||
this.getSingleSeries('Total domains', 'green'),
|
||||
this.getSingleSeries('New domains', 'yellow'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import {BaseLineChart} from './BaseLine.js?v=2';
|
||||
|
||||
export class EventsChart extends BaseLineChart {
|
||||
getSeries() {
|
||||
return [
|
||||
this.getDaySeries(),
|
||||
this.getSingleSeries('Regular events', 'green'),
|
||||
this.getSingleSeries('Warning events', 'yellow'),
|
||||
this.getSingleSeries('Alert events', 'red'),
|
||||
this.getSingleSeries('Unauthenticated events', 'purple'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import {BaseLineChart} from './BaseLine.js?v=2';
|
||||
|
||||
export class IpsChart extends BaseLineChart {
|
||||
getSeries() {
|
||||
return [
|
||||
this.getDaySeries(),
|
||||
this.getSingleSeries('Residential', 'green'),
|
||||
this.getSingleSeries('Privacy', 'yellow'),
|
||||
this.getSingleSeries('Suspicious', 'red'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import {BaseLineChart} from './BaseLine.js?v=2';
|
||||
|
||||
export class IspsChart extends BaseLineChart {
|
||||
getSeries() {
|
||||
return [
|
||||
this.getDaySeries(),
|
||||
this.getSingleSeries('Total ISPs', 'green'),
|
||||
this.getSingleSeries('New ISPs', 'yellow'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import {BaseLineChart} from './BaseLine.js?v=2';
|
||||
|
||||
export class LogbookChart extends BaseLineChart {
|
||||
getSeries() {
|
||||
return [
|
||||
this.getDaySeries(),
|
||||
this.getSingleSeries('Success', 'green'),
|
||||
this.getSingleSeries('Validation issues', 'yellow'),
|
||||
this.getSingleSeries('Failed', 'red'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import {BaseLineChart} from './BaseLine.js?v=2';
|
||||
|
||||
export class ResourcesChart extends BaseLineChart {
|
||||
getSeries() {
|
||||
return [
|
||||
this.getDaySeries(),
|
||||
this.getSingleSeries('200', 'green'),
|
||||
this.getSingleSeries('404', 'yellow'),
|
||||
this.getSingleSeries('403 & 500', 'red'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import {BaseLineChart} from './BaseLine.js?v=2';
|
||||
|
||||
export class ReviewQueueChart extends BaseLineChart {
|
||||
getSeries() {
|
||||
return [
|
||||
this.getDaySeries(),
|
||||
this.getSingleSeries('Whitelisted', 'green'),
|
||||
this.getSingleSeries('In review', 'yellow'),
|
||||
this.getSingleSeries('Blacklisted', 'red'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import {BaseLineChart} from './BaseLine.js?v=2';
|
||||
|
||||
export class UsersChart extends BaseLineChart {
|
||||
getSeries() {
|
||||
return [
|
||||
this.getDaySeries(),
|
||||
this.getSingleSeries('High trust', 'green'),
|
||||
this.getSingleSeries('Average trust', 'yellow'),
|
||||
this.getSingleSeries('In review', 'red'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import {fireEvent} from '../utils/Event.js?v=2';
|
||||
|
||||
export class BaseFilter {
|
||||
constructor(selectorId, renderItemFn, renderChoiceFn, eventType) {
|
||||
this.selectorId = selectorId;
|
||||
this.renderItemFn = renderItemFn;
|
||||
this.renderChoiceFn = renderChoiceFn;
|
||||
this.eventType = eventType;
|
||||
|
||||
const renderItem = renderItemFn;
|
||||
const renderChoice = renderChoiceFn;
|
||||
|
||||
const choices = new Choices(`${this.selectorId} select`, {
|
||||
removeItemButton: true,
|
||||
allowHTML: true,
|
||||
callbackOnCreateTemplates: function(strToEl) {
|
||||
const {classNames, itemSelectText} = this.config;
|
||||
return {
|
||||
item: function({classNames}, data) {
|
||||
return strToEl(renderItem(classNames, data));
|
||||
},
|
||||
choice: function({classNames}, data) {
|
||||
return strToEl(renderChoice(classNames, data, itemSelectText));
|
||||
},
|
||||
};
|
||||
}
|
||||
});
|
||||
choices.passedElement.element.addEventListener(
|
||||
'change',
|
||||
() => fireEvent(this.eventType)
|
||||
);
|
||||
}
|
||||
|
||||
getValues() {
|
||||
return Array.from(document.querySelector(`${this.selectorId} select`).options)
|
||||
.filter(option => option.selected)
|
||||
.map(option => option.value);
|
||||
}
|
||||
|
||||
getEventType() {
|
||||
return this.eventType;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import {BaseFilter} from './BaseFilter.js?v=2';
|
||||
import {
|
||||
renderDeviceTypeSelectorItem,
|
||||
renderDeviceTypeSelectorChoice,
|
||||
} from '../DataRenderers.js?v=2';
|
||||
|
||||
export class DeviceTypeFilter extends BaseFilter {
|
||||
constructor() {
|
||||
super(
|
||||
'#device-type-selectors',
|
||||
renderDeviceTypeSelectorItem,
|
||||
renderDeviceTypeSelectorChoice,
|
||||
'deviceTypeFilterChanged'
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import {BaseFilter} from './BaseFilter.js?v=2';
|
||||
import {
|
||||
renderEntityTypeSelectorItem,
|
||||
renderEntityTypeSelectorChoice,
|
||||
} from '../DataRenderers.js?v=2';
|
||||
|
||||
export class EntityTypeFilter extends BaseFilter {
|
||||
constructor() {
|
||||
super(
|
||||
'#entity-type-selectors',
|
||||
renderEntityTypeSelectorItem,
|
||||
renderEntityTypeSelectorChoice,
|
||||
'entityTypeFilterChanged'
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import {BaseFilter} from './BaseFilter.js?v=2';
|
||||
import {
|
||||
renderEventTypeSelectorItem,
|
||||
renderEventTypeSelectorChoice,
|
||||
} from '../DataRenderers.js?v=2';
|
||||
|
||||
export class EventTypeFilter extends BaseFilter {
|
||||
constructor() {
|
||||
super(
|
||||
'#event-type-selectors',
|
||||
renderEventTypeSelectorItem,
|
||||
renderEventTypeSelectorChoice,
|
||||
'eventTypeFilterChanged'
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import {BaseFilter} from './BaseFilter.js?v=2';
|
||||
import {
|
||||
renderIpTypeSelectorItem,
|
||||
renderIpTypeSelectorChoice,
|
||||
} from '../DataRenderers.js?v=2';
|
||||
|
||||
export class IpTypeFilter extends BaseFilter {
|
||||
constructor() {
|
||||
super(
|
||||
'#ip-type-selectors',
|
||||
renderIpTypeSelectorItem,
|
||||
renderIpTypeSelectorChoice,
|
||||
'ipTypeFilterChanged'
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import {BaseFilter} from './BaseFilter.js?v=2';
|
||||
import {
|
||||
renderRuleSelectorItem,
|
||||
renderRuleSelectorChoice,
|
||||
} from '../DataRenderers.js?v=2';
|
||||
|
||||
export class RulesFilter extends BaseFilter {
|
||||
constructor() {
|
||||
super(
|
||||
'#rule-selectors',
|
||||
renderRuleSelectorItem,
|
||||
renderRuleSelectorChoice,
|
||||
'rulesFilterChanged'
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import {BaseFilter} from './BaseFilter.js?v=2';
|
||||
import {
|
||||
renderScoresRangeSelectorItem,
|
||||
renderScoresRangeSelectorChoice,
|
||||
} from '../DataRenderers.js?v=2';
|
||||
|
||||
export class ScoresRangeFilter extends BaseFilter {
|
||||
constructor() {
|
||||
super(
|
||||
'#scores-range-selectors',
|
||||
renderScoresRangeSelectorItem,
|
||||
renderScoresRangeSelectorChoice,
|
||||
'scoresRangeFilterChanged'
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
import {Loader} from '../Loader.js?v=2';
|
||||
import {Tooltip} from '../Tooltip.js?v=2';
|
||||
import {handleAjaxError} from '../utils/ErrorHandler.js?v=2';
|
||||
import {fireEvent} from '../utils/Event.js?v=2';
|
||||
|
||||
export class BaseTiles {
|
||||
|
||||
constructor(tilesParams) {
|
||||
const me = this;
|
||||
this.config = tilesParams;
|
||||
this.loaders = {};
|
||||
|
||||
this.elems.forEach(elem => {
|
||||
me.loaders[elem] = new Loader();
|
||||
});
|
||||
|
||||
if (!this.config.sequential) {
|
||||
this.loadData();
|
||||
}
|
||||
}
|
||||
|
||||
loadData() {
|
||||
const me = this;
|
||||
const url = this.url;
|
||||
const params = this.config.getParams();
|
||||
const token = document.head.querySelector('[name=\'csrf-token\'][content]').content;
|
||||
|
||||
if (!this.config.sequential) {
|
||||
this.startLoaders();
|
||||
}
|
||||
|
||||
fireEvent('dateFilterChangedCaught');
|
||||
|
||||
$.ajax({
|
||||
url: `${url}?token=${token}`,
|
||||
type: 'get',
|
||||
scope: me,
|
||||
data: params,
|
||||
success: function(response) {
|
||||
me.onLoad(response, 'success');
|
||||
me.initTooltips();
|
||||
},
|
||||
error: handleAjaxError,
|
||||
});
|
||||
}
|
||||
|
||||
stopLoaders() {
|
||||
for (const property in this.loaders) {
|
||||
this.loaders[property].stop();
|
||||
}
|
||||
}
|
||||
|
||||
startLoaders() {
|
||||
for (const property in this.loaders) {
|
||||
const el = document.querySelector(`#${property}`);
|
||||
this.loaders[property].start(el);
|
||||
}
|
||||
}
|
||||
|
||||
startLoader() {
|
||||
this.startLoaders();
|
||||
}
|
||||
|
||||
onLoad(data, status) {
|
||||
if ('success' == status) {
|
||||
this.stopLoaders();
|
||||
this.updateTiles(data);
|
||||
fireEvent('dateFilterChangedCompleted');
|
||||
}
|
||||
}
|
||||
|
||||
initTooltips() {
|
||||
Tooltip.addTooltipsToTiles();
|
||||
}
|
||||
|
||||
updateTiles(data) {}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import {BaseTiles} from './BaseTiles.js?v=2';
|
||||
import {
|
||||
renderBoolean,
|
||||
renderDefaultIfEmptyElement,
|
||||
renderBrowser,
|
||||
renderOs,
|
||||
} from '../DataRenderers.js?v=2';
|
||||
|
||||
const URL = '/admin/loadBotDetails';
|
||||
const ELEMS = ['title', 'os', 'browser', 'modified'];
|
||||
|
||||
export class BotTiles extends BaseTiles {
|
||||
updateTiles(data) {
|
||||
|
||||
const os = [];
|
||||
if (data.os_name) os.push(data.os_name);
|
||||
if (data.os_version) os.push(data.os_version);
|
||||
|
||||
const browser = [];
|
||||
if (data.browser_name) browser.push(data.browser_name);
|
||||
if (data.browser_version) browser.push(data.browser_version);
|
||||
|
||||
const record = {
|
||||
os: os.join(' '),
|
||||
browser: browser.join(' ')
|
||||
};
|
||||
|
||||
document.getElementById('title').replaceChildren(renderDefaultIfEmptyElement(data.title));
|
||||
document.getElementById('os').replaceChildren(renderOs(record));
|
||||
document.getElementById('browser').replaceChildren(renderBrowser(record));
|
||||
document.getElementById('modified').replaceChildren(renderBoolean(data.modified));
|
||||
}
|
||||
|
||||
get elems() {
|
||||
return ELEMS;
|
||||
}
|
||||
|
||||
get url() {
|
||||
return URL;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import {BaseTiles} from './BaseTiles.js?v=2';
|
||||
import {
|
||||
renderBoolean,
|
||||
renderDefaultIfEmptyElement,
|
||||
renderDate,
|
||||
} from '../DataRenderers.js?v=2';
|
||||
|
||||
const URL = '/admin/loadDomainDetails';
|
||||
const ELEMS = [
|
||||
'free-email', 'tranco-rank', 'unavailable', 'disposable',
|
||||
'creation-date', 'expiration-date', 'total-account', 'fraud'];
|
||||
|
||||
export class DomainTiles extends BaseTiles {
|
||||
updateTiles(data) {
|
||||
document.getElementById('free-email').replaceChildren(renderBoolean(data.free_email_provider));
|
||||
document.getElementById('tranco-rank').replaceChildren(renderDefaultIfEmptyElement(data.tranco_rank));
|
||||
document.getElementById('unavailable').replaceChildren(renderBoolean(data.disabled));
|
||||
document.getElementById('disposable').replaceChildren(renderBoolean(data.disposable_domains));
|
||||
|
||||
document.getElementById('creation-date').replaceChildren(renderDate(data.creation_date));
|
||||
document.getElementById('expiration-date').replaceChildren(renderDate(data.expiration_date));
|
||||
document.getElementById('total-account').replaceChildren(data.total_account);
|
||||
document.getElementById('fraud').replaceChildren(data.fraud);
|
||||
}
|
||||
|
||||
get elems() {
|
||||
return ELEMS;
|
||||
}
|
||||
|
||||
get url() {
|
||||
return URL;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import {BaseTiles} from './BaseTiles.js?v=2';
|
||||
import {Tooltip} from '../Tooltip.js?v=2';
|
||||
import {
|
||||
renderBoolean,
|
||||
renderClickableCountryTruncated,
|
||||
renderClickableAsn,
|
||||
} from '../DataRenderers.js?v=2';
|
||||
|
||||
const URL = '/admin/loadIpDetails';
|
||||
const ELEMS = ['country', 'asn', 'blocklist', 'blacklist', 'dc', 'vpn', 'tor', 'ar'];
|
||||
|
||||
export class IpTiles extends BaseTiles {
|
||||
updateTiles(data) {
|
||||
const record = {
|
||||
full_country: data.full_country,
|
||||
country_id: data.country_id,
|
||||
country_iso: data.country_iso,
|
||||
asn: data.asn,
|
||||
ispid: data.ispid,
|
||||
};
|
||||
|
||||
document.getElementById('country').replaceChildren(renderClickableCountryTruncated(record));
|
||||
document.getElementById('asn').replaceChildren(renderClickableAsn(record));
|
||||
document.getElementById('blocklist').replaceChildren(renderBoolean(data.blocklist));
|
||||
document.getElementById('blacklist').replaceChildren(renderBoolean(data.fraud_detected));
|
||||
document.getElementById('dc').replaceChildren(renderBoolean(data.data_center));
|
||||
document.getElementById('vpn').replaceChildren(renderBoolean(data.vpn));
|
||||
document.getElementById('tor').replaceChildren(renderBoolean(data.tor));
|
||||
document.getElementById('ar').replaceChildren(renderBoolean(data.relay));
|
||||
}
|
||||
|
||||
initTooltips() {
|
||||
super.initTooltips();
|
||||
Tooltip.addTooltipToSpans();
|
||||
}
|
||||
|
||||
get elems() {
|
||||
return ELEMS;
|
||||
}
|
||||
|
||||
get url() {
|
||||
return URL;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import {BaseTiles} from './BaseTiles.js?v=2';
|
||||
import {renderAsn} from '../DataRenderers.js?v=2';
|
||||
|
||||
const URL = '/admin/loadIspDetails';
|
||||
const ELEMS = ['asn', 'total-ips', 'total-visits', 'total-accounts', 'total-fraud'];
|
||||
|
||||
export class IspTiles extends BaseTiles {
|
||||
updateTiles(data) {
|
||||
document.getElementById('asn').replaceChildren(renderAsn(data));
|
||||
document.getElementById('total-accounts').replaceChildren(data.total_account);
|
||||
document.getElementById('total-visits').replaceChildren(data.total_visit);
|
||||
document.getElementById('total-fraud').replaceChildren(data.total_fraud);
|
||||
document.getElementById('total-ips').replaceChildren(data.total_ip);
|
||||
}
|
||||
|
||||
get elems() {
|
||||
return ELEMS;
|
||||
}
|
||||
|
||||
get url() {
|
||||
return URL;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
import {BaseTiles} from './BaseTiles.js?v=2';
|
||||
import {
|
||||
renderDate,
|
||||
renderBoolean,
|
||||
renderUserCounter,
|
||||
renderReputation,
|
||||
renderUserId,
|
||||
renderUserFirstname,
|
||||
renderUserLastname,
|
||||
renderUserReviewedStatus,
|
||||
} from '../DataRenderers.js?v=2';
|
||||
|
||||
const URL = '/admin/loadUserDetails';
|
||||
|
||||
export class UserTiles extends BaseTiles {
|
||||
updateTiles(data) {
|
||||
this.updateIdDetails(data);
|
||||
this.updateIpDetails(data);
|
||||
this.updateDayDetails(data);
|
||||
this.updateWeekDetails(data);
|
||||
}
|
||||
|
||||
updateIdDetails(data) {
|
||||
const tile = document.querySelector('#user-id-tile');
|
||||
|
||||
if (!tile) {
|
||||
return;
|
||||
}
|
||||
|
||||
const record = data.userDetails;
|
||||
this.removeLoaderBackground(tile);
|
||||
|
||||
tile.querySelector('#signup-date').replaceChildren(renderDate(record.created));
|
||||
tile.querySelector('#lastseen').replaceChildren(renderDate(record.lastseen));
|
||||
tile.querySelector('#latest-decision').replaceChildren(renderDate(record.latest_decision));
|
||||
tile.querySelector('#review-status').replaceChildren(renderUserReviewedStatus(record));
|
||||
tile.querySelector('#firstname').replaceChildren(renderUserFirstname(record));
|
||||
tile.querySelector('#lastname').replaceChildren(renderUserLastname(record));
|
||||
tile.querySelector('#userid').replaceChildren(renderUserId(record.userid));
|
||||
}
|
||||
|
||||
updateIpDetails(data) {
|
||||
const tile = document.querySelector('#user-ip-tile');
|
||||
|
||||
if (!tile) {
|
||||
return;
|
||||
}
|
||||
|
||||
const record = data.ipDetails;
|
||||
this.removeLoaderBackground(tile);
|
||||
|
||||
tile.querySelector('#datacenter').replaceChildren(renderBoolean(record.withdc));
|
||||
tile.querySelector('#vpn').replaceChildren(renderBoolean(record.withvpn));
|
||||
tile.querySelector('#tor').replaceChildren(renderBoolean(record.withtor));
|
||||
tile.querySelector('#apple-relay').replaceChildren(renderBoolean(record.withar));
|
||||
tile.querySelector('#ip-shared').replaceChildren(renderBoolean(record.sharedips));
|
||||
tile.querySelector('#spam-list').replaceChildren(renderBoolean(record.spamlist));
|
||||
tile.querySelector('#blacklisted').replaceChildren(renderBoolean(record.fraud_detected));
|
||||
}
|
||||
|
||||
updateDayDetails(data) {
|
||||
const limits = {
|
||||
median_event_cnt: 20,
|
||||
login_cnt: 3,
|
||||
session_cnt: 5,
|
||||
};
|
||||
|
||||
this.updateDateRangeDetails(data.dayDetails, '#day-behaviour-tile', limits);
|
||||
}
|
||||
|
||||
updateWeekDetails(data) {
|
||||
const limits = {
|
||||
median_event_cnt: 20,
|
||||
login_cnt: 10,
|
||||
session_cnt: 25,
|
||||
};
|
||||
|
||||
this.updateDateRangeDetails(data.weekDetails, '#week-behaviour-tile', limits);
|
||||
}
|
||||
|
||||
updateDateRangeDetails(record, tileId, limits) {
|
||||
const tile = document.querySelector(tileId);
|
||||
|
||||
if (!tile) {
|
||||
return;
|
||||
}
|
||||
|
||||
const span = document.createElement('span');
|
||||
span.className = 'nolight';
|
||||
span.textContent = 'N/A';
|
||||
|
||||
this.removeLoaderBackground(tile);
|
||||
|
||||
if (record.session_cnt === 0) {
|
||||
tile.querySelector('#failed-login-count').replaceChildren(span.cloneNode(true));
|
||||
tile.querySelector('#password-reset-count').replaceChildren(span.cloneNode(true));
|
||||
tile.querySelector('#auth-error-count').replaceChildren(span.cloneNode(true));
|
||||
tile.querySelector('#off-hours-login-count').replaceChildren(span.cloneNode(true));
|
||||
tile.querySelector('#median-event-count').replaceChildren(span.cloneNode(true));
|
||||
tile.querySelector('#login-count').replaceChildren(span.cloneNode(true));
|
||||
tile.querySelector('#session-count').replaceChildren(span.cloneNode(true));
|
||||
} else {
|
||||
const map = [
|
||||
['#failed-login-count', 'failed_login_cnt'],
|
||||
['#password-reset-count', 'password_reset_cnt'],
|
||||
['#auth-error-count', 'auth_error_cnt'],
|
||||
['#off-hours-login-count', 'off_hours_login_cnt'],
|
||||
['#median-event-count', 'median_event_cnt'],
|
||||
['#login-count', 'login_cnt'],
|
||||
['#session-count', 'session_cnt'],
|
||||
];
|
||||
|
||||
for (const [id, el] of map) {
|
||||
tile.querySelector(id).replaceChildren(renderUserCounter(record[el], limits[el] || 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
removeLoaderBackground(tile) {
|
||||
const backgrounds = tile.querySelectorAll('.loading-background');
|
||||
for (let i = 0; i < backgrounds.length; i++) {
|
||||
backgrounds[i].classList.remove('loading-background');
|
||||
}
|
||||
}
|
||||
|
||||
get elems() {
|
||||
return [];
|
||||
}
|
||||
|
||||
get url() {
|
||||
return URL;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,466 @@
|
||||
import {Loader} from '../Loader.js?v=2';
|
||||
import {Tooltip} from '../Tooltip.js?v=2';
|
||||
import {fireEvent} from '../utils/Event.js?v=2';
|
||||
import {getQueryParams} from '../utils/DataSource.js?v=2';
|
||||
import {handleAjaxError} from '../utils/ErrorHandler.js?v=2';
|
||||
import {TotalTile} from '../TotalTile.js?v=2';
|
||||
import {renderTotalFrame} from '../DataRenderers.js?v=2';
|
||||
import {
|
||||
MIDLINE_HELLIP,
|
||||
} from '../utils/Constants.js?v=2';
|
||||
|
||||
export class BaseGrid {
|
||||
constructor(gridParams) {
|
||||
this.config = gridParams;
|
||||
|
||||
this.loader = new Loader();
|
||||
this.tooltip = new Tooltip();
|
||||
this.totalTile = new TotalTile();
|
||||
|
||||
this.firstload = true;
|
||||
|
||||
this.renderTotalsLoader = this.renderTotalsLoader.bind(this);
|
||||
|
||||
this.initLoad();
|
||||
|
||||
if (this.config.dateRangeGrid && !this.config.sequential) {
|
||||
const onDateFilterChanged = this.onDateFilterChanged.bind(this);
|
||||
window.addEventListener('dateFilterChanged', onDateFilterChanged, false);
|
||||
}
|
||||
|
||||
if (gridParams.choicesFilterEvents) {
|
||||
const onChoicesFilterChanged = this.onChoicesFilterChanged.bind(this);
|
||||
for (let i = 0; i < gridParams.choicesFilterEvents.length; i++) {
|
||||
window.addEventListener(gridParams.choicesFilterEvents[i], onChoicesFilterChanged, false);
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.config.sequential) {
|
||||
const onSearchFilterChanged = this.onSearchFilterChanged.bind(this);
|
||||
window.addEventListener('searchFilterChanged', onSearchFilterChanged, false);
|
||||
}
|
||||
}
|
||||
|
||||
initLoad() {
|
||||
const me = this;
|
||||
const tableId = this.config.tableId;
|
||||
|
||||
$(document).ready(() => {
|
||||
$.extend($.fn.dataTable.ext.classes, {
|
||||
sStripeEven: '', sStripeOdd: ''
|
||||
});
|
||||
|
||||
$.fn.dataTable.ext.pager.numbers_length = 9;
|
||||
|
||||
$.fn.dataTable.ext.errMode = function() {};
|
||||
$(`#${tableId}`).on('error.dt', me.onError);
|
||||
|
||||
const onBeforeLoad = me.onBeforeLoad.bind(me);
|
||||
$(`#${tableId}`).on('preXhr.dt', onBeforeLoad);
|
||||
|
||||
const onBeforePageChange = me.onBeforePageChange.bind(me);
|
||||
$(`#${tableId}`).on('page.dt', onBeforePageChange);
|
||||
|
||||
const config = me.getDataTableConfig();
|
||||
$(`#${tableId}`).DataTable(config);
|
||||
|
||||
const onTableRowClick = me.onTableRowClick.bind(me);
|
||||
$(`#${tableId} tbody`).on('click', 'tr', onTableRowClick);
|
||||
|
||||
const onDraw = me.onDraw.bind(me);
|
||||
$(`#${tableId}`).on('draw.dt', onDraw);
|
||||
|
||||
$(`#${tableId}`).closest('.dt-container').find('nav').empty();
|
||||
|
||||
document.getElementById(tableId).classList.add('hide-body');
|
||||
|
||||
if (!me.config.sequential) {
|
||||
me.loadData();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getDataTableConfig() {
|
||||
const me = this;
|
||||
const url = this.config.url;
|
||||
const columns = this.columns;
|
||||
const columnDefs = this.columnDefs;
|
||||
const isSortable = this.getConfigParam('isSortable');
|
||||
const order = this.orderConfig;
|
||||
|
||||
const config = {
|
||||
ajax: function(data, callback, settings) {
|
||||
$.ajax({
|
||||
url: url,
|
||||
method: 'GET',
|
||||
data: data,
|
||||
dataType: 'json',
|
||||
success: function(response, textStatus, jqXHR) {
|
||||
callback(response);
|
||||
me.performAdditional(response, me.config);
|
||||
me.stopAnimation();
|
||||
},
|
||||
error: handleAjaxError,
|
||||
});
|
||||
},
|
||||
processing: true,
|
||||
serverSide: true,
|
||||
deferRender: true,
|
||||
deferLoading: 0,
|
||||
pageLength: 25,
|
||||
autoWidth: false,
|
||||
lengthChange: false,
|
||||
searching: true,
|
||||
ordering: isSortable,
|
||||
info: false,
|
||||
pagingType: 'simple_numbers',
|
||||
language: {
|
||||
paginate: {
|
||||
previous: '<',
|
||||
next: '>',
|
||||
},
|
||||
},
|
||||
|
||||
layout: {
|
||||
topEnd: null,
|
||||
bottomEnd: {
|
||||
paging: {
|
||||
boundaryNumbers: false,
|
||||
type: 'simple_numbers',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
createdRow: function(row, data, dataIndex) {
|
||||
$(row).attr('data-item-id', data.id);
|
||||
},
|
||||
|
||||
drawCallback: function(settings) {
|
||||
me.drawCallback(settings);
|
||||
me.updateTableFooter(this);
|
||||
},
|
||||
|
||||
columnDefs: columnDefs,
|
||||
columns: columns,
|
||||
order: order
|
||||
};
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
performAdditional(response, config) {
|
||||
if (!config.calculateTotals) {
|
||||
fireEvent('dateFilterChangedCompleted');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const ids = response.data.map(item => item.id);
|
||||
const token = document.head.querySelector('[name=\'csrf-token\'][content]').content;
|
||||
const dateRange = response.dateRange;
|
||||
|
||||
if (dateRange && ids.length) {
|
||||
const requestData = {
|
||||
token: token,
|
||||
ids: ids,
|
||||
type: config.totals.type,
|
||||
startDate: dateRange.startDate,
|
||||
endDate: dateRange.endDate,
|
||||
};
|
||||
let preparedBase = {};
|
||||
response.data.forEach(rec => {
|
||||
preparedBase[rec.id] = rec;
|
||||
});
|
||||
$.ajax({
|
||||
type: 'GET',
|
||||
url: '/admin/timeFrameTotal',
|
||||
data: requestData,
|
||||
success: (data) => this.onTotalsSuccess(data, config, preparedBase),
|
||||
error: handleAjaxError,
|
||||
complete: function() {
|
||||
fireEvent('dateFilterChangedCompleted');
|
||||
},
|
||||
});
|
||||
} else {
|
||||
fireEvent('dateFilterChangedCompleted');
|
||||
}
|
||||
|
||||
if (!dateRange && ids.length) {
|
||||
// actualy making fake response from server
|
||||
let data = {totals: {}};
|
||||
let preparedBase = {};
|
||||
const cols = config.totals.columns;
|
||||
response.data.forEach(item => {
|
||||
data.totals[item.id] = {};
|
||||
preparedBase[item.id] = {};
|
||||
cols.forEach(col => {
|
||||
data.totals[item.id][col] = item[col];
|
||||
preparedBase[item.id][col] = item[col];
|
||||
});
|
||||
|
||||
});
|
||||
this.onTotalsSuccess(data, config, preparedBase);
|
||||
}
|
||||
}
|
||||
|
||||
onTotalsSuccess(data, config, base) {
|
||||
const table = $(`#${config.tableId}`).DataTable();
|
||||
const columns = config.totals.columns;
|
||||
|
||||
let idxs = {};
|
||||
|
||||
for (let i = 0; i < columns.length; i++) {
|
||||
idxs[columns[i]] = -1;
|
||||
}
|
||||
|
||||
table.settings().init().columns.forEach((col, index) => {
|
||||
columns.forEach(colName => {
|
||||
if (col.data === colName || col.name === colName) {
|
||||
idxs[colName] = index;
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
if (Object.values(idxs).includes(-1)) return;
|
||||
|
||||
let rowData;
|
||||
let id;
|
||||
|
||||
table.rows().every(function() {
|
||||
rowData = this.data();
|
||||
id = String(rowData.id);
|
||||
if (id in data.totals) {
|
||||
for (const col in idxs) {
|
||||
$(table.cell(this, idxs[col]).node()).html(renderTotalFrame(base[id][col], data.totals[id][col]));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
drawCallback(settings) {
|
||||
const me = this;
|
||||
|
||||
this.initTooltips();
|
||||
|
||||
//this.stopAnimation();
|
||||
|
||||
const params = {
|
||||
tableId: me.config.tableId
|
||||
};
|
||||
|
||||
if (settings && settings.iDraw > 1) {
|
||||
const total = settings.json.recordsTotal;
|
||||
const tileId = this.config.tileId;
|
||||
const tableId = this.config.tableId;
|
||||
|
||||
this.totalTile.update(tableId, tileId, total);
|
||||
this.updateTableTitle(total);
|
||||
|
||||
fireEvent('tableLoaded', params);
|
||||
}
|
||||
}
|
||||
|
||||
updateTableTitle(value) {
|
||||
const tableId = this.config.tableId;
|
||||
const wrapper = document.getElementById(tableId).closest('.card');
|
||||
|
||||
if (wrapper) {
|
||||
const span = wrapper.querySelector('header span');
|
||||
|
||||
span.textContent = value;
|
||||
}
|
||||
}
|
||||
|
||||
stopAnimation() {
|
||||
this.loader.stop();
|
||||
const el = document.getElementById(`${this.config.tableId}_loader`);
|
||||
if (el) el.remove();
|
||||
|
||||
const table = document.getElementById(this.config.tableId);
|
||||
table.classList.remove('dim-table');
|
||||
}
|
||||
|
||||
updateTableFooter(dataTable) {
|
||||
const tableId = this.config.tableId;
|
||||
const pagerSelector = `#${tableId}_wrapper .dt-paging`;
|
||||
|
||||
const api = dataTable.api();
|
||||
if (api.ajax && typeof api.ajax.json === 'function' && api.ajax.json() === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
$(`#${tableId}`).closest('.dt-container').find('nav').show();
|
||||
if (api.page.info().pages <= 1) {
|
||||
$(pagerSelector).hide();
|
||||
} else {
|
||||
$(pagerSelector).show();
|
||||
}
|
||||
}
|
||||
|
||||
initTooltips() {
|
||||
const tableId = this.config.tableId;
|
||||
Tooltip.addTooltipsToGridRecords(tableId);
|
||||
Tooltip.addTooltipToSpans();
|
||||
Tooltip.addTooltipToParagraphs();
|
||||
}
|
||||
|
||||
onBeforeLoad(e, settings, data) {
|
||||
if (!this.config.sequential) {
|
||||
this.startLoader();
|
||||
}
|
||||
this.updateTableTitle(MIDLINE_HELLIP);
|
||||
|
||||
fireEvent('dateFilterChangedCaught');
|
||||
|
||||
//TODO: move to events grid? Or not?
|
||||
const params = this.config.getParams();
|
||||
const queryParams = getQueryParams(params);
|
||||
|
||||
for (let key in queryParams) {
|
||||
data[key] = queryParams[key];
|
||||
}
|
||||
|
||||
const token = document.head.querySelector('[name=\'csrf-token\'][content]').content;
|
||||
data.token = token;
|
||||
}
|
||||
|
||||
onDraw(e, settings) {
|
||||
if (this.firstload) {
|
||||
document.getElementById(this.config.tableId).classList.remove('hide-body');
|
||||
this.firstload = false;
|
||||
}
|
||||
}
|
||||
|
||||
onBeforePageChange(e, settings) {
|
||||
const tableId = this.config.tableId;
|
||||
const pagesPath = `#${tableId}_paginate a`;
|
||||
|
||||
[...document.querySelectorAll(pagesPath)].forEach(a => {
|
||||
a.outerHTML =
|
||||
a.outerHTML
|
||||
.replace(/<a/g, '<span')
|
||||
.replace(/<\/a>/g, '</span>');
|
||||
});
|
||||
}
|
||||
|
||||
onTableRowClick(event) {
|
||||
const selection = window.getSelection();
|
||||
if ('Range' === selection.type) {
|
||||
return;
|
||||
}
|
||||
|
||||
const row = event.target.closest('tr');
|
||||
const link = row.querySelector('a');
|
||||
|
||||
if (link) {
|
||||
event.preventDefault();
|
||||
if (event.ctrlKey || event.metaKey) {
|
||||
window.open(link.href, '_blank');
|
||||
} else {
|
||||
window.location.href = link.href;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: leave blank and move to the events table
|
||||
addTableRowsEvents() {
|
||||
const tableId = this.config.tableId;
|
||||
const onRowClick = this.onRowClick.bind(this);
|
||||
|
||||
if ($(this.table).DataTable().data().any()) {
|
||||
const rows = document.querySelectorAll(`#${tableId} tbody tr`);
|
||||
rows.forEach(row => row.addEventListener('click', onRowClick, false));
|
||||
}
|
||||
}
|
||||
|
||||
startLoader() {
|
||||
const tableId = this.config.tableId;
|
||||
const loaderPath = `${tableId}_processing`;
|
||||
|
||||
const loaderWrapper = document.getElementById(loaderPath);
|
||||
const el = document.createElement('p');
|
||||
el.className = 'text-loader';
|
||||
loaderWrapper.replaceChildren(el);
|
||||
|
||||
this.loader.start(el);
|
||||
|
||||
loaderWrapper.style.display = null;
|
||||
|
||||
const table = document.getElementById(this.config.tableId);
|
||||
table.classList.add('dim-table');
|
||||
}
|
||||
|
||||
onRowClick(e) {
|
||||
const selection = window.getSelection();
|
||||
if ('Range' === selection.type) {
|
||||
return;
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
|
||||
const row = e.target.closest('tr');
|
||||
const itemId = row.dataset.itemId;
|
||||
const data = {itemId: itemId};
|
||||
|
||||
fireEvent('tableRowClicked', data);
|
||||
}
|
||||
|
||||
onError(e, settings, techNote, message) {
|
||||
if (settings.jqXHR !== undefined && 403 === settings.jqXHR.status) {
|
||||
window.location.href = escape('/');
|
||||
}
|
||||
|
||||
//console.warn('An error has been reported by DataTables: ', message);
|
||||
}
|
||||
|
||||
loadData() {
|
||||
//TODO: create getter for table el: $(me.table).DataTable().ajax.reload()
|
||||
const me = this;
|
||||
$(me.table).DataTable().ajax.reload();
|
||||
}
|
||||
|
||||
onDateFilterChanged() {
|
||||
this.loadData();
|
||||
}
|
||||
|
||||
onSearchFilterChanged() {
|
||||
this.loadData();
|
||||
}
|
||||
|
||||
onChoicesFilterChanged() {
|
||||
this.loadData();
|
||||
}
|
||||
|
||||
getConfigParam(key) {
|
||||
const cfg = this.config;
|
||||
const value = ('undefined' !== typeof cfg[key]) ? cfg[key] : true;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
get orderConfig() {
|
||||
return this.getConfigParam('isSortable') ? [[1, 'desc']] : [];
|
||||
}
|
||||
|
||||
get table() {
|
||||
const tableId = this.config.tableId;
|
||||
const tableEl = document.getElementById(tableId);
|
||||
|
||||
return tableEl;
|
||||
}
|
||||
|
||||
renderTotalsLoader(data, type, record, meta) {
|
||||
const span = document.createElement('span');
|
||||
|
||||
const col_name = meta.settings.aoColumns[meta.col].name;
|
||||
if (this.config.calculateTotals && this.config.totals.columns.includes(col_name)) {
|
||||
span.className = 'loading-table-total';
|
||||
span.textContent = MIDLINE_HELLIP;
|
||||
} else {
|
||||
span.textContent = data;
|
||||
}
|
||||
|
||||
return span;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user