return [self._serialize_pool(pool, attrs) for pool in pools]
- def list(self, attrs=None, stats=False):
+ def list(self, attrs=None, stats=True):
return self._pool_list(attrs, stats)
def _get(self, pool_name, attrs=None, stats=False):
import { TabsModule } from 'ngx-bootstrap/tabs';
import { SharedModule } from '../../shared/shared.module';
+import { CephSharedModule } from '../shared/ceph-shared.module';
import { DashboardComponent } from './dashboard/dashboard.component';
import { HealthPieComponent } from './health-pie/health-pie.component';
import { HealthComponent } from './health/health.component';
import { MgrSummaryPipe } from './mgr-summary.pipe';
import { MonSummaryPipe } from './mon-summary.pipe';
import { OsdSummaryPipe } from './osd-summary.pipe';
-import { PgStatusStylePipe } from './pg-status-style.pipe';
-import { PgStatusPipe } from './pg-status.pipe';
@NgModule({
imports: [
+ CephSharedModule,
CommonModule,
TabsModule.forRoot(),
SharedModule,
OsdSummaryPipe,
LogColorPipe,
MgrSummaryPipe,
- PgStatusPipe,
MdsSummaryPipe,
- PgStatusStylePipe,
HealthPieComponent,
InfoCardComponent,
InfoGroupComponent
import { Permissions } from '../../../shared/models/permissions';
import { AuthStorageService } from '../../../shared/services/auth-storage.service';
import { SharedModule } from '../../../shared/shared.module';
+import { PgCategoryService } from '../../shared/pg-category.service';
+import { HealthPieColor } from '../health-pie/health-pie-color.enum';
+import { HealthPieComponent } from '../health-pie/health-pie.component';
import { MdsSummaryPipe } from '../mds-summary.pipe';
import { MgrSummaryPipe } from '../mgr-summary.pipe';
import { MonSummaryPipe } from '../mon-summary.pipe';
import { OsdSummaryPipe } from '../osd-summary.pipe';
-import { PgStatusStylePipe } from '../pg-status-style.pipe';
-import { PgStatusPipe } from '../pg-status.pipe';
import { HealthComponent } from './health.component';
describe('HealthComponent', () => {
imports: [SharedModule, HttpClientTestingModule, PopoverModule.forRoot()],
declarations: [
HealthComponent,
+ HealthPieComponent,
MonSummaryPipe,
OsdSummaryPipe,
MdsSummaryPipe,
- MgrSummaryPipe,
- PgStatusStylePipe,
- PgStatusPipe
+ MgrSummaryPipe
],
schemas: [NO_ERRORS_SCHEMA],
- providers: [i18nProviders, { provide: AuthStorageService, useValue: fakeAuthStorageService }]
+ providers: [
+ i18nProviders,
+ { provide: AuthStorageService, useValue: fakeAuthStorageService },
+ PgCategoryService
+ ]
});
beforeEach(() => {
fixture = TestBed.createComponent(HealthComponent);
component = fixture.componentInstance;
getHealthSpy = spyOn(TestBed.get(HealthService), 'getMinimalHealth');
+ getHealthSpy.and.returnValue(of(healthPayload));
});
it('should create', () => {
});
it('should render all info groups and all info cards', () => {
- getHealthSpy.and.returnValue(of(healthPayload));
fixture.detectChanges();
const infoGroups = fixture.debugElement.nativeElement.querySelectorAll('cd-info-group');
});
it('should render "Cluster Status" card text that is not clickable', () => {
- getHealthSpy.and.returnValue(of(healthPayload));
fixture.detectChanges();
const clusterStatusCard = fixture.debugElement.query(
const clickableContent = clusterStatusCard.query(By.css('.info-card-content-clickable'));
expect(clickableContent.nativeElement.textContent).toEqual(` ${payload.health.status} `);
});
+
+ it('event binding "prepareReadWriteRatio" is called', () => {
+ const prepareReadWriteRatio = spyOn(component, 'prepareReadWriteRatio');
+
+ const payload = _.cloneDeep(healthPayload);
+ payload.client_perf['read_op_per_sec'] = 1;
+ payload.client_perf['write_op_per_sec'] = 1;
+ getHealthSpy.and.returnValue(of(payload));
+ fixture.detectChanges();
+
+ expect(prepareReadWriteRatio).toHaveBeenCalled();
+ });
+
+ it('event binding "prepareRawUsage" is called', () => {
+ const prepareRawUsage = spyOn(component, 'prepareRawUsage');
+
+ fixture.detectChanges();
+
+ expect(prepareRawUsage).toHaveBeenCalled();
+ });
+
+ it('event binding "preparePgStatus" is called', () => {
+ const preparePgStatus = spyOn(component, 'preparePgStatus');
+
+ fixture.detectChanges();
+
+ expect(preparePgStatus).toHaveBeenCalled();
+ });
+
+ describe('preparePgStatus', () => {
+ const expectedChart = (data: number[]) => ({
+ colors: [
+ {
+ backgroundColor: [
+ HealthPieColor.SHADE_GREEN_CYAN,
+ HealthPieColor.MEDIUM_DARK_SHADE_CYAN_BLUE,
+ HealthPieColor.LIGHT_SHADE_BROWN,
+ HealthPieColor.MEDIUM_LIGHT_SHADE_PINK_RED
+ ]
+ }
+ ],
+ labels: ['Clean', 'Working', 'Warning', 'Unknown'],
+ dataset: [{ data: data }]
+ });
+
+ it('gets no data', () => {
+ const chart = { dataset: [{}] };
+ component.preparePgStatus(chart, { pg_info: {} });
+ expect(chart).toEqual(expectedChart([undefined, undefined, undefined, undefined]));
+ });
+
+ it('gets data from all categories', () => {
+ const chart = { dataset: [{}] };
+ component.preparePgStatus(chart, {
+ pg_info: {
+ statuses: {
+ 'clean+active+scrubbing+nonMappedState': 4,
+ 'clean+active+scrubbing': 2,
+ 'clean+active': 1,
+ 'clean+active+scrubbing+down': 3
+ }
+ }
+ });
+ expect(chart).toEqual(expectedChart([1, 2, 3, 4]));
+ });
+ });
});
import { HealthService } from '../../../shared/api/health.service';
import { Permissions } from '../../../shared/models/permissions';
import { AuthStorageService } from '../../../shared/services/auth-storage.service';
+import { PgCategoryService } from '../../shared/pg-category.service';
+import { HealthPieColor } from '../health-pie/health-pie-color.enum';
@Component({
selector: 'cd-health',
constructor(
private healthService: HealthService,
private i18n: I18n,
- private authStorageService: AuthStorageService
+ private authStorageService: AuthStorageService,
+ private pgCategoryService: PgCategoryService
) {
this.permissions = this.authStorageService.getPermissions();
}
}
preparePgStatus(chart, data) {
- const pgCategoryClean = this.i18n('Clean');
- const pgCategoryCleanStates = ['active', 'clean'];
- const pgCategoryWarning = this.i18n('Warning');
- const pgCategoryWarningStates = [
- 'backfill_toofull',
- 'backfill_unfound',
- 'down',
- 'incomplete',
- 'inconsistent',
- 'recovery_toofull',
- 'recovery_unfound',
- 'remapped',
- 'snaptrim_error',
- 'stale',
- 'undersized'
+ const categoryPgAmount = {};
+ chart.labels = [
+ this.i18n('Clean'),
+ this.i18n('Working'),
+ this.i18n('Warning'),
+ this.i18n('Unknown')
];
- const pgCategoryUnknown = this.i18n('Unknown');
- const pgCategoryWorking = this.i18n('Working');
- const pgCategoryWorkingStates = [
- 'activating',
- 'backfill_wait',
- 'backfilling',
- 'creating',
- 'deep',
- 'degraded',
- 'forced_backfill',
- 'forced_recovery',
- 'peering',
- 'peered',
- 'recovering',
- 'recovery_wait',
- 'repair',
- 'scrubbing',
- 'snaptrim',
- 'snaptrim_wait'
+ chart.colors = [
+ {
+ backgroundColor: [
+ HealthPieColor.SHADE_GREEN_CYAN,
+ HealthPieColor.MEDIUM_DARK_SHADE_CYAN_BLUE,
+ HealthPieColor.LIGHT_SHADE_BROWN,
+ HealthPieColor.MEDIUM_LIGHT_SHADE_PINK_RED
+ ]
+ }
];
- let totalPgClean = 0;
- let totalPgWarning = 0;
- let totalPgUnknown = 0;
- let totalPgWorking = 0;
_.forEach(data.pg_info.statuses, (pgAmount, pgStatesText) => {
- const pgStates = pgStatesText.split('+');
- const isWarning = _.intersection(pgCategoryWarningStates, pgStates).length > 0;
- const pgWorkingStates = _.intersection(pgCategoryWorkingStates, pgStates);
- const pgCleanStates = _.intersection(pgCategoryCleanStates, pgStates);
-
- if (isWarning) {
- totalPgWarning += pgAmount;
- } else if (pgStates.length > pgCleanStates.length + pgWorkingStates.length) {
- totalPgUnknown += pgAmount;
- } else if (pgWorkingStates.length > 0) {
- totalPgWorking = pgAmount;
- } else {
- totalPgClean += pgAmount;
+ const categoryType = this.pgCategoryService.getTypeByStates(pgStatesText);
+
+ if (_.isUndefined(categoryPgAmount[categoryType])) {
+ categoryPgAmount[categoryType] = 0;
}
+ categoryPgAmount[categoryType] += pgAmount;
});
- chart.labels = [pgCategoryWarning, pgCategoryClean, pgCategoryUnknown, pgCategoryWorking];
- chart.dataset[0].data = [totalPgWarning, totalPgClean, totalPgUnknown, totalPgWorking];
+ chart.dataset[0].data = this.pgCategoryService
+ .getAllTypes()
+ .map((categoryType) => categoryPgAmount[categoryType]);
}
}
+++ /dev/null
-import { PgStatusStylePipe } from './pg-status-style.pipe';
-
-describe('PgStatusStylePipe', () => {
- const pipe = new PgStatusStylePipe();
-
- it('create an instance', () => {
- expect(pipe).toBeTruthy();
- });
-
- it('transforms with pg status error', () => {
- const value = { 'incomplete+clean': 8 };
- expect(pipe.transform(value)).toEqual({ color: '#FF0000' });
- });
-
- it('transforms with pg status warning', () => {
- const value = { active: 8 };
- expect(pipe.transform(value)).toEqual({ color: '#FFC200' });
- });
-
- it('transforms with pg status other', () => {
- const value = { 'active+clean': 8 };
- expect(pipe.transform(value)).toEqual({ color: '#00BB00' });
- });
-});
+++ /dev/null
-import { Pipe, PipeTransform } from '@angular/core';
-import * as _ from 'lodash';
-
-@Pipe({
- name: 'pgStatusStyle'
-})
-export class PgStatusStylePipe implements PipeTransform {
- transform(pgStatus: any, args?: any): any {
- let warning = false;
- let error = false;
-
- _.each(pgStatus, (value, state) => {
- if (
- state.includes('inconsistent') ||
- state.includes('incomplete') ||
- !state.includes('active')
- ) {
- error = true;
- }
-
- if (
- state !== 'active+clean' &&
- state !== 'active+clean+scrubbing' &&
- state !== 'active+clean+scrubbing+deep'
- ) {
- warning = true;
- }
- });
-
- if (error) {
- return { color: '#FF0000' };
- }
-
- if (warning) {
- return { color: '#FFC200' };
- }
-
- return { color: '#00BB00' };
- }
-}
+++ /dev/null
-import { PgStatusPipe } from './pg-status.pipe';
-
-describe('PgStatusPipe', () => {
- const pipe = new PgStatusPipe();
-
- it('create an instance', () => {
- expect(pipe).toBeTruthy();
- });
-
- it('transforms with 1 status', () => {
- const value = { 'active+clean': 8 };
- expect(pipe.transform(value)).toBe('8 active+clean');
- });
-
- it('transforms with 2 status', () => {
- const value = { active: 8, incomplete: 8 };
- expect(pipe.transform(value)).toBe('8 active, 8 incomplete');
- });
-});
+++ /dev/null
-import { Pipe, PipeTransform } from '@angular/core';
-import * as _ from 'lodash';
-
-@Pipe({
- name: 'pgStatus'
-})
-export class PgStatusPipe implements PipeTransform {
- transform(pgStatus: any, args?: any): any {
- const strings = [];
- _.each(pgStatus, (count, state) => {
- strings.push(count + ' ' + state);
- });
-
- return strings.join(', ');
- }
-}
+@import '../../../../defaults';
+
+::ng-deep .pg-clean {
+ color: $color-bright-green;
+}
+
+::ng-deep .pg-working {
+ color: $color-blue;
+}
+
+::ng-deep .pg-warning {
+ color: $color-bright-yellow;
+}
+
+::ng-deep .pg-unknown {
+ color: $color-solid-red;
+}
import { SummaryService } from '../../../shared/services/summary.service';
import { TaskWrapperService } from '../../../shared/services/task-wrapper.service';
import { SharedModule } from '../../../shared/shared.module';
+import { PgCategoryService } from '../../shared/pg-category.service';
import { Pool } from '../pool';
import { PoolListComponent } from './pool-list.component';
TabsModule.forRoot(),
HttpClientTestingModule
],
- providers: i18nProviders
+ providers: [i18nProviders, PgCategoryService]
});
beforeEach(() => {
expect(component.pools[2].cdExecuting).toBeFalsy();
});
});
+
+ describe('getPgStatusCellClass', () => {
+ const testMethod = (value, expected) =>
+ expect(component.getPgStatusCellClass({ row: '', column: '', value: value })).toEqual({
+ 'text-right': true,
+ [expected]: true
+ });
+
+ it('pg-clean', () => {
+ testMethod('8 active+clean', 'pg-clean');
+ });
+
+ it('pg-working', () => {
+ testMethod(' 8 active+clean+scrubbing+deep, 255 active+clean ', 'pg-working');
+ });
+
+ it('pg-warning', () => {
+ testMethod('8 active+clean+scrubbing+down', 'pg-warning');
+ testMethod('8 active+clean+scrubbing+down+nonMappedState', 'pg-warning');
+ });
+
+ it('pg-unknown', () => {
+ testMethod('8 active+clean+scrubbing+nonMappedState', 'pg-unknown');
+ testMethod('8 ', 'pg-unknown');
+ testMethod('', 'pg-unknown');
+ });
+ });
+
+ describe('transformPoolsData', () => {
+ it('transforms pools data correctly', () => {
+ const pools = [{ stats: { rate: 0 }, pg_status: { 'active+clean': 8, down: 2 } }];
+ const expected = [{ pg_status: '8 active+clean, 2 down' }];
+
+ expect(component.transformPoolsData(pools)).toEqual(expected);
+ });
+
+ it('transforms empty pools data correctly', () => {
+ const pools = undefined;
+ const expected = undefined;
+
+ expect(component.transformPoolsData(pools)).toEqual(expected);
+ });
+ });
+
+ describe('transformPgStatus', () => {
+ it('returns ststus groups correctly', () => {
+ const pgStatus = { 'active+clean': 8 };
+ const expected = '8 active+clean';
+
+ expect(component.transformPgStatus(pgStatus)).toEqual(expected);
+ });
+
+ it('returns separated status groups', () => {
+ const pgStatus = { 'active+clean': 8, down: 2 };
+ const expected = '8 active+clean, 2 down';
+
+ expect(component.transformPgStatus(pgStatus)).toEqual(expected);
+ });
+
+ it('returns separated statuses correctly', () => {
+ const pgStatus = { active: 8, down: 2 };
+ const expected = '8 active, 2 down';
+
+ expect(component.transformPgStatus(pgStatus)).toEqual(expected);
+ });
+
+ it('returns empty string', () => {
+ const pgStatus = undefined;
+ const expected = '';
+
+ expect(component.transformPgStatus(pgStatus)).toEqual(expected);
+ });
+ });
});
import { Component, OnInit, ViewChild } from '@angular/core';
import { I18n } from '@ngx-translate/i18n-polyfill';
+import * as _ from 'lodash';
import { BsModalRef, BsModalService } from 'ngx-bootstrap/modal';
import { PoolService } from '../../../shared/api/pool.service';
import { AuthStorageService } from '../../../shared/services/auth-storage.service';
import { TaskListService } from '../../../shared/services/task-list.service';
import { TaskWrapperService } from '../../../shared/services/task-wrapper.service';
+import { PgCategoryService } from '../../shared/pg-category.service';
import { Pool } from '../pool';
@Component({
private authStorageService: AuthStorageService,
private taskListService: TaskListService,
private modalService: BsModalService,
- private i18n: I18n
+ private i18n: I18n,
+ private pgCategoryService: PgCategoryService
) {
this.permissions = this.authStorageService.getPermissions();
this.tableActions = [
flexGrow: 3
},
{
- prop: 'pg_placement_num',
- name: this.i18n('Placement Groups'),
+ prop: 'pg_status',
+ name: this.i18n('PG Status'),
flexGrow: 1,
- cellClass: 'text-right'
+ cellClass: ({ row, column, value }): any => {
+ return this.getPgStatusCellClass({ row, column, value });
+ }
},
{
prop: 'size',
this.taskListService.init(
() => this.poolService.getList(),
undefined,
- (pools) => (this.pools = pools),
+ (pools) => (this.pools = this.transformPoolsData(pools)),
() => {
this.table.reset(); // Disable loading indicator.
this.viewCacheStatusList = [{ status: ViewCacheStatus.ValueException }];
}
});
}
+
+ getPgStatusCellClass({ row, column, value }): object {
+ return {
+ 'text-right': true,
+ [`pg-${this.pgCategoryService.getTypeByStates(value)}`]: true
+ };
+ }
+
+ transformPoolsData(pools: any) {
+ _.map(pools, (pool: object) => {
+ delete pool['stats'];
+ pool['pg_status'] = this.transformPgStatus(pool['pg_status']);
+ });
+
+ return pools;
+ }
+
+ transformPgStatus(pgStatus: any): string {
+ const strings = [];
+ _.forEach(pgStatus, (count, state) => {
+ strings.push(`${count} ${state}`);
+ });
+
+ return strings.join(', ');
+ }
}
import { ServicesModule } from '../../shared/services/services.module';
import { SharedModule } from '../../shared/shared.module';
+import { CephSharedModule } from '../shared/ceph-shared.module';
import { ErasureCodeProfileFormComponent } from './erasure-code-profile-form/erasure-code-profile-form.component';
import { PoolFormComponent } from './pool-form/pool-form.component';
import { PoolListComponent } from './pool-list/pool-list.component';
@NgModule({
imports: [
+ CephSharedModule,
CommonModule,
TabsModule,
PopoverModule.forRoot(),
last_change: string;
min_write_recency_for_promote: number;
read_tier: number;
+ pg_status: string;
constructor(name) {
this.pool_name = name;
--- /dev/null
+import { CommonModule } from '@angular/common';
+import { NgModule } from '@angular/core';
+
+@NgModule({
+ imports: [CommonModule]
+})
+export class CephSharedModule {}
--- /dev/null
+export class PgCategory {
+ static readonly CATEGORY_CLEAN = 'clean';
+ static readonly CATEGORY_WORKING = 'working';
+ static readonly CATEGORY_WARNING = 'warning';
+ static readonly CATEGORY_UNKNOWN = 'unknown';
+ static readonly VALID_CATEGORIES = [
+ PgCategory.CATEGORY_CLEAN,
+ PgCategory.CATEGORY_WORKING,
+ PgCategory.CATEGORY_WARNING,
+ PgCategory.CATEGORY_UNKNOWN
+ ];
+
+ states: string[];
+
+ constructor(public type: string) {
+ if (!this.isValidType()) {
+ throw new Error('Wrong placement group category type');
+ }
+
+ this.setTypeStates();
+ }
+
+ private isValidType() {
+ return PgCategory.VALID_CATEGORIES.includes(this.type);
+ }
+
+ private setTypeStates() {
+ switch (this.type) {
+ case PgCategory.CATEGORY_CLEAN:
+ this.states = ['active', 'clean'];
+ break;
+ case PgCategory.CATEGORY_WORKING:
+ this.states = [
+ 'activating',
+ 'backfill_wait',
+ 'backfilling',
+ 'creating',
+ 'deep',
+ 'degraded',
+ 'forced_backfill',
+ 'forced_recovery',
+ 'peering',
+ 'peered',
+ 'recovering',
+ 'recovery_wait',
+ 'repair',
+ 'scrubbing',
+ 'snaptrim',
+ 'snaptrim_wait'
+ ];
+ break;
+ case PgCategory.CATEGORY_WARNING:
+ this.states = [
+ 'backfill_toofull',
+ 'backfill_unfound',
+ 'down',
+ 'incomplete',
+ 'inconsistent',
+ 'recovery_toofull',
+ 'recovery_unfound',
+ 'remapped',
+ 'snaptrim_error',
+ 'stale',
+ 'undersized'
+ ];
+ break;
+ default:
+ this.states = [];
+ }
+ }
+}
--- /dev/null
+import { TestBed } from '@angular/core/testing';
+
+import { configureTestBed } from '../../../testing/unit-test-helper';
+import { PgCategory } from './pg-category.model';
+import { PgCategoryService } from './pg-category.service';
+
+describe('PgCategoryService', () => {
+ let service: PgCategoryService;
+
+ configureTestBed({
+ providers: [PgCategoryService]
+ });
+
+ beforeEach(() => {
+ service = TestBed.get(PgCategoryService);
+ });
+
+ it('should be created', () => {
+ expect(service).toBeTruthy();
+ });
+
+ it('returns all category types', () => {
+ const categoryTypes = service.getAllTypes();
+
+ expect(categoryTypes).toEqual(PgCategory.VALID_CATEGORIES);
+ });
+
+ describe('getTypeByStates', () => {
+ const testMethod = (value, expected) =>
+ expect(service.getTypeByStates(value)).toEqual(expected);
+
+ it(PgCategory.CATEGORY_CLEAN, () => {
+ testMethod('clean', PgCategory.CATEGORY_CLEAN);
+ });
+
+ it(PgCategory.CATEGORY_WORKING, () => {
+ testMethod('clean+scrubbing', PgCategory.CATEGORY_WORKING);
+ testMethod(
+ ' 8 active+clean+scrubbing+deep, 255 active+clean ',
+ PgCategory.CATEGORY_WORKING
+ );
+ });
+
+ it(PgCategory.CATEGORY_WARNING, () => {
+ testMethod('clean+scrubbing+down', PgCategory.CATEGORY_WARNING);
+ testMethod('clean+scrubbing+down+nonMappedState', PgCategory.CATEGORY_WARNING);
+ });
+
+ it(PgCategory.CATEGORY_UNKNOWN, () => {
+ testMethod('clean+scrubbing+nonMappedState', PgCategory.CATEGORY_UNKNOWN);
+ testMethod('nonMappedState', PgCategory.CATEGORY_UNKNOWN);
+ testMethod('', PgCategory.CATEGORY_UNKNOWN);
+ });
+ });
+});
--- /dev/null
+import { Injectable } from '@angular/core';
+
+import * as _ from 'lodash';
+
+import { CephSharedModule } from './ceph-shared.module';
+import { PgCategory } from './pg-category.model';
+
+@Injectable({
+ providedIn: CephSharedModule
+})
+export class PgCategoryService {
+ private categories: object;
+
+ constructor() {
+ this.categories = this.createCategories();
+ }
+
+ getAllTypes() {
+ return PgCategory.VALID_CATEGORIES;
+ }
+
+ getTypeByStates(pgStatesText: string): string {
+ const pgStates = this.getPgStatesFromText(pgStatesText);
+
+ if (pgStates.length === 0) {
+ return PgCategory.CATEGORY_UNKNOWN;
+ }
+
+ const intersections = _.zipObject(
+ PgCategory.VALID_CATEGORIES,
+ PgCategory.VALID_CATEGORIES.map(
+ (category) => _.intersection(this.categories[category].states, pgStates).length
+ )
+ );
+
+ if (intersections[PgCategory.CATEGORY_WARNING] > 0) {
+ return PgCategory.CATEGORY_WARNING;
+ }
+
+ const pgWorkingStates = intersections[PgCategory.CATEGORY_WORKING];
+ if (pgStates.length > intersections[PgCategory.CATEGORY_CLEAN] + pgWorkingStates) {
+ return PgCategory.CATEGORY_UNKNOWN;
+ }
+
+ return pgWorkingStates ? PgCategory.CATEGORY_WORKING : PgCategory.CATEGORY_CLEAN;
+ }
+
+ private createCategories() {
+ return _.zipObject(
+ PgCategory.VALID_CATEGORIES,
+ PgCategory.VALID_CATEGORIES.map((category) => new PgCategory(category))
+ );
+ }
+
+ private getPgStatesFromText(pgStatesText) {
+ const pgStates = pgStatesText
+ .replace(/[^a-z]+/g, ' ')
+ .trim()
+ .split(' ');
+
+ return _.uniq(pgStates);
+ }
+}
<context context-type="linenumber">1</context>
</context-group>
</trans-unit>
- <trans-unit id="a8059e31694578c1b0344a76a345357dd60e8f01" datatype="html">
- <source>Warning</source>
+ <trans-unit id="0054f5460090d6dde385e8f099d598df5d28cf54" datatype="html">
+ <source>Working</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/ceph/dashboard/health/health.component.ts</context>
<context context-type="linenumber">1</context>
</context-group>
</trans-unit>
- <trans-unit id="e5d8bb389c702588877f039d72178f219453a72d" datatype="html">
- <source>Unknown</source>
+ <trans-unit id="a8059e31694578c1b0344a76a345357dd60e8f01" datatype="html">
+ <source>Warning</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/ceph/dashboard/health/health.component.ts</context>
<context context-type="linenumber">1</context>
</context-group>
</trans-unit>
- <trans-unit id="0054f5460090d6dde385e8f099d598df5d28cf54" datatype="html">
- <source>Working</source>
+ <trans-unit id="e5d8bb389c702588877f039d72178f219453a72d" datatype="html">
+ <source>Unknown</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/ceph/dashboard/health/health.component.ts</context>
<context context-type="linenumber">1</context>
<context context-type="linenumber">1</context>
</context-group>
</trans-unit>
- <trans-unit id="1d1174ec0fd282599b9feb00fc7a770e12ee7294" datatype="html">
- <source>Placement Groups</source>
- <context-group purpose="location">
- <context context-type="sourcefile">src/app/ceph/pool/pool-list/pool-list.component.ts</context>
- <context context-type="linenumber">1</context>
- </context-group>
- </trans-unit>
<trans-unit id="112d356ad6846959ca4aa5ec9a1f0d9d473b24ec" datatype="html">
<source>Replica Size</source>
<context-group purpose="location">