From 6f5b9533b333521f7dc32b1879082d7bdaca71c5 Mon Sep 17 00:00:00 2001 From: bryanmontalvan Date: Wed, 10 Aug 2022 14:22:02 -0400 Subject: [PATCH] mgr/dashboard: dashboard-v3: inventory card This commit starts the inventory-card which is one of the cards which will be located in the landing-page revamp tracker: https://tracker.ceph.com/issues/58065 Signed-off-by: bryanmontalvan Signed-off-by: Nizamudeen A Signed-off-by: Pedro Gonzalez Gomez (cherry picked from commit c63523119dee6cc82ffae5162f2270ac7d06d02f) --- .../card-row/card-row.component.html | 167 ++++++++++++++++++ .../card-row/card-row.component.scss | 0 .../card-row/card-row.component.spec.ts | 23 +++ .../card-row/card-row.component.ts | 34 ++++ .../ceph/new-dashboard/dashboard.module.ts | 11 +- .../dashboard/dashboard.component.html | 3 +- .../dashboard/dashboard.component.spec.ts | 65 ++++++- .../dashboard/dashboard.component.ts | 11 +- .../new-dashboard/pg-summary.pipe.spec.ts | 36 ++++ .../app/ceph/new-dashboard/pg-summary.pipe.ts | 27 +++ .../src/app/shared/enum/icons.enum.ts | 1 + .../app/shared/pipes/mds-summary.pipe.spec.ts | 76 ++++++++ .../src/app/shared/pipes/mds-summary.pipe.ts | 55 ++++++ .../app/shared/pipes/mgr-summary.pipe.spec.ts | 38 ++++ .../src/app/shared/pipes/mgr-summary.pipe.ts | 37 ++++ .../app/shared/pipes/osd-summary.pipe.spec.ts | 43 +++++ .../src/app/shared/pipes/osd-summary.pipe.ts | 46 +++++ .../src/app/shared/pipes/pipes.module.ts | 18 +- 18 files changed, 678 insertions(+), 13 deletions(-) create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/new-dashboard/card-row/card-row.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/new-dashboard/card-row/card-row.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/new-dashboard/card-row/card-row.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/new-dashboard/card-row/card-row.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/new-dashboard/pg-summary.pipe.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/new-dashboard/pg-summary.pipe.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/mds-summary.pipe.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/mds-summary.pipe.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/mgr-summary.pipe.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/mgr-summary.pipe.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/osd-summary.pipe.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/osd-summary.pipe.ts diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/new-dashboard/card-row/card-row.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/new-dashboard/card-row/card-row.component.html new file mode 100644 index 0000000000000..9b7bf03e77507 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/new-dashboard/card-row/card-row.component.html @@ -0,0 +1,167 @@ + + + + + + {{ data.success }} + + + {{ data.categoryPgAmount?.clean }} + + + + + + + {{ data.info }} + + + + + + + {{ data.warn }} + + + {{ data.categoryPgAmount?.warning }} + + + + + + + {{ data.error }} + + + {{ data.categoryPgAmount?.unknown }} + + + + + + + {{ data.categoryPgAmount?.working }} + + + + + + + + + {{ data.up }} + + + + + {{ data.up }} + + up + + + + {{ data.in }} + + in + + + + {{ data.down }} + + down + + + + {{ data.out }} + + out + + + + {{ data.nearfull }} + + nearfull + + {{ data.full }} + + full + + + + + + + {{ data.up }} + + + + + {{ data.down }} + + + + + + + + {{ data }} + + + + + + + {{ total }} + {{ title }} + {{ title }} + {{ title }}s + + diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/new-dashboard/card-row/card-row.component.scss b/src/pybind/mgr/dashboard/frontend/src/app/ceph/new-dashboard/card-row/card-row.component.scss new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/new-dashboard/card-row/card-row.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/new-dashboard/card-row/card-row.component.spec.ts new file mode 100644 index 0000000000000..8932e67b29c5b --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/new-dashboard/card-row/card-row.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CardRowComponent } from './card-row.component'; + +describe('CardRowComponent', () => { + let component: CardRowComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [CardRowComponent] + }).compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(CardRowComponent); + component = fixture.componentInstance; + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/new-dashboard/card-row/card-row.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/new-dashboard/card-row/card-row.component.ts new file mode 100644 index 0000000000000..90c939160eb91 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/new-dashboard/card-row/card-row.component.ts @@ -0,0 +1,34 @@ +import { Component, Input, OnChanges } from '@angular/core'; +import { Icons } from '~/app/shared/enum/icons.enum'; + +@Component({ + selector: 'cd-card-row', + templateUrl: './card-row.component.html', + styleUrls: ['./card-row.component.scss'] +}) +export class CardRowComponent implements OnChanges { + @Input() + title: string; + + @Input() + link: string; + + @Input() + data: any; + + @Input() + summaryType = 'default'; + + icons = Icons; + total: number; + + ngOnChanges(): void { + if (this.data.total || this.data.total === 0) { + this.total = this.data.total; + } else if (this.summaryType === 'iscsi') { + this.total = this.data.up + this.data.down || 0; + } else { + this.total = this.data; + } + } +} diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/new-dashboard/dashboard.module.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/new-dashboard/dashboard.module.ts index ac60eec648169..c32c2a630a19d 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/new-dashboard/dashboard.module.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/new-dashboard/dashboard.module.ts @@ -12,6 +12,8 @@ import { CephSharedModule } from '../shared/ceph-shared.module'; import { CardComponent } from './card/card.component'; import { DashboardPieComponent } from './dashboard-pie/dashboard-pie.component'; import { DashboardComponent } from './dashboard/dashboard.component'; +import { CardRowComponent } from './card-row/card-row.component'; +import { PgSummaryPipe } from './pg-summary.pipe'; @NgModule({ imports: [ @@ -27,6 +29,13 @@ import { DashboardComponent } from './dashboard/dashboard.component'; SimplebarAngularModule ], - declarations: [DashboardComponent, CardComponent, DashboardPieComponent] + declarations: [ + DashboardComponent, + CardComponent, + DashboardPieComponent, + DashboardPieComponent, + CardRowComponent, + PgSummaryPipe + ] }) export class NewDashboardModule {} diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/new-dashboard/dashboard/dashboard.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/new-dashboard/dashboard/dashboard.component.html index b910f2f856b79..ec8f75a905d57 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/new-dashboard/dashboard/dashboard.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/new-dashboard/dashboard/dashboard.component.html @@ -1,4 +1,5 @@ -
+
{ let orchestratorService: MgrModuleService; let getHealthSpy: jasmine.Spy; let getAlertsSpy: jasmine.Spy; + let fakeFeatureTogglesService: jasmine.Spy; const healthPayload: Record = { health: { status: 'HEALTH_OK' }, @@ -50,12 +55,12 @@ describe('Dashbord Component', () => { hosts: 0, rgw: 0, fs_map: { filesystems: [], standbys: [] }, - iscsi_daemons: 0, + iscsi_daemons: 1, client_perf: {}, scrub_status: 'Inactive', pools: [], df: { stats: {} }, - pg_info: { object_stats: { num_objects: 0 } } + pg_info: { object_stats: { num_objects: 1 } } }; const alertsPayload: AlertmanagerAlert[] = [ @@ -145,13 +150,32 @@ describe('Dashbord Component', () => { }; configureTestBed({ - imports: [RouterTestingModule, HttpClientTestingModule, ToastrModule.forRoot(), PipesModule], - declarations: [DashboardComponent, CardComponent, DashboardPieComponent], + imports: [RouterTestingModule, HttpClientTestingModule, ToastrModule.forRoot(), SharedModule], + declarations: [ + DashboardComponent, + CardComponent, + DashboardPieComponent, + CardRowComponent, + PgSummaryPipe + ], schemas: [NO_ERRORS_SCHEMA], - providers: [{ provide: SummaryService, useClass: SummaryServiceMock }, CssHelper] + providers: [ + { provide: SummaryService, useClass: SummaryServiceMock }, + CssHelper, + PgCategoryService + ] }); beforeEach(() => { + fakeFeatureTogglesService = spyOn(TestBed.inject(FeatureTogglesService), 'get').and.returnValue( + of({ + rbd: true, + mirroring: true, + iscsi: true, + cephfs: true, + rgw: true + }) + ); fixture = TestBed.createComponent(DashboardComponent); component = fixture.componentInstance; configurationService = TestBed.inject(ConfigurationService); @@ -168,6 +192,7 @@ describe('Dashbord Component', () => { }); it('should render all cards', () => { + fixture.detectChanges(); const dashboardCards = fixture.debugElement.nativeElement.querySelectorAll('cd-card'); expect(dashboardCards.length).toBe(5); }); @@ -260,4 +285,32 @@ describe('Dashbord Component', () => { expect(successNotification).toBe(null); expect(dangerNotification).toBe(null); }); + + describe('features disabled', () => { + beforeEach(() => { + fakeFeatureTogglesService.and.returnValue( + of({ + rbd: false, + mirroring: false, + iscsi: false, + cephfs: false, + rgw: false + }) + ); + fixture = TestBed.createComponent(DashboardComponent); + component = fixture.componentInstance; + }); + + it('should not render items related to disabled features', () => { + fixture.detectChanges(); + + const iscsiCard = fixture.debugElement.query(By.css('li[id=iscsi-item]')); + const rgwCard = fixture.debugElement.query(By.css('li[id=rgw-item]')); + const mds = fixture.debugElement.query(By.css('li[id=mds-item]')); + + expect(iscsiCard).toBeFalsy(); + expect(rgwCard).toBeFalsy(); + expect(mds).toBeFalsy(); + }); + }); }); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/new-dashboard/dashboard/dashboard.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/new-dashboard/dashboard/dashboard.component.ts index 009b6717721c9..ba6eccedff429 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/new-dashboard/dashboard/dashboard.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/new-dashboard/dashboard/dashboard.component.ts @@ -1,4 +1,4 @@ -import { Component, NgZone, OnDestroy, OnInit } from '@angular/core'; +import { Component, OnDestroy, OnInit } from '@angular/core'; import _ from 'lodash'; import { Observable, Subscription } from 'rxjs'; @@ -19,6 +19,7 @@ import { FeatureTogglesMap$, FeatureTogglesService } from '~/app/shared/services/feature-toggles.service'; +import { RefreshIntervalService } from '~/app/shared/services/refresh-interval.service'; import { SummaryService } from '~/app/shared/services/summary.service'; @Component({ @@ -100,7 +101,13 @@ export class DashboardComponent implements OnInit, OnDestroy { } ngOnDestroy() { - window.clearInterval(this.interval); + this.interval.unsubscribe(); + } + + getHealth() { + this.healthService.getMinimalHealth().subscribe((data: any) => { + this.healthData = data; + }); } toggleAlertsWindow(type: string, isToggleButton: boolean = false) { diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/new-dashboard/pg-summary.pipe.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/new-dashboard/pg-summary.pipe.spec.ts new file mode 100644 index 0000000000000..b467167fdce06 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/new-dashboard/pg-summary.pipe.spec.ts @@ -0,0 +1,36 @@ +import { TestBed } from '@angular/core/testing'; + +import { configureTestBed } from '~/testing/unit-test-helper'; +import { PgCategoryService } from '../shared/pg-category.service'; +import { PgSummaryPipe } from './pg-summary.pipe'; + +describe('OsdSummaryPipe', () => { + let pipe: PgSummaryPipe; + + configureTestBed({ + providers: [PgSummaryPipe, PgCategoryService] + }); + + beforeEach(() => { + pipe = TestBed.inject(PgSummaryPipe); + }); + + it('create an instance', () => { + expect(pipe).toBeTruthy(); + }); + + it('tranforms value', () => { + const value = { + statuses: { + 'active+clean': 241 + }, + pgs_per_osd: 241 + }; + expect(pipe.transform(value)).toEqual({ + categoryPgAmount: { + clean: 241 + }, + total: 241 + }); + }); +}); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/new-dashboard/pg-summary.pipe.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/new-dashboard/pg-summary.pipe.ts new file mode 100644 index 0000000000000..a26097ee00508 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/new-dashboard/pg-summary.pipe.ts @@ -0,0 +1,27 @@ +import { Pipe, PipeTransform } from '@angular/core'; +import _ from 'lodash'; +import { PgCategoryService } from '~/app/ceph/shared/pg-category.service'; + +@Pipe({ + name: 'pgSummary' +}) +export class PgSummaryPipe implements PipeTransform { + constructor(private pgCategoryService: PgCategoryService) {} + + transform(value: any): any { + const categoryPgAmount: Record = {}; + let total = 0; + _.forEach(value.statuses, (pgAmount, pgStatesText) => { + const categoryType = this.pgCategoryService.getTypeByStates(pgStatesText); + if (_.isUndefined(categoryPgAmount[categoryType])) { + categoryPgAmount[categoryType] = 0; + } + categoryPgAmount[categoryType] += pgAmount; + total += pgAmount; + }); + return { + categoryPgAmount, + total + }; + } +} diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/enum/icons.enum.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/enum/icons.enum.ts index 8d7f9a8f8ab7d..dfeecc52c088a 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/enum/icons.enum.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/enum/icons.enum.ts @@ -21,6 +21,7 @@ export enum Icons { analyse = 'fa fa-stethoscope', // Scrub deepCheck = 'fa fa-cog', // Deep Scrub, Setting, Configuration reweight = 'fa fa-balance-scale', // Reweight + up = 'fa fa-arrow-up', // Up left = 'fa fa-arrow-left', // Mark out right = 'fa fa-arrow-right', // Mark in down = 'fa fa-arrow-down', // Mark Down diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/mds-summary.pipe.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/mds-summary.pipe.spec.ts new file mode 100644 index 0000000000000..846cfb0bc5dcb --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/mds-summary.pipe.spec.ts @@ -0,0 +1,76 @@ +import { TestBed } from '@angular/core/testing'; + +import { configureTestBed } from '~/testing/unit-test-helper'; +import { MdsSummaryPipe } from './mds-summary.pipe'; + +describe('MdsSummaryPipe', () => { + let pipe: MdsSummaryPipe; + + configureTestBed({ + providers: [MdsSummaryPipe] + }); + + beforeEach(() => { + pipe = TestBed.inject(MdsSummaryPipe); + }); + + it('create an instance', () => { + expect(pipe).toBeTruthy(); + }); + + it('transforms with 0 active and 2 standy', () => { + const payload = { + standbys: [{ name: 'a' }], + filesystems: [{ mdsmap: { info: [{ state: 'up:standby-replay' }] } }] + }; + + expect(pipe.transform(payload)).toEqual({ + success: 0, + info: 2, + total: 2 + }); + }); + + it('transforms with 1 active and 1 standy', () => { + const payload = { + standbys: [{ name: 'b' }], + filesystems: [{ mdsmap: { info: [{ state: 'up:active', name: 'a' }] } }] + }; + expect(pipe.transform(payload)).toEqual({ + success: 1, + info: 1, + total: 2 + }); + }); + + it('transforms with 0 filesystems', () => { + const payload: Record = { + standbys: [0], + filesystems: [] + }; + + expect(pipe.transform(payload)).toEqual({ + success: 0, + info: 0, + total: 0 + }); + }); + + it('transforms without filesystem', () => { + const payload = { standbys: [{ name: 'a' }] }; + + expect(pipe.transform(payload)).toEqual({ + success: 0, + info: 1, + total: 1 + }); + }); + + it('transforms without value', () => { + expect(pipe.transform(undefined)).toEqual({ + success: 0, + info: 0, + total: 0 + }); + }); +}); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/mds-summary.pipe.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/mds-summary.pipe.ts new file mode 100644 index 0000000000000..77758b71d3d57 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/mds-summary.pipe.ts @@ -0,0 +1,55 @@ +import { Pipe, PipeTransform } from '@angular/core'; + +import _ from 'lodash'; + +@Pipe({ + name: 'mdsSummary' +}) +export class MdsSummaryPipe implements PipeTransform { + transform(value: any): any { + if (!value) { + return { + success: 0, + info: 0, + total: 0 + }; + } + + let activeCount = 0; + let standbyCount = 0; + let standbys = 0; + let active = 0; + let standbyReplay = 0; + _.each(value.standbys, () => { + standbys += 1; + }); + + if (value.standbys && !value.filesystems) { + standbyCount = standbys; + activeCount = 0; + } else if (value.filesystems.length === 0) { + activeCount = 0; + } else { + _.each(value.filesystems, (fs) => { + _.each(fs.mdsmap.info, (mds) => { + if (mds.state === 'up:standby-replay') { + standbyReplay += 1; + } else { + active += 1; + } + }); + }); + + activeCount = active; + standbyCount = standbys + standbyReplay; + } + const totalCount = activeCount + standbyCount; + const mdsSummary = { + success: activeCount, + info: standbyCount, + total: totalCount + }; + + return mdsSummary; + } +} diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/mgr-summary.pipe.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/mgr-summary.pipe.spec.ts new file mode 100644 index 0000000000000..ac7dcc63fb954 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/mgr-summary.pipe.spec.ts @@ -0,0 +1,38 @@ +import { TestBed } from '@angular/core/testing'; + +import { configureTestBed } from '~/testing/unit-test-helper'; +import { MgrSummaryPipe } from './mgr-summary.pipe'; + +describe('MgrSummaryPipe', () => { + let pipe: MgrSummaryPipe; + + configureTestBed({ + providers: [MgrSummaryPipe] + }); + + beforeEach(() => { + pipe = TestBed.inject(MgrSummaryPipe); + }); + + it('create an instance', () => { + expect(pipe).toBeTruthy(); + }); + + it('transforms without value', () => { + expect(pipe.transform(undefined)).toEqual({ + success: 0, + info: 0, + total: 0 + }); + }); + + it('transforms with 1 active and 2 standbys', () => { + const payload = { + active_name: 'x', + standbys: [{ name: 'y' }, { name: 'z' }] + }; + const expected = { success: 1, info: 2, total: 3 }; + + expect(pipe.transform(payload)).toEqual(expected); + }); +}); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/mgr-summary.pipe.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/mgr-summary.pipe.ts new file mode 100644 index 0000000000000..14b38095210bb --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/mgr-summary.pipe.ts @@ -0,0 +1,37 @@ +import { Pipe, PipeTransform } from '@angular/core'; + +import _ from 'lodash'; + +@Pipe({ + name: 'mgrSummary' +}) +export class MgrSummaryPipe implements PipeTransform { + transform(value: any): any { + if (!value) { + return { + success: 0, + info: 0, + total: 0 + }; + } + + let activeCount: number; + const activeTitleText = _.isUndefined(value.active_name) + ? '' + : `${$localize`active daemon`}: ${value.active_name}`; + // There is always one standbyreplay to replace active daemon, if active one is down + if (activeTitleText.length > 0) { + activeCount = 1; + } + const standbyCount = value.standbys.length; + const totalCount = activeCount + standbyCount; + + const mgrSummary = { + success: activeCount, + info: standbyCount, + total: totalCount + }; + + return mgrSummary; + } +} diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/osd-summary.pipe.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/osd-summary.pipe.spec.ts new file mode 100644 index 0000000000000..2c60fa585c70f --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/osd-summary.pipe.spec.ts @@ -0,0 +1,43 @@ +import { TestBed } from '@angular/core/testing'; + +import { configureTestBed } from '~/testing/unit-test-helper'; +import { OsdSummaryPipe } from './osd-summary.pipe'; + +describe('OsdSummaryPipe', () => { + let pipe: OsdSummaryPipe; + + configureTestBed({ + providers: [OsdSummaryPipe] + }); + + beforeEach(() => { + pipe = TestBed.inject(OsdSummaryPipe); + }); + + it('create an instance', () => { + expect(pipe).toBeTruthy(); + }); + + it('transforms without value', () => { + expect(pipe.transform(undefined)).toBe(''); + }); + + it('transforms having 3 osd with 3 up, 3 in, 0 down, 0 out', () => { + const value = { + osds: [ + { up: 1, in: 1, state: ['up', 'exists'] }, + { up: 1, in: 1, state: ['up', 'exists'] }, + { up: 1, in: 1, state: ['up', 'exists'] } + ] + }; + expect(pipe.transform(value)).toEqual({ + total: 3, + down: 0, + out: 0, + up: 3, + in: 3, + nearfull: 0, + full: 0 + }); + }); +}); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/osd-summary.pipe.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/osd-summary.pipe.ts new file mode 100644 index 0000000000000..66e86970c6319 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/osd-summary.pipe.ts @@ -0,0 +1,46 @@ +import { Pipe, PipeTransform } from '@angular/core'; + +import _ from 'lodash'; + +@Pipe({ + name: 'osdSummary' +}) +export class OsdSummaryPipe implements PipeTransform { + transform(value: any): any { + if (!value) { + return ''; + } + + let inCount = 0; + let upCount = 0; + let nearFullCount = 0; + let fullCount = 0; + _.each(value.osds, (osd) => { + if (osd.in) { + inCount++; + } + if (osd.up) { + upCount++; + } + if (osd.state.includes('nearfull')) { + nearFullCount++; + } + if (osd.state.includes('full')) { + fullCount++; + } + }); + + const downCount = value.osds.length - upCount; + const outCount = value.osds.length - inCount; + const osdSummary = { + total: value.osds.length, + down: downCount, + out: outCount, + up: upCount, + in: inCount, + nearfull: nearFullCount, + full: fullCount + }; + return osdSummary; + } +} diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/pipes.module.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/pipes.module.ts index 226972ce0cae0..4abc029533ac3 100755 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/pipes.module.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/pipes.module.ts @@ -22,9 +22,12 @@ import { IscsiBackstorePipe } from './iscsi-backstore.pipe'; import { JoinPipe } from './join.pipe'; import { LogPriorityPipe } from './log-priority.pipe'; import { MapPipe } from './map.pipe'; +import { MdsSummaryPipe } from './mds-summary.pipe'; +import { MgrSummaryPipe } from './mgr-summary.pipe'; import { MillisecondsPipe } from './milliseconds.pipe'; import { NotAvailablePipe } from './not-available.pipe'; import { OrdinalPipe } from './ordinal.pipe'; +import { OsdSummaryPipe } from './osd-summary.pipe'; import { RbdConfigurationSourcePipe } from './rbd-configuration-source.pipe'; import { RelativeDatePipe } from './relative-date.pipe'; import { RoundPipe } from './round.pipe'; @@ -66,7 +69,10 @@ import { UpperFirstPipe } from './upper-first.pipe'; TruncatePipe, SanitizeHtmlPipe, SearchHighlightPipe, - HealthIconPipe + HealthIconPipe, + MgrSummaryPipe, + MdsSummaryPipe, + OsdSummaryPipe ], exports: [ ArrayPipe, @@ -99,7 +105,10 @@ import { UpperFirstPipe } from './upper-first.pipe'; TruncatePipe, SanitizeHtmlPipe, SearchHighlightPipe, - HealthIconPipe + HealthIconPipe, + MgrSummaryPipe, + MdsSummaryPipe, + OsdSummaryPipe ], providers: [ ArrayPipe, @@ -127,7 +136,10 @@ import { UpperFirstPipe } from './upper-first.pipe'; MapPipe, TruncatePipe, SanitizeHtmlPipe, - HealthIconPipe + HealthIconPipe, + MgrSummaryPipe, + MdsSummaryPipe, + OsdSummaryPipe ] }) export class PipesModule {} -- 2.39.5