Skip to content

Commit

Permalink
Merge pull request ceph#37275 from bk201/wip-47494
Browse files Browse the repository at this point in the history
mgr/dashboard: display devices' health information within a tabset

Reviewed-by: Patrick Seidensal <[email protected]>
Reviewed-by: Volker Theile <[email protected]>
  • Loading branch information
Lenz Grimmer authored Sep 24, 2020
2 parents 4d4fae3 + ba3350c commit e270e8e
Show file tree
Hide file tree
Showing 3 changed files with 156 additions and 65 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,75 +8,91 @@
dashboard.</cd-alert-panel>

<ng-container *ngIf="!error && !incompatible">
<cd-alert-panel *ngIf="!(data | keyvalue).length"
<cd-alert-panel *ngIf="isEmpty(data)"
type="info"
i18n>No SMART data available.</cd-alert-panel>

<ng-container *ngFor="let device of data | keyvalue">
<ng-container *ngIf="!isEmpty(data)">
<ul ngbNav
#nav="ngbNav"
class="nav-tabs">
<li ngbNavItem>
<a ngbNavLink
i18n>{{ device.value.device }} ({{ device.value.identifier }})</a>
<li ngbNavItem
*ngFor="let device of data | keyvalue">
<a ngbNavLink>{{ device.value.device }} ({{ device.value.identifier }})</a>
<ng-template ngbNavContent>

<ng-container *ngIf="device.value.error; else noError">
<cd-alert-panel id="alert-error"
type="warning">{{ device.value.userMessage }}</cd-alert-panel>
</ng-container>

<ng-template #noError>
<!-- HDD/NVMe self test -->
<ng-container *ngIf="device.value.info.smart_status.passed; else selfTestFailed">
<cd-alert-panel id="alert-self-test-passed"
size="slim"
type="info"
i18n-title
title="SMART overall-health self-assessment test result"
i18n>passed</cd-alert-panel>
</ng-container>
<ng-template #selfTestFailed>
<cd-alert-panel id="alert-self-test-failed"
size="slim"
type="warning"
i18n-title
title="SMART overall-health self-assessment test result"
i18n>failed</cd-alert-panel>
<cd-alert-panel *ngIf="isEmpty(device.value.info?.smart_status); else hasSmartStatus"
id="alert-self-test-unknown"
size="slim"
type="warning"
i18n-title
title="SMART overall-health self-assessment test result"
i18n>unknown</cd-alert-panel>
<ng-template #hasSmartStatus>
<!-- HDD/NVMe self test -->
<ng-container *ngIf="device.value.info.smart_status.passed; else selfTestFailed">
<cd-alert-panel id="alert-self-test-passed"
size="slim"
type="info"
i18n-title
title="SMART overall-health self-assessment test result"
i18n>passed</cd-alert-panel>
</ng-container>
<ng-template #selfTestFailed>
<cd-alert-panel id="alert-self-test-failed"
size="slim"
type="warning"
i18n-title
title="SMART overall-health self-assessment test result"
i18n>failed</cd-alert-panel>
</ng-template>
</ng-template>
</ng-template>

<ul ngbNav
#innerNav="ngbNav"
class="nav-tabs">
<li ngbNavItem>
<a ngbNavLink
i18n>Device Information</a>
<ng-template ngbNavContent>
<cd-table-key-value [renderObjects]="true"
[data]="device.value.info"></cd-table-key-value>
</ng-template>
</li>
<li ngbNavItem>
<a ngbNavLink
i18n>SMART</a>
<ng-template ngbNavContent>
<cd-table *ngIf="device.value.smart.attributes"
[data]="device.value.smart.attributes.table"
updateSelectionOnRefresh="never"
[columns]="smartDataColumns"></cd-table>
<cd-table-key-value *ngIf="device.value.smart.nvmeData"
[renderObjects]="true"
[data]="device.value.smart.nvmeData"
updateSelectionOnRefresh="never"></cd-table-key-value>
<cd-alert-panel *ngIf="!device.value.smart.attributes && !device.value.smart.nvmeData"
type="info"
i18n>No SMART data available for this device.</cd-alert-panel>
</ng-template>
</li>
</ul>
<ng-container *ngIf="!isEmpty(device.value.info) || !isEmpty(device.value.smart)">
<ul ngbNav
#innerNav="ngbNav"
class="nav-tabs">
<li [ngbNavItem]="1">
<a ngbNavLink
i18n>Device Information</a>
<ng-template ngbNavContent>
<cd-table-key-value *ngIf="!isEmpty(device.value.info)"
[renderObjects]="true"
[data]="device.value.info"></cd-table-key-value>
<cd-alert-panel *ngIf="isEmpty(device.value.info)"
id="alert-device-info-unavailable"
type="info"
i18n>No device information available for this device.</cd-alert-panel>
</ng-template>
</li>
<li [ngbNavItem]="2">
<a ngbNavLink
i18n>SMART</a>
<ng-template ngbNavContent>
<cd-table *ngIf="device.value.smart?.attributes"
[data]="device.value.smart.attributes.table"
updateSelectionOnRefresh="never"
[columns]="smartDataColumns"></cd-table>
<cd-table-key-value *ngIf="device.value.smart?.nvmeData"
[renderObjects]="true"
[data]="device.value.smart.nvmeData"
updateSelectionOnRefresh="never"></cd-table-key-value>
<cd-alert-panel *ngIf="!device.value.smart?.attributes && !device.value.smart?.nvmeData"
id="alert-device-smart-data-unavailable"
type="info"
i18n>No SMART data available for this device.</cd-alert-panel>
</ng-template>
</li>
</ul>

<div [ngbNavOutlet]="innerNav"></div>
<div [ngbNavOutlet]="innerNav"></div>
</ng-container>
</ng-template>
</li>
</ul>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,34 @@ describe('OsdSmartListComponent', () => {
component.ngOnChanges(changes);
};

/**
* Verify an alert panel and its attributes.
*
* @param selector The CSS selector for the alert panel.
* @param panelTitle The title should be displayed.
* @param panelType Alert level of panel. Can be in `warning` or `info`.
* @param panelSize Pass `slim` for slim alert panel.
*/
const verifyAlertPanel = (
selector: string,
panelTitle: string,
panelType: 'warning' | 'info',
panelSize?: 'slim'
) => {
const alertPanel = fixture.debugElement.query(By.css(selector));
expect(component.incompatible).toBe(false);
expect(component.loading).toBe(false);

expect(alertPanel.attributes.type).toBe(panelType);
if (panelSize === 'slim') {
expect(alertPanel.attributes.title).toBe(panelTitle);
expect(alertPanel.attributes.size).toBe(panelSize);
} else {
const panelText = alertPanel.query(By.css('.alert-panel-text'));
expect(panelText.nativeElement.textContent).toBe(panelTitle);
}
};

configureTestBed({
declarations: [SmartListComponent],
imports: [BrowserAnimationsModule, SharedModule, HttpClientTestingModule, NgbNavModule]
Expand Down Expand Up @@ -143,22 +171,61 @@ describe('OsdSmartListComponent', () => {
it('should display info panel for passed self test', () => {
initializeComponentWithData('hdd_v1');
fixture.detectChanges();
const alertPanel = fixture.debugElement.query(By.css('cd-alert-panel#alert-self-test-passed'));
expect(component.incompatible).toBe(false);
expect(component.loading).toBe(false);
expect(alertPanel.attributes.size).toBe('slim');
expect(alertPanel.attributes.title).toBe('SMART overall-health self-assessment test result');
expect(alertPanel.attributes.type).toBe('info');
verifyAlertPanel(
'cd-alert-panel#alert-self-test-passed',
'SMART overall-health self-assessment test result',
'info',
'slim'
);
});

it('should display warning panel for failed self test', () => {
initializeComponentWithData('hdd_v1', { 'smart_status.passed': false });
fixture.detectChanges();
const alertPanel = fixture.debugElement.query(By.css('cd-alert-panel#alert-self-test-failed'));
expect(component.incompatible).toBe(false);
expect(component.loading).toBe(false);
expect(alertPanel.attributes.size).toBe('slim');
expect(alertPanel.attributes.title).toBe('SMART overall-health self-assessment test result');
expect(alertPanel.attributes.type).toBe('warning');
verifyAlertPanel(
'cd-alert-panel#alert-self-test-failed',
'SMART overall-health self-assessment test result',
'warning',
'slim'
);
});

it('should display warning panel for unknown self test', () => {
initializeComponentWithData('hdd_v1', { smart_status: undefined });
fixture.detectChanges();
verifyAlertPanel(
'cd-alert-panel#alert-self-test-unknown',
'SMART overall-health self-assessment test result',
'warning',
'slim'
);
});

it('should display info panel for empty device info', () => {
initializeComponentWithData('hdd_v1');
const deviceId: string = _.keys(component.data)[0];
component.data[deviceId]['info'] = {};
fixture.detectChanges();
component.nav.select(1);
fixture.detectChanges();
verifyAlertPanel(
'cd-alert-panel#alert-device-info-unavailable',
'No device information available for this device.',
'info'
);
});

it('should display info panel for empty SMART data', () => {
initializeComponentWithData('hdd_v1');
const deviceId: string = _.keys(component.data)[0];
component.data[deviceId]['smart'] = {};
fixture.detectChanges();
component.nav.select(2);
fixture.detectChanges();
verifyAlertPanel(
'cd-alert-panel#alert-device-smart-data-unavailable',
'No SMART data available for this device.',
'info'
);
});
});
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Component, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core';
import { Component, Input, OnChanges, OnInit, SimpleChanges, ViewChild } from '@angular/core';

import { NgbNav } from '@ng-bootstrap/ng-bootstrap';
import _ from 'lodash';

import { HostService } from '../../../shared/api/host.service';
Expand All @@ -19,6 +20,9 @@ import {
styleUrls: ['./smart-list.component.scss']
})
export class SmartListComponent implements OnInit, OnChanges {
@ViewChild('innerNav')
nav: NgbNav;

@Input()
osdId: number = null;
@Input()
Expand Down Expand Up @@ -141,6 +145,10 @@ smartmontools is required to successfully retrieve data.`;
}
}

isEmpty(value: any) {
return _.isEmpty(value);
}

ngOnInit() {
this.smartDataColumns = [
{ prop: 'id', name: $localize`ID` },
Expand Down

0 comments on commit e270e8e

Please sign in to comment.