]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: display devices' health information within a tabset 37275/head
authorKiefer Chang <kiefer.chang@suse.com>
Thu, 17 Sep 2020 12:58:47 +0000 (20:58 +0800)
committerKiefer Chang <kiefer.chang@suse.com>
Tue, 22 Sep 2020 06:38:34 +0000 (14:38 +0800)
Wrap all devices' health information within a tabset
instead of displaying them from top to bottom.

Add more guard in the HTML template to prevent referencing undefined
variables.

Fixes: https://tracker.ceph.com/issues/47494
Fixes: https://tracker.ceph.com/issues/43177
Signed-off-by: Kiefer Chang <kiefer.chang@suse.com>
src/pybind/mgr/dashboard/frontend/src/app/ceph/shared/smart-list/smart-list.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/shared/smart-list/smart-list.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/shared/smart-list/smart-list.component.ts

index 2179c049d3496f499afe3a4377b7f358989e40cc..78e0c157850371d16cd83cdc305c659042886913 100644 (file)
@@ -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>
index 7570528aba625610131e6414ece0e775d729a2b5..6ee1af02e45f277eeee8cabc8774ee85d9b1e576 100644 (file)
@@ -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]
@@ -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'
+    );
   });
 });
index ee9c729813e4c8e356724ccfff6df74cd590007c..e9cab7e9c4d69fdd077eaea8d20f91441ee55dd5 100644 (file)
@@ -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';
@@ -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()
@@ -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` },