diff --git a/CHANGELOG.md b/CHANGELOG.md index 661a42373..d19e991d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,4 @@ -# UTMStack 10.9.0 Release Notes +# UTMStack 10.9.1 Release Notes -- Added New Suricata Integration. \ No newline at end of file +-- Dashboard Rendering with Time Filters + Resolved performance issues affecting dashboard responsiveness when applying time-based filters. \ No newline at end of file diff --git a/frontend/src/app/dashboard/dashboard-overview/dashboard-overview.component.html b/frontend/src/app/dashboard/dashboard-overview/dashboard-overview.component.html index dbedf3dfe..bcae0ecc5 100644 --- a/frontend/src/app/dashboard/dashboard-overview/dashboard-overview.component.html +++ b/frontend/src/app/dashboard/dashboard-overview/dashboard-overview.component.html @@ -1,7 +1,8 @@ -
+
Overview
+ @@ -20,14 +21,10 @@
Overview
- - +
- - +
@@ -39,8 +36,8 @@
Overview + [type]="RefreshType.CHART_COMMON_PIE_SEVERITY" + (loaded)="onRun()">
@@ -54,8 +51,8 @@
Overview + [type]="RefreshType.CHART_COMMON_TABLE_TOP_ALERT" + (loaded)="onRun()">
@@ -74,10 +71,10 @@
Overview + [type]="RefreshType.CHART_COMMON_PIE_EVENT">
-
@@ -86,8 +83,8 @@
Overview + [type]="RefreshType.CHART_COMMON_TABLE_TOP_EVENT" + (loaded)="onRun()">
diff --git a/frontend/src/app/dashboard/dashboard-overview/dashboard-overview.component.ts b/frontend/src/app/dashboard/dashboard-overview/dashboard-overview.component.ts index 003e9f7fc..e25f03526 100644 --- a/frontend/src/app/dashboard/dashboard-overview/dashboard-overview.component.ts +++ b/frontend/src/app/dashboard/dashboard-overview/dashboard-overview.component.ts @@ -23,6 +23,7 @@ import {OverviewAlertDashboardService} from '../../shared/services/charts-overvi import {IndexPatternService} from '../../shared/services/elasticsearch/index-pattern.service'; import {LocalFieldService} from '../../shared/services/elasticsearch/local-field.service'; import {ExportPdfService} from '../../shared/services/util/export-pdf.service'; +import {RefreshService, RefreshType} from '../../shared/services/util/refresh.service'; import {ChartSerieValueType} from '../../shared/types/chart-reponse/chart-serie-value.type'; import {ElasticFilterType} from '../../shared/types/filter/elastic-filter.type'; import {UtmIndexPatternFields} from '../../shared/types/index-pattern/utm-index-pattern-fields'; @@ -66,9 +67,6 @@ export class DashboardOverviewComponent implements OnInit, OnDestroy { indexPattern: IndexPatternSystemEnumName.LOG }; paramEvenTopCLick = 'logx.wineventlog.event_name.keyword'; - - adActive: boolean; - vulActive: boolean; alertSeverityColorMap: { value: string, color: string }[] = [ {color: '#42A5F5', value: LOW_TEXT}, {color: '#FF9800', value: MEDIUM_TEXT}, @@ -81,6 +79,8 @@ export class DashboardOverviewComponent implements OnInit, OnDestroy { runList = 0; visualizationRender = 8; preparingPrint = true; + RefreshType = RefreshType; + timeOutId: any; constructor(private overviewAlertDashboardService: OverviewAlertDashboardService, @@ -92,7 +92,8 @@ export class DashboardOverviewComponent implements OnInit, OnDestroy { private exportPdfService: ExportPdfService, private activatedRoute: ActivatedRoute, private timeFilterBehavior: TimeFilterBehavior, - private utmToastService: UtmToastService) { + private utmToastService: UtmToastService, + private refreshService: RefreshService) { } ngOnInit() { @@ -129,7 +130,7 @@ export class DashboardOverviewComponent implements OnInit, OnDestroy { // } // }); - setTimeout(() => { + this.timeOutId = setTimeout(() => { this.synchronizeFields(); }, 100000); @@ -266,6 +267,7 @@ export class DashboardOverviewComponent implements OnInit, OnDestroy { ngOnDestroy(): void { this.destroy$.next(); this.destroy$.complete(); + this.refreshService.stopInterval(); + clearTimeout(this.timeOutId); } - } diff --git a/frontend/src/app/dashboard/dashboard-render/dashboard-render.component.html b/frontend/src/app/dashboard/dashboard-render/dashboard-render.component.html index 3b28bb8db..f99dcb24d 100644 --- a/frontend/src/app/dashboard/dashboard-render/dashboard-render.component.html +++ b/frontend/src/app/dashboard/dashboard-render/dashboard-render.component.html @@ -4,11 +4,11 @@
{{dashboard.name}}
+ -
- + - - + +
diff --git a/frontend/src/app/dashboard/dashboard-render/dashboard-render.component.ts b/frontend/src/app/dashboard/dashboard-render/dashboard-render.component.ts index 25782de1b..d3f48e2a6 100644 --- a/frontend/src/app/dashboard/dashboard-render/dashboard-render.component.ts +++ b/frontend/src/app/dashboard/dashboard-render/dashboard-render.component.ts @@ -1,8 +1,10 @@ -import {AfterViewInit, ChangeDetectorRef, Component, OnDestroy, OnInit} from '@angular/core'; -import {ActivatedRoute} from '@angular/router'; -import {NgbModal} from '@ng-bootstrap/ng-bootstrap'; +import {AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit} from '@angular/core'; +import {ActivatedRoute, Router} from '@angular/router'; import {CompactType, GridsterConfig, GridsterItem, GridType} from 'angular-gridster2'; import {UUID} from 'angular2-uuid'; +import {NgxSpinnerService} from 'ngx-spinner'; +import {Observable} from 'rxjs'; +import {map, tap} from 'rxjs/operators'; import {IComponent} from '../../graphic-builder/dashboard-builder/shared/services/layout.service'; import {rebuildVisualizationFilterTime} from '../../graphic-builder/shared/util/chart-filter/chart-filter.util'; import {DashboardBehavior} from '../../shared/behaviors/dashboard.behavior'; @@ -11,20 +13,20 @@ import {UtmDashboardVisualizationType} from '../../shared/chart/types/dashboard/ import {UtmDashboardType} from '../../shared/chart/types/dashboard/utm-dashboard.type'; import {VisualizationType} from '../../shared/chart/types/visualization.type'; import {ChartTypeEnum} from '../../shared/enums/chart-type.enum'; +import {ExportPdfService} from '../../shared/services/util/export-pdf.service'; +import {RefreshService} from '../../shared/services/util/refresh.service'; import {DashboardFilterType} from '../../shared/types/filter/dashboard-filter.type'; import {ElasticFilterType} from '../../shared/types/filter/elastic-filter.type'; import {mergeParams, sanitizeFilters} from '../../shared/util/elastic-filter.util'; import {filtersToStringParam} from '../../shared/util/query-params-to-filter.util'; import {normalizeString} from '../../shared/util/string-util'; import {RenderLayoutService} from '../shared/services/render-layout.service'; -import {UtmRenderVisualization} from '../shared/services/utm-render-visualization.service'; -import {ExportPdfService} from "../../shared/services/util/export-pdf.service"; -import {NgxSpinnerService} from "ngx-spinner"; @Component({ selector: 'app-dashboard-render', templateUrl: './dashboard-render.component.html', - styleUrls: ['./dashboard-render.component.scss'] + styleUrls: ['./dashboard-render.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush }) export class DashboardRenderComponent implements OnInit, OnDestroy, AfterViewInit { dashboardId: number; @@ -59,25 +61,37 @@ export class DashboardRenderComponent implements OnInit, OnDestroy, AfterViewIni swap: false }; filtersValues: ElasticFilterType[] = []; - timeEnable: number[] = []; + layout$: Observable<{ grid: GridsterItem, visualization: VisualizationType } []>; constructor(private activatedRoute: ActivatedRoute, private layoutService: RenderLayoutService, private cdr: ChangeDetectorRef, - private modalService: NgbModal, private dashboardBehavior: DashboardBehavior, private timeFilterBehavior: TimeFilterBehavior, - private utmRenderVisualization: UtmRenderVisualization, private exportPdfService: ExportPdfService, - private spinner: NgxSpinnerService) { + private spinner: NgxSpinnerService, + private refreshService: RefreshService, + private router: Router) { } ngOnInit() { - console.log('Init dashboard'); - document.body.classList.add('overflow-hidden'); this.loadingVisualizations = true; - this.activatedRoute.params.subscribe(params => { + this.layout$ = this.activatedRoute.data + .pipe( + tap((visualizations) => { + this.dashboard = this.layoutService.dashboard; + this.filters = this.dashboard && this.dashboard.filters ? JSON.parse(this.dashboard.filters) : []; + /* if (this.dashboard.refreshTime) { + console.log(this.dashboard.refreshTime); + this.onRefreshTime(this.dashboard.refreshTime); + }*/ + this.loadingVisualizations = false; + }), + map(data => data.response) + ); + + /*this.activatedRoute.params.subscribe(params => { this.dashboardId = params.id; if (this.dashboardId) { const request = { @@ -105,7 +119,7 @@ export class DashboardRenderComponent implements OnInit, OnDestroy, AfterViewIni this.loadingVisualizations = false; }); } - }); + });*/ this.dashboardBehavior.$filterDashboard.subscribe(dashboardFilter => { if (dashboardFilter) { mergeParams(dashboardFilter.filter, this.filtersValues).then(newFilters => { @@ -113,6 +127,7 @@ export class DashboardRenderComponent implements OnInit, OnDestroy, AfterViewIni }); } }); + this.timeFilterBehavior.$time.subscribe(time => { if (time) { rebuildVisualizationFilterTime({timeFrom: time.from, timeTo: time.to}, this.filtersValues).then(filters => { @@ -146,9 +161,7 @@ export class DashboardRenderComponent implements OnInit, OnDestroy, AfterViewIni ngOnDestroy(): void { document.body.classList.remove('overflow-hidden'); clearInterval(this.interval); - this.visualizationRender = []; - this.layoutService.layout = []; - this.timeEnable = []; + this.layoutService.clearLayout(); this.dashboardBehavior.$filterDashboard.next(null); } @@ -164,7 +177,7 @@ export class DashboardRenderComponent implements OnInit, OnDestroy, AfterViewIni exportToPdf() { filtersToStringParam(this.filtersValues).then(queryParams => { this.spinner.show('buildPrintPDF'); - const url = '/dashboard/export/' + this.dashboardId + '/' + normalizeString(this.dashboard.name) + '?' + queryParams; + const url = '/dashboard/export/' + this.dashboard.id + '/' + normalizeString(this.dashboard.name) + '?' + queryParams; // window.open('/dashboard/export/' + this.dashboardId + '/' + normalizeString(this.dashboard.name) + '?' + queryParams, '_blank'); this.exportPdfService.getPdf(url, this.dashboard.name, 'PDF_TYPE_TOKEN').subscribe(response => { this.spinner.hide('buildPrintPDF').then(() => @@ -175,5 +188,9 @@ export class DashboardRenderComponent implements OnInit, OnDestroy, AfterViewIni }); }); } + + trackByFn(index: number, item: { grid: GridsterItem, visualization: VisualizationType }) { + return item.visualization.id; + } } diff --git a/frontend/src/app/dashboard/dashboard-routing.module.ts b/frontend/src/app/dashboard/dashboard-routing.module.ts index bab8386b9..243303c34 100644 --- a/frontend/src/app/dashboard/dashboard-routing.module.ts +++ b/frontend/src/app/dashboard/dashboard-routing.module.ts @@ -10,6 +10,7 @@ import {DashboardOverviewComponent} from './dashboard-overview/dashboard-overvie import {DashboardRenderComponent} from './dashboard-render/dashboard-render.component'; import {DashboardViewComponent} from './dashboard-view/dashboard-view.component'; import {ReportExportComponent} from './report-export/report-export.component'; +import {DashboardResolverService} from './shared/services/dashboard-resolver.service'; const routes: Routes = [ {path: '', redirectTo: 'overview', pathMatch: 'full'}, @@ -35,7 +36,10 @@ const routes: Routes = [ path: 'render/:id/:dashboard', component: DashboardRenderComponent, canActivate: [UserRouteAccessService], - data: {authorities: [USER_ROLE, ADMIN_ROLE]} + data: {authorities: [USER_ROLE, ADMIN_ROLE]}, + resolve: { + response: DashboardResolverService + } }, { path: 'export/:id/:dashboard', diff --git a/frontend/src/app/dashboard/dashboard.module.ts b/frontend/src/app/dashboard/dashboard.module.ts index 84ffbdf34..501af2af8 100644 --- a/frontend/src/app/dashboard/dashboard.module.ts +++ b/frontend/src/app/dashboard/dashboard.module.ts @@ -19,6 +19,7 @@ import {DashboardRoutingModule} from './dashboard-routing.module'; import {DashboardViewComponent} from './dashboard-view/dashboard-view.component'; import {ReportExportComponent} from './report-export/report-export.component'; import {UtmDashboardSharedModule} from './shared/utm-dashboard-shared.module'; +import {DashboardResolverService} from "./shared/services/dashboard-resolver.service"; @NgModule({ declarations: [DashboardViewComponent, @@ -43,6 +44,9 @@ import {UtmDashboardSharedModule} from './shared/utm-dashboard-shared.module'; VulnerabilitySharedModule, GridsterModule ], + providers: [ + DashboardResolverService + ], entryComponents: [DashboardExportPreviewComponent], exports: [ DashboardViewComponent diff --git a/frontend/src/app/dashboard/shared/services/dashboard-resolver.service.ts b/frontend/src/app/dashboard/shared/services/dashboard-resolver.service.ts new file mode 100644 index 000000000..ab7aef481 --- /dev/null +++ b/frontend/src/app/dashboard/shared/services/dashboard-resolver.service.ts @@ -0,0 +1,47 @@ +import {Injectable, Type} from '@angular/core'; +import {ActivatedRouteSnapshot, Resolve, RouterStateSnapshot} from '@angular/router'; +import {GridsterItem} from 'angular-gridster2'; +import {NgxSpinnerService} from 'ngx-spinner'; +import {Observable} from 'rxjs'; +import {map, tap} from 'rxjs/operators'; +import {VisualizationType} from '../../../shared/chart/types/visualization.type'; +import {RenderLayoutService} from './render-layout.service'; +import {UtmRenderVisualization} from './utm-render-visualization.service'; + +@Injectable() +export class DashboardResolverService implements Resolve<{ grid: GridsterItem, visualization: VisualizationType } []> { + + constructor(private spinner: NgxSpinnerService, + private utmRenderVisualization: UtmRenderVisualization, + private layoutService: RenderLayoutService) { + } + + resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): + Observable<{ grid: GridsterItem, visualization: VisualizationType } []> { + const request = { + page: 0, + size: 10000, + 'idDashboard.equals': route.params.id, + sort: 'order,asc' + }; + this.spinner.show('loadingSpinner'); + return this.utmRenderVisualization.query(request) + .pipe( + map((response) => { + this.layoutService.clearLayout(); + const visualizations = response.body; + this.layoutService.dashboard = visualizations.length > 0 ? visualizations[0].dashboard : null; + + visualizations.forEach((vis) => { + const visualization: VisualizationType = {...vis.visualization, showTime: vis.showTimeFilter}; + this.layoutService.addItem(visualization, JSON.parse(vis.gridInfo)); + }); + + return this.layoutService.layout; + } + ), + tap(() => { + this.spinner.hide('loadingSpinner'); + })); + } +} diff --git a/frontend/src/app/dashboard/shared/services/render-layout.service.ts b/frontend/src/app/dashboard/shared/services/render-layout.service.ts index 4993a4b3c..1b410f7ea 100644 --- a/frontend/src/app/dashboard/shared/services/render-layout.service.ts +++ b/frontend/src/app/dashboard/shared/services/render-layout.service.ts @@ -1,6 +1,7 @@ import {Injectable} from '@angular/core'; import {GridsterItem} from 'angular-gridster2'; import {UUID} from 'angular2-uuid'; +import {UtmDashboardType} from '../../../shared/chart/types/dashboard/utm-dashboard.type'; import {VisualizationType} from '../../../shared/chart/types/visualization.type'; @@ -13,8 +14,9 @@ export interface IComponent { providedIn: 'root' }) export class RenderLayoutService { - public layout: { grid: GridsterItem, visualization: VisualizationType } [] = []; + private _layout: { grid: GridsterItem, visualization: VisualizationType } [] = []; public components: IComponent[] = []; + private _dashboard: UtmDashboardType; dropId: string; @@ -25,7 +27,7 @@ export class RenderLayoutService { } addItem(vis: VisualizationType, grid?: GridsterItem): void { - this.layout.push({ + this._layout.push({ grid: grid ? grid : { cols: 10, id: UUID.UUID(), @@ -37,4 +39,20 @@ export class RenderLayoutService { }); } + get layout() { + return [...this._layout]; + } + + get dashboard() { + return this._dashboard; + } + + set dashboard(dashboard: UtmDashboardType) { + this._dashboard = {...dashboard }; + } + + clearLayout() { + this._layout = []; + this._dashboard = null; + } } diff --git a/frontend/src/app/dashboard/shared/services/utm-render-visualization.service.ts b/frontend/src/app/dashboard/shared/services/utm-render-visualization.service.ts index 8d121c193..4c1180525 100644 --- a/frontend/src/app/dashboard/shared/services/utm-render-visualization.service.ts +++ b/frontend/src/app/dashboard/shared/services/utm-render-visualization.service.ts @@ -3,8 +3,8 @@ import {Injectable} from '@angular/core'; import {Observable} from 'rxjs'; import {SERVER_API_URL} from '../../../app.constants'; import {UtmDashboardVisualizationType} from '../../../shared/chart/types/dashboard/utm-dashboard-visualization.type'; -import {createRequestOption} from '../../../shared/util/request-util'; import {RefreshDataService} from '../../../shared/services/util/refresh-data.service'; +import {createRequestOption} from '../../../shared/util/request-util'; @Injectable({ diff --git a/frontend/src/app/defined-charts/components/alert/chart-alert-by-status/chart-alert-by-status.component.html b/frontend/src/app/defined-charts/components/alert/chart-alert-by-status/chart-alert-by-status.component.html index 2e2794318..08fce6d05 100644 --- a/frontend/src/app/defined-charts/components/alert/chart-alert-by-status/chart-alert-by-status.component.html +++ b/frontend/src/app/defined-charts/components/alert/chart-alert-by-status/chart-alert-by-status.component.html @@ -15,26 +15,26 @@ label="Loading chart">
-
+ [value]="status[2]?.value" class="cursor-pointer">
diff --git a/frontend/src/app/defined-charts/components/alert/chart-alert-by-status/chart-alert-by-status.component.ts b/frontend/src/app/defined-charts/components/alert/chart-alert-by-status/chart-alert-by-status.component.ts index f86d6bf27..6dca4a0ec 100644 --- a/frontend/src/app/defined-charts/components/alert/chart-alert-by-status/chart-alert-by-status.component.ts +++ b/frontend/src/app/defined-charts/components/alert/chart-alert-by-status/chart-alert-by-status.component.ts @@ -1,7 +1,11 @@ import {Component, EventEmitter, Input, OnDestroy, OnInit, Output} from '@angular/core'; import {Router} from '@angular/router'; import {NgxSpinnerService} from 'ngx-spinner'; -import {ALERT_GLOBAL_FIELD, ALERT_STATUS_FIELD, ALERT_TIMESTAMP_FIELD} from '../../../../shared/constants/alert/alert-field.constant'; +import { + ALERT_GLOBAL_FIELD, + ALERT_STATUS_FIELD, + ALERT_TIMESTAMP_FIELD +} from '../../../../shared/constants/alert/alert-field.constant'; import { CLOSED, CLOSED_ICON, @@ -14,10 +18,16 @@ import { } from '../../../../shared/constants/alert/alert-status.constant'; import {ElasticOperatorsEnum} from '../../../../shared/enums/elastic-operators.enum'; import {ElasticTimeEnum} from '../../../../shared/enums/elastic-time.enum'; -import {OverviewAlertDashboardService} from '../../../../shared/services/charts-overview/overview-alert-dashboard.service'; +import { + OverviewAlertDashboardService +} from '../../../../shared/services/charts-overview/overview-alert-dashboard.service'; import {ChartSerieValueType} from '../../../../shared/types/chart-reponse/chart-serie-value.type'; import {ElasticFilterCommonType} from '../../../../shared/types/filter/elastic-filter-common.type'; import {TimeFilterType} from '../../../../shared/types/time-filter.type'; +import {RefreshService, RefreshType} from "../../../../shared/services/util/refresh.service"; +import {Observable, Subject} from "rxjs"; +import {filter, map, switchMap, takeUntil, tap} from "rxjs/operators"; +import {TimeFilterBehavior} from "../../../../shared/behaviors/time-filter.behavior"; @Component({ selector: 'app-chart-alert-by-status', @@ -25,11 +35,10 @@ import {TimeFilterType} from '../../../../shared/types/time-filter.type'; styleUrls: ['./chart-alert-by-status.component.scss'] }) export class ChartAlertByStatusComponent implements OnInit, OnDestroy { - @Input() refreshInterval; @Output() loaded = new EventEmitter(); interval: any; defaultTime: ElasticFilterCommonType = {time: ElasticTimeEnum.DAY, last: 7, label: 'last 7 days'}; - time: TimeFilterType; + time: TimeFilterType = {timeTo: 'now-7d/d', timeFrom: 'now'}; status: ChartSerieValueType[]; loadingStatusAlert = true; // @@ -41,23 +50,47 @@ export class ChartAlertByStatusComponent implements OnInit, OnDestroy { REVIEW_ICON = REVIEW_ICON; IGNORED_ICON = IGNORED_ICON; CLOSED_ICON = CLOSED_ICON; + destroy$ = new Subject(); + status$: Observable; constructor(private overviewAlertDashboardService: OverviewAlertDashboardService, + private refreshService: RefreshService, private router: Router, - private spinner: NgxSpinnerService) { + private spinner: NgxSpinnerService, + private timeFilterBehavior: TimeFilterBehavior) { } ngOnInit() { - if (this.refreshInterval) { + /*if (this.refreshInterval) { this.interval = setInterval(() => { this.getAlertByStatus(this.time); }, this.refreshInterval); - } + }*/ + + this.status$ = this.refreshService.refresh$ + .pipe( + takeUntil(this.destroy$), + filter((refreshType: string) => ( + refreshType === RefreshType.ALL || refreshType === RefreshType.CHART_ALERT_BY_STATUS)), + switchMap(() => this.getAlertByStatus(this.time))); + + this.timeFilterBehavior.$time + .pipe( + takeUntil(this.destroy$), + filter(time => !!time)) + .subscribe(time => { + if (time) { + this.onChangeAlertByStatus({ + timeFrom: time.from, + timeTo: time.to + }); + } + }); } onChangeAlertByStatus($event: TimeFilterType) { this.time = $event; - this.getAlertByStatus($event); + this.refreshService.sendRefresh(RefreshType.CHART_ALERT_BY_STATUS); } getAlertByStatus(time: TimeFilterType) { @@ -65,15 +98,14 @@ export class ChartAlertByStatusComponent implements OnInit, OnDestroy { to: time.timeTo, from: time.timeFrom }; - this.overviewAlertDashboardService.getCardAlertByStatus(req).subscribe(res => { - this.status = res.body; - this.loadingStatusAlert = false; - this.loaded.emit(); - }); - } - - ngOnDestroy(): void { - clearInterval(this.interval); + return this.overviewAlertDashboardService.getCardAlertByStatus(req) + .pipe( + tap(() => { + this.loaded.emit(); + this.loadingStatusAlert = false; + }), + map( response => response.body) + ); } chartEvent(status: number) { @@ -89,4 +121,10 @@ export class ChartAlertByStatusComponent implements OnInit, OnDestroy { this.spinner.hide('loadingSpinner'); }); } + + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + //clearInterval(this.interval); + } } diff --git a/frontend/src/app/defined-charts/components/alert/chart-alert-daily-week/chart-alert-daily-week.component.html b/frontend/src/app/defined-charts/components/alert/chart-alert-daily-week/chart-alert-daily-week.component.html index 22128f2e8..6e28530a3 100644 --- a/frontend/src/app/defined-charts/components/alert/chart-alert-daily-week/chart-alert-daily-week.component.html +++ b/frontend/src/app/defined-charts/components/alert/chart-alert-daily-week/chart-alert-daily-week.component.html @@ -12,19 +12,19 @@ label="Loading chart"> -
diff --git a/frontend/src/app/defined-charts/components/alert/chart-alert-daily-week/chart-alert-daily-week.component.ts b/frontend/src/app/defined-charts/components/alert/chart-alert-daily-week/chart-alert-daily-week.component.ts index 9fae0689b..be63aa7a8 100644 --- a/frontend/src/app/defined-charts/components/alert/chart-alert-daily-week/chart-alert-daily-week.component.ts +++ b/frontend/src/app/defined-charts/components/alert/chart-alert-daily-week/chart-alert-daily-week.component.ts @@ -1,47 +1,66 @@ -import {Component, EventEmitter, Input, OnDestroy, OnInit, Output} from '@angular/core'; +import {ChangeDetectionStrategy, Component, EventEmitter, OnDestroy, OnInit, Output} from '@angular/core'; import {Router} from '@angular/router'; import {NgxSpinnerService} from 'ngx-spinner'; +import {Observable, Subject} from 'rxjs'; +import {filter, map, startWith, switchMap, takeUntil, tap} from 'rxjs/operators'; import {ALERT_GLOBAL_FIELD, ALERT_TIMESTAMP_FIELD} from '../../../../shared/constants/alert/alert-field.constant'; import {ElasticOperatorsEnum} from '../../../../shared/enums/elastic-operators.enum'; -import {OverviewAlertDashboardService} from '../../../../shared/services/charts-overview/overview-alert-dashboard.service'; +import { + OverviewAlertDashboardService +} from '../../../../shared/services/charts-overview/overview-alert-dashboard.service'; +import {RefreshService, RefreshType} from '../../../../shared/services/util/refresh.service'; import {ChartSerieValueType} from '../../../../shared/types/chart-reponse/chart-serie-value.type'; @Component({ selector: 'app-chart-alert-daily-week', templateUrl: './chart-alert-daily-week.component.html', - styleUrls: ['./chart-alert-daily-week.component.scss'] + styleUrls: ['./chart-alert-daily-week.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush }) export class ChartAlertDailyWeekComponent implements OnInit, OnDestroy { - @Input() refreshInterval; @Output() loaded = new EventEmitter(); interval: any; dailyAlert: ChartSerieValueType[] = []; - loadingChartDailyAlert = true; + loadingChartDailyAlert = false; + destroy$ = new Subject(); + dailyAlert$: Observable; constructor(private overviewAlertDashboardService: OverviewAlertDashboardService, + private refreshService: RefreshService, private router: Router, private spinner: NgxSpinnerService) { } ngOnInit() { - this.getDailyAlert(); - if (this.refreshInterval) { - this.interval = setInterval(() => { - this.getDailyAlert(); - }, this.refreshInterval); - } + + this.dailyAlert$ = this.refreshService.refresh$ + .pipe( + takeUntil(this.destroy$), + filter((refreshType: string) => ( + refreshType === RefreshType.ALL)), + startWith(this.init()), + switchMap(() => { + return this.getDailyAlert(); + }) + ); } - ngOnDestroy(): void { - clearInterval(this.interval); + init() { + return this.getDailyAlert(); } getDailyAlert() { - this.overviewAlertDashboardService.getCardAlertTodayWeek().subscribe(response => { - this.dailyAlert = response.body; - this.loadingChartDailyAlert = false; - this.loaded.emit(); - }); + this.loadingChartDailyAlert = true; + return this.overviewAlertDashboardService + .getCardAlertTodayWeek() + .pipe( + map(response => { + return response.body; + }), + tap(() => { + this.loaded.emit(); + this.loadingChartDailyAlert = false; + })); } chartEvent(type: 'today' | 'week') { @@ -60,4 +79,9 @@ export class ChartAlertDailyWeekComponent implements OnInit, OnDestroy { }); } + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } + } diff --git a/frontend/src/app/defined-charts/components/common/chart-common-pie/chart-common-pie.component.html b/frontend/src/app/defined-charts/components/common/chart-common-pie/chart-common-pie.component.html index 4878f5c2f..04c35b2d2 100644 --- a/frontend/src/app/defined-charts/components/common/chart-common-pie/chart-common-pie.component.html +++ b/frontend/src/app/defined-charts/components/common/chart-common-pie/chart-common-pie.component.html @@ -9,7 +9,7 @@ [timeDefault]="defaultTime"> -
+
(); + data$: Observable; constructor(private overviewAlertDashboardService: OverviewAlertDashboardService, private router: Router, - private spinner: NgxSpinnerService) { + private refreshService: RefreshService, + private spinner: NgxSpinnerService, + private timeFilterBehavior: TimeFilterBehavior) { } ngOnInit() { this.queryParams.top = 10; - if (this.refreshInterval) { + /*if (this.refreshInterval) { this.interval = setInterval(() => { this.getPieData(); }, this.refreshInterval); - } + }*/ + + this.data$ = this.refreshService.refresh$ + .pipe(takeUntil(this.destroy$), + filter(refreshType => ( + refreshType === RefreshType.ALL || refreshType === this.type)), + switchMap(() => this.getPieData())); + + this.timeFilterBehavior.$time + .pipe( + takeUntil(this.destroy$), + filter(time => !!time)) + .subscribe(time => { + if (time) { + this.onTimeFilterChange({ + timeFrom: time.from, + timeTo: time.to + }); + } + }); } onTimeFilterChange($event: TimeFilterType) { this.queryParams.from = $event.timeFrom; this.queryParams.to = $event.timeTo; - this.getPieData(); - } - - ngOnDestroy(): void { - clearInterval(this.interval); + this.refreshService.sendRefresh(this.type); } getPieData() { - this.overviewAlertDashboardService.getDataPie(this.endpoint, this.queryParams).subscribe((severity) => { - this.loadingPieOption = false; - if (severity.body.data.length > 0) { - this.noData = false; - this.buildPieChart(severity.body); - } else { - this.noData = true; - } - this.loaded.emit(); - }); + return this.overviewAlertDashboardService.getDataPie(this.endpoint, this.queryParams) + .pipe( + map(response => response.body), + tap(response => { + this.loadingPieOption = false; + if (response.data.length > 0) { + this.noData = false; + this.buildPieChart(response); + } else { + this.noData = true; + } + this.loaded.emit(); + })); } buildPieChart(data: PieResponseType) { @@ -134,7 +162,6 @@ export class ChartCommonPieComponent implements OnInit, OnDestroy { } else { return UTM_COLOR_THEME[Math.random() * UTM_COLOR_THEME.length]; } - } private processMapColor(chart: PieResponseType): { name: string, itemStyle: any, value: number }[] { @@ -154,4 +181,9 @@ export class ChartCommonPieComponent implements OnInit, OnDestroy { return config; } + + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } } diff --git a/frontend/src/app/defined-charts/components/common/chart-common-table/chart-common-table.component.html b/frontend/src/app/defined-charts/components/common/chart-common-table/chart-common-table.component.html index 53e58cbc8..240d7a9be 100644 --- a/frontend/src/app/defined-charts/components/common/chart-common-table/chart-common-table.component.html +++ b/frontend/src/app/defined-charts/components/common/chart-common-table/chart-common-table.component.html @@ -20,9 +20,9 @@ label="Loading chart">
-
+
- + - - + + - +
@@ -42,7 +42,7 @@
@@ -50,7 +50,7 @@
-
+
; direction: SortDirection = ''; - loadingOption = true; + loadingOption = false; responseRows: Array<{ value: any; metric: boolean }[]>; queryParams = {from: 'now-7d', to: 'now', top: 20}; + destroy$: Subject = new Subject(); constructor(private overviewAlertDashboardService: OverviewAlertDashboardService, + private refreshService: RefreshService, private router: Router, - private spinner: NgxSpinnerService) { + private spinner: NgxSpinnerService, + private timeFilterBehavior: TimeFilterBehavior) { } ngOnInit() { this.queryParams.top = 20; - if (this.refreshInterval) { + /*if (this.refreshInterval) { this.interval = setInterval(() => { this.getTopAlert(); }, this.refreshInterval); - } - } + }*/ + this.data$ = this.refreshService.refresh$ + .pipe( + takeUntil(this.destroy$), + filter((refreshType: string) => ( + refreshType === RefreshType.ALL || refreshType === this.type )), + switchMap(() => this.getTopAlert()) + ); - ngOnDestroy(): void { - clearInterval(this.interval); + this.timeFilterBehavior.$time + .pipe( + takeUntil(this.destroy$), + filter(time => !!time)) + .subscribe(time => { + if (time) { + this.onTimeFilterChange({ + timeFrom: time.from, + timeTo: time.to + }); + } + }); } - getTopAlert() { - this.overviewAlertDashboardService.getDataTable(this.endpoint, this.queryParams) + /*this.overviewAlertDashboardService.getDataTable(this.endpoint, this.queryParams) .subscribe(response => { this.data = response.body; this.loadingOption = false; @@ -65,13 +101,26 @@ export class ChartCommonTableComponent implements OnInit, OnDestroy { this.pageEnd = this.itemsPerPage; this.responseRows = this.data.rows; this.totalItems = this.data.rows.length; - this.loaded.emit(); - }); + });*/ + return this.overviewAlertDashboardService.getDataTable(this.endpoint, this.queryParams) + .pipe( + map(response => response.body), + tap(data => { + this.loadingOption = false; + this.pageStart = 0; + this.pageEnd = this.itemsPerPage; + this.responseRows = data.rows; + this.totalItems = data.rows.length; + this.loaded.emit(); + } + )); } loadPage(page: number) { + this.loadingOption = true; this.pageEnd = page * this.itemsPerPage; this.pageStart = this.pageEnd - this.itemsPerPage; + this.refreshService.sendRefresh(this.type); } onSort({column, direction}: SortEvent) { @@ -91,9 +140,10 @@ export class ChartCommonTableComponent implements OnInit, OnDestroy { } onTimeFilterChange($event: TimeFilterType) { + this.loadingOption = true; this.queryParams.from = $event.timeFrom; this.queryParams.to = $event.timeTo; - this.getTopAlert(); + this.refreshService.sendRefresh(this.type); } rowEvent(row: { value: any; metric: boolean }[]) { @@ -107,5 +157,9 @@ export class ChartCommonTableComponent implements OnInit, OnDestroy { this.spinner.hide('loadingSpinner'); }); } - + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + clearInterval(this.interval); + } } diff --git a/frontend/src/app/defined-charts/components/events/chart-event-in-time/chart-event-in-time.component.html b/frontend/src/app/defined-charts/components/events/chart-event-in-time/chart-event-in-time.component.html index a4841cc20..7faa1a95c 100644 --- a/frontend/src/app/defined-charts/components/events/chart-event-in-time/chart-event-in-time.component.html +++ b/frontend/src/app/defined-charts/components/events/chart-event-in-time/chart-event-in-time.component.html @@ -9,7 +9,7 @@ [timeDefault]="defaultTime">
-
+
(); + @Input() type: RefreshType; interval: any; defaultTime: ElasticFilterCommonType = {time: ElasticTimeEnum.DAY, last: 7, label: 'last 7 days'}; - queryParams = {from: 'now-7d', to: 'now', interval: 'Day'}; + request = {from: 'now-7d', to: 'now', interval: 'Day'}; loadingPieOption = true; chartEnumType = ChartTypeEnum; multilineOption: any; noData = false; + destroy$ = new Subject(); + data$: Observable; constructor(private overviewAlertDashboardService: OverviewAlertDashboardService, + private refreshService: RefreshService, private router: Router, - private spinner: NgxSpinnerService) { + private spinner: NgxSpinnerService, + private timeFilterBehavior: TimeFilterBehavior) { } ngOnInit() { - if (this.refreshInterval) { + /*if (this.refreshInterval) { this.interval = setInterval(() => { this.getEventByTime(); }, this.refreshInterval); - } + }*/ + this.data$ = this.refreshService.refresh$ + .pipe( + takeUntil(this.destroy$), + filter(refreshType => ( + refreshType === RefreshType.ALL || refreshType === this.type)), + switchMap(() => this.getEventByTime())); + + this.timeFilterBehavior.$time + .pipe( + takeUntil(this.destroy$), + filter(time => !!time)) + .subscribe(time => { + if (time) { + this.onTimeFilterChange({ + timeFrom: time.from, + timeTo: time.to + }); + } + }); } onTimeFilterChange($event: TimeFilterType) { - this.queryParams.from = $event.timeFrom; - this.queryParams.to = $event.timeTo; - this.getEventByTime(); + this.request = { + from: $event.timeFrom, + to: $event.timeTo, + interval: this.getAutoInterval($event.timeFrom, $event.timeTo) + }; + this.refreshService.sendRefresh(this.type); } onChartClick($event: any) { @@ -68,12 +99,8 @@ export class ChartEventInTimeComponent implements OnInit, OnDestroy { // @timestamp is [date] } - ngOnDestroy(): void { - clearInterval(this.interval); - } - private getEventByTime() { - this.overviewAlertDashboardService.getEventInTime(this.queryParams) + /*this.overviewAlertDashboardService.getEventInTime(this.queryParams) .subscribe(event => { this.loadingPieOption = false; if (event.body.categories.length > 0) { @@ -84,8 +111,57 @@ export class ChartEventInTimeComponent implements OnInit, OnDestroy { } else { this.noData = true; } - this.loaded.emit(); - }); + });*/ + + return this.overviewAlertDashboardService.getEventInTime(this.request) + .pipe( + map( response => response.body), + tap(data => { + this.loadingPieOption = false; + if (data.categories.length > 0) { + buildMultilineObject(data).then(option => { + this.multilineOption = option; + this.noData = false; + }); + } else { + this.noData = true; + } + this.loaded.emit(); + }) + ); + } + getAutoInterval(from: string, to: string) { + if (to !== 'now') { return 'Day'; } + + const match = from.match(/^now-(\d+)([smhdwMy])$/i); + if (!match) { return 'Day'; } + + const [, amountStr, unit] = match; + const amount = parseInt(amountStr, 10); + const unitLower = unit.toLowerCase(); + + switch (unitLower) { + case 's': + case 'm': + return 'Minute'; + + case 'h': + return amount > 1 ? 'Hour' : 'Minute'; + + case 'd': + return amount <= 7 ? 'Day' : 'Week'; + + case 'w': + return 'Week'; + + default: + return 'Day'; + } + } + + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); } } diff --git a/frontend/src/app/graphic-builder/shared/components/viewer/chart-view/chart-view.component.html b/frontend/src/app/graphic-builder/shared/components/viewer/chart-view/chart-view.component.html index 8846356b4..e3f763484 100644 --- a/frontend/src/app/graphic-builder/shared/components/viewer/chart-view/chart-view.component.html +++ b/frontend/src/app/graphic-builder/shared/components/viewer/chart-view/chart-view.component.html @@ -11,7 +11,9 @@ [invertContent]="true">
-
+
-
(); loadingOption: boolean; - data: any[] = []; + data$: Observable; chartFactory = new ChartFactory(); echartOption: EChartOption; chartTypeEnum = ChartTypeEnum; @@ -43,34 +60,52 @@ export class ChartViewComponent implements OnInit { runWithError: boolean; defaultTime: ElasticFilterDefaultTime; echartsIntance: any; + refreshType: string; + destroy$ = new Subject(); constructor(private runVisualizationService: RunVisualizationService, private runVisualizationBehavior: RunVisualizationBehavior, private toastService: UtmToastService, private dashboardBehavior: DashboardBehavior, - private utmChartClickActionService: UtmChartClickActionService) { + private utmChartClickActionService: UtmChartClickActionService, + private refreshService: RefreshService, + private timeFilterBehavior: TimeFilterBehavior) { } ngOnInit() { - this.runVisualizationBehavior.$run.subscribe(id => { + this.defaultTime = resolveDefaultVisualizationTime(this.visualization); + this.refreshType = `${this.chartId}`; + + this.data$ = this.refreshService.refresh$ + .pipe( + takeUntil(this.destroy$), + tap((refreshType) => { + console.log('refresh chart-view', refreshType); + }), + filter((refreshType) => refreshType === RefreshType.ALL || + refreshType === this.refreshType), + switchMap((value, index) => this.runVisualization())); + + + this.runVisualizationBehavior.$run + .pipe(takeUntil(this.destroy$)) + .subscribe(id => { if (id && this.chartId === id) { - this.runVisualization(); + this.refreshService.sendRefresh(this.refreshType); this.defaultTime = resolveDefaultVisualizationTime(this.visualization); } }); - this.dashboardBehavior.$filterDashboard.subscribe(dashboardFilter => { + + this.dashboardBehavior.$filterDashboard + .pipe(takeUntil(this.destroy$)) + .subscribe(dashboardFilter => { if (dashboardFilter && dashboardFilter.indexPattern === this.visualization.pattern.pattern) { mergeParams(dashboardFilter.filter, this.visualization.filterType).then(newFilters => { this.visualization.filterType = sanitizeFilters(newFilters); - this.runVisualization(); + this.refreshService.sendRefresh(this.refreshType); }); } }); - this.defaultTime = resolveDefaultVisualizationTime(this.visualization); - - if (!this.defaultTime) { - this.runVisualization(); - } window.addEventListener('resize', (event) => { this.resizeChart(); @@ -84,69 +119,89 @@ export class ChartViewComponent implements OnInit { window.addEventListener('print', (event) => { this.resizeChart(); }); - } - chartEvent($event: any) { - if (typeof this.visualization.chartAction === 'string') { - this.visualization.chartAction = JSON.parse(this.visualization.chartAction); - } - if (!this.building) { - if (this.forceSingle()) { - $event.data.name = $event.seriesName; + this.timeFilterBehavior.$time + .pipe( + takeUntil(this.destroy$), + filter(time => !!time)) + .subscribe(time => { + if (time) { + this.onTimeFilterChange({ + timeFrom: time.from, + timeTo: time.to + }); } - this.utmChartClickActionService.onClickNavigate(this.visualization, $event, this.forceSingle()); + }); + + if (!this.defaultTime) { + this.refreshService.sendRefresh(this.refreshType); + } + } + + chartEvent($event: any, data: []) { + if (typeof this.visualization.chartAction === 'string') { + this.visualization.chartAction = JSON.parse(this.visualization.chartAction); + } + if (!this.building) { + if (this.forceSingle(data)) { + $event.data.name = $event.seriesName; } + this.utmChartClickActionService.onClickNavigate(this.visualization, $event, this.forceSingle(data)); } + } /** * This method return if click navigation behavior will treated as single one */ - forceSingle(): boolean { + forceSingle(data: any[]): boolean { return (this.visualization.chartType === ChartTypeEnum.BAR_CHART || - this.visualization.chartType === ChartTypeEnum.BAR_HORIZONTAL_CHART) && - this.data[0].series.length === 1 && + this.visualization.chartType === ChartTypeEnum.BAR_HORIZONTAL_CHART) && + data[0].series.length === 1 && (this.visualization.aggregationType.bucket !== null && this.visualization.aggregationType.bucket.subBucket !== null); } runVisualization() { this.loadingOption = true; - this.runVisualizationService.run(this.visualization).subscribe(data => { - this.loadingOption = false; - this.runned.emit('runned'); - this.data = data; - this.runWithError = false; - this.onChartChange(); - }, error => { - this.loadingOption = false; - this.runWithError = true; - this.echartOption = null; - this.runned.emit('runned'); - this.toastService.showError('Error', - 'Error occurred while running visualization' + this.visualization.name); - }); + return this.runVisualizationService.run(this.visualization) + .pipe( + tap((data) => { + console.log('run chart-view'); + this.loadingOption = false; + this.runned.emit('runned'); + this.runWithError = false; + this.onChartChange(data); + }), + catchError(() => { + this.loadingOption = false; + this.runWithError = true; + this.echartOption = null; + this.runned.emit('runned'); + this.toastService.showError('Error', + 'Error occurred while running visualization' + this.visualization.name); + return of([]); + })); } /** * Build echart object */ - onChartChange() { + onChartChange(data: any[]) { if (typeof this.visualization.chartConfig === 'string') { this.visualization.chartConfig = JSON.parse(this.visualization.chartConfig); } this.echartOption = deleteNullValues(this.chartFactory.createChart( - this.chart, - this.data, - this.visualization, - this.exportFormat + this.chart, + data, + this.visualization, + this.exportFormat ) ); } onTimeFilterChange($event: TimeFilterType) { - rebuildVisualizationFilterTime($event, this.visualization.filterType).then(filters => { this.visualization.filterType = filters; - this.runVisualization(); + this.refreshService.sendRefresh(this.refreshType); }); } @@ -159,4 +214,10 @@ export class ChartViewComponent implements OnInit { this.echartsIntance.resize(); } } + + ngOnDestroy(): void { + this.refreshService.sendRefresh(null); + this.destroy$.next(); + this.destroy$.complete(); + } } diff --git a/frontend/src/app/graphic-builder/shared/components/viewer/goal-view/goal-view.component.html b/frontend/src/app/graphic-builder/shared/components/viewer/goal-view/goal-view.component.html index 779bfc863..5f39e30d5 100644 --- a/frontend/src/app/graphic-builder/shared/components/viewer/goal-view/goal-view.component.html +++ b/frontend/src/app/graphic-builder/shared/components/viewer/goal-view/goal-view.component.html @@ -11,7 +11,8 @@ [invertContent]="true">
-
+
; @Input() chartId: number; @Input() visualization: VisualizationType; @Input() building: boolean; @@ -36,37 +40,68 @@ export class GoalViewComponent implements OnInit { GOAL_CHART = ChartTypeEnum.GOAL_CHART; error: boolean; defaultTime: ElasticFilterDefaultTime; + destroy$: Subject = new Subject(); + refreshType: string; constructor(private runVisualizationService: RunVisualizationService, private runVisualizationBehavior: RunVisualizationBehavior, private toastService: UtmToastService, private dashboardBehavior: DashboardBehavior, - private utmChartClickActionService: UtmChartClickActionService) { + private utmChartClickActionService: UtmChartClickActionService, + private refreshService: RefreshService, + private timeFilterBehavior: TimeFilterBehavior) { } ngOnInit() { - this.runVisualizationBehavior.$run.subscribe((id) => { + this.refreshType = `${this.chartId}`; + + this.data$ = this.refreshService.refresh$ + .pipe( + filter((refreshType) => refreshType === RefreshType.ALL || + refreshType === this.refreshType), + switchMap((value, index) => this.runVisualization())); + + this.runVisualizationBehavior.$run + .pipe(takeUntil(this.destroy$)) + .subscribe((id) => { if (id && this.chartId === id) { - this.runVisualization(); + this.refreshService.sendRefresh(this.refreshType); this.defaultTime = resolveDefaultVisualizationTime(this.visualization); } }); - this.dashboardBehavior.$filterDashboard.subscribe(dashboardFilter => { + this.dashboardBehavior.$filterDashboard + .pipe(takeUntil(this.destroy$)) + .subscribe(dashboardFilter => { if (dashboardFilter && dashboardFilter.indexPattern === this.visualization.pattern.pattern) { mergeParams(dashboardFilter.filter, this.visualization.filterType).then(newFilters => { this.visualization.filterType = sanitizeFilters(newFilters); - this.runVisualization(); + this.refreshService.sendRefresh(this.refreshType); }); } }); - this.runVisualization(); + + this.timeFilterBehavior.$time + .pipe( + takeUntil(this.destroy$), + filter(time => !!time)) + .subscribe(time => { + if (time) { + this.onTimeFilterChange({ + timeFrom: time.from, + timeTo: time.to + }); + } + }); + this.defaultTime = resolveDefaultVisualizationTime(this.visualization); - this.extractGoals(); + if (!this.defaultTime) { + this.refreshService.sendRefresh(this.refreshType); + } } runVisualization() { this.runningChart = true; - this.runVisualizationService.run(this.visualization).subscribe(data => { + /*this.runVisualizationService.run(this.visualization).subscribe(data => { this.runningChart = false; this.runned.emit('runned'); this.data = data; @@ -78,7 +113,25 @@ export class GoalViewComponent implements OnInit { this.runned.emit('runned'); this.toastService.showError('Error', 'Error occurred while running visualization'); - }); + });*/ + + return this.runVisualizationService.run(this.visualization) + .pipe( + tap((data) => { + this.runningChart = false; + this.runned.emit('runned'); + this.extractGoals(data); + this.error = false; + }), + catchError(() => { + this.runningChart = false; + this.error = true; + this.runned.emit('runned'); + this.toastService.showError('Error', + 'Error occurred while running visualization'); + return of([]); + }) + ); } chartEvent($event: UtmGoalOption) { @@ -93,14 +146,14 @@ export class GoalViewComponent implements OnInit { } } - extractGoals(): UtmGoalOption[] { + extractGoals(data: MetricResponse[]): UtmGoalOption[] { this.goals = []; const config: UtmGoalOption[] = this.visualization.chartConfig; - if (this.data) { - for (const d of this.data) { + if (data) { + for (const d of data) { const metricIndex = this.visualization.aggregationType.metrics.findIndex(value => Number(value.id) === Number(d.metricId)); const optionIndex = config.findIndex(value => Number(value.metricId) === Number(d.metricId)); - const max = (config[optionIndex].max ? config[optionIndex].max : this.calcTotal(this.data)); + const max = (config[optionIndex].max ? config[optionIndex].max : this.calcTotal(data)); const goal = new UtmGoalOption(Number(d.metricId), this.calcPercent(max, d.value, config[optionIndex].decimal), config[optionIndex].append, @@ -141,9 +194,15 @@ export class GoalViewComponent implements OnInit { onTimeFilterChange($event: TimeFilterType) { rebuildVisualizationFilterTime($event, this.visualization.filterType).then(filters => { this.visualization.filterType = filters; - this.runVisualization(); + this.refreshService.sendRefresh(this.refreshType); }); } + ngOnDestroy(): void { + this.refreshService.stopInterval(); + this.destroy$.next(); + this.destroy$.complete(); + } + } diff --git a/frontend/src/app/graphic-builder/shared/components/viewer/map-view/map-view.component.html b/frontend/src/app/graphic-builder/shared/components/viewer/map-view/map-view.component.html index 47e6d68b7..4695bcbf7 100644 --- a/frontend/src/app/graphic-builder/shared/components/viewer/map-view/map-view.component.html +++ b/frontend/src/app/graphic-builder/shared/components/viewer/map-view/map-view.component.html @@ -21,10 +21,12 @@ label="Loading chart">
-
- -
+ +
+ +
+
diff --git a/frontend/src/app/graphic-builder/shared/components/viewer/map-view/map-view.component.ts b/frontend/src/app/graphic-builder/shared/components/viewer/map-view/map-view.component.ts index 310c2db8a..bd9b6ab40 100644 --- a/frontend/src/app/graphic-builder/shared/components/viewer/map-view/map-view.component.ts +++ b/frontend/src/app/graphic-builder/shared/components/viewer/map-view/map-view.component.ts @@ -1,7 +1,7 @@ -import {AfterViewInit, Component, EventEmitter, Input, OnInit, Output} from '@angular/core'; +import {AfterViewInit, Component, EventEmitter, Input, OnDestroy, OnInit, Output} from '@angular/core'; import {UUID} from 'angular2-uuid'; import * as L from 'leaflet'; -import {Observable, Observer, Subject} from 'rxjs'; +import {Observable, Observer, of, Subject} from 'rxjs'; import {UtmToastService} from '../../../../../shared/alert/utm-toast.service'; import {DashboardBehavior} from '../../../../../shared/behaviors/dashboard.behavior'; import {EchartClickAction} from '../../../../../shared/chart/types/action/echart-click-action'; @@ -18,13 +18,16 @@ import {RunVisualizationService} from '../../../services/run-visualization.servi import {UtmChartClickActionService} from '../../../services/utm-chart-click-action.service'; import {rebuildVisualizationFilterTime} from '../../../util/chart-filter/chart-filter.util'; import {resolveDefaultVisualizationTime} from '../../../util/visualization/visualization-render.util'; +import {RefreshService, RefreshType} from "../../../../../shared/services/util/refresh.service"; +import {catchError, filter, map, switchMap, takeUntil, tap} from "rxjs/operators"; +import {TimeFilterBehavior} from "../../../../../shared/behaviors/time-filter.behavior"; @Component({ selector: 'app-map-view', templateUrl: './map-view.component.html', styleUrls: ['./map-view.component.scss'] }) -export class MapViewComponent implements OnInit, AfterViewInit { +export class MapViewComponent implements OnInit, AfterViewInit, OnDestroy { mapId = UUID.UUID(); @Input() building: boolean; @Input() chartId: number; @@ -37,6 +40,7 @@ export class MapViewComponent implements OnInit, AfterViewInit { @Output() runned = new EventEmitter(); loadingOption = true; data: { name: string, value: number[] }[] = []; + data$: Observable<{ name: string, value: number[] }[]>; mapOption: LeafletMapType; map: any; contentLoaded: Observer; @@ -45,12 +49,187 @@ export class MapViewComponent implements OnInit, AfterViewInit { error = false; defaultTime: ElasticFilterDefaultTime; markersLayer: any; + refreshType: string; + destroy$: Subject = new Subject(); + // tslint:disable-next-line:max-line-length + res: { name: string, value: number[] }[] = [ + { + "name": "57.135.189.63", + "value": [ + 26.2729, + -80.26, + 28.0 + ] + }, + { + "name": "2601:58a:8984:9240:6463:35f3:3a5f:c61f", + "value": [ + 37.751, + -97.822, + 22.0 + ] + }, + { + "name": "2601:58a:8984:9240:5c36:dc9f:f881:d87c", + "value": [ + 37.751, + -97.822, + 9.0 + ] + }, + { + "name": "88.196.79.239", + "value": [ + 59.433, + 24.7323, + 7.0 + ] + }, + { + "name": "2601:58a:8984:9240:c08d:edc:4c08:ca06", + "value": [ + 37.751, + -97.822, + 6.0 + ] + }, + { + "name": "2601:58a:8984:9240:a015:cc03:ee94:b65a", + "value": [ + 37.751, + -97.822, + 4.0 + ] + }, + { + "name": "2601:58a:8984:9240:b509:9ea9:cb8c:ba47", + "value": [ + 37.751, + -97.822, + 4.0 + ] + }, + { + "name": "2601:58a:8984:9240:1c73:b69d:5b2b:5b81", + "value": [ + 37.751, + -97.822, + 3.0 + ] + }, + { + "name": "2601:58a:8984:9240:5863:8f23:93eb:4f60", + "value": [ + 37.751, + -97.822, + 3.0 + ] + }, + { + "name": "2601:58a:8984:9240:6182:367b:f8f5:99cb", + "value": [ + 37.751, + -97.822, + 3.0 + ] + }, + { + "name": "2601:58a:8984:9240:6cca:ff0:72c2:98e1", + "value": [ + 37.751, + -97.822, + 3.0 + ] + }, + { + "name": "2601:58a:8984:9240:916a:63e6:9196:57ee", + "value": [ + 37.751, + -97.822, + 3.0 + ] + }, + { + "name": "2601:58a:8984:9240:9587:4412:99a:4169", + "value": [ + 37.751, + -97.822, + 3.0 + ] + }, + { + "name": "2601:58a:8984:9240:9ddb:ce24:5ea2:2f84", + "value": [ + 37.751, + -97.822, + 3.0 + ] + }, + { + "name": "2601:58a:8984:9240:b13f:97f3:2728:950", + "value": [ + 37.751, + -97.822, + 3.0 + ] + }, + { + "name": "2600:1700:234c:ac10:d0cb:fe60:ca36:8c59", + "value": [ + 25.6958, + -80.3626, + 2.0 + ] + }, + { + "name": "2601:58a:8984:9240:217b:7bdc:ab48:e41a", + "value": [ + 37.751, + -97.822, + 2.0 + ] + }, + { + "name": "2601:58a:8984:9240:9d3d:d2e1:d3f2:b458", + "value": [ + 37.751, + -97.822, + 2.0 + ] + }, + { + "name": "107.202.154.128", + "value": [ + 25.7689, + -80.1946, + 1.0 + ] + }, + { + "name": "2601:58a:8984:9240:a589:cf0c:9737:f4c6", + "value": [ + 37.751, + -97.822, + 1.0 + ] + }, + { + "name": "2601:58a:8984:9240:ccbc:1190:7f84:c0b8", + "value": [ + 37.751, + -97.822, + 1.0 + ] + } + ]; constructor(private runVisualizationService: RunVisualizationService, private runVisualizationBehavior: RunVisualizationBehavior, private utmChartClickActionService: UtmChartClickActionService, private dashboardBehavior: DashboardBehavior, - private toastService: UtmToastService) { + private toastService: UtmToastService, + private refreshService: RefreshService, + private timeFilterBehavior: TimeFilterBehavior) { } @@ -80,6 +259,13 @@ export class MapViewComponent implements OnInit, AfterViewInit { } ngOnInit() { + this.refreshType = `${this.chartId}`; + this.data$ = this.refreshService.refresh$ + .pipe( + filter((refreshType) => refreshType === RefreshType.ALL || + refreshType === this.refreshType), + switchMap((value, index) => this.runVisualization())); + this.mapLoaded().subscribe(value => { if (value) { const baseLayers = {}; @@ -104,27 +290,47 @@ export class MapViewComponent implements OnInit, AfterViewInit { }, 1); } }); - this.runVisualizationBehavior.$run.subscribe(id => { + this.runVisualizationBehavior.$run + .pipe(takeUntil(this.destroy$)) + .subscribe(id => { if (id && this.chartId === id) { - this.runVisualization(); + this.refreshService.sendRefresh(this.refreshType); this.defaultTime = resolveDefaultVisualizationTime(this.visualization); } }); - this.dashboardBehavior.$filterDashboard.subscribe(dashboardFilter => { + this.dashboardBehavior.$filterDashboard + .pipe(takeUntil(this.destroy$)) + .subscribe(dashboardFilter => { if (dashboardFilter && dashboardFilter.indexPattern === this.visualization.pattern.pattern) { mergeParams(dashboardFilter.filter, this.visualization.filterType).then(newFilters => { this.visualization.filterType = sanitizeFilters(newFilters); - this.runVisualization(); + this.refreshService.sendRefresh(this.refreshType); }); } }); - this.runVisualization(); + + this.timeFilterBehavior.$time + .pipe( + takeUntil(this.destroy$), + filter(time => !!time)) + .subscribe(time => { + if (time) { + this.onTimeFilterChange({ + timeFrom: time.from, + timeTo: time.to + }); + } + }); + this.defaultTime = resolveDefaultVisualizationTime(this.visualization); + if(!this.defaultTime){ + this.refreshService.sendRefresh(this.refreshType); + } } runVisualization() { this.loadingOption = true; - this.runVisualizationService.run(this.visualization).subscribe(resp => { + /*this.runVisualizationService.run(this.visualization).subscribe(resp => { this.loadingOption = false; this.runned.emit('runned'); this.data = resp; @@ -136,7 +342,27 @@ export class MapViewComponent implements OnInit, AfterViewInit { this.runned.emit('runned'); this.toastService.showError('Error', 'Error occurred while running visualization'); - }); + });*/ + + return this.runVisualizationService.run(this.visualization) + .pipe( + tap((data) => { + this.loadingOption = false; + this.runned.emit('runned'); + this.data = data; + this.onChartChange(); + this.error = false; + }), + map( data => data.length > 0 ? data[0] : [] ), + catchError(() => { + this.loadingOption = false; + this.error = true; + this.runned.emit('runned'); + this.toastService.showError('Error', + 'Error occurred while running visualization'); + return of([]); + }) + ); } /** @@ -232,7 +458,33 @@ export class MapViewComponent implements OnInit, AfterViewInit { onTimeFilterChange($event: TimeFilterType) { rebuildVisualizationFilterTime($event, this.visualization.filterType).then(filters => { this.visualization.filterType = filters; - this.runVisualization(); + this.refreshService.sendRefresh(this.refreshType); }); } + + initMap() { + // const osmLink = 'OpenStreetMap'; + // let osmUrl: string; + // let osmAttrib: string; + // if (this.mapOption && this.mapOption.tiles.length === 0) { + // osmUrl = 'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'; + // osmAttrib = '© ' + osmLink + ' Contributors'; + // } else { + // osmUrl = this.mapOption.tiles[0].urlTemplate; + // osmAttrib = this.mapOption.tiles[0].options.attribution; + // } + // const osmMap = L.tileLayer(osmUrl, {attribution: osmAttrib}); + this.map = new L.map(this.mapId, { + // layers: [osmMap], // only add one! + minZoom: 1 + }); + this.markersLayer = new L.LayerGroup(); + this.sub.next(true); + } + + ngOnDestroy(): void { + this.refreshService.stopInterval(); + this.destroy$.next(); + this.destroy$.complete(); + } } diff --git a/frontend/src/app/graphic-builder/shared/components/viewer/metric-view/metric-view.component.html b/frontend/src/app/graphic-builder/shared/components/viewer/metric-view/metric-view.component.html index c38f214e1..041ade0f7 100644 --- a/frontend/src/app/graphic-builder/shared/components/viewer/metric-view/metric-view.component.html +++ b/frontend/src/app/graphic-builder/shared/components/viewer/metric-view/metric-view.component.html @@ -12,7 +12,8 @@
-
+
-
diff --git a/frontend/src/app/graphic-builder/shared/components/viewer/metric-view/metric-view.component.ts b/frontend/src/app/graphic-builder/shared/components/viewer/metric-view/metric-view.component.ts index 4f39e2a24..5f383fd82 100644 --- a/frontend/src/app/graphic-builder/shared/components/viewer/metric-view/metric-view.component.ts +++ b/frontend/src/app/graphic-builder/shared/components/viewer/metric-view/metric-view.component.ts @@ -1,10 +1,12 @@ -import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core'; +import {ChangeDetectionStrategy, Component, EventEmitter, Input, OnDestroy, OnInit, Output} from '@angular/core'; import {UtmToastService} from '../../../../../shared/alert/utm-toast.service'; import {DashboardBehavior} from '../../../../../shared/behaviors/dashboard.behavior'; import {EchartClickAction} from '../../../../../shared/chart/types/action/echart-click-action'; import {MetricResponse} from '../../../../../shared/chart/types/metric/metric-response'; import {VisualizationType} from '../../../../../shared/chart/types/visualization.type'; -import {ElasticFilterDefaultTime} from '../../../../../shared/components/utm/filters/elastic-filter-time/elastic-filter-time.component'; +import { + ElasticFilterDefaultTime +} from '../../../../../shared/components/utm/filters/elastic-filter-time/elastic-filter-time.component'; import {ChartTypeEnum} from '../../../../../shared/enums/chart-type.enum'; import {TimeFilterType} from '../../../../../shared/types/time-filter.type'; import {mergeParams, sanitizeFilters} from '../../../../../shared/util/elastic-filter.util'; @@ -14,14 +16,20 @@ import {RunVisualizationService} from '../../../services/run-visualization.servi import {UtmChartClickActionService} from '../../../services/utm-chart-click-action.service'; import {rebuildVisualizationFilterTime} from '../../../util/chart-filter/chart-filter.util'; import {resolveDefaultVisualizationTime} from '../../../util/visualization/visualization-render.util'; +import {Observable, of, Subject} from "rxjs"; +import {RefreshService, RefreshType} from "../../../../../shared/services/util/refresh.service"; +import {catchError, filter, switchMap, takeUntil, tap} from 'rxjs/operators'; +import {TimeFilterBehavior} from "../../../../../shared/behaviors/time-filter.behavior"; @Component({ selector: 'app-metric-view', templateUrl: './metric-view.component.html', - styleUrls: ['./metric-view.component.scss'] + styleUrls: ['./metric-view.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush }) -export class MetricViewComponent implements OnInit { +export class MetricViewComponent implements OnInit, OnDestroy { data: MetricResponse[]; + data$: Observable; @Input() visualization: VisualizationType; @Input() building: boolean; @Output() runned = new EventEmitter(); @@ -35,37 +43,70 @@ export class MetricViewComponent implements OnInit { kpis: KpiValues[] = []; METRIC_CHART = ChartTypeEnum.METRIC_CHART; defaultTime: ElasticFilterDefaultTime; + refreshType: string; + destroy$ = new Subject(); constructor(private runVisualizationService: RunVisualizationService, private runVisualizationBehavior: RunVisualizationBehavior, private toastService: UtmToastService, private dashboardBehavior: DashboardBehavior, - private utmChartClickActionService: UtmChartClickActionService) { + private utmChartClickActionService: UtmChartClickActionService, + private timeFilterBehavior: TimeFilterBehavior, + private refreshService: RefreshService) { } ngOnInit() { - this.runVisualization(); this.defaultTime = resolveDefaultVisualizationTime(this.visualization); - this.runVisualizationBehavior.$run.subscribe((id) => { + this.refreshType = `${this.chartId}`; + + this.data$ = this.refreshService.refresh$ + .pipe( + filter((refreshType) => refreshType === RefreshType.ALL || + refreshType === this.refreshType), + switchMap((value, index) => this.runVisualization())); + + this.runVisualizationBehavior.$run + .pipe(takeUntil(this.destroy$)) + .subscribe((id) => { if (id && this.chartId === id) { - this.runVisualization(); + this.refreshService.sendRefresh(this.refreshType); this.defaultTime = resolveDefaultVisualizationTime(this.visualization); } }); - this.dashboardBehavior.$filterDashboard.subscribe(dashboardFilter => { + + this.dashboardBehavior.$filterDashboard + .pipe(takeUntil(this.destroy$)) + .subscribe(dashboardFilter => { if (dashboardFilter && dashboardFilter.indexPattern === this.visualization.pattern.pattern) { mergeParams(dashboardFilter.filter, this.visualization.filterType).then(newFilters => { this.visualization.filterType = sanitizeFilters(newFilters); - this.runVisualization(); + this.refreshService.sendRefresh(this.refreshType); + }); + } + }); + + this.timeFilterBehavior.$time + .pipe( + takeUntil(this.destroy$), + filter(time => !!time)) + .subscribe(time => { + if (time) { + this.onTimeFilterChange({ + timeFrom: time.from, + timeTo: time.to }); } }); + + if (!this.defaultTime) { + this.refreshService.sendRefresh(this.refreshType); + } } runVisualization() { this.runningChart = true; - this.runVisualizationService.run(this.visualization).subscribe(data => { + /*this.runVisualizationService.run(this.visualization).subscribe(data => { this.runningChart = false; this.runned.emit('runned'); this.data = data; @@ -79,7 +120,28 @@ export class MetricViewComponent implements OnInit { this.error = true; this.toastService.showError('Error', 'Error occurred while running visualization'); - }); + });*/ + + return this.runVisualizationService.run(this.visualization) + .pipe( + tap((data) => { + this.runningChart = false; + this.runned.emit('runned'); + this.data = data; + this.error = false; + this.extractKpiValues().then(kpis => { + this.kpis = kpis; + }); + }), + catchError(() => { + this.runningChart = false; + this.runned.emit('runned'); + this.error = true; + this.toastService.showError('Error', + 'Error occurred while running visualization'); + return of([]); + }) + ); } chartEvent($event: KpiValues) { @@ -131,9 +193,15 @@ export class MetricViewComponent implements OnInit { onTimeFilterChange($event: TimeFilterType) { rebuildVisualizationFilterTime($event, this.visualization.filterType).then(filters => { this.visualization.filterType = filters; - this.runVisualization(); + this.refreshService.sendRefresh(this.refreshType); }); } + + ngOnDestroy(): void { + this.refreshService.stopInterval(); + this.destroy$.next(); + this.destroy$.complete(); + } } export class KpiValues { diff --git a/frontend/src/app/graphic-builder/shared/components/viewer/table-view/table-view.component.html b/frontend/src/app/graphic-builder/shared/components/viewer/table-view/table-view.component.html index af4b772a6..f5fa5e626 100644 --- a/frontend/src/app/graphic-builder/shared/components/viewer/table-view/table-view.component.html +++ b/frontend/src/app/graphic-builder/shared/components/viewer/table-view/table-view.component.html @@ -38,7 +38,8 @@
-
+
; @Input() visualization: VisualizationType; @Input() building: boolean; @@ -69,38 +71,69 @@ export class TableViewComponent implements OnInit, AfterViewInit, OnChanges { screenResolution = window.screen.width; searching = false; empty = false; + refreshType: string; + destroy$ = new Subject(); + data$: Observable; constructor(private runVisualizationService: RunVisualizationService, private utmChartClickActionService: UtmChartClickActionService, private runVisualizationBehavior: RunVisualizationBehavior, - private cdr: ChangeDetectorRef, private dashboardBehavior: DashboardBehavior, - private toastService: UtmToastService) { + private toastService: UtmToastService, + private refreshService: RefreshService, + private timeFilterBehavior: TimeFilterBehavior) { } ngOnInit() { this.defaultTime = resolveDefaultVisualizationTime(this.visualization); - this.runVisualizationBehavior.$run.subscribe(id => { + this.refreshType = `${this.chartId}`; + + this.data$ = this.refreshService.refresh$ + .pipe( + filter((refreshType) => refreshType === RefreshType.ALL || + refreshType === this.refreshType), + switchMap((value, index) => this.runVisualization())); + + this.runVisualizationBehavior.$run + .pipe(takeUntil(this.destroy$)) + .subscribe(id => { if (id && this.chartId === id) { - this.runVisualization(); + this.refreshService.sendRefresh(this.refreshType); this.defaultTime = resolveDefaultVisualizationTime(this.visualization); } }); - this.dashboardBehavior.$filterDashboard.subscribe(dashboardFilter => { + this.dashboardBehavior.$filterDashboard + .pipe(takeUntil(this.destroy$)) + .subscribe(dashboardFilter => { if (dashboardFilter && dashboardFilter.indexPattern === this.visualization.pattern.pattern) { mergeParams(dashboardFilter.filter, this.visualization.filterType).then(newFilters => { this.visualization.filterType = sanitizeFilters(newFilters); - this.runVisualization(); + this.refreshService.sendRefresh(this.refreshType); }); } }); + this.timeFilterBehavior.$time + .pipe( + takeUntil(this.destroy$), + filter(time => !!time)) + .subscribe(time => { + if (time) { + this.onTimeFilterChange({ + timeFrom: time.from, + timeTo: time.to + }); + } + }); + if (!this.defaultTime) { - this.runVisualization(); + this.refreshService.sendRefresh(this.refreshType); } } ngOnChanges(changes: SimpleChanges) { + this.refreshType = `${this.chartId}`; + if (changes.height && changes.height.currentValue !== 'NaNpx') { const currentGridHeight = Number(changes.height.currentValue.replace('px', '')); if (!isNaN(currentGridHeight)) { @@ -111,10 +144,6 @@ export class TableViewComponent implements OnInit, AfterViewInit, OnChanges { } } - ngAfterViewInit(): void { - this.cdr.detectChanges(); - } - calcItemsPerPage(): number { this.tableOptions = this.visualization.chartConfig; if (this.exportFormat) { @@ -131,8 +160,7 @@ export class TableViewComponent implements OnInit, AfterViewInit, OnChanges { runVisualization() { this.loadingOption = true; const request = this.visualization.page ? this.visualization.page : {}; - console.log(request); - this.runVisualizationService.run(this.visualization, request).subscribe(data => { + /*this.runVisualizationService.run(this.visualization).subscribe(data => { this.empty = data.length === 0 || data[0].rows.length === 0; this.data = data.length > 0 ? data[0] : []; if (!this.empty) { @@ -156,7 +184,39 @@ export class TableViewComponent implements OnInit, AfterViewInit, OnChanges { this.runned.emit('runned'); this.toastService.showError('Error', 'Error occurred while running visualization'); - }); + });*/ + + return this.runVisualizationService.run(this.visualization, request) + .pipe( + tap((data) => { + this.empty = data.length === 0 || data[0].rows.length === 0; + this.data = data.length > 0 ? data[0] : []; + if (!this.empty) { + this.tableOptions = this.visualization.chartConfig; + this.itemsPerPage = this.itemsPerPage = this.calcItemsPerPage(); + this.pageStart = 0; + this.pageEnd = this.itemsPerPage; + this.responseRows = this.data.rows; + this.totalItems = this.data.rows.length; + this.mapMetric.clear(); + if (this.tableOptions && this.tableOptions.totalFunction) { + this.applyFunction(); + } + } + this.loadingOption = false; + this.error = false; + this.runned.emit('runned'); + }), + map( data => data.length > 0 ? data[0] : [] ), + catchError(() => { + this.loadingOption = false; + this.error = true; + this.runned.emit('runned'); + this.toastService.showError('Error', + 'Error occurred while running visualization'); + return of([]); + }) + ); } loadPage(page: number) { @@ -285,7 +345,7 @@ export class TableViewComponent implements OnInit, AfterViewInit, OnChanges { onTimeFilterChange($event: TimeFilterType) { rebuildVisualizationFilterTime($event, this.visualization.filterType).then(filters => { this.visualization.filterType = filters; - this.runVisualization(); + this.refreshService.sendRefresh(this.refreshType); }); } @@ -423,4 +483,10 @@ export class TableViewComponent implements OnInit, AfterViewInit, OnChanges { }); } + ngOnDestroy(): void { + this.refreshService.stopInterval(); + this.destroy$.next(); + this.destroy$.complete(); + } + } diff --git a/frontend/src/app/graphic-builder/shared/components/viewer/utm-viewer/utm-viewer.component.html b/frontend/src/app/graphic-builder/shared/components/viewer/utm-viewer/utm-viewer.component.html index e7d2c31db..af3ab41bf 100644 --- a/frontend/src/app/graphic-builder/shared/components/viewer/utm-viewer/utm-viewer.component.html +++ b/frontend/src/app/graphic-builder/shared/components/viewer/utm-viewer/utm-viewer.component.html @@ -1,5 +1,5 @@
+justify-content-center align-items-center viewer-container">
+ + + + + +
+ + +
+
+
+ + + +
+
+
+
+ +
+
+ diff --git a/frontend/src/app/shared/components/utm/filters/refresh-filter/refresh-filter.component.ts b/frontend/src/app/shared/components/utm/filters/refresh-filter/refresh-filter.component.ts new file mode 100644 index 000000000..f45c78269 --- /dev/null +++ b/frontend/src/app/shared/components/utm/filters/refresh-filter/refresh-filter.component.ts @@ -0,0 +1,38 @@ +import {Component, OnInit} from '@angular/core'; +import {LocalStorageService} from 'ngx-webstorage'; +import {RefreshService} from '../../../../services/util/refresh.service'; +import {TIME_DASHBOARD_REFRESH} from "../../../../constants/time-refresh.const"; + +export const TIME_REFRESH = 'TIME_REFRESH'; +@Component({ + selector: 'app-refresh-filter', + templateUrl: 'refresh-filter.component.html', + styleUrls: ['refresh-filter.component.css'] +}) +export class RefreshFilterComponent implements OnInit { + show = false; + refreshIntervals = [ + ... TIME_DASHBOARD_REFRESH, + { time: 'Paused', milliseconds: 0 }, + ]; + selectedInterval = 0; + constructor(private refreshService: RefreshService, + private storage: LocalStorageService) { + } + ngOnInit(): void { + const time = this.storage.retrieve(TIME_REFRESH); + if (time && time > 0) { + this.selectedInterval = time; + this.apply(); + } + } + + apply() { + this.storage.store(TIME_REFRESH, this.selectedInterval); + if (this.selectedInterval !== 0) { + this.refreshService.setRefreshInterval(this.selectedInterval); + } else { + this.refreshService.stopInterval(); + } + } +} diff --git a/frontend/src/app/shared/services/util/refresh.service.ts b/frontend/src/app/shared/services/util/refresh.service.ts new file mode 100644 index 000000000..48d812f1d --- /dev/null +++ b/frontend/src/app/shared/services/util/refresh.service.ts @@ -0,0 +1,58 @@ +import { Injectable } from '@angular/core'; +import { BehaviorSubject, interval, Observable, Subscription } from 'rxjs'; +import { filter } from 'rxjs/operators'; + +export enum RefreshType { + ALL = 'ALL', + CHART_ALERT_BY_STATUS = 'CHART_ALERT_BY_STATUS', + CHART_COMMON_PIE_SEVERITY = 'CHART_COMMON_PIE_SEVERITY', + CHART_ALERT_BY_CATEGORY = 'CHART_ALERT_BY_CATEGORY', + CHART_COMMON_TABLE_TOP_ALERT = 'CHART_COMMON_TABLE_TOP_ALERT', + CHART_COMMON_PIE_EVENT = 'CHART_COMMON_PIE_EVENT', + CHART_COMMON_TABLE_TOP_EVENT = 'CHART_COMMON_TABLE_TOP_EVENT', + CHART_EVENT_IN_TIME = 'CHART_EVENT_IN_TIME', +} + +@Injectable({ + providedIn: 'root' +}) +export class RefreshService { + private refreshSubject = new BehaviorSubject(null); + private refreshInterval = 0; + private interval$: Observable; + private subscription: Subscription; + + constructor() { + } + + startInterval() { + this.subscription = this.interval$ + .subscribe(() => { + this.sendRefresh(); + }); + } + + get refresh$() { + return this.refreshSubject.asObservable() + .pipe(filter(value => !!value)); + } + + sendRefresh(type: RefreshType | string = RefreshType.ALL ) { + this.refreshSubject.next(type); + } + + stopInterval() { + this.refreshSubject.next(null); + if (this.subscription) { + this.subscription.unsubscribe(); + console.log('refresh stopped'); + } + } + + setRefreshInterval(refreshInterval: number) { + this.stopInterval(); + this.refreshInterval = refreshInterval; + this.interval$ = interval(this.refreshInterval); + this.startInterval(); + } +} diff --git a/frontend/src/app/shared/utm-shared.module.ts b/frontend/src/app/shared/utm-shared.module.ts index 1dbec189f..9d54ae529 100644 --- a/frontend/src/app/shared/utm-shared.module.ts +++ b/frontend/src/app/shared/utm-shared.module.ts @@ -222,6 +222,7 @@ import {TimePeriodPipe} from "./pipes/time-period.pipe"; import {ModalAddNoteComponent} from "./components/utm/util/modal-add-note/modal-add-note.component"; import { UtmCollapsibleTextComponent } from './components/utm/util/utm-collapsible-text/utm-collapsible-text.component'; import {FilterPipe} from "./pipes/filter.pipe"; +import {RefreshFilterComponent} from "./components/utm/filters/refresh-filter/refresh-filter.component"; @NgModule({ @@ -367,7 +368,8 @@ import {FilterPipe} from "./pipes/filter.pipe"; ModalAddNoteComponent, TimePeriodPipe, UtmCollapsibleTextComponent, - FilterPipe + FilterPipe, + RefreshFilterComponent ], exports: [ IndexPatternCreateComponent, @@ -466,7 +468,8 @@ import {FilterPipe} from "./pipes/filter.pipe"; ModalAddNoteComponent, TimePeriodPipe, UtmCollapsibleTextComponent, - FilterPipe + FilterPipe, + RefreshFilterComponent ], entryComponents: [ LoginComponent, diff --git a/frontend/src/environments/environment.ts b/frontend/src/environments/environment.ts index 586cb5a3b..84b64075f 100644 --- a/frontend/src/environments/environment.ts +++ b/frontend/src/environments/environment.ts @@ -4,8 +4,8 @@ export const environment = { production: false, - // SERVER_API_URL: 'https://192.168.1.18/', - SERVER_API_URL: 'http://localhost:8080/', + SERVER_API_URL: 'https://192.168.1.20/', + //SERVER_API_URL: 'http://localhost:8080/', SERVER_API_CONTEXT: '', SESSION_AUTH_TOKEN: window.location.host.split(':')[0].toLocaleUpperCase(), WEBSOCKET_URL: '//localhost:8080', diff --git a/version.yml b/version.yml index fdbb687c1..9801dbb55 100644 --- a/version.yml +++ b/version.yml @@ -1 +1 @@ -version: 10.9.0 \ No newline at end of file +version: 10.9.1 \ No newline at end of file