From 5ea2a4a92ffbe58b8650642d8925b229f35bb20f Mon Sep 17 00:00:00 2001 From: Gunnsteinn Hall Date: Tue, 11 Dec 2018 14:26:23 +0000 Subject: [PATCH] Paginate device vulnerabilities under device detail --- src/organization/device/device.service.ts | 59 ++++++++++++- .../device/device_detail.component.html | 46 +++++----- .../device/device_detail.component.ts | 83 ++++++++++++++++++- src/organization/device/device_response.ts | 32 ++++++- 4 files changed, 192 insertions(+), 28 deletions(-) diff --git a/src/organization/device/device.service.ts b/src/organization/device/device.service.ts index 88eb39505e5..f755fbceacd 100644 --- a/src/organization/device/device.service.ts +++ b/src/organization/device/device.service.ts @@ -1,5 +1,11 @@ import {Injectable} from '@angular/core'; -import {DeviceListResponse, DeviceDetailResponse, DeviceListItem} from './device_response'; +import { + DeviceListResponse, + DeviceDetailResponse, + DeviceListItem, + DeviceVulnerabilityRequest, + DeviceVulnerabilityResponse, DeviceVulnerabilityItem +} from './device_response'; import {AccountService} from 'account/account.service'; import {Observable, Subscription} from 'rxjs'; import {BasicListRequest} from 'common/request'; @@ -8,6 +14,7 @@ import {Filter, ColumnSort} from 'common/object'; import {SpinnerService} from 'common/spinner/spinner.service'; import {CreateBaselineFilterForm, RemoveBaselineFilterForm} from "../benchmark_rules/benchmark_rules_response"; import { TableCache } from 'common/cache/table-cache'; +import {LoggedInUserDetailItem, LoggedInUserDetailResponse} from "../loggedin_user/loggedin_user_response"; @Injectable() export class DeviceService extends TableCache { @@ -26,12 +33,29 @@ export class DeviceService extends TableCache { private userFilters: Array; private profileFilterSubscription: Subscription; + // For device vulnerabilities. + public isVulnDetailRequesting: boolean; + private vulnDetailRequest: DeviceVulnerabilityRequest; + private vulnDetailResponse: DeviceVulnerabilityResponse; + private vulnDetailItems: Array; + private vulnDetailUserFilters: Array; + public vulnDetailResponse$: Observable; + private vulnDetailSorting: Array; + private vulnDetailObserver: any; + private vulnDetailPage: number; + constructor(public accountService: AccountService, public flashService: FlashService, private spinnerService: SpinnerService) { super(TableCache.PAGE_ROWS, TableCache.REQUEST_LIMIT); this.listResponse$ = new Observable(observer => this.listObserver = observer).share(); this.detailResponse$ = new Observable(observer => this.detailObserver = observer).share(); this.sorting$ = new Observable>(observer => this.sortingObserver = observer).share(); + this.vulnDetailResponse$ = new Observable(observer => this.vulnDetailObserver = observer).share(); + this.vulnDetailItems = []; + this.vulnDetailRequest = new DeviceVulnerabilityRequest(); + this.vulnDetailUserFilters = []; + this.vulnDetailPage = 0; + this.userFilters = []; } @@ -44,6 +68,39 @@ export class DeviceService extends TableCache { return this.accountService.executeGet(`/organization/${this.accountService.getOrganizationId()}/device/${id}/detail`); } + getVulnerabilities(deviceId: number, page: number, limit: number) { + if (this.vulnDetailPage == page) { + return; + } + this.spinnerService.setState(true); + this.isVulnDetailRequesting = true; + this.vulnDetailRequest = new DeviceVulnerabilityRequest(); + this.vulnDetailRequest.sort = this.vulnDetailSorting; + this.vulnDetailRequest.page = page; + this.vulnDetailRequest.limit = limit; + this.vulnDetailRequest.device_id = deviceId; + + this.accountService.executePost(`/organization/${this.accountService.getOrganizationId()}/device/vulnerability/list`, this.vulnDetailRequest) + .map(p => p.json()) + .subscribe(p => { + this.isVulnDetailRequesting = false; + this.vulnDetailResponse = p; + if (!this.vulnDetailResponse.items) { + this.vulnDetailResponse.items = []; + } + this.vulnDetailPage = page; + this.vulnDetailItems = this.vulnDetailResponse.items; + this.vulnDetailObserver.next(this.vulnDetailResponse); + this.spinnerService.setState(false); + }, + error => { + this.isVulnDetailRequesting = false; + this.spinnerService.setState(false); + this.accountService.handleError(error); + }); + + } + getLoggedInUsers(id: string, userId: number) { return this.accountService.executePost(`/organization/${this.accountService.getOrganizationId()}/device/loggedinuser_detail/${userId}`, null); } diff --git a/src/organization/device/device_detail.component.html b/src/organization/device/device_detail.component.html index ef2c770e957..5315ee1bf29 100644 --- a/src/organization/device/device_detail.component.html +++ b/src/organization/device/device_detail.component.html @@ -92,7 +92,7 @@

Credentials2

-
- - - - - - - - +
+ + +
+ + + + + + - - - - - - - - + + + + + + + - -
CVE IDSummaryScoreDiscoveredPublished
+
{{col.header}}
+
{{ item.cve_id }}{{ item.summary }}{{ item.score|mynumber }}{{ item.created_at*1000 | moment:'MMM Do YYYY HH:mm' }}{{ item.published_at*1000 | moment:'MMM Do YYYY HH:mm' }}
{{ rowData.cve_id }}{{ rowData.summary }}{{ rowData.score }}{{ rowData.published_at*1000 | moment:'MMM Do YYYY HH:mm' }}
+ +
diff --git a/src/organization/device/device_detail.component.ts b/src/organization/device/device_detail.component.ts index 1c23052f417..d3222d6d555 100644 --- a/src/organization/device/device_detail.component.ts +++ b/src/organization/device/device_detail.component.ts @@ -1,8 +1,13 @@ /// /// /// -import {Component, OnInit} from '@angular/core'; -import {DeviceDetailResponse, DeviceDetailItem, DeviceAssigmentItem} from './device_response'; +import {Component, OnInit, Renderer, ViewChild} from '@angular/core'; +import { + DeviceDetailResponse, + DeviceDetailItem, + DeviceAssigmentItem, + DeviceVulnerabilityResponse, DeviceVulnerabilityItem +} from './device_response'; import {BasicResponse} from '../../common/response'; import {DeviceService} from './device.service'; import {Router, ActivatedRoute} from '@angular/router'; @@ -14,6 +19,9 @@ import {SelectItem} from 'primeng/primeng'; import {Severity} from '../../common/enum'; import {BreadcrumbService} from '../../common/breadcrumb/breadcrumb.service'; import {Breadcrumb} from '../../common/breadcrumb/breadcrumb.model'; +import {Table} from "primeng/table"; +import {ColumnSort, ColumnWithSort} from "../../common/object"; +import { isEqual, cloneDeep } from 'lodash'; declare var jQuery:JQueryStatic; @@ -24,6 +32,16 @@ declare var jQuery:JQueryStatic; }) export class DeviceDetailComponent implements OnInit{ + @ViewChild('ptable') ptable: Table; + private HEADER_HEIGHT: number = 0; + private ROWS: number = 20; + private vulnCols: Array; + private vulnTotalRecords : number; + private vulnItems: Array; + private vulnResponse: DeviceVulnerabilityResponse; + private multiSortMeta: Array = null; + private savedScrollPos: number = 0; + private response: DeviceDetailResponse; private deviceId: string; private assignModel: DeviceAssignModel; @@ -38,14 +56,29 @@ export class DeviceDetailComponent implements OnInit{ private exclusionProfileId: number; private exclusionComment: string; - constructor(private service: DeviceService, private route: ActivatedRoute, private router: Router, private spinnerService: SpinnerService, private breadcrumbService: BreadcrumbService) { + private vulnerabilityEnabled: boolean; + + constructor(private service: DeviceService, private route: ActivatedRoute, private router: Router, private spinnerService: SpinnerService, private breadcrumbService: BreadcrumbService, + private renderer: Renderer) { this.deviceId = route.snapshot.params['id']; this.response = null; + this.vulnResponse = null; this.needsConnectionString = false; this.tabSelected = 1; + this.vulnTotalRecords = 0; + this.vulnerabilityEnabled = false; } ngOnInit() { + this.vulnCols = [ + { field: 'cve_id', header: 'CVE', order: 0, width: '120px', disabled: true }, + { field: 'summary', header: 'Summary', order: 0, width: '200px', disabled: true }, + { field: 'score', header: 'Score', order: 0, width: '120px', disabled: true }, + { field: 'published_at', header: 'Published at', order: 0, width: '140px', disabled: true } + ]; + + this.vulnerabilityEnabled = this.service.accountService.isVulnerabilityEnabled(this.service.accountService.getCurrentOrganization()); + this.refresh(); } @@ -83,15 +116,55 @@ export class DeviceDetailComponent implements OnInit{ p => { this.setResponse(p); this.spinnerService.setState(false); + this.loadVulnerabilityData(); }, err => { this.spinnerService.setState(false); this.service.accountService.handleError(err); } ); + this.service.vulnDetailResponse$.subscribe(p => { + this.spinnerService.setState(false); + this.vulnResponse = p; + this.vulnTotalRecords = this.vulnResponse.total; + this.vulnItems = this.vulnResponse.items; + }, + err => { + this.spinnerService.setState(false); + this.service.accountService.handleError(err); + } + ); + jQuery("#refresh-button").blur(); } + loadVulnerabilityData() { + this.service.getVulnerabilities(parseInt(this.deviceId), 1, this.ROWS); + } + + getVulnColumnWidth(field) { + let fieldd = this.vulnCols.filter(function (item) { + return item.field === field; + })[0]; + return fieldd.width; + } + + onPage(event) { + this.setFocusToList(); + this.ptable.setScrollPosition(0); + this.savedScrollPos = 0; + } + + + vulnSortingChanged(event) { + //loadMore, ptable in case with lazy and virtual scroll enabled calls the same function + this.service.getVulnerabilities(parseInt(this.deviceId), (event.first ? event.first : 0) / this.ROWS + 1, this.ROWS); + } + + setFocusToList() { + this.renderer.invokeElementMethod(jQuery(".ui-table-virtual-scroller"), 'focus', []); + } + clickRequestCheckin(event) { event.preventDefault(); this.spinnerService.setState(true); @@ -208,6 +281,7 @@ export class DeviceDetailComponent implements OnInit{ ); } + setResponse(r: DeviceDetailResponse) { this.benchmarks = []; @@ -262,6 +336,9 @@ export class DeviceDetailComponent implements OnInit{ } setTabSelected(index: number) { + if (index == 2) { + this.loadVulnerabilityData(); + } jQuery("#tab" + this.tabSelected).removeClass("active"); this.tabSelected = index; jQuery("#tab" + this.tabSelected).addClass("active"); diff --git a/src/organization/device/device_response.ts b/src/organization/device/device_response.ts index 49adadf015c..2a4d712c6a5 100644 --- a/src/organization/device/device_response.ts +++ b/src/organization/device/device_response.ts @@ -2,6 +2,7 @@ import {BasicListResponse} from '../../common/response'; import {LoggedInUserDetailItem} from '../loggedin_user/loggedin_user_response'; import {GroupPolicyListItem} from "../group_policy/group_policy_response"; import {SoftwareListItem} from "../software/software_response"; +import {BasicListRequest} from "../../common/request"; export interface DeviceListResponse extends BasicListResponse { items: Array @@ -51,13 +52,16 @@ export interface DeviceListItem { } export interface DeviceDetailResponse { - item: DeviceDetailItem, - available_assignments: Array - assignments: Array, - loggedin_users: Array + item: DeviceDetailItem; + available_assignments: Array; + assignments: Array; + loggedin_users: Array; + num_vulnerabilities: number; software_inventory: Array; } + + export interface DeviceDetailItem { id: number, hostname: string, @@ -125,4 +129,24 @@ export interface DeviceSoftwareListItem { version: string; publisher: string; created_at: number; +} + +export class DeviceVulnerabilityRequest extends BasicListRequest { + device_id: number; +} + +export interface DeviceVulnerabilityItem { + id: number; + published_at: number; + cve_id: string; + score: number; + summary: string; + summary_full: string; + url: string; + num_devices: number; + cvss_version: number; +} + +export interface DeviceVulnerabilityResponse extends BasicListResponse { + items: Array; } \ No newline at end of file