From: Sebastian Krah Date: Tue, 7 Jan 2020 15:56:40 +0000 (+0100) Subject: mgr/dashboard: Add expand/collapse datatable feature X-Git-Tag: v16.1.0~2585^2 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=d2d0efdc053b9818144b685be0d7593db8dbf398;p=ceph.git mgr/dashboard: Add expand/collapse datatable feature Adds expand/collapse feature to every datatable with details. Fixes: https://tracker.ceph.com/issues/40702 Signed-off-by: Sebastian Krah --- diff --git a/src/pybind/mgr/dashboard/frontend/e2e/block/images.po.ts b/src/pybind/mgr/dashboard/frontend/e2e/block/images.po.ts index d28b3c8952c9..11e5ab1f0e44 100644 --- a/src/pybind/mgr/dashboard/frontend/e2e/block/images.po.ts +++ b/src/pybind/mgr/dashboard/frontend/e2e/block/images.po.ts @@ -48,7 +48,7 @@ export class ImagesPageHelper extends PageHelper { await element(by.cssContainingText('button', 'Edit RBD')).click(); await this.navigateTo(); - await this.waitClickableAndClick(this.getFirstTableCellWithText(newName)); + await this.waitClickableAndClick(this.getExpandCollapseElement(newName)); await expect( element.all(by.css('.table.table-striped.table-bordered')).first().getText() ).toMatch(newSize); diff --git a/src/pybind/mgr/dashboard/frontend/e2e/cluster/configuration.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/e2e/cluster/configuration.e2e-spec.ts index a806c31ef036..57b4d921a459 100644 --- a/src/pybind/mgr/dashboard/frontend/e2e/cluster/configuration.e2e-spec.ts +++ b/src/pybind/mgr/dashboard/frontend/e2e/cluster/configuration.e2e-spec.ts @@ -25,16 +25,15 @@ describe('Configuration page', () => { describe('fields check', () => { beforeAll(async () => { await configuration.navigateTo(); + await configuration.waitClickableAndClick(configuration.getFirstExpandCollapseElement()); }); it('should verify that selected footer increases when an entry is clicked', async () => { - await configuration.getFirstCell().click(); const selectedCount = await configuration.getTableSelectedCount(); await expect(selectedCount).toBe(1); }); it('should check that details table opens and tab is correct', async () => { - await configuration.getFirstCell().click(); await expect($('.table.table-striped.table-bordered').isDisplayed()); await expect(configuration.getTabsCount()).toEqual(1); await expect(configuration.getTabText(0)).toEqual('Details'); diff --git a/src/pybind/mgr/dashboard/frontend/e2e/cluster/configuration.po.ts b/src/pybind/mgr/dashboard/frontend/e2e/cluster/configuration.po.ts index 2354f07bffa0..635522653996 100644 --- a/src/pybind/mgr/dashboard/frontend/e2e/cluster/configuration.po.ts +++ b/src/pybind/mgr/dashboard/frontend/e2e/cluster/configuration.po.ts @@ -33,7 +33,8 @@ export class ConfigurationPageHelper extends PageHelper { await $('input.form-control.ng-valid').clear(); await $('input.form-control.ng-valid').sendKeys(name); - await this.waitClickableAndClick(this.getFirstTableCellWithText(name)); + // Expand row + await this.waitClickableAndClick(this.getExpandCollapseElement(name)); // Clicks desired config await this.waitVisibility( $('.table.table-striped.table-bordered'), // Checks for visibility of details tab @@ -78,7 +79,7 @@ export class ConfigurationPageHelper extends PageHelper { await this.waitVisibility(this.getFirstTableCellWithText(name)); // Checks for visibility of config in table - await this.getFirstTableCellWithText(name).click(); + await this.getExpandCollapseElement(name).click(); // Clicks config for (let i = 0, valtuple; (valtuple = values[i]); i++) { // iterates through list of values and diff --git a/src/pybind/mgr/dashboard/frontend/e2e/cluster/mgr-modules.po.ts b/src/pybind/mgr/dashboard/frontend/e2e/cluster/mgr-modules.po.ts index bdd305628791..1b928c45505a 100644 --- a/src/pybind/mgr/dashboard/frontend/e2e/cluster/mgr-modules.po.ts +++ b/src/pybind/mgr/dashboard/frontend/e2e/cluster/mgr-modules.po.ts @@ -29,7 +29,7 @@ export class ManagerModulesPageHelper extends PageHelper { // Checks if edits appear await this.navigateTo(); await this.waitVisibility(this.getFirstTableCellWithText(name)); - await this.getFirstTableCellWithText(name).click(); + await this.getExpandCollapseElement(name).click(); for (const entry of tuple) { await this.waitTextToBePresent($$('.datatable-body').last(), entry[0]); } @@ -48,7 +48,7 @@ export class ManagerModulesPageHelper extends PageHelper { await element(by.cssContainingText('button', 'Update')).click(); await this.navigateTo(); await this.waitVisibility(this.getFirstTableCellWithText(name)); - await this.getFirstTableCellWithText(name).click(); + await this.getExpandCollapseElement(name).click(); for (const entry of tuple) { await this.waitTextNotPresent($$('.datatable-body').last(), entry[0]); } @@ -91,7 +91,7 @@ export class ManagerModulesPageHelper extends PageHelper { await this.navigateTo(); await this.waitVisibility(this.getFirstTableCellWithText('devicehealth')); // Checks for visibility of devicehealth in table - await this.getFirstTableCellWithText('devicehealth').click(); + await this.getExpandCollapseElement('devicehealth').click(); for (let i = 0, devHealthTuple: [string, string]; (devHealthTuple = devHealthArray[i]); i++) { if (devHealthTuple[0] !== undefined) { await this.waitFn(async () => { @@ -131,7 +131,7 @@ export class ManagerModulesPageHelper extends PageHelper { await this.waitClickableAndClick(element(by.cssContainingText('button', 'Update'))); await this.navigateTo(); await this.waitVisibility(this.getFirstTableCellWithText('devicehealth')); - await this.getFirstTableCellWithText('devicehealth').click(); + await this.getExpandCollapseElement('devicehealth').click(); for (let i = 0, devHealthTuple: [string, string]; (devHealthTuple = devHealthArray[i]); i++) { if (devHealthTuple[0] !== undefined) { await this.waitFn(async () => { diff --git a/src/pybind/mgr/dashboard/frontend/e2e/cluster/osds.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/e2e/cluster/osds.e2e-spec.ts index f4a08281ae76..f8a0939bc6c2 100644 --- a/src/pybind/mgr/dashboard/frontend/e2e/cluster/osds.e2e-spec.ts +++ b/src/pybind/mgr/dashboard/frontend/e2e/cluster/osds.e2e-spec.ts @@ -45,7 +45,7 @@ describe('OSDs page', () => { describe('by selecting one row in OSDs List', () => { beforeAll(async () => { - await osds.getFirstCell().click(); + await osds.waitClickableAndClick(osds.getFirstExpandCollapseElement()); }); it('should verify that selected footer increases', async () => { diff --git a/src/pybind/mgr/dashboard/frontend/e2e/page-helper.po.ts b/src/pybind/mgr/dashboard/frontend/e2e/page-helper.po.ts index 671ef34d225c..b5d4f5fb23e8 100644 --- a/src/pybind/mgr/dashboard/frontend/e2e/page-helper.po.ts +++ b/src/pybind/mgr/dashboard/frontend/e2e/page-helper.po.ts @@ -99,6 +99,15 @@ export abstract class PageHelper { return element.all(by.cssContainingText('.datatable-body-cell-label', content)).first(); } + getFirstExpandCollapseElement(): ElementFinder { + return element.all(by.className('tc_expand-collapse')).first(); + } + + getExpandCollapseElement(content: string): ElementFinder { + const tableRow = element(by.cssContainingText('.datatable-body-row', content)); + return tableRow.element(by.className('tc_expand-collapse')); + } + getTableRow(content: string) { return element(by.cssContainingText('.datatable-body-row', content)); } diff --git a/src/pybind/mgr/dashboard/frontend/e2e/rgw/buckets.po.ts b/src/pybind/mgr/dashboard/frontend/e2e/rgw/buckets.po.ts index 9da584403bcc..ab14627c2a53 100644 --- a/src/pybind/mgr/dashboard/frontend/e2e/rgw/buckets.po.ts +++ b/src/pybind/mgr/dashboard/frontend/e2e/rgw/buckets.po.ts @@ -65,7 +65,7 @@ export class BucketsPageHelper extends PageHelper { // wait to be back on buckets page with table visible and click await this.waitClickableAndClick( - this.getFirstTableCellWithText(name), + this.getExpandCollapseElement(name), 'Could not return to buckets page and load table after editing bucket' ); @@ -90,7 +90,7 @@ export class BucketsPageHelper extends PageHelper { // Check versioning suspended: await this.waitClickableAndClick( - this.getFirstTableCellWithText(name), + this.getExpandCollapseElement(name), 'Could not return to buckets page and load table after editing bucket' ); bucketDataTable = element.all(by.css('.table.table-striped.table-bordered')).first(); diff --git a/src/pybind/mgr/dashboard/frontend/e2e/rgw/users.po.ts b/src/pybind/mgr/dashboard/frontend/e2e/rgw/users.po.ts index 200d60fe5f90..77aa572bdf74 100644 --- a/src/pybind/mgr/dashboard/frontend/e2e/rgw/users.po.ts +++ b/src/pybind/mgr/dashboard/frontend/e2e/rgw/users.po.ts @@ -60,7 +60,7 @@ export class UsersPageHelper extends PageHelper { const editbutton = element(by.cssContainingText('button', 'Edit User')); await editbutton.click(); // Click the user and check its details table for updated content - await this.waitClickableAndClick(this.getFirstTableCellWithText(name)); + await this.waitClickableAndClick(this.getExpandCollapseElement(name)); await expect($('.active.tab-pane').getText()).toMatch(new_fullname); // check full name was changed await expect($('.active.tab-pane').getText()).toMatch(new_email); // check email was changed await expect($('.active.tab-pane').getText()).toMatch(new_maxbuckets); // check max buckets was changed diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-target-details/iscsi-target-details.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-target-details/iscsi-target-details.component.spec.ts index 26d726732cc9..9a4c9c2612d3 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-target-details/iscsi-target-details.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-target-details/iscsi-target-details.component.spec.ts @@ -5,7 +5,6 @@ import * as _ from 'lodash'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { configureTestBed, i18nProviders } from '../../../../testing/unit-test-helper'; -import { CdTableSelection } from '../../../shared/models/cd-table-selection'; import { SharedModule } from '../../../shared/shared.module'; import { IscsiTargetDetailsComponent } from './iscsi-target-details.component'; @@ -42,37 +41,35 @@ describe('IscsiTargetDetailsComponent', () => { backstores: ['backstore:1', 'backstore:2'], default_backstore: 'backstore:1' }; - component.selection = new CdTableSelection(); - component.selection.selected = [ - { - target_iqn: 'iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw', - portals: [{ host: 'node1', ip: '192.168.100.201' }], - disks: [ - { - pool: 'rbd', - image: 'disk_1', - backstore: 'backstore:1', - controls: { hw_max_sectors: 1 } - } - ], - clients: [ - { - client_iqn: 'iqn.1994-05.com.redhat:rh7-client', - luns: [{ pool: 'rbd', image: 'disk_1' }], - auth: { - user: 'myiscsiusername' - }, - info: { - alias: 'myhost', - ip_address: ['192.168.200.1'], - state: { LOGGED_IN: ['node1'] } - } + component.selection = undefined; + component.selection = { + target_iqn: 'iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw', + portals: [{ host: 'node1', ip: '192.168.100.201' }], + disks: [ + { + pool: 'rbd', + image: 'disk_1', + backstore: 'backstore:1', + controls: { hw_max_sectors: 1 } + } + ], + clients: [ + { + client_iqn: 'iqn.1994-05.com.redhat:rh7-client', + luns: [{ pool: 'rbd', image: 'disk_1' }], + auth: { + user: 'myiscsiusername' + }, + info: { + alias: 'myhost', + ip_address: ['192.168.200.1'], + state: { LOGGED_IN: ['node1'] } } - ], - groups: [], - target_controls: { dataout_timeout: 2 } - } - ]; + } + ], + groups: [], + target_controls: { dataout_timeout: 2 } + }; fixture.detectChanges(); }); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-target-details/iscsi-target-details.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-target-details/iscsi-target-details.component.ts index 893007bd8ec1..41bc99d9aacd 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-target-details/iscsi-target-details.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-target-details/iscsi-target-details.component.ts @@ -13,7 +13,6 @@ import * as _ from 'lodash'; import { TableComponent } from '../../../shared/datatable/table/table.component'; import { Icons } from '../../../shared/enum/icons.enum'; import { CdTableColumn } from '../../../shared/models/cd-table-column'; -import { CdTableSelection } from '../../../shared/models/cd-table-selection'; import { BooleanTextPipe } from '../../../shared/pipes/boolean-text.pipe'; import { IscsiBackstorePipe } from '../../../shared/pipes/iscsi-backstore.pipe'; @@ -24,7 +23,7 @@ import { IscsiBackstorePipe } from '../../../shared/pipes/iscsi-backstore.pipe'; }) export class IscsiTargetDetailsComponent implements OnChanges, OnInit { @Input() - selection: CdTableSelection; + selection: any; @Input() settings: any; @Input() @@ -91,8 +90,8 @@ export class IscsiTargetDetailsComponent implements OnChanges, OnInit { } ngOnChanges() { - if (this.selection.hasSelection) { - this.selectedItem = this.selection.first(); + if (this.selection) { + this.selectedItem = this.selection; this.generateTree(); } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-target-list/iscsi-target-list.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-target-list/iscsi-target-list.component.html index 1b8eeb83e4ae..276629cd413b 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-target-list/iscsi-target-list.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-target-list/iscsi-target-list.component.html @@ -23,6 +23,8 @@ identifier="target_iqn" forceIdentifier="true" selectionType="single" + [hasDetails]="true" + (setExpandedRow)="setExpandedRow($event)" (updateSelection)="updateSelection($event)">
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-target-list/iscsi-target-list.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-target-list/iscsi-target-list.component.ts index f3911d58a9dc..026cf343240e 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-target-list/iscsi-target-list.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-target-list/iscsi-target-list.component.ts @@ -6,6 +6,7 @@ import { BsModalRef, BsModalService } from 'ngx-bootstrap/modal'; import { Subscription } from 'rxjs'; import { IscsiService } from '../../../shared/api/iscsi.service'; +import { ListWithDetails } from '../../../shared/classes/list-with-details.class'; import { CriticalConfirmationModalComponent } from '../../../shared/components/critical-confirmation-modal/critical-confirmation-modal.component'; import { ActionLabelsI18n } from '../../../shared/constants/app.constants'; import { TableComponent } from '../../../shared/datatable/table/table.component'; @@ -31,7 +32,7 @@ import { IscsiTargetDiscoveryModalComponent } from '../iscsi-target-discovery-mo styleUrls: ['./iscsi-target-list.component.scss'], providers: [TaskListService] }) -export class IscsiTargetListComponent implements OnInit, OnDestroy { +export class IscsiTargetListComponent extends ListWithDetails implements OnInit, OnDestroy { @ViewChild(TableComponent, { static: false }) table: TableComponent; @@ -69,6 +70,7 @@ export class IscsiTargetListComponent implements OnInit, OnDestroy { private taskWrapper: TaskWrapperService, public actionLabels: ActionLabelsI18n ) { + super(); this.permission = this.authStorageService.getPermissions().iscsi; this.tableActions = [ diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-details/rbd-details.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-details/rbd-details.component.html index c5c776613a3c..fee853628f3d 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-details/rbd-details.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-details/rbd-details.component.html @@ -2,7 +2,7 @@ Only available for RBD images with fast-diff enabled - + @@ -10,43 +10,43 @@ - + - + - + - + - + - + - + @@ -55,14 +55,14 @@ @@ -70,59 +70,59 @@ - + - + - + - +
Name{{ selectedItem.name }}{{ selection.name }}
Pool{{ selectedItem.pool_name }}{{ selection.pool_name }}
Data Pool{{ selectedItem.data_pool | empty }}{{ selection.data_pool | empty }}
Created{{ selectedItem.timestamp | cdDate }}{{ selection.timestamp | cdDate }}
Size{{ selectedItem.size | dimlessBinary }}{{ selection.size | dimlessBinary }}
Objects{{ selectedItem.num_objs | dimless }}{{ selection.num_objs | dimless }}
Object size{{ selectedItem.obj_size | dimlessBinary }}{{ selection.obj_size | dimlessBinary }}
Features - + {{ feature }} Provisioned - + N/A - - {{ selectedItem.disk_usage | dimlessBinary }} + + {{ selection.disk_usage | dimlessBinary }}
Total provisioned - + N/A - - {{ selectedItem.total_disk_usage | dimlessBinary }} + + {{ selection.total_disk_usage | dimlessBinary }}
Striping unit{{ selectedItem.stripe_unit | dimlessBinary }}{{ selection.stripe_unit | dimlessBinary }}
Striping count{{ selectedItem.stripe_count }}{{ selection.stripe_count }}
Parent - {{ selectedItem.parent.pool_name }}/{{ selectedItem.parent.pool_namespace }}/{{ selectedItem.parent.image_name }}@{{ selectedItem.parent.snap_name }} - - + {{ selection.parent.pool_name }}/{{ selection.parent.pool_namespace }}/{{ selection.parent.image_name }}@{{ selection.parent.snap_name }} + -
Block name prefix{{ selectedItem.block_name_prefix }}{{ selection.block_name_prefix }}
Order{{ selectedItem.order }}{{ selection.order }}
- + - +
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-details/rbd-details.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-details/rbd-details.component.ts index 0463230ac01e..0916391e0663 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-details/rbd-details.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-details/rbd-details.component.ts @@ -1,6 +1,5 @@ -import { Component, Input, OnChanges, TemplateRef, ViewChild } from '@angular/core'; +import { Component, Input, TemplateRef, ViewChild } from '@angular/core'; -import { CdTableSelection } from '../../../shared/models/cd-table-selection'; import { RbdFormModel } from '../rbd-form/rbd-form.model'; @Component({ @@ -8,20 +7,13 @@ import { RbdFormModel } from '../rbd-form/rbd-form.model'; templateUrl: './rbd-details.component.html', styleUrls: ['./rbd-details.component.scss'] }) -export class RbdDetailsComponent implements OnChanges { +export class RbdDetailsComponent { @Input() - selection: CdTableSelection; - selectedItem: RbdFormModel; + selection: RbdFormModel; @Input() images: any; @ViewChild('poolConfigurationSourceTpl', { static: true }) poolConfigurationSourceTpl: TemplateRef; constructor() {} - - ngOnChanges() { - if (this.selection.hasSelection) { - this.selectedItem = this.selection.first(); - } - } } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-list/rbd-list.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-list/rbd-list.component.html index fbf6aa0a19ac..098b297e1bbb 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-list/rbd-list.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-list/rbd-list.component.html @@ -12,6 +12,8 @@ [searchableObjects]="true" forceIdentifier="true" selectionType="single" + [hasDetails]="true" + (setExpandedRow)="setExpandedRow($event)" (updateSelection)="updateSelection($event)"> + [selection]="expandedRow"> diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-list/rbd-list.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-list/rbd-list.component.ts index aaea7eb89055..f69945b6e7eb 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-list/rbd-list.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-list/rbd-list.component.ts @@ -5,6 +5,7 @@ import * as _ from 'lodash'; import { BsModalRef, BsModalService } from 'ngx-bootstrap/modal'; import { RbdService } from '../../../shared/api/rbd.service'; +import { ListWithDetails } from '../../../shared/classes/list-with-details.class'; import { ConfirmationModalComponent } from '../../../shared/components/confirmation-modal/confirmation-modal.component'; import { CriticalConfirmationModalComponent } from '../../../shared/components/critical-confirmation-modal/critical-confirmation-modal.component'; import { ActionLabelsI18n } from '../../../shared/constants/app.constants'; @@ -40,7 +41,7 @@ const BASE_URL = 'block/rbd'; { provide: URLBuilderService, useValue: new URLBuilderService(BASE_URL) } ] }) -export class RbdListComponent implements OnInit { +export class RbdListComponent extends ListWithDetails implements OnInit { @ViewChild(TableComponent, { static: true }) table: TableComponent; @ViewChild('usageTpl', { static: false }) @@ -108,6 +109,7 @@ export class RbdListComponent implements OnInit { private urlBuilder: URLBuilderService, public actionLabels: ActionLabelsI18n ) { + super(); this.permission = this.authStorageService.getPermissions().rbdImage; const getImageUri = () => this.selection.first() && diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-list/cephfs-list.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-list/cephfs-list.component.html index f5a8ac952b1d..05960e87fa19 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-list/cephfs-list.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-list/cephfs-list.component.html @@ -5,8 +5,10 @@ identifier="id" forceIdentifier="true" selectionType="single" + [hasDetails]="true" + (setExpandedRow)="setExpandedRow($event)" (updateSelection)="updateSelection($event)"> + [selection]="expandedRow"> diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-list/cephfs-list.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-list/cephfs-list.component.ts index 04657592e71c..9b4ece957fda 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-list/cephfs-list.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-list/cephfs-list.component.ts @@ -3,6 +3,7 @@ import { Component, OnInit } from '@angular/core'; import { I18n } from '@ngx-translate/i18n-polyfill'; import { CephfsService } from '../../../shared/api/cephfs.service'; +import { ListWithDetails } from '../../../shared/classes/list-with-details.class'; import { CellTemplate } from '../../../shared/enum/cell-template.enum'; import { CdTableColumn } from '../../../shared/models/cd-table-column'; import { CdTableFetchDataContext } from '../../../shared/models/cd-table-fetch-data-context'; @@ -14,7 +15,7 @@ import { CdDatePipe } from '../../../shared/pipes/cd-date.pipe'; templateUrl: './cephfs-list.component.html', styleUrls: ['./cephfs-list.component.scss'] }) -export class CephfsListComponent implements OnInit { +export class CephfsListComponent extends ListWithDetails implements OnInit { columns: CdTableColumn[]; filesystems: any = []; selection = new CdTableSelection(); @@ -23,7 +24,9 @@ export class CephfsListComponent implements OnInit { private cephfsService: CephfsService, private cdDatePipe: CdDatePipe, private i18n: I18n - ) {} + ) { + super(); + } ngOnInit() { this.columns = [ diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-tabs/cephfs-tabs.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-tabs/cephfs-tabs.component.html index bef5caf7ee9d..cd3800af5e16 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-tabs/cephfs-tabs.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-tabs/cephfs-tabs.component.html @@ -1,4 +1,4 @@ - + diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-tabs/cephfs-tabs.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-tabs/cephfs-tabs.component.spec.ts index 93853889a674..288863374673 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-tabs/cephfs-tabs.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-tabs/cephfs-tabs.component.spec.ts @@ -11,7 +11,6 @@ import { of } from 'rxjs'; import { configureTestBed, i18nProviders } from '../../../../testing/unit-test-helper'; import { CephfsService } from '../../../shared/api/cephfs.service'; import { ViewCacheStatus } from '../../../shared/enum/view-cache-status.enum'; -import { CdTableSelection } from '../../../shared/models/cd-table-selection'; import { SharedModule } from '../../../shared/shared.module'; import { CephfsClientsComponent } from '../cephfs-clients/cephfs-clients.component'; import { CephfsDetailComponent } from '../cephfs-detail/cephfs-detail.component'; @@ -49,24 +48,22 @@ describe('CephfsTabsComponent', () => { }; }; - const setSelection = (selection: object[]) => { - component.selection.selected = selection; + const setSelection = (selection: any) => { + component.selection = selection; component.ngOnChanges(); }; const selectFs = (id: number, name: string) => { - setSelection([ - { - id, - mdsmap: { - info: { - something: { - name - } + setSelection({ + id, + mdsmap: { + info: { + something: { + name } } } - ]); + }); }; const updateData = () => { @@ -101,7 +98,7 @@ describe('CephfsTabsComponent', () => { beforeEach(() => { fixture = TestBed.createComponent(CephfsTabsComponent); component = fixture.componentInstance; - component.selection = new CdTableSelection(); + component.selection = undefined; data = { standbys: 'b', pools: [{}, {}], @@ -126,14 +123,12 @@ describe('CephfsTabsComponent', () => { }); it('should resist invalid mds info', () => { - setSelection([ - { - id: 3, - mdsmap: { - info: {} - } + setSelection({ + id: 3, + mdsmap: { + info: {} } - ]); + }); expect(component.grafanaId).toBe(undefined); }); @@ -212,7 +207,7 @@ describe('CephfsTabsComponent', () => { }); it('should should unsubscribe on deselect', () => { - setSelection([]); + setSelection(undefined); expect(old.unsubscribed).toBe(true); expect(getReload()).toBe(undefined); // Cleared timer subscription }); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-tabs/cephfs-tabs.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-tabs/cephfs-tabs.component.ts index cd5e28a84b23..454922c00a0e 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-tabs/cephfs-tabs.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-tabs/cephfs-tabs.component.ts @@ -5,7 +5,6 @@ import { Subscription, timer } from 'rxjs'; import { CephfsService } from '../../../shared/api/cephfs.service'; import { ViewCacheStatus } from '../../../shared/enum/view-cache-status.enum'; -import { CdTableSelection } from '../../../shared/models/cd-table-selection'; import { Permission } from '../../../shared/models/permissions'; import { AuthStorageService } from '../../../shared/services/auth-storage.service'; @@ -16,8 +15,7 @@ import { AuthStorageService } from '../../../shared/services/auth-storage.servic }) export class CephfsTabsComponent implements OnChanges, OnDestroy { @Input() - selection: CdTableSelection; - selectedItem: any; + selection: any; // Grafana tab grafanaId: any; @@ -54,13 +52,12 @@ export class CephfsTabsComponent implements OnChanges, OnDestroy { } ngOnChanges() { - this.selectedItem = this.selection.first(); - if (!this.selectedItem) { + if (!this.selection) { this.unsubscribeInterval(); return; } - if (this.selectedItem.id !== this.id) { - this.setupSelected(this.selectedItem.id, this.selectedItem.mdsmap.info); + if (this.selection.id !== this.id) { + this.setupSelected(this.selection.id, this.selection.mdsmap.info); } } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/configuration/configuration-details/configuration-details.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/configuration/configuration-details/configuration-details.component.html index 1ceac7b25da4..e690b4a91c10 100755 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/configuration/configuration-details/configuration-details.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/configuration/configuration-details/configuration-details.component.html @@ -1,4 +1,4 @@ - + @@ -6,23 +6,23 @@ - + - + - + @@ -30,33 +30,33 @@ - + - + - + - + - + @@ -75,32 +75,32 @@ - + - + - + - + - + - +
Name{{ selectedItem.name }}{{ selection.name }}
Description{{ selectedItem.desc }}{{ selection.desc }}
Long description{{ selectedItem.long_desc }}{{ selection.long_desc }}
Current values - + {{ conf.section }}: {{ conf.value }}{{ !isLast ? "," : "" }}
Default{{ selectedItem.default }}{{ selection.default }}
Daemon default{{ selectedItem.daemon_default }}{{ selection.daemon_default }}
Type{{ selectedItem.type }}{{ selection.type }}
Min{{ selectedItem.min }}{{ selection.min }}
Max{{ selectedItem.max }}{{ selection.max }}
Flags - + {{ flag | uppercase }} @@ -67,7 +67,7 @@ Services - + {{ service }}
Source{{ selectedItem.source }}{{ selection.source }}
Level{{ selectedItem.level }}{{ selection.level }}
Can be updated at runtime (editable){{ selectedItem.can_update_at_runtime | booleanText }}{{ selection.can_update_at_runtime | booleanText }}
Tags{{ selectedItem.tags }}{{ selection.tags }}
Enum values{{ selectedItem.enum_values }}{{ selection.enum_values }}
See also{{ selectedItem.see_also }}{{ selection.see_also }}
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/configuration/configuration-details/configuration-details.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/configuration/configuration-details/configuration-details.component.ts index 258763696674..d80544bd02c7 100755 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/configuration/configuration-details/configuration-details.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/configuration/configuration-details/configuration-details.component.ts @@ -3,8 +3,6 @@ import { Component, Input, OnChanges } from '@angular/core'; import { I18n } from '@ngx-translate/i18n-polyfill'; import * as _ from 'lodash'; -import { CdTableSelection } from '../../../../shared/models/cd-table-selection'; - @Component({ selector: 'cd-configuration-details', templateUrl: './configuration-details.component.html', @@ -12,8 +10,7 @@ import { CdTableSelection } from '../../../../shared/models/cd-table-selection'; }) export class ConfigurationDetailsComponent implements OnChanges { @Input() - selection: CdTableSelection; - selectedItem: any; + selection: any; flags = { runtime: this.i18n('The value can be updated at runtime.'), no_mon_update: this.i18n(`Daemons/clients do not pull this value from the @@ -28,9 +25,8 @@ export class ConfigurationDetailsComponent implements OnChanges { constructor(private i18n: I18n) {} ngOnChanges() { - if (this.selection.hasSelection) { - this.selectedItem = this.selection.first(); - this.selectedItem.services = _.split(this.selectedItem.services, ','); + if (this.selection) { + this.selection.services = _.split(this.selection.services, ','); } } } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/configuration/configuration.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/configuration/configuration.component.html index f4b6401a2466..a1eb64963395 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/configuration/configuration.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/configuration/configuration.component.html @@ -3,6 +3,8 @@ [columns]="columns" [extraFilterableColumns]="filters" selectionType="single" + [hasDetails]="true" + (setExpandedRow)="setExpandedRow($event)" (updateSelection)="updateSelection($event)"> + [selection]="expandedRow"> diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/configuration/configuration.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/configuration/configuration.component.ts index f1dcdc4fa1c8..0c6863b9996e 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/configuration/configuration.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/configuration/configuration.component.ts @@ -3,6 +3,7 @@ import { Component, OnInit, TemplateRef, ViewChild } from '@angular/core'; import { I18n } from '@ngx-translate/i18n-polyfill'; import { ConfigurationService } from '../../../shared/api/configuration.service'; +import { ListWithDetails } from '../../../shared/classes/list-with-details.class'; import { ActionLabelsI18n } from '../../../shared/constants/app.constants'; import { CellTemplate } from '../../../shared/enum/cell-template.enum'; import { Icons } from '../../../shared/enum/icons.enum'; @@ -18,7 +19,7 @@ import { AuthStorageService } from '../../../shared/services/auth-storage.servic templateUrl: './configuration.component.html', styleUrls: ['./configuration.component.scss'] }) -export class ConfigurationComponent implements OnInit { +export class ConfigurationComponent extends ListWithDetails implements OnInit { permission: Permission; tableActions: CdTableAction[]; data: any[] = []; @@ -91,6 +92,7 @@ export class ConfigurationComponent implements OnInit { private i18n: I18n, public actionLabels: ActionLabelsI18n ) { + super(); this.permission = this.authStorageService.getPermissions().configOpt; const getConfigOptUri = () => this.selection.first() && `${encodeURIComponent(this.selection.first().name)}`; diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/hosts/host-details/host-details.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/hosts/host-details/host-details.component.html index a77f3bcd2226..ef072c8af680 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/hosts/host-details/host-details.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/hosts/host-details/host-details.component.html @@ -1,7 +1,7 @@ - + - + { beforeEach(() => { fixture = TestBed.createComponent(HostDetailsComponent); component = fixture.componentInstance; - component.selection = new CdTableSelection(); + component.selection = undefined; component.permissions = new Permissions({ hosts: ['read'], grafana: ['read'] @@ -55,7 +54,7 @@ describe('HostDetailsComponent', () => { describe('Host details tabset', () => { beforeEach(() => { - component.selection.selected = [{ hostname: 'localhost' }]; + component.selection = { hostname: 'localhost' }; fixture.detectChanges(); }); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/hosts/host-details/host-details.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/hosts/host-details/host-details.component.ts index 469c4c3ab7ed..f98fa9a093ab 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/hosts/host-details/host-details.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/hosts/host-details/host-details.component.ts @@ -2,7 +2,6 @@ import { Component, Input, ViewChild } from '@angular/core'; import { TabsetComponent } from 'ngx-bootstrap/tabs'; -import { CdTableSelection } from '../../../../shared/models/cd-table-selection'; import { Permissions } from '../../../../shared/models/permissions'; @Component({ @@ -15,13 +14,13 @@ export class HostDetailsComponent { permissions: Permissions; @Input() - selection: CdTableSelection; + selection: any; @ViewChild(TabsetComponent, { static: false }) tabsetChild: TabsetComponent; get selectedHostname(): string { - return this.selection.hasSelection ? this.selection.first()['hostname'] : null; + return this.selection !== undefined ? this.selection['hostname'] : null; } constructor() {} diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/hosts/hosts.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/hosts/hosts.component.html index ab63cc90b513..fa67d8c59dec 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/hosts/hosts.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/hosts/hosts.component.html @@ -6,6 +6,8 @@ columnMode="flex" (fetchData)="getHosts($event)" selectionType="single" + [hasDetails]="true" + (setExpandedRow)="setExpandedRow($event)" (updateSelection)="updateSelection($event)">
+ [selection]="expandedRow"> diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/hosts/hosts.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/hosts/hosts.component.ts index d6812be0d69d..de4a7069faba 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/hosts/hosts.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/hosts/hosts.component.ts @@ -5,6 +5,7 @@ import { I18n } from '@ngx-translate/i18n-polyfill'; import { BsModalRef, BsModalService } from 'ngx-bootstrap/modal'; import { HostService } from '../../../shared/api/host.service'; +import { ListWithDetails } from '../../../shared/classes/list-with-details.class'; import { CriticalConfirmationModalComponent } from '../../../shared/components/critical-confirmation-modal/critical-confirmation-modal.component'; import { ActionLabelsI18n } from '../../../shared/constants/app.constants'; import { Icons } from '../../../shared/enum/icons.enum'; @@ -28,7 +29,7 @@ const BASE_URL = 'hosts'; styleUrls: ['./hosts.component.scss'], providers: [{ provide: URLBuilderService, useValue: new URLBuilderService(BASE_URL) }] }) -export class HostsComponent implements OnInit { +export class HostsComponent extends ListWithDetails implements OnInit { permissions: Permissions; columns: Array = []; hosts: Array = []; @@ -53,6 +54,7 @@ export class HostsComponent implements OnInit { private router: Router, private depCheckerService: DepCheckerService ) { + super(); this.permissions = this.authStorageService.getPermissions(); this.tableActions = [ { diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/mgr-modules/mgr-module-details/mgr-module-details.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/mgr-modules/mgr-module-details/mgr-module-details.component.html index 7913fd407451..6ed9b65da399 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/mgr-modules/mgr-module-details/mgr-module-details.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/mgr-modules/mgr-module-details/mgr-module-details.component.html @@ -1,4 +1,4 @@ - + diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/mgr-modules/mgr-module-details/mgr-module-details.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/mgr-modules/mgr-module-details/mgr-module-details.component.spec.ts index 236683404554..1e0575e71a69 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/mgr-modules/mgr-module-details/mgr-module-details.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/mgr-modules/mgr-module-details/mgr-module-details.component.spec.ts @@ -4,7 +4,6 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { TabsModule } from 'ngx-bootstrap/tabs'; import { configureTestBed, i18nProviders } from '../../../../../testing/unit-test-helper'; -import { CdTableSelection } from '../../../../shared/models/cd-table-selection'; import { SharedModule } from '../../../../shared/shared.module'; import { MgrModuleDetailsComponent } from './mgr-module-details.component'; @@ -21,7 +20,7 @@ describe('MgrModuleDetailsComponent', () => { beforeEach(() => { fixture = TestBed.createComponent(MgrModuleDetailsComponent); component = fixture.componentInstance; - component.selection = new CdTableSelection(); + component.selection = undefined; fixture.detectChanges(); }); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/mgr-modules/mgr-module-details/mgr-module-details.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/mgr-modules/mgr-module-details/mgr-module-details.component.ts index dd166779d8af..dbe2066e4a37 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/mgr-modules/mgr-module-details/mgr-module-details.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/mgr-modules/mgr-module-details/mgr-module-details.component.ts @@ -1,7 +1,6 @@ import { Component, Input, OnChanges } from '@angular/core'; import { MgrModuleService } from '../../../../shared/api/mgr-module.service'; -import { CdTableSelection } from '../../../../shared/models/cd-table-selection'; @Component({ selector: 'cd-mgr-module-details', @@ -12,14 +11,13 @@ export class MgrModuleDetailsComponent implements OnChanges { module_config: any; @Input() - selection: CdTableSelection; + selection: any; constructor(private mgrModuleService: MgrModuleService) {} ngOnChanges() { - if (this.selection.hasSelection) { - const selectedItem = this.selection.first(); - this.mgrModuleService.getConfig(selectedItem.name).subscribe((resp: any) => { + if (this.selection) { + this.mgrModuleService.getConfig(this.selection.name).subscribe((resp: any) => { this.module_config = resp; }); } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/mgr-modules/mgr-module-list/mgr-module-list.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/mgr-modules/mgr-module-list/mgr-module-list.component.html index 967ae6612f7e..29b287de8bfe 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/mgr-modules/mgr-module-list/mgr-module-list.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/mgr-modules/mgr-module-list/mgr-module-list.component.html @@ -4,6 +4,8 @@ [columns]="columns" columnMode="flex" selectionType="single" + [hasDetails]="true" + (setExpandedRow)="setExpandedRow($event)" (updateSelection)="updateSelection($event)" identifier="module" (fetchData)="getModuleList($event)"> @@ -13,6 +15,6 @@ [tableActions]="tableActions"> + [selection]="expandedRow"> diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/mgr-modules/mgr-module-list/mgr-module-list.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/mgr-modules/mgr-module-list/mgr-module-list.component.ts index 5d93b6c17899..75042749a92c 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/mgr-modules/mgr-module-list/mgr-module-list.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/mgr-modules/mgr-module-list/mgr-module-list.component.ts @@ -5,6 +5,7 @@ import { BlockUI, NgBlockUI } from 'ng-block-ui'; import { timer as observableTimer } from 'rxjs'; import { MgrModuleService } from '../../../../shared/api/mgr-module.service'; +import { ListWithDetails } from '../../../../shared/classes/list-with-details.class'; import { TableComponent } from '../../../../shared/datatable/table/table.component'; import { CellTemplate } from '../../../../shared/enum/cell-template.enum'; import { Icons } from '../../../../shared/enum/icons.enum'; @@ -21,7 +22,7 @@ import { NotificationService } from '../../../../shared/services/notification.se templateUrl: './mgr-module-list.component.html', styleUrls: ['./mgr-module-list.component.scss'] }) -export class MgrModuleListComponent { +export class MgrModuleListComponent extends ListWithDetails { @ViewChild(TableComponent, { static: true }) table: TableComponent; @BlockUI() @@ -39,6 +40,7 @@ export class MgrModuleListComponent { private notificationService: NotificationService, private i18n: I18n ) { + super(); this.permission = this.authStorageService.getPermissions().configOpt; this.columns = [ { diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-details/osd-details.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-details/osd-details.component.html index 67ca9683be9e..3430d00b48af 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-details/osd-details.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-details/osd-details.component.html @@ -1,4 +1,4 @@ - diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-details/osd-details.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-details/osd-details.component.spec.ts index 16b7f20e331a..0a9bc9d49a0e 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-details/osd-details.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-details/osd-details.component.spec.ts @@ -7,7 +7,6 @@ import { of } from 'rxjs'; import { configureTestBed, i18nProviders } from '../../../../../testing/unit-test-helper'; import { OsdService } from '../../../../shared/api/osd.service'; -import { CdTableSelection } from '../../../../shared/models/cd-table-selection'; import { SharedModule } from '../../../../shared/shared.module'; import { TablePerformanceCounterComponent } from '../../../performance-counter/table-performance-counter/table-performance-counter.component'; import { DeviceListComponent } from '../../../shared/device-list/device-list.component'; @@ -38,7 +37,7 @@ describe('OsdDetailsComponent', () => { fixture = TestBed.createComponent(OsdDetailsComponent); component = fixture.componentInstance; - component.selection = new CdTableSelection(); + component.selection = undefined; debugElement = fixture.debugElement; osdService = debugElement.injector.get(OsdService); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-details/osd-details.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-details/osd-details.component.ts index 87c4d1fa9a60..9cac703d2622 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-details/osd-details.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-details/osd-details.component.ts @@ -3,7 +3,6 @@ import { Component, Input, OnChanges } from '@angular/core'; import * as _ from 'lodash'; import { OsdService } from '../../../../shared/api/osd.service'; -import { CdTableSelection } from '../../../../shared/models/cd-table-selection'; import { Permission } from '../../../../shared/models/permissions'; import { AuthStorageService } from '../../../../shared/services/auth-storage.service'; @@ -14,7 +13,7 @@ import { AuthStorageService } from '../../../../shared/services/auth-storage.ser }) export class OsdDetailsComponent implements OnChanges { @Input() - selection: CdTableSelection; + selection: any; osd: { id?: number; @@ -33,8 +32,8 @@ export class OsdDetailsComponent implements OnChanges { this.osd = { loaded: false }; - if (this.selection.hasSelection) { - this.osd = this.selection.first(); + if (this.selection) { + this.osd = this.selection; this.refresh(); } } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-list/osd-list.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-list/osd-list.component.html index f939dc723175..196a07e2ffb1 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-list/osd-list.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-list/osd-list.component.html @@ -2,10 +2,13 @@ - @@ -27,7 +30,7 @@ + [selection]="expandedRow"> diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-list/osd-list.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-list/osd-list.component.spec.ts index 20d953f9d3af..22ac804fa829 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-list/osd-list.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-list/osd-list.component.spec.ts @@ -126,7 +126,7 @@ describe('OsdListComponent', () => { fixture.detectChanges(); expect( component.columns - .filter((column) => !column.checkboxable) + .filter((column) => !(column.prop === undefined)) .every((column) => Boolean(column.prop)) ).toBeTruthy(); }); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-list/osd-list.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-list/osd-list.component.ts index bd5844b0f958..76a0130749af 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-list/osd-list.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-list/osd-list.component.ts @@ -7,6 +7,7 @@ import { BsModalRef, BsModalService } from 'ngx-bootstrap/modal'; import { forkJoin as observableForkJoin, Observable } from 'rxjs'; import { OsdService } from '../../../../shared/api/osd.service'; +import { ListWithDetails } from '../../../../shared/classes/list-with-details.class'; import { ConfirmationModalComponent } from '../../../../shared/components/confirmation-modal/confirmation-modal.component'; import { CriticalConfirmationModalComponent } from '../../../../shared/components/critical-confirmation-modal/critical-confirmation-modal.component'; import { FormModalComponent } from '../../../../shared/components/form-modal/form-modal.component'; @@ -40,7 +41,7 @@ const BASE_URL = 'osd'; styleUrls: ['./osd-list.component.scss'], providers: [{ provide: URLBuilderService, useValue: new URLBuilderService(BASE_URL) }] }) -export class OsdListComponent implements OnInit { +export class OsdListComponent extends ListWithDetails implements OnInit { @ViewChild('osdUsageTpl', { static: true }) osdUsageTpl: TemplateRef; @ViewChild('markOsdConfirmationTpl', { static: true }) @@ -89,6 +90,7 @@ export class OsdListComponent implements OnInit { public actionLabels: ActionLabelsI18n, public notificationService: NotificationService ) { + super(); this.permissions = this.authStorageService.getPermissions(); this.tableActions = [ { diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/active-alert-list/active-alert-list.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/active-alert-list/active-alert-list.component.html index 64a446c87fb8..e9ad6760a897 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/active-alert-list/active-alert-list.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/active-alert-list/active-alert-list.component.html @@ -4,25 +4,24 @@ [forceIdentifier]="true" [customCss]="customCss" selectionType="single" + [hasDetails]="true" + (setExpandedRow)="setExpandedRow($event)" (updateSelection)="updateSelection($event)"> - - - - - - + + + ; columns: CdTableColumn[]; @@ -41,6 +42,7 @@ export class ActiveAlertListComponent implements OnInit { private i18n: I18n, private cdDatePipe: CdDatePipe ) { + super(); this.permission = this.authStorageService.getPermissions().prometheus; this.tableActions = [ { diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/rules-list/rules-list.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/rules-list/rules-list.component.html index ef80573a2c89..c0050ac7dae7 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/rules-list/rules-list.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/rules-list/rules-list.component.html @@ -1,12 +1,13 @@ + *ngIf="expandedRow"> - diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/rules-list/rules-list.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/rules-list/rules-list.component.ts index 41577da9d9e4..ee5f3a0e49bd 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/rules-list/rules-list.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/rules-list/rules-list.component.ts @@ -2,8 +2,8 @@ import { Component, Input, OnInit } from '@angular/core'; import { I18n } from '@ngx-translate/i18n-polyfill'; +import { ListWithDetails } from '../../../../shared/classes/list-with-details.class'; import { CdTableColumn } from '../../../../shared/models/cd-table-column'; -import { CdTableSelection } from '../../../../shared/models/cd-table-selection'; import { PrometheusRule } from '../../../../shared/models/prometheus-alerts'; import { DurationPipe } from '../../../../shared/pipes/duration.pipe'; @@ -12,11 +12,11 @@ import { DurationPipe } from '../../../../shared/pipes/duration.pipe'; templateUrl: './rules-list.component.html', styleUrls: ['./rules-list.component.scss'] }) -export class RulesListComponent implements OnInit { +export class RulesListComponent extends ListWithDetails implements OnInit { @Input() data: any; columns: CdTableColumn[]; - selectedRule: PrometheusRule; + expandedRow: PrometheusRule; /** * Hide active alerts in details of alerting rules as they are already shown @@ -25,7 +25,9 @@ export class RulesListComponent implements OnInit { */ hideKeys = ['alerts', 'type']; - constructor(private i18n: I18n) {} + constructor(private i18n: I18n) { + super(); + } ngOnInit() { this.columns = [ @@ -37,8 +39,4 @@ export class RulesListComponent implements OnInit { { prop: 'annotations.description', name: this.i18n('Description') } ]; } - - selectionUpdated(selection: CdTableSelection) { - this.selectedRule = selection.first(); - } } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/silence-list/silence-list.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/silence-list/silence-list.component.html index 40dc8ffc8e54..5a25ec686c1e 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/silence-list/silence-list.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/silence-list/silence-list.component.html @@ -4,6 +4,8 @@ [customCss]="customCss" [sorts]="sorts" selectionType="single" + [hasDetails]="true" + (setExpandedRow)="setExpandedRow($event)" (fetchData)="refresh()" (updateSelection)="updateSelection($event)"> - - - - - - + + diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/silence-list/silence-list.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/silence-list/silence-list.component.ts index 76de16a6b49a..3d27ac484d90 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/silence-list/silence-list.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/silence-list/silence-list.component.ts @@ -6,6 +6,7 @@ import { BsModalRef, BsModalService } from 'ngx-bootstrap/modal'; import { Observable, Subscriber } from 'rxjs'; import { PrometheusService } from '../../../../shared/api/prometheus.service'; +import { ListWithDetails } from '../../../../shared/classes/list-with-details.class'; import { CriticalConfirmationModalComponent } from '../../../../shared/components/critical-confirmation-modal/critical-confirmation-modal.component'; import { ActionLabelsI18n, @@ -32,7 +33,7 @@ const BASE_URL = 'monitoring/silence'; templateUrl: './silence-list.component.html', styleUrls: ['./silence-list.component.scss'] }) -export class SilenceListComponent { +export class SilenceListComponent extends ListWithDetails { silences: AlertmanagerSilence[] = []; columns: CdTableColumn[]; tableActions: CdTableAction[]; @@ -57,6 +58,7 @@ export class SilenceListComponent { private actionLabels: ActionLabelsI18n, private succeededLabels: SucceededActionLabelsI18n ) { + super(); this.permission = this.authStorageService.getPermissions().prometheus; const selectionExpired = (selection: CdTableSelection) => selection.first() && selection.first().status && selection.first().status.state === 'expired'; diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-details/nfs-details.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-details/nfs-details.component.html index b8f10391a11b..5bb97e5eb221 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-details/nfs-details.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-details/nfs-details.component.html @@ -1,4 +1,4 @@ - + diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-details/nfs-details.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-details/nfs-details.component.spec.ts index 065cb69412cf..f1f3b49b6838 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-details/nfs-details.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-details/nfs-details.component.spec.ts @@ -7,7 +7,6 @@ import * as _ from 'lodash'; import { TabsModule } from 'ngx-bootstrap/tabs'; import { configureTestBed, i18nProviders } from '../../../../testing/unit-test-helper'; -import { CdTableSelection } from '../../../shared/models/cd-table-selection'; import { SharedModule } from '../../../shared/shared.module'; import { NfsDetailsComponent } from './nfs-details.component'; @@ -27,31 +26,29 @@ describe('NfsDetailsComponent', () => { fixture = TestBed.createComponent(NfsDetailsComponent); component = fixture.componentInstance; - component.selection = new CdTableSelection(); - component.selection.selected = [ - { - export_id: 1, - path: '/qwe', - fsal: { name: 'CEPH', user_id: 'fs', fs_name: 1 }, - cluster_id: 'cluster1', - daemons: ['node1', 'node2'], - pseudo: '/qwe', - tag: 'asd', - access_type: 'RW', - squash: 'no_root_squash', - protocols: [3, 4], - transports: ['TCP', 'UDP'], - clients: [ - { - addresses: ['192.168.0.10', '192.168.1.0/8'], - access_type: 'RW', - squash: 'root_id_squash' - } - ], - id: 'cluster1:1', - state: 'LOADING' - } - ]; + component.selection = undefined; + component.selection = { + export_id: 1, + path: '/qwe', + fsal: { name: 'CEPH', user_id: 'fs', fs_name: 1 }, + cluster_id: 'cluster1', + daemons: ['node1', 'node2'], + pseudo: '/qwe', + tag: 'asd', + access_type: 'RW', + squash: 'no_root_squash', + protocols: [3, 4], + transports: ['TCP', 'UDP'], + clients: [ + { + addresses: ['192.168.0.10', '192.168.1.0/8'], + access_type: 'RW', + squash: 'root_id_squash' + } + ], + id: 'cluster1:1', + state: 'LOADING' + }; component.ngOnChanges(); fixture.detectChanges(); }); @@ -78,13 +75,13 @@ describe('NfsDetailsComponent', () => { }); it('should prepare data if RGW', () => { - const newData = _.assignIn(component.selection.first(), { + const newData = _.assignIn(component.selection, { fsal: { name: 'RGW', rgw_user_id: 'rgw_user_id' } }); - component.selection.selected = [newData]; + component.selection = newData; component.ngOnChanges(); expect(component.data).toEqual({ 'Access Type': 'RW', diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-details/nfs-details.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-details/nfs-details.component.ts index db91c7d53388..c6a3ba0eca90 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-details/nfs-details.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-details/nfs-details.component.ts @@ -1,10 +1,8 @@ import { Component, Input, OnChanges } from '@angular/core'; import { I18n } from '@ngx-translate/i18n-polyfill'; -import * as _ from 'lodash'; import { CdTableColumn } from '../../../shared/models/cd-table-column'; -import { CdTableSelection } from '../../../shared/models/cd-table-selection'; @Component({ selector: 'cd-nfs-details', @@ -13,7 +11,7 @@ import { CdTableSelection } from '../../../shared/models/cd-table-selection'; }) export class NfsDetailsComponent implements OnChanges { @Input() - selection: CdTableSelection; + selection: any; selectedItem: any; data: any; @@ -42,8 +40,8 @@ export class NfsDetailsComponent implements OnChanges { } ngOnChanges() { - if (this.selection.hasSelection) { - this.selectedItem = this.selection.first(); + if (this.selection) { + this.selectedItem = this.selection; this.clients = this.selectedItem.clients; diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-list/nfs-list.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-list/nfs-list.component.html index ef42aa2929ce..79304265e7ea 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-list/nfs-list.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-list/nfs-list.component.html @@ -5,6 +5,8 @@ identifier="id" forceIdentifier="true" selectionType="single" + [hasDetails]="true" + (setExpandedRow)="setExpandedRow($event)" (updateSelection)="updateSelection($event)">
+ [selection]="expandedRow"> diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-list/nfs-list.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-list/nfs-list.component.ts index 2a5c874051d8..7de9a5100742 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-list/nfs-list.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-list/nfs-list.component.ts @@ -6,6 +6,7 @@ import { BsModalRef, BsModalService } from 'ngx-bootstrap/modal'; import { Subscription } from 'rxjs'; import { NfsService } from '../../../shared/api/nfs.service'; +import { ListWithDetails } from '../../../shared/classes/list-with-details.class'; import { CriticalConfirmationModalComponent } from '../../../shared/components/critical-confirmation-modal/critical-confirmation-modal.component'; import { ActionLabelsI18n } from '../../../shared/constants/app.constants'; import { TableComponent } from '../../../shared/datatable/table/table.component'; @@ -28,7 +29,7 @@ import { TaskWrapperService } from '../../../shared/services/task-wrapper.servic styleUrls: ['./nfs-list.component.scss'], providers: [TaskListService] }) -export class NfsListComponent implements OnInit, OnDestroy { +export class NfsListComponent extends ListWithDetails implements OnInit, OnDestroy { @ViewChild('nfsState', { static: false }) nfsState: TemplateRef; @ViewChild('nfsFsal', { static: true }) @@ -67,6 +68,7 @@ export class NfsListComponent implements OnInit, OnDestroy { private taskWrapper: TaskWrapperService, public actionLabels: ActionLabelsI18n ) { + super(); this.permission = this.authStorageService.getPermissions().nfs; const getNfsUri = () => this.selection.first() && diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-details/pool-details.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-details/pool-details.component.html index 78d23ac28c86..a1515e02b784 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-details/pool-details.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-details/pool-details.component.html @@ -1,10 +1,10 @@ + *ngIf="selection"> @@ -12,18 +12,18 @@ *ngIf="permissions.grafana.read" heading="Performance Details"> - { beforeEach(() => { fixture = TestBed.createComponent(PoolDetailsComponent); poolDetailsComponent = fixture.componentInstance; - poolDetailsComponent.selection = new CdTableSelection(); + poolDetailsComponent.selection = undefined; poolDetailsComponent.permissions = new Permissions({ grafana: ['read'] }); @@ -44,12 +43,10 @@ describe('PoolDetailsComponent', () => { describe('Pool details tabset', () => { beforeEach(() => { - poolDetailsComponent.selection.selected = [ - { - tiers: [0], - pool: 0 - } - ]; + poolDetailsComponent.selection = { + tiers: [0], + pool: 0 + }; }); it('should recognize a tabset child', () => { @@ -67,11 +64,9 @@ describe('PoolDetailsComponent', () => { }); it('should not show "Cache Tiers Details" tab if selected pool has no "tiers"', () => { - poolDetailsComponent.selection.selected = [ - { - tiers: [] - } - ]; + poolDetailsComponent.selection = { + tiers: [] + }; fixture.detectChanges(); const tabs = poolDetailsComponent.tabsetChild.tabs; expect(tabs.length).toEqual(2); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-details/pool-details.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-details/pool-details.component.ts index 76365843e9cf..8e7c395967ef 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-details/pool-details.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-details/pool-details.component.ts @@ -6,7 +6,6 @@ import { TabsetComponent } from 'ngx-bootstrap/tabs'; import { PoolService } from '../../../shared/api/pool.service'; import { CdTableColumn } from '../../../shared/models/cd-table-column'; -import { CdTableSelection } from '../../../shared/models/cd-table-selection'; import { RbdConfigurationEntry } from '../../../shared/models/configuration'; import { Permissions } from '../../../shared/models/permissions'; @@ -19,7 +18,7 @@ export class PoolDetailsComponent implements OnChanges { cacheTierColumns: Array = []; @Input() - selection: CdTableSelection; + selection: any; @Input() permissions: Permissions; @Input() @@ -64,8 +63,8 @@ export class PoolDetailsComponent implements OnChanges { } ngOnChanges() { - if (this.selection.hasSingleSelection) { - this.poolService.getConfiguration(this.selection.first().pool_name).subscribe((poolConf) => { + if (this.selection) { + this.poolService.getConfiguration(this.selection.pool_name).subscribe((poolConf) => { this.selectedPoolConfiguration = poolConf; }); } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-list/pool-list.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-list/pool-list.component.html index b2c31564acb0..8d6205817d81 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-list/pool-list.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-list/pool-list.component.html @@ -10,6 +10,8 @@ [data]="pools" [columns]="columns" selectionType="single" + [hasDetails]="true" + (setExpandedRow)="setExpandedRow($event)" (updateSelection)="updateSelection($event)"> + [cacheTiers]="cacheTiers"> diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-list/pool-list.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-list/pool-list.component.spec.ts index e5b1791df9a6..73d0925bc58e 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-list/pool-list.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-list/pool-list.component.spec.ts @@ -74,7 +74,11 @@ describe('PoolListComponent', () => { }); it('should have columns that are sortable', () => { - expect(component.columns.every((column) => Boolean(column.prop))).toBeTruthy(); + expect( + component.columns + .filter((column) => !(column.prop === undefined)) + .every((column) => Boolean(column.prop)) + ).toBeTruthy(); }); describe('monAllowPoolDelete', () => { @@ -429,7 +433,7 @@ describe('PoolListComponent', () => { describe('getSelectionTiers', () => { const setSelectionTiers = (tiers: number[]) => { - component.selection.selected = [{ tiers }]; + component.expandedRow = { tiers }; component.getSelectionTiers(); }; @@ -439,31 +443,31 @@ describe('PoolListComponent', () => { it('should select multiple existing cache tiers', () => { setSelectionTiers([0, 1, 2]); - expect(component.selectionCacheTiers).toEqual(getPoolList()); + expect(component.cacheTiers).toEqual(getPoolList()); }); it('should select correct existing cache tier', () => { setSelectionTiers([0]); - expect(component.selectionCacheTiers).toEqual([createPool('a', 0)]); + expect(component.cacheTiers).toEqual([createPool('a', 0)]); }); it('should not select cache tier if id is invalid', () => { setSelectionTiers([-1]); - expect(component.selectionCacheTiers).toEqual([]); + expect(component.cacheTiers).toEqual([]); }); it('should not select cache tier if empty', () => { setSelectionTiers([]); - expect(component.selectionCacheTiers).toEqual([]); + expect(component.cacheTiers).toEqual([]); }); it('should be able to selected one pool with multiple tiers, than with a single tier, than with no tiers', () => { setSelectionTiers([0, 1, 2]); - expect(component.selectionCacheTiers).toEqual(getPoolList()); + expect(component.cacheTiers).toEqual(getPoolList()); setSelectionTiers([0]); - expect(component.selectionCacheTiers).toEqual([createPool('a', 0)]); + expect(component.cacheTiers).toEqual([createPool('a', 0)]); setSelectionTiers([]); - expect(component.selectionCacheTiers).toEqual([]); + expect(component.cacheTiers).toEqual([]); }); }); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-list/pool-list.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-list/pool-list.component.ts index 49449f5badd9..c0cd8bb5f301 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-list/pool-list.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-list/pool-list.component.ts @@ -6,6 +6,7 @@ import { BsModalRef, BsModalService } from 'ngx-bootstrap/modal'; import { ConfigurationService } from '../../../shared/api/configuration.service'; import { PoolService } from '../../../shared/api/pool.service'; +import { ListWithDetails } from '../../../shared/classes/list-with-details.class'; import { CriticalConfirmationModalComponent } from '../../../shared/components/critical-confirmation-modal/critical-confirmation-modal.component'; import { ActionLabelsI18n, URLVerbs } from '../../../shared/constants/app.constants'; import { TableComponent } from '../../../shared/datatable/table/table.component'; @@ -38,7 +39,7 @@ const BASE_URL = 'pool'; ], styleUrls: ['./pool-list.component.scss'] }) -export class PoolListComponent implements OnInit { +export class PoolListComponent extends ListWithDetails implements OnInit { @ViewChild(TableComponent, { static: true }) table: TableComponent; @ViewChild('poolUsageTpl', { static: true }) @@ -55,7 +56,7 @@ export class PoolListComponent implements OnInit { permissions: Permissions; tableActions: CdTableAction[]; viewCacheStatusList: any[]; - selectionCacheTiers: any[] = []; + cacheTiers: any[] = []; monAllowPoolDelete = false; constructor( @@ -71,6 +72,7 @@ export class PoolListComponent implements OnInit { private configurationService: ConfigurationService, public actionLabels: ActionLabelsI18n ) { + super(); this.permissions = this.authStorageService.getPermissions(); this.tableActions = [ { @@ -214,7 +216,6 @@ export class PoolListComponent implements OnInit { updateSelection(selection: CdTableSelection) { this.selection = selection; - this.getSelectionTiers(); } deletePoolModal() { @@ -279,10 +280,10 @@ export class PoolListComponent implements OnInit { } getSelectionTiers() { - const cacheTierIds = this.selection.hasSingleSelection - ? this.selection.first()['tiers'] || [] - : []; - this.selectionCacheTiers = this.pools.filter((pool) => cacheTierIds.includes(pool.pool)); + if (typeof this.expandedRow !== 'undefined') { + const cacheTierIds = this.expandedRow['tiers']; + this.cacheTiers = this.pools.filter((pool) => cacheTierIds.includes(pool.pool)); + } } getDisableDesc(): string | undefined { @@ -294,4 +295,9 @@ export class PoolListComponent implements OnInit { return undefined; } + + setExpandedRow(expandedRow: any) { + super.setExpandedRow(expandedRow); + this.getSelectionTiers(); + } } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-details/rgw-bucket-details.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-details/rgw-bucket-details.component.html index 3d0cb7b48290..761c41b32b0d 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-details/rgw-bucket-details.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-details/rgw-bucket-details.component.html @@ -1,103 +1,103 @@ - + -
+
- + - + - + - + - + - + - + - + - + - + - + - + - +
Name{{ bucket.bid }}{{ selection.bid }}
ID{{ bucket.id }}{{ selection.id }}
Owner{{ bucket.owner }}{{ selection.owner }}
Index type{{ bucket.index_type }}{{ selection.index_type }}
Placement rule{{ bucket.placement_rule }}{{ selection.placement_rule }}
Marker{{ bucket.marker }}{{ selection.marker }}
Maximum marker{{ bucket.max_marker }}{{ selection.max_marker }}
Version{{ bucket.ver }}{{ selection.ver }}
Master version{{ bucket.master_ver }}{{ selection.master_ver }}
Modification time{{ bucket.mtime | cdDate }}{{ selection.mtime | cdDate }}
Zonegroup{{ bucket.zonegroup }}{{ selection.zonegroup }}
Versioning{{ bucket.versioning }}{{ selection.versioning }}
MFA Delete{{ bucket.mfa_delete }}{{ selection.mfa_delete }}
-
+
Bucket quota - + - - - - @@ -111,23 +111,23 @@ - + - + - + - + - + diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-details/rgw-bucket-details.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-details/rgw-bucket-details.component.ts index 0b4fa50626c8..48f255400382 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-details/rgw-bucket-details.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-details/rgw-bucket-details.component.ts @@ -1,23 +1,13 @@ -import { Component, Input, OnChanges } from '@angular/core'; - -import { CdTableSelection } from '../../../shared/models/cd-table-selection'; +import { Component, Input } from '@angular/core'; @Component({ selector: 'cd-rgw-bucket-details', templateUrl: './rgw-bucket-details.component.html', styleUrls: ['./rgw-bucket-details.component.scss'] }) -export class RgwBucketDetailsComponent implements OnChanges { - bucket: any; - +export class RgwBucketDetailsComponent { @Input() - selection: CdTableSelection; + selection: any; constructor() {} - - ngOnChanges() { - if (this.selection.hasSelection) { - this.bucket = this.selection.first(); - } - } } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-list/rgw-bucket-list.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-list/rgw-bucket-list.component.html index 37797e96e003..00ea3a036ecc 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-list/rgw-bucket-list.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-list/rgw-bucket-list.component.html @@ -4,6 +4,8 @@ [columns]="columns" columnMode="flex" selectionType="multiClick" + [hasDetails]="true" + (setExpandedRow)="setExpandedRow($event)" (updateSelection)="updateSelection($event)" identifier="bid" (fetchData)="getBucketList($event)"> @@ -13,6 +15,6 @@ [tableActions]="tableActions"> + [selection]="expandedRow"> diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-list/rgw-bucket-list.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-list/rgw-bucket-list.component.ts index 24050ebb74aa..bf28689d35d9 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-list/rgw-bucket-list.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-list/rgw-bucket-list.component.ts @@ -5,6 +5,7 @@ import { BsModalService } from 'ngx-bootstrap/modal'; import { forkJoin as observableForkJoin, Observable, Subscriber } from 'rxjs'; import { RgwBucketService } from '../../../shared/api/rgw-bucket.service'; +import { ListWithDetails } from '../../../shared/classes/list-with-details.class'; import { CriticalConfirmationModalComponent } from '../../../shared/components/critical-confirmation-modal/critical-confirmation-modal.component'; import { ActionLabelsI18n } from '../../../shared/constants/app.constants'; import { TableComponent } from '../../../shared/datatable/table/table.component'; @@ -25,7 +26,7 @@ const BASE_URL = 'rgw/bucket'; styleUrls: ['./rgw-bucket-list.component.scss'], providers: [{ provide: URLBuilderService, useValue: new URLBuilderService(BASE_URL) }] }) -export class RgwBucketListComponent { +export class RgwBucketListComponent extends ListWithDetails { @ViewChild(TableComponent, { static: true }) table: TableComponent; @@ -43,6 +44,7 @@ export class RgwBucketListComponent { private urlBuilder: URLBuilderService, public actionLabels: ActionLabelsI18n ) { + super(); this.permission = this.authStorageService.getPermissions().rgw; this.columns = [ { diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-daemon-details/rgw-daemon-details.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-daemon-details/rgw-daemon-details.component.html index d46bbb8c2cce..b7cea04f21cd 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-daemon-details/rgw-daemon-details.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-daemon-details/rgw-daemon-details.component.html @@ -1,4 +1,4 @@ - + - diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-daemon-details/rgw-daemon-details.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-daemon-details/rgw-daemon-details.component.spec.ts index 96b6d81a8f6f..f185f2133d96 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-daemon-details/rgw-daemon-details.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-daemon-details/rgw-daemon-details.component.spec.ts @@ -4,7 +4,6 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { TabsModule } from 'ngx-bootstrap/tabs'; import { configureTestBed } from '../../../../testing/unit-test-helper'; -import { CdTableSelection } from '../../../shared/models/cd-table-selection'; import { SharedModule } from '../../../shared/shared.module'; import { PerformanceCounterModule } from '../../performance-counter/performance-counter.module'; import { RgwDaemonDetailsComponent } from './rgw-daemon-details.component'; @@ -21,7 +20,7 @@ describe('RgwDaemonDetailsComponent', () => { beforeEach(() => { fixture = TestBed.createComponent(RgwDaemonDetailsComponent); component = fixture.componentInstance; - component.selection = new CdTableSelection(); + component.selection = undefined; fixture.detectChanges(); }); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-daemon-details/rgw-daemon-details.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-daemon-details/rgw-daemon-details.component.ts index c78754890dbc..e338572aae37 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-daemon-details/rgw-daemon-details.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-daemon-details/rgw-daemon-details.component.ts @@ -3,7 +3,6 @@ import { Component, Input, OnChanges } from '@angular/core'; import * as _ from 'lodash'; import { RgwDaemonService } from '../../../shared/api/rgw-daemon.service'; -import { CdTableSelection } from '../../../shared/models/cd-table-selection'; import { Permission } from '../../../shared/models/permissions'; import { AuthStorageService } from '../../../shared/services/auth-storage.service'; @@ -18,7 +17,7 @@ export class RgwDaemonDetailsComponent implements OnChanges { grafanaPermission: Permission; @Input() - selection: CdTableSelection; + selection: any; constructor( private rgwDaemonService: RgwDaemonService, @@ -29,8 +28,8 @@ export class RgwDaemonDetailsComponent implements OnChanges { ngOnChanges() { // Get the service id of the first selected row. - if (this.selection.hasSelection) { - this.serviceId = this.selection.first().id; + if (this.selection) { + this.serviceId = this.selection.id; } } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-daemon-list/rgw-daemon-list.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-daemon-list/rgw-daemon-list.component.html index cb485c6a648a..51c0fd95a5ac 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-daemon-list/rgw-daemon-list.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-daemon-list/rgw-daemon-list.component.html @@ -4,11 +4,11 @@ + [selection]="expandedRow"> diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-daemon-list/rgw-daemon-list.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-daemon-list/rgw-daemon-list.component.ts index 7f6896155278..9f22e860b4de 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-daemon-list/rgw-daemon-list.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-daemon-list/rgw-daemon-list.component.ts @@ -3,9 +3,9 @@ import { Component } from '@angular/core'; import { I18n } from '@ngx-translate/i18n-polyfill'; import { RgwDaemonService } from '../../../shared/api/rgw-daemon.service'; +import { ListWithDetails } from '../../../shared/classes/list-with-details.class'; import { CdTableColumn } from '../../../shared/models/cd-table-column'; import { CdTableFetchDataContext } from '../../../shared/models/cd-table-fetch-data-context'; -import { CdTableSelection } from '../../../shared/models/cd-table-selection'; import { Permission } from '../../../shared/models/permissions'; import { CephShortVersionPipe } from '../../../shared/pipes/ceph-short-version.pipe'; import { AuthStorageService } from '../../../shared/services/auth-storage.service'; @@ -15,10 +15,9 @@ import { AuthStorageService } from '../../../shared/services/auth-storage.servic templateUrl: './rgw-daemon-list.component.html', styleUrls: ['./rgw-daemon-list.component.scss'] }) -export class RgwDaemonListComponent { +export class RgwDaemonListComponent extends ListWithDetails { columns: CdTableColumn[] = []; daemons: object[] = []; - selection: CdTableSelection = new CdTableSelection(); grafanaPermission: Permission; constructor( @@ -27,6 +26,7 @@ export class RgwDaemonListComponent { cephShortVersionPipe: CephShortVersionPipe, private i18n: I18n ) { + super(); this.grafanaPermission = this.authStorageService.getPermissions().grafana; this.columns = [ { @@ -58,8 +58,4 @@ export class RgwDaemonListComponent { } ); } - - updateSelection(selection: CdTableSelection) { - this.selection = selection; - } } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-details/rgw-user-details.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-details/rgw-user-details.component.html index 4a87754f7ecf..e4080a26362c 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-details/rgw-user-details.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-details/rgw-user-details.component.html @@ -1,4 +1,4 @@ - +
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-details/rgw-user-details.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-details/rgw-user-details.component.spec.ts index 0b9d68bfe861..1b29228c38ca 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-details/rgw-user-details.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-details/rgw-user-details.component.spec.ts @@ -6,7 +6,6 @@ import { BsModalService } from 'ngx-bootstrap/modal'; import { TabsModule } from 'ngx-bootstrap/tabs'; import { configureTestBed, i18nProviders } from '../../../../testing/unit-test-helper'; -import { CdTableSelection } from '../../../shared/models/cd-table-selection'; import { SharedModule } from '../../../shared/shared.module'; import { RgwUserS3Key } from '../models/rgw-user-s3-key'; import { RgwUserDetailsComponent } from './rgw-user-details.component'; @@ -24,7 +23,7 @@ describe('RgwUserDetailsComponent', () => { beforeEach(() => { fixture = TestBed.createComponent(RgwUserDetailsComponent); component = fixture.componentInstance; - component.selection = new CdTableSelection(); + component.selection = {}; fixture.detectChanges(); }); @@ -32,13 +31,13 @@ describe('RgwUserDetailsComponent', () => { expect(component).toBeTruthy(); const detailsTab = fixture.debugElement.nativeElement.querySelector('tab[heading="Details"]'); - expect(detailsTab).toBeFalsy(); + expect(detailsTab).toBeTruthy(); const keysTab = fixture.debugElement.nativeElement.querySelector('tab[heading="Keys"]'); expect(keysTab).toBeFalsy(); }); it('should show "Details" tab', () => { - component.selection.selected = [{ uid: 'myUsername' }]; + component.selection = { uid: 'myUsername' }; fixture.detectChanges(); const detailsTab = fixture.debugElement.nativeElement.querySelector('tab[heading="Details"]'); @@ -49,7 +48,7 @@ describe('RgwUserDetailsComponent', () => { it('should show "Keys" tab', () => { const s3Key = new RgwUserS3Key(); - component.selection.selected = [{ keys: [s3Key] }]; + component.selection = { keys: [s3Key] }; component.ngOnChanges(); fixture.detectChanges(); @@ -60,9 +59,8 @@ describe('RgwUserDetailsComponent', () => { }); it('should show correct "System" info', () => { - component.selection.selected = [ - { uid: '', email: '', system: 'true', keys: [], swift_keys: [] } - ]; + component.selection = { uid: '', email: '', system: 'true', keys: [], swift_keys: [] }; + component.ngOnChanges(); fixture.detectChanges(); @@ -72,7 +70,7 @@ describe('RgwUserDetailsComponent', () => { expect(detailsTab[6].textContent).toEqual('System'); expect(detailsTab[7].textContent).toEqual('Yes'); - component.selection.selected[0].system = 'false'; + component.selection.system = 'false'; component.ngOnChanges(); fixture.detectChanges(); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-details/rgw-user-details.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-details/rgw-user-details.component.ts index 290822c9afbe..b2c95901f4ed 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-details/rgw-user-details.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-details/rgw-user-details.component.ts @@ -25,7 +25,7 @@ export class RgwUserDetailsComponent implements OnChanges, OnInit { public secretKeyTpl: TemplateRef; @Input() - selection: CdTableSelection; + selection: any; // Details tab user: any; @@ -64,8 +64,8 @@ export class RgwUserDetailsComponent implements OnChanges, OnInit { } ngOnChanges() { - if (this.selection.hasSelection) { - this.user = this.selection.first(); + if (this.selection) { + this.user = this.selection; // Sort subusers and capabilities. this.user.subusers = _.sortBy(this.user.subusers, 'id'); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-list/rgw-user-list.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-list/rgw-user-list.component.html index a154f1182e34..09aa5aac489b 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-list/rgw-user-list.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-list/rgw-user-list.component.html @@ -4,6 +4,8 @@ [columns]="columns" columnMode="flex" selectionType="multiClick" + [hasDetails]="true" + (setExpandedRow)="setExpandedRow($event)" (updateSelection)="updateSelection($event)" identifier="uid" (fetchData)="getUserList($event)"> @@ -13,6 +15,6 @@ [tableActions]="tableActions"> + [selection]="expandedRow"> diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-list/rgw-user-list.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-list/rgw-user-list.component.ts index 46c402019635..6aae6d17ed65 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-list/rgw-user-list.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-list/rgw-user-list.component.ts @@ -5,6 +5,7 @@ import { BsModalService } from 'ngx-bootstrap/modal'; import { forkJoin as observableForkJoin, Observable, Subscriber } from 'rxjs'; import { RgwUserService } from '../../../shared/api/rgw-user.service'; +import { ListWithDetails } from '../../../shared/classes/list-with-details.class'; import { CriticalConfirmationModalComponent } from '../../../shared/components/critical-confirmation-modal/critical-confirmation-modal.component'; import { ActionLabelsI18n } from '../../../shared/constants/app.constants'; import { TableComponent } from '../../../shared/datatable/table/table.component'; @@ -26,7 +27,7 @@ const BASE_URL = 'rgw/user'; styleUrls: ['./rgw-user-list.component.scss'], providers: [{ provide: URLBuilderService, useValue: new URLBuilderService(BASE_URL) }] }) -export class RgwUserListComponent { +export class RgwUserListComponent extends ListWithDetails { @ViewChild(TableComponent, { static: true }) table: TableComponent; @@ -44,6 +45,7 @@ export class RgwUserListComponent { private urlBuilder: URLBuilderService, public actionLabels: ActionLabelsI18n ) { + super(); this.permission = this.authStorageService.getPermissions().rgw; this.columns = [ { diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/auth/role-details/role-details.component.html b/src/pybind/mgr/dashboard/frontend/src/app/core/auth/role-details/role-details.component.html index 9a4cb7436a77..ec054c67fbb2 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/core/auth/role-details/role-details.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/core/auth/role-details/role-details.component.html @@ -1,4 +1,4 @@ - + { it('should create scopes permissions [1/2]', () => { component.scopes = ['log', 'rgw']; - component.selection = new CdTableSelection([ - { - description: 'RGW Manager', - name: 'rgw-manager', - scopes_permissions: { - rgw: ['read', 'create', 'update', 'delete'] - }, - system: true - } - ]); + component.selection = { + description: 'RGW Manager', + name: 'rgw-manager', + scopes_permissions: { + rgw: ['read', 'create', 'update', 'delete'] + }, + system: true + }; expect(component.scopes_permissions.length).toBe(0); component.ngOnChanges(); expect(component.scopes_permissions).toEqual([ @@ -51,17 +48,15 @@ describe('RoleDetailsComponent', () => { it('should create scopes permissions [2/2]', () => { component.scopes = ['cephfs', 'log', 'rgw']; - component.selection = new CdTableSelection([ - { - description: 'Test', - name: 'test', - scopes_permissions: { - log: ['read', 'update'], - rgw: ['read', 'create', 'update'] - }, - system: false - } - ]); + component.selection = { + description: 'Test', + name: 'test', + scopes_permissions: { + log: ['read', 'update'], + rgw: ['read', 'create', 'update'] + }, + system: false + }; expect(component.scopes_permissions.length).toBe(0); component.ngOnChanges(); expect(component.scopes_permissions).toEqual([ diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/auth/role-details/role-details.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/core/auth/role-details/role-details.component.ts index 1ed0568f773b..6ee914941042 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/core/auth/role-details/role-details.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/core/auth/role-details/role-details.component.ts @@ -5,7 +5,6 @@ import * as _ from 'lodash'; import { CellTemplate } from '../../../shared/enum/cell-template.enum'; import { CdTableColumn } from '../../../shared/models/cd-table-column'; -import { CdTableSelection } from '../../../shared/models/cd-table-selection'; @Component({ selector: 'cd-role-details', @@ -14,7 +13,7 @@ import { CdTableSelection } from '../../../shared/models/cd-table-selection'; }) export class RoleDetailsComponent implements OnChanges, OnInit { @Input() - selection: CdTableSelection; + selection: any; @Input() scopes: Array; selectedItem: any; @@ -63,8 +62,8 @@ export class RoleDetailsComponent implements OnChanges, OnInit { } ngOnChanges() { - if (this.selection.hasSelection) { - this.selectedItem = this.selection.first(); + if (this.selection) { + this.selectedItem = this.selection; // Build the scopes/permissions data used by the data table. const scopes_permissions: any[] = []; _.each(this.scopes, (scope) => { diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/auth/role-list/role-list.component.html b/src/pybind/mgr/dashboard/frontend/src/app/core/auth/role-list/role-list.component.html index 75331794fc40..6b8a5d73e7b8 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/core/auth/role-list/role-list.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/core/auth/role-list/role-list.component.html @@ -5,6 +5,8 @@ [columns]="columns" identifier="name" selectionType="single" + [hasDetails]="true" + (setExpandedRow)="setExpandedRow($event)" (fetchData)="getRoles()" (updateSelection)="updateSelection($event)"> diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/auth/role-list/role-list.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/core/auth/role-list/role-list.component.ts index 8e966cbaa567..a945b79fae4d 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/core/auth/role-list/role-list.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/core/auth/role-list/role-list.component.ts @@ -6,6 +6,7 @@ import { forkJoin } from 'rxjs'; import { RoleService } from '../../../shared/api/role.service'; import { ScopeService } from '../../../shared/api/scope.service'; +import { ListWithDetails } from '../../../shared/classes/list-with-details.class'; import { CriticalConfirmationModalComponent } from '../../../shared/components/critical-confirmation-modal/critical-confirmation-modal.component'; import { FormModalComponent } from '../../../shared/components/form-modal/form-modal.component'; import { ActionLabelsI18n } from '../../../shared/constants/app.constants'; @@ -29,7 +30,7 @@ const BASE_URL = 'user-management/roles'; styleUrls: ['./role-list.component.scss'], providers: [{ provide: URLBuilderService, useValue: new URLBuilderService(BASE_URL) }] }) -export class RoleListComponent implements OnInit { +export class RoleListComponent extends ListWithDetails implements OnInit { permission: Permission; tableActions: CdTableAction[]; columns: CdTableColumn[]; @@ -50,6 +51,7 @@ export class RoleListComponent implements OnInit { private urlBuilder: URLBuilderService, public actionLabels: ActionLabelsI18n ) { + super(); this.permission = this.authStorageService.getPermissions().user; const addAction: CdTableAction = { permission: 'create', diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/classes/list-with-details.class.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/classes/list-with-details.class.ts new file mode 100644 index 000000000000..585742622417 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/classes/list-with-details.class.ts @@ -0,0 +1,7 @@ +export class ListWithDetails { + expandedRow: any; + + setExpandedRow(expandedRow: any) { + this.expandedRow = expandedRow; + } +} diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table/table.component.html b/src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table/table.component.html index 82eeef2f4df6..c3ac07f15121 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table/table.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table/table.component.html @@ -116,19 +116,20 @@
@@ -188,6 +189,18 @@ [loadingIndicator]="loadingIndicator" [rowIdentity]="rowIdentity()" [rowHeight]="'auto'"> + + + + + + + + + - - - @@ -297,3 +307,17 @@ {{ value | truncate:column?.customTemplateConfig?.length:column?.customTemplateConfig?.omission }} + + + + + diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table/table.component.scss b/src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table/table.component.scss index 9dccecba61f6..4a5f9ae8a626 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table/table.component.scss +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table/table.component.scss @@ -1,5 +1,12 @@ @import 'defaults.scss'; +@mixin row-details-icon { + font-family: 'ForkAwesome', sans-serif; + font-size: 1rem; + color: $gray-900; + line-height: 1; +} + .dataTables_wrapper { margin-bottom: 25px; .separator { @@ -23,18 +30,18 @@ .dropdown-menu { white-space: nowrap; - & > li { - cursor: pointer; - & > label { + & li { + cursor: default; + & label { width: 100%; margin-bottom: 0; - padding-left: 20px; - padding-right: 20px; + padding-left: 0; + padding-right: 0; cursor: pointer; &:hover { background-color: $color-table-dropdown-bg; } - & > input { + & input { cursor: pointer; } } @@ -226,9 +233,30 @@ } .datatable-body-cell-label { display: block; + height: 100%; } } } + .datatable-row-detail { + padding: 20px; + border-bottom: 2px solid $color-table-header-border; + } + .expand-collapse-icon { + display: block; + height: 100%; + text-align: center; + &:hover { + text-decoration: none; + } + } + .expand-collapse-icon-right:before { + @include row-details-icon; + content: '\f105'; + } + .expand-collapse-icon-down:before { + @include row-details-icon; + content: '\f107'; + } } .datatable-footer { .selected-count, diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table/table.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table/table.component.spec.ts index 06162c07d5e2..7484b227409e 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table/table.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table/table.component.spec.ts @@ -615,4 +615,72 @@ describe('TableComponent', () => { expect(component.useCustomClass('https://secure.it')).toBe('btn secure'); }); }); + + describe('test expand and collapse feature', () => { + beforeEach(() => { + spyOn(component.setExpandedRow, 'emit'); + component.table = { + rowDetail: { collapseAllRows: jest.fn(), toggleExpandRow: jest.fn() } + } as any; + + // Setup table + component.identifier = 'a'; + component.data = createFakeData(10); + + // Select item + component.expanded = _.clone(component.data[1]); + }); + + describe('update expanded on refresh', () => { + const updateExpendedOnState = (state: 'always' | 'never' | 'onChange') => { + component.updateExpandedOnRefresh = state; + component.updateExpanded(); + }; + + beforeEach(() => { + // Mock change + component.data[1].b = 'test'; + }); + + it('refreshes "always"', () => { + updateExpendedOnState('always'); + expect(component.expanded.b).toBe('test'); + expect(component.setExpandedRow.emit).toHaveBeenCalled(); + }); + + it('refreshes "onChange"', () => { + updateExpendedOnState('onChange'); + expect(component.expanded.b).toBe('test'); + expect(component.setExpandedRow.emit).toHaveBeenCalled(); + }); + + it('does not refresh "onChange" if data is equal', () => { + component.data[1].b = 10; // Reverts change + updateExpendedOnState('onChange'); + expect(component.expanded.b).toBe(10); + expect(component.setExpandedRow.emit).not.toHaveBeenCalled(); + }); + + it('"never" refreshes', () => { + updateExpendedOnState('never'); + expect(component.expanded.b).toBe(10); + expect(component.setExpandedRow.emit).not.toHaveBeenCalled(); + }); + }); + + it('should open the table details and close other expanded rows', () => { + component.toggleExpandRow(component.expanded, false); + expect(component.expanded).toEqual({ a: 1, b: 10, c: true }); + expect(component.table.rowDetail.collapseAllRows).toHaveBeenCalled(); + expect(component.setExpandedRow.emit).toHaveBeenCalledWith(component.expanded); + expect(component.table.rowDetail.toggleExpandRow).toHaveBeenCalled(); + }); + + it('should close the current table details expansion', () => { + component.toggleExpandRow(component.expanded, true); + expect(component.expanded).toBeUndefined(); + expect(component.setExpandedRow.emit).toHaveBeenCalledWith(undefined); + expect(component.table.rowDetail.toggleExpandRow).toHaveBeenCalled(); + }); + }); }); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table/table.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table/table.component.ts index 9ab333c7a12e..7d9fb28a7080 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table/table.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table/table.component.ts @@ -63,6 +63,8 @@ export class TableComponent implements AfterContentChecked, OnInit, OnChanges, O mapTpl: TemplateRef; @ViewChild('truncateTpl', { static: true }) truncateTpl: TemplateRef; + @ViewChild('rowDetailsTpl', { static: true }) + rowDetailsTpl: TemplateRef; // This is the array with the items to be shown. @Input() @@ -94,6 +96,9 @@ export class TableComponent implements AfterContentChecked, OnInit, OnChanges, O // Page size to show. Set to 0 to show unlimited number of rows. @Input() limit? = 10; + // Has the row details? + @Input() + hasDetails = false; /** * Auto reload time in ms - per default every 5s @@ -118,6 +123,9 @@ export class TableComponent implements AfterContentChecked, OnInit, OnChanges, O // By default selected item details will be updated on table refresh, if data has changed @Input() updateSelectionOnRefresh: 'always' | 'never' | 'onChange' = 'onChange'; + // By default expanded item details will be updated on table refresh, if data has changed + @Input() + updateExpandedOnRefresh: 'always' | 'never' | 'onChange' = 'onChange'; @Input() autoSave = true; @@ -157,6 +165,9 @@ export class TableComponent implements AfterContentChecked, OnInit, OnChanges, O @Output() updateSelection = new EventEmitter(); + @Output() + setExpandedRow = new EventEmitter(); + /** * This should be defined if you need access to the applied column filters. * @@ -172,6 +183,11 @@ export class TableComponent implements AfterContentChecked, OnInit, OnChanges, O */ selection = new CdTableSelection(); + /** + * Use this variable to access the expanded row + */ + expanded: any = undefined; + tableColumns: CdTableColumn[]; icons = Icons; cellTemplates: { @@ -237,6 +253,7 @@ export class TableComponent implements AfterContentChecked, OnInit, OnChanges, O this.identifier = this.columns[0].prop + ''; } } + this.initUserConfig(); this.columns.forEach((c) => { if (c.cellTransformation) { @@ -250,6 +267,7 @@ export class TableComponent implements AfterContentChecked, OnInit, OnChanges, O } }); + this.initExpandCollapseColumn(); // If rows have details, add a column to expand or collapse the rows this.initCheckboxColumn(); this.filterHiddenColumns(); this.initColumnFilters(); @@ -367,6 +385,25 @@ export class TableComponent implements AfterContentChecked, OnInit, OnChanges, O } } + /** + * Add a column to expand and collapse the table row if it 'hasDetails' + */ + initExpandCollapseColumn() { + if (this.hasDetails) { + this.columns.unshift({ + prop: undefined, + resizeable: false, + sortable: false, + draggable: false, + isHidden: false, + canAutoResize: false, + cellClass: 'cd-datatable-expand-collapse', + width: 40, + cellTemplate: this.rowDetailsTpl + }); + } + } + filterHiddenColumns() { this.tableColumns = this.columns.filter((c) => !c.isHidden); } @@ -583,6 +620,7 @@ export class TableComponent implements AfterContentChecked, OnInit, OnChanges, O this.updateFilter(); this.reset(); this.updateSelected(); + this.updateExpanded(); } /** @@ -622,6 +660,22 @@ export class TableComponent implements AfterContentChecked, OnInit, OnChanges, O this.onSelect(this.selection); } + updateExpanded() { + if (_.isUndefined(this.expanded) || this.updateExpandedOnRefresh === 'never') { + return; + } + + const expandedId = this.expanded[this.identifier]; + const newExpanded = _.find(this.data, (row) => expandedId === row[this.identifier]); + + if (this.updateExpandedOnRefresh === 'onChange' && _.isEqual(this.expanded, newExpanded)) { + return; + } + + this.expanded = newExpanded; + this.setExpandedRow.emit(newExpanded); + } + onSelect($event: any) { this.selection.selected = $event['selected']; this.updateSelection.emit(_.clone(this.selection)); @@ -747,4 +801,18 @@ export class TableComponent implements AfterContentChecked, OnInit, OnChanges, O }; }; } + + toggleExpandRow(row: any, isExpanded: boolean) { + if (!isExpanded) { + // If current row isn't expanded, collapse others + this.expanded = row; + this.table.rowDetail.collapseAllRows(); + this.setExpandedRow.emit(row); + } else { + // If all rows are closed, emit undefined + this.expanded = undefined; + this.setExpandedRow.emit(undefined); + } + this.table.rowDetail.toggleExpandRow(row); + } } diff --git a/src/pybind/mgr/dashboard/frontend/src/styles/defaults.scss b/src/pybind/mgr/dashboard/frontend/src/styles/defaults.scss index 7865cb29a044..c6c853e6dd8a 100644 --- a/src/pybind/mgr/dashboard/frontend/src/styles/defaults.scss +++ b/src/pybind/mgr/dashboard/frontend/src/styles/defaults.scss @@ -2,6 +2,10 @@ @import 'vendor.variables'; +// Bootstrap defaults + +$gray-900: #212529 !default; + $screen-sm-min: 576px !default; $screen-md-min: 768px !default; $screen-lg-min: 992px !default;
Enabled{{ bucket.bucket_quota.enabled | booleanText }}{{ selection.bucket_quota.enabled | booleanText }}
Maximum sizeUnlimited - {{ bucket.bucket_quota.max_size | dimless }} + + {{ selection.bucket_quota.max_size | dimless }}
Maximum objectsUnlimited - {{ bucket.bucket_quota.max_objects }} + + {{ selection.bucket_quota.max_objects }}
Enabled{{ bucket.lock_enabled | booleanText }}{{ selection.lock_enabled | booleanText }}
Mode{{ bucket.lock_mode }}{{ selection.lock_mode }}
Days{{ bucket.lock_retention_period_days }}{{ selection.lock_retention_period_days }}
Years{{ bucket.lock_retention_period_years }}{{ selection.lock_retention_period_years }}