]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: Eye candy updates to datatable pages 31764/head
authorVolker Theile <vtheile@suse.com>
Thu, 28 Nov 2019 15:54:06 +0000 (16:54 +0100)
committerVolker Theile <vtheile@suse.com>
Thu, 28 Nov 2019 15:54:06 +0000 (16:54 +0100)
- Introduce boolean and array pipes
- Introduce configurable 'badge' datatable column
- Refactor the Inventory, OSD, RBD, iSCSI, ... datatable pages

Signed-off-by: Volker Theile <vtheile@suse.com>
22 files changed:
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi/iscsi.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi/iscsi.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-snapshot-list/rbd-snapshot-list.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-snapshot-list/rbd-snapshot-list.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/inventory/inventory-devices/inventory-devices.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/inventory/inventory-devices/inventory-devices.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-list/osd-list.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-list/osd-list.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/shared/device-list/device-list.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/shared/device-list/device-list.component.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table-key-value/table-key-value.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table/table.component.html
src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table/table.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table/table.component.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/enum/cell-template.enum.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/models/cd-table-column.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/array.pipe.spec.ts [new file with mode: 0755]
src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/array.pipe.ts [new file with mode: 0755]
src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/boolean.pipe.spec.ts [new file with mode: 0755]
src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/boolean.pipe.ts [new file with mode: 0755]
src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/pipes.module.ts [changed mode: 0644->0755]
src/pybind/mgr/dashboard/frontend/src/styles.scss

index 4f65b3f2c54d01ea26a75a8b034bf552e55e20d0..5ccdbf03807bcdffdd9a3ebb0c8dd6fcfb139c6f 100644 (file)
           [columns]="imagesColumns">
 </cd-table>
 
-<ng-template #statusColorTpl
-             let-value="value">
-  <span class="badge"
-        [ngClass]="{'badge-success': 'up' == value, 'badge-danger': 'down' == value}">{{ value }}</span>
-</ng-template>
-
 <ng-template #iscsiSparklineTpl
              let-row="row"
              let-value="value">
index 210309652253e8d95f9673178ddcce4b7b77aadf..7ba012f58a5667667f169d6a085367b87c7ff975 100644 (file)
@@ -3,6 +3,7 @@ import { Component, OnInit, TemplateRef, ViewChild } from '@angular/core';
 import { I18n } from '@ngx-translate/i18n-polyfill';
 
 import { IscsiService } from '../../../shared/api/iscsi.service';
+import { CellTemplate } from '../../../shared/enum/cell-template.enum';
 import { DimlessPipe } from '../../../shared/pipes/dimless.pipe';
 import { IscsiBackstorePipe } from '../../../shared/pipes/iscsi-backstore.pipe';
 
@@ -12,8 +13,6 @@ import { IscsiBackstorePipe } from '../../../shared/pipes/iscsi-backstore.pipe';
   styleUrls: ['./iscsi.component.scss']
 })
 export class IscsiComponent implements OnInit {
-  @ViewChild('statusColorTpl', { static: true })
-  statusColorTpl: TemplateRef<any>;
   @ViewChild('iscsiSparklineTpl', { static: true })
   iscsiSparklineTpl: TemplateRef<any>;
   @ViewChild('iscsiPerSecondTpl', { static: true })
@@ -42,7 +41,15 @@ export class IscsiComponent implements OnInit {
       {
         name: this.i18n('State'),
         prop: 'state',
-        cellTemplate: this.statusColorTpl
+        flexGrow: 1,
+        cellClass: 'text-center',
+        cellTransformation: CellTemplate.badge,
+        customTemplateConfig: {
+          map: {
+            up: { class: 'badge-success' },
+            down: { class: 'badge-danger' }
+          }
+        }
       },
       {
         name: this.i18n('# Targets'),
index b0a5abbbeb7296f56e697c8d06f15b7a1b0aa47d..90fbf53841ad55d1e39a6a590a0175242b4ae3bf 100644 (file)
   </cd-table-actions>
 </cd-table>
 
-<ng-template #protectTpl
-             let-value="value">
-  <span *ngIf="value"
-        class="badge badge-success"
-        i18n>PROTECTED</span>
-  <span *ngIf="!value"
-        class="badge badge-info"
-        i18n>UNPROTECTED</span>
-</ng-template>
-
 <ng-template #rollbackTpl
              let-value>
   <ng-container i18n>You are about to rollback</ng-container>
index 225354329cbe1e99a0aaada5052ce4c194a1c383..ae71c7ff83a7ae31c2d374ea7eacda434e23b445 100644 (file)
@@ -47,8 +47,6 @@ export class RbdSnapshotListComponent implements OnInit, OnChanges {
   rbdName: string;
   @ViewChild('nameTpl', { static: false })
   nameTpl: TemplateRef<any>;
-  @ViewChild('protectTpl', { static: true })
-  protectTpl: TemplateRef<any>;
   @ViewChild('rollbackTpl', { static: true })
   rollbackTpl: TemplateRef<any>;
 
@@ -113,7 +111,13 @@ export class RbdSnapshotListComponent implements OnInit, OnChanges {
         prop: 'is_protected',
         flexGrow: 1,
         cellClass: 'text-center',
-        cellTemplate: this.protectTpl
+        cellTransformation: CellTemplate.badge,
+        customTemplateConfig: {
+          map: {
+            true: { value: this.i18n('PROTECTED'), class: 'badge-success' },
+            false: { value: this.i18n('UNPROTECTED'), class: 'badge-info' }
+          }
+        }
       },
       {
         name: this.i18n('Created'),
index 334dcfce2311fe60321870350efa6a31f121dc65..208bb0d34f19f90df61b50725e23ae5ee0996279 100644 (file)
     </div>
   </div>
 </cd-table>
-
-<ng-template #osds
-             let-value="value">
-  <span *ngFor="let osdId of value; last as last">
-    <span class="badge badge-dark">osd.{{ osdId }}</span>
-    <span *ngIf="!last">&nbsp;</span>
-  </span>
-</ng-template>
index 1b8a00bb96bfed777bbd08e3d978711a451e4917..b54d846eef1cc76e27abe080ee5a3c38946b69ad 100644 (file)
@@ -1,18 +1,10 @@
-import {
-  Component,
-  EventEmitter,
-  Input,
-  OnChanges,
-  OnInit,
-  Output,
-  TemplateRef,
-  ViewChild
-} from '@angular/core';
+import { Component, EventEmitter, Input, OnChanges, OnInit, Output } from '@angular/core';
 import { I18n } from '@ngx-translate/i18n-polyfill';
 
 import { getterForProp } from '@swimlane/ngx-datatable/release/utils';
 import * as _ from 'lodash';
 
+import { CellTemplate } from '../../../../shared/enum/cell-template.enum';
 import { Icons } from '../../../../shared/enum/icons.enum';
 import { CdTableColumn } from '../../../../shared/models/cd-table-column';
 import { DimlessBinaryPipe } from '../../../../shared/pipes/dimless-binary.pipe';
@@ -26,9 +18,6 @@ import { InventoryDevice } from './inventory-device.model';
   styleUrls: ['./inventory-devices.component.scss']
 })
 export class InventoryDevicesComponent implements OnInit, OnChanges {
-  @ViewChild('osds', { static: true })
-  osds: TemplateRef<any>;
-
   // Devices
   @Input() devices: InventoryDevice[] = [];
 
@@ -74,7 +63,15 @@ export class InventoryDevicesComponent implements OnInit, OnChanges {
       {
         name: this.i18n('Type'),
         prop: 'human_readable_type',
-        flexGrow: 1
+        flexGrow: 1,
+        cellClass: 'text-center',
+        cellTransformation: CellTemplate.badge,
+        customTemplateConfig: {
+          map: {
+            hdd: { value: 'HDD', class: 'badge-hdd' },
+            'ssd/nvme': { value: 'SSD', class: 'badge-ssd' }
+          }
+        }
       },
       {
         name: this.i18n('Available'),
@@ -101,7 +98,12 @@ export class InventoryDevicesComponent implements OnInit, OnChanges {
         name: this.i18n('OSDs'),
         prop: 'osd_ids',
         flexGrow: 1,
-        cellTemplate: this.osds
+        cellClass: 'text-center',
+        cellTransformation: CellTemplate.badge,
+        customTemplateConfig: {
+          class: 'badge-dark',
+          prefix: 'osd.'
+        }
       }
     ];
 
index 45c488fb1bff814720c4750c72fc210d29a0f898..f2f0d2fabb3f41cc01616f6544169310357a13be 100644 (file)
       </cd-osd-details>
     </cd-table>
 
-    <ng-template #statusColor
-                 let-value="value">
-      <span *ngFor="let state of value; last as last">
-        <span class="badge"
-              [ngClass]="{'badge-success': ['in', 'up'].includes(state), 'badge-danger': ['down', 'out', 'destroyed'].includes(state)}">{{ state }}</span>
-        <span *ngIf="!last">&nbsp;</span>
-      </span>
-    </ng-template>
-
     <ng-template #osdUsageTpl
                  let-row="row">
       <cd-usage-bar [totalBytes]="row.stats.stat_bytes"
index 42f6775634d41f4cd8a05fbdcbd11d4acee76c7e..3f9e473ed345223935304b4598c0550ff1d1cba7 100644 (file)
@@ -34,8 +34,6 @@ const BASE_URL = 'osd';
   providers: [{ provide: URLBuilderService, useValue: new URLBuilderService(BASE_URL) }]
 })
 export class OsdListComponent implements OnInit {
-  @ViewChild('statusColor', { static: true })
-  statusColor: TemplateRef<any>;
   @ViewChild('osdUsageTpl', { static: true })
   osdUsageTpl: TemplateRef<any>;
   @ViewChild('markOsdConfirmationTpl', { static: true })
@@ -207,7 +205,20 @@ export class OsdListComponent implements OnInit {
     this.columns = [
       { prop: 'host.name', name: this.i18n('Host') },
       { prop: 'id', name: this.i18n('ID'), cellTransformation: CellTemplate.bold },
-      { prop: 'collectedStates', name: this.i18n('Status'), cellTemplate: this.statusColor },
+      {
+        prop: 'collectedStates',
+        name: this.i18n('Status'),
+        cellTransformation: CellTemplate.badge,
+        customTemplateConfig: {
+          map: {
+            in: { class: 'badge-success' },
+            up: { class: 'badge-success' },
+            down: { class: 'badge-danger' },
+            out: { class: 'badge-danger' },
+            destroyed: { class: 'badge-danger' }
+          }
+        }
+      },
       { prop: 'stats.numpg', name: this.i18n('PGs') },
       { prop: 'stats.stat_bytes', name: this.i18n('Size'), pipe: this.dimlessBinaryPipe },
       { prop: 'stats.usage', name: this.i18n('Usage'), cellTemplate: this.osdUsageTpl },
index 79eb99b455013059d907f00059063ccbd0d9bd25..66c18df61b316c0cd9b0ba0627ea732916f68ed2 100644 (file)
              let-value="value">
   {{value}}
 </ng-template>
-
-<ng-template #state
-             let-value="value">
-  <ng-container *ngIf="value === 'good'">
-    <span class="badge badge-success"
-          i18n>Good</span>
-  </ng-container>
-  <ng-container *ngIf="value === 'warning'">
-    <span class="badge badge-warning"
-          i18n>Warning</span>
-  </ng-container>
-  <ng-container *ngIf="value === 'bad'">
-    <span class="badge badge-danger"
-          i18n>Bad</span>
-  </ng-container>
-  <ng-container *ngIf="value === 'stale'">
-    <span class="badge badge-info"
-          i18n>Stale</span>
-  </ng-container>
-  <ng-container *ngIf="value === 'unknown'">
-    <span class="badge badge-dark"
-          i18n>Unknown</span>
-  </ng-container>
-</ng-template>
index 3aeab98c5ac75cc8887441b59d4739d1d8977b7e..2626c168e2b38281d63c70c04d478292b6a94658 100644 (file)
@@ -3,6 +3,7 @@ import { Component, Input, OnInit, TemplateRef, ViewChild } from '@angular/core'
 import { I18n } from '@ngx-translate/i18n-polyfill';
 import { HostService } from '../../../shared/api/host.service';
 import { OsdService } from '../../../shared/api/osd.service';
+import { CellTemplate } from '../../../shared/enum/cell-template.enum';
 import { CdTableColumn } from '../../../shared/models/cd-table-column';
 import { CdDevice } from '../../../shared/models/devices';
 
@@ -23,8 +24,6 @@ export class DeviceListComponent implements OnInit {
   lifeExpectancyTemplate: TemplateRef<any>;
   @ViewChild('lifeExpectancyTimestamp', { static: true })
   lifeExpectancyTimestampTemplate: TemplateRef<any>;
-  @ViewChild('state', { static: true })
-  stateTemplate: TemplateRef<any>;
 
   devices: CdDevice[] = null;
   columns: CdTableColumn[] = [];
@@ -52,7 +51,17 @@ export class DeviceListComponent implements OnInit {
       {
         prop: 'state',
         name: this.i18n('State of Health'),
-        cellTemplate: this.stateTemplate
+        flexGrow: 1,
+        cellTransformation: CellTemplate.badge,
+        customTemplateConfig: {
+          map: {
+            good: { value: this.i18n('Good'), class: 'badge-success' },
+            warning: { value: this.i18n('Warning'), class: 'badge-warning' },
+            bad: { value: this.i18n('Bad'), class: 'badge-danger' },
+            stale: { value: this.i18n('Stale'), class: 'badge-info' },
+            unknown: { value: this.i18n('Unknown'), class: 'badge-dark' }
+          }
+        }
       },
       {
         prop: 'life_expectancy_weeks',
index 77affe22fedd8191c2053f26deb76e19c4bd93b4..965418ddbf3b1371b5dd8b7e26a4692ac54dc3c9 100644 (file)
@@ -10,6 +10,7 @@ import { ComponentsModule } from '../../components/components.module';
 import { CellTemplate } from '../../enum/cell-template.enum';
 import { CdTableColumn } from '../../models/cd-table-column';
 import { CdDatePipe } from '../../pipes/cd-date.pipe';
+import { PipesModule } from '../../pipes/pipes.module';
 import { TableComponent } from '../table/table.component';
 import { TableKeyValueComponent } from './table-key-value.component';
 
@@ -24,7 +25,8 @@ describe('TableKeyValueComponent', () => {
       NgxDatatableModule,
       ComponentsModule,
       RouterTestingModule,
-      BsDropdownModule.forRoot()
+      BsDropdownModule.forRoot(),
+      PipesModule
     ]
   });
 
index b029f64ac9522c5b3ae019b864e11ee47e362e44..7b3fb44a183bdcd1156d449eae97e57fd9150d29 100644 (file)
 <ng-template #checkIconTpl
              let-value="value">
   <i [ngClass]="[icons.check]"
-     [hidden]="!value"></i>
+     [hidden]="!(value | boolean)"></i>
 </ng-template>
 
 <ng-template #perSecondTpl
              let-value="value">
   <span class="{{useCustomClass(value)}}">{{ value }}</span>
 </ng-template>
+
+<ng-template #badgeTpl
+             let-column="column"
+             let-value="value">
+  <span *ngFor="let item of (value | array); last as last">
+    <span class="badge"
+          [ngClass]="column?.customTemplateConfig?.map[item]?.class ? column.customTemplateConfig.map[item].class : (column?.customTemplateConfig?.class ? column.customTemplateConfig.class : 'badge-primary')">
+      {{ column?.customTemplateConfig?.map[item]?.value ? column.customTemplateConfig.map[item].value : column?.customTemplateConfig?.prefix ? column.customTemplateConfig.prefix + item : item }}
+    </span>
+    <span *ngIf="!last">&nbsp;</span>
+  </span>
+</ng-template>
index 46baec7e139cfd75e98dcf742fa15450a3f00d3e..32c095fb51a2bbd2586c209e997ca16288ebe297 100644 (file)
@@ -9,6 +9,7 @@ import { BsDropdownModule } from 'ngx-bootstrap/dropdown';
 import { configureTestBed } from '../../../../testing/unit-test-helper';
 import { ComponentsModule } from '../../components/components.module';
 import { CdTableFetchDataContext } from '../../models/cd-table-fetch-data-context';
+import { PipesModule } from '../../pipes/pipes.module';
 import { TableComponent } from './table.component';
 
 describe('TableComponent', () => {
@@ -38,7 +39,8 @@ describe('TableComponent', () => {
       FormsModule,
       ComponentsModule,
       RouterTestingModule,
-      BsDropdownModule.forRoot()
+      BsDropdownModule.forRoot(),
+      PipesModule
     ]
   });
 
index e2a1d08e63e9a4213c409903570753cc839125e7..e0053fc4a6003a61901a072b87fff1577de24c61 100644 (file)
@@ -53,6 +53,8 @@ export class TableComponent implements AfterContentChecked, OnInit, OnChanges, O
   executingTpl: TemplateRef<any>;
   @ViewChild('classAddingTpl', { static: true })
   classAddingTpl: TemplateRef<any>;
+  @ViewChild('badgeTpl', { static: true })
+  badgeTpl: TemplateRef<any>;
 
   // This is the array with the items to be shown.
   @Input()
@@ -370,6 +372,7 @@ export class TableComponent implements AfterContentChecked, OnInit, OnChanges, O
     this.cellTemplates.perSecond = this.perSecondTpl;
     this.cellTemplates.executing = this.executingTpl;
     this.cellTemplates.classAdding = this.classAddingTpl;
+    this.cellTemplates.badge = this.badgeTpl;
   }
 
   useCustomClass(value: any): string {
index cf3e11a7ecfb9db7cdb20bcb4c4c3e7b2b678fe1..76746b3bd030a8e297cc0973e0847348b446c6fc 100644 (file)
@@ -5,5 +5,19 @@ export enum CellTemplate {
   checkIcon = 'checkIcon',
   routerLink = 'routerLink',
   executing = 'executing',
-  classAdding = 'classAdding'
+  classAdding = 'classAdding',
+  // Display the cell value as a badge. The template
+  // supports an optional custom configuration:
+  // {
+  //   ...
+  //   customTemplateConfig: {
+  //     class?: string; // Additional class name.
+  //     prefix?: any;   // Prefix of the value to be displayed.
+  //                     // 'map' and 'prefix' exclude each other.
+  //     map?: {
+  //       [key: any]: { value: any, class?: string }
+  //     }
+  //   }
+  // }
+  badge = 'badge'
 }
index 69194e8b88d77f49fd386f5823fd682cc630db12..64cd7db402ef6d44a1283ee9940f0cba553d1c71 100644 (file)
@@ -6,4 +6,5 @@ export interface CdTableColumn extends TableColumn {
   cellTransformation?: CellTemplate;
   isHidden?: boolean;
   prop: TableColumnProp; // Enforces properties to get sortable columns
+  customTemplateConfig?: any; // Custom configuration used by cell templates.
 }
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/array.pipe.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/array.pipe.spec.ts
new file mode 100755 (executable)
index 0000000..610e22c
--- /dev/null
@@ -0,0 +1,21 @@
+import { ArrayPipe } from './array.pipe';
+
+describe('ArrayPipe', () => {
+  const pipe = new ArrayPipe();
+
+  it('create an instance', () => {
+    expect(pipe).toBeTruthy();
+  });
+
+  it('transforms string to array', () => {
+    expect(pipe.transform('foo')).toStrictEqual(['foo']);
+  });
+
+  it('transforms array to array', () => {
+    expect(pipe.transform(['foo'], true)).toStrictEqual([['foo']]);
+  });
+
+  it('do not transforms array to array', () => {
+    expect(pipe.transform(['foo'])).toStrictEqual(['foo']);
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/array.pipe.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/array.pipe.ts
new file mode 100755 (executable)
index 0000000..b61b0b0
--- /dev/null
@@ -0,0 +1,26 @@
+import { Pipe, PipeTransform } from '@angular/core';
+
+import * as _ from 'lodash';
+
+/**
+ * Convert the given value to an array.
+ */
+@Pipe({
+  name: 'array'
+})
+export class ArrayPipe implements PipeTransform {
+  /**
+   * Convert the given value into an array. If the value is already an
+   * array, then nothing happens, except the `force` flag is set.
+   * @param value The value to process.
+   * @param force Convert the specified value to an array, either it is
+   *              already an array.
+   */
+  transform(value: any, force = false): any[] {
+    let result = value;
+    if (!_.isArray(value) || (_.isArray(value) && force)) {
+      result = [value];
+    }
+    return result;
+  }
+}
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/boolean.pipe.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/boolean.pipe.spec.ts
new file mode 100755 (executable)
index 0000000..36c5ed0
--- /dev/null
@@ -0,0 +1,57 @@
+import { BooleanPipe } from './boolean.pipe';
+
+describe('BooleanPipe', () => {
+  const pipe = new BooleanPipe();
+
+  it('create an instance', () => {
+    expect(pipe).toBeTruthy();
+  });
+
+  it('transforms to false [1/4]', () => {
+    expect(pipe.transform('n')).toBe(false);
+  });
+
+  it('transforms to false [2/4]', () => {
+    expect(pipe.transform(false)).toBe(false);
+  });
+
+  it('transforms to false [3/4]', () => {
+    expect(pipe.transform('bar')).toBe(false);
+  });
+
+  it('transforms to false [4/4]', () => {
+    expect(pipe.transform(2)).toBe(false);
+  });
+
+  it('transforms to true [1/8]', () => {
+    expect(pipe.transform(true)).toBe(true);
+  });
+
+  it('transforms to true [2/8]', () => {
+    expect(pipe.transform(1)).toBe(true);
+  });
+
+  it('transforms to true [3/8]', () => {
+    expect(pipe.transform('y')).toBe(true);
+  });
+
+  it('transforms to true [4/8]', () => {
+    expect(pipe.transform('yes')).toBe(true);
+  });
+
+  it('transforms to true [5/8]', () => {
+    expect(pipe.transform('t')).toBe(true);
+  });
+
+  it('transforms to true [6/8]', () => {
+    expect(pipe.transform('true')).toBe(true);
+  });
+
+  it('transforms to true [7/8]', () => {
+    expect(pipe.transform('on')).toBe(true);
+  });
+
+  it('transforms to true [8/8]', () => {
+    expect(pipe.transform('1')).toBe(true);
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/boolean.pipe.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/boolean.pipe.ts
new file mode 100755 (executable)
index 0000000..b94a40b
--- /dev/null
@@ -0,0 +1,26 @@
+import { Pipe, PipeTransform } from '@angular/core';
+
+/**
+ * Convert the given value to a boolean value.
+ */
+@Pipe({
+  name: 'boolean'
+})
+export class BooleanPipe implements PipeTransform {
+  transform(value: any): boolean {
+    let result = false;
+    switch (value) {
+      case true:
+      case 1:
+      case 'y':
+      case 'yes':
+      case 't':
+      case 'true':
+      case 'on':
+      case '1':
+        result = true;
+        break;
+    }
+    return result;
+  }
+}
old mode 100644 (file)
new mode 100755 (executable)
index 8983168..4499466
@@ -1,7 +1,9 @@
 import { CommonModule, DatePipe } from '@angular/common';
 import { NgModule } from '@angular/core';
 
+import { ArrayPipe } from './array.pipe';
 import { BooleanTextPipe } from './boolean-text.pipe';
+import { BooleanPipe } from './boolean.pipe';
 import { CdDatePipe } from './cd-date.pipe';
 import { CephReleaseNamePipe } from './ceph-release-name.pipe';
 import { CephShortVersionPipe } from './ceph-short-version.pipe';
@@ -27,6 +29,8 @@ import { UpperFirstPipe } from './upper-first.pipe';
 @NgModule({
   imports: [CommonModule],
   declarations: [
+    ArrayPipe,
+    BooleanPipe,
     BooleanTextPipe,
     DimlessBinaryPipe,
     DimlessBinaryPerSecondPipe,
@@ -51,6 +55,8 @@ import { UpperFirstPipe } from './upper-first.pipe';
     DurationPipe
   ],
   exports: [
+    ArrayPipe,
+    BooleanPipe,
     BooleanTextPipe,
     DimlessBinaryPipe,
     DimlessBinaryPerSecondPipe,
@@ -75,6 +81,8 @@ import { UpperFirstPipe } from './upper-first.pipe';
     DurationPipe
   ],
   providers: [
+    ArrayPipe,
+    BooleanPipe,
     BooleanTextPipe,
     DatePipe,
     CephShortVersionPipe,
index cdff4f4c9ad52ce2d1bda9151666cb3dbfcc90b4..ef3cbb0f8bfebd178a9a7be5efff17d373536730 100644 (file)
@@ -431,3 +431,13 @@ bfv-messages {
     background: $color-green;
   }
 }
+
+// Custom badges.
+.badge-hdd {
+  color: $color-solid-white;
+  background-color: $color-blue-gray;
+}
+.badge-ssd {
+  color: $color-solid-white;
+  background-color: $color-blue;
+}