Skip to content

Commit

Permalink
Merge pull request primefaces#554 from Nanitor/trendmetrics
Browse files Browse the repository at this point in the history
[NNTR-582] Trend metrics UI
  • Loading branch information
Gunnsteinn Hall authored Jul 2, 2020
2 parents ee10823 + 73dcb9e commit 5728db6
Show file tree
Hide file tree
Showing 9 changed files with 226 additions and 59 deletions.
14 changes: 10 additions & 4 deletions src/common/nancommon.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';
import { RouterModule } from '@angular/router';
import { CommonModule } from '@angular/common';
import { PipeModule } from '../pipes/pipe.module';
import { InfiniteScrollModule } from 'angular2-infinite-scroll';
import { FullscreenNavigator } from './fullscreen/fullscreen-navigator.component';
import { SortableTableComponent } from './sortable_table/sortable-table.component';
Expand All @@ -23,7 +24,7 @@ import { MenuRunnerComponent } from './menurunner.component';
import { InlineEditableComponent } from './inline-editable/inline-editable.component';
import { RuleFormComponent } from '../organization/admin/device_label_rules/rule_form.component';
import { NotificationRuleFormComponent } from '../organization/admin/notification/rule_form.component';
import { DropdownModule, TreeModule, TriStateCheckboxModule, TooltipModule, RadioButtonModule } from 'primeng/primeng';
import { CalendarModule, DropdownModule, TreeModule, TriStateCheckboxModule, TooltipModule, RadioButtonModule } from 'primeng/primeng';
import { LabelService } from '../organization/admin/label/label.service';
import { LabelCreateDialogComponent } from '../organization/admin/label/label_create_dialog.component';
import { OSFilterSectionComponent } from './side_filter/side_filter_section/os-filter-section.component';
Expand All @@ -47,6 +48,7 @@ import { WhitelistingFilterComponent } from './filter_pane/whitelisting-filter.c
import { RiscFilterSectionComponent } from './side_filter/side_filter_section/risc-filter-section.component';
import { SwitchingFrameworkDialogComponent } from 'organization/issue/modal_dialog/switching_framework_dialog.component';
import { CompliancePDFDialogComponent } from 'organization/issue/modal_dialog/compliance_pdf_dialog.component';
import { TrendmetricsPdfReportDialogComponent } from 'organization/intelligence/trendmetrics_pdf_report_dialog.component';


@NgModule({
Expand Down Expand Up @@ -88,7 +90,8 @@ import { CompliancePDFDialogComponent } from 'organization/issue/modal_dialog/co
WhitelistingFilterComponent,
RiscFilterSectionComponent,
SwitchingFrameworkDialogComponent,
CompliancePDFDialogComponent
CompliancePDFDialogComponent,
TrendmetricsPdfReportDialogComponent
],
imports: [
// Angular modules
Expand All @@ -105,7 +108,9 @@ import { CompliancePDFDialogComponent } from 'organization/issue/modal_dialog/co
TooltipModule,
DragDropModule,
TriStateCheckboxModule,
RadioButtonModule
RadioButtonModule,
CalendarModule,
PipeModule
],
providers: [
LabelService
Expand Down Expand Up @@ -148,7 +153,8 @@ import { CompliancePDFDialogComponent } from 'organization/issue/modal_dialog/co
WhitelistingFilterComponent,
RiscFilterSectionComponent,
SwitchingFrameworkDialogComponent,
CompliancePDFDialogComponent
CompliancePDFDialogComponent,
TrendmetricsPdfReportDialogComponent
]
})

Expand Down
8 changes: 4 additions & 4 deletions src/organization/intelligence/intelligence_response.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,21 +129,21 @@ export interface Gpo {

// Trend metrics.

export interface TrendMetricsResponse {
export interface TrendmetricsResponse {
metric: string;
interval: string;
from?: number;
to?: number;
items: Array<TrendMetricsResponseItem>;
items: Array<TrendmetricsResponseItem>;
}

export interface TrendMetricsResponseItem {
export interface TrendmetricsResponseItem {
date: number;
dims: Map<string, string>;
values: any;
}

export interface TrendMetricsRequest {
export interface TrendmetricsRequest {
interval: string;
metric: string;
dims: Array<Array<string>>;
Expand Down
13 changes: 9 additions & 4 deletions src/organization/intelligence/trendmetrics.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -49,15 +49,18 @@
<h1 class="panel-title">
Trend Metrics
<button class="btn btn-sm btn-primary pull-right" (click)="refresh()" id="refresh-button" title="Refresh"><i class="fa fa-refresh"></i></button>
<button class="btn btn-sm btn-primary pull-right" (click)="clickOpenPdfReportDialog()" id="createPdfReport" title="Create PDF Report">
<i class="fa fa-file-pdf-o" aria-hidden="true"></i>
</button>
</h1>
</div>

<div class="panel-body">
<div class="no-data" *ngIf="!hasData">
<div class="no-data" *ngIf="!hasData || !dates.length">
<p>There is currently nothing to display.</p>
</div>

<div class="row" style="margin-bottom:60px">
<div class="row" style="margin-bottom:60px" *ngIf="dates.length > 0">
<div class="col-lg-6">
<div [ng2-highcharts]="charts.monitored_assets"></div>
</div>
Expand All @@ -66,7 +69,7 @@ <h1 class="panel-title">
</div>
</div>

<div class="table-responsive" [class.nanitor-hide]="!hasData">
<div class="table-responsive" [class.nanitor-hide]="!hasData || !dates.length || !items.monitored_assets && !items.device_issues && !items.security_checks">
<table class="table table-vertical-striped trendmetrics-report-table">
<thead>
<tr>
Expand Down Expand Up @@ -152,4 +155,6 @@ <h1 class="panel-title">
</div>
</div>
</div>
</div>
</div>

<trendmetrics-pdf-report-dialog></trendmetrics-pdf-report-dialog>
70 changes: 43 additions & 27 deletions src/organization/intelligence/trendmetrics.component.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,35 @@
import {Component, OnInit, ViewChild} from '@angular/core';
import {TrendMetricsService} from './trendmetrics.service';
import {BenchmarkScoreSimilarity, Device, Gpo, MisconfigurationsBenchmarkSummary, Rule, TrendMetricsResponseItem} from './intelligence_response';
import {TrendmetricsService} from './trendmetrics.service';
import {MisconfigurationsBenchmarkSummary, TrendmetricsResponseItem} from './intelligence_response';
import {SpinnerService} from '../../common/spinner/spinner.service';
import {AccountService} from '../../account/account.service';
import {BreadcrumbService} from '../../common/breadcrumb/breadcrumb.service';
import {Breadcrumb} from '../../common/breadcrumb/breadcrumb.model';
import { FlashService } from 'common/flash/flash.service';
import {StringUtils} from '../../common/utils/string-utils';
import * as moment from 'moment';
import { TrendmetricsPdfReportDialogComponent } from './trendmetrics_pdf_report_dialog.component';

interface TrendMetricsItem {
interface TrendmetricsItem {
dims: Map<string, string>;
subitems?: Array<TrendMetricsItem>;
subitems?: Array<TrendmetricsItem>;
dateValues?: Array<any>;
}

@Component({
selector: 'trendmetrics-list',
templateUrl: 'trendmetrics.component.html'
})
export class TrendMetricsComponent implements OnInit {
public items: {[metricName: string]: Array<TrendMetricsResponseItem>};
export class TrendmetricsComponent implements OnInit {
@ViewChild(TrendmetricsPdfReportDialogComponent, { static: true }) pdfReportDialog: TrendmetricsPdfReportDialogComponent;
public items: {[metricName: string]: Array<TrendmetricsResponseItem>};
public metrics: any;
benchmarkSummaries: {[categoryName: string]: Array<MisconfigurationsBenchmarkSummary>};
public hasData: boolean;
public stringUtils = StringUtils;
public dates: Array<number> = [];
public charts: {[metricName: string]: any};

constructor(public service: TrendMetricsService, private spinnerService: SpinnerService, private accountService: AccountService, private breadcrumbService: BreadcrumbService) {
constructor(public service: TrendmetricsService, private spinnerService: SpinnerService, private accountService: AccountService, private breadcrumbService: BreadcrumbService, private flashService: FlashService) {
}

ngOnInit() {
Expand All @@ -49,14 +51,18 @@ export class TrendMetricsComponent implements OnInit {
this.service.metricsResponses$.device_issues.subscribe(resp => {
this.items.device_issues = resp.items;
this.hasData = true;
this.metrics.device_isuses = {
patch: {},
vulnerability: {},
misconfiguration: {}
};

if (!resp.items) {
return;
if (resp.items) {
resp.items.forEach(item => {
this.addMetricsItem("device_issues", item);
});
}

resp.items.forEach(item => {
this.addMetricsItem("device_issues", item);
});
this.updateDates();

const issueType = this.service.filter['issue_type'] || '~';
Expand Down Expand Up @@ -86,14 +92,14 @@ export class TrendMetricsComponent implements OnInit {
this.service.metricsResponses$.monitored_assets.subscribe(resp => {
this.items.monitored_assets = resp.items;
this.hasData = true;
this.metrics.monitored_assets = {};

if (!resp.items) {
return;
if (resp.items) {
resp.items.forEach(item => {
this.addMetricsItem("monitored_assets", item);
});
}

resp.items.forEach(item => {
this.addMetricsItem("monitored_assets", item);
});
this.updateDates();

this.charts.monitored_assets = this.createChart(
Expand All @@ -107,14 +113,14 @@ export class TrendMetricsComponent implements OnInit {
this.service.metricsResponses$.security_checks.subscribe(resp => {
this.items.security_checks = resp.items;
this.hasData = true;
this.metrics.security_checks = {};

if (!resp.items) {
return;
if (resp.items) {
resp.items.forEach(item => {
this.addMetricsItem("security_checks", item);
});
}

resp.items.forEach(item => {
this.addMetricsItem("security_checks", item);
});
this.updateDates();
});

Expand All @@ -123,7 +129,7 @@ export class TrendMetricsComponent implements OnInit {
this.service.getSecurityCheckMetrics();
}

addMetricsItem(metric: string, item: TrendMetricsResponseItem) {
addMetricsItem(metric: string, item: TrendmetricsResponseItem) {
const dims = this.service.metricDimensions[metric].concat([this.service.secondaryDimension]);
let current = this.metrics[metric];
for (let dim of dims) {
Expand All @@ -140,8 +146,15 @@ export class TrendMetricsComponent implements OnInit {
}

createChart(title: string, dataTitle: string, metrics: any, dataFunc: (entry: any) => number) {
const series = Object.keys(metrics).map(secondary => {
const s = metrics[secondary];
if (!metrics) {
metrics = {};
}
let lines = Object.keys(metrics);
if (!lines.length) {
lines.push('~');
}
const series = lines.map(secondary => {
const s = metrics[secondary] || {};
const data = this.dates.map(date => [
date * 1000,
s[date] ? dataFunc(s[date]) : 0
Expand Down Expand Up @@ -170,7 +183,7 @@ export class TrendMetricsComponent implements OnInit {
tickInterval: 30 * 24 * 3600 * 1000,
tickLength: 0,
gridLineWidth: 1,
gridLineDashStyle: 'ShortDot',
gridLineDashStyle: 'ShortDot'
},
yAxis: [{
min: 0,
Expand Down Expand Up @@ -279,4 +292,7 @@ export class TrendMetricsComponent implements OnInit {
this.service.getSecurityCheckMetrics();
}

clickOpenPdfReportDialog() {
this.pdfReportDialog.show();
}
}
32 changes: 17 additions & 15 deletions src/organization/intelligence/trendmetrics.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ import {Filter, ColumnSort} from '../../common/object';
import {FlashService} from '../../common/flash/flash.service';
import {SpinnerService} from '../../common/spinner/spinner.service';
import {
TrendMetricsRequest, TrendMetricsResponse
TrendmetricsRequest, TrendmetricsResponse
} from './intelligence_response';
import { map, debounceTime, share } from 'rxjs/operators';

@Injectable()
export class TrendMetricsService {
public metricsResponses$: { device_issues: Observable<TrendMetricsResponse>; monitored_assets: Observable<TrendMetricsResponse>; security_checks: Observable<TrendMetricsResponse> };
export class TrendmetricsService {
public metricsResponses$: { device_issues: Observable<TrendmetricsResponse>; monitored_assets: Observable<TrendmetricsResponse>; security_checks: Observable<TrendmetricsResponse> };

public isListRequesting: boolean;
public metricsObservers: { device_issues: any; monitored_assets: any; security_checks: any } = {
Expand All @@ -36,26 +36,19 @@ export class TrendMetricsService {
public filter: { [filterDim: string]: string } = {};

private userFilters: any;
private request: TrendMetricsRequest;
private request: TrendmetricsRequest;

constructor(public accountService: AccountService, public flashService: FlashService, private spinnerService: SpinnerService) {
constructor(public accountService: AccountService, public flashService: FlashService, public spinnerService: SpinnerService) {
this.metricsResponses$ = {
device_issues: new Observable<TrendMetricsResponse>(observer => this.metricsObservers.device_issues = observer).pipe(share()),
monitored_assets: new Observable<TrendMetricsResponse>(observer => this.metricsObservers.monitored_assets = observer).pipe(share()),
security_checks: new Observable<TrendMetricsResponse>(observer => this.metricsObservers.security_checks = observer).pipe(share()),
device_issues: new Observable<TrendmetricsResponse>(observer => this.metricsObservers.device_issues = observer).pipe(share()),
monitored_assets: new Observable<TrendmetricsResponse>(observer => this.metricsObservers.monitored_assets = observer).pipe(share()),
security_checks: new Observable<TrendmetricsResponse>(observer => this.metricsObservers.security_checks = observer).pipe(share()),
};

this.request = null;
this.userFilters = {};
}

// Request update of intelligence data.
requestUpdate() {
let data = {};
data['request_update'] = true;
return this.accountService.executePost(`/organization/${this.accountService.getOrganizationId()}/intelligence/request_update`, data);
}

getMetrics(metric: string) {
this.spinnerService.setState(true);
this.isListRequesting = true;
Expand Down Expand Up @@ -102,4 +95,13 @@ export class TrendMetricsService {
getSecurityCheckMetrics() {
this.getMetrics("security_checks");
}

downloadPdfReport(fromDate: number, toDate: number, labelId: number) {
let path = `/organization/${this.accountService.getOrganizationId()}/trendmetrics/report`;
return this.accountService.executePostBlobParams(path, {
from: fromDate,
to: toDate,
label_id: labelId
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<div id="trendmetrics-pdf-report-dialog" class="modal fade" role="dialog" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h2 class="modal-title">Create PDF Report</h2>
</div>
<div class="modal-body" style="padding:40px 0; background-color: #ffffff">
<div style="display: inline-block; text-align: left;">
<div context class="form-inline form-group">
<label for="from-date">From</label>
{{' '}}
<select id="from-date" class="form-control" [(ngModel)]="dateRange[0]">
<option *ngFor="let date of availableDates" [ngValue]="date.valueOf() / 1000">{{date | moment:"MMM YYYY"}}</option>
</select>
{{' '}}
<label for="to-date">to</label>
{{' '}}
<select id="to-date" class="form-control" [(ngModel)]="dateRange[1]">
<option *ngFor="let date of availableDates" [ngValue]="date.valueOf() / 1000">{{date | moment:"MMM YYYY"}}</option>
</select>
</div>
<div class="label-picker">
<label for="device-label">Device label</label>
{{' '}}
<select id="device-label" class="form-control" [(ngModel)]="labelId">
<option [ngValue]="null">All</option>
<option *ngFor="let label of labels" [ngValue]="label.id">{{label.name}}</option>
</select>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" href="#" data-dismiss="modal" class="btn btn-default btn-lg btn-lg-width">Cancel</button>
<button type="button" class="btn btn-lg btn-success btn-lg-width" (click)="savePdfReport()">Save Report</button>
</div>
</div>
</div>
</div>
Loading

0 comments on commit 5728db6

Please sign in to comment.